/*--------------------------------*-C-*---------------------------------*
 * File:	utmp.c
 *
 * Public:
 *	extern void cleanutent (void);
 *	extern void makeutent (const char * pty, const char * hostname);
 *
 * Private:
 *	get_tslot ();
 *	write_utmp ();
 *	write_wtmp ();
 *
 * As usual, the author accepts no responsibility for anything, nor does
 * he guarantee anything whatsoever.
 * ---------------------------------------------------------------------*
 *
 * Extensive modifications by mj olesen <olesen@me.QueensU.CA>
 * No additional restrictions are applied.
 *
 * Revision 1.6  07DEC95:16	Piet W. Plomp,
 * wtmp support for (at least linux)
 *
 * Revision 1.55  1995/10/16  16:04:16  rgg
 * now works in Solaris 2.x and Linux 1.2.x
 *
 * Revision 1.5 08/09/1993 stempien
 * Something was wrong with the Linux support!
 * I fixed it using the provided utmp manipulation routines.
 * I also surrounded many varables that were not needed for Linux
 * in the BSD defines to reduce the memory needed to run.
 * I didn't touch the Sun part of the code so it should still work.
 *
 * Revision 1.4  1993/08/09  11:54:15  lipka
 * now works both on Linux and SunOs 4.1.3.
 * Brians clean-ups incorporated
 *
 * Revision 1.3  1993/08/09  07:16:42  lipka
 * nearly correct (running) linux-version.
 * Delete-Window is still a problem
 *
 * Revision 1.1  1993/08/07  18:03:53  lipka
 * Initial revision
 *
 * Clean-ups according to suggestions of Brian Stempien <stempien@cs.wmich.edu>
 *
 *    Bugs:
 *	UTMP should be locked from call to utmp_end() 'til makeutent() (?).
 *	Maybe the child should tell the parent, where the entry is made.
 *	Tested only on Linux.
 *
 *	Gives weird inital idle times. They go away after one uses the
 *	window, but......
 * ------------------------------------------------------------------------
 * This version has SYSV wtmp support for (at least Linux) added by:
 *    Piet W. Plomp,
 *    ICCE - Institute for educational technology,
 *    State University of Groningen, The Netherlands,
 *    <piet@icce.rug.nl> or (faster) <piet@idefix.icce.rug.nl>
 *
 * all WTMP specific code is #ifdef'd WTMP_SUPPORT, which currently depends
 * on UTMP_SUPPORT.
 * This code is valid and tested on linux (libc.so.4.6.27), but is
 * POSIX compliant and should work on SYSVR4, except possibly the use
 * of EAGAIN, which may be called EACCESS in SVR4. Linux has only EAGAIN,
 * POSIX allows either.
 *
 * My additions are tagged with an entry like "... pwp 95-12-07", where the
 * former are my initials and the latter the date in yy-mm-dd format.
 *----------------------------------------------------------------------*/
#include <config.h>
#include "feature.h"

#ifndef UTMP_SUPPORT
/* Dummy routines if utmp support isn't wanted */
void cleanutent (void) {}
void makeutent (const char * pty, const char * hostname) {}
#else /* UTMP_SUPPORT */

#include <stdio.h>
#include <string.h>

#ifdef HAVE_UTMPX_H
# include <utmpx.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>

#ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# ifdef HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#include <utmp.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#ifdef HAVE_LASTLOG_H
# include <lastlog.h>
#endif
#include <pwd.h>

/* WTMP_SUPPORT added pwp 95-12-07 */
#define WTMP_SUPPORT

#include <errno.h>
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif
#ifdef EACCESS
# ifndef EAGAIN
#  define EAGAIN EACCESS
# endif
#endif

#ifndef UTMP_FILENAME
# ifdef UTMP_FILE
#  define UTMP_FILENAME UTMP_FILE
# else
#  ifdef _PATH_UTMP
#   define UTMP_FILENAME _PATH_UTMP
#  else
#   define UTMP_FILENAME "/etc/utmp"
#  endif
# endif
#endif

#ifndef LASTLOG_FILENAME
# ifdef _PATH_LASTLOG
#  define LASTLOG_FILENAME _PATH_LASTLOG
# else
#  define LASTLOG_FILENAME "/usr/adm/lastlog"  /* only on BSD systems */
# endif
#endif

#ifndef WTMP_FILENAME
# ifdef WTMP_FILE
#  define WTMP_FILENAME WTMP_FILE
# else
#  ifdef _PATH_WTMP
#   define WTMP_FILENAME _PATH_WTMP
#  else
#   ifdef SYSV
#    define WTMP_FILENAME "/etc/wtmp"
#   else
#    define WTMP_FILENAME "/usr/adm/wtmp"
#   endif
#  endif
# endif
#endif

#ifndef TTYTAB_FILENAME
# ifdef TTYTAB
#  define TTYTAB_FILENAME TTYTAB_FILENAME
# else
#  define TTYTAB_FILENAME "/etc/ttytab"
# endif
#endif

