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
}