]> git.ipfire.org Git - thirdparty/openvpn.git/commitdiff
Add an optional username-only flag for auth-user-pass
authorSelva Nair <selva.nair@gmail.com>
Tue, 3 Mar 2026 14:28:14 +0000 (15:28 +0100)
committerGert Doering <gert@greenie.muc.de>
Mon, 30 Mar 2026 12:35:49 +0000 (14:35 +0200)
Specify "--auth-user-pass username-only" for openvpn to prompt
for only username, not password. Prompt via management interface
uses the usual ">PASSWORD 'Auth' " prompt with type "username"
instead of "username/password".

Internally, the password gets set as "[[BLANK]]" which is currently
used as tag for blank password.

Not compatible with --static-challenge or when username and
password are inlined or read from a file. In such cases, the user
hard-code a dummy password in the file instead.

Change-Id: I788f76e6a70a9c20bca3367140d2741bd0551582
Signed-off-by: Selva Nair <selva.nair@gmail.com>
Acked-by: Arne Schwabe <arne-openvpn@rfc2549.org>
Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/1548
Message-Id: <20260303142819.6123-1-gert@greenie.muc.de>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg35855.html
Signed-off-by: Gert Doering <gert@greenie.muc.de>
(cherry picked from commit dfbf80b0a04a986fc5b5d5fef67d86ce68439b0b)

12 files changed:
doc/man-sections/client-options.rst
doc/management-notes.txt
src/openvpn/init.c
src/openvpn/manage.c
src/openvpn/manage.h
src/openvpn/misc.c
src/openvpn/misc.h
src/openvpn/options.c
src/openvpn/options.h
src/openvpn/ssl.c
src/openvpn/ssl.h
src/openvpn/ssl_common.h

index 85d25e5189b903afcd74ff094a168f5157b5ea2d..1664eeddfd5dd3d5aef422cbae1065dbf908b808 100644 (file)
@@ -68,7 +68,9 @@ configuration.
       auth-user-pass up
 
   If ``up`` is present, it must be a file containing username/password on 2
-  lines. If the password line is missing, OpenVPN will prompt for one.
+  lines or a flag named :code:`username-only` to indicate no password
+  should be prompted for. In the former case, if the password line is missing
+  in the file, OpenVPN will prompt for one.
 
   If ``up`` is omitted, username/password will be prompted from the
   console.
@@ -84,6 +86,20 @@ configuration.
   where password is optional, and will be prompted from the console if
   missing.
 
+  The :code:`username-only` flag is meant to be used with SSO authentication.
+  In this case the user will be asked for a username but not password. Instead,
+  a dummy password :code:`[[BLANK]]` is generated internally and submitted to
+  the server. See management-notes.txt for how this option affects username/password
+  prompt via the management interface. For the console, it simply eliminates
+  the password prompt.
+
+  The :code:`username-only` flag cannot be used along with embedding username and/or
+  password in the config file, or while reading them from an external file. In
+  such cases, if only username is relevant and no password prompt is desired, a
+  dummy password like 'no_passsword' should be embedded as well. This flag is also
+  incompatible with the ``--static-challenge`` option and legacy ``dynamic challenge``
+  protocol.
+
   The server configuration must specify an ``--auth-user-pass-verify``
   script to verify the username/password provided by the client.
 
index 41e2a9142269d50e278254384aeaa5dbdec4f501..7da4aafd935933219862024032492f059ba85d3b 100644 (file)
@@ -304,6 +304,19 @@ COMMAND -- password and username
     username "Auth" foo
     password "Auth" bar
 
+  Example 3:
+
+    >PASSWORD:Need 'Auth' username
+
+  OpenVPN needs a --auth-user-pass username.  The
+  management interface client should respond:
+
+    username "Auth" foo
+
+  In this case the user should not be prompted for a password.
+  Support for such username-only prompting is conditional on the
+  client announcing a version >= 4.
+
   The username/password itself can be in quotes, and special
   characters such as double quote or backslash must be escaped,
   for example,
