/** $Header: /usr/src/nidentd-2.0/RCS/identd.c,v 2.0 92/05/04 17:42:54 nigelm Exp $
*** 
*** identd.c	- An implementation of an RFC931 Authentication Daemon
*** ~~~~~~~~	  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
***
***
*** Author: 	Nigel Metheringham
***		Department of Electronics, University of York, UK
***		nigelm@ohm.york.ac.uk
***
*** Very heavily based on:-
***		pauthd version 1.2
***		by  Peter Eriksson (pen@lysator.liu.se) 30 Oct 1991
***	and
***		authd version 3.01
***		by Dan Bernstein (brnstnd@nyu.edu) 7 Feb 1991
***
*** $Log:	identd.c,v $
***		Revision 2.0  92/05/04  17:42:54  nigelm
***		Version for release at base 2.0 version.
***		
*** 
***
**/


/**
***
*** Include files - it needs tons of these, and every system has
*** a slightly different set!!!!!
***
**/
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <sys/types.h>
#ifdef SYS5_INCLUDES
#include <bsd/sys/types.h>
#endif
#include <sys/param.h>
#include <netdb.h>
#include <nlist.h>
#include <pwd.h>
#include <signal.h>

#include "paths.h"

#ifdef SYS5_INCLUDES
#include <bsd/sys/socket.h>
#include <bsd/sys/socketvar.h>
#else
#include <sys/socket.h>
#include <sys/socketvar.h>
#endif

#include <sys/ioctl.h>
#define KERNEL
#define KERNEL_FEATURES 
#include <sys/file.h>
#undef KERNEL
#undef KERNEL_FEATURES 

#ifdef SYS5_INCLUDES  
#include <sys/pcb.h>
#endif

#include <sys/user.h>

#ifdef SYS5_INCLUDES  
#include <bsd/net/if.h>
#include <bsd/net/route.h>
#include <bsd/netinet/in.h>
#include <bsd/netinet/in_pcb.h>
#include <bsd/netinet/ip_var.h>
#include <bsd/netinet/tcp.h>
#include <bsd/netinet/tcp_timer.h>
#include <bsd/netinet/tcp_var.h>
#else
#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/in_pcb.h>
#include <netinet/ip_var.h>
#include <netinet/tcp.h>
#include <netinet/tcp_timer.h>
#include <netinet/tcp_var.h>
#endif

#ifdef SYS5_KERNEL
/* This declares the kernel structures and is needed even
   if you are compiling for a BSD universe on a mainly sys5
   machine */
#include <sysv/sys/var.h>
#endif


/**
***
*** Global variables and things that go bump in the night
***
**/

int flagidentd = 0;		/* Are we running as identd */
int flagpwnam = 0;		/* Do we want the user name returned */
int debug_flag = 0;		/* Squash that bug.... */
char * progname = "NOT-SET";    /* The name of this here program */
int local_port = 0;		/* The local port for identd checking */
int remote_port = 0;		/* The remote port for identd checking */

extern char optarg;		/* For getopt */
extern int optind;		/* For getopt */
extern int opterr;		/* For getopt */

int kmemfd;			/* File handle for kmem */


/**
***
*** Name list of things to fish out the kernel...
*** Of course this is different for every machine!
*** NB some compilers may gripe at the initialisers!
**/
struct nlist nl[] =
{
#ifdef SYS5_KERNEL
   { "file" },
   { "v" },
   { "tcb" },
   { "" }
#else
#ifdef NeXT
   { {"_file_list"} },
   { {"_max_file"} },
   { {"_tcb"} },
   { {""} }
#else
   { "_file" },
   { "_nfile" },
   { "_tcb" },
   { "" }
#endif
#endif
#define N_FILE 0		/* These are common for all architectures */
#define N_NFILE 1		/* at present, but may need moving into the */
#define N_TCB 2			/* machine dependant bits for wierder machines */
};




/**
***
*** Usage instructions
*** 
**/
usage()
{
   fprintf(stderr, "Usage:\n");
   fprintf(stderr, "\tidentd    [-d]\n");
   fprintf(stderr, "\ttcpuname [-d] remote-addr remote-port local-addr local-port\n");
   fprintf(stderr, "\ttcpuid   [-d] remote-addr remote-port local-addr local-port\n");
   fprintf(stderr, "\n");
   exit(1);
}