#ifndef USER_PROCESS
# define USER_PROCESS 7
#endif
#ifndef DEAD_PROCESS
# define DEAD_PROCESS 8
#endif

/*----------------------------------------------------------------------*
 * extern functions referenced
 */
/*----------------------------------------------------------------------*
 * extern variables referenced
 */
/*----------------------------------------------------------------------*
 * extern variables declared here
 */
/*----------------------------------------------------------------------*
 * local variables
 */
static unsigned char utmp_made = 0;	/* marks if an entry has been made */

/*----------------------------------------------------------------------*
 * local functions referenced
 */

/*----------------------------------------------------------------------*/

/*
 * HAVE_SETUTENT corresponds to SYSV-style utmp support.
 * Without it corresponds to using BSD utmp support.
 *
 * SYSV-style utmp support is further divided in normal utmp support
 * and utmpx support (Solaris 2.x) by HAVE_UTMPX_H
 */
#ifdef HAVE_UTMPX_H
# ifndef WTMPX_FILE
Error WTMPX_FILE must be defined on Solaris2.x
# endif
/*
 * write a utmp entry to the utmp file
 */
static void
write_utmp (struct utmpx * ux)
{
   struct utmp ut;

   utmpname (UTMP_FILENAME);
   getutmp (ux, &ut);
   pututline (&ut);
   pututxline (ux);
   updwtmpx (WTMPX_FILE, ux);
   endutent ();
   utmp_made = 1;
}

/*
 * make a utmp entry
 */
void
makeutent (const char * pty, const char * hostname)
{
   struct passwd * pwent = getpwuid (getuid ());
   struct utmpx ux;
   int num;

   memset (&ux, 0, sizeof(struct utmpx));

   /* get the host: */
   strncpy (ux.ut_host, hostname, sizeof(ux.ut_host));
   ux.ut_syslen = strlen (ux.ut_host) + 1;

   /* and now the line: */
   if (sscanf (pty, "/dev/pts/%d", &num) != 1)
     {
	print_error ("can't parse tty name \"%s\"", pty);
	return;
     }
   sprintf (ux.ut_line, "pts/%d", num);
   sprintf (ux.ut_id, "vt%02x", num);

   time (&ux.ut_xtime);
   strncpy (ux.ut_user, pwent->pw_name, sizeof(ux.ut_user));
   ux.ut_type = USER_PROCESS;
   ux.ut_pid = getpid ();

   write_utmp (&ux);
}

/*
 * remove a utmp entry
 */
void
cleanutent (void)
{
   struct utmp ut;

   if (!utmp_made)
     return;

   utmpname (UTMP_FILENAME);
   setutent ();
   if (getutid (&ut) == NULL)
     return;
   ut.ut_type = DEAD_PROCESS;
   time (&ut.ut_time);
   pututline (&ut);
   updwtmp (WTMP_FILENAME, &ut);
}
#else	/* not HAVE_UTMPX_H */

#ifdef HAVE_SETUTENT		/* SYSV utmp support */
/*
 * write a utmp entry to the utmp file
 */
static void
write_utmp (struct utmp * ut)
{
   utmpname (UTMP_FILENAME);
   setutent ();
   pututline (ut);
   endutent ();
   utmp_made = 1;
}

/*
 * write_wtmp added by pwp 95 12 07
 * called by makeutent() and cleanutent() below
 */
static void
write_wtmp (struct utmp * wt)
{
# ifdef WTMP_SUPPORT
   int fd, retry = 10;		/* 10 attempts at locking */
   struct flock lck;		/* fcntl locking scheme */

   if ((fd = open (WTMP_FILENAME, O_WRONLY|O_APPEND, 0)) < 0)
     return;

   lck.l_whence = SEEK_END;	/* start lock at current eof */
   lck.l_len    = 0;		/* end at ``largest possible eof'' */
   lck.l_start  = 0;
   lck.l_type   = F_WRLCK;	/* we want a write lock */

   /* attempt lock with F_SETLK - F_SETLKW would cause a deadlock! */
   while (retry--)
     if ((fcntl (fd, F_SETLK, &lck) < 0) && errno != EAGAIN) {
	close (fd);
	return;			/* failed for unknown reason: give up */
     }
   write (fd, wt, sizeof(struct utmp));

   /* unlocking the file */
   lck.l_type = F_UNLCK;
   fcntl (fd, F_SETLK, &lck);

   close (fd);
# endif	/* WTMP_SUPPORT */
}

/*
 * make a utmp entry
 */
