File 9902-Add-PasswordCommand-option-which-overrides-SSH_ASKPA.patch of Package openssh
From 5b9e539810eaeaf8f547afefa65244b7d11d2133 Mon Sep 17 00:00:00 2001
From: Ciprian Dorin Craciun <ciprian@volution.ro>
Date: Fri, 7 Apr 2017 01:42:33 +0300
Subject: [PATCH] Add `PasswordCommand` option which overrides `SSH_ASKPASS`
and should always provide a password (this is used only for login passwords)
---
misc.h | 1 +
readconf.c | 9 +++++++
readconf.h | 2 ++
readpass.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++
ssh.c | 19 +++++++++++++++
sshconnect2.c | 5 +++-
6 files changed, 101 insertions(+), 1 deletion(-)
diff --git a/misc.h b/misc.h
index 31b207a8..d5fb8da1 100644
--- a/misc.h
+++ b/misc.h
@@ -159,17 +159,18 @@ int safe_path_fd(int, const char *, struct passwd *,
char *err, size_t errlen);
/* readpass.c */
#define RP_ECHO 0x0001
#define RP_ALLOW_STDIN 0x0002
#define RP_ALLOW_EOF 0x0004
#define RP_USE_ASKPASS 0x0008
char *read_passphrase(const char *, int);
+char *read_passphrase_from_command(const char *);
int ask_permission(const char *, ...) __attribute__((format(printf, 1, 2)));
#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b))
#define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b))
#define ROUNDUP(x, y) ((((x)+((y)-1))/(y))*(y))
#endif /* _MISC_H */
diff --git a/readconf.c b/readconf.c
index 43381152..75263a20 100644
--- a/readconf.c
+++ b/readconf.c
@@ -166,20 +166,21 @@ typedef enum {
oHashKnownHosts,
oTunnel, oTunnelDevice,
oLocalCommand, oPermitLocalCommand, oRemoteCommand,
oVisualHostKey,
oKexAlgorithms, oIPQoS, oRequestTTY, oIgnoreUnknown, oProxyUseFdpass,
oCanonicalDomains, oCanonicalizeHostname, oCanonicalizeMaxDots,
oCanonicalizeFallbackLocal, oCanonicalizePermittedCNAMEs,
oStreamLocalBindMask, oStreamLocalBindUnlink, oRevokedHostKeys,
oFingerprintHash, oUpdateHostkeys, oHostbasedKeyTypes,
oPubkeyAcceptedKeyTypes, oCASignatureAlgorithms, oProxyJump,
+ oPasswordCommand,
oIgnore, oIgnoredUnknownOption, oDeprecated, oUnsupported
} OpCodes;
/* Textual representations of the tokens. */
static struct {
const char *name;
OpCodes opcode;
} keywords[] = {
/* Deprecated options */
@@ -302,20 +303,21 @@ static struct {
{ "canonicalizepermittedcnames", oCanonicalizePermittedCNAMEs },
{ "streamlocalbindmask", oStreamLocalBindMask },
{ "streamlocalbindunlink", oStreamLocalBindUnlink },
{ "revokedhostkeys", oRevokedHostKeys },
{ "fingerprinthash", oFingerprintHash },
{ "updatehostkeys", oUpdateHostkeys },
{ "hostbasedkeytypes", oHostbasedKeyTypes },
{ "pubkeyacceptedkeytypes", oPubkeyAcceptedKeyTypes },
{ "ignoreunknown", oIgnoreUnknown },
{ "proxyjump", oProxyJump },
+ { "passwordcommand", oPasswordCommand },
{ NULL, oBadOption }
};
/*
* Adds a local TCP/IP port forward to options. Never returns if there is an
* error.
*/
void
@@ -1715,20 +1717,24 @@ parse_keytypes:
filename, linenum);
/* Extra validation if the string represents an env var. */
if (arg[0] == '$' && !valid_env_name(arg + 1)) {
fatal("%.200s line %d: Invalid environment name %s.",
filename, linenum, arg);
}
if (*activep && *charptr == NULL)
*charptr = xstrdup(arg);
break;
+ case oPasswordCommand:
+ charptr = &options->password_command;
+ goto parse_command;
+
case oDeprecated:
debug("%s line %d: Deprecated option \"%s\"",
filename, linenum, keyword);
return 0;
case oUnsupported:
error("%s line %d: Unsupported option \"%s\"",
filename, linenum, keyword);
return 0;
@@ -1919,20 +1925,21 @@ initialize_options(Options * options)
options->num_canonical_domains = 0;
options->num_permitted_cnames = 0;
options->canonicalize_max_dots = -1;
options->canonicalize_fallback_local = -1;
options->canonicalize_hostname = -1;
options->revoked_host_keys = NULL;
options->fingerprint_hash = -1;
options->update_hostkeys = -1;
options->hostbased_key_types = NULL;
options->pubkey_key_types = NULL;
+ options->password_command = NULL;
}
/*
* A petite version of fill_default_options() that just fills the options
* needed for hostname canonicalization to proceed.
*/
void
fill_default_options_for_canonicalization(Options *options)
{
if (options->canonicalize_max_dots == -1)
@@ -2128,20 +2135,21 @@ fill_default_options(Options * options)
if (option_clear_or_none(v)) { \
free(v); \
v = NULL; \
} \
} while(0)
CLEAR_ON_NONE(options->local_command);
CLEAR_ON_NONE(options->remote_command);
CLEAR_ON_NONE(options->proxy_command);
CLEAR_ON_NONE(options->control_path);
CLEAR_ON_NONE(options->revoked_host_keys);
+ CLEAR_ON_NONE(options->password_command);
if (options->jump_host != NULL &&
strcmp(options->jump_host, "none") == 0 &&
options->jump_port == 0 && options->jump_user == NULL) {
free(options->jump_host);
options->jump_host = NULL;
}
/* options->identity_agent distinguishes NULL from 'none' */
/* options->user will be set in the main program if appropriate */
/* options->hostname will be set in the main program if appropriate */
/* options->host_key_alias should not be set by default */
@@ -2648,20 +2656,21 @@ dump_client_config(Options *o, const char *host)
dump_cfg_string(oRemoteCommand, o->remote_command);
dump_cfg_string(oLogLevel, log_level_name(o->log_level));
dump_cfg_string(oMacs, o->macs ? o->macs : KEX_CLIENT_MAC);
#ifdef ENABLE_PKCS11
dump_cfg_string(oPKCS11Provider, o->pkcs11_provider);
#endif
dump_cfg_string(oPreferredAuthentications, o->preferred_authentications);
dump_cfg_string(oPubkeyAcceptedKeyTypes, o->pubkey_key_types);
dump_cfg_string(oRevokedHostKeys, o->revoked_host_keys);
dump_cfg_string(oXAuthLocation, o->xauth_location);
+ dump_cfg_string(oPasswordCommand, o->password_command);
/* Forwards */
dump_cfg_forwards(oDynamicForward, o->num_local_forwards, o->local_forwards);
dump_cfg_forwards(oLocalForward, o->num_local_forwards, o->local_forwards);
dump_cfg_forwards(oRemoteForward, o->num_remote_forwards, o->remote_forwards);
/* String array options */
dump_cfg_strarray(oIdentityFile, o->num_identity_files, o->identity_files);
dump_cfg_strarray_oneline(oCanonicalDomains, o->num_canonical_domains, o->canonical_domains);
dump_cfg_strarray(oCertificateFile, o->num_certificate_files, o->certificate_files);
diff --git a/readconf.h b/readconf.h
index fc7e3825..d1f987d3 100644
--- a/readconf.h
+++ b/readconf.h
@@ -158,20 +158,22 @@ typedef struct {
int update_hostkeys; /* one of SSH_UPDATE_HOSTKEYS_* */
char *hostbased_key_types;
char *pubkey_key_types;
char *jump_user;
char *jump_host;
int jump_port;
char *jump_extra;
+ char *password_command;
+
char *ignored_unknown; /* Pattern list of unknown tokens to ignore */
} Options;
#define SSH_CANONICALISE_NO 0
#define SSH_CANONICALISE_YES 1
#define SSH_CANONICALISE_ALWAYS 2
#define SSHCTL_MASTER_NO 0
#define SSHCTL_MASTER_YES 1
#define SSHCTL_MASTER_AUTO 2
diff --git a/readpass.c b/readpass.c
index f160f866..561f5cfa 100644
--- a/readpass.c
+++ b/readpass.c
@@ -183,10 +183,76 @@ ask_permission(const char *fmt, ...)
* of the word "yes" as affirmative.
*/
if (*p == '\0' || *p == '\n' ||
strcasecmp(p, "yes") == 0)
allowed = 1;
free(p);
}
return (allowed);
}
+
+char *
+read_passphrase_from_command(const char *command)
+{
+ char *shell;
+ pid_t pid, ret;
+ size_t len;
+ char *pass;
+ int p[2], status;
+ char buf[1024];
+ void (*osigchld)(int);
+
+ if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
+ shell = _PATH_BSHELL;
+
+ if (fflush(stdout) != 0)
+ error("password_command: fflush: %s", strerror(errno));
+ if (shell == NULL)
+ fatal("internal error: shell undefined");
+ if (pipe(p) < 0) {
+ error("password_command: pipe: %s", strerror(errno));
+ return NULL;
+ }
+ osigchld = signal(SIGCHLD, SIG_DFL);
+ if ((pid = fork()) < 0) {
+ error("password_command: fork: %s", strerror(errno));
+ signal(SIGCHLD, osigchld);
+ return NULL;
+ }
+ if (pid == 0) {
+ close(p[0]);
+ if (dup2(p[1], STDOUT_FILENO) < 0)
+ fatal("password_command: dup2: %s", strerror(errno));
+ debug3("Executing (via shell %s): %s", shell, command);
+ execlp(shell, shell, "-c", command, (char *)NULL);
+ fatal("password_command: exec(%s): %s", command, strerror(errno));
+ }
+ close(p[1]);
+
+ len = 0;
+ do {
+ ssize_t r = read(p[0], buf + len, sizeof(buf) - 1 - len);
+
+ if (r == -1 && errno == EINTR)
+ continue;
+ if (r <= 0)
+ break;
+ len += r;
+ } while (sizeof(buf) - 1 - len > 0);
+ buf[len] = '\0';
+
+ close(p[0]);
+ while ((ret = waitpid(pid, &status, 0)) < 0)
+ if (errno != EINTR)
+ break;
+ signal(SIGCHLD, osigchld);
+ if (ret == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+ explicit_bzero(buf, sizeof(buf));
+ return NULL;
+ }
+
+ buf[strcspn(buf, "\r\n")] = '\0';
+ pass = xstrdup(buf);
+ explicit_bzero(buf, sizeof(buf));
+ return pass;
+}
diff --git a/ssh.c b/ssh.c
index 0777c31e..07e2c685 100644
--- a/ssh.c
+++ b/ssh.c
@@ -1308,20 +1308,39 @@ main(int ac, char **av)
"r", options.user,
"u", pw->pw_name,
(char *)NULL);
debug3("expanded RemoteCommand: %s", options.remote_command);
free(cp);
if ((r = sshbuf_put(command, options.remote_command,
strlen(options.remote_command))) != 0)
fatal("%s: buffer error: %s", __func__, ssh_err(r));
}
+ if (options.password_command != NULL) {
+ debug3("expanding PasswordCommand: %s", options.password_command);
+ cp = options.password_command;
+ options.password_command = percent_expand(cp,
+ "C", conn_hash_hex,
+ "L", shorthost,
+ "d", pw->pw_dir,
+ "h", host,
+ "l", thishost,
+ "n", host_arg,
+ "p", portstr,
+ "r", options.user,
+ "u", pw->pw_name,
+ (char *)NULL);
+ debug3("expanded PasswordCommand: %s", options.password_command);
+ free(cp);
+ }
+
+
if (options.control_path != NULL) {
cp = tilde_expand_filename(options.control_path, getuid());
free(options.control_path);
options.control_path = percent_expand(cp,
"C", conn_hash_hex,
"L", shorthost,
"h", host,
"i", uidstr,
"l", thishost,
"n", host_arg,
diff --git a/sshconnect2.c b/sshconnect2.c
index 1675f393..bedf6e8f 100644
--- a/sshconnect2.c
+++ b/sshconnect2.c
@@ -965,21 +965,24 @@ userauth_passwd(Authctxt *authctxt)
int r;
if (attempt++ >= options.number_of_password_prompts)
return 0;
if (attempt != 1)
error("Permission denied, please try again.");
snprintf(prompt, sizeof(prompt), "%.30s@%.128s's password: ",
authctxt->server_user, host);
- password = read_passphrase(prompt, 0);
+ if (options.password_command == NULL)
+ password = read_passphrase(prompt, 0);
+ else
+ password = read_passphrase_from_command(options.password_command);
if ((r = sshpkt_start(ssh, SSH2_MSG_USERAUTH_REQUEST)) != 0 ||
(r = sshpkt_put_cstring(ssh, authctxt->server_user)) != 0 ||
(r = sshpkt_put_cstring(ssh, authctxt->service)) != 0 ||
(r = sshpkt_put_cstring(ssh, authctxt->method->name)) != 0 ||
(r = sshpkt_put_u8(ssh, 0)) != 0 ||
(r = sshpkt_put_cstring(ssh, password)) != 0 ||
(r = sshpkt_add_padding(ssh, 64)) != 0 ||
(r = sshpkt_send(ssh)) != 0)
fatal("%s: %s", __func__, ssh_err(r));
--
2.21.0