diff options
author | Aris Adamantiadis <aris@0xbadc0de.be> | 2009-08-23 16:41:29 +0200 |
---|---|---|
committer | Aris Adamantiadis <aris@0xbadc0de.be> | 2009-08-23 16:41:29 +0200 |
commit | d4bc6fa954f11b1da0c8881c2826ac4a60f8c41e (patch) | |
tree | c24b7b1ec877a102b86a3af47e01bf40e04cb4ac /libssh | |
parent | 8bae43876fff891e33d48b177778ee4cb882c45a (diff) | |
parent | fbfea94559aa776bca7983ef989d024c2d3b72fe (diff) | |
download | libssh-d4bc6fa954f11b1da0c8881c2826ac4a60f8c41e.tar.gz libssh-d4bc6fa954f11b1da0c8881c2826ac4a60f8c41e.tar.xz libssh-d4bc6fa954f11b1da0c8881c2826ac4a60f8c41e.zip |
Merge branch 'master' of git://git.libssh.org/projects/libssh/libssh
Conflicts:
include/libssh/priv.h
Diffstat (limited to 'libssh')
-rw-r--r-- | libssh/CMakeLists.txt | 8 | ||||
-rw-r--r-- | libssh/channels.c | 213 | ||||
-rw-r--r-- | libssh/connect.c | 18 | ||||
-rw-r--r-- | libssh/keys.c | 1 | ||||
-rw-r--r-- | libssh/misc.c | 108 | ||||
-rw-r--r-- | libssh/packet.c | 1 | ||||
-rw-r--r-- | libssh/scp.c | 81 | ||||
-rw-r--r-- | libssh/sftp.c | 56 |
8 files changed, 433 insertions, 53 deletions
diff --git a/libssh/CMakeLists.txt b/libssh/CMakeLists.txt index 2fc8adbb..fc355bfd 100644 --- a/libssh/CMakeLists.txt +++ b/libssh/CMakeLists.txt @@ -35,19 +35,19 @@ if (WIN32) ) endif (WIN32) -if (HAVE_LIB_GETHOSTBYNAME) +if (HAVE_LIBNSL) set(LIBSSH_LINK_LIBRARIES ${LIBSSH_LINK_LIBRARIES} nsl ) -endif (HAVE_LIB_GETHOSTBYNAME) +endif (HAVE_LIBNSL) -if (HAVE_LIB_GETADDRINFO) +if (HAVE_LIBSOCKET) set(LIBSSH_LINK_LIBRARIES ${LIBSSH_LINK_LIBRARIES} socket ) -endif (HAVE_LIB_GETADDRINFO) +endif (HAVE_LIBSOCKET) if (CRYPTO_LIBRARY) set(LIBSSH_PRIVATE_INCLUDE_DIRS diff --git a/libssh/channels.c b/libssh/channels.c index ac49b381..6e70285c 100644 --- a/libssh/channels.c +++ b/libssh/channels.c @@ -1057,7 +1057,7 @@ static int channel_request(ssh_channel channel, const char *request, } rc = packet_wait(session, SSH2_MSG_CHANNEL_SUCCESS, 1); - if (rc) { + if (rc == SSH_ERROR) { if (session->in_packet.type == SSH2_MSG_CHANNEL_FAILURE) { ssh_log(session, SSH_LOG_PACKET, "%s channel request failed", request); @@ -1335,19 +1335,14 @@ error: return rc; } +static ssh_channel channel_accept(ssh_session session, int channeltype, + int timeout_ms) { #ifndef _WIN32 -/** - * @brief Accept an X11 forwarding channel. - * - * @param channel An x11-enabled session channel. - * - * @param timeout_ms Timeout in milli-seconds. - * - * @return Newly created channel, or NULL if no X11 request from the server - */ -ssh_channel channel_accept_x11(ssh_channel channel, int timeout_ms) { - static const struct timespec ts = {0, 50000000}; /* 50ms */ - SSH_SESSION *session = channel->session; + static const struct timespec ts = { + .tv_sec = 0, + .tv_nsec = 50000000 /* 50ms */ + }; +#endif SSH_MESSAGE *msg = NULL; struct ssh_iterator *iterator; int t; @@ -1361,19 +1356,207 @@ ssh_channel channel_accept_x11(ssh_channel channel, int timeout_ms) { while (iterator) { msg = (SSH_MESSAGE*)iterator->data; if (ssh_message_type(msg) == SSH_REQUEST_CHANNEL_OPEN && - ssh_message_subtype(msg) == SSH_CHANNEL_X11) { + ssh_message_subtype(msg) == channeltype) { ssh_list_remove(session->ssh_message_list, iterator); return ssh_message_channel_request_open_reply_accept(msg); } iterator = iterator->next; } } +#ifdef _WIN32 + Sleep(50); /* 50ms */ +#else nanosleep(&ts, NULL); +#endif } return NULL; } -#endif + +/** + * @brief Accept an X11 forwarding channel. + * + * @param channel An x11-enabled session channel. + * + * @param timeout_ms Timeout in milli-seconds. + * + * @return Newly created channel, or NULL if no X11 request from the server + */ +ssh_channel channel_accept_x11(ssh_channel channel, int timeout_ms) { + return channel_accept(channel->session, SSH_CHANNEL_X11, timeout_ms); +} + +static int global_request(ssh_session session, const char *request, + ssh_buffer buffer, int reply) { + ssh_string req = NULL; + int rc = SSH_ERROR; + + enter_function(); + + req = string_from_char(request); + if (req == NULL) { + goto error; + } + + if (buffer_add_u8(session->out_buffer, SSH2_MSG_GLOBAL_REQUEST) < 0 || + buffer_add_ssh_string(session->out_buffer, req) < 0 || + buffer_add_u8(session->out_buffer, reply == 0 ? 0 : 1) < 0) { + goto error; + } + string_free(req); + + if (buffer != NULL) { + if (buffer_add_data(session->out_buffer, buffer_get(buffer), + buffer_get_len(buffer)) < 0) { + goto error; + } + } + + if (packet_send(session) != SSH_OK) { + leave_function(); + return rc; + } + + ssh_log(session, SSH_LOG_RARE, + "Sent a SSH_MSG_GLOBAL_REQUEST %s", request); + if (reply == 0) { + leave_function(); + return SSH_OK; + } + + rc = packet_wait(session, SSH2_MSG_REQUEST_SUCCESS, 1); + if (rc == SSH_ERROR) { + if (session->in_packet.type == SSH2_MSG_REQUEST_FAILURE) { + ssh_log(session, SSH_LOG_PACKET, + "%s channel request failed", request); + ssh_set_error(session, SSH_REQUEST_DENIED, + "Channel request %s failed", request); + } else { + ssh_log(session, SSH_LOG_RARE, + "Received an unexpected %d message", session->in_packet.type); + } + } else { + ssh_log(session, SSH_LOG_RARE, "Received a SUCCESS"); + } + + leave_function(); + return rc; +error: + buffer_reinit(session->out_buffer); + string_free(req); + + leave_function(); + return rc; +} + +/** + * @brief Sends the "tcpip-forward" global request to ask the server to begin + * listening for inbound connections. + * + * @param session The ssh session to send the request. + * + * @param address The address to bind to on the server. Pass NULL to bind + * to all available addresses on all protocol families + * supported by the server. + * + * @param port The port to bind to on the server. Pass 0 to ask the + * server to allocate the next available unprivileged port + * number + * + * @param bound_port The pointer to get actual bound port. Pass NULL to + * ignore. + * + * @return SSH_OK on success\n + * SSH_ERROR on error + */ +int channel_forward_listen(ssh_session session, const char *address, int port, int *bound_port) { + ssh_buffer buffer = NULL; + ssh_string addr = NULL; + int rc = SSH_ERROR; + uint32_t tmp; + + buffer = buffer_new(); + if (buffer == NULL) { + goto error; + } + + addr = string_from_char(address ? address : ""); + if (addr == NULL) { + goto error; + } + + if (buffer_add_ssh_string(buffer, addr) < 0 || + buffer_add_u32(buffer, htonl(port)) < 0) { + goto error; + } + + rc = global_request(session, "tcpip-forward", buffer, 1); + + if (rc == SSH_OK && port == 0 && bound_port) { + buffer_get_u32(session->in_buffer, &tmp); + *bound_port = ntohl(tmp); + } + +error: + buffer_free(buffer); + string_free(addr); + return rc; +} + +/** + * @brief Accept an incoming TCP/IP forwarding channel. + * + * @param session The ssh session to use. + * + * @param timeout_ms Timeout in milli-seconds. + * + * @return Newly created channel, or NULL if no incoming channel request from + * the server + */ +ssh_channel channel_forward_accept(ssh_session session, int timeout_ms) { + return channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms); +} + +/** + * @brief Sends the "cancel-tcpip-forward" global request to ask the server to + * cancel the tcpip-forward request. + * + * @param session The ssh session to send the request. + * + * @param address The bound address on the server. + * + * @param port The bound port on the server. + * + * @return SSH_OK on success\n + * SSH_ERROR on error + */ +int channel_forward_cancel(ssh_session session, const char *address, int port) { + ssh_buffer buffer = NULL; + ssh_string addr = NULL; + int rc = SSH_ERROR; + + buffer = buffer_new(); + if (buffer == NULL) { + goto error; + } + + addr = string_from_char(address ? address : ""); + if (addr == NULL) { + goto error; + } + + if (buffer_add_ssh_string(buffer, addr) < 0 || + buffer_add_u32(buffer, htonl(port)) < 0) { + goto error; + } + + rc = global_request(session, "cancel-tcpip-forward", buffer, 1); + +error: + buffer_free(buffer); + string_free(addr); + return rc; +} /** * @brief Set environement variables. diff --git a/libssh/connect.c b/libssh/connect.c index e08cd16c..d8898ef6 100644 --- a/libssh/connect.c +++ b/libssh/connect.c @@ -28,13 +28,25 @@ #include <string.h> #ifdef _WIN32 -/* getaddrinfo, freeaddrinfo, getnameinfo */ -#define _WIN32_WINNT 0x0501 +/* + * Only use Windows API functions available on Windows 2000 SP4 or later. + * The available constants are in <sdkddkver.h>. + * http://msdn.microsoft.com/en-us/library/aa383745.aspx + * http://blogs.msdn.com/oldnewthing/archive/2007/04/11/2079137.aspx + */ +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0500 /* _WIN32_WINNT_WIN2K */ +#undef NTDDI_VERSION +#define NTDDI_VERSION 0x05000400 /* NTDDI_WIN2KSP4 */ #include <winsock2.h> #include <ws2tcpip.h> -#include "wspiapi.h" /* Workaround for w2k systems */ +/* <wspiapi.h> is necessary for getaddrinfo before Windows XP, but it isn't + * available on some platforms like MinGW. */ +#ifdef HAVE_WSPIAPI_H +#include <wspiapi.h> +#endif #else /* _WIN32 */ diff --git a/libssh/keys.c b/libssh/keys.c index 1382b765..626acc97 100644 --- a/libssh/keys.c +++ b/libssh/keys.c @@ -542,7 +542,6 @@ static int dsa_public_to_string(DSA *key, ssh_buffer buffer) { goto error; } string_fill(n, (char *) tmp, size); - gcry_sexp_release(sexp); #elif defined HAVE_LIBCRYPTO p = make_bignum_string(key->p); diff --git a/libssh/misc.c b/libssh/misc.c index 03fd3c38..5fbd28e2 100644 --- a/libssh/misc.c +++ b/libssh/misc.c @@ -238,6 +238,114 @@ const void *_ssh_list_get_head(struct ssh_list *list){ return data; } +/** + * @brief Parse directory component. + * + * dirname breaks a null-terminated pathname string into a directory component. + * In the usual case, ssh_dirname() returns the string up to, but not including, + * the final '/'. Trailing '/' characters are not counted as part of the + * pathname. The caller must free the memory. + * + * @param path The path to parse. + * + * @return The dirname of path or NULL if we can't allocate memory. If path + * does not contain a slash, c_dirname() returns the string ".". If + * path is the string "/", it returns the string "/". If path is + * NULL or an empty string, "." is returned. + */ +char *ssh_dirname (const char *path) { + char *new = NULL; + unsigned int len; + + if (path == NULL || *path == '\0') { + return strdup("."); + } + + len = strlen(path); + + /* Remove trailing slashes */ + while(len > 0 && path[len - 1] == '/') --len; + + /* We have only slashes */ + if (len == 0) { + return strdup("/"); + } + + /* goto next slash */ + while(len > 0 && path[len - 1] != '/') --len; + + if (len == 0) { + return strdup("."); + } else if (len == 1) { + return strdup("/"); + } + + /* Remove slashes again */ + while(len > 0 && path[len - 1] == '/') --len; + + new = malloc(len + 1); + if (new == NULL) { + return NULL; + } + + strncpy(new, path, len); + new[len] = '\0'; + + return new; +} + +/** + * @brief basename - parse filename component. + * + * basename breaks a null-terminated pathname string into a filename component. + * ssh_basename() returns the component following the final '/'. Trailing '/' + * characters are not counted as part of the pathname. + * + * @param path The path to parse. + * + * @return The filename of path or NULL if we can't allocate memory. If path + * is a the string "/", basename returns the string "/". If path is + * NULL or an empty string, "." is returned. + */ +char *ssh_basename (const char *path) { + char *new = NULL; + const char *s; + unsigned int len; + + if (path == NULL || *path == '\0') { + return strdup("."); + } + + len = strlen(path); + /* Remove trailing slashes */ + while(len > 0 && path[len - 1] == '/') --len; + + /* We have only slashes */ + if (len == 0) { + return strdup("/"); + } + + while(len > 0 && path[len - 1] != '/') --len; + + if (len > 0) { + s = path + len; + len = strlen(s); + + while(len > 0 && s[len - 1] == '/') --len; + } else { + return strdup(path); + } + + new = malloc(len + 1); + if (new == NULL) { + return NULL; + } + + strncpy(new, s, len); + new[len] = '\0'; + + return new; +} /** @} */ /* vim: set ts=2 sw=2 et cindent: */ diff --git a/libssh/packet.c b/libssh/packet.c index ed3a306c..a2e91e69 100644 --- a/libssh/packet.c +++ b/libssh/packet.c @@ -758,6 +758,7 @@ static int packet_wait2(SSH_SESSION *session, int type, int blocking) { packet_parse(session); break; case SSH2_MSG_IGNORE: + case SSH2_MSG_DEBUG: break; default: if (type && (type != session->in_packet.type)) { diff --git a/libssh/scp.c b/libssh/scp.c index c0ebac08..e4cb3480 100644 --- a/libssh/scp.c +++ b/libssh/scp.c @@ -21,9 +21,11 @@ * MA 02111-1307, USA. */ -#include "libssh/priv.h" +#include <stdio.h> #include <string.h> +#include "libssh/priv.h" + /** @brief Creates a new scp session * @param session the SSH session to use * @param mode one of SSH_SCP_WRITE or SSH_SCP_READ, depending if you need to drop files remotely or read them. @@ -121,6 +123,69 @@ void ssh_scp_free(ssh_scp scp){ SAFE_FREE(scp); } +/** @brief creates a directory in a scp in sink mode + * @param dirname Name of the directory being created. + * @param perms Text form of the unix permissions for the new directory, e.g. "0755". + * @returns SSH_OK if the directory was created. + * @returns SSH_ERROR if an error happened. + * @see ssh_scp_leave_directory + */ +int ssh_scp_push_directory(ssh_scp scp, const char *dirname, const char *perms){ + char buffer[1024]; + int r; + uint8_t code; + char *dir; + 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); + snprintf(buffer, sizeof(buffer), "D%s 0 %s\n", perms, dir); + SAFE_FREE(dir); + r=channel_write(scp->channel,buffer,strlen(buffer)); + if(r==SSH_ERROR){ + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + r=channel_read(scp->channel,&code,1,0); + 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 Leaves a directory + * @returns SSH_OK if the directory was created. + * @returns SSH_ERROR if an error happened. + * @see ssh_scp_push_directory + */ + int ssh_scp_leave_directory(ssh_scp scp){ + char buffer[1024]; + int r; + uint8_t code; + 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; + } + strcpy(buffer, "E\n"); + r=channel_write(scp->channel,buffer,strlen(buffer)); + if(r==SSH_ERROR){ + scp->state=SSH_SCP_ERROR; + return SSH_ERROR; + } + r=channel_read(scp->channel,&code,1,0); + 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 initializes the sending of a file to a scp in sink mode * @param filename Name of the file being sent. It should not contain any path indicator * @param size Exact size in bytes of the file being sent. @@ -132,11 +197,14 @@ int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, const char char buffer[1024]; int r; uint8_t code; + char *file; 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; } - snprintf(buffer,sizeof(buffer),"C%s %ld %s\n",perms, size, filename); + file=ssh_basename(filename); + snprintf(buffer, sizeof(buffer), "C%s %" PRIdS " %s\n", perms, size, file); + SAFE_FREE(file); r=channel_write(scp->channel,buffer,strlen(buffer)); if(r==SSH_ERROR){ scp->state=SSH_SCP_ERROR; @@ -198,3 +266,12 @@ int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len){ } return SSH_OK; } + +ssh_scp_request ssh_scp_request_new(void){ + ssh_scp_request r=malloc(sizeof(struct ssh_scp_request_struct)); + if(r==NULL) + return NULL; + ZERO_STRUCTP(r); + return r; +} + diff --git a/libssh/sftp.c b/libssh/sftp.c index 250b753e..40510ed8 100644 --- a/libssh/sftp.c +++ b/libssh/sftp.c @@ -578,28 +578,28 @@ unsigned int sftp_extensions_get_count(SFTP_SESSION *sftp) { return sftp->ext->count; } -const char *sftp_extensions_get_name(SFTP_SESSION *sftp, unsigned int index) { +const char *sftp_extensions_get_name(SFTP_SESSION *sftp, unsigned int idx) { if (sftp == NULL || sftp->ext == NULL || sftp->ext->name == NULL) { return NULL; } - if (index > sftp->ext->count) { + if (idx > sftp->ext->count) { return NULL; } - return sftp->ext->name[index]; + return sftp->ext->name[idx]; } -const char *sftp_extensions_get_data(SFTP_SESSION *sftp, unsigned int index) { +const char *sftp_extensions_get_data(SFTP_SESSION *sftp, unsigned int idx) { if (sftp == NULL || sftp->ext == NULL || sftp->ext->data == NULL) { return NULL; } - if (index > sftp->ext->count) { + if (idx > sftp->ext->count) { return NULL; } - return sftp->ext->data[index]; + return sftp->ext->data[idx]; } int sftp_extension_supported(SFTP_SESSION *sftp, const char *name, @@ -2453,7 +2453,7 @@ char *sftp_readlink(SFTP_SESSION *sftp, const char *path) { ssh_string path_s = NULL; ssh_string link_s = NULL; ssh_buffer buffer; - char *link; + char *lnk; uint32_t ignored; uint32_t id; @@ -2499,10 +2499,10 @@ char *sftp_readlink(SFTP_SESSION *sftp, const char *path) { if (link_s == NULL) { return NULL; } - link = string_to_char(link_s); + lnk = string_to_char(link_s); string_free(link_s); - return link; + return lnk; } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ status = parse_status_msg(msg); sftp_message_free(msg); @@ -2616,7 +2616,7 @@ SFTP_STATVFS *sftp_statvfs(SFTP_SESSION *sftp, const char *path) { STATUS_MESSAGE *status = NULL; SFTP_MESSAGE *msg = NULL; ssh_string pathstr; - ssh_string statvfs; + ssh_string ext; ssh_buffer buffer; uint32_t id; @@ -2629,8 +2629,8 @@ SFTP_STATVFS *sftp_statvfs(SFTP_SESSION *sftp, const char *path) { return NULL; } - statvfs = string_from_char("statvfs@openssh.com"); - if (statvfs == NULL) { + ext = string_from_char("statvfs@openssh.com"); + if (ext == NULL) { buffer_free(buffer); return NULL; } @@ -2638,22 +2638,22 @@ SFTP_STATVFS *sftp_statvfs(SFTP_SESSION *sftp, const char *path) { pathstr = string_from_char(path); if (pathstr == NULL) { buffer_free(buffer); - string_free(statvfs); + string_free(ext); return NULL; } id = sftp_get_new_id(sftp); if (buffer_add_u32(buffer, id) < 0 || - buffer_add_ssh_string(buffer, statvfs) < 0 || + buffer_add_ssh_string(buffer, ext) < 0 || buffer_add_ssh_string(buffer, pathstr) < 0 || sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer) < 0) { buffer_free(buffer); - string_free(statvfs); + string_free(ext); string_free(pathstr); return NULL; } buffer_free(buffer); - string_free(statvfs); + string_free(ext); string_free(pathstr); while (msg == NULL) { @@ -2664,13 +2664,13 @@ SFTP_STATVFS *sftp_statvfs(SFTP_SESSION *sftp, const char *path) { } if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) { - SFTP_STATVFS *statvfs = sftp_parse_statvfs(sftp, msg->payload); + SFTP_STATVFS *buf = sftp_parse_statvfs(sftp, msg->payload); sftp_message_free(msg); - if (statvfs == NULL) { + if (buf == NULL) { return NULL; } - return statvfs; + return buf; } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ status = parse_status_msg(msg); sftp_message_free(msg); @@ -2693,7 +2693,7 @@ SFTP_STATVFS *sftp_fstatvfs(SFTP_FILE *file) { STATUS_MESSAGE *status = NULL; SFTP_MESSAGE *msg = NULL; SFTP_SESSION *sftp; - ssh_string fstatvfs; + ssh_string ext; ssh_buffer buffer; uint32_t id; @@ -2707,23 +2707,23 @@ SFTP_STATVFS *sftp_fstatvfs(SFTP_FILE *file) { return NULL; } - fstatvfs = string_from_char("fstatvfs@openssh.com"); - if (fstatvfs == NULL) { + ext = string_from_char("fstatvfs@openssh.com"); + if (ext == NULL) { buffer_free(buffer); return NULL; } id = sftp_get_new_id(sftp); if (buffer_add_u32(buffer, id) < 0 || - buffer_add_ssh_string(buffer, fstatvfs) < 0 || + buffer_add_ssh_string(buffer, ext) < 0 || buffer_add_ssh_string(buffer, file->handle) < 0 || sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer) < 0) { buffer_free(buffer); - string_free(fstatvfs); + string_free(ext); return NULL; } buffer_free(buffer); - string_free(fstatvfs); + string_free(ext); while (msg == NULL) { if (sftp_read_and_dispatch(sftp) < 0) { @@ -2733,13 +2733,13 @@ SFTP_STATVFS *sftp_fstatvfs(SFTP_FILE *file) { } if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) { - SFTP_STATVFS *statvfs = sftp_parse_statvfs(sftp, msg->payload); + SFTP_STATVFS *buf = sftp_parse_statvfs(sftp, msg->payload); sftp_message_free(msg); - if (statvfs == NULL) { + if (buf == NULL) { return NULL; } - return statvfs; + return buf; } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ status = parse_status_msg(msg); sftp_message_free(msg); |