/* ucsb_display.c -- display_long_info () for ucsb. */

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

   This file is part of GNU Finger.

   GNU Finger 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 1, or (at your
   option) any later version.

   GNU Finger 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 GNU Finger; see the file COPYING.  If not, write to the
   Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */

/* **************************************************************** */
/*								    */
/*    Local Finger Display for UCSB using local GECOSFMT file	    */
/*								    */
/* **************************************************************** */

#define GECOSFORMATFILE "/usr/local/lib/gecosfmt"
#define DEFAULT_GECOS_SIZE 10
#define MAXLINE 1024
#define MAILDIR "/usr/spool/mail"

#include <config.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <grp.h>
#include <stdio.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/time.h>

char **read_gecos_labels();
char **read_gecos_fields();
char *path_concat();
char *getGecosLabel();

char field_separator; 	/* global - gecos field separator. */

/* Determine what the field separator for the gecos field is.  Usually
   it is a `,' but sometimes it may be `;'.  This should be hashed out
   a little more in the future. */
guess_field_separator (gecos)
     char *gecos;
{
  int comma = 0, semicolon = 0;

  while(*gecos) {
    if (*gecos == ',') comma++;
    if (*gecos == ';') semicolon++;
    gecos++;
  }
  if (comma > semicolon) return(',');
  return(';');
}

#define COLSIZE 37
print_entry (label, data, deflt, column, stream)
     char *label, *data, *deflt;
     int column;
     FILE *stream;
{
  int lsize, size;
  
  if (!data || *data == NULL) data = deflt;
  if (data && strlen(data) > 0) {
    size = strlen(label) % COLSIZE; 
    lsize = COLSIZE - size;

    if (!column) fprintf(stream, "\n");
    fprintf(stream,"%*.*s: %-*.*s",size,size,label,lsize,lsize,data);
    return(1);
  }
  return(0);
}

display_long_info(entry,stream,localflag)
  struct passwd *entry;
  FILE *stream;
  int localflag;
{
  FINGER_PACKET **packets = NULL;
  static char **gecoslabels = NULL;
  static char *authorized = NULL;
  char **gecosfields;
  struct group *group;
  int Authorized;
  time_t loggedin, idle = 0;
  char *tty;
  int i,column;

  char *path_concat ();
  char *mail_file = path_concat (MAILDIR, entry->pw_name);
  char *plan_file = path_concat (entry->pw_dir, ".plan");
  struct stat finfo;

  if (gecoslabels == NULL)
    gecoslabels = read_gecos_labels(GECOSFORMATFILE,&authorized);

  field_separator = guess_field_separator(entry -> pw_gecos);
  gecosfields = read_gecos_fields(entry -> pw_gecos);
 
  print_entry("Login Name",(entry) ? entry->pw_name : "Unknown","???",0,stream);
  if (entry == NULL || entry->pw_name[0] == NULL) return; 

  /* Are we authorized to see all of the info in the gecosfile? */
  Authorized = get_authorization(GECOSFORMATFILE);
  group = getgrgid(entry->pw_gid);

  print_entry("Group",(group) ? group->gr_name : "Unknown","???",1,stream);
  
  if (!authorized[0] || Authorized) /* Most likely the real name */
    print_entry(gecoslabels[0],gecosfields[0],"Unknown",0,stream);

  if (!authorized[1] || Authorized) /* Most likely the address */
    print_entry(gecoslabels[1],gecosfields[1],"Classified",1,stream);

  print_entry("Directory",entry->pw_dir,"No home Directory",0,stream);
  print_entry("Shell",entry->pw_shell,"No shell",1,stream);
  
  /* print out the rest of the gecos information */
  i = 2;
  column = 0;
  while (gecosfields[i] && gecoslabels[i]) {
    if (!authorized[i] || Authorized) {
      if (print_entry(gecoslabels[i],gecosfields[i],"",column,stream))
	column = (column) ? 0 : 1;
    }
    i++;
  }
  putc ('\n', stream);

  /* Now that's out of the way, lets print other info. */
  loggedin = 0;
  for (i = 0; packets && packets[i]; i++)
    { 
      if (strcmp(entry->pw_name,packets[i]->name) == 0) {
	if (!loggedin) {
	  loggedin = packets[i]->login_time;
	  tty = packets[i]->ttyname;
	  idle = packets[i]->idle_time;
	}
	else {
	  tty = (loggedin < packets[i]->login_time) ? tty : packets[i]->ttyname;
	  loggedin = (loggedin < packets[i]->login_time) ? loggedin : packets[i]->login_time;
	  idle = (idle < packets[i]-> idle_time) ? idle : packets[i]->idle_time;
	}
      }
    }
  if (loggedin) {
    extern int idle_time_threshold;

    /* The tty name can have a `*' appended, which tells us that the
       tty was a login session, instead of a window system session.
       Do we care?  No. */
    {
      char *t;
      t = tty + strlen (tty);
      if (t != tty) t--;
      if (*t == '*') *t = '\0';
    }

    {
      char *idle_time_string ();
      char *otime = idle_time_string (time ((long *)0) - loggedin);
      char *t = otime;

      if (*t)
	while (whitespace (*t)) t++;

      if (!*t)
	fprintf (stream, "Just now logged in.\n");
      else
	fprintf (stream, "On since %16.16s (%s) on %s.\n",
		 ctime (&loggedin), t, tty);

      free (otime);
    }

    if (idle > idle_time_threshold) {
      char *temp_string, *idle_time_string ();

      temp_string = idle_time_string (idle);
      fprintf(stream,"Idle for %s.\n", temp_string);
      free (temp_string);
    }
  }
  else 
    fprintf(stream,"Not logged in.\n");

  if (stat (mail_file, &finfo) < 0 || finfo.st_size == 0)
    fprintf (stream, "No mail.\n");
  else
    {
      if (finfo.st_atime < finfo.st_mtime)
	{
	  char *idle_time_string (), *temp_string, *ctime ();

	  temp_string = ctime (&finfo.st_mtime);
	  temp_string[strlen (temp_string) - 1] = '\0';
	  
	  fprintf (stream, "New mail since %s.\n", temp_string);
	  temp_string =
	    idle_time_string ((time_t)time ((time_t *)0) - finfo.st_atime);
	  fprintf (stream, "Has not read mail for %s.\n", temp_string);
	  free (temp_string);
	}
      else
	{
	  fprintf (stream, "No unread mail.\n");
	}
    }

  /* Maybe do the .plan file. */
  {
    FILE *istream = fopen (plan_file, "r");
    char buff[256];

    if (istream)
      {
	fprintf (stream, "Plan:\n");
	while (istream && fgets (buff, 256, istream))
	  fprintf (stream, "%s", buff);
	fclose (istream);
      }
    else
      fprintf (stream, "No plan.\n");
  }

  free (mail_file);
  free (plan_file);
  fflush (stream);
}


