/* os.c -- Operating system specific functions. */

/* Copyright (C) 1988, 1990, 1992  Free Software Foundation, Inc.

   This file is part of GNU Finger.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program 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.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include <stdio.h>
#include <config.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/acct.h>
#include <time.h>
#include <packet.h>

#ifdef HAVE_UTMPX_H
#include <utmpx.h>
typedef struct utmpx UTMP;
#else
#include <utmp.h>
typedef struct utmp UTMP;
#endif

#include <pwd.h>

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif

#ifdef HAVE_PROC_FS
#include <sys/signal.h>
#include <sys/fault.h>
#include <sys/syscall.h>
#include <sys/procfs.h>
#endif

#include <general.h>

#ifndef L_SET
#define L_SET SEEK_SET
#define L_INCR SEEK_CUR
#endif /* !L_SET */

/* What is the better way of doing this? */
UTMP foo_utmp;

#define LEN_USER_FIELD sizeof (foo_utmp.ut_name)
#define LEN_LINE_FIELD sizeof (foo_utmp.ut_line)

#if defined (HAVE_UT_HOST)
#define LEN_HOST_FIELD sizeof (foo_utmp.ut_host)
#endif

/* Location of the accounting file.  This is for filling in the WHAT field. */
#define ACCT_FILE "/usr/adm/acct"

/* Where the utmp file is located. */
#ifndef HAVE_GETUTENT
#define UTMP_FILE "/etc/utmp"
#endif

/* A non-null value is the address of the utmp entry which contains the
   information for the user using the console. */
UTMP *console_user;


/* **************************************************************** */
/*                                                                  */
/*              Getting the Original Finger Data                    */
/*                                                                  */
/* **************************************************************** */

/* Return a NULL terminated array of (FINGER_PACKET *) which represents
   the list of interesting users on *this* machine.  Single argument
   ALL_INFO when zero means don't place any user in the output list
   twice. */

FINGER_PACKET **
get_finger_data (all_info)
  int all_info;
{
  FINGER_PACKET **result, **utmp_to_packets ();

  UTMP **users, **make_utmp_array ();
  console_user = (UTMP *) NULL;

  users = make_utmp_array (!all_info);
  result = utmp_to_packets (users);

  free_array (users);
  return (result);
}

#ifdef _AIX
#include <procinfo.h>

int
aix_userinfo_sort (one, two)
void *one, *two;
{
  struct userinfo *One = (struct userinfo *)one,
		  *Two = (struct userinfo *)two;
  if (! One->ui_ttyp && Two->ui_ttyp)
    return -1;
  if (One->ui_ttyp && ! Two->ui_ttyp)
    return 1;
  if (One->ui_ttyd < Two->ui_ttyd)
    return -1;
  if (One->ui_ttyd > Two->ui_ttyd)
    return 1;
  if (One->ui_ttympx < Two->ui_ttympx)
    return -1;
  if (One->ui_ttympx > Two->ui_ttympx)
    return 1;
  if (One->ui_start > Two->ui_start)
    return -1;
  if (One->ui_start < Two->ui_start)
    return 1;
  return 0;
}

int
aix_userinfo_cmp (tty, userinfo)
void *tty, *userinfo;
{
  unsigned long *Tty = (unsigned long *)tty;
  struct userinfo *Userinfo = (struct userinfo *)userinfo;

  if (! Userinfo->ui_ttyp)
    return 1;
  if (Tty[0] < Userinfo->ui_ttyd)
    return -1;
  if (Tty[0] > Userinfo->ui_ttyd)
    return 1;
  if (Tty[1] < Userinfo->ui_ttympx)
    return -1;
  if (Tty[1] > Userinfo->ui_ttympx)
    return 1;
  return 0;
}

char *
aix_userinfo_lookup (userinfo, nproc, tty, device_name)
struct userinfo *userinfo;
int nproc;
dev_t tty;
char *device_name;
{
  struct userinfo *ptr;
  unsigned long Tty[2] = {tty};

  {
    char *foo;
    if (foo = strrchr (device_name, '/'))
      Tty[1] = atol (foo+1);
  }

  if ((ptr = bsearch (Tty, userinfo, nproc, sizeof (struct userinfo),
		      aix_userinfo_cmp)) == NULL)
    return ("");
  while (ptr > userinfo)
    if (aix_userinfo_cmp (Tty, --ptr)) {
      ++ptr;
      break;
    }
  return (ptr->ui_comm);
}
#endif /* _AIX */

