diff --git a/src/civetweb.c b/src/civetweb.c index 1a6b4148..f5dd8a20 100644 --- a/src/civetweb.c +++ b/src/civetweb.c @@ -3215,8 +3215,8 @@ static int is_authorized_for_put(struct mg_connection *conn) int mg_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+1]; + int found, i; + char line[512], u[512] = "", d[512] ="", ha1[33], tmp[PATH_MAX+8]; FILE *fp, *fp2; found = 0; @@ -3227,6 +3227,25 @@ int mg_modify_passwords_file(const char *fname, const char *domain, pass = NULL; } + /* Other arguments must not be empty */ + if (fname == NULL || domain == NULL || user == NULL) return 0; + + /* Using the given file format, user name and domain must not contain ':' */ + if (strchr(user, ':') != NULL) return 0; + if (strchr(domain, ':') != NULL) return 0; + + /* Do not allow control characters like newline in user name and domain. + Do not allow excessively long names either. */ + for (i=0; user[i]!=0 && i<255; i++) { + if (iscntrl(user[i])) return 0; + } + if (user[i]) return 0; + for (i=0; domain[i]!=0 && i<255; i++) { + if (iscntrl(domain[i])) return 0; + } + if (domain[i]) return 0; + + /* Create a temporary file name */ (void) snprintf(tmp, sizeof(tmp) - 1, "%s.tmp", fname); tmp[sizeof(tmp) - 1] = 0; diff --git a/src/main.c b/src/main.c index 061cb8e7..62a5f6b4 100644 --- a/src/main.c +++ b/src/main.c @@ -83,11 +83,16 @@ static char *server_name; /* Set by init_server_name() */ static char *icon_name; /* Set by init_server_name() */ static char config_file[PATH_MAX] = ""; /* Set by process_command_line_arguments() */ static struct mg_context *ctx; /* Set by start_civetweb() */ +static int guard = 0; /* test if any dialog is already open */ #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" @@ -168,7 +173,7 @@ static void show_usage_and_exit(void) static const char *config_file_top_comment = "# Civetweb web server configuration file.\n" "# For detailed description of every option, visit\n" - "# https://github.com/sunsetbrew/civetweb/blob/master/docs/UserManual.md\n" + "# https://github.com/bel2125/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"; @@ -575,9 +580,9 @@ static void start_civetweb(int argc, char *argv[]) #ifdef _WIN32 enum { ID_ICON = 100, ID_QUIT, ID_SETTINGS, ID_SEPARATOR, ID_INSTALL_SERVICE, - ID_REMOVE_SERVICE, ID_STATIC, ID_GROUP, + 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_STATUS, ID_CONNECT, ID_ADD_USER, ID_ADD_USER_NAME, ID_ADD_USER_REALM, /* All dynamically created text boxes for options have IDs starting from ID_CONTROLS, incremented by one. */ @@ -622,7 +627,6 @@ static void WINAPI ServiceMain(void) SetServiceStatus(hStatus, &ss); } - static void show_error(void) { char buf[256]; @@ -667,7 +671,8 @@ static void save_config(HWND hDlg, FILE *fp) } } -static BOOL CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lP) + +static BOOL CALLBACK SettingsDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lP) { FILE *fp; int i, j; @@ -740,7 +745,7 @@ static BOOL CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lP) CheckDlgButton(hDlg, ID_CONTROLS + i, !strcmp(value, "yes") ? BST_CHECKED : BST_UNCHECKED); } else { - SetWindowText(GetDlgItem(hDlg, ID_CONTROLS + i), value); + SetDlgItemText(hDlg, ID_CONTROLS + i, value == NULL ? "" : value); } } break; @@ -791,18 +796,73 @@ static BOOL CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lP) free(title); } SetFocus(GetDlgItem(hDlg, ID_SAVE)); - for (i = 0; default_options[i].name != NULL; i++) { - name = default_options[i].name; - value = mg_get_option(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); - } + + /* 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; +} + +static void GetPassword(char * password, unsigned maxlen) +{ + assert(maxlen>=255); + /* TODO: replace by a proper dialog */ + strcpy(password, "12345"); +} + +static BOOL CALLBACK PasswordDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lP) +{ + static const char *passfile = 0; + char domain[256], user[256], password[256]; + WORD ctrlId; + + 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)); + GetPassword(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)); + GetPassword(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: + passfile = (const char *)lP; + 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; } @@ -855,7 +915,6 @@ static void show_settings_dialog() DWORD style; DLGTEMPLATE *dia = (DLGTEMPLATE *) mem; WORD i, cl, x, y, width, nelems = 0; - static int guard = 0; /* test if dialog is already open */ static struct { DLGTEMPLATE template; /* 18 bytes */ @@ -939,8 +998,139 @@ static void show_settings_dialog() assert((int)p - (int)mem < sizeof(mem)); dia->cy = ((nelems + 1) / 2 + 1) * HEIGHT + 30; - DialogBoxIndirectParam(NULL, dia, NULL, DlgProc, (LPARAM) NULL); + DialogBoxIndirectParam(NULL, dia, NULL, SettingsDlgProc, (LPARAM) NULL); guard--; + +#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; + int y, nelems; + unsigned char mem[4096], *p; + DLGTEMPLATE *dia = (DLGTEMPLATE *) mem; + const char * domain = mg_get_option(ctx, "authentication_domain"); + + static struct { + DLGTEMPLATE template; /* 18 bytes */ + WORD menu, 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 (guard == 0) { + guard++; + } else { + 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(ctx, "document_root"); + of.Flags = OFN_CREATEPROMPT | OFN_NOCHANGEDIR | OFN_HIDEREADONLY; + + if (IDOK != GetSaveFileName(&of)) { + guard--; + return; + } + + f = fopen(path, "a+"); + if (f) { + fclose(f); + } else { + MessageBox(NULL, path, "Can not open file", MB_ICONERROR); + guard--; + return; + } + + do { + (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); + guard--; + 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 | WS_DISABLED, + 245, y, 60, 12, d); + add_control(&p, dia, 0x81, ID_CONTROLS + nelems, + WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL | WS_DISABLED, + 140, y, 100, 12, u); + + nelems++; + assert((int)p - (int)mem < sizeof(mem)); + } + fclose(f); + + y = (WORD) ((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_REALM, + WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL, + 245, y, 60, 12, domain); + add_control(&p, dia, 0x81, ID_ADD_USER_NAME, + WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL, + 140, y, 100, 12, ""); + + y = (WORD) ((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, server_base_name); + + assert((int)p - (int)mem < sizeof(mem)); + + dia->cy = y + 20; + } while (IDOK == DialogBoxIndirectParam(NULL, dia, NULL, PasswordDlgProc, (LPARAM) path)); + + guard--; + +#undef HEIGHT +#undef WIDTH +#undef LABEL_WIDTH } static int manage_service(int action) @@ -1022,6 +1212,9 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, case ID_SETTINGS: show_settings_dialog(); break; + case ID_PASSWORD: + change_password_file(); + break; case ID_INSTALL_SERVICE: case ID_REMOVE_SERVICE: manage_service(LOWORD(wParam)); @@ -1053,6 +1246,7 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, 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_SEPARATOR, ID_SEPARATOR, ""); AppendMenu(hMenu, MF_STRING, ID_QUIT, "Exit"); GetCursorPos(&pt);