/*
 * drac.syslogd/main.c
 * 
 * $Id: drac.syslogd.c,v 1.2 2005/01/17 20:41:55 dgc Exp $
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <errno.h>
#include <regex.h>
#include <sys/socket.h>
#include <libdrac.h>

#include <assert.h>
#include "config.h"             /* Autoconf */

#ifdef EMBED_SH
# include EMBED_SH
#endif

#ifdef HAVE_LIBWRAP
# include <tcpd.h>              /* libwrap support */
#endif /* HAVE_LIBWRAP */


/* Safe printing of strings: S(char *string) will always be dereferencable. */
const char *_S = "(null)";
#define S(s)    ((s)?(s):_S)

#ifndef MAX
# define MAX(a, b)	((a) > (b) ? (a) : (b))
#endif /* MAX */

static char cvsid[] = "$Id: drac.syslogd.c,v 1.2 2005/01/17 20:41:55 dgc Exp $";

typedef struct listener {
	struct sockaddr_in	 sin;
	int			 fd;
} listener;

typedef struct rxmatch {
	char		*pat;
	regex_t		*rx;
	regmatch_t	*refs;
	int		 nrefs;
} rxmatch;


char	*A0;
rxmatch	*Rx;
int	 UserRef = 1, HostRef = 2;	/* Index from 1 */

void
usage(void)
{
	fprintf(stderr, "usage: %s [-v] -e regex [-u userref] [-c clientref] [-i host[:port]] [-p host[:port]] [-s drac-server(s)]\n", A0);
}

/* Poor handling of malloc failures. */
inline void *
smalloc(int x)
{
	void *m;
	m = malloc(x);
	assert(m != NULL);
	return m;
}


/* Safe strdup(). */
char *
sstrdup(char *s)
{
	char	*new;
	char	*p;

	p = s;
	while (*p) {
		p++;
	}
	new = smalloc(p-s+1);

	p = new;
	while (*s)
		*p++ = *s++;
	*p = '\0';
	return new;
}


/* Copy an inet sockaddr. */
struct sockaddr_in *
dupsin(struct sockaddr_in *sin)
{
	struct sockaddr_in	*new;

	new = smalloc(sizeof(struct sockaddr_in));
	memcpy(new, sin, sizeof(struct sockaddr_in));
	return new;
}


/* Parse a hostname spec. */
char *
if_parse(struct sockaddr_in *sin, char *s)
{
	char		*p, *host, *err;
	struct hostent	*he;
	struct servent	*se;
	struct protoent	*pe;
	char		*proto = "udp";
	int		 protnum = IPPROTO_UDP;
	unsigned short	 port;

	host = sstrdup(s);
	if (p = strchr(host, ':'))
		*p++ = '\0';
	else
		p = "syslog";

	if (s == NULL || !strcmp(s, "*")) {
		sin->sin_addr.s_addr = INADDR_ANY;
	}
	else if (he = gethostbyname(host)) {
		memcpy(&sin->sin_addr.s_addr, he->h_addr,
			sizeof(sin->sin_addr.s_addr));
		sin->sin_family = he->h_addrtype;
	}
	else {
		sin->sin_addr.s_addr = inet_addr(host);
		if (sin->sin_addr.s_addr == 0xffffffff) {
			free(host);
			return err = "no such host";
		}
		sin->sin_family = AF_INET;
	}

	if (pe = getprotobyname(proto)) {
		proto = pe->p_name;
		protnum = pe->p_proto;
	}

	if (p && *p) {
		if (isdigit(*p)) {
			port = htons(atoi(p));
		}
		else {
			se = getservbyname(p, proto);
			if (se == NULL) {
				free(host);
				return err = "no such service";
			}
			port = se->s_port;
		}
	}
	else {
		port = 514;
	}

	sin->sin_port = port;
	free(host);
	return err = NULL;
}


