From 5425b94f39217982b526e90eafb6d5887920b95d Mon Sep 17 00:00:00 2001 From: Sergey Lyubka Date: Fri, 25 Jun 2010 17:23:17 +0200 Subject: [PATCH] introduced struct mg_config. Fixed Range response. --- main.c | 148 +++++++++--- mongoose.c | 649 +++++++++++++++------------------------------------ mongoose.h | 114 ++++----- test/test.pl | 6 +- 4 files changed, 352 insertions(+), 565 deletions(-) diff --git a/main.c b/main.c index 9a4e8f1d..0568c26b 100644 --- a/main.c +++ b/main.c @@ -32,6 +32,7 @@ #include #include #include +#include #include "mongoose.h" @@ -70,16 +71,6 @@ signal_handler(int sig_num) } } -/* - * Show usage string and exit. - */ -static void -show_usage_and_exit(void) -{ - mg_show_usage_string(stderr); - exit(EXIT_FAILURE); -} - /* * Edit the passwords file. */ @@ -88,18 +79,89 @@ mg_edit_passwords(const char *fname, const char *domain, const char *user, const char *pass) { struct mg_context *ctx; + struct mg_config config; int retval; - ctx = mg_start(); - (void) mg_set_option(ctx, "auth_realm", domain); + memset(&config, 0, sizeof(config)); + config.auth_domain = (char *) domain; + config.num_threads = "0"; + config.listening_ports = ""; + ctx = mg_start(&config); retval = mg_modify_passwords_file(ctx, fname, user, pass); mg_stop(ctx); return (retval); } +#define OFFSET(x) offsetof(struct mg_config, x) + +static struct option_descriptor { + const char *name; + const char *description; + size_t offset; +} known_options[] = { + {"root", "\tWeb root directory", OFFSET(document_root)}, + {"index_files", "Index files", OFFSET(index_files)}, + {"ssl_cert", "SSL certificate file", OFFSET(ssl_certificate)}, + {"ports", "Listening ports", OFFSET(listening_ports)}, + {"dir_list", "Directory listing", OFFSET(enable_directory_listing)}, + {"protect", "URI to htpasswd mapping", OFFSET(protect)}, + {"cgi_ext", "CGI extensions", OFFSET(cgi_extensions)}, + {"cgi_interp", "CGI interpreter to use", OFFSET(cgi_interpreter)}, + {"cgi_env", "Custom CGI enviroment variables", OFFSET(cgi_environment)}, + {"ssi_ext", "SSI extensions", OFFSET(ssi_extensions)}, + {"auth_realm", "Authentication domain name", OFFSET(auth_domain)}, + {"auth_gpass", "Global passwords file", OFFSET(global_passwords_file)}, + {"auth_PUT", "PUT,DELETE auth file", OFFSET(put_delete_passwords_file)}, + {"uid", "\tRun as user", OFFSET(uid)}, + {"access_log", "Access log file", OFFSET(access_log_file)}, + {"error_log", "Error log file", OFFSET(error_log_file)}, + {"acl", "\tAllow/deny IP addresses/subnets", OFFSET(acl)}, + {"num_threads", "Threads to spawn", OFFSET(num_threads)}, + {"mime_types", "Extra mime types to use", OFFSET(mime_types)}, + {NULL, NULL, 0} +}; + static void -process_command_line_arguments(struct mg_context *ctx, char *argv[]) +show_usage_and_exit(const struct mg_config *config) +{ + const struct option_descriptor *o; + const char *value; + + (void) fprintf(stderr, + "Mongoose version %s (c) Sergey Lyubka\n" + "usage: mongoose [options] [config_file]\n", mg_version()); + + fprintf(stderr, " -A \n"); + + for (o = known_options; o->name != NULL; o++) { + (void) fprintf(stderr, " -%s\t%s", o->name, o->description); + value = * (char **) ((char *) config + o->offset); + if (value != NULL) + fprintf(stderr, " (default: \"%s\")", value); + fputc('\n', stderr); + } + + exit(EXIT_FAILURE); +} + +static void +set_option(struct mg_config *config, const char *name, char *value) +{ + const struct option_descriptor *o; + + for (o = known_options; o->name != NULL; o++) + if (strcmp(name, o->name) == 0) { + * (char **) ((char *) config + o->offset) = value; + break; + } + + if (o->name == NULL) + show_usage_and_exit(config); +} + +static void +process_command_line_arguments(struct mg_config *config, char *argv[]) { const char *config_file = CONFIG_FILE; char line[512], opt[512], *vals[100], @@ -110,11 +172,11 @@ process_command_line_arguments(struct mg_context *ctx, char *argv[]) /* First find out, which config file to open */ for (i = 1; argv[i] != NULL && argv[i][0] == '-'; i += 2) if (argv[i + 1] == NULL) - show_usage_and_exit(); + show_usage_and_exit(config); if (argv[i] != NULL && argv[i + 1] != NULL) { /* More than one non-option arguments are given */ - show_usage_and_exit(); + show_usage_and_exit(config); } else if (argv[i] != NULL) { /* Just one non-option argument is given, this is config file */ config_file = argv[i]; @@ -140,7 +202,8 @@ process_command_line_arguments(struct mg_context *ctx, char *argv[]) (void) memset(vals, 0, sizeof(vals)); if (fp != NULL) { - (void) printf("Loading config file %s\n", config_file); + (void) printf("Loading config file %s, " + "ignoring command line arguments\n", config_file); /* Loop over the lines in config file */ while (fgets(line, sizeof(line), fp) != NULL) { @@ -156,56 +219,67 @@ process_command_line_arguments(struct mg_context *ctx, char *argv[]) config_file, (int) line_no); exit(EXIT_FAILURE); } - if (mg_set_option(ctx, opt, val) != 1) - exit(EXIT_FAILURE); + /* TODO(lsm): free this at some point */ + p = malloc(strlen(val) + 1); + (void) strcpy(p, val); + set_option(config, opt, p); } (void) fclose(fp); + } else { + for (i = 1; argv[i] != NULL && argv[i][0] == '-'; i += 2) + set_option(config, &argv[i][1], argv[i + 1]); } - - /* Now pass through the command line options */ - for (i = 1; argv[i] != NULL && argv[i][0] == '-'; i += 2) - if (mg_set_option(ctx, &argv[i][1], argv[i + 1]) != 1) - exit(EXIT_FAILURE); } int main(int argc, char *argv[]) { + struct mg_config config; struct mg_context *ctx; - char ports[1024], web_root[1024]; + /* Initialize configuration with default values */ + (void) memset(&config, 0, sizeof(config)); + config.document_root = "."; + config.enable_directory_listing = "yes"; + config.auth_domain = "mydomain.com"; + config.num_threads = "20"; + config.index_files = "index.html,index.htm,index.cgi"; + config.cgi_extensions = ".cgi,.pl,.php"; + config.ssi_extensions = ".shtml,.shtm"; + config.listening_ports = "8080"; + + /* Edit passwords file if -A option is specified */ if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'A') { if (argc != 6) - show_usage_and_exit(); + show_usage_and_exit(&config); exit(mg_edit_passwords(argv[2], argv[3], argv[4], argv[5]) == MG_SUCCESS ? EXIT_SUCCESS : EXIT_FAILURE); } + /* Show usage if -h or --help options are specified */ if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) - show_usage_and_exit(); + show_usage_and_exit(&config); + /* Update config based on command line arguments */ + process_command_line_arguments(&config, argv); + + /* Setup signal handler: quit on Ctrl-C */ #ifndef _WIN32 (void) signal(SIGCHLD, signal_handler); #endif /* _WIN32 */ (void) signal(SIGTERM, signal_handler); (void) signal(SIGINT, signal_handler); - if ((ctx = mg_start()) == NULL) { + /* Start Mongoose */ + if ((ctx = mg_start(&config)) == NULL) { (void) printf("%s\n", "Cannot initialize Mongoose context"); exit(EXIT_FAILURE); } - process_command_line_arguments(ctx, argv); - (void) mg_get_option(ctx, "ports", ports, sizeof(ports)); - if (ports[0] == '\0' && - mg_set_option(ctx, "ports", "8080") != MG_SUCCESS) - exit(EXIT_FAILURE); - - (void) mg_get_option(ctx, "ports", ports, sizeof(ports)); - (void) mg_get_option(ctx, "root", web_root, sizeof(web_root)); - (void) printf("Mongoose %s started on port(s) \"%s\", " - "serving directory \"%s\"\n", mg_version(), ports, web_root); + (void) printf("Mongoose %s started on port(s) %s " + "with web root [%s]\n", + mg_version(), config.listening_ports, config.document_root); fflush(stdout); while (exit_flag == 0) diff --git a/mongoose.c b/mongoose.c index 40494923..d7abd82e 100644 --- a/mongoose.c +++ b/mongoose.c @@ -26,8 +26,8 @@ #if defined(_WIN32) #define _CRT_SECURE_NO_WARNINGS /* Disable deprecation warning in VS2005 */ #else -#define _XOPEN_SOURCE 600 /* Needed for pthread_rwlock on linux */ -#define _LARGEFILE_SOURCE /* Enable 64-bit file offsets */ +#define _XOPEN_SOURCE 600 /* For flockfile() on Linux */ +#define _LARGEFILE_SOURCE /* Enable 64-bit file offsets */ #endif #ifndef _WIN32_WCE /* Some ANSI #includes are not available on Windows CE */ @@ -129,7 +129,6 @@ typedef long off_t; #endif /* !fileno MINGW #defines fileno */ typedef HANDLE pthread_mutex_t; -typedef HANDLE pthread_rwlock_t; typedef HANDLE pthread_cond_t; typedef DWORD pthread_t; #define pid_t HANDLE /* MINGW typedefs pid_t to int. Using #define here. */ @@ -143,13 +142,6 @@ static int pthread_mutex_lock(pthread_mutex_t *); static int pthread_mutex_unlock(pthread_mutex_t *); static FILE *mg_fopen(const char *path, const char *mode); -/* TODO(lsm): Implement these */ -#define pthread_rwlock_init pthread_mutex_init -#define pthread_rwlock_destroy pthread_mutex_destroy -#define pthread_rwlock_rdlock pthread_mutex_lock -#define pthread_rwlock_wrlock pthread_mutex_lock -#define pthread_rwlock_unlock pthread_mutex_unlock - #if defined(HAVE_STDINT) #include #else @@ -367,27 +359,6 @@ struct mgstat { time_t mtime; /* Modification time */ }; -struct mg_option { - const char *name; - const char *description; - const char *default_value; - int index; - enum mg_error_t (*setter)(struct mg_context *, const char *); -}; - -/* - * Numeric indexes for the option values in context, ctx->options - */ -enum mg_option_index { - OPT_ROOT, OPT_INDEX_FILES, OPT_PORTS, OPT_DIR_LIST, OPT_CGI_EXTENSIONS, - OPT_CGI_INTERPRETER, OPT_CGI_ENV, OPT_SSI_EXTENSIONS, OPT_AUTH_DOMAIN, - OPT_AUTH_GPASSWD, OPT_AUTH_PUT, OPT_ACCESS_LOG, OPT_ERROR_LOG, - OPT_SSL_CERTIFICATE, OPT_ALIASES, OPT_ACL, OPT_UID, OPT_PROTECT, - OPT_SERVICE, OPT_HIDE, OPT_ADMIN_URI, OPT_MAX_THREADS, OPT_IDLE_TIME, - OPT_MIME_TYPES, - NUM_OPTIONS -}; - /* * Structure used to describe listening socket, or socket which was * accept()-ed by the master thread and queued for future handling @@ -407,16 +378,14 @@ struct socket { struct mg_context { int stop_flag; /* Should we stop event loop */ SSL_CTX *ssl_ctx; /* SSL context */ + const struct mg_config *config; /* Mongoose configuration */ struct socket *listening_sockets; - char *options[NUM_OPTIONS]; - mg_callback_t callbacks[NUM_EVENTS]; int num_threads; /* Number of threads */ int num_idle; /* Number of idle threads */ pthread_mutex_t mutex; /* Protects (max|num)_threads */ - pthread_rwlock_t rwlock; /* Protects options, callbacks */ pthread_cond_t thr_cond; /* Condvar for thread sync */ struct socket queue[20]; /* Accepted sockets */ @@ -461,15 +430,13 @@ cry(struct mg_connection *conn, const char *fmt, ...) * I suppose this is fine, since function cannot disappear in the * same way string option can. */ - log_callback = conn->ctx->callbacks[MG_EVENT_LOG]; + log_callback = conn->ctx->config->event_log_handler; conn->request_info.log_message = buf; if (log_callback != NULL) processed = log_callback(conn, &conn->request_info); if (processed == MG_ERROR) { - (void) pthread_rwlock_rdlock(&conn->ctx->rwlock); - fp = conn->ctx->options[OPT_ERROR_LOG] == NULL ? stderr : - mg_fopen(conn->ctx->options[OPT_ERROR_LOG], "a+"); - (void) pthread_rwlock_unlock(&conn->ctx->rwlock); + fp = conn->ctx->config->error_log_file == NULL ? stderr : + mg_fopen(conn->ctx->config->error_log_file, "a+"); if (fp != NULL) { flockfile(fp); @@ -530,7 +497,7 @@ fc(struct mg_context *ctx) const char * mg_version(void) { - return ("\"" MONGOOSE_VERSION ", $Rev: 517 $\""); + return (MONGOOSE_VERSION); } static void @@ -634,22 +601,6 @@ mg_snprintf(struct mg_connection *conn, return (n); } -/* - * Convert string representing a boolean value to a boolean value - */ -static bool_t -is_true(const char *str) -{ - static const char *trues[] = {"1", "yes", "true", "ja", NULL}; - int i; - - for (i = 0; trues[i] != NULL; i++) - if (str != NULL && mg_strcasecmp(str, trues[i]) == 0) - return (TRUE); - - return (FALSE); -} - /* * Skip the characters until one of the delimiters characters found. * 0-terminate resulting word. Skip the rest of the delimiters if any. @@ -775,7 +726,7 @@ send_error(struct mg_connection *conn, int status, const char *reason, DEBUG_TRACE((DEBUG_MGS_PREFIX "%s: %d %s", __func__, status, reason)); conn->request_info.status_code = status; - error_handler = conn->ctx->callbacks[MG_EVENT_HTTP_ERROR]; + error_handler = conn->ctx->config->http_error_handler; handled = error_handler ? error_handler(conn, &conn->request_info) : FALSE; @@ -1225,7 +1176,6 @@ spawn_process(struct mg_connection *conn, const char *prog, char *envblk, &si.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS); /* If CGI file is a script, try to read the interpreter line */ - (void) pthread_rwlock_rdlock(&conn->ctx->rwlock); interp = conn->ctx->options[OPT_CGI_INTERPRETER]; if (interp == NULL) { line[2] = '\0'; @@ -1249,7 +1199,6 @@ spawn_process(struct mg_connection *conn, const char *prog, char *envblk, (void) mg_snprintf(conn, cmdline, sizeof(cmdline), "%s%s%s", interp, interp[0] == '\0' ? "" : " ", prog); - (void) pthread_rwlock_unlock(&conn->ctx->rwlock); (void) mg_snprintf(conn, line, sizeof(line), "%s", dir); fix_directory_separators(line); @@ -1364,7 +1313,7 @@ spawn_process(struct mg_connection *conn, const char *prog, char *envblk, (void) close(fd_stdout); /* Execute CGI program. No need to lock: new process */ - interp = conn->ctx->options[OPT_CGI_INTERPRETER]; + interp = conn->ctx->config->cgi_interpreter; if (interp == NULL) { (void) execle(prog, prog, NULL, envp); cry(conn, "%s: execle(%s): %s", @@ -1618,6 +1567,32 @@ mg_get_cookie(const struct mg_connection *conn, return (MG_NOT_FOUND); } +/* + * Get document root for the given URI. + */ +static int +get_document_root(const struct mg_connection *conn, struct vec *document_root) +{ + const char *root, *uri; + int len_of_matched_uri; + struct vec uri_vec, path_vec; + + uri = conn->request_info.uri; + len_of_matched_uri = 0; + root = next_option(conn->ctx->config->document_root, + document_root, NULL); + + while ((root = next_option(root, &uri_vec, &path_vec)) != NULL) { + if (memcmp(uri, uri_vec.ptr, uri_vec.len) == 0) { + *document_root = path_vec; + len_of_matched_uri = uri_vec.len; + break; + } + } + + return (len_of_matched_uri); +} + /* * Transform URI to the file name. */ @@ -1625,24 +1600,12 @@ static void convert_uri_to_file_name(struct mg_connection *conn, const char *uri, char *buf, size_t buf_len) { - struct mg_context *ctx = conn->ctx; - struct vec uri_vec, path_vec; - const char *list; + struct vec vec; + int match_len; - (void) pthread_rwlock_rdlock(&conn->ctx->rwlock); - mg_snprintf(conn, buf, buf_len, "%s%s", ctx->options[OPT_ROOT], uri); - - /* If requested URI has aliased prefix, use alternate root */ - list = ctx->options[OPT_ALIASES]; - - while ((list = next_option(list, &uri_vec, &path_vec)) != NULL) { - if (memcmp(uri, uri_vec.ptr, uri_vec.len) == 0) { - (void) mg_snprintf(conn, buf, buf_len, "%.*s%s", - path_vec.len, path_vec.ptr, uri + uri_vec.len); - break; - } - } - (void) pthread_rwlock_unlock(&conn->ctx->rwlock); + match_len = get_document_root(conn, &vec); + (void) mg_snprintf(conn, buf, buf_len, "%.*s%s", + vec.len, vec.ptr, uri + match_len); #ifdef _WIN32 fix_directory_separators(buf); @@ -1873,18 +1836,15 @@ get_mime_type(struct mg_context *ctx, const char *path, struct vec *vec) * Scan user-defined mime types first, in case user wants to * override default mime types. */ - (void) pthread_rwlock_rdlock(&ctx->rwlock); - list = ctx->options[OPT_MIME_TYPES]; + list = ctx->config->mime_types; while ((list = next_option(list, &ext_vec, &mime_vec)) != NULL) { /* ext now points to the path suffix */ ext = path + path_len - ext_vec.len; if (mg_strncasecmp(ext, ext_vec.ptr, ext_vec.len) == 0) { *vec = mime_vec; - (void) pthread_rwlock_unlock(&ctx->rwlock); return; } } - (void) pthread_rwlock_unlock(&ctx->rwlock); /* Now scan built-in mime types */ for (i = 0; mime_types[i].extension != NULL; i++) { @@ -2217,13 +2177,12 @@ open_auth_file(struct mg_connection *conn, const char *path) struct mgstat st; FILE *fp; - (void) pthread_rwlock_rdlock(&conn->ctx->rwlock); - if (ctx->options[OPT_AUTH_GPASSWD] != NULL) { + if (ctx->config->global_passwords_file != NULL) { /* Use global passwords file */ - fp = mg_fopen(ctx->options[OPT_AUTH_GPASSWD], "r"); + fp = mg_fopen(ctx->config->global_passwords_file, "r"); if (fp == NULL) cry(fc(ctx), "fopen(%s): %s", - ctx->options[OPT_AUTH_GPASSWD], strerror(ERRNO)); + ctx->config->global_passwords_file, strerror(ERRNO)); } else if (!mg_stat(path, &st) && st.is_directory) { (void) mg_snprintf(conn, name, sizeof(name), "%s%c%s", path, DIRSEP, PASSWORDS_FILE_NAME); @@ -2249,7 +2208,6 @@ open_auth_file(struct mg_connection *conn, const char *path) (int) (e - p), p, DIRSEP, PASSWORDS_FILE_NAME); fp = mg_fopen(name, "r"); } - (void) pthread_rwlock_unlock(&conn->ctx->rwlock); return (fp); } @@ -2330,24 +2288,20 @@ static bool_t authorize(struct mg_connection *conn, FILE *fp) { struct ah ah; - char line[256], f_user[256], domain[256], ha1[256], + char line[256], f_user[256], ha1[256], f_domain[256], buf[MAX_REQUEST_SIZE]; if (!parse_auth_header(conn, buf, sizeof(buf), &ah)) return (FALSE); - (void) pthread_rwlock_rdlock(&conn->ctx->rwlock); - mg_strlcpy(domain, conn->ctx->options[OPT_AUTH_DOMAIN], sizeof(domain)); - (void) pthread_rwlock_unlock(&conn->ctx->rwlock); - - /* Loop over passwords file */ while (fgets(line, sizeof(line), fp) != NULL) { if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) != 3) continue; - if (!strcmp(ah.user, f_user) && !strcmp(domain, f_domain)) + if (!strcmp(ah.user, f_user) && + !strcmp(conn->ctx->config->auth_domain, f_domain)) return (check_password( conn->request_info.request_method, ha1, ah.uri, ah.nonce, ah.nc, ah.cnonce, ah.qop, @@ -2372,8 +2326,7 @@ check_authorization(struct mg_connection *conn, const char *path) fp = NULL; authorized = TRUE; - (void) pthread_rwlock_rdlock(&conn->ctx->rwlock); - list = conn->ctx->options[OPT_PROTECT]; + list = conn->ctx->config->protect; while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) { if (!memcmp(conn->request_info.uri, uri_vec.ptr, uri_vec.len)) { (void) mg_snprintf(conn, fname, sizeof(fname), "%.*s", @@ -2384,7 +2337,6 @@ check_authorization(struct mg_connection *conn, const char *path) break; } } - (void) pthread_rwlock_unlock(&conn->ctx->rwlock); if (fp == NULL) fp = open_auth_file(conn, path); @@ -2400,18 +2352,13 @@ check_authorization(struct mg_connection *conn, const char *path) static void send_authorization_request(struct mg_connection *conn) { - char domain[128]; - - (void) pthread_rwlock_rdlock(&conn->ctx->rwlock); - mg_strlcpy(domain, conn->ctx->options[OPT_AUTH_DOMAIN], sizeof(domain)); - (void) pthread_rwlock_unlock(&conn->ctx->rwlock); - conn->request_info.status_code = 401; (void) mg_printf(conn, "HTTP/1.1 401 Unauthorized\r\n" "WWW-Authenticate: Digest qop=\"auth\", " "realm=\"%s\", nonce=\"%lu\"\r\n\r\n", - domain, (unsigned long) time(NULL)); + conn->ctx->config->auth_domain, + (unsigned long) time(NULL)); } static bool_t @@ -2420,10 +2367,8 @@ is_authorized_for_put(struct mg_connection *conn) FILE *fp; int ret = FALSE; - (void) pthread_rwlock_rdlock(&conn->ctx->rwlock); - fp = conn->ctx->options[OPT_AUTH_PUT] == NULL ? NULL : - mg_fopen(conn->ctx->options[OPT_AUTH_PUT], "r"); - (void) pthread_rwlock_unlock(&conn->ctx->rwlock); + fp = conn->ctx->config->put_delete_passwords_file == NULL ? NULL : + mg_fopen(conn->ctx->config->put_delete_passwords_file, "r"); if (fp != NULL) { @@ -2439,16 +2384,13 @@ mg_modify_passwords_file(struct mg_context *ctx, const char *fname, const char *user, const char *pass) { int found; - char line[512], u[512], d[512], domain[512], - ha1[33], tmp[FILENAME_MAX]; + char line[512], u[512], d[512], ha1[33], tmp[FILENAME_MAX]; + const char *domain; FILE *fp, *fp2; found = 0; fp = fp2 = NULL; - - (void) pthread_rwlock_rdlock(&ctx->rwlock); - mg_strlcpy(domain, ctx->options[OPT_AUTH_DOMAIN], sizeof(domain)); - (void) pthread_rwlock_unlock(&ctx->rwlock); + domain = ctx->config->auth_domain; /* Regard empty password as no password - remove user record. */ if (pass[0] == '\0') @@ -2731,7 +2673,7 @@ send_opened_file_stream(struct mg_connection *conn, FILE *fp, int64_t len) static int parse_range_header(const char *header, int64_t *a, int64_t *b) { - return sscanf(header, "bytes=%" INT64_FMT "u-%" INT64_FMT "u", a, b); + return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b); } /* @@ -2906,8 +2848,7 @@ substitute_index_file(struct mg_connection *conn, * Traverse index files list. For each entry, append it to the given * path and see if the file exists. If it exists, break the loop */ - (void) pthread_rwlock_rdlock(&conn->ctx->rwlock); - list = conn->ctx->options[OPT_INDEX_FILES]; + list = conn->ctx->config->index_files; found = FALSE; while ((list = next_option(list, &filename_vec, NULL)) != NULL) { @@ -2928,7 +2869,6 @@ substitute_index_file(struct mg_connection *conn, break; } } - (void) pthread_rwlock_unlock(&conn->ctx->rwlock); /* If no index file exists, restore directory path */ if (found == FALSE) @@ -3100,7 +3040,7 @@ prepare_cgi_environment(struct mg_connection *conn, const char *prog, struct cgi_env_block *blk) { const char *s, *script_filename, *slash; - struct vec var_vec; + struct vec var_vec, root; char *p; int i; @@ -3112,11 +3052,11 @@ prepare_cgi_environment(struct mg_connection *conn, const char *prog, if ((s = strrchr(prog, '/')) != NULL) script_filename = s + 1; - (void) pthread_rwlock_rdlock(&conn->ctx->rwlock); - addenv(blk, "SERVER_NAME=%s", conn->ctx->options[OPT_AUTH_DOMAIN]); - addenv(blk, "SERVER_ROOT=%s", conn->ctx->options[OPT_ROOT]); - addenv(blk, "DOCUMENT_ROOT=%s", conn->ctx->options[OPT_ROOT]); - (void) pthread_rwlock_unlock(&conn->ctx->rwlock); + get_document_root(conn, &root); + + addenv(blk, "SERVER_NAME=%s", conn->ctx->config->auth_domain); + addenv(blk, "SERVER_ROOT=%.*s", root.len, root.ptr); + addenv(blk, "DOCUMENT_ROOT=%.*s", root.len, root.ptr); /* Prepare the environment block */ addenv(blk, "%s", "GATEWAY_INTERFACE=CGI/1.1"); @@ -3183,11 +3123,9 @@ prepare_cgi_environment(struct mg_connection *conn, const char *prog, } /* Add user-specified variables */ - (void) pthread_rwlock_rdlock(&conn->ctx->rwlock); - s = conn->ctx->options[OPT_CGI_ENV]; + s = conn->ctx->config->cgi_environment; while ((s = next_option(s, &var_vec, NULL)) != NULL) addenv(blk, "%.*s", var_vec.len, var_vec.ptr); - (void) pthread_rwlock_unlock(&conn->ctx->rwlock); blk->vars[blk->nvars++] = NULL; blk->buf[blk->len++] = '\0'; @@ -3385,9 +3323,12 @@ static void do_ssi_include(struct mg_connection *conn, const char *ssi, char *tag, int include_level) { - char file_name[BUFSIZ], path[FILENAME_MAX], *p; - bool_t is_ssi; - FILE *fp; + char file_name[BUFSIZ], path[FILENAME_MAX], *p; + struct vec root; + bool_t is_ssi; + FILE *fp; + + get_document_root(conn, &root); /* * sscanf() is safe here, since send_ssi_file() also uses buffer @@ -3395,10 +3336,8 @@ do_ssi_include(struct mg_connection *conn, const char *ssi, char *tag, */ if (sscanf(tag, " virtual=\"%[^\"]\"", file_name) == 1) { /* File name is relative to the webserver root */ - (void) pthread_rwlock_rdlock(&conn->ctx->rwlock); - (void) mg_snprintf(conn, path, sizeof(path), "%s%c%s", - conn->ctx->options[OPT_ROOT], DIRSEP, file_name); - (void) pthread_rwlock_unlock(&conn->ctx->rwlock); + (void) mg_snprintf(conn, path, sizeof(path), "%.*s%c%s", + root.len, root.ptr, DIRSEP, file_name); } else if (sscanf(tag, " file=\"%[^\"]\"", file_name) == 1) { /* * File name is relative to the webserver working directory @@ -3422,10 +3361,8 @@ do_ssi_include(struct mg_connection *conn, const char *ssi, char *tag, tag, path, strerror(ERRNO)); } else { set_close_on_exec(fileno(fp)); - (void) pthread_rwlock_rdlock(&conn->ctx->rwlock); is_ssi = match_extension(path, - conn->ctx->options[OPT_SSI_EXTENSIONS]); - (void) pthread_rwlock_unlock(&conn->ctx->rwlock); + conn->ctx->config->ssi_extensions); if (is_ssi) { send_ssi_file(conn, path, fp, include_level + 1); } else { @@ -3542,7 +3479,7 @@ send_ssi(struct mg_connection *conn, const char *path) * a directory, or call embedded function, etcetera. */ static void -analyze_request(struct mg_connection *conn) +handle_request(struct mg_connection *conn) { struct mg_request_info *ri; char path[FILENAME_MAX]; @@ -3555,7 +3492,7 @@ analyze_request(struct mg_connection *conn) if ((conn->request_info.query_string = strchr(ri->uri, '?')) != NULL) * conn->request_info.query_string++ = '\0'; uri_len = strlen(ri->uri); - new_request_callback = conn->ctx->callbacks[MG_EVENT_NEW_REQUEST]; + new_request_callback = conn->ctx->config->new_request_handler; (void) url_decode(ri->uri, uri_len, ri->uri, uri_len + 1, FALSE); remove_double_dots_and_double_slashes(ri->uri); convert_uri_to_file_name(conn, ri->uri, path, sizeof(path)); @@ -3567,9 +3504,11 @@ analyze_request(struct mg_connection *conn) } else if (strstr(path, PASSWORDS_FILE_NAME)) { /* Do not allow to view passwords files */ send_error(conn, 403, "Forbidden", "Access Forbidden"); + } else if (conn->ctx->config->document_root == NULL) { + send_error(conn, 404, "Not Found", "Not Found"); } else if ((!strcmp(ri->request_method, "PUT") || !strcmp(ri->request_method, "DELETE")) && - (conn->ctx->options[OPT_AUTH_PUT] == NULL || + (conn->ctx->config->put_delete_passwords_file == NULL || !is_authorized_for_put(conn))) { send_authorization_request(conn); } else if (!strcmp(ri->request_method, "PUT")) { @@ -3588,15 +3527,14 @@ analyze_request(struct mg_connection *conn) "Location: %s/\r\n\r\n", ri->uri); } else if (st.is_directory && substitute_index_file(conn, path, sizeof(path), &st) == FALSE) { - if (is_true(conn->ctx->options[OPT_DIR_LIST])) { + if (conn->ctx->config->enable_directory_listing) { send_directory(conn, path); } else { send_error(conn, 403, "Directory Listing Denied", "Directory listing denied"); } #if !defined(NO_CGI) - } else if (match_extension(path, - conn->ctx->options[OPT_CGI_EXTENSIONS])) { + } else if (match_extension(path, conn->ctx->config->cgi_extensions)) { if (strcmp(ri->request_method, "POST") && strcmp(ri->request_method, "GET")) { send_error(conn, 501, "Not Implemented", @@ -3606,8 +3544,7 @@ analyze_request(struct mg_connection *conn) } #endif /* NO_CGI */ #if !defined(NO_SSI) - } else if (match_extension(path, - conn->ctx->options[OPT_SSI_EXTENSIONS])) { + } else if (match_extension(path, conn->ctx->config->ssi_extensions)) { send_ssi(conn, path); #endif /* NO_SSI */ } else if (is_not_modified(conn, &st)) { @@ -3630,12 +3567,13 @@ close_all_listening_sockets(struct mg_context *ctx) } static enum mg_error_t -set_ports_option(struct mg_context *ctx, const char *list) +set_ports_option(struct mg_context *ctx) { SOCKET sock; int is_ssl; struct vec vec; struct socket *listener; + const char *list = ctx->config->listening_ports; close_all_listening_sockets(ctx); @@ -3685,10 +3623,8 @@ log_access(const struct mg_connection *conn) FILE *fp; char date[64]; - (void) pthread_rwlock_rdlock(&conn->ctx->rwlock); - fp = conn->ctx->options[OPT_ACCESS_LOG] == NULL ? NULL : - mg_fopen(conn->ctx->options[OPT_ACCESS_LOG], "a+"); - (void) pthread_rwlock_unlock(&conn->ctx->rwlock); + fp = conn->ctx->config->access_log_file == NULL ? NULL : + mg_fopen(conn->ctx->config->access_log_file, "a+"); if (fp == NULL) return; @@ -3728,12 +3664,16 @@ isbyte(int n) { * Return -1 if ACL is malformed, 0 if address is disallowed, 1 if allowed. */ static enum mg_error_t -check_acl(struct mg_context *ctx, const char *list, const struct usa *usa) +check_acl(struct mg_context *ctx, const struct usa *usa) { int a, b, c, d, n, mask, allowed; char flag; uint32_t acl_subnet, acl_mask, remote_ip; struct vec vec; + const char *list = ctx->config->acl; + + if (list == NULL) + return (MG_SUCCESS); (void) memcpy(&remote_ip, &usa->u.sin.sin_addr, sizeof(remote_ip)); @@ -3783,58 +3723,28 @@ add_to_set(SOCKET fd, fd_set *set, int *max_fd) *max_fd = (int) fd; } -/* - * Deallocate mongoose context, free up the resources - */ -static void -mg_fini(struct mg_context *ctx) -{ - int i; - - close_all_listening_sockets(ctx); - - /* Wait until all threads finish */ - (void) pthread_mutex_lock(&ctx->mutex); - while (ctx->num_threads > 0) - (void) pthread_cond_wait(&ctx->thr_cond, &ctx->mutex); - (void) pthread_mutex_unlock(&ctx->mutex); - - /* Deallocate all options */ - for (i = 0; i < NUM_OPTIONS; i++) - if (ctx->options[i] != NULL) - free(ctx->options[i]); - - /* Deallocate SSL context */ - if (ctx->ssl_ctx) - SSL_CTX_free(ctx->ssl_ctx); - - (void) pthread_rwlock_destroy(&ctx->rwlock); - (void) pthread_mutex_destroy(&ctx->mutex); - (void) pthread_cond_destroy(&ctx->thr_cond); - (void) pthread_cond_destroy(&ctx->empty_cond); - (void) pthread_cond_destroy(&ctx->full_cond); - - /* Signal mg_stop() that we're done */ - ctx->stop_flag = 2; -} - #if !defined(_WIN32) static enum mg_error_t -set_uid_option(struct mg_context *ctx, const char *uid) +set_uid_option(struct mg_context *ctx) { struct passwd *pw; - enum mg_error_t error = MG_ERROR; + const char *uid = ctx->config->uid; + enum mg_error_t error; - if ((pw = getpwnam(uid)) == NULL) - cry(fc(ctx), "%s: unknown user [%s]", __func__, uid); - else if (setgid(pw->pw_gid) == -1) - cry(fc(ctx), "%s: setgid(%s): %s", - __func__, uid, strerror(errno)); - else if (setuid(pw->pw_uid) == -1) - cry(fc(ctx), "%s: setuid(%s): %s", - __func__, uid, strerror(errno)); - else + if (uid == NULL) { error = MG_SUCCESS; + } else { + if ((pw = getpwnam(uid)) == NULL) + cry(fc(ctx), "%s: unknown user [%s]", __func__, uid); + else if (setgid(pw->pw_gid) == -1) + cry(fc(ctx), "%s: setgid(%s): %s", + __func__, uid, strerror(errno)); + else if (setuid(pw->pw_uid) == -1) + cry(fc(ctx), "%s: setuid(%s): %s", + __func__, uid, strerror(errno)); + else + error = MG_SUCCESS; + } return (error); } @@ -3900,10 +3810,14 @@ load_dll(struct mg_context *ctx, const char *dll_name, struct ssl_func *sw) * Dynamically load SSL library. Set up ctx->ssl_ctx pointer. */ static enum mg_error_t -set_ssl_option(struct mg_context *ctx, const char *pem) +set_ssl_option(struct mg_context *ctx) { SSL_CTX *CTX; int i, size; + const char *pem = ctx->config->ssl_certificate; + + if (pem == NULL) + return (MG_SUCCESS); if (load_dll(ctx, SSL_LIB, ssl_sw) == FALSE || load_dll(ctx, CRYPTO_LIB, crypto_sw) == FALSE) @@ -3915,9 +3829,9 @@ set_ssl_option(struct mg_context *ctx, const char *pem) if ((CTX = SSL_CTX_new(SSLv23_server_method())) == NULL) ssl_cry(fc(ctx), "SSL_CTX_new error"); - else if (ctx->callbacks[MG_EVENT_SSL_PASSWORD] != NULL) + else if (ctx->config->ssl_password_handler != NULL) SSL_CTX_set_default_passwd_cb(CTX, - ctx->callbacks[MG_EVENT_SSL_PASSWORD]); + ctx->config->ssl_password_handler); if (CTX != NULL && SSL_CTX_use_certificate_file( CTX, pem, SSL_FILETYPE_PEM) == 0) { @@ -3953,248 +3867,41 @@ set_ssl_option(struct mg_context *ctx, const char *pem) #endif /* !NO_SSL */ static enum mg_error_t -set_gpass_option(struct mg_context *ctx, const char *path) +set_gpass_option(struct mg_context *ctx) { struct mgstat mgstat; - ctx = NULL; - return (mg_stat(path, &mgstat) == 0 ? MG_SUCCESS : MG_ERROR); + const char *path = ctx->config->global_passwords_file; + + return (path == NULL || mg_stat(path, &mgstat) == 0 ? + MG_SUCCESS : MG_ERROR); } static enum mg_error_t -set_acl_option(struct mg_context *ctx, const char *acl) +set_acl_option(struct mg_context *ctx) { struct usa fake; - return (check_acl(ctx, acl, &fake)); + return (check_acl(ctx, &fake)); } -/* - * Check if the comma-separated list of options has a format of key-value - * pairs: "k1=v1,k2=v2". Return FALSE if any entry has invalid key or value. - */ -static enum mg_error_t -set_kv_list_option(struct mg_context *ctx, const char *str) +static bool_t +verify_document_root(struct mg_context *ctx, const char *root) { - const char *list; - struct vec key, value; + char path[FILENAME_MAX], *p; + struct mgstat buf; - list = str; - while ((list = next_option(list, &key, &value)) != NULL) - if (key.len == 0 || value.len == 0) { - cry(fc(ctx), "Invalid list specified: [%s], " - "expecting key1=value1,key2=value2,...", str); - return (MG_ERROR); - } + if ((p = strchr(root, ',')) == NULL) { + mg_strlcpy(path, root, sizeof(path)); + } else { + mg_strlcpy(path, root, p - root + 1); + } - return (MG_SUCCESS); -} - -static enum mg_error_t -set_root_option(struct mg_context *ctx, const char *root) -{ - struct mgstat buf; - if ( mg_stat(root, &buf) != 0) { + if (mg_stat(path, &buf) != 0) { cry(fc(ctx), "Invalid root directory: \"%s\"", root); - return (MG_ERROR); - + return (FALSE); } - return (MG_SUCCESS); + return (TRUE); } -static const struct mg_option known_options[] = { - {"root", "\tWeb root directory", ".", OPT_ROOT, set_root_option}, - {"index_files", "Index files", "index.html,index.htm,index.cgi", - OPT_INDEX_FILES, NULL}, -#if !defined(NO_SSL) - {"ssl_cert", "SSL certificate file", NULL, - OPT_SSL_CERTIFICATE, &set_ssl_option}, -#endif /* !NO_SSL */ - {"ports", "Listening ports", NULL, - OPT_PORTS, &set_ports_option}, - {"dir_list", "Directory listing", "yes", - OPT_DIR_LIST, NULL}, - {"protect", "URI to htpasswd mapping", NULL, - OPT_PROTECT, &set_kv_list_option}, -#if !defined(NO_CGI) - {"cgi_ext", "CGI extensions", ".cgi,.pl,.php", - OPT_CGI_EXTENSIONS, NULL}, - {"cgi_interp", "CGI interpreter to use with all CGI scripts", NULL, - OPT_CGI_INTERPRETER, NULL}, - {"cgi_env", "Custom CGI enviroment variables", NULL, - OPT_CGI_ENV, &set_kv_list_option}, -#endif /* NO_CGI */ - {"ssi_ext", "SSI extensions", ".shtml,.shtm", - OPT_SSI_EXTENSIONS, NULL}, - {"auth_realm", "Authentication domain name", "mydomain.com", - OPT_AUTH_DOMAIN, NULL}, - {"auth_gpass", "Global passwords file", NULL, - OPT_AUTH_GPASSWD, &set_gpass_option}, - {"auth_PUT", "PUT,DELETE auth file", NULL, - OPT_AUTH_PUT, NULL}, -#if !defined(_WIN32) - {"uid", "\tRun as user", NULL, OPT_UID, &set_uid_option}, -#endif /* !_WIN32 */ - {"access_log", "Access log file", NULL, OPT_ACCESS_LOG, NULL}, - {"error_log", "Error log file", NULL, OPT_ERROR_LOG, NULL}, - {"aliases", "Path=URI mappings", NULL, - OPT_ALIASES, &set_kv_list_option}, - {"admin_uri", "Administration page URI", NULL, OPT_ADMIN_URI, NULL}, - {"acl", "\tAllow/deny IP addresses/subnets", NULL, - OPT_ACL, &set_acl_option}, - {"max_threads", "Maximum simultaneous threads to spawn", "100", - OPT_MAX_THREADS, NULL}, - {"idle_time", "Time in seconds connection stays idle", "10", - OPT_IDLE_TIME, NULL}, - {"mime_types", "Comma separated list of ext=mime_type pairs", NULL, - OPT_MIME_TYPES, &set_kv_list_option}, - {NULL, NULL, NULL, 0, NULL} -}; - -static const struct mg_option * -find_opt(const char *opt_name) -{ - int i; - - for (i = 0; known_options[i].name != NULL; i++) - if (!strcmp(opt_name, known_options[i].name)) - return (known_options + i); - - return (NULL); -} - -enum mg_error_t -mg_set_option(struct mg_context *ctx, const char *opt, const char *val) -{ - const struct mg_option *option; - int i, error; - - DEBUG_TRACE((DEBUG_MGS_PREFIX "%s: [%s]->[%s]", __func__, opt, val)); - if (opt != NULL && (option = find_opt(opt)) != NULL) { - i = (int) (option - known_options); - error = option->setter ? option->setter(ctx, val) : MG_SUCCESS; - - /* - * Write lock the option. Free old value, set new value. - * Make sure no calls that may trigger read lock are made. - */ - (void) pthread_rwlock_wrlock(&ctx->rwlock); - if (ctx->options[option->index] != NULL) - free(ctx->options[option->index]); - ctx->options[option->index] = val ? mg_strdup(val) : NULL; - (void) pthread_rwlock_unlock(&ctx->rwlock); - - if (error != MG_SUCCESS) - cry(fc(ctx), "%s(%s): failure", __func__, opt); - } else { - cry(fc(ctx), "%s: No such option: [%s]", __func__, opt); - error = MG_NOT_FOUND; - } - - return (error); -} - -void -mg_show_usage_string(FILE *fp) -{ - const struct mg_option *o; - - (void) fprintf(stderr, - "Mongoose version %s (c) Sergey Lyubka\n" - "usage: mongoose [options] [config_file]\n", mg_version()); - - fprintf(fp, " -A \n"); - - for (o = known_options; o->name != NULL; o++) { - (void) fprintf(fp, " -%s\t%s", o->name, o->description); - if (o->default_value != NULL) - fprintf(fp, " (default: \"%s\")", o->default_value); - fputc('\n', fp); - } -} - -enum mg_error_t -mg_get_option(struct mg_context *ctx, const char *option_name, - char *dst, size_t dst_len) -{ - const struct mg_option *option; - enum mg_error_t error; - - (void) pthread_rwlock_rdlock(&ctx->rwlock); - if ((option = find_opt(option_name)) == NULL) { - error = MG_NOT_FOUND; - } else if (ctx->options[option->index] == NULL) { - error = MG_SUCCESS; - dst[0] = '\0'; - } else if (strlen(ctx->options[option->index]) >= dst_len) { - error = MG_BUFFER_TOO_SMALL; - } else { - mg_strlcpy(dst, ctx->options[option->index], dst_len); - error = MG_SUCCESS; - } - (void) pthread_rwlock_unlock(&ctx->rwlock); - - return (error); -} - -void -mg_set_callback(struct mg_context *ctx, enum mg_event_t event, mg_callback_t cb) -{ - /* No locking, no sanity checking. Suppose this is fine? */ - ctx->callbacks[event] = cb; -} - -#if 0 -static void -admin_page(struct mg_connection *conn, const struct mg_request_info *ri, - void *user_data) -{ - const struct mg_option *option; - const char *option_name, *option_value; - - user_data = NULL; /* Unused */ - - (void) mg_printf(conn, - "HTTP/1.1 200 OK\r\n" - "Content-Type: text/html\r\n\r\n" - "