/**
***
*** Version
*** 
**/
version()
{
   fprintf(stderr, "%s\n\t$Revision: 2.0 $\n\t$Date\n");
   fprintf(stderr, "\tby\tNigel Metheringham, University of York, UK (nigelm@ohm.york.ac.uk)\n");
   fprintf(stderr, "\t\tPeter Eriksson (pen@lysator.liu.se)\n");
   fprintf(stderr, "\t\tDan Bernstein (brnstnd@nyu.edu)\n");
   exit(1);
}


/**
***
*** Last despairing message and suicide....!
*** 
**/
Error_Die(char * where,
	  char * message,
	  unsigned long x1,
	  unsigned long x2,
	  unsigned long x3,
	  unsigned long x4)
{
   if (flagidentd && !debug_flag) {
      printf("%d, %d: ERROR: UNKNOWN-ERROR\r\n", local_port, remote_port);
   } else {
      fprintf(stderr, "%s: ", progname);
      fprintf(stderr, message, x1, x2, x3, x4);
      fprintf(stderr, "/");
      perror(where);
   }
   exit(3);
}


/**
***
*** Get a piece of kernel memory
*** The "what" parameter is just for error messages
*** 
**/
getbuf( unsigned long addr, 
       void * buf, 
       int len, 
       char * what)

{
   if (lseek(kmemfd, addr, 0) == -1) {
      Error_Die("getbuf", "lseek(0x%08x, \"%s\")/", 
		(unsigned long) addr, (unsigned long) what, 0, 0);
   }

   if (read(kmemfd, buf, len) < len) {
      Error_Die("getbuf", "&%08x: read(%08x, %d, \"%s\")/", (unsigned long) addr, 
		(unsigned long) buf, (unsigned long) len, (unsigned long) what);
   }
}



/**
***
*** Traverse the inpcb list until a match is found 
*** 
*** 
**/

struct socket *getlist( struct inpcb *pcbp,
		       struct in_addr faddr,
		       int fport,
		       struct in_addr laddr,
		       int lport)
{
   struct inpcb *head;
  
   if (!pcbp)
      return NULL;		/* Someone gave us a duff one here */
  
   head = pcbp->inp_prev;
   do {
      if (pcbp->inp_faddr.s_addr == faddr.s_addr &&
	  pcbp->inp_laddr.s_addr == laddr.s_addr &&
	  pcbp->inp_fport        == fport &&
	  pcbp->inp_lport        == lport )
	 return pcbp->inp_socket;

   } while (pcbp->inp_next != head &&
	    getbuf((long) pcbp->inp_next, pcbp, sizeof(struct inpcb), "tcblist"));

   return NULL;			/* Not found */
}



/**
***
*** Locate the 'ucred' information
*** This routine varies a lot according to the type of machine
*** its being compiled for
**/

