From 6083b9c5c534b47a36b260b45ce9d2e4cc4ec5e2 Mon Sep 17 00:00:00 2001 From: Sergey Lyubka <valenok@gmail.com> Date: Tue, 9 Sep 2014 18:07:55 +0100 Subject: [PATCH] Updated to the recent skeleton. SSL address format changed --- examples/server.c | 1016 +++++++++-------- examples/unit_test.c | 9 +- examples/websocket_chat/websocket_chat.c | 2 +- .../websocket_echo_server.c | 2 +- examples/ws_ssl/Makefile | 10 +- examples/ws_ssl/net_skeleton.c | 2 +- examples/ws_ssl/net_skeleton.h | 2 +- examples/ws_ssl/ssl_wrapper.c | 2 +- examples/ws_ssl/ssl_wrapper.h | 2 +- mongoose.c | 671 +++++------ mongoose.h | 11 +- 11 files changed, 873 insertions(+), 856 deletions(-) diff --git a/examples/server.c b/examples/server.c index 7c524cecc..c46f12d1e 100644 --- a/examples/server.c +++ b/examples/server.c @@ -1,507 +1,509 @@ -// Copyright (c) 2004-2013 Sergey Lyubka -// Copyright (c) 2013-2014 Cesanta Software Limited -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#undef UNICODE // Use ANSI WinAPI functions -#undef _UNICODE // Use multibyte encoding on Windows -#define _MBCS // Use multibyte encoding on Windows -#define _WIN32_WINNT 0x500 // Enable MIIM_BITMAP -#define _CRT_SECURE_NO_WARNINGS // Disable deprecation warning in VS2005 -#define _XOPEN_SOURCE 600 // For PATH_MAX on linux -#undef WIN32_LEAN_AND_MEAN // Let windows.h always include winsock2.h - -#include <sys/stat.h> -#include <stdio.h> -#include <stdlib.h> -#include <signal.h> -#include <string.h> -#include <errno.h> -#include <limits.h> -#include <stddef.h> -#include <stdarg.h> -#include <ctype.h> -#include <time.h> - -#include "mongoose.h" - -#ifdef _WIN32 -#include <windows.h> -#include <direct.h> // For chdir() -#include <winsvc.h> -#include <shlobj.h> - -#ifndef PATH_MAX -#define PATH_MAX MAX_PATH -#endif - -#ifndef S_ISDIR -#define S_ISDIR(x) ((x) & _S_IFDIR) -#endif - -#define DIRSEP '\\' -#define snprintf _snprintf -#define vsnprintf _vsnprintf -#define sleep(x) Sleep((x) * 1000) -#define abs_path(rel, abs, abs_size) _fullpath((abs), (rel), (abs_size)) -#define SIGCHLD 0 -typedef struct _stat file_stat_t; -#define stat(x, y) _stat((x), (y)) -#else -typedef struct stat file_stat_t; -#include <sys/wait.h> -#include <unistd.h> - -#ifdef IOS -#include <ifaddrs.h> -#endif - -#define DIRSEP '/' -#define __cdecl -#define abs_path(rel, abs, abs_size) realpath((rel), (abs)) -#endif // _WIN32 - -#define MAX_OPTIONS 100 -#define MAX_CONF_FILE_LINE_SIZE (8 * 1024) - -#ifndef MVER -#define MVER MONGOOSE_VERSION -#endif - -static int exit_flag; -static char server_name[50]; // Set by init_server_name() -static char s_config_file[PATH_MAX]; // Set by process_command_line_arguments -static struct mg_server *server; // Set by start_mongoose() -static const char *s_default_document_root = "."; -static const char *s_default_listening_port = "8080"; -static char **s_argv = { NULL }; - -static void set_options(char *argv[]); - -#if !defined(CONFIG_FILE) -#define CONFIG_FILE "mongoose.conf" -#endif /* !CONFIG_FILE */ - -static void __cdecl signal_handler(int sig_num) { - // Reinstantiate signal handler - signal(sig_num, signal_handler); - -#ifndef _WIN32 - // Do not do the trick with ignoring SIGCHLD, cause not all OSes (e.g. QNX) - // reap zombies if SIGCHLD is ignored. On QNX, for example, waitpid() - // fails if SIGCHLD is ignored, making system() non-functional. - if (sig_num == SIGCHLD) { - do {} while (waitpid(-1, &sig_num, WNOHANG) > 0); - } else -#endif - { exit_flag = sig_num; } -} - -static void vnotify(const char *fmt, va_list ap, int must_exit) { - char msg[200]; - - vsnprintf(msg, sizeof(msg), fmt, ap); - fprintf(stderr, "%s\n", msg); - - if (must_exit) { - exit(EXIT_FAILURE); - } -} - -static void notify(const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - vnotify(fmt, ap, 0); - va_end(ap); -} - -static void die(const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - vnotify(fmt, ap, 1); - va_end(ap); -} - -static void show_usage_and_exit(void) { - const char **names; - int i; - - fprintf(stderr, "Mongoose version %s (c) Sergey Lyubka, built on %s\n", - MVER, __DATE__); - fprintf(stderr, "Usage:\n"); -#if !defined(MONGOOSE_NO_AUTH) && !defined(MONGOOSE_NO_FILESYSTEM) - fprintf(stderr, " mongoose -A <htpasswd_file> <realm> <user> <passwd>\n"); -#endif - fprintf(stderr, " mongoose [config_file]\n"); - fprintf(stderr, " mongoose [-option value ...]\n"); - fprintf(stderr, "\nOPTIONS:\n"); - - names = mg_get_valid_option_names(); - for (i = 0; names[i] != NULL; i += 2) { - fprintf(stderr, " -%s %s\n", - names[i], names[i + 1] == NULL ? "<empty>" : names[i + 1]); - } - exit(EXIT_FAILURE); -} - -#define EV_HANDLER NULL - -static char *sdup(const char *str) { - char *p; - if ((p = (char *) malloc(strlen(str) + 1)) != NULL) { - strcpy(p, str); - } - return p; -} - -static void set_option(char **options, const char *name, const char *value) { - int i; - - for (i = 0; i < MAX_OPTIONS - 3; i++) { - if (options[i] == NULL) { - options[i] = sdup(name); - options[i + 1] = sdup(value); - options[i + 2] = NULL; - break; - } else if (!strcmp(options[i], name)) { - free(options[i + 1]); - options[i + 1] = sdup(value); - break; - } - } - - if (i == MAX_OPTIONS - 3) { - die("%s", "Too many options specified"); - } -} - -static void process_command_line_arguments(char *argv[], char **options) { - char line[MAX_CONF_FILE_LINE_SIZE], opt[sizeof(line)], val[sizeof(line)], - *p, cpath[PATH_MAX]; - FILE *fp = NULL; - size_t i, cmd_line_opts_start = 1, line_no = 0; - - // Should we use a config file ? - if (argv[1] != NULL && argv[1][0] != '-') { - snprintf(cpath, sizeof(cpath), "%s", argv[1]); - cmd_line_opts_start = 2; - } else if ((p = strrchr(argv[0], DIRSEP)) == NULL) { - // No command line flags specified. Look where binary lives - snprintf(cpath, sizeof(cpath), "%s", CONFIG_FILE); - } else { - snprintf(cpath, sizeof(cpath), "%.*s%c%s", - (int) (p - argv[0]), argv[0], DIRSEP, CONFIG_FILE); - } - abs_path(cpath, s_config_file, sizeof(s_config_file)); - - fp = fopen(s_config_file, "r"); - - // If config file was set in command line and open failed, die - if (cmd_line_opts_start == 2 && fp == NULL) { - die("Cannot open config file %s: %s", s_config_file, strerror(errno)); - } - - // Load config file settings first - if (fp != NULL) { - fprintf(stderr, "Loading config file %s\n", s_config_file); - - // Loop over the lines in config file - while (fgets(line, sizeof(line), fp) != NULL) { - line_no++; - - // Ignore empty lines and comments - for (i = 0; isspace(* (unsigned char *) &line[i]); ) i++; - if (line[i] == '#' || line[i] == '\0') { - continue; - } - - if (sscanf(line, "%s %[^\r\n#]", opt, val) != 2) { - printf("%s: line %d is invalid, ignoring it:\n %s", - s_config_file, (int) line_no, line); - } else { - set_option(options, opt, val); - } - } - - fclose(fp); - } - - // If we're under MacOS and started by launchd, then the second - // argument is process serial number, -psn_..... - // In this case, don't process arguments at all. - if (argv[1] == NULL || memcmp(argv[1], "-psn_", 5) != 0) { - // Handle command line flags. - // They override config file and default settings. - for (i = cmd_line_opts_start; argv[i] != NULL; i += 2) { - if (argv[i][0] != '-' || argv[i + 1] == NULL) { - show_usage_and_exit(); - } - set_option(options, &argv[i][1], argv[i + 1]); - } - } -} - -static void init_server_name(void) { - const char *descr = ""; - snprintf(server_name, sizeof(server_name), "Mongoose web server v.%s%s", - MVER, descr); -} - -static int is_path_absolute(const char *path) { -#ifdef _WIN32 - return path != NULL && - ((path[0] == '\\' && path[1] == '\\') || // UNC path, e.g. \\server\dir - (isalpha(path[0]) && path[1] == ':' && path[2] == '\\')); // E.g. X:\dir -#else - return path != NULL && path[0] == '/'; -#endif -} - -static char *get_option(char **options, const char *option_name) { - int i; - - for (i = 0; options[i] != NULL; i++) - if (!strcmp(options[i], option_name)) - return options[i + 1]; - - return NULL; -} - -static void *serving_thread_func(void *param) { - struct mg_server *srv = (struct mg_server *) param; - while (exit_flag == 0) { - mg_poll_server(srv, 1000); - } - return NULL; -} - -static int path_exists(const char *path, int is_dir) { - file_stat_t st; - return path == NULL || (stat(path, &st) == 0 && - ((S_ISDIR(st.st_mode) ? 1 : 0) == is_dir)); -} - -static void verify_existence(char **options, const char *name, int is_dir) { - const char *path = get_option(options, name); - if (!path_exists(path, is_dir)) { - notify("Invalid path for %s: [%s]: (%s). Make sure that path is either " - "absolute, or it is relative to mongoose executable.", - name, path, strerror(errno)); - } -} - -static void set_absolute_path(char *options[], const char *option_name) { - char path[PATH_MAX], abs[PATH_MAX], *option_value; - const char *p; - - // Check whether option is already set - option_value = get_option(options, option_name); - - // If option is already set and it is an absolute path, - // leave it as it is -- it's already absolute. - if (option_value != NULL && !is_path_absolute(option_value)) { - // Not absolute. Use the directory where mongoose executable lives - // be the relative directory for everything. - // Extract mongoose executable directory into path. - if ((p = strrchr(s_config_file, DIRSEP)) == NULL) { - getcwd(path, sizeof(path)); - } else { - snprintf(path, sizeof(path), "%.*s", (int) (p - s_config_file), - s_config_file); - } - - strncat(path, "/", sizeof(path) - 1); - strncat(path, option_value, sizeof(path) - 1); - - // Absolutize the path, and set the option - abs_path(path, abs, sizeof(abs)); - set_option(options, option_name, abs); - } -} - -#if !defined(MONGOOSE_NO_AUTH) && !defined(MONGOOSE_NO_FILESYSTEM) -int modify_passwords_file(const char *fname, const char *domain, - const char *user, const char *pass) { - int found; - char line[512], u[512], d[512], ha1[33], tmp[PATH_MAX]; - FILE *fp, *fp2; - - found = 0; - fp = fp2 = NULL; - - // Regard empty password as no password - remove user record. - if (pass != NULL && pass[0] == '\0') { - pass = NULL; - } - - (void) snprintf(tmp, sizeof(tmp), "%s.tmp", fname); - - // Create the file if does not exist - if ((fp = fopen(fname, "a+")) != NULL) { - fclose(fp); - } - - // Open the given file and temporary file - if ((fp = fopen(fname, "r")) == NULL) { - return 0; - } else if ((fp2 = fopen(tmp, "w+")) == NULL) { - fclose(fp); - return 0; - } - - // Copy the stuff to temporary file - while (fgets(line, sizeof(line), fp) != NULL) { - if (sscanf(line, "%[^:]:%[^:]:%*s", u, d) != 2) { - continue; - } - - if (!strcmp(u, user) && !strcmp(d, domain)) { - found++; - if (pass != NULL) { - mg_md5(ha1, user, ":", domain, ":", pass, NULL); - fprintf(fp2, "%s:%s:%s\n", user, domain, ha1); - } - } else { - fprintf(fp2, "%s", line); - } - } - - // If new user, just add it - if (!found && pass != NULL) { - mg_md5(ha1, user, ":", domain, ":", pass, NULL); - fprintf(fp2, "%s:%s:%s\n", user, domain, ha1); - } - - // Close files - fclose(fp); - fclose(fp2); - - // Put the temp file in place of real file - remove(fname); - rename(tmp, fname); - - return 1; -} -#endif - -static void start_mongoose(int argc, char *argv[]) { - s_argv = argv; - if ((server = mg_create_server(NULL, EV_HANDLER)) == NULL) { - die("%s", "Failed to start Mongoose."); - } - -#if !defined(MONGOOSE_NO_AUTH) && !defined(MONGOOSE_NO_FILESYSTEM) - // Edit passwords file if -A option is specified - if (argc > 1 && !strcmp(argv[1], "-A")) { - if (argc != 6) { - show_usage_and_exit(); - } - exit(modify_passwords_file(argv[2], argv[3], argv[4], argv[5]) ? - EXIT_SUCCESS : EXIT_FAILURE); - } -#endif - - // Show usage if -h or --help options are specified - if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) { - show_usage_and_exit(); - } - set_options(argv); -} - -static void set_options(char *argv[]) { - char *options[MAX_OPTIONS]; - int i; - - options[0] = NULL; - set_option(options, "document_root", s_default_document_root); - set_option(options, "listening_port", s_default_listening_port); - - // Update config based on command line arguments - process_command_line_arguments(argv, options); - - // Make sure we have absolute paths for files and directories - // https://github.com/valenok/mongoose/issues/181 - set_absolute_path(options, "document_root"); - set_absolute_path(options, "dav_auth_file"); - set_absolute_path(options, "cgi_interpreter"); - set_absolute_path(options, "access_log_file"); - set_absolute_path(options, "global_auth_file"); - set_absolute_path(options, "ssl_certificate"); - - if (!path_exists(get_option(options, "document_root"), 1)) { - set_option(options, "document_root", s_default_document_root); - set_absolute_path(options, "document_root"); - notify("Setting document_root to [%s]", - mg_get_option(server, "document_root")); - } - - // Make extra verification for certain options - verify_existence(options, "document_root", 1); - verify_existence(options, "cgi_interpreter", 0); - verify_existence(options, "ssl_certificate", 0); - - for (i = 0; options[i] != NULL; i += 2) { - const char *msg = mg_set_option(server, options[i], options[i + 1]); - if (msg != NULL) { - notify("Failed to set option [%s] to [%s]: %s", - options[i], options[i + 1], msg); - if (!strcmp(options[i], "listening_port")) { - mg_set_option(server, "listening_port", s_default_listening_port); - notify("Setting %s to [%s]", options[i], s_default_listening_port); - } - } - free(options[i]); - free(options[i + 1]); - } - - // Change current working directory to document root. This way, - // scripts can use relative paths. - chdir(mg_get_option(server, "document_root")); - - // Add an ability to pass listening socket to mongoose - { - const char *env = getenv("MONGOOSE_LISTENING_SOCKET"); - if (env != NULL && atoi(env) > 0 ) { - mg_set_listening_socket(server, atoi(env)); - } - } - - // Setup signal handler: quit on Ctrl-C - signal(SIGTERM, signal_handler); - signal(SIGINT, signal_handler); -#ifndef _WIN32 - signal(SIGCHLD, signal_handler); -#endif -} - -int main(int argc, char *argv[]) { - init_server_name(); - start_mongoose(argc, argv); - printf("%s serving [%s] on port %s\n", - server_name, mg_get_option(server, "document_root"), - mg_get_option(server, "listening_port")); - fflush(stdout); // Needed, Windows terminals might not be line-buffered - serving_thread_func(server); - printf("Exiting on signal %d ...", exit_flag); - fflush(stdout); - mg_destroy_server(&server); - printf("%s\n", " done."); - - return EXIT_SUCCESS; -} +// Copyright (c) 2004-2013 Sergey Lyubka +// Copyright (c) 2013-2014 Cesanta Software Limited +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#undef UNICODE // Use ANSI WinAPI functions +#undef _UNICODE // Use multibyte encoding on Windows +#define _MBCS // Use multibyte encoding on Windows +#define _WIN32_WINNT 0x500 // Enable MIIM_BITMAP +#define _CRT_SECURE_NO_WARNINGS // Disable deprecation warning in VS2005 +#define _XOPEN_SOURCE 600 // For PATH_MAX on linux +#undef WIN32_LEAN_AND_MEAN // Let windows.h always include winsock2.h + +#include <sys/stat.h> +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <string.h> +#include <errno.h> +#include <limits.h> +#include <stddef.h> +#include <stdarg.h> +#include <ctype.h> +#include <time.h> + +#include "mongoose.h" + +#ifdef _WIN32 +#include <windows.h> +#include <direct.h> // For chdir() +#include <winsvc.h> +#include <shlobj.h> + +#ifndef PATH_MAX +#define PATH_MAX MAX_PATH +#endif + +#ifndef S_ISDIR +#define S_ISDIR(x) ((x) & _S_IFDIR) +#endif + +#define DIRSEP '\\' +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#define sleep(x) Sleep((x) * 1000) +#define abs_path(rel, abs, abs_size) _fullpath((abs), (rel), (abs_size)) +#define SIGCHLD 0 +typedef struct _stat file_stat_t; +#define stat(x, y) _stat((x), (y)) +#else +typedef struct stat file_stat_t; +#include <sys/wait.h> +#include <unistd.h> + +#ifdef IOS +#include <ifaddrs.h> +#endif + +#define DIRSEP '/' +#define __cdecl +#define abs_path(rel, abs, abs_size) realpath((rel), (abs)) +#endif // _WIN32 + +#define MAX_OPTIONS 100 +#define MAX_CONF_FILE_LINE_SIZE (8 * 1024) + +#ifndef MVER +#define MVER MONGOOSE_VERSION +#endif + +static int exit_flag; +static char server_name[50]; // Set by init_server_name() +static char s_config_file[PATH_MAX]; // Set by process_command_line_arguments +static struct mg_server *server; // Set by start_mongoose() +static const char *s_default_document_root = "."; +static const char *s_default_listening_port = "8080"; +static char **s_argv = { NULL }; + +static void set_options(char *argv[]); + +#if !defined(CONFIG_FILE) +#define CONFIG_FILE "mongoose.conf" +#endif /* !CONFIG_FILE */ + +static void __cdecl signal_handler(int sig_num) { + // Reinstantiate signal handler + signal(sig_num, signal_handler); + +#ifndef _WIN32 + // Do not do the trick with ignoring SIGCHLD, cause not all OSes (e.g. QNX) + // reap zombies if SIGCHLD is ignored. On QNX, for example, waitpid() + // fails if SIGCHLD is ignored, making system() non-functional. + if (sig_num == SIGCHLD) { + do {} while (waitpid(-1, &sig_num, WNOHANG) > 0); + } else +#endif + { exit_flag = sig_num; } +} + +static void vnotify(const char *fmt, va_list ap, int must_exit) { + char msg[200]; + + vsnprintf(msg, sizeof(msg), fmt, ap); + fprintf(stderr, "%s\n", msg); + + if (must_exit) { + exit(EXIT_FAILURE); + } +} + +static void notify(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + vnotify(fmt, ap, 0); + va_end(ap); +} + +static void die(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + vnotify(fmt, ap, 1); + va_end(ap); +} + +static void show_usage_and_exit(void) { + const char **names; + int i; + + fprintf(stderr, "Mongoose version %s (c) Sergey Lyubka, built on %s\n", + MVER, __DATE__); + fprintf(stderr, "Usage:\n"); +#if !defined(MONGOOSE_NO_AUTH) && !defined(MONGOOSE_NO_FILESYSTEM) + fprintf(stderr, " mongoose -A <htpasswd_file> <realm> <user> <passwd>\n"); +#endif + fprintf(stderr, " mongoose [config_file]\n"); + fprintf(stderr, " mongoose [-option value ...]\n"); + fprintf(stderr, "\nOPTIONS:\n"); + + names = mg_get_valid_option_names(); + for (i = 0; names[i] != NULL; i += 2) { + fprintf(stderr, " -%s %s\n", + names[i], names[i + 1] == NULL ? "<empty>" : names[i + 1]); + } + exit(EXIT_FAILURE); +} + +#define EV_HANDLER NULL + +static char *sdup(const char *str) { + char *p; + if ((p = (char *) malloc(strlen(str) + 1)) != NULL) { + strcpy(p, str); + } + return p; +} + +static void set_option(char **options, const char *name, const char *value) { + int i; + + for (i = 0; i < MAX_OPTIONS - 3; i++) { + if (options[i] == NULL) { + options[i] = sdup(name); + options[i + 1] = sdup(value); + options[i + 2] = NULL; + break; + } else if (!strcmp(options[i], name)) { + free(options[i + 1]); + options[i + 1] = sdup(value); + break; + } + } + + if (i == MAX_OPTIONS - 3) { + die("%s", "Too many options specified"); + } +} + +static void process_command_line_arguments(char *argv[], char **options) { + char line[MAX_CONF_FILE_LINE_SIZE], opt[sizeof(line)], val[sizeof(line)], + *p, cpath[PATH_MAX]; + FILE *fp = NULL; + size_t i, cmd_line_opts_start = 1, line_no = 0; + + // Should we use a config file ? + if (argv[1] != NULL && argv[1][0] != '-') { + snprintf(cpath, sizeof(cpath), "%s", argv[1]); + cmd_line_opts_start = 2; + } else if ((p = strrchr(argv[0], DIRSEP)) == NULL) { + // No command line flags specified. Look where binary lives + snprintf(cpath, sizeof(cpath), "%s", CONFIG_FILE); + } else { + snprintf(cpath, sizeof(cpath), "%.*s%c%s", + (int) (p - argv[0]), argv[0], DIRSEP, CONFIG_FILE); + } + abs_path(cpath, s_config_file, sizeof(s_config_file)); + + fp = fopen(s_config_file, "r"); + + // If config file was set in command line and open failed, die + if (cmd_line_opts_start == 2 && fp == NULL) { + die("Cannot open config file %s: %s", s_config_file, strerror(errno)); + } + + // Load config file settings first + if (fp != NULL) { + fprintf(stderr, "Loading config file %s\n", s_config_file); + + // Loop over the lines in config file + while (fgets(line, sizeof(line), fp) != NULL) { + line_no++; + + // Ignore empty lines and comments + for (i = 0; isspace(* (unsigned char *) &line[i]); ) i++; + if (line[i] == '#' || line[i] == '\0') { + continue; + } + + if (sscanf(line, "%s %[^\r\n#]", opt, val) != 2) { + printf("%s: line %d is invalid, ignoring it:\n %s", + s_config_file, (int) line_no, line); + } else { + set_option(options, opt, val); + } + } + + fclose(fp); + } + + // If we're under MacOS and started by launchd, then the second + // argument is process serial number, -psn_..... + // In this case, don't process arguments at all. + if (argv[1] == NULL || memcmp(argv[1], "-psn_", 5) != 0) { + // Handle command line flags. + // They override config file and default settings. + for (i = cmd_line_opts_start; argv[i] != NULL; i += 2) { + if (argv[i][0] != '-' || argv[i + 1] == NULL) { + show_usage_and_exit(); + } + set_option(options, &argv[i][1], argv[i + 1]); + } + } +} + +static void init_server_name(void) { + const char *descr = ""; + snprintf(server_name, sizeof(server_name), "Mongoose web server v.%s%s", + MVER, descr); +} + +static int is_path_absolute(const char *path) { +#ifdef _WIN32 + return path != NULL && + ((path[0] == '\\' && path[1] == '\\') || // UNC path, e.g. \\server\dir + (isalpha(path[0]) && path[1] == ':' && path[2] == '\\')); // E.g. X:\dir +#else + return path != NULL && path[0] == '/'; +#endif +} + +static char *get_option(char **options, const char *option_name) { + int i; + + for (i = 0; options[i] != NULL; i++) + if (!strcmp(options[i], option_name)) + return options[i + 1]; + + return NULL; +} + +static void *serving_thread_func(void *param) { + struct mg_server *srv = (struct mg_server *) param; + while (exit_flag == 0) { + mg_poll_server(srv, 1000); + } + return NULL; +} + +static int path_exists(const char *path, int is_dir) { + file_stat_t st; + return path == NULL || (stat(path, &st) == 0 && + ((S_ISDIR(st.st_mode) ? 1 : 0) == is_dir)); +} + +static void verify_existence(char **options, const char *name, int is_dir) { + const char *path = get_option(options, name); + if (!path_exists(path, is_dir)) { + notify("Invalid path for %s: [%s]: (%s). Make sure that path is either " + "absolute, or it is relative to mongoose executable.", + name, path, strerror(errno)); + } +} + +static void set_absolute_path(char *options[], const char *option_name) { + char path[PATH_MAX], abs[PATH_MAX], *option_value; + const char *p; + + // Check whether option is already set + option_value = get_option(options, option_name); + + // If option is already set and it is an absolute path, + // leave it as it is -- it's already absolute. + if (option_value != NULL && !is_path_absolute(option_value)) { + // Not absolute. Use the directory where mongoose executable lives + // be the relative directory for everything. + // Extract mongoose executable directory into path. + if ((p = strrchr(s_config_file, DIRSEP)) == NULL) { + getcwd(path, sizeof(path)); + } else { + snprintf(path, sizeof(path), "%.*s", (int) (p - s_config_file), + s_config_file); + } + + strncat(path, "/", sizeof(path) - 1); + strncat(path, option_value, sizeof(path) - 1); + + // Absolutize the path, and set the option + abs_path(path, abs, sizeof(abs)); + set_option(options, option_name, abs); + } +} + +#if !defined(MONGOOSE_NO_AUTH) && !defined(MONGOOSE_NO_FILESYSTEM) +int modify_passwords_file(const char *fname, const char *domain, + const char *user, const char *pass) { + int found; + char line[512], u[512], d[512], ha1[33], tmp[PATH_MAX]; + FILE *fp, *fp2; + + found = 0; + fp = fp2 = NULL; + + // Regard empty password as no password - remove user record. + if (pass != NULL && pass[0] == '\0') { + pass = NULL; + } + + (void) snprintf(tmp, sizeof(tmp), "%s.tmp", fname); + + // Create the file if does not exist + if ((fp = fopen(fname, "a+")) != NULL) { + fclose(fp); + } + + // Open the given file and temporary file + if ((fp = fopen(fname, "r")) == NULL) { + return 0; + } else if ((fp2 = fopen(tmp, "w+")) == NULL) { + fclose(fp); + return 0; + } + + // Copy the stuff to temporary file + while (fgets(line, sizeof(line), fp) != NULL) { + if (sscanf(line, "%[^:]:%[^:]:%*s", u, d) != 2) { + continue; + } + + if (!strcmp(u, user) && !strcmp(d, domain)) { + found++; + if (pass != NULL) { + mg_md5(ha1, user, ":", domain, ":", pass, NULL); + fprintf(fp2, "%s:%s:%s\n", user, domain, ha1); + } + } else { + fprintf(fp2, "%s", line); + } + } + + // If new user, just add it + if (!found && pass != NULL) { + mg_md5(ha1, user, ":", domain, ":", pass, NULL); + fprintf(fp2, "%s:%s:%s\n", user, domain, ha1); + } + + // Close files + fclose(fp); + fclose(fp2); + + // Put the temp file in place of real file + remove(fname); + rename(tmp, fname); + + return 1; +} +#endif + +static void start_mongoose(int argc, char *argv[]) { + s_argv = argv; + if ((server = mg_create_server(NULL, EV_HANDLER)) == NULL) { + die("%s", "Failed to start Mongoose."); + } + +#if !defined(MONGOOSE_NO_AUTH) && !defined(MONGOOSE_NO_FILESYSTEM) + // Edit passwords file if -A option is specified + if (argc > 1 && !strcmp(argv[1], "-A")) { + if (argc != 6) { + show_usage_and_exit(); + } + exit(modify_passwords_file(argv[2], argv[3], argv[4], argv[5]) ? + EXIT_SUCCESS : EXIT_FAILURE); + } +#endif + + // Show usage if -h or --help options are specified + if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) { + show_usage_and_exit(); + } + set_options(argv); +} + +static void set_options(char *argv[]) { + char *options[MAX_OPTIONS]; + int i; + + options[0] = NULL; + set_option(options, "document_root", s_default_document_root); + set_option(options, "listening_port", s_default_listening_port); + + // Update config based on command line arguments + process_command_line_arguments(argv, options); + + // Make sure we have absolute paths for files and directories + // https://github.com/valenok/mongoose/issues/181 + set_absolute_path(options, "document_root"); + set_absolute_path(options, "dav_auth_file"); + set_absolute_path(options, "cgi_interpreter"); + set_absolute_path(options, "access_log_file"); + set_absolute_path(options, "global_auth_file"); + set_absolute_path(options, "ssl_certificate"); + + if (!path_exists(get_option(options, "document_root"), 1)) { + set_option(options, "document_root", s_default_document_root); + set_absolute_path(options, "document_root"); + notify("Setting document_root to [%s]", + mg_get_option(server, "document_root")); + } + + // Make extra verification for certain options + verify_existence(options, "document_root", 1); + verify_existence(options, "cgi_interpreter", 0); + verify_existence(options, "ssl_certificate", 0); + + for (i = 0; options[i] != NULL; i += 2) { + const char *msg = mg_set_option(server, options[i], options[i + 1]); + if (msg != NULL) { + notify("Failed to set option [%s] to [%s]: %s", + options[i], options[i + 1], msg); + if (!strcmp(options[i], "listening_port")) { + mg_set_option(server, "listening_port", s_default_listening_port); + notify("Setting %s to [%s]", options[i], s_default_listening_port); + } + } + free(options[i]); + free(options[i + 1]); + } + + // Change current working directory to document root. This way, + // scripts can use relative paths. + chdir(mg_get_option(server, "document_root")); + +#if 0 + // Add an ability to pass listening socket to mongoose + { + const char *env = getenv("MONGOOSE_LISTENING_SOCKET"); + if (env != NULL && atoi(env) > 0 ) { + mg_set_listening_socket(server, atoi(env)); + } + } +#endif + + // Setup signal handler: quit on Ctrl-C + signal(SIGTERM, signal_handler); + signal(SIGINT, signal_handler); +#ifndef _WIN32 + signal(SIGCHLD, signal_handler); +#endif +} + +int main(int argc, char *argv[]) { + init_server_name(); + start_mongoose(argc, argv); + printf("%s serving [%s] on port %s\n", + server_name, mg_get_option(server, "document_root"), + mg_get_option(server, "listening_port")); + fflush(stdout); // Needed, Windows terminals might not be line-buffered + serving_thread_func(server); + printf("Exiting on signal %d ...", exit_flag); + fflush(stdout); + mg_destroy_server(&server); + printf("%s\n", " done."); + + return EXIT_SUCCESS; +} diff --git a/examples/unit_test.c b/examples/unit_test.c index 64e350cc6..18dfcaa6e 100644 --- a/examples/unit_test.c +++ b/examples/unit_test.c @@ -396,9 +396,9 @@ static const char *test_server(void) { ASSERT(mg_set_option(server, "listening_port", LISTENING_ADDR) == NULL); ASSERT(mg_set_option(server, "document_root", ".") == NULL); - ASSERT((conn = mg_connect(server, "127.0.0.1", atoi(HTTP_PORT), 0)) != NULL); + ASSERT((conn = mg_connect(server, "127.0.0.1:" HTTP_PORT)) != NULL); conn->connection_param = buf1; - ASSERT((conn = mg_connect(server, "127.0.0.1", atoi(HTTP_PORT), 0)) != NULL); + ASSERT((conn = mg_connect(server, "127.0.0.1:" HTTP_PORT)) != NULL); conn->connection_param = buf2; { int i; for (i = 0; i < 50; i++) mg_poll_server(server, 1); } @@ -483,7 +483,7 @@ static const char *test_mg_set_option(void) { } static const char *test_rewrites(void) { - char buf1[100] = "xx"; + char buf1[100] = "xx", addr[50]; struct mg_server *server = mg_create_server(NULL, evh2); struct mg_connection *conn; const char *port; @@ -492,7 +492,8 @@ static const char *test_rewrites(void) { ASSERT(mg_set_option(server, "document_root", ".") == NULL); ASSERT(mg_set_option(server, "url_rewrites", "/xx=unit_test.c") == NULL); ASSERT((port = mg_get_option(server, "listening_port")) != NULL); - ASSERT((conn = mg_connect(server, "127.0.0.1", atoi(port), 0)) != NULL); + snprintf(addr, sizeof(addr), "127.0.0.1:%s", port); + ASSERT((conn = mg_connect(server, addr)) != NULL); conn->connection_param = buf1; { int i; for (i = 0; i < 50; i++) mg_poll_server(server, 1); } diff --git a/examples/websocket_chat/websocket_chat.c b/examples/websocket_chat/websocket_chat.c index bbc92bafb..a1de7234e 100644 --- a/examples/websocket_chat/websocket_chat.c +++ b/examples/websocket_chat/websocket_chat.c @@ -1,5 +1,5 @@ // Copyright (c) 2013-2014 Cesanta Software Limited -// $Date: 2014-09-08 22:30:52 UTC $ +// $Date: 2014-09-09 17:07:55 UTC $ #include <string.h> #include <time.h> diff --git a/examples/websocket_echo_server/websocket_echo_server.c b/examples/websocket_echo_server/websocket_echo_server.c index 7e430fa86..ceb40917a 100644 --- a/examples/websocket_echo_server/websocket_echo_server.c +++ b/examples/websocket_echo_server/websocket_echo_server.c @@ -1,5 +1,5 @@ // Copyright (c) 2013-2014 Cesanta Software Limited -// $Date: 2014-09-09 08:27:35 UTC $ +// $Date: 2014-09-09 17:07:55 UTC $ #include <string.h> #include <time.h> diff --git a/examples/ws_ssl/Makefile b/examples/ws_ssl/Makefile index 52f92802a..b4fc4faf9 100644 --- a/examples/ws_ssl/Makefile +++ b/examples/ws_ssl/Makefile @@ -1,19 +1,17 @@ # Copyright (c) 2014 Cesanta Software # All rights reserved -CFLAGS = -W -Wall -I../.. -g -O0 $(CFLAGS_EXTRA) -NS = ../../../net_skeleton -SW = ../../../ssl_wrapper +CFLAGS = -W -Wall -I../.. -I. -g -O0 $(CFLAGS_EXTRA) -SOURCES = ws_ssl.c ../../mongoose.c $(NS)/net_skeleton.c $(SW)/ssl_wrapper.c +SOURCES = ws_ssl.c ../../mongoose.c net_skeleton.c ssl_wrapper.c PROG = ws_ssl all: $(PROG) $(PROG): $(SOURCES) $(CC) -o $(PROG) $(SOURCES) \ - -I$(NS) -DNS_ENABLE_SSL -DNOEMBED_NET_SKELETON \ - -I$(SW) -DSSL_WRAPPER_USE_AS_LIBRARY -lssl $(CFLAGS) + -DNS_ENABLE_SSL -DNOEMBED_NET_SKELETON \ + -DSSL_WRAPPER_USE_AS_LIBRARY -lssl $(CFLAGS) clean: rm -rf $(PROG) *.exe *.dSYM *.obj *.exp .*o *.lib diff --git a/examples/ws_ssl/net_skeleton.c b/examples/ws_ssl/net_skeleton.c index 669f25893..55b2a546d 100644 --- a/examples/ws_ssl/net_skeleton.c +++ b/examples/ws_ssl/net_skeleton.c @@ -14,7 +14,7 @@ // Alternatively, you can license this software under a commercial // license, as set out in <http://cesanta.com/>. // -// $Date: 2014-09-09 16:03:50 UTC $ +// $Date: 2014-09-09 17:07:55 UTC $ #include "net_skeleton.h" diff --git a/examples/ws_ssl/net_skeleton.h b/examples/ws_ssl/net_skeleton.h index 0985990a3..d662d17a2 100644 --- a/examples/ws_ssl/net_skeleton.h +++ b/examples/ws_ssl/net_skeleton.h @@ -14,7 +14,7 @@ // Alternatively, you can license this software under a commercial // license, as set out in <http://cesanta.com/>. // -// $Date: 2014-09-09 16:03:50 UTC $ +// $Date: 2014-09-09 17:07:55 UTC $ #ifndef NS_SKELETON_HEADER_INCLUDED #define NS_SKELETON_HEADER_INCLUDED diff --git a/examples/ws_ssl/ssl_wrapper.c b/examples/ws_ssl/ssl_wrapper.c index 9d0b44e58..9c33300e8 100644 --- a/examples/ws_ssl/ssl_wrapper.c +++ b/examples/ws_ssl/ssl_wrapper.c @@ -14,7 +14,7 @@ // Alternatively, you can license this software under a commercial // license, as set out in <http://cesanta.com/products.html>. // -// $Date: 2014-09-09 16:03:50 UTC $ +// $Date: 2014-09-09 17:07:55 UTC $ #include "net_skeleton.h" #include "ssl_wrapper.h" diff --git a/examples/ws_ssl/ssl_wrapper.h b/examples/ws_ssl/ssl_wrapper.h index b912103e2..d4c92e3ee 100644 --- a/examples/ws_ssl/ssl_wrapper.h +++ b/examples/ws_ssl/ssl_wrapper.h @@ -14,7 +14,7 @@ // Alternatively, you can license this software under a commercial // license, as set out in <http://cesanta.com/products.html>. // -// $Date: 2014-09-09 16:03:50 UTC $ +// $Date: 2014-09-09 17:07:55 UTC $ #ifndef SSL_WRAPPER_HEADER_INCLUDED #define SSL_WRAPPER_HEADER_INCLUDED diff --git a/mongoose.c b/mongoose.c index 3b2259fd1..1a0e7ccbb 100644 --- a/mongoose.c +++ b/mongoose.c @@ -15,7 +15,7 @@ // Alternatively, you can license this library under a commercial // license, as set out in <http://cesanta.com/>. // -// $Date: 2014-09-01 19:53:26 UTC $ +// $Date: 2014-09-09 17:07:55 UTC $ #ifdef NOEMBED_NET_SKELETON #include "net_skeleton.h" @@ -36,11 +36,13 @@ // // Alternatively, you can license this software under a commercial // license, as set out in <http://cesanta.com/>. +// +// $Date: 2014-09-09 17:07:55 UTC $ #ifndef NS_SKELETON_HEADER_INCLUDED #define NS_SKELETON_HEADER_INCLUDED -#define NS_SKELETON_VERSION "1.1" +#define NS_SKELETON_VERSION "2.0.0" #undef UNICODE // Use ANSI WinAPI functions #undef _UNICODE // Use multibyte encoding on Windows @@ -133,7 +135,9 @@ typedef int sock_t; #define DBG(x) #endif +#ifndef ARRAY_SIZE #define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) +#endif #ifdef NS_ENABLE_SSL #ifdef __APPLE__ @@ -174,7 +178,7 @@ void iobuf_remove(struct iobuf *, size_t data_size); // Net skeleton interface // Events. Meaning of event parameter (evp) is given in the comment. enum ns_event { - NS_POLL, // Sent to each connection on each call to ns_server_poll() + NS_POLL, // Sent to each connection on each call to ns_mgr_poll() NS_ACCEPT, // New connection accept()-ed. union socket_address *remote_addr NS_CONNECT, // connect() succeeded or failed. int *success_status NS_RECV, // Data has benn received. int *num_bytes @@ -187,36 +191,37 @@ enum ns_event { struct ns_connection; typedef void (*ns_callback_t)(struct ns_connection *, enum ns_event, void *evp); -struct ns_server { - void *server_data; - sock_t listening_sock; +struct ns_mgr { struct ns_connection *active_connections; - ns_callback_t callback; - SSL_CTX *ssl_ctx; - SSL_CTX *client_ssl_ctx; - const char *hexdump_file; - sock_t ctl[2]; + ns_callback_t callback; // Event handler function + const char *hexdump_file; // Debug hexdump file path + sock_t ctl[2]; // Socketpair for mg_wakeup() + void *user_data; // User data }; struct ns_connection { - struct ns_connection *prev, *next; - struct ns_server *server; + struct ns_connection *next, *prev; // ns_mgr::active_connections linkage + struct ns_connection *listener; // Set only for accept()-ed connections + struct ns_mgr *mgr; sock_t sock; union socket_address sa; struct iobuf recv_iobuf; struct iobuf send_iobuf; SSL *ssl; + SSL_CTX *ssl_ctx; void *connection_data; time_t last_io_time; + unsigned int flags; #define NSF_FINISHED_SENDING_DATA (1 << 0) #define NSF_BUFFER_BUT_DONT_SEND (1 << 1) #define NSF_SSL_HANDSHAKE_DONE (1 << 2) #define NSF_CONNECTING (1 << 3) #define NSF_CLOSE_IMMEDIATELY (1 << 4) -#define NSF_ACCEPTED (1 << 5) -#define NSF_WANT_READ (1 << 6) -#define NSF_WANT_WRITE (1 << 7) +#define NSF_WANT_READ (1 << 5) +#define NSF_WANT_WRITE (1 << 6) +#define NSF_LISTENING (1 << 7) +#define NSF_UDP (1 << 8) #define NSF_USER_1 (1 << 26) #define NSF_USER_2 (1 << 27) @@ -226,20 +231,15 @@ struct ns_connection { #define NSF_USER_6 (1 << 31) }; -void ns_server_init(struct ns_server *, void *server_data, ns_callback_t); -void ns_server_free(struct ns_server *); -int ns_server_poll(struct ns_server *, int milli); -void ns_server_wakeup(struct ns_server *); -void ns_server_wakeup_ex(struct ns_server *, ns_callback_t, void *, size_t); -void ns_iterate(struct ns_server *, ns_callback_t cb, void *param); -struct ns_connection *ns_next(struct ns_server *, struct ns_connection *); -struct ns_connection *ns_add_sock(struct ns_server *, sock_t sock, void *p); - -int ns_bind(struct ns_server *, const char *addr); -int ns_set_ssl_cert(struct ns_server *, const char *ssl_cert); -int ns_set_ssl_ca_cert(struct ns_server *, const char *ssl_ca_cert); -struct ns_connection *ns_connect(struct ns_server *, const char *host, - int port, int ssl, void *connection_param); +void ns_mgr_init(struct ns_mgr *, void *data, ns_callback_t); +void ns_mgr_free(struct ns_mgr *); +int ns_mgr_poll(struct ns_mgr *, int milli); +void ns_broadcast(struct ns_mgr *, ns_callback_t, void *, size_t); + +struct ns_connection *ns_next(struct ns_mgr *, struct ns_connection *); +struct ns_connection *ns_add_sock(struct ns_mgr *, sock_t sock, void *p); +struct ns_connection *ns_bind(struct ns_mgr *, const char *addr, void *p); +struct ns_connection *ns_connect(struct ns_mgr *, const char *addr, void *p); int ns_send(struct ns_connection *, const void *buf, int len); int ns_printf(struct ns_connection *, const char *fmt, ...); @@ -253,6 +253,7 @@ void ns_set_close_on_exec(sock_t); void ns_sock_to_str(sock_t sock, char *buf, size_t len, int flags); int ns_hexdump(const void *buf, int len, char *dst, int dst_len); int ns_avprintf(char **buf, size_t size, const char *fmt, va_list ap); +int ns_resolve(const char *domain_name, char *ip_addr_buf, size_t buf_len); #ifdef __cplusplus } @@ -274,6 +275,8 @@ int ns_avprintf(char **buf, size_t size, const char *fmt, va_list ap); // // Alternatively, you can license this software under a commercial // license, as set out in <http://cesanta.com/>. +// +// $Date: 2014-09-09 17:07:55 UTC $ #ifndef NS_MALLOC @@ -288,6 +291,9 @@ int ns_avprintf(char **buf, size_t size, const char *fmt, va_list ap); #define NS_FREE free #endif +#define NS_UDP_RECEIVE_BUFFER_SIZE 2000 +#define NS_VPRINTF_BUFFER_SIZE 500 + struct ctl_msg { ns_callback_t callback; char message[1024 * 8]; @@ -338,6 +344,16 @@ void iobuf_remove(struct iobuf *io, size_t n) { } } +static size_t ns_out(struct ns_connection *nc, const void *buf, size_t len) { + if (nc->flags & NSF_UDP) { + long n = send(nc->sock, buf, len, 0); + DBG(("%p %d send %ld (%d)", nc, nc->sock, n, errno)); + return n < 0 ? 0 : n; + } else { + return iobuf_append(&nc->send_iobuf, buf, len); + } +} + #ifndef NS_DISABLE_THREADS void *ns_start_thread(void *(*f)(void *), void *p) { #ifdef _WIN32 @@ -361,15 +377,15 @@ void *ns_start_thread(void *(*f)(void *), void *p) { } #endif // NS_DISABLE_THREADS -static void ns_add_conn(struct ns_server *server, struct ns_connection *c) { - c->next = server->active_connections; - server->active_connections = c; +static void ns_add_conn(struct ns_mgr *mgr, struct ns_connection *c) { + c->next = mgr->active_connections; + mgr->active_connections = c; c->prev = NULL; if (c->next != NULL) c->next->prev = c; } static void ns_remove_conn(struct ns_connection *conn) { - if (conn->prev == NULL) conn->server->active_connections = conn->next; + if (conn->prev == NULL) conn->mgr->active_connections = conn->next; if (conn->prev) conn->prev->next = conn->next; if (conn->next) conn->next->prev = conn->prev; } @@ -412,12 +428,12 @@ int ns_avprintf(char **buf, size_t size, const char *fmt, va_list ap) { return len; } -int ns_vprintf(struct ns_connection *conn, const char *fmt, va_list ap) { - char mem[2000], *buf = mem; +int ns_vprintf(struct ns_connection *nc, const char *fmt, va_list ap) { + char mem[NS_VPRINTF_BUFFER_SIZE], *buf = mem; int len; if ((len = ns_avprintf(&buf, sizeof(mem), fmt, ap)) > 0) { - iobuf_append(&conn->send_iobuf, buf, len); + ns_out(nc, buf, len); } if (buf != mem && buf != NULL) { free(buf); @@ -450,7 +466,7 @@ static void hexdump(struct ns_connection *nc, const char *path, ev == NS_RECV ? "<-" : ev == NS_SEND ? "->" : ev == NS_ACCEPT ? "<A" : ev == NS_CONNECT ? "C>" : "XX", dst, num_bytes); - if (num_bytes > 0 && (buf = (char *) malloc(buf_size)) != NULL) { + if (num_bytes > 0 && (buf = (char *) NS_MALLOC(buf_size)) != NULL) { ns_hexdump(io->buf + (ev == NS_SEND ? 0 : io->len) - (ev == NS_SEND ? 0 : num_bytes), num_bytes, buf, buf_size); fprintf(fp, "%s", buf); @@ -461,17 +477,14 @@ static void hexdump(struct ns_connection *nc, const char *path, } static void ns_call(struct ns_connection *conn, enum ns_event ev, void *p) { - if (conn->server->hexdump_file != NULL && ev != NS_POLL) { + if (conn->mgr->hexdump_file != NULL && ev != NS_POLL) { int len = (ev == NS_RECV || ev == NS_SEND) ? * (int *) p : 0; - hexdump(conn, conn->server->hexdump_file, len, ev); + hexdump(conn, conn->mgr->hexdump_file, len, ev); } - if (conn->server->callback) conn->server->callback(conn, ev, p); + if (conn->mgr->callback) conn->mgr->callback(conn, ev, p); } -static void ns_close_conn(struct ns_connection *conn) { - DBG(("%p %d", conn, conn->flags)); - ns_call(conn, NS_CLOSE, NULL); - ns_remove_conn(conn); +static void ns_destroy_conn(struct ns_connection *conn) { closesocket(conn->sock); iobuf_free(&conn->recv_iobuf); iobuf_free(&conn->send_iobuf); @@ -479,10 +492,20 @@ static void ns_close_conn(struct ns_connection *conn) { if (conn->ssl != NULL) { SSL_free(conn->ssl); } + if (conn->ssl_ctx != NULL) { + SSL_CTX_free(conn->ssl_ctx); + } #endif NS_FREE(conn); } +static void ns_close_conn(struct ns_connection *conn) { + DBG(("%p %d", conn, conn->flags)); + ns_call(conn, NS_CLOSE, NULL); + ns_remove_conn(conn); + ns_destroy_conn(conn); +} + void ns_set_close_on_exec(sock_t sock) { #ifdef _WIN32 (void) SetHandleInformation((HANDLE) sock, HANDLE_FLAG_INHERIT, 0); @@ -543,10 +566,31 @@ int ns_socketpair(sock_t sp[2]) { } #endif // NS_DISABLE_SOCKETPAIR -// Valid listening port spec is: [ip_address:]port, e.g. "80", "127.0.0.1:3128" -static int ns_parse_port_string(const char *str, union socket_address *sa) { +// TODO(lsm): use non-blocking resolver +static int ns_resolve2(const char *host, struct in_addr *ina) { + struct hostent *he; + if ((he = gethostbyname(host)) == NULL) { + DBG(("gethostbyname(%s) failed: %s", host, strerror(errno))); + } else { + memcpy(ina, he->h_addr_list[0], sizeof(*ina)); + return 1; + } + return 0; +} + +// Resolve FDQN "host", store IP address in the "ip". +// Return > 0 (IP address length) on success. +int ns_resolve(const char *host, char *buf, size_t n) { + struct in_addr ad; + return ns_resolve2(host, &ad) ? snprintf(buf, n, "%s", inet_ntoa(ad)) : 0; +} + +// Address format: [PROTO://][IP_ADDRESS:]PORT[:CERT][:CA_CERT] +static int ns_parse_address(const char *str, union socket_address *sa, + int *proto, int *use_ssl, char *cert, char *ca) { unsigned int a, b, c, d, port; - int len = 0; + int n = 0, len = 0; + char host[200]; #ifdef NS_ENABLE_IPV6 char buf[100]; #endif @@ -557,36 +601,57 @@ static int ns_parse_port_string(const char *str, union socket_address *sa) { memset(sa, 0, sizeof(*sa)); sa->sin.sin_family = AF_INET; + *proto = SOCK_STREAM; + *use_ssl = 0; + cert[0] = ca[0] = '\0'; + + if (memcmp(str, "ssl://", 6) == 0) { + str += 6; + *use_ssl = 1; + } else if (memcmp(str, "udp://", 6) == 0) { + str += 6; + *proto = SOCK_DGRAM; + } else if (memcmp(str, "tcp://", 6) == 0) { + str += 6; + } + if (sscanf(str, "%u.%u.%u.%u:%u%n", &a, &b, &c, &d, &port, &len) == 5) { // Bind to a specific IPv4 address, e.g. 192.168.1.5:8080 sa->sin.sin_addr.s_addr = htonl((a << 24) | (b << 16) | (c << 8) | d); sa->sin.sin_port = htons((uint16_t) port); #ifdef NS_ENABLE_IPV6 - } else if (sscanf(str, "[%49[^]]]:%u%n", buf, &port, &len) == 2 && + } else if (sscanf(str, "[%99[^]]]:%u%n", buf, &port, &len) == 2 && inet_pton(AF_INET6, buf, &sa->sin6.sin6_addr)) { // IPv6 address, e.g. [3ffe:2a00:100:7031::1]:8080 sa->sin6.sin6_family = AF_INET6; sa->sin6.sin6_port = htons((uint16_t) port); #endif + } else if (sscanf(str, "%199[^ :]:%u%n", host, &port, &len) == 2) { + sa->sin.sin_port = htons((uint16_t) port); + ns_resolve2(host, &sa->sin.sin_addr); } else if (sscanf(str, "%u%n", &port, &len) == 1) { // If only port is specified, bind to IPv4, INADDR_ANY sa->sin.sin_port = htons((uint16_t) port); - } else { - port = 0; // Parsing failure. Make port invalid. } - return port <= 0xffff && str[len] == '\0'; + if (*use_ssl && (sscanf(str + len, ":%99[^:]:%99[^:]%n", cert, ca, &n) == 2 || + sscanf(str + len, ":%99[^:]%n", cert, &n) == 1)) { + len += n; + } + + return port < 0xffff && str[len] == '\0' ? len : 0; } // 'sa' must be an initialized address to bind to -static sock_t ns_open_listening_socket(union socket_address *sa) { - socklen_t len = sizeof(*sa); +static sock_t ns_open_listening_socket(union socket_address *sa, int proto) { + socklen_t sa_len = (sa->sa.sa_family == AF_INET) ? + sizeof(sa->sin) : sizeof(sa->sin6); sock_t sock = INVALID_SOCKET; #ifndef _WIN32 int on = 1; #endif - if ((sock = socket(sa->sa.sa_family, SOCK_STREAM, 6)) != INVALID_SOCKET && + if ((sock = socket(sa->sa.sa_family, proto, 0)) != INVALID_SOCKET && #ifndef _WIN32 // SO_RESUSEADDR is not enabled on Windows because the semantics of // SO_REUSEADDR on UNIX and Windows is different. On Windows, @@ -596,12 +661,11 @@ static sock_t ns_open_listening_socket(union socket_address *sa) { // scenarios. Therefore, SO_REUSEADDR was disabled on Windows. !setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *) &on, sizeof(on)) && #endif - !bind(sock, &sa->sa, sa->sa.sa_family == AF_INET ? - sizeof(sa->sin) : sizeof(sa->sin6)) && - !listen(sock, SOMAXCONN)) { + !bind(sock, &sa->sa, sa_len) && + (proto == SOCK_DGRAM || listen(sock, SOMAXCONN) == 0)) { ns_set_non_blocking_mode(sock); // In case port was set to 0, get the real port number - (void) getsockname(sock, &sa->sa, &len); + (void) getsockname(sock, &sa->sa, &sa_len); } else if (sock != INVALID_SOCKET) { closesocket(sock); sock = INVALID_SOCKET; @@ -610,80 +674,97 @@ static sock_t ns_open_listening_socket(union socket_address *sa) { return sock; } -// Certificate generation script is at -// https://github.com/cesanta/net_skeleton/blob/master/examples/gen_certs.sh -int ns_set_ssl_ca_cert(struct ns_server *server, const char *cert) { #ifdef NS_ENABLE_SSL - STACK_OF(X509_NAME) *list = SSL_load_client_CA_file(cert); - if (cert != NULL && server->ssl_ctx != NULL && list != NULL) { - SSL_CTX_set_client_CA_list(server->ssl_ctx, list); - SSL_CTX_set_verify(server->ssl_ctx, SSL_VERIFY_PEER | - SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); +// Certificate generation script is at +// https://github.com/cesanta/net_skeleton/blob/master/scripts/gen_certs.sh + +static int ns_use_ca_cert(SSL_CTX *ctx, const char *cert) { + if (ctx == NULL) { + return -1; + } else if (cert == NULL || cert[0] == '\0') { return 0; } -#endif - return server != NULL && cert == NULL ? 0 : -1; + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, 0); + return SSL_CTX_load_verify_locations(ctx, cert, NULL) == 1 ? 0 : -2; } -int ns_set_ssl_cert(struct ns_server *server, const char *cert) { -#ifdef NS_ENABLE_SSL - if (cert != NULL && - (server->ssl_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) { +static int ns_use_cert(SSL_CTX *ctx, const char *pem_file) { + if (ctx == NULL) { return -1; - } else if (SSL_CTX_use_certificate_file(server->ssl_ctx, cert, 1) == 0 || - SSL_CTX_use_PrivateKey_file(server->ssl_ctx, cert, 1) == 0) { + } else if (pem_file == NULL || pem_file[0] == '\0') { + return 0; + } else if (SSL_CTX_use_certificate_file(ctx, pem_file, 1) == 0 || + SSL_CTX_use_PrivateKey_file(ctx, pem_file, 1) == 0) { return -2; } else { - SSL_CTX_set_mode(server->ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); - SSL_CTX_use_certificate_chain_file(server->ssl_ctx, cert); + SSL_CTX_set_mode(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + SSL_CTX_use_certificate_chain_file(ctx, pem_file); return 0; } -#endif - return server != NULL && cert == NULL ? 0 : -3; } +#endif // NS_ENABLE_SSL -int ns_bind(struct ns_server *server, const char *str) { +struct ns_connection *ns_bind(struct ns_mgr *srv, const char *str, void *data) { union socket_address sa; - ns_parse_port_string(str, &sa); - if (server->listening_sock != INVALID_SOCKET) { - closesocket(server->listening_sock); + struct ns_connection *nc = NULL; + int use_ssl, proto; + char cert[100], ca_cert[100]; + sock_t sock; + + ns_parse_address(str, &sa, &proto, &use_ssl, cert, ca_cert); + if (use_ssl && cert[0] == '\0') return NULL; + + if ((sock = ns_open_listening_socket(&sa, proto)) == INVALID_SOCKET) { + } else if ((nc = ns_add_sock(srv, sock, NULL)) == NULL) { + closesocket(sock); + } else { + nc->sa = sa; + nc->flags |= NSF_LISTENING; + nc->connection_data = data; + + if (proto == SOCK_DGRAM) { + nc->flags |= NSF_UDP; + } + +#ifdef NS_ENABLE_SSL + if (use_ssl) { + nc->ssl_ctx = SSL_CTX_new(SSLv23_server_method()); + if (ns_use_cert(nc->ssl_ctx, cert) != 0 || + ns_use_ca_cert(nc->ssl_ctx, ca_cert) != 0) { + ns_close_conn(nc); + nc = NULL; + } + } +#endif + + DBG(("%p sock %d/%d ssl %p %p", nc, sock, proto, nc->ssl_ctx, nc->ssl)); } - server->listening_sock = ns_open_listening_socket(&sa); - return server->listening_sock == INVALID_SOCKET ? -1 : - (int) ntohs(sa.sin.sin_port); -} + return nc; +} -static struct ns_connection *accept_conn(struct ns_server *server) { +static struct ns_connection *accept_conn(struct ns_connection *ls) { struct ns_connection *c = NULL; union socket_address sa; socklen_t len = sizeof(sa); sock_t sock = INVALID_SOCKET; // NOTE(lsm): on Windows, sock is always > FD_SETSIZE - if ((sock = accept(server->listening_sock, &sa.sa, &len)) == INVALID_SOCKET) { - } else if ((c = (struct ns_connection *) NS_MALLOC(sizeof(*c))) == NULL || - memset(c, 0, sizeof(*c)) == NULL) { + if ((sock = accept(ls->sock, &sa.sa, &len)) == INVALID_SOCKET) { + } else if ((c = ns_add_sock(ls->mgr, sock, NULL)) == NULL) { closesocket(sock); #ifdef NS_ENABLE_SSL - } else if (server->ssl_ctx != NULL && - ((c->ssl = SSL_new(server->ssl_ctx)) == NULL || + } else if (ls->ssl_ctx != NULL && + ((c->ssl = SSL_new(ls->ssl_ctx)) == NULL || SSL_set_fd(c->ssl, sock) != 1)) { DBG(("SSL error")); - closesocket(sock); - free(c); + ns_close_conn(c); c = NULL; #endif } else { - ns_set_close_on_exec(sock); - ns_set_non_blocking_mode(sock); - c->server = server; - c->sock = sock; - c->flags |= NSF_ACCEPTED; - - ns_add_conn(server, c); + c->listener = ls; ns_call(c, NS_ACCEPT, &sa); - DBG(("%p %d %p %p", c, c->sock, c->ssl, server->ssl_ctx)); + DBG(("%p %d %p %p", c, c->sock, c->ssl_ctx, c->ssl)); } return c; @@ -720,7 +801,7 @@ void ns_sock_to_str(sock_t sock, char *buf, size_t len, int flags) { // Only Windoze Vista (and newer) have inet_ntop() strncpy(buf, inet_ntoa(sa.sin.sin_addr), len); #else - inet_ntop(sa.sa.sa_family, (void *) &sa.sin.sin_addr, buf, len); + inet_ntop(sa.sa.sa_family, (void *) &sa.sin.sin_addr, buf,(socklen_t)len); #endif } if (flags & 2) { @@ -822,7 +903,7 @@ static void ns_read_from_socket(struct ns_connection *conn) { } else #endif { - while ((n = recv(conn->sock, buf, sizeof(buf), 0)) > 0) { + while ((n = (int) recv(conn->sock, buf, sizeof(buf), 0)) > 0) { DBG(("%p %d <- %d bytes (PLAIN)", conn, conn->flags, n)); iobuf_append(&conn->recv_iobuf, buf, n); ns_call(conn, NS_RECV, &n); @@ -851,7 +932,7 @@ static void ns_write_to_socket(struct ns_connection *conn) { } } else #endif - { n = send(conn->sock, io->buf, io->len, 0); } + { n = (int) send(conn->sock, io->buf, io->len, 0); } DBG(("%p %d -> %d bytes", conn, conn->flags, n)); @@ -864,7 +945,27 @@ static void ns_write_to_socket(struct ns_connection *conn) { } int ns_send(struct ns_connection *conn, const void *buf, int len) { - return iobuf_append(&conn->send_iobuf, buf, len); + return (int) ns_out(conn, buf, len); +} + +static void ns_handle_udp(struct ns_connection *ls) { + struct ns_connection nc; + char buf[NS_UDP_RECEIVE_BUFFER_SIZE]; + int n; + socklen_t s_len = sizeof(nc.sa); + + memset(&nc, 0, sizeof(nc)); + n = recvfrom(ls->sock, buf, sizeof(buf), 0, &nc.sa.sa, &s_len); + if (n <= 0) { + DBG(("%p recvfrom: %s", ls, strerror(errno))); + } else { + nc.recv_iobuf.buf = buf; + nc.recv_iobuf.len = nc.recv_iobuf.size = n; + nc.sock = ls->sock; + nc.mgr = ls->mgr; + DBG(("%p %d bytes received", ls, n)); + ns_call(&nc, NS_RECV, &n); + } } static void ns_add_to_set(sock_t sock, fd_set *set, sock_t *max_fd) { @@ -876,7 +977,7 @@ static void ns_add_to_set(sock_t sock, fd_set *set, sock_t *max_fd) { } } -int ns_server_poll(struct ns_server *server, int milli) { +int ns_mgr_poll(struct ns_mgr *mgr, int milli) { struct ns_connection *conn, *tmp_conn; struct timeval tv; fd_set read_set, write_set; @@ -884,15 +985,11 @@ int ns_server_poll(struct ns_server *server, int milli) { sock_t max_fd = INVALID_SOCKET; time_t current_time = time(NULL); - if (server->listening_sock == INVALID_SOCKET && - server->active_connections == NULL) return 0; - FD_ZERO(&read_set); FD_ZERO(&write_set); - ns_add_to_set(server->listening_sock, &read_set, &max_fd); - ns_add_to_set(server->ctl[1], &read_set, &max_fd); + ns_add_to_set(mgr->ctl[1], &read_set, &max_fd); - for (conn = server->active_connections; conn != NULL; conn = tmp_conn) { + for (conn = mgr->active_connections; conn != NULL; conn = tmp_conn) { tmp_conn = conn->next; ns_call(conn, NS_POLL, ¤t_time); if (!(conn->flags & NSF_WANT_WRITE)) { @@ -918,34 +1015,38 @@ int ns_server_poll(struct ns_server *server, int milli) { // now to prevent last_io_time being set to the past. current_time = time(NULL); - // Accept new connections - if (server->listening_sock != INVALID_SOCKET && - FD_ISSET(server->listening_sock, &read_set)) { - // We're not looping here, and accepting just one connection at - // a time. The reason is that eCos does not respect non-blocking - // flag on a listening socket and hangs in a loop. - if ((conn = accept_conn(server)) != NULL) { - conn->last_io_time = current_time; - } - } - // Read wakeup messages - if (server->ctl[1] != INVALID_SOCKET && - FD_ISSET(server->ctl[1], &read_set)) { + if (mgr->ctl[1] != INVALID_SOCKET && + FD_ISSET(mgr->ctl[1], &read_set)) { struct ctl_msg ctl_msg; - int len = recv(server->ctl[1], (char *) &ctl_msg, sizeof(ctl_msg), 0); - send(server->ctl[1], ctl_msg.message, 1, 0); + int len = (int) recv(mgr->ctl[1], (char *) &ctl_msg, sizeof(ctl_msg), 0); + send(mgr->ctl[1], ctl_msg.message, 1, 0); if (len >= (int) sizeof(ctl_msg.callback) && ctl_msg.callback != NULL) { - ns_iterate(server, ctl_msg.callback, ctl_msg.message); + struct ns_connection *c; + for (c = ns_next(mgr, NULL); c != NULL; c = ns_next(mgr, c)) { + ctl_msg.callback(c, NS_POLL, ctl_msg.message); + } } } - for (conn = server->active_connections; conn != NULL; conn = tmp_conn) { + for (conn = mgr->active_connections; conn != NULL; conn = tmp_conn) { tmp_conn = conn->next; if (FD_ISSET(conn->sock, &read_set)) { - conn->last_io_time = current_time; - ns_read_from_socket(conn); + if (conn->flags & NSF_LISTENING) { + if (conn->flags & NSF_UDP) { + ns_handle_udp(conn); + } else { + // We're not looping here, and accepting just one connection at + // a time. The reason is that eCos does not respect non-blocking + // flag on a listening socket and hangs in a loop. + accept_conn(conn); + } + } else { + conn->last_io_time = current_time; + ns_read_from_socket(conn); + } } + if (FD_ISSET(conn->sock, &write_set)) { if (conn->flags & NSF_CONNECTING) { ns_read_from_socket(conn); @@ -957,7 +1058,7 @@ int ns_server_poll(struct ns_server *server, int milli) { } } - for (conn = server->active_connections; conn != NULL; conn = tmp_conn) { + for (conn = mgr->active_connections; conn != NULL; conn = tmp_conn) { tmp_conn = conn->next; num_active_connections++; if ((conn->flags & NSF_CLOSE_IMMEDIATELY) || @@ -971,65 +1072,62 @@ int ns_server_poll(struct ns_server *server, int milli) { return num_active_connections; } -struct ns_connection *ns_connect(struct ns_server *server, const char *host, - int port, int use_ssl, void *param) { +struct ns_connection *ns_connect(struct ns_mgr *mgr, + const char *address, void *param) { sock_t sock = INVALID_SOCKET; - struct sockaddr_in sin; - struct hostent *he = NULL; - struct ns_connection *conn = NULL; - int connect_ret_val; - - (void) use_ssl; + struct ns_connection *nc = NULL; + union socket_address sa; + char cert[100], ca_cert[100]; + int connect_ret_val, use_ssl, proto; - if (host == NULL || (he = gethostbyname(host)) == NULL || - (sock = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { - DBG(("gethostbyname(%s) failed: %s", host, strerror(errno))); + ns_parse_address(address, &sa, &proto, &use_ssl, cert, ca_cert); + if ((sock = socket(AF_INET, proto, 0)) == INVALID_SOCKET) { return NULL; } - - sin.sin_family = AF_INET; - sin.sin_port = htons((uint16_t) port); - sin.sin_addr = * (struct in_addr *) he->h_addr_list[0]; ns_set_non_blocking_mode(sock); + connect_ret_val = connect(sock, &sa.sa, sizeof(sa.sin)); - connect_ret_val = connect(sock, (struct sockaddr *) &sin, sizeof(sin)); - if (ns_is_error(connect_ret_val)) { + if (connect_ret_val != 0 && ns_is_error(connect_ret_val)) { closesocket(sock); return NULL; - } else if ((conn = (struct ns_connection *) - NS_MALLOC(sizeof(*conn))) == NULL) { + } else if ((nc = ns_add_sock(mgr, sock, param)) == NULL) { closesocket(sock); return NULL; } - memset(conn, 0, sizeof(*conn)); - conn->server = server; - conn->sock = sock; - conn->connection_data = param; - conn->flags = NSF_CONNECTING; - conn->last_io_time = time(NULL); + nc->sa = sa; // Essential, cause UDP conns will use sendto() + if (proto == SOCK_DGRAM) { + nc->flags = NSF_UDP; + } else { + nc->flags = NSF_CONNECTING; + } #ifdef NS_ENABLE_SSL - if (use_ssl && - (conn->ssl = SSL_new(server->client_ssl_ctx)) != NULL) { - SSL_set_fd(conn->ssl, sock); + if (use_ssl) { + if ((nc->ssl_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL || + ns_use_cert(nc->ssl_ctx, cert) != 0 || + ns_use_ca_cert(nc->ssl_ctx, ca_cert) != 0 || + (nc->ssl = SSL_new(nc->ssl_ctx)) == NULL) { + ns_close_conn(nc); + return NULL; + } else { + SSL_set_fd(nc->ssl, sock); + } } #endif - ns_add_conn(server, conn); - DBG(("%p %s:%d %d %p", conn, host, port, conn->sock, conn->ssl)); - - return conn; + return nc; } -struct ns_connection *ns_add_sock(struct ns_server *s, sock_t sock, void *p) { +struct ns_connection *ns_add_sock(struct ns_mgr *s, sock_t sock, void *p) { struct ns_connection *conn; if ((conn = (struct ns_connection *) NS_MALLOC(sizeof(*conn))) != NULL) { memset(conn, 0, sizeof(*conn)); ns_set_non_blocking_mode(sock); + ns_set_close_on_exec(sock); conn->sock = sock; conn->connection_data = p; - conn->server = s; + conn->mgr = s; conn->last_io_time = time(NULL); ns_add_conn(s, conn); DBG(("%p %d", conn, sock)); @@ -1037,40 +1135,26 @@ struct ns_connection *ns_add_sock(struct ns_server *s, sock_t sock, void *p) { return conn; } -struct ns_connection *ns_next(struct ns_server *s, struct ns_connection *conn) { +struct ns_connection *ns_next(struct ns_mgr *s, struct ns_connection *conn) { return conn == NULL ? s->active_connections : conn->next; } -void ns_iterate(struct ns_server *server, ns_callback_t cb, void *param) { - struct ns_connection *conn, *tmp_conn; - - for (conn = server->active_connections; conn != NULL; conn = tmp_conn) { - tmp_conn = conn->next; - cb(conn, NS_POLL, param); - } -} - -void ns_server_wakeup_ex(struct ns_server *server, ns_callback_t cb, - void *data, size_t len) { +void ns_broadcast(struct ns_mgr *mgr, ns_callback_t cb,void *data, size_t len) { struct ctl_msg ctl_msg; - if (server->ctl[0] != INVALID_SOCKET && data != NULL && + if (mgr->ctl[0] != INVALID_SOCKET && data != NULL && len < sizeof(ctl_msg.message)) { ctl_msg.callback = cb; memcpy(ctl_msg.message, data, len); - send(server->ctl[0], (char *) &ctl_msg, + send(mgr->ctl[0], (char *) &ctl_msg, offsetof(struct ctl_msg, message) + len, 0); - recv(server->ctl[0], (char *) &len, 1, 0); + recv(mgr->ctl[0], (char *) &len, 1, 0); } } -void ns_server_wakeup(struct ns_server *server) { - ns_server_wakeup_ex(server, NULL, (void *) "", 0); -} - -void ns_server_init(struct ns_server *s, void *server_data, ns_callback_t cb) { +void ns_mgr_init(struct ns_mgr *s, void *user_data, ns_callback_t cb) { memset(s, 0, sizeof(*s)); - s->listening_sock = s->ctl[0] = s->ctl[1] = INVALID_SOCKET; - s->server_data = server_data; + s->ctl[0] = s->ctl[1] = INVALID_SOCKET; + s->user_data = user_data; s->callback = cb; #ifdef _WIN32 @@ -1089,33 +1173,25 @@ void ns_server_init(struct ns_server *s, void *server_data, ns_callback_t cb) { #ifdef NS_ENABLE_SSL {static int init_done; if (!init_done) { SSL_library_init(); init_done++; }} - s->client_ssl_ctx = SSL_CTX_new(SSLv23_client_method()); #endif } -void ns_server_free(struct ns_server *s) { +void ns_mgr_free(struct ns_mgr *s) { struct ns_connection *conn, *tmp_conn; DBG(("%p", s)); if (s == NULL) return; // Do one last poll, see https://github.com/cesanta/mongoose/issues/286 - ns_server_poll(s, 0); + ns_mgr_poll(s, 0); - if (s->listening_sock != INVALID_SOCKET) closesocket(s->listening_sock); if (s->ctl[0] != INVALID_SOCKET) closesocket(s->ctl[0]); if (s->ctl[1] != INVALID_SOCKET) closesocket(s->ctl[1]); - s->listening_sock = s->ctl[0] = s->ctl[1] = INVALID_SOCKET; + s->ctl[0] = s->ctl[1] = INVALID_SOCKET; for (conn = s->active_connections; conn != NULL; conn = tmp_conn) { tmp_conn = conn->next; ns_close_conn(conn); } - -#ifdef NS_ENABLE_SSL - if (s->ssl_ctx != NULL) SSL_CTX_free(s->ssl_ctx); - if (s->client_ssl_ctx != NULL) SSL_CTX_free(s->client_ssl_ctx); - s->ssl_ctx = s->client_ssl_ctx = NULL; -#endif } // net_skeleton end #endif // NOEMBED_NET_SKELETON @@ -1264,11 +1340,6 @@ enum { #endif #ifndef MONGOOSE_NO_SSI SSI_PATTERN, -#endif -#ifdef NS_ENABLE_SSL - SSL_CERTIFICATE, - SSL_CA_CERTIFICATE, - SSL_MITM_CERTS, #endif URL_REWRITES, NUM_OPTIONS @@ -1299,7 +1370,7 @@ static const char *static_config_options[] = { #ifndef MONGOOSE_NO_FILESYSTEM "hide_files_patterns", NULL, "hexdump_file", NULL, - "index_files","index.html,index.htm,index.shtml,index.cgi,index.php,index.lp", + "index_files","index.html,index.htm,index.shtml,index.cgi,index.php", #endif "listening_port", NULL, #ifndef _WIN32 @@ -1307,18 +1378,13 @@ static const char *static_config_options[] = { #endif #ifndef MONGOOSE_NO_SSI "ssi_pattern", "**.shtml$|**.shtm$", -#endif -#ifdef NS_ENABLE_SSL - "ssl_certificate", NULL, - "ssl_ca_certificate", NULL, - "ssl_mitm_certs", NULL, #endif "url_rewrites", NULL, NULL }; struct mg_server { - struct ns_server ns_server; + struct ns_mgr ns_mgr; union socket_address lsa; // Listening socket address mg_handler_t event_handler; char *config_options[NUM_OPTIONS]; @@ -1420,6 +1486,32 @@ void *mg_start_thread(void *(*f)(void *), void *p) { } #endif // MONGOOSE_NO_THREADS +#ifdef _WIN32 +static void *mmap(void *addr, int64_t len, int prot, int flags, int fd, + int offset) { + HANDLE fh = (HANDLE) _get_osfhandle(fd); + HANDLE mh = CreateFileMapping(fh, 0, PAGE_READONLY, 0, 0, 0); + void *p = MapViewOfFile(mh, FILE_MAP_READ, 0, 0, (size_t) len); + CloseHandle(mh); + return p; +} +#define munmap(x, y) UnmapViewOfFile(x) +#define MAP_FAILED NULL +#define MAP_PRIVATE 0 +#define PROT_READ 0 +#else +#include <sys/mman.h> +#endif + +void *mg_mmap(FILE *fp, size_t size) { + void *p = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fileno(fp), 0); + return p == MAP_FAILED ? NULL : p; +} + +void mg_munmap(void *p, size_t size) { + munmap(p, size); +} + #if defined(_WIN32) && !defined(MONGOOSE_NO_FILESYSTEM) // Encode 'path' which is assumed UTF-8 string, into UNICODE string. // wbuf and wbuf_len is a target buffer and its length. @@ -2093,7 +2185,7 @@ static void open_cgi_endpoint(struct connection *conn, const char *prog) { if (start_process(conn->server->config_options[CGI_INTERPRETER], prog, blk.buf, blk.vars, dir, fds[1]) != 0) { conn->endpoint_type = EP_CGI; - conn->endpoint.nc = ns_add_sock(&conn->server->ns_server, fds[0], conn); + conn->endpoint.nc = ns_add_sock(&conn->server->ns_mgr, fds[0], conn); conn->endpoint.nc->flags |= MG_CGI_CONN; ns_send(conn->ns_conn, cgi_status, sizeof(cgi_status) - 1); conn->mg_conn.status_code = 200; @@ -4160,7 +4252,6 @@ int mg_terminate_ssl(struct mg_connection *c, const char *cert) { SSL_CTX *ctx; DBG(("%p MITM", conn)); - SSL_library_init(); if ((ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) return 0; SSL_CTX_use_certificate_file(ctx, cert, 1); @@ -4170,7 +4261,7 @@ int mg_terminate_ssl(struct mg_connection *c, const char *cert) { // When clear-text reply is pushed to client, switch to SSL mode. // TODO(lsm): check for send() failure send(conn->ns_conn->sock, ok, sizeof(ok) - 1, 0); - DBG(("%p %lu %d SEND", c, (unsigned long)sizeof(ok) - 1, n)); + //DBG(("%p %lu %d SEND", c, (unsigned long) sizeof(ok) - 1, n)); conn->ns_conn->send_iobuf.len = 0; conn->endpoint_type = EP_USER; // To keep-alive in close_local_endpoint() close_local_endpoint(conn); // Clean up current CONNECT request @@ -4182,12 +4273,12 @@ int mg_terminate_ssl(struct mg_connection *c, const char *cert) { } #endif -int mg_forward(struct mg_connection *c, const char *host, int port, int ssl) { +int mg_forward(struct mg_connection *c, const char *addr) { + static const char ok[] = "HTTP/1.1 200 OK\r\n\r\n"; struct connection *conn = MG_CONN_2_CONN(c); - struct ns_server *server = &conn->server->ns_server; struct ns_connection *pc; - if ((pc = ns_connect(server, host, port, ssl, conn)) == NULL) { + if ((pc = ns_connect(&conn->server->ns_mgr, addr, conn)) == NULL) { conn->ns_conn->flags |= NSF_CLOSE_IMMEDIATELY; return 0; } @@ -4196,12 +4287,12 @@ int mg_forward(struct mg_connection *c, const char *host, int port, int ssl) { pc->flags |= MG_PROXY_CONN; conn->endpoint_type = EP_PROXY; conn->endpoint.nc = pc; - DBG(("%p [%s] [%s:%d] -> %p %p", - conn, c->uri, host, port, pc, conn->ns_conn->ssl)); + DBG(("%p [%s] [%s] -> %p %p", conn, c->uri, addr, pc, conn->ns_conn->ssl)); if (strcmp(c->request_method, "CONNECT") == 0) { // For CONNECT request, reply with 200 OK. Tunnel is established. - mg_printf(c, "%s", "HTTP/1.1 200 OK\r\n\r\n"); + // TODO(lsm): check for send() failure + (void) send(conn->ns_conn->sock, ok, sizeof(ok) - 1, 0); } else { // Strip "http://host:port" part from the URI if (memcmp(c->uri, "http://", 7) == 0) c->uri += 7; @@ -4212,7 +4303,7 @@ int mg_forward(struct mg_connection *c, const char *host, int port, int ssl) { } static void proxify_connection(struct connection *conn) { - char proto[10], host[500], cert[500]; + char proto[10], host[500], cert[500], addr[1000]; unsigned short port = 80; struct mg_connection *c = &conn->mg_conn; int n = 0; @@ -4225,25 +4316,9 @@ static void proxify_connection(struct connection *conn) { n = 0; } -#ifdef NS_ENABLE_SSL - // Find out whether we should be in the MITM mode - { - const char *certs = conn->server->config_options[SSL_MITM_CERTS]; - int host_len = strlen(host); - struct vec a, b; - - while (conn->ns_conn->ssl == NULL && port != 80 && - (certs = next_option(certs, &a, &b)) != NULL) { - if (a.len != host_len || mg_strncasecmp(a.ptr, host, a.len)) continue; - snprintf(cert, sizeof(cert), "%.*s", b.len, b.ptr); - mg_terminate_ssl(&conn->mg_conn, cert); - return; - } - } -#endif - - if (n > 0 && mg_forward(c, host, port, conn->ns_conn->ssl != NULL)) { - } else { + snprintf(addr, sizeof(addr), "%s://%s:%hu", + conn->ns_conn->ssl != NULL ? "ssl" : "tcp", host, port); + if (n <= 0 || !mg_forward(c, addr)) { conn->ns_conn->flags |= NSF_CLOSE_IMMEDIATELY; } } @@ -4535,12 +4610,11 @@ static void process_response(struct connection *conn) { } } -struct mg_connection *mg_connect(struct mg_server *server, const char *host, - int port, int use_ssl) { +struct mg_connection *mg_connect(struct mg_server *server, const char *addr) { struct ns_connection *nsconn; struct connection *conn; - nsconn = ns_connect(&server->ns_server, host, port, use_ssl, NULL); + nsconn = ns_connect(&server->ns_mgr, addr, NULL); if (nsconn == NULL) return 0; if ((conn = (struct connection *) calloc(1, sizeof(*conn))) == NULL) { @@ -4555,7 +4629,7 @@ struct mg_connection *mg_connect(struct mg_server *server, const char *host, conn->server = server; conn->endpoint_type = EP_CLIENT; //conn->handler = handler; - conn->mg_conn.server_param = server->ns_server.server_data; + conn->mg_conn.server_param = server->ns_mgr.user_data; conn->ns_conn->flags = NSF_CONNECTING; return &conn->mg_conn; @@ -4684,7 +4758,7 @@ static void transfer_file_data(struct connection *conn) { } int mg_poll_server(struct mg_server *server, int milliseconds) { - return ns_server_poll(&server->ns_server, milliseconds); + return ns_mgr_poll(&server->ns_mgr, milliseconds); } void mg_destroy_server(struct mg_server **server) { @@ -4692,7 +4766,7 @@ void mg_destroy_server(struct mg_server **server) { struct mg_server *s = *server; int i; - ns_server_free(&s->ns_server); + ns_mgr_free(&s->ns_mgr); for (i = 0; i < (int) ARRAY_SIZE(s->config_options); i++) { free(s->config_options[i]); // It is OK to free(NULL) } @@ -4701,34 +4775,15 @@ void mg_destroy_server(struct mg_server **server) { } } -struct mg_iterator { - mg_handler_t cb; - void *param; -}; - -static void iter(struct ns_connection *nsconn, enum ns_event ev, void *param) { - if (ev == NS_POLL) { - struct mg_iterator *it = (struct mg_iterator *) param; - struct connection *c = (struct connection *) nsconn->connection_data; - if (c != NULL) c->mg_conn.callback_param = it->param; - it->cb(&c->mg_conn, MG_POLL); - } -} - struct mg_connection *mg_next(struct mg_server *s, struct mg_connection *c) { struct connection *conn = MG_CONN_2_CONN(c); - struct ns_connection *nc = ns_next(&s->ns_server, + struct ns_connection *nc = ns_next(&s->ns_mgr, c == NULL ? NULL : conn->ns_conn); - - return nc == NULL ? NULL : - & ((struct connection *) nc->connection_data)->mg_conn; -} - -// Apply function to all active connections. -void mg_iterate_over_connections(struct mg_server *server, mg_handler_t cb, - void *param) { - struct mg_iterator it = { cb, param }; - ns_iterate(&server->ns_server, iter, &it); + if (nc != NULL && nc->connection_data != NULL) { + return & ((struct connection *) nc->connection_data)->mg_conn; + } else { + return NULL; + } } static int get_var(const char *data, size_t data_len, const char *name, @@ -4886,18 +4941,18 @@ const char *mg_set_option(struct mg_server *server, const char *name, DBG(("%s [%s]", name, *v)); if (ind == LISTENING_PORT) { - int port = ns_bind(&server->ns_server, value); - if (port < 0) { + struct ns_connection *c = ns_bind(&server->ns_mgr, value, NULL); + if (c == NULL) { error_msg = "Cannot bind to port"; } else { char buf[100]; - ns_sock_to_str(server->ns_server.listening_sock, buf, sizeof(buf), 2); + ns_sock_to_str(c->sock, buf, sizeof(buf), 2); free(*v); *v = mg_strdup(buf); } #ifndef MONGOOSE_NO_FILESYSTEM } else if (ind == HEXDUMP_FILE) { - server->ns_server.hexdump_file = *v; + server->ns_mgr.hexdump_file = *v; #endif #ifndef _WIN32 } else if (ind == RUN_AS_USER) { @@ -4909,21 +4964,6 @@ const char *mg_set_option(struct mg_server *server, const char *name, } else if (setuid(pw->pw_uid) != 0) { error_msg = "setuid() failed"; } -#endif -#ifdef NS_ENABLE_SSL - } else if (ind == SSL_CERTIFICATE) { - int res = ns_set_ssl_cert(&server->ns_server, value); - if (res == -2) { - error_msg = "Cannot load PEM"; - } else if (res == -3) { - error_msg = "SSL not enabled"; - } else if (res == -1) { - error_msg = "SSL_CTX_new() failed"; - } - } else if (ind == SSL_CA_CERTIFICATE) { - if (ns_set_ssl_ca_cert(&server->ns_server, value) != 0) { - error_msg = "Error setting CA cert"; - } #endif } @@ -4943,7 +4983,7 @@ static void set_ips(struct ns_connection *nc, int is_rem) { } static void on_accept(struct ns_connection *nc, union socket_address *sa) { - struct mg_server *server = (struct mg_server *) nc->server; + struct mg_server *server = (struct mg_server *) nc->mgr; struct connection *conn; if (!check_acl(server->config_options[ACCESS_CONTROL_LIST], @@ -4957,7 +4997,7 @@ static void on_accept(struct ns_connection *nc, union socket_address *sa) { // Initialize the rest of connection attributes conn->server = server; - conn->mg_conn.server_param = nc->server->server_data; + conn->mg_conn.server_param = nc->mgr->user_data; set_ips(nc, 1); set_ips(nc, 0); } @@ -5007,7 +5047,7 @@ static void mg_ev_handler(struct ns_connection *nc, enum ns_event ev, void *p) { if (conn != NULL) { conn->num_bytes_recv += * (int *) p; } - if (nc->flags & NSF_ACCEPTED) { + if (nc->listener != NULL) { on_recv_data(conn); #ifndef MONGOOSE_NO_CGI } else if (nc->flags & MG_CGI_CONN) { @@ -5110,23 +5150,25 @@ void mg_wakeup_server_ex(struct mg_server *server, mg_handler_t cb, va_end(ap); // "len + 1" is to include terminating \0 in the message - ns_server_wakeup_ex(&server->ns_server, iter2, buf, len + 1); + ns_broadcast(&server->ns_mgr, iter2, buf, len + 1); } void mg_wakeup_server(struct mg_server *server) { - ns_server_wakeup_ex(&server->ns_server, NULL, (void *) "", 0); + ns_broadcast(&server->ns_mgr, NULL, (void *) "", 0); } +#if 0 void mg_set_listening_socket(struct mg_server *server, int sock) { - if (server->ns_server.listening_sock != INVALID_SOCKET) { - closesocket(server->ns_server.listening_sock); + if (server->ns_mgr.listening_sock != INVALID_SOCKET) { + closesocket(server->ns_mgr.listening_sock); } - server->ns_server.listening_sock = (sock_t) sock; + server->ns_mgr.listening_sock = (sock_t) sock; } int mg_get_listening_socket(struct mg_server *server) { - return server->ns_server.listening_sock; + return server->ns_mgr.listening_sock; } +#endif const char *mg_get_option(const struct mg_server *server, const char *name) { const char **opts = (const char **) server->config_options; @@ -5136,33 +5178,8 @@ const char *mg_get_option(const struct mg_server *server, const char *name) { struct mg_server *mg_create_server(void *server_data, mg_handler_t handler) { struct mg_server *server = (struct mg_server *) calloc(1, sizeof(*server)); - ns_server_init(&server->ns_server, server_data, mg_ev_handler); + ns_mgr_init(&server->ns_mgr, server_data, mg_ev_handler); set_default_option_values(server->config_options); server->event_handler = handler; return server; } - -#ifdef _WIN32 -static void *mmap(void *addr, int64_t len, int prot, int flags, int fd, - int offset) { - HANDLE fh = (HANDLE) _get_osfhandle(fd); - HANDLE mh = CreateFileMapping(fh, 0, PAGE_READONLY, 0, 0, 0); - void *p = MapViewOfFile(mh, FILE_MAP_READ, 0, 0, (size_t) len); - CloseHandle(mh); - return p; -} -#define munmap(x, y) UnmapViewOfFile(x) -#define MAP_FAILED NULL -#define MAP_PRIVATE 0 -#define PROT_READ 0 -#else -#include <sys/mman.h> -#endif - -void *mg_mmap(FILE *fp, size_t size) { - return mmap(NULL, size, PROT_READ, MAP_PRIVATE, fileno(fp), 0); -} - -void mg_munmap(void *p, size_t size) { - munmap(p, size); -} diff --git a/mongoose.h b/mongoose.h index d39808ee1..572b863e3 100644 --- a/mongoose.h +++ b/mongoose.h @@ -15,7 +15,7 @@ // Alternatively, you can license this library under a commercial // license, as set out in <http://cesanta.com/>. // -// $Date: 2014-09-01 19:53:26 UTC $ +// $Date: 2014-09-09 17:07:55 UTC $ #ifndef MONGOOSE_HEADER_INCLUDED #define MONGOOSE_HEADER_INCLUDED @@ -55,7 +55,7 @@ struct mg_connection { int wsbits; // First byte of the websocket frame void *server_param; // Parameter passed to mg_add_uri_handler() void *connection_param; // Placeholder for connection-specific data - void *callback_param; // Needed by mg_iterate_over_connections() + void *callback_param; }; struct mg_server; // Opaque structure describing server instance @@ -95,11 +95,10 @@ const char **mg_get_valid_option_names(void); const char *mg_get_option(const struct mg_server *server, const char *name); void mg_set_listening_socket(struct mg_server *, int sock); int mg_get_listening_socket(struct mg_server *); -void mg_iterate_over_connections(struct mg_server *, mg_handler_t, void *); struct mg_connection *mg_next(struct mg_server *, struct mg_connection *); void mg_wakeup_server(struct mg_server *); void mg_wakeup_server_ex(struct mg_server *, mg_handler_t, const char *, ...); -struct mg_connection *mg_connect(struct mg_server *, const char *, int, int); +struct mg_connection *mg_connect(struct mg_server *, const char *); // Connection management functions void mg_send_status(struct mg_connection *, int status_code); @@ -127,6 +126,7 @@ int mg_parse_multipart(const char *buf, int buf_len, char *file_name, int file_name_len, const char **data, int *data_len); + // Utility functions void *mg_start_thread(void *(*func)(void *), void *param); char *mg_md5(char buf[33], ...); @@ -134,7 +134,7 @@ int mg_authorize_digest(struct mg_connection *c, FILE *fp); int mg_url_encode(const char *src, size_t s_len, char *dst, size_t dst_len); int mg_url_decode(const char *src, int src_len, char *dst, int dst_len, int); int mg_terminate_ssl(struct mg_connection *c, const char *cert); -int mg_forward(struct mg_connection *, const char *host, int port, int use_ssl); +int mg_forward(struct mg_connection *c, const char *addr); void *mg_mmap(FILE *fp, size_t size); void mg_munmap(void *p, size_t size); @@ -147,7 +147,6 @@ struct mg_expansion { void mg_template(struct mg_connection *, const char *text, struct mg_expansion *expansions); - #ifdef __cplusplus } #endif // __cplusplus -- GitLab