You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

651 lines
11 KiB

  1. /*
  2. * cgi-io - LuCI non-RPC helper
  3. *
  4. * Copyright (C) 2013 Jo-Philipp Wich <jo@mein.io>
  5. *
  6. * Permission to use, copy, modify, and/or distribute this software for any
  7. * purpose with or without fee is hereby granted, provided that the above
  8. * copyright notice and this permission notice appear in all copies.
  9. *
  10. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  11. * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  12. * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  13. * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  14. * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  15. * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  16. * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  17. */
  18. #include <stdio.h>
  19. #include <stdlib.h>
  20. #include <stdbool.h>
  21. #include <unistd.h>
  22. #include <string.h>
  23. #include <errno.h>
  24. #include <fcntl.h>
  25. #include <ctype.h>
  26. #include <sys/stat.h>
  27. #include <sys/wait.h>
  28. #include <libubus.h>
  29. #include <libubox/blobmsg.h>
  30. #include "multipart_parser.h"
  31. enum part {
  32. PART_UNKNOWN,
  33. PART_SESSIONID,
  34. PART_FILENAME,
  35. PART_FILEMODE,
  36. PART_FILEDATA
  37. };
  38. const char *parts[] = {
  39. "(bug)",
  40. "sessionid",
  41. "filename",
  42. "filemode",
  43. "filedata",
  44. };
  45. struct state
  46. {
  47. bool is_content_disposition;
  48. enum part parttype;
  49. char *sessionid;
  50. char *filename;
  51. bool filedata;
  52. int filemode;
  53. int filefd;
  54. int tempfd;
  55. };
  56. enum {
  57. SES_ACCESS,
  58. __SES_MAX,
  59. };
  60. static const struct blobmsg_policy ses_policy[__SES_MAX] = {
  61. [SES_ACCESS] = { .name = "access", .type = BLOBMSG_TYPE_BOOL },
  62. };
  63. static struct state st;
  64. static void
  65. session_access_cb(struct ubus_request *req, int type, struct blob_attr *msg)
  66. {
  67. struct blob_attr *tb[__SES_MAX];
  68. bool *allow = (bool *)req->priv;
  69. if (!msg)
  70. return;
  71. blobmsg_parse(ses_policy, __SES_MAX, tb, blob_data(msg), blob_len(msg));
  72. if (tb[SES_ACCESS])
  73. *allow = blobmsg_get_bool(tb[SES_ACCESS]);
  74. }
  75. static bool
  76. session_access(const char *sid, const char *obj, const char *func)
  77. {
  78. uint32_t id;
  79. bool allow = false;
  80. struct ubus_context *ctx;
  81. static struct blob_buf req;
  82. ctx = ubus_connect(NULL);
  83. if (!ctx || ubus_lookup_id(ctx, "session", &id))
  84. goto out;
  85. blob_buf_init(&req, 0);
  86. blobmsg_add_string(&req, "ubus_rpc_session", sid);
  87. blobmsg_add_string(&req, "scope", "cgi-io");
  88. blobmsg_add_string(&req, "object", obj);
  89. blobmsg_add_string(&req, "function", func);
  90. ubus_invoke(ctx, id, "access", req.head, session_access_cb, &allow, 500);
  91. out:
  92. if (ctx)
  93. ubus_free(ctx);
  94. return allow;
  95. }
  96. static char *
  97. md5sum(const char *file)
  98. {
  99. pid_t pid;
  100. int fds[2];
  101. static char md5[33];
  102. if (pipe(fds))
  103. return NULL;
  104. switch ((pid = fork()))
  105. {
  106. case -1:
  107. return NULL;
  108. case 0:
  109. uloop_done();
  110. dup2(fds[1], 1);
  111. close(0);
  112. close(2);
  113. close(fds[0]);
  114. close(fds[1]);
  115. if (execl("/bin/busybox", "/bin/busybox", "md5sum", file, NULL))
  116. return NULL;
  117. break;
  118. default:
  119. memset(md5, 0, sizeof(md5));
  120. read(fds[0], md5, 32);
  121. waitpid(pid, NULL, 0);
  122. close(fds[0]);
  123. close(fds[1]);
  124. }
  125. return md5;
  126. }
  127. static char *
  128. datadup(const void *in, size_t len)
  129. {
  130. char *out = malloc(len + 1);
  131. if (!out)
  132. return NULL;
  133. memcpy(out, in, len);
  134. *(out + len) = 0;
  135. return out;
  136. }
  137. static bool
  138. urldecode(char *buf)
  139. {
  140. char *c, *p;
  141. if (!buf || !*buf)
  142. return true;
  143. #define hex(x) \
  144. (((x) <= '9') ? ((x) - '0') : \
  145. (((x) <= 'F') ? ((x) - 'A' + 10) : \
  146. ((x) - 'a' + 10)))
  147. for (c = p = buf; *p; c++)
  148. {
  149. if (*p == '%')
  150. {
  151. if (!isxdigit(*(p + 1)) || !isxdigit(*(p + 2)))
  152. return false;
  153. *c = (char)(16 * hex(*(p + 1)) + hex(*(p + 2)));
  154. p += 3;
  155. }
  156. else if (*p == '+')
  157. {
  158. *c = ' ';
  159. p++;
  160. }
  161. else
  162. {
  163. *c = *p++;
  164. }
  165. }
  166. *c = 0;
  167. return true;
  168. }
  169. static bool
  170. postdecode(char **fields, int n_fields)
  171. {
  172. char *p;
  173. const char *var;
  174. static char buf[1024];
  175. int i, len, field, found = 0;
  176. var = getenv("CONTENT_TYPE");
  177. if (!var || strncmp(var, "application/x-www-form-urlencoded", 33))
  178. return false;
  179. memset(buf, 0, sizeof(buf));
  180. if ((len = read(0, buf, sizeof(buf) - 1)) > 0)
  181. {
  182. for (p = buf, i = 0; i <= len; i++)
  183. {
  184. if (buf[i] == '=')
  185. {
  186. buf[i] = 0;
  187. for (field = 0; field < (n_fields * 2); field += 2)
  188. {
  189. if (!strcmp(p, fields[field]))
  190. {
  191. fields[field + 1] = buf + i + 1;
  192. found++;
  193. }
  194. }
  195. }
  196. else if (buf[i] == '&' || buf[i] == '\0')
  197. {
  198. buf[i] = 0;
  199. if (found >= n_fields)
  200. break;
  201. p = buf + i + 1;
  202. }
  203. }
  204. }
  205. for (field = 0; field < (n_fields * 2); field += 2)
  206. if (!urldecode(fields[field + 1]))
  207. return false;
  208. return (found >= n_fields);
  209. }
  210. static int
  211. response(bool success, const char *message)
  212. {
  213. char *md5;
  214. struct stat s;
  215. printf("Status: 200 OK\r\n");
  216. printf("Content-Type: text/plain\r\n\r\n{\n");
  217. if (success)
  218. {
  219. if (!stat(st.filename, &s) && (md5 = md5sum(st.filename)) != NULL)
  220. printf("\t\"size\": %u,\n\t\"checksum\": \"%s\"\n",
  221. (unsigned int)s.st_size, md5);
  222. }
  223. else
  224. {
  225. if (message)
  226. printf("\t\"message\": \"%s\",\n", message);
  227. printf("\t\"failure\": [ %u, \"%s\" ]\n", errno, strerror(errno));
  228. if (st.filefd > -1)
  229. unlink(st.filename);
  230. }
  231. printf("}\n");
  232. return -1;
  233. }
  234. static int
  235. failure(int e, const char *message)
  236. {
  237. printf("Status: 500 Internal Server failure\r\n");
  238. printf("Content-Type: text/plain\r\n\r\n");
  239. printf("%s", message);
  240. if (e)
  241. printf(": %s", strerror(e));
  242. return -1;
  243. }
  244. static int
  245. filecopy(void)
  246. {
  247. int len;
  248. char buf[4096];
  249. if (!st.filedata)
  250. {
  251. close(st.tempfd);
  252. errno = EINVAL;
  253. return response(false, "No file data received");
  254. }
  255. if (lseek(st.tempfd, 0, SEEK_SET) < 0)
  256. {
  257. close(st.tempfd);
  258. return response(false, "Failed to rewind temp file");
  259. }
  260. st.filefd = open(st.filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
  261. if (st.filefd < 0)
  262. {
  263. close(st.tempfd);
  264. return response(false, "Failed to open target file");
  265. }
  266. while ((len = read(st.tempfd, buf, sizeof(buf))) > 0)
  267. {
  268. if (write(st.filefd, buf, len) != len)
  269. {
  270. close(st.tempfd);
  271. close(st.filefd);
  272. return response(false, "I/O failure while writing target file");
  273. }
  274. }
  275. close(st.tempfd);
  276. close(st.filefd);
  277. if (chmod(st.filename, st.filemode))
  278. return response(false, "Failed to chmod target file");
  279. return 0;
  280. }
  281. static int
  282. header_field(multipart_parser *p, const char *data, size_t len)
  283. {
  284. st.is_content_disposition = !strncasecmp(data, "Content-Disposition", len);
  285. return 0;
  286. }
  287. static int
  288. header_value(multipart_parser *p, const char *data, size_t len)
  289. {
  290. int i, j;
  291. if (!st.is_content_disposition)
  292. return 0;
  293. if (len < 10 || strncasecmp(data, "form-data", 9))
  294. return 0;
  295. for (data += 9, len -= 9; *data == ' ' || *data == ';'; data++, len--);
  296. if (len < 8 || strncasecmp(data, "name=\"", 6))
  297. return 0;
  298. for (data += 6, len -= 6, i = 0; i <= len; i++)
  299. {
  300. if (*(data + i) != '"')
  301. continue;
  302. for (j = 1; j < sizeof(parts) / sizeof(parts[0]); j++)
  303. if (!strncmp(data, parts[j], i))
  304. st.parttype = j;
  305. break;
  306. }
  307. return 0;
  308. }
  309. static int
  310. data_begin_cb(multipart_parser *p)
  311. {
  312. char tmpname[24] = "/tmp/luci-upload.XXXXXX";
  313. if (st.parttype == PART_FILEDATA)
  314. {
  315. if (!st.sessionid)
  316. return response(false, "File data without session");
  317. if (!st.filename)
  318. return response(false, "File data without name");
  319. st.tempfd = mkstemp(tmpname);
  320. if (st.tempfd < 0)
  321. return response(false, "Failed to create temporary file");
  322. unlink(tmpname);
  323. }
  324. return 0;
  325. }
  326. static int
  327. data_cb(multipart_parser *p, const char *data, size_t len)
  328. {
  329. switch (st.parttype)
  330. {
  331. case PART_SESSIONID:
  332. st.sessionid = datadup(data, len);
  333. break;
  334. case PART_FILENAME:
  335. st.filename = datadup(data, len);
  336. break;
  337. case PART_FILEMODE:
  338. st.filemode = strtoul(data, NULL, 8);
  339. break;
  340. case PART_FILEDATA:
  341. if (write(st.tempfd, data, len) != len)
  342. {
  343. close(st.tempfd);
  344. return response(false, "I/O failure while writing temporary file");
  345. }
  346. if (!st.filedata)
  347. st.filedata = !!len;
  348. break;
  349. default:
  350. break;
  351. }
  352. return 0;
  353. }
  354. static int
  355. data_end_cb(multipart_parser *p)
  356. {
  357. if (st.parttype == PART_SESSIONID)
  358. {
  359. if (!session_access(st.sessionid, "upload", "write"))
  360. {
  361. errno = EPERM;
  362. return response(false, "Upload permission denied");
  363. }
  364. }
  365. else if (st.parttype == PART_FILEDATA)
  366. {
  367. if (st.tempfd < 0)
  368. return response(false, "Internal program failure");
  369. #if 0
  370. /* prepare directory */
  371. for (ptr = st.filename; *ptr; ptr++)
  372. {
  373. if (*ptr == '/')
  374. {
  375. *ptr = 0;
  376. if (mkdir(st.filename, 0755))
  377. {
  378. unlink(st.tmpname);
  379. return response(false, "Failed to create destination directory");
  380. }
  381. *ptr = '/';
  382. }
  383. }
  384. #endif
  385. if (filecopy())
  386. return -1;
  387. return response(true, NULL);
  388. }
  389. st.parttype = PART_UNKNOWN;
  390. return 0;
  391. }
  392. static multipart_parser *
  393. init_parser(void)
  394. {
  395. char *boundary;
  396. const char *var;
  397. multipart_parser *p;
  398. static multipart_parser_settings s = {
  399. .on_part_data = data_cb,
  400. .on_headers_complete = data_begin_cb,
  401. .on_part_data_end = data_end_cb,
  402. .on_header_field = header_field,
  403. .on_header_value = header_value
  404. };
  405. var = getenv("CONTENT_TYPE");
  406. if (!var || strncmp(var, "multipart/form-data;", 20))
  407. return NULL;
  408. for (var += 20; *var && *var != '='; var++);
  409. if (*var++ != '=')
  410. return NULL;
  411. boundary = malloc(strlen(var) + 3);
  412. if (!boundary)
  413. return NULL;
  414. strcpy(boundary, "--");
  415. strcpy(boundary + 2, var);
  416. st.tempfd = -1;
  417. st.filefd = -1;
  418. st.filemode = 0600;
  419. p = multipart_parser_init(boundary, &s);
  420. free(boundary);
  421. return p;
  422. }
  423. static int
  424. main_upload(int argc, char *argv[])
  425. {
  426. int rem, len;
  427. char buf[4096];
  428. multipart_parser *p;
  429. p = init_parser();
  430. if (!p)
  431. {
  432. errno = EINVAL;
  433. return response(false, "Invalid request");
  434. }
  435. while ((len = read(0, buf, sizeof(buf))) > 0)
  436. {
  437. rem = multipart_parser_execute(p, buf, len);
  438. if (rem < len)
  439. break;
  440. }
  441. multipart_parser_free(p);
  442. /* read remaining post data */
  443. while ((len = read(0, buf, sizeof(buf))) > 0);
  444. return 0;
  445. }
  446. static int
  447. main_backup(int argc, char **argv)
  448. {
  449. pid_t pid;
  450. time_t now;
  451. int len;
  452. int status;
  453. int fds[2];
  454. char buf[4096];
  455. char datestr[16] = { 0 };
  456. char hostname[64] = { 0 };
  457. char *fields[] = { "sessionid", NULL };
  458. if (!postdecode(fields, 1) || !session_access(fields[1], "backup", "read"))
  459. return failure(0, "Backup permission denied");
  460. if (pipe(fds))
  461. return failure(errno, "Failed to spawn pipe");
  462. switch ((pid = fork()))
  463. {
  464. case -1:
  465. return failure(errno, "Failed to fork process");
  466. case 0:
  467. dup2(fds[1], 1);
  468. close(0);
  469. close(2);
  470. close(fds[0]);
  471. close(fds[1]);
  472. chdir("/");
  473. execl("/sbin/sysupgrade", "/sbin/sysupgrade",
  474. "--create-backup", "-", NULL);
  475. return -1;
  476. default:
  477. fcntl(fds[0], F_SETFL, fcntl(fds[0], F_GETFL) | O_NONBLOCK);
  478. now = time(NULL);
  479. strftime(datestr, sizeof(datestr) - 1, "%Y-%m-%d", localtime(&now));
  480. if (gethostname(hostname, sizeof(hostname) - 1))
  481. sprintf(hostname, "OpenWrt");
  482. printf("Status: 200 OK\r\n");
  483. printf("Content-Type: application/x-targz\r\n");
  484. printf("Content-Disposition: attachment; "
  485. "filename=\"backup-%s-%s.tar.gz\"\r\n\r\n", hostname, datestr);
  486. do {
  487. waitpid(pid, &status, 0);
  488. while ((len = read(fds[0], buf, sizeof(buf))) > 0) {
  489. fwrite(buf, len, 1, stdout);
  490. fflush(stdout);
  491. }
  492. } while (!WIFEXITED(status));
  493. close(fds[0]);
  494. close(fds[1]);
  495. return 0;
  496. }
  497. }
  498. int main(int argc, char **argv)
  499. {
  500. if (strstr(argv[0], "cgi-upload"))
  501. return main_upload(argc, argv);
  502. else if (strstr(argv[0], "cgi-backup"))
  503. return main_backup(argc, argv);
  504. return -1;
  505. }