diff options
author | Jo-Philipp Wich <jow@openwrt.org> | 2010-03-18 17:45:50 +0000 |
---|---|---|
committer | Jo-Philipp Wich <jow@openwrt.org> | 2010-03-18 17:45:50 +0000 |
commit | 87329b4522760f0a4a9a7962a52faf76d2db6f55 (patch) | |
tree | 09f47c80b40eda6de245cfb9ac05377243b603c7 /contrib/package/uhttpd/src/uhttpd.c | |
parent | 1661add529c525939a27ae47c1429bfe50615211 (diff) |
contrib/package: add uhttpd, a drop-in replacement for busybox httpd
Diffstat (limited to 'contrib/package/uhttpd/src/uhttpd.c')
-rw-r--r-- | contrib/package/uhttpd/src/uhttpd.c | 723 |
1 files changed, 723 insertions, 0 deletions
diff --git a/contrib/package/uhttpd/src/uhttpd.c b/contrib/package/uhttpd/src/uhttpd.c new file mode 100644 index 000000000..ffa44aacc --- /dev/null +++ b/contrib/package/uhttpd/src/uhttpd.c @@ -0,0 +1,723 @@ +#include "uhttpd.h" +#include "uhttpd-utils.h" +#include "uhttpd-file.h" + +#ifdef HAVE_CGI +#include "uhttpd-cgi.h" +#endif + +#ifdef HAVE_LUA +#include "uhttpd-lua.h" +#endif + +#ifdef HAVE_TLS +#include "uhttpd-tls.h" +#endif + + +static int run = 1; + +static void uh_sigterm(int sig) +{ + run = 0; +} + +static int uh_socket_bind( + fd_set *serv_fds, int *max_fd, const char *host, const char *port, + struct addrinfo *hints, int do_tls, struct config *conf +) { + int sock = -1; + int yes = 1; + int status; + int bound = 0; + + struct listener *l = NULL; + struct addrinfo *addrs = NULL, *p = NULL; + + if( (status = getaddrinfo(host, port, hints, &addrs)) != 0 ) + { + fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(status)); + } + + /* try to bind a new socket to each found address */ + for( p = addrs; p; p = p->ai_next ) + { + /* get the socket */ + if( (sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1 ) + { + perror("socket()"); + goto error; + } + + /* "address already in use" */ + if( setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1 ) + { + perror("setsockopt()"); + goto error; + } + + /* required to get parallel v4 + v6 working */ + if( p->ai_family == AF_INET6 ) + { + if( setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)) == -1 ) + { + perror("setsockopt()"); + goto error; + } + } + + /* bind */ + if( bind(sock, p->ai_addr, p->ai_addrlen) == -1 ) + { + perror("bind()"); + goto error; + } + + /* listen */ + if( listen(sock, UH_LIMIT_CLIENTS) == -1 ) + { + perror("listen()"); + goto error; + } + + /* add listener to global list */ + if( ! (l = uh_listener_add(sock, conf)) ) + { + fprintf(stderr, + "uh_listener_add(): Can not create more than " + "%i listen sockets\n", UH_LIMIT_LISTENERS + ); + + goto error; + } + +#ifdef HAVE_TLS + /* init TLS */ + l->tls = do_tls ? conf->tls : NULL; +#endif + + /* add socket to server fd set */ + FD_SET(sock, serv_fds); + *max_fd = max(*max_fd, sock); + + bound++; + continue; + + error: + if( sock > 0 ) + close(sock); + } + + freeaddrinfo(addrs); + + return bound; +} + +static struct http_request * uh_http_header_parse(struct client *cl, char *buffer, int buflen) +{ + char *method = &buffer[0]; + char *path = NULL; + char *version = NULL; + + char *headers = NULL; + char *hdrname = NULL; + char *hdrdata = NULL; + + int i; + int hdrcount = 0; + + static struct http_request req; + + memset(&req, 0, sizeof(req)); + + + /* terminate initial header line */ + if( (headers = strfind(buffer, buflen, "\r\n", 2)) != NULL ) + { + buffer[buflen-1] = 0; + + *headers++ = 0; + *headers++ = 0; + + /* find request path */ + if( (path = strchr(buffer, ' ')) != NULL ) + *path++ = 0; + + /* find http version */ + if( (path != NULL) && ((version = strchr(path, ' ')) != NULL) ) + *version++ = 0; + + + /* check method */ + if( strcmp(method, "GET") && strcmp(method, "HEAD") && strcmp(method, "POST") ) + { + /* invalid method */ + uh_http_response(cl, 405, "Method Not Allowed"); + return NULL; + } + else + { + switch(method[0]) + { + case 'G': + req.method = UH_HTTP_MSG_GET; + break; + + case 'H': + req.method = UH_HTTP_MSG_HEAD; + break; + + case 'P': + req.method = UH_HTTP_MSG_POST; + break; + } + } + + /* check path */ + if( !path || !strlen(path) ) + { + /* malformed request */ + uh_http_response(cl, 400, "Bad Request"); + return NULL; + } + else + { + req.url = path; + } + + /* check version */ + if( strcmp(version, "HTTP/0.9") && strcmp(version, "HTTP/1.0") && strcmp(version, "HTTP/1.1") ) + { + /* unsupported version */ + uh_http_response(cl, 400, "Bad Request"); + return NULL; + } + else + { + req.version = strtof(&version[5], NULL); + } + + + /* process header fields */ + for( i = (int)(headers - buffer); i < buflen; i++ ) + { + /* found eol and have name + value, push out header tuple */ + if( hdrname && hdrdata && (buffer[i] == '\r' || buffer[i] == '\n') ) + { + buffer[i] = 0; + + /* store */ + if( (hdrcount + 1) < array_size(req.headers) ) + { + req.headers[hdrcount++] = hdrname; + req.headers[hdrcount++] = hdrdata; + + hdrname = hdrdata = NULL; + } + + /* too large */ + else + { + uh_http_response(cl, 413, "Request Entity Too Large"); + return NULL; + } + } + + /* have name but no value and found a colon, start of value */ + else if( hdrname && !hdrdata && ((i+2) < buflen) && + (buffer[i] == ':') && (buffer[i+1] == ' ') + ) { + buffer[i] = 0; + hdrdata = &buffer[i+2]; + } + + /* have no name and found [A-Z], start of name */ + else if( !hdrname && isalpha(buffer[i]) && isupper(buffer[i]) ) + { + hdrname = &buffer[i]; + } + } + + /* valid enough */ + return &req; + } + + /* Malformed request */ + uh_http_response(cl, 400, "Bad Request"); + return NULL; +} + + +static struct http_request * uh_http_header_recv(struct client *cl) +{ + char buffer[UH_LIMIT_MSGHEAD]; + char *bufptr = &buffer[0]; + char *idxptr = NULL; + + struct timeval timeout; + + fd_set reader; + + ssize_t blen = sizeof(buffer)-1; + ssize_t rlen = 0; + + + memset(buffer, 0, sizeof(buffer)); + + while( blen > 0 ) + { + FD_ZERO(&reader); + FD_SET(cl->socket, &reader); + + /* fail after 0.1s */ + timeout.tv_sec = 0; + timeout.tv_usec = 100000; + + /* check whether fd is readable */ + if( select(cl->socket + 1, &reader, NULL, NULL, &timeout) > 0 ) + { + /* receive data */ + rlen = uh_tcp_peek(cl, bufptr, blen); + + if( rlen > 0 ) + { + if( (idxptr = strfind(buffer, sizeof(buffer), "\r\n\r\n", 4)) ) + { + blen -= uh_tcp_recv(cl, bufptr, (int)(idxptr - bufptr) + 4); + + /* header read complete ... */ + return uh_http_header_parse(cl, buffer, sizeof(buffer) - blen - 1); + } + else + { + rlen = uh_tcp_recv(cl, bufptr, rlen); + blen -= rlen; + bufptr += rlen; + } + } + else + { + /* invalid request (unexpected eof/timeout) */ + uh_http_response(cl, 408, "Request Timeout"); + return NULL; + } + } + else + { + /* invalid request (unexpected eof/timeout) */ + uh_http_response(cl, 408, "Request Timeout"); + return NULL; + } + } + + /* request entity too large */ + uh_http_response(cl, 413, "Request Entity Too Large"); + return NULL; +} + +static int uh_docroot_resolve(const char *path, char *buf) +{ + char curpath[PATH_MAX]; + + if( ! getcwd(curpath, sizeof(curpath)) ) + { + perror("getcwd()"); + return 0; + } + + if( chdir(path) || !getcwd(buf, PATH_MAX) ) + { + return 0; + } + else + { + buf[strlen(buf)] = '/'; + } + + if( chdir(curpath) ) + { + perror("chdir()"); + return 0; + } + + return 1; +} + + +int main (int argc, char **argv) +{ +#ifdef HAVE_LUA + /* init Lua runtime */ + lua_State *L; +#endif + + /* master file descriptor list */ + fd_set used_fds, serv_fds, read_fds; + + /* working structs */ + struct addrinfo hints; + struct http_request *req; + struct client *cl; + struct sigaction sa; + struct config conf; + + /* maximum file descriptor number */ + int new_fd, cur_fd, max_fd = 0; + int keys = 0; + int bound = 0; + int nofork = 0; + + /* args */ + char opt; + char bind[128]; + char *port = NULL; + + /* clear the master and temp sets */ + FD_ZERO(&used_fds); + FD_ZERO(&serv_fds); + FD_ZERO(&read_fds); + + /* handle SIGPIPE, SIGCHILD */ + sa.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &sa, NULL); + sigaction(SIGCHLD, &sa, NULL); + + sa.sa_handler = uh_sigterm; + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, 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 + /* init SSL context */ + if( ! (conf.tls = uh_tls_ctx_init()) ) + { + fprintf(stderr, "Failed to initalize SSL context\n"); + exit(1); + } +#endif + + while( (opt = getopt(argc, argv, "fC:K:p:s:h:c:l:L:d:")) > 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; + } + + /* bind sockets */ + bound += uh_socket_bind( + &serv_fds, &max_fd, bind[0] ? bind : NULL, port, &hints, + (opt == 's') ? 1 : 0, &conf + ); + + break; + +#ifdef HAVE_TLS + /* certificate */ + case 'C': + if( SSL_CTX_use_certificate_file(conf.tls, optarg, SSL_FILETYPE_ASN1) < 1 ) + { + fprintf(stderr, "Invalid certificate file given\n"); + exit(1); + } + + keys++; + break; + + /* key */ + case 'K': + if( SSL_CTX_use_PrivateKey_file(conf.tls, optarg, SSL_FILETYPE_ASN1) < 1 ) + { + fprintf(stderr, "Invalid private key file given\n"); + exit(1); + } + + keys++; + break; +#endif + + /* docroot */ + case 'h': + if( ! uh_docroot_resolve(optarg, conf.docroot) ) + { + fprintf(stderr, "Invalid directory: %s\n", optarg); + exit(1); + } + break; + +#ifdef HAVE_CGI + /* cgi prefix */ + case 'c': + 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 + + /* 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; + + default: + fprintf(stderr, + "Usage: %s -p [addr:]port [-h docroot]\n" + " -p Bind to specified address and port, multiple allowed\n" +#ifdef HAVE_TLS + " -s Like -p but provide HTTPS on this port\n" + " -C ASN.1 server certificate file\n" + " -K ASN.1 server private key file\n" +#endif + " -h Specify the document root, default is '.'\n" + " -f Do not fork to background\n" +#ifdef HAVE_LUA + " -l URL prefix for Lua handler, default is '/lua'\n" + " -L Lua handler script, default is './lua/handler.lua'\n" +#endif +#ifdef HAVE_CGI + " -c URL prefix for CGI handler, default is '/cgi-bin'\n" +#endif + " -d URL decode given string\n" + "\n", argv[0] + ); + + exit(1); + } + } + +#ifdef HAVE_TLS + if( keys < 2 ) + { + fprintf(stderr, "Missing private key or certificate file\n"); + exit(1); + } +#endif + + if( bound < 1 ) + { + fprintf(stderr, "No sockets bound, unable to continue\n"); + exit(1); + } + + /* default docroot */ + if( !conf.docroot[0] && !uh_docroot_resolve(".", conf.docroot) ) + { + fprintf(stderr, "Can not determine default document root\n"); + exit(1); + } + +#ifdef HAVE_LUA + /* default lua prefix and handler */ + if( ! conf.lua_handler ) + conf.lua_handler = "./lua/handler.lua"; + + if( ! conf.lua_prefix ) + conf.lua_prefix = "/lua"; +#endif + +#ifdef HAVE_CGI + /* default cgi prefix */ + if( ! conf.cgi_prefix ) + conf.cgi_prefix = "/cgi-bin"; +#endif + +#ifdef HAVE_LUA + /* init Lua runtime */ + L = uh_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 */ + uh_tls_client_accept(cl); +#endif + + /* add client socket to global fdset */ + FD_SET(new_fd, &used_fds); + 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 and dispatch request */ + if( (req = uh_http_header_recv(cl)) != NULL ) + { +#ifdef HAVE_CGI + if( strstr(req->url, conf.cgi_prefix) == req->url ) + { + uh_cgi_request(cl, req); + } + else +#endif + +#ifdef HAVE_LUA + if( strstr(req->url, conf.lua_prefix) == req->url ) + { + uh_lua_request(cl, req, L); + } + else +#endif + + { + uh_file_request(cl, req); + } + } + + +#ifdef HAVE_TLS + /* free client tls context */ + uh_tls_client_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 */ + lua_close(L); +#endif + + return 0; +} + |