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