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

#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 */
}