wolfssh/apps/wolfsshd/wolfsshd.c

2856 lines
87 KiB
C

/* wolfsshd.c
*
* Copyright (C) 2014-2024 wolfSSL Inc.
*
* This file is part of wolfSSH.
*
* wolfSSH is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* wolfSSH is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with wolfSSH. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef WOLFSSL_USER_SETTINGS
#include <wolfssl/wolfcrypt/settings.h>
#else
#include <wolfssl/options.h>
#endif
#ifdef WOLFSSH_SSHD
#include <wolfssh/ssh.h>
#include <wolfssh/internal.h>
#include <wolfssh/log.h>
#include <wolfssl/wolfcrypt/wc_port.h>
#include <wolfssl/wolfcrypt/error-crypt.h>
#include <wolfssl/wolfcrypt/logging.h>
#include <wolfssl/wolfcrypt/asn_public.h>
#define WOLFSSH_TEST_SERVER
#include <wolfssh/test.h>
#include "configuration.h"
#include "auth.h"
#include <signal.h>
#ifdef NO_INLINE
#include <wolfssh/misc.h>
#else
#define WOLFSSH_MISC_INCLUDED
#include "src/misc.c"
#endif
#ifndef WOLFSSHD_TIMEOUT
#define WOLFSSHD_TIMEOUT 1
#endif
#ifdef EXAMPLE_BUFFER_SZ
#warning use WOLFSSHD_SHELL_BUFFER_SZ instead of EXAMPLE_BUFFER_SZ
#define WOLFSSHD_SHELL_BUFFER_SZ EXAMPLE_BUFFER_SZ
#endif
#if defined(WOLFSSH_SHELL) && !defined(_WIN32)
#ifdef HAVE_PTY_H
#include <pty.h>
#endif
#ifdef HAVE_UTIL_H
#include <util.h>
#endif
#ifdef HAVE_TERMIOS_H
#include <termios.h>
#endif
#include <pwd.h>
#include <signal.h>
#include <sys/wait.h>
#if defined(__QNX__) || defined(__QNXNTO__)
#include <errno.h>
#include <unix.h>
#else
#include <sys/errno.h>
#endif
static volatile int ChildRunning = 0;
static void ChildSig(int sig)
{
(void)sig;
ChildRunning = 0;
}
static void ConnClose(int sig)
{
#ifndef WIN32
pid_t p;
int ret;
p = wait(&ret);
if (p == 0 || p == -1)
return; /* parent or error state*/
(void)ret;
#endif
(void)sig;
}
#endif /* WOLFSSH_SHELL */
static volatile byte debugMode = 0; /* default to off */
static WFILE* logFile = NULL;
/* catch interrupts and close down gracefully */
static volatile byte quit = 0;
/* Initial connection information to pass on to threads/forks */
typedef struct WOLFSSHD_CONNECTION {
WOLFSSH_CTX* ctx;
WOLFSSHD_AUTH* auth;
int fd;
int listenFd;
char ip[INET6_ADDRSTRLEN];
byte isThreaded;
} WOLFSSHD_CONNECTION;
#ifdef __unix__
#include <syslog.h>
static void SyslogCb(enum wolfSSH_LogLevel level, const char *const msgStr)
{
int priority;
switch (level) {
case WS_LOG_WARN:
priority = LOG_WARNING;
break;
case WS_LOG_ERROR:
priority = LOG_ERR;
break;
case WS_LOG_DEBUG:
priority = LOG_DEBUG;
break;
case WS_LOG_INFO:
case WS_LOG_USER:
case WS_LOG_SFTP:
case WS_LOG_SCP:
case WS_LOG_AGENT:
case WS_LOG_CERTMAN:
default:
priority = LOG_INFO;
break;
}
openlog("sshd", LOG_PID, LOG_DAEMON);
syslog(priority, "%s", msgStr);
closelog();
}
#endif
#ifdef _WIN32
static void ServiceDebugCb(enum wolfSSH_LogLevel level, const char* const msgStr)
#ifdef UNICODE
{
WCHAR* wc;
size_t szWord = WSTRLEN(msgStr) + 3; /* + 3 for null terminator and new
* line */
size_t sz = szWord *sizeof(wchar_t);
wc = (WCHAR*)WMALLOC(sz, NULL, DYNAMIC_TYPE_LOG);
if (wc) {
size_t con;
if (mbstowcs_s(&con, wc, szWord, msgStr, szWord-1) == 0) {
wc[con - 1] = L'\r';
wc[con] = L'\n';
wc[con + 1] = L'\0';
OutputDebugString(wc);
}
WFREE(wc, NULL, DYNAMIC_TYPE_LOG);
}
WOLFSSH_UNUSED(level);
}
#else
{
OutputDebugString(msgStr);
WOLFSSH_UNUSED(level);
}
#endif
#endif /* _WIN32 */
static void ShowUsage(void)
{
printf("wolfSSHd %s linked with wolfSSL %s\n", LIBWOLFSSH_VERSION_STRING,
LIBWOLFSSL_VERSION_STRING);
printf(" -? display this help and exit\n");
printf(" -f <file name> Configuration file to use, default is "
"/etc/ssh/sshd_config\n");
printf(" -p <int> Port number to listen on\n");
printf(" -d Turn on debug mode\n");
printf(" -D Run in foreground (do not detach)\n");
printf(" -h <file name> host private key file to use\n");
printf(" -E <file name> append to log file\n");
}
/* catch if interupted */
static void interruptCatch(int in)
{
(void)in;
if (logFile)
fprintf(logFile, "Closing down wolfSSHD\n");
quit = 1;
}
#ifdef WIN32
#include <processthreadsapi.h>
#define WGETPID GetCurrentProcessId
#else
#define WGETPID getpid
#endif
/* redirect logging to a specific file and add the PID value */
static void wolfSSHDLoggingCb(enum wolfSSH_LogLevel lvl, const char *const str)
{
/* always log errors and optionally log other info/debug level messages */
if (lvl == WS_LOG_ERROR) {
fprintf(logFile, "[PID %d]: %s\n", WGETPID(), str);
}
else if (debugMode) {
fprintf(logFile, "[PID %d]: %s\n", WGETPID(), str);
}
}
#ifndef NO_FILESYSTEM
static void freeBufferFromFile(byte* buf, void* heap)
{
if (buf != NULL)
WFREE(buf, heap, DYNTYPE_SSHD);
(void)heap;
}
/* set bufSz to size wanted if too small and buf is null */
static byte* getBufferFromFile(const char* fileName, word32* bufSz, void* heap)
{
FILE* file;
byte* buf = NULL;
word32 fileSz;
word32 readSz;
if (fileName == NULL) return NULL;
if (WFOPEN(NULL, &file, fileName, "rb") != 0)
return NULL;
WFSEEK(NULL, file, 0, WSEEK_END);
fileSz = (word32)WFTELL(NULL, file);
WREWIND(NULL, file);
buf = (byte*)WMALLOC(fileSz + 1, heap, DYNTYPE_SSHD);
if (buf != NULL) {
readSz = (word32)WFREAD(NULL, buf, 1, fileSz, file);
if (readSz < fileSz) {
WFCLOSE(NULL, file);
WFREE(buf, heap, DYNTYPE_SSHD);
return NULL;
}
if (bufSz)
*bufSz = readSz;
WFCLOSE(NULL, file);
}
(void)heap;
return buf;
}
#endif /* NO_FILESYSTEM */
static int UserAuthResult(byte result,
WS_UserAuthData* authData, void* userAuthResultCtx);
/* Frees up the WOLFSSH_CTX struct */
static void CleanupCTX(WOLFSSHD_CONFIG* conf, WOLFSSH_CTX** ctx,
byte** banner)
{
if (banner != NULL && *banner != NULL) {
#ifndef NO_FILESYSTEM
freeBufferFromFile(*banner, NULL);
#endif
*banner = NULL;
}
if (ctx != NULL && *ctx != NULL) {
wolfSSH_CTX_free(*ctx);
*ctx = NULL;
}
(void)conf;
}
/* Initializes and sets up the WOLFSSH_CTX struct based on the configure options
* return WS_SUCCESS on success
*/
static int SetupCTX(WOLFSSHD_CONFIG* conf, WOLFSSH_CTX** ctx,
byte** banner)
{
int ret = WS_SUCCESS;
DerBuffer* der = NULL;
byte* privBuf;
word32 privBufSz;
void* heap = NULL;
if (ctx == NULL) {
return WS_BAD_ARGUMENT;
}
/* create a new WOLFSSH_CTX */
*ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
if (ctx == NULL) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Couldn't allocate SSH CTX data.");
ret = WS_MEMORY_E;
}
/* setup authority callback for checking peer connections */
if (ret == WS_SUCCESS) {
wolfSSH_SetUserAuth(*ctx, DefaultUserAuth);
wolfSSH_SetUserAuthResult(*ctx, UserAuthResult);
}
/* set banner to display on connection */
if (ret == WS_SUCCESS) {
#ifndef NO_FILESYSTEM
*banner = getBufferFromFile(wolfSSHD_ConfigGetBanner(conf),
NULL, heap);
#endif
if (*banner) {
wolfSSH_CTX_SetBanner(*ctx, (char*)*banner);
}
}
/* Load in host private key */
if (ret == WS_SUCCESS) {
char* hostKey = wolfSSHD_ConfigGetHostKeyFile(conf);
if (hostKey == NULL) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] No host private key set");
ret = WS_BAD_ARGUMENT;
}
else {
byte* data;
word32 dataSz = 0;
data = getBufferFromFile(hostKey, &dataSz, heap);
if (data == NULL) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Error reading host key file.");
ret = WS_MEMORY_E;
}
if (ret == WS_SUCCESS) {
if (wc_PemToDer(data, dataSz, PRIVATEKEY_TYPE, &der, NULL,
NULL, NULL) != 0) {
wolfSSH_Log(WS_LOG_DEBUG, "[SSHD] Failed to convert host "
"private key from PEM. Assuming key in DER "
"format.");
privBuf = data;
privBufSz = dataSz;
}
else {
privBuf = der->buffer;
privBufSz = der->length;
}
if (wolfSSH_CTX_UsePrivateKey_buffer(*ctx, privBuf, privBufSz,
WOLFSSH_FORMAT_ASN1) < 0) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Failed to use host private key.");
ret = WS_BAD_ARGUMENT;
}
freeBufferFromFile(data, heap);
wc_FreeDer(&der);
}
}
}
#if defined(WOLFSSH_OSSH_CERTS) || defined(WOLFSSH_CERTS)
if (ret == WS_SUCCESS) {
/* TODO: Create a helper function that uses a file instead. */
char* hostCert = wolfSSHD_ConfigGetHostCertFile(conf);
if (hostCert != NULL) {
byte* data;
word32 dataSz = 0;
data = getBufferFromFile(hostCert, &dataSz, heap);
if (data == NULL) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Error reading host key file.");
ret = WS_MEMORY_E;
}
if (ret == WS_SUCCESS) {
#ifdef WOLFSSH_OPENSSH_CERTS
if (wolfSSH_CTX_UseOsshCert_buffer(*ctx, data, dataSz) < 0) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Failed to use host certificate.");
ret = WS_BAD_ARGUMENT;
}
#endif
#ifdef WOLFSSH_CERTS
if (ret == WS_SUCCESS || ret == WS_BAD_ARGUMENT) {
ret = wolfSSH_CTX_UseCert_buffer(*ctx, data, dataSz,
WOLFSSH_FORMAT_PEM);
if (ret != WS_SUCCESS) {
ret = wolfSSH_CTX_UseCert_buffer(*ctx, data, dataSz,
WOLFSSH_FORMAT_ASN1);
}
if (ret != WS_SUCCESS) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Failed to load in host certificate.");
}
}
#endif
freeBufferFromFile(data, heap);
}
}
}
#endif /* WOLFSSH_OSSH_CERTS || WOLFSSH_CERTS */
#ifdef WOLFSSH_CERTS
if (ret == WS_SUCCESS) {
char* caCert = wolfSSHD_ConfigGetUserCAKeysFile(conf);
if (caCert != NULL) {
byte* data;
word32 dataSz = 0;
wolfSSH_Log(WS_LOG_INFO, "[SSHD] Using CA keys file %s", caCert);
data = getBufferFromFile(caCert, &dataSz, heap);
if (data == NULL) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Error reading CA cert file.");
ret = WS_MEMORY_E;
}
if (ret == WS_SUCCESS) {
ret = wolfSSH_CTX_AddRootCert_buffer(*ctx, data, dataSz,
WOLFSSH_FORMAT_PEM);
if (ret != WS_SUCCESS) {
ret = wolfSSH_CTX_AddRootCert_buffer(*ctx, data, dataSz,
WOLFSSH_FORMAT_ASN1);
}
if (ret != WS_SUCCESS) {
#ifdef WOLFSSH_OPENSSH_CERTS
wolfSSH_Log(WS_LOG_INFO,
"[SSHD] Continuing on in case CA is openssh "
"style.");
ret = WS_SUCCESS;
#else
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Failed to load in CA certificate.");
#endif
}
freeBufferFromFile(data, heap);
}
}
}
#endif
if (ret == WS_SUCCESS) {
wolfSSH_SetUserAuthTypes(*ctx, DefaultUserAuthTypes);
}
/* @TODO Load in host public key */
/* Set allowed connection type, i.e. public key / password */
(void)heap;
return ret;
}
#ifndef _WIN32
/* return 1 if set, 0 if not set and negative values on error */
static int SetupChroot(WOLFSSHD_CONFIG* usrConf)
{
int ret = 0;
char* chrootPath;
/* check for chroot set */
chrootPath = wolfSSHD_ConfigGetChroot(usrConf);
if (chrootPath != NULL) {
ret = 1;
wolfSSH_Log(WS_LOG_INFO, "[SSHD] chroot to path %s", chrootPath);
if (chdir(chrootPath) != 0) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] chdir to chroot path failed, %s", chrootPath);
ret = WS_FATAL_ERROR;
}
if (chroot(chrootPath) != 0) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] chroot failed to path %s", chrootPath);
ret = WS_FATAL_ERROR;
}
if (chdir("/") != 0) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] chdir after chroot failed");
ret = WS_FATAL_ERROR;
}
}
return ret;
}
#endif
#ifdef WOLFSSH_SCP
static int SCP_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh,
WPASSWD* pPasswd, WOLFSSHD_CONFIG* usrConf)
{
int ret = WS_SUCCESS;
int error = WS_SUCCESS;
int select_ret = 0;
#ifndef _WIN32
/* temporarily elevate permissions to get users information */
if (wolfSSHD_AuthRaisePermissions(conn->auth) != WS_SUCCESS) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Failure to raise permissions for auth");
return WS_FATAL_ERROR;
}
/* set additional groups if needed */
if (wolfSSHD_AuthSetGroups(conn->auth, wolfSSH_GetUsername(ssh),
pPasswd->pw_gid) != WS_SUCCESS) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Error setting groups");
ret = WS_FATAL_ERROR;
}
if (ret == WS_SUCCESS) {
error = SetupChroot(usrConf);
if (error < 0) {
ret = error; /* error case with setup chroot */
}
}
if (wolfSSHD_AuthReducePermissionsUser(conn->auth, pPasswd->pw_uid,
pPasswd->pw_gid) != WS_SUCCESS) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Error setting user ID");
if (wolfSSHD_AuthReducePermissions(conn->auth) != WS_SUCCESS) {
/* stop everything if not able to reduce permissions level */
exit(1);
}
return WS_FATAL_ERROR;
}
#else
/* impersonate the logged on user for file permissions */
if (ImpersonateLoggedOnUser(wolfSSHD_GetAuthToken(conn->auth)) == FALSE) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Error impersonating logged on user");
ret = WS_FATAL_ERROR;
}
#endif
if (ret == WS_SUCCESS) {
ret = wolfSSH_accept(ssh);
error = wolfSSH_get_error(ssh);
while (ret != WS_SUCCESS && ret != WS_SCP_COMPLETE
&& (error == WS_WANT_READ || error == WS_WANT_WRITE)) {
select_ret = tcp_select(conn->fd, 1);
if (select_ret == WS_SELECT_RECV_READY ||
select_ret == WS_SELECT_ERROR_READY ||
error == WS_WANT_WRITE)
{
ret = wolfSSH_accept(ssh);
error = wolfSSH_get_error(ssh);
}
else if (select_ret == WS_SELECT_TIMEOUT)
error = WS_WANT_READ;
else
error = WS_FATAL_ERROR;
}
}
if (ret != WS_SUCCESS && ret != WS_SCP_COMPLETE) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Failed to finish SCP operation from IP %s",
conn->ip);
}
(void)conn;
#ifdef _WIN32
/* stop impersonating the user */
RevertToSelf();
#endif
return ret;
}
#endif /* WOLFSSH_SCP */
#ifdef WOLFSSH_SFTP
#define TEST_SFTP_TIMEOUT 1
#define TEST_SFTP_TIMEOUT_NONE 0
/* handle SFTP operations
* returns WS_SUCCESS on success
*/
static int SFTP_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh,
WPASSWD* pPasswd, WOLFSSHD_CONFIG* usrConf)
{
int ret = WS_SUCCESS;
int error = WS_SUCCESS;
WS_SOCKET_T sockfd;
int select_ret = 0;
int timeout = TEST_SFTP_TIMEOUT_NONE;
byte peek_buf[1];
#ifndef _WIN32
/* temporarily elevate permissions to get users information */
if (wolfSSHD_AuthRaisePermissions(conn->auth) != WS_SUCCESS) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Failure to raise permissions for auth");
return WS_FATAL_ERROR;
}
/* set additional groups if needed */
if (wolfSSHD_AuthSetGroups(conn->auth, wolfSSH_GetUsername(ssh),
pPasswd->pw_gid) != WS_SUCCESS) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Error setting groups");
ret = WS_FATAL_ERROR;
}
if (ret == WS_SUCCESS) {
error = SetupChroot(usrConf);
if (error == 1) {
/* chroot was executed */
if (wolfSSH_SFTP_SetDefaultPath(ssh, "/") != WS_SUCCESS) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Error setting SFTP default path");
ret = WS_FATAL_ERROR;
}
}
else if (error < 0) {
ret = error; /* error case with setup chroot */
}
}
/* set starting SFTP directory */
if (ret == WS_SUCCESS) {
WDIR dir;
/* if home directory exists than set it as the default */
if (WOPENDIR(NULL, NULL, &dir, pPasswd->pw_dir) == 0) {
if (wolfSSH_SFTP_SetDefaultPath(ssh, pPasswd->pw_dir)
!= WS_SUCCESS) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Error setting SFTP default home path");
ret = WS_FATAL_ERROR;
}
WCLOSEDIR(NULL, &dir);
}
}
if (wolfSSHD_AuthReducePermissionsUser(conn->auth, pPasswd->pw_uid,
pPasswd->pw_gid) != WS_SUCCESS) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Error setting user ID");
if (wolfSSHD_AuthReducePermissions(conn->auth) != WS_SUCCESS) {
/* stop everything if not able to reduce permissions level */
exit(1);
}
return WS_FATAL_ERROR;
}
#else
char r[MAX_PATH];
size_t rSz = 0;
WCHAR h[MAX_PATH];
ret = wolfSSHD_GetHomeDirectory(conn->auth, ssh, h, MAX_PATH);
/* convert home directory from wchar type to char */
if (ret == WS_SUCCESS) {
if (wcstombs_s(&rSz, r, MAX_PATH, h, MAX_PATH - 1) != 0) {
ret = WS_FATAL_ERROR;
}
}
if (ret == WS_SUCCESS) {
wolfSSH_Log(WS_LOG_INFO,
"[SSHD] Using directory %s for SFTP connection", r);
if (wolfSSH_SFTP_SetDefaultPath(ssh, r) != WS_SUCCESS) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Error setting SFTP default home path");
ret = WS_FATAL_ERROR;
}
}
/* impersonate the logged on user for file permissions */
if (ImpersonateLoggedOnUser(wolfSSHD_GetAuthToken(conn->auth)) == FALSE) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Error impersonating logged on user");
ret = WS_FATAL_ERROR;
}
#endif
if (ret == WS_SUCCESS) {
sockfd = (WS_SOCKET_T)wolfSSH_get_fd(ssh);
do {
if (wolfSSH_SFTP_PendingSend(ssh)) {
/* Yes, process the SFTP data. */
ret = wolfSSH_SFTP_read(ssh);
error = wolfSSH_get_error(ssh);
timeout = (ret == WS_REKEYING) ?
TEST_SFTP_TIMEOUT : TEST_SFTP_TIMEOUT_NONE;
if (error == WS_WANT_READ || error == WS_WANT_WRITE ||
error == WS_CHAN_RXD || error == WS_REKEYING ||
error == WS_WINDOW_FULL)
ret = error;
if (error == WS_WANT_WRITE && wolfSSH_SFTP_PendingSend(ssh)) {
continue; /* no need to spend time attempting to pull data
* if there is still pending sends */
}
if (error == WS_EOF) {
break;
}
}
select_ret = tcp_select(sockfd, timeout);
if (select_ret == WS_SELECT_ERROR_READY) {
break;
}
if (ret == WS_WANT_READ || ret == WS_WANT_WRITE ||
select_ret == WS_SELECT_RECV_READY) {
ret = wolfSSH_worker(ssh, NULL);
error = wolfSSH_get_error(ssh);
if (ret == WS_REKEYING) {
/* In a rekey, keeping turning the crank. */
timeout = TEST_SFTP_TIMEOUT;
continue;
}
if (error == WS_WANT_READ || error == WS_WANT_WRITE ||
error == WS_WINDOW_FULL) {
timeout = TEST_SFTP_TIMEOUT;
ret = error;
continue;
}
if (error == WS_EOF) {
break;
}
if (ret != WS_SUCCESS && ret != WS_CHAN_RXD) {
/* If not successful and no channel data, leave. */
break;
}
}
ret = wolfSSH_stream_peek(ssh, peek_buf, sizeof(peek_buf));
if (ret > 0) {
/* Yes, process the SFTP data. */
ret = wolfSSH_SFTP_read(ssh);
error = wolfSSH_get_error(ssh);
timeout = (ret == WS_REKEYING) ?
TEST_SFTP_TIMEOUT : TEST_SFTP_TIMEOUT_NONE;
if (error == WS_WANT_READ || error == WS_WANT_WRITE ||
error == WS_CHAN_RXD || error == WS_REKEYING ||
error == WS_WINDOW_FULL)
ret = error;
if (error == WS_EOF)
break;
continue;
}
else if (ret == WS_REKEYING) {
timeout = TEST_SFTP_TIMEOUT;
continue;
}
else if (ret < 0) {
error = wolfSSH_get_error(ssh);
if (error == WS_EOF)
break;
}
if (ret == WS_FATAL_ERROR && error == 0) {
WOLFSSH_CHANNEL* channel =
wolfSSH_ChannelNext(ssh, NULL);
if (channel && wolfSSH_ChannelGetEof(channel)) {
ret = 0;
break;
}
}
} while (ret != WS_FATAL_ERROR);
}
(void)conn;
#ifdef _WIN32
/* stop impersonating the user */
RevertToSelf();
#endif
return ret;
}
#endif
#ifdef WOLFSSH_SHELL
#ifndef MAX_COMMAND_SZ
#define MAX_COMMAND_SZ 80
#endif
#ifdef WIN32
/* handles creating a new shell env. and maintains SSH connection for incoming
* user input as well as output of the shell.
* return WS_SUCCESS on success */
static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh,
WPASSWD* pPasswd, WOLFSSHD_CONFIG* usrConf, const char* subCmd)
{
BOOL ret;
word32 shellChannelId = 0;
#ifndef WOLFSSHD_SHELL_BUFFER_SZ
/* default to try and read max packet size */
#define WOLFSSHD_SHELL_BUFFER_SZ 32768
#endif
byte shellBuffer[WOLFSSHD_SHELL_BUFFER_SZ];
int cnt_r, cnt_w;
HANDLE ptyIn = NULL, ptyOut = NULL;
HANDLE cnslIn = NULL, cnslOut = NULL;
STARTUPINFOEX ext;
PCWSTR sysCmd = L"c:\\windows\\system32\\cmd.exe";
#if 0
/* start powershell instead */
PCWSTR sysCmd = L"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe";
#endif
PWSTR cmd = NULL;
size_t cmdSz = 0;
PROCESS_INFORMATION processInfo;
size_t sz = 0;
WCHAR h[MAX_PATH];
char* forcedCmd;
forcedCmd = wolfSSHD_ConfigGetForcedCmd(usrConf);
/* @TODO check for conpty support LoadLibrary()and GetProcAddress(). */
if (forcedCmd != NULL && WSTRCMP(forcedCmd, "internal-sftp") == 0) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Only SFTP connections allowed for user "
"%s", wolfSSH_GetUsername(ssh));
return WS_FATAL_ERROR;
}
ret = wolfSSHD_GetHomeDirectory(conn->auth, ssh, h, MAX_PATH);
if (ret == WS_SUCCESS) {
ZeroMemory(&ext, sizeof(STARTUPINFOEX));
ZeroMemory(&processInfo, sizeof(PROCESS_INFORMATION));
/* use forced command if set over subCmd */
if (forcedCmd == NULL && subCmd != NULL) {
forcedCmd = (char*)subCmd;
}
if (forcedCmd != NULL) { /* copy over set command if present */
/* +1 for terminator and +2 for quotes */
cmdSz = WSTRLEN(forcedCmd) + wcslen(sysCmd) + WSTRLEN(" /C ") + 3;
cmd = (PWSTR)WMALLOC(sizeof(wchar_t) * cmdSz, NULL, DYNTYPE_SSHD);
if (cmd == NULL) {
ret = WS_MEMORY_E;
}
else {
WCHAR* tmp = (WCHAR*)WMALLOC(sizeof(wchar_t) * cmdSz, NULL,
DYNTYPE_SSHD);
if (tmp == NULL) {
ret = WS_MEMORY_E;
}
if (ret == WS_SUCCESS) {
size_t wr = 0;
if (mbstowcs_s(&wr, tmp, cmdSz, forcedCmd, cmdSz - 1) != 0) {
ret = WS_FATAL_ERROR;
}
}
if (ret == WS_SUCCESS) {
swprintf(cmd, cmdSz, L"%s /C %s", sysCmd, tmp);
}
if (tmp != NULL) {
WFREE(tmp, NULL, DYNTYPE_SSHD);
}
/* read in case a window-change packet might be queued */
{
int rc;
word32 lastChannel = 0;
do {
rc = wolfSSH_worker(ssh, &lastChannel);
if (rc < 0) {
rc = wolfSSH_get_error(ssh);
}
} while (rc == WS_WANT_WRITE);
}
}
}
else { /* when set command is not present start 'cmd.exe' */
cmdSz = wcslen(sysCmd) + 1; /* +1 for terminator */
cmd = (PWSTR)WMALLOC(sizeof(wchar_t) * cmdSz, NULL, DYNTYPE_SSHD);
if (cmd == NULL) {
ret = WS_MEMORY_E;
}
else {
wcscpy_s(cmd, cmdSz, sysCmd);
}
}
}
if (ImpersonateLoggedOnUser(wolfSSHD_GetAuthToken(conn->auth)) == FALSE) {
ret = WS_FATAL_ERROR;
}
if (ret == WS_SUCCESS) {
SECURITY_ATTRIBUTES saAttr;
ZeroMemory(&saAttr, sizeof(saAttr));
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
if (CreatePipe(&cnslIn, &ptyIn, &saAttr, 0) != TRUE) {
ret = WS_FATAL_ERROR;
}
if (CreatePipe(&ptyOut, &cnslOut, &saAttr, 0) != TRUE) {
ret = WS_FATAL_ERROR;
}
}
if (ret == WS_SUCCESS) {
STARTUPINFOW si;
PCWSTR conCmd = L"wolfsshd.exe -r ";
PWSTR conCmdPtr;
size_t conCmdSz;
SetHandleInformation(ptyIn, HANDLE_FLAG_INHERIT, 0);
SetHandleInformation(ptyOut, HANDLE_FLAG_INHERIT, 0);
wolfSSH_SetTerminalResizeCtx(ssh, (void*)&ptyIn);
conCmdSz = wcslen(conCmd) + cmdSz + 3;
/* +1 for terminator, +2 for quotes */
conCmdPtr = (PWSTR)WMALLOC(sizeof(wchar_t) * conCmdSz,
NULL, DYNTYPE_SSHD);
if (conCmdPtr == NULL) {
ret = WS_MEMORY_E;
}
else {
_snwprintf_s(conCmdPtr, conCmdSz, conCmdSz,
L"wolfsshd.exe -r \"%s\"", cmd);
}
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
si.hStdInput = cnslIn;
si.hStdOutput = cnslOut;
si.hStdError = cnslOut;
si.dwFlags = STARTF_USESTDHANDLES;
si.lpDesktop = NULL;
if (CreateProcessAsUserW(wolfSSHD_GetAuthToken(conn->auth), NULL, conCmdPtr,
NULL, NULL, TRUE, DETACHED_PROCESS, NULL, h,
&si, &processInfo) != TRUE) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Issue creating process, Windows error %d", GetLastError());
return WS_FATAL_ERROR;
}
CloseHandle(cnslIn);
CloseHandle(cnslOut);
WFREE(conCmdPtr, NULL, DYNTYPE_SSHD);
}
if (ret == WS_SUCCESS) {
char cmdWSize[20];
int cmdWSizeSz = 20;
DWORD wrtn = 0;
wolfSSH_Log(WS_LOG_INFO, "[SSHD] Successfully created process for "
"console, waiting for it to start");
WaitForInputIdle(processInfo.hProcess, 1000);
/* Send initial terminal size to pseudo console with VT control sequence */
cmdWSizeSz = snprintf(cmdWSize, cmdWSizeSz, "\x1b[8;%d;%dt", ssh->heightRows, ssh->widthChar);
if (WriteFile(ptyIn, cmdWSize, cmdWSizeSz, &wrtn, 0) != TRUE) {
WLOG(WS_LOG_ERROR, "Issue with pseudo console resize");
ret = WS_FATAL_ERROR;
}
}
if (ret == WS_SUCCESS) {
SOCKET sshFd;
byte tmp[2];
fd_set readFds;
WS_SOCKET_T maxFd;
int pending = 0;
int readPending = 0;
int rc = 0;
DWORD processState;
DWORD ava;
struct timeval t;
t.tv_sec = 0;
t.tv_usec = 800;
sshFd = wolfSSH_get_fd(ssh);
maxFd = sshFd;
FD_ZERO(&readFds);
FD_SET(sshFd, &readFds);
do {
/* @TODO currently not blocking till data comes in */
if (PeekNamedPipe(ptyOut, NULL, 0, NULL, &ava, NULL) == TRUE) {
if (ava > 0) {
readPending = 1;
}
}
if (readPending == 0) {
/* check if process is still running before waiting to read */
if (GetExitCodeProcess(processInfo.hProcess, &processState)
== TRUE) {
if (processState != STILL_ACTIVE) {
wolfSSH_Log(WS_LOG_INFO,
"[SSHD] Process has exited, exit state = %d, "
"close down SSH connection", processState);
Sleep(100); /* give the stdout/stderr of process a
* little time to write to pipe */
if (PeekNamedPipe(ptyOut, NULL, 0, NULL, &ava, NULL)
== TRUE) {
if (ava > 0) {
/* if data still pending then continue
* sending it over SSH */
readPending = 1;
continue;
}
}
break;
}
}
if (wolfSSH_stream_peek(ssh, tmp, 1) <= 0) {
rc = select((int)maxFd + 1, &readFds, NULL, NULL, &t);
if (rc == -1) {
wolfSSH_Log(WS_LOG_INFO,
"[SSHD] select call waiting on socket failed");
break;
}
/* when select times out and no socket is set as ready
Windows overwrites readFds with 0. Reset the fd here
for next select call */
if (rc == 0) {
FD_SET(sshFd, &readFds);
}
}
else {
pending = 1;
}
}
if (rc != 0 && (pending || FD_ISSET(sshFd, &readFds))) {
word32 lastChannel = 0;
/* The following tries to read from the first channel inside
the stream. If the pending data in the socket is for
another channel, this will return an error with id
WS_CHAN_RXD. That means the agent has pending data in its
channel. The additional channel is only used with the
agent. */
cnt_r = wolfSSH_worker(ssh, &lastChannel);
if (cnt_r < 0) {
rc = wolfSSH_get_error(ssh);
if (rc == WS_CHAN_RXD) {
if (lastChannel == shellChannelId) {
cnt_r = wolfSSH_ChannelIdRead(ssh,
shellChannelId, shellBuffer,
sizeof shellBuffer);
if (cnt_r <= 0)
break;
pending = 0;
if (WriteFile(ptyIn, shellBuffer, cnt_r, &cnt_r,
NULL) != TRUE) {
wolfSSH_Log(WS_LOG_INFO,
"[SSHD] Error writing to pipe for "
"console");
break;
}
}
}
else if (rc == WS_CHANNEL_CLOSED) {
continue;
}
else if (rc != WS_WANT_READ) {
break;
}
}
}
if (readPending) {
WMEMSET(shellBuffer, 0, WOLFSSHD_SHELL_BUFFER_SZ);
if (ReadFile(ptyOut, shellBuffer, WOLFSSHD_SHELL_BUFFER_SZ, &cnt_r,
NULL) != TRUE) {
wolfSSH_Log(WS_LOG_INFO,
"[SSHD] Error reading from pipe for console");
break;
}
else {
readPending = 0;
if (cnt_r > 0) {
cnt_w = wolfSSH_ChannelIdSend(ssh, shellChannelId,
shellBuffer, cnt_r);
if (cnt_w < 0)
break;
}
}
}
} while (1);
if (cmd != NULL) {
WFREE(cmd, NULL, DYNTYPE_SSHD);
}
wolfSSH_Log(WS_LOG_INFO,
"[SSHD] Closing down process for console");
if (ext.lpAttributeList != NULL) {
HeapFree(GetProcessHeap(), 0, ext.lpAttributeList);
}
if (wolfSSH_SetExitStatus(ssh, processState) !=
WS_SUCCESS) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Issue sending childs exit "
"status");
}
CloseHandle(processInfo.hThread);
CloseHandle(wolfSSHD_GetAuthToken(conn->auth));
}
RevertToSelf();
return ret;
}
#else
#if defined(HAVE_SYS_IOCTL_H)
#include <sys/ioctl.h>
#endif
/* handles creating a new shell env. and maintains SSH connection for incoming
* user input as well as output of the shell.
* return WS_SUCCESS on success */
static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh,
WPASSWD* pPasswd, WOLFSSHD_CONFIG* usrConf, const char* subCmd)
{
WS_SOCKET_T sshFd = 0;
int rc;
WS_SOCKET_T childFd = 0;
int stdoutPipe[2], stderrPipe[2];
int stdinPipe[2];
pid_t childPid;
#ifndef WOLFSSHD_SHELL_BUFFER_SZ
/* default to try and read max packet size */
#define WOLFSSHD_SHELL_BUFFER_SZ 32768
#endif
#ifndef MAX_IDLE_COUNT
#define MAX_IDLE_COUNT 2
#endif
byte shellBuffer[WOLFSSHD_SHELL_BUFFER_SZ];
byte channelBuffer[WOLFSSHD_SHELL_BUFFER_SZ];
char* forcedCmd;
int windowFull = 0; /* Contains size of bytes from shellBuffer that did
* not get passed on to wolfSSH yet. This happens
* with window full errors or when rekeying. */
int wantWrite = 0;
int peerConnected = 1;
int stdoutEmpty = 0;
childFd = -1;
stdoutPipe[0] = -1;
stdoutPipe[1] = -1;
stderrPipe[0] = -1;
stderrPipe[1] = -1;
stdinPipe[0] = -1;
stdinPipe[1] = -1;
forcedCmd = wolfSSHD_ConfigGetForcedCmd(usrConf);
/* do not overwrite a forced command with 'exec' sub shell. Only set the
* 'exec' command when no forced command is set */
if (forcedCmd == NULL) {
forcedCmd = (char*)subCmd;
}
if (forcedCmd != NULL && WSTRCMP(forcedCmd, "internal-sftp") == 0) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Only SFTP connections allowed for user "
"%s", wolfSSH_GetUsername(ssh));
return WS_FATAL_ERROR;
}
/* temporarily elevate permissions to get users information */
if (wolfSSHD_AuthRaisePermissions(conn->auth) != WS_SUCCESS) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Failure to raise permissions for auth");
return WS_FATAL_ERROR;
}
/* create pipes for stdout and stderr */
if (forcedCmd) {
if (pipe(stdoutPipe) != 0) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Issue creating stdout pipe");
return WS_FATAL_ERROR;
}
if (pipe(stderrPipe) != 0) {
close(stdoutPipe[0]);
close(stdoutPipe[1]);
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Issue creating stderr pipe");
return WS_FATAL_ERROR;
}
if (pipe(stdinPipe) != 0) {
close(stdoutPipe[0]);
close(stdoutPipe[1]);
close(stderrPipe[0]);
close(stderrPipe[1]);
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Issue creating stdin pipe");
return WS_FATAL_ERROR;
}
}
ChildRunning = 1;
childPid = forkpty(&childFd, NULL, NULL, NULL);
if (childPid < 0) {
/* forkpty failed, so return */
ChildRunning = 0;
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Issue creating new forkpty");
return WS_FATAL_ERROR;
}
else if (childPid == 0) {
/* Child process */
const char *args[] = {"-sh", NULL, NULL, NULL};
char cmd[MAX_COMMAND_SZ];
char shell[MAX_COMMAND_SZ];
int ret;
signal(SIGINT, SIG_DFL);
signal(SIGCHLD, SIG_DFL);
if (forcedCmd) {
close(stdoutPipe[0]);
close(stderrPipe[0]);
close(stdinPipe[1]);
stdoutPipe[0] = -1;
stderrPipe[0] = -1;
stdinPipe[1] = -1;
if (dup2(stdinPipe[0], STDIN_FILENO) == -1) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Error redirecting stdin pipe");
if (wolfSSHD_AuthReducePermissions(conn->auth) != WS_SUCCESS) {
exit(1);
}
return WS_FATAL_ERROR;
}
if (dup2(stdoutPipe[1], STDOUT_FILENO) == -1) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Error redirecting stdout pipe");
if (wolfSSHD_AuthReducePermissions(conn->auth) != WS_SUCCESS) {
exit(1);
}
return WS_FATAL_ERROR;
}
if (dup2(stderrPipe[1], STDERR_FILENO) == -1) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Error redirecting stderr pipe");
if (wolfSSHD_AuthReducePermissions(conn->auth) != WS_SUCCESS) {
exit(1);
}
return WS_FATAL_ERROR;
}
}
/* set additional groups if needed */
if (wolfSSHD_AuthSetGroups(conn->auth, wolfSSH_GetUsername(ssh),
pPasswd->pw_gid) != WS_SUCCESS) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Error setting groups");
if (wolfSSHD_AuthReducePermissions(conn->auth) != WS_SUCCESS) {
/* stop everything if not able to reduce permissions level */
exit(1);
}
return WS_FATAL_ERROR;
}
rc = SetupChroot(usrConf);
if (rc < 0) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Error setting chroot");
if (wolfSSHD_AuthReducePermissions(conn->auth) != WS_SUCCESS) {
/* stop everything if not able to reduce permissions level */
exit(1);
}
return WS_FATAL_ERROR;
}
else if (rc == 1) {
rc = chdir("/");
if (rc != 0) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Error going to / after chroot");
if (wolfSSHD_AuthReducePermissions(conn->auth) != WS_SUCCESS) {
/* stop everything if not able to reduce permissions level */
exit(1);
}
return WS_FATAL_ERROR;
}
}
if (wolfSSHD_AuthReducePermissionsUser(conn->auth, pPasswd->pw_uid,
pPasswd->pw_gid) != WS_SUCCESS) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Error setting user ID");
if (wolfSSHD_AuthReducePermissions(conn->auth) != WS_SUCCESS) {
/* stop everything if not able to reduce permissions level */
exit(1);
}
return WS_FATAL_ERROR;
}
setenv("HOME", pPasswd->pw_dir, 1);
setenv("LOGNAME", pPasswd->pw_name, 1);
setenv("SHELL", pPasswd->pw_shell, 1);
if (pPasswd->pw_shell) {
if (WSTRLEN(pPasswd->pw_shell) < sizeof(shell)) {
char* cursor;
char* start;
WSTRNCPY(shell, pPasswd->pw_shell, sizeof(shell));
cursor = shell;
do {
start = cursor;
*cursor = '-';
cursor = WSTRCHR(start, '/');
} while (cursor && *cursor != '\0');
args[0] = start;
}
}
rc = chdir(pPasswd->pw_dir);
if (rc != 0) {
/* not error'ing out if unable to find home directory */
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Error going to user home dir %s",
pPasswd->pw_dir);
}
/* default to /bin/sh if user shell is not set */
if (pPasswd->pw_shell && WSTRLEN(pPasswd->pw_shell)) {
WSNPRINTF(cmd, sizeof(cmd), "%s", pPasswd->pw_shell);
}
else {
WSNPRINTF(cmd, sizeof(cmd), "%s", "/bin/sh");
}
errno = 0;
if (forcedCmd) {
args[1] = "-c";
args[2] = forcedCmd;
ret = execv(cmd, (char**)args);
close(stdoutPipe[1]);
close(stderrPipe[1]);
close(stdinPipe[1]);
}
else {
ret = execv(cmd, (char**)args);
}
if (ret && errno) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Issue opening shell");
exit(1);
}
exit(ret); /* exit child process and close down SSH connection */
}
if (wolfSSHD_AuthReducePermissionsUser(conn->auth, pPasswd->pw_uid,
pPasswd->pw_gid) != WS_SUCCESS) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Error setting user ID");
if (wolfSSHD_AuthReducePermissions(conn->auth) != WS_SUCCESS) {
/* stop everything if not able to reduce permissions level */
exit(1);
}
return WS_FATAL_ERROR;
}
sshFd = wolfSSH_get_fd(ssh);
struct termios tios;
word32 shellChannelId = 0;
signal(SIGCHLD, ChildSig);
signal(SIGINT, SIG_DFL);
rc = tcgetattr(childFd, &tios);
if (rc != 0) {
return WS_FATAL_ERROR;
}
rc = tcsetattr(childFd, TCSAFLUSH, &tios);
if (rc != 0) {
return WS_FATAL_ERROR;
}
/* set initial size of terminal based on saved size */
#if defined(HAVE_SYS_IOCTL_H)
wolfSSH_DoModes(ssh->modes, ssh->modesSz, childFd);
{
struct winsize s;
WMEMSET(&s, 0, sizeof(s));
s.ws_col = ssh->widthChar;
s.ws_row = ssh->heightRows;
s.ws_xpixel = ssh->widthPixels;
s.ws_ypixel = ssh->heightPixels;
ioctl(childFd, TIOCSWINSZ, &s);
}
#endif
wolfSSH_SetTerminalResizeCtx(ssh, (void*)&childFd);
if (forcedCmd) {
close(stdoutPipe[1]);
close(stderrPipe[1]);
close(stdinPipe[0]);
}
while (ChildRunning || windowFull || !stdoutEmpty || peerConnected) {
byte tmp[2];
fd_set readFds;
fd_set writeFds;
WS_SOCKET_T maxFd;
int cnt_r;
int cnt_w;
int pending = 0;
FD_ZERO(&readFds);
FD_SET(sshFd, &readFds);
maxFd = sshFd;
FD_ZERO(&writeFds);
if (windowFull || wantWrite) {
FD_SET(sshFd, &writeFds);
}
if (wolfSSH_stream_peek(ssh, tmp, 1) <= 0) {
/* select on stdout/stderr pipes with forced commands */
if (forcedCmd) {
FD_SET(stdoutPipe[0], &readFds);
if (stdoutPipe[0] > maxFd)
maxFd = stdoutPipe[0];
FD_SET(stderrPipe[0], &readFds);
if (stderrPipe[0] > maxFd)
maxFd = stderrPipe[0];
}
else {
FD_SET(childFd, &readFds);
if (childFd > maxFd)
maxFd = childFd;
}
rc = select((int)maxFd + 1, &readFds, &writeFds, NULL, NULL);
if (rc == -1)
break;
}
else {
pending = 1; /* found some pending SSH data */
}
if (wantWrite || windowFull || pending || FD_ISSET(sshFd, &readFds)) {
word32 lastChannel = 0;
wantWrite = 0;
/* The following tries to read from the first channel inside
the stream. If the pending data in the socket is for
another channel, this will return an error with id
WS_CHAN_RXD. That means the agent has pending data in its
channel. The additional channel is only used with the
agent. */
cnt_r = wolfSSH_worker(ssh, &lastChannel);
if (cnt_r < 0) {
rc = wolfSSH_get_error(ssh);
if (rc == WS_CHAN_RXD) {
if (!windowFull) { /* don't rewrite channeldBuffer if full
* of windowFull left overs */
if (lastChannel == shellChannelId) {
cnt_r = wolfSSH_ChannelIdRead(ssh, shellChannelId,
channelBuffer,
sizeof channelBuffer);
if (cnt_r <= 0)
break;
if (forcedCmd) {
cnt_w = (int)write(stdinPipe[1], channelBuffer,
cnt_r);
}
else {
cnt_w = (int)write(childFd, channelBuffer,
cnt_r);
}
if (cnt_w <= 0)
break;
}
}
}
else if (rc == WS_CHANNEL_CLOSED) {
peerConnected = 0;
continue;
}
else if (rc == WS_WANT_WRITE) {
wantWrite = 1;
continue;
}
else if (rc == WS_REKEYING) {
wantWrite = 1;
continue;
}
else if (rc != WS_WANT_READ) {
/* unexpected error, kill off child process */
kill(childPid, SIGKILL);
break;
}
}
/* did the channel just receive an EOF? */
if (cnt_r == 0) {
int eof;
WOLFSSH_CHANNEL* current;
current = wolfSSH_ChannelFind(ssh, lastChannel,
WS_CHANNEL_ID_SELF);
eof = wolfSSH_ChannelGetEof(current);
if (eof && forcedCmd) {
/* SSH is done, close stdin pipe to child process */
close(stdinPipe[1]);
stdinPipe[1] = -1;
}
}
}
/* if the window was previously full, try resending the data */
if (windowFull) {
cnt_w = wolfSSH_ChannelIdSend(ssh, shellChannelId,
shellBuffer, windowFull);
if (cnt_w == WS_WINDOW_FULL || cnt_w == WS_REKEYING) {
continue;
}
else if (cnt_w == WS_WANT_WRITE) {
wantWrite = 1;
continue;
}
else {
windowFull -= cnt_w;
if (windowFull > 0) {
WMEMMOVE(shellBuffer, shellBuffer + cnt_w, windowFull);
continue;
}
if (windowFull < 0)
windowFull = 0;
}
}
if (forcedCmd) {
if (FD_ISSET(stderrPipe[0], &readFds)) {
cnt_r = (int)read(stderrPipe[0], shellBuffer,
sizeof shellBuffer);
/* This read will return 0 on EOF */
if (cnt_r <= 0) {
int err = errno;
if (err != EAGAIN && err != 0) {
break;
}
}
else {
if (cnt_r > 0) {
cnt_w = wolfSSH_extended_data_send(ssh, shellBuffer,
cnt_r);
if (cnt_w > 0 && cnt_w < cnt_r) { /* partial send */
windowFull = cnt_r - cnt_w;
WMEMMOVE(shellBuffer, shellBuffer + cnt_w,
windowFull);
}
else if (cnt_w == WS_WINDOW_FULL ||
cnt_w == WS_REKEYING) {
windowFull = cnt_r; /* save amount to be sent */
continue;
}
else if (cnt_w == WS_WANT_WRITE) {
wantWrite = 1;
continue;
}
else if (cnt_w < 0)
break;
}
}
}
/* handle stdout */
if (FD_ISSET(stdoutPipe[0], &readFds)) {
cnt_r = (int)read(stdoutPipe[0], shellBuffer,
sizeof shellBuffer);
/* This read will return 0 on EOF */
if (cnt_r < 0) {
int err = errno;
if (err != EAGAIN && err != 0) {
break;
}
}
else if (cnt_r == 0) {
stdoutEmpty = 1;
}
else {
if (cnt_r > 0) {
cnt_w = wolfSSH_ChannelIdSend(ssh, shellChannelId,
shellBuffer, cnt_r);
if (cnt_w > 0 && cnt_w < cnt_r) { /* partial send */
windowFull = cnt_r - cnt_w;
WMEMMOVE(shellBuffer, shellBuffer + cnt_w,
windowFull);
}
else if (cnt_w == WS_WINDOW_FULL ||
cnt_w == WS_REKEYING) {
windowFull = cnt_r; /* save amount to be sent */
continue;
}
else if (cnt_w == WS_WANT_WRITE) {
wantWrite = 1;
continue;
}
else if (cnt_w < 0) {
kill(childPid, SIGINT);
break;
}
}
}
}
}
else {
if (FD_ISSET(childFd, &readFds)) {
cnt_r = (int)read(childFd, shellBuffer, sizeof shellBuffer);
/* This read will return 0 on EOF */
if (cnt_r <= 0) {
int err = errno;
if (err != EAGAIN && err != 0) {
break;
}
}
else {
if (cnt_r > 0) {
cnt_w = wolfSSH_ChannelIdSend(ssh, shellChannelId,
shellBuffer, cnt_r);
if (cnt_w > 0 && cnt_w < cnt_r) { /* partial send */
windowFull = cnt_r - cnt_w;
WMEMMOVE(shellBuffer, shellBuffer + cnt_w,
windowFull);
}
else if (cnt_w == WS_WINDOW_FULL ||
cnt_w == WS_REKEYING) {
windowFull = cnt_r;
continue;
}
else if (cnt_w == WS_WANT_WRITE) {
wantWrite = 1;
continue;
}
else if (cnt_w < 0) {
kill(childPid, SIGINT);
break;
}
}
}
}
}
if (!ChildRunning && peerConnected && stdoutEmpty && !windowFull) {
peerConnected = 0;
}
}
/* get return value of child process */
{
int waitStatus;
do {
rc = waitpid(childPid, &waitStatus, 0);
/* if the waitpid experinced an interupt then try again */
} while (rc < 0 && errno == EINTR);
if (rc < 0) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Issue waiting for childs exit "
"status");
}
else {
if (wolfSSH_SetExitStatus(ssh, (word32)WEXITSTATUS(waitStatus)) !=
WS_SUCCESS) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Issue setting childs exit "
"status");
}
}
}
/* check for any left over data in pipes then close them */
if (forcedCmd) {
int readSz;
fcntl(stdoutPipe[0], F_SETFL, fcntl(stdoutPipe[0], F_GETFL)
| O_NONBLOCK);
readSz = (int)read(stdoutPipe[0], shellBuffer, sizeof shellBuffer);
if (readSz > 0) {
wolfSSH_ChannelIdSend(ssh, shellChannelId, shellBuffer, readSz);
}
fcntl(stderrPipe[0], F_SETFL, fcntl(stderrPipe[0], F_GETFL)
| O_NONBLOCK);
readSz = (int)read(stderrPipe[0], shellBuffer, sizeof shellBuffer);
if (readSz > 0) {
wolfSSH_extended_data_send(ssh, shellBuffer, readSz);
}
close(stdoutPipe[0]);
close(stderrPipe[0]);
if (stdinPipe[1] != -1) {
close(stdinPipe[1]);
}
}
(void)conn;
return WS_SUCCESS;
}
#endif
#endif
#ifdef WIN32
static volatile int timeOut = 0;
#else
static __thread int timeOut = 0;
#endif
static void alarmCatch(int signum)
{
timeOut = 1;
(void)signum;
}
static int UserAuthResult(byte result,
WS_UserAuthData* authData, void* userAuthResultCtx)
{
(void)authData;
(void)userAuthResultCtx;
if (result == WOLFSSH_USERAUTH_SUCCESS) {
#ifndef WIN32
/* @TODO alarm catch on windows */
alarm(0);
#endif
}
return WS_SUCCESS;
}
/* handle wolfSSH accept and directing to correct subsystem */
#ifdef _WIN32
static DWORD HandleConnection(void* arg)
#else
static void* HandleConnection(void* arg)
#endif
{
int ret = WS_SUCCESS;
int error;
WOLFSSHD_CONNECTION* conn = NULL;
WOLFSSH* ssh = NULL;
if (arg == NULL) {
ret = WS_BAD_ARGUMENT;
}
if (ret == WS_SUCCESS) {
conn = (WOLFSSHD_CONNECTION*)arg;
ssh = wolfSSH_new(conn->ctx);
if (ssh == NULL) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Failed to create new WOLFSSH struct");
ret = -1;
}
}
if (ret == WS_SUCCESS) {
int select_ret = 0;
long graceTime;
wolfSSH_set_fd(ssh, conn->fd);
wolfSSH_SetUserAuthCtx(ssh, conn->auth);
/* set alarm for login grace time */
graceTime = wolfSSHD_AuthGetGraceTime(conn->auth);
if (graceTime > 0) {
#ifdef WIN32
/* @TODO SetTimer(NULL, NULL, graceTime, alarmCatch); */
#else
signal(SIGALRM, alarmCatch);
alarm((unsigned int)graceTime);
#endif
}
ret = wolfSSH_accept(ssh);
error = wolfSSH_get_error(ssh);
while (timeOut == 0 && (ret != WS_SUCCESS
&& ret != WS_SCP_INIT && ret != WS_SFTP_COMPLETE)
&& (error == WS_WANT_READ || error == WS_WANT_WRITE)) {
select_ret = tcp_select(conn->fd, 1);
if (select_ret == WS_SELECT_RECV_READY ||
select_ret == WS_SELECT_ERROR_READY ||
error == WS_WANT_WRITE)
{
ret = wolfSSH_accept(ssh);
error = wolfSSH_get_error(ssh);
}
else if (select_ret == WS_SELECT_TIMEOUT)
error = WS_WANT_READ;
else
error = WS_FATAL_ERROR;
}
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] grace time = %ld timeout = %d", graceTime, timeOut);
if (graceTime > 0) {
if (timeOut) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Failed login within grace period");
}
#ifdef WIN32
/* @TODO SetTimer(NULL, NULL, graceTime, alarmCatch); */
#else
alarm(0); /* cancel any alarm */
#endif
}
if (ret != WS_SUCCESS && ret != WS_SFTP_COMPLETE &&
ret != WS_SCP_INIT) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Failed to accept WOLFSSH connection from %s error %d",
conn->ip, ret);
}
}
if (ret == WS_SUCCESS || ret == WS_SFTP_COMPLETE || ret == WS_SCP_INIT) {
WPASSWD* pPasswd = NULL;
WOLFSSHD_CONFIG* usrConf;
char* usr;
/* get configuration for user */
usr = wolfSSH_GetUsername(ssh);
usrConf = wolfSSHD_AuthGetUserConf(conn->auth, usr, NULL, NULL,
NULL, NULL, NULL);
if (usrConf == NULL) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Error getting user configuration");
ret = WS_FATAL_ERROR;
}
#ifndef WIN32
if (ret == WS_SUCCESS || ret == WS_SFTP_COMPLETE ||
ret == WS_SCP_INIT) {
pPasswd = getpwnam((const char *)usr);
if (pPasswd == NULL) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Error getting user info");
ret = WS_FATAL_ERROR;
}
}
#endif
if (ret != WS_FATAL_ERROR) {
/* check for any forced command set for the user */
switch (wolfSSH_GetSessionType(ssh)) {
case WOLFSSH_SESSION_SHELL:
#ifdef WOLFSSH_SHELL
if (ret == WS_SUCCESS) {
wolfSSH_Log(WS_LOG_INFO, "[SSHD] Entering new shell");
SHELL_Subsystem(conn, ssh, pPasswd, usrConf, NULL);
}
#else
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Shell support is disabled");
ret = WS_NOT_COMPILED;
#endif
break;
case WOLFSSH_SESSION_SUBSYSTEM:
/* test for known subsystems */
switch (ret) {
case WS_SFTP_COMPLETE:
#ifdef WOLFSSH_SFTP
ret = SFTP_Subsystem(conn, ssh, pPasswd, usrConf);
#else
err_sys("SFTP not compiled in. Please use "
"--enable-sftp");
#endif
break;
case WS_SCP_INIT:
#ifdef WOLFSSH_SCP
ret = SCP_Subsystem(conn, ssh, pPasswd, usrConf);
#else
err_sys("SCP not compiled in. Please use "
"--enable-scp");
#endif
break;
default:
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Unknown or build not supporting sub"
"system found [%s]",
wolfSSH_GetSessionCommand(ssh));
ret = WS_NOT_COMPILED;
}
break;
case WOLFSSH_SESSION_UNKNOWN:
case WOLFSSH_SESSION_EXEC:
#if defined(WOLFSSH_SHELL)
if (ret == WS_SUCCESS) {
wolfSSH_Log(WS_LOG_INFO,
"[SSHD] Entering exec session [%s]",
wolfSSH_GetSessionCommand(ssh));
SHELL_Subsystem(conn, ssh, pPasswd, usrConf,
wolfSSH_GetSessionCommand(ssh));
}
#endif /* WOLFSSH_SHELL */
/* SCP can be an exec type */
if (ret == WS_SCP_INIT) {
#ifdef WOLFSSH_SCP
ret = SCP_Subsystem(conn, ssh, pPasswd, usrConf);
#else
err_sys("SCP not compiled in. Please use "
"--enable-scp");
#endif
}
break;
case WOLFSSH_SESSION_TERMINAL:
default:
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Unknown or build not supporting session type "
"found");
ret = WS_NOT_COMPILED;
}
}
}
error = wolfSSH_get_error(ssh);
if (error != WS_SOCKET_ERROR_E && error != WS_FATAL_ERROR) {
wolfSSH_Log(WS_LOG_INFO, "[SSHD] Attempting to close down connection");
ret = wolfSSH_shutdown(ssh);
/* peer hung up, stop shutdown */
if (ret == WS_SOCKET_ERROR_E) {
ret = 0;
}
error = wolfSSH_get_error(ssh);
if (error != WS_SOCKET_ERROR_E &&
(error == WS_WANT_READ || error == WS_WANT_WRITE)) {
int maxAttempt = 10; /* make 10 attempts max before giving up */
int attempt;
for (attempt = 0; attempt < maxAttempt; attempt++) {
ret = wolfSSH_worker(ssh, NULL);
error = wolfSSH_get_error(ssh);
/* peer succesfully closed down gracefully */
if (ret == WS_CHANNEL_CLOSED) {
ret = 0;
break;
}
/* peer hung up, stop shutdown */
if (ret == WS_SOCKET_ERROR_E) {
ret = 0;
break;
}
if (ret == WS_FATAL_ERROR &&
(error != WS_WANT_READ &&
error != WS_WANT_WRITE)) {
break;
}
#ifdef _WIN32
Sleep(1);
#else
usleep(100000);
#endif
}
if (attempt == maxAttempt) {
wolfSSH_Log(WS_LOG_INFO,
"[SSHD] Gave up on gracefull shutdown, closing the socket");
}
}
}
/* check if there is a response to the shutdown */
wolfSSH_free(ssh);
if (conn != NULL) {
byte sc[1024];
shutdown(conn->fd, 1);
/* Spin until socket closes. */
do {
ret = (int)recv(conn->fd, sc, 1024, 0);
} while (ret > 0);
WCLOSESOCKET(conn->fd);
}
wolfSSH_Log(WS_LOG_INFO, "[SSHD] Return from closing connection = %d", ret);
WFREE(conn, NULL, DYNTYPE_SSHD);
#ifdef _WIN32
return 0;
#else
return NULL;
#endif
}
/* returns WS_SUCCESS on success */
static int NewConnection(WOLFSSHD_CONNECTION* conn)
{
int ret = WS_SUCCESS;
#ifndef WIN32
int pd = 0;
pd = fork();
if (pd < 0) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Issue spawning new process");
ret = -1;
}
if (ret == WS_SUCCESS) {
if (pd == 0) {
/* child process */
WCLOSESOCKET(conn->listenFd);
signal(SIGINT, SIG_DFL);
(void)HandleConnection((void*)conn);
exit(0);
}
else {
/* do not wait for status of child process, and signal that the
child can be reaped to avoid zombie processes when running in
the foreground */
signal(SIGCHLD, SIG_IGN);
wolfSSH_Log(WS_LOG_INFO, "[SSHD] Spawned new process %d\n", pd);
WCLOSESOCKET(conn->fd);
}
}
#else
HANDLE t;
DWORD id;
if (conn->isThreaded) {
t = CreateThread(NULL, 0, HandleConnection, (void*)conn, 0, &id);
if (t == NULL) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Issue creating new thread");
ret = WS_FATAL_ERROR;
}
else {
wolfSSH_Log(WS_LOG_INFO, "[SSHD] Spawned new thread %d\n", id);
CloseHandle(t);
}
}
else {
HandleConnection((void*)conn);
}
#endif
return ret;
}
/* return non zero value for a pending connection */
static int PendingConnection(WS_SOCKET_T fd)
{
int ret;
struct timeval t;
fd_set r, w, e;
WS_SOCKET_T nfds = fd + 1;
t.tv_usec = 0;
t.tv_sec = WOLFSSHD_TIMEOUT;
FD_ZERO(&r);
FD_ZERO(&w);
FD_ZERO(&e);
FD_SET(fd, &r);
errno = 0;
ret = select((int)nfds, &r, &w, &e, &t);
if (ret < 0) {
/* a socket level issue happend, could just be a system call int. */
if (errno != EINTR) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] TCP socket error on select()");
quit = 1;
}
ret = 0;
}
else if (ret > 0) {
if (FD_ISSET(fd, &r)) {
wolfSSH_Log(WS_LOG_INFO, "[SSHD] Incoming TCP data found");
}
else {
wolfSSH_Log(WS_LOG_INFO, "[SSHD] Found TCP write or error data");
ret = 0; /* nothing to read */
}
}
return ret;
}
int myoptind = 0;
char* myoptarg = NULL;
#ifdef _WIN32
#include <tchar.h>
#include <shellapi.h>
SERVICE_STATUS serviceStatus = { 0 };
SERVICE_STATUS_HANDLE serviceStatusHandle = NULL;
HANDLE serviceStop = INVALID_HANDLE_VALUE;
#define WOLFSSHD_SERVICE_NAME _T("wolfSSHd")
static void wolfSSHD_ServiceCb(DWORD CtrlCode)
{
switch (CtrlCode) {
case SERVICE_CONTROL_STOP:
if (serviceStatus.dwCurrentState != SERVICE_RUNNING)
break;
serviceStatus.dwControlsAccepted = 0;
serviceStatus.dwCurrentState = SERVICE_STOP_PENDING;
serviceStatus.dwWin32ExitCode = 0;
serviceStatus.dwCheckPoint = 4;
if (SetServiceStatus(serviceStatusHandle, &serviceStatus) == FALSE) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Issue setting service status");
}
/* send out signal that the service is stopping */
SetEvent(serviceStop);
break;
default:
break;
}
}
static char* _convertHelper(WCHAR* in, void* heap) {
int retSz;
char* ret;
retSz = (int)wcslen(in) * 2;
ret = (char*)WMALLOC(retSz + 1, heap, DYNTYPE_SSHD);
if (ret != NULL) {
size_t numConv = 0;
if (wcstombs_s(&numConv, ret, retSz, in, retSz) != 0) {
WFREE(ret, heap, DYNTYPE_SSHD);
ret = NULL;
}
}
return ret;
}
static void StartSSHD(DWORD argc, LPTSTR* wargv)
#else
static int StartSSHD(int argc, char** argv)
#endif
{
int ret = WS_SUCCESS;
word16 port = 0;
WS_SOCKET_T listenFd = 0;
int ch;
WOLFSSHD_CONFIG* conf = NULL;
WOLFSSHD_AUTH* auth = NULL;
WOLFSSH_CTX* ctx = NULL;
byte isDaemon = 1;
byte testMode = 0;
const char* configFile = "/etc/ssh/sshd_config";
const char* hostKeyFile = NULL;
byte* banner = NULL;
logFile = stderr;
wolfSSH_SetLoggingCb(wolfSSHDLoggingCb);
#ifdef DEBUG_WOLFSSL
wolfSSL_Debugging_ON();
#endif
#ifdef _WIN32
char** argv = NULL;
DWORD i;
LPWSTR* cmdArgs = NULL;
LPWSTR cmdLn;
int cmdArgC = 0;
/* get what the command line was and parse arguments from it */
cmdLn = GetCommandLineW();
cmdArgs = CommandLineToArgvW(cmdLn, &cmdArgC);
if (cmdArgs == NULL) {
ret = WS_FATAL_ERROR;
}
argc = cmdArgC;
if (ret == WS_SUCCESS) {
for (i = 0; i < argc; i++) {
if (WSTRCMP((char*)(cmdArgs[i]), "-D") == 0) {
isDaemon = 0;
}
}
}
if (isDaemon) {
/* Set the logging to go to OutputDebugString */
wolfSSH_SetLoggingCb(ServiceDebugCb);
if (ret == WS_SUCCESS) {
/* we want the arguments to be normal char strings not wchar_t */
argv = (char**)WMALLOC(argc * sizeof(char*), NULL, DYNTYPE_SSHD);
if (argv == NULL) {
ret = WS_MEMORY_E;
}
else {
unsigned int z;
for (z = 0; z < argc; z++) {
argv[z] = _convertHelper(cmdArgs[z], NULL);
}
}
}
}
else {
argv = (char**)wargv;
}
#endif
signal(SIGINT, interruptCatch);
WSTARTTCP();
if (ret == WS_SUCCESS) {
wolfSSH_Init();
}
if (ret == WS_SUCCESS) {
conf = wolfSSHD_ConfigNew(NULL);
if (conf == NULL) {
ret = WS_MEMORY_E;
}
}
while ((ch = mygetopt(argc, argv, "?f:p:h:dDE:o:t")) != -1) {
switch (ch) {
case 'f':
configFile = myoptarg;
break;
case 'p':
if (ret == WS_SUCCESS) {
if (myoptarg == NULL) {
ret = WS_BAD_ARGUMENT;
break;
}
ret = XATOI(myoptarg);
if (ret < 0) {
fprintf(stderr, "Issue parsing port number %s\n",
myoptarg);
ret = WS_BAD_ARGUMENT;
}
else {
if (ret <= (word16)-1) {
port = (word16)ret;
ret = WS_SUCCESS;
}
else {
fprintf(stderr, "Port number %d too big.\n", ret);
ret = WS_BAD_ARGUMENT;
}
}
}
break;
case 'h':
hostKeyFile = myoptarg;
break;
case 'd':
debugMode = 1; /* turn on debug mode */
break;
case 'D':
isDaemon = 0;
break;
case 'E':
ret = WFOPEN(NULL, &logFile, myoptarg, "ab");
if (ret != 0 || logFile == WBADFILE) {
fprintf(stderr, "Unable to open log file %s\n", myoptarg);
ret = WS_FATAL_ERROR;
}
break;
case 'o':
#ifdef WOLFSSH_IGNORE_UNKNOWN_CONFIG
wolfSSH_Log(WS_LOG_DEBUG, "[SSHD] ignoring -o.");
break;
#else
ShowUsage();
#ifndef _WIN32
return WS_FATAL_ERROR;
#else
return;
#endif
#endif
case 't':
testMode = 1;
break;
case '?':
ShowUsage();
#ifndef _WIN32
return WS_SUCCESS;
#else
return;
#endif
default:
ShowUsage();
#ifndef _WIN32
return WS_SUCCESS;
#else
return;
#endif
}
}
if (ret == WS_SUCCESS) {
ret = wolfSSHD_ConfigLoad(conf, configFile);
if (ret != WS_SUCCESS) {
wolfSSH_Log(WS_LOG_ERROR, "Error reading in configure file %s\n",
configFile);
}
}
/* port was not overridden with argument, read from config file */
if (ret == WS_SUCCESS && port == 0) {
port = wolfSSHD_ConfigGetPort(conf);
}
/* check if host key file was passed in */
if (hostKeyFile != NULL) {
wolfSSHD_ConfigSetHostKeyFile(conf, hostKeyFile);
}
if (ret == WS_SUCCESS) {
wolfSSH_Log(WS_LOG_INFO, "[SSHD] Starting wolfSSH SSHD application");
ret = SetupCTX(conf, &ctx, &banner);
}
if (ret == WS_SUCCESS) {
auth = wolfSSHD_AuthCreateUser(NULL, conf);
if (auth == NULL) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Issue creating auth struct");
ret = WS_MEMORY_E;
}
}
if (logFile == NULL) {
logFile = stderr;
}
/* run as a daemon or service */
#ifndef WIN32
if (ret == WS_SUCCESS && isDaemon) {
pid_t p;
#ifdef __unix__
/* Daemonizing in POSIX, so set a syslog based log */
if (logFile == stderr) {
wolfSSH_SetLoggingCb(SyslogCb);
}
#endif
p = fork();
if (p < 0) {
fprintf(stderr, "Failed to fork process\n");
exit(EXIT_FAILURE);
}
if (p > 0) {
exit(EXIT_SUCCESS); /* stop parent process */
}
if (setsid() < 0) {
fprintf(stderr, "Failed to set a new session");
ret = WS_FATAL_ERROR;
}
else {
signal(SIGCHLD, ConnClose);
p = fork();
if (p < 0) {
fprintf(stderr, "Failed to fork process\n");
exit(EXIT_FAILURE);
}
if (p > 0) {
exit(EXIT_SUCCESS);
}
umask(0);
if (chdir("/") < 0) {
ret = WS_FATAL_ERROR;
}
if (ret == WS_SUCCESS) {
int fd;
fd = open("/dev/null", O_RDWR);
if (fd < 0) {
ret = WS_FATAL_ERROR;
}
else {
if (dup2(fd, STDIN_FILENO) < 0 ||
dup2(fd, STDOUT_FILENO) < 0 ||
dup2(fd, STDERR_FILENO) < 0) {
ret = WS_FATAL_ERROR;
}
close(fd);
}
}
}
}
#else
if (isDaemon) {
/* Set function to handle service query and commands */
serviceStatusHandle = RegisterServiceCtrlHandler(WOLFSSHD_SERVICE_NAME, wolfSSHD_ServiceCb);
if (serviceStatusHandle == NULL) {
ret = WS_FATAL_ERROR;
}
else {
/* Update service status as 'start pending' */
ZeroMemory(&serviceStatus, sizeof(serviceStatus));
serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
serviceStatus.dwCurrentState = SERVICE_START_PENDING;
if (SetServiceStatus(serviceStatusHandle, &serviceStatus) == FALSE) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Issue updating service status");
}
}
if (ret == WS_SUCCESS) {
/* Create a stop event to watch on */
serviceStop = CreateEvent(NULL, TRUE, FALSE, NULL);
if (serviceStop == NULL) {
serviceStatus.dwControlsAccepted = 0;
serviceStatus.dwCurrentState = SERVICE_STOPPED;
serviceStatus.dwWin32ExitCode = GetLastError();
serviceStatus.dwCheckPoint = 1;
if (SetServiceStatus(serviceStatusHandle, &serviceStatus) == FALSE) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Issue updating service status");
}
return;
}
}
if (cmdArgs != NULL) {
LocalFree(cmdArgs);
}
}
#endif
if (ret == WS_SUCCESS && !testMode) {
wolfSSHD_ConfigSavePID(conf);
wolfSSH_Log(WS_LOG_INFO, "[SSHD] Starting to listen on port %d", port);
tcp_listen(&listenFd, &port, 1);
wolfSSH_Log(WS_LOG_INFO, "[SSHD] Listening on port %d", port);
if (wolfSSHD_AuthReducePermissions(auth) != WS_SUCCESS) {
wolfSSH_Log(WS_LOG_INFO, "[SSHD] Error lowering permissions level");
ret = WS_FATAL_ERROR;
}
#ifdef WIN32
if (ret == WS_SUCCESS && isDaemon) {
/* update service status as started */
serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
serviceStatus.dwCurrentState = SERVICE_RUNNING;
serviceStatus.dwWin32ExitCode = 0;
serviceStatus.dwCheckPoint = 0;
if (SetServiceStatus(serviceStatusHandle, &serviceStatus) == FALSE) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Issue updating service status");
}
}
#endif
/* wait for incoming connections and fork them off */
while (ret == WS_SUCCESS && quit == 0) {
WOLFSSHD_CONNECTION* conn;
#ifdef WOLFSSL_NUCLEUS
struct addr_struct clientAddr;
#else
struct sockaddr_in6 clientAddr;
socklen_t clientAddrSz = sizeof(clientAddr);
#endif
conn = (WOLFSSHD_CONNECTION*)WMALLOC(sizeof(WOLFSSHD_CONNECTION),
NULL, DYNTYPE_SSHD);
if (conn == NULL) {
wolfSSH_Log(WS_LOG_ERROR,
"[SSHD] Failed to malloc memory for connection");
break;
}
conn->auth = auth;
conn->listenFd = (int)listenFd;
conn->isThreaded = isDaemon;
/* wait for a connection */
if (PendingConnection(listenFd)) {
conn->ctx = ctx;
#ifdef WOLFSSL_NUCLEUS
conn->fd = NU_Accept(listenFd, &clientAddr, 0);
#else
conn->fd = (int)accept(listenFd, (struct sockaddr*)&clientAddr,
&clientAddrSz);
if (conn->fd >= 0) {
if (clientAddr.sin6_family == AF_INET) {
struct sockaddr_in* addr4 =
(struct sockaddr_in*)&clientAddr;
inet_ntop(AF_INET, &addr4->sin_addr, conn->ip,
INET_ADDRSTRLEN);
}
else if (clientAddr.sin6_family == AF_INET6) {
inet_ntop(AF_INET6, &clientAddr.sin6_addr, conn->ip,
INET6_ADDRSTRLEN);
}
}
#endif
{
#ifdef USE_WINDOWS_API
unsigned long blocking = 1;
if (ioctlsocket(conn->fd, FIONBIO, &blocking)
== SOCKET_ERROR) {
WLOG(WS_LOG_DEBUG, "wolfSSH non-fatal error: "
"ioctlsocket failed");
WCLOSESOCKET(conn->fd);
WFREE(conn, NULL, DYNTYPE_SSHD);
continue;
}
#elif defined(WOLFSSL_MDK_ARM) || defined(WOLFSSL_KEIL_TCP_NET) \
|| defined (WOLFSSL_TIRTOS)|| defined(WOLFSSL_VXWORKS) || \
defined(WOLFSSL_NUCLEUS)
/* non blocking not supported, for now */
#else
int flags = fcntl(conn->fd, F_GETFL, 0);
if (flags < 0) {
WLOG(WS_LOG_DEBUG, "wolfSSH non-fatal error: "
"fcntl get failed");
WCLOSESOCKET(conn->fd);
WFREE(conn, NULL, DYNTYPE_SSHD);
continue;
}
flags = fcntl(conn->fd, F_SETFL, flags | O_NONBLOCK);
if (flags < 0) {
WLOG(WS_LOG_DEBUG, "wolfSSH non-fatal error: "
"fcntl set failed");
WCLOSESOCKET(conn->fd);
WFREE(conn, NULL, DYNTYPE_SSHD);
continue;
}
#endif
}
ret = NewConnection(conn);
}
else {
XFREE(conn, NULL, DYNTYPE_SSHD);
}
#ifdef _WIN32
/* check if service has been shutdown */
if (isDaemon && WaitForSingleObject(serviceStop, 0) ==
WAIT_OBJECT_0) {
quit = 1;
}
#endif
}
}
#ifdef _WIN32
/* close down windows service */
if (isDaemon) {
CloseHandle(serviceStop);
serviceStatus.dwControlsAccepted = 0;
serviceStatus.dwCurrentState = SERVICE_STOPPED;
serviceStatus.dwWin32ExitCode = 0;
serviceStatus.dwCheckPoint = 3;
if (serviceStatusHandle != NULL &&
SetServiceStatus(serviceStatusHandle, &serviceStatus) == FALSE) {
wolfSSH_Log(WS_LOG_ERROR, "[SSHD] Issue updating service status");
}
}
#endif
CleanupCTX(conf, &ctx, &banner);
if (banner) {
WFREE(banner, NULL, DYNTYPE_STRING);
}
wolfSSHD_ConfigFree(conf);
wolfSSHD_AuthFreeUser(auth);
wolfSSH_Cleanup();
#ifdef _WIN32
if (isDaemon) { /* free up temporary memory used for conversion of args from wchar_t */
unsigned int z;
for (z = 0; z < argc; z++) {
WFREE(argv[z], NULL, DYNTYPE_SSHD);
}
WFREE(argv, NULL, DYNTYPE_SSHD);
}
#else
return 0;
#endif
}
#ifdef _WIN32
/* Used to setup a console and run command as a user.
* returns the process exit value */
static int SetupConsole(char* inCmd)
{
HANDLE sOut;
HANDLE sIn;
HPCON pCon = 0;
COORD cord = { 80,24 }; /* Default to 80x24. Updated later. */
STARTUPINFOEXW ext;
int ret = WS_SUCCESS;
PWSTR cmd = NULL;
size_t cmdSz = 0;
PROCESS_INFORMATION processInfo;
size_t sz = 0;
DWORD processState = 0;
PCSTR shellCmd = "c:\\windows\\system32\\cmd.exe";
if (inCmd == NULL) {
return -1;
}
sIn = GetStdHandle(STD_INPUT_HANDLE);
if (WSTRCMP(shellCmd, inCmd) != 0) {
/* if not opening a shell, pipe virtual terminal sequences to 'nul' */
if (CreatePseudoConsole(cord, sIn, INVALID_HANDLE_VALUE, 0, &pCon) != S_OK) {
ret = WS_FATAL_ERROR;
}
else {
CloseHandle(sIn);
}
}
else
{
/* if opening a shell, pipe virtual terminal sequences back to calling process */
sOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (CreatePseudoConsole(cord, sIn, sOut, 0, &pCon) != S_OK) {
ret = WS_FATAL_ERROR;
}
else {
CloseHandle(sIn);
CloseHandle(sOut);
}
}
/* setup startup extended info for pseudo terminal */
ZeroMemory(&ext, sizeof(ext));
if (ret == WS_SUCCESS) {
ext.StartupInfo.cb = sizeof(STARTUPINFOEX);
(void)InitializeProcThreadAttributeList(NULL, 1, 0, &sz);
if (sz == 0) {
ret = WS_FATAL_ERROR;
}
if (ret == WS_SUCCESS) {
/* Using HeapAlloc for better support when possibly passing
memory between Windows Modules */
ext.lpAttributeList =
(PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, sz);
if (ext.lpAttributeList == NULL) {
ret = WS_FATAL_ERROR;
}
}
if (ret == WS_SUCCESS) {
if (InitializeProcThreadAttributeList(ext.lpAttributeList, 1, 0,
&sz) != TRUE) {
ret = WS_FATAL_ERROR;
}
}
if (ret == WS_SUCCESS) {
if (UpdateProcThreadAttribute(ext.lpAttributeList, 0,
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
pCon, sizeof(HPCON), NULL, NULL) != TRUE) {
ret = WS_FATAL_ERROR;
}
}
}
if (ret == WS_SUCCESS) {
cmdSz = WSTRLEN(inCmd) + 1; /* +1 for terminator */
cmd = (PWSTR)HeapAlloc(GetProcessHeap(), 0, sizeof(wchar_t) * cmdSz);
if (cmd == NULL) {
ret = WS_MEMORY_E;
}
else {
size_t numConv = 0;
WMEMSET(cmd, 0, sizeof(wchar_t) * cmdSz);
mbstowcs_s(&numConv, cmd, cmdSz, inCmd, strlen(inCmd));
}
}
ZeroMemory(&processInfo, sizeof(processInfo));
if (ret == WS_SUCCESS) {
if (CreateProcessW(NULL, cmd,
NULL, NULL, FALSE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL,
&ext.StartupInfo, &processInfo) != TRUE) {
return WS_FATAL_ERROR;
}
else {
DWORD ava = 0;
WaitForInputIdle(processInfo.hProcess, 1000);
do {
/* wait indefinitly for console process to exit */
if (ava == 0) {
if (WaitForSingleObject(processInfo.hProcess, INFINITE) == WAIT_FAILED) {
break;
}
}
/* check if process is still running and give time to drain pipes */
if (GetExitCodeProcess(processInfo.hProcess, &processState)
== TRUE) {
if (processState != STILL_ACTIVE) {
Sleep(100); /* give the stdout/stderr of process a
* little time to write to pipe */
if (PeekNamedPipe(GetStdHandle(STD_OUTPUT_HANDLE), NULL, 0, NULL, &ava, NULL)
== TRUE) {
if (ava > 0) {
/* if data still pending then continue
* sending it over SSH */
continue;
}
}
break;
}
}
} while (1);
CloseHandle(processInfo.hThread);
}
}
if (cmd != NULL) {
HeapFree(GetProcessHeap(), 0, cmd);
}
if (ext.lpAttributeList != NULL) {
HeapFree(GetProcessHeap(), 0, ext.lpAttributeList);
}
if (pCon != 0) {
ClosePseudoConsole(pCon);
}
return processState;
}
#endif /* _WIN32 */
int main(int argc, char** argv)
{
#ifdef _WIN32
/* First look if this is a service being started */
int i, isService = 1;
for (i = 0; i < argc; i++) {
if (WSTRCMP(argv[i], "-D") == 0) {
isService = 0;
}
if (WSTRCMP(argv[i], "-r") == 0) {
if (argc < i + 1) {
/* was expecting command to run after -r argument */
return -1;
}
return SetupConsole(argv[i + 1]);
}
}
if (isService) {
SERVICE_TABLE_ENTRY ServiceTable[] =
{
{_T("wolfSSHd"), (LPSERVICE_MAIN_FUNCTION)StartSSHD},
{NULL, NULL}
};
if (StartServiceCtrlDispatcher(ServiceTable) == FALSE) {
printf("StartServiceCtrlDispatcher failed\n");
return GetLastError();
}
}
else {
StartSSHD(argc, (LPTSTR*)argv);
}
return 0;
#else
return StartSSHD(argc, argv);
#endif
}
#else
#include <stdio.h>
/* helpful print out if compiling without SSHD feature enabled */
int main(int argc, char** argv)
{
printf("Not compiled in. Please recompile wolfSSH with :\n");
printf("--enable-sshd (user_settings.h macro define WOLFSSH_SSHD\n");
return -1;
}
#endif /* WOLFSSH_SSHD */