summaryrefslogtreecommitdiffstats
path: root/misc.c
diff options
context:
space:
mode:
authormillert@openbsd.org <millert@openbsd.org>2017-10-21 23:06:24 +0000
committerDamien Miller <djm@mindrot.org>2017-10-23 16:10:08 +1100
commit887669ef032d63cf07f53cada216fa8a0c9a7d72 (patch)
tree089b20255da21a489d7bc796a8ee86bd0b8f028f /misc.c
parentd27bff293cfeb2252f4c7a58babe5ad3262c6c98 (diff)
upstream commit
Add URI support to ssh, sftp and scp. For example ssh://user@host or sftp://user@host/path. The connection parameters described in draft-ietf-secsh-scp-sftp-ssh-uri-04 are not implemented since the ssh fingerprint format in the draft uses md5 with no way to specify the hash function type. OK djm@ Upstream-ID: 4ba3768b662d6722de59e6ecb00abf2d4bf9cacc
Diffstat (limited to 'misc.c')
-rw-r--r--misc.c297
1 files changed, 292 insertions, 5 deletions
diff --git a/misc.c b/misc.c
index 05950a47..d4d0e44a 100644
--- a/misc.c
+++ b/misc.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: misc.c,v 1.113 2017/08/18 05:48:04 djm Exp $ */
+/* $OpenBSD: misc.c,v 1.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.
@@ -395,11 +395,12 @@ put_host_port(const char *host, u_short port)
* Search for next delimiter between hostnames/addresses and ports.
* Argument may be modified (for termination).
* Returns *cp if parsing succeeds.
- * *cp is set to the start of the next delimiter, if one was found.
+ * *cp is set to the start of the next field, if one was found.
+ * The delimiter char, if present, is stored in delim.
* If this is the last field, *cp is set to NULL.
*/
-char *
-hpdelim(char **cp)
+static char *
+hpdelim2(char **cp, char *delim)
{
char *s, *old;
@@ -422,6 +423,8 @@ hpdelim(char **cp)
case ':':
case '/':
+ if (delim != NULL)
+ *delim = *s;
*s = '\0'; /* terminate */
*cp = s + 1;
break;
@@ -434,6 +437,12 @@ hpdelim(char **cp)
}
char *
+hpdelim(char **cp)
+{
+ return hpdelim2(cp, NULL);
+}
+
+char *
cleanhostname(char *host)
{
if (*host == '[' && host[strlen(host) - 1] == ']') {
@@ -467,6 +476,75 @@ colon(char *cp)
}
/*
+ * Parse a [user@]host:[path] string.
+ * Caller must free returned user, host and path.
+ * Any of the pointer return arguments may be NULL (useful for syntax checking).
+ * If user was not specified then *userp will be set to NULL.
+ * If host was not specified then *hostp will be set to NULL.
+ * If path was not specified then *pathp will be set to ".".
+ * Returns 0 on success, -1 on failure.
+ */
+int
+parse_user_host_path(const char *s, char **userp, char **hostp, char **pathp)
+{
+ char *user = NULL, *host = NULL, *path = NULL;
+ char *sdup, *tmp;
+ int ret = -1;
+
+ if (userp != NULL)
+ *userp = NULL;
+ if (hostp != NULL)
+ *hostp = NULL;
+ if (pathp != NULL)
+ *pathp = NULL;
+
+ sdup = 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).
@@ -491,7 +569,7 @@ parse_user_host_port(const char *s, char **userp, char **hostp, int *portp)
if ((sdup = tmp = strdup(s)) == NULL)
return -1;
/* Extract optional username */
- if ((cp = strchr(tmp, '@')) != NULL) {
+ if ((cp = strrchr(tmp, '@')) != NULL) {
*cp = '\0';
if (*tmp == '\0')
goto out;
@@ -527,6 +605,168 @@ parse_user_host_port(const char *s, char **userp, char **hostp, int *portp)
return ret;
}
+/*
+ * Converts a two-byte hex string to decimal.
+ * Returns the decimal value or -1 for invalid input.
+ */
+static int
+hexchar(const char *s)
+{
+ unsigned char result[2];
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ if (s[i] >= '0' && s[i] <= '9')
+ result[i] = (unsigned char)(s[i] - '0');
+ else if (s[i] >= 'a' && s[i] <= 'f')
+ result[i] = (unsigned char)(s[i] - 'a') + 10;
+ else if (s[i] >= 'A' && s[i] <= 'F')
+ result[i] = (unsigned char)(s[i] - 'A') + 10;
+ else
+ return -1;
+ }
+ return (result[0] << 4) | result[1];
+}
+
+/*
+ * Decode an url-encoded string.
+ * Returns a newly allocated string on success or NULL on failure.
+ */
+static char *
+urldecode(const char *src)
+{
+ char *ret, *dst;
+ int ch;
+
+ ret = xmalloc(strlen(src) + 1);
+ for (dst = ret; *src != '\0'; src++) {
+ switch (*src) {
+ case '+':
+ *dst++ = ' ';
+ break;
+ case '%':
+ if (!isxdigit((unsigned char)src[1]) ||
+ !isxdigit((unsigned char)src[2]) ||
+ (ch = hexchar(src + 1)) == -1) {
+ free(ret);
+ return NULL;
+ }
+ *dst++ = ch;
+ src += 2;
+ break;
+ default:
+ *dst++ = *src;
+ break;
+ }
+ }
+ *dst = '\0';
+
+ return ret;
+}
+
+/*
+ * Parse an (scp|ssh|sftp)://[user@]host[:port][/path] URI.
+ * See https://tools.ietf.org/html/draft-ietf-secsh-scp-sftp-ssh-uri-04
+ * Either user or path may be url-encoded (but not host or port).
+ * Caller must free returned user, host and path.
+ * Any of the pointer return arguments may be NULL (useful for syntax checking)
+ * but the scheme must always be specified.
+ * If user was not specified then *userp will be set to NULL.
+ * If port was not specified then *portp will be -1.
+ * If path was not specified then *pathp will be set to NULL.
+ * Returns 0 on success, 1 if non-uri/wrong scheme, -1 on error/invalid uri.
+ */
+int
+parse_uri(const char *scheme, const char *uri, char **userp, char **hostp,
+ int *portp, char **pathp)
+{
+ char *uridup, *cp, *tmp, ch;
+ char *user = NULL, *host = NULL, *path = NULL;
+ int port = -1, ret = -1;
+ size_t len;
+
+ len = strlen(scheme);
+ if (strncmp(uri, scheme, len) != 0 || strncmp(uri + len, "://", 3) != 0)
+ return 1;
+ uri += len + 3;
+
+ if (userp != NULL)
+ *userp = NULL;
+ if (hostp != NULL)
+ *hostp = NULL;
+ if (portp != NULL)
+ *portp = -1;
+ if (pathp != NULL)
+ *pathp = NULL;
+
+ uridup = tmp = xstrdup(uri);
+
+ /* Extract optional ssh-info (username + connection params) */
+ if ((cp = strchr(tmp, '@')) != NULL) {
+ char *delim;
+
+ *cp = '\0';
+ /* Extract username and connection params */
+ if ((delim = strchr(tmp, ';')) != NULL) {
+ /* Just ignore connection params for now */
+ *delim = '\0';
+ }
+ if (*tmp == '\0') {
+ /* Empty username */
+ goto out;
+ }
+ if ((user = urldecode(tmp)) == NULL)
+ goto out;
+ tmp = cp + 1;
+ }
+
+ /* Extract mandatory hostname */
+ if ((cp = hpdelim2(&tmp, &ch)) == NULL || *cp == '\0')
+ goto out;
+ host = xstrdup(cleanhostname(cp));
+ if (!valid_domain(host, 0, NULL))
+ goto out;
+
+ if (tmp != NULL && *tmp != '\0') {
+ if (ch == ':') {
+ /* Convert and verify port. */
+ if ((cp = strchr(tmp, '/')) != NULL)
+ *cp = '\0';
+ if ((port = a2port(tmp)) <= 0)
+ goto out;
+ tmp = cp ? cp + 1 : NULL;
+ }
+ if (tmp != NULL && *tmp != '\0') {
+ /* Extract optional path */
+ if ((path = urldecode(tmp)) == NULL)
+ goto out;
+ }
+ }
+
+ /* Success */
+ if (userp != NULL) {
+ *userp = user;
+ user = NULL;
+ }
+ if (hostp != NULL) {
+ *hostp = host;
+ host = NULL;
+ }
+ if (portp != NULL)
+ *portp = port;
+ if (pathp != NULL) {
+ *pathp = path;
+ path = NULL;
+ }
+ ret = 0;
+ out:
+ free(uridup);
+ free(user);
+ free(host);
+ free(path);
+ return ret;
+}
+
/* function to assist building execv() arguments */
void
addargs(arglist *args, char *fmt, ...)
@@ -1743,3 +1983,50 @@ child_set_env(char ***envp, u_int *envsizep, const char *name,
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;
+}