/*
** uw2.c                UnixWare v2.x specific kernel access functions
**
** Please send mods/bug fixes/bug reports to: Paul F. Wells <paul@wellserv.com>
**
*/

#define _KMEMUSER
#include <sys/types.h>
#include <sys/ksynch.h>
#include <sys/flock.h>
#include <sys/pid.h>
#include <sys/var.h>
#include <sys/proc.h>
#include <sys/cred.h>
#include <sys/session.h>
#include <sys/socket.h>
#include <sys/strsubr.h>
#include <net/route.h>
#include <netinet/in_pcb.h>
#include <netinet/tcp_kern.h>
#include <sys/fs/snode.h>
#include <sys/stat.h>
#include <sys/mkdev.h>
#undef _KMEMUSER

#include <sys/ksym.h>
#include <sys/sysmacros.h>

#include <arpa/inet.h>

#include <fcntl.h>

#include "paths.h"
#include "kvm.h"
#include "identd.h"
#include "error.h"

#include <stdio.h>	/* this kinda reeks */

#ifndef TRUE
enum { FALSE, TRUE };
#endif

static kvm_t *kd;

int k_open()
{
  /*
  ** Open the kernel memory device
  */
  if (!(kd = kvm_open(path_unix, path_kmem, NULL, O_RDONLY, NULL)))
    ERROR("main: kvm_open");
  
#ifndef UW2	/* don't really need this */
  /*
  ** Extract offsets to the needed variables in the kernel
  */
  if (kvm_nlist(kd, nl) != 0)
    ERROR("main: kvm_nlist");
#endif

  return 0;
}

/*
** Get a piece of kernel memory with error handling.
** Returns 1 if call succeeded, else 0 (zero).
*/
static int
getbuf(long addr,void *buf,int len,char *what)
{
	if (kvm_read(kd, addr, buf, len) < 0) {
		if (syslog_flag)
			syslog(LOG_ERR, "getbuf: kvm_read(%08x, %d) - %s : %m",
					addr, len, what);
		return 0;
	}
	return 1;
}

int
k_getuid(struct in_addr *faddr,int fport,struct in_addr *laddr,int lport,int *uid)
{
	int gotit;
	ulong_t info;
	struct stat tcpstat;
	dev_t tcp_clone;
	minor_t tcp_minor;
	snode_t **snode_base, *snodep, spec_node;
	int snode_hash;
	vnode_t *vnodep;
	struct inpcb pcb, *tcb_first;
	proc_t *proc_head, *procp, uproc;
	int maxfd;
	int ifdt, fdtable_len;
	fd_entry_t *fdp;
	file_t *filep, file_entry;
	cred_t creds;

	/* initialize & find pcb for specified connection */

	if (stat("/dev/tcp", &tcpstat) != 0)
		return -1;
	tcp_minor = minor(tcpstat.st_rdev);

	info = 0;
	tcb_first = NULL;
	if (getksym("tcb", (ulong_t *) &tcb_first, &info) < 0)
		return -1;

	for (gotit = FALSE, pcb.inp_prev = tcb_first; ; ) {
		/* why do we have to go backward? */
		if (!getbuf((long) pcb.inp_prev, &pcb, sizeof(pcb), "inp_prev"))
			return -1;
		if (
			pcb.inp_faddr.s_addr == faddr->s_addr &&
			pcb.inp_laddr.s_addr == laddr->s_addr &&
			pcb.inp_fport == fport &&
			pcb.inp_lport == lport
		) {
			/* I screwed up: 2.01 allowed makedev() */
			tcp_clone = makedevice(tcp_minor, pcb.inp_minor);
			gotit = TRUE;
			break;
		}
		if (pcb.inp_prev == NULL || pcb.inp_prev == tcb_first) break;
	}
	if (!gotit) return -1;

	/* now crawl through snodes to find vnode of clone device */

	info = 0;
	snode_base = NULL;
	if (getksym("spectable", (ulong_t *) &snode_base, &info) < 0)
		return -1;
	snode_hash = SPECTBHASH(tcp_clone);
	if (!getbuf((long) (snode_base + snode_hash), &snodep, sizeof(snodep), "snode_hash"))
		return -1;

	for (gotit = FALSE; snodep != NULL ; snodep = spec_node.s_next) {
		if (!getbuf((long) snodep, &spec_node, sizeof(spec_node), "snodep"))
			return -1;
		if (spec_node.s_dev == tcp_clone) {
			vnodep = (vnode_t *) ((char *) snodep + offsetof(struct snode, s_vnode));
			gotit = TRUE;
			break;
		}
	}
	if (!gotit || (spec_node.s_flag & SINVALID)) return -1;

	/* find process with this vnode */

	info = 0;
	procp = NULL;
	if (getksym("practive", (ulong_t *) &procp, &info) < 0)
		return -1;
	if (!getbuf((long) procp, &proc_head, sizeof(proc_head), "practive"))
		return -1;

	for (gotit = FALSE, procp = proc_head; procp != NULL; procp = uproc.p_next) {
		if (!getbuf((long) procp, &uproc, sizeof(uproc), "procp"))
			return -1;
		if((maxfd = uproc.p_fdtab.fdt_sizeused) == 0) continue;
		fdtable_len = maxfd * sizeof(fd_entry_t);
		if ((fdp = (fd_entry_t *) malloc(fdtable_len)) == NULL)
			return -1;
		if (!getbuf((long) uproc.p_fdtab.fdt_entrytab, fdp, fdtable_len, "fdt_entrytab")) {
			free(fdp);
			return -1;
		}
		for (ifdt = 0; ifdt < maxfd; ifdt++) {
			if (fdp[ifdt].fd_status != FD_INUSE) continue;
			filep = fdp[ifdt].fd_file;
			if (!getbuf((long) filep, &file_entry, sizeof(file_entry), "filep")) {
				free(fdp);
				return -1;
			}
			if (vnodep == file_entry.f_vnode) {
				if (file_entry.f_cred != NULL) {
					if (!getbuf((long) file_entry.f_cred, &creds, sizeof(creds), "f_cred")) {
						free(fdp);
						return -1;
					}
					free(fdp);
					gotit = TRUE;
					goto got_proc;
				}
			}
		}
		free(fdp);
	}
got_proc:	;	/* maybe */
	if (!gotit) return -1;

	/* wow! */
	*uid = creds.cr_ruid;

	return 0;
}