/* Return a NULL terminated array of (FINGER PACKET *) manufactured
   from the members of UTMPLIST (a NULL terminated array of struct
   utmp *). */

FINGER_PACKET **
utmp_to_packets (utmplist)
  UTMP **utmplist;
{
  int i, uid;
  long idle, current_time, get_last_access ();
  char *hostname, *ttyloc, *tty_location ();
#ifdef HAVE_PROC_FS
  prpsinfo_t procinfo;
  struct procent
    {
      time_t start;
      int uid;
      dev_t tty;
      char fname[sizeof (procinfo.pr_fname)];
    }
  *proctable, *tableptr;
  int nprocs = 0;
#endif /* HAVE_PROC_FS */
      
  FINGER_PACKET **result = (FINGER_PACKET **)NULL;
  int result_size;

  if (!(hostname = xgethostname ()))
    {
      extern int errno;
      extern char *sys_errlist[];

      /* Arbitrary limit: we only return the first 128 characters of
	 an error. This limit would be too complicated to remove. */
      static char hostname_error[128];

      strncpy (hostname_error, sys_errlist[errno],
	       sizeof hostname_error);
      hostname = hostname_error;
    }

  current_time = time ((time_t *)0);

  if (!utmplist)
    return (result);

  /* Make the output array. */
  result_size = array_len (utmplist);
  result = (FINGER_PACKET **) xmalloc ((result_size + 1) * sizeof (FINGER_PACKET *));

  /* If the system has /proc, collect process info prior to
     searching through user list. */

#ifdef HAVE_PROC_FS
  {
    extern char *savedir ();
    char *dirstring = savedir ("/proc", 2*1024, &nprocs);
    char *proc;
    int procfd;
    static char procfile[] = "/proc/XXXXX";

    
    /* Allocate process table */
    if (nprocs)
      proctable = (struct procent *) xmalloc (sizeof *proctable * nprocs);

    /* Walk processes */
    if (dirstring && nprocs)
      {
	for (tableptr = proctable, proc = dirstring;
	     *proc; proc += strlen (proc) + 1)
	  {
	    strncpy (procfile+6, proc, 5);

	    if ((procfd = open (procfile, O_RDONLY)) >= 0)
	      {
		ioctl (procfd, PIOCPSINFO, &procinfo);
		
		/* Only collect non-zombies with controlling tty */
		if (procinfo.pr_ttydev != PRNODEV
		    && !procinfo.pr_zomb)
		  {
		    tableptr->start = procinfo.pr_start.tv_sec;
		    tableptr->uid = procinfo.pr_uid;
		    tableptr->tty = procinfo.pr_ttydev;
		    strncpy (tableptr->fname, procinfo.pr_fname, sizeof tableptr->fname);
		    tableptr++;
		  }
		else
		  nprocs--;
		
		close (procfd);
	      }
	    else
	      nprocs--;
	  }
	free (dirstring);
      }
  }
#endif /* HAVE_PROC_FS */


  /* Fill it in. */
  for (i = 0; utmplist[i]; i++)
    {
      idle = current_time - get_last_access (utmplist[i]->ut_line);

      if (idle < 0)
	idle = 0;

      result[i] = (FINGER_PACKET *)xmalloc (sizeof (FINGER_PACKET));
      bzero (result[i], sizeof (FINGER_PACKET));

      bcopy (utmplist[i]->ut_name, result[i]->name, LEN_USER_FIELD);

#ifdef HAVE_UTMPX_H
      bcopy (&utmplist[i]->ut_tv.tv_sec, &result[i]->login_time, sizeof (long));
      utmplist[i]->ut_tv.tv_usec = 0;
#else
      bcopy (&utmplist[i]->ut_time, &result[i]->login_time, sizeof (long));
#endif
      bcopy (&idle, &result[i]->idle_time, sizeof (long));

      strncpy (result[i]->host, hostname, sizeof result[i]->host);

      strncpy (result[i]->ttyname, utmplist[i]->ut_line, LEN_LINE_FIELD);
      result[i]->ttyname[LEN_LINE_FIELD - 1] = '\0';

      ttyloc = tty_location (utmplist[i]);
      strncpy (result[i]->ttyloc, ttyloc, TTYLOC_LEN - 1);

      {
	struct passwd *passwd_entry = getpwnam (result[i]->name);

	uid = -1;
	if (passwd_entry && passwd_entry->pw_gecos)
	  {
	    register char *t = passwd_entry->pw_gecos;
	    register int j;
	    char *device_name;
	    struct stat finfo;
	    extern char *pw_real_name ();

	    uid = passwd_entry->pw_uid;

	    device_name = xmalloc (5 + strlen (result[i]->ttyname) + 1);

	    sprintf (device_name, "/dev/%s", result[i]->ttyname);

	    if (stat (device_name, &finfo) != -1)
	      {
#ifdef HAVE_ST_RDEV
		dev_t tty = finfo.st_rdev;
#else
		dev_t tty = finfo.st_dev;
#endif
		int file;

#ifdef _AIX
		if (! (finfo.st_mode & S_IWOTH))
#else /* _AIX */
#ifdef sun
		if (! (finfo.st_mode & S_IWGRP))
#else
		if (finfo.st_uid == passwd_entry->pw_uid)
#endif /* sun */
#endif /* _AIX */
		  strcat (result[i]->ttyname, "*");

#ifdef _AIX
		{
#define NPROCS 16384
		  static struct userinfo userinfo[NPROCS];
		  static int nproc = -1;
		  if (nproc == -1) {
		    struct procinfo procinfo[NPROCS];
		    int n;
		    nproc = getproc (procinfo, NPROCS,
				     sizeof (struct procinfo));
		    for (n = 0; n < nproc; n++)
		      getuser (&procinfo[n], sizeof (struct procinfo),
			       &userinfo[n], sizeof (struct userinfo));
		    qsort (userinfo, nproc, sizeof (struct userinfo),
			   aix_userinfo_sort);
		  }
		  strncpy (result[i]->what,
			   aix_userinfo_lookup (userinfo, nproc, tty,
						result[i]->ttyname),
			   sizeof (result[i]->what));
		}
#else /* !_AIX */
#ifdef HAVE_PROC_FS
		{
		  time_t latest = 0;

		  /* Loop process table until we find a match */
		  for (tableptr = proctable; tableptr < proctable + nprocs;
		       tableptr++)
		    {
		      /* UID doesn't really matter - should report setuid
			 processes as well. */

		      if (/* tableptr->uid == uid && */ tableptr->tty == tty
			  && tableptr->start > latest)
			{
			  latest = tableptr->start;
			  strncpy (result[i]->what, tableptr->fname,
				   sizeof result[i]->what);
			  result[i]->what[sizeof (result[i]->what) - 1] = 0;
			}
		    }
		}
#else /* ! HAVE_PROC_FS */

		if ((finfo.st_mode & S_IFCHR) &&
		    (stat (ACCT_FILE, &finfo) == 0) &&
		    ((file = open (ACCT_FILE, O_RDONLY)) >= 0))
		  {
		    struct acct entry[100];
		    long offset = finfo.st_size;
		    int val;

		    /* Scan the entries from last to first.  */
		    while (offset > 0)
		      {
			/* Read another 100 entries, or however many
			   are left if less than that.  */
			int size = sizeof (entry);

			if (offset < size)
			  size = offset;

			offset -= size;

			val = lseek (file, offset, L_SET);

			if (val < 0)
			  break;

			val = read (file, entry, size);

			if (val != size)
			  break;

			/* Scan the entries just read, last to first. */
			for (j = size / sizeof (struct acct) - 1; j >= 0; j--)
			  if ((entry[j].ac_tty == tty) &&
			      (entry[j].ac_uid == uid))
			    {
			      strncpy (result[i]->what, entry[j].ac_comm,
				       sizeof (entry[j].ac_comm));
			      result[i]->what[sizeof (entry[j].ac_comm) - 1]
				= '\0';
			      offset = 0;
			      break;
			    }
		      }
		    close (file);
		  }
#endif /* !HAVE_PROC_FS */
#endif /* !_AIX */
	      }

	    strncpy (result[i]->real_name, pw_real_name (passwd_entry),
		     sizeof result[i]->real_name);
	    free (device_name);
      	  }
	else
	  {
	    strcpy (result[i]->real_name, "");
	  }
      }
    }
#ifdef HAVE_PROC_FS
  free (proctable);
#endif

  result[i] = (FINGER_PACKET *)NULL;
  return (result);
}


