/*
 * @(#) utmp.c  RCS: $Revision: 1.8 $ $Date: 95/03/08 01:13:19 $
 *
 * This file contains machine specific code for making sure getlogin() works.
 * See the function setlogin below.
 *
 * Make sure login database is updated getut(3c), but this is not 
 * available in POSIX, or SYSV XPG3; it's only a SYSV XPG2 feature.   
 * There thus is no machine independent way to make getlogin work.
 *
 * There are two distinct flavors of utmp.h files:
 *
 * System V style:  SGI's IRIX, Solaris, OSF1, HP-UX, UNICOS
 #    #define ut_name	ut_user
 *    struct utmp { 
 *	 char[] ut_user, ut_id, ut_line;
 *	 short ut_pid, ut_type;
 *	 struct { short e_termination; short e_exit } ut_exit;	except linux
 *	 time_t ut_time;
 *       char ut_host[]; // HP-UX, OSF1, UNICOS linux only !Solaris <utmp.h>
 *    }
 *
 * UTMPX style: Solaris	<utmpx.h> (*almost* the same as system V style!)
 *     struct utmpx {			<- different name
 *        char[] ut_user, ut_id, ut_line;
 *        pid_t ut_pid;
 *        short ut_type;
 *        struct exit_status ut_exit;	<- same
 *        struct timeval ut_tv;		<- different! (BSD style timeofday)
 *        long  ut_session;
 *        short ut_syslen;
 *        char ut_host[];
 *     }
 *
 *  BSD style:	Ultrix, SunOS4.2, UNICOS
 *     struct utmp {
 *	   char ut_line[8], ut_name[8], ut_host[16];
 *	   long ut_time;
 *     }
 * 
 * The least-common denominator of utmp entries:
 *     char ut_line[] 	  device name without path component (e.g. "ttyp4")
 *     			  8 chars on BSD, 12 on hp-ux, longer on osf1
 *     time_t ut_time	  time entry was made (long on BSD)
 *     char   ut_name[8]  login name of the user
 *
 * The following field is not present on SGI's IRIX and Solaris.
 *     char   ut_host[16] host name if remote
 *
 * Unfortunately, BSD and System V use different names for the field containing
 * the user login name (ut_name, ut_user respectively).  Fortunately, all
 * SYSV machines I can find have a #define for ut_name.
 *
 * For BSD, empty slots are indicated by ut_name is a null string.
 * Remote users are indicated by a non-null ut_host field.
 *
 * For System V, empty slots are indicated by ut_type field as 
 * DEAD_PROCESS. All other fields must be valid as usual.
 *
 * Solaris has two utmp files with different struct formats:
 *    /var/adm/utmp  based on <utmp.h>  using struct utmp.	UTMP_FILE
 *    /var/adm/utmpx based on <utmpx.h> using struct utmpx.	UTMPX_FILE
 *
 *    Changes UTMPX_FILE will not be noticed by the "last" and "who" commands 
 *    unless UTMP_FILE is also updated.  UTMPX_FILE has an rhost field.
 */
#define _POSIX_SOURCE	/* this isn't quite posix source */

/*
 * This is a mess.  HP-UX ANSI C won't define pid_t in <sys/types.h> unless
 * _POSIX_SOURCE is defined.  Other machines may refuse to define it.  Pick
 * what's appropriate.  <utmp.h> uses pid_t.
 */
#if defined(__SOLARIS__) /* { */
#undef _POSIX_SOURCE	/* allows struct timeval to be defined */
#undef _POSIX_C_SOURCE
#include <sys/types.h>	/* uid_t size_t */
#include <time.h>	/* time (also needed for "utmp.h") */
#define _POSIX_SOURCE
#define _POSIX_C_SOURCE
#else  /* } { */
#include <sys/types.h>	/* uid_t size_t */
#include <time.h>	/* time (also needed for "utmp.h") */
#endif /* } */

#include <unistd.h>	/* open read write lseek getuid */
#include <stdio.h>
#include <fcntl.h>	/* O_RDWR */
#include <string.h>	/* strchr memset */
#include <pwd.h>	/* struct passwd, getpwuid */


#define OFFSET(s, m)	((unsigned) &(((s *)0)->m))

#include "utmp.h"	/* UTMP_FILE WTMP_FILE <utmp.h> or <utmpx.h> */
#include "log.h"
#include "deslogin.h"	/* progName ERRMSG */

/*
 * Update the system's "currently logged in users" file (used by "who")
 *
 * Input:
 *    utmp_file 	- the name of the file to update
 *    ut		- address of the record insert into file
 *    utbuf		- storage for a temporary buffer
 *    utsz		- the size of the ut and utbuf record's storage
 *    lineoff		- the offset into the record for the ut_line field
 *    linesz		- the size of the ut_line field
 *
 * Must minimize the time the file is open to prevent write conflict with
 * other processes trying to update utmp.  
 */
