@@ -16021,6 +16021,167 @@ static void TestSftpAppendWritesAtEof(void)
1602116021 wolfSSH_free(ssh);
1602216022 wolfSSH_CTX_free(ctx);
1602316023}
16024+
16025+ /* FXP_RENAME path to newPath, or FXP_REMOVE path when newPath is NULL. */
16026+ static int SftpPathRequest(WOLFSSH* ssh, int reqId, const char* path,
16027+ const char* newPath)
16028+ {
16029+ byte pkt[2 * (WOLFSSH_MAX_FILENAME + UINT32_SZ)];
16030+ word32 idx = 0;
16031+ word32 sz = (word32)WSTRLEN(path);
16032+
16033+ SftpPutU32(sz, pkt + idx); idx += UINT32_SZ;
16034+ WMEMCPY(pkt + idx, path, sz); idx += sz;
16035+ if (newPath == NULL) {
16036+ return wolfSSH_SFTP_RecvRemove(ssh, reqId, pkt, idx);
16037+ }
16038+ sz = (word32)WSTRLEN(newPath);
16039+ SftpPutU32(sz, pkt + idx); idx += UINT32_SZ;
16040+ WMEMCPY(pkt + idx, newPath, sz); idx += sz;
16041+ return wolfSSH_SFTP_RecvRename(ssh, reqId, pkt, idx);
16042+ }
16043+
16044+ static void SftpWriteTestFile(const char* path, const char* content)
16045+ {
16046+ WFILE* file;
16047+
16048+ AssertIntEQ(WFOPEN(NULL, &file, path, "wb"), 0);
16049+ AssertTrue(file != WBADFILE);
16050+ AssertIntEQ((int)WFWRITE(NULL, content, 1, WSTRLEN(content), file),
16051+ (int)WSTRLEN(content));
16052+ WFCLOSE(NULL, file);
16053+ }
16054+
16055+ /* REMOVE refuses a directory, RENAME an existing newpath
16056+ * (draft-ietf-secsh-filexfer-02 6.5). */
16057+ static void TestSftpRemoveRenameSemantics(void)
16058+ {
16059+ WOLFSSH_CTX* ctx;
16060+ WOLFSSH* ssh;
16061+ int rid = 700;
16062+ WSTAT_T st;
16063+ char cwd[WOLFSSH_MAX_FILENAME];
16064+ char fileA[64], fileB[64], fileC[64];
16065+ char dirA[64], dirB[64], dirC[64];
16066+ #if defined(WOLFSSH_HAVE_SYMLINK) && !defined(USE_WINDOWS_API)
16067+ char lnk[64];
16068+ #endif
16069+ #ifndef USE_WINDOWS_API
16070+ char roDir[64], roFile[128], roDest[128];
16071+ #endif
16072+
16073+ ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
16074+ AssertNotNull(ctx);
16075+ ssh = wolfSSH_new(ctx);
16076+ AssertNotNull(ssh);
16077+ AssertIntEQ(wolfSSH_SFTP_TestRecvStateInit(ssh), WS_SUCCESS);
16078+
16079+ WSNPRINTF(fileA, sizeof(fileA), "wolfssh_rm_a_%lu.tmp", SFTP_TEST_PID());
16080+ WSNPRINTF(fileB, sizeof(fileB), "wolfssh_rm_b_%lu.tmp", SFTP_TEST_PID());
16081+ WSNPRINTF(fileC, sizeof(fileC), "wolfssh_rm_c_%lu.tmp", SFTP_TEST_PID());
16082+ WSNPRINTF(dirA, sizeof(dirA), "wolfssh_rm_da_%lu", SFTP_TEST_PID());
16083+ WSNPRINTF(dirB, sizeof(dirB), "wolfssh_rm_db_%lu", SFTP_TEST_PID());
16084+ WSNPRINTF(dirC, sizeof(dirC), "wolfssh_rm_dc_%lu", SFTP_TEST_PID());
16085+ WMEMSET(cwd, 0, sizeof(cwd));
16086+ AssertNotNull(WGETCWD(ssh->fs, cwd, sizeof(cwd) - 1));
16087+ AssertIntEQ(wolfSSH_SFTP_SetDefaultPath(ssh, cwd), WS_SUCCESS);
16088+
16089+ SftpWriteTestFile(fileA, "aaaa");
16090+ SftpWriteTestFile(fileB, "bb");
16091+ (void)WREMOVE(ssh->fs, fileC);
16092+ AssertIntEQ(WMKDIR(ssh->fs, dirA, 0755), 0);
16093+ AssertIntEQ(WMKDIR(ssh->fs, dirB, 0755), 0);
16094+ (void)WRMDIR(ssh->fs, dirC);
16095+
16096+ /* REMOVE refuses an empty directory */
16097+ AssertIntEQ(SftpPathRequest(ssh, rid, dirA, NULL), WS_BAD_FILE_E);
16098+ AssertSftpStatusReply(ssh, rid++, WOLFSSH_FTP_FAILURE);
16099+ AssertIntEQ(WSTAT(ssh->fs, dirA, &st), 0);
16100+
16101+ /* RENAME refuses an existing file; neither changes */
16102+ AssertIntEQ(SftpPathRequest(ssh, rid, fileA, fileB), WS_BAD_FILE_E);
16103+ AssertSftpStatusReply(ssh, rid++, WOLFSSH_FTP_FAILURE);
16104+ AssertSftpFileContents(fileA, "aaaa", 4);
16105+ AssertSftpFileContents(fileB, "bb", 2);
16106+
16107+ /* RENAME refuses an existing empty directory */
16108+ AssertIntEQ(SftpPathRequest(ssh, rid, dirA, dirB), WS_BAD_FILE_E);
16109+ AssertSftpStatusReply(ssh, rid++, WOLFSSH_FTP_FAILURE);
16110+ AssertIntEQ(WSTAT(ssh->fs, dirA, &st), 0);
16111+ AssertIntEQ(WSTAT(ssh->fs, dirB, &st), 0);
16112+
16113+ #if defined(WOLFSSH_HAVE_SYMLINK) && !defined(USE_WINDOWS_API)
16114+ /* a dangling symlink is an existing newpath too */
16115+ WSNPRINTF(lnk, sizeof(lnk), "wolfssh_rm_ln_%lu", SFTP_TEST_PID());
16116+ (void)WREMOVE(ssh->fs, lnk);
16117+ AssertIntEQ(symlink(fileC, lnk), 0);
16118+ AssertIntEQ(SftpPathRequest(ssh, rid, fileA, lnk), WS_BAD_FILE_E);
16119+ AssertSftpStatusReply(ssh, rid++, WOLFSSH_FTP_FAILURE);
16120+ AssertIntEQ(WLSTAT(ssh->fs, lnk, &st), 0);
16121+ AssertTrue(S_ISLNK(st.st_mode));
16122+ AssertSftpFileContents(fileA, "aaaa", 4);
16123+ /* REMOVE takes the link, not its target */
16124+ AssertIntEQ(SftpPathRequest(ssh, rid, lnk, NULL), WS_SUCCESS);
16125+ AssertSftpStatusReply(ssh, rid++, WOLFSSH_FTP_OK);
16126+ AssertTrue(WLSTAT(ssh->fs, lnk, &st) != 0);
16127+ /* a link to a directory is not a directory to REMOVE */
16128+ AssertIntEQ(symlink(dirB, lnk), 0);
16129+ AssertIntEQ(SftpPathRequest(ssh, rid, lnk, NULL), WS_SUCCESS);
16130+ AssertSftpStatusReply(ssh, rid++, WOLFSSH_FTP_OK);
16131+ AssertTrue(WLSTAT(ssh->fs, lnk, &st) != 0);
16132+ AssertIntEQ(WSTAT(ssh->fs, dirB, &st), 0);
16133+ #endif
16134+
16135+ #ifndef USE_WINDOWS_API
16136+ /* a failed move leaves both names as they were: out of a read-only
16137+ * directory takes the link rollback, into one the link fallback */
16138+ if (geteuid() != 0) {
16139+ WSNPRINTF(roDir, sizeof(roDir), "wolfssh_rm_ro_%lu",
16140+ SFTP_TEST_PID());
16141+ WSNPRINTF(roFile, sizeof(roFile), "%s/%s", roDir, fileC);
16142+ WSNPRINTF(roDest, sizeof(roDest), "%s/%s", roDir, fileB);
16143+ AssertIntEQ(WMKDIR(ssh->fs, roDir, 0755), 0);
16144+ SftpWriteTestFile(roFile, "cc");
16145+ AssertIntEQ(chmod(roDir, 0555), 0);
16146+
16147+ AssertIntEQ(SftpPathRequest(ssh, rid, roFile, fileC), WS_BAD_FILE_E);
16148+ AssertSftpStatusReply(ssh, rid++, WOLFSSH_FTP_FAILURE);
16149+ AssertSftpFileContents(roFile, "cc", 2);
16150+ AssertTrue(WLSTAT(ssh->fs, fileC, &st) != 0);
16151+
16152+ AssertIntEQ(SftpPathRequest(ssh, rid, fileB, roDest), WS_BAD_FILE_E);
16153+ AssertSftpStatusReply(ssh, rid++, WOLFSSH_FTP_FAILURE);
16154+ AssertSftpFileContents(fileB, "bb", 2);
16155+ AssertTrue(WLSTAT(ssh->fs, roDest, &st) != 0);
16156+
16157+ AssertIntEQ(chmod(roDir, 0755), 0);
16158+ AssertIntEQ(WREMOVE(ssh->fs, roFile), 0);
16159+ AssertIntEQ(WRMDIR(ssh->fs, roDir), 0);
16160+ }
16161+ #endif
16162+
16163+ /* RENAME to a free name moves a file and a directory */
16164+ AssertIntEQ(SftpPathRequest(ssh, rid, fileA, fileC), WS_SUCCESS);
16165+ AssertSftpStatusReply(ssh, rid++, WOLFSSH_FTP_OK);
16166+ AssertTrue(WSTAT(ssh->fs, fileA, &st) != 0);
16167+ AssertSftpFileContents(fileC, "aaaa", 4);
16168+ AssertIntEQ(SftpPathRequest(ssh, rid, dirA, dirC), WS_SUCCESS);
16169+ AssertSftpStatusReply(ssh, rid++, WOLFSSH_FTP_OK);
16170+ AssertTrue(WSTAT(ssh->fs, dirA, &st) != 0);
16171+ AssertIntEQ(WSTAT(ssh->fs, dirC, &st), 0);
16172+
16173+ /* REMOVE still removes a file */
16174+ AssertIntEQ(SftpPathRequest(ssh, rid, fileC, NULL), WS_SUCCESS);
16175+ AssertSftpStatusReply(ssh, rid++, WOLFSSH_FTP_OK);
16176+ AssertTrue(WSTAT(ssh->fs, fileC, &st) != 0);
16177+
16178+ (void)WREMOVE(ssh->fs, fileB);
16179+ (void)WRMDIR(ssh->fs, dirB);
16180+ (void)WRMDIR(ssh->fs, dirC);
16181+ wolfSSH_SFTP_TestRecvStateFree(ssh);
16182+ wolfSSH_free(ssh);
16183+ wolfSSH_CTX_free(ctx);
16184+ }
1602416185#endif /* !NO_WOLFSSH_SERVER && !NO_FILESYSTEM */
1602516186
1602616187#if !defined(NO_WOLFSSH_SERVER) && !defined(USE_WINDOWS_API) && \
@@ -20771,6 +20932,8 @@ int main(int argc, char** argv)
2077120932 #if !defined(NO_WOLFSSH_SERVER) && !defined(NO_FILESYSTEM)
2077220933 /* FXF_APPEND writes land at EOF regardless of the request offset */
2077320934 TestSftpAppendWritesAtEof();
20935+ /* REMOVE refuses directories, RENAME refuses an existing newpath */
20936+ TestSftpRemoveRenameSemantics();
2077420937 #endif
2077520938 #if !defined(NO_WOLFSSH_SERVER) && defined(USE_WINDOWS_API) && \
2077620939 !defined(NO_FILESYSTEM)
0 commit comments