+/*
+ * 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"
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
continue;
error:
- if( sock > 0 )
+ if( sock > 0 )
close(sock);
}
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;
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 */
/* 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;
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);
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)
{
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;
#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
}
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]
);
}
#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 */
#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) */
{
/* 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) )
{
{
#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 */
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:
#ifdef HAVE_LUA
/* destroy the Lua state */
- lua_close(L);
+ if( L != NULL )
+ conf.lua_close(L);
#endif
return 0;