struct ucred *getinfo(struct in_addr faddr,
		      int fport,
		      struct in_addr laddr,
		      int lport)
{
   static struct ucred ucb;
   struct socket *sockp;
   int i;
   int nfile;
   struct inpcb tcb;		/*  */
#ifdef NeXT
   struct file file_entry;
#else
   struct file * file_tab;
#endif
   void * addr;
#ifdef SYS5_KERNEL
   struct var kernel_params;
#endif
   void * calloc();		/* I hate it when things are missed from header files */


   /* -------------------- TCP PCB LIST -------------------- */
   getbuf(nl[N_TCB].n_value, &tcb, sizeof(tcb), "tcb");
   tcb.inp_prev = (struct inpcb *) nl[N_TCB].n_value;
   sockp = getlist(&tcb, faddr, fport, laddr, lport);
   if (!sockp)
      return NULL;

   /* -------------------- FILE DESCRIPTOR TABLE -------------------- */
   /* These bits are for semi-conventional - ie non-Mach - machines */
#ifndef NeXT
   /* Get the size of the file table */
#ifdef SYS5_KERNEL
   getbuf(nl[N_NFILE].n_value, &kernel_params, sizeof(kernel_params), "kernel_params");
   nfile = kernel_params.v_file; /* Size of file table */
#else SYS5_KERNEL
   getbuf(nl[N_NFILE].n_value, &nfile, sizeof(nfile), "nfile");
#endif SYS5_KERNEL
   /* Allocate space for a file table copy */
   if ((file_tab = calloc((size_t) nfile, (size_t) sizeof(struct file))) == NULL) {
      Error_Die("getinfo", "No memory", 0, 0, 0, 0);
   }

   /* Now get a copy of the file table */
#ifdef SYS5_KERNEL
   /* NB for SYS5 the file parameter points directly to the start of the file table */
   getbuf(nl[N_FILE].n_value, file_tab, sizeof(struct file)*nfile, "file[]");
#else SYS5_KERNEL
   getbuf(nl[N_FILE].n_value, &addr, sizeof(addr), "&file");
   getbuf(addr, file_tab, sizeof(struct file)*nfile, "file[]");
#endif SYS5_KERNEL

   /* Locate the file descriptor that has the socket in question
      open so that we can get the 'ucred' information */
   for (i = 0; i < nfile; i++) {
      /* This next bit is implemented as 2 ifs, cos its easier to debug that way! */
      if (file_tab[i].f_count &&
	  file_tab[i].f_type == DTYPE_SOCKET) {
	 if ((struct socket *) file_tab[i].f_data == sockp) {
	    getbuf(file_tab[i].f_cred, &ucb, sizeof(ucb), "ucb");
	    return &ucb;
	 }
      }
   }

#else NeXT
   /* So now we hit the fun Mach kernel structures */
   getbuf(nl[N_FILE].n_value, &addr, sizeof(addr), "&file_table");
   /* We only use nfile as a failsafe in case something goes wrong! */
   getbuf(nl[N_NFILE].n_value, &nfile, sizeof(nfile), "nfile");
   file_entry.links.next = addr;
   do {
      getbuf((unsigned long) file_entry.links.next, &file_entry, 
	     sizeof(file_entry), "struct file");
      if (file_entry.f_count &&
	  file_entry.f_type == DTYPE_SOCKET) {
	 if ((void *) file_entry.f_data == (void *) sockp) {
	    getbuf((unsigned long) file_entry.f_cred, &ucb, sizeof(ucb), "ucb");
	    return &ucb;
	 }
      }
   } while ((file_entry.links.next != addr) && (--nfile));
#endif NeXT
   return NULL;
}


/**
***
*** Processes the flag arguments to the program,
*** also determines the invoking name of the program.
***
**/
void process_flags(int argc, char * argv[])
{
   int c;
   char * ptr;

   /* Set up program name variable */
   progname = argv[0];
   for(ptr = argv[0]; *ptr; ptr++) {
      if (*ptr == '/')
	 progname = ptr;
   }
   if (*progname == '/')
      progname++;

   /* Check the program name and modify behavior accordingly */
   if ((!strcmp(progname,"identd"))
       || (!strcmp(progname,"in.identd"))) {
      flagidentd = flagpwnam = 1;
   } else {
      if (!strcmp(progname,"tcpuname"))
	 flagpwnam = 1;
   }


   /* Work through any command line flags */
   while ((c = getopt(argc, argv, "dV")) != EOF) {
      switch (c) {
       case 'V':
	 version();
	 break;

       case 'd':
	 debug_flag = 1;
	 break;
	
       default:
	 usage();
      }
   }
}

/**
***
*** Gets addresses of the current machine and the
*** Querying machine, reads socket numbers from stdin
***
**/
void get_identd_addresses(struct in_addr * local_addr, 
			 int * local_port,
			 struct in_addr * remote_addr, 
			 int * remote_port)
{
   struct sockaddr_in sin;
   int len;

   /* Get the local/foreign port pair from the luser */
   if (scanf("%d , %d", local_port, remote_port) != 2) {
      Error_Die("get_identd_addresses", "Incorrect input/", 0, 0, 0, 0);
   }


   /* Foreign internet address */
   len = sizeof(struct sockaddr_in);
   if (getpeername(0,&sin,&len) == -1) {
      Error_Die("get_identd_addresses", "getpeername failed/", 0, 0, 0, 0);
   }
   *remote_addr = sin.sin_addr;


   /* Local internet address */
   len = sizeof(struct sockaddr_in);
   if (getsockname(0,&sin,&len) == -1) {
      Error_Die("get_identd_addresses", "getsockname failed/", 0, 0, 0, 0);
   }
   *local_addr = sin.sin_addr;
}



