source: trunk/third-party/shttpd/compat_win32.c @ 6644

Last change on this file since 6644 was 6644, checked in by charles, 13 years ago

upgrade to shttpd 1.42

File size: 15.7 KB
Line 
1/*
2 * Copyright (c) 2004-2005 Sergey Lyubka <valenok@gmail.com>
3 * All rights reserved
4 *
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.
9 */
10
11#include "defs.h"
12
13static SERVICE_STATUS           ss; 
14static SERVICE_STATUS_HANDLE    hStatus; 
15static SERVICE_DESCRIPTION      service_descr = {"Web server"};
16
17static void
18fix_directory_separators(char *path)
19{
20        for (; *path != '\0'; path++) {
21                if (*path == '/')
22                        *path = '\\';
23                if (*path == '\\')
24                        while (path[1] == '\\' || path[1] == '/') 
25                                (void) memmove(path + 1,
26                                    path + 2, strlen(path + 2) + 1);
27        }
28}
29
30static int
31protect_against_code_disclosure(const wchar_t *path)
32{
33        WIN32_FIND_DATAW        data;
34        HANDLE                  handle;
35        const wchar_t           *p;
36
37        /*
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
45         * end of file name.
46         */
47
48        if ((handle = FindFirstFileW(path, &data)) == INVALID_HANDLE_VALUE)
49                return (FALSE);
50
51        FindClose(handle);
52
53        for (p = path + wcslen(path); p > path && p[-1] != L'\\';)
54                p--;
55       
56        if (!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
57            wcscmp(data.cFileName, p) != 0)
58                return (FALSE);
59
60        return (TRUE);
61}
62
63int
64_shttpd_open(const char *path, int flags, int mode)
65{
66        char    buf[FILENAME_MAX];
67        wchar_t wbuf[FILENAME_MAX];
68
69        _shttpd_strlcpy(buf, path, sizeof(buf));
70        fix_directory_separators(buf);
71        MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf));
72
73        if (protect_against_code_disclosure(wbuf) == FALSE)
74                return (-1);
75
76        return (_wopen(wbuf, flags));
77}
78
79int
80_shttpd_stat(const char *path, struct stat *stp)
81{
82        char    buf[FILENAME_MAX], *p;
83        wchar_t wbuf[FILENAME_MAX];
84
85        _shttpd_strlcpy(buf, path, sizeof(buf));
86        fix_directory_separators(buf);
87
88        p = buf + strlen(buf) - 1;
89        while (p > buf && *p == '\\' && p[-1] != ':')
90                *p-- = '\0';
91
92        MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf));
93
94        return (_wstat(wbuf, (struct _stat *) stp));
95}
96
97int
98_shttpd_remove(const char *path)
99{
100        char    buf[FILENAME_MAX];
101        wchar_t wbuf[FILENAME_MAX];
102
103        _shttpd_strlcpy(buf, path, sizeof(buf));
104        fix_directory_separators(buf);
105
106        MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf));
107
108        return (_wremove(wbuf));
109}
110
111int
112_shttpd_rename(const char *path1, const char *path2)
113{
114        char    buf1[FILENAME_MAX];
115        char    buf2[FILENAME_MAX];
116        wchar_t wbuf1[FILENAME_MAX];
117        wchar_t wbuf2[FILENAME_MAX];
118
119        _shttpd_strlcpy(buf1, path1, sizeof(buf1));
120        _shttpd_strlcpy(buf2, path2, sizeof(buf2));
121        fix_directory_separators(buf1);
122        fix_directory_separators(buf2);
123
124        MultiByteToWideChar(CP_UTF8, 0, buf1, -1, wbuf1, sizeof(wbuf1));
125        MultiByteToWideChar(CP_UTF8, 0, buf2, -1, wbuf2, sizeof(wbuf2));
126
127        return (_wrename(wbuf1, wbuf2));
128}
129
130int
131_shttpd_mkdir(const char *path, int mode)
132{
133        char    buf[FILENAME_MAX];
134        wchar_t wbuf[FILENAME_MAX];
135
136        _shttpd_strlcpy(buf, path, sizeof(buf));
137        fix_directory_separators(buf);
138
139        MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf));
140
141        return (_wmkdir(wbuf));
142}
143
144static char *
145wide_to_utf8(const wchar_t *str)
146{
147        char *buf = NULL;
148        if (str) {
149                int nchar = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL);
150                if (nchar > 0) {
151                        buf = malloc(nchar);
152                        if (!buf)
153                                errno = ENOMEM;
154                        else if (!WideCharToMultiByte(CP_UTF8, 0, str, -1, buf, nchar, NULL, NULL)) {
155                                free(buf);
156                                buf = NULL;
157                                errno = EINVAL;
158                        }
159                } else
160                        errno = EINVAL;
161        } else
162                errno = EINVAL;
163        return buf;
164}
165
166char *
167_shttpd_getcwd(char *buffer, int maxlen)
168{
169        char *result = NULL;
170        wchar_t *wbuffer, *wresult;
171
172        if (buffer) {
173                /* User-supplied buffer */
174                wbuffer = malloc(maxlen * sizeof(wchar_t));
175                if (wbuffer == NULL)
176                        return NULL;
177        } else
178                /* Dynamically allocated buffer */
179                wbuffer = NULL;
180        wresult = _wgetcwd(wbuffer, maxlen);
181        if (wresult) {
182                int err = errno;
183                if (buffer) {
184                        /* User-supplied buffer */
185                        int n = WideCharToMultiByte(CP_UTF8, 0, wresult, -1, buffer, maxlen, NULL, NULL);
186                        if (n == 0)
187                                err = ERANGE;
188                        free(wbuffer);
189                        result = buffer;
190                } else {
191                        /* Buffer allocated by _wgetcwd() */
192                        result = wide_to_utf8(wresult);
193                        err = errno;
194                        free(wresult);
195                }
196                errno = err;
197        }
198        return result;
199}
200
201DIR *
202opendir(const char *name)
203{
204        DIR             *dir = NULL;
205        char            path[FILENAME_MAX];
206        wchar_t         wpath[FILENAME_MAX];
207
208        if (name == NULL || name[0] == '\0') {
209                errno = EINVAL;
210        } else if ((dir = malloc(sizeof(*dir))) == NULL) {
211                errno = ENOMEM;
212        } else {
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);
217
218                if (dir->handle != INVALID_HANDLE_VALUE) {
219                        dir->result.d_name[0] = '\0';
220                } else {
221                        free(dir);
222                        dir = NULL;
223                }
224        }
225
226        return (dir);
227}
228
229int
230closedir(DIR *dir)
231{
232        int result = -1;
233
234        if (dir != NULL) {
235                if (dir->handle != INVALID_HANDLE_VALUE)
236                        result = FindClose(dir->handle) ? 0 : -1;
237
238                free(dir);
239        }
240
241        if (result == -1) 
242                errno = EBADF;
243
244        return (result);
245}
246
247struct dirent *
248readdir(DIR *dir)
249{
250        struct dirent *result = 0;
251
252        if (dir && dir->handle != INVALID_HANDLE_VALUE) {
253                if(!dir->result.d_name ||
254                    FindNextFileW(dir->handle, &dir->info)) {
255                        result = &dir->result;
256
257                        WideCharToMultiByte(CP_UTF8, 0, dir->info.cFileName,
258                            -1, result->d_name,
259                            sizeof(result->d_name), NULL, NULL);
260                }
261        } else {
262                errno = EBADF;
263        }
264
265        return (result);
266}
267
268int
269_shttpd_set_non_blocking_mode(int fd)
270{
271        unsigned long   on = 1;
272
273        return (ioctlsocket(fd, FIONBIO, &on));
274}
275
276void
277_shttpd_set_close_on_exec(int fd)
278{
279        fd = 0; /* Do nothing. There is no FD_CLOEXEC on Windows */
280}
281
282#if !defined(NO_CGI)
283
284struct threadparam {
285        SOCKET  s;
286        HANDLE  hPipe;
287        big_int_t content_len;
288};
289
290
291enum ready_mode_t {IS_READY_FOR_READ, IS_READY_FOR_WRITE};
292
293/*
294 * Wait until given socket is in ready state. Always return TRUE.
295 */
296static int
297is_socket_ready(int sock, enum ready_mode_t mode)
298{
299        fd_set          read_set, write_set;
300
301        FD_ZERO(&read_set);
302        FD_ZERO(&write_set);
303
304        if (mode == IS_READY_FOR_READ)
305                FD_SET(sock, &read_set);
306        else
307                FD_SET(sock, &write_set);
308
309        select(sock + 1, &read_set, &write_set, NULL, NULL);
310
311        return (TRUE);
312}
313
314/*
315 * Thread function that reads POST data from the socket pair
316 * and writes it to the CGI process.
317 */
318static void//DWORD WINAPI
319stdoutput(void *arg)
320{
321        struct threadparam      *tp = arg;
322        int                     n, sent, stop = 0;
323        big_int_t               total = 0;
324        DWORD k;
325        char                    buf[BUFSIZ];
326        size_t                  max_recv;
327
328        max_recv = min(sizeof(buf), tp->content_len - total);
329        while (!stop &&
330            max_recv > 0 &&
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)
334                        continue;
335                for (sent = 0; !stop && sent < n; sent += k)
336                        if (!WriteFile(tp->hPipe, buf + sent, n - sent, &k, 0))
337                                stop++;
338                total += n;
339                max_recv = min(sizeof(buf), tp->content_len - total);
340        }
341       
342        CloseHandle(tp->hPipe); /* Suppose we have POSTed everything */
343        free(tp);
344}
345
346/*
347 * Thread function that reads CGI output and pushes it to the socket pair.
348 */
349static void
350stdinput(void *arg)
351{
352        struct threadparam      *tp = arg;
353        static                  int ntotal;
354        int                     k, stop = 0;
355        DWORD n, sent;
356        char                    buf[BUFSIZ];
357
358        while (!stop && ReadFile(tp->hPipe, buf, sizeof(buf), &n, NULL)) {
359                ntotal += n;
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) {
364                                        k = 0;
365                                        continue;
366                                }
367                                stop++;
368                        }
369                }
370        }
371        CloseHandle(tp->hPipe);
372       
373        /*
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.
383         */
384        shutdown(tp->s, 2);
385
386        closesocket(tp->s);
387        free(tp);
388
389        _endthread();
390}
391
392static void
393spawn_stdio_thread(int sock, HANDLE hPipe, void (*func)(void *),
394                big_int_t content_len)
395{
396        struct threadparam      *tp;
397        DWORD                   tid;
398
399        tp = malloc(sizeof(*tp));
400        assert(tp != NULL);
401
402        tp->s           = sock;
403        tp->hPipe       = hPipe;
404        tp->content_len = content_len;
405        _beginthread(func, 0, tp);
406}
407
408int
409_shttpd_spawn_process(struct conn *c, const char *prog, char *envblk,
410                char *envp[], int sock, const char *dir)
411{
412        HANDLE  a[2], b[2], h[2], me;
413        DWORD   flags;
414        char    *p, *interp, cmdline[FILENAME_MAX], line[FILENAME_MAX];
415        FILE    *fp;
416        STARTUPINFOA            si;
417        PROCESS_INFORMATION     pi;
418
419        me = GetCurrentProcess();
420        flags = DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS;
421
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);
427       
428        (void) memset(&si, 0, sizeof(si));
429        (void) memset(&pi, 0, sizeof(pi));
430
431        /* XXX redirect CGI errors to the error log file */
432        si.cb           = sizeof(si);
433        si.dwFlags      = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
434        si.wShowWindow  = SW_HIDE;
435        si.hStdOutput   = h[1];
436        si.hStdInput    = h[0];
437
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)
444                                line[2] = '\0';
445                        /* Trim whitespaces from interpreter name */
446                        for (p = &line[strlen(line) - 1]; p > line &&
447                            isspace(*p); p--)
448                                *p = '\0';
449                        (void) fclose(fp);
450                }
451                interp = line + 2;
452                (void) _shttpd_snprintf(cmdline, sizeof(cmdline), "%s%s%s",
453                    line + 2, line[2] == '\0' ? "" : " ", prog);
454        }
455
456        if ((p = strrchr(prog, '/')) != NULL)
457                prog = p + 1;
458
459        (void) _shttpd_snprintf(cmdline, sizeof(cmdline), "%s %s", interp, prog);
460
461        (void) _shttpd_snprintf(line, sizeof(line), "%s", dir);
462        fix_directory_separators(line);
463        fix_directory_separators(cmdline);
464
465        /*
466         * Spawn reader & writer threads before we create CGI process.
467         * Otherwise CGI process may die too quickly, loosing the data
468         */
469        spawn_stdio_thread(sock, b[0], stdinput, 0);
470        spawn_stdio_thread(sock, a[1], stdoutput, c->rem.content_len);
471
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);
476                return (-1);
477        } else {
478                CloseHandle(h[0]);
479                CloseHandle(h[1]);
480                CloseHandle(pi.hThread);
481                CloseHandle(pi.hProcess);
482        }
483
484        return (0);
485}
486
487#endif /* !NO_CGI */
488
489#define ID_TRAYICON     100
490#define ID_QUIT         101
491static NOTIFYICONDATA   ni;
492
493static LRESULT CALLBACK
494WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
495{
496        POINT   pt;
497        HMENU   hMenu;   
498
499        switch (msg) {
500        case WM_COMMAND:
501                switch (LOWORD(wParam)) {
502                case ID_QUIT:
503                        exit(EXIT_SUCCESS);
504                        break;
505                }
506                break;
507        case WM_USER:
508                switch (lParam) {
509                case WM_RBUTTONUP:
510                case WM_LBUTTONUP:
511                case WM_LBUTTONDBLCLK:
512                        hMenu = CreatePopupMenu();
513                        AppendMenu(hMenu, 0, ID_QUIT, "Exit SHTTPD");
514                        GetCursorPos(&pt);
515                        TrackPopupMenu(hMenu, 0, pt.x, pt.y, 0, hWnd, NULL);
516                        DestroyMenu(hMenu);
517                        break;
518                }
519                break;
520        }
521
522        return (DefWindowProc(hWnd, msg, wParam, lParam));
523}
524
525static void
526systray(void *arg)
527{
528        WNDCLASS        cls;
529        HWND            hWnd;
530        MSG             msg;
531
532        (void) memset(&cls, 0, sizeof(cls));
533
534        cls.lpfnWndProc = (WNDPROC) WindowProc; 
535        cls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
536        cls.lpszClassName = "shttpd v." VERSION; 
537
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);
544       
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);
549        ni.hWnd = hWnd;
550        _shttpd_snprintf(ni.szTip, sizeof(ni.szTip), "SHTTPD web server");
551        ni.uCallbackMessage = WM_USER;
552        Shell_NotifyIcon(NIM_ADD, &ni);
553
554        while (GetMessage(&msg, hWnd, 0, 0)) { 
555                TranslateMessage(&msg); 
556                DispatchMessage(&msg); 
557        }
558}
559
560int
561_shttpd_set_systray(struct shttpd_ctx *ctx, const char *opt)
562{
563        HWND            hWnd;
564        char            title[512];
565        static WNDPROC  oldproc;
566
567        if (!_shttpd_is_true(opt))
568                return (TRUE);
569
570        FreeConsole();
571        GetConsoleTitle(title, sizeof(title));
572        hWnd = FindWindow(NULL, title);
573        ShowWindow(hWnd, SW_HIDE);
574        _beginthread(systray, 0, hWnd);
575
576        return (TRUE);
577}
578
579int
580_shttpd_set_nt_service(struct shttpd_ctx *ctx, const char *action)
581{
582        SC_HANDLE       hSCM, hService;
583        char            path[FILENAME_MAX], key[128];
584        HKEY            hKey;
585        DWORD           dwData;
586
587
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);
592
593                GetModuleFileName(NULL, path, sizeof(path));
594
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);
599
600                if (!hService)
601                        _shttpd_elog(E_FATAL, NULL,
602                            "Error installing service (%d)", ERRNO);
603
604                ChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION,
605                    &service_descr);
606                _shttpd_elog(E_FATAL, NULL, "Service successfully installed");
607
608
609        } else if (!strcmp(action, "uninstall")) {
610
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);
621                } else {
622                        _shttpd_elog(E_FATAL, NULL, "Service deleted");
623                }
624
625        } else {
626                _shttpd_elog(E_FATAL, NULL, "Use -service <install|uninstall>");
627        }
628
629        /* NOTREACHED */
630        return (TRUE);
631}
632
633static void WINAPI
634ControlHandler(DWORD code) 
635{ 
636        if (code == SERVICE_CONTROL_STOP || code == SERVICE_CONTROL_SHUTDOWN) {
637                ss.dwWin32ExitCode      = 0; 
638                ss.dwCurrentState       = SERVICE_STOPPED; 
639        } 
640 
641        SetServiceStatus(hStatus, &ss);
642}
643
644static void WINAPI
645ServiceMain(int argc, char *argv[]) 
646{
647        char    path[MAX_PATH], *p, *av[] = {"shttpd_service", path, NULL};
648        struct shttpd_ctx       *ctx;
649
650        ss.dwServiceType      = SERVICE_WIN32; 
651        ss.dwCurrentState     = SERVICE_RUNNING; 
652        ss.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
653
654        hStatus = RegisterServiceCtrlHandler(SERVICE_NAME, ControlHandler);
655        SetServiceStatus(hStatus, &ss); 
656
657        GetModuleFileName(NULL, path, sizeof(path));
658
659        if ((p = strrchr(path, DIRSEP)) != NULL)
660                *++p = '\0';
661
662        strcat(path, CONFIG_FILE);      /* woo ! */
663
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");
667
668        while (ss.dwCurrentState == SERVICE_RUNNING)
669                shttpd_poll(ctx, INT_MAX);
670        shttpd_fini(ctx);
671
672        ss.dwCurrentState  = SERVICE_STOPPED; 
673        ss.dwWin32ExitCode = -1; 
674        SetServiceStatus(hStatus, &ss); 
675}
676
677void
678try_to_run_as_nt_service(void)
679{
680        static SERVICE_TABLE_ENTRY service_table[] = {
681                {SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION) ServiceMain},
682                {NULL, NULL}
683        };
684
685        if (StartServiceCtrlDispatcher(service_table))
686                exit(EXIT_SUCCESS);
687}
Note: See TracBrowser for help on using the repository browser.