/* 
 * dhcpcd - DHCP client daemon
 * Copyright 2006-2008 Roy Marples <roy@marples.name>
 * All rights reserved

 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/stat.h>

#include <arpa/inet.h>

#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "config.h"
#include "common.h"
#include "dhcp.h"
#include "interface.h"
#include "logger.h"
#include "info.h"

#ifdef ENABLE_INFO

/* Create a malloced string of cstr, changing ' to '\''
 * so the contents work in a shell */
static char *cleanmetas (const char *cstr)
{
	const char *p = cstr;
	char *new;
	char *n;
	size_t len;

	if (cstr == NULL || (len = strlen (cstr)) == 0)
		return (xstrdup (""));

	n = new = xmalloc (sizeof (char) * len + 2);
	do
		if (*p == '\'') {
			size_t pos = n - new;
			len += 4;
			new = xrealloc (new, sizeof (char) * len + 1);
			n = new + pos;
			*n++ = '\'';
			*n++ = '\\';
			*n++ = '\'';
			*n++ = '\'';
		} else
			*n++ = *p;
	while (*p++);

	/* Terminate the sucker */
	*n = '\0';

	return (new);
}


static void print_addresses (FILE *f, const struct address_head *addresses)
{
	const address_t *addr;

	STAILQ_FOREACH (addr, addresses, entries) {
		fprintf (f, "%s", inet_ntoa (addr->address));
		if (STAILQ_NEXT (addr, entries))
			fprintf (f, " ");
	}
}

static void print_clean (FILE *f, const char *name, const char *value)
{
	char *clean;

	if (! value)
		return;

	clean = cleanmetas (value);
	fprintf (f, "%s='%s'\n", name, clean);
	free (clean);
}

bool write_info(const interface_t *iface, const dhcp_t *dhcp,
		const options_t *options, bool overwrite)
{
	FILE *f;
	route_t *route;
	struct stat sb;

	if (options->test)
		f = stdout;
	else {
		if (! overwrite && stat (iface->infofile, &sb) == 0)
			return (true);

		logger (LOG_DEBUG, "writing %s", iface->infofile);
		if ((f = fopen (iface->infofile, "w")) == NULL) {
			logger (LOG_ERR, "fopen `%s': %s",
				iface->infofile, strerror (errno));
			return (false);
		}
	}

	if (dhcp->address.s_addr) {
		struct in_addr n;
		n.s_addr = dhcp->address.s_addr & dhcp->netmask.s_addr;
		fprintf (f, "IPADDR='%s'\n", inet_ntoa (dhcp->address));
		fprintf (f, "NETMASK='%s'\n", inet_ntoa (dhcp->netmask));
		fprintf (f, "NETWORK='%s'\n", inet_ntoa (n));
		fprintf (f, "BROADCAST='%s'\n", inet_ntoa (dhcp->broadcast));
	}
	if (dhcp->mtu > 0)
		fprintf (f, "MTU='%d'\n", dhcp->mtu);

	if (dhcp->routes) {
		bool doneone = false;
		fprintf (f, "ROUTES='");
		STAILQ_FOREACH (route, dhcp->routes, entries) {
			if (route->destination.s_addr != 0) {
				if (doneone)
					fprintf (f, " ");
				fprintf (f, "%s", inet_ntoa (route->destination));
				fprintf (f, ",%s", inet_ntoa (route->netmask));
				fprintf (f, ",%s", inet_ntoa (route->gateway));
				doneone = true;
			}
		}
		fprintf (f, "'\n");

		doneone = false;
		fprintf (f, "GATEWAYS='");
		STAILQ_FOREACH (route, dhcp->routes, entries) {
			if (route->destination.s_addr == 0) {
				if (doneone)
					fprintf (f, " ");
				fprintf (f, "%s", inet_ntoa (route->gateway));
				doneone = true;
			}
		}
		fprintf (f, "'\n");
	}

	print_clean (f, "HOSTNAME", dhcp->hostname);
	print_clean (f, "DNSDOMAIN", dhcp->dnsdomain);
	print_clean (f, "DNSSEARCH", dhcp->dnssearch);

	if (dhcp->dnsservers) {
		fprintf (f, "DNSSERVERS='");
		print_addresses (f, dhcp->dnsservers);
		fprintf (f, "'\n");
	}

	if (dhcp->fqdn) {
		fprintf (f, "FQDNFLAGS='%u'\n", dhcp->fqdn->flags);
		fprintf (f, "FQDNRCODE1='%u'\n", dhcp->fqdn->r1);
		fprintf (f, "FQDNRCODE2='%u'\n", dhcp->fqdn->r2);
		print_clean (f, "FQDNHOSTNAME", dhcp->fqdn->name);
	}

	if (dhcp->ntpservers) {
		fprintf (f, "NTPSERVERS='");
		print_addresses (f, dhcp->ntpservers);
		fprintf (f, "'\n");
	}

	print_clean (f, "NISDOMAIN", dhcp->nisdomain);
	if (dhcp->nisservers) {
		fprintf (f, "NISSERVERS='");
		print_addresses (f, dhcp->nisservers);
		fprintf (f, "'\n");
	}

	print_clean (f, "ROOTPATH", dhcp->rootpath);
	print_clean (f, "SIPSERVERS", dhcp->sipservers);

	if (dhcp->serveraddress.s_addr)
		fprintf (f, "DHCPSID='%s'\n", inet_ntoa (dhcp->serveraddress));
	if (dhcp->servername[0])
		print_clean (f, "DHCPSNAME", dhcp->servername);

	if (! options->doinform && dhcp->address.s_addr) {
		if (! options->test)
			fprintf (f, "LEASEDFROM='%u'\n", dhcp->leasedfrom);
		fprintf (f, "LEASETIME='%u'\n", dhcp->leasetime);
		fprintf (f, "RENEWALTIME='%u'\n", dhcp->renewaltime);
		fprintf (f, "REBINDTIME='%u'\n", dhcp->rebindtime);
	}
	print_clean (f, "INTERFACE", iface->name);
	print_clean (f, "CLASSID", options->classid);
	if (iface->clientid_len > 0) {
		fprintf (f, "CLIENTID='%s'\n",
			 hwaddr_ntoa (iface->clientid, iface->clientid_len));
	}
	fprintf (f, "DHCPCHADDR='%s'\n", hwaddr_ntoa (iface->hwaddr,
						      iface->hwlen));

#ifdef ENABLE_INFO_COMPAT
	/* Support the old .info settings if we need to */
	fprintf (f, "\n# dhcpcd-1.x and 2.x compatible variables\n");
	if (dhcp->dnsservers) {
		address_t *addr;

		fprintf (f, "DNS='");
		STAILQ_FOREACH (addr, dhcp->dnsservers, entries) {
			fprintf (f, "%s", inet_ntoa (addr->address));
			if (STAILQ_NEXT (addr, entries))
				fprintf (f, ",");
		}
		fprintf (f, "'\n");
	}

	if (dhcp->routes) {
		bool doneone = false;
		fprintf (f, "GATEWAY='");
		STAILQ_FOREACH (route, dhcp->routes, entries) {
			if (route->destination.s_addr == 0) {
				if (doneone)
					fprintf (f, ",");
				fprintf (f, "%s", inet_ntoa (route->gateway));
				doneone = true;
			}
		}
		fprintf (f, "'\n");
	}
#endif

	if (! options->test)
		fclose (f);
	return (true);
}

