diff --git a/.gitignore b/.gitignore index c73676e9b..46dea9e79 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,7 @@ examples/sftpclient/wolfsftp examples/scpclient/wolfscp # applications +apps/wolfssh/wolfssh apps/wolfsshd/wolfsshd apps/wolfsshd/test/test_configuration diff --git a/Makefile.am b/Makefile.am index a8759d4f4..f03df800c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -33,7 +33,7 @@ EXTRA_DIST+= LICENSING README.md ChangeLog.md include src/include.am include wolfssh/include.am -include apps/wolfsshd/include.am +include apps/include.am include examples/include.am include tests/include.am include keys/include.am diff --git a/apps/include.am b/apps/include.am new file mode 100644 index 000000000..f832780b8 --- /dev/null +++ b/apps/include.am @@ -0,0 +1,6 @@ +# vim:ft=automake +# included from Top Level Makefile.am +# All paths should be given relative to the root + +include apps/wolfssh/include.am +include apps/wolfsshd/include.am diff --git a/apps/wolfssh/README.md b/apps/wolfssh/README.md new file mode 100644 index 000000000..96e12789f --- /dev/null +++ b/apps/wolfssh/README.md @@ -0,0 +1,32 @@ +WOLFSSH CLIENT +============== + +The wolfSSH client will connect to a server and try to open a terminal. It'll +default the username to your current username, and it will try to use your +ecdsa private key to authenticate. The key file path is hard coded to +`$HOME/.ssh/id_ecdsa`. It is currently far enough along I can use it. The +private keys are the ones produced by the OpenSSL command line tool, not the +ssh-keygen tool. + +Phase 2 is going to bring reading the config files `/etc/ssh/ssh_config` and +`$HOME/.ssh/config`. It will handle OpenSSH style modern keys. It will also +have support for SSH-AGENT and forwarding. + +Command Line Options +-------------------- + + -E logfile : Specify a different log file. + -G : Print out the configuration as used. + -l login_name : Overrides the login name specified in the destination. + -N : Do not execute remote command. + -p port : Overrides the destination port number. + -V : Print out the version. + +The destination option is the only required option. It can be in the two +following formats: + + [user@]hostname + ssh://[user@]hostname[:port] + +The default value for _user_ is the current user's login name. The default +value for _port_ is 22. diff --git a/apps/wolfssh/common.c b/apps/wolfssh/common.c new file mode 100644 index 000000000..3945866c2 --- /dev/null +++ b/apps/wolfssh/common.c @@ -0,0 +1,535 @@ +/* common.c + * + * Copyright (C) 2014-2023 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 . + */ + + +#ifdef HAVE_CONFIG_H + #include +#endif + +#define WOLFSSH_TEST_CLIENT + +#include +#include +#include +#include +#include +#include +#include "apps/wolfssh/common.h" +#ifndef USE_WINDOWS_API + #include +#endif + +#ifdef WOLFSSH_CERTS + #include +#endif + +static byte userPublicKeyBuf[512]; +static byte* userPublicKey = userPublicKeyBuf; +static const byte* userPublicKeyType = NULL; +static byte userPassword[256]; +static const byte* userPrivateKeyType = NULL; +static word32 userPublicKeySz = 0; +static byte pubKeyLoaded = 0; /* was a public key loaded */ +static byte userPrivateKeyBuf[1191]; +static byte* userPrivateKey = userPrivateKeyBuf; +static word32 userPublicKeyTypeSz = 0; +static word32 userPrivateKeySz = sizeof(userPrivateKeyBuf); +static word32 userPrivateKeyTypeSz = 0; +static byte isPrivate = 0; + + +#ifdef WOLFSSH_CERTS +#if 0 +/* compiled in for using RSA certificates instead of ECC certificate */ +static const byte publicKeyType[] = "x509v3-ssh-rsa"; +static const byte privateKeyType[] = "ssh-rsa"; +#else +static const byte publicKeyType[] = "x509v3-ecdsa-sha2-nistp256"; +#endif +#endif + + +#if defined(WOLFSSH_CERTS) + +static int load_der_file(const char* filename, byte** out, word32* outSz) +{ + WFILE* file; + byte* in; + word32 inSz; + int ret; + + if (filename == NULL || out == NULL || outSz == NULL) + return -1; + + ret = WFOPEN(NULL, &file, filename, "rb"); + if (ret != 0 || file == WBADFILE) + return -1; + + if (WFSEEK(NULL, file, 0, WSEEK_END) != 0) { + WFCLOSE(NULL, file); + return -1; + } + inSz = (word32)WFTELL(NULL, file); + WREWIND(NULL, file); + + if (inSz == 0) { + WFCLOSE(NULL, file); + return -1; + } + + in = (byte*)WMALLOC(inSz, NULL, 0); + if (in == NULL) { + WFCLOSE(NULL, file); + return -1; + } + + ret = (int)WFREAD(NULL, in, 1, inSz, file); + if (ret <= 0 || (word32)ret != inSz) { + ret = -1; + WFREE(in, NULL, 0); + in = 0; + inSz = 0; + } + else + ret = 0; + + *out = in; + *outSz = inSz; + + WFCLOSE(NULL, file); + + return ret; +} + + +#if (defined(OPENSSL_ALL) || defined(WOLFSSL_IP_ALT_NAME)) +static inline void ato32(const byte* c, word32* u32) +{ + *u32 = (c[0] << 24) | (c[1] << 16) | (c[2] << 8) | c[3]; +} + +/* when set as true then ignore miss matching IP addresses */ +static int IPOverride = 0; + +static int ParseRFC6187(const byte* in, word32 inSz, byte** leafOut, + word32* leafOutSz) +{ + int ret = WS_SUCCESS; + word32 l = 0, m = 0; + + if (inSz < sizeof(word32)) { + printf("inSz %d too small for holding cert name\n", inSz); + return WS_BUFFER_E; + } + + /* Skip the name */ + ato32(in, &l); + m += l + sizeof(word32); + + /* Get the cert count */ + if (ret == WS_SUCCESS) { + word32 count; + + if (inSz - m < sizeof(word32)) + return WS_BUFFER_E; + + ato32(in + m, &count); + m += sizeof(word32); + if (ret == WS_SUCCESS && count == 0) + ret = WS_FATAL_ERROR; /* need at least one cert */ + } + + if (ret == WS_SUCCESS) { + word32 certSz = 0; + + if (inSz - m < sizeof(word32)) + return WS_BUFFER_E; + + ato32(in + m, &certSz); + m += sizeof(word32); + if (ret == WS_SUCCESS) { + /* store leaf cert size to present to user callback */ + *leafOutSz = certSz; + *leafOut = (byte*)in + m; + } + + if (inSz - m < certSz) + return WS_BUFFER_E; + + } + + return ret; +} + +void ClientIPOverride(int flag) +{ + IPOverride = flag; +} +#endif /* OPENSSL_ALL || WOLFSSL_IP_ALT_NAME */ +#endif /* WOLFSSH_CERTS */ + + +int ClientPublicKeyCheck(const byte* pubKey, word32 pubKeySz, void* ctx) +{ + int ret = 0; + + #ifdef DEBUG_WOLFSSH + printf("Sample public key check callback\n" + " public key = %p\n" + " public key size = %u\n" + " ctx = %s\n", pubKey, pubKeySz, (const char*)ctx); + #else + (void)pubKey; + (void)pubKeySz; + (void)ctx; + #endif + +#ifdef WOLFSSH_CERTS +#if defined(OPENSSL_ALL) || defined(WOLFSSL_IP_ALT_NAME) + /* try to parse the certificate and check it's IP address */ + if (pubKeySz > 0) { + DecodedCert dCert; + byte* der = NULL; + word32 derSz = 0; + + if (ParseRFC6187(pubKey, pubKeySz, &der, &derSz) == WS_SUCCESS) { + wc_InitDecodedCert(&dCert, der, derSz, NULL); + if (wc_ParseCert(&dCert, CERT_TYPE, NO_VERIFY, NULL) != 0) { + WLOG(WS_LOG_DEBUG, "public key not a cert"); + } + else { + int ipMatch = 0; + DNS_entry* current = dCert.altNames; + + if (ctx == NULL) { + WLOG(WS_LOG_ERROR, "No host IP set to check against!"); + ret = -1; + } + + if (ret == 0) { + while (current != NULL) { + if (current->type == ASN_IP_TYPE) { + WLOG(WS_LOG_DEBUG, "host cert alt. name IP : %s", + current->ipString); + WLOG(WS_LOG_DEBUG, + "\texpecting host IP : %s", (char*)ctx); + if (XSTRCMP(ctx, current->ipString) == 0) { + WLOG(WS_LOG_DEBUG, "\tmatched!"); + ipMatch = 1; + } + } + current = current->next; + } + } + + if (ipMatch == 0) { + printf("IP did not match expected IP"); + if (!IPOverride) { + printf("\n"); + ret = -1; + } + else { + ret = 0; + printf("..overriding\n"); + } + } + } + FreeDecodedCert(&dCert); + } + } +#else + WLOG(WS_LOG_DEBUG, "wolfSSL not built with OPENSSL_ALL or WOLFSSL_IP_ALT_NAME"); + WLOG(WS_LOG_DEBUG, "\tnot checking IP address from peer's cert"); +#endif +#endif + + return ret; +} + + +int ClientUserAuth(byte authType, + WS_UserAuthData* authData, + void* ctx) +{ + int ret = WOLFSSH_USERAUTH_SUCCESS; + +#ifdef DEBUG_WOLFSSH + /* inspect supported types from server */ + printf("Server supports:\n"); + if (authData->type & WOLFSSH_USERAUTH_PASSWORD) { + printf(" - password\n"); + } + if (authData->type & WOLFSSH_USERAUTH_PUBLICKEY) { + printf(" - publickey\n"); + } + printf("wolfSSH requesting to use type %d\n", authType); +#endif + + /* Wait for request of public key on names known to have one */ + if ((authData->type & WOLFSSH_USERAUTH_PUBLICKEY) && + authData->username != NULL && + authData->usernameSz > 0) { + + /* in the case that the user passed in a public key file, + * use public key auth */ + if (pubKeyLoaded == 1) { + if (authType == WOLFSSH_USERAUTH_PASSWORD) { + return WOLFSSH_USERAUTH_FAILURE; + } + } + } + + if (authType == WOLFSSH_USERAUTH_PUBLICKEY) { + WS_UserAuthData_PublicKey* pk = &authData->sf.publicKey; + + pk->publicKeyType = userPublicKeyType; + pk->publicKeyTypeSz = userPublicKeyTypeSz; + pk->publicKey = userPublicKey; + pk->publicKeySz = userPublicKeySz; + pk->privateKey = userPrivateKey; + pk->privateKeySz = userPrivateKeySz; + + ret = WOLFSSH_USERAUTH_SUCCESS; + } + else if (authType == WOLFSSH_USERAUTH_PASSWORD) { + const char* defaultPassword = (const char*)ctx; + word32 passwordSz = 0; + + if (defaultPassword != NULL) { + passwordSz = (word32)strlen(defaultPassword); + memcpy(userPassword, defaultPassword, passwordSz); + } + else { + printf("Password: "); + fflush(stdout); + ClientSetEcho(0); + if (fgets((char*)userPassword, sizeof(userPassword), stdin) == NULL) { + fprintf(stderr, "Getting password failed.\n"); + ret = WOLFSSH_USERAUTH_FAILURE; + } + else { + char* c = strpbrk((char*)userPassword, "\r\n"); + if (c != NULL) + *c = '\0'; + } + passwordSz = (word32)strlen((const char*)userPassword); + ClientSetEcho(1); + #ifdef USE_WINDOWS_API + printf("\r\n"); + #endif + fflush(stdout); + } + + if (ret == WOLFSSH_USERAUTH_SUCCESS) { + authData->sf.password.password = userPassword; + authData->sf.password.passwordSz = passwordSz; + } + } + + return ret; +} + + +/* type = 2 : shell / execute command settings + * type = 0 : password + * type = 1 : restore default + * return 0 on success */ +int ClientSetEcho(int type) +{ +#if !defined(USE_WINDOWS_API) && !defined(MICROCHIP_PIC32) + static int echoInit = 0; + static struct termios originalTerm; + + if (!echoInit) { + if (tcgetattr(STDIN_FILENO, &originalTerm) != 0) { + printf("Couldn't get the original terminal settings.\n"); + return -1; + } + echoInit = 1; + } + if (type == 1) { + if (tcsetattr(STDIN_FILENO, TCSANOW, &originalTerm) != 0) { + printf("Couldn't restore the terminal settings.\n"); + return -1; + } + } + else { + struct termios newTerm; + memcpy(&newTerm, &originalTerm, sizeof(struct termios)); + + newTerm.c_lflag &= ~ECHO; + if (type == 2) { + newTerm.c_lflag &= ~(ICANON | ECHOE | ECHOK | ECHONL | ISIG); + } + else { + newTerm.c_lflag |= (ICANON | ECHONL); + } + + if (tcsetattr(STDIN_FILENO, TCSANOW, &newTerm) != 0) { + printf("Couldn't turn off echo.\n"); + return -1; + } + } +#else + static int echoInit = 0; + static DWORD originalTerm; + static CONSOLE_SCREEN_BUFFER_INFO screenOrig; + HANDLE stdinHandle = GetStdHandle(STD_INPUT_HANDLE); + if (!echoInit) { + if (GetConsoleMode(stdinHandle, &originalTerm) == 0) { + printf("Couldn't get the original terminal settings.\n"); + return -1; + } + echoInit = 1; + } + if (type == 1) { + if (SetConsoleMode(stdinHandle, originalTerm) == 0) { + printf("Couldn't restore the terminal settings.\n"); + return -1; + } + } + else if (type == 2) { + DWORD newTerm = originalTerm; + + newTerm &= ~ENABLE_PROCESSED_INPUT; + newTerm &= ~ENABLE_PROCESSED_OUTPUT; + newTerm &= ~ENABLE_LINE_INPUT; + newTerm &= ~ENABLE_ECHO_INPUT; + newTerm &= ~(ENABLE_EXTENDED_FLAGS | ENABLE_INSERT_MODE); + + if (SetConsoleMode(stdinHandle, newTerm) == 0) { + printf("Couldn't turn off echo.\n"); + return -1; + } + } + else { + DWORD newTerm = originalTerm; + + newTerm &= ~ENABLE_ECHO_INPUT; + + if (SetConsoleMode(stdinHandle, newTerm) == 0) { + printf("Couldn't turn off echo.\n"); + return -1; + } + } +#endif + + return 0; +} + + +/* Set certificate to use and public key. + * returns 0 on success */ +int ClientUseCert(const char* certName) +{ + int ret = 0; + + if (certName != NULL) { + #ifdef WOLFSSH_CERTS + ret = load_der_file(certName, &userPublicKey, &userPublicKeySz); + if (ret == 0) { + userPublicKeyType = publicKeyType; + userPublicKeyTypeSz = (word32)WSTRLEN((const char*)publicKeyType); + pubKeyLoaded = 1; + } + #else + fprintf(stderr, "Certificate support not compiled in"); + ret = WS_NOT_COMPILED; + #endif + } + + return ret; +} + + +/* Reads the private key to use from file name privKeyName. + * returns 0 on success */ +int ClientSetPrivateKey(const char* privKeyName) +{ + int ret; + + userPrivateKey = NULL; /* create new buffer based on parsed input */ + ret = wolfSSH_ReadKey_file(privKeyName, + (byte**)&userPrivateKey, &userPrivateKeySz, + (const byte**)&userPrivateKeyType, &userPrivateKeyTypeSz, + &isPrivate, NULL); + + return ret; +} + + +/* Set public key to use + * returns 0 on success */ +int ClientUsePubKey(const char* pubKeyName) +{ + int ret; + + userPublicKey = NULL; /* create new buffer based on parsed input */ + ret = wolfSSH_ReadKey_file(pubKeyName, + &userPublicKey, &userPublicKeySz, + (const byte**)&userPublicKeyType, &userPublicKeyTypeSz, + &isPrivate, NULL); + + if (ret == 0) { + pubKeyLoaded = 1; + } + + return ret; +} + +int ClientLoadCA(WOLFSSH_CTX* ctx, const char* caCert) +{ + int ret = 0; + + /* CA certificate to verify host cert with */ + if (caCert) { + #ifdef WOLFSSH_CERTS + byte* der = NULL; + word32 derSz; + + ret = load_der_file(caCert, &der, &derSz); + if (ret == 0) { + if (wolfSSH_CTX_AddRootCert_buffer(ctx, der, derSz, + WOLFSSH_FORMAT_ASN1) != WS_SUCCESS) { + fprintf(stderr, "Couldn't parse in CA certificate."); + ret = WS_PARSE_E; + } + WFREE(der, NULL, 0); + } + #else + (void)ctx; + fprintf(stderr, "Support for certificates not compiled in."); + ret = WS_NOT_COMPILED; + #endif + } + return ret; +} + + +void ClientFreeBuffers(void) +{ + if (userPublicKey != userPublicKeyBuf) { + WFREE(userPublicKey, NULL, DYNTYPE_PRIVKEY); + } + + if (userPrivateKey != userPrivateKeyBuf) { + WFREE(userPrivateKey, NULL, DYNTYPE_PRIVKEY); + } +} diff --git a/apps/wolfssh/common.h b/apps/wolfssh/common.h new file mode 100644 index 000000000..14d45dcba --- /dev/null +++ b/apps/wolfssh/common.h @@ -0,0 +1,36 @@ +/* common.h + * + * Copyright (C) 2014-2023 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 . + */ + +#ifndef APPS_WOLFSSH_COMMON_H +#define APPS_WOLFSSH_COMMON_H + +WOLFSSH_LOCAL int ClientLoadCA(WOLFSSH_CTX* ctx, const char* caCert); +WOLFSSH_LOCAL int ClientUsePubKey(const char* pubKeyName); +WOLFSSH_LOCAL int ClientSetPrivateKey(const char* privKeyName); +WOLFSSH_LOCAL int ClientUseCert(const char* certName); +WOLFSSH_LOCAL int ClientSetEcho(int type); +WOLFSSH_LOCAL int ClientUserAuth(byte authType, WS_UserAuthData* authData, + void* ctx); +WOLFSSH_LOCAL int ClientPublicKeyCheck(const byte* pubKey, word32 pubKeySz, + void* ctx); +WOLFSSH_LOCAL void ClientIPOverride(int flag); +WOLFSSH_LOCAL void ClientFreeBuffers(void); + +#endif /* APPS_WOLFSSH_COMMON_H */ diff --git a/apps/wolfssh/include.am b/apps/wolfssh/include.am new file mode 100644 index 000000000..bfb90663a --- /dev/null +++ b/apps/wolfssh/include.am @@ -0,0 +1,11 @@ +if BUILD_SSHCLIENT + +bin_PROGRAMS += apps/wolfssh/wolfssh +apps_wolfssh_wolfssh_SOURCES = apps/wolfssh/wolfssh.c \ + apps/wolfssh/common.c apps/wolfssh/common.h +apps_wolfssh_wolfssh_LDADD = src/libwolfssh.la +apps_wolfssh_wolfssh_DEPENDENCIES = src/libwolfssh.la + +endif BUILD_SSHCLIENT + +EXTRA_DIST+= apps/wolfssh/README.md diff --git a/apps/wolfssh/wolfssh.c b/apps/wolfssh/wolfssh.c new file mode 100644 index 000000000..9448e3f34 --- /dev/null +++ b/apps/wolfssh/wolfssh.c @@ -0,0 +1,1146 @@ +/* wolfssh.c + * + * Copyright (C) 2014-2023 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 . + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#define WOLFSSH_TEST_CLIENT + +#ifdef WOLFSSL_USER_SETTINGS + #include +#else + #include +#endif + +#include +#include +#include +#include +#ifdef WOLFSSH_AGENT + #include +#endif +#include +#include "examples/client/client.h" +#include "apps/wolfssh/common.h" +#if !defined(USE_WINDOWS_API) && !defined(MICROCHIP_PIC32) + #include +#endif + +#include +#include + +#ifdef WOLFSSH_SHELL + #ifdef HAVE_PTY_H + #include + #endif + #ifdef HAVE_UTIL_H + #include + #endif + #ifdef HAVE_TERMIOS_H + #include + #endif + #ifndef USE_WINDOWS_API + #include + #endif +#endif /* WOLFSSH_SHELL */ + +#ifdef WOLFSSH_AGENT + #include + #include + #include + #include +#endif /* WOLFSSH_AGENT */ + +#ifdef HAVE_SYS_SELECT_H + #include +#endif + +#ifdef WOLFSSH_CERTS + #include +#endif + + +int myoptind = 0; +char* myoptarg = NULL; + + +static void ShowUsage(char* appPath) +{ + const char* appName; + + appName = basename(appPath); + /* Attempt to use the actual program name from the caller. Otherwise, + * default to "wolfssh". */ + if (appName == NULL) { + appName = "wolfssh"; + } + + printf("%s v%s\n", appName, LIBWOLFSSH_VERSION_STRING); + printf("usage: %s [-E logfile] [-G] [-l login_name] [-N] [-p port] " + "[-V] destination\n", + appName); +} + + +#ifdef WOLFSSH_CERTS +static const char* certName = NULL; +static const char* caCert = NULL; +#endif + + +#if defined(WOLFSSH_AGENT) +static inline void ato32(const byte* c, word32* u32) +{ + *u32 = (c[0] << 24) | (c[1] << 16) | (c[2] << 8) | c[3]; +} +#endif + + +static int NonBlockSSH_connect(WOLFSSH* ssh) +{ + int ret; + int error; + SOCKET_T sockfd; + int select_ret = 0; + + ret = wolfSSH_connect(ssh); + error = wolfSSH_get_error(ssh); + sockfd = (SOCKET_T)wolfSSH_get_fd(ssh); + + while (ret != WS_SUCCESS && + (error == WS_WANT_READ || error == WS_WANT_WRITE)) + { + select_ret = tcp_select(sockfd, 1); + + /* Continue in want write cases even if did not select on socket + * because there could be pending data to be written. Added continue + * on want write for test cases where a forced want read was introduced + * and the socket will not be receiving more data. */ + if (error == WS_WANT_WRITE || error == WS_WANT_READ || + select_ret == WS_SELECT_RECV_READY || + select_ret == WS_SELECT_ERROR_READY) + { + ret = wolfSSH_connect(ssh); + error = wolfSSH_get_error(ssh); + } + else if (select_ret == WS_SELECT_TIMEOUT) + error = WS_WANT_READ; + else + error = WS_FATAL_ERROR; + } + + return ret; +} + +#if defined(HAVE_TERMIOS_H) && defined(WOLFSSH_TERM) +WOLFSSH_TERMIOS oldTerm; + +static void modes_store(void) +{ + tcgetattr(STDIN_FILENO, &oldTerm); +} + +static void modes_clear(void) +{ + WOLFSSH_TERMIOS term = oldTerm; + + term.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO | ECHOE | ECHOK + | ECHONL | ECHOPRT | NOFLSH | TOSTOP | FLUSHO + | PENDIN | EXTPROC); + + term.c_iflag &= ~(ISTRIP | INLCR | ICRNL | IGNCR | IXON | IXOFF + | IXANY | IGNBRK | INPCK | PARMRK); +#ifdef IUCLC + term.c_iflag &= ~IUCLC; +#endif + term.c_iflag |= IGNPAR; + + term.c_oflag &= ~(OPOST | ONOCR | ONLRET); +#ifdef OUCLC + term.c_oflag &= ~OLCUC; +#endif + + term.c_cflag &= ~(CSTOPB | PARENB | PARODD | CLOCAL | CRTSCTS); + + tcsetattr(STDIN_FILENO, TCSANOW, &term); +} + +static void modes_reset(void) +{ + tcsetattr(STDIN_FILENO, TCSAFLUSH, &oldTerm); +} + +#define MODES_STORE() modes_store() +#define MODES_CLEAR() modes_clear() +#define MODES_RESET() modes_reset() +#else /* HAVE_TERMIOS_H && WOLFSSH_TERM */ +#define MODES_STORE() do {} while(0) +#define MODES_CLEAR() do {} while(0) +#define MODES_RESET() do {} while(0) +#endif /* HAVE_TERMIOS_H && WOLFSSH_TERM */ + +#if !defined(SINGLE_THREADED) && !defined(WOLFSSL_NUCLEUS) + +typedef struct thread_args { + WOLFSSH* ssh; + wolfSSL_Mutex lock; + byte rawMode; + byte quit; +} thread_args; + +#ifdef _POSIX_THREADS + #define THREAD_RET void* + #define THREAD_RET_SUCCESS NULL +#elif defined(_MSC_VER) + #define THREAD_RET DWORD WINAPI + #define THREAD_RET_SUCCESS 0 +#else + #define THREAD_RET int + #define THREAD_RET_SUCCESS 0 +#endif + + +#ifdef WOLFSSH_TERM +static int sendCurrentWindowSize(thread_args* args) +{ + int ret; + word32 col = 80, row = 24, xpix = 0, ypix = 0; + + wc_LockMutex(&args->lock); +#if defined(_MSC_VER) + { + CONSOLE_SCREEN_BUFFER_INFO cs; + + if (GetConsoleScreenBufferInfo( + GetStdHandle(STD_OUTPUT_HANDLE), &cs) != 0) { + col = cs.srWindow.Right - cs.srWindow.Left + 1; + row = cs.srWindow.Bottom - cs.srWindow.Top + 1; + } + } +#else + { + struct winsize windowSize = { 0,0,0,0 }; + + ioctl(STDOUT_FILENO, TIOCGWINSZ, &windowSize); + col = windowSize.ws_col; + row = windowSize.ws_row; + xpix = windowSize.ws_xpixel; + ypix = windowSize.ws_ypixel; + } +#endif + ret = wolfSSH_ChangeTerminalSize(args->ssh, col, row, xpix, ypix); + wc_UnLockMutex(&args->lock); + + return ret; +} + + +#ifndef _MSC_VER + +#if (defined(__OSX__) || defined(__APPLE__)) +#include +dispatch_semaphore_t windowSem; +#else +#include +static sem_t windowSem; +#endif + +/* capture window change signales */ +static void WindowChangeSignal(int sig) +{ +#if (defined(__OSX__) || defined(__APPLE__)) + dispatch_semaphore_signal(windowSem); +#else + sem_post(&windowSem); +#endif + (void)sig; +} + +/* thread for handling window size adjustments */ +static THREAD_RET windowMonitor(void* in) +{ + thread_args* args; + int ret; + + args = (thread_args*)in; + do { + #if (defined(__OSX__) || defined(__APPLE__)) + dispatch_semaphore_wait(windowSem, DISPATCH_TIME_FOREVER); + #else + sem_wait(&windowSem); + #endif + if (args->quit) { + break; + } + ret = sendCurrentWindowSize(args); + (void)ret; + } while (1); + + return THREAD_RET_SUCCESS; +} +#else /* _MSC_VER */ +/* no SIGWINCH on Windows, poll current terminal size */ +static word32 prevCol, prevRow; + +static int windowMonitor(thread_args* args) +{ + word32 row, col; + int ret = WS_SUCCESS; + CONSOLE_SCREEN_BUFFER_INFO cs; + + if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cs) != 0) { + col = cs.srWindow.Right - cs.srWindow.Left + 1; + row = cs.srWindow.Bottom - cs.srWindow.Top + 1; + + if (prevCol != col || prevRow != row) { + prevCol = col; + prevRow = row; + + wc_LockMutex(&args->lock); + ret = wolfSSH_ChangeTerminalSize(args->ssh, col, row, 0, 0); + wc_UnLockMutex(&args->lock); + } + } + + return ret; +} +#endif /* _MSC_VER */ +#endif /* WOLFSSH_TERM */ + + +static THREAD_RET readInput(void* in) +{ + byte buf[256]; + int bufSz = sizeof(buf); + thread_args* args = (thread_args*)in; + int ret = 0; + word32 sz = 0; +#ifdef USE_WINDOWS_API + HANDLE stdinHandle = GetStdHandle(STD_INPUT_HANDLE); +#endif + + while (ret >= 0) { + WMEMSET(buf, 0, bufSz); + #ifdef USE_WINDOWS_API + /* Using A version to avoid potential 2 byte chars */ + ret = ReadConsoleA(stdinHandle, (void*)buf, bufSz - 1, (DWORD*)&sz, + NULL); + (void)windowMonitor(args); + #else + ret = (int)read(STDIN_FILENO, buf, bufSz -1); + sz = (word32)ret; + #endif + if (ret <= 0) { + fprintf(stderr, "Error reading stdin\n"); + return THREAD_RET_SUCCESS; + } + /* lock SSH structure access */ + wc_LockMutex(&args->lock); + ret = wolfSSH_stream_send(args->ssh, buf, sz); + wc_UnLockMutex(&args->lock); + if (ret <= 0) { + fprintf(stderr, "Couldn't send data\n"); + return THREAD_RET_SUCCESS; + } + } +#if !defined(WOLFSSH_NO_ECC) && defined(FP_ECC) && defined(HAVE_THREAD_LS) + wc_ecc_fp_free(); /* free per thread cache */ +#endif + return THREAD_RET_SUCCESS; +} + + +static THREAD_RET readPeer(void* in) +{ + byte buf[256]; + int bufSz = sizeof(buf); + thread_args* args = (thread_args*)in; + int ret = 0; + int fd = wolfSSH_get_fd(args->ssh); + word32 bytes; +#ifdef USE_WINDOWS_API + HANDLE stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); +#endif + fd_set readSet; + fd_set errSet; + + FD_ZERO(&readSet); + FD_ZERO(&errSet); + FD_SET(fd, &readSet); + FD_SET(fd, &errSet); + +#ifdef USE_WINDOWS_API + /* set handle to use for window resize */ + wc_LockMutex(&args->lock); + wolfSSH_SetTerminalResizeCtx(args->ssh, stdoutHandle); + wc_UnLockMutex(&args->lock); +#endif + + while (ret >= 0) { + #if defined(WOLFSSH_TERM) && defined(USE_WINDOWS_API) + (void)windowMonitor(args); + #endif + + bytes = select(fd + 1, &readSet, NULL, &errSet, NULL); + wc_LockMutex(&args->lock); + while (bytes > 0 && (FD_ISSET(fd, &readSet) || FD_ISSET(fd, &errSet))) { + /* there is something to read off the wire */ + WMEMSET(buf, 0, bufSz); + ret = wolfSSH_stream_read(args->ssh, buf, bufSz - 1); + if (ret == WS_EXTDATA) { /* handle extended data */ + do { + WMEMSET(buf, 0, bufSz); + ret = wolfSSH_extended_data_read(args->ssh, buf, bufSz - 1); + if (ret < 0) + err_sys("Extended data read failed."); + buf[bufSz - 1] = '\0'; + #ifdef USE_WINDOWS_API + fprintf(stderr, "%s", buf); + #else + if (write(STDERR_FILENO, buf, ret) < 0) { + perror("Issue with stderr write "); + } + #endif + } while (ret > 0); + } + else if (ret <= 0) { + if (ret == WS_FATAL_ERROR) { + ret = wolfSSH_get_error(args->ssh); + if (ret == WS_WANT_READ) { + continue; + } + #ifdef WOLFSSH_AGENT + else if (ret == WS_CHAN_RXD) { + byte agentBuf[512]; + int rxd, txd; + word32 channel = 0; + + wolfSSH_GetLastRxId(args->ssh, &channel); + rxd = wolfSSH_ChannelIdRead(args->ssh, channel, + agentBuf, sizeof(agentBuf)); + if (rxd > 4) { + word32 msgSz = 0; + + ato32(agentBuf, &msgSz); + if (msgSz > (word32)rxd - 4) { + rxd += wolfSSH_ChannelIdRead(args->ssh, channel, + agentBuf + rxd, + sizeof(agentBuf) - rxd); + } + + txd = rxd; + rxd = sizeof(agentBuf); + ret = wolfSSH_AGENT_Relay(args->ssh, + agentBuf, (word32*)&txd, + agentBuf, (word32*)&rxd); + if (ret == WS_SUCCESS) { + ret = wolfSSH_ChannelIdSend(args->ssh, channel, + agentBuf, rxd); + } + } + WMEMSET(agentBuf, 0, sizeof(agentBuf)); + continue; + } + #endif /* WOLFSSH_AGENT */ + } + else if (ret != WS_EOF) { + err_sys("Stream read failed."); + } + } + else { + buf[bufSz - 1] = '\0'; + + #ifdef USE_WINDOWS_API + if (args->rawMode == 0) { + ret = wolfSSH_ConvertConsole(args->ssh, stdoutHandle, buf, + ret); + if (ret != WS_SUCCESS && ret != WS_WANT_READ) { + err_sys("issue with print out"); + } + if (ret == WS_WANT_READ) { + ret = 0; + } + } + else { + printf("%s", buf); + fflush(stdout); + } + #else + if (write(STDOUT_FILENO, buf, ret) < 0) { + perror("write to stdout error "); + } + #endif + } + ret = wolfSSH_stream_peek(args->ssh, buf, bufSz); + if (ret <= 0) { + bytes = 0; /* read it all */ + } + } + wc_UnLockMutex(&args->lock); + } +#if !defined(WOLFSSH_NO_ECC) && defined(FP_ECC) && defined(HAVE_THREAD_LS) + wc_ecc_fp_free(); /* free per thread cache */ +#endif + + return THREAD_RET_SUCCESS; +} +#endif /* !SINGLE_THREADED && !WOLFSSL_NUCLEUS */ + + +#if defined(WOLFSSL_PTHREADS) && defined(WOLFSSL_TEST_GLOBAL_REQ) + +static int callbackGlobalReq(WOLFSSH *ssh, void *buf, word32 sz, + int reply, void *ctx) +{ + char reqStr[] = "SampleRequest"; + + if ((WOLFSSH *)ssh != *(WOLFSSH **)ctx) { + printf("ssh(%p) != ctx(%p)\n", ssh, *(WOLFSSH **)ctx); + return WS_FATAL_ERROR; + } + + if (WSTRLEN(reqStr) == sz + && (WSTRNCMP((char *)buf, reqStr, sz) == 0) + && reply == 1) { + printf("Global Request\n"); + return WS_SUCCESS; + } + else { + return WS_FATAL_ERROR; + } + +} +#endif + + +#ifdef WOLFSSH_AGENT +typedef struct WS_AgentCbActionCtx { + struct sockaddr_un name; + int fd; + int state; +} WS_AgentCbActionCtx; + +static const char EnvNameAuthPort[] = "SSH_AUTH_SOCK"; + +static int wolfSSH_AGENT_DefaultActions(WS_AgentCbAction action, void* vCtx) +{ + WS_AgentCbActionCtx* ctx = (WS_AgentCbActionCtx*)vCtx; + int ret = WS_AGENT_SUCCESS; + + if (action == WOLFSSH_AGENT_LOCAL_SETUP) { + const char* sockName; + struct sockaddr_un* name = &ctx->name; + size_t size; + int err; + + sockName = getenv(EnvNameAuthPort); + if (sockName == NULL) + ret = WS_AGENT_NOT_AVAILABLE; + + if (ret == WS_AGENT_SUCCESS) { + WMEMSET(name, 0, sizeof(struct sockaddr_un)); + name->sun_family = AF_LOCAL; + WSTRNCPY(name->sun_path, sockName, sizeof(name->sun_path)); + name->sun_path[sizeof(name->sun_path) - 1] = '\0'; + size = WSTRLEN(sockName) + + offsetof(struct sockaddr_un, sun_path); + + ctx->fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (ctx->fd == -1) { + ret = WS_AGENT_SETUP_E; + err = errno; + fprintf(stderr, "socket() = %d\n", err); + } + } + + if (ret == WS_AGENT_SUCCESS) { + ret = connect(ctx->fd, + (struct sockaddr *)name, (socklen_t)size); + if (ret < 0) { + ret = WS_AGENT_SETUP_E; + err = errno; + fprintf(stderr, "connect() = %d", err); + } + } + + if (ret == WS_AGENT_SUCCESS) + ctx->state = AGENT_STATE_CONNECTED; + } + else if (action == WOLFSSH_AGENT_LOCAL_CLEANUP) { + int err; + + err = close(ctx->fd); + if (err != 0) { + err = errno; + fprintf(stderr, "close() = %d", err); + if (ret == 0) + ret = WS_AGENT_SETUP_E; + } + } + else + ret = WS_AGENT_INVALID_ACTION; + + return ret; +} + + +static int wolfSSH_AGENT_IO_Cb(WS_AgentIoCbAction action, + void* buf, word32 bufSz, void* vCtx) +{ + WS_AgentCbActionCtx* ctx = (WS_AgentCbActionCtx*)vCtx; + int ret = WS_AGENT_INVALID_ACTION; + + if (action == WOLFSSH_AGENT_IO_WRITE) { + const byte* wBuf = (const byte*)buf; + ret = (int)write(ctx->fd, wBuf, bufSz); + if (ret < 0) { + ret = WS_CBIO_ERR_GENERAL; + } + } + else if (action == WOLFSSH_AGENT_IO_READ) { + byte* rBuf = (byte*)buf; + ret = (int)read(ctx->fd, rBuf, bufSz); + if (ret < 0) { + ret = WS_CBIO_ERR_GENERAL; + } + } + + return ret; +} + + +#endif /* WOLFSSH_AGENT */ + + +struct config { + char* logFile; + char* user; + char* hostname; + char* keyFile; + char* pubKeyFile; + char* command; + word32 printConfig:1; + word32 noCommand:1; + word16 port; +}; + + +static int config_init_default(struct config* config) +{ + char* env; + size_t sz; + + WMEMSET(config, 0, sizeof(*config)); + config->port = 22; + + env = getenv("USER"); + if (env != NULL) { + char* user; + + sz = WSTRLEN(env) + 1; + user = (char*)WMALLOC(sz, NULL, 0); + if (user != NULL) { + strcpy(user, env); + config->user = user; + } + } + + env = getenv("HOME"); + if (env != NULL) { + const char* defaultName = "/.ssh/id_ecdsa"; + const char* pubSuffix = ".pub"; + char* keyFile; + + sz = WSTRLEN(env) + WSTRLEN(defaultName) + 1; + keyFile = (char*)WMALLOC(sz, NULL, 0); + if (keyFile != NULL) { + strcpy(keyFile, env); + strcat(keyFile, defaultName); + config->keyFile = keyFile; + } + + sz += WSTRLEN(pubSuffix); + keyFile = (char*)WMALLOC(sz, NULL, 0); + if (keyFile != NULL) { + strcpy(keyFile, env); + strcat(keyFile, defaultName); + strcat(keyFile, pubSuffix); + config->pubKeyFile = keyFile; + } + } + + return 0; +} + + +static int config_parse_command_line(struct config* config, + int argc, char** argv) +{ + int ch; + + while ((ch = mygetopt(argc, argv, "E:Gl:Np:V")) != -1) { + switch (ch) { + case 'E': + config->logFile = myoptarg; + break; + + case 'G': + config->printConfig = 1; + break; + + case 'l': + config->user = myoptarg; + break; + + case 'N': + config->noCommand = 1; + break; + + case 'p': + config->port = (word16)atoi(myoptarg); + break; + + case 'V': + fprintf(stderr, "wolfSSH v%s, wolfSSL v%s\n", + LIBWOLFSSH_VERSION_STRING, + LIBWOLFSSL_VERSION_STRING); + exit(EXIT_SUCCESS); + + default: + ShowUsage(argv[0]); + exit(EXIT_FAILURE); + } + } + + /* Parse the destination. Either: + * - [user@]hostname + * - ssh://[user@]hostname[:port] */ + if (myoptind < argc) { + const char* uriPrefix = "ssh://"; + char* dest; + char* cursor; + char* found; + size_t sz; + int checkPort; + + myoptarg = argv[myoptind]; + + sz = WSTRLEN(myoptarg) + 1; + dest = (char*)WMALLOC(sz, NULL, 0); + WMEMCPY(dest, myoptarg, sz); + cursor = dest; + + if (WSTRSTR(cursor, uriPrefix)) { + checkPort = 1; + cursor += WSTRLEN(uriPrefix); + } + else { + checkPort = 0; + } + + found = WSTRCHR(cursor, '@'); + if (found == cursor) { + fprintf(stderr, "can't start destination with just an @\n"); + } + if (found != NULL) { + *found = '\0'; + if (config->user) { + free(config->user); + } + sz = WSTRLEN(cursor); + config->user = WMALLOC(sz + 1, NULL, 0); + strcpy(config->user, cursor); + cursor = found + 1; + } + + if (checkPort) { + found = WSTRCHR(cursor, ':'); + if (found != NULL) { + *found = '\0'; + sz = WSTRLEN(cursor); + config->hostname = (char*)WMALLOC(sz + 1, NULL, 0); + strcpy(config->hostname, cursor); + cursor = found + 1; + if (*cursor != 0) { + config->port = atoi(cursor); + } + } + } + else { + sz = WSTRLEN(cursor); + config->hostname = (char*)WMALLOC(sz + 1, NULL, 0); + strcpy(config->hostname, cursor); + } + + free(dest); + myoptind++; + } + + if (myoptind < argc) { + int i; + size_t commandSz; + char* cursor; + char* command; + + /* Count the spaces needed. The following will calculate one extra + * space but that's for the nul termination. */ + commandSz = argc - myoptind; + for (i = myoptind; i < argc; i++) { + commandSz += WSTRLEN(argv[i]); + } + + command = (char*)WMALLOC(commandSz, NULL, 0); + config->command = command; + cursor = command; + + for (i = myoptind; i < argc; i++) { + cursor = stpcpy(cursor, argv[i]); + *cursor = ' '; + cursor++; + } + *(--cursor) = '\0'; + myoptind++; + } + + return 0; +} + + +static int config_print(struct config* config) +{ + if (config->printConfig) { + printf("user %s\n", config->user ? config->user : "none"); + printf("hostname %s\n", config->hostname ? config->hostname : "none"); + printf("port %u\n", config->port); + printf("keyFile %s\n", config->keyFile ? config->keyFile : "none"); + printf("pubKeyFile %s\n", + config->keyFile ? config->keyFile : "none"); + printf("noCommand %s\n", config->noCommand ? "true" : "false"); + printf("logfile %s\n", config->logFile ? config->logFile : "default"); + printf("command %s\n", config->command ? config->command : "none"); + } + + return 0; +} + + +static int config_cleanup(struct config* config) +{ + if (config->user) { + WFREE(config->user, NULL, 0); + } + if (config->hostname) { + WFREE(config->hostname, NULL, 0); + } + if (config->keyFile) { + WFREE(config->keyFile, NULL, 0); + } + if (config->pubKeyFile) { + WFREE(config->pubKeyFile, NULL, 0); + } + if (config->command) { + WFREE(config->command, NULL, 0); + } + + return 0; +} + + +static THREAD_RETURN WOLFSSH_THREAD wolfSSH_Client(void* args) +{ + WOLFSSH_CTX* ctx = NULL; + WOLFSSH* ssh = NULL; + SOCKET_T sockFd = WOLFSSH_SOCKET_INVALID; + SOCKADDR_IN_T clientAddr; + socklen_t clientAddrSz = sizeof(clientAddr); + int ret = 0; + const char* password = NULL; + byte keepOpen = 1; +#ifdef USE_WINDOWS_API + byte rawMode = 0; +#endif +#ifdef WOLFSSH_AGENT + byte useAgent = 0; + WS_AgentCbActionCtx agentCbCtx; +#endif + struct config config; + + MODES_STORE(); + + ((func_args*)args)->return_code = 0; + + config_init_default(&config); + config_parse_command_line(&config, + ((func_args*)args)->argc, ((func_args*)args)->argv); + config_print(&config); + + if (config.user == NULL) + err_sys("client requires a username parameter."); + +#ifdef SINGLE_THREADED + if (keepOpen) + err_sys("Threading needed for terminal session\n"); +#endif + + if (config.keyFile) { + ret = ClientSetPrivateKey(config.keyFile); + if (ret == 0) { + #ifdef WOLFSSH_CERTS + /* passed in certificate to use */ + if (certName) { + (void)ClientUseCert(certName); + } + else + #endif + if (config.pubKeyFile) { + (void)ClientUsePubKey(config.pubKeyFile); + } + } + } + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); + if (ctx == NULL) + err_sys("Couldn't create wolfSSH client context."); + + wolfSSH_SetUserAuth(ctx, ClientUserAuth); + +#ifdef WOLFSSH_AGENT + if (useAgent) { + wolfSSH_CTX_set_agent_cb(ctx, + wolfSSH_AGENT_DefaultActions, wolfSSH_AGENT_IO_Cb); + wolfSSH_CTX_AGENT_enable(ctx, 1); + } +#endif + +#ifdef WOLFSSH_CERTS + ClientLoadCA(ctx, caCert); +#endif /* WOLFSSH_CERTS */ + + wolfSSH_CTX_SetPublicKeyCheck(ctx, ClientPublicKeyCheck); + + ssh = wolfSSH_new(ctx); + if (ssh == NULL) + err_sys("Couldn't create wolfSSH session."); + +#if defined(WOLFSSL_PTHREADS) && defined(WOLFSSL_TEST_GLOBAL_REQ) + wolfSSH_SetGlobalReq(ctx, callbackGlobalReq); + wolfSSH_SetGlobalReqCtx(ssh, &ssh); /* dummy ctx */ +#endif + + if (password != NULL) + wolfSSH_SetUserAuthCtx(ssh, (void*)password); + +#ifdef WOLFSSH_AGENT + if (useAgent) { + WMEMSET(&agentCbCtx, 0, sizeof(agentCbCtx)); + agentCbCtx.state = AGENT_STATE_INIT; + wolfSSH_set_agent_cb_ctx(ssh, &agentCbCtx); + } +#endif + + wolfSSH_SetPublicKeyCheckCtx(ssh, (void*)config.hostname); + + ret = wolfSSH_SetUsername(ssh, config.user); + if (ret != WS_SUCCESS) + err_sys("Couldn't set the username."); + + build_addr(&clientAddr, config.hostname, config.port); + tcp_socket(&sockFd); + ret = connect(sockFd, (const struct sockaddr *)&clientAddr, clientAddrSz); + if (ret != 0) + err_sys("Couldn't connect to server."); + + tcp_set_nonblocking(&sockFd); + + ret = wolfSSH_set_fd(ssh, (int)sockFd); + if (ret != WS_SUCCESS) + err_sys("Couldn't set the session's socket."); + + if (config.command != NULL) { + ret = wolfSSH_SetChannelType(ssh, WOLFSSH_SESSION_EXEC, + (byte*)config.command, + (word32)WSTRLEN((char*)config.command)); + if (ret != WS_SUCCESS) + err_sys("Couldn't set the channel type."); + } + +#ifdef WOLFSSH_TERM + if (keepOpen) { + ret = wolfSSH_SetChannelType(ssh, WOLFSSH_SESSION_TERMINAL, NULL, 0); + if (ret != WS_SUCCESS) + err_sys("Couldn't set the terminal channel type."); + } +#endif + + ret = NonBlockSSH_connect(ssh); + if (ret != WS_SUCCESS) + err_sys("Couldn't connect SSH stream."); + +#if !defined(SINGLE_THREADED) && !defined(WOLFSSL_NUCLEUS) +#if 0 + if (keepOpen) /* set up for psuedo-terminal */ + ClientSetEcho(2); +#endif + + MODES_CLEAR(); + + if (config.command != NULL || keepOpen == 1) { + #if defined(_POSIX_THREADS) + thread_args arg; + pthread_t thread[3]; + + wc_InitMutex(&arg.lock); + arg.ssh = ssh; +#ifdef WOLFSSH_TERM + arg.quit = 0; + #if (defined(__OSX__) || defined(__APPLE__)) + windowSem = dispatch_semaphore_create(0); + #else + sem_init(&windowSem, 0, 0); + #endif + + if (config.command) { + int err; + + /* exec command does not contain initial terminal size, + * unlike pty-req. Send an inital terminal size for recieving + * the results of the command */ + err = sendCurrentWindowSize(&arg); + if (err != WS_SUCCESS) { + fprintf(stderr, "Issue sending exec initial terminal size\n\r"); + } + } + + signal(SIGWINCH, WindowChangeSignal); + pthread_create(&thread[0], NULL, windowMonitor, (void*)&arg); +#endif /* WOLFSSH_TERM */ + pthread_create(&thread[1], NULL, readInput, (void*)&arg); + pthread_create(&thread[2], NULL, readPeer, (void*)&arg); + pthread_join(thread[2], NULL); +#ifdef WOLFSSH_TERM + /* Wake the windowMonitor thread so it can exit. */ + arg.quit = 1; + #if (defined(__OSX__) || defined(__APPLE__)) + dispatch_semaphore_signal(windowSem); + #else + sem_post(&windowSem); + #endif + pthread_join(thread[0], NULL); +#endif /* WOLFSSH_TERM */ + pthread_cancel(thread[1]); + pthread_join(thread[1], NULL); +#ifdef WOLFSSH_TERM + #if (defined(__OSX__) || defined(__APPLE__)) + dispatch_release(windowSem); + #else + sem_destroy(&windowSem); + #endif +#endif /* WOLFSSH_TERM */ + #elif defined(_MSC_VER) + thread_args arg; + HANDLE thread[2]; + + arg.ssh = ssh; + arg.rawMode = rawMode; + wc_InitMutex(&arg.lock); + + if (config.command) { + int err; + + /* exec command does not contain initial terminal size, + * unlike pty-req. Send an inital terminal size for recieving + * the results of the command */ + err = sendCurrentWindowSize(&arg); + if (err != WS_SUCCESS) { + fprintf(stderr, "Issue sending exec initial terminal size\n\r"); + } + } + + thread[0] = CreateThread(NULL, 0, readInput, (void*)&arg, 0, 0); + thread[1] = CreateThread(NULL, 0, readPeer, (void*)&arg, 0, 0); + WaitForSingleObject(thread[1], INFINITE); + CloseHandle(thread[0]); + CloseHandle(thread[1]); + #else + err_sys("No threading to use"); + #endif + if (keepOpen) + ClientSetEcho(1); + } +#endif + + ret = wolfSSH_shutdown(ssh); + /* do not continue on with shutdown process if peer already disconnected */ + if (ret != WS_SOCKET_ERROR_E + && wolfSSH_get_error(ssh) != WS_SOCKET_ERROR_E) { + if (ret != WS_SUCCESS) { + err_sys("Sending the shutdown messages failed."); + } + ret = wolfSSH_worker(ssh, NULL); + if (ret == WS_CHANNEL_CLOSED) { + /* Shutting down, channel closing isn't a fail. */ + ret = WS_SUCCESS; + } + else if (ret != WS_SUCCESS) { + err_sys("Failed to listen for close messages from the peer."); + } + } + WCLOSESOCKET(sockFd); + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); + if (ret != WS_SUCCESS && ret != WS_SOCKET_ERROR_E) + err_sys("Closing client stream failed"); + + ClientFreeBuffers(); +#if !defined(WOLFSSH_NO_ECC) && defined(FP_ECC) && defined(HAVE_THREAD_LS) + wc_ecc_fp_free(); /* free per thread cache */ +#endif + + config_cleanup(&config); + MODES_RESET(); + + return 0; +} + + +int main(int argc, char** argv) +{ + func_args args; + + args.argc = argc; + args.argv = argv; + args.return_code = 0; + args.user_auth = NULL; + + WSTARTTCP(); + + #ifdef DEBUG_WOLFSSH + wolfSSH_Debugging_ON(); + #endif + + wolfSSH_Init(); + + wolfSSH_Client(&args); + + wolfSSH_Cleanup(); + + return args.return_code; +} diff --git a/configure.ac b/configure.ac index 026af7c41..3d29d3aa8 100644 --- a/configure.ac +++ b/configure.ac @@ -177,6 +177,11 @@ AC_ARG_ENABLE([sshd], [AS_HELP_STRING([--enable-sshd],[Enable SSHD support (default: disabled)])], [ENABLED_SSHD=$enableval],[ENABLED_SSHD=no]) +# SSH Client +AC_ARG_ENABLE([sshclient], + [AS_HELP_STRING([--enable-sshclient],[Enable SSH client app (default: disabled)])], + [ENABLED_SSHCLIENT=$enableval],[ENABLED_SSHCLIENT=no]) + # TCP/IP Forwarding AC_ARG_ENABLE([fwd], [AS_HELP_STRING([--enable-fwd],[Enable TCP/IP Forwarding support (default: disabled)])], @@ -227,7 +232,7 @@ AC_ARG_ENABLE([distro], AS_IF([test "x$ENABLED_DISTRO" = "xyes"], [ENABLED_ALL=yes; enable_shared=yes; enable_static=yes]) AS_IF([test "x$ENABLED_ALL" = "xyes"], - [ENABLED_KEYGEN=yes; ENABLED_SCP=yes; ENABLED_SFTP=yes; ENABLED_FWD=yes; ENABLED_SHELL=yes; ENABLED_AGENT=yes; ENABLED_SSHD=yes; ENABLED_CERTS=yes]) + [ENABLED_KEYGEN=yes; ENABLED_SCP=yes; ENABLED_SFTP=yes; ENABLED_FWD=yes; ENABLED_SHELL=yes; ENABLED_AGENT=yes; ENABLED_SSHD=yes; ENABLED_SSHCLIENT=yes; ENABLED_CERTS=yes]) AS_IF([test "x$ENABLED_SSHD" = "xyes"], [ENABLED_SHELL=yes]) @@ -246,12 +251,16 @@ AS_IF([test "x$ENABLED_PTERM" = "xyes"], [AM_CPPFLAGS="$AM_CPPFLAGS -DWOLFSSH_TERM"]) AS_IF([test "x$ENABLED_SHELL" = "xyes"], [AM_CPPFLAGS="$AM_CPPFLAGS -DWOLFSSH_SHELL"]) -AS_IF([test "x$ENABLED_AGENT" = "xyes"],[AM_CPPFLAGS="$AM_CPPFLAGS -DWOLFSSH_AGENT"]) -AS_IF([test "x$ENABLED_CERTS" = "xyes"],[AM_CPPFLAGS="$AM_CPPFLAGS -DWOLFSSH_CERTS"]) +AS_IF([test "x$ENABLED_AGENT" = "xyes"], + [AM_CPPFLAGS="$AM_CPPFLAGS -DWOLFSSH_AGENT"]) +AS_IF([test "x$ENABLED_CERTS" = "xyes"], + [AM_CPPFLAGS="$AM_CPPFLAGS -DWOLFSSH_CERTS"]) AS_IF([test "x$ENABLED_SMALLSTACK" = "xyes"], - [AM_CPPFLAGS="$AM_CPPFLAGS -DWOLFSSH_SMALL_STACK"]) + [AM_CPPFLAGS="$AM_CPPFLAGS -DWOLFSSH_SMALL_STACK"]) AS_IF([test "x$ENABLED_SSHD" = "xyes"], - [AM_CPPFLAGS="$AM_CPPFLAGS -DWOLFSSH_SSHD"]) + [AM_CPPFLAGS="$AM_CPPFLAGS -DWOLFSSH_SSHD"]) +AS_IF([test "x$ENABLED_SSHCLIENT" = "xyes"], + [AM_CPPFLAGS="$AM_CPPFLAGS -DWOLFSSH_SSHCLIENT"]) if test "$ENABLED_SSHD" = "yes"; then if test -n "$PAM_LIB" @@ -304,6 +313,7 @@ AM_CONDITIONAL([BUILD_TERM],[test "x$ENABLED_TERM" = "xyes"]) AM_CONDITIONAL([BUILD_SHELL],[test "x$ENABLED_SHELL" = "xyes"]) AM_CONDITIONAL([BUILD_AGENT],[test "x$ENABLED_AGENT" = "xyes"]) AM_CONDITIONAL([BUILD_SSHD],[test "x$ENABLED_SSHD" = "xyes"]) +AM_CONDITIONAL([BUILD_SSHCLIENT],[test "x$ENABLED_SSHCLIENT" = "xyes"]) AM_CONDITIONAL([BUILD_CERTS],[test "x$ENABLED_CERTS" = "xyes"]) AX_HARDEN_CC_COMPILER_FLAGS @@ -346,6 +356,7 @@ AS_ECHO([" * echoserver shell support: $ENABLED_SHELL"]) AS_ECHO([" * scp: $ENABLED_SCP"]) AS_ECHO([" * sftp: $ENABLED_SFTP"]) AS_ECHO([" * sshd: $ENABLED_SSHD"]) +AS_ECHO([" * ssh client: $ENABLED_SSHCLIENT"]) AS_ECHO([" * agent: $ENABLED_AGENT"]) AS_ECHO([" * TCP/IP Forwarding: $ENABLED_FWD"]) AS_ECHO([" * X.509 Certs: $ENABLED_CERTS"])