Mongoose v. %s

", mg_version()); - - if (!strcmp(ri->request_method, "POST")) { - option_name = mg_get_var(conn, "o"); - option_value = mg_get_var(conn, "v"); - if (mg_set_option(conn->ctx, - option_name, option_value) == -1) { - (void) mg_printf(conn, - "

Error setting " - "option \"%s\"

", - option_name ? option_name : "(null)"); - } else { - (void) mg_printf(conn, - "

Saved: %s=%s

", - option_name, option_value ? option_value : "NULL"); - } - } - - /* Print table with all options */ - (void) mg_printf(conn, "%s", "" - ""); - - for (option = known_options; option->name != NULL; option++) { - option_value = mg_get_option(conn->ctx, option->name); - if (option_value == NULL) - option_value = ""; - (void) mg_printf(conn, - "" - "" - "" - "", - option->name, option->description, - option->name, option_value); - } - - (void) mg_printf(conn, "%s", "
OptionDescriptionValue
%s%s
"); -} -#endif - static void reset_per_request_attributes(struct mg_connection *conn) { @@ -4312,7 +4019,7 @@ process_new_connection(struct mg_connection *conn) ri->post_data = buf + request_len; ri->post_data_len = nread - request_len; conn->birth_time = time(NULL); - analyze_request(conn); + handle_request(conn); log_access(conn); shift_to_next(conn, buf, request_len, &nread); } @@ -4340,7 +4047,7 @@ get_socket(struct mg_context *ctx, struct socket *sp) ctx->num_idle++; while (ctx->sq_head == ctx->sq_tail) { ts.tv_nsec = 0; - ts.tv_sec = time(NULL) + atoi(ctx->options[OPT_IDLE_TIME]) + 1; + ts.tv_sec = time(NULL) + 10; if (pthread_cond_timedwait(&ctx->empty_cond, &ctx->mutex, &ts) != 0) { /* Timeout! release the mutex and return */ @@ -4447,7 +4154,7 @@ put_socket(struct mg_context *ctx, const struct socket *sp) /* If there are no idle threads, start one */ if (ctx->num_idle == 0 && - ctx->num_threads < atoi(ctx->options[OPT_MAX_THREADS])) { + ctx->num_threads < atoi(ctx->config->num_threads)) { if (start_thread(ctx, (mg_thread_func_t) worker_thread, ctx) != 0) cry(fc(ctx), "Cannot start thread: %d", ERRNO); @@ -4471,10 +4178,7 @@ accept_new_connection(const struct socket *listener, struct mg_context *ctx) &accepted.rsa.u.sa, &accepted.rsa.len)) == INVALID_SOCKET) return; - (void) pthread_rwlock_rdlock(&ctx->rwlock); - allowed = ctx->options[OPT_ACL] == NULL || - check_acl(ctx, ctx->options[OPT_ACL], &accepted.rsa) == MG_SUCCESS; - (void) pthread_rwlock_unlock(&ctx->rwlock); + allowed = check_acl(ctx, &accepted.rsa) == MG_SUCCESS; if (allowed) { /* Put accepted socket structure into the queue */ @@ -4502,10 +4206,8 @@ master_thread(struct mg_context *ctx) max_fd = -1; /* Add listening sockets to the read set */ - (void) pthread_rwlock_rdlock(&ctx->rwlock); for (sp = ctx->listening_sockets; sp != NULL; sp = sp->next) add_to_set(sp->sock, &read_set, &max_fd); - (void) pthread_rwlock_unlock(&ctx->rwlock); tv.tv_sec = 1; tv.tv_usec = 0; @@ -4521,17 +4223,33 @@ master_thread(struct mg_context *ctx) sleep(1); #endif /* _WIN32 */ } else { - (void) pthread_rwlock_rdlock(&ctx->rwlock); for (sp = ctx->listening_sockets; sp != NULL; sp = sp->next) if (FD_ISSET(sp->sock, &read_set)) accept_new_connection(sp, ctx); - (void) pthread_rwlock_unlock(&ctx->rwlock); } } /* Stop signal received: somebody called mg_stop. Quit. */ - mg_fini(ctx); + close_all_listening_sockets(ctx); + + /* Wait until all threads finish */ + (void) pthread_mutex_lock(&ctx->mutex); + while (ctx->num_threads > 0) + (void) pthread_cond_wait(&ctx->thr_cond, &ctx->mutex); + (void) pthread_mutex_unlock(&ctx->mutex); + + /* Deallocate SSL context */ + if (ctx->ssl_ctx != NULL) + SSL_CTX_free(ctx->ssl_ctx); + + (void) pthread_mutex_destroy(&ctx->mutex); + (void) pthread_cond_destroy(&ctx->thr_cond); + (void) pthread_cond_destroy(&ctx->empty_cond); + (void) pthread_cond_destroy(&ctx->full_cond); + + /* Signal mg_stop() that we're done */ + ctx->stop_flag = 2; } void @@ -4552,38 +4270,48 @@ mg_stop(struct mg_context *ctx) } struct mg_context * -mg_start(void) +mg_start(struct mg_config *config) { - struct mg_context *ctx; - const struct mg_option *option; + struct mg_context *ctx, fake_ctx; #if defined(_WIN32) WSADATA data; WSAStartup(MAKEWORD(2,2), &data); #endif /* _WIN32 */ - if ((ctx = (struct mg_context *) calloc(1, sizeof(*ctx))) == NULL) { - cry(fc(ctx), "cannot allocate mongoose context"); + fake_ctx.config = config; + + if (config->listening_ports == NULL || + config->num_threads == NULL || + config->auth_domain == NULL) { + cry(fc(&fake_ctx), "Please specify " + "num_threads, listening_ports, auth_domain"); + return (NULL); + } else if (config->document_root != NULL && + verify_document_root(&fake_ctx, config->document_root) != TRUE) { + cry(fc(&fake_ctx), "Invalid root directory: \"%s\"", + config->document_root); + return (NULL); + } else if ((ctx = calloc(1, sizeof(*ctx))) == NULL) { + cry(fc(&fake_ctx), "Cannot allocate mongoose context"); return (NULL); } + ctx->config = config; - /* Initialize options. First pass: set default option values */ - for (option = known_options; option->name != NULL; option++) - ctx->options[option->index] = option->default_value == NULL ? - NULL : mg_strdup(option->default_value); - - /* Call setter functions */ - for (option = known_options; option->name != NULL; option++) - if (option->setter != NULL && - ctx->options[option->index] != NULL) - if (option->setter(ctx, - ctx->options[option->index]) == FALSE) { - mg_fini(ctx); - return (NULL); - } - - DEBUG_TRACE((DEBUG_MGS_PREFIX "%s: root [%s]", - __func__, ctx->options[OPT_ROOT])); + /* + * NOTE(lsm): order is important here. SSL certificates must + * be initialized before listening ports. + */ + if (set_ssl_option(ctx) == MG_ERROR || + set_ports_option(ctx) == MG_ERROR || + set_gpass_option(ctx) == MG_ERROR || +#if !defined(_WIN32) + set_uid_option(ctx) == MG_ERROR || +#endif + set_acl_option(ctx) == MG_ERROR) { + free(ctx); + return (NULL); + } #if !defined(_WIN32) /* @@ -4591,9 +4319,8 @@ mg_start(void) * won't kill the whole process. */ (void) signal(SIGPIPE, SIG_IGN); -#endif /* _WIN32 */ +#endif /* !_WIN32 */ - (void) pthread_rwlock_init(&ctx->rwlock, NULL); (void) pthread_mutex_init(&ctx->mutex, NULL); (void) pthread_cond_init(&ctx->thr_cond, NULL); (void) pthread_cond_init(&ctx->empty_cond, NULL); diff --git a/mongoose.h b/mongoose.h index 6c9218f7..a33df485 100644 --- a/mongoose.h +++ b/mongoose.h @@ -59,8 +59,21 @@ struct mg_request_info { /* - * Error codes for all functions that return 'int'. + * User-defined handler function. It must return MG_SUCCESS or MG_ERROR. + * + * If handler returns MG_SUCCESS, that means that handler has processed the + * request by sending appropriate HTTP reply to the client. Mongoose treats + * the request as served. + * + * If callback returns MG_ERROR, that means that callback has not processed + * the request. Handler must not send any data to the client in this case. + * Mongoose proceeds with request handling as if nothing happened. + * + * NOTE: ssl_password_handler must have the following prototype: + * int (*)(char *, int, int, void *) + * Refer to OpenSSL documentation for more details. */ + enum mg_error_t { MG_ERROR, MG_SUCCESS, @@ -68,15 +81,48 @@ enum mg_error_t { MG_BUFFER_TOO_SMALL }; +typedef enum mg_error_t (*mg_callback_t)(struct mg_connection *, + const struct mg_request_info *); + +/* + * This structure describes Mongoose configuration. + */ +struct mg_config { + char *document_root; + char *index_files; + char *ssl_certificate; + char *listening_ports; + char *cgi_extensions; + char *cgi_interpreter; + char *cgi_environment; + char *ssi_extensions; + char *auth_domain; + char *protect; + char *global_passwords_file; + char *put_delete_passwords_file; + char *access_log_file; + char *error_log_file; + char *acl; + char *uid; + char *mime_types; + char *enable_directory_listing; + char *num_threads; + + mg_callback_t new_request_handler; + mg_callback_t http_error_handler; + mg_callback_t event_log_handler; + mg_callback_t ssl_password_handler; +}; + /* * Start the web server. * * This must be the first function called by the application. * It creates a serving thread, and returns a context structure that - * can be used to alter the configuration, and stop the server. + * can be used to stop the server. */ -struct mg_context *mg_start(void); +struct mg_context *mg_start(struct mg_config *); /* @@ -89,32 +135,6 @@ struct mg_context *mg_start(void); void mg_stop(struct mg_context *); -/* - * Get the current value of a particular option. - * - * Return: - * MG_SUCCESS, MG_NOT_FOUND, MG_BUFFER_TOO_SMALL - */ -enum mg_error_t mg_get_option(struct mg_context *, - const char *option_name, char *buf, size_t buf_len); - - -/* - * Set a value for a particular option. - * - * Mongoose makes an internal copy of the option value string, which must be - * valid nul-terminated ASCII or UTF-8 string. It is safe to change any option - * at any time. The order of setting various options is also irrelevant with - * one exception: if "ports" option contains SSL listening ports, a "ssl_cert" - * option must be set BEFORE the "ports" option. - * - * Return: - * MG_ERROR, MG_SUCCESS, or MG_NOT_FOUND if option is unknown. - */ -enum mg_error_t mg_set_option(struct mg_context *, - const char *name, const char *value); - - /* * Add, edit or delete the entry in the passwords file. * @@ -133,36 +153,6 @@ enum mg_error_t mg_modify_passwords_file(struct mg_context *ctx, const char *file_name, const char *user, const char *password); -/* - * Attach a callback function to certain event. - * Callback must return MG_SUCCESS or MG_ERROR. - * - * If callback returns MG_SUCCESS, that means that callback has processed the - * request by sending appropriate HTTP reply to the client. Mongoose treats - * the request as served. - * - * If callback returns MG_ERROR, that means that callback has not processed - * the request. Callback must not send any data to client in this case. - * Mongoose proceeds with request handling. - * - * NOTE: for MG_EVENT_SSL_PASSWORD event the callback must have - * int (*)(char *, int, int, void *) prototype. Refer to OpenSSL documentation - * for more details about the SSL password callback. - */ -enum mg_event_t { - MG_EVENT_NEW_REQUEST, /* New HTTP request has arrived */ - MG_EVENT_HTTP_ERROR, /* Mongoose is about to send HTTP error */ - MG_EVENT_LOG, /* Mongoose is about to log a message */ - MG_EVENT_SSL_PASSWORD, /* SSL certificate needs verification */ - NUM_EVENTS -}; - -typedef enum mg_error_t (*mg_callback_t)(struct mg_connection *, - const struct mg_request_info *); - -void mg_set_callback(struct mg_context *, enum mg_event_t, mg_callback_t); - - /* * Send data to the client. */ @@ -185,6 +175,7 @@ int mg_printf(struct mg_connection *, const char *fmt, ...); */ int mg_read(struct mg_connection *, void *buf, size_t len); + /* * Get the value of particular HTTP header. * @@ -247,11 +238,6 @@ const char *mg_version(void); void mg_md5(char *buf, ...); -/* - * Print command line usage string. - */ -void mg_show_usage_string(FILE *fp); - #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/test/test.pl b/test/test.pl index 263fd87e..36f2bb48 100644 --- a/test/test.pl +++ b/test/test.pl @@ -13,12 +13,11 @@ sub on_windows { $^O =~ /win32/i; } my $port = 23456; my $pid = undef; my $num_requests; -my $root = 'test'; my $dir_separator = on_windows() ? '\\' : '/'; my $copy_cmd = on_windows() ? 'copy' : 'cp'; my $test_dir_uri = "test_dir"; +my $root = 'test'; my $test_dir = $root . $dir_separator. $test_dir_uri; -my $alias = "/aliased=/etc/,/ta=$test_dir"; my $config = 'mongoose.conf'; my $exe = '.' . $dir_separator . 'mongoose'; my $embed_exe = '.' . $dir_separator . 'embed'; @@ -89,6 +88,7 @@ sub o { # Spawn a server listening on specified port sub spawn { my ($cmdline) = @_; + print 'Executing: ', @_, "\n"; if (on_windows()) { my @args = split /\s+/, $cmdline; my $executable = $args[0]; @@ -159,7 +159,7 @@ kill_spawned_child(); my $cmd = "$exe -ports $port -access_log access.log -error_log debug.log ". "-cgi_env CGI_FOO=foo,CGI_BAR=bar,CGI_BAZ=baz " . "-mime_types .bar=foo/bar,.tar.gz=blah,.baz=foo " . -"-root test -aliases $alias -admin_uri /hh"; +"-root $root,/aiased=/etc/,/ta=$test_dir"; $cmd .= ' -cgi_interp perl' if on_windows(); spawn($cmd);