/* $NetBSD: conffile.c,v 1.13 2020/04/22 23:53:27 joerg Exp $ */

/*
* Copyright (c) 2010 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Mihai Chelaru <[email protected]>
*
* 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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/mman.h>
#include <sys/stat.h>

#include <arpa/inet.h>
#include <netinet/in.h>

#include <ctype.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "conffile.h"
#include "ldp_errors.h"

#define NextCommand(x) strsep(&x, " ")
#define LINEMAXSIZE 1024

struct coifs_head coifs_head;
struct conei_head conei_head;

char *mapped, *nextline;
size_t mapsize;

extern int ldp_hello_time, ldp_keepalive_time, ldp_holddown_time, command_port,
       min_label, max_label, no_default_route, loop_detection;
struct in_addr conf_ldp_id;

static int conf_dispatch(char*);
static char * conf_getlinelimit(void);
static int checkeol(char*);
static int Fhellotime(char*);
static int Fport(char*);
static int Fholddown(char*);
static int Fkeepalive(char*);
static int Fmaxlabel(char*);
static int Fminlabel(char*);
static int Fldpid(char*);
static int Fneighbour(char*);
static int Gneighbour(struct conf_neighbour *, char *);
static int Fnodefault(char*);
static int Floopdetection(char*);
static int Finterface(char*);
static int Ginterface(struct conf_interface *, char *);
static int Ipassive(struct conf_interface *, char *);
static int Itaddr(struct conf_interface *, char *);

struct conf_func {
       char com[64];
       int (* func)(char *);
};

struct intf_func {
       char com[64];
       int (* func)(struct conf_interface *, char *);
};

struct conf_func main_commands[] = {
       { "hello-time", Fhellotime },
       { "keepalive-time", Fkeepalive },
       { "holddown-time", Fholddown },
       { "command-port", Fport },
       { "min-label", Fminlabel },
       { "max-label", Fmaxlabel },
       { "ldp-id", Fldpid },
       { "neighbor", Fneighbour },
       { "neighbour", Fneighbour },
       { "no-default-route", Fnodefault },
       { "loop-detection", Floopdetection },
       { "interface", Finterface },
       { "", NULL },
};

struct intf_func intf_commands[] = {
       { "passive", Ipassive },
       { "transport-address", Itaddr },
       { "", NULL },
};

static int parseline;

/*
* Parses config file
*/
int
conf_parsefile(const char *fname)
{
       char line[LINEMAXSIZE+1];
       struct stat fs;

       SLIST_INIT(&conei_head);
       SLIST_INIT(&coifs_head);
       conf_ldp_id.s_addr = 0;

       int confh = open(fname, O_RDONLY, 0);

       if (confh == -1 || fstat(confh, &fs) == -1 ||
           (mapped = mmap(NULL, fs.st_size, PROT_READ, MAP_SHARED, confh, 0))
           == MAP_FAILED) {
               if (confh != -1)
                       close(confh);
               return E_CONF_IO;
       }

       mapsize = fs.st_size;
       nextline = mapped;
       for (parseline = 1; ; parseline++) {
               char *prev = nextline;
               if ((nextline = conf_getlinelimit()) == NULL)
                       break;
               while (isspace((int)*prev) != 0 && prev < nextline)
                       prev++;
               if (nextline - prev < 2)
                       continue;
               else if (nextline - prev > LINEMAXSIZE)
                       goto parerr;
               memcpy(line, prev, nextline - prev);
               if (line[0] == '#')
                       continue;
               else
                       line[nextline - prev] = '\0';
               if (conf_dispatch(line) != 0)
                       goto parerr;
       }
       munmap(mapped, mapsize);
       close(confh);
       return 0;
parerr:
       munmap(mapped, mapsize);
       close(confh);
       return parseline;
}

char *
conf_getlinelimit(void)
{
       char *p = nextline;

       if (nextline < mapped || (size_t)(nextline - mapped) >= mapsize)
               return NULL;

       for (p = nextline; *p != '\n' && (size_t)(p - mapped) < mapsize; p++);
       return p + 1;
}

/*
* Looks for a matching command on a line
*/
int
conf_dispatch(char *line)
{
       int i, last_match = -1, matched = 0;
       char *command, *nline = line;

       if (strlen(line) == 0 || line[0] == '#')
               return E_CONF_OK;
       command = NextCommand(nline);
       for (i = 0; main_commands[i].func != NULL; i++)
               if (strncasecmp(main_commands[i].com, command,
                   strlen(main_commands[i].com)) == 0) {
                       matched++;
                       last_match = i;
               }
       if (matched == 0)
               return E_CONF_NOMATCH;
       else if (matched > 1)
               return E_CONF_AMBIGUOUS;

       if (nline == NULL || checkeol(nline) != 0)
               return E_CONF_PARAM;
       return main_commands[last_match].func(nline);
}

/*
* Checks if a line is terminated or else if it contains
* a start block bracket. If it's semicolon terminated
* then trim it.
*/
int
checkeol(char *line)
{
       size_t len = strlen(line);
       if (len > 0 && line[len - 1] == '\n') {
               line[len - 1] = '\0';
               len--;
       }
       if (len > 0 && line[len - 1] == ';') {
               line[len - 1] = '\0';
               return 0;
       }
       for (size_t i = 0; i < len; i++)
               if (line[i] == '{')
                       return 0;
       return -1;
}