/**
***
*** Gets addresses of the current machine and the
*** Querying machine, reads socket numbers from command line.
***
**/
void get_given_addresses(int argc,
			 char * argv[],
			 struct in_addr * local_addr, 
			 int * local_port,
			 struct in_addr * remote_addr, 
			 int * remote_port)
{
   int x1, x2, x3, x4;

  
   if(optind == 0)
      optind++;			/* Tacky getopts bug - grrrgh! */

   if ((argc - optind) < 3)
      Error_Die("get_given_addresses","need four arguments", 0, 0, 0, 0);

   if(sscanf(argv[optind++],"%d.%d.%d.%d",&x1,&x2,&x3,&x4) < 4)
      Error_Die("get_given_addresses","arg 1 must be a.b.c.d", 0, 0, 0, 0);
   remote_addr->s_addr = (x1 << 24) | (x2 << 16) | (x3 <<8) | x4;

   if(sscanf(argv[optind++],"%d",remote_port) < 1)
      Error_Die("get_given_addresses","arg 2 must be integer", 0, 0, 0, 0);

   if(sscanf(argv[optind++],"%d.%d.%d.%d",&x1,&x2,&x3,&x4) <4)
      Error_Die("get_given_addresses","arg 3 must be a.b.c.d", 0, 0, 0, 0);
   local_addr->s_addr = (x1 << 24) | (x2 << 16) | (x3 <<8) | x4;

   if(sscanf(argv[optind++],"%d",local_port) < 1)
      Error_Die("get_given_addresses","arg 4 must be integer", 0, 0, 0, 0);
}


/**
***
*** Dives into the deep end and drags out the kernel
*** addresses for required things from the name list
*** The list of things looked for is set at compile
*** time but may (will!) be different for each type
*** of machine!!
***
**/
void kernel_namelist(struct nlist nl[])
{
   /* There are (of course) two main ways of doing this,
      and one assumes that people will find other ways as well!
      The #ifdef-ed sections select out the different techniques.
      ** The KVM method has been removed since I can't test it **
      */

#ifdef NeXT
   /* I normally quite like NeXT, but fiddling with the names of
      components of the nlist structure could change that, hence
      the horrible defines just here */
#define N_NAME n_un.n_name
#else
#define N_NAME n_name
#endif

   /* First we open the kernel memory special file */
   if ((kmemfd = open(_PATH_KMEM, 0)) <= 0) {
      Error_Die("kernel_namelist", "open %s", _PATH_KMEM, 0, 0, 0);
   }

   /* Dig into the Unix kernel... */
   if (nlist(_PATH_UNIX, nl) != 0) {
      if (debug_flag) {		/* Here's some additional debug tracking */
	 int i;
	 for (i=0; (*(nl[i].N_NAME)); i++) {
	    if (nl[i].n_type == 0)
	       fprintf(stderr, "\t%s not found.\n", nl[i].N_NAME);
	 }
      }
      Error_Die("kernel_namelist", "nlist", 0, 0, 0 , 0);
   }
}


/**
***
*** The main bit...
***
**/
main(int argc, char * argv[])
{
   struct sockaddr_in sin;
   int len;
   struct in_addr local_addr, remote_addr;
   struct ucred *ucp;
   struct passwd *pwp;


   /* Process the command flags, and figure out what program we are! */
   process_flags(argc, argv);

   /* Get the ether & socket addresses involved */
   if (flagidentd) {
      get_identd_addresses(&local_addr, &local_port, &remote_addr, &remote_port);
   } else {
      get_given_addresses(argc, argv, &local_addr, &local_port, &remote_addr, &remote_port);
   }

   /* Get data from the kernel namelists */
   kernel_namelist(nl);

   /* Next - get locate the specific TCP connection and return the
      'ucred' - user credentials - information */

   if ((ucp = getinfo(remote_addr, 
		      htons(remote_port), 
		      local_addr, 
		      htons(local_port))) == NULL) {
      /* This bit is done as a discrete error message because its a 
	 different message to all the others! */
      printf("%d, %d: ERROR: NO-USER\r\n", local_port, remote_port);
      exit(37);
   }

   /* Then we should try to get the username - when required */
   if (flagpwnam) {
      if ((pwp = getpwuid(ucp->cr_ruid)) == NULL) {
	 Error_Die("main", "unable to lookup UID", 0, 0, 0, 0);
      }

      /* Hey! We finally made it!!! - output style depends on what we are! */
      if (flagidentd) {
	 printf("%d, %d: USERID: UNIX: %s\r\n", local_port, remote_port, pwp->pw_name);
	 fflush(stdout);
      } else
	 printf("%s\n", pwp->pw_name);
   } else {
      /* Just need to print UID */
      printf("%d\n", ucp->cr_ruid);
   }
   /* Time for bed said..... */
   exit(0);
}
