| /* Copyright (c) 2013-2017 the Civetweb developers |
| * Copyright (c) 2004-2013 Sergey Lyubka |
| * |
| * 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. |
| */ |
| |
| #if defined(_WIN32) |
| |
| #ifndef _CRT_SECURE_NO_WARNINGS |
| #define _CRT_SECURE_NO_WARNINGS /* Disable deprecation warning in VS2005 */ |
| #endif |
| #ifndef _CRT_SECURE_NO_DEPRECATE |
| #define _CRT_SECURE_NO_DEPRECATE |
| #endif |
| #ifdef WIN32_LEAN_AND_MEAN |
| #undef WIN32_LEAN_AND_MEAN /* Required for some functions (tray icons, ...) */ |
| #endif |
| |
| #else |
| |
| #define _XOPEN_SOURCE 600 /* For PATH_MAX on linux */ |
| /* This should also be sufficient for "realpath", according to |
| * http://man7.org/linux/man-pages/man3/realpath.3.html, but in |
| * reality it does not seem to work. */ |
| /* In case this causes a problem, disable the warning: |
| * #pragma GCC diagnostic ignored "-Wimplicit-function-declaration" |
| * #pragma clang diagnostic ignored "-Wimplicit-function-declaration" |
| */ |
| #endif |
| |
| #ifndef IGNORE_UNUSED_RESULT |
| #define IGNORE_UNUSED_RESULT(a) ((void)((a) && 1)) |
| #endif |
| |
| #if defined(__cplusplus) && (__cplusplus >= 201103L) |
| #define NO_RETURN [[noreturn]] |
| #elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) |
| #define NO_RETURN _Noreturn |
| #elif defined(__GNUC__) |
| #define NO_RETURN __attribute((noreturn)) |
| #else |
| #define NO_RETURN |
| #endif |
| |
| /* Use same defines as in civetweb.c before including system headers. */ |
| #ifndef _LARGEFILE_SOURCE |
| #define _LARGEFILE_SOURCE /* For fseeko(), ftello() */ |
| #endif |
| #ifndef _FILE_OFFSET_BITS |
| #define _FILE_OFFSET_BITS 64 /* Use 64-bit file offsets by default */ |
| #endif |
| #ifndef __STDC_FORMAT_MACROS |
| #define __STDC_FORMAT_MACROS /* <inttypes.h> wants this for C++ */ |
| #endif |
| #ifndef __STDC_LIMIT_MACROS |
| #define __STDC_LIMIT_MACROS /* C++ wants that for INT64_MAX */ |
| #endif |
| |
| #include <string.h> |
| #include <errno.h> |
| #include <sys/stat.h> |
| #include <stdio.h> |
| #include <stdint.h> |
| #include <limits.h> |
| #include <stdlib.h> |
| #include <signal.h> |
| #include <stddef.h> |
| #include <stdarg.h> |
| #include <ctype.h> |
| #include <assert.h> |
| |
| #include "civetweb.h" |
| |
| #define printf \ |
| DO_NOT_USE_THIS_FUNCTION__USE_fprintf /* Required for unit testing */ |
| |
| #if defined(_WIN32) \ |
| && !defined(__SYMBIAN32__) /* WINDOWS / UNIX include block */ |
| #ifndef _WIN32_WINNT |
| #define _WIN32_WINNT 0x0501 /* for tdm-gcc so we can use getconsolewindow */ |
| #endif |
| #undef UNICODE |
| #include <windows.h> |
| #include <winsvc.h> |
| #include <shlobj.h> |
| #include <io.h> |
| |
| #define getcwd(a, b) (_getcwd(a, b)) |
| #if !defined(__MINGW32__) |
| extern char *_getcwd(char *buf, size_t size); |
| #endif |
| |
| #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 WINCDECL __cdecl |
| #define abs_path(rel, abs, abs_size) (_fullpath((abs), (rel), (abs_size))) |
| |
| #else /* defined(_WIN32) && !defined(__SYMBIAN32__) - WINDOWS / UNIX include \ |
| block */ |
| |
| #include <unistd.h> |
| #include <sys/utsname.h> |
| #include <sys/wait.h> |
| |
| #define DIRSEP '/' |
| #define WINCDECL |
| #define abs_path(rel, abs, abs_size) (realpath((rel), (abs))) |
| |
| #endif /* defined(_WIN32) && !defined(__SYMBIAN32__) - WINDOWS / UNIX include \ |
| block */ |
| |
| #ifndef PATH_MAX |
| #define PATH_MAX (1024) |
| #endif |
| |
| #define MAX_OPTIONS (50) |
| #define MAX_CONF_FILE_LINE_SIZE (8 * 1024) |
| |
| struct tuser_data { |
| char *first_message; |
| }; |
| |
| |
| static int g_exit_flag = 0; /* Main loop should exit */ |
| static char g_server_base_name[40]; /* Set by init_server_name() */ |
| static const char *g_server_name; /* Set by init_server_name() */ |
| static const char *g_icon_name; /* Set by init_server_name() */ |
| static const char *g_website; /* Set by init_server_name() */ |
| static char *g_system_info; /* Set by init_system_info() */ |
| static char g_config_file_name[PATH_MAX] = |
| ""; /* Set by process_command_line_arguments() */ |
| static struct mg_context *g_ctx; /* Set by start_civetweb() */ |
| static struct tuser_data |
| g_user_data; /* Passed to mg_start() by start_civetweb() */ |
| |
| #if !defined(CONFIG_FILE) |
| #define CONFIG_FILE "civetweb.conf" |
| #endif /* !CONFIG_FILE */ |
| |
| #if !defined(PASSWORDS_FILE_NAME) |
| #define PASSWORDS_FILE_NAME ".htpasswd" |
| #endif |
| |
| /* backup config file */ |
| #if !defined(CONFIG_FILE2) && defined(__linux__) |
| #define CONFIG_FILE2 "/usr/local/etc/civetweb.conf" |
| #endif |
| |
| enum { OPTION_TITLE, OPTION_ICON, OPTION_WEBPAGE, NUM_MAIN_OPTIONS }; |
| |
| static struct mg_option main_config_options[] = { |
| {"title", CONFIG_TYPE_STRING, NULL}, |
| {"icon", CONFIG_TYPE_STRING, NULL}, |
| {"website", CONFIG_TYPE_STRING, NULL}, |
| {NULL, CONFIG_TYPE_UNKNOWN, NULL}}; |
| |
| |
| static void WINCDECL |
| signal_handler(int sig_num) |
| { |
| g_exit_flag = sig_num; |
| } |
| |
| |
| static NO_RETURN void |
| die(const char *fmt, ...) |
| { |
| va_list ap; |
| char msg[512] = ""; |
| |
| va_start(ap, fmt); |
| (void)vsnprintf(msg, sizeof(msg) - 1, fmt, ap); |
| msg[sizeof(msg) - 1] = 0; |
| va_end(ap); |
| |
| #if defined(_WIN32) |
| MessageBox(NULL, msg, "Error", MB_OK); |
| #else |
| fprintf(stderr, "%s\n", msg); |
| #endif |
| |
| exit(EXIT_FAILURE); |
| } |
| |
| |
| #ifdef WIN32 |
| static int MakeConsole(void); |
| #endif |
| |
| |
| static void |
| show_server_name(void) |
| { |
| #ifdef WIN32 |
| (void)MakeConsole(); |
| #endif |
| |
| fprintf(stderr, "CivetWeb v%s, built on %s\n", mg_version(), __DATE__); |
| } |
| |
| |
| static NO_RETURN void |
| show_usage_and_exit(const char *exeName) |
| { |
| const struct mg_option *options; |
| int i; |
| |
| if (exeName == 0 || *exeName == 0) { |
| exeName = "civetweb"; |
| } |
| |
| show_server_name(); |
| |
| fprintf(stderr, "\nUsage:\n"); |
| fprintf(stderr, " Start server with a set of options:\n"); |
| fprintf(stderr, " %s [config_file]\n", exeName); |
| fprintf(stderr, " %s [-option value ...]\n", exeName); |
| fprintf(stderr, " Show system information:\n"); |
| fprintf(stderr, " %s -I\n", exeName); |
| fprintf(stderr, " Add user/change password:\n"); |
| fprintf(stderr, |
| " %s -A <htpasswd_file> <realm> <user> <passwd>\n", |
| exeName); |
| fprintf(stderr, " Remove user:\n"); |
| fprintf(stderr, " %s -R <htpasswd_file> <realm> <user>\n", exeName); |
| fprintf(stderr, "\nOPTIONS:\n"); |
| |
| options = mg_get_valid_options(); |
| for (i = 0; options[i].name != NULL; i++) { |
| fprintf(stderr, |
| " -%s %s\n", |
| options[i].name, |
| ((options[i].default_value == NULL) |
| ? "<empty>" |
| : options[i].default_value)); |
| } |
| |
| options = main_config_options; |
| for (i = 0; options[i].name != NULL; i++) { |
| fprintf(stderr, |
| " -%s %s\n", |
| options[i].name, |
| ((options[i].default_value == NULL) |
| ? "<empty>" |
| : options[i].default_value)); |
| } |
| |
| exit(EXIT_FAILURE); |
| } |
| |
| |
| #if defined(_WIN32) || defined(USE_COCOA) |
| static const char *config_file_top_comment = |
| "# Civetweb web server configuration file.\n" |
| "# For detailed description of every option, visit\n" |
| "# https://github.com/civetweb/civetweb/blob/master/docs/UserManual.md\n" |
| "# Lines starting with '#' and empty lines are ignored.\n" |
| "# To make a change, remove leading '#', modify option's value,\n" |
| "# save this file and then restart Civetweb.\n\n"; |
| |
| static const char * |
| get_url_to_first_open_port(const struct mg_context *ctx) |
| { |
| static char url[100]; |
| const char *open_ports = mg_get_option(ctx, "listening_ports"); |
| int a, b, c, d, port, n; |
| |
| if (sscanf(open_ports, "%d.%d.%d.%d:%d%n", &a, &b, &c, &d, &port, &n) |
| == 5) { |
| snprintf(url, |
| sizeof(url), |
| "%s://%d.%d.%d.%d:%d", |
| open_ports[n] == 's' ? "https" : "http", |
| a, |
| b, |
| c, |
| d, |
| port); |
| } else if (sscanf(open_ports, "%d%n", &port, &n) == 1) { |
| snprintf(url, |
| sizeof(url), |
| "%s://localhost:%d", |
| open_ports[n] == 's' ? "https" : "http", |
| port); |
| } else { |
| snprintf(url, sizeof(url), "%s", "http://localhost:8080"); |
| } |
| |
| return url; |
| } |
| |
| |
| #ifdef ENABLE_CREATE_CONFIG_FILE |
| static void |
| create_config_file(const struct mg_context *ctx, const char *path) |
| { |
| const struct mg_option *options; |
| const char *value; |
| FILE *fp; |
| int i; |
| |
| /* Create config file if it is not present yet */ |
| if ((fp = fopen(path, "r")) != NULL) { |
| fclose(fp); |
| } else if ((fp = fopen(path, "a+")) != NULL) { |
| fprintf(fp, "%s", config_file_top_comment); |
| options = mg_get_valid_options(); |
| for (i = 0; options[i].name != NULL; i++) { |
| value = mg_get_option(ctx, options[i].name); |
| fprintf(fp, |
| "# %s %s\n", |
| options[i].name, |
| value ? value : "<value>"); |
| } |
| fclose(fp); |
| } |
| } |
| #endif |
| #endif |
| |
| |
| static char * |
| sdup(const char *str) |
| { |
| size_t len; |
| char *p; |
| |
| len = strlen(str) + 1; |
| if ((p = (char *)malloc(len)) != NULL) { |
| memcpy(p, str, len); |
| } |
| return p; |
| } |
| |
| |
| #if 0 /* Unused code from "string duplicate with escape" */ |
| static unsigned |
| hex2dec(char x) |
| { |
| if ((x >= '0') && (x <= '9')) { |
| return (unsigned)x - (unsigned)'0'; |
| } |
| if ((x >= 'A') && (x <= 'F')) { |
| return (unsigned)x - (unsigned)'A' + 10u; |
| } |
| if ((x >= 'a') && (x <= 'f')) { |
| return (unsigned)x - (unsigned)'a' + 10u; |
| } |
| return 0; |
| } |
| |
| |
| static char * |
| sdupesc(const char *str) |
| { |
| char *p = sdup(str); |
| |
| if (p) { |
| char *d = p; |
| while ((d = strchr(d, '\\')) != NULL) { |
| switch (d[1]) { |
| case 'a': |
| d[0] = '\a'; |
| memmove(d + 1, d + 2, strlen(d + 1)); |
| break; |
| case 'b': |
| d[0] = '\b'; |
| memmove(d + 1, d + 2, strlen(d + 1)); |
| break; |
| case 'e': |
| d[0] = 27; |
| memmove(d + 1, d + 2, strlen(d + 1)); |
| break; |
| case 'f': |
| d[0] = '\f'; |
| memmove(d + 1, d + 2, strlen(d + 1)); |
| break; |
| case 'n': |
| d[0] = '\n'; |
| memmove(d + 1, d + 2, strlen(d + 1)); |
| break; |
| case 'r': |
| d[0] = '\r'; |
| memmove(d + 1, d + 2, strlen(d + 1)); |
| break; |
| case 't': |
| d[0] = '\t'; |
| memmove(d + 1, d + 2, strlen(d + 1)); |
| break; |
| case 'u': |
| if (isxdigit(d[2]) && isxdigit(d[3]) && isxdigit(d[4]) |
| && isxdigit(d[5])) { |
| unsigned short u = (unsigned short)(hex2dec(d[2]) * 4096 |
| + hex2dec(d[3]) * 256 |
| + hex2dec(d[4]) * 16 |
| + hex2dec(d[5])); |
| char mbc[16]; |
| int mbl = wctomb(mbc, (wchar_t)u); |
| if ((mbl > 0) && (mbl < 6)) { |
| memcpy(d, mbc, (unsigned)mbl); |
| memmove(d + mbl, d + 6, strlen(d + 5)); |
| /* Advance mbl characters (+1 is below) */ |
| d += (mbl - 1); |
| } else { |
| /* Invalid multi byte character */ |
| /* TODO: define what to do */ |
| } |
| } else { |
| /* Invalid esc sequence */ |
| /* TODO: define what to do */ |
| } |
| break; |
| case 'v': |
| d[0] = '\v'; |
| memmove(d + 1, d + 2, strlen(d + 1)); |
| break; |
| case 'x': |
| if (isxdigit(d[2]) && isxdigit(d[3])) { |
| d[0] = (char)((unsigned char)(hex2dec(d[2]) * 16 |
| + hex2dec(d[3]))); |
| memmove(d + 1, d + 4, strlen(d + 3)); |
| } else { |
| /* Invalid esc sequence */ |
| /* TODO: define what to do */ |
| } |
| break; |
| case 'z': |
| d[0] = 0; |
| memmove(d + 1, d + 2, strlen(d + 1)); |
| break; |
| case '\\': |
| d[0] = '\\'; |
| memmove(d + 1, d + 2, strlen(d + 1)); |
| break; |
| case '\'': |
| d[0] = '\''; |
| memmove(d + 1, d + 2, strlen(d + 1)); |
| break; |
| case '\"': |
| d[0] = '\"'; |
| memmove(d + 1, d + 2, strlen(d + 1)); |
| break; |
| case 0: |
| if (d == p) { |
| /* Line is only \ */ |
| free(p); |
| return NULL; |
| } |
| /* no break */ |
| default: |
| /* invalid ESC sequence */ |
| /* TODO: define what to do */ |
| break; |
| } |
| |
| /* Advance to next character */ |
| d++; |
| } |
| } |
| return p; |
| } |
| #endif |
| |
| |
| static const char * |
| get_option(char **options, const char *option_name) |
| { |
| int i = 0; |
| const char *opt_value = NULL; |
| |
| /* TODO (low, api makeover): options should be an array of key-value-pairs, |
| * like |
| * struct {const char * key, const char * value} options[] |
| * but it currently is an array with |
| * options[2*i] = key, options[2*i + 1] = value |
| * (probably with a MG_LEGACY_INTERFACE definition) |
| */ |
| while (options[2 * i] != NULL) { |
| if (strcmp(options[2 * i], option_name) == 0) { |
| opt_value = options[2 * i + 1]; |
| break; |
| } |
| i++; |
| } |
| return opt_value; |
| } |
| |
| |
| static int |
| set_option(char **options, const char *name, const char *value) |
| { |
| int i, type; |
| const struct mg_option *default_options = mg_get_valid_options(); |
| const char *multi_sep = NULL; |
| |
| for (i = 0; main_config_options[i].name != NULL; i++) { |
| if (0 == strcmp(name, main_config_options[i].name)) { |
| /* This option is evaluated by main.c, not civetweb.c - just skip it |
| * and return OK */ |
| return 1; |
| } |
| } |
| |
| type = CONFIG_TYPE_UNKNOWN; |
| for (i = 0; default_options[i].name != NULL; i++) { |
| if (!strcmp(default_options[i].name, name)) { |
| type = default_options[i].type; |
| } |
| } |
| switch (type) { |
| case CONFIG_TYPE_UNKNOWN: |
| /* unknown option */ |
| return 0; |
| case CONFIG_TYPE_NUMBER: |
| /* integer number >= 0, e.g. number of threads */ |
| if (atol(value) < 0) { |
| /* invalid number */ |
| return 0; |
| } |
| break; |
| case CONFIG_TYPE_STRING: |
| /* any text */ |
| break; |
| case CONFIG_TYPE_STRING_LIST: |
| /* list of text items, separated by , */ |
| multi_sep = ","; |
| break; |
| case CONFIG_TYPE_STRING_MULTILINE: |
| /* lines of text, separated by carriage return line feed */ |
| multi_sep = "\r\n"; |
| break; |
| case CONFIG_TYPE_BOOLEAN: |
| /* boolean value, yes or no */ |
| if ((0 != strcmp(value, "yes")) && (0 != strcmp(value, "no"))) { |
| /* invalid boolean */ |
| return 0; |
| } |
| break; |
| case CONFIG_TYPE_FILE: |
| case CONFIG_TYPE_DIRECTORY: |
| /* TODO (low): check this option when it is set, instead of calling |
| * verify_existence later */ |
| break; |
| case CONFIG_TYPE_EXT_PATTERN: |
| /* list of patterns, separated by | */ |
| multi_sep = "|"; |
| break; |
| default: |
| die("Unknown option type - option %s", name); |
| } |
| |
| for (i = 0; i < MAX_OPTIONS; i++) { |
| if (options[2 * i] == NULL) { |
| /* Option not set yet. Add new option */ |
| options[2 * i] = sdup(name); |
| options[2 * i + 1] = sdup(value); |
| options[2 * i + 2] = NULL; |
| break; |
| } else if (!strcmp(options[2 * i], name)) { |
| if (multi_sep) { |
| /* Option already set. Overwrite */ |
| char *s = malloc(strlen(options[2 * i + 1]) + strlen(multi_sep) |
| + strlen(value) + 1); |
| if (!s) { |
| die("Out of memory"); |
| } |
| sprintf(s, "%s%s%s", options[2 * i + 1], multi_sep, value); |
| free(options[2 * i + 1]); |
| options[2 * i + 1] = s; |
| } else { |
| /* Option already set. Overwrite */ |
| free(options[2 * i + 1]); |
| options[2 * i + 1] = sdup(value); |
| } |
| break; |
| } |
| } |
| |
| if (i == MAX_OPTIONS) { |
| die("Too many options specified"); |
| } |
| |
| if (options[2 * i] == NULL) { |
| die("Out of memory"); |
| } |
| if (options[2 * i + 1] == NULL) { |
| die("Illegal escape sequence, or out of memory"); |
| } |
| |
| /* option set correctly */ |
| return 1; |
| } |
| |
| |
| static int |
| read_config_file(const char *config_file, char **options) |
| { |
| char line[MAX_CONF_FILE_LINE_SIZE], *p; |
| FILE *fp = NULL; |
| size_t i, j, line_no = 0; |
| |
| /* Open the config file */ |
| fp = fopen(config_file, "r"); |
| if (fp == NULL) { |
| /* Failed to open the file. Keep errno for the caller. */ |
| return 0; |
| } |
| |
| /* Load config file settings first */ |
| if (fp != NULL) { |
| fprintf(stderr, "Loading config file %s\n", config_file); |
| |
| /* Loop over the lines in config file */ |
| while (fgets(line, sizeof(line), fp) != NULL) { |
| |
| if (!line_no && !memcmp(line, "\xEF\xBB\xBF", 3)) { |
| /* strip UTF-8 BOM */ |
| p = line + 3; |
| } else { |
| p = line; |
| } |
| line_no++; |
| |
| /* Ignore empty lines and comments */ |
| for (i = 0; isspace(*(unsigned char *)&line[i]);) |
| i++; |
| if (p[i] == '#' || p[i] == '\0') { |
| continue; |
| } |
| |
| /* Skip spaces, \r and \n at the end of the line */ |
| for (j = strlen(line) - 1; |
| isspace(*(unsigned char *)&line[j]) |
| || iscntrl(*(unsigned char *)&line[j]);) |
| line[j--] = 0; |
| |
| /* Find the space character between option name and value */ |
| for (j = i; !isspace(*(unsigned char *)&line[j]) && (line[j] != 0);) |
| j++; |
| |
| /* Terminate the string - then the string at (line+i) contains the |
| * option name */ |
| line[j] = 0; |
| j++; |
| |
| /* Trim additional spaces between option name and value - then |
| * (line+j) contains the option value */ |
| while (isspace(line[j])) { |
| j++; |
| } |
| |
| /* Set option */ |
| if (!set_option(options, line + i, line + j)) { |
| fprintf(stderr, |
| "%s: line %d is invalid, ignoring it:\n %s", |
| config_file, |
| (int)line_no, |
| p); |
| } |
| } |
| |
| (void)fclose(fp); |
| } |
| return 1; |
| } |
| |
| |
| static void |
| process_command_line_arguments(int argc, char *argv[], char **options) |
| { |
| char *p; |
| size_t i, cmd_line_opts_start = 1; |
| #ifdef CONFIG_FILE2 |
| FILE *fp = NULL; |
| #endif |
| |
| /* Should we use a config file ? */ |
| if ((argc > 1) && (argv[1] != NULL) && (argv[1][0] != '-') |
| && (argv[1][0] != 0)) { |
| /* The first command line parameter is a config file name. */ |
| snprintf(g_config_file_name, |
| sizeof(g_config_file_name) - 1, |
| "%s", |
| argv[1]); |
| cmd_line_opts_start = 2; |
| } else if ((p = strrchr(argv[0], DIRSEP)) == NULL) { |
| /* No config file set. No path in arg[0] found. |
| * Use default file name in the current path. */ |
| snprintf(g_config_file_name, |
| sizeof(g_config_file_name) - 1, |
| "%s", |
| CONFIG_FILE); |
| } else { |
| /* No config file set. Path to exe found in arg[0]. |
| * Use default file name next to the executable. */ |
| snprintf(g_config_file_name, |
| sizeof(g_config_file_name) - 1, |
| "%.*s%c%s", |
| (int)(p - argv[0]), |
| argv[0], |
| DIRSEP, |
| CONFIG_FILE); |
| } |
| g_config_file_name[sizeof(g_config_file_name) - 1] = 0; |
| |
| #ifdef CONFIG_FILE2 |
| fp = fopen(g_config_file_name, "r"); |
| |
| /* try alternate config file */ |
| if (fp == NULL) { |
| fp = fopen(CONFIG_FILE2, "r"); |
| if (fp != NULL) { |
| strcpy(g_config_file_name, CONFIG_FILE2); |
| } |
| } |
| if (fp != NULL) { |
| fclose(fp); |
| } |
| #endif |
| |
| /* read all configurations from a config file */ |
| if (0 == read_config_file(g_config_file_name, options)) { |
| if (cmd_line_opts_start == 2) { |
| /* If config file was set in command line and open failed, die. */ |
| /* Errno will still hold the error from fopen. */ |
| die("Cannot open config file %s: %s", |
| g_config_file_name, |
| strerror(errno)); |
| } |
| /* Otherwise: CivetWeb can work without a config file */ |
| } |
| |
| /* 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(argv[0]); |
| } |
| if (!set_option(options, &argv[i][1], argv[i + 1])) { |
| fprintf( |
| stderr, |
| "command line option is invalid, ignoring it:\n %s %s\n", |
| argv[i], |
| argv[i + 1]); |
| } |
| } |
| } |
| } |
| |
| |
| static void |
| init_server_name(int argc, const char *argv[]) |
| { |
| int i; |
| assert(sizeof(main_config_options) / sizeof(main_config_options[0]) |
| == NUM_MAIN_OPTIONS + 1); |
| assert((strlen(mg_version()) + 12) < sizeof(g_server_base_name)); |
| snprintf(g_server_base_name, |
| sizeof(g_server_base_name), |
| "CivetWeb V%s", |
| mg_version()); |
| |
| g_server_name = g_server_base_name; |
| for (i = 0; i < argc - 1; i++) { |
| if ((argv[i][0] == '-') |
| && (0 == strcmp(argv[i] + 1, |
| main_config_options[OPTION_TITLE].name))) { |
| g_server_name = (const char *)(argv[i + 1]); |
| } |
| } |
| |
| g_icon_name = NULL; |
| for (i = 0; i < argc - 1; i++) { |
| if ((argv[i][0] == '-') |
| && (0 == strcmp(argv[i] + 1, |
| main_config_options[OPTION_ICON].name))) { |
| g_icon_name = (const char *)(argv[i + 1]); |
| } |
| } |
| |
| g_website = "http://civetweb.github.io/civetweb/"; |
| for (i = 0; i < argc - 1; i++) { |
| if ((argv[i][0] == '-') |
| && (0 == strcmp(argv[i] + 1, |
| main_config_options[OPTION_WEBPAGE].name))) { |
| g_website = (const char *)(argv[i + 1]); |
| } |
| } |
| } |
| |
| |
| static void |
| init_system_info(void) |
| { |
| int len = mg_get_system_info(NULL, 0); |
| if (len > 0) { |
| g_system_info = (char *)malloc((unsigned)len + 1); |
| (void)mg_get_system_info(g_system_info, len + 1); |
| } else { |
| g_system_info = sdup("Not available"); |
| } |
| } |
| |
| |
| static void |
| free_system_info(void) |
| { |
| free(g_system_info); |
| } |
| |
| |
| static int |
| log_message(const struct mg_connection *conn, const char *message) |
| { |
| const struct mg_context *ctx = mg_get_context(conn); |
| struct tuser_data *ud = (struct tuser_data *)mg_get_user_data(ctx); |
| |
| fprintf(stderr, "%s\n", message); |
| |
| if (ud->first_message == NULL) { |
| ud->first_message = sdup(message); |
| } |
| |
| return 0; |
| } |
| |
| |
| 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 void |
| verify_existence(char **options, const char *option_name, int must_be_dir) |
| { |
| struct stat st; |
| const char *path = get_option(options, option_name); |
| |
| #ifdef _WIN32 |
| wchar_t wbuf[1024]; |
| char mbbuf[1024]; |
| int len; |
| |
| if (path) { |
| memset(wbuf, 0, sizeof(wbuf)); |
| memset(mbbuf, 0, sizeof(mbbuf)); |
| len = MultiByteToWideChar(CP_UTF8, |
| 0, |
| path, |
| -1, |
| wbuf, |
| (int)sizeof(wbuf) / sizeof(wbuf[0]) - 1); |
| wcstombs(mbbuf, wbuf, sizeof(mbbuf) - 1); |
| path = mbbuf; |
| (void)len; |
| } |
| #endif |
| |
| if (path != NULL && (stat(path, &st) != 0 |
| || ((S_ISDIR(st.st_mode) ? 1 : 0) != must_be_dir))) { |
| die("Invalid path for %s: [%s]: (%s). Make sure that path is either " |
| "absolute, or it is relative to civetweb executable.", |
| option_name, |
| path, |
| strerror(errno)); |
| } |
| } |
| |
| |
| static void |
| set_absolute_path(char *options[], |
| const char *option_name, |
| const char *path_to_civetweb_exe) |
| { |
| char path[PATH_MAX] = "", absolute[PATH_MAX] = ""; |
| const char *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 civetweb executable lives |
| be the relative directory for everything. |
| Extract civetweb executable directory into path. */ |
| if ((p = strrchr(path_to_civetweb_exe, DIRSEP)) == NULL) { |
| IGNORE_UNUSED_RESULT(getcwd(path, sizeof(path))); |
| } else { |
| snprintf(path, |
| sizeof(path) - 1, |
| "%.*s", |
| (int)(p - path_to_civetweb_exe), |
| path_to_civetweb_exe); |
| path[sizeof(path) - 1] = 0; |
| } |
| |
| strncat(path, "/", sizeof(path) - strlen(path) - 1); |
| strncat(path, option_value, sizeof(path) - strlen(path) - 1); |
| |
| /* Absolutize the path, and set the option */ |
| IGNORE_UNUSED_RESULT(abs_path(path, absolute, sizeof(absolute))); |
| set_option(options, option_name, absolute); |
| } |
| } |
| |
| |
| #ifdef USE_LUA |
| |
| #include "civetweb_private_lua.h" |
| |
| #endif |
| |
| |
| #ifdef USE_DUKTAPE |
| |
| #include "duktape.h" |
| |
| static int |
| run_duktape(const char *file_name) |
| { |
| duk_context *ctx = NULL; |
| |
| ctx = duk_create_heap_default(); |
| if (!ctx) { |
| fprintf(stderr, "Failed to create a Duktape heap.\n"); |
| goto finished; |
| } |
| |
| if (duk_peval_file(ctx, file_name) != 0) { |
| fprintf(stderr, "%s\n", duk_safe_to_string(ctx, -1)); |
| goto finished; |
| } |
| duk_pop(ctx); /* ignore result */ |
| |
| finished: |
| duk_destroy_heap(ctx); |
| |
| return 0; |
| } |
| #endif |
| |
| |
| #if defined(__MINGW32__) || defined(__MINGW64__) |
| /* For __MINGW32/64_MAJOR/MINOR_VERSION define */ |
| #include <_mingw.h> |
| #endif |
| |
| |
| static void |
| start_civetweb(int argc, char *argv[]) |
| { |
| struct mg_callbacks callbacks; |
| char *options[2 * MAX_OPTIONS + 1]; |
| int i; |
| |
| /* Start option -I: |
| * Show system information and exit |
| * This is very useful for diagnosis. */ |
| if (argc > 1 && !strcmp(argv[1], "-I")) { |
| |
| #ifdef WIN32 |
| (void)MakeConsole(); |
| #endif |
| fprintf(stdout, |
| "\n%s (%s)\n%s\n", |
| g_server_base_name, |
| g_server_name, |
| g_system_info); |
| |
| exit(EXIT_SUCCESS); |
| } |
| |
| /* Edit passwords file: Add user or change password, if -A option is |
| * specified */ |
| if (argc > 1 && !strcmp(argv[1], "-A")) { |
| if (argc != 6) { |
| show_usage_and_exit(argv[0]); |
| } |
| exit(mg_modify_passwords_file(argv[2], argv[3], argv[4], argv[5]) |
| ? EXIT_SUCCESS |
| : EXIT_FAILURE); |
| } |
| |
| /* Edit passwords file: Remove user, if -R option is specified */ |
| if (argc > 1 && !strcmp(argv[1], "-R")) { |
| if (argc != 5) { |
| show_usage_and_exit(argv[0]); |
| } |
| exit(mg_modify_passwords_file(argv[2], argv[3], argv[4], NULL) |
| ? EXIT_SUCCESS |
| : EXIT_FAILURE); |
| } |
| |
| /* Call Lua with additional CivetWeb specific Lua functions, if -L option |
| * is specified */ |
| if (argc > 1 && !strcmp(argv[1], "-L")) { |
| |
| #ifdef USE_LUA |
| if (argc != 3) { |
| show_usage_and_exit(argv[0]); |
| } |
| #ifdef WIN32 |
| (void)MakeConsole(); |
| #endif |
| exit(run_lua(argv[2])); |
| #else |
| show_server_name(); |
| fprintf(stderr, "\nError: Lua support not enabled\n"); |
| exit(EXIT_FAILURE); |
| #endif |
| } |
| |
| /* Call Duktape, if -E option is specified */ |
| if (argc > 1 && !strcmp(argv[1], "-E")) { |
| |
| #ifdef USE_DUKTAPE |
| if (argc != 3) { |
| show_usage_and_exit(argv[0]); |
| } |
| #ifdef WIN32 |
| (void)MakeConsole(); |
| #endif |
| exit(run_duktape(argv[2])); |
| #else |
| show_server_name(); |
| fprintf(stderr, "\nError: Ecmascript support not enabled\n"); |
| exit(EXIT_FAILURE); |
| #endif |
| } |
| |
| /* Show usage if -h or --help options are specified */ |
| if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "-H") |
| || !strcmp(argv[1], "--help"))) { |
| show_usage_and_exit(argv[0]); |
| } |
| |
| options[0] = NULL; |
| set_option(options, "document_root", "."); |
| |
| /* Update config based on command line arguments */ |
| process_command_line_arguments(argc, argv, options); |
| |
| /* Make sure we have absolute paths for files and directories */ |
| set_absolute_path(options, "document_root", argv[0]); |
| set_absolute_path(options, "put_delete_auth_file", argv[0]); |
| set_absolute_path(options, "cgi_interpreter", argv[0]); |
| set_absolute_path(options, "access_log_file", argv[0]); |
| set_absolute_path(options, "error_log_file", argv[0]); |
| set_absolute_path(options, "global_auth_file", argv[0]); |
| #ifdef USE_LUA |
| set_absolute_path(options, "lua_preload_file", argv[0]); |
| #endif |
| set_absolute_path(options, "ssl_certificate", argv[0]); |
| |
| /* Make extra verification for certain options */ |
| verify_existence(options, "document_root", 1); |
| verify_existence(options, "cgi_interpreter", 0); |
| verify_existence(options, "ssl_certificate", 0); |
| verify_existence(options, "ssl_ca_path", 1); |
| verify_existence(options, "ssl_ca_file", 0); |
| #ifdef USE_LUA |
| verify_existence(options, "lua_preload_file", 0); |
| #endif |
| |
| /* Setup signal handler: quit on Ctrl-C */ |
| signal(SIGTERM, signal_handler); |
| signal(SIGINT, signal_handler); |
| |
| /* Initialize user data */ |
| memset(&g_user_data, 0, sizeof(g_user_data)); |
| |
| /* Start Civetweb */ |
| memset(&callbacks, 0, sizeof(callbacks)); |
| callbacks.log_message = &log_message; |
| g_ctx = mg_start(&callbacks, &g_user_data, (const char **)options); |
| |
| /* mg_start copies all options to an internal buffer. |
| * The options data field here is not required anymore. */ |
| for (i = 0; options[i] != NULL; i++) { |
| free(options[i]); |
| } |
| |
| /* If mg_start fails, it returns NULL */ |
| if (g_ctx == NULL) { |
| die("Failed to start %s:\n%s", |
| g_server_name, |
| ((g_user_data.first_message == NULL) ? "unknown reason" |
| : g_user_data.first_message)); |
| } |
| } |
| |
| |
| static void |
| stop_civetweb(void) |
| { |
| mg_stop(g_ctx); |
| free(g_user_data.first_message); |
| g_user_data.first_message = NULL; |
| } |
| |
| |
| #ifdef _WIN32 |
| /* Win32 has a small GUI. |
| * Define some GUI elements and Windows message handlers. */ |
| |
| enum { |
| ID_ICON = 100, |
| ID_QUIT, |
| ID_SETTINGS, |
| ID_SEPARATOR, |
| ID_INSTALL_SERVICE, |
| ID_REMOVE_SERVICE, |
| ID_STATIC, |
| ID_GROUP, |
| ID_PASSWORD, |
| ID_SAVE, |
| ID_RESET_DEFAULTS, |
| ID_RESET_FILE, |
| ID_RESET_ACTIVE, |
| ID_STATUS, |
| ID_CONNECT, |
| ID_ADD_USER, |
| ID_ADD_USER_NAME, |
| ID_ADD_USER_REALM, |
| ID_INPUT_LINE, |
| ID_SYSINFO, |
| ID_WEBSITE, |
| |
| /* All dynamically created text boxes for options have IDs starting from |
| ID_CONTROLS, incremented by one. */ |
| ID_CONTROLS = 200, |
| |
| /* Text boxes for files have "..." buttons to open file browser. These |
| buttons have IDs that are ID_FILE_BUTTONS_DELTA higher than associated |
| text box ID. */ |
| ID_FILE_BUTTONS_DELTA = 1000 |
| }; |
| |
| |
| static HICON hIcon; |
| static SERVICE_STATUS ss; |
| static SERVICE_STATUS_HANDLE hStatus; |
| static const char *service_magic_argument = "--"; |
| static NOTIFYICONDATA TrayIcon; |
| |
| static void WINAPI |
| ControlHandler(DWORD code) |
| { |
| if (code == SERVICE_CONTROL_STOP || code == SERVICE_CONTROL_SHUTDOWN) { |
| ss.dwWin32ExitCode = 0; |
| ss.dwCurrentState = SERVICE_STOPPED; |
| } |
| SetServiceStatus(hStatus, &ss); |
| } |
| |
| |
| static void WINAPI |
| ServiceMain(void) |
| { |
| ss.dwServiceType = SERVICE_WIN32; |
| ss.dwCurrentState = SERVICE_RUNNING; |
| ss.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; |
| |
| hStatus = RegisterServiceCtrlHandler(g_server_name, ControlHandler); |
| SetServiceStatus(hStatus, &ss); |
| |
| while (ss.dwCurrentState == SERVICE_RUNNING) { |
| Sleep(1000); |
| } |
| stop_civetweb(); |
| |
| ss.dwCurrentState = SERVICE_STOPPED; |
| ss.dwWin32ExitCode = (DWORD)-1; |
| SetServiceStatus(hStatus, &ss); |
| } |
| |
| |
| static void |
| show_error(void) |
| { |
| char buf[256]; |
| FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, |
| NULL, |
| GetLastError(), |
| MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |
| buf, |
| sizeof(buf), |
| NULL); |
| MessageBox(NULL, buf, "Error", MB_OK); |
| } |
| |
| |
| static void * |
| align(void *ptr, uintptr_t alig) |
| { |
| uintptr_t ul = (uintptr_t)ptr; |
| ul += alig; |
| ul &= ~alig; |
| return ((void *)ul); |
| } |
| |
| |
| static void |
| save_config(HWND hDlg, FILE *fp) |
| { |
| char value[2000] = ""; |
| const char *default_value; |
| const struct mg_option *options; |
| int i, id; |
| |
| fprintf(fp, "%s", config_file_top_comment); |
| options = mg_get_valid_options(); |
| for (i = 0; options[i].name != NULL; i++) { |
| id = ID_CONTROLS + i; |
| if (options[i].type == CONFIG_TYPE_BOOLEAN) { |
| snprintf(value, |
| sizeof(value) - 1, |
| "%s", |
| IsDlgButtonChecked(hDlg, id) ? "yes" : "no"); |
| value[sizeof(value) - 1] = 0; |
| } else { |
| GetDlgItemText(hDlg, id, value, sizeof(value)); |
| } |
| default_value = |
| options[i].default_value == NULL ? "" : options[i].default_value; |
| /* If value is the same as default, skip it */ |
| if (strcmp(value, default_value) != 0) { |
| fprintf(fp, "%s %s\n", options[i].name, value); |
| } |
| } |
| } |
| |
| |
| /* LPARAM pointer passed to WM_INITDIALOG */ |
| struct dlg_proc_param { |
| int guard; |
| HWND hWnd; |
| const char *name; |
| char *buffer; |
| unsigned buflen; |
| int idRetry; |
| BOOL (*fRetry)(struct dlg_proc_param *data); |
| }; |
| |
| |
| /* Dialog proc for settings dialog */ |
| static INT_PTR CALLBACK |
| SettingsDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) |
| { |
| FILE *fp; |
| int i, j; |
| const char *name, *value; |
| const struct mg_option *default_options = mg_get_valid_options(); |
| char *file_options[MAX_OPTIONS * 2 + 1] = {0}; |
| char *title; |
| struct dlg_proc_param *pdlg_proc_param; |
| |
| switch (msg) { |
| |
| case WM_CLOSE: |
| DestroyWindow(hDlg); |
| break; |
| |
| case WM_COMMAND: |
| switch (LOWORD(wParam)) { |
| |
| case ID_SAVE: |
| EnableWindow(GetDlgItem(hDlg, ID_SAVE), FALSE); |
| if ((fp = fopen(g_config_file_name, "w+")) != NULL) { |
| save_config(hDlg, fp); |
| fclose(fp); |
| stop_civetweb(); |
| start_civetweb(__argc, __argv); |
| } |
| EnableWindow(GetDlgItem(hDlg, ID_SAVE), TRUE); |
| break; |
| |
| case ID_RESET_DEFAULTS: |
| for (i = 0; default_options[i].name != NULL; i++) { |
| name = default_options[i].name; |
| value = default_options[i].default_value == NULL |
| ? "" |
| : default_options[i].default_value; |
| if (default_options[i].type == CONFIG_TYPE_BOOLEAN) { |
| CheckDlgButton(hDlg, |
| ID_CONTROLS + i, |
| !strcmp(value, "yes") ? BST_CHECKED |
| : BST_UNCHECKED); |
| } else { |
| SetWindowText(GetDlgItem(hDlg, ID_CONTROLS + i), value); |
| } |
| } |
| break; |
| |
| case ID_RESET_FILE: |
| read_config_file(g_config_file_name, file_options); |
| for (i = 0; default_options[i].name != NULL; i++) { |
| name = default_options[i].name; |
| value = default_options[i].default_value; |
| for (j = 0; file_options[j * 2] != NULL; j++) { |
| if (!strcmp(name, file_options[j * 2])) { |
| value = file_options[j * 2 + 1]; |
| } |
| } |
| if (value == NULL) { |
| value = ""; |
| } |
| if (default_options[i].type == CONFIG_TYPE_BOOLEAN) { |
| CheckDlgButton(hDlg, |
| ID_CONTROLS + i, |
| !strcmp(value, "yes") ? BST_CHECKED |
| : BST_UNCHECKED); |
| } else { |
| SetWindowText(GetDlgItem(hDlg, ID_CONTROLS + i), value); |
| } |
| } |
| for (i = 0; i < MAX_OPTIONS; i++) { |
| free(file_options[2 * i]); |
| free(file_options[2 * i + 1]); |
| } |
| break; |
| |
| case ID_RESET_ACTIVE: |
| for (i = 0; default_options[i].name != NULL; i++) { |
| name = default_options[i].name; |
| value = mg_get_option(g_ctx, name); |
| if (default_options[i].type == CONFIG_TYPE_BOOLEAN) { |
| CheckDlgButton(hDlg, |
| ID_CONTROLS + i, |
| !strcmp(value, "yes") ? BST_CHECKED |
| : BST_UNCHECKED); |
| } else { |
| SetDlgItemText(hDlg, |
| ID_CONTROLS + i, |
| value == NULL ? "" : value); |
| } |
| } |
| break; |
| } |
| |
| for (i = 0; default_options[i].name != NULL; i++) { |
| name = default_options[i].name; |
| if (((default_options[i].type == CONFIG_TYPE_FILE) |
| || (default_options[i].type == CONFIG_TYPE_DIRECTORY)) |
| && LOWORD(wParam) == ID_CONTROLS + i + ID_FILE_BUTTONS_DELTA) { |
| OPENFILENAME of; |
| BROWSEINFO bi; |
| char path[PATH_MAX] = ""; |
| |
| memset(&of, 0, sizeof(of)); |
| of.lStructSize = sizeof(of); |
| of.hwndOwner = (HWND)hDlg; |
| of.lpstrFile = path; |
| of.nMaxFile = sizeof(path); |
| of.lpstrInitialDir = mg_get_option(g_ctx, "document_root"); |
| of.Flags = |
| OFN_CREATEPROMPT | OFN_NOCHANGEDIR | OFN_HIDEREADONLY; |
| |
| memset(&bi, 0, sizeof(bi)); |
| bi.hwndOwner = (HWND)hDlg; |
| bi.lpszTitle = "Choose WWW root directory:"; |
| bi.ulFlags = BIF_RETURNONLYFSDIRS; |
| |
| if (default_options[i].type == CONFIG_TYPE_DIRECTORY) { |
| SHGetPathFromIDList(SHBrowseForFolder(&bi), path); |
| } else { |
| GetOpenFileName(&of); |
| } |
| |
| if (path[0] != '\0') { |
| SetWindowText(GetDlgItem(hDlg, ID_CONTROLS + i), path); |
| } |
| } |
| } |
| break; |
| |
| case WM_INITDIALOG: |
| /* Store hWnd in a parameter accessible by the parent, so we can |
| * bring this window to front if required. */ |
| pdlg_proc_param = (struct dlg_proc_param *)lParam; |
| pdlg_proc_param->hWnd = hDlg; |
| |
| /* Initialize the dialog elements */ |
| SendMessage(hDlg, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)hIcon); |
| SendMessage(hDlg, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)hIcon); |
| title = (char *)malloc(strlen(g_server_name) + 16); |
| if (title) { |
| strcpy(title, g_server_name); |
| strcat(title, " settings"); |
| SetWindowText(hDlg, title); |
| free(title); |
| } |
| SetFocus(GetDlgItem(hDlg, ID_SAVE)); |
| |
| /* Init dialog with active settings */ |
| SendMessage(hDlg, WM_COMMAND, ID_RESET_ACTIVE, 0); |
| /* alternative: SendMessage(hDlg, WM_COMMAND, ID_RESET_FILE, 0); */ |
| break; |
| |
| default: |
| break; |
| } |
| |
| return FALSE; |
| } |
| |
| |
| /* Dialog proc for input dialog */ |
| static INT_PTR CALLBACK |
| InputDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) |
| { |
| static struct dlg_proc_param *inBuf = 0; |
| WORD ctrlId; |
| HWND hIn; |
| |
| switch (msg) { |
| case WM_CLOSE: |
| inBuf = 0; |
| DestroyWindow(hDlg); |
| break; |
| |
| case WM_COMMAND: |
| ctrlId = LOWORD(wParam); |
| if (ctrlId == IDOK) { |
| /* Get handle of input line */ |
| hIn = GetDlgItem(hDlg, ID_INPUT_LINE); |
| |
| if (hIn) { |
| /* Get content of input line */ |
| GetWindowText(hIn, inBuf->buffer, (int)inBuf->buflen); |
| if (strlen(inBuf->buffer) > 0) { |
| /* Input dialog is not empty. */ |
| EndDialog(hDlg, IDOK); |
| } |
| } else { |
| /* There is no input line in this dialog. */ |
| EndDialog(hDlg, IDOK); |
| } |
| |
| } else if (ctrlId == IDRETRY) { |
| |
| /* Get handle of input line */ |
| hIn = GetDlgItem(hDlg, inBuf->idRetry); |
| |
| if (hIn) { |
| /* Load current string */ |
| GetWindowText(hIn, inBuf->buffer, (int)inBuf->buflen); |
| if (inBuf->fRetry) { |
| if (inBuf->fRetry(inBuf)) { |
| SetWindowText(hIn, inBuf->buffer); |
| } |
| } |
| } |
| |
| } else if (ctrlId == IDCANCEL) { |
| EndDialog(hDlg, IDCANCEL); |
| } |
| break; |
| |
| case WM_INITDIALOG: |
| /* Get handle of input line */ |
| hIn = GetDlgItem(hDlg, ID_INPUT_LINE); |
| |
| /* Get dialog parameters */ |
| inBuf = (struct dlg_proc_param *)lParam; |
| |
| /* Set dialog handle for the caller */ |
| inBuf->hWnd = hDlg; |
| |
| /* Set dialog name */ |
| SetWindowText(hDlg, inBuf->name); |
| |
| if (hIn) { |
| /* This is an input dialog */ |
| assert(inBuf != NULL); |
| assert((inBuf->buffer != NULL) && (inBuf->buflen != 0)); |
| assert(strlen(inBuf->buffer) < inBuf->buflen); |
| SendMessage(hDlg, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)hIcon); |
| SendMessage(hDlg, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)hIcon); |
| SendMessage(hIn, EM_LIMITTEXT, inBuf->buflen - 1, 0); |
| SetWindowText(hIn, inBuf->buffer); |
| SetFocus(hIn); |
| } |
| |
| break; |
| |
| default: |
| break; |
| } |
| |
| return FALSE; |
| } |
| |
| |
| static void |
| suggest_passwd(char *passwd) |
| { |
| unsigned u; |
| char *p; |
| union { |
| FILETIME ft; |
| LARGE_INTEGER li; |
| } num; |
| |
| /* valid characters are 32 to 126 */ |
| GetSystemTimeAsFileTime(&num.ft); |
| num.li.HighPart |= (LONG)GetCurrentProcessId(); |
| p = passwd; |
| while (num.li.QuadPart) { |
| u = (unsigned)(num.li.QuadPart % 95); |
| num.li.QuadPart -= u; |
| num.li.QuadPart /= 95; |
| *p = (char)(u + 32); |
| p++; |
| } |
| } |
| |
| |
| static void add_control(unsigned char **mem, |
| DLGTEMPLATE *dia, |
| WORD type, |
| WORD id, |
| DWORD style, |
| short x, |
| short y, |
| short cx, |
| short cy, |
| const char *caption); |
| |
| |
| static int |
| get_password(const char *user, |
| const char *realm, |
| char *passwd, |
| unsigned passwd_len) |
| { |
| #define HEIGHT (15) |
| #define WIDTH (280) |
| #define LABEL_WIDTH (90) |
| |
| unsigned char mem[4096], *p; |
| DLGTEMPLATE *dia = (DLGTEMPLATE *)mem; |
| int ok; |
| short y; |
| static struct dlg_proc_param s_dlg_proc_param; |
| |
| static struct { |
| DLGTEMPLATE dlg_template; /* 18 bytes */ |
| WORD menu, dlg_class; |
| wchar_t caption[1]; |
| WORD fontsiz; |
| wchar_t fontface[7]; |
| } dialog_header = {{WS_CAPTION | WS_POPUP | WS_SYSMENU | WS_VISIBLE |
| | DS_SETFONT | WS_DLGFRAME, |
| WS_EX_TOOLWINDOW, |
| 0, |
| 200, |
| 200, |
| WIDTH, |
| 0}, |
| 0, |
| 0, |
| L"", |
| 8, |
| L"Tahoma"}; |
| |
| assert((user != NULL) && (realm != NULL) && (passwd != NULL)); |
| |
| /* Only allow one instance of this dialog to be open. */ |
| if (s_dlg_proc_param.guard == 0) { |
| memset(&s_dlg_proc_param, 0, sizeof(s_dlg_proc_param)); |
| s_dlg_proc_param.guard = 1; |
| } else { |
| SetForegroundWindow(s_dlg_proc_param.hWnd); |
| return 0; |
| } |
| |
| /* Do not open a password dialog, if the username is empty */ |
| if (user[0] == 0) { |
| s_dlg_proc_param.guard = 0; |
| return 0; |
| } |
| |
| /* Create a password suggestion */ |
| memset(passwd, 0, passwd_len); |
| suggest_passwd(passwd); |
| |
| /* Make buffer available for input dialog */ |
| s_dlg_proc_param.buffer = passwd; |
| s_dlg_proc_param.buflen = passwd_len; |
| |
| /* Create the dialog */ |
| (void)memset(mem, 0, sizeof(mem)); |
| (void)memcpy(mem, &dialog_header, sizeof(dialog_header)); |
| p = mem + sizeof(dialog_header); |
| |
| y = HEIGHT; |
| add_control(&p, |
| dia, |
| 0x82, |
| ID_STATIC, |
| WS_VISIBLE | WS_CHILD, |
| 10, |
| y, |
| LABEL_WIDTH, |
| HEIGHT, |
| "User:"); |
| add_control(&p, |
| dia, |
| 0x81, |
| ID_CONTROLS + 1, |
| WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL |
| | ES_READONLY, |
| 15 + LABEL_WIDTH, |
| y, |
| WIDTH - LABEL_WIDTH - 25, |
| HEIGHT, |
| user); |
| |
| y += HEIGHT; |
| add_control(&p, |
| dia, |
| 0x82, |
| ID_STATIC, |
| WS_VISIBLE | WS_CHILD, |
| 10, |
| y, |
| LABEL_WIDTH, |
| HEIGHT, |
| "Realm:"); |
| add_control(&p, |
| dia, |
| 0x81, |
| ID_CONTROLS + 2, |
| WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL |
| | ES_READONLY, |
| 15 + LABEL_WIDTH, |
| y, |
| WIDTH - LABEL_WIDTH - 25, |
| HEIGHT, |
| realm); |
| |
| y += HEIGHT; |
| add_control(&p, |
| dia, |
| 0x82, |
| ID_STATIC, |
| WS_VISIBLE | WS_CHILD, |
| 10, |
| y, |
| LABEL_WIDTH, |
| HEIGHT, |
| "Password:"); |
| add_control(&p, |
| dia, |
| 0x81, |
| ID_INPUT_LINE, |
| WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL | WS_TABSTOP, |
| 15 + LABEL_WIDTH, |
| y, |
| WIDTH - LABEL_WIDTH - 25, |
| HEIGHT, |
| ""); |
| |
| y += (WORD)(HEIGHT * 2); |
| add_control(&p, |
| dia, |
| 0x80, |
| IDOK, |
| WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP, |
| 80, |
| y, |
| 55, |
| 12, |
| "Ok"); |
| add_control(&p, |
| dia, |
| 0x80, |
| IDCANCEL, |
| WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP, |
| 140, |
| y, |
| 55, |
| 12, |
| "Cancel"); |
| |
| assert((intptr_t)p - (intptr_t)mem < (intptr_t)sizeof(mem)); |
| |
| dia->cy = y + (WORD)(HEIGHT * 1.5); |
| |
| s_dlg_proc_param.name = "Modify password"; |
| s_dlg_proc_param.fRetry = NULL; |
| |
| ok = |
| (IDOK == DialogBoxIndirectParam( |
| NULL, dia, NULL, InputDlgProc, (LPARAM)&s_dlg_proc_param)); |
| |
| s_dlg_proc_param.hWnd = NULL; |
| s_dlg_proc_param.guard = 0; |
| |
| return ok; |
| |
| #undef HEIGHT |
| #undef WIDTH |
| #undef LABEL_WIDTH |
| } |
| |
| |
| /* Dialog proc for password dialog */ |
| static INT_PTR CALLBACK |
| PasswordDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) |
| { |
| static const char *passfile = 0; |
| char domain[256], user[256], password[256]; |
| WORD ctrlId; |
| struct dlg_proc_param *pdlg_proc_param; |
| |
| switch (msg) { |
| case WM_CLOSE: |
| passfile = 0; |
| DestroyWindow(hDlg); |
| break; |
| |
| case WM_COMMAND: |
| ctrlId = LOWORD(wParam); |
| if (ctrlId == ID_ADD_USER) { |
| /* Add user */ |
| GetWindowText(GetDlgItem(hDlg, ID_ADD_USER_NAME), |
| user, |
| sizeof(user)); |
| GetWindowText(GetDlgItem(hDlg, ID_ADD_USER_REALM), |
| domain, |
| sizeof(domain)); |
| if (get_password(user, domain, password, sizeof(password))) { |
| mg_modify_passwords_file(passfile, domain, user, password); |
| EndDialog(hDlg, IDOK); |
| } |
| } else if ((ctrlId >= (ID_CONTROLS + ID_FILE_BUTTONS_DELTA * 3)) |
| && (ctrlId < (ID_CONTROLS + ID_FILE_BUTTONS_DELTA * 4))) { |
| /* Modify password */ |
| GetWindowText(GetDlgItem(hDlg, ctrlId - ID_FILE_BUTTONS_DELTA * 3), |
| user, |
| sizeof(user)); |
| GetWindowText(GetDlgItem(hDlg, ctrlId - ID_FILE_BUTTONS_DELTA * 2), |
| domain, |
| sizeof(domain)); |
| if (get_password(user, domain, password, sizeof(password))) { |
| mg_modify_passwords_file(passfile, domain, user, password); |
| EndDialog(hDlg, IDOK); |
| } |
| } else if ((ctrlId >= (ID_CONTROLS + ID_FILE_BUTTONS_DELTA * 2)) |
| && (ctrlId < (ID_CONTROLS + ID_FILE_BUTTONS_DELTA * 3))) { |
| /* Remove user */ |
| GetWindowText(GetDlgItem(hDlg, ctrlId - ID_FILE_BUTTONS_DELTA * 2), |
| user, |
| sizeof(user)); |
| GetWindowText(GetDlgItem(hDlg, ctrlId - ID_FILE_BUTTONS_DELTA), |
| domain, |
| sizeof(domain)); |
| mg_modify_passwords_file(passfile, domain, user, NULL); |
| EndDialog(hDlg, IDOK); |
| } |
| break; |
| |
| case WM_INITDIALOG: |
| pdlg_proc_param = (struct dlg_proc_param *)lParam; |
| pdlg_proc_param->hWnd = hDlg; |
| passfile = pdlg_proc_param->name; |
| SendMessage(hDlg, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)hIcon); |
| SendMessage(hDlg, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)hIcon); |
| SetWindowText(hDlg, passfile); |
| SetFocus(GetDlgItem(hDlg, ID_ADD_USER_NAME)); |
| break; |
| |
| default: |
| break; |
| } |
| |
| return FALSE; |
| } |
| |
| |
| static void |
| add_control(unsigned char **mem, |
| DLGTEMPLATE *dia, |
| WORD type, |
| WORD id, |
| DWORD style, |
| short x, |
| short y, |
| short cx, |
| short cy, |
| const char *caption) |
| { |
| DLGITEMTEMPLATE *tp; |
| LPWORD p; |
| |
| dia->cdit++; |
| |
| *mem = (unsigned char *)align(*mem, 3); |
| tp = (DLGITEMTEMPLATE *)*mem; |
| |
| tp->id = id; |
| tp->style = style; |
| tp->dwExtendedStyle = 0; |
| tp->x = x; |
| tp->y = y; |
| tp->cx = cx; |
| tp->cy = cy; |
| |
| p = (LPWORD)align(*mem + sizeof(*tp), 1); |
| *p++ = 0xffff; |
| *p++ = type; |
| |
| while (*caption != '\0') { |
| *p++ = (WCHAR)*caption++; |
| } |
| *p++ = 0; |
| p = (LPWORD)align(p, 1); |
| |
| *p++ = 0; |
| *mem = (unsigned char *)p; |
| } |
| |
| |
| static void |
| show_settings_dialog() |
| { |
| #define HEIGHT (15) |
| #define WIDTH (460) |
| #define LABEL_WIDTH (90) |
| |
| unsigned char mem[16 * 1024], *p; |
| const struct mg_option *options; |
| DWORD style; |
| DLGTEMPLATE *dia = (DLGTEMPLATE *)mem; |
| WORD i, cl, nelems = 0; |
| short width, x, y; |
| static struct dlg_proc_param s_dlg_proc_param; |
| |
| static struct { |
| DLGTEMPLATE dlg_template; /* 18 bytes */ |
| WORD menu, dlg_class; |
| wchar_t caption[1]; |
| WORD fontsiz; |
| wchar_t fontface[7]; |
| } dialog_header = {{WS_CAPTION | WS_POPUP | WS_SYSMENU | WS_VISIBLE |
| | DS_SETFONT | WS_DLGFRAME, |
| WS_EX_TOOLWINDOW, |
| 0, |
| 200, |
| 200, |
| WIDTH, |
| 0}, |
| 0, |
| 0, |
| L"", |
| 8, |
| L"Tahoma"}; |
| |
| if (s_dlg_proc_param.guard == 0) { |
| memset(&s_dlg_proc_param, 0, sizeof(s_dlg_proc_param)); |
| s_dlg_proc_param.guard = 1; |
| } else { |
| SetForegroundWindow(s_dlg_proc_param.hWnd); |
| return; |
| } |
| |
| (void)memset(mem, 0, sizeof(mem)); |
| (void)memcpy(mem, &dialog_header, sizeof(dialog_header)); |
| p = mem + sizeof(dialog_header); |
| |
| options = mg_get_valid_options(); |
| for (i = 0; options[i].name != NULL; i++) { |
| style = WS_CHILD | WS_VISIBLE | WS_TABSTOP; |
| x = 10 + (WIDTH / 2) * (nelems % 2); |
| y = (nelems / 2 + 1) * HEIGHT + 5; |
| width = WIDTH / 2 - 20 - LABEL_WIDTH; |
| if (options[i].type == CONFIG_TYPE_NUMBER) { |
| style |= ES_NUMBER; |
| cl = 0x81; |
| style |= WS_BORDER | ES_AUTOHSCROLL; |
| } else if (options[i].type == CONFIG_TYPE_BOOLEAN) { |
| cl = 0x80; |
| style |= BS_AUTOCHECKBOX; |
| } else if ((options[i].type == CONFIG_TYPE_FILE) |
| || (options[i].type == CONFIG_TYPE_DIRECTORY)) { |
| style |= WS_BORDER | ES_AUTOHSCROLL; |
| width -= 20; |
| cl = 0x81; |
| add_control(&p, |
| dia, |
| 0x80, |
| ID_CONTROLS + i + ID_FILE_BUTTONS_DELTA, |
| WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON, |
| x + width + LABEL_WIDTH + 5, |
| y, |
| 15, |
| 12, |
| "..."); |
| } else if (options[i].type == CONFIG_TYPE_STRING_MULTILINE) { |
| /* TODO: This is not really uer friendly */ |
| cl = 0x81; |
| style |= WS_BORDER | ES_AUTOHSCROLL | ES_MULTILINE | ES_WANTRETURN |
| | ES_AUTOVSCROLL; |
| } else { |
| cl = 0x81; |
| style |= WS_BORDER | ES_AUTOHSCROLL; |
| } |
| add_control(&p, |
| dia, |
| 0x82, |
| ID_STATIC, |
| WS_VISIBLE | WS_CHILD, |
| x, |
| y, |
| LABEL_WIDTH, |
| HEIGHT, |
| options[i].name); |
| add_control(&p, |
| dia, |
| cl, |
| ID_CONTROLS + i, |
| style, |
| x + LABEL_WIDTH, |
| y, |
| width, |
| 12, |
| ""); |
| nelems++; |
| |
| assert(((intptr_t)p - (intptr_t)mem) < (intptr_t)sizeof(mem)); |
| } |
| |
| y = (((nelems + 1) / 2 + 1) * HEIGHT + 5); |
| add_control(&p, |
| dia, |
| 0x80, |
| ID_GROUP, |
| WS_CHILD | WS_VISIBLE | BS_GROUPBOX, |
| 5, |
| 5, |
| WIDTH - 10, |
| y, |
| " Settings "); |
| y += 10; |
| add_control(&p, |
| dia, |
| 0x80, |
| ID_SAVE, |
| WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP, |
| WIDTH - 70, |
| y, |
| 65, |
| 12, |
| "Save Settings"); |
| add_control(&p, |
| dia, |
| 0x80, |
| ID_RESET_DEFAULTS, |
| WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP, |
| WIDTH - 140, |
| y, |
| 65, |
| 12, |
| "Reset to defaults"); |
| add_control(&p, |
| dia, |
| 0x80, |
| ID_RESET_FILE, |
| WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP, |
| WIDTH - 210, |
| y, |
| 65, |
| 12, |
| "Reload from file"); |
| add_control(&p, |
| dia, |
| 0x80, |
| ID_RESET_ACTIVE, |
| WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP, |
| WIDTH - 280, |
| y, |
| 65, |
| 12, |
| "Reload active"); |
| add_control(&p, |
| dia, |
| 0x82, |
| ID_STATIC, |
| WS_CHILD | WS_VISIBLE | WS_DISABLED, |
| 5, |
| y, |
| 100, |
| 12, |
| g_server_base_name); |
| |
| assert(((intptr_t)p - (intptr_t)mem) < (intptr_t)sizeof(mem)); |
| |
| dia->cy = ((nelems + 1) / 2 + 1) * HEIGHT + 30; |
| |
| s_dlg_proc_param.fRetry = NULL; |
| |
| DialogBoxIndirectParam( |
| NULL, dia, NULL, SettingsDlgProc, (LPARAM)&s_dlg_proc_param); |
| |
| s_dlg_proc_param.hWnd = NULL; |
| s_dlg_proc_param.guard = 0; |
| |
| #undef HEIGHT |
| #undef WIDTH |
| #undef LABEL_WIDTH |
| } |
| |
| |
| static void |
| change_password_file() |
| { |
| #define HEIGHT (15) |
| #define WIDTH (320) |
| #define LABEL_WIDTH (90) |
| |
| OPENFILENAME of; |
| char path[PATH_MAX] = PASSWORDS_FILE_NAME; |
| char strbuf[256], u[256], d[256]; |
| HWND hDlg = NULL; |
| FILE *f; |
| short y, nelems; |
| unsigned char mem[4096], *p; |
| DLGTEMPLATE *dia = (DLGTEMPLATE *)mem; |
| const char *domain = mg_get_option(g_ctx, "authentication_domain"); |
| static struct dlg_proc_param s_dlg_proc_param; |
| |
| static struct { |
| DLGTEMPLATE dlg_template; /* 18 bytes */ |
| WORD menu, dlg_class; |
| wchar_t caption[1]; |
| WORD fontsiz; |
| wchar_t fontface[7]; |
| } dialog_header = {{WS_CAPTION | WS_POPUP | WS_SYSMENU | WS_VISIBLE |
| | DS_SETFONT | WS_DLGFRAME, |
| WS_EX_TOOLWINDOW, |
| 0, |
| 200, |
| 200, |
| WIDTH, |
| 0}, |
| 0, |
| 0, |
| L"", |
| 8, |
| L"Tahoma"}; |
| |
| if (s_dlg_proc_param.guard == 0) { |
| memset(&s_dlg_proc_param, 0, sizeof(s_dlg_proc_param)); |
| s_dlg_proc_param.guard = 1; |
| } else { |
| SetForegroundWindow(s_dlg_proc_param.hWnd); |
| return; |
| } |
| |
| memset(&of, 0, sizeof(of)); |
| of.lStructSize = sizeof(of); |
| of.hwndOwner = (HWND)hDlg; |
| of.lpstrFile = path; |
| of.nMaxFile = sizeof(path); |
| of.lpstrInitialDir = mg_get_option(g_ctx, "document_root"); |
| of.Flags = OFN_CREATEPROMPT | OFN_NOCHANGEDIR | OFN_HIDEREADONLY; |
| |
| if (IDOK != GetSaveFileName(&of)) { |
| s_dlg_proc_param.guard = 0; |
| return; |
| } |
| |
| f = fopen(path, "a+"); |
| if (f) { |
| fclose(f); |
| } else { |
| MessageBox(NULL, path, "Can not open file", MB_ICONERROR); |
| s_dlg_proc_param.guard = 0; |
| return; |
| } |
| |
| do { |
| s_dlg_proc_param.hWnd = NULL; |
| (void)memset(mem, 0, sizeof(mem)); |
| (void)memcpy(mem, &dialog_header, sizeof(dialog_header)); |
| p = mem + sizeof(dialog_header); |
| |
| f = fopen(path, "r+"); |
| if (!f) { |
| MessageBox(NULL, path, "Can not open file", MB_ICONERROR); |
| s_dlg_proc_param.guard = 0; |
| return; |
| } |
| |
| nelems = 0; |
| while (fgets(strbuf, sizeof(strbuf), f)) { |
| if (sscanf(strbuf, "%255[^:]:%255[^:]:%*s", u, d) != 2) { |
| continue; |
| } |
| u[255] = 0; |
| d[255] = 0; |
| y = (nelems + 1) * HEIGHT + 5; |
| add_control(&p, |
| dia, |
| 0x80, |
| ID_CONTROLS + nelems + ID_FILE_BUTTONS_DELTA * 3, |
| WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP, |
| 10, |
| y, |
| 65, |
| 12, |
| "Modify password"); |
| add_control(&p, |
| dia, |
| 0x80, |
| ID_CONTROLS + nelems + ID_FILE_BUTTONS_DELTA * 2, |
| WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP, |
| 80, |
| y, |
| 55, |
| 12, |
| "Remove user"); |
| add_control(&p, |
| dia, |
| 0x81, |
| ID_CONTROLS + nelems + ID_FILE_BUTTONS_DELTA, |
| WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL |
| | ES_READONLY, |
| 245, |
| y, |
| 60, |
| 12, |
| d); |
| add_control(&p, |
| dia, |
| 0x81, |
| ID_CONTROLS + nelems, |
| WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL |
| | ES_READONLY, |
| 140, |
| y, |
| 100, |
| 12, |
| u); |
| |
| nelems++; |
| assert(((intptr_t)p - (intptr_t)mem) < (intptr_t)sizeof(mem)); |
| } |
| fclose(f); |
| |
| y = (nelems + 1) * HEIGHT + 10; |
| add_control(&p, |
| dia, |
| 0x80, |
| ID_ADD_USER, |
| WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP, |
| 80, |
| y, |
| 55, |
| 12, |
| "Add user"); |
| add_control(&p, |
| dia, |
| 0x81, |
| ID_ADD_USER_NAME, |
| WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL |
| | WS_TABSTOP, |
| 140, |
| y, |
| 100, |
| 12, |
| ""); |
| add_control(&p, |
| dia, |
| 0x81, |
| ID_ADD_USER_REALM, |
| WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL |
| | WS_TABSTOP, |
| 245, |
| y, |
| 60, |
| 12, |
| domain); |
| |
| y = (nelems + 2) * HEIGHT + 10; |
| add_control(&p, |
| dia, |
| 0x80, |
| ID_GROUP, |
| WS_CHILD | WS_VISIBLE | BS_GROUPBOX, |
| 5, |
| 5, |
| WIDTH - 10, |
| y, |
| " Users "); |
| |
| y += HEIGHT; |
| add_control(&p, |
| dia, |
| 0x82, |
| ID_STATIC, |
| WS_CHILD | WS_VISIBLE | WS_DISABLED, |
| 5, |
| y, |
| 100, |
| 12, |
| g_server_base_name); |
| |
| assert(((intptr_t)p - (intptr_t)mem) < (intptr_t)sizeof(mem)); |
| |
| dia->cy = y + 20; |
| |
| s_dlg_proc_param.name = path; |
| s_dlg_proc_param.fRetry = NULL; |
| |
| } while ((IDOK == DialogBoxIndirectParam(NULL, |
| dia, |
| NULL, |
| PasswordDlgProc, |
| (LPARAM)&s_dlg_proc_param)) |
| && (!g_exit_flag)); |
| |
| s_dlg_proc_param.hWnd = NULL; |
| s_dlg_proc_param.guard = 0; |
| |
| #undef HEIGHT |
| #undef WIDTH |
| #undef LABEL_WIDTH |
| } |
| |
| |
| static BOOL |
| sysinfo_reload(struct dlg_proc_param *prm) |
| { |
| static char *buf = 0; |
| int cl, rl; |
| |
| cl = mg_get_context_info(g_ctx, NULL, 0); |
| free(buf); |
| cl += 510; |
| buf = (char *)malloc(cl + 1); |
| rl = mg_get_context_info(g_ctx, buf, cl); |
| if ((rl > cl) || (rl <= 0)) { |
| if (g_ctx == NULL) { |
| prm->buffer = "Server not running"; |
| } else if (rl <= 0) { |
| prm->buffer = "No server statistics available"; |
| } else { |
| prm->buffer = "Please retry"; |
| } |
| } else { |
| prm->buffer = buf; |
| } |
| |
| return TRUE; |
| } |
| |
| |
| int |
| show_system_info() |
| { |
| #define HEIGHT (15) |
| #define WIDTH (320) |
| #define LABEL_WIDTH (50) |
| |
| unsigned char mem[4096], *p; |
| DLGTEMPLATE *dia = (DLGTEMPLATE *)mem; |
| int ok; |
| short y; |
| static struct dlg_proc_param s_dlg_proc_param; |
| |
| static struct { |
| DLGTEMPLATE dlg_template; /* 18 bytes */ |
| WORD menu, dlg_class; |
| wchar_t caption[1]; |
| WORD fontsiz; |
| wchar_t fontface[7]; |
| } dialog_header = {{WS_CAPTION | WS_POPUP | WS_SYSMENU | WS_VISIBLE |
| | DS_SETFONT | WS_DLGFRAME, |
| WS_EX_TOOLWINDOW, |
| 0, |
| 200, |
| 200, |
| WIDTH, |
| 0}, |
| 0, |
| 0, |
| L"", |
| 8, |
| L"Tahoma"}; |
| |
| /* Only allow one instance of this dialog to be open. */ |
| if (s_dlg_proc_param.guard == 0) { |
| memset(&s_dlg_proc_param, 0, sizeof(s_dlg_proc_param)); |
| s_dlg_proc_param.guard = 1; |
| } else { |
| SetForegroundWindow(s_dlg_proc_param.hWnd); |
| return 0; |
| } |
| |
| /* Create the dialog */ |
| (void)memset(mem, 0, sizeof(mem)); |
| (void)memcpy(mem, &dialog_header, sizeof(dialog_header)); |
| p = mem + sizeof(dialog_header); |
| |
| y = HEIGHT; |
| add_control(&p, |
| dia, |
| 0x82, |
| ID_STATIC, |
| WS_VISIBLE | WS_CHILD, |
| 10, |
| y, |
| LABEL_WIDTH, |
| HEIGHT, |
| "System Information:"); |
| add_control(&p, |
| dia, |
| 0x81, |
| ID_CONTROLS + 1, |
| WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL |
| | ES_AUTOVSCROLL | ES_MULTILINE | ES_READONLY, |
| 15 + LABEL_WIDTH, |
| y, |
| WIDTH - LABEL_WIDTH - 25, |
| HEIGHT * 7, |
| g_system_info); |
| |
| y += (WORD)(HEIGHT * 8); |
| |
| add_control(&p, |
| dia, |
| 0x80, |
| IDRETRY, |
| WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP, |
| WIDTH - 10 - 55 - 10 - 55, |
| y, |
| 55, |
| 12, |
| "Reload"); |
| |
| add_control(&p, |
| dia, |
| 0x80, |
| IDOK, |
| WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP, |
| WIDTH - 10 - 55, |
| y, |
| 55, |
| 12, |
| "Close"); |
| |
| assert((intptr_t)p - (intptr_t)mem < (intptr_t)sizeof(mem)); |
| |
| dia->cy = y + (WORD)(HEIGHT * 1.5); |
| |
| s_dlg_proc_param.name = "System information"; |
| s_dlg_proc_param.fRetry = sysinfo_reload; |
| s_dlg_proc_param.idRetry = ID_CONTROLS + 1; /* Reload field with this ID */ |
| |
| ok = |
| (IDOK == DialogBoxIndirectParam( |
| NULL, dia, NULL, InputDlgProc, (LPARAM)&s_dlg_proc_param)); |
| |
| s_dlg_proc_param.hWnd = NULL; |
| s_dlg_proc_param.guard = 0; |
| |
| return ok; |
| |
| #undef HEIGHT |
| #undef WIDTH |
| #undef LABEL_WIDTH |
| } |
| |
| |
| static int |
| manage_service(int action) |
| { |
| const char *service_name = g_server_name; |
| SC_HANDLE hSCM = NULL, hService = NULL; |
| SERVICE_DESCRIPTION descr; |
| char path[PATH_MAX + 20] = ""; /* Path to executable plus magic argument */ |
| int success = 1; |
| |
| descr.lpDescription = (LPSTR)g_server_name; |
| |
| if ((hSCM = OpenSCManager(NULL, |
| NULL, |
| action == ID_INSTALL_SERVICE ? GENERIC_WRITE |
| : GENERIC_READ)) |
| == NULL) { |
| success = 0; |
| show_error(); |
| } else if (action == ID_INSTALL_SERVICE) { |
| path[sizeof(path) - 1] = 0; |
| GetModuleFileName(NULL, path, sizeof(path) - 1); |
| strncat(path, " ", sizeof(path) - 1); |
| strncat(path, service_magic_argument, sizeof(path) - 1); |
| hService = CreateService(hSCM, |
| service_name, |
| service_name, |
| SERVICE_ALL_ACCESS, |
| SERVICE_WIN32_OWN_PROCESS, |
| SERVICE_AUTO_START, |
| SERVICE_ERROR_NORMAL, |
| path, |
| NULL, |
| NULL, |
| NULL, |
| NULL, |
| NULL); |
| if (hService) { |
| ChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, &descr); |
| } else { |
| show_error(); |
| } |
| } else if (action == ID_REMOVE_SERVICE) { |
| if ((hService = OpenService(hSCM, service_name, DELETE)) == NULL |
| || !DeleteService(hService)) { |
| show_error(); |
| } |
| } else if ((hService = |
| OpenService(hSCM, service_name, SERVICE_QUERY_STATUS)) |
| == NULL) { |
| success = 0; |
| } |
| |
| if (hService) |
| CloseServiceHandle(hService); |
| if (hSCM) |
| CloseServiceHandle(hSCM); |
| |
| return success; |
| } |
| |
| |
| /* Window proc for taskbar icon */ |
| static LRESULT CALLBACK |
| WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) |
| { |
| static SERVICE_TABLE_ENTRY service_table[2]; |
| int service_installed; |
| char buf[200], *service_argv[2]; |
| POINT pt; |
| HMENU hMenu; |
| static UINT s_uTaskbarRestart; /* for taskbar creation */ |
| |
| service_argv[0] = __argv[0]; |
| service_argv[1] = NULL; |
| |
| memset(service_table, 0, sizeof(service_table)); |
| service_table[0].lpServiceName = (LPSTR)g_server_name; |
| service_table[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain; |
| |
| switch (msg) { |
| |
| case WM_CREATE: |
| if (__argv[1] != NULL && !strcmp(__argv[1], service_magic_argument)) { |
| start_civetweb(1, service_argv); |
| StartServiceCtrlDispatcher(service_table); |
| exit(EXIT_SUCCESS); |
| } else { |
| start_civetweb(__argc, __argv); |
| s_uTaskbarRestart = RegisterWindowMessage(TEXT("TaskbarCreated")); |
| } |
| break; |
| |
| case WM_COMMAND: |
| switch (LOWORD(wParam)) { |
| case ID_QUIT: |
| stop_civetweb(); |
| Shell_NotifyIcon(NIM_DELETE, &TrayIcon); |
| g_exit_flag = 1; |
| PostQuitMessage(0); |
| return 0; |
| case ID_SETTINGS: |
| show_settings_dialog(); |
| break; |
| case ID_PASSWORD: |
| change_password_file(); |
| break; |
| case ID_SYSINFO: |
| show_system_info(); |
| break; |
| case ID_INSTALL_SERVICE: |
| case ID_REMOVE_SERVICE: |
| manage_service(LOWORD(wParam)); |
| break; |
| case ID_CONNECT: |
| fprintf(stdout, "[%s]\n", get_url_to_first_open_port(g_ctx)); |
| ShellExecute(NULL, |
| "open", |
| get_url_to_first_open_port(g_ctx), |
| NULL, |
| NULL, |
| SW_SHOW); |
| break; |
| case ID_WEBSITE: |
| fprintf(stdout, "[%s]\n", g_website); |
| ShellExecute(NULL, "open", g_website, NULL, NULL, SW_SHOW); |
| break; |
| } |
| break; |
| |
| case WM_USER: |
| switch (lParam) { |
| case WM_RBUTTONUP: |
| case WM_LBUTTONUP: |
| case WM_LBUTTONDBLCLK: |
| hMenu = CreatePopupMenu(); |
| AppendMenu(hMenu, |
| MF_STRING | MF_GRAYED, |
| ID_SEPARATOR, |
| g_server_name); |
| AppendMenu(hMenu, MF_SEPARATOR, ID_SEPARATOR, ""); |
| service_installed = manage_service(0); |
| snprintf(buf, |
| sizeof(buf) - 1, |
| "NT service: %s installed", |
| service_installed ? "" : "not"); |
| buf[sizeof(buf) - 1] = 0; |
| AppendMenu(hMenu, MF_STRING | MF_GRAYED, ID_SEPARATOR, buf); |
| AppendMenu(hMenu, |
| MF_STRING | (service_installed ? MF_GRAYED : 0), |
| ID_INSTALL_SERVICE, |
| "Install service"); |
| AppendMenu(hMenu, |
| MF_STRING | (!service_installed ? MF_GRAYED : 0), |
| ID_REMOVE_SERVICE, |
| "Deinstall service"); |
| AppendMenu(hMenu, MF_SEPARATOR, ID_SEPARATOR, ""); |
| AppendMenu(hMenu, MF_STRING, ID_CONNECT, "Start browser"); |
| AppendMenu(hMenu, MF_STRING, ID_SETTINGS, "Edit settings"); |
| AppendMenu(hMenu, MF_STRING, ID_PASSWORD, "Modify password file"); |
| AppendMenu(hMenu, MF_STRING, ID_SYSINFO, "Show system info"); |
| AppendMenu(hMenu, MF_STRING, ID_WEBSITE, "Visit website"); |
| AppendMenu(hMenu, MF_SEPARATOR, ID_SEPARATOR, ""); |
| AppendMenu(hMenu, MF_STRING, ID_QUIT, "Exit"); |
| GetCursorPos(&pt); |
| SetForegroundWindow(hWnd); |
| TrackPopupMenu(hMenu, 0, pt.x, pt.y, 0, hWnd, NULL); |
| PostMessage(hWnd, WM_NULL, 0, 0); |
| DestroyMenu(hMenu); |
| break; |
| } |
| break; |
| |
| case WM_CLOSE: |
| stop_civetweb(); |
| Shell_NotifyIcon(NIM_DELETE, &TrayIcon); |
| g_exit_flag = 1; |
| PostQuitMessage(0); |
| return 0; /* We've just sent our own quit message, with proper hwnd. */ |
| |
| default: |
| if (msg == s_uTaskbarRestart) |
| Shell_NotifyIcon(NIM_ADD, &TrayIcon); |
| } |
| |
| return DefWindowProc(hWnd, msg, wParam, lParam); |
| } |
| |
| |
| static int |
| MakeConsole(void) |
| { |
| DWORD err; |
| int ok = (GetConsoleWindow() != NULL); |
| if (!ok) { |
| if (!AttachConsole(ATTACH_PARENT_PROCESS)) { |
| FreeConsole(); |
| if (!AllocConsole()) { |
| err = GetLastError(); |
| if (err == ERROR_ACCESS_DENIED) { |
| MessageBox(NULL, |
| "Insufficient rights to create a console window", |
| "Error", |
| MB_ICONERROR); |
| } |
| } |
| AttachConsole(GetCurrentProcessId()); |
| } |
| |
| ok = (GetConsoleWindow() != NULL); |
| if (ok) { |
| freopen("CONIN$", "r", stdin); |
| freopen("CONOUT$", "w", stdout); |
| freopen("CONOUT$", "w", stderr); |
| } |
| } |
| |
| if (ok) { |
| SetConsoleTitle(g_server_name); |
| } |
| |
| return ok; |
| } |
| |
| |
| int WINAPI |
| WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmdline, int show) |
| { |
| WNDCLASS cls; |
| HWND hWnd; |
| MSG msg; |
| |
| #if defined(DEBUG) |
| (void)MakeConsole(); |
| #endif |
| |
| (void)hInst; |
| (void)hPrev; |
| (void)cmdline; |
| (void)show; |
| |
| init_server_name((int)__argc, (const char **)__argv); |
| init_system_info(); |
| memset(&cls, 0, sizeof(cls)); |
| cls.lpfnWndProc = (WNDPROC)WindowProc; |
| cls.hIcon = LoadIcon(NULL, IDI_APPLICATION); |
| cls.lpszClassName = g_server_base_name; |
| |
| RegisterClass(&cls); |
| hWnd = CreateWindow(cls.lpszClassName, |
| g_server_name, |
| WS_OVERLAPPEDWINDOW, |
| 0, |
| 0, |
| 0, |
| 0, |
| NULL, |
| NULL, |
| NULL, |
| NULL); |
| ShowWindow(hWnd, SW_HIDE); |
| |
| if (g_icon_name) { |
| hIcon = (HICON) |
| LoadImage(NULL, g_icon_name, IMAGE_ICON, 16, 16, LR_LOADFROMFILE); |
| } else { |
| hIcon = (HICON)LoadImage(GetModuleHandle(NULL), |
| MAKEINTRESOURCE(ID_ICON), |
| IMAGE_ICON, |
| 16, |
| 16, |
| 0); |
| } |
| |
| TrayIcon.cbSize = sizeof(TrayIcon); |
| TrayIcon.uID = ID_ICON; |
| TrayIcon.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; |
| TrayIcon.hIcon = hIcon; |
| TrayIcon.hWnd = hWnd; |
| snprintf(TrayIcon.szTip, sizeof(TrayIcon.szTip), "%s", g_server_name); |
| TrayIcon.uCallbackMessage = WM_USER; |
| Shell_NotifyIcon(NIM_ADD, &TrayIcon); |
| |
| while (GetMessage(&msg, hWnd, 0, 0) > 0) { |
| TranslateMessage(&msg); |
| DispatchMessage(&msg); |
| } |
| |
| free_system_info(); |
| |
| /* Return the WM_QUIT value. */ |
| return (int)msg.wParam; |
| } |
| |
| |
| int |
| main(int argc, char *argv[]) |
| { |
| (void)argc; |
| (void)argv; |
| |
| return WinMain(0, 0, 0, 0); |
| } |
| |
| |
| #elif defined(USE_COCOA) |
| #import <Cocoa/Cocoa.h> |
| |
| @interface Civetweb : NSObject <NSApplicationDelegate> |
| - (void)openBrowser; |
| - (void)shutDown; |
| @end |
| |
| @implementation Civetweb |
| - (void)openBrowser |
| { |
| [[NSWorkspace sharedWorkspace] |
| openURL:[NSURL URLWithString:[NSString stringWithUTF8String: |
| get_url_to_first_open_port( |
| g_ctx)]]]; |
| } |
| - (void)editConfig |
| { |
| create_config_file(g_ctx, g_config_file_name); |
| NSString *path = [NSString stringWithUTF8String:g_config_file_name]; |
| if (![[NSWorkspace sharedWorkspace] openFile:path |
| withApplication:@"TextEdit"]) { |
| NSAlert *alert = [[[NSAlert alloc] init] autorelease]; |
| [alert setAlertStyle:NSWarningAlertStyle]; |
| [alert setMessageText:NSLocalizedString(@"Unable to open config file.", |
| "")]; |
| [alert setInformativeText:path]; |
| (void)[alert runModal]; |
| } |
| } |
| - (void)shutDown |
| { |
| [NSApp terminate:nil]; |
| } |
| @end |
| |
| int |
| main(int argc, char *argv[]) |
| { |
| init_server_name(argc, (const char **)argv); |
| init_system_info(); |
| start_civetweb(argc, argv); |
| |
| [NSAutoreleasePool new]; |
| [NSApplication sharedApplication]; |
| |
| /* Add delegate to process menu item actions */ |
| Civetweb *myDelegate = [[Civetweb alloc] autorelease]; |
| [NSApp setDelegate:myDelegate]; |
| |
| /* Run this app as agent */ |
| ProcessSerialNumber psn = {0, kCurrentProcess}; |
| TransformProcessType(&psn, kProcessTransformToBackgroundApplication); |
| SetFrontProcess(&psn); |
| |
| /* Add status bar menu */ |
| id menu = [[NSMenu new] autorelease]; |
| |
| /* Add version menu item */ |
| [menu |
| addItem: |
| [[[NSMenuItem alloc] |
| /*initWithTitle:[NSString stringWithFormat:@"%s", server_name]*/ |
| initWithTitle:[NSString stringWithUTF8String:g_server_name] |
| action:@selector(noexist) |
| keyEquivalent:@""] autorelease]]; |
| |
| /* Add configuration menu item */ |
| [menu addItem:[[[NSMenuItem alloc] initWithTitle:@"Edit configuration" |
| action:@selector(editConfig) |
| keyEquivalent:@""] autorelease]]; |
| |
| /* Add connect menu item */ |
| [menu |
| addItem:[[[NSMenuItem alloc] initWithTitle:@"Open web root in a browser" |
| action:@selector(openBrowser) |
| keyEquivalent:@""] autorelease]]; |
| |
| /* Separator */ |
| [menu addItem:[NSMenuItem separatorItem]]; |
| |
| /* Add quit menu item */ |
| [menu addItem:[[[NSMenuItem alloc] initWithTitle:@"Quit" |
| action:@selector(shutDown) |
| keyEquivalent:@"q"] autorelease]]; |
| |
| /* Attach menu to the status bar */ |
| id item = [[[NSStatusBar systemStatusBar] |
| statusItemWithLength:NSVariableStatusItemLength] retain]; |
| [item setHighlightMode:YES]; |
| [item setImage:[NSImage imageNamed:@"civetweb_22x22.png"]]; |
| [item setMenu:menu]; |
| |
| /* Run the app */ |
| [NSApp activateIgnoringOtherApps:YES]; |
| [NSApp run]; |
| |
| stop_civetweb(); |
| free_system_info(); |
| |
| return EXIT_SUCCESS; |
| } |
| |
| #else |
| |
| int |
| main(int argc, char *argv[]) |
| { |
| init_server_name(argc, (const char **)argv); |
| init_system_info(); |
| start_civetweb(argc, argv); |
| fprintf(stdout, |
| "%s started on port(s) %s with web root [%s]\n", |
| g_server_name, |
| mg_get_option(g_ctx, "listening_ports"), |
| mg_get_option(g_ctx, "document_root")); |
| |
| while (g_exit_flag == 0) { |
| sleep(1); |
| } |
| |
| fprintf(stdout, |
| "Exiting on signal %d, waiting for all threads to finish...", |
| g_exit_flag); |
| fflush(stdout); |
| stop_civetweb(); |
| fprintf(stdout, "%s", " done.\n"); |
| |
| free_system_info(); |
| |
| return EXIT_SUCCESS; |
| } |
| #endif /* _WIN32 */ |