mirror of
https://github.com/opnsense/src.git
synced 2026-05-27 20:02:43 -04:00
Some notable changes, from upstream's release notes: - sshd(8): Remove support for obsolete "host/port" syntax. - ssh(1): When prompting whether to record a new host key, accept the key fingerprint as a synonym for "yes". - ssh-keygen(1): when acting as a CA and signing certificates with an RSA key, default to using the rsa-sha2-512 signature algorithm. - ssh(1), sshd(8), ssh-keygen(1): this release removes the "ssh-rsa" (RSA/SHA1) algorithm from those accepted for certificate signatures. - ssh-sk-helper(8): this is a new binary. It is used by the FIDO/U2F support to provide address-space isolation for token middleware libraries (including the internal one). - ssh(1): this release enables UpdateHostkeys by default subject to some conservative preconditions. - scp(1): this release changes the behaviour of remote to remote copies (e.g. "scp host-a:/path host-b:") to transfer through the local host by default. - scp(1): experimental support for transfers using the SFTP protocol as a replacement for the venerable SCP/RCP protocol that it has traditionally used. Additional integration work is needed to support FIDO/U2F in the base system. Deprecation Notice ------------------ OpenSSH will disable the ssh-rsa signature scheme by default in the next release. Reviewed by: imp MFC after: 1 month Relnotes: Yes Sponsored by: The FreeBSD Foundation Differential Revision: https://reviews.freebsd.org/D29985 (cherry picked from commit19261079b7) (cherry picked from commitf448c3ed4a) (cherry picked from commit1f290c707a) (cherry picked from commit0f9bafdfc3) (cherry picked from commitadb56e58e8) (cherry picked from commit576b58108c) (cherry picked from commit1c99af1ebe) (cherry picked from commit87152f3405) (cherry picked from commit172fa4aa75)
331 lines
8.3 KiB
C
331 lines
8.3 KiB
C
/* $OpenBSD: readpass.c,v 1.69 2021/07/23 05:56:47 djm Exp $ */
|
|
/*
|
|
* Copyright (c) 2001 Markus Friedl. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#ifdef HAVE_PATHS_H
|
|
# include <paths.h>
|
|
#endif
|
|
#include <signal.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "xmalloc.h"
|
|
#include "misc.h"
|
|
#include "pathnames.h"
|
|
#include "log.h"
|
|
#include "ssh.h"
|
|
#include "uidswap.h"
|
|
|
|
static char *
|
|
ssh_askpass(char *askpass, const char *msg, const char *env_hint)
|
|
{
|
|
pid_t pid, ret;
|
|
size_t len;
|
|
char *pass;
|
|
int p[2], status;
|
|
char buf[1024];
|
|
void (*osigchld)(int);
|
|
|
|
if (fflush(stdout) != 0)
|
|
error_f("fflush: %s", strerror(errno));
|
|
if (askpass == NULL)
|
|
fatal("internal error: askpass undefined");
|
|
if (pipe(p) == -1) {
|
|
error_f("pipe: %s", strerror(errno));
|
|
return NULL;
|
|
}
|
|
osigchld = ssh_signal(SIGCHLD, SIG_DFL);
|
|
if ((pid = fork()) == -1) {
|
|
error_f("fork: %s", strerror(errno));
|
|
ssh_signal(SIGCHLD, osigchld);
|
|
return NULL;
|
|
}
|
|
if (pid == 0) {
|
|
close(p[0]);
|
|
if (dup2(p[1], STDOUT_FILENO) == -1)
|
|
fatal_f("dup2: %s", strerror(errno));
|
|
if (env_hint != NULL)
|
|
setenv("SSH_ASKPASS_PROMPT", env_hint, 1);
|
|
execlp(askpass, askpass, msg, (char *)NULL);
|
|
fatal_f("exec(%s): %s", askpass, 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)) == -1)
|
|
if (errno != EINTR)
|
|
break;
|
|
ssh_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;
|
|
}
|
|
|
|
/* private/internal read_passphrase flags */
|
|
#define RP_ASK_PERMISSION 0x8000 /* pass hint to askpass for confirm UI */
|
|
|
|
/*
|
|
* Reads a passphrase from /dev/tty with echo turned off/on. Returns the
|
|
* passphrase (allocated with xmalloc). Exits if EOF is encountered. If
|
|
* RP_ALLOW_STDIN is set, the passphrase will be read from stdin if no
|
|
* tty is or askpass program is available
|
|
*/
|
|
char *
|
|
read_passphrase(const char *prompt, int flags)
|
|
{
|
|
char cr = '\r', *askpass = NULL, *ret, buf[1024];
|
|
int rppflags, ttyfd, use_askpass = 0, allow_askpass = 0;
|
|
const char *askpass_hint = NULL;
|
|
const char *s;
|
|
|
|
if ((s = getenv("DISPLAY")) != NULL)
|
|
allow_askpass = *s != '\0';
|
|
if ((s = getenv(SSH_ASKPASS_REQUIRE_ENV)) != NULL) {
|
|
if (strcasecmp(s, "force") == 0) {
|
|
use_askpass = 1;
|
|
allow_askpass = 1;
|
|
} else if (strcasecmp(s, "prefer") == 0)
|
|
use_askpass = allow_askpass;
|
|
else if (strcasecmp(s, "never") == 0)
|
|
allow_askpass = 0;
|
|
}
|
|
|
|
rppflags = (flags & RP_ECHO) ? RPP_ECHO_ON : RPP_ECHO_OFF;
|
|
if (use_askpass)
|
|
debug_f("requested to askpass");
|
|
else if (flags & RP_USE_ASKPASS)
|
|
use_askpass = 1;
|
|
else if (flags & RP_ALLOW_STDIN) {
|
|
if (!isatty(STDIN_FILENO)) {
|
|
debug_f("stdin is not a tty");
|
|
use_askpass = 1;
|
|
}
|
|
} else {
|
|
rppflags |= RPP_REQUIRE_TTY;
|
|
ttyfd = open(_PATH_TTY, O_RDWR);
|
|
if (ttyfd >= 0) {
|
|
/*
|
|
* If we're on a tty, ensure that show the prompt at
|
|
* the beginning of the line. This will hopefully
|
|
* clobber any password characters the user has
|
|
* optimistically typed before echo is disabled.
|
|
*/
|
|
(void)write(ttyfd, &cr, 1);
|
|
close(ttyfd);
|
|
} else {
|
|
debug_f("can't open %s: %s", _PATH_TTY,
|
|
strerror(errno));
|
|
use_askpass = 1;
|
|
}
|
|
}
|
|
|
|
if ((flags & RP_USE_ASKPASS) && !allow_askpass)
|
|
return (flags & RP_ALLOW_EOF) ? NULL : xstrdup("");
|
|
|
|
if (use_askpass && allow_askpass) {
|
|
if (getenv(SSH_ASKPASS_ENV))
|
|
askpass = getenv(SSH_ASKPASS_ENV);
|
|
else
|
|
askpass = _PATH_SSH_ASKPASS_DEFAULT;
|
|
if ((flags & RP_ASK_PERMISSION) != 0)
|
|
askpass_hint = "confirm";
|
|
if ((ret = ssh_askpass(askpass, prompt, askpass_hint)) == NULL)
|
|
if (!(flags & RP_ALLOW_EOF))
|
|
return xstrdup("");
|
|
return ret;
|
|
}
|
|
|
|
if (readpassphrase(prompt, buf, sizeof buf, rppflags) == NULL) {
|
|
if (flags & RP_ALLOW_EOF)
|
|
return NULL;
|
|
return xstrdup("");
|
|
}
|
|
|
|
ret = xstrdup(buf);
|
|
explicit_bzero(buf, sizeof(buf));
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
ask_permission(const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
char *p, prompt[1024];
|
|
int allowed = 0;
|
|
|
|
va_start(args, fmt);
|
|
vsnprintf(prompt, sizeof(prompt), fmt, args);
|
|
va_end(args);
|
|
|
|
p = read_passphrase(prompt,
|
|
RP_USE_ASKPASS|RP_ALLOW_EOF|RP_ASK_PERMISSION);
|
|
if (p != NULL) {
|
|
/*
|
|
* Accept empty responses and responses consisting
|
|
* of the word "yes" as affirmative.
|
|
*/
|
|
if (*p == '\0' || *p == '\n' ||
|
|
strcasecmp(p, "yes") == 0)
|
|
allowed = 1;
|
|
free(p);
|
|
}
|
|
|
|
return (allowed);
|
|
}
|
|
|
|
static void
|
|
writemsg(const char *msg)
|
|
{
|
|
(void)write(STDERR_FILENO, "\r", 1);
|
|
(void)write(STDERR_FILENO, msg, strlen(msg));
|
|
(void)write(STDERR_FILENO, "\r\n", 2);
|
|
}
|
|
|
|
struct notifier_ctx {
|
|
pid_t pid;
|
|
void (*osigchld)(int);
|
|
};
|
|
|
|
struct notifier_ctx *
|
|
notify_start(int force_askpass, const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
char *prompt = NULL;
|
|
pid_t pid = -1;
|
|
void (*osigchld)(int) = NULL;
|
|
const char *askpass, *s;
|
|
struct notifier_ctx *ret = NULL;
|
|
|
|
va_start(args, fmt);
|
|
xvasprintf(&prompt, fmt, args);
|
|
va_end(args);
|
|
|
|
if (fflush(NULL) != 0)
|
|
error_f("fflush: %s", strerror(errno));
|
|
if (!force_askpass && isatty(STDERR_FILENO)) {
|
|
writemsg(prompt);
|
|
goto out_ctx;
|
|
}
|
|
if ((askpass = getenv("SSH_ASKPASS")) == NULL)
|
|
askpass = _PATH_SSH_ASKPASS_DEFAULT;
|
|
if (*askpass == '\0') {
|
|
debug3_f("cannot notify: no askpass");
|
|
goto out;
|
|
}
|
|
if (getenv("DISPLAY") == NULL &&
|
|
((s = getenv(SSH_ASKPASS_REQUIRE_ENV)) == NULL ||
|
|
strcmp(s, "force") != 0)) {
|
|
debug3_f("cannot notify: no display");
|
|
goto out;
|
|
}
|
|
osigchld = ssh_signal(SIGCHLD, SIG_DFL);
|
|
if ((pid = fork()) == -1) {
|
|
error_f("fork: %s", strerror(errno));
|
|
ssh_signal(SIGCHLD, osigchld);
|
|
free(prompt);
|
|
return NULL;
|
|
}
|
|
if (pid == 0) {
|
|
if (stdfd_devnull(1, 1, 0) == -1)
|
|
fatal_f("stdfd_devnull failed");
|
|
closefrom(STDERR_FILENO + 1);
|
|
setenv("SSH_ASKPASS_PROMPT", "none", 1); /* hint to UI */
|
|
execlp(askpass, askpass, prompt, (char *)NULL);
|
|
error_f("exec(%s): %s", askpass, strerror(errno));
|
|
_exit(1);
|
|
/* NOTREACHED */
|
|
}
|
|
out_ctx:
|
|
if ((ret = calloc(1, sizeof(*ret))) == NULL) {
|
|
kill(pid, SIGTERM);
|
|
fatal_f("calloc failed");
|
|
}
|
|
ret->pid = pid;
|
|
ret->osigchld = osigchld;
|
|
out:
|
|
free(prompt);
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
notify_complete(struct notifier_ctx *ctx, const char *fmt, ...)
|
|
{
|
|
int ret;
|
|
char *msg = NULL;
|
|
va_list args;
|
|
|
|
if (ctx != NULL && fmt != NULL && ctx->pid == -1) {
|
|
/*
|
|
* notify_start wrote to stderr, so send conclusion message
|
|
* there too
|
|
*/
|
|
va_start(args, fmt);
|
|
xvasprintf(&msg, fmt, args);
|
|
va_end(args);
|
|
writemsg(msg);
|
|
free(msg);
|
|
}
|
|
|
|
if (ctx == NULL || ctx->pid <= 0) {
|
|
free(ctx);
|
|
return;
|
|
}
|
|
kill(ctx->pid, SIGTERM);
|
|
while ((ret = waitpid(ctx->pid, NULL, 0)) == -1) {
|
|
if (errno != EINTR)
|
|
break;
|
|
}
|
|
if (ret == -1)
|
|
fatal_f("waitpid: %s", strerror(errno));
|
|
ssh_signal(SIGCHLD, ctx->osigchld);
|
|
free(ctx);
|
|
}
|