static void uh_mainloop(struct config *conf, fd_set serv_fds, int max_fd) { /* master file descriptor list */ fd_set used_fds, read_fds; /* working structs */ struct http_request *req; struct path_info *pin; struct client *cl; /* maximum file descriptor number */ int new_fd, cur_fd = 0; /* clear the master and temp sets */ FD_ZERO(&used_fds); FD_ZERO(&read_fds); /* backup server descriptor set */ used_fds = serv_fds; /* loop */ while(run) { /* create a working copy of the used fd set */ read_fds = used_fds; /* sleep until socket activity */ if( select(max_fd + 1, &read_fds, NULL, NULL, NULL) == -1 ) { perror("select()"); exit(1); } /* run through the existing connections looking for data to be read */ for( cur_fd = 0; cur_fd <= max_fd; cur_fd++ ) { /* is a socket managed by us */ if( FD_ISSET(cur_fd, &read_fds) ) { /* is one of our listen sockets */ if( FD_ISSET(cur_fd, &serv_fds) ) { /* handle new connections */ if( (new_fd = accept(cur_fd, NULL, 0)) != -1 ) { /* add to global client list */ if( (cl = uh_client_add(new_fd, uh_listener_lookup(cur_fd))) != NULL ) { #ifdef HAVE_TLS /* setup client tls context */ if( conf->tls ) { if( conf->tls_accept(cl) < 1 ) { fprintf(stderr, "tls_accept failed, " "connection dropped\n"); /* close client socket */ close(new_fd); /* remove from global client list */ uh_client_remove(new_fd); continue; } } #endif /* add client socket to global fdset */ FD_SET(new_fd, &used_fds); fd_cloexec(new_fd); max_fd = max(max_fd, new_fd); } /* insufficient resources */ else { fprintf(stderr, "uh_client_add(): Cannot allocate memory\n"); close(new_fd); } } } /* is a client socket */ else { if( ! (cl = uh_client_lookup(cur_fd)) ) { /* this should not happen! */ fprintf(stderr, "uh_client_lookup(): No entry for fd %i!\n", cur_fd); goto cleanup; } /* parse message header */ if( (req = uh_http_header_recv(cl)) != NULL ) { /* RFC1918 filtering required? */ if( conf->rfc1918_filter && sa_rfc1918(&cl->peeraddr) && !sa_rfc1918(&cl->servaddr) ) { uh_http_sendhf(cl, 403, "Forbidden", "Rejected request from RFC1918 IP " "to public server address"); } else #ifdef HAVE_LUA /* Lua request? */ if( conf->lua_state && uh_path_match(conf->lua_prefix, req->url) ) { conf->lua_request(cl, req, conf->lua_state); } else #endif /* dispatch request */ if( (pin = uh_path_lookup(cl, req->url)) != NULL ) { /* auth ok? */ if( !pin->redirected && uh_auth_check(cl, req, pin) ) uh_dispatch_request(cl, req, pin); } /* 404 */ else { /* Try to invoke an error handler */ pin = uh_path_lookup(cl, conf->error_handler); if( pin && uh_auth_check(cl, req, pin) ) { req->redirect_status = 404; uh_dispatch_request(cl, req, pin); } else { uh_http_sendhf(cl, 404, "Not Found", "No such file or directory"); } } } #ifdef HAVE_TLS /* free client tls context */ if( conf->tls ) conf->tls_close(cl); #endif cleanup: /* close client socket */ close(cur_fd); FD_CLR(cur_fd, &used_fds); /* remove from global client list */ uh_client_remove(cur_fd); } } } } #ifdef HAVE_LUA /* destroy the Lua state */ if( conf->lua_state != NULL ) conf->lua_close(conf->lua_state); #endif }
int main (int argc, char **argv) { #ifdef HAVE_LUA /* Lua runtime */ lua_State *L = NULL; #endif /* master file descriptor list */ fd_set used_fds, serv_fds, read_fds; /* working structs */ struct addrinfo hints; struct http_request *req; struct path_info *pin; struct client *cl; struct sigaction sa; struct config conf; /* signal mask */ sigset_t ss; /* maximum file descriptor number */ int new_fd, cur_fd, max_fd = 0; int tls = 0; int keys = 0; int bound = 0; int nofork = 0; /* args */ int opt; char bind[128]; char *port = NULL; /* library handles */ void *tls_lib; void *lua_lib; /* clear the master and temp sets */ FD_ZERO(&used_fds); FD_ZERO(&serv_fds); FD_ZERO(&read_fds); /* handle SIGPIPE, SIGINT, SIGTERM, SIGCHLD */ sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, NULL); sa.sa_handler = uh_sigchld; sigaction(SIGCHLD, &sa, NULL); sa.sa_handler = uh_sigterm; sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); /* defer SIGCHLD */ sigemptyset(&ss); sigaddset(&ss, SIGCHLD); sigprocmask(SIG_BLOCK, &ss, NULL); /* prepare addrinfo hints */ memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; /* parse args */ memset(&conf, 0, sizeof(conf)); memset(bind, 0, sizeof(bind)); #ifdef HAVE_TLS /* load TLS plugin */ if( ! (tls_lib = dlopen("uhttpd_tls.so", RTLD_LAZY | RTLD_GLOBAL)) ) { fprintf(stderr, "Notice: Unable to load TLS plugin - disabling SSL support! " "(Reason: %s)\n", dlerror() ); } else { /* resolve functions */ if( !(conf.tls_init = dlsym(tls_lib, "uh_tls_ctx_init")) || !(conf.tls_cert = dlsym(tls_lib, "uh_tls_ctx_cert")) || !(conf.tls_key = dlsym(tls_lib, "uh_tls_ctx_key")) || !(conf.tls_free = dlsym(tls_lib, "uh_tls_ctx_free")) || !(conf.tls_accept = dlsym(tls_lib, "uh_tls_client_accept")) || !(conf.tls_close = dlsym(tls_lib, "uh_tls_client_close")) || !(conf.tls_recv = dlsym(tls_lib, "uh_tls_client_recv")) || !(conf.tls_send = dlsym(tls_lib, "uh_tls_client_send")) ) { fprintf(stderr, "Error: Failed to lookup required symbols " "in TLS plugin: %s\n", dlerror() ); exit(1); } /* init SSL context */ if( ! (conf.tls = conf.tls_init()) ) { fprintf(stderr, "Error: Failed to initalize SSL context\n"); exit(1); } } #endif while( (opt = getopt(argc, argv, "fC:K:p:s:h:c:l:L:d:r:m:x:t:")) > 0 ) { switch(opt) { /* [addr:]port */ case 'p': case 's': if( (port = strrchr(optarg, ':')) != NULL ) { if( (optarg[0] == '[') && (port > optarg) && (port[-1] == ']') ) memcpy(bind, optarg + 1, min(sizeof(bind), (int)(port - optarg) - 2)); else memcpy(bind, optarg, min(sizeof(bind), (int)(port - optarg))); port++; } else { port = optarg; } #ifdef HAVE_TLS if( opt == 's' ) { if( !conf.tls ) { fprintf(stderr, "Notice: TLS support is disabled, " "ignoring '-s %s'\n", optarg ); continue; } tls = 1; } #endif /* bind sockets */ bound += uh_socket_bind( &serv_fds, &max_fd, bind[0] ? bind : NULL, port, &hints, (opt == 's'), &conf ); break; #ifdef HAVE_TLS /* certificate */ case 'C': if( conf.tls ) { if( conf.tls_cert(conf.tls, optarg) < 1 ) { fprintf(stderr, "Error: Invalid certificate file given\n"); exit(1); } keys++; } break; /* key */ case 'K': if( conf.tls ) { if( conf.tls_key(conf.tls, optarg) < 1 ) { fprintf(stderr, "Error: Invalid private key file given\n"); exit(1); } keys++; } break; #endif /* docroot */ case 'h': if( ! realpath(optarg, conf.docroot) ) { fprintf(stderr, "Error: Invalid directory %s: %s\n", optarg, strerror(errno)); exit(1); } break; #ifdef HAVE_CGI /* cgi prefix */ case 'x': conf.cgi_prefix = optarg; break; #endif #ifdef HAVE_LUA /* lua prefix */ case 'l': conf.lua_prefix = optarg; break; /* lua handler */ case 'L': conf.lua_handler = optarg; break; #endif #if defined(HAVE_CGI) || defined(HAVE_LUA) /* script timeout */ case 't': conf.script_timeout = atoi(optarg); break; #endif /* no fork */ case 'f': nofork = 1; break; /* urldecode */ case 'd': if( (port = malloc(strlen(optarg)+1)) != NULL ) { memset(port, 0, strlen(optarg)+1); uh_urldecode(port, strlen(optarg), optarg, strlen(optarg)); printf("%s", port); free(port); exit(0); } break; /* basic auth realm */ case 'r': conf.realm = optarg; break; /* md5 crypt */ case 'm': printf("%s\n", crypt(optarg, "$1$")); exit(0); break; /* config file */ case 'c': conf.file = optarg; break; default: fprintf(stderr, "Usage: %s -p [addr:]port [-h docroot]\n" " -f Do not fork to background\n" " -c file Configuration file, default is '/etc/httpd.conf'\n" " -p [addr:]port Bind to specified address and port, multiple allowed\n" #ifdef HAVE_TLS " -s [addr:]port Like -p but provide HTTPS on this port\n" " -C file ASN.1 server certificate file\n" " -K file ASN.1 server private key file\n" #endif " -h directory Specify the document root, default is '.'\n" #ifdef HAVE_LUA " -l string URL prefix for Lua handler, default is '/lua'\n" " -L file Lua handler script, omit to disable Lua\n" #endif #ifdef HAVE_CGI " -x string URL prefix for CGI handler, default is '/cgi-bin'\n" #endif #if defined(HAVE_CGI) || defined(HAVE_LUA) " -t seconds CGI and Lua script timeout in seconds, default is 60\n" #endif " -d string URL decode given string\n" " -r string Specify basic auth realm\n" " -m string MD5 crypt given string\n" "\n", argv[0] ); exit(1); } } #ifdef HAVE_TLS if( (tls == 1) && (keys < 2) ) { fprintf(stderr, "Error: Missing private key or certificate file\n"); exit(1); } #endif if( bound < 1 ) { fprintf(stderr, "Error: No sockets bound, unable to continue\n"); exit(1); } /* default docroot */ if( !conf.docroot[0] && !realpath(".", conf.docroot) ) { fprintf(stderr, "Error: Can not determine default document root: %s\n", strerror(errno)); exit(1); } /* default realm */ if( ! conf.realm ) conf.realm = "Protected Area"; /* config file */ uh_config_parse(conf.file); #if defined(HAVE_CGI) || defined(HAVE_LUA) /* default script timeout */ if( conf.script_timeout <= 0 ) conf.script_timeout = 60; #endif #ifdef HAVE_CGI /* default cgi prefix */ if( ! conf.cgi_prefix ) conf.cgi_prefix = "/cgi-bin"; #endif #ifdef HAVE_LUA /* load Lua plugin */ if( ! (lua_lib = dlopen("uhttpd_lua.so", RTLD_LAZY | RTLD_GLOBAL)) ) { fprintf(stderr, "Notice: Unable to load Lua plugin - disabling Lua support! " "(Reason: %s)\n", dlerror() ); } else { /* resolve functions */ if( !(conf.lua_init = dlsym(lua_lib, "uh_lua_init")) || !(conf.lua_close = dlsym(lua_lib, "uh_lua_close")) || !(conf.lua_request = dlsym(lua_lib, "uh_lua_request")) ) { fprintf(stderr, "Error: Failed to lookup required symbols " "in Lua plugin: %s\n", dlerror() ); exit(1); } /* init Lua runtime if handler is specified */ if( conf.lua_handler ) { /* default lua prefix */ if( ! conf.lua_prefix ) conf.lua_prefix = "/lua"; L = conf.lua_init(conf.lua_handler); } } #endif /* fork (if not disabled) */ if( ! nofork ) { switch( fork() ) { case -1: perror("fork()"); exit(1); case 0: /* daemon setup */ if( chdir("/") ) perror("chdir()"); if( (cur_fd = open("/dev/null", O_WRONLY)) > -1 ) dup2(cur_fd, 0); if( (cur_fd = open("/dev/null", O_RDONLY)) > -1 ) dup2(cur_fd, 1); if( (cur_fd = open("/dev/null", O_RDONLY)) > -1 ) dup2(cur_fd, 2); break; default: exit(0); } } /* backup server descriptor set */ used_fds = serv_fds; /* loop */ while(run) { /* create a working copy of the used fd set */ read_fds = used_fds; /* sleep until socket activity */ if( select(max_fd + 1, &read_fds, NULL, NULL, NULL) == -1 ) { perror("select()"); exit(1); } /* run through the existing connections looking for data to be read */ for( cur_fd = 0; cur_fd <= max_fd; cur_fd++ ) { /* is a socket managed by us */ if( FD_ISSET(cur_fd, &read_fds) ) { /* is one of our listen sockets */ if( FD_ISSET(cur_fd, &serv_fds) ) { /* handle new connections */ if( (new_fd = accept(cur_fd, NULL, 0)) != -1 ) { /* add to global client list */ if( (cl = uh_client_add(new_fd, uh_listener_lookup(cur_fd))) != NULL ) { #ifdef HAVE_TLS /* setup client tls context */ if( conf.tls ) conf.tls_accept(cl); #endif /* add client socket to global fdset */ FD_SET(new_fd, &used_fds); fd_cloexec(new_fd); max_fd = max(max_fd, new_fd); } /* insufficient resources */ else { fprintf(stderr, "uh_client_add(): Can not manage more than " "%i client sockets, connection dropped\n", UH_LIMIT_CLIENTS ); close(new_fd); } } } /* is a client socket */ else { if( ! (cl = uh_client_lookup(cur_fd)) ) { /* this should not happen! */ fprintf(stderr, "uh_client_lookup(): No entry for fd %i!\n", cur_fd); goto cleanup; } /* parse message header */ if( (req = uh_http_header_recv(cl)) != NULL ) { #ifdef HAVE_LUA /* Lua request? */ if( L && uh_path_match(conf.lua_prefix, req->url) ) { conf.lua_request(cl, req, L); } else #endif /* dispatch request */ if( (pin = uh_path_lookup(cl, req->url)) != NULL ) { /* auth ok? */ if( uh_auth_check(cl, req, pin) ) { #ifdef HAVE_CGI if( uh_path_match(conf.cgi_prefix, pin->name) ) { uh_cgi_request(cl, req, pin); } else #endif { uh_file_request(cl, req, pin); } } } /* 404 */ else { uh_http_sendhf(cl, 404, "Not Found", "No such file or directory"); } } /* 400 */ else { uh_http_sendhf(cl, 400, "Bad Request", "Malformed request received"); } #ifdef HAVE_TLS /* free client tls context */ if( conf.tls ) conf.tls_close(cl); #endif cleanup: /* close client socket */ close(cur_fd); FD_CLR(cur_fd, &used_fds); /* remove from global client list */ uh_client_remove(cur_fd); } } } } #ifdef HAVE_LUA /* destroy the Lua state */ if( L != NULL ) conf.lua_close(L); #endif return 0; }