/* * Author: Manoj Ampalam <manoj.ampalam@microsoft.com> * * Author: Bryan Berns <berns@uwalumni.com> * Modified group detection use s4u token information * * Copyright(c) 2016 Microsoft Corp. * All rights reserved * * Misc Unix POSIX routine implementations for Windows * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met : * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and / or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define UMDF_USING_NTSTATUS #define SECURITY_WIN32 #include <Windows.h> #include <stdio.h> #include <time.h> #include <Shlwapi.h> #include <conio.h> #include <LM.h> #include <Sddl.h> #include <Aclapi.h> #include <Ntsecapi.h> #include <security.h> #include <ntstatus.h> #include "inc\unistd.h" #include "inc\sys\stat.h" #include "inc\sys\statvfs.h" #include "inc\sys\time.h" #include "misc_internal.h" #include "inc\dlfcn.h" #include "inc\dirent.h" #include "inc\sys\types.h" #include "inc\sys\ioctl.h" #include "inc\fcntl.h" #include "inc\utf.h" #include "signal_internal.h" #include "misc_internal.h" #include "debug.h" #include "w32fd.h" #include "inc\string.h" #include "inc\grp.h" #include "inc\time.h" #include <wchar.h> static char* s_programdir = NULL; /* Maximum reparse buffer info size. The max user defined reparse * data is 16KB, plus there's a header. */ #define MAX_REPARSE_SIZE 17000 #define IO_REPARSE_TAG_SYMBOLIC_LINK IO_REPARSE_TAG_RESERVED_ZERO #define IO_REPARSE_TAG_MOUNT_POINT (0xA0000003L) /* winnt ntifs */ #define IO_REPARSE_TAG_HSM (0xC0000004L) /* winnt ntifs */ #define IO_REPARSE_TAG_SIS (0x80000007L) /* winnt ntifs */ #define REPARSE_MOUNTPOINT_HEADER_SIZE 8 /* Difference in us between UNIX Epoch and Win32 Epoch */ #define EPOCH_DELTA 116444736000000000ULL /* in 100 nsecs intervals */ #define RATE_DIFF 10000000ULL /* 100 nsecs */ #define NSEC_IN_SEC 1000000000ULL // 10**9 #define USEC_IN_SEC 1000000ULL // 10**6 /* Windows CRT defines error string messages only till 43 in errno.h * This is an extended list that defines messages for EADDRINUSE through EWOULDBLOCK */ char* _sys_errlist_ext[] = { "Address already in use", /* EADDRINUSE 100 */ "Address not available", /* EADDRNOTAVAIL 101 */ "Address family not supported", /* EAFNOSUPPORT 102 */ "Connection already in progress", /* EALREADY 103 */ "Bad message", /* EBADMSG 104 */ "Operation canceled", /* ECANCELED 105 */ "Connection aborted", /* ECONNABORTED 106 */ "Connection refused", /* ECONNREFUSED 107 */ "Connection reset", /* ECONNRESET 108 */ "Destination address required", /* EDESTADDRREQ 109 */ "Host is unreachable", /* EHOSTUNREACH 110 */ "Identifier removed", /* EIDRM 111 */ "Operation in progress", /* EINPROGRESS 112 */ "Socket is connected", /* EISCONN 113 */ "Too many levels of symbolic links", /* ELOOP 114 */ "Message too long", /* EMSGSIZE 115 */ "Network is down", /* ENETDOWN 116 */ "Connection aborted by network", /* ENETRESET 117 */ "Network unreachable", /* ENETUNREACH 118 */ "No buffer space available", /* ENOBUFS 119 */ "No message is available on the STREAM head read queue",/* ENODATA 120 */ "Link has been severed", /* ENOLINK 121 */ "No message of the desired type", /* ENOMSG 122 */ "Protocol not available", /* ENOPROTOOPT 123 */ "No STREAM resources", /* ENOSR 124 */ "Not a STREAM", /* ENOSTR 125 */ "The socket is not connected", /* ENOTCONN 126 */ "enotrecoverable", /* ENOTRECOVERABLE 127 */ "Not a socket", /* ENOTSOCK 128 */ "Operation not supported", /* ENOTSUP 129 */ "Operation not supported on socket", /* EOPNOTSUPP 130 */ "eother", /* EOTHER 131 */ "Value too large to be stored in data type", /* EOVERFLOW 132 */ "eownerdead", /* EOWNERDEAD 133 */ "Protocol error", /* EPROTO 134 */ "Protocol not supported", /* EPROTONOSUPPORT 135 */ "Protocol wrong type for socket", /* EPROTOTYPE 136 */ "Timer expired", /* ETIME 137 */ "Connection timed out", /* ETIMEDOUT 138 */ "Text file busy", /* ETXTBSY 139 */ "Operation would block" /* EWOULDBLOCK 140 */ }; /* chroot state */ char* chroot_path = NULL; int chroot_path_len = 0; /* UTF-16 version of the above */ wchar_t* chroot_pathw = NULL; int usleep(unsigned int useconds) { Sleep(useconds / 1000); return 1; } static LONGLONG timespec_to_nsec(const struct timespec *req) { LONGLONG sec = req->tv_sec; return sec * NSEC_IN_SEC + req->tv_nsec; } int nanosleep(const struct timespec *req, struct timespec *rem) { HANDLE timer; LARGE_INTEGER li; if (req->tv_sec < 0 || req->tv_nsec < 0 || req->tv_nsec > 999999999) { errno = EINVAL; return -1; } if ((timer = CreateWaitableTimerW(NULL, TRUE, NULL)) == NULL) { errno = EFAULT; return -1; } /* convert timespec to 100ns intervals */ li.QuadPart = -(timespec_to_nsec(req) / 100); if (!SetWaitableTimer(timer, &li, 0, NULL, NULL, FALSE)) { CloseHandle(timer); errno = EFAULT; return -1; } /* TODO - use wait_for_any_event, since we want to wake up on interrupts*/ switch (WaitForSingleObject(timer, INFINITE)) { case WAIT_OBJECT_0: CloseHandle(timer); return 0; default: CloseHandle(timer); errno = EFAULT; return -1; } } /* This routine is contributed by * Author: NoMachine <developers@nomachine.com> * Copyright (c) 2009, 2010 NoMachine * All rights reserved */ int gettimeofday(struct timeval *tv, void *tz) { union { FILETIME ft; unsigned long long ns; } timehelper; unsigned long long us; /* Fetch time since Jan 1, 1601 in 100ns increments */ GetSystemTimeAsFileTime(&timehelper.ft); /* Remove the epoch difference & convert 100ns to us */ us = (timehelper.ns - EPOCH_DELTA) / 10; /* Stuff result into the timeval */ tv->tv_sec = (long)(us / USEC_IN_SEC); tv->tv_usec = (long)(us % USEC_IN_SEC); return 0; } void explicit_bzero(void *b, size_t len) { SecureZeroMemory(b, len); } static DWORD last_dlerror = ERROR_SUCCESS; HMODULE dlopen(const char *filename, int flags) { wchar_t *wfilename = utf8_to_utf16(filename); if (wfilename == NULL) { last_dlerror = ERROR_INVALID_PARAMETER; return NULL; } HMODULE module = LoadLibraryW(wfilename); if (module == NULL) last_dlerror = GetLastError(); free(wfilename); return module; } int dlclose(HMODULE handle) { FreeLibrary(handle); return 0; } void * dlsym(HMODULE handle, const char *symbol) { void *ptr = GetProcAddress(handle, symbol); if (ptr == NULL) last_dlerror = GetLastError(); return ptr; } char * dlerror() { static char *message = NULL; if (message != NULL) { free(message); message = NULL; } if (last_dlerror == ERROR_SUCCESS) return NULL; wchar_t *wmessage = NULL; DWORD length = FormatMessageW( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, last_dlerror, 0, (wchar_t *) &wmessage, 0, NULL); last_dlerror = ERROR_SUCCESS; if (length == 0) goto error; if (wmessage[length - 1] == L'\n') wmessage[length - 1] = L'\0'; if (length > 1 && wmessage[length - 2] == L'\r') wmessage[length - 2] = L'\0'; message = utf16_to_utf8(wmessage); LocalFree(wmessage); if (message == NULL) goto error; return message; error: return "Failed to format error message"; } /*fopen on Windows to mimic https://linux.die.net/man/3/fopen * only r, w, a are supported for now */ FILE * w32_fopen_utf8(const char *input_path, const char *mode) { wchar_t *wmode = NULL, *wpath = NULL; FILE* f = NULL; char utf8_bom[] = { 0xEF,0xBB,0xBF }; char first3_bytes[3]; int status = 1; errno_t r = 0; int nonfs_dev = 0; /* opening a non file system device */ if (mode == NULL || mode[1] != '\0') { errno = ENOTSUP; return NULL; } if(NULL == input_path) { errno = EINVAL; debug3("fopen - ERROR:%d", errno); return NULL; } /* if opening null device, point to Windows equivalent */ if (strncmp(input_path, NULL_DEVICE, sizeof(NULL_DEVICE)) == 0) { nonfs_dev = 1; wpath = utf8_to_utf16(NULL_DEVICE_WIN); } else wpath = resolved_path_utf16(input_path); wmode = utf8_to_utf16(mode); if (wpath == NULL || wmode == NULL) goto cleanup; if ((_wfopen_s(&f, wpath, wmode) != 0) || (f == NULL)) { debug3("Failed to open file:%S error:%d", wpath, errno); goto cleanup; } if (chroot_pathw && !nonfs_dev) { /* ensure final path is within chroot */ HANDLE h = (HANDLE)_get_osfhandle(_fileno(f)); if (!file_in_chroot_jail(h)) { debug3("%s is not in chroot jail", input_path); fclose(f); f = NULL; errno = EACCES; goto cleanup; } } /* BOM adjustments for file streams*/ if (mode[0] == 'w' && fseek(f, 0, SEEK_SET) != EBADF) { /* write UTF-8 BOM - should we ?*/ /*if (fwrite(utf8_bom, sizeof(utf8_bom), 1, f) != 1) { fclose(f); goto cleanup; }*/ } else if (mode[0] == 'r' && fseek(f, 0, SEEK_SET) != EBADF) { /* read out UTF-8 BOM if present*/ if (fread(first3_bytes, 3, 1, f) != 1 || memcmp(first3_bytes, utf8_bom, 3) != 0) { fseek(f, 0, SEEK_SET); } } cleanup: if (wpath) free(wpath); if (wmode) free(wmode); return f; } /* * fgets to support Unicode input * each UTF-16 char may bloat up to 4 utf-8 chars. We cannot determine if the length of * input unicode string until it is readed and converted to utf8 string. * There is a risk to miss on unicode char when last unicode char read from console * does not fit the remain space in str. use cautiously. */ char* w32_fgets(char *str, int n, FILE *stream) { if (!str || !n || !stream) return NULL; HANDLE h = (HANDLE)_get_osfhandle(_fileno(stream)); wchar_t* str_w = NULL; char *ret = NULL, *str_tmp = NULL, *cp = NULL; int actual_read = 0; errno_t r = 0; if (h != NULL && h != INVALID_HANDLE_VALUE && GetFileType(h) == FILE_TYPE_CHAR) { /* Allocate memory for one UTF-16 char (up to 4 bytes) and a terminate char (\0) */ if ((str_w = malloc(3 * sizeof(wchar_t))) == NULL) { errno = ENOMEM; goto cleanup; } /* prepare for Unicode input */ _setmode(_fileno(stream), O_U16TEXT); cp = str; /* * each UTF-16 char may bloat up to 4 utf-8 chars * read one wide chars at time from console and convert it to utf8 * stop reading until reach '\n' or the converted utf8 string length is n-1 */ do { if (str_tmp) free(str_tmp); if (fgetws(str_w, 2, stream) == NULL) goto cleanup; if ((str_tmp = utf16_to_utf8(str_w)) == NULL) { debug3("utf16_to_utf8 failed!"); errno = ENOMEM; goto cleanup; } if((actual_read + (int)strlen(str_tmp)) >= n) break; if ((r = memcpy_s(cp, n - actual_read, str_tmp, strlen(str_tmp))) != 0) { debug3("memcpy_s failed with error: %d.", r); goto cleanup; } actual_read += (int)strlen(str_tmp); cp += strlen(str_tmp); } while ((actual_read < n - 1) && *str_tmp != '\n'); *cp = '\0'; if (actual_read > n - 1) { /* shouldn't happen. but handling in case */ debug3("actual_read %d exceeds the limit:%d", actual_read, n-1); errno = EINVAL; goto cleanup; } ret = str; } else ret = fgets(str, n, stream); cleanup: if (str_w) free(str_w); if (str_tmp) free(str_tmp); return ret; } /* Account for differences between Unix's and Windows versions of setvbuf */ int w32_setvbuf(FILE *stream, char *buffer, int mode, size_t size) { /* BUG: setvbuf on console stream interferes with Unicode I/O */ HANDLE h = (HANDLE)_get_osfhandle(_fileno(stream)); if (h != NULL && h != INVALID_HANDLE_VALUE && GetFileType(h) == FILE_TYPE_CHAR) return 0; /* BUG: setvbuf on file stream is interfering with w32_fopen */ /* short circuit for now*/ return 0; /* * if size is 0, set no buffering. * Windows does not differentiate __IOLBF and _IOFBF */ if (size == 0) return setvbuf(stream, NULL, _IONBF, 0); else return setvbuf(stream, buffer, mode, size); } int daemon(int nochdir, int noclose) { FreeConsole(); return 0; } int w32_ioctl(int d, int request, ...) { va_list valist; va_start(valist, request); switch (request) { case TIOCGWINSZ: { struct winsize* wsize = va_arg(valist, struct winsize*); CONSOLE_SCREEN_BUFFER_INFO c_info; if (wsize == NULL || !GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &c_info)) { errno = EINVAL; return -1; } wsize->ws_col = c_info.dwSize.X; wsize->ws_row = c_info.srWindow.Bottom - c_info.srWindow.Top + 1; wsize->ws_xpixel = 640; wsize->ws_ypixel = 480; return 0; } default: errno = ENOTSUP; return -1; } } /* p should be at least 12 bytes long*/ void strmode(mode_t mode, char *p) { /* print type */ switch (mode & S_IFMT) { case S_IFDIR: /* directory */ *p++ = 'd'; break; case S_IFCHR: /* character special */ *p++ = 'c'; break; case S_IFREG: /* regular */ *p++ = '-'; break; case S_IFLNK: /* symbolic link */ *p++ = 'l'; break; #ifdef S_IFSOCK case S_IFSOCK: /* socket */ *p++ = 's'; break; #endif case _S_IFIFO: /* fifo */ *p++ = 'p'; break; default: /* unknown */ *p++ = '?'; break; } /* group, other are not applicable on the windows */ /* usr */ if (mode & S_IREAD) *p++ = 'r'; else *p++ = '-'; if (mode & S_IWRITE) *p++ = 'w'; else *p++ = '-'; if (mode & S_IEXEC) *p++ = 'x'; else *p++ = '-'; const char *permissions = "****** "; for(int i = 0; i < (int)strlen(permissions); i++) *p++ = permissions[i]; *p = '\0'; } int w32_chmod(const char *pathname, mode_t mode) { /* TODO - * _wchmod() doesn't behave like unix "chmod" command. * _wchmod() only toggles the read-only bit and it doesn't touch ACL. */ int ret; wchar_t *resolvedPathName_utf16 = resolved_path_utf16(pathname); if (resolvedPathName_utf16 == NULL) return -1; ret = _wchmod(resolvedPathName_utf16, mode); free(resolvedPathName_utf16); return ret; } int w32_chown(const char *pathname, unsigned int owner, unsigned int group) { /* TODO - implement this */ errno = EOPNOTSUPP; return -1; } int w32_fchown( int fd, unsigned int owner, unsigned int group) { /* TODO - implement this */ errno = EOPNOTSUPP; return -1; } /* Convert a UNIX time into a Windows file time */ void unix_time_to_file_time(ULONG t, LPFILETIME pft) { ULONGLONG ull; ull = UInt32x32To64(t, RATE_DIFF) + EPOCH_DELTA; pft->dwLowDateTime = (DWORD)ull; pft->dwHighDateTime = (DWORD)(ull >> 32); } /* Convert a Windows file time into a UNIX time_t */ void file_time_to_unix_time(const LPFILETIME pft, time_t * winTime) { *winTime = ((long long)pft->dwHighDateTime << 32) + pft->dwLowDateTime; *winTime -= EPOCH_DELTA; *winTime /= RATE_DIFF; /* Nano to seconds resolution */ } static BOOL is_root_or_empty(wchar_t * path) { wchar_t * path_start; int len; if (!path) return FALSE; len = (int)wcslen(path); if((len > 1) && __ascii_iswalpha(path[0]) && path[1] == L':') path_start = path + 2; else path_start = path; /*path like c:\, /, \ are root directory*/ if ((*path_start == L'\0') || ((*path_start == L'\\' || *path_start == L'/' ) && path_start[1] == L'\0')) return TRUE; return FALSE; } static BOOL has_executable_extension(wchar_t * path) { wchar_t * last_dot; if (!path) return FALSE; last_dot = wcsrchr(path, L'.'); if (!last_dot) return FALSE; if (_wcsnicmp(last_dot, L".exe", 4) != 0 && _wcsnicmp(last_dot, L".cmd", 4) != 0 && _wcsnicmp(last_dot, L".bat", 4) != 0 && _wcsnicmp(last_dot, L".com", 4) != 0) return FALSE; return TRUE; } int file_attr_to_st_mode(wchar_t * path, DWORD attributes) { int mode = S_IREAD; BOOL isReadOnlyFile = FALSE; if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0 || is_root_or_empty(path)) mode |= S_IFDIR | _S_IEXEC; else { mode |= S_IFREG; /* See if file appears to be an executable by checking its extension */ if (has_executable_extension(path)) mode |= _S_IEXEC; } if (!(attributes & FILE_ATTRIBUTE_READONLY)) mode |= S_IWRITE; else isReadOnlyFile = TRUE; // We don't populate the group permissions as its not applicable to windows OS. // propagate owner read/write/execute bits to other fields. mode |= get_others_file_permissions(path, isReadOnlyFile); return mode; } static int settimes(wchar_t * path, FILETIME *cretime, FILETIME *acttime, FILETIME *modtime) { HANDLE handle; handle = CreateFileW(path, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (handle == INVALID_HANDLE_VALUE) { /* TODO - convert Win32 error to errno */ errno = GetLastError(); debug3("w32_settimes - CreateFileW ERROR:%d", errno); return -1; } if (SetFileTime(handle, cretime, acttime, modtime) == 0) { errno = GetLastError(); debug3("w32_settimes - SetFileTime ERROR:%d", errno); CloseHandle(handle); return -1; } CloseHandle(handle); return 0; } int w32_utimes(const char *filename, struct timeval *tvp) { int ret; FILETIME acttime, modtime; wchar_t *resolvedPathName_utf16 = resolved_path_utf16(filename); if (resolvedPathName_utf16 == NULL) return -1; memset(&acttime, 0, sizeof(FILETIME)); memset(&modtime, 0, sizeof(FILETIME)); unix_time_to_file_time((ULONG)tvp[0].tv_sec, &acttime); unix_time_to_file_time((ULONG)tvp[1].tv_sec, &modtime); ret = settimes(resolvedPathName_utf16, NULL, &acttime, &modtime); free(resolvedPathName_utf16); return ret; } int w32_symlink(const char *target, const char *linkpath) { return fileio_symlink(target, linkpath); } int w32_link(const char *oldpath, const char *newpath) { return fileio_link(oldpath, newpath); } int w32_rename(const char *old_name, const char *new_name) { if (old_name == NULL || new_name == NULL) { errno = EFAULT; return -1; } wchar_t *resolvedOldPathName_utf16 = resolved_path_utf16(old_name); wchar_t *resolvedNewPathName_utf16 = resolved_path_utf16(new_name); if (NULL == resolvedOldPathName_utf16 || NULL == resolvedNewPathName_utf16) return -1; /* * To be consistent with POSIX rename(), * 1) if the new_name is file, then delete it so that _wrename will succeed. * 2) if the new_name is directory and it is empty then delete it so that _wrename will succeed. */ struct _stat64 st_new; struct _stat64 st_old; if ((fileio_stat(new_name, &st_new) != -1) && (fileio_stat(old_name, &st_old) != -1)) { if (((st_old.st_mode & _S_IFMT) == _S_IFREG) && ((st_new.st_mode & _S_IFMT) == _S_IFREG)) w32_unlink(new_name); if (((st_old.st_mode & _S_IFMT) == _S_IFDIR) && ((st_new.st_mode & _S_IFMT) == _S_IFDIR)) { DIR *dirp = opendir(new_name); if (NULL != dirp) { struct dirent *dp = readdir(dirp); closedir(dirp); if (dp == NULL) w32_rmdir(new_name); } } } int returnStatus = _wrename(resolvedOldPathName_utf16, resolvedNewPathName_utf16); free(resolvedOldPathName_utf16); free(resolvedNewPathName_utf16); return returnStatus; } int w32_unlink(const char *path) { wchar_t *resolvedPathName_utf16 = resolved_path_utf16(path); if (NULL == resolvedPathName_utf16) return -1; int returnStatus = _wunlink(resolvedPathName_utf16); free(resolvedPathName_utf16); return returnStatus; } int w32_rmdir(const char *path) { wchar_t *resolvedPathName_utf16 = resolved_path_utf16(path); if (NULL == resolvedPathName_utf16) return -1; int returnStatus = _wrmdir(resolvedPathName_utf16); free(resolvedPathName_utf16); return returnStatus; } int w32_chdir(const char *dirname_utf8) { wchar_t *dirname_utf16 = resolved_path_utf16(dirname_utf8); if (dirname_utf16 == NULL) return -1; int returnStatus = _wchdir(dirname_utf16); free(dirname_utf16); return returnStatus; } char * w32_getcwd(char *buffer, int maxlen) { if(!buffer) return NULL; wchar_t wdirname[PATH_MAX]; char* putf8 = NULL; if (_wgetcwd(wdirname, PATH_MAX) == NULL) return NULL; if ((putf8 = utf16_to_utf8(wdirname)) == NULL) { errno = ENOMEM; return NULL; } if ((int)strlen(putf8) >= maxlen) { errno = ERANGE; free(putf8); return NULL; } if (strcpy_s(buffer, maxlen, putf8)) return NULL; free(putf8); to_lower_case(buffer); if (chroot_path) { /* ensure we are within chroot jail */ char c = buffer[chroot_path_len]; if ((int)strlen(buffer) < chroot_path_len || memcmp(chroot_path, buffer, chroot_path_len) != 0 || (c != '\0' && c!= '\\') ) { errno = EOTHER; error("cwd is not currently within chroot"); return NULL; } /* is cwd chroot ?*/ if (c == '\0') { buffer[0] = '\\'; buffer[1] = '\0'; } else { char *tail = buffer + chroot_path_len; memmove_s(buffer, maxlen, tail, strlen(tail) + 1); } } return buffer; } int w32_mkdir(const char *path_utf8, unsigned short mode) { int curmask; wchar_t *path_utf16 = resolved_path_utf16(path_utf8); if (path_utf16 == NULL) return -1; int returnStatus = _wmkdir(path_utf16); if (returnStatus < 0) { free(path_utf16); return -1; } errno_t error = _umask_s(0, &curmask); if(!error) _umask_s(curmask, &curmask); returnStatus = _wchmod(path_utf16, mode & ~curmask & (_S_IREAD | _S_IWRITE)); free(path_utf16); return returnStatus; } int w32_stat(const char *input_path, struct w32_stat *buf) { return fileio_stat(input_path, (struct _stat64*)buf); } int w32_lstat(const char *input_path, struct w32_stat *buf) { return fileio_lstat(input_path, (struct _stat64*)buf); } /* if file is symbolic link, copy its link into "link" */ int w32_readlink(const char *path, char *link, int linklen) { return fileio_readlink(path, link, linklen); } /* convert forward slash to back slash */ void convertToBackslash(char *str) { while (*str) { if (*str == '/') *str = '\\'; str++; } } void convertToBackslashW(wchar_t *str) { while (*str) { if (*str == L'/') *str = L'\\'; str++; } } /* convert back slash to forward slash */ void convertToForwardslash(char *str) { while (*str) { if (*str == '\\') *str = '/'; str++; } } /* * This method will resolves references to /./, /../ and extra '/' characters in the null-terminated string named by * path to produce a canonicalized absolute pathname. */ char * realpath(const char *inputpath, char * resolved) { wchar_t* temppath_utf16 = NULL; wchar_t* resolved_utf16 = NULL; char path[PATH_MAX] = { 0, }, tempPath[PATH_MAX] = { 0, }, *ret = NULL; int is_win_path = 1; if (!inputpath || !resolved) return NULL; size_t path_len = strlen(inputpath); resolved[0] = '\0'; if (path_len > PATH_MAX) { errno = EINVAL; return NULL; } if (is_bash_test_env() && bash_to_win_path(inputpath, path, _countof(path))) is_win_path = 0; if (is_win_path) { if (_strnicmp(inputpath, PROGRAM_DATA, strlen(PROGRAM_DATA)) == 0) { strcpy_s(path, PATH_MAX, __progdata); strcat_s(path, PATH_MAX, &inputpath[strlen(PROGRAM_DATA)]); } else { memcpy_s(path, PATH_MAX, inputpath, strlen(inputpath)); } } path_len = strlen(path); if (path_len > PATH_MAX) { errno = EINVAL; return NULL; } /* resolve root directory to the same */ if (path_len == 1 && (path[0] == '/' || path[0] == '\\')) { resolved[0] = '/'; resolved[1] = '\0'; ret = resolved; goto done; } /* resolve this common case scenario to root */ /* "cd .." from within a drive root */ if (path_len == 6 && !chroot_path) { char *tmplate = "/x:/.."; strcat_s(resolved, PATH_MAX, path); resolved[1] = 'x'; if (strcmp(tmplate, resolved) == 0) { resolved[0] = '/'; resolved[1] = '\0'; ret = resolved; goto done; } } if (chroot_path) { resolved[0] = '\0'; strcat_s(resolved, PATH_MAX, chroot_path); /* if path is relative, add cwd within chroot */ if (path[0] != '/' && path[0] != '\\') { w32_getcwd(resolved + chroot_path_len, PATH_MAX - chroot_path_len); strcat_s(resolved, PATH_MAX, "/"); } /* TODO - This logic will fail if the chroot_path is more than PATH_MAX/2. * resolved variable is of PATH_MAX. * We first copy chroot_path to resolved variable then incoming path (which can be again chroot_path). * In this case strcat_s will thrown a run time insufficient buffer exception. */ strcat_s(resolved, PATH_MAX, path); } else if ((path_len >= 2) && (path[0] == '/') && path[1] && (path[2] == ':')) { if((errno = strncpy_s(resolved, PATH_MAX, path + 1, path_len)) != 0 ) /* skip the first '/' */ { debug3("memcpy_s failed with error: %d.", errno); goto done; } } else if(( errno = strncpy_s(resolved, PATH_MAX, path, path_len + 1)) != 0) { debug3("memcpy_s failed with error: %d.", errno); goto done; } if ((resolved[0]) && (resolved[1] == ':') && (resolved[2] == '\0')) { /* make "x:" as "x:\\" */ resolved[2] = '\\'; resolved[3] = '\0'; } /* note: _wfullpath() is required to resolve paths containing unicode characters */ if ((resolved_utf16 = utf8_to_utf16(resolved)) == NULL || (temppath_utf16 = _wfullpath(NULL, resolved_utf16, 0)) == NULL || WideCharToMultiByte(CP_UTF8, 0, temppath_utf16, -1, tempPath, sizeof(tempPath), NULL, NULL) == 0) { errno = EINVAL; goto done; } if (chroot_path) { if (strlen(tempPath) < strlen(chroot_path)) { errno = EACCES; goto done; } if (memcmp(chroot_path, tempPath, strlen(chroot_path)) != 0) { errno = EACCES; goto done; } resolved[0] = '\0'; if (strlen(tempPath) == strlen(chroot_path)) /* realpath is the same as chroot_path */ strcat_s(resolved, PATH_MAX, "\\"); else strcat_s(resolved, PATH_MAX, tempPath + strlen(chroot_path)); if (resolved[0] != '\\') { errno = EACCES; goto done; } convertToForwardslash(resolved); ret = resolved; goto done; } else { convertToForwardslash(tempPath); resolved[0] = '/'; /* will be our first slash in /x:/users/test1 format */ if ((errno = strncpy_s(resolved + 1, PATH_MAX - 1, tempPath, sizeof(tempPath) - 1)) != 0) { debug3("memcpy_s failed with error: %d.", errno); goto done; } ret = resolved; goto done; } done: if (resolved_utf16 != NULL) free(resolved_utf16); if (temppath_utf16 != NULL) free(temppath_utf16); return ret; } /* on error returns NULL and sets errno */ char* resolved_path_utf8(const char *input_path) { wchar_t *resolved_path_w = resolved_path_utf16(input_path); char *resolved_path = NULL; if (resolved_path_w) { resolved_path = utf16_to_utf8(resolved_path_w); free(resolved_path_w); } return resolved_path; } /* on error returns NULL and sets errno */ wchar_t* resolved_path_utf16(const char *input_path) { wchar_t *resolved_path = NULL; char real_path[PATH_MAX]; if (!input_path) { errno = EINVAL; return NULL; } if (realpath(input_path, real_path) == NULL) return NULL; if (chroot_path) { char actual_path[PATH_MAX] = { 0 }; strcat_s(actual_path, _countof(actual_path), chroot_path); strcat_s(actual_path, _countof(actual_path), real_path); resolved_path = utf8_to_utf16(actual_path); } else { if ((strlen(real_path) == 1) && (real_path[0] == '/')) resolved_path = utf8_to_utf16(real_path); else resolved_path = utf8_to_utf16(real_path + 1); /* account for preceding / in real_path */ } return resolved_path; } int statvfs(const char *path, struct statvfs *buf) { DWORD sectorsPerCluster; DWORD bytesPerSector; DWORD freeClusters; DWORD totalClusters; wchar_t* path_utf16 = resolved_path_utf16(path); if (path_utf16 == NULL) return -1; if (GetDiskFreeSpaceW(path_utf16, §orsPerCluster, &bytesPerSector, &freeClusters, &totalClusters)) { debug5("path : [%s]", path); debug5("sectorsPerCluster : [%lu]", sectorsPerCluster); debug5("bytesPerSector : [%lu]", bytesPerSector); debug5("bytesPerCluster : [%lu]", sectorsPerCluster * bytesPerSector); debug5("freeClusters : [%lu]", freeClusters); debug5("totalClusters : [%lu]", totalClusters); buf->f_bsize = sectorsPerCluster * bytesPerSector; buf->f_frsize = sectorsPerCluster * bytesPerSector; buf->f_blocks = totalClusters; buf->f_bfree = freeClusters; buf->f_bavail = freeClusters; buf->f_files = -1; buf->f_ffree = -1; buf->f_favail = -1; buf->f_fsid = 0; buf->f_flag = 0; buf->f_namemax = PATH_MAX - 1; free(path_utf16); return 0; } else { debug5("ERROR: Cannot get free space for [%s]. Error code is : %d.", path, GetLastError()); errno = errno_from_Win32LastError(); free(path_utf16); return -1; } } int fstatvfs(int fd, struct statvfs *buf) { errno = ENOTSUP; return -1; } char * w32_strerror(int errnum) { if (errnum >= EADDRINUSE && errnum <= EWOULDBLOCK) return _sys_errlist_ext[errnum - EADDRINUSE]; strerror_s(errorBuf, ERROR_MSG_MAXLEN, errnum); return errorBuf; } char * readpassphrase(const char *prompt, char *outBuf, size_t outBufLen, int flags) { int current_index = 0; char ch; wchar_t* wtmp = NULL; if (outBufLen == 0) { errno = EINVAL; return NULL; } while (_kbhit()) _getch(); wtmp = utf8_to_utf16(prompt); if (wtmp == NULL) fatal("unable to alloc memory"); _cputws(wtmp); free(wtmp); while (current_index < (int)outBufLen - 1) { ch = _getch(); if (ch == '\r') { if (_kbhit()) _getch(); /* read linefeed if its there */ break; } else if (ch == '\n') { break; } else if (ch == '\b') { /* backspace */ if (current_index > 0) { if (flags & RPP_ECHO_ON) printf_s("%c \b", ch); current_index--; /* overwrite last character */ } } else if (ch == '\003') { /* exit on Ctrl+C */ fatal(""); } else { if (flags & RPP_SEVENBIT) ch &= 0x7f; if (isalpha((unsigned char)ch)) { if(flags & RPP_FORCELOWER) ch = tolower((unsigned char)ch); if(flags & RPP_FORCEUPPER) ch = toupper((unsigned char)ch); } outBuf[current_index++] = ch; if(flags & RPP_ECHO_ON) printf_s("%c", ch); } } outBuf[current_index] = '\0'; _cputs("\n"); return outBuf; } void invalid_parameter_handler(const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t pReserved) { debug3("Invalid parameter in function: %ls. File: %ls Line: %d.", function, file, line); debug3("Expression: %s", expression); } void to_lower_case(char *s) { for (; *s; s++) *s = tolower((u_char)*s); } void to_wlower_case(wchar_t *s) { for (; *s; s++) *s = towlower(*s); } static int get_final_mode(int allow_mode, int deny_mode) { // If deny permissions are not specified then return allow permissions. if (!deny_mode) return allow_mode; // If allow permissions are not specified then return allow permissions (0). if (!allow_mode) return allow_mode; if(deny_mode & S_IROTH) allow_mode = allow_mode & ~S_IROTH; if (deny_mode & S_IWOTH) allow_mode = allow_mode & ~S_IWOTH; if (deny_mode & S_IXOTH) allow_mode = allow_mode & ~S_IXOTH; return allow_mode; } int get_others_file_permissions(wchar_t * file_name, int isReadOnlyFile) { PSECURITY_DESCRIPTOR pSD = NULL; PSID owner_sid = NULL, current_trustee_sid = NULL; PACL dacl = NULL; DWORD error_code = ERROR_SUCCESS; BOOL is_valid_sid = FALSE, is_valid_acl = FALSE; int ret = 0, allow_mode_world = 0, allow_mode_auth_users = 0, deny_mode_world = 0, deny_mode_auth_users = 0; wchar_t *w_sid = NULL; /*Get the owner sid of the file.*/ if ((error_code = GetNamedSecurityInfoW(file_name, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, &owner_sid, NULL, &dacl, NULL, &pSD)) != ERROR_SUCCESS) { debug3("failed to retrieve the owner sid and dacl of file: %ls with error code: %d", file_name, error_code); goto cleanup; } if (((is_valid_sid = IsValidSid(owner_sid)) == FALSE) || dacl == NULL || ((is_valid_acl = IsValidAcl(dacl)) == FALSE)) { debug3("IsValidSid: %d; NULL Acl: %d; IsValidAcl: %d", is_valid_sid, dacl == NULL, is_valid_acl); goto cleanup; } for (DWORD i = 0; i < dacl->AceCount; i++) { PVOID current_ace = NULL; PACE_HEADER current_aceHeader = NULL; ACCESS_MASK current_access_mask = 0; int mode_tmp = 0; if (!GetAce(dacl, i, ¤t_ace)) { debug3("GetAce() failed"); goto cleanup; } current_aceHeader = (PACE_HEADER)current_ace; /* only interested in Allow ACE */ if (current_aceHeader->AceType == ACCESS_ALLOWED_ACE_TYPE) { PACCESS_ALLOWED_ACE pAllowedAce = (PACCESS_ALLOWED_ACE)current_ace; current_trustee_sid = &(pAllowedAce->SidStart); current_access_mask = pAllowedAce->Mask; } else if (current_aceHeader->AceType == ACCESS_DENIED_ACE_TYPE) { PACCESS_DENIED_ACE pDeniedAce = (PACCESS_DENIED_ACE)current_ace; current_trustee_sid = &(pDeniedAce->SidStart); current_access_mask = pDeniedAce->Mask; } else continue; if (!(IsWellKnownSid(current_trustee_sid, WinWorldSid) || IsWellKnownSid(current_trustee_sid, WinAuthenticatedUserSid))) continue; if ((current_access_mask & READ_PERMISSIONS) == READ_PERMISSIONS) mode_tmp |= S_IROTH; if (!isReadOnlyFile && ((current_access_mask & WRITE_PERMISSIONS) == WRITE_PERMISSIONS)) mode_tmp |= S_IWOTH; if ((current_access_mask & EXECUTE_PERMISSIONS) == EXECUTE_PERMISSIONS) mode_tmp |= S_IXOTH; if (IsWellKnownSid(current_trustee_sid, WinWorldSid)) { if(current_aceHeader->AceType == ACCESS_ALLOWED_ACE_TYPE) allow_mode_world |= mode_tmp; else deny_mode_world |= mode_tmp; } else if (IsWellKnownSid(current_trustee_sid, WinAuthenticatedUserSid)) { if (current_aceHeader->AceType == ACCESS_ALLOWED_ACE_TYPE) allow_mode_auth_users |= mode_tmp; else deny_mode_auth_users |= mode_tmp; } } allow_mode_world = get_final_mode(allow_mode_world, deny_mode_world); allow_mode_auth_users = get_final_mode(allow_mode_auth_users, deny_mode_auth_users); ret = allow_mode_world ? allow_mode_world : allow_mode_auth_users; cleanup: if (pSD) LocalFree(pSD); return ret; } /* Windows absolute paths - \abc, /abc, c:\abc, c:/abc, __PROGRAMDATA__\openssh\sshd_config */ int is_absolute_path(const char *path) { int retVal = 0; if(*path == '\"' || *path == '\'') /* skip double quote if path is "c:\abc" */ path++; if (*path == '/' || *path == '\\' || (*path != '\0' && isalpha(*path) && path[1] == ':') || ((strlen(path) >= strlen(PROGRAM_DATA)) && (memcmp(path, PROGRAM_DATA, strlen(PROGRAM_DATA)) == 0))) retVal = 1; return retVal; } /* return -1 - in case of failure, 0 - success */ int create_directory_withsddl(wchar_t *path_w, wchar_t *sddl_w) { if (GetFileAttributesW(path_w) == INVALID_FILE_ATTRIBUTES) { PSECURITY_DESCRIPTOR pSD = NULL; SECURITY_ATTRIBUTES sa; memset(&sa, 0, sizeof(SECURITY_ATTRIBUTES)); sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.bInheritHandle = FALSE; if (ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl_w, SDDL_REVISION, &pSD, NULL) == FALSE) { error("ConvertStringSecurityDescriptorToSecurityDescriptorW failed with error code %d", GetLastError()); return -1; } if (IsValidSecurityDescriptor(pSD) == FALSE) { error("IsValidSecurityDescriptor return FALSE"); return -1; } sa.lpSecurityDescriptor = pSD; if (!CreateDirectoryW(path_w, &sa)) { error("Failed to create directory:%ls error:%d", path_w, GetLastError()); return -1; } } return 0; } /* return -1 - in case of failure, 0 - success */ int copy_file(char *source, char *destination) { if (!source || !destination) return 0; struct stat st; if ((stat(source, &st) >= 0) && (stat(destination, &st) < 0)) { wchar_t *source_w = utf8_to_utf16(source); if (!source_w) { error("%s utf8_to_utf16() has failed to convert string:%s", __func__, source_w); return -1; } wchar_t *destination_w = utf8_to_utf16(destination); if (!destination_w) { error("%s utf8_to_utf16() has failed to convert string:%s", __func__, destination_w); return -1; } if (!CopyFileW(source_w, destination_w, FALSE)) { error("Failed to copy %ls to %ls, error:%d", source_w, destination_w, GetLastError()); return -1; } } return 0; } struct tm * localtime_r(const time_t *timep, struct tm *result) { return localtime_s(result, timep) == 0 ? result : NULL; } void freezero(void *ptr, size_t sz) { if (ptr == NULL) return; explicit_bzero(ptr, sz); free(ptr); } int setenv(const char *name, const char *value, int rewrite) { errno_t result = 0; /* If rewrite is 0, then set only if the variable name doesn't already exist in environment */ if (!rewrite) { char *envValue = NULL; size_t len = 0; _dupenv_s(&envValue, &len, name); if (envValue) return result; /* return success (as per setenv manpage) */ } if (!(result = _putenv_s(name, value))) return 0; else { error("failed to set the environment variable:%s to value:%s, error:%d", name, value, result); errno = result; return -1; } } int chroot(const char *path) { char cwd[MAX_PATH]; if (strcmp(path, ".") == 0) { if (w32_getcwd(cwd, MAX_PATH) == NULL) return -1; path = (const char *)cwd; } else if (*(path + 1) != ':') { errno = ENOTSUP; error("chroot only supports absolute paths"); return -1; } else { /* TODO - ensure path exists and is a directory */ } if ((chroot_path = _strdup(path)) == NULL) { errno = ENOMEM; return -1; } to_lower_case(chroot_path); convertToBackslash(chroot_path); /* strip trailing \ */ if (chroot_path[strlen(chroot_path) - 1] == '\\') chroot_path[strlen(chroot_path) - 1] = '\0'; chroot_path_len = (int) strlen(chroot_path); if ((chroot_pathw = utf8_to_utf16(chroot_path)) == NULL) { errno = ENOMEM; return -1; } /* TODO - set the env variable just in time in a posix_spawn_chroot like API */ #define POSIX_CHROOTW L"c28fc6f98a2c44abbbd89d6a3037d0d9_POSIX_CHROOT" _wputenv_s(POSIX_CHROOTW, chroot_pathw); return 0; } /* * Am I running as SYSTEM ? * a security sensitive call - fatal exits if it cannot definitively conclude */ int am_system() { HANDLE proc_token = NULL; DWORD info_len; TOKEN_USER* info = NULL; static int running_as_system = -1; if (running_as_system != -1) return running_as_system; if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &proc_token) == FALSE || GetTokenInformation(proc_token, TokenUser, NULL, 0, &info_len) == TRUE || (info = (TOKEN_USER*)malloc(info_len)) == NULL || GetTokenInformation(proc_token, TokenUser, info, info_len, &info_len) == FALSE) fatal("unable to know if I am running as system"); if (IsWellKnownSid(info->User.Sid, WinLocalSystemSid)) running_as_system = 1; else running_as_system = 0; CloseHandle(proc_token); free(info); return running_as_system; } /* * returns SID of user/group or current user if (user = NULL) * caller should free() return value */ PSID get_sid(const char* name) { HANDLE token = NULL; TOKEN_USER* info = NULL; DWORD info_len = 0; PSID ret = NULL, psid = NULL; wchar_t* name_utf16 = NULL; if (name) { DWORD sid_len = 0; SID_NAME_USE n_use; WCHAR dom[DNLEN + 1] = L""; DWORD dom_len = DNLEN + 1; if ((name_utf16 = utf8_to_utf16(name)) == NULL) goto cleanup; LookupAccountNameW(NULL, name_utf16, NULL, &sid_len, dom, &dom_len, &n_use); if (sid_len == 0) { errno = errno_from_Win32LastError(); goto cleanup; } if ((psid = malloc(sid_len)) == NULL) { errno = ENOMEM; goto cleanup; } if (!LookupAccountNameW(NULL, name_utf16, psid, &sid_len, dom, &dom_len, &n_use)) { errno = errno_from_Win32LastError(); goto cleanup; } } else { if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token) == FALSE || GetTokenInformation(token, TokenUser, NULL, 0, &info_len) == TRUE) { errno = EOTHER; goto cleanup; } if ((info = (TOKEN_USER*)malloc(info_len)) == NULL) { errno = ENOMEM; goto cleanup; } if (GetTokenInformation(token, TokenUser, info, info_len, &info_len) == FALSE) { errno = errno_from_Win32LastError(); goto cleanup; } if ((psid = malloc(GetLengthSid(info->User.Sid))) == NULL) { errno = ENOMEM; goto cleanup; } if (!CopySid(GetLengthSid(info->User.Sid), psid, info->User.Sid)) { errno = errno_from_Win32LastError(); goto cleanup; } } ret = psid; psid = NULL; cleanup: if (token) CloseHandle(token); if (name_utf16) free(name_utf16); if (psid) free(psid); if (info) free(info); return ret; } /* Interpret scp and sftp executables*/ char * build_exec_command(const char * command) { enum cmd_type { CMD_OTHER, CMD_SFTP, CMD_SCP } command_type = CMD_OTHER; char *cmd_sp = NULL; int len = 0, command_len; const char *command_args = NULL; if (!command) return NULL; command_len = (int)strlen(command); /*TODO - replace numbers below with readable compile time operators*/ if (command_len >= 13 && _memicmp(command, "internal-sftp", 13) == 0) { command_type = CMD_SFTP; command_args = command + 13; } else if (command_len >= 11 && _memicmp(command, "sftp-server", 11) == 0) { command_type = CMD_SFTP; /* account for possible .exe extension */ if (command_len >= 15 && _memicmp(command + 11, ".exe", 4) == 0) command_args = command + 15; else command_args = command + 11; } else if (command_len >= 3 && _memicmp(command, "scp", 3) == 0) { command_type = CMD_SCP; /* account for possible .exe extension */ if (command_len >= 7 && _memicmp(command + 3, ".exe", 4) == 0) command_args = command + 7; else command_args = command + 3; } len = command_len + 5; /* account for possible .exe addition and null term */ if ((cmd_sp = malloc(len)) == NULL) { errno = ENOMEM; return NULL; } memset(cmd_sp, '\0', len); if (command_type == CMD_SCP) { strcpy_s(cmd_sp, len, "scp.exe"); strcat_s(cmd_sp, len, command_args); } else if (command_type == CMD_SFTP) { strcpy_s(cmd_sp, len, "sftp-server.exe"); strcat_s(cmd_sp, len, command_args); } else strcpy_s(cmd_sp, len, command); return cmd_sp; } /* * cmd is internally decoarated with a set of '"' * to account for any spaces within the commandline * the double quotes and backslash is escaped if needed * this decoration is done only when additional arguments are passed in argv */ char * build_commandline_string(const char* cmd, char *const argv[], BOOLEAN prepend_module_path) { char *cmdline, *t, *tmp = NULL, *path = NULL, *ret = NULL; char * const *t1; DWORD cmdline_len = 0, path_len = 0; int add_module_path = 0; if (!cmd) { error("%s invalid argument cmd:%s", __func__, cmd); return NULL; } if (!(path = _strdup(cmd))) { error("failed to duplicate %s", cmd); return NULL; } path_len = (DWORD)strlen(path); if (is_bash_test_env()) { memset(path, 0, path_len + 1); bash_to_win_path(cmd, path, path_len + 1); } if (!is_absolute_path(path) && prepend_module_path) add_module_path = 1; /* compute total cmdline len*/ if (add_module_path) cmdline_len += (DWORD)strlen(__progdir) + 1 + (DWORD)strlen(path) + 1 + 2; else cmdline_len += (DWORD)strlen(path) + 1 + 2; if (argv) { t1 = argv; while (*t1) { char *p = *t1++; for (int i = 0; i < (int)strlen(p); i++) { if (p[i] == '\\') { char * b = p + i; int additional_backslash = 0; int backslash_count = 0; /* Backslashes are interpreted literally, unless they immediately precede a double quotation mark. */ while (b != NULL && *b == '\\') { backslash_count++; b++; if (b != NULL && *b == '\"') { additional_backslash = 1; break; } } cmdline_len += backslash_count * (additional_backslash + 1); i += backslash_count - 1; } else if (p[i] == '\"') /* backslash will be added for every double quote.*/ cmdline_len += 2; else cmdline_len++; } cmdline_len += 1 + 2; /*for "around cmd arg and traling space*/ } } if ((cmdline = malloc(cmdline_len)) == NULL) { errno = ENOMEM; goto cleanup; } t = cmdline; *t++ = '\"'; if (add_module_path) { /* add current module path to start if needed */ memcpy(t, __progdir, strlen(__progdir)); t += strlen(__progdir); *t++ = '\\'; } if (path[0] != '\"') { memcpy(t, path, path_len); t += path_len; *t++ = '\"'; } else { /*path already contains "*/ memcpy(t, path + 1, path_len - 1); t += path_len - 1; } *t = '\0'; t = cmdline + strlen(cmdline); if (argv) { t1 = argv; while (*t1) { *t++ = ' '; char * p1 = *t1++; BOOL add_quotes = FALSE; /* leave as is if the command is surrounded by single quotes*/ if (p1[0] != '\'') for (int i = 0; i < (int)strlen(p1); i++) { if (p1[i] == ' ') { add_quotes = TRUE; break; } } if (add_quotes) *t++ = '\"'; for (int i = 0; i < (int)strlen(p1); i++) { if (p1[i] == '\\') { char * b = p1 + i; int additional_backslash = 0; int backslash_count = 0; /* * Backslashes are interpreted literally, unless they immediately * precede a double quotation mark. */ while (b != NULL && *b == '\\') { backslash_count++; b++; if (b != NULL && *b == '\"') { additional_backslash = 1; break; } } i += backslash_count - 1; int escaped_backslash_count = backslash_count * (additional_backslash + 1); while (escaped_backslash_count--) *t++ = '\\'; } else if (p1[i] == '\"') { /* Add backslash for every double quote.*/ *t++ = '\\'; *t++ = '\"'; } else *t++ = p1[i]; } if (add_quotes) *t++ = '\"'; } } *t = '\0'; ret = cmdline; cmdline = NULL; cleanup: if (path) free(path); if (cmdline) free(cmdline); return ret; } BOOL is_bash_test_env() { char *envValue = NULL; size_t len = 0; BOOL retVal = FALSE; _dupenv_s(&envValue, &len, "SSH_TEST_ENVIRONMENT"); if ((NULL != envValue) && atoi(envValue)) retVal = TRUE; if (envValue) free(envValue); return retVal; } int bash_to_win_path(const char *in, char *out, const size_t out_len) { int retVal = 0; const size_t cygwin_path_prefix_len = strlen(CYGWIN_PATH_PREFIX); memset(out, 0, out_len); if (_strnicmp(in, CYGWIN_PATH_PREFIX, cygwin_path_prefix_len) == 0) { out[0] = in[cygwin_path_prefix_len]; out[1] = ':'; strcat_s(out, out_len, &in[cygwin_path_prefix_len + 1]); retVal = 1; } else strcpy_s(out, out_len, in); return retVal; } int getpeereid(int s, uid_t *euid, gid_t *egid) { verbose("%s is not supported", __func__); errno = ENOTSUP; return -1; } int getrrsetbyname(const char *hostname, unsigned int rdclass, unsigned int rdtype, unsigned int flags, struct rrsetinfo **res) { verbose("%s is not supported", __func__); errno = ENOTSUP; return -1; } int fnmatch(const char *pattern, const char *string, int flags) { int r = -1; wchar_t *pw = NULL, *sw = NULL; if (flags) { verbose("%s is not supported with flags", __func__); goto done; } pw = utf8_to_utf16(pattern); sw = utf8_to_utf16(string); if (!pw || !sw) goto done; convertToBackslashW(pw); convertToBackslashW(sw); if (PathMatchSpecW(sw, pw)) r = 0; done: if (pw) free(pw); if (sw) free(sw); return r; } void freerrset(struct rrsetinfo *rrset) { verbose("%s is not supported", __func__); return; } void debug_assert_internal() { /* debug break on non-release builds */ #ifndef NDEBUG DebugBreak(); #endif } char *crypt(const char *key, const char *salt) { verbose("%s is not supported", __func__); errno = ENOTSUP; return NULL; } int w32_system(const char *command) { int ret = -1; wchar_t *command_w = NULL; if (!command) { errno = ENOTSUP; goto cleanup; } if ((command_w = utf8_to_utf16(command)) == NULL) goto cleanup; ret = _wsystem(command_w); cleanup: if (command_w) free(command_w); return ret; }