Blob Blame History Raw
/*
 * Soft:        Keepalived is a failover program for the LVS project
 *              <www.linuxvirtualserver.org>. It monitor & manipulate
 *              a loadbalanced server pool using multi-layer checks.
 *
 * Part:        General program utils.
 *
 * Author:      Alexandre Cassen, <acassen@linux-vs.org>
 *
 *              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.
 *
 *              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
 *              2 of the License, or (at your option) any later version.
 *
 * Copyright (C) 2001-2017 Alexandre Cassen, <acassen@gmail.com>
 */

#include "config.h"

/* System includes */
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/utsname.h>
#include <sys/stat.h>
#include <stdint.h>
#include <errno.h>
#include <sys/prctl.h>
#if defined _WITH_LVS_ || defined _HAVE_LIBIPSET_
#include <sys/wait.h>
#endif
#ifdef _WITH_PERF_
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/epoll.h>
#include <sys/inotify.h>
#endif

#ifdef _WITH_STACKTRACE_
#include <sys/stat.h>
#include <execinfo.h>
#include <memory.h>
#endif

/* Local includes */
#include "utils.h"
#include "memory.h"
#include "utils.h"
#include "signals.h"
#include "bitops.h"
#include "parser.h"
#include "logger.h"
#include "process.h"

/* global vars */
unsigned long debug = 0;
mode_t umask_val = S_IXUSR | S_IRWXG | S_IRWXO;

#ifdef _EINTR_DEBUG_
bool do_eintr_debug;
#endif

/* Display a buffer into a HEXA formated output */
void
dump_buffer(const char *buff, size_t count, FILE* fp, int indent)
{
	size_t i, j, c;
	bool printnext = true;

	if (count % 16)
		c = count + (16 - count % 16);
	else
		c = count;

	for (i = 0; i < c; i++) {
		if (printnext) {
			printnext = false;
			fprintf(fp, "%*s%.4zu ", indent, "", i & 0xffff);
		}
		if (i < count)
			fprintf(fp, "%3.2x", (unsigned char)buff[i] & 0xff);
		else
			fprintf(fp, "   ");
		if (!((i + 1) % 8)) {
			if ((i + 1) % 16)
				fprintf(fp, " -");
			else {
				fprintf(fp, "   ");
				for (j = i - 15; j <= i; j++)
					if (j < count) {
						if ((buff[j] & 0xff) >= 0x20
						    && (buff[j] & 0xff) <= 0x7e)
							fprintf(fp, "%c",
							       buff[j] & 0xff);
						else
							fprintf(fp, ".");
					} else
						fprintf(fp, " ");
				fprintf(fp, "\n");
				printnext = true;
			}
		}
	}
}

#ifdef _CHECKSUM_DEBUG_
void
log_buffer(const char *msg, const void *buff, size_t count)
{
	char op_buf[60];	// Probably 56 really
	const unsigned char *bufp = buff;
	char *ptr;
	size_t offs = 0;
	unsigned i;

	log_message(LOG_INFO, "%s - len %zu", msg, count);

	while (offs < count) {
		ptr = op_buf;
		ptr += snprintf(ptr, op_buf + sizeof(op_buf) - ptr, "%4.4zx ", offs);

		for (i = 0; i < 16 && offs < count; i++) {
			if (i == 8)
				*ptr++ = ' ';
			ptr += snprintf(ptr, op_buf + sizeof(op_buf) - ptr, " %2.2x", bufp[offs++]);
		}

		log_message(LOG_INFO, "%s", op_buf);
	}
}
#endif

