/* * pmwho.c - Show logged-on users on Livingston Portmaster servers. * * General information * ------------------- * This is a utility that serves the purpose of showing the status on the * available ports on a Livingston Portmaster server. It takes care of logging * on and issuing the commands to retrieve the information. The main advantage * with this utility is that it can be run by any user and only the program * itself knows about the password. When Livingston incorporates a finger- * daemon of some sort into the Portmaster server this utility will no longer * be needed. An ANSI-C compiler is required to compile the program. The * program is completely free, do what you like with it. * * Usage * ----- * - If the program is named 'PMWHO' (constant defined below) and it is started * like 'PMWHO pm1 pm2 pm3 ...' it will use 'pm1' ... as the hostnames to * connect to (yes, it will connect to as many hosts as you specify, good if * you have several Portmaster server's at your site). * - If the program is named 'PMWHO' and it is started like 'PMWHO' it will * use 'PMWHO_DEFAULT' (constant defined below) as the hostname to connect * to. * - If the program is named 'pm1' and it is started like 'pm1' it will use * 'pm1' as the hostname to connect to. * - The program has two options with the following meaning attached to them: * 'h' - (it is started like 'PMWHO -h'), show usage. * 'a' - (it is started like 'PMWHO -a hosts ...'), show all serial ports * (including ports that are at present IDLE). * * Installation * ------------ * The program should be made protection mode 111 ("--x--x--x", executable * only) and the source-code should be made protection mode 400 ("r--------", * read-only to owner (user 'root')) since the password could otherwise easily * be found. You might even consider not having the source-code online after * it has been compiled, and please do not set the password on your other * systems to the same as that of the Portmaster server! There are so many * ways of breaching security that the possibility of someone being able to * copy the program and finding out the password to the Portmaster server must * be considered (one way would be to make the executable file readable and * running 'trace' (SunOS 4.x) or 'truss' (Solaris 2.x) on it and watch the * output). * * How it actually works * --------------------- * It starts a 'telnet' session to the Portmaster server, issues the commands * to login, show the information and then logs out. The information is passed * to the parent process through pipes (the parent process has a pipe through * which it can send commands to the 'stdin' of the 'telnet process, and * another pipe through which it receives the output from the 'stdout' (and * 'stderr') of the 'telnet' process). * * Configuration options * --------------------- * Before building the program you should check out (and modify if necessary) * the values of the constants to match your own configuration (good default * values are already provided for SunOS 4.x, Solaris 2.x, DNIX and Linux). * * PMWHO - a string, default name of this utility. * PMWHO_DEFAULT - a string, default host to connect to. * PASSWORD - a string, the default password to use for user '!root' if * the hostname is not defined in the 'struct pwtable'. * TELNET_EXEC - a string, the path to the executable 'telnet'. * TELNET_CLOSE - a string, the leading char's in the string that 'telnet' * sends when a connection is closed. * TELNET_PORT - a string, the port to use when starting 'telnet'. * TELNET_PROMPT - a string, the prompt of telnet. * SHOW_PRINTER - #define this if you want to see the information * on the parallel port as well (it is otherwise ignored). * HAVE_SYS_WAIT, HAVE_WAIT - #define only one of these to indicate which * headerfile you have ( _or_ , * in that order), if none of them is #define-d a * generic definition be used. * * Building the utility * -------------------- * - Using SunOS 4.x: * $ gcc -DSUNOS4 pmwho.c -o pmwho * $ chmod 111 pmwho * * - Using Solaris 2.x: * $ gcc -DSOLARIS pmwho.c -o pmwho * $ chmod 111 pmwho * * - Using DNIX: * $ cc -DDNIX pmwho.c -o pmwho * $ chmod 111 pmwho * * - Using Linux: * $ gcc -DLINUX pmwho.c -o pmwho * $ chmod 111 pmwho * * - Using HP-UX 9.0x * $ cc -DHPUX -D_INCLUDE_POSIX_SOURCE pmwho.c -o pmwho -O -Aa * $ chmod 111 pmwho * * Version Date Comments * ------- ------ ---------------------------------------------------------- * 1.0 950531 First release. * Bugs: 1. Only one password is used. * 2. If "dup" or "execl" fails (in the child) the * 'stderr' is closed. * * 1.1 950604 - Added PROMPT constant for those not using "Command> " as * prompt. * - The information shown is changed, now only the active * ports are shown unless the "-a" (show all ports * regardless of state) option is given. * - program uses 'getopt' to check for option flags. * * 1.2 950706 - Portmaster server's with only 10 ports do not get the * "-- Press Return for More -- " message, solved in a * generic way. * - An attempt to fix bug #2 (stderr). * - Added support for Linux. * - PROMPT replaced by "> " since we have to handle both * a prompt of type "Command> " and "$hostname> ", this * will probably mean that we will run into trouble when * someone enters a username with "> " in it... * * 1.3 950904 - Improved the description part. * - Connects to multiple hosts. * * 1.4 950930 - Takes care of signal handling (v1.3 could leave a * 'telnet' process behind...). * * 1.5 960404 - Fixed bug #1 above. * - Have #defines for compile on HP systems. * - Signal-handling bug fixed. * * Comments on how to improve this simple utility are very much welcome. * * /jp, Johan Persson, jp@abc.se * */ /* configuration constants - make your individual settings here */ #define PMWHO "pmwho" #define PMWHO_DEFAULT "pm1" /* #define SHOW_PRINTER */ /* configuration constants - make your individual settings here */ #define PASSWORD "secret!" /* the default password to use */ /* * a password table structure with the purpose of being able to have different * passwords on different servers. If you only want to have one single password * you do not have to change anything, it will work as expected by default * (by using the password PASSWORD as defined by the constant a few lines up). * * If you do however want to be able to have different passwords, then you must * enter the appropriate values below (I have added an example for the test- * server by the name of "pm1", uncomment and change as appropriate). Don't * forget that the constant PASSWORD is still the default if the hostname is * not found in the table. * */ struct { char hostname[32]; char password[32]; } pwtable[] = { /* { "pm1", "topsecret" } */ }; #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 /* header files */ #include #include #include #include #if defined(HPUX) #include #define NSIG _NSIG #endif #include #if defined(HAVE_SYS_WAIT) #include #elif defined(HAVE_WAIT) #include #else extern pid_t wait(int *status); #endif #include #include /* program constants and global vars */ char *progname; char *hostname; pid_t pid; int showall; int got_alarm; #define LINELEN 256 #define IDLE_POS 50 /* 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); /* the program */ /* main procedure, find out program name and the hostname to use */ void main(int argc, char *argv[]) { char *t; int ch, n; extern int optind; t = strrchr(argv[0], '/'); progname = (t != NULL) ? t+1 : argv[0]; hostname = NULL; pid = -1; showall = 0; while ((ch = getopt(argc, argv, "ah")) != -1) { switch (ch) { case 'a': showall = 1; break; case 'h': /* fall-through */ default: usage(); } } /* setup signals properly */ for(n = 1; n < NSIG; n++) signal(n, sigfunc); #if defined(SIGCHLD) signal(SIGCHLD, SIG_IGN); #endif #if defined(SIGCLD) signal(SIGCLD, SIG_IGN); #endif /* take different action according to usage */ if (strcmp(progname, PMWHO) == 0) { /* PMWHO */ if (optind >= argc) { /* usage: 'PMWHO' */ hostname = PMWHO_DEFAULT; pmwho(); } else if (optind == argc-1) { /* usage: 'PMWHO hostname' */ hostname = argv[optind]; pmwho(); } else { /* usage: 'PMWHO hostname1 hostname2 ...' */ for(; optind < argc; optind++) { hostname = argv[optind]; fprintf(stdout, "[%s]\n", hostname); fflush(stdout); pmwho(); if (optind < argc-1) { fprintf(stdout, "\n"); fflush(stdout); } } } } else { /* usage: 'hostname' */ hostname = progname; progname = PMWHO; /* for the messages */ pmwho(); } exit(0); } /* show usage info */ void usage(void) { fprintf(stderr, "usage: %s [-ah]\n", progname); exit(0); } /* retrieve the information from the host by logging on, giving the command */ /* "show session" and logging out from 'hostname' */ void pmwho() { int fdpipe[2][2], fdin, fdout, fderr, err, n; got_alarm = 0; /* we need pipes to communicate between the programs */ if (pipe(fdpipe[0]) < 0) { err_msg("%s: pipe failed: errno=%d\n", progname, errno); return; } if (pipe(fdpipe[1]) < 0) { err_msg("%s: pipe failed: errno=%d\n", progname, errno); close(fdpipe[0][0]); close(fdpipe[0][1]); return; } switch(pid = fork()) { case 0: /* child */ /* child:stdin */ close(0); if (dup(fdpipe[0][0]) < 0) { err_exit("%s: dup failed: errno=%d\n", progname, errno); } /* close first pipe */ close(fdpipe[0][0]); close(fdpipe[0][1]); /* child:stdout */ close(1); if (dup(fdpipe[1][1]) < 0) { err_exit("%s: dup failed: errno=%d\n", progname, errno); } /* child:stderr */ if ((fderr = dup(2)) < 0) { err_exit("%s: dup failed: errno=%d\n", progname, errno); } close(2); if (dup(fdpipe[1][1]) < 0) { err = errno; dup(fderr); err_exit("%s: dup failed: errno=%d\n", progname, err); } /* close second pipe */ close(fdpipe[1][0]); close(fdpipe[1][1]); /* exec TELNET application */ execl(TELNET_EXEC, "telnet", hostname, TELNET_PORT, (char*)NULL); /* if we're still here the TELNET_EXEC could not be exec'd */ err = errno; close(2); dup(fderr); err_exit("%s: execl(%s) failed: errno=%d\n", progname, TELNET_EXEC, err); break; case -1: /* error */ err_exit("%s: fork failed: errno=%d\n", progname, errno); return; default: /* parent */ /* close the childs end of the pipes */ close(fdpipe[0][0]); close(fdpipe[1][1]); break; } /* now communicate with the 'telnet' process */ fdin = fdpipe[1][0]; fdout = fdpipe[0][1]; for(;;) { n = do_read(fdin, "login: ", TELNET_PROMPT, 0, 15); if (n != 1) { n = -1; /* TELNET_PROMPT == connection failed */ break; } if ((n = do_write(fdout, "!root\n", 5)) < 0) break; if ((n = do_read(fdin, "Password: ", NULL, 0, 10)) < 0) break; { char *pw = PASSWORD; int k; for(k = 0; k < sizeof(pwtable)/sizeof(pwtable[0]); k++) { if (strcmp(pwtable[k].hostname, hostname) == 0) { pw = pwtable[k].password; break; } } if ((n = do_write(fdout, pw, 5)) < 0) break; } if ((n = do_write(fdout, "\n", 5)) < 0) break; if ((n = do_read(fdin, "> ", NULL, 0, 10)) < 0) break; if ((n = do_write(fdout, "show session\n", 5)) < 0) break; for(;;) { n = do_read(fdin, "-- Press Return for More -- ", "> ", 1, 20); if ((n < 0) || (n == 2)) break; if ((n = do_write(fdout, "\n", 5)) < 0) break; } if (n < 0) break; if ((n = do_write(fdout, "quit\n", 5)) < 0) break; if ((n = do_read(fdin, TELNET_CLOSE, NULL, 0, 20)) < 0) break; break; } close(fdin); close(fdout); if ((n < 0) && (got_alarm == 0)) { err_msg("%s: connection to host '%s' failed\n", progname, hostname); } stop_telnet(); } /* 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) { char line[LINELEN+1]; int n, len1, len2, count = 0, m, err; /* setup alarm (so we won't hang forever upon problems) */ signal(SIGALRM, sigalrm); alarm(timeout); len1 = strlen(text1); len2 = (text2 != NULL) ? strlen(text2) : 0; /* start reading from 'telnet' */ for(;;) { n = 0; for(;;) { if (n == LINELEN) { alarm(0); /* disable alarm */ stop_telnet(); err_exit("%s: too long line!\n", progname); } m = read(fd, &line[n], 1); if (m != 1) { err = errno; alarm(0); /* disable alarm */ #if 0 if (m < 0) { err_msg("%s: read failed: errno=%d\n", progname, err); } #endif return(-1); } if ((line[n] == '\r') || (m == 0)) continue; if (n >= len1-1) { if (strncmp(&line[n-(len1-1)], text1, len1) == 0) { /* we found the keyword we were searching for */ alarm(0); /* disable alarm */ return(1); } } if ((text2 != NULL) && (n >= len2-1)) { if (strncmp(&line[n-(len2-1)], text2, len2) == 0) { /* we found the keyword we were searching for */ alarm(0); /* disable alarm */ return(2); } } if (line[n] == '\n') break; n++; } if (show) { line[++n] = '\0'; if (++count > 1) { /* the very first line of information is the remains of */ /* a previously issued command and should be ignored. */ if ((line[0] == 'P') && (line[1] >= '0') && (line[1] <= '9')) { /* parallel port info */ #if defined(SHOW_PRINTER) fputs(line, stdout); fflush(stdout); #endif } else { /* serial port info */ if (showall) { fputs(line, stdout); fflush(stdout); } else if (strlen(line) >= IDLE_POS) { if (strncmp(&line[IDLE_POS], "IDLE ", 5) != 0) { fputs(line, stdout); fflush(stdout); } } } } } } } /* write a command to the 'telnet' process */ int do_write(int fd, char *text, int timeout) { int err, len, n; /* setup alarm (so we won't hang forever upon problems) */ signal(SIGALRM, sigalrm); alarm(timeout); len = strlen(text); n = write(fd, text, len); if (n != len) { err = errno; alarm(0); /* disable alarm */ #if 0 if (n < 0) { err_msg("%s: write failed: errno=%d\n", progname, err); } #endif return(-1); } else { alarm(0); /* disable alarm */ return(0); } } /* our timeout procedure, used to abort malfunctioning connections */ void sigalrm(int signo) { got_alarm = 1; err_msg("%s: timeout on connection to host '%s'\n", progname, hostname); } /* handle the reception of signals */ void sigfunc(int signo) { #if defined(SIGPIPE) if (signo != SIGPIPE) #endif { err_msg("%s: received signal #%d during connection to host '%s' -- aborting\n", progname, signo, hostname); } stop_telnet(); exit(1); } /* print error text */ void err_msg(const char *format, ...) { va_list ap; /* show error message */ va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); fflush(stderr); } /* print error text and exit gracefully */ void err_exit(const char *format, ...) { va_list ap; /* show error message */ va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); fflush(stderr); /* exit with error */ exit(1); } /* try to terminate the telnet-child that we started */ void stop_telnet(void) { pid_t p; if ((pid != -1) && (pid != 0)) { if (kill(pid, 0) >= 0) { kill(pid, SIGTERM); kill(pid, SIGKILL); if (kill(pid, 0) >= 0) { for(p = wait(0); (p != -1) && (p != pid); p = wait(0)) ; /* zombies */ } } pid = -1; } }