uhttpd: move Lua and TLS support into loadable plugins
[project/luci.git] / contrib / package / uhttpd / src / uhttpd.c
index ffa44aa..a7db794 100644 (file)
@@ -1,3 +1,23 @@
+/*
+ * uhttpd - Tiny single-threaded httpd - Main component
+ *
+ *   Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+#define _XOPEN_SOURCE 500      /* crypt() */
+
 #include "uhttpd.h"
 #include "uhttpd-utils.h"
 #include "uhttpd-file.h"
@@ -22,6 +42,43 @@ static void uh_sigterm(int sig)
        run = 0;
 }
 
+static void uh_config_parse(const char *path)
+{
+       FILE *c;
+       char line[512];
+       char *user = NULL;
+       char *pass = NULL;
+       char *eol  = NULL;
+
+       if( (c = fopen(path ? path : "/etc/httpd.conf", "r")) != NULL )
+       {
+               memset(line, 0, sizeof(line));
+
+               while( fgets(line, sizeof(line) - 1, c) )
+               {
+                       if( (line[0] == '/') && (strchr(line, ':') != NULL) )
+                       {
+                               if( !(user = strchr(line, ':')) || (*user++ = 0) ||
+                                   !(pass = strchr(user, ':')) || (*pass++ = 0) ||
+                                       !(eol = strchr(pass, '\n')) || (*eol++  = 0) )
+                                               continue;
+
+                               if( !uh_auth_add(line, user, pass) )
+                               {
+                                       fprintf(stderr,
+                                               "Can not manage more than %i basic auth realms, "
+                                               "will skip the rest\n", UH_LIMIT_AUTHREALMS
+                                       );
+
+                                       break;
+                               } 
+                       }
+               }
+
+               fclose(c);
+       }
+}
+
 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
@@ -104,7 +161,7 @@ static int uh_socket_bind(
                continue;
 
                error:
-               if( sock > 0 )  
+               if( sock > 0 )
                        close(sock);
        }
 
