/* packet.c -- Functions for manipulating packets. */

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

   This file is part of GNU Finger.

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


#include <stdio.h>
#include <config.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/signal.h>

#include <general.h>
#include <packet.h>
#include <error.h>
#include <tcp.h>
#include <util.h>

#include <setjmp.h>

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif

#ifndef L_SET
#define L_SET SEEK_SET
#define L_INCR SEEK_CUR
#endif /* !L_SET */

#include <netinet/in.h>

/* **************************************************************** */
/*								    */
/*			Sorting and filtering packets		    */
/*								    */
/* **************************************************************** */
/* Function for qsort to compare names. */
static int
name_sort_comp (p1, p2)
  FINGER_PACKET **p1, **p2;
{
  return (strcmp ((*p1)->name, (*p2)->name));
}

/* Sort the the packets in LIST by side-effecting them.  Use whatever
   the user default for sorting is.  (By username.) */
void
sort_packets (list)
  FINGER_PACKET **list;
{
  qsort (list, array_len (list), sizeof (FINGER_PACKET *), name_sort_comp);
}


/* Packet is for console user */
static int
console_user_p (packet)
  FINGER_PACKET *packet;
{
  return is_console (packet->ttyname);
}


/* Filter PACKETS through FILTER. Returns a new array. All packets
   that get the nod by the filter are returned. Notice that the pointers
   are copied, so the array is freed by a simple call to free(), which
   must be done before free_aray() is called for PACKETS. */
FINGER_PACKET **
filter_packets (packets, filter)
  FINGER_PACKET **packets;
  int (*filter)();
{
  FINGER_PACKET **new_packets;
  int entry, new_index;


  if (!packets)
    return NULL;

  new_packets = (FINGER_PACKET **) xmalloc (sizeof (FINGER_PACKET *)
					      * (array_len (packets) + 1));

  /* Loop and include in new array if given the nod */
  for (entry = new_index = 0; packets[entry]; entry++)
    if ((*filter)(packets[entry]))
      new_packets[new_index++] = packets[entry];
      
  new_packets[new_index] = NULL;

  return new_packets;
}


/* Return console users. */
FINGER_PACKET **
console_users (packets)
  FINGER_PACKET **packets;
{
  return filter_packets (packets, console_user_p);
}


/* **************************************************************** */
/*								    */
/*			Print a Packet				    */
/*								    */
/* **************************************************************** */

/* Print PACKET on STREAM.  HEADERP is non-zero if we should print
   a header first.
bfox     Brian Fox          fingerd        *p0 vision   (cs100-mux dialup from
bfox     Brian Fox                   0:38  *p0 fen      (vision  Eng I rm 3118
bfox     Brian Fox          rm       0:06  *p0 quagmire (vision  Eng I rm 3118
 */
void
ascii_packet (packet, stream, headerp)
  FINGER_PACKET *packet;
  FILE *stream;
  int headerp;
{
  void print_packet ();

  if (headerp)
    fprintf (stream,"\
  User     Real Name         What    Idle  TTY  Host      Console Location\r\n"); 
  fflush (stream);
  print_packet (packet, stream);
}

/* Print the contents of PACKET prettily.  The output goes to STREAM. */
void
print_packet (packet, stream)
     FINGER_PACKET *packet;
     FILE *stream;
{
  char tty[4], real_name[20], what[8], ttyloc[50];
  int offset = 0;
  char *idle = idle_time_string (packet->idle_time);
  char *hostname;

  if (!strncmp (packet->ttyname, "tty", 3)
      || !strncmp (packet->ttyname, "hty", 4))
    offset = 3;
  else if (!strncmp (packet->ttyname, "pty/tty", 7))
    offset = 7;
  else if (!strncmp (packet->ttyname, "pts/", 4))
    offset = 4;

  strncpy (tty + 1, packet->ttyname + offset, 2);

  if (* (packet->ttyname + (strlen (packet->ttyname) - 1)) == '*')
    tty[0] = '*';
  else
    tty[0] = ' ';

  tty[3] = '\0';

  strncpy (real_name, packet->real_name, 19);
  real_name[18] = '\0';

  hostname = sans_domain (packet->host);

  if (strlen (hostname) > 8)
    hostname[8] = '\0';

  if (idle)
    {
      idle[6] = '\0';

      if (idle[2] == ':' && idle[5] == ':')
        idle[5] = '\0';
    }

  strncpy (ttyloc, packet->ttyloc, 50);
  ttyloc[22] = '\0';

  strncpy (what, packet->what, 7);
  what[7] = '\0';

  fprintf (stream, "%-8s %-18s %-7s %-6s %3s %-8s %s\r\n",
	   packet->name, real_name, what, idle ? idle : "", tty,
	   hostname, ttyloc);
  if (idle)
    free (idle);
  fflush (stream);

  free (hostname);
}

