From 2dd205db90623ca8392b60b66c0db5e891fededf Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett" Date: Fri, 22 Jul 2016 14:10:41 -0400 Subject: [PATCH v4] Add --use-askpass=COMMAND support To: address@hidden Cc: address@hidden * 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 --- 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. 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. + +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 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 #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 }, @@ -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 '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 = 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