- Added CURLOPT_SSH_KNOWNHOSTS, CURLOPT_SSH_KEYFUNCTION, CURLOPT_SSH_KEYDATA.

They introduce known_host support for SSH keys to libcurl. See docs for
  details.
This commit is contained in:
Daniel Stenberg 2009-07-22 22:49:01 +00:00
parent 9cff716925
commit 47c392e135
9 changed files with 311 additions and 14 deletions

157
lib/ssh.c
View file

@ -306,6 +306,7 @@ static void state(struct connectdata *conn, sshstate nowstate)
static const char * const names[] = {
"SSH_STOP",
"SSH_S_STARTUP",
"SSH_HOSTKEY",
"SSH_AUTHLIST",
"SSH_AUTH_PKEY_INIT",
"SSH_AUTH_PKEY",
@ -433,6 +434,21 @@ static CURLcode ssh_getworkingpath(struct connectdata *conn,
return CURLE_OK;
}
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;
}
/*
* Earlier libssh2 versions didn't have the ability to seek to 64bit positions
* with 32bit size_t.
@ -483,9 +499,15 @@ static CURLcode ssh_statemach_act(struct connectdata *conn, bool *block)
break;
}
/* Set libssh2 to non-blocking, since cURL is all non-blocking */
/* Set libssh2 to non-blocking, since everything internally is
non-blocking */
libssh2_session_set_blocking(sshc->ssh_session, 0);
state(conn, SSH_HOSTKEY);
/* fall-through */
case SSH_HOSTKEY:
#ifdef CURL_LIBSSH2_DEBUG
/*
* Before we authenticate we should check the hostkey's fingerprint
@ -527,12 +549,121 @@ static CURLcode ssh_statemach_act(struct connectdata *conn, bool *block)
}
}
#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:
/* 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
@ -2278,6 +2409,26 @@ static CURLcode ssh_connect(struct connectdata *conn, bool *done)
return CURLE_FAILED_INIT;
}
#ifdef HAVE_LIBSSH2_KNOWNHOST_API
if(data->set.str[STRING_SSH_KNOWNHOSTS]) {
int rc;
ssh->kh = libssh2_knownhost_init(ssh->ssh_session);
if(!ssh->kh) {
/* eeek. TODO: free the ssh_session! */
return CURLE_FAILED_INIT;
}
/* read all known hosts from there */
rc = libssh2_knownhost_readfile(ssh->kh,
data->set.str[STRING_SSH_KNOWNHOSTS],
LIBSSH2_KNOWNHOST_FILE_OPENSSH);
if(rc) {
infof(data, "Failed to read known hosts from %s\n",
data->set.str[STRING_SSH_KNOWNHOSTS]);
}
}
#endif /* HAVE_LIBSSH2_KNOWNHOST_API */
#ifdef CURL_LIBSSH2_DEBUG
libssh2_trace(ssh->ssh_session, ~0);
infof(data, "SSH socket: %d\n", sock);