From Johan.Persson@abc.se Wed Apr 17 10:37:35 1996
Received: from bast.livingston.com (bast.livingston.com [149.198.247.2]) by server.livingston.com (8.7.1/8.6.9) with ESMTP id KAA05799; Wed, 17 Apr 1996 10:37:32 -0700 (PDT)
Received: from bure.abc.se (bure.abc.se [192.36.170.10]) by bast.livingston.com (8.7.1/8.6.9) with SMTP id KAA07306; Wed, 17 Apr 1996 10:41:33 -0700 (PDT)
Received: by bure.abc.se (SMI-8.6/SMI-SVR4)
	id TAA21833; Wed, 17 Apr 1996 19:34:10 +0200
Date: Wed, 17 Apr 1996 19:34:04 +0200 (MET DST)
From: Johan Persson <Johan.Persson@abc.se>
X-Sender: jp@bure
To: owner-portmaster-users@livingston.com, portmaster-users@livingston.com
cc: support@livingston.com, Johan Persson <Johan.Persson@abc.se>
Subject: pmwho-1.5 && pmcom-1.1
Message-ID: <Pine.SOL.3.91rindlow-bis.960417192810.21699A-100000@bure>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII
Status: R


	Hi,

A new version of the 'pmwho' (v1.5) and 'pmcom' (v1.1) utilities is here
again. After having fixed a little bug and listened to interesting ideas for
improvement (thanks!) I have made available a new version.

The improvements in both 'pmwho' and 'pmcom' are:

	- Support for HP at compile-time.

	- A signal-bug fixed in the function 'stop_telnet' (and I was
	  sure that I had taken care of that one before ;-) ).

	- Support for different passwords on different servers (not everyone
	  wants to have the same password everywhere), provided by a 
	  structure that maps hostname->password and a default password
	  (see comments in sourcecode for instructions on how to do this).

Specific improvement to 'pmcom':

	- Will now take commandline arguments.

General (for not previous users of 'pmwho' and 'pmcom'):

	- 'pmwho' is a nice 'who' utility for the Portmaster server with the
	  ability to poll multiple servers (it issues the 'show ses' command
	  to each of the servers in question and filters by default out the
	  inactive lines (override with 'pmwho -a')). 

	- 'pmcom' allows you to send commands from a commandfile, from
	  'stdin' ('-') or from commandline arguments to the Portmaster server.
	  It is possible to use it as "pmcom pm1 'reset s3' 'show ses' ver".
	  I used it myself to define a long list of filters on our servers.

As for comments about the programs you may find them in the beginning of the
respective source-files, read them through, there are security issues that you
should be aware of before installing it somewhere ('pmcom' program could issue 
ANY command to the servers, not very good in the wrong hands). 

The programs are absolutely free, do what you like with them. 

Regards,

	/jp, Johan Persson, jp@abc.se

Ps1. Those of you running older versions of 'pmwho' and 'pmcom' should switch
     to these new versions.

Ps2. The files can also be found as:

	ftp.abc.se:/pub/pmwho/pmwho.c
	ftp.abc.se:/pub/pmcom/pmcom.c

Ps3. To the people at Livingston and others that mirror/keep a separate copy:
     Make sure that you reference the right filenames in your WWW servers,
     and that the right files are in your ftp archives.

Ps4. Problems, feedback on new ideas etc to me ("jp@abc.se").

The files, in a 'shar'-file format (type 'sh sharfile' to extract the files):

------------------------------

#!/bin/sh
# This is a shell archive (produced by GNU sharutils 4.2).
# To extract the files from this archive, save it to some FILE, remove
# everything before the `!/bin/sh' line above, then type `sh FILE'.
#
# Made on 1996-04-17 18:53 MET DST by <jp@bure>.
# Source directory was `/home/jp/portmaster'.
#
# Existing files will *not* be overwritten unless `-c' is specified.
#
# This shar contains:
# length mode       name
# ------ ---------- ------------------------------------------
#  17550 -r--r--r-- pmcom-1.1.c
#  18027 -r--r--r-- pmwho-1.5.c
#
save_IFS="${IFS}"
IFS="${IFS}:"
gettext_dir=FAILED
locale_dir=FAILED
first_param="$1"
for dir in $PATH
do
  if test "$gettext_dir" = FAILED && test -f $dir/gettext \
     && ($dir/gettext --version >/dev/null 2>&1)
  then
    set `$dir/gettext --version 2>&1`
    if test "$3" = GNU
    then
      gettext_dir=$dir
    fi
  fi
  if test "$locale_dir" = FAILED && test -f $dir/shar \
     && ($dir/shar --print-text-domain-dir >/dev/null 2>&1)
  then
    locale_dir=`$dir/shar --print-text-domain-dir`
  fi
done
IFS="$save_IFS"
if test "$locale_dir" = FAILED || test "$gettext_dir" = FAILED
then
  echo=echo
else
  TEXTDOMAINDIR=$locale_dir
  export TEXTDOMAINDIR
  TEXTDOMAIN=sharutils
  export TEXTDOMAIN
  echo="$gettext_dir/gettext -s"
fi
touch -am 1231235999 $$.touch >/dev/null 2>&1
if test ! -f 1231235999 && test -f $$.touch; then
  shar_touch=touch
else
  shar_touch=:
  echo
  $echo 'WARNING: not restoring timestamps.  Consider getting and'
  $echo "installing GNU \`touch', distributed in GNU File Utilities..."
  echo
fi
rm -f 1231235999 $$.touch
#
if mkdir _sh20834; then
  $echo 'x -' 'creating lock directory'
else
  $echo 'failed to create lock directory'
  exit 1
fi
# ============= pmcom-1.1.c ==============
if test -f 'pmcom-1.1.c' && test "$first_param" != -c; then
  $echo 'x -' SKIPPING 'pmcom-1.1.c' '(file already exists)'
else
  $echo 'x -' extracting 'pmcom-1.1.c' '(text)'
  sed 's/^X//' << 'SHAR_EOF' > 'pmcom-1.1.c' &&
