Support I/O redirection in all ssh clients (ssh.exe, sftp.exe and scp.exe) (#113)

PowerShell/Win32-OpenSSH#668
This commit is contained in:
Manoj Ampalam 2017-04-14 16:15:32 -07:00 committed by GitHub
parent b924b42dea
commit c3c5c1fa7f
23 changed files with 404 additions and 298 deletions

View File

@ -297,7 +297,7 @@
/* Define to 1 if you have the declaration of `O_NONBLOCK', and to 0 if you
don't. */
#define HAVE_DECL_O_NONBLOCK 0
#define HAVE_DECL_O_NONBLOCK 1
/* Define to 1 if you have the declaration of `passwdexpired', and to 0 if you
don't. */

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="paths.targets" />
<ItemGroup Label="ProjectConfigurations">
@ -186,6 +186,7 @@
<ItemGroup>
<ClCompile Include="$(OpenSSH-Src-Path)ssh-keygen.c" />
<ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\wmain_common.c" />
<ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\win32-utf8.c" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="version.rc" />
@ -193,4 +194,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
@ -21,10 +21,13 @@
<ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\wmain_common.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\win32-utf8.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="version.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
</Project>
</Project>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="paths.targets" />
<ItemGroup Label="ProjectConfigurations">
@ -22,6 +22,7 @@
<ItemGroup>
<ClCompile Include="$(OpenSSH-Src-Path)scp.c" />
<ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\wmain_common.c" />
<ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\win32-utf8.c" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="version.rc" />
@ -191,4 +192,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
@ -21,10 +21,13 @@
<ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\wmain_common.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\win32-utf8.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="version.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
</Project>
</Project>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="paths.targets" />
<ItemGroup Label="ProjectConfigurations">
@ -26,6 +26,7 @@
<ClCompile Include="$(OpenSSH-Src-Path)sftp-glob.c" />
<ClCompile Include="$(OpenSSH-Src-Path)sftp.c" />
<ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\wmain_common.c" />
<ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\win32-utf8.c" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="version.rc" />
@ -197,4 +198,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
@ -33,10 +33,13 @@
<ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\wmain_common.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\win32-utf8.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="version.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
</Project>
</Project>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="paths.targets" />
<ItemGroup Label="ProjectConfigurations">
@ -298,8 +298,9 @@
<ClCompile Include="$(OpenSSH-Src-Path)sshconnect.c" />
<ClCompile Include="$(OpenSSH-Src-Path)sshconnect1.c" />
<ClCompile Include="$(OpenSSH-Src-Path)sshconnect2.c" />
<ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\wmain_common.c" />
<ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\wmain_common.c" />
<ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\win32_sshtty.c" />
<ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\win32-utf8.c" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="version.rc" />
@ -307,4 +308,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
@ -311,10 +311,13 @@
<ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\win32_sshtty.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\win32-utf8.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="version.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
</Project>
</Project>

View File

@ -160,7 +160,6 @@
<ClCompile Include="$(OpenSSH-Src-Path)\contrib\win32\win32compat\tncon.c" />
<ClCompile Include="$(OpenSSH-Src-Path)\contrib\win32\win32compat\tnnet.c" />
<ClCompile Include="$(OpenSSH-Src-Path)\contrib\win32\win32compat\utf.c" />
<ClCompile Include="$(OpenSSH-Src-Path)\contrib\win32\win32compat\win32-utf8.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="$(OpenSSH-Src-Path)\contrib\win32\win32compat\w32fd.h" />

View File

@ -19,7 +19,6 @@
<ClCompile Include="$(OpenSSH-Src-Path)\contrib\win32\win32compat\tncon.c" />
<ClCompile Include="$(OpenSSH-Src-Path)\contrib\win32\win32compat\tnnet.c" />
<ClCompile Include="$(OpenSSH-Src-Path)\contrib\win32\win32compat\utf.c" />
<ClCompile Include="$(OpenSSH-Src-Path)\contrib\win32\win32compat\win32-utf8.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="$(OpenSSH-Src-Path)\contrib\win32\win32compat\w32fd.h" />

View File

@ -55,8 +55,9 @@ struct createFile_flags {
DWORD dwFlagsAndAttributes;
};
int termio_initiate_read(struct w32_io* pio);
int termio_initiate_write(struct w32_io* pio, DWORD num_bytes);
int syncio_initiate_read(struct w32_io* pio);
int syncio_initiate_write(struct w32_io* pio, DWORD num_bytes);
int syncio_close(struct w32_io* pio);
/* maps Win32 error to errno */
int
@ -440,11 +441,10 @@ fileio_read(struct w32_io* pio, void *dst, unsigned int max)
}
if (fileio_is_io_available(pio, TRUE) == FALSE) {
if (FILETYPE(pio) == FILE_TYPE_CHAR) {
if (-1 == termio_initiate_read(pio))
if (pio->type == NONSOCK_SYNC_FD || FILETYPE(pio) == FILE_TYPE_CHAR) {
if (-1 == syncio_initiate_read(pio))
return -1;
}
else {
} else {
if (-1 == fileio_ReadFileEx(pio, max)) {
if ((FILETYPE(pio) == FILE_TYPE_PIPE)
&& (errno == ERROR_BROKEN_PIPE)) {
@ -571,46 +571,12 @@ fileio_write(struct w32_io* pio, const void *buf, unsigned int max)
bytes_copied = min(max, pio->write_details.buf_size);
memcpy(pio->write_details.buf, buf, bytes_copied);
if (FILETYPE(pio) == FILE_TYPE_CHAR) {
if (termio_initiate_write(pio, bytes_copied) == 0) {
if (pio->type == NONSOCK_SYNC_FD || FILETYPE(pio) == FILE_TYPE_CHAR) {
if (syncio_initiate_write(pio, bytes_copied) == 0) {
pio->write_details.pending = TRUE;
pio->write_details.remaining = bytes_copied;
}
else
} else
return -1;
} else if ( FILETYPE(pio) == FILE_TYPE_PIPE &&
GetNamedPipeInfo(WINHANDLE(pio), &pipe_flags, NULL, NULL, &pipe_instances) &&
pipe_flags == PIPE_CLIENT_END && pipe_instances == 1) {
/*
* TODO - Figure out a better solution to this problem
* IO handle corresponding to this object (pio->handle) may be referring
* to something that isn't opened in overlapped mode. While all handles
* opened by this POSIX wrapper are opened in overlapped mode, other handles
* that are inherited (ex. via std i/o) are typically not.
* Ex. When we do this in Powershell
* $o = ssh.exe user@target hostname
* Powershell creates anonymous pipes (that do not support overlapped i.o)
* Calling asynchronous I/O APIs (WriteFileEx) for example will not work in
* those cases (the callback is never called and it typically manifests as a
* hang to end user
*
* This conditional logic is put in place to specifically handle Powershell
* redirection scenarios. Thinking behind these conditions
* - should be a pipe handle. console I/O is handled in termio.c, impacting file i/o
* scenarios not found yet.
* - pipe should be the client end. This is to skip pipes created internally in POSIX
* wrapper (by pipe() calls) - The write ends on these pipes are on server
* - pipe_instances == 1. This is to skip pipe handles created as part of Connect(AF_UNIX)
* sockets (that typically are created for unlimited instances).
* For such I/O we do a synchronous write.
*/
/* DebugBreak() */;
if (WriteFile(WINHANDLE(pio), pio->write_details.buf, bytes_copied, &bytes_copied, NULL) == FALSE) {
errno = errno_from_Win32LastError();
debug3("write - WriteFile() ERROR:%d, io:%p", GetLastError(), pio);
return -1;
}
return bytes_copied;
} else {
if (WriteFileEx(WINHANDLE(pio), pio->write_details.buf, bytes_copied,
&pio->write_overlapped, &WriteCompletionRoutine)) {
@ -753,8 +719,8 @@ fileio_on_select(struct w32_io* pio, BOOL rd)
if (!pio->read_details.pending && !fileio_is_io_available(pio, rd))
/* initiate read, record any error so read() will pick up */
if (FILETYPE(pio) == FILE_TYPE_CHAR) {
if (termio_initiate_read(pio) != 0) {
if (pio->type == NONSOCK_SYNC_FD || FILETYPE(pio) == FILE_TYPE_CHAR) {
if (syncio_initiate_read(pio) != 0) {
pio->read_details.error = errno;
errno = 0;
return;
@ -773,6 +739,9 @@ fileio_close(struct w32_io* pio)
{
debug4("fileclose - pio:%p", pio);
if (pio->type == NONSOCK_SYNC_FD || FILETYPE(pio) == FILE_TYPE_CHAR)
return syncio_close(pio);
/* handle can be null on AF_UNIX sockets that are not yet connected */
if (WINHANDLE(pio) == 0 || WINHANDLE(pio) == INVALID_HANDLE_VALUE) {
free(pio);
@ -782,15 +751,13 @@ fileio_close(struct w32_io* pio)
CancelIo(WINHANDLE(pio));
/* let queued APCs (if any) drain */
SleepEx(0, TRUE);
if (pio->type != STD_IO_FD) { /* STD handles are never explicitly closed */
CloseHandle(WINHANDLE(pio));
CloseHandle(WINHANDLE(pio));
/* free up non stdio */
if (!IS_STDIO(pio)) {
if (pio->read_details.buf)
free(pio->read_details.buf);
if (pio->write_details.buf)
free(pio->write_details.buf);
free(pio);
}
return 0;

View File

@ -22,7 +22,6 @@ int w32_open(const char *pathname, int flags, ...);
void* w32_fd_to_handle(int fd);
int w32_allocate_fd_for_handle(void* h, int is_sock);
#define O_ACCMODE 0x0003
#define O_RDONLY _O_RDONLY
#define O_WRONLY _O_WRONLY
#define O_RDWR _O_RDWR
@ -37,4 +36,26 @@ int w32_allocate_fd_for_handle(void* h, int is_sock);
#define O_NOINHERIT _O_NOINHERIT
#define O_SEQUENTIAL _O_SEQUENTIAL
#define O_RANDOM _O_RANDOM
#define O_U16TEXT _O_U16TEXT
#define O_U16TEXT _O_U16TEXT
/*
* open() POSIX specific modes and flags.
* Caution while making changes
* - cross check conflict with common macros in Windows headers
* - Ex. #define O_APPEND 0x8
*/
#define O_ACCMODE 0x0003
#define O_NONBLOCK 0x0004 /*io operations wont block*/
# define S_IXUSR 0000100 /* execute/search permission, */
# define S_IXGRP 0000010 /* execute/search permission, */
# define S_IXOTH 0000001 /* execute/search permission, */
# define _S_IWUSR 0000200 /* write permission, */
# define S_IWUSR _S_IWUSR /* write permission, owner */
# define S_IWGRP 0000020 /* write permission, group */
# define S_IWOTH 0000002 /* write permission, other */
# define S_IRUSR 0000400 /* read permission, owner */
# define S_IRGRP 0000040 /* read permission, group */
# define S_IROTH 0000004 /* read permission, other */
# define S_IRWXU 0000700 /* read, write, execute */
# define S_IRWXG 0000070 /* read, write, execute */
# define S_IRWXO 0000007 /* read, write, execute */

View File

@ -404,123 +404,6 @@ w32_ioctl(int d, int request, ...)
}
}
/*
* spawn a child process
* - specified by cmd with agruments argv
* - with std handles set to in, out, err
* - flags are passed to CreateProcess call
*
* cmd will be internally decoarated with a set of '"'
* to account for any spaces within the commandline
* this decoration is done only when additional arguments are passed in argv
*/
int
spawn_child(char* cmd, char** argv, int in, int out, int err, DWORD flags)
{
PROCESS_INFORMATION pi;
STARTUPINFOW si;
BOOL b;
char *cmdline, *t, **t1;
DWORD cmdline_len = 0;
wchar_t * cmdline_utf16;
int add_module_path = 0, ret = -1;
/* should module path be added */
do {
if (!cmd)
break;
t = cmd;
if (*t == '\"')
t++;
if (t[0] == '\0' || t[0] == '\\' || t[0] == '.' || t[1] == ':')
break;
add_module_path = 1;
} while (0);
/* compute total cmdline len*/
if (add_module_path)
cmdline_len += strlen(w32_programdir()) + 1 + strlen(cmd) + 1 + 2;
else
cmdline_len += strlen(cmd) + 1 + 2;
if (argv) {
t1 = argv;
while (*t1)
cmdline_len += strlen(*t1++) + 1 + 2;
}
if ((cmdline = malloc(cmdline_len)) == NULL) {
errno = ENOMEM;
goto cleanup;
}
/* add current module path to start if needed */
t = cmdline;
if (argv && argv[0])
*t++ = '\"';
if (add_module_path) {
memcpy(t, w32_programdir(), strlen(w32_programdir()));
t += strlen(w32_programdir());
*t++ = '\\';
}
memcpy(t, cmd, strlen(cmd));
t += strlen(cmd);
if (argv && argv[0])
*t++ = '\"';
if (argv) {
t1 = argv;
while (*t1) {
*t++ = ' ';
*t++ = '\"';
memcpy(t, *t1, strlen(*t1));
t += strlen(*t1);
*t++ = '\"';
t1++;
}
}
*t = '\0';
if ((cmdline_utf16 = utf8_to_utf16(cmdline)) == NULL) {
errno = ENOMEM;
goto cleanup;
}
memset(&si, 0, sizeof(STARTUPINFOW));
si.cb = sizeof(STARTUPINFOW);
si.hStdInput = w32_fd_to_handle(in);
si.hStdOutput = w32_fd_to_handle(out);
si.hStdError = w32_fd_to_handle(err);
si.dwFlags = STARTF_USESTDHANDLES;
debug3("spawning %ls", cmdline_utf16);
b = CreateProcessW(NULL, cmdline_utf16, NULL, NULL, TRUE, flags, NULL, NULL, &si, &pi);
if (b) {
if (register_child(pi.hProcess, pi.dwProcessId) == -1) {
TerminateProcess(pi.hProcess, 0);
CloseHandle(pi.hProcess);
goto cleanup;
}
CloseHandle(pi.hThread);
} else {
errno = GetLastError();
goto cleanup;
}
ret = pi.dwProcessId;
cleanup:
if (cmdline)
free(cmdline);
if (cmdline_utf16)
free(cmdline_utf16);
return ret;
}
void
strmode(mode_t mode, char *p)
{

View File

@ -1,5 +1,8 @@
#pragma once
#define PATH_MAX MAX_PATH
#define SSH_ASYNC_STDIN "SSH_ASYNC_STDIN"
#define SSH_ASYNC_STDOUT "SSH_ASYNC_STDOUT"
#define SSH_ASYNC_STDERR "SSH_ASYNC_STDERR"
/* removes first '/' for Windows paths that are unix styled. Ex: /c:/ab.cd */
char * sanitized_path(const char *);

View File

@ -78,7 +78,7 @@ sigtstp_APCProc(_In_ ULONG_PTR dwParam)
BOOL WINAPI
native_sig_handler(DWORD dwCtrlType)
{
debug3("Native Ctrl+C handler, CtrlType %d", dwCtrlType);
debug4("Native Ctrl+C handler, CtrlType %d", dwCtrlType);
switch (dwCtrlType) {
case CTRL_C_EVENT:
QueueUserAPC(sigint_APCProc, main_thread, (ULONG_PTR)NULL);
@ -154,7 +154,7 @@ w32_sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
int
w32_raise(int sig)
{
debug3("raise sig:%d", sig);
debug4("raise sig:%d", sig);
if (sig == W32_SIGSEGV)
return raise(SIGSEGV); /* raise native exception handler*/
@ -229,7 +229,7 @@ sw_process_pending_signals()
DebugBreak();
if (sig_int) {
debug3("process_queued_signals: WARNING - A signal has interrupted and was processed");
debug4("process_queued_signals: WARNING - A signal has interrupted and was processed");
errno = EINTR;
return -1;
}

View File

@ -110,7 +110,7 @@ socketio_acceptEx(struct w32_io* pio)
context = (struct acceptEx_context *)pio->internal.context;
ResetEvent(pio->read_overlapped.hEvent);
if (getsockname(pio->sock, &addr, &addrlen) == SOCKET_ERROR) {
if (getsockname(pio->sock, (struct sockaddr*)&addr, &addrlen) == SOCKET_ERROR) {
errno = errno_from_WSALastError();
debug("acceptEx - getsockname() ERROR:%d, io:%p", errno, pio);
return -1;

View File

@ -9,6 +9,10 @@
* Author: Balu <bagajjal@microsoft.com>
* Misc fixes and code cleanup
*
* Author: Manoj Ampalam <manojamp@microsoft.com>
* Extended support to other Windows IO that does not support
* overlapped IO. Ex. pipe handles returned by CreatePipe()
*
* Copyright (c) 2017 Microsoft Corp.
* All rights reserved
*
@ -71,18 +75,26 @@ ReadAPCProc(_In_ ULONG_PTR dwParam)
/* Read worker thread */
static DWORD WINAPI
ReadConsoleThread(_In_ LPVOID lpParameter)
ReadThread(_In_ LPVOID lpParameter)
{
int nBytesReturned = 0;
struct w32_io* pio = (struct w32_io*)lpParameter;
debug5("TermRead thread, io:%p", pio);
memset(&read_status, 0, sizeof(read_status));
while (nBytesReturned == 0) {
nBytesReturned = ReadConsoleForTermEmul(WINHANDLE(pio),
pio->read_details.buf, pio->read_details.buf_size);
if (FILETYPE(pio) == FILE_TYPE_CHAR) {
while (nBytesReturned == 0) {
nBytesReturned = ReadConsoleForTermEmul(WINHANDLE(pio),
pio->read_details.buf, pio->read_details.buf_size);
}
read_status.transferred = nBytesReturned;
} else {
if (!ReadFile(WINHANDLE(pio), pio->read_details.buf,
pio->read_details.buf_size, &read_status.transferred, NULL)) {
read_status.error = GetLastError();
debug("ReadThread - ReadFile failed %d, io:%p", GetLastError(), pio);
}
}
read_status.transferred = nBytesReturned;
if (0 == QueueUserAPC(ReadAPCProc, main_thread, (ULONG_PTR)pio)) {
debug3("TermRead thread - ERROR QueueUserAPC failed %d, io:%p", GetLastError(), pio);
pio->read_details.pending = FALSE;
@ -95,11 +107,11 @@ ReadConsoleThread(_In_ LPVOID lpParameter)
/* Initiates read on tty */
int
termio_initiate_read(struct w32_io* pio)
syncio_initiate_read(struct w32_io* pio)
{
HANDLE read_thread;
debug5("TermRead initiate io:%p", pio);
debug5("syncio_initiate_read io:%p", pio);
if (pio->read_details.buf_size == 0) {
pio->read_details.buf = malloc(TERM_IO_BUF_SIZE);
if (pio->read_details.buf == NULL) {
@ -109,7 +121,7 @@ termio_initiate_read(struct w32_io* pio)
pio->read_details.buf_size = TERM_IO_BUF_SIZE;
}
read_thread = CreateThread(NULL, 0, ReadConsoleThread, pio, 0, NULL);
read_thread = CreateThread(NULL, 0, ReadThread, pio, 0, NULL);
if (read_thread == NULL) {
errno = errno_from_Win32Error(GetLastError());
debug3("TermRead initiate - ERROR CreateThread %d, io:%p", GetLastError(), pio);
@ -148,19 +160,26 @@ WriteThread(_In_ LPVOID lpParameter)
size_t resplen = 0;
debug5("TermWrite thread, io:%p", pio);
pio->write_details.buf[write_status.to_transfer] = '\0';
if (0 == in_raw_mode) {
wchar_t* t = utf8_to_utf16(pio->write_details.buf);
WriteConsoleW(WINHANDLE(pio), t, wcslen(t), 0, 0);
free(t);
if (FILETYPE(pio) == FILE_TYPE_CHAR) {
pio->write_details.buf[write_status.to_transfer] = '\0';
if (0 == in_raw_mode) {
wchar_t* t = utf8_to_utf16(pio->write_details.buf);
WriteConsoleW(WINHANDLE(pio), t, wcslen(t), 0, 0);
free(t);
} else {
processBuffer(WINHANDLE(pio), pio->write_details.buf, write_status.to_transfer, &respbuf, &resplen);
/* TODO - respbuf is not null in some cases, this needs to be returned back via read stream */
}
write_status.transferred = write_status.to_transfer;
} else {
processBuffer(WINHANDLE(pio), pio->write_details.buf, write_status.to_transfer, &respbuf, &resplen);
/* TODO - respbuf is not null in some cases, this needs to be returned back via read stream */
if (!WriteFile(WINHANDLE(pio), pio->write_details.buf, write_status.to_transfer,
&write_status.transferred, NULL)) {
write_status.error = GetLastError();
debug("WriteThread - ReadFile WriteFile %d, io:%p", GetLastError(), pio);
}
}
write_status.transferred = write_status.to_transfer;
if (0 == QueueUserAPC(WriteAPCProc, main_thread, (ULONG_PTR)pio)) {
debug3("TermWrite thread - ERROR QueueUserAPC failed %d, io:%p", GetLastError(), pio);
pio->write_details.pending = FALSE;
@ -173,7 +192,7 @@ WriteThread(_In_ LPVOID lpParameter)
/* Initiates write on tty */
int
termio_initiate_write(struct w32_io* pio, DWORD num_bytes)
syncio_initiate_write(struct w32_io* pio, DWORD num_bytes)
{
HANDLE write_thread;
debug5("TermWrite initiate io:%p", pio);
@ -193,21 +212,27 @@ termio_initiate_write(struct w32_io* pio, DWORD num_bytes)
/* tty close */
int
termio_close(struct w32_io* pio)
syncio_close(struct w32_io* pio)
{
debug4("termio_close - pio:%p", pio);
debug4("syncio_close - pio:%p", pio);
HANDLE h;
CancelIoEx(WINHANDLE(pio), NULL);
/* If io is pending, let write worker threads exit. The read thread is blocked so terminate it.*/
if (pio->read_details.pending)
TerminateThread(pio->read_overlapped.hEvent, 0);
/* If io is pending, let worker threads exit. */
if (pio->read_details.pending) {
/* For console - the read thread is blocked so terminate it. */
if (FILETYPE(pio) == FILE_TYPE_CHAR)
TerminateThread(pio->read_overlapped.hEvent, 0);
else
WaitForSingleObject(pio->read_overlapped.hEvent, INFINITE);
}
if (pio->write_details.pending)
WaitForSingleObject(pio->write_overlapped.hEvent, INFINITE);
/* drain queued APCs */
SleepEx(0, TRUE);
if (pio->type != STD_IO_FD) {
/* STD handles are never explicitly closed */
CloseHandle(WINHANDLE(pio));
CloseHandle(WINHANDLE(pio));
/* free up if non stdio */
if (!IS_STDIO(pio)) {
if (pio->read_details.buf)
free(pio->read_details.buf);
if (pio->write_details.buf)

View File

@ -36,6 +36,7 @@
#include "inc\unistd.h"
#include "inc\fcntl.h"
#include "inc\sys\un.h"
#include "inc\utf.h"
#include "w32fd.h"
#include "signal_internal.h"
@ -74,15 +75,24 @@ fd_table_initialize()
memset(&fd_table, 0, sizeof(fd_table));
memset(&w32_io_stdin, 0, sizeof(w32_io_stdin));
w32_io_stdin.std_handle = STD_INPUT_HANDLE;
w32_io_stdin.type = STD_IO_FD;
w32_io_stdin.type = NONSOCK_SYNC_FD;
if (getenv(SSH_ASYNC_STDIN) && strcmp(getenv(SSH_ASYNC_STDIN), "1") == 0)
w32_io_stdin.type = NONSOCK_FD;
_putenv_s(SSH_ASYNC_STDIN, "");
fd_table_set(&w32_io_stdin, STDIN_FILENO);
memset(&w32_io_stdout, 0, sizeof(w32_io_stdout));
w32_io_stdout.std_handle = STD_OUTPUT_HANDLE;
w32_io_stdout.type = STD_IO_FD;
w32_io_stdout.type = NONSOCK_SYNC_FD;
if (getenv(SSH_ASYNC_STDOUT) && strcmp(getenv(SSH_ASYNC_STDOUT), "1") == 0)
w32_io_stdout.type = NONSOCK_FD;
_putenv_s(SSH_ASYNC_STDOUT, "");
fd_table_set(&w32_io_stdout, STDOUT_FILENO);
memset(&w32_io_stderr, 0, sizeof(w32_io_stderr));
w32_io_stderr.std_handle = STD_ERROR_HANDLE;
w32_io_stderr.type = STD_IO_FD;
w32_io_stderr.type = NONSOCK_SYNC_FD;
if (getenv(SSH_ASYNC_STDERR) && strcmp(getenv(SSH_ASYNC_STDERR), "1") == 0)
w32_io_stderr.type = NONSOCK_FD;
_putenv_s(SSH_ASYNC_STDERR, "");
fd_table_set(&w32_io_stderr, STDERR_FILENO);
return 0;
}
@ -128,7 +138,6 @@ fd_table_set(struct w32_io* pio, int index)
static void
fd_table_clear(int index)
{
fd_table.w32_ios[index]->table_index = -1;
fd_table.w32_ios[index] = NULL;
FD_CLR(index, &(fd_table.occupied));
}
@ -483,6 +492,7 @@ int
w32_close(int fd)
{
struct w32_io* pio;
int r;
if ((fd < 0) || (fd > MAX_FDS - 1) || fd_table.w32_ios[fd] == NULL) {
errno = EBADF;
return -1;
@ -492,17 +502,14 @@ w32_close(int fd)
debug3("close - io:%p, type:%d, fd:%d, table_index:%d", pio, pio->type, fd,
pio->table_index);
fd_table_clear(pio->table_index);
if (pio->type == SOCK_FD)
return socketio_close(pio);
r = socketio_close(pio);
else
switch (FILETYPE(pio)) {
case FILE_TYPE_CHAR:
return termio_close(pio);
default:
return fileio_close(pio);
}
r = fileio_close(pio);
fd_table_clear(fd);
return r;
}
static int
@ -798,7 +805,7 @@ w32_dup(int oldfd)
memset(pio, 0, sizeof(struct w32_io));
pio->handle = target;
pio->type = NONSOCK_FD;
pio->type = fd_table.w32_ios[oldfd]->type;
fd_table_set(pio, min_index);
return min_index;
}
@ -866,3 +873,131 @@ w32_fsync(int fd)
CHECK_FD(fd);
return FlushFileBuffers(w32_fd_to_handle(fd));
}
/*
* spawn a child process
* - specified by cmd with agruments argv
* - with std handles set to in, out, err
* - flags are passed to CreateProcess call
*
* cmd will be internally decoarated with a set of '"'
* to account for any spaces within the commandline
* this decoration is done only when additional arguments are passed in argv
*/
int
spawn_child(char* cmd, char** argv, int in, int out, int err, DWORD flags)
{
PROCESS_INFORMATION pi;
STARTUPINFOW si;
BOOL b;
char *cmdline, *t, **t1;
DWORD cmdline_len = 0;
wchar_t * cmdline_utf16;
int add_module_path = 0, ret = -1;
/* should module path be added */
do {
if (!cmd)
break;
t = cmd;
if (*t == '\"')
t++;
if (t[0] == '\0' || t[0] == '\\' || t[0] == '.' || t[1] == ':')
break;
add_module_path = 1;
} while (0);
/* compute total cmdline len*/
if (add_module_path)
cmdline_len += strlen(w32_programdir()) + 1 + strlen(cmd) + 1 + 2;
else
cmdline_len += strlen(cmd) + 1 + 2;
if (argv) {
t1 = argv;
while (*t1)
cmdline_len += strlen(*t1++) + 1 + 2;
}
if ((cmdline = malloc(cmdline_len)) == NULL) {
errno = ENOMEM;
goto cleanup;
}
/* add current module path to start if needed */
t = cmdline;
if (argv && argv[0])
*t++ = '\"';
if (add_module_path) {
memcpy(t, w32_programdir(), strlen(w32_programdir()));
t += strlen(w32_programdir());
*t++ = '\\';
}
memcpy(t, cmd, strlen(cmd));
t += strlen(cmd);
if (argv && argv[0])
*t++ = '\"';
if (argv) {
t1 = argv;
while (*t1) {
*t++ = ' ';
*t++ = '\"';
memcpy(t, *t1, strlen(*t1));
t += strlen(*t1);
*t++ = '\"';
t1++;
}
}
*t = '\0';
if ((cmdline_utf16 = utf8_to_utf16(cmdline)) == NULL) {
errno = ENOMEM;
goto cleanup;
}
memset(&si, 0, sizeof(STARTUPINFOW));
si.cb = sizeof(STARTUPINFOW);
si.hStdInput = w32_fd_to_handle(in);
si.hStdOutput = w32_fd_to_handle(out);
si.hStdError = w32_fd_to_handle(err);
si.dwFlags = STARTF_USESTDHANDLES;
debug3("spawning %ls", cmdline_utf16);
if (fd_table.w32_ios[in]->type != NONSOCK_SYNC_FD)
_putenv_s(SSH_ASYNC_STDIN, "1");
if (fd_table.w32_ios[out]->type != NONSOCK_SYNC_FD)
_putenv_s(SSH_ASYNC_STDOUT, "1");
if (fd_table.w32_ios[err]->type != NONSOCK_SYNC_FD)
_putenv_s(SSH_ASYNC_STDERR, "1");
b = CreateProcessW(NULL, cmdline_utf16, NULL, NULL, TRUE, flags, NULL, NULL, &si, &pi);
_putenv_s(SSH_ASYNC_STDIN, "");
_putenv_s(SSH_ASYNC_STDOUT, "");
_putenv_s(SSH_ASYNC_STDERR, "");
if (b) {
if (register_child(pi.hProcess, pi.dwProcessId) == -1) {
TerminateProcess(pi.hProcess, 0);
CloseHandle(pi.hProcess);
goto cleanup;
}
CloseHandle(pi.hThread);
}
else {
errno = GetLastError();
goto cleanup;
}
ret = pi.dwProcessId;
cleanup:
if (cmdline)
free(cmdline);
if (cmdline_utf16)
free(cmdline_utf16);
return ret;
}

View File

@ -39,7 +39,22 @@ enum w32_io_type {
UNKNOWN_FD = 0,
SOCK_FD = 1, /*maps a socket fd*/
NONSOCK_FD = 2, /*maps a file fd, pipe fd or a tty fd*/
STD_IO_FD = 5 /*maps a std fd - ex. STDIN_FILE*/
/*
* maps a NONSOCK_FD that doesnt support async or overlapped io
* these are typically used for stdio on ssh client side
* executables (ssh, sftp and scp).
* Ex. ssh ... > output.txt
* In the above case, stdout passed to ssh.exe is a handle to
* output.txt that is opened in non-overlapped mode
* Ex. sample.exe | ssh ...
* In the above case, stdin passed to ssh.exe is a handle to
* a pipe opened in non-overlapped mode
* Ex. in Powershell
* $o = ssh ...
* In the above case, stdout passed to ssh.exe is a handle to
* a pipe opened in non-overlapped mode
*/
NONSOCK_SYNC_FD = 3
};
enum w32_io_sock_state {
@ -51,7 +66,7 @@ enum w32_io_sock_state {
};
/*
* This structure encapsulates the state info needed to map a File Descriptor
* This structure encapsulates the I/O state info needed to map a File Descriptor
* to Win32 Handle
*/
struct w32_io {
@ -94,7 +109,8 @@ struct w32_io {
}internal;
};
#define WINHANDLE(pio) (((pio)->type == STD_IO_FD)? GetStdHandle((pio)->std_handle):(pio)->handle)
#define IS_STDIO(pio) ((pio)->table_index <= 2)
#define WINHANDLE(pio) (IS_STDIO(pio)? GetStdHandle((pio)->std_handle):(pio)->handle)
#define FILETYPE(pio) (GetFileType(WINHANDLE(pio)))
extern HANDLE main_thread;
@ -102,7 +118,7 @@ BOOL w32_io_is_blocking(struct w32_io*);
BOOL w32_io_is_io_available(struct w32_io* pio, BOOL rd);
int wait_for_any_event(HANDLE* events, int num_events, DWORD milli_seconds);
/*POSIX mimic'ing socket API*/
/*POSIX mimic'ing socket API and socket helper API*/
int socketio_initialize();
int socketio_done();
BOOL socketio_is_io_available(struct w32_io* pio, BOOL rd);
@ -122,7 +138,7 @@ int socketio_send(struct w32_io* pio, const void *buf, size_t len, int flags);
int socketio_shutdown(struct w32_io* pio, int how);
int socketio_close(struct w32_io* pio);
/*POSIX mimic'ing file API*/
/*POSIX mimic'ing file API and file helper API*/
BOOL fileio_is_io_available(struct w32_io* pio, BOOL rd);
void fileio_on_select(struct w32_io* pio, BOOL rd);
int fileio_close(struct w32_io* pio);
@ -136,45 +152,3 @@ int fileio_fstat(struct w32_io* pio, struct _stat64 *buf);
int fileio_stat(const char *path, struct _stat64 *buf);
long fileio_lseek(struct w32_io* pio, long offset, int origin);
FILE* fileio_fdopen(struct w32_io* pio, const char *mode);
/* terminal io specific versions */
int termio_close(struct w32_io* pio);
/*
* open() flags and modes
* all commented out macros are defined in fcntl.h
* they are listed here so as to cross check any conflicts with macros explicitly
* defined below.
*/
/*open access modes. only one of these can be specified*/
/* #define O_RDONLY 0x0 */
/* #define O_WRONLY 0x1 */
/* #define O_RDWR 0x2 */
/* open file creation and file status flags can be bitwise-or'd*/
/* #define O_APPEND 0x8 /*file is opened in append mode*/
#ifndef O_NONBLOCK
#define O_NONBLOCK 0x0004 /*io operations wont block*/
#endif
/* #define O_CREAT 0x100 /*If the file does not exist it will be created*/
/*
* If the file exists and is a regular file, and the file is successfully
* opened O_RDWR or O_WRONLY, its length shall be truncated to 0, and the mode
* and owner shall be unchanged
*/
/* #define O_TRUNC 0x200 */
/* If O_CREAT and O_EXCL are set, open() shall fail if the file exists */
/* #define O_EXCL 0x400 */
/* #define O_BINARY 0x8000 //Gives raw data (while O_TEXT normalises line endings */
/* open modes */
#ifndef S_IRUSR
#define S_IRUSR 00400 /* user has read permission */
#endif /* ! S_IRUSR */
#ifndef S_IWUSR
#define S_IWUSR 00200 /* user has write permission */
#endif
#ifndef S_IRGRP
#define S_IRGRP 00040 /* group has read permission */
#endif
#ifndef S_IROTH
#define S_IROTH 00004 /* others have read permission */
#endif

View File

@ -52,8 +52,9 @@ wmain(int argc, wchar_t **wargv) {
if (getenv("SSH_AUTH_SOCK") == NULL)
_putenv("SSH_AUTH_SOCK=ssh-agent");
w32posix_initialize();
r = main(argc, argv);
w32posix_done();
return r;
w32posix_initialize();
r = main(argc, argv);
w32posix_done();
return r;
}

View File

@ -1,14 +1,30 @@
#covered -i -q -v -l -c -C
#todo: -i -q -v -l -c -C
#todo: -S -F -V -e
Describe "Tests for ssh command" -Tags "Scenario" {
$tB = 1
$tI = 0
Describe "ssh client tests" -Tags "CI" {
BeforeAll {
$fileName = "test.txt"
$filePath = Join-Path ${TestDrive} $fileName
if($OpenSSHTestInfo -eq $null)
{
Throw "`$OpenSSHTestInfo is null. Please run Setup-OpenSSHTestEnvironment to setup test environment."
}
[Machine] $client = [Machine]::new([MachineRole]::Client)
[Machine] $server = [Machine]::new([MachineRole]::Server)
$client.SetupClient($server)
$server.SetupServer($client)
if(-not (Test-Path $OpenSSHTestInfo["TestDataPath"]))
{
$null = New-Item $OpenSSHTestInfo["TestDataPath"] -ItemType directory -Force -ErrorAction SilentlyContinue
}
$server = $OpenSSHTestInfo["Target"]
$port = $OpenSSHTestInfo["Port"]
$ssouser = $OpenSSHTestInfo["SSOUser"]
$sshCmdDefault = "ssh -p $port $($ssouser)@$($server)"
$testDir = Join-Path $OpenSSHTestInfo["TestDataPath"] "ssh"
if(-not (Test-Path $testDir))
{
$null = New-Item $testDir -ItemType directory -Force -ErrorAction SilentlyContinue
}
$testData = @(
@{
@ -55,12 +71,71 @@ Describe "Tests for ssh command" -Tags "Scenario" {
}
AfterAll {
$client.CleanupClient()
$server.CleanupServer()
BeforeEach {
$tI++;
$tFile=Join-Path $testDir "$tB.$tI.txt"
}
Context "$tB - Basic Scenarios" {
BeforeAll {$tI=1}
AfterAll{$tB++}
<# these 2 tests dont work on AppVeyor that sniffs stderr channel
It "$tB.$tI - test version" {
iex "ssh -V 2> $tFile"
$tFile | Should Contain "OpenSSH_"
}
It "$tB.$tI - test help" {
iex "ssh -? 2> $tFile"
$tFile | Should Contain "usage: ssh"
}
#>
It "$tB.$tI - remote echo command" {
iex "$sshDefaultCmd echo 1234" | Should Be "1234"
}
}
Context "Key is not secured in ssh-agent on server" {
Context "$tB - Redirection Scenarios" {
BeforeAll {$tI=1}
AfterAll{$tB++}
It "$tB.$tI - stdout to file" {
iex "$sshDefaultCmd powershell get-process > $tFile"
$tFile | Should Contain "ProcessName"
}
It "$tB.$tI - stdout to PS object" {
$o = iex "$sshDefaultCmd echo 1234"
$o | Should Be "1234"
}
<#It "$tB.$tI - stdin from PS object" {
#if input redirection doesn't work, this would hang
0 | ssh -p $port $ssouser@$server pause
$true | Should Be $true
}#>
}
Context "$tB - cmdline parameters" {
BeforeAll {$tI=1}
AfterAll{$tB++}
It "$tB.$tI - verbose to file" {
$logFile = Join-Path $testDir "$tB.$tI.log.txt"
$o = ssh -p $port -v -E $logFile $ssouser@$server echo 1234
$o | Should Be "1234"
#TODO - checks below are very inefficient (time taking).
$logFile | Should Contain "OpenSSH_"
$logFile | Should Contain "Exit Status 0"
}
}
<#Context "Key is not secured in ssh-agent on server" {
BeforeAll {
$identifyFile = $client.clientPrivateKeyPaths[0]
Remove-Item -Path $filePath -Force -ea silentlycontinue
@ -156,5 +231,5 @@ Describe "Tests for ssh command" -Tags "Scenario" {
#validate file content.
Get-Content $filePath | Should be $server.MachineName
}
}
}#>
}

View File

@ -559,6 +559,10 @@ int do_exec_windows(Session *s, const char *command, int pty) {
debug("Executing command: %s", exec_command);
UTF8_TO_UTF16_FATAL(exec_command_w, exec_command);
_putenv_s("SSH_ASYNC_STDIN", "1");
_putenv_s("SSH_ASYNC_STDOUT", "1");
_putenv_s("SSH_ASYNC_STDERR", "1");
/* in debug mode launch using sshd.exe user context */
if (debug_flag)
b = CreateProcessW(NULL, exec_command_w, NULL, NULL, TRUE,
@ -569,6 +573,10 @@ int do_exec_windows(Session *s, const char *command, int pty) {
DETACHED_PROCESS , NULL, pw_dir_w,
&si, &pi);
_putenv_s("SSH_ASYNC_STDIN", "");
_putenv_s("SSH_ASYNC_STDOUT", "");
_putenv_s("SSH_ASYNC_STDERR", "");
if (!b)
fatal("ERROR. Cannot create process (%u).\n", GetLastError());
else if (pty) { /*attach to shell console */