Edit

IABSD.fr/src/usr.bin/ssh/misc.c

Branch :

  • Show log

    Commit

  • Author : tb
    Date : 2026-05-27 13:54:15
    Hash : 1708629b
    Message : ssh: use sentinel idiom for timegm(3) and mktime(3) There is nothing wrong with times before the epoch, even -1, so use the idiom recently added to the CAVEATS section to figure out whether there was an error in the timegm() or mktime() calls. We should sweep the tree for this. If anyone is bored, feel free to beat me to it... ok deraadt djm

  • usr.bin/ssh/misc.c
  • /* $OpenBSD: misc.c,v 1.214 2026/05/27 13:54:15 tb Exp $ */
    /*
     * Copyright (c) 2000 Markus Friedl.  All rights reserved.
     * Copyright (c) 2005-2020 Damien Miller.  All rights reserved.
     * Copyright (c) 2004 Henning Brauer <henning@openbsd.org>
     *
     * Permission to use, copy, modify, and distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */
    
    
    #include <sys/types.h>
    #include <sys/ioctl.h>
    #include <sys/mman.h>
    #include <sys/socket.h>
    #include <sys/stat.h>
    #include <sys/time.h>
    #include <sys/wait.h>
    #include <sys/un.h>
    
    #include <net/if.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <netinet/tcp.h>
    #include <arpa/inet.h>
    
    #include <ctype.h>
    #include <errno.h>
    #include <fcntl.h>
    #include <netdb.h>
    #include <paths.h>
    #include <pwd.h>
    #include <libgen.h>
    #include <limits.h>
    #include <nlist.h>
    #include <poll.h>
    #include <signal.h>
    #include <stdarg.h>
    #include <stdio.h>
    #include <stdint.h>
    #include <stdlib.h>
    #include <string.h>
    #include <time.h>
    #include <unistd.h>
    
    #include "xmalloc.h"
    #include "misc.h"
    #include "log.h"
    #include "ssh.h"
    #include "sshbuf.h"
    #include "ssherr.h"
    
    /* remove newline at end of string */
    char *
    chop(char *s)
    {
    	char *t = s;
    	while (*t) {
    		if (*t == '\n' || *t == '\r') {
    			*t = '\0';
    			return s;
    		}
    		t++;
    	}
    	return s;
    
    }
    
    /* remove whitespace from end of string */
    void
    rtrim(char *s)
    {
    	size_t i;
    
    	if ((i = strlen(s)) == 0)
    		return;
    	do {
    		i--;
    		if (isspace((unsigned char)s[i]))
    			s[i] = '\0';
    		else
    			break;
    	} while (i > 0);
    }
    
    /*
     * returns pointer to character after 'prefix' in 's' or otherwise NULL
     * if the prefix is not present.
     */
    const char *
    strprefix(const char *s, const char *prefix, int ignorecase)
    {
    	size_t prefixlen;
    
    	if ((prefixlen = strlen(prefix)) == 0)
    		return s;
    	if (ignorecase) {
    		if (strncasecmp(s, prefix, prefixlen) != 0)
    			return NULL;
    	} else {
    		if (strncmp(s, prefix, prefixlen) != 0)
    			return NULL;
    	}
    	return s + prefixlen;
    }
    
    /* Append string 's' to a NULL-terminated array of strings */
    void
    stringlist_append(char ***listp, const char *s)
    {
    	size_t i = 0;
    
    	if (*listp == NULL)
    		*listp = xcalloc(2, sizeof(**listp));
    	else {
    		for (i = 0; (*listp)[i] != NULL; i++)
    			; /* count */
    		*listp = xrecallocarray(*listp, i + 1, i + 2, sizeof(**listp));
    	}
    	(*listp)[i] = xstrdup(s);
    }
    
    void
    stringlist_free(char **list)
    {
    	size_t i = 0;
    
    	if (list == NULL)
    		return;
    	for (i = 0; list[i] != NULL; i++)
    		free(list[i]);
    	free(list);
    }
    
    /* set/unset filedescriptor to non-blocking */
    int
    set_nonblock(int fd)
    {
    	int val;
    
    	val = fcntl(fd, F_GETFL);
    	if (val == -1) {
    		error("fcntl(%d, F_GETFL): %s", fd, strerror(errno));
    		return (-1);
    	}
    	if (val & O_NONBLOCK) {
    		debug3("fd %d is O_NONBLOCK", fd);
    		return (0);
    	}
    	debug2("fd %d setting O_NONBLOCK", fd);
    	val |= O_NONBLOCK;
    	if (fcntl(fd, F_SETFL, val) == -1) {
    		debug("fcntl(%d, F_SETFL, O_NONBLOCK): %s", fd,
    		    strerror(errno));
    		return (-1);
    	}
    	return (0);
    }
    
    int
    unset_nonblock(int fd)
    {
    	int val;
    
    	val = fcntl(fd, F_GETFL);
    	if (val == -1) {
    		error("fcntl(%d, F_GETFL): %s", fd, strerror(errno));
    		return (-1);
    	}
    	if (!(val & O_NONBLOCK)) {
    		debug3("fd %d is not O_NONBLOCK", fd);
    		return (0);
    	}
    	debug("fd %d clearing O_NONBLOCK", fd);
    	val &= ~O_NONBLOCK;
    	if (fcntl(fd, F_SETFL, val) == -1) {
    		debug("fcntl(%d, F_SETFL, ~O_NONBLOCK): %s",
    		    fd, strerror(errno));
    		return (-1);
    	}
    	return (0);
    }
    
    const char *
    ssh_gai_strerror(int gaierr)
    {
    	if (gaierr == EAI_SYSTEM && errno != 0)
    		return strerror(errno);
    	return gai_strerror(gaierr);
    }
    
    /* disable nagle on socket */
    void
    set_nodelay(int fd)
    {
    	int opt;
    	socklen_t optlen;
    
    	optlen = sizeof opt;
    	if (getsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, &optlen) == -1) {
    		debug("getsockopt TCP_NODELAY: %.100s", strerror(errno));
    		return;
    	}
    	if (opt == 1) {
    		debug2("fd %d is TCP_NODELAY", fd);
    		return;
    	}
    	opt = 1;
    	debug2("fd %d setting TCP_NODELAY", fd);
    	if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof opt) == -1)
    		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)
    {
    	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;
    }
    
    int
    set_rdomain(int fd, const char *name)
    {
    	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;
    }
    
    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;
    	return to.ss_family;
    }
    
    void
    set_sock_tos(int fd, int tos)
    {
    	int af;
    
    	if (tos < 0 || tos == INT_MAX) {
    		debug_f("invalid TOS %d", tos);
    		return;
    	}
    	switch ((af = get_sock_af(fd))) {
    	case -1:
    		/* assume not a socket */
    		break;
    	case AF_INET:
    		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));
    		}
    		break;
    	case AF_INET6:
    		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: %s",
    			    fd, tos, strerror(errno));
    		}
    		break;
    	default:
    		debug2_f("unsupported socket family %d", af);
    		break;
    	}
    }
    
    /*
     * 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, volatile sig_atomic_t *stop)
    {
    	struct pollfd pfd;
    	struct timespec timeout;
    	int oerrno, r;
    	sigset_t nsigset, osigset;
    
    	if (timeoutp && *timeoutp == -1)
    		timeoutp = NULL;
    	pfd.fd = fd;
    	pfd.events = events;
    	ptimeout_init(&timeout);
    	if (timeoutp != NULL)
    		ptimeout_deadline_ms(&timeout, *timeoutp);
    	if (stop != NULL)
    		sigfillset(&nsigset);
    	for (; timeoutp == NULL || *timeoutp >= 0;) {
    		if (stop != NULL) {
    			sigprocmask(SIG_BLOCK, &nsigset, &osigset);
    			if (*stop) {
    				sigprocmask(SIG_SETMASK, &osigset, NULL);
    				errno = EINTR;
    				return -1;
    			}
    		}
    		r = ppoll(&pfd, 1, ptimeout_get_tsp(&timeout),
    		    stop != NULL ? &osigset : NULL);
    		oerrno = errno;
    		if (stop != NULL)
    			sigprocmask(SIG_SETMASK, &osigset, NULL);
    		if (timeoutp)
    			*timeoutp = ptimeout_get_ms(&timeout);
    		errno = oerrno;
    		if (r > 0)
    			return 0;
    		else if (r == -1 && errno != EAGAIN && errno != EINTR)
    			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, volatile sig_atomic_t *stop) {
    	return waitfd(fd, timeoutp, POLLIN, stop);
    }
    
    /*
     * 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);
    	for (;;) {
    		if (connect(sockfd, serv_addr, addrlen) == 0) {
    			/* Succeeded already? */
    			unset_nonblock(sockfd);
    			return 0;
    		} else if (errno == EINTR)
    			continue;
    		else if (errno != EINPROGRESS)
    			return -1;
    		break;
    	}
    
    	if (waitfd(sockfd, timeoutp, POLLIN | POLLOUT, NULL) == -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 */
    static char *
    strdelim_internal(char **s, int split_equals)
    {
    	char *old;
    	int wspace = 0;
    
    	if (*s == NULL)
    		return NULL;
    
    	old = *s;
    
    	*s = strpbrk(*s,
    	    split_equals ? WHITESPACE QUOTE "=" : WHITESPACE QUOTE);
    	if (*s == NULL)
    		return (old);
    
    	if (*s[0] == '\"') {
    		memmove(*s, *s + 1, strlen(*s)); /* move nul too */
    		/* Find matching quote */
    		if ((*s = strpbrk(*s, QUOTE)) == NULL) {
    			return (NULL);		/* no matching quote */
    		} else {
    			*s[0] = '\0';
    			*s += strspn(*s + 1, WHITESPACE) + 1;
    			return (old);
    		}
    	}
    
    	/* Allow only one '=' to be skipped */
    	if (split_equals && *s[0] == '=')
    		wspace = 1;
    	*s[0] = '\0';
    
    	/* Skip any extra whitespace after first token */
    	*s += strspn(*s + 1, WHITESPACE) + 1;
    	if (split_equals && *s[0] == '=' && !wspace)
    		*s += strspn(*s + 1, WHITESPACE) + 1;
    
    	return (old);
    }
    
    /*
     * Return next token in configuration line; splits on whitespace or a
     * single '=' character.
     */
    char *
    strdelim(char **s)
    {
    	return strdelim_internal(s, 1);
    }
    
    /*
     * Return next token in configuration line; splits on whitespace only.
     */
    char *
    strdelimw(char **s)
    {
    	return strdelim_internal(s, 0);
    }
    
    struct passwd *
    pwcopy(struct passwd *pw)
    {
    	struct passwd *copy = xcalloc(1, sizeof(*copy));
    
    	copy->pw_name = xstrdup(pw->pw_name);
    	copy->pw_passwd = xstrdup(pw->pw_passwd);
    	copy->pw_gecos = xstrdup(pw->pw_gecos);
    	copy->pw_uid = pw->pw_uid;
    	copy->pw_gid = pw->pw_gid;
    	copy->pw_expire = pw->pw_expire;
    	copy->pw_change = pw->pw_change;
    	copy->pw_class = xstrdup(pw->pw_class);
    	copy->pw_dir = xstrdup(pw->pw_dir);
    	copy->pw_shell = xstrdup(pw->pw_shell);
    	return copy;
    }
    
    void
    pwfree(struct passwd *pw)
    {
    	if (pw == NULL)
    		return;
    	free(pw->pw_name);
    	freezero(pw->pw_passwd,
    	    pw->pw_passwd == NULL ? 0 : strlen(pw->pw_passwd));
    	free(pw->pw_gecos);
    	free(pw->pw_class);
    	free(pw->pw_dir);
    	free(pw->pw_shell);
    	freezero(pw, sizeof(*pw));
    }
    
    /*
     * Convert ASCII string to TCP/IP port number.
     * Port must be >=0 and <=65535.
     * Return -1 if invalid.
     */
    int
    a2port(const char *s)
    {
    	struct servent *se;
    	long long port;
    	const char *errstr;
    
    	port = strtonum(s, 0, 65535, &errstr);
    	if (errstr == NULL)
    		return (int)port;
    	if ((se = getservbyname(s, "tcp")) != NULL)
    		return ntohs(se->s_port);
    	return -1;
    }
    
    int
    a2tun(const char *s, int *remote)
    {
    	const char *errstr = NULL;
    	char *sp, *ep;
    	int tun;
    
    	if (remote != NULL) {
    		*remote = SSH_TUNID_ANY;
    		sp = xstrdup(s);
    		if ((ep = strchr(sp, ':')) == NULL) {
    			free(sp);
    			return (a2tun(s, NULL));
    		}
    		ep[0] = '\0'; ep++;
    		*remote = a2tun(ep, NULL);
    		tun = a2tun(sp, NULL);
    		free(sp);
    		return (*remote == SSH_TUNID_ERR ? *remote : tun);
    	}
    
    	if (strcasecmp(s, "any") == 0)
    		return (SSH_TUNID_ANY);
    
    	tun = strtonum(s, 0, SSH_TUNID_MAX, &errstr);
    	if (errstr != NULL)
    		return (SSH_TUNID_ERR);
    
    	return (tun);
    }
    
    #define SECONDS		1.0
    #define MINUTES		(SECONDS * 60)
    #define HOURS		(MINUTES * 60)
    #define DAYS		(HOURS * 24)
    #define WEEKS		(DAYS * 7)
    
    /*
     * Convert an interval/duration time string into seconds, which may include
     * fractional seconds.
     *
     * The format is a sequence of:
     *      time[qualifier]
     *
     * This supports fractional values for the seconds value only. All other
     * values must be integers.
     *
     * Valid time qualifiers are:
     *      <none>  seconds
     *      s|S     seconds
     *      m|M     minutes
     *      h|H     hours
     *      d|D     days
     *      w|W     weeks
     *
     * Examples:
     *      90m      90 minutes
     *      1h30m    90 minutes
     *      1.5s     1.5 seconds
     *      2d       2 days
     *      1w       1 week
     *
     * Returns <0.0 if the time string is invalid.
     */
    double
    convtime_double(const char *s)
    {
    	double val, total_sec = 0.0, multiplier;
    	const char *p, *start_p;
    	char *endp;
    	int seen_seconds = 0;
    
    	if (s == NULL || *s == '\0')
    		return -1.0;
    
    	for (p = s; *p != '\0';) {
    		if (!isdigit((unsigned char)*p) && *p != '.')
    			return -1.0;
    
    		errno = 0;
    		if ((val = strtod(p, &endp)) < 0 || errno != 0 || p == endp)
    			return -1.0;
    		/* Allow only decimal forms */
    		if (p + strspn(p, "0123456789.") != endp)
    			return -1.0;
    		start_p = p;
    		p = endp;
    
    		switch (*p) {
    		case '\0':
    			/* FALLTHROUGH */
    		case 's':
    		case 'S':
    			if (seen_seconds++)
    				return -1.0;
    			multiplier = SECONDS;
    			break;
    		case 'm':
    		case 'M':
    			multiplier = MINUTES;
    			break;
    		case 'h':
    		case 'H':
    			multiplier = HOURS;
    			break;
    		case 'd':
    		case 'D':
    			multiplier = DAYS;
    			break;
    		case 'w':
    		case 'W':
    			multiplier = WEEKS;
    			break;
    		default:
    			return -1.0;
    		}
    
    		/* Special handling if this was a decimal */
    		if (memchr(start_p, '.', endp - start_p) != NULL) {
    			/* Decimal point present */
    			if (multiplier > 1.0)
    				return -1.0; /* No fractionals for non-seconds */
    			/* For seconds, ensure digits follow */
    			if (!isdigit((unsigned char)*(endp - 1)))
    				return -1.0;
    		}
    
    		total_sec += val * multiplier;
    
    		if (*p != '\0')
    			p++;
    	}
    	return total_sec;
    }
    
    /*
     * Same as convtime_double() above but fractional seconds are ignored.
     * Return -1 if time string is invalid.
     */
    int
    convtime(const char *s)
    {
    	double sec_val;
    
    	if ((sec_val = convtime_double(s)) < 0.0)
    		return -1;
    
    	/* Check for overflow into int */
    	if (sec_val < 0 || sec_val > INT_MAX)
    		return -1;
    
    	return (int)sec_val;
    }
    
    #define TF_BUFS	8
    #define TF_LEN	9
    
    const char *
    fmt_timeframe(time_t t)
    {
    	char		*buf;
    	static char	 tfbuf[TF_BUFS][TF_LEN];	/* ring buffer */
    	static int	 idx = 0;
    	unsigned int	 sec, min, hrs, day;
    	unsigned long long	week;
    
    	buf = tfbuf[idx++];
    	if (idx == TF_BUFS)
    		idx = 0;
    
    	week = t;
    
    	sec = week % 60;
    	week /= 60;
    	min = week % 60;
    	week /= 60;
    	hrs = week % 24;
    	week /= 24;
    	day = week % 7;
    	week /= 7;
    
    	if (week > 0)
    		snprintf(buf, TF_LEN, "%02lluw%01ud%02uh", week, day, hrs);
    	else if (day > 0)
    		snprintf(buf, TF_LEN, "%01ud%02uh%02um", day, hrs, min);
    	else
    		snprintf(buf, TF_LEN, "%02u:%02u:%02u", hrs, min, sec);
    
    	return (buf);
    }
    
    /*
     * Returns a standardized host+port identifier string.
     * Caller must free returned string.
     */
    char *
    put_host_port(const char *host, u_short port)
    {
    	char *hoststr;
    
    	if (port == 0 || port == SSH_DEFAULT_PORT)
    		return(xstrdup(host));
    	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;
    }
    
    /*
     * 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 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 *
    hpdelim2(char **cp, char *delim)
    {
    	char *s, *old;
    
    	if (cp == NULL || *cp == NULL)
    		return NULL;
    
    	old = s = *cp;
    	if (*s == '[') {
    		if ((s = strchr(s, ']')) == NULL)
    			return NULL;
    		else
    			s++;
    	} else if ((s = strpbrk(s, ":/")) == NULL)
    		s = *cp + strlen(*cp); /* skip to end (see first case below) */
    
    	switch (*s) {
    	case '\0':
    		*cp = NULL;	/* no more fields*/
    		break;
    
    	case ':':
    	case '/':
    		if (delim != NULL)
    			*delim = *s;
    		*s = '\0';	/* terminate */
    		*cp = s + 1;
    		break;
    
    	default:
    		return NULL;
    	}
    
    	return old;
    }
    
    /* The common case: only accept colon as delimiter. */
    char *
    hpdelim(char **cp)
    {
    	char *r, delim = '\0';
    
    	r =  hpdelim2(cp, &delim);
    	if (delim == '/')
    		return NULL;
    	return r;
    }
    
    char *
    cleanhostname(char *host)
    {
    	if (*host == '[' && host[strlen(host) - 1] == ']') {
    		host[strlen(host) - 1] = '\0';
    		return (host + 1);
    	} else
    		return host;
    }
    
    char *
    colon(char *cp)
    {
    	int flag = 0;
    
    	if (*cp == ':')		/* Leading colon is part of file name. */
    		return NULL;
    	if (*cp == '[')
    		flag = 1;
    
    	for (; *cp; ++cp) {
    		if (*cp == '@' && *(cp+1) == '[')
    			flag = 1;
    		if (*cp == ']' && *(cp+1) == ':' && flag)
    			return (cp+1);
    		if (*cp == ':' && !flag)
    			return (cp);
    		if (*cp == '/')
    			return NULL;
    	}
    	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;
    	size_t srclen;
    
    	if ((srclen = strlen(src)) >= SIZE_MAX)
    		return NULL;
    	ret = xmalloc(srclen + 1);
    	for (dst = ret; *src != '\0'; src++) {
    		switch (*src) {
    		case '+':
    			*dst++ = ' ';
    			break;
    		case '%':
    			/* note: don't allow \0 characters */
    			if (!isxdigit((unsigned char)src[1]) ||
    			    !isxdigit((unsigned char)src[2]) ||
    			    (ch = hexchar(src + 1)) == -1 || ch == 0) {
    				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, ...)
    {
    	va_list ap;
    	char *cp;
    	u_int nalloc;
    	int r;
    
    	va_start(ap, fmt);
    	r = vasprintf(&cp, fmt, ap);
    	va_end(ap);
    	if (r == -1)
    		fatal_f("argument too long");
    
    	nalloc = args->nalloc;
    	if (args->list == NULL) {
    		nalloc = 32;
    		args->num = 0;
    	} else if (args->num > (256 * 1024))
    		fatal_f("too many arguments");
    	else if (args->num >= args->nalloc)
    		fatal_f("arglist corrupt");
    	else if (args->num+2 >= nalloc)
    		nalloc *= 2;
    
    	args->list = xrecallocarray(args->list, args->nalloc,
    	    nalloc, sizeof(char *));
    	args->nalloc = nalloc;
    	args->list[args->num++] = cp;
    	args->list[args->num] = NULL;
    }
    
    void
    replacearg(arglist *args, u_int which, char *fmt, ...)
    {
    	va_list ap;
    	char *cp;
    	int r;
    
    	va_start(ap, fmt);
    	r = vasprintf(&cp, fmt, ap);
    	va_end(ap);
    	if (r == -1)
    		fatal_f("argument too long");
    	if (args->list == NULL || args->num >= args->nalloc)
    		fatal_f("arglist corrupt");
    
    	if (which >= args->num)
    		fatal_f("tried to replace invalid arg %d >= %d",
    		    which, args->num);
    	free(args->list[which]);
    	args->list[which] = cp;
    }
    
    void
    freeargs(arglist *args)
    {
    	u_int i;
    
    	if (args == NULL)
    		return;
    	if (args->list != NULL && args->num < args->nalloc) {
    		for (i = 0; i < args->num; i++)
    			free(args->list[i]);
    		free(args->list);
    	}
    	args->nalloc = args->num = 0;
    	args->list = NULL;
    }
    
    /*
     * Expands tildes in the file name.  Returns data allocated by xmalloc.
     * Warning: this calls getpw*.
     */
    int
    tilde_expand(const char *filename, uid_t uid, char **retp)
    {
    	char *ocopy = NULL, *copy, *s = NULL;
    	const char *path = NULL, *user = NULL;
    	struct passwd *pw;
    	size_t len;
    	int ret = -1, r, slash;
    
    	*retp = NULL;
    	if (*filename != '~') {
    		*retp = xstrdup(filename);
    		return 0;
    	}
    	ocopy = copy = xstrdup(filename + 1);
    
    	if (*copy == '\0')				/* ~ */
    		path = NULL;
    	else if (*copy == '/') {
    		copy += strspn(copy, "/");
    		if (*copy == '\0')
    			path = NULL;			/* ~/ */
    		else
    			path = copy;			/* ~/path */
    	} else {
    		user = copy;
    		if ((path = strchr(copy, '/')) != NULL) {
    			copy[path - copy] = '\0';
    			path++;
    			path += strspn(path, "/");
    			if (*path == '\0')		/* ~user/ */
    				path = NULL;
    			/* else				 ~user/path */
    		}
    		/* else					~user */
    	}
    	if (user != NULL) {
    		if ((pw = getpwnam(user)) == NULL) {
    			error_f("No such user %s", user);
    			goto out;
    		}
    	} else if ((pw = getpwuid(uid)) == NULL) {
    		error_f("No such uid %ld", (long)uid);
    		goto out;
    	}
    
    	/* Make sure directory has a trailing '/' */
    	slash = (len = strlen(pw->pw_dir)) == 0 || pw->pw_dir[len - 1] != '/';
    
    	if ((r = xasprintf(&s, "%s%s%s", pw->pw_dir,
    	    slash ? "/" : "", path != NULL ? path : "")) <= 0) {
    		error_f("xasprintf failed");
    		goto out;
    	}
    	if (r >= PATH_MAX) {
    		error_f("Path too long");
    		goto out;
    	}
    	/* success */
    	ret = 0;
    	*retp = s;
    	s = NULL;
     out:
    	free(s);
    	free(ocopy);
    	return ret;
    }
    
    char *
    tilde_expand_filename(const char *filename, uid_t uid)
    {
    	char *ret;
    
    	if (tilde_expand(filename, uid, &ret) != 0)
    		cleanup_exit(255);
    	return ret;
    }
    
    /*
     * Expand a string with a set of %[char] escapes and/or ${ENVIRONMENT}
     * substitutions.  A number of escapes may be specified as
     * (char *escape_chars, char *replacement) pairs. The list must be terminated
     * by a NULL escape_char. Returns replaced string in memory allocated by
     * xmalloc which the caller must free.
     */
    static char *
    vdollar_percent_expand(int *parseerror, int dollar, int percent,
        const char *string, va_list ap)
    {
    #define EXPAND_MAX_KEYS	64
    	u_int num_keys = 0, i;
    	struct {
    		const char *key;
    		const char *repl;
    	} keys[EXPAND_MAX_KEYS];
    	struct sshbuf *buf;
    	int r, missingvar = 0;
    	char *ret = NULL, *var, *varend, *val;
    	size_t len;
    
    	if ((buf = sshbuf_new()) == NULL)
    		fatal_f("sshbuf_new failed");
    	if (parseerror == NULL)
    		fatal_f("null parseerror arg");
    	*parseerror = 1;
    
    	/* Gather keys if we're doing percent expansion. */
    	if (percent) {
    		for (num_keys = 0; num_keys < EXPAND_MAX_KEYS; num_keys++) {
    			keys[num_keys].key = va_arg(ap, char *);
    			if (keys[num_keys].key == NULL)
    				break;
    			keys[num_keys].repl = va_arg(ap, char *);
    			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_f("too many keys");
    		if (num_keys == 0)
    			fatal_f("percent expansion without token list");
    	}
    
    	/* Expand string */
    	for (i = 0; *string != '\0'; string++) {
    		/* Optionally process ${ENVIRONMENT} expansions. */
    		if (dollar && string[0] == '$' && string[1] == '{') {
    			string += 2;  /* skip over '${' */
    			if ((varend = strchr(string, '}')) == NULL) {
    				error_f("environment variable '%s' missing "
    				    "closing '}'", string);
    				goto out;
    			}
    			len = varend - string;
    			if (len == 0) {
    				error_f("zero-length environment variable");
    				goto out;
    			}
    			var = xmalloc(len + 1);
    			(void)strlcpy(var, string, len + 1);
    			if ((val = getenv(var)) == NULL) {
    				error_f("env var ${%s} has no value", var);
    				missingvar = 1;
    			} else {
    				debug3_f("expand ${%s} -> '%s'", var, val);
    				if ((r = sshbuf_put(buf, val, strlen(val))) !=0)
    					fatal_fr(r, "sshbuf_put ${}");
    			}
    			free(var);
    			string += len;
    			continue;
    		}
    
    		/*
    		 * Process percent expansions if we have a list of TOKENs.
    		 * If we're not doing percent expansion everything just gets
    		 * appended here.
    		 */
    		if (*string != '%' || !percent) {
     append:
    			if ((r = sshbuf_put_u8(buf, *string)) != 0)
    				fatal_fr(r, "sshbuf_put_u8 %%");
    			continue;
    		}
    		string++;
    		/* %% case */
    		if (*string == '%')
    			goto append;
    		if (*string == '\0') {
    			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_fr(r, "sshbuf_put %%-repl");
    				break;
    			}
    		}
    		if (i >= num_keys) {
    			error_f("unknown key %%%c", *string);
    			goto out;
    		}
    	}
    	if (!missingvar && (ret = sshbuf_dup_string(buf)) == NULL)
    		fatal_f("sshbuf_dup_string failed");
    	*parseerror = 0;
     out:
    	sshbuf_free(buf);
    	return *parseerror ? NULL : ret;
    #undef EXPAND_MAX_KEYS
    }
    
    /*
     * Expand only environment variables.
     * Note that although this function is variadic like the other similar
     * functions, any such arguments will be unused.
     */
    
    char *
    dollar_expand(int *parseerr, const char *string, ...)
    {
    	char *ret;
    	int err;
    	va_list ap;
    
    	va_start(ap, string);
    	ret = vdollar_percent_expand(&err, 1, 0, string, ap);
    	va_end(ap);
    	if (parseerr != NULL)
    		*parseerr = err;
    	return ret;
    }
    
    /*
     * Returns expanded string or NULL if a specified environment variable is
     * not defined, or calls fatal if the string is invalid.
     */
    char *
    percent_expand(const char *string, ...)
    {
    	char *ret;
    	int err;
    	va_list ap;
    
    	va_start(ap, string);
    	ret = vdollar_percent_expand(&err, 0, 1, string, ap);
    	va_end(ap);
    	if (err)
    		fatal_f("failed");
    	return ret;
    }
    
    /*
     * Returns expanded string or NULL if a specified environment variable is
     * not defined, or calls fatal if the string is invalid.
     */
    char *
    percent_dollar_expand(const char *string, ...)
    {
    	char *ret;
    	int err;
    	va_list ap;
    
    	va_start(ap, string);
    	ret = vdollar_percent_expand(&err, 1, 1, string, ap);
    	va_end(ap);
    	if (err)
    		fatal_f("failed");
    	return ret;
    }
    
    int
    tun_open(int tun, int mode, char **ifname)
    {
    	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/%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/%s%d",
    			    tunbase, tun);
    			if ((fd = open(name, O_RDWR)) >= 0)
    				break;
    		}
    	} else {
    		debug_f("invalid tunnel %u", tun);
    		return -1;
    	}
    
    	if (fd == -1) {
    		debug_f("%s open: %s", name, strerror(errno));
    		return -1;
    	}
    
    	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);
    	if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
    		goto failed;
    
    	if (ioctl(sock, SIOCGIFFLAGS, &ifr) == -1) {
    		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_f("activate interface %s: %s", ifr.ifr_name,
    			    strerror(errno));
    			goto failed;
    		}
    	}
    
    	if (ifname != NULL)
    		*ifname = xstrdup(ifr.ifr_name);
    
    	close(sock);
    	return fd;
    
     failed:
    	if (fd >= 0)
    		close(fd);
    	if (sock >= 0)
    		close(sock);
    	return -1;
    }
    
    void
    sanitise_stdfd(void)
    {
    	int nullfd, dupfd;
    
    	if ((nullfd = dupfd = open(_PATH_DEVNULL, O_RDWR)) == -1) {
    		fprintf(stderr, "Couldn't open /dev/null: %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 > STDERR_FILENO)
    		close(nullfd);
    }
    
    char *
    tohex(const void *vp, size_t l)
    {
    	const u_char *p = (const u_char *)vp;
    	char b[3], *r;
    	size_t i, hl;
    
    	if (l > 65536)
    		return xstrdup("tohex: length > 65536");
    
    	hl = l * 2 + 1;
    	r = xcalloc(1, hl);
    	for (i = 0; i < l; i++) {
    		snprintf(b, sizeof(b), "%02x", p[i]);
    		strlcat(r, b, hl);
    	}
    	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;
    }
    
    
    uint64_t
    get_u64(const void *vp)
    {
    	const u_char *p = (const u_char *)vp;
    	uint64_t v;
    
    	v  = (uint64_t)p[0] << 56;
    	v |= (uint64_t)p[1] << 48;
    	v |= (uint64_t)p[2] << 40;
    	v |= (uint64_t)p[3] << 32;
    	v |= (uint64_t)p[4] << 24;
    	v |= (uint64_t)p[5] << 16;
    	v |= (uint64_t)p[6] << 8;
    	v |= (uint64_t)p[7];
    
    	return (v);
    }
    
    uint32_t
    get_u32(const void *vp)
    {
    	const u_char *p = (const u_char *)vp;
    	uint32_t v;
    
    	v  = (uint32_t)p[0] << 24;
    	v |= (uint32_t)p[1] << 16;
    	v |= (uint32_t)p[2] << 8;
    	v |= (uint32_t)p[3];
    
    	return (v);
    }
    
    uint32_t
    get_u32_le(const void *vp)
    {
    	const u_char *p = (const u_char *)vp;
    	uint32_t v;
    
    	v  = (uint32_t)p[0];
    	v |= (uint32_t)p[1] << 8;
    	v |= (uint32_t)p[2] << 16;
    	v |= (uint32_t)p[3] << 24;
    
    	return (v);
    }
    
    uint16_t
    get_u16(const void *vp)
    {
    	const u_char *p = (const u_char *)vp;
    	uint16_t v;
    
    	v  = (uint16_t)p[0] << 8;
    	v |= (uint16_t)p[1];
    
    	return (v);
    }
    
    void
    put_u64(void *vp, uint64_t v)
    {
    	u_char *p = (u_char *)vp;
    
    	p[0] = (u_char)(v >> 56) & 0xff;
    	p[1] = (u_char)(v >> 48) & 0xff;
    	p[2] = (u_char)(v >> 40) & 0xff;
    	p[3] = (u_char)(v >> 32) & 0xff;
    	p[4] = (u_char)(v >> 24) & 0xff;
    	p[5] = (u_char)(v >> 16) & 0xff;
    	p[6] = (u_char)(v >> 8) & 0xff;
    	p[7] = (u_char)v & 0xff;
    }
    
    void
    put_u32(void *vp, uint32_t v)
    {
    	u_char *p = (u_char *)vp;
    
    	p[0] = (u_char)(v >> 24) & 0xff;
    	p[1] = (u_char)(v >> 16) & 0xff;
    	p[2] = (u_char)(v >> 8) & 0xff;
    	p[3] = (u_char)v & 0xff;
    }
    
    void
    put_u32_le(void *vp, uint32_t v)
    {
    	u_char *p = (u_char *)vp;
    
    	p[0] = (u_char)v & 0xff;
    	p[1] = (u_char)(v >> 8) & 0xff;
    	p[2] = (u_char)(v >> 16) & 0xff;
    	p[3] = (u_char)(v >> 24) & 0xff;
    }
    
    void
    put_u16(void *vp, uint16_t v)
    {
    	u_char *p = (u_char *)vp;
    
    	p[0] = (u_char)(v >> 8) & 0xff;
    	p[1] = (u_char)v & 0xff;
    }
    
    void
    ms_subtract_diff(struct timeval *start, int *ms)
    {
    	struct timeval diff, finish;
    
    	monotime_tv(&finish);
    	timersub(&finish, start, &diff);
    	*ms -= (diff.tv_sec * 1000) + (diff.tv_usec / 1000);
    }
    
    void
    ms_to_timespec(struct timespec *ts, int ms)
    {
    	if (ms < 0)
    		ms = 0;
    	ts->tv_sec = ms / 1000;
    	ts->tv_nsec = (ms % 1000) * 1000 * 1000;
    }
    
    void
    monotime_ts(struct timespec *ts)
    {
    	if (clock_gettime(CLOCK_MONOTONIC, ts) != 0)
    		fatal("clock_gettime: %s", strerror(errno));
    }
    
    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 (double)ts.tv_sec + (double)ts.tv_nsec / 1000000000.0;
    }
    
    void
    bandwidth_limit_init(struct bwlimit *bw, uint64_t kbps, size_t buflen)
    {
    	bw->buflen = buflen;
    	bw->rate = kbps;
    	bw->thresh = buflen;
    	bw->lamt = 0;
    	timerclear(&bw->bwstart);
    	timerclear(&bw->bwend);
    }
    
    /* Callback from read/write loop to insert bandwidth-limiting delays */
    void
    bandwidth_limit(struct bwlimit *bw, size_t read_len)
    {
    	uint64_t waitlen;
    	struct timespec ts, rm;
    
    	bw->lamt += read_len;
    	if (!timerisset(&bw->bwstart)) {
    		monotime_tv(&bw->bwstart);
    		return;
    	}
    	if (bw->lamt < bw->thresh)
    		return;
    
    	monotime_tv(&bw->bwend);
    	timersub(&bw->bwend, &bw->bwstart, &bw->bwend);
    	if (!timerisset(&bw->bwend))
    		return;
    
    	bw->lamt *= 8;
    	waitlen = (double)1000000L * bw->lamt / bw->rate;
    
    	bw->bwstart.tv_sec = waitlen / 1000000L;
    	bw->bwstart.tv_usec = waitlen % 1000000L;
    
    	if (timercmp(&bw->bwstart, &bw->bwend, >)) {
    		timersub(&bw->bwstart, &bw->bwend, &bw->bwend);
    
    		/* Adjust the wait time */
    		if (bw->bwend.tv_sec) {
    			bw->thresh /= 2;
    			if (bw->thresh < bw->buflen / 4)
    				bw->thresh = bw->buflen / 4;
    		} else if (bw->bwend.tv_usec < 10000) {
    			bw->thresh *= 2;
    			if (bw->thresh > bw->buflen * 8)
    				bw->thresh = bw->buflen * 8;
    		}
    
    		TIMEVAL_TO_TIMESPEC(&bw->bwend, &ts);
    		while (nanosleep(&ts, &rm) == -1) {
    			if (errno != EINTR)
    				break;
    			ts = rm;
    		}
    	}
    
    	bw->lamt = 0;
    	monotime_tv(&bw->bwstart);
    }
    
    /* Make a template filename for mk[sd]temp() */
    void
    mktemp_proto(char *s, size_t len)
    {
    	const char *tmpdir;
    	int r;
    
    	if ((tmpdir = getenv("TMPDIR")) != NULL) {
    		r = snprintf(s, len, "%s/ssh-XXXXXXXXXXXX", tmpdir);
    		if (r > 0 && (size_t)r < len)
    			return;
    	}
    	r = snprintf(s, len, "/tmp/ssh-XXXXXXXXXXXX");
    	if (r < 0 || (size_t)r >= len)
    		fatal_f("template string too short");
    }
    
    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 },
    	{ "af21", IPTOS_DSCP_AF21 },
    	{ "af22", IPTOS_DSCP_AF22 },
    	{ "af23", IPTOS_DSCP_AF23 },
    	{ "af31", IPTOS_DSCP_AF31 },
    	{ "af32", IPTOS_DSCP_AF32 },
    	{ "af33", IPTOS_DSCP_AF33 },
    	{ "af41", IPTOS_DSCP_AF41 },
    	{ "af42", IPTOS_DSCP_AF42 },
    	{ "af43", IPTOS_DSCP_AF43 },
    	{ "cs0", IPTOS_DSCP_CS0 },
    	{ "cs1", IPTOS_DSCP_CS1 },
    	{ "cs2", IPTOS_DSCP_CS2 },
    	{ "cs3", IPTOS_DSCP_CS3 },
    	{ "cs4", IPTOS_DSCP_CS4 },
    	{ "cs5", IPTOS_DSCP_CS5 },
    	{ "cs6", IPTOS_DSCP_CS6 },
    	{ "cs7", IPTOS_DSCP_CS7 },
    	{ "ef", IPTOS_DSCP_EF },
    	{ "le", IPTOS_DSCP_LE },
    	{ "va",	IPTOS_DSCP_VA },
    	{ "lowdelay", INT_MIN },	/* deprecated */
    	{ "throughput", INT_MIN },	/* deprecated */
    	{ "reliability", INT_MIN },	/* deprecated */
    	{ NULL, -1 }
    };
    
    int
    parse_ipqos(const char *cp)
    {
    	const char *errstr;
    	u_int i;
    	int val;
    
    	if (cp == NULL)
    		return -1;
    	for (i = 0; ipqos[i].name != NULL; i++) {
    		if (strcasecmp(cp, ipqos[i].name) == 0)
    			return ipqos[i].value;
    	}
    	/* Try parsing as an integer */
    	val = (int)strtonum(cp, 0, 255, &errstr);
    	if (errstr)
    		return -1;
    	return val;
    }
    
    const char *
    iptos2str(int iptos)
    {
    	int i;
    	static char iptos_str[sizeof "0xff"];
    
    	for (i = 0; ipqos[i].name != NULL; i++) {
    		if (ipqos[i].value == iptos)
    			return ipqos[i].name;
    	}
    	snprintf(iptos_str, sizeof iptos_str, "0x%02x", iptos);
    	return iptos_str;
    }
    
    void
    lowercase(char *s)
    {
    	for (; *s; s++)
    		*s = tolower((u_char)*s);
    }
    
    int
    unix_listener(const char *path, int backlog, int unlink_first)
    {
    	struct sockaddr_un sunaddr;
    	int saved_errno, sock;
    
    	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_f("path \"%s\" too long for Unix domain socket", path);
    		errno = ENAMETOOLONG;
    		return -1;
    	}
    
    	sock = socket(PF_UNIX, SOCK_STREAM, 0);
    	if (sock == -1) {
    		saved_errno = errno;
    		error_f("socket: %.100s", strerror(errno));
    		errno = saved_errno;
    		return -1;
    	}
    	if (unlink_first == 1) {
    		if (unlink(path) != 0 && errno != ENOENT)
    			error("unlink(%s): %.100s", path, strerror(errno));
    	}
    	if (bind(sock, (struct sockaddr *)&sunaddr, sizeof(sunaddr)) == -1) {
    		saved_errno = 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_f("cannot listen on path %s: %s", path, strerror(errno));
    		close(sock);
    		unlink(path);
    		errno = saved_errno;
    		return -1;
    	}
    	return sock;
    }
    
    /*
     * 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 port number, FWD_PERMIT_ANY_PORT or -1 on error */
    int
    permitopen_port(const char *p)
    {
    	int port;
    
    	if (strcmp(p, "*") == 0)
    		return FWD_PERMIT_ANY_PORT;
    	if ((port = a2port(p)) > 0)
    		return port;
    	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 terminate_on_comment)
    {
    	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;
    		if (terminate_on_comment && s[i] == '#')
    			break;
    		/* Start of a token */
    		quote = 0;
    
    		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] == '\\' ||
    				    (quote == 0 && 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] == '\"' || s[i] == '\''))
    				quote = s[i]; /* quote start */
    			else if (quote != 0 && s[i] == quote)
    				quote = 0; /* quote end */
    			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_f("sshbuf_new failed");
    
    	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_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_fr(r, "assemble");
    	}
    	if ((ret = malloc(sshbuf_len(buf) + 1)) == NULL)
    		fatal_f("malloc failed");
    	memcpy(ret, sshbuf_ptr(buf), sshbuf_len(buf));
    	ret[sshbuf_len(buf)] = '\0';
    	sshbuf_free(buf);
    	sshbuf_free(arg);
    	return ret;
    }
    
    char *
    argv_next(int *argcp, char ***argvp)
    {
    	char *ret = (*argvp)[0];
    
    	if (*argcp > 0 && ret != NULL) {
    		(*argcp)--;
    		(*argvp)++;
    	}
    	return ret;
    }
    
    void
    argv_consume(int *argcp)
    {
    	*argcp = 0;
    }
    
    void
    argv_free(char **av, int ac)
    {
    	int i;
    
    	if (av == NULL)
    		return;
    	for (i = 0; i < ac; i++)
    		free(av[i]);
    	free(av);
    }
    
    /* 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], buf2[PATH_MAX], homedir[PATH_MAX];
    	char *cp;
    	int comparehome = 0;
    	struct stat st;
    
    	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 ((stp->st_uid != 0 && 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 (;;) {
    		/*
    		 * POSIX allows dirname to modify its argument and return a
    		 * pointer into it, so make a copy to avoid overlapping strlcpy.
    		 */
    		strlcpy(buf2, buf, sizeof(buf2));
    		if ((cp = dirname(buf2)) == NULL) {
    			snprintf(err, errlen, "dirname() failed");
    			return -1;
    		}
    		strlcpy(buf, cp, sizeof(buf));
    
    		if (stat(buf, &st) == -1 ||
    		    (st.st_uid != 0 && st.st_uid != uid) ||
    		    (st.st_mode & 022) != 0) {
    			snprintf(err, errlen,
    			    "bad ownership or modes for directory %s", buf);
    			return -1;
    		}
    
    		/* 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;
    	}
    
    	/*
    	 * 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]) &&
    	   name[0] != '_' /* technically invalid, but common */) {
    		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;
    
    	if (nptr == NULL || *nptr == '\0')
    		return "missing";
    	*val = strtonum(nptr, 0, INT_MAX, &errstr);
    	return errstr;
    }
    
    int
    parse_absolute_time(const char *s, uint64_t *tp)
    {
    	struct tm tm;
    	time_t tt;
    	char buf[32], *fmt;
    	const char *cp;
    	size_t l;
    	int is_utc = 0;
    
    	*tp = 0;
    
    	l = strlen(s);
    	if (l > 1 && strcasecmp(s + l - 1, "Z") == 0) {
    		is_utc = 1;
    		l--;
    	} else if (l > 3 && strcasecmp(s + l - 3, "UTC") == 0) {
    		is_utc = 1;
    		l -= 3;
    	}
    	/*
    	 * 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 (l) {
    	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 ((cp = strptime(buf, fmt, &tm)) == NULL || *cp != '\0')
    		return SSH_ERR_INVALID_FORMAT;
    	if (is_utc) {
    		tm.tm_wday = -1;
    		if ((tt = timegm(&tm)) == -1 && tm.tm_wday == -1)
    			return SSH_ERR_INVALID_FORMAT;
    	} else {
    		tm.tm_wday = -1;
    		if ((tt = mktime(&tm)) == -1 && tm.tm_wday == -1)
    			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 > SSH_TIME_T_MAX ? SSH_TIME_T_MAX : t;
    	struct tm tm;
    
    	if (localtime_r(&tt, &tm) == NULL)
    		strlcpy(buf, "UNKNOWN-TIME", len);
    	else
    		strftime(buf, len, "%Y-%m-%dT%H:%M:%S", &tm);
    }
    
    /*
     * Parse a "pattern=interval" clause (e.g. a ChannelTimeout).
     * Returns 0 on success or non-zero on failure.
     * Caller must free *typep.
     */
    int
    parse_pattern_interval(const char *s, char **typep, int *secsp)
    {
    	char *cp, *sdup;
    	int secs;
    
    	if (typep != NULL)
    		*typep = NULL;
    	if (secsp != NULL)
    		*secsp = 0;
    	if (s == NULL)
    		return -1;
    	sdup = xstrdup(s);
    
    	if ((cp = strchr(sdup, '=')) == NULL || cp == sdup) {
    		free(sdup);
    		return -1;
    	}
    	*cp++ = '\0';
    	if ((secs = convtime(cp)) < 0) {
    		free(sdup);
    		return -1;
    	}
    	/* success */
    	if (typep != NULL)
    		*typep = xstrdup(sdup);
    	if (secsp != NULL)
    		*secsp = secs;
    	free(sdup);
    	return 0;
    }
    
    /* 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;
    }
    
    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);
    }
    
    void
    opt_array_free2(char **array, int **iarray, u_int l)
    {
    	u_int i;
    
    	if (array == NULL || l == 0)
    		return;
    	for (i = 0; i < l; i++)
    		free(array[i]);
    	free(array);
    	free(iarray);
    }
    
    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 (signum != SIGALRM)
    		sa.sa_flags = SA_RESTART;
    	if (sigaction(signum, &sa, &osa) == -1) {
    		debug3("sigaction(%s): %s", strsignal(signum), strerror(errno));
    		return SIG_ERR;
    	}
    	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 (geteuid() == 0 &&
    		    initgroups(pw->pw_name, pw->pw_gid) == -1) {
    			error("%s: initgroups(%s, %u): %s", tag,
    			    pw->pw_name, (u_int)pw->pw_gid, strerror(errno));
    			_exit(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;
    }
    
    const char *
    lookup_env_in_list(const char *env, char * const *envs, size_t nenvs)
    {
    	size_t i, envlen;
    
    	envlen = strlen(env);
    	for (i = 0; i < nenvs; i++) {
    		if (strncmp(envs[i], env, envlen) == 0 &&
    		    envs[i][envlen] == '=') {
    			return envs[i] + envlen + 1;
    		}
    	}
    	return NULL;
    }
    
    const char *
    lookup_setenv_in_list(const char *env, char * const *envs, size_t nenvs)
    {
    	char *name, *cp;
    	const char *ret;
    
    	name = xstrdup(env);
    	if ((cp = strchr(name, '=')) == NULL) {
    		free(name);
    		return NULL; /* not env=val */
    	}
    	*cp = '\0';
    	ret = lookup_env_in_list(name, envs, nenvs);
    	free(name);
    	return ret;
    }
    
    /*
     * Helpers for managing poll(2)/ppoll(2) timeouts
     * Will remember the earliest deadline and return it for use in poll/ppoll.
     */
    
    /* Initialise a poll/ppoll timeout with an indefinite deadline */
    void
    ptimeout_init(struct timespec *pt)
    {
    	/*
    	 * Deliberately invalid for ppoll(2).
    	 * Will be converted to NULL in ptimeout_get_tspec() later.
    	 */
    	pt->tv_sec = -1;
    	pt->tv_nsec = 0;
    }
    
    /* Specify a poll/ppoll deadline of at most 'sec' seconds */
    void
    ptimeout_deadline_sec(struct timespec *pt, long sec)
    {
    	if (pt->tv_sec == -1 || pt->tv_sec >= sec) {
    		pt->tv_sec = sec;
    		pt->tv_nsec = 0;
    	}
    }
    
    /* Specify a poll/ppoll deadline of at most 'p' (timespec) */
    static void
    ptimeout_deadline_tsp(struct timespec *pt, struct timespec *p)
    {
    	if (pt->tv_sec == -1 || timespeccmp(pt, p, >=))
    		*pt = *p;
    }
    
    /* Specify a poll/ppoll deadline of at most 'ms' milliseconds */
    void
    ptimeout_deadline_ms(struct timespec *pt, long ms)
    {
    	struct timespec p;
    
    	p.tv_sec = ms / 1000;
    	p.tv_nsec = (ms % 1000) * 1000000;
    	ptimeout_deadline_tsp(pt, &p);
    }
    
    /* Specify a poll/ppoll deadline at wall clock monotime 'when' (timespec) */
    void
    ptimeout_deadline_monotime_tsp(struct timespec *pt, struct timespec *when)
    {
    	struct timespec now, t;
    
    	monotime_ts(&now);
    
    	if (timespeccmp(&now, when, >=)) {
    		/* 'when' is now or in the past. Timeout ASAP */
    		pt->tv_sec = 0;
    		pt->tv_nsec = 0;
    	} else {
    		timespecsub(when, &now, &t);
    		ptimeout_deadline_tsp(pt, &t);
    	}
    }
    
    /* Specify a poll/ppoll deadline at wall clock monotime 'when' */
    void
    ptimeout_deadline_monotime(struct timespec *pt, time_t when)
    {
    	struct timespec t;
    
    	t.tv_sec = when;
    	t.tv_nsec = 0;
    	ptimeout_deadline_monotime_tsp(pt, &t);
    }
    
    /* Get a poll(2) timeout value in milliseconds */
    int
    ptimeout_get_ms(struct timespec *pt)
    {
    	if (pt->tv_sec == -1)
    		return -1;
    	if (pt->tv_sec >= (INT_MAX - (pt->tv_nsec / 1000000)) / 1000)
    		return INT_MAX;
    	return (pt->tv_sec * 1000) + (pt->tv_nsec / 1000000);
    }
    
    /* Get a ppoll(2) timeout value as a timespec pointer */
    struct timespec *
    ptimeout_get_tsp(struct timespec *pt)
    {
    	return pt->tv_sec == -1 ? NULL : pt;
    }
    
    /* Returns non-zero if a timeout has been set (i.e. is not indefinite) */
    int
    ptimeout_isset(struct timespec *pt)
    {
    	return pt->tv_sec != -1;
    }
    
    /*
     * Returns zero if the library at 'path' contains symbol 's', nonzero
     * otherwise.
     */
    int
    lib_contains_symbol(const char *path, const char *s)
    {
    	struct nlist nl[2];
    	int ret = -1, r;
    
    	memset(nl, 0, sizeof(nl));
    	nl[0].n_name = xstrdup(s);
    	nl[1].n_name = NULL;
    	if ((r = nlist(path, nl)) == -1) {
    		error_f("nlist failed for %s", path);
    		goto out;
    	}
    	if (r != 0 || nl[0].n_value == 0 || nl[0].n_type == 0) {
    		error_f("library %s does not contain symbol %s", path, s);
    		goto out;
    	}
    	/* success */
    	ret = 0;
     out:
    	free(nl[0].n_name);
    	return ret;
    }
    
    int
    signal_is_crash(int sig)
    {
    	switch (sig) {
    	case SIGSEGV:
    	case SIGBUS:
    	case SIGTRAP:
    	case SIGSYS:
    	case SIGFPE:
    	case SIGILL:
    	case SIGABRT:
    		return 1;
    	}
    	return 0;
    }
    
    char *
    get_homedir(void)
    {
    	char *cp;
    	struct passwd *pw;
    
    	if ((cp = getenv("HOME")) != NULL && *cp != '\0')
    		return xstrdup(cp);
    
    	if ((pw = getpwuid(getuid())) != NULL && *pw->pw_dir != '\0')
    		return xstrdup(pw->pw_dir);
    
    	return NULL;
    }