/*
 * Copyright (c) 1990 Regents of the University of Michigan.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that this notice is preserved and that due credit is given
 * to the University of Michigan at Ann Arbor. The name of the University
 * may not be used to endorse or promote products derived from this
 * software without specific prior written permission. This software
 * is provided ``as is'' without express or implied warranty.
 */

#include "dixie.h"
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/syslog.h>
#include <sys/wait.h>
#include <signal.h>

#ifndef FD_SET
#define NFDBITS         32
#define FD_SETSIZE      32
#define FD_SET(n, p)    ((p)->fds_bits[(n)/NFDBITS] |= (1 << ((n) % NFDBITS)))
#define FD_CLR(n, p)    ((p)->fds_bits[(n)/NFDBITS] &= ~(1 << ((n) % NFDBITS)))
#define FD_ISSET(n, p)  ((p)->fds_bits[(n)/NFDBITS] & (1 << ((n) % NFDBITS)))
#define FD_ZERO(p)      bzero((char *)(p), sizeof(*(p)))
#endif

/*#define DAPUSER			"c=US@o=University of Minnesota@ou=Miscellaneous Servers@cn=Go500"*/
#define DAPUSER NULL
#define DEFAULT_BASE		"c=US@o=University of Minnesota"
#define DEFAULT_DIXIEHOST	"x500.tc.umn.edu"
#define DEFAULT_GO500PORT	5555

int	debug;
int	dosyslog;

char	*dixiehost = DEFAULT_DIXIEHOST;
char	*base = DEFAULT_BASE;
int	strip = 3;
int	searchaliases = 1;

char	*myhost = "mudhoney.micro.umn.edu";
int	myport;

static usage( name )
char	*name;
{
	fprintf( stderr, "usage: %s [-d debuglevel] [-p port] [-l] [-x dixiehost] [-b searchbase] [-a]\n" );
	exit( 1 );
}

main (argc, argv)
int	argc;
char	**argv;
{
	int			s, ns, rc;
	int			port = -1;
	int			tblsize;
	int			i, pid;
	char			*myname;
	fd_set			readfds;
	struct hostent		*hp;
	struct sockaddr_in	from;
	int			fromlen;
	int			wait4child();
	extern char		*optarg;

	while ( (i = getopt( argc, argv, "ab:d:lp:s:x:" )) != EOF ) {
		switch( i ) {
		case 'a':
			searchaliases = 0;
			break;

		case 'b':
			base = strdup( optarg );
			break;

		case 'd':
			debug = atoi( optarg );
			break;

		case 'l':
			dosyslog = 1;
			break;

		case 'p':
			port = atoi( optarg );
			break;

		case 's':
			strip = atoi( optarg );
			break;

		case 'x':
			dixiehost = strdup( optarg );
			break;

		default:
			usage( argv[0] );
		}
	}

	/* detach if stderr is redirected or no debugging */
	(void) detach( debug );

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

	if ( dosyslog && openlog( myname, LOG_PID | LOG_NOWAIT, LOG_LOCAL3 )
	    < 0 ) {
		perror("openlog");
	}
	if ( dosyslog ) syslog( LOG_INFO, "initializing" );

	/* set up the socket to listen on */
	s = set_socket( port );

	/* arrange to reap children */
	(void) signal( SIGCHLD, wait4child );

	tblsize = getdtablesize();
	for ( ;; ) {
		FD_ZERO( &readfds );
		FD_SET( s, &readfds );

		if ( (rc = select( tblsize, &readfds, 0, 0 ,0 )) == -1 ) {
			if ( debug ) perror( "select" );
			continue;
		} else if ( rc == 0 ) {
			continue;
		}

		if ( ! FD_ISSET( s, &readfds ) )
			continue;

		fromlen = sizeof(from);
		if ( (ns = accept( s, &from, &fromlen )) == -1 ) {
			if ( debug ) perror( "accept" );
			exit( 1 );
		}

		hp = gethostbyaddr( (char *) &(from.sin_addr.s_addr),
		    sizeof(from.sin_addr.s_addr), AF_INET );
		syslog( LOG_INFO, "TCP connection from %s (%s)",
		    (hp == NULL) ? "unknown" : hp->h_name,
		    inet_ntoa( from.sin_addr ) );

		switch( pid = fork() ) {
		case 0:		/* child */
			close( s );
			do_queries( ns );
			break;

		case -1:	/* failed */
			perror( "fork" );
			break;

		default:	/* parent */
			close( ns );
			if ( debug )
				fprintf( stderr, "forked child %d\n", pid );
			break;
		}
	}
	/* NOT REACHED */
}

static set_socket( port )
int	port;
{
	int			s;
	struct sockaddr_in	addr;

	if ( port == -1 )
		port = DEFAULT_GO500PORT;
	myport = port;

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

        /* set option so clients can't keep us from coming back up */
        if ( setsockopt( s, SOL_SOCKET, SO_REUSEADDR, 0, 0 ) < 0 ) {
                perror( "setsockopt" );
                exit( 1 );
        }

        /* bind to a name */
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = INADDR_ANY;
        addr.sin_port = htons( port );
        if ( bind( s, &addr, sizeof(addr) ) ) {
                perror( "bind" );
                exit( 1 );
        }

	/* listen for connections */
        if ( listen( s, 5 ) == -1 ) {
                perror( "listen" );
                exit( 1 );
        }

        if ( debug ) printf("tcp socket allocated, bound, and listening\n");

	return( s );
}

static wait4child()
{
#if defined(SunOS) && SunOS < 40
        union wait status;
#else
        int     status;
#endif

        if ( debug ) printf( "parent: catching child status\n" );
        while ( wait3( &status, WNOHANG | WUNTRACED, 0 ) > 0 )
                ;       /* NULL */
}

