This is bgplgd that can be used to provide a JSON REST API for bgpd.
It can be used to integrate bgpd into external looking glasses.
The bgplgd uses a modified version of slowcgi. It forks bgpctl per request
and returns the output via the slowcgi socket.

Securing the API needs to be done in the http server that is handling the
fcgi requests.

-- 
:wq Claudio

Index: Makefile
===================================================================
RCS file: Makefile
diff -N Makefile
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ Makefile    1 Mar 2022 17:00:06 -0000
@@ -0,0 +1,14 @@
+#      $OpenBSD: Makefile,v 1.2 2014/12/23 19:32:16 pascal Exp $
+
+PROG=          bgplgd
+SRCS=          bgplgd.c slowcgi.c qs.c
+CFLAGS+=       -Wall
+CFLAGS+=       -Wstrict-prototypes -Wmissing-prototypes
+CLFAGS+=       -Wmissing-declarations -Wredundant-decls
+CFLAGS+=       -Wshadow -Wpointer-arith -Wcast-qual
+CFLAGS+=       -Wsign-compare
+LDADD=  -levent
+DPADD=  ${LIBEVENT}
+MAN=           bgplgd.8
+
+.include <bsd.prog.mk>
Index: bgplgd.8
===================================================================
RCS file: bgplgd.8
diff -N bgplgd.8
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ bgplgd.8    28 Jun 2022 14:43:39 -0000
@@ -0,0 +1,179 @@
+.\" $OpenBSD$
+.\"
+.\" Copyright (c) 2021 Claudio Jeker <[email protected]>
+.\"
+.\" 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.
+.\"
+.Dd $Mdocdate$
+.Dt BGPLGD 8
+.Os
+.Sh NAME
+.Nm bgplgd
+.Nd a bgpctl FastCGI server
+.Sh SYNOPSIS
+.Nm
+.Op Fl d
+.Op Fl p Ar path
+.Op Fl S Ar socket
+.Op Fl s Ar socket
+.Op Fl U Ar user
+.Op Fl u Ar user
+.Sh DESCRIPTION
+.Nm
+is a server which implements the FastCGI Protocol to execute
+.Xr bgpctl 8
+commands.
+.Nm
+is a simple server that implements a simple web API to query
+.Xr bgpd 8 .
+.Pp
+.Nm
+opens a socket at
+.Pa /var/www/run/bgplgd.sock ,
+owned by www:www,
+with permissions 0660.
+It will then
+and drop privileges to user
+.Qq www ,
+.Xr unveil 2
+the
+.Xr bgpctl 8
+binary
+and restrict itself with
+.Xr pledge 2 .
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl d
+Do not daemonize.
+If this option is specified,
+.Nm
+will run in the foreground and log to stderr.
+.It Fl p Ar path
+Use
+.Ar path
+instead of
+.Xr bgpctl 8
+to query
+.Xr bgpd 8 .
+.It Fl S Ar socket
+Use
+.Ar socket
+instead of the default
+.Pa /var/run/bgpd.rsock
+to communicate with
+.Xr bgpd 8 .
+.It Fl s Ar socket
+Create and bind to alternative local socket at
+.Ar socket .
+.It Fl U Ar user
+Change the owner of
+.Pa /var/www/run/bgplgd.sock
+to
+.Ar user
+and its primary group instead of the default www:www.
+.El
+.Pp
+.Nm
+provides the following API endpoints.
+Unless further specified the endpoints do not take any parameters:
+.Bl -tag -width Ds
+.It Pa /interfaces
+Show the interface states.
+.It Pa /memory
+Show RIB memory statistics.
+.It Pa /neighbors
+Show detailed neighbors information.
+The output can be limited with the following parameters:
+.Pp
+.Bl -tag -width "neighbor=peer" -compact
+.It Cm neighbor Ns = Ns Ar peer
+Show infromation for a specific neighbor.
+.Ar peer
+may be the neighbor's address or description.
+.It Cm group Ns = Ns Ar name
+Show only entries from the specified peer group.
+.El
+.It Pa /nexthops
+Show the list of BGP nexthops and the result of their validity check.
+.It Pa /rib
+Show routes from the bgpd(8) Routing Information Base.
+The following parameters can be used to filter the output:
+.Pp
+.Bl -tag -width "neighbor=peer" -compact
+.It Cm neighbor Ns = Ns Ar peer
+Show infromation for a specific neighbor.
+.Ar peer
+may be the neighbor's address or description.
+.It Cm group Ns = Ns Ar name
+Show only entries from the specified peer group.
+.It Cm as Ns = Ns Ar number
+Show only entries with the specified source AS number.
+.It Cm community Ns = Ns Ar string
+.It Cm ext-community Ns = Ns Ar string
+.It Cm large-community Ns = Ns Ar string
+Show only entries that match the specified community.
+.It Xo
+.Ic af Ns = Ns
+.Pq Ic ipv4 | ipv6 | vpnv4 | vpnv6
+.Xc
+Show only entries that match the specified address family.
+.It Cm rib Ns = Ns Ar name
+Show only entries from the RIB with name
+.Ar name .
+.It Xo
+.Ic ovs Ns = Ns
+.Pq Ic valid | not-found | invalid
+.Xc
+Show only prefixes that match the specified Origin Validation State.
+.It Cm best Ns = Ns 1
+Show only selected routes.
+.It Cm error Ns = Ns 1
+Show only prefixes which are marked invalid and were treated as withdrawn.
+.It Cm prefix Ns = Ns Ar addr
+Show only entries that match prefix either as the best matching route or
+show the entry for this CIDR prefix.
+.It Cm all Ns = Ns 1
+Show all entries in the specified prefix range.
+.It Cm or-shorter Ns = Ns 1
+Show all entries covering and including the specified prefix.
+.El
+.It Pa /rtr
+Show a list of all RTR sessions.
+.It Pa /sets
+Show a list summarizing all roa-set, as-set, prefix-set, and origin-set tables.
+.It Pa /summary
+Show a list of all neighbors, including information about the session state
+and message counters.
+.El
+.Sh EXAMPLES
+An example setup in
+.Xr httpd 8
+is:
+.Bd -literal -offset indent
+        location "/bgplgd/*" {
+                fastcgi socket "/run/bgplgd.sock"
+                request strip 1
+        }
+.Ed
+.Sh SEE ALSO
+.Xr bgpctl 8 ,
+.Xr bgpd 8 ,
+.Xr httpd 8
+.Sh HISTORY
+The
+.Nm
+server first appeared in
+.Ox 7.2 .
+.Sh AUTHORS
+.An Claudio Jeker Aq Mt [email protected]
Index: bgplgd.c
===================================================================
RCS file: bgplgd.c
diff -N bgplgd.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ bgplgd.c    4 Feb 2022 15:16:25 -0000
@@ -0,0 +1,116 @@
+/*     $OpenBSD$ */
+/*
+ * Copyright (c) 2020 Claudio Jeker <[email protected]>
+ *
+ * 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/queue.h>
+#include <err.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "bgplgd.h"
+
+#define NCMDARGS       4
+
+const struct cmd {
+       const char      *path;
+       char            *args[NCMDARGS];
+       unsigned int    qs_mask;
+       int             barenbr;
+} cmds[] = {
+       { "/interfaces", { "show", "interfaces", NULL }, 0 },
+       { "/memory", { "show", "rib", "memory", NULL }, 0 },
+       { "/neighbors", { "show", "neighbor", NULL }, QS_MASK_NEIGHBOR, 1 },
+       { "/nexthops", { "show", "nexthop", NULL }, 0 },
+       { "/rib", { "show", "rib", "detail", NULL }, QS_MASK_RIB },
+       { "/rtr", { "show", "rtr", NULL }, 0 },
+       { "/sets", { "show", "sets", NULL }, 0 },
+       { "/summary", { "show", NULL }, 0 },
+       { NULL }
+};
+
+static int
+command_from_path(const char *path, struct lg_ctx *ctx)
+{
+       size_t i;
+
+       for (i = 0; cmds[i].path != NULL; i++) {
+               if (strcmp(cmds[i].path, path) == 0) {
+                       ctx->command = &cmds[i];
+                       ctx->qs_mask = cmds[i].qs_mask;
+                       return 0;
+               }
+       }
+       return 404;
+}
+
+/*
+ * Prepare a request into a context to call bgpctl.
+ * Parse method, path and querystring. On failure return the correct
+ * HTTP error code. On success 0 is returned.
+ */
+int
+prep_request(struct lg_ctx *ctx, const char *meth, const char *path,
+    const char *qs)
+{
+       if (meth == NULL || path == NULL)
+               return 500;
+       if (strcmp(meth, "GET") != 0)
+               return 405;
+       if (command_from_path(path, ctx) != 0)
+               return 404;
+       if (parse_querystring(qs, ctx) != 0)
+               return 400;
+
+       return 0;
+}
+
+/*
+ * Entry point from the FastCGI handler.
+ * This runs as an own process and must use STDOUT and STDERR.
+ * The log functions should no longer be used here.
+ */
+void
+bgpctl_call(struct lg_ctx *ctx)
+{
+       char *argv[64];
+       size_t i, argc = 0;
+
+       argv[argc++] = bgpctlpath;
+       argv[argc++] = "-j";
+       argv[argc++] = "-s";
+       argv[argc++] = bgpctlsock;
+
+       for (i = 0; ctx->command->args[i] != NULL; i++)
+               argv[argc++] = ctx->command->args[i];
+
+       argc = qs_argv(argv, argc, sizeof(argv) / sizeof(argv[0]), ctx,
+           ctx->command->barenbr);
+
+       argv[argc++] = NULL;
+
+       signal(SIGPIPE, SIG_DFL);
+
+       /* Write server header first */
+       printf("Content-type: application/json\r\n\r\n");
+       fflush(stdout);
+
+       execvp(bgpctlpath, argv);
+
+       err(1, "failed to execute %s", bgpctlpath);
+}
Index: bgplgd.h
===================================================================
RCS file: bgplgd.h
diff -N bgplgd.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ bgplgd.h    4 Feb 2022 15:16:25 -0000
@@ -0,0 +1,65 @@
+/*     $OpenBSD$ */
+/*
+ * Copyright (c) 2020 Claudio Jeker <[email protected]>
+ *
+ * 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.
+ */
+
+#define QS_NEIGHBOR            1
+#define QS_GROUP               2
+#define QS_AS                  3
+#define QS_PREFIX              4
+#define QS_COMMUNITY           5
+#define QS_EXTCOMMUNITY                6
+#define QS_LARGECOMMUNITY      7
+#define QS_AF                  8
+#define QS_RIB                 9
+#define QS_OVS                 10
+#define QS_BEST                        11
+#define QS_ALL                 12
+#define QS_SHORTER             13
+#define QS_ERROR               14
+#define QS_MAX                 15
+
+/* too add: empty-as, in, out, peer-as, source-as, transit-as */
+
+#define QS_MASK_NEIGHBOR       ((1 << QS_NEIGHBOR) | (1 << QS_GROUP))
+#define QS_MASK_RIB                                            \
+       ((1 << QS_NEIGHBOR) | (1 << QS_GROUP) | (1 << QS_AS) |  \
+       (1 << QS_PREFIX) | (1 << QS_COMMUNITY) |                \
+       (1 << QS_EXTCOMMUNITY) | (1 << QS_LARGECOMMUNITY) |     \
+       (1 << QS_AF) |  (1 << QS_RIB) | (1 << QS_OVS) |         \
+       (1 << QS_BEST) | (1 << QS_ALL) | (1 << QS_SHORTER) |    \
+       (1 << QS_ERROR))
+
+struct cmd;
+struct lg_ctx {
+       const struct cmd        *command;
+       unsigned int            qs_mask;
+       unsigned int            qs_set;
+       union {
+               char    *string;
+               int     one;
+       }                       qs_args[QS_MAX];
+};
+
+extern char    *bgpctlpath;
+extern char    *bgpctlsock;
+
+/* qs.c - query string handling */
+int    parse_querystring(const char *, struct lg_ctx *);
+size_t qs_argv(char **, size_t, size_t, struct lg_ctx *, int);
+
+/* main entry points for slowcgi */
+int    prep_request(struct lg_ctx *, const char *, const char *, const char *);
+void   bgpctl_call(struct lg_ctx *);
Index: http.h
===================================================================
RCS file: http.h
diff -N http.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ http.h      4 Feb 2022 15:16:25 -0000
@@ -0,0 +1,104 @@
+/*     $OpenBSD: http.h,v 1.15 2019/05/08 21:41:06 tb Exp $    */
+
+/*
+ * Copyright (c) 2012 - 2015 Reyk Floeter <[email protected]>
+ *
+ * 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.
+ */
+
+struct http_error {
+       int                      error_code;
+       const char              *error_name;
+};
+
+/*
+ * HTTP status codes based on IANA assignments (2014-06-11 version):
+ * https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
+ * plus legacy (306) and non-standard (420).
+ */
+#define HTTP_ERRORS            {                       \
+       { 100,  "Continue" },                           \
+       { 101,  "Switching Protocols" },                \
+       { 102,  "Processing" },                         \
+       /* 103-199 unassigned */                        \
+       { 200,  "OK" },                                 \
+       { 201,  "Created" },                            \
+       { 202,  "Accepted" },                           \
+       { 203,  "Non-Authoritative Information" },      \
+       { 204,  "No Content" },                         \
+       { 205,  "Reset Content" },                      \
+       { 206,  "Partial Content" },                    \
+       { 207,  "Multi-Status" },                       \
+       { 208,  "Already Reported" },                   \
+       /* 209-225 unassigned */                        \
+       { 226,  "IM Used" },                            \
+       /* 227-299 unassigned */                        \
+       { 300,  "Multiple Choices" },                   \
+       { 301,  "Moved Permanently" },                  \
+       { 302,  "Found" },                              \
+       { 303,  "See Other" },                          \
+       { 304,  "Not Modified" },                       \
+       { 305,  "Use Proxy" },                          \
+       { 306,  "Switch Proxy" },                       \
+       { 307,  "Temporary Redirect" },                 \
+       { 308,  "Permanent Redirect" },                 \
+       /* 309-399 unassigned */                        \
+       { 400,  "Bad Request" },                        \
+       { 401,  "Unauthorized" },                       \
+       { 402,  "Payment Required" },                   \
+       { 403,  "Forbidden" },                          \
+       { 404,  "Not Found" },                          \
+       { 405,  "Method Not Allowed" },                 \
+       { 406,  "Not Acceptable" },                     \
+       { 407,  "Proxy Authentication Required" },      \
+       { 408,  "Request Timeout" },                    \
+       { 409,  "Conflict" },                           \
+       { 410,  "Gone" },                               \
+       { 411,  "Length Required" },                    \
+       { 412,  "Precondition Failed" },                \
+       { 413,  "Payload Too Large" },                  \
+       { 414,  "URI Too Long" },                       \
+       { 415,  "Unsupported Media Type" },             \
+       { 416,  "Range Not Satisfiable" },              \
+       { 417,  "Expectation Failed" },                 \
+       { 418,  "I'm a teapot" },                       \
+       /* 419-421 unassigned */                        \
+       { 420,  "Enhance Your Calm" },                  \
+       { 422,  "Unprocessable Entity" },               \
+       { 423,  "Locked" },                             \
+       { 424,  "Failed Dependency" },                  \
+       /* 425 unassigned */                            \
+       { 426,  "Upgrade Required" },                   \
+       /* 427 unassigned */                            \
+       { 428,  "Precondition Required" },              \
+       { 429,  "Too Many Requests" },                  \
+       /* 430 unassigned */                            \
+       { 431,  "Request Header Fields Too Large" },    \
+       /* 432-450 unassigned */                        \
+       { 451,  "Unavailable For Legal Reasons" },      \
+       /* 452-499 unassigned */                        \
+       { 500,  "Internal Server Error" },              \
+       { 501,  "Not Implemented" },                    \
+       { 502,  "Bad Gateway" },                        \
+       { 503,  "Service Unavailable" },                \
+       { 504,  "Gateway Timeout" },                    \
+       { 505,  "HTTP Version Not Supported" },         \
+       { 506,  "Variant Also Negotiates" },            \
+       { 507,  "Insufficient Storage" },               \
+       { 508,  "Loop Detected" },                      \
+       /* 509 unassigned */                            \
+       { 510,  "Not Extended" },                       \
+       { 511,  "Network Authentication Required" },    \
+       /* 512-599 unassigned */                        \
+       { 0,    NULL }                                  \
+}
Index: qs.c
===================================================================
RCS file: qs.c
diff -N qs.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ qs.c        4 Feb 2022 15:16:25 -0000
@@ -0,0 +1,400 @@
+/*     $OpenBSD$ */
+/*
+ * Copyright (c) 2020 Claudio Jeker <[email protected]>
+ *
+ * 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/socket.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "bgplgd.h"
+#include "slowcgi.h"
+
+enum qs_type {
+       ONE,
+       STRING,
+       PREFIX,
+       NUMBER,
+       FAMILY,
+       OVS
+};
+
+const struct qs {
+       unsigned int    qs;
+       const char      *key;
+       enum qs_type    type;
+} qsargs[] = {
+       { QS_NEIGHBOR, "neighbor", STRING, },
+       { QS_GROUP, "group", STRING },
+       { QS_AS, "as", NUMBER },
+       { QS_PREFIX, "prefix", PREFIX },
+       { QS_COMMUNITY, "community", STRING },
+       { QS_EXTCOMMUNITY, "ext-community", STRING },
+       { QS_LARGECOMMUNITY, "large-community", STRING },
+       { QS_AF, "af", FAMILY },
+       { QS_RIB, "rib", STRING },
+       { QS_OVS, "ovs", OVS },
+       { QS_BEST, "best", ONE },
+       { QS_ALL, "all", ONE },
+       { QS_SHORTER, "or-shorter", ONE },
+       { QS_ERROR, "error", ONE },
+       { 0, NULL }
+};
+
+const char *qs2str(unsigned int qs);
+
+static int
+hex(char x)
+{
+       if ('0' <= x && x <= '9')
+               return x - '0';
+       if ('a' <= x && x <= 'f')
+               return x - 'a' + 10;
+       else
+               return x - 'A' + 10;
+}
+
+static char *
+urldecode(const char *s, size_t len)
+{
+       static char buf[256];
+       size_t i, blen = 0;
+
+       for (i = 0; i < len; i++) {
+               if (blen >= sizeof(buf))
+                       return NULL;
+               if (s[i] == '+') {
+                       buf[blen++] = ' ';
+               } else if (s[i] == '%' && i + 2 < len) {
+                       if (isxdigit((unsigned char)s[i + 1]) &&
+                           isxdigit((unsigned char)s[i + 2])) {
+                               char c;
+                               c = hex(s[i + 1]) << 4 | hex(s[i + 2]);
+                               /* replace NUL chars with space */
+                               if (c == 0)
+                                       c = ' ';
+                               buf[blen++] = c;
+                               i += 2;
+                       } else
+                               buf[blen++] = s[i];
+               } else {
+                       buf[blen++] = s[i];
+               }
+       }
+       buf[blen] = '\0';
+
+       return buf;
+}
+
+static int
+valid_string(const char *str)
+{
+       unsigned char c;
+
+       while ((c = *str++) != '\0')
+               if (!isalnum(c) && !ispunct(c) && c != ' ')
+                       return 0;
+       return 1;
+}
+
+/* validate that the input is pure decimal number */
+static int
+valid_number(const char *str)
+{
+       unsigned char c;
+       int first = 1;
+
+       while ((c = *str++) != '\0') {
+               /* special handling of 0 */
+               if (first && c == '0') {
+                       if (*str != '\0')
+                               return 0;
+               }
+               first = 0;
+               if (!isdigit(c))
+                       return 0;
+       }
+       return 1;
+}
+
+/* validate a prefix, does not support old 10/8 notation but that is ok */
+static int
+valid_prefix(char *str)
+{
+       struct addrinfo hints, *res;
+       char *p;
+       int mask;
+
+       if ((p = strrchr(str, '/')) != NULL) {
+               const char *errstr;
+               mask = strtonum(p+1, 0, 128, &errstr);
+               if (errstr)
+                       return 0;
+               p[0] = '\0';
+       }
+
+       bzero(&hints, sizeof(hints));
+       hints.ai_family = AF_UNSPEC;
+       hints.ai_socktype = SOCK_DGRAM;
+       hints.ai_flags = AI_NUMERICHOST;
+       if (getaddrinfo(str, NULL, &hints, &res) != 0)
+               return 0;
+       if (p) {
+               if (res->ai_family == AF_INET && mask > 32)
+                       return 0;
+               p[0] = '/';
+       }
+       freeaddrinfo(res);
+       return 1;
+}
+
+static int
+parse_value(struct lg_ctx *ctx, unsigned int qs, enum qs_type type, char *val)
+{
+       /* val can only be NULL if urldecode failed. */
+       if (val == NULL) {
+               lwarnx("urldecode of querystring failed");
+               return 400;
+       }
+
+       switch (type) {
+       case ONE:
+               if (strcmp("1", val) == 0) {
+                       ctx->qs_args[qs].one = 1;
+               } else if (strcmp("0", val) == 0) {
+                       /* silently ignored */
+               } else {
+                       lwarnx("%s: bad value %s expected 1", qs2str(qs), val);
+                       return 400;
+               }
+               break;
+       case STRING:
+               /* limit string to limited ascii chars */
+               if (!valid_string(val)) {
+                       lwarnx("%s: bad string", qs2str(qs));
+                       return 400;
+               }
+               ctx->qs_args[qs].string = strdup(val);
+               if (ctx->qs_args[qs].string == NULL) {
+                       lwarn("parse_value");
+                       return 500;
+               }
+               break;
+       case NUMBER:
+               if (!valid_number(val)) {
+                       lwarnx("%s: bad number", qs2str(qs));
+                       return 400;
+               }
+               ctx->qs_args[qs].string = strdup(val);
+               if (ctx->qs_args[qs].string == NULL) {
+                       lwarn("parse_value");
+                       return 500;
+               }
+               break;
+       case PREFIX:
+               if (!valid_prefix(val)) {
+                       lwarnx("%s: bad prefix", qs2str(qs));
+                       return 400;
+               }
+               ctx->qs_args[qs].string = strdup(val);
+               if (ctx->qs_args[qs].string == NULL) {
+                       lwarn("parse_value");
+                       return 500;
+               }
+               break;
+       case FAMILY:
+               if (strcasecmp("ipv4", val) == 0 ||
+                   strcasecmp("ipv6", val) == 0 ||
+                   strcasecmp("vpnv4", val) == 0 ||
+                   strcasecmp("vpnv6", val) == 0) {
+                       ctx->qs_args[qs].string = strdup(val);
+                       if (ctx->qs_args[qs].string == NULL) {
+                               lwarn("parse_value");
+                               return 500;
+                       }
+               } else {
+                       lwarnx("%s: bad value %s", qs2str(qs), val);
+                       return 400;
+               }
+               break;
+       case OVS:
+               if (strcmp("not-found", val) == 0 ||
+                   strcmp("valid", val) == 0 ||
+                   strcmp("invalid", val) == 0) {
+                       ctx->qs_args[qs].string = strdup(val);
+                       if (ctx->qs_args[qs].string == NULL) {
+                               lwarn("parse_value");
+                               return 500;
+                       }
+               } else {
+                       lwarnx("%s: bad OVS value %s", qs2str(qs), val);
+                       return 400;
+               }
+               break;
+       }
+       return 0;
+}
+
+int
+parse_querystring(const char *param, struct lg_ctx *ctx)
+{
+       size_t len, i;
+       int rv;
+
+       while (param && *param) {
+               len = strcspn(param, "=");
+               for (i = 0; qsargs[i].key != NULL; i++)
+                       if (strncmp(qsargs[i].key, param, len) == 0)
+                               break;
+               if (qsargs[i].key == NULL) {
+                       lwarnx("unknown querystring key %.*s", (int)len, param);
+                       return 400;
+               }
+               if (((1 << qsargs[i].qs) & ctx->qs_mask) == 0) {
+                       lwarnx("querystring param %s not allowed for command",
+                           qsargs[i].key);
+                       return 400;
+               }
+               if (((1 << qsargs[i].qs) & ctx->qs_set) != 0) {
+                       lwarnx("querystring param %s already set",
+                           qsargs[i].key);
+                       return 400;
+               }
+               ctx->qs_set |= (1 << qsargs[i].qs);
+
+               if (param[len] != '=') {
+                       lwarnx("querystring %s without value", qsargs[i].key);
+                       return 400;
+               }
+
+               param += len + 1;
+               len = strcspn(param, "&");
+
+               if ((rv = parse_value(ctx, qsargs[i].qs, qsargs[i].type,
+                   urldecode(param, len))) != 0)
+                       return rv;
+
+               param += len;
+               if (*param == '&')
+                       param++;
+       }
+
+       return 0;
+}
+
+size_t
+qs_argv(char **argv, size_t argc, size_t len, struct lg_ctx *ctx, int barenbr)
+{
+       /* keep space for the final NULL in argv */
+       len -= 1;
+
+       /* NEIGHBOR and GROUP are exclusive */
+       if (ctx->qs_set & (1 << QS_NEIGHBOR)) {
+               if (!barenbr)
+                       if (argc < len)
+                               argv[argc++] = "neighbor";
+               if (argc < len)
+                       argv[argc++] = ctx->qs_args[QS_NEIGHBOR].string;
+       } else if (ctx->qs_set & (1 << QS_GROUP)) {
+               if (argc < len)
+                       argv[argc++] = "group";
+               if (argc < len)
+                       argv[argc++] = ctx->qs_args[QS_GROUP].string;
+       }
+
+       if (ctx->qs_set & (1 << QS_AS)) {
+               if (argc < len)
+                       argv[argc++] = "source-as";
+               if (argc < len)
+                       argv[argc++] = ctx->qs_args[QS_AS].string;
+       }
+       if (ctx->qs_set & (1 << QS_COMMUNITY)) {
+               if (argc < len)
+                       argv[argc++] = "community";
+               if (argc < len)
+                       argv[argc++] = ctx->qs_args[QS_COMMUNITY].string;
+       }
+       if (ctx->qs_set & (1 << QS_EXTCOMMUNITY)) {
+               if (argc < len)
+                       argv[argc++] = "ext-community";
+               if (argc < len)
+                       argv[argc++] = ctx->qs_args[QS_EXTCOMMUNITY].string;
+       }
+       if (ctx->qs_set & (1 << QS_LARGECOMMUNITY)) {
+               if (argc < len)
+                       argv[argc++] = "large-community";
+               if (argc < len)
+                       argv[argc++] = ctx->qs_args[QS_LARGECOMMUNITY].string;
+       }
+       if (ctx->qs_set & (1 << QS_AF)) {
+               if (argc < len)
+                       argv[argc++] = ctx->qs_args[QS_AF].string;
+       }
+       if (ctx->qs_set & (1 << QS_RIB)) {
+               if (argc < len)
+                       argv[argc++] = "rib";
+               if (argc < len)
+                       argv[argc++] = ctx->qs_args[QS_RIB].string;
+       }
+       if (ctx->qs_set & (1 << QS_OVS)) {
+               if (argc < len)
+                       argv[argc++] = "ovs";
+               if (argc < len)
+                       argv[argc++] = ctx->qs_args[QS_OVS].string;
+       }
+       /* BEST and ERROR are exclusive */
+       if (ctx->qs_args[QS_BEST].one) {
+               if (argc < len)
+                       argv[argc++] = "best";
+       } else if (ctx->qs_args[QS_ERROR].one) {
+               if (argc < len)
+                       argv[argc++] = "error";
+       }
+
+       /* prefix must be last for show rib */
+       if (ctx->qs_set & (1 << QS_PREFIX)) {
+               if (argc < len)
+                       argv[argc++] = ctx->qs_args[QS_PREFIX].string;
+
+               /* ALL and SHORTER are exclusive */
+               if (ctx->qs_args[QS_ALL].one) {
+                       if (argc < len)
+                               argv[argc++] = "all";
+               } else if (ctx->qs_args[QS_SHORTER].one) {
+                       if (argc < len)
+                               argv[argc++] = "or-shorter";
+               }
+       }
+
+       if (argc >= len)
+               lwarnx("hit limit of argv in qs_argv");
+
+       return argc;
+}
+
+const char *
+qs2str(unsigned int qs)
+{
+       size_t i;
+
+       for (i = 0; qsargs[i].key != NULL; i++)
+               if (qsargs[i].qs == qs)
+                       return qsargs[i].key;
+
+       lerrx(1, "unknown querystring param %d", qs);
+}
Index: slowcgi.c
===================================================================
RCS file: slowcgi.c
diff -N slowcgi.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ slowcgi.c   28 Jun 2022 14:42:21 -0000
@@ -0,0 +1,1279 @@
+/*     $OpenBSD: slowcgi.c,v 1.31 2014/04/16 14:43:43 florian Exp $ */
+/*
+ * Copyright (c) 2020 Claudio Jeker <[email protected]>
+ * Copyright (c) 2019 Kristaps Dzonsons <[email protected]>
+ * Copyright (c) 2013 David Gwynne <[email protected]>
+ * Copyright (c) 2013 Florian Obser <[email protected]>
+ *
+ * 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/ioctl.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <err.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <event.h>
+#include <limits.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "slowcgi.h"
+#include "bgplgd.h"
+#include "http.h"
+
+#define TIMEOUT_DEFAULT                 120
+#define WWW_USER                "www"
+#define BGPLGD_USER             "_bgplgd"
+
+#define FCGI_CONTENT_SIZE       65535
+#define FCGI_PADDING_SIZE       255
+#define FCGI_RECORD_SIZE        \
+    (sizeof(struct fcgi_record_header) + FCGI_CONTENT_SIZE + FCGI_PADDING_SIZE)
+
+#define FCGI_ALIGNMENT          8
+#define FCGI_ALIGN(n)           \
+    (((n) + (FCGI_ALIGNMENT - 1)) & ~(FCGI_ALIGNMENT - 1))
+
+#define STDOUT_DONE            0x1
+#define STDERR_DONE            0x2
+#define SCRIPT_DONE            0x4
+
+#define FCGI_REQUEST_COMPLETE  0
+#define FCGI_CANT_MPX_CONN     1
+#define FCGI_OVERLOADED                2
+#define FCGI_UNKNOWN_ROLE      3
+
+#define FD_RESERVE             5
+#define FD_NEEDED              6
+int cgi_inflight = 0;
+
+struct listener {
+       struct event    ev, pause;
+};
+
+struct env_val {
+       SLIST_ENTRY(env_val)     entry;
+       char                    *key;
+       char                    *val;
+};
+SLIST_HEAD(env_head, env_val);
+
+struct fcgi_record_header {
+       uint8_t         version;
+       uint8_t         type;
+       uint16_t        id;
+       uint16_t        content_len;
+       uint8_t         padding_len;
+       uint8_t         reserved;
+}__packed;
+
+struct fcgi_response {
+       TAILQ_ENTRY(fcgi_response)      entry;
+       uint8_t                         data[FCGI_RECORD_SIZE];
+       size_t                          data_pos;
+       size_t                          data_len;
+};
+TAILQ_HEAD(fcgi_response_head, fcgi_response);
+
+struct request {
+       LIST_ENTRY(request)             entry;
+       struct event                    ev;
+       struct event                    resp_ev;
+       struct event                    tmo;
+       struct event                    script_ev;
+       struct event                    script_err_ev;
+       int                             fd;
+       uint8_t                         buf[FCGI_RECORD_SIZE];
+       size_t                          buf_pos;
+       size_t                          buf_len;
+       struct fcgi_response_head       response_head;
+       struct env_head                 env;
+       int                             env_count;
+       pid_t                           command_pid;
+       int                             command_status;
+       int                             script_flags;
+       uint16_t                        id;
+       uint8_t                         request_started;
+       uint8_t                         request_done;
+       int                             inflight_fds_accounted;
+};
+
+LIST_HEAD(requests_head, request);
+
+struct slowcgi_proc {
+       struct requests_head    requests;
+       struct event            ev_sigchld;
+};
+
+struct fcgi_begin_request_body {
+       uint16_t        role;
+       uint8_t         flags;
+       uint8_t         reserved[5];
+}__packed;
+
+struct fcgi_end_request_body {
+       uint32_t        app_status;
+       uint8_t         protocol_status;
+       uint8_t         reserved[3];
+}__packed;
+
+__dead void    usage(void);
+int            slowcgi_listen(char *, struct passwd *);
+void           slowcgi_paused(int, short, void *);
+int            accept_reserve(int, struct sockaddr *, socklen_t *, int,
+                   volatile int *);
+void           slowcgi_accept(int, short, void *);
+void           slowcgi_request(int, short, void *);
+void           slowcgi_response(int, short, void *);
+void           slowcgi_add_response(struct request *, struct fcgi_response *);
+void           slowcgi_timeout(int, short, void *);
+void           slowcgi_sig_handler(int, short, void *);
+size_t         parse_record(uint8_t * , size_t, struct request *);
+void           parse_begin_request(uint8_t *, uint16_t, struct request *,
+                   uint16_t);
+void           parse_params(uint8_t *, uint16_t, struct request *, uint16_t);
+void           parse_stdin(uint8_t *, uint16_t, struct request *, uint16_t);
+char           *env_get(struct request *, const char *);
+void           exec_cgi(struct request *);
+void           script_std_in(int, short, void *);
+void           script_err_in(int, short, void *);
+void           create_data_record(struct request *, uint8_t, const void *,
+                   size_t);
+void           create_end_record(struct request *);
+void           cleanup_request(struct request *);
+void           dump_fcgi_record(const char *,
+                   struct fcgi_record_header *);
+void           dump_fcgi_record_header(const char *,
+                   struct fcgi_record_header *);
+void           dump_fcgi_begin_request_body(const char *,
+                   struct fcgi_begin_request_body *);
+void           dump_fcgi_end_request_body(const char *,
+                   struct fcgi_end_request_body *);
+
+const struct loggers conslogger = {
+       err,
+       errx,
+       warn,
+       warnx,
+       warnx, /* info */
+       warnx /* debug */
+};
+
+__dead void    syslog_err(int, const char *, ...)
+                   __attribute__((__format__ (printf, 2, 3)));
+__dead void    syslog_errx(int, const char *, ...)
+                   __attribute__((__format__ (printf, 2, 3)));
+void           syslog_warn(const char *, ...)
+                   __attribute__((__format__ (printf, 1, 2)));
+void           syslog_warnx(const char *, ...)
+                   __attribute__((__format__ (printf, 1, 2)));
+void           syslog_info(const char *, ...)
+                   __attribute__((__format__ (printf, 1, 2)));
+void           syslog_debug(const char *, ...)
+                   __attribute__((__format__ (printf, 1, 2)));
+void           syslog_vstrerror(int, int, const char *, va_list)
+                   __attribute__((__format__ (printf, 3, 0)));
+
+const struct loggers syslogger = {
+       syslog_err,
+       syslog_errx,
+       syslog_warn,
+       syslog_warnx,
+       syslog_info,
+       syslog_debug
+};
+
+const struct loggers *logger = &conslogger;
+
+__dead void
+usage(void)
+{
+       extern char *__progname;
+       fprintf(stderr,
+           "usage: %s [-d] [-p path] [-S socket] [-s socket] [-U user]\n",
+           __progname);
+       exit(1);
+}
+
+struct timeval         timeout = { TIMEOUT_DEFAULT, 0 };
+struct slowcgi_proc    slowcgi_proc;
+int                    debug = 0;
+int                    on = 1;
+char                   *fcgi_socket = "/var/www/run/bgplgd.sock";
+char                   *bgpctlpath = "bgpctl";
+char                   *bgpctlsock = "/var/run/bgpd.rsock";
+
+
+/*
+ * Unveil the command we want to run.
+ * If this has a pathname component in it, interpret as a file
+ * and unveil the file directly.
+ * Otherwise, look up the command in our PATH.
+ */
+static void
+unveil_command(const char *prog)
+{
+       const char *pp;
+       char *save, *cmd, *path;
+       struct stat st;
+
+       if (strchr(prog, '/') != NULL) {
+               if (unveil(prog, "x") == -1)
+                       err(1, "%s: unveil", prog);
+               return;
+       }
+
+       if (getenv("PATH") == NULL)
+               lerrx(1, "PATH is unset");
+       if ((path = strdup(getenv("PATH"))) == NULL)
+               lerr(1, NULL);
+       save = path;
+       while ((pp = strsep(&path, ":")) != NULL) {
+               if (*pp == '\0')
+                       continue;
+               if (asprintf(&cmd, "%s/%s", pp, prog) == -1)
+                       lerr(1, NULL);
+               if (lstat(cmd, &st) == -1) {
+                       free(cmd);
+                       continue;
+               }
+               if (unveil(cmd, "x") == -1)
+                       lerr(1, "%s: unveil", cmd);
+               free(cmd);
+               break;
+       }
+       free(save);
+}
+
+int
+main(int argc, char *argv[])
+{
+       extern char *__progname;
+       struct listener *l = NULL;
+       struct passwd   *pw;
+       struct stat      sb;
+       int              c, fd;
+       const char      *sock_user = WWW_USER;
+       const char      *cgi_user = BGPLGD_USER;
+
+       /*
+        * Ensure we have fds 0-2 open so that we have no fd overlaps
+        * in exec_cgi() later. Just exit on error, we don't have enough
+        * fds open to output an error message anywhere.
+        */
+       for (c=0; c < 3; c++) {
+               if (fstat(c, &sb) == -1) {
+                       if ((fd = open("/dev/null", O_RDWR)) != -1) {
+                               if (dup2(fd, c) == -1)
+                                       exit(1);
+                               if (fd > c)
+                                       close(fd);
+                       } else
+                               exit(1);
+               }
+       }
+
+       while ((c = getopt(argc, argv, "dp:S:s:U:u:")) != -1) {
+               switch (c) {
+               case 'd':
+                       debug++;
+                       break;
+               case 'p':
+                       bgpctlpath = optarg;
+                       break;
+               case 'S':
+                       bgpctlsock = optarg;
+                       break;
+               case 's':
+                       fcgi_socket = optarg;
+                       break;
+               case 'U':
+                       sock_user = optarg;
+                       break;
+               default:
+                       usage();
+                       /* NOTREACHED */
+               }
+       }
+
+       if (geteuid() != 0)
+               errx(1, "need root privileges");
+
+       if (!debug && daemon(0, 0) == -1)
+               err(1, "daemon");
+
+       if (!debug) {
+               openlog(__progname, LOG_PID|LOG_NDELAY, LOG_DAEMON);
+               logger = &syslogger;
+       }
+
+       ldebug("sock_user: %s", sock_user);
+       pw = getpwnam(sock_user);
+       if (pw == NULL)
+               lerrx(1, "no %s user", sock_user);
+
+       fd = slowcgi_listen(fcgi_socket, pw);
+
+       ldebug("cgi_user: %s", cgi_user);
+       pw = getpwnam(cgi_user);
+       if (pw == NULL)
+               lerrx(1, "no %s user", cgi_user);
+
+       if (setgroups(1, &pw->pw_gid) ||
+           setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
+           setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
+               lerr(1, "unable to revoke privs");
+
+       unveil_command(bgpctlpath);
+
+       if (pledge("stdio rpath unix proc exec", NULL) == -1)
+               lerr(1, "pledge");
+
+       LIST_INIT(&slowcgi_proc.requests);
+       event_init();
+
+       l = calloc(1, sizeof(*l));
+       if (l == NULL)
+               lerr(1, "listener ev alloc");
+
+       event_set(&l->ev, fd, EV_READ | EV_PERSIST, slowcgi_accept, l);
+       event_add(&l->ev, NULL);
+       evtimer_set(&l->pause, slowcgi_paused, l);
+
+       signal_set(&slowcgi_proc.ev_sigchld, SIGCHLD, slowcgi_sig_handler,
+           &slowcgi_proc);
+       signal_add(&slowcgi_proc.ev_sigchld, NULL);
+
+       signal(SIGPIPE, SIG_IGN);
+
+       event_dispatch();
+       return (0);
+}
+
+int
+slowcgi_listen(char *path, struct passwd *pw)
+{
+       struct sockaddr_un       sun;
+       mode_t                   old_umask;
+       int                      fd;
+
+       if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
+           0)) == -1)
+               lerr(1, "slowcgi_listen: socket");
+
+       bzero(&sun, sizeof(sun));
+       sun.sun_family = AF_UNIX;
+       if (strlcpy(sun.sun_path, path, sizeof(sun.sun_path)) >=
+           sizeof(sun.sun_path))
+               lerrx(1, "socket path too long");
+
+       if (unlink(path) == -1)
+               if (errno != ENOENT)
+                       lerr(1, "slowcgi_listen: unlink %s", path);
+
+       old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
+
+       if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1)
+               lerr(1,"slowcgi_listen: bind: %s", path);
+
+       umask(old_umask);
+
+       if (chown(path, pw->pw_uid, pw->pw_gid) == -1)
+               lerr(1, "slowcgi_listen: chown: %s", path);
+
+       if (listen(fd, 5) == -1)
+               lerr(1, "listen");
+
+       ldebug("socket: %s", path);
+       return fd;
+}
+
+void
+slowcgi_paused(int fd, short events, void *arg)
+{
+       struct listener *l = arg;
+       event_add(&l->ev, NULL);
+}
+
+int
+accept_reserve(int sockfd, struct sockaddr *addr, socklen_t *addrlen,
+    int reserve, volatile int *counter)
+{
+       int ret;
+       if (getdtablecount() + reserve +
+           ((*counter + 1) * FD_NEEDED) >= getdtablesize()) {
+               ldebug("inflight fds exceeded");
+               errno = EMFILE;
+               return -1;
+       }
+
+       if ((ret = accept4(sockfd, addr, addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC))
+           > -1) {
+               (*counter)++;
+               ldebug("inflight incremented, now %d", *counter);
+       }
+       return ret;
+}
+
+void
+slowcgi_accept(int fd, short events, void *arg)
+{
+       struct listener         *l;
+       struct sockaddr_storage  ss;
+       struct timeval           backoff;
+       struct request          *c;
+       socklen_t                len;
+       int                      s;
+
+       l = arg;
+       backoff.tv_sec = 1;
+       backoff.tv_usec = 0;
+       c = NULL;
+
+       len = sizeof(ss);
+       if ((s = accept_reserve(fd, (struct sockaddr *)&ss,
+           &len, FD_RESERVE, &cgi_inflight)) == -1) {
+               switch (errno) {
+               case EINTR:
+               case EWOULDBLOCK:
+               case ECONNABORTED:
+                       return;
+               case EMFILE:
+               case ENFILE:
+                       event_del(&l->ev);
+                       evtimer_add(&l->pause, &backoff);
+                       return;
+               default:
+                       lerr(1, "accept");
+               }
+       }
+
+       c = calloc(1, sizeof(*c));
+       if (c == NULL) {
+               lwarn("cannot calloc request");
+               close(s);
+               cgi_inflight--;
+               return;
+       }
+       c->fd = s;
+       c->buf_pos = 0;
+       c->buf_len = 0;
+       c->request_started = 0;
+       c->inflight_fds_accounted = 0;
+       TAILQ_INIT(&c->response_head);
+
+       event_set(&c->ev, s, EV_READ | EV_PERSIST, slowcgi_request, c);
+       event_add(&c->ev, NULL);
+       event_set(&c->resp_ev, s, EV_WRITE | EV_PERSIST, slowcgi_response, c);
+       evtimer_set(&c->tmo, slowcgi_timeout, c);
+       evtimer_add(&c->tmo, &timeout);
+       LIST_INSERT_HEAD(&slowcgi_proc.requests, c, entry);
+}
+
+void
+slowcgi_timeout(int fd, short events, void *arg)
+{
+       cleanup_request((struct request*) arg);
+}
+
+void
+slowcgi_sig_handler(int sig, short event, void *arg)
+{
+       struct request          *c;
+       struct slowcgi_proc     *p;
+       pid_t                    pid;
+       int                      status;
+
+       p = arg;
+
+       switch (sig) {
+       case SIGCHLD:
+               while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) > 0) {
+                       LIST_FOREACH(c, &p->requests, entry)
+                               if (c->command_pid == pid)
+                                       break;
+                       if (c == NULL) {
+                               lwarnx("caught exit of unknown child %i", pid);
+                               continue;
+                       }
+
+                       if (WIFSIGNALED(status))
+                               c->command_status = WTERMSIG(status);
+                       else
+                               c->command_status = WEXITSTATUS(status);
+
+                       ldebug("exit %s%d",
+                           WIFSIGNALED(status) ? "signal" : "",
+                           c->command_status);
+
+                       c->script_flags |= SCRIPT_DONE;
+
+                       if (c->script_flags == (STDOUT_DONE | STDERR_DONE |
+                           SCRIPT_DONE))
+                               create_end_record(c);
+               }
+               if (pid == -1 && errno != ECHILD)
+                       lwarn("waitpid");
+               break;
+       default:
+               lerr(1, "unexpected signal: %d", sig);
+               break;
+       }
+}
+
+void
+slowcgi_add_response(struct request *c, struct fcgi_response *resp)
+{
+       struct fcgi_record_header       *header;
+       size_t                           padded_len;
+
+       header = (struct fcgi_record_header*)resp->data;
+
+       /* The FastCGI spec suggests to align the output buffer */
+       padded_len = FCGI_ALIGN(resp->data_len);
+       if (padded_len > resp->data_len) {
+               /* There should always be FCGI_PADDING_SIZE bytes left */
+               if (padded_len > FCGI_RECORD_SIZE)
+                       lerr(1, "response too long");
+               header->padding_len = padded_len - resp->data_len;
+               resp->data_len = padded_len;
+       }
+
+       TAILQ_INSERT_TAIL(&c->response_head, resp, entry);
+       event_add(&c->resp_ev, NULL);
+}
+
+void
+slowcgi_response(int fd, short events, void *arg)
+{
+       struct request                  *c;
+       struct fcgi_record_header       *header;
+       struct fcgi_response            *resp;
+       ssize_t                          n;
+
+       c = arg;
+
+       while ((resp = TAILQ_FIRST(&c->response_head))) {
+               header = (struct fcgi_record_header*) resp->data;
+               if (debug > 1)
+                       dump_fcgi_record("resp ", header);
+
+               n = write(fd, resp->data + resp->data_pos, resp->data_len);
+               if (n == -1) {
+                       if (errno == EAGAIN || errno == EINTR)
+                               return;
+                       cleanup_request(c);
+                       return;
+               }
+               resp->data_pos += n;
+               resp->data_len -= n;
+               if (resp->data_len == 0) {
+                       TAILQ_REMOVE(&c->response_head, resp, entry);
+                       free(resp);
+               }
+       }
+
+       if (TAILQ_EMPTY(&c->response_head)) {
+               if (c->request_done)
+                       cleanup_request(c);
+               else
+                       event_del(&c->resp_ev);
+       }
+}
+
+void
+slowcgi_request(int fd, short events, void *arg)
+{
+       struct request  *c;
+       ssize_t          n;
+       size_t           parsed;
+
+       c = arg;
+
+       n = read(fd, c->buf + c->buf_pos + c->buf_len,
+           FCGI_RECORD_SIZE - c->buf_pos-c->buf_len);
+
+       switch (n) {
+       case -1:
+               switch (errno) {
+               case EINTR:
+               case EAGAIN:
+                       return;
+               default:
+                       goto fail;
+               }
+               break;
+
+       case 0:
+               ldebug("closed connection");
+               goto fail;
+       default:
+               break;
+       }
+
+       c->buf_len += n;
+
+       /*
+        * Parse the records as they are received. Per the FastCGI
+        * specification, the server need only receive the FastCGI
+        * parameter records in full; it is free to begin execution
+        * at that point, which is what happens here.
+        */
+       do {
+               parsed = parse_record(c->buf + c->buf_pos, c->buf_len, c);
+               c->buf_pos += parsed;
+               c->buf_len -= parsed;
+       } while (parsed > 0 && c->buf_len > 0);
+
+       /* Make space for further reads */
+       if (c->buf_len > 0) {
+               bcopy(c->buf + c->buf_pos, c->buf, c->buf_len);
+               c->buf_pos = 0;
+       }
+       return;
+fail:
+       cleanup_request(c);
+}
+
+void
+parse_begin_request(uint8_t *buf, uint16_t n, struct request *c, uint16_t id)
+{
+       /* XXX -- FCGI_CANT_MPX_CONN */
+       if (c->request_started) {
+               lwarnx("unexpected FCGI_BEGIN_REQUEST, ignoring");
+               return;
+       }
+
+       if (n != sizeof(struct fcgi_begin_request_body)) {
+               lwarnx("wrong size %d != %lu", n,
+                   sizeof(struct fcgi_begin_request_body));
+               return;
+       }
+
+       c->request_started = 1;
+
+       c->id = id;
+       SLIST_INIT(&c->env);
+       c->env_count = 0;
+}
+
+void
+parse_params(uint8_t *buf, uint16_t n, struct request *c, uint16_t id)
+{
+       struct env_val                  *env_entry;
+       uint32_t                         name_len, val_len;
+
+       if (!c->request_started) {
+               lwarnx("FCGI_PARAMS without FCGI_BEGIN_REQUEST, ignoring");
+               return;
+       }
+
+       if (c->id != id) {
+               lwarnx("unexpected id, ignoring");
+               return;
+       }
+
+       /*
+        * If this is the last FastCGI parameter record,
+        * begin execution of the CGI script.
+        */
+       if (n == 0) {
+               exec_cgi(c);
+               return;
+       }
+
+       while (n > 0) {
+               if (buf[0] >> 7 == 0) {
+                       name_len = buf[0];
+                       n--;
+                       buf++;
+               } else {
+                       if (n > 3) {
+                               name_len = ((buf[0] & 0x7f) << 24) +
+                                   (buf[1] << 16) + (buf[2] << 8) + buf[3];
+                               n -= 4;
+                               buf += 4;
+                       } else
+                               return;
+               }
+
+               if (n > 0) {
+                       if (buf[0] >> 7 == 0) {
+                               val_len = buf[0];
+                               n--;
+                               buf++;
+                       } else {
+                               if (n > 3) {
+                                       val_len = ((buf[0] & 0x7f) << 24) +
+                                           (buf[1] << 16) + (buf[2] << 8) +
+                                            buf[3];
+                                       n -= 4;
+                                       buf += 4;
+                               } else
+                                       return;
+                       }
+               } else
+                       return;
+
+               if (n < name_len + val_len)
+                       return;
+
+               if ((env_entry = malloc(sizeof(struct env_val))) == NULL) {
+                       lwarnx("cannot allocate env_entry");
+                       return;
+               }
+
+               if ((env_entry->key = malloc(name_len + 1)) == NULL) {
+                       lwarnx("cannot allocate env_entry->key");
+                       free(env_entry);
+                       return;
+               }
+               if ((env_entry->val = malloc(val_len + 1)) == NULL) {
+                       lwarnx("cannot allocate env_entry->val");
+                       free(env_entry->key);
+                       free(env_entry);
+                       return;
+               }
+
+               bcopy(buf, env_entry->key, name_len);
+               buf += name_len;
+               n -= name_len;
+               env_entry->key[name_len] = '\0';
+               bcopy(buf, env_entry->val, val_len);
+               buf += val_len;
+               n -= val_len;
+               env_entry->val[val_len] = '\0';
+
+               SLIST_INSERT_HEAD(&c->env, env_entry, entry);
+               ldebug("env[%d], %s=%s", c->env_count, env_entry->key,
+                   env_entry->val);
+               c->env_count++;
+       }
+}
+
+void
+parse_stdin(uint8_t *buf, uint16_t n, struct request *c, uint16_t id)
+{
+       if (c->id != id) {
+               lwarnx("unexpected id, ignoring");
+               return;
+       }
+
+       if (n != 0)
+               lwarnx("unexpected stdin input, ignoring");
+}
+
+size_t
+parse_record(uint8_t *buf, size_t n, struct request *c)
+{
+       struct fcgi_record_header       *h;
+
+       if (n < sizeof(struct fcgi_record_header))
+               return (0);
+
+       h = (struct fcgi_record_header*) buf;
+
+       if (debug > 1)
+               dump_fcgi_record("", h);
+
+       if (n < sizeof(struct fcgi_record_header) + ntohs(h->content_len)
+           + h->padding_len)
+               return (0);
+
+       if (h->version != 1)
+               lerrx(1, "wrong version");
+
+       switch (h->type) {
+       case FCGI_BEGIN_REQUEST:
+               parse_begin_request(buf + sizeof(struct fcgi_record_header),
+                   ntohs(h->content_len), c, ntohs(h->id));
+               break;
+       case FCGI_PARAMS:
+               parse_params(buf + sizeof(struct fcgi_record_header),
+                   ntohs(h->content_len), c, ntohs(h->id));
+               break;
+       case FCGI_STDIN:
+               parse_stdin(buf + sizeof(struct fcgi_record_header),
+                   ntohs(h->content_len), c, ntohs(h->id));
+               break;
+       default:
+               lwarnx("unimplemented type %d", h->type);
+               break;
+       }
+
+       return (sizeof(struct fcgi_record_header) + ntohs(h->content_len)
+           + h->padding_len);
+}
+
+char *
+env_get(struct request *c, const char *key)
+{
+       struct env_val  *env;
+
+       SLIST_FOREACH(env, &c->env, entry) {
+               if (strcmp(env->key, key) == 0)
+                       return (env->val);
+       }
+       return (NULL);
+}
+
+static const char *
+http_error(int *res)
+{
+       const struct http_error errors[] = HTTP_ERRORS;
+       size_t i;
+
+       for (i = 0; errors[i].error_code != 0; i++)
+               if (errors[i].error_code == *res)
+                       return errors[i].error_name;
+
+       /* unknown error - change to 500 */
+       lwarnx("unknown http error %d", *res);
+       *res = 500;
+       return "Internal Server Error";
+}
+
+static void
+error_response(struct request *c, int res)
+{
+       const char *type = "text/html";
+       const char *errstr = http_error(&res);
+       char *buf;
+       int len;
+
+       lwarnx("HTTP status %d: %s", res, errstr);
+
+       len = asprintf(&buf,
+           "Content-Type: %s\n"
+           "Status: %d\n"
+           "Cache-Control: no-cache\n"
+           "\n"
+           "<!DOCTYPE html>\n"
+           "<html>\n"
+           " <head>\n"
+           "  <meta http-equiv=\"Content-Type\" "
+           "content=\"%s; charset=utf-8\"/>\n"
+           "  <title>%d %s</title>\n"
+           " </head>\n"
+           " <body>\n"
+           "  <h1>%d %s</h1>\n"
+           "  <hr>\n"
+           "  <address>OpenBSD bgplgd</address>\n"
+           " </body>\n"
+           "</html>\n",
+           type, res, type, res, errstr, res, errstr);
+
+       if (len == -1)
+               lerr(1, NULL);
+
+       create_data_record(c, FCGI_STDOUT, buf, len);
+       free(buf);
+       c->script_flags = (STDOUT_DONE | STDERR_DONE | SCRIPT_DONE);
+       create_end_record(c);
+}
+
+/*
+ * Fork a new CGI process to handle the request, translating
+ * between FastCGI parameter records and CGI's environment variables,
+ * as well as between the CGI process' stdin/stdout and the
+ * corresponding FastCGI records.
+ */
+void
+exec_cgi(struct request *c)
+{
+       struct lg_ctx    ctx = { 0 };
+       int              s_in[2], s_out[2], s_err[2], res;
+       pid_t            pid;
+
+       res = prep_request(&ctx, env_get(c, "REQUEST_METHOD"),
+           env_get(c, "PATH_INFO"), env_get(c, "QUERY_STRING"));
+       if (res != 0) {
+               error_response(c, res);
+               return;
+       }
+
+       if (pipe(s_in) == -1)
+               lerr(1, "pipe");
+       if (pipe(s_out) == -1)
+               lerr(1, "pipe");
+       if (pipe(s_err) == -1)
+               lerr(1, "pipe");
+       cgi_inflight--;
+       c->inflight_fds_accounted = 1;
+
+       switch (pid = fork()) {
+       case -1:
+               c->command_status = errno;
+
+               lwarn("fork");
+
+               close(s_in[0]);
+               close(s_out[0]);
+               close(s_err[0]);
+
+               close(s_in[1]);
+               close(s_out[1]);
+               close(s_err[1]);
+
+               c->script_flags = (STDOUT_DONE | STDERR_DONE | SCRIPT_DONE);
+               create_end_record(c);
+               return;
+       case 0:
+               /* Child process */
+               if (pledge("stdio rpath exec", NULL) == -1)
+                       lerr(1, "pledge");
+               close(s_in[0]);
+               close(s_out[0]);
+               close(s_err[0]);
+
+               if (dup2(s_in[1], STDIN_FILENO) == -1)
+                       _exit(1);
+               if (dup2(s_out[1], STDOUT_FILENO) == -1)
+                       _exit(1);
+               if (dup2(s_err[1], STDERR_FILENO) == -1)
+                       _exit(1);
+
+               close(s_in[1]);
+               close(s_out[1]);
+               close(s_err[1]);
+
+               bgpctl_call(&ctx);
+
+               /* should not be reached */
+               _exit(1);
+
+       }
+
+       ldebug("fork %d", pid);
+
+       /* Parent process*/
+       close(s_in[1]);
+       close(s_out[1]);
+       close(s_err[1]);
+
+       fcntl(s_in[0], F_SETFD, FD_CLOEXEC);
+       fcntl(s_out[0], F_SETFD, FD_CLOEXEC);
+       fcntl(s_err[0], F_SETFD, FD_CLOEXEC);
+
+       if (ioctl(s_in[0], FIONBIO, &on) == -1)
+               lerr(1, "script ioctl(FIONBIO)");
+       if (ioctl(s_out[0], FIONBIO, &on) == -1)
+               lerr(1, "script ioctl(FIONBIO)");
+       if (ioctl(s_err[0], FIONBIO, &on) == -1)
+               lerr(1, "script ioctl(FIONBIO)");
+
+       close(s_in[0]); /* close stdin, bgpctl does not expect anything */
+
+       c->command_pid = pid;
+       event_set(&c->script_ev, s_out[0], EV_READ | EV_PERSIST,
+           script_std_in, c);
+       event_add(&c->script_ev, NULL);
+       event_set(&c->script_err_ev, s_err[0], EV_READ | EV_PERSIST,
+           script_err_in, c);
+       event_add(&c->script_err_ev, NULL);
+}
+
+static void
+script_in(int fd, struct event *ev, struct request *c, uint8_t type)
+{
+       struct fcgi_response            *resp;
+       struct fcgi_record_header       *header;
+       ssize_t                          n;
+
+       if ((resp = calloc(1, sizeof(struct fcgi_response))) == NULL) {
+               lwarnx("cannot malloc fcgi_response");
+               return;
+       }
+       header = (struct fcgi_record_header*) resp->data;
+       header->version = 1;
+       header->type = type;
+       header->id = htons(c->id);
+       header->padding_len = 0;
+       header->reserved = 0;
+
+       n = read(fd, resp->data + sizeof(struct fcgi_record_header),
+           FCGI_CONTENT_SIZE);
+
+       if (n == -1) {
+               switch (errno) {
+               case EINTR:
+               case EAGAIN:
+                       free(resp);
+                       return;
+               default:
+                       n = 0; /* fake empty FCGI_STD{OUT,ERR} response */
+               }
+       }
+       header->content_len = htons(n);
+       resp->data_pos = 0;
+       resp->data_len = n + sizeof(struct fcgi_record_header);
+       slowcgi_add_response(c, resp);
+
+       if (n == 0) {
+               if (type == FCGI_STDOUT)
+                       c->script_flags |= STDOUT_DONE;
+               else
+                       c->script_flags |= STDERR_DONE;
+
+               if (c->script_flags == (STDOUT_DONE | STDERR_DONE |
+                   SCRIPT_DONE))
+                       create_end_record(c);
+               event_del(ev);
+               close(fd);
+       }
+}
+
+void
+script_std_in(int fd, short events, void *arg)
+{
+       struct request *c = arg;
+       script_in(fd, &c->script_ev, c, FCGI_STDOUT);
+}
+
+void
+script_err_in(int fd, short events, void *arg)
+{
+       struct request *c = arg;
+       script_in(fd, &c->script_err_ev, c, FCGI_STDERR);
+}
+
+void
+create_data_record(struct request *c, uint8_t type, const void *buf, size_t 
len)
+{
+       struct fcgi_response            *resp;
+       struct fcgi_record_header       *header;
+       const char                      *d = buf;
+       size_t                           n;
+
+       do {
+               if ((resp = calloc(1, sizeof(struct fcgi_response))) == NULL) {
+                       lwarnx("cannot malloc fcgi_response");
+                       return;
+               }
+               header = (struct fcgi_record_header*) resp->data;
+               header->version = 1;
+               header->type = type;
+               header->id = htons(c->id);
+               header->padding_len = 0;
+               header->reserved = 0;
+
+               n = len > FCGI_CONTENT_SIZE ? FCGI_CONTENT_SIZE : len;
+               memcpy(resp->data + sizeof(struct fcgi_record_header), d, n);
+
+               header->content_len = htons(n);
+               resp->data_pos = 0;
+               resp->data_len = n + sizeof(struct fcgi_record_header);
+               slowcgi_add_response(c, resp);
+
+               len -= n;
+               d += n;
+       } while (len > 0);
+}
+
+void
+create_end_record(struct request *c)
+{
+       struct fcgi_response            *resp;
+       struct fcgi_record_header       *header;
+       struct fcgi_end_request_body    *end_request;
+
+       if ((resp = calloc(1, sizeof(struct fcgi_response))) == NULL) {
+               lwarnx("cannot malloc fcgi_response");
+               return;
+       }
+       header = (struct fcgi_record_header*) resp->data;
+       header->version = 1;
+       header->type = FCGI_END_REQUEST;
+       header->id = htons(c->id);
+       header->content_len = htons(sizeof(struct
+           fcgi_end_request_body));
+       header->padding_len = 0;
+       header->reserved = 0;
+       end_request = (struct fcgi_end_request_body *) (resp->data +
+           sizeof(struct fcgi_record_header));
+       end_request->app_status = htonl(c->command_status);
+       end_request->protocol_status = FCGI_REQUEST_COMPLETE;
+       end_request->reserved[0] = 0;
+       end_request->reserved[1] = 0;
+       end_request->reserved[2] = 0;
+       resp->data_pos = 0;
+       resp->data_len = sizeof(struct fcgi_end_request_body) +
+           sizeof(struct fcgi_record_header);
+       slowcgi_add_response(c, resp);
+       c->request_done = 1;
+}
+
+void
+cleanup_request(struct request *c)
+{
+       struct fcgi_response    *resp;
+       struct env_val          *env_entry;
+
+       evtimer_del(&c->tmo);
+       if (event_initialized(&c->ev))
+               event_del(&c->ev);
+       if (event_initialized(&c->resp_ev))
+               event_del(&c->resp_ev);
+       if (event_initialized(&c->script_ev)) {
+               close(EVENT_FD(&c->script_ev));
+               event_del(&c->script_ev);
+       }
+       if (event_initialized(&c->script_err_ev)) {
+               close(EVENT_FD(&c->script_err_ev));
+               event_del(&c->script_err_ev);
+       }
+
+       close(c->fd);
+       while (!SLIST_EMPTY(&c->env)) {
+               env_entry = SLIST_FIRST(&c->env);
+               SLIST_REMOVE_HEAD(&c->env, entry);
+               free(env_entry->key);
+               free(env_entry->val);
+               free(env_entry);
+       }
+
+       while ((resp = TAILQ_FIRST(&c->response_head))) {
+               TAILQ_REMOVE(&c->response_head, resp, entry);
+               free(resp);
+       }
+       LIST_REMOVE(c, entry);
+       if (! c->inflight_fds_accounted)
+               cgi_inflight--;
+       free(c);
+}
+
+void
+dump_fcgi_record(const char *p, struct fcgi_record_header *h)
+{
+       dump_fcgi_record_header(p, h);
+
+       if (h->type == FCGI_BEGIN_REQUEST)
+               dump_fcgi_begin_request_body(p,
+                   (struct fcgi_begin_request_body *)(h + 1));
+       else if (h->type == FCGI_END_REQUEST)
+               dump_fcgi_end_request_body(p,
+                   (struct fcgi_end_request_body *)(h + 1));
+}
+
+void
+dump_fcgi_record_header(const char* p, struct fcgi_record_header *h)
+{
+       ldebug("%sversion:         %d", p, h->version);
+       ldebug("%stype:            %d", p, h->type);
+       ldebug("%srequestId:       %d", p, ntohs(h->id));
+       ldebug("%scontentLength:   %d", p, ntohs(h->content_len));
+       ldebug("%spaddingLength:   %d", p, h->padding_len);
+       ldebug("%sreserved:        %d", p, h->reserved);
+}
+
+void
+dump_fcgi_begin_request_body(const char *p, struct fcgi_begin_request_body *b)
+{
+       ldebug("%srole             %d", p, ntohs(b->role));
+       ldebug("%sflags            %d", p, b->flags);
+}
+
+void
+dump_fcgi_end_request_body(const char *p, struct fcgi_end_request_body *b)
+{
+       ldebug("%sappStatus:       %d", p, ntohl(b->app_status));
+       ldebug("%sprotocolStatus:  %d", p, b->protocol_status);
+}
+
+void
+syslog_vstrerror(int e, int priority, const char *fmt, va_list ap)
+{
+       char *s;
+
+       if (vasprintf(&s, fmt, ap) == -1) {
+               syslog(LOG_EMERG, "unable to alloc in syslog_vstrerror");
+               exit(1);
+       }
+       syslog(priority, "%s: %s", s, strerror(e));
+       free(s);
+}
+
+__dead void
+syslog_err(int ecode, const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       syslog_vstrerror(errno, LOG_CRIT, fmt, ap);
+       va_end(ap);
+       exit(ecode);
+}
+
+__dead void
+syslog_errx(int ecode, const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       vsyslog(LOG_CRIT, fmt, ap);
+       va_end(ap);
+       exit(ecode);
+}
+
+void
+syslog_warn(const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       syslog_vstrerror(errno, LOG_ERR, fmt, ap);
+       va_end(ap);
+}
+
+void
+syslog_warnx(const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       vsyslog(LOG_ERR, fmt, ap);
+       va_end(ap);
+}
+
+void
+syslog_info(const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       vsyslog(LOG_INFO, fmt, ap);
+       va_end(ap);
+}
+
+void
+syslog_debug(const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       vsyslog(LOG_DEBUG, fmt, ap);
+       va_end(ap);
+}
Index: slowcgi.h
===================================================================
RCS file: slowcgi.h
diff -N slowcgi.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ slowcgi.h   4 Feb 2022 15:16:25 -0000
@@ -0,0 +1,55 @@
+/*     $OpenBSD: slowcgi.c,v 1.31 2014/04/16 14:43:43 florian Exp $ */
+/*
+ * Copyright (c) 2013 David Gwynne <[email protected]>
+ * Copyright (c) 2013 Florian Obser <[email protected]>
+ *
+ * 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.
+ */
+
+#define FCGI_BEGIN_REQUEST      1
+#define FCGI_ABORT_REQUEST      2
+#define FCGI_END_REQUEST        3
+#define FCGI_PARAMS             4
+#define FCGI_STDIN              5
+#define FCGI_STDOUT             6
+#define FCGI_STDERR             7
+#define FCGI_DATA               8
+#define FCGI_GET_VALUES                 9
+#define FCGI_GET_VALUES_RESULT 10
+#define FCGI_UNKNOWN_TYPE      11
+#define FCGI_MAXTYPE           (FCGI_UNKNOWN_TYPE)
+
+struct loggers {
+       __dead void (*err)(int, const char *, ...)
+           __attribute__((__format__ (printf, 2, 3)));
+       __dead void (*errx)(int, const char *, ...)
+           __attribute__((__format__ (printf, 2, 3)));
+       void (*warn)(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+       void (*warnx)(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+       void (*info)(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+       void (*debug)(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+};
+
+extern const struct loggers *logger;
+
+#define lerr(_e, _f...) logger->err((_e), _f)
+#define lerrx(_e, _f...) logger->errx((_e), _f)
+#define lwarn(_f...) logger->warn(_f)
+#define lwarnx(_f...) logger->warnx(_f)
+#define linfo(_f...) logger->info(_f)
+#define ldebug(_f...) logger->debug(_f)
+

Reply via email to