/* * 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. */ #include "defs.h" 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 char *path) { WIN32_FIND_DATA data; HANDLE handle; const char *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 = FindFirstFile(path, &data)) == INVALID_HANDLE_VALUE) return (FALSE); FindClose(handle); for (p = path + strlen(path); p > path && p[-1] != '\\';) p--; if (!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && strcmp(data.cFileName, p) != 0) return (FALSE); return (TRUE); } int my_open(const char *path, int flags, int mode) { char buf[FILENAME_MAX]; wchar_t wbuf[FILENAME_MAX]; my_strlcpy(buf, path, sizeof(buf)); fix_directory_separators(buf); MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf)); if (protect_against_code_disclosure(buf) == FALSE) return (-1); return (_wopen(wbuf, flags)); } int my_stat(const char *path, struct stat *stp) { char buf[FILENAME_MAX], *p; wchar_t wbuf[FILENAME_MAX]; my_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 my_remove(const char *path) { char buf[FILENAME_MAX]; wchar_t wbuf[FILENAME_MAX]; my_strlcpy(buf, path, sizeof(buf)); fix_directory_separators(buf); MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf)); return (_wremove(wbuf)); } int my_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]; my_strlcpy(buf1, path1, sizeof(buf1)); my_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 my_mkdir(const char *path, int mode) { char buf[FILENAME_MAX]; wchar_t wbuf[FILENAME_MAX]; my_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 * my_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 { my_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 set_non_blocking_mode(int fd) { unsigned long on = 1; return (ioctlsocket(fd, FIONBIO, &on)); } void 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; }; /* * 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 && (n = recv(tp->s, buf, max_recv, 0)) > 0) { 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 ((k = send(tp->s, buf + sent, n - sent, 0)) <= 0) 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 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, 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 = si.hStdError = h[1]; si.hStdInput = h[0]; /* If CGI file is a script, try to read the interpreter line */ if (c->ctx->options[OPT_CGI_INTERPRETER] == 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); } (void) my_snprintf(cmdline, sizeof(cmdline), "%s%s%s", line + 2, line[2] == '\0' ? "" : " ", prog); } else { (void) my_snprintf(cmdline, sizeof(cmdline), "%s %s", c->ctx->options[OPT_CGI_INTERPRETER], prog); } (void) my_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) { 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 */