root/usr.bin/bgplg/bgplg.c
/*      $OpenBSD: bgplg.c,v 1.20 2024/02/09 12:56:53 claudio Exp $      */

/*
 * Copyright (c) 2005, 2006 Reyk Floeter <reyk@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/types.h>
#include <sys/stat.h>

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <err.h>

#include "bgplg.h"

#define INC_STYLE       "/conf/bgplg.css"
#define INC_HEAD        "/conf/bgplg.head"
#define INC_FOOT        "/conf/bgplg.foot"

#define BGPDSOCK        "/run/bgpd.rsock"
#define BGPCTL          "/bin/bgpctl", "-s", BGPDSOCK
#define PING            "/bin/ping"
#define TRACEROUTE      "/bin/traceroute"
#define PING6           "/bin/ping6"
#define TRACEROUTE6     "/bin/traceroute6"
#define CONTENT_TYPE    "text/html"

static struct cmd cmds[] = CMDS;

char             *lg_getenv(const char *, int *);
void              lg_urldecode(char *);
char            **lg_arg2argv(char *, int *);
char            **lg_argextra(char **, int, struct cmd *);
char             *lg_getarg(const char *, char *, int);
int               lg_incl(const char *);

void
lg_urldecode(char *str)
{
        size_t i, c, len;
        char code[3];
        long result;

        if (str && *str) {
                len = strlen(str);
                i = c = 0;
                while (i < len) {
                        if (str[i] == '%' && i <= (len - 2)) {
                                if (isxdigit((unsigned char)str[i + 1]) &&
                                    isxdigit((unsigned char)str[i + 2])) {
                                        code[0] = str[i + 1];
                                        code[1] = str[i + 2];
                                        code[2] = 0;
                                        result = strtol(code, NULL, 16);
                                        /* Replace NUL chars with a space */
                                        if (result == 0)
                                                result = ' ';
                                        str[c++] = result;
                                        i += 3;
                                } else {
                                        str[c++] = '%';
                                        i++;
                                }
                        } else if (str[i] == '+') {
                                str[i] = ' ';
                        } else {
                                if (c != i)
                                        str[c] = str[i];
                                c++;
                                i++;
                        }
                }
                str[c] = 0x0;
        }
}

char *
lg_getenv(const char *name, int *lenp)
{
        size_t len;
        u_int i;
        char *ptr;

        if ((ptr = getenv(name)) == NULL)
                return (NULL);

        lg_urldecode(ptr);

        if (!(len = strlen(ptr)))
                return (NULL);

        if (lenp != NULL)
                *lenp = len;

#define allowed_in_string(_x)                                           \
        (isalnum((unsigned char)_x) || strchr("-_.:/= ", _x))

        for (i = 0; i < len; i++) {
                if (ptr[i] == '&')
                        ptr[i] = '\0';
                if (!allowed_in_string(ptr[i])) {
                        printf("invalid character in input\n");
                        return (NULL);
                }
        }

        return (ptr);
#undef allowed_in_string
}

char *
lg_getarg(const char *name, char *arg, int len)
{
        char *ptr = arg;
        size_t namelen, ptrlen;
        int i;

        namelen = strlen(name);

        for (i = 0; i < len; i++) {
                if (arg[i] == '\0')
                        continue;
                ptr = arg + i;
                ptrlen = strlen(ptr);
                if (namelen >= ptrlen)
                        continue;
                if (strncmp(name, ptr, namelen) == 0)
                        return (ptr + namelen);
        }

        return (NULL);
}

char **
lg_arg2argv(char *arg, int *argc)
{
        char **argv, *ptr = arg;
        size_t len;
        u_int i, c = 1;

        len = strlen(arg);

        /* Count elements */
        for (i = 0; i < len; i++) {
                if (isspace((unsigned char)arg[i])) {
                        /* filter out additional options */
                        if (arg[i + 1] == '-') {
                                printf("invalid input\n");
                                return (NULL);
                        }
                        arg[i] = '\0';
                        c++;
                }
        }

        /* Generate array */
        if ((argv = calloc(c + 1, sizeof(char *))) == NULL) {
                printf("fatal error: %s\n", strerror(errno));
                return (NULL);
        }

        argv[c] = NULL;
        *argc = c;

        /* Fill array */
        for (i = c = 0; i < len; i++) {
                if (arg[i] == '\0' || i == 0) {
                        if (i != 0)
                                ptr = &arg[i + 1];
                        argv[c++] = ptr;
                }
        }

        return (argv);
}

