sftpserver.c 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704
  1. /*
  2. * Implement the SftpServer abstraction, in the 'live' form (i.e.
  3. * really operating on the Unix filesystem).
  4. */
  5. #include <assert.h>
  6. #include <stdio.h>
  7. #include <stdlib.h>
  8. #include <errno.h>
  9. #include <sys/stat.h>
  10. #include <sys/types.h>
  11. #include <sys/time.h>
  12. #include <fcntl.h>
  13. #include <unistd.h>
  14. #include <pwd.h>
  15. #include <grp.h>
  16. #include <dirent.h>
  17. #include <utime.h>
  18. #include "putty.h"
  19. #include "ssh.h"
  20. #include "ssh/server.h"
  21. #include "ssh/sftp.h"
  22. #include "tree234.h"
  23. typedef struct UnixSftpServer UnixSftpServer;
  24. struct UnixSftpServer {
  25. unsigned *fdseqs;
  26. bool *fdsopen;
  27. size_t fdsize;
  28. tree234 *dirhandles;
  29. int last_dirhandle_index;
  30. char handlekey[8];
  31. SftpServer srv;
  32. };
  33. struct uss_dirhandle {
  34. int index;
  35. DIR *dp;
  36. };
  37. #define USS_DIRHANDLE_SEQ (0xFFFFFFFFU)
  38. static int uss_dirhandle_cmp(void *av, void *bv)
  39. {
  40. struct uss_dirhandle *a = (struct uss_dirhandle *)av;
  41. struct uss_dirhandle *b = (struct uss_dirhandle *)bv;
  42. if (a->index < b->index)
  43. return -1;
  44. if (a->index > b->index)
  45. return +1;
  46. return 0;
  47. }
  48. static SftpServer *uss_new(const SftpServerVtable *vt)
  49. {
  50. UnixSftpServer *uss = snew(UnixSftpServer);
  51. memset(uss, 0, sizeof(UnixSftpServer));
  52. uss->dirhandles = newtree234(uss_dirhandle_cmp);
  53. uss->srv.vt = vt;
  54. make_unix_sftp_filehandle_key(uss->handlekey, sizeof(uss->handlekey));
  55. return &uss->srv;
  56. }
  57. static void uss_free(SftpServer *srv)
  58. {
  59. UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
  60. struct uss_dirhandle *udh;
  61. for (size_t i = 0; i < uss->fdsize; i++)
  62. if (uss->fdsopen[i])
  63. close(i);
  64. sfree(uss->fdseqs);
  65. while ((udh = delpos234(uss->dirhandles, 0)) != NULL) {
  66. closedir(udh->dp);
  67. sfree(udh);
  68. }
  69. sfree(uss);
  70. }
  71. static void uss_return_handle_raw(
  72. UnixSftpServer *uss, SftpReplyBuilder *reply, int index, unsigned seq)
  73. {
  74. unsigned char handlebuf[8];
  75. PUT_32BIT_MSB_FIRST(handlebuf, index);
  76. PUT_32BIT_MSB_FIRST(handlebuf + 4, seq);
  77. des_encrypt_xdmauth(uss->handlekey, handlebuf, 8);
  78. fxp_reply_handle(reply, make_ptrlen(handlebuf, 8));
  79. }
  80. static bool uss_decode_handle(
  81. UnixSftpServer *uss, ptrlen handle, int *index, unsigned *seq)
  82. {
  83. unsigned char handlebuf[8];
  84. if (handle.len != 8)
  85. return false;
  86. memcpy(handlebuf, handle.ptr, 8);
  87. des_decrypt_xdmauth(uss->handlekey, handlebuf, 8);
  88. *index = toint(GET_32BIT_MSB_FIRST(handlebuf));
  89. *seq = GET_32BIT_MSB_FIRST(handlebuf + 4);
  90. return true;
  91. }
  92. static void uss_return_new_handle(
  93. UnixSftpServer *uss, SftpReplyBuilder *reply, int fd)
  94. {
  95. assert(fd >= 0);
  96. if (fd >= uss->fdsize) {
  97. size_t old_size = uss->fdsize;
  98. sgrowarray(uss->fdseqs, uss->fdsize, fd);
  99. uss->fdsopen = sresize(uss->fdsopen, uss->fdsize, bool);
  100. while (old_size < uss->fdsize) {
  101. uss->fdseqs[old_size] = 0;
  102. uss->fdsopen[old_size] = false;
  103. old_size++;
  104. }
  105. }
  106. assert(!uss->fdsopen[fd]);
  107. uss->fdsopen[fd] = true;
  108. if (++uss->fdseqs[fd] == USS_DIRHANDLE_SEQ)
  109. uss->fdseqs[fd] = 0;
  110. uss_return_handle_raw(uss, reply, fd, uss->fdseqs[fd]);
  111. }
  112. static int uss_try_lookup_fd(UnixSftpServer *uss, ptrlen handle)
  113. {
  114. int fd;
  115. unsigned seq;
  116. if (!uss_decode_handle(uss, handle, &fd, &seq) ||
  117. fd < 0 || fd >= uss->fdsize ||
  118. !uss->fdsopen[fd] || uss->fdseqs[fd] != seq)
  119. return -1;
  120. return fd;
  121. }
  122. static int uss_lookup_fd(UnixSftpServer *uss, SftpReplyBuilder *reply,
  123. ptrlen handle)
  124. {
  125. int fd = uss_try_lookup_fd(uss, handle);
  126. if (fd < 0)
  127. fxp_reply_error(reply, SSH_FX_FAILURE, "invalid file handle");
  128. return fd;
  129. }
  130. static void uss_return_new_dirhandle(
  131. UnixSftpServer *uss, SftpReplyBuilder *reply, DIR *dp)
  132. {
  133. struct uss_dirhandle *udh = snew(struct uss_dirhandle);
  134. udh->index = uss->last_dirhandle_index++;
  135. udh->dp = dp;
  136. struct uss_dirhandle *added = add234(uss->dirhandles, udh);
  137. assert(added == udh);
  138. uss_return_handle_raw(uss, reply, udh->index, USS_DIRHANDLE_SEQ);
  139. }
  140. static struct uss_dirhandle *uss_try_lookup_dirhandle(
  141. UnixSftpServer *uss, ptrlen handle)
  142. {
  143. struct uss_dirhandle key, *udh;
  144. unsigned seq;
  145. if (!uss_decode_handle(uss, handle, &key.index, &seq) ||
  146. seq != USS_DIRHANDLE_SEQ ||
  147. (udh = find234(uss->dirhandles, &key, NULL)) == NULL)
  148. return NULL;
  149. return udh;
  150. }
  151. static struct uss_dirhandle *uss_lookup_dirhandle(
  152. UnixSftpServer *uss, SftpReplyBuilder *reply, ptrlen handle)
  153. {
  154. struct uss_dirhandle *udh = uss_try_lookup_dirhandle(uss, handle);
  155. if (!udh)
  156. fxp_reply_error(reply, SSH_FX_FAILURE, "invalid file handle");
  157. return udh;
  158. }
  159. static void uss_error(UnixSftpServer *uss, SftpReplyBuilder *reply)
  160. {
  161. unsigned code = SSH_FX_FAILURE;
  162. switch (errno) {
  163. case ENOENT:
  164. code = SSH_FX_NO_SUCH_FILE;
  165. break;
  166. case EPERM:
  167. code = SSH_FX_PERMISSION_DENIED;
  168. break;
  169. }
  170. fxp_reply_error(reply, code, strerror(errno));
  171. }
  172. static void uss_realpath(SftpServer *srv, SftpReplyBuilder *reply,
  173. ptrlen path)
  174. {
  175. UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
  176. char *inpath = mkstr(path);
  177. char *outpath = realpath(inpath, NULL);
  178. free(inpath);
  179. if (!outpath) {
  180. uss_error(uss, reply);
  181. } else {
  182. fxp_reply_simple_name(reply, ptrlen_from_asciz(outpath));
  183. free(outpath);
  184. }
  185. }
  186. static void uss_open(SftpServer *srv, SftpReplyBuilder *reply,
  187. ptrlen path, unsigned flags, struct fxp_attrs attrs)
  188. {
  189. UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
  190. int openflags = 0;
  191. if (!((SSH_FXF_READ | SSH_FXF_WRITE) &~ flags))
  192. openflags |= O_RDWR;
  193. else if (flags & SSH_FXF_WRITE)
  194. openflags |= O_WRONLY;
  195. else if (flags & SSH_FXF_READ)
  196. openflags |= O_RDONLY;
  197. if (flags & SSH_FXF_APPEND)
  198. openflags |= O_APPEND;
  199. if (flags & SSH_FXF_CREAT)
  200. openflags |= O_CREAT;
  201. if (flags & SSH_FXF_TRUNC)
  202. openflags |= O_TRUNC;
  203. if (flags & SSH_FXF_EXCL)
  204. openflags |= O_EXCL;
  205. char *pathstr = mkstr(path);
  206. int fd = open(pathstr, openflags, GET_PERMISSIONS(attrs, 0777));
  207. free(pathstr);
  208. if (fd < 0) {
  209. uss_error(uss, reply);
  210. } else {
  211. uss_return_new_handle(uss, reply, fd);
  212. }
  213. }
  214. static void uss_opendir(SftpServer *srv, SftpReplyBuilder *reply,
  215. ptrlen path)
  216. {
  217. UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
  218. char *pathstr = mkstr(path);
  219. DIR *dp = opendir(pathstr);
  220. free(pathstr);
  221. if (!dp) {
  222. uss_error(uss, reply);
  223. } else {
  224. uss_return_new_dirhandle(uss, reply, dp);
  225. }
  226. }
  227. static void uss_close(SftpServer *srv, SftpReplyBuilder *reply,
  228. ptrlen handle)
  229. {
  230. UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
  231. int fd;
  232. struct uss_dirhandle *udh;
  233. if ((udh = uss_try_lookup_dirhandle(uss, handle)) != NULL) {
  234. closedir(udh->dp);
  235. del234(uss->dirhandles, udh);
  236. sfree(udh);
  237. fxp_reply_ok(reply);
  238. } else if ((fd = uss_lookup_fd(uss, reply, handle)) >= 0) {
  239. close(fd);
  240. assert(0 <= fd && fd <= uss->fdsize);
  241. uss->fdsopen[fd] = false;
  242. fxp_reply_ok(reply);
  243. }
  244. /* if both failed, uss_lookup_fd will have filled in an error response */
  245. }
  246. static void uss_mkdir(SftpServer *srv, SftpReplyBuilder *reply,
  247. ptrlen path, struct fxp_attrs attrs)
  248. {
  249. UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
  250. char *pathstr = mkstr(path);
  251. int status = mkdir(pathstr, GET_PERMISSIONS(attrs, 0777));
  252. free(pathstr);
  253. if (status < 0) {
  254. uss_error(uss, reply);
  255. } else {
  256. fxp_reply_ok(reply);
  257. }
  258. }
  259. static void uss_rmdir(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path)
  260. {
  261. UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
  262. char *pathstr = mkstr(path);
  263. int status = rmdir(pathstr);
  264. free(pathstr);
  265. if (status < 0) {
  266. uss_error(uss, reply);
  267. } else {
  268. fxp_reply_ok(reply);
  269. }
  270. }
  271. static void uss_remove(SftpServer *srv, SftpReplyBuilder *reply,
  272. ptrlen path)
  273. {
  274. UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
  275. char *pathstr = mkstr(path);
  276. int status = unlink(pathstr);
  277. free(pathstr);
  278. if (status < 0) {
  279. uss_error(uss, reply);
  280. } else {
  281. fxp_reply_ok(reply);
  282. }
  283. }
  284. static void uss_rename(SftpServer *srv, SftpReplyBuilder *reply,
  285. ptrlen srcpath, ptrlen dstpath)
  286. {
  287. UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
  288. char *srcstr = mkstr(srcpath), *dststr = mkstr(dstpath);
  289. int status = rename(srcstr, dststr);
  290. free(srcstr);
  291. free(dststr);
  292. if (status < 0) {
  293. uss_error(uss, reply);
  294. } else {
  295. fxp_reply_ok(reply);
  296. }
  297. }
  298. static struct fxp_attrs uss_translate_struct_stat(const struct stat *st)
  299. {
  300. struct fxp_attrs attrs;
  301. attrs.flags = (SSH_FILEXFER_ATTR_SIZE |
  302. SSH_FILEXFER_ATTR_PERMISSIONS |
  303. SSH_FILEXFER_ATTR_UIDGID |
  304. SSH_FILEXFER_ATTR_ACMODTIME);
  305. attrs.size = st->st_size;
  306. attrs.permissions = st->st_mode;
  307. attrs.uid = st->st_uid;
  308. attrs.gid = st->st_gid;
  309. attrs.atime = st->st_atime;
  310. attrs.mtime = st->st_mtime;
  311. return attrs;
  312. }
  313. static void uss_reply_struct_stat(SftpReplyBuilder *reply,
  314. const struct stat *st)
  315. {
  316. fxp_reply_attrs(reply, uss_translate_struct_stat(st));
  317. }
  318. static void uss_stat(SftpServer *srv, SftpReplyBuilder *reply,
  319. ptrlen path, bool follow_symlinks)
  320. {
  321. UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
  322. struct stat st;
  323. char *pathstr = mkstr(path);
  324. int status = (follow_symlinks ? stat : lstat) (pathstr, &st);
  325. free(pathstr);
  326. if (status < 0) {
  327. uss_error(uss, reply);
  328. } else {
  329. uss_reply_struct_stat(reply, &st);
  330. }
  331. }
  332. static void uss_fstat(SftpServer *srv, SftpReplyBuilder *reply,
  333. ptrlen handle)
  334. {
  335. UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
  336. struct stat st;
  337. int fd;
  338. if ((fd = uss_lookup_fd(uss, reply, handle)) < 0)
  339. return;
  340. int status = fstat(fd, &st);
  341. if (status < 0) {
  342. uss_error(uss, reply);
  343. } else {
  344. uss_reply_struct_stat(reply, &st);
  345. }
  346. }
  347. #if !HAVE_FUTIMES
  348. static inline int futimes(int fd, const struct timeval tv[2])
  349. {
  350. /* If the OS doesn't support futimes(3) then we have to pretend it
  351. * always returns failure */
  352. errno = EINVAL;
  353. return -1;
  354. }
  355. #endif
  356. /*
  357. * The guts of setstat and fsetstat, macroised so that they can call
  358. * fchown(fd,...) or chown(path,...) depending on parameters.
  359. */
  360. #define SETSTAT_GUTS(api_prefix, api_arg, attrs, success) do \
  361. { \
  362. if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) \
  363. if (api_prefix(truncate)(api_arg, attrs.size) < 0) \
  364. success = false; \
  365. if (attrs.flags & SSH_FILEXFER_ATTR_UIDGID) \
  366. if (api_prefix(chown)(api_arg, attrs.uid, attrs.gid) < 0) \
  367. success = false; \
  368. if (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) \
  369. if (api_prefix(chmod)(api_arg, attrs.permissions) < 0) \
  370. success = false; \
  371. if (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME) { \
  372. struct timeval tv[2]; \
  373. tv[0].tv_sec = attrs.atime; \
  374. tv[1].tv_sec = attrs.mtime; \
  375. tv[0].tv_usec = tv[1].tv_usec = 0; \
  376. if (api_prefix(utimes)(api_arg, tv) < 0) \
  377. success = false; \
  378. } \
  379. } while (0)
  380. #define PATH_PREFIX(func) func
  381. #define FD_PREFIX(func) f ## func
  382. static void uss_setstat(SftpServer *srv, SftpReplyBuilder *reply,
  383. ptrlen path, struct fxp_attrs attrs)
  384. {
  385. UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
  386. char *pathstr = mkstr(path);
  387. bool success = true;
  388. SETSTAT_GUTS(PATH_PREFIX, pathstr, attrs, success);
  389. free(pathstr);
  390. if (!success) {
  391. uss_error(uss, reply);
  392. } else {
  393. fxp_reply_ok(reply);
  394. }
  395. }
  396. static void uss_fsetstat(SftpServer *srv, SftpReplyBuilder *reply,
  397. ptrlen handle, struct fxp_attrs attrs)
  398. {
  399. UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
  400. int fd;
  401. if ((fd = uss_lookup_fd(uss, reply, handle)) < 0)
  402. return;
  403. bool success = true;
  404. SETSTAT_GUTS(FD_PREFIX, fd, attrs, success);
  405. if (!success) {
  406. uss_error(uss, reply);
  407. } else {
  408. fxp_reply_ok(reply);
  409. }
  410. }
  411. static void uss_read(SftpServer *srv, SftpReplyBuilder *reply,
  412. ptrlen handle, uint64_t offset, unsigned length)
  413. {
  414. UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
  415. int fd;
  416. char *buf;
  417. if ((fd = uss_lookup_fd(uss, reply, handle)) < 0)
  418. return;
  419. if ((buf = malloc(length)) == NULL) {
  420. /* A rare case in which I bother to check malloc failure,
  421. * because in this case we can localise the problem easily by
  422. * turning it into a failure response from this one sftp
  423. * request */
  424. fxp_reply_error(reply, SSH_FX_FAILURE,
  425. "Out of memory for read buffer");
  426. return;
  427. }
  428. char *p = buf;
  429. int status = lseek(fd, offset, SEEK_SET);
  430. if (status >= 0 || errno == ESPIPE) {
  431. bool seekable = (status >= 0);
  432. while (length > 0) {
  433. status = read(fd, p, length);
  434. if (status <= 0)
  435. break;
  436. unsigned bytes_read = status;
  437. assert(bytes_read <= length);
  438. length -= bytes_read;
  439. p += bytes_read;
  440. if (!seekable) {
  441. /*
  442. * If the seek failed because the file is fundamentally
  443. * not a seekable kind of thing, abandon this loop after
  444. * one attempt, i.e. we just read whatever we could get
  445. * and we don't mind returning a short buffer.
  446. */
  447. }
  448. }
  449. }
  450. if (status < 0) {
  451. uss_error(uss, reply);
  452. } else if (p == buf) {
  453. fxp_reply_error(reply, SSH_FX_EOF, "End of file");
  454. } else {
  455. fxp_reply_data(reply, make_ptrlen(buf, p - buf));
  456. }
  457. free(buf);
  458. }
  459. static void uss_write(SftpServer *srv, SftpReplyBuilder *reply,
  460. ptrlen handle, uint64_t offset, ptrlen data)
  461. {
  462. UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
  463. int fd;
  464. if ((fd = uss_lookup_fd(uss, reply, handle)) < 0)
  465. return;
  466. const char *p = data.ptr;
  467. unsigned length = data.len;
  468. int status = lseek(fd, offset, SEEK_SET);
  469. if (status >= 0 || errno == ESPIPE) {
  470. while (length > 0) {
  471. status = write(fd, p, length);
  472. assert(status != 0);
  473. if (status < 0)
  474. break;
  475. unsigned bytes_written = status;
  476. assert(bytes_written <= length);
  477. length -= bytes_written;
  478. p += bytes_written;
  479. }
  480. }
  481. if (status < 0) {
  482. uss_error(uss, reply);
  483. } else {
  484. fxp_reply_ok(reply);
  485. }
  486. }
  487. static void uss_readdir(SftpServer *srv, SftpReplyBuilder *reply,
  488. ptrlen handle, int max_entries, bool omit_longname)
  489. {
  490. UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
  491. struct dirent *de;
  492. struct uss_dirhandle *udh;
  493. if ((udh = uss_lookup_dirhandle(uss, reply, handle)) == NULL)
  494. return;
  495. errno = 0;
  496. de = readdir(udh->dp);
  497. if (!de) {
  498. if (errno == 0) {
  499. fxp_reply_error(reply, SSH_FX_EOF, "End of directory");
  500. } else {
  501. uss_error(uss, reply);
  502. }
  503. } else {
  504. ptrlen longname = PTRLEN_LITERAL("");
  505. char *longnamebuf = NULL;
  506. struct fxp_attrs attrs = no_attrs;
  507. #if HAVE_FSTATAT && HAVE_DIRFD
  508. struct stat st;
  509. if (!fstatat(dirfd(udh->dp), de->d_name, &st, AT_SYMLINK_NOFOLLOW)) {
  510. char perms[11], *uidbuf = NULL, *gidbuf = NULL;
  511. struct passwd *pwd;
  512. struct group *grp;
  513. const char *user, *group;
  514. struct tm tm;
  515. attrs = uss_translate_struct_stat(&st);
  516. if (!omit_longname) {
  517. strcpy(perms, "----------");
  518. switch (st.st_mode & S_IFMT) {
  519. case S_IFBLK: perms[0] = 'b'; break;
  520. case S_IFCHR: perms[0] = 'c'; break;
  521. case S_IFDIR: perms[0] = 'd'; break;
  522. case S_IFIFO: perms[0] = 'p'; break;
  523. case S_IFLNK: perms[0] = 'l'; break;
  524. case S_IFSOCK: perms[0] = 's'; break;
  525. }
  526. if (st.st_mode & S_IRUSR)
  527. perms[1] = 'r';
  528. if (st.st_mode & S_IWUSR)
  529. perms[2] = 'w';
  530. if (st.st_mode & S_IXUSR)
  531. perms[3] = (st.st_mode & S_ISUID ? 's' : 'x');
  532. else
  533. perms[3] = (st.st_mode & S_ISUID ? 'S' : '-');
  534. if (st.st_mode & S_IRGRP)
  535. perms[4] = 'r';
  536. if (st.st_mode & S_IWGRP)
  537. perms[5] = 'w';
  538. if (st.st_mode & S_IXGRP)
  539. perms[6] = (st.st_mode & S_ISGID ? 's' : 'x');
  540. else
  541. perms[6] = (st.st_mode & S_ISGID ? 'S' : '-');
  542. if (st.st_mode & S_IROTH)
  543. perms[7] = 'r';
  544. if (st.st_mode & S_IWOTH)
  545. perms[8] = 'w';
  546. if (st.st_mode & S_IXOTH)
  547. perms[9] = 'x';
  548. if ((pwd = getpwuid(st.st_uid)) != NULL)
  549. user = pwd->pw_name;
  550. else
  551. user = uidbuf = dupprintf("%u", (unsigned)st.st_uid);
  552. if ((grp = getgrgid(st.st_gid)) != NULL)
  553. group = grp->gr_name;
  554. else
  555. group = gidbuf = dupprintf("%u", (unsigned)st.st_gid);
  556. tm = *localtime(&st.st_mtime);
  557. longnamebuf = dupprintf(
  558. "%s %3u %-8s %-8s %8"PRIuMAX" %.3s %2d %02d:%02d %s",
  559. perms, (unsigned)st.st_nlink, user, group,
  560. (uintmax_t)st.st_size,
  561. (&"JanFebMarAprMayJunJulAugSepOctNovDec"[3*tm.tm_mon]),
  562. tm.tm_mday, tm.tm_hour, tm.tm_min, de->d_name);
  563. longname = ptrlen_from_asciz(longnamebuf);
  564. sfree(uidbuf);
  565. sfree(gidbuf);
  566. }
  567. }
  568. #endif
  569. /* FIXME: be able to return more than one, in which case we
  570. * must also check max_entries */
  571. fxp_reply_name_count(reply, 1);
  572. fxp_reply_full_name(reply, ptrlen_from_asciz(de->d_name),
  573. longname, attrs);
  574. sfree(longnamebuf);
  575. }
  576. }
  577. const SftpServerVtable unix_live_sftpserver_vt = {
  578. .new = uss_new,
  579. .free = uss_free,
  580. .realpath = uss_realpath,
  581. .open = uss_open,
  582. .opendir = uss_opendir,
  583. .close = uss_close,
  584. .mkdir = uss_mkdir,
  585. .rmdir = uss_rmdir,
  586. .remove = uss_remove,
  587. .rename = uss_rename,
  588. .stat = uss_stat,
  589. .fstat = uss_fstat,
  590. .setstat = uss_setstat,
  591. .fsetstat = uss_fsetstat,
  592. .read = uss_read,
  593. .write = uss_write,
  594. .readdir = uss_readdir,
  595. };