aboutsummaryrefslogblamecommitdiffstats
path: root/glougloud/glougloud.c
blob: 49a16088a19884e786899df4385f52269033a7f8 (plain) (tree)
1
2
3
4
5
6
7
8
9
10









                       









                      



                            





















                                       
                           
                        
                                                   


                                                                   

                                                      





                                   
                        
           


      



























                                                                    
                        











































































































                                                                             


                                                         






















































                                                                         
                                                                                                
















                                                                                          
                                                                                            




































































































































































































                                                                               










                                        
#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 <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 "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");
}