/*-
 * Copyright (c) 1998 Brian Fundakowski Feldman
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

static const char copyright[] = "@(#) Copyright 1998 Brian Fundakowski Feldman.\
	All rights reserved.";

/* This is identdtestd version 1.1; this is the second public release of
 * my useful utility. Basically, the idea was: I saw this type of utility
 * on a server before, so I thought "Heck, why not release my own version
 * of this nice little thing?"; so I wrote this, and here it is! The usage
 * is:
 *	identdtestd [ -c children ] [ -d (debug, use multiple times for
 *	more debugging, currently ignored for (d > 1)) ] [ -p port ]
 *
 * To compile:
 * 	cc -O identdtestd.c -o identdtestd (with -lsocket and possibly -lnsl
 *	on some SVR4* if given errors about undefined symbols like "socket")
 */

/* What's new in this release (1.1)?
 *	improved response parsing routine
 *	look, we have comments!!!
 *	other random things
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/uio.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>

void cleanup(int);

/* PARSE:
 *	Go through a string, skipping white space. Then, skip all alphanumeric
 *	characters, and finally, separate the string by giving it a '\0'.
 */
#define PARSE(x)	do {						\
				while (isspace(*(x))) (x)++;		\
				for (str = x; isalnum(*str); str++);	\
				*str = '\0';				\
			} while (0)

int fd;					/* This is the socket we listen on! */

/* Hey, lookey, almost everything is contained in main()! Why not? Heck, the
 * children don't do anything special enough to warrant a whole _function_ call!
 * See any reason for them to?
 */
 
int
main(int argc, char *argv[]) {
	struct sockaddr_in so;
	extern int fd;
	int ch, chl = 2, arg;
	char cha, debug = 0;
	
	signal(SIGINT, cleanup);	/* Aren't signal handlers beautiful? */
	signal(SIGTERM, cleanup);
	so.sin_family = AF_INET;
	so.sin_port = htons(11113);
	so.sin_addr.s_addr = INADDR_ANY;
	if ((fd = socket(PF_INET, SOCK_STREAM, 0)) == -1)/* Grab a socket we to
		exit(1);				  * which we'll bind  */
	/* Let's do those funky command line arguments! */
	while ((cha = getopt(argc, argv, "c:dp:")) != -1)
		switch (cha) {
		case 'p':
		    if ((arg = atoi(optarg)) > 0xffff || arg < 1024) {
			fprintf(stderr, "argument to -p must be a valid"
				" port number!\n");
			exit(EX_USAGE);
		    }
		    so.sin_port = htons(arg);
		    break;
		case 'c':
		    if ((arg = atoi(optarg)) > 0xff || arg < 0) {
			fprintf(stderr, "argument to -c must be a valid"
				" quantity less than 256\n");
			exit(EX_USAGE);
		    }
		    chl = arg;
		    break;
		case 'd':
		    debug++;
		    break;
		}
	argc -= optind;
	argv += optind;
	/* Now we'll take our nice TCP socket and grab a port to listen on. */
	if (bind(fd, (struct sockaddr *)&so, sizeof(so)) == -1) {
		perror("bind");
		close(fd);
		exit(2);
	}
	if (!debug)				/* if not debugging...	   */
		switch (fork()) {		/* go into the background! */
		case 0:
			setsid();
			break;
		case -1:
			perror("fork");
			close(fd);
			exit(1);
		default:
			printf("Brian Feldman's identd tester v1.1 "
				"backgrounding...\n");
			exit(0);
		}
	listen(fd, 1);	
	for (ch = 0; ch < chl; ch++) /* fork off some children, too */
		switch (fork()) {
		case -1:
			close(fd);
			exit(-1);
		case 0:
			goto mainchild;		
		default:
			break;
		}
mainchild:
	for (;;) {
		struct sockaddr_in ck; /* we need something to connect to! */
		fd_set fdset;
		char *str,			/* standard temporary string */
		     *os,			/* operating system	     */
		     *user;			/* user name		     */
		FILE *fptr,			/* identd socket connection  */
						/* file pointer		     */
		     *lfptr;			
		char buf[100];
		int lu, lfd,  len = sizeof(so);
		u_short port;			/* We will reuse the sockaddr_in
						 * so we need to save that port.
						 */

	begin:
		FD_ZERO(&fdset);
		FD_SET(fd, &fdset);
		select(fd + 1, &fdset, NULL, NULL, NULL); /* wait for a conn */
		lfd = accept(fd, (struct sockaddr *)&ck, &len); /* grab one! */
		lfptr = fdopen(lfd, "w");
		fprintf(lfptr, "Welcome to Brian Feldman's identd tester v1.1!"
			"\nPlease wait, your host %s is being contacted for "
			"identification...\n", inet_ntoa(ck.sin_addr));
		fflush(lfptr);
		lu = socket(PF_INET, SOCK_STREAM, 0);
		if (lu == -1) {
			fprintf(lfptr, "A socket could not be allocated :(.\n"
				"Error: %s\n", strerror(errno));
			fflush(lfptr);
			fclose(lfptr);
			close(lfd);
			goto begin;
		}
		FD_ZERO(&fdset);
		FD_SET(lu, &fdset);
		port = ck.sin_port;
		ck.sin_port = htons(113);
		bzero(buf, 100);
		if (connect(lu, (struct sockaddr *)&ck, len) == -1) {
			close(lu);
			fprintf(lfptr, "Your identd port (113) could not be "
				"contacted.\nError: %s\n", strerror(errno));
			fflush(lfptr);
			fclose(lfptr);
			close(lfd);
			goto begin;
		}
		select(lu + 1, 0, &fdset, 0, NULL);
		fptr = fdopen(lu, "r+");
		/* In ident, we send first their port, then our port */
		fprintf(fptr, "%d, %d\n", ntohs(port), ntohs(so.sin_port));
		fflush(fptr);
		fgets(buf, 100, fptr);
		fclose(fptr);
		close(lu);
		/* Now here's the fun part, to examine the respone! An identd
		 * respone (a valid one), goes along the lines of:
		 * my_port , foreign_port : USERID : OS :user
		 */
		if ((str = strchr(buf, ':') + 1) == (char *)1 || (str +=
		    (isspace(*str) ? 1 : 0), strncmp(str, "USERID", 6)) ||
		    (os = strchr(str, ':') + 1) == (char *)1 ||
		    (user = strchr(os, ':') + 1) == (char *)1) {
			os = user = "invalid";
			goto info;
		}
		PARSE(os);
		PARSE(user);
	info:
		fprintf(lfptr, "You're user \"%s\", from port %d on a %s host "
				"computer.\n", user, ntohs(port), os);
		fflush(lfptr);
		fclose(lfptr);
		close(lfd);
	}
	/* NOTREACHED */
	exit(3);
}

void
cleanup(int sig) {
	extern int fd;

	close(fd);
	exit(0);
}