#ifdef _WITH_STACKTRACE_
void
write_stacktrace(const char *file_name, const char *str)
{
	int fd;
	void *buffer[100];
	unsigned int nptrs;
	unsigned int i;
	char **strs;

	nptrs = backtrace(buffer, 100);
	if (file_name) {
		fd = open(file_name, O_WRONLY | O_APPEND | O_CREAT | O_NOFOLLOW, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
		if (str)
			dprintf(fd, "%s\n", str);
		backtrace_symbols_fd(buffer, nptrs, fd);
		if (write(fd, "\n", 1) != 1) {
			/* We don't care, but this stops a warning on Ubuntu */
		}
		close(fd);
	} else {
		if (str)
			log_message(LOG_INFO, "%s", str);
		strs = backtrace_symbols(buffer, nptrs);
		if (strs == NULL) {
			log_message(LOG_INFO, "Unable to get stack backtrace");
			return;
		}

		/* We don't need the call to this function, or the first two entries on the stack */
		nptrs -= 2;
		for (i = 1; i < nptrs; i++)
			log_message(LOG_INFO, "  %s", strs[i]);
		free(strs);	/* malloc'd by backtrace_symbols */
	}
}
#endif

const char *
make_file_name(const char *name, const char *prog, const char *namespace, const char *instance)
{
	const char *extn_start;
	const char *dir_end;
	size_t len;
	char *file_name;

	if (!name)
		return NULL;

	len = strlen(name);
	if (prog)
		len += strlen(prog) + 1;
	if (namespace)
		len += strlen(namespace) + 1;
	if (instance)
		len += strlen(instance) + 1;

	file_name = MALLOC(len + 1);
	dir_end = strrchr(name, '/');
	extn_start = strrchr(dir_end ? dir_end : name, '.');
	strncpy(file_name, name, extn_start ? (size_t)(extn_start - name) : len);

	if (prog) {
		strcat(file_name, "_");
		strcat(file_name, prog);
	}
	if (namespace) {
		strcat(file_name, "_");
		strcat(file_name, namespace);
	}
	if (instance) {
		strcat(file_name, "_");
		strcat(file_name, instance);
	}
	if (extn_start)
		strcat(file_name, extn_start);

	return file_name;
}

void
set_process_name(const char *name)
{
	if (!name)
		name = "keepalived";

	if (prctl(PR_SET_NAME, name))
		log_message(LOG_INFO, "Failed to set process name '%s'", name);
}

#ifdef _WITH_PERF_
void
run_perf(const char *process, const char *network_namespace, const char *instance_name)
{
	int ret;
	pid_t pid;
	char *orig_name = NULL;
	const char *new_name;
	const char *perf_name = "perf.data";
	int in = -1;
	int ep = -1;

	do {
		orig_name = MALLOC(PATH_MAX);
		if (!getcwd(orig_name, PATH_MAX)) {
			log_message(LOG_INFO, "Unable to get cwd");
			break;
		}

#ifdef IN_CLOEXEC
		in = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
#else
		if ((in = inotify_init()) != -1) {
			fcntl(in, F_SETFD, FD_CLOEXEC | fcntl(n, F_GETFD));
			fcntl(in, F_SETFL, O_NONBLOCK | fcntl(n, F_GETFL));
		}
#endif
		if (in == -1) {
			log_message(LOG_INFO, "inotify_init failed %d - %m", errno);
			break;
		}

		if (inotify_add_watch(in, orig_name, IN_CREATE) == -1) {
			log_message(LOG_INFO, "inotify_add_watch of %s failed %d - %m", orig_name, errno);
			break;
		}

		pid = fork();

		if (pid == -1) {
			log_message(LOG_INFO, "fork() for perf failed");
			break;
		}

		/* Child */
		if (!pid) {
			char buf[PID_MAX_DIGITS + 1];

			snprintf(buf, sizeof buf, "%d", getppid());
			execlp("perf", "perf", "record", "-p", buf, "-q", "-g", "--call-graph", "fp", NULL);
			exit(0);
		}

		/* Parent */
		char buf[sizeof(struct inotify_event) + NAME_MAX + 1];
		struct inotify_event *ie = (struct inotify_event*)buf;
		struct epoll_event ee = { .events = EPOLLIN, .data.fd = in };

		if ((ep = epoll_create(1)) == -1) {
			log_message(LOG_INFO, "perf epoll_create failed errno %d - %m", errno);
			break;
		}

		if (epoll_ctl(ep, EPOLL_CTL_ADD, in, &ee) == -1) {
			log_message(LOG_INFO, "perf epoll_ctl failed errno %d - %m", errno);
			break;
		}

		do {
			ret = epoll_wait(ep, &ee, 1, 1000);
			if (ret == 0) {
				log_message(LOG_INFO, "Timed out waiting for creation of %s", perf_name);
				break;
			}
			else if (ret == -1) {
				if (check_EINTR(errno))
					continue;

				log_message(LOG_INFO, "perf epoll returned errno %d - %m", errno);
				break;
			}

			ret = read(in, buf, sizeof(buf));
			if (ret == -1) {
				if (check_EINTR(errno))
					continue;

				log_message(LOG_INFO, "perf inotify read returned errno %d %m", errno);
				break;
			}
			if (ret < (int)sizeof(*ie)) {
				log_message(LOG_INFO, "read returned %d", ret);
				break;
			}
			if (!(ie->mask & IN_CREATE)) {
				log_message(LOG_INFO, "mask is 0x%x", ie->mask);
				continue;
			}
			if (!ie->len) {
				log_message(LOG_INFO, "perf inotify read returned no len");
				continue;
			}

			if (strcmp(ie->name, perf_name))
				continue;

			/* Rename the /perf.data file */
			strcat(orig_name, perf_name);
			new_name = make_file_name(orig_name, process,
#if HAVE_DECL_CLONE_NEWNET
							network_namespace,
#else
							NULL,
#endif
							instance_name);

			if (rename(orig_name, new_name))
				log_message(LOG_INFO, "Rename %s to %s failed - %m (%d)", orig_name, new_name, errno);

			FREE_CONST(new_name);
		} while (false);
	} while (false);

	if (ep != -1)
		close(ep);
	if (in != -1)
		close(in);
	if (orig_name)
		FREE(orig_name);
}
#endif

/* Compute a checksum */
uint16_t
in_csum(const uint16_t *addr, size_t len, uint32_t csum, uint32_t *acc)
{
	register size_t nleft = len;
	const uint16_t *w = addr;
	register uint16_t answer;
	register uint32_t sum = csum;

	/*
	 *  Our algorithm is simple, using a 32 bit accumulator (sum),
	 *  we add sequential 16 bit words to it, and at the end, fold
	 *  back all the carry bits from the top 16 bits into the lower
	 *  16 bits.
	 */
	while (nleft > 1) {
		sum += *w++;
		nleft -= 2;
	}

	/* mop up an odd byte, if necessary */
	if (nleft == 1)
		sum += htons(*(const u_char *)w << 8);

	if (acc)
		*acc = sum;

	/*
	 * add back carry outs from top 16 bits to low 16 bits
	 */
	sum = (sum >> 16) + (sum & 0xffff);	/* add hi 16 to low 16 */
	sum += (sum >> 16);			/* add carry */
	answer = (~sum & 0xffff);		/* truncate to 16 bits */
	return (answer);
}

/* IP network to ascii representation */
const char *
inet_ntop2(uint32_t ip)
{
	static char buf[16];
	const unsigned char *bytep;

	bytep = (const unsigned char *)&ip;
	sprintf(buf, "%d.%d.%d.%d", bytep[0], bytep[1], bytep[2], bytep[3]);
	return buf;
}

#ifdef _INCLUDE_UNUSED_CODE_
/*
 * IP network to ascii representation. To use
 * for multiple IP address convertion into the same call.
 */
char *
inet_ntoa2(uint32_t ip, char *buf)
{
	const unsigned char *bytep;

	bytep = (const unsigned char *)&ip;
	sprintf(buf, "%d.%d.%d.%d", bytep[0], bytep[1], bytep[2], bytep[3]);
	return buf;
}
#endif

/* IP string to network range representation. */
bool
inet_stor(const char *addr, uint32_t *range_end)
{
	const char *cp;
	char *endptr;
	unsigned long range;
	int family = strchr(addr, ':') ? AF_INET6 : AF_INET;
	const char *warn = "";

#ifndef _STRICT_CONFIG_
	if (!__test_bit(CONFIG_TEST_BIT, &debug))
		warn = "WARNING - ";
#endif

	/* Return UINT32_MAX to indicate no range */
	if (!(cp = strchr(addr, '-'))) {
		*range_end = UINT32_MAX;
		return true;
	}

	errno = 0;
	range = strtoul(cp + 1, &endptr, family == AF_INET6 ? 16 : 10);
	*range_end = range;

	if (*endptr)
		report_config_error(CONFIG_INVALID_NUMBER, "%sVirtual server group range '%s' has extra characters at end '%s'", warn, addr, endptr);
	else if (errno == ERANGE ||
		 (family == AF_INET6 && range > 0xffff) ||
		 (family == AF_INET && range > 255)) {
		report_config_error(CONFIG_INVALID_NUMBER, "Virtual server group range '%s' end '%s' too large", addr, cp + 1);

		/* Indicate error */
		return false;
	}
	else
		return true;

#ifdef _STRICT_CONFIG_
	return false;
#else
	return !__test_bit(CONFIG_TEST_BIT, &debug);
#endif
}

/* Domain to sockaddr_storage */
int
domain_stosockaddr(const char *domain, const char *port, struct sockaddr_storage *addr)
{
	struct addrinfo *res = NULL;
	unsigned port_num;

	if (port) {
		if (!read_unsigned(port, &port_num, 1, 65535, true)) {
			addr->ss_family = AF_UNSPEC;
			return -1;
		}
	}

	if (getaddrinfo(domain, NULL, NULL, &res) != 0 || !res) {
		addr->ss_family = AF_UNSPEC;
		return -1;
	}

	addr->ss_family = (sa_family_t)res->ai_family;

	if (addr->ss_family == AF_INET6) {
		struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr;
		*addr6 = *(struct sockaddr_in6 *)res->ai_addr;
		if (port)
			addr6->sin6_port = htons(port_num);
	} else {
		struct sockaddr_in *addr4 = (struct sockaddr_in *)addr;
		*addr4 = *(struct sockaddr_in *)res->ai_addr;
		if (port)
			addr4->sin_port = htons(port_num);
	}

	freeaddrinfo(res);

	return 0;
}

/* IP string to sockaddr_storage
 *   return value is "error". */
bool
inet_stosockaddr(const char *ip, const char *port, struct sockaddr_storage *addr)
{
	void *addr_ip;
	const char *cp;
	char *ip_str = NULL;
	unsigned port_num;
	int res;

	addr->ss_family = (strchr(ip, ':')) ? AF_INET6 : AF_INET;

	if (port) {
		if (!read_unsigned(port, &port_num, 1, 65535, true)) {
			addr->ss_family = AF_UNSPEC;
			return true;
		}
	}

	if (addr->ss_family == AF_INET6) {
		struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) addr;
		if (port)
			addr6->sin6_port = htons(port_num);
		addr_ip = &addr6->sin6_addr;
	} else {
		struct sockaddr_in *addr4 = (struct sockaddr_in *) addr;
		if (port)
			addr4->sin_port = htons(port_num);
		addr_ip = &addr4->sin_addr;
	}

	/* remove range and mask stuff */
	if ((cp = strchr(ip, '-')) ||
	    (cp = strchr(ip, '/')))
		ip_str = STRNDUP(ip, cp - ip);

	res = inet_pton(addr->ss_family, ip_str ? ip_str : ip, addr_ip);

	if (ip_str)
		FREE(ip_str);

	if (!res) {
		addr->ss_family = AF_UNSPEC;
		return true;
	}

	return false;
}

