/*
This was written for 3.2; it may not work under 3.1, though I'd be 
(relatively) happy to have someone try it.

Note that this could probably be more efficient if I could figure out
how to directly search the TCP control blocks.  Unfortunately, I have
no idea where they're stored in the kernel, and I didn't really want to
spend a lot of time figuring it out.  With 20 users logged on and >100
connections, it only takes about .1 second to identify a connection on
this 530.

Like authd, pauthd, and pidentd, aixident is meant to be started from
inetd:

ident   stream  tcp     nowait  root    /usr/local/etc/aixident in.identd -l

The name is pronounced like "accident", because that's what I believe
RFC931 is.  Use at your own risk.
*/

/*
**  aixident -- AIX identification daemon
**  version 1.0
**
**  Copyright 1992 by Charles M. Hannum.
**
**  Permission is granted to copy, modify, and use this program in any way,
**  so long as the above copyright notice, this permission notice, and the
**  warranty disclaimer below remain on all copies, and are unaltered.
**
**  aixident 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.
*/

/*
**  Usage:
**
**    aixident [-l] [-V] [-v] [-d] [kmem_file]
**
**  Where:
**
**    -l  log via syslog
**
**    -V  print version number and exit
**
**    -v  verbose (currently useless)
**
**    -d  enable debugging; lists connections in a lsof-like manner
**
**    kmem_file  file to read kernel memory from
*/

char version[] = "aixident, version 1.0";

#include <sys/types.h>

#include <stdlib.h>
#include <errno.h>
#include <pwd.h>
#include <stdio.h>
#include <syslog.h>

#include <sys/domain.h>
#include <sys/protosw.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/vfs.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/in_pcb.h>
#include <arpa/inet.h>

#define _KERNEL 1
#include <sys/file.h>
#undef  _KERNEL
#include <procinfo.h>

int kmem;

int kread ();
char *printuid ();
void printaddr ();

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

#define ERROR(v,m) \
  do {									\
    printf ("%d, %d: ERROR: %s\r\n", local_port, foreign_port, m);	\
    exit (v);								\
  } while (0)

#define UNKNOWN_ERROR() ERROR (1, "UNKNOWN-ERROR");

