filesystem.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  1. // SPDX-License-Identifier: GPL-2.0-or-later
  2. /*
  3. *
  4. * OBEX Server
  5. *
  6. * Copyright (C) 2009-2010 Intel Corporation
  7. * Copyright (C) 2007-2010 Marcel Holtmann <marcel@holtmann.org>
  8. *
  9. *
  10. */
  11. #ifdef HAVE_CONFIG_H
  12. #include <config.h>
  13. #endif
  14. #define _GNU_SOURCE
  15. #include <stdio.h>
  16. #include <errno.h>
  17. #include <stdlib.h>
  18. #include <string.h>
  19. #include <unistd.h>
  20. #include <dirent.h>
  21. #include <sys/types.h>
  22. #include <sys/stat.h>
  23. #include <sys/statvfs.h>
  24. #include <sys/sendfile.h>
  25. #include <fcntl.h>
  26. #include <sys/wait.h>
  27. #include <inttypes.h>
  28. #include <glib.h>
  29. #include "obexd/src/obexd.h"
  30. #include "obexd/src/plugin.h"
  31. #include "obexd/src/log.h"
  32. #include "obexd/src/mimetype.h"
  33. #include "filesystem.h"
  34. #define EOL_CHARS "\n"
  35. #define FL_VERSION "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" EOL_CHARS
  36. #define FL_TYPE "<!DOCTYPE folder-listing SYSTEM \"obex-folder-listing.dtd\">" EOL_CHARS
  37. #define FL_TYPE_PCSUITE "<!DOCTYPE folder-listing SYSTEM \"obex-folder-listing.dtd\"" EOL_CHARS \
  38. " [ <!ATTLIST folder mem-type CDATA #IMPLIED> ]>" EOL_CHARS
  39. #define FL_BODY_BEGIN "<folder-listing version=\"1.0\">" EOL_CHARS
  40. #define FL_BODY_END "</folder-listing>" EOL_CHARS
  41. #define FL_PARENT_FOLDER_ELEMENT "<parent-folder/>" EOL_CHARS
  42. #define FL_FILE_ELEMENT "<file name=\"%s\" size=\"%" PRIu64 "\"" \
  43. " %s accessed=\"%s\" " \
  44. "modified=\"%s\" created=\"%s\"/>" EOL_CHARS
  45. #define FL_FOLDER_ELEMENT "<folder name=\"%s\" %s accessed=\"%s\" " \
  46. "modified=\"%s\" created=\"%s\"/>" EOL_CHARS
  47. #define FL_FOLDER_ELEMENT_PCSUITE "<folder name=\"%s\" %s accessed=\"%s\"" \
  48. " modified=\"%s\" mem-type=\"DEV\"" \
  49. " created=\"%s\"/>" EOL_CHARS
  50. #define FTP_TARGET_SIZE 16
  51. static const uint8_t FTP_TARGET[FTP_TARGET_SIZE] = {
  52. 0xF9, 0xEC, 0x7B, 0xC4, 0x95, 0x3C, 0x11, 0xD2,
  53. 0x98, 0x4E, 0x52, 0x54, 0x00, 0xDC, 0x9E, 0x09 };
  54. #define PCSUITE_WHO_SIZE 8
  55. static const uint8_t PCSUITE_WHO[PCSUITE_WHO_SIZE] = {
  56. 'P', 'C', ' ', 'S', 'u', 'i', 't', 'e' };
  57. gboolean is_filename(const char *name)
  58. {
  59. if (strchr(name, '/'))
  60. return FALSE;
  61. if (strcmp(name, ".") == 0)
  62. return FALSE;
  63. if (strcmp(name, "..") == 0)
  64. return FALSE;
  65. return TRUE;
  66. }
  67. int verify_path(const char *path)
  68. {
  69. char *t;
  70. int ret = 0;
  71. if (obex_option_symlinks())
  72. return 0;
  73. t = realpath(path, NULL);
  74. if (t == NULL)
  75. return -errno;
  76. if (!g_str_has_prefix(t, obex_option_root_folder()))
  77. ret = -EPERM;
  78. free(t);
  79. return ret;
  80. }
  81. static char *file_stat_line(char *filename, struct stat *fstat,
  82. struct stat *dstat, gboolean root,
  83. gboolean pcsuite)
  84. {
  85. char perm[51], atime[18], ctime[18], mtime[18];
  86. char *escaped, *ret = NULL;
  87. snprintf(perm, 50, "user-perm=\"%s%s%s\" group-perm=\"%s%s%s\" "
  88. "other-perm=\"%s%s%s\"",
  89. (fstat->st_mode & 0400 ? "R" : ""),
  90. (fstat->st_mode & 0200 ? "W" : ""),
  91. (dstat->st_mode & 0200 ? "D" : ""),
  92. (fstat->st_mode & 0040 ? "R" : ""),
  93. (fstat->st_mode & 0020 ? "W" : ""),
  94. (dstat->st_mode & 0020 ? "D" : ""),
  95. (fstat->st_mode & 0004 ? "R" : ""),
  96. (fstat->st_mode & 0002 ? "W" : ""),
  97. (dstat->st_mode & 0002 ? "D" : ""));
  98. strftime(atime, 17, "%Y%m%dT%H%M%SZ", gmtime(&fstat->st_atime));
  99. strftime(ctime, 17, "%Y%m%dT%H%M%SZ", gmtime(&fstat->st_ctime));
  100. strftime(mtime, 17, "%Y%m%dT%H%M%SZ", gmtime(&fstat->st_mtime));
  101. escaped = g_markup_escape_text(filename, -1);
  102. if (S_ISDIR(fstat->st_mode)) {
  103. if (pcsuite && root && g_str_equal(filename, "Data"))
  104. ret = g_strdup_printf(FL_FOLDER_ELEMENT_PCSUITE,
  105. escaped, perm, atime,
  106. mtime, ctime);
  107. else
  108. ret = g_strdup_printf(FL_FOLDER_ELEMENT, escaped, perm,
  109. atime, mtime, ctime);
  110. } else if (S_ISREG(fstat->st_mode))
  111. ret = g_strdup_printf(FL_FILE_ELEMENT, escaped,
  112. (uint64_t) fstat->st_size,
  113. perm, atime, mtime, ctime);
  114. g_free(escaped);
  115. return ret;
  116. }
  117. static void *filesystem_open(const char *name, int oflag, mode_t mode,
  118. void *context, size_t *size, int *err)
  119. {
  120. struct stat stats;
  121. struct statvfs buf;
  122. int fd, ret;
  123. uint64_t avail;
  124. fd = open(name, oflag, mode);
  125. if (fd < 0) {
  126. if (err)
  127. *err = -errno;
  128. return NULL;
  129. }
  130. if (fstat(fd, &stats) < 0) {
  131. if (err)
  132. *err = -errno;
  133. goto failed;
  134. }
  135. ret = verify_path(name);
  136. if (ret < 0) {
  137. if (err)
  138. *err = ret;
  139. goto failed;
  140. }
  141. if (oflag == O_RDONLY) {
  142. if (size)
  143. *size = stats.st_size;
  144. goto done;
  145. }
  146. if (fstatvfs(fd, &buf) < 0) {
  147. if (err)
  148. *err = -errno;
  149. goto failed;
  150. }
  151. if (size == NULL)
  152. goto done;
  153. avail = (uint64_t) buf.f_bsize * buf.f_bavail;
  154. if (avail < *size) {
  155. if (err)
  156. *err = -ENOSPC;
  157. goto failed;
  158. }
  159. done:
  160. if (err)
  161. *err = 0;
  162. return GINT_TO_POINTER(fd);
  163. failed:
  164. close(fd);
  165. return NULL;
  166. }
  167. static int filesystem_close(void *object)
  168. {
  169. if (close(GPOINTER_TO_INT(object)) < 0)
  170. return -errno;
  171. return 0;
  172. }
  173. static ssize_t filesystem_read(void *object, void *buf, size_t count)
  174. {
  175. ssize_t ret;
  176. ret = read(GPOINTER_TO_INT(object), buf, count);
  177. if (ret < 0)
  178. return -errno;
  179. return ret;
  180. }
  181. static ssize_t filesystem_write(void *object, const void *buf, size_t count)
  182. {
  183. ssize_t ret;
  184. ret = write(GPOINTER_TO_INT(object), buf, count);
  185. if (ret < 0)
  186. return -errno;
  187. return ret;
  188. }
  189. static int filesystem_rename(const char *name, const char *destname)
  190. {
  191. int ret;
  192. ret = rename(name, destname);
  193. if (ret < 0) {
  194. error("rename(%s, %s): %s (%d)", name, destname,
  195. strerror(errno), errno);
  196. return -errno;
  197. }
  198. return ret;
  199. }
  200. static int sendfile_async(int out_fd, int in_fd, off_t *offset, size_t count)
  201. {
  202. int pid;
  203. /* Run sendfile on child process */
  204. pid = fork();
  205. switch (pid) {
  206. case 0:
  207. break;
  208. case -1:
  209. error("fork() %s (%d)", strerror(errno), errno);
  210. return -errno;
  211. default:
  212. DBG("child %d forked", pid);
  213. return pid;
  214. }
  215. /* At child */
  216. if (sendfile(out_fd, in_fd, offset, count) < 0)
  217. error("sendfile(): %s (%d)", strerror(errno), errno);
  218. close(in_fd);
  219. close(out_fd);
  220. exit(errno);
  221. }
  222. static int filesystem_copy(const char *name, const char *destname)
  223. {
  224. void *in, *out;
  225. ssize_t ret;
  226. size_t size;
  227. struct stat st;
  228. int in_fd, out_fd, err;
  229. in = filesystem_open(name, O_RDONLY, 0, NULL, &size, &err);
  230. if (in == NULL) {
  231. error("open(%s): %s (%d)", name, strerror(-err), -err);
  232. return -err;
  233. }
  234. in_fd = GPOINTER_TO_INT(in);
  235. ret = fstat(in_fd, &st);
  236. if (ret < 0) {
  237. error("stat(%s): %s (%d)", name, strerror(errno), errno);
  238. return -errno;
  239. }
  240. out = filesystem_open(destname, O_WRONLY | O_CREAT | O_TRUNC,
  241. st.st_mode, NULL, &size, &err);
  242. if (out == NULL) {
  243. error("open(%s): %s (%d)", destname, strerror(-err), -err);
  244. filesystem_close(in);
  245. return -errno;
  246. }
  247. out_fd = GPOINTER_TO_INT(out);
  248. /* Check if sendfile is supported */
  249. ret = sendfile(out_fd, in_fd, NULL, 0);
  250. if (ret < 0) {
  251. ret = -errno;
  252. error("sendfile: %s (%zd)", strerror(-ret), -ret);
  253. goto done;
  254. }
  255. ret = sendfile_async(out_fd, in_fd, NULL, st.st_size);
  256. if (ret < 0)
  257. goto done;
  258. return 0;
  259. done:
  260. filesystem_close(in);
  261. filesystem_close(out);
  262. return ret;
  263. }
  264. struct capability_object {
  265. int pid;
  266. int output;
  267. int err;
  268. gboolean aborted;
  269. GString *buffer;
  270. };
  271. static void script_exited(GPid pid, int status, void *data)
  272. {
  273. struct capability_object *object = data;
  274. char buf[128];
  275. object->pid = -1;
  276. DBG("pid: %d status: %d", pid, status);
  277. g_spawn_close_pid(pid);
  278. /* free the object if aborted */
  279. if (object->aborted) {
  280. if (object->buffer != NULL)
  281. g_string_free(object->buffer, TRUE);
  282. g_free(object);
  283. return;
  284. }
  285. if (WEXITSTATUS(status) != EXIT_SUCCESS) {
  286. memset(buf, 0, sizeof(buf));
  287. if (read(object->err, buf, sizeof(buf)) > 0)
  288. error("%s", buf);
  289. obex_object_set_io_flags(data, G_IO_ERR, -EPERM);
  290. } else
  291. obex_object_set_io_flags(data, G_IO_IN, 0);
  292. }
  293. static int capability_exec(const char **argv, int *output, int *err)
  294. {
  295. GError *gerr = NULL;
  296. int pid;
  297. GSpawnFlags flags = G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH;
  298. if (!g_spawn_async_with_pipes(NULL, (char **) argv, NULL, flags, NULL,
  299. NULL, &pid, NULL, output, err, &gerr)) {
  300. error("%s", gerr->message);
  301. g_error_free(gerr);
  302. return -EPERM;
  303. }
  304. DBG("executing %s pid %d", argv[0], pid);
  305. return pid;
  306. }
  307. static void *capability_open(const char *name, int oflag, mode_t mode,
  308. void *context, size_t *size, int *err)
  309. {
  310. struct capability_object *object = NULL;
  311. char *buf;
  312. const char *argv[2];
  313. if (oflag != O_RDONLY)
  314. goto fail;
  315. object = g_new0(struct capability_object, 1);
  316. object->pid = -1;
  317. object->output = -1;
  318. object->err = -1;
  319. if (name[0] != '!') {
  320. GError *gerr = NULL;
  321. gboolean ret;
  322. ret = g_file_get_contents(name, &buf, NULL, &gerr);
  323. if (ret == FALSE) {
  324. error("%s", gerr->message);
  325. g_error_free(gerr);
  326. goto fail;
  327. }
  328. object->buffer = g_string_new(buf);
  329. if (size)
  330. *size = object->buffer->len;
  331. goto done;
  332. }
  333. argv[0] = &name[1];
  334. argv[1] = NULL;
  335. object->pid = capability_exec(argv, &object->output, &object->err);
  336. if (object->pid < 0)
  337. goto fail;
  338. /* Watch cannot be removed while the process is still running */
  339. g_child_watch_add(object->pid, script_exited, object);
  340. done:
  341. if (err)
  342. *err = 0;
  343. return object;
  344. fail:
  345. if (err)
  346. *err = -EPERM;
  347. g_free(object);
  348. return NULL;
  349. }
  350. static GString *append_pcsuite_preamble(GString *object)
  351. {
  352. return g_string_append(object, FL_TYPE_PCSUITE);
  353. }
  354. static GString *append_folder_preamble(GString *object)
  355. {
  356. return g_string_append(object, FL_TYPE);
  357. }
  358. static GString *append_listing(GString *object, const char *name,
  359. gboolean pcsuite, size_t *size, int *err)
  360. {
  361. struct stat fstat, dstat;
  362. struct dirent *ep;
  363. DIR *dp;
  364. gboolean root;
  365. int ret;
  366. root = g_str_equal(name, obex_option_root_folder());
  367. dp = opendir(name);
  368. if (dp == NULL) {
  369. if (err)
  370. *err = -ENOENT;
  371. goto failed;
  372. }
  373. if (!root)
  374. object = g_string_append(object, FL_PARENT_FOLDER_ELEMENT);
  375. ret = verify_path(name);
  376. if (ret < 0) {
  377. *err = ret;
  378. goto failed;
  379. }
  380. ret = stat(name, &dstat);
  381. if (ret < 0) {
  382. if (err)
  383. *err = -errno;
  384. goto failed;
  385. }
  386. while ((ep = readdir(dp))) {
  387. char *filename;
  388. char *fullname;
  389. char *line;
  390. if (ep->d_name[0] == '.')
  391. continue;
  392. filename = g_filename_to_utf8(ep->d_name, -1, NULL, NULL, NULL);
  393. if (filename == NULL) {
  394. error("g_filename_to_utf8: invalid filename");
  395. continue;
  396. }
  397. fullname = g_build_filename(name, ep->d_name, NULL);
  398. ret = stat(fullname, &fstat);
  399. if (ret < 0) {
  400. DBG("stat: %s(%d)", strerror(errno), errno);
  401. g_free(filename);
  402. g_free(fullname);
  403. continue;
  404. }
  405. g_free(fullname);
  406. line = file_stat_line(filename, &fstat, &dstat, root, FALSE);
  407. if (line == NULL) {
  408. g_free(filename);
  409. continue;
  410. }
  411. g_free(filename);
  412. object = g_string_append(object, line);
  413. g_free(line);
  414. }
  415. closedir(dp);
  416. object = g_string_append(object, FL_BODY_END);
  417. if (size)
  418. *size = object->len;
  419. if (err)
  420. *err = 0;
  421. return object;
  422. failed:
  423. if (dp)
  424. closedir(dp);
  425. g_string_free(object, TRUE);
  426. return NULL;
  427. }
  428. static void *folder_open(const char *name, int oflag, mode_t mode,
  429. void *context, size_t *size, int *err)
  430. {
  431. GString *object;
  432. object = g_string_new(FL_VERSION);
  433. object = append_folder_preamble(object);
  434. object = g_string_append(object, FL_BODY_BEGIN);
  435. return append_listing(object, name, FALSE, size, err);
  436. }
  437. static void *pcsuite_open(const char *name, int oflag, mode_t mode,
  438. void *context, size_t *size, int *err)
  439. {
  440. GString *object;
  441. object = g_string_new(FL_VERSION);
  442. object = append_pcsuite_preamble(object);
  443. object = g_string_append(object, FL_BODY_BEGIN);
  444. return append_listing(object, name, TRUE, size, err);
  445. }
  446. static int string_free(void *object)
  447. {
  448. GString *string = object;
  449. g_string_free(string, TRUE);
  450. return 0;
  451. }
  452. ssize_t string_read(void *object, void *buf, size_t count)
  453. {
  454. GString *string = object;
  455. ssize_t len;
  456. if (string->len == 0)
  457. return 0;
  458. len = MIN(string->len, count);
  459. memcpy(buf, string->str, len);
  460. g_string_erase(string, 0, len);
  461. return len;
  462. }
  463. static ssize_t folder_read(void *object, void *buf, size_t count)
  464. {
  465. return string_read(object, buf, count);
  466. }
  467. static ssize_t capability_read(void *object, void *buf, size_t count)
  468. {
  469. struct capability_object *obj = object;
  470. if (obj->buffer)
  471. return string_read(obj->buffer, buf, count);
  472. if (obj->pid >= 0)
  473. return -EAGAIN;
  474. return read(obj->output, buf, count);
  475. }
  476. static int capability_close(void *object)
  477. {
  478. struct capability_object *obj = object;
  479. int err = 0;
  480. if (obj->pid < 0)
  481. goto done;
  482. DBG("kill: pid %d", obj->pid);
  483. err = kill(obj->pid, SIGTERM);
  484. if (err < 0) {
  485. err = -errno;
  486. error("kill: %s (%d)", strerror(-err), -err);
  487. goto done;
  488. }
  489. obj->aborted = TRUE;
  490. return 0;
  491. done:
  492. if (obj->buffer != NULL)
  493. g_string_free(obj->buffer, TRUE);
  494. g_free(obj);
  495. return err;
  496. }
  497. static struct obex_mime_type_driver file = {
  498. .open = filesystem_open,
  499. .close = filesystem_close,
  500. .read = filesystem_read,
  501. .write = filesystem_write,
  502. .remove = remove,
  503. .move = filesystem_rename,
  504. .copy = filesystem_copy,
  505. };
  506. static struct obex_mime_type_driver capability = {
  507. .target = FTP_TARGET,
  508. .target_size = FTP_TARGET_SIZE,
  509. .mimetype = "x-obex/capability",
  510. .open = capability_open,
  511. .close = capability_close,
  512. .read = capability_read,
  513. };
  514. static struct obex_mime_type_driver folder = {
  515. .target = FTP_TARGET,
  516. .target_size = FTP_TARGET_SIZE,
  517. .mimetype = "x-obex/folder-listing",
  518. .open = folder_open,
  519. .close = string_free,
  520. .read = folder_read,
  521. };
  522. static struct obex_mime_type_driver pcsuite = {
  523. .target = FTP_TARGET,
  524. .target_size = FTP_TARGET_SIZE,
  525. .who = PCSUITE_WHO,
  526. .who_size = PCSUITE_WHO_SIZE,
  527. .mimetype = "x-obex/folder-listing",
  528. .open = pcsuite_open,
  529. .close = string_free,
  530. .read = folder_read,
  531. };
  532. static int filesystem_init(void)
  533. {
  534. int err;
  535. err = obex_mime_type_driver_register(&folder);
  536. if (err < 0)
  537. return err;
  538. err = obex_mime_type_driver_register(&capability);
  539. if (err < 0)
  540. return err;
  541. err = obex_mime_type_driver_register(&pcsuite);
  542. if (err < 0)
  543. return err;
  544. return obex_mime_type_driver_register(&file);
  545. }
  546. static void filesystem_exit(void)
  547. {
  548. obex_mime_type_driver_unregister(&folder);
  549. obex_mime_type_driver_unregister(&capability);
  550. obex_mime_type_driver_unregister(&file);
  551. }
  552. OBEX_PLUGIN_DEFINE(filesystem, filesystem_init, filesystem_exit)