/* IPv4 to sockaddr_storage */
void
inet_ip4tosockaddr(const struct in_addr *sin_addr, struct sockaddr_storage *addr)
{
	struct sockaddr_in *addr4 = (struct sockaddr_in *) addr;
	addr4->sin_family = AF_INET;
	addr4->sin_addr = *sin_addr;
}

/* IPv6 to sockaddr_storage */
void
inet_ip6tosockaddr(const struct in6_addr *sin_addr, struct sockaddr_storage *addr)
{
	struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) addr;
	addr6->sin6_family = AF_INET6;
	addr6->sin6_addr = *sin_addr;
}

/* Check address, possibly with mask, is valid */
bool
check_valid_ipaddress(const char *str, bool allow_subnet_mask)
{
	int family;
	unsigned long prefixlen;
	const char *p;
	char *endptr;
	union {
		struct in_addr in;
		struct in6_addr in6;
	} addr;
	int res;
	const char *str_dup = NULL;

	if (!strchr(str, ':') && !strchr(str, '.'))
		return false;

	family = (strchr(str, ':')) ? AF_INET6 : AF_INET;

	if (allow_subnet_mask)
		p = strchr(str, '/');
	else
		p = NULL;

	if (p) {
		if (!p[1])
			return false;
		prefixlen = strtoul(p + 1, &endptr, 10);
		if (*endptr || prefixlen > (family == AF_INET6 ? 128 : 32))
			return false;
		str_dup = STRNDUP(str, p - str);
	}

	res = inet_pton(family, str_dup ? str_dup : str, &addr);

	if (str_dup)
		FREE_CONST(str_dup);

	return res;
}