@@ -250,7 +307,7 @@ static struct http_request * uh_http_header_parse(struct client *cl, char *buffe
 
 static struct http_request * uh_http_header_recv(struct client *cl)
 {
-       char buffer[UH_LIMIT_MSGHEAD];
+       static char buffer[UH_LIMIT_MSGHEAD];
        char *bufptr = &buffer[0];
        char *idxptr = NULL;
 
@@ -315,40 +372,25 @@ static struct http_request * uh_http_header_recv(struct client *cl)
        return NULL;
 }
 
-static int uh_docroot_resolve(const char *path, char *buf)
+static int uh_path_match(const char *prefix, const char *url)
 {
-       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( (strstr(url, prefix) == url) &&
+           ((prefix[strlen(prefix)-1] == '/') ||
+                (strlen(url) == strlen(prefix))   ||
+                (url[strlen(prefix)] == '/'))
+       ) {
+               return 1;
        }
 
-       if( chdir(curpath) )
-       {
-               perror("chdir()");
-               return 0;
-       }
-
-       return 1;
+       return 0;
 }
 
 
 int main (int argc, char **argv)
 {
 #ifdef HAVE_LUA
-       /* init Lua runtime */
-       lua_State *L;
+       /* Lua runtime */
+       lua_State *L = NULL;
 #endif
 
        /* master file descriptor list */
@@ -357,12 +399,15 @@ int main (int argc, char **argv)
        /* working structs */
        struct addrinfo hints;
        struct http_request *req;
+       struct path_info *pin;
        struct client *cl;
        struct sigaction sa;
        struct config conf;
 
        /* maximum file descriptor number */
        int new_fd, cur_fd, max_fd = 0;
+
+       int tls = 0;
        int keys = 0;
        int bound = 0;
        int nofork = 0;
@@ -372,12 +417,19 @@ int main (int argc, char **argv)
        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, SIGCHILD */
+       sa.sa_flags = 0;
+       sigemptyset(&sa.sa_mask);
+
        sa.sa_handler = SIG_IGN;
        sigaction(SIGPIPE, &sa, NULL);
        sigaction(SIGCHLD, &sa, NULL);
@@ -397,15 +449,43 @@ int main (int argc, char **argv)
        memset(bind, 0, sizeof(bind));
 
 #ifdef HAVE_TLS
-       /* init SSL context */
-       if( ! (conf.tls = uh_tls_ctx_init()) )
+       /* load TLS plugin */
+       if( ! (tls_lib = dlopen("uhttpd_tls.so", RTLD_LAZY | RTLD_GLOBAL)) )
        {
-               fprintf(stderr, "Failed to initalize SSL context\n");
-               exit(1);
+               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:")) > 0 )
+       while( (opt = getopt(argc, argv, "fC:K:p:s:h:c:l:L:d:r:m:x:")) > 0 )
        {
                switch(opt)
                {
@@ -428,10 +508,24 @@ int main (int argc, char **argv)
                                        port = optarg;
                                }
 
+                               if( opt == 's' )
+                               {
+                                       if( !conf.tls )
+                                       {
+                                               fprintf(stderr,
+                                                       "Notice: TLS support is disabled, "
+                                                       "ignoring '-s %s'\n", optarg
+                                               );
+                                               continue;
+                                       }
+
+                                       tls = 1;
+                               }
+
                                /* bind sockets */
                                bound += uh_socket_bind(
-                                       &serv_fds, &max_fd, bind[0] ? bind : NULL, port, &hints,
-                                       (opt == 's') ? 1 : 0, &conf
+                                       &serv_fds, &max_fd, bind[0] ? bind : NULL, port,
+                                       &hints, (opt == 's'), &conf
                                );
 
                                break;
@@ -439,39 +533,50 @@ int main (int argc, char **argv)
 #ifdef HAVE_TLS
                        /* certificate */
                        case 'C':
-                               if( SSL_CTX_use_certificate_file(conf.tls, optarg, SSL_FILETYPE_ASN1) < 1 )
+                               if( conf.tls )
                                {
-                                       fprintf(stderr, "Invalid certificate file given\n");
-                                       exit(1);
+                                       if( conf.tls_cert(conf.tls, optarg) < 1 )
+                                       {
+                                               fprintf(stderr,
+                                                       "Error: Invalid certificate file given\n");
+                                               exit(1);
+                                       }
+
+                                       keys++;
                                }
 
-                               keys++;
                                break;
 
                        /* key */
                        case 'K':
-                               if( SSL_CTX_use_PrivateKey_file(conf.tls, optarg, SSL_FILETYPE_ASN1) < 1 )
+                               if( conf.tls )
                                {
-                                       fprintf(stderr, "Invalid private key file given\n");
-                                       exit(1);
+                                       if( conf.tls_key(conf.tls, optarg) < 1 )
+                                       {
+                                               fprintf(stderr,
+                                                       "Error: Invalid private key file given\n");
+                                               exit(1);
+                                       }
+
+                                       keys++;
                                }
 
-                               keys++;
                                break;
 #endif
 
                        /* docroot */
                        case 'h':
-                               if( ! uh_docroot_resolve(optarg, conf.docroot) )
+                               if( ! realpath(optarg, conf.docroot) )
                                {
-                                       fprintf(stderr, "Invalid directory: %s\n", optarg);
+                                       fprintf(stderr, "Error: Invalid directory %s: %s\n",
+                                               optarg, strerror(errno));
                                        exit(1);
                                }
                                break;
 
 #ifdef HAVE_CGI
                        /* cgi prefix */
-                       case 'c':
+                       case 'x':
                                conf.cgi_prefix = optarg;
                                break;
 #endif
@@ -505,25 +610,44 @@ int main (int argc, char **argv)
                                }
                                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"
-                                       "       -p      Bind to specified address and port, multiple allowed\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      Like -p but provide HTTPS on this port\n"
-                                       "       -C      ASN.1 server certificate file\n"
-                                       "       -K      ASN.1 server private key file\n"
+                                       "       -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      Specify the document root, default is '.'\n"
-                                       "       -f      Do not fork to background\n"
+                                       "       -h directory    Specify the document root, default is '.'\n"
 #ifdef HAVE_LUA
-                                       "       -l      URL prefix for Lua handler, default is '/lua'\n"
-                                       "       -L      Lua handler script, default is './lua/handler.lua'\n"
+                                       "       -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
-                                       "       -c      URL prefix for CGI handler, default is '/cgi-bin'\n"
+                                       "       -x string       URL prefix for CGI handler, default is '/cgi-bin'\n"
 #endif
-                                       "       -d      URL decode given string\n"
+                                       "       -d string       URL decode given string\n"
+                                       "       -r string       Specify basic auth realm\n"
+                                       "       -m string       MD5 crypt given string\n"
                                        "\n", argv[0]
                                );
 
@@ -532,34 +656,33 @@ int main (int argc, char **argv)
        }
 
 #ifdef HAVE_TLS
-       if( keys < 2 )
+       if( (tls == 1) && (keys < 2) )
        {
-               fprintf(stderr, "Missing private key or certificate file\n");
+               fprintf(stderr, "Error: Missing private key or certificate file\n");
                exit(1);
        }
 #endif
 
        if( bound < 1 )
        {
-               fprintf(stderr, "No sockets bound, unable to continue\n");
+               fprintf(stderr, "Error: No sockets bound, unable to continue\n");
                exit(1);
        }
 
        /* default docroot */
-       if( !conf.docroot[0] && !uh_docroot_resolve(".", conf.docroot) )
+       if( !conf.docroot[0] && !realpath(".", conf.docroot) )
        {
-               fprintf(stderr, "Can not determine default document root\n");
+               fprintf(stderr, "Error: Can not determine default document root: %s\n",
+                       strerror(errno));
                exit(1);
        }
 
-#ifdef HAVE_LUA
-       /* default lua prefix and handler */
-       if( ! conf.lua_handler )
-               conf.lua_handler = "./lua/handler.lua";
+       /* default realm */
+       if( ! conf.realm )
+               conf.realm = "Protected Area";
 
-       if( ! conf.lua_prefix )
-               conf.lua_prefix = "/lua";
-#endif
+       /* config file */
+       uh_config_parse(conf.file);
 
 #ifdef HAVE_CGI
        /* default cgi prefix */
@@ -568,8 +691,38 @@ int main (int argc, char **argv)
 #endif
 
 #ifdef HAVE_LUA
-       /* init Lua runtime */
-       L = uh_lua_init(conf.lua_handler);
+       /* 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) */
@@ -623,7 +776,7 @@ int main (int argc, char **argv)
                {
                        /* 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) )
                                {
@@ -635,12 +788,13 @@ int main (int argc, char **argv)
                                                {
 #ifdef HAVE_TLS
                                                        /* setup client tls context */
-                                                       uh_tls_client_accept(cl);
+                                                       if( conf.tls )
+                                                               conf.tls_accept(cl);
 #endif
 
                                                        /* add client socket to global fdset */
                                                        FD_SET(new_fd, &used_fds);
-                                                       max_fd = max(max_fd, new_fd);                                                   
+                                                       max_fd = max(max_fd, new_fd);
                                                }
 
                                                /* insufficient resources */
@@ -670,34 +824,55 @@ int main (int argc, char **argv)
                                                goto cleanup;
                                        }
 
