/*
** uidentd.c                    A TCP/IP link identification protocol server
**
** Version: 1.0
**
** This program is in the public domain and may be used freely by anyone
** who wants to. 
**
** Last update: 26 May 1992
**
** Please send bug fixes/bug reports to: Peter Eriksson <pen@lysator.liu.se>
*/

#define VERSION "[udp.identd, version 1.0]"

#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <netdb.h>
#include <nlist.h>
#include <pwd.h>
#include <signal.h>
#include <syslog.h>

#include "ident.h"

#ifdef HAVE_KVM
#include <kvm.h>
#else
#include "kvm.h"
#endif

#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/ioctl.h>

#ifndef HPUX7
#define KERNEL
#endif

#include <sys/file.h>

#ifndef HPUX7
#undef KERNEL
#endif

#include <fcntl.h>

#ifdef ultrix
#include <sys/dir.h>
#endif

#include <sys/user.h>
  
#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/in_pcb.h>
#include <netinet/tcp.h>
#include <netinet/ip_var.h>
#include <netinet/tcp_timer.h>
#include <netinet/tcp_var.h>
#include <arpa/inet.h>

#ifdef MIPS
#include <sysv/sys/var.h>
extern int errno;
#endif


extern void *calloc();


struct nlist nl[] =
{
#ifdef hp9000s800	/* HP9000 Series 800, HP-UX 8.0 */

#define N_FILE  0  
#define N_NFILE 1
#define N_TCB   2

  { "file" },
  { "nfile" },
  { "tcb" },
  
#else
#ifdef HPUX7		/* HP-UX 7.0 */

#define N_FILE  0  
#define N_NFILE 1
#define N_TCB   2

  { "_file" },
  { "_nfile" },
  { "_tcp_cb" },

#else
#ifdef MIPS		/* MIPS RISC/OS */

#define N_FILE 0  
#define N_V    1
#define N_TCB  2

  { "_file" },
  { "_v" },
  { "_tcb" },

#else			/* SUN or BSD or other systems */

#define N_FILE 0  
#define N_NFILE 1
#define N_TCB 2

  { "_file" },
  { "_nfile" },
  { "_tcb" },

#endif
#endif
#endif
  { "" }
};


char *path_unix = NULL;
char *path_kmem = NULL;

kvm_t *kd;

int timeout = 30;     /* 30 seconds */

int verbose_flag = 0;
int debug_flag = 0;
int syslog_flag = 0;

#ifdef LOG_DAEMON
int syslog_facility = LOG_DAEMON;
#endif

struct file *xfile;
int nfile;

#ifdef MIPS
struct var v;
#endif

int lport, fport;
struct inpcb tcb;

struct uident_request request;
struct uident_reply   reply;
int fromlen;
struct sockaddr_in fromsin;

  

/*
** Error handling macros
*/
#define ERROR(fmt) \
    ((syslog_flag ? syslog(LOG_ERR, fmt) : 0), \
     error(UIDENT_E_UNKNOWN_ERROR), \
     exit(1), 0)

#define ERROR1(fmt,v1) \
    ((syslog_flag ? syslog(LOG_ERR, fmt, v1) : 0), \
     error(UIDENT_E_UNKNOWN_ERROR), \
     exit(1), 0)


#define ERROR2(fmt,v1,v2) \
    ((syslog_flag ? syslog(LOG_ERR, fmt, v1, v2) : 0), \
     error(UIDENT_E_UNKNOWN_ERROR), \
     exit(1), 0)



/*
** The structure passing convention for GCC is incompatible with
** Suns own C compiler, so we define our own inet_ntoa() function.
** (This should only affect GCC version 1 I think, a well, this works
** for version 2 also so why bother.. :-)
*/
#if defined(__GNUC__) && defined(__sparc__)

#ifdef inet_ntoa
#undef inet_ntoa
#endif

char *inet_ntoa(ad)
  struct in_addr ad;
{
  unsigned long int s_ad;
  int a, b, c, d;
  static char addr[20];
  
  s_ad = ad.s_addr;
  d = s_ad % 256;
  s_ad /= 256;
  c = s_ad % 256;
  s_ad /= 256;
  b = s_ad % 256;
  a = s_ad / 256;
  sprintf(addr, "%d.%d.%d.%d", a, b, c, d);
  
  return addr;
}
#endif


/*
** Return the name of the connecting host, or the IP number as a string.
*/
char *gethost(addr)
  struct in_addr *addr;
{
  struct hostent *hp;

  
  hp = gethostbyaddr(addr, sizeof(struct in_addr), AF_INET);
  if (hp)
    return hp->h_name;
  else
    return inet_ntoa(*addr);
}


/*
** Get a piece of kernel memory with error handling.
** Always return 1.
*/
int getbuf(addr, buf, len, what)
  long addr;
  char *buf;
  int len;
  char *what;
{

  if (kvm_read(kd, addr, buf, len) < 0)
    ERROR2("getbuf: kvm_read(%08x, %d)", addr, len);
  
  return 1;
}



/*
** Traverse the inpcb list until a match is found.
** Returns NULL if no match.
*/
struct socket *getlist(pcbp, faddr, fport, laddr, lport)
  struct inpcb *pcbp;
  struct in_addr *faddr;
  int fport;
  struct in_addr *laddr;
  int lport;
{
  struct inpcb *head;
  

  if (!pcbp)
    return NULL;
  
  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;
}



