9d7d3f
From 95924615ab42529e4dc7b95da1115346bf607fc6 Mon Sep 17 00:00:00 2001
9d7d3f
From: Jeremy Lin <jjlin@cs.stanford.edu>
9d7d3f
Date: Mon, 15 Sep 2014 21:16:46 -0700
9d7d3f
Subject: [PATCH 1/2] ssh: improve key file search
9d7d3f
9d7d3f
For private keys, use the first match from: user-specified key file
9d7d3f
(if provided), ~/.ssh/id_rsa, ~/.ssh/id_dsa, ./id_rsa, ./id_dsa
9d7d3f
9d7d3f
Note that the previous code only looked for id_dsa files. id_rsa is
9d7d3f
now generally preferred, as it supports larger key sizes.
9d7d3f
9d7d3f
For public keys, use the user-specified key file, if provided.
9d7d3f
Otherwise, try to extract the public key from the private key file.
9d7d3f
This means that passing --pubkey is typically no longer required,
9d7d3f
and makes the key-handling behavior more like OpenSSH.
9d7d3f
9d7d3f
Upstream-commit: fa7d04fed4d4578fe29bdff0b5465f6e4a7da81a
9d7d3f
Signed-off-by: Kamil Dudka <kdudka@redhat.com>
9d7d3f
---
9d7d3f
 docs/MANUAL | 26 ++++++++++++++-------
9d7d3f
 docs/curl.1 |  8 ++++++-
9d7d3f
 lib/ssh.c   | 75 +++++++++++++++++++++++++++++++++++++++----------------------
9d7d3f
 3 files changed, 73 insertions(+), 36 deletions(-)
9d7d3f
9d7d3f
diff --git a/docs/MANUAL b/docs/MANUAL
9d7d3f
index 4ad2e13..3f8d9b8 100644
9d7d3f
--- a/docs/MANUAL
9d7d3f
+++ b/docs/MANUAL
9d7d3f
@@ -41,12 +41,19 @@ SIMPLE USAGE
9d7d3f
 
9d7d3f
   Get a file from an SSH server using SFTP:
9d7d3f
 
9d7d3f
-        curl -u username sftp://shell.example.com/etc/issue
9d7d3f
+        curl -u username sftp://example.com/etc/issue
9d7d3f
 
9d7d3f
-  Get a file from an SSH server using SCP using a private key to authenticate:
9d7d3f
+  Get a file from an SSH server using SCP using a private key
9d7d3f
+  (not password-protected) to authenticate:
9d7d3f
 
9d7d3f
-        curl -u username: --key ~/.ssh/id_dsa --pubkey ~/.ssh/id_dsa.pub \
9d7d3f
-            scp://shell.example.com/~/personal.txt
9d7d3f
+        curl -u username: --key ~/.ssh/id_rsa \
9d7d3f
+             scp://example.com/~/file.txt
9d7d3f
+
9d7d3f
+  Get a file from an SSH server using SCP using a private key
9d7d3f
+  (password-protected) to authenticate:
9d7d3f
+
9d7d3f
+        curl -u username: --key ~/.ssh/id_rsa --pass private_key_password \
9d7d3f
+             scp://example.com/~/file.txt
9d7d3f
 
9d7d3f
   Get the main page from an IPv6 web server:
9d7d3f
 
9d7d3f
@@ -91,10 +98,13 @@ USING PASSWORDS
9d7d3f
 
9d7d3f
  SFTP / SCP
9d7d3f
 
9d7d3f
-   This is similar to FTP, but you can specify a private key to use instead of
9d7d3f
-   a password. Note that the private key may itself be protected by a password
9d7d3f
-   that is unrelated to the login password of the remote system.  If you
9d7d3f
-   provide a private key file you must also provide a public key file.
9d7d3f
+   This is similar to FTP, but you can use the --key option to specify a
9d7d3f
+   private key to use instead of a password. Note that the private key may
9d7d3f
+   itself be protected by a password that is unrelated to the login password
9d7d3f
+   of the remote system; this password is specified using the --pass option.
9d7d3f
+   Typically, curl will automatically extract the public key from the private
9d7d3f
+   key file, but in cases where curl does not have the proper library support,
9d7d3f
+   a matching public key file must be specified using the --pubkey option.
9d7d3f
 
9d7d3f
  HTTP
9d7d3f
 
