Newer
Older
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
Daniel Stenberg
committed
* 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>
#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 */
72
73
74
75
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
#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 "strtoofft.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
#ifndef LIBSSH2_SFTP_S_IRUSR
/* Here's a work-around for those of you who happend to run a libssh2 version
that is 0.14 or older. We should remove this kludge as soon as we can
require a more recent libssh2 release. */
#ifndef S_IRGRP
#define S_IRGRP 0
#endif
#ifndef S_IROTH
#define S_IROTH 0
#endif
/* File mode */
/* Read, write, execute/search by owner */
#define LIBSSH2_SFTP_S_IRWXU S_IRWXU /* RWX mask for owner */
#define LIBSSH2_SFTP_S_IRUSR S_IRUSR /* R for owner */
#define LIBSSH2_SFTP_S_IWUSR S_IWUSR /* W for owner */
#define LIBSSH2_SFTP_S_IXUSR S_IXUSR /* X for owner */
/* Read, write, execute/search by group */
#define LIBSSH2_SFTP_S_IRWXG S_IRWXG /* RWX mask for group */
#define LIBSSH2_SFTP_S_IRGRP S_IRGRP /* R for group */
#define LIBSSH2_SFTP_S_IWGRP S_IWGRP /* W for group */
#define LIBSSH2_SFTP_S_IXGRP S_IXGRP /* X for group */
/* Read, write, execute/search by others */
#define LIBSSH2_SFTP_S_IRWXO S_IRWXO /* RWX mask for other */
#define LIBSSH2_SFTP_S_IROTH S_IROTH /* R for other */
#define LIBSSH2_SFTP_S_IWOTH S_IWOTH /* W for other */
#define LIBSSH2_SFTP_S_IXOTH S_IXOTH /* X for other */
/* File type */
#define LIBSSH2_SFTP_S_IFMT S_IFMT /* type of file mask */
#define LIBSSH2_SFTP_S_IFDIR S_IFDIR /* directory */
#define LIBSSH2_SFTP_S_IFLNK S_IFLNK /* symbolic link */
#define LIBSSH2_SFTP_S_IFSOCK S_IFSOCK /* socket */
#define LIBSSH2_SFTP_S_IFCHR S_IFCHR /* character special */
#define LIBSSH2_SFTP_S_IFBLK S_IFBLK /* block special */
#endif
/* Local functions: */
static const char *sftp_libssh2_strerror(unsigned long err);
static CURLcode sftp_sendquote(struct connectdata *conn,
struct curl_slist *quote);
static CURLcode sftp_create_dirs(struct connectdata *conn);
static LIBSSH2_ALLOC_FUNC(libssh2_malloc);
static LIBSSH2_REALLOC_FUNC(libssh2_realloc);
static LIBSSH2_FREE_FUNC(libssh2_free);
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)
return CURLE_OK;
/* TODO: map some 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 */
if (err == LIBSSH2_FX_NO_SUCH_FILE)
return CURLE_REMOTE_FILE_NOT_FOUND;
return CURLE_SSH;
}
static CURLcode libssh2_session_error_to_CURLE(int err)
{
(void)err;
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;
}
#if (LIBSSH2_APINO >= 200706012030)
* SSH State machine related code
*/
/* This is the ONLY way to change SSH state! */
static void state(struct connectdata *conn, ftpstate state)
{
#if defined(CURLDEBUG) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
/* for debug purposes */
const char *names[]={
"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",
"SSH_SFTP_SHUTDOWN",
"SSH_SESSION_FREE",
"QUIT"
};
#endif
struct ssh_conn *sshc = &conn->proto.sshc;
#if defined(CURLDEBUG) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
if (sshc->state != state) {
infof(conn->data, "FTP %p state change from %s to %s\n",
sshc, names[sshc->state], names[state]);
}
#endif
sshc->state = state;
}
static CURLcode ssh_statemach_act(struct connectdata *conn)
{
CURLcode result = CURLE_OK;
struct SessionHandle *data=conn->data;
struct ssh_conn *sshc = &conn->proto.sshc;
curl_socket_t sock = conn->sock[FIRSTSOCKET];
struct SSHPROTO *ssh;
#ifdef CURL_LIBSSH2_DEBUG
const char *fingerprint;
#endif /* CURL_LIBSSH2_DEBUG */
int rc;
ssh = data->reqdata.proto.ssh;
switch(sshc->state) {
case SSH_S_STARTUP:
rc = libssh2_session_startup(ssh->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 */
libssh2_session_set_blocking(ssh->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.
*/
fingerprint = libssh2_hostkey_hash(ssh->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.
*/
sshc->authlist = libssh2_userauth_list(ssh->ssh_session, ssh->user,
strlen(ssh->user));
if (!sshc->authlist) {
if (libssh2_session_last_errno(ssh->ssh_session) ==
LIBSSH2_ERROR_EAGAIN) {
break;
} else {
state(conn, SSH_SESSION_FREE);
sshc->actualCode = CURLE_OUT_OF_MEMORY;
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.ssh_public_key)
sshc->rsa_pub = aprintf("%s", data->set.ssh_public_key);
sshc->rsa_pub = aprintf("%s/.ssh/id_dsa.pub", home);
if (sshc->rsa_pub == NULL) {
curl_free(home);
state(conn, SSH_SESSION_FREE);
sshc->actualCode = CURLE_OUT_OF_MEMORY;
break;
}
if (data->set.ssh_private_key)
sshc->rsa = aprintf("%s", data->set.ssh_private_key);
sshc->rsa = aprintf("%s/.ssh/id_dsa", home);
if (sshc->rsa == NULL) {
curl_free(home);
curl_free(sshc->rsa_pub);
state(conn, SSH_SESSION_FREE);
sshc->actualCode = CURLE_OUT_OF_MEMORY;
break;
}
sshc->passphrase = data->set.key_passwd;
if (!sshc->passphrase)
sshc->passphrase = "";
curl_free(home);
infof(conn->data, "Using ssh public key file %s\n", sshc->rsa_pub);
infof(conn->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(ssh->ssh_session, ssh->user,
sshc->rsa_pub, sshc->rsa,
sshc->passphrase);
if (rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
curl_free(sshc->rsa_pub);
curl_free(sshc->rsa);
if (rc == 0) {
sshc->authed = TRUE;
infof(conn->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:
rc = libssh2_userauth_password(ssh->ssh_session, ssh->user,
ssh->passwd);
if (rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
else if (rc == 0) {
sshc->authed = TRUE;
infof(conn->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. */
rc = libssh2_userauth_keyboard_interactive_ex(ssh->ssh_session,
ssh->user,
strlen(ssh->user),
&kbd_callback);
if (rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
else if (rc == 0) {
sshc->authed = TRUE;
infof(conn->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.
*/
infof(conn->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
*/
ssh->sftp_session = libssh2_sftp_init(ssh->ssh_session);
if (!ssh->sftp_session) {
if (libssh2_session_last_errno(ssh->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
*/
rc = libssh2_sftp_realpath(ssh->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';
ssh->homedir = (char *)strdup(tempHome);
if (!ssh->homedir) {
state(conn, SSH_SFTP_SHUTDOWN);
sshc->actualCode = CURLE_OUT_OF_MEMORY;
break;
}
} else {
/* Return the error type */
result = libssh2_sftp_last_error(ssh->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) {
state(conn, SSH_STOP);
result = CURLE_OUT_OF_MEMORY;
break;
}
/* 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);
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] == '~') {
real_path = (char *)malloc(strlen(ssh->homedir) +
working_path_len + 1);
if (real_path == NULL) {
Curl_safefree(ssh->homedir);
ssh->homedir = NULL;
Curl_safefree(working_path);
state(conn, SSH_SFTP_SHUTDOWN);
sshc->actualCode = CURLE_OUT_OF_MEMORY;
break;
}
/* It is referenced to the home directory, so strip the
leading '/' */
memcpy(real_path, ssh->homedir, strlen(ssh->homedir));
real_path[strlen(ssh->homedir)] = '/';
real_path[strlen(ssh->homedir)+1] = '\0';
if (working_path_len > 3) {
memcpy(real_path+strlen(ssh->homedir)+1, working_path + 3,
1 + working_path_len -3);
}
}
else {
real_path = (char *)malloc(working_path_len+1);
if (real_path == NULL) {
Curl_safefree(ssh->homedir);
ssh->homedir = NULL;
Curl_safefree(working_path);
state(conn, SSH_SFTP_SHUTDOWN);
sshc->actualCode = CURLE_OUT_OF_MEMORY;
break;
}
memcpy(real_path, working_path, 1+working_path_len);
}
}
else {
Curl_safefree(working_path);
state(conn, SSH_SESSION_FREE);
sshc->actualCode = CURLE_FAILED_INIT;
Curl_safefree(working_path);
ssh->path = real_path;
/* Connect is all done */
state(conn, SSH_STOP);
}
break;
case SSH_SFTP_SHUTDOWN:
rc = libssh2_sftp_shutdown(ssh->sftp_session);
if (rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
ssh->sftp_session = NULL;
state(conn, SSH_SESSION_FREE);
break;
case SSH_SESSION_FREE:
rc = libssh2_session_free(ssh->ssh_session);
if (rc == LIBSSH2_ERROR_EAGAIN) {
break;
}
ssh->ssh_session = NULL;
state(conn, SSH_STOP);
result = sshc->actualCode;
break;
case SSH_QUIT:
/* fallthrough, just stop! */
default:
/* internal error */
state(conn, SSH_STOP);
break;
}
return result;
}
/* called repeatedly until done from multi.c */
CURLcode Curl_ssh_multi_statemach(struct connectdata *conn,
bool *done)
{
#if 0
curl_socket_t sock = conn->sock[FIRSTSOCKET];
#endif
int rc = 1;
struct SessionHandle *data=conn->data;
struct ssh_conn *sshc = &conn->proto.sshc;
CURLcode result = CURLE_OK;
#if 0
long timeout_ms = ssh_state_timeout(conn);
#endif
*done = FALSE; /* default to not done yet */
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
#if 0
if (timeout_ms <= 0) {
failf(data, "SSH response timeout");
return CURLE_OPERATION_TIMEDOUT;
}
rc = Curl_socket_ready(sshc->sendleft?CURL_SOCKET_BAD:sock, /* reading */
sshc->sendleft?sock:CURL_SOCKET_BAD, /* writing */
0);
#endif
if (rc == -1) {
failf(data, "select/poll error");
return CURLE_OUT_OF_MEMORY;
}
else if (rc != 0) {
result = ssh_statemach_act(conn);
*done = (bool)(sshc->state == SSH_STOP);
}
/* if rc == 0, then select() timed out */
return result;
}
static CURLcode ssh_easy_statemach(struct connectdata *conn)
{
#if 0
curl_socket_t sock = conn->sock[FIRSTSOCKET];
#endif
int rc = 1;
struct SessionHandle *data=conn->data;
struct ssh_conn *sshc = &conn->proto.sshc;
CURLcode result = CURLE_OK;
while(sshc->state != SSH_STOP) {
#if 0
long timeout_ms = ssh_state_timeout(conn);
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
if (timeout_ms <=0 ) {
failf(data, "SSH response timeout");
return CURLE_OPERATION_TIMEDOUT; /* already too little time */
}
rc = Curl_socket_ready(sshc->sendleft?CURL_SOCKET_BAD:sock, /* reading */
sshc->sendleft?sock:CURL_SOCKET_BAD, /* writing */
(int)timeout_ms);
#endif
if (rc == -1) {
failf(data, "select/poll error");
return CURLE_OUT_OF_MEMORY;
}
else if (rc == 0) {
result = CURLE_OPERATION_TIMEDOUT;
break;
}
else {
result = ssh_statemach_act(conn);
if (result)
break;
}
}
return result;
}
#endif /* (LIBSSH2_APINO >= 200706012030) */
/*
* SSH setup and connection
*/
static CURLcode ssh_init(struct connectdata *conn)
{
struct SessionHandle *data = conn->data;
struct SSHPROTO *ssh;
if (data->reqdata.proto.ssh)
ssh = (struct SSHPROTO *)calloc(sizeof(struct SSHPROTO), 1);
if (!ssh)
return CURLE_OUT_OF_MEMORY;
data->reqdata.proto.ssh = ssh;
/* get some initial data into the ssh struct */
ssh->bytecountp = &data->reqdata.keep.bytecount;
/* no need to duplicate them, this connectdata struct won't change */
ssh->user = conn->user;
ssh->passwd = conn->passwd;
ssh->errorstr = NULL;
ssh->ssh_session = NULL;
ssh->ssh_channel = NULL;
ssh->sftp_session = NULL;
ssh->sftp_handle = NULL;
return CURLE_OK;
}
/*
* Curl_ssh_connect() gets called from Curl_protocol_connect() to allow us to
* do protocol-specific actions at connect-time.
*/
CURLcode Curl_ssh_connect(struct connectdata *conn, bool *done)
struct SSHPROTO *ssh;
const char *fingerprint;
const char *authlist;
char tempHome[PATH_MAX];
curl_socket_t sock;
char *real_path;
char *working_path;
int working_path_len;
bool authed = FALSE;
CURLcode result;
struct SessionHandle *data = conn->data;
result = ssh_init(conn);
if (result)
return result;
ssh = data->reqdata.proto.ssh;
#ifdef CURL_LIBSSH2_DEBUG
if (ssh->user) {
infof(data, "User: %s\n", ssh->user);
if (ssh->passwd) {
infof(data, "Password: %s\n", ssh->passwd);
}
#endif /* CURL_LIBSSH2_DEBUG */
sock = conn->sock[FIRSTSOCKET];
ssh->ssh_session = libssh2_session_init_ex(libssh2_malloc, libssh2_free,
libssh2_realloc, ssh);
if (ssh->ssh_session == NULL) {
failf(data, "Failure initialising ssh session");
return CURLE_FAILED_INIT;
}
#ifdef CURL_LIBSSH2_DEBUG
libssh2_trace(ssh->ssh_session, LIBSSH2_TRACE_CONN|LIBSSH2_TRACE_TRANS|
LIBSSH2_TRACE_KEX|LIBSSH2_TRACE_AUTH|LIBSSH2_TRACE_SCP|
LIBSSH2_TRACE_SFTP|LIBSSH2_TRACE_ERROR|
LIBSSH2_TRACE_PUBLICKEY);
infof(data, "SSH socket: %d\n", sock);
#endif /* CURL_LIBSSH2_DEBUG */
#if (LIBSSH2_APINO >= 200706012030)
state(conn, SSH_S_STARTUP);
if (data->state.used_interface == Curl_if_multi)
result = Curl_ssh_multi_statemach(conn, done);
else {
result = ssh_easy_statemach(conn);
if (!result)
*done = TRUE;
}
return result;
(void)authed; /* not used */
(void)working_path; /* not used */
(void)working_path_len; /* not used */
(void)real_path; /* not used */
(void)tempHome; /* not used */
(void)authlist; /* not used */
(void)fingerprint; /* not used */
(void)i; /* not used */
#else /* !(LIBSSH2_APINO >= 200706012030) */
if (libssh2_session_startup(ssh->ssh_session, sock)) {
failf(data, "Failure establishing ssh session");
libssh2_session_free(ssh->ssh_session);
ssh->ssh_session = NULL;
return CURLE_FAILED_INIT;
}
/*
* 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(ssh->ssh_session,
LIBSSH2_HOSTKEY_HASH_MD5);
#ifdef CURL_LIBSSH2_DEBUG
/* The fingerprint points to static storage (!), don't free() it. */
for (i = 0; i < 16; i++) {
infof(data, "%02X ", (unsigned char) fingerprint[i]);
}
infof(data, "\n");
#endif /* CURL_LIBSSH2_DEBUG */
/* 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.
*/
authlist = libssh2_userauth_list(ssh->ssh_session, ssh->user,
strlen(ssh->user));
if (!authlist) {
libssh2_session_free(ssh->ssh_session);
ssh->ssh_session = NULL;
return CURLE_OUT_OF_MEMORY;
}
infof(data, "SSH authentication methods available: %s\n", authlist);
/*
* Check the supported auth types in the order I feel is most secure with the
* requested type of authentication
*/
if ((data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY) &&
(strstr(authlist, "publickey") != NULL)) {
const char *passphrase;
char rsa_pub[PATH_MAX];
char rsa[PATH_MAX];
rsa_pub[0] = rsa[0] = '\0';
/* To ponder about: should really the lib be messing about with the HOME
environment variable etc? */
home = curl_getenv("HOME");
if (data->set.ssh_public_key)
snprintf(rsa_pub, sizeof(rsa_pub), "%s", data->set.ssh_public_key);
else if (home)
snprintf(rsa_pub, sizeof(rsa_pub), "%s/.ssh/id_dsa.pub", home);
if (data->set.ssh_private_key)
snprintf(rsa, sizeof(rsa), "%s", data->set.ssh_private_key);
else if (home)
snprintf(rsa, sizeof(rsa), "%s/.ssh/id_dsa", home);
passphrase = data->set.key_passwd;
if (!passphrase)
passphrase = "";
infof(conn->data, "Using ssh public key file %s\n", rsa_pub);
infof(conn->data, "Using ssh private key file %s\n", rsa);
if (rsa_pub[0]) {
/* The function below checks if the files exists, no need to stat() here.
if (libssh2_userauth_publickey_fromfile(ssh->ssh_session, ssh->user,
rsa_pub, rsa, passphrase) == 0) {
infof(conn->data, "Initialized SSH public key authentication\n");
}
}
}
if (!authed &&
(data->set.ssh_auth_types & CURLSSH_AUTH_PASSWORD) &&
(strstr(authlist, "password") != NULL)) {
if (!libssh2_userauth_password(ssh->ssh_session, ssh->user, ssh->passwd)) {
infof(conn->data, "Initialized password authentication\n");
}
}
if (!authed && (data->set.ssh_auth_types & CURLSSH_AUTH_HOST) &&
(strstr(authlist, "hostbased") != NULL)) {
}
if (!authed && (data->set.ssh_auth_types & CURLSSH_AUTH_KEYBOARD)
&& (strstr(authlist, "keyboard-interactive") != NULL)) {
/* Authentication failed. Continue with keyboard-interactive now. */
if (!libssh2_userauth_keyboard_interactive_ex(ssh->ssh_session, ssh->user,
strlen(ssh->user),
&kbd_callback)) {
infof(conn->data, "Initialized keyboard interactive authentication\n");
Curl_safefree((void *)authlist);
authlist = NULL;
failf(data, "Authentication failure");
libssh2_session_free(ssh->ssh_session);
ssh->ssh_session = NULL;