#include <AL/AL.h>
#include <sys/param.h>

#ifndef UTMP_FILE
#define UTMP_FILE _PATH_UTMP
#endif
#ifndef WTMP_FILE
#define WTMP_FILE _PATH_WTMP
#endif

long
ALinitUtmp(ALsession session)
{
  ALutFlagClear(session, ALflagAll);
  memset(&session->ut, 0, sizeof(session->ut));
}

/* Type is ALutLOGIN or ALutUSER. telnetd, dm, and nanny
   would use LOGIN, while xlogin and login would use USER.

   id seems to be open to interpretation; everyone does something
   different.

	Irix uses the last two characters of the pty (q9).
	Solaris uses tn**, where ** is generated by makeutx.
	What AIX does I don't know yet.
	What we are doing under AIX is unmentionable.

   No children appear to actually search on the id field; they
   all search on the line field.

   When overwriting a parent entry (ALutUSER) and no ID has been
   set, the id field of the overwritten entry is preserved.

   Pass tty name without /dev/ into line.

   Pass DISPLAY into host if doing an X login on the local host,
   or nothing if the host is local and not X, the host if the
   login is remote.
*/
long
ALsetUtmpInfo(ALsession session, ALflag_t flags, ALut *ut)
{
  if (flags & ALutUSER)
    strncpy(ALut_user(session), ut->user, sizeof(ALut_user(session)));

  if (flags & ALutHOST)
    {
      strncpy(ALut_host(session), ut->host, sizeof(ALut_host(session)));
#ifdef UTMPX_FILE
      ALut_syslen(session) = strlen(ALut_host(session)) + 1;
      if (ALut_syslen(session) == 1)
	ALut_syslen(session) = 0;
#endif
    }

  if (flags & ALutLINE)
    strncpy(ALut_line(session), ut->line, sizeof(ALut_line(session)));

#if !defined(BSD) && !defined(ultrix)
  if (flags & ALutID)
    strncpy(ALut_id(session), ut->id, sizeof(ALut_id(session)));

  if (flags & ALutTYPE)
    ALut_type(session) = ut->type;
#endif

  /* All platforms need this variable; we need to be able to tell
     whether or not we should write to wtmp. */
  if (flags & ALutTYPE)
    ALut_auxtype(session) = ut->type;

  ALutFlagSet(session, flags);
  return 0L;
}

/*
 * Lots of applications might want to do this. Additionally, on
 * some platforms ttyname() may not really give us what we want,
 * so that could potentially be accounted for here, and then we
 * can keep still more ugly ifdefs out of other code. (RS/6000
 * seems to return /dev/pts/0, which seems to disagree from what
 * telnetd is using.)
 */ 
long
ALsetTtyFd(ALsession sess, int fd)
{
  char *tty;

  tty = ttyname(fd);

  if (tty == NULL || strncmp(tty, "/dev/", 5))
    return(ALerrNoTtyName);
  
  strncpy(ALut_line(sess), tty + 5, sizeof(ALut_line(sess)));
  ALutFlagSet(sess, ALutLINE);
  return 0L;
}

/*
 * Note that "logging out" requires both changing the user field on
 * the appropriate line, and changing the type from USER_PROCESS to
 * something else. Changing the user field is required for last(1)
 * to correctly interpret the wtmp entry as a logout. Changing the type
 * field is required to prevent programs which interpret utmp from
 * showing the entry as a logged-in user. Except under the Irix; for some
 * reason, they added a "feature" to last. In order to not show up
 * when running last, you need to put "LOGIN" (or "telnet") in the
 * user field. I'm not sure yet what happens if your username happens
 * to be telnet.
 *
 * If the process logging the user out of utmp is going to exit, it
 * should change the type to DEAD_PROCESS. If not, it can probably
 * change the type back to LOGIN_PROCESS.
 */
