From 8b9717beaa7d81aa8638643fc67fb7709093d17a Mon Sep 17 00:00:00 2001
From: "Liam R. Howlett"
Date: Fri, 22 Jul 2016 14:10:41 -0400
Subject: [PATCH v3] wget: Add --use-askpass=COMMAND support
To: address@hidden
Cc: address@hidden
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
---
ChangeLog | 16 +++++++++
doc/wget.texi | 6 ++++
src/init.c | 22 ++++++++++++
src/main.c | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/options.h | 1 +
src/url.c | 6 ++++
src/url.h | 1 +
7 files changed, 161 insertions(+)
diff --git a/ChangeLog b/ChangeLog
index dca7846..faae8c4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,19 @@
+2016-07-28 Liam R. Howlett
+
+ Add --use-askpass
+ * 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.
+
+
2016-06-09 Giuseppe Scrivano
NEWS: update
diff --git a/doc/wget.texi b/doc/wget.texi
index 1e55e63..a79cee1 100644
--- a/doc/wget.texi
+++ b/doc/wget.texi
@@ -1154,6 +1154,12 @@ 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.
address@hidden address@hidden
+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.
+
@cindex iri support
@cindex idn support
@item --no-iri
diff --git a/src/init.c b/src/init.c
index d043d83..465a295 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 },
@@ -1371,6 +1374,24 @@ 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;
+
+ if (val[0] == '\0')
+ {
+ env = getenv ("WGET_ASKPASS");
+ if (!env)
+ env = getenv ("SSH_ASKPASS");
+ if (env)
+ return cmd_string (com, env, place);
+ }
+
+ return cmd_string (com, val, place);
+}
+
#ifdef HAVE_SSL
static bool
cmd_cert_type (const char *com, const char *val, void *place)
@@ -1930,6 +1951,7 @@ cleanup (void)
xfree (opt.body_data);
xfree (opt.body_file);
xfree (opt.rejected_log);
+ xfree (opt.use_askpass);
#ifdef HAVE_LIBCARES
#include
diff --git a/src/main.c b/src/main.c
index e7d5c66..901beb1 100644
--- a/src/main.c
+++ b/src/main.c
@@ -36,6 +36,7 @@ as that of the covered work. */
#include
#include
#include
+#include
#ifdef ENABLE_NLS
# include
#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 },
@@ -691,6 +693,8 @@ Download:\n"),
N_("\
--ask-password prompt for passwords\n"),
N_("\
+ --use-askpass=COMMAND specify credential handler for requesting username and password. If no COMMAND is specified the WGET_ASKPASS or the SSH_ASKPASS 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"),
@@ -1019,6 +1023,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));
+ 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);
+ }
+ tmp[bytes] = '\0';
+
+ /* Remove a possible new line */
+ while ((tmp[bytes] == '\0' || tmp[bytes] == '\n' || tmp[bytes] == '\r') &&
+ bytes >= 0)
+ tmp[bytes--] = '\0';
+
+ *answer = strndup (tmp, sizeof (tmp));
+}
+
+/* set the user name and password*/
+void
+use_askpass (struct url *u)
+{
+ static char question[1024];
+ size_t max_size = 1024;
+
+ if (u->user == NULL || u->user[0] == '\0')
+ {
+ snprintf (question, max_size, _("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 = strdup (u->user);
+ }
+
+ if (u->passwd == NULL || u->passwd[0] == '\0')
+ {
+ snprintf(question, max_size, _("Password for 'address@hidden': "),
+ scheme_leading_string (u->scheme), u->user, u->host);
+ /* Prompt for password */
+ run_use_askpass (question, &u->passwd);
+ if (opt.recursive)
+ opt.passwd = strdup (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
@@ -1702,6 +1797,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();
@@ -1920,6 +2025,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 a8c494b..78bb17a 100644
--- a/src/options.h
+++ b/src/options.h
@@ -130,6 +130,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 ec38d6f..c133d91 100644
--- a/src/url.c
+++ b/src/url.c
@@ -512,6 +512,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