patches to cfingerd-1.4.3 ( & 1.4.2 )


Finbarr O'Kane (finbarro@netsoc.ucd.ie)
Thu, 30 Sep 1999 22:42:06 +0100 (BST)


Hi guys

Ive spent the last while customising cfingerd for use on a cluster of
client servers we have running

I have made a few alterations to the code allowing for a number of
different features.

1. tuned MAX_TTYS in the Configure script [ DEFAULT 256 ]
        Note: MAX_TTYS has some extra meaning as a result of this patch
        being applied, will be explained shortly.

   affected file Configure, src/config.h.in, userlist/config.h.in

2. added 'HAS_TTY_GROUP' to userlist/config.h.in to let the userlist
   program accurately display whether people have mesg y/n (by way of a *)

   affected file userlist/initialize.c

3. modified the layout of the userlist output, expanding the size of
   the TTY field from 2, to 5, to add support for the following
        If a user is logged on from
        'console' " con" is displayed "*con" if msgs are off
        'ttyS0' " S0" is displayed "*S0" if msgs are off
        'ttyp0' " p0" is displayed "*p0" if msgs are off
        'pts/0' "/0" is displayed "*0" if msgs are off

        the /dev/pts/<number> modifications described above work on
        Solaris 2.6, Solaris 7, and Redhat 6 (or any form of Linux using
        the Unix98 pty system)

   affected file userlist/initialize.c
  
4. changed:
        display.c: char ru[8], fn[STRLEN];
        to
        display.c: char ru[9], fn[STRLEN];

        where ru represents username, set to 8 chars it was chopping
        off the last letter of my username, and as a result not
        displaying stuff correctly...

   affected file userlist/display.c

5. the following change only really applies to multihost sessions

   instead of polling all hosts for their respective 'userlists' and
   printing them in the order in which they are polled, the 'buf' is
   broken down into a char ** struct and passed through qsort(..)
   where the array is sorted by the first 9 characters (ie username)
   It is of course possible to insert any sort function in here..

   it is also trivial, in display.c to add a facility for showing what
   host the user is logged into, but , beware, if you change the
   header (as I have)

   display.c: printf ("Username Real name Idletime
TTY Remote location\n");

   even slightly, then you will also need to update these changes
   in src/standard.c

   the result is that it prints the Username Real name... title
   and then a sorted list of users logged in at various hosts

   affected file src/userlist.c

6. minor changes to the latests switch to "index(cp, ',')" instead
   of snprintf to the following

   if ((x = index (cp, ',')) != NULL)
     to
   if ((x = (char *)index (cp, ',')) != NULL)

   to stop the compiler complaining about invalid assignments
   (on Solaris 2.6 w/ gcc anyway)

   affected file src/parse.c src/search.c src/standard.c

7. changed the idle detection for a given tty from the st_mtime
   to st_atime, to prevent the idle time resetting if someone
   'writes' a message to another users terminal

   affected file src/idle.c

Please let me know if Ive missed anything, or explained anything badly

Similarly, Im afraid that my management of strings etc isnt really the
best, so I would appreciate if someone would cast an eye over the code

Its working happily across 4 machines for me, fyi

Regards

Finbarr O'Kane
System Administrator
UCD Internet Society

diff -r -N -c cfingerd-1.4.3/Configure cfingerd-1.4.3.netsoc/Configure
*** cfingerd-1.4.3/Configure Sat Sep 4 23:39:24 1999
--- cfingerd-1.4.3.netsoc/Configure Thu Sep 30 22:08:41 1999
***************
*** 45,50 ****
--- 45,57 ----
          open(CF, "userlist/config.h.in");
          open(CFO, ">userlist/config.h");
          while(<CF>) {
+ if (getgrnam("tty")) {
+ s/\@HAS_TTY_GROUP\@/#define\tHAS_TTY_GROUP\t\t1/;
+ } else {
+ s/\@HAS_TTY_GROUP\@/#undef\tHAS_TTY_GROUP/;
+ }
+
+ s/\@MAX_TTYS\@/#define\tMAX_TTYS\t\t256/;
                  print CFO;
          }
  
***************
*** 96,101 ****
--- 103,109 ----
                          s/\@HAS_TTY_GROUP\@/#undef\tHAS_TTY_GROUP/;
                  }
  
