/* sftp.c * * Copyright (C) 2014-2026 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 #include #ifdef WOLFSSL_USER_SETTINGS #include #else #include #endif #include #if defined(WOLFSSH_SFTP) && !defined(SINGLE_THREADED) #include #include #define WOLFSSH_TEST_LOCKING #define WOLFSSH_TEST_THREADING #include #include "tests/sftp.h" #include "examples/echoserver/echoserver.h" #include "examples/sftpclient/sftpclient.h" /* * Each test command is paired with an optional check function that * validates the output it produces. This eliminates the fragile * index-based coupling between the command array and the validator. */ typedef int (*SftpTestCheck)(void); typedef struct { const char* cmd; SftpTestCheck check; /* validates output from THIS cmd, or NULL */ } SftpTestCmd; /* Test buffer */ static char inBuf[1024] = {0}; /* check that pwd output ends in /a */ static int checkPwdInA(void) { int i; int len; for (i = 0; i < (int)sizeof(inBuf); i++) { if (inBuf[i] == '\n') { inBuf[i] = '\0'; break; } } len = (int)WSTRLEN(inBuf); if (len < 2) { printf("pwd output too short: %s\n", inBuf); return -1; } if (inBuf[len - 2] != '/') { printf("unexpected pwd of %s, looking for '/'\n", inBuf); return -1; } if (inBuf[len - 1] != 'a') { printf("unexpected pwd of %s, looking for 'a'\n", inBuf); return -1; } return 0; } /* check that ls of empty dir shows only . and .. */ static int checkLsEmpty(void) { #ifdef WOLFSSH_ZEPHYR /* No . and .. in zephyr fs API */ char expt1[] = "wolfSSH sftp> "; char expt2[] = "wolfSSH sftp> "; #else char expt1[] = ".\n..\nwolfSSH sftp> "; char expt2[] = "..\n.\nwolfSSH sftp> "; #endif if (WMEMCMP(expt1, inBuf, sizeof(expt1)) != 0 && WMEMCMP(expt2, inBuf, sizeof(expt2)) != 0) { printf("unexpected ls\n"); printf("\texpected \n%s\n\tor\n%s\n\tbut got\n%s\n", expt1, expt2, inBuf); return -1; } return 0; } /* check that ls output contains a specific file */ static int checkLsHasConfigureAc(void) { if (WSTRNSTR(inBuf, "configure.ac", sizeof(inBuf)) == NULL) { fprintf(stderr, "configure.ac not found in %s\n", inBuf); return 1; } return 0; } static int checkLsHasTestGet(void) { return (WSTRNSTR(inBuf, "test-get", sizeof(inBuf)) == NULL) ? 1 : 0; } static int checkLsHasTestGet2(void) { return (WSTRNSTR(inBuf, "test-get-2", sizeof(inBuf)) == NULL) ? 1 : 0; } static int checkLsSize(void) { return (WSTRNSTR(inBuf, "size in bytes", sizeof(inBuf)) == NULL) ? 1 : 0; } static int checkCdNonexistent(void) { if (WSTRNSTR(inBuf, "Error changing directory", sizeof(inBuf)) == NULL) { fprintf(stderr, "cd: expected error not found in %s\n", inBuf); return 1; } return 0; } #if !defined(NO_WOLFSSH_DIR) && !defined(WOLFSSH_FATFS) static int checkLlsHasConfigureAc(void) { if (WSTRNSTR(inBuf, "configure.ac", sizeof(inBuf)) == NULL) { fprintf(stderr, "lls: configure.ac not found in %s\n", inBuf); return 1; } return 0; } #endif /* !NO_WOLFSSH_DIR && !WOLFSSH_FATFS */ static int checkLcdNonexistent(void) { if (WSTRNSTR(inBuf, "Error changing local directory", sizeof(inBuf)) == NULL) { fprintf(stderr, "lcd: expected error not found in %s\n", inBuf); return 1; } return 0; } #if !defined(NO_WOLFSSH_DIR) && !defined(WOLFSSH_FATFS) \ && !defined(WOLFSSH_ZEPHYR) /* after lcd into keys/, lls should show key files */ static int checkLlsInKeys(void) { if (WSTRNSTR(inBuf, ".pem", sizeof(inBuf)) == NULL && WSTRNSTR(inBuf, ".der", sizeof(inBuf)) == NULL) { fprintf(stderr, "lls: expected key files not found in %s\n", inBuf); return 1; } return 0; } /* after lcd back to .., lls should show project root files */ static int checkLlsBackToRoot(void) { if (WSTRNSTR(inBuf, "configure.ac", sizeof(inBuf)) == NULL) { fprintf(stderr, "lls: configure.ac not found after lcd ..\n"); return 1; } return 0; } #endif /* !NO_WOLFSSH_DIR && !WOLFSSH_FATFS && !WOLFSSH_ZEPHYR */ static const SftpTestCmd cmds[] = { /* If a prior run was interrupted, files and directories * created during the test may still exist in the working * directory, causing mkdir to fail and ls checks to see * unexpected entries. Remove them here before starting. * These run as SFTP commands rather than local syscalls * so they are portable across all platforms (Windows, * Zephyr, POSIX). Failures are silently ignored since * the files may not exist. */ { "rm a/configure.ac", NULL }, { "rmdir a", NULL }, { "rm test-get", NULL }, { "rm test-get-2", NULL }, /* --- test sequence starts here --- */ { "mkdir a", NULL }, { "cd a", NULL }, { "pwd", checkPwdInA }, { "ls", checkLsEmpty }, #ifdef WOLFSSH_ZEPHYR { "put " CONFIG_WOLFSSH_SFTP_DEFAULT_DIR "/configure.ac", NULL }, #else { "put configure.ac", NULL }, #endif { "ls", checkLsHasConfigureAc }, #ifdef WOLFSSH_ZEPHYR { "get configure.ac " CONFIG_WOLFSSH_SFTP_DEFAULT_DIR "/test-get", NULL }, #else { "get configure.ac test-get", NULL }, #endif { "rm configure.ac", NULL }, { "cd ../", NULL }, { "ls", checkLsHasTestGet }, { "rename test-get test-get-2", NULL }, { "rmdir a", NULL }, { "ls", checkLsHasTestGet2 }, { "chmod 600 test-get-2", NULL }, { "rm test-get-2", NULL }, { "ls -s", checkLsSize }, { "cd /nonexistent_path_xyz", checkCdNonexistent }, #if !defined(NO_WOLFSSH_DIR) && !defined(WOLFSSH_FATFS) { "lls", checkLlsHasConfigureAc }, #endif { "lcd /nonexistent_path_xyz", checkLcdNonexistent }, #if !defined(NO_WOLFSSH_DIR) && !defined(WOLFSSH_FATFS) \ && !defined(WOLFSSH_ZEPHYR) /* lcd into a subdirectory, verify with lls, then return. * Skipped on Zephyr: lls always lists the default dir * (no WGETCWD) and the keys/ tree is not on the RAM fs. */ { "lcd keys", NULL }, { "lls", checkLlsInKeys }, { "lcd ..", NULL }, { "lls", checkLlsBackToRoot }, #endif /* empty arg tests: must not underflow on pt[sz-1] */ { "mkdir", NULL }, { "cd", NULL }, { "ls", NULL }, { "chmod", NULL }, { "rmdir", NULL }, { "rm", NULL }, { "rename", NULL }, { "get", NULL }, { "put", NULL }, { "exit", NULL }, }; static int commandIdx = 0; static int commandCb(const char* in, char* out, int outSz) { int ret = 0; if (in) { /* print out */ WSTRNCAT(inBuf, in, sizeof(inBuf)); } /* get command input */ if (out) { int sz = (int)WSTRLEN(cmds[commandIdx].cmd); if (outSz < sz) { ret = -1; } else { WMEMCPY(out, cmds[commandIdx].cmd, sz); } /* validate output from the previous command */ if (commandIdx > 0 && cmds[commandIdx - 1].check != NULL) { if (cmds[commandIdx - 1].check() != 0) { fprintf(stderr, "Check failed for \"%s\" (index %d)\n", cmds[commandIdx - 1].cmd, commandIdx - 1); exit(1); } } WMEMSET(inBuf, 0, sizeof(inBuf)); commandIdx++; } return ret; } /* test SFTP commands, if flag is set to 1 then use non blocking * return 0 on success */ int wolfSSH_SftpTest(int flag) { func_args ser; func_args cli; tcp_ready ready; int ret = 0; int argsCount; const char* args[10]; #ifndef USE_WINDOWS_API char portNumber[8]; #endif THREAD_TYPE serThread; wolfSSH_Init(); WMEMSET(&ser, 0, sizeof(func_args)); WMEMSET(&cli, 0, sizeof(func_args)); commandIdx = 0; wolfSSH_Debugging_ON(); argsCount = 0; args[argsCount++] = "."; args[argsCount++] = "-1"; #ifndef USE_WINDOWS_API args[argsCount++] = "-p"; args[argsCount++] = "0"; #endif if (flag) args[argsCount++] = "-N"; ser.argv = (char**)args; ser.argc = argsCount; ser.signal = &ready; InitTcpReady(ser.signal); ThreadStart(echoserver_test, (void*)&ser, &serThread); WaitTcpReady(&ready); argsCount = 0; args[argsCount++] = "."; args[argsCount++] = "-u"; args[argsCount++] = "jill"; args[argsCount++] = "-P"; args[argsCount++] = "upthehill"; #ifndef USE_WINDOWS_API /* use port that server has found */ args[argsCount++] = "-p"; snprintf(portNumber, sizeof(portNumber), "%d", ready.port); args[argsCount++] = portNumber; #endif if (flag) args[argsCount++] = "-N"; cli.argv = (char**)args; cli.argc = argsCount; cli.signal = &ready; cli.sftp_cb = commandCb; sftpclient_test(&cli); #ifdef WOLFSSH_ZEPHYR /* Weird deadlock without this sleep */ k_sleep(Z_TIMEOUT_TICKS(100)); #endif ThreadJoin(serThread); wolfSSH_Cleanup(); FreeTcpReady(&ready); return ret; } #endif /* WOLFSSH_SFTP */