/* Return an array of the users which are using this machine.  If UNIQUE
   is non-zero, we only return one entry per each user; the one with the
   least idle time.  The utmp entry which corresponds to the user who is
   on the console has its address remembered in CONSOLE_USER.  */

UTMP **
make_utmp_array (unique)
  int unique;
{
  register int i, n, file;
#ifdef HAVE_GETUTENT
#define UT(base, member)  (base->member)
#define UTSTRUCT(base) *(base)
  UTMP *entry;
#else
#define UT(base, member)  (base.member)
#define UTSTRUCT(base) (base)
  UTMP entry;
#endif

  UTMP **result;
  int result_size = 0;

#ifndef HAVE_GETUTENT
  file = open (UTMP_FILE, O_RDONLY);

  if (file < 0)
    {
      perror (UTMP_FILE);
      exit (1);
    }
#endif

  result_size = 20;

  result = (UTMP **) xmalloc ((result_size + 1) * sizeof (UTMP *));
  *result = (UTMP *) NULL;

  while (
#ifdef HAVE_GETUTENT
#ifdef HAVE_UTMPX_H
	 entry = (UTMP *) getutxent ()
#else
	 entry = (UTMP *) getutent ()
#endif
#else
	 read (file, &entry, sizeof (entry)) == sizeof (entry)
#endif
	 )
    {
      /* Hopefully the nonuser macro will eliminate the lossage we had with
	 all of the non-existant users laying around in the utmp file. */

#if defined (USER_PROCESS)
      if (UT (entry, ut_type) != USER_PROCESS)
	continue;
#endif /* USER_PROCESS */

#if defined (nonuser)
      if (nonuser (UTSTRUCT (entry)) || !UT (entry, ut_name)[0])
	continue;
#else
      if (!UT (entry, ut_name)[0])
	continue;
#endif /* sun */

      for (i = 0; result[i]; i++)
	{
	  /* If the same person is logged in more than once, we are
	     interested in the least idle time.  However, we want to
	     keep track of the console user. */
	  if (unique && !strncmp (result[i]->ut_name, UT (entry, ut_name),
				  sizeof (UT (entry, ut_name))))
	    {
	      if (idle_less_than (&UTSTRUCT (entry), result[i]))
		bcopy (&UTSTRUCT (entry), result[i], sizeof (UTSTRUCT (entry)));
	      break;
	    }
	}

      if (!result[i])
	{
	  result[i] = (UTMP *) xmalloc (sizeof (UTSTRUCT (entry)));
	  bcopy (&UTSTRUCT (entry), result[i], sizeof (UTSTRUCT (entry)));

	  /* If this user is using the console device remember that for
	     finding the tty location.  `v0' is what X window terminals
	     use for the console login window. */
	  if ((strcmp (result[i]->ut_line, "console") == 0) ||
	      (strcmp (result[i]->ut_line, "v0") == 0))
	    {
	      console_user = result[i];
	      strcpy (result[i]->ut_line, "console");
	    }

	  if (i + 1 >= result_size)
	    {
	      result = (UTMP **)
		xrealloc (result,
			  (result_size += 10) * sizeof (UTMP *));
	    }

	  result[i + 1] = (UTMP *) NULL;
	}
    }
#ifdef HAVE_GETUTENT
  endutent ();
#else
  close (file);
#endif

  return (result);
}


