/*
 * (c) 2001 Dirk-Willem van Gulik, All Rights Reserved. See
 * http://www.webweaving.org/LICENSE for the license
 *
 * $Id: widentd.c 1573 2004-01-27 15:33:28Z dirkx $
 *
 */

#define USAGE "widentd [-i ip] [-s port/service] [-v] [-o OS] [-u User]\n"

#include <ctype.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/socket.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <signal.h>
#include <term.h>
#include <sys/select.h>
#include <sys/time.h>
#include <syslog.h>
#include <stdarg.h>
#include <fcntl.h>


#define pdie(x) { perror(x); exit(4); }

#define BLOG (5)		/* Listen queue - should not bee too high */
#define TIMEOUT (10)		/* 10 second timeout - total transaction 
				 * less than 4 x Timeout */
#define UID			"chuck-the-bsd-deamon"
#define OS			"OTHER"
#define REPLY 			"%d , %d : USERID : %s : %s\r\n"

#define SERVICE			("ident")	/* 113 */

int             verbose = 0;
const char     *user = UID;
const char     *os = OS;

static void
usage(void)
{
	fprintf(stderr, USAGE);
	exit(1);
}


int
main(int argc, char **argv)
{
	char            ch;
	const int       yes = 1;
	int             blog = BLOG;
	unsigned int    i;
	char           *myname = argv[0];
	fd_set         *lst = malloc(FD_SETSIZE), *xst = malloc(FD_SETSIZE),
	               *wst = malloc(FD_SETSIZE);
	struct addrinfo *ports, *p;
	const char	* node = NULL, * service = SERVICE;
	struct addrinfo hints;
	int s = -1;
	int e;

	while ((ch = getopt(argc, argv, "vo:u:s:i:")) != -1)
		switch (ch) {
		case 'v':
			verbose = 1;
			break;
		case 'u':
			user = optarg;
			for (i = 0; i < strlen(user); i++)
				if (!(isalnum(user[i]) || (user[i] == '@') || (user[i] == '-') || (user[i] == '.'))) {
					fprintf(stderr, "User name can only contiain a-z, '-', '@' and '.'.\n");
					exit(1);
				}
			break;
		case 'o':
			os = optarg;
			for (i = 0; i < strlen(os); i++)
				if (!isalnum(os[i])) {
					fprintf(stderr, "OS name can only contain a-z.\n");
					exit(1);
				}
			break;
		case 'i':
			node = optarg;
			break;
		case 's':
			service = optarg;
			break;
		case 'h':	/* no break */
		case '?':	/* no break */
		default:
			usage();
			break;
		}

	argc -= optind;
	argv += optind;

	if (argc)
		usage();

	if (verbose)
		printf("Inbound (i.e. listening to)\n\t");
	else
		openlog(myname, LOG_CONS | LOG_NDELAY, LOG_DAEMON);	/* no error returned */

	memset(&hints,0,sizeof(hints));
	hints.ai_family = PF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE;

	if ((e=getaddrinfo(node, service, & hints , &ports))) {
		fprintf(stderr,"interface/service errror: %s\n",gai_strerror(e));
		exit(3);
	};

	for( p = ports; p; p = p->ai_next ) {
		if ((s = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
			pdie("Failed to open socket");

		if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0)
			pdie("Set socket reuse");

		if (bind(s, p->ai_addr, p->ai_addrlen) < 0)
			pdie("Failed to bind in-port");

		if (listen(s, blog) < 0)
			pdie("Listen");
	}
	freeaddrinfo(ports);

	if (s == -1) 
		pdie("No addresses to bind to");

	if (verbose)
		printf("Wident waiting for requests\n");
	else
		daemon(0, 0);

	signal(SIGPIPE, SIG_IGN);

	while (1) {
		unsigned char   buff[1024];
		struct sockaddr_storage con;
		int             len = sizeof(con);
		int             cons = accept(s, (struct sockaddr *)&con, &len);
		struct timeval  to;
		int             noreply = 0;
		enum {
			READ, WRITE
		}               r_or_w = READ;
		unsigned int    inbuff = 0;
		char            ip[128];
		char 		clienthost[ NI_MAXHOST ];
		char		clientserv[ NI_MAXSERV ];

		if (cons == -1) {
			perror("Error on accept");
			continue;
		};

		if (fcntl(cons, O_NONBLOCK, &yes) == -1) {
			perror("Error when making non blocking");
			close(cons);
			continue;
		};

		memset(ip,0,sizeof(ip));
		memset(clienthost,0,sizeof(clienthost));
		memset(clientserv,0,sizeof(clientserv));

		getnameinfo((struct sockaddr *)&con, len, 
				clienthost,sizeof(clienthost), clientserv,sizeof(clientserv), 
				NI_NUMERICHOST | NI_NUMERICSERV);

		snprintf(ip, sizeof(ip) - 1, "%s:%s", clienthost, clientserv);

		if (verbose)
			printf("Incoming commection from %s\n", ip);
		else
			syslog(LOG_INFO, "widentd rq from %s\n", ip);

		to.tv_sec = TIMEOUT;
		to.tv_usec = 0;
		memset(buff, sizeof(buff), 0);
		do {
			int             n;

			FD_ZERO(lst);
			FD_ZERO(xst);
			FD_ZERO(wst);

			if (r_or_w == READ)
				FD_SET(cons, lst);
			else
				FD_SET(cons, wst);

			n = select(cons + 1, lst, wst, xst, &to);
			if (n == 0) {
				if (verbose)
					fprintf(stderr, "Timeout waiting for %s\n", ip);
				else
					syslog(LOG_NOTICE, "widentd timeout for %s\n", ip);
				break;
			} else if ((n == -1) && (errno == EAGAIN))
				continue;
			else if (n == -1) {
				if (verbose)
					fprintf(stderr, "Select error:%s for %s\n",
						strerror(errno), ip);
				else
					syslog(LOG_ERR, "widentd select error:%s for %s\n",
					       strerror(errno), ip);
				break;
			}
			if (FD_ISSET(cons, xst))
				break;

			if (r_or_w == READ)
				n = read(cons, buff + inbuff, sizeof(buff) - inbuff);
			else
				n = write(cons, buff + inbuff, strlen(buff) - inbuff);

			if (n == 0)
				break;	/* normal close */
			else if ((n == -1) && (errno == EAGAIN))
				continue;	/* normal interrupt. */
			else if (n == -1) {
				if (verbose)
					fprintf(stderr, "I/O error:%s\n",
						strerror(errno));
				else
					syslog(LOG_ERR, "widentd i/o error:%s\n",
					       strerror(errno));
				break;
			}
			if (r_or_w == READ) {
				/* We are still in reading mode */
				for (i = inbuff; (r_or_w == READ) && (i < inbuff + n); i++) {
					/*
					 * if we see any CR or LF - consider
					 * the read done
					 */
					if ((buff[i] == '\r') || (buff[i] == '\n')) {
						int             a, b;
						buff[i] = '\0';

						if (sscanf(buff, "%d , %d", &a, &b) != 2) {
							syslog(LOG_NOTICE,"Malformed auth line from %s",ip);
							a = b = 1;
						}

						snprintf(buff, sizeof(buff) - 1, REPLY, a, b, os, user);

						if (verbose)
							printf("Sending %s\n", buff);

						/* no further reads expected. */
						shutdown(cons, SHUT_RD);

						r_or_w = WRITE;
						inbuff = 0;
					}
				}
				/*
				 * If we've not yet seen a CR or LF, keep
				 * listening but keep a counter to guard
				 * against slow feeding DOS style things.
				 */
				if (r_or_w == READ) {
					inbuff += n;
					/*
					 * just bail out if things get too
					 * big
					 */
					if (inbuff >= sizeof(buff)) {
						syslog(LOG_NOTICE,"Extremely long line from %s",ip);
						break;
					};
				};
			} else {
				inbuff += n;
				if (inbuff >= strlen(buff))
					break; /* we're done */
			}

			/*
			 * Try the read/write's about three times; and then
			 * bail out.
			 */
			if ((noreply++) > 3) {
				syslog(LOG_NOTICE,"Extremely Slow feed from %s",ip);
				break;
			}
		} while (1);

		if (verbose)
			printf("And closing\n");

		/* shutdown for read and write */
		shutdown(cons, SHUT_RDWR);
		close(cons);

	} while (1);
	/* NOT REACHED */
}


syntax highlighted by Code2HTML, v. 0.9.1