diff options
Diffstat (limited to 'glougloud/glougloud.c')
-rw-r--r-- | glougloud/glougloud.c | 464 |
1 files changed, 464 insertions, 0 deletions
diff --git a/glougloud/glougloud.c b/glougloud/glougloud.c new file mode 100644 index 0000000..7b86aa4 --- /dev/null +++ b/glougloud/glougloud.c @@ -0,0 +1,464 @@ +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <sys/ioctl.h> + +#include <net/if.h> +#include <netinet/in.h> + +#include <netdb.h> +#include <pcap.h> +#include <pcap-int.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <err.h> +#include <errno.h> +#include <unistd.h> +#include <signal.h> +#include <string.h> + +#include "glougloud.h" +#include "imsgev.h" + +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 "lo0" +#define PCAP_SNAPLEN 100 +#define PCAP_FILTER "not port 4430" + +static void imsgev_server(struct imsgev *, int, struct imsg *); +static void imsgev_user(struct imsgev *, int, struct imsg *); + +struct server_proc *srv_proc; +struct user_proc *usr_proc; +LIST_HEAD(, user) usr_list; +int net_socket; + +void __dead +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) +{ + 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; +} + +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); + + 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); + + 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; + } +} + |