=================================================================== RCS file: /cvsrepo/anoncvs/cvs/src/usr.bin/ssh/misc.c,v retrieving revision 1.113 retrieving revision 1.114 diff -u -r1.113 -r1.114 --- src/usr.bin/ssh/misc.c 2017/08/18 05:48:04 1.113 +++ src/usr.bin/ssh/misc.c 2017/10/21 23:06:24 1.114 @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.c,v 1.113 2017/08/18 05:48:04 djm Exp $ */ +/* $OpenBSD: misc.c,v 1.114 2017/10/21 23:06:24 millert Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2005,2006 Damien Miller. All rights reserved. @@ -375,11 +375,12 @@ * Search for next delimiter between hostnames/addresses and ports. * Argument may be modified (for termination). * Returns *cp if parsing succeeds. - * *cp is set to the start of the next delimiter, if one was found. + * *cp is set to the start of the next field, if one was found. + * The delimiter char, if present, is stored in delim. * If this is the last field, *cp is set to NULL. */ -char * -hpdelim(char **cp) +static char * +hpdelim2(char **cp, char *delim) { char *s, *old; @@ -402,6 +403,8 @@ case ':': case '/': + if (delim != NULL) + *delim = *s; *s = '\0'; /* terminate */ *cp = s + 1; break; @@ -414,6 +417,12 @@ } char * +hpdelim(char **cp) +{ + return hpdelim2(cp, NULL); +} + +char * cleanhostname(char *host) { if (*host == '[' && host[strlen(host) - 1] == ']') { @@ -447,6 +456,75 @@ } /* + * 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 = tmp = 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). @@ -471,7 +549,7 @@ if ((sdup = tmp = strdup(s)) == NULL) return -1; /* Extract optional username */ - if ((cp = strchr(tmp, '@')) != NULL) { + if ((cp = strrchr(tmp, '@')) != NULL) { *cp = '\0'; if (*tmp == '\0') goto out; @@ -507,6 +585,168 @@ 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, ...) @@ -1666,3 +1906,50 @@ 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; +}