Add bgplgd a fastcgi daemon that provide a REST JSON api to bgpctl.
authorclaudio <claudio@openbsd.org>
Tue, 28 Jun 2022 16:11:30 +0000 (16:11 +0000)
committerclaudio <claudio@openbsd.org>
Tue, 28 Jun 2022 16:11:30 +0000 (16:11 +0000)
Most code stolen from slowcgi, it just exec bgpctl with -j.
Put it in tb@ deraadt@

usr.sbin/bgplgd/Makefile [new file with mode: 0644]
usr.sbin/bgplgd/bgplgd.8 [new file with mode: 0644]
usr.sbin/bgplgd/bgplgd.c [new file with mode: 0644]
usr.sbin/bgplgd/bgplgd.h [new file with mode: 0644]
usr.sbin/bgplgd/http.h [new file with mode: 0644]
usr.sbin/bgplgd/qs.c [new file with mode: 0644]
usr.sbin/bgplgd/slowcgi.c [new file with mode: 0644]
usr.sbin/bgplgd/slowcgi.h [new file with mode: 0644]

diff --git a/usr.sbin/bgplgd/Makefile b/usr.sbin/bgplgd/Makefile
new file mode 100644 (file)
index 0000000..6944734
--- /dev/null
@@ -0,0 +1,14 @@
+#      $OpenBSD: Makefile,v 1.1 2022/06/28 16:11:30 claudio 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>
diff --git a/usr.sbin/bgplgd/bgplgd.8 b/usr.sbin/bgplgd/bgplgd.8
new file mode 100644 (file)
index 0000000..8d8f021
--- /dev/null
@@ -0,0 +1,179 @@
+.\" $OpenBSD: bgplgd.8,v 1.1 2022/06/28 16:11:30 claudio Exp $
+.\"
+.\" Copyright (c) 2021 Claudio Jeker <claudio@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.
+.\"
+.Dd $Mdocdate: June 28 2022 $
+.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 claudio@openbsd.org
diff --git a/usr.sbin/bgplgd/bgplgd.c b/usr.sbin/bgplgd/bgplgd.c
new file mode 100644 (file)
index 0000000..12d993e
--- /dev/null
@@ -0,0 +1,116 @@
+/*     $OpenBSD: bgplgd.c,v 1.1 2022/06/28 16:11:30 claudio Exp $ */
+/*
+ * Copyright (c) 2020 Claudio Jeker <claudio@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/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);
+}
diff --git a/usr.sbin/bgplgd/bgplgd.h b/usr.sbin/bgplgd/bgplgd.h
new file mode 100644 (file)
index 0000000..3856b8a
--- /dev/null
@@ -0,0 +1,65 @@
+/*     $OpenBSD: bgplgd.h,v 1.1 2022/06/28 16:11:30 claudio Exp $ */
+/*
+ * Copyright (c) 2020 Claudio Jeker <claudio@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.
+ */
+
+#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 *);
diff --git a/usr.sbin/bgplgd/http.h b/usr.sbin/bgplgd/http.h
new file mode 100644 (file)
index 0000000..7f68f7b
--- /dev/null
@@ -0,0 +1,104 @@
+/*     $OpenBSD: http.h,v 1.1 2022/06/28 16:11:30 claudio Exp $        */
+
+/*
+ * Copyright (c) 2012 - 2015 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.
+ */
+
+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 }                                  \
+}
diff --git a/usr.sbin/bgplgd/qs.c b/usr.sbin/bgplgd/qs.c
new file mode 100644 (file)
index 0000000..dca907c
--- /dev/null
@@ -0,0 +1,400 @@
+/*     $OpenBSD: qs.c,v 1.1 2022/06/28 16:11:30 claudio Exp $ */
+/*
+ * Copyright (c) 2020 Claudio Jeker <claudio@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/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);
+}
diff --git a/usr.sbin/bgplgd/slowcgi.c b/usr.sbin/bgplgd/slowcgi.c
new file mode 100644 (file)
index 0000000..c229142
--- /dev/null
@@ -0,0 +1,1279 @@
+/*     $OpenBSD: slowcgi.c,v 1.1 2022/06/28 16:11:30 claudio Exp $ */
+/*
+ * Copyright (c) 2020 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2013 David Gwynne <dlg@openbsd.org>
+ * Copyright (c) 2013 Florian Obser <florian@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/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);
+}
diff --git a/usr.sbin/bgplgd/slowcgi.h b/usr.sbin/bgplgd/slowcgi.h
new file mode 100644 (file)
index 0000000..1431ba7
--- /dev/null
@@ -0,0 +1,55 @@
+/*     $OpenBSD: slowcgi.h,v 1.1 2022/06/28 16:11:30 claudio Exp $ */
+/*
+ * Copyright (c) 2013 David Gwynne <dlg@openbsd.org>
+ * Copyright (c) 2013 Florian Obser <florian@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.
+ */
+
+#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)
+