void
takemsg(dracctx_t *drac, listener *l, listener **pass)
{
	char			 buf[512];
	struct sockaddr_in	 sin;
	int			 sinlen = sizeof(struct sockaddr_in);
	int			 n, fd;
	char			 user[64], clnt[256], *err;

	n = recvfrom(l->fd, buf, sizeof(buf), 0,
		(struct sockaddr *)&sin, &sinlen);
	buf[n] = '\0';

#if DEBUG
	{
		char *to, *from;
		to = sstrdup((char *)inet_ntoa(l->sin.sin_addr));
		from = sstrdup((char *)inet_ntoa(sin.sin_addr));
		printf("To %-15.15s From %-15.15s: [%d] %s\n",
			to, from, n, buf);
		free(to);
		free(from);
	}
#endif /* DEBUG */

	/* Log to drac? */
	if (regexec(Rx->rx, buf, Rx->nrefs, Rx->refs, 0) == 0) {
		n = Rx->refs[UserRef].rm_eo - Rx->refs[UserRef].rm_so;
		strncpy(user, &buf[Rx->refs[UserRef].rm_so], n);
		user[n] = '\0';

		n = Rx->refs[HostRef].rm_eo - Rx->refs[HostRef].rm_so;
		strncpy(clnt, &buf[Rx->refs[HostRef].rm_so], n);
		clnt[n] = '\0';

		err = if_parse(&sin, clnt);
#ifdef DEBUG
		fprintf(stderr, "auth: user=%s, host=%s\n", user, clnt);
#endif
		if (err == NULL) {
			drac_client_sin(drac, &sin);
			drac_reauth(drac);
		}
	}

	while (pass && *pass) {
		if ((*pass)->fd < 0) {
			(*pass)->fd = socket(PF_INET, SOCK_DGRAM, 0);
		}
		if ((*pass)->fd < 0) {
			continue;
		}
		sendto((*pass)->fd, buf, n, 0,
			(struct sockaddr *)(&(*pass)->sin),
			sizeof(struct sockaddr_in));
		pass++;
	}
}


/* Given a list of sins, listen and wait for syslog messages. */
int
syslogd(dracctx_t *drac, listener **socks, listener **pass)
{
	fd_set			 fds_waiting, fds_ready;
	int			 i, n, nfds;


	FD_ZERO(&fds_waiting);
	nfds = 0;
	for (i = 0; socks[i]; i++) {
		socks[i]->fd = socket(PF_INET, SOCK_DGRAM, 0);
		if (socks[i]->fd < 0) {
			fprintf(stderr,
			    "%s: cannot create listener socket for %s: %s\n",
			    A0, inet_ntoa(socks[i]->sin.sin_addr),
			    strerror(errno));
			return 1;
		}
		if (bind(socks[i]->fd, (struct sockaddr *)&socks[i]->sin,
					sizeof(struct sockaddr_in)) != 0) {
			fprintf(stderr,
				"%s: cannot bind listener for %s: %s\n",
				A0, inet_ntoa(socks[i]->sin.sin_addr),
				strerror(errno));
			return 2;
		}
		FD_SET(socks[i]->fd, &fds_waiting);
		nfds = MAX(nfds, socks[i]->fd);
	}

	/* Once bound, we no longer need privileges. */
	setreuid(-1, -1);

	nfds++;

	while (1) {
		memcpy(&fds_ready, &fds_waiting, sizeof(fd_set));
		n = select(nfds, &fds_ready, NULL, NULL, 0);
		if (n == 0)
			break;
		for (i = 0; socks[i]; i++) {
			if (FD_ISSET(socks[i]->fd, &fds_ready)) {
				takemsg(drac, socks[i], pass);
				FD_CLR(socks[i]->fd, &fds_ready);
			}
		}
	}
	return 0;
}


