Commit Diff


commit - /dev/null
commit + 41042ecc3a6168d2e49663712058487a80d9e99b
blob - /dev/null
blob + c04e70f30369a2a0190da1934dd43dec4594434e (mode 644)
--- /dev/null
+++ 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>
blob - /dev/null
blob + 3abe27b1e6255ffd5c3fb8a73b28bcbee905c012 (mode 644)
--- /dev/null
+++ 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);
+}
blob - /dev/null
blob + df92b444af9499a1a822448f24f922b71b14099b (mode 644)
--- /dev/null
+++ 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);
+}
blob - /dev/null
blob + 5ef386731f941dcab225c0646f1e2b52b1c41f56 (mode 644)
--- /dev/null
+++ 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);
+}
blob - /dev/null
blob + 2f994e458f4d41227d116a6cbc00d5f46734e184 (mode 644)
--- /dev/null
+++ 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");
+}
blob - /dev/null
blob + 7144357538bb33993c14cf58af1181aadde10c45 (mode 644)
--- /dev/null
+++ 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);
+        }
+}
blob - /dev/null
blob + feafd8700c18e9c53a45e186ae41f4b210d35ef4 (mode 644)
--- /dev/null
+++ 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);
+}
blob - /dev/null
blob + 28a7c9a7d4ca482884630d674a277697de3168c2 (mode 644)
--- /dev/null
+++ 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);
+}
blob - /dev/null
blob + 977b7a74cb1f946bb96e1d9ab513a5b86c34abc6 (mode 644)
--- /dev/null
+++ 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));
+}
blob - /dev/null
blob + 0a7ff43949fcf5e36612a4b8f82bbaf1c2ce7aeb (mode 644)
--- /dev/null
+++ 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);
+		}
+	}
+}
blob - /dev/null
blob + b7bd7550070e1952afd1c3b43964a1099d84e788 (mode 644)
--- /dev/null
+++ 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");
+}
blob - /dev/null
blob + ea9f7db07be07c236fd71a7d532f24b3fa4fcff1 (mode 644)
--- /dev/null
+++ 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
blob - /dev/null
blob + fb67140c8c89a4611c3c32bf7dceb9bf8010e515 (mode 644)
--- /dev/null
+++ 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);
+}
blob - /dev/null
blob + 349dce672b479036f0ea93146ccda2990230e18c (mode 644)
--- /dev/null
+++ 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 .
blob - /dev/null
blob + d59a934be4843daed15d003b5bdc38aac1b03d0b (mode 644)
--- /dev/null
+++ 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);