9d7d3f
diff --git a/docs/curl.1 b/docs/curl.1
9d7d3f
index 38fa084..d1675a0 100644
9d7d3f
--- a/docs/curl.1
9d7d3f
+++ b/docs/curl.1
9d7d3f
@@ -724,7 +724,8 @@ If this option is used several times, the last one will be used. If
9d7d3f
 unspecified, the option defaults to 60 seconds.
9d7d3f
 .IP "--key <key>"
9d7d3f
 (SSL/SSH) Private key file name. Allows you to provide your private key in this
9d7d3f
-separate file.
9d7d3f
+separate file. For SSH, if not specified, curl tries the following candidates
9d7d3f
+in order: '~/.ssh/id_rsa', '~/.ssh/id_dsa', './id_rsa', './id_dsa'.
9d7d3f
 
9d7d3f
 If this option is used several times, the last one will be used.
9d7d3f
 .IP "--key-type <type>"
9d7d3f
@@ -1124,6 +1125,11 @@ protocol instead of the default HTTP 1.1.
9d7d3f
 separate file.
9d7d3f
 
9d7d3f
 If this option is used several times, the last one will be used.
9d7d3f
+
9d7d3f
+(As of 7.39.0, curl attempts to automatically extract the public key from the
9d7d3f
+private key file, so passing this option is generally not required. Note that
9d7d3f
+this public key extraction requires libcurl to be linked against a copy of
9d7d3f
+libssh2 1.2.8 or higher that is itself linked against OpenSSL.)
9d7d3f
 .IP "-q"
9d7d3f
 If used as the first parameter on the command line, the \fIcurlrc\fP config
9d7d3f
 file will not be read and used. See the \fI-K, --config\fP for details on the
