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