@@ -499,6 +512,7 @@ version. This was fixed starting version 4: clients should expect
 Minimum client version required for certain features is listed below:
     >PK_SIGN:[base64]           -- version 2 or greater
     >PK_SIGN:[base64],[alg]     -- version 3 or greater
+    >PASSWORD:Need 'Auth' username -- version 4 or greater
 
 COMMAND -- auth-retry
 ---------------------
index 70c0b5d10fda08c7782647fd92d56bd790611b22..1246cfb3dece42cc736639decefb769c9e13df92 100644 (file)
@@ -655,10 +655,10 @@ init_query_passwords(const struct context *c)
         enable_auth_user_pass();
 #ifdef ENABLE_MANAGEMENT
         auth_user_pass_setup(c->options.auth_user_pass_file, c->options.auth_user_pass_file_inline,
-                             &c->options.sc_info);
+                             c->options.auth_user_pass_username_only, &c->options.sc_info);
 #else
         auth_user_pass_setup(c->options.auth_user_pass_file, c->options.auth_user_pass_file_inline,
-                             NULL);
+                             c->options.auth_user_pass_username_only, NULL);
 #endif
     }
 }
@@ -3383,6 +3383,7 @@ do_init_crypto_tls(struct context *c, const unsigned int flags)
     }
     to.auth_user_pass_file = options->auth_user_pass_file;
     to.auth_user_pass_file_inline = options->auth_user_pass_file_inline;
+    to.auth_user_pass_username_only = options->auth_user_pass_username_only;
     to.auth_token_generate = options->auth_token_generate;
     to.auth_token_lifetime = options->auth_token_lifetime;
     to.auth_token_renewal = options->auth_token_renewal;
index d26c9b2cd611699faa32ea61716a1ef7f7da5f7e..9e77031fc5edaaf128ba34edeacaddf9af24f0d5 100644 (file)
@@ -58,9 +58,6 @@
 #define MANAGEMENT_ECHO_FLAGS 0
 #endif
 
-/* tag for blank username/password */
-static const char blank_up[] = "[[BLANK]]";
-
 /*
  * Management client versions indicating feature support in client.
  * Append new values as needed but do not change exisiting ones.
@@ -70,6 +67,7 @@ enum mcv
     MCV_DEFAULT = 1,
     MCV_PKSIGN = 2,
     MCV_PKSIGN_ALG = 3,
+    MCV_USERNAME_ONLY = 4,
 };
 
 struct management *management; /* GLOBAL */
