aboutsummaryrefslogtreecommitdiff
path: root/misc.c
diff options
context:
space:
mode:
Diffstat (limited to 'misc.c')
-rw-r--r--misc.c696
1 files changed, 491 insertions, 205 deletions
diff --git a/misc.c b/misc.c
index 05950a471246..874dcc8a2344 100644
--- a/misc.c
+++ b/misc.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: misc.c,v 1.113 2017/08/18 05:48:04 djm Exp $ */
+/* $OpenBSD: misc.c,v 1.127 2018/03/12 00:52:01 djm Exp $ */
/*
* Copyright (c) 2000 Markus Friedl. All rights reserved.
* Copyright (c) 2005,2006 Damien Miller. All rights reserved.
@@ -167,6 +167,73 @@ set_nodelay(int fd)
error("setsockopt TCP_NODELAY: %.100s", strerror(errno));
}
+/* Allow local port reuse in TIME_WAIT */
+int
+set_reuseaddr(int fd)
+{
+ int on = 1;
+
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
+ error("setsockopt SO_REUSEADDR fd %d: %s", fd, strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+/* Get/set routing domain */
+char *
+get_rdomain(int fd)
+{
+#if defined(HAVE_SYS_GET_RDOMAIN)
+ return sys_get_rdomain(fd);
+#elif defined(__OpenBSD__)
+ int rtable;
+ char *ret;
+ socklen_t len = sizeof(rtable);
+
+ if (getsockopt(fd, SOL_SOCKET, SO_RTABLE, &rtable, &len) == -1) {
+ error("Failed to get routing domain for fd %d: %s",
+ fd, strerror(errno));
+ return NULL;
+ }
+ xasprintf(&ret, "%d", rtable);
+ return ret;
+#else /* defined(__OpenBSD__) */
+ return NULL;
+#endif
+}
+
+int
+set_rdomain(int fd, const char *name)
+{
+#if defined(HAVE_SYS_SET_RDOMAIN)
+ return sys_set_rdomain(fd, name);
+#elif defined(__OpenBSD__)
+ int rtable;
+ const char *errstr;
+
+ if (name == NULL)
+ return 0; /* default table */
+
+ rtable = (int)strtonum(name, 0, 255, &errstr);
+ if (errstr != NULL) {
+ /* Shouldn't happen */
+ error("Invalid routing domain \"%s\": %s", name, errstr);
+ return -1;
+ }
+ if (setsockopt(fd, SOL_SOCKET, SO_RTABLE,
+ &rtable, sizeof(rtable)) == -1) {
+ error("Failed to set routing domain %d on fd %d: %s",
+ rtable, fd, strerror(errno));
+ return -1;
+ }
+ return 0;
+#else /* defined(__OpenBSD__) */
+ error("Setting routing domain is not supported on this platform");
+ return -1;
+#endif
+}
+
/* Characters considered whitespace in strsep calls. */
#define WHITESPACE " \t\r\n"
#define QUOTE "\""
@@ -395,11 +462,12 @@ put_host_port(const char *host, u_short port)
* Search for next delimiter between hostnames/addresses and ports.
* Argument may be modified (for termination).
* Returns *cp if parsing succeeds.
- * *cp is set to the start of the next delimiter, if one was found.
+ * *cp is set to the start of the next field, if one was found.
+ * The delimiter char, if present, is stored in delim.
* If this is the last field, *cp is set to NULL.
*/
-char *
-hpdelim(char **cp)
+static char *
+hpdelim2(char **cp, char *delim)
{
char *s, *old;
@@ -422,6 +490,8 @@ hpdelim(char **cp)
case ':':
case '/':
+ if (delim != NULL)
+ *delim = *s;
*s = '\0'; /* terminate */
*cp = s + 1;
break;
@@ -434,6 +504,12 @@ hpdelim(char **cp)
}
char *
+hpdelim(char **cp)
+{
+ return hpdelim2(cp, NULL);
+}
+
+char *
cleanhostname(char *host)
{
if (*host == '[' && host[strlen(host) - 1] == ']') {
@@ -467,6 +543,75 @@ colon(char *cp)
}
/*
+ * Parse a [user@]host:[path] string.
+ * Caller must free returned user, host and path.
+ * Any of the pointer return arguments may be NULL (useful for syntax checking).
+ * If user was not specified then *userp will be set to NULL.
+ * If host was not specified then *hostp will be set to NULL.
+ * If path was not specified then *pathp will be set to ".".
+ * Returns 0 on success, -1 on failure.
+ */
+int
+parse_user_host_path(const char *s, char **userp, char **hostp, char **pathp)
+{
+ char *user = NULL, *host = NULL, *path = NULL;
+ char *sdup, *tmp;
+ int ret = -1;
+
+ if (userp != NULL)
+ *userp = NULL;
+ if (hostp != NULL)
+ *hostp = NULL;
+ if (pathp != NULL)
+ *pathp = NULL;
+
+ sdup = xstrdup(s);
+
+ /* Check for remote syntax: [user@]host:[path] */
+ if ((tmp = colon(sdup)) == NULL)
+ goto out;
+
+ /* Extract optional path */
+ *tmp++ = '\0';
+ if (*tmp == '\0')
+ tmp = ".";
+ path = xstrdup(tmp);
+
+ /* Extract optional user and mandatory host */
+ tmp = strrchr(sdup, '@');
+ if (tmp != NULL) {
+ *tmp++ = '\0';
+ host = xstrdup(cleanhostname(tmp));
+ if (*sdup != '\0')
+ user = xstrdup(sdup);
+ } else {
+ host = xstrdup(cleanhostname(sdup));
+ user = NULL;
+ }
+
+ /* Success */
+ if (userp != NULL) {
+ *userp = user;
+ user = NULL;
+ }
+ if (hostp != NULL) {
+ *hostp = host;
+ host = NULL;
+ }
+ if (pathp != NULL) {
+ *pathp = path;
+ path = NULL;
+ }
+ ret = 0;
+out:
+ free(sdup);
+ free(user);
+ free(host);
+ free(path);
+ return ret;
+}
+
+/*
* Parse a [user@]host[:port] string.
* Caller must free returned user and host.
* Any of the pointer return arguments may be NULL (useful for syntax checking).
@@ -491,7 +636,7 @@ parse_user_host_port(const char *s, char **userp, char **hostp, int *portp)
if ((sdup = tmp = strdup(s)) == NULL)
return -1;
/* Extract optional username */
- if ((cp = strchr(tmp, '@')) != NULL) {
+ if ((cp = strrchr(tmp, '@')) != NULL) {
*cp = '\0';
if (*tmp == '\0')
goto out;
@@ -527,6 +672,168 @@ parse_user_host_port(const char *s, char **userp, char **hostp, int *portp)
return ret;
}
+/*
+ * Converts a two-byte hex string to decimal.
+ * Returns the decimal value or -1 for invalid input.
+ */
+static int
+hexchar(const char *s)
+{
+ unsigned char result[2];
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ if (s[i] >= '0' && s[i] <= '9')
+ result[i] = (unsigned char)(s[i] - '0');
+ else if (s[i] >= 'a' && s[i] <= 'f')
+ result[i] = (unsigned char)(s[i] - 'a') + 10;
+ else if (s[i] >= 'A' && s[i] <= 'F')
+ result[i] = (unsigned char)(s[i] - 'A') + 10;
+ else
+ return -1;
+ }
+ return (result[0] << 4) | result[1];
+}
+
+/*
+ * Decode an url-encoded string.
+ * Returns a newly allocated string on success or NULL on failure.
+ */
+static char *
+urldecode(const char *src)
+{
+ char *ret, *dst;
+ int ch;
+
+ ret = xmalloc(strlen(src) + 1);
+ for (dst = ret; *src != '\0'; src++) {
+ switch (*src) {
+ case '+':
+ *dst++ = ' ';
+ break;
+ case '%':
+ if (!isxdigit((unsigned char)src[1]) ||
+ !isxdigit((unsigned char)src[2]) ||
+ (ch = hexchar(src + 1)) == -1) {
+ free(ret);
+ return NULL;
+ }
+ *dst++ = ch;
+ src += 2;
+ break;
+ default:
+ *dst++ = *src;
+ break;
+ }
+ }
+ *dst = '\0';
+
+ return ret;
+}
+
+/*
+ * Parse an (scp|ssh|sftp)://[user@]host[:port][/path] URI.
+ * See https://tools.ietf.org/html/draft-ietf-secsh-scp-sftp-ssh-uri-04
+ * Either user or path may be url-encoded (but not host or port).
+ * Caller must free returned user, host and path.
+ * Any of the pointer return arguments may be NULL (useful for syntax checking)
+ * but the scheme must always be specified.
+ * If user was not specified then *userp will be set to NULL.
+ * If port was not specified then *portp will be -1.
+ * If path was not specified then *pathp will be set to NULL.
+ * Returns 0 on success, 1 if non-uri/wrong scheme, -1 on error/invalid uri.
+ */
+int
+parse_uri(const char *scheme, const char *uri, char **userp, char **hostp,
+ int *portp, char **pathp)
+{
+ char *uridup, *cp, *tmp, ch;
+ char *user = NULL, *host = NULL, *path = NULL;
+ int port = -1, ret = -1;
+ size_t len;
+
+ len = strlen(scheme);
+ if (strncmp(uri, scheme, len) != 0 || strncmp(uri + len, "://", 3) != 0)
+ return 1;
+ uri += len + 3;
+
+ if (userp != NULL)
+ *userp = NULL;
+ if (hostp != NULL)
+ *hostp = NULL;
+ if (portp != NULL)
+ *portp = -1;
+ if (pathp != NULL)
+ *pathp = NULL;
+
+ uridup = tmp = xstrdup(uri);
+
+ /* Extract optional ssh-info (username + connection params) */
+ if ((cp = strchr(tmp, '@')) != NULL) {
+ char *delim;
+
+ *cp = '\0';
+ /* Extract username and connection params */
+ if ((delim = strchr(tmp, ';')) != NULL) {
+ /* Just ignore connection params for now */
+ *delim = '\0';
+ }
+ if (*tmp == '\0') {
+ /* Empty username */
+ goto out;
+ }
+ if ((user = urldecode(tmp)) == NULL)
+ goto out;
+ tmp = cp + 1;
+ }
+
+ /* Extract mandatory hostname */
+ if ((cp = hpdelim2(&tmp, &ch)) == NULL || *cp == '\0')
+ goto out;
+ host = xstrdup(cleanhostname(cp));
+ if (!valid_domain(host, 0, NULL))
+ goto out;
+
+ if (tmp != NULL && *tmp != '\0') {
+ if (ch == ':') {
+ /* Convert and verify port. */
+ if ((cp = strchr(tmp, '/')) != NULL)
+ *cp = '\0';
+ if ((port = a2port(tmp)) <= 0)
+ goto out;
+ tmp = cp ? cp + 1 : NULL;
+ }
+ if (tmp != NULL && *tmp != '\0') {
+ /* Extract optional path */
+ if ((path = urldecode(tmp)) == NULL)
+ goto out;
+ }
+ }
+
+ /* Success */
+ if (userp != NULL) {
+ *userp = user;
+ user = NULL;
+ }
+ if (hostp != NULL) {
+ *hostp = host;
+ host = NULL;
+ }
+ if (portp != NULL)
+ *portp = port;
+ if (pathp != NULL) {
+ *pathp = path;
+ path = NULL;
+ }
+ ret = 0;
+ out:
+ free(uridup);
+ free(user);
+ free(host);
+ free(path);
+ return ret;
+}
+
/* function to assist building execv() arguments */
void
addargs(arglist *args, char *fmt, ...)
@@ -724,16 +1031,19 @@ read_keyfile_line(FILE *f, const char *filename, char *buf, size_t bufsz,
}
int
-tun_open(int tun, int mode)
+tun_open(int tun, int mode, char **ifname)
{
#if defined(CUSTOM_SYS_TUN_OPEN)
- return (sys_tun_open(tun, mode));
+ return (sys_tun_open(tun, mode, ifname));
#elif defined(SSH_TUN_OPENBSD)
struct ifreq ifr;
char name[100];
int fd = -1, sock;
const char *tunbase = "tun";
+ if (ifname != NULL)
+ *ifname = NULL;
+
if (mode == SSH_TUNMODE_ETHERNET)
tunbase = "tap";
@@ -780,6 +1090,9 @@ tun_open(int tun, int mode)
}
}
+ if (ifname != NULL)
+ *ifname = xstrdup(ifr.ifr_name);
+
close(sock);
return fd;
@@ -946,8 +1259,8 @@ ms_subtract_diff(struct timeval *start, int *ms)
{
struct timeval diff, finish;
- gettimeofday(&finish, NULL);
- timersub(&finish, start, &diff);
+ monotime_tv(&finish);
+ timersub(&finish, start, &diff);
*ms -= (diff.tv_sec * 1000) + (diff.tv_usec / 1000);
}
@@ -960,54 +1273,63 @@ ms_to_timeval(struct timeval *tv, int ms)
tv->tv_usec = (ms % 1000) * 1000;
}
-time_t
-monotime(void)
+void
+monotime_ts(struct timespec *ts)
{
-#if defined(HAVE_CLOCK_GETTIME) && \
- (defined(CLOCK_MONOTONIC) || defined(CLOCK_BOOTTIME))
- struct timespec ts;
+ struct timeval tv;
+#if defined(HAVE_CLOCK_GETTIME) && (defined(CLOCK_BOOTTIME) || \
+ defined(CLOCK_MONOTONIC) || defined(CLOCK_REALTIME))
static int gettime_failed = 0;
if (!gettime_failed) {
-#if defined(CLOCK_BOOTTIME)
- if (clock_gettime(CLOCK_BOOTTIME, &ts) == 0)
- return (ts.tv_sec);
-#endif
-#if defined(CLOCK_MONOTONIC)
- if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0)
- return (ts.tv_sec);
-#endif
+# ifdef CLOCK_BOOTTIME
+ if (clock_gettime(CLOCK_BOOTTIME, ts) == 0)
+ return;
+# endif /* CLOCK_BOOTTIME */
+# ifdef CLOCK_MONOTONIC
+ if (clock_gettime(CLOCK_MONOTONIC, ts) == 0)
+ return;
+# endif /* CLOCK_MONOTONIC */
+# ifdef CLOCK_REALTIME
+ /* Not monotonic, but we're almost out of options here. */
+ if (clock_gettime(CLOCK_REALTIME, ts) == 0)
+ return;
+# endif /* CLOCK_REALTIME */
debug3("clock_gettime: %s", strerror(errno));
gettime_failed = 1;
}
-#endif /* HAVE_CLOCK_GETTIME && (CLOCK_MONOTONIC || CLOCK_BOOTTIME */
+#endif /* HAVE_CLOCK_GETTIME && (BOOTTIME || MONOTONIC || REALTIME) */
+ gettimeofday(&tv, NULL);
+ ts->tv_sec = tv.tv_sec;
+ ts->tv_nsec = (long)tv.tv_usec * 1000;
+}
- return time(NULL);
+void
+monotime_tv(struct timeval *tv)
+{
+ struct timespec ts;
+
+ monotime_ts(&ts);
+ tv->tv_sec = ts.tv_sec;
+ tv->tv_usec = ts.tv_nsec / 1000;
+}
+
+time_t
+monotime(void)
+{
+ struct timespec ts;
+
+ monotime_ts(&ts);
+ return ts.tv_sec;
}
double
monotime_double(void)
{
-#if defined(HAVE_CLOCK_GETTIME) && \
- (defined(CLOCK_MONOTONIC) || defined(CLOCK_BOOTTIME))
struct timespec ts;
- static int gettime_failed = 0;
- if (!gettime_failed) {
-#if defined(CLOCK_BOOTTIME)
- if (clock_gettime(CLOCK_BOOTTIME, &ts) == 0)
- return (ts.tv_sec + (double)ts.tv_nsec / 1000000000);
-#endif
-#if defined(CLOCK_MONOTONIC)
- if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0)
- return (ts.tv_sec + (double)ts.tv_nsec / 1000000000);
-#endif
- debug3("clock_gettime: %s", strerror(errno));
- gettime_failed = 1;
- }
-#endif /* HAVE_CLOCK_GETTIME && (CLOCK_MONOTONIC || CLOCK_BOOTTIME */
-
- return (double)time(NULL);
+ monotime_ts(&ts);
+ return ts.tv_sec + ((double)ts.tv_nsec / 1000000000);
}
void
@@ -1029,7 +1351,7 @@ bandwidth_limit(struct bwlimit *bw, size_t read_len)
struct timespec ts, rm;
if (!timerisset(&bw->bwstart)) {
- gettimeofday(&bw->bwstart, NULL);
+ monotime_tv(&bw->bwstart);
return;
}
@@ -1037,7 +1359,7 @@ bandwidth_limit(struct bwlimit *bw, size_t read_len)
if (bw->lamt < bw->thresh)
return;
- gettimeofday(&bw->bwend, NULL);
+ monotime_tv(&bw->bwend);
timersub(&bw->bwend, &bw->bwstart, &bw->bwend);
if (!timerisset(&bw->bwend))
return;
@@ -1071,7 +1393,7 @@ bandwidth_limit(struct bwlimit *bw, size_t read_len)
}
bw->lamt = 0;
- gettimeofday(&bw->bwstart, NULL);
+ monotime_tv(&bw->bwstart);
}
/* Make a template filename for mk[sd]temp() */
@@ -1172,9 +1494,10 @@ unix_listener(const char *path, int backlog, int unlink_first)
memset(&sunaddr, 0, sizeof(sunaddr));
sunaddr.sun_family = AF_UNIX;
- if (strlcpy(sunaddr.sun_path, path, sizeof(sunaddr.sun_path)) >= sizeof(sunaddr.sun_path)) {
- error("%s: \"%s\" too long for Unix domain socket", __func__,
- path);
+ 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);
errno = ENAMETOOLONG;
return -1;
}
@@ -1182,7 +1505,7 @@ unix_listener(const char *path, int backlog, int unlink_first)
sock = socket(PF_UNIX, SOCK_STREAM, 0);
if (sock < 0) {
saved_errno = errno;
- error("socket: %.100s", strerror(errno));
+ error("%s: socket: %.100s", __func__, strerror(errno));
errno = saved_errno;
return -1;
}
@@ -1192,18 +1515,18 @@ unix_listener(const char *path, int backlog, int unlink_first)
}
if (bind(sock, (struct sockaddr *)&sunaddr, sizeof(sunaddr)) < 0) {
saved_errno = errno;
- error("bind: %.100s", strerror(errno));
+ error("%s: cannot bind to path %s: %s",
+ __func__, path, strerror(errno));
close(sock);
- error("%s: cannot bind to path: %s", __func__, path);
errno = saved_errno;
return -1;
}
if (listen(sock, backlog) < 0) {
saved_errno = errno;
- error("listen: %.100s", strerror(errno));
+ error("%s: cannot listen on path %s: %s",
+ __func__, path, strerror(errno));
close(sock);
unlink(path);
- error("%s: cannot listen on path: %s", __func__, path);
errno = saved_errno;
return -1;
}
@@ -1417,158 +1740,6 @@ argv_assemble(int argc, char **argv)
return ret;
}
-/*
- * Runs command in a subprocess wuth 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, struct passwd *pw, const char *command,
- int ac, char **av, FILE **child, u_int flags)
-{
- FILE *f = NULL;
- struct stat st;
- int fd, devnull, p[2], i;
- pid_t pid;
- char *cp, errmsg[512];
- u_int envsize;
- char **child_env;
-
- if (child != NULL)
- *child = NULL;
-
- debug3("%s: %s command \"%s\" running as %s (flags 0x%x)", __func__,
- tag, command, pw->pw_name, flags);
-
- /* Check consistency */
- if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0 &&
- (flags & SSH_SUBPROCESS_STDOUT_CAPTURE) != 0) {
- error("%s: inconsistent flags", __func__);
- return 0;
- }
- if (((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) == 0) != (child == NULL)) {
- error("%s: inconsistent flags/output", __func__);
- return 0;
- }
-
- /*
- * If executing an explicit binary, then verify the it exists
- * and appears safe-ish to execute
- */
- if (*av[0] != '/') {
- error("%s path is not absolute", tag);
- return 0;
- }
- temporarily_use_uid(pw);
- if (stat(av[0], &st) < 0) {
- error("Could not stat %s \"%s\": %s", tag,
- av[0], strerror(errno));
- restore_uid();
- return 0;
- }
- if (safe_path(av[0], &st, NULL, 0, errmsg, sizeof(errmsg)) != 0) {
- error("Unsafe %s \"%s\": %s", tag, av[0], errmsg);
- restore_uid();
- return 0;
- }
- /* Prepare to keep the child's stdout if requested */
- if (pipe(p) != 0) {
- error("%s: pipe: %s", tag, strerror(errno));
- restore_uid();
- return 0;
- }
- restore_uid();
-
- 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. */
- envsize = 5;
- child_env = xcalloc(sizeof(*child_env), envsize);
- child_set_env(&child_env, &envsize, "PATH", _PATH_STDPATH);
- child_set_env(&child_env, &envsize, "USER", pw->pw_name);
- child_set_env(&child_env, &envsize, "LOGNAME", pw->pw_name);
- child_set_env(&child_env, &envsize, "HOME", pw->pw_dir);
- if ((cp = getenv("LANG")) != NULL)
- child_set_env(&child_env, &envsize, "LANG", cp);
-
- for (i = 0; i < NSIG; i++)
- 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);
-
- /* Don't use permanently_set_uid() here to avoid fatal() */
- if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0) {
- 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) != 0) {
- 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);
- }
-
- execve(av[0], av, child_env);
- error("%s exec \"%s\": %s", tag, 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("%s: %s pid %ld", __func__, tag, (long)pid);
- if (child != NULL)
- *child = f;
- return pid;
-}
-
/* Returns 0 if pid exited cleanly, non-zero otherwise */
int
exited_cleanly(pid_t pid, const char *tag, const char *cmd, int quiet)
@@ -1739,7 +1910,122 @@ child_set_env(char ***envp, u_int *envsizep, const char *name,
}
/* Allocate space and format the variable in the appropriate slot. */
+ /* XXX xasprintf */
env[i] = xmalloc(strlen(name) + 1 + strlen(value) + 1);
snprintf(env[i], strlen(name) + 1 + strlen(value) + 1, "%s=%s", name, value);
}
+/*
+ * Check and optionally lowercase a domain name, also removes trailing '.'
+ * Returns 1 on success and 0 on failure, storing an error message in errstr.
+ */
+int
+valid_domain(char *name, int makelower, const char **errstr)
+{
+ size_t i, l = strlen(name);
+ u_char c, last = '\0';
+ static char errbuf[256];
+
+ if (l == 0) {
+ strlcpy(errbuf, "empty domain name", sizeof(errbuf));
+ goto bad;
+ }
+ if (!isalpha((u_char)name[0]) && !isdigit((u_char)name[0])) {
+ snprintf(errbuf, sizeof(errbuf), "domain name \"%.100s\" "
+ "starts with invalid character", name);
+ goto bad;
+ }
+ for (i = 0; i < l; i++) {
+ c = tolower((u_char)name[i]);
+ if (makelower)
+ name[i] = (char)c;
+ if (last == '.' && c == '.') {
+ snprintf(errbuf, sizeof(errbuf), "domain name "
+ "\"%.100s\" contains consecutive separators", name);
+ goto bad;
+ }
+ if (c != '.' && c != '-' && !isalnum(c) &&
+ c != '_') /* technically invalid, but common */ {
+ snprintf(errbuf, sizeof(errbuf), "domain name "
+ "\"%.100s\" contains invalid characters", name);
+ goto bad;
+ }
+ last = c;
+ }
+ if (name[l - 1] == '.')
+ name[l - 1] = '\0';
+ if (errstr != NULL)
+ *errstr = NULL;
+ return 1;
+bad:
+ if (errstr != NULL)
+ *errstr = errbuf;
+ return 0;
+}
+
+const char *
+atoi_err(const char *nptr, int *val)
+{
+ const char *errstr = NULL;
+ long long num;
+
+ if (nptr == NULL || *nptr == '\0')
+ return "missing";
+ num = strtonum(nptr, 0, INT_MAX, &errstr);
+ if (errstr == NULL)
+ *val = (int)num;
+ return errstr;
+}
+
+int
+parse_absolute_time(const char *s, uint64_t *tp)
+{
+ struct tm tm;
+ time_t tt;
+ char buf[32], *fmt;
+
+ *tp = 0;
+
+ /*
+ * POSIX strptime says "The application shall ensure that there
+ * is white-space or other non-alphanumeric characters between
+ * any two conversion specifications" so arrange things this way.
+ */
+ switch (strlen(s)) {
+ case 8: /* YYYYMMDD */
+ fmt = "%Y-%m-%d";
+ snprintf(buf, sizeof(buf), "%.4s-%.2s-%.2s", s, s + 4, s + 6);
+ break;
+ case 12: /* YYYYMMDDHHMM */
+ fmt = "%Y-%m-%dT%H:%M";
+ snprintf(buf, sizeof(buf), "%.4s-%.2s-%.2sT%.2s:%.2s",
+ s, s + 4, s + 6, s + 8, s + 10);
+ break;
+ case 14: /* YYYYMMDDHHMMSS */
+ fmt = "%Y-%m-%dT%H:%M:%S";
+ snprintf(buf, sizeof(buf), "%.4s-%.2s-%.2sT%.2s:%.2s:%.2s",
+ s, s + 4, s + 6, s + 8, s + 10, s + 12);
+ break;
+ default:
+ return SSH_ERR_INVALID_FORMAT;
+ }
+
+ memset(&tm, 0, sizeof(tm));
+ if (strptime(buf, fmt, &tm) == NULL)
+ return SSH_ERR_INVALID_FORMAT;
+ if ((tt = mktime(&tm)) < 0)
+ return SSH_ERR_INVALID_FORMAT;
+ /* success */
+ *tp = (uint64_t)tt;
+ return 0;
+}
+
+void
+format_absolute_time(uint64_t t, char *buf, size_t len)
+{
+ time_t tt = t > INT_MAX ? INT_MAX : t; /* XXX revisit in 2038 :P */
+ struct tm tm;
+
+ localtime_r(&tt, &tm);
+ strftime(buf, len, "%Y-%m-%dT%H:%M:%S", &tm);
+}