transmission/third-party/shttpd/compat_win32.c

443 lines
10 KiB
C

/*
* Copyright (c) 2004-2005 Sergey Lyubka <valenok@gmail.com>
* 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 */