int update_utmp(utmp_file, ut, utbuf, utsz, lineoff, linesz)
   char *utmp_file;
   void *ut, *utbuf;
   unsigned utsz, lineoff, linesz;
{
   int fd, count, res = -1;
   long offset = 0, lres;

   fd = open(utmp_file, O_RDWR);
   if (fd < 0) {
      log("%s: (update_utmp) open(\"%s\", O_RDWR) failed -- %s\n", 
	 progName, utmp_file, ERRMSG);
      return -1;
   }

   /* 
    * Find offset of entry with the same line value if present
    */
   while ((count = read(fd, utbuf, utsz)) == utsz) {
      if (strncmp((char *)ut+lineoff, (char *)utbuf+lineoff, linesz) == 0) {
	 break;
      }
      offset += count;
   }
   if (count < 0) {
      log("%s: (update_utmp) read failed from \"%s\" -- %s\n", 
	 progName, utmp_file, ERRMSG);
      goto utmp_failed;
   }

   lres = lseek(fd, (off_t) offset, SEEK_SET);
   if (lres < 0) {
      log("%s: (update_utmp) lseek to offset %ld failed in \"%s\" -- %s\n", 
	 progName, offset, utmp_file, ERRMSG);
      goto utmp_failed;
   }

   count = write(fd, (char *) ut, utsz);
   if (count < 0) {
      log("%s: (update_utmp) write failed to \"%s\" -- %s\n", 
	 progName, utmp_file, ERRMSG);
      goto utmp_failed;
   }
   if (count != utsz) {
      log("%s: (update_utmp) short write to \"%s\"\n", progName, utmp_file);
      goto utmp_failed;
   }
   res = 0;	/* success */
utmp_failed:
   close(fd);
   return res;
}

/*
 * Update the system login history file (used by "last" and "lastb")
 *
 * Must minimize the time the file is open to prevent write conflict with
 * other processes trying to update utmp.  
 */
int update_wtmp(wtmp_file, ut, utsz)
   char *wtmp_file;
   void *ut;
   unsigned utsz;
{
   int fd, count, res = -1;

   fd = open(wtmp_file, O_WRONLY | O_APPEND);
   if (fd < 0) {
      log("%s: (update_wtmp) open(\"%s\", O_WRONLY|O_APPEND) failed -- %s\n", 
	 progName, wtmp_file, ERRMSG);
      return -1;
   }
   count  = write(fd, ut, utsz);
   if (count < 0) {
      log("%s: (update_wtmp) write failed to \"%s\" -- %s\n", 
	 progName, wtmp_file, ERRMSG);
      goto wtmp_failed;
   }
   if (count != utsz) {
      log("%s: (update_wtmp) short write to \"%s\"\n", progName, wtmp_file);
      goto wtmp_failed;
   }
   res = 0;	/* success */
wtmp_failed:
   close(fd);
   return res;
}

/*
 * The inverse of the POSIX getlogin call.  Setup the login for the given
 * user name.
 *
 * Input:
 *    tty   = the device file assigned as the controlling terminal
 *    name  = the login name (only first 8 characters signifacant)
 *    rhost = the hostname of the remote host (only 1st 16 chars used)
 *    statloc = the address of the process exit status
 *              (if zero, then this is a new process stating up)
 *
 * The calling process should ensure that it owns the tty before it calls
 * this routine, or it could overwrite a legitimate processes tty.
 *
 * Returns: 0 success, -1 failure.
 */
