aboutsummaryrefslogblamecommitdiffstats
path: root/src/libmdsclient/address.c
blob: 2aac894f4311954da352f504d0e3a2708eb684eb (plain) (tree)
1
2
3

                                 
                                                                       















                                                                        

                       
                   
                  
                       
                   



                   

                   
 









                                                                    

                                      
 



                                             



   
                                                            
   
                                                                









                                                                                              


                                                                                              
 





















                                                                          
 


   


                                                     










                                                                                      



                                                                             



                                                                                        
 
























                                                                                          



   






                                                                              


                                                                                   
   

                                                                                                      
 



















































































                                                                                                                

                 
 






                                                                        
/**
 * mds — A micro-display server
 * Copyright © 2014, 2015, 2016, 2017  Mattias Andrée (m@maandree.se)
 * 
 * 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
 */
static int __attribute__((nonnull))
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
 */
static int __attribute__((nonnull, format(gnu_printf, 3, 4)))
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)
		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).
 */
static int __attribute__((nonnull))
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)
		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, ':'))
		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)
		*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 ? 0 : !strcasecmp(params, f)))
#define set(d, t, p)\
	(address->domain = (d), address->type = (t), address->protocol = (p))

	if      (!params)                                              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);
}