main.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. /*
  2. * uhttpd - Tiny single-threaded httpd
  3. *
  4. * Copyright (C) 2010-2013 Jo-Philipp Wich <xm@subsignal.org>
  5. * Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
  6. *
  7. * Permission to use, copy, modify, and/or distribute this software for any
  8. * purpose with or without fee is hereby granted, provided that the above
  9. * copyright notice and this permission notice appear in all copies.
  10. *
  11. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  12. * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  13. * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  14. * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  15. * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  16. * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  17. * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  18. */
  19. #define _DEFAULT_SOURCE
  20. #define _BSD_SOURCE
  21. #define _GNU_SOURCE
  22. #define _XOPEN_SOURCE 700
  23. #include <sys/types.h>
  24. #include <sys/socket.h>
  25. #include <netinet/in.h>
  26. #include <getopt.h>
  27. #include <errno.h>
  28. #include <netdb.h>
  29. #include <signal.h>
  30. #include <libubox/usock.h>
  31. #include <libubox/utils.h>
  32. #include "uhttpd.h"
  33. #include "tls.h"
  34. char uh_buf[4096];
  35. static int run_server(void)
  36. {
  37. uloop_init();
  38. uh_setup_listeners();
  39. uh_plugin_post_init();
  40. uloop_run();
  41. return 0;
  42. }
  43. static void uh_config_parse(void)
  44. {
  45. const char *path = conf.file;
  46. FILE *c;
  47. char line[512];
  48. char *col1;
  49. char *col2;
  50. char *eol;
  51. if (!path)
  52. path = "/etc/httpd.conf";
  53. c = fopen(path, "r");
  54. if (!c)
  55. return;
  56. memset(line, 0, sizeof(line));
  57. while (fgets(line, sizeof(line) - 1, c)) {
  58. if ((line[0] == '/') && (strchr(line, ':') != NULL)) {
  59. if (!(col1 = strchr(line, ':')) || (*col1++ = 0) ||
  60. !(col2 = strchr(col1, ':')) || (*col2++ = 0) ||
  61. !(eol = strchr(col2, '\n')) || (*eol++ = 0))
  62. continue;
  63. uh_auth_add(line, col1, col2);
  64. } else if (!strncmp(line, "I:", 2)) {
  65. if (!(col1 = strchr(line, ':')) || (*col1++ = 0) ||
  66. !(eol = strchr(col1, '\n')) || (*eol++ = 0))
  67. continue;
  68. uh_index_add(strdup(col1));
  69. } else if (!strncmp(line, "E404:", 5)) {
  70. if (!(col1 = strchr(line, ':')) || (*col1++ = 0) ||
  71. !(eol = strchr(col1, '\n')) || (*eol++ = 0))
  72. continue;
  73. conf.error_handler = strdup(col1);
  74. }
  75. else if ((line[0] == '*') && (strchr(line, ':') != NULL)) {
  76. if (!(col1 = strchr(line, '*')) || (*col1++ = 0) ||
  77. !(col2 = strchr(col1, ':')) || (*col2++ = 0) ||
  78. !(eol = strchr(col2, '\n')) || (*eol++ = 0))
  79. continue;
  80. uh_interpreter_add(col1, col2);
  81. }
  82. }
  83. fclose(c);
  84. }
  85. static int add_listener_arg(char *arg, bool tls)
  86. {
  87. char *host = NULL;
  88. char *port = arg;
  89. char *s;
  90. int l;
  91. s = strrchr(arg, ':');
  92. if (s) {
  93. host = arg;
  94. port = s + 1;
  95. *s = 0;
  96. }
  97. if (host && *host == '[') {
  98. l = strlen(host);
  99. if (l >= 2) {
  100. host[l-1] = 0;
  101. host++;
  102. }
  103. }
  104. return uh_socket_bind(host, port, tls);
  105. }
  106. static int usage(const char *name)
  107. {
  108. fprintf(stderr,
  109. "Usage: %s -p [addr:]port -h docroot\n"
  110. " -f Do not fork to background\n"
  111. " -c file Configuration file, default is '/etc/httpd.conf'\n"
  112. " -p [addr:]port Bind to specified address and port, multiple allowed\n"
  113. #ifdef HAVE_TLS
  114. " -s [addr:]port Like -p but provide HTTPS on this port\n"
  115. " -C file ASN.1 server certificate file\n"
  116. " -K file ASN.1 server private key file\n"
  117. " -q Redirect all HTTP requests to HTTPS\n"
  118. #endif
  119. " -h directory Specify the document root, default is '.'\n"
  120. " -E string Use given virtual URL as 404 error handler\n"
  121. " -I string Use given filename as index for directories, multiple allowed\n"
  122. " -S Do not follow symbolic links outside of the docroot\n"
  123. " -D Do not allow directory listings, send 403 instead\n"
  124. " -R Enable RFC1918 filter\n"
  125. " -n count Maximum allowed number of concurrent script requests\n"
  126. " -N count Maximum allowed number of concurrent connections\n"
  127. #ifdef HAVE_LUA
  128. " -l string URL prefix for Lua handler, default is '/lua'\n"
  129. " -L file Lua handler script, omit to disable Lua\n"
  130. #endif
  131. #ifdef HAVE_UBUS
  132. " -u string URL prefix for UBUS via JSON-RPC handler\n"
  133. " -U file Override ubus socket path\n"
  134. " -a Do not authenticate JSON-RPC requests against UBUS session api\n"
  135. " -X Enable CORS HTTP headers on JSON-RPC api\n"
  136. #endif
  137. " -x string URL prefix for CGI handler, default is '/cgi-bin'\n"
  138. " -y alias[=path] URL alias handle\n"
  139. " -i .ext=path Use interpreter at path for files with the given extension\n"
  140. " -t seconds CGI, Lua and UBUS script timeout in seconds, default is 60\n"
  141. " -T seconds Network timeout in seconds, default is 30\n"
  142. " -k seconds HTTP keepalive timeout\n"
  143. " -d string URL decode given string\n"
  144. " -r string Specify basic auth realm\n"
  145. " -m string MD5 crypt given string\n"
  146. "\n", name
  147. );
  148. return 1;
  149. }
  150. static void init_defaults_pre(void)
  151. {
  152. conf.script_timeout = 60;
  153. conf.network_timeout = 30;
  154. conf.http_keepalive = 20;
  155. conf.max_script_requests = 3;
  156. conf.max_connections = 100;
  157. conf.realm = "Protected Area";
  158. conf.cgi_prefix = "/cgi-bin";
  159. conf.cgi_path = "/sbin:/usr/sbin:/bin:/usr/bin";
  160. INIT_LIST_HEAD(&conf.cgi_alias);
  161. INIT_LIST_HEAD(&conf.lua_prefix);
  162. }
  163. static void init_defaults_post(void)
  164. {
  165. uh_index_add("index.html");
  166. uh_index_add("index.htm");
  167. uh_index_add("default.html");
  168. uh_index_add("default.htm");
  169. if (conf.cgi_prefix) {
  170. char *str = malloc(strlen(conf.docroot) + strlen(conf.cgi_prefix) + 1);
  171. strcpy(str, conf.docroot);
  172. strcat(str, conf.cgi_prefix);
  173. conf.cgi_docroot_path = str;
  174. conf.cgi_prefix_len = strlen(conf.cgi_prefix);
  175. };
  176. }
  177. static void fixup_prefix(char *str)
  178. {
  179. int len;
  180. if (!str || !str[0])
  181. return;
  182. len = strlen(str) - 1;
  183. while (len >= 0 && str[len] == '/')
  184. len--;
  185. str[len + 1] = 0;
  186. }
  187. static void add_lua_prefix(const char *prefix, const char *handler) {
  188. struct lua_prefix *p;
  189. char *pprefix, *phandler;
  190. p = calloc_a(sizeof(*p),
  191. &pprefix, strlen(prefix) + 1,
  192. &phandler, strlen(handler) + 1);
  193. if (!p)
  194. return;
  195. p->prefix = strcpy(pprefix, prefix);
  196. p->handler = strcpy(phandler, handler);
  197. list_add_tail(&p->list, &conf.lua_prefix);
  198. }
  199. int main(int argc, char **argv)
  200. {
  201. struct alias *alias;
  202. bool nofork = false;
  203. char *port;
  204. int opt, ch;
  205. int cur_fd;
  206. int bound = 0;
  207. #ifdef HAVE_TLS
  208. int n_tls = 0;
  209. const char *tls_key = NULL, *tls_crt = NULL;
  210. #endif
  211. #ifdef HAVE_LUA
  212. const char *lua_prefix = NULL, *lua_handler = NULL;
  213. #endif
  214. BUILD_BUG_ON(sizeof(uh_buf) < PATH_MAX);
  215. uh_dispatch_add(&cgi_dispatch);
  216. init_defaults_pre();
  217. signal(SIGPIPE, SIG_IGN);
  218. while ((ch = getopt(argc, argv, "A:aC:c:Dd:E:fh:H:I:i:K:k:L:l:m:N:n:p:qRr:Ss:T:t:U:u:Xx:y:")) != -1) {
  219. switch(ch) {
  220. #ifdef HAVE_TLS
  221. case 'C':
  222. tls_crt = optarg;
  223. break;
  224. case 'K':
  225. tls_key = optarg;
  226. break;
  227. case 'q':
  228. conf.tls_redirect = 1;
  229. break;
  230. case 's':
  231. n_tls++;
  232. /* fall through */
  233. #else
  234. case 'C':
  235. case 'K':
  236. case 'q':
  237. case 's':
  238. fprintf(stderr, "uhttpd: TLS support not compiled, "
  239. "ignoring -%c\n", ch);
  240. break;
  241. #endif
  242. case 'p':
  243. optarg = strdup(optarg);
  244. bound += add_listener_arg(optarg, (ch == 's'));
  245. break;
  246. case 'h':
  247. if (!realpath(optarg, uh_buf)) {
  248. fprintf(stderr, "Error: Invalid directory %s: %s\n",
  249. optarg, strerror(errno));
  250. exit(1);
  251. }
  252. conf.docroot = strdup(uh_buf);
  253. break;
  254. case 'H':
  255. if (uh_handler_add(optarg)) {
  256. fprintf(stderr, "Error: Failed to load handler script %s\n",
  257. optarg);
  258. exit(1);
  259. }
  260. break;
  261. case 'E':
  262. if (optarg[0] != '/') {
  263. fprintf(stderr, "Error: Invalid error handler: %s\n",
  264. optarg);
  265. exit(1);
  266. }
  267. conf.error_handler = optarg;
  268. break;
  269. case 'I':
  270. if (optarg[0] == '/') {
  271. fprintf(stderr, "Error: Invalid index page: %s\n",
  272. optarg);
  273. exit(1);
  274. }
  275. uh_index_add(optarg);
  276. break;
  277. case 'S':
  278. conf.no_symlinks = 1;
  279. break;
  280. case 'D':
  281. conf.no_dirlists = 1;
  282. break;
  283. case 'R':
  284. conf.rfc1918_filter = 1;
  285. break;
  286. case 'n':
  287. conf.max_script_requests = atoi(optarg);
  288. break;
  289. case 'N':
  290. conf.max_connections = atoi(optarg);
  291. break;
  292. case 'x':
  293. fixup_prefix(optarg);
  294. conf.cgi_prefix = optarg;
  295. break;
  296. case 'y':
  297. alias = calloc(1, sizeof(*alias));
  298. if (!alias) {
  299. fprintf(stderr, "Error: failed to allocate alias\n");
  300. exit(1);
  301. }
  302. alias->alias = strdup(optarg);
  303. alias->path = strchr(alias->alias, '=');
  304. if (alias->path)
  305. *alias->path++ = 0;
  306. list_add(&alias->list, &conf.cgi_alias);
  307. break;
  308. case 'i':
  309. optarg = strdup(optarg);
  310. port = strchr(optarg, '=');
  311. if (optarg[0] != '.' || !port) {
  312. fprintf(stderr, "Error: Invalid interpreter: %s\n",
  313. optarg);
  314. exit(1);
  315. }
  316. *port++ = 0;
  317. uh_interpreter_add(optarg, port);
  318. break;
  319. case 't':
  320. conf.script_timeout = atoi(optarg);
  321. break;
  322. case 'T':
  323. conf.network_timeout = atoi(optarg);
  324. break;
  325. case 'k':
  326. conf.http_keepalive = atoi(optarg);
  327. break;
  328. case 'A':
  329. conf.tcp_keepalive = atoi(optarg);
  330. break;
  331. case 'f':
  332. nofork = 1;
  333. break;
  334. case 'd':
  335. optarg = strdup(optarg);
  336. port = alloca(strlen(optarg) + 1);
  337. if (!port)
  338. return -1;
  339. /* "decode" plus to space to retain compat */
  340. for (opt = 0; optarg[opt]; opt++)
  341. if (optarg[opt] == '+')
  342. optarg[opt] = ' ';
  343. /* opt now contains strlen(optarg) -- no need to re-scan */
  344. if (uh_urldecode(port, opt, optarg, opt) < 0) {
  345. fprintf(stderr, "uhttpd: invalid encoding\n");
  346. return -1;
  347. }
  348. printf("%s", port);
  349. return 0;
  350. break;
  351. /* basic auth realm */
  352. case 'r':
  353. conf.realm = optarg;
  354. break;
  355. /* md5 crypt */
  356. case 'm':
  357. printf("%s\n", crypt(optarg, "$1$"));
  358. return 0;
  359. break;
  360. /* config file */
  361. case 'c':
  362. conf.file = optarg;
  363. break;
  364. #ifdef HAVE_LUA
  365. case 'l':
  366. case 'L':
  367. if (ch == 'l') {
  368. if (lua_prefix)
  369. fprintf(stderr, "uhttpd: Ignoring previous -%c %s\n",
  370. ch, lua_prefix);
  371. lua_prefix = optarg;
  372. }
  373. else {
  374. if (lua_handler)
  375. fprintf(stderr, "uhttpd: Ignoring previous -%c %s\n",
  376. ch, lua_handler);
  377. lua_handler = optarg;
  378. }
  379. if (lua_prefix && lua_handler) {
  380. add_lua_prefix(lua_prefix, lua_handler);
  381. lua_prefix = NULL;
  382. lua_handler = NULL;
  383. }
  384. break;
  385. #else
  386. case 'l':
  387. case 'L':
  388. fprintf(stderr, "uhttpd: Lua support not compiled, "
  389. "ignoring -%c\n", ch);
  390. break;
  391. #endif
  392. #ifdef HAVE_UBUS
  393. case 'a':
  394. conf.ubus_noauth = 1;
  395. break;
  396. case 'u':
  397. conf.ubus_prefix = optarg;
  398. break;
  399. case 'U':
  400. conf.ubus_socket = optarg;
  401. break;
  402. case 'X':
  403. conf.ubus_cors = 1;
  404. break;
  405. #else
  406. case 'a':
  407. case 'u':
  408. case 'U':
  409. case 'X':
  410. fprintf(stderr, "uhttpd: UBUS support not compiled, "
  411. "ignoring -%c\n", ch);
  412. break;
  413. #endif
  414. default:
  415. return usage(argv[0]);
  416. }
  417. }
  418. uh_config_parse();
  419. if (!conf.docroot) {
  420. if (!realpath(".", uh_buf)) {
  421. fprintf(stderr, "Error: Unable to determine work dir\n");
  422. return 1;
  423. }
  424. conf.docroot = strdup(uh_buf);
  425. }
  426. init_defaults_post();
  427. if (!bound) {
  428. fprintf(stderr, "Error: No sockets bound, unable to continue\n");
  429. return 1;
  430. }
  431. #ifdef HAVE_TLS
  432. if (n_tls) {
  433. if (!tls_crt || !tls_key) {
  434. fprintf(stderr, "Please specify a certificate and "
  435. "a key file to enable SSL support\n");
  436. return 1;
  437. }
  438. if (uh_tls_init(tls_key, tls_crt))
  439. return 1;
  440. }
  441. #endif
  442. #ifdef HAVE_LUA
  443. if (lua_handler || lua_prefix) {
  444. fprintf(stderr, "Need handler and prefix to enable Lua support\n");
  445. return 1;
  446. }
  447. if (!list_empty(&conf.lua_prefix) && uh_plugin_init("uhttpd_lua.so"))
  448. return 1;
  449. #endif
  450. #ifdef HAVE_UBUS
  451. if (conf.ubus_prefix && uh_plugin_init("uhttpd_ubus.so"))
  452. return 1;
  453. #endif
  454. /* fork (if not disabled) */
  455. if (!nofork) {
  456. switch (fork()) {
  457. case -1:
  458. perror("fork()");
  459. exit(1);
  460. case 0:
  461. /* daemon setup */
  462. if (chdir("/"))
  463. perror("chdir()");
  464. cur_fd = open("/dev/null", O_WRONLY);
  465. if (cur_fd > 0) {
  466. dup2(cur_fd, 0);
  467. dup2(cur_fd, 1);
  468. dup2(cur_fd, 2);
  469. }
  470. break;
  471. default:
  472. exit(0);
  473. }
  474. }
  475. return run_server();
  476. }