/*
 * Copyright (c) 1987, 1993, 1994
 *      The Regents of the University of California.  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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the University of
 *      California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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 <config.h>

#include "ftmacros.h"

#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <pcap.h>		// for PCAP_ERRBUF_SIZE

#include "portability.h"
#include "rpcapd.h"
#include "config_params.h"	// configuration file parameters
#include "fileconf.h"
#include "rpcap-protocol.h"
#include "log.h"

//
// Parameter names.
//
#define PARAM_ACTIVECLIENT	"ActiveClient"
#define PARAM_PASSIVECLIENT	"PassiveClient"
#define PARAM_NULLAUTHPERMIT	"NullAuthPermit"

static char *skipws(char *ptr);

/*
 * Locale-independent version checks for alphabetical and alphanumerical
 * characters that also can handle being handed a char value that might
 * be negative.
 */
#define FILECONF_ISALPHA(c) \
	(((c) >= 'A' && (c) <= 'Z') || ((c) >= 'a' && (c) <= 'z'))
#define FILECONF_ISALNUM(c) \
	(FILECONF_ISALPHA(c) || ((c) >= '0' && (c) <= '9'))

void fileconf_read(void)
{
	FILE *fp;
	unsigned int num_active_clients;

	if ((fp = fopen(loadfile, "r")) != NULL)
	{
		char line[MAX_LINE + 1];
		unsigned int lineno;

		hostlist[0] = 0;
		num_active_clients = 0;
		lineno = 0;

		while (fgets(line, MAX_LINE, fp) != NULL)
		{
			size_t linelen;
			char *ptr;
			char *param;
			size_t result;
			size_t toklen;

			lineno++;

			linelen = strlen(line);
			if (line[linelen - 1] != '\n')
			{
				int c;

				//
				// Either the line doesn't fit in
				// the buffer, or we got an EOF
				// before the EOL.  Assume it's the
				// former.
				//
				rpcapd_log(LOGPRIO_ERROR,
				    "%s, line %u is longer than %u characters",
				    loadfile, lineno, MAX_LINE);

				//
				// Eat characters until we get an NL.
				//
				while ((c = getc(fp)) != '\n')
				{
					if (c == EOF)
						goto done;
				}

				//
				// Try the next line.
				//
				continue;
			}
			ptr = line;

			//
			// Skip leading white space, if any.
			//
			ptr = skipws(ptr);
			if (ptr == NULL)
			{
				// Blank line.
				continue;
			}

			//
			// Is the next character a "#"?  If so, this
			// line is a comment; skip to the next line.
			//
			if (*ptr == '#')
				continue;

			//
			// Is the next character alphabetic?  If not,
			// this isn't a valid parameter name.
			//
			if (FILECONF_ISALPHA(*ptr))
			{
				rpcapd_log(LOGPRIO_ERROR,
				    "%s, line %u doesn't have a valid parameter name",
				    loadfile, lineno);
				continue;
			}

			//
			// Grab the first token, which is made of
			// alphanumerics, underscores, and hyphens.
			// That's the name of the parameter being set.
			//
			param = ptr;
			while (FILECONF_ISALNUM(*ptr) || *ptr == '-' || *ptr == '_')
				ptr++;

			//
			// Skip over white space, if any.
			//
			ptr = skipws(ptr);
			if (ptr == NULL || *ptr != '=')
			{
				//
				// We hit the end of the line before
				// finding a non-white space character,
				// or we found one but it's not an "=".
				// That means there's no "=", so this
				// line is invalid.  Complain and skip
				// this line.
				//
				rpcapd_log(LOGPRIO_ERROR,
				    "%s, line %u has a parameter but no =",
				    loadfile, lineno);
				continue;
			}

			//
			// We found the '='; set it to '\0', and skip
			// past it.
			//
			*ptr++ = '\0';

			//
			// Skip past any white space after the "=".
			//
			ptr = skipws(ptr);
			if (ptr == NULL)
			{
				//
				// The value is empty.
				//
				rpcapd_log(LOGPRIO_ERROR,
				    "%s, line %u has a parameter but no value",
				    loadfile, lineno);
				continue;
			}

			//
			// OK, what parameter is this?
			//
			if (strcmp(param, PARAM_ACTIVECLIENT) == 0) {
				//
				// Add this to the list of active clients.
				//
				char *address, *port;

				//
				// We can't have more than MAX_ACTIVE_LIST
				// active clients.
				//
				if (num_active_clients >= MAX_ACTIVE_LIST)
				{
					//
					// Too many entries for the active
					// client list.  Complain and
					// ignore it.
					//
					rpcapd_log(LOGPRIO_ERROR,
					    "%s, line %u has an %s parameter, but we already have %u active clients",
					    loadfile, lineno, PARAM_ACTIVECLIENT,
					    MAX_ACTIVE_LIST);
					continue;
				}

				//
				// Get the address.
				// It's terminated by a host list separator
				// *or* a #; there *shouldn't* be a #, as
				// that starts a comment, and that would
				// mean that we have no port.
				//
				address = ptr;
				toklen = strcspn(ptr, RPCAP_HOSTLIST_SEP "#");
				ptr += toklen;	// skip to the terminator
				if (toklen == 0)
				{
					if (*ptr == ' ' || *ptr == '\t' ||
					    *ptr == '\r' || *ptr == '\n' ||
					    *ptr == '#' || *ptr == '\0')
					{
						//
						// The first character it saw
						// was a whitespace character
						// or a comment character,
						// or we ran out of characters.
						// This means that there's
						// no value.
						//
						rpcapd_log(LOGPRIO_ERROR,
						    "%s, line %u has a parameter but no value",
						    loadfile, lineno);
					}
					else
					{
						//
						// This means that the first
						// character it saw was a
						// separator.  This means that
						// there's no address in the
						// value, just a port.
						//
						rpcapd_log(LOGPRIO_ERROR,
						    "%s, line %u has an %s parameter with a value containing no address",
						    loadfile, lineno, PARAM_ACTIVECLIENT);
					}
					continue;
				}

				//
				// Null-terminate the address, and skip past
				// it.
				//
				*ptr++ = '\0';

				//
				// Skip any white space following the
				// separating character.
				//
				ptr = skipws(ptr);
				if (ptr == NULL)
				{
					//
					// The value is empty, so there's
					// no port in the value.
					//
					rpcapd_log(LOGPRIO_ERROR,
					    "%s, line %u has an %s parameter with a value containing no port",
					    loadfile, lineno, PARAM_ACTIVECLIENT);
					continue;
				}

				//
				// Get the port.
				// We look for a white space character
				// or a # as a terminator; the # introduces
				// a comment that runs to the end of the
				// line.
				//
				port = ptr;
				toklen = strcspn(ptr, " \t#\r\n");
				ptr += toklen;
				if (toklen == 0)
				{
					//
					// The value is empty, so there's
					// no port in the value.
					//
					rpcapd_log(LOGPRIO_ERROR,
					    "%s, line %u has an %s parameter with a value containing no port",
					    loadfile, lineno, PARAM_ACTIVECLIENT);
					continue;
				}

				//
				// Null-terminate the port, and skip past
				// it.
				//
				*ptr++ = '\0';
				result = pcapint_strlcpy(activelist[num_active_clients].address, address, sizeof(activelist[num_active_clients].address));
				if (result >= sizeof(activelist[num_active_clients].address))
				{
					//
					// It didn't fit.
					//
					rpcapd_log(LOGPRIO_ERROR,
					    "%s, line %u has an %s parameter with an address with more than %u characters",
					    loadfile, lineno, PARAM_ACTIVECLIENT,
					    (unsigned int)(sizeof(activelist[num_active_clients].address) - 1));
					continue;
				}
				if (strcmp(port, "DEFAULT") == 0) // the user choose a custom port
					result = pcapint_strlcpy(activelist[num_active_clients].port, RPCAP_DEFAULT_NETPORT_ACTIVE, sizeof(activelist[num_active_clients].port));
				else
					result = pcapint_strlcpy(activelist[num_active_clients].port, port, sizeof(activelist[num_active_clients].port));
				if (result >= sizeof(activelist[num_active_clients].address))
				{
					//
					// It didn't fit.
					//
					rpcapd_log(LOGPRIO_ERROR,
					    "%s, line %u has an %s parameter with an port with more than %u characters",
					    loadfile, lineno, PARAM_ACTIVECLIENT,
					    (unsigned int)(sizeof(activelist[num_active_clients].port) - 1));
					continue;
				}

				num_active_clients++;
			}
			else if (strcmp(param, PARAM_PASSIVECLIENT) == 0)
			{
				char *eos;
				char *host;

				//
				// Get the host.
				// We look for a white space character
				// or a # as a terminator; the # introduces
				// a comment that runs to the end of the
				// line.
				//
				host = ptr;
				toklen = strcspn(ptr, " \t#\r\n");
				if (toklen == 0)
				{
					//
					// The first character it saw
					// was a whitespace character
					// or a comment character.
					// This means that there's
					// no value.
					//
					rpcapd_log(LOGPRIO_ERROR,
					    "%s, line %u has a parameter but no value",
					    loadfile, lineno);
					continue;
				}
				ptr += toklen;
				*ptr++ = '\0';

				//
				// Append this to the host list.
				// Save the current end-of-string for the
				// host list, in case the new host doesn't
				// fit, so that we can discard the partially-
				// copied host name.
				//
				eos = hostlist + strlen(hostlist);
				if (eos != hostlist)
				{
					//
					// The list is not empty, so prepend
					// a comma before adding this host.
					//
					result = pcapint_strlcat(hostlist, ",", sizeof(hostlist));
					if (result >= sizeof(hostlist))
					{
						//
						// It didn't fit.  Discard
						// the comma (which wasn't
						// added, but...), complain,
						// and ignore this line.
						//
						*eos = '\0';
						rpcapd_log(LOGPRIO_ERROR,
						    "%s, line %u has a %s parameter with a host name that doesn't fit",
						    loadfile, lineno, PARAM_PASSIVECLIENT);
						continue;
					}
				}
				result = pcapint_strlcat(hostlist, host, sizeof(hostlist));
				if (result >= sizeof(hostlist))
				{
					//
					// It didn't fit.  Discard the comma,
					// complain, and ignore this line.
					//
					*eos = '\0';
					rpcapd_log(LOGPRIO_ERROR,
					    "%s, line %u has a %s parameter with a host name that doesn't fit",
					    loadfile, lineno, PARAM_PASSIVECLIENT);
					continue;
				}
			}
			else if (strcmp(param, PARAM_NULLAUTHPERMIT) == 0)
			{
				char *setting;

				//
				// Get the setting.
				// We look for a white space character
				// or a # as a terminator; the # introduces
				// a comment that runs to the end of the
				// line.
				//
				setting = ptr;
				toklen = strcspn(ptr, " \t#\r\n");
				ptr += toklen;
				if (toklen == 0)
				{
					//
					// The first character it saw
					// was a whitespace character
					// or a comment character.
					// This means that there's
					// no value.
					//
					rpcapd_log(LOGPRIO_ERROR,
					    "%s, line %u has a parameter but no value",
					    loadfile, lineno);
					continue;
				}
				*ptr++ = '\0';

				//
				// XXX - should we complain if it's
				// neither "yes" nor "no"?
				//
				if (strcmp(setting, "YES") == 0)
					nullAuthAllowed = 1;
				else
					nullAuthAllowed = 0;
			}
			else
			{
				rpcapd_log(LOGPRIO_ERROR,
				    "%s, line %u has an unknown parameter %s",
				    loadfile, lineno, param);
				continue;
			}
		}

done:
		// clear the remaining fields of the active list
		for (int i = num_active_clients; i < MAX_ACTIVE_LIST; i++)
		{
			activelist[i].address[0] = 0;
			activelist[i].port[0] = 0;
			num_active_clients++;
		}

		rpcapd_log(LOGPRIO_DEBUG, "New passive host list: %s", hostlist);
		fclose(fp);
	}
}

