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