version 0.2.1
[fms.git] / libs / shttpd / compat_win32.c
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
13 static void
14 fix_directory_separators(char *path)
15 {
16         for (; *path != '\0'; path++) {
17                 if (*path == '/')
18                         *path = '\\';
19                 if (*path == '\\')
20                         while (path[1] == '\\' || path[1] == '/') 
21                                 (void) memmove(path + 1,
22                                     path + 2, strlen(path + 2) + 1);
23         }
24 }
25
26 static int
27 protect_against_code_disclosure(const char *path)
28 {
29         WIN32_FIND_DATA data;
30         HANDLE          handle;
31         const char      *p;
32
33         /*
34          * Protect against CGI code disclosure under Windows.
35          * This is very nasty hole. Windows happily opens files with
36          * some garbage in the end of file name. So fopen("a.cgi    ", "r")
37          * actually opens "a.cgi", and does not return an error! And since
38          * "a.cgi    " does not have valid CGI extension, this leads to
39          * the CGI code disclosure.
40          * To protect, here we delete all fishy characters from the
41          * end of file name.
42          */
43
44         if ((handle = FindFirstFile(path, &data)) == INVALID_HANDLE_VALUE)
45                 return (FALSE);
46
47         FindClose(handle);
48
49         for (p = path + strlen(path); p > path && p[-1] != '\\';)
50                 p--;
51         
52         if (!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
53             strcmp(data.cFileName, p) != 0)
54                 return (FALSE);
55
56         return (TRUE);
57 }
58
59 int
60 my_open(const char *path, int flags, int mode)
61 {
62         char    buf[FILENAME_MAX];
63         wchar_t wbuf[FILENAME_MAX];
64
65         my_strlcpy(buf, path, sizeof(buf));
66         fix_directory_separators(buf);
67         MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf));
68
69         if (protect_against_code_disclosure(buf) == FALSE)
70                 return (-1);
71
72         return (_wopen(wbuf, flags));
73 }
74
75 int
76 my_stat(const char *path, struct stat *stp)
77 {
78         char    buf[FILENAME_MAX], *p;
79         wchar_t wbuf[FILENAME_MAX];
80
81         my_strlcpy(buf, path, sizeof(buf));
82         fix_directory_separators(buf);
83
84         p = buf + strlen(buf) - 1;
85         while (p > buf && *p == '\\' && p[-1] != ':')
86                 *p-- = '\0';
87
88         MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf));
89
90         return (_wstat(wbuf, (struct _stat *) stp));
91 }
92
93 int
94 my_remove(const char *path)
95 {
96         char    buf[FILENAME_MAX];
97         wchar_t wbuf[FILENAME_MAX];
98
99         my_strlcpy(buf, path, sizeof(buf));
100         fix_directory_separators(buf);
101
102         MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf));
103
104         return (_wremove(wbuf));
105 }
106
107 int
108 my_rename(const char *path1, const char *path2)
109 {
110         char    buf1[FILENAME_MAX];
111         char    buf2[FILENAME_MAX];
112         wchar_t wbuf1[FILENAME_MAX];
113         wchar_t wbuf2[FILENAME_MAX];
114
115         my_strlcpy(buf1, path1, sizeof(buf1));
116         my_strlcpy(buf2, path2, sizeof(buf2));
117         fix_directory_separators(buf1);
118         fix_directory_separators(buf2);
119
120         MultiByteToWideChar(CP_UTF8, 0, buf1, -1, wbuf1, sizeof(wbuf1));
121         MultiByteToWideChar(CP_UTF8, 0, buf2, -1, wbuf2, sizeof(wbuf2));
122
123         return (_wrename(wbuf1, wbuf2));
124 }
125
126 int
127 my_mkdir(const char *path, int mode)
128 {
129         char    buf[FILENAME_MAX];
130         wchar_t wbuf[FILENAME_MAX];
131
132         my_strlcpy(buf, path, sizeof(buf));
133         fix_directory_separators(buf);
134
135         MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf));
136
137         return (_wmkdir(wbuf));
138 }
139
140 static char *
141 wide_to_utf8(const wchar_t *str)
142 {
143         char *buf = NULL;
144         if (str) {
145                 int nchar = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL);
146                 if (nchar > 0) {
147                         buf = malloc(nchar);
148                         if (!buf)
149                                 errno = ENOMEM;
150                         else if (!WideCharToMultiByte(CP_UTF8, 0, str, -1, buf, nchar, NULL, NULL)) {
151                                 free(buf);
152                                 buf = NULL;
153                                 errno = EINVAL;
154                         }
155                 } else
156                         errno = EINVAL;
157         } else
158                 errno = EINVAL;
159         return buf;
160 }
161
162 char *
163 my_getcwd(char *buffer, int maxlen)
164 {
165         char *result = NULL;
166         wchar_t *wbuffer, *wresult;
167
168         if (buffer) {
169                 /* User-supplied buffer */
170                 wbuffer = malloc(maxlen * sizeof(wchar_t));
171                 if (wbuffer == NULL)
172                         return NULL;
173         } else
174                 /* Dynamically allocated buffer */
175                 wbuffer = NULL;
176         wresult = _wgetcwd(wbuffer, maxlen);
177         if (wresult) {
178                 int err = errno;
179                 if (buffer) {
180                         /* User-supplied buffer */
181                         int n = WideCharToMultiByte(CP_UTF8, 0, wresult, -1, buffer, maxlen, NULL, NULL);
182                         if (n == 0)
183                                 err = ERANGE;
184                         free(wbuffer);
185                         result = buffer;
186                 } else {
187                         /* Buffer allocated by _wgetcwd() */
188                         result = wide_to_utf8(wresult);
189                         err = errno;
190                         free(wresult);
191                 }
192                 errno = err;
193         }
194         return result;
195 }
196
197 DIR *
198 opendir(const char *name)
199 {
200         DIR             *dir = NULL;
201         char            path[FILENAME_MAX];
202         wchar_t         wpath[FILENAME_MAX];
203
204         if (name == NULL || name[0] == '\0') {
205                 errno = EINVAL;
206         } else if ((dir = malloc(sizeof(*dir))) == NULL) {
207                 errno = ENOMEM;
208         } else {
209                 my_snprintf(path, sizeof(path), "%s/*", name);
210                 fix_directory_separators(path);
211                 MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, sizeof(wpath));
212                 dir->handle = FindFirstFileW(wpath, &dir->info);
213
214                 if (dir->handle != INVALID_HANDLE_VALUE) {
215                         dir->result.d_name[0] = '\0';
216                 } else {
217                         free(dir);
218                         dir = NULL;
219                 }
220         }
221
222         return (dir);
223 }
224
225 int
226 closedir(DIR *dir)
227 {
228         int result = -1;
229
230         if (dir != NULL) {
231                 if (dir->handle != INVALID_HANDLE_VALUE)
232                         result = FindClose(dir->handle) ? 0 : -1;
233
234                 free(dir);
235         }
236
237         if (result == -1) 
238                 errno = EBADF;
239
240         return (result);
241 }
242
243 struct dirent *
244 readdir(DIR *dir)
245 {
246         struct dirent *result = 0;
247
248         if (dir && dir->handle != INVALID_HANDLE_VALUE) {
249                 if(!dir->result.d_name ||
250                     FindNextFileW(dir->handle, &dir->info)) {
251                         result = &dir->result;
252
253                         WideCharToMultiByte(CP_UTF8, 0, dir->info.cFileName,
254                             -1, result->d_name,
255                             sizeof(result->d_name), NULL, NULL);
256                 }
257         } else {
258                 errno = EBADF;
259         }
260
261         return (result);
262 }
263
264 int
265 set_non_blocking_mode(int fd)
266 {
267         unsigned long   on = 1;
268
269         return (ioctlsocket(fd, FIONBIO, &on));
270 }
271
272 void
273 set_close_on_exec(int fd)
274 {
275         fd = 0; /* Do nothing. There is no FD_CLOEXEC on Windows */
276 }
277
278 #if !defined(NO_CGI)
279
280 struct threadparam {
281         SOCKET  s;
282         HANDLE  hPipe;
283         big_int_t content_len;
284 };
285
286 /*
287  * Thread function that reads POST data from the socket pair
288  * and writes it to the CGI process.
289  */
290 static void//DWORD WINAPI
291 stdoutput(void *arg)
292 {
293         struct threadparam      *tp = arg;
294         int                     n, sent, stop = 0;
295         big_int_t               total = 0;
296         DWORD k;
297         char                    buf[BUFSIZ];
298         size_t                  max_recv;
299
300         max_recv = min(sizeof(buf), tp->content_len - total);
301         while (!stop && max_recv > 0 && (n = recv(tp->s, buf, max_recv, 0)) > 0) {
302                 for (sent = 0; !stop && sent < n; sent += k)
303                         if (!WriteFile(tp->hPipe, buf + sent, n - sent, &k, 0))
304                                 stop++;
305                 total += n;
306                 max_recv = min(sizeof(buf), tp->content_len - total);
307         }
308         
309         CloseHandle(tp->hPipe); /* Suppose we have POSTed everything */
310         free(tp);
311 }
312
313 /*
314  * Thread function that reads CGI output and pushes it to the socket pair.
315  */
316 static void
317 stdinput(void *arg)
318 {
319         struct threadparam      *tp = arg;
320         static                  int ntotal;
321         int                     k, stop = 0;
322         DWORD n, sent;
323         char                    buf[BUFSIZ];
324
325         while (!stop && ReadFile(tp->hPipe, buf, sizeof(buf), &n, NULL)) {
326                 ntotal += n;
327                 for (sent = 0; !stop && sent < n; sent += k)
328                         if ((k = send(tp->s, buf + sent, n - sent, 0)) <= 0)
329                                 stop++;
330         }
331         CloseHandle(tp->hPipe);
332         
333         /*
334          * Windows is a piece of crap. When this thread closes its end
335          * of the socket pair, the other end (get_cgi() function) may loose
336          * some data. I presume, this happens if get_cgi() is not fast enough,
337          * and the data written by this end does not "push-ed" to the other
338          * end socket buffer. So after closesocket() the remaining data is
339          * gone. If I put shutdown() before closesocket(), that seems to
340          * fix the problem, but I am not sure this is the right fix.
341          * XXX (submitted by James Marshall) we do not do shutdown() on UNIX.
342          * If fork() is called from user callback, shutdown() messes up things.
343          */
344         shutdown(tp->s, 2);
345
346         closesocket(tp->s);
347         free(tp);
348
349         _endthread();
350 }
351
352 static void
353 spawn_stdio_thread(int sock, HANDLE hPipe, void (*func)(void *),
354                 big_int_t content_len)
355 {
356         struct threadparam      *tp;
357         DWORD                   tid;
358
359         tp = malloc(sizeof(*tp));
360         assert(tp != NULL);
361
362         tp->s           = sock;
363         tp->hPipe       = hPipe;
364         tp->content_len = content_len;
365         _beginthread(func, 0, tp);
366 }
367
368 int
369 spawn_process(struct conn *c, const char *prog, char *envblk,
370                 char *envp[], int sock, const char *dir)
371 {
372         HANDLE                  a[2], b[2], h[2], me;
373         DWORD                   flags;
374         char                    *p, cmdline[FILENAME_MAX], line[FILENAME_MAX];
375         FILE                    *fp;
376         STARTUPINFOA    si;
377         PROCESS_INFORMATION     pi;
378
379         me = GetCurrentProcess();
380         flags = DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS;
381
382         /* FIXME add error checking code here */
383         CreatePipe(&a[0], &a[1], NULL, 0);
384         CreatePipe(&b[0], &b[1], NULL, 0);
385         DuplicateHandle(me, a[0], me, &h[0], 0, TRUE, flags);
386         DuplicateHandle(me, b[1], me, &h[1], 0, TRUE, flags);
387         
388         (void) memset(&si, 0, sizeof(si));
389         (void) memset(&pi, 0, sizeof(pi));
390
391         /* XXX redirect CGI errors to the error log file */
392         si.cb           = sizeof(si);
393         si.dwFlags      = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
394         si.wShowWindow  = SW_HIDE;
395         si.hStdOutput   = si.hStdError = h[1];
396         si.hStdInput    = h[0];
397
398         /* If CGI file is a script, try to read the interpreter line */
399         if (c->ctx->options[OPT_CGI_INTERPRETER] == NULL) {
400                 if ((fp = fopen(prog, "r")) != NULL) {
401                         (void) fgets(line, sizeof(line), fp);
402                         if (memcmp(line, "#!", 2) != 0)
403                                 line[2] = '\0';
404                         /* Trim whitespaces from interpreter name */
405                         for (p = &line[strlen(line) - 1]; p > line &&
406                             isspace(*p); p--)
407                                 *p = '\0';
408                         (void) fclose(fp);
409                 }
410                 (void) my_snprintf(cmdline, sizeof(cmdline), "%s%s%s",
411                     line + 2, line[2] == '\0' ? "" : " ", prog);
412         } else {
413                 (void) my_snprintf(cmdline, sizeof(cmdline), "%s %s",
414                     c->ctx->options[OPT_CGI_INTERPRETER], prog);
415         }
416
417         (void) my_snprintf(line, sizeof(line), "%s", dir);
418         fix_directory_separators(line);
419         fix_directory_separators(cmdline);
420
421         /*
422          * Spawn reader & writer threads before we create CGI process.
423          * Otherwise CGI process may die too quickly, loosing the data
424          */
425         spawn_stdio_thread(sock, b[0], stdinput, 0);
426         spawn_stdio_thread(sock, a[1], stdoutput, c->rem.content_len);
427
428         if (CreateProcessA(NULL, cmdline, NULL, NULL, TRUE,
429             CREATE_NEW_PROCESS_GROUP, envblk, line, &si, &pi) == 0) {
430                 elog(E_LOG, c,"redirect: CreateProcess(%s): %d",cmdline,ERRNO);
431                 return (-1);
432         } else {
433                 CloseHandle(h[0]);
434                 CloseHandle(h[1]);
435                 CloseHandle(pi.hThread);
436                 CloseHandle(pi.hProcess);
437         }
438
439         return (0);
440 }
441
442 #endif /* !NO_CGI */