Commit 5425b94f authored by Sergey Lyubka's avatar Sergey Lyubka

introduced struct mg_config. Fixed Range response.

parent 9a5f96e7
......@@ -32,6 +32,7 @@
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <stddef.h>
#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
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 <htpasswd_file> <realm> <user> <passwd>\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_context *ctx, char *argv[])
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)
......
......@@ -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 <stdint.h>
#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",
......@@ -1619,30 +1568,44 @@ mg_get_cookie(const struct mg_connection *conn,
}
/*
* Transform URI to the file name.
* Get document root for the given URI.
*/
static void
convert_uri_to_file_name(struct mg_connection *conn, const char *uri,
char *buf, size_t buf_len)
static int
get_document_root(const struct mg_connection *conn, struct vec *document_root)
{
struct mg_context *ctx = conn->ctx;
struct vec uri_vec, path_vec;
const char *list;
(void) pthread_rwlock_rdlock(&conn->ctx->rwlock);
mg_snprintf(conn, buf, buf_len, "%s%s", ctx->options[OPT_ROOT], uri);
const char *root, *uri;
int len_of_matched_uri;
struct vec uri_vec, path_vec;
/* If requested URI has aliased prefix, use alternate root */
list = ctx->options[OPT_ALIASES];
uri = conn->request_info.uri;
len_of_matched_uri = 0;
root = next_option(conn->ctx->config->document_root,
document_root, NULL);
while ((list = next_option(list, &uri_vec, &path_vec)) != NULL) {
while ((root = next_option(root, &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);
*document_root = path_vec;
len_of_matched_uri = uri_vec.len;
break;
}
}
(void) pthread_rwlock_unlock(&conn->ctx->rwlock);
return (len_of_matched_uri);
}
/*
* Transform URI to the file name.
*/
static void
convert_uri_to_file_name(struct mg_connection *conn, const char *uri,
char *buf, size_t buf_len)
{
struct vec vec;
int match_len;
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;
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
const char *uid = ctx->config->uid;
enum mg_error_t error;
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,247 +3867,40 @@ 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);
}
static enum mg_error_t
set_acl_option(struct mg_context *ctx, const char *acl)
{
struct usa fake;
return (check_acl(ctx, acl, &fake));
}
const char *path = ctx->config->global_passwords_file;
/*
* 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)
{
const char *list;
struct vec key, value;
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);
}
return (MG_SUCCESS);
return (path == NULL || mg_stat(path, &mgstat) == 0 ?
MG_SUCCESS : MG_ERROR);
}
static enum mg_error_t
set_root_option(struct mg_context *ctx, const char *root)
set_acl_option(struct mg_context *ctx)
{
struct mgstat buf;
if ( mg_stat(root, &buf) != 0) {
cry(fc(ctx), "Invalid root directory: \"%s\"", root);
return (MG_ERROR);
}
return (MG_SUCCESS);
}
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 <htpasswd_file> <realm> <user> <passwd>\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);
}
struct usa fake;
return (check_acl(ctx, &fake));
}
enum mg_error_t
mg_get_option(struct mg_context *ctx, const char *option_name,
char *dst, size_t dst_len)
static bool_t
verify_document_root(struct mg_context *ctx, const char *root)
{
const struct mg_option *option;
enum mg_error_t error;
char path[FILENAME_MAX], *p;
struct mgstat buf;
(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;
if ((p = strchr(root, ',')) == NULL) {
mg_strlcpy(path, root, sizeof(path));
} else {
mg_strlcpy(dst, ctx->options[option->index], dst_len);
error = MG_SUCCESS;
mg_strlcpy(path, root, p - root + 1);
}
(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"
"<html><body><h1>Mongoose v. %s</h1>", 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,
"<p style=\"background: red\">Error setting "
"option \"%s\"</p>",
option_name ? option_name : "(null)");
} else {
(void) mg_printf(conn,
"<p style=\"color: green\">Saved: %s=%s</p>",
option_name, option_value ? option_value : "NULL");
}
}
/* Print table with all options */
(void) mg_printf(conn, "%s", "<table border=\"1\""
"<tr><th>Option</th><th>Description</th>"
"<th colspan=2>Value</th></tr>");
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,
"<form method=post><tr><td>%s</td><td>%s</td>"
"<input type=hidden name=o value='%s'>"
"<td><input type=text name=v value='%s'></td>"
"<td><input type=submit value=save></td></form></tr>",
option->name, option->description,
option->name, option_value);
if (mg_stat(path, &buf) != 0) {
cry(fc(ctx), "Invalid root directory: \"%s\"", root);
return (FALSE);
}
(void) mg_printf(conn, "%s", "</table></body></html>");
return (TRUE);
}
#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);
......
......@@ -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 */
......
......@@ -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);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment