summaryrefslogtreecommitdiff
path: root/sbin
diff options
context:
space:
mode:
authorFlorian Obser <florian@cvs.openbsd.org>2019-02-03 12:02:31 +0000
committerFlorian Obser <florian@cvs.openbsd.org>2019-02-03 12:02:31 +0000
commitf9afdffacbc80ce38d0f3382a2910cbb69f5a6fe (patch)
tree0d0082630584cf5e779c025f45172bbc6245c9b8 /sbin
parentf5bb41ae112a156756ad002d5c89c53df6adeb6d (diff)
Captive portal detection for unwind(8).
Diffstat (limited to 'sbin')
-rw-r--r--sbin/unwind/Makefile7
-rw-r--r--sbin/unwind/captiveportal.c720
-rw-r--r--sbin/unwind/captiveportal.h41
-rw-r--r--sbin/unwind/control.c6
-rw-r--r--sbin/unwind/frontend.c118
-rw-r--r--sbin/unwind/frontend.h5
-rw-r--r--sbin/unwind/printconf.c15
-rw-r--r--sbin/unwind/resolver.c354
-rw-r--r--sbin/unwind/resolver.h8
-rw-r--r--sbin/unwind/unwind.c225
-rw-r--r--sbin/unwind/unwind.conf.555
-rw-r--r--sbin/unwind/unwind.h32
-rw-r--r--sbin/unwind/uw_parse.y67
13 files changed, 1589 insertions, 64 deletions
diff --git a/sbin/unwind/Makefile b/sbin/unwind/Makefile
index 245c528ffbb..563e6a9cc89 100644
--- a/sbin/unwind/Makefile
+++ b/sbin/unwind/Makefile
@@ -1,7 +1,8 @@
-# $OpenBSD: Makefile,v 1.2 2019/01/24 15:33:44 florian Exp $
+# $OpenBSD: Makefile,v 1.3 2019/02/03 12:02:30 florian Exp $
PROG= unwind
SRCS= control.c resolver.c frontend.c uw_log.c unwind.c uw_parse.y printconf.c
+SRCS+= captiveportal.c
MAN= unwind.8 unwind.conf.5
.include "${.CURDIR}/libunbound/Makefile.inc"
@@ -14,7 +15,7 @@ CFLAGS+= -Wmissing-declarations
CFLAGS+= -Wshadow -Wpointer-arith
CFLAGS+= -Wsign-compare
YFLAGS=
-LDADD+= -levent -lutil -lssl -lcrypto
-DPADD+= ${LIBEVENT} ${LIBUTIL} ${LIBSSL} ${LIBCRYPTO}
+LDADD+= -levent -lutil -ltls -lssl -lcrypto
+DPADD+= ${LIBEVENT} ${LIBUTIL} ${LIBTLS} ${LIBSSL} ${LIBCRYPTO}
.include <bsd.prog.mk>
diff --git a/sbin/unwind/captiveportal.c b/sbin/unwind/captiveportal.c
new file mode 100644
index 00000000000..c9a9f8f1d34
--- /dev/null
+++ b/sbin/unwind/captiveportal.c
@@ -0,0 +1,720 @@
+/* $OpenBSD: captiveportal.c,v 1.1 2019/02/03 12:02:30 florian Exp $ */
+
+/*
+ * Copyright (c) 2018 Florian Obser <florian@openbsd.org>
+ * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2004 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@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/queue.h>
+#include <sys/socket.h>
+#include <sys/syslog.h>
+#include <sys/uio.h>
+
+#include <netinet/in.h>
+#include <net/if.h>
+#include <net/route.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <event.h>
+#include <imsg.h>
+#include <limits.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <vis.h>
+
+#include "uw_log.h"
+#include "unwind.h"
+#include "captiveportal.h"
+
+enum http_global_state {
+ IDLE,
+ READING
+};
+
+enum http_state {
+ INIT,
+ SENT_QUERY,
+ HEADER_READ
+};
+
+struct http_ctx {
+ TAILQ_ENTRY(http_ctx) entry;
+ struct event ev;
+ int fd;
+ enum http_state state;
+ char *buf;
+ size_t bufsz;
+ int status;
+ int content_length;
+};
+
+__dead void captiveportal_shutdown(void);
+void captiveportal_sig_handler(int, short, void *);
+void captiveportal_startup(void);
+void http_callback(int, short, void *);
+int parse_http_header(struct http_ctx *);
+void check_http_body(struct http_ctx *ctx);
+void free_http_ctx(struct http_ctx *);
+void close_other_http_contexts(struct http_ctx *);
+
+struct unwind_conf *captiveportal_conf;
+struct imsgev *iev_main;
+struct imsgev *iev_resolver;
+struct imsgev *iev_frontend;
+
+#define MAX_SERVERS_DNS 8
+enum http_global_state http_global_state = IDLE;
+TAILQ_HEAD(, http_ctx) http_contexts;
+int http_contexts_count;
+
+struct timeval tv = {5, 0};
+
+void
+captiveportal_sig_handler(int sig, short event, void *bula)
+{
+ /*
+ * Normal signal handler rules don't apply because libevent
+ * decouples for us.
+ */
+
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ captiveportal_shutdown();
+ default:
+ fatalx("unexpected signal");
+ }
+}
+
+void
+captiveportal(int debug, int verbose)
+{
+ struct event ev_sigint, ev_sigterm;
+ struct passwd *pw;
+
+ captiveportal_conf = config_new_empty();
+
+ log_init(debug, LOG_DAEMON);
+ log_setverbose(verbose);
+
+ if ((pw = getpwnam(UNWIND_USER)) == NULL)
+ fatal("getpwnam");
+
+ if (chroot(pw->pw_dir) == -1)
+ fatal("chroot");
+ if (chdir("/") == -1)
+ fatal("chdir(\"/\")");
+
+ unwind_process = PROC_CAPTIVEPORTAL;
+ setproctitle("%s", log_procnames[unwind_process]);
+ log_procinit(log_procnames[unwind_process]);
+
+ 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))
+ fatal("can't drop privileges");
+
+ if (pledge("stdio recvfd", NULL) == -1)
+ fatal("pledge");
+
+ event_init();
+
+ /* Setup signal handler. */
+ signal_set(&ev_sigint, SIGINT, captiveportal_sig_handler, NULL);
+ signal_set(&ev_sigterm, SIGTERM, captiveportal_sig_handler, NULL);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGHUP, SIG_IGN);
+
+ /* Setup pipe and event handler to the parent process. */
+ if ((iev_main = malloc(sizeof(struct imsgev))) == NULL)
+ fatal(NULL);
+ imsg_init(&iev_main->ibuf, 3);
+ iev_main->handler = captiveportal_dispatch_main;
+ iev_main->events = EV_READ;
+ event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events,
+ iev_main->handler, iev_main);
+ event_add(&iev_main->ev, NULL);
+
+ TAILQ_INIT(&http_contexts);
+
+ event_dispatch();
+
+ captiveportal_shutdown();
+}
+
+__dead void
+captiveportal_shutdown(void)
+{
+ /* Close pipes. */
+ msgbuf_write(&iev_resolver->ibuf.w);
+ msgbuf_clear(&iev_resolver->ibuf.w);
+ close(iev_resolver->ibuf.fd);
+ msgbuf_write(&iev_frontend->ibuf.w);
+ msgbuf_clear(&iev_frontend->ibuf.w);
+ close(iev_frontend->ibuf.fd);
+ msgbuf_write(&iev_main->ibuf.w);
+ msgbuf_clear(&iev_main->ibuf.w);
+ close(iev_main->ibuf.fd);
+
+ config_clear(captiveportal_conf);
+
+ free(iev_resolver);
+ free(iev_frontend);
+ free(iev_main);
+
+ log_info("captiveportal exiting");
+ exit(0);
+}
+
+int
+captiveportal_imsg_compose_main(int type, pid_t pid, void *data, uint16_t datalen)
+{
+ return (imsg_compose_event(iev_main, type, 0, pid, -1, data,
+ datalen));
+}
+
+int
+captiveportal_imsg_compose_resolver(int type, pid_t pid, void *data, uint16_t datalen)
+{
+ return (imsg_compose_event(iev_resolver, type, 0, pid, -1, data,
+ datalen));
+}
+
+int
+captiveportal_imsg_compose_frontend(int type, pid_t pid, void *data,
+ uint16_t datalen)
+{
+ return (imsg_compose_event(iev_frontend, type, 0, pid, -1, data,
+ datalen));
+}
+
+void
+captiveportal_dispatch_main(int fd, short event, void *bula)
+{
+ static struct unwind_conf *nconf;
+ struct unwind_forwarder *unwind_forwarder;
+ struct imsg imsg;
+ struct imsgev *iev = bula;
+ struct imsgbuf *ibuf = &iev->ibuf;
+ struct http_ctx *ctx;
+ int n, shut = 0;
+
+ if (event & EV_READ) {
+ if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+ fatal("imsg_read error");
+ if (n == 0) /* Connection closed. */
+ shut = 1;
+ }
+ if (event & EV_WRITE) {
+ if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+ fatal("msgbuf_write");
+ if (n == 0) /* Connection closed. */
+ shut = 1;
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("%s: imsg_get error", __func__);
+ if (n == 0) /* No more messages. */
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_SOCKET_IPC_RESOLVER:
+ /*
+ * Setup pipe and event handler to the resolver
+ * process.
+ */
+ if (iev_resolver) {
+ fatalx("%s: received unexpected imsg fd "
+ "to captiveportal", __func__);
+ break;
+ }
+ if ((fd = imsg.fd) == -1) {
+ fatalx("%s: expected to receive imsg fd to "
+ "captiveportal but didn't receive any",
+ __func__);
+ break;
+ }
+
+ iev_resolver = malloc(sizeof(struct imsgev));
+ if (iev_resolver == NULL)
+ fatal(NULL);
+
+ imsg_init(&iev_resolver->ibuf, fd);
+ iev_resolver->handler = captiveportal_dispatch_resolver;
+ iev_resolver->events = EV_READ;
+
+ event_set(&iev_resolver->ev, iev_resolver->ibuf.fd,
+ iev_resolver->events, iev_resolver->handler, iev_resolver);
+ event_add(&iev_resolver->ev, NULL);
+ break;
+ case IMSG_SOCKET_IPC_FRONTEND:
+ /*
+ * Setup pipe and event handler to the frontend
+ * process.
+ */
+ if (iev_frontend) {
+ fatalx("%s: received unexpected imsg fd "
+ "to frontend", __func__);
+ break;
+ }
+ if ((fd = imsg.fd) == -1) {
+ fatalx("%s: expected to receive imsg fd to "
+ "frontend but didn't receive any",
+ __func__);
+ break;
+ }
+
+ iev_frontend = malloc(sizeof(struct imsgev));
+ if (iev_frontend == NULL)
+ fatal(NULL);
+
+ imsg_init(&iev_frontend->ibuf, fd);
+ iev_frontend->handler = captiveportal_dispatch_frontend;
+ iev_frontend->events = EV_READ;
+
+ event_set(&iev_frontend->ev, iev_frontend->ibuf.fd,
+ iev_frontend->events, iev_frontend->handler, iev_frontend);
+ event_add(&iev_frontend->ev, NULL);
+ break;
+ case IMSG_RECONF_CONF:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE +
+ sizeof(struct unwind_conf))
+ fatalx("%s: IMSG_RECONF_CONF wrong length: %d",
+ __func__, imsg.hdr.len);
+ if ((nconf = malloc(sizeof(struct unwind_conf))) ==
+ NULL)
+ fatal(NULL);
+ memcpy(nconf, imsg.data, sizeof(struct unwind_conf));
+ nconf->captive_portal_host = NULL;
+ nconf->captive_portal_path = NULL;
+ nconf->captive_portal_expected_response = NULL;
+ SIMPLEQ_INIT(&nconf->unwind_forwarder_list);
+ SIMPLEQ_INIT(&nconf->unwind_dot_forwarder_list);
+ break;
+ case IMSG_RECONF_CAPTIVE_PORTAL_HOST:
+ /* make sure this is a string */
+ ((char *)imsg.data)[imsg.hdr.len - IMSG_HEADER_SIZE - 1]
+ = '\0';
+ if ((nconf->captive_portal_host = strdup(imsg.data)) ==
+ NULL)
+ fatal("%s: strdup", __func__);
+ break;
+ case IMSG_RECONF_CAPTIVE_PORTAL_PATH:
+ /* make sure this is a string */
+ ((char *)imsg.data)[imsg.hdr.len - IMSG_HEADER_SIZE - 1]
+ = '\0';
+ if ((nconf->captive_portal_path = strdup(imsg.data)) ==
+ NULL)
+ fatal("%s: strdup", __func__);
+ break;
+ case IMSG_RECONF_CAPTIVE_PORTAL_EXPECTED_RESPONSE:
+ /* make sure this is a string */
+ ((char *)imsg.data)[imsg.hdr.len - IMSG_HEADER_SIZE - 1]
+ = '\0';
+ if ((nconf->captive_portal_expected_response =
+ strdup(imsg.data)) == NULL)
+ fatal("%s: strdup", __func__);
+ break;
+ case IMSG_RECONF_FORWARDER:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE +
+ sizeof(struct unwind_forwarder))
+ fatalx("%s: IMSG_RECONF_FORWARDER wrong length:"
+ " %d", __func__, imsg.hdr.len);
+ if ((unwind_forwarder = malloc(sizeof(struct
+ unwind_forwarder))) == NULL)
+ fatal(NULL);
+ memcpy(unwind_forwarder, imsg.data, sizeof(struct
+ unwind_forwarder));
+ SIMPLEQ_INSERT_TAIL(&nconf->unwind_forwarder_list,
+ unwind_forwarder, entry);
+ break;
+ case IMSG_RECONF_DOT_FORWARDER:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE +
+ sizeof(struct unwind_forwarder))
+ fatalx("%s: IMSG_RECONF_DOT_FORWARDER wrong "
+ "length: %d", __func__, imsg.hdr.len);
+ if ((unwind_forwarder = malloc(sizeof(struct
+ unwind_forwarder))) == NULL)
+ fatal(NULL);
+ memcpy(unwind_forwarder, imsg.data, sizeof(struct
+ unwind_forwarder));
+ SIMPLEQ_INSERT_TAIL(&nconf->unwind_dot_forwarder_list,
+ unwind_forwarder, entry);
+ break;
+ case IMSG_RECONF_END:
+ merge_config(captiveportal_conf, nconf);
+ nconf = NULL;
+ break;
+ case IMSG_HTTPSOCK:
+ if ((fd = imsg.fd) == -1) {
+ fatalx("%s: expected to receive imsg fd to "
+ "captiveportal but didn't receive any",
+ __func__);
+ break;
+ }
+
+ if (http_global_state == READING ||
+ http_contexts_count >= MAX_SERVERS_DNS) {
+ /* don't try more servers */
+ close(fd);
+ break;
+ }
+
+ if ((ctx = malloc(sizeof(*ctx))) == NULL) {
+ close(fd);
+ break;
+ }
+
+ ctx->state = INIT;
+ ctx->fd = fd;
+ ctx->bufsz = 0;
+ ctx->buf = NULL;
+ ctx->status = -1;
+ ctx->content_length = -1;
+
+ event_set(&ctx->ev, fd, EV_READ | EV_WRITE |
+ EV_PERSIST, http_callback, ctx);
+ event_add(&ctx->ev, &tv);
+
+ TAILQ_INSERT_TAIL(&http_contexts, ctx, entry);
+
+ http_contexts_count++;
+
+ break;
+ default:
+ log_debug("%s: error handling imsg %d", __func__,
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ if (!shut)
+ imsg_event_add(iev);
+ else {
+ /* This pipe is dead. Remove its event handler. */
+ event_del(&iev->ev);
+ event_loopexit(NULL);
+ }
+}
+
+void
+captiveportal_dispatch_resolver(int fd, short event, void *bula)
+{
+ struct imsgev *iev = bula;
+ struct imsgbuf *ibuf = &iev->ibuf;
+ struct imsg imsg;
+ int n, shut = 0;
+
+ if (event & EV_READ) {
+ if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+ fatal("imsg_read error");
+ if (n == 0) /* Connection closed. */
+ shut = 1;
+ }
+ if (event & EV_WRITE) {
+ if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+ fatal("msgbuf_write");
+ if (n == 0) /* Connection closed. */
+ shut = 1;
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("%s: imsg_get error", __func__);
+ if (n == 0) /* No more messages. */
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ log_debug("%s: error handling imsg %d", __func__,
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ if (!shut)
+ imsg_event_add(iev);
+ else {
+ /* This pipe is dead. Remove its event handler. */
+ event_del(&iev->ev);
+ event_loopexit(NULL);
+ }
+}
+
+void
+captiveportal_dispatch_frontend(int fd, short event, void *bula)
+{
+ struct imsgev *iev = bula;
+ struct imsgbuf *ibuf = &iev->ibuf;
+ struct imsg imsg;
+ int n, shut = 0;
+
+ if (event & EV_READ) {
+ if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+ fatal("imsg_read error");
+ if (n == 0) /* Connection closed. */
+ shut = 1;
+ }
+ if (event & EV_WRITE) {
+ if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+ fatal("msgbuf_write");
+ if (n == 0) /* Connection closed. */
+ shut = 1;
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("%s: imsg_get error", __func__);
+ if (n == 0) /* No more messages. */
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ log_debug("%s: error handling imsg %d", __func__,
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ if (!shut)
+ imsg_event_add(iev);
+ else {
+ /* This pipe is dead. Remove its event handler. */
+ event_del(&iev->ev);
+ event_loopexit(NULL);
+ }
+}
+
+void
+http_callback(int fd, short events, void *arg)
+{
+ struct http_ctx *ctx;
+ ssize_t n;
+ char *query, buf[512], *vis_str, *p, *ep;
+
+ ctx = (struct http_ctx *)arg;
+
+ if (events & EV_TIMEOUT) {
+ log_debug("%s: TIMEOUT", __func__);
+ goto err;
+ }
+
+ if (events & EV_READ) {
+ if ((n = read(fd, buf, sizeof(buf))) == -1) {
+ if (errno == EAGAIN || errno == EINTR)
+ return;
+ else {
+ log_warn("%s: read", __func__);
+ if (http_global_state == READING)
+ http_global_state = IDLE;
+ goto err;
+ }
+ }
+
+ if (http_contexts_count > 1)
+ close_other_http_contexts(ctx);
+ http_global_state = READING;
+
+ if (n == 0) {
+ check_http_body(ctx);
+ return;
+ }
+ p = recallocarray(ctx->buf, ctx->bufsz, ctx->bufsz + n, 1);
+ if (p == NULL) {
+ log_warn("%s", __func__);
+ goto err;
+ }
+ ctx->buf = p;
+ memcpy(ctx->buf + ctx->bufsz, buf, n);
+ ctx->bufsz += n;
+
+ if (ctx->state == HEADER_READ && ctx->content_length != -1 &&
+ ctx->bufsz >= (size_t)ctx->content_length) {
+ check_http_body(ctx);
+ return;
+ }
+
+ if (ctx->state == SENT_QUERY) {
+ ep = memmem(ctx->buf, ctx->bufsz, "\r\n\r\n", 4);
+ if (ep != NULL) {
+ ctx->state = HEADER_READ;
+ *ep = '\0';
+ if (strlen(ctx->buf) != (uintptr_t)
+ (ep - ctx->buf)) {
+ log_warnx("binary data in header");
+ goto err;
+ }
+ stravis(&vis_str, ctx->buf,
+ VIS_NL | VIS_CSTYLE);
+ log_debug("header\n%s", vis_str);
+ free(vis_str);
+
+ if (parse_http_header(ctx) != 0)
+ goto err;
+
+ p = ctx->buf;
+ ep += 4;
+ ctx->bufsz = (ctx->buf + ctx->bufsz) - ep;
+ ctx->buf = malloc(ctx->bufsz);
+ memcpy(ctx->buf, ep, ctx->bufsz);
+ free(p);
+ }
+ }
+ }
+
+ if (events & EV_WRITE) {
+ if (ctx->state == INIT) {
+ n = asprintf(&query,
+ "GET %s HTTP/1.1\r\nHost: %s\r\n"
+ "Connection: close\r\n\r\n",
+ captiveportal_conf->captive_portal_path,
+ captiveportal_conf->captive_portal_host);
+ write(fd, query, n);
+ free(query);
+ event_del(&ctx->ev);
+ event_set(&ctx->ev, fd, EV_READ | EV_PERSIST,
+ http_callback, ctx);
+ event_add(&ctx->ev, &tv);
+ ctx->state = SENT_QUERY;
+ } else {
+ log_warnx("invalid state: %d", ctx->state);
+ goto err;
+ }
+ }
+ return;
+err:
+ free_http_ctx(ctx);
+}
+
+int
+parse_http_header(struct http_ctx *ctx)
+{
+ char *p, *ep;
+ const char *errstr;
+
+ /* scan past HTTP/1.x */
+ p = strchr(ctx->buf, ' ');
+ if (p == NULL)
+ return (1);
+ while (isspace((int)*p))
+ p++;
+ ep = strchr(p, ' ');
+ if (ep == NULL)
+ return (1);
+ *ep = '\0';
+ ctx->status = strtonum(p, 100, 599, &errstr);
+ if (errstr != NULL) {
+ log_warnx("%s: status is %s: %s", __func__, errstr, p);
+ return (1);
+ }
+
+ log_debug("%s: status: %d", __func__, ctx->status);
+
+ /* ignore parse errors from here on out, we got the status */
+
+ p = strstr(ep + 1, "Content-Length:");
+ if (p == NULL)
+ return (0);
+
+ p += sizeof("Content-Length:") - 1;
+ while (isspace((int)*p))
+ p++;
+
+ ep = strchr(p, '\r');
+ if (ep == NULL)
+ return (0);
+
+ *ep = '\0';
+ ctx->content_length = strtonum(p, 0, INT_MAX, &errstr);
+ if (errstr != NULL) {
+ log_warnx("%s: Content-Lenght is %s: %s", __func__, errstr, p);
+ ctx->content_length = -1;
+ return (0);
+ }
+ log_debug("content-length: %d", ctx->content_length);
+ return (0);
+}
+
+void
+check_http_body(struct http_ctx *ctx)
+{
+ enum captive_portal_state state;
+ char *p, *vis_str;
+
+ p = recallocarray(ctx->buf, ctx->bufsz, ctx->bufsz + 1, 1);
+ if (p == NULL) {
+ log_warn("%s", __func__);
+ free_http_ctx(ctx);
+ return;
+ }
+ ctx->buf = p;
+ *(ctx->buf + ctx->bufsz) = '\0';
+ ctx->bufsz++;
+ stravis(&vis_str, ctx->buf, VIS_NL | VIS_CSTYLE);
+ log_debug("body[%ld]\n%s", ctx->bufsz, vis_str);
+
+ if (ctx->status == captiveportal_conf->captive_portal_expected_status &&
+ strcmp(vis_str,
+ captiveportal_conf->captive_portal_expected_response) == 0) {
+ log_debug("%s: not behind captive portal", __func__);
+ state = NOT_BEHIND;
+ } else {
+ log_debug("%s: behind captive portal", __func__);
+ state = BEHIND;
+ }
+ captiveportal_imsg_compose_resolver(IMSG_CAPTIVEPORTAL_STATE, 0,
+ &state, sizeof(state));
+ free_http_ctx(ctx);
+ http_global_state = IDLE;
+}
+
+void
+free_http_ctx(struct http_ctx *ctx)
+{
+ if (ctx == NULL)
+ return;
+
+ event_del(&ctx->ev);
+ close(ctx->fd);
+ TAILQ_REMOVE(&http_contexts, ctx, entry);
+ free(ctx->buf);
+ free(ctx);
+ http_contexts_count--;
+}
+
+void
+close_other_http_contexts(struct http_ctx *octx)
+{
+ struct http_ctx *ctx, *t;
+
+ log_debug("%s", __func__);
+ TAILQ_FOREACH_SAFE(ctx, &http_contexts, entry, t)
+ if(ctx != octx)
+ free_http_ctx(ctx);
+}
diff --git a/sbin/unwind/captiveportal.h b/sbin/unwind/captiveportal.h
new file mode 100644
index 00000000000..cee72005e5c
--- /dev/null
+++ b/sbin/unwind/captiveportal.h
@@ -0,0 +1,41 @@
+/* $OpenBSD: captiveportal.h,v 1.1 2019/02/03 12:02:30 florian Exp $ */
+
+/*
+ * Copyright (c) 2018 Florian Obser <florian@openbsd.org>
+ * Copyright (c) 2004, 2005 Esben Norby <norby@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.
+ */
+
+
+enum captive_portal_state {
+ PORTAL_UNCHECKED,
+ PORTAL_UNKNOWN,
+ BEHIND,
+ NOT_BEHIND
+};
+
+static const char * const captive_portal_state_str[] = {
+ "unchecked",
+ "unknown",
+ "behind",
+ "not behind"
+};
+
+void captiveportal(int, int);
+void captiveportal_dispatch_main(int, short, void *);
+void captiveportal_dispatch_resolver(int, short, void *);
+void captiveportal_dispatch_frontend(int, short, void *);
+int captiveportal_imsg_compose_main(int, pid_t, void *, uint16_t);
+int captiveportal_imsg_compose_resolver(int, pid_t, void *, uint16_t);
+int captiveportal_imsg_compose_frontend(int, pid_t, void *, uint16_t);
diff --git a/sbin/unwind/control.c b/sbin/unwind/control.c
index b442be42fec..0c4b8fc412b 100644
--- a/sbin/unwind/control.c
+++ b/sbin/unwind/control.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: control.c,v 1.5 2019/01/31 18:06:14 solene Exp $ */
+/* $OpenBSD: control.c,v 1.6 2019/02/03 12:02:30 florian Exp $ */
/*
* Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
@@ -256,6 +256,10 @@ control_dispatch_imsg(int fd, short event, void *bula)
case IMSG_CTL_RELOAD:
frontend_imsg_compose_main(imsg.hdr.type, 0, NULL, 0);
break;
+ case IMSG_CTL_RECHECK_CAPTIVEPORTAL:
+ frontend_imsg_compose_resolver(imsg.hdr.type,
+ imsg.hdr.pid, NULL, 0);
+ break;
case IMSG_CTL_LOG_VERBOSE:
if (imsg.hdr.len != IMSG_HEADER_SIZE +
sizeof(verbose))
diff --git a/sbin/unwind/frontend.c b/sbin/unwind/frontend.c
index ab8dfececa8..b7e4381c99e 100644
--- a/sbin/unwind/frontend.c
+++ b/sbin/unwind/frontend.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: frontend.c,v 1.9 2019/02/01 13:05:55 florian Exp $ */
+/* $OpenBSD: frontend.c,v 1.10 2019/02/03 12:02:30 florian Exp $ */
/*
* Copyright (c) 2018 Florian Obser <florian@openbsd.org>
@@ -86,13 +86,13 @@ void handle_route_message(struct rt_msghdr *,
void get_rtaddrs(int, struct sockaddr *,
struct sockaddr **);
void rtmget_default(void);
-char *ip_port(struct sockaddr *);
struct pending_query *find_pending_query(uint64_t);
void parse_dhcp_lease(int);
struct unwind_conf *frontend_conf;
struct imsgev *iev_main;
struct imsgev *iev_resolver;
+struct imsgev *iev_captiveportal;
struct event ev_route;
int udp4sock = -1, udp6sock = -1, routesock = -1;
@@ -206,6 +206,9 @@ frontend_shutdown(void)
msgbuf_write(&iev_resolver->ibuf.w);
msgbuf_clear(&iev_resolver->ibuf.w);
close(iev_resolver->ibuf.fd);
+ msgbuf_write(&iev_captiveportal->ibuf.w);
+ msgbuf_clear(&iev_captiveportal->ibuf.w);
+ close(iev_captiveportal->ibuf.fd);
msgbuf_write(&iev_main->ibuf.w);
msgbuf_clear(&iev_main->ibuf.w);
close(iev_main->ibuf.fd);
@@ -213,6 +216,7 @@ frontend_shutdown(void)
config_clear(frontend_conf);
free(iev_resolver);
+ free(iev_captiveportal);
free(iev_main);
log_info("frontend exiting");
@@ -233,6 +237,13 @@ frontend_imsg_compose_resolver(int type, pid_t pid, void *data, uint16_t datalen
datalen));
}
+int
+frontend_imsg_compose_captiveportal(int type, pid_t pid, void *data, uint16_t datalen)
+{
+ return (imsg_compose_event(iev_captiveportal, type, 0, pid, -1, data,
+ datalen));
+}
+
void
frontend_dispatch_main(int fd, short event, void *bula)
{
@@ -263,7 +274,7 @@ frontend_dispatch_main(int fd, short event, void *bula)
break;
switch (imsg.hdr.type) {
- case IMSG_SOCKET_IPC:
+ case IMSG_SOCKET_IPC_RESOLVER:
/*
* Setup pipe and event handler to the resolver
* process.
@@ -292,6 +303,35 @@ frontend_dispatch_main(int fd, short event, void *bula)
iev_resolver->events, iev_resolver->handler, iev_resolver);
event_add(&iev_resolver->ev, NULL);
break;
+ case IMSG_SOCKET_IPC_CAPTIVEPORTAL:
+ /*
+ * Setup pipe and event handler to the captiveportal
+ * process.
+ */
+ if (iev_captiveportal) {
+ fatalx("%s: received unexpected imsg fd "
+ "to frontend", __func__);
+ break;
+ }
+ if ((fd = imsg.fd) == -1) {
+ fatalx("%s: expected to receive imsg fd to "
+ "frontend but didn't receive any",
+ __func__);
+ break;
+ }
+
+ iev_captiveportal = malloc(sizeof(struct imsgev));
+ if (iev_captiveportal == NULL)
+ fatal(NULL);
+
+ imsg_init(&iev_captiveportal->ibuf, fd);
+ iev_captiveportal->handler = frontend_dispatch_captiveportal;
+ iev_captiveportal->events = EV_READ;
+
+ event_set(&iev_captiveportal->ev, iev_captiveportal->ibuf.fd,
+ iev_captiveportal->events, iev_captiveportal->handler, iev_captiveportal);
+ event_add(&iev_captiveportal->ev, NULL);
+ break;
case IMSG_RECONF_CONF:
if (imsg.hdr.len != IMSG_HEADER_SIZE +
sizeof(struct unwind_conf))
@@ -301,9 +341,36 @@ frontend_dispatch_main(int fd, short event, void *bula)
NULL)
fatal(NULL);
memcpy(nconf, imsg.data, sizeof(struct unwind_conf));
+ nconf->captive_portal_host = NULL;
+ nconf->captive_portal_path = NULL;
+ nconf->captive_portal_expected_response = NULL;
SIMPLEQ_INIT(&nconf->unwind_forwarder_list);
SIMPLEQ_INIT(&nconf->unwind_dot_forwarder_list);
break;
+ case IMSG_RECONF_CAPTIVE_PORTAL_HOST:
+ /* make sure this is a string */
+ ((char *)imsg.data)[imsg.hdr.len - IMSG_HEADER_SIZE - 1]
+ = '\0';
+ if ((nconf->captive_portal_host = strdup(imsg.data)) ==
+ NULL)
+ fatal("%s: strdup", __func__);
+ break;
+ case IMSG_RECONF_CAPTIVE_PORTAL_PATH:
+ /* make sure this is a string */
+ ((char *)imsg.data)[imsg.hdr.len - IMSG_HEADER_SIZE - 1]
+ = '\0';
+ if ((nconf->captive_portal_path = strdup(imsg.data)) ==
+ NULL)
+ fatal("%s: strdup", __func__);
+ break;
+ case IMSG_RECONF_CAPTIVE_PORTAL_EXPECTED_RESPONSE:
+ /* make sure this is a string */
+ ((char *)imsg.data)[imsg.hdr.len - IMSG_HEADER_SIZE - 1]
+ = '\0';
+ if ((nconf->captive_portal_expected_response =
+ strdup(imsg.data)) == NULL)
+ fatal("%s: strdup", __func__);
+ break;
case IMSG_RECONF_FORWARDER:
if (imsg.hdr.len != IMSG_HEADER_SIZE +
sizeof(struct unwind_forwarder))
@@ -482,6 +549,7 @@ frontend_dispatch_resolver(int fd, short event, void *bula)
frontend_imsg_compose_main(IMSG_OPEN_PORTS, 0, NULL, 0);
break;
case IMSG_CTL_RESOLVER_INFO:
+ case IMSG_CTL_CAPTIVEPORTAL_INFO:
case IMSG_CTL_RESOLVER_WHY_BOGUS:
case IMSG_CTL_RESOLVER_HISTOGRAM:
case IMSG_CTL_END:
@@ -504,6 +572,50 @@ frontend_dispatch_resolver(int fd, short event, void *bula)
}
void
+frontend_dispatch_captiveportal(int fd, short event, void *bula)
+{
+ struct imsgev *iev = bula;
+ struct imsgbuf *ibuf = &iev->ibuf;
+ struct imsg imsg;
+ int n, shut = 0;
+
+ if (event & EV_READ) {
+ if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+ fatal("imsg_read error");
+ if (n == 0) /* Connection closed. */
+ shut = 1;
+ }
+ if (event & EV_WRITE) {
+ if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+ fatal("msgbuf_write");
+ if (n == 0) /* Connection closed. */
+ shut = 1;
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("%s: imsg_get error", __func__);
+ if (n == 0) /* No more messages. */
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ log_debug("%s: error handling imsg %d", __func__,
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ if (!shut)
+ imsg_event_add(iev);
+ else {
+ /* This pipe is dead. Remove its event handler. */
+ event_del(&iev->ev);
+ event_loopexit(NULL);
+ }
+}
+
+void
frontend_startup(void)
{
if (!event_initialized(&ev_route))
diff --git a/sbin/unwind/frontend.h b/sbin/unwind/frontend.h
index 052c9e72cfc..102df500cf3 100644
--- a/sbin/unwind/frontend.h
+++ b/sbin/unwind/frontend.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: frontend.h,v 1.1 2019/01/23 13:11:00 florian Exp $ */
+/* $OpenBSD: frontend.h,v 1.2 2019/02/03 12:02:30 florian Exp $ */
/*
* Copyright (c) 2018 Florian Obser <florian@openbsd.org>
@@ -22,5 +22,8 @@ TAILQ_HEAD(ctl_conns, ctl_conn) ctl_conns;
void frontend(int, int);
void frontend_dispatch_main(int, short, void *);
void frontend_dispatch_resolver(int, short, void *);
+void frontend_dispatch_captiveportal(int, short, void *);
int frontend_imsg_compose_main(int, pid_t, void *, uint16_t);
int frontend_imsg_compose_resolver(int, pid_t, void *, uint16_t);
+int frontend_imsg_compose_captiveportal(int, pid_t, void *, uint16_t);
+char *ip_port(struct sockaddr *);
diff --git a/sbin/unwind/printconf.c b/sbin/unwind/printconf.c
index 6f8fe1bddcd..5f544fd8e4f 100644
--- a/sbin/unwind/printconf.c
+++ b/sbin/unwind/printconf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: printconf.c,v 1.4 2019/01/29 19:32:36 florian Exp $ */
+/* $OpenBSD: printconf.c,v 1.5 2019/02/03 12:02:30 florian Exp $ */
/*
* Copyright (c) 2018 Florian Obser <florian@openbsd.org>
@@ -77,4 +77,17 @@ print_config(struct unwind_conf *conf)
}
printf("}\n");
}
+
+ if (conf->captive_portal_host != NULL) {
+ printf("captive portal {\n");
+ printf("\turl \"http://%s%s\"\n",
+ conf->captive_portal_host, conf->captive_portal_path);
+ printf("\texpected status %d\n",
+ conf->captive_portal_expected_status);
+ if (conf->captive_portal_expected_response != NULL)
+ printf("\texpected response \"%s\"\n",
+ conf->captive_portal_expected_response);
+ printf("\tauto %s\n", yesno(conf->captive_portal_auto));
+ printf("}\n");
+ }
}
diff --git a/sbin/unwind/resolver.c b/sbin/unwind/resolver.c
index 3e18936b1b0..6ce00e41afe 100644
--- a/sbin/unwind/resolver.c
+++ b/sbin/unwind/resolver.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: resolver.c,v 1.13 2019/01/29 19:13:01 florian Exp $ */
+/* $OpenBSD: resolver.c,v 1.14 2019/02/03 12:02:30 florian Exp $ */
/*
* Copyright (c) 2018 Florian Obser <florian@openbsd.org>
@@ -39,15 +39,20 @@
#include <unistd.h>
#include "libunbound/config.h"
+#include "libunbound/libunbound/libworker.h"
#include "libunbound/libunbound/unbound.h"
#include "libunbound/libunbound/unbound-event.h"
+#include "libunbound/sldns/sbuffer.h"
#include "libunbound/sldns/rrdef.h"
#include "libunbound/sldns/pkthdr.h"
#include "libunbound/sldns/wire2str.h"
+#include "libunbound/util/regional.h"
#include <openssl/crypto.h>
+#include "captiveportal.h"
#include "uw_log.h"
+#include "frontend.h"
#include "unwind.h"
#include "resolver.h"
@@ -78,6 +83,7 @@ struct check_resolver_data {
__dead void resolver_shutdown(void);
void resolver_sig_handler(int sig, short, void *);
void resolver_dispatch_frontend(int, short, void *);
+void resolver_dispatch_captiveportal(int, short, void *);
void resolver_dispatch_main(int, short, void *);
void resolve_done(void *, int, void *, int, int,
char *, int);
@@ -113,7 +119,12 @@ void send_detailed_resolver_info(struct unwind_resolver *,
pid_t);
void send_resolver_histogram_info(struct unwind_resolver *,
pid_t pid);
-
+void check_captive_portal(int);
+void check_captive_portal_timo(int, short, void *);
+void check_captive_portal_resolve_done(void *, int, void *,
+ int, int, char *, int);
+int check_captive_portal_changed(struct unwind_conf *,
+ struct unwind_conf *);
/* for openssl */
void init_locks(void);
unsigned long id_callback(void);
@@ -121,18 +132,23 @@ void lock_callback(int, int, const char *, int);
struct unwind_conf *resolver_conf;
struct imsgev *iev_frontend;
+struct imsgev *iev_captiveportal;
struct imsgev *iev_main;
struct unwind_forwarder_head dhcp_forwarder_list;
struct unwind_resolver *recursor, *forwarder, *static_forwarder;
struct unwind_resolver *static_dot_forwarder;
struct timeval resolver_check_pause = { 30, 0};
-
+#define PORTAL_CHECK_SEC 15
+#define PORTAL_CHECK_MAXSEC 600
+struct timeval captive_portal_check_tv = {PORTAL_CHECK_SEC, 0};
+struct event captive_portal_check_ev;
struct event_base *ev_base;
/* for openssl */
pthread_mutex_t *locks;
enum unwind_resolver_state global_state = DEAD;
+enum captive_portal_state captive_portal_state = PORTAL_UNCHECKED;
void
resolver_sig_handler(int sig, short event, void *arg)
@@ -207,6 +223,8 @@ resolver(int debug, int verbose)
iev_main->handler, iev_main);
event_add(&iev_main->ev, NULL);
+ evtimer_set(&captive_portal_check_ev, check_captive_portal_timo, NULL);
+
init_locks();
CRYPTO_set_id_callback(id_callback);
CRYPTO_set_locking_callback(lock_callback);
@@ -224,29 +242,19 @@ __dead void
resolver_shutdown(void)
{
log_debug("%s", __func__);
- /* XXX we might have many more ctx lying aroung */
- if (recursor != NULL) {
- recursor->ref_cnt = 0; /* not coming back to deref ctx*/
- free_resolver(recursor);
- }
- if (forwarder != NULL) {
- forwarder->ref_cnt = 0; /* not coming back to deref ctx */
- free_resolver(forwarder);
- }
- if (static_forwarder != NULL) {
- static_forwarder->ref_cnt = 0;
- free_resolver(static_forwarder);
- }
/* Close pipes. */
msgbuf_clear(&iev_frontend->ibuf.w);
close(iev_frontend->ibuf.fd);
+ msgbuf_clear(&iev_captiveportal->ibuf.w);
+ close(iev_captiveportal->ibuf.fd);
msgbuf_clear(&iev_main->ibuf.w);
close(iev_main->ibuf.fd);
config_clear(resolver_conf);
free(iev_frontend);
+ free(iev_captiveportal);
free(iev_main);
log_info("resolver exiting");
@@ -254,12 +262,27 @@ resolver_shutdown(void)
}
int
+resolver_imsg_compose_main(int type, pid_t pid, void *data, uint16_t datalen)
+{
+ return (imsg_compose_event(iev_main, type, 0, pid, -1, data,
+ datalen));
+}
+
+int
resolver_imsg_compose_frontend(int type, pid_t pid, void *data, uint16_t datalen)
{
return (imsg_compose_event(iev_frontend, type, 0, pid, -1,
data, datalen));
}
+int
+resolver_imsg_compose_captiveportal(int type, pid_t pid, void *data,
+ uint16_t datalen)
+{
+ return (imsg_compose_event(iev_captiveportal, type, 0, pid, -1,
+ data, datalen));
+}
+
void
resolver_dispatch_frontend(int fd, short event, void *bula)
{
@@ -336,7 +359,7 @@ resolver_dispatch_frontend(int fd, short event, void *bula)
if ((err = ub_resolve_event(res->ctx,
query_imsg->qname, query_imsg->t, query_imsg->c,
- (void *)query_imsg, resolve_done,
+ query_imsg, resolve_done,
&query_imsg->async_id)) != 0) {
log_warn("%s: ub_resolve_async: err: %d, %s",
__func__, err, ub_strerror(err));
@@ -356,6 +379,71 @@ resolver_dispatch_frontend(int fd, short event, void *bula)
memcpy(&type, imsg.data, sizeof(type));
show_status(type, imsg.hdr.pid);
break;
+ case IMSG_CTL_RECHECK_CAPTIVEPORTAL:
+ check_captive_portal(1);
+ break;
+ default:
+ log_debug("%s: unexpected imsg %d", __func__,
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ if (!shut)
+ imsg_event_add(iev);
+ else {
+ /* This pipe is dead. Remove its event handler. */
+ event_del(&iev->ev);
+ event_loopexit(NULL);
+ }
+}
+
+void
+resolver_dispatch_captiveportal(int fd, short event, void *bula)
+{
+ struct imsgev *iev = bula;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+ int shut = 0;
+
+
+ ibuf = &iev->ibuf;
+
+ if (event & EV_READ) {
+ if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+ fatal("imsg_read error");
+ if (n == 0) /* Connection closed. */
+ shut = 1;
+ }
+ if (event & EV_WRITE) {
+ if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+ fatal("msgbuf_write");
+ if (n == 0) /* Connection closed. */
+ shut = 1;
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("%s: imsg_get error", __func__);
+ if (n == 0) /* No more messages. */
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_CAPTIVEPORTAL_STATE:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE +
+ sizeof(captive_portal_state))
+ fatalx("%s: IMSG_CAPTIVEPORTAL_STATE wrong "
+ "length: %d", __func__, imsg.hdr.len);
+ memcpy(&captive_portal_state, imsg.data,
+ sizeof(captive_portal_state));
+ log_debug("%s: IMSG_CAPTIVEPORTAL_STATE: %s", __func__,
+ captive_portal_state_str[captive_portal_state]);
+
+ if (captive_portal_state == NOT_BEHIND)
+ evtimer_del(&captive_portal_check_ev);
+
+ break;
default:
log_debug("%s: unexpected imsg %d", __func__,
imsg.hdr.type);
@@ -383,6 +471,7 @@ resolver_dispatch_main(int fd, short event, void *bula)
ssize_t n;
int shut = 0, forwarders_changed;
int dot_forwarders_changed;
+ int captive_portal_changed;
ibuf = &iev->ibuf;
@@ -406,7 +495,7 @@ resolver_dispatch_main(int fd, short event, void *bula)
break;
switch (imsg.hdr.type) {
- case IMSG_SOCKET_IPC:
+ case IMSG_SOCKET_IPC_FRONTEND:
/*
* Setup pipe and event handler to the frontend
* process.
@@ -432,6 +521,34 @@ resolver_dispatch_main(int fd, short event, void *bula)
iev_frontend);
event_add(&iev_frontend->ev, NULL);
break;
+ case IMSG_SOCKET_IPC_CAPTIVEPORTAL:
+ /*
+ * Setup pipe and event handler to the captiveportal
+ * process.
+ */
+ if (iev_captiveportal)
+ fatalx("%s: received unexpected imsg fd "
+ "to resolver", __func__);
+
+ if ((fd = imsg.fd) == -1)
+ fatalx("%s: expected to receive imsg fd to "
+ "resolver but didn't receive any", __func__);
+
+ iev_captiveportal = malloc(sizeof(struct imsgev));
+ if (iev_captiveportal == NULL)
+ fatal(NULL);
+
+ imsg_init(&iev_captiveportal->ibuf, fd);
+ iev_captiveportal->handler =
+ resolver_dispatch_captiveportal;
+ iev_captiveportal->events = EV_READ;
+
+ event_set(&iev_captiveportal->ev,
+ iev_captiveportal->ibuf.fd,
+ iev_captiveportal->events, iev_captiveportal->handler,
+ iev_captiveportal);
+ event_add(&iev_captiveportal->ev, NULL);
+ break;
case IMSG_RECONF_CONF:
if (imsg.hdr.len != IMSG_HEADER_SIZE +
sizeof(struct unwind_conf))
@@ -440,9 +557,36 @@ resolver_dispatch_main(int fd, short event, void *bula)
if ((nconf = malloc(sizeof(struct unwind_conf))) == NULL)
fatal(NULL);
memcpy(nconf, imsg.data, sizeof(struct unwind_conf));
+ nconf->captive_portal_host = NULL;
+ nconf->captive_portal_path = NULL;
+ nconf->captive_portal_expected_response = NULL;
SIMPLEQ_INIT(&nconf->unwind_forwarder_list);
SIMPLEQ_INIT(&nconf->unwind_dot_forwarder_list);
break;
+ case IMSG_RECONF_CAPTIVE_PORTAL_HOST:
+ /* make sure this is a string */
+ ((char *)imsg.data)[imsg.hdr.len - IMSG_HEADER_SIZE - 1]
+ = '\0';
+ if ((nconf->captive_portal_host = strdup(imsg.data)) ==
+ NULL)
+ fatal("%s: strdup", __func__);
+ break;
+ case IMSG_RECONF_CAPTIVE_PORTAL_PATH:
+ /* make sure this is a string */
+ ((char *)imsg.data)[imsg.hdr.len - IMSG_HEADER_SIZE - 1]
+ = '\0';
+ if ((nconf->captive_portal_path = strdup(imsg.data)) ==
+ NULL)
+ fatal("%s: strdup", __func__);
+ break;
+ case IMSG_RECONF_CAPTIVE_PORTAL_EXPECTED_RESPONSE:
+ /* make sure this is a string */
+ ((char *)imsg.data)[imsg.hdr.len - IMSG_HEADER_SIZE - 1]
+ = '\0';
+ if ((nconf->captive_portal_expected_response =
+ strdup(imsg.data)) == NULL)
+ fatal("%s: strdup", __func__);
+ break;
case IMSG_RECONF_FORWARDER:
if (imsg.hdr.len != IMSG_HEADER_SIZE +
sizeof(struct unwind_forwarder))
@@ -476,6 +620,8 @@ resolver_dispatch_main(int fd, short event, void *bula)
dot_forwarders_changed = check_forwarders_changed(
&resolver_conf->unwind_dot_forwarder_list,
&nconf->unwind_dot_forwarder_list);
+ captive_portal_changed = check_captive_portal_changed(
+ resolver_conf, nconf);
merge_config(resolver_conf, nconf);
nconf = NULL;
if (forwarders_changed) {
@@ -486,6 +632,13 @@ resolver_dispatch_main(int fd, short event, void *bula)
log_debug("static DoT forwarders changed");
new_static_dot_forwarders();
}
+ if (resolver_conf->captive_portal_host == NULL)
+ captive_portal_state = PORTAL_UNCHECKED;
+ else if (captive_portal_state == PORTAL_UNCHECKED ||
+ captive_portal_changed) {
+ if (resolver_conf->captive_portal_auto)
+ check_captive_portal(1);
+ }
break;
default:
log_debug("%s: unexpected imsg %d", __func__,
@@ -607,6 +760,8 @@ parse_dhcp_forwarders(char *resolvers)
&dhcp_forwarder_list)) {
replace_forwarders(&new_forwarder_list, &dhcp_forwarder_list);
new_forwarders();
+ if (resolver_conf->captive_portal_auto)
+ check_captive_portal(1);
} else
log_debug("%s: forwarders didn't change", __func__);
}
@@ -1012,6 +1167,14 @@ best_resolver(void)
unwind_resolver_type_str[forwarder->type],
unwind_resolver_state_str[forwarder->state]);
+ log_debug("%s: %s captive portal", __func__, captive_portal_state_str[
+ captive_portal_state]);
+
+ if (captive_portal_state == UNKNOWN || captive_portal_state == BEHIND) {
+ if (forwarder != NULL)
+ return (forwarder);
+ }
+
res = recursor;
if (resolver_cmp(res, static_dot_forwarder) < 0)
@@ -1065,6 +1228,8 @@ show_status(enum unwind_resolver_type type, pid_t pid)
switch(type) {
case RESOLVER_NONE:
+ resolver_imsg_compose_frontend(IMSG_CTL_CAPTIVEPORTAL_INFO,
+ pid, &captive_portal_state, sizeof(captive_portal_state));
send_resolver_info(recursor, recursor == best, pid);
send_resolver_info(forwarder, forwarder == best, pid);
send_resolver_info(static_forwarder, static_forwarder == best,
@@ -1134,3 +1299,156 @@ send_resolver_histogram_info(struct unwind_resolver *res, pid_t pid)
resolver_imsg_compose_frontend(IMSG_CTL_RESOLVER_HISTOGRAM,
pid, histogram, sizeof(histogram));
}
+
+void
+check_captive_portal_timo(int fd, short events, void *arg)
+{
+ captive_portal_check_tv.tv_sec *= 2;
+ if (captive_portal_check_tv.tv_sec > PORTAL_CHECK_MAXSEC)
+ captive_portal_check_tv.tv_sec = PORTAL_CHECK_MAXSEC;
+ check_captive_portal(0);
+}
+
+void
+check_captive_portal(int timer_reset)
+{
+ struct unwind_resolver *res;
+ int err;
+
+ log_debug("%s", __func__);
+
+ if (resolver_conf->captive_portal_host == NULL) {
+ log_debug("%s: no captive portal url configured", __func__);
+ return;
+ }
+
+ if (forwarder == NULL) {
+ log_debug("%s no DHCP nameservers known", __func__);
+ return;
+ }
+
+ if (timer_reset)
+ captive_portal_check_tv.tv_sec = PORTAL_CHECK_SEC;
+
+ evtimer_add(&captive_portal_check_ev, &captive_portal_check_tv);
+
+
+ captive_portal_state = PORTAL_UNKNOWN;
+
+ res = forwarder;
+
+ resolver_ref(res);
+
+ if ((err = ub_resolve_event(res->ctx,
+ resolver_conf->captive_portal_host, LDNS_RR_TYPE_A,
+ LDNS_RR_CLASS_IN, res, check_captive_portal_resolve_done,
+ NULL)) != 0)
+ log_warn("%s: ub_resolve_async: err: %d, %s",
+ __func__, err, ub_strerror(err));
+
+}
+
+void
+check_captive_portal_resolve_done(void *arg, int rcode, void *answer_packet,
+ int answer_len, int sec, char *why_bogus, int was_ratelimited)
+{
+ struct unwind_resolver *res;
+ struct ub_result *result;
+ sldns_buffer *buf;
+ struct regional *region;
+ struct sockaddr_in sin;
+ int i;
+ char *str, rdata_buf[sizeof("XXX.XXX.XXX.XXX")];
+
+ res = (struct unwind_resolver *)arg;
+
+ if ((result = calloc(1, sizeof(*result))) == NULL)
+ goto out;
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_len = sizeof(sin);
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(80);
+
+ log_debug("%s: %d", __func__, rcode);
+
+ if ((str = sldns_wire2str_pkt(answer_packet, answer_len)) != NULL) {
+ log_debug("%s", str);
+ free(str);
+ }
+
+ buf = sldns_buffer_new(answer_len);
+ region = regional_create();
+ result->rcode = LDNS_RCODE_SERVFAIL;
+ if(region && buf) {
+ sldns_buffer_clear(buf);
+ sldns_buffer_write(buf, answer_packet, answer_len);
+ sldns_buffer_flip(buf);
+ libworker_enter_result(result, buf, region, sec);
+ result->answer_packet = NULL; //answer_packet;
+ result->answer_len = 0; //answer_len;
+ sldns_buffer_free(buf);
+ regional_destroy(region);
+
+ i = 0;
+ while(result->data[i] != NULL) {
+ sldns_wire2str_rdata_buf(result->data[i],
+ result->len[i], rdata_buf, sizeof(rdata_buf),
+ LDNS_RR_TYPE_A);
+ log_debug("%s: result[%d] = %d: %s", __func__, i,
+ result->len[i], rdata_buf);
+
+ memcpy(&sin.sin_addr.s_addr, result->data[i],
+ sizeof(sin.sin_addr.s_addr));
+ log_debug("%s: ip_port: %s", __func__,
+ ip_port((struct sockaddr *)&sin));
+
+ resolver_imsg_compose_main(IMSG_OPEN_HTTP_PORT, 0,
+ &sin, sizeof(sin));
+ i++;
+ }
+ }
+out:
+ ub_resolve_free(result);
+ resolver_unref(res);
+}
+
+int
+check_captive_portal_changed(struct unwind_conf *a, struct unwind_conf *b)
+{
+
+ if (a->captive_portal_expected_status !=
+ b->captive_portal_expected_status)
+ return (1);
+
+ if (a->captive_portal_host == NULL && b->captive_portal_host != NULL)
+ return (1);
+ if (a->captive_portal_host != NULL && b->captive_portal_host == NULL)
+ return (1);
+ if (a->captive_portal_host != NULL && b->captive_portal_host != NULL &&
+ strcmp(a->captive_portal_host, b->captive_portal_host) != 0)
+ return (1);
+
+ if (a->captive_portal_path == NULL && b->captive_portal_path != NULL)
+ return (1);
+ if (a->captive_portal_path != NULL && b->captive_portal_path == NULL)
+ return (1);
+ if (a->captive_portal_path != NULL && b->captive_portal_path != NULL &&
+ strcmp(a->captive_portal_path, b->captive_portal_path) != 0)
+ return (1);
+
+ if (a->captive_portal_expected_response == NULL &&
+ b->captive_portal_expected_response != NULL)
+ return (1);
+ if (a->captive_portal_expected_response != NULL &&
+ b->captive_portal_expected_response == NULL)
+ return (1);
+ if (a->captive_portal_expected_response != NULL &&
+ b->captive_portal_expected_response != NULL &&
+ strcmp(a->captive_portal_expected_response,
+ b->captive_portal_expected_response) != 0)
+ return (1);
+
+ return (0);
+
+}
diff --git a/sbin/unwind/resolver.h b/sbin/unwind/resolver.h
index da9525ba3a0..2265e3c7ec1 100644
--- a/sbin/unwind/resolver.h
+++ b/sbin/unwind/resolver.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: resolver.h,v 1.2 2019/01/27 12:40:54 florian Exp $ */
+/* $OpenBSD: resolver.h,v 1.3 2019/02/03 12:02:30 florian Exp $ */
/*
* Copyright (c) 2018 Florian Obser <florian@openbsd.org>
@@ -68,5 +68,7 @@ struct ctl_resolver_info {
int selected;
};
-void resolver(int, int);
-int resolver_imsg_compose_frontend(int, pid_t, void *, uint16_t);
+void resolver(int, int);
+int resolver_imsg_compose_main(int, pid_t, void *, uint16_t);
+int resolver_imsg_compose_frontend(int, pid_t, void *, uint16_t);
+int resolver_imsg_compose_captiveportal(int, pid_t, void *, uint16_t);
diff --git a/sbin/unwind/unwind.c b/sbin/unwind/unwind.c
index d72b98f98b0..5e829a9c389 100644
--- a/sbin/unwind/unwind.c
+++ b/sbin/unwind/unwind.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: unwind.c,v 1.9 2019/02/01 15:52:35 florian Exp $ */
+/* $OpenBSD: unwind.c,v 1.10 2019/02/03 12:02:30 florian Exp $ */
/*
* Copyright (c) 2018 Florian Obser <florian@openbsd.org>
@@ -45,6 +45,7 @@
#include "frontend.h"
#include "resolver.h"
#include "control.h"
+#include "captiveportal.h"
__dead void usage(void);
__dead void main_shutdown(void);
@@ -55,22 +56,26 @@ static pid_t start_child(int, char *, int, int, int);
void main_dispatch_frontend(int, short, void *);
void main_dispatch_resolver(int, short, void *);
+void main_dispatch_captiveportal(int, short, void *);
-static int main_imsg_send_ipc_sockets(struct imsgbuf *, struct imsgbuf *);
+static int main_imsg_send_ipc_sockets(struct imsgbuf *, struct imsgbuf *,
+ struct imsgbuf *);
static int main_imsg_send_config(struct unwind_conf *);
int main_reload(void);
-int main_sendboth(enum imsg_type, void *, uint16_t);
+int main_sendall(enum imsg_type, void *, uint16_t);
void open_dhcp_lease(int);
void open_ports(void);
struct unwind_conf *main_conf;
struct imsgev *iev_frontend;
struct imsgev *iev_resolver;
+struct imsgev *iev_captiveportal;
char *conffile;
pid_t frontend_pid;
pid_t resolver_pid;
+pid_t captiveportal_pid;
uint32_t cmd_opts;
@@ -113,10 +118,12 @@ main(int argc, char *argv[])
{
struct event ev_sigint, ev_sigterm, ev_sighup;
int ch;
- int debug = 0, resolver_flag = 0, frontend_flag = 0;
+ int debug = 0, resolver_flag = 0;
+ int frontend_flag = 0, captiveportal_flag = 0;
char *saved_argv0;
int pipe_main2frontend[2];
int pipe_main2resolver[2];
+ int pipe_main2captiveportal[2];
int frontend_routesock, rtfilter;
int control_fd;
char *csock;
@@ -131,8 +138,11 @@ main(int argc, char *argv[])
if (saved_argv0 == NULL)
saved_argv0 = "unwind";
- while ((ch = getopt(argc, argv, "dEFf:ns:v")) != -1) {
+ while ((ch = getopt(argc, argv, "CdEFf:ns:v")) != -1) {
switch (ch) {
+ case 'C':
+ captiveportal_flag = 1;
+ break;
case 'd':
debug = 1;
break;
@@ -163,13 +173,15 @@ main(int argc, char *argv[])
argc -= optind;
argv += optind;
- if (argc > 0 || (resolver_flag && frontend_flag))
+ if (argc > 0 || (resolver_flag && frontend_flag && captiveportal_flag))
usage();
if (resolver_flag)
resolver(debug, cmd_opts & (OPT_VERBOSE | OPT_VERBOSE2));
else if (frontend_flag)
frontend(debug, cmd_opts & (OPT_VERBOSE | OPT_VERBOSE2));
+ else if (captiveportal_flag)
+ captiveportal(debug, cmd_opts & (OPT_VERBOSE | OPT_VERBOSE2));
if ((main_conf = parse_config(conffile)) == NULL)
exit(1);
@@ -204,6 +216,9 @@ main(int argc, char *argv[])
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
PF_UNSPEC, pipe_main2resolver) == -1)
fatal("main2resolver socketpair");
+ if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
+ PF_UNSPEC, pipe_main2captiveportal) == -1)
+ fatal("main2captiveportal socketpair");
/* Start children. */
resolver_pid = start_child(PROC_RESOLVER, saved_argv0,
@@ -212,6 +227,9 @@ main(int argc, char *argv[])
frontend_pid = start_child(PROC_FRONTEND, saved_argv0,
pipe_main2frontend[1], debug, cmd_opts & (OPT_VERBOSE |
OPT_VERBOSE2));
+ captiveportal_pid = start_child(PROC_CAPTIVEPORTAL, saved_argv0,
+ pipe_main2captiveportal[1], debug, cmd_opts & (OPT_VERBOSE |
+ OPT_VERBOSE2));
unwind_process = PROC_MAIN;
log_procinit(log_procnames[unwind_process]);
@@ -230,14 +248,17 @@ main(int argc, char *argv[])
/* Setup pipes to children. */
if ((iev_frontend = malloc(sizeof(struct imsgev))) == NULL ||
+ (iev_captiveportal = malloc(sizeof(struct imsgev))) == NULL ||
(iev_resolver = malloc(sizeof(struct imsgev))) == NULL)
fatal(NULL);
imsg_init(&iev_frontend->ibuf, pipe_main2frontend[0]);
iev_frontend->handler = main_dispatch_frontend;
imsg_init(&iev_resolver->ibuf, pipe_main2resolver[0]);
iev_resolver->handler = main_dispatch_resolver;
+ imsg_init(&iev_captiveportal->ibuf, pipe_main2captiveportal[0]);
+ iev_captiveportal->handler = main_dispatch_captiveportal;
- /* Setup event handlers for pipes to resolver & frontend. */
+ /* Setup event handlers for pipes. */
iev_frontend->events = EV_READ;
event_set(&iev_frontend->ev, iev_frontend->ibuf.fd,
iev_frontend->events, iev_frontend->handler, iev_frontend);
@@ -248,7 +269,14 @@ main(int argc, char *argv[])
iev_resolver->handler, iev_resolver);
event_add(&iev_resolver->ev, NULL);
- if (main_imsg_send_ipc_sockets(&iev_frontend->ibuf, &iev_resolver->ibuf))
+ iev_captiveportal->events = EV_READ;
+ event_set(&iev_captiveportal->ev, iev_captiveportal->ibuf.fd,
+ iev_captiveportal->events, iev_captiveportal->handler,
+ iev_captiveportal);
+ event_add(&iev_captiveportal->ev, NULL);
+
+ if (main_imsg_send_ipc_sockets(&iev_frontend->ibuf,
+ &iev_resolver->ibuf, &iev_captiveportal->ibuf))
fatal("could not establish imsg links");
if ((control_fd = control_init(csock)) == -1)
@@ -290,6 +318,8 @@ main_shutdown(void)
close(iev_frontend->ibuf.fd);
msgbuf_clear(&iev_resolver->ibuf.w);
close(iev_resolver->ibuf.fd);
+ msgbuf_clear(&iev_captiveportal->ibuf.w);
+ close(iev_captiveportal->ibuf.fd);
config_clear(main_conf);
@@ -307,6 +337,7 @@ main_shutdown(void)
free(iev_frontend);
free(iev_resolver);
+ free(iev_captiveportal);
log_info("terminating");
exit(0);
@@ -342,6 +373,9 @@ start_child(int p, char *argv0, int fd, int debug, int verbose)
case PROC_FRONTEND:
argv[argc++] = "-F";
break;
+ case PROC_CAPTIVEPORTAL:
+ argv[argc++] = "-C";
+ break;
}
if (debug)
argv[argc++] = "-d";
@@ -433,6 +467,77 @@ main_dispatch_frontend(int fd, short event, void *bula)
void
main_dispatch_resolver(int fd, short event, void *bula)
{
+ struct imsgev *iev = bula;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ struct sockaddr_in sin;
+ ssize_t n;
+ int shut = 0, httpsock;
+
+ ibuf = &iev->ibuf;
+
+ if (event & EV_READ) {
+ if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+ fatal("imsg_read error");
+ if (n == 0) /* Connection closed. */
+ shut = 1;
+ }
+ if (event & EV_WRITE) {
+ if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+ fatal("msgbuf_write");
+ if (n == 0) /* Connection closed. */
+ shut = 1;
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("imsg_get");
+ if (n == 0) /* No more messages. */
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_OPEN_HTTP_PORT:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE +
+ sizeof(sin))
+ fatalx("%s: IMSG_OPEN_HTTP_PORT wrong length: "
+ "%d", __func__, imsg.hdr.len);
+ memcpy(&sin, imsg.data, sizeof(sin));
+
+ if ((httpsock = socket(AF_INET, SOCK_STREAM |
+ SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) == -1) {
+ log_warn("%s: socket", __func__);
+ break;
+ }
+ if (connect(httpsock, (struct sockaddr *)&sin,
+ sin.sin_len) == -1) {
+ if (errno != EINPROGRESS) {
+ log_warn("%s: connect", __func__);
+ close(httpsock);
+ break;
+ }
+ }
+ main_imsg_compose_captiveportal_fd(IMSG_HTTPSOCK, 0,
+ httpsock);
+ break;
+ default:
+ log_debug("%s: error handling imsg %d", __func__,
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ if (!shut)
+ imsg_event_add(iev);
+ else {
+ /* This pipe is dead. Remove its event handler. */
+ event_del(&iev->ev);
+ event_loopexit(NULL);
+ }
+}
+
+void
+main_dispatch_captiveportal(int fd, short event, void *bula)
+{
struct imsgev *iev = bula;
struct imsgbuf *ibuf;
struct imsg imsg;
@@ -492,7 +597,6 @@ main_imsg_compose_frontend_fd(int type, pid_t pid, int fd)
imsg_compose_event(iev_frontend, type, 0, pid, fd, NULL, 0);
}
-
void
main_imsg_compose_resolver(int type, pid_t pid, void *data, uint16_t datalen)
{
@@ -502,6 +606,23 @@ main_imsg_compose_resolver(int type, pid_t pid, void *data, uint16_t datalen)
}
void
+main_imsg_compose_captiveportal(int type, pid_t pid, void *data,
+ uint16_t datalen)
+{
+ if (iev_captiveportal)
+ imsg_compose_event(iev_captiveportal, type, 0, pid, -1, data,
+ datalen);
+}
+
+void
+main_imsg_compose_captiveportal_fd(int type, pid_t pid, int fd)
+{
+ if (iev_frontend)
+ imsg_compose_event(iev_captiveportal, type, 0, pid, fd, NULL,
+ 0);
+}
+
+void
imsg_event_add(struct imsgev *iev)
{
iev->events = EV_READ;
@@ -528,21 +649,45 @@ imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid,
static int
main_imsg_send_ipc_sockets(struct imsgbuf *frontend_buf,
- struct imsgbuf *resolver_buf)
+ struct imsgbuf *resolver_buf, struct imsgbuf *captiveportal_buf)
{
int pipe_frontend2resolver[2];
+ int pipe_frontend2captiveportal[2];
+ int pipe_resolver2captiveportal[2];
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
PF_UNSPEC, pipe_frontend2resolver) == -1)
return (-1);
- if (imsg_compose(frontend_buf, IMSG_SOCKET_IPC, 0, 0,
+ if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
+ PF_UNSPEC, pipe_frontend2captiveportal) == -1)
+ return (-1);
+
+ if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
+ PF_UNSPEC, pipe_resolver2captiveportal) == -1)
+ return (-1);
+
+ if (imsg_compose(frontend_buf, IMSG_SOCKET_IPC_RESOLVER, 0, 0,
pipe_frontend2resolver[0], NULL, 0) == -1)
return (-1);
- if (imsg_compose(resolver_buf, IMSG_SOCKET_IPC, 0, 0,
+ if (imsg_compose(resolver_buf, IMSG_SOCKET_IPC_FRONTEND, 0, 0,
pipe_frontend2resolver[1], NULL, 0) == -1)
return (-1);
+ if (imsg_compose(frontend_buf, IMSG_SOCKET_IPC_CAPTIVEPORTAL, 0, 0,
+ pipe_frontend2captiveportal[0], NULL, 0) == -1)
+ return (-1);
+ if (imsg_compose(captiveportal_buf, IMSG_SOCKET_IPC_FRONTEND, 0, 0,
+ pipe_frontend2captiveportal[1], NULL, 0) == -1)
+ return (-1);
+
+ if (imsg_compose(resolver_buf, IMSG_SOCKET_IPC_CAPTIVEPORTAL, 0, 0,
+ pipe_resolver2captiveportal[0], NULL, 0) == -1)
+ return (-1);
+ if (imsg_compose(captiveportal_buf, IMSG_SOCKET_IPC_RESOLVER, 0, 0,
+ pipe_resolver2captiveportal[1], NULL, 0) == -1)
+ return (-1);
+
return (0);
}
@@ -568,40 +713,62 @@ main_imsg_send_config(struct unwind_conf *xconf)
struct unwind_forwarder *unwind_forwarder;
/* Send fixed part of config to children. */
- if (main_sendboth(IMSG_RECONF_CONF, xconf, sizeof(*xconf)) == -1)
+ if (main_sendall(IMSG_RECONF_CONF, xconf, sizeof(*xconf)) == -1)
return (-1);
+ if (xconf->captive_portal_host != NULL) {
+ if (main_sendall(IMSG_RECONF_CAPTIVE_PORTAL_HOST,
+ xconf->captive_portal_host,
+ strlen(xconf->captive_portal_host) + 1) == -1)
+ return (-1);
+ }
+
+ if (xconf->captive_portal_path != NULL) {
+ if (main_sendall(IMSG_RECONF_CAPTIVE_PORTAL_PATH,
+ xconf->captive_portal_path,
+ strlen(xconf->captive_portal_path) + 1) == -1)
+ return (-1);
+ }
+
+ if (xconf->captive_portal_expected_response != NULL) {
+ if (main_sendall(IMSG_RECONF_CAPTIVE_PORTAL_EXPECTED_RESPONSE,
+ xconf->captive_portal_expected_response,
+ strlen(xconf->captive_portal_expected_response) + 1)
+ == -1)
+ return (-1);
+ }
/* send static forwarders to children */
SIMPLEQ_FOREACH(unwind_forwarder, &xconf->unwind_forwarder_list, entry) {
- if (main_sendboth(IMSG_RECONF_FORWARDER, unwind_forwarder,
+ if (main_sendall(IMSG_RECONF_FORWARDER, unwind_forwarder,
sizeof(*unwind_forwarder)) == -1)
return (-1);
-
}
/* send static DoT forwarders to children */
SIMPLEQ_FOREACH(unwind_forwarder, &xconf->unwind_dot_forwarder_list,
entry) {
- if (main_sendboth(IMSG_RECONF_DOT_FORWARDER, unwind_forwarder,
+ if (main_sendall(IMSG_RECONF_DOT_FORWARDER, unwind_forwarder,
sizeof(*unwind_forwarder)) == -1)
return (-1);
-
}
/* Tell children the revised config is now complete. */
- if (main_sendboth(IMSG_RECONF_END, NULL, 0) == -1)
+ if (main_sendall(IMSG_RECONF_END, NULL, 0) == -1)
return (-1);
return (0);
}
int
-main_sendboth(enum imsg_type type, void *buf, uint16_t len)
+main_sendall(enum imsg_type type, void *buf, uint16_t len)
{
if (imsg_compose_event(iev_frontend, type, 0, 0, -1, buf, len) == -1)
return (-1);
if (imsg_compose_event(iev_resolver, type, 0, 0, -1, buf, len) == -1)
return (-1);
+ if (imsg_compose_event(iev_captiveportal, type, 0, 0, -1, buf, len) ==
+ -1)
+ return (-1);
return (0);
}
@@ -624,6 +791,21 @@ merge_config(struct unwind_conf *conf, struct unwind_conf *xconf)
conf->unwind_options = xconf->unwind_options;
+ free(conf->captive_portal_host);
+ conf->captive_portal_host = xconf->captive_portal_host;
+
+ free(conf->captive_portal_path);
+ conf->captive_portal_path = xconf->captive_portal_path;
+
+ free(conf->captive_portal_expected_response);
+ conf->captive_portal_expected_response =
+ xconf->captive_portal_expected_response;
+
+ conf->captive_portal_expected_status =
+ xconf->captive_portal_expected_status;
+
+ conf->captive_portal_auto = xconf->captive_portal_auto;
+
/* Add new forwarders. */
while ((unwind_forwarder =
SIMPLEQ_FIRST(&xconf->unwind_forwarder_list)) != NULL) {
@@ -653,6 +835,11 @@ config_new_empty(void)
SIMPLEQ_INIT(&xconf->unwind_forwarder_list);
SIMPLEQ_INIT(&xconf->unwind_dot_forwarder_list);
+ if ((xconf->captive_portal_expected_response = strdup("")) == NULL)
+ fatal(NULL);
+ xconf->captive_portal_expected_status = 200;
+ xconf->captive_portal_auto = 1;
+
return (xconf);
}
diff --git a/sbin/unwind/unwind.conf.5 b/sbin/unwind/unwind.conf.5
index d20526e4a27..8e4b89e5292 100644
--- a/sbin/unwind/unwind.conf.5
+++ b/sbin/unwind/unwind.conf.5
@@ -1,4 +1,4 @@
-.\" $OpenBSD: unwind.conf.5,v 1.3 2019/01/29 19:30:41 florian Exp $
+.\" $OpenBSD: unwind.conf.5,v 1.4 2019/02/03 12:02:30 florian Exp $
.\"
.\" Copyright (c) 2018 Florian Obser <florian@openbsd.org>
.\" Copyright (c) 2005 Esben Norby <norby@openbsd.org>
@@ -18,7 +18,7 @@
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
-.Dd $Mdocdate: January 29 2019 $
+.Dd $Mdocdate: February 3 2019 $
.Dt UNWIND.CONF 5
.Os
.Sh NAME
@@ -69,7 +69,56 @@ If
.Ic DoT
is specified use DNS over TLS when sending queries to the server at
.Ar address .
-
+.El
+.Pp
+.Nm unwind
+can detect when it is running behind a
+.Dq Captive Portal
+by sending a HTTP request and checking the response against the
+configured expected response.
+The check will be triggered when
+.Xr dhclient 8
+reports new nameservers.
+If the response does not match,
+.Nm unwind
+will use the DHCP provided nameservers and periodically re-checks if the user
+passed the
+.Dq Captive Portal .
+.Bl -tag -width Ds
+.It Ic captive portal Brq ...
+.Dq CaptivePortal
+detection is configured inside the
+.Oc captive portal
+block.
+.Bl -tag -width "url URL" -compatc
+.It Ic url Ar URL
+URL to send HTTP queries to.
+This parameter is required.
+.It Ic expected response Ar response
+The body of the HTTP response is compare to
+.Ar response.
+The default is the empty string.
+.It Ic expteced status Ar status
+The expected HTTP status code.
+The default is 200.
+.It Ic auto Op Cm yes | Cm no
+When
+.Ic auto
+is set to
+.Cm yes
+.Nm unwind
+automatically triggers a
+.Dq Captive Portal
+check when the network is changed.
+When set to
+.Cm no
+a
+.Dq Captive Portal
+check can be triggered by
+.Xr unwindctl 8 .
+The default is
+.Cm yes
+.El
.El
.Sh FILES
.Bl -tag -width "/etc/unwind.conf" -compact
diff --git a/sbin/unwind/unwind.h b/sbin/unwind/unwind.h
index 2ee967fde81..94d11b45682 100644
--- a/sbin/unwind/unwind.h
+++ b/sbin/unwind/unwind.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: unwind.h,v 1.5 2019/01/30 12:17:02 benno Exp $ */
+/* $OpenBSD: unwind.h,v 1.6 2019/02/03 12:02:30 florian Exp $ */
/*
* Copyright (c) 2018 Florian Obser <florian@openbsd.org>
@@ -38,13 +38,15 @@
enum {
PROC_MAIN,
PROC_RESOLVER,
- PROC_FRONTEND
+ PROC_FRONTEND,
+ PROC_CAPTIVEPORTAL,
} unwind_process;
static const char * const log_procnames[] = {
"main",
"resolver",
"frontend",
+ "captive portal",
};
struct imsgev {
@@ -59,7 +61,11 @@ enum imsg_type {
IMSG_CTL_LOG_VERBOSE,
IMSG_CTL_RELOAD,
IMSG_CTL_STATUS,
+ IMSG_CTL_CAPTIVEPORTAL_INFO,
IMSG_RECONF_CONF,
+ IMSG_RECONF_CAPTIVE_PORTAL_HOST,
+ IMSG_RECONF_CAPTIVE_PORTAL_PATH,
+ IMSG_RECONF_CAPTIVE_PORTAL_EXPECTED_RESPONSE,
IMSG_RECONF_FORWARDER,
IMSG_RECONF_DOT_FORWARDER,
IMSG_RECONF_END,
@@ -69,7 +75,9 @@ enum imsg_type {
IMSG_CONTROLFD,
IMSG_STARTUP,
IMSG_STARTUP_DONE,
- IMSG_SOCKET_IPC,
+ IMSG_SOCKET_IPC_FRONTEND,
+ IMSG_SOCKET_IPC_RESOLVER,
+ IMSG_SOCKET_IPC_CAPTIVEPORTAL,
IMSG_QUERY,
IMSG_ANSWER_HEADER,
IMSG_ANSWER,
@@ -82,7 +90,11 @@ enum imsg_type {
IMSG_CTL_RESOLVER_INFO,
IMSG_CTL_RESOLVER_WHY_BOGUS,
IMSG_CTL_RESOLVER_HISTOGRAM,
- IMSG_CTL_END
+ IMSG_CTL_END,
+ IMSG_CTL_RECHECK_CAPTIVEPORTAL,
+ IMSG_OPEN_HTTP_PORT,
+ IMSG_HTTPSOCK,
+ IMSG_CAPTIVEPORTAL_STATE
};
struct unwind_forwarder {
@@ -92,8 +104,13 @@ struct unwind_forwarder {
struct unwind_conf {
SIMPLEQ_HEAD(unwind_forwarder_head, unwind_forwarder) unwind_forwarder_list;
- struct unwind_forwarder_head unwind_dot_forwarder_list;
- int unwind_options;
+ struct unwind_forwarder_head unwind_dot_forwarder_list;
+ int unwind_options;
+ char *captive_portal_host;
+ char *captive_portal_path;
+ char *captive_portal_expected_response;
+ int captive_portal_expected_status;
+ int captive_portal_auto;
};
struct query_imsg {
@@ -113,8 +130,9 @@ extern uint32_t cmd_opts;
/* unwind.c */
void main_imsg_compose_frontend(int, pid_t, void *, uint16_t);
void main_imsg_compose_frontend_fd(int, pid_t, int);
-
void main_imsg_compose_resolver(int, pid_t, void *, uint16_t);
+void main_imsg_compose_captiveportal(int, pid_t, void *, uint16_t);
+void main_imsg_compose_captiveportal_fd(int, pid_t, int);
void merge_config(struct unwind_conf *, struct unwind_conf *);
void imsg_event_add(struct imsgev *);
int imsg_compose_event(struct imsgev *, uint16_t, uint32_t, pid_t,
diff --git a/sbin/unwind/uw_parse.y b/sbin/unwind/uw_parse.y
index c5804759a44..cd710d0bff0 100644
--- a/sbin/unwind/uw_parse.y
+++ b/sbin/unwind/uw_parse.y
@@ -1,4 +1,4 @@
-/* $OpenBSD: uw_parse.y,v 1.8 2019/01/30 12:18:48 benno Exp $ */
+/* $OpenBSD: uw_parse.y,v 1.9 2019/02/03 12:02:30 florian Exp $ */
/*
* Copyright (c) 2018 Florian Obser <florian@openbsd.org>
@@ -99,7 +99,8 @@ typedef struct {
%}
%token STRICT YES NO INCLUDE ERROR
-%token FORWARDER DOT PORT
+%token FORWARDER DOT PORT CAPTIVE PORTAL URL EXPECTED RESPONSE
+%token STATUS AUTO
%token <v.string> STRING
%token <v.number> NUMBER
@@ -114,6 +115,7 @@ grammar : /* empty */
| grammar conf_main '\n'
| grammar varset '\n'
| grammar unwind_forwarder '\n'
+ | grammar captive_portal '\n'
| grammar error '\n' { file->errors++; }
;
@@ -176,8 +178,55 @@ optnl : '\n' optnl /* zero or more newlines */
| /*empty*/
;
-nl : '\n' optnl /* one or more newlines */
- ;
+captive_portal : CAPTIVE PORTAL captive_portal_block
+ ;
+captive_portal_block : '{' optnl captive_portal_opts_l '}'
+ | captive_portal_optsl
+ ;
+
+captive_portal_opts_l : captive_portal_opts_l captive_portal_optsl optnl
+ | captive_portal_optsl optnl
+ ;
+
+captive_portal_optsl : URL STRING {
+ char *ep;
+ if (strncmp($2, "http://", 7) != 0) {
+ yyerror("only http:// urls are "
+ "supported: %s", $2);
+ free($2);
+ YYERROR;
+ }
+ if ((ep = strchr($2 + 7, '/')) != NULL) {
+ conf->captive_portal_path =
+ strdup(ep);
+ *ep = '\0';
+ } else
+ conf->captive_portal_path = strdup("/");
+ if (conf->captive_portal_path == NULL)
+ err(1, "strdup");
+ if ((conf->captive_portal_host =
+ strdup($2 + 7)) == NULL)
+ err(1, "strdup");
+ free($2);
+ }
+ | EXPECTED RESPONSE STRING {
+ if ((conf->captive_portal_expected_response =
+ strdup($3)) == NULL)
+ err(1, "strdup");
+ free($3);
+ }
+ | EXPECTED STATUS NUMBER {
+ if ($3 < 100 || $3 > 599) {
+ yyerror("%lld is an invalid http "
+ "status", $3);
+ YYERROR;
+ }
+ conf->captive_portal_expected_status = $3;
+ }
+ | AUTO yesno {
+ conf->captive_portal_auto = $2;
+ }
+ ;
unwind_forwarder : FORWARDER forwarder_block
;
@@ -188,6 +237,7 @@ forwarder_block : '{' optnl forwarderopts_l '}'
forwarderopts_l : forwarderopts_l forwarderoptsl optnl
| forwarderoptsl optnl
+ ;
forwarderoptsl : STRING {
struct sockaddr_storage *ss;
@@ -346,14 +396,21 @@ lookup(char *s)
{
/* This has to be sorted always. */
static const struct keywords keywords[] = {
- {"dot", DOT},
{"DoT", DOT},
+ {"auto", AUTO},
+ {"captive", CAPTIVE},
+ {"dot", DOT},
+ {"expected", EXPECTED},
{"forwarder", FORWARDER},
{"include", INCLUDE},
{"no", NO},
{"port", PORT},
+ {"portal", PORTAL},
+ {"response", RESPONSE},
+ {"status", STATUS},
{"strict", STRICT},
{"tls", DOT},
+ {"url", URL},
{"yes", YES},
};
const struct keywords *p;