diff --git a/apps/wolfsshd/wolfsshd.c b/apps/wolfsshd/wolfsshd.c index d86ac426f..4bb20e0a0 100644 --- a/apps/wolfsshd/wolfsshd.c +++ b/apps/wolfsshd/wolfsshd.c @@ -2881,11 +2881,12 @@ static int StartSSHD(int argc, char** argv) if (cmdArgs == NULL) { ret = WS_FATAL_ERROR; } - argc = cmdArgC; if (ret == WS_SUCCESS) { - for (i = 0; i < argc; i++) { - if (WSTRCMP((char*)(cmdArgs[i]), "-D") == 0) { + for (i = 0; i < (DWORD)cmdArgC; i++) { + /* cmdArgs entries are wide strings (CommandLineToArgvW); compare + * as such instead of reinterpreting as narrow char data. */ + if (wcscmp(cmdArgs[i], L"-D") == 0) { isDaemon = 0; } } @@ -2894,24 +2895,27 @@ static int StartSSHD(int argc, char** argv) if (isDaemon) { /* Set the logging to go to OutputDebugString */ wolfSSH_SetLoggingCb(ServiceDebugCb); + } - if (ret == WS_SUCCESS) { - /* we want the arguments to be normal char strings not wchar_t */ - argv = (char**)WMALLOC(argc * sizeof(char*), NULL, DYNTYPE_SSHD); - if (argv == NULL) { - ret = WS_MEMORY_E; - } - else { - unsigned int z; - for (z = 0; z < argc; z++) { - argv[z] = _convertHelper(cmdArgs[z], NULL); - } + if (ret == WS_SUCCESS) { + /* Rebuild argv from cmdArgs, not the caller's wargv: wargv is + * narrow data when called from main(), so it may not be a real + * wide string. cmdArgs is always correct either way. */ + argc = (DWORD)cmdArgC; + + /* we want the arguments to be normal char strings not wchar_t */ + argv = (char**)WMALLOC(argc * sizeof(char*), NULL, DYNTYPE_SSHD); + if (argv == NULL) { + ret = WS_MEMORY_E; + } + else { + unsigned int z; + for (z = 0; z < argc; z++) { + argv[z] = _convertHelper(cmdArgs[z], NULL); } } } - else { - argv = (char**)wargv; - } + (void)wargv; #endif signal(SIGINT, interruptCatch); @@ -2928,7 +2932,8 @@ static int StartSSHD(int argc, char** argv) } } - while ((ch = mygetopt(argc, argv, "?f:p:h:dDE:o:t")) != -1) { + while (ret == WS_SUCCESS && + (ch = mygetopt(argc, argv, "?f:p:h:dDE:o:t")) != -1) { switch (ch) { case 'f': configFile = myoptarg; diff --git a/src/wolfsftp.c b/src/wolfsftp.c index 6f125184f..c9199a002 100644 --- a/src/wolfsftp.c +++ b/src/wolfsftp.c @@ -416,7 +416,7 @@ static int SFTP_AddFileHandle(WOLFSSH* ssh, #else WFD fd, #endif - const char* fileName, word32 id[2]); + const char* fileName, word32 id[2], int isAppend); static int SFTP_RemoveFileHandle(WOLFSSH* ssh, word32 id[2]); static int SFTP_FileHandleCapped(WOLFSSH* ssh); #endif /* !NO_WOLFSSH_SERVER */ @@ -2327,6 +2327,35 @@ static void SFTP_HandleIdNext(WOLFSSH* ssh, word32 id[2]) #endif /* !NO_WOLFSSH_SERVER */ +#ifdef USE_WINDOWS_API +/* dwCreationDisposition takes one enumerated value, not a bitmask, so + * resolve CREAT/EXCL/TRUNC to a single disposition here. */ +static DWORD SFTP_WinCreationDisp(word32 reason) +{ + DWORD disp; + + if (reason & WOLFSSH_FXF_CREAT) { + if (reason & WOLFSSH_FXF_EXCL) + disp = CREATE_NEW; + else if (reason & WOLFSSH_FXF_TRUNC) + disp = CREATE_ALWAYS; + else + disp = OPEN_ALWAYS; + } + else { + /* TRUNCATE_EXISTING requires GENERIC_WRITE in dwDesiredAccess or + * CreateFile() fails with ERROR_INVALID_PARAMETER; without WRITE + * there is no way to truncate, so fall back to OPEN_EXISTING. */ + if ((reason & WOLFSSH_FXF_TRUNC) && (reason & WOLFSSH_FXF_WRITE)) + disp = TRUNCATE_EXISTING; + else + disp = OPEN_EXISTING; + } + + return disp; +} +#endif /* USE_WINDOWS_API */ + /* Handles packet to open a file * * returns WS_SUCCESS on success @@ -2505,7 +2534,8 @@ int wolfSSH_SFTP_RecvOpen(WOLFSSH* ssh, int reqId, byte* data, word32 maxSz) /* Generate unique file handle ID and add to tracking list */ SFTP_HandleIdNext(ssh, id); - if ((ret = SFTP_AddFileHandle(ssh, fd, dir, id)) != WS_SUCCESS) { + if ((ret = SFTP_AddFileHandle(ssh, fd, dir, id, + (reason & WOLFSSH_FXF_APPEND) ? 1 : 0)) != WS_SUCCESS) { WLOG(WS_LOG_SFTP, "Unable to store handle"); res = ier; if (wolfSSH_SFTP_CreateStatus(ssh, WOLFSSH_FTP_FAILURE, reqId, res, @@ -2650,25 +2680,14 @@ int wolfSSH_SFTP_RecvOpen(WOLFSSH* ssh, int reqId, byte* data, word32 maxSz) } #endif - if (reason & WOLFSSH_FXF_READ) { + if (reason & WOLFSSH_FXF_READ) desiredAccess |= GENERIC_READ; - creationDisp |= OPEN_EXISTING; - } - if (reason & WOLFSSH_FXF_WRITE) { + if (reason & WOLFSSH_FXF_WRITE) desiredAccess |= GENERIC_WRITE; - if (reason & WOLFSSH_FXF_CREAT) { - if (reason & WOLFSSH_FXF_TRUNC) - creationDisp = CREATE_ALWAYS; - else - creationDisp = OPEN_ALWAYS; - } - #if 0 - if (reason & WOLFSSH_FXF_EXCL) - creationDisp |= CREATE_NEW; - if (reason & WOLFSSH_FXF_APPEND) - desiredAccess |= FILE_APPEND_DATA; - #endif - } + if (reason & WOLFSSH_FXF_APPEND) + desiredAccess |= FILE_APPEND_DATA; + + creationDisp = SFTP_WinCreationDisp(reason); #if 0 /* if file permissions not set then use default */ @@ -2698,7 +2717,8 @@ int wolfSSH_SFTP_RecvOpen(WOLFSSH* ssh, int reqId, byte* data, word32 maxSz) /* Generate unique file handle ID and add to tracking list */ SFTP_HandleIdNext(ssh, id); - if (SFTP_AddFileHandle(ssh, fileHandle, dir, id) != WS_SUCCESS) { + if (SFTP_AddFileHandle(ssh, fileHandle, dir, id, + (reason & WOLFSSH_FXF_APPEND) ? 1 : 0) != WS_SUCCESS) { WLOG(WS_LOG_SFTP, "Unable to store handle"); res = ier; if (wolfSSH_SFTP_CreateStatus(ssh, WOLFSSH_FTP_FAILURE, reqId, res, @@ -2767,6 +2787,7 @@ struct WS_FILE_LIST { char* fileName; /* cleaned full path of the open file */ word32 id[2]; /* handle ID */ struct WS_FILE_LIST* next; + byte isAppend:1; /* WOLFSSH_FXF_APPEND was requested at open */ }; #ifndef NO_WOLFSSH_DIR @@ -4089,7 +4110,7 @@ static int SFTP_AddFileHandle(WOLFSSH* ssh, #else WFD fd, #endif - const char* fileName, word32 id[2]) + const char* fileName, word32 id[2], int isAppend) { WS_FILE_LIST* cur = NULL; char* fileNameCopy = NULL; @@ -4130,6 +4151,7 @@ static int SFTP_AddFileHandle(WOLFSSH* ssh, cur->fileName = fileNameCopy; cur->id[0] = id[0]; cur->id[1] = id[1]; + cur->isAppend = (isAppend != 0) ? 1 : 0; cur->next = ssh->fileList; ssh->fileList = cur; @@ -4348,8 +4370,10 @@ int wolfSSH_SFTP_RecvWrite(WOLFSSH* ssh, int reqId, byte* data, word32 maxSz) OVERLAPPED offset; HANDLE fd; DWORD bytesWritten; + LARGE_INTEGER fileSize; int ret = WS_SUCCESS; int rc; + int isAppend = 0; word32 idx = 0; const byte* str; @@ -4397,6 +4421,7 @@ int wolfSSH_SFTP_RecvWrite(WOLFSSH* ssh, int reqId, byte* data, word32 maxSz) } else { fd = fileEntry->fd; + isAppend = fileEntry->isAppend; } } } @@ -4413,6 +4438,25 @@ int wolfSSH_SFTP_RecvWrite(WOLFSSH* ssh, int reqId, byte* data, word32 maxSz) } offset.Offset = (DWORD)strSz; + /* WOLFSSH_FXF_APPEND was requested at open: FILE_APPEND_DATA alone + * does not force writes to EOF once the handle also carries + * FILE_WRITE_DATA (granted implicitly by GENERIC_WRITE), so the + * client-supplied offset must be overridden with the current EOF. */ + if (isAppend) { + if (GetFileSizeEx(fd, &fileSize) == 0) { + WLOG(WS_LOG_SFTP, "Error getting file size for append"); + res = err; + type = WOLFSSH_FTP_FAILURE; + ret = WS_INVALID_STATE_E; + } + else { + offset.Offset = fileSize.LowPart; + offset.OffsetHigh = (DWORD)fileSize.HighPart; + } + } + } + + if (ret == WS_SUCCESS) { /* get length to be written */ if (GetStringRef(&strSz, &str, data, maxSz, &idx) != WS_SUCCESS) { return WS_BUFFER_E; @@ -6331,8 +6375,7 @@ int wolfSSH_SFTP_RecvFSetSTAT(WOLFSSH* ssh, int reqId, byte* data, word32 maxSz) #endif /* _WIN32_WCE */ -#if defined(WOLFSSH_TEST_INTERNAL) && !defined(USE_WINDOWS_API) && \ - !defined(NO_FILESYSTEM) +#if defined(WOLFSSH_TEST_INTERNAL) && !defined(NO_FILESYSTEM) /* Test-only plumbing for the forged-handle regression test in tests/regress.c. * * The SFTP request handlers buffer their status/handle reply into ssh->recvState @@ -6415,10 +6458,12 @@ int wolfSSH_SFTP_TestDirHandleCount(WOLFSSH* ssh) } #endif /* NO_WOLFSSH_DIR */ +#ifndef USE_WINDOWS_API /* Close the underlying descriptor of the head tracked file handle out of band, * leaving the node in the list with a now-stale fd. The next RecvClose on that * handle will see its close() fail, exercising the path that must still drop - * the handle from the tracking list. Returns WS_SUCCESS if a node was found. */ + * the handle from the tracking list. Returns WS_SUCCESS if a node was found. + * Not provided for Windows, where fd is a HANDLE, not a WCLOSE-able fd. */ int wolfSSH_SFTP_TestInvalidateHeadFd(WOLFSSH* ssh) { if (ssh == NULL || ssh->fileList == NULL) { @@ -6431,7 +6476,8 @@ int wolfSSH_SFTP_TestInvalidateHeadFd(WOLFSSH* ssh) #endif return WS_SUCCESS; } -#endif /* WOLFSSH_TEST_INTERNAL && !USE_WINDOWS_API && !NO_FILESYSTEM */ +#endif /* !USE_WINDOWS_API */ +#endif /* WOLFSSH_TEST_INTERNAL && !NO_FILESYSTEM */ #endif /* !NO_WOLFSSH_SERVER */ diff --git a/tests/regress.c b/tests/regress.c index 76ff4618a..214e6fd82 100644 --- a/tests/regress.c +++ b/tests/regress.c @@ -4566,8 +4566,7 @@ static void TestSftpBufferSendPendingOutput(void) wolfSSH_CTX_free(ctx); } -#if !defined(NO_WOLFSSH_SERVER) && !defined(USE_WINDOWS_API) && \ - !defined(NO_FILESYSTEM) +#if !defined(NO_WOLFSSH_SERVER) && !defined(NO_FILESYSTEM) /* Write a big-endian uint32 (the SFTP wire encoding). */ static void SftpPutU32(word32 val, byte* out) { @@ -4584,6 +4583,28 @@ static word32 SftpGetU32(const byte* in) ((word32)in[2] << 8) | (word32)in[3]; } +/* A refused request must still answer the peer with an FXP_STATUS carrying the + * expected code. Asserting only that the call returned non-success would not + * catch a refusal that dropped the reply and left the session hung. + * The request id is checked too: TestRecvReply returns whatever is currently + * buffered, so a handler that dropped its reply would otherwise pass here by + * re-presenting the previous request's status. */ +static void AssertSftpStatusReply(WOLFSSH* ssh, int reqId, word32 code) +{ + const byte* reply; + word32 replySz; + + reply = wolfSSH_SFTP_TestRecvReply(ssh, &replySz); + AssertNotNull(reply); + AssertTrue(replySz >= WOLFSSH_SFTP_HEADER + UINT32_SZ); + AssertIntEQ(reply[LENGTH_SZ], WOLFSSH_FTP_STATUS); + AssertIntEQ((int)SftpGetU32(reply + LENGTH_SZ + MSG_ID_SZ), reqId); + AssertIntEQ((int)SftpGetU32(reply + WOLFSSH_SFTP_HEADER), (int)code); +} +#endif /* !NO_WOLFSSH_SERVER && !NO_FILESYSTEM */ + +#if !defined(NO_WOLFSSH_SERVER) && !defined(USE_WINDOWS_API) && \ + !defined(NO_FILESYSTEM) /* Return 1 if needle occurs in haystack, 0 otherwise. */ static int SftpBufContains(const byte* hay, word32 haySz, const byte* needle, word32 needleSz) @@ -4946,25 +4967,6 @@ static void TestSftpHandleNamespaceIsolation(void) } #endif /* NO_WOLFSSH_DIR */ -/* A refused request must still answer the peer with an FXP_STATUS carrying the - * expected code. Asserting only that the call returned non-success would not - * catch a refusal that dropped the reply and left the session hung. - * The request id is checked too: TestRecvReply returns whatever is currently - * buffered, so a handler that dropped its reply would otherwise pass here by - * re-presenting the previous request's status. */ -static void AssertSftpStatusReply(WOLFSSH* ssh, int reqId, word32 code) -{ - const byte* reply; - word32 replySz; - - reply = wolfSSH_SFTP_TestRecvReply(ssh, &replySz); - AssertNotNull(reply); - AssertTrue(replySz >= WOLFSSH_SFTP_HEADER + UINT32_SZ); - AssertIntEQ(reply[LENGTH_SZ], WOLFSSH_FTP_STATUS); - AssertIntEQ((int)SftpGetU32(reply + LENGTH_SZ + MSG_ID_SZ), reqId); - AssertIntEQ((int)SftpGetU32(reply + WOLFSSH_SFTP_HEADER), (int)code); -} - /* The per-session open-file-handle count is capped at WOLFSSH_MAX_SFTP_HANDLES * to bound memory and keep the linear handle lookup from becoming a CPU DoS * vector. Open exactly the cap's worth of handles (all must succeed), confirm @@ -5410,6 +5412,442 @@ static void TestSftpStartPathInsideConfineRoot(void) #endif /* !NO_WOLFSSH_SERVER && !USE_WINDOWS_API && !NO_FILESYSTEM */ +#if !defined(NO_WOLFSSH_SERVER) && defined(USE_WINDOWS_API) && \ + !defined(NO_FILESYSTEM) +/* Walks the RecvOpen CREAT/EXCL/TRUNC flag matrix on Windows, checking both + * the open result and the resulting file state for each case. */ +static void TestSftpWindowsOpenFlagMatrix(void) +{ + WOLFSSH_CTX* ctx; + WOLFSSH* ssh; + int rid = 500; + int reqId; + word32 idx; + word32 replySz; + const byte* reply; + const word32 hOff = WOLFSSH_SFTP_HEADER + UINT32_SZ; /* handle in reply */ + WSTAT_T st; + byte handle[WOLFSSH_HANDLE_ID_SZ]; + byte pkt[256]; + char cwd[WOLFSSH_MAX_FILENAME]; + char path[64]; + word32 pathSz; + const char content[] = "0123456789"; + const char content2[] = "abcde"; + WFILE* file; + char readBuf[32]; + word32 readSz; + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); + AssertNotNull(ctx); + ssh = wolfSSH_new(ctx); + AssertNotNull(ssh); + AssertIntEQ(wolfSSH_SFTP_TestRecvStateInit(ssh), WS_SUCCESS); + + /* unique per-process fixture name so parallel runs don't collide */ + WSNPRINTF(path, sizeof(path), "wolfssh_winflags_%lu.tmp", + (unsigned long)GetCurrentProcessId()); + pathSz = (word32)WSTRLEN(path); + + WMEMSET(cwd, 0, sizeof(cwd)); + AssertNotNull(WGETCWD(ssh->fs, cwd, sizeof(cwd) - 1)); + AssertIntEQ(wolfSSH_SFTP_SetDefaultPath(ssh, cwd), WS_SUCCESS); + + (void)WREMOVE(ssh->fs, path); + + /* WRITE only, no CREAT: must fail against a missing file, untouched. */ + idx = 0; + SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, path, pathSz); idx += pathSz; + SftpPutU32(WOLFSSH_FXF_WRITE, pkt + idx); idx += UINT32_SZ; + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; + reqId = rid++; + AssertTrue(wolfSSH_SFTP_RecvOpen(ssh, reqId, pkt, idx) != WS_SUCCESS); + AssertSftpStatusReply(ssh, reqId, WOLFSSH_FTP_FAILURE); + AssertTrue(WSTAT(ssh->fs, path, &st) != 0); + + /* WRITE|CREAT, no TRUNC: must create the missing file (OPEN_ALWAYS). */ + idx = 0; + SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, path, pathSz); idx += pathSz; + SftpPutU32(WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT, pkt + idx); + idx += UINT32_SZ; + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; + AssertIntEQ(wolfSSH_SFTP_RecvOpen(ssh, rid++, pkt, idx), WS_SUCCESS); + reply = wolfSSH_SFTP_TestRecvReply(ssh, &replySz); + AssertNotNull(reply); + AssertTrue(replySz >= hOff + WOLFSSH_HANDLE_ID_SZ); + WMEMCPY(handle, reply + hOff, WOLFSSH_HANDLE_ID_SZ); + + /* seed content through the handle just opened */ + idx = 0; + SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ); + idx += WOLFSSH_HANDLE_ID_SZ; + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset hi */ + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset lo */ + SftpPutU32((word32)(sizeof(content) - 1), pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, content, sizeof(content) - 1); + idx += (word32)(sizeof(content) - 1); + AssertIntEQ(wolfSSH_SFTP_RecvWrite(ssh, rid++, pkt, idx), WS_SUCCESS); + + idx = 0; + SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ); + idx += WOLFSSH_HANDLE_ID_SZ; + AssertIntEQ(wolfSSH_SFTP_RecvClose(ssh, rid++, pkt, idx), WS_SUCCESS); + + AssertIntEQ(WSTAT(ssh->fs, path, &st), 0); + AssertIntEQ((int)st.st_size, (int)(sizeof(content) - 1)); + + /* WRITE|CREAT, no TRUNC, on the existing file: must not truncate it. */ + idx = 0; + SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, path, pathSz); idx += pathSz; + SftpPutU32(WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT, pkt + idx); + idx += UINT32_SZ; + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; + AssertIntEQ(wolfSSH_SFTP_RecvOpen(ssh, rid++, pkt, idx), WS_SUCCESS); + reply = wolfSSH_SFTP_TestRecvReply(ssh, &replySz); + AssertNotNull(reply); + AssertTrue(replySz >= hOff + WOLFSSH_HANDLE_ID_SZ); + WMEMCPY(handle, reply + hOff, WOLFSSH_HANDLE_ID_SZ); + + AssertIntEQ(WSTAT(ssh->fs, path, &st), 0); + AssertIntEQ((int)st.st_size, (int)(sizeof(content) - 1)); + + idx = 0; + SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ); + idx += WOLFSSH_HANDLE_ID_SZ; + AssertIntEQ(wolfSSH_SFTP_RecvClose(ssh, rid++, pkt, idx), WS_SUCCESS); + + /* WRITE|CREAT|TRUNC: must truncate the existing file immediately. */ + idx = 0; + SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, path, pathSz); idx += pathSz; + SftpPutU32(WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT | WOLFSSH_FXF_TRUNC, + pkt + idx); idx += UINT32_SZ; + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; + AssertIntEQ(wolfSSH_SFTP_RecvOpen(ssh, rid++, pkt, idx), WS_SUCCESS); + reply = wolfSSH_SFTP_TestRecvReply(ssh, &replySz); + AssertNotNull(reply); + AssertTrue(replySz >= hOff + WOLFSSH_HANDLE_ID_SZ); + WMEMCPY(handle, reply + hOff, WOLFSSH_HANDLE_ID_SZ); + + AssertIntEQ(WSTAT(ssh->fs, path, &st), 0); + AssertIntEQ((int)st.st_size, 0); + + idx = 0; + SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ); + idx += WOLFSSH_HANDLE_ID_SZ; + AssertIntEQ(wolfSSH_SFTP_RecvClose(ssh, rid++, pkt, idx), WS_SUCCESS); + + /* WRITE|CREAT|EXCL against the existing file: must fail. */ + idx = 0; + SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, path, pathSz); idx += pathSz; + SftpPutU32(WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT | WOLFSSH_FXF_EXCL, + pkt + idx); idx += UINT32_SZ; + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; + reqId = rid++; + AssertTrue(wolfSSH_SFTP_RecvOpen(ssh, reqId, pkt, idx) != WS_SUCCESS); + AssertSftpStatusReply(ssh, reqId, WOLFSSH_FTP_FAILURE); + + (void)WREMOVE(ssh->fs, path); + + /* WRITE|CREAT|EXCL against a missing path: must succeed. */ + idx = 0; + SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, path, pathSz); idx += pathSz; + SftpPutU32(WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT | WOLFSSH_FXF_EXCL, + pkt + idx); idx += UINT32_SZ; + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; + AssertIntEQ(wolfSSH_SFTP_RecvOpen(ssh, rid++, pkt, idx), WS_SUCCESS); + reply = wolfSSH_SFTP_TestRecvReply(ssh, &replySz); + AssertNotNull(reply); + AssertTrue(replySz >= hOff + WOLFSSH_HANDLE_ID_SZ); + WMEMCPY(handle, reply + hOff, WOLFSSH_HANDLE_ID_SZ); + + idx = 0; + SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ); + idx += WOLFSSH_HANDLE_ID_SZ; + AssertIntEQ(wolfSSH_SFTP_RecvClose(ssh, rid++, pkt, idx), WS_SUCCESS); + + (void)WREMOVE(ssh->fs, path); + + /* READ|WRITE|CREAT, no TRUNC, against a missing path: must create it. */ + idx = 0; + SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, path, pathSz); idx += pathSz; + SftpPutU32(WOLFSSH_FXF_READ | WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT, + pkt + idx); idx += UINT32_SZ; + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; + AssertIntEQ(wolfSSH_SFTP_RecvOpen(ssh, rid++, pkt, idx), WS_SUCCESS); + reply = wolfSSH_SFTP_TestRecvReply(ssh, &replySz); + AssertNotNull(reply); + AssertTrue(replySz >= hOff + WOLFSSH_HANDLE_ID_SZ); + WMEMCPY(handle, reply + hOff, WOLFSSH_HANDLE_ID_SZ); + AssertIntEQ(WSTAT(ssh->fs, path, &st), 0); + + idx = 0; + SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ); + idx += WOLFSSH_HANDLE_ID_SZ; + AssertIntEQ(wolfSSH_SFTP_RecvClose(ssh, rid++, pkt, idx), WS_SUCCESS); + + (void)WREMOVE(ssh->fs, path); + + /* WRITE|TRUNC, no CREAT, against a missing path: must fail + * (TRUNCATE_EXISTING requires the file to already exist). */ + idx = 0; + SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, path, pathSz); idx += pathSz; + SftpPutU32(WOLFSSH_FXF_WRITE | WOLFSSH_FXF_TRUNC, pkt + idx); + idx += UINT32_SZ; + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; + reqId = rid++; + AssertTrue(wolfSSH_SFTP_RecvOpen(ssh, reqId, pkt, idx) != WS_SUCCESS); + AssertSftpStatusReply(ssh, reqId, WOLFSSH_FTP_FAILURE); + + /* seed an existing file with content, then WRITE|TRUNC, no CREAT: must + * truncate it immediately (TRUNCATE_EXISTING). */ + idx = 0; + SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, path, pathSz); idx += pathSz; + SftpPutU32(WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT, pkt + idx); + idx += UINT32_SZ; + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; + AssertIntEQ(wolfSSH_SFTP_RecvOpen(ssh, rid++, pkt, idx), WS_SUCCESS); + reply = wolfSSH_SFTP_TestRecvReply(ssh, &replySz); + AssertNotNull(reply); + AssertTrue(replySz >= hOff + WOLFSSH_HANDLE_ID_SZ); + WMEMCPY(handle, reply + hOff, WOLFSSH_HANDLE_ID_SZ); + + idx = 0; + SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ); + idx += WOLFSSH_HANDLE_ID_SZ; + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset hi */ + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset lo */ + SftpPutU32((word32)(sizeof(content) - 1), pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, content, sizeof(content) - 1); + idx += (word32)(sizeof(content) - 1); + AssertIntEQ(wolfSSH_SFTP_RecvWrite(ssh, rid++, pkt, idx), WS_SUCCESS); + + idx = 0; + SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ); + idx += WOLFSSH_HANDLE_ID_SZ; + AssertIntEQ(wolfSSH_SFTP_RecvClose(ssh, rid++, pkt, idx), WS_SUCCESS); + + AssertIntEQ(WSTAT(ssh->fs, path, &st), 0); + AssertIntEQ((int)st.st_size, (int)(sizeof(content) - 1)); + + idx = 0; + SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, path, pathSz); idx += pathSz; + SftpPutU32(WOLFSSH_FXF_WRITE | WOLFSSH_FXF_TRUNC, pkt + idx); + idx += UINT32_SZ; + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; + AssertIntEQ(wolfSSH_SFTP_RecvOpen(ssh, rid++, pkt, idx), WS_SUCCESS); + reply = wolfSSH_SFTP_TestRecvReply(ssh, &replySz); + AssertNotNull(reply); + AssertTrue(replySz >= hOff + WOLFSSH_HANDLE_ID_SZ); + WMEMCPY(handle, reply + hOff, WOLFSSH_HANDLE_ID_SZ); + + AssertIntEQ(WSTAT(ssh->fs, path, &st), 0); + AssertIntEQ((int)st.st_size, 0); + + idx = 0; + SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ); + idx += WOLFSSH_HANDLE_ID_SZ; + AssertIntEQ(wolfSSH_SFTP_RecvClose(ssh, rid++, pkt, idx), WS_SUCCESS); + + (void)WREMOVE(ssh->fs, path); + + /* seed an existing file with content for the READ|TRUNC case below. */ + idx = 0; + SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, path, pathSz); idx += pathSz; + SftpPutU32(WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT, pkt + idx); + idx += UINT32_SZ; + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; + AssertIntEQ(wolfSSH_SFTP_RecvOpen(ssh, rid++, pkt, idx), WS_SUCCESS); + reply = wolfSSH_SFTP_TestRecvReply(ssh, &replySz); + AssertNotNull(reply); + AssertTrue(replySz >= hOff + WOLFSSH_HANDLE_ID_SZ); + WMEMCPY(handle, reply + hOff, WOLFSSH_HANDLE_ID_SZ); + + idx = 0; + SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ); + idx += WOLFSSH_HANDLE_ID_SZ; + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset hi */ + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset lo */ + SftpPutU32((word32)(sizeof(content) - 1), pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, content, sizeof(content) - 1); + idx += (word32)(sizeof(content) - 1); + AssertIntEQ(wolfSSH_SFTP_RecvWrite(ssh, rid++, pkt, idx), WS_SUCCESS); + + idx = 0; + SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ); + idx += WOLFSSH_HANDLE_ID_SZ; + AssertIntEQ(wolfSSH_SFTP_RecvClose(ssh, rid++, pkt, idx), WS_SUCCESS); + + AssertIntEQ(WSTAT(ssh->fs, path, &st), 0); + AssertIntEQ((int)st.st_size, (int)(sizeof(content) - 1)); + + /* READ|TRUNC, no WRITE, no CREAT: TRUNCATE_EXISTING requires + * GENERIC_WRITE in dwDesiredAccess, so this must still open (falling + * back to OPEN_EXISTING) rather than fail with ERROR_INVALID_PARAMETER, + * and must leave the content untouched since it cannot actually + * truncate. */ + idx = 0; + SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, path, pathSz); idx += pathSz; + SftpPutU32(WOLFSSH_FXF_READ | WOLFSSH_FXF_TRUNC, pkt + idx); + idx += UINT32_SZ; + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; + reqId = rid++; + AssertIntEQ(wolfSSH_SFTP_RecvOpen(ssh, reqId, pkt, idx), WS_SUCCESS); + reply = wolfSSH_SFTP_TestRecvReply(ssh, &replySz); + AssertNotNull(reply); + AssertTrue(replySz >= hOff + WOLFSSH_HANDLE_ID_SZ); + WMEMCPY(handle, reply + hOff, WOLFSSH_HANDLE_ID_SZ); + + AssertIntEQ(WSTAT(ssh->fs, path, &st), 0); + AssertIntEQ((int)st.st_size, (int)(sizeof(content) - 1)); + + idx = 0; + SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ); + idx += WOLFSSH_HANDLE_ID_SZ; + AssertIntEQ(wolfSSH_SFTP_RecvClose(ssh, rid++, pkt, idx), WS_SUCCESS); + + (void)WREMOVE(ssh->fs, path); + + /* WRITE|APPEND|CREAT against a missing path: must create it, and every + * write must land at EOF regardless of the client-supplied offset. */ + idx = 0; + SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, path, pathSz); idx += pathSz; + SftpPutU32(WOLFSSH_FXF_WRITE | WOLFSSH_FXF_APPEND | WOLFSSH_FXF_CREAT, + pkt + idx); idx += UINT32_SZ; + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; + AssertIntEQ(wolfSSH_SFTP_RecvOpen(ssh, rid++, pkt, idx), WS_SUCCESS); + reply = wolfSSH_SFTP_TestRecvReply(ssh, &replySz); + AssertNotNull(reply); + AssertTrue(replySz >= hOff + WOLFSSH_HANDLE_ID_SZ); + WMEMCPY(handle, reply + hOff, WOLFSSH_HANDLE_ID_SZ); + + /* first write, offset 0: lands at EOF (0), file becomes "0123456789" */ + idx = 0; + SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ); + idx += WOLFSSH_HANDLE_ID_SZ; + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset hi */ + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset lo */ + SftpPutU32((word32)(sizeof(content) - 1), pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, content, sizeof(content) - 1); + idx += (word32)(sizeof(content) - 1); + AssertIntEQ(wolfSSH_SFTP_RecvWrite(ssh, rid++, pkt, idx), WS_SUCCESS); + + /* second write, offset stale at 0 again: must still append at EOF (10) + * rather than overwrite the start of the file. */ + idx = 0; + SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ); + idx += WOLFSSH_HANDLE_ID_SZ; + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset hi */ + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset lo */ + SftpPutU32((word32)(sizeof(content2) - 1), pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, content2, sizeof(content2) - 1); + idx += (word32)(sizeof(content2) - 1); + AssertIntEQ(wolfSSH_SFTP_RecvWrite(ssh, rid++, pkt, idx), WS_SUCCESS); + + idx = 0; + SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ); + idx += WOLFSSH_HANDLE_ID_SZ; + AssertIntEQ(wolfSSH_SFTP_RecvClose(ssh, rid++, pkt, idx), WS_SUCCESS); + + AssertIntEQ(WSTAT(ssh->fs, path, &st), 0); + AssertIntEQ((int)st.st_size, + (int)(sizeof(content) - 1 + sizeof(content2) - 1)); + + /* content, not just length: a build that appended the right byte + * count in the wrong order, or left a gap, would still pass the size + * check above. */ + AssertIntEQ(WFOPEN(NULL, &file, path, "rb"), 0); + AssertTrue(file != WBADFILE); + readSz = (word32)WFREAD(NULL, readBuf, 1, + sizeof(content) - 1 + sizeof(content2) - 1, file); + WFCLOSE(NULL, file); + AssertIntEQ((int)readSz, + (int)(sizeof(content) - 1 + sizeof(content2) - 1)); + AssertIntEQ(WMEMCMP(readBuf, "0123456789abcde", readSz), 0); + + (void)WREMOVE(ssh->fs, path); + + /* APPEND|CREAT, no WRITE: FILE_APPEND_DATA alone is enough to create + * the file and append to it, unlike master, which could not open this + * combination at all. */ + idx = 0; + SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, path, pathSz); idx += pathSz; + SftpPutU32(WOLFSSH_FXF_APPEND | WOLFSSH_FXF_CREAT, pkt + idx); + idx += UINT32_SZ; + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; + AssertIntEQ(wolfSSH_SFTP_RecvOpen(ssh, rid++, pkt, idx), WS_SUCCESS); + reply = wolfSSH_SFTP_TestRecvReply(ssh, &replySz); + AssertNotNull(reply); + AssertTrue(replySz >= hOff + WOLFSSH_HANDLE_ID_SZ); + WMEMCPY(handle, reply + hOff, WOLFSSH_HANDLE_ID_SZ); + + idx = 0; + SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ); + idx += WOLFSSH_HANDLE_ID_SZ; + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset hi */ + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset lo */ + SftpPutU32((word32)(sizeof(content) - 1), pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, content, sizeof(content) - 1); + idx += (word32)(sizeof(content) - 1); + AssertIntEQ(wolfSSH_SFTP_RecvWrite(ssh, rid++, pkt, idx), WS_SUCCESS); + + idx = 0; + SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ); + idx += WOLFSSH_HANDLE_ID_SZ; + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset hi */ + SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset lo */ + SftpPutU32((word32)(sizeof(content2) - 1), pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, content2, sizeof(content2) - 1); + idx += (word32)(sizeof(content2) - 1); + AssertIntEQ(wolfSSH_SFTP_RecvWrite(ssh, rid++, pkt, idx), WS_SUCCESS); + + idx = 0; + SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ; + WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ); + idx += WOLFSSH_HANDLE_ID_SZ; + AssertIntEQ(wolfSSH_SFTP_RecvClose(ssh, rid++, pkt, idx), WS_SUCCESS); + + AssertIntEQ(WSTAT(ssh->fs, path, &st), 0); + AssertIntEQ((int)st.st_size, + (int)(sizeof(content) - 1 + sizeof(content2) - 1)); + + (void)WREMOVE(ssh->fs, path); + wolfSSH_SFTP_TestRecvStateFree(ssh); + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); +} +#endif /* !NO_WOLFSSH_SERVER && USE_WINDOWS_API && !NO_FILESYSTEM */ + #if defined(WOLFSSL_NUCLEUS) && !defined(NO_WOLFSSH_MKTIME) static void TestNucleusMonthConversion(void) { @@ -8165,6 +8603,11 @@ int main(int argc, char** argv) /* confinement follows the confine root, not the start path */ TestSftpStartPathInsideConfineRoot(); #endif + #if !defined(NO_WOLFSSH_SERVER) && defined(USE_WINDOWS_API) && \ + !defined(NO_FILESYSTEM) + /* RecvOpen's Windows open-flag matrix */ + TestSftpWindowsOpenFlagMatrix(); + #endif #if defined(WOLFSSL_NUCLEUS) && !defined(NO_WOLFSSH_MKTIME) TestNucleusMonthConversion(); #endif diff --git a/wolfssh/wolfsftp.h b/wolfssh/wolfsftp.h index a7ae54669..676912aa6 100644 --- a/wolfssh/wolfsftp.h +++ b/wolfssh/wolfsftp.h @@ -356,8 +356,7 @@ WOLFSSH_LOCAL void wolfSSH_SFTP_ShowSizes(void); word32* handleSz); WOLFSSH_API int wolfSSH_TestSftpSendCap(WOLFSSH* ssh, word32 cap); WOLFSSH_API int wolfSSH_TestSftpStallPending(WOLFSSH* ssh, word32 count); - #if !defined(NO_WOLFSSH_SERVER) && !defined(USE_WINDOWS_API) && \ - !defined(NO_FILESYSTEM) + #if !defined(NO_WOLFSSH_SERVER) && !defined(NO_FILESYSTEM) WOLFSSH_API int wolfSSH_SFTP_TestRecvStateInit(WOLFSSH* ssh); WOLFSSH_API const byte* wolfSSH_SFTP_TestRecvReply(WOLFSSH* ssh, word32* sz); @@ -366,7 +365,9 @@ WOLFSSH_LOCAL void wolfSSH_SFTP_ShowSizes(void); #ifndef NO_WOLFSSH_DIR WOLFSSH_API int wolfSSH_SFTP_TestDirHandleCount(WOLFSSH* ssh); #endif - WOLFSSH_API int wolfSSH_SFTP_TestInvalidateHeadFd(WOLFSSH* ssh); + #ifndef USE_WINDOWS_API + WOLFSSH_API int wolfSSH_SFTP_TestInvalidateHeadFd(WOLFSSH* ssh); + #endif #endif #if defined(WOLFSSL_NUCLEUS) && !defined(NO_WOLFSSH_MKTIME) WOLFSSH_API int wolfSSH_TestNucleusMonthFromDate(word16 d);