/*
	This is the finger host server. 

	This server program collects remote host names and
	requested services from the client program. The first
	time that it receives a particular remote host-name
	from the client it spawns a "safe finger" towards
	the remote host the results of which get Mailed to
	root. After a specified length of time, if no
	more events occur, the server mails out a table of the
	accumulated events to root and then, if it is
	not running as a daemon process, terminates.
	
    Copyright (C) 1996 <Ed Alley, Email: wea@llnl.gov>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <syslog.h>

/**********************************************************************

	These defines must be set properly for fhost to work.

**********************************************************************/

#define ADDRESS	"/tmp/fhost_socket" 		/* Address of socket */
#define MAIL_AD "root"     			/* Address to send mail */
#define MHOSTS	100				/* Maximum number of entries */
#define MPATH	"/usr/bin/Mail"			/* Path to Mail */
#define FPATH	"/usr/local/sbin/safe_finger"	/* Path to safe_finger */

static long time_out = 1020L;			/* Fhostd timeout 17 mins */

/*
  Define the event table.
*/

struct event_table {
	char	*name;
	char	*service;
	int	hits;
};

typedef struct event_table table_ev;

int reap_children();
void block_sigchld(void);
void unblock_sigchld(void);
int finger_host(char *);
int daemon_init(void);

main(argc, argv, envp)
int argc;
char **argv;
char **envp;
{
	char c, cc[3], localhost[192], host[192], service[192];
	char message[192];
	int fromlen, len, itab, itabf, is_daemon;
	int i, j, s, ns, nsel, nbr, pipefds[2];
	pid_t pid, pid2;
	struct sockaddr_un saun;
	struct sockaddr_un fsaun;
	struct timeval timeout;
	fd_set readfds;
	table_ev *table[MHOSTS];

	/*
	  Number of arguments is less than four.
	*/

	if (argc > 3) {
		printf("Usage: fhostd @host service\n");
		exit(1);
	}

	/*
	  Check argument lengths (must be less than 64 characters).
	*/

	for (i=0; i<argc; i++) {
	 if (strlen(argv[i]) > 63) {
	  printf("You have exceeded the argument length. ...Exiting\n");
	  syslog(LOG_ERR,"%.32s: Possible buffer overrun attack.\n", argv[i]);
	  exit (1);
	 }
	}

	/*
	  Check to see if we're a daemon.
	*/

	if (argc == 2) {
		cc[0] = argv[1][0];
		cc[1] = argv[1][1];
		cc[2] = '\0';
		if (strcmp(cc, "-d") != 0) {
			printf("Fhostd input error\n");
			exit(1);
		}
		daemon_init();		/* Turn into a daemon */
		reap_children();	/* Handle death of children */
		is_daemon = 1;		/* We're a daemon. */
		itabf = 0;		/* Table raparound flag */
		itab  = 0;		/* Table entry count */
		goto ldae;
	}

	is_daemon = 0;		/* We're not a daemon */

	if (argc != 3) {
		printf("Usage: fhostd @host service\n");
		exit(1);
	}

	/*
	  Check to see if the input argument is a host's name.
	*/

	cc[0] = argv[1][0];
	cc[1] = '\0';

	if (strcmp(cc, "@") != 0) {
		printf("FHOSTD: host-name error.\n");
		exit(1);
	}

	/*
	  Store the remote host-name and the requested
	  service at the beginning of the table.
	*/

	table[0] = (table_ev *) malloc(sizeof(table_ev));

	len = strlen(argv[1]) + 1;
	table[0]->name = (char *) malloc(sizeof(char)*len);
	for (i=0; i<len; i++)
		table[0]->name[i] = argv[1][i];

	len = strlen(argv[2]) + 1;
	table[0]->service = (char *) malloc(sizeof(char)*len);
	for (i=0; i<len; i++)
		table[0]->service[i] = argv[2][i];

	table[0]->hits = 1;

	/*
	  Set the table index to point to the next entry.
	  itabf is a flag to indicate loop around in the table.
	*/

	itabf = 0;
	itab  = 1;

	reap_children();	/* Handle death of children */

	finger_host (argv[1]); /* Finger the remote host. */

	/*
	  Get a socket. This socket will be in the UNIX
	  domain, and will be a stream socket.
	*/

 ldae:
	block_sigchld();

	if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
		perror("socket");
		exit(1);
	}

	/*
	  Create the address we will be binding to by
	  copying into the sockaddr_un structure which is
	  defined in /usr/include/sys/un.h.
	*/

	bzero((char *) &saun, sizeof(saun));
	saun.sun_family = AF_UNIX;
	strcpy(saun.sun_path, ADDRESS);
	len = strlen(saun.sun_path) + sizeof(saun.sun_family);

	/*
	  Bind the address to the socket. We unlink the name
	  first so that the bind won't fail.

	  The third argument indicates the "length" of the
	  structure, not just the length of the socket name.
	*/

	unlink(ADDRESS);

	if (bind(s, (struct sockaddr *) &saun, len) < 0) {
		perror("bind");
		exit(1);
	}

	if (listen(s, 5) < 0) {
		perror("listen");
		exit(1);
	}

	unblock_sigchld();

    for (;;) { /* Forever loop */

	/*
	  Use select() as a fancy timer.

	  Set the select bits to correspond to our socket.

	  We allow select to be interrupted by SIGCHLD. If that
	  happens then we handle the signal and then go to lsel.
	*/

 lsel:

	block_sigchld();

	FD_ZERO (&readfds);
	FD_SET  (s, &readfds);

	/*
	  Set the time out interval.
	*/

	timeout.tv_sec  = time_out;
	timeout.tv_usec = 0L;
	len = (int) getdtablesize();

        unblock_sigchld();

	nsel = select(len, &readfds, (fd_set *) 0, (fd_set *) 0, &timeout);

	block_sigchld();

	if (nsel < 0) {
                if (errno == EINTR) goto lsel;
		perror("select");
		exit(1);
	}

	/*
	  See if a descriptor is ready or we timed out.
	*/

	if (FD_ISSET(s, &readfds) <= 0) {

		/*
		  We timed out. If we're a daemon check to see
		  if we have any entries in the host table.
		  If we do then send the host table to mail
		  and return to select.
		*/

		if (is_daemon != 0 && itab == 0) {
			unblock_sigchld();
			goto lsel;
		}

		/*
		  If we're not a daemon, then close the socket
		  and prepare to exit.
		*/

		if (is_daemon == 0) {
			close(s);
			unlink(ADDRESS);
		}

		/*
		  Create a child to write the host table to mail.
		*/

		if ((pid = fork()) < 0) {
			perror("fork");
			exit(1);
		}

		/*
		  If a daemon, then the parent returns to select.
		  otherwise the parent exits.
		*/

		if (pid != 0) {
			if (is_daemon == 0) exit (0);
			itab  = 0;
			itabf = 0;
			unblock_sigchld();
			goto lsel;
		}

		/*
		  Now create a pipe to Mail, and
		  send it the host table.
		*/


		if (pipe(pipefds) < 0) {
			perror("pipe");
			exit(1);
		}

		/*
		  Fork a grandchild to exec Mail.
		*/

		if ((pid2 = fork()) < 0) {
			perror("fork");
			exit(1);
		}

		/*
		  The grandchild listens on the pipe.
		*/

		if (pid2 == 0) {
			dup2(pipefds[0],0);
			close(pipefds[0]);
			close(pipefds[1]);

			execl(MPATH, "Mail","-n", "-s",
			     "HOST_TABLE", MAIL_AD, (char *) 0);

			perror("execl");
			exit(1);
		}

		/*
		  The child sends the host table down the pipe.
		  Standard out and standard error both write
		  to the pipe.
		*/

		dup2(pipefds[1],1);
		dup2(pipefds[1],2);
		close(pipefds[0]);
		close(pipefds[1]);

		printf("HOST TABLE:\n");

		for (i=0; i<itab; i++)
			printf("%s-%s %i\n",table[i]->service,
			        table[i]->name,table[i]->hits);
		/*
		  Exit so standard out and standard error will flush.
		*/

		exit(0);
	}

	/*
	  Come here if we haven't timed out yet and
	  accept connections. When we accept one, ns will
	  be connected to the client.  fsaun will contain
	  the address of the client.
	*/

	fromlen = sizeof(fsaun);
	if ((ns = accept(s, (struct sockaddr *) &fsaun, &fromlen)) < 0) {
		perror("accept");
		exit(1);
	}

	/*
	  Read the message from the client.
	*/

	if ((nbr = recv(ns, message, 192, 0)) < 0) {
		perror("recv");
		exit(1);
	}

	/*
	  Pad the message with nulls to prevent over-runs.
	*/

        if (nbr > 189) nbr = 189;

	message[nbr++] = '\0';
        message[nbr++] = '\0';
        message[nbr]   = '\0';

	close(ns);

	/*
	  Decompose the message into a host-name and a service.
	*/

	i=0;
	j=0;
	while ((c = message[j++]) != '\0')
		localhost[i++] = c;
	localhost[i] = '\0';

        i=0;
	while ((c = message[j++]) != '\0')
		host[i++] = c;
	host[i] = '\0';

	i=0;
	while ((c = message[j++]) != '\0')
		service[i++] = c;
	service[i] = '\0';

	/*
	  Compare the new host-name with the table.
	  If a match is found then update the table
	  and go back to the select call.
	*/

	j = 0;
	for (i=0; i<itab; i++) {
		if (strcmp(table[i]->name, host) == 0) {
			j = 1;
			if (strcmp(table[i]->service, service) == 0) {
				++table[i]->hits;
				j = 2;
				break;
			}
		}
	}

	/*
	  If j = 0 then we have a new host name so finger
	  the remote host and store the new name in
	  hosttable. If j = 1 then we have a new service
	  associated with the hostname so store into the
	  table.
	*/

	if (j == 0) finger_host(host);


	if (j == 0 || j == 1) {
		if (itabf != 0) {
			free((char *)table[itab]->name);
			free((char *)table[itab]->service);
		}
		else
			table[itab] = (table_ev *) malloc(sizeof(table_ev));

		len = strlen(host) + 1;
		table[itab]->name = (char *) malloc(sizeof(char)*len);
		for (i=0; i<len; i++)
			table[itab]->name[i] = host[i];

		len = strlen(service) + 1;
		table[itab]->service = (char *) malloc(sizeof(char)*len);
		for (i=0; i<len; i++)
			table[itab]->service[i] = service[i];

		table[itab++]->hits = 1;

		if (itab >= MHOSTS) {
			itab  = 0;
			itabf = 1;
		}
	}

	unblock_sigchld();

    } /* End of the forever loop */

}

