/**
* mds — A micro-display server
* Copyright © 2014, 2015, 2016 Mattias Andrée (maandree@member.fsf.org)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "address.h"
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netdb.h>
#include <netinet/ip.h>
#include <limits.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <alloca.h>
#include <libmdsserver/config.h>
/**
* Check whether a string is a radix-10 non-negative integer
*
* @param str The string
* @return Whether a string is a radix-10 non-negative integer
*/
__attribute__((nonnull))
static int is_pzinteger(const char* restrict str)
{
for (; *str; str++)
if (('0' > *str) || (*str > '9'))
return 0;
return 1;
}
/**
* Set the socket address, with the address family `AF_UNIX`
*
* @param out_address Output parameter for the socket address
* @param pathlen Pointer to a variable where the length of the pathname will be stored
* @param format Formatting string for the pathname (should end with "%zn")
* @param ... Formatting arguments for the pathname (should end with `pathlen`)
* @return Zero on success, -1 on error, `errno`
* will have been set accordinly on error
*
* @throws ENOMEM Out of memory. Possibly, the application hit the
* RLIMIT_AS or RLIMIT_DATA limit described in getrlimit(2).
* @throws ENAMETOOLONG The filename of the target socket is too long
*/
__attribute__((nonnull, format(gnu_printf, 3, 4)))
static int set_af_unix(struct sockaddr** restrict out_address, ssize_t* pathlen, const char* format, ...)
#define set_af_unix(a, f, ...) ((set_af_unix)(a, &pathlen, f "%zn", __VA_ARGS__, &pathlen))
{
struct sockaddr_un* address;
size_t maxlen;
va_list args;
address = malloc(sizeof(struct sockaddr_un));
*out_address = (struct sockaddr*)address;
if (address == NULL)
return -1;
maxlen = sizeof(address->sun_path) / sizeof(address->sun_path[0]);
va_start(args, format);
address->sun_family = AF_UNIX;
vsnprintf(address->sun_path, maxlen, format, args);
va_end(args);
if ((size_t)*pathlen > maxlen)
return errno = ENAMETOOLONG, -1;
return 0;
}
/**
* Set the socket address, with either of the address
* families `AF_INET` and `AF_INET6`
*
* @param out_address Output parameter for the socket address
* @param out_address_len Output parameter for the socket address's allocation size
* @param out_gai_error Output parameter for error at `getaddrinfo`
* @param out_domain Output parameter for the protocol famility,
* unused unless the address familiy is unknown
* @param address_family The address family, `AF_UNSPEC` if unknown
* @param host The host
* @param port The address
* @return Zero on success, -1 on error, `errno` will have been
* set accordinly on error, `*out_gai_error` may be set
* on error and zero returned
*
* @throws ENOMEM Out of memory. Possibly, the application hit the
* RLIMIT_AS or RLIMIT_DATA limit described in getrlimit(2).
*/
__attribute__((nonnull))
static int set_af_inet(struct sockaddr** restrict out_address, socklen_t* restrict out_address_len,
int* restrict out_gai_error, int* restrict out_domain, int address_family,
const char* restrict host, const char* restrict port)
{
struct addrinfo hints;
struct addrinfo* result;
int saved_errno;
memset(&hints, 0, sizeof(hints));
hints.ai_family = address_family;
*out_gai_error = getaddrinfo(host, port, &hints, &result);
if (*out_gai_error)
return 0;
*out_address = malloc(result->ai_addrlen);
if (*out_address == NULL)
return saved_errno = errno, freeaddrinfo(result), errno = saved_errno, -1;
memcpy(*out_address, result->ai_addr, result->ai_addrlen);
*out_address_len = result->ai_addrlen;
*out_domain =
result->ai_family == AF_UNSPEC ? PF_UNSPEC :
result->ai_family == AF_INET ? PF_INET :
result->ai_family == AF_INET6 ? PF_INET6 :
result->ai_family;
freeaddrinfo(result);
return 0;
}
/**
* Parse a display address string
*
* @param display The address in MDS_DISPLAY-formatting, must not be `NULL`
* @param address Output parameter for parsed address, must not be `NULL`
* @return Zero on success, even if parsing failed, -1 on error,
* `errno` will have been set accordinly on error
*
* @throws ENOMEM Out of memory. Possibly, the application hit the
* RLIMIT_AS or RLIMIT_DATA limit described in getrlimit(2).
* @throws ENAMETOOLONG The filename of the target socket is too long
*/
int libmds_parse_display_address(const char* restrict display, libmds_display_address_t* restrict address)
{
ssize_t pathlen = 0;
char* host;
char* port;
char* params;
size_t i = 0;
char c;
int esc = 0;
int colesc = 0;
address->domain = -1;
address->type = -1;
address->protocol = -1;
address->address = NULL;
address->address_len = 0;
address->gai_error = 0;
if (strchr(display, ':') == NULL)
return 0;
if (*display == ':')
{
address->domain = PF_UNIX;
address->type = SOCK_STREAM;
address->protocol = 0;
address->address_len = sizeof(struct sockaddr_un);
if (strstr(display, ":file:") == display)
return set_af_unix(&(address->address), "%s", display + 6);
else if (display[1] && is_pzinteger(display + 1))
return set_af_unix(&(address->address), "%s/%s.socket",
MDS_RUNTIME_ROOT_DIRECTORY, display + 1);
return 0;
}
host = alloca((strlen(display) + 1) * sizeof(char));
if (*display == '[')
colesc = 1, i++;
for (; (c = display[i]); i++)
if (esc) host[pathlen++] = c, esc = 0;
else if (c == '\\') esc = 1;
else if (c == ']')
if (colesc) { i++; break; }
else host[pathlen++] = c;
else if (c == ':')
if (colesc) host[pathlen++] = c;
else break;
else host[pathlen++] = c;
if (esc || (display[i++] != ':'))
return 0;
host[pathlen++] = '\0';
port = host + pathlen;
memcpy(port, display + i, (strlen(display) + 1 - i) * sizeof(char));
params = strchr(port, ':');
if (params != NULL)
*params++ = '\0';
#define param_test(f, n, a, b, c) \
((n >= 3) && (!strcasecmp(params, a ":" b ":" c))) || \
((n >= 2) && (!strcasecmp(params, a ":" b))) || \
((n >= 1) && (!strcasecmp(params, a))) || \
(f == NULL ? 0 : !strcasecmp(params, f))
#define set(d, t, p) \
address->domain = (d), address->type = (t), address->protocol = (p)
if (params == NULL) set(PF_UNSPEC, SOCK_STREAM, IPPROTO_TCP);
else if (param_test("ip/tcp", 1, "ip", "stream", "tcp")) set(PF_UNSPEC, SOCK_STREAM, IPPROTO_TCP);
else if (param_test("ipv4/tcp", 1, "ipv4", "stream", "tcp")) set(PF_INET, SOCK_STREAM, IPPROTO_TCP);
else if (param_test("ipv6/tcp", 1, "ipv6", "stream", "tcp")) set(PF_INET6, SOCK_STREAM, IPPROTO_TCP);
else if (param_test("inet/tcp", 1, "inet", "stream", "tcp")) set(PF_INET, SOCK_STREAM, IPPROTO_TCP);
else if (param_test("inet6/tcp", 1, "inet6", "stream", "tcp")) set(PF_INET6, SOCK_STREAM, IPPROTO_TCP);
else
return 0;
#undef set
#undef param_test
return set_af_inet(&(address->address), &(address->address_len),
&(address->gai_error), &(address->domain),
address->domain == PF_UNSPEC ? AF_UNSPEC :
address->domain == PF_INET ? AF_INET :
address->domain == PF_INET6 ? AF_INET6 :
address->domain, host, port);
}