/** * 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 . */ #include "address.h" #include #include #include #include #include #include #include #include #include #include #include #include #include /** * 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); }