/*
 * Copyright (C) 2017-2026 Red Hat, Inc.  All rights reserved.
 *
 * Author: Fabio M. Di Nitto <fabbione@kronosnet.org>
 *
 * This software licensed under LGPL-2.0+
 */

#include "config.h"

#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <limits.h>
#include <stdio.h>
#include <stdint.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "libnozzle.h"
#include "internals.h"

static int read_pipe(int fd, char **file, size_t *length)
{
	char buf[4096];
	int n;
	int done = 0;

	*file = NULL;
	*length = 0;

	memset(buf, 0, sizeof(buf));

	while (!done) {

		n = read(fd, buf, sizeof(buf));

		if (n < 0) {
			if (errno == EINTR)
				continue;

			if (*file)
				free(*file);

			return n;
		}

		if (n == 0 && (!*length))
			return 0;

		if (n == 0)
			done = 1;

		if (*file)
			*file = realloc(*file, (*length) + n + done);
		else
			*file = malloc(n + done);

		if (!*file)
			return -1;

		memmove((*file) + (*length), buf, n);
		*length += (done + n);
	}

	/* Null terminator */
	(*file)[(*length) - 1] = 0;

	return 0;
}

int execute_bin_sh_command(const char *command, char **error_string)
{
	pid_t pid;
	int status, err = 0;
	int fd[2];
	size_t size = 0;

	if ((command == NULL) || (!error_string)) {
		errno = EINVAL;
		return -1;
	}

	*error_string = NULL;

	err = pipe(fd);
	if (err)
		goto out_clean;

	pid = fork();
	if (pid < 0) {
		err = pid;
		goto out_clean;
	}

	if (pid) { /* parent */

		close(fd[1]);
		err = read_pipe(fd[0], error_string, &size);
		if (err)
			goto out_clean0;

		waitpid(pid, &status, 0);
		if (!WIFEXITED(status)) {
			err = -1;
			goto out_clean0;
		}
		if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
			err = WEXITSTATUS(status);
			goto out_clean0;
		}
		goto out_clean0;
	} else { /* child */
		close(0);
		close(1);
		close(2);

		close(fd[0]);
		dup2(fd[1], 1);
		dup2(fd[1], 2);
		close(fd[1]);

		execlp("/bin/sh", "/bin/sh", "-c", command, NULL);
		exit(EXIT_FAILURE);
	}

out_clean:
	close(fd[1]);
out_clean0:
	close(fd[0]);

	return err;
}

char *generate_v4_broadcast(const char *ipaddr, const char *prefix)
{
	int prefix_len;
	struct in_addr mask;
	struct in_addr broadcast;
	struct in_addr address;
	char buf[INET6_ADDRSTRLEN];

	prefix_len = atoi(prefix);

	if ((prefix_len > 32) || (prefix_len <= 0))
		return NULL;

	if (inet_pton(AF_INET, ipaddr, &address) <= 0)
		return NULL;

	mask.s_addr = htonl(~((1 << (32 - prefix_len)) - 1));

	memset(&broadcast, 0, sizeof(broadcast));
	broadcast.s_addr = (address.s_addr & mask.s_addr) | ~mask.s_addr;

	return strdup(inet_ntop(AF_INET, (void *)&broadcast, buf, sizeof(buf)));
}

int _determine_family(const char *ipaddr)
{
	if (!strchr(ipaddr, ':')) {
		return AF_INET;
	}
	return AF_INET6;
}

int _validate_prefix(int family, const char *prefix)
{
	int prefix_len;

	prefix_len = atoi(prefix);

	if (family == AF_INET) {
		if (prefix_len <= 0 || prefix_len > 32) {
			errno = EINVAL;
			return -1;
		}
	} else {
		if (prefix_len <= 0 || prefix_len > 128) {
			errno = EINVAL;
			return -1;
		}
	}

	return prefix_len;
}

uint32_t _ipv4_prefix_to_netmask(int prefix_len)
{
	return htonl(~((1 << (32 - prefix_len)) - 1));
}

void _ipv6_prefix_to_mask(int prefix_len, struct in6_addr *mask)
{
	int i;

	for (i = 0; i < (int)sizeof(struct in6_addr); i++) {
		if (prefix_len >= 8) {
			mask->s6_addr[i] = 0xff;
			prefix_len -= 8;
		} else if (prefix_len > 0) {
			mask->s6_addr[i] = (0xff << (8 - prefix_len)) & 0xff;
			prefix_len = 0;
		} else {
			mask->s6_addr[i] = 0;
		}
	}
}

int find_ip(nozzle_t nozzle,
	    const char *ipaddr, const char *prefix,
	    struct nozzle_ip **ip, struct nozzle_ip **ip_prev)
{
	struct nozzle_ip *local_ip, *local_ip_prev;
	int found = 0;

	local_ip = local_ip_prev = nozzle->ip;

	while(local_ip) {
		if ((!strcmp(local_ip->ipaddr, ipaddr)) && (!strcmp(local_ip->prefix, prefix))) {
			found = 1;
			break;
		}
		local_ip_prev = local_ip;
		local_ip = local_ip->next;
	}

	if (found) {
		*ip = local_ip;
		*ip_prev = local_ip_prev;
	}

	return found;
}
