Index: contrib/relayctl/relayctl.c =================================================================== --- contrib/relayctl/relayctl.c (revision 0) +++ contrib/relayctl/relayctl.c (revision 0) @@ -0,0 +1,530 @@ +/* $OpenBSD: relayctl.c,v 1.38 2009/08/07 11:21:53 reyk Exp $ */ + +/* + * Copyright (c) 2006 Pierre-Yves Ritschard + * Copyright (c) 2005 Claudio Jeker + * Copyright (c) 2004, 2005 Esben Norby + * Copyright (c) 2003 Henning Brauer + * + * 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 +#ifdef __FreeBSD__ +#include +#endif +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "relayd.h" +#include "parser.h" + +__dead void usage(void); +int show_summary_msg(struct imsg *, int); +int show_session_msg(struct imsg *); +int show_command_output(struct imsg *); +char *print_rdr_status(int); +char *print_host_status(int, int); +char *print_table_status(int, int); +char *print_relay_status(int); +void print_statistics(struct ctl_stats[RELAY_MAXPROC + 1]); + +struct imsgname { + int type; + char *name; + void (*func)(struct imsg *); +}; + +struct imsgname *monitor_lookup(u_int8_t); +void monitor_host_status(struct imsg *); +void monitor_id(struct imsg *); +int monitor(struct imsg *); + +struct imsgname imsgs[] = { + { IMSG_HOST_STATUS, "host_status", monitor_host_status }, + { IMSG_CTL_RDR_DISABLE, "ctl_rdr_disable", monitor_id }, + { IMSG_CTL_RDR_ENABLE, "ctl_rdr_enable", monitor_id }, + { IMSG_CTL_TABLE_DISABLE, "ctl_table_disable", monitor_id }, + { IMSG_CTL_TABLE_ENABLE, "ctl_table_enable", monitor_id }, + { IMSG_CTL_HOST_DISABLE, "ctl_host_disable", monitor_id }, + { IMSG_CTL_HOST_ENABLE, "ctl_host_enable", monitor_id }, + { IMSG_CTL_TABLE_CHANGED, "ctl_table_changed", monitor_id }, + { IMSG_CTL_PULL_RULESET, "ctl_pull_ruleset", monitor_id }, + { IMSG_CTL_PUSH_RULESET, "ctl_push_ruleset", monitor_id }, + { IMSG_SYNC, "sync", NULL }, + { 0, NULL, NULL } +}; +struct imsgname imsgunknown = { + -1, "", NULL +}; + +struct imsgbuf *ibuf; +int error = 0; + +__dead void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s command [argument ...]\n", __progname); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + struct sockaddr_un sun; + struct parse_result *res; + struct imsg imsg; + int ctl_sock; + int done = 0; + int n; + + /* parse options */ + if ((res = parse(argc - 1, argv + 1)) == NULL) + exit(1); + + /* connect to relayd control socket */ + if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + err(1, "socket"); + + bzero(&sun, sizeof(sun)); + sun.sun_family = AF_UNIX; + strlcpy(sun.sun_path, RELAYD_SOCKET, sizeof(sun.sun_path)); + reconnect: + if (connect(ctl_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1) { + /* Keep retrying if running in monitor mode */ + if (res->action == MONITOR && + (errno == ENOENT || errno == ECONNREFUSED)) { + usleep(100); + goto reconnect; + } + err(1, "connect: %s", RELAYD_SOCKET); + } + + if ((ibuf = malloc(sizeof(struct imsgbuf))) == NULL) + err(1, NULL); + imsg_init(ibuf, ctl_sock); + done = 0; + + /* process user request */ + switch (res->action) { + case NONE: + usage(); + /* not reached */ + case SHOW_SUM: + case SHOW_HOSTS: + case SHOW_RDRS: + case SHOW_RELAYS: + imsg_compose(ibuf, IMSG_CTL_SHOW_SUM, 0, 0, -1, NULL, 0); + printf("%-4s\t%-8s\t%-24s\t%-7s\tStatus\n", + "Id", "Type", "Name", "Avlblty"); + break; + case SHOW_SESSIONS: + imsg_compose(ibuf, IMSG_CTL_SESSION, 0, 0, -1, NULL, 0); + break; + case RDR_ENABLE: + imsg_compose(ibuf, IMSG_CTL_RDR_ENABLE, 0, 0, -1, + &res->id, sizeof(res->id)); + break; + case RDR_DISABLE: + imsg_compose(ibuf, IMSG_CTL_RDR_DISABLE, 0, 0, -1, + &res->id, sizeof(res->id)); + break; + case TABLE_ENABLE: + imsg_compose(ibuf, IMSG_CTL_TABLE_ENABLE, 0, 0, -1, + &res->id, sizeof(res->id)); + break; + case TABLE_DISABLE: + imsg_compose(ibuf, IMSG_CTL_TABLE_DISABLE, 0, 0, -1, + &res->id, sizeof(res->id)); + break; + case HOST_ENABLE: + imsg_compose(ibuf, IMSG_CTL_HOST_ENABLE, 0, 0, -1, + &res->id, sizeof(res->id)); + break; + case HOST_DISABLE: + imsg_compose(ibuf, IMSG_CTL_HOST_DISABLE, 0, 0, -1, + &res->id, sizeof(res->id)); + break; + case SHUTDOWN: + imsg_compose(ibuf, IMSG_CTL_SHUTDOWN, 0, 0, -1, NULL, 0); + break; + case POLL: + imsg_compose(ibuf, IMSG_CTL_POLL, 0, 0, -1, NULL, 0); + break; + case RELOAD: + imsg_compose(ibuf, IMSG_CTL_RELOAD, 0, 0, -1, NULL, 0); + break; + case MONITOR: + imsg_compose(ibuf, IMSG_CTL_NOTIFY, 0, 0, -1, NULL, 0); + break; + } + + while (ibuf->w.queued) + if (msgbuf_write(&ibuf->w) < 0) + err(1, "write error"); + + while (!done) { + if ((n = imsg_read(ibuf)) == -1) + errx(1, "imsg_read error"); + if (n == 0) + errx(1, "pipe closed"); + + while (!done) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + errx(1, "imsg_get error"); + if (n == 0) + break; + switch (res->action) { + case SHOW_SUM: + case SHOW_HOSTS: + case SHOW_RDRS: + case SHOW_RELAYS: + done = show_summary_msg(&imsg, res->action); + break; + case SHOW_SESSIONS: + done = show_session_msg(&imsg); + break; + case RDR_DISABLE: + case RDR_ENABLE: + case TABLE_DISABLE: + case TABLE_ENABLE: + case HOST_DISABLE: + case HOST_ENABLE: + case POLL: + case RELOAD: + case SHUTDOWN: + done = show_command_output(&imsg); + break; + case NONE: + break; + case MONITOR: + done = monitor(&imsg); + break; + } + imsg_free(&imsg); + } + } + close(ctl_sock); + free(ibuf); + + return (error ? 1 : 0); +} + +struct imsgname * +monitor_lookup(u_int8_t type) +{ + int i; + + for (i = 0; imsgs[i].name != NULL; i++) + if (imsgs[i].type == type) + return (&imsgs[i]); + return (&imsgunknown); +} + +void +monitor_host_status(struct imsg *imsg) +{ + struct ctl_status cs; + + memcpy(&cs, imsg->data, sizeof(cs)); + printf("\tid: %u\n", cs.id); + printf("\tstate: "); + switch (cs.up) { + case HOST_UP: + printf("up\n"); + break; + case HOST_DOWN: + printf("down\n"); + break; + default: + printf("unknown\n"); + break; + } +} + +void +monitor_id(struct imsg *imsg) +{ + struct ctl_id id; + + memcpy(&id, imsg->data, sizeof(id)); + printf("\tid: %u\n", id.id); + if (strlen(id.name)) + printf("\tname: %s\n", id.name); +} + +int +monitor(struct imsg *imsg) +{ + time_t now; + int done = 0; + struct imsgname *imn; + + now = time(NULL); + + imn = monitor_lookup(imsg->hdr.type); + printf("%s: imsg type %u len %u peerid %u pid %d\n", imn->name, + imsg->hdr.type, imsg->hdr.len, imsg->hdr.peerid, imsg->hdr.pid); +#ifdef __FreeBSD__ + printf("\ttimestamp: %lu, %s", (unsigned long)now, ctime(&now)); +#else + printf("\ttimestamp: %u, %s", now, ctime(&now)); +#endif + if (imn->type == -1) + done = 1; + if (imn->func != NULL) + (*imn->func)(imsg); + + return (done); +} + +int +show_summary_msg(struct imsg *imsg, int type) +{ + struct rdr *rdr; + struct table *table; + struct host *host; + struct relay *rlay; + struct ctl_stats stats[RELAY_MAXPROC]; + char name[MAXHOSTNAMELEN]; + + switch (imsg->hdr.type) { + case IMSG_CTL_RDR: + if (type == SHOW_HOSTS || type == SHOW_RELAYS) + break; + rdr = imsg->data; + printf("%-4u\t%-8s\t%-24s\t%-7s\t%s\n", + rdr->conf.id, "redirect", rdr->conf.name, "", + print_rdr_status(rdr->conf.flags)); + break; + case IMSG_CTL_TABLE: + if (type == SHOW_RELAYS || type == SHOW_RDRS) + break; + table = imsg->data; + printf("%-4u\t%-8s\t%-24s\t%-7s\t%s\n", + table->conf.id, "table", table->conf.name, "", + print_table_status(table->up, table->conf.flags)); + break; + case IMSG_CTL_HOST: + if (type == SHOW_RELAYS || type == SHOW_RDRS) + break; + host = imsg->data; + if (host->conf.parentid) + snprintf(name, sizeof(name), "%s parent %u", + host->conf.name, host->conf.parentid); + else + strlcpy(name, host->conf.name, sizeof(name)); + printf("%-4u\t%-8s\t%-24s\t%-7s\t%s\n", + host->conf.id, "host", name, + print_availability(host->check_cnt, host->up_cnt), + print_host_status(host->up, host->flags)); + if (type == SHOW_HOSTS && host->check_cnt) { + printf("\t%8s\ttotal: %lu/%lu checks", + "", host->up_cnt, host->check_cnt); + if (host->retry_cnt) + printf(", %d retries", host->retry_cnt); + if (host->he && host->up == HOST_DOWN) + printf(", error: %s", host_error(host->he)); + printf("\n"); + } + break; + case IMSG_CTL_RELAY: + if (type == SHOW_HOSTS || type == SHOW_RDRS) + break; + rlay = imsg->data; + printf("%-4u\t%-8s\t%-24s\t%-7s\t%s\n", + rlay->rl_conf.id, "relay", rlay->rl_conf.name, "", + print_relay_status(rlay->rl_conf.flags)); + break; + case IMSG_CTL_RDR_STATS: + if (type != SHOW_RDRS) + break; + bcopy(imsg->data, &stats[0], sizeof(stats[0])); + stats[1].id = EMPTY_ID; + print_statistics(stats); + break; + case IMSG_CTL_RELAY_STATS: + if (type != SHOW_RELAYS) + break; + bcopy(imsg->data, &stats, sizeof(stats)); + print_statistics(stats); + break; + case IMSG_CTL_END: + return (1); + default: + errx(1, "wrong message in summary: %u", imsg->hdr.type); + break; + } + return (0); +} + +int +show_session_msg(struct imsg *imsg) +{ + struct rsession *con; + char a[128], b[128]; + struct timeval tv_now; + + switch (imsg->hdr.type) { + case IMSG_CTL_SESSION: + con = imsg->data; + + (void)print_host(&con->se_in.ss, a, sizeof(a)); + (void)print_host(&con->se_out.ss, b, sizeof(b)); + printf("session %u:%u %s:%u -> %s:%u\t%s\n", + imsg->hdr.peerid, con->se_id, + a, ntohs(con->se_in.port), b, ntohs(con->se_out.port), + con->se_done ? "DONE" : "RUNNING"); + + if (gettimeofday(&tv_now, NULL)) + fatal("show_session_msg: gettimeofday"); + print_time(&tv_now, &con->se_tv_start, a, sizeof(a)); + print_time(&tv_now, &con->se_tv_last, b, sizeof(b)); + printf("\tage %s, idle %s, relay %u", a, b, con->se_relayid); + if (con->se_mark) + printf(", mark %u", con->se_mark); + printf("\n"); + break; + case IMSG_CTL_END: + return (1); + default: + errx(1, "wrong message in session: %u", imsg->hdr.type); + break; + } + return (0); +} + +int +show_command_output(struct imsg *imsg) +{ + switch (imsg->hdr.type) { + case IMSG_CTL_OK: + printf("command succeeded\n"); + break; + case IMSG_CTL_FAIL: + printf("command failed\n"); + error++; + break; + default: + errx(1, "wrong message in summary: %u", imsg->hdr.type); + } + return (1); +} + +char * +print_rdr_status(int flags) +{ + if (flags & F_DISABLE) { + return ("disabled"); + } else if (flags & F_DOWN) { + return ("down"); + } else if (flags & F_BACKUP) { + return ("active (using backup table)"); + } else + return ("active"); +} + +char * +print_table_status(int up, int fl) +{ + static char buf[1024]; + + bzero(buf, sizeof(buf)); + + if (fl & F_DISABLE) { + snprintf(buf, sizeof(buf) - 1, "disabled"); + } else if (!up) { + snprintf(buf, sizeof(buf) - 1, "empty"); + } else + snprintf(buf, sizeof(buf) - 1, "active (%d hosts)", up); + return (buf); +} + +char * +print_host_status(int status, int fl) +{ + if (fl & F_DISABLE) + return ("disabled"); + + switch (status) { + case HOST_DOWN: + return ("down"); + case HOST_UNKNOWN: + return ("unknown"); + case HOST_UP: + return ("up"); + default: + errx(1, "invalid status: %d", status); + } +} + +char * +print_relay_status(int flags) +{ + if (flags & F_DISABLE) { + return ("disabled"); + } else + return ("active"); +} + +void +print_statistics(struct ctl_stats stats[RELAY_MAXPROC + 1]) +{ + struct ctl_stats crs; + int i; + + bzero(&crs, sizeof(crs)); + crs.interval = stats[0].interval; + for (i = 0; stats[i].id != EMPTY_ID; i++) { + crs.cnt += stats[i].cnt; + crs.last += stats[i].last; + crs.avg += stats[i].avg; + crs.last_hour += stats[i].last_hour; + crs.avg_hour += stats[i].avg_hour; + crs.last_day += stats[i].last_day; + crs.avg_day += stats[i].avg_day; + } + if (crs.cnt == 0) + return; + printf("\t%8s\ttotal: %llu sessions\n" + "\t%8s\tlast: %u/%us %u/h %u/d sessions\n" + "\t%8s\taverage: %u/%us %u/h %u/d sessions\n", +#ifdef __FreeBSD__ + "", (long long unsigned)crs.cnt, +#else + "", crs.cnt, +#endif + "", crs.last, crs.interval, + crs.last_hour, crs.last_day, + "", crs.avg, crs.interval, + crs.avg_hour, crs.avg_day); +} Index: contrib/relayctl/parser.h =================================================================== --- contrib/relayctl/parser.h (revision 0) +++ contrib/relayctl/parser.h (revision 0) @@ -0,0 +1,47 @@ +/* $OpenBSD: parser.h,v 1.9 2007/12/20 20:15:43 reyk Exp $ */ + +/* + * Copyright (c) 2006 Pierre-Yves Ritschard + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +enum actions { + NONE, + SHOW_SUM, + SHOW_HOSTS, + SHOW_RDRS, + SHOW_RELAYS, + SHOW_SESSIONS, + RDR_DISABLE, + RDR_ENABLE, + TABLE_DISABLE, + TABLE_ENABLE, + HOST_DISABLE, + HOST_ENABLE, + SHUTDOWN, + POLL, + RELOAD, + MONITOR +}; + +struct parse_result { + struct ctl_id id; + enum actions action; +}; + +struct parse_result *parse(int, char *[]); +#ifndef __FreeBSD__ +const struct token *match_token(const char *, const struct token []); +void show_valid_args(const struct token []); +#endif Index: contrib/relayctl/relayctl.8 =================================================================== --- contrib/relayctl/relayctl.8 (revision 0) +++ contrib/relayctl/relayctl.8 (revision 0) @@ -0,0 +1,205 @@ +.\" $OpenBSD: relayctl.8,v 1.23 2008/12/31 15:22:27 sobrado Exp $ +.\" +.\" Copyright (c) 2006 Pierre-Yves Ritschard +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: December 31 2008 $ +.Dt RELAYCTL 8 +.Os +.Sh NAME +.Nm relayctl +.Nd control the relay daemon +.Sh SYNOPSIS +.Nm +.Ar command +.Op Ar argument ... +.Sh DESCRIPTION +The +.Nm +program controls the +.Xr relayd 8 +daemon. +.Pp +The following commands are available: +.Bl -tag -width Ds +.It Cm host disable Op Ar name | id +Disable a host. +Treat it as though it were always down. +.It Cm host enable Op Ar name | id +Enable the host. +Start checking its health again. +.It Cm monitor +Continuously report any changes in the host checking engine and the +.Xr pf 4 +engine. +.It Cm poll +Schedule an immediate check of all hosts. +.It Cm redirect disable Op Ar name | id +Disable a redirection. +If it has +.Xr pf 4 +redirection rules installed, remove them. +Mark the redirection's main table and \(en +if applicable \(en disable the backup table as well. +.It Cm redirect enable Op Ar name | id +Enable a redirection. +Mark the redirection's main table and \(en if applicable \(en enable +the backup table as well. +.It Cm reload +Reload the configuration file. +.It Cm show hosts +Show detailed status of hosts and tables. +It will also print the last error for failed host checks; +see the +.Sx ERRORS +section below. +.It Cm show redirects +Show detailed status of redirections including the current and average +access statistics. +The statistics will be updated every minute. +Redirections using the +.Ic sticky-address +option will count the number of sticky states, +not the total number of redirected connections. +.It Cm show relays +Show detailed status of relays including the current and average +access statistics. +The statistics will be updated every minute. +.It Cm show sessions +Dump the complete list of running relay sessions. +.It Cm show summary +Display a list of all relays, redirections, tables, and hosts. +.It Cm table disable Op Ar name | id +Disable a table. +Consider all hosts disabled. +If it is a main table of a redirection which has a non-empty backup table, +swap the contents of the +.Xr pf 4 +table with those of the backup table. +.It Cm table enable Op Ar name | id +Enable a table. +Start doing checks for all hosts that aren't individually disabled +again. +.El +.Sh FILES +.Bl -tag -width "/var/run/relayd.sockXX" -compact +.It /var/run/relayd.sock +Unix-domain socket used for communication with +.Xr relayd 8 . +.El +.Sh ERRORS +If a host is down and a previous check failed, +.Nm +will display the last error in the output of the +.Cm show hosts +command. +This is especially useful for debugging server or configuration failures. +The following errors will be reported: +.Pp +.Bl -tag -width Ds -compact +.It Em none +No specific error was reported by the check engine. +.Pp +.It Em aborted +All checks were aborted by an external event, like a configuration reload. +.Pp +.It Em interval timeout +The check did not finish in the configured time of an interval. +This can happen if there are too many hosts that have to be checked by +.Xr relayd 8 +and can be avoided by increasing the global +.Ic interval +option in +.Xr relayd.conf 5 . +.Pp +.It Em icmp read timeout +.It Em ssl read timeout +.It Em tcp read timeout +The check failed because the remote host did not send a reply within +the configured timeout. +.Pp +.It Em icmp write timeout +.It Em ssl write timeout +.It Em tcp write timeout +.It Em ssl connect timeout +.It Em tcp connect timeout +The check failed because +.Xr relayd 8 +was not ready to send the request within the configured timeout. +.Pp +.It Em ssl connect error +.It Em ssl read error +.It Em ssl write error +.It Em tcp connect error +.It Em tcp read failed +.It Em tcp write failed +An I/O error occurred. +This indicates that +.Xr relayd 8 +was running low on resources, +file descriptors, or was too busy to run the request. +It can also indicate that an SSL/TCP protocol error occurred or that the +connection was unexpectedly aborted. +.Pp +.It Em ssl connect failed +.It Em tcp connect failed +The check failed because the protocol handshake did not succeed in +opening a stateful connection with the remote host. +.Pp +.It Em script failed +The external script executed by the check did not return a valid return code. +.Pp +.It Em send/expect failed +The payload data returned by the remote host did not match the +expected pattern. +.Pp +.It Em http code malformed +.It Em http digest malformed +The remote host did not return a valid HTTP header or body. +.Pp +.It Em http code mismatch +The remote host did not return a matching HTTP error code. +This may indicate a real server problem (a server error, the page was +not found, permission was denied) or a configuration error. +For example, it is a very common mistake that +.Xr relayd 8 +was configured to expect a +HTTP 200 OK +status but the host is returning a +HTTP 302 Found +redirection. +See +.Xr relayd.conf 5 +for more information on validating the HTTP return code. +.Pp +.It Em http digest mismatch +The remote host did not return the expected content and the computed +digest was different to the configured value. +See +.Xr relayd.conf 5 +for more information on validating the digest. +.El +.Sh SEE ALSO +.Xr relayd 8 +.Sh HISTORY +The +.Nm +program, formerly known as +.Ic hoststatectl , +first appeared in +.Ox 4.1 . +It was renamed to +.Nm +in +.Ox 4.3 . Index: contrib/relayctl/parser.c =================================================================== --- contrib/relayctl/parser.c (revision 0) +++ contrib/relayctl/parser.c (revision 0) @@ -0,0 +1,271 @@ +/* $OpenBSD: parser.c,v 1.20 2007/12/20 20:15:43 reyk Exp $ */ + +/* + * Copyright (c) 2006 Pierre-Yves Ritschard + * Copyright (c) 2004 Esben Norby + * Copyright (c) 2003, 2004 Henning Brauer + * + * 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. + */ + +#ifdef __FreeBSD__ +#include +#else +#include +#endif +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "relayd.h" + +#include "parser.h" + +enum token_type { + NOTOKEN, + ENDTOKEN, + HOSTID, + TABLEID, + RDRID, + KEYWORD +}; + +struct token { + enum token_type type; + const char *keyword; + int value; + const struct token *next; +}; + +static const struct token t_main[]; +static const struct token t_show[]; +static const struct token t_rdr[]; +static const struct token t_table[]; +static const struct token t_host[]; +static const struct token t_rdr_id[]; +static const struct token t_table_id[]; +static const struct token t_host_id[]; + +static const struct token t_main[] = { + {KEYWORD, "monitor", MONITOR, NULL}, + {KEYWORD, "show", NONE, t_show}, + {KEYWORD, "poll", POLL, NULL}, + {KEYWORD, "reload", RELOAD, NULL}, + {KEYWORD, "stop", SHUTDOWN, NULL}, + {KEYWORD, "redirect", NONE, t_rdr}, + {KEYWORD, "table", NONE, t_table}, + {KEYWORD, "host", NONE, t_host}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_show[] = { + {KEYWORD, "summary", SHOW_SUM, NULL}, + {KEYWORD, "hosts", SHOW_HOSTS, NULL}, + {KEYWORD, "redirects", SHOW_RDRS, NULL}, + {KEYWORD, "relays", SHOW_RELAYS, NULL}, + {KEYWORD, "sessions", SHOW_SESSIONS, NULL}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_rdr[] = { + {NOTOKEN, "", NONE, NULL}, + {KEYWORD, "disable", RDR_DISABLE, t_rdr_id}, + {KEYWORD, "enable", RDR_ENABLE, t_rdr_id}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_table[] = { + {NOTOKEN, "", NONE, NULL}, + {KEYWORD, "disable", TABLE_DISABLE, t_table_id}, + {KEYWORD, "enable", TABLE_ENABLE, t_table_id}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_host[] = { + {NOTOKEN, "", NONE, NULL}, + {KEYWORD, "disable", HOST_DISABLE, t_host_id}, + {KEYWORD, "enable", HOST_ENABLE, t_host_id}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_rdr_id[] = { + {RDRID, "", NONE, NULL}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_table_id[] = { + {TABLEID, "", NONE, NULL}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_host_id[] = { + {HOSTID, "", NONE, NULL}, + {ENDTOKEN, "", NONE, NULL} +}; + +static struct parse_result res; + +#ifdef __FreeBSD__ +const struct token *match_token(const char *, const struct token []); +void show_valid_args(const struct token []); +#endif + +struct parse_result * +parse(int argc, char *argv[]) +{ + const struct token *table = t_main; + const struct token *match; + + bzero(&res, sizeof(res)); + + while (argc >= 0) { + if ((match = match_token(argv[0], table)) == NULL) { + fprintf(stderr, "valid commands/args:\n"); + show_valid_args(table); + return (NULL); + } + + argc--; + argv++; + + if (match->type == NOTOKEN || match->next == NULL) + break; + + table = match->next; + } + + if (argc > 0) { + fprintf(stderr, "superfluous argument: %s\n", argv[0]); + return (NULL); + } + + return (&res); +} + +const struct token * +match_token(const char *word, const struct token *table) +{ + u_int i, match; + const struct token *t = NULL; + const char *errstr; + + match = 0; + + for (i = 0; table[i].type != ENDTOKEN; i++) { + switch (table[i].type) { + case NOTOKEN: + if (word == NULL || strlen(word) == 0) { + match++; + t = &table[i]; + } + break; + case KEYWORD: + if (word != NULL && strncmp(word, table[i].keyword, + strlen(word)) == 0) { + match++; + t = &table[i]; + if (t->value) + res.action = t->value; + } + break; + case HOSTID: + if (word == NULL) + break; + res.id.id = strtonum(word, 0, UINT_MAX, &errstr); + if (errstr) { + strlcpy(res.id.name, word, sizeof(res.id.name)); + res.id.id = EMPTY_ID; + } + t = &table[i]; + match++; + break; + case TABLEID: + if (word == NULL) + break; + res.id.id = strtonum(word, 0, UINT_MAX, &errstr); + if (errstr) { + strlcpy(res.id.name, word, sizeof(res.id.name)); + res.id.id = EMPTY_ID; + } + t = &table[i]; + match++; + break; + case RDRID: + if (word == NULL) + break; + res.id.id = strtonum(word, 0, UINT_MAX, &errstr); + if (errstr) { + strlcpy(res.id.name, word, sizeof(res.id.name)); + res.id.id = EMPTY_ID; + } + t = &table[i]; + match++; + break; + case ENDTOKEN: + break; + } + } + + if (match != 1) { + if (word == NULL) + fprintf(stderr, "missing argument:\n"); + else if (match > 1) + fprintf(stderr, "ambiguous argument: %s\n", word); + else if (match < 1) + fprintf(stderr, "unknown argument: %s\n", word); + return (NULL); + } + + return (t); +} + +void +show_valid_args(const struct token *table) +{ + int i; + + for (i = 0; table[i].type != ENDTOKEN; i++) { + switch (table[i].type) { + case NOTOKEN: + fprintf(stderr, " \n"); + break; + case KEYWORD: + fprintf(stderr, " %s\n", table[i].keyword); + break; + case RDRID: + fprintf(stderr, " \n"); + break; + case TABLEID: + fprintf(stderr, " \n"); + break; + case HOSTID: + fprintf(stderr, " \n"); + break; + case ENDTOKEN: + break; + } + } +} Index: contrib/relayd/imsg.c =================================================================== --- contrib/relayd/imsg.c (revision 0) +++ contrib/relayd/imsg.c (revision 0) @@ -0,0 +1,271 @@ +/* $OpenBSD: imsg.c,v 1.29 2009/08/08 18:33:40 nicm Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * 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 +#include +#include +#include + +#include +#include +#include +#include + +#include "imsg.h" + +int imsg_get_fd(struct imsgbuf *); + +void +imsg_init(struct imsgbuf *ibuf, int fd) +{ + msgbuf_init(&ibuf->w); + bzero(&ibuf->r, sizeof(ibuf->r)); + ibuf->fd = fd; + ibuf->w.fd = fd; + ibuf->pid = getpid(); + TAILQ_INIT(&ibuf->fds); +} + +ssize_t +imsg_read(struct imsgbuf *ibuf) +{ + struct msghdr msg; + struct cmsghdr *cmsg; + union { + struct cmsghdr hdr; + char buf[CMSG_SPACE(sizeof(int) * 16)]; + } cmsgbuf; + struct iovec iov; + ssize_t n; + int fd; + struct imsg_fd *ifd; + + bzero(&msg, sizeof(msg)); + + iov.iov_base = ibuf->r.buf + ibuf->r.wpos; + iov.iov_len = sizeof(ibuf->r.buf) - ibuf->r.wpos; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = &cmsgbuf.buf; + msg.msg_controllen = sizeof(cmsgbuf.buf); + + if ((n = recvmsg(ibuf->fd, &msg, 0)) == -1) { + if (errno != EINTR && errno != EAGAIN) { + return (-1); + } + return (-2); + } + + ibuf->r.wpos += n; + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) { + fd = (*(int *)CMSG_DATA(cmsg)); + if ((ifd = calloc(1, sizeof(struct imsg_fd))) == NULL) { + /* XXX: this return can leak */ + return (-1); + } + ifd->fd = fd; + TAILQ_INSERT_TAIL(&ibuf->fds, ifd, entry); + } + /* we do not handle other ctl data level */ + } + + 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) { + errno = ERANGE; + 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) + return (-1); + + if (imsg->hdr.flags & IMSGF_HASFD) + imsg->fd = imsg_get_fd(ibuf); + else + imsg->fd = -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, u_int32_t type, u_int32_t peerid, + pid_t pid, int fd, void *data, u_int16_t datalen) +{ + struct buf *wbuf; + + if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL) + return (-1); + + if (imsg_add(wbuf, data, datalen) == -1) + return (-1); + + wbuf->fd = fd; + + imsg_close(ibuf, wbuf); + + return (1); +} + +int +imsg_composev(struct imsgbuf *ibuf, u_int32_t type, u_int32_t peerid, + pid_t pid, int fd, const struct iovec *iov, int iovcnt) +{ + struct buf *wbuf; + int i, datalen = 0; + + for (i = 0; i < iovcnt; i++) + datalen += iov[i].iov_len; + + if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL) + return (-1); + + for (i = 0; i < iovcnt; i++) + if (imsg_add(wbuf, iov[i].iov_base, iov[i].iov_len) == -1) + return (-1); + + wbuf->fd = fd; + + imsg_close(ibuf, wbuf); + + return (1); +} + +/* ARGSUSED */ +struct buf * +imsg_create(struct imsgbuf *ibuf, u_int32_t type, u_int32_t peerid, + pid_t pid, u_int16_t datalen) +{ + struct buf *wbuf; + struct imsg_hdr hdr; + + datalen += IMSG_HEADER_SIZE; + if (datalen > MAX_IMSGSIZE) { + errno = ERANGE; + return (NULL); + } + + hdr.type = type; + hdr.flags = 0; + hdr.peerid = peerid; + if ((hdr.pid = pid) == 0) + hdr.pid = ibuf->pid; + if ((wbuf = buf_dynamic(datalen, MAX_IMSGSIZE)) == NULL) { + 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) { + buf_free(msg); + return (-1); + } + return (datalen); +} + +void +imsg_close(struct imsgbuf *ibuf, struct buf *msg) +{ + struct imsg_hdr *hdr; + + hdr = (struct imsg_hdr *)msg->buf; + + hdr->flags &= ~IMSGF_HASFD; + if (msg->fd != -1) + hdr->flags |= IMSGF_HASFD; + + hdr->len = (u_int16_t)msg->wpos; + + buf_close(&ibuf->w, msg); +} + +void +imsg_free(struct imsg *imsg) +{ + free(imsg->data); +} + +int +imsg_get_fd(struct imsgbuf *ibuf) +{ + int fd; + struct imsg_fd *ifd; + + if ((ifd = TAILQ_FIRST(&ibuf->fds)) == NULL) + return (-1); + + fd = ifd->fd; + TAILQ_REMOVE(&ibuf->fds, ifd, entry); + free(ifd); + + return (fd); +} + +int +imsg_flush(struct imsgbuf *ibuf) +{ + while (ibuf->w.queued) + if (msgbuf_write(&ibuf->w) < 0) + return (-1); + return (0); +} + +void +imsg_clear(struct imsgbuf *ibuf) +{ + int fd; + + msgbuf_clear(&ibuf->w); + while ((fd = imsg_get_fd(ibuf)) != -1) + close(fd); +} Index: contrib/relayd/hce.c =================================================================== --- contrib/relayd/hce.c (revision 0) +++ contrib/relayd/hce.c (revision 0) @@ -0,0 +1,547 @@ +/* $OpenBSD: hce.c,v 1.53 2009/06/05 23:39:51 pyr Exp $ */ + +/* + * Copyright (c) 2006 Pierre-Yves Ritschard + * + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "relayd.h" + +__dead void hce_shutdown(void); +void hce_sig_handler(int sig, short, void *); +void hce_dispatch_imsg(int, short, void *); +void hce_dispatch_parent(int, short, void *); +void hce_launch_checks(int, short, void *); +void hce_setup_events(void); +void hce_disable_events(void); + +static struct relayd *env = NULL; +struct imsgev *iev_pfe; +struct imsgev *iev_main; +int running = 0; + +void +hce_sig_handler(int sig, short event, void *arg) +{ + switch (sig) { + case SIGINT: + case SIGTERM: + hce_shutdown(); + break; + default: + fatalx("hce_sig_handler: unexpected signal"); + } +} + +pid_t +hce(struct relayd *x_env, int pipe_parent2pfe[2], int pipe_parent2hce[2], + int pipe_parent2relay[RELAY_MAXPROC][2], int pipe_pfe2hce[2], + int pipe_pfe2relay[RELAY_MAXPROC][2]) +{ + pid_t pid; + struct passwd *pw; + int i; + 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; + purge_config(env, PURGE_RDRS|PURGE_RELAYS|PURGE_PROTOS); + + if ((pw = getpwnam(RELAYD_USER)) == NULL) + fatal("hce: getpwnam"); + +#ifndef DEBUG + if (chroot(pw->pw_dir) == -1) + fatal("hce: chroot"); + if (chdir("/") == -1) + fatal("hce: chdir(\"/\")"); +#else +#warning disabling privilege revocation and chroot in DEBUG mode +#endif + + setproctitle("host check engine"); + relayd_process = PROC_HCE; + + /* this is needed for icmp tests */ + icmp_init(env); + +#ifndef DEBUG + 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"); +#endif + + event_init(); + + if ((iev_pfe = calloc(1, sizeof(struct imsgev))) == NULL || + (iev_main = calloc(1, sizeof(struct imsgev))) == NULL) + fatal("hce"); + imsg_init(&iev_pfe->ibuf, pipe_pfe2hce[0]); + iev_pfe->handler = hce_dispatch_imsg; + imsg_init(&iev_main->ibuf, pipe_parent2hce[1]); + iev_main->handler = hce_dispatch_parent; + + iev_pfe->events = EV_READ; + event_set(&iev_pfe->ev, iev_pfe->ibuf.fd, iev_pfe->events, + iev_pfe->handler, iev_pfe); + event_add(&iev_pfe->ev, NULL); + + iev_main->events = EV_READ; + event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events, + iev_main->handler, iev_main); + event_add(&iev_main->ev, NULL); + + 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); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + + /* setup pipes */ + close(pipe_pfe2hce[1]); + close(pipe_parent2hce[0]); + close(pipe_parent2pfe[0]); + close(pipe_parent2pfe[1]); + for (i = 0; i < env->sc_prefork_relay; i++) { + close(pipe_parent2relay[i][0]); + close(pipe_parent2relay[i][1]); + close(pipe_pfe2relay[i][0]); + close(pipe_pfe2relay[i][1]); + } + + hce_setup_events(); + event_dispatch(); + hce_shutdown(); + + return (0); +} + +void +hce_setup_events(void) +{ + struct timeval tv; + struct table *table; + +// snmp_init(env, iev_main); + + if (!TAILQ_EMPTY(env->sc_tables)) { + evtimer_set(&env->sc_ev, hce_launch_checks, env); + bzero(&tv, sizeof(tv)); + evtimer_add(&env->sc_ev, &tv); + } + + if (env->sc_flags & F_SSL) { + ssl_init(env); + TAILQ_FOREACH(table, env->sc_tables, entry) { + if (!(table->conf.flags & F_SSL)) + continue; + table->ssl_ctx = ssl_ctx_create(env); + } + } +} + +void +hce_disable_events(void) +{ + struct table *table; + struct host *host; + + evtimer_del(&env->sc_ev); + TAILQ_FOREACH(table, env->sc_tables, entry) { + TAILQ_FOREACH(host, &table->hosts, entry) { + host->he = HCE_ABORT; + event_del(&host->cte.ev); + close(host->cte.s); + } + } + if (env->sc_has_icmp) { + event_del(&env->sc_icmp_send.ev); + event_del(&env->sc_icmp_recv.ev); + } + if (env->sc_has_icmp6) { + event_del(&env->sc_icmp6_send.ev); + event_del(&env->sc_icmp6_recv.ev); + } +} + +void +hce_launch_checks(int fd, short event, void *arg) +{ + struct host *host; + struct table *table; + struct timeval tv; + + /* + * notify pfe checks are done and schedule next check + */ + imsg_compose_event(iev_pfe, IMSG_SYNC, 0, 0, -1, NULL, 0); + TAILQ_FOREACH(table, env->sc_tables, entry) { + TAILQ_FOREACH(host, &table->hosts, entry) { + if ((host->flags & F_CHECK_DONE) == 0) + host->he = HCE_INTERVAL_TIMEOUT; + host->flags &= ~(F_CHECK_SENT|F_CHECK_DONE); + event_del(&host->cte.ev); + } + } + + if (gettimeofday(&tv, NULL) == -1) + fatal("hce_launch_checks: gettimeofday"); + + TAILQ_FOREACH(table, env->sc_tables, entry) { + if (table->conf.flags & F_DISABLE) + continue; + if (table->conf.skip_cnt) { + if (table->skipped++ > table->conf.skip_cnt) + table->skipped = 0; + if (table->skipped != 1) + continue; + } + if (table->conf.check == CHECK_NOCHECK) + fatalx("hce_launch_checks: unknown check type"); + + TAILQ_FOREACH(host, &table->hosts, entry) { + if (host->flags & F_DISABLE || host->conf.parentid) + continue; + switch (table->conf.check) { + case CHECK_ICMP: + schedule_icmp(env, host); + break; + case CHECK_SCRIPT: + check_script(host); + break; + default: + /* Any other TCP-style checks */ + host->last_up = host->up; + host->cte.host = host; + host->cte.table = table; + bcopy(&tv, &host->cte.tv_start, + sizeof(host->cte.tv_start)); + check_tcp(&host->cte); + break; + } + } + } + check_icmp(env, &tv); + + bcopy(&env->sc_interval, &tv, sizeof(tv)); + evtimer_add(&env->sc_ev, &tv); +} + +void +hce_notify_done(struct host *host, enum host_error he) +{ + struct table *table; + struct ctl_status st; + struct timeval tv_now, tv_dur; + u_long duration; + u_int logopt; + struct host *h; + int hostup; + const char *msg; + + hostup = host->up; + host->he = he; + + if (host->up == HOST_DOWN && host->retry_cnt) { + log_debug("hce_notify_done: host %s retry %d", + host->conf.name, host->retry_cnt); + host->up = host->last_up; + host->retry_cnt--; + } else + host->retry_cnt = host->conf.retry; + if (host->up != HOST_UNKNOWN) { + host->check_cnt++; + if (host->up == HOST_UP) + host->up_cnt++; + } + st.id = host->conf.id; + st.up = host->up; + st.check_cnt = host->check_cnt; + st.retry_cnt = host->retry_cnt; + st.he = he; + host->flags |= (F_CHECK_SENT|F_CHECK_DONE); + msg = host_error(he); + if (msg) + log_debug("hce_notify_done: %s (%s)", host->conf.name, msg); + + imsg_compose_event(iev_pfe, IMSG_HOST_STATUS, + 0, 0, -1, &st, sizeof(st)); + if (host->up != host->last_up) + logopt = RELAYD_OPT_LOGUPDATE; + else + logopt = RELAYD_OPT_LOGNOTIFY; + + if (gettimeofday(&tv_now, NULL) == -1) + fatal("hce_notify_done: gettimeofday"); + timersub(&tv_now, &host->cte.tv_start, &tv_dur); + if (timercmp(&host->cte.tv_start, &tv_dur, >)) + duration = (tv_dur.tv_sec * 1000) + (tv_dur.tv_usec / 1000.0); + else + duration = 0; + + if ((table = table_find(env, host->conf.tableid)) == NULL) + fatalx("hce_notify_done: invalid table id"); + + if (env->sc_opts & logopt) { + log_info("host %s, check %s%s (%lums), state %s -> %s, " + "availability %s", + host->conf.name, table_check(table->conf.check), + (table->conf.flags & F_SSL) ? " use ssl" : "", duration, + host_status(host->last_up), host_status(host->up), + print_availability(host->check_cnt, host->up_cnt)); + } + +/* + if (host->last_up != host->up) + snmp_hosttrap(table, host); +*/ + + host->last_up = host->up; + + if (SLIST_EMPTY(&host->children)) + return; + + /* Notify for all other hosts that inherit the state from this one */ + SLIST_FOREACH(h, &host->children, child) { + h->up = hostup; + hce_notify_done(h, he); + } +} + +void +hce_shutdown(void) +{ + log_info("host check engine exiting"); + _exit(0); +} + +void +hce_dispatch_imsg(int fd, short event, void *ptr) +{ + struct imsgev *iev; + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + objid_t id; + struct host *host; + struct table *table; + + iev = ptr; + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1) + fatal("hce_dispatch_imsg: imsg_read_error"); + if (n == 0) { + /* this pipe is dead, so remove the event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + return; + } + } + + if (event & EV_WRITE) { + if (msgbuf_write(&ibuf->w) == -1) + fatal("hce_dispatch_imsg: msgbuf_write"); + } + + 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; + host->check_cnt = 0; + host->up_cnt = 0; + host->he = HCE_NONE; + 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; + host->he = HCE_NONE; + break; + case IMSG_TABLE_DISABLE: + memcpy(&id, imsg.data, sizeof(id)); + if ((table = table_find(env, id)) == NULL) + fatalx("hce_dispatch_imsg: desynchronized"); + table->conf.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->conf.flags &= ~(F_DISABLE); + TAILQ_FOREACH(host, &table->hosts, entry) + host->up = HOST_UNKNOWN; + break; + case IMSG_CTL_POLL: + evtimer_del(&env->sc_ev); + TAILQ_FOREACH(table, env->sc_tables, entry) + table->skipped = 0; + hce_launch_checks(-1, EV_TIMEOUT, env); + break; + default: + log_debug("hce_dispatch_msg: unexpected imsg %d", + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + imsg_event_add(iev); +} + +void +hce_dispatch_parent(int fd, short event, void * ptr) +{ + struct imsgev *iev; + struct imsgbuf *ibuf; + struct imsg imsg; + struct ctl_script scr; + ssize_t n; + size_t len; + static struct table *table = NULL; + struct host *host, *parent; + + iev = ptr; + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1) + fatal("hce_dispatch_parent: imsg_read error"); + if (n == 0) { + /* this pipe is dead, so remove the event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + return; + } + } + + if (event & EV_WRITE) { + if (msgbuf_write(&ibuf->w) == -1) + fatal("hce_dispatch_parent: msgbuf_write"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("hce_dispatch_parent: imsg_read error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_SCRIPT: + if (imsg.hdr.len - IMSG_HEADER_SIZE != + sizeof(scr)) + fatalx("hce_dispatch_parent: " + "invalid size of script request"); + bcopy(imsg.data, &scr, sizeof(scr)); + script_done(env, &scr); + break; + case IMSG_RECONF: + log_debug("hce: reloading configuration"); + if (imsg.hdr.len != + sizeof(struct relayd) + IMSG_HEADER_SIZE) + fatalx("corrupted reload data"); + hce_disable_events(); + purge_config(env, PURGE_TABLES); + merge_config(env, (struct relayd *)imsg.data); + + env->sc_tables = calloc(1, sizeof(*env->sc_tables)); + if (env->sc_tables == NULL) + fatal(NULL); + + TAILQ_INIT(env->sc_tables); + break; + case IMSG_RECONF_TABLE: + if ((table = calloc(1, sizeof(*table))) == NULL) + fatal(NULL); + memcpy(&table->conf, imsg.data, sizeof(table->conf)); + TAILQ_INIT(&table->hosts); + TAILQ_INSERT_TAIL(env->sc_tables, table, entry); + break; + case IMSG_RECONF_SENDBUF: + len = imsg.hdr.len - IMSG_HEADER_SIZE; + table->sendbuf = calloc(1, len); + (void)strlcpy(table->sendbuf, (char *)imsg.data, len); + break; + case IMSG_RECONF_HOST: + if ((host = calloc(1, sizeof(*host))) == NULL) + fatal(NULL); + memcpy(&host->conf, imsg.data, sizeof(host->conf)); + host->tablename = table->conf.name; + TAILQ_INSERT_TAIL(&table->hosts, host, entry); + if (host->conf.parentid) { + parent = host_find(env, host->conf.parentid); + SLIST_INSERT_HEAD(&parent->children, + host, child); + } + break; + case IMSG_RECONF_END: + log_warnx("hce: configuration reloaded"); + hce_setup_events(); + break; + default: + log_debug("hce_dispatch_parent: unexpected imsg %d", + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + imsg_event_add(iev); +} Index: contrib/relayd/parse.y =================================================================== --- contrib/relayd/parse.y (revision 0) +++ contrib/relayd/parse.y (revision 0) @@ -0,0 +1,2491 @@ +/* $OpenBSD: parse.y,v 1.140 2009/08/07 11:10:23 reyk Exp $ */ + +/* + * Copyright (c) 2007, 2008 Reyk Floeter + * Copyright (c) 2006 Pierre-Yves Ritschard + * Copyright (c) 2004, 2005 Esben Norby + * Copyright (c) 2004 Ryan McBride + * Copyright (c) 2002, 2003, 2004 Henning Brauer + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "relayd.h" + +TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); +static struct file { + TAILQ_ENTRY(file) entry; + FILE *stream; + char *name; + int lineno; + int errors; +} *file, *topfile; +struct file *pushfile(const char *, int); +int popfile(void); +int check_file_secrecy(int, const char *); +int yyparse(void); +int yylex(void); +int yyerror(const char *, ...); +int kw_cmp(const void *, const void *); +int lookup(char *); +int lgetc(int); +int lungetc(int); +int findeol(void); + +TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); +struct sym { + TAILQ_ENTRY(sym) entry; + int used; + int persist; + char *nam; + char *val; +}; +int symset(const char *, const char *, int); +char *symget(const char *); + +struct relayd *conf = NULL; +static int errors = 0; +objid_t last_rdr_id = 0; +objid_t last_table_id = 0; +objid_t last_host_id = 0; +objid_t last_relay_id = 0; +objid_t last_proto_id = 0; + +static struct rdr *rdr = NULL; +static struct table *table = NULL; +static struct relay *rlay = NULL; +static struct host *hst = NULL; +struct relaylist relays; +static struct protocol *proto = NULL; +static struct protonode node; +static u_int16_t label = 0; +static in_port_t tableport = 0; +static int nodedirection; + +struct address *host_v4(const char *); +struct address *host_v6(const char *); +int host_dns(const char *, struct addresslist *, + int, struct portrange *, const char *, int); +int host(const char *, struct addresslist *, + int, struct portrange *, const char *, int); + +struct table *table_inherit(struct table *); +struct relay *relay_inherit(struct relay *, struct relay *); +int getservice(char *); + +typedef struct { + union { + int64_t number; + char *string; + struct host *host; + struct timeval tv; + struct table *table; + struct portrange port; + struct { + struct sockaddr_storage ss; + char name[MAXHOSTNAMELEN]; + } addr; + struct { + enum digest_type type; + char *digest; + } digest; + } v; + int lineno; +} YYSTYPE; + +%} + +%token ALL APPEND BACKLOG BACKUP BUFFER CA CACHE CHANGE CHECK +%token CIPHERS CODE COOKIE DEMOTE DIGEST DISABLE ERROR EXPECT +%token EXTERNAL FILENAME FILTER FORWARD FROM HASH HEADER HOST ICMP +%token INCLUDE INET INET6 INTERFACE INTERVAL IP LABEL LISTEN +%token LOADBALANCE LOG LOOKUP MARK MARKED MODE NAT NO +%token NODELAY NOTHING ON PARENT PATH PORT PREFORK PROTO +%token QUERYSTR REAL REDIRECT RELAY REMOVE REQUEST RESPONSE RETRY +%token RETURN ROUNDROBIN ROUTE SACK SCRIPT SEND SESSION SOCKET +%token SSL STICKYADDR STYLE TABLE TAG TCP TIMEOUT TO +%token TRANSPARENT TRAP UPDATES URL VIRTUAL WITH TTL +%token STRING +%token NUMBER +%type hostname interface table +%type http_type loglevel mark +%type direction dstmode flag forwardmode retry +%type optssl optsslclient sslcache +%type redirect_proto relay_proto +%type port +%type host +%type address +%type timeout +%type digest +%type tablespec + +%% + +grammar : /* empty */ + | grammar include '\n' + | grammar '\n' + | grammar varset '\n' + | grammar main '\n' + | grammar rdr '\n' + | grammar tabledef '\n' + | grammar relay '\n' + | grammar proto '\n' + | grammar error '\n' { file->errors++; } + ; + +include : INCLUDE STRING { + struct file *nfile; + + if ((nfile = pushfile($2, 0)) == NULL) { + yyerror("failed to include file %s", $2); + free($2); + YYERROR; + } + free($2); + + file = nfile; + lungetc('\n'); + } + +optssl : /*empty*/ { $$ = 0; } + | SSL { $$ = 1; } + ; + +optsslclient : /*empty*/ { $$ = 0; } + | WITH SSL { $$ = 1; } + ; + +http_type : STRING { + if (strcmp("https", $1) == 0) { + $$ = 1; + } else if (strcmp("http", $1) == 0) { + $$ = 0; + } else { + yyerror("invalid check type: %s", $1); + free($1); + YYERROR; + } + free($1); + } + ; + +hostname : /* empty */ { + $$ = strdup(""); + if ($$ == NULL) + fatal("calloc"); + } + | HOST STRING { + if (asprintf(&$$, "Host: %s\r\nConnection: close\r\n", + $2) == -1) + fatal("asprintf"); + } + ; + +relay_proto : /* empty */ { $$ = RELAY_PROTO_TCP; } + | TCP { $$ = RELAY_PROTO_TCP; } + | STRING { + if (strcmp("http", $1) == 0) { + $$ = RELAY_PROTO_HTTP; + } else if (strcmp("dns", $1) == 0) { + $$ = RELAY_PROTO_DNS; + } else { + yyerror("invalid protocol type: %s", $1); + free($1); + YYERROR; + } + free($1); + } + ; + +redirect_proto : /* empty */ { $$ = IPPROTO_TCP; } + | TCP { $$ = IPPROTO_TCP; } + | STRING { + struct protoent *p; + + if ((p = getprotobyname($1)) == NULL) { + yyerror("invalid protocol: %s", $1); + free($1); + YYERROR; + } + free($1); + + $$ = p->p_proto; + } + ; + +eflags_l : eflags comma eflags_l + | eflags + ; + +opteflags : /* nothing */ + | eflags + ; + +eflags : STYLE STRING + { + if ((proto->style = strdup($2)) == NULL) + fatal("out of memory"); + free($2); + } + ; + +port : PORT STRING { + char *a, *b; + int p[2]; + + p[0] = p[1] = 0; + + a = $2; + b = strchr($2, ':'); + if (b == NULL) + $$.op = PF_OP_EQ; + else { + *b++ = '\0'; + if ((p[1] = getservice(b)) == -1) { + free($2); + YYERROR; + } + $$.op = PF_OP_RRG; + } + if ((p[0] = getservice(a)) == -1) { + free($2); + YYERROR; + } + $$.val[0] = p[0]; + $$.val[1] = p[1]; + free($2); + } + | PORT NUMBER { + if ($2 <= 0 || $2 >= (int)USHRT_MAX) { + yyerror("invalid port: %d", $2); + YYERROR; + } + $$.val[0] = htons($2); + $$.op = PF_OP_EQ; + } + ; + +varset : STRING '=' STRING { + if (symset($1, $3, 0) == -1) + fatal("cannot store variable"); + free($1); + free($3); + } + ; + +sendbuf : NOTHING { + table->sendbuf = NULL; + table->sendbuf_len = 0; + } + | STRING { + table->sendbuf = strdup($1); + if (table->sendbuf == NULL) + fatal("out of memory"); + table->sendbuf_len = strlen(table->sendbuf); + free($1); + } + ; + +main : INTERVAL NUMBER { + if ((conf->sc_interval.tv_sec = $2) < 0) { + yyerror("invalid interval: %d", $2); + YYERROR; + } + } + | LOG loglevel { conf->sc_opts |= $2; } + | TIMEOUT timeout { + bcopy(&$2, &conf->sc_timeout, sizeof(struct timeval)); + } + | PREFORK NUMBER { + if ($2 <= 0 || $2 > RELAY_MAXPROC) { + yyerror("invalid number of preforked " + "relays: %d", $2); + YYERROR; + } + conf->sc_prefork_relay = $2; + } +/* FreeBSD exclude + | DEMOTE STRING { + conf->sc_flags |= F_DEMOTE; + if (strlcpy(conf->sc_demote_group, $2, + sizeof(conf->sc_demote_group)) + >= sizeof(conf->sc_demote_group)) { + yyerror("yyparse: demote group name too long"); + free($2); + YYERROR; + } + free($2); + if (carp_demote_init(conf->sc_demote_group, 1) == -1) { + yyerror("yyparse: error initializing group %s", + conf->sc_demote_group); + YYERROR; + } + } + | SEND TRAP { conf->sc_flags |= F_TRAP; } +*/ + ; + +loglevel : UPDATES { $$ = RELAYD_OPT_LOGUPDATE; } + | ALL { $$ = RELAYD_OPT_LOGALL; } + ; + +rdr : REDIRECT STRING { + struct rdr *srv; + + conf->sc_flags |= F_NEEDPF; + TAILQ_FOREACH(srv, conf->sc_rdrs, entry) + if (!strcmp(srv->conf.name, $2)) + break; + if (srv != NULL) { + yyerror("redirection %s defined twice", $2); + free($2); + YYERROR; + } + if ((srv = calloc(1, sizeof (*srv))) == NULL) + fatal("out of memory"); + + if (strlcpy(srv->conf.name, $2, + sizeof(srv->conf.name)) >= + sizeof(srv->conf.name)) { + yyerror("redirection name truncated"); + free(srv); + YYERROR; + } + free($2); + srv->conf.id = ++last_rdr_id; + srv->conf.timeout.tv_sec = RELAY_TIMEOUT; + if (last_rdr_id == INT_MAX) { + yyerror("too many redirections defined"); + free(srv); + YYERROR; + } + rdr = srv; + } '{' optnl rdropts_l '}' { + if (rdr->table == NULL) { + yyerror("redirection %s has no table", + rdr->conf.name); + YYERROR; + } + if (TAILQ_EMPTY(&rdr->virts)) { + yyerror("redirection %s has no virtual ip", + rdr->conf.name); + YYERROR; + } + conf->sc_rdrcount++; + if (rdr->backup == NULL) { + rdr->conf.backup_id = + conf->sc_empty_table.conf.id; + rdr->backup = &conf->sc_empty_table; + } else if (rdr->backup->conf.port != + rdr->table->conf.port) { + yyerror("redirection %s uses two different " + "ports for its table and backup table", + rdr->conf.name); + YYERROR; + } + if (!(rdr->conf.flags & F_DISABLE)) + rdr->conf.flags |= F_ADD; + TAILQ_INSERT_TAIL(conf->sc_rdrs, rdr, entry); + tableport = 0; + rdr = NULL; + } + ; + +rdropts_l : rdropts_l rdroptsl nl + | rdroptsl optnl + ; + +rdroptsl : forwardmode TO tablespec interface { + switch ($1) { + case FWD_NORMAL: + if ($4 == NULL) + break; + yyerror("superfluous interface"); + YYERROR; + case FWD_ROUTE: + if ($4 != NULL) + break; + yyerror("missing interface to route to"); + YYERROR; + case FWD_TRANS: + yyerror("no transparent forward here"); + YYERROR; + } + if ($4 != NULL) { + strlcpy($3->conf.ifname, $4, + sizeof($3->conf.ifname)); + free($4); + } + + if ($3->conf.check == CHECK_NOCHECK) { + yyerror("table %s has no check", $3->conf.name); + purge_table(conf->sc_tables, $3); + YYERROR; + } + if (rdr->backup) { + yyerror("only one backup table is allowed"); + purge_table(conf->sc_tables, $3); + YYERROR; + } + if (rdr->table) { + rdr->backup = $3; + rdr->conf.backup_id = $3->conf.id; + } else { + rdr->table = $3; + rdr->conf.table_id = $3->conf.id; + } + $3->conf.fwdmode = $1; + $3->conf.rdrid = rdr->conf.id; + $3->conf.flags |= F_USED; + } + | LISTEN ON STRING redirect_proto port interface { + if (host($3, &rdr->virts, + SRV_MAX_VIRTS, &$5, $6, $4) <= 0) { + yyerror("invalid virtual ip: %s", $3); + free($3); + free($6); + YYERROR; + } + free($3); + free($6); + if (rdr->conf.port == 0) + rdr->conf.port = $5.val[0]; + tableport = rdr->conf.port; + } + | DISABLE { rdr->conf.flags |= F_DISABLE; } + | STICKYADDR { rdr->conf.flags |= F_STICKY; } + | TAG STRING { + conf->sc_flags |= F_NEEDPF; + if (strlcpy(rdr->conf.tag, $2, + sizeof(rdr->conf.tag)) >= + sizeof(rdr->conf.tag)) { + yyerror("redirection tag name truncated"); + free($2); + YYERROR; + } + free($2); + } + | SESSION TIMEOUT NUMBER { + if ((rdr->conf.timeout.tv_sec = $3) < 0) { + yyerror("invalid timeout: %d", $3); + YYERROR; + } + } + | include + ; + +forwardmode : FORWARD { $$ = FWD_NORMAL; } + | ROUTE { $$ = FWD_ROUTE; } + | TRANSPARENT FORWARD { $$ = FWD_TRANS; } + ; + +table : '<' STRING '>' { + if (strlen($2) >= TABLE_NAME_SIZE) { + yyerror("invalid table name"); + free($2); + YYERROR; + } + $$ = $2; + } + ; + +tabledef : TABLE table { + struct table *tb; + + TAILQ_FOREACH(tb, conf->sc_tables, entry) + if (!strcmp(tb->conf.name, $2)) + break; + if (tb != NULL) { + yyerror("table %s defined twice", $2); + free($2); + YYERROR; + } + + if ((tb = calloc(1, sizeof (*tb))) == NULL) + fatal("out of memory"); + + (void)strlcpy(tb->conf.name, $2, sizeof(tb->conf.name)); + free($2); + + tb->conf.id = 0; /* will be set later */ + bcopy(&conf->sc_timeout, &tb->conf.timeout, + sizeof(struct timeval)); + TAILQ_INIT(&tb->hosts); + table = tb; + } tabledefopts_l { + if (TAILQ_EMPTY(&table->hosts)) { + yyerror("table %s has no hosts", + table->conf.name); + YYERROR; + } + conf->sc_tablecount++; + TAILQ_INSERT_TAIL(conf->sc_tables, table, entry); + } + ; + +tabledefopts_l : tabledefopts_l tabledefopts + | tabledefopts + ; + +tabledefopts : DISABLE { table->conf.flags |= F_DISABLE; } + | '{' optnl tablelist_l '}' + ; + +tablelist_l : tablelist comma tablelist_l + | tablelist optnl + ; + +tablelist : host { + $1->conf.tableid = table->conf.id; + $1->tablename = table->conf.name; + TAILQ_INSERT_TAIL(&table->hosts, $1, entry); + } + | include + ; + +tablespec : table { + struct table *tb; + if ((tb = calloc(1, sizeof (*tb))) == NULL) + fatal("out of memory"); + (void)strlcpy(tb->conf.name, $1, sizeof(tb->conf.name)); + free($1); + table = tb; + } tableopts_l { + struct table *tb; + if (table->conf.port == 0) + table->conf.port = tableport; + else + table->conf.flags |= F_PORT; + if ((tb = table_inherit(table)) == NULL) + YYERROR; + $$ = tb; + } + ; + +tableopts_l : tableopts tableopts_l + | tableopts + ; + +tableopts : CHECK tablecheck + | port { + if ($1.op != PF_OP_EQ) { + yyerror("invalid port"); + YYERROR; + } + table->conf.port = $1.val[0]; + } + | TIMEOUT timeout { + bcopy(&$2, &table->conf.timeout, + sizeof(struct timeval)); + } +/* FreeBSD exclude + | DEMOTE STRING { + table->conf.flags |= F_DEMOTE; + if (strlcpy(table->conf.demote_group, $2, + sizeof(table->conf.demote_group)) + >= sizeof(table->conf.demote_group)) { + yyerror("yyparse: demote group name too long"); + free($2); + YYERROR; + } + free($2); + if (carp_demote_init(table->conf.demote_group, 1) + == -1) { + yyerror("yyparse: error initializing group " + "'%s'", table->conf.demote_group); + YYERROR; + } + } +*/ + | INTERVAL NUMBER { + if ($2 < conf->sc_interval.tv_sec || + $2 % conf->sc_interval.tv_sec) { + yyerror("table interval must be " + "divisible by global interval"); + YYERROR; + } + table->conf.skip_cnt = ($2 / conf->sc_interval.tv_sec) - 1; + } + | MODE dstmode { + switch ($2) { + case RELAY_DSTMODE_LOADBALANCE: + case RELAY_DSTMODE_HASH: + if (rdr != NULL) { + yyerror("mode not supported " + "for redirections"); + YYERROR; + } + /* FALLTHROUGH */ + case RELAY_DSTMODE_ROUNDROBIN: + if (rlay != NULL) + rlay->rl_conf.dstmode = $2; + break; + } + } + ; + +tablecheck : ICMP { table->conf.check = CHECK_ICMP; } + | TCP { table->conf.check = CHECK_TCP; } + | SSL { + table->conf.check = CHECK_TCP; + conf->sc_flags |= F_SSL; + table->conf.flags |= F_SSL; + } + | http_type STRING hostname CODE NUMBER { + if ($1) { + conf->sc_flags |= F_SSL; + table->conf.flags |= F_SSL; + } + table->conf.check = CHECK_HTTP_CODE; + if ((table->conf.retcode = $5) <= 0) { + yyerror("invalid HTTP code: %d", $5); + free($2); + free($3); + YYERROR; + } + if (asprintf(&table->sendbuf, + "HEAD %s HTTP/1.%c\r\n%s\r\n", + $2, strlen($3) ? '1' : '0', $3) == -1) + fatal("asprintf"); + free($2); + free($3); + if (table->sendbuf == NULL) + fatal("out of memory"); + table->sendbuf_len = strlen(table->sendbuf); + } + | http_type STRING hostname digest { + if ($1) { + conf->sc_flags |= F_SSL; + table->conf.flags |= F_SSL; + } + table->conf.check = CHECK_HTTP_DIGEST; + if (asprintf(&table->sendbuf, + "GET %s HTTP/1.%c\r\n%s\r\n", + $2, strlen($3) ? '1' : '0', $3) == -1) + fatal("asprintf"); + free($2); + free($3); + if (table->sendbuf == NULL) + fatal("out of memory"); + table->sendbuf_len = strlen(table->sendbuf); + (void)strlcpy(table->conf.digest, $4.digest, + sizeof(table->conf.digest)); + table->conf.digest_type = $4.type; + free($4.digest); + } + | SEND sendbuf EXPECT STRING optssl { + table->conf.check = CHECK_SEND_EXPECT; + if ($5) { + conf->sc_flags |= F_SSL; + table->conf.flags |= F_SSL; + } + if (strlcpy(table->conf.exbuf, $4, + sizeof(table->conf.exbuf)) + >= sizeof(table->conf.exbuf)) { + yyerror("yyparse: expect buffer truncated"); + free($4); + YYERROR; + } + translate_string(table->conf.exbuf); + free($4); + } + | SCRIPT STRING { + table->conf.check = CHECK_SCRIPT; + if (strlcpy(table->conf.path, $2, + sizeof(table->conf.path)) >= + sizeof(table->conf.path)) { + yyerror("script path truncated"); + free($2); + YYERROR; + } + free($2); + } + ; + +digest : DIGEST STRING + { + switch (strlen($2)) { + case 40: + $$.type = DIGEST_SHA1; + break; + case 32: + $$.type = DIGEST_MD5; + break; + default: + yyerror("invalid http digest"); + free($2); + YYERROR; + } + $$.digest = $2; + } + ; + +proto : relay_proto PROTO STRING { + struct protocol *p; + + if (strcmp($3, "default") == 0) { + p = &conf->sc_proto_default; + } else { + TAILQ_FOREACH(p, conf->sc_protos, entry) + if (!strcmp(p->name, $3)) + break; + } + if (p != NULL) { + yyerror("protocol %s defined twice", $3); + free($3); + YYERROR; + } + if ((p = calloc(1, sizeof (*p))) == NULL) + fatal("out of memory"); + + if (strlcpy(p->name, $3, sizeof(p->name)) >= + sizeof(p->name)) { + yyerror("protocol name truncated"); + free(p); + YYERROR; + } + free($3); + p->id = ++last_proto_id; + p->type = $1; + p->cache = RELAY_CACHESIZE; + p->tcpflags = TCPFLAG_DEFAULT; + p->sslflags = SSLFLAG_DEFAULT; + p->tcpbacklog = RELAY_BACKLOG; + (void)strlcpy(p->sslciphers, SSLCIPHERS_DEFAULT, + sizeof(p->sslciphers)); + if (last_proto_id == INT_MAX) { + yyerror("too many protocols defined"); + free(p); + YYERROR; + } + RB_INIT(&p->request_tree); + RB_INIT(&p->response_tree); + proto = p; + } protopts_n { + conf->sc_protocount++; + + if ((proto->sslflags & SSLFLAG_VERSION) == 0) { + yyerror("invalid SSL protocol"); + YYERROR; + } + + TAILQ_INSERT_TAIL(conf->sc_protos, proto, entry); + } + ; + +protopts_n : /* empty */ + | '{' '}' + | '{' optnl protopts_l '}' + ; + +protopts_l : protopts_l protoptsl nl + | protoptsl optnl + ; + +protoptsl : SSL sslflags + | SSL '{' sslflags_l '}' + | TCP tcpflags + | TCP '{' tcpflags_l '}' + | RETURN ERROR opteflags { proto->flags |= F_RETURN; } + | RETURN ERROR '{' eflags_l '}' { proto->flags |= F_RETURN; } + | LABEL STRING { + label = pn_name2id($2); + free($2); + if (label == 0) { + yyerror("invalid protocol action label"); + YYERROR; + } + } + | NO LABEL { + label = 0; + } + | direction { + node.label = label; + nodedirection = $1; + } protonode { + if (nodedirection != -1 && + protonode_add(nodedirection, proto, &node) == -1) { + yyerror("failed to add protocol node"); + YYERROR; + } + bzero(&node, sizeof(node)); + } + | include + ; + +direction : /* empty */ { $$ = RELAY_DIR_REQUEST; } + | REQUEST { $$ = RELAY_DIR_REQUEST; } + | RESPONSE { $$ = RELAY_DIR_RESPONSE; } + ; + +tcpflags_l : tcpflags comma tcpflags_l + | tcpflags + ; + +tcpflags : SACK { proto->tcpflags |= TCPFLAG_SACK; } + | NO SACK { proto->tcpflags |= TCPFLAG_NSACK; } + | NODELAY { proto->tcpflags |= TCPFLAG_NODELAY; } + | NO NODELAY { proto->tcpflags |= TCPFLAG_NNODELAY; } + | BACKLOG NUMBER { + if ($2 < 0 || $2 > RELAY_MAX_SESSIONS) { + yyerror("invalid backlog: %d", $2); + YYERROR; + } + proto->tcpbacklog = $2; + } + | SOCKET BUFFER NUMBER { + proto->tcpflags |= TCPFLAG_BUFSIZ; + if ((proto->tcpbufsiz = $3) < 0) { + yyerror("invalid socket buffer size: %d", $3); + YYERROR; + } + } + | IP STRING NUMBER { + if ($3 < 0) { + yyerror("invalid ttl: %d", $3); + free($2); + YYERROR; + } + if (strcasecmp("ttl", $2) == 0) { + proto->tcpflags |= TCPFLAG_IPTTL; + proto->tcpipttl = $3; + } else if (strcasecmp("minttl", $2) == 0) { + proto->tcpflags |= TCPFLAG_IPMINTTL; + proto->tcpipminttl = $3; + } else { + yyerror("invalid TCP/IP flag: %s", $2); + free($2); + YYERROR; + } + free($2); + } + ; + +sslflags_l : sslflags comma sslflags_l + | sslflags + ; + +sslflags : SESSION CACHE sslcache { proto->cache = $3; } + | CIPHERS STRING { + if (strlcpy(proto->sslciphers, $2, + sizeof(proto->sslciphers)) >= + sizeof(proto->sslciphers)) { + yyerror("sslciphers truncated"); + free($2); + YYERROR; + } + free($2); + } + | CA FILENAME STRING { + if (proto->sslca != NULL) { + yyerror("sslca already specified"); + free($3); + YYERROR; + } + proto->sslca = $3; + } + | NO flag { proto->sslflags &= ~($2); } + | flag { proto->sslflags |= $1; } + ; + +flag : STRING { + if (strcmp("sslv2", $1) == 0) + $$ = SSLFLAG_SSLV2; + else if (strcmp("sslv3", $1) == 0) + $$ = SSLFLAG_SSLV3; + else if (strcmp("tlsv1", $1) == 0) + $$ = SSLFLAG_TLSV1; + else { + yyerror("invalid SSL flag: %s", $1); + free($1); + YYERROR; + } + free($1); + } + ; + +protonode : nodetype APPEND STRING TO STRING nodeopts { + node.action = NODE_ACTION_APPEND; + node.key = strdup($5); + node.value = strdup($3); + if (node.key == NULL || node.value == NULL) + fatal("out of memory"); + if (strchr(node.value, '$') != NULL) + node.flags |= PNFLAG_MACRO; + free($5); + free($3); + } + | nodetype CHANGE STRING TO STRING nodeopts { + node.action = NODE_ACTION_CHANGE; + node.key = strdup($3); + node.value = strdup($5); + if (node.key == NULL || node.value == NULL) + fatal("out of memory"); + if (strchr(node.value, '$') != NULL) + node.flags |= PNFLAG_MACRO; + free($5); + free($3); + } + | nodetype REMOVE STRING nodeopts { + node.action = NODE_ACTION_REMOVE; + node.key = strdup($3); + node.value = NULL; + if (node.key == NULL) + fatal("out of memory"); + free($3); + } + | nodetype REMOVE { + node.action = NODE_ACTION_REMOVE; + node.key = NULL; + node.value = NULL; + } nodefile + | nodetype EXPECT STRING FROM STRING nodeopts { + node.action = NODE_ACTION_EXPECT; + node.key = strdup($5); + node.value = strdup($3); + if (node.key == NULL || node.value == NULL) + fatal("out of memory"); + free($5); + free($3); + proto->lateconnect++; + } + | nodetype EXPECT STRING nodeopts { + node.action = NODE_ACTION_EXPECT; + node.key = strdup($3); + node.value = strdup("*"); + if (node.key == NULL || node.value == NULL) + fatal("out of memory"); + free($3); + proto->lateconnect++; + } + | nodetype EXPECT { + node.action = NODE_ACTION_EXPECT; + node.key = NULL; + node.value = "*"; + proto->lateconnect++; + } nodefile + | nodetype EXPECT digest nodeopts { + if (node.type != NODE_TYPE_URL) { + yyerror("digest not supported for this type"); + free($3.digest); + YYERROR; + } + node.action = NODE_ACTION_EXPECT; + node.key = strdup($3.digest); + node.flags |= PNFLAG_LOOKUP_DIGEST($3.type); + node.value = strdup("*"); + if (node.key == NULL || node.value == NULL) + fatal("out of memory"); + free($3.digest); + proto->lateconnect++; + } + | nodetype FILTER STRING FROM STRING nodeopts { + node.action = NODE_ACTION_FILTER; + node.key = strdup($5); + node.value = strdup($3); + if (node.key == NULL || node.value == NULL) + fatal("out of memory"); + free($5); + free($3); + proto->lateconnect++; + } + | nodetype FILTER STRING nodeopts { + node.action = NODE_ACTION_FILTER; + node.key = strdup($3); + node.value = strdup("*"); + if (node.key == NULL || node.value == NULL) + fatal("out of memory"); + free($3); + proto->lateconnect++; + } + | nodetype FILTER { + node.action = NODE_ACTION_FILTER; + node.key = NULL; + node.value = "*"; + proto->lateconnect++; + } nodefile + | nodetype FILTER digest nodeopts { + if (node.type != NODE_TYPE_URL) { + yyerror("digest not supported for this type"); + free($3.digest); + YYERROR; + } + node.action = NODE_ACTION_FILTER; + node.key = strdup($3.digest); + node.flags |= PNFLAG_LOOKUP_DIGEST($3.type); + node.value = strdup("*"); + if (node.key == NULL || node.value == NULL) + fatal("out of memory"); + free($3.digest); + proto->lateconnect++; + } + | nodetype HASH STRING nodeopts { + node.action = NODE_ACTION_HASH; + node.key = strdup($3); + node.value = NULL; + if (node.key == NULL) + fatal("out of memory"); + free($3); + proto->lateconnect++; + } + | nodetype LOG STRING nodeopts { + node.action = NODE_ACTION_LOG; + node.key = strdup($3); + node.value = NULL; + node.flags |= PNFLAG_LOG; + if (node.key == NULL) + fatal("out of memory"); + free($3); + } + | nodetype LOG { + node.action = NODE_ACTION_LOG; + node.key = NULL; + node.value = NULL; + node.flags |= PNFLAG_LOG; + } nodefile + | nodetype MARK STRING FROM STRING WITH mark log { + node.action = NODE_ACTION_MARK; + node.key = strdup($5); + node.value = strdup($3); + node.mark = $7; + if (node.key == NULL || node.value == NULL) + fatal("out of memory"); + free($3); + free($5); + } + | nodetype MARK STRING WITH mark nodeopts { + node.action = NODE_ACTION_MARK; + node.key = strdup($3); + node.value = strdup("*"); + node.mark = $5; /* overwrite */ + if (node.key == NULL || node.value == NULL) + fatal("out of memory"); + free($3); + } + ; + +nodefile : FILENAME STRING nodeopts { + if (protonode_load(nodedirection, + proto, &node, $2) == -1) { + yyerror("failed to load from file: %s", $2); + free($2); + YYERROR; + } + free($2); + nodedirection = -1; /* don't add template node */ + } + ; + +nodeopts : marked log + ; + +marked : /* empty */ + | MARKED mark { node.mark = $2; } + ; + +log : /* empty */ + | LOG { node.flags |= PNFLAG_LOG; } + ; + +mark : NUMBER { + if ($1 <= 0 || $1 >= (int)USHRT_MAX) { + yyerror("invalid mark: %d", $1); + YYERROR; + } + $$ = $1; + } + ; + +nodetype : HEADER { + node.type = NODE_TYPE_HEADER; + } + | QUERYSTR { node.type = NODE_TYPE_QUERY; } + | COOKIE { + node.type = NODE_TYPE_COOKIE; + } + | PATH { + proto->flags |= F_LOOKUP_PATH; + node.type = NODE_TYPE_PATH; + } + | URL { node.type = NODE_TYPE_URL; } + ; + +sslcache : NUMBER { + if ($1 < 0) { + yyerror("invalid sslcache value: %d", $1); + YYERROR; + } + $$ = $1; + } + | DISABLE { $$ = -2; } + ; + +relay : RELAY STRING { + struct relay *r; + + TAILQ_FOREACH(r, conf->sc_relays, rl_entry) + if (!strcmp(r->rl_conf.name, $2)) + break; + if (r != NULL) { + yyerror("relay %s defined twice", $2); + free($2); + YYERROR; + } + TAILQ_INIT(&relays); + + if ((r = calloc(1, sizeof (*r))) == NULL) + fatal("out of memory"); + + if (strlcpy(r->rl_conf.name, $2, sizeof(r->rl_conf.name)) >= + sizeof(r->rl_conf.name)) { + yyerror("relay name truncated"); + free(r); + YYERROR; + } + free($2); + r->rl_conf.id = ++last_relay_id; + r->rl_conf.timeout.tv_sec = RELAY_TIMEOUT; + r->rl_proto = NULL; + r->rl_conf.proto = EMPTY_ID; + r->rl_conf.dsttable = EMPTY_ID; + r->rl_conf.dstmode = RELAY_DSTMODE_DEFAULT; + r->rl_conf.dstretry = 0; + if (last_relay_id == INT_MAX) { + yyerror("too many relays defined"); + free(r); + YYERROR; + } + rlay = r; + } '{' optnl relayopts_l '}' { + struct relay *r; + + if (rlay->rl_conf.ss.ss_family == AF_UNSPEC) { + yyerror("relay %s has no listener", + rlay->rl_conf.name); + YYERROR; + } + if ((rlay->rl_conf.flags & F_NATLOOK) == 0 && + rlay->rl_conf.dstss.ss_family == AF_UNSPEC && + rlay->rl_conf.dsttable == EMPTY_ID) { + yyerror("relay %s has no target, rdr, " + "or table", rlay->rl_conf.name); + YYERROR; + } + if (rlay->rl_conf.proto == EMPTY_ID) { + rlay->rl_proto = &conf->sc_proto_default; + rlay->rl_conf.proto = conf->sc_proto_default.id; + } + if (relay_load_certfiles(rlay) == -1) { + yyerror("cannot load certificates for relay %s", + rlay->rl_conf.name); + YYERROR; + } + conf->sc_relaycount++; + SPLAY_INIT(&rlay->rl_sessions); + TAILQ_INSERT_TAIL(conf->sc_relays, rlay, rl_entry); + tableport = 0; + + while ((r = TAILQ_FIRST(&relays)) != NULL) { + TAILQ_REMOVE(&relays, r, rl_entry); + if (relay_inherit(rlay, r) == NULL) { + YYERROR; + } + } + rlay = NULL; + } + ; + +relayopts_l : relayopts_l relayoptsl nl + | relayoptsl optnl + ; + +relayoptsl : LISTEN ON STRING port optssl { + struct addresslist al; + struct address *h; + struct relay *r; + + if (rlay->rl_conf.ss.ss_family != AF_UNSPEC) { + if ((r = calloc(1, sizeof (*r))) == NULL) + fatal("out of memory"); + TAILQ_INSERT_TAIL(&relays, r, rl_entry); + } else + r = rlay; + if ($4.op != PF_OP_EQ) { + yyerror("invalid port"); + free($3); + YYERROR; + } + + TAILQ_INIT(&al); + if (host($3, &al, 1, &$4, NULL, -1) <= 0) { + yyerror("invalid listen ip: %s", $3); + free($3); + YYERROR; + } + free($3); + h = TAILQ_FIRST(&al); + bcopy(&h->ss, &r->rl_conf.ss, sizeof(r->rl_conf.ss)); + r->rl_conf.port = h->port.val[0]; + if ($5) { + r->rl_conf.flags |= F_SSL; + conf->sc_flags |= F_SSL; + } + tableport = h->port.val[0]; + } + | forwardmode optsslclient TO forwardspec interface dstaf { + rlay->rl_conf.fwdmode = $1; + switch ($1) { + case FWD_NORMAL: + if ($5 == NULL) + break; + yyerror("superfluous interface"); + YYERROR; + case FWD_ROUTE: + yyerror("no route for redirections"); + YYERROR; + case FWD_TRANS: + if ($5 != NULL) + break; + yyerror("missing interface"); + YYERROR; + } + if ($5 != NULL) { + strlcpy(rlay->rl_conf.ifname, $5, + sizeof(rlay->rl_conf.ifname)); + free($5); + } + if ($2) { + rlay->rl_conf.flags |= F_SSLCLIENT; + conf->sc_flags |= F_SSLCLIENT; + } + } + | SESSION TIMEOUT NUMBER { + if ((rlay->rl_conf.timeout.tv_sec = $3) < 0) { + yyerror("invalid timeout: %d", $3); + YYERROR; + } + } + | PROTO STRING { + struct protocol *p; + + TAILQ_FOREACH(p, conf->sc_protos, entry) + if (!strcmp(p->name, $2)) + break; + if (p == NULL) { + yyerror("no such protocol: %s", $2); + free($2); + YYERROR; + } + p->flags |= F_USED; + rlay->rl_conf.proto = p->id; + rlay->rl_proto = p; + free($2); + } + | DISABLE { rlay->rl_conf.flags |= F_DISABLE; } + | include + ; + +forwardspec : STRING port retry { + struct addresslist al; + struct address *h; + + if (rlay->rl_conf.dstss.ss_family != AF_UNSPEC) { + yyerror("relay %s target or redirection already " + "specified", rlay->rl_conf.name); + free($1); + YYERROR; + } + if ($2.op != PF_OP_EQ) { + yyerror("invalid port"); + free($1); + YYERROR; + } + + TAILQ_INIT(&al); + if (host($1, &al, 1, &$2, NULL, -1) <= 0) { + yyerror("invalid listen ip: %s", $1); + free($1); + YYERROR; + } + free($1); + h = TAILQ_FIRST(&al); + bcopy(&h->ss, &rlay->rl_conf.dstss, + sizeof(rlay->rl_conf.dstss)); + rlay->rl_conf.dstport = h->port.val[0]; + rlay->rl_conf.dstretry = $3; + } + | NAT LOOKUP retry { + conf->sc_flags |= F_NEEDPF; + rlay->rl_conf.flags |= F_NATLOOK; + rlay->rl_conf.dstretry = $3; + } + | tablespec { + if (rlay->rl_dsttable) { + yyerror("table already specified"); + purge_table(conf->sc_tables, $1); + YYERROR; + } + + rlay->rl_dsttable = $1; + rlay->rl_dsttable->conf.flags |= F_USED; + rlay->rl_conf.dsttable = $1->conf.id; + rlay->rl_conf.dstport = $1->conf.port; + } + ; + +dstmode : /* empty */ { $$ = RELAY_DSTMODE_DEFAULT; } + | LOADBALANCE { $$ = RELAY_DSTMODE_LOADBALANCE; } + | ROUNDROBIN { $$ = RELAY_DSTMODE_ROUNDROBIN; } + | HASH { $$ = RELAY_DSTMODE_HASH; } + ; + +dstaf : /* empty */ { + rlay->rl_conf.dstaf.ss_family = AF_UNSPEC; + } + | INET { + rlay->rl_conf.dstaf.ss_family = AF_INET; + } + | INET6 STRING { + struct sockaddr_in6 *sin6; + + sin6 = (struct sockaddr_in6 *)&rlay->rl_conf.dstaf; + if (inet_pton(AF_INET6, $2, &sin6->sin6_addr) == -1) { + yyerror("invalid ipv6 address %s", $2); + free($2); + YYERROR; + } + free($2); + + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(*sin6); + } + ; + +interface : /*empty*/ { $$ = NULL; } + | INTERFACE STRING { $$ = $2; } + ; + +host : address { + if ((hst = calloc(1, sizeof(*(hst)))) == NULL) + fatal("out of memory"); + + if (strlcpy(hst->conf.name, $1.name, + sizeof(hst->conf.name)) >= sizeof(hst->conf.name)) { + yyerror("host name truncated"); + free(hst); + YYERROR; + } + bcopy(&$1.ss, &hst->conf.ss, sizeof($1.ss)); + hst->conf.id = 0; /* will be set later */ + SLIST_INIT(&hst->children); + } opthostflags { + $$ = hst; + hst = NULL; + } + ; + +opthostflags : /* empty */ + | hostflags_l + ; + +hostflags_l : hostflags hostflags_l + | hostflags + ; + +hostflags : RETRY NUMBER { + if (hst->conf.retry) { + yyerror("retry value already set"); + YYERROR; + } + if ($2 < 0) { + yyerror("invalid retry value: %d\n", $2); + YYERROR; + } + hst->conf.retry = $2; + } + | PARENT NUMBER { + if (hst->conf.parentid) { + yyerror("parent value already set"); + YYERROR; + } + if ($2 < 0) { + yyerror("invalid parent value: %d\n", $2); + YYERROR; + } + hst->conf.parentid = $2; + } + | IP TTL NUMBER { + if (hst->conf.ttl) { + yyerror("ttl value already set"); + YYERROR; + } + if ($3 < 0) { + yyerror("invalid ttl value: %d\n", $3); + YYERROR; + } + hst->conf.ttl = $3; + } + ; + +address : STRING { + struct address *a; + struct addresslist al; + + if (strlcpy($$.name, $1, + sizeof($$.name)) >= sizeof($$.name)) { + yyerror("host name truncated"); + free($1); + YYERROR; + } + + TAILQ_INIT(&al); + if (host($1, &al, 1, NULL, NULL, -1) <= 0) { + yyerror("invalid host %s", $1); + free($1); + YYERROR; + } + free($1); + a = TAILQ_FIRST(&al); + memcpy(&$$.ss, &a->ss, sizeof($$.ss)); + free(a); + } + ; + +retry : /* empty */ { $$ = 0; } + | RETRY NUMBER { + if (($$ = $2) < 0) { + yyerror("invalid retry value: %d\n", $2); + YYERROR; + } + } + ; + +timeout : NUMBER + { + if ($1 < 0) { + yyerror("invalid timeout: %d\n", $1); + YYERROR; + } + $$.tv_sec = $1 / 1000; + $$.tv_usec = ($1 % 1000) * 1000; + } + ; + +comma : ',' + | nl + | /* empty */ + ; + +optnl : '\n' optnl + | + ; + +nl : '\n' optnl + ; + +%% + +struct keywords { + const char *k_name; + int k_val; +}; + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + + file->errors++; + va_start(ap, fmt); + fprintf(stderr, "%s:%d: ", file->name, 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[] = { + { "all", ALL }, + { "append", APPEND }, + { "backlog", BACKLOG }, + { "backup", BACKUP }, + { "buffer", BUFFER }, + { "ca", CA }, + { "cache", CACHE }, + { "change", CHANGE }, + { "check", CHECK }, + { "ciphers", CIPHERS }, + { "code", CODE }, + { "cookie", COOKIE }, +// FreeBSD { "demote", DEMOTE }, + { "digest", DIGEST }, + { "disable", DISABLE }, + { "error", ERROR }, + { "expect", EXPECT }, + { "external", EXTERNAL }, + { "file", FILENAME }, + { "filter", FILTER }, + { "forward", FORWARD }, + { "from", FROM }, + { "hash", HASH }, + { "header", HEADER }, + { "host", HOST }, + { "icmp", ICMP }, + { "include", INCLUDE }, + { "inet", INET }, + { "inet6", INET6 }, + { "interface", INTERFACE }, + { "interval", INTERVAL }, + { "ip", IP }, + { "label", LABEL }, + { "listen", LISTEN }, + { "loadbalance", LOADBALANCE }, + { "log", LOG }, + { "lookup", LOOKUP }, + { "mark", MARK }, + { "marked", MARKED }, + { "mode", MODE }, + { "nat", NAT }, + { "no", NO }, + { "nodelay", NODELAY }, + { "nothing", NOTHING }, + { "on", ON }, + { "parent", PARENT }, + { "path", PATH }, + { "port", PORT }, + { "prefork", PREFORK }, + { "protocol", PROTO }, + { "query", QUERYSTR }, + { "real", REAL }, + { "redirect", REDIRECT }, + { "relay", RELAY }, + { "remove", REMOVE }, + { "request", REQUEST }, + { "response", RESPONSE }, + { "retry", RETRY }, + { "return", RETURN }, + { "roundrobin", ROUNDROBIN }, + { "route", ROUTE }, + { "sack", SACK }, + { "script", SCRIPT }, + { "send", SEND }, + { "session", SESSION }, + { "socket", SOCKET }, + { "ssl", SSL }, + { "sticky-address", STICKYADDR }, + { "style", STYLE }, + { "table", TABLE }, + { "tag", TAG }, + { "tcp", TCP }, + { "timeout", TIMEOUT }, + { "to", TO }, + { "transparent", TRANSPARENT }, +// FreeBSD { "trap", TRAP }, + { "ttl", TTL }, + { "updates", UPDATES }, + { "url", URL }, + { "virtual", VIRTUAL }, + { "with", WITH } + }; + 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(int quotec) +{ + 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]); + + if (quotec) { + if ((c = getc(file->stream)) == EOF) { + yyerror("reached end of file while parsing " + "quoted string"); + if (file == topfile || popfile() == EOF) + return (EOF); + return (quotec); + } + return (c); + } + + while ((c = getc(file->stream)) == '\\') { + next = getc(file->stream); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = file->lineno; + file->lineno++; + } + + while (c == EOF) { + if (file == topfile || popfile() == EOF) + return (EOF); + c = getc(file->stream); + } + 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; + + /* skip to either EOF or the first real EOL */ + while (1) { + if (pushback_index) + c = pushback_buffer[--pushback_index]; + else + c = lgetc(0); + if (c == '\n') { + file->lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} + +int +yylex(void) +{ + char buf[8096]; + char *p, *val; + int quotec, next, c; + int token; + +top: + p = buf; + while ((c = lgetc(0)) == ' ' || c == '\t') + ; /* nothing */ + + yylval.lineno = file->lineno; + if (c == '#') + while ((c = lgetc(0)) != '\n' && c != EOF) + ; /* nothing */ + if (c == '$' && parsebuf == NULL) { + while (1) { + if ((c = lgetc(0)) == 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 '"': + quotec = c; + while (1) { + if ((c = lgetc(quotec)) == EOF) + return (0); + if (c == '\n') { + file->lineno++; + continue; + } else if (c == '\\') { + if ((next = lgetc(quotec)) == EOF) + return (0); + if (next == quotec || c == ' ' || c == '\t') + c = next; + else if (next == '\n') + continue; + else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } + 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) + err(1, "yylex: strdup"); + return (STRING); + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && isdigit(c)); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + const char *errstr = NULL; + + *p = '\0'; + yylval.v.number = strtonum(buf, LLONG_MIN, + LLONG_MAX, &errstr); + if (errstr) { + yyerror("\"%s\" invalid number: %s", + buf, errstr); + return (findeol()); + } + return (NUMBER); + } else { +nodigits: + while (p > buf + 1) + lungetc(*--p); + c = *--p; + if (c == '-') + return (c); + } + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && 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(0)) != 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 = file->lineno; + file->lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +int +check_file_secrecy(int fd, const char *fname) +{ + struct stat st; + + if (fstat(fd, &st)) { + log_warn("cannot stat %s", fname); + return (-1); + } + if (st.st_uid != 0 && st.st_uid != getuid()) { + log_warnx("%s: owner not root or current user", fname); + return (-1); + } + if (st.st_mode & (S_IRWXG | S_IRWXO)) { + log_warnx("%s: group/world readable/writeable", fname); + return (-1); + } + return (0); +} + +struct file * +pushfile(const char *name, int secret) +{ + struct file *nfile; + + if ((nfile = calloc(1, sizeof(struct file))) == NULL) { + log_warn("malloc"); + return (NULL); + } + if ((nfile->name = strdup(name)) == NULL) { + log_warn("malloc"); + free(nfile); + return (NULL); + } + if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { + log_warn("%s", nfile->name); + free(nfile->name); + free(nfile); + return (NULL); + } else if (secret && + check_file_secrecy(fileno(nfile->stream), nfile->name)) { + fclose(nfile->stream); + free(nfile->name); + free(nfile); + return (NULL); + } + nfile->lineno = 1; + TAILQ_INSERT_TAIL(&files, nfile, entry); + return (nfile); +} + +int +popfile(void) +{ + struct file *prev; + + if ((prev = TAILQ_PREV(file, files, entry)) != NULL) + prev->errors += file->errors; + + TAILQ_REMOVE(&files, file, entry); + fclose(file->stream); + free(file->name); + free(file); + file = prev; + return (file ? 0 : EOF); +} + +struct relayd * +parse_config(const char *filename, int opts) +{ + struct sym *sym, *next; + struct table *nexttb; + struct host *h, *ph; + + if ((conf = calloc(1, sizeof(*conf))) == NULL || + (conf->sc_tables = calloc(1, sizeof(*conf->sc_tables))) == NULL || + (conf->sc_relays = calloc(1, sizeof(*conf->sc_relays))) == NULL || + (conf->sc_protos = calloc(1, sizeof(*conf->sc_protos))) == NULL || + (conf->sc_rdrs = calloc(1, sizeof(*conf->sc_rdrs))) == NULL) { + if (conf != NULL) { + if (conf->sc_tables != NULL) + free(conf->sc_tables); + if (conf->sc_relays != NULL) + free(conf->sc_relays); + if (conf->sc_protos != NULL) + free(conf->sc_protos); + if (conf->sc_rdrs != NULL) + free(conf->sc_rdrs); + free(conf); + } + log_warn("cannot allocate memory"); + return (NULL); + } + + errors = 0; + last_host_id = last_table_id = last_rdr_id = last_proto_id = + last_relay_id = 0; + + rdr = NULL; + table = NULL; + rlay = NULL; + proto = NULL; + + TAILQ_INIT(conf->sc_rdrs); + TAILQ_INIT(conf->sc_tables); + TAILQ_INIT(conf->sc_protos); + TAILQ_INIT(conf->sc_relays); + + memset(&conf->sc_empty_table, 0, sizeof(conf->sc_empty_table)); + conf->sc_empty_table.conf.id = EMPTY_TABLE; + conf->sc_empty_table.conf.flags |= F_DISABLE; + (void)strlcpy(conf->sc_empty_table.conf.name, "empty", + sizeof(conf->sc_empty_table.conf.name)); + + bzero(&conf->sc_proto_default, sizeof(conf->sc_proto_default)); + conf->sc_proto_default.flags = F_USED; + conf->sc_proto_default.cache = RELAY_CACHESIZE; + conf->sc_proto_default.type = RELAY_PROTO_TCP; + (void)strlcpy(conf->sc_proto_default.name, "default", + sizeof(conf->sc_proto_default.name)); + RB_INIT(&conf->sc_proto_default.request_tree); + RB_INIT(&conf->sc_proto_default.response_tree); + + conf->sc_timeout.tv_sec = CHECK_TIMEOUT / 1000; + conf->sc_timeout.tv_usec = (CHECK_TIMEOUT % 1000) * 1000; + conf->sc_interval.tv_sec = CHECK_INTERVAL; + conf->sc_interval.tv_usec = 0; + conf->sc_prefork_relay = RELAY_NUMPROC; + conf->sc_statinterval.tv_sec = RELAY_STATINTERVAL; + conf->sc_opts = opts; + conf->sc_confpath = filename; + + if ((file = pushfile(filename, 0)) == NULL) { + free(conf); + return (NULL); + } + topfile = file; + setservent(1); + + yyparse(); + errors = file->errors; + popfile(); + + endservent(); + endprotoent(); + + /* Free macros and check which have not been used. */ + for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) { + next = TAILQ_NEXT(sym, entry); + if ((conf->sc_opts & RELAYD_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, entry); + free(sym); + } + } + + if (TAILQ_EMPTY(conf->sc_rdrs) && TAILQ_EMPTY(conf->sc_relays)) { + log_warnx("no redirections, nothing to do"); + errors++; + } + + if (TAILQ_EMPTY(conf->sc_relays)) + conf->sc_prefork_relay = 0; + + /* Cleanup relay list to inherit */ + while ((rlay = TAILQ_FIRST(&relays)) != NULL) { + TAILQ_REMOVE(&relays, rlay, rl_entry); + free(rlay); + } + + if (timercmp(&conf->sc_timeout, &conf->sc_interval, >=)) { + log_warnx("global timeout exceeds interval"); + errors++; + } + + /* Verify that every table is used */ + for (table = TAILQ_FIRST(conf->sc_tables); table != NULL; + table = nexttb) { + nexttb = TAILQ_NEXT(table, entry); + if (table->conf.port == 0) { + TAILQ_REMOVE(conf->sc_tables, table, entry); + while ((h = TAILQ_FIRST(&table->hosts)) != NULL) { + TAILQ_REMOVE(&table->hosts, h, entry); + free(h); + } + if (table->sendbuf != NULL) + free(table->sendbuf); + free(table); + continue; + } + + TAILQ_FOREACH(h, &table->hosts, entry) { + if (h->conf.parentid) { + ph = host_find(conf, h->conf.parentid); + + /* Validate the parent id */ + if (h->conf.id == h->conf.parentid || + ph == NULL || ph->conf.parentid) + ph = NULL; + + if (ph == NULL) { + log_warnx("host parent id %d invalid", + h->conf.parentid); + errors++; + } else + SLIST_INSERT_HEAD(&ph->children, + h, child); + } + } + + if (!(table->conf.flags & F_USED)) { + log_warnx("unused table: %s", table->conf.name); + errors++; + } + if (timercmp(&table->conf.timeout, &conf->sc_interval, >=)) { + log_warnx("table timeout exceeds interval: %s", + table->conf.name); + errors++; + } + } + + /* Verify that every non-default protocol is used */ + TAILQ_FOREACH(proto, conf->sc_protos, entry) { + if (!(proto->flags & F_USED)) { + log_warnx("unused protocol: %s", proto->name); + } + } + + if (errors) { + free(conf); + return (NULL); + } + + return (conf); +} + +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, entry)) + ; /* nothing */ + + if (sym != NULL) { + if (sym->persist == 1) + return (0); + else { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + 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, entry); + 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"); + + (void)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, entry) + 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 addrinfo hints, *res; + struct sockaddr_in6 *sa_in6; + struct address *h = NULL; + + bzero(&hints, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_DGRAM; /* dummy */ + hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo(s, "0", &hints, &res) == 0) { + if ((h = calloc(1, sizeof(*h))) == NULL) + fatal(NULL); + sa_in6 = (struct sockaddr_in6 *)&h->ss; + sa_in6->sin6_len = sizeof(struct sockaddr_in6); + sa_in6->sin6_family = AF_INET6; + memcpy(&sa_in6->sin6_addr, + &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, + sizeof(sa_in6->sin6_addr)); + sa_in6->sin6_scope_id = + ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id; + + freeaddrinfo(res); + } + + return (h); +} + +int +host_dns(const char *s, struct addresslist *al, int max, + struct portrange *port, const char *ifname, int ipproto) +{ + 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) + if (error == EAI_AGAIN || 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); + + if (port != NULL) + bcopy(port, &h->port, sizeof(h->port)); + if (ifname != NULL) { + if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >= + sizeof(h->ifname)) + log_warnx("host_dns: interface name truncated"); + freeaddrinfo(res0); + return (-1); + } + if (ipproto != -1) + h->ipproto = ipproto; + 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, + struct portrange *port, const char *ifname, int ipproto) +{ + struct address *h; + + h = host_v4(s); + + /* IPv6 address? */ + if (h == NULL) + h = host_v6(s); + + if (h != NULL) { + if (port != NULL) + bcopy(port, &h->port, sizeof(h->port)); + if (ifname != NULL) { + if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >= + sizeof(h->ifname)) { + log_warnx("host: interface name truncated"); + return (-1); + } + } + if (ipproto != -1) + h->ipproto = ipproto; + + TAILQ_INSERT_HEAD(al, h, entry); + return (1); + } + + return (host_dns(s, al, max, port, ifname, ipproto)); +} + +struct table * +table_inherit(struct table *tb) +{ + char pname[TABLE_NAME_SIZE + 6]; + struct host *h, *dsth; + struct table *dsttb, *oldtb; + + /* Get the table or table template */ + if ((dsttb = table_findbyname(conf, tb->conf.name)) == NULL) { + yyerror("unknown table %s", tb->conf.name); + purge_table(NULL, tb); + return (NULL); + } + if (dsttb->conf.port != 0) + fatal("invalid table"); /* should not happen */ + + if (tb->conf.port == 0) { + yyerror("invalid port"); + purge_table(NULL, tb); + return (NULL); + } + + /* Check if a matching table already exists */ + if (snprintf(pname, sizeof(pname), "%s:%u", + tb->conf.name, ntohs(tb->conf.port)) >= (int)sizeof(pname)) { + yyerror("invalid table name"); + return (NULL); + } + (void)strlcpy(tb->conf.name, pname, sizeof(tb->conf.name)); + if ((oldtb = table_findbyconf(conf, tb)) != NULL) + return (oldtb); + + /* Create a new table */ + tb->conf.id = ++last_table_id; + if (last_table_id == INT_MAX) { + yyerror("too many tables defined"); + purge_table(NULL, tb); + return (NULL); + } + tb->conf.flags |= dsttb->conf.flags; + + /* Inherit global table options */ + bcopy(&dsttb->conf.timeout, &tb->conf.timeout, sizeof(struct timeval)); + tb->conf.skip_cnt = dsttb->conf.skip_cnt; + strlcpy(tb->conf.demote_group, dsttb->conf.demote_group, + sizeof(tb->conf.demote_group)); + + /* Copy the associated hosts */ + TAILQ_INIT(&tb->hosts); + TAILQ_FOREACH(dsth, &dsttb->hosts, entry) { + if ((h = (struct host *) + calloc(1, sizeof (*h))) == NULL) + fatal("out of memory"); + bcopy(dsth, h, sizeof(*h)); + h->conf.id = ++last_host_id; + if (last_host_id == INT_MAX) { + yyerror("too many hosts defined"); + purge_table(NULL, tb); + return (NULL); + } + h->conf.tableid = tb->conf.id; + h->tablename = tb->conf.name; + SLIST_INIT(&h->children); + TAILQ_INSERT_TAIL(&tb->hosts, h, entry); + } + + conf->sc_tablecount++; + TAILQ_INSERT_TAIL(conf->sc_tables, tb, entry); + + return (tb); +} + +struct relay * +relay_inherit(struct relay *ra, struct relay *rb) +{ + struct relay_config rc; + + bcopy(&rb->rl_conf, &rc, sizeof(rc)); + bcopy(ra, rb, sizeof(*rb)); + + bcopy(&rc.ss, &rb->rl_conf.ss, sizeof(rb->rl_conf.ss)); + rb->rl_conf.port = rc.port; + rb->rl_conf.flags = + (ra->rl_conf.flags & ~F_SSL) | (rc.flags & F_SSL); + + rb->rl_conf.id = ++last_relay_id; + if (last_relay_id == INT_MAX) { + yyerror("too many relays defined"); + goto err; + } + + if (snprintf(rb->rl_conf.name, sizeof(rb->rl_conf.name), "%s%u:%u", + ra->rl_conf.name, rb->rl_conf.id, ntohs(rc.port)) >= + (int)sizeof(rb->rl_conf.name)) { + yyerror("invalid relay name"); + goto err; + } + + if (relay_findbyname(conf, rb->rl_conf.name) != NULL || + relay_findbyaddr(conf, &rb->rl_conf) != NULL) { + yyerror("relay %s defined twice", rb->rl_conf.name); + goto err; + } + if (relay_load_certfiles(rb) == -1) { + yyerror("cannot load certificates for relay %s", + rb->rl_conf.name); + goto err; + } + + conf->sc_relaycount++; + SPLAY_INIT(&rlay->rl_sessions); + TAILQ_INSERT_TAIL(conf->sc_relays, rb, rl_entry); + + return (rb); + + err: + free(rb); + return (NULL); +} + +int +getservice(char *n) +{ + struct servent *s; + const char *errstr; + long long llval; + + llval = strtonum(n, 0, UINT16_MAX, &errstr); + if (errstr) { + s = getservbyname(n, "tcp"); + if (s == NULL) + s = getservbyname(n, "udp"); + if (s == NULL) { + yyerror("unknown port %s", n); + return (-1); + } + return (s->s_port); + } + + return (htons((u_short)llval)); +} Index: contrib/relayd/ssl.c =================================================================== --- contrib/relayd/ssl.c (revision 0) +++ contrib/relayd/ssl.c (revision 0) @@ -0,0 +1,291 @@ +/* $OpenBSD: ssl.c,v 1.15 2009/06/04 13:46:07 reyk Exp $ */ + +/* + * Copyright (c) 2006 Pierre-Yves Ritschard + * + * 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 +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "relayd.h" + +void ssl_read(int, short, void *); +void ssl_write(int, short, void *); +void ssl_connect(int, short, void *); +void ssl_cleanup(struct ctl_tcp_event *); + +void +ssl_read(int s, short event, void *arg) +{ + struct ctl_tcp_event *cte = arg; + int ret; + int ssl_err; + int retry_flag; + char rbuf[SMALL_READ_BUF_SIZE]; + + if (event == EV_TIMEOUT) { + cte->host->up = HOST_DOWN; + ssl_cleanup(cte); + hce_notify_done(cte->host, HCE_SSL_READ_TIMEOUT); + return; + } + + bzero(rbuf, sizeof(rbuf)); + ssl_err = 0; + retry_flag = EV_READ; + + ret = SSL_read(cte->ssl, rbuf, sizeof(rbuf)); + if (ret <= 0) { + ssl_err = SSL_get_error(cte->ssl, ret); + switch (ssl_err) { + case SSL_ERROR_WANT_READ: + retry_flag = EV_READ; + goto retry; + case SSL_ERROR_WANT_WRITE: + retry_flag = EV_WRITE; + goto retry; + case SSL_ERROR_ZERO_RETURN: /* FALLTHROUGH */ + case SSL_ERROR_SYSCALL: + if (ret == 0) { + cte->host->up = HOST_DOWN; + (void)cte->validate_close(cte); + ssl_cleanup(cte); + hce_notify_done(cte->host, cte->host->he); + return; + } + /* FALLTHROUGH */ + default: + cte->host->up = HOST_DOWN; + ssl_error(cte->host->conf.name, "cannot read"); + ssl_cleanup(cte); + hce_notify_done(cte->host, HCE_SSL_READ_ERROR); + break; + } + return; + } + if (buf_add(cte->buf, rbuf, ret) == -1) + fatal("ssl_read: buf_add error"); + if (cte->validate_read != NULL) { + if (cte->validate_read(cte) != 0) + goto retry; + + ssl_cleanup(cte); + hce_notify_done(cte->host, cte->host->he); + return; + } + +retry: + event_again(&cte->ev, s, EV_TIMEOUT|retry_flag, ssl_read, + &cte->tv_start, &cte->table->conf.timeout, cte); + return; +} + +void +ssl_write(int s, short event, void *arg) +{ + struct ctl_tcp_event *cte = arg; + int len; + int ret; + int ssl_err; + int retry_flag; + + if (event == EV_TIMEOUT) { + cte->host->up = HOST_DOWN; + ssl_cleanup(cte); + hce_notify_done(cte->host, HCE_SSL_WRITE_TIMEOUT); + return; + } + + len = strlen(cte->table->sendbuf); + retry_flag = EV_WRITE; + + ret = SSL_write(cte->ssl, cte->table->sendbuf, len); + if (ret <= 0) { + ssl_err = SSL_get_error(cte->ssl, ret); + switch (ssl_err) { + case SSL_ERROR_WANT_READ: + retry_flag = EV_READ; + goto retry; + case SSL_ERROR_WANT_WRITE: + retry_flag = EV_WRITE; + goto retry; + default: + cte->host->up = HOST_DOWN; + ssl_error(cte->host->conf.name, "cannot write"); + ssl_cleanup(cte); + hce_notify_done(cte->host, HCE_SSL_WRITE_ERROR); + return; + } + } + if ((cte->buf = buf_dynamic(SMALL_READ_BUF_SIZE, UINT_MAX)) == NULL) + fatalx("ssl_write: cannot create dynamic buffer"); + + event_again(&cte->ev, s, EV_TIMEOUT|EV_READ, ssl_read, + &cte->tv_start, &cte->table->conf.timeout, cte); + return; +retry: + event_again(&cte->ev, s, EV_TIMEOUT|retry_flag, ssl_write, + &cte->tv_start, &cte->table->conf.timeout, cte); +} + +void +ssl_connect(int s, short event, void *arg) +{ + struct ctl_tcp_event *cte = arg; + int ret; + int ssl_err; + int retry_flag; + + if (event == EV_TIMEOUT) { + cte->host->up = HOST_DOWN; + hce_notify_done(cte->host, HCE_SSL_CONNECT_TIMEOUT); + ssl_cleanup(cte); + return; + } + + retry_flag = ssl_err = 0; + + ret = SSL_connect(cte->ssl); + if (ret <= 0) { + ssl_err = SSL_get_error(cte->ssl, ret); + switch (ssl_err) { + case SSL_ERROR_WANT_READ: + retry_flag = EV_READ; + goto retry; + case SSL_ERROR_WANT_WRITE: + retry_flag = EV_WRITE; + goto retry; + default: + cte->host->up = HOST_DOWN; + ssl_error(cte->host->conf.name, "cannot connect"); + hce_notify_done(cte->host, HCE_SSL_CONNECT_FAIL); + ssl_cleanup(cte); + return; + } + } + + if (cte->table->conf.check == CHECK_TCP) { + cte->host->up = HOST_UP; + hce_notify_done(cte->host, HCE_SSL_CONNECT_OK); + ssl_cleanup(cte); + return; + } + if (cte->table->sendbuf != NULL) { + event_again(&cte->ev, cte->s, EV_TIMEOUT|EV_WRITE, ssl_write, + &cte->tv_start, &cte->table->conf.timeout, cte); + return; + } + + if ((cte->buf = buf_dynamic(SMALL_READ_BUF_SIZE, UINT_MAX)) == NULL) + fatalx("ssl_connect: cannot create dynamic buffer"); + event_again(&cte->ev, cte->s, EV_TIMEOUT|EV_READ, ssl_read, + &cte->tv_start, &cte->table->conf.timeout, cte); + return; + +retry: + event_again(&cte->ev, s, EV_TIMEOUT|retry_flag, ssl_connect, + &cte->tv_start, &cte->table->conf.timeout, cte); +} + +void +ssl_cleanup(struct ctl_tcp_event *cte) +{ + close(cte->s); + if (cte->ssl != NULL) { + SSL_shutdown(cte->ssl); + SSL_clear(cte->ssl); + } + if (cte->buf != NULL) + buf_free(cte->buf); +} + +void +ssl_error(const char *where, const char *what) +{ + unsigned long code; + char errbuf[128]; + extern int debug; + + if (!debug) + return; + for (; (code = ERR_get_error()) != 0 ;) { + ERR_error_string_n(code, errbuf, sizeof(errbuf)); + log_debug("SSL library error: %s: %s: %s", where, what, errbuf); + } +} + +void +ssl_init(struct relayd *env) +{ + SSL_library_init(); + SSL_load_error_strings(); + + /* Init hardware crypto engines. */ + ENGINE_load_builtin_engines(); + ENGINE_register_all_complete(); +} + +void +ssl_transaction(struct ctl_tcp_event *cte) +{ + if (cte->ssl == NULL) { + cte->ssl = SSL_new(cte->table->ssl_ctx); + if (cte->ssl == NULL) { + ssl_error(cte->host->conf.name, "cannot create object"); + fatal("cannot create SSL object"); + } + } + + if (SSL_set_fd(cte->ssl, cte->s) == 0) { + cte->host->up = HOST_UNKNOWN; + ssl_error(cte->host->conf.name, "cannot set fd"); + ssl_cleanup(cte); + hce_notify_done(cte->host, HCE_SSL_CONNECT_ERROR); + return; + } + SSL_set_connect_state(cte->ssl); + + event_again(&cte->ev, cte->s, EV_TIMEOUT|EV_WRITE, ssl_connect, + &cte->tv_start, &cte->table->conf.timeout, cte); +} + +SSL_CTX * +ssl_ctx_create(struct relayd *env) +{ + SSL_CTX *ctx; + + ctx = SSL_CTX_new(SSLv23_client_method()); + if (ctx == NULL) { + ssl_error("ssl_ctx_create", "cannot create context"); + fatal("could not create SSL context"); + } + return (ctx); +} Index: contrib/relayd/check_script.c =================================================================== --- contrib/relayd/check_script.c (revision 0) +++ contrib/relayd/check_script.c (revision 0) @@ -0,0 +1,174 @@ +/* $OpenBSD: check_script.c,v 1.10 2009/06/05 23:39:51 pyr Exp $ */ + +/* + * Copyright (c) 2007, 2008 Reyk Floeter + * + * 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 +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "relayd.h" + +void script_sig_alarm(int); + +extern struct imsgev *iev_main; +pid_t child = -1; + +void +check_script(struct host *host) +{ + struct ctl_script scr; + + host->last_up = host->up; + host->flags &= ~(F_CHECK_SENT|F_CHECK_DONE); + + scr.host = host->conf.id; + imsg_compose_event(iev_main, IMSG_SCRIPT, 0, 0, -1, &scr, sizeof(scr)); +} + +void +script_done(struct relayd *env, struct ctl_script *scr) +{ + struct host *host; + + if ((host = host_find(env, scr->host)) == NULL) + fatalx("hce_dispatch_parent: invalid host id"); + + if (scr->retval < 0) + host->up = HOST_UNKNOWN; + else if (scr->retval == 0) + host->up = HOST_DOWN; + else + host->up = HOST_UP; + host->flags |= F_CHECK_DONE; + + hce_notify_done(host, host->up == HOST_UP ? + HCE_SCRIPT_OK : HCE_SCRIPT_FAIL); +} + +void +script_sig_alarm(int sig) +{ + int save_errno = errno; + + if (child != -1) + kill(child, SIGKILL); + errno = save_errno; +} + +int +script_exec(struct relayd *env, struct ctl_script *scr) +{ + int status = 0, ret = 0; + sig_t save_quit, save_int, save_chld; + struct itimerval it; + struct timeval *tv; + const char *file, *arg; + struct host *host; + struct table *table; + struct passwd *pw; + + if ((host = host_find(env, scr->host)) == NULL) + fatalx("script_exec: invalid host id"); + if ((table = table_find(env, host->conf.tableid)) == NULL) + fatalx("script_exec: invalid table id"); + + arg = host->conf.name; + file = table->conf.path; + tv = &table->conf.timeout; + + save_quit = signal(SIGQUIT, SIG_IGN); + save_int = signal(SIGINT, SIG_IGN); + save_chld = signal(SIGCHLD, SIG_DFL); + + switch (child = fork()) { + case -1: + ret = -1; + goto done; + case 0: + signal(SIGQUIT, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGCHLD, SIG_DFL); + + if ((pw = getpwnam(RELAYD_USER)) == NULL) + fatal("script_exec: getpwnam"); + if (chdir("/") == -1) + fatal("script_exec: chdir(\"/\")"); + 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("script_exec: can't drop privileges"); + + /* + * close fds before executing an external program, to + * prevent access to internal fds, eg. IMSG connections + * of internal processes. + */ + closefrom(STDERR_FILENO + 1); + + execlp(file, file, arg, (char *)NULL); + _exit(0); + break; + default: + /* Kill the process after a timeout */ + signal(SIGALRM, script_sig_alarm); + bzero(&it, sizeof(it)); + bcopy(tv, &it.it_value, sizeof(it.it_value)); + setitimer(ITIMER_REAL, &it, NULL); + + waitpid(child, &status, 0); + break; + } + + switch (ret) { + case -1: + ret = -1; + break; + default: + if (WIFEXITED(status)) + ret = WEXITSTATUS(status); + else + ret = -1; + } + + done: + /* Disable the process timeout timer */ + bzero(&it, sizeof(it)); + setitimer(ITIMER_REAL, &it, NULL); + child = -1; + + signal(SIGQUIT, save_quit); + signal(SIGINT, save_int); + signal(SIGCHLD, save_chld); + signal(SIGALRM, SIG_DFL); + + return (ret); +} Index: contrib/relayd/imsg.h =================================================================== --- contrib/relayd/imsg.h (revision 0) +++ contrib/relayd/imsg.h (revision 0) @@ -0,0 +1,108 @@ +/* $OpenBSD: imsg.h,v 1.12 2009/06/07 05:56:25 eric Exp $ */ + +/* + * Copyright (c) 2006, 2007 Pierre-Yves Ritschard + * Copyright (c) 2006, 2007, 2008 Reyk Floeter + * Copyright (c) 2003, 2004 Henning Brauer + * + * 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 + +#define READ_BUF_SIZE 65535 +#define IMSG_HEADER_SIZE sizeof(struct imsg_hdr) +#define MAX_IMSGSIZE 16384 + +struct buf { + TAILQ_ENTRY(buf) entry; + u_char *buf; + size_t size; + size_t max; + size_t wpos; + size_t rpos; + int fd; +}; + +struct msgbuf { + TAILQ_HEAD(, buf) bufs; + u_int32_t queued; + int fd; +}; + +struct buf_read { + u_char buf[READ_BUF_SIZE]; + u_char *rptr; + size_t wpos; +}; + +struct imsg_fd { + TAILQ_ENTRY(imsg_fd) entry; + int fd; +}; + +struct imsgbuf { + TAILQ_HEAD(, imsg_fd) fds; + struct buf_read r; + struct msgbuf w; + int fd; + pid_t pid; +}; + +#define IMSGF_HASFD 1 + +struct imsg_hdr { + u_int32_t type; + u_int16_t len; + u_int16_t flags; + u_int32_t peerid; + u_int32_t pid; +}; + +struct imsg { + struct imsg_hdr hdr; + int fd; + void *data; +}; + + +/* buffer.c */ +struct buf *buf_open(size_t); +struct buf *buf_dynamic(size_t, size_t); +int buf_add(struct buf *, const void *, size_t); +void *buf_reserve(struct buf *, size_t); +void *buf_seek(struct buf *, size_t, size_t); +size_t buf_size(struct buf *); +size_t buf_left(struct buf *); +void buf_close(struct msgbuf *, struct buf *); +int buf_write(struct msgbuf *); +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); +ssize_t imsg_read(struct imsgbuf *); +ssize_t imsg_get(struct imsgbuf *, struct imsg *); +int imsg_compose(struct imsgbuf *, u_int32_t, u_int32_t, pid_t, + int, void *, u_int16_t); +int imsg_composev(struct imsgbuf *, u_int32_t, u_int32_t, pid_t, + int, const struct iovec *, int); +struct buf *imsg_create(struct imsgbuf *, u_int32_t, u_int32_t, pid_t, + u_int16_t); +int imsg_add(struct buf *, void *, u_int16_t); +void imsg_close(struct imsgbuf *, struct buf *); +void imsg_free(struct imsg *); +int imsg_flush(struct imsgbuf *); +void imsg_clear(struct imsgbuf *); Index: contrib/relayd/relayd.8 =================================================================== --- contrib/relayd/relayd.8 (revision 0) +++ contrib/relayd/relayd.8 (revision 0) @@ -0,0 +1,156 @@ +.\" $OpenBSD: relayd.8,v 1.17 2008/05/07 01:49:29 reyk Exp $ +.\" +.\" Copyright (c) 2006 Pierre-Yves Ritschard +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: May 7 2008 $ +.Dt RELAYD 8 +.Os +.Sh NAME +.Nm relayd +.Nd relay daemon +.Sh SYNOPSIS +.Nm +.Op Fl dnv +.Oo Xo +.Fl D Ar macro Ns = Ns Ar value Oc +.Xc +.Op Fl f Ar file +.Sh DESCRIPTION +.Nm +is a daemon to relay and dynamically redirect incoming connections to +a target host. +Its main purposes are to run as a load-balancer, application layer +gateway, or transparent proxy. +The daemon is able to monitor groups of hosts for availability, which +is determined by checking for a specific service common to a host +group. +When availability is confirmed, +Layer 3 and/or layer 7 forwarding services are set up by +.Nm . +.Pp +Layer 3 redirection happens at the packet level; to configure +it, +.Nm +communicates with +.Xr pf 4 . +To allow +.Nm +to properly set up +.Xr pf 4 +rules, the following lines are required in the NAT and filter sections of +.Xr pf.conf 5 : +.Bd -literal -offset indent +rdr-anchor "relayd/*" +anchor "relayd/*" +.Ed +.Pp +Layer 7 relaying happens at the application level and is +handled by +.Nm +itself. +Various application level filtering and protocol-specific +load-balancing options are available for relays. +.Pp +.Nm +works in terms of the following +.Em entities : +relays, protocols, redirections, and tables. +A +.Em relay +represents a layer 7 load-balancing instance. +Each instance translates to a listening TCP or UDP port. +A +.Em protocol +defines which actions, if any, are taken on the +packet payload as data crosses a relay. +A +.Em redirection +represents a layer 3 load-balancing instance. +Each instance translates to a +.Xr pf 4 +rdr rule being added. +A +.Em table +represents a group of hosts which can be checked for +availability using the same method. +Each table contains at least one host. +If a table is used in a layer 3 load-balancing instance, it +will be mapped to a +.Xr pf 4 +table containing only those hosts which are up. +.Pp +All these entities can be configured in +.Xr relayd.conf 5 , +and +.Xr relayctl 8 +can be used to alter or report on the status of each entity. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl D Ar macro Ns = Ns Ar value +Define +.Ar macro +to be set to +.Ar value +on the command line. +Overrides the definition of +.Ar macro +in the configuration file. +.It Fl d +Do not daemonize. +If this option is specified, +.Nm +will run in the foreground and log to +.Em stderr . +.It Fl f Ar file +Specify an alternative configuration file. +The default is +.Pa /etc/relayd.conf . +.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/relayd.sockXX" -compact +.It /etc/relayd.conf +Default configuration file. +.It /var/run/relayd.sock +Unix-domain socket used for communication with +.Xr relayctl 8 . +.El +.Sh SEE ALSO +.Xr relayd.conf 5 , +.Xr relayctl 8 +.Sh HISTORY +The +.Nm +program, formerly known as +.Ic hoststated , +first appeared in +.Ox 4.1 . +It was renamed to +.Nm +in +.Ox 4.3 . +.Sh AUTHORS +.An -nosplit +The +.Nm +program was written by +.An Pierre-Yves Ritschard Aq pyr@openbsd.org +and +.An Reyk Floeter Aq reyk@openbsd.org . Index: contrib/relayd/ssl_privsep.c =================================================================== --- contrib/relayd/ssl_privsep.c (revision 0) +++ contrib/relayd/ssl_privsep.c (revision 0) @@ -0,0 +1,253 @@ +/* $OpenBSD: ssl_privsep.c,v 1.7 2009/04/02 14:30:51 reyk Exp $ */ + +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + +/* + * SSL operations needed when running in a privilege separated environment. + * Adapted from openssl's ssl_rsa.c by Pierre-Yves Ritschard . + */ + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +int ssl_ctx_use_private_key(SSL_CTX *, char *, off_t); +int ssl_ctx_use_certificate_chain(SSL_CTX *, char *, off_t); +int ssl_ctx_load_verify_memory(SSL_CTX *, char *, off_t); +int ssl_by_mem_ctrl(X509_LOOKUP *, int, const char *, long, char **); + +X509_LOOKUP_METHOD x509_mem_lookup = { + "Load cert from memory", + NULL, /* new */ + NULL, /* free */ + NULL, /* init */ + NULL, /* shutdown */ + ssl_by_mem_ctrl, /* ctrl */ + NULL, /* get_by_subject */ + NULL, /* get_by_issuer_serial */ + NULL, /* get_by_fingerprint */ + NULL, /* get_by_alias */ +}; + +#define X509_L_ADD_MEM 3 + +int +ssl_ctx_use_private_key(SSL_CTX *ctx, char *buf, off_t len) +{ + int ret; + BIO *in; + EVP_PKEY *pkey; + + ret = 0; + + if ((in = BIO_new_mem_buf(buf, len)) == NULL) { + SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY_FILE, ERR_R_BUF_LIB); + return 0; + } + + pkey = PEM_read_bio_PrivateKey(in, NULL, + ctx->default_passwd_callback, + ctx->default_passwd_callback_userdata); + + if (pkey == NULL) { + SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY_FILE, ERR_R_PEM_LIB); + goto end; + } + ret = SSL_CTX_use_PrivateKey(ctx, pkey); + EVP_PKEY_free(pkey); +end: + if (in != NULL) + BIO_free(in); + return ret; +} + + +int +ssl_ctx_use_certificate_chain(SSL_CTX *ctx, char *buf, off_t len) +{ + int ret; + BIO *in; + X509 *x; + X509 *ca; + unsigned long err; + + ret = 0; + x = ca = NULL; + + if ((in = BIO_new_mem_buf(buf, len)) == NULL) { + SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_CHAIN_FILE, ERR_R_BUF_LIB); + goto end; + } + + if ((x = PEM_read_bio_X509(in, NULL, + ctx->default_passwd_callback, + ctx->default_passwd_callback_userdata)) == NULL) { + SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_CHAIN_FILE, ERR_R_PEM_LIB); + goto end; + } + + if (!SSL_CTX_use_certificate(ctx, x) || ERR_peek_error() != 0) + goto end; + + /* If we could set up our certificate, now proceed to + * the CA certificates. + */ + + if (ctx->extra_certs != NULL) { + sk_X509_pop_free(ctx->extra_certs, X509_free); + ctx->extra_certs = NULL; + } + + while ((ca = PEM_read_bio_X509(in, NULL, + ctx->default_passwd_callback, + ctx->default_passwd_callback_userdata)) != NULL) { + + if (!SSL_CTX_add_extra_chain_cert(ctx, ca)) + goto end; + } + + err = ERR_peek_last_error(); + if (ERR_GET_LIB(err) == ERR_LIB_PEM && + ERR_GET_REASON(err) == PEM_R_NO_START_LINE) + ERR_clear_error(); + else + goto end; + + ret = 1; +end: + if (ca != NULL) + X509_free(ca); + if (x != NULL) + X509_free(x); + if (in != NULL) + BIO_free(in); + return (ret); +} + +int +ssl_ctx_load_verify_memory(SSL_CTX *ctx, char *buf, off_t len) +{ + X509_LOOKUP *lu; + struct iovec iov; + + if ((lu = X509_STORE_add_lookup(ctx->cert_store, + &x509_mem_lookup)) == NULL) + return (0); + + iov.iov_base = buf; + iov.iov_len = len; + + if (!ssl_by_mem_ctrl(lu, X509_L_ADD_MEM, + (const char *)&iov, X509_FILETYPE_PEM, NULL)) + return (0); + + return (1); +} + +int +ssl_by_mem_ctrl(X509_LOOKUP *lu, int cmd, const char *buf, + long type, char **ret) +{ + STACK_OF(X509_INFO) *inf; + const struct iovec *iov; + X509_INFO *itmp; + BIO *in = NULL; + int i, count = 0; + + iov = (const struct iovec *)buf; + + if (type != X509_FILETYPE_PEM) + goto done; + + if ((in = BIO_new_mem_buf(iov->iov_base, iov->iov_len)) == NULL) + goto done; + + if ((inf = PEM_X509_INFO_read_bio(in, NULL, NULL, NULL)) == NULL) + goto done; + + for(i = 0; i < sk_X509_INFO_num(inf); i++) { + itmp = sk_X509_INFO_value(inf, i); + if(itmp->x509) { + X509_STORE_add_cert(lu->store_ctx, itmp->x509); + count++; + } + if(itmp->crl) { + X509_STORE_add_crl(lu->store_ctx, itmp->crl); + count++; + } + } + sk_X509_INFO_pop_free(inf, X509_INFO_free); + + done: + if (!count) + X509err(X509_F_X509_LOAD_CERT_CRL_FILE,ERR_R_PEM_LIB); + + if (in != NULL) + BIO_free(in); + return (count); +} Index: contrib/relayd/buffer.c =================================================================== --- contrib/relayd/buffer.c (revision 0) +++ contrib/relayd/buffer.c (revision 0) @@ -0,0 +1,305 @@ +/* $OpenBSD: buffer.c,v 1.21 2009/07/23 18:58:42 eric Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * 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 +#include +#include +#include + +#include +#include +#include +#include + +#include "imsg.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; + buf->fd = -1; + + 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, const 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); +} + +size_t +buf_size(struct buf *buf) +{ + return (buf->wpos); +} + +size_t +buf_left(struct buf *buf) +{ + return (buf->max - buf->wpos); +} + +void +buf_close(struct msgbuf *msgbuf, struct buf *buf) +{ + buf_enqueue(msgbuf, buf); +} + +int +buf_write(struct msgbuf *msgbuf) +{ + struct iovec iov[IOV_MAX]; + struct buf *buf, *next; + unsigned int i = 0; + ssize_t n; + + bzero(&iov, sizeof(iov)); + TAILQ_FOREACH(buf, &msgbuf->bufs, entry) { + if (i >= IOV_MAX) + break; + iov[i].iov_base = buf->buf + buf->rpos; + iov[i].iov_len = buf->wpos - buf->rpos; + i++; + } + + if ((n = writev(msgbuf->fd, iov, i)) == -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->wpos) { + n -= buf->wpos - buf->rpos; + buf_dequeue(msgbuf, buf); + } else { + buf->rpos += n; + n = 0; + } + } + + return (0); +} + +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; + unsigned int i = 0; + ssize_t n; + struct msghdr msg; + struct cmsghdr *cmsg; + union { + struct cmsghdr hdr; + char buf[CMSG_SPACE(sizeof(int))]; + } cmsgbuf; + + 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->wpos - buf->rpos; + i++; + if (buf->fd != -1) + break; + } + + msg.msg_iov = iov; + msg.msg_iovlen = i; + + if (buf != NULL && buf->fd != -1) { + msg.msg_control = (caddr_t)&cmsgbuf.buf; + msg.msg_controllen = sizeof(cmsgbuf.buf); + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + *(int *)CMSG_DATA(cmsg) = buf->fd; + } + + 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); + } + + /* + * assumption: fd got sent if sendmsg sent anything + * this works because fds are passed one at a time + */ + if (buf != NULL && buf->fd != -1) { + close(buf->fd); + buf->fd = -1; + } + + for (buf = TAILQ_FIRST(&msgbuf->bufs); buf != NULL && n > 0; + buf = next) { + next = TAILQ_NEXT(buf, entry); + if (buf->rpos + n >= buf->wpos) { + n -= buf->wpos - 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); + + if (buf->fd != -1) + close(buf->fd); + + msgbuf->queued--; + buf_free(buf); +} Index: contrib/relayd/pfe.c =================================================================== --- contrib/relayd/pfe.c (revision 0) +++ contrib/relayd/pfe.c (revision 0) @@ -0,0 +1,1003 @@ +/* $OpenBSD: pfe.c,v 1.61 2009/08/07 11:21:53 reyk Exp $ */ + +/* + * Copyright (c) 2006 Pierre-Yves Ritschard + * + * 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 +#ifdef __FreeBSD__ +#include +#endif +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "relayd.h" + +void pfe_sig_handler(int sig, short, void *); +void pfe_shutdown(void); +void pfe_setup_events(void); +void pfe_disable_events(void); +void pfe_dispatch_imsg(int, short, void *); +void pfe_dispatch_parent(int, short, void *); +void pfe_dispatch_relay(int, short, void *); +void pfe_sync(void); +void pfe_statistics(int, short, void *); + +static struct relayd *env = NULL; + +struct imsgev *iev_main; +struct imsgev *iev_hce; +struct imsgev *iev_relay; + +void +pfe_sig_handler(int sig, short event, void *arg) +{ + switch (sig) { + case SIGINT: + case SIGTERM: + pfe_shutdown(); + break; + default: + fatalx("pfe_sig_handler: unexpected signal"); + } +} + +pid_t +pfe(struct relayd *x_env, int pipe_parent2pfe[2], int pipe_parent2hce[2], + int pipe_parent2relay[RELAY_MAXPROC][2], int pipe_pfe2hce[2], + int pipe_pfe2relay[RELAY_MAXPROC][2]) +{ + pid_t pid; + struct passwd *pw; + struct event ev_sigint; + struct event ev_sigterm; + int i; + size_t size; + + switch (pid = fork()) { + case -1: + fatal("pfe: cannot fork"); + case 0: + break; + default: + return (pid); + } + + env = x_env; + purge_config(env, PURGE_PROTOS); + + if (control_init() == -1) + fatalx("pfe: control socket setup failed"); + + init_filter(env); + init_tables(env); + + if ((pw = getpwnam(RELAYD_USER)) == NULL) + fatal("pfe: getpwnam"); + +#ifndef DEBUG + if (chroot(pw->pw_dir) == -1) + fatal("pfe: chroot"); + if (chdir("/") == -1) + fatal("pfe: chdir(\"/\")"); +#else +#warning disabling privilege revocation and chroot in DEBUG mode +#endif + + setproctitle("pf update engine"); + relayd_process = PROC_PFE; + +#ifndef DEBUG + 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"); +#endif + + 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); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + + /* setup pipes */ + close(pipe_pfe2hce[0]); + close(pipe_parent2pfe[0]); + close(pipe_parent2hce[0]); + close(pipe_parent2hce[1]); + for (i = 0; i < env->sc_prefork_relay; i++) { + close(pipe_parent2relay[i][0]); + close(pipe_parent2relay[i][1]); + close(pipe_pfe2relay[i][0]); + } + + size = sizeof(struct imsgev); + if ((iev_hce = calloc(1, size)) == NULL || + (iev_relay = calloc(env->sc_prefork_relay, size)) == NULL || + (iev_main = calloc(1, size)) == NULL) + fatal("pfe"); + + imsg_init(&iev_hce->ibuf, pipe_pfe2hce[1]); + iev_hce->handler = pfe_dispatch_imsg; + imsg_init(&iev_main->ibuf, pipe_parent2pfe[1]); + iev_main->handler = pfe_dispatch_parent; + for (i = 0; i < env->sc_prefork_relay; i++) { + imsg_init(&iev_relay[i].ibuf, pipe_pfe2relay[i][1]); + iev_relay[i].handler = pfe_dispatch_relay; + } + + iev_main->events = EV_READ; + event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events, + iev_main->handler, iev_main); + event_add(&iev_main->ev, NULL); + + pfe_setup_events(); + + TAILQ_INIT(&ctl_conns); + + if (control_listen(env, iev_main, iev_hce) == -1) + fatalx("pfe: control socket listen failed"); + + /* Initial sync */ + pfe_sync(); + + event_dispatch(); + pfe_shutdown(); + + return (0); +} + +void +pfe_shutdown(void) +{ + flush_rulesets(env); + log_info("pf update engine exiting"); + _exit(0); +} + +void +pfe_setup_events(void) +{ + int i; + struct imsgev *iev; + struct timeval tv; + + iev_hce->events = EV_READ; + event_set(&iev_hce->ev, iev_hce->ibuf.fd, iev_hce->events, + iev_hce->handler, iev_hce); + event_add(&iev_hce->ev, NULL); + + for (i = 0; i < env->sc_prefork_relay; i++) { + iev = &iev_relay[i]; + + iev->events = EV_READ; + event_set(&iev->ev, iev->ibuf.fd, iev->events, + iev->handler, iev); + event_add(&iev->ev, NULL); + } + + /* Schedule statistics timer */ + evtimer_set(&env->sc_statev, pfe_statistics, NULL); + bcopy(&env->sc_statinterval, &tv, sizeof(tv)); + evtimer_add(&env->sc_statev, &tv); +} + +void +pfe_disable_events(void) +{ + int i; + + event_del(&iev_hce->ev); + + for (i = 0; i < env->sc_prefork_relay; i++) + event_del(&iev_relay[i].ev); + + event_del(&env->sc_statev); +} + +void +pfe_dispatch_imsg(int fd, short event, void *ptr) +{ + struct imsgev *iev; + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + + struct host *host; + struct table *table; + struct ctl_status st; + + iev = ptr; + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1) + fatal("pfe_dispatch_imsg: imsg_read_error"); + if (n == 0) { + /* this pipe is dead, so remove the event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + return; + } + } + + if (event & EV_WRITE) { + if (msgbuf_write(&ibuf->w) == -1) + fatal("pfe_dispatch_imsg: msgbuf_write"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("pfe_dispatch_imsg: imsg_read error"); + if (n == 0) + break; + + control_imsg_forward(&imsg); + 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"); + host->he = st.he; + if (host->flags & F_DISABLE) + break; + host->retry_cnt = st.retry_cnt; + if (st.up != HOST_UNKNOWN) { + host->check_cnt++; + if (st.up == HOST_UP) + host->up_cnt++; + } + if (host->check_cnt != st.check_cnt) { + log_debug("pfe_dispatch_imsg: host %d => %d", + host->conf.id, host->up); + fatalx("pfe_dispatch_imsg: desynchronized"); + } + + if (host->up == st.up) + break; + + /* Forward to relay engine(s) */ + for (n = 0; n < env->sc_prefork_relay; n++) + imsg_compose_event(&iev_relay[n], + IMSG_HOST_STATUS, 0, 0, -1, &st, + sizeof(st)); + + if ((table = table_find(env, host->conf.tableid)) + == NULL) + fatalx("pfe_dispatch_imsg: invalid table id"); + + log_debug("pfe_dispatch_imsg: state %d for host %u %s", + st.up, host->conf.id, host->conf.name); + + /* + * Do not change the table state when the host + * state switches between UNKNOWN and DOWN. + */ + if (HOST_ISUP(st.up)) { + table->conf.flags |= F_CHANGED; + table->up++; + host->flags |= F_ADD; + host->flags &= ~(F_DEL); + } else if (HOST_ISUP(host->up)) { + table->up--; + table->conf.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(iev); +} + +void +pfe_dispatch_parent(int fd, short event, void * ptr) +{ + struct imsgev *iev; + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + + static struct rdr *rdr = NULL; + static struct table *table = NULL; + struct host *host, *parent; + struct address *virt; + + iev = ptr; + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1) + fatal("imsg_read error"); + if (n == 0) { + /* this pipe is dead, so remove the event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + return; + } + } + + + if (event & EV_WRITE) { + if (msgbuf_write(&ibuf->w) == -1) + fatal("msgbuf_write"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("pfe_dispatch_parent: imsg_read error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_RECONF: + log_debug("pfe: reloading configuration"); + if (imsg.hdr.len != + sizeof(struct relayd) + IMSG_HEADER_SIZE) + fatalx("corrupted reload data"); + pfe_disable_events(); + purge_config(env, PURGE_RDRS|PURGE_TABLES); + merge_config(env, (struct relayd *)imsg.data); + /* + * no relays when reconfiguring yet. + */ + env->sc_relays = NULL; + env->sc_protos = NULL; + + env->sc_tables = calloc(1, sizeof(*env->sc_tables)); + env->sc_rdrs = calloc(1, sizeof(*env->sc_rdrs)); + if (env->sc_tables == NULL || env->sc_rdrs == NULL) + fatal(NULL); + + TAILQ_INIT(env->sc_tables); + TAILQ_INIT(env->sc_rdrs); + break; + case IMSG_RECONF_TABLE: + if ((table = calloc(1, sizeof(*table))) == NULL) + fatal(NULL); + memcpy(&table->conf, imsg.data, sizeof(table->conf)); + TAILQ_INIT(&table->hosts); + TAILQ_INSERT_TAIL(env->sc_tables, table, entry); + break; + case IMSG_RECONF_HOST: + if ((host = calloc(1, sizeof(*host))) == NULL) + fatal(NULL); + memcpy(&host->conf, imsg.data, sizeof(host->conf)); + host->tablename = table->conf.name; + TAILQ_INSERT_TAIL(&table->hosts, host, entry); + if (host->conf.parentid) { + parent = host_find(env, host->conf.parentid); + SLIST_INSERT_HEAD(&parent->children, + host, child); + } + break; + case IMSG_RECONF_RDR: + if ((rdr = calloc(1, sizeof(*rdr))) == NULL) + fatal(NULL); + memcpy(&rdr->conf, imsg.data, + sizeof(rdr->conf)); + rdr->table = table_find(env, + rdr->conf.table_id); + if (rdr->conf.backup_id == EMPTY_TABLE) + rdr->backup = &env->sc_empty_table; + else + rdr->backup = table_find(env, + rdr->conf.backup_id); + if (rdr->table == NULL || rdr->backup == NULL) + fatal("pfe_dispatch_parent:" + " corrupted configuration"); + log_debug("pfe_dispatch_parent: rdr->table: %s", + rdr->table->conf.name); + log_debug("pfe_dispatch_parent: rdr->backup: %s", + rdr->backup->conf.name); + TAILQ_INIT(&rdr->virts); + TAILQ_INSERT_TAIL(env->sc_rdrs, rdr, entry); + break; + case IMSG_RECONF_VIRT: + if ((virt = calloc(1, sizeof(*virt))) == NULL) + fatal(NULL); + memcpy(virt, imsg.data, sizeof(*virt)); + TAILQ_INSERT_TAIL(&rdr->virts, virt, entry); + break; + case IMSG_RECONF_END: + log_warnx("pfe: configuration reloaded"); + init_tables(env); + pfe_setup_events(); + pfe_sync(); + break; + default: + log_debug("pfe_dispatch_parent: unexpected imsg %d", + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + imsg_event_add(iev); +} + +void +pfe_dispatch_relay(int fd, short event, void * ptr) +{ + struct imsgev *iev; + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + struct ctl_natlook cnl; + struct ctl_stats crs; + struct relay *rlay; + + iev = ptr; + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1) + fatal("imsg_read error"); + if (n == 0) { + /* this pipe is dead, so remove the event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + return; + } + } + + if (event & EV_WRITE) { + if (msgbuf_write(&ibuf->w) == -1) + fatal("msgbuf_write"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("pfe_dispatch_relay: imsg_read error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_NATLOOK: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(cnl)) + fatalx("invalid imsg header len"); + bcopy(imsg.data, &cnl, sizeof(cnl)); + if (cnl.proc > env->sc_prefork_relay) + fatalx("pfe_dispatch_relay: " + "invalid relay proc"); + if (natlook(env, &cnl) != 0) + cnl.in = -1; + imsg_compose_event(&iev_relay[cnl.proc], IMSG_NATLOOK, + 0, 0, -1, &cnl, sizeof(cnl)); + break; + case IMSG_STATISTICS: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(crs)) + fatalx("invalid imsg header len"); + bcopy(imsg.data, &crs, sizeof(crs)); + if (crs.proc > env->sc_prefork_relay) + fatalx("pfe_dispatch_relay: " + "invalid relay proc"); + if ((rlay = relay_find(env, crs.id)) == NULL) + fatalx("pfe_dispatch_relay: invalid relay id"); + bcopy(&crs, &rlay->rl_stats[crs.proc], sizeof(crs)); + rlay->rl_stats[crs.proc].interval = + env->sc_statinterval.tv_sec; + break; + default: + log_debug("pfe_dispatch_relay: unexpected imsg %d", + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + imsg_event_add(iev); +} + +void +show(struct ctl_conn *c) +{ + struct rdr *rdr; + struct host *host; + struct relay *rlay; + + if (env->sc_rdrs == NULL) + goto relays; + TAILQ_FOREACH(rdr, env->sc_rdrs, entry) { + imsg_compose_event(&c->iev, IMSG_CTL_RDR, 0, 0, -1, + rdr, sizeof(*rdr)); + if (rdr->conf.flags & F_DISABLE) + continue; + + imsg_compose_event(&c->iev, IMSG_CTL_RDR_STATS, 0, 0, -1, + &rdr->stats, sizeof(rdr->stats)); + + imsg_compose_event(&c->iev, IMSG_CTL_TABLE, 0, 0, -1, + rdr->table, sizeof(*rdr->table)); + if (!(rdr->table->conf.flags & F_DISABLE)) + TAILQ_FOREACH(host, &rdr->table->hosts, entry) + imsg_compose_event(&c->iev, IMSG_CTL_HOST, + 0, 0, -1, host, sizeof(*host)); + + if (rdr->backup->conf.id == EMPTY_TABLE) + continue; + imsg_compose_event(&c->iev, IMSG_CTL_TABLE, 0, 0, -1, + rdr->backup, sizeof(*rdr->backup)); + if (!(rdr->backup->conf.flags & F_DISABLE)) + TAILQ_FOREACH(host, &rdr->backup->hosts, entry) + imsg_compose_event(&c->iev, IMSG_CTL_HOST, + 0, 0, -1, host, sizeof(*host)); + } +relays: + if (env->sc_relays == NULL) + goto end; + TAILQ_FOREACH(rlay, env->sc_relays, rl_entry) { + rlay->rl_stats[env->sc_prefork_relay].id = EMPTY_ID; + imsg_compose_event(&c->iev, IMSG_CTL_RELAY, 0, 0, -1, + rlay, sizeof(*rlay)); + imsg_compose_event(&c->iev, IMSG_CTL_RELAY_STATS, 0, 0, -1, + &rlay->rl_stats, sizeof(rlay->rl_stats)); + + if (rlay->rl_dsttable == NULL) + continue; + imsg_compose_event(&c->iev, IMSG_CTL_TABLE, 0, 0, -1, + rlay->rl_dsttable, sizeof(*rlay->rl_dsttable)); + if (!(rlay->rl_dsttable->conf.flags & F_DISABLE)) + TAILQ_FOREACH(host, &rlay->rl_dsttable->hosts, entry) + imsg_compose_event(&c->iev, IMSG_CTL_HOST, + 0, 0, -1, host, sizeof(*host)); + } +end: + imsg_compose_event(&c->iev, IMSG_CTL_END, 0, 0, -1, NULL, 0); +} + +void +show_sessions(struct ctl_conn *c) +{ + int n, proc, done; + struct imsg imsg; + + for (proc = 0; proc < env->sc_prefork_relay; proc++) { + /* + * Request all the running sessions from the process + */ + imsg_compose_event(&iev_relay[proc], + IMSG_CTL_SESSION, 0, 0, -1, NULL, 0); + while (iev_relay[proc].ibuf.w.queued) + if (msgbuf_write(&iev_relay[proc].ibuf.w) < 0) + fatalx("write error"); + + /* + * Wait for the reply and forward the messages to the + * control connection. + */ + done = 0; + while (!done) { + do { + if ((n = imsg_read(&iev_relay[proc].ibuf)) == -1) + fatalx("imsg_read error"); + } while (n == -2); /* handle non-blocking I/O */ + while (!done) { + if ((n = imsg_get(&iev_relay[proc].ibuf, + &imsg)) == -1) + fatalx("imsg_get error"); + if (n == 0) + break; + switch (imsg.hdr.type) { + case IMSG_CTL_SESSION: + imsg_compose_event(&c->iev, + IMSG_CTL_SESSION, proc, 0, -1, + imsg.data, sizeof(struct rsession)); + break; + case IMSG_CTL_END: + done = 1; + break; + default: + fatalx("wrong message for session"); + break; + } + imsg_free(&imsg); + } + } + } + + imsg_compose_event(&c->iev, IMSG_CTL_END, 0, 0, -1, NULL, 0); +} + +int +disable_rdr(struct ctl_conn *c, struct ctl_id *id) +{ + struct rdr *rdr; + + if (id->id == EMPTY_ID) + rdr = rdr_findbyname(env, id->name); + else + rdr = rdr_find(env, id->id); + if (rdr == NULL) + return (-1); + id->id = rdr->conf.id; + + if (rdr->conf.flags & F_DISABLE) + return (0); + + rdr->conf.flags |= F_DISABLE; + rdr->conf.flags &= ~(F_ADD); + rdr->conf.flags |= F_DEL; + rdr->table->conf.flags |= F_DISABLE; + log_debug("disable_rdr: disabled rdr %d", rdr->conf.id); + pfe_sync(); + return (0); +} + +int +enable_rdr(struct ctl_conn *c, struct ctl_id *id) +{ + struct rdr *rdr; + struct ctl_id eid; + + if (id->id == EMPTY_ID) + rdr = rdr_findbyname(env, id->name); + else + rdr = rdr_find(env, id->id); + if (rdr == NULL) + return (-1); + id->id = rdr->conf.id; + + if (!(rdr->conf.flags & F_DISABLE)) + return (0); + + rdr->conf.flags &= ~(F_DISABLE); + rdr->conf.flags &= ~(F_DEL); + rdr->conf.flags |= F_ADD; + log_debug("enable_rdr: enabled rdr %d", rdr->conf.id); + + bzero(&eid, sizeof(eid)); + + /* XXX: we're syncing twice */ + eid.id = rdr->table->conf.id; + if (enable_table(c, &eid) == -1) + return (-1); + if (rdr->backup->conf.id == EMPTY_ID) + return (0); + eid.id = rdr->backup->conf.id; + if (enable_table(c, &eid) == -1) + return (-1); + return (0); +} + +int +disable_table(struct ctl_conn *c, struct ctl_id *id) +{ + struct table *table; + struct rdr *rdr; + struct host *host; + + if (id->id == EMPTY_ID) + table = table_findbyname(env, id->name); + else + table = table_find(env, id->id); + if (table == NULL) + return (-1); + id->id = table->conf.id; + if ((rdr = rdr_find(env, table->conf.rdrid)) == NULL) + fatalx("disable_table: desynchronised"); + + if (table->conf.flags & F_DISABLE) + return (0); + table->conf.flags |= (F_DISABLE|F_CHANGED); + table->up = 0; + TAILQ_FOREACH(host, &table->hosts, entry) + host->up = HOST_UNKNOWN; + imsg_compose_event(iev_hce, IMSG_TABLE_DISABLE, 0, 0, -1, + &table->conf.id, sizeof(table->conf.id)); + log_debug("disable_table: disabled table %d", table->conf.id); + pfe_sync(); + return (0); +} + +int +enable_table(struct ctl_conn *c, struct ctl_id *id) +{ + struct rdr *rdr; + struct table *table; + struct host *host; + + if (id->id == EMPTY_ID) + table = table_findbyname(env, id->name); + else + table = table_find(env, id->id); + if (table == NULL) + return (-1); + id->id = table->conf.id; + + if ((rdr = rdr_find(env, table->conf.rdrid)) == NULL) + fatalx("enable_table: desynchronised"); + + if (!(table->conf.flags & F_DISABLE)) + return (0); + table->conf.flags &= ~(F_DISABLE); + table->conf.flags |= F_CHANGED; + table->up = 0; + TAILQ_FOREACH(host, &table->hosts, entry) + host->up = HOST_UNKNOWN; + imsg_compose_event(iev_hce, IMSG_TABLE_ENABLE, 0, 0, -1, + &table->conf.id, sizeof(table->conf.id)); + log_debug("enable_table: enabled table %d", table->conf.id); + pfe_sync(); + return (0); +} + +int +disable_host(struct ctl_conn *c, struct ctl_id *id, struct host *host) +{ + struct host *h; + struct table *table; + int n; + + if (host == NULL) { + if (id->id == EMPTY_ID) + host = host_findbyname(env, id->name); + else + host = host_find(env, id->id); + if (host == NULL || host->conf.parentid) + return (-1); + } + id->id = host->conf.id; + + if (host->flags & F_DISABLE) + return (0); + + if (host->up == HOST_UP) { + if ((table = table_find(env, host->conf.tableid)) == NULL) + fatalx("disable_host: invalid table id"); + table->up--; + table->conf.flags |= F_CHANGED; + } + + host->up = HOST_UNKNOWN; + host->flags |= F_DISABLE; + host->flags |= F_DEL; + host->flags &= ~(F_ADD); + host->check_cnt = 0; + host->up_cnt = 0; + + imsg_compose_event(iev_hce, IMSG_HOST_DISABLE, 0, 0, -1, + &host->conf.id, sizeof(host->conf.id)); + /* Forward to relay engine(s) */ + for (n = 0; n < env->sc_prefork_relay; n++) + imsg_compose_event(&iev_relay[n], + IMSG_HOST_DISABLE, 0, 0, -1, + &host->conf.id, sizeof(host->conf.id)); + log_debug("disable_host: disabled host %d", host->conf.id); + + if (!host->conf.parentid) { + /* Disable all children */ + SLIST_FOREACH(h, &host->children, child) + disable_host(c, id, h); + pfe_sync(); + } + return (0); +} + +int +enable_host(struct ctl_conn *c, struct ctl_id *id, struct host *host) +{ + struct host *h; + int n; + + if (host == NULL) { + if (id->id == EMPTY_ID) + host = host_findbyname(env, id->name); + else + host = host_find(env, id->id); + if (host == NULL || host->conf.parentid) + return (-1); + } + id->id = host->conf.id; + + 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_event(iev_hce, IMSG_HOST_ENABLE, 0, 0, -1, + &host->conf.id, sizeof (host->conf.id)); + /* Forward to relay engine(s) */ + for (n = 0; n < env->sc_prefork_relay; n++) + imsg_compose_event(&iev_relay[n], + IMSG_HOST_ENABLE, 0, 0, -1, + &host->conf.id, sizeof(host->conf.id)); + log_debug("enable_host: enabled host %d", host->conf.id); + + if (!host->conf.parentid) { + /* Enable all children */ + SLIST_FOREACH(h, &host->children, child) + enable_host(c, id, h); + pfe_sync(); + } + return (0); +} + +void +pfe_sync(void) +{ + struct rdr *rdr; + struct table *active; + struct table *table; + struct ctl_id id; + struct imsg imsg; +#ifndef __FreeBSD__ + struct ctl_demote demote; +#endif + + bzero(&id, sizeof(id)); + bzero(&imsg, sizeof(imsg)); + TAILQ_FOREACH(rdr, env->sc_rdrs, entry) { + rdr->conf.flags &= ~(F_BACKUP); + rdr->conf.flags &= ~(F_DOWN); + + if (rdr->conf.flags & F_DISABLE || + (rdr->table->up == 0 && rdr->backup->up == 0)) { + rdr->conf.flags |= F_DOWN; + active = NULL; + } else if (rdr->table->up == 0 && rdr->backup->up > 0) { + rdr->conf.flags |= F_BACKUP; + active = rdr->backup; + active->conf.flags |= + rdr->table->conf.flags & F_CHANGED; + active->conf.flags |= + rdr->backup->conf.flags & F_CHANGED; + } else + active = rdr->table; + + if (active != NULL && active->conf.flags & F_CHANGED) { + id.id = active->conf.id; + imsg.hdr.type = IMSG_CTL_TABLE_CHANGED; + imsg.hdr.len = sizeof(id) + IMSG_HEADER_SIZE; + imsg.data = &id; + sync_table(env, rdr, active); + control_imsg_forward(&imsg); + } + + if (rdr->conf.flags & F_DOWN) { + if (rdr->conf.flags & F_ACTIVE_RULESET) { + flush_table(env, rdr); + log_debug("pfe_sync: disabling ruleset"); + rdr->conf.flags &= ~(F_ACTIVE_RULESET); + id.id = rdr->conf.id; + imsg.hdr.type = IMSG_CTL_PULL_RULESET; + imsg.hdr.len = sizeof(id) + IMSG_HEADER_SIZE; + imsg.data = &id; + sync_ruleset(env, rdr, 0); + control_imsg_forward(&imsg); + } + } else if (!(rdr->conf.flags & F_ACTIVE_RULESET)) { + log_debug("pfe_sync: enabling ruleset"); + rdr->conf.flags |= F_ACTIVE_RULESET; + id.id = rdr->conf.id; + imsg.hdr.type = IMSG_CTL_PUSH_RULESET; + imsg.hdr.len = sizeof(id) + IMSG_HEADER_SIZE; + imsg.data = &id; + sync_ruleset(env, rdr, 1); + control_imsg_forward(&imsg); + } + } + + TAILQ_FOREACH(table, env->sc_tables, entry) { + /* + * clean up change flag. + */ + table->conf.flags &= ~(F_CHANGED); + +#ifndef __FreeBSD__ + /* + * handle demotion. + */ + if ((table->conf.flags & F_DEMOTE) == 0) + continue; + demote.level = 0; + if (table->up && table->conf.flags & F_DEMOTED) { + demote.level = -1; + table->conf.flags &= ~F_DEMOTED; + } + else if (!table->up && !(table->conf.flags & F_DEMOTED)) { + demote.level = 1; + table->conf.flags |= F_DEMOTED; + } + if (demote.level == 0) + continue; + log_debug("pfe_sync: demote %d table '%s' group '%s'", + demote.level, table->conf.name, table->conf.demote_group); + (void)strlcpy(demote.group, table->conf.demote_group, + sizeof(demote.group)); + imsg_compose_event(iev_main, IMSG_DEMOTE, 0, 0, -1, + &demote, sizeof(demote)); +#endif + } +} + +void +pfe_statistics(int fd, short events, void *arg) +{ + struct rdr *rdr; + struct ctl_stats *cur; + struct timeval tv, tv_now; + int resethour, resetday; + u_long cnt; + + timerclear(&tv); + if (gettimeofday(&tv_now, NULL) == -1) + fatal("pfe_statistics: gettimeofday"); + + TAILQ_FOREACH(rdr, env->sc_rdrs, entry) { + cnt = check_table(env, rdr, rdr->table); + if (rdr->conf.backup_id != EMPTY_TABLE) + cnt += check_table(env, rdr, rdr->backup); + + resethour = resetday = 0; + + cur = &rdr->stats; + cur->last = cnt > cur->cnt ? cnt - cur->cnt : 0; + + cur->cnt = cnt; + cur->tick++; + cur->avg = (cur->last + cur->avg) / 2; + cur->last_hour += cur->last; + if ((cur->tick % (3600 / env->sc_statinterval.tv_sec)) == 0) { + cur->avg_hour = (cur->last_hour + cur->avg_hour) / 2; + resethour++; + } + cur->last_day += cur->last; + if ((cur->tick % (86400 / env->sc_statinterval.tv_sec)) == 0) { + cur->avg_day = (cur->last_day + cur->avg_day) / 2; + resethour++; + } + if (resethour) + cur->last_hour = 0; + if (resetday) + cur->last_day = 0; + + rdr->stats.interval = env->sc_statinterval.tv_sec; + } + + /* Schedule statistics timer */ + evtimer_set(&env->sc_statev, pfe_statistics, NULL); + bcopy(&env->sc_statinterval, &tv, sizeof(tv)); + evtimer_add(&env->sc_statev, &tv); +} Index: contrib/relayd/relay.c =================================================================== --- contrib/relayd/relay.c (revision 0) +++ contrib/relayd/relay.c (revision 0) @@ -0,0 +1,3234 @@ +/* $OpenBSD: relay.c,v 1.117 2009/08/07 11:21:53 reyk Exp $ */ + +/* + * Copyright (c) 2006, 2007, 2008 Reyk Floeter + * + * 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. + */ + +#ifdef __FreeBSD__ +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "relayd.h" + +void relay_sig_handler(int sig, short, void *); +void relay_statistics(int, short, void *); +void relay_dispatch_pfe(int, short, void *); +void relay_dispatch_parent(int, short, void *); +void relay_shutdown(void); + +void relay_privinit(void); +void relay_nodedebug(const char *, struct protonode *); +void relay_protodebug(struct relay *); +void relay_init(void); +void relay_launch(void); +int relay_socket(struct sockaddr_storage *, in_port_t, + struct protocol *, int); +int relay_socket_listen(struct sockaddr_storage *, in_port_t, + struct protocol *); +int relay_socket_connect(struct sockaddr_storage *, in_port_t, + struct protocol *, int); + +void relay_accept(int, short, void *); +void relay_input(struct rsession *); + +int relay_connect(struct rsession *); +void relay_connected(int, short, void *); +void relay_bindanyreq(struct rsession *, in_port_t, int); +void relay_bindany(int, short, void *); + +u_int32_t relay_hash_addr(struct sockaddr_storage *, u_int32_t); + +void relay_write(struct bufferevent *, void *); +void relay_read(struct bufferevent *, void *); +void relay_error(struct bufferevent *, short, void *); +void relay_dump(struct ctl_relay_event *, const void *, size_t); + +int relay_resolve(struct ctl_relay_event *, + struct protonode *, struct protonode *); +int relay_handle_http(struct ctl_relay_event *, + struct protonode *, struct protonode *, + struct protonode *, int); +int relay_lognode(struct rsession *, + struct protonode *, struct protonode *, char *, size_t); +void relay_read_http(struct bufferevent *, void *); +static int _relay_lookup_url(struct ctl_relay_event *, char *, char *, + char *, enum digest_type); +int relay_lookup_url(struct ctl_relay_event *, + const char *, enum digest_type); +int relay_lookup_query(struct ctl_relay_event *); +int relay_lookup_cookie(struct ctl_relay_event *, const char *); +void relay_read_httpcontent(struct bufferevent *, void *); +void relay_read_httpchunks(struct bufferevent *, void *); +char *relay_expand_http(struct ctl_relay_event *, char *, + char *, size_t); +void relay_close_http(struct rsession *, u_int, const char *, + u_int16_t); +void relay_http_request_close(struct ctl_relay_event *); + +SSL_CTX *relay_ssl_ctx_create(struct relay *); +void relay_ssl_transaction(struct rsession *, + struct ctl_relay_event *); +void relay_ssl_accept(int, short, void *); +void relay_ssl_connect(int, short, void *); +void relay_ssl_connected(struct ctl_relay_event *); +void relay_ssl_readcb(int, short, void *); +void relay_ssl_writecb(int, short, void *); + +int relay_bufferevent_add(struct event *, int); +#ifdef notyet +int relay_bufferevent_printf(struct ctl_relay_event *, + const char *, ...); +#endif +int relay_bufferevent_print(struct ctl_relay_event *, char *); +int relay_bufferevent_write_buffer(struct ctl_relay_event *, + struct evbuffer *); +int relay_bufferevent_write_chunk(struct ctl_relay_event *, + struct evbuffer *, size_t); +int relay_bufferevent_write(struct ctl_relay_event *, + void *, size_t); +char *relay_load_file(const char *, off_t *); +static __inline int + relay_proto_cmp(struct protonode *, struct protonode *); +extern void bufferevent_read_pressure_cb(struct evbuffer *, size_t, + size_t, void *); + +volatile sig_atomic_t relay_sessions; +objid_t relay_conid; + +static struct relayd *env = NULL; +struct imsgev *iev_pfe; +struct imsgev *iev_main; +int proc_id; + +void +relay_sig_handler(int sig, short event, void *arg) +{ + switch (sig) { + case SIGTERM: + case SIGINT: + (void)event_loopexit(NULL); + } +} + +pid_t +relay(struct relayd *x_env, int pipe_parent2pfe[2], int pipe_parent2hce[2], + int pipe_parent2relay[RELAY_MAXPROC][2], int pipe_pfe2hce[2], + int pipe_pfe2relay[RELAY_MAXPROC][2]) +{ + pid_t pid; + struct passwd *pw; + struct event ev_sigint; + struct event ev_sigterm; + int i; + + switch (pid = fork()) { + case -1: + fatal("relay: cannot fork"); + case 0: + break; + default: + return (pid); + } + + env = x_env; + purge_config(env, PURGE_RDRS); + + /* Need root privileges for relay initialization */ + relay_privinit(); + + if ((pw = getpwnam(RELAYD_USER)) == NULL) + fatal("relay: getpwnam"); + +#ifndef DEBUG + if (chroot(pw->pw_dir) == -1) + fatal("relay: chroot"); + if (chdir("/") == -1) + fatal("relay: chdir(\"/\")"); + +#else +#warning disabling privilege revocation and chroot in DEBUG mode +#endif + + setproctitle("socket relay engine"); + relayd_process = PROC_RELAY; + +#ifndef DEBUG + 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("relay: can't drop privileges"); +#endif + + /* Fork child handlers */ + for (i = 1; i < env->sc_prefork_relay; i++) { + if (fork() == 0) { + proc_id = i; + break; + } + } + + event_init(); + + /* Per-child initialization */ + relay_init(); + + signal_set(&ev_sigint, SIGINT, relay_sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, relay_sig_handler, NULL); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + signal(SIGHUP, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + + /* setup pipes */ + close(pipe_pfe2hce[0]); + close(pipe_pfe2hce[1]); + close(pipe_parent2hce[0]); + close(pipe_parent2hce[1]); + close(pipe_parent2pfe[0]); + close(pipe_parent2pfe[1]); + for (i = 0; i < env->sc_prefork_relay; i++) { + if (i == proc_id) + continue; + close(pipe_parent2relay[i][0]); + close(pipe_parent2relay[i][1]); + close(pipe_pfe2relay[i][0]); + close(pipe_pfe2relay[i][1]); + } + close(pipe_parent2relay[proc_id][1]); + close(pipe_pfe2relay[proc_id][1]); + + if ((iev_pfe = calloc(1, sizeof(struct imsgev))) == NULL || + (iev_main = calloc(1, sizeof(struct imsgev))) == NULL) + fatal("relay"); + imsg_init(&iev_pfe->ibuf, pipe_pfe2relay[proc_id][0]); + imsg_init(&iev_main->ibuf, pipe_parent2relay[proc_id][0]); + iev_pfe->handler = relay_dispatch_pfe; + iev_main->handler = relay_dispatch_parent; + + iev_pfe->events = EV_READ; + event_set(&iev_pfe->ev, iev_pfe->ibuf.fd, iev_pfe->events, + iev_pfe->handler, iev_pfe); + event_add(&iev_pfe->ev, NULL); + + iev_main->events = EV_READ; + event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events, + iev_main->handler, iev_main); + event_add(&iev_main->ev, NULL); + + relay_launch(); + + event_dispatch(); + relay_shutdown(); + + return (0); +} + +void +relay_shutdown(void) +{ + struct rsession *con; + + struct relay *rlay; + TAILQ_FOREACH(rlay, env->sc_relays, rl_entry) { + if (rlay->rl_conf.flags & F_DISABLE) + continue; + close(rlay->rl_s); + while ((con = SPLAY_ROOT(&rlay->rl_sessions)) != NULL) + relay_close(con, "shutdown"); + } + usleep(200); /* XXX relay needs to shutdown last */ + log_info("socket relay engine exiting"); + _exit(0); +} + +void +relay_nodedebug(const char *name, struct protonode *pn) +{ + const char *s; + int digest; + + if (pn->action == NODE_ACTION_NONE) + return; + + fprintf(stderr, "\t\t"); + fprintf(stderr, "%s ", name); + + switch (pn->type) { + case NODE_TYPE_HEADER: + break; + case NODE_TYPE_QUERY: + fprintf(stderr, "query "); + break; + case NODE_TYPE_COOKIE: + fprintf(stderr, "cookie "); + break; + case NODE_TYPE_PATH: + fprintf(stderr, "path "); + break; + case NODE_TYPE_URL: + fprintf(stderr, "url "); + break; + } + + switch (pn->action) { + case NODE_ACTION_APPEND: + fprintf(stderr, "append \"%s\" to \"%s\"", + pn->value, pn->key); + break; + case NODE_ACTION_CHANGE: + fprintf(stderr, "change \"%s\" to \"%s\"", + pn->key, pn->value); + break; + case NODE_ACTION_REMOVE: + fprintf(stderr, "remove \"%s\"", + pn->key); + break; + case NODE_ACTION_EXPECT: + case NODE_ACTION_FILTER: + s = pn->action == NODE_ACTION_EXPECT ? "expect" : "filter"; + digest = pn->flags & PNFLAG_LOOKUP_URL_DIGEST; + if (strcmp(pn->value, "*") == 0) + fprintf(stderr, "%s %s\"%s\"", s, + digest ? "digest " : "", pn->key); + else + fprintf(stderr, "%s \"%s\" from \"%s\"", s, + pn->value, pn->key); + break; + case NODE_ACTION_HASH: + fprintf(stderr, "hash \"%s\"", pn->key); + break; + case NODE_ACTION_LOG: + fprintf(stderr, "log \"%s\"", pn->key); + break; + case NODE_ACTION_MARK: + if (strcmp(pn->value, "*") == 0) + fprintf(stderr, "mark \"%s\"", pn->key); + else + fprintf(stderr, "mark \"%s\" from \"%s\"", + pn->value, pn->key); + break; + case NODE_ACTION_NONE: + break; + } + fprintf(stderr, "\n"); +} + +void +relay_protodebug(struct relay *rlay) +{ + struct protocol *proto = rlay->rl_proto; + struct protonode *proot, *pn; + struct proto_tree *tree; + const char *name; + int i; + + fprintf(stderr, "protocol %d: name %s\n", proto->id, proto->name); + fprintf(stderr, "\tflags: 0x%04x\n", proto->flags); + if (proto->cache != -1) + fprintf(stderr, "\tssl session cache: %d\n", proto->cache); + fprintf(stderr, "\ttype: "); + switch (proto->type) { + case RELAY_PROTO_TCP: + fprintf(stderr, "tcp\n"); + break; + case RELAY_PROTO_HTTP: + fprintf(stderr, "http\n"); + break; + case RELAY_PROTO_DNS: + fprintf(stderr, "dns\n"); + break; + } + + name = "request"; + tree = &proto->request_tree; + show: + i = 0; + RB_FOREACH(proot, proto_tree, tree) { +#if DEBUG > 1 + i = 0; +#endif + PROTONODE_FOREACH(pn, proot, entry) { +#if DEBUG > 1 + i = 0; +#endif + if (++i > 100) + break; + relay_nodedebug(name, pn); + } + /* Limit the number of displayed lines */ + if (++i > 100) { + fprintf(stderr, "\t\t...\n"); + break; + } + } + if (tree == &proto->request_tree) { + name = "response"; + tree = &proto->response_tree; + goto show; + } +} + +void +relay_privinit(void) +{ + struct relay *rlay; + extern int debug; + + if (env->sc_flags & (F_SSL|F_SSLCLIENT)) + ssl_init(env); + + TAILQ_FOREACH(rlay, env->sc_relays, rl_entry) { + log_debug("relay_privinit: adding relay %s", + rlay->rl_conf.name); + + if (debug) + relay_protodebug(rlay); + + switch (rlay->rl_proto->type) { + case RELAY_PROTO_DNS: + relay_udp_privinit(env, rlay); + break; + case RELAY_PROTO_TCP: + case RELAY_PROTO_HTTP: + /* Use defaults */ + break; + } + + if (rlay->rl_conf.flags & F_UDP) + rlay->rl_s = relay_udp_bind(&rlay->rl_conf.ss, + rlay->rl_conf.port, rlay->rl_proto); + else + rlay->rl_s = relay_socket_listen(&rlay->rl_conf.ss, + rlay->rl_conf.port, rlay->rl_proto); + if (rlay->rl_s == -1) + fatal("relay_privinit: failed to listen"); + } +} + +void +relay_init(void) +{ + struct relay *rlay; + struct host *host; + struct timeval tv; + struct rlimit rl; + + if (getrlimit(RLIMIT_NOFILE, &rl) == -1) + fatal("relay_init: failed to get resource limit"); + log_debug("relay_init: max open files %d", rl.rlim_max); + + /* + * Allow the maximum number of open file descriptors for this + * login class (which should be the class "daemon" by default). + */ + rl.rlim_cur = rl.rlim_max; + if (setrlimit(RLIMIT_NOFILE, &rl) == -1) + fatal("relay_init: failed to set resource limit"); + + TAILQ_FOREACH(rlay, env->sc_relays, rl_entry) { + if ((rlay->rl_conf.flags & (F_SSL|F_SSLCLIENT)) && + (rlay->rl_ssl_ctx = relay_ssl_ctx_create(rlay)) == NULL) + fatal("relay_init: failed to create SSL context"); + + if (rlay->rl_dsttable != NULL) { + switch (rlay->rl_conf.dstmode) { + case RELAY_DSTMODE_ROUNDROBIN: + rlay->rl_dstkey = 0; + break; + case RELAY_DSTMODE_LOADBALANCE: + case RELAY_DSTMODE_HASH: + rlay->rl_dstkey = + hash32_str(rlay->rl_conf.name, HASHINIT); + rlay->rl_dstkey = + hash32_str(rlay->rl_dsttable->conf.name, + rlay->rl_dstkey); + break; + } + rlay->rl_dstnhosts = 0; + TAILQ_FOREACH(host, &rlay->rl_dsttable->hosts, entry) { + if (rlay->rl_dstnhosts >= RELAY_MAXHOSTS) + fatal("relay_init: " + "too many hosts in table"); + host->idx = rlay->rl_dstnhosts; + rlay->rl_dsthost[rlay->rl_dstnhosts++] = host; + } + log_info("adding %d hosts from table %s%s", + rlay->rl_dstnhosts, rlay->rl_dsttable->conf.name, + rlay->rl_dsttable->conf.check ? "" : " (no check)"); + } + + switch (rlay->rl_proto->type) { + case RELAY_PROTO_DNS: + relay_udp_init(rlay); + break; + case RELAY_PROTO_TCP: + case RELAY_PROTO_HTTP: + /* Use defaults */ + break; + } + } + + /* Schedule statistics timer */ + evtimer_set(&env->sc_statev, relay_statistics, NULL); + bcopy(&env->sc_statinterval, &tv, sizeof(tv)); + evtimer_add(&env->sc_statev, &tv); +} + +void +relay_statistics(int fd, short events, void *arg) +{ + struct relay *rlay; + struct ctl_stats crs, *cur; + struct timeval tv, tv_now; + int resethour = 0, resetday = 0; + struct rsession *con, *next_con; + + /* + * This is a hack to calculate some average statistics. + * It doesn't try to be very accurate, but could be improved... + */ + + timerclear(&tv); + if (gettimeofday(&tv_now, NULL) == -1) + fatal("relay_init: gettimeofday"); + + TAILQ_FOREACH(rlay, env->sc_relays, rl_entry) { + bzero(&crs, sizeof(crs)); + resethour = resetday = 0; + + cur = &rlay->rl_stats[proc_id]; + cur->cnt += cur->last; + cur->tick++; + cur->avg = (cur->last + cur->avg) / 2; + cur->last_hour += cur->last; + if ((cur->tick % (3600 / env->sc_statinterval.tv_sec)) == 0) { + cur->avg_hour = (cur->last_hour + cur->avg_hour) / 2; + resethour++; + } + cur->last_day += cur->last; + if ((cur->tick % (86400 / env->sc_statinterval.tv_sec)) == 0) { + cur->avg_day = (cur->last_day + cur->avg_day) / 2; + resethour++; + } + bcopy(cur, &crs, sizeof(crs)); + + cur->last = 0; + if (resethour) + cur->last_hour = 0; + if (resetday) + cur->last_day = 0; + + crs.id = rlay->rl_conf.id; + crs.proc = proc_id; + imsg_compose_event(iev_pfe, IMSG_STATISTICS, 0, 0, -1, + &crs, sizeof(crs)); + + for (con = SPLAY_ROOT(&rlay->rl_sessions); + con != NULL; con = next_con) { + next_con = SPLAY_NEXT(session_tree, + &rlay->rl_sessions, con); + timersub(&tv_now, &con->se_tv_last, &tv); + if (timercmp(&tv, &rlay->rl_conf.timeout, >=)) + relay_close(con, "hard timeout"); + } + } + + /* Schedule statistics timer */ + evtimer_set(&env->sc_statev, relay_statistics, NULL); + bcopy(&env->sc_statinterval, &tv, sizeof(tv)); + evtimer_add(&env->sc_statev, &tv); +} + +void +relay_launch(void) +{ + struct relay *rlay; + void (*callback)(int, short, void *); + + TAILQ_FOREACH(rlay, env->sc_relays, rl_entry) { + log_debug("relay_launch: running relay %s", rlay->rl_conf.name); + + rlay->rl_up = HOST_UP; + + if (rlay->rl_conf.flags & F_UDP) + callback = relay_udp_server; + else + callback = relay_accept; + + event_set(&rlay->rl_ev, rlay->rl_s, EV_READ|EV_PERSIST, + callback, rlay); + event_add(&rlay->rl_ev, NULL); + } +} + +int +relay_socket_af(struct sockaddr_storage *ss, in_port_t port) +{ + switch (ss->ss_family) { + case AF_INET: + ((struct sockaddr_in *)ss)->sin_port = port; + ((struct sockaddr_in *)ss)->sin_len = + sizeof(struct sockaddr_in); + break; + case AF_INET6: + ((struct sockaddr_in6 *)ss)->sin6_port = port; + ((struct sockaddr_in6 *)ss)->sin6_len = + sizeof(struct sockaddr_in6); + break; + default: + return (-1); + } + + return (0); +} + +int +relay_socket(struct sockaddr_storage *ss, in_port_t port, + struct protocol *proto, int fd) +{ + int s = -1, val; + struct linger lng; + + if (relay_socket_af(ss, port) == -1) + goto bad; + + s = fd == -1 ? socket(ss->ss_family, SOCK_STREAM, IPPROTO_TCP) : fd; + if (s == -1) + goto bad; + + /* + * Socket options + */ + bzero(&lng, sizeof(lng)); + if (setsockopt(s, SOL_SOCKET, SO_LINGER, &lng, sizeof(lng)) == -1) + goto bad; + val = 1; + if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(int)) == -1) + goto bad; + if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) + goto bad; + if (proto->tcpflags & TCPFLAG_BUFSIZ) { + val = proto->tcpbufsiz; + if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, + &val, sizeof(val)) == -1) + goto bad; + val = proto->tcpbufsiz; + if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, + &val, sizeof(val)) == -1) + goto bad; + } + + /* + * IP options + */ + if (proto->tcpflags & TCPFLAG_IPTTL) { + val = (int)proto->tcpipttl; + if (setsockopt(s, IPPROTO_IP, IP_TTL, + &val, sizeof(val)) == -1) + goto bad; + } + if (proto->tcpflags & TCPFLAG_IPMINTTL) { + val = (int)proto->tcpipminttl; + if (setsockopt(s, IPPROTO_IP, IP_MINTTL, + &val, sizeof(val)) == -1) + goto bad; + } + + /* + * TCP options + */ + if (proto->tcpflags & (TCPFLAG_NODELAY|TCPFLAG_NNODELAY)) { + if (proto->tcpflags & TCPFLAG_NNODELAY) + val = 0; + else + val = 1; + if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, + &val, sizeof(val)) == -1) + goto bad; + } +#ifndef __FreeBSD__ + if (proto->tcpflags & (TCPFLAG_SACK|TCPFLAG_NSACK)) { + if (proto->tcpflags & TCPFLAG_NSACK) + val = 0; + else + val = 1; + if (setsockopt(s, IPPROTO_TCP, TCP_SACK_ENABLE, + &val, sizeof(val)) == -1) + goto bad; + } +#endif + + return (s); + + bad: + if (s != -1) + close(s); + return (-1); +} + +int +relay_socket_connect(struct sockaddr_storage *ss, in_port_t port, + struct protocol *proto, int fd) +{ + int s; + + if ((s = relay_socket(ss, port, proto, fd)) == -1) + return (-1); + + if (connect(s, (struct sockaddr *)ss, ss->ss_len) == -1) { + if (errno != EINPROGRESS) + goto bad; + } + + return (s); + + bad: + close(s); + return (-1); +} + +int +relay_socket_listen(struct sockaddr_storage *ss, in_port_t port, + struct protocol *proto) +{ + int s; + + if ((s = relay_socket(ss, port, proto, -1)) == -1) + return (-1); + + if (bind(s, (struct sockaddr *)ss, ss->ss_len) == -1) + goto bad; + if (listen(s, proto->tcpbacklog) == -1) + goto bad; + + return (s); + + bad: + close(s); + return (-1); +} + +void +relay_connected(int fd, short sig, void *arg) +{ + struct rsession *con = (struct rsession *)arg; + struct relay *rlay = (struct relay *)con->se_relay; + struct protocol *proto = rlay->rl_proto; + evbuffercb outrd = relay_read; + evbuffercb outwr = relay_write; + struct bufferevent *bev; + struct ctl_relay_event *out = &con->se_out; + + if (sig == EV_TIMEOUT) { + relay_close_http(con, 504, "connect timeout", 0); + return; + } + + if ((rlay->rl_conf.flags & F_SSLCLIENT) && (out->ssl == NULL)) { + relay_ssl_transaction(con, out); + return; + } + + DPRINTF("relay_connected: session %d: %ssuccessful", + con->se_id, rlay->rl_proto->lateconnect ? "late connect " : ""); + + switch (rlay->rl_proto->type) { + case RELAY_PROTO_HTTP: + /* Check the servers's HTTP response */ + if (!RB_EMPTY(&rlay->rl_proto->response_tree)) { + outrd = relay_read_http; + if ((con->se_out.nodes = calloc(proto->response_nodes, + sizeof(u_int8_t))) == NULL) { + relay_close_http(con, 500, + "failed to allocate nodes", 0); + return; + } + } + break; + case RELAY_PROTO_TCP: + /* Use defaults */ + break; + default: + fatalx("relay_input: unknown protocol"); + } + + /* + * Relay <-> Server + */ + bev = bufferevent_new(fd, outrd, outwr, relay_error, &con->se_out); + if (bev == NULL) { + relay_close_http(con, 500, + "failed to allocate output buffer event", 0); + return; + } + evbuffer_free(bev->output); + bev->output = con->se_out.output; + if (bev->output == NULL) + fatal("relay_connected: invalid output buffer"); + con->se_out.bev = bev; + + /* Initialize the SSL wrapper */ + if ((rlay->rl_conf.flags & F_SSLCLIENT) && (out->ssl != NULL)) + relay_ssl_connected(out); + + bufferevent_settimeout(bev, + rlay->rl_conf.timeout.tv_sec, rlay->rl_conf.timeout.tv_sec); + bufferevent_enable(bev, EV_READ|EV_WRITE); +} + +void +relay_input(struct rsession *con) +{ + struct relay *rlay = (struct relay *)con->se_relay; + struct protocol *proto = rlay->rl_proto; + evbuffercb inrd = relay_read; + evbuffercb inwr = relay_write; + + switch (rlay->rl_proto->type) { + case RELAY_PROTO_HTTP: + /* Check the client's HTTP request */ + if (!RB_EMPTY(&rlay->rl_proto->request_tree) || + proto->lateconnect) { + inrd = relay_read_http; + if ((con->se_in.nodes = calloc(proto->request_nodes, + sizeof(u_int8_t))) == NULL) { + relay_close(con, "failed to allocate nodes"); + return; + } + } + break; + case RELAY_PROTO_TCP: + /* Use defaults */ + break; + default: + fatalx("relay_input: unknown protocol"); + } + + /* + * Client <-> Relay + */ + con->se_in.bev = bufferevent_new(con->se_in.s, inrd, inwr, + relay_error, &con->se_in); + if (con->se_in.bev == NULL) { + relay_close(con, "failed to allocate input buffer event"); + return; + } + + /* Initialize the SSL wrapper */ + if ((rlay->rl_conf.flags & F_SSL) && con->se_in.ssl != NULL) + relay_ssl_connected(&con->se_in); + + bufferevent_settimeout(con->se_in.bev, + rlay->rl_conf.timeout.tv_sec, rlay->rl_conf.timeout.tv_sec); + bufferevent_enable(con->se_in.bev, EV_READ|EV_WRITE); +} + +void +relay_write(struct bufferevent *bev, void *arg) +{ + struct ctl_relay_event *cre = (struct ctl_relay_event *)arg; + struct rsession *con = (struct rsession *)cre->con; + if (gettimeofday(&con->se_tv_last, NULL) == -1) + con->se_done = 1; + if (con->se_done) + relay_close(con, "last write (done)"); +} + +void +relay_dump(struct ctl_relay_event *cre, const void *buf, size_t len) +{ + if (!len) + return; + + /* + * This function will dump the specified message directly + * to the underlying session, without waiting for success + * of non-blocking events etc. This is useful to print an + * error message before gracefully closing the session. + */ + if (cre->ssl != NULL) + (void)SSL_write(cre->ssl, buf, len); + else + (void)write(cre->s, buf, len); +} + +void +relay_read(struct bufferevent *bev, void *arg) +{ + struct ctl_relay_event *cre = (struct ctl_relay_event *)arg; + struct rsession *con = (struct rsession *)cre->con; + struct evbuffer *src = EVBUFFER_INPUT(bev); + + if (gettimeofday(&con->se_tv_last, NULL) == -1) + goto fail; + if (!EVBUFFER_LENGTH(src)) + return; + if (relay_bufferevent_write_buffer(cre->dst, src) == -1) + goto fail; + if (con->se_done) + goto done; + bufferevent_enable(con->se_in.bev, EV_READ); + return; + done: + relay_close(con, "last read (done)"); + return; + fail: + relay_close(con, strerror(errno)); +} + +int +relay_resolve(struct ctl_relay_event *cre, + struct protonode *proot, struct protonode *pn) +{ + struct rsession *con = (struct rsession *)cre->con; + char buf[READ_BUF_SIZE], *ptr; + int id; + + if (pn->mark && (pn->mark != con->se_mark)) + return (0); + + switch (pn->action) { + case NODE_ACTION_FILTER: + id = cre->nodes[proot->id]; + if (SIMPLEQ_NEXT(pn, entry) == NULL) + cre->nodes[proot->id] = 0; + if (id <= 1) + return (0); + break; + case NODE_ACTION_EXPECT: + id = cre->nodes[proot->id]; + if (SIMPLEQ_NEXT(pn, entry) == NULL) + cre->nodes[proot->id] = 0; + if (id > 1) + return (0); + break; + default: + if (cre->nodes[pn->id]) { + cre->nodes[pn->id] = 0; + return (0); + } + break; + } + switch (pn->action) { + case NODE_ACTION_APPEND: + case NODE_ACTION_CHANGE: + ptr = pn->value; + if ((pn->flags & PNFLAG_MACRO) && + (ptr = relay_expand_http(cre, pn->value, + buf, sizeof(buf))) == NULL) + break; + if (relay_bufferevent_print(cre->dst, pn->key) == -1 || + relay_bufferevent_print(cre->dst, ": ") == -1 || + relay_bufferevent_print(cre->dst, ptr) == -1 || + relay_bufferevent_print(cre->dst, "\r\n") == -1) { + relay_close_http(con, 500, + "failed to modify header", 0); + return (-1); + } + DPRINTF("relay_resolve: add '%s: %s'", + pn->key, ptr); + break; + case NODE_ACTION_EXPECT: + DPRINTF("relay_resolve: missing '%s: %s'", + pn->key, pn->value); + relay_close_http(con, 403, "incomplete request", pn->label); + return (-1); + case NODE_ACTION_FILTER: + DPRINTF("relay_resolve: filtered '%s: %s'", + pn->key, pn->value); + relay_close_http(con, 403, "rejecting request", pn->label); + return (-1); + default: + break; + } + return (0); +} + +char * +relay_expand_http(struct ctl_relay_event *cre, char *val, char *buf, size_t len) +{ + struct rsession *con = (struct rsession *)cre->con; + struct relay *rlay = (struct relay *)con->se_relay; + char ibuf[128]; + + (void)strlcpy(buf, val, len); + + if (strstr(val, "$REMOTE_") != NULL) { + if (strstr(val, "$REMOTE_ADDR") != NULL) { + if (print_host(&cre->ss, ibuf, sizeof(ibuf)) == NULL) + return (NULL); + if (expand_string(buf, len, + "$REMOTE_ADDR", ibuf) != 0) + return (NULL); + } + if (strstr(val, "$REMOTE_PORT") != NULL) { + snprintf(ibuf, sizeof(ibuf), "%u", ntohs(cre->port)); + if (expand_string(buf, len, + "$REMOTE_PORT", ibuf) != 0) + return (NULL); + } + } + if (strstr(val, "$SERVER_") != NULL) { + if (strstr(val, "$SERVER_ADDR") != NULL) { + if (print_host(&rlay->rl_conf.ss, + ibuf, sizeof(ibuf)) == NULL) + return (NULL); + if (expand_string(buf, len, + "$SERVER_ADDR", ibuf) != 0) + return (NULL); + } + if (strstr(val, "$SERVER_PORT") != NULL) { + snprintf(ibuf, sizeof(ibuf), "%u", + ntohs(rlay->rl_conf.port)); + if (expand_string(buf, len, + "$SERVER_PORT", ibuf) != 0) + return (NULL); + } + if (strstr(val, "$SERVER_NAME") != NULL) { + if (expand_string(buf, len, + "$SERVER_NAME", RELAYD_SERVERNAME) != 0) + return (NULL); + } + } + if (strstr(val, "$TIMEOUT") != NULL) { + snprintf(ibuf, sizeof(ibuf), "%lu", +#ifdef __FreeBSD__ + (unsigned long)rlay->rl_conf.timeout.tv_sec); +#else + rlay->rl_conf.timeout.tv_sec); +#endif + if (expand_string(buf, len, "$TIMEOUT", ibuf) != 0) + return (NULL); + } + + return (buf); +} + +int +relay_lognode(struct rsession *con, struct protonode *pn, struct protonode *pk, + char *buf, size_t len) +{ + const char *label = NULL; + + if ((pn->flags & PNFLAG_LOG) == 0) + return (0); + bzero(buf, len); + if (pn->label != 0) + label = pn_id2name(pn->label); + if (snprintf(buf, len, " [%s%s%s: %s]", + label == NULL ? "" : label, + label == NULL ? "" : ", ", + pk->key, pk->value) == -1 || + evbuffer_add(con->se_log, buf, strlen(buf)) == -1) + return (-1); + return (0); +} + +int +relay_handle_http(struct ctl_relay_event *cre, struct protonode *proot, + struct protonode *pn, struct protonode *pk, int header) +{ + struct rsession *con = (struct rsession *)cre->con; + char buf[READ_BUF_SIZE], *ptr; + int ret = PN_DROP, mark = 0; + struct protonode *next; + + /* Check if this action depends on a marked session */ + if (pn->mark != 0) + mark = pn->mark == con->se_mark ? 1 : -1; + + switch (pn->action) { + case NODE_ACTION_EXPECT: + case NODE_ACTION_FILTER: + case NODE_ACTION_MARK: + break; + default: + if (mark == -1) + return (PN_PASS); + break; + } + + switch (pn->action) { + case NODE_ACTION_APPEND: + if (!header) + return (PN_PASS); + ptr = pn->value; + if ((pn->flags & PNFLAG_MACRO) && + (ptr = relay_expand_http(cre, pn->value, + buf, sizeof(buf))) == NULL) + break; + if (relay_bufferevent_print(cre->dst, pn->key) == -1 || + relay_bufferevent_print(cre->dst, ": ") == -1 || + relay_bufferevent_print(cre->dst, pk->value) == -1 || + relay_bufferevent_print(cre->dst, ", ") == -1 || + relay_bufferevent_print(cre->dst, ptr) == -1 || + relay_bufferevent_print(cre->dst, "\r\n") == -1) + goto fail; + cre->nodes[pn->id] = 1; + DPRINTF("relay_handle_http: append '%s: %s, %s'", + pk->key, pk->value, ptr); + break; + case NODE_ACTION_CHANGE: + case NODE_ACTION_REMOVE: + if (!header) + return (PN_PASS); + DPRINTF("relay_handle_http: change/remove '%s: %s'", + pk->key, pk->value); + break; + case NODE_ACTION_EXPECT: + /* + * A client may specify the header line for multiple times + * trying to circumvent the filter. + */ + if (cre->nodes[proot->id] > 1) { + relay_close_http(con, 400, "repeated header line", 0); + return (PN_FAIL); + } + /* FALLTHROUGH */ + case NODE_ACTION_FILTER: + DPRINTF("relay_handle_http: %s '%s: %s'", + (pn->action == NODE_ACTION_EXPECT) ? "expect" : "filter", + pn->key, pn->value); + + /* Do not drop the entity */ + ret = PN_PASS; + + if (mark != -1 && + fnmatch(pn->value, pk->value, FNM_CASEFOLD) == 0) { + cre->nodes[proot->id] = 1; + + /* Fail instantly */ + if (pn->action == NODE_ACTION_FILTER) { + (void)relay_lognode(con, pn, pk, + buf, sizeof(buf)); + relay_close_http(con, 403, + "rejecting request", pn->label); + return (PN_FAIL); + } + } + next = SIMPLEQ_NEXT(pn, entry); + if (next == NULL || next->action != pn->action) + cre->nodes[proot->id]++; + break; + case NODE_ACTION_HASH: + DPRINTF("relay_handle_http: hash '%s: %s'", + pn->key, pk->value); + con->se_hashkey = hash32_str(pk->value, con->se_hashkey); + ret = PN_PASS; + break; + case NODE_ACTION_LOG: + DPRINTF("relay_handle_http: log '%s: %s'", + pn->key, pk->value); + ret = PN_PASS; + break; + case NODE_ACTION_MARK: + DPRINTF("relay_handle_http: mark '%s: %s'", + pn->key, pk->value); + if (fnmatch(pn->value, pk->value, FNM_CASEFOLD) == 0) + con->se_mark = pn->mark; + ret = PN_PASS; + break; + case NODE_ACTION_NONE: + return (PN_PASS); + } + if (mark != -1 && relay_lognode(con, pn, pk, buf, sizeof(buf)) == -1) + goto fail; + + return (ret); + fail: + relay_close_http(con, 500, strerror(errno), 0); + return (PN_FAIL); +} + +void +relay_read_httpcontent(struct bufferevent *bev, void *arg) +{ + struct ctl_relay_event *cre = (struct ctl_relay_event *)arg; + struct rsession *con = (struct rsession *)cre->con; + struct evbuffer *src = EVBUFFER_INPUT(bev); + size_t size; + + if (gettimeofday(&con->se_tv_last, NULL) == -1) + goto fail; + size = EVBUFFER_LENGTH(src); + DPRINTF("relay_read_httpcontent: size %d, to read %d", + size, cre->toread); + if (!size) + return; + if (relay_bufferevent_write_buffer(cre->dst, src) == -1) + goto fail; + if (size >= cre->toread) + bev->readcb = relay_read_http; + cre->toread -= size; + DPRINTF("relay_read_httpcontent: done, size %d, to read %d", + size, cre->toread); + if (con->se_done) + goto done; + if (bev->readcb != relay_read_httpcontent) + bev->readcb(bev, arg); + bufferevent_enable(bev, EV_READ); + return; + done: + relay_close(con, "last http content read"); + return; + fail: + relay_close(con, strerror(errno)); +} + +void +relay_read_httpchunks(struct bufferevent *bev, void *arg) +{ + struct ctl_relay_event *cre = (struct ctl_relay_event *)arg; + struct rsession *con = (struct rsession *)cre->con; + struct evbuffer *src = EVBUFFER_INPUT(bev); + char *line; + long lval; + size_t size; + + if (gettimeofday(&con->se_tv_last, NULL) == -1) + goto fail; + size = EVBUFFER_LENGTH(src); + DPRINTF("relay_read_httpchunks: size %d, to read %d", + size, cre->toread); + if (!size) + return; + + if (!cre->toread) { + line = evbuffer_readline(src); + if (line == NULL) { + /* Ignore empty line, continue */ + bufferevent_enable(bev, EV_READ); + return; + } + if (!strlen(line)) { + free(line); + goto next; + } + + /* Read prepended chunk size in hex, ingore the trailer */ + if (sscanf(line, "%lx", &lval) != 1) { + free(line); + relay_close(con, "invalid chunk size"); + return; + } + + if (relay_bufferevent_print(cre->dst, line) == -1 || + relay_bufferevent_print(cre->dst, "\r\n") == -1) { + free(line); + goto fail; + } + free(line); + + /* Last chunk is 0 bytes followed by an empty newline */ + if ((cre->toread = lval) == 0) { + DPRINTF("relay_read_httpchunks: last chunk"); + + line = evbuffer_readline(src); + if (line == NULL) { + relay_close(con, "invalid last chunk"); + return; + } + free(line); + if (relay_bufferevent_print(cre->dst, "\r\n") == -1) + goto fail; + + /* Switch to HTTP header mode */ + bev->readcb = relay_read_http; + } + } else { + /* Read chunk data */ + if (size > cre->toread) + size = cre->toread; + if (relay_bufferevent_write_chunk(cre->dst, src, size) == -1) + goto fail; + cre->toread -= size; + DPRINTF("relay_read_httpchunks: done, size %d, to read %d", + size, cre->toread); + + if (cre->toread == 0) { + /* Chunk is terminated by an empty (empty) newline */ + line = evbuffer_readline(src); + if (line != NULL) + free(line); + if (relay_bufferevent_print(cre->dst, "\r\n\r\n") == -1) + goto fail; + } + } + + next: + if (con->se_done) + goto done; + if (EVBUFFER_LENGTH(src)) + bev->readcb(bev, arg); + bufferevent_enable(bev, EV_READ); + return; + + done: + relay_close(con, "last http chunk read (done)"); + return; + fail: + relay_close(con, strerror(errno)); +} + +void +relay_http_request_close(struct ctl_relay_event *cre) +{ + if (cre->path != NULL) { + free(cre->path); + cre->path = NULL; + } + + cre->args = NULL; + cre->version = NULL; + + if (cre->buf != NULL) { + free(cre->buf); + cre->buf = NULL; + cre->buflen = 0; + } + + cre->line = 0; + cre->method = 0; + cre->done = 0; + cre->chunked = 0; +} + +void +relay_read_http(struct bufferevent *bev, void *arg) +{ + struct ctl_relay_event *cre = (struct ctl_relay_event *)arg; + struct rsession *con = (struct rsession *)cre->con; + struct relay *rlay = (struct relay *)con->se_relay; + struct protocol *proto = rlay->rl_proto; + struct evbuffer *src = EVBUFFER_INPUT(bev); + struct protonode *pn, pk, *proot, *pnv = NULL, pkv; + char *line; + int header = 0, ret, pass = 0; + const char *errstr; + size_t size; + + if (gettimeofday(&con->se_tv_last, NULL) == -1) + goto fail; + size = EVBUFFER_LENGTH(src); + DPRINTF("relay_read_http: size %d, to read %d", size, cre->toread); + if (!size) { + if (cre->dir == RELAY_DIR_RESPONSE) + return; + cre->toread = 0; + goto done; + } + + pk.type = NODE_TYPE_HEADER; + + while (!cre->done && (line = evbuffer_readline(src)) != NULL) { + /* + * An empty line indicates the end of the request. + * libevent already stripped the \r\n for us. + */ + if (!strlen(line)) { + cre->done = 1; + free(line); + break; + } + pk.key = line; + + /* + * The first line is the GET/POST/PUT/... request, + * subsequent lines are HTTP headers. + */ + if (++cre->line == 1) { + pk.value = strchr(pk.key, ' '); + } else + pk.value = strchr(pk.key, ':'); + if (pk.value == NULL || strlen(pk.value) < 3) { + if (cre->line == 1) { + free(line); + relay_close_http(con, 400, "malformed", 0); + return; + } + + DPRINTF("relay_read_http: request '%s'", line); + /* Append line to the output buffer */ + if (relay_bufferevent_print(cre->dst, line) == -1 || + relay_bufferevent_print(cre->dst, "\r\n") == -1) { + free(line); + goto fail; + } + free(line); + continue; + } + if (*pk.value == ':') { + *pk.value++ = '\0'; + pk.value += strspn(pk.value, " \t\r\n"); + header = 1; + } else { + *pk.value++ = '\0'; + header = 0; + } + + DPRINTF("relay_read_http: header '%s: %s'", pk.key, pk.value); + + /* + * Identify and handle specific HTTP request methods + */ + if (cre->line == 1) { + if (cre->dir == RELAY_DIR_RESPONSE) { + cre->method = HTTP_METHOD_RESPONSE; + goto lookup; + } else if (strcmp("HEAD", pk.key) == 0) + cre->method = HTTP_METHOD_HEAD; + else if (strcmp("POST", pk.key) == 0) + cre->method = HTTP_METHOD_POST; + else if (strcmp("PUT", pk.key) == 0) + cre->method = HTTP_METHOD_PUT; + else if (strcmp("DELETE", pk.key) == 0) + cre->method = HTTP_METHOD_DELETE; + else if (strcmp("OPTIONS", pk.key) == 0) + cre->method = HTTP_METHOD_OPTIONS; + else if (strcmp("TRACE", pk.key) == 0) + cre->method = HTTP_METHOD_TRACE; + else if (strcmp("CONNECT", pk.key) == 0) + cre->method = HTTP_METHOD_CONNECT; + else { + /* Use GET method as the default */ + cre->method = HTTP_METHOD_GET; + } + + /* + * Decode the path and query + */ + cre->path = strdup(pk.value); + if (cre->path == NULL) { + free(line); + goto fail; + } + cre->version = strchr(cre->path, ' '); + if (cre->version != NULL) + *cre->version++ = '\0'; + cre->args = strchr(cre->path, '?'); + if (cre->args != NULL) + *cre->args++ = '\0'; +#ifdef DEBUG + char buf[BUFSIZ]; + if (snprintf(buf, sizeof(buf), " \"%s\"", + cre->path) == -1 || + evbuffer_add(con->se_log, buf, strlen(buf)) == -1) { + free(line); + goto fail; + } +#endif + + /* + * Lookup protocol handlers in the URL path + */ + if ((proto->flags & F_LOOKUP_PATH) == 0) + goto lookup; + + pkv.key = cre->path; + pkv.type = NODE_TYPE_PATH; + pkv.value = cre->args == NULL ? "" : cre->args; + + DPRINTF("relay_read_http: " + "lookup path '%s: %s'", pkv.key, pkv.value); + + if ((proot = RB_FIND(proto_tree, + cre->tree, &pkv)) == NULL) + goto lookup; + + PROTONODE_FOREACH(pnv, proot, entry) { + ret = relay_handle_http(cre, proot, + pnv, &pkv, 0); + if (ret == PN_FAIL) + goto abort; + } + } else if ((cre->method == HTTP_METHOD_POST || + cre->method == HTTP_METHOD_PUT || + cre->method == HTTP_METHOD_RESPONSE) && + strcasecmp("Content-Length", pk.key) == 0) { + /* + * Need to read data from the client after the + * HTTP header. + * XXX What about non-standard clients not using + * the carriage return? And some browsers seem to + * include the line length in the content-length. + */ + cre->toread = strtonum(pk.value, 0, INT_MAX, &errstr); + if (errstr) { + relay_close_http(con, 500, errstr, 0); + goto abort; + } + } + lookup: + if (strcasecmp("Transfer-Encoding", pk.key) == 0 && + strcasecmp("chunked", pk.value) == 0) + cre->chunked = 1; + + /* Match the HTTP header */ + if ((pn = RB_FIND(proto_tree, cre->tree, &pk)) == NULL) + goto next; + + if (cre->dir == RELAY_DIR_RESPONSE) + goto handle; + + if (pn->flags & PNFLAG_LOOKUP_URL) { + /* + * Lookup the URL of type example.com/path?args. + * Either as a plain string or SHA1/MD5 digest. + */ + if ((pn->flags & PNFLAG_LOOKUP_DIGEST(0)) && + relay_lookup_url(cre, pk.value, + DIGEST_NONE) == PN_FAIL) + goto abort; + if ((pn->flags & PNFLAG_LOOKUP_DIGEST(DIGEST_SHA1)) && + relay_lookup_url(cre, pk.value, + DIGEST_SHA1) == PN_FAIL) + goto abort; + if ((pn->flags & PNFLAG_LOOKUP_DIGEST(DIGEST_MD5)) && + relay_lookup_url(cre, pk.value, + DIGEST_MD5) == PN_FAIL) + goto abort; + } else if (pn->flags & PNFLAG_LOOKUP_QUERY) { + /* Lookup the HTTP query arguments */ + if (relay_lookup_query(cre) == PN_FAIL) + goto abort; + } else if (pn->flags & PNFLAG_LOOKUP_COOKIE) { + /* Lookup the HTTP cookie */ + if (relay_lookup_cookie(cre, pk.value) == PN_FAIL) + goto abort; + } + + handle: + pass = 0; + PROTONODE_FOREACH(pnv, pn, entry) { + ret = relay_handle_http(cre, pn, pnv, &pk, header); + if (ret == PN_PASS) + pass = 1; + else if (ret == PN_FAIL) + goto abort; + } + + if (pass) { + next: + if (relay_bufferevent_print(cre->dst, pk.key) == -1 || + relay_bufferevent_print(cre->dst, + header ? ": " : " ") == -1 || + relay_bufferevent_print(cre->dst, pk.value) == -1 || + relay_bufferevent_print(cre->dst, "\r\n") == -1) { + free(line); + goto fail; + } + } + free(line); + } + if (cre->done) { + RB_FOREACH(proot, proto_tree, cre->tree) { + PROTONODE_FOREACH(pn, proot, entry) + if (relay_resolve(cre, proot, pn) != 0) + return; + } + + switch (cre->method) { + case HTTP_METHOD_NONE: + relay_close_http(con, 406, "no method", 0); + return; + case HTTP_METHOD_CONNECT: + /* Data stream */ + bev->readcb = relay_read; + break; + case HTTP_METHOD_POST: + case HTTP_METHOD_PUT: + case HTTP_METHOD_RESPONSE: + /* HTTP request payload */ + if (cre->toread) { + bev->readcb = relay_read_httpcontent; + break; + } + + /* Single-pass HTTP response */ + bev->readcb = relay_read; + break; + default: + /* HTTP handler */ + bev->readcb = relay_read_http; + break; + } + if (cre->chunked) { + /* Chunked transfer encoding */ + cre->toread = 0; + bev->readcb = relay_read_httpchunks; + } + + /* Write empty newline and switch to relay mode */ + if (relay_bufferevent_print(cre->dst, "\r\n") == -1) + goto fail; + + relay_http_request_close(cre); + + done: + if (cre->dir == RELAY_DIR_REQUEST && !cre->toread && + proto->lateconnect && cre->dst->bev == NULL) { + if (rlay->rl_conf.fwdmode == FWD_TRANS) { + relay_bindanyreq(con, 0, IPPROTO_TCP); + return; + } + if (relay_connect(con) == -1) + relay_close_http(con, 502, "session failed", 0); + return; + } + } + if (con->se_done) { + relay_close(con, "last http read (done)"); + return; + } + if (EVBUFFER_LENGTH(src) && bev->readcb != relay_read_http) + bev->readcb(bev, arg); + bufferevent_enable(bev, EV_READ); + return; + fail: + relay_close_http(con, 500, strerror(errno), 0); + return; + abort: + free(line); +} + +static int +_relay_lookup_url(struct ctl_relay_event *cre, char *host, char *path, + char *query, enum digest_type type) +{ + struct rsession *con = (struct rsession *)cre->con; + struct protonode *proot, *pnv, pkv; + char *val, *md = NULL; + int ret = PN_FAIL; + + if (asprintf(&val, "%s%s%s%s", + host, path, + query == NULL ? "" : "?", + query == NULL ? "" : query) == -1) { + relay_close_http(con, 500, "failed to allocate URL", 0); + return (PN_FAIL); + } + + DPRINTF("_relay_lookup_url: %s", val); + + switch (type) { + case DIGEST_SHA1: + case DIGEST_MD5: +#ifdef __FreeBSD__ + if ((md = digeststr(type, (u_int8_t*)val, strlen(val), NULL)) == NULL) { +#else + if ((md = digeststr(type, val, strlen(val), NULL)) == NULL) { +#endif + relay_close_http(con, 500, + "failed to allocate digest", 0); + goto fail; + } + pkv.key = md; + break; + case DIGEST_NONE: + pkv.key = val; + break; + } + pkv.type = NODE_TYPE_URL; + pkv.value = ""; + + if ((proot = RB_FIND(proto_tree, cre->tree, &pkv)) == NULL) + goto done; + + PROTONODE_FOREACH(pnv, proot, entry) { + ret = relay_handle_http(cre, proot, pnv, &pkv, 0); + if (ret == PN_FAIL) + goto fail; + } + + done: + ret = PN_PASS; + fail: + if (md != NULL) + free(md); + free(val); + return (ret); +} + +int +relay_lookup_url(struct ctl_relay_event *cre, const char *str, + enum digest_type type) +{ + struct rsession *con = (struct rsession *)cre->con; + int i, j, dots; + char *hi[RELAY_MAXLOOKUPLEVELS], *p, *pp, *c, ch; + char ph[MAXHOSTNAMELEN]; + int ret; + + if (cre->path == NULL) + return (PN_PASS); + + /* + * This is an URL lookup algorithm inspired by + * http://code.google.com/apis/safebrowsing/ + * developers_guide.html#PerformingLookups + */ + + DPRINTF("relay_lookup_url: host: '%s', path: '%s', query: '%s'", + str, cre->path, cre->args == NULL ? "" : cre->args); + + if (canonicalize_host(str, ph, sizeof(ph)) == NULL) { + relay_close_http(con, 400, "invalid host name", 0); + return (PN_FAIL); + } + + bzero(hi, sizeof(hi)); + for (dots = -1, i = strlen(ph) - 1; i > 0; i--) { + if (ph[i] == '.' && ++dots) + hi[dots - 1] = &ph[i + 1]; + if (dots > (RELAY_MAXLOOKUPLEVELS - 2)) + break; + } + if (dots == -1) + dots = 0; + hi[dots] = ph; + + if ((pp = strdup(cre->path)) == NULL) { + relay_close_http(con, 500, "failed to allocate path", 0); + return (PN_FAIL); + } + for (i = (RELAY_MAXLOOKUPLEVELS - 1); i >= 0; i--) { + if (hi[i] == NULL) + continue; + + /* 1. complete path with query */ + if (cre->args != NULL) + if ((ret = _relay_lookup_url(cre, hi[i], + pp, cre->args, type)) != PN_PASS) + goto done; + + /* 2. complete path without query */ + if ((ret = _relay_lookup_url(cre, hi[i], + pp, NULL, type)) != PN_PASS) + goto done; + + /* 3. traverse path */ + for (j = 0, p = strchr(pp, '/'); + p != NULL; p = strchr(p, '/'), j++) { + if (j > (RELAY_MAXLOOKUPLEVELS - 2) || ++p == '\0') + break; + c = &pp[p - pp]; + ch = *c; + *c = '\0'; + if ((ret = _relay_lookup_url(cre, hi[i], + pp, NULL, type)) != PN_PASS) + goto done; + *c = ch; + } + } + + ret = PN_PASS; + done: + free(pp); + return (ret); +} + +int +relay_lookup_query(struct ctl_relay_event *cre) +{ + struct rsession *con = (struct rsession *)cre->con; + struct protonode *proot, *pnv, pkv; + char *val, *ptr; + int ret; + + if (cre->path == NULL || cre->args == NULL || strlen(cre->args) < 2) + return (PN_PASS); + if ((val = strdup(cre->args)) == NULL) { + relay_close_http(con, 500, "failed to allocate query", 0); + return (PN_FAIL); + } + + ptr = val; + while (ptr != NULL && strlen(ptr)) { + pkv.key = ptr; + pkv.type = NODE_TYPE_QUERY; + if ((ptr = strchr(ptr, '&')) != NULL) + *ptr++ = '\0'; + if ((pkv.value = + strchr(pkv.key, '=')) == NULL || + strlen(pkv.value) < 1) + continue; + *pkv.value++ = '\0'; + + if ((proot = RB_FIND(proto_tree, cre->tree, &pkv)) == NULL) + continue; + PROTONODE_FOREACH(pnv, proot, entry) { + ret = relay_handle_http(cre, proot, + pnv, &pkv, 0); + if (ret == PN_FAIL) + goto done; + } + } + + ret = PN_PASS; + done: + free(val); + return (ret); +} + +int +relay_lookup_cookie(struct ctl_relay_event *cre, const char *str) +{ + struct rsession *con = (struct rsession *)cre->con; + struct protonode *proot, *pnv, pkv; + char *val, *ptr; + int ret; + + if ((val = strdup(str)) == NULL) { + relay_close_http(con, 500, "failed to allocate cookie", 0); + return (PN_FAIL); + } + + for (ptr = val; ptr != NULL && strlen(ptr);) { + if (*ptr == ' ') + *ptr++ = '\0'; + pkv.key = ptr; + pkv.type = NODE_TYPE_COOKIE; + if ((ptr = strchr(ptr, ';')) != NULL) + *ptr++ = '\0'; + /* + * XXX We do not handle attributes + * ($Path, $Domain, or $Port) + */ + if (*pkv.key == '$') + continue; + + if ((pkv.value = + strchr(pkv.key, '=')) == NULL || + strlen(pkv.value) < 1) + continue; + *pkv.value++ = '\0'; + if (*pkv.value == '"') + *pkv.value++ = '\0'; + if (pkv.value[strlen(pkv.value) - 1] == '"') + pkv.value[strlen(pkv.value) - 1] = '\0'; + if ((proot = RB_FIND(proto_tree, cre->tree, &pkv)) == NULL) + continue; + PROTONODE_FOREACH(pnv, proot, entry) { + ret = relay_handle_http(cre, proot, pnv, &pkv, 0); + if (ret == PN_FAIL) + goto done; + } + } + + ret = PN_PASS; + done: + free(val); + return (ret); +} + +void +relay_close_http(struct rsession *con, u_int code, const char *msg, + u_int16_t labelid) +{ + struct relay *rlay = (struct relay *)con->se_relay; + struct bufferevent *bev = con->se_in.bev; + const char *httperr = print_httperror(code), *text = ""; + char *httpmsg; + time_t t; + struct tm *lt; + char tmbuf[32], hbuf[128]; + const char *style, *label = NULL; + + /* In some cases this function may be called from generic places */ + if (rlay->rl_proto->type != RELAY_PROTO_HTTP || + (rlay->rl_proto->flags & F_RETURN) == 0) { + relay_close(con, msg); + return; + } + + if (bev == NULL) + goto done; + + /* Some system information */ + if (print_host(&rlay->rl_conf.ss, hbuf, sizeof(hbuf)) == NULL) + goto done; + + /* RFC 2616 "tolerates" asctime() */ + time(&t); + lt = localtime(&t); + tmbuf[0] = '\0'; + if (asctime_r(lt, tmbuf) != NULL) + tmbuf[strlen(tmbuf) - 1] = '\0'; /* skip final '\n' */ + + /* Do not send details of the Internal Server Error */ + if (code != 500) + text = msg; + if (labelid != 0) + label = pn_id2name(labelid); + + /* A CSS stylesheet allows minimal customization by the user */ + if ((style = rlay->rl_proto->style) == NULL) + style = "body { background-color: #a00000; color: white; }"; + + /* Generate simple HTTP+HTML error document */ + if (asprintf(&httpmsg, + "HTTP/1.x %03d %s\r\n" + "Date: %s\r\n" + "Server: %s\r\n" + "Connection: close\r\n" + "Content-Type: text/html\r\n" + "\r\n" + "\n" + "\n" + "\n" + "%03d %s\n" + "\n" + "\n" + "\n" + "

%s

\n" + "
%s
\n" + "
%s
\n" + "
%s at %s port %d
\n" + "\n" + "\n", + code, httperr, tmbuf, RELAYD_SERVERNAME, + code, httperr, style, httperr, text, + label == NULL ? "" : label, + RELAYD_SERVERNAME, hbuf, ntohs(rlay->rl_conf.port)) == -1) + goto done; + + /* Dump the message without checking for success */ + relay_dump(&con->se_in, httpmsg, strlen(httpmsg)); + free(httpmsg); + + done: + if (asprintf(&httpmsg, "%s (%03d %s)", msg, code, httperr) == -1) + relay_close(con, msg); + else { + relay_close(con, httpmsg); + free(httpmsg); + } +} + +void +relay_error(struct bufferevent *bev, short error, void *arg) +{ + struct ctl_relay_event *cre = (struct ctl_relay_event *)arg; + struct rsession *con = (struct rsession *)cre->con; + struct evbuffer *dst; + + if (error & EVBUFFER_TIMEOUT) { + relay_close(con, "buffer event timeout"); + return; + } + if (error & (EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF)) { + bufferevent_disable(bev, EV_READ|EV_WRITE); + + con->se_done = 1; + if (cre->dst->bev != NULL) { + dst = EVBUFFER_OUTPUT(cre->dst->bev); + if (EVBUFFER_LENGTH(dst)) + return; + } + + relay_close(con, "done"); + return; + } + relay_close(con, "buffer event error"); +} + +void +relay_accept(int fd, short sig, void *arg) +{ + struct relay *rlay = (struct relay *)arg; + struct protocol *proto = rlay->rl_proto; + struct rsession *con = NULL; + struct ctl_natlook *cnl = NULL; + socklen_t slen; + struct timeval tv; + struct sockaddr_storage ss; + int s = -1; + + slen = sizeof(ss); + if ((s = accept(fd, (struct sockaddr *)&ss, (socklen_t *)&slen)) == -1) + return; + + if (relay_sessions >= RELAY_MAX_SESSIONS || + rlay->rl_conf.flags & F_DISABLE) + goto err; + + if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) + goto err; + + if ((con = (struct rsession *) + calloc(1, sizeof(struct rsession))) == NULL) + goto err; + + con->se_in.s = s; + con->se_in.ssl = NULL; + con->se_out.s = -1; + con->se_out.ssl = NULL; + con->se_in.dst = &con->se_out; + con->se_out.dst = &con->se_in; + con->se_in.con = con; + con->se_out.con = con; + con->se_relay = rlay; + con->se_id = ++relay_conid; + con->se_relayid = rlay->rl_conf.id; + con->se_hashkey = rlay->rl_dstkey; + con->se_in.tree = &proto->request_tree; + con->se_out.tree = &proto->response_tree; + con->se_in.dir = RELAY_DIR_REQUEST; + con->se_out.dir = RELAY_DIR_RESPONSE; + con->se_retry = rlay->rl_conf.dstretry; + con->se_bnds = -1; + if (gettimeofday(&con->se_tv_start, NULL) == -1) + goto err; + bcopy(&con->se_tv_start, &con->se_tv_last, sizeof(con->se_tv_last)); + bcopy(&ss, &con->se_in.ss, sizeof(con->se_in.ss)); + con->se_out.port = rlay->rl_conf.dstport; + switch (ss.ss_family) { + case AF_INET: + con->se_in.port = ((struct sockaddr_in *)&ss)->sin_port; + break; + case AF_INET6: + con->se_in.port = ((struct sockaddr_in6 *)&ss)->sin6_port; + break; + } + + relay_sessions++; + SPLAY_INSERT(session_tree, &rlay->rl_sessions, con); + + /* Increment the per-relay session counter */ + rlay->rl_stats[proc_id].last++; + + /* Pre-allocate output buffer */ + con->se_out.output = evbuffer_new(); + if (con->se_out.output == NULL) { + relay_close(con, "failed to allocate output buffer"); + return; + } + + /* Pre-allocate log buffer */ + con->se_log = evbuffer_new(); + if (con->se_log == NULL) { + relay_close(con, "failed to allocate log buffer"); + return; + } + + if (rlay->rl_conf.flags & F_NATLOOK) { + if ((cnl = (struct ctl_natlook *) + calloc(1, sizeof(struct ctl_natlook))) == NULL) { + relay_close(con, "failed to allocate nat lookup"); + return; + } + } + + if (rlay->rl_conf.flags & F_NATLOOK && cnl != NULL) { + con->se_cnl = cnl; + bzero(cnl, sizeof(*cnl)); + cnl->in = -1; + cnl->id = con->se_id; + cnl->proc = proc_id; + cnl->proto = IPPROTO_TCP; + + bcopy(&con->se_in.ss, &cnl->src, sizeof(cnl->src)); + slen = sizeof(cnl->dst); + if (getsockname(s, + (struct sockaddr *)&cnl->dst, &slen) == -1) { + relay_close(con, "failed to get local address"); + return; + } + + imsg_compose_event(iev_pfe, IMSG_NATLOOK, 0, 0, -1, cnl, + sizeof(*cnl)); + + /* Schedule timeout */ + evtimer_set(&con->se_ev, relay_natlook, con); + bcopy(&rlay->rl_conf.timeout, &tv, sizeof(tv)); + evtimer_add(&con->se_ev, &tv); + return; + } + + relay_session(con); + return; + err: + if (s != -1) { + close(s); + if (con != NULL) + free(con); + } +} + +u_int32_t +relay_hash_addr(struct sockaddr_storage *ss, u_int32_t p) +{ + struct sockaddr_in *sin4; + struct sockaddr_in6 *sin6; + + if (ss->ss_family == AF_INET) { + sin4 = (struct sockaddr_in *)ss; + p = hash32_buf(&sin4->sin_addr, + sizeof(struct in_addr), p); + } else { + sin6 = (struct sockaddr_in6 *)ss; + p = hash32_buf(&sin6->sin6_addr, + sizeof(struct in6_addr), p); + } + + return (p); +} + +int +relay_from_table(struct rsession *con) +{ + struct relay *rlay = (struct relay *)con->se_relay; + struct host *host; + struct table *table = rlay->rl_dsttable; + u_int32_t p = con->se_hashkey; + int idx = 0; + + if (table->conf.check && !table->up) { + log_debug("relay_from_table: no active hosts"); + return (-1); + } + + switch (rlay->rl_conf.dstmode) { + case RELAY_DSTMODE_ROUNDROBIN: + if ((int)rlay->rl_dstkey >= rlay->rl_dstnhosts) + rlay->rl_dstkey = 0; + idx = (int)rlay->rl_dstkey; + break; + case RELAY_DSTMODE_LOADBALANCE: + p = relay_hash_addr(&con->se_in.ss, p); + /* FALLTHROUGH */ + case RELAY_DSTMODE_HASH: + p = relay_hash_addr(&rlay->rl_conf.ss, p); + p = hash32_buf(&rlay->rl_conf.port, + sizeof(rlay->rl_conf.port), p); + if ((idx = p % rlay->rl_dstnhosts) >= RELAY_MAXHOSTS) + return (-1); + } + host = rlay->rl_dsthost[idx]; + DPRINTF("relay_from_table: host %s, p 0x%08x, idx %d", + host->conf.name, p, idx); + while (host != NULL) { + DPRINTF("relay_from_table: host %s", host->conf.name); + if (!table->conf.check || host->up == HOST_UP) + goto found; + host = TAILQ_NEXT(host, entry); + } + TAILQ_FOREACH(host, &table->hosts, entry) { + DPRINTF("relay_from_table: next host %s", host->conf.name); + if (!table->conf.check || host->up == HOST_UP) + goto found; + } + + /* Should not happen */ + fatalx("relay_from_table: no active hosts, desynchronized"); + + found: + if (rlay->rl_conf.dstmode == RELAY_DSTMODE_ROUNDROBIN) + rlay->rl_dstkey = host->idx + 1; + con->se_retry = host->conf.retry; + con->se_out.port = table->conf.port; + bcopy(&host->conf.ss, &con->se_out.ss, sizeof(con->se_out.ss)); + + return (0); +} + +void +relay_natlook(int fd, short event, void *arg) +{ + struct rsession *con = (struct rsession *)arg; + struct relay *rlay = (struct relay *)con->se_relay; + struct ctl_natlook *cnl = con->se_cnl; + + if (cnl == NULL) + fatalx("invalid NAT lookup"); + + if (con->se_out.ss.ss_family == AF_UNSPEC && cnl->in == -1 && + rlay->rl_conf.dstss.ss_family == AF_UNSPEC && + rlay->rl_dsttable == NULL) { + relay_close(con, "session NAT lookup failed"); + return; + } + if (cnl->in != -1) { + bcopy(&cnl->rdst, &con->se_out.ss, sizeof(con->se_out.ss)); + con->se_out.port = cnl->rdport; + } + free(con->se_cnl); + con->se_cnl = NULL; + + relay_session(con); +} + +void +relay_session(struct rsession *con) +{ + struct relay *rlay = (struct relay *)con->se_relay; + struct ctl_relay_event *in = &con->se_in, *out = &con->se_out; + + if (bcmp(&rlay->rl_conf.ss, &out->ss, sizeof(out->ss)) == 0 && + out->port == rlay->rl_conf.port) { + log_debug("relay_session: session %d: looping", + con->se_id); + relay_close(con, "session aborted"); + return; + } + + if (rlay->rl_conf.flags & F_UDP) { + /* + * Call the UDP protocol-specific handler + */ + if (rlay->rl_proto->request == NULL) + fatalx("invalide UDP session"); + if ((*rlay->rl_proto->request)(con) == -1) + relay_close(con, "session failed"); + return; + } + + if ((rlay->rl_conf.flags & F_SSL) && (in->ssl == NULL)) { + relay_ssl_transaction(con, in); + return; + } + + if (!rlay->rl_proto->lateconnect) { + if (rlay->rl_conf.fwdmode == FWD_TRANS) + relay_bindanyreq(con, 0, IPPROTO_TCP); + else if (relay_connect(con) == -1) { + relay_close(con, "session failed"); + return; + } + } + + relay_input(con); +} + +void +relay_bindanyreq(struct rsession *con, in_port_t port, int proto) +{ + struct relay *rlay = (struct relay *)con->se_relay; + struct ctl_bindany bnd; + struct timeval tv; + + bzero(&bnd, sizeof(bnd)); + bnd.bnd_id = con->se_id; + bnd.bnd_proc = proc_id; + bnd.bnd_port = port; + bnd.bnd_proto = proto; + bcopy(&con->se_in.ss, &bnd.bnd_ss, sizeof(bnd.bnd_ss)); + imsg_compose_event(iev_main, IMSG_BINDANY, + 0, 0, -1, &bnd, sizeof(bnd)); + + /* Schedule timeout */ + evtimer_set(&con->se_ev, relay_bindany, con); + bcopy(&rlay->rl_conf.timeout, &tv, sizeof(tv)); + evtimer_add(&con->se_ev, &tv); +} + +void +relay_bindany(int fd, short event, void *arg) +{ + struct rsession *con = (struct rsession *)arg; + + if (con->se_bnds == -1) { + relay_close(con, "bindany failed, invalid socket"); + return; + } + + if (relay_connect((struct rsession *)con) == -1) + relay_close(con, "session failed"); +} + +int +relay_connect(struct rsession *con) +{ + struct relay *rlay = (struct relay *)con->se_relay; + int bnds = -1, ret; + + if (gettimeofday(&con->se_tv_start, NULL) == -1) + return (-1); + + if (rlay->rl_dsttable != NULL) { + if (relay_from_table(con) != 0) + return (-1); + } else if (con->se_out.ss.ss_family == AF_UNSPEC) { + bcopy(&rlay->rl_conf.dstss, &con->se_out.ss, + sizeof(con->se_out.ss)); + con->se_out.port = rlay->rl_conf.dstport; + } + + if (rlay->rl_conf.fwdmode == FWD_TRANS) { + if (con->se_bnds == -1) { + log_debug("relay_connect: could not bind any sock"); + return (-1); + } + bnds = con->se_bnds; + } + + /* Do the IPv4-to-IPv6 or IPv6-to-IPv4 translation if requested */ + if (rlay->rl_conf.dstaf.ss_family != AF_UNSPEC) { + if (con->se_out.ss.ss_family == AF_INET && + rlay->rl_conf.dstaf.ss_family == AF_INET6) + ret = map4to6(&con->se_out.ss, &rlay->rl_conf.dstaf); + else if (con->se_out.ss.ss_family == AF_INET6 && + rlay->rl_conf.dstaf.ss_family == AF_INET) + ret = map6to4(&con->se_out.ss); + else + ret = 0; + if (ret != 0) { + log_debug("relay_connect: mapped to invalid address"); + return (-1); + } + } + + retry: + if ((con->se_out.s = relay_socket_connect(&con->se_out.ss, + con->se_out.port, rlay->rl_proto, bnds)) == -1) { + if (con->se_retry) { + con->se_retry--; + log_debug("relay_connect: session %d: " + "forward failed: %s, %s", + con->se_id, strerror(errno), + con->se_retry ? "next retry" : "last retry"); + goto retry; + } + log_debug("relay_connect: session %d: forward failed: %s", + con->se_id, strerror(errno)); + return (-1); + } + + if (errno == EINPROGRESS) + event_again(&con->se_ev, con->se_out.s, EV_WRITE|EV_TIMEOUT, + relay_connected, &con->se_tv_start, &env->sc_timeout, con); + else + relay_connected(con->se_out.s, EV_WRITE, con); + + return (0); +} + +void +relay_close(struct rsession *con, const char *msg) +{ + struct relay *rlay = (struct relay *)con->se_relay; + char ibuf[128], obuf[128], *ptr = NULL; + + SPLAY_REMOVE(session_tree, &rlay->rl_sessions, con); + + event_del(&con->se_ev); + if (con->se_in.bev != NULL) + bufferevent_disable(con->se_in.bev, EV_READ|EV_WRITE); + if (con->se_out.bev != NULL) + bufferevent_disable(con->se_out.bev, EV_READ|EV_WRITE); + + if (env->sc_opts & RELAYD_OPT_LOGUPDATE) { + bzero(&ibuf, sizeof(ibuf)); + bzero(&obuf, sizeof(obuf)); + (void)print_host(&con->se_in.ss, ibuf, sizeof(ibuf)); + (void)print_host(&con->se_out.ss, obuf, sizeof(obuf)); + if (EVBUFFER_LENGTH(con->se_log) && + evbuffer_add_printf(con->se_log, "\r\n") != -1) + ptr = evbuffer_readline(con->se_log); + log_info("relay %s, session %d (%d active), %d, %s -> %s:%d, " + "%s%s%s", rlay->rl_conf.name, con->se_id, relay_sessions, + con->se_mark, ibuf, obuf, ntohs(con->se_out.port), msg, + ptr == NULL ? "" : ",", ptr == NULL ? "" : ptr); + if (ptr != NULL) + free(ptr); + } + + if (con->se_priv != NULL) + free(con->se_priv); + if (con->se_in.bev != NULL) + bufferevent_free(con->se_in.bev); + else if (con->se_in.output != NULL) + evbuffer_free(con->se_in.output); + if (con->se_in.ssl != NULL) { + /* XXX handle non-blocking shutdown */ + if (SSL_shutdown(con->se_in.ssl) == 0) + SSL_shutdown(con->se_in.ssl); + SSL_free(con->se_in.ssl); + } + if (con->se_in.s != -1) + close(con->se_in.s); + if (con->se_in.path != NULL) + free(con->se_in.path); + if (con->se_in.buf != NULL) + free(con->se_in.buf); + if (con->se_in.nodes != NULL) + free(con->se_in.nodes); + + if (con->se_out.bev != NULL) + bufferevent_free(con->se_out.bev); + else if (con->se_out.output != NULL) + evbuffer_free(con->se_out.output); + if (con->se_out.s != -1) + close(con->se_out.s); + if (con->se_out.path != NULL) + free(con->se_out.path); + if (con->se_out.buf != NULL) + free(con->se_out.buf); + if (con->se_out.nodes != NULL) + free(con->se_out.nodes); + + if (con->se_log != NULL) + evbuffer_free(con->se_log); + + if (con->se_cnl != NULL) { +#if 0 + imsg_compose_event(iev_pfe, IMSG_KILLSTATES, 0, 0, -1, + cnl, sizeof(*cnl)); +#endif + free(con->se_cnl); + } + + free(con); + relay_sessions--; +} + +void +relay_dispatch_pfe(int fd, short event, void *ptr) +{ + struct imsgev *iev; + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + struct relay *rlay; + struct rsession *con; + struct ctl_natlook cnl; + struct timeval tv; + struct host *host; + struct table *table; + struct ctl_status st; + objid_t id; + + iev = ptr; + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1) + fatal("relay_dispatch_pfe: imsg_read_error"); + if (n == 0) { + /* this pipe is dead, so remove the event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + return; + } + } + + if (event & EV_WRITE) { + if (msgbuf_write(&ibuf->w) == -1) + fatal("relay_dispatch_pfe: msgbuf_write"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("relay_dispatch_pfe: 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("relay_dispatch_pfe: desynchronized"); + if ((table = table_find(env, host->conf.tableid)) == + NULL) + fatalx("relay_dispatch_pfe: invalid table id"); + if (host->up == HOST_UP) + table->up--; + 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("relay_dispatch_pfe: desynchronized"); + host->flags &= ~(F_DISABLE); + host->up = HOST_UNKNOWN; + break; + case IMSG_HOST_STATUS: + if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(st)) + fatalx("relay_dispatch_pfe: invalid request"); + memcpy(&st, imsg.data, sizeof(st)); + if ((host = host_find(env, st.id)) == NULL) + fatalx("relay_dispatch_pfe: invalid host id"); + if (host->flags & F_DISABLE) + break; + if (host->up == st.up) { + log_debug("relay_dispatch_pfe: host %d => %d", + host->conf.id, host->up); + fatalx("relay_dispatch_pfe: desynchronized"); + } + + if ((table = table_find(env, host->conf.tableid)) + == NULL) + fatalx("relay_dispatch_pfe: invalid table id"); + + DPRINTF("relay_dispatch_pfe: [%d] state %d for " + "host %u %s", proc_id, st.up, + host->conf.id, host->conf.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->up++; + else + table->up--; + host->up = st.up; + break; + case IMSG_NATLOOK: + bcopy(imsg.data, &cnl, sizeof(cnl)); + if ((con = session_find(env, cnl.id)) == NULL || + con->se_cnl == NULL) { + log_debug("relay_dispatch_pfe: " + "session expired"); + break; + } + bcopy(&cnl, con->se_cnl, sizeof(*con->se_cnl)); + evtimer_del(&con->se_ev); + evtimer_set(&con->se_ev, relay_natlook, con); + bzero(&tv, sizeof(tv)); + evtimer_add(&con->se_ev, &tv); + break; + case IMSG_CTL_SESSION: + TAILQ_FOREACH(rlay, env->sc_relays, rl_entry) + SPLAY_FOREACH(con, session_tree, + &rlay->rl_sessions) + imsg_compose_event(iev, + IMSG_CTL_SESSION, + 0, 0, -1, con, sizeof(*con)); + imsg_compose_event(iev, IMSG_CTL_END, + 0, 0, -1, NULL, 0); + break; + default: + log_debug("relay_dispatch_msg: unexpected imsg %d", + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + imsg_event_add(iev); +} + +void +relay_dispatch_parent(int fd, short event, void * ptr) +{ + struct rsession *con; + struct imsgev *iev; + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + struct timeval tv; + objid_t id; + + iev = ptr; + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1) + fatal("relay_dispatch_parent: imsg_read error"); + if (n == 0) { + /* this pipe is dead, so remove the event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + return; + } + } + + if (event & EV_WRITE) { + if (msgbuf_write(&ibuf->w) == -1) + fatal("relay_dispatch_parent: msgbuf_write"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("relay_dispatch_parent: imsg_read error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_BINDANY: + bcopy(imsg.data, &id, sizeof(id)); + if ((con = session_find(env, id)) == NULL) { + log_debug("relay_dispatch_parent: " + "session expired"); + break; + } + + /* Will validate the result later */ + con->se_bnds = imsg.fd; + + evtimer_del(&con->se_ev); + evtimer_set(&con->se_ev, relay_bindany, con); + bzero(&tv, sizeof(tv)); + evtimer_add(&con->se_ev, &tv); + break; + default: + log_debug("relay_dispatch_parent: unexpected imsg %d", + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + imsg_event_add(iev); +} + +SSL_CTX * +relay_ssl_ctx_create(struct relay *rlay) +{ + struct protocol *proto = rlay->rl_proto; + SSL_CTX *ctx; + + ctx = SSL_CTX_new(SSLv23_method()); + if (ctx == NULL) + goto err; + + /* Modify session timeout and cache size*/ + SSL_CTX_set_timeout(ctx, rlay->rl_conf.timeout.tv_sec); + if (proto->cache < -1) { + SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); + } else if (proto->cache >= -1) { + SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER); + if (proto->cache >= 0) + SSL_CTX_sess_set_cache_size(ctx, proto->cache); + } + + /* Enable all workarounds and set SSL options */ + SSL_CTX_set_options(ctx, SSL_OP_ALL); + SSL_CTX_set_options(ctx, + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + /* Set the allowed SSL protocols */ + if ((proto->sslflags & SSLFLAG_SSLV2) == 0) + SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2); + if ((proto->sslflags & SSLFLAG_SSLV3) == 0) + SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3); + if ((proto->sslflags & SSLFLAG_TLSV1) == 0) + SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1); + + if (!SSL_CTX_set_cipher_list(ctx, proto->sslciphers)) + goto err; + + /* Verify the server certificate if we have a CA chain */ + if ((rlay->rl_conf.flags & F_SSLCLIENT) && + (rlay->rl_ssl_ca != NULL)) { + if (!ssl_ctx_load_verify_memory(ctx, + rlay->rl_ssl_ca, rlay->rl_ssl_ca_len)) + goto err; + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); + } + + if ((rlay->rl_conf.flags & F_SSL) == 0) + return (ctx); + + log_debug("relay_ssl_ctx_create: loading certificate"); + if (!ssl_ctx_use_certificate_chain(ctx, + rlay->rl_ssl_cert, rlay->rl_ssl_cert_len)) + goto err; + + log_debug("relay_ssl_ctx_create: loading private key"); + if (!ssl_ctx_use_private_key(ctx, rlay->rl_ssl_key, + rlay->rl_ssl_key_len)) + goto err; + if (!SSL_CTX_check_private_key(ctx)) + goto err; + + /* Set session context to the local relay name */ + if (!SSL_CTX_set_session_id_context(ctx, +#ifdef __FreeBSD__ + (unsigned char*)rlay->rl_conf.name, strlen(rlay->rl_conf.name))) +#else + rlay->rl_conf.name, strlen(rlay->rl_conf.name))) +#endif + goto err; + + return (ctx); + + err: + if (ctx != NULL) + SSL_CTX_free(ctx); + ssl_error(rlay->rl_conf.name, "relay_ssl_ctx_create"); + return (NULL); +} + +void +relay_ssl_transaction(struct rsession *con, struct ctl_relay_event *cre) +{ + struct relay *rlay = (struct relay *)con->se_relay; + SSL *ssl; + SSL_METHOD *method; + void (*cb)(int, short, void *); + u_int flags = EV_TIMEOUT; + + ssl = SSL_new(rlay->rl_ssl_ctx); + if (ssl == NULL) + goto err; + + if (cre->dir == RELAY_DIR_REQUEST) { + cb = relay_ssl_accept; + method = SSLv23_server_method(); + flags |= EV_READ; + } else { + cb = relay_ssl_connect; + method = SSLv23_client_method(); + flags |= EV_WRITE; + } + + if (!SSL_set_ssl_method(ssl, method)) + goto err; + if (!SSL_set_fd(ssl, cre->s)) + goto err; + + if (cre->dir == RELAY_DIR_REQUEST) + SSL_set_accept_state(ssl); + else + SSL_set_connect_state(ssl); + + cre->ssl = ssl; + + event_again(&con->se_ev, cre->s, EV_TIMEOUT|flags, + cb, &con->se_tv_start, &env->sc_timeout, con); + return; + + err: + if (ssl != NULL) + SSL_free(ssl); + ssl_error(rlay->rl_conf.name, "relay_ssl_transaction"); + relay_close(con, "session ssl failed"); +} + +void +relay_ssl_accept(int fd, short event, void *arg) +{ + struct rsession *con = (struct rsession *)arg; + struct relay *rlay = (struct relay *)con->se_relay; + int ret; + int ssl_err; + int retry_flag; + + if (event == EV_TIMEOUT) { + relay_close(con, "SSL accept timeout"); + return; + } + + retry_flag = ssl_err = 0; + + ret = SSL_accept(con->se_in.ssl); + if (ret <= 0) { + ssl_err = SSL_get_error(con->se_in.ssl, ret); + + switch (ssl_err) { + case SSL_ERROR_WANT_READ: + retry_flag = EV_READ; + goto retry; + case SSL_ERROR_WANT_WRITE: + retry_flag = EV_WRITE; + goto retry; + case SSL_ERROR_ZERO_RETURN: + case SSL_ERROR_SYSCALL: + if (ret == 0) { + relay_close(con, "closed"); + return; + } + /* FALLTHROUGH */ + default: + ssl_error(rlay->rl_conf.name, "relay_ssl_accept"); + relay_close(con, "SSL accept error"); + return; + } + } + + +#ifdef DEBUG + log_info("relay %s, session %d established (%d active)", + rlay->rl_conf.name, con->se_id, relay_sessions); +#else + log_debug("relay %s, session %d established (%d active)", + rlay->rl_conf.name, con->se_id, relay_sessions); +#endif + relay_session(con); + return; + +retry: + DPRINTF("relay_ssl_accept: session %d: scheduling on %s", con->se_id, + (retry_flag == EV_READ) ? "EV_READ" : "EV_WRITE"); + event_again(&con->se_ev, fd, EV_TIMEOUT|retry_flag, relay_ssl_accept, + &con->se_tv_start, &env->sc_timeout, con); +} + +void +relay_ssl_connect(int fd, short event, void *arg) +{ + struct rsession *con = (struct rsession *)arg; + struct relay *rlay = (struct relay *)con->se_relay; + int ret; + int ssl_err; + int retry_flag; + + if (event == EV_TIMEOUT) { + relay_close(con, "SSL connect timeout"); + return; + } + + retry_flag = ssl_err = 0; + + ret = SSL_connect(con->se_out.ssl); + if (ret <= 0) { + ssl_err = SSL_get_error(con->se_out.ssl, ret); + + switch (ssl_err) { + case SSL_ERROR_WANT_READ: + retry_flag = EV_READ; + goto retry; + case SSL_ERROR_WANT_WRITE: + retry_flag = EV_WRITE; + goto retry; + case SSL_ERROR_ZERO_RETURN: + case SSL_ERROR_SYSCALL: + if (ret == 0) { + relay_close(con, "closed"); + return; + } + /* FALLTHROUGH */ + default: + ssl_error(rlay->rl_conf.name, "relay_ssl_connect"); + relay_close(con, "SSL connect error"); + return; + } + } + +#ifdef DEBUG + log_info("relay %s, session %d connected (%d active)", + rlay->rl_conf.name, con->se_id, relay_sessions); +#else + log_debug("relay %s, session %d connected (%d active)", + rlay->rl_conf.name, con->se_id, relay_sessions); +#endif + relay_connected(fd, EV_WRITE, con); + return; + +retry: + DPRINTF("relay_ssl_connect: session %d: scheduling on %s", con->se_id, + (retry_flag == EV_READ) ? "EV_READ" : "EV_WRITE"); + event_again(&con->se_ev, fd, EV_TIMEOUT|retry_flag, relay_ssl_connect, + &con->se_tv_start, &env->sc_timeout, con); +} + +void +relay_ssl_connected(struct ctl_relay_event *cre) +{ + /* + * Hack libevent - we overwrite the internal bufferevent I/O + * functions to handle the SSL abstraction. + */ + event_set(&cre->bev->ev_read, cre->s, EV_READ, + relay_ssl_readcb, cre->bev); + event_set(&cre->bev->ev_write, cre->s, EV_WRITE, + relay_ssl_writecb, cre->bev); +} + +void +relay_ssl_readcb(int fd, short event, void *arg) +{ + struct bufferevent *bufev = arg; + struct ctl_relay_event *cre = (struct ctl_relay_event *)bufev->cbarg; + struct rsession *con = (struct rsession *)cre->con; + struct relay *rlay = (struct relay *)con->se_relay; + int ret = 0, ssl_err = 0; + short what = EVBUFFER_READ; + size_t len; + char rbuf[READ_BUF_SIZE]; + int howmuch = READ_BUF_SIZE; + + if (event == EV_TIMEOUT) { + what |= EVBUFFER_TIMEOUT; + goto err; + } + + if (bufev->wm_read.high != 0) + howmuch = MIN(sizeof(rbuf), bufev->wm_read.high); + + ret = SSL_read(cre->ssl, rbuf, howmuch); + if (ret <= 0) { + ssl_err = SSL_get_error(cre->ssl, ret); + + switch (ssl_err) { + case SSL_ERROR_WANT_READ: + DPRINTF("relay_ssl_readcb: session %d: " + "want read", con->se_id); + goto retry; + case SSL_ERROR_WANT_WRITE: + DPRINTF("relay_ssl_readcb: session %d: " + "want write", con->se_id); + goto retry; + default: + if (ret == 0) + what |= EVBUFFER_EOF; + else { + ssl_error(rlay->rl_conf.name, + "relay_ssl_readcb"); + what |= EVBUFFER_ERROR; + } + goto err; + } + } + + if (evbuffer_add(bufev->input, rbuf, ret) == -1) { + what |= EVBUFFER_ERROR; + goto err; + } + + relay_bufferevent_add(&bufev->ev_read, bufev->timeout_read); + + len = EVBUFFER_LENGTH(bufev->input); + if (bufev->wm_read.low != 0 && len < bufev->wm_read.low) + return; + if (bufev->wm_read.high != 0 && len > bufev->wm_read.high) { + struct evbuffer *buf = bufev->input; + event_del(&bufev->ev_read); + evbuffer_setcb(buf, bufferevent_read_pressure_cb, bufev); + return; + } + + if (bufev->readcb != NULL) + (*bufev->readcb)(bufev, bufev->cbarg); + return; + + retry: + relay_bufferevent_add(&bufev->ev_read, bufev->timeout_read); + return; + + err: + (*bufev->errorcb)(bufev, what, bufev->cbarg); +} + +void +relay_ssl_writecb(int fd, short event, void *arg) +{ + struct bufferevent *bufev = arg; + struct ctl_relay_event *cre = (struct ctl_relay_event *)bufev->cbarg; + struct rsession *con = (struct rsession *)cre->con; + struct relay *rlay = (struct relay *)con->se_relay; + int ret = 0, ssl_err; + short what = EVBUFFER_WRITE; + + if (event == EV_TIMEOUT) { + what |= EVBUFFER_TIMEOUT; + goto err; + } + + if (EVBUFFER_LENGTH(bufev->output)) { + if (cre->buf == NULL) { + cre->buflen = EVBUFFER_LENGTH(bufev->output); + if ((cre->buf = malloc(cre->buflen)) == NULL) { + what |= EVBUFFER_ERROR; + goto err; + } + bcopy(EVBUFFER_DATA(bufev->output), + cre->buf, cre->buflen); + } + + ret = SSL_write(cre->ssl, cre->buf, cre->buflen); + if (ret <= 0) { + ssl_err = SSL_get_error(cre->ssl, ret); + + switch (ssl_err) { + case SSL_ERROR_WANT_READ: + DPRINTF("relay_ssl_writecb: session %d: " + "want read", con->se_id); + goto retry; + case SSL_ERROR_WANT_WRITE: + DPRINTF("relay_ssl_writecb: session %d: " + "want write", con->se_id); + goto retry; + default: + if (ret == 0) + what |= EVBUFFER_EOF; + else { + ssl_error(rlay->rl_conf.name, + "relay_ssl_writecb"); + what |= EVBUFFER_ERROR; + } + goto err; + } + } + evbuffer_drain(bufev->output, ret); + } + if (cre->buf != NULL) { + free(cre->buf); + cre->buf = NULL; + cre->buflen = 0; + } + + if (EVBUFFER_LENGTH(bufev->output) != 0) + relay_bufferevent_add(&bufev->ev_write, bufev->timeout_write); + + if (bufev->writecb != NULL && + EVBUFFER_LENGTH(bufev->output) <= bufev->wm_write.low) + (*bufev->writecb)(bufev, bufev->cbarg); + return; + + retry: + if (cre->buflen != 0) + relay_bufferevent_add(&bufev->ev_write, bufev->timeout_write); + return; + + err: + if (cre->buf != NULL) { + free(cre->buf); + cre->buf = NULL; + cre->buflen = 0; + } + (*bufev->errorcb)(bufev, what, bufev->cbarg); +} + +int +relay_bufferevent_add(struct event *ev, int timeout) +{ + struct timeval tv, *ptv = NULL; + + if (timeout) { + timerclear(&tv); + tv.tv_sec = timeout; + ptv = &tv; + } + + return (event_add(ev, ptv)); +} + +#ifdef notyet +int +relay_bufferevent_printf(struct ctl_relay_event *cre, const char *fmt, ...) +{ + int ret; + va_list ap; + + va_start(ap, fmt); + ret = evbuffer_add_vprintf(cre->output, fmt, ap); + va_end(ap); + + if (cre->bev != NULL && + ret != -1 && EVBUFFER_LENGTH(cre->output) > 0 && + (cre->bev->enabled & EV_WRITE)) + bufferevent_enable(cre->bev, EV_WRITE); + + return (ret); +} +#endif + +int +relay_bufferevent_print(struct ctl_relay_event *cre, char *str) +{ + if (cre->bev == NULL) + return (evbuffer_add(cre->output, str, strlen(str))); + return (bufferevent_write(cre->bev, str, strlen(str))); +} + +int +relay_bufferevent_write_buffer(struct ctl_relay_event *cre, + struct evbuffer *buf) +{ + if (cre->bev == NULL) + return (evbuffer_add_buffer(cre->output, buf)); + return (bufferevent_write_buffer(cre->bev, buf)); +} + +int +relay_bufferevent_write_chunk(struct ctl_relay_event *cre, + struct evbuffer *buf, size_t size) +{ + int ret; + ret = relay_bufferevent_write(cre, buf->buffer, size); + if (ret != -1) + evbuffer_drain(buf, size); + return (ret); +} + +int +relay_bufferevent_write(struct ctl_relay_event *cre, void *data, size_t size) +{ + if (cre->bev == NULL) + return (evbuffer_add(cre->output, data, size)); + return (bufferevent_write(cre->bev, data, size)); +} + +int +relay_cmp_af(struct sockaddr_storage *a, struct sockaddr_storage *b) +{ + int ret = -1; + struct sockaddr_in ia, ib; + struct sockaddr_in6 ia6, ib6; + + switch (a->ss_family) { + case AF_INET: + bcopy(a, &ia, sizeof(struct sockaddr_in)); + bcopy(b, &ib, sizeof(struct sockaddr_in)); + + ret = memcmp(&ia.sin_addr, &ib.sin_addr, + sizeof(ia.sin_addr)); + if (ret == 0) + ret = memcmp(&ia.sin_port, &ib.sin_port, + sizeof(ia.sin_port)); + break; + case AF_INET6: + bcopy(a, &ia6, sizeof(struct sockaddr_in6)); + bcopy(b, &ib6, sizeof(struct sockaddr_in6)); + + ret = memcmp(&ia6.sin6_addr, &ib6.sin6_addr, + sizeof(ia6.sin6_addr)); + if (ret == 0) + ret = memcmp(&ia6.sin6_port, &ib6.sin6_port, + sizeof(ia6.sin6_port)); + break; + default: + break; + } + + return (ret); +} + +char * +relay_load_file(const char *name, off_t *len) +{ + struct stat st; + off_t size; + u_int8_t *buf = NULL; + int fd; + + if ((fd = open(name, O_RDONLY)) == -1) + return (NULL); + if (fstat(fd, &st) != 0) + goto fail; + size = st.st_size; +#ifndef __FreeBSD__ + if ((buf = (char *)calloc(1, size + 1)) == NULL) +#else + if ((buf = (u_int8_t *)calloc(1, size + 1)) == NULL) +#endif + goto fail; + if (read(fd, buf, size) != size) + goto fail; + + close(fd); + + *len = size + 1; +#ifndef __FreeBSD__ + return (buf); +#else + return (char *)(buf); +#endif + + fail: + if (buf != NULL) + free(buf); + close(fd); + return (NULL); +} + +int +relay_load_certfiles(struct relay *rlay) +{ + struct protocol *proto = rlay->rl_proto; + char certfile[PATH_MAX]; + char hbuf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; + + if ((rlay->rl_conf.flags & F_SSLCLIENT) && (proto->sslca != NULL)) { + if ((rlay->rl_ssl_ca = relay_load_file(proto->sslca, + &rlay->rl_ssl_ca_len)) == NULL) + return (-1); + log_debug("relay_load_certfiles: using ca %s", proto->sslca); + } + + if ((rlay->rl_conf.flags & F_SSL) == 0) + return (0); + + if (print_host(&rlay->rl_conf.ss, hbuf, sizeof(hbuf)) == NULL) + return (-1); + + if (snprintf(certfile, sizeof(certfile), + "/etc/ssl/%s.crt", hbuf) == -1) + return (-1); + if ((rlay->rl_ssl_cert = relay_load_file(certfile, + &rlay->rl_ssl_cert_len)) == NULL) + return (-1); + log_debug("relay_load_certfiles: using certificate %s", certfile); + + if (snprintf(certfile, sizeof(certfile), + "/etc/ssl/private/%s.key", hbuf) == -1) + return -1; + if ((rlay->rl_ssl_key = relay_load_file(certfile, + &rlay->rl_ssl_key_len)) == NULL) + return (-1); + log_debug("relay_load_certfiles: using private key %s", certfile); + + return (0); +} + +static __inline int +relay_proto_cmp(struct protonode *a, struct protonode *b) +{ + int ret; + ret = strcasecmp(a->key, b->key); + if (ret == 0) + ret = (int)a->type - b->type; + return (ret); +} + +RB_GENERATE(proto_tree, protonode, nodes, relay_proto_cmp); + +int +relay_session_cmp(struct rsession *a, struct rsession *b) +{ + struct relay *rlay = (struct relay *)b->se_relay; + struct protocol *proto = rlay->rl_proto; + + if (proto != NULL && proto->cmp != NULL) + return ((*proto->cmp)(a, b)); + + return ((int)a->se_id - b->se_id); +} + +SPLAY_GENERATE(session_tree, rsession, se_nodes, relay_session_cmp); Index: contrib/relayd/snmp.c =================================================================== --- contrib/relayd/snmp.c (revision 0) +++ contrib/relayd/snmp.c (revision 0) @@ -0,0 +1,278 @@ +/* $OpenBSD: snmp.c,v 1.8 2009/06/09 16:26:03 deraadt Exp $ */ + +/* + * Copyright (c) 2008 Reyk Floeter + * + * 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 +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "relayd.h" +#include "snmp.h" + +#define RELAYD_MIB "1.3.6.1.4.1.30155.3" +#define SNMP_ELEMENT(x...) do { \ + if (snmp_element(RELAYD_MIB x) == -1) \ + goto done; \ +} while (0) + +static struct imsgev *iev_snmp = NULL; +static struct imsgev *iev_main = NULL; +static struct relayd *env = NULL; + +void snmp_sock(int, short, void *); +int snmp_getsock(struct imsgev *); +int snmp_element(const char *, enum snmp_type, void *, int64_t); + +void +snmp_init(struct relayd *x_env, struct imsgev *iev) +{ + env = x_env; + iev_main = iev; + + if (event_initialized(&env->sc_snmpev)) + event_del(&env->sc_snmpev); + if (event_initialized(&env->sc_snmpto)) + event_del(&env->sc_snmpto); + if (env->sc_snmp != -1) { + close(env->sc_snmp); + env->sc_snmp = -1; + } + + if ((env->sc_flags & F_TRAP) == 0) { + iev_main = NULL; + return; + } + + snmp_sock(-1, -1, iev); +} + +int +snmp_sendsock(struct imsgev *iev) +{ + struct imsgev tmpiev; + struct sockaddr_un sun; + int s = -1; + + if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + goto fail; + + bzero(&sun, sizeof(sun)); + sun.sun_family = AF_UNIX; + strlcpy(sun.sun_path, SNMP_SOCKET, sizeof(sun.sun_path)); + if (connect(s, (struct sockaddr *)&sun, sizeof(sun)) == -1) + goto fail; + + /* enable restricted snmp socket mode */ + bzero(&tmpiev, sizeof(tmpiev)); + imsg_init(&tmpiev.ibuf, s); + imsg_compose_event(&tmpiev, IMSG_SNMP_LOCK, 0, 0, -1, NULL, 0); + + imsg_compose_event(iev, IMSG_SNMPSOCK, 0, 0, s, NULL, 0); + imsg_flush(&iev->ibuf); /* need to send the socket now */ + close(s); + return (0); + + fail: + if (s != -1) + close(s); + imsg_compose_event(iev, IMSG_NONE, 0, 0, -1, NULL, 0); + return (-1); +} + +int +snmp_getsock(struct imsgev *iev) +{ + struct imsg imsg; + int n, s = -1, done = 0; + + imsg_compose_event(iev, IMSG_SNMPSOCK, 0, 0, -1, NULL, 0); + imsg_flush(&iev->ibuf); + + while (!done) { + if ((n = imsg_read(&iev->ibuf)) == -1) + fatal("snmp_getsock: failed to read imsg"); + if (n == 0) + fatal("snmp_getsock: pipe closed"); + while (!done) { + if ((n = imsg_get(&iev->ibuf, &imsg)) == -1) + fatal("snmp_getsock: failed to get imsg"); + if (n == 0) + break; + done = 1; + switch (imsg.hdr.type) { + case IMSG_SNMPSOCK: + s = imsg.fd; + break; + default: + break; + } + imsg_free(&imsg); + } + } + + if (s != -1) { + log_debug("snmp_getsock: got new snmp socket %d", s); + if (iev_snmp == NULL && (iev_snmp = (struct imsgev *) + calloc(1, sizeof(struct imsgev))) == NULL) + fatal("snmp_getsock: calloc"); + imsg_init(&iev_snmp->ibuf, s); + } + + return (s); +} + +void +snmp_sock(int fd, short event, void *arg) +{ + struct timeval tv = SNMP_RECONNECT_TIMEOUT; + + switch (event) { + case -1: + bzero(&tv, sizeof(tv)); + goto retry; + case EV_READ: + log_debug("snmp_sock: snmp socket closed %d", env->sc_snmp); + (void)close(env->sc_snmp); + break; + } + + if ((env->sc_snmp = snmp_getsock(iev_main)) == -1) { + DPRINTF("snmp_sock: failed to open snmp socket"); + goto retry; + } + + event_set(&env->sc_snmpev, env->sc_snmp, + EV_READ|EV_TIMEOUT, snmp_sock, arg); + event_add(&env->sc_snmpev, NULL); + return; + retry: + evtimer_set(&env->sc_snmpto, snmp_sock, env); + evtimer_add(&env->sc_snmpto, &tv); +} + +int +snmp_element(const char *oid, enum snmp_type type, void *buf, int64_t val) +{ + struct iovec iov[2]; + int iovcnt = 2; + u_int32_t d; + u_int64_t l; + struct snmp_imsg sm; + + DPRINTF("snmp_element: oid %s type %d buf %p val %lld", + oid, type, buf, val); + + bzero(&iov, sizeof(iov)); + + switch (type) { + case SNMP_COUNTER32: + case SNMP_GAUGE32: + case SNMP_TIMETICKS: + case SNMP_OPAQUE: + case SNMP_UINTEGER32: + case SNMP_INTEGER32: + d = (u_int32_t)val; + iov[1].iov_base = &d; + iov[1].iov_len = sizeof(d); + break; + case SNMP_COUNTER64: + l = (u_int64_t)val; + iov[1].iov_base = &l; + iov[1].iov_len = sizeof(l); + break; + case SNMP_NSAPADDR: + case SNMP_BITSTRING: + case SNMP_OCTETSTRING: + case SNMP_IPADDR: + case SNMP_OBJECT: + iov[1].iov_base = buf; + if (val == 0) + iov[1].iov_len = strlen((char *)buf); + else + iov[1].iov_len = val; + break; + case SNMP_NULL: + iovcnt--; + break; + } + + bzero(&sm, sizeof(sm)); + if (strlcpy(sm.snmp_oid, oid, sizeof(sm.snmp_oid)) >= + sizeof(sm.snmp_oid)) + return (-1); + sm.snmp_type = type; + sm.snmp_len = iov[1].iov_len; + iov[0].iov_base = &sm; + iov[0].iov_len = sizeof(sm); + + if (imsg_composev(&iev_snmp->ibuf, IMSG_SNMP_ELEMENT, 0, 0, -1, + iov, iovcnt) == -1) + return (-1); + imsg_event_add(iev_snmp); + + return (0); +} + +/* + * SNMP traps for relayd + */ + +void +snmp_hosttrap(struct table *table, struct host *host) +{ + if (iev_snmp == NULL || env->sc_snmp == -1) + return; + + /* + * OPENBSD-RELAYD-MIB host status trap + * XXX The trap format needs some tweaks and other OIDs + */ + + imsg_compose_event(iev_snmp, IMSG_SNMP_TRAP, 0, 0, -1, NULL, 0); + + SNMP_ELEMENT(".1", SNMP_NULL, NULL, 0); + SNMP_ELEMENT(".1.1", SNMP_OCTETSTRING, host->conf.name, 0); + SNMP_ELEMENT(".1.2", SNMP_INTEGER32, NULL, host->up); + SNMP_ELEMENT(".1.3", SNMP_INTEGER32, NULL, host->last_up); + SNMP_ELEMENT(".1.4", SNMP_INTEGER32, NULL, host->up_cnt); + SNMP_ELEMENT(".1.5", SNMP_INTEGER32, NULL, host->check_cnt); + SNMP_ELEMENT(".1.6", SNMP_OCTETSTRING, table->conf.name, 0); + SNMP_ELEMENT(".1.7", SNMP_INTEGER32, NULL, table->up); + if (!host->conf.retry) + goto done; + SNMP_ELEMENT(".1.8", SNMP_INTEGER32, NULL, host->conf.retry); + SNMP_ELEMENT(".1.9", SNMP_INTEGER32, NULL, host->retry_cnt); + + done: + imsg_compose_event(iev_snmp, IMSG_SNMP_END, 0, 0, -1, NULL, 0); +} Index: contrib/relayd/name2id.c =================================================================== --- contrib/relayd/name2id.c (revision 0) +++ contrib/relayd/name2id.c (revision 0) @@ -0,0 +1,177 @@ +/* $OpenBSD: name2id.c,v 1.2 2007/12/07 17:17:00 reyk Exp $ */ + +/* + * Copyright (c) 2004, 2005 Henning Brauer + * + * 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 +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#include "relayd.h" + +#define IDVAL_MAX 50000 + +struct n2id_label { + TAILQ_ENTRY(n2id_label) entry; + char *name; + u_int16_t id; + int ref; +}; + +TAILQ_HEAD(n2id_labels, n2id_label); + +u_int16_t _name2id(struct n2id_labels *, const char *); +const char *_id2name(struct n2id_labels *, u_int16_t); +void _unref(struct n2id_labels *, u_int16_t); +void _ref(struct n2id_labels *, u_int16_t); + +/* for protocolnode labels */ +struct n2id_labels pn_labels = TAILQ_HEAD_INITIALIZER(pn_labels); + +u_int16_t +pn_name2id(const char *name) +{ + return (_name2id(&pn_labels, name)); +} + +const char * +pn_id2name(u_int16_t id) +{ + return (_id2name(&pn_labels, id)); +} + +void +pn_unref(u_int16_t id) +{ + _unref(&pn_labels, id); +} + +void +pn_ref(u_int16_t id) +{ + _ref(&pn_labels, id); +} + +u_int16_t +_name2id(struct n2id_labels *head, const char *name) +{ + struct n2id_label *label, *p = NULL; + u_int16_t new_id = 1; + + if (!name[0]) { + errno = EINVAL; + return (0); + } + + TAILQ_FOREACH(label, head, entry) + if (strcmp(name, label->name) == 0) { + label->ref++; + return (label->id); + } + + /* + * to avoid fragmentation, we do a linear search from the beginning + * and take the first free slot we find. if there is none or the list + * is empty, append a new entry at the end. + */ + + if (!TAILQ_EMPTY(head)) + for (p = TAILQ_FIRST(head); p != NULL && + p->id == new_id; p = TAILQ_NEXT(p, entry)) + new_id = p->id + 1; + + if (new_id > IDVAL_MAX) { + errno = ERANGE; + return (0); + } + + if ((label = calloc(1, sizeof(struct n2id_label))) == NULL) + return (0); + if ((label->name = strdup(name)) == NULL) { + free(label); + return (0); + } + label->id = new_id; + label->ref++; + + if (p != NULL) /* insert new entry before p */ + TAILQ_INSERT_BEFORE(p, label, entry); + else /* either list empty or no free slot in between */ + TAILQ_INSERT_TAIL(head, label, entry); + + return (label->id); +} + +const char * +_id2name(struct n2id_labels *head, u_int16_t id) +{ + struct n2id_label *label; + + if (id == 0) + return (""); + + TAILQ_FOREACH(label, head, entry) + if (label->id == id) + return (label->name); + + return (""); +} + +void +_unref(struct n2id_labels *head, u_int16_t id) +{ + struct n2id_label *p, *next; + + if (id == 0) + return; + + for (p = TAILQ_FIRST(head); p != NULL; p = next) { + next = TAILQ_NEXT(p, entry); + if (id == p->id) { + if (--p->ref == 0) { + TAILQ_REMOVE(head, p, entry); + free(p->name); + free(p); + } + break; + } + } +} + +void +_ref(struct n2id_labels *head, u_int16_t id) +{ + struct n2id_label *label; + + if (id == 0) + return; + + TAILQ_FOREACH(label, head, entry) + if (label->id == id) { + ++label->ref; + break; + } +} Index: contrib/relayd/pfe_filter.c =================================================================== --- contrib/relayd/pfe_filter.c (revision 0) +++ contrib/relayd/pfe_filter.c (revision 0) @@ -0,0 +1,658 @@ +/* $OpenBSD: pfe_filter.c,v 1.38 2009/04/24 14:20:24 reyk Exp $ */ + +/* + * Copyright (c) 2006 Pierre-Yves Ritschard + * + * 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 +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "relayd.h" + +struct pfdata { + int dev; + struct pf_anchor *anchor; + struct pfioc_trans pft[PF_RULESET_MAX]; + struct pfioc_trans_e pfte[PF_RULESET_MAX]; + u_int8_t pfused; +}; + +int transaction_init(struct relayd *, const char *); +int transaction_commit(struct relayd *); +void kill_tables(struct relayd *); +int kill_srcnodes(struct relayd *, struct table *); + +void +init_filter(struct relayd *env) +{ + struct pf_status status; + + if (!(env->sc_flags & F_NEEDPF)) + return; + + if ((env->sc_pf = calloc(1, sizeof(*(env->sc_pf)))) == NULL) + fatal("calloc"); + if ((env->sc_pf->dev = open(PF_SOCKET, O_RDWR)) == -1) + fatal("init_filter: cannot open pf socket"); + if (ioctl(env->sc_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 relayd *env) +{ + int i; + struct rdr *rdr; + struct pfr_table *tables; + struct pfioc_table io; + + if (!(env->sc_flags & F_NEEDPF)) + return; + + if ((tables = calloc(env->sc_rdrcount, sizeof(*tables))) == NULL) + fatal("calloc"); + i = 0; + + TAILQ_FOREACH(rdr, env->sc_rdrs, entry) { + if (strlcpy(tables[i].pfrt_anchor, RELAYD_ANCHOR "/", + sizeof(tables[i].pfrt_anchor)) >= PF_ANCHOR_NAME_SIZE) + goto toolong; + if (strlcat(tables[i].pfrt_anchor, rdr->conf.name, + sizeof(tables[i].pfrt_anchor)) >= PF_ANCHOR_NAME_SIZE) + goto toolong; + if (strlcpy(tables[i].pfrt_name, rdr->conf.name, + sizeof(tables[i].pfrt_name)) >= + sizeof(tables[i].pfrt_name)) + goto toolong; + tables[i].pfrt_flags |= PFR_TFLAG_PERSIST; +#ifdef __FreeBSD__ + log_debug("init_tables: prepare anchor \"%s\" and table \"%s\"", +#endif + tables[i].pfrt_anchor, tables[i].pfrt_name); + i++; + } + if (i != env->sc_rdrcount) + fatalx("init_tables: table count modified"); + + memset(&io, 0, sizeof(io)); + io.pfrio_size = env->sc_rdrcount; + io.pfrio_esize = sizeof(*tables); + io.pfrio_buffer = tables; + + if (ioctl(env->sc_pf->dev, DIOCRADDTABLES, &io) == -1) + fatal("init_tables: cannot create tables"); + log_debug("init_tables: created %d tables", io.pfrio_nadd); + + free(tables); + + if (io.pfrio_nadd == env->sc_rdrcount) + return; + + /* + * clear all tables, since some already existed + */ + TAILQ_FOREACH(rdr, env->sc_rdrs, entry) + flush_table(env, rdr); + + return; + + toolong: + fatal("init_tables: name too long"); +} + +void +kill_tables(struct relayd *env) +{ + struct pfioc_table io; + struct rdr *rdr; + int cnt = 0; + + if (!(env->sc_flags & F_NEEDPF)) + return; + + TAILQ_FOREACH(rdr, env->sc_rdrs, entry) { + memset(&io, 0, sizeof(io)); + if (strlcpy(io.pfrio_table.pfrt_anchor, RELAYD_ANCHOR "/", + sizeof(io.pfrio_table.pfrt_anchor)) >= PF_ANCHOR_NAME_SIZE) + goto toolong; + if (strlcat(io.pfrio_table.pfrt_anchor, rdr->conf.name, + sizeof(io.pfrio_table.pfrt_anchor)) >= PF_ANCHOR_NAME_SIZE) + goto toolong; + if (ioctl(env->sc_pf->dev, DIOCRCLRTABLES, &io) == -1) + fatal("kill_tables: ioctl failed"); + cnt += io.pfrio_ndel; + } + log_debug("kill_tables: deleted %d tables", cnt); + return; + + toolong: + fatal("kill_tables: name too long"); +} + +void +sync_table(struct relayd *env, struct rdr *rdr, struct table *table) +{ + int i, cnt = 0; + struct pfioc_table io; + struct pfr_addr *addlist; + struct sockaddr_in *sain; + struct sockaddr_in6 *sain6; + struct host *host; + + if (!(env->sc_flags & F_NEEDPF)) + return; + + if (table == NULL) + return; + + if (table->up == 0) { + flush_table(env, rdr); + 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; + if (strlcpy(io.pfrio_table.pfrt_anchor, RELAYD_ANCHOR "/", + sizeof(io.pfrio_table.pfrt_anchor)) >= PF_ANCHOR_NAME_SIZE) + goto toolong; + if (strlcat(io.pfrio_table.pfrt_anchor, rdr->conf.name, + sizeof(io.pfrio_table.pfrt_anchor)) >= PF_ANCHOR_NAME_SIZE) + goto toolong; + if (strlcpy(io.pfrio_table.pfrt_name, rdr->conf.name, + sizeof(io.pfrio_table.pfrt_name)) >= + sizeof(io.pfrio_table.pfrt_name)) + goto toolong; + + i = 0; + TAILQ_FOREACH(host, &table->hosts, entry) { + if (host->up != HOST_UP) + continue; + memset(&(addlist[i]), 0, sizeof(addlist[i])); + switch (host->conf.ss.ss_family) { + case AF_INET: + sain = (struct sockaddr_in *)&host->conf.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->conf.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->sc_pf->dev, DIOCRSETADDRS, &io) == -1) + fatal("sync_table: cannot set address list"); + if (rdr->conf.flags & F_STICKY) + cnt = kill_srcnodes(env, table); + free(addlist); + + if (env->sc_opts & RELAYD_OPT_LOGUPDATE) + log_info("table %s: %d added, %d deleted, " + "%d changed, %d killed", io.pfrio_table.pfrt_name, + io.pfrio_nadd, io.pfrio_ndel, io.pfrio_nchange, cnt); + return; + + toolong: + fatal("sync_table: name too long"); +} + +int +kill_srcnodes(struct relayd *env, struct table *table) +{ + struct host *host; + struct pfioc_src_node_kill psnk; + int cnt = 0; + struct sockaddr_in *sain; + struct sockaddr_in6 *sain6; + + bzero(&psnk, sizeof(psnk)); + + /* Only match the destination address, source mask will be zero */ + memset(&psnk.psnk_dst.addr.v.a.mask, 0xff, + sizeof(psnk.psnk_dst.addr.v.a.mask)); + + TAILQ_FOREACH(host, &table->hosts, entry) { + if (host->up != HOST_DOWN) + continue; + + switch (host->conf.ss.ss_family) { + case AF_INET: + sain = (struct sockaddr_in *)&host->conf.ss; + bcopy(&sain->sin_addr, + &psnk.psnk_dst.addr.v.a.addr.v4, + sizeof(psnk.psnk_dst.addr.v.a.addr.v4)); + break; + case AF_INET6: + sain6 = (struct sockaddr_in6 *)&host->conf.ss; + bcopy(&sain6->sin6_addr, + &psnk.psnk_dst.addr.v.a.addr.v6, + sizeof(psnk.psnk_dst.addr.v.a.addr.v6)); + break; + default: + fatalx("kill_srcnodes: unknown address family"); + break; + } + + psnk.psnk_af = host->conf.ss.ss_family; + + if (ioctl(env->sc_pf->dev, + DIOCKILLSRCNODES, &psnk) == -1) + fatal("kill_srcnodes: cannot kill src nodes"); + cnt += psnk.psnk_af; + } + + return (cnt); +} + +void +flush_table(struct relayd *env, struct rdr *rdr) +{ + struct pfioc_table io; + + if (!(env->sc_flags & F_NEEDPF)) + return; + + memset(&io, 0, sizeof(io)); + if (strlcpy(io.pfrio_table.pfrt_anchor, RELAYD_ANCHOR "/", + sizeof(io.pfrio_table.pfrt_anchor)) >= PF_ANCHOR_NAME_SIZE) + goto toolong; + if (strlcat(io.pfrio_table.pfrt_anchor, rdr->conf.name, + sizeof(io.pfrio_table.pfrt_anchor)) >= PF_ANCHOR_NAME_SIZE) + goto toolong; + if (strlcpy(io.pfrio_table.pfrt_name, rdr->conf.name, + sizeof(io.pfrio_table.pfrt_name)) >= + sizeof(io.pfrio_table.pfrt_name)) + goto toolong; + if (ioctl(env->sc_pf->dev, DIOCRCLRADDRS, &io) == -1) + fatal("flush_table: cannot flush table addresses"); + + io.pfrio_esize = sizeof(io.pfrio_table); + io.pfrio_size = 1; + io.pfrio_buffer = &io.pfrio_table; + if (ioctl(env->sc_pf->dev, DIOCRCLRTSTATS, &io) == -1) + fatal("flush_table: cannot flush table stats"); + + log_debug("flush_table: flushed table %s", rdr->conf.name); + return; + + toolong: + fatal("flush_table: name too long"); +} + +int +transaction_init(struct relayd *env, const char *anchor) +{ + int i; + + for (i = 0; i < PF_RULESET_MAX; i++) { + env->sc_pf->pft[i].size = 1; + env->sc_pf->pft[i].esize = sizeof(env->sc_pf->pfte[i]); + env->sc_pf->pft[i].array = &env->sc_pf->pfte[i]; + + bzero(&env->sc_pf->pfte[i], sizeof(env->sc_pf->pfte[i])); + (void)strlcpy(env->sc_pf->pfte[i].anchor, + anchor, PF_ANCHOR_NAME_SIZE); + env->sc_pf->pfte[i].rs_num = i; + + if (ioctl(env->sc_pf->dev, DIOCXBEGIN, + &env->sc_pf->pft[i]) == -1) + return (-1); + } + return (0); +} + +int +transaction_commit(struct relayd *env) +{ + int i; + + for (i = 0; i < PF_RULESET_MAX; i++) { + if (ioctl(env->sc_pf->dev, DIOCXCOMMIT, + &env->sc_pf->pft[i]) == -1) + return (-1); + } + return (0); +} + +void +sync_ruleset(struct relayd *env, struct rdr *rdr, 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]; +#ifndef __FreeBSD__ + int rs; +#else + int rs = 0; +#endif + struct table *t = rdr->table; + + if (!(env->sc_flags & F_NEEDPF)) + return; + + bzero(anchor, sizeof(anchor)); + if (strlcpy(anchor, RELAYD_ANCHOR "/", sizeof(anchor)) >= + PF_ANCHOR_NAME_SIZE) + goto toolong; + if (strlcat(anchor, rdr->conf.name, sizeof(anchor)) >= + PF_ANCHOR_NAME_SIZE) + goto toolong; + if (transaction_init(env, anchor) == -1) { + log_warn("sync_ruleset: transaction init failed"); + return; + } + + if (!enable) { + if (transaction_commit(env) == -1) + log_warn("sync_ruleset: " + "remove rules transaction failed"); + else + log_debug("sync_ruleset: rules removed"); + return; + } + + TAILQ_FOREACH(address, &rdr->virts, entry) { + memset(&rio, 0, sizeof(rio)); + memset(&pio, 0, sizeof(pio)); + (void)strlcpy(rio.anchor, anchor, sizeof(rio.anchor)); + + switch (t->conf.fwdmode) { + case FWD_NORMAL: + /* traditional redirection in the rdr-anchor */ + rs = PF_RULESET_RDR; + rio.rule.action = PF_RDR; + break; + case FWD_ROUTE: + /* re-route with pf for DSR (direct server return) */ + rs = PF_RULESET_FILTER; + rio.rule.action = PF_PASS; + rio.rule.rt = PF_ROUTETO; + rio.rule.direction = PF_IN; + rio.rule.quick = 1; /* force first match */ + + /* Use sloppy state handling for half connections */ + rio.rule.keep_state = PF_STATE_NORMAL; +#if 0 + rio.rule.rule_flag = PFRULE_STATESLOPPY; +#endif + break; + default: + fatalx("sync_ruleset: invalid forward mode"); + /* NOTREACHED */ + } + + rio.ticket = env->sc_pf->pfte[rs].ticket; + if (ioctl(env->sc_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 = address->ipproto; + rio.rule.src.addr.type = PF_ADDR_ADDRMASK; + rio.rule.dst.addr.type = PF_ADDR_ADDRMASK; + rio.rule.dst.port_op = address->port.op; + rio.rule.dst.port[0] = address->port.val[0]; + rio.rule.dst.port[1] = address->port.val[1]; +#if 0 + rio.rule.rtableid = -1; /* stay in the main routing table */ +#endif + + if (rio.rule.proto == IPPROTO_TCP) + rio.rule.timeout[PFTM_TCP_ESTABLISHED] = + rdr->conf.timeout.tv_sec; + + if (strlen(rdr->conf.tag)) + (void)strlcpy(rio.rule.tagname, rdr->conf.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; + if (strlen(t->conf.ifname)) + (void)strlcpy(pio.addr.ifname, t->conf.ifname, + sizeof(pio.addr.ifname)); + if (strlcpy(pio.addr.addr.v.tblname, rdr->conf.name, + sizeof(pio.addr.addr.v.tblname)) >= + sizeof(pio.addr.addr.v.tblname)) + fatal("sync_ruleset: table name too long"); + if (ioctl(env->sc_pf->dev, DIOCADDADDR, &pio) == -1) + fatal("sync_ruleset: cannot add address to pool"); + + if (address->port.op == PF_OP_EQ || + rdr->table->conf.flags & F_PORT) { + rio.rule.rpool.proxy_port[0] = + ntohs(rdr->table->conf.port); + rio.rule.rpool.port_op = PF_OP_EQ; + } + rio.rule.rpool.opts = PF_POOL_ROUNDROBIN; + if (rdr->conf.flags & F_STICKY) + rio.rule.rpool.opts |= PF_POOL_STICKYADDR; + + if (ioctl(env->sc_pf->dev, DIOCADDRULE, &rio) == -1) + fatal("cannot add rule"); + log_debug("sync_ruleset: rule added to %sanchor \"%s\"", + rdr->table->conf.fwdmode == FWD_ROUTE ? + "" : "rdr-", anchor); + } + if (transaction_commit(env) == -1) + log_warn("sync_ruleset: add rules transaction failed"); + return; + + toolong: + fatal("sync_ruleset: name too long"); +} + +void +flush_rulesets(struct relayd *env) +{ + struct rdr *rdr; + char anchor[PF_ANCHOR_NAME_SIZE]; + + if (!(env->sc_flags & F_NEEDPF)) + return; + + kill_tables(env); + TAILQ_FOREACH(rdr, env->sc_rdrs, entry) { + if (strlcpy(anchor, RELAYD_ANCHOR "/", sizeof(anchor)) >= + PF_ANCHOR_NAME_SIZE) + goto toolong; + if (strlcat(anchor, rdr->conf.name, sizeof(anchor)) >= + PF_ANCHOR_NAME_SIZE) + goto toolong; + if (transaction_init(env, anchor) == -1 || + transaction_commit(env) == -1) + log_warn("flush_rulesets: transaction for %s/ failed", + RELAYD_ANCHOR); + } + if (strlcpy(anchor, RELAYD_ANCHOR, sizeof(anchor)) >= + PF_ANCHOR_NAME_SIZE) + goto toolong; + if (transaction_init(env, anchor) == -1 || + transaction_commit(env) == -1) + log_warn("flush_rulesets: transaction for %s failed", + RELAYD_ANCHOR); + log_debug("flush_rulesets: flushed rules"); + return; + + toolong: + fatal("flush_rulesets: name too long"); +} + +int +natlook(struct relayd *env, struct ctl_natlook *cnl) +{ + struct pfioc_natlook pnl; + struct sockaddr_in *in, *out; + struct sockaddr_in6 *in6, *out6; + char ibuf[BUFSIZ], obuf[BUFSIZ]; + + if (!(env->sc_flags & F_NEEDPF)) + return (0); + + bzero(&pnl, sizeof(pnl)); + + if ((pnl.af = cnl->src.ss_family) != cnl->dst.ss_family) + fatalx("natlook: illegal address families"); + switch (pnl.af) { + case AF_INET: + in = (struct sockaddr_in *)&cnl->src; + out = (struct sockaddr_in *)&cnl->dst; + bcopy(&in->sin_addr, &pnl.saddr.v4, sizeof(pnl.saddr.v4)); + pnl.sport = in->sin_port; + bcopy(&out->sin_addr, &pnl.daddr.v4, sizeof(pnl.daddr.v4)); + pnl.dport = out->sin_port; + break; + case AF_INET6: + in6 = (struct sockaddr_in6 *)&cnl->src; + out6 = (struct sockaddr_in6 *)&cnl->dst; + bcopy(&in6->sin6_addr, &pnl.saddr.v6, sizeof(pnl.saddr.v6)); + pnl.sport = in6->sin6_port; + bcopy(&out6->sin6_addr, &pnl.daddr.v6, sizeof(pnl.daddr.v6)); + pnl.dport = out6->sin6_port; + } + pnl.proto = cnl->proto; + pnl.direction = PF_IN; + cnl->in = 1; + + if (ioctl(env->sc_pf->dev, DIOCNATLOOK, &pnl) == -1) { + pnl.direction = PF_OUT; + cnl->in = 0; + if (ioctl(env->sc_pf->dev, DIOCNATLOOK, &pnl) == -1) { + log_debug("natlook: error: %s", strerror(errno)); + return (-1); + } + } + + inet_ntop(pnl.af, &pnl.rsaddr, ibuf, sizeof(ibuf)); + inet_ntop(pnl.af, &pnl.rdaddr, obuf, sizeof(obuf)); + log_debug("natlook: %s %s:%d -> %s:%d", + pnl.direction == PF_IN ? "in" : "out", + ibuf, ntohs(pnl.rsport), obuf, ntohs(pnl.rdport)); + + switch (pnl.af) { + case AF_INET: + in = (struct sockaddr_in *)&cnl->rsrc; + out = (struct sockaddr_in *)&cnl->rdst; + bcopy(&pnl.rsaddr.v4, &in->sin_addr, sizeof(in->sin_addr)); + in->sin_port = pnl.rsport; + bcopy(&pnl.rdaddr.v4, &out->sin_addr, sizeof(out->sin_addr)); + out->sin_port = pnl.rdport; + break; + case AF_INET6: + in6 = (struct sockaddr_in6 *)&cnl->rsrc; + out6 = (struct sockaddr_in6 *)&cnl->rdst; + bcopy(&pnl.rsaddr.v6, &in6->sin6_addr, sizeof(in6->sin6_addr)); + bcopy(&pnl.rdaddr.v6, &out6->sin6_addr, + sizeof(out6->sin6_addr)); + break; + } + cnl->rsrc.ss_family = pnl.af; + cnl->rdst.ss_family = pnl.af; + cnl->rsport = pnl.rsport; + cnl->rdport = pnl.rdport; + + return (0); +} + +u_int64_t +check_table(struct relayd *env, struct rdr *rdr, struct table *table) +{ + struct pfioc_table io; + struct pfr_tstats tstats; + + if (table == NULL) + return (0); + + bzero(&io, sizeof(io)); + io.pfrio_esize = sizeof(struct pfr_tstats); + io.pfrio_size = 1; + io.pfrio_buffer = &tstats; + if (strlcpy(io.pfrio_table.pfrt_anchor, RELAYD_ANCHOR "/", + sizeof(io.pfrio_table.pfrt_anchor)) >= PF_ANCHOR_NAME_SIZE) + goto toolong; + if (strlcat(io.pfrio_table.pfrt_anchor, rdr->conf.name, + sizeof(io.pfrio_table.pfrt_anchor)) >= PF_ANCHOR_NAME_SIZE) + goto toolong; + if (strlcpy(io.pfrio_table.pfrt_name, rdr->conf.name, + sizeof(io.pfrio_table.pfrt_name)) >= + sizeof(io.pfrio_table.pfrt_name)) + goto toolong; + + if (ioctl(env->sc_pf->dev, DIOCRGETTSTATS, &io) == -1) + fatal("check_table: cannot get table stats"); + + return (tstats.pfrts_match); + + toolong: + fatal("check_table: name too long"); + return (0); +} Index: contrib/relayd/control.c =================================================================== --- contrib/relayd/control.c (revision 0) +++ contrib/relayd/control.c (revision 0) @@ -0,0 +1,397 @@ +/* $OpenBSD: control.c,v 1.32 2009/06/05 23:39:51 pyr Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * 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 +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "relayd.h" + +#define CONTROL_BACKLOG 5 + +struct ctl_connlist ctl_conns; + +struct ctl_conn *control_connbyfd(int); +void control_close(int); + +struct imsgev *iev_main = NULL; +struct imsgev *iev_hce = NULL; + +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); + } + + sun.sun_family = AF_UNIX; + if (strlcpy(sun.sun_path, RELAYD_SOCKET, + sizeof(sun.sun_path)) >= sizeof(sun.sun_path)) { + log_warn("control_init: %s name too long", RELAYD_SOCKET); + close(fd); + return (-1); + } + + if (unlink(RELAYD_SOCKET) == -1) + if (errno != ENOENT) { + log_warn("control_init: unlink %s", RELAYD_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", RELAYD_SOCKET); + close(fd); + (void)umask(old_umask); + return (-1); + } + (void)umask(old_umask); + + if (chmod(RELAYD_SOCKET, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) { + log_warn("control_init: chmod"); + close(fd); + (void)unlink(RELAYD_SOCKET); + return (-1); + } + + session_socket_blockmode(fd, BM_NONBLOCK); + control_state.fd = fd; + TAILQ_INIT(&ctl_conns); + + return (0); +} + +int +control_listen(struct relayd *env, struct imsgev *i_main, + struct imsgev *i_hce) +{ + + iev_main = i_main; + iev_hce = i_hce; + + 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, env); + event_add(&control_state.ev, NULL); + + return (0); +} + +void +control_cleanup(void) +{ + (void)unlink(RELAYD_SOCKET); +} + +/* ARGSUSED */ +void +control_accept(int listenfd, short event, void *arg) +{ + int connfd; + socklen_t len; + struct sockaddr_un sun; + struct ctl_conn *c; + struct relayd *env = arg; + + 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 = calloc(1, sizeof(struct ctl_conn))) == NULL) { + close(connfd); + log_warn("control_accept"); + return; + } + + imsg_init(&c->iev.ibuf, connfd); + c->iev.handler = control_dispatch_imsg; + c->iev.events = EV_READ; + event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events, + c->iev.handler, env); + event_add(&c->iev.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->iev.ibuf.fd != fd; + 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); + return; + } + + msgbuf_clear(&c->iev.ibuf.w); + TAILQ_REMOVE(&ctl_conns, c, entry); + + event_del(&c->iev.ev); + close(c->iev.ibuf.fd); + free(c); +} + +/* ARGSUSED */ +void +control_dispatch_imsg(int fd, short event, void *arg) +{ + struct ctl_conn *c; + struct imsg imsg; + struct ctl_id id; + int n; + struct relayd *env = arg; + + if ((c = control_connbyfd(fd)) == NULL) { + log_warn("control_dispatch_imsg: fd %d: not found", fd); + return; + } + + if (event & EV_READ) { + if ((n = imsg_read(&c->iev.ibuf)) == -1 || n == 0) { + control_close(fd); + return; + } + } + + if (event & EV_WRITE) { + if (msgbuf_write(&c->iev.ibuf.w) < 0) { + control_close(fd); + return; + } + } + + for (;;) { + if ((n = imsg_get(&c->iev.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_SESSION: + show_sessions(c); + break; + case IMSG_CTL_RDR_DISABLE: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id)) + fatalx("invalid imsg header len"); + memcpy(&id, imsg.data, sizeof(id)); + if (disable_rdr(c, &id)) + imsg_compose_event(&c->iev, IMSG_CTL_FAIL, + 0, 0, -1, NULL, 0); + else { + memcpy(imsg.data, &id, sizeof(id)); + control_imsg_forward(&imsg); + imsg_compose_event(&c->iev, IMSG_CTL_OK, + 0, 0, -1, NULL, 0); + } + break; + case IMSG_CTL_RDR_ENABLE: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id)) + fatalx("invalid imsg header len"); + memcpy(&id, imsg.data, sizeof(id)); + if (enable_rdr(c, &id)) + imsg_compose_event(&c->iev, IMSG_CTL_FAIL, + 0, 0, -1, NULL, 0); + else { + memcpy(imsg.data, &id, sizeof(id)); + control_imsg_forward(&imsg); + imsg_compose_event(&c->iev, IMSG_CTL_OK, + 0, 0, -1, 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_event(&c->iev, IMSG_CTL_FAIL, + 0, 0, -1, NULL, 0); + else { + memcpy(imsg.data, &id, sizeof(id)); + control_imsg_forward(&imsg); + imsg_compose_event(&c->iev, IMSG_CTL_OK, + 0, 0, -1, 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_event(&c->iev, IMSG_CTL_FAIL, + 0, 0, -1, NULL, 0); + else { + memcpy(imsg.data, &id, sizeof(id)); + control_imsg_forward(&imsg); + imsg_compose_event(&c->iev, IMSG_CTL_OK, + 0, 0, -1, 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, NULL)) + imsg_compose_event(&c->iev, IMSG_CTL_FAIL, + 0, 0, -1, NULL, 0); + else { + memcpy(imsg.data, &id, sizeof(id)); + control_imsg_forward(&imsg); + imsg_compose_event(&c->iev, IMSG_CTL_OK, + 0, 0, -1, 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, NULL)) + imsg_compose_event(&c->iev, IMSG_CTL_FAIL, + 0, 0, -1, NULL, 0); + else { + memcpy(imsg.data, &id, sizeof(id)); + control_imsg_forward(&imsg); + imsg_compose_event(&c->iev, IMSG_CTL_OK, + 0, 0, -1, NULL, 0); + } + break; + case IMSG_CTL_SHUTDOWN: + imsg_compose_event(&c->iev, IMSG_CTL_FAIL, + 0, 0, -1, NULL, 0); + break; + case IMSG_CTL_POLL: + imsg_compose_event(iev_hce, IMSG_CTL_POLL, + 0, 0,-1, NULL, 0); + imsg_compose_event(&c->iev, IMSG_CTL_OK, + 0, 0, -1, NULL, 0); + break; + case IMSG_CTL_RELOAD: + if (env->sc_prefork_relay > 0) { + imsg_compose_event(&c->iev, IMSG_CTL_FAIL, + 0, 0, -1, NULL, 0); + break; + } + imsg_compose_event(iev_main, IMSG_CTL_RELOAD, + 0, 0, -1, NULL, 0); + /* + * we unconditionnaly return a CTL_OK imsg because + * we have no choice. + * + * so in this case, the reply relayctl gets means + * that the reload command has been set, + * it doesn't say wether the command succeeded or not. + */ + imsg_compose_event(&c->iev, IMSG_CTL_OK, + 0, 0, -1, NULL, 0); + break; + case IMSG_CTL_NOTIFY: + if (c->flags & CTL_CONN_NOTIFY) { + log_debug("control_dispatch_imsg: " + "client requested notify more than once"); + imsg_compose_event(&c->iev, IMSG_CTL_FAIL, + 0, 0, -1, NULL, 0); + break; + } + c->flags |= CTL_CONN_NOTIFY; + break; + default: + log_debug("control_dispatch_imsg: " + "error handling imsg %d", imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + + imsg_event_add(&c->iev); +} + +void +control_imsg_forward(struct imsg *imsg) +{ + struct ctl_conn *c; + + TAILQ_FOREACH(c, &ctl_conns, entry) + if (c->flags & CTL_CONN_NOTIFY) + imsg_compose_event(&c->iev, imsg->hdr.type, + 0, imsg->hdr.pid, -1, 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("fcntl F_GETFL"); + + if (bm == BM_NONBLOCK) + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + + if ((flags = fcntl(fd, F_SETFL, flags)) == -1) + fatal("fcntl F_SETFL"); +} Index: contrib/relayd/relayd.c =================================================================== --- contrib/relayd/relayd.c (revision 0) +++ contrib/relayd/relayd.c (revision 0) @@ -0,0 +1,1358 @@ +/* $OpenBSD: relayd.c,v 1.91 2009/08/07 11:21:53 reyk Exp $ */ + +/* + * Copyright (c) 2007, 2008 Reyk Floeter + * Copyright (c) 2006 Pierre-Yves Ritschard + * + * 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. + */ + +#ifdef __FreeBSD__ +#include +#else +#include +#endif +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __FreeBSD__ +#include +#else +#include +#endif +#include + +#include + +#include "relayd.h" + +__dead void usage(void); + +void main_sig_handler(int, short, void *); +void main_shutdown(struct relayd *); +void main_dispatch_pfe(int, short, void *); +void main_dispatch_hce(int, short, void *); +void main_dispatch_relay(int, short, void *); +int check_child(pid_t, const char *); +int send_all(struct relayd *, enum imsg_type, + void *, u_int16_t); +void reconfigure(void); +void purge_tree(struct proto_tree *); +int bindany(struct ctl_bindany *); + +int pipe_parent2pfe[2]; +int pipe_parent2hce[2]; +int pipe_pfe2hce[2]; +int pipe_parent2relay[RELAY_MAXPROC][2]; +int pipe_pfe2relay[RELAY_MAXPROC][2]; + +struct relayd *relayd_env; + +struct imsgev *iev_pfe; +struct imsgev *iev_hce; +struct imsgev *iev_relay; + +pid_t pfe_pid = 0; +pid_t hce_pid = 0; +pid_t relay_pid = 0; + +void +main_sig_handler(int sig, short event, void *arg) +{ + struct relayd *env = arg; + int die = 0; + + switch (sig) { + case SIGTERM: + case SIGINT: + die = 1; + /* FALLTHROUGH */ + case SIGCHLD: + if (check_child(pfe_pid, "pf update engine")) { + pfe_pid = 0; + die = 1; + } + if (check_child(hce_pid, "host check engine")) { + hce_pid = 0; + die = 1; + } + if (check_child(relay_pid, "socket relay engine")) { + relay_pid = 0; + die = 1; + } + if (die) + main_shutdown(env); + break; + case SIGHUP: + reconfigure(); + break; + default: + fatalx("unexpected signal"); + } +} + +/* __dead is for lint */ +#ifdef __FreeBSD__ +void +#else +__dead void +#endif +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s [-dnv] [-D macro=value] [-f file]\n", + __progname); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + int c; + int debug; + u_int32_t opts; + struct relayd *env; + const char *conffile; + struct event ev_sigint; + struct event ev_sigterm; + struct event ev_sigchld; + struct event ev_sighup; + struct imsgev *iev; + + opts = 0; + debug = 0; + conffile = CONF_FILE; + + log_init(1); /* log to stderr until daemonized */ + + while ((c = getopt(argc, argv, "dD:nf:v")) != -1) { + switch (c) { + case 'd': + debug = 2; + break; + case 'D': + if (cmdline_symset(optarg) < 0) + log_warnx("could not parse macro definition %s", + optarg); + break; + case 'n': + debug = 2; + opts |= RELAYD_OPT_NOACTION; + break; + case 'f': + conffile = optarg; + break; + case 'v': + opts |= RELAYD_OPT_VERBOSE; + break; + default: + usage(); + } + } + + argc -= optind; + argv += optind; + if (argc > 0) + usage(); + + if ((env = parse_config(conffile, opts)) == NULL) + exit(1); + relayd_env = env; + + if (env->sc_opts & RELAYD_OPT_NOACTION) { + fprintf(stderr, "configuration OK\n"); + exit(0); + } + if (debug) + env->sc_opts |= RELAYD_OPT_LOGUPDATE; + + if (geteuid()) + errx(1, "need root privileges"); + + if (getpwnam(RELAYD_USER) == NULL) + errx(1, "unknown user %s", RELAYD_USER); + + log_init(debug); + + if (!debug) { + if (daemon(1, 0) == -1) + err(1, "failed to daemonize"); + } + + 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"); + for (c = 0; c < env->sc_prefork_relay; c++) { + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, + pipe_parent2relay[c]) == -1) + fatal("socketpair"); + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, + pipe_pfe2relay[c]) == -1) + fatal("socketpair"); + session_socket_blockmode(pipe_pfe2relay[c][0], BM_NONBLOCK); + session_socket_blockmode(pipe_pfe2relay[c][1], BM_NONBLOCK); + session_socket_blockmode(pipe_parent2relay[c][0], BM_NONBLOCK); + session_socket_blockmode(pipe_parent2relay[c][1], BM_NONBLOCK); + } + + 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_parent2relay, pipe_pfe2hce, pipe_pfe2relay); + hce_pid = hce(env, pipe_parent2pfe, pipe_parent2hce, + pipe_parent2relay, pipe_pfe2hce, pipe_pfe2relay); + if (env->sc_prefork_relay > 0) + relay_pid = relay(env, pipe_parent2pfe, pipe_parent2hce, + pipe_parent2relay, pipe_pfe2hce, pipe_pfe2relay); + + setproctitle("parent"); + + event_init(); + + signal_set(&ev_sigint, SIGINT, main_sig_handler, env); + signal_set(&ev_sigterm, SIGTERM, main_sig_handler, env); + signal_set(&ev_sigchld, SIGCHLD, main_sig_handler, env); + signal_set(&ev_sighup, SIGHUP, main_sig_handler, env); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + signal_add(&ev_sigchld, NULL); + signal_add(&ev_sighup, NULL); + signal(SIGPIPE, SIG_IGN); + + close(pipe_parent2pfe[1]); + close(pipe_parent2hce[1]); + close(pipe_pfe2hce[0]); + close(pipe_pfe2hce[1]); + for (c = 0; c < env->sc_prefork_relay; c++) { + close(pipe_pfe2relay[c][0]); + close(pipe_pfe2relay[c][1]); + close(pipe_parent2relay[c][0]); + } + + if ((iev_pfe = calloc(1, sizeof(struct imsgev))) == NULL || + (iev_hce = calloc(1, sizeof(struct imsgev))) == NULL) + fatal(NULL); + + if (env->sc_prefork_relay > 0) { + if ((iev_relay = calloc(env->sc_prefork_relay, + sizeof(struct imsgev))) == NULL) + fatal(NULL); + } + + imsg_init(&iev_pfe->ibuf, pipe_parent2pfe[0]); + imsg_init(&iev_hce->ibuf, pipe_parent2hce[0]); + iev_pfe->handler = main_dispatch_pfe; + iev_hce->handler = main_dispatch_hce; + + for (c = 0; c < env->sc_prefork_relay; c++) { + iev = &iev_relay[c]; + imsg_init(&iev->ibuf, pipe_parent2relay[c][1]); + iev->handler = main_dispatch_relay; + iev->events = EV_READ; + event_set(&iev->ev, iev->ibuf.fd, iev->events, + iev->handler, iev); + event_add(&iev->ev, NULL); + } + + iev_pfe->events = EV_READ; + event_set(&iev_pfe->ev, iev_pfe->ibuf.fd, iev_pfe->events, + iev_pfe->handler, iev_pfe); + event_add(&iev_pfe->ev, NULL); + + iev_hce->events = EV_READ; + event_set(&iev_hce->ev, iev_hce->ibuf.fd, iev_hce->events, + iev_hce->handler, iev_hce); + event_add(&iev_hce->ev, NULL); + +#ifndef __FreeBSD__ + if (env->sc_flags & F_DEMOTE) + carp_demote_reset(env->sc_demote_group, 0); +#endif + event_dispatch(); + + main_shutdown(env); + /* NOTREACHED */ + return (0); +} + +void +main_shutdown(struct relayd *env) +{ + pid_t pid; + + if (pfe_pid) + kill(pfe_pid, SIGTERM); + if (hce_pid) + kill(hce_pid, SIGTERM); + if (relay_pid) + kill(relay_pid, SIGTERM); + + do { + if ((pid = wait(NULL)) == -1 && + errno != EINTR && errno != ECHILD) + fatal("wait"); + } while (pid != -1 || (pid == -1 && errno == EINTR)); + + control_cleanup(); +#ifndef __FreeBSD__ + carp_demote_shutdown(); + if (env->sc_flags & F_DEMOTE) + carp_demote_reset(env->sc_demote_group, 128); +#endif + 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); +} + +int +send_all(struct relayd *env, enum imsg_type type, void *buf, u_int16_t len) +{ + int i; + + if (imsg_compose_event(iev_pfe, type, 0, 0, -1, buf, len) == -1) + return (-1); + if (imsg_compose_event(iev_hce, type, 0, 0, -1, buf, len) == -1) + return (-1); + for (i = 0; i < env->sc_prefork_relay; i++) { + if (imsg_compose_event(&iev_relay[i], type, 0, 0, -1, buf, len) + == -1) + return (-1); + } + return (0); +} + +void +merge_config(struct relayd *env, struct relayd *new_env) +{ + env->sc_opts = new_env->sc_opts; + env->sc_flags = new_env->sc_flags; + env->sc_confpath = new_env->sc_confpath; + env->sc_tablecount = new_env->sc_tablecount; + env->sc_rdrcount = new_env->sc_rdrcount; + env->sc_protocount = new_env->sc_protocount; + env->sc_relaycount = new_env->sc_relaycount; + + memcpy(&env->sc_interval, &new_env->sc_interval, + sizeof(env->sc_interval)); + memcpy(&env->sc_timeout, &new_env->sc_timeout, + sizeof(env->sc_timeout)); + memcpy(&env->sc_empty_table, &new_env->sc_empty_table, + sizeof(env->sc_empty_table)); + memcpy(&env->sc_proto_default, &new_env->sc_proto_default, + sizeof(env->sc_proto_default)); + env->sc_prefork_relay = new_env->sc_prefork_relay; +#ifndef __FreeBSD__ + (void)strlcpy(env->sc_demote_group, new_env->sc_demote_group, + sizeof(env->sc_demote_group)); +#endif + + env->sc_tables = new_env->sc_tables; + env->sc_rdrs = new_env->sc_rdrs; + env->sc_relays = new_env->sc_relays; + env->sc_protos = new_env->sc_protos; +} + + +void +reconfigure(void) +{ + struct relayd *env = relayd_env; + struct relayd *new_env = NULL; + struct rdr *rdr; + struct address *virt; + struct table *table; + struct host *host; + + log_info("reloading configuration"); + if ((new_env = parse_config(env->sc_confpath, env->sc_opts)) == NULL) { + log_warnx("configuration reloading FAILED"); + return; + } + + if (!(env->sc_flags & F_NEEDPF) && (new_env->sc_flags & F_NEEDPF)) { + log_warnx("new configuration requires pf while it " + "was previously disabled." + "configuration will not be reloaded"); + purge_config(new_env, PURGE_EVERYTHING); + free(new_env); + return; + } + + purge_config(env, PURGE_EVERYTHING); + merge_config(env, new_env); + free(new_env); + log_info("configuration merge done"); + + /* + * first reconfigure pfe + */ + imsg_compose_event(iev_pfe, IMSG_RECONF, 0, 0, -1, env, sizeof(*env)); + TAILQ_FOREACH(table, env->sc_tables, entry) { + imsg_compose_event(iev_pfe, IMSG_RECONF_TABLE, 0, 0, -1, + &table->conf, sizeof(table->conf)); + TAILQ_FOREACH(host, &table->hosts, entry) { + imsg_compose_event(iev_pfe, IMSG_RECONF_HOST, 0, 0, -1, + &host->conf, sizeof(host->conf)); + } + } + TAILQ_FOREACH(rdr, env->sc_rdrs, entry) { + imsg_compose_event(iev_pfe, IMSG_RECONF_RDR, 0, 0, -1, + &rdr->conf, sizeof(rdr->conf)); + TAILQ_FOREACH(virt, &rdr->virts, entry) + imsg_compose_event(iev_pfe, IMSG_RECONF_VIRT, 0, 0, -1, + virt, sizeof(*virt)); + } + imsg_compose_event(iev_pfe, IMSG_RECONF_END, 0, 0, -1, NULL, 0); + + /* + * then reconfigure hce + */ + imsg_compose_event(iev_hce, IMSG_RECONF, 0, 0, -1, env, sizeof(*env)); + TAILQ_FOREACH(table, env->sc_tables, entry) { + imsg_compose_event(iev_hce, IMSG_RECONF_TABLE, 0, 0, -1, + &table->conf, sizeof(table->conf)); + if (table->sendbuf != NULL) + imsg_compose_event(iev_hce, IMSG_RECONF_SENDBUF, + 0, 0, -1, table->sendbuf, + strlen(table->sendbuf) + 1); + TAILQ_FOREACH(host, &table->hosts, entry) { + imsg_compose_event(iev_hce, IMSG_RECONF_HOST, 0, 0, -1, + &host->conf, sizeof(host->conf)); + } + } + imsg_compose_event(iev_hce, IMSG_RECONF_END, 0, 0, -1, NULL, 0); +} + +void +purge_config(struct relayd *env, u_int8_t what) +{ + struct table *table; + struct rdr *rdr; + struct address *virt; + struct protocol *proto; + struct relay *rlay; + struct rsession *sess; + + if (what & PURGE_TABLES && env->sc_tables != NULL) { + while ((table = TAILQ_FIRST(env->sc_tables)) != NULL) + purge_table(env->sc_tables, table); + free(env->sc_tables); + env->sc_tables = NULL; + } + + if (what & PURGE_RDRS && env->sc_rdrs != NULL) { + while ((rdr = TAILQ_FIRST(env->sc_rdrs)) != NULL) { + TAILQ_REMOVE(env->sc_rdrs, rdr, entry); + while ((virt = TAILQ_FIRST(&rdr->virts)) != NULL) { + TAILQ_REMOVE(&rdr->virts, virt, entry); + free(virt); + } + free(rdr); + } + free(env->sc_rdrs); + env->sc_rdrs = NULL; + } + + if (what & PURGE_RELAYS && env->sc_relays != NULL) { + while ((rlay = TAILQ_FIRST(env->sc_relays)) != NULL) { + TAILQ_REMOVE(env->sc_relays, rlay, rl_entry); + while ((sess = + SPLAY_ROOT(&rlay->rl_sessions)) != NULL) { + SPLAY_REMOVE(session_tree, + &rlay->rl_sessions, sess); + free(sess); + } + if (rlay->rl_bev != NULL) + bufferevent_free(rlay->rl_bev); + if (rlay->rl_dstbev != NULL) + bufferevent_free(rlay->rl_dstbev); + if (rlay->rl_ssl_ctx != NULL) + SSL_CTX_free(rlay->rl_ssl_ctx); + free(rlay); + } + free(env->sc_relays); + env->sc_relays = NULL; + } + + if (what & PURGE_PROTOS && env->sc_protos != NULL) { + while ((proto = TAILQ_FIRST(env->sc_protos)) != NULL) { + TAILQ_REMOVE(env->sc_protos, proto, entry); + purge_tree(&proto->request_tree); + purge_tree(&proto->response_tree); + if (proto->style != NULL) + free(proto->style); + free(proto); + } + free(env->sc_protos); + env->sc_protos = NULL; + } +} + +void +purge_tree(struct proto_tree *tree) +{ + struct protonode *proot, *pn; + + while ((proot = RB_ROOT(tree)) != NULL) { + RB_REMOVE(proto_tree, tree, proot); + if (proot->key != NULL) + free(proot->key); + if (proot->value != NULL) + free(proot->value); + while ((pn = SIMPLEQ_FIRST(&proot->head)) != NULL) { + SIMPLEQ_REMOVE_HEAD(&proot->head, entry); + if (pn->key != NULL) + free(pn->key); + if (pn->value != NULL) + free(pn->value); + if (pn->label != 0) + pn_unref(pn->label); + free(pn); + } + free(proot); + } +} + +void +purge_table(struct tablelist *head, struct table *table) +{ + struct host *host; + + while ((host = TAILQ_FIRST(&table->hosts)) != NULL) { + TAILQ_REMOVE(&table->hosts, host, entry); + if (host->cte.ssl != NULL) + SSL_free(host->cte.ssl); + free(host); + } + if (table->sendbuf != NULL) + free(table->sendbuf); + if (table->conf.flags & F_SSL) + SSL_CTX_free(table->ssl_ctx); + + if (head != NULL) + TAILQ_REMOVE(head, table, entry); + free(table); +} + +void +imsg_event_add(struct imsgev *iev) +{ + if (iev->handler == NULL) { + imsg_flush(&iev->ibuf); + return; + } + + iev->events = EV_READ; + if (iev->ibuf.w.queued) + iev->events |= EV_WRITE; + + event_del(&iev->ev); + event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev); + event_add(&iev->ev, NULL); +} + +int +imsg_compose_event(struct imsgev *iev, u_int16_t type, + u_int32_t peerid, pid_t pid, int fd, void *data, u_int16_t datalen) +{ + int ret; + + if ((ret = imsg_compose(&iev->ibuf, type, peerid, + pid, fd, data, datalen)) != -1) + imsg_event_add(iev); + return (ret); +} + +void +main_dispatch_pfe(int fd, short event, void *ptr) +{ + struct imsgev *iev; + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; +#ifndef __FreeBSD__ + struct ctl_demote demote; +#endif + + iev = ptr; + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1) + fatal("imsg_read_error"); + if (n == 0) { + /* this pipe is dead, so remove the event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + return; + } + } + + if (event & EV_WRITE) { + if (msgbuf_write(&ibuf->w) == -1) + fatal("msgbuf_write"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("main_dispatch_pfe: imsg_read error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { +#ifndef __FreeBSD__ + case IMSG_DEMOTE: + if (imsg.hdr.len - IMSG_HEADER_SIZE != + sizeof(demote)) + fatalx("main_dispatch_pfe: " + "invalid size of demote request"); + memcpy(&demote, imsg.data, sizeof(demote)); + carp_demote_set(demote.group, demote.level); + break; +#endif + case IMSG_CTL_RELOAD: + /* + * so far we only get here if no L7 (relay) is done. + */ + reconfigure(); + break; + default: + log_debug("main_dispatch_pfe: unexpected imsg %d", + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + imsg_event_add(iev); +} + +void +main_dispatch_hce(int fd, short event, void * ptr) +{ + struct imsgev *iev; + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + struct ctl_script scr; + struct relayd *env; + + env = relayd_env; + iev = ptr; + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1) + fatal("imsg_read error"); + if (n == 0) { + /* this pipe is dead, so remove the event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + return; + } + } + + if (event & EV_WRITE) { + if (msgbuf_write(&ibuf->w) == -1) + fatal("msgbuf_write"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("main_dispatch_hce: imsg_read error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_SCRIPT: + if (imsg.hdr.len - IMSG_HEADER_SIZE != + sizeof(scr)) + fatalx("main_dispatch_hce: " + "invalid size of script request"); + bcopy(imsg.data, &scr, sizeof(scr)); + scr.retval = script_exec(env, &scr); + imsg_compose_event(iev_hce, IMSG_SCRIPT, + 0, 0, -1, &scr, sizeof(scr)); + break; +/* + case IMSG_SNMPSOCK: + (void)snmp_sendsock(iev); + break; +*/ + default: + log_debug("main_dispatch_hce: unexpected imsg %d", + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + imsg_event_add(iev); +} + +void +main_dispatch_relay(int fd, short event, void * ptr) +{ + struct relayd *env = relayd_env; + struct imsgev *iev; + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + struct ctl_bindany bnd; + int s; + + iev = ptr; + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1) + fatal("imsg_read error"); + if (n == 0) { + /* this pipe is dead, so remove the event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + return; + } + } + + if (event & EV_WRITE) { + if (msgbuf_write(&ibuf->w) == -1) + fatal("msgbuf_write"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("main_dispatch_relay: imsg_read error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_BINDANY: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(bnd)) + fatalx("invalid imsg header len"); + bcopy(imsg.data, &bnd, sizeof(bnd)); + if (bnd.bnd_proc > env->sc_prefork_relay) + fatalx("pfe_dispatch_relay: " + "invalid relay proc"); + switch (bnd.bnd_proto) { + case IPPROTO_TCP: + case IPPROTO_UDP: + break; + default: + fatalx("pfe_dispatch_relay: requested socket " + "for invalid protocol"); + /* NOTREACHED */ + } + s = bindany(&bnd); + imsg_compose_event(&iev_relay[bnd.bnd_proc], + IMSG_BINDANY, + 0, 0, s, &bnd.bnd_id, sizeof(bnd.bnd_id)); + break; + default: + log_debug("main_dispatch_relay: unexpected imsg %d", + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + imsg_event_add(iev); +} + +struct host * +host_find(struct relayd *env, objid_t id) +{ + struct table *table; + struct host *host; + + TAILQ_FOREACH(table, env->sc_tables, entry) + TAILQ_FOREACH(host, &table->hosts, entry) + if (host->conf.id == id) + return (host); + return (NULL); +} + +struct table * +table_find(struct relayd *env, objid_t id) +{ + struct table *table; + + TAILQ_FOREACH(table, env->sc_tables, entry) + if (table->conf.id == id) + return (table); + return (NULL); +} + +struct rdr * +rdr_find(struct relayd *env, objid_t id) +{ + struct rdr *rdr; + + TAILQ_FOREACH(rdr, env->sc_rdrs, entry) + if (rdr->conf.id == id) + return (rdr); + return (NULL); +} + +struct relay * +relay_find(struct relayd *env, objid_t id) +{ + struct relay *rlay; + + TAILQ_FOREACH(rlay, env->sc_relays, rl_entry) + if (rlay->rl_conf.id == id) + return (rlay); + return (NULL); +} + +struct rsession * +session_find(struct relayd *env, objid_t id) +{ + struct relay *rlay; + struct rsession *con; + + TAILQ_FOREACH(rlay, env->sc_relays, rl_entry) + SPLAY_FOREACH(con, session_tree, &rlay->rl_sessions) + if (con->se_id == id) + return (con); + return (NULL); +} + +struct host * +host_findbyname(struct relayd *env, const char *name) +{ + struct table *table; + struct host *host; + + TAILQ_FOREACH(table, env->sc_tables, entry) + TAILQ_FOREACH(host, &table->hosts, entry) + if (strcmp(host->conf.name, name) == 0) + return (host); + return (NULL); +} + +struct table * +table_findbyname(struct relayd *env, const char *name) +{ + struct table *table; + + TAILQ_FOREACH(table, env->sc_tables, entry) + if (strcmp(table->conf.name, name) == 0) + return (table); + return (NULL); +} + +struct table * +table_findbyconf(struct relayd *env, struct table *tb) +{ + struct table *table; + struct table_config a, b; + + bcopy(&tb->conf, &a, sizeof(a)); + a.id = a.rdrid = 0; + a.flags &= ~(F_USED|F_BACKUP); + + TAILQ_FOREACH(table, env->sc_tables, entry) { + bcopy(&table->conf, &b, sizeof(b)); + b.id = b.rdrid = 0; + b.flags &= ~(F_USED|F_BACKUP); + + /* + * Compare two tables and return the existing table if + * the configuration seems to be the same. + */ + if (bcmp(&a, &b, sizeof(b)) == 0 && + ((tb->sendbuf == NULL && table->sendbuf == NULL) || + (tb->sendbuf != NULL && table->sendbuf != NULL && + strcmp(tb->sendbuf, table->sendbuf) == 0))) + return (table); + } + return (NULL); +} + +struct rdr * +rdr_findbyname(struct relayd *env, const char *name) +{ + struct rdr *rdr; + + TAILQ_FOREACH(rdr, env->sc_rdrs, entry) + if (strcmp(rdr->conf.name, name) == 0) + return (rdr); + return (NULL); +} + +struct relay * +relay_findbyname(struct relayd *env, const char *name) +{ + struct relay *rlay; + + TAILQ_FOREACH(rlay, env->sc_relays, rl_entry) + if (strcmp(rlay->rl_conf.name, name) == 0) + return (rlay); + return (NULL); +} + +struct relay * +relay_findbyaddr(struct relayd *env, struct relay_config *rc) +{ + struct relay *rlay; + + TAILQ_FOREACH(rlay, env->sc_relays, rl_entry) + if (bcmp(&rlay->rl_conf.ss, &rc->ss, sizeof(rc->ss)) == 0 && + rlay->rl_conf.port == rc->port) + return (rlay); + return (NULL); +} + +void +event_again(struct event *ev, int fd, short event, + void (*fn)(int, short, void *), + struct timeval *start, struct timeval *end, void *arg) +{ + struct timeval tv_next, tv_now, tv; + + if (gettimeofday(&tv_now, NULL) == -1) + fatal("event_again: gettimeofday"); + + bcopy(end, &tv_next, sizeof(tv_next)); + timersub(&tv_now, start, &tv_now); + timersub(&tv_next, &tv_now, &tv_next); + + bzero(&tv, sizeof(tv)); + if (timercmp(&tv_next, &tv, >)) + bcopy(&tv_next, &tv, sizeof(tv)); + + event_del(ev); + event_set(ev, fd, event, fn, arg); + event_add(ev, &tv); +} + +int +expand_string(char *label, size_t len, const char *srch, const char *repl) +{ + char *tmp; + char *p, *q; + + if ((tmp = calloc(1, len)) == NULL) { + log_debug("expand_string: calloc"); + return (-1); + } + p = q = label; + while ((q = strstr(p, srch)) != NULL) { + *q = '\0'; + if ((strlcat(tmp, p, len) >= len) || + (strlcat(tmp, repl, len) >= len)) { + log_debug("expand_string: string too long"); + return (-1); + } + q += strlen(srch); + p = q; + } + if (strlcat(tmp, p, len) >= len) { + log_debug("expand_string: string too long"); + return (-1); + } + (void)strlcpy(label, tmp, len); /* always fits */ + free(tmp); + + return (0); +} + +void +translate_string(char *str) +{ + char *reader; + char *writer; + + reader = writer = str; + + while (*reader) { + if (*reader == '\\') { + reader++; + switch (*reader) { + case 'n': + *writer++ = '\n'; + break; + case 'r': + *writer++ = '\r'; + break; + default: + *writer++ = *reader; + } + } else + *writer++ = *reader; + reader++; + } + *writer = '\0'; +} + +char * +digeststr(enum digest_type type, const u_int8_t *data, size_t len, char *buf) +{ + switch (type) { + case DIGEST_SHA1: +#ifdef __FreeBSD__ + return (SHA1_Data(data, len, buf)); +#else + return (SHA1Data(data, len, buf)); +#endif + break; + case DIGEST_MD5: + return (MD5Data(data, len, buf)); + break; + default: + break; + } + return (NULL); +} + +const char * +canonicalize_host(const char *host, char *name, size_t len) +{ + struct sockaddr_in sin4; + struct sockaddr_in6 sin6; + u_int i, j; + size_t plen; + char c; + + if (len < 2) + goto fail; + + /* + * Canonicalize an IPv4/6 address + */ + if (inet_pton(AF_INET, host, &sin4) == 1) + return (inet_ntop(AF_INET, &sin4, name, len)); + if (inet_pton(AF_INET6, host, &sin6) == 1) + return (inet_ntop(AF_INET6, &sin6, name, len)); + + /* + * Canonicalize a hostname + */ + + /* 1. remove repeated dots and convert upper case to lower case */ + plen = strlen(host); + bzero(name, len); + for (i = j = 0; i < plen; i++) { + if (j >= (len - 1)) + goto fail; + c = tolower(host[i]); + if ((c == '.') && (j == 0 || name[j - 1] == '.')) + continue; + name[j++] = c; + } + + /* 2. remove trailing dots */ + for (i = j; i > 0; i--) { + if (name[i - 1] != '.') + break; + name[i - 1] = '\0'; + j--; + } + if (j <= 0) + goto fail; + + return (name); + + fail: + errno = EINVAL; + return (NULL); +} + +struct protonode * +protonode_header(enum direction dir, struct protocol *proto, + struct protonode *pk) +{ + struct protonode *pn; + struct proto_tree *tree; + + if (dir == RELAY_DIR_RESPONSE) + tree = &proto->response_tree; + else + tree = &proto->request_tree; + + pn = RB_FIND(proto_tree, tree, pk); + if (pn != NULL) + return (pn); + if ((pn = (struct protonode *)calloc(1, sizeof(*pn))) == NULL) { + log_warn("out of memory"); + return (NULL); + } + pn->key = strdup(pk->key); + if (pn->key == NULL) { + free(pn); + log_warn("out of memory"); + return (NULL); + } + pn->value = NULL; + pn->action = NODE_ACTION_NONE; + pn->type = pk->type; + SIMPLEQ_INIT(&pn->head); + if (dir == RELAY_DIR_RESPONSE) + pn->id = + proto->response_nodes++; + else + pn->id = proto->request_nodes++; + if (pn->id == INT_MAX) { + log_warnx("too many protocol " + "nodes defined"); + return (NULL); + } + RB_INSERT(proto_tree, tree, pn); + return (pn); +} + +int +protonode_add(enum direction dir, struct protocol *proto, + struct protonode *node) +{ + struct protonode *pn, *proot, pk; + struct proto_tree *tree; + + if (dir == RELAY_DIR_RESPONSE) + tree = &proto->response_tree; + else + tree = &proto->request_tree; + + if ((pn = calloc(1, sizeof (*pn))) == NULL) { + log_warn("out of memory"); + return (-1); + } + bcopy(node, pn, sizeof(*pn)); + pn->key = node->key; + pn->value = node->value; + SIMPLEQ_INIT(&pn->head); + if (dir == RELAY_DIR_RESPONSE) + pn->id = proto->response_nodes++; + else + pn->id = proto->request_nodes++; + if (pn->id == INT_MAX) { + log_warnx("too many protocol nodes defined"); + free(pn); + return (-1); + } + if ((proot = + RB_INSERT(proto_tree, tree, pn)) != NULL) { + /* + * A protocol node with the same key already + * exists, append it to a queue behind the + * existing node-> + */ + if (SIMPLEQ_EMPTY(&proot->head)) + SIMPLEQ_NEXT(proot, entry) = pn; + SIMPLEQ_INSERT_TAIL(&proot->head, pn, entry); + } + + if (node->type == NODE_TYPE_COOKIE) + pk.key = "Cookie"; + else if (node->type == NODE_TYPE_URL) + pk.key = "Host"; + else + pk.key = "GET"; + if (node->type != NODE_TYPE_HEADER) { + pk.type = NODE_TYPE_HEADER; + pn = protonode_header(dir, proto, &pk); + if (pn == NULL) + return (-1); + switch (node->type) { + case NODE_TYPE_QUERY: + pn->flags |= PNFLAG_LOOKUP_QUERY; + break; + case NODE_TYPE_COOKIE: + pn->flags |= PNFLAG_LOOKUP_COOKIE; + break; + case NODE_TYPE_URL: + if (node->flags & + PNFLAG_LOOKUP_URL_DIGEST) + pn->flags |= node->flags & + PNFLAG_LOOKUP_URL_DIGEST; + else + pn->flags |= + PNFLAG_LOOKUP_DIGEST(0); + break; + default: + break; + } + } + + return (0); +} + +int +protonode_load(enum direction dir, struct protocol *proto, + struct protonode *node, const char *name) +{ + FILE *fp; + char buf[BUFSIZ]; + int ret = -1; + struct protonode pn; + + bcopy(node, &pn, sizeof(pn)); + pn.key = pn.value = NULL; + + if ((fp = fopen(name, "r")) == NULL) + return (-1); + + while (fgets(buf, sizeof(buf), fp) != NULL) { + /* strip whitespace and newline characters */ + buf[strcspn(buf, "\r\n\t ")] = '\0'; + if (!strlen(buf) || buf[0] == '#') + continue; + pn.key = strdup(buf); + if (node->value != NULL) + pn.value = strdup(node->value); + if (pn.key == NULL || + (node->value != NULL && pn.value == NULL)) + goto fail; + if (protonode_add(dir, proto, &pn) == -1) + goto fail; + pn.key = pn.value = NULL; + } + + ret = 0; + fail: + if (pn.key != NULL) + free(pn.key); + if (pn.value != NULL) + free(pn.value); + fclose(fp); + return (ret); +} + +int +bindany(struct ctl_bindany *bnd) +{ + int s, v; + + s = -1; + v = 1; + + if (relay_socket_af(&bnd->bnd_ss, bnd->bnd_port) == -1) + goto fail; + if ((s = socket(bnd->bnd_ss.ss_family, + bnd->bnd_proto == IPPROTO_TCP ? SOCK_STREAM : SOCK_DGRAM, + bnd->bnd_proto)) == -1) + goto fail; +#ifndef __FreeBSD__ + if (setsockopt(s, SOL_SOCKET, SO_BINDANY, + &v, sizeof(v)) == -1) + goto fail; +#endif + if (bind(s, (struct sockaddr *)&bnd->bnd_ss, + bnd->bnd_ss.ss_len) == -1) + goto fail; + + return (s); + + fail: + if (s != -1) + close(s); + return (-1); +} + +int +map6to4(struct sockaddr_storage *in6) +{ + struct sockaddr_storage out4; + struct sockaddr_in *sin4 = (struct sockaddr_in *)&out4; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)in6; + + bzero(sin4, sizeof(*sin4)); + sin4->sin_len = sizeof(*sin4); + sin4->sin_family = AF_INET; + sin4->sin_port = sin6->sin6_port; + + bcopy(&sin6->sin6_addr.s6_addr[12], &sin4->sin_addr.s_addr, + sizeof(sin4->sin_addr)); + + if (sin4->sin_addr.s_addr == INADDR_ANY || + sin4->sin_addr.s_addr == INADDR_BROADCAST || + IN_MULTICAST(ntohl(sin4->sin_addr.s_addr))) + return (-1); + + bcopy(&out4, in6, sizeof(*in6)); + + return (0); +} + +int +map4to6(struct sockaddr_storage *in4, struct sockaddr_storage *map) +{ + struct sockaddr_storage out6; + struct sockaddr_in *sin4 = (struct sockaddr_in *)in4; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&out6; + struct sockaddr_in6 *map6 = (struct sockaddr_in6 *)map; + + if (sin4->sin_addr.s_addr == INADDR_ANY || + sin4->sin_addr.s_addr == INADDR_BROADCAST || + IN_MULTICAST(ntohl(sin4->sin_addr.s_addr))) + return (-1); + + bcopy(map6, sin6, sizeof(*sin6)); + sin6->sin6_len = sizeof(*sin6); + sin6->sin6_family = AF_INET6; + sin6->sin6_port = sin4->sin_port; + + bcopy(&sin4->sin_addr.s_addr, &sin6->sin6_addr.s6_addr[12], + sizeof(sin4->sin_addr)); + + bcopy(&out6, in4, sizeof(*in4)); + + return (0); +} Index: contrib/relayd/log.c =================================================================== --- contrib/relayd/log.c (revision 0) +++ contrib/relayd/log.c (revision 0) @@ -0,0 +1,409 @@ +/* $OpenBSD: log.c,v 1.14 2008/12/05 16:37:55 reyk Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * 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. + */ + +#ifdef __FreeBSD__ +#include +#else +#include +#endif +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "relayd.h" + +int debug; + +void vlog(int, const char *, va_list); +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 > 1) { + 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); +} + +const char * +host_error(enum host_error he) +{ + switch (he) { + case HCE_NONE: + return ("none"); + break; + case HCE_ABORT: + return ("aborted"); + break; + case HCE_INTERVAL_TIMEOUT: + return ("interval timeout"); + break; + case HCE_ICMP_OK: + return ("icmp ok"); + break; + case HCE_ICMP_READ_TIMEOUT: + return ("icmp read timeout"); + break; + case HCE_ICMP_WRITE_TIMEOUT: + return ("icmp write timeout"); + break; + case HCE_TCP_CONNECT_ERROR: + return ("tcp connect error"); + break; + case HCE_TCP_CONNECT_FAIL: + return ("tcp connect failed"); + break; + case HCE_TCP_CONNECT_TIMEOUT: + return ("tcp connect timeout"); + break; + case HCE_TCP_CONNECT_OK: + return ("tcp connect ok"); + break; + case HCE_TCP_WRITE_TIMEOUT: + return ("tcp write timeout"); + break; + case HCE_TCP_WRITE_FAIL: + return ("tcp write failed"); + break; + case HCE_TCP_READ_TIMEOUT: + return ("tcp read timeout"); + break; + case HCE_TCP_READ_FAIL: + return ("tcp read failed"); + break; + case HCE_SCRIPT_OK: + return ("script ok"); + break; + case HCE_SCRIPT_FAIL: + return ("script failed"); + break; + case HCE_SSL_CONNECT_OK: + return ("ssl connect ok"); + break; + case HCE_SSL_CONNECT_FAIL: + return ("ssl connect failed"); + break; + case HCE_SSL_CONNECT_TIMEOUT: + return ("ssl connect timeout"); + break; + case HCE_SSL_CONNECT_ERROR: + return ("ssl connect error"); + break; + case HCE_SSL_READ_TIMEOUT: + return ("ssl read timeout"); + break; + case HCE_SSL_WRITE_TIMEOUT: + return ("ssl write timeout"); + break; + case HCE_SSL_READ_ERROR: + return ("ssl read error"); + break; + case HCE_SSL_WRITE_ERROR: + return ("ssl write error"); + break; + case HCE_SEND_EXPECT_FAIL: + return ("send/expect failed"); + break; + case HCE_SEND_EXPECT_OK: + return ("send/expect ok"); + break; + case HCE_HTTP_CODE_ERROR: + return ("http code malformed"); + break; + case HCE_HTTP_CODE_FAIL: + return ("http code mismatch"); + break; + case HCE_HTTP_CODE_OK: + return ("http code ok"); + break; + case HCE_HTTP_DIGEST_ERROR: + return ("http digest malformed"); + break; + case HCE_HTTP_DIGEST_FAIL: + return ("http digest mismatch"); + break; + case HCE_HTTP_DIGEST_OK: + return ("http digest ok"); + break; + } + /* NOTREACHED */ + return ("invalid"); +} + +const char * +host_status(enum host_status status) +{ + switch (status) { + case HOST_DOWN: + return ("down"); + case HOST_UNKNOWN: + return ("unknown"); + case HOST_UP: + return ("up"); + }; + /* NOTREACHED */ + return ("invalid"); +} + +const char * +table_check(enum table_check check) +{ + switch (check) { + case CHECK_NOCHECK: + return ("none"); + case CHECK_ICMP: + return ("icmp"); + case CHECK_TCP: + return ("tcp"); + case CHECK_HTTP_CODE: + return ("http code"); + case CHECK_HTTP_DIGEST: + return ("http digest"); + case CHECK_SEND_EXPECT: + return ("send expect"); + case CHECK_SCRIPT: + return ("script"); + }; + /* NOTREACHED */ + return ("invalid"); +} + +const char * +print_availability(u_long cnt, u_long up) +{ + static char buf[BUFSIZ]; + + if (cnt == 0) + return (""); + bzero(buf, sizeof(buf)); + snprintf(buf, sizeof(buf), "%.2f%%", (double)up / cnt * 100); + return (buf); +} + +const char * +print_host(struct sockaddr_storage *ss, char *buf, size_t len) +{ + if (getnameinfo((struct sockaddr *)ss, ss->ss_len, + buf, len, NULL, 0, NI_NUMERICHOST) != 0) { + buf[0] = '\0'; + return (NULL); + } + return (buf); +} + +const char * +print_time(struct timeval *a, struct timeval *b, char *buf, size_t len) +{ + struct timeval tv; + u_long h, sec, min; + + timerclear(&tv); + timersub(a, b, &tv); + sec = tv.tv_sec % 60; + min = tv.tv_sec / 60 % 60; + h = tv.tv_sec / 60 / 60; + + snprintf(buf, len, "%.2lu:%.2lu:%.2lu", h, min, sec); + return (buf); +} + +const char * +print_httperror(u_int code) +{ + u_int i; + struct { + u_int ht_code; + const char *ht_err; + } httperr[] = { + { 100, "Continue" }, + { 101, "Switching Protocols" }, + { 200, "OK" }, + { 201, "Created" }, + { 202, "Accepted" }, + { 203, "Non-Authorative Information" }, + { 204, "No Content" }, + { 205, "Reset Content" }, + { 206, "Partial Content" }, + { 300, "Multiple Choices" }, + { 301, "Moved Permanently" }, + { 302, "Moved Temporarily" }, + { 303, "See Other" }, + { 304, "Not Modified" }, + { 307, "Temporary Redirect" }, + { 400, "Bad Request" }, + { 401, "Unauthorized" }, + { 402, "Payment Required" }, + { 403, "Forbidden" }, + { 404, "Not Found" }, + { 405, "Method Not Allowed" }, + { 406, "Not Acceptable" }, + { 407, "Proxy Authentication Required" }, + { 408, "Request Timeout" }, + { 409, "Conflict" }, + { 410, "Gone" }, + { 411, "Length Required" }, + { 412, "Precondition Failed" }, + { 413, "Request Entity Too Large" }, + { 414, "Request-URL Too Long" }, + { 415, "Unsupported Media Type" }, + { 416, "Requested Range Not Satisfiable" }, + { 417, "Expectation Failed" }, + { 500, "Internal Server Error" }, + { 501, "Not Implemented" }, + { 502, "Bad Gateway" }, + { 503, "Service Unavailable" }, + { 504, "Gateway Timeout" }, + { 505, "HTTP Version Not Supported" }, + { 0 } + }; + + for (i = 0; httperr[i].ht_code != 0; i++) + if (httperr[i].ht_code == code) + return (httperr[i].ht_err); + return ("Unknown Error"); +} Index: contrib/relayd/check_tcp.c =================================================================== --- contrib/relayd/check_tcp.c (revision 0) +++ contrib/relayd/check_tcp.c (revision 0) @@ -0,0 +1,414 @@ +/* $OpenBSD: check_tcp.c,v 1.35 2009/08/07 11:10:23 reyk Exp $ */ + +/* + * Copyright (c) 2006 Pierre-Yves Ritschard + * + * 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 +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "relayd.h" + +void tcp_write(int, short, void *); +void tcp_host_up(int, struct ctl_tcp_event *); +void tcp_send_req(int, short, void *); +void tcp_read_buf(int, short, void *); + +int check_http_code(struct ctl_tcp_event *); +int check_http_digest(struct ctl_tcp_event *); +int check_send_expect(struct ctl_tcp_event *); + +void +check_tcp(struct ctl_tcp_event *cte) +{ + int s; + int type; + socklen_t len; + struct timeval tv; + struct linger lng; + int he = HCE_TCP_CONNECT_ERROR; + + switch (cte->host->conf.ss.ss_family) { + case AF_INET: + ((struct sockaddr_in *)&cte->host->conf.ss)->sin_port = + cte->table->conf.port; + break; + case AF_INET6: + ((struct sockaddr_in6 *)&cte->host->conf.ss)->sin6_port = + cte->table->conf.port; + break; + } + + len = ((struct sockaddr *)&cte->host->conf.ss)->sa_len; + + if ((s = socket(cte->host->conf.ss.ss_family, SOCK_STREAM, 0)) == -1) + goto bad; + + bzero(&lng, sizeof(lng)); + if (setsockopt(s, SOL_SOCKET, SO_LINGER, &lng, sizeof(lng)) == -1) + goto bad; + + type = 1; + if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &type, sizeof(type)) == -1) + goto bad; + + if (cte->host->conf.ttl > 0) { + if (setsockopt(s, IPPROTO_IP, IP_TTL, + &cte->host->conf.ttl, sizeof(int)) == -1) + goto bad; + } + + if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) + goto bad; + + bcopy(&cte->table->conf.timeout, &tv, sizeof(tv)); + if (connect(s, (struct sockaddr *)&cte->host->conf.ss, len) == -1) { + if (errno != EINPROGRESS) { + he = HCE_TCP_CONNECT_FAIL; + goto bad; + } + } + + cte->buf = NULL; + cte->host->up = HOST_UP; + event_del(&cte->ev); + event_set(&cte->ev, s, EV_TIMEOUT|EV_WRITE, tcp_write, cte); + event_add(&cte->ev, &tv); + return; + +bad: + close(s); + cte->host->up = HOST_DOWN; + hce_notify_done(cte->host, he); +} + +void +tcp_write(int s, short event, void *arg) +{ + struct ctl_tcp_event *cte = arg; + int err; + socklen_t len; + + if (event == EV_TIMEOUT) { + close(s); + cte->host->up = HOST_DOWN; + hce_notify_done(cte->host, HCE_TCP_CONNECT_TIMEOUT); + return; + } + + len = sizeof(err); + if (getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &len)) + fatal("tcp_write: getsockopt"); + if (err != 0) { + close(s); + cte->host->up = HOST_DOWN; + hce_notify_done(cte->host, HCE_TCP_CONNECT_FAIL); + return; + } + + cte->host->up = HOST_UP; + tcp_host_up(s, cte); +} + +void +tcp_host_up(int s, struct ctl_tcp_event *cte) +{ + cte->s = s; + + switch (cte->table->conf.check) { + case CHECK_TCP: + if (cte->table->conf.flags & F_SSL) + break; + close(s); + hce_notify_done(cte->host, HCE_TCP_CONNECT_OK); + return; + case CHECK_HTTP_CODE: + cte->validate_read = NULL; + cte->validate_close = check_http_code; + break; + case CHECK_HTTP_DIGEST: + cte->validate_read = NULL; + cte->validate_close = check_http_digest; + break; + case CHECK_SEND_EXPECT: + cte->validate_read = check_send_expect; + cte->validate_close = check_send_expect; + break; + } + + if (cte->table->conf.flags & F_SSL) { + ssl_transaction(cte); + return; + } + + if (cte->table->sendbuf != NULL) { + cte->req = cte->table->sendbuf; + event_again(&cte->ev, s, EV_TIMEOUT|EV_WRITE, tcp_send_req, + &cte->tv_start, &cte->table->conf.timeout, cte); + return; + } + + if ((cte->buf = buf_dynamic(SMALL_READ_BUF_SIZE, UINT_MAX)) == NULL) + fatalx("tcp_host_up: cannot create dynamic buffer"); + event_again(&cte->ev, s, EV_TIMEOUT|EV_READ, tcp_read_buf, + &cte->tv_start, &cte->table->conf.timeout, cte); +} + +void +tcp_send_req(int s, short event, void *arg) +{ + struct ctl_tcp_event *cte = arg; + int bs; + int len; + + if (event == EV_TIMEOUT) { + cte->host->up = HOST_DOWN; + close(cte->s); + hce_notify_done(cte->host, HCE_TCP_WRITE_TIMEOUT); + return; + } + len = strlen(cte->req); + do { + bs = write(s, cte->req, len); + if (bs == -1) { + if (errno == EAGAIN || errno == EINTR) + goto retry; + log_warnx("tcp_send_req: cannot send request"); + cte->host->up = HOST_DOWN; + close(cte->s); + hce_notify_done(cte->host, HCE_TCP_WRITE_FAIL); + return; + } + cte->req += bs; + len -= bs; + } while (len > 0); + + if ((cte->buf = buf_dynamic(SMALL_READ_BUF_SIZE, UINT_MAX)) == NULL) + fatalx("tcp_send_req: cannot create dynamic buffer"); + event_again(&cte->ev, s, EV_TIMEOUT|EV_READ, tcp_read_buf, + &cte->tv_start, &cte->table->conf.timeout, cte); + return; + + retry: + event_again(&cte->ev, s, EV_TIMEOUT|EV_WRITE, tcp_send_req, + &cte->tv_start, &cte->table->conf.timeout, cte); +} + +void +tcp_read_buf(int s, short event, void *arg) +{ + ssize_t br; + char rbuf[SMALL_READ_BUF_SIZE]; + struct ctl_tcp_event *cte = arg; + + if (event == EV_TIMEOUT) { + cte->host->up = HOST_DOWN; + buf_free(cte->buf); + close(s); + hce_notify_done(cte->host, HCE_TCP_READ_TIMEOUT); + return; + } + + bzero(rbuf, sizeof(rbuf)); + br = read(s, rbuf, sizeof(rbuf) - 1); + switch (br) { + case -1: + if (errno == EAGAIN || errno == EINTR) + goto retry; + cte->host->up = HOST_DOWN; + buf_free(cte->buf); + close(cte->s); + hce_notify_done(cte->host, HCE_TCP_READ_FAIL); + return; + case 0: + cte->host->up = HOST_DOWN; + (void)cte->validate_close(cte); + close(cte->s); + buf_free(cte->buf); + hce_notify_done(cte->host, cte->host->he); + return; + default: + if (buf_add(cte->buf, rbuf, br) == -1) + fatal("tcp_read_buf: buf_add error"); + if (cte->validate_read != NULL) { + if (cte->validate_read(cte) != 0) + goto retry; + + close(cte->s); + buf_free(cte->buf); + hce_notify_done(cte->host, cte->host->he); + return; + } + break; /* retry */ + } +retry: + event_again(&cte->ev, s, EV_TIMEOUT|EV_READ, tcp_read_buf, + &cte->tv_start, &cte->table->conf.timeout, cte); +} + +int +check_send_expect(struct ctl_tcp_event *cte) +{ + u_char *b; + + /* + * ensure string is nul-terminated. + */ + b = buf_reserve(cte->buf, 1); + if (b == NULL) + fatal("out of memory"); + *b = '\0'; +#ifndef __FreeBSD__ + if (fnmatch(cte->table->conf.exbuf, cte->buf->buf, 0) == 0) { +#else + if (fnmatch(cte->table->conf.exbuf, (char *)cte->buf->buf, 0) == 0) { +#endif + cte->host->he = HCE_SEND_EXPECT_OK; + cte->host->up = HOST_UP; + return (0); + } + cte->host->he = HCE_SEND_EXPECT_FAIL; + cte->host->up = HOST_UNKNOWN; + + /* + * go back to original position. + */ + cte->buf->wpos--; + return (1); +} + +int +check_http_code(struct ctl_tcp_event *cte) +{ + char *head; + char scode[4]; + const char *estr; + u_char *b; + int code; + struct host *host; + + /* + * ensure string is nul-terminated. + */ + b = buf_reserve(cte->buf, 1); + if (b == NULL) + fatal("out of memory"); + *b = '\0'; + +#ifndef __FreeBSD__ + head = cte->buf->buf; +#else + head = (char *)cte->buf->buf; +#endif + host = cte->host; + host->he = HCE_HTTP_CODE_ERROR; + + if (strncmp(head, "HTTP/1.1 ", strlen("HTTP/1.1 ")) && + strncmp(head, "HTTP/1.0 ", strlen("HTTP/1.0 "))) { + log_debug("check_http_code: %s failed " + "(cannot parse HTTP version)", host->conf.name); + host->up = HOST_DOWN; + return (1); + } + head += strlen("HTTP/1.1 "); + if (strlen(head) < 5) /* code + \r\n */ { + host->up = HOST_DOWN; + return (1); + } + (void)strlcpy(scode, head, sizeof(scode)); + code = strtonum(scode, 100, 999, &estr); + if (estr != NULL) { + log_debug("check_http_code: %s failed " + "(cannot parse HTTP code)", host->conf.name); + host->up = HOST_DOWN; + return (1); + } + if (code != cte->table->conf.retcode) { + log_debug("check_http_code: %s failed " + "(invalid HTTP code returned)", host->conf.name); + host->he = HCE_HTTP_CODE_FAIL; + host->up = HOST_DOWN; + } else { + host->he = HCE_HTTP_CODE_OK; + host->up = HOST_UP; + } + return (!(host->up == HOST_UP)); +} + +int +check_http_digest(struct ctl_tcp_event *cte) +{ + char *head; + u_char *b; + char digest[SHA1_DIGEST_STRING_LENGTH]; + struct host *host; + + /* + * ensure string is nul-terminated. + */ + b = buf_reserve(cte->buf, 1); + if (b == NULL) + fatal("out of memory"); + *b = '\0'; + +#ifndef __FreeBSD__ + head = cte->buf->buf; +#else + head = (char *)cte->buf->buf; +#endif + host = cte->host; + host->he = HCE_HTTP_DIGEST_ERROR; + + if ((head = strstr(head, "\r\n\r\n")) == NULL) { + log_debug("check_http_digest: %s failed " + "(no end of headers)", host->conf.name); + host->up = HOST_DOWN; + return (1); + } + head += strlen("\r\n\r\n"); + +#ifndef __FreeBSD__ + digeststr(cte->table->conf.digest_type, head, strlen(head), digest); +#else + digeststr(cte->table->conf.digest_type, (u_int8_t*)head, strlen(head), digest); +#endif + + if (strcmp(cte->table->conf.digest, digest)) { + log_warnx("check_http_digest: %s failed " + "(wrong digest)", host->conf.name); + host->he = HCE_HTTP_DIGEST_FAIL; + host->up = HOST_DOWN; + } else { + host->he = HCE_HTTP_DIGEST_OK; + host->up = HOST_UP; + } + return (!(host->up == HOST_UP)); +} Index: contrib/relayd/relay_udp.c =================================================================== --- contrib/relayd/relay_udp.c (revision 0) +++ contrib/relayd/relay_udp.c (revision 0) @@ -0,0 +1,561 @@ +/* $OpenBSD: relay_udp.c,v 1.21 2009/08/07 11:21:53 reyk Exp $ */ + +/* + * Copyright (c) 2007, 2008 Reyk Floeter + * + * 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. + */ + +#ifdef __FreeBSD__ +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "relayd.h" + +extern volatile sig_atomic_t relay_sessions; +extern objid_t relay_conid; +extern int proc_id; +extern struct imsgev *iev_pfe; +extern int debug; + +struct relayd *env = NULL; +struct shuffle relay_shuffle; + +int relay_udp_socket(struct sockaddr_storage *, in_port_t, + struct protocol *); +void relay_udp_request(struct rsession *); +void relay_udp_timeout(int, short, void *); + +void relay_dns_log(struct rsession *, u_int8_t *, size_t); +void *relay_dns_validate(struct rsession *, + struct relay *, struct sockaddr_storage *, + u_int8_t *, size_t); +int relay_dns_request(struct rsession *); +void relay_udp_response(int, short, void *); +void relay_dns_result(struct rsession *, u_int8_t *, size_t); +int relay_dns_cmp(struct rsession *, struct rsession *); + +void +relay_udp_privinit(struct relayd *x_env, struct relay *rlay) +{ + if (env == NULL) + env = x_env; + + if (rlay->rl_conf.flags & F_SSL) + fatalx("ssl over udp is not supported"); + rlay->rl_conf.flags |= F_UDP; +} + +void +relay_udp_init(struct relay *rlay) +{ + struct protocol *proto = rlay->rl_proto; + + switch (proto->type) { + case RELAY_PROTO_DNS: + proto->validate = relay_dns_validate; + proto->request = relay_dns_request; + proto->cmp = relay_dns_cmp; + shuffle_init(&relay_shuffle); + break; + default: + fatalx("unsupported udp protocol"); + break; + } +} + +int +relay_udp_bind(struct sockaddr_storage *ss, in_port_t port, + struct protocol *proto) +{ + int s; + + if ((s = relay_udp_socket(ss, port, proto)) == -1) + return (-1); + + if (bind(s, (struct sockaddr *)ss, ss->ss_len) == -1) + goto bad; + + return (s); + + bad: + close(s); + return (-1); +} + +int +relay_udp_socket(struct sockaddr_storage *ss, in_port_t port, + struct protocol *proto) +{ + int s = -1, val; + + if (relay_socket_af(ss, port) == -1) + goto bad; + + if ((s = socket(ss->ss_family, SOCK_DGRAM, IPPROTO_UDP)) == -1) + goto bad; + + /* + * Socket options + */ + if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) + goto bad; + if (proto->tcpflags & TCPFLAG_BUFSIZ) { + val = proto->tcpbufsiz; + if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, + &val, sizeof(val)) == -1) + goto bad; + val = proto->tcpbufsiz; + if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, + &val, sizeof(val)) == -1) + goto bad; + } + + /* + * IP options + */ + if (proto->tcpflags & TCPFLAG_IPTTL) { + val = (int)proto->tcpipttl; + if (setsockopt(s, IPPROTO_IP, IP_TTL, + &val, sizeof(val)) == -1) + goto bad; + } + if (proto->tcpflags & TCPFLAG_IPMINTTL) { + val = (int)proto->tcpipminttl; + if (setsockopt(s, IPPROTO_IP, IP_MINTTL, + &val, sizeof(val)) == -1) + goto bad; + } + + return (s); + + bad: + if (s != -1) + close(s); + return (-1); +} + +void +relay_udp_response(int fd, short sig, void *arg) +{ + struct rsession *con = (struct rsession *)arg; + struct relay *rlay = con->se_relay; + struct protocol *proto = rlay->rl_proto; + void *priv = NULL; + struct sockaddr_storage ss; + u_int8_t buf[READ_BUF_SIZE]; + ssize_t len; + socklen_t slen; + + if (sig == EV_TIMEOUT) { + relay_udp_timeout(fd, sig, arg); + return; + } + + if (relay_sessions >= RELAY_MAX_SESSIONS || + rlay->rl_conf.flags & F_DISABLE) + return; + + slen = sizeof(ss); + if ((len = recvfrom(fd, buf, sizeof(buf), 0, + (struct sockaddr*)&ss, &slen)) < 1) + return; + + /* Parse and validate the packet header */ + if (proto->validate != NULL && + (priv = (*proto->validate)(con, rlay, &ss, buf, len)) == NULL) + return; + + relay_close(con, "unknown response"); + if (priv != NULL) + free(priv); +} + +void +relay_udp_server(int fd, short sig, void *arg) +{ + struct relay *rlay = (struct relay *)arg; + struct protocol *proto = rlay->rl_proto; + struct rsession *con = NULL; + struct ctl_natlook *cnl = NULL; + socklen_t slen; + struct timeval tv; + struct sockaddr_storage ss; + u_int8_t buf[READ_BUF_SIZE]; + void *priv = NULL; + ssize_t len; + + if (relay_sessions >= RELAY_MAX_SESSIONS || + rlay->rl_conf.flags & F_DISABLE) + return; + + slen = sizeof(ss); + if ((len = recvfrom(fd, buf, sizeof(buf), 0, + (struct sockaddr*)&ss, &slen)) < 1) + return; + + if (proto->validate != NULL && + (priv = (*proto->validate)(NULL, rlay, &ss, buf, len)) == NULL) + return; + + if ((con = (struct rsession *) + calloc(1, sizeof(struct rsession))) == NULL) { + free(priv); + return; + } + + /* + * Replace the DNS request Id with a random Id. + */ + con->se_priv = priv; + con->se_in.s = -1; + con->se_out.s = -1; + con->se_in.dst = &con->se_out; + con->se_out.dst = &con->se_in; + con->se_in.con = con; + con->se_out.con = con; + con->se_relay = rlay; + con->se_hashkey = rlay->rl_dstkey; + con->se_id = ++relay_conid; + con->se_in.tree = &proto->request_tree; + con->se_out.tree = &proto->response_tree; + con->se_in.dir = RELAY_DIR_REQUEST; + con->se_out.dir = RELAY_DIR_RESPONSE; + con->se_retry = rlay->rl_conf.dstretry; + + if (gettimeofday(&con->se_tv_start, NULL) == -1) { + free(con); + free(priv); + return; + } + + bcopy(&con->se_tv_start, &con->se_tv_last, sizeof(con->se_tv_last)); + bcopy(&ss, &con->se_in.ss, sizeof(con->se_in.ss)); + con->se_out.port = rlay->rl_conf.dstport; + switch (ss.ss_family) { + case AF_INET: + con->se_in.port = ((struct sockaddr_in *)&ss)->sin_port; + break; + case AF_INET6: + con->se_in.port = ((struct sockaddr_in6 *)&ss)->sin6_port; + break; + } + + relay_sessions++; + SPLAY_INSERT(session_tree, &rlay->rl_sessions, con); + + /* Increment the per-relay session counter */ + rlay->rl_stats[proc_id].last++; + + /* Pre-allocate output buffer */ + con->se_out.output = evbuffer_new(); + if (con->se_out.output == NULL) { + relay_close(con, "failed to allocate output buffer"); + return; + } + + /* Pre-allocate log buffer */ + con->se_log = evbuffer_new(); + if (con->se_log == NULL) { + relay_close(con, "failed to allocate log buffer"); + return; + } + + if (rlay->rl_conf.flags & F_NATLOOK) { + if ((cnl = (struct ctl_natlook *) + calloc(1, sizeof(struct ctl_natlook))) == NULL) { + relay_close(con, "failed to allocate natlookup"); + return; + } + } + + /* Save the received data */ + if (evbuffer_add(con->se_out.output, buf, len) == -1) { + relay_close(con, "failed to store buffer"); + if (cnl != NULL) + free(cnl); + return; + } + + if (rlay->rl_conf.flags & F_NATLOOK && cnl != NULL) { + con->se_cnl = cnl; + bzero(cnl, sizeof(*cnl)); + cnl->in = -1; + cnl->id = con->se_id; + cnl->proc = proc_id; + cnl->proto = IPPROTO_UDP; + bcopy(&con->se_in.ss, &cnl->src, sizeof(cnl->src)); + bcopy(&rlay->rl_conf.ss, &cnl->dst, sizeof(cnl->dst)); + imsg_compose_event(iev_pfe, IMSG_NATLOOK, 0, 0, -1, cnl, + sizeof(*cnl)); + + /* Schedule timeout */ + evtimer_set(&con->se_ev, relay_natlook, con); + bcopy(&rlay->rl_conf.timeout, &tv, sizeof(tv)); + evtimer_add(&con->se_ev, &tv); + return; + } + + relay_session(con); +} + +void +relay_udp_timeout(int fd, short sig, void *arg) +{ + struct rsession *con = (struct rsession *)arg; + + if (sig != EV_TIMEOUT) + fatalx("invalid timeout event"); + + relay_close(con, "udp timeout"); +} + +/* + * Domain Name System support + */ + +struct relay_dns_priv { + u_int16_t dp_inkey; + u_int16_t dp_outkey; +}; + +struct relay_dnshdr { + u_int16_t dns_id; + + u_int8_t dns_flags0; +#define DNS_F0_QR 0x80 /* response flag */ +#define DNS_F0_OPCODE 0x78 /* message type */ +#define DNS_F0_AA 0x04 /* authorative answer */ +#define DNS_F0_TC 0x02 /* truncated message */ +#define DNS_F0_RD 0x01 /* recursion desired */ + + u_int8_t dns_flags1; +#define DNS_F1_RA 0x80 /* recursion available */ +#define DNS_F1_RES 0x40 /* reserved */ +#define DNS_F1_AD 0x20 /* authentic data */ +#define DNS_F1_CD 0x10 /* checking disabled */ +#define DNS_F1_RCODE 0x0f /* response code */ + + u_int16_t dns_qdcount; + u_int16_t dns_ancount; + u_int16_t dns_nscount; + u_int16_t dns_arcount; +} __packed; + +void +relay_dns_log(struct rsession *con, u_int8_t *buf, size_t len) +{ + struct relay_dnshdr *hdr = (struct relay_dnshdr *)buf; + + /* Validate the header length */ + if (len < sizeof(*hdr)) { + log_debug("relay_dns_log: session %d: short dns packet", + con->se_id); + return; + } + + log_debug("relay_dns_log: session %d: %s id 0x%x " + "flags 0x%x:0x%x qd %u an %u ns %u ar %u", + con->se_id, + hdr->dns_flags0 & DNS_F0_QR ? "response" : "request", + ntohs(hdr->dns_id), + hdr->dns_flags0, + hdr->dns_flags1, + ntohs(hdr->dns_qdcount), + ntohs(hdr->dns_ancount), + ntohs(hdr->dns_nscount), + ntohs(hdr->dns_arcount)); +} + +void * +relay_dns_validate(struct rsession *con, struct relay *rlay, + struct sockaddr_storage *ss, u_int8_t *buf, size_t len) +{ + struct relay_dnshdr *hdr = (struct relay_dnshdr *)buf; + struct rsession lookup; + u_int16_t key; + struct relay_dns_priv *priv, lpriv; + + /* Validate the header length */ + if (len < sizeof(*hdr)) + return (NULL); + + key = ntohs(hdr->dns_id); + + /* + * Check if the header has the response flag set, otherwise + * return 0 to tell the UDP server to create a new session. + */ + if ((hdr->dns_flags0 & DNS_F0_QR) == 0) { + priv = malloc(sizeof(struct relay_dns_priv)); + if (priv == NULL) + return (NULL); + priv->dp_inkey = shuffle_generate16(&relay_shuffle); + priv->dp_outkey = key; + return ((void *)priv); + } + + /* + * Lookup if this response is for a known session and if the + * remote host matches the original destination of the request. + */ + if (con == NULL) { + lpriv.dp_inkey = key; + lookup.se_priv = &lpriv; + if ((con = SPLAY_FIND(session_tree, + &rlay->rl_sessions, &lookup)) != NULL && + con->se_priv != NULL && + relay_cmp_af(ss, &con->se_out.ss) == 0) + relay_dns_result(con, buf, len); + } else { + priv = (struct relay_dns_priv *)con->se_priv; + if (priv == NULL || key != priv->dp_inkey) { + relay_close(con, "invalid response"); + return (NULL); + } + relay_dns_result(con, buf, len); + } + + /* + * This is not a new session, ignore it in the UDP server. + */ + return (NULL); +} + +int +relay_dns_request(struct rsession *con) +{ + struct relay *rlay = (struct relay *)con->se_relay; + struct relay_dns_priv *priv = (struct relay_dns_priv *)con->se_priv; + u_int8_t *buf = EVBUFFER_DATA(con->se_out.output); + size_t len = EVBUFFER_LENGTH(con->se_out.output); + struct relay_dnshdr *hdr; + socklen_t slen; + + if (buf == NULL || priv == NULL || len < 1) + return (-1); + if (debug) + relay_dns_log(con, buf, len); + + if (gettimeofday(&con->se_tv_start, NULL) == -1) + return (-1); + + if (rlay->rl_dsttable != NULL) { + if (relay_from_table(con) != 0) + return (-1); + } else if (con->se_out.ss.ss_family == AF_UNSPEC) { + bcopy(&rlay->rl_conf.dstss, &con->se_out.ss, + sizeof(con->se_out.ss)); + con->se_out.port = rlay->rl_conf.dstport; + } + + if ((con->se_out.s = relay_udp_socket(&con->se_out.ss, + con->se_out.port, rlay->rl_proto)) == -1) + return (-1); + slen = con->se_out.ss.ss_len; + + hdr = (struct relay_dnshdr *)buf; + hdr->dns_id = htons(priv->dp_inkey); + + retry: + if (sendto(con->se_out.s, buf, len, 0, + (struct sockaddr *)&con->se_out.ss, slen) == -1) { + if (con->se_retry) { + con->se_retry--; + log_debug("relay_dns_request: session %d: " + "forward failed: %s, %s", + con->se_id, strerror(errno), + con->se_retry ? "next retry" : "last retry"); + goto retry; + } + log_debug("relay_dns_request: session %d: forward failed: %s", + con->se_id, strerror(errno)); + return (-1); + } + + event_again(&con->se_ev, con->se_out.s, EV_TIMEOUT|EV_READ, + relay_udp_response, &con->se_tv_start, &env->sc_timeout, con); + + return (0); +} + +void +relay_dns_result(struct rsession *con, u_int8_t *buf, size_t len) +{ + struct relay *rlay = (struct relay *)con->se_relay; + struct relay_dns_priv *priv = (struct relay_dns_priv *)con->se_priv; + struct relay_dnshdr *hdr; + socklen_t slen; + + if (priv == NULL) + fatalx("relay_dns_result: response to invalid session"); + + if (debug) + relay_dns_log(con, buf, len); + + /* + * Replace the random DNS request Id with the original Id + */ + hdr = (struct relay_dnshdr *)buf; + hdr->dns_id = htons(priv->dp_outkey); + + slen = con->se_out.ss.ss_len; + if (sendto(rlay->rl_s, buf, len, 0, + (struct sockaddr *)&con->se_in.ss, slen) == -1) { + relay_close(con, "response failed"); + return; + } + + relay_close(con, "session closed"); +} + +int +relay_dns_cmp(struct rsession *a, struct rsession *b) +{ + struct relay_dns_priv *ap = (struct relay_dns_priv *)a->se_priv; + struct relay_dns_priv *bp = (struct relay_dns_priv *)b->se_priv; + + if (ap == NULL || bp == NULL) + fatalx("relay_dns_cmp: invalid session"); + + return (memcmp(&ap->dp_inkey, &bp->dp_inkey, sizeof(u_int16_t))); +} Index: contrib/relayd/relayd.h =================================================================== --- contrib/relayd/relayd.h (revision 0) +++ contrib/relayd/relayd.h (revision 0) @@ -0,0 +1,898 @@ +/* $OpenBSD: relayd.h,v 1.129 2009/08/07 11:21:53 reyk Exp $ */ + +/* + * Copyright (c) 2006, 2007 Pierre-Yves Ritschard + * Copyright (c) 2006, 2007, 2008 Reyk Floeter + * Copyright (c) 2003, 2004 Henning Brauer + * + * 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 +#ifdef __FreeBSD__ +#include +#include +#endif + +#include + +#define CONF_FILE "/etc/relayd.conf" +#define RELAYD_SOCKET "/var/run/relayd.sock" +#define PF_SOCKET "/dev/pf" +#define RELAYD_USER "_relayd" +#define RELAYD_ANCHOR "relayd" +#define RELAYD_SERVERNAME "OpenBSD relayd" +#define CHECK_TIMEOUT 200 +#define CHECK_INTERVAL 10 +#define EMPTY_TABLE UINT_MAX +#define EMPTY_ID UINT_MAX +#define TABLE_NAME_SIZE 64 +#define TAG_NAME_SIZE 64 +#define SRV_NAME_SIZE 64 +#define MAX_NAME_SIZE 64 +#define SRV_MAX_VIRTS 16 + +#define RELAY_MAX_SESSIONS 1024 +#define RELAY_TIMEOUT 600 +#define RELAY_CACHESIZE -1 /* use default size */ +#define RELAY_NUMPROC 5 +#define RELAY_MAXPROC 32 +#define RELAY_MAXHOSTS 32 +#define RELAY_STATINTERVAL 60 +#define RELAY_BACKLOG 10 +#define RELAY_MAXLOOKUPLEVELS 5 + +#define SMALL_READ_BUF_SIZE 1024 +#define ICMP_BUF_SIZE 64 + +#define PURGE_TABLES 0x01 +#define PURGE_RDRS 0x02 +#define PURGE_RELAYS 0x04 +#define PURGE_PROTOS 0x08 +#define PURGE_EVERYTHING 0xff + +#ifndef __FreeBSD__ +#define SNMP_RECONNECT_TIMEOUT { 3, 0 } /* sec, usec */ +#else +#define SIMPLEQ_HEAD STAILQ_HEAD +#define SIMPLEQ_FIRST STAILQ_FIRST +#define SIMPLEQ_REMOVE_HEAD STAILQ_REMOVE_HEAD +#define SIMPLEQ_ENTRY STAILQ_ENTRY +#define SIMPLEQ_INIT STAILQ_INIT +#define SIMPLEQ_EMPTY STAILQ_EMPTY +#define SIMPLEQ_NEXT STAILQ_NEXT +#define SIMPLEQ_INSERT_TAIL STAILQ_INSERT_TAIL +#endif + +#if DEBUG > 1 +#define DPRINTF log_debug +#else +#define DPRINTF(x...) do {} while(0) +#endif + +/* Used for DNS request ID randomization */ +struct shuffle { + u_int16_t id_shuffle[65536]; + int isindex; +}; + + +typedef u_int32_t objid_t; + +struct ctl_status { + objid_t id; + int up; + int retry_cnt; + u_long check_cnt; + u_int16_t he; +}; + +struct ctl_id { + objid_t id; + char name[MAX_NAME_SIZE]; +}; + +struct ctl_script { + objid_t host; + int retval; +}; + +struct ctl_demote { + char group[IFNAMSIZ]; + int level; +}; + +struct ctl_icmp_event { + struct relayd *env; + int s; + int af; + int last_up; + struct event ev; + struct timeval tv_start; +}; + +struct ctl_tcp_event { + int s; + char *req; + struct buf *buf; + struct host *host; + struct table *table; + struct timeval tv_start; + struct event ev; + int (*validate_read)(struct ctl_tcp_event *); + int (*validate_close)(struct ctl_tcp_event *); + SSL *ssl; +}; + +enum httpmethod { + HTTP_METHOD_NONE = 0, + HTTP_METHOD_GET = 1, + HTTP_METHOD_HEAD = 2, + HTTP_METHOD_POST = 3, + HTTP_METHOD_PUT = 4, + HTTP_METHOD_DELETE = 5, + HTTP_METHOD_OPTIONS = 6, + HTTP_METHOD_TRACE = 7, + HTTP_METHOD_CONNECT = 8, + HTTP_METHOD_RESPONSE = 9 /* Server response */ +}; + +enum direction { + RELAY_DIR_REQUEST = 0, + RELAY_DIR_RESPONSE = 1 +}; + +struct ctl_relay_event { + int s; + in_port_t port; + struct sockaddr_storage ss; + struct bufferevent *bev; + struct evbuffer *output; + struct ctl_relay_event *dst; + void *con; + SSL *ssl; + u_int8_t *nodes; + struct proto_tree *tree; + + char *path; + char *args; + char *version; + + int line; + size_t toread; + int chunked; + int done; + enum httpmethod method; + enum direction dir; + + u_int8_t *buf; + int buflen; +}; + +struct ctl_natlook { + objid_t id; + int proc; + + struct sockaddr_storage src; + struct sockaddr_storage dst; + struct sockaddr_storage rsrc; + struct sockaddr_storage rdst; + in_port_t rsport; + in_port_t rdport; + int in; + int proto; +}; + +struct ctl_bindany { + objid_t bnd_id; + int bnd_proc; + + struct sockaddr_storage bnd_ss; + in_port_t bnd_port; + int bnd_proto; +}; + +struct ctl_stats { + objid_t id; + int proc; + + u_int interval; + u_int64_t cnt; + u_int32_t tick; + u_int32_t avg; + u_int32_t last; + u_int32_t avg_hour; + u_int32_t last_hour; + u_int32_t avg_day; + u_int32_t last_day; +}; + +struct portrange { + in_port_t val[2]; + u_int8_t op; +}; + +struct address { + struct sockaddr_storage ss; + int ipproto; + struct portrange port; + char ifname[IFNAMSIZ]; + TAILQ_ENTRY(address) entry; +}; +TAILQ_HEAD(addresslist, address); + +#define F_DISABLE 0x00000001 +#define F_BACKUP 0x00000002 +#define F_USED 0x00000004 +#define F_DOWN 0x00000008 +#define F_ADD 0x00000010 +#define F_DEL 0x00000020 +#define F_CHANGED 0x00000040 +#define F_STICKY 0x00000080 +#define F_CHECK_DONE 0x00000100 +#define F_ACTIVE_RULESET 0x00000200 +#define F_CHECK_SENT 0x00000400 +#define F_SSL 0x00000800 +#define F_NATLOOK 0x00001000 +#define F_DEMOTE 0x00002000 +#define F_LOOKUP_PATH 0x00004000 +#define F_DEMOTED 0x00008000 +#define F_UDP 0x00010000 +#define F_RETURN 0x00020000 +#define F_TRAP 0x00040000 +#define F_NEEDPF 0x00080000 +#define F_PORT 0x00100000 +#define F_SSLCLIENT 0x00200000 + +enum forwardmode { + FWD_NORMAL = 0, + FWD_ROUTE, + FWD_TRANS +}; + +struct host_config { + objid_t id; + objid_t parentid; + objid_t tableid; + int retry; + char name[MAXHOSTNAMELEN]; + struct sockaddr_storage ss; + int ttl; +}; + +struct host { + TAILQ_ENTRY(host) entry; + SLIST_ENTRY(host) child; + SLIST_HEAD(,host) children; + struct host_config conf; + u_int32_t flags; + char *tablename; + int up; + int last_up; + u_long check_cnt; + u_long up_cnt; + int retry_cnt; + int idx; + u_int16_t he; + struct ctl_tcp_event cte; +}; +TAILQ_HEAD(hostlist, host); + +enum host_error { + HCE_NONE = 0, + HCE_ABORT, + HCE_INTERVAL_TIMEOUT, + HCE_ICMP_OK, + HCE_ICMP_READ_TIMEOUT, + HCE_ICMP_WRITE_TIMEOUT, + HCE_TCP_CONNECT_ERROR, + HCE_TCP_CONNECT_FAIL, + HCE_TCP_CONNECT_TIMEOUT, + HCE_TCP_CONNECT_OK, + HCE_TCP_WRITE_TIMEOUT, + HCE_TCP_WRITE_FAIL, + HCE_TCP_READ_TIMEOUT, + HCE_TCP_READ_FAIL, + HCE_SCRIPT_OK, + HCE_SCRIPT_FAIL, + HCE_SSL_CONNECT_ERROR, + HCE_SSL_CONNECT_FAIL, + HCE_SSL_CONNECT_OK, + HCE_SSL_CONNECT_TIMEOUT, + HCE_SSL_READ_TIMEOUT, + HCE_SSL_WRITE_TIMEOUT, + HCE_SSL_READ_ERROR, + HCE_SSL_WRITE_ERROR, + HCE_SEND_EXPECT_FAIL, + HCE_SEND_EXPECT_OK, + HCE_HTTP_CODE_ERROR, + HCE_HTTP_CODE_FAIL, + HCE_HTTP_CODE_OK, + HCE_HTTP_DIGEST_ERROR, + HCE_HTTP_DIGEST_FAIL, + HCE_HTTP_DIGEST_OK, +}; + +enum host_status { + HOST_DOWN = -1, + HOST_UNKNOWN = 0, + HOST_UP = 1 +}; +#define HOST_ISUP(x) (x == HOST_UP) + +enum digest_type { + DIGEST_NONE = 0, + DIGEST_SHA1 = 1, + DIGEST_MD5 = 2 +}; + +struct table_config { + objid_t id; + objid_t rdrid; + u_int32_t flags; + int check; + char demote_group[IFNAMSIZ]; + char ifname[IFNAMSIZ]; + struct timeval timeout; + in_port_t port; + int retcode; + int skip_cnt; + char name[TABLE_NAME_SIZE]; + char path[MAXPATHLEN]; + char exbuf[64]; + char digest[41]; /* length of sha1 digest * 2 */ + u_int8_t digest_type; + enum forwardmode fwdmode; +}; + +struct table { + TAILQ_ENTRY(table) entry; + struct table_config conf; + int up; + int skipped; + struct hostlist hosts; + SSL_CTX *ssl_ctx; + int sendbuf_len; + char *sendbuf; +}; +TAILQ_HEAD(tablelist, table); + +enum table_check { + CHECK_NOCHECK = 0, + CHECK_ICMP = 1, + CHECK_TCP = 2, + CHECK_HTTP_CODE = 3, + CHECK_HTTP_DIGEST = 4, + CHECK_SEND_EXPECT = 5, + CHECK_SCRIPT = 6 +}; + +struct rdr_config { + objid_t id; + u_int32_t flags; + in_port_t port; + objid_t table_id; + objid_t backup_id; + char name[SRV_NAME_SIZE]; + char tag[TAG_NAME_SIZE]; + struct timeval timeout; +}; + +struct rdr { + TAILQ_ENTRY(rdr) entry; + struct rdr_config conf; + struct addresslist virts; + struct table *table; + struct table *backup; /* use this if no host up */ + struct ctl_stats stats; +}; +TAILQ_HEAD(rdrlist, rdr); + +struct relay; +struct rsession { + objid_t se_id; + objid_t se_relayid; + struct ctl_relay_event se_in; + struct ctl_relay_event se_out; + void *se_priv; + u_int32_t se_hashkey; + struct event se_ev; + struct timeval se_timeout; + struct timeval se_tv_start; + struct timeval se_tv_last; + int se_done; + int se_retry; + u_int16_t se_mark; + struct evbuffer *se_log; + struct relay *se_relay; + struct ctl_natlook *se_cnl; + int se_bnds; + + SPLAY_ENTRY(rsession) se_nodes; +}; +SPLAY_HEAD(session_tree, rsession); + +enum nodeaction { + NODE_ACTION_NONE = 0, + NODE_ACTION_APPEND = 1, + NODE_ACTION_CHANGE = 2, + NODE_ACTION_REMOVE = 3, + NODE_ACTION_EXPECT = 4, + NODE_ACTION_FILTER = 5, + NODE_ACTION_HASH = 6, + NODE_ACTION_LOG = 7, + NODE_ACTION_MARK = 8 +}; + +enum nodetype { + NODE_TYPE_HEADER = 0, + NODE_TYPE_QUERY = 1, + NODE_TYPE_COOKIE = 2, + NODE_TYPE_PATH = 3, + NODE_TYPE_URL = 4 +}; + +#define PNFLAG_MACRO 0x01 +#define PNFLAG_MARK 0x02 +#define PNFLAG_LOG 0x04 +#define PNFLAG_LOOKUP_QUERY 0x08 +#define PNFLAG_LOOKUP_COOKIE 0x10 +#define PNFLAG_LOOKUP_URL 0xe0 +#define PNFLAG_LOOKUP_URL_DIGEST 0xc0 +#define PNFLAG_LOOKUP_DIGEST(x) (0x20 << x) + +enum noderesult { + PN_DROP = 0, + PN_PASS = 1, + PN_FAIL = -1 +}; + +struct protonode { + objid_t id; + char *key; + enum nodeaction action; + char *value; + u_int8_t flags; + enum nodetype type; + u_int16_t mark; + u_int16_t label; + + SIMPLEQ_HEAD(, protonode) head; + SIMPLEQ_ENTRY(protonode) entry; + + RB_ENTRY(protonode) nodes; +}; +RB_HEAD(proto_tree, protonode); + +#define PROTONODE_FOREACH(elm, root, field) \ + for (elm = root; elm != NULL; elm = SIMPLEQ_NEXT(elm, entry)) \ + +enum prototype { + RELAY_PROTO_TCP = 0, + RELAY_PROTO_HTTP = 1, + RELAY_PROTO_DNS = 2 +}; + +#define TCPFLAG_NODELAY 0x01 +#define TCPFLAG_NNODELAY 0x02 +#define TCPFLAG_SACK 0x04 +#define TCPFLAG_NSACK 0x08 +#define TCPFLAG_BUFSIZ 0x10 +#define TCPFLAG_IPTTL 0x20 +#define TCPFLAG_IPMINTTL 0x40 +#define TCPFLAG_DEFAULT 0x00 + +#define SSLFLAG_SSLV2 0x01 +#define SSLFLAG_SSLV3 0x02 +#define SSLFLAG_TLSV1 0x04 +#define SSLFLAG_VERSION 0x07 +#define SSLFLAG_DEFAULT (SSLFLAG_SSLV3|SSLFLAG_TLSV1) + +#define SSLCIPHERS_DEFAULT "HIGH:!ADH" + +struct protocol { + objid_t id; + u_int32_t flags; + u_int8_t tcpflags; + int tcpbufsiz; + int tcpbacklog; + u_int8_t tcpipttl; + u_int8_t tcpipminttl; + u_int8_t sslflags; + char sslciphers[768]; + char *sslca; + char name[MAX_NAME_SIZE]; + int cache; + enum prototype type; + int lateconnect; + char *style; + + int request_nodes; + struct proto_tree request_tree; + int response_nodes; + struct proto_tree response_tree; + + int (*cmp)(struct rsession *, struct rsession *); + void *(*validate)(struct rsession *, struct relay *, + struct sockaddr_storage *, + u_int8_t *, size_t); + int (*request)(struct rsession *); + + TAILQ_ENTRY(protocol) entry; +}; +TAILQ_HEAD(protolist, protocol); + +struct relay_config { + objid_t id; + u_int32_t flags; + objid_t proto; + char name[MAXHOSTNAMELEN]; + char ifname[IFNAMSIZ]; + in_port_t port; + in_port_t dstport; + int dstmode; + int dstretry; + objid_t dsttable; + struct sockaddr_storage ss; + struct sockaddr_storage dstss; + struct sockaddr_storage dstaf; + struct timeval timeout; + enum forwardmode fwdmode; +}; + +struct relay { + TAILQ_ENTRY(relay) rl_entry; + struct relay_config rl_conf; + + int rl_up; + struct protocol *rl_proto; + int rl_s; + struct bufferevent *rl_bev; + + int rl_dsts; + struct bufferevent *rl_dstbev; + + struct table *rl_dsttable; + u_int32_t rl_dstkey; + struct host *rl_dsthost[RELAY_MAXHOSTS]; + int rl_dstnhosts; + + struct event rl_ev; + + SSL_CTX *rl_ssl_ctx; + char *rl_ssl_cert; + off_t rl_ssl_cert_len; + char *rl_ssl_key; + off_t rl_ssl_key_len; + char *rl_ssl_ca; + off_t rl_ssl_ca_len; + + struct ctl_stats rl_stats[RELAY_MAXPROC + 1]; + + struct session_tree rl_sessions; +}; +TAILQ_HEAD(relaylist, relay); + +enum dstmode { + RELAY_DSTMODE_LOADBALANCE = 0, + RELAY_DSTMODE_ROUNDROBIN = 1, + RELAY_DSTMODE_HASH = 2 +}; +#define RELAY_DSTMODE_DEFAULT RELAY_DSTMODE_ROUNDROBIN + +enum { + PROC_MAIN, + PROC_PFE, + PROC_HCE, + PROC_RELAY +} relayd_process; + +struct relayd { + u_int8_t sc_opts; + u_int32_t sc_flags; + const char *sc_confpath; + struct pfdata *sc_pf; + int sc_tablecount; + int sc_rdrcount; + int sc_protocount; + int sc_relaycount; + struct timeval sc_interval; + struct timeval sc_timeout; + struct table sc_empty_table; + struct protocol sc_proto_default; + struct event sc_ev; + struct tablelist *sc_tables; + struct rdrlist *sc_rdrs; + struct protolist *sc_protos; + struct relaylist *sc_relays; + u_int16_t sc_prefork_relay; + char sc_demote_group[IFNAMSIZ]; + u_int16_t sc_id; + + struct event sc_statev; + struct timeval sc_statinterval; + +#ifndef __FreeBSD__ + int sc_snmp; + struct event sc_snmpto; + struct event sc_snmpev; +#endif + int sc_has_icmp; + int sc_has_icmp6; + struct ctl_icmp_event sc_icmp_send; + struct ctl_icmp_event sc_icmp_recv; + struct ctl_icmp_event sc_icmp6_send; + struct ctl_icmp_event sc_icmp6_recv; +}; + +#define RELAYD_OPT_VERBOSE 0x01 +#define RELAYD_OPT_NOACTION 0x04 +#define RELAYD_OPT_LOGUPDATE 0x08 +#define RELAYD_OPT_LOGNOTIFY 0x10 +#define RELAYD_OPT_LOGALL 0x18 + +/* initially control.h */ +struct { + struct event ev; + int fd; +} control_state; + +enum blockmodes { + BM_NORMAL, + BM_NONBLOCK +}; + +struct imsgev { + struct imsgbuf ibuf; + void (*handler)(int, short, void *); + struct event ev; + void *data; + short events; +}; + +struct ctl_conn { + TAILQ_ENTRY(ctl_conn) entry; + u_int8_t flags; +#define CTL_CONN_NOTIFY 0x01 + struct imsgev iev; + +}; +TAILQ_HEAD(ctl_connlist, ctl_conn); + +enum imsg_type { + IMSG_NONE, + IMSG_CTL_OK, /* answer to relayctl requests */ + IMSG_CTL_FAIL, + IMSG_CTL_END, + IMSG_CTL_RDR, + IMSG_CTL_TABLE, + IMSG_CTL_HOST, + IMSG_CTL_RELAY, + IMSG_CTL_SESSION, + IMSG_CTL_TABLE_CHANGED, + IMSG_CTL_PULL_RULESET, + IMSG_CTL_PUSH_RULESET, + IMSG_CTL_SHOW_SUM, /* relayctl requests */ + IMSG_CTL_RDR_ENABLE, + IMSG_CTL_RDR_DISABLE, + IMSG_CTL_TABLE_ENABLE, + IMSG_CTL_TABLE_DISABLE, + IMSG_CTL_HOST_ENABLE, + IMSG_CTL_HOST_DISABLE, + IMSG_CTL_SHUTDOWN, + IMSG_CTL_RELOAD, + IMSG_CTL_POLL, + IMSG_CTL_NOTIFY, + IMSG_CTL_RDR_STATS, + IMSG_CTL_RELAY_STATS, + IMSG_RDR_ENABLE, /* notifies from pfe to hce */ + IMSG_RDR_DISABLE, + IMSG_TABLE_ENABLE, + IMSG_TABLE_DISABLE, + IMSG_HOST_ENABLE, + IMSG_HOST_DISABLE, + IMSG_HOST_STATUS, /* notifies from hce to pfe */ + IMSG_SYNC, + IMSG_NATLOOK, +#ifndef __FreeBSD__ + IMSG_DEMOTE, +#endif + IMSG_STATISTICS, + IMSG_RECONF, /* reconfiguration notifies */ + IMSG_RECONF_TABLE, + IMSG_RECONF_SENDBUF, + IMSG_RECONF_HOST, + IMSG_RECONF_RDR, + IMSG_RECONF_VIRT, + IMSG_RECONF_PROTO, + IMSG_RECONF_REQUEST_TREE, + IMSG_RECONF_RESPONSE_TREE, + IMSG_RECONF_PNODE_KEY, + IMSG_RECONF_PNODE_VAL, + IMSG_RECONF_RELAY, + IMSG_RECONF_END, + IMSG_SCRIPT, +#ifndef __FreeBSD__ + IMSG_SNMPSOCK, +#endif + IMSG_BINDANY +}; + +/* control.c */ +int control_init(void); +int control_listen(struct relayd *, struct imsgev *, struct imsgev *); +void control_accept(int, short, void *); +void control_dispatch_imsg(int, short, void *); +void control_imsg_forward(struct imsg *); +void control_cleanup(void); + +void session_socket_blockmode(int, enum blockmodes); + +extern struct ctl_connlist ctl_conns; + +/* parse.y */ +struct relayd *parse_config(const char *, int); +int cmdline_symset(char *); + +/* log.c */ +const char *host_error(enum host_error); +const char *host_status(enum host_status); +const char *table_check(enum table_check); +const char *print_availability(u_long, u_long); +const char *print_host(struct sockaddr_storage *, char *, size_t); +const char *print_time(struct timeval *, struct timeval *, char *, size_t); +const char *print_httperror(u_int); + +/* pfe.c */ +pid_t pfe(struct relayd *, int [2], int [2], int [RELAY_MAXPROC][2], + int [2], int [RELAY_MAXPROC][2]); +void show(struct ctl_conn *); +void show_sessions(struct ctl_conn *); +int enable_rdr(struct ctl_conn *, struct ctl_id *); +int enable_table(struct ctl_conn *, struct ctl_id *); +int enable_host(struct ctl_conn *, struct ctl_id *, struct host *); +int disable_rdr(struct ctl_conn *, struct ctl_id *); +int disable_table(struct ctl_conn *, struct ctl_id *); +int disable_host(struct ctl_conn *, struct ctl_id *, struct host *); + +/* pfe_filter.c */ +void init_filter(struct relayd *); +void init_tables(struct relayd *); +void flush_table(struct relayd *, struct rdr *); +void sync_table(struct relayd *, struct rdr *, struct table *); +void sync_ruleset(struct relayd *, struct rdr *, int); +void flush_rulesets(struct relayd *); +int natlook(struct relayd *, struct ctl_natlook *); +u_int64_t + check_table(struct relayd *, struct rdr *, struct table *); + +/* hce.c */ +pid_t hce(struct relayd *, int [2], int [2], int [RELAY_MAXPROC][2], + int [2], int [RELAY_MAXPROC][2]); +void hce_notify_done(struct host *, enum host_error); + +/* relay.c */ +pid_t relay(struct relayd *, int [2], int [2], int [RELAY_MAXPROC][2], + int [2], int [RELAY_MAXPROC][2]); +void relay_notify_done(struct host *, const char *); +int relay_session_cmp(struct rsession *, struct rsession *); +int relay_load_certfiles(struct relay *); +void relay_close(struct rsession *, const char *); +void relay_natlook(int, short, void *); +void relay_session(struct rsession *); +int relay_from_table(struct rsession *); +int relay_socket_af(struct sockaddr_storage *, in_port_t); +int relay_cmp_af(struct sockaddr_storage *, + struct sockaddr_storage *); + + +RB_PROTOTYPE(proto_tree, protonode, se_nodes, relay_proto_cmp); +SPLAY_PROTOTYPE(session_tree, rsession, se_nodes, relay_session_cmp); + +/* relay_udp.c */ +void relay_udp_privinit(struct relayd *, struct relay *); +void relay_udp_init(struct relay *); +int relay_udp_bind(struct sockaddr_storage *, in_port_t, + struct protocol *); +void relay_udp_server(int, short, void *); + +/* check_icmp.c */ +void icmp_init(struct relayd *); +void schedule_icmp(struct relayd *, struct host *); +void check_icmp(struct relayd *, struct timeval *); + +/* check_tcp.c */ +void check_tcp(struct ctl_tcp_event *); + +/* check_script.c */ +void check_script(struct host *); +void script_done(struct relayd *, struct ctl_script *); +int script_exec(struct relayd *, struct ctl_script *); + +/* ssl.c */ +void ssl_init(struct relayd *); +void ssl_transaction(struct ctl_tcp_event *); +SSL_CTX *ssl_ctx_create(struct relayd *); +void ssl_error(const char *, const char *); + +/* ssl_privsep.c */ +int ssl_ctx_use_private_key(SSL_CTX *, char *, off_t); +int ssl_ctx_use_certificate_chain(SSL_CTX *, char *, off_t); +int ssl_ctx_load_verify_memory(SSL_CTX *, char *, off_t); + +/* relayd.c */ +struct host *host_find(struct relayd *, objid_t); +struct table *table_find(struct relayd *, objid_t); +struct rdr *rdr_find(struct relayd *, objid_t); +struct host *host_findbyname(struct relayd *, const char *); +struct table *table_findbyname(struct relayd *, const char *); +struct table *table_findbyconf(struct relayd *, struct table *); +struct rdr *rdr_findbyname(struct relayd *, const char *); +void event_again(struct event *, int, short, + void (*)(int, short, void *), + struct timeval *, struct timeval *, void *); +struct relay *relay_find(struct relayd *, objid_t); +struct rsession *session_find(struct relayd *, objid_t); +struct relay *relay_findbyname(struct relayd *, const char *); +struct relay *relay_findbyaddr(struct relayd *, struct relay_config *); +int expand_string(char *, size_t, const char *, const char *); +void translate_string(char *); +void purge_config(struct relayd *, u_int8_t); +void purge_table(struct tablelist *, struct table *); +void merge_config(struct relayd *, struct relayd *); +char *digeststr(enum digest_type, const u_int8_t *, size_t, char *); +const char *canonicalize_host(const char *, char *, size_t); +struct protonode *protonode_header(enum direction, struct protocol *, + struct protonode *); +int protonode_add(enum direction, struct protocol *, + struct protonode *); +int protonode_load(enum direction, struct protocol *, + struct protonode *, const char *); +int map6to4(struct sockaddr_storage *); +int map4to6(struct sockaddr_storage *, struct sockaddr_storage *); +void imsg_event_add(struct imsgev *); +int imsg_compose_event(struct imsgev *, u_int16_t, u_int32_t, + pid_t, int, void *, u_int16_t); + +/* carp.c */ +int carp_demote_init(char *, int); +void carp_demote_shutdown(void); +int carp_demote_get(char *); +int carp_demote_set(char *, int); +int carp_demote_reset(char *, int); + +/* name2id.c */ +u_int16_t pn_name2id(const char *); +const char *pn_id2name(u_int16_t); +void pn_unref(u_int16_t); +void pn_ref(u_int16_t); + +#ifndef __FreeBSD__ +/* snmp.c */ +void snmp_init(struct relayd *, struct imsgev *); +int snmp_sendsock(struct imsgev *); +void snmp_hosttrap(struct table *, struct host *); +#endif + +/* shuffle.c */ +void shuffle_init(struct shuffle *); +u_int16_t shuffle_generate16(struct shuffle *); + +/* 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 *, ...); +__dead void fatal(const char *); +__dead void fatalx(const char *); Index: contrib/relayd/carp.c =================================================================== --- contrib/relayd/carp.c (revision 0) +++ contrib/relayd/carp.c (revision 0) @@ -0,0 +1,217 @@ +/* $OpenBSD: carp.c,v 1.5 2007/12/07 17:17:00 reyk Exp $ */ + +/* + * Copyright (c) 2006 Henning Brauer + * + * 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 +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#include "relayd.h" + +struct carpgroup { + TAILQ_ENTRY(carpgroup) entry; + char *group; + int do_demote; + int changed_by; +}; + +TAILQ_HEAD(carpgroups, carpgroup) carpgroups = + TAILQ_HEAD_INITIALIZER(carpgroups); + +struct carpgroup *carp_group_find(char *group); +int carp_demote_ioctl(char *, int); + +struct carpgroup * +carp_group_find(char *group) +{ + struct carpgroup *c; + + TAILQ_FOREACH(c, &carpgroups, entry) + if (!strcmp(c->group, group)) + return (c); + + return (NULL); +} + +int +carp_demote_init(char *group, int force) +{ + struct carpgroup *c; + int level; + + if ((c = carp_group_find(group)) == NULL) { + if ((c = calloc(1, sizeof(struct carpgroup))) == NULL) { + log_warn("carp_demote_init calloc"); + return (-1); + } + if ((c->group = strdup(group)) == NULL) { + log_warn("carp_demote_init calloc"); + free(c); + return (-1); + } + + /* only demote if this group already is demoted */ + if ((level = carp_demote_get(group)) == -1) + return (-1); + if (level > 0 || force) + c->do_demote = 1; + + TAILQ_INSERT_TAIL(&carpgroups, c, entry); + } + + return (0); +} + +void +carp_demote_shutdown(void) +{ + struct carpgroup *c; + + while ((c = TAILQ_FIRST(&carpgroups)) != NULL) { + TAILQ_REMOVE(&carpgroups, c, entry); + for (; c->changed_by > 0; c->changed_by--) + if (c->do_demote) + carp_demote_ioctl(c->group, -1); + + free(c->group); + free(c); + } +} + +int +carp_demote_get(char *group) +{ + int s; + struct ifgroupreq ifgr; + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { + log_warn("carp_demote_get: socket"); + return (-1); + } + + bzero(&ifgr, sizeof(ifgr)); + if (strlcpy(ifgr.ifgr_name, group, sizeof(ifgr.ifgr_name)) >= + sizeof(ifgr.ifgr_name)) { + log_warn("carp_demote_get: invalid group"); + return (-1); + } + + if (ioctl(s, SIOCGIFGATTR, (caddr_t)&ifgr) == -1) { + if (errno == ENOENT) + log_warnx("group \"%s\" does not exist", group); + else + log_warn("carp_demote_get: ioctl"); + close(s); + return (-1); + } + + close(s); + return ((int)ifgr.ifgr_attrib.ifg_carp_demoted); +} + +int +carp_demote_set(char *group, int demote) +{ + struct carpgroup *c; + + if ((c = carp_group_find(group)) == NULL) { + log_warnx("carp_group_find for %s returned NULL?!", group); + return (-1); + } + + if (c->changed_by + demote < 0) { + log_warnx("carp_demote_set: changed_by + demote < 0"); + return (-1); + } + + if (c->do_demote && carp_demote_ioctl(group, demote) == -1) + return (-1); + + c->changed_by += demote; + + /* enable demotion when we return to 0, i. e. all sessions up */ + if (demote < 0 && c->changed_by == 0) + c->do_demote = 1; + + return (0); +} + +int +carp_demote_reset(char *group, int value) +{ + int level; + int demote = 0; + + if (value < 0) { + log_warnx("carp_demote_reset: value < 0"); + return (-1); + } + + if ((level = carp_demote_get(group)) == -1) + return (-1); + if (level == value) + return (0); + + demote -= level; + demote += value; + + if (carp_demote_ioctl(group, demote) == -1) + return (-1); + + return (0); +} + +int +carp_demote_ioctl(char *group, int demote) +{ + int s, res; + struct ifgroupreq ifgr; + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { + log_warn("carp_demote_get: socket"); + return (-1); + } + + bzero(&ifgr, sizeof(ifgr)); + if (strlcpy(ifgr.ifgr_name, group, sizeof(ifgr.ifgr_name)) >= + sizeof(ifgr.ifgr_name)) { + log_warn("carp_demote_ioctl: invalid group"); + return (-1); + } + ifgr.ifgr_attrib.ifg_carp_demoted = demote; + + if ((res = ioctl(s, SIOCSIFGATTR, (caddr_t)&ifgr)) == -1) + log_warn("unable to %s the demote state " + "of group '%s'", (demote > 0) ? "increment" : "decrement", + group); + else + log_info("%s the demote state of group '%s'", + (demote > 0) ? "incremented" : "decremented", group); + + close(s); + return (res); +} Index: contrib/relayd/check_icmp.c =================================================================== --- contrib/relayd/check_icmp.c (revision 0) +++ contrib/relayd/check_icmp.c (revision 0) @@ -0,0 +1,387 @@ +/* $OpenBSD: check_icmp.c,v 1.28 2009/08/07 11:32:54 reyk Exp $ */ + +/* + * Copyright (c) 2006 Pierre-Yves Ritschard + * + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "relayd.h" + +void icmp_setup(struct relayd *, struct ctl_icmp_event *, int); +void check_icmp_add(struct ctl_icmp_event *, int, struct timeval *, + void (*)(int, short, void *)); +int icmp_checks_done(struct ctl_icmp_event *); +void icmp_checks_timeout(struct ctl_icmp_event *, enum host_error); +void send_icmp(int, short, void *); +void recv_icmp(int, short, void *); +int in_cksum(u_short *, int); + +void +icmp_setup(struct relayd *env, struct ctl_icmp_event *cie, int af) +{ + int proto = IPPROTO_ICMP; + + if (af == AF_INET6) + proto = IPPROTO_ICMPV6; + if ((cie->s = socket(af, SOCK_RAW, proto)) < 0) + fatal("icmp_init: socket"); + session_socket_blockmode(cie->s, BM_NONBLOCK); + cie->env = env; + cie->af = af; +} + +void +icmp_init(struct relayd *env) +{ + icmp_setup(env, &env->sc_icmp_send, AF_INET); + icmp_setup(env, &env->sc_icmp_recv, AF_INET); + icmp_setup(env, &env->sc_icmp6_send, AF_INET6); + icmp_setup(env, &env->sc_icmp6_recv, AF_INET6); + env->sc_id = getpid() & 0xffff; +} + +void +schedule_icmp(struct relayd *env, struct host *host) +{ + host->last_up = host->up; + host->flags &= ~(F_CHECK_SENT|F_CHECK_DONE); + + if (((struct sockaddr *)&host->conf.ss)->sa_family == AF_INET) + env->sc_has_icmp = 1; + else + env->sc_has_icmp6 = 1; +} + +void +check_icmp_add(struct ctl_icmp_event *cie, int flags, struct timeval *start, + void (*fn)(int, short, void *)) +{ + struct timeval tv; + + if (start != NULL) + bcopy(start, &cie->tv_start, sizeof(cie->tv_start)); + bcopy(&cie->env->sc_timeout, &tv, sizeof(tv)); + if (gettimeofday(&cie->tv_start, NULL) == -1) + fatal("check_icmp_add: gettimeofday"); + event_del(&cie->ev); + event_set(&cie->ev, cie->s, EV_TIMEOUT|flags, fn, cie); + event_add(&cie->ev, &tv); +} + +void +check_icmp(struct relayd *env, struct timeval *tv) +{ + if (env->sc_has_icmp) { + check_icmp_add(&env->sc_icmp_recv, EV_READ, tv, recv_icmp); + check_icmp_add(&env->sc_icmp_send, EV_WRITE, tv, send_icmp); + } + if (env->sc_has_icmp6) { + check_icmp_add(&env->sc_icmp6_recv, EV_READ, tv, recv_icmp); + check_icmp_add(&env->sc_icmp6_send, EV_WRITE, tv, send_icmp); + } +} + +int +icmp_checks_done(struct ctl_icmp_event *cie) +{ + struct table *table; + struct host *host; + + TAILQ_FOREACH(table, cie->env->sc_tables, entry) { + if (table->conf.flags & F_DISABLE || + table->conf.check != CHECK_ICMP) + continue; + TAILQ_FOREACH(host, &table->hosts, entry) { + if (((struct sockaddr *)&host->conf.ss)->sa_family != + cie->af) + continue; + if (!(host->flags & F_CHECK_DONE)) + return (0); + } + } + return (1); +} + +void +icmp_checks_timeout(struct ctl_icmp_event *cie, enum host_error he) +{ + struct table *table; + struct host *host; + + TAILQ_FOREACH(table, cie->env->sc_tables, entry) { + if (table->conf.flags & F_DISABLE || + table->conf.check != CHECK_ICMP) + continue; + TAILQ_FOREACH(host, &table->hosts, entry) { + if (((struct sockaddr *)&host->conf.ss)->sa_family != + cie->af) + continue; + if (!(host->flags & (F_CHECK_DONE|F_DISABLE))) { + host->up = HOST_DOWN; + hce_notify_done(host, he); + } + } + } +} + +void +send_icmp(int s, short event, void *arg) +{ + struct ctl_icmp_event *cie = (struct ctl_icmp_event *)arg; + struct table *table; + struct host *host; + struct sockaddr *to; + struct icmp *icp; + struct icmp6_hdr *icp6; + ssize_t r; + u_char packet[ICMP_BUF_SIZE]; + socklen_t slen; + int i = 0, ttl, mib[4]; + size_t len; + + if (event == EV_TIMEOUT) { + icmp_checks_timeout(cie, HCE_ICMP_WRITE_TIMEOUT); + return; + } + + bzero(&packet, sizeof(packet)); + icp = (struct icmp *)packet; + icp6 = (struct icmp6_hdr *)packet; + if (cie->af == AF_INET) { + icp->icmp_type = ICMP_ECHO; + icp->icmp_code = 0; + icp->icmp_id = htons(cie->env->sc_id); + icp->icmp_cksum = 0; + slen = sizeof(struct sockaddr_in); + } else { + icp6->icmp6_type = ICMP6_ECHO_REQUEST; + icp6->icmp6_code = 0; + icp6->icmp6_cksum = 0; + icp6->icmp6_id = htons(cie->env->sc_id); + slen = sizeof(struct sockaddr_in6); + } + + TAILQ_FOREACH(table, cie->env->sc_tables, entry) { + if (table->conf.check != CHECK_ICMP || + table->conf.flags & F_DISABLE) + continue; + TAILQ_FOREACH(host, &table->hosts, entry) { + if (host->flags & (F_DISABLE | F_CHECK_SENT) || + host->conf.parentid) + continue; + if (((struct sockaddr *)&host->conf.ss)->sa_family != + cie->af) + continue; + i++; + to = (struct sockaddr *)&host->conf.ss; + if (cie->af == AF_INET) { + icp->icmp_seq = htons(i); + icp->icmp_cksum = 0; + memcpy(icp->icmp_data, &host->conf.id, + sizeof(host->conf.id)); + icp->icmp_cksum = in_cksum((u_short *)icp, + sizeof(packet)); + } else { + icp6->icmp6_seq = htons(i); + icp6->icmp6_cksum = 0; + memcpy(packet + sizeof(*icp6), &host->conf.id, + sizeof(host->conf.id)); + icp6->icmp6_cksum = in_cksum((u_short *)icp6, + sizeof(packet)); + } + + if ((ttl = host->conf.ttl) > 0) + (void)setsockopt(s, IPPROTO_IP, IP_TTL, + &host->conf.ttl, sizeof(int)); + else { + /* Revert to default TTL */ + mib[0] = CTL_NET; + mib[1] = cie->af; + mib[2] = IPPROTO_IP; + mib[3] = IPCTL_DEFTTL; + len = sizeof(ttl); + if (sysctl(mib, 4, &ttl, &len, NULL, 0) == 0) + (void)setsockopt(s, IPPROTO_IP, IP_TTL, + &ttl, sizeof(int)); + } + + r = sendto(s, packet, sizeof(packet), 0, to, slen); + if (r == -1) { + if (errno == EAGAIN || errno == EINTR) + goto retry; + host->flags |= F_CHECK_SENT|F_CHECK_DONE; + host->up = HOST_DOWN; + } else if (r != sizeof(packet)) + goto retry; + host->flags |= F_CHECK_SENT; + } + } + + return; + + retry: + event_again(&cie->ev, s, EV_TIMEOUT|EV_WRITE, send_icmp, + &cie->tv_start, &cie->env->sc_timeout, cie); +} + +void +recv_icmp(int s, short event, void *arg) +{ + struct ctl_icmp_event *cie = (struct ctl_icmp_event *)arg; + u_char packet[ICMP_BUF_SIZE]; + socklen_t slen; + struct sockaddr_storage ss; + struct icmp *icp; + struct icmp6_hdr *icp6; + u_int16_t icpid; + struct host *host; + ssize_t r; + objid_t id; + + if (event == EV_TIMEOUT) { + icmp_checks_timeout(cie, HCE_ICMP_READ_TIMEOUT); + return; + } + + bzero(&packet, sizeof(packet)); + bzero(&ss, sizeof(ss)); + + r = recvfrom(s, packet, sizeof(packet), 0, + (struct sockaddr *)&ss, &slen); + if (r == -1 || r != ICMP_BUF_SIZE) { + if (r == -1 && errno != EAGAIN && errno != EINTR) + log_debug("recv_icmp: receive error"); + goto retry; + } + + if (cie->af == AF_INET) { + icp = (struct icmp *)(packet + sizeof(struct ip)); + icpid = ntohs(icp->icmp_id); + memcpy(&id, icp->icmp_data, sizeof(id)); + } else { + icp6 = (struct icmp6_hdr *)packet; + icpid = ntohs(icp6->icmp6_id); + memcpy(&id, packet + sizeof(*icp6), sizeof(id)); + } + if (icpid != cie->env->sc_id) + goto retry; + host = host_find(cie->env, id); + if (host == NULL) { + log_warn("recv_icmp: ping for unknown host received"); + goto retry; + } + if (bcmp(&ss, &host->conf.ss, slen)) { + log_warnx("recv_icmp: forged icmp packet?"); + goto retry; + } + + host->up = HOST_UP; + host->flags |= F_CHECK_DONE; + hce_notify_done(host, HCE_ICMP_OK); + + if (icmp_checks_done(cie)) + return; + + retry: + event_again(&cie->ev, s, EV_TIMEOUT|EV_READ, recv_icmp, + &cie->tv_start, &cie->env->sc_timeout, cie); +} + +/* in_cksum from ping.c -- + * Checksum routine for Internet Protocol family headers (C Version) + * + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Mike Muuss. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +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); +} Index: contrib/relayd/relayd.conf.5 =================================================================== --- contrib/relayd/relayd.conf.5 (revision 0) +++ contrib/relayd/relayd.conf.5 (revision 0) @@ -0,0 +1,1143 @@ +.\" $OpenBSD: relayd.conf.5,v 1.108 2009/08/07 11:10:23 reyk Exp $ +.\" +.\" Copyright (c) 2006, 2007 Reyk Floeter +.\" Copyright (c) 2006, 2007 Pierre-Yves Ritschard +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: August 7 2009 $ +.Dt RELAYD.CONF 5 +.Os +.Sh NAME +.Nm relayd.conf +.Nd relay daemon configuration file +.Sh DESCRIPTION +.Nm +is the configuration file for the relay daemon, +.Xr relayd 8 . +.Sh SECTIONS +.Nm +is divided into six 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 relayd 8 . +Do note that the config file allows global settings to be added after +defining tables in the config file, but those tables will use the +built-in defaults instead of the global settings below them. +.It Sy Tables +Table definitions describe a list of hosts, +in a similar fashion to +.Xr pf 4 +tables. +They are used for relay and redirection target selection with the +described options and health checking on the host they contain. +.It Sy Redirections +Redirections are translated to +.Xr pf 4 +rdr rules for stateful forwarding to a target host from a +health-checked table on layer 3. +.It Sy Relays +Relays allow application layer load balancing, SSL acceleration, and +general purpose TCP proxying on layer 7. +.It Sy Protocols +Protocols are predefined protocol handlers and settings for relays. +.El +.Pp +Within the sections, +a host +.Ar address +can be specified by IPv4 address, IPv6 address, or DNS hostname. +A +.Ar port +can be specified by number or name. +The port name to number mappings are found in the file +.Pa /etc/services ; +see +.Xr services 5 +for details. +.Pp +Comments can be put anywhere in the file using a hash mark +.Pq Sq # , +and extend to the end of the current line. +.Pp +Additional configuration files can be included with the +.Ic include +keyword, for example: +.Bd -literal -offset indent +include "/etc/relayd.conf.local" +.Ed +.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 relay , +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 \*(Ltwebhosts\*(Gt { + $www1 + $www2 +} +.Ed +.Sh GLOBAL CONFIGURATION +Here are the settings that can be set globally: +.Bl -tag -width Ds +.It Ic interval Ar number +Set the interval in seconds at which the hosts will be checked. +The default interval is 10 seconds. +.It Xo +.Ic log +.Pq Ic updates Ns \&| Ns Ic all +.Xc +Log state notifications after completed host checks. +Either only log the +.Ic updates +to new states or log +.Ic all +state notifications, even if the state didn't change. +The host state can be +.Ar up +(the health check completed successfully), +.Ar down +(the host is down or didn't match the check criteria), +or +.Ar unknown +(the host is disabled or has not been checked yet). +.It Ic prefork Ar number +When using relays, run the specified number of processes to handle +relayed connections. +This increases the performance and prevents delays when connecting +to a relay. +.Xr relayd 8 +runs 5 relay processes by default and every process will handle +all configured relays. +.It Ic timeout Ar number +Set the global timeout in milliseconds for checks. +This can be overridden by the timeout value in the table definitions. +The default interval is 200 milliseconds and it must not exceed the +global interval. +Please note that the default value is optimized for checks within the +same collision domain \(en use a higher timeout, such as 1000 milliseconds, +for checks of hosts in other subnets. +If this option is to be set, it should be placed before overrides in tables. +.El +.Sh TABLES +Tables are used to group a set of hosts as the target for redirections +or relays; they will be mapped to a +.Xr pf 4 +table for redirections. +Tables may be defined with the following attribute: +.Bl -tag -width disable +.It Ic disable +Start the table disabled \(en no hosts will be checked in this table. +The table can be later enabled through +.Xr relayctl 8 . +.Pp +.El +Each table must contain at least one host; +multiple hosts are separated by newline, comma, or whitespace. +Host entries may be defined with the following attributes: +.Bl -tag -width retry +.It Ic ip ttl Ar number +Change the default time-to-live value in the IP headers for host checks. +.It Ic parent Ar number +The optional parent option inherits the state from a parent +host with the specified identifier. +The check will be skipped for this host and copied from the parent host. +This can be used to prevent multiple checks on hosts with multiple IP +addresses for the same service. +The host identifiers are sequentially assigned to the configured hosts +starting with 1; it can be shown with the +.Xr relayctl 8 +.Ic show summary +commands. +.It Ic retry Ar number +The optional retry option adds a tolerance for failed host checks; +the check will be retried for +.Ar number +more times before setting the host state to down. +If this table is used by a relay, it will also specify the number of +retries for outgoing connection attempts. +.El +.Pp +For example: +.Bd -literal -offset indent +table \*(Ltservice\*(Gt { 192.168.1.1, 192.168.1.2, 192.168.2.3 } +table \*(Ltfallback\*(Gt disable { 10.1.5.1 retry 2 } + +redirect "www" { + listen on www.example.com port 80 + forward to \*(Ltservice\*(Gt check http "/" code 200 + forward to \*(Ltfallback\*(Gt check http "/" code 200 +} +.Ed +.Pp +Tables are used by +.Ic forward to +directives in redirections or relays with a set of general options, +health-checking rules, and timings; +see the +.Sx REDIRECTIONS +and +.Sx RELAYS +sections for more information about the forward context. +Table specific configuration directives are described below. +Multiple options can be appended to +.Ic forward to +directives, separated by whitespaces. +.Pp +The following options will configure the health-checking method for +the table, and is mandatory for redirections: +.Bl -tag -width Ds +.It Xo +.Ic check http Ar path +.Op Ic host Ar hostname +.Ic code Ar number +.Xc +For each host in the table, verify that retrieving the URL +.Ar path +gives the HTTP return code +.Ar number . +If +.Ar hostname +is specified, it is used as the +.Dq Host: +header to query a specific hostname at the target host. +To validate the HTTP return code, use this shell command: +.Bd -literal -offset indent +$ echo -n "HEAD HTTP/1.0\er\en\er\en" | \e + nc | head -n1 +.Ed +.Pp +This prints the status header including the actual return code: +.Bd -literal -offset indent +HTTP/1.1 200 OK +.Ed +.It Xo +.Ic check https Ar path +.Op Ic host Ar hostname +.Ic code Ar number +.Xc +This has the same effect as above but wraps the HTTP request in SSL. +.It Xo +.Ic check http Ar path +.Op Ic host Ar hostname +.Ic digest Ar string +.Xc +For each host in the table, verify that retrieving the URL +.Ar path +produces non-binary content whose message digest matches the defined string. +The algorithm used is determined by the string length of the +.Ar digest +argument, either SHA1 (40 characters) or MD5 (32 characters). +If +.Ar hostname +is specified, it is used as the +.Dq Host: +header to query a specific hostname at the target host. +The digest does not take the HTTP headers into account. +Do not specify a binary object (such as a graphic) as the target of the +request, as +.Nm +expects the data returned to be a string. +To compute the digest, use this simple command: +.Bd -literal -offset indent +$ ftp -o - http://host[:port]/path | sha1 +.Ed +.Pp +This gives a digest that can be used as-is in a digest statement: +.Bd -literal -offset indent +a9993e36476816aba3e25717850c26c9cd0d89d +.Ed +.It Xo +.Ic check https Ar path +.Op Ic host Ar hostname +.Ic digest Ar string +.Xc +This has the same effect as above but wraps the HTTP request in SSL. +.It Ic check icmp +Ping hosts in this table to determine whether they are up or not. +This method will automatically use ICMP or ICMPV6 depending on the +address family of each host. +.It Ic check script Ar path +Execute an external program to check the host state. +The program will be executed for each host by specifying the hostname +on the command line: +.Bd -literal -offset indent +/usr/local/bin/checkload.pl front-www1.private.example.com +.Ed +.Pp +.Xr relayd 8 +expects a positive return value on success and zero on failure. +Note that the script will be executed with the privileges of the +.Qq _relayd +user and terminated after +.Ar timeout +milliseconds. +.It Xo +.Ic check send +.Ar data +.Ic expect +.Ar pattern +.Op Ic ssl +.Xc +For each host in the table, a TCP connection is established on the +port specified, then +.Ar data +is sent. +Incoming data is then read and is expected to match against +.Ar pattern +using shell globbing rules. +If +.Ar data +is an empty string or +.Ic nothing +then nothing is sent on the connection and data is immediately +read. +This can be useful with protocols that output a banner like +SMTP, NNTP, and FTP. +If the +.Ic ssl +keyword is present, +the transaction will occur in an SSL tunnel. +.It Ic check ssl +Perform a complete SSL handshake with each host to check their availability. +.It Ic check tcp +Use a simple TCP connect to check that hosts are up. +.El +.Pp +The following general table options are available: +.Bl -tag -width Ds +.It Ic interval Ar number +Override the global interval and specify one for this table. +It must be a multiple of the global interval. +.It Ic timeout Ar number +Set the timeout in milliseconds for each host that is checked using +TCP as the transport. +This will override the global timeout, which is 200 milliseconds by default. +.El +.Pp +The following options will set the scheduling algorithm to select a +host from the specified table: +.Bl -tag -width Ds +.It Ic mode hash +Balances the outgoing connections across the active hosts based on the +hashed name of the table. +Additional input can be fed into the hash by looking at HTTP +headers and GET variables; see the +.Sx PROTOCOLS +section below. +This mode is only supported by relays. +.It Ic mode loadbalance +Balances the outgoing connections across the active hosts based on the +hashed name of the table, the source and destination addresses, +and the corresponding ports. +This mode is only supported by relays. +.It Ic mode roundrobin +Distributes the outgoing connections using a round-robin scheduler +through all active hosts. +This is the default mode and will be used if no option has been specified. +This mode is supported by redirections and relays. +.El +.Sh REDIRECTIONS +Redirections represent a +.Xr pf 4 +rdr rule. +They are used for stateful redirections to the hosts in the specified +tables. +.Xr pf 4 +rewrites the target IP addresses and ports of the incoming +connections, operating on layer 3. +The configuration directives that are valid in the +.Ic redirect +context are described below: +.Bl -tag -width Ds +.It Ic disable +The redirection is initially disabled. +It can be later enabled through +.Xr relayctl 8 . +.It Xo +.Ic forward to +.Aq Ar table +.Op Ic port Ar number +.Ar options ... +.Xc +Specify the tables of target hosts to be used; see the +.Sx TABLES +section above for information about table options. +If the +.Ic port +option is not specified, the first port from the +.Ic listen on +directive will be used. +This directive can be specified twice \(en the second entry will be used +as the backup table if all hosts in the main table are down. +At least one entry for the main table is mandatory. +.It Xo +.Ic listen on Ar address +.Op ip-proto +.Ic port Ar port +.Op Ic interface Ar name +.Xc +Specify an +.Ar address +and a +.Ar port +to listen on. +.Xr pf 4 +will redirect incoming connections for the specified target to the +hosts in the main or backup table. +The +.Ar port +argument can optionally specify a port range instead of a single port; +the format is +.Ar min-port : Ns Ar max-port . +The optional argument +.Ar ip-proto +can be used to specify an IP protocol like +.Ar tcp +or +.Ar udp ; +it defaults to +.Ar tcp . +The rdr rule can be optionally restricted to a given interface name. +.It Xo +.Ic route to +.Aq Ar table +.Op Ic port Ar number +.Ar options ... +.Xc +Like the +.Ic forward to +directive, but directly routes the packets to the target host without +modifying the target address. +This can be used for +.Dq direct server return +to force the target host to respond via a different gateway. +Note that hosts have to accept sessions for the same address as +the gateway, which is typically done by configuring a loopback +interface on the host with this address. +.It Ic session timeout Ar seconds +Specify the inactivity timeout in seconds for established redirections. +The default timeout is 600 seconds (10 minutes). +.It Ic sticky-address +This has the same effect as specifying sticky-address +for an rdr rule in +.Xr pf.conf 5 . +It will ensure that multiple connections from the same source are +mapped to the same redirection address. +.It Ic tag Ar name +Automatically tag packets passing through the +.Xr pf 4 +rdr rule with the name supplied. +This allows simpler filter rules. +.El +.Sh RELAYS +Relays will forward traffic between a client and a target server. +In contrast to redirections and IP forwarding in the network stack, a +relay will accept incoming connections from remote clients as a +server, open an outgoing connection to a target host, and forward +any traffic between the target host and the remote client, +operating on layer 7. +A relay is also called an application layer gateway or layer 7 proxy. +.Pp +The main purpose of a relay is to provide advanced load balancing +functionality based on specified protocol characteristics, such as +HTTP headers, to provide SSL acceleration and to allow +basic handling of the underlying application protocol. +.Pp +The +.Ic relay +configuration directives are described below: +.Bl -tag -width Ds +.It Ic disable +Start the relay but immediately close any accepted connections. +.It Xo +.Op Ic transparent +.Ic forward +.Op Ic with ssl +.Ic to +.Ar address +.Op Ic port Ar port +.Ar options ... +.Xc +Specify the address and port of the target host to connect to. +If the +.Ic port +option is not specified, the port from the +.Ic listen on +directive will be used. +Use the +.Ic transparent +keyword to enable fully-transparent mode; the source address of the +client will be retained in this case. +.Pp +The +.Ic with ssl +directive enables client-side SSL mode to connect to the remote host. +Verification of server certificates can be enabled by setting the +.Ic ca file +option in the protocol section. +.Pp +The following options may be specified for forward directives: +.Pp +.Bl -tag -width Ds +.It Ic retry Ar number +The optional host +.Ic retry +option will be used as a tolerance for failed +host connections; the connection will be retried for +.Ar number +more times. +.It Ic inet +If the requested destination is an IPv6 address, +.Xr relayd 8 +will forward the connection to an IPv4 address which is determined by +the last 4 octets of the original IPv6 destination. +For example, if the original IPv6 destination address is +2001:db8:7395:ffff::a01:101, the session is relayed to the IPv4 +address 10.1.1.1 (a01:101). +.It Ic inet6 Ar address-prefix +If the requested destination is an IPv4 address, +.Xr relayd 8 +will forward the connection to an IPv6 address which is determined by +setting the last 4 octets of the specified IPv6 +.Ar address-prefix +to the 4 octets of the original IPv4 destination. +For example, if the original IPv4 destination address is 10.1.1.1 and +the specified address prefix is 2001:db8:7395:ffff::, the session is +relayed to the IPv6 address 2001:db8:7395:ffff::a01:101. +.El +.It Xo +.Ic forward to +.Aq Ar table +.Op Ic port Ar port +.Ar options ... +.Xc +Like the previous directive, but connect to a host from the specified +table; see the +.Sx TABLES +section above for information about table options. +.It Xo +.Ic forward to +.Ic nat lookup +.Ar options ... +.Xc +When redirecting connections with an +.Ar rdr +rule in +.Xr pf.conf 5 +to a relay listening on localhost, this directive will +look up the real destination address of the intended target host, +allowing the relay to be run as a transparent proxy. +If an additional +.Ic forward to +directive to a specified address or table is present, +it will be used as a backup if the NAT lookup failed. +.It Xo +.Ic listen on Ar address +.Op Ic port Ar port +.Op Ic ssl +.Xc +Specify the address and port for the relay to listen on. +The relay will accept incoming connections to the specified address. +If the +.Ic port +option is not specified, the port from the +.Ic listen on +directive will be used. +.Pp +If the +.Ic ssl +keyword is present, the relay will accept connections using the +encrypted SSL protocol. +The relay will look up a private key in +.Pa /etc/ssl/private/address.key +and a public certificate in +.Pa /etc/ssl/address.crt , +where +.Ar address +is the specified IP address of the relay to listen on. +See +.Xr ssl 8 +for details about SSL server certificates. +.It Ic protocol Ar name +Use the specified protocol definition for the relay. +The generic TCP protocol options will be used by default; +see the +.Sx PROTOCOLS +section below. +.It Ic session timeout Ar seconds +Specify the inactivity timeout in seconds for accepted sessions. +The default timeout is 600 seconds (10 minutes). +.El +.Sh PROTOCOLS +Protocols are templates defining actions and settings for relays. +They allow setting generic TCP options, SSL settings, and actions +specific to the selected application layer protocol. +.Pp +The protocol directive is available for a number of different +application layer protocols. +There is no generic handler for UDP-based protocols because it is a +stateless datagram-based protocol which has to look into the +application layer protocol to find any possible state information. +.Bl -tag -width Ds +.It Ic dns protocol +(UDP) +Domain Name System (DNS) protocol. +The requested IDs in the DNS header will be used to match the state. +.Xr relayd 8 +replaces these IDs with random values to compensate for +predictable values generated by some hosts. +.It Ic http protocol +Handle the Hypertext Transfer Protocol +(HTTP, or "HTTPS" if encapsulated in an SSL tunnel). +.It Xo +.Op Ic tcp +.Ic protocol +.Xc +Generic handler for TCP-based protocols. +This is the default. +.El +.Pp +The available configuration directives are described below: +.Bl -tag -width Ds +.It Xo +.Op Ar direction +.Op Ar type +.Ar action +.Op Ic marked Ar id +.Op Ic log +.Xc +Define an action for the selected entity. +The optional +.Ic log +keyword will log the entity name and the value and +the optional +.Ic marked +keyword requires that the session has been marked with a given +identifier in order to execute the action. +The actions are dependent on the underlying application +.Ic protocol . +.El +.Pp +.Bq Ar direction +may be one of: +.Bl -tag -width Ds +.It Ic request +Handle the data stream from the client to the relay, like HTTP +requests. +This is the default if the +.Ar direction +directive is omitted. +.It Ic response +Handle the data stream from the target host to the relay, like +HTTP server replies. +.El +.Pp +.Bq Ar type +may be one of: +.Bl -tag -width Ds +.It Ic cookie +Look up the entity as a value in the Cookie header when using the +.Ic http +protocol. +This type is only available with the direction +.Ic request . +.It Ic header +Look up the entity in the application protocol headers, like HTTP +headers in +.Ic http +mode. +.It Ic path +Look up the entity as a value in the URL path when using the +.Ic http +protocol. +This type is only available with the direction +.Ic request . +The +.Ar key +will match the path of the requested URL without the hostname +and query and the value will match the complete query, +for example: +.Bd -literal -offset indent +request path filter "/index.html" +request path filter "foo=bar*" from "/cgi-bin/t.cgi" +.Ed +.It Ic query +Look up the entity as a query variable in the URL when using the +.Ic http +protocol. +This type is only available with the direction +.Ic request , +for example: +.Bd -literal -offset indent +# Will match /cgi-bin/example.pl?foo=bar&ok=yes +request query expect "bar" from "foo" +.Ed +.It Ic url +Look up the entity as a URL suffix/prefix expression consisting of a +canonicalized hostname without port or suffix and a path name or +prefix when using the +.Ic http +protocol. +This type is only available with the direction +.Ic request , +for example: +.Bd -literal -offset indent +request url filter "example.com/index.html" +request url filter "example.com/test.cgi?val=1" +.Ed +.Pp +.Xr relayd 8 +will match the full URL and different possible suffix/prefix +combinations by stripping subdomains and path components (up to 5 +levels), and the query string. +For example, the following +lookups will be done for +.Ar http://www.example.com:81/1/2/3/4/5.html?query=yes : +.Bd -literal -offset indent +www.example.com/1/2/3/4/5.html?query=yes +www.example.com/1/2/3/4/5.html +www.example.com/ +www.example.com/1/ +www.example.com/1/2/ +www.example.com/1/2/3/ +example.com/1/2/3/4/5.html?query=yes +example.com/1/2/3/4/5.html +example.com/ +example.com/1/ +example.com/1/2/ +example.com/1/2/3/ +.Ed +.El +.Pp +.Bq Ar action +may be one of: +.Bl -tag -width Ds +.It Ic append Ar value Ic to Ar key +Append the specified value to a protocol entity with the selected name. +When using the +.Ic http +protocol, +.Ic key +will indicate a specified HTTP header. +If +.Ar key +does not exist in the request, it will be created with the value +set to +.Ar value . +.Pp +The +.Ar value +string +may contain predefined macros that will be expanded at runtime: +.Pp +.Bl -tag -width $SERVER_ADDR -offset indent -compact +.It Ic $REMOTE_ADDR +The IP address of the connected client. +.It Ic $REMOTE_PORT +The TCP source port of the connected client. +.It Ic $SERVER_ADDR +The configured IP address of the relay. +.It Ic $SERVER_PORT +The configured TCP server port of the relay. +.It Ic $SERVER_NAME +The server software name of +.Xr relayd 8 . +.It Ic $TIMEOUT +The configured session timeout of the relay. +.El +.It Ic change Ar key Ic to Ar value +Like the +.Ic append +directive above, but change the contents of the specified entity. +If +.Ar key +does not exist in the request, it will be created with the value +set to +.Ar value . +.Pp +The +.Ar value +string +may contain predefined macros that will be expanded at runtime, +as detailed for the +.Ic append +directive above. +.It Ic expect Ar value Ic from Ar key +Expect an entity +.Ar key +and match against +.Ar value +using shell globbing rules. +If the entity is not present or the value doesn't match, the connection +will be dropped. +.It Xo +.Ic expect +.Op Ic digest +.Ar key +.Xc +Expect an entity +.Ar key +with any possible value. +This is the short form of +.Ic expect Ar * Ic from Ar key . +.Pp +If the +.Ic digest +keyword is specified, +compare the message digest of the entity against the defined string. +The algorithm used is determined by the string length of the +.Ar key +argument, either SHA1 (40 characters) or MD5 (32 characters). +To compute the digest, use this simple command: +.Bd -literal -offset indent +$ echo -n "example.com/path/?args" | sha1 +.Ed +.It Ic expect file Ar path +Like the directive above, but load the non-digest keys from an +external file with the specified +.Ar path +containing one key per line. +Lines will be stripped at the first whitespace or newline character. +Any empty lines or lines beginning with a hash mark +.Pq Sq # +will be ignored. +.It Ic filter Ar value Ic from Ar key +Like the +.Ic expect Ar .. Ic from +directive above, but drop any connections with the specified entity +.Ar key +and a matching +.Ar value . +.It Xo +.Ic filter +.Op Ic digest +.Ar key +.Xc +Like the +.Ic expect +directive above, but drop any connections with the specified entity +.Ar key +and any possible value. +This is the short form of +.Ic filter Ar * Ic from Ar key . +.It Ic filter file Ar path +Like the directive above, but load the non-digest keys from +.Ar path . +See +.Ic expect file Ar path +for more information. +.It Ic hash Ar key +Feed the value of the selected entity into the load balancing hash to +select the target host. +See the +.Ic table +keyword in the +.Sx RELAYS +section above. +.It Ic log Ar key +Log the name and the value of the entity. +.It Ic log file Ar path +Like the directive above, but load the keys from +.Ar path . +See +.Ic expect file Ar path +for more information. +.It Xo +.Ic mark +.Op Ar value Ic from +.Ar key Ic with Ar id +.Xc +Mark the session with the specified identifier (a positive number +between 1 and 65535) if the specified condition matches. +Note that the +.Ic mark +action does not accept the +.Ic marked +option (see above). +.It Ic label Ar string +Add a label to subsequently added actions. +The label will be printed as part of the error message if the +.Ic return error +option is set and may contain HTML tags, for example: +.Bd -literal -offset indent +label "\*(Lta href='http://example.com/advisory.pl?id=7359'\*(Gt\e + Advisory provided by example.com\*(Lt/a\*(Gt" +url filter digest 5c1e03f58f8ce0b457474ffb371fd1ef +url filter digest 80c1a7b8337462093ef8359c57b4d56a +no label +.Ed +.It Ic no label +Do not set a label for subsequently added actions; this is the default. +.It Ic remove Ar key +Remove the entity with the selected name. +.It Ic remove file Ar path +Like the directive above, but load the keys from +.Ar path . +See +.Ic expect file Ar path +for more information. +.It Ic return error Op Ar option +Return an error response to the client if an internal operation or the +forward connection to the client failed. +By default, the connection will be silently dropped. +The effect of this option depends on the protocol: HTTP will send an +error header and page to the client before closing the connection. +Additional valid options are: +.Bl -tag -width Ds +.It Ic style Ar string +Specify a Cascading Style Sheet (CSS) to be used for the returned +HTTP error pages, for example: +.Bd -literal -offset indent +body { background: #a00000; color: white; } +.Ed +.El +.It Ic ssl Ar option +Set the SSL options and session settings. +This is only used if SSL is enabled in the relay. +Valid options are: +.Bl -tag -width Ds +.It Ic ca file Ar path +This option enables CA verification in SSL client mode. +The daemon will load the CA (Certificate Authority) certificates from +the specified path to verify the server certificates. +.Ox +provides a default CA bundle in +.Pa /etc/ssl/cert.pem . +.It Ic ciphers Ar string +Set the string defining the SSL cipher suite. +If not specified, the default value +.Ar HIGH:!ADH +will be used (strong crypto cipher suites without anonymous DH). +See the +.Sx CIPHERS +section of +.Xr openssl 1 +for information about SSL cipher suites and preference lists. +.It Ic session cache Ar value +Set the maximum size of the SSL session cache. +If the +.Ar value +is zero, the default size defined by the SSL library will be used. +A positive number will set the maximum size in bytes and the keyword +.Ic disable +will disable the SSL session cache. +.It Xo +.Op Ic no +.Ic sslv2 +.Xc +Enable the SSLv2 protocol; +disabled by default. +.It Xo +.Op Ic no +.Ic sslv3 +.Xc +Disable the SSLv3 protocol; +enabled by default. +.It Xo +.Op Ic no +.Ic tlsv1 +.Xc +Disable the TLSv1/SSLv3.1 protocol; +enabled by default. +.El +.It Ic tcp Ar option +Enable or disable the specified TCP/IP options; see +.Xr tcp 4 +and +.Xr ip 4 +for more information about the options. +Valid options are: +.Bl -tag -width Ds +.It Ic backlog Ar number +Set the maximum length the queue of pending connections may grow to. +The backlog option is 10 by default and is limited by the +.Ic kern.somaxconn +.Xr sysctl 8 +variable. +.It Ic ip minttl Ar number +This option for the underlying IP connection may be used to discard packets +with a TTL lower than the specified value. +This can be used to implement the +.Ar Generalized TTL Security Mechanism (GTSM) +according to RFC 3682. +.It Ic ip ttl Ar number +Change the default time-to-live value in the IP headers. +.It Xo +.Op Ic no +.Ic nodelay +.Xc +Enable the TCP NODELAY option for this connection. +This is recommended to avoid delays in the relayed data stream, +e.g. for SSH connections. +.It Xo +.Op Ic no +.Ic sack +.Xc +Use selective acknowledgements for this connection. +.It Ic socket buffer Ar number +Set the socket-level buffer size for input and output for this +connection. +This will affect the TCP window size. +.El +.El +.Sh FILES +.Bl -tag -width "/etc/ssl/private/address.keyXX" -compact +.It Pa /etc/relayd.conf +.Xr relayd 8 +configuration file. +.Pp +.It Pa /etc/services +Service name database. +.Pp +.It Pa /etc/ssl/address.crt +.It Pa /etc/ssl/private/address.key +Location of the relay SSL server certificates, where +.Ar address +is the configured IP address of the relay. +.It Pa /etc/ssl/cert.pem +Default location of the CA bundle that can be used with +.Xr relayd 8 . +.El +.Sh EXAMPLES +This configuration file would create a redirection service +.Dq www +which load balances four hosts +and falls back to one host containing a +.Dq 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 \*(Ltphphosts\*(Gt { $www1, $www2, $www3, $www4 } +table \*(Ltsorryhost\*(Gt disable { sorryhost.private.example.com } + +redirect "www" { + listen on www.example.com port 8080 interface trunk0 + listen on www6.example.com port 80 interface trunk0 + + tag REDIRECTED + + forward to \*(Ltphphosts\*(Gt port 8080 timeout 300 \e + check http "/" digest "630aa3c2f..." + forward to \*(Ltsorryhost\*(Gt port 8080 timeout 300 check icmp +} +.Ed +.Pp +It is possible to specify multiple listen directives with different IP +protocols in a single redirection configuration: +.Bd -literal -offset indent +redirect "dns" { + listen on dns.example.com tcp port 53 + listen on dns.example.com udp port 53 + + forward to \*(Ltdnshosts\*(Gt port 53 check tcp +} +.Ed +.Pp +The following configuration would add a relay to forward +secure HTTPS connections to a pool of HTTP webservers +using the +.Ic loadbalance +mode (SSL acceleration and layer 7 load balancing). +The HTTP protocol definition will add two HTTP headers containing +address information of the client and the server, set the +.Dq Keep-Alive +header value to the configured session timeout, +and include the +.Dq sessid +variable in the hash to calculate the target host: +.Bd -literal -offset indent +http protocol "http_ssl" { + header append "$REMOTE_ADDR" to "X-Forwarded-For" + header append "$SERVER_ADDR:$SERVER_PORT" to "X-Forwarded-By" + header change "Keep-Alive" to "$TIMEOUT" + query hash "sessid" + cookie hash "sessid" + path filter "*command=*" from "/cgi-bin/index.cgi" + + ssl { sslv2, ciphers "MEDIUM:HIGH" } +} + +relay "sslaccel" { + listen on www.example.com port 443 ssl + protocol "http_ssl" + forward to \*(Ltphphosts\*(Gt port 8080 mode loadbalance check tcp +} +.Ed +.Pp +The second relay example will accept incoming connections to port +2222 and forward them to a remote SSH server. +The TCP +.Ic nodelay +option will allow a +.Dq smooth +SSH session without delays between keystrokes or displayed output on +the terminal: +.Bd -literal -offset indent +protocol "myssh" { + tcp { nodelay, socket buffer 65536 } +} + +relay "sshforward" { + listen on www.example.com port 2222 + protocol "myssh" + forward to shell.example.com port 22 +} +.Ed +.Sh SEE ALSO +.Xr relayctl 8 , +.Xr relayd 8 , +.Xr ssl 8 +.Sh HISTORY +The +.Nm +file format, formerly known as +.Ic hoststated.conf , +first appeared in +.Ox 4.1 . +It was renamed to +.Nm +in +.Ox 4.3 . +.Sh AUTHORS +.An -nosplit +The +.Xr relayd 8 +program was written by +.An Pierre-Yves Ritschard Aq pyr@openbsd.org +and +.An Reyk Floeter Aq reyk@openbsd.org . +.Sh CAVEATS +.Xr relayd 8 +Verification of SSL server certificates is based on a static CA bundle +and +.Xr relayd 8 +currently does not support CRLs (Certificate Revocation Lists). Index: contrib/relayd/shuffle.c =================================================================== --- contrib/relayd/shuffle.c (revision 0) +++ contrib/relayd/shuffle.c (revision 0) @@ -0,0 +1,76 @@ +/* $OpenBSD: shuffle.c,v 1.2 2009/06/09 16:26:03 deraadt Exp $ */ + +/* + * Portions Copyright (C) 2008 Theo de Raadt + * + * 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 INTERNET SOFTWARE CONSORTIUM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM 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. + */ + +/* based on: bind/lib/isc/shuffle.c,v 1.4 2008/07/09 17:07:32 reyk Exp $ */ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "relayd.h" + +#define VALID_SHUFFLE(x) (x != NULL) + +void +shuffle_init(struct shuffle *shuffle) +{ + int i, i2; + + assert(VALID_SHUFFLE(shuffle)); + + shuffle->isindex = 0; + /* Initialize using a Knuth shuffle */ + for (i = 0; i < 65536; ++i) { + i2 = arc4random_uniform(i + 1); + shuffle->id_shuffle[i] = shuffle->id_shuffle[i2]; + shuffle->id_shuffle[i2] = i; + } +} + +u_int16_t +shuffle_generate16(struct shuffle *shuffle) +{ + u_int32_t si; + u_int16_t r; + int i, i2; + + assert(VALID_SHUFFLE(shuffle)); + + do { + si = arc4random(); + i = shuffle->isindex & 0xFFFF; + i2 = (shuffle->isindex - (si & 0x7FFF)) & 0xFFFF; + r = shuffle->id_shuffle[i]; + shuffle->id_shuffle[i] = shuffle->id_shuffle[i2]; + shuffle->id_shuffle[i2] = r; + shuffle->isindex++; + } while (r == 0); + + return (r); +} Index: usr.sbin/relayd/relayd/Makefile =================================================================== --- usr.sbin/relayd/relayd/Makefile (revision 0) +++ usr.sbin/relayd/relayd/Makefile (revision 0) @@ -0,0 +1,41 @@ +# $FreeBSD$ +# $OpenBSD: Makefile,v 1.18 2008/07/09 17:16:51 reyk Exp $ + +PROG= relayd + +MAN= relayd.8 \ + relayd.conf.5 + +.PATH: ${.CURDIR}/../../../contrib/relayd + +SRCS= parse.y \ + log.c \ + control.c \ + buffer.c \ + imsg.c \ + ssl.c \ + ssl_privsep.c \ + relayd.c \ + pfe.c \ + pfe_filter.c \ + hce.c \ + relay.c \ + relay_udp.c \ + check_icmp.c \ + check_tcp.c \ + check_script.c \ + name2id.c \ + shuffle.c + +CFLAGS+= -DSHA1_DIGEST_LENGTH=SHA_DIGEST_LENGTH \ + -DSHA1_DIGEST_STRING_LENGTH=SHA_DIGEST_LENGTH \ + -DOPENSSL_NO_SHA -DOPENSSL_NO_MD5 \ + -D__dead='' +CFLAGS+= -I${.CURDIR}/../../../contrib/relayd \ + -I${.CURDIR}/../../../contrib/pf/libevent +CLEANFILES+= y.tab.h + +LDADD= -lmd ${LIBEVENT} -lssl -lcrypto +DPADD= ${LIBEVENT} ${LIBSSL} ${LIBCRYPTO} + +.include Index: usr.sbin/relayd/Makefile.inc =================================================================== --- usr.sbin/relayd/Makefile.inc (revision 0) +++ usr.sbin/relayd/Makefile.inc (revision 0) @@ -0,0 +1,5 @@ +# $FreeBSD$ + +LIBEVENT= ${.OBJDIR}/../../ftp-proxy/libevent/libevent.a + +.include "../Makefile.inc" Index: usr.sbin/relayd/Makefile =================================================================== --- usr.sbin/relayd/Makefile (revision 0) +++ usr.sbin/relayd/Makefile (revision 0) @@ -0,0 +1,5 @@ +# $FreeBSD$ + +SUBDIR= ../ftp-proxy/libevent relayd + +.include Index: usr.sbin/relayctl/Makefile =================================================================== --- usr.sbin/relayctl/Makefile (revision 0) +++ usr.sbin/relayctl/Makefile (revision 0) @@ -0,0 +1,22 @@ +# $FreeBSD$ +# $OpenBSD: Makefile,v 1.3 2007/12/07 17:17:01 reyk Exp $ + +PROG= relayctl + +.PATH: ${.CURDIR}/../../contrib/relayd +SRCS= buffer.c \ + imsg.c \ + log.c + +.PATH: ${.CURDIR}/../../contrib/relayctl +SRCS+= relayctl.c \ + parser.c + +MAN= relayctl.8 + +CFLAGS+= -D__dead='' +CFLAGS+= -I${.CURDIR}/../../contrib/relayctl \ + -I${.CURDIR}/../../contrib/relayd \ + -I${.CURDIR}/../../contrib/pf/libevent + +.include Index: usr.sbin/Makefile =================================================================== --- usr.sbin/Makefile (revision 208589) +++ usr.sbin/Makefile (working copy) @@ -146,6 +146,8 @@ quot \ ${_quotaon} \ rarpd \ + relayctl \ + relayd \ ${_repquota} \ ${_rip6query} \ rmt \ Index: etc/Makefile =================================================================== --- etc/Makefile (revision 208589) +++ etc/Makefile (working copy) @@ -17,7 +17,7 @@ phones profile protocols \ rc rc.bsdextended rc.firewall rc.initdiskless \ rc.sendmail rc.shutdown \ - rc.subr remote rpc services shells \ + rc.subr relayd.conf remote rpc services shells \ sysctl.conf syslog.conf termcap.small \ etc.${MACHINE_ARCH}/ttys Index: etc/rc.d/relayd =================================================================== --- etc/rc.d/relayd (revision 0) +++ etc/rc.d/relayd (revision 0) @@ -0,0 +1,27 @@ +#!/bin/sh +# +# $FreeBSD$ +# + +# PROVIDE: relayd +# REQUIRE: NETWORKING syslogd +# BEFORE: DAEMON +# KEYWORD: nojail shutdown + +. /etc/rc.subr + +name="relayd" +rcvar=`set_rcvar` +pidfile="/var/run/${name}.pid" +command="/usr/sbin/${name}" +relayctl="/usr/sbin/relayctl" +required_files="/etc/relayd.conf" +reload_cmd="relayd_reload_cmd" + +relayd_reload_cmd() +{ + ${relayctl} reload +} + +load_rc_config $name +run_rc_command "$1" Index: etc/rc.d/Makefile =================================================================== --- etc/rc.d/Makefile (revision 208589) +++ etc/rc.d/Makefile (working copy) @@ -29,7 +29,7 @@ pf pflog pfsync \ powerd power_profile ppp pppoed pwcheck \ quota \ - random rarpd resolv rfcomm_pppd_server root \ + random rarpd relayd resolv rfcomm_pppd_server root \ route6d routed routing rpcbind rtadvd rtsold rwho \ savecore sdpd securelevel sendmail \ serial sppp statd static_arp swap1 \ Index: etc/relayd.conf =================================================================== --- etc/relayd.conf (revision 0) +++ etc/relayd.conf (revision 0) @@ -0,0 +1,106 @@ +# $FreeBSD$ +# $OpenBSD: relayd.conf,v 1.13 2008/03/03 16:58:41 reyk Exp $ +# +# Macros +# +#ext_addr="192.168.1.1" +#webhost1="10.0.0.1" +#webhost2="10.0.0.2" +#sshhost1="10.0.0.3" +# +# +# Global Options +# +# interval 10 +# timeout 1000 +# prefork 5 +# +# +# Each table will be mapped to a pf table. +# +#table { $webhost1 $webhost2 } +#table { 127.0.0.1 } +# +# +# Services will be mapped to a rdr rule. +# +#redirect www { +# listen on $ext_addr port http interface trunk0 +# +# # tag every packet that goes thru the rdr rule with RELAYD +# tag RELAYD +# +# forward to check http "/" code 200 +# forward to check icmp +#} +# +# +# Relay and protocol for HTTP layer 7 loadbalancing and SSL acceleration +# +#http protocol httpssl { +# header append "$REMOTE_ADDR" to "X-Forwarded-For" +# header append "$SERVER_ADDR:$SERVER_PORT" to "X-Forwarded-By" +# header change "Connection" to "close" +# +# # Various TCP performance options +# tcp { nodelay, sack, socket buffer 65536, backlog 128 } +# +# ssl { no sslv2, sslv3, tlsv1, ciphers HIGH } +# ssl session cache disable +#} +# +#relay wwwssl { +# # Run as a SSL accelerator +# listen on $ext_addr port 443 ssl +# protocol httpssl +# +# # Forward to hosts in the webhosts table using a src/dst hash +# forward to port http mode loadbalance \ +# check http "/" code 200 +#} +# +# +# Relay and protocol for simple TCP forwarding on layer 7 +# +#protocol sshtcp { +# # The TCP_NODELAY option is required for "smooth" terminal sessions +# tcp nodelay +#} +# +#relay sshgw { +# # Run as a simple TCP relay +# listen on $ext_addr port 2222 +# protocol sshtcp +# +# # Forward to the shared carp(4) address of an internal gateway +# forward to $sshhost1 port 22 +#} +# +# +# Relay and protocol for a transparent HTTP proxy +# +#http protocol httpfilter { +# # Return HTTP/HTML error pages to the client +# return error +# +# # Block disallowed browsers +# label "Please try a different Browser" +# header filter "Mozilla/4.0 (compatible; MSIE *" from "User-Agent" +# +# # Block some well-known Instant Messengers +# label "Instant messenger disallowed!" +# response header filter "application/x-msn-messenger" from "Content-Type" +# response header filter "app/x-hotbar-xip20" from "Content-Type" +# response header filter "application/x-icq" from "Content-Type" +# response header filter "AIM/HTTP" from "Content-Type" +# response header filter "application/x-comet-log" from "Content-Type" +#} +# +#relay httpproxy { +# # Listen on localhost, accept redirected connections from pf(4) +# listen on 127.0.0.1 port 8080 +# protocol httpfilter +# +# # Forward to the original target host +# forward to nat lookup +#} Index: etc/defaults/rc.conf =================================================================== --- etc/defaults/rc.conf (revision 208589) +++ etc/defaults/rc.conf (working copy) @@ -261,6 +261,9 @@ hastd_enable="NO" # Run the HAST daemon (YES/NO). hastd_program="/sbin/hastd" # path to hastd, if you want a different one. hastd_flags="" # Optional flags to hastd. +relayd_enable="NO" # Run the relayd daemon (YES/NO). +relayd_program="/usr/sbin/relayd" # path to relayd, if you want a diferent one. +relayd_flags="" # Optional flags to relayd. # # named. It may be possible to run named in a sandbox, man security for # details.