aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES19
-rw-r--r--RELEASE-NOTES2
-rw-r--r--docs/libcurl/curl_easy_setopt.338
-rw-r--r--include/curl/curl.h49
-rw-r--r--lib/ssh.c157
-rw-r--r--lib/strerror.c2
-rw-r--r--lib/url.c27
-rw-r--r--lib/urldata.h21
-rw-r--r--src/main.c10
9 files changed, 311 insertions, 14 deletions
diff --git a/CHANGES b/CHANGES
index 6dc2af3a5..9cabff059 100644
--- a/CHANGES
+++ b/CHANGES
@@ -6,19 +6,26 @@
Changelog
+Daniel Stenberg (23 Jul 2009)
+- Added CURLOPT_SSH_KNOWNHOSTS, CURLOPT_SSH_KEYFUNCTION, CURLOPT_SSH_KEYDATA.
+ They introduce known_host support for SSH keys to libcurl. See docs for
+ details. Note that this feature depends on a new enough libssh2 version, to
+ be supported in libssh2 1.2 and later (or current git repo at this time).
+
Michal Marek (22 Jul 2009)
- David Binderman found a memory and fd leak in lib/gtls.c:load_file()
- (https://bugzilla.novell.com/523919). When looking at the code, I found
- that also the ptr pointer can leak.
+ (https://bugzilla.novell.com/523919). When looking at the code, I found that
+ also the ptr pointer can leak.
+
Kamil Dudka (20 Jul 2009)
-- Claes Jakobsson improved the support for client certificates handling
- in NSS-powered libcurl. Now the client certificates can be selected
+- Claes Jakobsson improved the support for client certificates handling in
+ NSS-powered libcurl. Now the client certificates can be selected
automatically by a NSS built-in hook. Additionally pre-login to all PKCS11
slots is no more performed. It used to cause problems with HW tokens.
- Fixed reference counting for NSS client certificates. Now the PEM reader
- module should be always properly unloaded on Curl_nss_cleanup(). If the unload
- fails though, libcurl will try to reuse the already loaded instance.
+ module should be always properly unloaded on Curl_nss_cleanup(). If the
+ unload fails though, libcurl will try to reuse the already loaded instance.
Daniel Fandrich (15 Jul 2009)
- Added nonblock.c to the non-automake makefiles (note that the dependencies
diff --git a/RELEASE-NOTES b/RELEASE-NOTES
index 0f4da0b0e..0261cf882 100644
--- a/RELEASE-NOTES
+++ b/RELEASE-NOTES
@@ -10,6 +10,7 @@ Curl and libcurl 7.19.6
This release includes the following changes:
o CURLOPT_FTPPORT (and curl's -P/--ftpport) support port ranges
+ o Added CURLOPT_SSH_KNOWNHOSTS, CURLOPT_SSH_KEYFUNCTION, CURLOPT_SSH_KEYDATA
This release includes the following bugfixes:
@@ -32,6 +33,7 @@ This release includes the following bugfixes:
o curl -o - sends data to stdout using binary mode on windows
o fixed the separators for "array" style string that CURLINFO_CERTINFO returns
o auth problem over several hosts with re-used connection
+ o improved the support for client certificates in libcurl+NSS
o fix leak in gtls code
This release includes the following known bugs:
diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3
index 94c1fc971..b30e04f06 100644
--- a/docs/libcurl/curl_easy_setopt.3
+++ b/docs/libcurl/curl_easy_setopt.3
@@ -1743,6 +1743,44 @@ Pass a char * pointing to a file name for your private key. If not used,
libcurl defaults to using \fB~/.ssh/id_dsa\fP.
If the file is password-protected, set the password with \fICURLOPT_KEYPASSWD\fP.
(Added in 7.16.1)
+.IP CURLOPT_SSH_KNOWNHOSTS
+Pass a pointer to a zero terminated string holding the file name of the
+known_host file to use. The known_hosts file should use the OpenSSH file
+format as supported by libssh2. If this file is specified, libcurl will only
+accept connections with hosts that are known and present in that file, with a
+matching public key. Use \fICURLOPT_SSH_KEYFUNCTION\fP to alter the default
+behavior on host and key (mis)matching. (Added in 7.19.6)
+.IP CURLOPT_SSH_KEYFUNCTION
+Pass a pointer to a curl_sshkeycallback function. It gets called when the
+known_host matching has been done, to allow the application to act and decide
+for libcurl how to proceed. It gets passed the CURL handle, the key from the
+known_hosts file, the key from the remote site, info from libcurl on the
+matching status and a custom pointer (set with \fICURLOPT_SSH_KEYDATA\fP). It
+MUST return one of the following return codes to tell libcurl how to act:
+.RS
+.IP CURLKHSTAT_FINE_ADD_TO_FILE
+The host+key is accepted and libcurl will append it to the known_hosts file
+before continuing with the connection. This will also add the host+key combo
+to the known_host pool kept in memory if it wasn't already present there. Note
+that the adding of data to the file is done by completely replacing the file
+with a new copy, so the permissions of the file must allow this.
+.IP CURLKHSTAT_FINE
+The host+key is accepted libcurl will continue with the connection. This will
+also add the host+key combo to the known_host pool kept in memory if it wasn't
+already present there.
+.IP CURLKHSTAT_REJECT
+The host+key is rejected. libcurl will deny the connection to continue and it
+will be closed.
+.IP CURLKHSTAT_DEFER
+The host+key is rejected, but the SSH connection is asked to be kept alive.
+This feature could be used when the app wants to somehow return back and act
+on the host+key situation and then retry without needing the overhead of
+setting it up from scratch again.
+.RE
+ (Added in 7.19.6)
+.IP CURLOPT_SSH_KEYDATA
+Pass a void * as parameter. This pointer will be passed along verbatim to the
+callback set with \fICURLOPT_SSH_KEYFUNCTION\fP. (Added in 7.19.6)
.SH OTHER OPTIONS
.IP CURLOPT_PRIVATE
Pass a void * as parameter, pointing to data that should be associated with
diff --git a/include/curl/curl.h b/include/curl/curl.h
index 970c11678..d261ce39b 100644
--- a/include/curl/curl.h
+++ b/include/curl/curl.h
@@ -493,6 +493,45 @@ typedef enum {
#define CURL_ERROR_SIZE 256
+struct curl_khkey {
+ const char *key; /* points to a zero-terminated string encoded with base64
+ if len is zero, otherwise to the "raw" data */
+ size_t len;
+ enum type {
+ CURLKHTYPE_UNKNOWN,
+ CURLKHTYPE_RSA1,
+ CURLKHTYPE_RSA,
+ CURLKHTYPE_DSS
+ } keytype;
+};
+
+/* this is the set of return values expected from the curl_sshkeycallback
+ callback */
+enum curl_khstat {
+ CURLKHSTAT_FINE_ADD_TO_FILE,
+ CURLKHSTAT_FINE,
+ CURLKHSTAT_REJECT, /* reject the connection, return an error */
+ CURLKHSTAT_DEFER, /* do not accept it, but we can't answer right now so
+ this causes a CURLE_DEFER error but otherwise the
+ connection will be left intact etc */
+ CURLKHSTAT_LAST /* not for use, only a marker for last-in-list */
+};
+
+/* this is the set of status codes pass in to the callback */
+enum curl_khmatch {
+ CURLKHMATCH_OK, /* match */
+ CURLKHMATCH_MISMATCH, /* host found, key mismatch! */
+ CURLKHMATCH_MISSING, /* no matching host/key found */
+ CURLKHMATCH_LAST /* not for use, only a marker for last-in-list */
+};
+
+typedef int
+ (*curl_sshkeycallback) (CURL *easy, /* easy handle */
+ const struct curl_khkey *knownkey, /* known */
+ const struct curl_khkey *foundkey, /* found */
+ enum curl_khmatch, /* libcurl's view on the keys */
+ void *clientp); /* custom pointer passed from app */
+
/* parameter for the CURLOPT_USE_SSL option */
typedef enum {
CURLUSESSL_NONE, /* do not attempt to use SSL */
@@ -1214,6 +1253,16 @@ typedef enum {
to all protocols except FILE and SCP. */
CINIT(REDIR_PROTOCOLS, LONG, 182),
+ /* set the SSH knownhost file name to use */
+ CINIT(SSH_KNOWNHOSTS, OBJECTPOINT, 183),
+
+ /* set the SSH host key callback, must point to a curl_sshkeycallback
+ function */
+ CINIT(SSH_KEYFUNCTION, FUNCTIONPOINT, 184),
+
+ /* set the SSH host key callback custom pointer */
+ CINIT(SSH_KEYDATA, OBJECTPOINT, 185),
+
CURLOPT_LASTENTRY /* the last unused */
} CURLoption;
diff --git a/lib/ssh.c b/lib/ssh.c
index e4f5057e6..29e2fe2f7 100644
--- a/lib/ssh.c
+++ b/lib/ssh.c
@@ -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)";
diff --git a/lib/url.c b/lib/url.c
index 11e336c0f..5f209fe5e 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -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. */
diff --git a/src/main.c b/src/main.c
index 58e5bf960..aeaa58831 100644
--- a/src/main.c
+++ b/src/main.c
@@ -4694,6 +4694,16 @@ operate(struct Configurable *config, int argc, argv_item_t argv[])
my_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);
my_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1);
}
+ else {
+ char *home = homedir();
+ char *file = aprintf("%s/%sssh/known_hosts", home, DOT_CHAR);
+ if(home && file) {
+ free(home);
+ my_setopt_str(curl, CURLOPT_SSH_KNOWNHOSTS, file);
+ }
+ else
+ return CURLE_OUT_OF_MEMORY;
+ }
if(config->no_body || config->remote_time) {
/* no body or use remote time */