version 1.113, 2017/08/18 05:48:04 |
version 1.114, 2017/10/21 23:06:24 |
|
|
* Search for next delimiter between hostnames/addresses and ports. |
* Search for next delimiter between hostnames/addresses and ports. |
* Argument may be modified (for termination). |
* Argument may be modified (for termination). |
* Returns *cp if parsing succeeds. |
* 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. |
* If this is the last field, *cp is set to NULL. |
*/ |
*/ |
char * |
static char * |
hpdelim(char **cp) |
hpdelim2(char **cp, char *delim) |
{ |
{ |
char *s, *old; |
char *s, *old; |
|
|
|
|
|
|
case ':': |
case ':': |
case '/': |
case '/': |
|
if (delim != NULL) |
|
*delim = *s; |
*s = '\0'; /* terminate */ |
*s = '\0'; /* terminate */ |
*cp = s + 1; |
*cp = s + 1; |
break; |
break; |
|
|
} |
} |
|
|
char * |
char * |
|
hpdelim(char **cp) |
|
{ |
|
return hpdelim2(cp, NULL); |
|
} |
|
|
|
char * |
cleanhostname(char *host) |
cleanhostname(char *host) |
{ |
{ |
if (*host == '[' && host[strlen(host) - 1] == ']') { |
if (*host == '[' && host[strlen(host) - 1] == ']') { |
|
|
} |
} |
|
|
/* |
/* |
|
* 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. |
* Parse a [user@]host[:port] string. |
* Caller must free returned user and host. |
* Caller must free returned user and host. |
* Any of the pointer return arguments may be NULL (useful for syntax checking). |
* Any of the pointer return arguments may be NULL (useful for syntax checking). |
|
|
if ((sdup = tmp = strdup(s)) == NULL) |
if ((sdup = tmp = strdup(s)) == NULL) |
return -1; |
return -1; |
/* Extract optional username */ |
/* Extract optional username */ |
if ((cp = strchr(tmp, '@')) != NULL) { |
if ((cp = strrchr(tmp, '@')) != NULL) { |
*cp = '\0'; |
*cp = '\0'; |
if (*tmp == '\0') |
if (*tmp == '\0') |
goto out; |
goto out; |
|
|
return ret; |
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 */ |
/* function to assist building execv() arguments */ |
void |
void |
addargs(arglist *args, char *fmt, ...) |
addargs(arglist *args, char *fmt, ...) |
|
|
snprintf(env[i], strlen(name) + 1 + strlen(value) + 1, "%s=%s", name, value); |
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; |
|
} |