diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/ssh.c | 157 | ||||
-rw-r--r-- | lib/strerror.c | 2 | ||||
-rw-r--r-- | lib/url.c | 27 | ||||
-rw-r--r-- | lib/urldata.h | 21 |
4 files changed, 199 insertions, 8 deletions
@@ -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); diff --git a/lib/strerror.c b/lib/strerror.c index a45e1f181..75067a7d0 100644 --- a/lib/strerror.c +++ b/lib/strerror.c @@ -172,7 +172,7 @@ curl_easy_strerror(CURLcode error) return "Malformed telnet option"; case CURLE_PEER_FAILED_VERIFICATION: - return "SSL peer certificate or SSH md5 fingerprint was not OK"; + return "SSL peer certificate or SSH remote key was not OK"; case CURLE_GOT_NOTHING: return "Server returned nothing (no headers, no data)"; @@ -2169,6 +2169,8 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option, data->set.ssl.sessionid = (bool)(0 != va_arg(param, long)); break; +#ifdef USE_LIBSSH2 + /* we only include SSH options if explicitly built to support SSH */ case CURLOPT_SSH_AUTH_TYPES: data->set.ssh_auth_types = va_arg(param, long); break; @@ -2196,6 +2198,31 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option, result = setstropt(&data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5], va_arg(param, char *)); break; +#ifdef HAVE_LIBSSH2_KNOWNHOST_API + case CURLOPT_SSH_KNOWNHOSTS: + /* + * Store the file name to read known hosts from. + */ + result = setstropt(&data->set.str[STRING_SSH_KNOWNHOSTS], + va_arg(param, char *)); + break; + + case CURLOPT_SSH_KEYFUNCTION: + /* setting to NULL is fine since the ssh.c functions themselves will + then rever to use the internal default */ + data->set.ssh_keyfunc = va_arg(param, curl_sshkeycallback); + break; + + case CURLOPT_SSH_KEYDATA: + /* + * Custom client data to pass to the SSH keyfunc callback + */ + data->set.ssh_keyfunc_userp = va_arg(param, void *); + break; +#endif /* HAVE_LIBSSH2_KNOWNHOST_API */ + +#endif /* USE_LIBSSH2 */ + case CURLOPT_HTTP_TRANSFER_DECODING: /* * disable libcurl transfer encoding is used diff --git a/lib/urldata.h b/lib/urldata.h index 6a857174f..a48fc91b7 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -468,6 +468,7 @@ typedef enum { SSH_STOP = 0, /* do nothing state, stops the state machine */ SSH_S_STARTUP, /* Session startup, First state in SSH-CONNECT */ + SSH_HOSTKEY, /* verify hostkey */ SSH_AUTHLIST, SSH_AUTH_PKEY_INIT, SSH_AUTH_PKEY, @@ -525,7 +526,7 @@ typedef enum { Everything that is strictly related to a connection is banned from this struct. */ struct SSHPROTO { - char *path; /* the path we operate on */ + char *path; /* the path we operate on */ }; /* ssh_conn is used for struct connection-oriented data in the connectdata @@ -566,6 +567,12 @@ struct ssh_conn { LIBSSH2_SFTP_HANDLE *sftp_handle; int waitfor; /* current READ/WRITE bits to wait for */ int orig_waitfor; /* default READ/WRITE bits wait for */ + + /* note that HAVE_LIBSSH2_KNOWNHOST_API is a define set in the libssh2.h + header */ +#ifdef HAVE_LIBSSH2_KNOWNHOST_API + LIBSSH2_KNOWNHOSTS *kh; +#endif #endif /* USE_LIBSSH2 */ }; @@ -1366,15 +1373,12 @@ enum dupstring { STRING_SET_RANGE, /* range, if used */ STRING_SET_REFERER, /* custom string for the HTTP referer field */ STRING_SET_URL, /* what original URL to work on */ - STRING_SSH_PRIVATE_KEY, /* path to the private key file for auth */ - STRING_SSH_PUBLIC_KEY, /* path to the public key file for auth */ STRING_SSL_CAPATH, /* CA directory name (doesn't work on windows) */ STRING_SSL_CAFILE, /* certificate file to verify peer against */ STRING_SSL_CIPHER_LIST, /* list of ciphers to use */ STRING_SSL_EGDSOCKET, /* path to file containing the EGD daemon socket */ STRING_SSL_RANDOM_FILE, /* path to file containing "random" data */ STRING_USERAGENT, /* User-Agent string */ - STRING_SSH_HOST_PUBLIC_KEY_MD5, /* md5 of host public key in ascii hex */ STRING_SSL_CRLFILE, /* crl file to check certificate */ STRING_SSL_ISSUERCERT, /* issuer cert file to check certificate */ STRING_USERNAME, /* <username>, if used */ @@ -1383,6 +1387,12 @@ enum dupstring { STRING_PROXYPASSWORD, /* Proxy <password>, if used */ STRING_NOPROXY, /* List of hosts which should not use the proxy, if used */ +#ifdef USE_LIBSSH2 + STRING_SSH_PRIVATE_KEY, /* path to the private key file for auth */ + STRING_SSH_PUBLIC_KEY, /* path to the public key file for auth */ + STRING_SSH_HOST_PUBLIC_KEY_MD5, /* md5 of host public key in ascii hex */ + STRING_SSH_KNOWNHOSTS, /* file name of knownhosts file */ +#endif #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) STRING_SOCKS5_GSSAPI_SERVICE, /* GSSAPI service name */ #endif @@ -1496,6 +1506,9 @@ struct UserDefined { 2 - the same but also allow MKD to fail once */ + curl_sshkeycallback ssh_keyfunc; /* key matching callback */ + void *ssh_keyfunc_userp; /* custom pointer to callback */ + /* Here follows boolean settings that define how to behave during this session. They are STATIC, set by libcurl users or at least initially and they don't change during operations. */ |