/* fingerd.c -- Stripped finger daemon (shows logins and idle time).
   $Id: fingerd.c,v 0.1 1997/05/21 18:48:54 eagle Exp $

   Copyright 1997 by Russ Allbery <rra@stanford.edu>
   Based on code by Larry Schwimmer <opusl@leland.stanford.edu>

   This is a stripped version of fingerd, designed to do nothing other than
   show the list of logins and idle times for a given user (the so-called
   short format).  It was motivated by the need for other people to
   determine my idle time without having a local login, but under a
   situation where I would prefer people not be able to get full finger
   information or a full list of users.

   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.  */

/***************************************************************************
* Includes and #defines
***************************************************************************/

#ifdef UTMPX
#define HAVE_UTMPX_H
#endif

#include <ctype.h>		/* isspace(), isprint() */
#include <pwd.h>		/* getpwnam(), struct passwd */
#include <stdio.h>		/* fgets(), stdin, printf() */
#include <stdlib.h>		/* exit() */
#include <string.h>		/* strncmp(), memcpy() */
#include <sys/stat.h>		/* stat(), S_IWGRP, S_IWOTH */
#include <sys/types.h>		/* time_t */
#include <time.h>		/* time(), strftime() */
#ifdef HAVE_UTMPX_H
#include <utmpx.h>		/* getutxent(), setutxent(), etc. */
#else
#include <utmp.h>		/* getutent(), setutnet(), etc. */
#endif

#ifndef MIN
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#endif


/***************************************************************************
* Display utmp information
***************************************************************************/

/* Create a string that indicates when a user logged in.  This is tricky.
   We want to just give day of the weak unless they logged in more than a
   week ago, in which case we want to give the month and day, unless they
   logged in more than a year ago, in which case we want to give month and
   year. */
void
build_when (char *buffer, int bufsize, struct tm *login_ptr, time_t current)
{
  struct tm login;
  struct tm *now;

  /* Make a local copy, since we're going to call localtime() again. */
  memcpy (&login, login_ptr, sizeof (struct tm));

  /* Get the current time. */
  now = localtime (&current);

  /* Put the appropriate string in the buffer. */
  if ((now->tm_year == login.tm_year && now->tm_yday - login.tm_yday < 7)
      || (now->tm_year - login.tm_year == 1
	  && (now->tm_yday + 365
	      + ((login.tm_year % 400 == 0) ? 1 : 0)
	      + ((login.tm_year % 100 != 0
		  && (login.tm_year % 4 == 0)) ? 1 : 0)
	      - login.tm_yday < 7)))
    {
      /* Less than a week ago. */
      strftime (buffer, bufsize, "%a %H:%M", &login);
    }
  else if (now->tm_year == login.tm_year
	   || (now->tm_year - login.tm_year == 1
	       && now->tm_mon < login.tm_mon))
    {
      /* Less than a year ago. */
      strftime (buffer, bufsize, "%e %h %y", &login);
    }
  else
    {
      /* More than a year ago. */
      strftime (buffer, bufsize, " %h %Y", &login);
    }
}


/* Print out the header line for user login information. */

void
print_banner ()
{
  printf ("Login        Name              TTY       Idle    When    Where");
  printf ("\r\n");
}


/* We're being passed in a username which corresponds to some user on the
   system.  Step through each line in utmp/utmpx looking for any entries
   matching that user which are active processes, and for each one print out
   the login information. */

