Index: include/curl/curl.h
===================================================================
RCS file: /cvsroot/curl/curl/include/curl/curl.h,v
retrieving revision 1.388
diff -u -r1.388 curl.h
--- include/curl/curl.h	10 Jun 2009 02:49:43 -0000	1.388
+++ include/curl/curl.h	11 Jul 2009 22:12:21 -0000
@@ -493,6 +493,45 @@
 
 #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 @@
      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;
 
Index: lib/ssh.c
===================================================================
RCS file: /cvsroot/curl/curl/lib/ssh.c,v
retrieving revision 1.134
diff -u -r1.134 ssh.c
--- lib/ssh.c	27 Jun 2009 06:05:08 -0000	1.134
+++ lib/ssh.c	11 Jul 2009 22:12:22 -0000
@@ -306,6 +306,7 @@
   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 @@
   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 @@
       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,93 @@
       }
     }
 
+#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);
+      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;
+
+        int keybit = (keytype == LIBSSH2_HOSTKEY_TYPE_RSA)?
+          LIBSSH2_KNOWNHOST_KEY_SSHRSA:LIBSSH2_KNOWNHOST_KEY_SSHDSS;
+        int check = 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", check,
+              (check <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH)?
+              host->key:"<none>");
+
+        /* setup 'knownkey' */
+        if(check <= 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)check;
+
+        /* 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:
+        /* proceed */
+        break;
+      case CURLKHSTAT_FINE_ADD_TO_FILE:
+        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 +2381,25 @@
     return CURLE_FAILED_INIT;
   }
 
+  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]);
+    }
+  }
+
+
 #ifdef CURL_LIBSSH2_DEBUG
   libssh2_trace(ssh->ssh_session, ~0);
   infof(data, "SSH socket: %d\n", sock);
Index: lib/strerror.c
===================================================================
RCS file: /cvsroot/curl/curl/lib/strerror.c,v
retrieving revision 1.55
diff -u -r1.55 strerror.c
--- lib/strerror.c	4 Jun 2009 19:11:11 -0000	1.55
+++ lib/strerror.c	11 Jul 2009 22:12:22 -0000
@@ -172,7 +172,7 @@
     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)";
Index: lib/url.c
===================================================================
RCS file: /cvsroot/curl/curl/lib/url.c,v
retrieving revision 1.806
diff -u -r1.806 url.c
--- lib/url.c	16 Jun 2009 13:16:28 -0000	1.806
+++ lib/url.c	11 Jul 2009 22:12:24 -0000
@@ -2169,6 +2169,8 @@
     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 @@
     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
Index: lib/urldata.h
===================================================================
RCS file: /cvsroot/curl/curl/lib/urldata.h,v
retrieving revision 1.414
diff -u -r1.414 urldata.h
--- lib/urldata.h	16 Jun 2009 13:16:28 -0000	1.414
+++ lib/urldata.h	11 Jul 2009 22:12:24 -0000
@@ -467,6 +467,7 @@
   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,
@@ -524,7 +525,7 @@
    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
@@ -565,6 +566,12 @@
   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 */
 };
 
@@ -1365,15 +1372,12 @@
   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 */
@@ -1382,6 +1386,12 @@
   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
@@ -1495,6 +1505,9 @@
                                   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. */
Index: src/main.c
===================================================================
RCS file: /cvsroot/curl/curl/src/main.c,v
retrieving revision 1.526
diff -u -r1.526 main.c
--- src/main.c	9 Jul 2009 21:47:24 -0000	1.526
+++ src/main.c	11 Jul 2009 22:12:26 -0000
@@ -4694,6 +4694,16 @@
           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 */