/* Returns non-zero if the utmp entry pointed to by A has been idle less
   time than the utmp entry pointed to by B. */
int
idle_less_than (a, b)
  UTMP *a, *b;
{
  return (get_last_access (a->ut_line) > get_last_access (b->ut_line));
}


/* Return a string which is the location of the terminal in utmp ENTRY. */
char *
tty_location (entry)
  UTMP *entry;
{
  char *result, *lookup_tty ();
#ifdef HAVE_UT_HOST
  char *hostname = "";
#endif

  /* If this user is the console user, then return that location. */
  if (!strcmp (entry->ut_line, "console"))
    {
      if (result = lookup_tty ("console"))
	return (result);

#ifdef HAVE_UT_HOST
      /* If console user, and there is no entry in the list for
	 `console', then try the local host. */

      if (hostname = xgethostname ())
	if (result = lookup_tty (hostname))
	  return result;
#endif
    }

  /* If there is an entry in the ttylocs file corresponding to this terminal,
     then use that one, regardless of where the user is actually logged in
     from. */
  if (result = lookup_tty (entry->ut_line))
    return (result);

#ifdef HAVE_UT_HOST
  /* If this user is remotely logged in, then the tty location is whatever
     that remote host is.  But, if there is a local entry in the table for
     that remote host, then use that entry. */
  if (entry->ut_host[0])
    {
      char host[LEN_HOST_FIELD + 1], *t = entry->ut_host;
      register int i;

      for (i = 0; i < LEN_HOST_FIELD; i++)
	{
	  host[i] = *t++;
	  if (!host[i] || host[i] == ':')
	    break;
	}
      host[i] = '\0';

      /* Watch out.  X11 `remote' entries can look like "unix:0.0".  In that
	 case, the name of the remote host is "unix", which I think is stupid.
	 So, if that is the case, then the host is the localhost. */
      if (!strcmp (host, "unix"))
	{
	  if (!hostname[0])
	    hostname = xgethostname ();

	  if (result = lookup_tty (hostname))
	    return (result);
	}

      /* Don't confuse the caller.  Make sure they can tell the location of
	 the host is referencing the host that is rlogging into this host.
	 An open paren as the first character of the hostname is a reasonable
	 solution. */
      {
	char *temp;
	static char *buffer = NULL;
	static int buffer_size = 128;

	result = lookup_tty (host);

	if (result)
	  {
	    temp = xmalloc (11 + LEN_HOST_FIELD + strlen (result));

	    sprintf (temp, "(%-7s %s)", host, result);
	  }
	else
	  {
	    temp = xmalloc (4 + LEN_HOST_FIELD);
	    bzero (temp, 4 + LEN_HOST_FIELD);
	    *temp = '(';
	    strncpy (temp + 1, entry->ut_host, LEN_HOST_FIELD);
	    strcat (temp, ")");
	  }
	
	if (!buffer)
	  buffer = xmalloc (buffer_size);
	
	if (buffer_size < strlen (temp) + 1)
	  {
	    buffer_size = strlen (temp) + 1;
	    buffer = xrealloc (buffer, strlen (temp) + 1);
	  }
	
	strcpy (buffer, temp);
	free (temp);
	return buffer;
      }
    }
  else
    {
      /* No remote host recorded - assume user is local */
      if ((hostname = xgethostname ())
	  && (result = lookup_tty (hostname)))
	return result;
	  
    }

#endif /* HAVE_UT_HOST */

  return "";
}

