aboutsummaryrefslogtreecommitdiff
path: root/misc.c
diff options
context:
space:
mode:
Diffstat (limited to 'misc.c')
-rw-r--r--misc.c373
1 files changed, 316 insertions, 57 deletions
diff --git a/misc.c b/misc.c
index 4623b5755912..d988ce3b96c5 100644
--- a/misc.c
+++ b/misc.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: misc.c,v 1.153 2020/06/26 05:16:38 djm Exp $ */
+/* $OpenBSD: misc.c,v 1.162 2021/02/28 01:50:47 dtucker Exp $ */
/*
* Copyright (c) 2000 Markus Friedl. All rights reserved.
* Copyright (c) 2005-2020 Damien Miller. All rights reserved.
@@ -231,6 +231,60 @@ set_rdomain(int fd, const char *name)
#endif
}
+int
+get_sock_af(int fd)
+{
+ struct sockaddr_storage to;
+ socklen_t tolen = sizeof(to);
+
+ memset(&to, 0, sizeof(to));
+ if (getsockname(fd, (struct sockaddr *)&to, &tolen) == -1)
+ return -1;
+#ifdef IPV4_IN_IPV6
+ if (to.ss_family == AF_INET6 &&
+ IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)&to)->sin6_addr))
+ return AF_INET;
+#endif
+ return to.ss_family;
+}
+
+void
+set_sock_tos(int fd, int tos)
+{
+#ifndef IP_TOS_IS_BROKEN
+ int af;
+
+ switch ((af = get_sock_af(fd))) {
+ case -1:
+ /* assume not a socket */
+ break;
+ case AF_INET:
+# ifdef IP_TOS
+ debug3_f("set socket %d IP_TOS 0x%02x", fd, tos);
+ if (setsockopt(fd, IPPROTO_IP, IP_TOS,
+ &tos, sizeof(tos)) == -1) {
+ error("setsockopt socket %d IP_TOS %d: %s:",
+ fd, tos, strerror(errno));
+ }
+# endif /* IP_TOS */
+ break;
+ case AF_INET6:
+# ifdef IPV6_TCLASS
+ debug3_f("set socket %d IPV6_TCLASS 0x%02x", fd, tos);
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS,
+ &tos, sizeof(tos)) == -1) {
+ error("setsockopt socket %d IPV6_TCLASS %d: %.100s:",
+ fd, tos, strerror(errno));
+ }
+# endif /* IPV6_TCLASS */
+ break;
+ default:
+ debug2_f("unsupported socket family %d", af);
+ break;
+ }
+#endif /* IP_TOS_IS_BROKEN */
+}
+
/*
* Wait up to *timeoutp milliseconds for events on fd. Updates
* *timeoutp with time remaining.
@@ -243,10 +297,10 @@ waitfd(int fd, int *timeoutp, short events)
struct timeval t_start;
int oerrno, r;
- monotime_tv(&t_start);
pfd.fd = fd;
pfd.events = events;
for (; *timeoutp >= 0;) {
+ monotime_tv(&t_start);
r = poll(&pfd, 1, *timeoutp);
oerrno = errno;
ms_subtract_diff(&t_start, timeoutp);
@@ -489,7 +543,7 @@ a2tun(const char *s, int *remote)
*
* Return -1 if time string is invalid.
*/
-long
+int
convtime(const char *s)
{
long total, secs, multiplier;
@@ -506,7 +560,7 @@ convtime(const char *s)
while (*p) {
secs = strtol(p, &endp, 10);
if (p == endp ||
- (errno == ERANGE && (secs == LONG_MIN || secs == LONG_MAX)) ||
+ (errno == ERANGE && (secs == INT_MIN || secs == INT_MAX)) ||
secs < 0)
return -1;
@@ -537,10 +591,10 @@ convtime(const char *s)
default:
return -1;
}
- if (secs >= LONG_MAX / multiplier)
+ if (secs > INT_MAX / multiplier)
return -1;
secs *= multiplier;
- if (total >= LONG_MAX - secs)
+ if (total > INT_MAX - secs)
return -1;
total += secs;
if (total < 0)
@@ -1111,9 +1165,9 @@ vdollar_percent_expand(int *parseerror, int dollar, int percent,
size_t len;
if ((buf = sshbuf_new()) == NULL)
- fatal("%s: sshbuf_new failed", __func__);
+ fatal_f("sshbuf_new failed");
if (parseerror == NULL)
- fatal("%s: null parseerror arg", __func__);
+ fatal_f("null parseerror arg");
*parseerror = 1;
/* Gather keys if we're doing percent expansion. */
@@ -1123,14 +1177,15 @@ vdollar_percent_expand(int *parseerror, int dollar, int percent,
if (keys[num_keys].key == NULL)
break;
keys[num_keys].repl = va_arg(ap, char *);
- if (keys[num_keys].repl == NULL)
- fatal("%s: NULL replacement for token %s", __func__, keys[num_keys].key);
+ if (keys[num_keys].repl == NULL) {
+ fatal_f("NULL replacement for token %s",
+ keys[num_keys].key);
+ }
}
if (num_keys == EXPAND_MAX_KEYS && va_arg(ap, char *) != NULL)
- fatal("%s: too many keys", __func__);
+ fatal_f("too many keys");
if (num_keys == 0)
- fatal("%s: percent expansion without token list",
- __func__);
+ fatal_f("percent expansion without token list");
}
/* Expand string */
@@ -1139,28 +1194,24 @@ vdollar_percent_expand(int *parseerror, int dollar, int percent,
if (dollar && string[0] == '$' && string[1] == '{') {
string += 2; /* skip over '${' */
if ((varend = strchr(string, '}')) == NULL) {
- error("%s: environment variable '%s' missing "
- "closing '}'", __func__, string);
+ error_f("environment variable '%s' missing "
+ "closing '}'", string);
goto out;
}
len = varend - string;
if (len == 0) {
- error("%s: zero-length environment variable",
- __func__);
+ error_f("zero-length environment variable");
goto out;
}
var = xmalloc(len + 1);
(void)strlcpy(var, string, len + 1);
if ((val = getenv(var)) == NULL) {
- error("%s: env var ${%s} has no value",
- __func__, var);
+ error_f("env var ${%s} has no value", var);
missingvar = 1;
} else {
- debug3("%s: expand ${%s} -> '%s'", __func__,
- var, val);
+ debug3_f("expand ${%s} -> '%s'", var, val);
if ((r = sshbuf_put(buf, val, strlen(val))) !=0)
- fatal("%s: sshbuf_put: %s", __func__,
- ssh_err(r));
+ fatal_fr(r, "sshbuf_put ${}");
}
free(var);
string += len;
@@ -1174,10 +1225,8 @@ vdollar_percent_expand(int *parseerror, int dollar, int percent,
*/
if (*string != '%' || !percent) {
append:
- if ((r = sshbuf_put_u8(buf, *string)) != 0) {
- fatal("%s: sshbuf_put_u8: %s",
- __func__, ssh_err(r));
- }
+ if ((r = sshbuf_put_u8(buf, *string)) != 0)
+ fatal_fr(r, "sshbuf_put_u8 %%");
continue;
}
string++;
@@ -1185,26 +1234,24 @@ vdollar_percent_expand(int *parseerror, int dollar, int percent,
if (*string == '%')
goto append;
if (*string == '\0') {
- error("%s: invalid format", __func__);
+ error_f("invalid format");
goto out;
}
for (i = 0; i < num_keys; i++) {
if (strchr(keys[i].key, *string) != NULL) {
if ((r = sshbuf_put(buf, keys[i].repl,
- strlen(keys[i].repl))) != 0) {
- fatal("%s: sshbuf_put: %s",
- __func__, ssh_err(r));
- }
+ strlen(keys[i].repl))) != 0)
+ fatal_fr(r, "sshbuf_put %%-repl");
break;
}
}
if (i >= num_keys) {
- error("%s: unknown key %%%c", __func__, *string);
+ error_f("unknown key %%%c", *string);
goto out;
}
}
if (!missingvar && (ret = sshbuf_dup_string(buf)) == NULL)
- fatal("%s: sshbuf_dup_string failed", __func__);
+ fatal_f("sshbuf_dup_string failed");
*parseerror = 0;
out:
sshbuf_free(buf);
@@ -1248,7 +1295,7 @@ percent_expand(const char *string, ...)
ret = vdollar_percent_expand(&err, 0, 1, string, ap);
va_end(ap);
if (err)
- fatal("%s failed", __func__);
+ fatal_f("failed");
return ret;
}
@@ -1267,7 +1314,7 @@ percent_dollar_expand(const char *string, ...)
ret = vdollar_percent_expand(&err, 1, 1, string, ap);
va_end(ap);
if (err)
- fatal("%s failed", __func__);
+ fatal_f("failed");
return ret;
}
@@ -1300,16 +1347,16 @@ tun_open(int tun, int mode, char **ifname)
break;
}
} else {
- debug("%s: invalid tunnel %u", __func__, tun);
+ debug_f("invalid tunnel %u", tun);
return -1;
}
if (fd == -1) {
- debug("%s: %s open: %s", __func__, name, strerror(errno));
+ debug_f("%s open: %s", name, strerror(errno));
return -1;
}
- debug("%s: %s mode %d fd %d", __func__, name, mode, fd);
+ debug_f("%s mode %d fd %d", name, mode, fd);
/* Bring interface up if it is not already */
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s%d", tunbase, tun);
@@ -1317,16 +1364,16 @@ tun_open(int tun, int mode, char **ifname)
goto failed;
if (ioctl(sock, SIOCGIFFLAGS, &ifr) == -1) {
- debug("%s: get interface %s flags: %s", __func__,
- ifr.ifr_name, strerror(errno));
+ debug_f("get interface %s flags: %s", ifr.ifr_name,
+ strerror(errno));
goto failed;
}
if (!(ifr.ifr_flags & IFF_UP)) {
ifr.ifr_flags |= IFF_UP;
if (ioctl(sock, SIOCSIFFLAGS, &ifr) == -1) {
- debug("%s: activate interface %s: %s", __func__,
- ifr.ifr_name, strerror(errno));
+ debug_f("activate interface %s: %s", ifr.ifr_name,
+ strerror(errno));
goto failed;
}
}
@@ -1677,7 +1724,7 @@ mktemp_proto(char *s, size_t len)
}
r = snprintf(s, len, "/tmp/ssh-XXXXXXXXXXXX");
if (r < 0 || (size_t)r >= len)
- fatal("%s: template string too short", __func__);
+ fatal_f("template string too short");
}
static const struct {
@@ -1764,8 +1811,7 @@ unix_listener(const char *path, int backlog, int unlink_first)
sunaddr.sun_family = AF_UNIX;
if (strlcpy(sunaddr.sun_path, path,
sizeof(sunaddr.sun_path)) >= sizeof(sunaddr.sun_path)) {
- error("%s: path \"%s\" too long for Unix domain socket",
- __func__, path);
+ error_f("path \"%s\" too long for Unix domain socket", path);
errno = ENAMETOOLONG;
return -1;
}
@@ -1773,7 +1819,7 @@ unix_listener(const char *path, int backlog, int unlink_first)
sock = socket(PF_UNIX, SOCK_STREAM, 0);
if (sock == -1) {
saved_errno = errno;
- error("%s: socket: %.100s", __func__, strerror(errno));
+ error_f("socket: %.100s", strerror(errno));
errno = saved_errno;
return -1;
}
@@ -1783,16 +1829,14 @@ unix_listener(const char *path, int backlog, int unlink_first)
}
if (bind(sock, (struct sockaddr *)&sunaddr, sizeof(sunaddr)) == -1) {
saved_errno = errno;
- error("%s: cannot bind to path %s: %s",
- __func__, path, strerror(errno));
+ error_f("cannot bind to path %s: %s", path, strerror(errno));
close(sock);
errno = saved_errno;
return -1;
}
if (listen(sock, backlog) == -1) {
saved_errno = errno;
- error("%s: cannot listen on path %s: %s",
- __func__, path, strerror(errno));
+ error_f("cannot listen on path %s: %s", path, strerror(errno));
close(sock);
unlink(path);
errno = saved_errno;
@@ -1956,7 +2000,7 @@ argv_assemble(int argc, char **argv)
struct sshbuf *buf, *arg;
if ((buf = sshbuf_new()) == NULL || (arg = sshbuf_new()) == NULL)
- fatal("%s: sshbuf_new failed", __func__);
+ fatal_f("sshbuf_new failed");
for (i = 0; i < argc; i++) {
ws = 0;
@@ -1981,17 +2025,16 @@ argv_assemble(int argc, char **argv)
break;
}
if (r != 0)
- fatal("%s: sshbuf_put_u8: %s",
- __func__, ssh_err(r));
+ fatal_fr(r, "sshbuf_put_u8");
}
if ((i != 0 && (r = sshbuf_put_u8(buf, ' ')) != 0) ||
(ws != 0 && (r = sshbuf_put_u8(buf, '"')) != 0) ||
(r = sshbuf_putb(buf, arg)) != 0 ||
(ws != 0 && (r = sshbuf_put_u8(buf, '"')) != 0))
- fatal("%s: buffer error: %s", __func__, ssh_err(r));
+ fatal_fr(r, "assemble");
}
if ((ret = malloc(sshbuf_len(buf) + 1)) == NULL)
- fatal("%s: malloc failed", __func__);
+ fatal_f("malloc failed");
memcpy(ret, sshbuf_ptr(buf), sshbuf_len(buf));
ret[sshbuf_len(buf)] = '\0';
sshbuf_free(buf);
@@ -2007,7 +2050,7 @@ exited_cleanly(pid_t pid, const char *tag, const char *cmd, int quiet)
while (waitpid(pid, &status, 0) == -1) {
if (errno != EINTR) {
- error("%s: waitpid: %s", tag, strerror(errno));
+ error("%s waitpid: %s", tag, strerror(errno));
return -1;
}
}
@@ -2396,6 +2439,32 @@ opt_match(const char **opts, const char *term)
return 0;
}
+void
+opt_array_append2(const char *file, const int line, const char *directive,
+ char ***array, int **iarray, u_int *lp, const char *s, int i)
+{
+
+ if (*lp >= INT_MAX)
+ fatal("%s line %d: Too many %s entries", file, line, directive);
+
+ if (iarray != NULL) {
+ *iarray = xrecallocarray(*iarray, *lp, *lp + 1,
+ sizeof(**iarray));
+ (*iarray)[*lp] = i;
+ }
+
+ *array = xrecallocarray(*array, *lp, *lp + 1, sizeof(**array));
+ (*array)[*lp] = xstrdup(s);
+ (*lp)++;
+}
+
+void
+opt_array_append(const char *file, const int line, const char *directive,
+ char ***array, u_int *lp, const char *s)
+{
+ opt_array_append2(file, line, directive, array, NULL, lp, s, 0);
+}
+
sshsig_t
ssh_signal(int signum, sshsig_t handler)
{
@@ -2415,3 +2484,193 @@ ssh_signal(int signum, sshsig_t handler)
}
return osa.sa_handler;
}
+
+int
+stdfd_devnull(int do_stdin, int do_stdout, int do_stderr)
+{
+ int devnull, ret = 0;
+
+ if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) {
+ error_f("open %s: %s", _PATH_DEVNULL,
+ strerror(errno));
+ return -1;
+ }
+ if ((do_stdin && dup2(devnull, STDIN_FILENO) == -1) ||
+ (do_stdout && dup2(devnull, STDOUT_FILENO) == -1) ||
+ (do_stderr && dup2(devnull, STDERR_FILENO) == -1)) {
+ error_f("dup2: %s", strerror(errno));
+ ret = -1;
+ }
+ if (devnull > STDERR_FILENO)
+ close(devnull);
+ return ret;
+}
+
+/*
+ * Runs command in a subprocess with a minimal environment.
+ * Returns pid on success, 0 on failure.
+ * The child stdout and stderr maybe captured, left attached or sent to
+ * /dev/null depending on the contents of flags.
+ * "tag" is prepended to log messages.
+ * NB. "command" is only used for logging; the actual command executed is
+ * av[0].
+ */
+pid_t
+subprocess(const char *tag, const char *command,
+ int ac, char **av, FILE **child, u_int flags,
+ struct passwd *pw, privdrop_fn *drop_privs, privrestore_fn *restore_privs)
+{
+ FILE *f = NULL;
+ struct stat st;
+ int fd, devnull, p[2], i;
+ pid_t pid;
+ char *cp, errmsg[512];
+ u_int nenv = 0;
+ char **env = NULL;
+
+ /* If dropping privs, then must specify user and restore function */
+ if (drop_privs != NULL && (pw == NULL || restore_privs == NULL)) {
+ error("%s: inconsistent arguments", tag); /* XXX fatal? */
+ return 0;
+ }
+ if (pw == NULL && (pw = getpwuid(getuid())) == NULL) {
+ error("%s: no user for current uid", tag);
+ return 0;
+ }
+ if (child != NULL)
+ *child = NULL;
+
+ debug3_f("%s command \"%s\" running as %s (flags 0x%x)",
+ tag, command, pw->pw_name, flags);
+
+ /* Check consistency */
+ if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0 &&
+ (flags & SSH_SUBPROCESS_STDOUT_CAPTURE) != 0) {
+ error_f("inconsistent flags");
+ return 0;
+ }
+ if (((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) == 0) != (child == NULL)) {
+ error_f("inconsistent flags/output");
+ return 0;
+ }
+
+ /*
+ * If executing an explicit binary, then verify the it exists
+ * and appears safe-ish to execute
+ */
+ if (!path_absolute(av[0])) {
+ error("%s path is not absolute", tag);
+ return 0;
+ }
+ if (drop_privs != NULL)
+ drop_privs(pw);
+ if (stat(av[0], &st) == -1) {
+ error("Could not stat %s \"%s\": %s", tag,
+ av[0], strerror(errno));
+ goto restore_return;
+ }
+ if ((flags & SSH_SUBPROCESS_UNSAFE_PATH) == 0 &&
+ safe_path(av[0], &st, NULL, 0, errmsg, sizeof(errmsg)) != 0) {
+ error("Unsafe %s \"%s\": %s", tag, av[0], errmsg);
+ goto restore_return;
+ }
+ /* Prepare to keep the child's stdout if requested */
+ if (pipe(p) == -1) {
+ error("%s: pipe: %s", tag, strerror(errno));
+ restore_return:
+ if (restore_privs != NULL)
+ restore_privs();
+ return 0;
+ }
+ if (restore_privs != NULL)
+ restore_privs();
+
+ switch ((pid = fork())) {
+ case -1: /* error */
+ error("%s: fork: %s", tag, strerror(errno));
+ close(p[0]);
+ close(p[1]);
+ return 0;
+ case 0: /* child */
+ /* Prepare a minimal environment for the child. */
+ if ((flags & SSH_SUBPROCESS_PRESERVE_ENV) == 0) {
+ nenv = 5;
+ env = xcalloc(sizeof(*env), nenv);
+ child_set_env(&env, &nenv, "PATH", _PATH_STDPATH);
+ child_set_env(&env, &nenv, "USER", pw->pw_name);
+ child_set_env(&env, &nenv, "LOGNAME", pw->pw_name);
+ child_set_env(&env, &nenv, "HOME", pw->pw_dir);
+ if ((cp = getenv("LANG")) != NULL)
+ child_set_env(&env, &nenv, "LANG", cp);
+ }
+
+ for (i = 1; i < NSIG; i++)
+ ssh_signal(i, SIG_DFL);
+
+ if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) {
+ error("%s: open %s: %s", tag, _PATH_DEVNULL,
+ strerror(errno));
+ _exit(1);
+ }
+ if (dup2(devnull, STDIN_FILENO) == -1) {
+ error("%s: dup2: %s", tag, strerror(errno));
+ _exit(1);
+ }
+
+ /* Set up stdout as requested; leave stderr in place for now. */
+ fd = -1;
+ if ((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) != 0)
+ fd = p[1];
+ else if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0)
+ fd = devnull;
+ if (fd != -1 && dup2(fd, STDOUT_FILENO) == -1) {
+ error("%s: dup2: %s", tag, strerror(errno));
+ _exit(1);
+ }
+ closefrom(STDERR_FILENO + 1);
+
+ if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1) {
+ error("%s: setresgid %u: %s", tag, (u_int)pw->pw_gid,
+ strerror(errno));
+ _exit(1);
+ }
+ if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1) {
+ error("%s: setresuid %u: %s", tag, (u_int)pw->pw_uid,
+ strerror(errno));
+ _exit(1);
+ }
+ /* stdin is pointed to /dev/null at this point */
+ if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0 &&
+ dup2(STDIN_FILENO, STDERR_FILENO) == -1) {
+ error("%s: dup2: %s", tag, strerror(errno));
+ _exit(1);
+ }
+ if (env != NULL)
+ execve(av[0], av, env);
+ else
+ execv(av[0], av);
+ error("%s %s \"%s\": %s", tag, env == NULL ? "execv" : "execve",
+ command, strerror(errno));
+ _exit(127);
+ default: /* parent */
+ break;
+ }
+
+ close(p[1]);
+ if ((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) == 0)
+ close(p[0]);
+ else if ((f = fdopen(p[0], "r")) == NULL) {
+ error("%s: fdopen: %s", tag, strerror(errno));
+ close(p[0]);
+ /* Don't leave zombie child */
+ kill(pid, SIGTERM);
+ while (waitpid(pid, NULL, 0) == -1 && errno == EINTR)
+ ;
+ return 0;
+ }
+ /* Success */
+ debug3_f("%s pid %ld", tag, (long)pid);
+ if (child != NULL)
+ *child = f;
+ return pid;
+}