This adds the --use-askpass option which is disabled by default.

--use-askpass=COMMAND will request the username and password for a given
URL by executing the external program COMMAND.  If COMMAND is left
blank, then the external program in the environment variable
WGET_ASKPASS will be used.  If WGET_ASKPASS is not set then the
environment variable SSH_ASKPASS is used.  If there is no value set, an
error is returned.  If an error occurs requesting the username or
password, wget will exit.

Note: I am waiting on a member of our legal team to return from vacation to add
wget to our FSF Contribution Agreement.

Liam R. Howlett (1):
  Add --use-askpass=COMMAND support

 bootstrap.conf |   1 +
 doc/wget.texi  |  17 ++++++---
 src/init.c     |  44 +++++++++++++++++++++++
 src/main.c     | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/options.h  |   1 +
 src/url.c      |   6 ++++
 src/url.h      |   1 +
 7 files changed, 178 insertions(+), 4 deletions(-)

-- 
1.9.1

From 2dd205db90623ca8392b60b66c0db5e891fededf Mon Sep 17 00:00:00 2001
From: "Liam R. Howlett" <[email protected]>
Date: Fri, 22 Jul 2016 14:10:41 -0400
Subject: [PATCH v4] Add --use-askpass=COMMAND support
To: [email protected]
Cc: [email protected]

* doc/wget.texi: Add --use-askpass to documentation.
* src/init.c: Add cmd_use_askpasss to set opt.use_askpass based on
argument, WGET_ASKPASS, and SSH_ASKPASS environment variables.
opt.wget-askpass is freed in cleanup ()
* src/main.c: Update options & add spawn process of opt.use_askpass
command.
* src/options.h: Addition of string use_askpass.
* src/url.c: Function scheme_leading_string to access the leading
string of a parsed url.
* src/url.h: Prototype for scheme_leading_string for returning the
leading string.
* bootstrap.conf: Add posix_spawn to gnulib_modules

This adds the --use-askpass option which is disabled by default.

--use-askpass=COMMAND will request the username and password for a given
URL by executing the external program COMMAND.  If COMMAND is left
blank, then the external program in the environment variable
WGET_ASKPASS will be used.  If WGET_ASKPASS is not set then the
environment variable SSH_ASKPASS is used.  If there is no value set, an
error is returned.  If an error occurs requesting the username or
password, wget will exit.

Signed-off-by: Liam R. Howlett <[email protected]>
---
 bootstrap.conf |   1 +
 doc/wget.texi  |  17 ++++++---
 src/init.c     |  44 +++++++++++++++++++++++
 src/main.c     | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/options.h  |   1 +
 src/url.c      |   6 ++++
 src/url.h      |   1 +
 7 files changed, 178 insertions(+), 4 deletions(-)

diff --git a/bootstrap.conf b/bootstrap.conf
index 3925d55..b1bfd12 100644
--- a/bootstrap.conf
+++ b/bootstrap.conf
@@ -67,6 +67,7 @@ mkostemp
 crypto/md5
 crypto/sha1
 crypto/sha256
+posix_spawn
 quote
 quotearg
 recv
diff --git a/doc/wget.texi b/doc/wget.texi
index f6f0fbc..b2145d2 100644
--- a/doc/wget.texi
+++ b/doc/wget.texi
@@ -1154,6 +1154,15 @@ options for @sc{http} connections.
 Prompt for a password for each connection established. Cannot be specified
 when @samp{--password} is being used, because they are mutually exclusive.
 
+@item --use-askpass=@var{command}
+Prompt for a user and password using the specified command.  If no command is
+specified then the command in the environment variable WGET_ASKPASS is used.
+If WGET_ASKPASS is not set then the command in the environment variable
+SSH_ASKPASS is used.
+
+You can set the default command for use-askpass in the @file{.wgetrc}.  That
+setting may be overridden from the command line.
+
 @cindex iri support
 @cindex idn support
 @item --no-iri
@@ -1327,10 +1336,10 @@ the @code{digest}, or the Windows @code{NTLM} authentication scheme.
 Another way to specify username and password is in the @sc{url} itself
 (@pxref{URL Format}).  Either method reveals your password to anyone who
 bothers to run @code{ps}.  To prevent the passwords from being seen,
-store them in @file{.wgetrc} or @file{.netrc}, and make sure to protect
-those files from other users with @code{chmod}.  If the passwords are
-really important, do not leave them lying in those files either---edit
-the files and delete them after Wget has started the download.
+use the @samp{--use-askpass} or store them in @file{.wgetrc} or @file{.netrc},
+and make sure to protect those files from other users with @code{chmod}.  If
+the passwords are really important, do not leave them lying in those files
+either---edit the files and delete them after Wget has started the download.
 
 @iftex
 @xref{Security Considerations}, for more information about security
diff --git a/src/init.c b/src/init.c
index 06d2e44..5857588 100644
--- a/src/init.c
+++ b/src/init.c
@@ -97,6 +97,8 @@ CMD_DECLARE (cmd_directory);
 CMD_DECLARE (cmd_time);
 CMD_DECLARE (cmd_vector);
 