/* 
X * pmcom.c - Execute commands on Livingston Portmaster servers.
X *
X * General information
X * -------------------
X * This is a utility that serves the purpose of executing commands remotely
X * on a Livingston Portmaster server. It takes care of logging on, issuing the
X * commands (from a commandfile, 'stdin' or from the commandline) and then 
X * logging off. The commandfile (and stdin) can use '#' as start of comments, 
X * leading and trailing whitespace are filtered away. This is what a command-
X * file may look like:
X *
X *   $ cat cmdfile
X *   # pmcom script to show status of our Livingston Portmaster servers
X *
X *   show ether0
X *   show all
X *   show netstat
X *   show global
X *   show memory
X *   show netconns
X *   $
X *
X * The usage of 'pmcom' is like this:
X *
X *   $ ./pmcom
X *   usage: pmcom [-vch] server [commandfile | - | commands]
X *   $
X *
X * where 'commandfile' is the name of a commandfile ('-' = stdin), 'commands'
X * are normal commandline arguments (except that each argument is a command to
X * send to the server which means that you may have to quote some of them, you
X * must also specify the option '-c' to indicate that the arguments are indeed 
X * commands) and '-v' means to output the commands given in the session as 
X * well as the results of them (the default is to only show the results and 
X * not the commands; error messages are output to 'stderr' in any case).
X *
X * This is how to send the commands using a commandfile:
X * 
X *   $ ./pmcom -v pm1 cmdfile
X *   < the resulting output >
X *   $
X *
X * and by using commandline arguments:
X *
X *   $ ./pmcom -vc pm1 'reset s7' 'sh ses' ver
X *   < the resulting output >
X *   $
X *
X * One thing to remember in the commandfile (or in the commandline arguments)
X * is to forget about the "quit" command since 'pmcom' will automatically 
X * issue that command at the end of the commandfile (or end of the commandline-
X * arguments). This utility could for instance be used in a 'cron' script 
X * to check status regularly or to setup other lengthy things such as filters.
X *
X * This utility should be protected *very* carefully, preferably only available
X * to the administrators that handle the Portmaster server since anyone who 
X * can run it may give *any* command to the server (including changing setup, 
X * reset ports etc.). Please think twice on where to put it and when setting 
X * the protection mode (something like protection mode 110 ("--x--x---") or
X * 100 ("--x------") with the appropriate user/group ownership sounds 
X * reasonable, remember that the password used to logon to the server can be
X * found in the executable and that utilities such as 'trace' (SunOS 4.x),
X * 'truss' (Solaris 2.x), 'strace' (Linux) may be used to find it). 
X * An ANSI-C compiler is required to compile the program. The program is 
X * completely free, do what you like with it.
X *
X * How it actually works
X * ---------------------
X * It starts a 'telnet' session to the Portmaster server, issues the commands 
X * to login, the commands from the commandfile and then logs out. The 
X * information is passed to the parent process through pipes (the parent 
X * process has a pipe through which it can send commands to the 'stdin' of the 
X * 'telnet process, and another pipe through which it receives the output from 
X * the 'stdout' (and 'stderr') of the 'telnet' process).
X *
X * Configuration options
X * ---------------------
X * Before building the program you should check out (and modify if necessary) 
X * the values of the constants to match your own configuration (good default 
X * values are already provided for SunOS 4.x, Solaris 2.x, DNIX and Linux).
X *
X * PASSWORD - a string, the default password to use for user '!root' if
X *            the hostname is not defined in the 'struct pwtable'.
X * TELNET_EXEC - a string, the path to the executable 'telnet'.
X * TELNET_CLOSE - a string, the leading char's in the string that 'telnet' 
X *                sends when a connection is closed.
X * TELNET_PORT - a string, the port to use when starting 'telnet'.
X * TELNET_PROMPT - a string, the prompt of telnet.
X * HAVE_SYS_WAIT, HAVE_WAIT - #define only one of these to indicate which
X *                            headerfile you have (<sys/wait.h> _or_ <wait.h>,
X *                            in that order), if none of them is #define-d a 
X *                            generic definition be used.
X *
X * Building the utility
X * --------------------
X * - Using SunOS 4.x:
X *   $ gcc -DSUNOS4 pmcom.c -o pmcom
X *   $ chmod 111 pmcom
X *
X * - Using Solaris 2.x:
X *   $ gcc -DSOLARIS pmcom.c -o pmcom
X *   $ chmod 111 pmcom
X *
X * - Using DNIX:
X *   $ cc -DDNIX pmcom.c -o pmcom
X *   $ chmod 111 pmcom
X *
X * - Using Linux:
X *   $ gcc -DLINUX pmcom.c -o pmcom
X *   $ chmod 111 pmcom
X *
X * - Using HP-UX 9.0x
X *   $ cc -DHPUX -D_INCLUDE_POSIX_SOURCE pmcom.c -o pmcom -O -Aa
X *   $ chmod 111 pmcom
X *
X * Version   Date   Comments
X * -------  ------  ----------------------------------------------------------
X *   1.0    960113  First release, copy lots from 'pmwho.c' v1.4.
X *                  Bugs: 1. Only one password is used.
X *                        2. If "dup" or "execl" fails (in the child) the
X *                           'stderr' is closed.
X *
X *   1.1    960404  Fixed bug #1 above. Can also take input from commandline
X *                  arguments and have #defines for compile on HP systems.
X *                  Signal-handling bug fixed.
X *
X * Comments on how to improve this simple utility are very much welcome.
X *
X * /jp, Johan Persson, jp@abc.se
X *
X */
X
/* configuration constants - make your individual settings here */
X
#define PASSWORD        "secret!" /* the default password to use */
X
/* 
X * a password table structure with the purpose of being able to have different
X * passwords on different servers. If you only want to have one single password
X * you do not have to change anything, it will work as expected by default 
X * (by using the password PASSWORD as defined by the constant a few lines up).
X *
X * If you do however want to be able to have different passwords, then you must
X * enter the appropriate values below (I have added an example for the test-
X * server by the name of "pm1", uncomment and change as appropriate). Don't
X * forget that the constant PASSWORD is still the default if the hostname is
X * not found in the table.
X *
X */
X   
struct {
X  char hostname[32];
X  char password[32];
} pwtable[] = {
X  /* { "pm1", "topsecret" } */
};
X
#if defined(SUNOS4) || defined(SOLARIS)
#define TELNET_EXEC     "/usr/ucb/telnet"
#define TELNET_CLOSE    "Connection closed"
#define TELNET_PORT     "23"
#define TELNET_PROMPT   "telnet> "
#define HAVE_SYS_WAIT
#elif defined(DNIX) || defined(HPUX)
#define TELNET_EXEC     "/usr/bin/telnet"
#define TELNET_CLOSE    "Trying"
#define TELNET_PORT     "23"
#define TELNET_PROMPT   "telnet> "
#define HAVE_SYS_WAIT
#elif defined(LINUX)
#define TELNET_EXEC     "/bin/telnet"
#define TELNET_CLOSE    "Connection closed"
#define TELNET_PORT     "23"
#define TELNET_PROMPT   "telnet> "
#define HAVE_SYS_WAIT
#else /* generic entry */
#define TELNET_EXEC     "/usr/ucb/telnet"
#define TELNET_CLOSE    "Connection closed"
#define TELNET_PORT     "23"
#define TELNET_PROMPT   "telnet> "
/* #define HAVE_SYS_WAIT */
/* #define HAVE_WAIT */
#endif
X
/* header files */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
X
#if defined(HPUX)
#include <unistd.h>
#define NSIG _NSIG
#endif
X
#include <sys/types.h>
X
#if defined(HAVE_SYS_WAIT)
#include <sys/wait.h>
#elif defined(HAVE_WAIT)
#include <wait.h>
#else
extern pid_t wait(int *status);
#endif
X
#include <errno.h>
#include <signal.h>
X
/* program constants and global vars */
char  *progname;
char  *hostname;
char  *filename;
pid_t pid;
int   got_alarm;
int   verbose;
int   cmdline;
int   cmdc;
char  **cmdv;
X
#define LINELEN  256
#define IDLE_POS  50
X
/* program declarations */
void main(int argc, char *argv[]);
void usage(void);
void pmcom(FILE *fp);
int  do_line(char *t);
int  do_read(int fd, char *text1, char *text2, int show, int timeout);
int  do_write(int fd, char *text, int timeout);
void sigalrm(int signo);
void sigfunc(int signo);
void err_msg(const char *format, ...);
void err_exit(const char *format, ...);
void stop_telnet(void);
X
/* the program */
X
/* main procedure, find out program name and the hostname to use */
void main(int argc, char *argv[])
{
X  char *t;
X  int n, ch;
X  FILE *fp;
X
X  extern int optind;
X
X  t = strrchr(argv[0], '/');
X  progname = (t != NULL) ? t+1 : argv[0];
X  hostname = NULL;
X  pid = -1;
X  verbose = cmdline = 0;
X
X  while ((ch = getopt(argc, argv, "cvh")) != -1) {
X    switch (ch) {
X      case 'v':
X        verbose = 1;
X	break;
X      case 'c':
X	cmdline = 1;
X	break;
X      case 'h':
X	/* fall-through */
X      default:
X	usage();
X    }
X  }
X
X  if (optind >= argc) usage();
X  hostname = argv[optind++];
X  if (optind >= argc) usage();
X
X  /* setup signals properly */
X  for(n = 1; n < NSIG; n++) signal(n, sigfunc);
#if defined(SIGCHLD)
X  signal(SIGCHLD, SIG_IGN);
#endif
#if defined(SIGCLD)
X  signal(SIGCLD, SIG_IGN);
#endif
X
X  if (cmdline) {
X    cmdv = &argv[optind];
X    cmdc = argc - optind;
X    pmcom(NULL);
X  }
X  else if (strcmp(argv[optind], "-") == 0) {
X    filename = "(stdin)";
X    pmcom(stdin);
X  }
X  else {
X    filename = argv[optind];
X    fp = fopen(filename, "r");
X    if (!fp) {
X      err_exit("%s: cannot open commandfile '%s'\n", progname, filename);
X    }
X    pmcom(fp);
X    fclose(fp);
X  }
X
X  fputc('\n', stdout);
X  exit(0);
}
X
/* show usage info */
void usage(void)
{
X  fprintf(stderr, "usage: %s [-vch] server [commandfile | - | commands]\n", progname);
X  exit(0);
}
X
/* retrieve the information from the host by logging on, giving the command */
/* "show session" and logging out from 'hostname'                           */
void pmcom(FILE *fp)
{
X  int fdpipe[2][2], fdin, fdout, fderr, err, n;
X
X  got_alarm = 0;
X
X  /* we need pipes to communicate between the programs */
X  if (pipe(fdpipe[0]) < 0) {
X    err_msg("%s: pipe failed: errno=%d\n", progname, errno);
X    return;
X  }
X  if (pipe(fdpipe[1]) < 0) {
X    err_msg("%s: pipe failed: errno=%d\n", progname, errno);
X    close(fdpipe[0][0]);
X    close(fdpipe[0][1]);
X    return;
X  }
X
X  switch(pid = fork()) {
X    case 0: /* child */
X      /* child:stdin */
X      close(0);
X      if (dup(fdpipe[0][0]) < 0) {
X	err_exit("%s: dup failed: errno=%d\n", progname, errno);
X      }
X
X      /* close first pipe */
X      close(fdpipe[0][0]);
X      close(fdpipe[0][1]);
X
X      /* child:stdout */
X      close(1);
X      if (dup(fdpipe[1][1]) < 0) {
X	err_exit("%s: dup failed: errno=%d\n", progname, errno);
X      }
X
X      /* child:stderr */
X      if ((fderr = dup(2)) < 0) {
X	err_exit("%s: dup failed: errno=%d\n", progname, errno);
X      }
X
X      close(2);
X      if (dup(fdpipe[1][1]) < 0) {
X	err = errno;
X	dup(fderr);
X	err_exit("%s: dup failed: errno=%d\n", progname, err);
X      }
X
X      /* close second pipe */
X      close(fdpipe[1][0]);
X      close(fdpipe[1][1]);
X
X      /* exec TELNET application */
X      execl(TELNET_EXEC, "telnet", hostname, TELNET_PORT, (char*)NULL);
X
X      /* if we're still here the TELNET_EXEC could not be exec'd */
X      err = errno;
X      close(2);
X      dup(fderr);
X      err_exit("%s: execl(%s) failed: errno=%d\n", progname, TELNET_EXEC, err);
X      break;
X
X    case -1: /* error */
X      err_exit("%s: fork failed: errno=%d\n", progname, errno);
X      return;
X
X    default: /* parent */
X      /* close the childs end of the pipes */
X      close(fdpipe[0][0]);
X      close(fdpipe[1][1]);
X      break;
X  }
X
X  /* now communicate with the 'telnet' process */
X  fdin = fdpipe[1][0];
X  fdout = fdpipe[0][1];
X
X  for(;;) {
X    n = do_read(fdin, "login: ", TELNET_PROMPT, 0, 15);
X    if (n != 1) {
X      n = -1; /* TELNET_PROMPT == connection failed */
X      break;
X    }
X    if ((n = do_write(fdout, "!root\n", 5)) < 0) break;
X
X    if ((n = do_read(fdin, "Password: ", NULL, 0, 10)) < 0) break;
X
X    {
X      char *pw = PASSWORD;
X      int k;
X
X      for(k = 0; k < sizeof(pwtable)/sizeof(pwtable[0]); k++) {
X	if (strcmp(pwtable[k].hostname, hostname) == 0) {
X	  pw = pwtable[k].password;
X	  break;
X	}
X      }
X
X      if ((n = do_write(fdout, pw, 5)) < 0) break;
X    }
X
X    if ((n = do_write(fdout, "\n", 5)) < 0) break;
X
X    if ((n = do_read(fdin, "> ", NULL, 0, 10)) < 0) break;
X
X    if (cmdline) {
X      char buf[BUFSIZ];
X      int m;
X
X      for(m = 0; m < cmdc; m++) {
X	strcpy(buf, cmdv[m]);
X	if (do_line(buf) == 0) continue;
X	if ((n = do_write(fdout, buf, 5)) < 0) break;
X	if ((n = do_write(fdout, "\n", 5)) < 0) break;
X      
X	for(;;) {
X	  n = do_read(fdin, "-- Press Return for More -- ", "> ", 1, 20);
X	  if ((n < 0) || (n == 2)) break;
X	  if ((n = do_write(fdout, "\n", 5)) < 0) break;
X	}
X	if (n < 0) break;
X      }
X    }
X    else {
X      char buf[BUFSIZ];
X
X      while (fgets(buf, sizeof(buf)-1, fp)) {
X	if (do_line(buf) == 0) continue;
X	if ((n = do_write(fdout, buf, 5)) < 0) break;
X	if ((n = do_write(fdout, "\n", 5)) < 0) break;
X      
X	for(;;) {
X	  n = do_read(fdin, "-- Press Return for More -- ", "> ", 1, 20);
X	  if ((n < 0) || (n == 2)) break;
X	  if ((n = do_write(fdout, "\n", 5)) < 0) break;
X	}
X	if (n < 0) break;
X      }
X    }
X
X    if ((n = do_write(fdout, "quit\n", 5)) < 0) break;
X    
X    if ((n = do_read(fdin, TELNET_CLOSE, NULL, 0, 20)) < 0) break;
X
X    break;
X  }
X
X  close(fdin);
X  close(fdout);
X
X  if ((n < 0) && (got_alarm == 0)) {
X    err_msg("%s: connection to host '%s' failed\n", progname, hostname);
X  }
X
X  stop_telnet();
}
X
/* prepare a line that is to be sent to the portmaster server as a command */
int do_line(char *t)
{
X  char *p;
X  char buf[BUFSIZ];
X  int n;
X
X  if ((p = strchr(t, '\n')) != NULL) *p = '\0';
X  if ((p = strchr(t, '\r')) != NULL) *p = '\0';
X
X  strcpy(buf, t);
X  if ((p = strchr(buf, '#')) != NULL) *p = '\0';
X  if ((n = strlen(buf)) == 0) return n;
X
X  while (--n >= 0) {
X    if ((buf[n] != ' ') && (buf[n] != '\t')) break;
X    buf[n] = '\0';
X  }
X
X  for(p = buf; (*p == ' ') || (*p == '\t'); p++) ;
X
X  strcpy(t, p);
X
X  return strlen(t);
}
X
/* read information from 'telnet', stop reading upon errors, on a timeout */
/* and when the beginning of a line equals to 'text1' (and then return 1) */
/* or when the beginning of a line equals to 'text2' (and then return 2), */
/* if 'show' is non zero we display the information to the screen         */
/* (using 'stdout').                                                      */
int do_read(int fd, char *text1, char *text2, int show, int timeout)
{
X  char line[LINELEN+1];
X  int n, len1, len2, count = 0, m, err;
X
X  /* setup alarm (so we won't hang forever upon problems) */
X  signal(SIGALRM, sigalrm);
X  alarm(timeout);
X  
X  len1 = strlen(text1);
X  len2 = (text2 != NULL) ? strlen(text2) : 0;
X
X  /* start reading from 'telnet' */
X  for(;;) {
X    n = 0;
X    for(;;) {
X      if (n == LINELEN) {
X	alarm(0); /* disable alarm */
X	stop_telnet();
X	err_exit("%s: too long line!\n", progname);
X      }
X      m = read(fd, &line[n], 1);
X      if (m != 1) {
X	err = errno;
X	alarm(0); /* disable alarm */
X	return(-1);
X      }
X      if (verbose) fputc(0xff & (int)line[n], stdout);
X      if (line[n] == '\r') continue;
X      if (n >= len1-1) {
X	if (strncmp(&line[n-(len1-1)], text1, len1) == 0) {
X	  /* we found the keyword we were searching for */
X	  alarm(0); /* disable alarm */
X	  return(1);
X	}
X      }
X      if ((text2 != NULL) && (n >= len2-1)) {
X	if (strncmp(&line[n-(len2-1)], text2, len2) == 0) {
X	  /* we found the keyword we were searching for */
X	  alarm(0); /* disable alarm */
X	  return(2);
X	}
X      }
X      if (line[n] == '\n') break;
X      n++;
X    }
X    if (show && !verbose) {
X      line[++n] = '\0';
X      if (++count > 1) {
X	/* the very first line of information is the remains of  */
X	/* a previously issued command and should be ignored.    */
X
X	fputs(line, stdout);
X	fflush(stdout);
X      }
X    }
X  }
}
X
/* write a command to the 'telnet' process */
int do_write(int fd, char *text, int timeout)
{
X  int err, len, n;
X
X  /* setup alarm (so we won't hang forever upon problems) */
X  signal(SIGALRM, sigalrm);
X  alarm(timeout);
X
X  len = strlen(text);
X  n = write(fd, text, len);
X  if (n != len) {
X    err = errno;
X    alarm(0); /* disable alarm */
X    return(-1);
X  }
X  else {
X    alarm(0); /* disable alarm */
X    return(0);
X  }
}
X
/* our timeout procedure, used to abort malfunctioning connections */
void sigalrm(int signo)
{
X  got_alarm = 1;
X  err_msg("%s: timeout on connection to host '%s'\n", progname, hostname);
}
X
/* handle the reception of signals */
void sigfunc(int signo)
{
#if defined(SIGPIPE)
X  if (signo != SIGPIPE)
#endif
X  {
X    err_msg("%s: received signal #%d during connection to host '%s' -- aborting\n", progname, signo, hostname);
X  }
X
X  stop_telnet();
X  exit(1);
}
X
/* print error text */
void err_msg(const char *format, ...)
{
X  va_list ap;
X
X  /* show error message */
X  va_start(ap, format);
X  vfprintf(stderr, format, ap);
X  va_end(ap);
X  fflush(stderr);
}
X
/* print error text and exit gracefully */
void err_exit(const char *format, ...)
{
X  va_list ap;
X
X  /* show error message */
X  va_start(ap, format);
X  vfprintf(stderr, format, ap);
X  va_end(ap);
X  fflush(stderr);
X
X  /* exit with error */
X  exit(1);
}
X
/* try to terminate the telnet-child that we started */
void stop_telnet(void)
{
X  pid_t p;
X
X  if ((pid != -1) && (pid != 0)) {
X    if (kill(pid, 0) >= 0) {
X      kill(pid, SIGTERM);
X      kill(pid, SIGKILL);
X
X      if (kill(pid, 0) >= 0) {
X	for(p = wait(0); (p != -1) && (p != pid); p = wait(0)) ; /* zombies */
X      }
X    }
X    pid = -1;
X  }
}
SHAR_EOF
  $shar_touch -am 0404172296 'pmcom-1.1.c' &&
  chmod 0444 'pmcom-1.1.c' ||
  $echo 'restore of' 'pmcom-1.1.c' 'failed'
  if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \
  && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then
    md5sum -c << SHAR_EOF >/dev/null 2>&1 \
    || $echo 'pmcom-1.1.c:' 'MD5 check failed'