static char	dixie_buffer[MAX_PKT_SIZE];

static do_queries( s )
int	s;
{
	char		buf[256];
	int		len;
	FILE		*fp;
	int		rc, tblsize;
	struct timeval	timeout;
	fd_set		readfds;

	if ( dxi_init( dixiehost, 0, 0 ) == -1 ) {
		if ( debug ) perror( "dxi_init" );
		exit( 1 );
	}
	if ( !searchaliases )
		dxi_options |= DXI_OPT_DONTSEARCHALIASES;

	if ( dxi_bind( DAPUSER, NULL ) == -1 ) {
		if ( debug ) dxi_perror( "dxi_bind" );
		exit( 1 );
	}
	dxi_not61 = 1;

	if ( (fp = fdopen( s, "a+")) == NULL ) {
		perror( "fdopen" );
		exit( 1 );
	}

	tblsize = getdtablesize();
	timeout.tv_sec = 300;
	timeout.tv_usec = 0;
	FD_ZERO( &readfds );
	FD_SET( fileno( fp ), &readfds );

	if ( (rc = select( tblsize, &readfds, 0, 0, &timeout )) <= 0 )
		exit( 1 );

	if ( fgets( buf, sizeof(buf), fp ) == NULL )
		exit( 1 );

	len = strlen( buf );
	if ( debug ) {
		fprintf( stderr, "got %d bytes\n", len );
		dxi_bprint( buf, len );
	}

	/* strip of \r \n */
	if ( buf[len - 1] == '\n' )
		buf[len - 1] = '\0';
	len--;
	if ( buf[len - 1] == '\r' )
		buf[len - 1] = '\0';
	len--;

	rewind(fp);
	if ( strchr( buf, '=' ) )
		do_read( fp, buf );
	else
		do_search( fp, buf );
	fprintf( fp, ".\r\n" );
	rewind(fp);

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

static do_search( fp, buf )
FILE	*fp;
char	*buf;
{
	char	filter[1024];
	char	*dn, *e, *rdn;
	char	**title;
	static char	*attrs[] = { "title", 0 };

	sprintf( filter, "uid=%s|sn=%s|cn=%s", buf, buf, buf );

	if ( dxi_search( base, DXI_SRCH_SUBTREE, filter, attrs, dixie_buffer )
	    == -1 ) {
		dxi_perror( "dxi_search" );
		exit( 1 );
	}

	for ( e = dxi_firstentry( dixie_buffer ); e != NULL;
	    e = dxi_nextentry() ) {
		dn = dxi_getdn( e );
		rdn = dxi_lastdncomp( dn );
		title = dxi_getvalues( e, "title" );

		fprintf( fp, "0%-20s    %s\t%s\t%s\t%d\r\n", rdn,
		    title ? title[0] : "", dn, myhost, myport );

		dxi_valuefree( title );
		free( dn );
	}
}

static print_attr( fp, label, attr, e, multiline )
FILE	*fp;
char	*label;
char	*attr;
char	*e;
int	multiline;
{
	char	**val;
	int	i, gotone = 0, firstline = 1;

	if ( (val = dxi_getvalues( e, attr )) == NULL )
		return;

	fprintf( fp, "%-19s\r\n", label );
	for ( i = 0; val[i] != NULL; i++ ) {
		if ( multiline ) {
			char	*s, *p;

			if ( gotone )
				fprintf( fp, "%-19s\r\n", label );
			p = s = val[i];
			while ( s = strchr( s, '$' ) ) {
				*s++ = '\0';
				while ( isspace( *s ) )
					s++;
				if ( firstline ) {
					fprintf( fp, "                    %s\r\n", p );
					firstline = 0;
				} else {
					fprintf( fp, "                    %s\r\n", p );
				}
				p = s;
			}
			if ( firstline ) {
				fprintf( fp, "                    %s\r\n", p );
			} else {
				fprintf( fp, "                    %s\r\n", p );
			}
			gotone = 1;
			firstline = 1;
		} else {
			if ( firstline ) {
				fprintf( fp, "                    %s\r\n", val[i] );
				firstline = 0;
			} else {
				fprintf( fp, "                    %s\r\n", val[i] );
			}
		}
	}
	dxi_valuefree( val );
}

static do_read( fp, buf )
FILE	*fp;
char	*buf;
{
	char		*dn, *e, *ufn;
	int		i;
	char		*s;
	char		**cn, **paddr, **tnumber, **uid, **title, **mail;
	static char	*attrs[] = { "cn", "postalAddress", "uid", "title",
				     "mail", "telephoneNumber",
				     "facsimileTelephoneNumber", 0 };

	if ( dxi_read( buf, attrs, dixie_buffer ) == -1 ) {
		if ( debug ) dxi_perror( "dxi_read" );
		exit( 1 );
	}

	e = dxi_getreadentry( dixie_buffer );
	dn = dxi_getdn( e );
	ufn = dxi_dn2ufn( dn );
	for ( i = 0; i < strip; i++ ) {
		if ( (s = strrchr( ufn, ',' )) == NULL )
			break;
		*s = '\0';
	}
	fprintf( fp, "\"%s\"\r\n", ufn );
	print_attr( fp, "  Also known as:", "cn", e, 0 );
	print_attr( fp, "  E-mail address:", "mail", e, 0 );
	print_attr( fp, "  Fax number:", "facsimileTelephoneNumber", e, 0 );
	print_attr( fp, "  Business phone:", "telephoneNumber", e, 0 );
	print_attr( fp, "  Business address:", "postalAddress", e, 1 );
	print_attr( fp, "  Title:", "title", e, 0 );
	print_attr( fp, "  Uniqname:", "uid", e, 0 );

	free( dn );
}