static bool parse_address (struct in_addr *addr,
			   const char *value, const char *var)
{
	if (inet_aton (value, addr) == 0) {
		logger (LOG_ERR, "%s `%s': %s", var, value,
			strerror (errno));
		return (false);
	}
	return (true);
}

static bool parse_uint (unsigned int *i,
			const char *value, const char *var)
{
	if (sscanf (value, "%u", i) != 1) {
		logger (LOG_ERR, "%s `%s': not a valid number",
			var, value);
		return (false);
	}
	return (true);
}

static bool parse_ushort (unsigned short *s,
			  const char *value, const char *var)
{
	if (sscanf (value, "%hu", s) != 1) {
		logger (LOG_ERR, "%s `%s': not a valid number",
			var, value);
		return (false);
	}
	return (true);
}

static struct address_head *parse_addresses (char *value, const char *var)
{
	char *token;
	char *p = value;
	struct address_head *head = NULL;

	while ((token = strsep (&p, " "))) {
		address_t *a = xzalloc (sizeof (*a));

		if (inet_aton (token, &a->address) == 0) {
			logger (LOG_ERR, "%s: invalid address `%s'", var, token);
			free_address (head);
			free (a);
			return (NULL);
		}

		if (! head) {
			head = xmalloc (sizeof (*head));
			STAILQ_INIT (head);
		}
		STAILQ_INSERT_TAIL (head, a, entries);
	}

	return (head);
}