/* Print out everything in a packet to stderr. */
void
debug_packet (packet)
     FINGER_PACKET *packet;
{
  char *idle = idle_time_string (packet->idle_time);

  fprintf (stderr, "FINGER_PACKET *0x%8x {\r\n\
        name: \"%s\"\r\n\
   real_name: \"%s\"\r\n\
        host: \"%s\"\r\n\
  login_time: \"%s\"\r\n\
   idle_time: \"%s\"\r\n\
     ttyname: \"%s\"\r\n\
      ttyloc: \"%s\"\r\n\
        what: \"%s\"\r\n\
}", packet,
	   packet->name, packet->real_name, packet->host,
	   ctime (&(packet->login_time)), idle ? idle : "",
	   packet->ttyname, packet->ttyloc, packet->what);
  free (idle);
  fflush (stderr);
}


/* **************************************************************** */
/*								    */
/*		  Building and Procuring Packets		    */
/*								    */
/* **************************************************************** */

/* Return a NULL terminated array of (FINGER_PACKET *) which
   represents the list of users at ADDRESS, a pointer to an inet address.
   If there are no users on that machine, an array of zero length is
   returned.  If we cannot talk to that machine, a NULL pointer is
   returned instead.  USER is an argument to be passed to our server.
   NULL or empty is the same as ".all". PORT_OR_SERVICE is the port or
   service to which the connection is made; the default cfinger service
   is used if NULL (which usually means none was specified in the
   `clients' file). */

FINGER_PACKET **
finger_at_address (address, user, port_or_service)
  char *address;
  char *user;
  char *port_or_service;
{
  FINGER_PACKET **packets, **read_packets ();
  FILE *stream;
  int connection;


  if (!user)
    user = "";

  connection = tcp_to_service (port_or_service && *port_or_service
			       ? port_or_service : CFINGER_SERVICE, address);

  if (connection < 0)
    return ((FINGER_PACKET **)NULL);

  /* Ask the server to return all of the users in binary form, please. */
  {
    char *wakeup;

    wakeup = (char *) xmalloc (3 + strlen (user));
    sprintf (wakeup, ":%s\r\n", user);
    write (connection, wakeup, strlen (wakeup));
    free (wakeup);
  }

  stream = fdopen (connection, "r");
  packets = receive_packets (stream);

  fclose (stream);
  close (connection);
  return packets;
}
  

/* **************************************************************** */
/*								    */
/*			Files and Packets			    */
/*								    */
/* **************************************************************** */

/* Remove packets from LIST, side-effecting LIST, a NULL terminated array
   of FINGER_PACKET *.  Remove the entries for which FUNCTION returns
   non-zero when called with the current packet and ARG, as in
   function (list[i], arg). */
void
remove_packets (list, function, arg)
  FINGER_PACKET **list;
  Function *function;
{
  int i, start, end;
  int host_index;

  for (host_index = 0; list[host_index]; host_index++)
    {
      if ((*function)(list[host_index], arg))
	{
	  /* Search forward for more contiguous packets that share this host,
	     and then delete them all in one fell swoop. */

	  start = host_index;
	  i = host_index + 1;

	  for (;list[i] && ((*function)(list[i], arg)); i++);

	  end = i;

	  for (i = start; i < end; i++)
	    free (list[i]);

	  for (i = 0; list[end + i]; i++)
	    list[start + i] = list[end + i];

	  list[start + i] = (FINGER_PACKET *)NULL;
	}
    }
}

