From 9d1cbbab8ee6bf665dabb84ccb091bb1af5d9405 Mon Sep 17 00:00:00 2001 From: John Safranek Date: Mon, 2 Mar 2020 13:09:18 -0800 Subject: [PATCH] wolfSCP Add support for an example SCP client. --- .gitignore | 1 + examples/include.am | 1 + examples/scpclient/include.am | 14 ++ examples/scpclient/scpclient.c | 398 +++++++++++++++++++++++++++++++++ examples/scpclient/scpclient.h | 29 +++ src/log.c | 3 + src/wolfscp.c | 90 ++++++++ wolfssh/log.h | 1 + wolfssh/wolfscp.h | 4 + 9 files changed, 541 insertions(+) create mode 100644 examples/scpclient/include.am create mode 100644 examples/scpclient/scpclient.c create mode 100644 examples/scpclient/scpclient.h diff --git a/.gitignore b/.gitignore index bfe0e14..3f05dd8 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,7 @@ examples/echoserver/echoserver examples/server/server examples/portfwd/portfwd examples/sftpclient/wolfsftp +examples/scpclient/wolfscp # test output tests/*.test diff --git a/examples/include.am b/examples/include.am index 34a78a9..6d11933 100644 --- a/examples/include.am +++ b/examples/include.am @@ -7,3 +7,4 @@ include examples/server/include.am include examples/echoserver/include.am include examples/portfwd/include.am include examples/sftpclient/include.am +include examples/scpclient/include.am diff --git a/examples/scpclient/include.am b/examples/scpclient/include.am new file mode 100644 index 0000000..81d562a --- /dev/null +++ b/examples/scpclient/include.am @@ -0,0 +1,14 @@ +# vim:ft=automake +# All paths should be given relative to the root + +if BUILD_SCP +if BUILD_EXAMPLE_CLIENTS +noinst_PROGRAMS += examples/scpclient/wolfscp +noinst_HEADERS += examples/scpclient/scpclient.h +examples_scpclient_wolfscp_SOURCES = examples/scpclient/scpclient.c +examples_scpclient_wolfscp_LDADD = src/libwolfssh.la +examples_scpclient_wolfscp_DEPENDENCIES = src/libwolfssh.la +endif +endif + +DISTCLEANFILES+= examples/scpclient/.libs/wolfscp diff --git a/examples/scpclient/scpclient.c b/examples/scpclient/scpclient.c new file mode 100644 index 0000000..8e31cb7 --- /dev/null +++ b/examples/scpclient/scpclient.c @@ -0,0 +1,398 @@ +/* scpclient.c + * + * Copyright (C) 2014-2020 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 . + */ + +#define WOLFSSH_TEST_CLIENT + +#include +#if !defined(USE_WINDOWS_API) && !defined(MICROCHIP_PIC32) + #include +#endif +#include +#include +#include +#include + +#if defined(HAVE_ECC) && defined(FP_ECC) && defined(HAVE_THREAD_LS) + #include +#endif +#include "examples/scpclient/scpclient.h" + + +/* type = 2 : shell / execute command settings + * type = 0 : password + * type = 1 : restore default + * return 0 on success */ +static int SetEcho(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; +} + + +byte userPassword[256]; + +static int wsUserAuth(byte authType, + WS_UserAuthData* authData, + void* ctx) +{ + const char* defaultPassword = (const char*)ctx; + word32 passwordSz = 0; + int ret = WOLFSSH_USERAUTH_SUCCESS; + + if (authType == WOLFSSH_USERAUTH_PASSWORD) { + if (defaultPassword != NULL) { + passwordSz = (word32)strlen(defaultPassword); + memcpy(userPassword, defaultPassword, passwordSz); + } + else { + printf("Password: "); + fflush(stdout); + SetEcho(0); + if (fgets((char*)userPassword, sizeof(userPassword), stdin) == NULL) { + printf("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); + SetEcho(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; + } + } + else if (authType == WOLFSSH_USERAUTH_PUBLICKEY) { + ret = WOLFSSH_USERAUTH_INVALID_AUTHTYPE; + } + + return ret; +} + + +static int wsPublicKeyCheck(const byte* pubKey, word32 pubKeySz, void* ctx) +{ + #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 + return 0; +} + + +#define USAGE_WIDE "12" +static void ShowUsage(void) +{ + printf("wolfscp %s\n", LIBWOLFSSH_VERSION_STRING); + printf(" -%c %-" USAGE_WIDE "s %s\n", 'h', "", + "display this help and exit"); + printf(" -%c %-" USAGE_WIDE "s %s, default %s\n", 'H', "", + "host to connect to", wolfSshIp); + printf(" -%c %-" USAGE_WIDE "s %s, default %u\n", 'p', "", + "port to connect on", wolfSshPort); + printf(" -%c %-" USAGE_WIDE "s %s\n", 'u', "", + "username to authenticate as (REQUIRED)"); + printf(" -%c %-" USAGE_WIDE "s %s\n", 'P', "", + "password for username, prompted if omitted"); + printf(" -%c %-" USAGE_WIDE "s %s\n", 'L', ":", + "copy from local to server"); + printf(" -%c %-" USAGE_WIDE "s %s\n", 'S', ":", + "copy from server to local"); +} + + +enum copyDir {copyNone, copyToSrv, copyFromSrv}; + + +THREAD_RETURN WOLFSSH_THREAD scp_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 argc = ((func_args*)args)->argc; + int ret = 0; + char** argv = ((func_args*)args)->argv; + const char* username = NULL; + const char* password = NULL; + char* host = (char*)wolfSshIp; + char* path1 = NULL; + char* path2 = NULL; + word16 port = wolfSshPort; + byte nonBlock = 0; + enum copyDir dir = copyNone; + char ch; + + ((func_args*)args)->return_code = 0; + + while ((ch = mygetopt(argc, argv, "H:L:NP:S:hp:u:")) != -1) { + switch (ch) { + case 'H': + host = myoptarg; + break; + + case 'L': + dir = copyToSrv; + path1 = myoptarg; + break; + + case 'N': + nonBlock = 1; + break; + + case 'P': + password = myoptarg; + break; + + case 'S': + dir = copyFromSrv; + path1 = myoptarg; + break; + + case 'h': + ShowUsage(); + exit(EXIT_SUCCESS); + + case 'u': + username = myoptarg; + break; + + case 'p': + port = (word16)atoi(myoptarg); + #if !defined(NO_MAIN_DRIVER) || defined(USE_WINDOWS_API) + if (port == 0) + err_sys("port number cannot be 0"); + #endif + break; + + default: + ShowUsage(); + exit(MY_EX_USAGE); + break; + } + } + + myoptind = 0; /* reset for test cases */ + + if (username == NULL) + err_sys("client requires a username parameter."); + + if (dir == copyNone) + err_sys("didn't specify a copy direction"); + + /* split file path */ + if (path1 == NULL) { + err_sys("Missing path value"); + } + + path2 = strchr(path1, ':'); + if (path2 == NULL) { + err_sys("Missing colon separator"); + } + + *path2 = 0; + path2++; + + if (strlen(path1) == 0 || strlen(path2) == 0) { + err_sys("Empty path values"); + } + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); + if (ctx == NULL) + err_sys("Couldn't create wolfSSH client context."); + + if (((func_args*)args)->user_auth == NULL) + wolfSSH_SetUserAuth(ctx, wsUserAuth); + else + wolfSSH_SetUserAuth(ctx, ((func_args*)args)->user_auth); + + ssh = wolfSSH_new(ctx); + if (ssh == NULL) + err_sys("Couldn't create wolfSSH session."); + + if (password != NULL) + wolfSSH_SetUserAuthCtx(ssh, (void*)password); + + wolfSSH_CTX_SetPublicKeyCheck(ctx, wsPublicKeyCheck); + wolfSSH_SetPublicKeyCheckCtx(ssh, (void*)"You've been sampled!"); + + ret = wolfSSH_SetUsername(ssh, username); + if (ret != WS_SUCCESS) + err_sys("Couldn't set the username."); + + build_addr(&clientAddr, host, port); + tcp_socket(&sockFd); + ret = connect(sockFd, (const struct sockaddr *)&clientAddr, clientAddrSz); + if (ret != 0) + err_sys("Couldn't connect to server."); + + if (nonBlock) + 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 (ret != WS_SUCCESS) + err_sys("Couldn't set the channel type."); + + if (dir == copyFromSrv) + ret = wolfSSH_SCP_from(ssh, path1, path2); + else if (dir == copyToSrv) + ret = wolfSSH_SCP_to(ssh, path1, path2); + if (ret != WS_SUCCESS) + err_sys("Couldn't copy the file."); + + ret = wolfSSH_shutdown(ssh); + WCLOSESOCKET(sockFd); + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); + if (ret != WS_SUCCESS) + err_sys("Closing stream failed. Connection could have been closed by peer"); + +#if defined(HAVE_ECC) && defined(FP_ECC) && defined(HAVE_THREAD_LS) + wc_ecc_fp_free(); /* free per thread cache */ +#endif + + return 0; +} + + +#ifndef NO_MAIN_DRIVER + +int main(int argc, char* argv[]) +{ + func_args args; + + args.argc = argc; + args.argv = argv; + args.return_code = 0; + args.user_auth = NULL; + + #ifdef DEBUG_WOLFSSH + wolfSSH_Debugging_ON(); + #endif + + wolfSSH_Init(); + + scp_client(&args); + + wolfSSH_Cleanup(); + + return args.return_code; +} + + +int myoptind = 0; +char* myoptarg = NULL; + +#endif /* NO_MAIN_DRIVER */ diff --git a/examples/scpclient/scpclient.h b/examples/scpclient/scpclient.h new file mode 100644 index 0000000..9ced604 --- /dev/null +++ b/examples/scpclient/scpclient.h @@ -0,0 +1,29 @@ +/* scpclient.h + * + * Copyright (C) 2014-2020 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 . + */ + + +#pragma once + +#ifndef _WOLFSSH_SCPCLIENT_H_ +#define _WOLFSSH_SCPCLIENT_H_ + +THREAD_RETURN WOLFSSH_THREAD scp_client(void*); + +#endif /* _WOLFSSH_SCPCLIENT_H_ */ diff --git a/src/log.c b/src/log.c index b210989..164a2b3 100644 --- a/src/log.c +++ b/src/log.c @@ -122,6 +122,9 @@ static const char* GetLogStr(enum wolfSSH_LogLevel level) case WS_LOG_SFTP: return "SFTP"; + case WS_LOG_SCP: + return "SCP"; + default: return "UNKNOWN"; } diff --git a/src/wolfscp.c b/src/wolfscp.c index 757ae9b..d70ef52 100644 --- a/src/wolfscp.c +++ b/src/wolfscp.c @@ -1508,6 +1508,96 @@ void* wolfSSH_GetScpSendCtx(WOLFSSH* ssh) } +int wolfSSH_SCP_connect(WOLFSSH* ssh, byte* cmd) +{ + int ret = WS_SUCCESS; + + if (ssh == NULL) + return WS_BAD_ARGUMENT; + + if (ssh->error == WS_WANT_READ || ssh->error == WS_WANT_WRITE) + ssh->error = WS_SUCCESS; + + if (ssh->connectState < CONNECT_SERVER_CHANNEL_REQUEST_DONE) { + + WLOG(WS_LOG_SCP, "Trying to do SSH connect first"); + WLOG(WS_LOG_SCP, "cmd = %s", (const char*)cmd); + if ((ret = wolfSSH_SetChannelType(ssh, WOLFSSH_SESSION_EXEC, cmd, + (word32)WSTRLEN((const char*)cmd))) != WS_SUCCESS) { + WLOG(WS_LOG_SCP, "Unable to set subsystem channel type"); + return ret; + } + + if ((ret = wolfSSH_connect(ssh)) != WS_SUCCESS) { + return ret; + } + } + + return ret; +} + + +static int wolfSSH_SCP_cmd(WOLFSSH* ssh, const char* localName, + const char* remoteName, byte dir) +{ + char* cmd = NULL; + word32 remoteNameSz, cmdSz; + int ret = WS_SUCCESS; + + if (ssh == NULL || localName == NULL || remoteName == NULL) + return WS_BAD_ARGUMENT; + + if (dir != 't' && dir != 'f') + return WS_BAD_ARGUMENT; + + remoteNameSz = (word32)WSTRLEN(remoteName); + cmdSz = remoteNameSz + (word32)WSTRLEN("scp -5 ") + 1; + cmd = (char*)WMALLOC(cmdSz, ssh->ctx->heap, DYNTYPE_STRING); + + /* Need to set up the context for the local interaction callback. */ + + if (cmd != NULL) { + WSNPRINTF(cmd, cmdSz, "scp -%c %s", dir, remoteName); + ssh->scpBasePath = localName; + ret = wolfSSH_SCP_connect(ssh, (byte*)cmd); + if (dir == 't') { + ssh->scpState = SCP_SOURCE_BEGIN; + ssh->scpRequestState = SCP_SOURCE; + ret = DoScpSource(ssh); + } + else { + ssh->scpState = SCP_SINK_BEGIN; + ssh->scpRequestState = SCP_SINK; + ret = DoScpSink(ssh); + } + WFREE(cmd, ssh->ctx->heap, DYNTYPE_STRING); + } + else { + WLOG(WS_LOG_SCP, "Cannot build scp command"); + ssh->error = WS_MEMORY_E; + ret = WS_FATAL_ERROR; + } + + return ret; +} + + +int wolfSSH_SCP_to(WOLFSSH* ssh, const char* src, const char* dst) +{ + return wolfSSH_SCP_cmd(ssh, src, dst, 't'); + /* dst is passed to the server in the scp -t command */ + /* src is used locally to fopen and read for copy to */ +} + + +int wolfSSH_SCP_from(WOLFSSH* ssh, const char* src, const char* dst) +{ + return wolfSSH_SCP_cmd(ssh, dst, src, 'f'); + /* src is passed to the server in the scp -f command */ + /* dst is used locally to fopen and write for copy from */ +} + + #if !defined(WOLFSSH_SCP_USER_CALLBACKS) && !defined(NO_FILESYSTEM) /* for porting to systems without errno */ diff --git a/wolfssh/log.h b/wolfssh/log.h index 4a6ed7a..1d2c188 100644 --- a/wolfssh/log.h +++ b/wolfssh/log.h @@ -42,6 +42,7 @@ extern "C" { enum wolfSSH_LogLevel { + WS_LOG_SCP = 7, WS_LOG_SFTP = 6, WS_LOG_USER = 5, WS_LOG_ERROR = 4, diff --git a/wolfssh/wolfscp.h b/wolfssh/wolfscp.h index d72ad1b..b67e613 100644 --- a/wolfssh/wolfscp.h +++ b/wolfssh/wolfscp.h @@ -110,6 +110,10 @@ WOLFSSH_API void* wolfSSH_GetScpSendCtx(WOLFSSH*); WOLFSSH_API int wolfSSH_SetScpErrorMsg(WOLFSSH*, const char*); +WOLFSSH_API int wolfSSH_SCP_connect(WOLFSSH*, byte*); +WOLFSSH_API int wolfSSH_SCP_to(WOLFSSH*, const char*, const char*); +WOLFSSH_API int wolfSSH_SCP_from(WOLFSSH*, const char*, const char*); + #ifdef __cplusplus }