long
ALputUtmp(ALsession sess)
{
#ifdef UTMPX_FILE
  struct utmpx *ut;
  char line[sizeof(ut->ut_line)];
#else
  struct utmp utwrite;
  struct utmp *ut, *utsrc;
  int f, t;

  utsrc = &(sess->ut);
#endif

  /*
   * Set default values for the common case.
   *   The line field is set to the return value of ttyname on
   *     stdin, stripped of /dev/.
   *   The user field is lifted from the session structure.
   *   The type field is set to USER_PROCESS.
   *   The id field is set to "XX" if the device was ttyXX, otherwise
   *     set to the first two characters.
   *
   * It's a good idea to set a plausible id all the time just in
   * case we're intending to overwrite a particular line that isn't
   * active (either is DEAD or does not exist). This way we don't
   * create extra garbage in utmp and have a better chance at being
   * cleaned up by someone else using the same line later.
   *
   * Other fields don't really have appropriate default values.
   */

  if (!ALutIsSet(sess, ALutLINE))
    {
      /*
       * This code supports setting entries for specific lines only.
       * If there isn't one set, and we can't figure it out, give up.
       */
      if (ALsetTtyFd(sess, fileno(stdin)))
	return(ALerrNoLineSet);
    }

  if (!ALutIsSet(sess, ALutUSER))
    strncpy(ALut_user(sess), ALpw_name(sess), sizeof(ALut_user(sess)));

  if (!ALutIsSet(sess, ALutTYPE))
    ALut_auxtype(sess) = ALutUSER_PROC;

#if !defined(BSD) && !defined(ultrix)
  if (!ALutIsSet(sess, ALutTYPE))
    ALut_type(sess) = ALutUSER_PROC;

  if (!ALutIsSet(sess, ALutID))
    {
      if (!strncmp("tty", ALut_line(sess), 3))
	{
	  strncpy(ALut_id(sess), ALut_line(sess)+3, sizeof(ALut_id(sess)));
	  ALutFlagSet(sess, ALutID);
	}
      else
	{
	  strncpy(ALut_id(sess), "", sizeof(ALut_id(sess)));
	  strncpy(ALut_id(sess), ALut_line(sess), 2);
	}
    }
#endif

  /*
   * Fill in the current time and pid.
   */

#ifdef UTMPX_FILE
  gettimeofday(&ALut_tv(sess));
#else
  time(&ALut_time(sess));
#endif

#if !defined(BSD) && !defined(ultrix)
  ALut_pid(sess) = getpid();
#endif

  /*
   * Now all of the fields of whatever utmp type structure
   * we're using have been initialized as far as possible.
   * It's time to talk to the utmp file.
   *
   * The first block of code handles writing to BSD type utmps
   * (with ttyslot). Later we handle the SYSV type.
   */

#if defined(BSD) || defined(ultrix)
  /* BSD has no mapping for LOGIN_PROC. */
  if (ALut_auxtype(sess) == ALutUSER_PROC ||
      ALut_auxtype(sess) == ALutDEAD_PROC)
    {
      if (ALut_auxtype(sess) == ALutDEAD_PROC)
	{
	  memcpy(&utwrite, &(sess->ut), sizeof(utwrite));
	  memset(&utwrite.ut_name, 0, sizeof(utwrite.ut_name));
	  utsrc = &utwrite;
	}

      t = ttyslot();
      if (t > 0 && (f = open(UTMP_FILE, O_WRONLY)) >= 0)
	{
	  lseek(f, (long)(t * sizeof(*utsrc)), SEEK_SET);
	  write(f, (char *)utsrc, sizeof(*utsrc));
	  close(f);
	}
    }
#else /* BSD || ultrix */
  /*
   * Find the location we want to write to.
   */
#ifdef UTMPX_FILE

#ifdef SOLARIS
  /* Of course, Solaris is different from everyone else. It
   * searches for /dev/pts/0 instead of pts/0. */
  strncpy(line, ALut_line(sess), sizeof(line));
  strcpy(ALut_line(sess), "/dev/");
  strncat(ALut_line(sess) + 5, line, sizeof(ALut_line(sess) - 5));
#endif

  setutxent();
  ut = getutxline(&(sess->ut));
#else
  setutent();
  ut = getutline(&(sess->ut));
#endif

  /*
   * We've got it. Copy the ID field from the entry we're going to
   * overwrite, unless we plan to set it ourselves.
   */
  if (!ALutIsSet(sess, ALutID) && ut != NULL)
    strncpy(ALut_id(sess), ut->ut_id, sizeof(ut->ut_id));

#ifdef SOLARIS
  /* Fix /dev/pts/0 to be pts/0 again. */
  strncpy(ALut_line(sess), line, sizeof(ALut_line(sess)));
#endif

  /*
   * All set... Write it out to utmp. Note that, in spite of the fact
   * it's undocumented and even hinted against, pututxline does in
   * fact write an appropriate entry into utmp as well as utmpx.
   */
#ifdef UTMPX_FILE
  pututxline(&(sess->ut));
  endutxent();
#else
  pututline(&(sess->ut));
  endutent();
#endif

#endif /* BSD || ultrix */

  /*
   * Append to wtmp... Note updwtmpx writes to both wtmp and wtmpx.
   */
#ifdef SYSV
    updwtmpx(WTMPX_FILE, &(sess->ut));
#else
#if defined(BSD) || defined(ultrix) /* no mapping for LOGIN_PROC */
  if (ALut_auxtype(sess) == ALutUSER_PROC ||
      ALut_auxtype(sess) == ALutDEAD_PROC)
#endif
    if ((f = open(WTMP_FILE, O_WRONLY|O_APPEND)) >= 0)
      {
	write(f, (char *)utsrc, sizeof(*utsrc));
	close(f);
      }
#endif

  return 0L;
}
