/* * Copyright (c) 2004-2005 Sergey Lyubka * All rights reserved * * "THE BEER-WARE LICENSE" (Revision 42): * Sergey Lyubka wrote this file. As long as you retain this notice you * can do whatever you want with this stuff. If we meet some day, and you think * this stuff is worth it, you can buy me a beer in return. */ #ifdef _WIN32 #include "defs.h" static SERVICE_STATUS ss; static SERVICE_STATUS_HANDLE hStatus; static SERVICE_DESCRIPTION service_descr = {"Web server"}; static void fix_directory_separators(char *path) { for (; *path != '\0'; path++) { if (*path == '/') *path = '\\'; if (*path == '\\') while (path[1] == '\\' || path[1] == '/') (void) memmove(path + 1, path + 2, strlen(path + 2) + 1); } } static int protect_against_code_disclosure(const wchar_t *path) { WIN32_FIND_DATAW data; HANDLE handle; const wchar_t *p; /* * Protect against CGI code disclosure under Windows. * This is very nasty hole. Windows happily opens files with * some garbage in the end of file name. So fopen("a.cgi ", "r") * actually opens "a.cgi", and does not return an error! And since * "a.cgi " does not have valid CGI extension, this leads to * the CGI code disclosure. * To protect, here we delete all fishy characters from the * end of file name. */ if ((handle = FindFirstFileW(path, &data)) == INVALID_HANDLE_VALUE) return (FALSE); FindClose(handle); for (p = path + wcslen(path); p > path && p[-1] != L'\\';) p--; if (!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && wcscmp(data.cFileName, p) != 0) return (FALSE); return (TRUE); } int _shttpd_open(const char *path, int flags, int mode) { char buf[FILENAME_MAX]; wchar_t wbuf[FILENAME_MAX]; _shttpd_strlcpy(buf, path, sizeof(buf)); fix_directory_separators(buf); MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf)); if (protect_against_code_disclosure(wbuf) == FALSE) return (-1); return (_wopen(wbuf, flags)); } int _shttpd_stat(const char *path, struct stat *stp) { char buf[FILENAME_MAX], *p; wchar_t wbuf[FILENAME_MAX]; _shttpd_strlcpy(buf, path, sizeof(buf)); fix_directory_separators(buf); p = buf + strlen(buf) - 1; while (p > buf && *p == '\\' && p[-1] != ':') *p-- = '\0'; MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf)); return (_wstat(wbuf, (struct _stat *) stp)); } int _shttpd_remove(const char *path) { char buf[FILENAME_MAX]; wchar_t wbuf[FILENAME_MAX]; _shttpd_strlcpy(buf, path, sizeof(buf)); fix_directory_separators(buf); MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf)); return (_wremove(wbuf)); } int _shttpd_rename(const char *path1, const char *path2) { char buf1[FILENAME_MAX]; char buf2[FILENAME_MAX]; wchar_t wbuf1[FILENAME_MAX]; wchar_t wbuf2[FILENAME_MAX]; _shttpd_strlcpy(buf1, path1, sizeof(buf1)); _shttpd_strlcpy(buf2, path2, sizeof(buf2)); fix_directory_separators(buf1); fix_directory_separators(buf2); MultiByteToWideChar(CP_UTF8, 0, buf1, -1, wbuf1, sizeof(wbuf1)); MultiByteToWideChar(CP_UTF8, 0, buf2, -1, wbuf2, sizeof(wbuf2)); return (_wrename(wbuf1, wbuf2)); } int _shttpd_mkdir(const char *path, int mode) { char buf[FILENAME_MAX]; wchar_t wbuf[FILENAME_MAX]; _shttpd_strlcpy(buf, path, sizeof(buf)); fix_directory_separators(buf); MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf)); return (_wmkdir(wbuf)); } static char * wide_to_utf8(const wchar_t *str) { char *buf = NULL; if (str) { int nchar = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL); if (nchar > 0) { buf = malloc(nchar); if (!buf) errno = ENOMEM; else if (!WideCharToMultiByte(CP_UTF8, 0, str, -1, buf, nchar, NULL, NULL)) { free(buf); buf = NULL; errno = EINVAL; } } else errno = EINVAL; } else errno = EINVAL; return buf; } char * _shttpd_getcwd(char *buffer, int maxlen) { char *result = NULL; wchar_t *wbuffer, *wresult; if (buffer) { /* User-supplied buffer */ wbuffer = malloc(maxlen * sizeof(wchar_t)); if (wbuffer == NULL) return NULL; } else /* Dynamically allocated buffer */ wbuffer = NULL; wresult = _wgetcwd(wbuffer, maxlen); if (wresult) { int err = errno; if (buffer) { /* User-supplied buffer */ int n = WideCharToMultiByte(CP_UTF8, 0, wresult, -1, buffer, maxlen, NULL, NULL); if (n == 0) err = ERANGE; free(wbuffer); result = buffer; } else { /* Buffer allocated by _wgetcwd() */ result = wide_to_utf8(wresult); err = errno; free(wresult); } errno = err; } return result; } DIR * opendir(const char *name) { DIR *dir = NULL; char path[FILENAME_MAX]; wchar_t wpath[FILENAME_MAX]; if (name == NULL || name[0] == '\0') { errno = EINVAL; } else if ((dir = malloc(sizeof(*dir))) == NULL) { errno = ENOMEM; } else { _shttpd_snprintf(path, sizeof(path), "%s/*", name); fix_directory_separators(path); MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, sizeof(wpath)); dir->handle = FindFirstFileW(wpath, &dir->info); if (dir->handle != INVALID_HANDLE_VALUE) { dir->result.d_name[0] = '\0'; } else { free(dir); dir = NULL; } } return (dir); } int closedir(DIR *dir) { int result = -1; if (dir != NULL) { if (dir->handle != INVALID_HANDLE_VALUE) result = FindClose(dir->handle) ? 0 : -1; free(dir); } if (result == -1) errno = EBADF; return (result); } struct dirent * readdir(DIR *dir) { struct dirent *result = 0; if (dir && dir->handle != INVALID_HANDLE_VALUE) { if(!dir->result.d_name || FindNextFileW(dir->handle, &dir->info)) { result = &dir->result; WideCharToMultiByte(CP_UTF8, 0, dir->info.cFileName, -1, result->d_name, sizeof(result->d_name), NULL, NULL); } } else { errno = EBADF; } return (result); } int _shttpd_set_non_blocking_mode(int fd) { unsigned long on = 1; return (ioctlsocket(fd, FIONBIO, &on)); } void _shttpd_set_close_on_exec(int fd) { fd = 0; /* Do nothing. There is no FD_CLOEXEC on Windows */ } #if !defined(NO_CGI) struct threadparam { SOCKET s; HANDLE hPipe; big_int_t content_len; }; enum ready_mode_t {IS_READY_FOR_READ, IS_READY_FOR_WRITE}; /* * Wait until given socket is in ready state. Always return TRUE. */ static int is_socket_ready(int sock, enum ready_mode_t mode) { fd_set read_set, write_set; FD_ZERO(&read_set); FD_ZERO(&write_set); if (mode == IS_READY_FOR_READ) FD_SET(sock, &read_set); else FD_SET(sock, &write_set); select(sock + 1, &read_set, &write_set, NULL, NULL); return (TRUE); } /* * Thread function that reads POST data from the socket pair * and writes it to the CGI process. */ static void//DWORD WINAPI stdoutput(void *arg) { struct threadparam *tp = arg; int n, sent, stop = 0; big_int_t total = 0; DWORD k; char buf[BUFSIZ]; size_t max_recv; max_recv = min(sizeof(buf), tp->content_len - total); while (!stop && max_recv > 0 && is_socket_ready(tp->s, IS_READY_FOR_READ) && (n = recv(tp->s, buf, max_recv, 0)) > 0) { if (n == -1 && ERRNO == EWOULDBLOCK) continue; for (sent = 0; !stop && sent < n; sent += k) if (!WriteFile(tp->hPipe, buf + sent, n - sent, &k, 0)) stop++; total += n; max_recv = min(sizeof(buf), tp->content_len - total); } CloseHandle(tp->hPipe); /* Suppose we have POSTed everything */ free(tp); } /* * Thread function that reads CGI output and pushes it to the socket pair. */ static void stdinput(void *arg) { struct threadparam *tp = arg; static int ntotal; int k, stop = 0; DWORD n, sent; char buf[BUFSIZ]; while (!stop && ReadFile(tp->hPipe, buf, sizeof(buf), &n, NULL)) { ntotal += n; for (sent = 0; !stop && sent < n; sent += k) { if (is_socket_ready(tp->s, IS_READY_FOR_WRITE) && (k = send(tp->s, buf + sent, n - sent, 0)) <= 0) { if (k == -1 && ERRNO == EWOULDBLOCK) { k = 0; continue; } stop++; } } } CloseHandle(tp->hPipe); /* * Windows is a piece of crap. When this thread closes its end * of the socket pair, the other end (get_cgi() function) may loose * some data. I presume, this happens if get_cgi() is not fast enough, * and the data written by this end does not "push-ed" to the other * end socket buffer. So after closesocket() the remaining data is * gone. If I put shutdown() before closesocket(), that seems to * fix the problem, but I am not sure this is the right fix. * XXX (submitted by James Marshall) we do not do shutdown() on UNIX. * If fork() is called from user callback, shutdown() messes up things. */ shutdown(tp->s, 2); closesocket(tp->s); free(tp); _endthread(); } static void spawn_stdio_thread(int sock, HANDLE hPipe, void (*func)(void *), big_int_t content_len) { struct threadparam *tp; DWORD tid; tp = malloc(sizeof(*tp)); assert(tp != NULL); tp->s = sock; tp->hPipe = hPipe; tp->content_len = content_len; _beginthread(func, 0, tp); } int _shttpd_spawn_process(struct conn *c, const char *prog, char *envblk, char *envp[], int sock, const char *dir) { HANDLE a[2], b[2], h[2], me; DWORD flags; char *p, *interp, cmdline[FILENAME_MAX], line[FILENAME_MAX]; FILE *fp; STARTUPINFOA si; PROCESS_INFORMATION pi; me = GetCurrentProcess(); flags = DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS; /* FIXME add error checking code here */ CreatePipe(&a[0], &a[1], NULL, 0); CreatePipe(&b[0], &b[1], NULL, 0); DuplicateHandle(me, a[0], me, &h[0], 0, TRUE, flags); DuplicateHandle(me, b[1], me, &h[1], 0, TRUE, flags); (void) memset(&si, 0, sizeof(si)); (void) memset(&pi, 0, sizeof(pi)); /* XXX redirect CGI errors to the error log file */ si.cb = sizeof(si); si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE; si.hStdOutput = h[1]; si.hStdInput = h[0]; /* If CGI file is a script, try to read the interpreter line */ interp = c->ctx->options[OPT_CGI_INTERPRETER]; if (interp == NULL) { if ((fp = fopen(prog, "r")) != NULL) { (void) fgets(line, sizeof(line), fp); if (memcmp(line, "#!", 2) != 0) line[2] = '\0'; /* Trim whitespaces from interpreter name */ for (p = &line[strlen(line) - 1]; p > line && isspace(*p); p--) *p = '\0'; (void) fclose(fp); } interp = line + 2; (void) _shttpd_snprintf(cmdline, sizeof(cmdline), "%s%s%s", line + 2, line[2] == '\0' ? "" : " ", prog); } if ((p = strrchr(prog, '/')) != NULL) prog = p + 1; (void) _shttpd_snprintf(cmdline, sizeof(cmdline), "%s %s", interp, prog); (void) _shttpd_snprintf(line, sizeof(line), "%s", dir); fix_directory_separators(line); fix_directory_separators(cmdline); /* * Spawn reader & writer threads before we create CGI process. * Otherwise CGI process may die too quickly, loosing the data */ spawn_stdio_thread(sock, b[0], stdinput, 0); spawn_stdio_thread(sock, a[1], stdoutput, c->rem.content_len); if (CreateProcessA(NULL, cmdline, NULL, NULL, TRUE, CREATE_NEW_PROCESS_GROUP, envblk, line, &si, &pi) == 0) { _shttpd_elog(E_LOG, c, "redirect: CreateProcess(%s): %d", cmdline, ERRNO); return (-1); } else { CloseHandle(h[0]); CloseHandle(h[1]); CloseHandle(pi.hThread); CloseHandle(pi.hProcess); } return (0); } #endif /* !NO_CGI */ #define ID_TRAYICON 100 #define ID_QUIT 101 static NOTIFYICONDATA ni; static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { POINT pt; HMENU hMenu; switch (msg) { case WM_COMMAND: switch (LOWORD(wParam)) { case ID_QUIT: exit(EXIT_SUCCESS); break; } break; case WM_USER: switch (lParam) { case WM_RBUTTONUP: case WM_LBUTTONUP: case WM_LBUTTONDBLCLK: hMenu = CreatePopupMenu(); AppendMenu(hMenu, 0, ID_QUIT, "Exit SHTTPD"); GetCursorPos(&pt); TrackPopupMenu(hMenu, 0, pt.x, pt.y, 0, hWnd, NULL); DestroyMenu(hMenu); break; } break; } return (DefWindowProc(hWnd, msg, wParam, lParam)); } static void systray(void *arg) { WNDCLASS cls; HWND hWnd; MSG msg; (void) memset(&cls, 0, sizeof(cls)); cls.lpfnWndProc = (WNDPROC) WindowProc; cls.hIcon = LoadIcon(NULL, IDI_APPLICATION); cls.lpszClassName = "shttpd v." SHTTPD_VERSION; if (!RegisterClass(&cls)) _shttpd_elog(E_FATAL, NULL, "RegisterClass: %d", ERRNO); else if ((hWnd = CreateWindow(cls.lpszClassName, "", WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, NULL, NULL, NULL, arg)) == NULL) _shttpd_elog(E_FATAL, NULL, "CreateWindow: %d", ERRNO); ShowWindow(hWnd, SW_HIDE); ni.cbSize = sizeof(ni); ni.uID = ID_TRAYICON; ni.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; ni.hIcon = LoadIcon(NULL, IDI_APPLICATION); ni.hWnd = hWnd; _shttpd_snprintf(ni.szTip, sizeof(ni.szTip), "SHTTPD web server"); ni.uCallbackMessage = WM_USER; Shell_NotifyIcon(NIM_ADD, &ni); while (GetMessage(&msg, hWnd, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } } int _shttpd_set_systray(struct shttpd_ctx *ctx, const char *opt) { HWND hWnd; char title[512]; static WNDPROC oldproc; if (!_shttpd_is_true(opt)) return (TRUE); FreeConsole(); GetConsoleTitle(title, sizeof(title)); hWnd = FindWindow(NULL, title); ShowWindow(hWnd, SW_HIDE); _beginthread(systray, 0, hWnd); return (TRUE); } int _shttpd_set_nt_service(struct shttpd_ctx *ctx, const char *action) { SC_HANDLE hSCM, hService; char path[FILENAME_MAX], key[128]; HKEY hKey; DWORD dwData; if (!strcmp(action, "install")) { if ((hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS)) == NULL) _shttpd_elog(E_FATAL, NULL, "Error opening SCM (%d)", ERRNO); GetModuleFileName(NULL, path, sizeof(path)); 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) _shttpd_elog(E_FATAL, NULL, "Error installing service (%d)", ERRNO); ChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, &service_descr); _shttpd_elog(E_FATAL, NULL, "Service successfully installed"); } else if (!strcmp(action, "uninstall")) { if ((hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS)) == NULL) { _shttpd_elog(E_FATAL, NULL, "Error opening SCM (%d)", ERRNO); } else if ((hService = OpenService(hSCM, SERVICE_NAME, DELETE)) == NULL) { _shttpd_elog(E_FATAL, NULL, "Error opening service (%d)", ERRNO); } else if (!DeleteService(hService)) { _shttpd_elog(E_FATAL, NULL, "Error deleting service (%d)", ERRNO); } else { _shttpd_elog(E_FATAL, NULL, "Service deleted"); } } else { _shttpd_elog(E_FATAL, NULL, "Use -service "); } /* NOTREACHED */ return (TRUE); } 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(int argc, char *argv[]) { char path[MAX_PATH], *p, *av[] = {"shttpd_service", path, NULL}; struct shttpd_ctx *ctx; ss.dwServiceType = SERVICE_WIN32; ss.dwCurrentState = SERVICE_RUNNING; ss.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; hStatus = RegisterServiceCtrlHandler(SERVICE_NAME, ControlHandler); SetServiceStatus(hStatus, &ss); GetModuleFileName(NULL, path, sizeof(path)); if ((p = strrchr(path, DIRSEP)) != NULL) *++p = '\0'; strcat(path, CONFIG_FILE); /* woo ! */ ctx = shttpd_init(NELEMS(av) - 1, av); if ((ctx = shttpd_init(NELEMS(av) - 1, av)) == NULL) _shttpd_elog(E_FATAL, NULL, "Cannot initialize SHTTP context"); while (ss.dwCurrentState == SERVICE_RUNNING) shttpd_poll(ctx, INT_MAX); shttpd_fini(ctx); ss.dwCurrentState = SERVICE_STOPPED; ss.dwWin32ExitCode = -1; SetServiceStatus(hStatus, &ss); } void try_to_run_as_nt_service(void) { static SERVICE_TABLE_ENTRY service_table[] = { {SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION) ServiceMain}, {NULL, NULL} }; if (StartServiceCtrlDispatcher(service_table)) exit(EXIT_SUCCESS); } #endif /* _WIN32 */