+CMD_DECLARE (cmd_use_askpass);
+
 CMD_DECLARE (cmd_spec_dirstruct);
 CMD_DECLARE (cmd_spec_header);
 CMD_DECLARE (cmd_spec_warc_header);
@@ -318,6 +320,7 @@ static const struct {
   { "tries",            &opt.ntry,              cmd_number_inf },
   { "trustservernames", &opt.trustservernames,  cmd_boolean },
   { "unlink",           &opt.unlink,            cmd_boolean },
+  { "useaskpass" ,      &opt.use_askpass,       cmd_use_askpass },
   { "useproxy",         &opt.use_proxy,         cmd_boolean },
   { "user",             &opt.user,              cmd_string },
   { "useragent",        NULL,                   cmd_spec_useragent },
@@ -1380,6 +1383,46 @@ cmd_time (const char *com, const char *val, void *place)
   return true;
 }
 
+
+static bool
+cmd_use_askpass (const char *com _GL_UNUSED, const char *val, void *place)
+{
+  char *env_name = "WGET_ASKPASS";
+  char *env;
+
+  if (val && *val)
+    {
+      if (!file_exists_p (val))
+        {
+          fprintf (stderr, _("%s does not exist.\n"), val);
+          exit (WGET_EXIT_GENERIC_ERROR);
+        }
+      return cmd_string (com, val, place);
+    }
+
+  env = getenv (env_name);
+  if (!(env && *env))
+    {
+      env_name = "SSH_ASKPASS";
+      env = getenv (env_name);
+    }
+
+  if (!(env && *env))
+    {
+      fprintf (stderr, _("use-askpass requires a string or either environment variable WGET_ASKPASS or SSH_ASKPASS to be set.\n"));
+      exit (WGET_EXIT_GENERIC_ERROR);
+    }
+
+  if (!file_exists_p (env))
+    {
+      fprintf (stderr, _("%s points to %s, which does not exist.\n"),
+              env_name, env);
+      exit (WGET_EXIT_GENERIC_ERROR);
+    }
+
+  return cmd_string (com, env, place);
+}
+
 #ifdef HAVE_SSL
 static bool
 cmd_cert_type (const char *com, const char *val, void *place)
@@ -1939,6 +1982,7 @@ cleanup (void)
   xfree (opt.body_data);
   xfree (opt.body_file);
   xfree (opt.rejected_log);
+  xfree (opt.use_askpass);
 
 #ifdef HAVE_LIBCARES
 #include <ares.h>
diff --git a/src/main.c b/src/main.c
index 4d69e03..d3a890c 100644
--- a/src/main.c
+++ b/src/main.c
@@ -36,6 +36,7 @@ as that of the covered work.  */
 #include <unistd.h>
 #include <string.h>
 #include <signal.h>
+#include <spawn.h>
 #ifdef ENABLE_NLS
 # include <locale.h>
 #endif
@@ -414,6 +415,7 @@ static struct cmdline_option option_data[] =
     { "tries", 't', OPT_VALUE, "tries", -1 },
     { "unlink", 0, OPT_BOOLEAN, "unlink", -1 },
     { "trust-server-names", 0, OPT_BOOLEAN, "trustservernames", -1 },
+    { "use-askpass", 0, OPT_VALUE, "useaskpass", -1},
     { "use-server-timestamps", 0, OPT_BOOLEAN, "useservertimestamps", -1 },
     { "user", 0, OPT_VALUE, "user", -1 },
     { "user-agent", 'U', OPT_VALUE, "useragent", -1 },
@@ -694,6 +696,11 @@ Download:\n"),
     N_("\
        --ask-password              prompt for passwords\n"),
     N_("\
+       --use-askpass=COMMAND       specify credential handler for requesting \n\
+                                     username and password.  If no COMMAND is \n\
+                                     specified the WGET_ASKPASS or the SSH_ASKPASS \n\
+                                     environment variable is used.\n"),
+    N_("\
        --no-iri                    turn off IRI support\n"),
     N_("\
        --local-encoding=ENC        use ENC as the local encoding for IRIs\n"),
@@ -1026,6 +1033,97 @@ prompt_for_password (void)
   return getpass("");
 }
 