char *
read_field(line)
     char *line;
{
  char *field;
  int i;

  for (i = 0; line[i] && line[i] != field_separator; i++);

  if (line[i] == field_separator) i--;
  while(i >= 0  && whitespace(line[i])) i--;
  field = (char *) xmalloc(i+2);
  strncpy(field,line,i+1);
  field[i+1] = '\0';
  return(field);
}

char *
nextfield(line)
     char *line;
{
  while (*line && *line != field_separator) line++;
  if (*line) line++;
  return(line);
}

char **
read_gecos_fields(line)
     char *line;
{
  static char **fields = NULL;
  static int maxfields = DEFAULT_GECOS_SIZE;

  int n;


  if (fields == NULL) {
    fields = (char **) xmalloc((1 + maxfields) * sizeof(*fields));
    fields[0] = (char *) NULL;
  }
  
  n = 0;
  while (fields[n])
    free(fields[n++]);

  n = 0;
  while(*line) {
    if (n >= (maxfields - 1)) {
      maxfields *= 2;
      fields = (char **) xrealloc(fields,(1 + maxfields)*sizeof(*fields));
    }
    
    fields[n++] = read_field(line);
    line = nextfield(line);
  }
  fields[n] = NULL;

  return(fields);
}


char **
read_gecos_labels(file,authorization)
  char *file;
  char **authorization;
{ 
  static char **labels = NULL;
  static max = DEFAULT_GECOS_SIZE;

  FILE *input;
  int n;
  char line[MAXLINE], *label;
  
  if ((input = fopen(file,"r")) == NULL) {
    fprintf(stderr,"Cannot open '%s'\n",file);
    exit(1);
  }

  if (labels == NULL) {
    labels = (char **) xmalloc((1 + max)*sizeof(char **));
    *authorization = (char *) xmalloc((1 + max)*sizeof(char *));
  }
  n = 0;

  while (fgets(line,MAXLINE,input)) {
    if (*line == '#') continue;	/* ignore commented lines */

    label = getGecosLabel(line);
    if (*label == NULL) {
      free (label);
      continue;
    }
    
    if (n >= (max - 1)) {
      max = 2*max;
      labels = (char **) xrealloc(labels,(1 + max)*sizeof(char **));
      *authorization = (char *) xrealloc(*authorization,(1 + max)*sizeof(char *));
    }

    if (*line == '*' || *(line+1) == '*')
      (*authorization)[n] =  0x1;
    else
      (*authorization)[n] = '\0';

    labels[n++] = label;
  }
  labels[n] = (char *) NULL;
  return(labels);
}