void
makeutent (const char * pty, const char * hostname)
{
   struct passwd * pwent = getpwuid (getuid ());
   struct utmp ut;

   memset (&ut, 0, sizeof(struct utmp));

   strncpy (ut.ut_host, hostname, sizeof(ut.ut_host));

   if (!strncmp (pty, "/dev/", 5)) pty += 5;
   strncpy (ut.ut_line, pty, sizeof(ut_line));
   strncpy (ut.ut_id, (pty+3), 2);

   time (&ut.ut_time);
   strncpy (ut.ut_user, pwent->pw_name, sizeof(ut.ut_user));
   ut.ut_type = USER_PROCESS;
   ut.ut_pid = getpid ();

   write_utmp (&ut);
   write_wtmp (&ut);
}

/*----------------------------------------------------------------------*
 * remove a utmp entry
 *
 * there is a nice function "endutent" defined in <utmp.h>;
 * like "setutent" it takes no arguments, so I think it gets all information
 * from library-calls.
 * That's why "setutent" has to be called by the child-process with
 * file-descriptors 0/1 set to the pty. But that child execs to the
 * application-program and therefore can't clean it's own utmp-entry!(?)
 * The master on the other hand doesn't have the correct process-id
 * and io-channels... I'll do it by hand:
 * (what's the position of the utmp-entry, the child wrote? :-)
 *----------------------------------------------------------------------*/
void
cleanutent (void)
{
   int pid;
   struct utmp * ut;

   if (!utmp_made)
     return;

   utmpname (UTMP_FILENAME);
   setutent ();
   pid = getpid ();
   /* The following 11 lines of code were copied from the
    * poeigl-1.20 login/init package. Special thanks to
    * poe for the code examples.
    */
   while ((ut = getutent ()))
     {
	if (ut->ut_pid == pid)
	  {
	     time (&ut->ut_time);
	     memset (&ut->ut_user, 0, sizeof(ut->ut_user));
	     memset (&ut->ut_host, 0, sizeof(ut->ut_host));
	     ut->ut_type = DEAD_PROCESS;
	     ut->ut_pid = 0;
# if !defined (_AIX) && !defined (__svr4__)
	     ut->ut_addr = 0;
# endif
	     pututline (ut);
	     endutent ();
             write_wtmp (ut);	/* add empty entry to wtmp: line has closed */
	  }
     }
}

#else	/* HAVE_SETUTENT */

/*
 * BSD utmp support
 */
static int utmp_pos = 0;		/* position of utmp-stamp */

/*----------------------------------------------------------------------*
 * get_tslot() - grabbed from xvt-1.0 - modified by David Perry
 *
 * find ttyname in /etc/ttytab and return a slot number that can be used to
 * access the utmp file.  Can't use ttyslot() because the tty name is not
 * that of fd 0.
 *----------------------------------------------------------------------*/
static int
get_tslot (const char * ttyname)
{
   FILE *fd;
   char buf [256], name [256];

   if ((fd = fopen (TTYTAB_FILENAME, "r")) != NULL)
     {
	int i;
	for (i = 1; fgets (buf, sizeof(buf), fd) != NULL; i++)
	  {
	     if (*buf == '#' || sscanf (buf, "%s", name) != 1)
	       continue;
	     if (!strcmp (ttyname, name))
	       {
		  fclose (fd);
		  return i;
	       }
	  }
	fclose (fd);
     }
   return -1;
}

/*
 * make a utmp entry
 */
void
makeutent (const char * pty, const char * hostname)
{
   struct passwd * pwent = getpwuid (getuid ());
   struct utmp ut;
   FILE *fd;

   memset (&ut, 0, sizeof(struct utmp));

   strncpy (ut.ut_host, hostname, sizeof(ut.ut_host));

   if (!strncmp (pty, "/dev/", 5)) pty += 5;
   strncpy (ut.ut_line, pty, sizeof(ut.ut_line));
   strncpy (ut.ut_id, (pty+3), 2);

   time (&(ut.ut_time));
   strncpy (ut.ut_name, pwent->pw_name, sizeof(ut.ut_name));

   /*
    * write utmp entry to UTMP_FILENAME
    */
   if ((fd = fopen (UTMP_FILENAME, "r+")) != NULL)
     {
	utmp_pos = get_tslot (pty) * sizeof(struct utmp);
	if (utmp_pos >= 0)
	  {
	     fseek (fd, utmp_pos, 0);
	     fwrite (&ut, sizeof(struct utmp), 1, fd);
	     utmp_made = 1;
	  }
	fclose (fd);
     }
}

/*
 * remove a utmp entry
 */
void
cleanutent (void)
{
   FILE *fd;
   struct utmp ut;
   memset (&ut, 0, sizeof(struct utmp));

   if (!utmp_made || (fd = fopen (UTMP_FILENAME, "r+")) == NULL)
     return;
   fseek (fd, utmp_pos, 0);
   fwrite (&ut, sizeof(struct utmp), 1, fd);
   fclose (fd);
}
#endif	/* HAVE_SETUTENT */
#endif	/* HAVE_UTMPX_H */
#endif	/* UTMP_SUPPORT */
/*----------------------- end-of-file (C source) -----------------------*/