/*
* Sets hello time
*/
int
Fhellotime(char *line)
{
       int ht = atoi(line);
       if (ht <= 0)
               return E_CONF_PARAM;
       ldp_hello_time = ht;
       return 0;
}

/*
* Sets command port
*/
int
Fport(char *line)
{
       int cp = atoi(line);
       if (cp <= 0 || cp > 65535)
               return E_CONF_PARAM;
       command_port = cp;
       return 0;
}

/*
* Sets neighbour keepalive
*/
int
Fkeepalive(char *line)
{
       int kt = atoi(line);
       if (kt <= 0)
               return E_CONF_PARAM;
       ldp_keepalive_time = kt;
       return 0;
}

/*
* Sets neighbour holddown timer
*/
int
Fholddown(char *line)
{
       int hdt = atoi(line);
       if (hdt <= 0)
               return E_CONF_PARAM;
       ldp_holddown_time = hdt;
       return 0;
}

int
Fminlabel(char *line)
{
       int ml = atoi(line);
       if (ml <= 0)
               return E_CONF_PARAM;
       min_label = ml;
       return 0;
}

int
Fmaxlabel(char *line)
{
       int ml = atoi(line);
       if (ml <= 0)
               return E_CONF_PARAM;
       max_label = ml;
       return 0;
}

int
Fldpid(char *line)
{
       if (inet_pton(AF_INET, line, &conf_ldp_id) != 1)
               return E_CONF_PARAM;
       return 0;
}

int
Fneighbour(char *line)
{
       char *peer;
       struct conf_neighbour *nei;
       struct in_addr ad;
       char buf[LINEMAXSIZE];

       peer = NextCommand(line);
       if (inet_pton(AF_INET, peer, &ad) != 1)
               return E_CONF_PARAM;

       nei = calloc(1, sizeof(*nei));
       if (nei == NULL)
               return E_CONF_MEM;
       nei->address.s_addr = ad.s_addr;
       SLIST_INSERT_HEAD(&conei_head, nei, neilist);

       for ( ; ; ) {
               char *prev = nextline;
               parseline++;
               nextline = conf_getlinelimit();
               if (nextline == NULL || (size_t)(nextline - prev) > LINEMAXSIZE)
                       return -1;
               while (isspace((int)*prev) != 0 && prev < nextline)
                       prev++;
               memcpy(buf, prev, nextline - prev);
               if (nextline - prev < 2 || buf[0] == '#')
                       continue;
               else if (buf[0] == '}')
                       break;
               else
                       buf[nextline - prev] = '\0';
               if (Gneighbour(nei, buf) == -1)
                       return -1;
       }
       return -1;
}

/*
* neighbour { } sub-commands
*/
int
Gneighbour(struct conf_neighbour *nei, char *line)
{
       if (strncasecmp("authenticate", line, 12) == 0) {
               nei->authenticate = 1;
               return 0;
       }
       return -1;
}

int
Fnodefault(char *line)
{
       int nd = atoi(line);
       if (nd < 0)
               return E_CONF_PARAM;
       no_default_route = nd;
       return 0;
}

int
Floopdetection(char *line)
{
       int loopd = atoi(line);
       if (loopd < 0)
               return E_CONF_PARAM;
       loop_detection = loopd;
       return 0;
}

/*
* Interface sub-commands
*/
int
Finterface(char *line)
{
       char *ifname;
       struct conf_interface *conf_if;
       char buf[LINEMAXSIZE];

       if ((ifname = NextCommand(line)) == NULL)
               return -1;
       if ((conf_if = calloc(1, sizeof(*conf_if))) == NULL)
               return -1;

       strlcpy(conf_if->if_name, ifname, IF_NAMESIZE);
       SLIST_INSERT_HEAD(&coifs_head, conf_if, iflist);

       for ( ; ; ) {
               char *prev = nextline;
               parseline++;
               nextline = conf_getlinelimit();
               if (nextline == NULL || (size_t)(nextline - prev) > LINEMAXSIZE)
                       return -1;
               while (isspace((int)*prev) != 0 && prev < nextline)
                       prev++;
               memcpy(buf, prev, nextline - prev);
               if (nextline - prev < 2 || buf[0] == '#')
                       continue;
               else if (buf[0] == '}')
                       break;
               else
                       buf[nextline - prev] = '\0';
               if (Ginterface(conf_if, buf) == -1)
                       return -1;
       }
       return 0;
}

int
Ginterface(struct conf_interface *conf_if, char *buf)
{
       int i;

       for (i = 0; intf_commands[i].func != NULL; i++)
               if (strncasecmp(buf, intf_commands[i].com,
                   strlen(intf_commands[i].com)) == 0)
                       return intf_commands[i].func(conf_if, buf +
                           strlen(intf_commands[i].com) + 1);
       /* command not found */
       return -1;
}

/* sets transport address */
int
Itaddr(struct conf_interface *conf_if, char *buf)
{
       if (inet_pton(AF_INET, buf, &conf_if->tr_addr) != 1)
               return -1;
       return 0;
}

/* sets passive-interface on */
int
Ipassive(struct conf_interface *conf_if, char *buf)
{
       conf_if->passive = 1;
       return 0;
}