/* **************************************************************** */
/*								    */
/*	Get the gecos label from a ucsb-gecosformat-file	    */
/*								    */
/* **************************************************************** */

char *
getGecosLabel (line)
     register char *line;
{ 
  char *end, *result;

  /* Skip until we find a pure alphabetic character. */

  while (*line && !pure_alphabetic (*line)) line++;

  /* Find the end of the text string.  Anything that is a proper
     character for a label is allowed. */
  end = line;
  while (*end && label_character (*end)) end++;

  /* Strip off trailing whitespace. */
  if (end != line) end--;
  while (end != line && whitespace (*end)) end--;
  if (*end && !whitespace (*end)) end++;

  result = (char *)xmalloc (1 + (end - line));
  strncpy (result, line, (end - line));
  result[(end - line)] = '\0';
  
  return (result);
}

/* GetAuthorization()
	Returns 1 if:
		a) current userid is equal to owner of file,
		b) current user is in group of file
*/
	
int get_authorization(filename)
char *filename;
{
int  uid,gid[NGROUPS], n, i;
struct stat buf;


	if (stat(filename,&buf) < 0) {
		perror("GetAuthorization");
		return(0);
		}

	uid = getuid();
	n = getgroups(NGROUPS,gid);

	if (uid == buf.st_uid )
		return(1);
	else {
		for (i = 0; i < n; i++)	
		  if (gid[i] == buf.st_gid) {
			return(1);
			break;
			}
	 	}
	return(0);
}


/* **************************************************************** */
/*								    */
/*		       Testing the Display Stuff.		    */
/*								    */
/* **************************************************************** */

#ifdef TEST
main(argc, argv)
     int argc;
     char **argv;
{
  struct passwd *entry; 

  if (argc == 1)
    {
      entry = getpwname ("root");
      display_finger_info (entry, stdout, NULL);
    }
  else
    {
      int i = 1;
      while (i < argc)
	{
	  entry = getpwname (argv[i]);
	  if (entry)
	    display_finger_info (entry, stdout, NULL);
	  else
	    printf ("%s: unregistered user.\n", argv[i]);
	  i++;
	}
    }
}

/* Return the concatenation of PATH and FILE. */
char *
path_concat (path, file)
     char *path, *file;
{
  char *output = (char *)xmalloc (2 + strlen (path) + strlen (file));

  strcpy (output, path);
  if (output[0] && (output[strlen (output) - 1] != '/'))
    strcat (output, "/");
  strcat (output, file);
  return (output);
}


/* Number of seconds that below which, the terminal is not idle. */
long idle_time_threshold = 60;

/* Macro for appending possibly pluralized strings to time_buffer. */
#define ADD_TIME(str,var)\
{\
  sprintf (time_buffer + strlen (time_buffer), str, var);\
  if (var > 1) strcat (time_buffer, "s");\
  strcat (time_buffer, ", ");\
}

/* Return a static string which is the English representation of the
   amount of idle time present in ITIME.  I think that other languages
   should be supported. */
char *
idle_time_string (itime)
     time_t itime;
{
  int seconds, minutes, hours, days, weeks, months, years;
  char time_buffer[128];

  time_buffer[0] = '\0';

  if (itime < idle_time_threshold)
    return (time_buffer);

  hours = days = weeks = months = years = 0;

  seconds = itime % 60;
  minutes = itime / 60;

  if (minutes > 60)	{ hours = minutes / 60; minutes %= 60; }
  if (hours > 24)	{ days = hours / 24; hours %= 24; }
  if (days  > 7)	{ weeks = days / 7; days %= 7; }
  if (weeks > 4)	{ months = weeks / 4; weeks %= 4; }
  if (months > 12)	{ years = months / 12; months %= 12; }

  {
    if (years)	ADD_TIME ("%d year", years)
    if (months)	ADD_TIME ("%d month", months)
    if (weeks)	ADD_TIME ("%d week", weeks)
    if (days)	ADD_TIME ("%d day", days)
  }

  sprintf (time_buffer + strlen (time_buffer), "%2d:%02d:%02d",
	   hours, minutes, seconds);

  return ((char *)strcpy
	  ((char *)xmalloc (1 + strlen (time_buffer)), time_buffer));
}

#endif


/*
 * Local variables:
 * compile-command: "cc -g -DTEST -Bstatic -o ucsb-local-finger ucsb-local-finger.c"
 * end:
 */