/* IP network to string representation */
static char *
inet_sockaddrtos2(const struct sockaddr_storage *addr, char *addr_str)
{
	const void *addr_ip;

	if (addr->ss_family == AF_INET6) {
		const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *) addr;
		addr_ip = &addr6->sin6_addr;
	} else {
		const struct sockaddr_in *addr4 = (const struct sockaddr_in *) addr;
		addr_ip = &addr4->sin_addr;
	}

	if (!inet_ntop(addr->ss_family, addr_ip, addr_str, INET6_ADDRSTRLEN))
		return NULL;

	return addr_str;
}

const char *
inet_sockaddrtos(const struct sockaddr_storage *addr)
{
	static char addr_str[INET6_ADDRSTRLEN];
	inet_sockaddrtos2(addr, addr_str);
	return addr_str;
}

uint16_t __attribute__ ((pure))
inet_sockaddrport(const struct sockaddr_storage *addr)
{
	if (addr->ss_family == AF_INET6) {
		const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *) addr;
		return addr6->sin6_port;
	}

	/* Note: this might be AF_UNSPEC if it is the sequence number of
	 * a virtual server in a virtual server group */
	const struct sockaddr_in *addr4 = (const struct sockaddr_in *) addr;
	return addr4->sin_port;
}

void
inet_set_sockaddrport(struct sockaddr_storage *addr, uint16_t port)
{
	if (addr->ss_family == AF_INET6) {
		struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) addr;
		addr6->sin6_port = port;
	} else {
		struct sockaddr_in *addr4 = (struct sockaddr_in *) addr;
		addr4->sin_port = port;
	}
}