-                                       /* parse message header and dispatch request */
+                                       /* parse message header */
                                        if( (req = uh_http_header_recv(cl)) != NULL )
                                        {
-#ifdef HAVE_CGI
-                                               if( strstr(req->url, conf.cgi_prefix) == req->url )
+#ifdef HAVE_LUA
+                                               /* Lua request? */
+                                               if( L && uh_path_match(conf.lua_prefix, req->url) )
                                                {
-                                                       uh_cgi_request(cl, req);
+                                                       conf.lua_request(cl, req, L);
                                                }
                                                else
 #endif
-
-#ifdef HAVE_LUA
-                                               if( strstr(req->url, conf.lua_prefix) == req->url )
+                                               /* dispatch request */
+                                               if( (pin = uh_path_lookup(cl, req->url)) != NULL )
                                                {
-                                                       uh_lua_request(cl, req, L);
-                                               }
-                                               else
+                                                       /* 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_file_request(cl, req);
+                                                       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 */
-                                       uh_tls_client_close(cl);
+                                       if( conf.tls )
+                                               conf.tls_close(cl);
 #endif
 
                                        cleanup:
@@ -715,7 +890,8 @@ int main (int argc, char **argv)
 
 #ifdef HAVE_LUA
        /* destroy the Lua state */
-       lua_close(L);
+       if( L != NULL )
+               conf.lua_close(L);
 #endif
 
        return 0;