int daemon_init(void)
/*
 *	Turn into a daemon process.
*/
{
	pid_t pid;

	if ((pid = fork()) < 0) {
		perror("fork");
		return(-1);
	}

	if (pid != 0) exit(0); /* Parent goes bye-bye */
/*
 *	Child continues
 */
	setsid();	/* Become a session leader */

	chdir("/");	/* Change the working directory. */

	umask(0);	/* Clear our file mode creation mask */

	return(0);
}

int finger_host(char *host)
/*
 *	This subroutine fingers the remote host
 *	and sends the results to Mail. It does this
 *	by spawning a child and a grandchild. The
 *	child runs finger and the grandchild runs
 *	Mail. The processes communicate through a
 *	pipe. After spawning its child finger_host
 *	returns.
 */
{
	int pipefds[2];
	pid_t pid, pid2;

/*
 *	Spawn a child to finger the host.
 */
  
	if ((pid = fork()) < 0) {
		perror("fork");
		return(1);
	}

	if (pid != 0) return(0);	/* The parent just returns */
/*
 *	The child is responsible for fingering the host.
 */
	if (pipe(pipefds) < 0) {
	  	perror("pipe");
	  	exit(1);
	}
/*
 *	The child spawns a child to run mail.
 */
	if ((pid2 = fork()) < 0) {
	  	perror("fork");
		exit(1);
	}

	if (pid2 == 0) {
/*
 *	The grandchild execs Mail and listens on the pipe.
 */
	dup2(pipefds[0],0);
	close(pipefds[0]);
	close(pipefds[1]);

	execl(MPATH, "Mail","-n", "-s",
	     "Finger Host", MAIL_AD, (char *) 0);

	perror("execl");
	exit(1);
	}
/*
 *	The child execs safe_finger and writes to the pipe.
 */
	dup2(pipefds[1],1);
	dup2(pipefds[1],2);
	close(pipefds[0]);
	close(pipefds[1]);

	execl(FPATH, "safe_finger", "-l", host, (char *) 0);

	perror("execl finger");
	exit(1);
/*
 *	We should never get here!
 */
	exit(-1);
}

void block_sigchld ()
/*
 *	Block SIGCHLD.
 */
{
  sigset_t set;

  sigemptyset(&set);
  sigaddset(&set, SIGCHLD);
  sigprocmask(SIG_BLOCK, &set,  NULL);

  return;
}

void unblock_sigchld ()
/*
 *	Unblock SIGCHLD.
 */
{
  sigset_t set;

  sigemptyset(&set);
  sigaddset(&set, SIGCHLD);
  sigprocmask(SIG_UNBLOCK, &set,  NULL);

  return;
}

int reap_children()
/*
 *	Set up a handler for SIGCHLD.
 */
{
  sigset_t set;
  struct sigaction sact, osact;
  void *reap_child (int);

  sigemptyset(&set);

  sact.sa_handler = (void *)reap_child;
  sact.sa_mask = set;
  sact.sa_flags = 0;
  sact.sa_flags |= SA_RESTART | SA_NOCLDSTOP;

  sigaction (SIGCHLD, &sact, &osact);

  return (0);

}

void *reap_child (int signo)
/*
 *	Wait on the child.
 */
{
  waitpid (0, NULL, WNOHANG);
  return;
}