bool read_info (const interface_t *iface, dhcp_t *dhcp)
{
	FILE *fp;
	char *line;
	char *var;
	char *value;
	char *p;
	struct stat sb;

	if (stat (iface->infofile, &sb) != 0) {
		logger (LOG_ERR, "lease information file `%s' does not exist",
			iface->infofile);
		return (false);
	}

	if (! (fp = fopen (iface->infofile, "r"))) {
		logger (LOG_ERR, "fopen `%s': %s",
			iface->infofile, strerror (errno));
		return (false);
	}

	dhcp->frominfo = true;

	while ((line = get_line (fp))) {
		var = line;

		/* Strip leading spaces/tabs */
		while ((*var == ' ') || (*var == '\t'))
			var++;

		/* Trim trailing \n */
		p = var + strlen (var) - 1;
		if (*p == '\n')
			*p = 0;

		/* Skip comments */
		if (*var == '#')
			goto next;

		/* If we don't have an equals sign then skip it */
		if (! (p = strchr (var, '=')))
			goto next;	

		/* Terminate the = so we have two strings */
		*p = 0;

		value = p + 1;
		/* Strip leading and trailing quotes if present */
		if (*value == '\'' || *value == '"')
			value++;
		p = value + strlen (value) - 1;
		if (*p == '\'' || *p == '"')
			*p = 0;

		/* Don't process null vars or values */
		if (! *var || ! *value)
			goto next;

		if (strcmp (var, "IPADDR") == 0)
			parse_address (&dhcp->address, value, "IPADDR");
		else if (strcmp (var, "NETMASK") == 0)
			parse_address (&dhcp->netmask, value, "NETMASK");
		else if (strcmp (var, "BROADCAST") == 0)
			parse_address (&dhcp->broadcast, value, "BROADCAST");
		else if (strcmp (var, "MTU") == 0)
			parse_ushort (&dhcp->mtu, value, "MTU");
		else if (strcmp (var, "ROUTES") == 0) {
			p = value;
			while ((value = strsep (&p, " "))) {
				char *pp = value;
				char *dest = strsep (&pp, ",");
				char *net = strsep (&pp, ",");
				char *gate = strsep (&pp, ",");
				route_t *route;

				if (! dest || ! net || ! gate) {
					logger (LOG_ERR, "read_info ROUTES `%s,%s,%s': invalid route",
						dest, net, gate);
					goto next;
				}

				/* See if we can create a route */
				route = xzalloc (sizeof (*route));
				if (inet_aton (dest, &route->destination) == 0) {
					logger (LOG_ERR, "read_info ROUTES `%s': not a valid destination address",
						dest);
					free (route);
					goto next;
				}
				if (inet_aton (dest, &route->netmask) == 0) {
					logger (LOG_ERR, "read_info ROUTES `%s': not a valid netmask address",
						net);
					free (route);
					goto next;
				}
				if (inet_aton (dest, &route->gateway) == 0) {
					logger (LOG_ERR, "read_info ROUTES `%s': not a valid gateway address",
						gate);
					free (route);
					goto next;
				}

				/* OK, now add our route */
				if (! dhcp->routes) {
					dhcp->routes = xmalloc (sizeof (*dhcp->routes));
					STAILQ_INIT (dhcp->routes);
				}
				STAILQ_INSERT_TAIL (dhcp->routes, route, entries);
			}
		} else if (strcmp (var, "GATEWAYS") == 0) {
			p = value;
			while ((value = strsep (&p, " "))) {
				route_t *route = xzalloc (sizeof (*route));
				if (parse_address (&route->gateway, value, "GATEWAYS")) {
					if (! dhcp->routes) {
						dhcp->routes = xmalloc (sizeof (*dhcp->routes));
						STAILQ_INIT (dhcp->routes);
					}
					STAILQ_INSERT_TAIL (dhcp->routes, route, entries);
				} else
					free (route);
			}
		} else if (strcmp (var, "HOSTNAME") == 0)
			dhcp->hostname = xstrdup (value);
		else if (strcmp (var, "DNSDOMAIN") == 0)
			dhcp->dnsdomain = xstrdup (value);
		else if (strcmp (var, "DNSSEARCH") == 0)
			dhcp->dnssearch = xstrdup (value);
		else if (strcmp (var, "DNSSERVERS") == 0)
			dhcp->dnsservers = parse_addresses (value, "DNSSERVERS");
		else if (strcmp (var, "NTPSERVERS") == 0)
			dhcp->ntpservers = parse_addresses (value, "NTPSERVERS");
		else if (strcmp (var, "NISDOMAIN") == 0)
			dhcp->nisdomain = xstrdup (value);
		else if (strcmp (var, "NISSERVERS") == 0)
			dhcp->nisservers = parse_addresses (value, "NISSERVERS");
		else if (strcmp (var, "ROOTPATH") == 0)
			dhcp->rootpath = xstrdup (value);
		else if (strcmp (var, "DHCPSID") == 0)
			parse_address (&dhcp->serveraddress, value, "DHCPSID");
		else if (strcmp (var, "DHCPSNAME") == 0)
			strlcpy (dhcp->servername, value, sizeof (dhcp->servername));
		else if (strcmp (var, "LEASEDFROM") == 0)
			parse_uint (&dhcp->leasedfrom, value, "LEASEDFROM");
		else if (strcmp (var, "LEASETIME") == 0)
			parse_uint (&dhcp->leasetime, value, "LEASETIME");
		else if (strcmp (var, "RENEWALTIME") == 0)
			parse_uint (&dhcp->renewaltime, value, "RENEWALTIME");
		else if (strcmp (var, "REBINDTIME") == 0)
			parse_uint (&dhcp->rebindtime, value, "REBINDTIME");

next:
		free (line);
	}

	fclose (fp);
	return (true);
}

#endif

