| /* |
| * Copyright (c) 2004-2005 Sergey Lyubka <valenok@gmail.com> |
| * All rights reserved |
| * |
| * "THE BEER-WARE LICENSE" (Revision 42): |
| * Sergey Lyubka wrote this file. As long as you retain this notice you |
| * can do whatever you want with this stuff. If we meet some day, and you think |
| * this stuff is worth it, you can buy me a beer in return. |
| */ |
| |
| #include "shttpd_defs.h" |
| #include "wsmand-daemon.h" |
| |
| /* |
| * Configuration parameters setters |
| */ |
| static void |
| set_int(struct shttpd_ctx *ctx, void *ptr, const char *string) |
| { |
| ctx = NULL; /* Unused */ |
| * (int *) ptr = atoi(string); |
| } |
| |
| static void |
| set_str(struct shttpd_ctx *ctx, void *ptr, const char *string) |
| { |
| ctx = NULL; /* Unused */ |
| * (char **) ptr = strdup(string); |
| } |
| |
| static void |
| set_log_file(struct shttpd_ctx *ctx, void *ptr, const char *string) |
| { |
| FILE **fp = ptr; |
| ctx = NULL; |
| |
| if ((*fp = fopen(string, "a")) == NULL) |
| elog(E_FATAL, NULL, "cannot open log file %s: %s", |
| string, strerror(errno)); |
| } |
| |
| static int isbyte(int n) { return (n >= 0 && n <= 255); } |
| |
| static void |
| set_acl(struct shttpd_ctx *ctx, void *ptr, const char *s) |
| { |
| struct llhead *head = ptr; |
| struct acl *acl; |
| char flag; |
| int len, a, b, c, d, n, mask; |
| |
| ctx = NULL; |
| |
| FOR_EACH_WORD_IN_LIST(s, len) { |
| |
| mask = 32; |
| |
| if (sscanf(s, "%c%d.%d.%d.%d%n",&flag,&a,&b,&c,&d,&n) != 5) { |
| elog(E_FATAL, NULL, "[%s]: subnet must be " |
| "[+|-]x.x.x.x[/x]", s); |
| } else if (flag != '+' && flag != '-') { |
| elog(E_FATAL, NULL, "flag must be + or -: [%s]", s); |
| } else if (!isbyte(a)||!isbyte(b)||!isbyte(c)||!isbyte(d)) { |
| elog(E_FATAL, NULL, "bad ip address: [%s]", s); |
| } else if ((acl = malloc(sizeof(*acl))) == NULL) { |
| elog(E_FATAL, NULL, "%s", "cannot malloc subnet"); |
| } else if (sscanf(s + n, "/%d", &mask) == 0) { |
| /* Do nothing, no mask specified */ |
| } else if (mask < 0 || mask > 32) { |
| elog(E_FATAL, NULL, "bad subnet mask: %d [%s]", n, s); |
| } |
| |
| acl->ip = (a << 24) | (b << 16) | (c << 8) | d; |
| acl->mask = mask ? 0xffffffffU << (32 - mask) : 0; |
| acl->flag = flag; |
| LL_TAIL(head, &acl->link); |
| } |
| } |
| |
| #ifndef NO_SSL |
| /* |
| * Dynamically load SSL library. Set up ctx->ssl_ctx pointer. |
| */ |
| static void |
| set_ssl(struct shttpd_ctx *ctx, void *arg, const char *pem) |
| { |
| SSL_CTX *CTX; |
| void *lib; |
| struct ssl_func *fp; |
| char *ssl_disabled_protocols = wsmand_options_get_ssl_disabled_protocols(); |
| |
| arg = NULL; /* Unused */ |
| |
| /* Load SSL library dynamically */ |
| if ((lib = dlopen(SSL_LIB, RTLD_LAZY)) == NULL) { |
| elog(E_FATAL, NULL, "set_ssl: cannot load %s", SSL_LIB); |
| ctx->ssl_ctx = NULL; |
| return; |
| } |
| |
| for (fp = ssl_sw; fp->name != NULL; fp++) { |
| if ((fp->ptr.v_void = dlsym(lib, fp->name)) == NULL) { |
| elog(E_FATAL, NULL,"set_ssl: cannot find %s", fp->name); |
| ctx->ssl_ctx = NULL; |
| return; |
| } |
| } |
| |
| /* Initialize SSL crap */ |
| static int ssl_library_initialized = 0; |
| if(!ssl_library_initialized) { |
| debug("Initialize SSL"); |
| SSL_library_init(); |
| ssl_library_initialized = 1; |
| } |
| if ((CTX = SSL_CTX_new(SSLv23_server_method())) == NULL) { |
| elog(E_FATAL, NULL, "SSL_CTX_new error"); |
| } |
| else if (wsmand_options_get_ssl_cert_file() && SSL_CTX_use_certificate_file(CTX, wsmand_options_get_ssl_cert_file(),SSL_FILETYPE_PEM) == 0) { |
| elog(E_FATAL, NULL, "cannot open %s : %s", pem, strerror(errno)); |
| SSL_CTX_free(CTX); |
| CTX = NULL; |
| } |
| else if (wsmand_options_get_ssl_key_file() && SSL_CTX_use_PrivateKey_file(CTX, wsmand_options_get_ssl_key_file(), SSL_FILETYPE_PEM) == 0) { |
| elog(E_FATAL, NULL, "cannot open %s : %s", pem, strerror(errno)); |
| SSL_CTX_free(CTX); |
| CTX = NULL; |
| } |
| while (ssl_disabled_protocols) { |
| struct ctx_opts_t { |
| char *name; |
| long opt; |
| } protocols[] = { |
| { "SSLv2", SSL_OP_NO_SSLv2 }, |
| { "SSLv3", SSL_OP_NO_SSLv3 }, |
| { "TLSv1", SSL_OP_NO_TLSv1 }, |
| # if OPENSSL_VERSION_NUMBER >= 0x10001000L |
| { "TLSv1_1", SSL_OP_NO_TLSv1_1 }, |
| { "TLSv1_2", SSL_OP_NO_TLSv1_2 }, |
| # endif |
| { NULL, 0 } |
| }; |
| char *blank_ptr; |
| int idx; |
| if (*ssl_disabled_protocols == 0) |
| break; |
| blank_ptr = strchr(ssl_disabled_protocols, ' '); |
| if (blank_ptr == NULL) |
| blank_ptr = ssl_disabled_protocols + strlen(ssl_disabled_protocols); |
| for (idx = 0; protocols[idx].name ; ++idx) { |
| if (strncasecmp(protocols[idx].name, ssl_disabled_protocols, blank_ptr-ssl_disabled_protocols) == 0) { |
| debug("SSL: disable %s protocol", protocols[idx].name); |
| SSL_CTX_ctrl(CTX, SSL_CTRL_OPTIONS, protocols[idx].opt, NULL); |
| break; |
| } |
| } |
| if (*blank_ptr == 0) |
| break; |
| ssl_disabled_protocols = blank_ptr + 1; |
| } |
| |
| ctx->ssl_ctx = CTX; |
| } |
| #endif /* NO_SSL */ |
| |
| static void |
| set_mime(struct shttpd_ctx *ctx, void *arg, const char *string) |
| { |
| arg = NULL; |
| set_mime_types(ctx, string); |
| } |
| |
| #define OFS(x) offsetof(struct shttpd_ctx, x) |
| #define BOOL_OPT "0|1" |
| const struct opt options[] = { |
| {'d', "document_root", "Web root directory", set_str, |
| OFS(document_root), "directory", NULL, OPT_DIR}, |
| {'i', "index_files", "Index files", set_str, OFS(index_files), |
| "file_list", INDEX_FILES, OPT_ADVANCED}, |
| {'p', "listen_ports", "Listening ports", set_str, |
| OFS(ports), "ports", LISTENING_PORTS, OPT_ADVANCED}, |
| {'D', "list_directories", "Directory listing", set_int, |
| OFS(dirlist), BOOL_OPT, "1", OPT_BOOL | OPT_ADVANCED}, |
| #ifndef NO_CGI |
| {'c', "cgi_extensions", "CGI extensions", set_str, |
| OFS(cgi_extensions), "ext_list", CGI_EXT, OPT_ADVANCED}, |
| {'C', "cgi_interpreter", "CGI interpreter", set_str, |
| OFS(cgi_interpreter), "file", NULL, OPT_FILE | OPT_ADVANCED}, |
| {'V', "cgi_envvar", "CGI envir variables", set_str, |
| OFS(cgi_vars), "X=Y,....", NULL, OPT_ADVANCED}, |
| #endif /* NO_CGI */ |
| #if !defined(NO_SSI) |
| {'S', "ssi_extensions", "SSI extensions", set_str, |
| OFS(ssi_extensions), "ext_list", SSI_EXT, OPT_ADVANCED}, |
| #endif /* NO_SSI */ |
| {'N', "auth_realm", "Authentication realm", set_str, |
| OFS(auth_realm), "auth_realm", REALM, OPT_ADVANCED}, |
| {'l', "access_log", "Access log file", set_log_file, |
| OFS(access_log), "file", NULL, OPT_FILE | OPT_ADVANCED}, |
| {'e', "error_log", "Error log file", set_log_file, |
| OFS(error_log), "file", NULL, OPT_FILE | OPT_ADVANCED}, |
| {'m', "mime_types", "Mime types file", set_mime, |
| OFS(mime_file), "file", NULL, OPT_FILE | OPT_ADVANCED}, |
| {'P', "global_htpasswd", "Global passwords file", set_str, |
| OFS(global_passwd_file), "file", NULL, OPT_FILE | OPT_ADVANCED}, |
| #ifndef NO_SSL |
| {'s', "ssl_certificate", "SSL certificate file", set_ssl, |
| OFS(ssl_ctx), "pem_file", NULL, OPT_FILE | OPT_ADVANCED}, |
| #endif /* NO_SSL */ |
| {'U', "put_auth", "PUT,DELETE auth file",set_str, |
| OFS(put_auth_file), "file", NULL, OPT_FILE | OPT_ADVANCED}, |
| {'a', "aliases", "Aliases", set_str, |
| OFS(aliases), "X=Y,...", NULL, OPT_ADVANCED}, |
| {'b', "io_buf_size", "IO buffer size", set_int, OFS(io_buf_size), |
| "bytes", DFLT_IO_SIZ, OPT_INT | OPT_ADVANCED}, |
| {'x', "acl", "Allow/deny IP addresses/subnets", set_acl, |
| OFS(acl), "acl_list", NULL, OPT_ADVANCED}, |
| #ifdef _WIN32 |
| {'B', "auto_start", "Autostart with Windows", set_int, |
| OFS(auto_start), BOOL_OPT, "1", OPT_BOOL}, |
| #else |
| {'I', "inetd_mode", "Inetd mode", set_int, |
| OFS(inetd_mode), BOOL_OPT, NULL, OPT_BOOL }, |
| {'u', "runtime_uid", "Run as user", set_str, |
| OFS(uid), "user_name", NULL, 0 }, |
| #endif /* _WIN32 */ |
| {0, NULL, NULL, NULL, 0, NULL, NULL, 0 } |
| }; |
| |
| static const struct opt * |
| find_option(int sw, const char *name) |
| { |
| const struct opt *opt; |
| |
| for (opt = options; opt->sw != 0; opt++) |
| if (sw == opt->sw || (name && strcmp(opt->name, name) == 0)) |
| return (opt); |
| |
| return (NULL); |
| } |
| |
| static void |
| set_option(const struct opt *opt, const char *val, char **tmpvars) |
| { |
| tmpvars += opt - options; |
| |
| if (*tmpvars != NULL) |
| free(*tmpvars); |
| |
| *tmpvars = strdup(val); |
| } |
| |
| /* |
| * Initialize shttpd context |
| */ |
| static void |
| initialize_context(struct shttpd_ctx *ctx, const char *config_file, |
| int argc, char *argv[], char **tmpvars) |
| { |
| char line[FILENAME_MAX], root[FILENAME_MAX], |
| var[sizeof(line)], val[sizeof(line)]; |
| const char *arg; |
| size_t i; |
| const struct opt *opt; |
| FILE *fp; |
| struct tm *tm; |
| |
| current_time = time(NULL); |
| tm = localtime(¤t_time); |
| tz_offset = 0; |
| #if 0 |
| tm->tm_gmtoff - 3600 * (tm->tm_isdst > 0 ? 1 : 0); |
| #endif |
| |
| (void) memset(ctx, 0, sizeof(*ctx)); |
| |
| ctx->start_time = current_time; |
| InitializeCriticalSection(&ctx->mutex); |
| |
| LL_INIT(&ctx->connections); |
| LL_INIT(&ctx->mime_types); |
| LL_INIT(&ctx->registered_uris); |
| LL_INIT(&ctx->uri_auths); |
| LL_INIT(&ctx->error_handlers); |
| LL_INIT(&ctx->acl); |
| |
| #if !defined(NO_SSI) |
| LL_INIT(&ctx->ssi_funcs); |
| #endif /* NO_SSI */ |
| |
| /* First pass: set the defaults */ |
| for (opt = options; opt->sw != 0; opt++) |
| if (tmpvars[opt - options] == NULL && opt->def != NULL) |
| tmpvars[opt - options] = strdup(opt->def); |
| |
| /* Second pass: load config file */ |
| if (config_file != NULL && (fp = fopen(config_file, "r")) != NULL) { |
| DBG(("init_ctx: config file %s", config_file)); |
| |
| /* Loop through the lines in config file */ |
| while (fgets(line, sizeof(line), fp) != NULL) { |
| |
| /* Skip comments and empty lines */ |
| if (line[0] == '#' || line[0] == '\n') |
| continue; |
| |
| /* Trim trailing newline character */ |
| line[strlen(line) - 1] = '\0'; |
| |
| if (sscanf(line, "%s %[^#\n]", var, val) != 2) |
| elog(E_FATAL,0,"init_ctx: bad line: [%s]",line); |
| |
| if ((opt = find_option(0, var)) == NULL) |
| elog(E_FATAL, NULL, |
| "set_option: unknown variable [%s]", var); |
| set_option(opt, val, tmpvars); |
| } |
| (void) fclose(fp); |
| } |
| |
| /* Third pass: process command line args */ |
| for (i = 1; i < (size_t) argc && argv[i][0] == '-'; i++) |
| if ((opt = find_option(argv[i][1], NULL)) != NULL) { |
| arg = argv[i][2] ? &argv[i][2] : argv[++i]; |
| |
| if (arg == NULL) |
| usage(argv[0]); |
| |
| set_option(opt, arg, tmpvars); |
| } else { |
| usage(argv[0]); |
| } |
| |
| /* Call setters functions now */ |
| for (i = 0; i < NELEMS(options); i++) |
| if (tmpvars[i] != NULL) { |
| options[i].setter(ctx, |
| ((char *) ctx) + options[i].ofs, tmpvars[i]); |
| free(tmpvars[i]); |
| } |
| |
| /* If document_root is not set, set it to current directory */ |
| if (ctx->document_root == NULL) { |
| (void) my_getcwd(root, sizeof(root)); |
| ctx->document_root = strdup(root); |
| } |
| |
| #ifdef _WIN32 |
| {WSADATA data; WSAStartup(MAKEWORD(2,2), &data);} |
| #endif /* _WIN32 */ |
| |
| DBG(("init_ctx: initialized context %p", (void *) ctx)); |
| } |
| |
| /* |
| * Show usage string and exit. |
| */ |
| void |
| usage(const char *prog) |
| { |
| const struct opt *opt; |
| |
| (void) fprintf(stderr, |
| "SHTTPD version %s (c) Sergey Lyubka\n" |
| "usage: %s [OPTIONS] [config_file]\n" |
| "Note: config line keyword for every option is in the " |
| "round brackets\n", VERSION, prog); |
| |
| #if !defined(NO_AUTH) |
| (void) fprintf(stderr, "-A <htpasswd_file> <realm> <user> <passwd>\n"); |
| #endif /* NO_AUTH */ |
| |
| for (opt = options; opt->name != NULL; opt++) |
| (void) fprintf(stderr, "-%c <%s>\t\t%s (%s)\n", |
| opt->sw, opt->arg, opt->desc, opt->name); |
| |
| exit(EXIT_FAILURE); |
| } |
| |
| struct shttpd_ctx * |
| init_from_argc_argv(const char *config_file, int argc, char *argv[]) |
| { |
| struct shttpd_ctx *ctx; |
| char *tmpvars[NELEMS(options)]; |
| size_t i; |
| |
| /* Initialize all temporary holders to NULL */ |
| for (i = 0; i < NELEMS(tmpvars); i++) |
| tmpvars[i] = NULL; |
| |
| if ((ctx = malloc(sizeof(*ctx))) != NULL) |
| initialize_context(ctx, config_file, argc, argv, tmpvars); |
| |
| return (ctx); |
| } |
| |
| struct shttpd_ctx * |
| shttpd_init(const char *config_file, ...) |
| { |
| struct shttpd_ctx *ctx; |
| va_list ap; |
| const char *opt_name, *opt_value; |
| char *tmpvars[NELEMS(options)]; |
| const struct opt *opt; |
| size_t i; |
| |
| elog(E_LOG, NULL, "Initializing http server"); |
| /* Initialize all temporary holders to NULL */ |
| for (i = 0; i < NELEMS(tmpvars); i++) |
| tmpvars[i] = NULL; |
| |
| if ((ctx = malloc(sizeof(*ctx))) != NULL) { |
| |
| va_start(ap, config_file); |
| while ((opt_name = va_arg(ap, const char *)) != NULL) { |
| opt_value = va_arg(ap, const char *); |
| |
| if ((opt = find_option(0, opt_name)) == NULL) |
| elog(E_FATAL, NULL, "shttpd_init: " |
| "unknown variable [%s]", opt_name); |
| set_option(opt, opt_value, tmpvars); |
| } |
| va_end(ap); |
| |
| initialize_context(ctx, config_file, 0, NULL, tmpvars); |
| } |
| |
| return (ctx); |
| } |