const char *
inet_sockaddrtopair(const struct sockaddr_storage *addr)
{
	char addr_str[INET6_ADDRSTRLEN];
	static char ret[sizeof(addr_str) + 8];	/* '[' + addr_str + ']' + ':' + 'nnnnn' */

	inet_sockaddrtos2(addr, addr_str);
	snprintf(ret, sizeof(ret), "[%s]:%d"
		, addr_str
		, ntohs(inet_sockaddrport(addr)));
	return ret;
}

char *
inet_sockaddrtotrio_r(const struct sockaddr_storage *addr, uint16_t proto, char *buf)
{
	char addr_str[INET6_ADDRSTRLEN];
	const char *proto_str =
			proto == IPPROTO_TCP ? "tcp" :
			proto == IPPROTO_UDP ? "udp" :
			proto == IPPROTO_SCTP ? "sctp" :
			proto == 0 ? "none" : "?";

	inet_sockaddrtos2(addr, addr_str);
	snprintf(buf, SOCKADDRTRIO_STR_LEN, "[%s]:%s:%d", addr_str, proto_str,
		 ntohs(inet_sockaddrport(addr)));
	return buf;
}

const char *
inet_sockaddrtotrio(const struct sockaddr_storage *addr, uint16_t proto)
{
	static char ret[SOCKADDRTRIO_STR_LEN];

	inet_sockaddrtotrio_r(addr, proto, ret);

	return ret;
}

uint32_t __attribute__ ((pure))
inet_sockaddrip4(const struct sockaddr_storage *addr)
{
	if (addr->ss_family != AF_INET)
		return 0xffffffff;

	return ((const struct sockaddr_in *) addr)->sin_addr.s_addr;
}

int
inet_sockaddrip6(const struct sockaddr_storage *addr, struct in6_addr *ip6)
{
	if (addr->ss_family != AF_INET6)
		return -1;

	*ip6 = ((const struct sockaddr_in6 *) addr)->sin6_addr;
	return 0;
}

/* IPv6 address compare */
int __attribute__ ((pure))
inet_inaddrcmp(const int family, const void *a, const void *b)
{
	int64_t addr_diff;

	if (family == AF_INET) {
		addr_diff = (int64_t)ntohl(*((const uint32_t *) a)) - (int64_t)ntohl(*((const uint32_t *) b));
		if (addr_diff > 0)
			return 1;
		if (addr_diff < 0)
			return -1;
		return 0;
	}

	if (family == AF_INET6) {
		int i;

		for (i = 0; i < 4; i++ ) {
			addr_diff = (int64_t)ntohl(((const uint32_t *) (a))[i]) - (int64_t)ntohl(((const uint32_t *) (b))[i]);
			if (addr_diff > 0)
				return 1;
			if (addr_diff < 0)
				return -1;
		}
		return 0;
	}

	return -2;
}