char **
lg_argextra(char **argv, int argc, struct cmd *cmdp)
{
        char **new_argv;
        int i, c = 0;

        /* Count elements */
        for (i = 0; cmdp->earg[i] != NULL; i++)
                c++;

        /* Generate array */
        if ((new_argv = calloc(c + argc + 1, sizeof(char *))) == NULL) {
                printf("fatal error: %s\n", strerror(errno));
                return (NULL);
        }

        /* Fill array */
        for (i = c = 0; cmdp->earg[i] != NULL; i++)
                new_argv[c++] = cmdp->earg[i];

        /* Append old array */
        for (i = 0; i < argc; i++)
                new_argv[c++] = argv[i];

        new_argv[c] = NULL;

        free(argv);

        return (new_argv);
}

int
lg_incl(const char *file)
{
        char buf[BUFSIZ];
        int fd, len;

        if ((fd = open(file, O_RDONLY)) == -1)
                return (errno);

        do {
                len = read(fd, buf, sizeof(buf));
                fwrite(buf, len, 1, stdout);
        } while(len == BUFSIZ);

        close(fd);
        return (0);
}

int
main(void)
{
        char *query, *myname, *self, *cmd = NULL, *req;
        char **argv = NULL;
        int ret = 1, argc = 0, query_length = 0;
        struct stat st;
        u_int i;
        struct cmd *cmdp = NULL;

        if (pledge("stdio rpath proc exec", NULL) == -1)
                err(1, "pledge");

        if ((myname = lg_getenv("SERVER_NAME", NULL)) == NULL)
                return (1);

        printf("Content-Type: %s\n"
            "Cache-Control: no-cache\n\n"
            "<!doctype html>\n"
            "<html>\n"
            "<head>\n"
            "<title>%s</title>\n",
            CONTENT_TYPE, myname);
        if (stat(INC_STYLE, &st) == 0) {
                printf("<style>\n");
                lg_incl(INC_STYLE);
                printf("</style>\n");
        }
        if (stat(INC_HEAD, &st) != 0 || lg_incl(INC_HEAD) != 0) {
                printf("</head>\n"
                    "<body>\n");
        }

        /* print a form with possible options */
        if ((self = lg_getenv("SCRIPT_NAME", NULL)) == NULL) {
                printf("fatal error: invalid request\n");
                goto err;
        }
        if ((query = lg_getenv("QUERY_STRING", &query_length)) != NULL)
                cmd = lg_getarg("cmd=", query, query_length);
        printf(
            "<form action='%s'>\n"
            "<div class=\"command\">\n"
            "<select name='cmd'>\n",
            self);
        for (i = 0; cmds[i].name != NULL; i++) {
                if (!lg_checkperm(&cmds[i]))
                        continue;

                if (cmd != NULL && strcmp(cmd, cmds[i].name) == 0)
                        printf("<option value='%s' selected='selected'>%s"
                            "</option>\n",
                            cmds[i].name, cmds[i].name);
                else
                        printf("<option value='%s'>%s</option>\n",
                            cmds[i].name, cmds[i].name);
        }

        if ((req = lg_getarg("req=", query, query_length)) != NULL) {
                /* Could be NULL */
                argv = lg_arg2argv(req, &argc);
        }

        printf("</select>\n"
            "<input type='text' value='%s' name='req'>\n"
            "<input type='submit' value='submit'>\n"
            "</div>\n"
            "</form>\n"
            "<pre>\n", req ? req : "");
        fflush(stdout);

#ifdef DEBUG
        if (close(2) == -1 || dup2(1, 2) == -1)
#else
        if (close(2) == -1)
#endif
        {
                printf("fatal error: %s\n", strerror(errno));
                goto err;
        }

        if (query == NULL)
                goto err;
        if (cmd == NULL) {
                printf("unspecified command\n");
                goto err;
        }

        for (i = 0; cmds[i].name != NULL; i++) {
                if (strcmp(cmd, cmds[i].name) == 0) {
                        cmdp = &cmds[i];
                        break;
                }
        }

        if (cmdp == NULL) {
                printf("invalid command: %s\n", cmd);
                goto err;
        }
        if (argc > cmdp->maxargs) {
                printf("superfluous argument(s): %s %s\n",
                    cmd, cmdp->args ? cmdp->args : "");
                goto err;
        }
        if (argc < cmdp->minargs) {
                printf("missing argument(s): %s %s\n", cmd, cmdp->args);
                goto err;
        }

        if (cmdp->func != NULL) {
                ret = cmdp->func(cmds, argv);
        } else {
                if ((argv = lg_argextra(argv, argc, cmdp)) == NULL)
                        goto err;
                ret = lg_exec(cmdp->earg[0], argv);
        }
        if (ret != 0)
                printf("\nfailed%s\n", ret == 127 ? ": file not found" : ".");
        else
                printf("\nsuccess.\n");

 err:
        fflush(stdout);

        free(argv);

        printf("</pre>\n");

        if (stat(INC_FOOT, &st) != 0 || lg_incl(INC_FOOT) != 0)
                printf("<hr/>\n");

        printf("<div class='footer'>\n"
            "</div>\n"
            "</body>\n"
            "</html>\n");

        return (ret);
}