summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--usr.sbin/hostatectl/Makefile16
-rw-r--r--usr.sbin/hostatectl/hostatectl.869
-rw-r--r--usr.sbin/hostatectl/hostatectl.c276
-rw-r--r--usr.sbin/hostatectl/parser.c232
-rw-r--r--usr.sbin/hostatectl/parser.h39
-rw-r--r--usr.sbin/hostated/Makefile17
-rw-r--r--usr.sbin/hostated/buffer.c222
-rw-r--r--usr.sbin/hostated/check_http.c144
-rw-r--r--usr.sbin/hostated/check_icmp.c212
-rw-r--r--usr.sbin/hostated/check_tcp.c109
-rw-r--r--usr.sbin/hostated/control.c340
-rw-r--r--usr.sbin/hostated/hce.c318
-rw-r--r--usr.sbin/hostated/hostated.894
-rw-r--r--usr.sbin/hostated/hostated.c377
-rw-r--r--usr.sbin/hostated/hostated.conf.5214
-rw-r--r--usr.sbin/hostated/hostated.h307
-rw-r--r--usr.sbin/hostated/imsg.c181
-rw-r--r--usr.sbin/hostated/log.c159
-rw-r--r--usr.sbin/hostated/parse.y937
-rw-r--r--usr.sbin/hostated/pfe.c497
-rw-r--r--usr.sbin/hostated/pfe_filter.c336
-rw-r--r--usr.sbin/hoststatectl/Makefile16
-rw-r--r--usr.sbin/hoststatectl/hoststatectl.869
-rw-r--r--usr.sbin/hoststatectl/hoststatectl.c276
-rw-r--r--usr.sbin/hoststatectl/parser.c232
-rw-r--r--usr.sbin/hoststatectl/parser.h39
-rw-r--r--usr.sbin/hoststated/Makefile17
-rw-r--r--usr.sbin/hoststated/buffer.c222
-rw-r--r--usr.sbin/hoststated/check_http.c144
-rw-r--r--usr.sbin/hoststated/check_icmp.c212
-rw-r--r--usr.sbin/hoststated/check_tcp.c109
-rw-r--r--usr.sbin/hoststated/control.c340
-rw-r--r--usr.sbin/hoststated/hce.c318
-rw-r--r--usr.sbin/hoststated/hoststated.894
-rw-r--r--usr.sbin/hoststated/hoststated.c377
-rw-r--r--usr.sbin/hoststated/hoststated.conf.5214
-rw-r--r--usr.sbin/hoststated/hoststated.h307
-rw-r--r--usr.sbin/hoststated/imsg.c181
-rw-r--r--usr.sbin/hoststated/log.c159
-rw-r--r--usr.sbin/hoststated/parse.y937
-rw-r--r--usr.sbin/hoststated/pfe.c497
-rw-r--r--usr.sbin/hoststated/pfe_filter.c336
-rw-r--r--usr.sbin/relayctl/Makefile16
-rw-r--r--usr.sbin/relayctl/parser.c232
-rw-r--r--usr.sbin/relayctl/parser.h39
-rw-r--r--usr.sbin/relayctl/relayctl.869
-rw-r--r--usr.sbin/relayctl/relayctl.c276
-rw-r--r--usr.sbin/relayd/Makefile17
-rw-r--r--usr.sbin/relayd/buffer.c222
-rw-r--r--usr.sbin/relayd/check_icmp.c212
-rw-r--r--usr.sbin/relayd/check_tcp.c109
-rw-r--r--usr.sbin/relayd/control.c340
-rw-r--r--usr.sbin/relayd/hce.c318
-rw-r--r--usr.sbin/relayd/imsg.c181
-rw-r--r--usr.sbin/relayd/log.c159
-rw-r--r--usr.sbin/relayd/parse.y937
-rw-r--r--usr.sbin/relayd/pfe.c497
-rw-r--r--usr.sbin/relayd/pfe_filter.c336
-rw-r--r--usr.sbin/relayd/relayd.894
-rw-r--r--usr.sbin/relayd/relayd.c377
-rw-r--r--usr.sbin/relayd/relayd.conf.5214
-rw-r--r--usr.sbin/relayd/relayd.h307
62 files changed, 15144 insertions, 0 deletions
diff --git a/usr.sbin/hostatectl/Makefile b/usr.sbin/hostatectl/Makefile
new file mode 100644
index 00000000000..cf9a63acd8b
--- /dev/null
+++ b/usr.sbin/hostatectl/Makefile
@@ -0,0 +1,16 @@
+# $OpenBSD: Makefile,v 1.1 2006/12/16 11:45:07 reyk Exp $
+
+.PATH: ${.CURDIR}/../hostated
+
+PROG= hostatectl
+SRCS= buffer.c imsg.c log.c hostatectl.c parser.c
+
+MAN= hostatectl.8
+
+CFLAGS+= -Wall -Werror -I${.CURDIR} -I${.CURDIR}/../hostated
+CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes
+CFLAGS+= -Wmissing-declarations
+CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual
+CFLAGS+= -Wsign-compare -Wbounded
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/hostatectl/hostatectl.8 b/usr.sbin/hostatectl/hostatectl.8
new file mode 100644
index 00000000000..fec16e42c89
--- /dev/null
+++ b/usr.sbin/hostatectl/hostatectl.8
@@ -0,0 +1,69 @@
+.\" $OpenBSD: hostatectl.8,v 1.1 2006/12/16 11:45:07 reyk Exp $
+.\"
+.\" Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd November 1, 2006
+.Dt HOSTATECTL 8
+.Os
+.Sh NAME
+.Nm hostatectl
+.Nd control the host status daemon
+.Sh SYNOPSIS
+.Nm
+.Ar command
+.Op Ar arguments ...
+.Sh DESCRIPTION
+The
+.Nm
+program controls the
+.Xr hostated 8
+daemon.
+.Pp
+The following commands are available:
+.Bl -tag -width Ds
+.It Cm show
+.It Cm show summary
+Show status of services, tables and hosts.
+.It Cm service disable id
+Disable a service. If it has
+.Xr pf 4
+redirection rules installed, remove them. Mark the service's main table and -
+if applicable - backup table disabled as well.
+.It Cm service enable id
+Enable a service. Mark the service's main table and - if applicable - backup
+table enabled as well.
+.It Cm table disable id
+Disable a table. Consider all hosts disabled. If it is a
+main table of a service which has a non-empty backup table,
+swap the contents of the
+.Xr pf 4
+table with those of the backup table.
+.It Cm table enable id
+Enable a table. Start doing checks for all hosts that aren't
+individually disabled again.
+.It Cm host disable id
+Disable a host. Treat it as though it were always down.
+.It Cm host enable id
+Enable the host. Start checking its health again.
+.El
+.Sh FILES
+.Bl -tag -width "/var/run/hostated.sockXX" -compact
+.It /var/run/hostated.sock
+Unix-domain socket used for communication with
+.Xr hostated 8 .
+.El
+.Sh SEE ALSO
+.Xr hostated.conf 5 ,
+.Xr hostated 8
diff --git a/usr.sbin/hostatectl/hostatectl.c b/usr.sbin/hostatectl/hostatectl.c
new file mode 100644
index 00000000000..2865ca78d32
--- /dev/null
+++ b/usr.sbin/hostatectl/hostatectl.c
@@ -0,0 +1,276 @@
+/* $OpenBSD: hostatectl.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org>
+ * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2003 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/socket.h>
+#include <sys/queue.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <event.h>
+
+#include "hostated.h"
+#include "parser.h"
+
+__dead void usage(void);
+int show_summary_msg(struct imsg *);
+int show_command_output(struct imsg *);
+char *print_service_status(int);
+char *print_host_status(int, int);
+char *print_table_status(int, int);
+
+struct imsgbuf *ibuf;
+
+__dead void
+usage(void)
+{
+ extern char *__progname;
+
+ fprintf(stderr, "usage: %s <command> [arg [...]]\n", __progname);
+ exit(1);
+}
+
+/* dummy function so that ospfctl does not need libevent */
+void
+imsg_event_add(struct imsgbuf *i)
+{
+ /* nothing */
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct sockaddr_un sun;
+ struct parse_result *res;
+ struct imsg imsg;
+ int ctl_sock;
+ int done = 0;
+ int n;
+
+ /* parse options */
+ if ((res = parse(argc - 1, argv + 1)) == NULL)
+ exit(1);
+
+ /* connect to ospfd control socket */
+ if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
+ err(1, "socket");
+
+ bzero(&sun, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ strlcpy(sun.sun_path, HOSTATED_SOCKET, sizeof(sun.sun_path));
+ if (connect(ctl_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1)
+ err(1, "connect: %s", HOSTATED_SOCKET);
+
+ if ((ibuf = malloc(sizeof(struct imsgbuf))) == NULL)
+ err(1, NULL);
+ imsg_init(ibuf, ctl_sock, NULL);
+ done = 0;
+
+ /* process user request */
+ switch (res->action) {
+ case NONE:
+ usage();
+ /* not reached */
+ case SHOW_SUM:
+ imsg_compose(ibuf, IMSG_CTL_SHOW_SUM, 0, 0, NULL, 0);
+ printf("type\t\%4s\t%-16s\tstatus\n\n", "id", "name");
+ break;
+ case SERV_ENABLE:
+ imsg_compose(ibuf, IMSG_CTL_SERVICE_ENABLE, 0, 0,
+ &res->id, sizeof(res->id));
+ break;
+ case SERV_DISABLE:
+ imsg_compose(ibuf, IMSG_CTL_SERVICE_DISABLE, 0, 0,
+ &res->id, sizeof(res->id));
+ break;
+ case TABLE_ENABLE:
+ imsg_compose(ibuf, IMSG_CTL_TABLE_ENABLE, 0, 0,
+ &res->id, sizeof(res->id));
+ break;
+ case TABLE_DISABLE:
+ imsg_compose(ibuf, IMSG_CTL_TABLE_DISABLE, 0, 0,
+ &res->id, sizeof(res->id));
+ break;
+ case HOST_ENABLE:
+ imsg_compose(ibuf, IMSG_CTL_HOST_ENABLE, 0, 0,
+ &res->id, sizeof(res->id));
+ break;
+ case HOST_DISABLE:
+ imsg_compose(ibuf, IMSG_CTL_HOST_DISABLE, 0, 0,
+ &res->id, sizeof(res->id));
+ break;
+ case SHUTDOWN:
+ imsg_compose(ibuf, IMSG_CTL_SHUTDOWN, 0, 0, NULL, 0);
+ break;
+ case RELOAD:
+ imsg_compose(ibuf, IMSG_CTL_RELOAD, 0, 0, NULL, 0);
+ break;
+ }
+
+ while (ibuf->w.queued)
+ if (msgbuf_write(&ibuf->w) < 0)
+ err(1, "write error");
+
+ while (!done) {
+ if ((n = imsg_read(ibuf)) == -1)
+ errx(1, "imsg_read error");
+ if (n == 0)
+ errx(1, "pipe closed");
+
+ while (!done) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ errx(1, "imsg_get error");
+ if (n == 0)
+ break;
+ switch (res->action) {
+ case SHOW_SUM:
+ done = show_summary_msg(&imsg);
+ break;
+ case SERV_DISABLE:
+ case SERV_ENABLE:
+ case TABLE_DISABLE:
+ case TABLE_ENABLE:
+ case HOST_DISABLE:
+ case HOST_ENABLE:
+ done = show_command_output(&imsg);
+ break;
+ case RELOAD:
+ case SHUTDOWN:
+ case NONE:
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ }
+ close(ctl_sock);
+ free(ibuf);
+
+ return (0);
+}
+
+int
+show_summary_msg(struct imsg *imsg)
+{
+ struct service *service;
+ struct table *table;
+ struct host *host;
+
+ switch (imsg->hdr.type) {
+ case IMSG_CTL_SERVICE:
+ service = imsg->data;
+ printf("service\t%4u\t%-16s\t%s\n",
+ service->id, service->name,
+ print_service_status(service->flags));
+ break;
+ case IMSG_CTL_TABLE:
+ table = imsg->data;
+ printf("table\t%4u\t%-16s\t%s",
+ table->id, table->name,
+ print_table_status(table->up, table->flags));
+ printf("\n");
+ break;
+ case IMSG_CTL_HOST:
+ host = imsg->data;
+ printf("host\t%4u\t%-16s\t%s\n",
+ host->id, host->name,
+ print_host_status(host->up, host->flags));
+ break;
+ case IMSG_CTL_END:
+ return (1);
+ default:
+ errx(1, "wrong message in summary: %u", imsg->hdr.type);
+ break;
+ }
+ return (0);
+}
+
+int
+show_command_output(struct imsg *imsg)
+{
+ switch (imsg->hdr.type) {
+ case IMSG_CTL_OK:
+ printf("command succeeded\n");
+ break;
+ case IMSG_CTL_FAIL:
+ printf("command failed\n");
+ break;
+ default:
+ errx(1, "wrong message in summary: %u", imsg->hdr.type);
+ }
+ return (1);
+}
+
+char *
+print_service_status(int flags)
+{
+ if (flags & F_DISABLE) {
+ return("disabled");
+ } else if (flags & F_DOWN) {
+ return("down");
+ } else if (flags & F_BACKUP) {
+ return("active (using backup table)");
+ } else
+ return("active");
+}
+
+char *
+print_table_status(int up, int fl)
+{
+ static char buf[1024];
+
+ bzero(buf, sizeof(buf));
+
+ if (fl & F_DISABLE) {
+ snprintf(buf, sizeof(buf) - 1, "disabled");
+ } else if (!up) {
+ snprintf(buf, sizeof(buf) - 1, "empty");
+ } else
+ snprintf(buf, sizeof(buf) - 1, "active (%d hosts up)",
+ up);
+ return (buf);
+}
+
+char *
+print_host_status(int status, int fl)
+{
+ if (fl & F_DISABLE)
+ return("disabled");
+
+ switch (status) {
+ case HOST_DOWN:
+ return("down");
+ case HOST_UNKNOWN:
+ return("unknown");
+ case HOST_UP:
+ return("up");
+ default:
+ errx(1, "invalid status: %d", status);
+ }
+}
diff --git a/usr.sbin/hostatectl/parser.c b/usr.sbin/hostatectl/parser.c
new file mode 100644
index 00000000000..a2247a907fc
--- /dev/null
+++ b/usr.sbin/hostatectl/parser.c
@@ -0,0 +1,232 @@
+/* $OpenBSD: parser.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.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/socket.h>
+#include <sys/queue.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <event.h>
+
+#include "hostated.h"
+
+#include "parser.h"
+
+enum token_type {
+ NOTOKEN,
+ ENDTOKEN,
+ HOSTID,
+ TABLEID,
+ SERVICEID,
+ KEYWORD
+};
+
+struct token {
+ enum token_type type;
+ const char *keyword;
+ int value;
+ const struct token *next;
+};
+
+static const struct token t_main[];
+static const struct token t_show[];
+static const struct token t_service[];
+static const struct token t_table[];
+static const struct token t_host[];
+static const struct token t_service_id[];
+static const struct token t_table_id[];
+static const struct token t_host_id[];
+
+static const struct token t_main[] = {
+ {KEYWORD, "show", SHOW_SUM, NULL},
+ {KEYWORD, "stop", SHUTDOWN, NULL},
+ {KEYWORD, "service", NULL, t_service},
+ {KEYWORD, "table", NULL, t_table},
+ {KEYWORD, "host", NULL, t_host},
+ {ENDTOKEN, "", NONE, NULL}
+};
+
+static const struct token t_service[] = {
+ {NOTOKEN, "", NONE, NULL},
+ {KEYWORD, "disable", SERV_DISABLE, t_service_id},
+ {KEYWORD, "enable", SERV_ENABLE, t_service_id},
+ {ENDTOKEN, "", NONE, NULL}
+};
+
+static const struct token t_table[] = {
+ {NOTOKEN, "", NONE, NULL},
+ {KEYWORD, "disable", TABLE_DISABLE, t_table_id},
+ {KEYWORD, "enable", TABLE_ENABLE, t_table_id},
+ {ENDTOKEN, "", NONE, NULL}
+};
+
+static const struct token t_host[] = {
+ {NOTOKEN, "", NONE, NULL},
+ {KEYWORD, "disable", HOST_DISABLE, t_host_id},
+ {KEYWORD, "enable", HOST_ENABLE, t_host_id},
+ {ENDTOKEN, "", NONE, NULL}
+};
+
+static const struct token t_service_id[] = {
+ {SERVICEID, "", NONE, NULL},
+ {ENDTOKEN, "", NONE, NULL}
+};
+
+static const struct token t_table_id[] = {
+ {TABLEID, "", NONE, NULL},
+ {ENDTOKEN, "", NONE, NULL}
+};
+
+static const struct token t_host_id[] = {
+ {HOSTID, "", NONE, NULL},
+ {ENDTOKEN, "", NONE, NULL}
+};
+
+static struct parse_result res;
+
+struct parse_result *
+parse(int argc, char *argv[])
+{
+ const struct token *table = t_main;
+ const struct token *match;
+
+ bzero(&res, sizeof(res));
+
+ while (argc > 0) {
+ if ((match = match_token(argv[0], table)) == NULL) {
+ fprintf(stderr, "valid commands/args:\n");
+ show_valid_args(table);
+ return (NULL);
+ }
+
+ argc--;
+ argv++;
+
+ if (match->type == NOTOKEN || match->next == NULL)
+ break;
+
+ table = match->next;
+ }
+
+ if (argc > 0) {
+ fprintf(stderr, "superfluous argument: %s\n", argv[0]);
+ return (NULL);
+ }
+
+ return (&res);
+}
+
+const struct token *
+match_token(const char *word, const struct token table[])
+{
+ u_int i, match;
+ const struct token *t = NULL;
+ const char *errstr;
+
+ match = 0;
+
+ for (i = 0; table[i].type != ENDTOKEN; i++) {
+ switch (table[i].type) {
+ case NOTOKEN:
+ if (word == NULL || strlen(word) == 0) {
+ match++;
+ t = &table[i];
+ }
+ break;
+ case KEYWORD:
+ if (word != NULL && strncmp(word, table[i].keyword,
+ strlen(word)) == 0) {
+ match++;
+ t = &table[i];
+ if (t->value)
+ res.action = t->value;
+ }
+ break;
+ case HOSTID:
+ res.id = strtonum(word, 0, UINT_MAX, &errstr);
+ if (errstr)
+ errx(1, "host id %s is %s", word, errstr);
+ t = &table[i];
+ match++;
+ break;
+ case TABLEID:
+ res.id = strtonum(word, 0, UINT_MAX, &errstr);
+ if (errstr)
+ errx(1, "table id %s is %s", word, errstr);
+ t = &table[i];
+ match++;
+ break;
+ case SERVICEID:
+ res.id = strtonum(word, 0, UINT_MAX, &errstr);
+ if (errstr)
+ errx(1, "service id %s is %s", word, errstr);
+ t = &table[i];
+ match++;
+ break;
+ case ENDTOKEN:
+ break;
+ }
+ }
+
+ if (match != 1) {
+ if (match > 1)
+ fprintf(stderr, "ambiguous argument: %s\n", word);
+ if (match < 1)
+ fprintf(stderr, "unknown argument: %s\n", word);
+ return (NULL);
+ }
+
+ return (t);
+}
+
+void
+show_valid_args(const struct token table[])
+{
+ int i;
+
+ for (i = 0; table[i].type != ENDTOKEN; i++) {
+ switch (table[i].type) {
+ case NOTOKEN:
+ fprintf(stderr, " <cr>\n");
+ break;
+ case KEYWORD:
+ fprintf(stderr, " %s\n", table[i].keyword);
+ break;
+ case SERVICEID:
+ fprintf(stderr, " <serviceid>\n");
+ break;
+ case TABLEID:
+ fprintf(stderr, " <tableid>\n");
+ break;
+ case HOSTID:
+ fprintf(stderr, " <hostid>\n");
+ break;
+ case ENDTOKEN:
+ break;
+ }
+ }
+}
diff --git a/usr.sbin/hostatectl/parser.h b/usr.sbin/hostatectl/parser.h
new file mode 100644
index 00000000000..2fef7464faf
--- /dev/null
+++ b/usr.sbin/hostatectl/parser.h
@@ -0,0 +1,39 @@
+/* $OpenBSD: parser.h,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.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 actions {
+ NONE,
+ SHOW_SUM,
+ SERV_DISABLE,
+ SERV_ENABLE,
+ TABLE_DISABLE,
+ TABLE_ENABLE,
+ HOST_DISABLE,
+ HOST_ENABLE,
+ SHUTDOWN,
+ RELOAD
+};
+
+struct parse_result {
+ objid_t id;
+ enum actions action;
+};
+
+struct parse_result *parse(int, char *[]);
+const struct token *match_token(const char *, const struct token []);
+void show_valid_args(const struct token []);
diff --git a/usr.sbin/hostated/Makefile b/usr.sbin/hostated/Makefile
new file mode 100644
index 00000000000..c04e70f3036
--- /dev/null
+++ b/usr.sbin/hostated/Makefile
@@ -0,0 +1,17 @@
+# $OpenBSD: Makefile,v 1.1 2006/12/16 11:45:07 reyk Exp $
+
+PROG= hostated
+SRCS= parse.y log.c control.c buffer.c imsg.c hostated.c \
+ pfe.c pfe_filter.c hce.c check_icmp.c check_tcp.c check_http.c
+MAN= hostated.8 hostated.conf.5
+
+LDADD= -levent
+DPADD= ${LIBEVENT}
+CFLAGS+= -Wall -Werror -I${.CURDIR}
+CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes
+CFLAGS+= -Wmissing-declarations
+CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual
+CFLAGS+= -Wsign-compare -Wbounded
+CLEANFILES+= y.tab.h
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/hostated/buffer.c b/usr.sbin/hostated/buffer.c
new file mode 100644
index 00000000000..3abe27b1e62
--- /dev/null
+++ b/usr.sbin/hostated/buffer.c
@@ -0,0 +1,222 @@
+/* $OpenBSD: buffer.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * 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/uio.h>
+#include <sys/param.h>
+#include <net/if.h>
+#include <errno.h>
+#include <event.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "hostated.h"
+
+int buf_realloc(struct buf *, size_t);
+void buf_enqueue(struct msgbuf *, struct buf *);
+void buf_dequeue(struct msgbuf *, struct buf *);
+
+struct buf *
+buf_open(size_t len)
+{
+ struct buf *buf;
+
+ if ((buf = calloc(1, sizeof(struct buf))) == NULL)
+ return (NULL);
+ if ((buf->buf = malloc(len)) == NULL) {
+ free(buf);
+ return (NULL);
+ }
+ buf->size = buf->max = len;
+
+ return (buf);
+}
+
+struct buf *
+buf_dynamic(size_t len, size_t max)
+{
+ struct buf *buf;
+
+ if (max < len)
+ return (NULL);
+
+ if ((buf = buf_open(len)) == NULL)
+ return (NULL);
+
+ if (max > 0)
+ buf->max = max;
+
+ return (buf);
+}
+
+int
+buf_realloc(struct buf *buf, size_t len)
+{
+ u_char *b;
+
+ /* on static buffers max is eq size and so the following fails */
+ if (buf->wpos + len > buf->max) {
+ errno = ENOMEM;
+ return (-1);
+ }
+
+ b = realloc(buf->buf, buf->wpos + len);
+ if (b == NULL)
+ return (-1);
+ buf->buf = b;
+ buf->size = buf->wpos + len;
+
+ return (0);
+}
+
+int
+buf_add(struct buf *buf, void *data, size_t len)
+{
+ if (buf->wpos + len > buf->size)
+ if (buf_realloc(buf, len) == -1)
+ return (-1);
+
+ memcpy(buf->buf + buf->wpos, data, len);
+ buf->wpos += len;
+ return (0);
+}
+
+void *
+buf_reserve(struct buf *buf, size_t len)
+{
+ void *b;
+
+ if (buf->wpos + len > buf->size)
+ if (buf_realloc(buf, len) == -1)
+ return (NULL);
+
+ b = buf->buf + buf->wpos;
+ buf->wpos += len;
+ return (b);
+}
+
+void *
+buf_seek(struct buf *buf, size_t pos, size_t len)
+{
+ /* only allowed to seek in already written parts */
+ if (pos + len > buf->wpos)
+ return (NULL);
+
+ return (buf->buf + pos);
+}
+
+int
+buf_close(struct msgbuf *msgbuf, struct buf *buf)
+{
+ buf_enqueue(msgbuf, buf);
+ return (1);
+}
+
+void
+buf_free(struct buf *buf)
+{
+ free(buf->buf);
+ free(buf);
+}
+
+void
+msgbuf_init(struct msgbuf *msgbuf)
+{
+ msgbuf->queued = 0;
+ msgbuf->fd = -1;
+ TAILQ_INIT(&msgbuf->bufs);
+}
+
+void
+msgbuf_clear(struct msgbuf *msgbuf)
+{
+ struct buf *buf;
+
+ while ((buf = TAILQ_FIRST(&msgbuf->bufs)) != NULL)
+ buf_dequeue(msgbuf, buf);
+}
+
+int
+msgbuf_write(struct msgbuf *msgbuf)
+{
+ struct iovec iov[IOV_MAX];
+ struct buf *buf, *next;
+ int i = 0;
+ ssize_t n;
+ struct msghdr msg;
+
+ bzero(&iov, sizeof(iov));
+ bzero(&msg, sizeof(msg));
+ TAILQ_FOREACH(buf, &msgbuf->bufs, entry) {
+ if (i >= IOV_MAX)
+ break;
+ iov[i].iov_base = buf->buf + buf->rpos;
+ iov[i].iov_len = buf->size - buf->rpos;
+ i++;
+ }
+
+ msg.msg_iov = iov;
+ msg.msg_iovlen = i;
+
+ if ((n = sendmsg(msgbuf->fd, &msg, 0)) == -1) {
+ if (errno == EAGAIN || errno == ENOBUFS ||
+ errno == EINTR) /* try later */
+ return (0);
+ else
+ return (-1);
+ }
+
+ if (n == 0) { /* connection closed */
+ errno = 0;
+ return (-2);
+ }
+
+ for (buf = TAILQ_FIRST(&msgbuf->bufs); buf != NULL && n > 0;
+ buf = next) {
+ next = TAILQ_NEXT(buf, entry);
+ if (buf->rpos + n >= buf->size) {
+ n -= buf->size - buf->rpos;
+ buf_dequeue(msgbuf, buf);
+ } else {
+ buf->rpos += n;
+ n = 0;
+ }
+ }
+
+ return (0);
+}
+
+void
+buf_enqueue(struct msgbuf *msgbuf, struct buf *buf)
+{
+ TAILQ_INSERT_TAIL(&msgbuf->bufs, buf, entry);
+ msgbuf->queued++;
+}
+
+void
+buf_dequeue(struct msgbuf *msgbuf, struct buf *buf)
+{
+ TAILQ_REMOVE(&msgbuf->bufs, buf, entry);
+ msgbuf->queued--;
+ buf_free(buf);
+}
diff --git a/usr.sbin/hostated/check_http.c b/usr.sbin/hostated/check_http.c
new file mode 100644
index 00000000000..3d39ceebe27
--- /dev/null
+++ b/usr.sbin/hostated/check_http.c
@@ -0,0 +1,144 @@
+/* $OpenBSD: check_http.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.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/param.h>
+#include <net/if.h>
+#include <sha1.h>
+#include <limits.h>
+#include <event.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "hostated.h"
+
+struct buf *http_request(struct host *, struct table *, int, const char *);
+
+struct buf *
+http_request(struct host *host, struct table *table, int s, const char *req)
+{
+ int fl;
+ ssize_t sz;
+ char rbuf[1024];
+ struct buf *buf;
+
+ if ((fl = fcntl(s, F_GETFL, 0)) == -1)
+ fatal("http_request: cannot get flags for socket");
+ if (fcntl(s, F_SETFL, fl & ~(O_NONBLOCK)) == -1)
+ fatal("http_request: cannot set blocking socket");
+ if ((buf = buf_dynamic(sizeof(rbuf), UINT_MAX)) == NULL)
+ fatalx("http_request: cannot create dynamic buffer");
+
+ if (write(s, req, strlen(req)) != (ssize_t) strlen(req)) {
+ close(s);
+ return (NULL);
+ }
+ for (; (sz = read(s, rbuf, sizeof(rbuf))) != 0; ) {
+ if (sz == -1)
+ fatal("http_request: read");
+ if (buf_add(buf, rbuf, sz) == -1)
+ fatal("http_request: buf_add");
+ }
+ return (buf);
+}
+
+int
+check_http_code(struct host *host, struct table *table)
+{
+ int s;
+ int code;
+ char scode[4];
+ char *req;
+ char *head;
+ const char *estr;
+ struct buf *buf;
+
+ if ((s = tcp_connect(host, table)) <= 0)
+ return (s);
+
+ asprintf(&req, "HEAD %s HTTP/1.0\r\n\r\n", table->path);
+ if ((buf = http_request(host, table, s, req)) == NULL)
+ return (HOST_UNKNOWN);
+ free(req);
+
+ head = buf->buf;
+ if (strncmp(head, "HTTP/1.1 ", strlen("HTTP/1.1 ")) &&
+ strncmp(head, "HTTP/1.0 ", strlen("HTTP/1.0 "))) {
+ log_debug("check_http_code: cannot parse HTTP version");
+ close(s);
+ return (HOST_DOWN);
+ }
+ head += strlen("HTTP/1.1 ");
+ if (strlen(head) < 5) /* code + \r\n */
+ return (HOST_DOWN);
+ strlcpy(scode, head, sizeof(scode));
+ code = strtonum(scode, 100, 999, &estr);
+ if (estr != NULL) {
+ log_debug("check_http_code: cannot parse HTTP code");
+ close(s);
+ return (HOST_DOWN);
+ }
+ if (code != table->retcode) {
+ log_debug("check_http_code: invalid HTTP code returned");
+ close(s);
+ return (HOST_DOWN);
+ }
+ close(s);
+ return (HOST_UP);
+}
+
+int
+check_http_digest(struct host *host, struct table *table)
+{
+ int s;
+ char *head;
+ char *req;
+ struct buf *buf;
+ char digest[(SHA1_DIGEST_LENGTH*2)+1];
+
+ if ((s = tcp_connect(host, table)) <= 0)
+ return (s);
+
+ asprintf(&req, "GET %s HTTP/1.0\r\n\r\n", table->path);
+ if ((buf = http_request(host, table, s, req)) == NULL)
+ return (HOST_UNKNOWN);
+ free(req);
+
+ head = buf->buf;
+ if ((head = strstr(head, "\r\n\r\n")) == NULL) {
+ log_debug("check_http_digest: host %u no end of headers",
+ host->id);
+ close(s);
+ return (HOST_DOWN);
+ }
+ head += strlen("\r\n\r\n");
+ SHA1Data(head, strlen(head), digest);
+ close(s);
+ buf_free(buf);
+
+ if (strcmp(table->digest, digest)) {
+ log_warnx("check_http_digest: wrong digest for host %u",
+ host->id);
+ return(HOST_DOWN);
+ }
+ return (HOST_UP);
+}
diff --git a/usr.sbin/hostated/check_icmp.c b/usr.sbin/hostated/check_icmp.c
new file mode 100644
index 00000000000..df92b444af9
--- /dev/null
+++ b/usr.sbin/hostated/check_icmp.c
@@ -0,0 +1,212 @@
+/* $OpenBSD: check_icmp.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.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/param.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/icmp6.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+#include <limits.h>
+#include <event.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "hostated.h"
+
+int check_icmp6(struct host *, int, int);
+int check_icmp4(struct host *, int, int);
+int in_cksum(u_short *, int);
+
+int check_icmp(struct host *host, int s, int s6, int timeout)
+{
+ if (host->ss.ss_family == AF_INET)
+ return (check_icmp4(host, s, timeout));
+ else
+ return (check_icmp6(host, s6, timeout));
+}
+
+int check_icmp6(struct host *host, int s, int timeout)
+{
+ struct sockaddr *to;
+ struct icmp6_hdr *icp;
+ int ident;
+ ssize_t i;
+ int cc;
+ int datalen = (64 - 8);
+ u_char packet[datalen];
+ fd_set fdset;
+ socklen_t len;
+ struct timeval tv;
+
+ to = (struct sockaddr *)&host->ss;
+ ident = getpid() & 0xFFFF;
+ len = sizeof(struct sockaddr_in6);
+
+ bzero(&packet, sizeof(packet));
+ icp = (struct icmp6_hdr *)packet;
+ icp->icmp6_type = ICMP6_ECHO_REQUEST;
+ icp->icmp6_code = 0;
+ icp->icmp6_seq = 1;
+ icp->icmp6_id = ident;
+
+ memset((packet + sizeof(*icp)), 'X', datalen);
+ cc = datalen + 8;
+
+ i = sendto(s, packet, cc, 0, to, len);
+
+ if (i < 0 || i != cc) {
+ log_warn("check_icmp6: cannot send ping");
+ return (HOST_UNKNOWN);
+ }
+
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = timeout % 1000;
+ FD_ZERO(&fdset);
+ FD_SET(s, &fdset);
+ switch (select(s + 1, &fdset, NULL, NULL, &tv)) {
+ case -1:
+ if (errno == EINTR) {
+ log_warnx("check_icmp6: interrupted");
+ return (HOST_UNKNOWN);
+ } else
+ fatal("check_icmp6: select");
+ case 0:
+ log_debug("check_icmp6: timeout");
+ return (HOST_DOWN);
+ default:
+ bzero(&packet, sizeof(packet));
+ i = recvfrom(s, packet, cc, 0, to, &len);
+ if (i < 0 || i != cc) {
+ log_warn("check_icmp6: did not receive valid ping");
+ return (HOST_DOWN);
+ }
+ icp = (struct icmp6_hdr *)(packet);
+ if (icp->icmp6_id != ident) {
+ log_warnx("check_icmp6: did not receive valid ident");
+ return (HOST_DOWN);
+ }
+ break;
+ }
+ return (HOST_UP);
+}
+
+int check_icmp4(struct host *host, int s, int timeout)
+{
+ struct sockaddr *to;
+ struct icmp *icp;
+ int ident;
+ ssize_t i;
+ int cc;
+ int datalen = (64 - 8);
+ u_char packet[datalen];
+ fd_set fdset;
+ socklen_t len;
+ struct timeval tv;
+
+ to = (struct sockaddr *)&host->ss;
+ ident = getpid() & 0xFFFF;
+ len = sizeof(struct sockaddr_in);
+
+ bzero(&packet, sizeof(packet));
+ icp = (struct icmp *)packet;
+ icp->icmp_type = htons(ICMP_ECHO);
+ icp->icmp_code = 0;
+ icp->icmp_seq = htons(1);
+ icp->icmp_id = htons(ident);
+ icp->icmp_cksum = 0;
+
+ memset(icp->icmp_data, 'X', datalen);
+ cc = datalen + 8;
+ icp->icmp_cksum = in_cksum((u_short *)icp, cc);
+
+ i = sendto(s, packet, cc, 0, to, len);
+
+ if (i < 0 || i != cc) {
+ log_warn("check_icmp4: cannot send ping");
+ return (HOST_UNKNOWN);
+ }
+
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = timeout % 1000;
+ FD_ZERO(&fdset);
+ FD_SET(s, &fdset);
+ switch (select(s + 1, &fdset, NULL, NULL, &tv)) {
+ case -1:
+ if (errno == EINTR) {
+ log_warnx("check_icmp4: ping interrupted");
+ return (HOST_UNKNOWN);
+ } else
+ fatal("check_icmp4: select");
+ case 0:
+ log_debug("check_icmp4: timeout");
+ return (HOST_DOWN);
+ default:
+ bzero(&packet, sizeof(packet));
+ i = recvfrom(s, packet, cc, 0, to, &len);
+ if (i < 0 || i != cc) {
+ log_warn("check_icmp4: did not receive valid ping");
+ return (HOST_DOWN);
+ }
+ icp = (struct icmp *)(packet + sizeof(struct ip));
+ if (ntohs(icp->icmp_id) != ident) {
+ log_warnx("check_icmp4: did not receive valid ident");
+ return (HOST_DOWN);
+ }
+ break;
+ }
+ return (HOST_UP);
+}
+
+/* from ping.c */
+int
+in_cksum(u_short *addr, int len)
+{
+ int nleft = len;
+ u_short *w = addr;
+ int sum = 0;
+ u_short answer = 0;
+
+ /*
+ * Our algorithm is simple, using a 32 bit accumulator (sum), we add
+ * sequential 16 bit words to it, and at the end, fold back all the
+ * carry bits from the top 16 bits into the lower 16 bits.
+ */
+ while (nleft > 1) {
+ sum += *w++;
+ nleft -= 2;
+ }
+
+ /* mop up an odd byte, if necessary */
+ if (nleft == 1) {
+ *(u_char *)(&answer) = *(u_char *)w ;
+ sum += answer;
+ }
+
+ /* add back carry outs from top 16 bits to low 16 bits */
+ sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
+ sum += (sum >> 16); /* add carry */
+ answer = ~sum; /* truncate to 16 bits */
+ return(answer);
+}
diff --git a/usr.sbin/hostated/check_tcp.c b/usr.sbin/hostated/check_tcp.c
new file mode 100644
index 00000000000..5ef386731f9
--- /dev/null
+++ b/usr.sbin/hostated/check_tcp.c
@@ -0,0 +1,109 @@
+/* $OpenBSD: check_tcp.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.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/param.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <limits.h>
+#include <event.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include "hostated.h"
+
+int
+check_tcp(struct host *host, struct table *table)
+{
+ int sock;
+
+ if ((sock = tcp_connect(host, table)) <= 0)
+ return (sock);
+ close(sock);
+ return (HOST_UP);
+}
+
+int
+tcp_connect(struct host *host, struct table *table)
+{
+ int s;
+ socklen_t len;
+ struct timeval tv;
+ struct sockaddr sa;
+ fd_set fdset;
+
+ switch (host->ss.ss_family) {
+ case AF_INET:
+ ((struct sockaddr_in *)&host->ss)->sin_port =
+ htons(table->port);
+ break;
+ case AF_INET6:
+ ((struct sockaddr_in6 *)&host->ss)->sin6_port =
+ htons(table->port);
+ break;
+ }
+
+ len = ((struct sockaddr *)&host->ss)->sa_len;
+
+ if ((s = socket(host->ss.ss_family, SOCK_STREAM, 0)) == -1)
+ fatal("check_tcp: cannot create socket");
+
+ if (fcntl(s, F_SETFL, O_NONBLOCK) == -1)
+ fatal("check_tcp: cannot set non blocking socket");
+
+ if (connect(s, (struct sockaddr *)&host->ss, len) == -1) {
+ if (errno != EINPROGRESS && errno != EWOULDBLOCK) {
+ close(s);
+ return (HOST_DOWN);
+ }
+ } else
+ return (s);
+
+ tv.tv_sec = table->timeout / 1000;
+ tv.tv_usec = table->timeout % 1000;
+ FD_ZERO(&fdset);
+ FD_SET(s, &fdset);
+
+ switch(select(s + 1, NULL, &fdset, NULL, &tv)) {
+ case -1:
+ if (errno != EINTR)
+ fatal("check_tcp: select");
+ else
+ return(HOST_UNKNOWN);
+ case 0:
+ close(s);
+ return (HOST_DOWN);
+ default:
+ if (getpeername(s, &sa, &len) == -1) {
+ if (errno == ENOTCONN) {
+ close(s);
+ return (HOST_DOWN);
+ } else {
+ log_debug("check_tcp: unknown peername");
+ close(s);
+ return (HOST_UNKNOWN);
+ }
+ } else
+ return (s);
+ }
+ return (HOST_UNKNOWN);
+}
diff --git a/usr.sbin/hostated/control.c b/usr.sbin/hostated/control.c
new file mode 100644
index 00000000000..2f994e458f4
--- /dev/null
+++ b/usr.sbin/hostated/control.c
@@ -0,0 +1,340 @@
+/* $OpenBSD: control.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * 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/queue.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <net/if.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include "hostated.h"
+
+#define CONTROL_BACKLOG 5
+
+struct ctl_connlist ctl_conns;
+
+int control_imsg_relay(struct imsg *imsg);
+
+struct ctl_conn *control_connbyfd(int);
+struct ctl_conn *control_connbypid(pid_t);
+void control_close(int);
+
+int
+control_init(void)
+{
+ struct sockaddr_un sun;
+ int fd;
+ mode_t old_umask;
+
+ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+ log_warn("control_init: socket");
+ return (-1);
+ }
+
+ bzero(&sun, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ strlcpy(sun.sun_path, HOSTATED_SOCKET, sizeof(sun.sun_path));
+
+ if (unlink(HOSTATED_SOCKET) == -1)
+ if (errno != ENOENT) {
+ log_warn("control_init: unlink %s", HOSTATED_SOCKET);
+ close(fd);
+ return (-1);
+ }
+
+ old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
+ if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
+ log_warn("control_init: bind: %s", HOSTATED_SOCKET);
+ close(fd);
+ umask(old_umask);
+ return (-1);
+ }
+ umask(old_umask);
+
+ if (chmod(HOSTATED_SOCKET, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) {
+ log_warn("control_init: chmod");
+ close(fd);
+ (void)unlink(HOSTATED_SOCKET);
+ return (-1);
+ }
+
+ session_socket_blockmode(fd, BM_NONBLOCK);
+ control_state.fd = fd;
+
+ return (0);
+}
+
+int
+control_listen(void)
+{
+
+ if (listen(control_state.fd, CONTROL_BACKLOG) == -1) {
+ log_warn("control_listen: listen");
+ return (-1);
+ }
+
+ event_set(&control_state.ev, control_state.fd, EV_READ | EV_PERSIST,
+ control_accept, NULL);
+ event_add(&control_state.ev, NULL);
+
+ return (0);
+}
+
+void
+control_cleanup(void)
+{
+
+ unlink(HOSTATED_SOCKET);
+}
+
+/* ARGSUSED */
+void
+control_accept(int listenfd, short event, void *arg)
+{
+ int connfd;
+ socklen_t len;
+ struct sockaddr_un sun;
+ struct ctl_conn *c;
+
+ len = sizeof(sun);
+ if ((connfd = accept(listenfd,
+ (struct sockaddr *)&sun, &len)) == -1) {
+ if (errno != EWOULDBLOCK && errno != EINTR)
+ log_warn("control_accept");
+ return;
+ }
+
+ session_socket_blockmode(connfd, BM_NONBLOCK);
+
+ if ((c = malloc(sizeof(struct ctl_conn))) == NULL) {
+ log_warn("control_accept");
+ return;
+ }
+
+ imsg_init(&c->ibuf, connfd, control_dispatch_imsg);
+ c->ibuf.events = EV_READ;
+ event_set(&c->ibuf.ev, c->ibuf.fd, c->ibuf.events,
+ c->ibuf.handler, &c->ibuf);
+ event_add(&c->ibuf.ev, NULL);
+
+ TAILQ_INSERT_TAIL(&ctl_conns, c, entry);
+}
+
+struct ctl_conn *
+control_connbyfd(int fd)
+{
+ struct ctl_conn *c;
+
+ for (c = TAILQ_FIRST(&ctl_conns); c != NULL && c->ibuf.fd != fd;
+ c = TAILQ_NEXT(c, entry))
+ ; /* nothing */
+
+ return (c);
+}
+
+struct ctl_conn *
+control_connbypid(pid_t pid)
+{
+ struct ctl_conn *c;
+
+ for (c = TAILQ_FIRST(&ctl_conns); c != NULL && c->ibuf.pid != pid;
+ c = TAILQ_NEXT(c, entry))
+ ; /* nothing */
+
+ return (c);
+}
+
+void
+control_close(int fd)
+{
+ struct ctl_conn *c;
+
+ if ((c = control_connbyfd(fd)) == NULL)
+ log_warn("control_close: fd %d: not found", fd);
+
+ msgbuf_clear(&c->ibuf.w);
+ TAILQ_REMOVE(&ctl_conns, c, entry);
+
+ event_del(&c->ibuf.ev);
+ close(c->ibuf.fd);
+ free(c);
+}
+
+/* ARGSUSED */
+void
+control_dispatch_imsg(int fd, short event, void *arg)
+{
+ struct ctl_conn *c;
+ struct imsg imsg;
+ objid_t id;
+ int n;
+
+ if ((c = control_connbyfd(fd)) == NULL) {
+ log_warn("control_dispatch_imsg: fd %d: not found", fd);
+ return;
+ }
+
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(&c->ibuf)) <= 0) {
+ control_close(fd);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&c->ibuf.w) < 0) {
+ control_close(fd);
+ return;
+ }
+ imsg_event_add(&c->ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(&c->ibuf, &imsg)) == -1) {
+ control_close(fd);
+ return;
+ }
+
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_CTL_SHOW_SUM:
+ show(c);
+ break;
+ case IMSG_CTL_SERVICE_DISABLE:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id))
+ fatalx("invalid imsg header len");
+ memcpy(&id, imsg.data, sizeof(id));
+ if (disable_service(c, id))
+ imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0,
+ NULL, 0);
+ else
+ imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0,
+ NULL, 0);
+ break;
+ case IMSG_CTL_SERVICE_ENABLE:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id))
+ fatalx("invalid imsg header len");
+ memcpy(&id, imsg.data, sizeof(id));
+ if (enable_service(c, id))
+ imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0,
+ NULL, 0);
+ else
+ imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0,
+ NULL, 0);
+ break;
+ case IMSG_CTL_TABLE_DISABLE:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id))
+ fatalx("invalid imsg header len");
+ memcpy(&id, imsg.data, sizeof(id));
+ if (disable_table(c, id))
+ imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0,
+ NULL, 0);
+ else
+ imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0,
+ NULL, 0);
+ break;
+ case IMSG_CTL_TABLE_ENABLE:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id))
+ fatalx("invalid imsg header len");
+ memcpy(&id, imsg.data, sizeof(id));
+ if (enable_table(c, id))
+ imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0,
+ NULL, 0);
+ else
+ imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0,
+ NULL, 0);
+ break;
+ case IMSG_CTL_HOST_DISABLE:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id))
+ fatalx("invalid imsg header len");
+ memcpy(&id, imsg.data, sizeof(id));
+ if (disable_host(c, id))
+ imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0,
+ NULL, 0);
+ else
+ imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0,
+ NULL, 0);
+ break;
+ case IMSG_CTL_HOST_ENABLE:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id))
+ fatalx("invalid imsg header len");
+ memcpy(&id, imsg.data, sizeof(id));
+ if (enable_host(c, id))
+ imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0,
+ NULL, 0);
+ else
+ imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0,
+ NULL, 0);
+ break;
+ case IMSG_CTL_SHUTDOWN:
+ case IMSG_CTL_RELOAD:
+ imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0, NULL, 0);
+ break;
+ default:
+ log_debug("control_dispatch_imsg: "
+ "error handling imsg %d", imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+
+ imsg_event_add(&c->ibuf);
+}
+
+int
+control_imsg_relay(struct imsg *imsg)
+{
+ struct ctl_conn *c;
+
+ if ((c = control_connbypid(imsg->hdr.pid)) == NULL)
+ return (0);
+
+ return (imsg_compose(&c->ibuf, imsg->hdr.type, 0, imsg->hdr.pid,
+ imsg->data, imsg->hdr.len - IMSG_HEADER_SIZE));
+}
+
+void
+session_socket_blockmode(int fd, enum blockmodes bm)
+{
+ int flags;
+
+ if ((flags = fcntl(fd, F_GETFL, 0)) == -1)
+ fatal("fnctl F_GETFL");
+
+ if (bm == BM_NONBLOCK)
+ flags |= O_NONBLOCK;
+ else
+ flags &= ~O_NONBLOCK;
+
+ if ((flags = fcntl(fd, F_SETFL, flags)) == -1)
+ fatal("fnctl F_SETFL");
+}
diff --git a/usr.sbin/hostated/hce.c b/usr.sbin/hostated/hce.c
new file mode 100644
index 00000000000..7144357538b
--- /dev/null
+++ b/usr.sbin/hostated/hce.c
@@ -0,0 +1,318 @@
+/* $OpenBSD: hce.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/queue.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <net/if.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <err.h>
+#include <pwd.h>
+
+#include "hostated.h"
+
+void hce_sig_handler(int sig, short, void *);
+void hce_shutdown(void);
+void hce_dispatch_imsg(int, short, void *);
+void hce_dispatch_parent(int, short, void *);
+void hce_launch_checks(int, short, void *);
+
+static struct hostated *env = NULL;
+struct imsgbuf *ibuf_pfe;
+struct imsgbuf *ibuf_main;
+
+void
+hce_sig_handler(int sig, short event, void *arg)
+{
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ hce_shutdown();
+ default:
+ fatalx("hce_sig_handler: unexpected signal");
+ }
+}
+
+pid_t
+hce(struct hostated *x_env, int pipe_parent2pfe[2], int pipe_parent2hce[2],
+ int pipe_pfe2hce[2])
+{
+ pid_t pid;
+ struct passwd *pw;
+ struct timeval tv;
+ struct event ev_sigint;
+ struct event ev_sigterm;
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("hce: cannot fork");
+ case 0:
+ break;
+ default:
+ return (pid);
+ }
+
+ env = x_env;
+
+ /* this is needed for icmp tests */
+ if ((env->icmp_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0)
+ err(1, "socket");
+ if ((env->icmp6_sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0)
+ err(1, "socket");
+
+ if ((pw = getpwnam(HOSTATED_USER)) == NULL)
+ fatal("hce: getpwnam");
+
+ if (chroot(pw->pw_dir) == -1)
+ fatal("hce: chroot");
+ if (chdir("/") == -1)
+ fatal("hce: chdir(\"/\")");
+
+ setproctitle("host check engine");
+ hostated_process = PROC_HCE;
+
+ 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("hce: can't drop privileges");
+
+ event_init();
+
+ signal_set(&ev_sigint, SIGINT, hce_sig_handler, NULL);
+ signal_set(&ev_sigterm, SIGTERM, hce_sig_handler, NULL);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+
+ /* setup pipes */
+ close(pipe_pfe2hce[1]);
+ close(pipe_parent2hce[0]);
+ close(pipe_parent2pfe[0]);
+ close(pipe_parent2pfe[1]);
+
+ if ((ibuf_pfe = calloc(1, sizeof(struct imsgbuf))) == NULL ||
+ (ibuf_main = calloc(1, sizeof(struct imsgbuf))) == NULL)
+ fatal("hce");
+ imsg_init(ibuf_pfe, pipe_pfe2hce[0], hce_dispatch_imsg);
+ imsg_init(ibuf_main, pipe_parent2hce[1], hce_dispatch_parent);
+
+ ibuf_pfe->events = EV_READ;
+ event_set(&ibuf_pfe->ev, ibuf_pfe->fd, ibuf_pfe->events,
+ ibuf_pfe->handler, ibuf_pfe);
+ event_add(&ibuf_pfe->ev, NULL);
+
+ ibuf_main->events = EV_READ;
+ event_set(&ibuf_main->ev, ibuf_main->fd, ibuf_main->events,
+ ibuf_main->handler, ibuf_main);
+ event_add(&ibuf_main->ev, NULL);
+
+ evtimer_set(&env->ev, hce_launch_checks, NULL);
+ tv.tv_sec = env->interval;
+ tv.tv_usec = 0;
+ evtimer_add(&env->ev, &tv);
+
+ hce_launch_checks(0, 0, NULL);
+ event_dispatch();
+
+ hce_shutdown();
+
+ return (0);
+}
+
+void
+hce_launch_checks(int fd, short event, void *arg)
+{
+ int previous_up;
+ struct host *host;
+ struct table *table;
+ struct ctl_status st;
+ struct timeval tv;
+
+ tv.tv_sec = env->interval;
+ tv.tv_usec = 0;
+ evtimer_add(&env->ev, &tv);
+ bzero(&st, sizeof(st));
+ TAILQ_FOREACH(table, &env->tables, entry) {
+ if (table->flags & F_DISABLE)
+ continue;
+ TAILQ_FOREACH(host, &table->hosts, entry) {
+ if (host->flags & F_DISABLE)
+ continue;
+ previous_up = host->up;
+ switch (table->check) {
+ case CHECK_ICMP:
+ host->up = check_icmp(host, env->icmp_sock,
+ env->icmp6_sock,
+ table->timeout);
+ break;
+ case CHECK_TCP:
+ host->up = check_tcp(host, table);
+ break;
+ case CHECK_HTTP_CODE:
+ host->up = check_http_code(host, table);
+ break;
+ case CHECK_HTTP_DIGEST:
+ host->up = check_http_digest(host, table);
+ break;
+ default:
+ fatalx("hce_launch_checks: unknown check type");
+ break;
+ }
+ if (host->up != previous_up) {
+ st.id = host->id;
+ st.up = host->up;
+ imsg_compose(ibuf_pfe, IMSG_HOST_STATUS, 0, 0,
+ &st, sizeof(st));
+ }
+ }
+ }
+ /* tell pfe we're finished */
+ imsg_compose(ibuf_pfe, IMSG_SYNC, 0, 0, NULL, 0);
+}
+
+void
+hce_shutdown(void)
+{
+ log_info("host check engine exiting");
+ _exit(0);
+}
+
+void
+hce_dispatch_imsg(int fd, short event, void *ptr)
+{
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+ objid_t id;
+ struct host *host;
+ struct table *table;
+
+ ibuf = ptr;
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("hce_dispatch_imsg: imsg_read_error");
+ if (n == 0)
+ fatalx("hce_dispatch_imsg: pipe closed");
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("hce_dispatch_imsg: msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("hce_dispatch_imsg: unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("hce_dispatch_imsg: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_HOST_DISABLE:
+ memcpy(&id, imsg.data, sizeof(id));
+ if ((host = host_find(env, id)) == NULL)
+ fatalx("hce_dispatch_imsg: desynchronized");
+ host->flags |= F_DISABLE;
+ host->up = HOST_UNKNOWN;
+ break;
+ case IMSG_HOST_ENABLE:
+ memcpy(&id, imsg.data, sizeof(id));
+ if ((host = host_find(env, id)) == NULL)
+ fatalx("hce_dispatch_imsg: desynchronized");
+ host->flags &= ~(F_DISABLE);
+ host->up = HOST_UNKNOWN;
+ break;
+ case IMSG_TABLE_DISABLE:
+ memcpy(&id, imsg.data, sizeof(id));
+ if ((table = table_find(env, id)) == NULL)
+ fatalx("hce_dispatch_imsg: desynchronized");
+ table->flags |= F_DISABLE;
+ TAILQ_FOREACH(host, &table->hosts, entry)
+ host->up = HOST_UNKNOWN;
+ break;
+ case IMSG_TABLE_ENABLE:
+ memcpy(&id, imsg.data, sizeof(id));
+ if ((table = table_find(env, id)) == NULL)
+ fatalx("hce_dispatch_imsg: desynchronized");
+ table->flags &= ~(F_DISABLE);
+ TAILQ_FOREACH(host, &table->hosts, entry)
+ host->up = HOST_UNKNOWN;
+ break;
+ default:
+ log_debug("hce_dispatch_msg: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+hce_dispatch_parent(int fd, short event, void * ptr)
+{
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = ptr;
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("hce_dispatch_parent: imsg_read error");
+ if (n == 0) /* connection closed */
+ fatalx("hce_dispatch_parent: pipe closed");
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("hce_dispatch_parent: msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("hce_dispatch_parent: unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("hce_dispatch_parent: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ log_debug("hce_dispatch_parent: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+}
diff --git a/usr.sbin/hostated/hostated.8 b/usr.sbin/hostated/hostated.8
new file mode 100644
index 00000000000..1a74ea55a63
--- /dev/null
+++ b/usr.sbin/hostated/hostated.8
@@ -0,0 +1,94 @@
+.\" $OpenBSD: hostated.8,v 1.1 2006/12/16 11:45:07 reyk Exp $
+.\"
+.\" Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd November 1, 2006
+.Dt HOSTATED 8
+.Os
+.Sh NAME
+.Nm hostated
+.Nd Host Status daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl dnv
+.Op Fl f Ar file
+.Sh DESCRIPTION
+.Nm
+is the host status daemon for server load balancing.
+Its main purpose is to maintain pf tables up to date
+as well as related pf rdr rules.
+To communicate with
+.Xr pf 4
+.Nm
+uses the anchor facility. To enable
+.Nm
+to install rulesets through the anchor you will
+need the following line in the NAT section of your
+.Xr pf.conf 5
+configuration file:
+.Bd -literal -offset 2n
+rdr-anchor "hostated/*"
+.Ed
+.Pp
+.Nm
+manipulates three data types: services, tables and hosts.
+Each service represents a
+.Xr pf 4
+rdr rule. A service contains at least one table and one virtual ip which
+are used to create the proper rule.
+Each table contains at least one host, and is mapped to a
+.Xr pf 4
+table. Additionnaly, a table can be backed up i.e its content will be swapped
+by the content of another table when it is empty. This can be used to serve
+static content when a dynamic service goes down.
+See
+.Xr hostated.conf 5
+for a more detailed explanation of how to configure
+.Nm
+.
+.Pp
+.Xr hostatectl 8
+can be used to enable or disable hosts, tables and services as well
+as showing the current status of each object.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl d
+Do not daemonize.
+If this options is specified,
+.Nm
+will run in the foreground and log to
+.Em stderr .
+.It Fl f Ar file
+Specify an alternative configurate file.
+.It Fl n
+Configtest mode.
+Only check the configuration file for validity.
+.It Fl v
+Produce more verbose output.
+.El
+.Sh FILES
+.Bl -tag -width "/var/run/hostated.sockXX" -compact
+.It /etc/hostated.conf
+Default
+.Nm
+configuration file.
+.It /var/run/hostated.sock
+Unix-domain socket used for communication with
+.Xr hostatectl 8 .
+.El
+.Sh SEE ALSO
+.Xr hostated.conf 5 ,
+.Xr hostatectl 8
diff --git a/usr.sbin/hostated/hostated.c b/usr.sbin/hostated/hostated.c
new file mode 100644
index 00000000000..32c0989c4cc
--- /dev/null
+++ b/usr.sbin/hostated/hostated.c
@@ -0,0 +1,377 @@
+/* $OpenBSD: hostated.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.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/param.h>
+#include <sys/wait.h>
+#include <net/if.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <signal.h>
+#include <unistd.h>
+#include <pwd.h>
+
+#include "hostated.h"
+
+__dead void usage(void);
+
+void main_sig_handler(int, short, void *);
+void main_shutdown(void);
+void main_dispatch_pfe(int, short, void *);
+void main_dispatch_hce(int, short, void *);
+int check_child(pid_t, const char *);
+
+int pipe_parent2pfe[2];
+int pipe_parent2hce[2];
+int pipe_pfe2hce[2];
+
+struct imsgbuf *ibuf_pfe;
+struct imsgbuf *ibuf_hce;
+
+pid_t pfe_pid = 0;
+pid_t hce_pid = 0;
+
+void
+main_sig_handler(int sig, short event, void *arg)
+{
+ int die = 0;
+
+ switch (sig) {
+ case SIGTERM:
+ case SIGINT:
+ die = 1;
+ case SIGCHLD:
+ if (check_child(pfe_pid, "pf udpate engine")) {
+ pfe_pid = 0;
+ die = 1;
+ }
+ if (check_child(hce_pid, "host check engine")) {
+ hce_pid = 0;
+ die = 1;
+ }
+ if (die)
+ main_shutdown();
+ break;
+ case SIGHUP:
+ /* reconfigure */
+ break;
+ default:
+ fatalx("unexpected signal");
+ }
+}
+
+/* __dead is for lint */
+__dead void
+usage(void)
+{
+ extern char *__progname;
+
+ fprintf(stderr, "%s [-dnv] [-f file]\n", __progname);
+ exit (1);
+}
+
+int main(int argc, char *argv[])
+{
+ int c;
+ int debug;
+ u_int32_t opts;
+ struct hostated env;
+ const char *conffile;
+ struct event ev_sigint;
+ struct event ev_sigterm;
+ struct event ev_sigchld;
+ struct event ev_sighup;
+
+ opts = 0;
+ debug = 0;
+ conffile = CONF_FILE;
+ bzero(&env, sizeof (env));
+
+ for (;(c = getopt(argc, argv, "dnf:v")) != -1;) {
+ switch (c) {
+ case 'd':
+ debug = 1;
+ break;
+ case 'n':
+ opts |= HOSTATED_OPT_NOACTION;
+ break;
+ case 'f':
+ conffile = optarg;
+ break;
+ case 'v':
+ opts |= HOSTATED_OPT_VERBOSE;
+ break;
+ default:
+ usage();
+ }
+
+ }
+
+ log_init(debug);
+
+ if (parse_config(&env, conffile, opts))
+ exit(1);
+
+ if (env.opts & HOSTATED_OPT_NOACTION) {
+ fprintf(stderr, "configuration OK\n");
+ exit(0);
+ }
+
+ if (geteuid())
+ errx(1, "need root privileges");
+
+ if (getpwnam(HOSTATED_USER) == NULL)
+ errx(1, "unknown user %s", HOSTATED_USER);
+
+ if (!debug)
+ daemon(1, 0);
+
+ log_info("startup");
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_parent2pfe) == -1)
+ fatal("socketpair");
+ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_parent2hce) == -1)
+ fatal("socketpair");
+ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_pfe2hce) == -1)
+ fatal("socketpair");
+
+ session_socket_blockmode(pipe_parent2pfe[0], BM_NONBLOCK);
+ session_socket_blockmode(pipe_parent2pfe[1], BM_NONBLOCK);
+ session_socket_blockmode(pipe_parent2hce[0], BM_NONBLOCK);
+ session_socket_blockmode(pipe_parent2hce[1], BM_NONBLOCK);
+ session_socket_blockmode(pipe_pfe2hce[0], BM_NONBLOCK);
+ session_socket_blockmode(pipe_pfe2hce[1], BM_NONBLOCK);
+
+ pfe_pid = pfe(&env, pipe_parent2pfe, pipe_parent2hce, pipe_pfe2hce);
+ hce_pid = hce(&env, pipe_parent2pfe, pipe_parent2hce, pipe_pfe2hce);
+
+ setproctitle("parent");
+
+ event_init();
+
+ signal_set(&ev_sigint, SIGINT, main_sig_handler, NULL);
+ signal_set(&ev_sigterm, SIGTERM, main_sig_handler, NULL);
+ signal_set(&ev_sigchld, SIGCHLD, main_sig_handler, NULL);
+ signal_set(&ev_sighup, SIGHUP, main_sig_handler, NULL);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+ signal_add(&ev_sigchld, NULL);
+ signal_add(&ev_sighup, NULL);
+
+ close(pipe_parent2pfe[1]);
+ close(pipe_parent2hce[1]);
+ close(pipe_pfe2hce[0]);
+ close(pipe_pfe2hce[1]);
+
+ if ((ibuf_pfe = calloc(1, sizeof(struct imsgbuf))) == NULL ||
+ (ibuf_hce = calloc(1, sizeof(struct imsgbuf))) == NULL)
+ fatal(NULL);
+
+ imsg_init(ibuf_pfe, pipe_parent2pfe[0], main_dispatch_pfe);
+ imsg_init(ibuf_hce, pipe_parent2hce[0], main_dispatch_hce);
+
+ ibuf_pfe->events = EV_READ;
+ event_set(&ibuf_pfe->ev, ibuf_pfe->fd, ibuf_pfe->events,
+ ibuf_pfe->handler, ibuf_pfe);
+ event_add(&ibuf_pfe->ev, NULL);
+
+ ibuf_hce->events = EV_READ;
+ event_set(&ibuf_hce->ev, ibuf_hce->fd, ibuf_hce->events,
+ ibuf_hce->handler, ibuf_hce);
+ event_add(&ibuf_hce->ev, NULL);
+
+ event_dispatch();
+
+ return (0);
+}
+
+void
+main_shutdown(void)
+{
+ pid_t pid;
+
+ if (pfe_pid)
+ kill(pfe_pid, SIGTERM);
+ if (hce_pid)
+ kill(hce_pid, SIGTERM);
+
+ do {
+ if ((pid = wait(NULL)) == -1 &&
+ errno != EINTR && errno != ECHILD)
+ fatal("wait");
+ } while (pid != -1 || (pid == -1 && errno == EINTR));
+
+ control_cleanup();
+ log_info("terminating");
+ exit(0);
+}
+
+int
+check_child(pid_t pid, const char *pname)
+{
+ int status;
+
+ if (waitpid(pid, &status, WNOHANG) > 0) {
+ if (WIFEXITED(status)) {
+ log_warnx("check_child: lost child: %s exited", pname);
+ return (1);
+ }
+ if (WIFSIGNALED(status)) {
+ log_warnx("check_child: lost child: %s terminated; signal %d",
+ pname, WTERMSIG(status));
+ return (1);
+ }
+ }
+
+ return (0);
+}
+
+void
+imsg_event_add(struct imsgbuf *ibuf)
+{
+ ibuf->events = EV_READ;
+ if (ibuf->w.queued)
+ ibuf->events |= EV_WRITE;
+
+ event_del(&ibuf->ev);
+ event_set(&ibuf->ev, ibuf->fd, ibuf->events, ibuf->handler, ibuf);
+ event_add(&ibuf->ev, NULL);
+}
+
+void
+main_dispatch_pfe(int fd, short event, void *ptr)
+{
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = ptr;
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0)
+ fatalx("parent: pipe closed");
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("main_dispatch_pfe: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ log_debug("main_dispatch_pfe: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+main_dispatch_hce(int fd, short event, void * ptr)
+{
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = ptr;
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read error");
+ if (n == 0) /* connection closed */
+ fatalx("parent: pipe closed");
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("main_dispatch_hce: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ log_debug("main_dispatch_hce: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+}
+
+struct host *
+host_find(struct hostated *env, objid_t id)
+{
+ struct table *table;
+ struct host *host;
+
+ TAILQ_FOREACH(table, &env->tables, entry)
+ TAILQ_FOREACH(host, &table->hosts, entry)
+ if (host->id == id)
+ return (host);
+ return (NULL);
+}
+
+struct table *
+table_find(struct hostated *env, objid_t id)
+{
+ struct table *table;
+
+ TAILQ_FOREACH(table, &env->tables, entry)
+ if (table->id == id)
+ return (table);
+ return (NULL);
+}
+
+struct service *
+service_find(struct hostated *env, objid_t id)
+{
+ struct service *service;
+
+ TAILQ_FOREACH(service, &env->services, entry)
+ if (service->id == id)
+ return (service);
+ return (NULL);
+}
diff --git a/usr.sbin/hostated/hostated.conf.5 b/usr.sbin/hostated/hostated.conf.5
new file mode 100644
index 00000000000..c4f7b76ba0f
--- /dev/null
+++ b/usr.sbin/hostated/hostated.conf.5
@@ -0,0 +1,214 @@
+.\" $OpenBSD: hostated.conf.5,v 1.1 2006/12/16 11:45:07 reyk Exp $
+.\"
+.\" Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd November 1, 2006
+.Dt HOSTATED.CONF 5
+.Os
+.Sh NAME
+.Nm hostated.conf
+.Nd Host Status daemon configuration file.
+.Sh DESCRIPTION
+The
+.Xr hostated 8
+daemon maintains
+.Xr pf 4
+tables up to date.
+.Sh SECTIONS
+The
+.Nm
+configuration file is divided into four main sections.
+.Bl -tag -width xxxx
+.It Sy Macros
+User-defined variables may be defined and used later, simplifying the
+configuration file.
+.It Sy Global Configuration
+Global settings for
+.Xr hostated 8 .
+.It Sy Tables
+Table definitions describe the content of a
+.Xr pf 4
+table and the method used for checking the health of the hosts
+they contain.
+.It Sy Services
+Services will be translated to
+.Xr pf 4
+rdr rules if their table or backup table have content.
+.El
+.Sh MACROS
+Macros can be defined that will later be expanded in context.
+Macro names must start with a letter, and may contain letters, digits,
+and underscores.
+Macro names may not be reserved words (for example,
+.Ic table ,
+.Ic service ,
+or
+.Ic timeout ) .
+Macros are not expanded inside quotes.
+.Pp
+For example:
+.Bd -literal -offset indent
+www1="10.0.0.1"
+www2="10.0.0.2"
+table webhosts {
+ check tcp
+ timeout 300
+ real port 80
+ host $www1
+ host $www2
+}
+.Ed
+.Sh GLOBAL CONFIGURATION
+Only one global setting can be set.
+.Pp
+.Bl -tag -width Ds -compact
+.It Xo
+.Ic interval Ar number
+.Xc
+Set the interval in seconds at which the hosts will be checked.
+The default interval is 10 seconds.
+.El
+.Sh TABLES
+Tables are used to group a set of hosts that can be checked using the same
+method. Only one health-checking method can be used per table.
+Table specific configuration directives are described below.
+.Bl -tag -width Ds
+.It Ic check tcp
+Use a simple tcp connect to check that hosts are up.
+.It Ic check icmp
+Ping hosts in this table to determine wether they are up or not.
+This method will automatically use icmp or icmpv6 depending on the
+address family of each host.
+.It Ic check http Ar path Ic code Ar number
+For each host in the table, verify that retrieving the URL
+.Ar path
+gives the HTTP return code
+.Ar number
+.It Ic check http Ar path Ic digest Ar string
+For each host in the table, verify that retrieving the URL
+.Ar path
+produces a content whose SHA1 digest is
+.Ar digest
+. The digest does not take the HTTP headers into account. To compute the
+digest you can use this simple command:
+.Bd -literal -offset 2n
+ftp -o - http://host[:port]/path | sha1
+
+.Ed
+This will give you a digest of the form
+.Bd -literal -offset 2n
+a9993e36476816aba3e25717850c26c9cd0d89d
+
+.Ed
+that you can use as-is in your digest statement.
+.It Ic timeout Ar number
+Set the timeout in milliseconds for each host that is checked.
+The default timeout is 200 milliseconds.
+.It Ic real port Ar number
+When using the tcp or http checking methods, use this port to connect
+to hosts. This parameter is mandatory. Main and backup tables need
+to have the same real port.
+.It Ic host Ar address
+Add the host whose address is
+.Ar address
+to the list of hosts to be checked in this table.
+Each table needs at least one host.
+.It Ic disable
+Start the table disabled, no hosts will be checked in this table.
+The table can be later enabled through
+.Xr hostatectl 8 .
+.El
+.Sh SERVICES
+Services represent a
+.Xr pf 4
+rdr rule, they are used to specify which addresses will be redirected
+to the hosts in the specified tables.
+The configuration directives that are valid in this context are described
+below.
+.Bl -tag -width Ds
+.It Ic virtual ip Ar address Ic port Ar number
+Specify an address and a port that will be used to redirect requests
+to the hosts in the main or backup table.
+Optionally an interface name can be specified like this
+.Bd -literal -offset indent
+interface ``ifname''
+
+.Ed
+to specify which interface the rdr rule will be enabled on.
+.It Ic table Ar name
+Specify the main table to be used. This is mandatory.
+.It Ic backup table Ar name
+Specify the table to switch to when all hosts in the main table
+are seen as down or disabled.
+.It Ic disable
+Set the service initially disabled. It can be later enabled through
+.It Ic tag Ar name
+Automatically tag packets passing through the
+.Xr pf 4
+rdr rule with the name supplied. This allows for easier filter rules
+in your main
+.Xr pf 4
+configuration.
+.Xr hostatectl 5 .
+.El
+.Sh EXAMPLE
+This configuration file would create a service 'www' which load-balances
+4 hosts and falls back to 1 host containing a ``sorry page'':
+.Bd -literal -offset indent
+##
+##
+www1=front-www1.private.example.com
+www2=front-www2.private.example.com
+www3=front-www3.private.example.com
+www4=front-www4.private.example.com
+
+interval 5
+
+table phphosts {
+ timeout 300
+ real port 8080
+ check http "/" digest 630aa3c2f...
+ host $www1
+ host $www2
+ host $www3
+ host $www4
+}
+
+table sorryhost {
+ check icmp
+ disable
+ timeout 300
+ real port 8080
+ host sorryhost.private.example.com
+}
+
+service www {
+ virtual ip www.example.com port 8080 interface trunk0
+ virtual ip www6.example.com port 80 interface trunk0
+
+ tag HOSTATED
+ table phphosts
+ backup table sorryhost
+}
+.Ed
+.Sh FILES
+.Bl -tag -width "/etc/hostated.conf" -compact
+.It Pa /etc/hostated.conf
+.Xr hostated 8
+configuration file
+.El
+.Sh SEE ALSO
+.Xr hostated 8 ,
+.Xr hostatectl 8 .
diff --git a/usr.sbin/hostated/hostated.h b/usr.sbin/hostated/hostated.h
new file mode 100644
index 00000000000..d59a934be48
--- /dev/null
+++ b/usr.sbin/hostated/hostated.h
@@ -0,0 +1,307 @@
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.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.
+ */
+
+#define CONF_FILE "/etc/hostated.conf"
+#define HOSTATED_SOCKET "/var/run/hostated.sock"
+#define PF_SOCKET "/dev/pf"
+#define HOSTATED_USER "_hostated"
+#define HOSTATED_ANCHOR "hostated"
+#define CONNECT_TIMEOUT 200
+#define CHECK_INTERVAL 10
+#define EMPTY_TABLE UINT_MAX
+#define TABLE_NAME_SIZE 16
+#define TAG_NAME_SIZE 16
+#define SRV_NAME_SIZE 16
+#define SRV_MAX_VIRTS 16
+
+#define READ_BUF_SIZE 65535
+
+/* buffer */
+struct buf {
+ TAILQ_ENTRY(buf) entry;
+ u_char *buf;
+ size_t size;
+ size_t max;
+ size_t wpos;
+ size_t rpos;
+};
+
+struct msgbuf {
+ TAILQ_HEAD(, buf) bufs;
+ u_int32_t queued;
+ int fd;
+};
+
+#define IMSG_HEADER_SIZE sizeof(struct imsg_hdr)
+#define MAX_IMSGSIZE 8192
+
+struct buf_read {
+ u_char buf[READ_BUF_SIZE];
+ u_char *rptr;
+ size_t wpos;
+};
+
+struct imsgbuf {
+ TAILQ_HEAD(, imsg_fd) fds;
+ struct buf_read r;
+ struct msgbuf w;
+ struct event ev;
+ void (*handler)(int, short, void *);
+ int fd;
+ pid_t pid;
+ short events;
+};
+
+enum imsg_type {
+ IMSG_NONE,
+ IMSG_CTL_OK, /* answer to hostatectl requests */
+ IMSG_CTL_FAIL,
+ IMSG_CTL_END,
+ IMSG_CTL_SERVICE,
+ IMSG_CTL_TABLE,
+ IMSG_CTL_HOST,
+ IMSG_CTL_SHOW_SUM, /* hostatectl requests */
+ IMSG_CTL_SERVICE_ENABLE,
+ IMSG_CTL_SERVICE_DISABLE,
+ IMSG_CTL_TABLE_ENABLE,
+ IMSG_CTL_TABLE_DISABLE,
+ IMSG_CTL_HOST_ENABLE,
+ IMSG_CTL_HOST_DISABLE,
+ IMSG_CTL_SHUTDOWN,
+ IMSG_CTL_RELOAD,
+ IMSG_SERVICE_ENABLE, /* notifies from pfe to hce */
+ IMSG_SERVICE_DISABLE,
+ IMSG_TABLE_ENABLE,
+ IMSG_TABLE_DISABLE,
+ IMSG_HOST_ENABLE,
+ IMSG_HOST_DISABLE,
+ IMSG_TABLE_STATUS, /* notifies from hce to pfe */
+ IMSG_HOST_STATUS,
+ IMSG_SYNC
+};
+
+struct imsg_hdr {
+ enum imsg_type type;
+ u_int16_t len;
+ u_int32_t peerid;
+ pid_t pid;
+};
+
+struct imsg {
+ struct imsg_hdr hdr;
+ void *data;
+};
+
+typedef u_int32_t objid_t;
+
+struct ctl_status {
+ objid_t id;
+ int up;
+};
+
+struct address {
+ struct sockaddr_storage ss;
+ in_port_t port;
+ char ifname[IFNAMSIZ];
+ TAILQ_ENTRY(address) entry;
+};
+TAILQ_HEAD(addresslist, address);
+
+#define F_DISABLE 0x01
+#define F_BACKUP 0x02
+#define F_USED 0x04
+#define F_ACTIVE_RULESET 0x04
+#define F_DOWN 0x08
+#define F_ADD 0x10
+#define F_DEL 0x20
+#define F_CHANGED 0x40
+
+struct host {
+ u_int8_t flags;
+ objid_t id;
+ objid_t tableid;
+ char *tablename;
+ char name[MAXHOSTNAMELEN];
+ int up;
+#define HOST_DOWN -1
+#define HOST_UNKNOWN 0
+#define HOST_UP 1
+ struct sockaddr_storage ss;
+ TAILQ_ENTRY(host) entry;
+};
+TAILQ_HEAD(hostlist, host);
+
+struct table {
+ objid_t id;
+ objid_t serviceid;
+ u_int8_t flags;
+ int check;
+#define CHECK_NOCHECK 0
+#define CHECK_ICMP 1
+#define CHECK_TCP 2
+#define CHECK_HTTP_CODE 3
+#define CHECK_HTTP_DIGEST 4
+ int up;
+ in_port_t port;
+ int retcode;
+ int timeout;
+ char name[TABLE_NAME_SIZE];
+ char path[MAXPATHLEN];
+ char digest[41]; /* length of sha1 digest * 2 */
+ struct hostlist hosts;
+ TAILQ_ENTRY(table) entry;
+};
+TAILQ_HEAD(tablelist, table);
+
+struct service {
+ objid_t id;
+ u_int8_t flags;
+ in_port_t port;
+ char name[SRV_NAME_SIZE];
+ char tag[TAG_NAME_SIZE];
+ struct addresslist virts;
+ struct table *table;
+ struct table *backup; /* use this if no host up */
+ TAILQ_ENTRY(service) entry;
+};
+TAILQ_HEAD(servicelist, service);
+
+enum {
+ PROC_MAIN,
+ PROC_PFE,
+ PROC_HCE
+} hostated_process;
+
+struct hostated {
+ u_int8_t opts;
+#define HOSTATED_OPT_VERBOSE 0x01
+#define HOSTATED_OPT_NOACTION 0x04
+ struct pfdata *pf;
+ int interval;
+ int icmp_sock;
+ int icmp6_sock;
+ int tablecount;
+ int servicecount;
+ struct table empty_table;
+ struct event ev;
+ struct tablelist tables;
+ struct servicelist services;
+};
+
+/* initially control.h */
+struct {
+ struct event ev;
+ int fd;
+} control_state;
+
+enum blockmodes {
+ BM_NORMAL,
+ BM_NONBLOCK
+};
+
+struct ctl_conn {
+ TAILQ_ENTRY(ctl_conn) entry;
+ struct imsgbuf ibuf;
+
+};
+TAILQ_HEAD(ctl_connlist, ctl_conn);
+
+/* control.c */
+int control_init(void);
+int control_listen(void);
+void control_accept(int, short, void *);
+void control_dispatch_imsg(int, short, void *);
+int control_imsg_relay(struct imsg *);
+void control_cleanup(void);
+
+void session_socket_blockmode(int, enum blockmodes);
+
+extern struct ctl_connlist ctl_conns;
+
+/* parse.y */
+int parse_config(struct hostated *, const char *, int);
+
+/* log.c */
+void log_init(int);
+void log_warn(const char *, ...);
+void log_warnx(const char *, ...);
+void log_info(const char *, ...);
+void log_debug(const char *, ...);
+void fatal(const char *);
+void fatalx(const char *);
+
+/* buffer.c */
+struct buf *buf_open(size_t);
+struct buf *buf_dynamic(size_t, size_t);
+int buf_add(struct buf *, void *, size_t);
+void *buf_reserve(struct buf *, size_t);
+void *buf_seek(struct buf *, size_t, size_t);
+int buf_close(struct msgbuf *, struct buf *);
+void buf_free(struct buf *);
+void msgbuf_init(struct msgbuf *);
+void msgbuf_clear(struct msgbuf *);
+int msgbuf_write(struct msgbuf *);
+
+/* imsg.c */
+void imsg_init(struct imsgbuf *, int, void (*)(int, short, void *));
+ssize_t imsg_read(struct imsgbuf *);
+ssize_t imsg_get(struct imsgbuf *, struct imsg *);
+int imsg_compose(struct imsgbuf *, enum imsg_type, u_int32_t, pid_t,
+ void *, u_int16_t);
+struct buf *imsg_create(struct imsgbuf *, enum imsg_type, u_int32_t, pid_t,
+ u_int16_t);
+int imsg_add(struct buf *, void *, u_int16_t);
+int imsg_close(struct imsgbuf *, struct buf *);
+void imsg_free(struct imsg *);
+void imsg_event_add(struct imsgbuf *); /* needs to be provided externally */
+
+/* pfe.c */
+pid_t pfe(struct hostated *, int [2], int [2], int [2]);
+void show(struct ctl_conn *);
+int enable_service(struct ctl_conn *, objid_t);
+int enable_table(struct ctl_conn *, objid_t);
+int enable_host(struct ctl_conn *, objid_t);
+int disable_service(struct ctl_conn *, objid_t);
+int disable_table(struct ctl_conn *, objid_t);
+int disable_host(struct ctl_conn *, objid_t);
+
+/* pfe_filter.c */
+void init_filter(struct hostated *);
+void init_tables(struct hostated *);
+void flush_table(struct hostated *, struct service *);
+void sync_table(struct hostated *, struct service *, struct table *);
+void sync_ruleset(struct hostated *, struct service *, int);
+void flush_rulesets(struct hostated *);
+
+/* hce.c */
+pid_t hce(struct hostated *, int [2], int [2], int [2]);
+
+/* check_icmp.c */
+int check_icmp(struct host *, int, int, int);
+
+/* check_tcp.c */
+int check_tcp(struct host *, struct table *);
+int tcp_connect(struct host *, struct table *);
+
+/* check_tcp.c */
+int check_http_code(struct host *, struct table *);
+int check_http_digest(struct host *, struct table *);
+
+/* hostated.c */
+struct host *host_find(struct hostated *, objid_t);
+struct table *table_find(struct hostated *, objid_t);
+struct service *service_find(struct hostated *, objid_t);
diff --git a/usr.sbin/hostated/imsg.c b/usr.sbin/hostated/imsg.c
new file mode 100644
index 00000000000..feafd8700c1
--- /dev/null
+++ b/usr.sbin/hostated/imsg.c
@@ -0,0 +1,181 @@
+/* $OpenBSD: imsg.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * 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/socket.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+#include <sys/param.h>
+#include <net/if.h>
+#include <errno.h>
+#include <event.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "hostated.h"
+
+void
+imsg_init(struct imsgbuf *ibuf, int fd, void (*handler)(int, short, void *))
+{
+ msgbuf_init(&ibuf->w);
+ bzero(&ibuf->r, sizeof(ibuf->r));
+ ibuf->fd = fd;
+ ibuf->w.fd = fd;
+ ibuf->pid = getpid();
+ ibuf->handler = handler;
+ TAILQ_INIT(&ibuf->fds);
+}
+
+ssize_t
+imsg_read(struct imsgbuf *ibuf)
+{
+ ssize_t n;
+
+ if ((n = recv(ibuf->fd, ibuf->r.buf + ibuf->r.wpos,
+ sizeof(ibuf->r.buf) - ibuf->r.wpos, 0)) == -1) {
+ if (errno != EINTR && errno != EAGAIN) {
+ log_warn("imsg_read: pipe read error");
+ return (-1);
+ }
+ return (0);
+ }
+
+ ibuf->r.wpos += n;
+
+ return (n);
+}
+
+ssize_t
+imsg_get(struct imsgbuf *ibuf, struct imsg *imsg)
+{
+ size_t av, left, datalen;
+
+ av = ibuf->r.wpos;
+
+ if (IMSG_HEADER_SIZE > av)
+ return (0);
+
+ memcpy(&imsg->hdr, ibuf->r.buf, sizeof(imsg->hdr));
+ if (imsg->hdr.len < IMSG_HEADER_SIZE ||
+ imsg->hdr.len > MAX_IMSGSIZE) {
+ log_warnx("imsg_get: imsg hdr len %u out of bounds, type=%u",
+ imsg->hdr.len, imsg->hdr.type);
+ return (-1);
+ }
+ if (imsg->hdr.len > av)
+ return (0);
+ datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ ibuf->r.rptr = ibuf->r.buf + IMSG_HEADER_SIZE;
+ if ((imsg->data = malloc(datalen)) == NULL) {
+ log_warn("imsg_get");
+ return (-1);
+ }
+ memcpy(imsg->data, ibuf->r.rptr, datalen);
+
+ if (imsg->hdr.len < av) {
+ left = av - imsg->hdr.len;
+ memmove(&ibuf->r.buf, ibuf->r.buf + imsg->hdr.len, left);
+ ibuf->r.wpos = left;
+ } else
+ ibuf->r.wpos = 0;
+
+ return (datalen + IMSG_HEADER_SIZE);
+}
+
+int
+imsg_compose(struct imsgbuf *ibuf, enum imsg_type type, u_int32_t peerid,
+ pid_t pid, void *data, u_int16_t datalen)
+{
+ struct buf *wbuf;
+ int n;
+
+ if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL)
+ return (-1);
+
+ if (imsg_add(wbuf, data, datalen) == -1)
+ return (-1);
+
+ if ((n = imsg_close(ibuf, wbuf)) < 0)
+ return (-1);
+
+ return (n);
+}
+
+/* ARGSUSED */
+struct buf *
+imsg_create(struct imsgbuf *ibuf, enum imsg_type type, u_int32_t peerid,
+ pid_t pid, u_int16_t datalen)
+{
+ struct buf *wbuf;
+ struct imsg_hdr hdr;
+
+ if (datalen > MAX_IMSGSIZE - IMSG_HEADER_SIZE) {
+ log_warnx("imsg_create: len %u > MAX_IMSGSIZE; "
+ "type %u peerid %lu", datalen + IMSG_HEADER_SIZE,
+ type, peerid);
+ return (NULL);
+ }
+
+ hdr.len = (u_int16_t)(datalen + IMSG_HEADER_SIZE);
+ hdr.type = type;
+ hdr.peerid = peerid;
+ if ((hdr.pid = pid) == 0)
+ hdr.pid = ibuf->pid;
+ if ((wbuf = buf_open(hdr.len)) == NULL) {
+ log_warn("imsg_create: buf_open");
+ return (NULL);
+ }
+ if (imsg_add(wbuf, &hdr, sizeof(hdr)) == -1)
+ return (NULL);
+
+ return (wbuf);
+}
+
+int
+imsg_add(struct buf *msg, void *data, u_int16_t datalen)
+{
+ if (datalen)
+ if (buf_add(msg, data, datalen) == -1) {
+ log_warnx("imsg_add: buf_add error");
+ buf_free(msg);
+ return (-1);
+ }
+ return (datalen);
+}
+
+int
+imsg_close(struct imsgbuf *ibuf, struct buf *msg)
+{
+ int n;
+
+ if ((n = buf_close(&ibuf->w, msg)) < 0) {
+ log_warnx("imsg_close: buf_close error");
+ buf_free(msg);
+ return (-1);
+ }
+ imsg_event_add(ibuf);
+
+ return (n);
+}
+
+void
+imsg_free(struct imsg *imsg)
+{
+ free(imsg->data);
+}
diff --git a/usr.sbin/hostated/log.c b/usr.sbin/hostated/log.c
new file mode 100644
index 00000000000..28a7c9a7d4c
--- /dev/null
+++ b/usr.sbin/hostated/log.c
@@ -0,0 +1,159 @@
+/* $OpenBSD: log.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * 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 MIND, 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 <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+/* prototypes */
+void log_init(int);
+void vlog(int, const char *, va_list);
+void log_warn(const char *, ...);
+void log_warnx(const char *, ...);
+void log_info(const char *, ...);
+void log_debug(const char *, ...);
+void fatal(const char *);
+void fatalx(const char *);
+
+int debug;
+
+void logit(int, const char *, ...);
+
+void
+log_init(int n_debug)
+{
+ extern char *__progname;
+
+ debug = n_debug;
+
+ if (!debug)
+ openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON);
+
+ tzset();
+}
+
+void
+logit(int pri, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog(pri, fmt, ap);
+ va_end(ap);
+}
+
+void
+vlog(int pri, const char *fmt, va_list ap)
+{
+ char *nfmt;
+
+ if (debug) {
+ /* best effort in out of mem situations */
+ if (asprintf(&nfmt, "%s\n", fmt) == -1) {
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ } else {
+ vfprintf(stderr, nfmt, ap);
+ free(nfmt);
+ }
+ fflush(stderr);
+ } else
+ vsyslog(pri, fmt, ap);
+}
+
+
+void
+log_warn(const char *emsg, ...)
+{
+ char *nfmt;
+ va_list ap;
+
+ /* best effort to even work in out of memory situations */
+ if (emsg == NULL)
+ logit(LOG_CRIT, "%s", strerror(errno));
+ else {
+ va_start(ap, emsg);
+
+ if (asprintf(&nfmt, "%s: %s", emsg, strerror(errno)) == -1) {
+ /* we tried it... */
+ vlog(LOG_CRIT, emsg, ap);
+ logit(LOG_CRIT, "%s", strerror(errno));
+ } else {
+ vlog(LOG_CRIT, nfmt, ap);
+ free(nfmt);
+ }
+ va_end(ap);
+ }
+}
+
+void
+log_warnx(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_CRIT, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_info(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_INFO, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_debug(const char *emsg, ...)
+{
+ va_list ap;
+
+ if (debug) {
+ va_start(ap, emsg);
+ vlog(LOG_DEBUG, emsg, ap);
+ va_end(ap);
+ }
+}
+
+void
+fatal(const char *emsg)
+{
+ if (emsg == NULL)
+ logit(LOG_CRIT, "fatal: %s", strerror(errno));
+ else
+ if (errno)
+ logit(LOG_CRIT, "fatal: %s: %s",
+ emsg, strerror(errno));
+ else
+ logit(LOG_CRIT, "fatal: %s", emsg);
+
+ exit(1);
+}
+
+void
+fatalx(const char *emsg)
+{
+ errno = 0;
+ fatal(emsg);
+}
diff --git a/usr.sbin/hostated/parse.y b/usr.sbin/hostated/parse.y
new file mode 100644
index 00000000000..977b7a74cb1
--- /dev/null
+++ b/usr.sbin/hostated/parse.y
@@ -0,0 +1,937 @@
+/* $OpenBSD: parse.y,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org>
+ * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
+ * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
+ * Copyright (c) 2001 Markus Friedl. All rights reserved.
+ * Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
+ * Copyright (c) 2001 Theo de Raadt. All rights reserved.
+ *
+ * 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 <sys/queue.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+
+#include "hostated.h"
+
+struct hostated *conf = NULL;
+static FILE *fin = NULL;
+static int lineno = 1;
+static int errors = 0;
+const char *infile;
+char *start_state;
+objid_t last_service_id = 0;
+objid_t last_table_id = 0;
+objid_t last_host_id = 0;
+
+static struct service *service = NULL;
+static struct table *table = NULL;
+
+int yyerror(const char *, ...);
+int yyparse(void);
+int kw_cmp(const void *, const void *);
+int lookup(char *);
+int lgetc(FILE *);
+int lungetc(int);
+int findeol(void);
+int yylex(void);
+
+TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
+struct sym {
+ TAILQ_ENTRY(sym) entries;
+ int used;
+ int persist;
+ char *nam;
+ char *val;
+};
+
+int symset(const char *, const char *, int);
+char *symget(const char *);
+int cmdline_symset(char *);
+
+struct address *host_v4(const char *);
+struct address *host_v6(const char *);
+int host_dns(const char *, struct addresslist *,
+ int, in_port_t, const char *);
+int host(const char *, struct addresslist *,
+ int, in_port_t, const char *);
+
+typedef struct {
+ union {
+ u_int32_t number;
+ char *string;
+ struct host *host;
+ } v;
+ int lineno;
+} YYSTYPE;
+
+%}
+
+%token SERVICE TABLE BACKUP HOST REAL
+%token CHECK HTTP HTTPS TCP ICMP EXTERNAL
+%token TIMEOUT CODE DIGEST PORT TAG INTERFACE
+%token VIRTUAL IP INTERVAL DISABLE
+%token ERROR
+%token <v.string> STRING
+%type <v.string> interface
+%type <v.number> number
+%type <v.host> host
+
+%%
+
+grammar : /* empty */
+ | grammar '\n'
+ | grammar varset '\n'
+ | grammar main '\n'
+ | grammar service '\n'
+ | grammar table '\n'
+ | grammar error '\n' { errors++; }
+ ;
+
+number : STRING {
+ const char *estr;
+
+ $$ = strtonum($1, 0, UINT_MAX, &estr);
+ if (estr) {
+ yyerror("cannot parse number %s : %s",
+ $1, estr);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ ;
+
+varset : STRING '=' STRING {
+ if (symset($1, $3, 0) == -1)
+ fatal("cannot store variable");
+ free($1);
+ free($3);
+ }
+ ;
+
+main : INTERVAL number { conf->interval = $2; }
+ ;
+
+service : SERVICE STRING {
+ struct service *srv;
+
+ TAILQ_FOREACH(srv, &conf->services, entry)
+ if (!strcmp(srv->name, $2))
+ break;
+ if (srv != NULL) {
+ yyerror("service %s defined twice", $2);
+ free($2);
+ YYERROR;
+ }
+ if ((srv = calloc(1, sizeof (*srv))) == NULL)
+ fatal("out of memory");
+
+ if (strlcpy(srv->name, $2, sizeof (srv->name)) >=
+ sizeof (srv->name)) {
+ yyerror("service name truncated");
+ YYERROR;
+ }
+ free ($2);
+ srv->id = last_service_id++;
+ if (last_service_id == UINT_MAX) {
+ yyerror("too many services defined");
+ YYERROR;
+ }
+ service = srv;
+ } '{' optnl serviceopts_l '}' {
+ if (service->table == NULL) {
+ yyerror("service %s has no table",
+ service->name);
+ YYERROR;
+ }
+ if (TAILQ_EMPTY(&service->virts)) {
+ yyerror("service %s has no virtual ip",
+ service->name);
+ YYERROR;
+ }
+ conf->servicecount++;
+ if (service->backup == NULL)
+ service->backup = &conf->empty_table;
+ else if (service->backup->port !=
+ service->table->port) {
+ yyerror("service %s uses two different ports "
+ "for its table and backup table",
+ service->name);
+ YYERROR;
+ }
+
+ if (!(service->flags & F_DISABLE))
+ service->flags |= F_ADD;
+ TAILQ_INSERT_HEAD(&conf->services, service, entry);
+ }
+ ;
+
+serviceopts_l : serviceopts_l serviceoptsl nl
+ | serviceoptsl optnl
+ ;
+
+serviceoptsl : TABLE STRING {
+ struct table *tb;
+
+ TAILQ_FOREACH(tb, &conf->tables, entry)
+ if (!strcmp(tb->name, $2))
+ break;
+ if (tb == NULL) {
+ yyerror("no such table: %s", $2);
+ free($2);
+ YYERROR;
+ } else {
+ service->table = tb;
+ service->table->serviceid = service->id;
+ service->table->flags |= F_USED;
+ free($2);
+ }
+ }
+ | BACKUP TABLE STRING {
+ struct table *tb;
+
+ if (service->backup) {
+ yyerror("backup already specified");
+ free($3);
+ YYERROR;
+ }
+
+ TAILQ_FOREACH(tb, &conf->tables, entry)
+ if (!strcmp(tb->name, $3))
+ break;
+
+ if (tb == NULL) {
+ yyerror("no such table: %s", $3);
+ free($3);
+ YYERROR;
+ } else {
+ service->backup = tb;
+ service->backup->serviceid = service->id;
+ service->backup->flags |= (F_USED|F_BACKUP);
+ free($3);
+ }
+ }
+ | VIRTUAL IP STRING PORT number interface {
+ if ($5 < 1 || $5 > USHRT_MAX) {
+ yyerror("invalid port number: %d", $5);
+ free($3);
+ free($6);
+ YYERROR;
+ }
+ if (host($3, &service->virts,
+ SRV_MAX_VIRTS, htons($5), $6) <= 0) {
+ yyerror("invalid virtual ip: %s", $3);
+ free($3);
+ free($6);
+ YYERROR;
+ }
+ free($3);
+ free($6);
+ }
+ | DISABLE { service->flags |= F_DISABLE; }
+ | TAG STRING {
+ if (strlcpy(service->tag, $2, sizeof(service->tag)) >=
+ sizeof(service->tag)) {
+ yyerror("service tag name truncated");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ ;
+
+table : TABLE STRING {
+ struct table *tb;
+
+ TAILQ_FOREACH(tb, &conf->tables, entry)
+ if (!strcmp(tb->name, $2))
+ break;
+ if (tb != NULL) {
+ yyerror("table %s defined twice");
+ free($2);
+ YYERROR;
+ }
+
+ if ((tb = calloc(1, sizeof (*tb))) == NULL)
+ fatal("out of memory");
+
+ if (strlcpy(tb->name, $2, sizeof (tb->name)) >=
+ sizeof (tb->name)) {
+ yyerror("table name truncated");
+ YYERROR;
+ }
+ tb->id = last_table_id++;
+ tb->timeout = CONNECT_TIMEOUT;
+ if (last_table_id == UINT_MAX) {
+ yyerror("too many tables defined");
+ YYERROR;
+ }
+ free ($2);
+ table = tb;
+ } '{' optnl tableopts_l '}' {
+ if (table->port == 0) {
+ yyerror("table %s has no port", table->name);
+ YYERROR;
+ }
+ if (TAILQ_EMPTY(&table->hosts)) {
+ yyerror("table %s has no hosts", table->name);
+ YYERROR;
+ }
+ if (table->check == CHECK_NOCHECK) {
+ yyerror("table %s has no check", table->name);
+ YYERROR;
+ }
+ conf->tablecount++;
+ TAILQ_INSERT_HEAD(&conf->tables, table, entry);
+ }
+ ;
+
+tableopts_l : tableopts_l tableoptsl nl
+ | tableoptsl optnl
+ ;
+
+tableoptsl : host {
+ $1->tableid = table->id;
+ $1->tablename = table->name;
+ TAILQ_INSERT_HEAD(&table->hosts, $1, entry);
+ }
+ | TIMEOUT number {
+ table->timeout = $2;
+ }
+ | CHECK ICMP {
+ table->check = CHECK_ICMP;
+ }
+ | CHECK TCP {
+ table->check = CHECK_TCP;
+ }
+ | CHECK HTTP STRING CODE number {
+ table->check = CHECK_HTTP_CODE;
+ table->retcode = $5;
+ if (strlcpy(table->path, $3, sizeof (table->path)) >=
+ sizeof (table->path)) {
+ yyerror("http path truncated");
+ free($3);
+ YYERROR;
+ }
+ }
+ | CHECK HTTP STRING DIGEST STRING {
+ table->check = CHECK_HTTP_DIGEST;
+ if (strlcpy(table->path, $3, sizeof (table->path)) >=
+ sizeof (table->path)) {
+ yyerror("http path truncated");
+ free($3);
+ free($5);
+ YYERROR;
+ }
+ if (strlcpy(table->digest, $5, sizeof (table->digest))
+ >= sizeof (table->digest)) {
+ yyerror("http digest truncated");
+ free($3);
+ free($5);
+ YYERROR;
+ }
+ free($3);
+ free($5);
+ }
+ | REAL PORT number {
+ if ($3 < 1 || $3 >= USHRT_MAX) {
+ yyerror("invalid port number: %d", $3);
+ YYERROR;
+ }
+ table->port = $3;
+ }
+ | DISABLE { table->flags |= F_DISABLE; }
+ ;
+
+interface : /*empty*/ { $$ = NULL; }
+ | INTERFACE STRING { $$ = $2; }
+ ;
+
+host : HOST STRING {
+ struct host *r;
+ struct address *a;
+ struct addresslist al;
+
+ if ((r = calloc(1, sizeof (*r))) == NULL)
+ fatal("out of memory");
+
+ TAILQ_INIT(&al);
+ if (host($2, &al, 1, 0, NULL) <= 0) {
+ yyerror("invalid host %s", $2);
+ free($2);
+ YYERROR;
+ }
+ a = TAILQ_FIRST(&al);
+ memcpy(&r->ss, &a->ss, sizeof(r->ss));
+ free(a);
+
+ if (strlcpy(r->name, $2, sizeof (r->name)) >=
+ sizeof (r->name)) {
+ yyerror("host name truncated");
+ free($2);
+ YYERROR;
+ } else {
+ r->id = last_host_id++;
+ if (last_host_id == UINT_MAX) {
+ yyerror("too many hosts defined");
+ YYERROR;
+ }
+ free($2);
+ $$ = r;
+ }
+ }
+ ;
+
+optnl : '\n' optnl
+ |
+ ;
+
+nl : '\n' optnl
+ ;
+
+%%
+
+struct keywords {
+ const char *k_name;
+ int k_val;
+};
+
+int
+yyerror(const char *fmt, ...)
+{
+ va_list ap;
+
+ errors = 1;
+ va_start(ap, fmt);
+ fprintf(stderr, "%s:%d: ", infile, yylval.lineno);
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ va_end(ap);
+ return (0);
+}
+
+int
+kw_cmp(const void *k, const void *e)
+{
+
+ return (strcmp(k, ((const struct keywords *)e)->k_name));
+}
+
+int
+lookup(char *s)
+{
+ /* this has to be sorted always */
+ static const struct keywords keywords[] = {
+ {"backup", BACKUP},
+ {"check", CHECK},
+ {"code", CODE},
+ {"digest", DIGEST},
+ {"disable", DISABLE},
+ {"external", EXTERNAL},
+ {"host", HOST},
+ {"http", HTTP},
+ {"https", HTTPS},
+ {"icmp", ICMP},
+ {"interface", INTERFACE},
+ {"interval", INTERVAL},
+ {"ip", IP},
+ {"port", PORT},
+ {"real", REAL},
+ {"service", SERVICE},
+ {"table", TABLE},
+ {"tag", TAG},
+ {"tcp", TCP},
+ {"timeout", TIMEOUT},
+ {"virtual", VIRTUAL}
+ };
+ const struct keywords *p;
+
+ p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
+ sizeof(keywords[0]), kw_cmp);
+
+ if (p)
+ return (p->k_val);
+ else
+ return (STRING);
+}
+
+#define MAXPUSHBACK 128
+
+char *parsebuf;
+int parseindex;
+char pushback_buffer[MAXPUSHBACK];
+int pushback_index = 0;
+
+int
+lgetc(FILE *f)
+{
+ int c, next;
+
+ if (parsebuf) {
+ /* Read character from the parsebuffer instead of input. */
+ if (parseindex >= 0) {
+ c = parsebuf[parseindex++];
+ if (c != '\0')
+ return (c);
+ parsebuf = NULL;
+ } else
+ parseindex++;
+ }
+
+ if (pushback_index)
+ return (pushback_buffer[--pushback_index]);
+
+ while ((c = getc(f)) == '\\') {
+ next = getc(f);
+ if (next != '\n') {
+ c = next;
+ break;
+ }
+ yylval.lineno = lineno;
+ lineno++;
+ }
+ if (c == '\t' || c == ' ') {
+ /* Compress blanks to a single space. */
+ do {
+ c = getc(f);
+ } while (c == '\t' || c == ' ');
+ ungetc(c, f);
+ c = ' ';
+ }
+
+ return (c);
+}
+
+int
+lungetc(int c)
+{
+ if (c == EOF)
+ return (EOF);
+ if (parsebuf) {
+ parseindex--;
+ if (parseindex >= 0)
+ return (c);
+ }
+ if (pushback_index < MAXPUSHBACK-1)
+ return (pushback_buffer[pushback_index++] = c);
+ else
+ return (EOF);
+}
+
+int
+findeol(void)
+{
+ int c;
+
+ parsebuf = NULL;
+ pushback_index = 0;
+
+ /* skip to either EOF or the first real EOL */
+ while (1) {
+ c = lgetc(fin);
+ if (c == '\n') {
+ lineno++;
+ break;
+ }
+ if (c == EOF)
+ break;
+ }
+ return (ERROR);
+}
+
+int
+yylex(void)
+{
+ char buf[8096];
+ char *p, *val;
+ int endc, c;
+ int token;
+
+top:
+ p = buf;
+ while ((c = lgetc(fin)) == ' ')
+ ; /* nothing */
+
+ yylval.lineno = lineno;
+ if (c == '#')
+ while ((c = lgetc(fin)) != '\n' && c != EOF)
+ ; /* nothing */
+ if (c == '$' && parsebuf == NULL) {
+ while (1) {
+ if ((c = lgetc(fin)) == EOF)
+ return (0);
+
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ if (isalnum(c) || c == '_') {
+ *p++ = (char)c;
+ continue;
+ }
+ *p = '\0';
+ lungetc(c);
+ break;
+ }
+ val = symget(buf);
+ if (val == NULL) {
+ yyerror("macro '%s' not defined", buf);
+ return (findeol());
+ }
+ parsebuf = val;
+ parseindex = 0;
+ goto top;
+ }
+
+ switch (c) {
+ case '\'':
+ case '"':
+ endc = c;
+ while (1) {
+ if ((c = lgetc(fin)) == EOF)
+ return (0);
+ if (c == endc) {
+ *p = '\0';
+ break;
+ }
+ if (c == '\n') {
+ lineno++;
+ continue;
+ }
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ *p++ = (char)c;
+ }
+ yylval.v.string = strdup(buf);
+ if (yylval.v.string == NULL)
+ errx(1, "yylex: strdup");
+ return (STRING);
+ }
+
+#define allowed_in_string(x) \
+ (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
+ x != '{' && x != '}' && \
+ x != '!' && x != '=' && x != '#' && \
+ x != ','))
+
+ if (isalnum(c) || c == ':' || c == '_') {
+ do {
+ *p++ = c;
+ if ((unsigned)(p-buf) >= sizeof(buf)) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ } while ((c = lgetc(fin)) != EOF && (allowed_in_string(c)));
+ lungetc(c);
+ *p = '\0';
+ if ((token = lookup(buf)) == STRING)
+ if ((yylval.v.string = strdup(buf)) == NULL)
+ err(1, "yylex: strdup");
+ return (token);
+ }
+ if (c == '\n') {
+ yylval.lineno = lineno;
+ lineno++;
+ }
+ if (c == EOF)
+ return (0);
+ return (c);
+}
+
+int
+parse_config(struct hostated *x_conf, const char *filename, int opts)
+{
+ struct sym *sym, *next;
+
+ conf = x_conf;
+
+ TAILQ_INIT(&conf->services);
+ TAILQ_INIT(&conf->tables);
+ memset(&conf->empty_table, 0, sizeof(conf->empty_table));
+ conf->empty_table.id = EMPTY_TABLE;
+ conf->empty_table.flags |= F_DISABLE;
+ (void)strlcpy(conf->empty_table.name, "empty",
+ sizeof(conf->empty_table.name));
+
+ conf->interval = CHECK_INTERVAL;
+ conf->opts = opts;
+
+ if ((fin = fopen(filename, "r")) == NULL) {
+ warn("%s", filename);
+ return (NULL);
+ }
+ infile = filename;
+ yyparse();
+ fclose(fin);
+
+ /* Free macros and check which have not been used. */
+ for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) {
+ next = TAILQ_NEXT(sym, entries);
+ if ((conf->opts & HOSTATED_OPT_VERBOSE) && !sym->used)
+ fprintf(stderr, "warning: macro '%s' not "
+ "used\n", sym->nam);
+ if (!sym->persist) {
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entries);
+ free(sym);
+ }
+ }
+
+ if (TAILQ_EMPTY(&conf->services)) {
+ log_warnx("no services, nothing to do");
+ errors++;
+ }
+
+ /* Verify that every table is used */
+ TAILQ_FOREACH(table, &conf->tables, entry)
+ if (!(table->flags & F_USED)) {
+ log_warnx("unused table: %s", table->name);
+ errors++;
+ }
+
+ if (errors) {
+ bzero(&conf, sizeof (*conf));
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+symset(const char *nam, const char *val, int persist)
+{
+ struct sym *sym;
+
+ for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam);
+ sym = TAILQ_NEXT(sym, entries))
+ ; /* nothing */
+
+ if (sym != NULL) {
+ if (sym->persist == 1)
+ return (0);
+ else {
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entries);
+ free(sym);
+ }
+ }
+ if ((sym = calloc(1, sizeof(*sym))) == NULL)
+ return (-1);
+
+ sym->nam = strdup(nam);
+ if (sym->nam == NULL) {
+ free(sym);
+ return (-1);
+ }
+ sym->val = strdup(val);
+ if (sym->val == NULL) {
+ free(sym->nam);
+ free(sym);
+ return (-1);
+ }
+ sym->used = 0;
+ sym->persist = persist;
+ TAILQ_INSERT_TAIL(&symhead, sym, entries);
+ return (0);
+}
+
+int
+cmdline_symset(char *s)
+{
+ char *sym, *val;
+ int ret;
+ size_t len;
+
+ if ((val = strrchr(s, '=')) == NULL)
+ return (-1);
+
+ len = strlen(s) - strlen(val) + 1;
+ if ((sym = malloc(len)) == NULL)
+ errx(1, "cmdline_symset: malloc");
+
+ strlcpy(sym, s, len);
+
+ ret = symset(sym, val + 1, 1);
+ free(sym);
+
+ return (ret);
+}
+
+char *
+symget(const char *nam)
+{
+ struct sym *sym;
+
+ TAILQ_FOREACH(sym, &symhead, entries)
+ if (strcmp(nam, sym->nam) == 0) {
+ sym->used = 1;
+ return (sym->val);
+ }
+ return (NULL);
+}
+
+struct address *
+host_v4(const char *s)
+{
+ struct in_addr ina;
+ struct sockaddr_in *sain;
+ struct address *h;
+
+ bzero(&ina, sizeof(ina));
+ if (inet_pton(AF_INET, s, &ina) != 1)
+ return (NULL);
+
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(NULL);
+ sain = (struct sockaddr_in *)&h->ss;
+ sain->sin_len = sizeof(struct sockaddr_in);
+ sain->sin_family = AF_INET;
+ sain->sin_addr.s_addr = ina.s_addr;
+
+ return (h);
+}
+
+struct address *
+host_v6(const char *s)
+{
+ struct in6_addr ina6;
+ struct sockaddr_in6 *sin6;
+ struct address *h;
+
+ bzero(&ina6, sizeof(ina6));
+ if (inet_pton(AF_INET6, s, &ina6) != 1)
+ return (NULL);
+
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(NULL);
+ sin6 = (struct sockaddr_in6 *)&h->ss;
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ sin6->sin6_family = AF_INET6;
+ memcpy(&sin6->sin6_addr, &ina6, sizeof(ina6));
+
+ return (h);
+}
+
+int
+host_dns(const char *s, struct addresslist *al, int max,
+ in_port_t port, const char *ifname)
+{
+ struct addrinfo hints, *res0, *res;
+ int error, cnt = 0;
+ struct sockaddr_in *sain;
+ struct sockaddr_in6 *sin6;
+ struct address *h;
+
+ bzero(&hints, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM; /* DUMMY */
+ error = getaddrinfo(s, NULL, &hints, &res0);
+ if (error == EAI_AGAIN || error == EAI_NODATA || error == EAI_NONAME)
+ return (0);
+ if (error) {
+ log_warnx("host_dns: could not parse \"%s\": %s", s,
+ gai_strerror(error));
+ return (-1);
+ }
+
+ for (res = res0; res && cnt < max; res = res->ai_next) {
+ if (res->ai_family != AF_INET &&
+ res->ai_family != AF_INET6)
+ continue;
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(NULL);
+
+ h->port = port;
+ if (ifname != NULL) {
+ if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >=
+ sizeof(h->ifname))
+ log_warnx("host_dns: interface name truncated");
+ return (-1);
+ }
+ h->ss.ss_family = res->ai_family;
+ if (res->ai_family == AF_INET) {
+ sain = (struct sockaddr_in *)&h->ss;
+ sain->sin_len = sizeof(struct sockaddr_in);
+ sain->sin_addr.s_addr = ((struct sockaddr_in *)
+ res->ai_addr)->sin_addr.s_addr;
+ } else {
+ sin6 = (struct sockaddr_in6 *)&h->ss;
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ memcpy(&sin6->sin6_addr, &((struct sockaddr_in6 *)
+ res->ai_addr)->sin6_addr, sizeof(struct in6_addr));
+ }
+
+ TAILQ_INSERT_HEAD(al, h, entry);
+ cnt++;
+ }
+ if (cnt == max && res) {
+ log_warnx("host_dns: %s resolves to more than %d hosts",
+ s, max);
+ }
+ freeaddrinfo(res0);
+
+ return (cnt);
+}
+
+int
+host(const char *s, struct addresslist *al, int max,
+ in_port_t port, const char *ifname)
+{
+ struct address *h;
+
+ h = host_v4(s);
+
+ /* IPv6 address? */
+ if (h == NULL)
+ h = host_v6(s);
+
+ if (h != NULL) {
+ h->port = port;
+ if (ifname != NULL) {
+ if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >=
+ sizeof(h->ifname)) {
+ log_warnx("host: interface name truncated");
+ return (-1);
+ }
+ }
+
+ TAILQ_INSERT_HEAD(al, h, entry);
+ return (1);
+ }
+
+ return (host_dns(s, al, max, port, ifname));
+}
diff --git a/usr.sbin/hostated/pfe.c b/usr.sbin/hostated/pfe.c
new file mode 100644
index 00000000000..0a7ff43949f
--- /dev/null
+++ b/usr.sbin/hostated/pfe.c
@@ -0,0 +1,497 @@
+/* $OpenBSD: pfe.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/queue.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <net/if.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <pwd.h>
+
+#include "hostated.h"
+
+void pfe_sig_handler(int sig, short, void *);
+void pfe_shutdown(void);
+void pfe_dispatch_imsg(int, short, void *);
+void pfe_dispatch_parent(int, short, void *);
+
+void pfe_sync(void);
+
+static struct hostated *env = NULL;
+
+struct imsgbuf *ibuf_main;
+struct imsgbuf *ibuf_hce;
+
+void
+pfe_sig_handler(int sig, short event, void *arg)
+{
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ pfe_shutdown();
+ default:
+ fatalx("pfe_sig_handler: unexpected signal");
+ }
+}
+
+pid_t
+pfe(struct hostated *x_env, int pipe_parent2pfe[2], int pipe_parent2hce[2],
+ int pipe_pfe2hce[2])
+{
+ pid_t pid;
+ struct passwd *pw;
+ struct event ev_sigint;
+ struct event ev_sigterm;
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("pfe: cannot fork");
+ case 0:
+ break;
+ default:
+ return (pid);
+ }
+
+ env = x_env;
+
+ if (control_init() == -1)
+ fatalx("pfe: control socket setup failed");
+
+ init_filter(env);
+ init_tables(env);
+
+ if ((pw = getpwnam(HOSTATED_USER)) == NULL)
+ fatal("pfe: getpwnam");
+
+ if (chroot(pw->pw_dir) == -1)
+ fatal("pfe: chroot");
+ if (chdir("/") == -1)
+ fatal("pfe: chdir(\"/\")");
+
+ setproctitle("pf update engine");
+ hostated_process = PROC_PFE;
+
+ 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("pfe: cannot drop privileges");
+
+ event_init();
+
+ signal_set(&ev_sigint, SIGINT, pfe_sig_handler, NULL);
+ signal_set(&ev_sigterm, SIGTERM, pfe_sig_handler, NULL);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+
+ /* setup pipes */
+ close(pipe_pfe2hce[0]);
+ close(pipe_parent2pfe[0]);
+ close(pipe_parent2hce[0]);
+ close(pipe_parent2hce[1]);
+
+ if ((ibuf_hce = calloc(1, sizeof(struct imsgbuf))) == NULL ||
+ (ibuf_main = calloc(1, sizeof(struct imsgbuf))) == NULL)
+ fatal("pfe");
+ imsg_init(ibuf_hce, pipe_pfe2hce[1], pfe_dispatch_imsg);
+ imsg_init(ibuf_main, pipe_parent2pfe[1], pfe_dispatch_parent);
+
+ ibuf_hce->events = EV_READ;
+ event_set(&ibuf_hce->ev, ibuf_hce->fd, ibuf_hce->events,
+ ibuf_hce->handler, ibuf_hce);
+ event_add(&ibuf_hce->ev, NULL);
+
+ ibuf_main->events = EV_READ;
+ event_set(&ibuf_main->ev, ibuf_main->fd, ibuf_main->events,
+ ibuf_main->handler, ibuf_main);
+ event_add(&ibuf_main->ev, NULL);
+
+ TAILQ_INIT(&ctl_conns);
+ control_listen();
+
+ event_dispatch();
+ pfe_shutdown();
+
+ return (0);
+}
+
+void
+pfe_shutdown(void)
+{
+ flush_rulesets(env);
+ log_info("pf update engine exiting");
+ _exit(0);
+}
+
+void
+pfe_dispatch_imsg(int fd, short event, void *ptr)
+{
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ struct host *host;
+ struct table *table;
+ struct ctl_status st;
+
+ ibuf = ptr;
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("pfe_dispatch_imsg: imsg_read_error");
+ if (n == 0)
+ fatalx("pfe_dispatch_imsg: pipe closed");
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("pfe_dispatch_imsg: msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("pfe_dispatch_imsg: unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("pfe_dispatch_imsg: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_HOST_STATUS:
+ if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(st))
+ fatalx("pfe_dispatch_imsg: invalid request");
+ memcpy(&st, imsg.data, sizeof(st));
+ if ((host = host_find(env, st.id)) == NULL)
+ fatalx("pfe_dispatch_imsg: invalid host id");
+ if (host->up == st.up) {
+ log_debug("pfe_dispatch_imsg: host %d => %d",
+ host->id, host->up);
+ fatalx("pfe_dispatch_imsg: desynchronized");
+ }
+
+ if ((table = table_find(env, host->tableid)) == NULL)
+ fatalx("pfe_dispatch_imsg: invalid table id");
+
+ log_debug("pfe_dispatch_imsg: state %d for host %u %s",
+ st.up, host->id, host->name);
+
+ if ((st.up == HOST_UNKNOWN && host->up == HOST_DOWN) ||
+ (st.up == HOST_DOWN && host->up == HOST_UNKNOWN)) {
+ host->up = st.up;
+ break;
+ }
+
+ if (st.up == HOST_UP) {
+ table->flags |= F_CHANGED;
+ table->up++;
+ host->flags |= F_ADD;
+ host->flags &= ~(F_DEL);
+ } else {
+ table->up--;
+ table->flags |= F_CHANGED;
+ host->flags |= F_DEL;
+ host->flags &= ~(F_ADD);
+ }
+ host->up = st.up;
+ break;
+ case IMSG_SYNC:
+ pfe_sync();
+ break;
+ default:
+ log_debug("pfe_dispatch_imsg: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+pfe_dispatch_parent(int fd, short event, void * ptr)
+{
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = ptr;
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read error");
+ if (n == 0) /* connection closed */
+ fatalx("pfe_dispatch_parent: pipe closed");
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("pfe_dispatch_parent: unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("pfe_dispatch_parent: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ log_debug("pfe_dispatch_parent: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+}
+
+void
+show(struct ctl_conn *c)
+{
+ struct service *service;
+ struct host *host;
+
+ TAILQ_FOREACH(service, &env->services, entry) {
+ imsg_compose(&c->ibuf, IMSG_CTL_SERVICE, 0, 0,
+ service, sizeof(*service));
+ if (service->flags & F_DISABLE)
+ continue;
+
+ imsg_compose(&c->ibuf, IMSG_CTL_TABLE, 0, 0,
+ service->table, sizeof(*service->table));
+ if (!(service->table->flags & F_DISABLE))
+ TAILQ_FOREACH(host, &service->table->hosts, entry)
+ imsg_compose(&c->ibuf, IMSG_CTL_HOST, 0, 0,
+ host, sizeof(*host));
+
+ if (service->backup->id == EMPTY_TABLE)
+ continue;
+ imsg_compose(&c->ibuf, IMSG_CTL_TABLE, 0, 0,
+ service->backup, sizeof(*service->backup));
+ if (!(service->backup->flags & F_DISABLE))
+ TAILQ_FOREACH(host, &service->backup->hosts, entry)
+ imsg_compose(&c->ibuf, IMSG_CTL_HOST, 0, 0,
+ host, sizeof(*host));
+ }
+ imsg_compose(&c->ibuf, IMSG_CTL_END, 0, 0, NULL, 0);
+}
+
+
+int
+disable_service(struct ctl_conn *c, objid_t id)
+{
+ struct service *service;
+
+ if ((service = service_find(env, id)) == NULL)
+ return (-1);
+
+ if (service->flags & F_DISABLE)
+ return (0);
+
+ service->flags |= F_DISABLE;
+ service->flags &= ~(F_ADD);
+ service->flags |= F_DEL;
+ service->table->flags |= F_DISABLE;
+ log_debug("disable_service: disabled service %d", service->id);
+ pfe_sync();
+ return (0);
+}
+
+int
+enable_service(struct ctl_conn *c, objid_t id)
+{
+ struct service *service;
+
+ if ((service = service_find(env, id)) == NULL)
+ return (-1);
+
+ if (!(service->flags & F_DISABLE))
+ return (0);
+
+ service->flags &= ~(F_DISABLE);
+ service->flags &= ~(F_DEL);
+ service->flags |= F_ADD;
+ log_debug("enable_service: enabled service %d", service->id);
+
+ /* XXX: we're syncing twice */
+ if (enable_table(c, service->table->id))
+ return (-1);
+ if (enable_table(c, service->backup->id))
+ return (-1);
+ return (0);
+}
+
+int
+disable_table(struct ctl_conn *c, objid_t id)
+{
+ struct table *table;
+ struct service *service;
+ struct host *host;
+
+ if (id == EMPTY_TABLE)
+ return (-1);
+ if ((table = table_find(env, id)) == NULL)
+ return (-1);
+ if ((service = service_find(env, table->serviceid)) == NULL)
+ fatalx("disable_table: desynchronised");
+
+ if (table->flags & F_DISABLE)
+ return (0);
+ table->flags |= (F_DISABLE|F_CHANGED);
+ table->up = 0;
+ TAILQ_FOREACH(host, &table->hosts, entry)
+ host->up = HOST_UNKNOWN;
+ imsg_compose(ibuf_hce, IMSG_TABLE_DISABLE, 0, 0, &id, sizeof(id));
+ log_debug("disable_table: disabled table %d", table->id);
+ pfe_sync();
+ return (0);
+}
+
+int
+enable_table(struct ctl_conn *c, objid_t id)
+{
+ struct service *service;
+ struct table *table;
+ struct host *host;
+
+ if (id == EMPTY_TABLE)
+ return (-1);
+ if ((table = table_find(env, id)) == NULL)
+ return (-1);
+ if ((service = service_find(env, table->serviceid)) == NULL)
+ fatalx("enable_table: desynchronised");
+
+ if (!(table->flags & F_DISABLE))
+ return (0);
+ table->flags &= ~(F_DISABLE);
+ table->flags |= F_CHANGED;
+ table->up = 0;
+ TAILQ_FOREACH(host, &table->hosts, entry)
+ host->up = HOST_UNKNOWN;
+ imsg_compose(ibuf_hce, IMSG_TABLE_ENABLE, 0, 0, &id, sizeof(id));
+ log_debug("enable_table: enabled table %d", table->id);
+ pfe_sync();
+ return (0);
+}
+
+int
+disable_host(struct ctl_conn *c, objid_t id)
+{
+ struct host *host;
+ struct table *table;
+
+ if ((host = host_find(env, id)) == NULL)
+ return (-1);
+
+ if (host->flags & F_DISABLE)
+ return (0);
+
+ if (host->up == HOST_UP) {
+ if ((table = table_find(env, host->tableid)) == NULL)
+ fatalx("disable_host: invalid table id");
+ table->up--;
+ table->flags |= F_CHANGED;
+ }
+
+ host->up = HOST_UNKNOWN;
+ host->flags |= F_DISABLE;
+ host->flags |= F_DEL;
+ host->flags &= ~(F_ADD);
+
+ imsg_compose(ibuf_hce, IMSG_HOST_DISABLE, 0, 0, &id, sizeof (id));
+ log_debug("disable_host: disabled host %d", host->id);
+ pfe_sync();
+ return (0);
+}
+
+int
+enable_host(struct ctl_conn *c, objid_t id)
+{
+ struct host *host;
+
+ if ((host = host_find(env, id)) == NULL)
+ return (-1);
+
+ if (!(host->flags & F_DISABLE))
+ return (0);
+
+ host->up = HOST_UNKNOWN;
+ host->flags &= ~(F_DISABLE);
+ host->flags &= ~(F_DEL);
+ host->flags &= ~(F_ADD);
+
+ imsg_compose(ibuf_hce, IMSG_HOST_ENABLE, 0, 0, &id, sizeof (id));
+ log_debug("enable_host: enabled host %d", host->id);
+ pfe_sync();
+ return (0);
+}
+
+void
+pfe_sync(void)
+{
+ struct service *service;
+ struct table *active;
+ int backup;
+
+ TAILQ_FOREACH(service, &env->services, entry) {
+ backup = (service->flags & F_BACKUP);
+ service->flags &= ~(F_BACKUP);
+ service->flags &= ~(F_DOWN);
+
+ if (service->flags & F_DISABLE ||
+ (service->table->up == 0 && service->backup->up == 0)) {
+ service->flags |= F_DOWN;
+ active = NULL;
+ } else if (service->table->up == 0 && service->backup->up > 0) {
+ service->flags |= F_BACKUP;
+ active = service->backup;
+ active->flags |= service->table->flags & F_CHANGED;
+ active->flags |= service->backup->flags & F_CHANGED;
+ } else
+ active = service->table;
+
+ if (active != NULL && active->flags & F_CHANGED)
+ sync_table(env, service, active);
+
+ service->table->flags &= ~(F_CHANGED);
+ service->backup->flags &= ~(F_CHANGED);
+
+ if (service->flags & F_DOWN) {
+ if (service->flags & F_ACTIVE_RULESET) {
+ flush_table(env, service);
+ log_debug("pfe_sync: disabling ruleset");
+ service->flags &= ~(F_ACTIVE_RULESET);
+ sync_ruleset(env, service, 0);
+ }
+ } else if (!(service->flags & F_ACTIVE_RULESET)) {
+ log_debug("pfe_sync: enabling ruleset");
+ service->flags |= F_ACTIVE_RULESET;
+ sync_ruleset(env, service, 1);
+ }
+ }
+}
diff --git a/usr.sbin/hostated/pfe_filter.c b/usr.sbin/hostated/pfe_filter.c
new file mode 100644
index 00000000000..b7bd7550070
--- /dev/null
+++ b/usr.sbin/hostated/pfe_filter.c
@@ -0,0 +1,336 @@
+/* $OpenBSD: pfe_filter.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.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/ioctl.h>
+#include <sys/param.h>
+
+#include <net/if.h>
+#include <net/pfvar.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <limits.h>
+#include <fcntl.h>
+#include <event.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "hostated.h"
+
+struct pfdata {
+ int dev;
+ struct pf_anchor *anchor;
+ struct pfioc_trans pft;
+ struct pfioc_trans_e pfte;
+};
+
+int transaction_init(struct hostated *, const char *);
+int transaction_commit(struct hostated *);
+void kill_tables(struct hostated *);
+
+void
+init_filter(struct hostated *env)
+{
+ struct pf_status status;
+
+ if ((env->pf = calloc(1, sizeof(*(env->pf)))) == NULL)
+ fatal("calloc");
+ if ((env->pf->dev = open(PF_SOCKET, O_RDWR)) == -1)
+ fatal("init_filter: cannot open pf socket");
+ if (ioctl(env->pf->dev, DIOCGETSTATUS, &status) == -1)
+ fatal("init_filter: DIOCGETSTATUS");
+ if (!status.running)
+ fatalx("init_filter: pf is disabled");
+ log_debug("init_filter: filter init done");
+}
+
+void
+init_tables(struct hostated *env)
+{
+ int i;
+ struct service *service;
+ struct pfr_table *tables;
+ struct pfioc_table io;
+
+ if ((tables = calloc(env->servicecount, sizeof(*tables))) == NULL)
+ fatal("calloc");
+ i = 0;
+
+ TAILQ_FOREACH(service, &env->services, entry) {
+ (void)strlcpy(tables[i].pfrt_anchor, HOSTATED_ANCHOR "/",
+ sizeof(tables[i].pfrt_anchor));
+ (void)strlcat(tables[i].pfrt_anchor, service->name,
+ sizeof(tables[i].pfrt_anchor));
+ (void)strlcpy(tables[i].pfrt_name, service->name,
+ sizeof(tables[i].pfrt_name));
+ tables[i].pfrt_flags |= PFR_TFLAG_PERSIST;
+ i++;
+ }
+ if (i != env->servicecount)
+ fatalx("init_tables: table count modified");
+
+ memset(&io, 0, sizeof(io));
+ io.pfrio_size = env->servicecount;
+ io.pfrio_esize = sizeof(*tables);
+ io.pfrio_buffer = tables;
+
+ if (ioctl(env->pf->dev, DIOCRADDTABLES, &io) == -1)
+ fatal("init_tables: cannot create tables");
+ log_debug("created %d tables", io.pfrio_nadd);
+
+ if (io.pfrio_nadd == env->servicecount)
+ return;
+
+ /*
+ * clear all tables, since some already existed
+ */
+ TAILQ_FOREACH(service, &env->services, entry)
+ flush_table(env, service);
+}
+
+void
+kill_tables(struct hostated *env) {
+ struct pfioc_table io;
+ struct service *service;
+
+ memset(&io, 0, sizeof(io));
+ TAILQ_FOREACH(service, &env->services, entry) {
+ (void)strlcpy(io.pfrio_table.pfrt_anchor, HOSTATED_ANCHOR "/",
+ sizeof(io.pfrio_table.pfrt_anchor));
+ (void)strlcat(io.pfrio_table.pfrt_anchor, service->name,
+ sizeof(io.pfrio_table.pfrt_anchor));
+ if (ioctl(env->pf->dev, DIOCRCLRTABLES, &io) == -1)
+ fatal("kill_tables: ioctl faile: ioctl failed");
+ }
+ log_debug("kill_tables: deleted %d tables", io.pfrio_ndel);
+}
+
+void
+sync_table(struct hostated *env, struct service *service, struct table *table)
+{
+ int i;
+ struct pfioc_table io;
+ struct pfr_addr *addlist;
+ struct sockaddr_in *sain;
+ struct sockaddr_in6 *sain6;
+ struct host *host;
+
+ if (table == NULL)
+ return;
+
+ if (table->up == 0) {
+ flush_table(env, service);
+ return;
+ }
+
+ if ((addlist = calloc(table->up, sizeof(*addlist))) == NULL)
+ fatal("calloc");
+
+ memset(&io, 0, sizeof(io));
+ io.pfrio_esize = sizeof(struct pfr_addr);
+ io.pfrio_size = table->up;
+ io.pfrio_size2 = 0;
+ io.pfrio_buffer = addlist;
+ (void)strlcpy(io.pfrio_table.pfrt_anchor, HOSTATED_ANCHOR "/",
+ sizeof(io.pfrio_table.pfrt_anchor));
+ (void)strlcat(io.pfrio_table.pfrt_anchor, service->name,
+ sizeof(io.pfrio_table.pfrt_anchor));
+ (void)strlcpy(io.pfrio_table.pfrt_name, service->name,
+ sizeof(io.pfrio_table.pfrt_name));
+
+ i = 0;
+ TAILQ_FOREACH(host, &table->hosts, entry) {
+ if (host->up != 1)
+ continue;
+ memset(&(addlist[i]), 0, sizeof(addlist[i]));
+ switch (host->ss.ss_family) {
+ case AF_INET:
+ sain = (struct sockaddr_in *)&host->ss;
+ addlist[i].pfra_af = AF_INET;
+ memcpy(&(addlist[i].pfra_ip4addr), &sain->sin_addr,
+ sizeof(sain->sin_addr));
+ addlist[i].pfra_net = 32;
+ break;
+ case AF_INET6:
+ sain6 = (struct sockaddr_in6 *)&host->ss;
+ addlist[i].pfra_af = AF_INET6;
+ memcpy(&(addlist[i].pfra_ip6addr), &sain6->sin6_addr,
+ sizeof(sain6->sin6_addr));
+ addlist[i].pfra_net = 128;
+ break;
+ default:
+ fatalx("sync_table: unknown address family");
+ break;
+ }
+ i++;
+ }
+ if (i != table->up)
+ fatalx("sync_table: desynchronized");
+
+ if (ioctl(env->pf->dev, DIOCRSETADDRS, &io) == -1)
+ fatal("sync_table: cannot set address list");
+
+ log_debug("sync_table: table %s: %d added, %d deleted, %d changed",
+ io.pfrio_table.pfrt_name,
+ io.pfrio_nadd, io.pfrio_ndel, io.pfrio_nchange);
+}
+
+void
+flush_table(struct hostated *env, struct service *service)
+{
+ struct pfioc_table io;
+
+ memset(&io, 0, sizeof(io));
+ (void)strlcpy(io.pfrio_table.pfrt_anchor, HOSTATED_ANCHOR "/",
+ sizeof(io.pfrio_table.pfrt_anchor));
+ (void)strlcat(io.pfrio_table.pfrt_anchor, service->name,
+ sizeof(io.pfrio_table.pfrt_anchor));
+ (void)strlcpy(io.pfrio_table.pfrt_name, service->name,
+ sizeof(io.pfrio_table.pfrt_name));
+ if (ioctl(env->pf->dev, DIOCRCLRADDRS, &io) == -1)
+ fatal("flush_table: cannot flush table");
+ log_debug("flush_table: flushed table %s", service->name);
+ return;
+}
+
+int
+transaction_init(struct hostated *env, const char *anchor)
+{
+ env->pf->pft.size = 1;
+ env->pf->pft.esize = sizeof env->pf->pfte;
+ env->pf->pft.array = &env->pf->pfte;
+
+ memset(&env->pf->pfte, 0, sizeof env->pf->pfte);
+ strlcpy(env->pf->pfte.anchor, anchor, PF_ANCHOR_NAME_SIZE);
+ env->pf->pfte.rs_num = PF_RULESET_RDR;
+
+ if (ioctl(env->pf->dev, DIOCXBEGIN, &env->pf->pft) == -1)
+ return (-1);
+ return (0);
+}
+
+int
+transaction_commit(struct hostated *env)
+{
+ if (ioctl(env->pf->dev, DIOCXCOMMIT, &env->pf->pft) == -1)
+ return (-1);
+ return (0);
+}
+
+void
+sync_ruleset(struct hostated *env, struct service *service, int enable)
+{
+ struct pfioc_rule rio;
+ struct pfioc_pooladdr pio;
+ struct sockaddr_in *sain;
+ struct sockaddr_in6 *sain6;
+ struct address *address;
+ char anchor[PF_ANCHOR_NAME_SIZE];
+
+ bzero(anchor, sizeof(anchor));
+ (void)strlcpy(anchor, HOSTATED_ANCHOR "/", sizeof(anchor));
+ (void)strlcat(anchor, service->name, sizeof(anchor));
+ transaction_init(env, anchor);
+
+ if (!enable) {
+ transaction_commit(env);
+ log_debug("sync_ruleset: rules removed");
+ return;
+ }
+
+ TAILQ_FOREACH(address, &service->virts, entry) {
+ memset(&rio, 0, sizeof(rio));
+ memset(&pio, 0, sizeof(pio));
+ (void)strlcpy(rio.anchor, anchor, sizeof(rio.anchor));
+
+ rio.ticket = env->pf->pfte.ticket;
+ if (ioctl(env->pf->dev, DIOCBEGINADDRS, &pio) == -1)
+ fatal("sync_ruleset: cannot initialise address pool");
+
+ rio.pool_ticket = pio.ticket;
+ rio.rule.af = address->ss.ss_family;
+ rio.rule.proto = IPPROTO_TCP;
+ rio.rule.src.addr.type = PF_ADDR_ADDRMASK;
+ rio.rule.dst.addr.type = PF_ADDR_ADDRMASK;
+ rio.rule.dst.port_op = PF_OP_EQ;
+ rio.rule.dst.port[0] = address->port;
+ rio.rule.rtableid = -1; /* stay in the main routing table */
+ rio.rule.action = PF_RDR;
+ if (strlen(service->tag))
+ (void)strlcpy(rio.rule.tagname, service->tag,
+ sizeof(rio.rule.tagname));
+ if (strlen(address->ifname))
+ (void)strlcpy(rio.rule.ifname, address->ifname,
+ sizeof(rio.rule.ifname));
+
+ if (address->ss.ss_family == AF_INET) {
+ sain = (struct sockaddr_in *)&address->ss;
+
+ rio.rule.dst.addr.v.a.addr.addr32[0] =
+ sain->sin_addr.s_addr;
+ rio.rule.dst.addr.v.a.mask.addr32[0] = 0xffffffff;
+
+ } else {
+ sain6 = (struct sockaddr_in6 *)&address->ss;
+
+ memcpy(&rio.rule.dst.addr.v.a.addr.v6,
+ &sain6->sin6_addr.s6_addr,
+ sizeof(sain6->sin6_addr.s6_addr));
+ memset(&rio.rule.dst.addr.v.a.mask.addr8, 0xff, 16);
+ }
+
+ pio.addr.addr.type = PF_ADDR_TABLE;
+ (void)strlcpy(pio.addr.addr.v.tblname, service->name,
+ sizeof(pio.addr.addr.v.tblname));
+ if (ioctl(env->pf->dev, DIOCADDADDR, &pio) == -1)
+ fatal("sync_ruleset: cannot add address to pool");
+
+ rio.rule.rpool.proxy_port[0] = service->table->port;
+ rio.rule.rpool.port_op = PF_OP_EQ;
+ rio.rule.rpool.opts = PF_POOL_ROUNDROBIN;
+
+ if (ioctl(env->pf->dev, DIOCADDRULE, &rio) == -1)
+ fatal("cannot add rule");
+ log_debug("sync_ruleset: rule added");
+ }
+ transaction_commit(env);
+}
+
+void
+flush_rulesets(struct hostated *env)
+{
+ struct service *service;
+ char anchor[PF_ANCHOR_NAME_SIZE];
+
+ kill_tables(env);
+ TAILQ_FOREACH(service, &env->services, entry) {
+ strlcpy(anchor, HOSTATED_ANCHOR "/", sizeof(anchor));
+ strlcat(anchor, service->name, sizeof(anchor));
+ transaction_init(env, anchor);
+ transaction_commit(env);
+ }
+ strlcpy(anchor, HOSTATED_ANCHOR, sizeof(anchor));
+ transaction_init(env, anchor);
+ transaction_commit(env);
+ log_debug("flush_rulesets: flushed rules");
+}
diff --git a/usr.sbin/hoststatectl/Makefile b/usr.sbin/hoststatectl/Makefile
new file mode 100644
index 00000000000..cf9a63acd8b
--- /dev/null
+++ b/usr.sbin/hoststatectl/Makefile
@@ -0,0 +1,16 @@
+# $OpenBSD: Makefile,v 1.1 2006/12/16 11:45:07 reyk Exp $
+
+.PATH: ${.CURDIR}/../hostated
+
+PROG= hostatectl
+SRCS= buffer.c imsg.c log.c hostatectl.c parser.c
+
+MAN= hostatectl.8
+
+CFLAGS+= -Wall -Werror -I${.CURDIR} -I${.CURDIR}/../hostated
+CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes
+CFLAGS+= -Wmissing-declarations
+CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual
+CFLAGS+= -Wsign-compare -Wbounded
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/hoststatectl/hoststatectl.8 b/usr.sbin/hoststatectl/hoststatectl.8
new file mode 100644
index 00000000000..0c998053ce6
--- /dev/null
+++ b/usr.sbin/hoststatectl/hoststatectl.8
@@ -0,0 +1,69 @@
+.\" $OpenBSD: hoststatectl.8,v 1.1 2006/12/16 11:45:07 reyk Exp $
+.\"
+.\" Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd November 1, 2006
+.Dt HOSTATECTL 8
+.Os
+.Sh NAME
+.Nm hostatectl
+.Nd control the host status daemon
+.Sh SYNOPSIS
+.Nm
+.Ar command
+.Op Ar arguments ...
+.Sh DESCRIPTION
+The
+.Nm
+program controls the
+.Xr hostated 8
+daemon.
+.Pp
+The following commands are available:
+.Bl -tag -width Ds
+.It Cm show
+.It Cm show summary
+Show status of services, tables and hosts.
+.It Cm service disable id
+Disable a service. If it has
+.Xr pf 4
+redirection rules installed, remove them. Mark the service's main table and -
+if applicable - backup table disabled as well.
+.It Cm service enable id
+Enable a service. Mark the service's main table and - if applicable - backup
+table enabled as well.
+.It Cm table disable id
+Disable a table. Consider all hosts disabled. If it is a
+main table of a service which has a non-empty backup table,
+swap the contents of the
+.Xr pf 4
+table with those of the backup table.
+.It Cm table enable id
+Enable a table. Start doing checks for all hosts that aren't
+individually disabled again.
+.It Cm host disable id
+Disable a host. Treat it as though it were always down.
+.It Cm host enable id
+Enable the host. Start checking its health again.
+.El
+.Sh FILES
+.Bl -tag -width "/var/run/hostated.sockXX" -compact
+.It /var/run/hostated.sock
+Unix-domain socket used for communication with
+.Xr hostated 8 .
+.El
+.Sh SEE ALSO
+.Xr hostated.conf 5 ,
+.Xr hostated 8
diff --git a/usr.sbin/hoststatectl/hoststatectl.c b/usr.sbin/hoststatectl/hoststatectl.c
new file mode 100644
index 00000000000..e0e0999f53d
--- /dev/null
+++ b/usr.sbin/hoststatectl/hoststatectl.c
@@ -0,0 +1,276 @@
+/* $OpenBSD: hoststatectl.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org>
+ * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2003 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/socket.h>
+#include <sys/queue.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <event.h>
+
+#include "hostated.h"
+#include "parser.h"
+
+__dead void usage(void);
+int show_summary_msg(struct imsg *);
+int show_command_output(struct imsg *);
+char *print_service_status(int);
+char *print_host_status(int, int);
+char *print_table_status(int, int);
+
+struct imsgbuf *ibuf;
+
+__dead void
+usage(void)
+{
+ extern char *__progname;
+
+ fprintf(stderr, "usage: %s <command> [arg [...]]\n", __progname);
+ exit(1);
+}
+
+/* dummy function so that ospfctl does not need libevent */
+void
+imsg_event_add(struct imsgbuf *i)
+{
+ /* nothing */
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct sockaddr_un sun;
+ struct parse_result *res;
+ struct imsg imsg;
+ int ctl_sock;
+ int done = 0;
+ int n;
+
+ /* parse options */
+ if ((res = parse(argc - 1, argv + 1)) == NULL)
+ exit(1);
+
+ /* connect to ospfd control socket */
+ if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
+ err(1, "socket");
+
+ bzero(&sun, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ strlcpy(sun.sun_path, HOSTATED_SOCKET, sizeof(sun.sun_path));
+ if (connect(ctl_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1)
+ err(1, "connect: %s", HOSTATED_SOCKET);
+
+ if ((ibuf = malloc(sizeof(struct imsgbuf))) == NULL)
+ err(1, NULL);
+ imsg_init(ibuf, ctl_sock, NULL);
+ done = 0;
+
+ /* process user request */
+ switch (res->action) {
+ case NONE:
+ usage();
+ /* not reached */
+ case SHOW_SUM:
+ imsg_compose(ibuf, IMSG_CTL_SHOW_SUM, 0, 0, NULL, 0);
+ printf("type\t\%4s\t%-16s\tstatus\n\n", "id", "name");
+ break;
+ case SERV_ENABLE:
+ imsg_compose(ibuf, IMSG_CTL_SERVICE_ENABLE, 0, 0,
+ &res->id, sizeof(res->id));
+ break;
+ case SERV_DISABLE:
+ imsg_compose(ibuf, IMSG_CTL_SERVICE_DISABLE, 0, 0,
+ &res->id, sizeof(res->id));
+ break;
+ case TABLE_ENABLE:
+ imsg_compose(ibuf, IMSG_CTL_TABLE_ENABLE, 0, 0,
+ &res->id, sizeof(res->id));
+ break;
+ case TABLE_DISABLE:
+ imsg_compose(ibuf, IMSG_CTL_TABLE_DISABLE, 0, 0,
+ &res->id, sizeof(res->id));
+ break;
+ case HOST_ENABLE:
+ imsg_compose(ibuf, IMSG_CTL_HOST_ENABLE, 0, 0,
+ &res->id, sizeof(res->id));
+ break;
+ case HOST_DISABLE:
+ imsg_compose(ibuf, IMSG_CTL_HOST_DISABLE, 0, 0,
+ &res->id, sizeof(res->id));
+ break;
+ case SHUTDOWN:
+ imsg_compose(ibuf, IMSG_CTL_SHUTDOWN, 0, 0, NULL, 0);
+ break;
+ case RELOAD:
+ imsg_compose(ibuf, IMSG_CTL_RELOAD, 0, 0, NULL, 0);
+ break;
+ }
+
+ while (ibuf->w.queued)
+ if (msgbuf_write(&ibuf->w) < 0)
+ err(1, "write error");
+
+ while (!done) {
+ if ((n = imsg_read(ibuf)) == -1)
+ errx(1, "imsg_read error");
+ if (n == 0)
+ errx(1, "pipe closed");
+
+ while (!done) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ errx(1, "imsg_get error");
+ if (n == 0)
+ break;
+ switch (res->action) {
+ case SHOW_SUM:
+ done = show_summary_msg(&imsg);
+ break;
+ case SERV_DISABLE:
+ case SERV_ENABLE:
+ case TABLE_DISABLE:
+ case TABLE_ENABLE:
+ case HOST_DISABLE:
+ case HOST_ENABLE:
+ done = show_command_output(&imsg);
+ break;
+ case RELOAD:
+ case SHUTDOWN:
+ case NONE:
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ }
+ close(ctl_sock);
+ free(ibuf);
+
+ return (0);
+}
+
+int
+show_summary_msg(struct imsg *imsg)
+{
+ struct service *service;
+ struct table *table;
+ struct host *host;
+
+ switch (imsg->hdr.type) {
+ case IMSG_CTL_SERVICE:
+ service = imsg->data;
+ printf("service\t%4u\t%-16s\t%s\n",
+ service->id, service->name,
+ print_service_status(service->flags));
+ break;
+ case IMSG_CTL_TABLE:
+ table = imsg->data;
+ printf("table\t%4u\t%-16s\t%s",
+ table->id, table->name,
+ print_table_status(table->up, table->flags));
+ printf("\n");
+ break;
+ case IMSG_CTL_HOST:
+ host = imsg->data;
+ printf("host\t%4u\t%-16s\t%s\n",
+ host->id, host->name,
+ print_host_status(host->up, host->flags));
+ break;
+ case IMSG_CTL_END:
+ return (1);
+ default:
+ errx(1, "wrong message in summary: %u", imsg->hdr.type);
+ break;
+ }
+ return (0);
+}
+
+int
+show_command_output(struct imsg *imsg)
+{
+ switch (imsg->hdr.type) {
+ case IMSG_CTL_OK:
+ printf("command succeeded\n");
+ break;
+ case IMSG_CTL_FAIL:
+ printf("command failed\n");
+ break;
+ default:
+ errx(1, "wrong message in summary: %u", imsg->hdr.type);
+ }
+ return (1);
+}
+
+char *
+print_service_status(int flags)
+{
+ if (flags & F_DISABLE) {
+ return("disabled");
+ } else if (flags & F_DOWN) {
+ return("down");
+ } else if (flags & F_BACKUP) {
+ return("active (using backup table)");
+ } else
+ return("active");
+}
+
+char *
+print_table_status(int up, int fl)
+{
+ static char buf[1024];
+
+ bzero(buf, sizeof(buf));
+
+ if (fl & F_DISABLE) {
+ snprintf(buf, sizeof(buf) - 1, "disabled");
+ } else if (!up) {
+ snprintf(buf, sizeof(buf) - 1, "empty");
+ } else
+ snprintf(buf, sizeof(buf) - 1, "active (%d hosts up)",
+ up);
+ return (buf);
+}
+
+char *
+print_host_status(int status, int fl)
+{
+ if (fl & F_DISABLE)
+ return("disabled");
+
+ switch (status) {
+ case HOST_DOWN:
+ return("down");
+ case HOST_UNKNOWN:
+ return("unknown");
+ case HOST_UP:
+ return("up");
+ default:
+ errx(1, "invalid status: %d", status);
+ }
+}
diff --git a/usr.sbin/hoststatectl/parser.c b/usr.sbin/hoststatectl/parser.c
new file mode 100644
index 00000000000..a2247a907fc
--- /dev/null
+++ b/usr.sbin/hoststatectl/parser.c
@@ -0,0 +1,232 @@
+/* $OpenBSD: parser.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.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/socket.h>
+#include <sys/queue.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <event.h>
+
+#include "hostated.h"
+
+#include "parser.h"
+
+enum token_type {
+ NOTOKEN,
+ ENDTOKEN,
+ HOSTID,
+ TABLEID,
+ SERVICEID,
+ KEYWORD
+};
+
+struct token {
+ enum token_type type;
+ const char *keyword;
+ int value;
+ const struct token *next;
+};
+
+static const struct token t_main[];
+static const struct token t_show[];
+static const struct token t_service[];
+static const struct token t_table[];
+static const struct token t_host[];
+static const struct token t_service_id[];
+static const struct token t_table_id[];
+static const struct token t_host_id[];
+
+static const struct token t_main[] = {
+ {KEYWORD, "show", SHOW_SUM, NULL},
+ {KEYWORD, "stop", SHUTDOWN, NULL},
+ {KEYWORD, "service", NULL, t_service},
+ {KEYWORD, "table", NULL, t_table},
+ {KEYWORD, "host", NULL, t_host},
+ {ENDTOKEN, "", NONE, NULL}
+};
+
+static const struct token t_service[] = {
+ {NOTOKEN, "", NONE, NULL},
+ {KEYWORD, "disable", SERV_DISABLE, t_service_id},
+ {KEYWORD, "enable", SERV_ENABLE, t_service_id},
+ {ENDTOKEN, "", NONE, NULL}
+};
+
+static const struct token t_table[] = {
+ {NOTOKEN, "", NONE, NULL},
+ {KEYWORD, "disable", TABLE_DISABLE, t_table_id},
+ {KEYWORD, "enable", TABLE_ENABLE, t_table_id},
+ {ENDTOKEN, "", NONE, NULL}
+};
+
+static const struct token t_host[] = {
+ {NOTOKEN, "", NONE, NULL},
+ {KEYWORD, "disable", HOST_DISABLE, t_host_id},
+ {KEYWORD, "enable", HOST_ENABLE, t_host_id},
+ {ENDTOKEN, "", NONE, NULL}
+};
+
+static const struct token t_service_id[] = {
+ {SERVICEID, "", NONE, NULL},
+ {ENDTOKEN, "", NONE, NULL}
+};
+
+static const struct token t_table_id[] = {
+ {TABLEID, "", NONE, NULL},
+ {ENDTOKEN, "", NONE, NULL}
+};
+
+static const struct token t_host_id[] = {
+ {HOSTID, "", NONE, NULL},
+ {ENDTOKEN, "", NONE, NULL}
+};
+
+static struct parse_result res;
+
+struct parse_result *
+parse(int argc, char *argv[])
+{
+ const struct token *table = t_main;
+ const struct token *match;
+
+ bzero(&res, sizeof(res));
+
+ while (argc > 0) {
+ if ((match = match_token(argv[0], table)) == NULL) {
+ fprintf(stderr, "valid commands/args:\n");
+ show_valid_args(table);
+ return (NULL);
+ }
+
+ argc--;
+ argv++;
+
+ if (match->type == NOTOKEN || match->next == NULL)
+ break;
+
+ table = match->next;
+ }
+
+ if (argc > 0) {
+ fprintf(stderr, "superfluous argument: %s\n", argv[0]);
+ return (NULL);
+ }
+
+ return (&res);
+}
+
+const struct token *
+match_token(const char *word, const struct token table[])
+{
+ u_int i, match;
+ const struct token *t = NULL;
+ const char *errstr;
+
+ match = 0;
+
+ for (i = 0; table[i].type != ENDTOKEN; i++) {
+ switch (table[i].type) {
+ case NOTOKEN:
+ if (word == NULL || strlen(word) == 0) {
+ match++;
+ t = &table[i];
+ }
+ break;
+ case KEYWORD:
+ if (word != NULL && strncmp(word, table[i].keyword,
+ strlen(word)) == 0) {
+ match++;
+ t = &table[i];
+ if (t->value)
+ res.action = t->value;
+ }
+ break;
+ case HOSTID:
+ res.id = strtonum(word, 0, UINT_MAX, &errstr);
+ if (errstr)
+ errx(1, "host id %s is %s", word, errstr);
+ t = &table[i];
+ match++;
+ break;
+ case TABLEID:
+ res.id = strtonum(word, 0, UINT_MAX, &errstr);
+ if (errstr)
+ errx(1, "table id %s is %s", word, errstr);
+ t = &table[i];
+ match++;
+ break;
+ case SERVICEID:
+ res.id = strtonum(word, 0, UINT_MAX, &errstr);
+ if (errstr)
+ errx(1, "service id %s is %s", word, errstr);
+ t = &table[i];
+ match++;
+ break;
+ case ENDTOKEN:
+ break;
+ }
+ }
+
+ if (match != 1) {
+ if (match > 1)
+ fprintf(stderr, "ambiguous argument: %s\n", word);
+ if (match < 1)
+ fprintf(stderr, "unknown argument: %s\n", word);
+ return (NULL);
+ }
+
+ return (t);
+}
+
+void
+show_valid_args(const struct token table[])
+{
+ int i;
+
+ for (i = 0; table[i].type != ENDTOKEN; i++) {
+ switch (table[i].type) {
+ case NOTOKEN:
+ fprintf(stderr, " <cr>\n");
+ break;
+ case KEYWORD:
+ fprintf(stderr, " %s\n", table[i].keyword);
+ break;
+ case SERVICEID:
+ fprintf(stderr, " <serviceid>\n");
+ break;
+ case TABLEID:
+ fprintf(stderr, " <tableid>\n");
+ break;
+ case HOSTID:
+ fprintf(stderr, " <hostid>\n");
+ break;
+ case ENDTOKEN:
+ break;
+ }
+ }
+}
diff --git a/usr.sbin/hoststatectl/parser.h b/usr.sbin/hoststatectl/parser.h
new file mode 100644
index 00000000000..2fef7464faf
--- /dev/null
+++ b/usr.sbin/hoststatectl/parser.h
@@ -0,0 +1,39 @@
+/* $OpenBSD: parser.h,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.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 actions {
+ NONE,
+ SHOW_SUM,
+ SERV_DISABLE,
+ SERV_ENABLE,
+ TABLE_DISABLE,
+ TABLE_ENABLE,
+ HOST_DISABLE,
+ HOST_ENABLE,
+ SHUTDOWN,
+ RELOAD
+};
+
+struct parse_result {
+ objid_t id;
+ enum actions action;
+};
+
+struct parse_result *parse(int, char *[]);
+const struct token *match_token(const char *, const struct token []);
+void show_valid_args(const struct token []);
diff --git a/usr.sbin/hoststated/Makefile b/usr.sbin/hoststated/Makefile
new file mode 100644
index 00000000000..c04e70f3036
--- /dev/null
+++ b/usr.sbin/hoststated/Makefile
@@ -0,0 +1,17 @@
+# $OpenBSD: Makefile,v 1.1 2006/12/16 11:45:07 reyk Exp $
+
+PROG= hostated
+SRCS= parse.y log.c control.c buffer.c imsg.c hostated.c \
+ pfe.c pfe_filter.c hce.c check_icmp.c check_tcp.c check_http.c
+MAN= hostated.8 hostated.conf.5
+
+LDADD= -levent
+DPADD= ${LIBEVENT}
+CFLAGS+= -Wall -Werror -I${.CURDIR}
+CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes
+CFLAGS+= -Wmissing-declarations
+CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual
+CFLAGS+= -Wsign-compare -Wbounded
+CLEANFILES+= y.tab.h
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/hoststated/buffer.c b/usr.sbin/hoststated/buffer.c
new file mode 100644
index 00000000000..3abe27b1e62
--- /dev/null
+++ b/usr.sbin/hoststated/buffer.c
@@ -0,0 +1,222 @@
+/* $OpenBSD: buffer.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * 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/uio.h>
+#include <sys/param.h>
+#include <net/if.h>
+#include <errno.h>
+#include <event.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "hostated.h"
+
+int buf_realloc(struct buf *, size_t);
+void buf_enqueue(struct msgbuf *, struct buf *);
+void buf_dequeue(struct msgbuf *, struct buf *);
+
+struct buf *
+buf_open(size_t len)
+{
+ struct buf *buf;
+
+ if ((buf = calloc(1, sizeof(struct buf))) == NULL)
+ return (NULL);
+ if ((buf->buf = malloc(len)) == NULL) {
+ free(buf);
+ return (NULL);
+ }
+ buf->size = buf->max = len;
+
+ return (buf);
+}
+
+struct buf *
+buf_dynamic(size_t len, size_t max)
+{
+ struct buf *buf;
+
+ if (max < len)
+ return (NULL);
+
+ if ((buf = buf_open(len)) == NULL)
+ return (NULL);
+
+ if (max > 0)
+ buf->max = max;
+
+ return (buf);
+}
+
+int
+buf_realloc(struct buf *buf, size_t len)
+{
+ u_char *b;
+
+ /* on static buffers max is eq size and so the following fails */
+ if (buf->wpos + len > buf->max) {
+ errno = ENOMEM;
+ return (-1);
+ }
+
+ b = realloc(buf->buf, buf->wpos + len);
+ if (b == NULL)
+ return (-1);
+ buf->buf = b;
+ buf->size = buf->wpos + len;
+
+ return (0);
+}
+
+int
+buf_add(struct buf *buf, void *data, size_t len)
+{
+ if (buf->wpos + len > buf->size)
+ if (buf_realloc(buf, len) == -1)
+ return (-1);
+
+ memcpy(buf->buf + buf->wpos, data, len);
+ buf->wpos += len;
+ return (0);
+}
+
+void *
+buf_reserve(struct buf *buf, size_t len)
+{
+ void *b;
+
+ if (buf->wpos + len > buf->size)
+ if (buf_realloc(buf, len) == -1)
+ return (NULL);
+
+ b = buf->buf + buf->wpos;
+ buf->wpos += len;
+ return (b);
+}
+
+void *
+buf_seek(struct buf *buf, size_t pos, size_t len)
+{
+ /* only allowed to seek in already written parts */
+ if (pos + len > buf->wpos)
+ return (NULL);
+
+ return (buf->buf + pos);
+}
+
+int
+buf_close(struct msgbuf *msgbuf, struct buf *buf)
+{
+ buf_enqueue(msgbuf, buf);
+ return (1);
+}
+
+void
+buf_free(struct buf *buf)
+{
+ free(buf->buf);
+ free(buf);
+}
+
+void
+msgbuf_init(struct msgbuf *msgbuf)
+{
+ msgbuf->queued = 0;
+ msgbuf->fd = -1;
+ TAILQ_INIT(&msgbuf->bufs);
+}
+
+void
+msgbuf_clear(struct msgbuf *msgbuf)
+{
+ struct buf *buf;
+
+ while ((buf = TAILQ_FIRST(&msgbuf->bufs)) != NULL)
+ buf_dequeue(msgbuf, buf);
+}
+
+int
+msgbuf_write(struct msgbuf *msgbuf)
+{
+ struct iovec iov[IOV_MAX];
+ struct buf *buf, *next;
+ int i = 0;
+ ssize_t n;
+ struct msghdr msg;
+
+ bzero(&iov, sizeof(iov));
+ bzero(&msg, sizeof(msg));
+ TAILQ_FOREACH(buf, &msgbuf->bufs, entry) {
+ if (i >= IOV_MAX)
+ break;
+ iov[i].iov_base = buf->buf + buf->rpos;
+ iov[i].iov_len = buf->size - buf->rpos;
+ i++;
+ }
+
+ msg.msg_iov = iov;
+ msg.msg_iovlen = i;
+
+ if ((n = sendmsg(msgbuf->fd, &msg, 0)) == -1) {
+ if (errno == EAGAIN || errno == ENOBUFS ||
+ errno == EINTR) /* try later */
+ return (0);
+ else
+ return (-1);
+ }
+
+ if (n == 0) { /* connection closed */
+ errno = 0;
+ return (-2);
+ }
+
+ for (buf = TAILQ_FIRST(&msgbuf->bufs); buf != NULL && n > 0;
+ buf = next) {
+ next = TAILQ_NEXT(buf, entry);
+ if (buf->rpos + n >= buf->size) {
+ n -= buf->size - buf->rpos;
+ buf_dequeue(msgbuf, buf);
+ } else {
+ buf->rpos += n;
+ n = 0;
+ }
+ }
+
+ return (0);
+}
+
+void
+buf_enqueue(struct msgbuf *msgbuf, struct buf *buf)
+{
+ TAILQ_INSERT_TAIL(&msgbuf->bufs, buf, entry);
+ msgbuf->queued++;
+}
+
+void
+buf_dequeue(struct msgbuf *msgbuf, struct buf *buf)
+{
+ TAILQ_REMOVE(&msgbuf->bufs, buf, entry);
+ msgbuf->queued--;
+ buf_free(buf);
+}
diff --git a/usr.sbin/hoststated/check_http.c b/usr.sbin/hoststated/check_http.c
new file mode 100644
index 00000000000..3d39ceebe27
--- /dev/null
+++ b/usr.sbin/hoststated/check_http.c
@@ -0,0 +1,144 @@
+/* $OpenBSD: check_http.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.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/param.h>
+#include <net/if.h>
+#include <sha1.h>
+#include <limits.h>
+#include <event.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "hostated.h"
+
+struct buf *http_request(struct host *, struct table *, int, const char *);
+
+struct buf *
+http_request(struct host *host, struct table *table, int s, const char *req)
+{
+ int fl;
+ ssize_t sz;
+ char rbuf[1024];
+ struct buf *buf;
+
+ if ((fl = fcntl(s, F_GETFL, 0)) == -1)
+ fatal("http_request: cannot get flags for socket");
+ if (fcntl(s, F_SETFL, fl & ~(O_NONBLOCK)) == -1)
+ fatal("http_request: cannot set blocking socket");
+ if ((buf = buf_dynamic(sizeof(rbuf), UINT_MAX)) == NULL)
+ fatalx("http_request: cannot create dynamic buffer");
+
+ if (write(s, req, strlen(req)) != (ssize_t) strlen(req)) {
+ close(s);
+ return (NULL);
+ }
+ for (; (sz = read(s, rbuf, sizeof(rbuf))) != 0; ) {
+ if (sz == -1)
+ fatal("http_request: read");
+ if (buf_add(buf, rbuf, sz) == -1)
+ fatal("http_request: buf_add");
+ }
+ return (buf);
+}
+
+int
+check_http_code(struct host *host, struct table *table)
+{
+ int s;
+ int code;
+ char scode[4];
+ char *req;
+ char *head;
+ const char *estr;
+ struct buf *buf;
+
+ if ((s = tcp_connect(host, table)) <= 0)
+ return (s);
+
+ asprintf(&req, "HEAD %s HTTP/1.0\r\n\r\n", table->path);
+ if ((buf = http_request(host, table, s, req)) == NULL)
+ return (HOST_UNKNOWN);
+ free(req);
+
+ head = buf->buf;
+ if (strncmp(head, "HTTP/1.1 ", strlen("HTTP/1.1 ")) &&
+ strncmp(head, "HTTP/1.0 ", strlen("HTTP/1.0 "))) {
+ log_debug("check_http_code: cannot parse HTTP version");
+ close(s);
+ return (HOST_DOWN);
+ }
+ head += strlen("HTTP/1.1 ");
+ if (strlen(head) < 5) /* code + \r\n */
+ return (HOST_DOWN);
+ strlcpy(scode, head, sizeof(scode));
+ code = strtonum(scode, 100, 999, &estr);
+ if (estr != NULL) {
+ log_debug("check_http_code: cannot parse HTTP code");
+ close(s);
+ return (HOST_DOWN);
+ }
+ if (code != table->retcode) {
+ log_debug("check_http_code: invalid HTTP code returned");
+ close(s);
+ return (HOST_DOWN);
+ }
+ close(s);
+ return (HOST_UP);
+}
+
+int
+check_http_digest(struct host *host, struct table *table)
+{
+ int s;
+ char *head;
+ char *req;
+ struct buf *buf;
+ char digest[(SHA1_DIGEST_LENGTH*2)+1];
+
+ if ((s = tcp_connect(host, table)) <= 0)
+ return (s);
+
+ asprintf(&req, "GET %s HTTP/1.0\r\n\r\n", table->path);
+ if ((buf = http_request(host, table, s, req)) == NULL)
+ return (HOST_UNKNOWN);
+ free(req);
+
+ head = buf->buf;
+ if ((head = strstr(head, "\r\n\r\n")) == NULL) {
+ log_debug("check_http_digest: host %u no end of headers",
+ host->id);
+ close(s);
+ return (HOST_DOWN);
+ }
+ head += strlen("\r\n\r\n");
+ SHA1Data(head, strlen(head), digest);
+ close(s);
+ buf_free(buf);
+
+ if (strcmp(table->digest, digest)) {
+ log_warnx("check_http_digest: wrong digest for host %u",
+ host->id);
+ return(HOST_DOWN);
+ }
+ return (HOST_UP);
+}
diff --git a/usr.sbin/hoststated/check_icmp.c b/usr.sbin/hoststated/check_icmp.c
new file mode 100644
index 00000000000..df92b444af9
--- /dev/null
+++ b/usr.sbin/hoststated/check_icmp.c
@@ -0,0 +1,212 @@
+/* $OpenBSD: check_icmp.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.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/param.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/icmp6.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+#include <limits.h>
+#include <event.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "hostated.h"
+
+int check_icmp6(struct host *, int, int);
+int check_icmp4(struct host *, int, int);
+int in_cksum(u_short *, int);
+
+int check_icmp(struct host *host, int s, int s6, int timeout)
+{
+ if (host->ss.ss_family == AF_INET)
+ return (check_icmp4(host, s, timeout));
+ else
+ return (check_icmp6(host, s6, timeout));
+}
+
+int check_icmp6(struct host *host, int s, int timeout)
+{
+ struct sockaddr *to;
+ struct icmp6_hdr *icp;
+ int ident;
+ ssize_t i;
+ int cc;
+ int datalen = (64 - 8);
+ u_char packet[datalen];
+ fd_set fdset;
+ socklen_t len;
+ struct timeval tv;
+
+ to = (struct sockaddr *)&host->ss;
+ ident = getpid() & 0xFFFF;
+ len = sizeof(struct sockaddr_in6);
+
+ bzero(&packet, sizeof(packet));
+ icp = (struct icmp6_hdr *)packet;
+ icp->icmp6_type = ICMP6_ECHO_REQUEST;
+ icp->icmp6_code = 0;
+ icp->icmp6_seq = 1;
+ icp->icmp6_id = ident;
+
+ memset((packet + sizeof(*icp)), 'X', datalen);
+ cc = datalen + 8;
+
+ i = sendto(s, packet, cc, 0, to, len);
+
+ if (i < 0 || i != cc) {
+ log_warn("check_icmp6: cannot send ping");
+ return (HOST_UNKNOWN);
+ }
+
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = timeout % 1000;
+ FD_ZERO(&fdset);
+ FD_SET(s, &fdset);
+ switch (select(s + 1, &fdset, NULL, NULL, &tv)) {
+ case -1:
+ if (errno == EINTR) {
+ log_warnx("check_icmp6: interrupted");
+ return (HOST_UNKNOWN);
+ } else
+ fatal("check_icmp6: select");
+ case 0:
+ log_debug("check_icmp6: timeout");
+ return (HOST_DOWN);
+ default:
+ bzero(&packet, sizeof(packet));
+ i = recvfrom(s, packet, cc, 0, to, &len);
+ if (i < 0 || i != cc) {
+ log_warn("check_icmp6: did not receive valid ping");
+ return (HOST_DOWN);
+ }
+ icp = (struct icmp6_hdr *)(packet);
+ if (icp->icmp6_id != ident) {
+ log_warnx("check_icmp6: did not receive valid ident");
+ return (HOST_DOWN);
+ }
+ break;
+ }
+ return (HOST_UP);
+}
+
+int check_icmp4(struct host *host, int s, int timeout)
+{
+ struct sockaddr *to;
+ struct icmp *icp;
+ int ident;
+ ssize_t i;
+ int cc;
+ int datalen = (64 - 8);
+ u_char packet[datalen];
+ fd_set fdset;
+ socklen_t len;
+ struct timeval tv;
+
+ to = (struct sockaddr *)&host->ss;
+ ident = getpid() & 0xFFFF;
+ len = sizeof(struct sockaddr_in);
+
+ bzero(&packet, sizeof(packet));
+ icp = (struct icmp *)packet;
+ icp->icmp_type = htons(ICMP_ECHO);
+ icp->icmp_code = 0;
+ icp->icmp_seq = htons(1);
+ icp->icmp_id = htons(ident);
+ icp->icmp_cksum = 0;
+
+ memset(icp->icmp_data, 'X', datalen);
+ cc = datalen + 8;
+ icp->icmp_cksum = in_cksum((u_short *)icp, cc);
+
+ i = sendto(s, packet, cc, 0, to, len);
+
+ if (i < 0 || i != cc) {
+ log_warn("check_icmp4: cannot send ping");
+ return (HOST_UNKNOWN);
+ }
+
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = timeout % 1000;
+ FD_ZERO(&fdset);
+ FD_SET(s, &fdset);
+ switch (select(s + 1, &fdset, NULL, NULL, &tv)) {
+ case -1:
+ if (errno == EINTR) {
+ log_warnx("check_icmp4: ping interrupted");
+ return (HOST_UNKNOWN);
+ } else
+ fatal("check_icmp4: select");
+ case 0:
+ log_debug("check_icmp4: timeout");
+ return (HOST_DOWN);
+ default:
+ bzero(&packet, sizeof(packet));
+ i = recvfrom(s, packet, cc, 0, to, &len);
+ if (i < 0 || i != cc) {
+ log_warn("check_icmp4: did not receive valid ping");
+ return (HOST_DOWN);
+ }
+ icp = (struct icmp *)(packet + sizeof(struct ip));
+ if (ntohs(icp->icmp_id) != ident) {
+ log_warnx("check_icmp4: did not receive valid ident");
+ return (HOST_DOWN);
+ }
+ break;
+ }
+ return (HOST_UP);
+}
+
+/* from ping.c */
+int
+in_cksum(u_short *addr, int len)
+{
+ int nleft = len;
+ u_short *w = addr;
+ int sum = 0;
+ u_short answer = 0;
+
+ /*
+ * Our algorithm is simple, using a 32 bit accumulator (sum), we add
+ * sequential 16 bit words to it, and at the end, fold back all the
+ * carry bits from the top 16 bits into the lower 16 bits.
+ */
+ while (nleft > 1) {
+ sum += *w++;
+ nleft -= 2;
+ }
+
+ /* mop up an odd byte, if necessary */
+ if (nleft == 1) {
+ *(u_char *)(&answer) = *(u_char *)w ;
+ sum += answer;
+ }
+
+ /* add back carry outs from top 16 bits to low 16 bits */
+ sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
+ sum += (sum >> 16); /* add carry */
+ answer = ~sum; /* truncate to 16 bits */
+ return(answer);
+}
diff --git a/usr.sbin/hoststated/check_tcp.c b/usr.sbin/hoststated/check_tcp.c
new file mode 100644
index 00000000000..5ef386731f9
--- /dev/null
+++ b/usr.sbin/hoststated/check_tcp.c
@@ -0,0 +1,109 @@
+/* $OpenBSD: check_tcp.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.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/param.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <limits.h>
+#include <event.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include "hostated.h"
+
+int
+check_tcp(struct host *host, struct table *table)
+{
+ int sock;
+
+ if ((sock = tcp_connect(host, table)) <= 0)
+ return (sock);
+ close(sock);
+ return (HOST_UP);
+}
+
+int
+tcp_connect(struct host *host, struct table *table)
+{
+ int s;
+ socklen_t len;
+ struct timeval tv;
+ struct sockaddr sa;
+ fd_set fdset;
+
+ switch (host->ss.ss_family) {
+ case AF_INET:
+ ((struct sockaddr_in *)&host->ss)->sin_port =
+ htons(table->port);
+ break;
+ case AF_INET6:
+ ((struct sockaddr_in6 *)&host->ss)->sin6_port =
+ htons(table->port);
+ break;
+ }
+
+ len = ((struct sockaddr *)&host->ss)->sa_len;
+
+ if ((s = socket(host->ss.ss_family, SOCK_STREAM, 0)) == -1)
+ fatal("check_tcp: cannot create socket");
+
+ if (fcntl(s, F_SETFL, O_NONBLOCK) == -1)
+ fatal("check_tcp: cannot set non blocking socket");
+
+ if (connect(s, (struct sockaddr *)&host->ss, len) == -1) {
+ if (errno != EINPROGRESS && errno != EWOULDBLOCK) {
+ close(s);
+ return (HOST_DOWN);
+ }
+ } else
+ return (s);
+
+ tv.tv_sec = table->timeout / 1000;
+ tv.tv_usec = table->timeout % 1000;
+ FD_ZERO(&fdset);
+ FD_SET(s, &fdset);
+
+ switch(select(s + 1, NULL, &fdset, NULL, &tv)) {
+ case -1:
+ if (errno != EINTR)
+ fatal("check_tcp: select");
+ else
+ return(HOST_UNKNOWN);
+ case 0:
+ close(s);
+ return (HOST_DOWN);
+ default:
+ if (getpeername(s, &sa, &len) == -1) {
+ if (errno == ENOTCONN) {
+ close(s);
+ return (HOST_DOWN);
+ } else {
+ log_debug("check_tcp: unknown peername");
+ close(s);
+ return (HOST_UNKNOWN);
+ }
+ } else
+ return (s);
+ }
+ return (HOST_UNKNOWN);
+}
diff --git a/usr.sbin/hoststated/control.c b/usr.sbin/hoststated/control.c
new file mode 100644
index 00000000000..2f994e458f4
--- /dev/null
+++ b/usr.sbin/hoststated/control.c
@@ -0,0 +1,340 @@
+/* $OpenBSD: control.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * 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/queue.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <net/if.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include "hostated.h"
+
+#define CONTROL_BACKLOG 5
+
+struct ctl_connlist ctl_conns;
+
+int control_imsg_relay(struct imsg *imsg);
+
+struct ctl_conn *control_connbyfd(int);
+struct ctl_conn *control_connbypid(pid_t);
+void control_close(int);
+
+int
+control_init(void)
+{
+ struct sockaddr_un sun;
+ int fd;
+ mode_t old_umask;
+
+ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+ log_warn("control_init: socket");
+ return (-1);
+ }
+
+ bzero(&sun, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ strlcpy(sun.sun_path, HOSTATED_SOCKET, sizeof(sun.sun_path));
+
+ if (unlink(HOSTATED_SOCKET) == -1)
+ if (errno != ENOENT) {
+ log_warn("control_init: unlink %s", HOSTATED_SOCKET);
+ close(fd);
+ return (-1);
+ }
+
+ old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
+ if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
+ log_warn("control_init: bind: %s", HOSTATED_SOCKET);
+ close(fd);
+ umask(old_umask);
+ return (-1);
+ }
+ umask(old_umask);
+
+ if (chmod(HOSTATED_SOCKET, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) {
+ log_warn("control_init: chmod");
+ close(fd);
+ (void)unlink(HOSTATED_SOCKET);
+ return (-1);
+ }
+
+ session_socket_blockmode(fd, BM_NONBLOCK);
+ control_state.fd = fd;
+
+ return (0);
+}
+
+int
+control_listen(void)
+{
+
+ if (listen(control_state.fd, CONTROL_BACKLOG) == -1) {
+ log_warn("control_listen: listen");
+ return (-1);
+ }
+
+ event_set(&control_state.ev, control_state.fd, EV_READ | EV_PERSIST,
+ control_accept, NULL);
+ event_add(&control_state.ev, NULL);
+
+ return (0);
+}
+
+void
+control_cleanup(void)
+{
+
+ unlink(HOSTATED_SOCKET);
+}
+
+/* ARGSUSED */
+void
+control_accept(int listenfd, short event, void *arg)
+{
+ int connfd;
+ socklen_t len;
+ struct sockaddr_un sun;
+ struct ctl_conn *c;
+
+ len = sizeof(sun);
+ if ((connfd = accept(listenfd,
+ (struct sockaddr *)&sun, &len)) == -1) {
+ if (errno != EWOULDBLOCK && errno != EINTR)
+ log_warn("control_accept");
+ return;
+ }
+
+ session_socket_blockmode(connfd, BM_NONBLOCK);
+
+ if ((c = malloc(sizeof(struct ctl_conn))) == NULL) {
+ log_warn("control_accept");
+ return;
+ }
+
+ imsg_init(&c->ibuf, connfd, control_dispatch_imsg);
+ c->ibuf.events = EV_READ;
+ event_set(&c->ibuf.ev, c->ibuf.fd, c->ibuf.events,
+ c->ibuf.handler, &c->ibuf);
+ event_add(&c->ibuf.ev, NULL);
+
+ TAILQ_INSERT_TAIL(&ctl_conns, c, entry);
+}
+
+struct ctl_conn *
+control_connbyfd(int fd)
+{
+ struct ctl_conn *c;
+
+ for (c = TAILQ_FIRST(&ctl_conns); c != NULL && c->ibuf.fd != fd;
+ c = TAILQ_NEXT(c, entry))
+ ; /* nothing */
+
+ return (c);
+}
+
+struct ctl_conn *
+control_connbypid(pid_t pid)
+{
+ struct ctl_conn *c;
+
+ for (c = TAILQ_FIRST(&ctl_conns); c != NULL && c->ibuf.pid != pid;
+ c = TAILQ_NEXT(c, entry))
+ ; /* nothing */
+
+ return (c);
+}
+
+void
+control_close(int fd)
+{
+ struct ctl_conn *c;
+
+ if ((c = control_connbyfd(fd)) == NULL)
+ log_warn("control_close: fd %d: not found", fd);
+
+ msgbuf_clear(&c->ibuf.w);
+ TAILQ_REMOVE(&ctl_conns, c, entry);
+
+ event_del(&c->ibuf.ev);
+ close(c->ibuf.fd);
+ free(c);
+}
+
+/* ARGSUSED */
+void
+control_dispatch_imsg(int fd, short event, void *arg)
+{
+ struct ctl_conn *c;
+ struct imsg imsg;
+ objid_t id;
+ int n;
+
+ if ((c = control_connbyfd(fd)) == NULL) {
+ log_warn("control_dispatch_imsg: fd %d: not found", fd);
+ return;
+ }
+
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(&c->ibuf)) <= 0) {
+ control_close(fd);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&c->ibuf.w) < 0) {
+ control_close(fd);
+ return;
+ }
+ imsg_event_add(&c->ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(&c->ibuf, &imsg)) == -1) {
+ control_close(fd);
+ return;
+ }
+
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_CTL_SHOW_SUM:
+ show(c);
+ break;
+ case IMSG_CTL_SERVICE_DISABLE:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id))
+ fatalx("invalid imsg header len");
+ memcpy(&id, imsg.data, sizeof(id));
+ if (disable_service(c, id))
+ imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0,
+ NULL, 0);
+ else
+ imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0,
+ NULL, 0);
+ break;
+ case IMSG_CTL_SERVICE_ENABLE:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id))
+ fatalx("invalid imsg header len");
+ memcpy(&id, imsg.data, sizeof(id));
+ if (enable_service(c, id))
+ imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0,
+ NULL, 0);
+ else
+ imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0,
+ NULL, 0);
+ break;
+ case IMSG_CTL_TABLE_DISABLE:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id))
+ fatalx("invalid imsg header len");
+ memcpy(&id, imsg.data, sizeof(id));
+ if (disable_table(c, id))
+ imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0,
+ NULL, 0);
+ else
+ imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0,
+ NULL, 0);
+ break;
+ case IMSG_CTL_TABLE_ENABLE:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id))
+ fatalx("invalid imsg header len");
+ memcpy(&id, imsg.data, sizeof(id));
+ if (enable_table(c, id))
+ imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0,
+ NULL, 0);
+ else
+ imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0,
+ NULL, 0);
+ break;
+ case IMSG_CTL_HOST_DISABLE:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id))
+ fatalx("invalid imsg header len");
+ memcpy(&id, imsg.data, sizeof(id));
+ if (disable_host(c, id))
+ imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0,
+ NULL, 0);
+ else
+ imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0,
+ NULL, 0);
+ break;
+ case IMSG_CTL_HOST_ENABLE:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id))
+ fatalx("invalid imsg header len");
+ memcpy(&id, imsg.data, sizeof(id));
+ if (enable_host(c, id))
+ imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0,
+ NULL, 0);
+ else
+ imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0,
+ NULL, 0);
+ break;
+ case IMSG_CTL_SHUTDOWN:
+ case IMSG_CTL_RELOAD:
+ imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0, NULL, 0);
+ break;
+ default:
+ log_debug("control_dispatch_imsg: "
+ "error handling imsg %d", imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+
+ imsg_event_add(&c->ibuf);
+}
+
+int
+control_imsg_relay(struct imsg *imsg)
+{
+ struct ctl_conn *c;
+
+ if ((c = control_connbypid(imsg->hdr.pid)) == NULL)
+ return (0);
+
+ return (imsg_compose(&c->ibuf, imsg->hdr.type, 0, imsg->hdr.pid,
+ imsg->data, imsg->hdr.len - IMSG_HEADER_SIZE));
+}
+
+void
+session_socket_blockmode(int fd, enum blockmodes bm)
+{
+ int flags;
+
+ if ((flags = fcntl(fd, F_GETFL, 0)) == -1)
+ fatal("fnctl F_GETFL");
+
+ if (bm == BM_NONBLOCK)
+ flags |= O_NONBLOCK;
+ else
+ flags &= ~O_NONBLOCK;
+
+ if ((flags = fcntl(fd, F_SETFL, flags)) == -1)
+ fatal("fnctl F_SETFL");
+}
diff --git a/usr.sbin/hoststated/hce.c b/usr.sbin/hoststated/hce.c
new file mode 100644
index 00000000000..7144357538b
--- /dev/null
+++ b/usr.sbin/hoststated/hce.c
@@ -0,0 +1,318 @@
+/* $OpenBSD: hce.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/queue.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <net/if.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <err.h>
+#include <pwd.h>
+
+#include "hostated.h"
+
+void hce_sig_handler(int sig, short, void *);
+void hce_shutdown(void);
+void hce_dispatch_imsg(int, short, void *);
+void hce_dispatch_parent(int, short, void *);
+void hce_launch_checks(int, short, void *);
+
+static struct hostated *env = NULL;
+struct imsgbuf *ibuf_pfe;
+struct imsgbuf *ibuf_main;
+
+void
+hce_sig_handler(int sig, short event, void *arg)
+{
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ hce_shutdown();
+ default:
+ fatalx("hce_sig_handler: unexpected signal");
+ }
+}
+
+pid_t
+hce(struct hostated *x_env, int pipe_parent2pfe[2], int pipe_parent2hce[2],
+ int pipe_pfe2hce[2])
+{
+ pid_t pid;
+ struct passwd *pw;
+ struct timeval tv;
+ struct event ev_sigint;
+ struct event ev_sigterm;
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("hce: cannot fork");
+ case 0:
+ break;
+ default:
+ return (pid);
+ }
+
+ env = x_env;
+
+ /* this is needed for icmp tests */
+ if ((env->icmp_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0)
+ err(1, "socket");
+ if ((env->icmp6_sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0)
+ err(1, "socket");
+
+ if ((pw = getpwnam(HOSTATED_USER)) == NULL)
+ fatal("hce: getpwnam");
+
+ if (chroot(pw->pw_dir) == -1)
+ fatal("hce: chroot");
+ if (chdir("/") == -1)
+ fatal("hce: chdir(\"/\")");
+
+ setproctitle("host check engine");
+ hostated_process = PROC_HCE;
+
+ 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("hce: can't drop privileges");
+
+ event_init();
+
+ signal_set(&ev_sigint, SIGINT, hce_sig_handler, NULL);
+ signal_set(&ev_sigterm, SIGTERM, hce_sig_handler, NULL);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+
+ /* setup pipes */
+ close(pipe_pfe2hce[1]);
+ close(pipe_parent2hce[0]);
+ close(pipe_parent2pfe[0]);
+ close(pipe_parent2pfe[1]);
+
+ if ((ibuf_pfe = calloc(1, sizeof(struct imsgbuf))) == NULL ||
+ (ibuf_main = calloc(1, sizeof(struct imsgbuf))) == NULL)
+ fatal("hce");
+ imsg_init(ibuf_pfe, pipe_pfe2hce[0], hce_dispatch_imsg);
+ imsg_init(ibuf_main, pipe_parent2hce[1], hce_dispatch_parent);
+
+ ibuf_pfe->events = EV_READ;
+ event_set(&ibuf_pfe->ev, ibuf_pfe->fd, ibuf_pfe->events,
+ ibuf_pfe->handler, ibuf_pfe);
+ event_add(&ibuf_pfe->ev, NULL);
+
+ ibuf_main->events = EV_READ;
+ event_set(&ibuf_main->ev, ibuf_main->fd, ibuf_main->events,
+ ibuf_main->handler, ibuf_main);
+ event_add(&ibuf_main->ev, NULL);
+
+ evtimer_set(&env->ev, hce_launch_checks, NULL);
+ tv.tv_sec = env->interval;
+ tv.tv_usec = 0;
+ evtimer_add(&env->ev, &tv);
+
+ hce_launch_checks(0, 0, NULL);
+ event_dispatch();
+
+ hce_shutdown();
+
+ return (0);
+}
+
+void
+hce_launch_checks(int fd, short event, void *arg)
+{
+ int previous_up;
+ struct host *host;
+ struct table *table;
+ struct ctl_status st;
+ struct timeval tv;
+
+ tv.tv_sec = env->interval;
+ tv.tv_usec = 0;
+ evtimer_add(&env->ev, &tv);
+ bzero(&st, sizeof(st));
+ TAILQ_FOREACH(table, &env->tables, entry) {
+ if (table->flags & F_DISABLE)
+ continue;
+ TAILQ_FOREACH(host, &table->hosts, entry) {
+ if (host->flags & F_DISABLE)
+ continue;
+ previous_up = host->up;
+ switch (table->check) {
+ case CHECK_ICMP:
+ host->up = check_icmp(host, env->icmp_sock,
+ env->icmp6_sock,
+ table->timeout);
+ break;
+ case CHECK_TCP:
+ host->up = check_tcp(host, table);
+ break;
+ case CHECK_HTTP_CODE:
+ host->up = check_http_code(host, table);
+ break;
+ case CHECK_HTTP_DIGEST:
+ host->up = check_http_digest(host, table);
+ break;
+ default:
+ fatalx("hce_launch_checks: unknown check type");
+ break;
+ }
+ if (host->up != previous_up) {
+ st.id = host->id;
+ st.up = host->up;
+ imsg_compose(ibuf_pfe, IMSG_HOST_STATUS, 0, 0,
+ &st, sizeof(st));
+ }
+ }
+ }
+ /* tell pfe we're finished */
+ imsg_compose(ibuf_pfe, IMSG_SYNC, 0, 0, NULL, 0);
+}
+
+void
+hce_shutdown(void)
+{
+ log_info("host check engine exiting");
+ _exit(0);
+}
+
+void
+hce_dispatch_imsg(int fd, short event, void *ptr)
+{
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+ objid_t id;
+ struct host *host;
+ struct table *table;
+
+ ibuf = ptr;
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("hce_dispatch_imsg: imsg_read_error");
+ if (n == 0)
+ fatalx("hce_dispatch_imsg: pipe closed");
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("hce_dispatch_imsg: msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("hce_dispatch_imsg: unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("hce_dispatch_imsg: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_HOST_DISABLE:
+ memcpy(&id, imsg.data, sizeof(id));
+ if ((host = host_find(env, id)) == NULL)
+ fatalx("hce_dispatch_imsg: desynchronized");
+ host->flags |= F_DISABLE;
+ host->up = HOST_UNKNOWN;
+ break;
+ case IMSG_HOST_ENABLE:
+ memcpy(&id, imsg.data, sizeof(id));
+ if ((host = host_find(env, id)) == NULL)
+ fatalx("hce_dispatch_imsg: desynchronized");
+ host->flags &= ~(F_DISABLE);
+ host->up = HOST_UNKNOWN;
+ break;
+ case IMSG_TABLE_DISABLE:
+ memcpy(&id, imsg.data, sizeof(id));
+ if ((table = table_find(env, id)) == NULL)
+ fatalx("hce_dispatch_imsg: desynchronized");
+ table->flags |= F_DISABLE;
+ TAILQ_FOREACH(host, &table->hosts, entry)
+ host->up = HOST_UNKNOWN;
+ break;
+ case IMSG_TABLE_ENABLE:
+ memcpy(&id, imsg.data, sizeof(id));
+ if ((table = table_find(env, id)) == NULL)
+ fatalx("hce_dispatch_imsg: desynchronized");
+ table->flags &= ~(F_DISABLE);
+ TAILQ_FOREACH(host, &table->hosts, entry)
+ host->up = HOST_UNKNOWN;
+ break;
+ default:
+ log_debug("hce_dispatch_msg: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+hce_dispatch_parent(int fd, short event, void * ptr)
+{
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = ptr;
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("hce_dispatch_parent: imsg_read error");
+ if (n == 0) /* connection closed */
+ fatalx("hce_dispatch_parent: pipe closed");
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("hce_dispatch_parent: msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("hce_dispatch_parent: unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("hce_dispatch_parent: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ log_debug("hce_dispatch_parent: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+}
diff --git a/usr.sbin/hoststated/hoststated.8 b/usr.sbin/hoststated/hoststated.8
new file mode 100644
index 00000000000..6c2ae336645
--- /dev/null
+++ b/usr.sbin/hoststated/hoststated.8
@@ -0,0 +1,94 @@
+.\" $OpenBSD: hoststated.8,v 1.1 2006/12/16 11:45:07 reyk Exp $
+.\"
+.\" Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd November 1, 2006
+.Dt HOSTATED 8
+.Os
+.Sh NAME
+.Nm hostated
+.Nd Host Status daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl dnv
+.Op Fl f Ar file
+.Sh DESCRIPTION
+.Nm
+is the host status daemon for server load balancing.
+Its main purpose is to maintain pf tables up to date
+as well as related pf rdr rules.
+To communicate with
+.Xr pf 4
+.Nm
+uses the anchor facility. To enable
+.Nm
+to install rulesets through the anchor you will
+need the following line in the NAT section of your
+.Xr pf.conf 5
+configuration file:
+.Bd -literal -offset 2n
+rdr-anchor "hostated/*"
+.Ed
+.Pp
+.Nm
+manipulates three data types: services, tables and hosts.
+Each service represents a
+.Xr pf 4
+rdr rule. A service contains at least one table and one virtual ip which
+are used to create the proper rule.
+Each table contains at least one host, and is mapped to a
+.Xr pf 4
+table. Additionnaly, a table can be backed up i.e its content will be swapped
+by the content of another table when it is empty. This can be used to serve
+static content when a dynamic service goes down.
+See
+.Xr hostated.conf 5
+for a more detailed explanation of how to configure
+.Nm
+.
+.Pp
+.Xr hostatectl 8
+can be used to enable or disable hosts, tables and services as well
+as showing the current status of each object.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl d
+Do not daemonize.
+If this options is specified,
+.Nm
+will run in the foreground and log to
+.Em stderr .
+.It Fl f Ar file
+Specify an alternative configurate file.
+.It Fl n
+Configtest mode.
+Only check the configuration file for validity.
+.It Fl v
+Produce more verbose output.
+.El
+.Sh FILES
+.Bl -tag -width "/var/run/hostated.sockXX" -compact
+.It /etc/hostated.conf
+Default
+.Nm
+configuration file.
+.It /var/run/hostated.sock
+Unix-domain socket used for communication with
+.Xr hostatectl 8 .
+.El
+.Sh SEE ALSO
+.Xr hostated.conf 5 ,
+.Xr hostatectl 8
diff --git a/usr.sbin/hoststated/hoststated.c b/usr.sbin/hoststated/hoststated.c
new file mode 100644
index 00000000000..3932ab3e723
--- /dev/null
+++ b/usr.sbin/hoststated/hoststated.c
@@ -0,0 +1,377 @@
+/* $OpenBSD: hoststated.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.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/param.h>
+#include <sys/wait.h>
+#include <net/if.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <signal.h>
+#include <unistd.h>
+#include <pwd.h>
+
+#include "hostated.h"
+
+__dead void usage(void);
+
+void main_sig_handler(int, short, void *);
+void main_shutdown(void);
+void main_dispatch_pfe(int, short, void *);
+void main_dispatch_hce(int, short, void *);
+int check_child(pid_t, const char *);
+
+int pipe_parent2pfe[2];
+int pipe_parent2hce[2];
+int pipe_pfe2hce[2];
+
+struct imsgbuf *ibuf_pfe;
+struct imsgbuf *ibuf_hce;
+
+pid_t pfe_pid = 0;
+pid_t hce_pid = 0;
+
+void
+main_sig_handler(int sig, short event, void *arg)
+{
+ int die = 0;
+
+ switch (sig) {
+ case SIGTERM:
+ case SIGINT:
+ die = 1;
+ case SIGCHLD:
+ if (check_child(pfe_pid, "pf udpate engine")) {
+ pfe_pid = 0;
+ die = 1;
+ }
+ if (check_child(hce_pid, "host check engine")) {
+ hce_pid = 0;
+ die = 1;
+ }
+ if (die)
+ main_shutdown();
+ break;
+ case SIGHUP:
+ /* reconfigure */
+ break;
+ default:
+ fatalx("unexpected signal");
+ }
+}
+
+/* __dead is for lint */
+__dead void
+usage(void)
+{
+ extern char *__progname;
+
+ fprintf(stderr, "%s [-dnv] [-f file]\n", __progname);
+ exit (1);
+}
+
+int main(int argc, char *argv[])
+{
+ int c;
+ int debug;
+ u_int32_t opts;
+ struct hostated env;
+ const char *conffile;
+ struct event ev_sigint;
+ struct event ev_sigterm;
+ struct event ev_sigchld;
+ struct event ev_sighup;
+
+ opts = 0;
+ debug = 0;
+ conffile = CONF_FILE;
+ bzero(&env, sizeof (env));
+
+ for (;(c = getopt(argc, argv, "dnf:v")) != -1;) {
+ switch (c) {
+ case 'd':
+ debug = 1;
+ break;
+ case 'n':
+ opts |= HOSTATED_OPT_NOACTION;
+ break;
+ case 'f':
+ conffile = optarg;
+ break;
+ case 'v':
+ opts |= HOSTATED_OPT_VERBOSE;
+ break;
+ default:
+ usage();
+ }
+
+ }
+
+ log_init(debug);
+
+ if (parse_config(&env, conffile, opts))
+ exit(1);
+
+ if (env.opts & HOSTATED_OPT_NOACTION) {
+ fprintf(stderr, "configuration OK\n");
+ exit(0);
+ }
+
+ if (geteuid())
+ errx(1, "need root privileges");
+
+ if (getpwnam(HOSTATED_USER) == NULL)
+ errx(1, "unknown user %s", HOSTATED_USER);
+
+ if (!debug)
+ daemon(1, 0);
+
+ log_info("startup");
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_parent2pfe) == -1)
+ fatal("socketpair");
+ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_parent2hce) == -1)
+ fatal("socketpair");
+ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_pfe2hce) == -1)
+ fatal("socketpair");
+
+ session_socket_blockmode(pipe_parent2pfe[0], BM_NONBLOCK);
+ session_socket_blockmode(pipe_parent2pfe[1], BM_NONBLOCK);
+ session_socket_blockmode(pipe_parent2hce[0], BM_NONBLOCK);
+ session_socket_blockmode(pipe_parent2hce[1], BM_NONBLOCK);
+ session_socket_blockmode(pipe_pfe2hce[0], BM_NONBLOCK);
+ session_socket_blockmode(pipe_pfe2hce[1], BM_NONBLOCK);
+
+ pfe_pid = pfe(&env, pipe_parent2pfe, pipe_parent2hce, pipe_pfe2hce);
+ hce_pid = hce(&env, pipe_parent2pfe, pipe_parent2hce, pipe_pfe2hce);
+
+ setproctitle("parent");
+
+ event_init();
+
+ signal_set(&ev_sigint, SIGINT, main_sig_handler, NULL);
+ signal_set(&ev_sigterm, SIGTERM, main_sig_handler, NULL);
+ signal_set(&ev_sigchld, SIGCHLD, main_sig_handler, NULL);
+ signal_set(&ev_sighup, SIGHUP, main_sig_handler, NULL);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+ signal_add(&ev_sigchld, NULL);
+ signal_add(&ev_sighup, NULL);
+
+ close(pipe_parent2pfe[1]);
+ close(pipe_parent2hce[1]);
+ close(pipe_pfe2hce[0]);
+ close(pipe_pfe2hce[1]);
+
+ if ((ibuf_pfe = calloc(1, sizeof(struct imsgbuf))) == NULL ||
+ (ibuf_hce = calloc(1, sizeof(struct imsgbuf))) == NULL)
+ fatal(NULL);
+
+ imsg_init(ibuf_pfe, pipe_parent2pfe[0], main_dispatch_pfe);
+ imsg_init(ibuf_hce, pipe_parent2hce[0], main_dispatch_hce);
+
+ ibuf_pfe->events = EV_READ;
+ event_set(&ibuf_pfe->ev, ibuf_pfe->fd, ibuf_pfe->events,
+ ibuf_pfe->handler, ibuf_pfe);
+ event_add(&ibuf_pfe->ev, NULL);
+
+ ibuf_hce->events = EV_READ;
+ event_set(&ibuf_hce->ev, ibuf_hce->fd, ibuf_hce->events,
+ ibuf_hce->handler, ibuf_hce);
+ event_add(&ibuf_hce->ev, NULL);
+
+ event_dispatch();
+
+ return (0);
+}
+
+void
+main_shutdown(void)
+{
+ pid_t pid;
+
+ if (pfe_pid)
+ kill(pfe_pid, SIGTERM);
+ if (hce_pid)
+ kill(hce_pid, SIGTERM);
+
+ do {
+ if ((pid = wait(NULL)) == -1 &&
+ errno != EINTR && errno != ECHILD)
+ fatal("wait");
+ } while (pid != -1 || (pid == -1 && errno == EINTR));
+
+ control_cleanup();
+ log_info("terminating");
+ exit(0);
+}
+
+int
+check_child(pid_t pid, const char *pname)
+{
+ int status;
+
+ if (waitpid(pid, &status, WNOHANG) > 0) {
+ if (WIFEXITED(status)) {
+ log_warnx("check_child: lost child: %s exited", pname);
+ return (1);
+ }
+ if (WIFSIGNALED(status)) {
+ log_warnx("check_child: lost child: %s terminated; signal %d",
+ pname, WTERMSIG(status));
+ return (1);
+ }
+ }
+
+ return (0);
+}
+
+void
+imsg_event_add(struct imsgbuf *ibuf)
+{
+ ibuf->events = EV_READ;
+ if (ibuf->w.queued)
+ ibuf->events |= EV_WRITE;
+
+ event_del(&ibuf->ev);
+ event_set(&ibuf->ev, ibuf->fd, ibuf->events, ibuf->handler, ibuf);
+ event_add(&ibuf->ev, NULL);
+}
+
+void
+main_dispatch_pfe(int fd, short event, void *ptr)
+{
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = ptr;
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0)
+ fatalx("parent: pipe closed");
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("main_dispatch_pfe: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ log_debug("main_dispatch_pfe: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+main_dispatch_hce(int fd, short event, void * ptr)
+{
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = ptr;
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read error");
+ if (n == 0) /* connection closed */
+ fatalx("parent: pipe closed");
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("main_dispatch_hce: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ log_debug("main_dispatch_hce: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+}
+
+struct host *
+host_find(struct hostated *env, objid_t id)
+{
+ struct table *table;
+ struct host *host;
+
+ TAILQ_FOREACH(table, &env->tables, entry)
+ TAILQ_FOREACH(host, &table->hosts, entry)
+ if (host->id == id)
+ return (host);
+ return (NULL);
+}
+
+struct table *
+table_find(struct hostated *env, objid_t id)
+{
+ struct table *table;
+
+ TAILQ_FOREACH(table, &env->tables, entry)
+ if (table->id == id)
+ return (table);
+ return (NULL);
+}
+
+struct service *
+service_find(struct hostated *env, objid_t id)
+{
+ struct service *service;
+
+ TAILQ_FOREACH(service, &env->services, entry)
+ if (service->id == id)
+ return (service);
+ return (NULL);
+}
diff --git a/usr.sbin/hoststated/hoststated.conf.5 b/usr.sbin/hoststated/hoststated.conf.5
new file mode 100644
index 00000000000..1c87944228d
--- /dev/null
+++ b/usr.sbin/hoststated/hoststated.conf.5
@@ -0,0 +1,214 @@
+.\" $OpenBSD: hoststated.conf.5,v 1.1 2006/12/16 11:45:07 reyk Exp $
+.\"
+.\" Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd November 1, 2006
+.Dt HOSTATED.CONF 5
+.Os
+.Sh NAME
+.Nm hostated.conf
+.Nd Host Status daemon configuration file.
+.Sh DESCRIPTION
+The
+.Xr hostated 8
+daemon maintains
+.Xr pf 4
+tables up to date.
+.Sh SECTIONS
+The
+.Nm
+configuration file is divided into four main sections.
+.Bl -tag -width xxxx
+.It Sy Macros
+User-defined variables may be defined and used later, simplifying the
+configuration file.
+.It Sy Global Configuration
+Global settings for
+.Xr hostated 8 .
+.It Sy Tables
+Table definitions describe the content of a
+.Xr pf 4
+table and the method used for checking the health of the hosts
+they contain.
+.It Sy Services
+Services will be translated to
+.Xr pf 4
+rdr rules if their table or backup table have content.
+.El
+.Sh MACROS
+Macros can be defined that will later be expanded in context.
+Macro names must start with a letter, and may contain letters, digits,
+and underscores.
+Macro names may not be reserved words (for example,
+.Ic table ,
+.Ic service ,
+or
+.Ic timeout ) .
+Macros are not expanded inside quotes.
+.Pp
+For example:
+.Bd -literal -offset indent
+www1="10.0.0.1"
+www2="10.0.0.2"
+table webhosts {
+ check tcp
+ timeout 300
+ real port 80
+ host $www1
+ host $www2
+}
+.Ed
+.Sh GLOBAL CONFIGURATION
+Only one global setting can be set.
+.Pp
+.Bl -tag -width Ds -compact
+.It Xo
+.Ic interval Ar number
+.Xc
+Set the interval in seconds at which the hosts will be checked.
+The default interval is 10 seconds.
+.El
+.Sh TABLES
+Tables are used to group a set of hosts that can be checked using the same
+method. Only one health-checking method can be used per table.
+Table specific configuration directives are described below.
+.Bl -tag -width Ds
+.It Ic check tcp
+Use a simple tcp connect to check that hosts are up.
+.It Ic check icmp
+Ping hosts in this table to determine wether they are up or not.
+This method will automatically use icmp or icmpv6 depending on the
+address family of each host.
+.It Ic check http Ar path Ic code Ar number
+For each host in the table, verify that retrieving the URL
+.Ar path
+gives the HTTP return code
+.Ar number
+.It Ic check http Ar path Ic digest Ar string
+For each host in the table, verify that retrieving the URL
+.Ar path
+produces a content whose SHA1 digest is
+.Ar digest
+. The digest does not take the HTTP headers into account. To compute the
+digest you can use this simple command:
+.Bd -literal -offset 2n
+ftp -o - http://host[:port]/path | sha1
+
+.Ed
+This will give you a digest of the form
+.Bd -literal -offset 2n
+a9993e36476816aba3e25717850c26c9cd0d89d
+
+.Ed
+that you can use as-is in your digest statement.
+.It Ic timeout Ar number
+Set the timeout in milliseconds for each host that is checked.
+The default timeout is 200 milliseconds.
+.It Ic real port Ar number
+When using the tcp or http checking methods, use this port to connect
+to hosts. This parameter is mandatory. Main and backup tables need
+to have the same real port.
+.It Ic host Ar address
+Add the host whose address is
+.Ar address
+to the list of hosts to be checked in this table.
+Each table needs at least one host.
+.It Ic disable
+Start the table disabled, no hosts will be checked in this table.
+The table can be later enabled through
+.Xr hostatectl 8 .
+.El
+.Sh SERVICES
+Services represent a
+.Xr pf 4
+rdr rule, they are used to specify which addresses will be redirected
+to the hosts in the specified tables.
+The configuration directives that are valid in this context are described
+below.
+.Bl -tag -width Ds
+.It Ic virtual ip Ar address Ic port Ar number
+Specify an address and a port that will be used to redirect requests
+to the hosts in the main or backup table.
+Optionally an interface name can be specified like this
+.Bd -literal -offset indent
+interface ``ifname''
+
+.Ed
+to specify which interface the rdr rule will be enabled on.
+.It Ic table Ar name
+Specify the main table to be used. This is mandatory.
+.It Ic backup table Ar name
+Specify the table to switch to when all hosts in the main table
+are seen as down or disabled.
+.It Ic disable
+Set the service initially disabled. It can be later enabled through
+.It Ic tag Ar name
+Automatically tag packets passing through the
+.Xr pf 4
+rdr rule with the name supplied. This allows for easier filter rules
+in your main
+.Xr pf 4
+configuration.
+.Xr hostatectl 5 .
+.El
+.Sh EXAMPLE
+This configuration file would create a service 'www' which load-balances
+4 hosts and falls back to 1 host containing a ``sorry page'':
+.Bd -literal -offset indent
+##
+##
+www1=front-www1.private.example.com
+www2=front-www2.private.example.com
+www3=front-www3.private.example.com
+www4=front-www4.private.example.com
+
+interval 5
+
+table phphosts {
+ timeout 300
+ real port 8080
+ check http "/" digest 630aa3c2f...
+ host $www1
+ host $www2
+ host $www3
+ host $www4
+}
+
+table sorryhost {
+ check icmp
+ disable
+ timeout 300
+ real port 8080
+ host sorryhost.private.example.com
+}
+
+service www {
+ virtual ip www.example.com port 8080 interface trunk0
+ virtual ip www6.example.com port 80 interface trunk0
+
+ tag HOSTATED
+ table phphosts
+ backup table sorryhost
+}
+.Ed
+.Sh FILES
+.Bl -tag -width "/etc/hostated.conf" -compact
+.It Pa /etc/hostated.conf
+.Xr hostated 8
+configuration file
+.El
+.Sh SEE ALSO
+.Xr hostated 8 ,
+.Xr hostatectl 8 .
diff --git a/usr.sbin/hoststated/hoststated.h b/usr.sbin/hoststated/hoststated.h
new file mode 100644
index 00000000000..d59a934be48
--- /dev/null
+++ b/usr.sbin/hoststated/hoststated.h
@@ -0,0 +1,307 @@
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.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.
+ */
+
+#define CONF_FILE "/etc/hostated.conf"
+#define HOSTATED_SOCKET "/var/run/hostated.sock"
+#define PF_SOCKET "/dev/pf"
+#define HOSTATED_USER "_hostated"
+#define HOSTATED_ANCHOR "hostated"
+#define CONNECT_TIMEOUT 200
+#define CHECK_INTERVAL 10
+#define EMPTY_TABLE UINT_MAX
+#define TABLE_NAME_SIZE 16
+#define TAG_NAME_SIZE 16
+#define SRV_NAME_SIZE 16
+#define SRV_MAX_VIRTS 16
+
+#define READ_BUF_SIZE 65535
+
+/* buffer */
+struct buf {
+ TAILQ_ENTRY(buf) entry;
+ u_char *buf;
+ size_t size;
+ size_t max;
+ size_t wpos;
+ size_t rpos;
+};
+
+struct msgbuf {
+ TAILQ_HEAD(, buf) bufs;
+ u_int32_t queued;
+ int fd;
+};
+
+#define IMSG_HEADER_SIZE sizeof(struct imsg_hdr)
+#define MAX_IMSGSIZE 8192
+
+struct buf_read {
+ u_char buf[READ_BUF_SIZE];
+ u_char *rptr;
+ size_t wpos;
+};
+
+struct imsgbuf {
+ TAILQ_HEAD(, imsg_fd) fds;
+ struct buf_read r;
+ struct msgbuf w;
+ struct event ev;
+ void (*handler)(int, short, void *);
+ int fd;
+ pid_t pid;
+ short events;
+};
+
+enum imsg_type {
+ IMSG_NONE,
+ IMSG_CTL_OK, /* answer to hostatectl requests */
+ IMSG_CTL_FAIL,
+ IMSG_CTL_END,
+ IMSG_CTL_SERVICE,
+ IMSG_CTL_TABLE,
+ IMSG_CTL_HOST,
+ IMSG_CTL_SHOW_SUM, /* hostatectl requests */
+ IMSG_CTL_SERVICE_ENABLE,
+ IMSG_CTL_SERVICE_DISABLE,
+ IMSG_CTL_TABLE_ENABLE,
+ IMSG_CTL_TABLE_DISABLE,
+ IMSG_CTL_HOST_ENABLE,
+ IMSG_CTL_HOST_DISABLE,
+ IMSG_CTL_SHUTDOWN,
+ IMSG_CTL_RELOAD,
+ IMSG_SERVICE_ENABLE, /* notifies from pfe to hce */
+ IMSG_SERVICE_DISABLE,
+ IMSG_TABLE_ENABLE,
+ IMSG_TABLE_DISABLE,
+ IMSG_HOST_ENABLE,
+ IMSG_HOST_DISABLE,
+ IMSG_TABLE_STATUS, /* notifies from hce to pfe */
+ IMSG_HOST_STATUS,
+ IMSG_SYNC
+};
+
+struct imsg_hdr {
+ enum imsg_type type;
+ u_int16_t len;
+ u_int32_t peerid;
+ pid_t pid;
+};
+
+struct imsg {
+ struct imsg_hdr hdr;
+ void *data;
+};
+
+typedef u_int32_t objid_t;
+
+struct ctl_status {
+ objid_t id;
+ int up;
+};
+
+struct address {
+ struct sockaddr_storage ss;
+ in_port_t port;
+ char ifname[IFNAMSIZ];
+ TAILQ_ENTRY(address) entry;
+};
+TAILQ_HEAD(addresslist, address);
+
+#define F_DISABLE 0x01
+#define F_BACKUP 0x02
+#define F_USED 0x04
+#define F_ACTIVE_RULESET 0x04
+#define F_DOWN 0x08
+#define F_ADD 0x10
+#define F_DEL 0x20
+#define F_CHANGED 0x40
+
+struct host {
+ u_int8_t flags;
+ objid_t id;
+ objid_t tableid;
+ char *tablename;
+ char name[MAXHOSTNAMELEN];
+ int up;
+#define HOST_DOWN -1
+#define HOST_UNKNOWN 0
+#define HOST_UP 1
+ struct sockaddr_storage ss;
+ TAILQ_ENTRY(host) entry;
+};
+TAILQ_HEAD(hostlist, host);
+
+struct table {
+ objid_t id;
+ objid_t serviceid;
+ u_int8_t flags;
+ int check;
+#define CHECK_NOCHECK 0
+#define CHECK_ICMP 1
+#define CHECK_TCP 2
+#define CHECK_HTTP_CODE 3
+#define CHECK_HTTP_DIGEST 4
+ int up;
+ in_port_t port;
+ int retcode;
+ int timeout;
+ char name[TABLE_NAME_SIZE];
+ char path[MAXPATHLEN];
+ char digest[41]; /* length of sha1 digest * 2 */
+ struct hostlist hosts;
+ TAILQ_ENTRY(table) entry;
+};
+TAILQ_HEAD(tablelist, table);
+
+struct service {
+ objid_t id;
+ u_int8_t flags;
+ in_port_t port;
+ char name[SRV_NAME_SIZE];
+ char tag[TAG_NAME_SIZE];
+ struct addresslist virts;
+ struct table *table;
+ struct table *backup; /* use this if no host up */
+ TAILQ_ENTRY(service) entry;
+};
+TAILQ_HEAD(servicelist, service);
+
+enum {
+ PROC_MAIN,
+ PROC_PFE,
+ PROC_HCE
+} hostated_process;
+
+struct hostated {
+ u_int8_t opts;
+#define HOSTATED_OPT_VERBOSE 0x01
+#define HOSTATED_OPT_NOACTION 0x04
+ struct pfdata *pf;
+ int interval;
+ int icmp_sock;
+ int icmp6_sock;
+ int tablecount;
+ int servicecount;
+ struct table empty_table;
+ struct event ev;
+ struct tablelist tables;
+ struct servicelist services;
+};
+
+/* initially control.h */
+struct {
+ struct event ev;
+ int fd;
+} control_state;
+
+enum blockmodes {
+ BM_NORMAL,
+ BM_NONBLOCK
+};
+
+struct ctl_conn {
+ TAILQ_ENTRY(ctl_conn) entry;
+ struct imsgbuf ibuf;
+
+};
+TAILQ_HEAD(ctl_connlist, ctl_conn);
+
+/* control.c */
+int control_init(void);
+int control_listen(void);
+void control_accept(int, short, void *);
+void control_dispatch_imsg(int, short, void *);
+int control_imsg_relay(struct imsg *);
+void control_cleanup(void);
+
+void session_socket_blockmode(int, enum blockmodes);
+
+extern struct ctl_connlist ctl_conns;
+
+/* parse.y */
+int parse_config(struct hostated *, const char *, int);
+
+/* log.c */
+void log_init(int);
+void log_warn(const char *, ...);
+void log_warnx(const char *, ...);
+void log_info(const char *, ...);
+void log_debug(const char *, ...);
+void fatal(const char *);
+void fatalx(const char *);
+
+/* buffer.c */
+struct buf *buf_open(size_t);
+struct buf *buf_dynamic(size_t, size_t);
+int buf_add(struct buf *, void *, size_t);
+void *buf_reserve(struct buf *, size_t);
+void *buf_seek(struct buf *, size_t, size_t);
+int buf_close(struct msgbuf *, struct buf *);
+void buf_free(struct buf *);
+void msgbuf_init(struct msgbuf *);
+void msgbuf_clear(struct msgbuf *);
+int msgbuf_write(struct msgbuf *);
+
+/* imsg.c */
+void imsg_init(struct imsgbuf *, int, void (*)(int, short, void *));
+ssize_t imsg_read(struct imsgbuf *);
+ssize_t imsg_get(struct imsgbuf *, struct imsg *);
+int imsg_compose(struct imsgbuf *, enum imsg_type, u_int32_t, pid_t,
+ void *, u_int16_t);
+struct buf *imsg_create(struct imsgbuf *, enum imsg_type, u_int32_t, pid_t,
+ u_int16_t);
+int imsg_add(struct buf *, void *, u_int16_t);
+int imsg_close(struct imsgbuf *, struct buf *);
+void imsg_free(struct imsg *);
+void imsg_event_add(struct imsgbuf *); /* needs to be provided externally */
+
+/* pfe.c */
+pid_t pfe(struct hostated *, int [2], int [2], int [2]);
+void show(struct ctl_conn *);
+int enable_service(struct ctl_conn *, objid_t);
+int enable_table(struct ctl_conn *, objid_t);
+int enable_host(struct ctl_conn *, objid_t);
+int disable_service(struct ctl_conn *, objid_t);
+int disable_table(struct ctl_conn *, objid_t);
+int disable_host(struct ctl_conn *, objid_t);
+
+/* pfe_filter.c */
+void init_filter(struct hostated *);
+void init_tables(struct hostated *);
+void flush_table(struct hostated *, struct service *);
+void sync_table(struct hostated *, struct service *, struct table *);
+void sync_ruleset(struct hostated *, struct service *, int);
+void flush_rulesets(struct hostated *);
+
+/* hce.c */
+pid_t hce(struct hostated *, int [2], int [2], int [2]);
+
+/* check_icmp.c */
+int check_icmp(struct host *, int, int, int);
+
+/* check_tcp.c */
+int check_tcp(struct host *, struct table *);
+int tcp_connect(struct host *, struct table *);
+
+/* check_tcp.c */
+int check_http_code(struct host *, struct table *);
+int check_http_digest(struct host *, struct table *);
+
+/* hostated.c */
+struct host *host_find(struct hostated *, objid_t);
+struct table *table_find(struct hostated *, objid_t);
+struct service *service_find(struct hostated *, objid_t);
diff --git a/usr.sbin/hoststated/imsg.c b/usr.sbin/hoststated/imsg.c
new file mode 100644
index 00000000000..feafd8700c1
--- /dev/null
+++ b/usr.sbin/hoststated/imsg.c
@@ -0,0 +1,181 @@
+/* $OpenBSD: imsg.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * 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/socket.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+#include <sys/param.h>
+#include <net/if.h>
+#include <errno.h>
+#include <event.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "hostated.h"
+
+void
+imsg_init(struct imsgbuf *ibuf, int fd, void (*handler)(int, short, void *))
+{
+ msgbuf_init(&ibuf->w);
+ bzero(&ibuf->r, sizeof(ibuf->r));
+ ibuf->fd = fd;
+ ibuf->w.fd = fd;
+ ibuf->pid = getpid();
+ ibuf->handler = handler;
+ TAILQ_INIT(&ibuf->fds);
+}
+
+ssize_t
+imsg_read(struct imsgbuf *ibuf)
+{
+ ssize_t n;
+
+ if ((n = recv(ibuf->fd, ibuf->r.buf + ibuf->r.wpos,
+ sizeof(ibuf->r.buf) - ibuf->r.wpos, 0)) == -1) {
+ if (errno != EINTR && errno != EAGAIN) {
+ log_warn("imsg_read: pipe read error");
+ return (-1);
+ }
+ return (0);
+ }
+
+ ibuf->r.wpos += n;
+
+ return (n);
+}
+
+ssize_t
+imsg_get(struct imsgbuf *ibuf, struct imsg *imsg)
+{
+ size_t av, left, datalen;
+
+ av = ibuf->r.wpos;
+
+ if (IMSG_HEADER_SIZE > av)
+ return (0);
+
+ memcpy(&imsg->hdr, ibuf->r.buf, sizeof(imsg->hdr));
+ if (imsg->hdr.len < IMSG_HEADER_SIZE ||
+ imsg->hdr.len > MAX_IMSGSIZE) {
+ log_warnx("imsg_get: imsg hdr len %u out of bounds, type=%u",
+ imsg->hdr.len, imsg->hdr.type);
+ return (-1);
+ }
+ if (imsg->hdr.len > av)
+ return (0);
+ datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ ibuf->r.rptr = ibuf->r.buf + IMSG_HEADER_SIZE;
+ if ((imsg->data = malloc(datalen)) == NULL) {
+ log_warn("imsg_get");
+ return (-1);
+ }
+ memcpy(imsg->data, ibuf->r.rptr, datalen);
+
+ if (imsg->hdr.len < av) {
+ left = av - imsg->hdr.len;
+ memmove(&ibuf->r.buf, ibuf->r.buf + imsg->hdr.len, left);
+ ibuf->r.wpos = left;
+ } else
+ ibuf->r.wpos = 0;
+
+ return (datalen + IMSG_HEADER_SIZE);
+}
+
+int
+imsg_compose(struct imsgbuf *ibuf, enum imsg_type type, u_int32_t peerid,
+ pid_t pid, void *data, u_int16_t datalen)
+{
+ struct buf *wbuf;
+ int n;
+
+ if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL)
+ return (-1);
+
+ if (imsg_add(wbuf, data, datalen) == -1)
+ return (-1);
+
+ if ((n = imsg_close(ibuf, wbuf)) < 0)
+ return (-1);
+
+ return (n);
+}
+
+/* ARGSUSED */
+struct buf *
+imsg_create(struct imsgbuf *ibuf, enum imsg_type type, u_int32_t peerid,
+ pid_t pid, u_int16_t datalen)
+{
+ struct buf *wbuf;
+ struct imsg_hdr hdr;
+
+ if (datalen > MAX_IMSGSIZE - IMSG_HEADER_SIZE) {
+ log_warnx("imsg_create: len %u > MAX_IMSGSIZE; "
+ "type %u peerid %lu", datalen + IMSG_HEADER_SIZE,
+ type, peerid);
+ return (NULL);
+ }
+
+ hdr.len = (u_int16_t)(datalen + IMSG_HEADER_SIZE);
+ hdr.type = type;
+ hdr.peerid = peerid;
+ if ((hdr.pid = pid) == 0)
+ hdr.pid = ibuf->pid;
+ if ((wbuf = buf_open(hdr.len)) == NULL) {
+ log_warn("imsg_create: buf_open");
+ return (NULL);
+ }
+ if (imsg_add(wbuf, &hdr, sizeof(hdr)) == -1)
+ return (NULL);
+
+ return (wbuf);
+}
+
+int
+imsg_add(struct buf *msg, void *data, u_int16_t datalen)
+{
+ if (datalen)
+ if (buf_add(msg, data, datalen) == -1) {
+ log_warnx("imsg_add: buf_add error");
+ buf_free(msg);
+ return (-1);
+ }
+ return (datalen);
+}
+
+int
+imsg_close(struct imsgbuf *ibuf, struct buf *msg)
+{
+ int n;
+
+ if ((n = buf_close(&ibuf->w, msg)) < 0) {
+ log_warnx("imsg_close: buf_close error");
+ buf_free(msg);
+ return (-1);
+ }
+ imsg_event_add(ibuf);
+
+ return (n);
+}
+
+void
+imsg_free(struct imsg *imsg)
+{
+ free(imsg->data);
+}
diff --git a/usr.sbin/hoststated/log.c b/usr.sbin/hoststated/log.c
new file mode 100644
index 00000000000..28a7c9a7d4c
--- /dev/null
+++ b/usr.sbin/hoststated/log.c
@@ -0,0 +1,159 @@
+/* $OpenBSD: log.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * 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 MIND, 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 <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+/* prototypes */
+void log_init(int);
+void vlog(int, const char *, va_list);
+void log_warn(const char *, ...);
+void log_warnx(const char *, ...);
+void log_info(const char *, ...);
+void log_debug(const char *, ...);
+void fatal(const char *);
+void fatalx(const char *);
+
+int debug;
+
+void logit(int, const char *, ...);
+
+void
+log_init(int n_debug)
+{
+ extern char *__progname;
+
+ debug = n_debug;
+
+ if (!debug)
+ openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON);
+
+ tzset();
+}
+
+void
+logit(int pri, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog(pri, fmt, ap);
+ va_end(ap);
+}
+
+void
+vlog(int pri, const char *fmt, va_list ap)
+{
+ char *nfmt;
+
+ if (debug) {
+ /* best effort in out of mem situations */
+ if (asprintf(&nfmt, "%s\n", fmt) == -1) {
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ } else {
+ vfprintf(stderr, nfmt, ap);
+ free(nfmt);
+ }
+ fflush(stderr);
+ } else
+ vsyslog(pri, fmt, ap);
+}
+
+
+void
+log_warn(const char *emsg, ...)
+{
+ char *nfmt;
+ va_list ap;
+
+ /* best effort to even work in out of memory situations */
+ if (emsg == NULL)
+ logit(LOG_CRIT, "%s", strerror(errno));
+ else {
+ va_start(ap, emsg);
+
+ if (asprintf(&nfmt, "%s: %s", emsg, strerror(errno)) == -1) {
+ /* we tried it... */
+ vlog(LOG_CRIT, emsg, ap);
+ logit(LOG_CRIT, "%s", strerror(errno));
+ } else {
+ vlog(LOG_CRIT, nfmt, ap);
+ free(nfmt);
+ }
+ va_end(ap);
+ }
+}
+
+void
+log_warnx(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_CRIT, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_info(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_INFO, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_debug(const char *emsg, ...)
+{
+ va_list ap;
+
+ if (debug) {
+ va_start(ap, emsg);
+ vlog(LOG_DEBUG, emsg, ap);
+ va_end(ap);
+ }
+}
+
+void
+fatal(const char *emsg)
+{
+ if (emsg == NULL)
+ logit(LOG_CRIT, "fatal: %s", strerror(errno));
+ else
+ if (errno)
+ logit(LOG_CRIT, "fatal: %s: %s",
+ emsg, strerror(errno));
+ else
+ logit(LOG_CRIT, "fatal: %s", emsg);
+
+ exit(1);
+}
+
+void
+fatalx(const char *emsg)
+{
+ errno = 0;
+ fatal(emsg);
+}
diff --git a/usr.sbin/hoststated/parse.y b/usr.sbin/hoststated/parse.y
new file mode 100644
index 00000000000..977b7a74cb1
--- /dev/null
+++ b/usr.sbin/hoststated/parse.y
@@ -0,0 +1,937 @@
+/* $OpenBSD: parse.y,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org>
+ * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
+ * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
+ * Copyright (c) 2001 Markus Friedl. All rights reserved.
+ * Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
+ * Copyright (c) 2001 Theo de Raadt. All rights reserved.
+ *
+ * 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 <sys/queue.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+
+#include "hostated.h"
+
+struct hostated *conf = NULL;
+static FILE *fin = NULL;
+static int lineno = 1;
+static int errors = 0;
+const char *infile;
+char *start_state;
+objid_t last_service_id = 0;
+objid_t last_table_id = 0;
+objid_t last_host_id = 0;
+
+static struct service *service = NULL;
+static struct table *table = NULL;
+
+int yyerror(const char *, ...);
+int yyparse(void);
+int kw_cmp(const void *, const void *);
+int lookup(char *);
+int lgetc(FILE *);
+int lungetc(int);
+int findeol(void);
+int yylex(void);
+
+TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
+struct sym {
+ TAILQ_ENTRY(sym) entries;
+ int used;
+ int persist;
+ char *nam;
+ char *val;
+};
+
+int symset(const char *, const char *, int);
+char *symget(const char *);
+int cmdline_symset(char *);
+
+struct address *host_v4(const char *);
+struct address *host_v6(const char *);
+int host_dns(const char *, struct addresslist *,
+ int, in_port_t, const char *);
+int host(const char *, struct addresslist *,
+ int, in_port_t, const char *);
+
+typedef struct {
+ union {
+ u_int32_t number;
+ char *string;
+ struct host *host;
+ } v;
+ int lineno;
+} YYSTYPE;
+
+%}
+
+%token SERVICE TABLE BACKUP HOST REAL
+%token CHECK HTTP HTTPS TCP ICMP EXTERNAL
+%token TIMEOUT CODE DIGEST PORT TAG INTERFACE
+%token VIRTUAL IP INTERVAL DISABLE
+%token ERROR
+%token <v.string> STRING
+%type <v.string> interface
+%type <v.number> number
+%type <v.host> host
+
+%%
+
+grammar : /* empty */
+ | grammar '\n'
+ | grammar varset '\n'
+ | grammar main '\n'
+ | grammar service '\n'
+ | grammar table '\n'
+ | grammar error '\n' { errors++; }
+ ;
+
+number : STRING {
+ const char *estr;
+
+ $$ = strtonum($1, 0, UINT_MAX, &estr);
+ if (estr) {
+ yyerror("cannot parse number %s : %s",
+ $1, estr);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ ;
+
+varset : STRING '=' STRING {
+ if (symset($1, $3, 0) == -1)
+ fatal("cannot store variable");
+ free($1);
+ free($3);
+ }
+ ;
+
+main : INTERVAL number { conf->interval = $2; }
+ ;
+
+service : SERVICE STRING {
+ struct service *srv;
+
+ TAILQ_FOREACH(srv, &conf->services, entry)
+ if (!strcmp(srv->name, $2))
+ break;
+ if (srv != NULL) {
+ yyerror("service %s defined twice", $2);
+ free($2);
+ YYERROR;
+ }
+ if ((srv = calloc(1, sizeof (*srv))) == NULL)
+ fatal("out of memory");
+
+ if (strlcpy(srv->name, $2, sizeof (srv->name)) >=
+ sizeof (srv->name)) {
+ yyerror("service name truncated");
+ YYERROR;
+ }
+ free ($2);
+ srv->id = last_service_id++;
+ if (last_service_id == UINT_MAX) {
+ yyerror("too many services defined");
+ YYERROR;
+ }
+ service = srv;
+ } '{' optnl serviceopts_l '}' {
+ if (service->table == NULL) {
+ yyerror("service %s has no table",
+ service->name);
+ YYERROR;
+ }
+ if (TAILQ_EMPTY(&service->virts)) {
+ yyerror("service %s has no virtual ip",
+ service->name);
+ YYERROR;
+ }
+ conf->servicecount++;
+ if (service->backup == NULL)
+ service->backup = &conf->empty_table;
+ else if (service->backup->port !=
+ service->table->port) {
+ yyerror("service %s uses two different ports "
+ "for its table and backup table",
+ service->name);
+ YYERROR;
+ }
+
+ if (!(service->flags & F_DISABLE))
+ service->flags |= F_ADD;
+ TAILQ_INSERT_HEAD(&conf->services, service, entry);
+ }
+ ;
+
+serviceopts_l : serviceopts_l serviceoptsl nl
+ | serviceoptsl optnl
+ ;
+
+serviceoptsl : TABLE STRING {
+ struct table *tb;
+
+ TAILQ_FOREACH(tb, &conf->tables, entry)
+ if (!strcmp(tb->name, $2))
+ break;
+ if (tb == NULL) {
+ yyerror("no such table: %s", $2);
+ free($2);
+ YYERROR;
+ } else {
+ service->table = tb;
+ service->table->serviceid = service->id;
+ service->table->flags |= F_USED;
+ free($2);
+ }
+ }
+ | BACKUP TABLE STRING {
+ struct table *tb;
+
+ if (service->backup) {
+ yyerror("backup already specified");
+ free($3);
+ YYERROR;
+ }
+
+ TAILQ_FOREACH(tb, &conf->tables, entry)
+ if (!strcmp(tb->name, $3))
+ break;
+
+ if (tb == NULL) {
+ yyerror("no such table: %s", $3);
+ free($3);
+ YYERROR;
+ } else {
+ service->backup = tb;
+ service->backup->serviceid = service->id;
+ service->backup->flags |= (F_USED|F_BACKUP);
+ free($3);
+ }
+ }
+ | VIRTUAL IP STRING PORT number interface {
+ if ($5 < 1 || $5 > USHRT_MAX) {
+ yyerror("invalid port number: %d", $5);
+ free($3);
+ free($6);
+ YYERROR;
+ }
+ if (host($3, &service->virts,
+ SRV_MAX_VIRTS, htons($5), $6) <= 0) {
+ yyerror("invalid virtual ip: %s", $3);
+ free($3);
+ free($6);
+ YYERROR;
+ }
+ free($3);
+ free($6);
+ }
+ | DISABLE { service->flags |= F_DISABLE; }
+ | TAG STRING {
+ if (strlcpy(service->tag, $2, sizeof(service->tag)) >=
+ sizeof(service->tag)) {
+ yyerror("service tag name truncated");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ ;
+
+table : TABLE STRING {
+ struct table *tb;
+
+ TAILQ_FOREACH(tb, &conf->tables, entry)
+ if (!strcmp(tb->name, $2))
+ break;
+ if (tb != NULL) {
+ yyerror("table %s defined twice");
+ free($2);
+ YYERROR;
+ }
+
+ if ((tb = calloc(1, sizeof (*tb))) == NULL)
+ fatal("out of memory");
+
+ if (strlcpy(tb->name, $2, sizeof (tb->name)) >=
+ sizeof (tb->name)) {
+ yyerror("table name truncated");
+ YYERROR;
+ }
+ tb->id = last_table_id++;
+ tb->timeout = CONNECT_TIMEOUT;
+ if (last_table_id == UINT_MAX) {
+ yyerror("too many tables defined");
+ YYERROR;
+ }
+ free ($2);
+ table = tb;
+ } '{' optnl tableopts_l '}' {
+ if (table->port == 0) {
+ yyerror("table %s has no port", table->name);
+ YYERROR;
+ }
+ if (TAILQ_EMPTY(&table->hosts)) {
+ yyerror("table %s has no hosts", table->name);
+ YYERROR;
+ }
+ if (table->check == CHECK_NOCHECK) {
+ yyerror("table %s has no check", table->name);
+ YYERROR;
+ }
+ conf->tablecount++;
+ TAILQ_INSERT_HEAD(&conf->tables, table, entry);
+ }
+ ;
+
+tableopts_l : tableopts_l tableoptsl nl
+ | tableoptsl optnl
+ ;
+
+tableoptsl : host {
+ $1->tableid = table->id;
+ $1->tablename = table->name;
+ TAILQ_INSERT_HEAD(&table->hosts, $1, entry);
+ }
+ | TIMEOUT number {
+ table->timeout = $2;
+ }
+ | CHECK ICMP {
+ table->check = CHECK_ICMP;
+ }
+ | CHECK TCP {
+ table->check = CHECK_TCP;
+ }
+ | CHECK HTTP STRING CODE number {
+ table->check = CHECK_HTTP_CODE;
+ table->retcode = $5;
+ if (strlcpy(table->path, $3, sizeof (table->path)) >=
+ sizeof (table->path)) {
+ yyerror("http path truncated");
+ free($3);
+ YYERROR;
+ }
+ }
+ | CHECK HTTP STRING DIGEST STRING {
+ table->check = CHECK_HTTP_DIGEST;
+ if (strlcpy(table->path, $3, sizeof (table->path)) >=
+ sizeof (table->path)) {
+ yyerror("http path truncated");
+ free($3);
+ free($5);
+ YYERROR;
+ }
+ if (strlcpy(table->digest, $5, sizeof (table->digest))
+ >= sizeof (table->digest)) {
+ yyerror("http digest truncated");
+ free($3);
+ free($5);
+ YYERROR;
+ }
+ free($3);
+ free($5);
+ }
+ | REAL PORT number {
+ if ($3 < 1 || $3 >= USHRT_MAX) {
+ yyerror("invalid port number: %d", $3);
+ YYERROR;
+ }
+ table->port = $3;
+ }
+ | DISABLE { table->flags |= F_DISABLE; }
+ ;
+
+interface : /*empty*/ { $$ = NULL; }
+ | INTERFACE STRING { $$ = $2; }
+ ;
+
+host : HOST STRING {
+ struct host *r;
+ struct address *a;
+ struct addresslist al;
+
+ if ((r = calloc(1, sizeof (*r))) == NULL)
+ fatal("out of memory");
+
+ TAILQ_INIT(&al);
+ if (host($2, &al, 1, 0, NULL) <= 0) {
+ yyerror("invalid host %s", $2);
+ free($2);
+ YYERROR;
+ }
+ a = TAILQ_FIRST(&al);
+ memcpy(&r->ss, &a->ss, sizeof(r->ss));
+ free(a);
+
+ if (strlcpy(r->name, $2, sizeof (r->name)) >=
+ sizeof (r->name)) {
+ yyerror("host name truncated");
+ free($2);
+ YYERROR;
+ } else {
+ r->id = last_host_id++;
+ if (last_host_id == UINT_MAX) {
+ yyerror("too many hosts defined");
+ YYERROR;
+ }
+ free($2);
+ $$ = r;
+ }
+ }
+ ;
+
+optnl : '\n' optnl
+ |
+ ;
+
+nl : '\n' optnl
+ ;
+
+%%
+
+struct keywords {
+ const char *k_name;
+ int k_val;
+};
+
+int
+yyerror(const char *fmt, ...)
+{
+ va_list ap;
+
+ errors = 1;
+ va_start(ap, fmt);
+ fprintf(stderr, "%s:%d: ", infile, yylval.lineno);
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ va_end(ap);
+ return (0);
+}
+
+int
+kw_cmp(const void *k, const void *e)
+{
+
+ return (strcmp(k, ((const struct keywords *)e)->k_name));
+}
+
+int
+lookup(char *s)
+{
+ /* this has to be sorted always */
+ static const struct keywords keywords[] = {
+ {"backup", BACKUP},
+ {"check", CHECK},
+ {"code", CODE},
+ {"digest", DIGEST},
+ {"disable", DISABLE},
+ {"external", EXTERNAL},
+ {"host", HOST},
+ {"http", HTTP},
+ {"https", HTTPS},
+ {"icmp", ICMP},
+ {"interface", INTERFACE},
+ {"interval", INTERVAL},
+ {"ip", IP},
+ {"port", PORT},
+ {"real", REAL},
+ {"service", SERVICE},
+ {"table", TABLE},
+ {"tag", TAG},
+ {"tcp", TCP},
+ {"timeout", TIMEOUT},
+ {"virtual", VIRTUAL}
+ };
+ const struct keywords *p;
+
+ p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
+ sizeof(keywords[0]), kw_cmp);
+
+ if (p)
+ return (p->k_val);
+ else
+ return (STRING);
+}
+
+#define MAXPUSHBACK 128
+
+char *parsebuf;
+int parseindex;
+char pushback_buffer[MAXPUSHBACK];
+int pushback_index = 0;
+
+int
+lgetc(FILE *f)
+{
+ int c, next;
+
+ if (parsebuf) {
+ /* Read character from the parsebuffer instead of input. */
+ if (parseindex >= 0) {
+ c = parsebuf[parseindex++];
+ if (c != '\0')
+ return (c);
+ parsebuf = NULL;
+ } else
+ parseindex++;
+ }
+
+ if (pushback_index)
+ return (pushback_buffer[--pushback_index]);
+
+ while ((c = getc(f)) == '\\') {
+ next = getc(f);
+ if (next != '\n') {
+ c = next;
+ break;
+ }
+ yylval.lineno = lineno;
+ lineno++;
+ }
+ if (c == '\t' || c == ' ') {
+ /* Compress blanks to a single space. */
+ do {
+ c = getc(f);
+ } while (c == '\t' || c == ' ');
+ ungetc(c, f);
+ c = ' ';
+ }
+
+ return (c);
+}
+
+int
+lungetc(int c)
+{
+ if (c == EOF)
+ return (EOF);
+ if (parsebuf) {
+ parseindex--;
+ if (parseindex >= 0)
+ return (c);
+ }
+ if (pushback_index < MAXPUSHBACK-1)
+ return (pushback_buffer[pushback_index++] = c);
+ else
+ return (EOF);
+}
+
+int
+findeol(void)
+{
+ int c;
+
+ parsebuf = NULL;
+ pushback_index = 0;
+
+ /* skip to either EOF or the first real EOL */
+ while (1) {
+ c = lgetc(fin);
+ if (c == '\n') {
+ lineno++;
+ break;
+ }
+ if (c == EOF)
+ break;
+ }
+ return (ERROR);
+}
+
+int
+yylex(void)
+{
+ char buf[8096];
+ char *p, *val;
+ int endc, c;
+ int token;
+
+top:
+ p = buf;
+ while ((c = lgetc(fin)) == ' ')
+ ; /* nothing */
+
+ yylval.lineno = lineno;
+ if (c == '#')
+ while ((c = lgetc(fin)) != '\n' && c != EOF)
+ ; /* nothing */
+ if (c == '$' && parsebuf == NULL) {
+ while (1) {
+ if ((c = lgetc(fin)) == EOF)
+ return (0);
+
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ if (isalnum(c) || c == '_') {
+ *p++ = (char)c;
+ continue;
+ }
+ *p = '\0';
+ lungetc(c);
+ break;
+ }
+ val = symget(buf);
+ if (val == NULL) {
+ yyerror("macro '%s' not defined", buf);
+ return (findeol());
+ }
+ parsebuf = val;
+ parseindex = 0;
+ goto top;
+ }
+
+ switch (c) {
+ case '\'':
+ case '"':
+ endc = c;
+ while (1) {
+ if ((c = lgetc(fin)) == EOF)
+ return (0);
+ if (c == endc) {
+ *p = '\0';
+ break;
+ }
+ if (c == '\n') {
+ lineno++;
+ continue;
+ }
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ *p++ = (char)c;
+ }
+ yylval.v.string = strdup(buf);
+ if (yylval.v.string == NULL)
+ errx(1, "yylex: strdup");
+ return (STRING);
+ }
+
+#define allowed_in_string(x) \
+ (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
+ x != '{' && x != '}' && \
+ x != '!' && x != '=' && x != '#' && \
+ x != ','))
+
+ if (isalnum(c) || c == ':' || c == '_') {
+ do {
+ *p++ = c;
+ if ((unsigned)(p-buf) >= sizeof(buf)) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ } while ((c = lgetc(fin)) != EOF && (allowed_in_string(c)));
+ lungetc(c);
+ *p = '\0';
+ if ((token = lookup(buf)) == STRING)
+ if ((yylval.v.string = strdup(buf)) == NULL)
+ err(1, "yylex: strdup");
+ return (token);
+ }
+ if (c == '\n') {
+ yylval.lineno = lineno;
+ lineno++;
+ }
+ if (c == EOF)
+ return (0);
+ return (c);
+}
+
+int
+parse_config(struct hostated *x_conf, const char *filename, int opts)
+{
+ struct sym *sym, *next;
+
+ conf = x_conf;
+
+ TAILQ_INIT(&conf->services);
+ TAILQ_INIT(&conf->tables);
+ memset(&conf->empty_table, 0, sizeof(conf->empty_table));
+ conf->empty_table.id = EMPTY_TABLE;
+ conf->empty_table.flags |= F_DISABLE;
+ (void)strlcpy(conf->empty_table.name, "empty",
+ sizeof(conf->empty_table.name));
+
+ conf->interval = CHECK_INTERVAL;
+ conf->opts = opts;
+
+ if ((fin = fopen(filename, "r")) == NULL) {
+ warn("%s", filename);
+ return (NULL);
+ }
+ infile = filename;
+ yyparse();
+ fclose(fin);
+
+ /* Free macros and check which have not been used. */
+ for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) {
+ next = TAILQ_NEXT(sym, entries);
+ if ((conf->opts & HOSTATED_OPT_VERBOSE) && !sym->used)
+ fprintf(stderr, "warning: macro '%s' not "
+ "used\n", sym->nam);
+ if (!sym->persist) {
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entries);
+ free(sym);
+ }
+ }
+
+ if (TAILQ_EMPTY(&conf->services)) {
+ log_warnx("no services, nothing to do");
+ errors++;
+ }
+
+ /* Verify that every table is used */
+ TAILQ_FOREACH(table, &conf->tables, entry)
+ if (!(table->flags & F_USED)) {
+ log_warnx("unused table: %s", table->name);
+ errors++;
+ }
+
+ if (errors) {
+ bzero(&conf, sizeof (*conf));
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+symset(const char *nam, const char *val, int persist)
+{
+ struct sym *sym;
+
+ for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam);
+ sym = TAILQ_NEXT(sym, entries))
+ ; /* nothing */
+
+ if (sym != NULL) {
+ if (sym->persist == 1)
+ return (0);
+ else {
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entries);
+ free(sym);
+ }
+ }
+ if ((sym = calloc(1, sizeof(*sym))) == NULL)
+ return (-1);
+
+ sym->nam = strdup(nam);
+ if (sym->nam == NULL) {
+ free(sym);
+ return (-1);
+ }
+ sym->val = strdup(val);
+ if (sym->val == NULL) {
+ free(sym->nam);
+ free(sym);
+ return (-1);
+ }
+ sym->used = 0;
+ sym->persist = persist;
+ TAILQ_INSERT_TAIL(&symhead, sym, entries);
+ return (0);
+}
+
+int
+cmdline_symset(char *s)
+{
+ char *sym, *val;
+ int ret;
+ size_t len;
+
+ if ((val = strrchr(s, '=')) == NULL)
+ return (-1);
+
+ len = strlen(s) - strlen(val) + 1;
+ if ((sym = malloc(len)) == NULL)
+ errx(1, "cmdline_symset: malloc");
+
+ strlcpy(sym, s, len);
+
+ ret = symset(sym, val + 1, 1);
+ free(sym);
+
+ return (ret);
+}
+
+char *
+symget(const char *nam)
+{
+ struct sym *sym;
+
+ TAILQ_FOREACH(sym, &symhead, entries)
+ if (strcmp(nam, sym->nam) == 0) {
+ sym->used = 1;
+ return (sym->val);
+ }
+ return (NULL);
+}
+
+struct address *
+host_v4(const char *s)
+{
+ struct in_addr ina;
+ struct sockaddr_in *sain;
+ struct address *h;
+
+ bzero(&ina, sizeof(ina));
+ if (inet_pton(AF_INET, s, &ina) != 1)
+ return (NULL);
+
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(NULL);
+ sain = (struct sockaddr_in *)&h->ss;
+ sain->sin_len = sizeof(struct sockaddr_in);
+ sain->sin_family = AF_INET;
+ sain->sin_addr.s_addr = ina.s_addr;
+
+ return (h);
+}
+
+struct address *
+host_v6(const char *s)
+{
+ struct in6_addr ina6;
+ struct sockaddr_in6 *sin6;
+ struct address *h;
+
+ bzero(&ina6, sizeof(ina6));
+ if (inet_pton(AF_INET6, s, &ina6) != 1)
+ return (NULL);
+
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(NULL);
+ sin6 = (struct sockaddr_in6 *)&h->ss;
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ sin6->sin6_family = AF_INET6;
+ memcpy(&sin6->sin6_addr, &ina6, sizeof(ina6));
+
+ return (h);
+}
+
+int
+host_dns(const char *s, struct addresslist *al, int max,
+ in_port_t port, const char *ifname)
+{
+ struct addrinfo hints, *res0, *res;
+ int error, cnt = 0;
+ struct sockaddr_in *sain;
+ struct sockaddr_in6 *sin6;
+ struct address *h;
+
+ bzero(&hints, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM; /* DUMMY */
+ error = getaddrinfo(s, NULL, &hints, &res0);
+ if (error == EAI_AGAIN || error == EAI_NODATA || error == EAI_NONAME)
+ return (0);
+ if (error) {
+ log_warnx("host_dns: could not parse \"%s\": %s", s,
+ gai_strerror(error));
+ return (-1);
+ }
+
+ for (res = res0; res && cnt < max; res = res->ai_next) {
+ if (res->ai_family != AF_INET &&
+ res->ai_family != AF_INET6)
+ continue;
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(NULL);
+
+ h->port = port;
+ if (ifname != NULL) {
+ if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >=
+ sizeof(h->ifname))
+ log_warnx("host_dns: interface name truncated");
+ return (-1);
+ }
+ h->ss.ss_family = res->ai_family;
+ if (res->ai_family == AF_INET) {
+ sain = (struct sockaddr_in *)&h->ss;
+ sain->sin_len = sizeof(struct sockaddr_in);
+ sain->sin_addr.s_addr = ((struct sockaddr_in *)
+ res->ai_addr)->sin_addr.s_addr;
+ } else {
+ sin6 = (struct sockaddr_in6 *)&h->ss;
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ memcpy(&sin6->sin6_addr, &((struct sockaddr_in6 *)
+ res->ai_addr)->sin6_addr, sizeof(struct in6_addr));
+ }
+
+ TAILQ_INSERT_HEAD(al, h, entry);
+ cnt++;
+ }
+ if (cnt == max && res) {
+ log_warnx("host_dns: %s resolves to more than %d hosts",
+ s, max);
+ }
+ freeaddrinfo(res0);
+
+ return (cnt);
+}
+
+int
+host(const char *s, struct addresslist *al, int max,
+ in_port_t port, const char *ifname)
+{
+ struct address *h;
+
+ h = host_v4(s);
+
+ /* IPv6 address? */
+ if (h == NULL)
+ h = host_v6(s);
+
+ if (h != NULL) {
+ h->port = port;
+ if (ifname != NULL) {
+ if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >=
+ sizeof(h->ifname)) {
+ log_warnx("host: interface name truncated");
+ return (-1);
+ }
+ }
+
+ TAILQ_INSERT_HEAD(al, h, entry);
+ return (1);
+ }
+
+ return (host_dns(s, al, max, port, ifname));
+}
diff --git a/usr.sbin/hoststated/pfe.c b/usr.sbin/hoststated/pfe.c
new file mode 100644
index 00000000000..0a7ff43949f
--- /dev/null
+++ b/usr.sbin/hoststated/pfe.c
@@ -0,0 +1,497 @@
+/* $OpenBSD: pfe.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/queue.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <net/if.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <pwd.h>
+
+#include "hostated.h"
+
+void pfe_sig_handler(int sig, short, void *);
+void pfe_shutdown(void);
+void pfe_dispatch_imsg(int, short, void *);
+void pfe_dispatch_parent(int, short, void *);
+
+void pfe_sync(void);
+
+static struct hostated *env = NULL;
+
+struct imsgbuf *ibuf_main;
+struct imsgbuf *ibuf_hce;
+
+void
+pfe_sig_handler(int sig, short event, void *arg)
+{
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ pfe_shutdown();
+ default:
+ fatalx("pfe_sig_handler: unexpected signal");
+ }
+}
+
+pid_t
+pfe(struct hostated *x_env, int pipe_parent2pfe[2], int pipe_parent2hce[2],
+ int pipe_pfe2hce[2])
+{
+ pid_t pid;
+ struct passwd *pw;
+ struct event ev_sigint;
+ struct event ev_sigterm;
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("pfe: cannot fork");
+ case 0:
+ break;
+ default:
+ return (pid);
+ }
+
+ env = x_env;
+
+ if (control_init() == -1)
+ fatalx("pfe: control socket setup failed");
+
+ init_filter(env);
+ init_tables(env);
+
+ if ((pw = getpwnam(HOSTATED_USER)) == NULL)
+ fatal("pfe: getpwnam");
+
+ if (chroot(pw->pw_dir) == -1)
+ fatal("pfe: chroot");
+ if (chdir("/") == -1)
+ fatal("pfe: chdir(\"/\")");
+
+ setproctitle("pf update engine");
+ hostated_process = PROC_PFE;
+
+ 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("pfe: cannot drop privileges");
+
+ event_init();
+
+ signal_set(&ev_sigint, SIGINT, pfe_sig_handler, NULL);
+ signal_set(&ev_sigterm, SIGTERM, pfe_sig_handler, NULL);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+
+ /* setup pipes */
+ close(pipe_pfe2hce[0]);
+ close(pipe_parent2pfe[0]);
+ close(pipe_parent2hce[0]);
+ close(pipe_parent2hce[1]);
+
+ if ((ibuf_hce = calloc(1, sizeof(struct imsgbuf))) == NULL ||
+ (ibuf_main = calloc(1, sizeof(struct imsgbuf))) == NULL)
+ fatal("pfe");
+ imsg_init(ibuf_hce, pipe_pfe2hce[1], pfe_dispatch_imsg);
+ imsg_init(ibuf_main, pipe_parent2pfe[1], pfe_dispatch_parent);
+
+ ibuf_hce->events = EV_READ;
+ event_set(&ibuf_hce->ev, ibuf_hce->fd, ibuf_hce->events,
+ ibuf_hce->handler, ibuf_hce);
+ event_add(&ibuf_hce->ev, NULL);
+
+ ibuf_main->events = EV_READ;
+ event_set(&ibuf_main->ev, ibuf_main->fd, ibuf_main->events,
+ ibuf_main->handler, ibuf_main);
+ event_add(&ibuf_main->ev, NULL);
+
+ TAILQ_INIT(&ctl_conns);
+ control_listen();
+
+ event_dispatch();
+ pfe_shutdown();
+
+ return (0);
+}
+
+void
+pfe_shutdown(void)
+{
+ flush_rulesets(env);
+ log_info("pf update engine exiting");
+ _exit(0);
+}
+
+void
+pfe_dispatch_imsg(int fd, short event, void *ptr)
+{
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ struct host *host;
+ struct table *table;
+ struct ctl_status st;
+
+ ibuf = ptr;
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("pfe_dispatch_imsg: imsg_read_error");
+ if (n == 0)
+ fatalx("pfe_dispatch_imsg: pipe closed");
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("pfe_dispatch_imsg: msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("pfe_dispatch_imsg: unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("pfe_dispatch_imsg: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_HOST_STATUS:
+ if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(st))
+ fatalx("pfe_dispatch_imsg: invalid request");
+ memcpy(&st, imsg.data, sizeof(st));
+ if ((host = host_find(env, st.id)) == NULL)
+ fatalx("pfe_dispatch_imsg: invalid host id");
+ if (host->up == st.up) {
+ log_debug("pfe_dispatch_imsg: host %d => %d",
+ host->id, host->up);
+ fatalx("pfe_dispatch_imsg: desynchronized");
+ }
+
+ if ((table = table_find(env, host->tableid)) == NULL)
+ fatalx("pfe_dispatch_imsg: invalid table id");
+
+ log_debug("pfe_dispatch_imsg: state %d for host %u %s",
+ st.up, host->id, host->name);
+
+ if ((st.up == HOST_UNKNOWN && host->up == HOST_DOWN) ||
+ (st.up == HOST_DOWN && host->up == HOST_UNKNOWN)) {
+ host->up = st.up;
+ break;
+ }
+
+ if (st.up == HOST_UP) {
+ table->flags |= F_CHANGED;
+ table->up++;
+ host->flags |= F_ADD;
+ host->flags &= ~(F_DEL);
+ } else {
+ table->up--;
+ table->flags |= F_CHANGED;
+ host->flags |= F_DEL;
+ host->flags &= ~(F_ADD);
+ }
+ host->up = st.up;
+ break;
+ case IMSG_SYNC:
+ pfe_sync();
+ break;
+ default:
+ log_debug("pfe_dispatch_imsg: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+pfe_dispatch_parent(int fd, short event, void * ptr)
+{
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = ptr;
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read error");
+ if (n == 0) /* connection closed */
+ fatalx("pfe_dispatch_parent: pipe closed");
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("pfe_dispatch_parent: unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("pfe_dispatch_parent: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ log_debug("pfe_dispatch_parent: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+}
+
+void
+show(struct ctl_conn *c)
+{
+ struct service *service;
+ struct host *host;
+
+ TAILQ_FOREACH(service, &env->services, entry) {
+ imsg_compose(&c->ibuf, IMSG_CTL_SERVICE, 0, 0,
+ service, sizeof(*service));
+ if (service->flags & F_DISABLE)
+ continue;
+
+ imsg_compose(&c->ibuf, IMSG_CTL_TABLE, 0, 0,
+ service->table, sizeof(*service->table));
+ if (!(service->table->flags & F_DISABLE))
+ TAILQ_FOREACH(host, &service->table->hosts, entry)
+ imsg_compose(&c->ibuf, IMSG_CTL_HOST, 0, 0,
+ host, sizeof(*host));
+
+ if (service->backup->id == EMPTY_TABLE)
+ continue;
+ imsg_compose(&c->ibuf, IMSG_CTL_TABLE, 0, 0,
+ service->backup, sizeof(*service->backup));
+ if (!(service->backup->flags & F_DISABLE))
+ TAILQ_FOREACH(host, &service->backup->hosts, entry)
+ imsg_compose(&c->ibuf, IMSG_CTL_HOST, 0, 0,
+ host, sizeof(*host));
+ }
+ imsg_compose(&c->ibuf, IMSG_CTL_END, 0, 0, NULL, 0);
+}
+
+
+int
+disable_service(struct ctl_conn *c, objid_t id)
+{
+ struct service *service;
+
+ if ((service = service_find(env, id)) == NULL)
+ return (-1);
+
+ if (service->flags & F_DISABLE)
+ return (0);
+
+ service->flags |= F_DISABLE;
+ service->flags &= ~(F_ADD);
+ service->flags |= F_DEL;
+ service->table->flags |= F_DISABLE;
+ log_debug("disable_service: disabled service %d", service->id);
+ pfe_sync();
+ return (0);
+}
+
+int
+enable_service(struct ctl_conn *c, objid_t id)
+{
+ struct service *service;
+
+ if ((service = service_find(env, id)) == NULL)
+ return (-1);
+
+ if (!(service->flags & F_DISABLE))
+ return (0);
+
+ service->flags &= ~(F_DISABLE);
+ service->flags &= ~(F_DEL);
+ service->flags |= F_ADD;
+ log_debug("enable_service: enabled service %d", service->id);
+
+ /* XXX: we're syncing twice */
+ if (enable_table(c, service->table->id))
+ return (-1);
+ if (enable_table(c, service->backup->id))
+ return (-1);
+ return (0);
+}
+
+int
+disable_table(struct ctl_conn *c, objid_t id)
+{
+ struct table *table;
+ struct service *service;
+ struct host *host;
+
+ if (id == EMPTY_TABLE)
+ return (-1);
+ if ((table = table_find(env, id)) == NULL)
+ return (-1);
+ if ((service = service_find(env, table->serviceid)) == NULL)
+ fatalx("disable_table: desynchronised");
+
+ if (table->flags & F_DISABLE)
+ return (0);
+ table->flags |= (F_DISABLE|F_CHANGED);
+ table->up = 0;
+ TAILQ_FOREACH(host, &table->hosts, entry)
+ host->up = HOST_UNKNOWN;
+ imsg_compose(ibuf_hce, IMSG_TABLE_DISABLE, 0, 0, &id, sizeof(id));
+ log_debug("disable_table: disabled table %d", table->id);
+ pfe_sync();
+ return (0);
+}
+
+int
+enable_table(struct ctl_conn *c, objid_t id)
+{
+ struct service *service;
+ struct table *table;
+ struct host *host;
+
+ if (id == EMPTY_TABLE)
+ return (-1);
+ if ((table = table_find(env, id)) == NULL)
+ return (-1);
+ if ((service = service_find(env, table->serviceid)) == NULL)
+ fatalx("enable_table: desynchronised");
+
+ if (!(table->flags & F_DISABLE))
+ return (0);
+ table->flags &= ~(F_DISABLE);
+ table->flags |= F_CHANGED;
+ table->up = 0;
+ TAILQ_FOREACH(host, &table->hosts, entry)
+ host->up = HOST_UNKNOWN;
+ imsg_compose(ibuf_hce, IMSG_TABLE_ENABLE, 0, 0, &id, sizeof(id));
+ log_debug("enable_table: enabled table %d", table->id);
+ pfe_sync();
+ return (0);
+}
+
+int
+disable_host(struct ctl_conn *c, objid_t id)
+{
+ struct host *host;
+ struct table *table;
+
+ if ((host = host_find(env, id)) == NULL)
+ return (-1);
+
+ if (host->flags & F_DISABLE)
+ return (0);
+
+ if (host->up == HOST_UP) {
+ if ((table = table_find(env, host->tableid)) == NULL)
+ fatalx("disable_host: invalid table id");
+ table->up--;
+ table->flags |= F_CHANGED;
+ }
+
+ host->up = HOST_UNKNOWN;
+ host->flags |= F_DISABLE;
+ host->flags |= F_DEL;
+ host->flags &= ~(F_ADD);
+
+ imsg_compose(ibuf_hce, IMSG_HOST_DISABLE, 0, 0, &id, sizeof (id));
+ log_debug("disable_host: disabled host %d", host->id);
+ pfe_sync();
+ return (0);
+}
+
+int
+enable_host(struct ctl_conn *c, objid_t id)
+{
+ struct host *host;
+
+ if ((host = host_find(env, id)) == NULL)
+ return (-1);
+
+ if (!(host->flags & F_DISABLE))
+ return (0);
+
+ host->up = HOST_UNKNOWN;
+ host->flags &= ~(F_DISABLE);
+ host->flags &= ~(F_DEL);
+ host->flags &= ~(F_ADD);
+
+ imsg_compose(ibuf_hce, IMSG_HOST_ENABLE, 0, 0, &id, sizeof (id));
+ log_debug("enable_host: enabled host %d", host->id);
+ pfe_sync();
+ return (0);
+}
+
+void
+pfe_sync(void)
+{
+ struct service *service;
+ struct table *active;
+ int backup;
+
+ TAILQ_FOREACH(service, &env->services, entry) {
+ backup = (service->flags & F_BACKUP);
+ service->flags &= ~(F_BACKUP);
+ service->flags &= ~(F_DOWN);
+
+ if (service->flags & F_DISABLE ||
+ (service->table->up == 0 && service->backup->up == 0)) {
+ service->flags |= F_DOWN;
+ active = NULL;
+ } else if (service->table->up == 0 && service->backup->up > 0) {
+ service->flags |= F_BACKUP;
+ active = service->backup;
+ active->flags |= service->table->flags & F_CHANGED;
+ active->flags |= service->backup->flags & F_CHANGED;
+ } else
+ active = service->table;
+
+ if (active != NULL && active->flags & F_CHANGED)
+ sync_table(env, service, active);
+
+ service->table->flags &= ~(F_CHANGED);
+ service->backup->flags &= ~(F_CHANGED);
+
+ if (service->flags & F_DOWN) {
+ if (service->flags & F_ACTIVE_RULESET) {
+ flush_table(env, service);
+ log_debug("pfe_sync: disabling ruleset");
+ service->flags &= ~(F_ACTIVE_RULESET);
+ sync_ruleset(env, service, 0);
+ }
+ } else if (!(service->flags & F_ACTIVE_RULESET)) {
+ log_debug("pfe_sync: enabling ruleset");
+ service->flags |= F_ACTIVE_RULESET;
+ sync_ruleset(env, service, 1);
+ }
+ }
+}
diff --git a/usr.sbin/hoststated/pfe_filter.c b/usr.sbin/hoststated/pfe_filter.c
new file mode 100644
index 00000000000..b7bd7550070
--- /dev/null
+++ b/usr.sbin/hoststated/pfe_filter.c
@@ -0,0 +1,336 @@
+/* $OpenBSD: pfe_filter.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.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/ioctl.h>
+#include <sys/param.h>
+
+#include <net/if.h>
+#include <net/pfvar.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <limits.h>
+#include <fcntl.h>
+#include <event.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "hostated.h"
+
+struct pfdata {
+ int dev;
+ struct pf_anchor *anchor;
+ struct pfioc_trans pft;
+ struct pfioc_trans_e pfte;
+};
+
+int transaction_init(struct hostated *, const char *);
+int transaction_commit(struct hostated *);
+void kill_tables(struct hostated *);
+
+void
+init_filter(struct hostated *env)
+{
+ struct pf_status status;
+
+ if ((env->pf = calloc(1, sizeof(*(env->pf)))) == NULL)
+ fatal("calloc");
+ if ((env->pf->dev = open(PF_SOCKET, O_RDWR)) == -1)
+ fatal("init_filter: cannot open pf socket");
+ if (ioctl(env->pf->dev, DIOCGETSTATUS, &status) == -1)
+ fatal("init_filter: DIOCGETSTATUS");
+ if (!status.running)
+ fatalx("init_filter: pf is disabled");
+ log_debug("init_filter: filter init done");
+}
+
+void
+init_tables(struct hostated *env)
+{
+ int i;
+ struct service *service;
+ struct pfr_table *tables;
+ struct pfioc_table io;
+
+ if ((tables = calloc(env->servicecount, sizeof(*tables))) == NULL)
+ fatal("calloc");
+ i = 0;
+
+ TAILQ_FOREACH(service, &env->services, entry) {
+ (void)strlcpy(tables[i].pfrt_anchor, HOSTATED_ANCHOR "/",
+ sizeof(tables[i].pfrt_anchor));
+ (void)strlcat(tables[i].pfrt_anchor, service->name,
+ sizeof(tables[i].pfrt_anchor));
+ (void)strlcpy(tables[i].pfrt_name, service->name,
+ sizeof(tables[i].pfrt_name));
+ tables[i].pfrt_flags |= PFR_TFLAG_PERSIST;
+ i++;
+ }
+ if (i != env->servicecount)
+ fatalx("init_tables: table count modified");
+
+ memset(&io, 0, sizeof(io));
+ io.pfrio_size = env->servicecount;
+ io.pfrio_esize = sizeof(*tables);
+ io.pfrio_buffer = tables;
+
+ if (ioctl(env->pf->dev, DIOCRADDTABLES, &io) == -1)
+ fatal("init_tables: cannot create tables");
+ log_debug("created %d tables", io.pfrio_nadd);
+
+ if (io.pfrio_nadd == env->servicecount)
+ return;
+
+ /*
+ * clear all tables, since some already existed
+ */
+ TAILQ_FOREACH(service, &env->services, entry)
+ flush_table(env, service);
+}
+
+void
+kill_tables(struct hostated *env) {
+ struct pfioc_table io;
+ struct service *service;
+
+ memset(&io, 0, sizeof(io));
+ TAILQ_FOREACH(service, &env->services, entry) {
+ (void)strlcpy(io.pfrio_table.pfrt_anchor, HOSTATED_ANCHOR "/",
+ sizeof(io.pfrio_table.pfrt_anchor));
+ (void)strlcat(io.pfrio_table.pfrt_anchor, service->name,
+ sizeof(io.pfrio_table.pfrt_anchor));
+ if (ioctl(env->pf->dev, DIOCRCLRTABLES, &io) == -1)
+ fatal("kill_tables: ioctl faile: ioctl failed");
+ }
+ log_debug("kill_tables: deleted %d tables", io.pfrio_ndel);
+}
+
+void
+sync_table(struct hostated *env, struct service *service, struct table *table)
+{
+ int i;
+ struct pfioc_table io;
+ struct pfr_addr *addlist;
+ struct sockaddr_in *sain;
+ struct sockaddr_in6 *sain6;
+ struct host *host;
+
+ if (table == NULL)
+ return;
+
+ if (table->up == 0) {
+ flush_table(env, service);
+ return;
+ }
+
+ if ((addlist = calloc(table->up, sizeof(*addlist))) == NULL)
+ fatal("calloc");
+
+ memset(&io, 0, sizeof(io));
+ io.pfrio_esize = sizeof(struct pfr_addr);
+ io.pfrio_size = table->up;
+ io.pfrio_size2 = 0;
+ io.pfrio_buffer = addlist;
+ (void)strlcpy(io.pfrio_table.pfrt_anchor, HOSTATED_ANCHOR "/",
+ sizeof(io.pfrio_table.pfrt_anchor));
+ (void)strlcat(io.pfrio_table.pfrt_anchor, service->name,
+ sizeof(io.pfrio_table.pfrt_anchor));
+ (void)strlcpy(io.pfrio_table.pfrt_name, service->name,
+ sizeof(io.pfrio_table.pfrt_name));
+
+ i = 0;
+ TAILQ_FOREACH(host, &table->hosts, entry) {
+ if (host->up != 1)
+ continue;
+ memset(&(addlist[i]), 0, sizeof(addlist[i]));
+ switch (host->ss.ss_family) {
+ case AF_INET:
+ sain = (struct sockaddr_in *)&host->ss;
+ addlist[i].pfra_af = AF_INET;
+ memcpy(&(addlist[i].pfra_ip4addr), &sain->sin_addr,
+ sizeof(sain->sin_addr));
+ addlist[i].pfra_net = 32;
+ break;
+ case AF_INET6:
+ sain6 = (struct sockaddr_in6 *)&host->ss;
+ addlist[i].pfra_af = AF_INET6;
+ memcpy(&(addlist[i].pfra_ip6addr), &sain6->sin6_addr,
+ sizeof(sain6->sin6_addr));
+ addlist[i].pfra_net = 128;
+ break;
+ default:
+ fatalx("sync_table: unknown address family");
+ break;
+ }
+ i++;
+ }
+ if (i != table->up)
+ fatalx("sync_table: desynchronized");
+
+ if (ioctl(env->pf->dev, DIOCRSETADDRS, &io) == -1)
+ fatal("sync_table: cannot set address list");
+
+ log_debug("sync_table: table %s: %d added, %d deleted, %d changed",
+ io.pfrio_table.pfrt_name,
+ io.pfrio_nadd, io.pfrio_ndel, io.pfrio_nchange);
+}
+
+void
+flush_table(struct hostated *env, struct service *service)
+{
+ struct pfioc_table io;
+
+ memset(&io, 0, sizeof(io));
+ (void)strlcpy(io.pfrio_table.pfrt_anchor, HOSTATED_ANCHOR "/",
+ sizeof(io.pfrio_table.pfrt_anchor));
+ (void)strlcat(io.pfrio_table.pfrt_anchor, service->name,
+ sizeof(io.pfrio_table.pfrt_anchor));
+ (void)strlcpy(io.pfrio_table.pfrt_name, service->name,
+ sizeof(io.pfrio_table.pfrt_name));
+ if (ioctl(env->pf->dev, DIOCRCLRADDRS, &io) == -1)
+ fatal("flush_table: cannot flush table");
+ log_debug("flush_table: flushed table %s", service->name);
+ return;
+}
+
+int
+transaction_init(struct hostated *env, const char *anchor)
+{
+ env->pf->pft.size = 1;
+ env->pf->pft.esize = sizeof env->pf->pfte;
+ env->pf->pft.array = &env->pf->pfte;
+
+ memset(&env->pf->pfte, 0, sizeof env->pf->pfte);
+ strlcpy(env->pf->pfte.anchor, anchor, PF_ANCHOR_NAME_SIZE);
+ env->pf->pfte.rs_num = PF_RULESET_RDR;
+
+ if (ioctl(env->pf->dev, DIOCXBEGIN, &env->pf->pft) == -1)
+ return (-1);
+ return (0);
+}
+
+int
+transaction_commit(struct hostated *env)
+{
+ if (ioctl(env->pf->dev, DIOCXCOMMIT, &env->pf->pft) == -1)
+ return (-1);
+ return (0);
+}
+
+void
+sync_ruleset(struct hostated *env, struct service *service, int enable)
+{
+ struct pfioc_rule rio;
+ struct pfioc_pooladdr pio;
+ struct sockaddr_in *sain;
+ struct sockaddr_in6 *sain6;
+ struct address *address;
+ char anchor[PF_ANCHOR_NAME_SIZE];
+
+ bzero(anchor, sizeof(anchor));
+ (void)strlcpy(anchor, HOSTATED_ANCHOR "/", sizeof(anchor));
+ (void)strlcat(anchor, service->name, sizeof(anchor));
+ transaction_init(env, anchor);
+
+ if (!enable) {
+ transaction_commit(env);
+ log_debug("sync_ruleset: rules removed");
+ return;
+ }
+
+ TAILQ_FOREACH(address, &service->virts, entry) {
+ memset(&rio, 0, sizeof(rio));
+ memset(&pio, 0, sizeof(pio));
+ (void)strlcpy(rio.anchor, anchor, sizeof(rio.anchor));
+
+ rio.ticket = env->pf->pfte.ticket;
+ if (ioctl(env->pf->dev, DIOCBEGINADDRS, &pio) == -1)
+ fatal("sync_ruleset: cannot initialise address pool");
+
+ rio.pool_ticket = pio.ticket;
+ rio.rule.af = address->ss.ss_family;
+ rio.rule.proto = IPPROTO_TCP;
+ rio.rule.src.addr.type = PF_ADDR_ADDRMASK;
+ rio.rule.dst.addr.type = PF_ADDR_ADDRMASK;
+ rio.rule.dst.port_op = PF_OP_EQ;
+ rio.rule.dst.port[0] = address->port;
+ rio.rule.rtableid = -1; /* stay in the main routing table */
+ rio.rule.action = PF_RDR;
+ if (strlen(service->tag))
+ (void)strlcpy(rio.rule.tagname, service->tag,
+ sizeof(rio.rule.tagname));
+ if (strlen(address->ifname))
+ (void)strlcpy(rio.rule.ifname, address->ifname,
+ sizeof(rio.rule.ifname));
+
+ if (address->ss.ss_family == AF_INET) {
+ sain = (struct sockaddr_in *)&address->ss;
+
+ rio.rule.dst.addr.v.a.addr.addr32[0] =
+ sain->sin_addr.s_addr;
+ rio.rule.dst.addr.v.a.mask.addr32[0] = 0xffffffff;
+
+ } else {
+ sain6 = (struct sockaddr_in6 *)&address->ss;
+
+ memcpy(&rio.rule.dst.addr.v.a.addr.v6,
+ &sain6->sin6_addr.s6_addr,
+ sizeof(sain6->sin6_addr.s6_addr));
+ memset(&rio.rule.dst.addr.v.a.mask.addr8, 0xff, 16);
+ }
+
+ pio.addr.addr.type = PF_ADDR_TABLE;
+ (void)strlcpy(pio.addr.addr.v.tblname, service->name,
+ sizeof(pio.addr.addr.v.tblname));
+ if (ioctl(env->pf->dev, DIOCADDADDR, &pio) == -1)
+ fatal("sync_ruleset: cannot add address to pool");
+
+ rio.rule.rpool.proxy_port[0] = service->table->port;
+ rio.rule.rpool.port_op = PF_OP_EQ;
+ rio.rule.rpool.opts = PF_POOL_ROUNDROBIN;
+
+ if (ioctl(env->pf->dev, DIOCADDRULE, &rio) == -1)
+ fatal("cannot add rule");
+ log_debug("sync_ruleset: rule added");
+ }
+ transaction_commit(env);
+}
+
+void
+flush_rulesets(struct hostated *env)
+{
+ struct service *service;
+ char anchor[PF_ANCHOR_NAME_SIZE];
+
+ kill_tables(env);
+ TAILQ_FOREACH(service, &env->services, entry) {
+ strlcpy(anchor, HOSTATED_ANCHOR "/", sizeof(anchor));
+ strlcat(anchor, service->name, sizeof(anchor));
+ transaction_init(env, anchor);
+ transaction_commit(env);
+ }
+ strlcpy(anchor, HOSTATED_ANCHOR, sizeof(anchor));
+ transaction_init(env, anchor);
+ transaction_commit(env);
+ log_debug("flush_rulesets: flushed rules");
+}
diff --git a/usr.sbin/relayctl/Makefile b/usr.sbin/relayctl/Makefile
new file mode 100644
index 00000000000..cf9a63acd8b
--- /dev/null
+++ b/usr.sbin/relayctl/Makefile
@@ -0,0 +1,16 @@
+# $OpenBSD: Makefile,v 1.1 2006/12/16 11:45:07 reyk Exp $
+
+.PATH: ${.CURDIR}/../hostated
+
+PROG= hostatectl
+SRCS= buffer.c imsg.c log.c hostatectl.c parser.c
+
+MAN= hostatectl.8
+
+CFLAGS+= -Wall -Werror -I${.CURDIR} -I${.CURDIR}/../hostated
+CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes
+CFLAGS+= -Wmissing-declarations
+CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual
+CFLAGS+= -Wsign-compare -Wbounded
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/relayctl/parser.c b/usr.sbin/relayctl/parser.c
new file mode 100644
index 00000000000..a2247a907fc
--- /dev/null
+++ b/usr.sbin/relayctl/parser.c
@@ -0,0 +1,232 @@
+/* $OpenBSD: parser.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.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/socket.h>
+#include <sys/queue.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <event.h>
+
+#include "hostated.h"
+
+#include "parser.h"
+
+enum token_type {
+ NOTOKEN,
+ ENDTOKEN,
+ HOSTID,
+ TABLEID,
+ SERVICEID,
+ KEYWORD
+};
+
+struct token {
+ enum token_type type;
+ const char *keyword;
+ int value;
+ const struct token *next;
+};
+
+static const struct token t_main[];
+static const struct token t_show[];
+static const struct token t_service[];
+static const struct token t_table[];
+static const struct token t_host[];
+static const struct token t_service_id[];
+static const struct token t_table_id[];
+static const struct token t_host_id[];
+
+static const struct token t_main[] = {
+ {KEYWORD, "show", SHOW_SUM, NULL},
+ {KEYWORD, "stop", SHUTDOWN, NULL},
+ {KEYWORD, "service", NULL, t_service},
+ {KEYWORD, "table", NULL, t_table},
+ {KEYWORD, "host", NULL, t_host},
+ {ENDTOKEN, "", NONE, NULL}
+};
+
+static const struct token t_service[] = {
+ {NOTOKEN, "", NONE, NULL},
+ {KEYWORD, "disable", SERV_DISABLE, t_service_id},
+ {KEYWORD, "enable", SERV_ENABLE, t_service_id},
+ {ENDTOKEN, "", NONE, NULL}
+};
+
+static const struct token t_table[] = {
+ {NOTOKEN, "", NONE, NULL},
+ {KEYWORD, "disable", TABLE_DISABLE, t_table_id},
+ {KEYWORD, "enable", TABLE_ENABLE, t_table_id},
+ {ENDTOKEN, "", NONE, NULL}
+};
+
+static const struct token t_host[] = {
+ {NOTOKEN, "", NONE, NULL},
+ {KEYWORD, "disable", HOST_DISABLE, t_host_id},
+ {KEYWORD, "enable", HOST_ENABLE, t_host_id},
+ {ENDTOKEN, "", NONE, NULL}
+};
+
+static const struct token t_service_id[] = {
+ {SERVICEID, "", NONE, NULL},
+ {ENDTOKEN, "", NONE, NULL}
+};
+
+static const struct token t_table_id[] = {
+ {TABLEID, "", NONE, NULL},
+ {ENDTOKEN, "", NONE, NULL}
+};
+
+static const struct token t_host_id[] = {
+ {HOSTID, "", NONE, NULL},
+ {ENDTOKEN, "", NONE, NULL}
+};
+
+static struct parse_result res;
+
+struct parse_result *
+parse(int argc, char *argv[])
+{
+ const struct token *table = t_main;
+ const struct token *match;
+
+ bzero(&res, sizeof(res));
+
+ while (argc > 0) {
+ if ((match = match_token(argv[0], table)) == NULL) {
+ fprintf(stderr, "valid commands/args:\n");
+ show_valid_args(table);
+ return (NULL);
+ }
+
+ argc--;
+ argv++;
+
+ if (match->type == NOTOKEN || match->next == NULL)
+ break;
+
+ table = match->next;
+ }
+
+ if (argc > 0) {
+ fprintf(stderr, "superfluous argument: %s\n", argv[0]);
+ return (NULL);
+ }
+
+ return (&res);
+}
+
+const struct token *
+match_token(const char *word, const struct token table[])
+{
+ u_int i, match;
+ const struct token *t = NULL;
+ const char *errstr;
+
+ match = 0;
+
+ for (i = 0; table[i].type != ENDTOKEN; i++) {
+ switch (table[i].type) {
+ case NOTOKEN:
+ if (word == NULL || strlen(word) == 0) {
+ match++;
+ t = &table[i];
+ }
+ break;
+ case KEYWORD:
+ if (word != NULL && strncmp(word, table[i].keyword,
+ strlen(word)) == 0) {
+ match++;
+ t = &table[i];
+ if (t->value)
+ res.action = t->value;
+ }
+ break;
+ case HOSTID:
+ res.id = strtonum(word, 0, UINT_MAX, &errstr);
+ if (errstr)
+ errx(1, "host id %s is %s", word, errstr);
+ t = &table[i];
+ match++;
+ break;
+ case TABLEID:
+ res.id = strtonum(word, 0, UINT_MAX, &errstr);
+ if (errstr)
+ errx(1, "table id %s is %s", word, errstr);
+ t = &table[i];
+ match++;
+ break;
+ case SERVICEID:
+ res.id = strtonum(word, 0, UINT_MAX, &errstr);
+ if (errstr)
+ errx(1, "service id %s is %s", word, errstr);
+ t = &table[i];
+ match++;
+ break;
+ case ENDTOKEN:
+ break;
+ }
+ }
+
+ if (match != 1) {
+ if (match > 1)
+ fprintf(stderr, "ambiguous argument: %s\n", word);
+ if (match < 1)
+ fprintf(stderr, "unknown argument: %s\n", word);
+ return (NULL);
+ }
+
+ return (t);
+}
+
+void
+show_valid_args(const struct token table[])
+{
+ int i;
+
+ for (i = 0; table[i].type != ENDTOKEN; i++) {
+ switch (table[i].type) {
+ case NOTOKEN:
+ fprintf(stderr, " <cr>\n");
+ break;
+ case KEYWORD:
+ fprintf(stderr, " %s\n", table[i].keyword);
+ break;
+ case SERVICEID:
+ fprintf(stderr, " <serviceid>\n");
+ break;
+ case TABLEID:
+ fprintf(stderr, " <tableid>\n");
+ break;
+ case HOSTID:
+ fprintf(stderr, " <hostid>\n");
+ break;
+ case ENDTOKEN:
+ break;
+ }
+ }
+}
diff --git a/usr.sbin/relayctl/parser.h b/usr.sbin/relayctl/parser.h
new file mode 100644
index 00000000000..2fef7464faf
--- /dev/null
+++ b/usr.sbin/relayctl/parser.h
@@ -0,0 +1,39 @@
+/* $OpenBSD: parser.h,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.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 actions {
+ NONE,
+ SHOW_SUM,
+ SERV_DISABLE,
+ SERV_ENABLE,
+ TABLE_DISABLE,
+ TABLE_ENABLE,
+ HOST_DISABLE,
+ HOST_ENABLE,
+ SHUTDOWN,
+ RELOAD
+};
+
+struct parse_result {
+ objid_t id;
+ enum actions action;
+};
+
+struct parse_result *parse(int, char *[]);
+const struct token *match_token(const char *, const struct token []);
+void show_valid_args(const struct token []);
diff --git a/usr.sbin/relayctl/relayctl.8 b/usr.sbin/relayctl/relayctl.8
new file mode 100644
index 00000000000..4a5e8c14b90
--- /dev/null
+++ b/usr.sbin/relayctl/relayctl.8
@@ -0,0 +1,69 @@
+.\" $OpenBSD: relayctl.8,v 1.1 2006/12/16 11:45:07 reyk Exp $
+.\"
+.\" Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd November 1, 2006
+.Dt HOSTATECTL 8
+.Os
+.Sh NAME
+.Nm hostatectl
+.Nd control the host status daemon
+.Sh SYNOPSIS
+.Nm
+.Ar command
+.Op Ar arguments ...
+.Sh DESCRIPTION
+The
+.Nm
+program controls the
+.Xr hostated 8
+daemon.
+.Pp
+The following commands are available:
+.Bl -tag -width Ds
+.It Cm show
+.It Cm show summary
+Show status of services, tables and hosts.
+.It Cm service disable id
+Disable a service. If it has
+.Xr pf 4
+redirection rules installed, remove them. Mark the service's main table and -
+if applicable - backup table disabled as well.
+.It Cm service enable id
+Enable a service. Mark the service's main table and - if applicable - backup
+table enabled as well.
+.It Cm table disable id
+Disable a table. Consider all hosts disabled. If it is a
+main table of a service which has a non-empty backup table,
+swap the contents of the
+.Xr pf 4
+table with those of the backup table.
+.It Cm table enable id
+Enable a table. Start doing checks for all hosts that aren't
+individually disabled again.
+.It Cm host disable id
+Disable a host. Treat it as though it were always down.
+.It Cm host enable id
+Enable the host. Start checking its health again.
+.El
+.Sh FILES
+.Bl -tag -width "/var/run/hostated.sockXX" -compact
+.It /var/run/hostated.sock
+Unix-domain socket used for communication with
+.Xr hostated 8 .
+.El
+.Sh SEE ALSO
+.Xr hostated.conf 5 ,
+.Xr hostated 8
diff --git a/usr.sbin/relayctl/relayctl.c b/usr.sbin/relayctl/relayctl.c
new file mode 100644
index 00000000000..ffb29147dc8
--- /dev/null
+++ b/usr.sbin/relayctl/relayctl.c
@@ -0,0 +1,276 @@
+/* $OpenBSD: relayctl.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org>
+ * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2003 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/socket.h>
+#include <sys/queue.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <event.h>
+
+#include "hostated.h"
+#include "parser.h"
+
+__dead void usage(void);
+int show_summary_msg(struct imsg *);
+int show_command_output(struct imsg *);
+char *print_service_status(int);
+char *print_host_status(int, int);
+char *print_table_status(int, int);
+
+struct imsgbuf *ibuf;
+
+__dead void
+usage(void)
+{
+ extern char *__progname;
+
+ fprintf(stderr, "usage: %s <command> [arg [...]]\n", __progname);
+ exit(1);
+}
+
+/* dummy function so that ospfctl does not need libevent */
+void
+imsg_event_add(struct imsgbuf *i)
+{
+ /* nothing */
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct sockaddr_un sun;
+ struct parse_result *res;
+ struct imsg imsg;
+ int ctl_sock;
+ int done = 0;
+ int n;
+
+ /* parse options */
+ if ((res = parse(argc - 1, argv + 1)) == NULL)
+ exit(1);
+
+ /* connect to ospfd control socket */
+ if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
+ err(1, "socket");
+
+ bzero(&sun, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ strlcpy(sun.sun_path, HOSTATED_SOCKET, sizeof(sun.sun_path));
+ if (connect(ctl_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1)
+ err(1, "connect: %s", HOSTATED_SOCKET);
+
+ if ((ibuf = malloc(sizeof(struct imsgbuf))) == NULL)
+ err(1, NULL);
+ imsg_init(ibuf, ctl_sock, NULL);
+ done = 0;
+
+ /* process user request */
+ switch (res->action) {
+ case NONE:
+ usage();
+ /* not reached */
+ case SHOW_SUM:
+ imsg_compose(ibuf, IMSG_CTL_SHOW_SUM, 0, 0, NULL, 0);
+ printf("type\t\%4s\t%-16s\tstatus\n\n", "id", "name");
+ break;
+ case SERV_ENABLE:
+ imsg_compose(ibuf, IMSG_CTL_SERVICE_ENABLE, 0, 0,
+ &res->id, sizeof(res->id));
+ break;
+ case SERV_DISABLE:
+ imsg_compose(ibuf, IMSG_CTL_SERVICE_DISABLE, 0, 0,
+ &res->id, sizeof(res->id));
+ break;
+ case TABLE_ENABLE:
+ imsg_compose(ibuf, IMSG_CTL_TABLE_ENABLE, 0, 0,
+ &res->id, sizeof(res->id));
+ break;
+ case TABLE_DISABLE:
+ imsg_compose(ibuf, IMSG_CTL_TABLE_DISABLE, 0, 0,
+ &res->id, sizeof(res->id));
+ break;
+ case HOST_ENABLE:
+ imsg_compose(ibuf, IMSG_CTL_HOST_ENABLE, 0, 0,
+ &res->id, sizeof(res->id));
+ break;
+ case HOST_DISABLE:
+ imsg_compose(ibuf, IMSG_CTL_HOST_DISABLE, 0, 0,
+ &res->id, sizeof(res->id));
+ break;
+ case SHUTDOWN:
+ imsg_compose(ibuf, IMSG_CTL_SHUTDOWN, 0, 0, NULL, 0);
+ break;
+ case RELOAD:
+ imsg_compose(ibuf, IMSG_CTL_RELOAD, 0, 0, NULL, 0);
+ break;
+ }
+
+ while (ibuf->w.queued)
+ if (msgbuf_write(&ibuf->w) < 0)
+ err(1, "write error");
+
+ while (!done) {
+ if ((n = imsg_read(ibuf)) == -1)
+ errx(1, "imsg_read error");
+ if (n == 0)
+ errx(1, "pipe closed");
+
+ while (!done) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ errx(1, "imsg_get error");
+ if (n == 0)
+ break;
+ switch (res->action) {
+ case SHOW_SUM:
+ done = show_summary_msg(&imsg);
+ break;
+ case SERV_DISABLE:
+ case SERV_ENABLE:
+ case TABLE_DISABLE:
+ case TABLE_ENABLE:
+ case HOST_DISABLE:
+ case HOST_ENABLE:
+ done = show_command_output(&imsg);
+ break;
+ case RELOAD:
+ case SHUTDOWN:
+ case NONE:
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ }
+ close(ctl_sock);
+ free(ibuf);
+
+ return (0);
+}
+
+int
+show_summary_msg(struct imsg *imsg)
+{
+ struct service *service;
+ struct table *table;
+ struct host *host;
+
+ switch (imsg->hdr.type) {
+ case IMSG_CTL_SERVICE:
+ service = imsg->data;
+ printf("service\t%4u\t%-16s\t%s\n",
+ service->id, service->name,
+ print_service_status(service->flags));
+ break;
+ case IMSG_CTL_TABLE:
+ table = imsg->data;
+ printf("table\t%4u\t%-16s\t%s",
+ table->id, table->name,
+ print_table_status(table->up, table->flags));
+ printf("\n");
+ break;
+ case IMSG_CTL_HOST:
+ host = imsg->data;
+ printf("host\t%4u\t%-16s\t%s\n",
+ host->id, host->name,
+ print_host_status(host->up, host->flags));
+ break;
+ case IMSG_CTL_END:
+ return (1);
+ default:
+ errx(1, "wrong message in summary: %u", imsg->hdr.type);
+ break;
+ }
+ return (0);
+}
+
+int
+show_command_output(struct imsg *imsg)
+{
+ switch (imsg->hdr.type) {
+ case IMSG_CTL_OK:
+ printf("command succeeded\n");
+ break;
+ case IMSG_CTL_FAIL:
+ printf("command failed\n");
+ break;
+ default:
+ errx(1, "wrong message in summary: %u", imsg->hdr.type);
+ }
+ return (1);
+}
+
+char *
+print_service_status(int flags)
+{
+ if (flags & F_DISABLE) {
+ return("disabled");
+ } else if (flags & F_DOWN) {
+ return("down");
+ } else if (flags & F_BACKUP) {
+ return("active (using backup table)");
+ } else
+ return("active");
+}
+
+char *
+print_table_status(int up, int fl)
+{
+ static char buf[1024];
+
+ bzero(buf, sizeof(buf));
+
+ if (fl & F_DISABLE) {
+ snprintf(buf, sizeof(buf) - 1, "disabled");
+ } else if (!up) {
+ snprintf(buf, sizeof(buf) - 1, "empty");
+ } else
+ snprintf(buf, sizeof(buf) - 1, "active (%d hosts up)",
+ up);
+ return (buf);
+}
+
+char *
+print_host_status(int status, int fl)
+{
+ if (fl & F_DISABLE)
+ return("disabled");
+
+ switch (status) {
+ case HOST_DOWN:
+ return("down");
+ case HOST_UNKNOWN:
+ return("unknown");
+ case HOST_UP:
+ return("up");
+ default:
+ errx(1, "invalid status: %d", status);
+ }
+}
diff --git a/usr.sbin/relayd/Makefile b/usr.sbin/relayd/Makefile
new file mode 100644
index 00000000000..c04e70f3036
--- /dev/null
+++ b/usr.sbin/relayd/Makefile
@@ -0,0 +1,17 @@
+# $OpenBSD: Makefile,v 1.1 2006/12/16 11:45:07 reyk Exp $
+
+PROG= hostated
+SRCS= parse.y log.c control.c buffer.c imsg.c hostated.c \
+ pfe.c pfe_filter.c hce.c check_icmp.c check_tcp.c check_http.c
+MAN= hostated.8 hostated.conf.5
+
+LDADD= -levent
+DPADD= ${LIBEVENT}
+CFLAGS+= -Wall -Werror -I${.CURDIR}
+CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes
+CFLAGS+= -Wmissing-declarations
+CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual
+CFLAGS+= -Wsign-compare -Wbounded
+CLEANFILES+= y.tab.h
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/relayd/buffer.c b/usr.sbin/relayd/buffer.c
new file mode 100644
index 00000000000..3abe27b1e62
--- /dev/null
+++ b/usr.sbin/relayd/buffer.c
@@ -0,0 +1,222 @@
+/* $OpenBSD: buffer.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * 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/uio.h>
+#include <sys/param.h>
+#include <net/if.h>
+#include <errno.h>
+#include <event.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "hostated.h"
+
+int buf_realloc(struct buf *, size_t);
+void buf_enqueue(struct msgbuf *, struct buf *);
+void buf_dequeue(struct msgbuf *, struct buf *);
+
+struct buf *
+buf_open(size_t len)
+{
+ struct buf *buf;
+
+ if ((buf = calloc(1, sizeof(struct buf))) == NULL)
+ return (NULL);
+ if ((buf->buf = malloc(len)) == NULL) {
+ free(buf);
+ return (NULL);
+ }
+ buf->size = buf->max = len;
+
+ return (buf);
+}
+
+struct buf *
+buf_dynamic(size_t len, size_t max)
+{
+ struct buf *buf;
+
+ if (max < len)
+ return (NULL);
+
+ if ((buf = buf_open(len)) == NULL)
+ return (NULL);
+
+ if (max > 0)
+ buf->max = max;
+
+ return (buf);
+}
+
+int
+buf_realloc(struct buf *buf, size_t len)
+{
+ u_char *b;
+
+ /* on static buffers max is eq size and so the following fails */
+ if (buf->wpos + len > buf->max) {
+ errno = ENOMEM;
+ return (-1);
+ }
+
+ b = realloc(buf->buf, buf->wpos + len);
+ if (b == NULL)
+ return (-1);
+ buf->buf = b;
+ buf->size = buf->wpos + len;
+
+ return (0);
+}
+
+int
+buf_add(struct buf *buf, void *data, size_t len)
+{
+ if (buf->wpos + len > buf->size)
+ if (buf_realloc(buf, len) == -1)
+ return (-1);
+
+ memcpy(buf->buf + buf->wpos, data, len);
+ buf->wpos += len;
+ return (0);
+}
+
+void *
+buf_reserve(struct buf *buf, size_t len)
+{
+ void *b;
+
+ if (buf->wpos + len > buf->size)
+ if (buf_realloc(buf, len) == -1)
+ return (NULL);
+
+ b = buf->buf + buf->wpos;
+ buf->wpos += len;
+ return (b);
+}
+
+void *
+buf_seek(struct buf *buf, size_t pos, size_t len)
+{
+ /* only allowed to seek in already written parts */
+ if (pos + len > buf->wpos)
+ return (NULL);
+
+ return (buf->buf + pos);
+}
+
+int
+buf_close(struct msgbuf *msgbuf, struct buf *buf)
+{
+ buf_enqueue(msgbuf, buf);
+ return (1);
+}
+
+void
+buf_free(struct buf *buf)
+{
+ free(buf->buf);
+ free(buf);
+}
+
+void
+msgbuf_init(struct msgbuf *msgbuf)
+{
+ msgbuf->queued = 0;
+ msgbuf->fd = -1;
+ TAILQ_INIT(&msgbuf->bufs);
+}
+
+void
+msgbuf_clear(struct msgbuf *msgbuf)
+{
+ struct buf *buf;
+
+ while ((buf = TAILQ_FIRST(&msgbuf->bufs)) != NULL)
+ buf_dequeue(msgbuf, buf);
+}
+
+int
+msgbuf_write(struct msgbuf *msgbuf)
+{
+ struct iovec iov[IOV_MAX];
+ struct buf *buf, *next;
+ int i = 0;
+ ssize_t n;
+ struct msghdr msg;
+
+ bzero(&iov, sizeof(iov));
+ bzero(&msg, sizeof(msg));
+ TAILQ_FOREACH(buf, &msgbuf->bufs, entry) {
+ if (i >= IOV_MAX)
+ break;
+ iov[i].iov_base = buf->buf + buf->rpos;
+ iov[i].iov_len = buf->size - buf->rpos;
+ i++;
+ }
+
+ msg.msg_iov = iov;
+ msg.msg_iovlen = i;
+
+ if ((n = sendmsg(msgbuf->fd, &msg, 0)) == -1) {
+ if (errno == EAGAIN || errno == ENOBUFS ||
+ errno == EINTR) /* try later */
+ return (0);
+ else
+ return (-1);
+ }
+
+ if (n == 0) { /* connection closed */
+ errno = 0;
+ return (-2);
+ }
+
+ for (buf = TAILQ_FIRST(&msgbuf->bufs); buf != NULL && n > 0;
+ buf = next) {
+ next = TAILQ_NEXT(buf, entry);
+ if (buf->rpos + n >= buf->size) {
+ n -= buf->size - buf->rpos;
+ buf_dequeue(msgbuf, buf);
+ } else {
+ buf->rpos += n;
+ n = 0;
+ }
+ }
+
+ return (0);
+}
+
+void
+buf_enqueue(struct msgbuf *msgbuf, struct buf *buf)
+{
+ TAILQ_INSERT_TAIL(&msgbuf->bufs, buf, entry);
+ msgbuf->queued++;
+}
+
+void
+buf_dequeue(struct msgbuf *msgbuf, struct buf *buf)
+{
+ TAILQ_REMOVE(&msgbuf->bufs, buf, entry);
+ msgbuf->queued--;
+ buf_free(buf);
+}
diff --git a/usr.sbin/relayd/check_icmp.c b/usr.sbin/relayd/check_icmp.c
new file mode 100644
index 00000000000..df92b444af9
--- /dev/null
+++ b/usr.sbin/relayd/check_icmp.c
@@ -0,0 +1,212 @@
+/* $OpenBSD: check_icmp.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.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/param.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/icmp6.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+#include <limits.h>
+#include <event.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "hostated.h"
+
+int check_icmp6(struct host *, int, int);
+int check_icmp4(struct host *, int, int);
+int in_cksum(u_short *, int);
+
+int check_icmp(struct host *host, int s, int s6, int timeout)
+{
+ if (host->ss.ss_family == AF_INET)
+ return (check_icmp4(host, s, timeout));
+ else
+ return (check_icmp6(host, s6, timeout));
+}
+
+int check_icmp6(struct host *host, int s, int timeout)
+{
+ struct sockaddr *to;
+ struct icmp6_hdr *icp;
+ int ident;
+ ssize_t i;
+ int cc;
+ int datalen = (64 - 8);
+ u_char packet[datalen];
+ fd_set fdset;
+ socklen_t len;
+ struct timeval tv;
+
+ to = (struct sockaddr *)&host->ss;
+ ident = getpid() & 0xFFFF;
+ len = sizeof(struct sockaddr_in6);
+
+ bzero(&packet, sizeof(packet));
+ icp = (struct icmp6_hdr *)packet;
+ icp->icmp6_type = ICMP6_ECHO_REQUEST;
+ icp->icmp6_code = 0;
+ icp->icmp6_seq = 1;
+ icp->icmp6_id = ident;
+
+ memset((packet + sizeof(*icp)), 'X', datalen);
+ cc = datalen + 8;
+
+ i = sendto(s, packet, cc, 0, to, len);
+
+ if (i < 0 || i != cc) {
+ log_warn("check_icmp6: cannot send ping");
+ return (HOST_UNKNOWN);
+ }
+
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = timeout % 1000;
+ FD_ZERO(&fdset);
+ FD_SET(s, &fdset);
+ switch (select(s + 1, &fdset, NULL, NULL, &tv)) {
+ case -1:
+ if (errno == EINTR) {
+ log_warnx("check_icmp6: interrupted");
+ return (HOST_UNKNOWN);
+ } else
+ fatal("check_icmp6: select");
+ case 0:
+ log_debug("check_icmp6: timeout");
+ return (HOST_DOWN);
+ default:
+ bzero(&packet, sizeof(packet));
+ i = recvfrom(s, packet, cc, 0, to, &len);
+ if (i < 0 || i != cc) {
+ log_warn("check_icmp6: did not receive valid ping");
+ return (HOST_DOWN);
+ }
+ icp = (struct icmp6_hdr *)(packet);
+ if (icp->icmp6_id != ident) {
+ log_warnx("check_icmp6: did not receive valid ident");
+ return (HOST_DOWN);
+ }
+ break;
+ }
+ return (HOST_UP);
+}
+
+int check_icmp4(struct host *host, int s, int timeout)
+{
+ struct sockaddr *to;
+ struct icmp *icp;
+ int ident;
+ ssize_t i;
+ int cc;
+ int datalen = (64 - 8);
+ u_char packet[datalen];
+ fd_set fdset;
+ socklen_t len;
+ struct timeval tv;
+
+ to = (struct sockaddr *)&host->ss;
+ ident = getpid() & 0xFFFF;
+ len = sizeof(struct sockaddr_in);
+
+ bzero(&packet, sizeof(packet));
+ icp = (struct icmp *)packet;
+ icp->icmp_type = htons(ICMP_ECHO);
+ icp->icmp_code = 0;
+ icp->icmp_seq = htons(1);
+ icp->icmp_id = htons(ident);
+ icp->icmp_cksum = 0;
+
+ memset(icp->icmp_data, 'X', datalen);
+ cc = datalen + 8;
+ icp->icmp_cksum = in_cksum((u_short *)icp, cc);
+
+ i = sendto(s, packet, cc, 0, to, len);
+
+ if (i < 0 || i != cc) {
+ log_warn("check_icmp4: cannot send ping");
+ return (HOST_UNKNOWN);
+ }
+
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = timeout % 1000;
+ FD_ZERO(&fdset);
+ FD_SET(s, &fdset);
+ switch (select(s + 1, &fdset, NULL, NULL, &tv)) {
+ case -1:
+ if (errno == EINTR) {
+ log_warnx("check_icmp4: ping interrupted");
+ return (HOST_UNKNOWN);
+ } else
+ fatal("check_icmp4: select");
+ case 0:
+ log_debug("check_icmp4: timeout");
+ return (HOST_DOWN);
+ default:
+ bzero(&packet, sizeof(packet));
+ i = recvfrom(s, packet, cc, 0, to, &len);
+ if (i < 0 || i != cc) {
+ log_warn("check_icmp4: did not receive valid ping");
+ return (HOST_DOWN);
+ }
+ icp = (struct icmp *)(packet + sizeof(struct ip));
+ if (ntohs(icp->icmp_id) != ident) {
+ log_warnx("check_icmp4: did not receive valid ident");
+ return (HOST_DOWN);
+ }
+ break;
+ }
+ return (HOST_UP);
+}
+
+/* from ping.c */
+int
+in_cksum(u_short *addr, int len)
+{
+ int nleft = len;
+ u_short *w = addr;
+ int sum = 0;
+ u_short answer = 0;
+
+ /*
+ * Our algorithm is simple, using a 32 bit accumulator (sum), we add
+ * sequential 16 bit words to it, and at the end, fold back all the
+ * carry bits from the top 16 bits into the lower 16 bits.
+ */
+ while (nleft > 1) {
+ sum += *w++;
+ nleft -= 2;
+ }
+
+ /* mop up an odd byte, if necessary */
+ if (nleft == 1) {
+ *(u_char *)(&answer) = *(u_char *)w ;
+ sum += answer;
+ }
+
+ /* add back carry outs from top 16 bits to low 16 bits */
+ sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
+ sum += (sum >> 16); /* add carry */
+ answer = ~sum; /* truncate to 16 bits */
+ return(answer);
+}
diff --git a/usr.sbin/relayd/check_tcp.c b/usr.sbin/relayd/check_tcp.c
new file mode 100644
index 00000000000..5ef386731f9
--- /dev/null
+++ b/usr.sbin/relayd/check_tcp.c
@@ -0,0 +1,109 @@
+/* $OpenBSD: check_tcp.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.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/param.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <limits.h>
+#include <event.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include "hostated.h"
+
+int
+check_tcp(struct host *host, struct table *table)
+{
+ int sock;
+
+ if ((sock = tcp_connect(host, table)) <= 0)
+ return (sock);
+ close(sock);
+ return (HOST_UP);
+}
+
+int
+tcp_connect(struct host *host, struct table *table)
+{
+ int s;
+ socklen_t len;
+ struct timeval tv;
+ struct sockaddr sa;
+ fd_set fdset;
+
+ switch (host->ss.ss_family) {
+ case AF_INET:
+ ((struct sockaddr_in *)&host->ss)->sin_port =
+ htons(table->port);
+ break;
+ case AF_INET6:
+ ((struct sockaddr_in6 *)&host->ss)->sin6_port =
+ htons(table->port);
+ break;
+ }
+
+ len = ((struct sockaddr *)&host->ss)->sa_len;
+
+ if ((s = socket(host->ss.ss_family, SOCK_STREAM, 0)) == -1)
+ fatal("check_tcp: cannot create socket");
+
+ if (fcntl(s, F_SETFL, O_NONBLOCK) == -1)
+ fatal("check_tcp: cannot set non blocking socket");
+
+ if (connect(s, (struct sockaddr *)&host->ss, len) == -1) {
+ if (errno != EINPROGRESS && errno != EWOULDBLOCK) {
+ close(s);
+ return (HOST_DOWN);
+ }
+ } else
+ return (s);
+
+ tv.tv_sec = table->timeout / 1000;
+ tv.tv_usec = table->timeout % 1000;
+ FD_ZERO(&fdset);
+ FD_SET(s, &fdset);
+
+ switch(select(s + 1, NULL, &fdset, NULL, &tv)) {
+ case -1:
+ if (errno != EINTR)
+ fatal("check_tcp: select");
+ else
+ return(HOST_UNKNOWN);
+ case 0:
+ close(s);
+ return (HOST_DOWN);
+ default:
+ if (getpeername(s, &sa, &len) == -1) {
+ if (errno == ENOTCONN) {
+ close(s);
+ return (HOST_DOWN);
+ } else {
+ log_debug("check_tcp: unknown peername");
+ close(s);
+ return (HOST_UNKNOWN);
+ }
+ } else
+ return (s);
+ }
+ return (HOST_UNKNOWN);
+}
diff --git a/usr.sbin/relayd/control.c b/usr.sbin/relayd/control.c
new file mode 100644
index 00000000000..2f994e458f4
--- /dev/null
+++ b/usr.sbin/relayd/control.c
@@ -0,0 +1,340 @@
+/* $OpenBSD: control.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * 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/queue.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <net/if.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include "hostated.h"
+
+#define CONTROL_BACKLOG 5
+
+struct ctl_connlist ctl_conns;
+
+int control_imsg_relay(struct imsg *imsg);
+
+struct ctl_conn *control_connbyfd(int);
+struct ctl_conn *control_connbypid(pid_t);
+void control_close(int);
+
+int
+control_init(void)
+{
+ struct sockaddr_un sun;
+ int fd;
+ mode_t old_umask;
+
+ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+ log_warn("control_init: socket");
+ return (-1);
+ }
+
+ bzero(&sun, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ strlcpy(sun.sun_path, HOSTATED_SOCKET, sizeof(sun.sun_path));
+
+ if (unlink(HOSTATED_SOCKET) == -1)
+ if (errno != ENOENT) {
+ log_warn("control_init: unlink %s", HOSTATED_SOCKET);
+ close(fd);
+ return (-1);
+ }
+
+ old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
+ if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
+ log_warn("control_init: bind: %s", HOSTATED_SOCKET);
+ close(fd);
+ umask(old_umask);
+ return (-1);
+ }
+ umask(old_umask);
+
+ if (chmod(HOSTATED_SOCKET, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) {
+ log_warn("control_init: chmod");
+ close(fd);
+ (void)unlink(HOSTATED_SOCKET);
+ return (-1);
+ }
+
+ session_socket_blockmode(fd, BM_NONBLOCK);
+ control_state.fd = fd;
+
+ return (0);
+}
+
+int
+control_listen(void)
+{
+
+ if (listen(control_state.fd, CONTROL_BACKLOG) == -1) {
+ log_warn("control_listen: listen");
+ return (-1);
+ }
+
+ event_set(&control_state.ev, control_state.fd, EV_READ | EV_PERSIST,
+ control_accept, NULL);
+ event_add(&control_state.ev, NULL);
+
+ return (0);
+}
+
+void
+control_cleanup(void)
+{
+
+ unlink(HOSTATED_SOCKET);
+}
+
+/* ARGSUSED */
+void
+control_accept(int listenfd, short event, void *arg)
+{
+ int connfd;
+ socklen_t len;
+ struct sockaddr_un sun;
+ struct ctl_conn *c;
+
+ len = sizeof(sun);
+ if ((connfd = accept(listenfd,
+ (struct sockaddr *)&sun, &len)) == -1) {
+ if (errno != EWOULDBLOCK && errno != EINTR)
+ log_warn("control_accept");
+ return;
+ }
+
+ session_socket_blockmode(connfd, BM_NONBLOCK);
+
+ if ((c = malloc(sizeof(struct ctl_conn))) == NULL) {
+ log_warn("control_accept");
+ return;
+ }
+
+ imsg_init(&c->ibuf, connfd, control_dispatch_imsg);
+ c->ibuf.events = EV_READ;
+ event_set(&c->ibuf.ev, c->ibuf.fd, c->ibuf.events,
+ c->ibuf.handler, &c->ibuf);
+ event_add(&c->ibuf.ev, NULL);
+
+ TAILQ_INSERT_TAIL(&ctl_conns, c, entry);
+}
+
+struct ctl_conn *
+control_connbyfd(int fd)
+{
+ struct ctl_conn *c;
+
+ for (c = TAILQ_FIRST(&ctl_conns); c != NULL && c->ibuf.fd != fd;
+ c = TAILQ_NEXT(c, entry))
+ ; /* nothing */
+
+ return (c);
+}
+
+struct ctl_conn *
+control_connbypid(pid_t pid)
+{
+ struct ctl_conn *c;
+
+ for (c = TAILQ_FIRST(&ctl_conns); c != NULL && c->ibuf.pid != pid;
+ c = TAILQ_NEXT(c, entry))
+ ; /* nothing */
+
+ return (c);
+}
+
+void
+control_close(int fd)
+{
+ struct ctl_conn *c;
+
+ if ((c = control_connbyfd(fd)) == NULL)
+ log_warn("control_close: fd %d: not found", fd);
+
+ msgbuf_clear(&c->ibuf.w);
+ TAILQ_REMOVE(&ctl_conns, c, entry);
+
+ event_del(&c->ibuf.ev);
+ close(c->ibuf.fd);
+ free(c);
+}
+
+/* ARGSUSED */
+void
+control_dispatch_imsg(int fd, short event, void *arg)
+{
+ struct ctl_conn *c;
+ struct imsg imsg;
+ objid_t id;
+ int n;
+
+ if ((c = control_connbyfd(fd)) == NULL) {
+ log_warn("control_dispatch_imsg: fd %d: not found", fd);
+ return;
+ }
+
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(&c->ibuf)) <= 0) {
+ control_close(fd);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&c->ibuf.w) < 0) {
+ control_close(fd);
+ return;
+ }
+ imsg_event_add(&c->ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(&c->ibuf, &imsg)) == -1) {
+ control_close(fd);
+ return;
+ }
+
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_CTL_SHOW_SUM:
+ show(c);
+ break;
+ case IMSG_CTL_SERVICE_DISABLE:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id))
+ fatalx("invalid imsg header len");
+ memcpy(&id, imsg.data, sizeof(id));
+ if (disable_service(c, id))
+ imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0,
+ NULL, 0);
+ else
+ imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0,
+ NULL, 0);
+ break;
+ case IMSG_CTL_SERVICE_ENABLE:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id))
+ fatalx("invalid imsg header len");
+ memcpy(&id, imsg.data, sizeof(id));
+ if (enable_service(c, id))
+ imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0,
+ NULL, 0);
+ else
+ imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0,
+ NULL, 0);
+ break;
+ case IMSG_CTL_TABLE_DISABLE:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id))
+ fatalx("invalid imsg header len");
+ memcpy(&id, imsg.data, sizeof(id));
+ if (disable_table(c, id))
+ imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0,
+ NULL, 0);
+ else
+ imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0,
+ NULL, 0);
+ break;
+ case IMSG_CTL_TABLE_ENABLE:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id))
+ fatalx("invalid imsg header len");
+ memcpy(&id, imsg.data, sizeof(id));
+ if (enable_table(c, id))
+ imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0,
+ NULL, 0);
+ else
+ imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0,
+ NULL, 0);
+ break;
+ case IMSG_CTL_HOST_DISABLE:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id))
+ fatalx("invalid imsg header len");
+ memcpy(&id, imsg.data, sizeof(id));
+ if (disable_host(c, id))
+ imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0,
+ NULL, 0);
+ else
+ imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0,
+ NULL, 0);
+ break;
+ case IMSG_CTL_HOST_ENABLE:
+ if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id))
+ fatalx("invalid imsg header len");
+ memcpy(&id, imsg.data, sizeof(id));
+ if (enable_host(c, id))
+ imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0,
+ NULL, 0);
+ else
+ imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0,
+ NULL, 0);
+ break;
+ case IMSG_CTL_SHUTDOWN:
+ case IMSG_CTL_RELOAD:
+ imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0, NULL, 0);
+ break;
+ default:
+ log_debug("control_dispatch_imsg: "
+ "error handling imsg %d", imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+
+ imsg_event_add(&c->ibuf);
+}
+
+int
+control_imsg_relay(struct imsg *imsg)
+{
+ struct ctl_conn *c;
+
+ if ((c = control_connbypid(imsg->hdr.pid)) == NULL)
+ return (0);
+
+ return (imsg_compose(&c->ibuf, imsg->hdr.type, 0, imsg->hdr.pid,
+ imsg->data, imsg->hdr.len - IMSG_HEADER_SIZE));
+}
+
+void
+session_socket_blockmode(int fd, enum blockmodes bm)
+{
+ int flags;
+
+ if ((flags = fcntl(fd, F_GETFL, 0)) == -1)
+ fatal("fnctl F_GETFL");
+
+ if (bm == BM_NONBLOCK)
+ flags |= O_NONBLOCK;
+ else
+ flags &= ~O_NONBLOCK;
+
+ if ((flags = fcntl(fd, F_SETFL, flags)) == -1)
+ fatal("fnctl F_SETFL");
+}
diff --git a/usr.sbin/relayd/hce.c b/usr.sbin/relayd/hce.c
new file mode 100644
index 00000000000..7144357538b
--- /dev/null
+++ b/usr.sbin/relayd/hce.c
@@ -0,0 +1,318 @@
+/* $OpenBSD: hce.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/queue.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <net/if.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <err.h>
+#include <pwd.h>
+
+#include "hostated.h"
+
+void hce_sig_handler(int sig, short, void *);
+void hce_shutdown(void);
+void hce_dispatch_imsg(int, short, void *);
+void hce_dispatch_parent(int, short, void *);
+void hce_launch_checks(int, short, void *);
+
+static struct hostated *env = NULL;
+struct imsgbuf *ibuf_pfe;
+struct imsgbuf *ibuf_main;
+
+void
+hce_sig_handler(int sig, short event, void *arg)
+{
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ hce_shutdown();
+ default:
+ fatalx("hce_sig_handler: unexpected signal");
+ }
+}
+
+pid_t
+hce(struct hostated *x_env, int pipe_parent2pfe[2], int pipe_parent2hce[2],
+ int pipe_pfe2hce[2])
+{
+ pid_t pid;
+ struct passwd *pw;
+ struct timeval tv;
+ struct event ev_sigint;
+ struct event ev_sigterm;
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("hce: cannot fork");
+ case 0:
+ break;
+ default:
+ return (pid);
+ }
+
+ env = x_env;
+
+ /* this is needed for icmp tests */
+ if ((env->icmp_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0)
+ err(1, "socket");
+ if ((env->icmp6_sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0)
+ err(1, "socket");
+
+ if ((pw = getpwnam(HOSTATED_USER)) == NULL)
+ fatal("hce: getpwnam");
+
+ if (chroot(pw->pw_dir) == -1)
+ fatal("hce: chroot");
+ if (chdir("/") == -1)
+ fatal("hce: chdir(\"/\")");
+
+ setproctitle("host check engine");
+ hostated_process = PROC_HCE;
+
+ 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("hce: can't drop privileges");
+
+ event_init();
+
+ signal_set(&ev_sigint, SIGINT, hce_sig_handler, NULL);
+ signal_set(&ev_sigterm, SIGTERM, hce_sig_handler, NULL);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+
+ /* setup pipes */
+ close(pipe_pfe2hce[1]);
+ close(pipe_parent2hce[0]);
+ close(pipe_parent2pfe[0]);
+ close(pipe_parent2pfe[1]);
+
+ if ((ibuf_pfe = calloc(1, sizeof(struct imsgbuf))) == NULL ||
+ (ibuf_main = calloc(1, sizeof(struct imsgbuf))) == NULL)
+ fatal("hce");
+ imsg_init(ibuf_pfe, pipe_pfe2hce[0], hce_dispatch_imsg);
+ imsg_init(ibuf_main, pipe_parent2hce[1], hce_dispatch_parent);
+
+ ibuf_pfe->events = EV_READ;
+ event_set(&ibuf_pfe->ev, ibuf_pfe->fd, ibuf_pfe->events,
+ ibuf_pfe->handler, ibuf_pfe);
+ event_add(&ibuf_pfe->ev, NULL);
+
+ ibuf_main->events = EV_READ;
+ event_set(&ibuf_main->ev, ibuf_main->fd, ibuf_main->events,
+ ibuf_main->handler, ibuf_main);
+ event_add(&ibuf_main->ev, NULL);
+
+ evtimer_set(&env->ev, hce_launch_checks, NULL);
+ tv.tv_sec = env->interval;
+ tv.tv_usec = 0;
+ evtimer_add(&env->ev, &tv);
+
+ hce_launch_checks(0, 0, NULL);
+ event_dispatch();
+
+ hce_shutdown();
+
+ return (0);
+}
+
+void
+hce_launch_checks(int fd, short event, void *arg)
+{
+ int previous_up;
+ struct host *host;
+ struct table *table;
+ struct ctl_status st;
+ struct timeval tv;
+
+ tv.tv_sec = env->interval;
+ tv.tv_usec = 0;
+ evtimer_add(&env->ev, &tv);
+ bzero(&st, sizeof(st));
+ TAILQ_FOREACH(table, &env->tables, entry) {
+ if (table->flags & F_DISABLE)
+ continue;
+ TAILQ_FOREACH(host, &table->hosts, entry) {
+ if (host->flags & F_DISABLE)
+ continue;
+ previous_up = host->up;
+ switch (table->check) {
+ case CHECK_ICMP:
+ host->up = check_icmp(host, env->icmp_sock,
+ env->icmp6_sock,
+ table->timeout);
+ break;
+ case CHECK_TCP:
+ host->up = check_tcp(host, table);
+ break;
+ case CHECK_HTTP_CODE:
+ host->up = check_http_code(host, table);
+ break;
+ case CHECK_HTTP_DIGEST:
+ host->up = check_http_digest(host, table);
+ break;
+ default:
+ fatalx("hce_launch_checks: unknown check type");
+ break;
+ }
+ if (host->up != previous_up) {
+ st.id = host->id;
+ st.up = host->up;
+ imsg_compose(ibuf_pfe, IMSG_HOST_STATUS, 0, 0,
+ &st, sizeof(st));
+ }
+ }
+ }
+ /* tell pfe we're finished */
+ imsg_compose(ibuf_pfe, IMSG_SYNC, 0, 0, NULL, 0);
+}
+
+void
+hce_shutdown(void)
+{
+ log_info("host check engine exiting");
+ _exit(0);
+}
+
+void
+hce_dispatch_imsg(int fd, short event, void *ptr)
+{
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+ objid_t id;
+ struct host *host;
+ struct table *table;
+
+ ibuf = ptr;
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("hce_dispatch_imsg: imsg_read_error");
+ if (n == 0)
+ fatalx("hce_dispatch_imsg: pipe closed");
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("hce_dispatch_imsg: msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("hce_dispatch_imsg: unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("hce_dispatch_imsg: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_HOST_DISABLE:
+ memcpy(&id, imsg.data, sizeof(id));
+ if ((host = host_find(env, id)) == NULL)
+ fatalx("hce_dispatch_imsg: desynchronized");
+ host->flags |= F_DISABLE;
+ host->up = HOST_UNKNOWN;
+ break;
+ case IMSG_HOST_ENABLE:
+ memcpy(&id, imsg.data, sizeof(id));
+ if ((host = host_find(env, id)) == NULL)
+ fatalx("hce_dispatch_imsg: desynchronized");
+ host->flags &= ~(F_DISABLE);
+ host->up = HOST_UNKNOWN;
+ break;
+ case IMSG_TABLE_DISABLE:
+ memcpy(&id, imsg.data, sizeof(id));
+ if ((table = table_find(env, id)) == NULL)
+ fatalx("hce_dispatch_imsg: desynchronized");
+ table->flags |= F_DISABLE;
+ TAILQ_FOREACH(host, &table->hosts, entry)
+ host->up = HOST_UNKNOWN;
+ break;
+ case IMSG_TABLE_ENABLE:
+ memcpy(&id, imsg.data, sizeof(id));
+ if ((table = table_find(env, id)) == NULL)
+ fatalx("hce_dispatch_imsg: desynchronized");
+ table->flags &= ~(F_DISABLE);
+ TAILQ_FOREACH(host, &table->hosts, entry)
+ host->up = HOST_UNKNOWN;
+ break;
+ default:
+ log_debug("hce_dispatch_msg: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+hce_dispatch_parent(int fd, short event, void * ptr)
+{
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = ptr;
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("hce_dispatch_parent: imsg_read error");
+ if (n == 0) /* connection closed */
+ fatalx("hce_dispatch_parent: pipe closed");
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("hce_dispatch_parent: msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("hce_dispatch_parent: unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("hce_dispatch_parent: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ log_debug("hce_dispatch_parent: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+}
diff --git a/usr.sbin/relayd/imsg.c b/usr.sbin/relayd/imsg.c
new file mode 100644
index 00000000000..feafd8700c1
--- /dev/null
+++ b/usr.sbin/relayd/imsg.c
@@ -0,0 +1,181 @@
+/* $OpenBSD: imsg.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * 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/socket.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+#include <sys/param.h>
+#include <net/if.h>
+#include <errno.h>
+#include <event.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "hostated.h"
+
+void
+imsg_init(struct imsgbuf *ibuf, int fd, void (*handler)(int, short, void *))
+{
+ msgbuf_init(&ibuf->w);
+ bzero(&ibuf->r, sizeof(ibuf->r));
+ ibuf->fd = fd;
+ ibuf->w.fd = fd;
+ ibuf->pid = getpid();
+ ibuf->handler = handler;
+ TAILQ_INIT(&ibuf->fds);
+}
+
+ssize_t
+imsg_read(struct imsgbuf *ibuf)
+{
+ ssize_t n;
+
+ if ((n = recv(ibuf->fd, ibuf->r.buf + ibuf->r.wpos,
+ sizeof(ibuf->r.buf) - ibuf->r.wpos, 0)) == -1) {
+ if (errno != EINTR && errno != EAGAIN) {
+ log_warn("imsg_read: pipe read error");
+ return (-1);
+ }
+ return (0);
+ }
+
+ ibuf->r.wpos += n;
+
+ return (n);
+}
+
+ssize_t
+imsg_get(struct imsgbuf *ibuf, struct imsg *imsg)
+{
+ size_t av, left, datalen;
+
+ av = ibuf->r.wpos;
+
+ if (IMSG_HEADER_SIZE > av)
+ return (0);
+
+ memcpy(&imsg->hdr, ibuf->r.buf, sizeof(imsg->hdr));
+ if (imsg->hdr.len < IMSG_HEADER_SIZE ||
+ imsg->hdr.len > MAX_IMSGSIZE) {
+ log_warnx("imsg_get: imsg hdr len %u out of bounds, type=%u",
+ imsg->hdr.len, imsg->hdr.type);
+ return (-1);
+ }
+ if (imsg->hdr.len > av)
+ return (0);
+ datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ ibuf->r.rptr = ibuf->r.buf + IMSG_HEADER_SIZE;
+ if ((imsg->data = malloc(datalen)) == NULL) {
+ log_warn("imsg_get");
+ return (-1);
+ }
+ memcpy(imsg->data, ibuf->r.rptr, datalen);
+
+ if (imsg->hdr.len < av) {
+ left = av - imsg->hdr.len;
+ memmove(&ibuf->r.buf, ibuf->r.buf + imsg->hdr.len, left);
+ ibuf->r.wpos = left;
+ } else
+ ibuf->r.wpos = 0;
+
+ return (datalen + IMSG_HEADER_SIZE);
+}
+
+int
+imsg_compose(struct imsgbuf *ibuf, enum imsg_type type, u_int32_t peerid,
+ pid_t pid, void *data, u_int16_t datalen)
+{
+ struct buf *wbuf;
+ int n;
+
+ if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL)
+ return (-1);
+
+ if (imsg_add(wbuf, data, datalen) == -1)
+ return (-1);
+
+ if ((n = imsg_close(ibuf, wbuf)) < 0)
+ return (-1);
+
+ return (n);
+}
+
+/* ARGSUSED */
+struct buf *
+imsg_create(struct imsgbuf *ibuf, enum imsg_type type, u_int32_t peerid,
+ pid_t pid, u_int16_t datalen)
+{
+ struct buf *wbuf;
+ struct imsg_hdr hdr;
+
+ if (datalen > MAX_IMSGSIZE - IMSG_HEADER_SIZE) {
+ log_warnx("imsg_create: len %u > MAX_IMSGSIZE; "
+ "type %u peerid %lu", datalen + IMSG_HEADER_SIZE,
+ type, peerid);
+ return (NULL);
+ }
+
+ hdr.len = (u_int16_t)(datalen + IMSG_HEADER_SIZE);
+ hdr.type = type;
+ hdr.peerid = peerid;
+ if ((hdr.pid = pid) == 0)
+ hdr.pid = ibuf->pid;
+ if ((wbuf = buf_open(hdr.len)) == NULL) {
+ log_warn("imsg_create: buf_open");
+ return (NULL);
+ }
+ if (imsg_add(wbuf, &hdr, sizeof(hdr)) == -1)
+ return (NULL);
+
+ return (wbuf);
+}
+
+int
+imsg_add(struct buf *msg, void *data, u_int16_t datalen)
+{
+ if (datalen)
+ if (buf_add(msg, data, datalen) == -1) {
+ log_warnx("imsg_add: buf_add error");
+ buf_free(msg);
+ return (-1);
+ }
+ return (datalen);
+}
+
+int
+imsg_close(struct imsgbuf *ibuf, struct buf *msg)
+{
+ int n;
+
+ if ((n = buf_close(&ibuf->w, msg)) < 0) {
+ log_warnx("imsg_close: buf_close error");
+ buf_free(msg);
+ return (-1);
+ }
+ imsg_event_add(ibuf);
+
+ return (n);
+}
+
+void
+imsg_free(struct imsg *imsg)
+{
+ free(imsg->data);
+}
diff --git a/usr.sbin/relayd/log.c b/usr.sbin/relayd/log.c
new file mode 100644
index 00000000000..28a7c9a7d4c
--- /dev/null
+++ b/usr.sbin/relayd/log.c
@@ -0,0 +1,159 @@
+/* $OpenBSD: log.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * 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 MIND, 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 <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+/* prototypes */
+void log_init(int);
+void vlog(int, const char *, va_list);
+void log_warn(const char *, ...);
+void log_warnx(const char *, ...);
+void log_info(const char *, ...);
+void log_debug(const char *, ...);
+void fatal(const char *);
+void fatalx(const char *);
+
+int debug;
+
+void logit(int, const char *, ...);
+
+void
+log_init(int n_debug)
+{
+ extern char *__progname;
+
+ debug = n_debug;
+
+ if (!debug)
+ openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON);
+
+ tzset();
+}
+
+void
+logit(int pri, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog(pri, fmt, ap);
+ va_end(ap);
+}
+
+void
+vlog(int pri, const char *fmt, va_list ap)
+{
+ char *nfmt;
+
+ if (debug) {
+ /* best effort in out of mem situations */
+ if (asprintf(&nfmt, "%s\n", fmt) == -1) {
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ } else {
+ vfprintf(stderr, nfmt, ap);
+ free(nfmt);
+ }
+ fflush(stderr);
+ } else
+ vsyslog(pri, fmt, ap);
+}
+
+
+void
+log_warn(const char *emsg, ...)
+{
+ char *nfmt;
+ va_list ap;
+
+ /* best effort to even work in out of memory situations */
+ if (emsg == NULL)
+ logit(LOG_CRIT, "%s", strerror(errno));
+ else {
+ va_start(ap, emsg);
+
+ if (asprintf(&nfmt, "%s: %s", emsg, strerror(errno)) == -1) {
+ /* we tried it... */
+ vlog(LOG_CRIT, emsg, ap);
+ logit(LOG_CRIT, "%s", strerror(errno));
+ } else {
+ vlog(LOG_CRIT, nfmt, ap);
+ free(nfmt);
+ }
+ va_end(ap);
+ }
+}
+
+void
+log_warnx(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_CRIT, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_info(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_INFO, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_debug(const char *emsg, ...)
+{
+ va_list ap;
+
+ if (debug) {
+ va_start(ap, emsg);
+ vlog(LOG_DEBUG, emsg, ap);
+ va_end(ap);
+ }
+}
+
+void
+fatal(const char *emsg)
+{
+ if (emsg == NULL)
+ logit(LOG_CRIT, "fatal: %s", strerror(errno));
+ else
+ if (errno)
+ logit(LOG_CRIT, "fatal: %s: %s",
+ emsg, strerror(errno));
+ else
+ logit(LOG_CRIT, "fatal: %s", emsg);
+
+ exit(1);
+}
+
+void
+fatalx(const char *emsg)
+{
+ errno = 0;
+ fatal(emsg);
+}
diff --git a/usr.sbin/relayd/parse.y b/usr.sbin/relayd/parse.y
new file mode 100644
index 00000000000..977b7a74cb1
--- /dev/null
+++ b/usr.sbin/relayd/parse.y
@@ -0,0 +1,937 @@
+/* $OpenBSD: parse.y,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org>
+ * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
+ * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
+ * Copyright (c) 2001 Markus Friedl. All rights reserved.
+ * Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
+ * Copyright (c) 2001 Theo de Raadt. All rights reserved.
+ *
+ * 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 <sys/queue.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+
+#include "hostated.h"
+
+struct hostated *conf = NULL;
+static FILE *fin = NULL;
+static int lineno = 1;
+static int errors = 0;
+const char *infile;
+char *start_state;
+objid_t last_service_id = 0;
+objid_t last_table_id = 0;
+objid_t last_host_id = 0;
+
+static struct service *service = NULL;
+static struct table *table = NULL;
+
+int yyerror(const char *, ...);
+int yyparse(void);
+int kw_cmp(const void *, const void *);
+int lookup(char *);
+int lgetc(FILE *);
+int lungetc(int);
+int findeol(void);
+int yylex(void);
+
+TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
+struct sym {
+ TAILQ_ENTRY(sym) entries;
+ int used;
+ int persist;
+ char *nam;
+ char *val;
+};
+
+int symset(const char *, const char *, int);
+char *symget(const char *);
+int cmdline_symset(char *);
+
+struct address *host_v4(const char *);
+struct address *host_v6(const char *);
+int host_dns(const char *, struct addresslist *,
+ int, in_port_t, const char *);
+int host(const char *, struct addresslist *,
+ int, in_port_t, const char *);
+
+typedef struct {
+ union {
+ u_int32_t number;
+ char *string;
+ struct host *host;
+ } v;
+ int lineno;
+} YYSTYPE;
+
+%}
+
+%token SERVICE TABLE BACKUP HOST REAL
+%token CHECK HTTP HTTPS TCP ICMP EXTERNAL
+%token TIMEOUT CODE DIGEST PORT TAG INTERFACE
+%token VIRTUAL IP INTERVAL DISABLE
+%token ERROR
+%token <v.string> STRING
+%type <v.string> interface
+%type <v.number> number
+%type <v.host> host
+
+%%
+
+grammar : /* empty */
+ | grammar '\n'
+ | grammar varset '\n'
+ | grammar main '\n'
+ | grammar service '\n'
+ | grammar table '\n'
+ | grammar error '\n' { errors++; }
+ ;
+
+number : STRING {
+ const char *estr;
+
+ $$ = strtonum($1, 0, UINT_MAX, &estr);
+ if (estr) {
+ yyerror("cannot parse number %s : %s",
+ $1, estr);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ ;
+
+varset : STRING '=' STRING {
+ if (symset($1, $3, 0) == -1)
+ fatal("cannot store variable");
+ free($1);
+ free($3);
+ }
+ ;
+
+main : INTERVAL number { conf->interval = $2; }
+ ;
+
+service : SERVICE STRING {
+ struct service *srv;
+
+ TAILQ_FOREACH(srv, &conf->services, entry)
+ if (!strcmp(srv->name, $2))
+ break;
+ if (srv != NULL) {
+ yyerror("service %s defined twice", $2);
+ free($2);
+ YYERROR;
+ }
+ if ((srv = calloc(1, sizeof (*srv))) == NULL)
+ fatal("out of memory");
+
+ if (strlcpy(srv->name, $2, sizeof (srv->name)) >=
+ sizeof (srv->name)) {
+ yyerror("service name truncated");
+ YYERROR;
+ }
+ free ($2);
+ srv->id = last_service_id++;
+ if (last_service_id == UINT_MAX) {
+ yyerror("too many services defined");
+ YYERROR;
+ }
+ service = srv;
+ } '{' optnl serviceopts_l '}' {
+ if (service->table == NULL) {
+ yyerror("service %s has no table",
+ service->name);
+ YYERROR;
+ }
+ if (TAILQ_EMPTY(&service->virts)) {
+ yyerror("service %s has no virtual ip",
+ service->name);
+ YYERROR;
+ }
+ conf->servicecount++;
+ if (service->backup == NULL)
+ service->backup = &conf->empty_table;
+ else if (service->backup->port !=
+ service->table->port) {
+ yyerror("service %s uses two different ports "
+ "for its table and backup table",
+ service->name);
+ YYERROR;
+ }
+
+ if (!(service->flags & F_DISABLE))
+ service->flags |= F_ADD;
+ TAILQ_INSERT_HEAD(&conf->services, service, entry);
+ }
+ ;
+
+serviceopts_l : serviceopts_l serviceoptsl nl
+ | serviceoptsl optnl
+ ;
+
+serviceoptsl : TABLE STRING {
+ struct table *tb;
+
+ TAILQ_FOREACH(tb, &conf->tables, entry)
+ if (!strcmp(tb->name, $2))
+ break;
+ if (tb == NULL) {
+ yyerror("no such table: %s", $2);
+ free($2);
+ YYERROR;
+ } else {
+ service->table = tb;
+ service->table->serviceid = service->id;
+ service->table->flags |= F_USED;
+ free($2);
+ }
+ }
+ | BACKUP TABLE STRING {
+ struct table *tb;
+
+ if (service->backup) {
+ yyerror("backup already specified");
+ free($3);
+ YYERROR;
+ }
+
+ TAILQ_FOREACH(tb, &conf->tables, entry)
+ if (!strcmp(tb->name, $3))
+ break;
+
+ if (tb == NULL) {
+ yyerror("no such table: %s", $3);
+ free($3);
+ YYERROR;
+ } else {
+ service->backup = tb;
+ service->backup->serviceid = service->id;
+ service->backup->flags |= (F_USED|F_BACKUP);
+ free($3);
+ }
+ }
+ | VIRTUAL IP STRING PORT number interface {
+ if ($5 < 1 || $5 > USHRT_MAX) {
+ yyerror("invalid port number: %d", $5);
+ free($3);
+ free($6);
+ YYERROR;
+ }
+ if (host($3, &service->virts,
+ SRV_MAX_VIRTS, htons($5), $6) <= 0) {
+ yyerror("invalid virtual ip: %s", $3);
+ free($3);
+ free($6);
+ YYERROR;
+ }
+ free($3);
+ free($6);
+ }
+ | DISABLE { service->flags |= F_DISABLE; }
+ | TAG STRING {
+ if (strlcpy(service->tag, $2, sizeof(service->tag)) >=
+ sizeof(service->tag)) {
+ yyerror("service tag name truncated");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ ;
+
+table : TABLE STRING {
+ struct table *tb;
+
+ TAILQ_FOREACH(tb, &conf->tables, entry)
+ if (!strcmp(tb->name, $2))
+ break;
+ if (tb != NULL) {
+ yyerror("table %s defined twice");
+ free($2);
+ YYERROR;
+ }
+
+ if ((tb = calloc(1, sizeof (*tb))) == NULL)
+ fatal("out of memory");
+
+ if (strlcpy(tb->name, $2, sizeof (tb->name)) >=
+ sizeof (tb->name)) {
+ yyerror("table name truncated");
+ YYERROR;
+ }
+ tb->id = last_table_id++;
+ tb->timeout = CONNECT_TIMEOUT;
+ if (last_table_id == UINT_MAX) {
+ yyerror("too many tables defined");
+ YYERROR;
+ }
+ free ($2);
+ table = tb;
+ } '{' optnl tableopts_l '}' {
+ if (table->port == 0) {
+ yyerror("table %s has no port", table->name);
+ YYERROR;
+ }
+ if (TAILQ_EMPTY(&table->hosts)) {
+ yyerror("table %s has no hosts", table->name);
+ YYERROR;
+ }
+ if (table->check == CHECK_NOCHECK) {
+ yyerror("table %s has no check", table->name);
+ YYERROR;
+ }
+ conf->tablecount++;
+ TAILQ_INSERT_HEAD(&conf->tables, table, entry);
+ }
+ ;
+
+tableopts_l : tableopts_l tableoptsl nl
+ | tableoptsl optnl
+ ;
+
+tableoptsl : host {
+ $1->tableid = table->id;
+ $1->tablename = table->name;
+ TAILQ_INSERT_HEAD(&table->hosts, $1, entry);
+ }
+ | TIMEOUT number {
+ table->timeout = $2;
+ }
+ | CHECK ICMP {
+ table->check = CHECK_ICMP;
+ }
+ | CHECK TCP {
+ table->check = CHECK_TCP;
+ }
+ | CHECK HTTP STRING CODE number {
+ table->check = CHECK_HTTP_CODE;
+ table->retcode = $5;
+ if (strlcpy(table->path, $3, sizeof (table->path)) >=
+ sizeof (table->path)) {
+ yyerror("http path truncated");
+ free($3);
+ YYERROR;
+ }
+ }
+ | CHECK HTTP STRING DIGEST STRING {
+ table->check = CHECK_HTTP_DIGEST;
+ if (strlcpy(table->path, $3, sizeof (table->path)) >=
+ sizeof (table->path)) {
+ yyerror("http path truncated");
+ free($3);
+ free($5);
+ YYERROR;
+ }
+ if (strlcpy(table->digest, $5, sizeof (table->digest))
+ >= sizeof (table->digest)) {
+ yyerror("http digest truncated");
+ free($3);
+ free($5);
+ YYERROR;
+ }
+ free($3);
+ free($5);
+ }
+ | REAL PORT number {
+ if ($3 < 1 || $3 >= USHRT_MAX) {
+ yyerror("invalid port number: %d", $3);
+ YYERROR;
+ }
+ table->port = $3;
+ }
+ | DISABLE { table->flags |= F_DISABLE; }
+ ;
+
+interface : /*empty*/ { $$ = NULL; }
+ | INTERFACE STRING { $$ = $2; }
+ ;
+
+host : HOST STRING {
+ struct host *r;
+ struct address *a;
+ struct addresslist al;
+
+ if ((r = calloc(1, sizeof (*r))) == NULL)
+ fatal("out of memory");
+
+ TAILQ_INIT(&al);
+ if (host($2, &al, 1, 0, NULL) <= 0) {
+ yyerror("invalid host %s", $2);
+ free($2);
+ YYERROR;
+ }
+ a = TAILQ_FIRST(&al);
+ memcpy(&r->ss, &a->ss, sizeof(r->ss));
+ free(a);
+
+ if (strlcpy(r->name, $2, sizeof (r->name)) >=
+ sizeof (r->name)) {
+ yyerror("host name truncated");
+ free($2);
+ YYERROR;
+ } else {
+ r->id = last_host_id++;
+ if (last_host_id == UINT_MAX) {
+ yyerror("too many hosts defined");
+ YYERROR;
+ }
+ free($2);
+ $$ = r;
+ }
+ }
+ ;
+
+optnl : '\n' optnl
+ |
+ ;
+
+nl : '\n' optnl
+ ;
+
+%%
+
+struct keywords {
+ const char *k_name;
+ int k_val;
+};
+
+int
+yyerror(const char *fmt, ...)
+{
+ va_list ap;
+
+ errors = 1;
+ va_start(ap, fmt);
+ fprintf(stderr, "%s:%d: ", infile, yylval.lineno);
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ va_end(ap);
+ return (0);
+}
+
+int
+kw_cmp(const void *k, const void *e)
+{
+
+ return (strcmp(k, ((const struct keywords *)e)->k_name));
+}
+
+int
+lookup(char *s)
+{
+ /* this has to be sorted always */
+ static const struct keywords keywords[] = {
+ {"backup", BACKUP},
+ {"check", CHECK},
+ {"code", CODE},
+ {"digest", DIGEST},
+ {"disable", DISABLE},
+ {"external", EXTERNAL},
+ {"host", HOST},
+ {"http", HTTP},
+ {"https", HTTPS},
+ {"icmp", ICMP},
+ {"interface", INTERFACE},
+ {"interval", INTERVAL},
+ {"ip", IP},
+ {"port", PORT},
+ {"real", REAL},
+ {"service", SERVICE},
+ {"table", TABLE},
+ {"tag", TAG},
+ {"tcp", TCP},
+ {"timeout", TIMEOUT},
+ {"virtual", VIRTUAL}
+ };
+ const struct keywords *p;
+
+ p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
+ sizeof(keywords[0]), kw_cmp);
+
+ if (p)
+ return (p->k_val);
+ else
+ return (STRING);
+}
+
+#define MAXPUSHBACK 128
+
+char *parsebuf;
+int parseindex;
+char pushback_buffer[MAXPUSHBACK];
+int pushback_index = 0;
+
+int
+lgetc(FILE *f)
+{
+ int c, next;
+
+ if (parsebuf) {
+ /* Read character from the parsebuffer instead of input. */
+ if (parseindex >= 0) {
+ c = parsebuf[parseindex++];
+ if (c != '\0')
+ return (c);
+ parsebuf = NULL;
+ } else
+ parseindex++;
+ }
+
+ if (pushback_index)
+ return (pushback_buffer[--pushback_index]);
+
+ while ((c = getc(f)) == '\\') {
+ next = getc(f);
+ if (next != '\n') {
+ c = next;
+ break;
+ }
+ yylval.lineno = lineno;
+ lineno++;
+ }
+ if (c == '\t' || c == ' ') {
+ /* Compress blanks to a single space. */
+ do {
+ c = getc(f);
+ } while (c == '\t' || c == ' ');
+ ungetc(c, f);
+ c = ' ';
+ }
+
+ return (c);
+}
+
+int
+lungetc(int c)
+{
+ if (c == EOF)
+ return (EOF);
+ if (parsebuf) {
+ parseindex--;
+ if (parseindex >= 0)
+ return (c);
+ }
+ if (pushback_index < MAXPUSHBACK-1)
+ return (pushback_buffer[pushback_index++] = c);
+ else
+ return (EOF);
+}
+
+int
+findeol(void)
+{
+ int c;
+
+ parsebuf = NULL;
+ pushback_index = 0;
+
+ /* skip to either EOF or the first real EOL */
+ while (1) {
+ c = lgetc(fin);
+ if (c == '\n') {
+ lineno++;
+ break;
+ }
+ if (c == EOF)
+ break;
+ }
+ return (ERROR);
+}
+
+int
+yylex(void)
+{
+ char buf[8096];
+ char *p, *val;
+ int endc, c;
+ int token;
+
+top:
+ p = buf;
+ while ((c = lgetc(fin)) == ' ')
+ ; /* nothing */
+
+ yylval.lineno = lineno;
+ if (c == '#')
+ while ((c = lgetc(fin)) != '\n' && c != EOF)
+ ; /* nothing */
+ if (c == '$' && parsebuf == NULL) {
+ while (1) {
+ if ((c = lgetc(fin)) == EOF)
+ return (0);
+
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ if (isalnum(c) || c == '_') {
+ *p++ = (char)c;
+ continue;
+ }
+ *p = '\0';
+ lungetc(c);
+ break;
+ }
+ val = symget(buf);
+ if (val == NULL) {
+ yyerror("macro '%s' not defined", buf);
+ return (findeol());
+ }
+ parsebuf = val;
+ parseindex = 0;
+ goto top;
+ }
+
+ switch (c) {
+ case '\'':
+ case '"':
+ endc = c;
+ while (1) {
+ if ((c = lgetc(fin)) == EOF)
+ return (0);
+ if (c == endc) {
+ *p = '\0';
+ break;
+ }
+ if (c == '\n') {
+ lineno++;
+ continue;
+ }
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ *p++ = (char)c;
+ }
+ yylval.v.string = strdup(buf);
+ if (yylval.v.string == NULL)
+ errx(1, "yylex: strdup");
+ return (STRING);
+ }
+
+#define allowed_in_string(x) \
+ (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
+ x != '{' && x != '}' && \
+ x != '!' && x != '=' && x != '#' && \
+ x != ','))
+
+ if (isalnum(c) || c == ':' || c == '_') {
+ do {
+ *p++ = c;
+ if ((unsigned)(p-buf) >= sizeof(buf)) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ } while ((c = lgetc(fin)) != EOF && (allowed_in_string(c)));
+ lungetc(c);
+ *p = '\0';
+ if ((token = lookup(buf)) == STRING)
+ if ((yylval.v.string = strdup(buf)) == NULL)
+ err(1, "yylex: strdup");
+ return (token);
+ }
+ if (c == '\n') {
+ yylval.lineno = lineno;
+ lineno++;
+ }
+ if (c == EOF)
+ return (0);
+ return (c);
+}
+
+int
+parse_config(struct hostated *x_conf, const char *filename, int opts)
+{
+ struct sym *sym, *next;
+
+ conf = x_conf;
+
+ TAILQ_INIT(&conf->services);
+ TAILQ_INIT(&conf->tables);
+ memset(&conf->empty_table, 0, sizeof(conf->empty_table));
+ conf->empty_table.id = EMPTY_TABLE;
+ conf->empty_table.flags |= F_DISABLE;
+ (void)strlcpy(conf->empty_table.name, "empty",
+ sizeof(conf->empty_table.name));
+
+ conf->interval = CHECK_INTERVAL;
+ conf->opts = opts;
+
+ if ((fin = fopen(filename, "r")) == NULL) {
+ warn("%s", filename);
+ return (NULL);
+ }
+ infile = filename;
+ yyparse();
+ fclose(fin);
+
+ /* Free macros and check which have not been used. */
+ for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) {
+ next = TAILQ_NEXT(sym, entries);
+ if ((conf->opts & HOSTATED_OPT_VERBOSE) && !sym->used)
+ fprintf(stderr, "warning: macro '%s' not "
+ "used\n", sym->nam);
+ if (!sym->persist) {
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entries);
+ free(sym);
+ }
+ }
+
+ if (TAILQ_EMPTY(&conf->services)) {
+ log_warnx("no services, nothing to do");
+ errors++;
+ }
+
+ /* Verify that every table is used */
+ TAILQ_FOREACH(table, &conf->tables, entry)
+ if (!(table->flags & F_USED)) {
+ log_warnx("unused table: %s", table->name);
+ errors++;
+ }
+
+ if (errors) {
+ bzero(&conf, sizeof (*conf));
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+symset(const char *nam, const char *val, int persist)
+{
+ struct sym *sym;
+
+ for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam);
+ sym = TAILQ_NEXT(sym, entries))
+ ; /* nothing */
+
+ if (sym != NULL) {
+ if (sym->persist == 1)
+ return (0);
+ else {
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entries);
+ free(sym);
+ }
+ }
+ if ((sym = calloc(1, sizeof(*sym))) == NULL)
+ return (-1);
+
+ sym->nam = strdup(nam);
+ if (sym->nam == NULL) {
+ free(sym);
+ return (-1);
+ }
+ sym->val = strdup(val);
+ if (sym->val == NULL) {
+ free(sym->nam);
+ free(sym);
+ return (-1);
+ }
+ sym->used = 0;
+ sym->persist = persist;
+ TAILQ_INSERT_TAIL(&symhead, sym, entries);
+ return (0);
+}
+
+int
+cmdline_symset(char *s)
+{
+ char *sym, *val;
+ int ret;
+ size_t len;
+
+ if ((val = strrchr(s, '=')) == NULL)
+ return (-1);
+
+ len = strlen(s) - strlen(val) + 1;
+ if ((sym = malloc(len)) == NULL)
+ errx(1, "cmdline_symset: malloc");
+
+ strlcpy(sym, s, len);
+
+ ret = symset(sym, val + 1, 1);
+ free(sym);
+
+ return (ret);
+}
+
+char *
+symget(const char *nam)
+{
+ struct sym *sym;
+
+ TAILQ_FOREACH(sym, &symhead, entries)
+ if (strcmp(nam, sym->nam) == 0) {
+ sym->used = 1;
+ return (sym->val);
+ }
+ return (NULL);
+}
+
+struct address *
+host_v4(const char *s)
+{
+ struct in_addr ina;
+ struct sockaddr_in *sain;
+ struct address *h;
+
+ bzero(&ina, sizeof(ina));
+ if (inet_pton(AF_INET, s, &ina) != 1)
+ return (NULL);
+
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(NULL);
+ sain = (struct sockaddr_in *)&h->ss;
+ sain->sin_len = sizeof(struct sockaddr_in);
+ sain->sin_family = AF_INET;
+ sain->sin_addr.s_addr = ina.s_addr;
+
+ return (h);
+}
+
+struct address *
+host_v6(const char *s)
+{
+ struct in6_addr ina6;
+ struct sockaddr_in6 *sin6;
+ struct address *h;
+
+ bzero(&ina6, sizeof(ina6));
+ if (inet_pton(AF_INET6, s, &ina6) != 1)
+ return (NULL);
+
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(NULL);
+ sin6 = (struct sockaddr_in6 *)&h->ss;
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ sin6->sin6_family = AF_INET6;
+ memcpy(&sin6->sin6_addr, &ina6, sizeof(ina6));
+
+ return (h);
+}
+
+int
+host_dns(const char *s, struct addresslist *al, int max,
+ in_port_t port, const char *ifname)
+{
+ struct addrinfo hints, *res0, *res;
+ int error, cnt = 0;
+ struct sockaddr_in *sain;
+ struct sockaddr_in6 *sin6;
+ struct address *h;
+
+ bzero(&hints, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM; /* DUMMY */
+ error = getaddrinfo(s, NULL, &hints, &res0);
+ if (error == EAI_AGAIN || error == EAI_NODATA || error == EAI_NONAME)
+ return (0);
+ if (error) {
+ log_warnx("host_dns: could not parse \"%s\": %s", s,
+ gai_strerror(error));
+ return (-1);
+ }
+
+ for (res = res0; res && cnt < max; res = res->ai_next) {
+ if (res->ai_family != AF_INET &&
+ res->ai_family != AF_INET6)
+ continue;
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(NULL);
+
+ h->port = port;
+ if (ifname != NULL) {
+ if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >=
+ sizeof(h->ifname))
+ log_warnx("host_dns: interface name truncated");
+ return (-1);
+ }
+ h->ss.ss_family = res->ai_family;
+ if (res->ai_family == AF_INET) {
+ sain = (struct sockaddr_in *)&h->ss;
+ sain->sin_len = sizeof(struct sockaddr_in);
+ sain->sin_addr.s_addr = ((struct sockaddr_in *)
+ res->ai_addr)->sin_addr.s_addr;
+ } else {
+ sin6 = (struct sockaddr_in6 *)&h->ss;
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ memcpy(&sin6->sin6_addr, &((struct sockaddr_in6 *)
+ res->ai_addr)->sin6_addr, sizeof(struct in6_addr));
+ }
+
+ TAILQ_INSERT_HEAD(al, h, entry);
+ cnt++;
+ }
+ if (cnt == max && res) {
+ log_warnx("host_dns: %s resolves to more than %d hosts",
+ s, max);
+ }
+ freeaddrinfo(res0);
+
+ return (cnt);
+}
+
+int
+host(const char *s, struct addresslist *al, int max,
+ in_port_t port, const char *ifname)
+{
+ struct address *h;
+
+ h = host_v4(s);
+
+ /* IPv6 address? */
+ if (h == NULL)
+ h = host_v6(s);
+
+ if (h != NULL) {
+ h->port = port;
+ if (ifname != NULL) {
+ if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >=
+ sizeof(h->ifname)) {
+ log_warnx("host: interface name truncated");
+ return (-1);
+ }
+ }
+
+ TAILQ_INSERT_HEAD(al, h, entry);
+ return (1);
+ }
+
+ return (host_dns(s, al, max, port, ifname));
+}
diff --git a/usr.sbin/relayd/pfe.c b/usr.sbin/relayd/pfe.c
new file mode 100644
index 00000000000..0a7ff43949f
--- /dev/null
+++ b/usr.sbin/relayd/pfe.c
@@ -0,0 +1,497 @@
+/* $OpenBSD: pfe.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/queue.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <net/if.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <pwd.h>
+
+#include "hostated.h"
+
+void pfe_sig_handler(int sig, short, void *);
+void pfe_shutdown(void);
+void pfe_dispatch_imsg(int, short, void *);
+void pfe_dispatch_parent(int, short, void *);
+
+void pfe_sync(void);
+
+static struct hostated *env = NULL;
+
+struct imsgbuf *ibuf_main;
+struct imsgbuf *ibuf_hce;
+
+void
+pfe_sig_handler(int sig, short event, void *arg)
+{
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ pfe_shutdown();
+ default:
+ fatalx("pfe_sig_handler: unexpected signal");
+ }
+}
+
+pid_t
+pfe(struct hostated *x_env, int pipe_parent2pfe[2], int pipe_parent2hce[2],
+ int pipe_pfe2hce[2])
+{
+ pid_t pid;
+ struct passwd *pw;
+ struct event ev_sigint;
+ struct event ev_sigterm;
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("pfe: cannot fork");
+ case 0:
+ break;
+ default:
+ return (pid);
+ }
+
+ env = x_env;
+
+ if (control_init() == -1)
+ fatalx("pfe: control socket setup failed");
+
+ init_filter(env);
+ init_tables(env);
+
+ if ((pw = getpwnam(HOSTATED_USER)) == NULL)
+ fatal("pfe: getpwnam");
+
+ if (chroot(pw->pw_dir) == -1)
+ fatal("pfe: chroot");
+ if (chdir("/") == -1)
+ fatal("pfe: chdir(\"/\")");
+
+ setproctitle("pf update engine");
+ hostated_process = PROC_PFE;
+
+ 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("pfe: cannot drop privileges");
+
+ event_init();
+
+ signal_set(&ev_sigint, SIGINT, pfe_sig_handler, NULL);
+ signal_set(&ev_sigterm, SIGTERM, pfe_sig_handler, NULL);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+
+ /* setup pipes */
+ close(pipe_pfe2hce[0]);
+ close(pipe_parent2pfe[0]);
+ close(pipe_parent2hce[0]);
+ close(pipe_parent2hce[1]);
+
+ if ((ibuf_hce = calloc(1, sizeof(struct imsgbuf))) == NULL ||
+ (ibuf_main = calloc(1, sizeof(struct imsgbuf))) == NULL)
+ fatal("pfe");
+ imsg_init(ibuf_hce, pipe_pfe2hce[1], pfe_dispatch_imsg);
+ imsg_init(ibuf_main, pipe_parent2pfe[1], pfe_dispatch_parent);
+
+ ibuf_hce->events = EV_READ;
+ event_set(&ibuf_hce->ev, ibuf_hce->fd, ibuf_hce->events,
+ ibuf_hce->handler, ibuf_hce);
+ event_add(&ibuf_hce->ev, NULL);
+
+ ibuf_main->events = EV_READ;
+ event_set(&ibuf_main->ev, ibuf_main->fd, ibuf_main->events,
+ ibuf_main->handler, ibuf_main);
+ event_add(&ibuf_main->ev, NULL);
+
+ TAILQ_INIT(&ctl_conns);
+ control_listen();
+
+ event_dispatch();
+ pfe_shutdown();
+
+ return (0);
+}
+
+void
+pfe_shutdown(void)
+{
+ flush_rulesets(env);
+ log_info("pf update engine exiting");
+ _exit(0);
+}
+
+void
+pfe_dispatch_imsg(int fd, short event, void *ptr)
+{
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ struct host *host;
+ struct table *table;
+ struct ctl_status st;
+
+ ibuf = ptr;
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("pfe_dispatch_imsg: imsg_read_error");
+ if (n == 0)
+ fatalx("pfe_dispatch_imsg: pipe closed");
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("pfe_dispatch_imsg: msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("pfe_dispatch_imsg: unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("pfe_dispatch_imsg: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_HOST_STATUS:
+ if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(st))
+ fatalx("pfe_dispatch_imsg: invalid request");
+ memcpy(&st, imsg.data, sizeof(st));
+ if ((host = host_find(env, st.id)) == NULL)
+ fatalx("pfe_dispatch_imsg: invalid host id");
+ if (host->up == st.up) {
+ log_debug("pfe_dispatch_imsg: host %d => %d",
+ host->id, host->up);
+ fatalx("pfe_dispatch_imsg: desynchronized");
+ }
+
+ if ((table = table_find(env, host->tableid)) == NULL)
+ fatalx("pfe_dispatch_imsg: invalid table id");
+
+ log_debug("pfe_dispatch_imsg: state %d for host %u %s",
+ st.up, host->id, host->name);
+
+ if ((st.up == HOST_UNKNOWN && host->up == HOST_DOWN) ||
+ (st.up == HOST_DOWN && host->up == HOST_UNKNOWN)) {
+ host->up = st.up;
+ break;
+ }
+
+ if (st.up == HOST_UP) {
+ table->flags |= F_CHANGED;
+ table->up++;
+ host->flags |= F_ADD;
+ host->flags &= ~(F_DEL);
+ } else {
+ table->up--;
+ table->flags |= F_CHANGED;
+ host->flags |= F_DEL;
+ host->flags &= ~(F_ADD);
+ }
+ host->up = st.up;
+ break;
+ case IMSG_SYNC:
+ pfe_sync();
+ break;
+ default:
+ log_debug("pfe_dispatch_imsg: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+pfe_dispatch_parent(int fd, short event, void * ptr)
+{
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = ptr;
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read error");
+ if (n == 0) /* connection closed */
+ fatalx("pfe_dispatch_parent: pipe closed");
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("pfe_dispatch_parent: unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("pfe_dispatch_parent: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ log_debug("pfe_dispatch_parent: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+}
+
+void
+show(struct ctl_conn *c)
+{
+ struct service *service;
+ struct host *host;
+
+ TAILQ_FOREACH(service, &env->services, entry) {
+ imsg_compose(&c->ibuf, IMSG_CTL_SERVICE, 0, 0,
+ service, sizeof(*service));
+ if (service->flags & F_DISABLE)
+ continue;
+
+ imsg_compose(&c->ibuf, IMSG_CTL_TABLE, 0, 0,
+ service->table, sizeof(*service->table));
+ if (!(service->table->flags & F_DISABLE))
+ TAILQ_FOREACH(host, &service->table->hosts, entry)
+ imsg_compose(&c->ibuf, IMSG_CTL_HOST, 0, 0,
+ host, sizeof(*host));
+
+ if (service->backup->id == EMPTY_TABLE)
+ continue;
+ imsg_compose(&c->ibuf, IMSG_CTL_TABLE, 0, 0,
+ service->backup, sizeof(*service->backup));
+ if (!(service->backup->flags & F_DISABLE))
+ TAILQ_FOREACH(host, &service->backup->hosts, entry)
+ imsg_compose(&c->ibuf, IMSG_CTL_HOST, 0, 0,
+ host, sizeof(*host));
+ }
+ imsg_compose(&c->ibuf, IMSG_CTL_END, 0, 0, NULL, 0);
+}
+
+
+int
+disable_service(struct ctl_conn *c, objid_t id)
+{
+ struct service *service;
+
+ if ((service = service_find(env, id)) == NULL)
+ return (-1);
+
+ if (service->flags & F_DISABLE)
+ return (0);
+
+ service->flags |= F_DISABLE;
+ service->flags &= ~(F_ADD);
+ service->flags |= F_DEL;
+ service->table->flags |= F_DISABLE;
+ log_debug("disable_service: disabled service %d", service->id);
+ pfe_sync();
+ return (0);
+}
+
+int
+enable_service(struct ctl_conn *c, objid_t id)
+{
+ struct service *service;
+
+ if ((service = service_find(env, id)) == NULL)
+ return (-1);
+
+ if (!(service->flags & F_DISABLE))
+ return (0);
+
+ service->flags &= ~(F_DISABLE);
+ service->flags &= ~(F_DEL);
+ service->flags |= F_ADD;
+ log_debug("enable_service: enabled service %d", service->id);
+
+ /* XXX: we're syncing twice */
+ if (enable_table(c, service->table->id))
+ return (-1);
+ if (enable_table(c, service->backup->id))
+ return (-1);
+ return (0);
+}
+
+int
+disable_table(struct ctl_conn *c, objid_t id)
+{
+ struct table *table;
+ struct service *service;
+ struct host *host;
+
+ if (id == EMPTY_TABLE)
+ return (-1);
+ if ((table = table_find(env, id)) == NULL)
+ return (-1);
+ if ((service = service_find(env, table->serviceid)) == NULL)
+ fatalx("disable_table: desynchronised");
+
+ if (table->flags & F_DISABLE)
+ return (0);
+ table->flags |= (F_DISABLE|F_CHANGED);
+ table->up = 0;
+ TAILQ_FOREACH(host, &table->hosts, entry)
+ host->up = HOST_UNKNOWN;
+ imsg_compose(ibuf_hce, IMSG_TABLE_DISABLE, 0, 0, &id, sizeof(id));
+ log_debug("disable_table: disabled table %d", table->id);
+ pfe_sync();
+ return (0);
+}
+
+int
+enable_table(struct ctl_conn *c, objid_t id)
+{
+ struct service *service;
+ struct table *table;
+ struct host *host;
+
+ if (id == EMPTY_TABLE)
+ return (-1);
+ if ((table = table_find(env, id)) == NULL)
+ return (-1);
+ if ((service = service_find(env, table->serviceid)) == NULL)
+ fatalx("enable_table: desynchronised");
+
+ if (!(table->flags & F_DISABLE))
+ return (0);
+ table->flags &= ~(F_DISABLE);
+ table->flags |= F_CHANGED;
+ table->up = 0;
+ TAILQ_FOREACH(host, &table->hosts, entry)
+ host->up = HOST_UNKNOWN;
+ imsg_compose(ibuf_hce, IMSG_TABLE_ENABLE, 0, 0, &id, sizeof(id));
+ log_debug("enable_table: enabled table %d", table->id);
+ pfe_sync();
+ return (0);
+}
+
+int
+disable_host(struct ctl_conn *c, objid_t id)
+{
+ struct host *host;
+ struct table *table;
+
+ if ((host = host_find(env, id)) == NULL)
+ return (-1);
+
+ if (host->flags & F_DISABLE)
+ return (0);
+
+ if (host->up == HOST_UP) {
+ if ((table = table_find(env, host->tableid)) == NULL)
+ fatalx("disable_host: invalid table id");
+ table->up--;
+ table->flags |= F_CHANGED;
+ }
+
+ host->up = HOST_UNKNOWN;
+ host->flags |= F_DISABLE;
+ host->flags |= F_DEL;
+ host->flags &= ~(F_ADD);
+
+ imsg_compose(ibuf_hce, IMSG_HOST_DISABLE, 0, 0, &id, sizeof (id));
+ log_debug("disable_host: disabled host %d", host->id);
+ pfe_sync();
+ return (0);
+}
+
+int
+enable_host(struct ctl_conn *c, objid_t id)
+{
+ struct host *host;
+
+ if ((host = host_find(env, id)) == NULL)
+ return (-1);
+
+ if (!(host->flags & F_DISABLE))
+ return (0);
+
+ host->up = HOST_UNKNOWN;
+ host->flags &= ~(F_DISABLE);
+ host->flags &= ~(F_DEL);
+ host->flags &= ~(F_ADD);
+
+ imsg_compose(ibuf_hce, IMSG_HOST_ENABLE, 0, 0, &id, sizeof (id));
+ log_debug("enable_host: enabled host %d", host->id);
+ pfe_sync();
+ return (0);
+}
+
+void
+pfe_sync(void)
+{
+ struct service *service;
+ struct table *active;
+ int backup;
+
+ TAILQ_FOREACH(service, &env->services, entry) {
+ backup = (service->flags & F_BACKUP);
+ service->flags &= ~(F_BACKUP);
+ service->flags &= ~(F_DOWN);
+
+ if (service->flags & F_DISABLE ||
+ (service->table->up == 0 && service->backup->up == 0)) {
+ service->flags |= F_DOWN;
+ active = NULL;
+ } else if (service->table->up == 0 && service->backup->up > 0) {
+ service->flags |= F_BACKUP;
+ active = service->backup;
+ active->flags |= service->table->flags & F_CHANGED;
+ active->flags |= service->backup->flags & F_CHANGED;
+ } else
+ active = service->table;
+
+ if (active != NULL && active->flags & F_CHANGED)
+ sync_table(env, service, active);
+
+ service->table->flags &= ~(F_CHANGED);
+ service->backup->flags &= ~(F_CHANGED);
+
+ if (service->flags & F_DOWN) {
+ if (service->flags & F_ACTIVE_RULESET) {
+ flush_table(env, service);
+ log_debug("pfe_sync: disabling ruleset");
+ service->flags &= ~(F_ACTIVE_RULESET);
+ sync_ruleset(env, service, 0);
+ }
+ } else if (!(service->flags & F_ACTIVE_RULESET)) {
+ log_debug("pfe_sync: enabling ruleset");
+ service->flags |= F_ACTIVE_RULESET;
+ sync_ruleset(env, service, 1);
+ }
+ }
+}
diff --git a/usr.sbin/relayd/pfe_filter.c b/usr.sbin/relayd/pfe_filter.c
new file mode 100644
index 00000000000..b7bd7550070
--- /dev/null
+++ b/usr.sbin/relayd/pfe_filter.c
@@ -0,0 +1,336 @@
+/* $OpenBSD: pfe_filter.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.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/ioctl.h>
+#include <sys/param.h>
+
+#include <net/if.h>
+#include <net/pfvar.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <limits.h>
+#include <fcntl.h>
+#include <event.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "hostated.h"
+
+struct pfdata {
+ int dev;
+ struct pf_anchor *anchor;
+ struct pfioc_trans pft;
+ struct pfioc_trans_e pfte;
+};
+
+int transaction_init(struct hostated *, const char *);
+int transaction_commit(struct hostated *);
+void kill_tables(struct hostated *);
+
+void
+init_filter(struct hostated *env)
+{
+ struct pf_status status;
+
+ if ((env->pf = calloc(1, sizeof(*(env->pf)))) == NULL)
+ fatal("calloc");
+ if ((env->pf->dev = open(PF_SOCKET, O_RDWR)) == -1)
+ fatal("init_filter: cannot open pf socket");
+ if (ioctl(env->pf->dev, DIOCGETSTATUS, &status) == -1)
+ fatal("init_filter: DIOCGETSTATUS");
+ if (!status.running)
+ fatalx("init_filter: pf is disabled");
+ log_debug("init_filter: filter init done");
+}
+
+void
+init_tables(struct hostated *env)
+{
+ int i;
+ struct service *service;
+ struct pfr_table *tables;
+ struct pfioc_table io;
+
+ if ((tables = calloc(env->servicecount, sizeof(*tables))) == NULL)
+ fatal("calloc");
+ i = 0;
+
+ TAILQ_FOREACH(service, &env->services, entry) {
+ (void)strlcpy(tables[i].pfrt_anchor, HOSTATED_ANCHOR "/",
+ sizeof(tables[i].pfrt_anchor));
+ (void)strlcat(tables[i].pfrt_anchor, service->name,
+ sizeof(tables[i].pfrt_anchor));
+ (void)strlcpy(tables[i].pfrt_name, service->name,
+ sizeof(tables[i].pfrt_name));
+ tables[i].pfrt_flags |= PFR_TFLAG_PERSIST;
+ i++;
+ }
+ if (i != env->servicecount)
+ fatalx("init_tables: table count modified");
+
+ memset(&io, 0, sizeof(io));
+ io.pfrio_size = env->servicecount;
+ io.pfrio_esize = sizeof(*tables);
+ io.pfrio_buffer = tables;
+
+ if (ioctl(env->pf->dev, DIOCRADDTABLES, &io) == -1)
+ fatal("init_tables: cannot create tables");
+ log_debug("created %d tables", io.pfrio_nadd);
+
+ if (io.pfrio_nadd == env->servicecount)
+ return;
+
+ /*
+ * clear all tables, since some already existed
+ */
+ TAILQ_FOREACH(service, &env->services, entry)
+ flush_table(env, service);
+}
+
+void
+kill_tables(struct hostated *env) {
+ struct pfioc_table io;
+ struct service *service;
+
+ memset(&io, 0, sizeof(io));
+ TAILQ_FOREACH(service, &env->services, entry) {
+ (void)strlcpy(io.pfrio_table.pfrt_anchor, HOSTATED_ANCHOR "/",
+ sizeof(io.pfrio_table.pfrt_anchor));
+ (void)strlcat(io.pfrio_table.pfrt_anchor, service->name,
+ sizeof(io.pfrio_table.pfrt_anchor));
+ if (ioctl(env->pf->dev, DIOCRCLRTABLES, &io) == -1)
+ fatal("kill_tables: ioctl faile: ioctl failed");
+ }
+ log_debug("kill_tables: deleted %d tables", io.pfrio_ndel);
+}
+
+void
+sync_table(struct hostated *env, struct service *service, struct table *table)
+{
+ int i;
+ struct pfioc_table io;
+ struct pfr_addr *addlist;
+ struct sockaddr_in *sain;
+ struct sockaddr_in6 *sain6;
+ struct host *host;
+
+ if (table == NULL)
+ return;
+
+ if (table->up == 0) {
+ flush_table(env, service);
+ return;
+ }
+
+ if ((addlist = calloc(table->up, sizeof(*addlist))) == NULL)
+ fatal("calloc");
+
+ memset(&io, 0, sizeof(io));
+ io.pfrio_esize = sizeof(struct pfr_addr);
+ io.pfrio_size = table->up;
+ io.pfrio_size2 = 0;
+ io.pfrio_buffer = addlist;
+ (void)strlcpy(io.pfrio_table.pfrt_anchor, HOSTATED_ANCHOR "/",
+ sizeof(io.pfrio_table.pfrt_anchor));
+ (void)strlcat(io.pfrio_table.pfrt_anchor, service->name,
+ sizeof(io.pfrio_table.pfrt_anchor));
+ (void)strlcpy(io.pfrio_table.pfrt_name, service->name,
+ sizeof(io.pfrio_table.pfrt_name));
+
+ i = 0;
+ TAILQ_FOREACH(host, &table->hosts, entry) {
+ if (host->up != 1)
+ continue;
+ memset(&(addlist[i]), 0, sizeof(addlist[i]));
+ switch (host->ss.ss_family) {
+ case AF_INET:
+ sain = (struct sockaddr_in *)&host->ss;
+ addlist[i].pfra_af = AF_INET;
+ memcpy(&(addlist[i].pfra_ip4addr), &sain->sin_addr,
+ sizeof(sain->sin_addr));
+ addlist[i].pfra_net = 32;
+ break;
+ case AF_INET6:
+ sain6 = (struct sockaddr_in6 *)&host->ss;
+ addlist[i].pfra_af = AF_INET6;
+ memcpy(&(addlist[i].pfra_ip6addr), &sain6->sin6_addr,
+ sizeof(sain6->sin6_addr));
+ addlist[i].pfra_net = 128;
+ break;
+ default:
+ fatalx("sync_table: unknown address family");
+ break;
+ }
+ i++;
+ }
+ if (i != table->up)
+ fatalx("sync_table: desynchronized");
+
+ if (ioctl(env->pf->dev, DIOCRSETADDRS, &io) == -1)
+ fatal("sync_table: cannot set address list");
+
+ log_debug("sync_table: table %s: %d added, %d deleted, %d changed",
+ io.pfrio_table.pfrt_name,
+ io.pfrio_nadd, io.pfrio_ndel, io.pfrio_nchange);
+}
+
+void
+flush_table(struct hostated *env, struct service *service)
+{
+ struct pfioc_table io;
+
+ memset(&io, 0, sizeof(io));
+ (void)strlcpy(io.pfrio_table.pfrt_anchor, HOSTATED_ANCHOR "/",
+ sizeof(io.pfrio_table.pfrt_anchor));
+ (void)strlcat(io.pfrio_table.pfrt_anchor, service->name,
+ sizeof(io.pfrio_table.pfrt_anchor));
+ (void)strlcpy(io.pfrio_table.pfrt_name, service->name,
+ sizeof(io.pfrio_table.pfrt_name));
+ if (ioctl(env->pf->dev, DIOCRCLRADDRS, &io) == -1)
+ fatal("flush_table: cannot flush table");
+ log_debug("flush_table: flushed table %s", service->name);
+ return;
+}
+
+int
+transaction_init(struct hostated *env, const char *anchor)
+{
+ env->pf->pft.size = 1;
+ env->pf->pft.esize = sizeof env->pf->pfte;
+ env->pf->pft.array = &env->pf->pfte;
+
+ memset(&env->pf->pfte, 0, sizeof env->pf->pfte);
+ strlcpy(env->pf->pfte.anchor, anchor, PF_ANCHOR_NAME_SIZE);
+ env->pf->pfte.rs_num = PF_RULESET_RDR;
+
+ if (ioctl(env->pf->dev, DIOCXBEGIN, &env->pf->pft) == -1)
+ return (-1);
+ return (0);
+}
+
+int
+transaction_commit(struct hostated *env)
+{
+ if (ioctl(env->pf->dev, DIOCXCOMMIT, &env->pf->pft) == -1)
+ return (-1);
+ return (0);
+}
+
+void
+sync_ruleset(struct hostated *env, struct service *service, int enable)
+{
+ struct pfioc_rule rio;
+ struct pfioc_pooladdr pio;
+ struct sockaddr_in *sain;
+ struct sockaddr_in6 *sain6;
+ struct address *address;
+ char anchor[PF_ANCHOR_NAME_SIZE];
+
+ bzero(anchor, sizeof(anchor));
+ (void)strlcpy(anchor, HOSTATED_ANCHOR "/", sizeof(anchor));
+ (void)strlcat(anchor, service->name, sizeof(anchor));
+ transaction_init(env, anchor);
+
+ if (!enable) {
+ transaction_commit(env);
+ log_debug("sync_ruleset: rules removed");
+ return;
+ }
+
+ TAILQ_FOREACH(address, &service->virts, entry) {
+ memset(&rio, 0, sizeof(rio));
+ memset(&pio, 0, sizeof(pio));
+ (void)strlcpy(rio.anchor, anchor, sizeof(rio.anchor));
+
+ rio.ticket = env->pf->pfte.ticket;
+ if (ioctl(env->pf->dev, DIOCBEGINADDRS, &pio) == -1)
+ fatal("sync_ruleset: cannot initialise address pool");
+
+ rio.pool_ticket = pio.ticket;
+ rio.rule.af = address->ss.ss_family;
+ rio.rule.proto = IPPROTO_TCP;
+ rio.rule.src.addr.type = PF_ADDR_ADDRMASK;
+ rio.rule.dst.addr.type = PF_ADDR_ADDRMASK;
+ rio.rule.dst.port_op = PF_OP_EQ;
+ rio.rule.dst.port[0] = address->port;
+ rio.rule.rtableid = -1; /* stay in the main routing table */
+ rio.rule.action = PF_RDR;
+ if (strlen(service->tag))
+ (void)strlcpy(rio.rule.tagname, service->tag,
+ sizeof(rio.rule.tagname));
+ if (strlen(address->ifname))
+ (void)strlcpy(rio.rule.ifname, address->ifname,
+ sizeof(rio.rule.ifname));
+
+ if (address->ss.ss_family == AF_INET) {
+ sain = (struct sockaddr_in *)&address->ss;
+
+ rio.rule.dst.addr.v.a.addr.addr32[0] =
+ sain->sin_addr.s_addr;
+ rio.rule.dst.addr.v.a.mask.addr32[0] = 0xffffffff;
+
+ } else {
+ sain6 = (struct sockaddr_in6 *)&address->ss;
+
+ memcpy(&rio.rule.dst.addr.v.a.addr.v6,
+ &sain6->sin6_addr.s6_addr,
+ sizeof(sain6->sin6_addr.s6_addr));
+ memset(&rio.rule.dst.addr.v.a.mask.addr8, 0xff, 16);
+ }
+
+ pio.addr.addr.type = PF_ADDR_TABLE;
+ (void)strlcpy(pio.addr.addr.v.tblname, service->name,
+ sizeof(pio.addr.addr.v.tblname));
+ if (ioctl(env->pf->dev, DIOCADDADDR, &pio) == -1)
+ fatal("sync_ruleset: cannot add address to pool");
+
+ rio.rule.rpool.proxy_port[0] = service->table->port;
+ rio.rule.rpool.port_op = PF_OP_EQ;
+ rio.rule.rpool.opts = PF_POOL_ROUNDROBIN;
+
+ if (ioctl(env->pf->dev, DIOCADDRULE, &rio) == -1)
+ fatal("cannot add rule");
+ log_debug("sync_ruleset: rule added");
+ }
+ transaction_commit(env);
+}
+
+void
+flush_rulesets(struct hostated *env)
+{
+ struct service *service;
+ char anchor[PF_ANCHOR_NAME_SIZE];
+
+ kill_tables(env);
+ TAILQ_FOREACH(service, &env->services, entry) {
+ strlcpy(anchor, HOSTATED_ANCHOR "/", sizeof(anchor));
+ strlcat(anchor, service->name, sizeof(anchor));
+ transaction_init(env, anchor);
+ transaction_commit(env);
+ }
+ strlcpy(anchor, HOSTATED_ANCHOR, sizeof(anchor));
+ transaction_init(env, anchor);
+ transaction_commit(env);
+ log_debug("flush_rulesets: flushed rules");
+}
diff --git a/usr.sbin/relayd/relayd.8 b/usr.sbin/relayd/relayd.8
new file mode 100644
index 00000000000..ea9f7db07be
--- /dev/null
+++ b/usr.sbin/relayd/relayd.8
@@ -0,0 +1,94 @@
+.\" $OpenBSD: relayd.8,v 1.1 2006/12/16 11:45:07 reyk Exp $
+.\"
+.\" Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd November 1, 2006
+.Dt HOSTATED 8
+.Os
+.Sh NAME
+.Nm hostated
+.Nd Host Status daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl dnv
+.Op Fl f Ar file
+.Sh DESCRIPTION
+.Nm
+is the host status daemon for server load balancing.
+Its main purpose is to maintain pf tables up to date
+as well as related pf rdr rules.
+To communicate with
+.Xr pf 4
+.Nm
+uses the anchor facility. To enable
+.Nm
+to install rulesets through the anchor you will
+need the following line in the NAT section of your
+.Xr pf.conf 5
+configuration file:
+.Bd -literal -offset 2n
+rdr-anchor "hostated/*"
+.Ed
+.Pp
+.Nm
+manipulates three data types: services, tables and hosts.
+Each service represents a
+.Xr pf 4
+rdr rule. A service contains at least one table and one virtual ip which
+are used to create the proper rule.
+Each table contains at least one host, and is mapped to a
+.Xr pf 4
+table. Additionnaly, a table can be backed up i.e its content will be swapped
+by the content of another table when it is empty. This can be used to serve
+static content when a dynamic service goes down.
+See
+.Xr hostated.conf 5
+for a more detailed explanation of how to configure
+.Nm
+.
+.Pp
+.Xr hostatectl 8
+can be used to enable or disable hosts, tables and services as well
+as showing the current status of each object.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl d
+Do not daemonize.
+If this options is specified,
+.Nm
+will run in the foreground and log to
+.Em stderr .
+.It Fl f Ar file
+Specify an alternative configurate file.
+.It Fl n
+Configtest mode.
+Only check the configuration file for validity.
+.It Fl v
+Produce more verbose output.
+.El
+.Sh FILES
+.Bl -tag -width "/var/run/hostated.sockXX" -compact
+.It /etc/hostated.conf
+Default
+.Nm
+configuration file.
+.It /var/run/hostated.sock
+Unix-domain socket used for communication with
+.Xr hostatectl 8 .
+.El
+.Sh SEE ALSO
+.Xr hostated.conf 5 ,
+.Xr hostatectl 8
diff --git a/usr.sbin/relayd/relayd.c b/usr.sbin/relayd/relayd.c
new file mode 100644
index 00000000000..fb67140c8c8
--- /dev/null
+++ b/usr.sbin/relayd/relayd.c
@@ -0,0 +1,377 @@
+/* $OpenBSD: relayd.c,v 1.1 2006/12/16 11:45:07 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.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/param.h>
+#include <sys/wait.h>
+#include <net/if.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <signal.h>
+#include <unistd.h>
+#include <pwd.h>
+
+#include "hostated.h"
+
+__dead void usage(void);
+
+void main_sig_handler(int, short, void *);
+void main_shutdown(void);
+void main_dispatch_pfe(int, short, void *);
+void main_dispatch_hce(int, short, void *);
+int check_child(pid_t, const char *);
+
+int pipe_parent2pfe[2];
+int pipe_parent2hce[2];
+int pipe_pfe2hce[2];
+
+struct imsgbuf *ibuf_pfe;
+struct imsgbuf *ibuf_hce;
+
+pid_t pfe_pid = 0;
+pid_t hce_pid = 0;
+
+void
+main_sig_handler(int sig, short event, void *arg)
+{
+ int die = 0;
+
+ switch (sig) {
+ case SIGTERM:
+ case SIGINT:
+ die = 1;
+ case SIGCHLD:
+ if (check_child(pfe_pid, "pf udpate engine")) {
+ pfe_pid = 0;
+ die = 1;
+ }
+ if (check_child(hce_pid, "host check engine")) {
+ hce_pid = 0;
+ die = 1;
+ }
+ if (die)
+ main_shutdown();
+ break;
+ case SIGHUP:
+ /* reconfigure */
+ break;
+ default:
+ fatalx("unexpected signal");
+ }
+}
+
+/* __dead is for lint */
+__dead void
+usage(void)
+{
+ extern char *__progname;
+
+ fprintf(stderr, "%s [-dnv] [-f file]\n", __progname);
+ exit (1);
+}
+
+int main(int argc, char *argv[])
+{
+ int c;
+ int debug;
+ u_int32_t opts;
+ struct hostated env;
+ const char *conffile;
+ struct event ev_sigint;
+ struct event ev_sigterm;
+ struct event ev_sigchld;
+ struct event ev_sighup;
+
+ opts = 0;
+ debug = 0;
+ conffile = CONF_FILE;
+ bzero(&env, sizeof (env));
+
+ for (;(c = getopt(argc, argv, "dnf:v")) != -1;) {
+ switch (c) {
+ case 'd':
+ debug = 1;
+ break;
+ case 'n':
+ opts |= HOSTATED_OPT_NOACTION;
+ break;
+ case 'f':
+ conffile = optarg;
+ break;
+ case 'v':
+ opts |= HOSTATED_OPT_VERBOSE;
+ break;
+ default:
+ usage();
+ }
+
+ }
+
+ log_init(debug);
+
+ if (parse_config(&env, conffile, opts))
+ exit(1);
+
+ if (env.opts & HOSTATED_OPT_NOACTION) {
+ fprintf(stderr, "configuration OK\n");
+ exit(0);
+ }
+
+ if (geteuid())
+ errx(1, "need root privileges");
+
+ if (getpwnam(HOSTATED_USER) == NULL)
+ errx(1, "unknown user %s", HOSTATED_USER);
+
+ if (!debug)
+ daemon(1, 0);
+
+ log_info("startup");
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_parent2pfe) == -1)
+ fatal("socketpair");
+ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_parent2hce) == -1)
+ fatal("socketpair");
+ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_pfe2hce) == -1)
+ fatal("socketpair");
+
+ session_socket_blockmode(pipe_parent2pfe[0], BM_NONBLOCK);
+ session_socket_blockmode(pipe_parent2pfe[1], BM_NONBLOCK);
+ session_socket_blockmode(pipe_parent2hce[0], BM_NONBLOCK);
+ session_socket_blockmode(pipe_parent2hce[1], BM_NONBLOCK);
+ session_socket_blockmode(pipe_pfe2hce[0], BM_NONBLOCK);
+ session_socket_blockmode(pipe_pfe2hce[1], BM_NONBLOCK);
+
+ pfe_pid = pfe(&env, pipe_parent2pfe, pipe_parent2hce, pipe_pfe2hce);
+ hce_pid = hce(&env, pipe_parent2pfe, pipe_parent2hce, pipe_pfe2hce);
+
+ setproctitle("parent");
+
+ event_init();
+
+ signal_set(&ev_sigint, SIGINT, main_sig_handler, NULL);
+ signal_set(&ev_sigterm, SIGTERM, main_sig_handler, NULL);
+ signal_set(&ev_sigchld, SIGCHLD, main_sig_handler, NULL);
+ signal_set(&ev_sighup, SIGHUP, main_sig_handler, NULL);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+ signal_add(&ev_sigchld, NULL);
+ signal_add(&ev_sighup, NULL);
+
+ close(pipe_parent2pfe[1]);
+ close(pipe_parent2hce[1]);
+ close(pipe_pfe2hce[0]);
+ close(pipe_pfe2hce[1]);
+
+ if ((ibuf_pfe = calloc(1, sizeof(struct imsgbuf))) == NULL ||
+ (ibuf_hce = calloc(1, sizeof(struct imsgbuf))) == NULL)
+ fatal(NULL);
+
+ imsg_init(ibuf_pfe, pipe_parent2pfe[0], main_dispatch_pfe);
+ imsg_init(ibuf_hce, pipe_parent2hce[0], main_dispatch_hce);
+
+ ibuf_pfe->events = EV_READ;
+ event_set(&ibuf_pfe->ev, ibuf_pfe->fd, ibuf_pfe->events,
+ ibuf_pfe->handler, ibuf_pfe);
+ event_add(&ibuf_pfe->ev, NULL);
+
+ ibuf_hce->events = EV_READ;
+ event_set(&ibuf_hce->ev, ibuf_hce->fd, ibuf_hce->events,
+ ibuf_hce->handler, ibuf_hce);
+ event_add(&ibuf_hce->ev, NULL);
+
+ event_dispatch();
+
+ return (0);
+}
+
+void
+main_shutdown(void)
+{
+ pid_t pid;
+
+ if (pfe_pid)
+ kill(pfe_pid, SIGTERM);
+ if (hce_pid)
+ kill(hce_pid, SIGTERM);
+
+ do {
+ if ((pid = wait(NULL)) == -1 &&
+ errno != EINTR && errno != ECHILD)
+ fatal("wait");
+ } while (pid != -1 || (pid == -1 && errno == EINTR));
+
+ control_cleanup();
+ log_info("terminating");
+ exit(0);
+}
+
+int
+check_child(pid_t pid, const char *pname)
+{
+ int status;
+
+ if (waitpid(pid, &status, WNOHANG) > 0) {
+ if (WIFEXITED(status)) {
+ log_warnx("check_child: lost child: %s exited", pname);
+ return (1);
+ }
+ if (WIFSIGNALED(status)) {
+ log_warnx("check_child: lost child: %s terminated; signal %d",
+ pname, WTERMSIG(status));
+ return (1);
+ }
+ }
+
+ return (0);
+}
+
+void
+imsg_event_add(struct imsgbuf *ibuf)
+{
+ ibuf->events = EV_READ;
+ if (ibuf->w.queued)
+ ibuf->events |= EV_WRITE;
+
+ event_del(&ibuf->ev);
+ event_set(&ibuf->ev, ibuf->fd, ibuf->events, ibuf->handler, ibuf);
+ event_add(&ibuf->ev, NULL);
+}
+
+void
+main_dispatch_pfe(int fd, short event, void *ptr)
+{
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = ptr;
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0)
+ fatalx("parent: pipe closed");
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("main_dispatch_pfe: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ log_debug("main_dispatch_pfe: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+main_dispatch_hce(int fd, short event, void * ptr)
+{
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = ptr;
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read error");
+ if (n == 0) /* connection closed */
+ fatalx("parent: pipe closed");
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("main_dispatch_hce: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ log_debug("main_dispatch_hce: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+}
+
+struct host *
+host_find(struct hostated *env, objid_t id)
+{
+ struct table *table;
+ struct host *host;
+
+ TAILQ_FOREACH(table, &env->tables, entry)
+ TAILQ_FOREACH(host, &table->hosts, entry)
+ if (host->id == id)
+ return (host);
+ return (NULL);
+}
+
+struct table *
+table_find(struct hostated *env, objid_t id)
+{
+ struct table *table;
+
+ TAILQ_FOREACH(table, &env->tables, entry)
+ if (table->id == id)
+ return (table);
+ return (NULL);
+}
+
+struct service *
+service_find(struct hostated *env, objid_t id)
+{
+ struct service *service;
+
+ TAILQ_FOREACH(service, &env->services, entry)
+ if (service->id == id)
+ return (service);
+ return (NULL);
+}
diff --git a/usr.sbin/relayd/relayd.conf.5 b/usr.sbin/relayd/relayd.conf.5
new file mode 100644
index 00000000000..349dce672b4
--- /dev/null
+++ b/usr.sbin/relayd/relayd.conf.5
@@ -0,0 +1,214 @@
+.\" $OpenBSD: relayd.conf.5,v 1.1 2006/12/16 11:45:07 reyk Exp $
+.\"
+.\" Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd November 1, 2006
+.Dt HOSTATED.CONF 5
+.Os
+.Sh NAME
+.Nm hostated.conf
+.Nd Host Status daemon configuration file.
+.Sh DESCRIPTION
+The
+.Xr hostated 8
+daemon maintains
+.Xr pf 4
+tables up to date.
+.Sh SECTIONS
+The
+.Nm
+configuration file is divided into four main sections.
+.Bl -tag -width xxxx
+.It Sy Macros
+User-defined variables may be defined and used later, simplifying the
+configuration file.
+.It Sy Global Configuration
+Global settings for
+.Xr hostated 8 .
+.It Sy Tables
+Table definitions describe the content of a
+.Xr pf 4
+table and the method used for checking the health of the hosts
+they contain.
+.It Sy Services
+Services will be translated to
+.Xr pf 4
+rdr rules if their table or backup table have content.
+.El
+.Sh MACROS
+Macros can be defined that will later be expanded in context.
+Macro names must start with a letter, and may contain letters, digits,
+and underscores.
+Macro names may not be reserved words (for example,
+.Ic table ,
+.Ic service ,
+or
+.Ic timeout ) .
+Macros are not expanded inside quotes.
+.Pp
+For example:
+.Bd -literal -offset indent
+www1="10.0.0.1"
+www2="10.0.0.2"
+table webhosts {
+ check tcp
+ timeout 300
+ real port 80
+ host $www1
+ host $www2
+}
+.Ed
+.Sh GLOBAL CONFIGURATION
+Only one global setting can be set.
+.Pp
+.Bl -tag -width Ds -compact
+.It Xo
+.Ic interval Ar number
+.Xc
+Set the interval in seconds at which the hosts will be checked.
+The default interval is 10 seconds.
+.El
+.Sh TABLES
+Tables are used to group a set of hosts that can be checked using the same
+method. Only one health-checking method can be used per table.
+Table specific configuration directives are described below.
+.Bl -tag -width Ds
+.It Ic check tcp
+Use a simple tcp connect to check that hosts are up.
+.It Ic check icmp
+Ping hosts in this table to determine wether they are up or not.
+This method will automatically use icmp or icmpv6 depending on the
+address family of each host.
+.It Ic check http Ar path Ic code Ar number
+For each host in the table, verify that retrieving the URL
+.Ar path
+gives the HTTP return code
+.Ar number
+.It Ic check http Ar path Ic digest Ar string
+For each host in the table, verify that retrieving the URL
+.Ar path
+produces a content whose SHA1 digest is
+.Ar digest
+. The digest does not take the HTTP headers into account. To compute the
+digest you can use this simple command:
+.Bd -literal -offset 2n
+ftp -o - http://host[:port]/path | sha1
+
+.Ed
+This will give you a digest of the form
+.Bd -literal -offset 2n
+a9993e36476816aba3e25717850c26c9cd0d89d
+
+.Ed
+that you can use as-is in your digest statement.
+.It Ic timeout Ar number
+Set the timeout in milliseconds for each host that is checked.
+The default timeout is 200 milliseconds.
+.It Ic real port Ar number
+When using the tcp or http checking methods, use this port to connect
+to hosts. This parameter is mandatory. Main and backup tables need
+to have the same real port.
+.It Ic host Ar address
+Add the host whose address is
+.Ar address
+to the list of hosts to be checked in this table.
+Each table needs at least one host.
+.It Ic disable
+Start the table disabled, no hosts will be checked in this table.
+The table can be later enabled through
+.Xr hostatectl 8 .
+.El
+.Sh SERVICES
+Services represent a
+.Xr pf 4
+rdr rule, they are used to specify which addresses will be redirected
+to the hosts in the specified tables.
+The configuration directives that are valid in this context are described
+below.
+.Bl -tag -width Ds
+.It Ic virtual ip Ar address Ic port Ar number
+Specify an address and a port that will be used to redirect requests
+to the hosts in the main or backup table.
+Optionally an interface name can be specified like this
+.Bd -literal -offset indent
+interface ``ifname''
+
+.Ed
+to specify which interface the rdr rule will be enabled on.
+.It Ic table Ar name
+Specify the main table to be used. This is mandatory.
+.It Ic backup table Ar name
+Specify the table to switch to when all hosts in the main table
+are seen as down or disabled.
+.It Ic disable
+Set the service initially disabled. It can be later enabled through
+.It Ic tag Ar name
+Automatically tag packets passing through the
+.Xr pf 4
+rdr rule with the name supplied. This allows for easier filter rules
+in your main
+.Xr pf 4
+configuration.
+.Xr hostatectl 5 .
+.El
+.Sh EXAMPLE
+This configuration file would create a service 'www' which load-balances
+4 hosts and falls back to 1 host containing a ``sorry page'':
+.Bd -literal -offset indent
+##
+##
+www1=front-www1.private.example.com
+www2=front-www2.private.example.com
+www3=front-www3.private.example.com
+www4=front-www4.private.example.com
+
+interval 5
+
+table phphosts {
+ timeout 300
+ real port 8080
+ check http "/" digest 630aa3c2f...
+ host $www1
+ host $www2
+ host $www3
+ host $www4
+}
+
+table sorryhost {
+ check icmp
+ disable
+ timeout 300
+ real port 8080
+ host sorryhost.private.example.com
+}
+
+service www {
+ virtual ip www.example.com port 8080 interface trunk0
+ virtual ip www6.example.com port 80 interface trunk0
+
+ tag HOSTATED
+ table phphosts
+ backup table sorryhost
+}
+.Ed
+.Sh FILES
+.Bl -tag -width "/etc/hostated.conf" -compact
+.It Pa /etc/hostated.conf
+.Xr hostated 8
+configuration file
+.El
+.Sh SEE ALSO
+.Xr hostated 8 ,
+.Xr hostatectl 8 .
diff --git a/usr.sbin/relayd/relayd.h b/usr.sbin/relayd/relayd.h
new file mode 100644
index 00000000000..d59a934be48
--- /dev/null
+++ b/usr.sbin/relayd/relayd.h
@@ -0,0 +1,307 @@
+/*
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.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.
+ */
+
+#define CONF_FILE "/etc/hostated.conf"
+#define HOSTATED_SOCKET "/var/run/hostated.sock"
+#define PF_SOCKET "/dev/pf"
+#define HOSTATED_USER "_hostated"
+#define HOSTATED_ANCHOR "hostated"
+#define CONNECT_TIMEOUT 200
+#define CHECK_INTERVAL 10
+#define EMPTY_TABLE UINT_MAX
+#define TABLE_NAME_SIZE 16
+#define TAG_NAME_SIZE 16
+#define SRV_NAME_SIZE 16
+#define SRV_MAX_VIRTS 16
+
+#define READ_BUF_SIZE 65535
+
+/* buffer */
+struct buf {
+ TAILQ_ENTRY(buf) entry;
+ u_char *buf;
+ size_t size;
+ size_t max;
+ size_t wpos;
+ size_t rpos;
+};
+
+struct msgbuf {
+ TAILQ_HEAD(, buf) bufs;
+ u_int32_t queued;
+ int fd;
+};
+
+#define IMSG_HEADER_SIZE sizeof(struct imsg_hdr)
+#define MAX_IMSGSIZE 8192
+
+struct buf_read {
+ u_char buf[READ_BUF_SIZE];
+ u_char *rptr;
+ size_t wpos;
+};
+
+struct imsgbuf {
+ TAILQ_HEAD(, imsg_fd) fds;
+ struct buf_read r;
+ struct msgbuf w;
+ struct event ev;
+ void (*handler)(int, short, void *);
+ int fd;
+ pid_t pid;
+ short events;
+};
+
+enum imsg_type {
+ IMSG_NONE,
+ IMSG_CTL_OK, /* answer to hostatectl requests */
+ IMSG_CTL_FAIL,
+ IMSG_CTL_END,
+ IMSG_CTL_SERVICE,
+ IMSG_CTL_TABLE,
+ IMSG_CTL_HOST,
+ IMSG_CTL_SHOW_SUM, /* hostatectl requests */
+ IMSG_CTL_SERVICE_ENABLE,
+ IMSG_CTL_SERVICE_DISABLE,
+ IMSG_CTL_TABLE_ENABLE,
+ IMSG_CTL_TABLE_DISABLE,
+ IMSG_CTL_HOST_ENABLE,
+ IMSG_CTL_HOST_DISABLE,
+ IMSG_CTL_SHUTDOWN,
+ IMSG_CTL_RELOAD,
+ IMSG_SERVICE_ENABLE, /* notifies from pfe to hce */
+ IMSG_SERVICE_DISABLE,
+ IMSG_TABLE_ENABLE,
+ IMSG_TABLE_DISABLE,
+ IMSG_HOST_ENABLE,
+ IMSG_HOST_DISABLE,
+ IMSG_TABLE_STATUS, /* notifies from hce to pfe */
+ IMSG_HOST_STATUS,
+ IMSG_SYNC
+};
+
+struct imsg_hdr {
+ enum imsg_type type;
+ u_int16_t len;
+ u_int32_t peerid;
+ pid_t pid;
+};
+
+struct imsg {
+ struct imsg_hdr hdr;
+ void *data;
+};
+
+typedef u_int32_t objid_t;
+
+struct ctl_status {
+ objid_t id;
+ int up;
+};
+
+struct address {
+ struct sockaddr_storage ss;
+ in_port_t port;
+ char ifname[IFNAMSIZ];
+ TAILQ_ENTRY(address) entry;
+};
+TAILQ_HEAD(addresslist, address);
+
+#define F_DISABLE 0x01
+#define F_BACKUP 0x02
+#define F_USED 0x04
+#define F_ACTIVE_RULESET 0x04
+#define F_DOWN 0x08
+#define F_ADD 0x10
+#define F_DEL 0x20
+#define F_CHANGED 0x40
+
+struct host {
+ u_int8_t flags;
+ objid_t id;
+ objid_t tableid;
+ char *tablename;
+ char name[MAXHOSTNAMELEN];
+ int up;
+#define HOST_DOWN -1
+#define HOST_UNKNOWN 0
+#define HOST_UP 1
+ struct sockaddr_storage ss;
+ TAILQ_ENTRY(host) entry;
+};
+TAILQ_HEAD(hostlist, host);
+
+struct table {
+ objid_t id;
+ objid_t serviceid;
+ u_int8_t flags;
+ int check;
+#define CHECK_NOCHECK 0
+#define CHECK_ICMP 1
+#define CHECK_TCP 2
+#define CHECK_HTTP_CODE 3
+#define CHECK_HTTP_DIGEST 4
+ int up;
+ in_port_t port;
+ int retcode;
+ int timeout;
+ char name[TABLE_NAME_SIZE];
+ char path[MAXPATHLEN];
+ char digest[41]; /* length of sha1 digest * 2 */
+ struct hostlist hosts;
+ TAILQ_ENTRY(table) entry;
+};
+TAILQ_HEAD(tablelist, table);
+
+struct service {
+ objid_t id;
+ u_int8_t flags;
+ in_port_t port;
+ char name[SRV_NAME_SIZE];
+ char tag[TAG_NAME_SIZE];
+ struct addresslist virts;
+ struct table *table;
+ struct table *backup; /* use this if no host up */
+ TAILQ_ENTRY(service) entry;
+};
+TAILQ_HEAD(servicelist, service);
+
+enum {
+ PROC_MAIN,
+ PROC_PFE,
+ PROC_HCE
+} hostated_process;
+
+struct hostated {
+ u_int8_t opts;
+#define HOSTATED_OPT_VERBOSE 0x01
+#define HOSTATED_OPT_NOACTION 0x04
+ struct pfdata *pf;
+ int interval;
+ int icmp_sock;
+ int icmp6_sock;
+ int tablecount;
+ int servicecount;
+ struct table empty_table;
+ struct event ev;
+ struct tablelist tables;
+ struct servicelist services;
+};
+
+/* initially control.h */
+struct {
+ struct event ev;
+ int fd;
+} control_state;
+
+enum blockmodes {
+ BM_NORMAL,
+ BM_NONBLOCK
+};
+
+struct ctl_conn {
+ TAILQ_ENTRY(ctl_conn) entry;
+ struct imsgbuf ibuf;
+
+};
+TAILQ_HEAD(ctl_connlist, ctl_conn);
+
+/* control.c */
+int control_init(void);
+int control_listen(void);
+void control_accept(int, short, void *);
+void control_dispatch_imsg(int, short, void *);
+int control_imsg_relay(struct imsg *);
+void control_cleanup(void);
+
+void session_socket_blockmode(int, enum blockmodes);
+
+extern struct ctl_connlist ctl_conns;
+
+/* parse.y */
+int parse_config(struct hostated *, const char *, int);
+
+/* log.c */
+void log_init(int);
+void log_warn(const char *, ...);
+void log_warnx(const char *, ...);
+void log_info(const char *, ...);
+void log_debug(const char *, ...);
+void fatal(const char *);
+void fatalx(const char *);
+
+/* buffer.c */
+struct buf *buf_open(size_t);
+struct buf *buf_dynamic(size_t, size_t);
+int buf_add(struct buf *, void *, size_t);
+void *buf_reserve(struct buf *, size_t);
+void *buf_seek(struct buf *, size_t, size_t);
+int buf_close(struct msgbuf *, struct buf *);
+void buf_free(struct buf *);
+void msgbuf_init(struct msgbuf *);
+void msgbuf_clear(struct msgbuf *);
+int msgbuf_write(struct msgbuf *);
+
+/* imsg.c */
+void imsg_init(struct imsgbuf *, int, void (*)(int, short, void *));
+ssize_t imsg_read(struct imsgbuf *);
+ssize_t imsg_get(struct imsgbuf *, struct imsg *);
+int imsg_compose(struct imsgbuf *, enum imsg_type, u_int32_t, pid_t,
+ void *, u_int16_t);
+struct buf *imsg_create(struct imsgbuf *, enum imsg_type, u_int32_t, pid_t,
+ u_int16_t);
+int imsg_add(struct buf *, void *, u_int16_t);
+int imsg_close(struct imsgbuf *, struct buf *);
+void imsg_free(struct imsg *);
+void imsg_event_add(struct imsgbuf *); /* needs to be provided externally */
+
+/* pfe.c */
+pid_t pfe(struct hostated *, int [2], int [2], int [2]);
+void show(struct ctl_conn *);
+int enable_service(struct ctl_conn *, objid_t);
+int enable_table(struct ctl_conn *, objid_t);
+int enable_host(struct ctl_conn *, objid_t);
+int disable_service(struct ctl_conn *, objid_t);
+int disable_table(struct ctl_conn *, objid_t);
+int disable_host(struct ctl_conn *, objid_t);
+
+/* pfe_filter.c */
+void init_filter(struct hostated *);
+void init_tables(struct hostated *);
+void flush_table(struct hostated *, struct service *);
+void sync_table(struct hostated *, struct service *, struct table *);
+void sync_ruleset(struct hostated *, struct service *, int);
+void flush_rulesets(struct hostated *);
+
+/* hce.c */
+pid_t hce(struct hostated *, int [2], int [2], int [2]);
+
+/* check_icmp.c */
+int check_icmp(struct host *, int, int, int);
+
+/* check_tcp.c */
+int check_tcp(struct host *, struct table *);
+int tcp_connect(struct host *, struct table *);
+
+/* check_tcp.c */
+int check_http_code(struct host *, struct table *);
+int check_http_digest(struct host *, struct table *);
+
+/* hostated.c */
+struct host *host_find(struct hostated *, objid_t);
+struct table *table_find(struct hostated *, objid_t);
+struct service *service_find(struct hostated *, objid_t);