]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
scp/sftp: fix disconnect
authorStefan Eissing <stefan@eissing.org>
Fri, 31 Oct 2025 12:46:18 +0000 (13:46 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Fri, 31 Oct 2025 14:56:58 +0000 (15:56 +0100)
When a SCP/SFTP connection calls the protocol handler disconnect, it
required the connections *and* the easy handles SSH meta data to be
present. When the disconnect is called with an admin handle, the easy
meta data is not present, which prevented the shutdown to run.

The easy meta data is however not necessary to run the shutdown state
machine. Calling it with a NULL `sshp` is fine. To avoid any mixups,
check `sshp` in state operations that need it.

Fixes #19293
Reported-by: And-yW on github
Closes #19295

lib/vssh/libssh.c
lib/vssh/libssh2.c

index df466dac4c76af4682bf7a6d4a8bcefd043a565b..af3767ca71091e79d1a244735a008fb30bb8d80f 100644 (file)
@@ -1979,13 +1979,15 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
       rc = myssh_in_SFTP_REALPATH(data, sshc);
       break;
     case SSH_SFTP_QUOTE_INIT:
-      rc = myssh_in_SFTP_QUOTE_INIT(data, sshc, sshp);
+      rc = sshp ? myssh_in_SFTP_QUOTE_INIT(data, sshc, sshp) :
+           CURLE_FAILED_INIT;
       break;
     case SSH_SFTP_POSTQUOTE_INIT:
       rc = myssh_in_SFTP_POSTQUOTE_INIT(data, sshc);
       break;
     case SSH_SFTP_QUOTE:
-      rc = myssh_in_SFTP_QUOTE(data, sshc, sshp);
+      rc = sshp ? myssh_in_SFTP_QUOTE(data, sshc, sshp) :
+           CURLE_FAILED_INIT;
       break;
     case SSH_SFTP_NEXT_QUOTE:
       rc = myssh_in_SFTP_NEXT_QUOTE(data, sshc);
@@ -2115,7 +2117,10 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
 
     case SSH_SFTP_FILETIME: {
       sftp_attributes attrs;
-
+      if(!sshp) {
+        result = CURLE_FAILED_INIT;
+        break;
+      }
       attrs = sftp_stat(sshc->sftp_session, sshp->path);
       if(attrs) {
         data->info.filetime = attrs->mtime;
@@ -2129,20 +2134,27 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
     case SSH_SFTP_TRANS_INIT:
       if(data->state.upload)
         myssh_to(data, sshc, SSH_SFTP_UPLOAD_INIT);
-      else {
+      else if(sshp) {
         if(sshp->path[strlen(sshp->path)-1] == '/')
           myssh_to(data, sshc, SSH_SFTP_READDIR_INIT);
         else
           myssh_to(data, sshc, SSH_SFTP_DOWNLOAD_INIT);
       }
+      else
+        result = CURLE_FAILED_INIT;
       break;
 
     case SSH_SFTP_UPLOAD_INIT:
-      rc = myssh_in_UPLOAD_INIT(data, sshc, sshp);
+      rc = sshp ? myssh_in_UPLOAD_INIT(data, sshc, sshp) :
+           CURLE_FAILED_INIT;
       break;
 
     case SSH_SFTP_CREATE_DIRS_INIT:
-      if(strlen(sshp->path) > 1) {
+      if(!sshp) {
+        result = CURLE_FAILED_INIT;
+        break;
+      }
+      else if(strlen(sshp->path) > 1) {
         sshc->slash_pos = sshp->path + 1; /* ignore the leading '/' */
         myssh_to(data, sshc, SSH_SFTP_CREATE_DIRS);
       }
@@ -2153,7 +2165,11 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
 
     case SSH_SFTP_CREATE_DIRS:
       sshc->slash_pos = strchr(sshc->slash_pos, '/');
-      if(sshc->slash_pos) {
+      if(!sshp) {
+        result = CURLE_FAILED_INIT;
+        break;
+      }
+      else if(sshc->slash_pos) {
         *sshc->slash_pos = 0;
 
         infof(data, "Creating directory '%s'", sshp->path);
@@ -2165,6 +2181,10 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
 
     case SSH_SFTP_CREATE_DIRS_MKDIR:
       /* 'mode' - parameter is preliminary - default to 0644 */
+      if(!sshp) {
+        result = CURLE_FAILED_INIT;
+        break;
+      }
       rc = sftp_mkdir(sshc->sftp_session, sshp->path,
                       (mode_t)data->set.new_directory_perms);
       *sshc->slash_pos = '/';
@@ -2188,10 +2208,12 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
       break;
 
     case SSH_SFTP_READDIR_INIT:
-      rc = myssh_in_SFTP_READDIR_INIT(data, sshc, sshp);
+      rc = sshp ? myssh_in_SFTP_READDIR_INIT(data, sshc, sshp) :
+           CURLE_FAILED_INIT;
       break;
     case SSH_SFTP_READDIR:
-      rc = myssh_in_SFTP_READDIR(data, sshc, sshp);
+      rc = sshp ? myssh_in_SFTP_READDIR(data, sshc, sshp) :
+           CURLE_FAILED_INIT;
       break;
     case SSH_SFTP_READDIR_LINK:
       rc = myssh_in_SFTP_READDIR_LINK(data, sshc);
@@ -2203,19 +2225,25 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
       rc = myssh_in_SFTP_READDIR_DONE(data, sshc);
       break;
     case SSH_SFTP_DOWNLOAD_INIT:
-      rc = myssh_in_SFTP_DOWNLOAD_INIT(data, sshc, sshp);
+      rc = sshp ? myssh_in_SFTP_DOWNLOAD_INIT(data, sshc, sshp) :
+           CURLE_FAILED_INIT;
       break;
     case SSH_SFTP_DOWNLOAD_STAT:
       rc = myssh_in_SFTP_DOWNLOAD_STAT(data, sshc);
       break;
     case SSH_SFTP_CLOSE:
-      rc = myssh_in_SFTP_CLOSE(data, sshc, sshp);
+      rc = sshp ? myssh_in_SFTP_CLOSE(data, sshc, sshp) :
+           CURLE_FAILED_INIT;
       break;
     case SSH_SFTP_SHUTDOWN:
       rc = myssh_in_SFTP_SHUTDOWN(data, sshc);
       break;
 
     case SSH_SCP_TRANS_INIT:
+      if(!sshp) {
+        result = CURLE_FAILED_INIT;
+        break;
+      }
       result = Curl_getworkingpath(data, sshc->homedir, &sshp->path);
       if(result) {
         sshc->actualcode = result;
@@ -2252,7 +2280,10 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
       break;
 
     case SSH_SCP_UPLOAD_INIT:
-
+      if(!sshp) {
+        result = CURLE_FAILED_INIT;
+        break;
+      }
       rc = ssh_scp_init(sshc->scp_session);
       if(rc != SSH_OK) {
         err_msg = ssh_get_error(sshc->ssh_session);
@@ -2832,11 +2863,9 @@ static CURLcode scp_disconnect(struct Curl_easy *data,
   struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY);
   (void)dead_connection;
 
-  if(sshc && sshc->ssh_session && sshp) {
+  if(sshc && sshc->ssh_session) {
     /* only if there is a session still around to use! */
-
     myssh_to(data, sshc, SSH_SESSION_DISCONNECT);
-
     result = myssh_block_statemach(data, sshc, sshp, TRUE);
   }
 
@@ -3012,7 +3041,7 @@ static CURLcode sftp_disconnect(struct Curl_easy *data,
 
   DEBUGF(infof(data, "SSH DISCONNECT starts now"));
 
-  if(sshc && sshc->ssh_session && sshp) {
+  if(sshc && sshc->ssh_session) {
     /* only if there is a session still around to use! */
     myssh_to(data, sshc, SSH_SFTP_SHUTDOWN);
     result = myssh_block_statemach(data, sshc, sshp, TRUE);
index 18e3cdb1d560f893e3381c44f8ebf6ce62e07684..5990da25bf41c3e5929e3c691c709f7b41bddb7c 100644 (file)
@@ -826,7 +826,7 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data,
         char *errmsg = NULL;
         int errlen;
         libssh2_session_last_error(sshc->ssh_session, &errmsg, &errlen, 0);
-        failf(data, "libssh2: %s", errmsg);
+        failf(data, "libssh2 method '%s' failed: %s", hostkey_method, errmsg);
         result = libssh2_session_error_to_CURLE(rc);
       }
     }
@@ -2721,11 +2721,13 @@ static CURLcode ssh_statemachine(struct Curl_easy *data,
       break;
 
     case SSH_SFTP_REALPATH:
-      result = ssh_state_sftp_realpath(data, sshc, sshp);
+      result = sshp ? ssh_state_sftp_realpath(data, sshc, sshp) :
+               CURLE_FAILED_INIT;
       break;
 
     case SSH_SFTP_QUOTE_INIT:
-      result = ssh_state_sftp_quote_init(data, sshc, sshp);
+      result = sshp ? ssh_state_sftp_quote_init(data, sshc, sshp) :
+               CURLE_FAILED_INIT;
       break;
 
     case SSH_SFTP_POSTQUOTE_INIT:
@@ -2733,7 +2735,8 @@ static CURLcode ssh_statemachine(struct Curl_easy *data,
       break;
 
     case SSH_SFTP_QUOTE:
-      result = ssh_state_sftp_quote(data, sshc, sshp);
+      result = sshp ? ssh_state_sftp_quote(data, sshc, sshp) :
+               CURLE_FAILED_INIT;
       break;
 
     case SSH_SFTP_NEXT_QUOTE:
@@ -2741,11 +2744,13 @@ static CURLcode ssh_statemachine(struct Curl_easy *data,
       break;
 
     case SSH_SFTP_QUOTE_STAT:
-      result = ssh_state_sftp_quote_stat(data, sshc, sshp, block);
+      result = sshp ? ssh_state_sftp_quote_stat(data, sshc, sshp, block) :
+               CURLE_FAILED_INIT;
       break;
 
     case SSH_SFTP_QUOTE_SETSTAT:
-      result = ssh_state_sftp_quote_setstat(data, sshc, sshp);
+      result = sshp ? ssh_state_sftp_quote_setstat(data, sshc, sshp) :
+               CURLE_FAILED_INIT;
       break;
 
     case SSH_SFTP_QUOTE_SYMLINK:
@@ -2784,10 +2789,15 @@ static CURLcode ssh_statemachine(struct Curl_easy *data,
     case SSH_SFTP_FILETIME:
     {
       LIBSSH2_SFTP_ATTRIBUTES attrs;
+      int rc;
 
-      int rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path,
-                                    curlx_uztoui(strlen(sshp->path)),
-                                    LIBSSH2_SFTP_STAT, &attrs);
+      if(!sshp) {
+        result = CURLE_FAILED_INIT;
+        break;
+      }
+      rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path,
+                                curlx_uztoui(strlen(sshp->path)),
+                                LIBSSH2_SFTP_STAT, &attrs);
       if(rc == LIBSSH2_ERROR_EAGAIN) {
         result = CURLE_AGAIN;
         break;
@@ -2803,16 +2813,19 @@ static CURLcode ssh_statemachine(struct Curl_easy *data,
     case SSH_SFTP_TRANS_INIT:
       if(data->state.upload)
         myssh_state(data, sshc, SSH_SFTP_UPLOAD_INIT);
-      else {
+      else if(sshp) {
         if(sshp->path[strlen(sshp->path)-1] == '/')
           myssh_state(data, sshc, SSH_SFTP_READDIR_INIT);
         else
           myssh_state(data, sshc, SSH_SFTP_DOWNLOAD_INIT);
       }
+      else
+        result = CURLE_FAILED_INIT;
       break;
 
     case SSH_SFTP_UPLOAD_INIT:
-      result = sftp_upload_init(data, sshc, sshp, block);
+      result = sshp ? sftp_upload_init(data, sshc, sshp, block) :
+               CURLE_FAILED_INIT;
       if(result) {
         myssh_state(data, sshc, SSH_SFTP_CLOSE);
         sshc->nextstate = SSH_NO_STATE;
@@ -2820,7 +2833,9 @@ static CURLcode ssh_statemachine(struct Curl_easy *data,
       break;
 
     case SSH_SFTP_CREATE_DIRS_INIT:
-      if(strlen(sshp->path) > 1) {
+      if(!sshp)
+        result = CURLE_FAILED_INIT;
+      else if(strlen(sshp->path) > 1) {
         sshc->slash_pos = sshp->path + 1; /* ignore the leading '/' */
         myssh_state(data, sshc, SSH_SFTP_CREATE_DIRS);
       }
@@ -2830,6 +2845,10 @@ static CURLcode ssh_statemachine(struct Curl_easy *data,
       break;
 
     case SSH_SFTP_CREATE_DIRS:
+      if(!sshp) {
+        result = CURLE_FAILED_INIT;
+        break;
+      }
       sshc->slash_pos = strchr(sshc->slash_pos, '/');
       if(sshc->slash_pos) {
         *sshc->slash_pos = 0;
@@ -2842,25 +2861,33 @@ static CURLcode ssh_statemachine(struct Curl_easy *data,
       break;
 
     case SSH_SFTP_CREATE_DIRS_MKDIR:
-      result = ssh_state_sftp_create_dirs_mkdir(data, sshc, sshp);
+      result = sshp ? ssh_state_sftp_create_dirs_mkdir(data, sshc, sshp) :
+               CURLE_FAILED_INIT;
       break;
 
     case SSH_SFTP_READDIR_INIT:
-      result = ssh_state_sftp_readdir_init(data, sshc, sshp);
+      result = sshp ? ssh_state_sftp_readdir_init(data, sshc, sshp) :
+               CURLE_FAILED_INIT;
       break;
 
     case SSH_SFTP_READDIR:
-      result = sftp_readdir(data, sshc, sshp, block);
+      result = sshp ? sftp_readdir(data, sshc, sshp, block) :
+               CURLE_FAILED_INIT;
       if(result) {
         myssh_state(data, sshc, SSH_SFTP_CLOSE);
       }
       break;
 
     case SSH_SFTP_READDIR_LINK:
-      result = ssh_state_sftp_readdir_link(data, sshc, sshp);
+      result = sshp ? ssh_state_sftp_readdir_link(data, sshc, sshp) :
+               CURLE_FAILED_INIT;
       break;
 
     case SSH_SFTP_READDIR_BOTTOM:
+      if(!sshp) {
+        result = CURLE_FAILED_INIT;
+        break;
+      }
       result = curlx_dyn_addn(&sshp->readdir, "\n", 1);
       if(!result)
         result = Curl_client_write(data, CLIENTWRITE_BODY,
@@ -2890,11 +2917,13 @@ static CURLcode ssh_statemachine(struct Curl_easy *data,
       break;
 
     case SSH_SFTP_DOWNLOAD_INIT:
-      result = ssh_state_sftp_download_init(data, sshc, sshp);
+      result = sshp ? ssh_state_sftp_download_init(data, sshc, sshp) :
+               CURLE_FAILED_INIT;
       break;
 
     case SSH_SFTP_DOWNLOAD_STAT:
-      result = sftp_download_stat(data, sshc, sshp, block);
+      result = sshp ? sftp_download_stat(data, sshc, sshp, block) :
+               CURLE_FAILED_INIT;
       if(result) {
         myssh_state(data, sshc, SSH_SFTP_CLOSE);
         sshc->nextstate = SSH_NO_STATE;
@@ -2902,7 +2931,8 @@ static CURLcode ssh_statemachine(struct Curl_easy *data,
       break;
 
     case SSH_SFTP_CLOSE:
-      result = ssh_state_sftp_close(data, sshc, sshp);
+      result = sshp ? ssh_state_sftp_close(data, sshc, sshp) :
+               CURLE_FAILED_INIT;
       break;
 
     case SSH_SFTP_SHUTDOWN:
@@ -2910,7 +2940,8 @@ static CURLcode ssh_statemachine(struct Curl_easy *data,
       break;
 
     case SSH_SCP_TRANS_INIT:
-      result = Curl_getworkingpath(data, sshc->homedir, &sshp->path);
+      result = sshp ? Curl_getworkingpath(data, sshc->homedir, &sshp->path) :
+               CURLE_FAILED_INIT;
       if(result) {
         myssh_state(data, sshc, SSH_STOP);
         break;
@@ -2931,11 +2962,13 @@ static CURLcode ssh_statemachine(struct Curl_easy *data,
       break;
 
     case SSH_SCP_UPLOAD_INIT:
-      result = ssh_state_scp_upload_init(data, sshc, sshp);
+      result = sshp ? ssh_state_scp_upload_init(data, sshc, sshp) :
+               CURLE_FAILED_INIT;
       break;
 
     case SSH_SCP_DOWNLOAD_INIT:
-      result = ssh_state_scp_download_init(data, sshc, sshp);
+      result = sshp ? ssh_state_scp_download_init(data, sshc, sshp) :
+               CURLE_FAILED_INIT;
       break;
 
     case SSH_SCP_DONE:
@@ -3559,9 +3592,6 @@ static CURLcode sshc_cleanup(struct ssh_conn *sshc, struct Curl_easy *data,
 
   if(sshc->ssh_agent) {
     rc = libssh2_agent_disconnect(sshc->ssh_agent);
-    if(!block && (rc == LIBSSH2_ERROR_EAGAIN))
-      return CURLE_AGAIN;
-
     if((rc < 0) && data) {
       char *err_msg = NULL;
       (void)libssh2_session_last_error(sshc->ssh_session,
@@ -3569,6 +3599,9 @@ static CURLcode sshc_cleanup(struct ssh_conn *sshc, struct Curl_easy *data,
       infof(data, "Failed to disconnect from libssh2 agent: %d %s",
             rc, err_msg);
     }
+    if(!block && (rc == LIBSSH2_ERROR_EAGAIN))
+      return CURLE_AGAIN;
+
     libssh2_agent_free(sshc->ssh_agent);
     sshc->ssh_agent = NULL;
 
@@ -3580,23 +3613,20 @@ static CURLcode sshc_cleanup(struct ssh_conn *sshc, struct Curl_easy *data,
 
   if(sshc->sftp_handle) {
     rc = libssh2_sftp_close(sshc->sftp_handle);
-    if(!block && (rc == LIBSSH2_ERROR_EAGAIN))
-      return CURLE_AGAIN;
-
     if((rc < 0) && data) {
       char *err_msg = NULL;
       (void)libssh2_session_last_error(sshc->ssh_session, &err_msg,
                                        NULL, 0);
       infof(data, "Failed to close libssh2 file: %d %s", rc, err_msg);
     }
+    if(!block && (rc == LIBSSH2_ERROR_EAGAIN))
+      return CURLE_AGAIN;
+
     sshc->sftp_handle = NULL;
   }
 
   if(sshc->ssh_channel) {
     rc = libssh2_channel_free(sshc->ssh_channel);
-    if(!block && (rc == LIBSSH2_ERROR_EAGAIN))
-      return CURLE_AGAIN;
-
     if((rc < 0) && data) {
       char *err_msg = NULL;
       (void)libssh2_session_last_error(sshc->ssh_session,
@@ -3604,30 +3634,37 @@ static CURLcode sshc_cleanup(struct ssh_conn *sshc, struct Curl_easy *data,
       infof(data, "Failed to free libssh2 scp subsystem: %d %s",
             rc, err_msg);
     }
+    if(!block && (rc == LIBSSH2_ERROR_EAGAIN))
+      return CURLE_AGAIN;
+
     sshc->ssh_channel = NULL;
   }
 
   if(sshc->sftp_session) {
     rc = libssh2_sftp_shutdown(sshc->sftp_session);
+    if((rc < 0) && data) {
+      char *err_msg = NULL;
+      (void)libssh2_session_last_error(sshc->ssh_session,
+                                       &err_msg, NULL, 0);
+      infof(data, "Failed to stop libssh2 sftp subsystem: %d %s", rc, err_msg);
+    }
     if(!block && (rc == LIBSSH2_ERROR_EAGAIN))
       return CURLE_AGAIN;
 
-    if((rc < 0) && data)
-      infof(data, "Failed to stop libssh2 sftp subsystem");
     sshc->sftp_session = NULL;
   }
 
   if(sshc->ssh_session) {
     rc = libssh2_session_free(sshc->ssh_session);
-    if(!block && (rc == LIBSSH2_ERROR_EAGAIN))
-      return CURLE_AGAIN;
-
     if((rc < 0) && data) {
       char *err_msg = NULL;
       (void)libssh2_session_last_error(sshc->ssh_session,
                                        &err_msg, NULL, 0);
       infof(data, "Failed to free libssh2 session: %d %s", rc, err_msg);
     }
+    if(!block && (rc == LIBSSH2_ERROR_EAGAIN))
+      return CURLE_AGAIN;
+
     sshc->ssh_session = NULL;
   }
 
@@ -3661,7 +3698,7 @@ static CURLcode scp_disconnect(struct Curl_easy *data,
   struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY);
   (void)dead_connection;
 
-  if(sshc && sshc->ssh_session && sshp) {
+  if(sshc && sshc->ssh_session) {
     /* only if there is a session still around to use! */
     myssh_state(data, sshc, SSH_SESSION_DISCONNECT);
     result = ssh_block_statemach(data, sshc, sshp, TRUE);
@@ -3834,7 +3871,7 @@ static CURLcode sftp_disconnect(struct Curl_easy *data,
   (void)dead_connection;
 
   if(sshc) {
-    if(sshc->ssh_session && sshp) {
+    if(sshc->ssh_session) {
       /* only if there is a session still around to use! */
       DEBUGF(infof(data, "SSH DISCONNECT starts now"));
       myssh_state(data, sshc, SSH_SFTP_SHUTDOWN);