#if defined (NOTDEF)
#if defined (USG)
#define	MSEC	1000000

unsigned
ualarm (value, interval)
unsigned value;
unsigned interval;
{
    struct itimerval new, old;

    new.it_value.tv_usec    = value % MSEC;
    new.it_value.tv_sec     = value / MSEC;

    new.it_interval.tv_usec = interval % MSEC;
    new.it_interval.tv_sec  = interval / MSEC;
	
    /*
     * How do we know a -1????
     */
    return  (unsigned) (setitimer(ITIMER_REAL, &new, &old) == 0 ?
	old.it_value.tv_sec * MSEC + old.it_value.tv_usec : -1);
}

#endif /* USG */

/*
 * BSD functionality needs BSD signals
 */

#ifdef hpux
#define _sigvec_
#endif /* hpux */

#ifdef aiws
#define _sigvec_
#endif /* aiws */

#ifdef _sigvec_
#include <signal.h>
/* Turn into BSD signals. */
void (*
signal (s, a))()
     int s;
     void (*a)();
{
  struct sigvec osv, sv;
  static int first_time = 1;

  if (first_time)
    {
#ifdef aiws
      struct sigstack inst;
      inst.ss_sp = xmalloc (4192) + 4192;
      inst.ss_onstack = 0;
      sigstack (&inst, (struct sigstack *) 0);
#endif				/* aiws */
#ifdef hpux
      sigspace (4192);
#endif				/* hpux */
      first_time = 0;
    }
	
#ifndef hpux
  sigvec(s, 0, &osv);
#else
  sigvector(s, 0, &osv);
#endif				/* aiws */
  sv = osv;
  sv.sv_handler = a;
#ifdef aiws
  sv.sv_onstack = SIG_STK;
#endif
#ifdef hpux
  sv.sv_flags = SV_BSDSIG | SV_ONSTACK;
#endif				/* hpux */

#ifndef hpux
  if (sigvec (s, &sv, 0) < 0)
#else
    if (sigvector (s, &sv, 0) < 0)
#endif				/* aiws */
      return (BADSIG);
  return (osv.sv_handler);
}
#endif /* _sigvec_ */
#endif /* NOTDEF */