int  __attribute__ ((pure))
inet_sockaddrcmp(const struct sockaddr_storage *a, const struct sockaddr_storage *b)
{
	if (a->ss_family != b->ss_family)
		return -2;

	if (a->ss_family == AF_INET)
		return inet_inaddrcmp(a->ss_family,
				      &((const struct sockaddr_in *) a)->sin_addr,
				      &((const struct sockaddr_in *) b)->sin_addr);
	if (a->ss_family == AF_INET6)
		return inet_inaddrcmp(a->ss_family,
				      &((const struct sockaddr_in6 *) a)->sin6_addr,
				      &((const struct sockaddr_in6 *) b)->sin6_addr);
	return 0;
}


#ifdef _INCLUDE_UNUSED_CODE_
/*
 * IP string to network representation
 * Highly inspired from Paul Vixie code.
 */
int
inet_ston(const char *addr, uint32_t *dst)
{
	static char digits[] = "0123456789";
	int saw_digit, octets, ch;
	u_char tmp[INADDRSZ], *tp;

	saw_digit = 0;
	octets = 0;
	*(tp = tmp) = 0;

	while ((ch = *addr++) != '\0' && ch != '/' && ch != '-') {
		const char *pch;
		if ((pch = strchr(digits, ch)) != NULL) {
			u_int new = *tp * 10 + (pch - digits);
			if (new > 255)
				return 0;
			*tp = new;
			if (!saw_digit) {
				if (++octets > 4)
					return 0;
				saw_digit = 1;
			}
		} else if (ch == '.' && saw_digit) {
			if (octets == 4)
				return 0;
			*++tp = 0;
			saw_digit = 0;
		} else
			return 0;
	}

	if (octets < 4)
		return 0;

	memcpy(dst, tmp, INADDRSZ);
	return 1;
}

/*
 * Return broadcast address from network and netmask.
 */
uint32_t
inet_broadcast(uint32_t network, uint32_t netmask)
{
	return 0xffffffff - netmask + network;
}

/*
 * Convert CIDR netmask notation to long notation.
 */
uint32_t
inet_cidrtomask(uint8_t cidr)
{
	uint32_t mask = 0;
	int b;

	for (b = 0; b < cidr; b++)
		mask |= (1 << (31 - b));
	return ntohl(mask);
}
#endif

void
format_mac_buf(char *op, size_t op_len, const unsigned char *addr, size_t addr_len)
{
	size_t i;
	char *buf_end = op + op_len;

	/* If there is no address, clear the op buffer */
	if (!addr_len && op_len) {
		op[0] = '\0';
		return;
	}

	for (i = 0; i < addr_len; i++)
		op += snprintf(op, buf_end - op, "%.2x%s",
		      addr[i], i < addr_len -1 ? ":" : "");
}

/* Getting localhost official canonical name */
const char * __attribute__((malloc))
get_local_name(void)
{
	struct utsname name;
	struct addrinfo hints, *res = NULL;
	char *canonname = NULL;

	memset(&hints, 0, sizeof(struct addrinfo));
	hints.ai_flags = AI_CANONNAME;

	if (uname(&name) < 0)
		return NULL;

	if (getaddrinfo(name.nodename, NULL, &hints, &res) != 0)
		return NULL;

	if (res && res->ai_canonname)
		canonname = STRDUP(res->ai_canonname);

	freeaddrinfo(res);

	return canonname;
}

/* String compare with NULL string handling */
bool __attribute__ ((pure))
string_equal(const char *str1, const char *str2)
{
	if (!str1 && !str2)
		return true;
	if (!str1 != !str2)
		return false;

	return !strcmp(str1, str2);
}

/* Convert an integer into a string */
int
integer_to_string(const int value, char *str, size_t size)
{
	int i, len = 0, t = value, s = size;

	for (i = value; i; i/=10) {
		if (++len > s)
			return -1;
	}

	for (i = 0; i < len; i++,t/=10)
		str[len - (i + 1)] = t % 10 + '0';

	return len;
}

