root/usr.sbin/ldpd/conffile.c
/* $NetBSD: conffile.c,v 1.15 2025/09/20 15:42:01 christos 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 <kefren@NetBSD.org>
 *
 * 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((unsigned char)*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;
}

static int
fill_info(void *param, int (*func)(void *, char *))
{
        char buf[LINEMAXSIZE];

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

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

        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);

        return fill_info(nei, (int (*)(void *, char *))Gneighbour); 
}

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

        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);

        return fill_info(conf_if, (int (*)(void *, char *))Ginterface); 
}

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