#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "glougloud.h" #include "external/imsgev.h" #if defined(__OpenBSD__) #include "pcap-int.h" #endif struct server_proc { pid_t pid; int fd[2]; struct imsgev iev; }; struct user_proc { pid_t pid; int fd[2]; struct imsgev iev; }; enum user_status { USER_REQUESTED, USER_ACCEPTED }; struct user { LIST_ENTRY(user) entry; struct sockaddr_in addr; enum user_status status; }; // XXX CONF #define PCAP_INTERFACE "lo" #define PCAP_SNAPLEN 100 #define PCAP_FILTER "not port 4430 and not port 53" static void imsgev_server(struct imsgev *, int, struct imsg *); static void imsgev_user(struct imsgev *, int, struct imsg *); static void imsgev_server_needfd(struct imsgev *); static void imsgev_user_needfd(struct imsgev *); struct server_proc *srv_proc; struct user_proc *usr_proc; LIST_HEAD(, user) usr_list; int net_socket; #if defined(__OPENBSD__) void __dead #else void #endif usage(void) { extern char *__progname; fprintf(stderr, "usage: %s [-vi]", __progname); exit(1); } static void sig_handler(int sig, short why, void *data) { log_info("got signal %d", sig); if (sig == SIGINT || sig == SIGTERM) event_loopexit(NULL); } /* * reimplement pcap_open_live with more restrictions on the bpf fd : * - open device read only * - lock the fd * based on OpenBSD tcpdump, privsep_pcap.c v1.16 */ static pcap_t * my_pcap_open_live(const char *dev, int slen, int promisc, int to_ms, char *ebuf, u_int dlt, u_int dirfilt) { #if defined(__OpenBSD__) struct bpf_version bv; u_int v; pcap_t *p; char bpf[sizeof "/dev/bpf0000000000"]; int fd, n = 0; struct ifreq ifr; p = xmalloc(sizeof(*p)); bzero(p, sizeof(*p)); /* priv part */ do { snprintf(bpf, sizeof(bpf), "/dev/bpf%d", n++); fd = open(bpf, O_RDONLY); } while (fd < 0 && errno == EBUSY); if (fd < 0) return NULL; v = 32768; /* XXX this should be a user-accessible hook */ ioctl(fd, BIOCSBLEN, &v); strlcpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); if (ioctl(fd, BIOCSETIF, &ifr) < 0) return NULL; if (dlt != (u_int) -1 && ioctl(fd, BIOCSDLT, &dlt)) return NULL; if (promisc) /* this is allowed to fail */ ioctl(fd, BIOCPROMISC, NULL); if (ioctl(fd, BIOCSDIRFILT, &dirfilt) < 0) return NULL; /* lock the descriptor */ if (ioctl(fd, BIOCLOCK, NULL) < 0) return NULL; /* end of priv part */ /* fd is locked, can only use 'safe' ioctls */ if (ioctl(fd, BIOCVERSION, &bv) < 0) { snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCVERSION: %s", pcap_strerror(errno)); return NULL; } if (bv.bv_major != BPF_MAJOR_VERSION || bv.bv_minor < BPF_MINOR_VERSION) { snprintf(ebuf, PCAP_ERRBUF_SIZE, "kernel bpf filter out of date"); return NULL; } p->fd = fd; p->snapshot = slen; /* Get the data link layer type. */ if (ioctl(fd, BIOCGDLT, &v) < 0) { snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCGDLT: %s", pcap_strerror(errno)); return NULL; } #if _BSDI_VERSION - 0 >= 199510 /* The SLIP and PPP link layer header changed in BSD/OS 2.1 */ switch (v) { case DLT_SLIP: v = DLT_SLIP_BSDOS; break; case DLT_PPP: v = DLT_PPP_BSDOS; break; } #endif p->linktype = v; /* XXX hack from tcpdump */ if (p->linktype == DLT_PFLOG && p->snapshot < 160) p->snapshot = 160; /* set timeout */ if (to_ms != 0) { struct timeval to; to.tv_sec = to_ms / 1000; to.tv_usec = (to_ms * 1000) % 1000000; if (ioctl(p->fd, BIOCSRTIMEOUT, &to) < 0) { snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCSRTIMEOUT: %s", pcap_strerror(errno)); return NULL; } } if (ioctl(fd, BIOCGBLEN, &v) < 0) { snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCGBLEN: %s", pcap_strerror(errno)); return NULL; } p->bufsize = v; p->buffer = (u_char *)malloc(p->bufsize); if (p->buffer == NULL) { snprintf(ebuf, PCAP_ERRBUF_SIZE, "malloc: %s", pcap_strerror(errno)); return NULL; } return p; #else /* defined(__OpenBSD__) */ return pcap_open_live(dev, slen, promisc, to_ms, ebuf); #endif } int main(int argc, char **argv) { struct sockaddr_in sock_addr; struct event ev_sigint, ev_sigterm, ev_sigchld, ev_sighup; char errbuf[PCAP_ERRBUF_SIZE]; struct bpf_program bprog; pcap_t *pcap; int loglevel = 0, logpinvalid = 0; int op; int sock_on = 1; if (geteuid() != 0) errx(1, "need root privileges"); while ((op = getopt(argc, argv, "hvi")) != -1) { switch (op) { case 'h': usage(); /* NOTREACHED */ case 'v': loglevel++; break; case 'i': logpinvalid = 1; break; default: usage(); /* NOTREACHED */ } } log_init(loglevel, logpinvalid); event_init(); net_socket = socket(AF_INET, SOCK_DGRAM, 0); if (net_socket < 0) fatal("server: socket failed"); setsockopt(net_socket, SOL_SOCKET, SO_REUSEADDR, &sock_on, sizeof(sock_on)); memset(&sock_addr, 0, sizeof(sock_addr)); sock_addr.sin_family = AF_INET; sock_addr.sin_addr.s_addr = htonl(INADDR_ANY); sock_addr.sin_port = htons(4430); if (bind(net_socket, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) != 0) fatal("server: socket bind failed, %s", strerror(errno)); fd_nonblock(net_socket); srv_proc = xmalloc(sizeof(struct server_proc)); socketpair_prepare(srv_proc->fd); srv_proc->pid = server_init(srv_proc->fd); close(srv_proc->fd[1]); imsgev_init(&srv_proc->iev, srv_proc->fd[0], NULL, imsgev_server, imsgev_server_needfd); pcap = my_pcap_open_live(PCAP_INTERFACE, PCAP_SNAPLEN, 1, PCAP_TO, errbuf, -1, 0); if (pcap == NULL) fatal("capture: pcap_open_live failed on interface %s\n" "with snaplen %d : %s", PCAP_INTERFACE, PCAP_SNAPLEN, errbuf); if (pcap_compile(pcap, &bprog, PCAP_FILTER, 0, 0) < 0) fatal("capture: pcap_compile failed with filter %s : %s", PCAP_FILTER, pcap_geterr(pcap)); if (pcap_setfilter(pcap, &bprog) < 0) fatal("capture: pcap_setfilter failed : %s", pcap_geterr(pcap)); usr_proc = xmalloc(sizeof(struct user_proc)); socketpair_prepare(usr_proc->fd); usr_proc->pid = user_init(usr_proc->fd, pcap); close(usr_proc->fd[1]); imsgev_init(&usr_proc->iev, usr_proc->fd[0], NULL, imsgev_user, imsgev_user_needfd); signal_set(&ev_sigint, SIGINT, sig_handler, NULL); signal_set(&ev_sigterm, SIGTERM, sig_handler, NULL); signal_set(&ev_sigchld, SIGCHLD, sig_handler, NULL); signal_set(&ev_sighup, SIGHUP, sig_handler, NULL); signal_add(&ev_sigint, NULL); signal_add(&ev_sigterm, NULL); signal_add(&ev_sigchld, NULL); signal_add(&ev_sighup, NULL); signal(SIGPIPE, SIG_IGN); log_info("entering event loop"); event_dispatch(); log_info("exiting"); exit(0); } static struct user * finduser(struct sockaddr_in *remote) { struct user *usr; struct sockaddr_in *u; LIST_FOREACH(usr, &usr_list, entry) { u = &usr->addr; if (u->sin_addr.s_addr == remote->sin_addr.s_addr && u->sin_port == remote->sin_port) return usr; } return NULL; } static void srvconn(struct imsgev *iev, struct imsg *imsg) { struct imsg_srvconn *req; struct imsg_srvconn res; struct imsg_usrconn req2; struct user *usr; req = imsg->data; if (req->deco) { log_tmp("srvconn deco"); usr = finduser(&req->addr); if (!usr) fatal("trying to deco an inexistant user !"); addrcpy(&req2.addr, &usr->addr); req2.deco = 1; imsgev_compose(&usr_proc->iev, IMSG_USRCONN_REQ, 0, 0, -1, &req2, sizeof(req2)); LIST_REMOVE(usr, entry); free(usr); return; } if (req->addr.sin_addr.s_addr == htonl(INADDR_LOOPBACK)) { log_tmp("srvconn accepted"); usr = xmalloc(sizeof(struct user)); addrcpy(&usr->addr, &req->addr); usr->status = USER_REQUESTED; LIST_INSERT_HEAD(&usr_list, usr, entry); addrcpy(&req2.addr, &req->addr); req2.deco = 0; imsgev_compose(&usr_proc->iev, IMSG_USRCONN_REQ, 0, 0, -1, &req2, sizeof(req2)); } else { log_tmp("srvconn refused"); res.ok = 0; addrcpy(&res.addr, &req->addr); imsgev_compose(iev, IMSG_SRVCONN_RES, 0, 0, -1, &res, sizeof(res)); } } static void imsgev_server(struct imsgev *iev, int code, struct imsg *imsg) { switch (code) { case IMSGEV_IMSG: log_debug("%s, got imsg %i on fd %i", __func__, imsg->hdr.type, iev->ibuf.fd); switch (imsg->hdr.type) { case IMSG_SRVCONN_REQ: srvconn(iev, imsg); break; default: fatal("%s, unexpected imsg %d", __func__, imsg->hdr.type); } break; case IMSGEV_EREAD: case IMSGEV_EWRITE: case IMSGEV_EIMSG: fatal("imsgev server read/write error"); /* NOTREACHED */ break; case IMSGEV_DONE: event_loopexit(NULL); break; } } static void usrconn(struct imsgev *iev, struct imsg *imsg) { struct user *usr; struct imsg_usrconn *res; struct imsg_srvconn res2; res = imsg->data; usr = finduser(&res->addr); if (!usr || usr->status != USER_REQUESTED) fatal("received usrconn result that wasn't initiated !"); usr->status = USER_ACCEPTED; addrcpy(&res2.addr, &res->addr); res2.ok = 1; imsgev_compose(&srv_proc->iev, IMSG_SRVCONN_RES, 0, 0, -1, &res2, sizeof(res2)); } /* XXX drop imsg when msgbuf->queued is too high ? */ /* XXX review that func for correctness and security */ static void usrdns(struct imsgev *iev, struct imsg *imsg) { struct imsg_usrdns_req *req; struct imsg_usrdns_res res; struct hostent *ent; int len; char *name; extern int h_errno; struct in_addr addr; req = imsg->data; res.addr.s_addr = req->addr.s_addr; addr.s_addr = htonl(req->addr.s_addr); ent = gethostbyaddr(&addr, sizeof(addr), AF_INET); if (ent) { len = strlen(ent->h_name); if (len > DNSNAME_MAX) name = ent->h_name - DNSNAME_MAX; /* keep right part */ else name = ent->h_name; strncpy(res.name, name, sizeof(res.name)); } else { log_debug("usrdns failed for %x : %s", addr, hstrerror(h_errno)); res.name[0] = '\0'; } imsgev_compose(&usr_proc->iev, IMSG_USRDNS_RES, 0, 0, -1, &res, sizeof(res)); } static void imsgev_user(struct imsgev *iev, int code, struct imsg *imsg) { switch (code) { case IMSGEV_IMSG: log_debug("%s, got imsg %i on fd %i", __func__, imsg->hdr.type, iev->ibuf.fd); switch (imsg->hdr.type) { case IMSG_USRCONN_RES: usrconn(iev, imsg); break; case IMSG_USRDNS_REQ: usrdns(iev, imsg); break; default: fatal("%s, unexpected imsg %d", __func__, imsg->hdr.type); } break; case IMSGEV_EREAD: case IMSGEV_EWRITE: case IMSGEV_EIMSG: fatal("imsgev user read/write error"); /* NOTREACHED */ break; case IMSGEV_DONE: event_loopexit(NULL); break; } } static void imsgev_server_needfd(struct imsgev *iev) { fatal("imsgev_server_needfd"); } static void imsgev_user_needfd(struct imsgev *iev) { fatal("imsgev_user_needfd"); }