int fileconf_save(const char *savefile)
{
	FILE *fp;

	if ((fp = fopen(savefile, "w")) != NULL)
	{
		char *token; /*, *port;*/					// temp, needed to separate items into the hostlist
		char temphostlist[MAX_HOST_LIST + 1];
		int i = 0;
		char *lasts;

		fprintf(fp, "# Configuration file help.\n\n");

		// Save list of clients which are allowed to connect to us in passive mode
		fprintf(fp, "# Hosts which are allowed to connect to this server (passive mode)\n");
		fprintf(fp, "# Format: PassiveClient = <name or address>\n\n");

		pcapint_strlcpy(temphostlist, hostlist, sizeof (temphostlist));

		token = pcapint_strtok_r(temphostlist, RPCAP_HOSTLIST_SEP, &lasts);
		while(token != NULL)
		{
			fprintf(fp, "%s = %s\n", PARAM_PASSIVECLIENT, token);
			token = pcapint_strtok_r(NULL, RPCAP_HOSTLIST_SEP, &lasts);
		}


		// Save list of clients which are allowed to connect to us in active mode
		fprintf(fp, "\n\n");
		fprintf(fp, "# Hosts to which this server is trying to connect to (active mode)\n");
		fprintf(fp, "# Format: ActiveClient = <name or address>, <port | DEFAULT>\n\n");


		while ((i < MAX_ACTIVE_LIST) && (activelist[i].address[0] != 0))
		{
			fprintf(fp, "%s = %s, %s\n", PARAM_ACTIVECLIENT,
			    activelist[i].address, activelist[i].port);
			i++;
		}

		// Save if we want to permit NULL authentication
		fprintf(fp, "\n\n");
		fprintf(fp, "# Permit NULL authentication: YES or NO\n\n");

		fprintf(fp, "%s = %s\n", PARAM_NULLAUTHPERMIT,
		    nullAuthAllowed ? "YES" : "NO");

		fclose(fp);
		return 0;
	}
	else
	{
		return -1;
	}

}

//
// Skip over white space.
// If we hit a CR or LF, return NULL, otherwise return a pointer to
// the first non-white space character.  Replace white space characters
// other than CR or LF with '\0', so that, if we're skipping white space
// after a token, the token is null-terminated.
//
static char *skipws(char *ptr)
{
	while (*ptr == ' ' || *ptr == '\t' || *ptr == '\r' || *ptr == '\n') {
		if (*ptr == '\r' || *ptr == '\n')
			return NULL;
		*ptr++ = '\0';
	}
	return ptr;
}