void
print_logins (char *name)
{
  int banner = 0;
#ifdef HAVE_UTMPX_H
  struct utmpx *ut;
#else
  struct utmp *ut;
#endif
  time_t now = time (NULL);

  /* Reset the getut(x)ent pointers and loop through all utmp(x) entries. */
#ifdef HAVE_UTMPX_H
  setutxent ();
  while (ut = getutxent ())
#else
  setutent ();
  while (ut = getutent ())
#endif
    {
      struct stat tty_status;
      struct tm *login_time;
      struct passwd *user_info;
      char tty[38] = "/dev/";
      char frombuf[23];
      char idlebuf[6] = "     ";
      char linebuf[9];
      char namebuf[21] = "";
      char userbuf[9];
      char whenbuf[10];
      int mesg = 0;
      time_t idle;
    
      if (ut->ut_type != USER_PROCESS) continue;
      if (strncmp (name, ut->ut_user, sizeof (ut->ut_user))) continue;

      /* Create a string representing the login time. */
#ifdef HAVE_UTMPX_H
      login_time = localtime (&ut->ut_tv.tv_sec);
#else
      login_time = localtime (&ut->ut_time);
#endif
      build_when (whenbuf, sizeof (whenbuf), login_time, now);

      /* Usernames could be more than 8 characters, but only print 8. */
      strncpy (userbuf, name, 8);
      userbuf[8] = '\0';

#ifdef HAVE_UTMPX_H
      /* Correct for munged utmpx files. */
      if (ut->ut_syslen < 0) ut->ut_syslen = 22;
#endif

      /* Build the contents of the "where" field. */
      if (ut->ut_host[0] && isprint (ut->ut_host[0]))
	{
#ifdef HAVE_UTMP_X
	  strncpy (frombuf, ut->ut_host, MIN (ut->ut_syslen, 22));
	  frombuf[MIN (ut->ut_syslen, 22)] = '\0';
#else
	  strncpy (frombuf, ut->ut_host, MIN (sizeof (ut->ut_host), 22));
	  frombuf[MIN (sizeof (ut->ut_host), 22)] = '\0';
#endif
	}
      else
	frombuf[0] = '\0';

      /* Figure out the current idle time. */
      strncat (tty, ut->ut_line, sizeof (tty) - 6);
      tty[37] = '\0';
      if (stat (tty, &tty_status) == 0)
	{
	  mesg = (tty_status.st_mode & (S_IWGRP | S_IWOTH));
	  idle = now - tty_status.st_atime;

	  /* Convert the idle time to a string. */
	  idle /= 60;
	  if (idle >= 60)
	    {
	      if (idle >= 1440)
		{
		  /* Over one day of idle time, convert to days. */
		  idle /= 1440;
		  if (idle <= 9999)
		    sprintf (idlebuf, "%4dd", idle);
		  else
		    sprintf (idlebuf, "years");
		}
	      else
		{
		  /* Idle time measured in hours.  Show minues if < 10h. */
		  if (idle >= 600)
		    sprintf (idlebuf, "  %2dh", idle / 60);
		  else
		    sprintf (idlebuf, "%2d:%02d", idle / 60, idle % 60);
		}
	    }
	  else if (idle > 0)
	    {
	      /* Idle time measured in minutes (don't show if seconds). */
	      sprintf (idlebuf, "   %2d", idle);
	    }
	}

      /* Build the full name field. */
      user_info = getpwnam (name);
      if (user_info != NULL)
	{
	  strncpy (namebuf, user_info->pw_gecos, 20);
	  namebuf[20] = '\0';
	}

      /* Build the name of the tty.  Remap pts/ttyXY to ttyXY on HP-UX. */
      if (ut->ut_line[0] == 'p' && ut->ut_line[4] == 't')
	strncpy (linebuf, ut->ut_line + 4, 8);
      else
	strncpy (linebuf, ut->ut_line, 8);
      linebuf[8] = '\0';

      /* Actually print out the line. */
      if (!banner)
	{
	  banner = 1;
	  print_banner ();
	}
      printf ("%-8s %-21s%c%-8s %5s %9s  %-16s\r\n", userbuf, namebuf,
	      mesg ? ' ' : '*', linebuf, idlebuf, whenbuf, frombuf);
    }

  /* If we never printed anything out, say something uninformative. */
  if (!banner)
    printf ("Sorry, no information available for this user.\r\n");
}


/***************************************************************************
* Main routine
***************************************************************************/

/* Read a single line in from stdin, stripping off options if they exist
   (since we completely ignore whether the client wants wide format or not),
   and then call print_logins with the username there.  If they're trying to
   do finger forwarding or null queries, tell them to go away. */

int
main (void)
{
  char buffer[128];
  char *name, *end;

  /* Read the name in from stdin. */
  name = fgets (buffer, sizeof (buffer), stdin);
  if (name == NULL || *name == '\0' || *name == '\r')
    {
      printf ("Sorry, list of on-line users not available.\r\n");
      exit (0);
    }

  /* Skip past an initial /W and white space, if present (ignoring it). */
  if (name[0] == '/' && name[1] == 'W')
    {
      name += 2;
      while (*name == ' ' || *name == '\t') name++;
    }

  /* Find the end of the username and null-terminate. */
  end = name;
  while (*end && !isspace (*end)) end++;
  *end = '\0';

  /* Verify that they actually gave us a username.  If so, display the
     results, otherwise bail with an uninformative error message. */
  if (end == name || getpwnam (name) == NULL)
    printf ("Sorry, no information available for this user.\r\n");
  else
    print_logins (name);
}
