Newer
Older
/***************************************************************************
James Housley
committed
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2007, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://curl.haxx.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* $Id$
***************************************************************************/
/* #define CURL_LIBSSH2_DEBUG */
#include "setup.h"
#ifdef USE_LIBSSH2
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <limits.h>
#include <libssh2.h>
#include <libssh2_sftp.h>
#if !defined(LIBSSH2_VERSION_NUM) || (LIBSSH2_VERSION_NUM < 0x001000)
#error "this requires libssh2 0.16 or later"
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_TIME_H
#include <time.h>
#endif
#ifndef WIN32
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef HAVE_UTSNAME_H
#include <sys/utsname.h>
#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef VMS
#include <in.h>
#include <inet.h>
#endif
#endif /* !WIN32 */
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#if (defined(NETWARE) && defined(__NOVELL_LIBC__))
#undef in_addr_t
#define in_addr_t unsigned long
#endif
#include <curl/curl.h>
#include "urldata.h"
#include "sendf.h"
#include "easyif.h" /* for Curl_convert_... prototypes */
#include "if2ip.h"
#include "hostip.h"
#include "progress.h"
#include "transfer.h"
#include "escape.h"
#include "http.h" /* for HTTP proxy tunnel stuff */
#include "ssh.h"
#include "url.h"
#include "speedcheck.h"
#include "getinfo.h"
#include "strequal.h"
#include "sslgen.h"
#include "connect.h"
#include "strerror.h"
#include "memory.h"
#include "inet_ntop.h"
#include "select.h"
#include "parsedate.h" /* for the week day and month names */
#include "sockaddr.h" /* required for Curl_sockaddr_storage */
#include "multiif.h"
#if defined(HAVE_INET_NTOA_R) && !defined(HAVE_INET_NTOA_R_DECL)
#include "inet_ntoa_r.h"
#endif
#define _MPRINTF_REPLACE /* use our functions only */
#include <curl/mprintf.h>
#define _MPRINTF_REPLACE /* use our functions only */
#include <curl/mprintf.h>
/* The last #include file should be: */
#ifdef CURLDEBUG
#include "memdebug.h"
#endif
#ifndef PATH_MAX
#define PATH_MAX 1024 /* just an extra precaution since there are systems that
have their definition hidden well */
#endif
/* Local functions: */
static const char *sftp_libssh2_strerror(unsigned long err);
static LIBSSH2_ALLOC_FUNC(libssh2_malloc);
static LIBSSH2_REALLOC_FUNC(libssh2_realloc);
static LIBSSH2_FREE_FUNC(libssh2_free);
James Housley
committed
static int get_pathname(const char **cpp, char **path);
static void
kbd_callback(const char *name, int name_len, const char *instruction,
int instruction_len, int num_prompts,
const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts,
LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses,
void **abstract)
{
struct SSHPROTO *ssh = (struct SSHPROTO *)*abstract;
#ifdef CURL_LIBSSH2_DEBUG
fprintf(stderr, "name=%s\n", name);
fprintf(stderr, "name_len=%d\n", name_len);
fprintf(stderr, "instruction=%s\n", instruction);
fprintf(stderr, "instruction_len=%d\n", instruction_len);
fprintf(stderr, "num_prompts=%d\n", num_prompts);
#else
(void)name;
(void)name_len;
(void)instruction;
(void)instruction_len;
#endif /* CURL_LIBSSH2_DEBUG */
if (num_prompts == 1) {
responses[0].text = strdup(ssh->passwd);
responses[0].length = strlen(ssh->passwd);
}
(void)prompts;
(void)abstract;
} /* kbd_callback */
static CURLcode sftp_libssh2_error_to_CURLE(unsigned long err)
switch (err) {
case LIBSSH2_FX_OK:
return CURLE_OK;
case LIBSSH2_FX_NO_SUCH_FILE:
case LIBSSH2_FX_NO_SUCH_PATH:
return CURLE_REMOTE_FILE_NOT_FOUND;
case LIBSSH2_FX_PERMISSION_DENIED:
case LIBSSH2_FX_WRITE_PROTECT:
case LIBSSH2_FX_LOCK_CONFlICT:
return CURLE_REMOTE_ACCESS_DENIED;
case LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM:
case LIBSSH2_FX_QUOTA_EXCEEDED:
return CURLE_REMOTE_DISK_FULL;
case LIBSSH2_FX_FILE_ALREADY_EXISTS:
return CURLE_REMOTE_FILE_EXISTS;
case LIBSSH2_FX_DIR_NOT_EMPTY:
return CURLE_QUOTE_ERROR;
default:
break;
}
return CURLE_SSH;
}
static CURLcode libssh2_session_error_to_CURLE(int err)
{
if (err == LIBSSH2_ERROR_ALLOC)
return CURLE_OUT_OF_MEMORY;
/* TODO: map some more of the libssh2 errors to the more appropriate CURLcode
error code, and possibly add a few new SSH-related one. We must however
not return or even depend on libssh2 errors in the public libcurl API */
return CURLE_SSH;
}
static LIBSSH2_ALLOC_FUNC(libssh2_malloc)
{
return malloc(count);
(void)abstract;
}
static LIBSSH2_REALLOC_FUNC(libssh2_realloc)
{
return realloc(ptr, count);
(void)abstract;
}
static LIBSSH2_FREE_FUNC(libssh2_free)
{
free(ptr);
(void)abstract;
}
* SSH State machine related code
*/
/* This is the ONLY way to change SSH state! */
static void state(struct connectdata *conn, sshstate nowstate)
{
#if defined(CURLDEBUG) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
/* for debug purposes */
static const char * const names[] = {
James Housley
committed
"SSH_STOP",
"SSH_S_STARTUP",
"SSH_AUTHLIST",
"SSH_AUTH_PKEY_INIT",
"SSH_AUTH_PKEY",
"SSH_AUTH_PASS_INIT",
"SSH_AUTH_PASS",
"SSH_AUTH_HOST_INIT",
"SSH_AUTH_HOST",
"SSH_AUTH_KEY_INIT",
"SSH_AUTH_KEY",
"SSH_AUTH_DONE",
"SSH_SFTP_INIT",
"SSH_SFTP_REALPATH",
"SSH_GET_WORKINGPATH",
James Housley
committed
"SSH_SFTP_QUOTE_INIT",
"SSH_SFTP_POSTQUOTE_INIT",
"SSH_SFTP_QUOTE",
"SSH_SFTP_NEXT_QUOTE",
"SSH_SFTP_QUOTE_STAT",
"SSH_SFTP_QUOTE_SETSTAT",
"SSH_SFTP_QUOTE_SYMLINK",
"SSH_SFTP_QUOTE_MKDIR",
"SSH_SFTP_QUOTE_RENAME",
"SSH_SFTP_QUOTE_RMDIR",
"SSH_SFTP_QUOTE_UNLINK",
"SSH_SFTP_TRANS_INIT",
"SSH_SFTP_UPLOAD_INIT",
"SSH_SFTP_CREATE_DIRS_INIT",
"SSH_SFTP_CREATE_DIRS",
"SSH_SFTP_CREATE_DIRS_MKDIR",
"SSH_SFTP_READDIR_INIT",
"SSH_SFTP_READDIR",
"SSH_SFTP_READDIR_LINK",
"SSH_SFTP_READDIR_BOTTOM",
"SSH_SFTP_READDIR_DONE",
"SSH_SFTP_DOWNLOAD_INIT",
"SSH_SFTP_DOWNLOAD_STAT",
"SSH_SFTP_CLOSE",
"SSH_SFTP_SHUTDOWN",
James Housley
committed
"SSH_SCP_TRANS_INIT",
"SSH_SCP_UPLOAD_INIT",
"SSH_SCP_DOWNLOAD_INIT",
"SSH_SCP_DONE",
"SSH_SCP_SEND_EOF",
"SSH_SCP_WAIT_EOF",
"SSH_SCP_WAIT_CLOSE",
"SSH_SCP_CHANNEL_FREE",
"SSH_CHANNEL_CLOSE",
"SSH_SESSION_DISCONECT",
"SSH_SESSION_FREE",
"QUIT"
};
#endif
struct ssh_conn *sshc = &conn->proto.sshc;
#if defined(CURLDEBUG) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
if (sshc->state != nowstate) {
James Housley
committed
infof(conn->data, "SFTP %p state change from %s to %s\n",
sshc, names[sshc->state], names[nowstate]);
}
static CURLcode ssh_statemach_act(struct connectdata *conn)
{
CURLcode result = CURLE_OK;
James Housley
committed
struct SessionHandle *data = conn->data;
struct SSHPROTO *sftp_scp = data->reqdata.proto.ssh;
struct ssh_conn *sshc = &conn->proto.sshc;
curl_socket_t sock = conn->sock[FIRSTSOCKET];
#ifdef CURL_LIBSSH2_DEBUG
const char *fingerprint;
#endif /* CURL_LIBSSH2_DEBUG */
int rc;
James Housley
committed
long err;
switch(sshc->state) {
case SSH_S_STARTUP:
James Housley
committed
sshc->secondCreateDirs = 0;
sshc->nextState = SSH_NO_STATE;
sshc->actualCode = CURLE_OK;
rc = libssh2_session_startup(sftp_scp->ssh_session, sock);
if (rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
else if (rc) {
failf(data, "Failure establishing ssh session");
state(conn, SSH_SESSION_FREE);
sshc->actualCode = CURLE_FAILED_INIT;
/* Set libssh2 to non-blocking, since cURL is all non-blocking */
James Housley
committed
libssh2_session_set_blocking(sftp_scp->ssh_session, 0);
#ifdef CURL_LIBSSH2_DEBUG
/*
* Before we authenticate we should check the hostkey's fingerprint
* against our known hosts. How that is handled (reading from file,
* whatever) is up to us. As for know not much is implemented, besides
* showing how to get the fingerprint.
*/
James Housley
committed
fingerprint = libssh2_hostkey_hash(sftp_scp->ssh_session,
LIBSSH2_HOSTKEY_HASH_MD5);
/* The fingerprint points to static storage (!), don't free() it. */
infof(data, "Fingerprint: ");
for (rc = 0; rc < 16; rc++) {
infof(data, "%02X ", (unsigned char) fingerprint[rc]);
}
infof(data, "\n");
#endif /* CURL_LIBSSH2_DEBUG */
state(conn, SSH_AUTHLIST);
break;
case SSH_AUTHLIST:
/* TBD - methods to check the host keys need to be done */
/*
* Figure out authentication methods
* NB: As soon as we have provided a username to an openssh server we
* must never change it later. Thus, always specify the correct username
* here, even though the libssh2 docs kind of indicate that it should be
* possible to get a 'generic' list (not user-specific) of authentication
* methods, presumably with a blank username. That won't work in my
* experience.
* So always specify it here.
*/
James Housley
committed
sshc->authlist = libssh2_userauth_list(sftp_scp->ssh_session,
sftp_scp->user,
strlen(sftp_scp->user));
if (!sshc->authlist) {
if ((err = libssh2_session_last_errno(sftp_scp->ssh_session)) ==
LIBSSH2_ERROR_EAGAIN) {
break;
} else {
state(conn, SSH_SESSION_FREE);
sshc->actualCode = libssh2_session_error_to_CURLE(err);
break;
}
}
infof(data, "SSH authentication methods available: %s\n", sshc->authlist);
state(conn, SSH_AUTH_PKEY_INIT);
break;
case SSH_AUTH_PKEY_INIT:
/*
James Housley
committed
* Check the supported auth types in the order I feel is most secure
* with the requested type of authentication
*/
sshc->authed = FALSE;
if ((data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY) &&
(strstr(sshc->authlist, "publickey") != NULL)) {
char *home;
sshc->rsa_pub = sshc->rsa = NULL;
/* To ponder about: should really the lib be messing about with the
HOME environment variable etc? */
home = curl_getenv("HOME");
Daniel Stenberg
committed
if (data->set.str[STRING_SSH_PUBLIC_KEY])
sshc->rsa_pub = aprintf("%s", data->set.str[STRING_SSH_PUBLIC_KEY]);
sshc->rsa_pub = aprintf("%s/.ssh/id_dsa.pub", home);
else
/* as a final resort, try current dir! */
sshc->rsa_pub = strdup("id_dsa.pub");
if (sshc->rsa_pub == NULL) {
James Housley
committed
Curl_safefree(home);
home = NULL;
state(conn, SSH_SESSION_FREE);
sshc->actualCode = CURLE_OUT_OF_MEMORY;
break;
}
Daniel Stenberg
committed
if (data->set.str[STRING_SSH_PRIVATE_KEY])
sshc->rsa = aprintf("%s", data->set.str[STRING_SSH_PRIVATE_KEY]);
sshc->rsa = aprintf("%s/.ssh/id_dsa", home);
else
/* as a final resort, try current dir! */
sshc->rsa = strdup("id_dsa");
if (sshc->rsa == NULL) {
James Housley
committed
Curl_safefree(home);
home = NULL;
Curl_safefree(sshc->rsa_pub);
sshc->rsa_pub = NULL;
state(conn, SSH_SESSION_FREE);
sshc->actualCode = CURLE_OUT_OF_MEMORY;
break;
}
Daniel Stenberg
committed
sshc->passphrase = data->set.str[STRING_KEY_PASSWD];
if (!sshc->passphrase)
sshc->passphrase = "";
James Housley
committed
Curl_safefree(home);
home = NULL;
James Housley
committed
infof(data, "Using ssh public key file %s\n", sshc->rsa_pub);
infof(data, "Using ssh private key file %s\n", sshc->rsa);
state(conn, SSH_AUTH_PKEY);
} else {
state(conn, SSH_AUTH_PASS_INIT);
}
break;
case SSH_AUTH_PKEY:
/* The function below checks if the files exists, no need to stat() here.
*/
James Housley
committed
rc = libssh2_userauth_publickey_fromfile(sftp_scp->ssh_session,
sftp_scp->user, sshc->rsa_pub,
sshc->rsa, sshc->passphrase);
if (rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
James Housley
committed
Curl_safefree(sshc->rsa_pub);
sshc->rsa_pub = NULL;
Curl_safefree(sshc->rsa);
sshc->rsa = NULL;
if (rc == 0) {
sshc->authed = TRUE;
James Housley
committed
infof(data, "Initialized SSH public key authentication\n");
state(conn, SSH_AUTH_DONE);
} else {
state(conn, SSH_AUTH_PASS_INIT);
}
break;
case SSH_AUTH_PASS_INIT:
if ((data->set.ssh_auth_types & CURLSSH_AUTH_PASSWORD) &&
(strstr(sshc->authlist, "password") != NULL)) {
state(conn, SSH_AUTH_PASS);
} else {
state(conn, SSH_AUTH_HOST_INIT);
}
break;
case SSH_AUTH_PASS:
James Housley
committed
rc = libssh2_userauth_password(sftp_scp->ssh_session, sftp_scp->user,
sftp_scp->passwd);
if (rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
else if (rc == 0) {
sshc->authed = TRUE;
James Housley
committed
infof(data, "Initialized password authentication\n");
state(conn, SSH_AUTH_DONE);
} else {
state(conn, SSH_AUTH_HOST_INIT);
}
break;
case SSH_AUTH_HOST_INIT:
if ((data->set.ssh_auth_types & CURLSSH_AUTH_HOST) &&
(strstr(sshc->authlist, "hostbased") != NULL)) {
state(conn, SSH_AUTH_HOST);
} else {
state(conn, SSH_AUTH_KEY_INIT);
case SSH_AUTH_HOST:
state(conn, SSH_AUTH_KEY_INIT);
break;
case SSH_AUTH_KEY_INIT:
if ((data->set.ssh_auth_types & CURLSSH_AUTH_KEYBOARD)
&& (strstr(sshc->authlist, "keyboard-interactive") != NULL)) {
state(conn, SSH_AUTH_KEY);
} else {
state(conn, SSH_AUTH_DONE);
case SSH_AUTH_KEY:
/* Authentication failed. Continue with keyboard-interactive now. */
James Housley
committed
rc = libssh2_userauth_keyboard_interactive_ex(sftp_scp->ssh_session,
sftp_scp->user,
strlen(sftp_scp->user),
&kbd_callback);
if (rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
else if (rc == 0) {
sshc->authed = TRUE;
James Housley
committed
infof(data, "Initialized keyboard interactive authentication\n");
}
state(conn, SSH_AUTH_DONE);
break;
case SSH_AUTH_DONE:
if (!sshc->authed) {
failf(data, "Authentication failure");
state(conn, SSH_SESSION_FREE);
sshc->actualCode = CURLE_LOGIN_DENIED;
/*
* At this point we have an authenticated ssh session.
*/
James Housley
committed
infof(data, "Authentication complete\n");
conn->sockfd = sock;
conn->writesockfd = CURL_SOCKET_BAD;
if (conn->protocol == PROT_SFTP) {
state(conn, SSH_SFTP_INIT);
break;
}
state(conn, SSH_GET_WORKINGPATH);
break;
case SSH_SFTP_INIT:
/*
* Start the libssh2 sftp session
*/
James Housley
committed
sftp_scp->sftp_session = libssh2_sftp_init(sftp_scp->ssh_session);
if (!sftp_scp->sftp_session) {
if (libssh2_session_last_errno(sftp_scp->ssh_session) ==
LIBSSH2_ERROR_EAGAIN) {
break;
} else {
failf(data, "Failure initialising sftp session\n");
state(conn, SSH_SESSION_FREE);
sshc->actualCode = CURLE_FAILED_INIT;
break;
}
}
state(conn, SSH_SFTP_REALPATH);
break;
case SSH_SFTP_REALPATH:
{
char tempHome[PATH_MAX];
/*
* Get the "home" directory
*/
James Housley
committed
rc = libssh2_sftp_realpath(sftp_scp->sftp_session, ".",
tempHome, PATH_MAX-1);
if (rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
else if (rc > 0) {
/* It seems that this string is not always NULL terminated */
tempHome[rc] = '\0';
James Housley
committed
sftp_scp->homedir = (char *)strdup(tempHome);
if (!sftp_scp->homedir) {
state(conn, SSH_SFTP_CLOSE);
sshc->actualCode = CURLE_OUT_OF_MEMORY;
break;
}
} else {
/* Return the error type */
James Housley
committed
result = libssh2_sftp_last_error(sftp_scp->sftp_session);
DEBUGF(infof(data, "error = %d\n", result));
state(conn, SSH_STOP);
break;
}
state(conn, SSH_GET_WORKINGPATH);
}
break;
case SSH_GET_WORKINGPATH:
{
char *real_path;
char *working_path;
int working_path_len;
working_path = curl_easy_unescape(data, data->reqdata.path, 0,
&working_path_len);
if (!working_path) {
result = CURLE_OUT_OF_MEMORY;
James Housley
committed
state(conn, SSH_STOP);
/* Check for /~/ , indicating relative to the user's home directory */
if (conn->protocol == PROT_SCP) {
real_path = (char *)malloc(working_path_len+1);
if (real_path == NULL) {
Curl_safefree(working_path);
James Housley
committed
working_path = NULL;
state(conn, SSH_SESSION_FREE);
sshc->actualCode = CURLE_OUT_OF_MEMORY;
break;
}
if (working_path[1] == '~')
/* It is referenced to the home directory, so strip the
leading '/' */
memcpy(real_path, working_path+1, 1 + working_path_len-1);
else
memcpy(real_path, working_path, 1 + working_path_len);
}
else if (conn->protocol == PROT_SFTP) {
if (working_path[1] == '~') {
James Housley
committed
real_path = (char *)malloc(strlen(sftp_scp->homedir) +
working_path_len + 1);
if (real_path == NULL) {
James Housley
committed
Curl_safefree(sftp_scp->homedir);
sftp_scp->homedir = NULL;
Curl_safefree(working_path);
James Housley
committed
working_path = NULL;
state(conn, SSH_SFTP_CLOSE);
sshc->actualCode = CURLE_OUT_OF_MEMORY;
break;
}
/* It is referenced to the home directory, so strip the
leading '/' */
James Housley
committed
memcpy(real_path, sftp_scp->homedir, strlen(sftp_scp->homedir));
real_path[strlen(sftp_scp->homedir)] = '/';
real_path[strlen(sftp_scp->homedir)+1] = '\0';
if (working_path_len > 3) {
James Housley
committed
memcpy(real_path+strlen(sftp_scp->homedir)+1, working_path + 3,
1 + working_path_len -3);
}
}
else {
real_path = (char *)malloc(working_path_len+1);
if (real_path == NULL) {
James Housley
committed
Curl_safefree(sftp_scp->homedir);
sftp_scp->homedir = NULL;
Curl_safefree(working_path);
James Housley
committed
working_path = NULL;
state(conn, SSH_SFTP_CLOSE);
sshc->actualCode = CURLE_OUT_OF_MEMORY;
break;
}
memcpy(real_path, working_path, 1+working_path_len);
}
}
else {
Curl_safefree(working_path);
James Housley
committed
working_path = NULL;
state(conn, SSH_SESSION_FREE);
sshc->actualCode = CURLE_FAILED_INIT;
Curl_safefree(working_path);
James Housley
committed
working_path = NULL;
sftp_scp->path = real_path;
/* Connect is all done */
state(conn, SSH_STOP);
}
break;
James Housley
committed
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
case SSH_SFTP_QUOTE_INIT:
if (data->set.quote) {
infof(data, "Sending quote commands\n");
sshc->quote_item = data->set.quote;
state(conn, SSH_SFTP_QUOTE);
} else {
state(conn, SSH_SFTP_TRANS_INIT);
}
break;
case SSH_SFTP_POSTQUOTE_INIT:
if (data->set.postquote) {
infof(data, "Sending quote commands\n");
sshc->quote_item = data->set.postquote;
state(conn, SSH_SFTP_QUOTE);
} else {
state(conn, SSH_STOP);
}
break;
case SSH_SFTP_QUOTE:
/* Send any quote commands */
{
const char *cp;
/*
* Support some of the "FTP" commands
*/
if (curl_strnequal(sshc->quote_item->data, "PWD", 3)) {
/* output debug output if that is requested */
if (data->set.verbose) {
char tmp[PATH_MAX+1];
Curl_debug(data, CURLINFO_HEADER_OUT, (char *)"PWD\n", 4, conn);
snprintf(tmp, PATH_MAX, "257 \"%s\" is current directory.\n",
sftp_scp->path);
Curl_debug(data, CURLINFO_HEADER_IN, tmp, strlen(tmp), conn);
}
James Housley
committed
}
else if (sshc->quote_item->data) {
fprintf(stderr, "data: %s\n", sshc->quote_item->data);
/*
* the arguments following the command must be separated from the
* command with a space so we can check for it unconditionally
*/
cp = strchr(sshc->quote_item->data, ' ');
if (cp == NULL) {
failf(data, "Syntax error in SFTP command. Supply parameter(s)!");
state(conn, SSH_SFTP_CLOSE);
sshc->actualCode = CURLE_QUOTE_ERROR;
James Housley
committed
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
break;
}
/*
* also, every command takes at least one argument so we get that
* first argument right now
*/
err = get_pathname(&cp, &sshc->quote_path1);
if (err) {
if (err == CURLE_OUT_OF_MEMORY)
failf(data, "Out of memory");
else
failf(data, "Syntax error: Bad first parameter");
state(conn, SSH_SFTP_CLOSE);
sshc->actualCode = err;
break;
}
/*
* SFTP is a binary protocol, so we don't send text commands to
* the server. Instead, we scan for commands for commands used by
* OpenSSH's sftp program and call the appropriate libssh2
* functions.
*/
if (curl_strnequal(sshc->quote_item->data, "chgrp ", 6) ||
curl_strnequal(sshc->quote_item->data, "chmod ", 6) ||
curl_strnequal(sshc->quote_item->data, "chown ", 6) ) {
/* attribute change */
/* sshc->quote_path1 contains the mode to set */
/* get the destination */
err = get_pathname(&cp, &sshc->quote_path2);
if (err) {
if (err == CURLE_OUT_OF_MEMORY)
failf(data, "Out of memory");
else
failf(data, "Syntax error in chgrp/chmod/chown: "
"Bad second parameter");
Curl_safefree(sshc->quote_path1);
sshc->quote_path1 = NULL;
state(conn, SSH_SFTP_CLOSE);
sshc->actualCode = err;
break;
}
memset(&sshc->quote_attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES));
state(conn, SSH_SFTP_QUOTE_STAT);
break;
}
else if (curl_strnequal(sshc->quote_item->data, "ln ", 3) ||
curl_strnequal(sshc->quote_item->data, "symlink ", 8)) {
/* symbolic linking */
/* sshc->quote_path1 is the source */
/* get the destination */
err = get_pathname(&cp, &sshc->quote_path2);
if (err) {
if (err == CURLE_OUT_OF_MEMORY)
failf(data, "Out of memory");
else
failf(data,
"Syntax error in ln/symlink: Bad second parameter");
Curl_safefree(sshc->quote_path1);
sshc->quote_path1 = NULL;
state(conn, SSH_SFTP_CLOSE);
sshc->actualCode = err;
break;
}
state(conn, SSH_SFTP_QUOTE_SYMLINK);
break;
}
else if (curl_strnequal(sshc->quote_item->data, "mkdir ", 6)) {
/* create dir */
state(conn, SSH_SFTP_QUOTE_MKDIR);
break;
}
else if (curl_strnequal(sshc->quote_item->data, "rename ", 7)) {
/* rename file */
/* first param is the source path */
/* second param is the dest. path */
err = get_pathname(&cp, &sshc->quote_path2);
if (err) {
if (err == CURLE_OUT_OF_MEMORY)
failf(data, "Out of memory");
else
failf(data, "Syntax error in rename: Bad second parameter");
Curl_safefree(sshc->quote_path1);
sshc->quote_path1 = NULL;
state(conn, SSH_SFTP_CLOSE);
sshc->actualCode = err;
break;
}
state(conn, SSH_SFTP_QUOTE_RENAME);
break;
}
else if (curl_strnequal(sshc->quote_item->data, "rmdir ", 6)) {
/* delete dir */
state(conn, SSH_SFTP_QUOTE_RMDIR);
break;
}
else if (curl_strnequal(sshc->quote_item->data, "rm ", 3)) {
state(conn, SSH_SFTP_QUOTE_UNLINK);
break;
}
if (sshc->quote_path1) {
Curl_safefree(sshc->quote_path1);
sshc->quote_path1 = NULL;
}
if (sshc->quote_path2) {
Curl_safefree(sshc->quote_path2);
sshc->quote_path2 = NULL;
}
}
}
if (!sshc->quote_item) {
state(conn, SSH_SFTP_TRANS_INIT);
}
break;
case SSH_SFTP_NEXT_QUOTE:
if (sshc->quote_path1) {
Curl_safefree(sshc->quote_path1);
sshc->quote_path1 = NULL;
}
if (sshc->quote_path2) {
Curl_safefree(sshc->quote_path2);
sshc->quote_path2 = NULL;
}
sshc->quote_item = sshc->quote_item->next;
if (sshc->quote_item) {
state(conn, SSH_SFTP_QUOTE);
} else {
if (sshc->nextState != SSH_NO_STATE) {
state(conn, sshc->nextState);
sshc->nextState = SSH_NO_STATE;
} else {
state(conn, SSH_SFTP_TRANS_INIT);
}
}
break;
case SSH_SFTP_QUOTE_STAT:
rc = libssh2_sftp_stat(sftp_scp->sftp_session, sshc->quote_path2,
&sshc->quote_attrs);
if (rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
James Housley
committed
else if (rc != 0) { /* get those attributes */
err = libssh2_sftp_last_error(sftp_scp->sftp_session);
Curl_safefree(sshc->quote_path1);
sshc->quote_path1 = NULL;
Curl_safefree(sshc->quote_path2);
sshc->quote_path2 = NULL;
failf(data, "Attempt to get SFTP stats failed: %s",
sftp_libssh2_strerror(err));
state(conn, SSH_SFTP_CLOSE);
sshc->actualCode = CURLE_QUOTE_ERROR;
James Housley
committed
break;
}
/* Now set the new attributes... */
if (curl_strnequal(sshc->quote_item->data, "chgrp", 5)) {
sshc->quote_attrs.gid = strtol(sshc->quote_path1, NULL, 10);
if (sshc->quote_attrs.gid == 0 && !ISDIGIT(sshc->quote_path1[0])) {
Curl_safefree(sshc->quote_path1);
sshc->quote_path1 = NULL;
Curl_safefree(sshc->quote_path2);
sshc->quote_path2 = NULL;
failf(data, "Syntax error: chgrp gid not a number");
state(conn, SSH_SFTP_CLOSE);
sshc->actualCode = CURLE_QUOTE_ERROR;
James Housley
committed
break;
}
}
else if (curl_strnequal(sshc->quote_item->data, "chmod", 5)) {
sshc->quote_attrs.permissions = strtol(sshc->quote_path1, NULL, 8);
/* permissions are octal */
if (sshc->quote_attrs.permissions == 0 &&
!ISDIGIT(sshc->quote_path1[0])) {
Curl_safefree(sshc->quote_path1);
sshc->quote_path1 = NULL;
Curl_safefree(sshc->quote_path2);
sshc->quote_path2 = NULL;
failf(data, "Syntax error: chmod permissions not a number");
state(conn, SSH_SFTP_CLOSE);
sshc->actualCode = CURLE_QUOTE_ERROR;
James Housley
committed
break;
}
}
else if (curl_strnequal(sshc->quote_item->data, "chown", 5)) {
sshc->quote_attrs.uid = strtol(sshc->quote_path1, NULL, 10);
if (sshc->quote_attrs.uid == 0 && !ISDIGIT(sshc->quote_path1[0])) {
Curl_safefree(sshc->quote_path1);
sshc->quote_path1 = NULL;
Curl_safefree(sshc->quote_path2);
sshc->quote_path2 = NULL;
failf(data, "Syntax error: chown uid not a number");
state(conn, SSH_SFTP_CLOSE);
sshc->actualCode = CURLE_QUOTE_ERROR;
James Housley
committed
break;
}
}
/* Now send the completed structure... */
state(conn, SSH_SFTP_QUOTE_SETSTAT);
James Housley
committed
case SSH_SFTP_QUOTE_SETSTAT:
rc = libssh2_sftp_setstat(sftp_scp->sftp_session, sshc->quote_path2,
&sshc->quote_attrs);
if (rc == LIBSSH2_ERROR_EAGAIN) {
break;
James Housley
committed
} else if (rc != 0) {
err = libssh2_sftp_last_error(sftp_scp->sftp_session);
Curl_safefree(sshc->quote_path1);
sshc->quote_path1 = NULL;
Curl_safefree(sshc->quote_path2);
sshc->quote_path2 = NULL;
failf(data, "Attempt to set SFTP stats failed: %s",
sftp_libssh2_strerror(err));
state(conn, SSH_SFTP_CLOSE);
sshc->actualCode = CURLE_QUOTE_ERROR;
James Housley
committed
break;
James Housley
committed
state(conn, SSH_SFTP_NEXT_QUOTE);
break;
case SSH_SFTP_QUOTE_SYMLINK:
rc = libssh2_sftp_symlink(sftp_scp->sftp_session, sshc->quote_path1,
sshc->quote_path2);
if (rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
else if (rc != 0) {
err = libssh2_sftp_last_error(sftp_scp->sftp_session);
Curl_safefree(sshc->quote_path1);
sshc->quote_path1 = NULL;
Curl_safefree(sshc->quote_path2);
sshc->quote_path2 = NULL;
failf(data, "symlink command failed: %s",
sftp_libssh2_strerror(err));
state(conn, SSH_SFTP_CLOSE);
sshc->actualCode = CURLE_QUOTE_ERROR;
James Housley
committed
break;
}
state(conn, SSH_SFTP_NEXT_QUOTE);
break;