Newer
Older
/***************************************************************************
James Housley
committed
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2009, Daniel Stenberg, <daniel@haxx.se>, et al.
James Housley
committed
*
* 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>
#ifdef HAVE_LIMITS_H
# include <limits.h>
#endif
#include <libssh2.h>
#include <libssh2_sftp.h>
#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 */
#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 "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 "inet_ntop.h"
#include "parsedate.h" /* for the week day and month names */
#include "sockaddr.h" /* required for Curl_sockaddr_storage */
Daniel Stenberg
committed
#include "strtoofft.h"
#include "select.h"
#define _MPRINTF_REPLACE /* use our functions only */
#include <curl/mprintf.h>
/* The last #include file should be: */
#include "memdebug.h"
#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);
static CURLcode get_pathname(const char **cpp, char **path);
James Housley
committed
static CURLcode ssh_connect(struct connectdata *conn, bool *done);
static CURLcode ssh_multi_statemach(struct connectdata *conn, bool *done);
static CURLcode ssh_do(struct connectdata *conn, bool *done);
static CURLcode ssh_getworkingpath(struct connectdata *conn,
char *homedir, /* when SFTP is used */
char **path);
static CURLcode scp_done(struct connectdata *conn,
CURLcode, bool premature);
static CURLcode scp_doing(struct connectdata *conn,
bool *dophase_done);
static CURLcode scp_disconnect(struct connectdata *conn);
static CURLcode sftp_done(struct connectdata *conn,
CURLcode, bool premature);
static CURLcode sftp_doing(struct connectdata *conn,
bool *dophase_done);
static CURLcode sftp_disconnect(struct connectdata *conn);
static
CURLcode sftp_perform(struct connectdata *conn,
bool *connected,
bool *dophase_done);
Daniel Stenberg
committed
static int ssh_getsock(struct connectdata *conn,
curl_socket_t *sock, /* points to numsocks number
of sockets */
int numsocks);
static int ssh_perform_getsock(const struct connectdata *conn,
curl_socket_t *sock, /* points to numsocks
number of sockets */
int numsocks);
Patrick Monnerat
committed
/*
* SCP protocol handler.
*/
const struct Curl_handler Curl_handler_scp = {
"SCP", /* scheme */
ZERO_NULL, /* setup_connection */
ssh_do, /* do_it */
scp_done, /* done */
ZERO_NULL, /* do_more */
ssh_connect, /* connect_it */
ssh_multi_statemach, /* connecting */
scp_doing, /* doing */
Daniel Stenberg
committed
ssh_getsock, /* proto_getsock */
ssh_getsock, /* doing_getsock */
ssh_perform_getsock, /* perform_getsock */
scp_disconnect, /* disconnect */
Patrick Monnerat
committed
PORT_SSH, /* defport */
PROT_SCP /* protocol */
};
/*
* SFTP protocol handler.
*/
const struct Curl_handler Curl_handler_sftp = {
"SFTP", /* scheme */
ZERO_NULL, /* setup_connection */
ssh_do, /* do_it */
sftp_done, /* done */
ZERO_NULL, /* do_more */
ssh_connect, /* connect_it */
ssh_multi_statemach, /* connecting */
sftp_doing, /* doing */
Daniel Stenberg
committed
ssh_getsock, /* proto_getsock */
ssh_getsock, /* doing_getsock */
ssh_perform_getsock, /* perform_getsock */
sftp_disconnect, /* disconnect */
Patrick Monnerat
committed
PORT_SSH, /* defport */
PROT_SFTP /* protocol */
};
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 connectdata *conn = (struct connectdata *)*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(conn->passwd);
responses[0].length = strlen(conn->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_ERROR_ALLOC:
return CURLE_OUT_OF_MEMORY;
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)
{
(void)abstract; /* arg not used */
return malloc(count);
}
static LIBSSH2_REALLOC_FUNC(libssh2_realloc)
{
(void)abstract; /* arg not used */
return realloc(ptr, count);
}
static LIBSSH2_FREE_FUNC(libssh2_free)
{
(void)abstract; /* arg not used */
* SSH State machine related code
*/
/* This is the ONLY way to change SSH state! */
static void state(struct connectdata *conn, sshstate nowstate)
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
/* for debug purposes */
static const char * const names[] = {
James Housley
committed
"SSH_STOP",
"SSH_S_STARTUP",
Daniel Stenberg
committed
"SSH_HOSTKEY",
"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",
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_SESSION_DISCONNECT",
"SSH_SESSION_FREE",
"QUIT"
};
#endif
struct ssh_conn *sshc = &conn->proto.sshc;
#if defined(DEBUGBUILD) && !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]);
/* figure out the path to work with in this particular request */
static CURLcode ssh_getworkingpath(struct connectdata *conn,
char *homedir, /* when SFTP is used */
char **path) /* returns the allocated
real path to work with */
{
struct SessionHandle *data = conn->data;
char *working_path;
int working_path_len;
Daniel Stenberg
committed
working_path = curl_easy_unescape(data, data->state.path, 0,
&working_path_len);
if(!working_path)
return CURLE_OUT_OF_MEMORY;
/* Check for /~/ , indicating relative to the user's home directory */
if(conn->protocol & PROT_SCP) {
real_path = malloc(working_path_len+1);
if(real_path == NULL) {
free(working_path);
return CURLE_OUT_OF_MEMORY;
}
if((working_path_len > 1) && (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_len > 1) && (working_path[1] == '~')) {
size_t homelen = strlen(homedir);
real_path = malloc(homelen + working_path_len + 1);
if(real_path == NULL) {
free(working_path);
return CURLE_OUT_OF_MEMORY;
}
/* It is referenced to the home directory, so strip the
leading '/' */
memcpy(real_path, homedir, homelen);
real_path[homelen] = '/';
real_path[homelen+1] = '\0';
if(working_path_len > 3) {
memcpy(real_path+homelen+1, working_path + 3,
1 + working_path_len -3);
}
}
else {
real_path = malloc(working_path_len+1);
if(real_path == NULL) {
free(working_path);
return CURLE_OUT_OF_MEMORY;
}
memcpy(real_path, working_path, 1+working_path_len);
}
}
free(working_path);
/* store the pointer for the caller to receive */
*path = real_path;
return CURLE_OK;
}
Guenter Knauf
committed
#ifdef HAVE_LIBSSH2_KNOWNHOST_API
Daniel Stenberg
committed
static int sshkeycallback(CURL *easy,
const struct curl_khkey *knownkey, /* known */
const struct curl_khkey *foundkey, /* found */
enum curl_khmatch match,
void *clientp)
{
(void)easy;
(void)knownkey;
(void)foundkey;
(void)clientp;
/* we only allow perfect matches, and we reject everything else */
return (match != CURLKHMATCH_OK)?CURLKHSTAT_REJECT:CURLKHSTAT_FINE;
}
Guenter Knauf
committed
#endif
Daniel Stenberg
committed
Daniel Stenberg
committed
/*
* Earlier libssh2 versions didn't have the ability to seek to 64bit positions
* with 32bit size_t.
*/
#ifdef HAVE_LIBSSH2_SFTP_SEEK64
#define SFTP_SEEK(x,y) libssh2_sftp_seek64(x, (libssh2_uint64_t)y)
Daniel Stenberg
committed
#else
#define SFTP_SEEK(x,y) libssh2_sftp_seek(x, y)
#endif
/*
* ssh_statemach_act() runs the SSH statemachine "one round" and returns. The
* data the pointer 'block' points to will be set to TRUE if the libssh2
* function returns LIBSSH2_ERROR_EAGAIN meaning it wants to be called again
* when the socket is ready
*/
static CURLcode ssh_statemach_act(struct connectdata *conn, bool *block)
{
CURLcode result = CURLE_OK;
James Housley
committed
struct SessionHandle *data = conn->data;
Daniel Stenberg
committed
struct SSHPROTO *sftp_scp = data->state.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 */
const char *host_public_key_md5;
int rc = LIBSSH2_ERROR_NONE, i;
int err;
Daniel Stenberg
committed
int seekerr = CURL_SEEKFUNC_OK;
*block = 0; /* we're not blocking by default */
switch(sshc->state) {
case SSH_S_STARTUP:
sshc->secondCreateDirs = 0;
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_OK;
James Housley
committed
rc = libssh2_session_startup(sshc->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;
break;
}
Daniel Stenberg
committed
/* Set libssh2 to non-blocking, since everything internally is
non-blocking */
libssh2_session_set_blocking(sshc->ssh_session, 0);
Daniel Stenberg
committed
state(conn, SSH_HOSTKEY);
/* fall-through */
case SSH_HOSTKEY:
#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.
*/
fingerprint = libssh2_hostkey_hash(sshc->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 */
/* Before we authenticate we check the hostkey's MD5 fingerprint
* against a known fingerprint, if available. This implementation pulls
* it from the curl option.
*/
if(data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5] &&
strlen(data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]) == 32) {
char buf[33];
host_public_key_md5 = libssh2_hostkey_hash(sshc->ssh_session,
LIBSSH2_HOSTKEY_HASH_MD5);
for (i = 0; i < 16; i++)
snprintf(&buf[i*2], 3, "%02x",
(unsigned char) host_public_key_md5[i]);
if(!strequal(buf, data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5])) {
failf(data,
"Denied establishing ssh session: mismatch md5 fingerprint. "
"Remote %s is not equal to %s",
buf, data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]);
state(conn, SSH_SESSION_FREE);
sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION;
break;
}
Daniel Stenberg
committed
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
#ifdef HAVE_LIBSSH2_KNOWNHOST_API
if(data->set.str[STRING_SSH_KNOWNHOSTS]) {
/* we're asked to verify the host against a file */
int keytype;
size_t keylen;
const char *remotekey = libssh2_session_hostkey(sshc->ssh_session,
&keylen, &keytype);
int keycheck;
int keybit;
if(remotekey) {
/*
* A subject to figure out is what host name we need to pass in here.
* What host name does OpenSSH store in its file if an IDN name is
* used?
*/
struct libssh2_knownhost *host;
enum curl_khmatch keymatch;
curl_sshkeycallback func =
data->set.ssh_keyfunc?data->set.ssh_keyfunc:sshkeycallback;
struct curl_khkey knownkey;
struct curl_khkey *knownkeyp = NULL;
struct curl_khkey foundkey;
keybit = (keytype == LIBSSH2_HOSTKEY_TYPE_RSA)?
LIBSSH2_KNOWNHOST_KEY_SSHRSA:LIBSSH2_KNOWNHOST_KEY_SSHDSS;
keycheck = libssh2_knownhost_check(sshc->kh,
conn->host.name,
remotekey, keylen,
LIBSSH2_KNOWNHOST_TYPE_PLAIN|
LIBSSH2_KNOWNHOST_KEYENC_RAW|
keybit,
&host);
infof(data, "SSH host check: %d, key: %s\n", keycheck,
(keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH)?
host->key:"<none>");
/* setup 'knownkey' */
if(keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH) {
knownkey.key = host->key;
knownkey.len = 0;
knownkey.keytype = (keytype == LIBSSH2_HOSTKEY_TYPE_RSA)?
CURLKHTYPE_RSA : CURLKHTYPE_DSS;
knownkeyp = &knownkey;
}
/* setup 'foundkey' */
foundkey.key = remotekey;
foundkey.len = keylen;
foundkey.keytype = (keytype == LIBSSH2_HOSTKEY_TYPE_RSA)?
CURLKHTYPE_RSA : CURLKHTYPE_DSS;
/*
* if any of the LIBSSH2_KNOWNHOST_CHECK_* defines and the
* curl_khmatch enum are ever modified, we need to introduce a
* translation table here!
*/
keymatch = (enum curl_khmatch)keycheck;
/* Ask the callback how to behave */
rc = func(data, knownkeyp, /* from the knownhosts file */
&foundkey, /* from the remote host */
keymatch, data->set.ssh_keyfunc_userp);
}
else
/* no remotekey means failure! */
rc = CURLKHSTAT_REJECT;
switch(rc) {
default: /* unknown return codes will equal reject */
case CURLKHSTAT_REJECT:
state(conn, SSH_SESSION_FREE);
case CURLKHSTAT_DEFER:
/* DEFER means bail out but keep the SSH_HOSTKEY state */
result = sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION;
break;
case CURLKHSTAT_FINE:
case CURLKHSTAT_FINE_ADD_TO_FILE:
/* proceed */
if(keycheck != LIBSSH2_KNOWNHOST_CHECK_MATCH) {
/* the found host+key didn't match but has been told to be fine
anyway so we add it in memory */
int addrc = libssh2_knownhost_add(sshc->kh,
conn->host.name, NULL,
remotekey, keylen,
LIBSSH2_KNOWNHOST_TYPE_PLAIN|
LIBSSH2_KNOWNHOST_KEYENC_RAW|
keybit, NULL);
if(addrc)
infof(data, "Warning adding the known host %s failed!\n",
conn->host.name);
else if(rc == CURLKHSTAT_FINE_ADD_TO_FILE) {
/* now we write the entire in-memory list of known hosts to the
known_hosts file */
int wrc =
libssh2_knownhost_writefile(sshc->kh,
data->set.str[STRING_SSH_KNOWNHOSTS],
LIBSSH2_KNOWNHOST_FILE_OPENSSH);
if(wrc) {
infof(data, "Warning, writing %s failed!\n",
data->set.str[STRING_SSH_KNOWNHOSTS]);
}
}
}
break;
}
}
#endif /* HAVE_LIBSSH2_KNOWNHOST_API */
state(conn, SSH_AUTHLIST);
break;
case SSH_AUTHLIST:
/*
* 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.
*/
sshc->authlist = libssh2_userauth_list(sshc->ssh_session,
conn->user,
strlen(conn->user));
if(!sshc->authlist) {
if((err = libssh2_session_last_errno(sshc->ssh_session)) ==
LIBSSH2_ERROR_EAGAIN) {
rc = 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:
/*
* 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");
if(data->set.str[STRING_SSH_PUBLIC_KEY])
sshc->rsa_pub = aprintf("%s", data->set.str[STRING_SSH_PUBLIC_KEY]);
else if(home)
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;
if(data->set.str[STRING_SSH_PRIVATE_KEY])
sshc->rsa = aprintf("%s", data->set.str[STRING_SSH_PRIVATE_KEY]);
else if(home)
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) {
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;
sshc->passphrase = data->set.str[STRING_KEY_PASSWD];
if(!sshc->passphrase)
sshc->passphrase = "";
Curl_safefree(home);
home = NULL;
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.
*/
rc = libssh2_userauth_publickey_fromfile(sshc->ssh_session,
conn->user, sshc->rsa_pub,
sshc->rsa, sshc->passphrase);
if(rc == LIBSSH2_ERROR_EAGAIN) {
}
Curl_safefree(sshc->rsa_pub);
sshc->rsa_pub = NULL;
Curl_safefree(sshc->rsa);
sshc->rsa = NULL;
if(rc == 0) {
sshc->authed = TRUE;
infof(data, "Initialized SSH public key authentication\n");
state(conn, SSH_AUTH_DONE);
}
else {
char *err_msg;
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
infof(data, "SSH public key authentication failed: %s\n", err_msg);
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:
rc = libssh2_userauth_password(sshc->ssh_session, conn->user,
conn->passwd);
if(rc == LIBSSH2_ERROR_EAGAIN) {
}
else if(rc == 0) {
sshc->authed = TRUE;
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);
}
break;
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);
}
break;
case SSH_AUTH_KEY:
/* Authentication failed. Continue with keyboard-interactive now. */
rc = libssh2_userauth_keyboard_interactive_ex(sshc->ssh_session,
conn->user,
strlen(conn->user),
&kbd_callback);
if(rc == LIBSSH2_ERROR_EAGAIN) {
}
else if(rc == 0) {
sshc->authed = TRUE;
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;
break;
}
/*
* At this point we have an authenticated ssh session.
*/
infof(data, "Authentication complete\n");
Curl_pgrsTime(conn->data, TIMER_APPCONNECT); /* SSH is connected */
conn->sockfd = sock;
conn->writesockfd = CURL_SOCKET_BAD;
if(conn->protocol == PROT_SFTP) {
state(conn, SSH_SFTP_INIT);
}
infof(data, "SSH CONNECT phase done\n");
state(conn, SSH_STOP);
break;
case SSH_SFTP_INIT:
/*
* Start the libssh2 sftp session
*/
sshc->sftp_session = libssh2_sftp_init(sshc->ssh_session);
if(!sshc->sftp_session) {
if(libssh2_session_last_errno(sshc->ssh_session) ==
LIBSSH2_ERROR_EAGAIN) {
rc = LIBSSH2_ERROR_EAGAIN;
break;
}
else {
char *err_msg;
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
failf(data, "Failure initializing sftp session: %s", err_msg);
state(conn, SSH_SESSION_FREE);
sshc->actualcode = CURLE_FAILED_INIT;
break;
James Housley
committed
}
}
state(conn, SSH_SFTP_REALPATH);
break;
case SSH_SFTP_REALPATH:
{
char tempHome[PATH_MAX];
/*
* Get the "home" directory
*/
rc = libssh2_sftp_realpath(sshc->sftp_session, ".",
tempHome, PATH_MAX-1);
if(rc == LIBSSH2_ERROR_EAGAIN) {
James Housley
committed
break;
}
else if(rc > 0) {
/* It seems that this string is not always NULL terminated */
tempHome[rc] = '\0';
if(!sshc->homedir) {
state(conn, SSH_SFTP_CLOSE);
sshc->actualcode = CURLE_OUT_OF_MEMORY;
break;
James Housley
committed
}
}
else {
/* Return the error type */
err = libssh2_sftp_last_error(sshc->sftp_session);
result = sftp_libssh2_error_to_CURLE(err);
Daniel Stenberg
committed
sshc->actualcode = result?result:CURLE_SSH;
DEBUGF(infof(data, "error = %d makes libcurl = %d\n", err, result));
state(conn, SSH_STOP);
James Housley
committed
break;
}
}
/* This is the last step in the SFTP connect phase. Do note that while
we get the homedir here, we get the "workingpath" in the DO action
since the homedir will remain the same between request but the
working path will not. */
DEBUGF(infof(data, "SSH CONNECT phase done\n"));
state(conn, SSH_STOP);
break;
James Housley
committed
case SSH_SFTP_QUOTE_INIT:
James Housley
committed
result = ssh_getworkingpath(conn, sshc->homedir, &sftp_scp->path);
if(result) {
sshc->actualcode = result;
state(conn, SSH_STOP);
break;
}
James Housley
committed
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;
James Housley
committed
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;
James Housley
committed
case SSH_SFTP_QUOTE:
/* Send any quote commands */
{
const char *cp;
James Housley
committed
/*
* Support some of the "FTP" commands
*/
if(curl_strequal("pwd", sshc->quote_item->data)) {
/* output debug output if that is requested */
if(data->set.verbose) {
char tmp[PATH_MAX+1];
James Housley
committed
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);