+ s/\@MAX_TTYS\@/#define\tMAX_TTYS\t\t256/;
                  print CFO "$_\n";
          }
  
Binary files cfingerd-1.4.3/gmake and cfingerd-1.4.3.netsoc/gmake differ
diff -r -N -c cfingerd-1.4.3/src/config.h.in cfingerd-1.4.3.netsoc/src/config.h.in
*** cfingerd-1.4.3/src/config.h.in Thu Aug 12 15:15:59 1999
--- cfingerd-1.4.3.netsoc/src/config.h.in Thu Sep 30 22:10:17 1999
***************
*** 113,115 ****
--- 113,125 ----
   * Start with `-d' to make cfingerd run as daemon.
   */
  /* #define DAEMON_MODE 1 */
+
+ /*
+ * MAX_TTYS
+ *
+ * This is the maximum number of login sessions your system will ever have.
+ * Most only allow a maximum of 32 on a single CPU for slower machines (ie.
+ * Linux 486/100 systems). For faster machines (such as an Alpha), try
+ * 64 or 96.
+ */
+ @MAX_TTYS@
diff -r -N -c cfingerd-1.4.3/src/parse.c cfingerd-1.4.3.netsoc/src/parse.c
*** cfingerd-1.4.3/src/parse.c Wed Sep 29 08:04:24 1999
--- cfingerd-1.4.3.netsoc/src/parse.c Thu Sep 30 22:18:58 1999
***************
*** 86,92 ****
      char *parsed;
      char *cp;
  
! if ((cp = index (username, '.')) != NULL)
        *cp='\0';
      if ((parsed = (char *)malloc (strlen(username)+1)) != NULL) {
        memset (parsed, 0, strlen(username)+1);
--- 86,92 ----
      char *parsed;
      char *cp;
  
! if ((cp = (char *)index (username, '.')) != NULL)
        *cp='\0';
      if ((parsed = (char *)malloc (strlen(username)+1)) != NULL) {
        memset (parsed, 0, strlen(username)+1);
diff -r -N -c cfingerd-1.4.3/src/search.c cfingerd-1.4.3.netsoc/src/search.c
*** cfingerd-1.4.3/src/search.c Wed Sep 29 08:04:24 1999
--- cfingerd-1.4.3.netsoc/src/search.c Thu Sep 30 22:19:27 1999
***************
*** 41,47 ****
      show_top();
  
      cp=uname;
! if ((xp = index (cp, '.')) != NULL) {
          ++xp;
          if ((searchname = (char *)malloc (strlen(xp)+1)) != NULL) {
              memset (searchname, 0, strlen(xp)+1);
--- 41,47 ----
      show_top();
  
      cp=uname;
! if ((xp = (char *)index (cp, '.')) != NULL) {
          ++xp;
          if ((searchname = (char *)malloc (strlen(xp)+1)) != NULL) {
              memset (searchname, 0, strlen(xp)+1);
diff -r -N -c cfingerd-1.4.3/src/standard.c cfingerd-1.4.3.netsoc/src/standard.c
*** cfingerd-1.4.3/src/standard.c Wed Sep 29 08:04:24 1999
--- cfingerd-1.4.3.netsoc/src/standard.c Thu Sep 30 22:18:21 1999
***************
*** 25,30 ****
--- 25,31 ----
  #include <sys/types.h>
  
  #include "privs.h"
+ #include "config.h"
  
  int columns;
  int times_on;
***************
*** 37,43 ****
      BOOL writable;
  } TTY_FROM;
  
- #define MAX_TTYS 64
  TTY_FROM tty_list[MAX_TTYS];
  static int uid, nuid, ngid;
  
--- 38,43 ----
***************
*** 847,877 ****
      ngid = pwent->pw_gid;
  
      cp = pwent->pw_gecos;
! if ((x = index (cp, ',')) != NULL) { /* username */
          if ((username = (char *)malloc (x-cp+1)) != NULL) {
              memset (username, 0, x-cp+1);
              strncpy (username, cp, x-cp);
          }
          cp = ++x;
! if ((x = index (cp, ',')) != NULL) { /* room */
              if ((room = (char *)malloc (x-cp+1)) != NULL) {
                  memset (room, 0, x-cp+1);
                  strncpy (room, cp, x-cp);
              }
              cp = ++x;
! if ((x = index (cp, ',')) != NULL) { /* work phone */
                  if ((work_phone = (char *)malloc (x-cp+1)) != NULL) {
                      memset (work_phone, 0, x-cp+1);
                      strncpy (work_phone, cp, x-cp);
                  }
                  cp = ++x;
! if ((x = index (cp, ',')) != NULL) { /* home phone */
                      if ((home_phone = (char *)malloc (x-cp+1)) != NULL) {
                          memset (home_phone, 0, x-cp+1);
                          strncpy (home_phone, cp, x-cp);
                      }
                      cp = ++x;
! if ((x = index (cp, ',')) != NULL) /* other */
                          *x = '\0';
                      if ((other = (char *)malloc (strlen(cp)+1)) != NULL) {
                          memset (other, 0, strlen(cp)+1);
--- 847,877 ----
      ngid = pwent->pw_gid;
  
      cp = pwent->pw_gecos;
! if ((x = (char *)index (cp, ',')) != NULL) { /* username */
          if ((username = (char *)malloc (x-cp+1)) != NULL) {
              memset (username, 0, x-cp+1);
              strncpy (username, cp, x-cp);
          }
          cp = ++x;
! if ((x = (char *)index (cp, ',')) != NULL) { /* room */
              if ((room = (char *)malloc (x-cp+1)) != NULL) {
                  memset (room, 0, x-cp+1);
                  strncpy (room, cp, x-cp);
              }
              cp = ++x;
! if ((x = (char *)index (cp, ',')) != NULL) { /* work phone */
                  if ((work_phone = (char *)malloc (x-cp+1)) != NULL) {
                      memset (work_phone, 0, x-cp+1);
                      strncpy (work_phone, cp, x-cp);
                  }
                  cp = ++x;
! if ((x = (char *)index (cp, ',')) != NULL) { /* home phone */
                      if ((home_phone = (char *)malloc (x-cp+1)) != NULL) {
                          memset (home_phone, 0, x-cp+1);
                          strncpy (home_phone, cp, x-cp);
                      }
                      cp = ++x;
! if ((x = (char *)index (cp, ',')) != NULL) /* other */
                          *x = '\0';
                      if ((other = (char *)malloc (strlen(cp)+1)) != NULL) {
                          memset (other, 0, strlen(cp)+1);
diff -r -N -c cfingerd-1.4.3/src/userlist.c cfingerd-1.4.3.netsoc/src/userlist.c
*** cfingerd-1.4.3/src/userlist.c Sun Aug 29 10:57:25 1999
--- cfingerd-1.4.3.netsoc/src/userlist.c Thu Sep 30 22:12:11 1999
***************
*** 16,21 ****
--- 16,57 ----
  #include "cfingerd.h"
  #include "proto.h"
  #include "privs.h"
+ #include "config.h"
+
+ #define WIDTH 85
+
+ /*
+ * simple sort mechanism used by qsort() to arrange our array of strings
+ * in whichever order we like
+ */
+ static int do_sort (const void *p1, const void *p2)
+ {
+ return (strncmp(*(char * const *)p1,*(char * const *)p2,9));
+ }
+
+ /* chop_buf takes 3 args
+ * 1. buf returned from the 'userlist' program... preformatted output
+ * 2. cnt = the current 'count' of home many lines there are to sort
+ * 3. array of char ** of lines already chopped out
+ */
+ int chop_buf(char *buf, int cnt, char **strarray) {
+ char *line;
+
+ line=strtok(buf,"\n");
+ if(line!=NULL) {
+ strarray[cnt]=(char *)malloc(WIDTH);
+ strncpy(strarray[cnt],line,strlen(line)+1);
+ cnt++;
+ while((line=strtok(NULL,"\n"))!=NULL) {
+ strarray[cnt]=(char *)malloc(WIDTH);
+ strncpy(strarray[cnt],line,strlen(line)+1);
+ cnt++;
+ }
+ } else {
+ return -1;
+ }
+ return cnt;
+ }
  
  /*
   * HANDLE_USERLIST
***************
*** 27,32 ****
--- 63,74 ----
      BOOL can_show = FALSE;
      char *buf;
      int head = 0;
+ char **strarray;
+ char *line;
+ int count = 0;
+
+ strarray=(char **)malloc(MAX_TTYS+1);
+ line = (char *)malloc(WIDTH+1);
  
      if (local_finger) {
          if (prog_config.local_config_bits2 & SHOW_SYSTEMLIST)
***************
*** 63,72 ****
  
                      if ((buf = safe_exec(NOBODY_UID, NOBODY_GID, cmdline)) != NULL) {
                          if (!head) {
! printf ("Username Real name Idletime TTY Remote console location\n");
                              head = 1;
                          }
! printf ("%s", buf);
                          fflush(stdout);
                          free (buf);
                      }
--- 105,115 ----
  
                      if ((buf = safe_exec(NOBODY_UID, NOBODY_GID, cmdline)) != NULL) {
                          if (!head) {
! printf ("Username Real name Idletime TTY Remote location\n");
                              head = 1;
                          }
! /* chop out the strings returned in 'buf' into its an array of char *'s */
! count=chop_buf(buf, count, strarray);
                          fflush(stdout);
                          free (buf);
                      }
***************
*** 80,89 ****
  
                      if ((buf = safe_exec(NOBODY_UID, NOBODY_GID, cmdline)) != NULL) {
                          if (!head) {
! printf ("Username Real name Idletime TTY Remote console location\n");
                              head = 1;
                          }
! printf ("%s", buf);
                          fflush(stdout);
                          free (buf);
                      }
--- 123,133 ----
  
                      if ((buf = safe_exec(NOBODY_UID, NOBODY_GID, cmdline)) != NULL) {
                          if (!head) {
! printf ("Username Real name Idletime TTY Remote location\n");
                              head = 1;
                          }
! /* chop out the strings returned in 'buf' into its an array of char *'s */
! count=chop_buf(buf, count, strarray);
                          fflush(stdout);
                          free (buf);
                      }
***************
*** 98,104 ****
  
              if ((buf = safe_exec(NOBODY_UID, NOBODY_GID, cmdline)) != NULL) {
                  if (!head) {
! printf("Username Real name Idletime TTY Remote console location\n");
                      head = 1;
                  }
                  printf ("%s", buf);
--- 142,148 ----
  
              if ((buf = safe_exec(NOBODY_UID, NOBODY_GID, cmdline)) != NULL) {
                  if (!head) {
! printf ("Username Real name Idletime TTY Remote location\n");
                      head = 1;
                  }
                  printf ("%s", buf);
***************
*** 110,123 ****
          if (!head)
              printf ("Nobody logged in.\n");
  
! if ((num_finger_sites > 1) &&
              prog_config.config_bits2 & SHOW_MULTFING)
              printf("\n**> This is the complete listing of %d sites total.\n",
! num_finger_sites);
  
          if ((num_finger_sites > 1) &&
! prog_config.config_bits2 & SHOW_MULTFING)
! SEND_RAW_RETURN;
  
          fflush(stdout);
  
--- 154,175 ----
          if (!head)
              printf ("Nobody logged in.\n");
  
! /*if ((num_finger_sites > 1) &&
              prog_config.config_bits2 & SHOW_MULTFING)
              printf("\n**> This is the complete listing of %d sites total.\n",
! num_finger_sites); */
  
          if ((num_finger_sites > 1) &&
! prog_config.config_bits2 & SHOW_MULTFING) {
! /* re-using variable head since its no longer used */
! qsort(strarray,count,sizeof(char *),do_sort);
! for (head =0;head < count;head++) {
! printf("%s\n",strarray[head]);
! free(strarray[head]);
! }
! free(strarray);
! }
!
  
          fflush(stdout);
  
diff -r -N -c cfingerd-1.4.3/userlist/config.h.in cfingerd-1.4.3.netsoc/userlist/config.h.in
*** cfingerd-1.4.3/userlist/config.h.in Mon Aug 30 09:23:57 1999
--- cfingerd-1.4.3.netsoc/userlist/config.h.in Thu Sep 30 22:09:00 1999
***************
*** 13,16 ****
   * Linux 486/100 systems). For faster machines (such as an Alpha), try
   * 64 or 96.
   */
! #define MAX_TTYS 256
--- 13,27 ----
   * Linux 486/100 systems). For faster machines (such as an Alpha), try
   * 64 or 96.
   */
! @MAX_TTYS@
!
! /*
! * HAS_TTY_GROUP
! *
! * On modern systems the used tty's (/dev/tty*) doesn't need to be world
! * writable anymore to allow to write(1) to it. To be honest it is a
! * security hole if the tty is world writable. On newer systems the tty's
! * are owned by group tty which is allowed to write to it if mesg is set
! * to y. This define ensures that it will be detected properly.
! */
! @HAS_TTY_GROUP@
diff -r -N -c cfingerd-1.4.3/userlist/display.c cfingerd-1.4.3.netsoc/userlist/display.c
*** cfingerd-1.4.3/userlist/display.c Wed Sep 29 08:04:24 1999
--- cfingerd-1.4.3.netsoc/userlist/display.c Thu Sep 30 21:00:38 1999
***************
*** 5,10 ****
--- 5,12 ----
  
  #include "userlist.h"
  #include "proto.h"
+ #include <pwd.h>
+ #include <sys/types.h>
  
  char *inettos(long addr)
  {
***************
*** 66,76 ****
          printf ("Nobody logged in.\n");
          return;
      } else
! printf ("USERNAME Real name Idletime TTY Remote console location\n");
  
      for (i = 0; (i < times_on) && (i < MAX_TTYS); i++) {
          char console[30];
          struct passwd *pwent;
  
          if (strlen((char *) tty_list[i].locale) == 0)
              snprintf(console, sizeof(console), "(%s)", our_host);
--- 68,80 ----
          printf ("Nobody logged in.\n");
          return;
      } else
! printf ("Username Real name Idletime TTY Remote location\n");
  
      for (i = 0; (i < times_on) && (i < MAX_TTYS); i++) {
          char console[30];
          struct passwd *pwent;
+ char *cp;
+ char *x;
  
          if (strlen((char *) tty_list[i].locale) == 0)
              snprintf(console, sizeof(console), "(%s)", our_host);
***************
*** 79,85 ****
  
          if (strlen((char *) tty_list[i].username) > 1) {
              char *username=NULL;
! char ru[8], fn[STRLEN];
  
              memset(ru, 0, sizeof (ru));
              memset(fn, 0, sizeof (fn));
--- 83,89 ----
  
          if (strlen((char *) tty_list[i].username) > 1) {
              char *username=NULL;
! char ru[9], fn[STRLEN];
  
              memset(ru, 0, sizeof (ru));
              memset(fn, 0, sizeof (fn));
***************
*** 104,113 ****
                  if (!exist((char *) fn)) {
                      if (idle) {
                          printf("%-8.8s %-30.30s %8.8s ",
! ru, username?username:"", idle);
!
! printf("%3.3s %-25.25s\n",
! (char *) tty_list[i].tty, console);
                      }
                  }
              } else {
--- 108,116 ----
                  if (!exist((char *) fn)) {
                      if (idle) {
                          printf("%-8.8s %-30.30s %8.8s ",
! ru, username, idle);
! printf("%-5.5s %-24.24s\n",
! (char *) tty_list[i].tty, console);
                      }
                  }
              } else {
***************
*** 115,121 ****
                      printf("%-8.8s %-30.30s %8.8s ",
                             ru, ru, idle);
  
! printf("%3.3s %-25.25s\n",
                             (char *) tty_list[i].tty, console);
                  }
              }
--- 118,124 ----
                      printf("%-8.8s %-30.30s %8.8s ",
                             ru, ru, idle);
  
! printf("%-5.5s %-24.24s\n",
                             (char *) tty_list[i].tty, console);
                  }
              }
diff -r -N -c cfingerd-1.4.3/userlist/idle.c cfingerd-1.4.3.netsoc/userlist/idle.c
*** cfingerd-1.4.3/userlist/idle.c Thu Aug 5 23:52:37 1999
--- cfingerd-1.4.3.netsoc/userlist/idle.c Thu Sep 30 21:37:12 1999
***************
*** 27,33 ****
      stat((char *) dev_file, &buf);
      cur_time = time(NULL);
  
! diff_time = (long) cur_time - (long) buf.st_mtime;
  
      min = hour = day = 0;
  
--- 27,33 ----
      stat((char *) dev_file, &buf);
      cur_time = time(NULL);
  
! diff_time = (long) cur_time - (long) buf.st_atime;
  
      min = hour = day = 0;
  
diff -r -N -c cfingerd-1.4.3/userlist/initialize.c cfingerd-1.4.3.netsoc/userlist/initialize.c
*** cfingerd-1.4.3/userlist/initialize.c Wed Jul 28 20:28:24 1999
--- cfingerd-1.4.3.netsoc/userlist/initialize.c Thu Sep 30 22:04:29 1999
***************
*** 11,16 ****
--- 11,19 ----
  #endif
  
  #include <ctype.h>
+ #include <sys/stat.h>
+ #include <unistd.h>
+ #include <string.h>
  
  TTY_FROM tty_list[MAX_TTYS];
  int times_on;
***************
*** 33,39 ****
  #else
  #define ULIST_UNAME 8
  #endif
! #define ULIST_TTY 2
  #define ULIST_LOCALE 16
  #define ULIST_LINE 12
  
--- 36,42 ----
  #else
  #define ULIST_UNAME 8
  #endif
! #define ULIST_TTY 5
  #define ULIST_LOCALE 16
  #define ULIST_LINE 12
  
***************
*** 41,52 ****
--- 44,58 ----
  {
      struct utmp *ut;
      char *cp;
+ char ttydevice[40];
+ struct stat buf;
  
      times_on = 0;
  
      while(((ut = getutent()) != NULL) && (times_on < MAX_TTYS))
  #ifdef BSD
          {
+
  #else
          if (ut->ut_type == USER_PROCESS) {
  #endif
***************
*** 58,71 ****
              memset (tty_list[times_on].tty, 0, ULIST_TTY+1);
              memset (tty_list[times_on].locale, 0, ULIST_LOCALE+1);
              memset (tty_list[times_on].line, 0, ULIST_LINE+1);
-
  #ifdef BSD
! strncpy(tty_list[times_on].username, (char *) ut->ut_name, ULIST_UNAME);
! #else
              strncpy(tty_list[times_on].username, (char *) ut->ut_user, ULIST_UNAME);
              cp = (char *) ut->ut_line;
! if (!strncmp(cp, "tty", 3)) cp+=3; /* strip ^tty */
! strncpy(tty_list[times_on].tty, cp, ULIST_TTY);
  #ifdef SUNOS
              tty_list[times_on].ip_addr = 0;
  #else /* SUNOS */
--- 64,105 ----
              memset (tty_list[times_on].tty, 0, ULIST_TTY+1);
              memset (tty_list[times_on].locale, 0, ULIST_LOCALE+1);
              memset (tty_list[times_on].line, 0, ULIST_LINE+1);
  #ifdef BSD
! strncpy(tty_list[times_on].username, (char *) ut->ut_name, ULIST_UNAME);
! #else
              strncpy(tty_list[times_on].username, (char *) ut->ut_user, ULIST_UNAME);
              cp = (char *) ut->ut_line;
! if (strncmp(cp, "cons", 4) == 0)
! {
! strncpy(tty_list[times_on].tty, " con", 4);
! } else {
! if (!strncmp (cp , "pts", 3))
! {
! cp+=3;
! strncpy(tty_list[times_on].tty, cp, ULIST_TTY);
! } else {
! if (!strncmp(cp, "tty", 3))
! {
! cp+=3;
! strcpy(tty_list[times_on].tty," ");
! strncat(tty_list[times_on].tty, cp, ULIST_TTY-1);
! } else {
! strncpy(tty_list[times_on].tty, cp, ULIST_TTY);
! }
! }
! }
!
! snprintf(ttydevice,40 , "/dev/%s", (char *) ut->ut_line);
! stat(ttydevice, &buf);
!
! #ifdef HAS_TTY_GROUP
! if((buf.st_mode & S_IWGRP) == 0)
! #else
! if ((buf.st_mode & S_IWGRP) && (buf.st_mode & S_IWOTH))
! #endif
! tty_list[times_on].tty[0]='*';
!
!
  #ifdef SUNOS
              tty_list[times_on].ip_addr = 0;
  #else /* SUNOS */
***************
*** 80,86 ****
                  if ((cp = strrchr(tty_list[times_on].locale, '.')) != NULL)
                      *cp = '\0';
              strncpy(tty_list[times_on].line, (char *) ut->ut_line, ULIST_LINE);
-
              tty_list[times_on].time = ut->ut_time;
              times_on++;
          }
--- 114,119 ----



This archive was generated by hypermail 2.0b3 on Thu Sep 30 1999 - 23:43:16 CEST