2 * Copyright (c) 2004-2005 Sergey Lyubka <valenok@gmail.com>
5 * "THE BEER-WARE LICENSE" (Revision 42):
6 * Sergey Lyubka wrote this file. As long as you retain this notice you
7 * can do whatever you want with this stuff. If we meet some day, and you think
8 * this stuff is worth it, you can buy me a beer in return.
13 static SERVICE_STATUS ss;
14 static SERVICE_STATUS_HANDLE hStatus;
15 static SERVICE_DESCRIPTION service_descr = {"Web server"};
18 fix_directory_separators(char *path)
20 for (; *path != '\0'; path++) {
24 while (path[1] == '\\' || path[1] == '/')
25 (void) memmove(path + 1,
26 path + 2, strlen(path + 2) + 1);
31 protect_against_code_disclosure(const wchar_t *path)
33 WIN32_FIND_DATAW data;
38 * Protect against CGI code disclosure under Windows.
39 * This is very nasty hole. Windows happily opens files with
40 * some garbage in the end of file name. So fopen("a.cgi ", "r")
41 * actually opens "a.cgi", and does not return an error! And since
42 * "a.cgi " does not have valid CGI extension, this leads to
43 * the CGI code disclosure.
44 * To protect, here we delete all fishy characters from the
48 if ((handle = FindFirstFileW(path, &data)) == INVALID_HANDLE_VALUE)
53 for (p = path + wcslen(path); p > path && p[-1] != L'\\';)
56 if (!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
57 wcscmp(data.cFileName, p) != 0)
64 _shttpd_open(const char *path, int flags, int mode)
66 char buf[FILENAME_MAX];
67 wchar_t wbuf[FILENAME_MAX];
69 _shttpd_strlcpy(buf, path, sizeof(buf));
70 fix_directory_separators(buf);
71 MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf));
73 if (protect_against_code_disclosure(wbuf) == FALSE)
76 return (_wopen(wbuf, flags));
80 _shttpd_stat(const char *path, struct stat *stp)
82 char buf[FILENAME_MAX], *p;
83 wchar_t wbuf[FILENAME_MAX];
85 _shttpd_strlcpy(buf, path, sizeof(buf));
86 fix_directory_separators(buf);
88 p = buf + strlen(buf) - 1;
89 while (p > buf && *p == '\\' && p[-1] != ':')
92 MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf));
94 return (_wstat(wbuf, (struct _stat *) stp));
98 _shttpd_remove(const char *path)
100 char buf[FILENAME_MAX];
101 wchar_t wbuf[FILENAME_MAX];
103 _shttpd_strlcpy(buf, path, sizeof(buf));
104 fix_directory_separators(buf);
106 MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf));
108 return (_wremove(wbuf));
112 _shttpd_rename(const char *path1, const char *path2)
114 char buf1[FILENAME_MAX];
115 char buf2[FILENAME_MAX];
116 wchar_t wbuf1[FILENAME_MAX];
117 wchar_t wbuf2[FILENAME_MAX];
119 _shttpd_strlcpy(buf1, path1, sizeof(buf1));
120 _shttpd_strlcpy(buf2, path2, sizeof(buf2));
121 fix_directory_separators(buf1);
122 fix_directory_separators(buf2);
124 MultiByteToWideChar(CP_UTF8, 0, buf1, -1, wbuf1, sizeof(wbuf1));
125 MultiByteToWideChar(CP_UTF8, 0, buf2, -1, wbuf2, sizeof(wbuf2));
127 return (_wrename(wbuf1, wbuf2));
131 _shttpd_mkdir(const char *path, int mode)
133 char buf[FILENAME_MAX];
134 wchar_t wbuf[FILENAME_MAX];
136 _shttpd_strlcpy(buf, path, sizeof(buf));
137 fix_directory_separators(buf);
139 MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf));
141 return (_wmkdir(wbuf));
145 wide_to_utf8(const wchar_t *str)
149 int nchar = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL);
154 else if (!WideCharToMultiByte(CP_UTF8, 0, str, -1, buf, nchar, NULL, NULL)) {
167 _shttpd_getcwd(char *buffer, int maxlen)
170 wchar_t *wbuffer, *wresult;
173 /* User-supplied buffer */
174 wbuffer = malloc(maxlen * sizeof(wchar_t));
178 /* Dynamically allocated buffer */
180 wresult = _wgetcwd(wbuffer, maxlen);
184 /* User-supplied buffer */
185 int n = WideCharToMultiByte(CP_UTF8, 0, wresult, -1, buffer, maxlen, NULL, NULL);
191 /* Buffer allocated by _wgetcwd() */
192 result = wide_to_utf8(wresult);
202 opendir(const char *name)
205 char path[FILENAME_MAX];
206 wchar_t wpath[FILENAME_MAX];
208 if (name == NULL || name[0] == '\0') {
210 } else if ((dir = malloc(sizeof(*dir))) == NULL) {
213 _shttpd_snprintf(path, sizeof(path), "%s/*", name);
214 fix_directory_separators(path);
215 MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, sizeof(wpath));
216 dir->handle = FindFirstFileW(wpath, &dir->info);
218 if (dir->handle != INVALID_HANDLE_VALUE) {
219 dir->result.d_name[0] = '\0';
235 if (dir->handle != INVALID_HANDLE_VALUE)
236 result = FindClose(dir->handle) ? 0 : -1;
250 struct dirent *result = 0;
252 if (dir && dir->handle != INVALID_HANDLE_VALUE) {
253 if(!dir->result.d_name ||
254 FindNextFileW(dir->handle, &dir->info)) {
255 result = &dir->result;
257 WideCharToMultiByte(CP_UTF8, 0, dir->info.cFileName,
259 sizeof(result->d_name), NULL, NULL);
269 _shttpd_set_non_blocking_mode(int fd)
271 unsigned long on = 1;
273 return (ioctlsocket(fd, FIONBIO, &on));
277 _shttpd_set_close_on_exec(int fd)
279 fd = 0; /* Do nothing. There is no FD_CLOEXEC on Windows */
287 big_int_t content_len;
291 enum ready_mode_t {IS_READY_FOR_READ, IS_READY_FOR_WRITE};
294 * Wait until given socket is in ready state. Always return TRUE.
297 is_socket_ready(int sock, enum ready_mode_t mode)
299 fd_set read_set, write_set;
304 if (mode == IS_READY_FOR_READ)
305 FD_SET(sock, &read_set);
307 FD_SET(sock, &write_set);
309 select(sock + 1, &read_set, &write_set, NULL, NULL);
315 * Thread function that reads POST data from the socket pair
316 * and writes it to the CGI process.
318 static void//DWORD WINAPI
321 struct threadparam *tp = arg;
322 int n, sent, stop = 0;
328 max_recv = min(sizeof(buf), tp->content_len - total);
331 is_socket_ready(tp->s, IS_READY_FOR_READ) &&
332 (n = recv(tp->s, buf, max_recv, 0)) > 0) {
333 if (n == -1 && ERRNO == EWOULDBLOCK)
335 for (sent = 0; !stop && sent < n; sent += k)
336 if (!WriteFile(tp->hPipe, buf + sent, n - sent, &k, 0))
339 max_recv = min(sizeof(buf), tp->content_len - total);
342 CloseHandle(tp->hPipe); /* Suppose we have POSTed everything */
347 * Thread function that reads CGI output and pushes it to the socket pair.
352 struct threadparam *tp = arg;
358 while (!stop && ReadFile(tp->hPipe, buf, sizeof(buf), &n, NULL)) {
360 for (sent = 0; !stop && sent < n; sent += k) {
361 if (is_socket_ready(tp->s, IS_READY_FOR_WRITE) &&
362 (k = send(tp->s, buf + sent, n - sent, 0)) <= 0) {
363 if (k == -1 && ERRNO == EWOULDBLOCK) {
371 CloseHandle(tp->hPipe);
374 * Windows is a piece of crap. When this thread closes its end
375 * of the socket pair, the other end (get_cgi() function) may loose
376 * some data. I presume, this happens if get_cgi() is not fast enough,
377 * and the data written by this end does not "push-ed" to the other
378 * end socket buffer. So after closesocket() the remaining data is
379 * gone. If I put shutdown() before closesocket(), that seems to
380 * fix the problem, but I am not sure this is the right fix.
381 * XXX (submitted by James Marshall) we do not do shutdown() on UNIX.
382 * If fork() is called from user callback, shutdown() messes up things.
393 spawn_stdio_thread(int sock, HANDLE hPipe, void (*func)(void *),
394 big_int_t content_len)
396 struct threadparam *tp;
399 tp = malloc(sizeof(*tp));
404 tp->content_len = content_len;
405 _beginthread(func, 0, tp);
409 _shttpd_spawn_process(struct conn *c, const char *prog, char *envblk,
410 char *envp[], int sock, const char *dir)
412 HANDLE a[2], b[2], h[2], me;
414 char *p, *interp, cmdline[FILENAME_MAX], line[FILENAME_MAX];
417 PROCESS_INFORMATION pi;
419 me = GetCurrentProcess();
420 flags = DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS;
422 /* FIXME add error checking code here */
423 CreatePipe(&a[0], &a[1], NULL, 0);
424 CreatePipe(&b[0], &b[1], NULL, 0);
425 DuplicateHandle(me, a[0], me, &h[0], 0, TRUE, flags);
426 DuplicateHandle(me, b[1], me, &h[1], 0, TRUE, flags);
428 (void) memset(&si, 0, sizeof(si));
429 (void) memset(&pi, 0, sizeof(pi));
431 /* XXX redirect CGI errors to the error log file */
433 si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
434 si.wShowWindow = SW_HIDE;
435 si.hStdOutput = h[1];
438 /* If CGI file is a script, try to read the interpreter line */
439 interp = c->ctx->options[OPT_CGI_INTERPRETER];
440 if (interp == NULL) {
441 if ((fp = fopen(prog, "r")) != NULL) {
442 (void) fgets(line, sizeof(line), fp);
443 if (memcmp(line, "#!", 2) != 0)
445 /* Trim whitespaces from interpreter name */
446 for (p = &line[strlen(line) - 1]; p > line &&
452 (void) _shttpd_snprintf(cmdline, sizeof(cmdline), "%s%s%s",
453 line + 2, line[2] == '\0' ? "" : " ", prog);
456 if ((p = strrchr(prog, '/')) != NULL)
459 (void) _shttpd_snprintf(cmdline, sizeof(cmdline), "%s %s", interp, prog);
461 (void) _shttpd_snprintf(line, sizeof(line), "%s", dir);
462 fix_directory_separators(line);
463 fix_directory_separators(cmdline);
466 * Spawn reader & writer threads before we create CGI process.
467 * Otherwise CGI process may die too quickly, loosing the data
469 spawn_stdio_thread(sock, b[0], stdinput, 0);
470 spawn_stdio_thread(sock, a[1], stdoutput, c->rem.content_len);
472 if (CreateProcessA(NULL, cmdline, NULL, NULL, TRUE,
473 CREATE_NEW_PROCESS_GROUP, envblk, line, &si, &pi) == 0) {
474 _shttpd_elog(E_LOG, c,
475 "redirect: CreateProcess(%s): %d", cmdline, ERRNO);
480 CloseHandle(pi.hThread);
481 CloseHandle(pi.hProcess);
489 #define ID_TRAYICON 100
491 static NOTIFYICONDATA ni;
493 static LRESULT CALLBACK
494 WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
501 switch (LOWORD(wParam)) {
511 case WM_LBUTTONDBLCLK:
512 hMenu = CreatePopupMenu();
513 AppendMenu(hMenu, 0, ID_QUIT, "Exit SHTTPD");
515 TrackPopupMenu(hMenu, 0, pt.x, pt.y, 0, hWnd, NULL);
522 return (DefWindowProc(hWnd, msg, wParam, lParam));
532 (void) memset(&cls, 0, sizeof(cls));
534 cls.lpfnWndProc = (WNDPROC) WindowProc;
535 cls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
536 cls.lpszClassName = "shttpd v." VERSION;
538 if (!RegisterClass(&cls))
539 _shttpd_elog(E_FATAL, NULL, "RegisterClass: %d", ERRNO);
540 else if ((hWnd = CreateWindow(cls.lpszClassName, "",
541 WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, NULL, NULL, NULL, arg)) == NULL)
542 _shttpd_elog(E_FATAL, NULL, "CreateWindow: %d", ERRNO);
543 ShowWindow(hWnd, SW_HIDE);
545 ni.cbSize = sizeof(ni);
546 ni.uID = ID_TRAYICON;
547 ni.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
548 ni.hIcon = LoadIcon(NULL, IDI_APPLICATION);
550 _shttpd_snprintf(ni.szTip, sizeof(ni.szTip), "SHTTPD web server");
551 ni.uCallbackMessage = WM_USER;
552 Shell_NotifyIcon(NIM_ADD, &ni);
554 while (GetMessage(&msg, hWnd, 0, 0)) {
555 TranslateMessage(&msg);
556 DispatchMessage(&msg);
561 _shttpd_set_systray(struct shttpd_ctx *ctx, const char *opt)
565 static WNDPROC oldproc;
567 if (!_shttpd_is_true(opt))
571 GetConsoleTitle(title, sizeof(title));
572 hWnd = FindWindow(NULL, title);
573 ShowWindow(hWnd, SW_HIDE);
574 _beginthread(systray, 0, hWnd);
580 _shttpd_set_nt_service(struct shttpd_ctx *ctx, const char *action)
582 SC_HANDLE hSCM, hService;
583 char path[FILENAME_MAX], key[128];
588 if (!strcmp(action, "install")) {
589 if ((hSCM = OpenSCManager(NULL, NULL,
590 SC_MANAGER_ALL_ACCESS)) == NULL)
591 _shttpd_elog(E_FATAL, NULL, "Error opening SCM (%d)", ERRNO);
593 GetModuleFileName(NULL, path, sizeof(path));
595 hService = CreateService(hSCM, SERVICE_NAME, SERVICE_NAME,
596 SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
597 SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, path,
598 NULL, NULL, NULL, NULL, NULL);
601 _shttpd_elog(E_FATAL, NULL,
602 "Error installing service (%d)", ERRNO);
604 ChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION,
606 _shttpd_elog(E_FATAL, NULL, "Service successfully installed");
609 } else if (!strcmp(action, "uninstall")) {
611 if ((hSCM = OpenSCManager(NULL, NULL,
612 SC_MANAGER_ALL_ACCESS)) == NULL) {
613 _shttpd_elog(E_FATAL, NULL, "Error opening SCM (%d)", ERRNO);
614 } else if ((hService = OpenService(hSCM,
615 SERVICE_NAME, DELETE)) == NULL) {
616 _shttpd_elog(E_FATAL, NULL,
617 "Error opening service (%d)", ERRNO);
618 } else if (!DeleteService(hService)) {
619 _shttpd_elog(E_FATAL, NULL,
620 "Error deleting service (%d)", ERRNO);
622 _shttpd_elog(E_FATAL, NULL, "Service deleted");
626 _shttpd_elog(E_FATAL, NULL, "Use -service <install|uninstall>");
634 ControlHandler(DWORD code)
636 if (code == SERVICE_CONTROL_STOP || code == SERVICE_CONTROL_SHUTDOWN) {
637 ss.dwWin32ExitCode = 0;
638 ss.dwCurrentState = SERVICE_STOPPED;
641 SetServiceStatus(hStatus, &ss);
645 ServiceMain(int argc, char *argv[])
647 char path[MAX_PATH], *p, *av[] = {"shttpd_service", path, NULL};
648 struct shttpd_ctx *ctx;
650 ss.dwServiceType = SERVICE_WIN32;
651 ss.dwCurrentState = SERVICE_RUNNING;
652 ss.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
654 hStatus = RegisterServiceCtrlHandler(SERVICE_NAME, ControlHandler);
655 SetServiceStatus(hStatus, &ss);
657 GetModuleFileName(NULL, path, sizeof(path));
659 if ((p = strrchr(path, DIRSEP)) != NULL)
662 strcat(path, CONFIG_FILE); /* woo ! */
664 ctx = shttpd_init(NELEMS(av) - 1, av);
665 if ((ctx = shttpd_init(NELEMS(av) - 1, av)) == NULL)
666 _shttpd_elog(E_FATAL, NULL, "Cannot initialize SHTTP context");
668 while (ss.dwCurrentState == SERVICE_RUNNING)
669 shttpd_poll(ctx, INT_MAX);
672 ss.dwCurrentState = SERVICE_STOPPED;
673 ss.dwWin32ExitCode = -1;
674 SetServiceStatus(hStatus, &ss);
678 try_to_run_as_nt_service(void)
680 static SERVICE_TABLE_ENTRY service_table[] = {
681 {SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION) ServiceMain},
685 if (StartServiceCtrlDispatcher(service_table))