mirror of https://github.com/wolfSSL/wolfssh.git
3560 lines
117 KiB
C
3560 lines
117 KiB
C
/* wolfscp.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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
/*
|
|
* The scp module provides SCP server functionality including a default
|
|
* receive callback. The default callbacks assume a filesystem is
|
|
* available, but users can write and register their own callbacks if
|
|
* no filesystem is available.
|
|
*/
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <wolfssh/wolfscp.h>
|
|
|
|
#ifdef WOLFSSH_SCP
|
|
|
|
#include <wolfssh/ssh.h>
|
|
#include <wolfssh/internal.h>
|
|
#include <wolfssh/log.h>
|
|
|
|
|
|
#ifdef NO_INLINE
|
|
#include <wolfssh/misc.h>
|
|
#else
|
|
#define WOLFSSH_MISC_INCLUDED
|
|
#include "src/misc.c"
|
|
#endif
|
|
|
|
#ifndef WOLFSSH_DEFAULT_EXTDATA_SZ
|
|
#define WOLFSSH_DEFAULT_EXTDATA_SZ 128
|
|
#endif
|
|
|
|
#ifndef NO_FILESYSTEM
|
|
static int ScpFileIsDir(ScpSendCtx* ctx);
|
|
static int ScpPushDir(void *fs, ScpSendCtx* ctx, const char* path, void* heap);
|
|
static int ScpPopDir(void *fs, ScpSendCtx* ctx, void* heap);
|
|
#endif
|
|
|
|
#define WOLFSSH_MODE_MASK 0777
|
|
|
|
const char scpError[] = "scp error: %s, %d";
|
|
const char scpState[] = "scp state: %s";
|
|
|
|
|
|
/* Logs an SCP state error, except a non-blocking WS_WANT_READ/WS_WANT_WRITE,
|
|
* which is normal back-pressure the caller retries (matching the quiet
|
|
* SCP_SEND_FILE handling) rather than a transfer failure. */
|
|
static void LogScpStateError(const char* state, int ret)
|
|
{
|
|
(void)state;
|
|
if (ret != WS_WANT_READ && ret != WS_WANT_WRITE)
|
|
WLOG(WS_LOG_ERROR, scpError, state, ret);
|
|
}
|
|
|
|
|
|
static int _DumpExtendedData(WOLFSSH* ssh)
|
|
{
|
|
byte msg[WOLFSSH_DEFAULT_EXTDATA_SZ];
|
|
int msgSz;
|
|
|
|
msgSz = wolfSSH_extended_data_read(ssh, msg, WOLFSSH_DEFAULT_EXTDATA_SZ-1);
|
|
if (msgSz > 0) {
|
|
msg[msgSz] = 0;
|
|
fprintf(stderr, "%s", msg);
|
|
msgSz = WS_SUCCESS;
|
|
}
|
|
|
|
return msgSz;
|
|
}
|
|
|
|
|
|
/* Sends sz bytes from data, completing any rekey or full window that blocks
|
|
* the send.
|
|
*
|
|
* Attempts the send; on a WS_WINDOW_FULL (peer window reached zero) or
|
|
* WS_REKEYING result, drives wolfSSH_worker once to pick up the peer's window
|
|
* adjust or to advance the rekey, then retries the send. The retry is what
|
|
* clears the status: wolfSSH_worker does not reset ssh->error, so a loop that
|
|
* keyed on ssh->error staying WS_WINDOW_FULL would keep driving the worker
|
|
* after the window already reopened and stall the transfer. This mirrors the
|
|
* original SCP_SEND_FILE handling, extended to the control-message senders so a
|
|
* rekey or full window during a timestamp/header/confirmation send is no longer
|
|
* reported as fatal. Termination needs no retry count: in blocking mode
|
|
* wolfSSH_worker waits on the socket for the peer's packet; in non-blocking
|
|
* mode it returns WS_WANT_READ/WS_WANT_WRITE, which is returned to the caller
|
|
* so a stalled rekey cannot spin forever. Returns the byte count from
|
|
* wolfSSH_stream_send (>= 0) or a negative error code, matching the wrapped
|
|
* call so callers keep their existing return handling.
|
|
*/
|
|
static int ScpStreamSend(WOLFSSH* ssh, byte* data, word32 sz)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
int err;
|
|
int done = 0;
|
|
|
|
if (ssh == NULL || data == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
/* Flush queued output before sending. Otherwise a KEXINIT enqueued by a
|
|
* rekey triggered on the prior send sits unsent while wolfSSH_worker runs
|
|
* DoReceive before its own flush, blocking on the socket with the peer
|
|
* waiting for our KEXINIT. */
|
|
if (wolfSSH_OutputPending(ssh)) {
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
while (!done) {
|
|
ret = wolfSSH_stream_send(ssh, data, sz);
|
|
if (ret >= 0) {
|
|
/* sent (full or partial byte count); exits via while (!done) */
|
|
done = 1;
|
|
}
|
|
else {
|
|
err = wolfSSH_get_error(ssh);
|
|
if (err == WS_WINDOW_FULL || err == WS_REKEYING) {
|
|
ret = wolfSSH_worker(ssh, NULL);
|
|
err = wolfSSH_get_error(ssh);
|
|
/* A non-blocking want surfaces as a generic worker error with
|
|
* the want recorded in ssh->error (see GetInputData). Return it
|
|
* so the caller retries instead of tearing down the send. */
|
|
if (err == WS_WANT_READ || err == WS_WANT_WRITE)
|
|
return err;
|
|
/* Only a rekey/window/channel-data status means "keep driving".
|
|
* Any other negative status is fatal and returned. */
|
|
if (ret < 0 && ret != WS_REKEYING && ret != WS_WINDOW_FULL
|
|
&& ret != WS_CHAN_RXD)
|
|
return ret;
|
|
/* otherwise loop and retry the send, which clears the status */
|
|
}
|
|
else {
|
|
/* Fatal or other final status; return it unchanged. */
|
|
done = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Reads up to sz bytes into data, completing any rekey that fires mid-read.
|
|
*
|
|
* Flushes queued output before reading so a KEXINIT enqueued by a receive-side
|
|
* highwater rekey is actually sent, otherwise the peer can wait for our KEXINIT
|
|
* while we block on the read. On a read that fails with WS_REKEYING the worker
|
|
* is driven to finish the rekey and the read is retried. The helper is
|
|
* error-code transparent: every other status (WS_EOF, WS_EXTDATA,
|
|
* WS_CHANNEL_CLOSED, WS_SOCKET_ERROR_E, WS_WANT_READ/WS_WANT_WRITE, byte count)
|
|
* is returned unchanged so each caller keeps its existing branch handling.
|
|
*/
|
|
static int ScpStreamRead(WOLFSSH* ssh, byte* data, word32 sz)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
int done = 0;
|
|
|
|
if (ssh == NULL || data == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
do {
|
|
if (wolfSSH_OutputPending(ssh)) {
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
ret = wolfSSH_stream_read(ssh, data, sz);
|
|
if (ret < 0 && wolfSSH_get_error(ssh) == WS_REKEYING) {
|
|
/* Drive the rekey to completion, then retry the read. A worker
|
|
* status that is not rekey or channel data means the rekey stalled
|
|
* or a non-blocking want occurred, so return it rather than
|
|
* spin. */
|
|
ret = wolfSSH_worker(ssh, NULL);
|
|
if (ret < 0 && ret != WS_CHAN_RXD
|
|
&& wolfSSH_get_error(ssh) != WS_REKEYING)
|
|
return ret;
|
|
}
|
|
else {
|
|
done = 1;
|
|
}
|
|
} while (!done);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int DoScpSink(WOLFSSH* ssh)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
|
|
if (ssh == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
while (ret == WS_SUCCESS && ssh->scpState != SCP_DONE) {
|
|
|
|
switch (ssh->scpState) {
|
|
|
|
case SCP_SINK_BEGIN:
|
|
WLOG(WS_LOG_DEBUG, scpState, "SCP_SINK_BEGIN");
|
|
|
|
ssh->scpState = SCP_SEND_CONFIRMATION;
|
|
ssh->scpNextState = SCP_RECEIVE_MESSAGE;
|
|
|
|
ssh->scpConfirm = ssh->ctx->scpRecvCb(ssh,
|
|
WOLFSSH_SCP_NEW_REQUEST, ssh->scpBasePath,
|
|
NULL, 0, 0, 0, 0, NULL, 0, 0,
|
|
wolfSSH_GetScpRecvCtx(ssh));
|
|
continue;
|
|
|
|
case SCP_RECEIVE_MESSAGE:
|
|
WLOG(WS_LOG_DEBUG, scpState, "SCP_RECEIVE_MESSAGE");
|
|
|
|
if ( (ret = ReceiveScpMessage(ssh)) < WS_SUCCESS) {
|
|
if (ret == WS_EOF) {
|
|
ret = WS_SUCCESS; /* successfully received message */
|
|
ssh->scpState = SCP_DONE;
|
|
break;
|
|
}
|
|
|
|
LogScpStateError("RECEIVE_MESSAGE", ret);
|
|
break;
|
|
}
|
|
|
|
if (ssh->scpMsgType == WOLFSSH_SCP_MSG_DIR) {
|
|
ssh->scpState = SCP_SEND_CONFIRMATION;
|
|
ssh->scpNextState = SCP_RECEIVE_MESSAGE;
|
|
ssh->scpFileState = WOLFSSH_SCP_NEW_DIR;
|
|
|
|
} else if (ssh->scpMsgType == WOLFSSH_SCP_MSG_END_DIR) {
|
|
ssh->scpState = SCP_SEND_CONFIRMATION;
|
|
ssh->scpNextState = SCP_RECEIVE_MESSAGE;
|
|
|
|
} else if (ssh->scpMsgType == WOLFSSH_SCP_MSG_TIME) {
|
|
ssh->scpState = SCP_SEND_CONFIRMATION;
|
|
ssh->scpNextState = SCP_RECEIVE_MESSAGE;
|
|
continue;
|
|
|
|
} else if (ssh->scpMsgType == WOLFSSH_SCP_MSG_FILE) {
|
|
ssh->scpState = SCP_SEND_CONFIRMATION;
|
|
ssh->scpNextState = SCP_RECEIVE_FILE;
|
|
ssh->scpFileState = WOLFSSH_SCP_NEW_FILE;
|
|
|
|
} else {
|
|
ret = WS_SCP_BAD_MSG_E;
|
|
WLOG(WS_LOG_ERROR, scpError, "bad msg type", ret);
|
|
break;
|
|
}
|
|
|
|
/* scp receive callback */
|
|
ssh->scpConfirm = ssh->ctx->scpRecvCb(ssh, ssh->scpFileState,
|
|
ssh->scpBasePath, ssh->scpFileName, ssh->scpFileMode,
|
|
ssh->scpMTime, ssh->scpATime, ssh->scpFileSz, NULL, 0,
|
|
0, wolfSSH_GetScpRecvCtx(ssh));
|
|
|
|
continue;
|
|
|
|
case SCP_SEND_CONFIRMATION:
|
|
WLOG(WS_LOG_DEBUG, scpState, "SCP_SEND_CONFIRMATION");
|
|
|
|
if ( (ret = SendScpConfirmation(ssh)) < WS_SUCCESS) {
|
|
LogScpStateError("SEND_CONFIRMATION", ret);
|
|
break;
|
|
}
|
|
|
|
ssh->scpState = ssh->scpNextState;
|
|
continue;
|
|
|
|
case SCP_RECEIVE_CONFIRMATION:
|
|
WLOG(WS_LOG_DEBUG, scpState, "SCP_RECEIVE_CONFIRMATION");
|
|
|
|
if ( (ret = ReceiveScpConfirmation(ssh)) < WS_SUCCESS) {
|
|
LogScpStateError("RECEIVE_CONFIRMATION", ret);
|
|
break;
|
|
}
|
|
|
|
ssh->scpState = SCP_RECEIVE_MESSAGE;
|
|
continue;
|
|
|
|
case SCP_RECEIVE_FILE:
|
|
WLOG(WS_LOG_DEBUG, scpState, "SCP_RECEIVE_FILE");
|
|
|
|
if ( (ret = ReceiveScpFile(ssh)) < WS_SUCCESS) {
|
|
LogScpStateError("RECEIVE_FILE", ret);
|
|
break;
|
|
}
|
|
|
|
/* reset success status */
|
|
ret = WS_SUCCESS;
|
|
|
|
/* scp receive callback, give user file data */
|
|
ssh->scpConfirm = ssh->ctx->scpRecvCb(ssh,
|
|
WOLFSSH_SCP_FILE_PART, ssh->scpBasePath,
|
|
ssh->scpFileName, ssh->scpFileMode, ssh->scpMTime,
|
|
ssh->scpATime, ssh->scpFileSz, ssh->scpFileBuffer,
|
|
ssh->scpFileBufferSz, ssh->scpFileOffset,
|
|
wolfSSH_GetScpRecvCtx(ssh));
|
|
ssh->scpFileOffset += ssh->scpFileBufferSz;
|
|
|
|
/* reset recv buffer */
|
|
ssh->scpFileBufferSz = 0;
|
|
|
|
if (ssh->scpConfirm != WS_SCP_CONTINUE) {
|
|
/* user aborted, send failure confirmation */
|
|
ssh->scpState = SCP_SEND_CONFIRMATION;
|
|
ssh->scpNextState = SCP_DONE;
|
|
|
|
} else if (ssh->scpFileOffset < ssh->scpFileSz) {
|
|
ssh->scpNextState = SCP_RECEIVE_FILE;
|
|
|
|
} else {
|
|
/* scp receive callback, notify user file is done */
|
|
ssh->scpConfirm = ssh->ctx->scpRecvCb(ssh,
|
|
WOLFSSH_SCP_FILE_DONE, ssh->scpBasePath,
|
|
ssh->scpFileName, ssh->scpFileMode, ssh->scpMTime,
|
|
ssh->scpATime, ssh->scpFileSz, NULL, 0, 0,
|
|
wolfSSH_GetScpRecvCtx(ssh));
|
|
|
|
ssh->scpFileOffset = 0;
|
|
ssh->scpATime = 0;
|
|
ssh->scpMTime = 0;
|
|
ssh->scpState = SCP_SEND_CONFIRMATION;
|
|
ssh->scpNextState = SCP_RECEIVE_CONFIRMATION;
|
|
}
|
|
continue;
|
|
|
|
} /* end switch */
|
|
|
|
} /* end while */
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ScpSourceInit(WOLFSSH* ssh)
|
|
{
|
|
/* file name */
|
|
if (ssh->scpFileName != NULL) {
|
|
WFREE(ssh->scpFileName, ssh->ctx->heap, DYNTYPE_STRING);
|
|
ssh->scpFileName = NULL;
|
|
ssh->scpFileNameSz = 0;
|
|
}
|
|
|
|
ssh->scpFileName = (char*)WMALLOC(DEFAULT_SCP_FILE_NAME_SZ, ssh->ctx->heap,
|
|
DYNTYPE_STRING);
|
|
if (ssh->scpFileName == NULL)
|
|
return WS_MEMORY_E;
|
|
|
|
ssh->scpFileNameSz = DEFAULT_SCP_FILE_NAME_SZ;
|
|
WMEMSET(ssh->scpFileName, 0, DEFAULT_SCP_FILE_NAME_SZ);
|
|
|
|
/* file buffer */
|
|
ssh->scpFileBuffer = (byte*)WMALLOC(DEFAULT_SCP_BUFFER_SZ, ssh->ctx->heap,
|
|
DYNTYPE_BUFFER);
|
|
if (ssh->scpFileBuffer == NULL) {
|
|
WFREE(ssh->scpFileName, ssh->ctx->heap, DYNTYPE_STRING);
|
|
ssh->scpFileName = NULL;
|
|
return WS_MEMORY_E;
|
|
}
|
|
ssh->scpFileBufferSz = DEFAULT_SCP_BUFFER_SZ;
|
|
WMEMSET(ssh->scpFileBuffer, 0, DEFAULT_SCP_BUFFER_SZ);
|
|
|
|
return WS_SUCCESS;
|
|
}
|
|
|
|
|
|
/* Sends timestamp information (access, modification) to peer.
|
|
*
|
|
* T<modification_secs> 0 <access_secs> 0
|
|
*
|
|
* returns WS_SUCCESS on success, negative upon error
|
|
*/
|
|
static int SendScpTimestamp(WOLFSSH* ssh)
|
|
{
|
|
int ret = WS_SUCCESS, bufSz;
|
|
char buf[DEFAULT_SCP_MSG_SZ];
|
|
|
|
if (ssh == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
WMEMSET(buf, 0, sizeof(buf));
|
|
#ifdef WORD64_AVAILABLE
|
|
WSNPRINTF(buf, sizeof(buf), "T%llu 0 %llu 0\n",
|
|
(unsigned long long)ssh->scpMTime,
|
|
(unsigned long long)ssh->scpATime);
|
|
#else
|
|
WSNPRINTF(buf, sizeof(buf), "T%lu 0 %lu 0\n",
|
|
(unsigned long)ssh->scpMTime,
|
|
(unsigned long)ssh->scpATime);
|
|
#endif
|
|
bufSz = (int)WSTRLEN(buf);
|
|
|
|
ret = ScpStreamSend(ssh, (byte*)buf, bufSz);
|
|
if (ret == bufSz) {
|
|
WLOG(WS_LOG_DEBUG, "scp: sent timestamp: %s", buf);
|
|
ret = WS_SUCCESS;
|
|
}
|
|
/* A non-blocking want is left as WS_WANT_READ/WS_WANT_WRITE for the caller
|
|
* to retry (nothing is queued yet, so the resend is clean), consistent with
|
|
* the SCP_SEND_FILE data path; only a real short send is fatal. */
|
|
else if (ret != WS_WANT_READ && ret != WS_WANT_WRITE) {
|
|
ret = WS_FATAL_ERROR;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Sends file header (mode, file name) to peer.
|
|
*
|
|
* C<mode> <length> <filename>
|
|
*
|
|
* returns WS_SUCCESS on success, negative upon error
|
|
*/
|
|
static int SendScpFileHeader(WOLFSSH* ssh)
|
|
{
|
|
int ret = WS_SUCCESS, bufSz;
|
|
char buf[DEFAULT_SCP_MSG_SZ];
|
|
char *filehdr;
|
|
|
|
if (ssh == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
#ifndef WSCPFILEHDR
|
|
WMEMSET(buf, 0, sizeof(buf));
|
|
WSNPRINTF(buf, sizeof(buf), "C%04o %u %s\n",
|
|
ssh->scpFileMode & WOLFSSH_MODE_MASK,
|
|
ssh->scpFileSz, ssh->scpFileName);
|
|
filehdr = buf;
|
|
#else
|
|
filehdr = WSCPFILEHDR(ssh);
|
|
if (!filehdr)
|
|
return WS_BAD_ARGUMENT;
|
|
#endif
|
|
bufSz = (int)WSTRLEN(filehdr);
|
|
ret = ScpStreamSend(ssh, (byte*)filehdr, bufSz);
|
|
if (ret == bufSz) {
|
|
WLOG(WS_LOG_DEBUG, "scp: sent file header: %s", filehdr);
|
|
ret = WS_SUCCESS;
|
|
}
|
|
/* leave a non-blocking want for the caller to retry; only a real short
|
|
* send is fatal (see SendScpTimestamp) */
|
|
else if (ret != WS_WANT_READ && ret != WS_WANT_WRITE) {
|
|
ret = WS_FATAL_ERROR;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* Sends directory message to peer, length is ignored but must
|
|
* be present in message format (set to 0).
|
|
*
|
|
* D<mode> <length> <dirname>
|
|
*
|
|
* returns WS_SUCCESS on success, negative upon error
|
|
*/
|
|
static int SendScpEnterDirectory(WOLFSSH* ssh)
|
|
{
|
|
int ret = WS_SUCCESS, bufSz;
|
|
char buf[DEFAULT_SCP_MSG_SZ];
|
|
|
|
if (ssh == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
WMEMSET(buf, 0, sizeof(buf));
|
|
|
|
WSNPRINTF(buf, sizeof(buf), "D%04o 0 %s\n",
|
|
ssh->scpFileMode & WOLFSSH_MODE_MASK,
|
|
ssh->scpFileName);
|
|
|
|
bufSz = (int)WSTRLEN(buf);
|
|
|
|
ret = ScpStreamSend(ssh, (byte*)buf, bufSz);
|
|
if (ret == bufSz) {
|
|
WLOG(WS_LOG_DEBUG, "scp: sent directory msg: %s", buf);
|
|
ret = WS_SUCCESS;
|
|
}
|
|
/* leave a non-blocking want for the caller to retry; only a real short
|
|
* send is fatal (see SendScpTimestamp) */
|
|
else if (ret != WS_WANT_READ && ret != WS_WANT_WRITE) {
|
|
ret = WS_FATAL_ERROR;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Sends end directory message to peer.
|
|
*
|
|
* returns WS_SUCCESS on success, negative upon error
|
|
*/
|
|
static int SendScpExitDirectory(WOLFSSH* ssh)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
char buf[2];
|
|
|
|
if (ssh == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
buf[0] = 'E';
|
|
buf[1] = '\n';
|
|
|
|
ret = ScpStreamSend(ssh, (byte*)buf, sizeof(buf));
|
|
if (ret == sizeof(buf)) {
|
|
WLOG(WS_LOG_DEBUG, "scp: sent end directory msg: E");
|
|
ret = WS_SUCCESS;
|
|
}
|
|
/* leave a non-blocking want for the caller to retry; only a real short
|
|
* send is fatal (see SendScpTimestamp) */
|
|
else if (ret != WS_WANT_READ && ret != WS_WANT_WRITE) {
|
|
ret = WS_FATAL_ERROR;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int DoScpSource(WOLFSSH* ssh)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
|
|
if (ssh == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
while (ret == WS_SUCCESS && ssh->scpState != SCP_DONE) {
|
|
|
|
switch (ssh->scpState) {
|
|
|
|
case SCP_SOURCE_BEGIN:
|
|
WLOG(WS_LOG_DEBUG, scpState, "SCP_SOURCE_BEGIN");
|
|
|
|
ssh->scpConfirm = ssh->ctx->scpSendCb(ssh,
|
|
WOLFSSH_SCP_NEW_REQUEST, NULL, NULL, 0, NULL, NULL,
|
|
NULL, 0, NULL, NULL, 0, wolfSSH_GetScpSendCtx(ssh));
|
|
|
|
if (ssh->scpConfirm == WS_SCP_ABORT ||
|
|
ssh->scpConfirm == WS_EOF) {
|
|
ssh->scpState = SCP_RECEIVE_CONFIRMATION_WITH_RECEIPT;
|
|
ssh->scpNextState = SCP_DONE;
|
|
} else {
|
|
ssh->scpState = SCP_SOURCE_INIT;
|
|
}
|
|
continue;
|
|
|
|
case SCP_SOURCE_INIT:
|
|
WLOG(WS_LOG_DEBUG, scpState, "SCP_SOURCE_INIT");
|
|
|
|
if ( (ret = ScpSourceInit(ssh)) < WS_SUCCESS) {
|
|
break;
|
|
}
|
|
|
|
ssh->scpState = SCP_RECEIVE_CONFIRMATION;
|
|
ssh->scpNextState = SCP_TRANSFER;
|
|
continue;
|
|
|
|
case SCP_SEND_CONFIRMATION:
|
|
WLOG(WS_LOG_DEBUG, scpState, "SCP_SEND_CONFIRMATION");
|
|
|
|
if ( (ret = SendScpConfirmation(ssh)) < WS_SUCCESS) {
|
|
LogScpStateError("SEND_CONFIRMATION", ret);
|
|
break;
|
|
}
|
|
|
|
ssh->scpState = ssh->scpNextState;
|
|
continue;
|
|
|
|
case SCP_CONFIRMATION_WITH_RECEIPT:
|
|
WLOG(WS_LOG_DEBUG, scpState, "SCP_CONFIRMATION_WITH_RECEIPT");
|
|
|
|
if ( (ret = SendScpConfirmation(ssh)) < WS_SUCCESS) {
|
|
LogScpStateError("SEND_CONFIRMATION", ret);
|
|
break;
|
|
}
|
|
|
|
ssh->scpState = SCP_RECEIVE_CONFIRMATION;
|
|
continue;
|
|
|
|
case SCP_RECEIVE_CONFIRMATION_WITH_RECEIPT:
|
|
WLOG(WS_LOG_DEBUG, scpState,
|
|
"SCP_RECEIVE_CONFIRMATION_WITH_RECEIPT");
|
|
|
|
if ( (ret = ReceiveScpConfirmation(ssh)) < WS_SUCCESS) {
|
|
LogScpStateError("RECEIVE_CONFIRMATION_WITH_RECEIPT", ret);
|
|
break;
|
|
}
|
|
|
|
ssh->scpState = SCP_SEND_CONFIRMATION;
|
|
continue;
|
|
|
|
case SCP_RECEIVE_CONFIRMATION:
|
|
WLOG(WS_LOG_DEBUG, scpState, "SCP_RECEIVE_CONFIRMATION");
|
|
|
|
if ( (ret = ReceiveScpConfirmation(ssh)) < WS_SUCCESS) {
|
|
LogScpStateError("RECEIVE_CONFIRMATION", ret);
|
|
break;
|
|
}
|
|
|
|
ssh->scpState = ssh->scpNextState;
|
|
continue;
|
|
|
|
case SCP_TRANSFER:
|
|
WLOG(WS_LOG_DEBUG, scpState, "SCP_TRANSFER");
|
|
|
|
ssh->scpConfirm = ssh->ctx->scpSendCb(ssh,
|
|
ssh->scpRequestType, ssh->scpBasePath,
|
|
ssh->scpFileName, ssh->scpFileNameSz, &(ssh->scpMTime),
|
|
&(ssh->scpATime), &(ssh->scpFileMode),
|
|
ssh->scpFileOffset, &(ssh->scpFileSz),
|
|
ssh->scpFileBuffer + ssh->scpBufferedSz,
|
|
ssh->scpFileBufferSz - ssh->scpBufferedSz,
|
|
wolfSSH_GetScpSendCtx(ssh));
|
|
|
|
if (ssh->scpConfirm == WS_SCP_ENTER_DIR) {
|
|
ssh->scpState = SCP_SEND_ENTER_DIRECTORY;
|
|
continue;
|
|
|
|
} else if (ssh->scpConfirm == WS_SCP_EXIT_DIR) {
|
|
ssh->scpState = SCP_SEND_EXIT_DIRECTORY;
|
|
continue;
|
|
|
|
} else if (ssh->scpConfirm == WS_SCP_EXIT_DIR_FINAL) {
|
|
ssh->scpState = SCP_SEND_EXIT_DIRECTORY_FINAL;
|
|
continue;
|
|
|
|
} else if (ssh->scpConfirm == WS_SCP_COMPLETE) {
|
|
ssh->scpState = SCP_DONE;
|
|
continue;
|
|
|
|
} else if (ssh->scpConfirm == WS_SCP_ABORT) {
|
|
#if !defined(NO_FILESYSTEM) && \
|
|
!defined(WOLFSSH_SCP_USER_CALLBACKS)
|
|
/* drain any partial recursive dir stack so a later exec on
|
|
* this connection starts from a fresh root, not a stale
|
|
* handle left by the aborted walk */
|
|
ScpSendCtx* sendCtx =
|
|
(ScpSendCtx*)wolfSSH_GetScpSendCtx(ssh);
|
|
if (sendCtx != NULL)
|
|
ScpSendCtxFreeDirs(ssh->fs, sendCtx, ssh->ctx->heap);
|
|
#endif
|
|
ssh->scpState = SCP_SEND_CONFIRMATION;
|
|
ssh->scpNextState = SCP_DONE;
|
|
continue;
|
|
|
|
} else if (ssh->scpConfirm >= 0) {
|
|
|
|
/* transfer buffered file data */
|
|
ssh->scpBufferedSz += ssh->scpConfirm;
|
|
ssh->scpConfirm = WS_SCP_CONTINUE;
|
|
|
|
/* only send timestamp and file header first time */
|
|
if (ssh->scpFileOffset == 0) {
|
|
if (ssh->scpTimestamp == 1) {
|
|
ssh->scpState = SCP_SEND_TIMESTAMP;
|
|
} else {
|
|
ssh->scpState = SCP_SEND_FILE_HEADER;
|
|
}
|
|
} else {
|
|
ssh->scpState = SCP_SEND_FILE;
|
|
}
|
|
continue;
|
|
|
|
} else {
|
|
|
|
/* error */
|
|
ret = ssh->scpConfirm;
|
|
break;
|
|
}
|
|
|
|
case SCP_SEND_TIMESTAMP:
|
|
WLOG(WS_LOG_DEBUG, scpState, "SCP_SEND_TIMESTAMP");
|
|
|
|
if ( (ret = SendScpTimestamp(ssh)) < WS_SUCCESS) {
|
|
LogScpStateError("SEND_TIMESTAMP", ret);
|
|
break;
|
|
}
|
|
|
|
ssh->scpState = SCP_RECEIVE_CONFIRMATION;
|
|
ssh->scpNextState = SCP_SEND_FILE_HEADER;
|
|
continue;
|
|
|
|
case SCP_SEND_ENTER_DIRECTORY:
|
|
WLOG(WS_LOG_DEBUG, scpState, "SCP_SEND_ENTER_DIRECTORY");
|
|
|
|
if ( (ret = SendScpEnterDirectory(ssh)) < WS_SUCCESS) {
|
|
LogScpStateError("SEND_ENTER_DIRECTORY", ret);
|
|
break;
|
|
}
|
|
|
|
ssh->scpState = SCP_RECEIVE_CONFIRMATION;
|
|
ssh->scpNextState = SCP_TRANSFER;
|
|
continue;
|
|
|
|
case SCP_SEND_EXIT_DIRECTORY:
|
|
WLOG(WS_LOG_DEBUG, scpState, "SCP_SEND_EXIT_DIRECTORY");
|
|
|
|
if ( (ret = SendScpExitDirectory(ssh)) < WS_SUCCESS) {
|
|
LogScpStateError("SEND_EXIT_DIRECTORY", ret);
|
|
break;
|
|
}
|
|
|
|
ssh->scpState = SCP_RECEIVE_CONFIRMATION;
|
|
ssh->scpNextState = SCP_TRANSFER;
|
|
continue;
|
|
|
|
case SCP_SEND_EXIT_DIRECTORY_FINAL:
|
|
WLOG(WS_LOG_DEBUG, scpState, "SCP_SEND_EXIT_DIRECTORY_FINAL");
|
|
|
|
if ( (ret = SendScpExitDirectory(ssh)) < WS_SUCCESS) {
|
|
LogScpStateError("SEND_EXIT_DIRECTORY", ret);
|
|
break;
|
|
}
|
|
|
|
ssh->scpState = SCP_RECEIVE_CONFIRMATION;
|
|
ssh->scpNextState = SCP_DONE;
|
|
continue;
|
|
case SCP_SEND_FILE_HEADER:
|
|
WLOG(WS_LOG_DEBUG, scpState, "SCP_SEND_FILE_HEADER");
|
|
|
|
if ( (ret = SendScpFileHeader(ssh)) < WS_SUCCESS) {
|
|
LogScpStateError("SEND_FILE_HEADER", ret);
|
|
break;
|
|
}
|
|
|
|
ssh->scpState = SCP_RECEIVE_CONFIRMATION;
|
|
ssh->scpNextState = SCP_SEND_FILE;
|
|
continue;
|
|
|
|
case SCP_SEND_FILE:
|
|
WLOG(WS_LOG_DEBUG, scpState, "SCP_SEND_FILE");
|
|
|
|
ret = ScpStreamSend(ssh, ssh->scpFileBuffer,
|
|
ssh->scpBufferedSz);
|
|
if (ret == WS_WANT_READ || ret == WS_WANT_WRITE) {
|
|
/* ScpStreamSend already drove the worker through any rekey
|
|
* or full window; a non-blocking want means the socket is
|
|
* not ready. Surface it for the caller to retry without
|
|
* closing the file mid-transfer. scpBufferedSz and
|
|
* scpFileOffset are preserved for the next call. */
|
|
break;
|
|
}
|
|
if (ret == WS_EXTDATA) {
|
|
_DumpExtendedData(ssh);
|
|
continue;
|
|
}
|
|
if (ret < 0) {
|
|
#if !defined(NO_FILESYSTEM) && \
|
|
!defined(WOLFSSH_SCP_USER_CALLBACKS)
|
|
/* if the socket send had a fatal error, try to close any
|
|
* open file descriptor before exit */
|
|
ScpSendCtx* sendCtx = NULL;
|
|
sendCtx = (ScpSendCtx*)wolfSSH_GetScpSendCtx(ssh);
|
|
if (sendCtx != NULL) {
|
|
WFCLOSE(ssh->fs, sendCtx->fp);
|
|
sendCtx->fp = NULL;
|
|
}
|
|
#endif
|
|
WLOG(WS_LOG_ERROR, scpError, "failed to send file", ret);
|
|
break;
|
|
}
|
|
|
|
ssh->scpFileOffset += ret;
|
|
if (ret != (int)ssh->scpBufferedSz) {
|
|
/* case where not all of buffer was sent */
|
|
WMEMMOVE(ssh->scpFileBuffer, ssh->scpFileBuffer + ret,
|
|
ssh->scpBufferedSz - ret);
|
|
}
|
|
ssh->scpBufferedSz -= ret;
|
|
ret = WS_SUCCESS;
|
|
|
|
if (ssh->scpBufferedSz > 0) {
|
|
/* There is still file data in the buffer to send,
|
|
* go ahead and try to send it by repeating this
|
|
* state. */
|
|
continue;
|
|
}
|
|
else if (ssh->scpFileOffset < ssh->scpFileSz) {
|
|
ssh->scpState = SCP_TRANSFER;
|
|
ssh->scpRequestType = WOLFSSH_SCP_CONTINUE_FILE_TRANSFER;
|
|
|
|
} else {
|
|
ssh->scpState = SCP_CONFIRMATION_WITH_RECEIPT;
|
|
if (ssh->scpIsRecursive) {
|
|
ssh->scpFileOffset = 0;
|
|
ssh->scpBufferedSz = 0;
|
|
ssh->scpATime = 0;
|
|
ssh->scpMTime = 0;
|
|
ssh->scpNextState = SCP_TRANSFER;
|
|
ssh->scpRequestType = WOLFSSH_SCP_RECURSIVE_REQUEST;
|
|
} else {
|
|
ssh->scpNextState = SCP_DONE;
|
|
}
|
|
}
|
|
|
|
continue;
|
|
|
|
default:
|
|
break;
|
|
|
|
} /* end switch */
|
|
|
|
} /* end while */
|
|
|
|
if (ret == WS_SUCCESS && ssh->scpState == SCP_DONE) {
|
|
/* Send SSH_MSG_CHANNEL_CLOSE */
|
|
ret = wolfSSH_stream_exit(ssh, 0);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int DoScpRequest(WOLFSSH* ssh)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
|
|
if (ssh == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
if (ssh->ctx->scpRecvCb == NULL) {
|
|
WLOG(WS_LOG_DEBUG, "scp error: receive callback is null, please set");
|
|
return WS_BAD_ARGUMENT;
|
|
}
|
|
|
|
while (ret == WS_SUCCESS && ssh->scpState != SCP_DONE) {
|
|
|
|
switch (ssh->scpRequestState) {
|
|
|
|
case SCP_PARSE_COMMAND:
|
|
WLOG(WS_LOG_DEBUG, scpState, "SCP_PARSE_COMMAND");
|
|
|
|
if ( (ret = ParseScpCommand(ssh)) < WS_SUCCESS) {
|
|
WLOG(WS_LOG_ERROR, scpError, "PARSE_COMMAND", ret);
|
|
break;
|
|
}
|
|
|
|
if (ssh->scpDirection == WOLFSSH_SCP_TO) {
|
|
ssh->scpRequestState = SCP_SINK;
|
|
ssh->scpState = SCP_SINK_BEGIN;
|
|
continue;
|
|
|
|
} else if (ssh->scpDirection == WOLFSSH_SCP_FROM) {
|
|
ssh->scpRequestState = SCP_SOURCE;
|
|
ssh->scpState = SCP_SOURCE_BEGIN;
|
|
continue;
|
|
|
|
} else {
|
|
ret = WS_SCP_CMD_E;
|
|
WLOG(WS_LOG_ERROR, scpError, "invalid command", ret);
|
|
break;
|
|
}
|
|
|
|
case SCP_SINK:
|
|
WLOG(WS_LOG_DEBUG, scpState, "SCP_SINK");
|
|
if ( (ret = DoScpSink(ssh)) < WS_SUCCESS) {
|
|
LogScpStateError("SCP_SINK", ret);
|
|
}
|
|
break;
|
|
|
|
case SCP_SOURCE:
|
|
WLOG(WS_LOG_DEBUG, scpState, "SCP_SOURCE");
|
|
if ( (ret = DoScpSource(ssh)) < WS_SUCCESS) {
|
|
LogScpStateError("SCP_SOURCE", ret);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS && ssh->scpState == SCP_DONE) {
|
|
byte buf[1];
|
|
|
|
/* Peer MUST send back a SSH_MSG_CHANNEL_CLOSE unless already
|
|
sent*/
|
|
ret = ScpStreamRead(ssh, buf, 1);
|
|
if (ret == WS_SOCKET_ERROR_E || ret == WS_CHANNEL_CLOSED) {
|
|
WLOG(WS_LOG_DEBUG, scpState, "Peer hung up, but SCP is done");
|
|
ret = WS_SUCCESS;
|
|
}
|
|
else if (ret != WS_EOF) {
|
|
WLOG(WS_LOG_DEBUG, scpState, "Did not receive EOF packet");
|
|
}
|
|
else {
|
|
ret = WS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Sets the error message that is sent back to the peer when an error or fatal
|
|
* confirmation message is sent. Expected to be used inside the scp
|
|
* callbacks.
|
|
*
|
|
* ssh - pointer to initialized WOLFSSH structure
|
|
* message - error message to be sent to peer, dynamically allocated, freed
|
|
* internally when WOLFSSH session is freed.
|
|
*
|
|
* Returns WS_SUCCESS on success, negative upon error
|
|
*/
|
|
WOLFSSH_API int wolfSSH_SetScpErrorMsg(WOLFSSH* ssh, const char* message)
|
|
{
|
|
char* value = NULL;
|
|
word32 valueSz = 0;
|
|
int ret = WS_SUCCESS;
|
|
|
|
if (ssh == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
valueSz = (word32)WSTRLEN(message) + 1;
|
|
if (valueSz > 0)
|
|
value = (char*)WMALLOC(valueSz + SCP_MIN_CONFIRM_SZ,
|
|
ssh->ctx->heap, DYNTYPE_STRING);
|
|
if (value == NULL)
|
|
ret = WS_MEMORY_E;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
/* leave room for cmd at beginning, add \n\0 at end */
|
|
WSTRNCPY(value + 1, message, valueSz);
|
|
*(value + valueSz) = '\n';
|
|
*(value + valueSz + 1) = '\0';
|
|
|
|
if (ssh->scpConfirmMsg != NULL) {
|
|
WFREE(ssh->scpConfirmMsg, ssh->ctx->heap, DYNTYPE_STRING);
|
|
ssh->scpConfirmMsg = NULL;
|
|
}
|
|
|
|
ssh->scpConfirmMsg = value;
|
|
ssh->scpConfirmMsgSz = valueSz + SCP_MIN_CONFIRM_SZ;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Determine if channel command sent in initial negotiation is scp.
|
|
* Return 1 if yes, 0 if no */
|
|
int ChannelCommandIsScp(WOLFSSH* ssh)
|
|
{
|
|
const char* cmd;
|
|
int ret = 0;
|
|
|
|
if (ssh == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
cmd = wolfSSH_GetSessionCommand(ssh);
|
|
if (cmd != NULL && WSTRLEN(cmd) >= 3) {
|
|
if (WSTRNCMP(cmd, "scp", 3) == 0)
|
|
ret = 1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Reads file mode from SCP header string, mode is prefixed by "C", ex: "C0644",
|
|
* places mode in ssh->scpFileMode.
|
|
*
|
|
* buf - buffer containing mode string
|
|
* bufSz - size of buffer
|
|
* inOutIdx - [IN/OUT] index into buffer, sets index after mode
|
|
*
|
|
* returns WS_SUCCESS on success, negative upon error
|
|
*/
|
|
static int GetScpFileMode(WOLFSSH* ssh, byte* buf, word32 bufSz,
|
|
word32* inOutIdx)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
word32 idx;
|
|
byte modeOctet[SCP_MODE_OCTET_LEN + 1];
|
|
int mode, i;
|
|
|
|
if (ssh == NULL || buf == NULL || inOutIdx == NULL ||
|
|
bufSz < (SCP_MODE_OCTET_LEN + 1))
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
idx = *inOutIdx;
|
|
|
|
/* skip leading "C" or "D" */
|
|
if ((buf[idx] != 'C' && buf[idx] != 'D') || (idx + 1 > bufSz))
|
|
return WS_BAD_ARGUMENT;
|
|
idx++;
|
|
|
|
WMEMCPY(modeOctet, buf + idx, SCP_MODE_OCTET_LEN);
|
|
modeOctet[SCP_MODE_OCTET_LEN] = '\0';
|
|
idx += SCP_MODE_OCTET_LEN;
|
|
|
|
/* convert octal string to int without mp_read_radix() */
|
|
mode = 0;
|
|
|
|
for (i = 0; i < SCP_MODE_OCTET_LEN; i++)
|
|
{
|
|
if (modeOctet[i] < '0' || modeOctet[i] > '7') {
|
|
ret = WS_BAD_ARGUMENT;
|
|
break;
|
|
}
|
|
mode <<= 3;
|
|
mode |= (modeOctet[i] - '0');
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
/* store file mode, masking off setuid/setgid/sticky bits from the
|
|
* peer-supplied value to match the send path */
|
|
ssh->scpFileMode = mode & WOLFSSH_MODE_MASK;
|
|
/* eat trailing space */
|
|
if (bufSz >= (word32)(idx +1))
|
|
idx++;
|
|
ret = WS_SUCCESS;
|
|
*inOutIdx = idx;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
#ifdef WOLFSSH_TEST_INTERNAL
|
|
int wolfSSH_TestScpGetFileMode(WOLFSSH* ssh, byte* buf, word32 bufSz,
|
|
word32* inOutIdx)
|
|
{
|
|
return GetScpFileMode(ssh, buf, bufSz, inOutIdx);
|
|
}
|
|
#endif /* WOLFSSH_TEST_INTERNAL */
|
|
|
|
|
|
/* Locates first space present in given string (buf) and sets inOutIdx
|
|
* to that offset.
|
|
*
|
|
* Returns WS_SUCCESS on success, or negative upon error */
|
|
static int FindSpaceInString(byte* buf, word32 bufSz, word32* inOutIdx)
|
|
{
|
|
word32 idx;
|
|
int spaceFound = 0;
|
|
|
|
if (buf == NULL || inOutIdx == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
idx = *inOutIdx;
|
|
|
|
while (idx < bufSz) {
|
|
if (buf[idx] == ' ') {
|
|
spaceFound = 1;
|
|
break;
|
|
}
|
|
idx++;
|
|
}
|
|
|
|
if (!spaceFound)
|
|
return WS_FATAL_ERROR;
|
|
|
|
*inOutIdx = idx;
|
|
|
|
return WS_SUCCESS;
|
|
}
|
|
|
|
/* Reads file size from beginning of string, expects space to be after,
|
|
* places size in ssh->scpFileSz.
|
|
*
|
|
* buf - buffer containing size string
|
|
* bufSz - size of buffer
|
|
* inOutIdx - [IN/OUT] index into buffer, increments index upon return
|
|
*
|
|
* returns WS_SUCCESS on success, negative upon error
|
|
*/
|
|
static int GetScpFileSize(WOLFSSH* ssh, byte* buf, word32 bufSz,
|
|
word32* inOutIdx)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
word32 idx, spaceIdx;
|
|
|
|
if (ssh == NULL || buf == NULL || inOutIdx == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
idx = *inOutIdx;
|
|
spaceIdx = idx;
|
|
|
|
if (FindSpaceInString(buf, bufSz, &spaceIdx) != WS_SUCCESS)
|
|
ret = WS_SCP_BAD_MSG_E;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
/* replace space with newline for atoi */
|
|
buf[spaceIdx] = '\n';
|
|
ssh->scpFileSz = atoi((char *)(buf + idx));
|
|
|
|
/* restore space, increment idx to space */
|
|
buf[spaceIdx] = ' ';
|
|
idx = spaceIdx;
|
|
|
|
/* eat trailing space */
|
|
if (bufSz >= (word32)(idx + 1))
|
|
idx++;
|
|
|
|
*inOutIdx = idx;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Reads file name from beginning of string, expects string to be
|
|
* null terminated.
|
|
*
|
|
* Places null-terminated file name in ssh->scpFileName and file name
|
|
* length (not including null terminator) in ssh->scpFileNameSz.
|
|
*
|
|
* buf - buffer containing size string
|
|
* bufSz - size of buffer
|
|
* inOutIdx - [IN/OUT] index into buffer, increments index upon return
|
|
*
|
|
* returns WS_SUCCESS on success, negative upon error
|
|
*/
|
|
static int GetScpFileName(WOLFSSH* ssh, byte* buf, word32 bufSz,
|
|
word32* inOutIdx)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
word32 idx, len;
|
|
const char* fileName;
|
|
|
|
if (ssh == NULL || buf == NULL || inOutIdx == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
idx = *inOutIdx;
|
|
len = (word32)WSTRLEN((char*)(buf + idx));
|
|
|
|
if ((idx + len) > bufSz)
|
|
ret = WS_SCP_CMD_E;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
word32 i;
|
|
|
|
fileName = (const char*)(buf + idx);
|
|
|
|
if (len == 0 ||
|
|
(len == 1 && fileName[0] == '.') ||
|
|
(len == 2 && fileName[0] == '.' && fileName[1] == '.')) {
|
|
WLOG(WS_LOG_ERROR, "scp: invalid file name component received");
|
|
wolfSSH_SetScpErrorMsg(ssh, "invalid file name");
|
|
return WS_SCP_BAD_MSG_E;
|
|
}
|
|
|
|
for (i = 0; i < len; i++) {
|
|
char c = fileName[i];
|
|
|
|
if (c == '/' || c == '\\'
|
|
#if defined(USE_WINDOWS_API) || defined(WOLFSSL_NUCLEUS)
|
|
|| c == ':'
|
|
#endif
|
|
) {
|
|
WLOG(WS_LOG_ERROR, "scp: invalid file name component received");
|
|
wolfSSH_SetScpErrorMsg(ssh, "invalid file name");
|
|
return WS_SCP_BAD_MSG_E;
|
|
}
|
|
}
|
|
|
|
if (ssh->scpFileName != NULL) {
|
|
WFREE(ssh->scpFileName, ssh->ctx->heap, DYNTYPE_STRING);
|
|
ssh->scpFileName = NULL;
|
|
ssh->scpFileNameSz = 0;
|
|
}
|
|
|
|
ssh->scpFileName = (char*)WMALLOC(len + 1, ssh->ctx->heap,
|
|
DYNTYPE_STRING);
|
|
if (ssh->scpFileName == NULL)
|
|
ret = WS_MEMORY_E;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
WMEMCPY(ssh->scpFileName, buf + idx, len);
|
|
ssh->scpFileName[len] = '\0';
|
|
ssh->scpFileNameSz = len;
|
|
|
|
*inOutIdx = idx;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Reads timestamp information (access, modification) from beginning
|
|
* of string, expects space to be after each time value:
|
|
*
|
|
* T<modification_secs> 0 <access_secs> 0
|
|
*
|
|
* Places modification time in ssh->scpMTime and access time in
|
|
* ssh->scpATime variables.
|
|
*
|
|
* buf - buffer containing size string
|
|
* bufSz - size of buffer
|
|
* inOutIdx - [IN/OUT] index into buffer, increments index upon return
|
|
*
|
|
* returns WS_SUCCESS on success, negative upon error
|
|
*/
|
|
static int GetScpTimestamp(WOLFSSH* ssh, byte* buf, word32 bufSz,
|
|
word32* inOutIdx)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
word32 idx, spaceIdx;
|
|
|
|
if (ssh == NULL || buf == NULL || inOutIdx == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
idx = *inOutIdx;
|
|
spaceIdx = idx;
|
|
|
|
/* skip leading "T" */
|
|
if (buf[idx] != 'T' || (idx + 1) > bufSz)
|
|
return WS_SCP_TIMESTAMP_E;
|
|
idx++;
|
|
|
|
if (FindSpaceInString(buf, bufSz, &spaceIdx) != WS_SUCCESS)
|
|
ret = WS_SCP_TIMESTAMP_E;
|
|
|
|
/* read modification time */
|
|
if (ret == WS_SUCCESS) {
|
|
/* replace space with newline for atoi */
|
|
buf[spaceIdx] = '\n';
|
|
ssh->scpMTime = atoi((char*)(buf + idx));
|
|
|
|
/* restore space, increment idx past it */
|
|
buf[spaceIdx] = ' ';
|
|
if (spaceIdx + 1 < bufSz) {
|
|
idx = spaceIdx + 1;
|
|
} else {
|
|
ret = WS_SCP_TIMESTAMP_E;
|
|
}
|
|
}
|
|
|
|
/* skip '0 ' */
|
|
if (ret == WS_SUCCESS) {
|
|
if (buf[idx] != '0') {
|
|
ret = WS_SCP_TIMESTAMP_E;
|
|
}
|
|
else {
|
|
idx++;
|
|
if (idx >= bufSz || buf[idx] != ' ') {
|
|
ret = WS_SCP_TIMESTAMP_E;
|
|
}
|
|
else {
|
|
idx++;
|
|
if (idx >= bufSz)
|
|
ret = WS_SCP_TIMESTAMP_E;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* read access time */
|
|
if (ret == WS_SUCCESS) {
|
|
spaceIdx = idx;
|
|
if (FindSpaceInString(buf, bufSz, &spaceIdx) != WS_SUCCESS)
|
|
ret = WS_SCP_TIMESTAMP_E;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
/* replace space with newline for atoi */
|
|
buf[spaceIdx] = '\n';
|
|
ssh->scpATime = atoi((char*)(buf + idx));
|
|
|
|
/* restore space, increment idx past it */
|
|
buf[spaceIdx] = ' ';
|
|
if (spaceIdx + 1 < bufSz) {
|
|
idx = spaceIdx + 1;
|
|
} else {
|
|
ret = WS_SCP_TIMESTAMP_E;
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
*inOutIdx = idx;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* checks for if directory is being renamed in command
|
|
*
|
|
* returns WS_SUCCESS on success
|
|
*/
|
|
static int ScpCheckForRename(WOLFSSH* ssh, int cmdSz)
|
|
{
|
|
/* case of file, not directory */
|
|
char buf[DEFAULT_SCP_MSG_SZ];
|
|
int sz = (int)WSTRLEN(ssh->scpBasePath);
|
|
int idx;
|
|
|
|
if (sz >= DEFAULT_SCP_MSG_SZ) {
|
|
return WS_BUFFER_E;
|
|
}
|
|
|
|
if (cmdSz + 4 > DEFAULT_SCP_MSG_SZ) {
|
|
return WS_BUFFER_E;
|
|
}
|
|
|
|
WSTRNCPY(buf, ssh->scpBasePath, cmdSz);
|
|
buf[sz] = '\0';
|
|
WSTRNCAT(buf, "/..", DEFAULT_SCP_MSG_SZ);
|
|
|
|
idx = wolfSSH_CleanPath(ssh, buf, DEFAULT_SCP_MSG_SZ);
|
|
if (idx < 0) {
|
|
return WS_FATAL_ERROR;
|
|
}
|
|
idx = idx + 1; /* +1 for delimiter */
|
|
#ifdef WOLFSSL_NUCLEUS
|
|
/* no delimiter to skip in case of at base address */
|
|
if (idx == 4) { /* case of 4 for drive letter plus ":\" + 1 */
|
|
idx--;
|
|
}
|
|
#else
|
|
/* allow placing file at base address ':/' */
|
|
if (WSTRLEN(buf) == 1 && buf[0] == WS_DELIM) {
|
|
idx--; /* no delimiter at base */
|
|
}
|
|
#endif
|
|
if (idx > cmdSz || idx > sz) {
|
|
return WS_BUFFER_E;
|
|
}
|
|
|
|
sz = sz - idx; /* size of file name */
|
|
if (ssh->scpFileNameSz < (word32)sz || ssh->scpFileName == NULL) {
|
|
if (ssh->scpFileName != NULL) {
|
|
WFREE(ssh->scpFileName, ssh->ctx->heap, DYNTYPE_STRING);
|
|
ssh->scpFileNameSz = 0;
|
|
}
|
|
ssh->scpFileName = (char*)WMALLOC(sz + 1, ssh->ctx->heap,
|
|
DYNTYPE_STRING);
|
|
if (ssh->scpFileName == NULL) {
|
|
WLOG(WS_LOG_DEBUG, scpError, "memory error creating file name",
|
|
WS_MEMORY_E);
|
|
ssh->scpBasePath = NULL;
|
|
return WS_MEMORY_E;
|
|
}
|
|
ssh->scpFileName[0] = '\0'; /* make sure null terminated for check */
|
|
}
|
|
|
|
/* are we not going down into a directory? i.e. last char is delimiter */
|
|
if (ssh->scpBasePath[WSTRLEN(ssh->scpBasePath) - 1] != '/' &&
|
|
ssh->scpBasePath[WSTRLEN(ssh->scpBasePath) - 1] != '\\') {
|
|
|
|
/* is the last name in the path different then fileName found */
|
|
if (WSTRNCMP(ssh->scpFileName, ssh->scpBasePath + idx, sz) != 0) {
|
|
WLOG(WS_LOG_DEBUG, "scp: renaming from %s to %s",
|
|
ssh->scpFileName, ssh->scpBasePath + idx);
|
|
ssh->scpFileReName = ssh->scpFileName;
|
|
WSTRNCPY(ssh->scpFileName, ssh->scpBasePath + idx, sz + 1);
|
|
ssh->scpFileName[sz] = '\0';
|
|
ssh->scpFileNameSz = sz;
|
|
*((char*)ssh->scpBasePath + idx) = '\0';
|
|
}
|
|
}
|
|
|
|
return WS_SUCCESS;
|
|
}
|
|
|
|
|
|
/* helps with checking if the base path is a directory or file
|
|
* returns WS_SUCCESS on success */
|
|
static int ParseBasePathHelper(WOLFSSH* ssh, int cmdSz)
|
|
{
|
|
int ret;
|
|
ret = ScpCheckForRename(ssh, cmdSz);
|
|
#ifndef NO_FILESYSTEM
|
|
if (ret == WS_SUCCESS) {
|
|
ScpSendCtx ctx;
|
|
|
|
WMEMSET(&ctx, 0, sizeof(ScpSendCtx));
|
|
|
|
if (ScpPushDir(ssh->fs, &ctx, ssh->scpBasePath, ssh->ctx->heap) != WS_SUCCESS) {
|
|
WLOG(WS_LOG_DEBUG, "scp : issue opening base dir");
|
|
ssh->error = WS_INVALID_PATH_E;
|
|
ret = WS_FATAL_ERROR;
|
|
}
|
|
else {
|
|
ret = ScpPopDir(ssh->fs, &ctx, ssh->ctx->heap);
|
|
if (ret == WS_SCP_DIR_STACK_EMPTY_E) {
|
|
ret = WS_SUCCESS; /* is ok to empty the directory stack here */
|
|
}
|
|
}
|
|
}
|
|
#endif /* NO_FILESYSTEM */
|
|
|
|
/* default case of directory */
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*int GetScpBasePath(WOLFSSH* ssh, const char* in)
|
|
{
|
|
int ret = WS_SUCCESS, len;
|
|
|
|
if (ssh == NULL || in == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
if (ssh->scpBasePath != NULL) {
|
|
WFREE(ssh->scpBasePath, ssh->ctx->heap, DYNTYPE_STRING);
|
|
ssh->spBasePath = NULL;
|
|
}
|
|
|
|
len = (int)WSTRLEN(in);
|
|
ssh->scpBasePath = (char*)WMALLOC(len, ssh->ctx->heap, DYNTYPE_STRING);
|
|
|
|
if (ssh->scpBasePath == NULL) {
|
|
ret = WS_MEMORY_E;
|
|
} else {
|
|
WMEMCPY(ssh->scpBasePath, in, len);
|
|
ssh->scpBasePath[len] = '\0';
|
|
}
|
|
|
|
return ret;
|
|
}*/
|
|
|
|
/* Parse scp command received, currently only looks for and stores the
|
|
* SCP base path being written to.
|
|
*
|
|
* return WS_SUCCESS on success, negative upon error
|
|
*/
|
|
int ParseScpCommand(WOLFSSH* ssh)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
const char* cmd;
|
|
word32 cmdSz, idx = 0;
|
|
|
|
if (ssh == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
cmd = wolfSSH_GetSessionCommand(ssh);
|
|
if (cmd == NULL)
|
|
ret = WS_SCP_CMD_E;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
cmdSz = (word32)WSTRLEN(cmd);
|
|
|
|
while ((idx + 1 < cmdSz) && (ret == WS_SUCCESS)) {
|
|
|
|
if (cmd[idx] == ' ' && cmd[idx + 1] == '-' && (idx + 2 < cmdSz)) {
|
|
idx = idx + 2;
|
|
|
|
switch (cmd[idx]) {
|
|
case 'r':
|
|
ssh->scpIsRecursive = 1;
|
|
ssh->scpRequestType = WOLFSSH_SCP_RECURSIVE_REQUEST;
|
|
break;
|
|
|
|
case 'p':
|
|
ssh->scpTimestamp = 1;
|
|
break;
|
|
|
|
case 't':
|
|
ssh->scpDirection = WOLFSSH_SCP_TO;
|
|
ssh->scpBasePathSz = cmdSz + WOLFSSH_MAX_FILENAME;
|
|
ssh->scpBasePathDynamic = (char*)WMALLOC(
|
|
ssh->scpBasePathSz,
|
|
ssh->ctx->heap, DYNTYPE_BUFFER);
|
|
if (ssh->scpBasePathDynamic == NULL) {
|
|
return WS_MEMORY_E;
|
|
}
|
|
WMEMSET(ssh->scpBasePathDynamic, 0, ssh->scpBasePathSz);
|
|
if (idx + 2 < cmdSz) {
|
|
/* skip space */
|
|
idx += 2;
|
|
ssh->scpBasePath = ssh->scpBasePathDynamic;
|
|
WMEMCPY(ssh->scpBasePathDynamic, cmd + idx,
|
|
cmdSz - idx);
|
|
if (wolfSSH_CleanPath(ssh,
|
|
ssh->scpBasePathDynamic,
|
|
ssh->scpBasePathSz) < 0) {
|
|
ret = WS_FATAL_ERROR;
|
|
}
|
|
else {
|
|
ret = ParseBasePathHelper(ssh, cmdSz);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'f':
|
|
ssh->scpDirection = WOLFSSH_SCP_FROM;
|
|
ssh->scpBasePathSz = cmdSz + WOLFSSH_MAX_FILENAME;
|
|
ssh->scpBasePathDynamic = (char*)WMALLOC(
|
|
ssh->scpBasePathSz,
|
|
ssh->ctx->heap, DYNTYPE_BUFFER);
|
|
if (ssh->scpBasePathDynamic == NULL) {
|
|
return WS_MEMORY_E;
|
|
}
|
|
WMEMSET(ssh->scpBasePathDynamic, 0, ssh->scpBasePathSz);
|
|
if (idx + 2 < cmdSz) {
|
|
/* skip space */
|
|
idx += 2;
|
|
ssh->scpBasePath = ssh->scpBasePathDynamic;
|
|
WMEMCPY(ssh->scpBasePathDynamic, cmd + idx,
|
|
cmdSz - idx);
|
|
if (wolfSSH_CleanPath(ssh,
|
|
ssh->scpBasePathDynamic,
|
|
ssh->scpBasePathSz) < 0)
|
|
ret = WS_FATAL_ERROR;
|
|
}
|
|
break;
|
|
} /* end switch */
|
|
}
|
|
idx++;
|
|
} /* end while */
|
|
|
|
if (ssh->scpDirection != WOLFSSH_SCP_TO &&
|
|
ssh->scpDirection != WOLFSSH_SCP_FROM) {
|
|
ret = WS_SCP_CMD_E;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Reads and parses SCP protocol control messages
|
|
*
|
|
* Reads up to DEFAULT_SCP_MSG_SZ characters and null-terminates the string.
|
|
* If string is greater than DEFAULT_SCP_MSG_SZ, then only
|
|
* DEFAULT_SCP_MSG_SZ-1 characters are read and string is NULL-terminated.
|
|
*
|
|
* returns WS_SUCCESS on success, negative upon error
|
|
*/
|
|
int ReceiveScpMessage(WOLFSSH* ssh)
|
|
{
|
|
int sz = 0, ret = WS_SUCCESS;
|
|
word32 idx = 0;
|
|
byte* buf;
|
|
|
|
if (ssh == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
/* create persistent msg buffer in case of nonblocking */
|
|
if (ssh->scpRecvMsg == NULL) {
|
|
ssh->scpRecvMsg = (char*)WMALLOC(DEFAULT_SCP_MSG_SZ, ssh->ctx->heap,
|
|
DYNTYPE_STRING);
|
|
if (ssh->scpRecvMsg == NULL) {
|
|
return WS_MEMORY_E;
|
|
}
|
|
ssh->scpRecvMsgSz = 0;
|
|
}
|
|
buf = (byte*)ssh->scpRecvMsg;
|
|
|
|
/* keep reading until newline found */
|
|
do {
|
|
int err;
|
|
word32 lastChannel = 0;
|
|
|
|
if (ssh->scpRecvMsgSz >= DEFAULT_SCP_MSG_SZ - 1) {
|
|
WLOG(WS_LOG_ERROR, "scp: buffer not big enough to recv message");
|
|
return WS_BUFFER_E;
|
|
}
|
|
|
|
/* Flush queued output before polling. A KEXINIT enqueued by a rekey
|
|
* would otherwise sit unsent while wolfSSH_worker runs DoReceive before
|
|
* its own flush, deadlocking against a peer that waits for our
|
|
* KEXINIT. */
|
|
if (wolfSSH_OutputPending(ssh)) {
|
|
ret = wolfSSH_SendPacket(ssh);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
/* If channel data is already buffered, read it directly rather than
|
|
* polling the socket. A control message delivered into the channel
|
|
* buffer while a rekey was completing leaves wolfSSH_worker returning
|
|
* the rekey status (not WS_CHAN_RXD), so without this the buffered
|
|
* message is never read and the next worker blocks on the socket. */
|
|
if (wolfSSH_stream_peek(ssh, NULL, 1) > 0) {
|
|
sz = wolfSSH_stream_read(ssh, buf + ssh->scpRecvMsgSz,
|
|
DEFAULT_SCP_MSG_SZ - ssh->scpRecvMsgSz);
|
|
/* match the WS_CHAN_RXD branch below: return on a non-positive
|
|
* read so a hypothetical zero cannot re-loop this peek path */
|
|
if (sz <= 0)
|
|
return sz;
|
|
ssh->scpRecvMsgSz += sz;
|
|
sz = ssh->scpRecvMsgSz;
|
|
continue;
|
|
}
|
|
|
|
err = wolfSSH_worker(ssh, &lastChannel);
|
|
if (err < 0) {
|
|
int rc;
|
|
|
|
rc = wolfSSH_get_error(ssh);
|
|
switch (rc) {
|
|
case WS_CHAN_RXD:
|
|
sz = wolfSSH_ChannelIdRead(ssh, lastChannel,
|
|
buf + ssh->scpRecvMsgSz,
|
|
DEFAULT_SCP_MSG_SZ - ssh->scpRecvMsgSz);
|
|
if (sz <= 0) {
|
|
return sz;
|
|
}
|
|
ssh->scpRecvMsgSz += sz;
|
|
sz = ssh->scpRecvMsgSz;
|
|
break;
|
|
|
|
case WS_EXTDATA:
|
|
_DumpExtendedData(ssh);
|
|
break;
|
|
|
|
case WS_WINDOW_FULL:
|
|
case WS_REKEYING:
|
|
continue;
|
|
case WS_CHANNEL_CLOSED:
|
|
return WS_EOF;
|
|
default:
|
|
return err;
|
|
}
|
|
}
|
|
|
|
/* check if wolfSSH_worker returns 0 from handling a channel eof */
|
|
if (err == 0) {
|
|
WOLFSSH_CHANNEL* channel;
|
|
channel = wolfSSH_ChannelFind(ssh, lastChannel, WS_CHANNEL_ID_SELF);
|
|
if (channel == NULL) {
|
|
ret = WS_INVALID_CHANID;
|
|
}
|
|
else if (wolfSSH_ChannelGetEof(channel)) {
|
|
return WS_EOF;
|
|
}
|
|
}
|
|
} while (sz == 0 || buf[sz - 1] != 0x0a);
|
|
|
|
/* null-terminate request, replace newline */
|
|
if (sz > 0) {
|
|
buf[sz - 1] = '\0';
|
|
}
|
|
|
|
switch (buf[0]) {
|
|
case 'C':
|
|
FALL_THROUGH;
|
|
|
|
case 'D':
|
|
if (buf[0] == 'C') {
|
|
WLOG(WS_LOG_DEBUG, "scp: Receiving file: %s\n", buf);
|
|
ssh->scpMsgType = WOLFSSH_SCP_MSG_FILE;
|
|
} else {
|
|
WLOG(WS_LOG_DEBUG, "scp: Receiving directory: %s\n", buf);
|
|
ssh->scpMsgType = WOLFSSH_SCP_MSG_DIR;
|
|
}
|
|
|
|
if ((ret = GetScpFileMode(ssh, buf, sz, &idx)) != WS_SUCCESS)
|
|
break;
|
|
|
|
if ((ret = GetScpFileSize(ssh, buf, sz, &idx)) != WS_SUCCESS)
|
|
break;
|
|
|
|
if (ssh->scpFileReName == NULL) {
|
|
ret = GetScpFileName(ssh, buf, sz, &idx);
|
|
}
|
|
else {
|
|
ssh->scpFileReName = NULL;
|
|
}
|
|
break;
|
|
|
|
case 'E':
|
|
ssh->scpMsgType = WOLFSSH_SCP_MSG_END_DIR;
|
|
ssh->scpFileState = WOLFSSH_SCP_END_DIR;
|
|
break;
|
|
|
|
case 'T':
|
|
WLOG(WS_LOG_DEBUG, "scp: Receiving timestamp: %s\n", buf);
|
|
ssh->scpMsgType = WOLFSSH_SCP_MSG_TIME;
|
|
|
|
/* parse access and modification times */
|
|
ret = GetScpTimestamp(ssh, buf, sz, &idx);
|
|
break;
|
|
|
|
default:
|
|
ret = WS_SCP_BAD_MSG_E;
|
|
WLOG(WS_LOG_DEBUG, "scp: Received invalid message\n");
|
|
break;
|
|
}
|
|
|
|
ssh->scpRecvMsgSz = 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
int ReceiveScpFile(WOLFSSH* ssh)
|
|
{
|
|
int partSz, ret = WS_SUCCESS;
|
|
|
|
if (ssh == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
/* We don't want to over-read the buffer. The file data is
|
|
* terminated by the sender with a nul which is checked later. */
|
|
partSz = min(ssh->scpFileSz - ssh->scpFileOffset, DEFAULT_SCP_BUFFER_SZ);
|
|
|
|
/* don't even bother reading if read size is 0 */
|
|
if (partSz == 0) return ret;
|
|
|
|
if (ssh->scpFileBuffer == NULL) {
|
|
ssh->scpFileBuffer = (byte*)WMALLOC(DEFAULT_SCP_BUFFER_SZ,
|
|
ssh->ctx->heap, DYNTYPE_BUFFER);
|
|
if (ssh->scpFileBuffer == NULL)
|
|
ret = WS_MEMORY_E;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ret = ScpStreamRead(ssh, ssh->scpFileBuffer, partSz);
|
|
if (ret > 0) {
|
|
ssh->scpFileBufferSz = ret;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int SendScpConfirmation(WOLFSSH* ssh)
|
|
{
|
|
char* msg;
|
|
int msgSz, ret = WS_SUCCESS;
|
|
char defaultMsg[2] = { 0x00, 0x00 };
|
|
|
|
if (ssh == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
if (ssh->scpConfirmMsg != NULL &&
|
|
ssh->scpConfirmMsgSz > SCP_MIN_CONFIRM_SZ &&
|
|
ssh->scpConfirm == WS_SCP_ABORT) {
|
|
msg = ssh->scpConfirmMsg;
|
|
} else {
|
|
msg = defaultMsg;
|
|
}
|
|
|
|
switch (ssh->scpConfirm) {
|
|
case WS_SCP_ABORT:
|
|
msg[0] = SCP_CONFIRM_ERR;
|
|
break;
|
|
|
|
case WS_SCP_CONTINUE:
|
|
/* default to ok confirmation */
|
|
FALL_THROUGH;
|
|
|
|
default:
|
|
msg[0] = SCP_CONFIRM_OK;
|
|
break;
|
|
}
|
|
|
|
/* skip first byte for accurate strlen, may be 0 */
|
|
msgSz = (int)XSTRLEN(msg + 1) + 1;
|
|
ret = ScpStreamSend(ssh, (byte*)msg, msgSz);
|
|
if (ret == msgSz && ssh->scpConfirm != WS_SCP_ABORT) {
|
|
ret = WS_SUCCESS;
|
|
WLOG(WS_LOG_DEBUG, "scp: sent confirmation (code: %d)", msg[0]);
|
|
|
|
if (ssh->scpConfirmMsg != NULL) {
|
|
WFREE(ssh->scpConfirmMsg, ssh->ctx->heap, DYNTYPE_STRING);
|
|
ssh->scpConfirmMsg = NULL;
|
|
ssh->scpConfirmMsgSz = 0;
|
|
}
|
|
}
|
|
/* leave a non-blocking want for the caller to retry; a real short send or a
|
|
* peer abort is fatal (see SendScpTimestamp) */
|
|
else if (ret != WS_WANT_READ && ret != WS_WANT_WRITE) {
|
|
ret = WS_FATAL_ERROR;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int ReceiveScpConfirmation(WOLFSSH* ssh)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
int msgSz;
|
|
byte msg[DEFAULT_SCP_MSG_SZ + 1];
|
|
|
|
if (ssh == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
WMEMSET(msg, 0, sizeof(msg));
|
|
msgSz = ScpStreamRead(ssh, msg, DEFAULT_SCP_MSG_SZ);
|
|
|
|
if (msgSz < 0) {
|
|
if (wolfSSH_get_error(ssh) == WS_EXTDATA)
|
|
_DumpExtendedData(ssh);
|
|
else
|
|
ret = msgSz;
|
|
} else if (msgSz > 1) {
|
|
/* null terminate */
|
|
msg[msgSz] = 0x00;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
switch (msg[0]) {
|
|
case SCP_CONFIRM_OK:
|
|
break;
|
|
case SCP_CONFIRM_ERR:
|
|
FALL_THROUGH;
|
|
case SCP_CONFIRM_FATAL:
|
|
FALL_THROUGH;
|
|
default:
|
|
WLOG(WS_LOG_ERROR,
|
|
"scp error: peer sent error confirmation (code: %d)",
|
|
msg[0]);
|
|
ret = WS_FATAL_ERROR;
|
|
|
|
/* SCP peer signaled a failure, propagate the error back
|
|
* to the caller. If not set here WS_CHAN_RXD could be
|
|
* returned. */
|
|
ssh->error = ret;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* allow SCP callback handlers whether user or not */
|
|
|
|
/* install SCP recv callback */
|
|
void wolfSSH_SetScpRecv(WOLFSSH_CTX* ctx, WS_CallbackScpRecv cb)
|
|
{
|
|
if (ctx)
|
|
ctx->scpRecvCb = cb;
|
|
}
|
|
|
|
|
|
/* install SCP recv context */
|
|
void wolfSSH_SetScpRecvCtx(WOLFSSH* ssh, void *ctx)
|
|
{
|
|
if (ssh)
|
|
ssh->scpRecvCtx = ctx;
|
|
}
|
|
|
|
|
|
/* get SCP recv context */
|
|
void* wolfSSH_GetScpRecvCtx(WOLFSSH* ssh)
|
|
{
|
|
if (ssh)
|
|
return ssh->scpRecvCtx;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* install SCP send callback */
|
|
void wolfSSH_SetScpSend(WOLFSSH_CTX* ctx, WS_CallbackScpSend cb)
|
|
{
|
|
if (ctx)
|
|
ctx->scpSendCb = cb;
|
|
}
|
|
|
|
|
|
/* install SCP send context */
|
|
void wolfSSH_SetScpSendCtx(WOLFSSH* ssh, void *ctx)
|
|
{
|
|
if (ssh)
|
|
ssh->scpSendCtx = ctx;
|
|
}
|
|
|
|
|
|
/* get SCP send context */
|
|
void* wolfSSH_GetScpSendCtx(WOLFSSH* ssh)
|
|
{
|
|
if (ssh)
|
|
return ssh->scpSendCtx;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
#ifndef NO_WOLFSSH_CLIENT
|
|
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 char* MakeScpCmd(const char* name, char dir, void* heap)
|
|
{
|
|
char* cmd;
|
|
int sz;
|
|
|
|
sz = WSNPRINTF(NULL, 0, "scp -%c %s", dir, name) + 1;
|
|
if (sz <= 0) {
|
|
return NULL;
|
|
}
|
|
cmd = (char*)WMALLOC(sz, heap, DYNTYPE_STRING);
|
|
if (cmd == NULL) {
|
|
return NULL;
|
|
}
|
|
sz = WSNPRINTF(cmd, sz, "scp -%c %s", dir, name);
|
|
if (sz <= 0) {
|
|
WFREE(cmd, heap, DYNTYPE_STRING);
|
|
return NULL;
|
|
}
|
|
|
|
return cmd;
|
|
}
|
|
|
|
|
|
int wolfSSH_SCP_to(WOLFSSH* ssh, const char* src, const char* dst)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
|
|
/* dst is passed to the server in the scp -t command */
|
|
/* src is used locally to fopen and read for copy to */
|
|
|
|
if (ssh == NULL || src == NULL || dst == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
if (ssh->scpState == SCP_SETUP) {
|
|
char* cmd = MakeScpCmd(dst, 't', ssh->ctx->heap);
|
|
if (cmd == NULL) {
|
|
WLOG(WS_LOG_SCP, "Cannot allocate scp command");
|
|
ssh->error = WS_MEMORY_E;
|
|
return WS_ERROR;
|
|
}
|
|
|
|
ssh->scpBasePath = src;
|
|
ret = wolfSSH_SCP_connect(ssh, (byte*)cmd);
|
|
if (ret == WS_SUCCESS) {
|
|
ssh->scpState = SCP_SOURCE_BEGIN;
|
|
ssh->scpRequestState = SCP_SOURCE;
|
|
}
|
|
if (cmd) {
|
|
WFREE(cmd, ssh->ctx->heap, DYNTYPE_STRING);
|
|
}
|
|
}
|
|
if (ssh->scpState != SCP_SETUP) {
|
|
if (ret == WS_SUCCESS) {
|
|
ret = DoScpSource(ssh);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int wolfSSH_SCP_from(WOLFSSH* ssh, const char* src, const char* dst)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
|
|
/* src is passed to the server in the scp -f command */
|
|
/* dst is used locally to fopen and write for copy from */
|
|
|
|
if (ssh == NULL || src == NULL || dst == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
if (ssh->scpState == SCP_SETUP) {
|
|
char* cmd = MakeScpCmd(src, 'f', ssh->ctx->heap);
|
|
if (cmd == NULL) {
|
|
WLOG(WS_LOG_SCP, "Cannot allocate scp command");
|
|
ssh->error = WS_MEMORY_E;
|
|
return WS_ERROR;
|
|
}
|
|
|
|
ssh->scpBasePath = dst;
|
|
ret = wolfSSH_SCP_connect(ssh, (byte*)cmd);
|
|
if (ret == WS_SUCCESS) {
|
|
word32 srcSz = (word32)WSTRLEN(src);
|
|
ret = ParseBasePathHelper(ssh, srcSz);
|
|
}
|
|
if (ret == WS_SUCCESS) {
|
|
ssh->scpState = SCP_SINK_BEGIN;
|
|
ssh->scpRequestState = SCP_SINK;
|
|
}
|
|
if (cmd) {
|
|
WFREE(cmd, ssh->ctx->heap, DYNTYPE_STRING);
|
|
}
|
|
}
|
|
if (ssh->scpState != SCP_SETUP) {
|
|
if (ret == WS_SUCCESS) {
|
|
ret = DoScpSink(ssh);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif /* ! NO_WOLFSSH_CLIENT */
|
|
|
|
|
|
#if !defined(WOLFSSH_SCP_USER_CALLBACKS)
|
|
|
|
/* Extract file name from full path, store in fileName.
|
|
* Return WS_SUCCESS on success, negative upon error */
|
|
static int ExtractFileName(const char* filePath, char* fileName,
|
|
word32 fileNameSz)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
word32 fileLen;
|
|
int idx = 0, pathLen, separator = -1;
|
|
|
|
if (filePath == NULL || fileName == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
pathLen = (int)WSTRLEN(filePath);
|
|
|
|
/* find last separator */
|
|
while (idx < pathLen) {
|
|
if (filePath[idx] == '/' || filePath[idx] == '\\')
|
|
separator = idx;
|
|
idx++;
|
|
}
|
|
|
|
/* a path with no separator is a bare file or directory name; the whole
|
|
* string is then the file name (separator == -1 is handled correctly by
|
|
* the length math below) */
|
|
if (pathLen == 0)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
fileLen = pathLen - separator - 1;
|
|
if (fileLen + 1 > fileNameSz)
|
|
return WS_SCP_PATH_LEN_E;
|
|
|
|
WMEMCPY(fileName, filePath + separator + 1, fileLen);
|
|
fileName[fileLen] = '\0';
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef WOLFSSH_TEST_INTERNAL
|
|
int wolfSSH_TestScpExtractFileName(const char* filePath, char* fileName,
|
|
word32 fileNameSz)
|
|
{
|
|
return ExtractFileName(filePath, fileName, fileNameSz);
|
|
}
|
|
#endif
|
|
|
|
#if !defined(NO_FILESYSTEM)
|
|
|
|
/* for porting to systems without errno */
|
|
static INLINE int wolfSSH_LastError(void)
|
|
{
|
|
return errno;
|
|
}
|
|
|
|
|
|
/* WOLFSSH_SCP_FD_UTIMES is defined for platforms that can set file timestamps
|
|
* on the still-open descriptor, binding the update to the inode. On those the
|
|
* timestamp is applied before the file is closed. Other platforms fall back to
|
|
* applying the timestamp by path after the file is closed. */
|
|
#if defined(USE_WINDOWS_API) || defined(WFUTIMES)
|
|
#define WOLFSSH_SCP_FD_UTIMES
|
|
#endif
|
|
|
|
/* set file access and modification times
|
|
*
|
|
* On descriptor-capable platforms (fp != NULL and WOLFSSH_SCP_FD_UTIMES) the
|
|
* timestamps are applied to the open descriptor so the update is bound to the
|
|
* inode. This avoids a race where the path could be replaced by a symlink
|
|
* between closing the file and a path-based time update, which would let a
|
|
* peer-supplied timestamp be applied to an arbitrary target. Buffered file
|
|
* data is flushed first so the later close does not write and overwrite the
|
|
* modification time. On the POSIX path-based fallback, fp is NULL because the
|
|
* file has already been closed and the timestamp is applied by path; that path
|
|
* update uses utimensat() with AT_SYMLINK_NOFOLLOW when available so a swapped
|
|
* symlink is not followed, and only drops to plain utimes() otherwise. The
|
|
* Windows branch always requires an open fp and rejects fp == NULL with
|
|
* WS_BAD_ARGUMENT (there is no path-based fallback there).
|
|
*
|
|
* Returns WS_SUCCESS on success, or negative upon error */
|
|
static int SetTimestampInfo(WFILE* fp, const char* fileName,
|
|
word64 mTime, word64 aTime)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
#ifdef USE_WINDOWS_API
|
|
struct _utimbuf tmp;
|
|
int fd;
|
|
#else
|
|
struct timeval tmp[2];
|
|
#endif
|
|
|
|
if (fileName == NULL)
|
|
ret= WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
#ifdef USE_WINDOWS_API
|
|
if (fp == NULL) {
|
|
ret = WS_BAD_ARGUMENT;
|
|
}
|
|
else {
|
|
fd = _fileno(fp);
|
|
tmp.actime = aTime;
|
|
tmp.modtime = mTime;
|
|
/* commit buffered data to disk before stamping so the close cannot
|
|
* flush a write that bumps the modification time back */
|
|
if (WFFLUSH(fp) != 0 || _commit(fd) != 0 || _futime(fd, &tmp) != 0)
|
|
ret = WS_FATAL_ERROR;
|
|
}
|
|
#else
|
|
tmp[0].tv_sec = (time_t)aTime;
|
|
tmp[0].tv_usec = 0;
|
|
tmp[1].tv_sec = (time_t)mTime;
|
|
tmp[1].tv_usec = 0;
|
|
|
|
#ifdef WFUTIMES
|
|
if (fp != NULL) {
|
|
if (WFFLUSH(fp) != 0 || WFUTIMES(fileno(fp), tmp) != 0)
|
|
ret = WS_FATAL_ERROR;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
(void)fp;
|
|
/* no open descriptor to bind to; prefer the no-follow path update
|
|
* so a swapped symlink is not followed, falling back to plain
|
|
* utimes() only when utimensat() is unavailable */
|
|
#ifdef WUTIMES_NOFOLLOW
|
|
if (WUTIMES_NOFOLLOW(fileName, tmp) != 0)
|
|
#else
|
|
if (WUTIMES(fileName, tmp) != 0)
|
|
#endif
|
|
ret = WS_FATAL_ERROR;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Default SCP receive callback, called by wolfSSH when application has called
|
|
* wolfSSH_accept() and a new SCP request has been received for an incoming
|
|
* file or directory.
|
|
*
|
|
* Handles accepting recursive directories by having wolfSSH tell the callback
|
|
* when to step into and out of a directory.
|
|
*
|
|
* When a new file copy "to" request is received, this callback is called in
|
|
* the WOLFSSH_SCP_NEW_FILE state, where the base directory is placed in
|
|
* 'basePath'. If the peer sends a recursive directory copy, wolfSSH calls
|
|
* this callback in the WOLFSSH_SCP_NEW_DIR state, with a directory name in
|
|
* 'fileName', when a directory should be created and entered. Directory
|
|
* mode is located in 'fileMode'. When a directory should be exited, the
|
|
* callback is called in the WOLFSSH_SCP_END_DIR state.
|
|
*
|
|
* When an file transfer is incoming, the callback will first be called in
|
|
* the WOLFSSH_SCP_NEW_FILE, with the file name in 'fileName', file mode
|
|
* in 'fileMode', and optionally modification and access times in 'mTime'
|
|
* and 'aTime', respectively. These timestamps may or may not be present,
|
|
* depenidng on the peer command that was executed. If the peer did not send
|
|
* these, they will be set to 0 when entering the callback.
|
|
*
|
|
* After each state is completed, the callback should return either
|
|
* WS_SCP_CONTINUE to continue the copy operation, or WS_SCP_ABORT to abort
|
|
* the copy. When WS_SCP_ABORT is returned, an optional error message can be
|
|
* sent to the peer. This error message can be set by calling
|
|
* wolfSSH_SetScpErrorMsg().
|
|
*
|
|
* ssh - pointer to active WOLFSSH session
|
|
* state - current state of operation, can be one of:
|
|
* WOLFSSH_SCP_NEW_FILE - new incoming file, no data yet, but size
|
|
* and name
|
|
* WOLFSSH_SCP_FILE_PART - new file data, or continuation of
|
|
* existing file
|
|
* WOLFSSH_SCP_FILE_DONE - indicates named file transfer is done
|
|
* WOLFSSH_SCP_NEW_DIR - indicates new directory, name in fileName
|
|
* WOLFSSH_SCP_END_DIR - indicates leaving directory, up recursively
|
|
* basePath - base directory path peer is requesting that file be written to
|
|
* fileName - name of incoming file or directory
|
|
* fileMode - mode/permission of incoming file or directory
|
|
* mTime - file modification time, if sent by peer in seconds since
|
|
* Unix epoch (00:00:00 UTC, Jan. 1, 1970). mTime is 0 if
|
|
* peer did not send time value.
|
|
* aTime - file access time, if sent by peer in seconds since Unix
|
|
* epoch (00:00:00 UTC, Jan. 1, 1970). aTime is 0 if peer did
|
|
* not send time value.
|
|
* totalFileSz - total size of incoming file (directory size may list zero)
|
|
* buf - file or file chunk
|
|
* bufSz - size of buf, bytes
|
|
* fileOffset - offset into total file size, where buf should be placed
|
|
* ctx - optional user context, stores file pointer in default case
|
|
*
|
|
* Return SCP status that is sent to client/sender. One of:
|
|
* WS_SCP_CONTINUE - continue SCP operation
|
|
* WS_SCP_ABORT - abort SCP operation, send error to peer
|
|
*/
|
|
int wsScpRecvCallback(WOLFSSH* ssh, int state, const char* basePath,
|
|
const char* fileName, int fileMode, word64 mTime, word64 aTime,
|
|
word32 totalFileSz, byte* buf, word32 bufSz, word32 fileOffset,
|
|
void* ctx)
|
|
{
|
|
WFILE* fp = NULL;
|
|
int ret = WS_SCP_CONTINUE;
|
|
word32 bytes;
|
|
#ifdef WOLFSCP_FLUSH
|
|
static word32 flush_bytes = 0;
|
|
#ifndef WRITE_FLUSH_SIZE
|
|
#define WRITE_FLUSH_SIZE (64*1024)
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef WOLFSSL_NUCLEUS
|
|
char abslut[WOLFSSH_MAX_FILENAME];
|
|
fp = (WFILE*)&ssh->scpFd; /* uses file descriptor for file operations */
|
|
abslut[0] = '\0';
|
|
#endif
|
|
|
|
if (ctx != NULL) {
|
|
fp = (WFILE*)ctx;
|
|
}
|
|
|
|
switch (state) {
|
|
|
|
case WOLFSSH_SCP_NEW_REQUEST:
|
|
|
|
/* cd into requested root path */
|
|
#ifdef WOLFSSL_NUCLEUS
|
|
{
|
|
DSTAT stat;
|
|
|
|
wolfSSH_CleanPath(ssh, (char*)basePath, WOLFSSH_MAX_FILENAME);
|
|
/* make sure is directory */
|
|
if ((ret = NU_Get_First(&stat, basePath)) != NU_SUCCESS) {
|
|
/* if back to root directory i.e. A:/ then handle case
|
|
* where file system has nothing in it. */
|
|
if (basePath[1] == ':' && ret == NUF_NOFILE) {
|
|
ret = WS_SCP_CONTINUE;
|
|
}
|
|
else {
|
|
WLOG(WS_LOG_ERROR,
|
|
"scp: invalid destination directory, abort");
|
|
wolfSSH_SetScpErrorMsg(ssh,
|
|
"invalid destination directory");
|
|
ret = WS_SCP_ABORT;
|
|
}
|
|
}
|
|
else {
|
|
ret = WS_SCP_CONTINUE;
|
|
|
|
/* check to make sure that it is a directory */
|
|
if ((stat.fattribute & ADIRENT) == 0) {
|
|
WLOG(WS_LOG_ERROR,
|
|
"scp: invalid destination directory, abort");
|
|
wolfSSH_SetScpErrorMsg(ssh,
|
|
"invalid destination directory");
|
|
ret = WS_SCP_ABORT;
|
|
}
|
|
NU_Done(&stat);
|
|
}
|
|
}
|
|
if (ret != WS_SCP_ABORT) {
|
|
ssh->scpDirDepth = 0;
|
|
}
|
|
#else
|
|
if (WCHDIR(ssh->fs, basePath) != 0) {
|
|
WLOG(WS_LOG_ERROR,
|
|
"scp: invalid destination directory, abort");
|
|
wolfSSH_SetScpErrorMsg(ssh, "invalid destination directory");
|
|
ret = WS_SCP_ABORT;
|
|
}
|
|
else {
|
|
ssh->scpDirDepth = 0;
|
|
}
|
|
#endif
|
|
break;
|
|
|
|
case WOLFSSH_SCP_NEW_FILE:
|
|
|
|
/* open file */
|
|
#ifdef WOLFSSL_NUCLEUS
|
|
/* use absolute path */
|
|
WSTRNCAT(abslut, (char*)basePath, WOLFSSH_MAX_FILENAME);
|
|
WSTRNCAT(abslut, "/", WOLFSSH_MAX_FILENAME);
|
|
WSTRNCAT(abslut, fileName, WOLFSSH_MAX_FILENAME);
|
|
wolfSSH_CleanPath(ssh, abslut, WOLFSSH_MAX_FILENAME);
|
|
if (WFOPEN(ssh->fs, &fp, abslut, "wb") != 0) {
|
|
#else
|
|
#ifdef WOLFSSH_HAVE_SYMLINK
|
|
/* refuse to write through a pre-existing symlink, which would
|
|
* escape the destination directory */
|
|
if (wIsSymlink(fileName)) {
|
|
WLOG(WS_LOG_ERROR,
|
|
"scp: refusing to write through symlink, abort");
|
|
wolfSSH_SetScpErrorMsg(ssh, "symlink target rejected");
|
|
ret = WS_SCP_ABORT;
|
|
break;
|
|
}
|
|
#endif
|
|
if (WFOPEN(ssh->fs, &fp, fileName, "wb") != 0) {
|
|
#endif
|
|
WLOG(WS_LOG_ERROR,
|
|
"scp: unable to open file for writing, abort");
|
|
wolfSSH_SetScpErrorMsg(ssh, "unable to open file for writing");
|
|
ret = WS_SCP_ABORT;
|
|
break;
|
|
}
|
|
|
|
#ifdef WOLFSCP_FLUSH
|
|
flush_bytes = 0;
|
|
#endif
|
|
/* store file pointer in user ctx */
|
|
wolfSSH_SetScpRecvCtx(ssh, fp);
|
|
break;
|
|
|
|
case WOLFSSH_SCP_FILE_PART:
|
|
|
|
if (fp == NULL) {
|
|
WLOG(WS_LOG_ERROR, "scp: file pointer was null, abort");
|
|
ret = WS_SCP_ABORT;
|
|
break;
|
|
}
|
|
/* read file, or file part */
|
|
bytes = (word32)WFWRITE(ssh->fs, buf, 1, bufSz, fp);
|
|
if (bytes != bufSz) {
|
|
WLOG(WS_LOG_ERROR, scpError, "scp receive callback unable "
|
|
"to write requested size to file", bytes);
|
|
WFCLOSE(ssh->fs, fp);
|
|
fp = NULL;
|
|
ret = WS_SCP_ABORT;
|
|
} else {
|
|
#ifdef WOLFSCP_FLUSH
|
|
flush_bytes += bytes;
|
|
if (flush_bytes >= WRITE_FLUSH_SIZE) {
|
|
if (WFFLUSH(fp) != 0)
|
|
WLOG(WS_LOG_ERROR, scpError, "scp fflush failed", 0);
|
|
if (fsync(fileno(fp)) != 0)
|
|
WLOG(WS_LOG_ERROR, scpError, "scp fsync failed", 0);
|
|
flush_bytes = 0;
|
|
usleep(1000);
|
|
}
|
|
#endif
|
|
}
|
|
break;
|
|
|
|
case WOLFSSH_SCP_FILE_DONE:
|
|
|
|
/* close file */
|
|
if (fp != NULL) {
|
|
#ifdef WOLFSCP_FLUSH
|
|
(void)WFFLUSH(fp);
|
|
(void)fsync(fileno(fp));
|
|
flush_bytes = 0;
|
|
#endif
|
|
#ifdef WOLFSSH_SCP_FD_UTIMES
|
|
/* set timestamp info on the open file, before closing, so the
|
|
* update is bound to the inode and cannot be redirected to a
|
|
* symlink swapped in over the path */
|
|
if (mTime != 0 || aTime != 0)
|
|
ret = SetTimestampInfo(fp, fileName, mTime, aTime);
|
|
#endif
|
|
WFCLOSE(ssh->fs, fp);
|
|
fp = NULL;
|
|
}
|
|
#ifdef WOLFSSH_SCP_FD_UTIMES
|
|
else if (mTime != 0 || aTime != 0) {
|
|
/* descriptor-based update required but the file was never
|
|
* opened; do not fall back to a path update that could follow
|
|
* a swapped symlink, fail so the handling below aborts */
|
|
ret = WS_FATAL_ERROR;
|
|
}
|
|
#endif
|
|
|
|
/* set timestamp info */
|
|
if (mTime != 0 || aTime != 0) {
|
|
#ifndef WOLFSSH_SCP_FD_UTIMES
|
|
/* no descriptor-based update available, set by path now that
|
|
* the file is closed so the close cannot overwrite the time */
|
|
ret = SetTimestampInfo(NULL, fileName, mTime, aTime);
|
|
#endif
|
|
if (ret == WS_SUCCESS) {
|
|
ret = WS_SCP_CONTINUE;
|
|
} else {
|
|
WLOG(WS_LOG_ERROR,
|
|
"scp: unable to set timestamp info, abort");
|
|
ret = WS_SCP_ABORT;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case WOLFSSH_SCP_NEW_DIR:
|
|
|
|
if (WSTRLEN(fileName) > 0) {
|
|
/* try to create new directory */
|
|
#ifdef WOLFSSL_NUCLEUS
|
|
/* get absolute path */
|
|
WSTRNCAT(abslut, (char*)basePath, WOLFSSH_MAX_FILENAME);
|
|
WSTRNCAT(abslut, "/", WOLFSSH_MAX_FILENAME);
|
|
WSTRNCAT(abslut, fileName, WOLFSSH_MAX_FILENAME);
|
|
wolfSSH_CleanPath(ssh, abslut, WOLFSSH_MAX_FILENAME);
|
|
if (WMKDIR(ssh->fs, abslut, fileMode) != 0) {
|
|
/* check if directory already exists */
|
|
if (NU_Make_Dir(abslut) != NUF_EXIST) {
|
|
WLOG(WS_LOG_ERROR, scpState,
|
|
"error creating directory, abort");
|
|
wolfSSH_SetScpErrorMsg(ssh, "error creating directory");
|
|
ret = WS_SCP_ABORT;
|
|
break;
|
|
|
|
}
|
|
}
|
|
#else
|
|
if (WMKDIR(ssh->fs, fileName, fileMode) != 0) {
|
|
if (wolfSSH_LastError() != EEXIST) {
|
|
WLOG(WS_LOG_ERROR,
|
|
"scp: error creating directory, abort");
|
|
wolfSSH_SetScpErrorMsg(ssh, "error creating directory");
|
|
ret = WS_SCP_ABORT;
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* cd into directory */
|
|
#ifdef WOLFSSL_NUCLEUS
|
|
WSTRNCAT((char*)basePath, "/", sizeof("/"));
|
|
WSTRNCAT((char*)basePath, fileName, WOLFSSH_MAX_FILENAME);
|
|
wolfSSH_CleanPath(ssh, (char*)basePath, WOLFSSH_MAX_FILENAME);
|
|
ssh->scpDirDepth++;
|
|
#else
|
|
#ifdef WOLFSSH_HAVE_SYMLINK
|
|
/* WMKDIR returning EEXIST above may have matched a pre-existing
|
|
* symlink; refuse to follow it out of the destination dir */
|
|
if (wIsSymlink(fileName)) {
|
|
WLOG(WS_LOG_ERROR,
|
|
"scp: refusing to enter symlinked directory, abort");
|
|
wolfSSH_SetScpErrorMsg(ssh, "symlink in destination path");
|
|
ret = WS_SCP_ABORT;
|
|
break;
|
|
}
|
|
#endif
|
|
if (WCHDIR(ssh->fs, fileName) != 0) {
|
|
WLOG(WS_LOG_ERROR,
|
|
"scp: unable to cd into directory, abort");
|
|
wolfSSH_SetScpErrorMsg(ssh, "unable to cd into directory");
|
|
ret = WS_SCP_ABORT;
|
|
}
|
|
else {
|
|
ssh->scpDirDepth++;
|
|
}
|
|
#endif
|
|
}
|
|
break;
|
|
|
|
case WOLFSSH_SCP_END_DIR:
|
|
|
|
/* abort if peer sent END_DIR without a matching NEW_DIR */
|
|
if (ssh->scpDirDepth == 0) {
|
|
WLOG(WS_LOG_ERROR,
|
|
"scp: end directory without matching start, abort");
|
|
wolfSSH_SetScpErrorMsg(ssh,
|
|
"end directory without matching start");
|
|
ret = WS_SCP_ABORT;
|
|
break;
|
|
}
|
|
|
|
/* cd out of directory */
|
|
#ifdef WOLFSSL_NUCLEUS
|
|
WSTRNCAT((char*)basePath, "/..", WOLFSSH_MAX_FILENAME - 1);
|
|
wolfSSH_CleanPath(ssh, (char*)basePath, WOLFSSH_MAX_FILENAME);
|
|
ssh->scpDirDepth--;
|
|
#else
|
|
if (WCHDIR(ssh->fs, "..") != 0) {
|
|
WLOG(WS_LOG_ERROR,
|
|
"scp: unable to cd out of directory, abort");
|
|
wolfSSH_SetScpErrorMsg(ssh, "unable to cd out of directory");
|
|
ret = WS_SCP_ABORT;
|
|
}
|
|
else {
|
|
ssh->scpDirDepth--;
|
|
}
|
|
#endif
|
|
break;
|
|
|
|
default:
|
|
WLOG(WS_LOG_ERROR,
|
|
"scp: invalid scp command request, abort");
|
|
wolfSSH_SetScpErrorMsg(ssh, "invalid scp command request");
|
|
ret = WS_SCP_ABORT;
|
|
}
|
|
|
|
WOLFSSH_UNUSED(totalFileSz);
|
|
WOLFSSH_UNUSED(fileOffset);
|
|
return ret;
|
|
}
|
|
|
|
static int _GetFileSize(void* fs, WFILE* fp, word32* fileSz)
|
|
{
|
|
WOLFSSH_UNUSED(fs);
|
|
|
|
if (fp == NULL || fileSz == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
/* get file size */
|
|
WFSEEK(fs, fp, 0, WSEEK_END);
|
|
*fileSz = (word32)WFTELL(fs, fp);
|
|
WREWIND(fs, fp);
|
|
|
|
return WS_SUCCESS;
|
|
}
|
|
|
|
static int GetFileStats(void *fs, ScpSendCtx* ctx, const char* fileName,
|
|
word64* mTime, word64* aTime, int* fileMode)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
|
|
WOLFSSH_UNUSED(fs);
|
|
|
|
if (ctx == NULL || fileName == NULL || mTime == NULL ||
|
|
aTime == NULL || fileMode == NULL) {
|
|
return WS_BAD_ARGUMENT;
|
|
}
|
|
|
|
/* get file stats for times and mode */
|
|
#if defined(USE_WINDOWS_API)
|
|
BOOL error;
|
|
|
|
error = !WS_GetFileAttributesExA(fileName, &ctx->s, NULL);
|
|
if (error)
|
|
return WS_BAD_FILE_E;
|
|
|
|
*aTime = ((word64)ctx->s.ftLastAccessTime.dwHighDateTime << 32) |
|
|
(word64)ctx->s.ftLastAccessTime.dwLowDateTime;
|
|
*mTime = ((word64)ctx->s.ftLastWriteTime.dwHighDateTime << 32) |
|
|
(word64)ctx->s.ftLastWriteTime.dwLowDateTime;
|
|
|
|
*fileMode = 0555 |
|
|
(ctx->s.dwFileAttributes & FILE_ATTRIBUTE_READONLY ? 0 : 0200);
|
|
*fileMode |= (ctx->s.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? 040000 : 0;
|
|
#else
|
|
/* WLSTAT (lstat on POSIX) leaves a symlink unfollowed, so it classifies as
|
|
* neither dir nor file and is skipped; WOLFSSH_NO_SYMLINK_CHECK falls back
|
|
* to WSTAT so links are followed by design. */
|
|
#ifdef WOLFSSH_HAVE_SYMLINK
|
|
if (WLSTAT(fs, fileName, &ctx->s) < 0) {
|
|
#else
|
|
if (WSTAT(fs, fileName, &ctx->s) < 0) {
|
|
#endif
|
|
ret = WS_BAD_FILE_E;
|
|
#ifdef WOLFSSL_NUCLEUS
|
|
if (WSTRLEN(fileName) < 4 && WSTRLEN(fileName) > 2 &&
|
|
fileName[1] == ':') {
|
|
*fileMode = 0755;
|
|
ret = WS_SUCCESS;
|
|
}
|
|
#endif
|
|
}
|
|
else {
|
|
#ifdef WOLFSSL_NUCLEUS
|
|
if (ctx->s.fattribute & ARDONLY) {
|
|
*fileMode = 0444;
|
|
}
|
|
if (ctx->s.fattribute == ANORMAL) { /* ANORMAL = 0 */
|
|
*fileMode = 0666;
|
|
}
|
|
if (ctx->s.fattribute == ADIRENT) {
|
|
*fileMode = 0755;
|
|
}
|
|
*mTime = ctx->s.fupdate;
|
|
*aTime = ctx->s.faccdate;
|
|
NU_Done(&ctx->s);
|
|
#elif defined(WOLFSSH_ZEPHYR)
|
|
/* No time data in zephyr fs */
|
|
*mTime = (word64)0;
|
|
*aTime = (word64)0;
|
|
/* Default perms */
|
|
*fileMode = 0755;
|
|
/* Mimic S_IFMT */
|
|
if (ctx->s.type == FS_DIR_ENTRY_FILE)
|
|
*fileMode |= 0100000; /* S_IFREG */
|
|
else if (ctx->s.type == FS_DIR_ENTRY_DIR)
|
|
*fileMode |= 0040000; /* S_IFDIR */
|
|
else
|
|
ret = WS_BAD_FILE_E;
|
|
|
|
#else
|
|
*mTime = (word64)ctx->s.st_mtime;
|
|
*aTime = (word64)ctx->s.st_atime;
|
|
*fileMode = ctx->s.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO);
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Create new ScpDir struct for pushing on directory stack.
|
|
* Return valid pointer on success, NULL on failure */
|
|
static ScpDir* ScpNewDir(void *fs, const char* path, void* heap)
|
|
{
|
|
WOLFSSH_UNUSED(fs);
|
|
|
|
ScpDir* entry = NULL;
|
|
|
|
if (path == NULL) {
|
|
WLOG(WS_LOG_ERROR, scpError, "invalid directory path",
|
|
WS_INVALID_PATH_E);
|
|
return NULL;
|
|
}
|
|
|
|
entry = (ScpDir*)WMALLOC(sizeof(ScpDir), heap, DYNTYPE_SCPDIR);
|
|
if (entry == NULL) {
|
|
WLOG(WS_LOG_ERROR, scpError, "error allocating ScpDir" , WS_MEMORY_E);
|
|
return NULL;
|
|
}
|
|
|
|
entry->next = NULL;
|
|
#ifdef USE_WINDOWS_API
|
|
{
|
|
char sPath[MAX_PATH];
|
|
int isDir;
|
|
|
|
/* add wildcard to get full directory */
|
|
WSNPRINTF(sPath, MAX_PATH, "%s/*", path);
|
|
|
|
entry->dir = (HANDLE)WS_FindFirstFileA(sPath,
|
|
sPath, sizeof(sPath), &isDir, heap);
|
|
if (entry->dir == INVALID_HANDLE_VALUE) {
|
|
WFREE(entry, heap, DYNTYPE_SCPDIR);
|
|
WLOG(WS_LOG_ERROR, scpError, "opendir failed on directory",
|
|
WS_INVALID_PATH_E);
|
|
return NULL;
|
|
}
|
|
}
|
|
#else
|
|
#ifdef WOLFSSH_HAVE_SYMLINK
|
|
/* refuse a symlinked directory leaf atomically (closes the descend race) */
|
|
if (wOpendirNoFollow(fs, &entry->dir, path) != 0) {
|
|
#else
|
|
if (WOPENDIR(fs, heap, &entry->dir, path) != 0
|
|
#if !defined(WOLFSSL_NUCLEUS) && !defined(WOLFSSH_ZEPHYR)
|
|
|| entry->dir == NULL
|
|
#endif
|
|
) {
|
|
#endif
|
|
WFREE(entry, heap, DYNTYPE_SCPDIR);
|
|
WLOG(WS_LOG_ERROR, scpError, "opendir failed on directory",
|
|
WS_INVALID_PATH_E);
|
|
return NULL;
|
|
}
|
|
#endif
|
|
return entry;
|
|
}
|
|
|
|
/* Create and push new ScpDir on stack, append directory to ctx->dirName */
|
|
int ScpPushDir(void *fs, ScpSendCtx* ctx, const char* path, void* heap)
|
|
{
|
|
ScpDir* entry;
|
|
|
|
if (ctx == NULL || path == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
entry = ScpNewDir(fs, path, heap);
|
|
if (entry == NULL) {
|
|
return WS_FATAL_ERROR;
|
|
}
|
|
|
|
if (ctx->currentDir == NULL) {
|
|
entry->next = NULL;
|
|
ctx->currentDir = entry;
|
|
} else {
|
|
entry->next = ctx->currentDir;
|
|
ctx->currentDir = entry;
|
|
}
|
|
|
|
/* append directory name to ctx->dirName */
|
|
WSTRNCPY(ctx->dirName, path, DEFAULT_SCP_FILE_NAME_SZ-1);
|
|
ctx->dirName[DEFAULT_SCP_FILE_NAME_SZ-1] = '\0';
|
|
|
|
return WS_SUCCESS;
|
|
}
|
|
|
|
/* Remove top ScpDir from directory stack, remove dir from ctx->dirName */
|
|
int ScpPopDir(void *fs, ScpSendCtx* ctx, void* heap)
|
|
{
|
|
WOLFSSH_UNUSED(fs);
|
|
|
|
ScpDir* entry = NULL;
|
|
int idx = 0, separator = 0;
|
|
|
|
if (ctx->currentDir != NULL) {
|
|
entry = ctx->currentDir;
|
|
ctx->currentDir = entry->next;
|
|
}
|
|
|
|
if (entry != NULL) {
|
|
#ifdef USE_WINDOWS_API
|
|
FindClose(entry->dir);
|
|
#else
|
|
WCLOSEDIR(fs, &entry->dir);
|
|
#endif
|
|
WFREE(entry, heap, DYNTYPE_SCPDIR);
|
|
}
|
|
|
|
/* remove directory from ctx->dirName path, find last separator */
|
|
while (idx < (int)sizeof(ctx->dirName)) {
|
|
if (ctx->dirName[idx] == '/' || ctx->dirName[idx] == '\\')
|
|
separator = idx;
|
|
idx++;
|
|
}
|
|
|
|
if (separator != 0) {
|
|
WMEMSET(ctx->dirName + separator, 0,
|
|
sizeof(ctx->dirName) - separator);
|
|
}
|
|
|
|
if (ctx->currentDir == NULL)
|
|
return WS_SCP_DIR_STACK_EMPTY_E;
|
|
|
|
WOLFSSH_UNUSED(heap);
|
|
return WS_SUCCESS;
|
|
}
|
|
|
|
/* Drain dir-stack entries (and open dir handles) left on a send context after
|
|
* a recursive transfer aborts mid-tree before popping. Safe on an empty
|
|
* stack. */
|
|
void ScpSendCtxFreeDirs(void* fs, ScpSendCtx* ctx, void* heap)
|
|
{
|
|
if (ctx != NULL) {
|
|
while (ctx->currentDir != NULL)
|
|
(void)ScpPopDir(fs, ctx, heap);
|
|
}
|
|
}
|
|
|
|
/* Get next entry in directory, either file or directory, skips self (.)
|
|
* and parent (..) directories, stores in ctx->entry.
|
|
* Return WS_SUCCESS on success or negative upon error */
|
|
static int FindNextDirEntry(void *fs, ScpSendCtx* ctx)
|
|
{
|
|
WOLFSSH_UNUSED(fs);
|
|
|
|
if (ctx == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
/* skip self (.) and parent (..) directories */
|
|
#ifdef WOLFSSL_NUCLEUS
|
|
|
|
if (WSTRNCMP(ctx->currentDir->dir.lfname, "", 1) == 0) {
|
|
WLOG(WS_LOG_DEBUG, scpError, "no file name found, no . or ..",
|
|
WS_NEXT_ERROR);
|
|
return WS_NEXT_ERROR;
|
|
}
|
|
|
|
/* There is a special case with Nucleus on root directory where the first
|
|
* entry is not "." and should not be skipped over */
|
|
if ((WSTRNCMP(ctx->currentDir->dir.lfname, ".", 1) == 0) ||
|
|
(WSTRNCMP(ctx->currentDir->dir.lfname ,"..", 2) == 0)) {
|
|
ctx->nextError = 1;
|
|
}
|
|
|
|
if (ctx->nextError == 1) {
|
|
WDIR* dr;
|
|
do {
|
|
dr = WREADDIR(fs, &ctx->currentDir->dir);
|
|
} while (dr != NULL &&
|
|
(WSTRNCMP(ctx->currentDir->dir.lfname, ".", 1) == 0 ||
|
|
WSTRNCMP(ctx->currentDir->dir.lfname ,"..", 2) == 0));
|
|
if (dr == NULL) {
|
|
return WS_NEXT_ERROR;
|
|
}
|
|
}
|
|
ctx->nextError = 1;
|
|
#elif defined(USE_WINDOWS_API)
|
|
do {
|
|
char realFileName[MAX_PATH];
|
|
int sz;
|
|
|
|
if (WS_FindNextFileA(ctx->currentDir->dir,
|
|
realFileName, sizeof(realFileName)) == 0) {
|
|
return WS_FATAL_ERROR;
|
|
}
|
|
|
|
sz = (int)WSTRLEN(realFileName);
|
|
if (ctx->entry != NULL) {
|
|
WFREE(ctx->entry, NULL, DYNTYPE_SCPDIR);
|
|
ctx->entry = NULL;
|
|
}
|
|
|
|
ctx->entry = (char*)WMALLOC(sz + 1, NULL, DYNTYPE_SCPDIR);
|
|
if (ctx->entry == NULL) {
|
|
return WS_MEMORY_E;
|
|
}
|
|
WMEMCPY(ctx->entry, realFileName, sz);
|
|
ctx->entry[sz] = '\0';
|
|
} while ((ctx->entry != NULL) &&
|
|
(((WSTRLEN(ctx->entry) == 1) && WSTRNCMP(ctx->entry, ".", 1) == 0) ||
|
|
((WSTRLEN(ctx->entry) == 2) && WSTRNCMP(ctx->entry, "..", 2) == 0)));
|
|
#elif defined(WOLFSSH_ZEPHYR)
|
|
do {
|
|
if (fs_readdir(&ctx->currentDir->dir, &ctx->entry) != 0)
|
|
return WS_FATAL_ERROR;
|
|
if (ctx->entry.name[0] == 0) /* Reached end-of-dir */
|
|
return WS_NEXT_ERROR;
|
|
} while (1);
|
|
#else
|
|
do {
|
|
ctx->entry = WREADDIR(fs, &ctx->currentDir->dir);
|
|
} while ((ctx->entry != NULL) &&
|
|
(
|
|
((WSTRLEN(ctx->entry->d_name) == 1) &&
|
|
(WSTRNCMP(ctx->entry->d_name, ".", 1) == 0))
|
|
||
|
|
((WSTRLEN(ctx->entry->d_name) == 2) &&
|
|
(WSTRNCMP(ctx->entry->d_name ,"..", 2) == 0))
|
|
));
|
|
#endif
|
|
|
|
return WS_SUCCESS;
|
|
}
|
|
|
|
/* Test if directory stack is empty, return 1 if empty, otherwise 0 */
|
|
static int ScpDirStackIsEmpty(ScpSendCtx* ctx)
|
|
{
|
|
if (ctx && ctx->currentDir == NULL)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* returns 1 if is directory */
|
|
int ScpFileIsDir(ScpSendCtx* ctx)
|
|
{
|
|
#ifdef WOLFSSL_NUCLEUS
|
|
return (ctx->s.fattribute & ADIRENT);
|
|
#elif defined(USE_WINDOWS_API)
|
|
return (ctx->s.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
|
|
#elif defined(WOLFSSH_ZEPHYR)
|
|
return ctx->s.type == FS_DIR_ENTRY_DIR;
|
|
#else
|
|
return S_ISDIR(ctx->s.st_mode);
|
|
#endif
|
|
}
|
|
|
|
static int ScpFileIsFile(ScpSendCtx* ctx)
|
|
{
|
|
#ifdef WOLFSSL_NUCLEUS
|
|
return (ctx->s.fattribute != ADIRENT);
|
|
#elif defined(USE_WINDOWS_API)
|
|
return ((ctx->s.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0);
|
|
#elif defined(WOLFSSH_ZEPHYR)
|
|
return ctx->s.type == FS_DIR_ENTRY_FILE;
|
|
#else
|
|
return S_ISREG(ctx->s.st_mode);
|
|
#endif
|
|
}
|
|
|
|
|
|
/* Process a single entry, testing if is directory and opening files
|
|
*
|
|
* returns WS_SCP_ENTER_DIR when entering a new directory
|
|
* WS_SCP_ABORT on fail case
|
|
* WS_NEXT_ERROR when next call to the function will cause an error
|
|
* WS_SUCCESS when successfully opening a file
|
|
*/
|
|
static int ScpProcessEntry(WOLFSSH* ssh, char* fileName, word64* mTime,
|
|
word64* aTime, int* fileMode, word32* totalFileSz, byte* buf,
|
|
word32 bufSz, void* ctx, ScpSendCtx* sendCtx)
|
|
{
|
|
int ret = WS_SUCCESS, dirNameLen, dNameLen;
|
|
char filePath[DEFAULT_SCP_FILE_NAME_SZ];
|
|
|
|
#ifdef WOLFSSL_NUCLEUS
|
|
if (WSTRNCMP(sendCtx->currentDir->dir.lfname, ".", 1) == 0 ||
|
|
WSTRNCMP(sendCtx->currentDir->dir.lfname ,"..", 2) == 0) {
|
|
ret = WS_NEXT_ERROR;
|
|
}
|
|
#endif
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
|
|
dirNameLen = (int)WSTRLEN(sendCtx->dirName);
|
|
#if defined(WOLFSSL_NUCLEUS)
|
|
dNameLen = (int)WSTRLEN(sendCtx->currentDir->dir.lfname);
|
|
#elif defined(USE_WINDOWS_API)
|
|
{
|
|
char path[MAX_PATH];
|
|
|
|
GetFullPathNameA(fileName, MAX_PATH, path, NULL);
|
|
dNameLen = (int)WSTRLEN(path);
|
|
}
|
|
#elif defined(WOLFSSH_ZEPHYR)
|
|
dNameLen = (int)WSTRLEN(sendCtx->entry.name);
|
|
#else
|
|
dNameLen = (int)WSTRLEN(sendCtx->entry->d_name);
|
|
#endif
|
|
if ((dirNameLen + 1 + dNameLen) > DEFAULT_SCP_FILE_NAME_SZ) {
|
|
WLOG(WS_LOG_ERROR, "scp: dir name length too long, abort");
|
|
ret = WS_SCP_ABORT;
|
|
|
|
} else {
|
|
WSTRNCPY(filePath, sendCtx->dirName,
|
|
DEFAULT_SCP_FILE_NAME_SZ);
|
|
WSTRNCAT(filePath, "/", DEFAULT_SCP_FILE_NAME_SZ);
|
|
|
|
#ifdef WOLFSSL_NUCLEUS
|
|
WSTRNCAT(filePath, sendCtx->currentDir->dir.lfname,
|
|
DEFAULT_SCP_FILE_NAME_SZ);
|
|
WSTRNCPY(fileName, sendCtx->currentDir->dir.lfname,
|
|
DEFAULT_SCP_FILE_NAME_SZ);
|
|
if (wolfSSH_CleanPath(ssh, filePath, DEFAULT_SCP_FILE_NAME_SZ) < 0) {
|
|
ret = WS_SCP_ABORT;
|
|
}
|
|
#elif defined(USE_WINDOWS_API)
|
|
WSTRNCAT(filePath, sendCtx->entry,
|
|
DEFAULT_SCP_FILE_NAME_SZ);
|
|
WSTRNCPY(fileName, sendCtx->entry,
|
|
DEFAULT_SCP_FILE_NAME_SZ);
|
|
#elif defined(WOLFSSH_ZEPHYR)
|
|
WSTRNCAT(filePath, sendCtx->entry.name,
|
|
DEFAULT_SCP_FILE_NAME_SZ);
|
|
WSTRNCPY(fileName, sendCtx->entry.name,
|
|
DEFAULT_SCP_FILE_NAME_SZ);
|
|
#else
|
|
WSTRNCAT(filePath, sendCtx->entry->d_name,
|
|
DEFAULT_SCP_FILE_NAME_SZ);
|
|
WSTRNCPY(fileName, sendCtx->entry->d_name,
|
|
DEFAULT_SCP_FILE_NAME_SZ);
|
|
#endif
|
|
#ifdef WOLFSSH_HAVE_SYMLINK
|
|
/* filePath is fully built; reject a planted symlink before
|
|
* GetFileStats or any descend/open follows it. */
|
|
if (ret == WS_SUCCESS && wIsSymlink(filePath)) {
|
|
WLOG(WS_LOG_ERROR,
|
|
"scp: symlink entry rejected, aborting transfer");
|
|
ret = WS_SCP_ABORT;
|
|
}
|
|
#endif /* WOLFSSH_HAVE_SYMLINK */
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ret = GetFileStats(ssh->fs, sendCtx, filePath, mTime, aTime, fileMode);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
|
|
if (ScpFileIsDir(sendCtx)) {
|
|
|
|
ret = ScpPushDir(ssh->fs, sendCtx, filePath, ssh->ctx->heap);
|
|
if (ret == WS_SUCCESS) {
|
|
ret = WS_SCP_ENTER_DIR;
|
|
} else {
|
|
WLOG(WS_LOG_ERROR, "scp: Error with push dir, abort");
|
|
ret = WS_SCP_ABORT;
|
|
}
|
|
|
|
} else if (ScpFileIsFile(sendCtx)) {
|
|
#ifdef WOLFSSH_HAVE_SYMLINK
|
|
if (wFopenNoFollow(ssh->fs, &(sendCtx->fp), filePath) != 0) {
|
|
#else
|
|
if (WFOPEN(ssh->fs, &(sendCtx->fp), filePath, "rb") != 0) {
|
|
#endif
|
|
WLOG(WS_LOG_ERROR, "scp: Error with opening file, abort");
|
|
wolfSSH_SetScpErrorMsg(ssh, "unable to open file "
|
|
"for reading");
|
|
ret = WS_SCP_ABORT;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ret = _GetFileSize(ssh->fs, sendCtx->fp, totalFileSz);
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = (word32)WFREAD(ssh->fs, buf, 1, bufSz, sendCtx->fp);
|
|
}
|
|
|
|
/* keep fp open if no errors and transfer will continue */
|
|
if ((sendCtx->fp != NULL) &&
|
|
((ret < 0) || (*totalFileSz == (word32)ret))) {
|
|
WFCLOSE(ssh->fs, sendCtx->fp);
|
|
sendCtx->fp = NULL;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
/* WS_SCP_ABORT entries (e.g. a rejected symlink) were already logged at
|
|
* their source, so only the generic, unexpected-error case is noted
|
|
* here to avoid a misleading second log line. */
|
|
if (ret != WS_NEXT_ERROR && ret != WS_SCP_ABORT) {
|
|
WLOG(WS_LOG_ERROR, "scp: ret does not equal WS_NEXT_ERROR, abort");
|
|
ret = WS_SCP_ABORT;
|
|
}
|
|
}
|
|
|
|
WOLFSSH_UNUSED(ctx);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Default SCP send callback, called by wolfSSH when an application has called
|
|
* wolfSSH_accept() and a new SCP request has been received requesting a file
|
|
* be copied from the server to the peer.
|
|
*
|
|
* Depending on the peer request, this callback can be called in one of
|
|
* several different states. If the peer requested a single file, the
|
|
* WOLFSSH_SCP_SINGLE_FILE_REQUEST state will be passed to the callback, where
|
|
* the callback is responsible for populating file info and placing the single
|
|
* file (or file part) into 'buf'.
|
|
*
|
|
* If the peer requests a directory of files be transferred, in a recursive
|
|
* request, WOLFSSH_SCP_RECURSIVE_REQUEST will be passed to the callback. The
|
|
* callback is then responsible for traversing through the requested directory
|
|
* one directory or file at a time, returning WS_SCP_ENTER_DIR when a new
|
|
* directory is entered, WS_SCP_EXIT_DIR when a directory is exited (not
|
|
* including the final directory exit), and WS_SCP_EXIT_DIR_FINAL when the
|
|
* final directory is done.
|
|
*
|
|
* At any time, the callback can abort the transfer by returning WS_SCP_ABORT.
|
|
* This will send an error confirmation message to the peer. When returning
|
|
* WS_SCP_ABORT, the callback can call wolfSSH_SetScpErrorMsg() with an
|
|
* optional error message to send back to the peer.
|
|
*
|
|
* When sending file data, the callback should copy up to 'bufSz' bytes
|
|
* into 'buf', and return the number of bytes copied into 'buf'. Less than
|
|
* 'bufSz' bytes can be copied into buf, which will cause only some file data
|
|
* to be sent to the peer. In this scenario, the callback will be called
|
|
* again with the state set to WOLFSSH_SCP_CONTINUE_FILE_TRANSFER. In this
|
|
* state, the callback should again place up to 'bufSz' data in 'buf. The
|
|
* 'fileOffset' variable holds the current offset into the file where file
|
|
* bytes should be copied from.
|
|
*
|
|
* ssh - [IN] pointer to active WOLFSSH session
|
|
* state - [IN] current state of operation, can be one of:
|
|
* WOLFSSH_SCP_SINGLE_FILE_REQUEST - peer requested a single file to
|
|
* be copied from the server
|
|
* WOLFSSH_SCP_RECURSIVE_REQUEST - peer requested an entire
|
|
* directory be copied from the
|
|
* server, recursively
|
|
* WOLFSSH_SCP_CONTINUE_FILE_TRANSFER - file did not transfer completely
|
|
* in previous call, need more
|
|
* data to be sent from server
|
|
* to peer to complete file
|
|
* transfer.
|
|
* peerRequest - [IN] name of file/directory the peer is requesting to be copied
|
|
* fileName - [OUT] name of file/directory callback is sending to peer,
|
|
* should be NULL terminated.
|
|
* mTime - [OUT] file modification time, in seconds since
|
|
* Unix epoch (00:00:00 UTC, Jan. 1, 1970). Optional, and set
|
|
* to 0 by default.
|
|
* aTime - [OUT] file access time, in seconds since Unix
|
|
* epoch (00:00:00 UTC, Jan. 1, 1970). Optional, and set to 0
|
|
* by default.
|
|
* fileMode - [OUT] mode/permission of outgoing file or directory, in
|
|
* decimal representation (ie: 0644 octal == 420 decimal)
|
|
* fileOffset - offset into total file size, from where file data should be
|
|
* read into 'buf'.
|
|
* totalFileSz - total size of file being sent, bytes
|
|
* buf - [OUT] buffer to place file (or file part) in, of size bufSz
|
|
* bufSz - [IN] size of buf, bytes
|
|
* ctx - [IN] optional user context, stores file pointer in default
|
|
* case. Can be set by calling wolfSSH_SetScpSendCtx().
|
|
*
|
|
* Return number of bytes copied into buf, if doing a file transfer, otherwise
|
|
* one of:
|
|
* WS_SCP_ENTER_DIR - send directory name to peer - fileName,
|
|
* mode (optional), mTime (optional), and
|
|
* aTime (optional) should be set. Return
|
|
* when callback and "entered" a directory.
|
|
* WS_SCP_EXIT_DIR - return when recursive directory traversal
|
|
* has "exited" a directory.
|
|
* WS_SCP_EXIT_DIR_FINAL - return when recursive directory transfer
|
|
* is complete.
|
|
* WS_SCP_ABORT - abort file transfer request
|
|
* WS_BAD_FILE_E - local file open error hit
|
|
*
|
|
* Symlink handling: file-content opens go through wFopenNoFollow and directory
|
|
* opens (both the recursive root and every descend) go through wOpendirNoFollow.
|
|
* Both are atomic against a swapped link on POSIX (O_NOFOLLOW, plus O_DIRECTORY
|
|
* for the directory open) and fall back to a wIsSymlink check-then-open on
|
|
* Windows or where O_NOFOLLOW is absent. The root also gets an explicit
|
|
* wIsSymlink pre-check because a trailing separator (open("link/", O_NOFOLLOW))
|
|
* can still follow the link; symlinks below the root are rejected as
|
|
* ScpProcessEntry traverses them. No "stays under a trusted base" containment
|
|
* is attempted: SCP has no library-level base path (wolfsshd relies on OS
|
|
* chroot) and wolfSSH_RealPath does not resolve links. GetFileStats uses WLSTAT
|
|
* so it does not follow a link for metadata or classification. On the
|
|
* Windows/fallback path the open is check-then-open, so a concurrent in-jail
|
|
* writer racing it remains a best-effort gap. For hostile multi-tenant use,
|
|
* confine the session with an OS mechanism (chroot, dropped privileges) and
|
|
* treat these checks as defense in depth.
|
|
*/
|
|
int wsScpSendCallback(WOLFSSH* ssh, int state, const char* peerRequest,
|
|
char* fileName, word32 fileNameSz, word64* mTime, word64* aTime,
|
|
int* fileMode, word32 fileOffset, word32* totalFileSz, byte* buf,
|
|
word32 bufSz, void* ctx)
|
|
{
|
|
ScpSendCtx* sendCtx = NULL;
|
|
int ret = WS_SUCCESS;
|
|
char filePath[DEFAULT_SCP_FILE_NAME_SZ];
|
|
|
|
if (ctx != NULL) {
|
|
sendCtx = (ScpSendCtx*)ctx;
|
|
}
|
|
|
|
#ifdef WOLFSSL_NUCLEUS
|
|
if (sendCtx != NULL) sendCtx->fp = &sendCtx->fd;
|
|
#endif
|
|
|
|
WMEMSET(filePath, 0, DEFAULT_SCP_FILE_NAME_SZ);
|
|
|
|
switch (state) {
|
|
|
|
case WOLFSSH_SCP_NEW_REQUEST:
|
|
|
|
/* new request, user may return WS_SCP_ABORT
|
|
* to abort/reject transfer attempt, ie:
|
|
*
|
|
* wolfSSH_SetScpErrorMsg(ssh, "scp transfer rejected");
|
|
* ret = WS_SCP_ABORT;
|
|
*/
|
|
break;
|
|
|
|
case WOLFSSH_SCP_SINGLE_FILE_REQUEST:
|
|
/* open without following a symlink so its target is not streamed
|
|
* to the peer; see this function's symlink-handling note. */
|
|
if ((sendCtx == NULL) ||
|
|
#ifdef WOLFSSH_HAVE_SYMLINK
|
|
wFopenNoFollow(ssh->fs, &(sendCtx->fp), peerRequest) != 0) {
|
|
#else
|
|
WFOPEN(ssh->fs, &(sendCtx->fp), peerRequest, "rb") != 0) {
|
|
#endif
|
|
WLOG(WS_LOG_ERROR, "scp: unable to open file, abort");
|
|
wolfSSH_SetScpErrorMsg(ssh, "unable to open file for reading");
|
|
ret = WS_BAD_FILE_E;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
#ifdef WOLFSSL_NUCLEUS
|
|
if (sendCtx->fd < 0)
|
|
ret = WS_SCP_ABORT;
|
|
#else
|
|
if (sendCtx->fp == NULL)
|
|
ret = WS_SCP_ABORT;
|
|
#endif
|
|
}
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = _GetFileSize(ssh->fs, sendCtx->fp, totalFileSz);
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = GetFileStats(ssh->fs, sendCtx, peerRequest, mTime, aTime, fileMode);
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = ExtractFileName(peerRequest, fileName, fileNameSz);
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
if (sendCtx != NULL && sendCtx->fp != NULL) {
|
|
/* If it is an empty file, do not read. */
|
|
if (*totalFileSz != 0) {
|
|
ret = (word32)WFREAD(ssh->fs, buf, 1, bufSz,
|
|
sendCtx->fp);
|
|
if (ret == 0) { /* handle unexpected case */
|
|
ret = WS_EOF;
|
|
}
|
|
}
|
|
} else {
|
|
WLOG(WS_LOG_ERROR,
|
|
"scp: error extracting file name, abort");
|
|
ret = WS_SCP_ABORT;
|
|
}
|
|
}
|
|
|
|
/* keep fp open if no errors and transfer will continue */
|
|
if ((sendCtx != NULL) && (sendCtx->fp != NULL) &&
|
|
((ret < 0) || (*totalFileSz == (word32)ret))) {
|
|
WFCLOSE(ssh->fs, sendCtx->fp);
|
|
sendCtx->fp = NULL;
|
|
}
|
|
|
|
break;
|
|
|
|
case WOLFSSH_SCP_RECURSIVE_REQUEST:
|
|
|
|
if (ScpDirStackIsEmpty(sendCtx)) {
|
|
#ifdef WOLFSSH_HAVE_SYMLINK
|
|
word32 rootLen;
|
|
#endif
|
|
|
|
/* first request, keep track of request directory. Reject a
|
|
* symlink root here (a trailing separator can still follow);
|
|
* see the symlink-handling note in this function's header. */
|
|
ret = WS_SUCCESS;
|
|
if (peerRequest == NULL) {
|
|
WLOG(WS_LOG_ERROR,
|
|
"scp: missing recursive root path, abort");
|
|
ret = WS_SCP_ABORT;
|
|
}
|
|
#ifdef WOLFSSH_HAVE_SYMLINK
|
|
/* lstat() follows the link when the path ends in a separator,
|
|
* so check the root with any trailing separators removed */
|
|
else {
|
|
rootLen = (word32)WSTRLEN(peerRequest);
|
|
while (rootLen > 1 && (peerRequest[rootLen - 1] == '/' ||
|
|
peerRequest[rootLen - 1] == '\\'))
|
|
rootLen--;
|
|
if (rootLen >= DEFAULT_SCP_FILE_NAME_SZ) {
|
|
WLOG(WS_LOG_ERROR,
|
|
"scp: recursive root path too long, abort");
|
|
wolfSSH_SetScpErrorMsg(ssh,
|
|
"unable to open file for reading");
|
|
ret = WS_SCP_ABORT;
|
|
}
|
|
else {
|
|
WMEMCPY(filePath, peerRequest, rootLen);
|
|
filePath[rootLen] = '\0';
|
|
if (wIsSymlink(filePath)) {
|
|
WLOG(WS_LOG_ERROR,
|
|
"scp: refusing recursive root symlink, abort");
|
|
wolfSSH_SetScpErrorMsg(ssh,
|
|
"unable to open file for reading");
|
|
ret = WS_SCP_ABORT;
|
|
}
|
|
}
|
|
}
|
|
#endif /* WOLFSSH_HAVE_SYMLINK */
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ret = ScpPushDir(ssh->fs, sendCtx, peerRequest,
|
|
ssh->ctx->heap);
|
|
if (ret != WS_SUCCESS) {
|
|
WLOG(WS_LOG_ERROR,
|
|
"scp: error opening base directory, abort");
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
/* get file name from request */
|
|
ret = ExtractFileName(peerRequest, fileName, fileNameSz);
|
|
if (ret != WS_SUCCESS) {
|
|
WLOG(WS_LOG_ERROR,
|
|
"scp: error extracting directory name, abort");
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ret = GetFileStats(ssh->fs, sendCtx, peerRequest, mTime, aTime,
|
|
fileMode);
|
|
if (ret != WS_SUCCESS) {
|
|
WLOG(WS_LOG_ERROR,
|
|
"scp: error getting file stats, abort");
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ret = WS_SCP_ENTER_DIR;
|
|
} else {
|
|
ret = WS_SCP_ABORT;
|
|
}
|
|
|
|
|
|
/* send directory msg or abort */
|
|
break;
|
|
}
|
|
ret = FindNextDirEntry(ssh->fs, sendCtx);
|
|
|
|
/* help out static analysis tool */
|
|
if (ret != WS_BAD_ARGUMENT && sendCtx == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS || ret == WS_NEXT_ERROR) {
|
|
|
|
#if defined(WOLFSSL_NUCLEUS) || defined(WOLFSSH_ZEPHYR)
|
|
if (ret == WS_NEXT_ERROR) {
|
|
#else
|
|
/* reached end of directory */
|
|
if (sendCtx->entry == NULL) {
|
|
#endif
|
|
ret = ScpPopDir(ssh->fs, sendCtx, ssh->ctx->heap);
|
|
if (ret == WS_SUCCESS) {
|
|
ret = WS_SCP_EXIT_DIR;
|
|
|
|
} else if (ret == WS_SCP_DIR_STACK_EMPTY_E) {
|
|
ret = WS_SCP_EXIT_DIR_FINAL;
|
|
|
|
} else {
|
|
WLOG(WS_LOG_ERROR,
|
|
"scp: error popping directory, abort");
|
|
ret = WS_SCP_ABORT;
|
|
}
|
|
|
|
/* send exit directory msg or abort */
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ret != WS_BAD_ARGUMENT && sendCtx == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
ret = ScpProcessEntry(ssh, fileName,
|
|
mTime, aTime, fileMode, totalFileSz, buf,
|
|
bufSz, ctx, sendCtx);
|
|
}
|
|
break;
|
|
|
|
case WOLFSSH_SCP_CONTINUE_FILE_TRANSFER:
|
|
|
|
if (sendCtx == NULL) {
|
|
WLOG(WS_LOG_ERROR, "scp: sendCtx was null, abort");
|
|
ret = WS_SCP_ABORT;
|
|
break;
|
|
}
|
|
|
|
if (sendCtx->fp == NULL) {
|
|
WLOG(WS_LOG_ERROR, "scp: file has been closed, abort");
|
|
ret = WS_SCP_ABORT;
|
|
break;
|
|
}
|
|
|
|
ret = (word32)WFREAD(ssh->fs, buf, 1, bufSz, sendCtx->fp);
|
|
if (ret == 0) { /* handle case of EOF */
|
|
ret = WS_EOF;
|
|
}
|
|
|
|
if ((ret <= 0) || (fileOffset + ret == *totalFileSz)) {
|
|
WFCLOSE(ssh->fs, sendCtx->fp);
|
|
sendCtx->fp = NULL;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#else
|
|
|
|
/* single file transfer with no filesystem */
|
|
int wsScpRecvCallback(WOLFSSH* ssh, int state, const char* basePath,
|
|
const char* fileName, int fileMode, word64 mTime, word64 aTime,
|
|
word32 totalFileSz, byte* buf, word32 bufSz, word32 fileOffset,
|
|
void* ctx)
|
|
{
|
|
ScpBuffer* recvBuffer;
|
|
int ret = WS_SCP_CONTINUE;
|
|
int sz;
|
|
|
|
if (ctx == NULL) {
|
|
WLOG(WS_LOG_DEBUG, scpState, "SCP receive ctx not set");
|
|
return WS_SCP_ABORT;
|
|
}
|
|
recvBuffer = (ScpBuffer*)ctx;
|
|
|
|
switch (state) {
|
|
|
|
case WOLFSSH_SCP_NEW_REQUEST:
|
|
break;
|
|
|
|
case WOLFSSH_SCP_NEW_FILE:
|
|
/* create file */
|
|
sz = (int)WSTRLEN(fileName);
|
|
if (sz >= DEFAULT_SCP_FILE_NAME_SZ) {
|
|
WLOG(WS_LOG_ERROR, "scp: file name is too large, abort");
|
|
wolfSSH_SetScpErrorMsg(ssh, "file name is too large");
|
|
ret = WS_SCP_ABORT;
|
|
break;
|
|
}
|
|
WMEMCPY(recvBuffer->name, fileName, sz);
|
|
recvBuffer->mTime = mTime;
|
|
recvBuffer->mode = fileMode;
|
|
if (recvBuffer->status) {
|
|
if (recvBuffer->status(ssh, fileName, WOLFSSH_SCP_NEW_FILE,
|
|
recvBuffer) != WS_SUCCESS) {
|
|
WLOG(WS_LOG_ERROR, "scp: status of new file failed, abort");
|
|
ret = WS_SCP_ABORT;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WOLFSSH_SCP_FILE_PART:
|
|
/* read file, or file part */
|
|
sz = (bufSz < recvBuffer->bufferSz - recvBuffer->idx) ?
|
|
bufSz : recvBuffer->bufferSz - recvBuffer->idx;
|
|
|
|
if (recvBuffer->idx >= recvBuffer->bufferSz) {
|
|
wolfSSH_SetScpErrorMsg(ssh,
|
|
"buffer is not large enough for file");
|
|
WLOG(WS_LOG_ERROR, scpState, "SCP buffer too small for file");
|
|
ret = WS_SCP_ABORT;
|
|
break;
|
|
}
|
|
|
|
WMEMCPY(recvBuffer->buffer + recvBuffer->idx, buf, sz);
|
|
recvBuffer->idx += sz;
|
|
recvBuffer->fileSz += sz;
|
|
if (recvBuffer->status) {
|
|
if (recvBuffer->status(ssh, recvBuffer->name,
|
|
WOLFSSH_SCP_FILE_PART, recvBuffer) != WS_SUCCESS) {
|
|
WLOG(WS_LOG_ERROR, "scp: bad status, abort");
|
|
ret = WS_SCP_ABORT;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WOLFSSH_SCP_FILE_DONE:
|
|
recvBuffer->idx = 0; /* rewind when done */
|
|
recvBuffer->mTime = 0; /* @TODO set time if wanted */
|
|
if (recvBuffer->status) {
|
|
if (recvBuffer->status(ssh, recvBuffer->name,
|
|
WOLFSSH_SCP_FILE_DONE, recvBuffer) != WS_SUCCESS) {
|
|
WLOG(WS_LOG_ERROR, "scp: bad status, abort");
|
|
ret = WS_SCP_ABORT;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WOLFSSH_SCP_NEW_DIR:
|
|
case WOLFSSH_SCP_END_DIR:
|
|
WLOG(WS_LOG_ERROR,
|
|
"scp: creating a new directory not supported");
|
|
wolfSSH_SetScpErrorMsg(ssh,
|
|
"creating a new directory not supported");
|
|
ret = WS_SCP_ABORT;
|
|
break;
|
|
|
|
default:
|
|
WLOG(WS_LOG_ERROR, scpState,
|
|
"invalid scp command request");
|
|
wolfSSH_SetScpErrorMsg(ssh, "invalid scp command request");
|
|
ret = WS_SCP_ABORT;
|
|
}
|
|
|
|
WOLFSSH_UNUSED(totalFileSz);
|
|
WOLFSSH_UNUSED(fileOffset);
|
|
WOLFSSH_UNUSED(aTime);
|
|
WOLFSSH_UNUSED(basePath);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* callback for single file transfer with no file system */
|
|
int wsScpSendCallback(WOLFSSH* ssh, int state, const char* peerRequest,
|
|
char* fileName, word32 fileNameSz, word64* mTime, word64* aTime,
|
|
int* fileMode, word32 fileOffset, word32* totalFileSz, byte* buf,
|
|
word32 bufSz, void* ctx)
|
|
{
|
|
ScpBuffer* sendBuffer= NULL;
|
|
int ret = WS_SUCCESS;
|
|
|
|
if (ctx == NULL) {
|
|
WLOG(WS_LOG_DEBUG, scpState, "no ctx sent to hold file info");
|
|
return WS_SCP_ABORT;
|
|
}
|
|
sendBuffer = (ScpBuffer*)ctx;
|
|
|
|
switch (state) {
|
|
case WOLFSSH_SCP_NEW_REQUEST:
|
|
break;
|
|
|
|
case WOLFSSH_SCP_SINGLE_FILE_REQUEST:
|
|
if (sendBuffer->buffer == NULL) {
|
|
WLOG(WS_LOG_DEBUG, scpState, "no buffer to send");
|
|
ret = WS_SCP_ABORT;
|
|
break;
|
|
}
|
|
|
|
ret = ExtractFileName(peerRequest, fileName, fileNameSz);
|
|
if (ret == WS_SUCCESS && sendBuffer->status) {
|
|
if ( sendBuffer->status(ssh, fileName,
|
|
WOLFSSH_SCP_SINGLE_FILE_REQUEST, sendBuffer)
|
|
!= WS_SUCCESS) {
|
|
WLOG(WS_LOG_ERROR, scpState, "bad status of file, abort");
|
|
ret = WS_SCP_ABORT;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (WSTRLEN(fileName) != sendBuffer->nameSz ||
|
|
WMEMCMP(sendBuffer->name, fileName, sendBuffer->nameSz) != 0) {
|
|
WLOG(WS_LOG_ERROR, scpState, "file name did not match, abort");
|
|
wolfSSH_SetScpErrorMsg(ssh, "file name did not match");
|
|
ret = WS_SCP_ABORT;
|
|
break;
|
|
}
|
|
*totalFileSz = sendBuffer->fileSz;
|
|
*mTime = sendBuffer->mTime;
|
|
*aTime = sendBuffer->mTime;
|
|
*fileMode = sendBuffer->mode;
|
|
|
|
/* copy over buffer info */
|
|
ret = (bufSz < (sendBuffer->fileSz - sendBuffer->idx))?
|
|
bufSz : sendBuffer->fileSz - sendBuffer->idx;
|
|
if (sendBuffer->idx + ret >= sendBuffer->bufferSz) {
|
|
WLOG(WS_LOG_ERROR, scpState,
|
|
"potential buffer overflow caught, abort");
|
|
ret = WS_SCP_ABORT;
|
|
break;
|
|
}
|
|
WMEMCPY(buf, sendBuffer->buffer + sendBuffer->idx, ret);
|
|
sendBuffer->idx += ret;
|
|
|
|
break;
|
|
|
|
case WOLFSSH_SCP_RECURSIVE_REQUEST:
|
|
WLOG(WS_LOG_ERROR, scpState,
|
|
"recursive request without filesystem not supported, abort");
|
|
wolfSSH_SetScpErrorMsg(ssh,
|
|
"recursive request without filesystem not supported");
|
|
ret = WS_SCP_ABORT;
|
|
break;
|
|
|
|
case WOLFSSH_SCP_CONTINUE_FILE_TRANSFER:
|
|
/* copy over buffer info */
|
|
if (sendBuffer->idx >= sendBuffer->bufferSz) {
|
|
WLOG(WS_LOG_ERROR, scpState,
|
|
"sendbuffer idx greater than buffer size, abort");
|
|
ret = WS_SCP_ABORT;
|
|
break;
|
|
}
|
|
ret = (bufSz < (sendBuffer->fileSz - sendBuffer->idx))?
|
|
bufSz : sendBuffer->fileSz - sendBuffer->idx;
|
|
if (ret > 0) {
|
|
if (sendBuffer->idx + ret >= sendBuffer->bufferSz) {
|
|
ret = WS_SCP_ABORT;
|
|
WLOG(WS_LOG_ERROR, scpState, "buffer size issue, abort");
|
|
break;
|
|
}
|
|
WMEMCPY(buf, sendBuffer->buffer + sendBuffer->idx, ret);
|
|
sendBuffer->idx += ret;
|
|
}
|
|
if (ret == 0) { /* handle case of EOF */
|
|
ret = WS_EOF;
|
|
}
|
|
|
|
if (sendBuffer->status(ssh, sendBuffer->name,
|
|
WOLFSSH_SCP_CONTINUE_FILE_TRANSFER, sendBuffer)
|
|
!= WS_SUCCESS) {
|
|
WLOG(WS_LOG_DEBUG, scpState, "continue status fail, abort");
|
|
ret = WS_SCP_ABORT;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
WLOG(WS_LOG_DEBUG, scpState, "bad state");
|
|
ret = WS_SCP_ABORT;
|
|
}
|
|
WOLFSSH_UNUSED(fileOffset);
|
|
|
|
return ret;
|
|
}
|
|
#endif /* NO_FILESYSTEM */
|
|
#endif /* WOLFSSH_SCP_USER_CALLBACKS */
|
|
|
|
#endif /* WOLFSSH_SCP */
|