/* Write out the contents of LIST (some FINGER_PACKET *'s) to FILENAME. */
void
write_packets (list, filename)
     FINGER_PACKET **list;
     char *filename;
{
  register int i, file;

  file = open (filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);

  if (file < 0)
    {
      file_error (WARNING, filename);
      return;
    }

  if (list)
    {
      for (i = 0; list[i]; i++)
	if (write (file, list[i], sizeof (FINGER_PACKET)) == -1)
	  perror (filename);
    }

  fsync (file);
  close (file);
}

/* Read a list of packets from FILEDES.  This always returns a pointer
   to a NULL terminated array of FINGER_PACKET *, even if no packets were
   read. */
FINGER_PACKET **
read_packets (filedes)
     int filedes;
{
  return (generic_read_packets (filedes, 0));
}

/* Read a list of packets from the network connection FILEDES.  This always
   returns a pointer to NULL terminated array of FINGER_PACKET *, even if no
   packets were read.  This differs from read_packets () in that the network
   byte ordering of longs in the packets is handled. */
FINGER_PACKET **
read_network_packets (filedes)
    int filedes;
{
  return (generic_read_packets (filedes, 1));
}


/* Read a list of packets from the file descriptor FILEDES. This
   always returns a pointer to NULL terminated array of FINGER_PACKET *,
   even if no packets were read. */

FINGER_PACKET **
generic_read_packets (filedes, network_p)
  int filedes, network_p;
{
  int packet_index = 0, packet_size = 10;
  FINGER_PACKET **packets =
    (FINGER_PACKET **)xmalloc (packet_size * sizeof (FINGER_PACKET *));
  FINGER_PACKET packet;
  
  packets[0] = (FINGER_PACKET *)NULL;

  while (timed_read (filedes, &packet, sizeof (packet)) == sizeof (packet))
    {
      if (packet_index + 2 >= packet_size)
	{
	  packets = (FINGER_PACKET **)
	    xrealloc (packets,
		      (1 + (packet_size *= 2)) * sizeof (FINGER_PACKET *));
	}

      packets[packet_index] =
	(FINGER_PACKET *)xmalloc (sizeof (FINGER_PACKET));

      bcopy (&packet, packets[packet_index], sizeof (FINGER_PACKET));
      packets[++packet_index] = (FINGER_PACKET *)NULL;
    }
  return (packets);
}


/* Read a list of packets from the network connection STREAM. This
   always returns a pointer to NULL terminated array of FINGER_PACKET *,
   even if no packets were read. */

FINGER_PACKET **
receive_packets (stream)
  FILE *stream;
{
  int packet_index = 0, packet_size = 10;
  FINGER_PACKET **packets =
    (FINGER_PACKET **)xmalloc (packet_size * sizeof (FINGER_PACKET *));
  FINGER_PACKET packet;
  
  packets[0] = (FINGER_PACKET *)NULL;

  while (read_packet (stream, &packet))
    {
      if (packet_index + 2 >= packet_size)
	{
	  packets = (FINGER_PACKET **)
	    xrealloc (packets,
		      (1 + (packet_size *= 2)) * sizeof (FINGER_PACKET *));
	}

      packets[packet_index] =
	(FINGER_PACKET *) xmalloc (sizeof (FINGER_PACKET));

      bcopy (&packet, packets[packet_index], sizeof (FINGER_PACKET));
      packets[++packet_index] = (FINGER_PACKET *)NULL;
    }

  return packets;
}


/* Allow 1 minute to read a single packet. */
#define TIMED_READ_TIME (20 * 1)

/* Just like read (), but this times out after a reasonable time. */
jmp_buf timed_read_frame;