9d7d3f
diff --git a/lib/ssh.c b/lib/ssh.c
9d7d3f
index 43e3342..4ea7d9b 100644
9d7d3f
--- a/lib/ssh.c
9d7d3f
+++ b/lib/ssh.c
9d7d3f
@@ -780,7 +780,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn, bool *block)
9d7d3f
       if((data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY) &&
9d7d3f
          (strstr(sshc->authlist, "publickey") != NULL)) {
9d7d3f
         char *home = NULL;
9d7d3f
-        bool rsa_pub_empty_but_ok = FALSE;
9d7d3f
+        bool out_of_memory = FALSE;
9d7d3f
 
9d7d3f
         sshc->rsa_pub = sshc->rsa = NULL;
9d7d3f
 
9d7d3f
@@ -788,34 +788,55 @@ static CURLcode ssh_statemach_act(struct connectdata *conn, bool *block)
9d7d3f
            HOME environment variable etc? */
9d7d3f
         home = curl_getenv("HOME");
9d7d3f
 
9d7d3f
-        if(data->set.str[STRING_SSH_PUBLIC_KEY] &&
9d7d3f
-           !*data->set.str[STRING_SSH_PUBLIC_KEY])
9d7d3f
-           rsa_pub_empty_but_ok = true;
9d7d3f
-        else if(data->set.str[STRING_SSH_PUBLIC_KEY])
9d7d3f
-          sshc->rsa_pub = aprintf("%s", data->set.str[STRING_SSH_PUBLIC_KEY]);
9d7d3f
-        else if(home)
9d7d3f
-          sshc->rsa_pub = aprintf("%s/.ssh/id_dsa.pub", home);
9d7d3f
-        else
9d7d3f
-          /* as a final resort, try current dir! */
9d7d3f
-          sshc->rsa_pub = strdup("id_dsa.pub");
9d7d3f
-
9d7d3f
-        if(!rsa_pub_empty_but_ok && (sshc->rsa_pub == NULL)) {
9d7d3f
-          Curl_safefree(home);
9d7d3f
-          state(conn, SSH_SESSION_FREE);
9d7d3f
-          sshc->actualcode = CURLE_OUT_OF_MEMORY;
9d7d3f
-          break;
9d7d3f
+        if(data->set.str[STRING_SSH_PRIVATE_KEY])
9d7d3f
+          sshc->rsa = strdup(data->set.str[STRING_SSH_PRIVATE_KEY]);
9d7d3f
+        else {
9d7d3f
+          /* If no private key file is specified, try some common paths. */
9d7d3f
+          if(home) {
9d7d3f
+            /* Try ~/.ssh first. */
9d7d3f
+            sshc->rsa = aprintf("%s/.ssh/id_rsa", home);
9d7d3f
+            if(!sshc->rsa)
9d7d3f
+              out_of_memory = TRUE;
9d7d3f
+            else if(access(sshc->rsa, R_OK) != 0) {
9d7d3f
+              Curl_safefree(sshc->rsa);
9d7d3f
+              sshc->rsa = aprintf("%s/.ssh/id_dsa", home);
9d7d3f
+              if(!sshc->rsa)
9d7d3f
+                out_of_memory = TRUE;
9d7d3f
+              else if(access(sshc->rsa, R_OK) != 0) {
9d7d3f
+                Curl_safefree(sshc->rsa);
9d7d3f
+              }
9d7d3f
+            }
9d7d3f
+          }
9d7d3f
+          if(!out_of_memory && !sshc->rsa) {
9d7d3f
+            /* Nothing found; try the current dir. */
9d7d3f
+            sshc->rsa = strdup("id_rsa");
9d7d3f
+            if(sshc->rsa && access(sshc->rsa, R_OK) != 0) {
9d7d3f
+              Curl_safefree(sshc->rsa);
9d7d3f
+              sshc->rsa = strdup("id_dsa");
9d7d3f
+              if(sshc->rsa && access(sshc->rsa, R_OK) != 0) {
9d7d3f
+                Curl_safefree(sshc->rsa);
9d7d3f
+                /* Out of guesses. Set to the empty string to avoid
9d7d3f
+                 * surprising info messages. */
9d7d3f
+                sshc->rsa = strdup("");
9d7d3f
+              }
9d7d3f
+            }
9d7d3f
+          }
9d7d3f
         }
9d7d3f
 
9d7d3f
-        if(data->set.str[STRING_SSH_PRIVATE_KEY])
9d7d3f
-          sshc->rsa = aprintf("%s", data->set.str[STRING_SSH_PRIVATE_KEY]);
9d7d3f
-        else if(home)
9d7d3f
-          sshc->rsa = aprintf("%s/.ssh/id_dsa", home);
9d7d3f
-        else
9d7d3f
-          /* as a final resort, try current dir! */
9d7d3f
-          sshc->rsa = strdup("id_dsa");
9d7d3f
+        /*
9d7d3f
+         * Unless the user explicitly specifies a public key file, let
9d7d3f
+         * libssh2 extract the public key from the private key file.
9d7d3f
+         * This is done by simply passing sshc->rsa_pub = NULL.
9d7d3f
+         */
9d7d3f
+        if(data->set.str[STRING_SSH_PUBLIC_KEY]) {
9d7d3f
+          sshc->rsa_pub = strdup(data->set.str[STRING_SSH_PUBLIC_KEY]);
9d7d3f
+          if(!sshc->rsa_pub)
9d7d3f
+            out_of_memory = TRUE;
9d7d3f
+        }
9d7d3f
 
9d7d3f
-        if(sshc->rsa == NULL) {
9d7d3f
+        if(out_of_memory || sshc->rsa == NULL) {
9d7d3f
           Curl_safefree(home);
9d7d3f
+          Curl_safefree(sshc->rsa);
9d7d3f
           Curl_safefree(sshc->rsa_pub);
9d7d3f
           state(conn, SSH_SESSION_FREE);
9d7d3f
           sshc->actualcode = CURLE_OUT_OF_MEMORY;
9d7d3f
@@ -828,8 +849,8 @@ static CURLcode ssh_statemach_act(struct connectdata *conn, bool *block)
9d7d3f
 
9d7d3f
         Curl_safefree(home);
9d7d3f
 
9d7d3f
-        infof(data, "Using ssh public key file %s\n", sshc->rsa_pub);
9d7d3f
-        infof(data, "Using ssh private key file %s\n", sshc->rsa);
9d7d3f
+        infof(data, "Using SSH public key file '%s'\n", sshc->rsa_pub);
9d7d3f
+        infof(data, "Using SSH private key file '%s'\n", sshc->rsa);
9d7d3f
 
9d7d3f
         state(conn, SSH_AUTH_PKEY);
9d7d3f
       }
9d7d3f
-- 
9d7d3f
2.5.2
9d7d3f
9d7d3f
9d7d3f
From 2e18c6a12fc5dbab278670f22e58fcce51d32cac Mon Sep 17 00:00:00 2001
9d7d3f
From: Kamil Dudka <kdudka@redhat.com>
9d7d3f
Date: Fri, 15 Jan 2016 10:27:33 +0100
9d7d3f
Subject: [PATCH 2/2] ssh: make CURLOPT_SSH_PUBLIC_KEYFILE treat "" as NULL
9d7d3f
9d7d3f
The CURLOPT_SSH_PUBLIC_KEYFILE option has been documented to handle
9d7d3f
empty strings specially since curl-7_25_0-31-g05a443a but the behavior
9d7d3f
was unintentionally removed in curl-7_38_0-47-gfa7d04f.
9d7d3f
9d7d3f
This commit restores the original behavior and clarifies it in the
9d7d3f
documentation that NULL and "" have both the same meaning when passed
9d7d3f
to CURLOPT_SSH_PUBLIC_KEYFILE.
9d7d3f
9d7d3f
Bug: http://curl.haxx.se/mail/lib-2016-01/0072.html
9d7d3f
9d7d3f
Upstream-commit: be538e07667e1ba880b7201014be706851428d40
9d7d3f
Signed-off-by: Kamil Dudka <kdudka@redhat.com>
9d7d3f
---
9d7d3f
 docs/libcurl/curl_easy_setopt.3 | 6 +++---
9d7d3f
 lib/ssh.c                       | 7 +++++--
9d7d3f
 2 files changed, 8 insertions(+), 5 deletions(-)
9d7d3f
9d7d3f
diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3
9d7d3f
index ad739e1..0a9375e 100644
9d7d3f
--- a/docs/libcurl/curl_easy_setopt.3
9d7d3f
+++ b/docs/libcurl/curl_easy_setopt.3
9d7d3f
@@ -2446,9 +2446,9 @@ Pass a char * pointing to a file name for your public key. If not used,
9d7d3f
 libcurl defaults to \fB$HOME/.ssh/id_dsa.pub\fP if the HOME environment
9d7d3f
 variable is set, and just "id_dsa.pub" in the current directory if HOME is not
9d7d3f
 set.  (Added in 7.16.1)
9d7d3f
-If an empty string is passed, libcurl will pass no public key to libssh2
9d7d3f
-which then tries to compute it from the private key, this is known to work
9d7d3f
-when libssh2 1.4.0+ is linked against OpenSSL. (Added in 7.26.0)
9d7d3f
+If NULL (or an empty string) is passed, libcurl will pass no public key to
9d7d3f
+libssh2, which then tries to compute it from the private key.  This is known
9d7d3f
+to work with libssh2 1.4.0+ linked against OpenSSL. (Added in 7.26.0)
9d7d3f
 .IP CURLOPT_SSH_PRIVATE_KEYFILE
9d7d3f
 Pass a char * pointing to a file name for your private key. If not used,
9d7d3f
 libcurl defaults to \fB$HOME/.ssh/id_dsa\fP if the HOME environment variable
9d7d3f
diff --git a/lib/ssh.c b/lib/ssh.c
9d7d3f
index 4ea7d9b..589d4a3 100644
9d7d3f
--- a/lib/ssh.c
9d7d3f
+++ b/lib/ssh.c
9d7d3f
@@ -828,7 +828,9 @@ static CURLcode ssh_statemach_act(struct connectdata *conn, bool *block)
9d7d3f
          * libssh2 extract the public key from the private key file.
9d7d3f
          * This is done by simply passing sshc->rsa_pub = NULL.
9d7d3f
          */
9d7d3f
-        if(data->set.str[STRING_SSH_PUBLIC_KEY]) {
9d7d3f
+        if(data->set.str[STRING_SSH_PUBLIC_KEY]
9d7d3f
+            /* treat empty string the same way as NULL */
9d7d3f
+            && data->set.str[STRING_SSH_PUBLIC_KEY][0]) {
9d7d3f
           sshc->rsa_pub = strdup(data->set.str[STRING_SSH_PUBLIC_KEY]);
9d7d3f
           if(!sshc->rsa_pub)
9d7d3f
             out_of_memory = TRUE;
9d7d3f
@@ -849,7 +851,8 @@ static CURLcode ssh_statemach_act(struct connectdata *conn, bool *block)
9d7d3f
 
9d7d3f
         Curl_safefree(home);
9d7d3f
 
9d7d3f
-        infof(data, "Using SSH public key file '%s'\n", sshc->rsa_pub);
9d7d3f
+        if(sshc->rsa_pub)
9d7d3f
+          infof(data, "Using SSH public key file '%s'\n", sshc->rsa_pub);
9d7d3f
         infof(data, "Using SSH private key file '%s'\n", sshc->rsa);
9d7d3f
 
9d7d3f
         state(conn, SSH_AUTH_PKEY);
9d7d3f
-- 
9d7d3f
2.5.0
9d7d3f