diff options
author | Alistair Delva <adelva@google.com> | 2020-08-21 00:00:13 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2020-08-21 00:00:13 +0000 |
commit | ed358b3546c776c1c677fd88eb8f716cf6187510 (patch) | |
tree | 3c6134bcb2cda4b9dccc57b4a8b997a945aab62d /misc.c | |
parent | 22246b08952d746a7cc5a292570636cf4277598f (diff) | |
parent | 44a1065de8a58c51a021243a28bfa01e87822e4f (diff) |
Merge changes I934c73d4,I28cdc9a0,I9e734da9,I3c079d86
* changes:
UPSTREAM: depend
UPSTREAM: upstream: avoid possible NULL deref; from Pedro Martelletto
Revert "upstream: fix compilation with DEBUG_KEXDH; bz#3160 ok dtucker@"
Merge upstream-master into master
Diffstat (limited to 'misc.c')
-rw-r--r-- | misc.c | 1426 |
1 files changed, 1292 insertions, 134 deletions
@@ -1,4 +1,4 @@ -/* $OpenBSD: misc.c,v 1.96 2015/01/16 06:40:12 deraadt Exp $ */ +/* $OpenBSD: misc.c,v 1.147 2020/04/25 06:59:36 dtucker Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2005,2006 Damien Miller. All rights reserved. @@ -29,9 +29,19 @@ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/wait.h> #include <sys/un.h> #include <limits.h> +#ifdef HAVE_LIBGEN_H +# include <libgen.h> +#endif +#ifdef HAVE_POLL_H +#include <poll.h> +#endif +#include <signal.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> @@ -43,6 +53,7 @@ #include <netinet/in_systm.h> #include <netinet/ip.h> #include <netinet/tcp.h> +#include <arpa/inet.h> #include <ctype.h> #include <errno.h> @@ -60,6 +71,9 @@ #include "misc.h" #include "log.h" #include "ssh.h" +#include "sshbuf.h" +#include "ssherr.h" +#include "platform.h" /* remove newline at end of string */ char * @@ -83,9 +97,9 @@ set_nonblock(int fd) { int val; - val = fcntl(fd, F_GETFL, 0); - if (val < 0) { - error("fcntl(%d, F_GETFL, 0): %s", fd, strerror(errno)); + val = fcntl(fd, F_GETFL); + if (val == -1) { + error("fcntl(%d, F_GETFL): %s", fd, strerror(errno)); return (-1); } if (val & O_NONBLOCK) { @@ -107,9 +121,9 @@ unset_nonblock(int fd) { int val; - val = fcntl(fd, F_GETFL, 0); - if (val < 0) { - error("fcntl(%d, F_GETFL, 0): %s", fd, strerror(errno)); + val = fcntl(fd, F_GETFL); + if (val == -1) { + error("fcntl(%d, F_GETFL): %s", fd, strerror(errno)); return (-1); } if (!(val & O_NONBLOCK)) { @@ -156,13 +170,164 @@ 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 +} + +/* + * Wait up to *timeoutp milliseconds for events on fd. Updates + * *timeoutp with time remaining. + * Returns 0 if fd ready or -1 on timeout or error (see errno). + */ +static int +waitfd(int fd, int *timeoutp, short events) +{ + struct pollfd pfd; + struct timeval t_start; + int oerrno, r; + + monotime_tv(&t_start); + pfd.fd = fd; + pfd.events = events; + for (; *timeoutp >= 0;) { + r = poll(&pfd, 1, *timeoutp); + oerrno = errno; + ms_subtract_diff(&t_start, timeoutp); + errno = oerrno; + if (r > 0) + return 0; + else if (r == -1 && errno != EAGAIN) + return -1; + else if (r == 0) + break; + } + /* timeout */ + errno = ETIMEDOUT; + return -1; +} + +/* + * Wait up to *timeoutp milliseconds for fd to be readable. Updates + * *timeoutp with time remaining. + * Returns 0 if fd ready or -1 on timeout or error (see errno). + */ +int +waitrfd(int fd, int *timeoutp) { + return waitfd(fd, timeoutp, POLLIN); +} + +/* + * Attempt a non-blocking connect(2) to the specified address, waiting up to + * *timeoutp milliseconds for the connection to complete. If the timeout is + * <=0, then wait indefinitely. + * + * Returns 0 on success or -1 on failure. + */ +int +timeout_connect(int sockfd, const struct sockaddr *serv_addr, + socklen_t addrlen, int *timeoutp) +{ + int optval = 0; + socklen_t optlen = sizeof(optval); + + /* No timeout: just do a blocking connect() */ + if (timeoutp == NULL || *timeoutp <= 0) + return connect(sockfd, serv_addr, addrlen); + + set_nonblock(sockfd); + if (connect(sockfd, serv_addr, addrlen) == 0) { + /* Succeeded already? */ + unset_nonblock(sockfd); + return 0; + } else if (errno != EINPROGRESS) + return -1; + + if (waitfd(sockfd, timeoutp, POLLIN | POLLOUT) == -1) + return -1; + + /* Completed or failed */ + if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1) { + debug("getsockopt: %s", strerror(errno)); + return -1; + } + if (optval != 0) { + errno = optval; + return -1; + } + unset_nonblock(sockfd); + return 0; +} + /* Characters considered whitespace in strsep calls. */ #define WHITESPACE " \t\r\n" #define QUOTE "\"" /* return next token in configuration line */ -char * -strdelim(char **s) +static char * +strdelim_internal(char **s, int split_equals) { char *old; int wspace = 0; @@ -172,7 +337,8 @@ strdelim(char **s) old = *s; - *s = strpbrk(*s, WHITESPACE QUOTE "="); + *s = strpbrk(*s, + split_equals ? WHITESPACE QUOTE "=" : WHITESPACE QUOTE); if (*s == NULL) return (old); @@ -189,18 +355,37 @@ strdelim(char **s) } /* Allow only one '=' to be skipped */ - if (*s[0] == '=') + if (split_equals && *s[0] == '=') wspace = 1; *s[0] = '\0'; /* Skip any extra whitespace after first token */ *s += strspn(*s + 1, WHITESPACE) + 1; - if (*s[0] == '=' && !wspace) + if (split_equals && *s[0] == '=' && !wspace) *s += strspn(*s + 1, WHITESPACE) + 1; return (old); } +/* + * Return next token in configuration line; splts on whitespace or a + * single '=' character. + */ +char * +strdelim(char **s) +{ + return strdelim_internal(s, 1); +} + +/* + * Return next token in configuration line; splts on whitespace only. + */ +char * +strdelimw(char **s) +{ + return strdelim_internal(s, 0); +} + struct passwd * pwcopy(struct passwd *pw) { @@ -235,13 +420,16 @@ pwcopy(struct passwd *pw) int a2port(const char *s) { + struct servent *se; long long port; const char *errstr; port = strtonum(s, 0, 65535, &errstr); - if (errstr != NULL) - return -1; - return (int)port; + if (errstr == NULL) + return (int)port; + if ((se = getservbyname(s, "tcp")) != NULL) + return ntohs(se->s_port); + return -1; } int @@ -305,7 +493,7 @@ a2tun(const char *s, int *remote) long convtime(const char *s) { - long total, secs; + long total, secs, multiplier = 1; const char *p; char *endp; @@ -332,23 +520,28 @@ convtime(const char *s) break; case 'm': case 'M': - secs *= MINUTES; + multiplier = MINUTES; break; case 'h': case 'H': - secs *= HOURS; + multiplier = HOURS; break; case 'd': case 'D': - secs *= DAYS; + multiplier = DAYS; break; case 'w': case 'W': - secs *= WEEKS; + multiplier = WEEKS; break; default: return -1; } + if (secs >= LONG_MAX / multiplier) + return -1; + secs *= multiplier; + if (total >= LONG_MAX - secs) + return -1; total += secs; if (total < 0) return -1; @@ -369,7 +562,7 @@ put_host_port(const char *host, u_short port) if (port == 0 || port == SSH_DEFAULT_PORT) return(xstrdup(host)); - if (asprintf(&hoststr, "[%s]:%d", host, (int)port) < 0) + if (asprintf(&hoststr, "[%s]:%d", host, (int)port) == -1) fatal("put_host_port: asprintf: %s", strerror(errno)); debug3("put_host_port: %s", hoststr); return hoststr; @@ -379,11 +572,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) +hpdelim2(char **cp, char *delim) { char *s, *old; @@ -406,6 +600,8 @@ hpdelim(char **cp) case ':': case '/': + if (delim != NULL) + *delim = *s; *s = '\0'; /* terminate */ *cp = s + 1; break; @@ -418,6 +614,12 @@ hpdelim(char **cp) } char * +hpdelim(char **cp) +{ + return hpdelim2(cp, NULL); +} + +char * cleanhostname(char *host) { if (*host == '[' && host[strlen(host) - 1] == ']') { @@ -450,6 +652,298 @@ colon(char *cp) return NULL; } +/* + * 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). + * If user was not specified then *userp will be set to NULL. + * If port was not specified then *portp will be -1. + * Returns 0 on success, -1 on failure. + */ +int +parse_user_host_port(const char *s, char **userp, char **hostp, int *portp) +{ + char *sdup, *cp, *tmp; + char *user = NULL, *host = NULL; + int port = -1, ret = -1; + + if (userp != NULL) + *userp = NULL; + if (hostp != NULL) + *hostp = NULL; + if (portp != NULL) + *portp = -1; + + if ((sdup = tmp = strdup(s)) == NULL) + return -1; + /* Extract optional username */ + if ((cp = strrchr(tmp, '@')) != NULL) { + *cp = '\0'; + if (*tmp == '\0') + goto out; + if ((user = strdup(tmp)) == NULL) + goto out; + tmp = cp + 1; + } + /* Extract mandatory hostname */ + if ((cp = hpdelim(&tmp)) == NULL || *cp == '\0') + goto out; + host = xstrdup(cleanhostname(cp)); + /* Convert and verify optional port */ + if (tmp != NULL && *tmp != '\0') { + if ((port = a2port(tmp)) <= 0) + goto out; + } + /* Success */ + if (userp != NULL) { + *userp = user; + user = NULL; + } + if (hostp != NULL) { + *hostp = host; + host = NULL; + } + if (portp != NULL) + *portp = port; + ret = 0; + out: + free(sdup); + free(user); + free(host); + 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, ...) @@ -472,7 +966,7 @@ addargs(arglist *args, char *fmt, ...) } else if (args->num+2 >= nalloc) nalloc *= 2; - args->list = xrealloc(args->list, nalloc, sizeof(char *)); + args->list = xrecallocarray(args->list, args->nalloc, nalloc, sizeof(char *)); args->nalloc = nalloc; args->list[args->num++] = cp; args->list[args->num] = NULL; @@ -567,13 +1061,18 @@ char * percent_expand(const char *string, ...) { #define EXPAND_MAX_KEYS 16 - u_int num_keys, i, j; + u_int num_keys, i; struct { const char *key; const char *repl; } keys[EXPAND_MAX_KEYS]; - char buf[4096]; + struct sshbuf *buf; va_list ap; + int r; + char *ret; + + if ((buf = sshbuf_new()) == NULL) + fatal("%s: sshbuf_new failed", __func__); /* Gather keys */ va_start(ap, string); @@ -590,125 +1089,113 @@ percent_expand(const char *string, ...) va_end(ap); /* Expand string */ - *buf = '\0'; for (i = 0; *string != '\0'; string++) { if (*string != '%') { append: - buf[i++] = *string; - if (i >= sizeof(buf)) - fatal("%s: string too long", __func__); - buf[i] = '\0'; + if ((r = sshbuf_put_u8(buf, *string)) != 0) { + fatal("%s: sshbuf_put_u8: %s", + __func__, ssh_err(r)); + } continue; } string++; /* %% case */ if (*string == '%') goto append; - for (j = 0; j < num_keys; j++) { - if (strchr(keys[j].key, *string) != NULL) { - i = strlcat(buf, keys[j].repl, sizeof(buf)); - if (i >= sizeof(buf)) - fatal("%s: string too long", __func__); + if (*string == '\0') + fatal("%s: invalid format", __func__); + 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)); + } break; } } - if (j >= num_keys) + if (i >= num_keys) fatal("%s: unknown key %%%c", __func__, *string); } - return (xstrdup(buf)); + if ((ret = sshbuf_dup_string(buf)) == NULL) + fatal("%s: sshbuf_dup_string failed", __func__); + sshbuf_free(buf); + return ret; #undef EXPAND_MAX_KEYS } -/* - * Read an entire line from a public key file into a static buffer, discarding - * lines that exceed the buffer size. Returns 0 on success, -1 on failure. - */ -int -read_keyfile_line(FILE *f, const char *filename, char *buf, size_t bufsz, - u_long *lineno) -{ - while (fgets(buf, bufsz, f) != NULL) { - if (buf[0] == '\0') - continue; - (*lineno)++; - if (buf[strlen(buf) - 1] == '\n' || feof(f)) { - return 0; - } else { - debug("%s: %s line %lu exceeds size limit", __func__, - filename, *lineno); - /* discard remainder of line */ - while (fgetc(f) != '\n' && !feof(f)) - ; /* nothing */ - } - } - return -1; -} - 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"; /* Open the tunnel device */ if (tun <= SSH_TUNID_MAX) { - snprintf(name, sizeof(name), "/dev/tun%d", tun); + snprintf(name, sizeof(name), "/dev/%s%d", tunbase, tun); fd = open(name, O_RDWR); } else if (tun == SSH_TUNID_ANY) { for (tun = 100; tun >= 0; tun--) { - snprintf(name, sizeof(name), "/dev/tun%d", tun); + snprintf(name, sizeof(name), "/dev/%s%d", + tunbase, tun); if ((fd = open(name, O_RDWR)) >= 0) break; } } else { debug("%s: invalid tunnel %u", __func__, tun); - return (-1); + return -1; } - if (fd < 0) { - debug("%s: %s open failed: %s", __func__, name, strerror(errno)); - return (-1); + if (fd == -1) { + debug("%s: %s open: %s", __func__, name, strerror(errno)); + return -1; } debug("%s: %s mode %d fd %d", __func__, name, mode, fd); - /* Set the tunnel device operation mode */ - snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "tun%d", tun); + /* Bring interface up if it is not already */ + snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s%d", tunbase, tun); if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) goto failed; - if (ioctl(sock, SIOCGIFFLAGS, &ifr) == -1) + if (ioctl(sock, SIOCGIFFLAGS, &ifr) == -1) { + debug("%s: get interface %s flags: %s", __func__, + ifr.ifr_name, strerror(errno)); goto failed; + } - /* Set interface mode */ - ifr.ifr_flags &= ~IFF_UP; - if (mode == SSH_TUNMODE_ETHERNET) - ifr.ifr_flags |= IFF_LINK0; - else - ifr.ifr_flags &= ~IFF_LINK0; - if (ioctl(sock, SIOCSIFFLAGS, &ifr) == -1) - 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)); + goto failed; + } + } - /* Bring interface up */ - ifr.ifr_flags |= IFF_UP; - if (ioctl(sock, SIOCSIFFLAGS, &ifr) == -1) - goto failed; + if (ifname != NULL) + *ifname = xstrdup(ifr.ifr_name); close(sock); - return (fd); + return fd; failed: if (fd >= 0) close(fd); if (sock >= 0) close(sock); - debug("%s: failed to set %s mode %d: %s", __func__, name, - mode, strerror(errno)); - return (-1); + return -1; #else error("Tunnel interfaces are not supported on this platform"); return (-1); @@ -725,16 +1212,16 @@ sanitise_stdfd(void) strerror(errno)); exit(1); } - while (++dupfd <= 2) { - /* Only clobber closed fds */ - if (fcntl(dupfd, F_GETFL, 0) >= 0) - continue; - if (dup2(nullfd, dupfd) == -1) { - fprintf(stderr, "dup2: %s\n", strerror(errno)); - exit(1); + while (++dupfd <= STDERR_FILENO) { + /* Only populate closed fds. */ + if (fcntl(dupfd, F_GETFL) == -1 && errno == EBADF) { + if (dup2(nullfd, dupfd) == -1) { + fprintf(stderr, "dup2: %s\n", strerror(errno)); + exit(1); + } } } - if (nullfd > 2) + if (nullfd > STDERR_FILENO) close(nullfd); } @@ -757,6 +1244,33 @@ tohex(const void *vp, size_t l) return (r); } +/* + * Extend string *sp by the specified format. If *sp is not NULL (or empty), + * then the separator 'sep' will be prepended before the formatted arguments. + * Extended strings are heap allocated. + */ +void +xextendf(char **sp, const char *sep, const char *fmt, ...) +{ + va_list ap; + char *tmp1, *tmp2; + + va_start(ap, fmt); + xvasprintf(&tmp1, fmt, ap); + va_end(ap); + + if (*sp == NULL || **sp == '\0') { + free(*sp); + *sp = tmp1; + return; + } + xasprintf(&tmp2, "%s%s%s", *sp, sep == NULL ? "" : sep, tmp1); + free(tmp1); + free(*sp); + *sp = tmp2; +} + + u_int64_t get_u64(const void *vp) { @@ -866,8 +1380,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); } @@ -880,29 +1394,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) +{ + struct timespec ts; + + monotime_ts(&ts); + return ts.tv_sec + ((double)ts.tv_nsec / 1000000000); } void @@ -910,11 +1458,11 @@ bandwidth_limit_init(struct bwlimit *bw, u_int64_t kbps, size_t buflen) { bw->buflen = buflen; bw->rate = kbps; - bw->thresh = bw->rate; + bw->thresh = buflen; bw->lamt = 0; timerclear(&bw->bwstart); timerclear(&bw->bwend); -} +} /* Callback from read/write loop to insert bandwidth-limiting delays */ void @@ -923,16 +1471,15 @@ bandwidth_limit(struct bwlimit *bw, size_t read_len) u_int64_t waitlen; struct timespec ts, rm; + bw->lamt += read_len; if (!timerisset(&bw->bwstart)) { - gettimeofday(&bw->bwstart, NULL); + monotime_tv(&bw->bwstart); return; } - - bw->lamt += 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; @@ -966,7 +1513,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() */ @@ -990,6 +1537,7 @@ static const struct { const char *name; int value; } ipqos[] = { + { "none", INT_MAX }, /* can't use 0 here; that's CS0 */ { "af11", IPTOS_DSCP_AF11 }, { "af12", IPTOS_DSCP_AF12 }, { "af13", IPTOS_DSCP_AF13 }, @@ -1011,6 +1559,7 @@ static const struct { { "cs6", IPTOS_DSCP_CS6 }, { "cs7", IPTOS_DSCP_CS7 }, { "ef", IPTOS_DSCP_EF }, + { "le", IPTOS_DSCP_LE }, { "lowdelay", IPTOS_LOWDELAY }, { "throughput", IPTOS_THROUGHPUT }, { "reliability", IPTOS_RELIABILITY }, @@ -1066,17 +1615,18 @@ 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; } sock = socket(PF_UNIX, SOCK_STREAM, 0); - if (sock < 0) { + if (sock == -1) { saved_errno = errno; - error("socket: %.100s", strerror(errno)); + error("%s: socket: %.100s", __func__, strerror(errno)); errno = saved_errno; return -1; } @@ -1084,20 +1634,20 @@ unix_listener(const char *path, int backlog, int unlink_first) if (unlink(path) != 0 && errno != ENOENT) error("unlink(%s): %.100s", path, strerror(errno)); } - if (bind(sock, (struct sockaddr *)&sunaddr, sizeof(sunaddr)) < 0) { + if (bind(sock, (struct sockaddr *)&sunaddr, sizeof(sunaddr)) == -1) { 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) { + if (listen(sock, backlog) == -1) { 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; } @@ -1107,7 +1657,7 @@ unix_listener(const char *path, int backlog, int unlink_first) void sock_set_v6only(int s) { -#ifdef IPV6_V6ONLY +#if defined(IPV6_V6ONLY) && !defined(__OpenBSD__) int on = 1; debug3("%s: set socket %d IPV6_V6ONLY", __func__, s); @@ -1115,3 +1665,611 @@ sock_set_v6only(int s) error("setsockopt IPV6_V6ONLY: %s", strerror(errno)); #endif } + +/* + * Compares two strings that maybe be NULL. Returns non-zero if strings + * are both NULL or are identical, returns zero otherwise. + */ +static int +strcmp_maybe_null(const char *a, const char *b) +{ + if ((a == NULL && b != NULL) || (a != NULL && b == NULL)) + return 0; + if (a != NULL && strcmp(a, b) != 0) + return 0; + return 1; +} + +/* + * Compare two forwards, returning non-zero if they are identical or + * zero otherwise. + */ +int +forward_equals(const struct Forward *a, const struct Forward *b) +{ + if (strcmp_maybe_null(a->listen_host, b->listen_host) == 0) + return 0; + if (a->listen_port != b->listen_port) + return 0; + if (strcmp_maybe_null(a->listen_path, b->listen_path) == 0) + return 0; + if (strcmp_maybe_null(a->connect_host, b->connect_host) == 0) + return 0; + if (a->connect_port != b->connect_port) + return 0; + if (strcmp_maybe_null(a->connect_path, b->connect_path) == 0) + return 0; + /* allocated_port and handle are not checked */ + return 1; +} + +/* returns 1 if process is already daemonized, 0 otherwise */ +int +daemonized(void) +{ + int fd; + + if ((fd = open(_PATH_TTY, O_RDONLY | O_NOCTTY)) >= 0) { + close(fd); + return 0; /* have controlling terminal */ + } + if (getppid() != 1) + return 0; /* parent is not init */ + if (getsid(0) != getpid()) + return 0; /* not session leader */ + debug3("already daemonized"); + return 1; +} + + +/* + * Splits 's' into an argument vector. Handles quoted string and basic + * escape characters (\\, \", \'). Caller must free the argument vector + * and its members. + */ +int +argv_split(const char *s, int *argcp, char ***argvp) +{ + int r = SSH_ERR_INTERNAL_ERROR; + int argc = 0, quote, i, j; + char *arg, **argv = xcalloc(1, sizeof(*argv)); + + *argvp = NULL; + *argcp = 0; + + for (i = 0; s[i] != '\0'; i++) { + /* Skip leading whitespace */ + if (s[i] == ' ' || s[i] == '\t') + continue; + + /* Start of a token */ + quote = 0; + if (s[i] == '\\' && + (s[i + 1] == '\'' || s[i + 1] == '\"' || s[i + 1] == '\\')) + i++; + else if (s[i] == '\'' || s[i] == '"') + quote = s[i++]; + + argv = xreallocarray(argv, (argc + 2), sizeof(*argv)); + arg = argv[argc++] = xcalloc(1, strlen(s + i) + 1); + argv[argc] = NULL; + + /* Copy the token in, removing escapes */ + for (j = 0; s[i] != '\0'; i++) { + if (s[i] == '\\') { + if (s[i + 1] == '\'' || + s[i + 1] == '\"' || + s[i + 1] == '\\') { + i++; /* Skip '\' */ + arg[j++] = s[i]; + } else { + /* Unrecognised escape */ + arg[j++] = s[i]; + } + } else if (quote == 0 && (s[i] == ' ' || s[i] == '\t')) + break; /* done */ + else if (quote != 0 && s[i] == quote) + break; /* done */ + else + arg[j++] = s[i]; + } + if (s[i] == '\0') { + if (quote != 0) { + /* Ran out of string looking for close quote */ + r = SSH_ERR_INVALID_FORMAT; + goto out; + } + break; + } + } + /* Success */ + *argcp = argc; + *argvp = argv; + argc = 0; + argv = NULL; + r = 0; + out: + if (argc != 0 && argv != NULL) { + for (i = 0; i < argc; i++) + free(argv[i]); + free(argv); + } + return r; +} + +/* + * Reassemble an argument vector into a string, quoting and escaping as + * necessary. Caller must free returned string. + */ +char * +argv_assemble(int argc, char **argv) +{ + int i, j, ws, r; + char c, *ret; + struct sshbuf *buf, *arg; + + if ((buf = sshbuf_new()) == NULL || (arg = sshbuf_new()) == NULL) + fatal("%s: sshbuf_new failed", __func__); + + for (i = 0; i < argc; i++) { + ws = 0; + sshbuf_reset(arg); + for (j = 0; argv[i][j] != '\0'; j++) { + r = 0; + c = argv[i][j]; + switch (c) { + case ' ': + case '\t': + ws = 1; + r = sshbuf_put_u8(arg, c); + break; + case '\\': + case '\'': + case '"': + if ((r = sshbuf_put_u8(arg, '\\')) != 0) + break; + /* FALLTHROUGH */ + default: + r = sshbuf_put_u8(arg, c); + break; + } + if (r != 0) + fatal("%s: sshbuf_put_u8: %s", + __func__, ssh_err(r)); + } + 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)); + } + if ((ret = malloc(sshbuf_len(buf) + 1)) == NULL) + fatal("%s: malloc failed", __func__); + memcpy(ret, sshbuf_ptr(buf), sshbuf_len(buf)); + ret[sshbuf_len(buf)] = '\0'; + sshbuf_free(buf); + sshbuf_free(arg); + return ret; +} + +/* Returns 0 if pid exited cleanly, non-zero otherwise */ +int +exited_cleanly(pid_t pid, const char *tag, const char *cmd, int quiet) +{ + int status; + + while (waitpid(pid, &status, 0) == -1) { + if (errno != EINTR) { + error("%s: waitpid: %s", tag, strerror(errno)); + return -1; + } + } + if (WIFSIGNALED(status)) { + error("%s %s exited on signal %d", tag, cmd, WTERMSIG(status)); + return -1; + } else if (WEXITSTATUS(status) != 0) { + do_log2(quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_INFO, + "%s %s failed, status %d", tag, cmd, WEXITSTATUS(status)); + return -1; + } + return 0; +} + +/* + * Check a given path for security. This is defined as all components + * of the path to the file must be owned by either the owner of + * of the file or root and no directories must be group or world writable. + * + * XXX Should any specific check be done for sym links ? + * + * Takes a file name, its stat information (preferably from fstat() to + * avoid races), the uid of the expected owner, their home directory and an + * error buffer plus max size as arguments. + * + * Returns 0 on success and -1 on failure + */ +int +safe_path(const char *name, struct stat *stp, const char *pw_dir, + uid_t uid, char *err, size_t errlen) +{ + char buf[PATH_MAX], homedir[PATH_MAX]; + char *cp; + int comparehome = 0; +#if !defined(ANDROID) + struct stat st; +#endif + + if (realpath(name, buf) == NULL) { + snprintf(err, errlen, "realpath %s failed: %s", name, + strerror(errno)); + return -1; + } + if (pw_dir != NULL && realpath(pw_dir, homedir) != NULL) + comparehome = 1; + + if (!S_ISREG(stp->st_mode)) { + snprintf(err, errlen, "%s is not a regular file", buf); + return -1; + } + if ((!platform_sys_dir_uid(stp->st_uid) && stp->st_uid != uid) || + (stp->st_mode & 022) != 0) { + snprintf(err, errlen, "bad ownership or modes for file %s", + buf); + return -1; + } + + /* for each component of the canonical path, walking upwards */ + for (;;) { + if ((cp = dirname(buf)) == NULL) { + snprintf(err, errlen, "dirname() failed"); + return -1; + } + strlcpy(buf, cp, sizeof(buf)); + +#if !defined(ANDROID) + /* /data is owned by system user, which causes this check to fail */ + if (stat(buf, &st) == -1 || + (!platform_sys_dir_uid(st.st_uid) && st.st_uid != uid) || + (st.st_mode & 022) != 0) { + snprintf(err, errlen, + "bad ownership or modes for directory %s", buf); + return -1; + } +#endif + + /* If are past the homedir then we can stop */ + if (comparehome && strcmp(homedir, buf) == 0) + break; + + /* + * dirname should always complete with a "/" path, + * but we can be paranoid and check for "." too + */ + if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0)) + break; + } + return 0; +} + +/* + * Version of safe_path() that accepts an open file descriptor to + * avoid races. + * + * Returns 0 on success and -1 on failure + */ +int +safe_path_fd(int fd, const char *file, struct passwd *pw, + char *err, size_t errlen) +{ + struct stat st; + + /* check the open file to avoid races */ + if (fstat(fd, &st) == -1) { + snprintf(err, errlen, "cannot stat file %s: %s", + file, strerror(errno)); + return -1; + } + return safe_path(file, &st, pw->pw_dir, pw->pw_uid, err, errlen); +} + +/* + * Sets the value of the given variable in the environment. If the variable + * already exists, its value is overridden. + */ +void +child_set_env(char ***envp, u_int *envsizep, const char *name, + const char *value) +{ + char **env; + u_int envsize; + u_int i, namelen; + + if (strchr(name, '=') != NULL) { + error("Invalid environment variable \"%.100s\"", name); + return; + } + + /* + * If we're passed an uninitialized list, allocate a single null + * entry before continuing. + */ + if (*envp == NULL && *envsizep == 0) { + *envp = xmalloc(sizeof(char *)); + *envp[0] = NULL; + *envsizep = 1; + } + + /* + * Find the slot where the value should be stored. If the variable + * already exists, we reuse the slot; otherwise we append a new slot + * at the end of the array, expanding if necessary. + */ + env = *envp; + namelen = strlen(name); + for (i = 0; env[i]; i++) + if (strncmp(env[i], name, namelen) == 0 && env[i][namelen] == '=') + break; + if (env[i]) { + /* Reuse the slot. */ + free(env[i]); + } else { + /* New variable. Expand if necessary. */ + envsize = *envsizep; + if (i >= envsize - 1) { + if (envsize >= 1000) + fatal("child_set_env: too many env vars"); + envsize += 50; + env = (*envp) = xreallocarray(env, envsize, sizeof(char *)); + *envsizep = envsize; + } + /* Need to set the NULL pointer at end of array beyond the new slot. */ + env[i + 1] = NULL; + } + + /* 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; +} + +/* + * Verify that a environment variable name (not including initial '$') is + * valid; consisting of one or more alphanumeric or underscore characters only. + * Returns 1 on valid, 0 otherwise. + */ +int +valid_env_name(const char *name) +{ + const char *cp; + + if (name[0] == '\0') + return 0; + for (cp = name; *cp != '\0'; cp++) { + if (!isalnum((u_char)*cp) && *cp != '_') + return 0; + } + return 1; +} + +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); +} + +/* check if path is absolute */ +int +path_absolute(const char *path) +{ + return (*path == '/') ? 1 : 0; +} + +void +skip_space(char **cpp) +{ + char *cp; + + for (cp = *cpp; *cp == ' ' || *cp == '\t'; cp++) + ; + *cpp = cp; +} + +/* authorized_key-style options parsing helpers */ + +/* + * Match flag 'opt' in *optsp, and if allow_negate is set then also match + * 'no-opt'. Returns -1 if option not matched, 1 if option matches or 0 + * if negated option matches. + * If the option or negated option matches, then *optsp is updated to + * point to the first character after the option. + */ +int +opt_flag(const char *opt, int allow_negate, const char **optsp) +{ + size_t opt_len = strlen(opt); + const char *opts = *optsp; + int negate = 0; + + if (allow_negate && strncasecmp(opts, "no-", 3) == 0) { + opts += 3; + negate = 1; + } + if (strncasecmp(opts, opt, opt_len) == 0) { + *optsp = opts + opt_len; + return negate ? 0 : 1; + } + return -1; +} + +char * +opt_dequote(const char **sp, const char **errstrp) +{ + const char *s = *sp; + char *ret; + size_t i; + + *errstrp = NULL; + if (*s != '"') { + *errstrp = "missing start quote"; + return NULL; + } + s++; + if ((ret = malloc(strlen((s)) + 1)) == NULL) { + *errstrp = "memory allocation failed"; + return NULL; + } + for (i = 0; *s != '\0' && *s != '"';) { + if (s[0] == '\\' && s[1] == '"') + s++; + ret[i++] = *s++; + } + if (*s == '\0') { + *errstrp = "missing end quote"; + free(ret); + return NULL; + } + ret[i] = '\0'; + s++; + *sp = s; + return ret; +} + +int +opt_match(const char **opts, const char *term) +{ + if (strncasecmp((*opts), term, strlen(term)) == 0 && + (*opts)[strlen(term)] == '=') { + *opts += strlen(term) + 1; + return 1; + } + return 0; +} + +sshsig_t +ssh_signal(int signum, sshsig_t handler) +{ + struct sigaction sa, osa; + + /* mask all other signals while in handler */ + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handler; + sigfillset(&sa.sa_mask); +#if defined(SA_RESTART) && !defined(NO_SA_RESTART) + if (signum != SIGALRM) + sa.sa_flags = SA_RESTART; +#endif + if (sigaction(signum, &sa, &osa) == -1) { + debug3("sigaction(%s): %s", strsignal(signum), strerror(errno)); + return SIG_ERR; + } + return osa.sa_handler; +} |