int
timed_read (fd, buffer, buffer_size)
     int fd, buffer_size;
     byte *buffer;
{
  int return_val;
  void timed_read_timed_out ();

  signal (SIGALRM, timed_read_timed_out);
  alarm (TIMED_READ_TIME);

  if (!setjmp (timed_read_frame))
    return_val = read (fd, buffer, buffer_size);
  else
    return_val = 0;

  alarm (0);
  signal (SIGALRM, SIG_DFL);

  return (return_val);
}

/* Stub for timed_read (), above.
   This function is called when at least TIMED_READ_TIME seconds have
   passed in timed_read ().  It jumps out of the read() above. */
void
timed_read_timed_out ()
{
  alarm (0);
  longjmp (timed_read_frame, !0);
}


/* Write PACKET to STREAM as text. The packet is formatted like:

   field<CR><LF>field<CR><LF>...<CR><LF><FF><CR><LF>

   The <FF> on a line by itself signifies the end of the packet. */

void
write_packet (packet, stream)
  FINGER_PACKET *packet;
  FILE *stream;
{
  fprintf (stream, "%s\r\n", packet->name);
  fprintf (stream, "%s\r\n", packet->real_name);
  fprintf (stream, "%s\r\n", packet->host);
  fprintf (stream, "%ld\r\n", packet->login_time);
  fprintf (stream, "%ld\r\n", packet->idle_time);
  fprintf (stream, "%s\r\n", packet->ttyname);
  fprintf (stream, "%s\r\n", packet->ttyloc);
  fprintf (stream, "%s\r\n", packet->what);
  fprintf (stream, "\014\r\n");
  fflush (stream);
}

static int read_component ();

/* Read PACKET from STREAM. For each line read, make sure the the end
   of the packet hasn't been reached. This is so to facilitate reading
   packets written by older versions of write_packet() that provide fewer
   components. On the other hand, if the other end is a more recent
   version than this is, then the extra components are ignored.  */

int
read_packet (stream, packet)
  FINGER_PACKET *packet;
  FILE *stream;
{
  char *line= NULL;
  int line_size = 0;
  int return_val = 0;


  signal (SIGALRM, timed_read_timed_out);
  alarm (TIMED_READ_TIME);

  if (!setjmp (timed_read_frame))
    {
      bzero (packet, sizeof *packet);
      
      /* Sigh. This is really ugly, but I can't think of a better way
	 to do it at the moment. */
      
#define COMPONENT(ACTION) \
      if (!read_component (stream, &line, &line_size)) \
	goto end_of_packet_reached; \
      ACTION

      COMPONENT ((strncpy (packet->name, line, sizeof packet->name)));
      COMPONENT ((strncpy (packet->real_name, line, sizeof packet->real_name)));
      COMPONENT ((strncpy (packet->host, line, sizeof packet->host)));
      COMPONENT ((packet->login_time = atoi (line)));
      COMPONENT ((packet->idle_time = atoi (line)));
      COMPONENT ((strncpy (packet->ttyname, line, sizeof packet->ttyname)));
      COMPONENT ((strncpy (packet->ttyloc, line, sizeof packet->ttyloc)));
      COMPONENT ((strncpy (packet->what, line, sizeof packet->what)));
      
      /* Flush remainder of packet */
      while (read_component (stream, &line, &line_size));

    end_of_packet_reached:

      return_val = 1;
    }
  else
    return_val = 0;

  alarm (0);
  signal (SIGALRM, SIG_DFL);

  if (line)
    free (line);

  return return_val;

#undef COMPONENT
}


/* Read one packet component. Returns zero if end of packet. */
static int
read_component (stream, buf, bufsize)
  FILE *stream;
  char **buf;
  int *bufsize;
{
  int n;

  if (getline (buf, bufsize, stream) < 0)
    longjmp (timed_read_frame, 2);

  /* Strip off trailing CR LF */
  n = strlen (*buf);
  if (n > 1 && (*buf)[n-2] == '\r' && (*buf)[n-1] == '\n')
    {
      (*buf)[n-2] = 0;
      n -= 2;
    }

  /* Return 0 if end of packet, otherwise 1 */
  return !(n == 1  && **buf == 014);
}