+
+/* Execute external application opt.use_askpass */
+void
+run_use_askpass (char *question, char **answer)
+{
+  char tmp[1024];
+  pid_t pid;
+  int status;
+  int com[2];
+  ssize_t bytes = 0;
+  char * const argv[] = { opt.use_askpass, question, NULL };
+  posix_spawn_file_actions_t fa;
+
+  if (pipe (com) == -1)
+    {
+      fprintf (stderr, _("Cannot create pipe"));
+      exit (WGET_EXIT_GENERIC_ERROR);
+    }
+
+  status = posix_spawn_file_actions_init (&fa);
+  if (status)
+    {
+      fprintf (stderr,
+              _("Error initializing spawn file actions for use-askpass: %d"),
+              status);
+      exit (WGET_EXIT_GENERIC_ERROR);
+    }
+
+  status = posix_spawn_file_actions_adddup2 (&fa, com[1], STDOUT_FILENO);
+  if (status)
+    {
+      fprintf (stderr,
+              _("Error setting spawn file actions for use-askpass: %d"),
+              status);
+      exit (WGET_EXIT_GENERIC_ERROR);
+    }
+
+  status = posix_spawnp (&pid, opt.use_askpass, &fa, NULL, argv, environ);
+  if (status)
+    {
+      fprintf (stderr, "Error spawning %s: %d", opt.use_askpass, status);
+      exit (WGET_EXIT_GENERIC_ERROR);
+    }
+
+  /* Parent process reads from child. */
+  close (com[1]);
+  bytes = read (com[0], tmp, sizeof (tmp) - 1);
+  if (bytes <= 0)
+    {
+      fprintf (stderr,
+              _("Error reading response from command \"%s %s\": %s\n"),
+              opt.use_askpass, question, strerror (errno));
+      exit (WGET_EXIT_GENERIC_ERROR);
+    }
+  /* Set the end byte to \0, and decrement bytes */
+  tmp[bytes--] = '\0';
+
+  /* Remove a possible new line */
+  while (bytes >= 0 &&
+        (tmp[bytes] == '\0' || tmp[bytes] == '\n' || tmp[bytes] == '\r'))
+    tmp[bytes--] = '\0';
+
+  *answer = xmemdup (tmp, bytes + 2);
+}
+
+/* set the user name and password*/
+void
+use_askpass (struct url *u)
+{
+  static char question[1024];
+
+  if (u->user == NULL || u->user[0] == '\0')
+    {
+      snprintf (question, sizeof (question),  _("Username for '%s%s': "),
+                scheme_leading_string(u->scheme), u->host);
+      /* Prompt for username */
+      run_use_askpass (question, &u->user);
+      if (opt.recursive)
+        opt.user = xstrdup (u->user);
+    }
+
+  if (u->passwd == NULL || u->passwd[0] == '\0')
+    {
+      snprintf(question, sizeof (question), _("Password for '%s%s@%s': "),
+               scheme_leading_string (u->scheme), u->user, u->host);
+      /* Prompt for password */
+      run_use_askpass (question, &u->passwd);
+      if (opt.recursive)
+        opt.passwd = xstrdup (u->passwd);
+    }
+}
 /* Function that prints the line argument while limiting it
    to at most line_length. prefix is printed on the first line
    and an appropriate number of spaces are added on subsequent
@@ -1709,6 +1807,16 @@ for details.\n\n"));
         exit (WGET_EXIT_GENERIC_ERROR);
     }
 
+  if (opt.use_askpass)
+  {
+    if (opt.use_askpass[0] == '\0')
+      {
+        fprintf (stderr,
+                 _("use-askpass requires a string or either environment variable WGET_ASKPASS or SSH_ASKPASS to be set.\n"));
+        exit (WGET_EXIT_GENERIC_ERROR);
+      }
+  }
+
 #ifdef USE_WATT32
   if (opt.wdebug)
      dbug_init();
@@ -1927,6 +2035,10 @@ only if outputting to a regular file.\n"));
         }
       else
         {
+          /* Request credentials if use_askpass is set. */
+          if (opt.use_askpass)
+            use_askpass (url_parsed);
+
           if ((opt.recursive || opt.page_requisites)
               && ((url_scheme (*t) != SCHEME_FTP
 #ifdef HAVE_SSL
diff --git a/src/options.h b/src/options.h
index b2e31a8..7a3ebc6 100644
--- a/src/options.h
+++ b/src/options.h
@@ -132,6 +132,7 @@ struct options
   char *user;                   /* Generic username */
   char *passwd;                 /* Generic password */
   bool ask_passwd;              /* Ask for password? */
+  char *use_askpass;           /* value to use for use-askpass if WGET_ASKPASS is not set */
 
   bool always_rest;             /* Always use REST. */
   wgint start_pos;              /* Start position of a download. */
diff --git a/src/url.c b/src/url.c
index 4876d04..ea25fa7 100644
--- a/src/url.c
+++ b/src/url.c
@@ -510,6 +510,12 @@ scheme_disable (enum url_scheme scheme)
   supported_schemes[scheme].flags |= scm_disabled;
 }
 
+const char *
+scheme_leading_string (enum url_scheme scheme)
+{
+  return supported_schemes[scheme].leading_string;
+}
+
 /* Skip the username and password, if present in the URL.  The
    function should *not* be called with the complete URL, but with the
    portion after the scheme.
diff --git a/src/url.h b/src/url.h
index 7c77737..f4ffe20 100644
--- a/src/url.h
+++ b/src/url.h
@@ -124,6 +124,7 @@ bool url_has_scheme (const char *);
 bool url_valid_scheme (const char *);
 int scheme_default_port (enum url_scheme);
 void scheme_disable (enum url_scheme);
+const char *scheme_leading_string (enum url_scheme);
 
 char *url_string (const struct url *, enum url_auth_mode);
 char *url_file_name (const struct url *, char *);
-- 
1.9.1

Reply via email to