You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
455 lines
9.5 KiB
C
455 lines
9.5 KiB
C
#include "utils.h"
|
|
|
|
long long
|
|
strtonum(const char *numstr, long long minval, long long maxval,
|
|
const char **errstrp)
|
|
{
|
|
long long ll = 0;
|
|
char *ep;
|
|
int error = 0;
|
|
struct errval {
|
|
const char *errstr;
|
|
int err;
|
|
} ev[4] = {
|
|
{ NULL, 0 },
|
|
{ "invalid", EINVAL },
|
|
{ "too small", ERANGE },
|
|
{ "too large", ERANGE },
|
|
};
|
|
|
|
ev[0].err = errno;
|
|
errno = 0;
|
|
if (minval > maxval)
|
|
error = INVALID;
|
|
else {
|
|
ll = strtoll(numstr, &ep, 10);
|
|
if (numstr == ep || *ep != '\0')
|
|
error = INVALID;
|
|
else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval)
|
|
error = TOOSMALL;
|
|
else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval)
|
|
error = TOOLARGE;
|
|
}
|
|
if (errstrp != NULL)
|
|
*errstrp = ev[error].errstr;
|
|
errno = ev[error].err;
|
|
if (error)
|
|
ll = 0;
|
|
|
|
return (ll);
|
|
}
|
|
|
|
void *
|
|
xmalloc(size_t size)
|
|
{
|
|
void *ptr;
|
|
|
|
if (size == 0)
|
|
fprintf(stderr, "xmalloc: zero size");
|
|
ptr = malloc(size);
|
|
if (ptr == NULL)
|
|
fprintf(stderr, "xmalloc: out of memory (allocating %zu bytes)", size);
|
|
return ptr;
|
|
}
|
|
|
|
/*
|
|
* 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];
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
|
|
char *
|
|
cleanhostname(char *host)
|
|
{
|
|
if (*host == '[' && host[strlen(host) - 1] == ']') {
|
|
host[strlen(host) - 1] = '\0';
|
|
return (host + 1);
|
|
} else
|
|
return host;
|
|
}
|
|
|
|
char *
|
|
xstrdup(const char *str)
|
|
{
|
|
size_t len;
|
|
char *cp;
|
|
|
|
len = strlen(str) + 1;
|
|
cp = xmalloc(len);
|
|
strlcpy(cp, str, len);
|
|
return cp;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
int
|
|
parse_ssh_uri(const char *uri, char **userp, char **hostp, int *portp)
|
|
{
|
|
char *path;
|
|
int r;
|
|
|
|
r = parse_uri("ssh", uri, userp, hostp, portp, &path);
|
|
if (r == 0 && path != NULL)
|
|
r = -1; /* path not allowed */
|
|
return r;
|
|
}
|
|
|
|
/*
|
|
* 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 = hpdelim2(&tmp, NULL)) == 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;
|
|
}
|
|
|
|
/*
|
|
* Copy string src to buffer dst of size dsize. At most dsize-1
|
|
* chars will be copied. Always NUL terminates (unless dsize == 0).
|
|
* Returns strlen(src); if retval >= dsize, truncation occurred.
|
|
*/
|
|
size_t
|
|
strlcpy(char * __restrict dst, const char * __restrict src, size_t dsize)
|
|
{
|
|
const char *osrc = src;
|
|
size_t nleft = dsize;
|
|
|
|
/* Copy as many bytes as will fit. */
|
|
if (nleft != 0) {
|
|
while (--nleft != 0) {
|
|
if ((*dst++ = *src++) == '\0')
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Not enough room in dst, add NUL and traverse rest of src. */
|
|
if (nleft == 0) {
|
|
if (dsize != 0)
|
|
*dst = '\0'; /* NUL-terminate dst */
|
|
while (*src++)
|
|
;
|
|
}
|
|
|
|
return(src - osrc - 1); /* count does not include NUL */
|
|
}
|