@@ -740,6 +738,13 @@ man_up_finalize(struct management *man)
 {
     switch (man->connection.up_query_mode)
     {
+        case UP_QUERY_USERNAME:
+            if (strlen(man->connection.up_query.username))
+            {
+                man->connection.up_query.defined = true;
+            }
+            break;
+
         case UP_QUERY_USER_PASS:
             if (!strlen(man->connection.up_query.username))
             {
@@ -794,7 +799,9 @@ static void
 man_query_username(struct management *man, const char *type, const char *string)
 {
     const bool needed =
-        ((man->connection.up_query_mode == UP_QUERY_USER_PASS) && man->connection.up_query_type);
+        ((man->connection.up_query_mode == UP_QUERY_USER_PASS
+          || man->connection.up_query_mode == UP_QUERY_USERNAME)
+         && man->connection.up_query_type);
     man_query_user_pass(man, type, string, needed, "username", man->connection.up_query.username,
                         USER_PASS_LEN);
 }
@@ -3558,6 +3565,12 @@ management_query_user_pass(struct management *man, struct user_pass *up, const c
             prefix = "PASSWORD";
             alert_type = "password";
         }
+        else if ((man->connection.client_version >= MCV_USERNAME_ONLY) && (flags & GET_USER_PASS_USERNAME_ONLY))
+        {
+            up_query_mode = UP_QUERY_USERNAME;
+            prefix = "PASSWORD";
+            alert_type = "username";
+        }
         else
         {
             up_query_mode = UP_QUERY_USER_PASS;
index 38f437f47c2cce69ede5a1b9070b7b24c58d05e8..e5ad23fb2005f4e8c8f9e77b4d9fef784681b2b9 100644 (file)
@@ -264,6 +264,7 @@ struct man_settings
 #define UP_QUERY_PASS      2
 #define UP_QUERY_NEED_OK   3
 #define UP_QUERY_NEED_STR  4
+#define UP_QUERY_USERNAME  5
 
 /* states */
 #define MS_INITIAL       0 /* all sockets are closed */
index 9ff281c9c4501bde2365c38cbece10a83538f7e1..c00a3ce6cf10bce572fdc5c0956849ed18e6f617 100644 (file)
@@ -215,7 +215,6 @@ get_user_pass_cr(struct user_pass *up, const char *auth_file, const char *prefix
         {
             msg(M_WARN, "Note: previous '%s' credentials failed", prefix);
         }
-
 #ifdef ENABLE_MANAGEMENT
         /*
          * Get username/password from management interface?
@@ -389,7 +388,7 @@ get_user_pass_cr(struct user_pass *up, const char *auth_file, const char *prefix
                     query_user_add(BSTR(&user_prompt), up->username, USER_PASS_LEN, true);
                 }
 
-                if (password_from_stdin)
+                if (password_from_stdin && !(flags & GET_USER_PASS_USERNAME_ONLY))
                 {
                     query_user_add(BSTR(&pass_prompt), up->password, USER_PASS_LEN, false);
                 }
@@ -451,6 +450,12 @@ get_user_pass_cr(struct user_pass *up, const char *auth_file, const char *prefix
             }
         }
 
+        /* Use tag for blank password if we are not prompting for one */
+        if (flags & GET_USER_PASS_USERNAME_ONLY)
+        {
+            strncpy(up->password, blank_up, sizeof(up->password));
+        }
+
         string_mod(up->username, CC_PRINT, CC_CRLF, 0);
         string_mod(up->password, CC_PRINT, CC_CRLF, 0);
 
index e9cfadbafea3d2ddc3a49749016de4b5bc739a9b..76bcac3630f999358b4aa17326c4c16f35a5b32e 100644 (file)
@@ -48,6 +48,9 @@ const char **make_extended_arg_array(char **p, bool is_inline, struct gc_arena *
  * Get and store a username/password
  */
 
+/* tag for blank username/password */
+static const char blank_up[] = "[[BLANK]]";
+
 struct user_pass
 {
     bool defined;
@@ -123,6 +126,8 @@ struct static_challenge_info
 #define GET_USER_PASS_INLINE_CREDS            (1 << 10)
 /** indicates password and response should be concatenated */
 #define GET_USER_PASS_STATIC_CHALLENGE_CONCAT (1 << 11)
+/** indicate that only username should be prompted for auth-user-pass */
+#define GET_USER_PASS_USERNAME_ONLY           (1 << 12)
 
 /**
  * Retrieves the user credentials from various sources depending on the flags.
index f2d5efe438848a75f94610125da16c5567ecf058..b3a126dab2ae57caa3d3a6bcec393d856ae2aad7 100644 (file)
@@ -511,7 +511,8 @@ static const char usage_message[] =
     "                  up is a file containing the username on the first line,\n"
     "                  and a password on the second. If either the password or both\n"
     "                  the username and the password are omitted OpenVPN will prompt\n"
-    "                  for them from console.\n"
+    "                  for them from console. If [up] is 'username-only', only username\n"
+    "                  will be prompted for from console or management interface.\n"
     "--pull           : Accept certain config file options from the peer as if they\n"
     "                  were part of the local config file.  Must be specified\n"
     "                  when connecting to a '--mode server' remote host.\n"
@@ -3939,6 +3940,12 @@ options_postprocess_mutate(struct options *o, struct env_set *es)
     {
         o->auth_token_renewal = o->renegotiate_seconds;
     }
+#if ENABLE_MANAGEMENT
+    if (o->auth_user_pass_username_only && o->sc_info.challenge_text)
+    {
+        msg(M_USAGE, "'auth-user-pass username-only' cannot be used with static challenge");
+    }
+#endif
     pre_connect_save(o);
 }
 
@@ -7742,7 +7749,13 @@ add_option(struct options *options, char *p[], bool is_inline, const char *file,
     else if (streq(p[0], "auth-user-pass") && !p[2])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL | OPT_P_INLINE);
-        if (p[1])
+        options->auth_user_pass_username_only = false;
+        if (p[1] && streq(p[1], "username-only"))
+        {
+            options->auth_user_pass_username_only = true;
+            options->auth_user_pass_file = "stdin";
+        }
+        else if (p[1])
         {
             options->auth_user_pass_file = p[1];
             options->auth_user_pass_file_inline = is_inline;
index 781c258d3832f75a91d6d1977d2a349d29a196fc..05256d614394a8dcd02bd814322f40efbd32f8f3 100644 (file)
@@ -557,6 +557,7 @@ struct options
     unsigned int push_update_options_found; /* tracks which option types have been reset in current PUSH_UPDATE sequence */
     const char *auth_user_pass_file;
     bool auth_user_pass_file_inline;
+    bool auth_user_pass_username_only;
     struct options_pre_connect *pre_connect;
 
     int scheduled_exit_interval;
index 69d0e4eec358e86eb1aaa173c01b5e918308e1ec..962f5df4aabe2a55b6b23ae12e4ad0741bb85e00 100644 (file)
@@ -290,7 +290,8 @@ enable_auth_user_pass(void)
 }
 
 void
-auth_user_pass_setup(const char *auth_file, bool is_inline, const struct static_challenge_info *sci)
+auth_user_pass_setup(const char *auth_file, bool is_inline, bool username_only,
+                     const struct static_challenge_info *sci)
 {
     unsigned int flags = GET_USER_PASS_MANAGEMENT;
 
@@ -298,6 +299,10 @@ auth_user_pass_setup(const char *auth_file, bool is_inline, const struct static_
     {
         flags |= GET_USER_PASS_INLINE_CREDS;
     }
+    if (username_only)
+    {
+        flags |= GET_USER_PASS_USERNAME_ONLY;
+    }
 
     if (!auth_user_pass.defined && !auth_token.defined)
     {
@@ -2099,10 +2104,12 @@ key_method_2_write(struct buffer *buf, struct tls_multi *multi, struct tls_sessi
     {
 #ifdef ENABLE_MANAGEMENT
         auth_user_pass_setup(session->opt->auth_user_pass_file,
-                             session->opt->auth_user_pass_file_inline, session->opt->sci);
+                             session->opt->auth_user_pass_file_inline,
+                             session->opt->auth_user_pass_username_only, session->opt->sci);
 #else
         auth_user_pass_setup(session->opt->auth_user_pass_file,
-                             session->opt->auth_user_pass_file_inline, NULL);
+                             session->opt->auth_user_pass_file_inline,
+                             session->opt->auth_user_pass_username_only, NULL);
 #endif
         struct user_pass *up = &auth_user_pass;
 
index 28a3b781b6d9c2a97700175f4f988858a80cc6a9..5822336f885978cc83a5116e74af23116b50827c 100644 (file)
@@ -389,7 +389,7 @@ void enable_auth_user_pass(void);
  * credentials stored in the file, however, if is_inline is true then auth_file
  * contains the username/password inline.
  */
-void auth_user_pass_setup(const char *auth_file, bool is_inline,
+void auth_user_pass_setup(const char *auth_file, bool is_inline, bool username_only,
                           const struct static_challenge_info *sc_info);
 
 /*
index fba01bbe32003620deda0c3d39e5f5526890f9c6..6f310a56a54b654e7ac86996d5973ab69de2739d 100644 (file)
@@ -396,6 +396,7 @@ struct tls_options
     const char *export_peer_cert_dir;
     const char *auth_user_pass_file;
     bool auth_user_pass_file_inline;
+    bool auth_user_pass_username_only;
 
     bool auth_token_generate;  /**< Generate auth-tokens on successful
                                 * user/pass auth,seet via