openssh-portable/contrib/win32/win32compat/misc.c

1997 lines
48 KiB
C

/*
* 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, &sectorsPerCluster, &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, &current_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;
}