From e9b9bbac22c26cf67316fa8e6c6b9e831af31949 Mon Sep 17 00:00:00 2001
From: Daniel Stenberg <daniel@haxx.se>
Date: Fri, 15 Nov 2024 11:06:36 +0100
Subject: [PATCH] netrc: address several netrc parser flaws

- make sure that a match that returns a username also returns a
  password, that should be blank if no password is found

- fix handling of multiple logins for same host where the password/login
  order might be reversed.

- reject credentials provided in the .netrc if they contain ASCII control
  codes - if the used protocol does not support such (like HTTP and WS do)

Reported-by: Harry Sintonen

Add test 478, 479 and 480 to verify. Updated unit 1304.

Closes #15586
---
 lib/netrc.c            | 113 +++++++++++++++++++++++------------------
 lib/url.c              |  60 +++++++++++++++-------
 tests/data/Makefile.am |   2 +-
 tests/data/test478     |  73 ++++++++++++++++++++++++++
 tests/data/test479     | 107 ++++++++++++++++++++++++++++++++++++++
 tests/data/test480     |  38 ++++++++++++++
 tests/unit/unit1304.c  |  75 ++++++++-------------------
 7 files changed, 345 insertions(+), 123 deletions(-)
 create mode 100644 tests/data/test478
 create mode 100644 tests/data/test479
 create mode 100644 tests/data/test480

Index: curl-7.66.0/lib/netrc.c
===================================================================
--- curl-7.66.0.orig/lib/netrc.c
+++ curl-7.66.0/lib/netrc.c
@@ -164,6 +164,16 @@ static int parsenetrc(const char *host,
             state = HOSTFOUND;
             state_our_login = FALSE;
           }
+          else if(strcasecompare("default", tok)) {
+            state = HOSTVALID;
+            retcode = NETRC_SUCCESS; /* we did find our host */
+            Curl_safefree(password);
+            if(!specific_login)
+              if(login_alloc) {
+                free(login);
+                login_alloc = FALSE;
+              }
+          }
           break;
         } /* switch (state) */
 
@@ -172,6 +182,12 @@ static int parsenetrc(const char *host,
     } /* while fgets() */
 
     out:
+    if(!retcode && !password && state_our_login) {
+      /* success without a password, set a blank one */
+      password = strdup("");
+      if(!password)
+        retcode = 1; /* out of memory */
+    }
     if(!retcode) {
       /* success */
       *login_changed = FALSE;
Index: curl-7.66.0/lib/url.c
===================================================================
--- curl-7.66.0.orig/lib/url.c
+++ curl-7.66.0/lib/url.c
@@ -2674,6 +2674,17 @@ static CURLcode parse_remote_port(struct
   return CURLE_OK;
 }
 
+static bool str_has_ctrl(const char *input)
+{
+  const unsigned char *str = (const unsigned char *)input;
+  while(*str) {
+    if(*str < 0x20)
+      return TRUE;
+    str++;
+  }
+  return FALSE;
+}
+
 /*
  * Override the login details from the URL with that in the CURLOPT_USERPWD
  * option or a .netrc file, if applicable.
@@ -2731,29 +2742,39 @@ static CURLcode override_login(struct Cu
     bool netrc_passwd_changed = FALSE;
     int ret;
 
-    ret = Curl_parsenetrc(conn->host.name,
-                          userp, passwdp,
-                          &netrc_user_changed, &netrc_passwd_changed,
-                          data->set.str[STRING_NETRC_FILE]);
-    if(ret > 0) {
-      infof(data, "Couldn't find host %s in the .netrc file; using defaults\n",
-            conn->host.name);
-    }
-    else if(ret < 0) {
-      return CURLE_OUT_OF_MEMORY;
-    }
-    else {
-      /* set bits.netrc TRUE to remember that we got the name from a .netrc
-         file, so that it is safe to use even if we followed a Location: to a
-         different host or similar. */
-      conn->bits.netrc = TRUE;
-      conn->bits.user_passwd = TRUE; /* enable user+password */
-
-      if(netrc_user_changed) {
-        user_changed = TRUE;
+    if(!*passwdp) {
+      ret = Curl_parsenetrc(conn->host.name,
+                            userp, passwdp,
+                            &netrc_user_changed, &netrc_passwd_changed,
+                            data->set.str[STRING_NETRC_FILE]);
+      if(ret > 0) {
+        infof(data, "Couldn't find host %s in the .netrc file; using defaults\n",
+              conn->host.name);
+      }
+      else if(ret < 0) {
+        return CURLE_OUT_OF_MEMORY;
       }
-      if(netrc_passwd_changed) {
-        passwd_changed = TRUE;
+      else {
+          if(!(conn->handler->flags&PROTOPT_USERPWDCTRL)) {
+            /* if the protocol can't handle control codes in credentials, make
+              sure there are none */
+            if(str_has_ctrl(*userp) || str_has_ctrl(*passwdp)) {
+              failf(data, "control code detected in .netrc credentials");
+              return CURLE_READ_ERROR;
+            }
+          }
+        /* set bits.netrc TRUE to remember that we got the name from a .netrc
+          file, so that it is safe to use even if we followed a Location: to a
+          different host or similar. */
+        conn->bits.netrc = TRUE;
+        conn->bits.user_passwd = TRUE; /* enable user+password */
+
+        if(netrc_user_changed) {
+          user_changed = TRUE;
+        }
+        if(netrc_passwd_changed) {
+          passwd_changed = TRUE;
+        }
       }
     }
   }
Index: curl-7.66.0/lib/urldata.h
===================================================================
--- curl-7.66.0.orig/lib/urldata.h
+++ curl-7.66.0/lib/urldata.h
@@ -734,6 +734,9 @@ struct Curl_handler {
                                          HTTP proxy as HTTP proxies may know
                                          this protocol and act as a gateway */
 #define PROTOPT_WILDCARD (1<<12) /* protocol supports wildcard matching */
+#define PROTOPT_USERPWDCTRL (1<<13) /* Allow "control bytes" (< 32 ascii) in 
+                                       user name and password */
+
 
 #define CONNCHECK_NONE 0                 /* No checks */
 #define CONNCHECK_ISDEAD (1<<0)          /* Check if the connection is dead. */