/* We need to use O_NOFOLLOW if opening a file for write, so that a non privileged user can't
 * create a symbolic link from the path to a system file and cause a system file to be overwritten. */
FILE *fopen_safe(const char *path, const char *mode)
{
	int fd;
	FILE *file;
#ifdef ENABLE_LOG_FILE_APPEND
	int flags = O_NOFOLLOW | O_CREAT | O_CLOEXEC;
#endif
	int sav_errno;
	char file_tmp_name[PATH_MAX];

	if (mode[0] == 'r')
		return fopen(path, mode);

	if ((mode[0] != 'a' && mode[0] != 'w') ||
	    (mode[1] &&
	     (mode[1] != '+' || mode[2]))) {
		errno = EINVAL;
		return NULL;
	}

	if (mode[0] == 'w') {
		/* If we truncate an existing file, any non-privileged user who already has the file
		 * open would be able to read what we write, even though the file access mode is changed.
		 *
		 * If we unlink an existing file and the desired file is subsequently created via open,
		 * it leaves a window for someone else to create the same file between the unlink and the open.
		 *
		 * The solution is to create a temporary file that we will rename to the desired file name.
		 * Since the temporary file is created owned by root with the only file access permissions being
		 * owner read and write, no non root user will have access to the file. Further, the rename to
		 * the requested filename is atomic, and so there is no window when someone else could create
		 * another file of the same name.
		 */
		strcpy_safe(file_tmp_name, path);
		if (strlen(path) + 6 < sizeof(file_tmp_name))
			strcat(file_tmp_name, "XXXXXX");
		else
			strcpy(file_tmp_name + sizeof(file_tmp_name) - 6 - 1, "XXXXXX");
		fd = mkostemp(file_tmp_name, O_CLOEXEC);
	} else {
		/* Only allow append mode if debugging features requiring append are enabled. Since we
		 * can't unlink the file, there may be a non privileged user who already has the file open
		 * for read (e.g. tail -f). If these debug option aren't enabled, there is no potential
		 * security risk in that respect. */
#ifndef ENABLE_LOG_FILE_APPEND
		log_message(LOG_INFO, "BUG - shouldn't be opening file for append with current build options");
		errno = EINVAL;
		return NULL;
#else
		flags = O_NOFOLLOW | O_CREAT | O_CLOEXEC | O_APPEND;

		if (mode[1])
			flags |= O_RDWR;
		else
			flags |= O_WRONLY;

		fd = open(path, flags, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
#endif
	}

	if (fd == -1) {
		sav_errno = errno;
		log_message(LOG_INFO, "Unable to open '%s' - errno %d (%m)", path, errno);
		errno = sav_errno;
		return NULL;
	}

#ifndef ENABLE_LOG_FILE_APPEND
	/* Change file ownership to root */
	if (mode[0] == 'a' && fchown(fd, 0, 0)) {
		sav_errno = errno;
		log_message(LOG_INFO, "Unable to change file ownership of %s- errno %d (%m)", path, errno);
		close(fd);
		errno = sav_errno;
		return NULL;
	}
#endif

	/* Set file mode, default rw------- */
	if (fchmod(fd, (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) & ~umask_val)) {
		sav_errno = errno;
		log_message(LOG_INFO, "Unable to change file permission of %s - errno %d (%m)", path, errno);
		close(fd);
		errno = sav_errno;
		return NULL;
	}

	if (mode[0] == 'w') {
		/* Rename the temporary file to the one we want */
		if (rename(file_tmp_name, path)) {
			sav_errno = errno;
			log_message(LOG_INFO, "Failed to rename %s to %s - errno %d (%m)", file_tmp_name, path, errno);
			close(fd);
			errno = sav_errno;
			return NULL;
		}
	}

	file = fdopen (fd, "w");
	if (!file) {
		sav_errno = errno;
		log_message(LOG_INFO, "fdopen(\"%s\") failed - errno %d (%m)", path, errno);
		close(fd);
		errno = sav_errno;
		return NULL;
	}

	return file;
}

void
set_std_fd(bool force)
{
	int fd;

	if (force || __test_bit(DONT_FORK_BIT, &debug)) {
		fd = open("/dev/null", O_RDWR);
		if (fd != -1) {
			dup2(fd, STDIN_FILENO);
			dup2(fd, STDOUT_FILENO);
			dup2(fd, STDERR_FILENO);
			if (fd > STDERR_FILENO)
				close(fd);
		}
	}

	signal_fd_close(STDERR_FILENO+1);

	/* coverity[leaked_handle] */
}

void
close_std_fd(void)
{
	close(STDIN_FILENO);
	close(STDOUT_FILENO);
	close(STDERR_FILENO);
}

#if defined _WITH_VRRP_ || defined _WITH_BFD_
int
open_pipe(int pipe_arr[2])
{
	/* Open pipe */
#ifdef HAVE_PIPE2
	if (pipe2(pipe_arr, O_CLOEXEC | O_NONBLOCK) == -1)
#else
	if (pipe(pipe_arr) == -1)
#endif
		return -1;

#ifndef HAVE_PIPE2
	fcntl(pipe_arr[0], F_SETFL, O_NONBLOCK | fcntl(pipe_arr[0], F_GETFL));
	fcntl(pipe_arr[1], F_SETFL, O_NONBLOCK | fcntl(pipe_arr[1], F_GETFL));

	fcntl(pipe_arr[0], F_SETFD, FD_CLOEXEC | fcntl(pipe_arr[0], F_GETFD));
	fcntl(pipe_arr[1], F_SETFD, FD_CLOEXEC | fcntl(pipe_arr[1], F_GETFD));
#endif

	return 0;
}
#endif

/*
 * memcmp time constant variant.
 * Need to ensure compiler doesnt get too smart by optimizing generated asm code.
 */
__attribute__((optimize("O0"))) int
memcmp_constant_time(const void *s1, const void *s2, size_t n)
{
	const unsigned char *a, *b;
	unsigned char ret = 0;
	size_t i;

	for (i = 0, a = s1, b = s2; i < n; i++)
		ret |= (*a++ ^ *b++);

	return ret;
}

/*
 * Utility functions coming from Wensong code
 */

#if defined _WITH_LVS_ || defined _HAVE_LIBIPSET_
static char*
get_modprobe(void)
{
	int procfile;
	char *ret;
	ssize_t count;
	struct stat buf;

	ret = MALLOC(PATH_MAX);
	if (!ret)
		return NULL;

	procfile = open("/proc/sys/kernel/modprobe", O_RDONLY | O_CLOEXEC);
	if (procfile < 0) {
		FREE(ret);
		return NULL;
	}

	count = read(procfile, ret, PATH_MAX - 1);
	ret[PATH_MAX - 1] = '\0';
	close(procfile);

	if (count > 0 && count < PATH_MAX - 1)
	{
		if (ret[count - 1] == '\n')
			ret[count - 1] = '\0';
		else
			ret[count] = '\0';

		/* Check it is a regular file, with a execute bit set */
		if (!stat(ret, &buf) &&
		    S_ISREG(buf.st_mode) &&
		    (buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
			return ret;
	}

	FREE(ret);

	return NULL;
}

bool
keepalived_modprobe(const char *mod_name)
{
	const char *argv[] = { "/sbin/modprobe", "-s", "--", mod_name, NULL };
	int child;
	int status;
	int rc;
	char *modprobe = get_modprobe();
	struct sigaction act, old_act;
	union non_const_args args;

	if (modprobe)
		argv[0] = modprobe;

	act.sa_handler = SIG_DFL;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;

	sigaction ( SIGCHLD, &act, &old_act);

#ifdef ENABLE_LOG_TO_FILE
	if (log_file_name)
		flush_log_file();
#endif

	if (!(child = fork())) {
		args.args = argv;
		/* coverity[tainted_string] */
		execv(argv[0], args.execve_args);
		exit(1);
	}

	rc = waitpid(child, &status, 0);

	sigaction ( SIGCHLD, &old_act, NULL);

	if (rc < 0) {
		log_message(LOG_INFO, "IPVS: waitpid error (%s)"
				    , strerror(errno));
	}

	if (modprobe)
		FREE(modprobe);

	if (!WIFEXITED(status) || WEXITSTATUS(status)) {
		return true;
	}

	return false;
}
#endif