main(int argc, char *argv[])
{
	int			 c, i, errs, rc;
	listener		**socks = NULL;
	listener		**passthru = NULL;
	int			 nsocks = 0, npass = 0;
	struct sockaddr_in	 sin;
	char			*msg;
	dracctx_t		*drac;
	char			*p, *hlist = NULL;

	if ((A0 = strrchr(argv[0], '/')) == NULL)
		A0 = argv[0];
	else
		++A0;

	opterr = 0;
	errs = 0;

	drac = drac_init(NULL);
	if (drac == NULL) {
		fprintf(stderr, "%s: cannot init DRAC\n", A0);
		exit(10);
	}
	drac_setlog(drac, drac_log_stderr);
	drac_settimeout(drac, 0);
	hlist = getenv("DRACHOST");
	if (hlist == NULL)
		hlist = getenv("DRAC_HOST");
	if (hlist == NULL)
		hlist = getenv("DRACHOSTS");
	if (hlist == NULL)
		hlist = getenv("DRAC_HOSTS");

	Rx = smalloc(sizeof(rxmatch));
	Rx->pat = sstrdup("^none");

	while ((c = getopt(argc, argv, "hvi:p:s:e:u:c:")) != EOF) {
		switch (c) {
		case 'h':
			usage();
			exit(0);
			break;

		case 'v':
			printf("%s %s\n", A0, S((char *)release()));
			puts(cvsid);
			break;

		case 'i':
			if ((msg = if_parse(&sin, optarg)) == NULL) {
				socks = realloc(socks,
					sizeof(listener) * (nsocks + 2));
				socks[nsocks] = smalloc(sizeof(listener));
				memcpy(&socks[nsocks]->sin, &sin,
					sizeof(struct sockaddr_in));
				socks[nsocks]->fd = -1;
				socks[++nsocks] = NULL;
			}
			else {
				fprintf(stderr,
					"%s: cannot listen on \"%s\": %s\n",
					A0, optarg, msg);
				++errs;
			}
			break;

		case 'p':
			if ((msg = if_parse(&sin, optarg)) == NULL) {
				passthru = realloc(passthru,
					sizeof(listener) * (npass + 2));
				passthru[npass] = smalloc(sizeof(listener));
				memcpy(&passthru[npass]->sin, &sin,
					sizeof(struct sockaddr_in));
				passthru[npass]->fd = -1;
				passthru[++npass] = NULL;
			}
			else {
				fprintf(stderr,
					"%s: cannot pass to \"%s\": %s\n",
					A0, optarg, msg);
				++errs;
			}
			break;

		case 's':
			hlist = optarg;
			break;

		case 'e':
			free(Rx->pat);
			Rx->pat = sstrdup(optarg);
			break;

		case 'u':
			UserRef = atoi(optarg);
			break;

		case 'c':
			/* Indexed from 0, but let the user think otherwise. */
			HostRef = atoi(optarg);
			break;

		default:
			fprintf(stderr, "%s: unknown option -%c\n",
				A0, optopt);
			++errs;
			break;
		}
	}


	/* Prohibit arguments. */
	if (optind > argc)
		++errs;

	if (errs) {
		usage();
		fprintf(stderr, "%s: %d error%s.\n",
			A0, errs, (errs == 1 ? "" : "s"));
		exit(2);
	}

	/* If no interfaces specified, do INADDR_ANY. */
	if (nsocks == 0) {
		nsocks = 1;
		socks = smalloc(sizeof(listener) * 2);
		socks[0] = smalloc(sizeof(struct sockaddr_in));
		if_parse(&socks[0]->sin, "*");
		socks[0]->fd = -1;
		socks[1] = NULL;
	}

#if DEBUG
	for (i = 0; i < nsocks; i++) {
		printf("Enabling socks[%02d] %08x = %s\n", i,
			socks[i]->sin.sin_addr.s_addr,
			inet_ntoa(socks[i]->sin.sin_addr)
			);
	}
#endif

	/* Prepare matching regex. */
	Rx->rx = smalloc(sizeof(regex_t));
	rc = regcomp(Rx->rx, Rx->pat, REG_EXTENDED|REG_ICASE);
	if (rc != 0) {
		char	 regerr[1024];
		regerror(rc, Rx->rx, regerr, sizeof(regerr));
		fprintf(stderr, "%s: cannot compile matching re \"%s\": %s\n",
			A0, Rx->pat, regerr);
		exit(20);
	}
	Rx->nrefs = 0;
	for (p = Rx->pat; p && *p; p++) {
		if (*p == '(')
			Rx->nrefs++;
	}
	if (Rx->nrefs < UserRef || Rx->nrefs < HostRef) {
		fprintf(stderr, "%s: not enough backrefs for user and host (use -u0 if user is ignored)\n",
			A0);
		exit(22);
	}
	Rx->nrefs++;	 /* offset from 1, not 0 */
	Rx->refs = malloc(sizeof(regmatch_t) * (Rx->nrefs));

	if (hlist == NULL) {
		fprintf(stderr, "%s: no DRAC servers listed\n", A0);
		exit(12);
	}
	drac_server(drac, hlist);

	syslogd(drac, socks, passthru);

	exit(0);
}