/*
** Locate the 'ucred' information for a certain connection.
*/
struct ucred *getinfo(faddr, fport, laddr, lport)
  struct in_addr faddr;
  int fport;
  struct in_addr laddr;
  int lport;
{
  static struct ucred ucb;
  long addr;
  struct socket *sockp;
  int i;
  
  

  /* -------------------- FILE DESCRIPTOR TABLE -------------------- */
#ifdef MIPS
  getbuf(nl[N_V].n_value, &v, sizeof(v), "v");
  nfile = v.v_file;
  addr = nl[N_FILE].n_value;
#else
  getbuf(nl[N_NFILE].n_value, &nfile, sizeof(nfile), "nfile");
  getbuf(nl[N_FILE].n_value, &addr, sizeof(addr), "&file");
#endif
  
  xfile = (struct file *) calloc(nfile, sizeof(struct file));
  if (!xfile)
    ERROR2("getinfo: calloc(%d,%d)", nfile, sizeof(struct file));
  
  getbuf(addr, xfile, sizeof(struct file)*nfile, "file[]");


  /* -------------------- 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;


  /*
  ** 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++)
    if (xfile[i].f_count &&
	xfile[i].f_type == DTYPE_SOCKET &&
	(struct socket *) xfile[i].f_data == sockp)
    {
      getbuf(xfile[i].f_cred, &ucb, sizeof(ucb));
      return &ucb;
    }

  return NULL;
}



error(code)
  int code;
{
  reply.type = UIDENT_T_ERROR;
  reply.u.error.code = code;

  sendto(0, &reply, sizeof(reply), 0, &fromsin, fromlen);
}

userid(username)
  char *username;
{
  reply.type = UIDENT_T_USERID;
  reply.u.userid.idlen = strlen(username);
  strcpy(reply.u.userid.buf, username);
  sendto(0, &reply, sizeof(reply), 0, &fromsin, fromlen);
}



int main(argc,argv)
  int argc;
  char *argv[];
{
  int i;
  int len;
  struct sockaddr_in sin;
  struct in_addr laddr, faddr;
  struct ucred *ucp;
  struct passwd *pwp;
  
  /*
  ** Parse the command line arguments
  */
  for (i = 1; i < argc && argv[i][0] == '-'; i++)
    while (*(++argv[i]))
      switch (*argv[i])
      {
	case 'l':
	  syslog_flag++;
	  break;
	  
        case 'V':
	  puts(VERSION);
	  exit(0);
	  break;

	case 'v':
	  verbose_flag++;
	  break;
	  
        case 'd':
	  debug_flag++;
	  break;
      }

  /*
  ** Path to kernel namelist file specified on command line
  */
  if (i < argc)
    path_unix = argv[i++];

  /*
  ** Path the kernel memory device specified on command line
  */
  if (i < argc)
    path_kmem = argv[i++];


  /*
  ** Open the connection to the Syslog daemon if requested
  */
  if (syslog_flag)
  {
#ifdef LOG_DAEMON
    openlog("uidentd", LOG_PID, syslog_facility);
#else
    openlog("uidentd", LOG_PID);
#endif
  }
  

  /*
  ** Open the kernel memory device
  */
  if (!(kd = kvm_open(path_unix, path_kmem, NULL, O_RDONLY, NULL)))
    ERROR("main: kvm_open");
  
  /*
  ** Extract offsets to the needed variables in the kernel
  */
  if (kvm_nlist(kd, nl) != 0)
    ERROR("main: kvm_nlist");

  fromlen = sizeof(fromsin);
  alarm(timeout);
  while (recvfrom(0, &request, sizeof(request), 0, &fromsin, &fromlen) != -1)
  {
    alarm(timeout);
    
    faddr = fromsin.sin_addr;
    
    reply.version = request.version;
    reply.reqno   = request.reqno;
    reply.lport   = request.lport;
    reply.fport   = request.fport;
    reply.laddr   = request.laddr;
    laddr.s_addr  = request.laddr;
    
    if (request.version != 1)
      error(UIDENT_E_ILLEGAL_VERSION);

    if (request.lport < 1 || request.lport > 65535 ||
	request.fport < 1 || request.fport > 65535)
    {
      if (syslog_flag)
	syslog(LOG_INFO, "invalid-port(s): %d, %d",
	       request.lport,
	       request.fport);

      error(UIDENT_E_INVALID_PORT);
      continue;
    }

    /*
    ** Next - get the specific TCP connection and return the
    ** 'ucred' - user credentials - information 
    */
    ucp = getinfo(&faddr, htons(request.fport),
		  &laddr, htons(request.lport));
    if (!ucp)
    {
      if (syslog_flag)
	syslog(LOG_DEBUG, "Returned: %d, %d: NO-USER",
	       request.lport, request.fport);
      
      error(UIDENT_E_NO_USER);
      continue;
    }
    
    /*
    ** Then we should try to get the username
    */
    pwp = getpwuid(ucp->cr_ruid);
    if (!pwp)
    {
      if (syslog_flag)
	syslog(LOG_WARNING, "getpwuid() could not map uid (%d) to name",
	       ucp->cr_ruid);
      
      error(UIDENT_E_UNKNOWN_ERROR);
      continue;
    }

    /*
    ** Hey! We finally made it!!!
    */
    if (syslog_flag)
      syslog(LOG_DEBUG, "Successful lookup: %d, %d: %s\n",
	     request.lport, request.fport, pwp->pw_name);
    
    userid(pwp->pw_name);
  }
}

