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

openSUSE Build Service is sponsored by