int setlogin(tty, username, rhost, statloc)
   char *tty, *username, *rhost;
   int *statloc;
{
   char         *line, *id;
   long         offset;
   int          res;
   struct utmp  ut, utbuf;
#if defined(UTMPX_FILE) 	/* { */
   struct utmpx utx, utxbuf;
#endif /* } */

   if ((tty == 0) || (username == 0) || (rhost == 0)) return -1;

   /*
    * There are two possibly conflicting ways to get the line:
    *   1) take the last component of the path (SYSV) (e.g. fails for /dev/pts/6)
    *   2) Strip of the first component of the path. (irix) (/dev/pts/6 = pts/6)
    *   3) Don't strip any components at all (Solaris)
    *
    * Which one is the most "popular"?  Probably should be a compile-time test.
    * I'm using the last component of the path.
    */
   line = strchr(tty, '/');
   if (line == tty) {
      line = strchr(tty + 1, '/');
      if (line == (char *) 0) line = tty;
   }
   if (line == (char *) 0) line = tty; else line++;

   /*
    * The id field (for init) is the last two characters of the line on SYSV
    * For SOLARIS, id is prefixed by two character service abbreviation.
    *    (e.g. Telnet is 'tn', rlogin is 'rl')
    * I'm using the last two characters of the line.
    */
   offset = strlen(line) - 2;
   if (offset < 0) offset = 0;
   id = line + offset;

#if defined(UTMPX_FILE) 	/* { */
   memset(&utx, '\0', sizeof utx);
#endif /* } */
   memset(&ut, '\0', sizeof ut);
   time((time_t *) &ut.ut_time);
   strncpy(ut.ut_line, line,     sizeof ut.ut_line);
   strncpy(ut.ut_name, username, sizeof ut.ut_name); 
#if defined(USER_PROCESS)		/* { SYSV */
   strncpy(ut.ut_id,    id,      sizeof ut.ut_id);
   ut.ut_pid  = getpid();
   ut.ut_type = USER_PROCESS;		/* assume login until proven othewise */
   if (statloc != 0) {		/* process exit */
      ut.ut_type = DEAD_PROCESS;
#if !defined(_LINUX_SOURCE) /* { */
      ut.ut_exit.e_termination = *statloc & 0xff;
      ut.ut_exit.e_exit        = (*statloc >> 8) & 0xff;
#endif /* } */
   }
#else 	/* } { BSD */
   if (statloc != 0) {
      ut.ut_name[0] = '\0';
   }
#endif	/* } BSD */

#if !defined(__sgi) && !defined(__DYNIX__) && !defined(__SOLARIS__) /* { */
   strncpy(ut.ut_host, rhost, sizeof ut.ut_host);
#endif /* } */

   /*
    * This used to be broken for __DYNIX__ and __SOLARIS__.
    * Version 1.3 fixed this.  If not, mail barrett@asgard.cs.colorado.edu
    */
   res = update_utmp(UTMP_FILE, &ut, &utbuf, sizeof ut, 
                     OFFSET(struct utmp, ut_line), sizeof ut.ut_line);
   if (res >= 0) {
      res = update_wtmp(WTMP_FILE, &ut, sizeof ut); 
   }

#if defined(UTMPX_FILE) /* { __SOLARIS__ <utmpx.h> */
   getutmpx(&ut, &utx);		/* no return result */
   gettimeofday(&utx.ut_tv);	/* shouldn't be necessary; do it anyway */
   strncpy(utx.ut_host, rhost, sizeof utx.ut_host);

   if (res >= 0) {
      res = update_utmp(UTMPX_FILE, &utx, &utxbuf, sizeof utx, 
	                OFFSET(struct utmpx, ut_line), sizeof utx.ut_line);
   }
   if (res >= 0) {
      res = update_wtmp(WTMPX_FILE, &utx, sizeof utx); 
   }
#endif /* } */

   return res;
}

/*
 * Because of severe braindamage, the POSIX getlogin() call has a 
 * high-probability of failing to work on many machines.  The things to
 * consider are:
 *
 * This process must have a controlling terminal to determine the tty line
 * to lookup in utmp.
 *
 * There must be a (unique) entry for that line in utmp.
 * 
 * There is no guarantee that the contents of utmp is correct. (It's world
 * writable on most SUN BSD machines for example!  Thus, the login name 
 * returned there may not be correct at all.
 *
 * cuserid() is obsolete.  The problem is that it could return the login
 * name corresponding to the effective user id instead of the real user id.
 *
 * There may be multiple user names corresponding to each user id.  
 *
 * The bottom line is that there is *no way* to accurately determine which
 * of the (possibly many) login names were used to get this particular real
 * user id.
 *
 * I've decided not to trust utmp at all, and just to get the first valid
 * user id using the POSIX.1 conformant calls to <pwd.h>, getpwuid.
 *
 * Note that the only "portable" fields of a struct passwd are:
 *
 *    char *pw_name
 *    char *pw_passwd
 *    uid_t pw_uid
 *    gid_t pw_gid
 *
 * The only way this routine should fail (returning (char *) 0) is if 
 * /etc/passwd is not readable.  Note that yellow-pages lookups could occur
 * as a result of using this function.
 *
 * AnnexB.4.2.4:2682-2694
 * The getlogin() function returns a pointer to the user's login name.  The
 * same user ID may be shared by several login names.  If it is desired to get
 * the user database entry that is used during login, the result of getlogin()
 * should be used to provide the argument to the getpwnam() function.  (This
 * might be  used to determine the user's login shell, particularly where a
 * single user has multiple login shells with distinct login names, but the
 * same user ID.)
 *    The information provided by the cuserid() function, which was originally
 * defined in IEEE Std 1003.1-1990 and subsequently removed, can be obtained
 * by the following:  getpwuid(geteuid()), while the information provided by
 * historical implementations of cuserid() can be obtained by:
 * getpwuid(getuid()).
 */
char *getLoginName() {
   register uid_t uid = getuid();
   register struct passwd *pwent;
   
   pwent = getpwuid(uid);
   if (pwent == (struct passwd *) 0) {
      return (char *) 0;
   } else {
      return pwent->pw_name;
   }
}
