/*
* 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;
}