721ca8d0c2bed39e37e3e99b1eaaa85b  pmcom-1.1.c
SHAR_EOF
  else
    shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'pmcom-1.1.c'`"
    test 17550 -eq "$shar_count" ||
    $echo 'pmcom-1.1.c:' 'original size' '17550,' 'current size' "$shar_count!"
  fi
fi
# ============= pmwho-1.5.c ==============
if test -f 'pmwho-1.5.c' && test "$first_param" != -c; then
  $echo 'x -' SKIPPING 'pmwho-1.5.c' '(file already exists)'
else
  $echo 'x -' extracting 'pmwho-1.5.c' '(text)'
  sed 's/^X//' << 'SHAR_EOF' > 'pmwho-1.5.c' &&
/* 
X * pmwho.c - Show logged-on users on Livingston Portmaster servers.
X *
X * General information
X * -------------------
X * This is a utility that serves the purpose of showing the status on the 
X * available ports on a Livingston Portmaster server. It takes care of logging 
X * on and issuing the commands to retrieve the information. The main advantage
X * with this utility is that it can be run by any user and only the program 
X * itself knows about the password. When Livingston incorporates a finger-
X * daemon of some sort into the Portmaster server this utility will no longer
X * be needed. An ANSI-C compiler is required to compile the program. The 
X * program is completely free, do what you like with it.
X *
X * Usage
X * -----
X * - If the program is named 'PMWHO' (constant defined below) and it is started
X *   like 'PMWHO pm1 pm2 pm3 ...' it will use 'pm1' ... as the hostnames to
X *   connect to (yes, it will connect to as many hosts as you specify, good if
X *   you have several Portmaster server's at your site).
X * - If the program is named 'PMWHO' and it is started like 'PMWHO' it will 
X *   use 'PMWHO_DEFAULT' (constant defined below) as the hostname to connect 
X *   to.
X * - If the program is named 'pm1' and it is started like 'pm1' it will use 
X *   'pm1' as the hostname to connect to.
X * - The program has two options with the following meaning attached to them:
X *   'h' - (it is started like 'PMWHO -h'), show usage.
X *   'a' - (it is started like 'PMWHO -a hosts ...'), show all serial ports 
X *         (including ports that are at present IDLE).
X *
X * Installation
X * ------------
X * The program should be made protection mode 111 ("--x--x--x", executable 
X * only) and the source-code should be made protection mode 400 ("r--------", 
X * read-only to owner (user 'root')) since the password could otherwise easily 
X * be found. You might even consider not having the source-code online after 
X * it has been compiled, and please do not set the password on your other 
X * systems to the same as that of the Portmaster server! There are so many 
X * ways of breaching security that the possibility of someone being able to 
X * copy the program and finding out the password to the Portmaster server must
X * be considered (one way would be to make the executable file readable and 
X * running 'trace' (SunOS 4.x) or 'truss' (Solaris 2.x) on it and watch the 
X * output).
X *
X * How it actually works
X * ---------------------
X * It starts a 'telnet' session to the Portmaster server, issues the commands 
X * to login, show the information and then logs out. The information is passed
X * to the parent process through pipes (the parent process has a pipe through 
X * which it can send commands to the 'stdin' of the 'telnet process, and 
X * another pipe through which it receives the output from the 'stdout' (and
X * 'stderr') of the 'telnet' process).
X *
X * Configuration options
X * ---------------------
X * Before building the program you should check out (and modify if necessary) 
X * the values of the constants to match your own configuration (good default 
X * values are already provided for SunOS 4.x, Solaris 2.x, DNIX and Linux).
X *
X * PMWHO - a string, default name of this utility.
X * PMWHO_DEFAULT - a string, default host to connect to.
X * PASSWORD - a string, the default password to use for user '!root' if
X *            the hostname is not defined in the 'struct pwtable'.
X * TELNET_EXEC - a string, the path to the executable 'telnet'.
X * TELNET_CLOSE - a string, the leading char's in the string that 'telnet' 
X *                sends when a connection is closed.
X * TELNET_PORT - a string, the port to use when starting 'telnet'.
X * TELNET_PROMPT - a string, the prompt of telnet.
X * SHOW_PRINTER - #define this if you want to see the information
X *                on the parallel port as well (it is otherwise ignored).
X * HAVE_SYS_WAIT, HAVE_WAIT - #define only one of these to indicate which
X *                            headerfile you have (<sys/wait.h> _or_ <wait.h>,
X *                            in that order), if none of them is #define-d a 
X *                            generic definition be used.
X *
X * Building the utility
X * --------------------
X * - Using SunOS 4.x:
X *   $ gcc -DSUNOS4 pmwho.c -o pmwho
X *   $ chmod 111 pmwho
X *
X * - Using Solaris 2.x:
X *   $ gcc -DSOLARIS pmwho.c -o pmwho
X *   $ chmod 111 pmwho
X *
X * - Using DNIX:
X *   $ cc -DDNIX pmwho.c -o pmwho
X *   $ chmod 111 pmwho
X *
X * - Using Linux:
X *   $ gcc -DLINUX pmwho.c -o pmwho
X *   $ chmod 111 pmwho
X *
X * - Using HP-UX 9.0x
X *   $ cc -DHPUX -D_INCLUDE_POSIX_SOURCE pmwho.c -o pmwho -O -Aa
X *   $ chmod 111 pmwho
X *
X * Version   Date   Comments
X * -------  ------  ----------------------------------------------------------
X *   1.0    950531  First release.
X *                  Bugs: 1. Only one password is used.
X *                        2. If "dup" or "execl" fails (in the child) the
X *                           'stderr' is closed.
X *
X *   1.1    950604  - Added PROMPT constant for those not using "Command> " as
X *                    prompt. 
X *                  - The information shown is changed, now only the active 
X *                    ports are shown unless the "-a" (show all ports 
X *                    regardless of state) option is given.
X *                  - program uses 'getopt' to check for option flags.
X *
X *   1.2    950706  - Portmaster server's with only 10 ports do not get the
X *                    "-- Press Return for More -- " message, solved in a 
X *                    generic way.
X *                  - An attempt to fix bug #2 (stderr).
X *                  - Added support for Linux.
X *                  - PROMPT replaced by "> " since we have to handle both 
X *                    a prompt of type "Command> " and "$hostname> ", this
X *                    will probably mean that we will run into trouble when
X *                    someone enters a username with "> " in it...
X *
X *   1.3    950904  - Improved the description part.
X *                  - Connects to multiple hosts.
X *
X *   1.4    950930  - Takes care of signal handling (v1.3 could leave a 
X *                    'telnet' process behind...).
X *
X *   1.5    960404  - Fixed bug #1 above. 
X *                  - Have #defines for compile on HP systems.
X *                  - Signal-handling bug fixed.
X *
X * Comments on how to improve this simple utility are very much welcome.
X *
X * /jp, Johan Persson, jp@abc.se
X *
X */
X
/* configuration constants - make your individual settings here */
#define PMWHO           "pmwho"
#define PMWHO_DEFAULT   "pm1"
/* #define SHOW_PRINTER */
X
/* configuration constants - make your individual settings here */
X
#define PASSWORD        "secret!" /* the default password to use */
X
/* 
X * a password table structure with the purpose of being able to have different
X * passwords on different servers. If you only want to have one single password
X * you do not have to change anything, it will work as expected by default 
X * (by using the password PASSWORD as defined by the constant a few lines up).
X *
X * If you do however want to be able to have different passwords, then you must
X * enter the appropriate values below (I have added an example for the test-
X * server by the name of "pm1", uncomment and change as appropriate). Don't
X * forget that the constant PASSWORD is still the default if the hostname is
X * not found in the table.
X *
X */
X   
struct {
X  char hostname[32];
X  char password[32];
} pwtable[] = {
X  /* { "pm1", "topsecret" } */
};
X
#if defined(SUNOS4) || defined(SOLARIS)
#define TELNET_EXEC     "/usr/ucb/telnet"
#define TELNET_CLOSE    "Connection closed"
#define TELNET_PORT     "23"
#define TELNET_PROMPT   "telnet> "
#define HAVE_SYS_WAIT
#elif defined(DNIX) || defined(HPUX)
#define TELNET_EXEC     "/usr/bin/telnet"
#define TELNET_CLOSE    "Trying"
#define TELNET_PORT     "23"
#define TELNET_PROMPT   "telnet> "
#define HAVE_SYS_WAIT
#elif defined(LINUX)
#define TELNET_EXEC     "/bin/telnet"
#define TELNET_CLOSE    "Connection closed"
#define TELNET_PORT     "23"
#define TELNET_PROMPT   "telnet> "
#define HAVE_SYS_WAIT
#else /* generic entry */
#define TELNET_EXEC     "/usr/ucb/telnet"
#define TELNET_CLOSE    "Connection closed"
#define TELNET_PORT     "23"
#define TELNET_PROMPT   "telnet> "
/* #define HAVE_SYS_WAIT */
/* #define HAVE_WAIT */
#endif
X
/* header files */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
X
#if defined(HPUX)
#include <unistd.h>
#define NSIG _NSIG
#endif
X
#include <sys/types.h>
X
#if defined(HAVE_SYS_WAIT)
#include <sys/wait.h>
#elif defined(HAVE_WAIT)
#include <wait.h>
#else
extern pid_t wait(int *status);
#endif
X
#include <errno.h>
#include <signal.h>
X
/* program constants and global vars */
char  *progname;
char  *hostname;
pid_t pid;
int   showall;
int   got_alarm;
X
#define LINELEN  256
#define IDLE_POS  50
X
/* program declarations */
void main(int argc, char *argv[]);
void usage(void);
void pmwho(void);
int  do_read(int fd, char *text1, char *text2, int show, int timeout);
int  do_write(int fd, char *text, int timeout);
void sigalrm(int signo);
void sigfunc(int signo);
void err_msg(const char *format, ...);
void err_exit(const char *format, ...);
void stop_telnet(void);
X
/* the program */
X
/* main procedure, find out program name and the hostname to use */
void main(int argc, char *argv[])
{
X  char *t;
X  int ch, n;
X
X  extern int optind;
X
X  t = strrchr(argv[0], '/');
X  progname = (t != NULL) ? t+1 : argv[0];
X  hostname = NULL;
X  pid = -1;
X  showall = 0;
X
X  while ((ch = getopt(argc, argv, "ah")) != -1) {
X    switch (ch) {
X      case 'a':
X	showall = 1;
X	break;
X      case 'h':
X	/* fall-through */
X      default:
X	usage();
X    }
X  }
X
X  /* setup signals properly */
X  for(n = 1; n < NSIG; n++) signal(n, sigfunc);
#if defined(SIGCHLD)
X  signal(SIGCHLD, SIG_IGN);
#endif
#if defined(SIGCLD)
X  signal(SIGCLD, SIG_IGN);
#endif
X
X  /* take different action according to usage */
X  if (strcmp(progname, PMWHO) == 0) { /* PMWHO */
X    if (optind >= argc) { /* usage: 'PMWHO' */
X      hostname = PMWHO_DEFAULT;
X      pmwho();
X    }
X    else if (optind == argc-1) { /* usage: 'PMWHO hostname' */
X      hostname = argv[optind];
X      pmwho();
X    }
X    else { /* usage: 'PMWHO hostname1 hostname2 ...' */
X      for(; optind < argc; optind++) {
X	hostname = argv[optind];
X	fprintf(stdout, "[%s]\n", hostname);
X	fflush(stdout);
X	pmwho();
X	if (optind < argc-1) {
X	  fprintf(stdout, "\n");
X	  fflush(stdout);
X	}
X      }
X    }
X  }
X  else { /* usage: 'hostname' */
X    hostname = progname;
X    progname = PMWHO; /* for the messages */
X    pmwho();
X  }
X  exit(0);
}
X
/* show usage info */
void usage(void)
{
X  fprintf(stderr, "usage: %s [-ah]\n", progname);
X  exit(0);
}
X
/* retrieve the information from the host by logging on, giving the command */
/* "show session" and logging out from 'hostname'                           */
void pmwho()
{
X  int fdpipe[2][2], fdin, fdout, fderr, err, n;
X
X  got_alarm = 0;
X
X  /* we need pipes to communicate between the programs */
X  if (pipe(fdpipe[0]) < 0) {
X    err_msg("%s: pipe failed: errno=%d\n", progname, errno);
X    return;
X  }
X  if (pipe(fdpipe[1]) < 0) {
X    err_msg("%s: pipe failed: errno=%d\n", progname, errno);
X    close(fdpipe[0][0]);
X    close(fdpipe[0][1]);
X    return;
X  }
X
X  switch(pid = fork()) {
X    case 0: /* child */
X      /* child:stdin */
X      close(0);
X      if (dup(fdpipe[0][0]) < 0) {
X	err_exit("%s: dup failed: errno=%d\n", progname, errno);
X      }
X
X      /* close first pipe */
X      close(fdpipe[0][0]);
X      close(fdpipe[0][1]);
X
X      /* child:stdout */
X      close(1);
X      if (dup(fdpipe[1][1]) < 0) {
X	err_exit("%s: dup failed: errno=%d\n", progname, errno);
X      }
X
X      /* child:stderr */
X      if ((fderr = dup(2)) < 0) {
X	err_exit("%s: dup failed: errno=%d\n", progname, errno);
X      }
X
X      close(2);
X      if (dup(fdpipe[1][1]) < 0) {
X	err = errno;
X	dup(fderr);
X	err_exit("%s: dup failed: errno=%d\n", progname, err);
X      }
X
X      /* close second pipe */
X      close(fdpipe[1][0]);
X      close(fdpipe[1][1]);
X
X      /* exec TELNET application */
X      execl(TELNET_EXEC, "telnet", hostname, TELNET_PORT, (char*)NULL);
X
X      /* if we're still here the TELNET_EXEC could not be exec'd */
X      err = errno;
X      close(2);
X      dup(fderr);
X      err_exit("%s: execl(%s) failed: errno=%d\n", progname, TELNET_EXEC, err);
X      break;
X
X    case -1: /* error */
X      err_exit("%s: fork failed: errno=%d\n", progname, errno);
X      return;
X
X    default: /* parent */
X      /* close the childs end of the pipes */
X      close(fdpipe[0][0]);
X      close(fdpipe[1][1]);
X      break;
X  }
X
X  /* now communicate with the 'telnet' process */
X  fdin = fdpipe[1][0];
X  fdout = fdpipe[0][1];
X
X  for(;;) {
X    n = do_read(fdin, "login: ", TELNET_PROMPT, 0, 15);
X    if (n != 1) {
X      n = -1; /* TELNET_PROMPT == connection failed */
X      break;
X    }
X    if ((n = do_write(fdout, "!root\n", 5)) < 0) break;
X
X    if ((n = do_read(fdin, "Password: ", NULL, 0, 10)) < 0) break;
X
X    {
X      char *pw = PASSWORD;
X      int k;
X
X      for(k = 0; k < sizeof(pwtable)/sizeof(pwtable[0]); k++) {
X	if (strcmp(pwtable[k].hostname, hostname) == 0) {
X	  pw = pwtable[k].password;
X	  break;
X	}
X      }
X
X      if ((n = do_write(fdout, pw, 5)) < 0) break;
X    }
X
X    if ((n = do_write(fdout, "\n", 5)) < 0) break;
X
X    if ((n = do_read(fdin, "> ", NULL, 0, 10)) < 0) break;
X    if ((n = do_write(fdout, "show session\n", 5)) < 0) break;
X
X    for(;;) {
X      n = do_read(fdin, "-- Press Return for More -- ", "> ", 1, 20);
X      if ((n < 0) || (n == 2)) break;
X      if ((n = do_write(fdout, "\n", 5)) < 0) break;
X    }
X    if (n < 0) break;
X
X    if ((n = do_write(fdout, "quit\n", 5)) < 0) break;
X    
X    if ((n = do_read(fdin, TELNET_CLOSE, NULL, 0, 20)) < 0) break;
X
X    break;
X  }
X
X  close(fdin);
X  close(fdout);
X
X  if ((n < 0) && (got_alarm == 0)) {
X    err_msg("%s: connection to host '%s' failed\n", progname, hostname);
X  }
X
X  stop_telnet();
}
X
/* read information from 'telnet', stop reading upon errors, on a timeout */
/* and when the beginning of a line equals to 'text1' (and then return 1) */
/* or when the beginning of a line equals to 'text2' (and then return 2), */
/* if 'show' is non zero we display the information to the screen         */
/* (using 'stdout').                                                      */
int do_read(int fd, char *text1, char *text2, int show, int timeout)
{
X  char line[LINELEN+1];
X  int n, len1, len2, count = 0, m, err;
X
X  /* setup alarm (so we won't hang forever upon problems) */
X  signal(SIGALRM, sigalrm);
X  alarm(timeout);
X  
X  len1 = strlen(text1);
X  len2 = (text2 != NULL) ? strlen(text2) : 0;
X
X  /* start reading from 'telnet' */
X  for(;;) {
X    n = 0;
X    for(;;) {
X      if (n == LINELEN) {
X	alarm(0); /* disable alarm */
X	stop_telnet();
X	err_exit("%s: too long line!\n", progname);
X      }
X      m = read(fd, &line[n], 1);
X      if (m != 1) {
X	err = errno;
X	alarm(0); /* disable alarm */
#if 0
X	if (m < 0) {
X	  err_msg("%s: read failed: errno=%d\n", progname, err);
X	}
#endif
X	return(-1);
X      }
X      if ((line[n] == '\r') || (m == 0)) continue;
X      if (n >= len1-1) {
X	if (strncmp(&line[n-(len1-1)], text1, len1) == 0) {
X	  /* we found the keyword we were searching for */
X	  alarm(0); /* disable alarm */
X	  return(1);
X	}
X      }
X      if ((text2 != NULL) && (n >= len2-1)) {
X	if (strncmp(&line[n-(len2-1)], text2, len2) == 0) {
X	  /* we found the keyword we were searching for */
X	  alarm(0); /* disable alarm */
X	  return(2);
X	}
X      }
X      if (line[n] == '\n') break;
X      n++;
X    }
X    if (show) {
X      line[++n] = '\0';
X      if (++count > 1) {
X	/* the very first line of information is the remains of  */
X	/* a previously issued command and should be ignored.    */
X
X	if ((line[0] == 'P') && (line[1] >= '0') && (line[1] <= '9')) {
X	  /* parallel port info */
#if defined(SHOW_PRINTER)
X	    fputs(line, stdout);
X	    fflush(stdout);
#endif
X	}
X	else {
X	  /* serial port info */
X	  if (showall) {
X	    fputs(line, stdout);
X	    fflush(stdout);
X	  }
X	  else if (strlen(line) >= IDLE_POS) {
X	    if (strncmp(&line[IDLE_POS], "IDLE ", 5) != 0) {
X	      fputs(line, stdout);
X	      fflush(stdout);
X	    }
X	  }
X	}
X      }
X    }
X  }
}
X
/* write a command to the 'telnet' process */
int do_write(int fd, char *text, int timeout)
{
X  int err, len, n;
X
X  /* setup alarm (so we won't hang forever upon problems) */
X  signal(SIGALRM, sigalrm);
X  alarm(timeout);
X
X  len = strlen(text);
X  n = write(fd, text, len);
X  if (n != len) {
X    err = errno;
X    alarm(0); /* disable alarm */
#if 0
X    if (n < 0) {
X      err_msg("%s: write failed: errno=%d\n", progname, err);
X    }
#endif
X    return(-1);
X  }
X  else {
X    alarm(0); /* disable alarm */
X    return(0);
X  }
}
X
/* our timeout procedure, used to abort malfunctioning connections */
void sigalrm(int signo)
{
X  got_alarm = 1;
X  err_msg("%s: timeout on connection to host '%s'\n", progname, hostname);
}
X
/* handle the reception of signals */
void sigfunc(int signo)
{
#if defined(SIGPIPE)
X  if (signo != SIGPIPE)
#endif
X  {
X    err_msg("%s: received signal #%d during connection to host '%s' -- aborting\n", progname, signo, hostname);
X  }
X
X  stop_telnet();
X  exit(1);
}
X
/* print error text */
void err_msg(const char *format, ...)
{
X  va_list ap;
X
X  /* show error message */
X  va_start(ap, format);
X  vfprintf(stderr, format, ap);
X  va_end(ap);
X  fflush(stderr);
}
X
/* print error text and exit gracefully */
void err_exit(const char *format, ...)
{
X  va_list ap;
X
X  /* show error message */
X  va_start(ap, format);
X  vfprintf(stderr, format, ap);
X  va_end(ap);
X  fflush(stderr);
X
X  /* exit with error */
X  exit(1);
}
X
/* try to terminate the telnet-child that we started */
void stop_telnet(void)
{
X  pid_t p;
X
X  if ((pid != -1) && (pid != 0)) {
X    if (kill(pid, 0) >= 0) {
X      kill(pid, SIGTERM);
X      kill(pid, SIGKILL);
X
X      if (kill(pid, 0) >= 0) {
X	for(p = wait(0); (p != -1) && (p != pid); p = wait(0)) ; /* zombies */
X      }
X    }
X    pid = -1;
X  }
}
SHAR_EOF
  $shar_touch -am 0404172596 'pmwho-1.5.c' &&
  chmod 0444 'pmwho-1.5.c' ||
  $echo 'restore of' 'pmwho-1.5.c' 'failed'
  if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \
  && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then
    md5sum -c << SHAR_EOF >/dev/null 2>&1 \
    || $echo 'pmwho-1.5.c:' 'MD5 check failed'
13ea9fb1e240a9f1948822a3670481d6  pmwho-1.5.c
SHAR_EOF
  else
    shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'pmwho-1.5.c'`"
    test 18027 -eq "$shar_count" ||
    $echo 'pmwho-1.5.c:' 'original size' '18027,' 'current size' "$shar_count!"
  fi
fi
rm -fr _sh20834
exit 0