int
main (argc, argv)
  int argc;
  char **argv;
{
  char *path_kmem = "/dev/kmem";
  struct sockaddr_in foreign, local;
  int foreign_len, local_len;
  int foreign_port = 0, local_port = 0;
  int max_procs = 64,
      num_procs, fd;
  struct procinfo *procinfo;
  struct user user;
  struct file *filep, file;
  struct socket *socketp, socket;
  struct protosw *protoswp, protosw;
  struct domain *domainp, domain;
  struct inpcb *inpcbp, inpcb;
  struct passwd *passwd;

  for (--argc; argc && **++argv == '-'; argc--)
    while (*(++*argv))
      switch (**argv) {
        case 'l':
	  syslog_flag = 1;
	  break;
	case 'V':
	  puts (version);
	  exit (0);
	  break;
	case 'v':
	  verbose_flag = 1;
	  break;
	case 'd':
	  debug_flag = 1;
	  break;
      }

  if (argc) {
    path_kmem = *(argv++);
    --argc;
  }

  foreign_len = sizeof (foreign);
  if (getpeername (0, &foreign, &foreign_len) == -1) {
    perror ("getpeername");
    exit (1);
  }

  local_len = sizeof (local);
  if (getsockname (0, &local, &local_len) == -1) {
    perror ("getsockname");
    exit (1);
  }

  if (syslog_flag) {
    (void) openlog ("identd", LOG_PID, LOG_DAEMON);
    (void) syslog (LOG_NOTICE, "Connection from %s",
		   inet_ntoa (foreign.sin_addr.s_addr));
  }

  if (scanf ("%d , %d", &local_port, &foreign_port) != 2 ||
      local_port < 1 || local_port > 65535 || foreign_port < 1 ||
      foreign_port > 65535) {
    if (syslog_flag)
      (void) syslog (LOG_INFO, "invalid port(s): %d, %d", local_port,
		     foreign_port);
    ERROR (0, "INVALID-PORT");
  }

  if ((kmem = open (path_kmem, O_RDONLY)) == -1) {
    if (syslog_flag)
      (void) syslog (LOG_ERR, "error opening /dev/kmem: %s", strerror (errno));
    UNKNOWN_ERROR ();
  }

  while ((procinfo = (struct procinfo *)
		     malloc ((size_t) (max_procs * sizeof (*procinfo)))) &&
         (num_procs = getproc (procinfo, max_procs,
			       sizeof (*procinfo))) == -1 &&
	 errno == ENOSPC) {
    max_procs <<= 1;
    free (procinfo);
  }

  if (! procinfo) {
    if (syslog_flag)
      (void) syslog (LOG_ERR, "out of memory allocating %ld procinfo structs\n",
		     max_procs);
    UNKNOWN_ERROR ();
  }

  for (; num_procs; num_procs--, procinfo++) {

    if (procinfo->pi_stat == 0 || procinfo->pi_stat == SZOMB)
      continue;

    if (getuser (procinfo, sizeof (*procinfo), &user, sizeof (user)))
      continue;

    for (fd = 0; fd < user.u_maxofile; fd++) {

      if (! (filep = user.u_ufd[fd].fp))
	continue;

      if (kread ((off_t) filep, (char *) &file, sizeof (file))) {
	if (syslog_flag)
          (void) syslog (LOG_ERR, "can't read file struct from %#x",
		         (unsigned) filep);
	UNKNOWN_ERROR ();
      }

      if (file.f_type != DTYPE_SOCKET)
	continue;

      if (! (socketp = (struct socket *) file.f_data))
	continue;

      if (kread ((off_t) socketp, (char *) &socket, sizeof (socket))) {
	if (syslog_flag)
          (void) syslog (LOG_ERR, "can't read socket struct from %#x",
			 (unsigned) socketp);
	UNKNOWN_ERROR ();
      }

      if (! (protoswp = socket.so_proto))
	continue;

      if (kread ((off_t) protoswp, (char *) &protosw, sizeof (protosw))) {
	if (syslog_flag)
	  (void) syslog (LOG_ERR, "can't read protosw struct from %#x",
			 (unsigned) protoswp);
	UNKNOWN_ERROR ();
      }

      if (protosw.pr_protocol != IPPROTO_TCP)
	continue;

      if (! (domainp = protosw.pr_domain))
	continue;

      if (kread ((off_t) domainp, (char *) &domain, sizeof (domain))) {
	if (syslog_flag)
	  (void) syslog (LOG_ERR, "can't read domain struct from %#x",
			 (unsigned) domainp);
	UNKNOWN_ERROR ();
      }

      if (domain.dom_family != AF_INET)
	continue;

      if (! (inpcbp = (struct inpcb *) socket.so_pcb))
	continue;

      if (kread ((off_t) inpcbp, (char *) &inpcb, sizeof (inpcb))) {
	if (syslog_flag)
	  (void) syslog (LOG_ERR, "can't read inpcb struct from %#x",
			 (unsigned) inpcbp);
	UNKNOWN_ERROR ();
      }

      if (socketp != inpcb.inp_socket)
	continue;

      if (inpcb.inp_faddr.s_addr != foreign.sin_addr.s_addr ||
	  inpcb.inp_fport != foreign_port ||
	  inpcb.inp_laddr.s_addr != local.sin_addr.s_addr ||
	  inpcb.inp_lport != local_port)
	continue;

      if (! (passwd = getpwuid (procinfo->pi_uid))) {
	if (syslog_flag)
	  (void) syslog (LOG_WARNING, "could not map uid to name: %d",
			 procinfo->pi_uid);
	UNKNOWN_ERROR ();
      }

      if (!debug_flag) {
        if (syslog_flag)
	  (void) syslog (LOG_NOTICE, "identified: %d, %d, %s", local_port,
		         foreign_port, passwd->pw_name);
        printf ("%d, %d: USERID: UNIX: %s\r\n", local_port, foreign_port,
	        passwd->pw_name);
        exit (0);
      } else {
        printf ("%-9.9s%6d %8s %4d%c %4.4s 0x%08x %8s %7.7s ",
                user.u_comm, procinfo->pi_pid, printuid (procinfo->pi_uid),
                fd, ' ', "inet", inpcb.inp_ppcb ?
                                   (int) inpcb.inp_ppcb : (int) socket.so_pcb,
                "", "TCP");

        printaddr (&inpcb.inp_laddr, inpcb.inp_lport);
        if (inpcb.inp_faddr.s_addr != INADDR_ANY || inpcb.inp_fport) {
          printf ("->");
          printaddr (&inpcb.inp_faddr, inpcb.inp_fport);
        }

        printf ("\n");
      }
    }
  }

  if (syslog_flag)
    (void) syslog (LOG_DEBUG, "not found: %d, %d", local_port, foreign_port);
  ERROR (0, "NO-USER");
}

int
kread (addr, buf, len)
  off_t addr;
  char *buf;
  int len;
{
  int br;

  if (lseek (kmem, addr, L_SET) == (off_t) -1)
    return (-1);

  br = read(kmem, buf, len);

  return ((br == len) ? 0 : 1);
}

char *
printuid (uid)
  uid_t uid;
{
  static int used = 0;
  static uid_t last_uid;
  struct passwd *passwd;
  static char user[9];

  if (!used || uid != last_uid) {
    used = 1;
    last_uid = uid;

    if (passwd = getpwuid (uid))
      (void) strcpy (user, passwd->pw_name);
    else
      (void) sprintf (user, "%d", uid);
  }

  return (user);
}

void
printaddr (in_addr, port)
  struct in_addr *in_addr;
  u_short port;
{
  if (in_addr->s_addr == INADDR_ANY)
    printf ("*:%d", port);
  else
    printf ("%s:%d", inet_ntoa (in_addr->s_addr), port);
}

