From: Jo-Philipp Wich Date: Mon, 6 Sep 2010 20:49:31 +0000 (+0000) Subject: [backfire] merge r22630, r22692, r22805 X-Git-Url: http://git.openwrt.org/?p=openwrt%2Fsvn-archive%2Farchive.git;a=commitdiff_plain;hb=f992ce9289d63cbb41b4c3b93c236017977683de [backfire] merge r22630, r22692, r22805 SVN-Revision: 22962 --- diff --git a/package/uhttpd/Makefile b/package/uhttpd/Makefile index cb85ad88c0..086c294580 100644 --- a/package/uhttpd/Makefile +++ b/package/uhttpd/Makefile @@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=uhttpd -PKG_RELEASE:=14 +PKG_RELEASE:=17 PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME) PKG_BUILD_DEPENDS := libcyassl liblua diff --git a/package/uhttpd/files/uhttpd.config b/package/uhttpd/files/uhttpd.config index 534e8f8b29..a29910a65f 100644 --- a/package/uhttpd/files/uhttpd.config +++ b/package/uhttpd/files/uhttpd.config @@ -27,6 +27,13 @@ config uhttpd main # Default is /cgi-bin option cgi_prefix /cgi-bin + # List of extension->interpreter mappings. + # Files with an associated interpreter can + # be called outside of the CGI prefix and do + # not need to be executable. +# list interpreter ".php=/usr/bin/php-cgi" +# list interpreter ".cgi=/usr/bin/perl" + # Lua url prefix and handler script. # Lua support is disabled if no prefix given. # option lua_prefix /luci diff --git a/package/uhttpd/files/uhttpd.init b/package/uhttpd/files/uhttpd.init index 8221d859bf..f8f1754e90 100755 --- a/package/uhttpd/files/uhttpd.init +++ b/package/uhttpd/files/uhttpd.init @@ -56,7 +56,7 @@ start_instance() local cfg="$1" local realm="$(uci_get system.@system[0].hostname)" - local listen http https + local listen http https interpreter path append_arg "$cfg" home "-h" append_arg "$cfg" realm "-r" "${realm:-OpenWrt}" @@ -78,6 +78,11 @@ start_instance() append UHTTPD_ARGS "-p $listen" done + config_get interpreter "$cfg" interpreter + for path in $interpreter; do + append UHTTPD_ARGS "-i $path" + done + config_get https "$cfg" listen_https config_get UHTTPD_KEY "$cfg" key /etc/uhttpd.key config_get UHTTPD_CERT "$cfg" cert /etc/uhttpd.crt diff --git a/package/uhttpd/src/uhttpd-cgi.c b/package/uhttpd/src/uhttpd-cgi.c index 0861249162..8c17251b31 100644 --- a/package/uhttpd/src/uhttpd-cgi.c +++ b/package/uhttpd/src/uhttpd-cgi.c @@ -135,9 +135,11 @@ static int uh_cgi_error_500(struct client *cl, struct http_request *req, const c } -void uh_cgi_request(struct client *cl, struct http_request *req, struct path_info *pi) -{ - int i, hdroff, bufoff; +void uh_cgi_request( + struct client *cl, struct http_request *req, + struct path_info *pi, struct interpreter *ip +) { + int i, hdroff, bufoff, rv; int hdrlen = 0; int buflen = 0; int fd_max = 0; @@ -199,9 +201,9 @@ void uh_cgi_request(struct client *cl, struct http_request *req, struct path_inf dup2(rfd[1], 1); dup2(wfd[0], 0); - /* check for regular, world-executable file */ - if( (pi->stat.st_mode & S_IFREG) && - (pi->stat.st_mode & S_IXOTH) + /* check for regular, world-executable file _or_ interpreter */ + if( ((pi->stat.st_mode & S_IFREG) && + (pi->stat.st_mode & S_IXOTH)) || (ip != NULL) ) { /* build environment */ clearenv(); @@ -320,14 +322,17 @@ void uh_cgi_request(struct client *cl, struct http_request *req, struct path_inf if( chdir(pi->root) ) perror("chdir()"); - execl(pi->phys, pi->phys, NULL); + if( ip != NULL ) + execl(ip->path, ip->path, pi->phys, NULL); + else + execl(pi->phys, pi->phys, NULL); /* in case it fails ... */ printf( "Status: 500 Internal Server Error\r\n\r\n" "Unable to launch the requested CGI program:\n" " %s: %s\n", - pi->phys, strerror(errno) + ip ? ip->path : pi->phys, strerror(errno) ); } @@ -371,12 +376,6 @@ void uh_cgi_request(struct client *cl, struct http_request *req, struct path_inf memset(hdr, 0, sizeof(hdr)); - timeout.tv_sec = cl->server->conf->script_timeout; - timeout.tv_usec = 0; - -#define ensure(x) \ - do { if( x < 0 ) goto out; } while(0) - /* I/O loop, watch our pipe ends and dispatch child reads/writes from/to socket */ while( 1 ) { @@ -386,11 +385,21 @@ void uh_cgi_request(struct client *cl, struct http_request *req, struct path_inf FD_SET(rfd[0], &reader); FD_SET(wfd[1], &writer); + timeout.tv_sec = (header_sent < 1) ? cl->server->conf->script_timeout : 3; + timeout.tv_usec = 0; + + ensure_out(rv = select_intr(fd_max, &reader, + (content_length > -1) ? &writer : NULL, NULL, &timeout)); + + /* timeout */ + if( rv == 0 ) + { + ensure_out(kill(child, 0)); + } + /* wait until we can read or write or both */ - if( select_intr(fd_max, &reader, - (content_length > -1) ? &writer : NULL, NULL, - (header_sent < 1) ? &timeout : NULL) > 0 - ) { + else if( rv > 0 ) + { /* ready to write to cgi program */ if( FD_ISSET(wfd[1], &writer) ) { @@ -398,7 +407,10 @@ void uh_cgi_request(struct client *cl, struct http_request *req, struct path_inf if( content_length > 0 ) { /* read it from socket ... */ - if( (buflen = uh_tcp_recv(cl, buf, min(content_length, sizeof(buf)))) > 0 ) + ensure_out(buflen = uh_tcp_recv(cl, buf, + min(content_length, sizeof(buf)))); + + if( buflen > 0 ) { /* ... and write it to child's stdin */ if( write(wfd[1], buf, buflen) < 0 ) @@ -451,7 +463,7 @@ void uh_cgi_request(struct client *cl, struct http_request *req, struct path_inf if( (res = uh_cgi_header_parse(hdr, hdrlen, &hdroff)) != NULL ) { /* write status */ - ensure(uh_http_sendf(cl, NULL, + ensure_out(uh_http_sendf(cl, NULL, "HTTP/%.1f %03d %s\r\n" "Connection: close\r\n", req->version, res->statuscode, @@ -461,7 +473,7 @@ void uh_cgi_request(struct client *cl, struct http_request *req, struct path_inf if( !uh_cgi_header_lookup(res, "Location") && !uh_cgi_header_lookup(res, "Content-Type") ) { - ensure(uh_http_send(cl, NULL, + ensure_out(uh_http_send(cl, NULL, "Content-Type: text/plain\r\n", -1)); } @@ -469,32 +481,32 @@ void uh_cgi_request(struct client *cl, struct http_request *req, struct path_inf if( (req->version > 1.0) && !uh_cgi_header_lookup(res, "Transfer-Encoding") ) { - ensure(uh_http_send(cl, NULL, + ensure_out(uh_http_send(cl, NULL, "Transfer-Encoding: chunked\r\n", -1)); } /* write headers from CGI program */ foreach_header(i, res->headers) { - ensure(uh_http_sendf(cl, NULL, "%s: %s\r\n", + ensure_out(uh_http_sendf(cl, NULL, "%s: %s\r\n", res->headers[i], res->headers[i+1])); } /* terminate header */ - ensure(uh_http_send(cl, NULL, "\r\n", -1)); + ensure_out(uh_http_send(cl, NULL, "\r\n", -1)); /* push out remaining head buffer */ if( hdroff < hdrlen ) - ensure(uh_http_send(cl, req, &hdr[hdroff], hdrlen - hdroff)); + ensure_out(uh_http_send(cl, req, &hdr[hdroff], hdrlen - hdroff)); } /* ... failed and head buffer exceeded */ else if( hdrlen >= sizeof(hdr) ) { - ensure(uh_cgi_error_500(cl, req, + ensure_out(uh_cgi_error_500(cl, req, "The CGI program generated an invalid response:\n\n")); - ensure(uh_http_send(cl, req, hdr, hdrlen)); + ensure_out(uh_http_send(cl, req, hdr, hdrlen)); } /* ... failed but free buffer space, try again */ @@ -505,7 +517,7 @@ void uh_cgi_request(struct client *cl, struct http_request *req, struct path_inf /* push out remaining read buffer */ if( bufoff < buflen ) - ensure(uh_http_send(cl, req, &buf[bufoff], buflen - bufoff)); + ensure_out(uh_http_send(cl, req, &buf[bufoff], buflen - bufoff)); header_sent = 1; continue; @@ -513,7 +525,7 @@ void uh_cgi_request(struct client *cl, struct http_request *req, struct path_inf /* headers complete, pass through buffer to socket */ - ensure(uh_http_send(cl, req, buf, buflen)); + ensure_out(uh_http_send(cl, req, buf, buflen)); } /* looks like eof from child */ @@ -533,7 +545,7 @@ void uh_cgi_request(struct client *cl, struct http_request *req, struct path_inf * build the required headers here. */ - ensure(uh_http_sendf(cl, NULL, + ensure_out(uh_http_sendf(cl, NULL, "HTTP/%.1f 200 OK\r\n" "Content-Type: text/plain\r\n" "%s\r\n", @@ -541,11 +553,11 @@ void uh_cgi_request(struct client *cl, struct http_request *req, struct path_inf ? "Transfer-Encoding: chunked\r\n" : "" )); - ensure(uh_http_send(cl, req, hdr, hdrlen)); + ensure_out(uh_http_send(cl, req, hdr, hdrlen)); } /* send final chunk if we're in chunked transfer mode */ - ensure(uh_http_send(cl, req, "", 0)); + ensure_out(uh_http_send(cl, req, "", 0)); break; } } @@ -556,13 +568,13 @@ void uh_cgi_request(struct client *cl, struct http_request *req, struct path_inf { if( (errno != EINTR) && ! header_sent ) { - ensure(uh_http_sendhf(cl, 504, "Gateway Timeout", + ensure_out(uh_http_sendhf(cl, 504, "Gateway Timeout", "The CGI script took too long to produce " "a response")); } /* send final chunk if we're in chunked transfer mode */ - ensure(uh_http_send(cl, req, "", 0)); + ensure_out(uh_http_send(cl, req, "", 0)); break; } diff --git a/package/uhttpd/src/uhttpd-cgi.h b/package/uhttpd/src/uhttpd-cgi.h index c90557d8fd..cb84dae0c6 100644 --- a/package/uhttpd/src/uhttpd-cgi.h +++ b/package/uhttpd/src/uhttpd-cgi.h @@ -25,7 +25,8 @@ #include void uh_cgi_request( - struct client *cl, struct http_request *req, struct path_info *pi + struct client *cl, struct http_request *req, + struct path_info *pi, struct interpreter *ip ); #endif diff --git a/package/uhttpd/src/uhttpd-file.c b/package/uhttpd/src/uhttpd-file.c index ef9a77b6cc..fda86d7263 100644 --- a/package/uhttpd/src/uhttpd-file.c +++ b/package/uhttpd/src/uhttpd-file.c @@ -97,10 +97,8 @@ static char * uh_file_header_lookup(struct http_request *req, const char *name) return NULL; } -#define ensure_ret(x) \ - do { if( x < 0 ) return; } while(0) -static void uh_file_response_ok_hdrs(struct client *cl, struct http_request *req, struct stat *s) +static int uh_file_response_ok_hdrs(struct client *cl, struct http_request *req, struct stat *s) { ensure_ret(uh_http_sendf(cl, NULL, "Connection: close\r\n")); @@ -110,29 +108,29 @@ static void uh_file_response_ok_hdrs(struct client *cl, struct http_request *req ensure_ret(uh_http_sendf(cl, NULL, "Last-Modified: %s\r\n", uh_file_unix2date(s->st_mtime))); } - ensure_ret(uh_http_sendf(cl, NULL, "Date: %s\r\n", uh_file_unix2date(time(NULL)))); + return uh_http_sendf(cl, NULL, "Date: %s\r\n", uh_file_unix2date(time(NULL))); } -static void uh_file_response_200(struct client *cl, struct http_request *req, struct stat *s) +static int uh_file_response_200(struct client *cl, struct http_request *req, struct stat *s) { ensure_ret(uh_http_sendf(cl, NULL, "HTTP/%.1f 200 OK\r\n", req->version)); - uh_file_response_ok_hdrs(cl, req, s); + return uh_file_response_ok_hdrs(cl, req, s); } -static void uh_file_response_304(struct client *cl, struct http_request *req, struct stat *s) +static int uh_file_response_304(struct client *cl, struct http_request *req, struct stat *s) { ensure_ret(uh_http_sendf(cl, NULL, "HTTP/%.1f 304 Not Modified\r\n", req->version)); - uh_file_response_ok_hdrs(cl, req, s); + return uh_file_response_ok_hdrs(cl, req, s); } -static void uh_file_response_412(struct client *cl, struct http_request *req) +static int uh_file_response_412(struct client *cl, struct http_request *req) { - ensure_ret(uh_http_sendf(cl, NULL, + return uh_http_sendf(cl, NULL, "HTTP/%.1f 412 Precondition Failed\r\n" - "Connection: close\r\n", req->version)); + "Connection: close\r\n", req->version); } -static int uh_file_if_match(struct client *cl, struct http_request *req, struct stat *s) +static int uh_file_if_match(struct client *cl, struct http_request *req, struct stat *s, int *ok) { const char *tag = uh_file_mktag(s); char *hdr = uh_file_header_lookup(req, "If-Match"); @@ -152,43 +150,44 @@ static int uh_file_if_match(struct client *cl, struct http_request *req, struct } else if( !strcmp(p, "*") || !strcmp(p, tag) ) { - return 1; + *ok = 1; + return *ok; } } - uh_file_response_412(cl, req); - return 0; + *ok = 0; + ensure_ret(uh_file_response_412(cl, req)); + return *ok; } - return 1; + *ok = 1; + return *ok; } -static int uh_file_if_modified_since(struct client *cl, struct http_request *req, struct stat *s) +static int uh_file_if_modified_since(struct client *cl, struct http_request *req, struct stat *s, int *ok) { char *hdr = uh_file_header_lookup(req, "If-Modified-Since"); + *ok = 1; if( hdr ) { - if( uh_file_date2unix(hdr) < s->st_mtime ) + if( uh_file_date2unix(hdr) >= s->st_mtime ) { - return 1; - } - else - { - uh_file_response_304(cl, req, s); - return 0; + *ok = 0; + ensure_ret(uh_file_response_304(cl, req, s)); } } - return 1; + return *ok; } -static int uh_file_if_none_match(struct client *cl, struct http_request *req, struct stat *s) +static int uh_file_if_none_match(struct client *cl, struct http_request *req, struct stat *s, int *ok) { const char *tag = uh_file_mktag(s); char *hdr = uh_file_header_lookup(req, "If-None-Match"); char *p; int i; + *ok = 1; if( hdr ) { @@ -203,53 +202,54 @@ static int uh_file_if_none_match(struct client *cl, struct http_request *req, st } else if( !strcmp(p, "*") || !strcmp(p, tag) ) { + *ok = 0; + if( (req->method == UH_HTTP_MSG_GET) || (req->method == UH_HTTP_MSG_HEAD) ) - uh_file_response_304(cl, req, s); + ensure_ret(uh_file_response_304(cl, req, s)); else - uh_file_response_412(cl, req); + ensure_ret(uh_file_response_412(cl, req)); - return 0; + break; } } } - return 1; + return *ok; } -static int uh_file_if_range(struct client *cl, struct http_request *req, struct stat *s) +static int uh_file_if_range(struct client *cl, struct http_request *req, struct stat *s, int *ok) { char *hdr = uh_file_header_lookup(req, "If-Range"); + *ok = 1; if( hdr ) { - uh_file_response_412(cl, req); - return 0; + *ok = 0; + ensure_ret(uh_file_response_412(cl, req)); } - return 1; + return *ok; } -static int uh_file_if_unmodified_since(struct client *cl, struct http_request *req, struct stat *s) +static int uh_file_if_unmodified_since(struct client *cl, struct http_request *req, struct stat *s, int *ok) { char *hdr = uh_file_header_lookup(req, "If-Unmodified-Since"); + *ok = 1; if( hdr ) { if( uh_file_date2unix(hdr) <= s->st_mtime ) { - uh_file_response_412(cl, req); - return 0; + *ok = 0; + ensure_ret(uh_file_response_412(cl, req)); } } - return 1; + return *ok; } -#define ensure_out(x) \ - do { if( x < 0 ) goto out; } while(0) - static int uh_file_scandir_filter_dir(const struct dirent *e) { return strcmp(e->d_name, ".") ? 1 : 0; @@ -335,6 +335,7 @@ out: void uh_file_request(struct client *cl, struct http_request *req, struct path_info *pi) { int rlen; + int ok = 1; int fd = -1; char buf[UH_LIMIT_MSGHEAD]; @@ -342,15 +343,16 @@ void uh_file_request(struct client *cl, struct http_request *req, struct path_in if( (pi->stat.st_mode & S_IFREG) && ((fd = open(pi->phys, O_RDONLY)) > 0) ) { /* test preconditions */ - if( - uh_file_if_modified_since(cl, req, &pi->stat) && - uh_file_if_match(cl, req, &pi->stat) && - uh_file_if_range(cl, req, &pi->stat) && - uh_file_if_unmodified_since(cl, req, &pi->stat) && - uh_file_if_none_match(cl, req, &pi->stat) - ) { + if(ok) ensure_out(uh_file_if_modified_since(cl, req, &pi->stat, &ok)); + if(ok) ensure_out(uh_file_if_match(cl, req, &pi->stat, &ok)); + if(ok) ensure_out(uh_file_if_range(cl, req, &pi->stat, &ok)); + if(ok) ensure_out(uh_file_if_unmodified_since(cl, req, &pi->stat, &ok)); + if(ok) ensure_out(uh_file_if_none_match(cl, req, &pi->stat, &ok)); + + if( ok > 0 ) + { /* write status */ - uh_file_response_200(cl, req, &pi->stat); + ensure_out(uh_file_response_200(cl, req, &pi->stat)); ensure_out(uh_http_sendf(cl, NULL, "Content-Type: %s\r\n", uh_file_mime_lookup(pi->name))); ensure_out(uh_http_sendf(cl, NULL, "Content-Length: %i\r\n", pi->stat.st_size)); @@ -385,7 +387,7 @@ void uh_file_request(struct client *cl, struct http_request *req, struct path_in else if( (pi->stat.st_mode & S_IFDIR) && !cl->server->conf->no_dirlists ) { /* write status */ - uh_file_response_200(cl, req, NULL); + ensure_out(uh_file_response_200(cl, req, NULL)); if( req->version > 1.0 ) ensure_out(uh_http_send(cl, NULL, "Transfer-Encoding: chunked\r\n", -1)); diff --git a/package/uhttpd/src/uhttpd-tls.c b/package/uhttpd/src/uhttpd-tls.c index 26143ddf71..008f8e0df6 100644 --- a/package/uhttpd/src/uhttpd-tls.c +++ b/package/uhttpd/src/uhttpd-tls.c @@ -70,12 +70,14 @@ void uh_tls_client_accept(struct client *c) int uh_tls_client_recv(struct client *c, void *buf, int len) { - return SSL_read(c->tls, buf, len); + int rv = SSL_read(c->tls, buf, len); + return (rv > 0) ? rv : -1; } int uh_tls_client_send(struct client *c, void *buf, int len) { - return SSL_write(c->tls, buf, len); + int rv = SSL_write(c->tls, buf, len); + return (rv > 0) ? rv : -1; } void uh_tls_client_close(struct client *c) diff --git a/package/uhttpd/src/uhttpd-utils.c b/package/uhttpd/src/uhttpd-utils.c index 4a1423c715..3821eb54cb 100644 --- a/package/uhttpd/src/uhttpd-utils.c +++ b/package/uhttpd/src/uhttpd-utils.c @@ -112,6 +112,7 @@ int select_intr(int n, fd_set *r, fd_set *w, fd_set *e, struct timeval *t) /* unblock SIGCHLD */ sigemptyset(&ssn); sigaddset(&ssn, SIGCHLD); + sigaddset(&ssn, SIGPIPE); sigprocmask(SIG_UNBLOCK, &ssn, &sso); rv = select(n, r, w, e, t); @@ -193,8 +194,6 @@ int uh_tcp_recv(struct client *cl, char *buf, int len) return sz; } -#define ensure(x) \ - do { if( x < 0 ) return -1; } while(0) int uh_http_sendhf(struct client *cl, int code, const char *summary, const char *fmt, ...) { @@ -211,14 +210,14 @@ int uh_http_sendhf(struct client *cl, int code, const char *summary, const char code, summary ); - ensure(uh_tcp_send(cl, buffer, len)); + ensure_ret(uh_tcp_send(cl, buffer, len)); va_start(ap, fmt); len = vsnprintf(buffer, sizeof(buffer), fmt, ap); va_end(ap); - ensure(uh_http_sendc(cl, buffer, len)); - ensure(uh_http_sendc(cl, NULL, 0)); + ensure_ret(uh_http_sendc(cl, buffer, len)); + ensure_ret(uh_http_sendc(cl, NULL, 0)); return 0; } @@ -235,13 +234,13 @@ int uh_http_sendc(struct client *cl, const char *data, int len) if( len > 0 ) { clen = snprintf(chunk, sizeof(chunk), "%X\r\n", len); - ensure(uh_tcp_send(cl, chunk, clen)); - ensure(uh_tcp_send(cl, data, len)); - ensure(uh_tcp_send(cl, "\r\n", 2)); + ensure_ret(uh_tcp_send(cl, chunk, clen)); + ensure_ret(uh_tcp_send(cl, data, len)); + ensure_ret(uh_tcp_send(cl, "\r\n", 2)); } else { - ensure(uh_tcp_send(cl, "0\r\n\r\n", 5)); + ensure_ret(uh_tcp_send(cl, "0\r\n\r\n", 5)); } return 0; @@ -259,9 +258,9 @@ int uh_http_sendf( va_end(ap); if( (req != NULL) && (req->version > 1.0) ) - ensure(uh_http_sendc(cl, buffer, len)); + ensure_ret(uh_http_sendc(cl, buffer, len)); else if( len > 0 ) - ensure(uh_tcp_send(cl, buffer, len)); + ensure_ret(uh_tcp_send(cl, buffer, len)); return 0; } @@ -273,9 +272,9 @@ int uh_http_send( len = strlen(buf); if( (req != NULL) && (req->version > 1.0) ) - ensure(uh_http_sendc(cl, buf, len)); + ensure_ret(uh_http_sendc(cl, buf, len)); else if( len > 0 ) - ensure(uh_tcp_send(cl, buf, len)); + ensure_ret(uh_tcp_send(cl, buf, len)); return 0; } @@ -605,8 +604,7 @@ struct path_info * uh_path_lookup(struct client *cl, const char *url) } -static char uh_realms[UH_LIMIT_AUTHREALMS * sizeof(struct auth_realm)] = { 0 }; -static int uh_realm_count = 0; +static struct auth_realm *uh_realms = NULL; struct auth_realm * uh_auth_add(char *path, char *user, char *pass) { @@ -614,11 +612,8 @@ struct auth_realm * uh_auth_add(char *path, char *user, char *pass) struct passwd *pwd; struct spwd *spwd; - if( uh_realm_count < UH_LIMIT_AUTHREALMS ) + if((new = (struct auth_realm *)malloc(sizeof(struct auth_realm))) != NULL) { - new = (struct auth_realm *) - &uh_realms[uh_realm_count * sizeof(struct auth_realm)]; - memset(new, 0, sizeof(struct auth_realm)); memcpy(new->path, path, @@ -643,7 +638,7 @@ struct auth_realm * uh_auth_add(char *path, char *user, char *pass) ) { memcpy(new->pass, pwd->pw_passwd, min(strlen(pwd->pw_passwd), sizeof(new->pass) - 1)); - } + } } /* ordinary pwd */ @@ -655,9 +650,13 @@ struct auth_realm * uh_auth_add(char *path, char *user, char *pass) if( new->pass[0] ) { - uh_realm_count++; + new->next = uh_realms; + uh_realms = new; + return new; } + + free(new); } return NULL; @@ -677,11 +676,8 @@ int uh_auth_check( protected = 0; /* check whether at least one realm covers the requested url */ - for( i = 0; i < uh_realm_count; i++ ) + for( realm = uh_realms; realm; realm = realm->next ) { - realm = (struct auth_realm *) - &uh_realms[i * sizeof(struct auth_realm)]; - rlen = strlen(realm->path); if( (plen >= rlen) && !strncasecmp(pi->name, realm->path, rlen) ) @@ -721,11 +717,8 @@ int uh_auth_check( if( user && pass ) { /* find matching realm */ - for( i = 0, realm = NULL; i < uh_realm_count; i++ ) + for( realm = uh_realms; realm; realm = realm->next ) { - realm = (struct auth_realm *) - &uh_realms[i * sizeof(struct auth_realm)]; - rlen = strlen(realm->path); if( (plen >= rlen) && @@ -735,8 +728,6 @@ int uh_auth_check( req->realm = realm; break; } - - realm = NULL; } /* found a realm matching the username */ @@ -769,22 +760,17 @@ int uh_auth_check( } -static char uh_listeners[UH_LIMIT_LISTENERS * sizeof(struct listener)] = { 0 }; -static char uh_clients[UH_LIMIT_CLIENTS * sizeof(struct client)] = { 0 }; - -static int uh_listener_count = 0; -static int uh_client_count = 0; - +static struct listener *uh_listeners = NULL; +static struct client *uh_clients = NULL; struct listener * uh_listener_add(int sock, struct config *conf) { struct listener *new = NULL; socklen_t sl; - if( uh_listener_count < UH_LIMIT_LISTENERS ) + if( (new = (struct listener *)malloc(sizeof(struct listener))) != NULL ) { - new = (struct listener *) - &uh_listeners[uh_listener_count * sizeof(struct listener)]; + memset(new, 0, sizeof(struct listener)); new->socket = sock; new->conf = conf; @@ -794,24 +780,22 @@ struct listener * uh_listener_add(int sock, struct config *conf) memset(&(new->addr), 0, sl); getsockname(sock, (struct sockaddr *) &(new->addr), &sl); - uh_listener_count++; + new->next = uh_listeners; + uh_listeners = new; + + return new; } - return new; + return NULL; } struct listener * uh_listener_lookup(int sock) { struct listener *cur = NULL; - int i; - - for( i = 0; i < uh_listener_count; i++ ) - { - cur = (struct listener *) &uh_listeners[i * sizeof(struct listener)]; + for( cur = uh_listeners; cur; cur = cur->next ) if( cur->socket == sock ) return cur; - } return NULL; } @@ -822,10 +806,9 @@ struct client * uh_client_add(int sock, struct listener *serv) struct client *new = NULL; socklen_t sl; - if( uh_client_count < UH_LIMIT_CLIENTS ) + if( (new = (struct client *)malloc(sizeof(struct client))) != NULL ) { - new = (struct client *) - &uh_clients[uh_client_count * sizeof(struct client)]; + memset(new, 0, sizeof(struct client)); new->socket = sock; new->server = serv; @@ -840,7 +823,8 @@ struct client * uh_client_add(int sock, struct listener *serv) memset(&(new->servaddr), 0, sl); getsockname(sock, (struct sockaddr *) &(new->servaddr), &sl); - uh_client_count++; + new->next = uh_clients; + uh_clients = new; } return new; @@ -849,30 +833,72 @@ struct client * uh_client_add(int sock, struct listener *serv) struct client * uh_client_lookup(int sock) { struct client *cur = NULL; - int i; - - for( i = 0; i < uh_client_count; i++ ) - { - cur = (struct client *) &uh_clients[i * sizeof(struct client)]; + for( cur = uh_clients; cur; cur = cur->next ) if( cur->socket == sock ) return cur; - } return NULL; } void uh_client_remove(int sock) { - struct client *del = uh_client_lookup(sock); + struct client *cur = NULL; + struct client *prv = NULL; + + for( cur = uh_clients; cur; prv = cur, cur = cur->next ) + { + if( cur->socket == sock ) + { + if( prv ) + prv->next = cur->next; + else + uh_clients = cur->next; + + free(cur); + break; + } + } +} + + +#ifdef HAVE_CGI +static struct interpreter *uh_interpreters = NULL; + +struct interpreter * uh_interpreter_add(const char *extn, const char *path) +{ + struct interpreter *new = NULL; - if( del ) + if( (new = (struct interpreter *) + malloc(sizeof(struct interpreter))) != NULL ) { - memmove(del, del + 1, - sizeof(uh_clients) - (int)((char *)del - uh_clients) - sizeof(struct client)); + memset(new, 0, sizeof(struct interpreter)); + + memcpy(new->extn, extn, min(strlen(extn), sizeof(new->extn)-1)); + memcpy(new->path, path, min(strlen(path), sizeof(new->path)-1)); - uh_client_count--; + new->next = uh_interpreters; + uh_interpreters = new; + + return new; } + + return NULL; } +struct interpreter * uh_interpreter_lookup(const char *path) +{ + struct interpreter *cur = NULL; + const char *e; + + for( cur = uh_interpreters; cur; cur = cur->next ) + { + e = &path[max(strlen(path) - strlen(cur->extn), 0)]; + + if( !strcmp(e, cur->extn) ) + return cur; + } + return NULL; +} +#endif diff --git a/package/uhttpd/src/uhttpd-utils.h b/package/uhttpd/src/uhttpd-utils.h index 1b18265417..3514ce1cac 100644 --- a/package/uhttpd/src/uhttpd-utils.h +++ b/package/uhttpd/src/uhttpd-utils.h @@ -36,6 +36,13 @@ #define fd_cloexec(fd) \ fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC) +#define ensure_out(x) \ + do { if((x) < 0) goto out; } while(0) + +#define ensure_ret(x) \ + do { if((x) < 0) return -1; } while(0) + + struct path_info { char *root; char *phys; @@ -101,4 +108,9 @@ struct client * uh_client_add(int sock, struct listener *serv); struct client * uh_client_lookup(int sock); void uh_client_remove(int sock); +#ifdef HAVE_CGI +struct interpreter * uh_interpreter_add(const char *extn, const char *path); +struct interpreter * uh_interpreter_lookup(const char *path); +#endif + #endif diff --git a/package/uhttpd/src/uhttpd.c b/package/uhttpd/src/uhttpd.c index 764ff7d570..6f5e616345 100644 --- a/package/uhttpd/src/uhttpd.c +++ b/package/uhttpd/src/uhttpd.c @@ -51,8 +51,8 @@ static void uh_config_parse(struct config *conf) { FILE *c; char line[512]; - char *user = NULL; - char *pass = NULL; + char *col1 = NULL; + char *col2 = NULL; char *eol = NULL; const char *path = conf->file ? conf->file : "/etc/httpd.conf"; @@ -66,35 +66,52 @@ static void uh_config_parse(struct config *conf) { if( (line[0] == '/') && (strchr(line, ':') != NULL) ) { - if( !(user = strchr(line, ':')) || (*user++ = 0) || - !(pass = strchr(user, ':')) || (*pass++ = 0) || - !(eol = strchr(pass, '\n')) || (*eol++ = 0) ) + if( !(col1 = strchr(line, ':')) || (*col1++ = 0) || + !(col2 = strchr(col1, ':')) || (*col2++ = 0) || + !(eol = strchr(col2, '\n')) || (*eol++ = 0) ) continue; - if( !uh_auth_add(line, user, pass) ) + if( !uh_auth_add(line, col1, col2) ) { fprintf(stderr, "Notice: No password set for user %s, ignoring " - "authentication on %s\n", user, line + "authentication on %s\n", col1, line ); } } else if( !strncmp(line, "I:", 2) ) { - if( !(user = strchr(line, ':')) || (*user++ = 0) || - !(eol = strchr(user, '\n')) || (*eol++ = 0) ) + if( !(col1 = strchr(line, ':')) || (*col1++ = 0) || + !(eol = strchr(col1, '\n')) || (*eol++ = 0) ) continue; - conf->index_file = strdup(user); + conf->index_file = strdup(col1); } else if( !strncmp(line, "E404:", 5) ) { - if( !(user = strchr(line, ':')) || (*user++ = 0) || - !(eol = strchr(user, '\n')) || (*eol++ = 0) ) + if( !(col1 = strchr(line, ':')) || (*col1++ = 0) || + !(eol = strchr(col1, '\n')) || (*eol++ = 0) ) continue; - conf->error_handler = strdup(user); + conf->error_handler = strdup(col1); } +#ifdef HAVE_CGI + else if( (line[0] == '*') && (strchr(line, ':') != NULL) ) + { + if( !(col1 = strchr(line, '*')) || (*col1++ = 0) || + !(col2 = strchr(col1, ':')) || (*col2++ = 0) || + !(eol = strchr(col2, '\n')) || (*eol++ = 0) ) + continue; + + if( !uh_interpreter_add(col1, col2) ) + { + fprintf(stderr, + "Unable to add interpreter %s for extension %s: " + "Out of memory\n", col2, col1 + ); + } + } +#endif } fclose(c); @@ -110,6 +127,10 @@ static int uh_socket_bind( int status; int bound = 0; + int tcp_ka_idl = 1; + int tcp_ka_int = 1; + int tcp_ka_cnt = 3; + struct listener *l = NULL; struct addrinfo *addrs = NULL, *p = NULL; @@ -129,12 +150,22 @@ static int uh_socket_bind( } /* "address already in use" */ - if( setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1 ) + if( setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) ) { perror("setsockopt()"); goto error; } + /* TCP keep-alive */ + if( setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)) || + setsockopt(sock, SOL_TCP, TCP_KEEPIDLE, &tcp_ka_idl, sizeof(tcp_ka_idl)) || + setsockopt(sock, SOL_TCP, TCP_KEEPINTVL, &tcp_ka_int, sizeof(tcp_ka_int)) || + setsockopt(sock, SOL_TCP, TCP_KEEPCNT, &tcp_ka_cnt, sizeof(tcp_ka_cnt)) ) + { + fprintf(stderr, "Notice: Unable to enable TCP keep-alive: %s\n", + strerror(errno)); + } + /* required to get parallel v4 + v6 working */ if( p->ai_family == AF_INET6 ) { @@ -162,11 +193,7 @@ static int uh_socket_bind( /* 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 - ); - + fprintf(stderr, "uh_listener_add(): Failed to allocate memory\n"); goto error; } @@ -343,7 +370,6 @@ static struct http_request * uh_http_header_recv(struct client *cl) ssize_t blen = sizeof(buffer)-1; ssize_t rlen = 0; - memset(buffer, 0, sizeof(buffer)); while( blen > 0 ) @@ -359,44 +385,41 @@ static struct http_request * uh_http_header_recv(struct client *cl) if( select(cl->socket + 1, &reader, NULL, NULL, &timeout) > 0 ) { /* receive data */ - rlen = uh_tcp_peek(cl, bufptr, blen); + ensure_out(rlen = uh_tcp_peek(cl, bufptr, blen)); - if( rlen > 0 ) + if( (idxptr = strfind(buffer, sizeof(buffer), "\r\n\r\n", 4)) ) { - if( (idxptr = strfind(buffer, sizeof(buffer), "\r\n\r\n", 4)) ) - { - blen -= uh_tcp_recv(cl, bufptr, (int)(idxptr - bufptr) + 4); + ensure_out(rlen = 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; - } + /* header read complete ... */ + blen -= rlen; + return uh_http_header_parse(cl, buffer, + sizeof(buffer) - blen - 1); } else { - /* invalid request (unexpected eof/timeout) */ - uh_http_response(cl, 408, "Request Timeout"); - return NULL; + ensure_out(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; } } /* request entity too large */ uh_http_response(cl, 413, "Request Entity Too Large"); + +out: return NULL; } +#if defined(HAVE_LUA) || defined(HAVE_CGI) static int uh_path_match(const char *prefix, const char *url) { if( (strstr(url, prefix) == url) && @@ -409,23 +432,193 @@ static int uh_path_match(const char *prefix, const char *url) return 0; } +#endif +static void uh_dispatch_request( + struct client *cl, struct http_request *req, struct path_info *pin +) { +#ifdef HAVE_CGI + struct interpreter *ipr = NULL; -int main (int argc, char **argv) + if( uh_path_match(cl->server->conf->cgi_prefix, pin->name) || + (ipr = uh_interpreter_lookup(pin->phys)) ) + { + uh_cgi_request(cl, req, pin, ipr); + } + else +#endif + { + uh_file_request(cl, req, pin); + } +} + +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 ) + 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(): 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 runtime */ - lua_State *L = NULL; + /* 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( 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) +{ /* 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; @@ -433,7 +626,7 @@ int main (int argc, char **argv) sigset_t ss; /* maximum file descriptor number */ - int new_fd, cur_fd, max_fd = 0; + int cur_fd, max_fd = 0; #ifdef HAVE_TLS int tls = 0; @@ -525,7 +718,7 @@ int main (int argc, char **argv) #endif while( (opt = getopt(argc, argv, - "fSDRC:K:E:I:p:s:h:c:l:L:d:r:m:x:t:T:")) > 0 + "fSDRC:K:E:I:p:s:h:c:l:L:d:r:m:x:i:t:T:")) > 0 ) { switch(opt) { @@ -658,6 +851,21 @@ int main (int argc, char **argv) case 'x': conf.cgi_prefix = optarg; break; + + /* interpreter */ + case 'i': + if( (optarg[0] == '.') && (port = strchr(optarg, '=')) ) + { + *port++ = 0; + uh_interpreter_add(optarg, port); + } + else + { + fprintf(stderr, "Error: Invalid interpreter: %s\n", + optarg); + exit(1); + } + break; #endif #ifdef HAVE_LUA @@ -740,6 +948,7 @@ int main (int argc, char **argv) #endif #ifdef HAVE_CGI " -x string URL prefix for CGI handler, default is '/cgi-bin'\n" + " -i .ext=path Use interpreter at path for files with the given extension\n" #endif #if defined(HAVE_CGI) || defined(HAVE_LUA) " -t seconds CGI and Lua script timeout in seconds, default is 60\n" @@ -830,7 +1039,7 @@ int main (int argc, char **argv) if( ! conf.lua_prefix ) conf.lua_prefix = "/lua"; - L = conf.lua_init(conf.lua_handler); + conf.lua_state = conf.lua_init(conf.lua_handler); } } #endif @@ -865,166 +1074,13 @@ int main (int argc, char **argv) } } - /* 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 ) - { - /* 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( 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 - { - /* 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; - -#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); - } - } - 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); - } - } - } - } + /* server main loop */ + uh_mainloop(&conf, serv_fds, max_fd); #ifdef HAVE_LUA /* destroy the Lua state */ - if( L != NULL ) - conf.lua_close(L); + if( conf.lua_state != NULL ) + conf.lua_close(conf.lua_state); #endif return 0; diff --git a/package/uhttpd/src/uhttpd.h b/package/uhttpd/src/uhttpd.h index fd2176ebdd..6747b905ff 100644 --- a/package/uhttpd/src/uhttpd.h +++ b/package/uhttpd/src/uhttpd.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -44,13 +45,16 @@ #include #endif +/* uClibc... */ +#ifndef SOL_TCP +#define SOL_TCP 6 +#endif + #define UH_LIMIT_MSGHEAD 4096 #define UH_LIMIT_HEADERS 64 -#define UH_LIMIT_LISTENERS 16 #define UH_LIMIT_CLIENTS 64 -#define UH_LIMIT_AUTHREALMS 8 #define UH_HTTP_MSG_GET 0 #define UH_HTTP_MSG_HEAD 1 @@ -58,6 +62,7 @@ struct listener; struct client; +struct interpreter; struct http_request; struct config { @@ -76,6 +81,7 @@ struct config { #ifdef HAVE_LUA char *lua_prefix; char *lua_handler; + lua_State *lua_state; lua_State * (*lua_init) (const char *handler); void (*lua_close) (lua_State *L); void (*lua_request) (struct client *cl, struct http_request *req, lua_State *L); @@ -105,6 +111,7 @@ struct listener { #ifdef HAVE_TLS SSL_CTX *tls; #endif + struct listener *next; }; struct client { @@ -117,12 +124,14 @@ struct client { #ifdef HAVE_TLS SSL *tls; #endif + struct client *next; }; struct auth_realm { char path[PATH_MAX]; char user[32]; char pass[128]; + struct auth_realm *next; }; struct http_request { @@ -140,5 +149,13 @@ struct http_response { char *headers[UH_LIMIT_HEADERS]; }; +#ifdef HAVE_CGI +struct interpreter { + char path[PATH_MAX]; + char extn[32]; + struct interpreter *next; +}; +#endif + #endif