diff options
author | Andreas Schneider <asn@cynapses.org> | 2010-09-06 14:28:38 +0200 |
---|---|---|
committer | Andreas Schneider <asn@cynapses.org> | 2010-09-06 14:28:38 +0200 |
commit | f7842e3a4b9acea2126ff725f993c299aef0e6db (patch) | |
tree | 18239f819a5edbcfc7f2961c48f3f9297314ef22 /src/scp.c | |
parent | 38421403d2dc45636e597f2a909daa6ae31976de (diff) | |
download | libssh-f7842e3a4b9acea2126ff725f993c299aef0e6db.tar.gz libssh-f7842e3a4b9acea2126ff725f993c299aef0e6db.tar.xz libssh-f7842e3a4b9acea2126ff725f993c299aef0e6db.zip |
misc: Rename libssh/ to src/
Diffstat (limited to 'src/scp.c')
-rw-r--r-- | src/scp.c | 752 |
1 files changed, 752 insertions, 0 deletions
diff --git a/src/scp.c b/src/scp.c new file mode 100644 index 00000000..4a6f6f14 --- /dev/null +++ b/src/scp.c @@ -0,0 +1,752 @@ +/* + * scp - SSH scp wrapper functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis <aris@0xbadc0de.be> + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library 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 Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include <stdio.h> +#include <string.h> + +#include "libssh/priv.h" +#include "libssh/scp.h" + +/** + * @defgroup libssh_scp The SSH scp functions + * @ingroup libssh + * + * SCP protocol over SSH functions + * + * @{ + */ + +/** + * @brief Create a new scp session. + * + * @param[in] session The SSH session to use. + * + * @param[in] mode One of SSH_SCP_WRITE or SSH_SCP_READ, depending if you + * need to drop files remotely or read them. + * It is not possible to combine read and write. + * + * @param[in] location The directory in which write or read will be done. Any + * push or pull will be relative to this place. + * + * @returns A ssh_scp handle, NULL if the creation was impossible. + */ +ssh_scp ssh_scp_new(ssh_session session, int mode, const char *location){ + ssh_scp scp=malloc(sizeof(struct ssh_scp_struct)); + if(scp == NULL){ + ssh_set_error(session,SSH_FATAL,"Error allocating memory for ssh_scp"); + return NULL; + } + ZERO_STRUCTP(scp); + if((mode&~SSH_SCP_RECURSIVE) != SSH_SCP_WRITE && (mode &~SSH_SCP_RECURSIVE) != SSH_SCP_READ){ + ssh_set_error(session,SSH_FATAL,"Invalid mode %d for ssh_scp_new()",mode); + ssh_scp_free(scp); + return NULL; + } + scp->location=strdup(location); + if (scp->location == NULL) { + ssh_set_error(session,SSH_FATAL,"Error allocating memory for ssh_scp"); + ssh_scp_free(scp); + return NULL; + } + scp->session=session; + scp->mode=mode & ~SSH_SCP_RECURSIVE; + scp->recursive = (mode & SSH_SCP_RECURSIVE) != 0; + scp->channel=NULL; + scp->state=SSH_SCP_NEW; + return scp; +} + +int ssh_scp_init(ssh_scp scp){ + int r; + char execbuffer[1024]; + uint8_t code; + if(scp==NULL) + return SSH_ERROR; + if(scp->state != SSH_SCP_NEW){ + ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_init called under invalid state"); + return SSH_ERROR; + } + ssh_log(scp->session,SSH_LOG_PROTOCOL,"Initializing scp session %s %son location '%s'", + scp->mode==SSH_SCP_WRITE?"write":"read", + scp->recursive?"recursive ":"", + scp->location); + scp->channel=ssh_channel_new(scp->session); + if(scp->channel == NULL){ + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + r= ssh_channel_open_session(scp->channel); + if(r==SSH_ERROR){ + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + if(scp->mode == SSH_SCP_WRITE) + snprintf(execbuffer,sizeof(execbuffer),"scp -t %s %s", + scp->recursive ? "-r":"", scp->location); + else + snprintf(execbuffer,sizeof(execbuffer),"scp -f %s %s", + scp->recursive ? "-r":"", scp->location); + if(ssh_channel_request_exec(scp->channel,execbuffer) == SSH_ERROR){ + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + if(scp->mode == SSH_SCP_WRITE){ + r=ssh_channel_read(scp->channel,&code,1,0); + if(r<=0){ + ssh_set_error(scp->session,SSH_FATAL, "Error reading status code: %s",ssh_get_error(scp->session)); + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + if(code != 0){ + ssh_set_error(scp->session,SSH_FATAL, "scp status code %ud not valid", code); + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + } else { + ssh_channel_write(scp->channel,"",1); + } + if(scp->mode == SSH_SCP_WRITE) + scp->state=SSH_SCP_WRITE_INITED; + else + scp->state=SSH_SCP_READ_INITED; + return SSH_OK; +} + +int ssh_scp_close(ssh_scp scp){ + char buffer[128]; + int err; + if(scp==NULL) + return SSH_ERROR; + if(scp->channel != NULL){ + if(ssh_channel_send_eof(scp->channel) == SSH_ERROR){ + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + /* avoid situations where data are buffered and + * not yet stored on disk. This can happen if the close is sent + * before we got the EOF back + */ + while(!ssh_channel_is_eof(scp->channel)){ + err=ssh_channel_read(scp->channel,buffer,sizeof(buffer),0); + if(err==SSH_ERROR) + break; + } + if(ssh_channel_close(scp->channel) == SSH_ERROR){ + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + ssh_channel_free(scp->channel); + scp->channel=NULL; + } + scp->state=SSH_SCP_NEW; + return SSH_OK; +} + +void ssh_scp_free(ssh_scp scp){ + if(scp==NULL) + return; + if(scp->state != SSH_SCP_NEW) + ssh_scp_close(scp); + if(scp->channel) + ssh_channel_free(scp->channel); + SAFE_FREE(scp->location); + SAFE_FREE(scp->request_name); + SAFE_FREE(scp->warning); + SAFE_FREE(scp); +} + +/** + * @brief Create a directory in a scp in sink mode. + * + * @param[in] scp The scp handle. + * + * @param[in] dirname The name of the directory being created. + * + * @param[in] mode The UNIX permissions for the new directory, e.g. 0755. + * + * @returns SSH_OK if the directory has been created, SSH_ERROR if + * an error occured. + * + * @see ssh_scp_leave_directory() + */ +int ssh_scp_push_directory(ssh_scp scp, const char *dirname, int mode){ + char buffer[1024]; + int r; + uint8_t code; + char *dir; + char *perms; + if(scp==NULL) + return SSH_ERROR; + if(scp->state != SSH_SCP_WRITE_INITED){ + ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_push_directory called under invalid state"); + return SSH_ERROR; + } + dir=ssh_basename(dirname); + perms=ssh_scp_string_mode(mode); + snprintf(buffer, sizeof(buffer), "D%s 0 %s\n", perms, dir); + SAFE_FREE(dir); + SAFE_FREE(perms); + r=ssh_channel_write(scp->channel,buffer,strlen(buffer)); + if(r==SSH_ERROR){ + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + r=ssh_channel_read(scp->channel,&code,1,0); + if(r<=0){ + ssh_set_error(scp->session,SSH_FATAL, "Error reading status code: %s",ssh_get_error(scp->session)); + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + if(code != 0){ + ssh_set_error(scp->session,SSH_FATAL, "scp status code %ud not valid", code); + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + return SSH_OK; +} + +/** + * @brief Leave a directory. + * + * @returns SSH_OK if the directory has been left,SSH_ERROR if an + * error occured. + * + * @see ssh_scp_push_directory() + */ + int ssh_scp_leave_directory(ssh_scp scp){ + char buffer[]="E\n"; + int r; + uint8_t code; + if(scp==NULL) + return SSH_ERROR; + if(scp->state != SSH_SCP_WRITE_INITED){ + ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_leave_directory called under invalid state"); + return SSH_ERROR; + } + r=ssh_channel_write(scp->channel,buffer,strlen(buffer)); + if(r==SSH_ERROR){ + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + r=ssh_channel_read(scp->channel,&code,1,0); + if(r<=0){ + ssh_set_error(scp->session,SSH_FATAL, "Error reading status code: %s",ssh_get_error(scp->session)); + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + if(code != 0){ + ssh_set_error(scp->session,SSH_FATAL, "scp status code %ud not valid", code); + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + return SSH_OK; +} + +/** + * @brief Initialize the sending of a file to a scp in sink mode. + * + * @param[in] scp The scp handle. + * + * @param[in] filename The name of the file being sent. It should not contain + * any path indicator + * + * @param[in] size Exact size in bytes of the file being sent. + * + * @param[in] mode The UNIX permissions for the new file, e.g. 0644. + * + * @returns SSH_OK if the file is ready to be sent, SSH_ERROR if an + * error occured. + */ +int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, int mode){ + char buffer[1024]; + int r; + uint8_t code; + char *file; + char *perms; + if(scp==NULL) + return SSH_ERROR; + if(scp->state != SSH_SCP_WRITE_INITED){ + ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_push_file called under invalid state"); + return SSH_ERROR; + } + file=ssh_basename(filename); + perms=ssh_scp_string_mode(mode); + ssh_log(scp->session,SSH_LOG_PROTOCOL,"SCP pushing file %s, size %" PRIdS " with permissions '%s'",file,size,perms); + snprintf(buffer, sizeof(buffer), "C%s %" PRIdS " %s\n", perms, size, file); + SAFE_FREE(file); + SAFE_FREE(perms); + r=ssh_channel_write(scp->channel,buffer,strlen(buffer)); + if(r==SSH_ERROR){ + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + r=ssh_channel_read(scp->channel,&code,1,0); + if(r<=0){ + ssh_set_error(scp->session,SSH_FATAL, "Error reading status code: %s",ssh_get_error(scp->session)); + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + if(code != 0){ + ssh_set_error(scp->session,SSH_FATAL, "scp status code %ud not valid", code); + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + scp->filelen = size; + scp->processed = 0; + scp->state=SSH_SCP_WRITE_WRITING; + return SSH_OK; +} + +/** + * @internal + * + * @brief Wait for a response of the scp server. + * + * @param[in] scp The scp handle. + * + * @param[out] response A pointer where the response message must be copied if + * any. This pointer must then be free'd. + * + * @returns The return code, SSH_ERROR a error occured. + */ +int ssh_scp_response(ssh_scp scp, char **response){ + unsigned char code; + int r; + char msg[128]; + if(scp==NULL) + return SSH_ERROR; + r=ssh_channel_read(scp->channel,&code,1,0); + if(r == SSH_ERROR) + return SSH_ERROR; + if(code == 0) + return 0; + if(code > 2){ + ssh_set_error(scp->session,SSH_FATAL, "SCP: invalid status code %ud received", code); + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + r=ssh_scp_read_string(scp,msg,sizeof(msg)); + if(r==SSH_ERROR) + return r; + /* Warning */ + if(code == 1){ + ssh_set_error(scp->session,SSH_REQUEST_DENIED, "SCP: Warning: status code 1 received: %s", msg); + ssh_log(scp->session,SSH_LOG_RARE,"SCP: Warning: status code 1 received: %s", msg); + if(response) + *response=strdup(msg); + return 1; + } + if(code == 2){ + ssh_set_error(scp->session,SSH_FATAL, "SCP: Error: status code 2 received: %s", msg); + if(response) + *response=strdup(msg); + return 2; + } + /* Not reached */ + return SSH_ERROR; +} + +/** + * @brief Write into a remote scp file. + * + * @param[in] scp The scp handle. + * + * @param[in] buffer The buffer to write. + * + * @param[in] len The number of bytes to write. + * + * @returns SSH_OK if the write was successful, SSH_ERROR an error + * occured while writing. + */ +int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len){ + int w; + //int r; + //uint8_t code; + if(scp==NULL) + return SSH_ERROR; + if(scp->state != SSH_SCP_WRITE_WRITING){ + ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_write called under invalid state"); + return SSH_ERROR; + } + if(scp->processed + len > scp->filelen) + len = scp->filelen - scp->processed; + /* hack to avoid waiting for window change */ + ssh_channel_poll(scp->channel,0); + w=ssh_channel_write(scp->channel,buffer,len); + if(w != SSH_ERROR) + scp->processed += w; + else { + scp->state=SSH_SCP_ERROR; + //return=channel_get_exit_status(scp->channel); + return SSH_ERROR; + } + /* Check if we arrived at end of file */ + if(scp->processed == scp->filelen) { +/* r=channel_read(scp->channel,&code,1,0); + if(r==SSH_ERROR){ + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + if(code != 0){ + ssh_set_error(scp->session,SSH_FATAL, "scp status code %ud not valid", code); + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } +*/ + scp->processed=scp->filelen=0; + scp->state=SSH_SCP_WRITE_INITED; + } + return SSH_OK; +} + +/** + * @brief Read a string on a channel, terminated by '\n' + * + * @param[in] scp The scp handle. + * + * @param[out] buffer A pointer to a buffer to place the string. + * + * @param[in] len The size of the buffer in bytes. If the string is bigger + * than len-1, only len-1 bytes are read and the string is + * null-terminated. + * + * @returns SSH_OK if the string was read, SSH_ERROR if an error + * occured while reading. + */ +int ssh_scp_read_string(ssh_scp scp, char *buffer, size_t len){ + size_t r=0; + int err=SSH_OK; + if(scp==NULL) + return SSH_ERROR; + while(r<len-1){ + err=ssh_channel_read(scp->channel,&buffer[r],1,0); + if(err==SSH_ERROR){ + break; + } + if(err==0){ + ssh_set_error(scp->session,SSH_FATAL,"End of file while reading string"); + err=SSH_ERROR; + break; + } + r++; + if(buffer[r-1] == '\n') + break; + } + buffer[r]=0; + return err; +} + +/** + * @brief Wait for a scp request (file, directory). + * + * @returns SSH_SCP_REQUEST_NEWFILE: The other side is sending + * a file + * SSH_SCP_REQUEST_NEWDIRECTORY: The other side is sending + * a directory + * SSH_SCP_REQUEST_END_DIRECTORY: The other side has + * finished with the current + * directory + * SSH_ERROR: Some error happened + * + * @see ssh_scp_read() + * @see ssh_scp_deny_request() + * @see ssh_scp_accept_request() + */ +int ssh_scp_pull_request(ssh_scp scp){ + char buffer[4096]; + char *mode=NULL; + char *p,*tmp; + size_t size; + char *name=NULL; + int err; + if(scp==NULL) + return SSH_ERROR; + if(scp->state != SSH_SCP_READ_INITED){ + ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_pull_request called under invalid state"); + return SSH_ERROR; + } + err=ssh_scp_read_string(scp,buffer,sizeof(buffer)); + if(err==SSH_ERROR){ + if(ssh_channel_is_eof(scp->channel)){ + scp->state=SSH_SCP_TERMINATED; + return SSH_SCP_REQUEST_EOF; + } + return err; + } + p=strchr(buffer,'\n'); + if(p!=NULL) + *p='\0'; + ssh_log(scp->session,SSH_LOG_PROTOCOL,"Received SCP request: '%s'",buffer); + switch(buffer[0]){ + case 'C': + /* File */ + case 'D': + /* Directory */ + p=strchr(buffer,' '); + if(p==NULL) + goto error; + *p='\0'; + p++; + //mode=strdup(&buffer[1]); + scp->request_mode=ssh_scp_integer_mode(&buffer[1]); + tmp=p; + p=strchr(p,' '); + if(p==NULL) + goto error; + *p=0; + size=strtoull(tmp,NULL,10); + p++; + name=strdup(p); + SAFE_FREE(scp->request_name); + scp->request_name=name; + if(buffer[0]=='C'){ + scp->filelen=size; + scp->request_type=SSH_SCP_REQUEST_NEWFILE; + } else { + scp->filelen='0'; + scp->request_type=SSH_SCP_REQUEST_NEWDIR; + } + scp->state=SSH_SCP_READ_REQUESTED; + scp->processed = 0; + return scp->request_type; + break; + case 'E': + scp->request_type=SSH_SCP_REQUEST_ENDDIR; + ssh_channel_write(scp->channel,"",1); + return scp->request_type; + case 0x1: + ssh_set_error(scp->session,SSH_REQUEST_DENIED,"SCP: Warning: %s",&buffer[1]); + scp->request_type=SSH_SCP_REQUEST_WARNING; + SAFE_FREE(scp->warning); + scp->warning=strdup(&buffer[1]); + return scp->request_type; + case 0x2: + ssh_set_error(scp->session,SSH_FATAL,"SCP: Error: %s",&buffer[1]); + return SSH_ERROR; + case 'T': + /* Timestamp */ + default: + ssh_set_error(scp->session,SSH_FATAL,"Unhandled message: (%d)%s",buffer[0],buffer); + return SSH_ERROR; + } + + /* a parsing error occured */ + error: + SAFE_FREE(name); + SAFE_FREE(mode); + ssh_set_error(scp->session,SSH_FATAL,"Parsing error while parsing message: %s",buffer); + return SSH_ERROR; +} + +/** + * @brief Deny the transfer of a file or creation of a directory coming from the + * remote party. + * + * @param[in] scp The scp handle. + * @param[in] reason A nul-terminated string with a human-readable + * explanation of the deny. + * + * @returns SSH_OK if the message was sent, SSH_ERROR if the sending + * the message failed, or sending it in a bad state. + */ +int ssh_scp_deny_request(ssh_scp scp, const char *reason){ + char buffer[4096]; + int err; + if(scp==NULL) + return SSH_ERROR; + if(scp->state != SSH_SCP_READ_REQUESTED){ + ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_deny_request called under invalid state"); + return SSH_ERROR; + } + snprintf(buffer,sizeof(buffer),"%c%s\n",2,reason); + err=ssh_channel_write(scp->channel,buffer,strlen(buffer)); + if(err==SSH_ERROR) { + return SSH_ERROR; + } + else { + scp->state=SSH_SCP_READ_INITED; + return SSH_OK; + } +} + +/** + * @brief Accepts transfer of a file or creation of a directory coming from the + * remote party. + * + * @param[in] scp The scp handle. + * + * @returns SSH_OK if the message was sent, SSH_ERROR if sending the + * message failed, or sending it in a bad state. + */ +int ssh_scp_accept_request(ssh_scp scp){ + char buffer[]={0x00}; + int err; + if(scp==NULL) + return SSH_ERROR; + if(scp->state != SSH_SCP_READ_REQUESTED){ + ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_deny_request called under invalid state"); + return SSH_ERROR; + } + err=ssh_channel_write(scp->channel,buffer,1); + if(err==SSH_ERROR) { + return SSH_ERROR; + } + if(scp->request_type==SSH_SCP_REQUEST_NEWFILE) + scp->state=SSH_SCP_READ_READING; + else + scp->state=SSH_SCP_READ_INITED; + return SSH_OK; +} + +/** @brief Read from a remote scp file + * @param[in] scp The scp handle. + * + * @param[in] buffer The destination buffer. + * + * @param[in] size The size of the buffer. + * + * @returns The nNumber of bytes read, SSH_ERROR if an error occured + * while reading. + */ +int ssh_scp_read(ssh_scp scp, void *buffer, size_t size){ + int r; + int code; + if(scp==NULL) + return SSH_ERROR; + if(scp->state == SSH_SCP_READ_REQUESTED && scp->request_type == SSH_SCP_REQUEST_NEWFILE){ + r=ssh_scp_accept_request(scp); + if(r==SSH_ERROR) + return r; + } + if(scp->state != SSH_SCP_READ_READING){ + ssh_set_error(scp->session,SSH_FATAL,"ssh_scp_read called under invalid state"); + return SSH_ERROR; + } + if(scp->processed + size > scp->filelen) + size = scp->filelen - scp->processed; + if(size > 65536) + size=65536; /* avoid too large reads */ + r=ssh_channel_read(scp->channel,buffer,size,0); + if(r != SSH_ERROR) + scp->processed += r; + else { + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + /* Check if we arrived at end of file */ + if(scp->processed == scp->filelen) { + scp->processed=scp->filelen=0; + ssh_channel_write(scp->channel,"",1); + code=ssh_scp_response(scp,NULL); + if(code == 0){ + scp->state=SSH_SCP_READ_INITED; + return r; + } + if(code==1){ + scp->state=SSH_SCP_READ_INITED; + return SSH_ERROR; + } + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + return r; +} + +/** + * @brief Get the name of the directory or file being pushed from the other + * party. + * + * @returns The file name, NULL on error. The string should not be + * freed. + */ +const char *ssh_scp_request_get_filename(ssh_scp scp){ + if(scp==NULL) + return NULL; + return scp->request_name; +} + +/** + * @brief Get the permissions of the directory or file being pushed from the + * other party. + * + * @returns The UNIX permission, e.g 0644, -1 on error. + */ +int ssh_scp_request_get_permissions(ssh_scp scp){ + if(scp==NULL) + return -1; + return scp->request_mode; +} + +/** @brief Get the size of the file being pushed from the other party. + * + * @returns The numeric size of the file being read. + */ +size_t ssh_scp_request_get_size(ssh_scp scp){ + if(scp==NULL) + return 0; + return scp->filelen; +} + +/** + * @brief Convert a scp text mode to an integer. + * + * @param[in] mode The mode to convert, e.g. "0644". + * + * @returns An integer value, e.g. 420 for "0644". + */ +int ssh_scp_integer_mode(const char *mode){ + int value=strtoul(mode,NULL,8) & 0xffff; + return value; +} + +/** + * @brief Convert a unix mode into a scp string. + * + * @param[in] mode The mode to convert, e.g. 420 or 0644. + * + * @returns A pointer to a malloc'ed string containing the scp mode, + * e.g. "0644". + */ +char *ssh_scp_string_mode(int mode){ + char buffer[16]; + snprintf(buffer,sizeof(buffer),"%.4o",mode); + return strdup(buffer); +} + +/** + * @brief Get the warning string from a scp handle. + * + * @param[in] scp The scp handle. + * + * @returns A warning string, or NULL on error. The string should + * not be freed. + */ +const char *ssh_scp_request_get_warning(ssh_scp scp){ + if(scp==NULL) + return NULL; + return scp->warning; +} + +/** @} */ + +/* vim: set ts=4 sw=4 et cindent: */ |