/* * poppassd.c * * John Norstad * Northwestern University * j-norstad@nwu.edu * * IMPORTANT NOTE: * * Please do not write to me asking for help getting this program running * on your system. You are on your own. If you send me email about this * program, I will not read it, and I will not reply to it. Sorry, but the * only alternative is to not distribute it at all. * * Based on earlier versions by Roy Smith and Daniel * L. Leavitt . #!/bin/sh # This is a shell archive (shar 3.32) # made 01/28/1994 20:35 UTC by sdorner@ux1.cso.uiuc.edu # Source directory /cso/staff/sdorner # # existing files WILL be overwritten # # This shar contains: # length mode name # ------ ---------- ------------------------------------------ # 345 -rw-r--r-- poppassd/Makefile # 138 -rw-r--r-- poppassd/README # 18598 -rw-r--r-- poppassd/poppassd.c # if touch 2>&1 | fgrep 'amc' > /dev/null then TOUCH=touch else TOUCH=true fi # ============= poppassd/Makefile ============== if test ! -d 'poppassd'; then echo "x - creating directory poppassd" mkdir 'poppassd' fi echo "x - extracting poppassd/Makefile (Text)" sed 's/^X//' << 'SHAR_EOF' > poppassd/Makefile && XBINDIR = /usr/etc XLIBDIR = XCFLAGS = -g XLFLAGS = -g XCCM = cc -Em X XOBJECTS = poppassd.o XLIBS = X Xpoppassd: $(OBJECTS) X cc -o poppassd $(LFLAGS) $(OBJECTS) $(LIBS) X Xinstall: poppassd X install -g bin -o root -m 500 poppassd $(BINDIR) X Xclean: X rm -f *.o *~* core Makefile.new Makefile.bak poppassd X Xpoppassd.o: poppassd.c X cc -c $(CFLAGS) poppassd.c SHAR_EOF $TOUCH -am 0128143394 poppassd/Makefile && chmod 0644 poppassd/Makefile || echo "restore of poppassd/Makefile failed" set `wc -c poppassd/Makefile`;Wc_c=$1 if test "$Wc_c" != "345"; then echo original size 345, current size $Wc_c fi # ============= poppassd/README ============== echo "x - extracting poppassd/README (Text)" sed 's/^X//' << 'SHAR_EOF' > poppassd/README && Xpoppassd is a password change server for Eudora and NUPOP. XSee the poppassd.c source file for more details and Xinstallation instructions. SHAR_EOF $TOUCH -am 0128143394 poppassd/README && chmod 0644 poppassd/README || echo "restore of poppassd/README failed" set `wc -c poppassd/README`;Wc_c=$1 if test "$Wc_c" != "138"; then echo original size 138, current size $Wc_c fi # ============= poppassd/poppassd.c ============== echo "x - extracting poppassd/poppassd.c (Text)" sed 's/^X//' << 'SHAR_EOF' > poppassd/poppassd.c && X/* X * poppassd.c X * X * A Eudora and NUPOP change password server. X * X * John Norstad X * Academic Computing and Network Services X * Northwestern University X * j-norstad@nwu.edu X * X * Based on earlier versions by Roy Smith and Daniel X * L. Leavitt . X * X * Doesn't actually change any passwords itself. It simply listens for X * incoming requests, gathers the required information (user name, old X * password, new password) and executes /bin/passwd, talking to it over X * a pseudo-terminal pair. The advantage of this is that we don't need X * to have any knowledge of either the password file format (which may X * include dbx files that need to be rebuilt) or of any file locking X * protocol /bin/passwd and cohorts may use (and which isn't documented). X * X * The current version has been tested at NU under SunOS release 4.1.2 X * and 4.1.3, and under HP-UX 8.02 and 9.01. We have tested the server X * with both Eudora 1.3.1 and NUPOP 2.0. X * X * Other sites report that this version also works under AIX and NIS, X * and with PC Eudora. X * X * Note that unencrypted passwords are transmitted over the network. If X * this bothers you, think hard about whether you want to implement the X * password changing feature. On the other hand, it's no worse than what X * happens when you run /bin/passwd while connected via telnet or rlogin. X * Well, maybe it is, since the use of a dedicated port makes it slightly X * easier for a network snooper to snarf passwords off the wire. X * X * NOTE: In addition to the security issue outlined in the above paragraph, X * you should be aware that this program is going to be run as root by X * ordinary users and it mucks around with the password file. This should X * set alarms off in your head. I think I've devised a pretty foolproof X * way to ensure that security is maintained, but I'm no security expert and X * you would be a fool to install this without first reading the code and X * ensuring yourself that what I consider safe is good enough for you. If X * something goes wrong, it's your fault, not mine. X * X * The front-end code (which talks to the client) is directly X * descended from Leavitt's original version. The back-end pseudo-tty stuff X * (which talks to /bin/password) is directly descended from Smith's X * version, with changes for SunOS and HP-UX by Norstad (with help from X * sample code in "Advanced Programming in the UNIX Environment" X * by W. Richard Stevens). The code to report /bin/passwd error messages X * back to the client in the final 500 response, and a new version of the X * code to find the next free pty, is by Norstad. X * X * Should be owned by root, and executable only by root. It can be started X * with an entry in /etc/inetd.conf such as the following: X * X * poppassd stream tcp nowait root /usr/local/bin/poppassd poppassd X * X * and in /etc/services: X * X * poppassd 106/tcp X * X * Logs to the local2 facility. Should have an entry in /etc/syslog.conf X * like the following: X * X * local2.err /var/adm/poppassd-log X */ X X/* Modification history. X * X * 06/09/93. Version 1.0. X * X * 06/29/93. Version 1.1. X * Include program name 'poppassd' and version number in initial X * hello message. X * Case insensitive command keywords (user, pass, newpass, quit). X * Fixes problem reported by Raoul Schaffner with PC Eudora. X * Read 'quit' command from client instead of just terminating after X * password change. X * Add new code for NIS support (contributed by Max Caines). X * X * 08/31/93. Version 1.2. X * Generalized the expected string matching to solve several problems X * with NIS and AIX. The new "*" character in pattern strings X * matches any sequence of 0 or more characters. X * Fix an error in the "getemess" function which could cause the X * program to hang if more than one string was defined in the X * P2 array. X */ X X/* Steve Dorner's description of the simple protocol: X * X * The server's responses should be like an FTP server's responses; X * 1xx for in progress, 2xx for success, 3xx for more information X * needed, 4xx for temporary failure, and 5xx for permanent failure. X * Putting it all together, here's a sample conversation: X * X * S: 200 hello\r\n X * E: user yourloginname\r\n X * S: 300 please send your password now\r\n X * E: pass yourcurrentpassword\r\n X * S: 200 My, that was tasty\r\n X * E: newpass yournewpassword\r\n X * S: 200 Happy to oblige\r\n X * E: quit\r\n X * S: 200 Bye-bye\r\n X * S: X * E: X */ X X#define VERSION "1.2" X X#define SUCCESS 1 X#define FAILURE 0 X#define BUFSIZE 512 X X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X X X/* Prompt strings expected from the "passwd" command. If you want X * to port this program to yet another flavor of UNIX, you may need to add X * more prompt strings here. X * X * Each prompt is defined as an array of pointers to alternate X * strings, terminated by an empty string. In the strings, '*' X * matches any sequence of 0 or more characters. Pattern matching X * is case-insensitive. X */ X Xstatic char *P1[] = X {"Old password:", X "Changing password for *.\nOld password:", X "Changing password for * on *.\nOld password:", X "Changing NIS password for * on *.\nOld password:", X "Changing password for *\n*'s Old password:", X ""}; X Xstatic char *P2[] = X {"\nNew password:", X "\n*'s New password:", X ""}; X Xstatic char *P3[] = X {"\nRe-enter new password:", X "\nRetype new password:", X "\nEnter the new password again:", X "\n*Re-enter *'s new password:", X "\nVerify:", X ""}; X Xstatic char *P4[] = X {"\n", X "NIS entry changed on *\n", X ""}; X X Xmain (argc, argv) Xint argc; Xchar *argv[]; X{ X char line[BUFSIZE]; X char user[BUFSIZE]; X char oldpass[BUFSIZE]; X char newpass[BUFSIZE]; X char emess[BUFSIZE]; X char *slavedev; X struct passwd *pw, *getpwnam(); X int c, master; X pid_t pid, wpid; X int wstat; X X *user = *oldpass = *newpass = 0; X X if (openlog ("poppassd", LOG_PID, LOG_LOCAL2) < 0) X { X WriteToClient ("500 Can't open syslog."); X exit (1); X } X X WriteToClient ("200 poppassd v%s hello, who are you?", VERSION); X ReadFromClient (line); X sscanf (line, "user %s", user) ; X if (strlen (user) == 0) X { X WriteToClient ("500 Username required."); X exit(1); X } X X WriteToClient ("200 your password please."); X ReadFromClient (line); X sscanf (line, "pass %s", oldpass) ; X if (strlen (oldpass) == 0) X { X WriteToClient ("500 Password required."); X exit(1); X } X X if ((pw = getpwnam (user)) == NULL) X { X WriteToClient ("500 Unknown user, %s.", user); X exit(1); X } X X if (chkPass (user, oldpass, pw) == FAILURE) X { X WriteToClient ("500 Old password is incorrect."); X exit(1); X } X X WriteToClient ("200 your new password please."); X ReadFromClient (line); X sscanf (line, "newpass %s", newpass); X X /* new pass required */ X if (strlen (newpass) == 0) X { X WriteToClient ("500 New password required."); X exit(1); X } X /* get pty to talk to password program */ X if ((master = findpty (&slavedev)) < 0) X { X syslog (LOG_ERR, "can't find pty"); X WriteToClient("500 Server busy - try again later."); X exit (1); X } X X /* fork child process to talk to password program */ X if ((pid = fork()) < 0) /* Error, can't fork */ X { X syslog (LOG_ERR, "can't fork for passwd: %m"); X WriteToClient ("500 Server error (can't fork passwd), get help!"); X exit (1); X } X X if (pid) /* Parent */ X { X sleep (1); /* Make sure child is ready. Is this really needed? */ X if (talktochild (master, user, oldpass, newpass, emess) == FAILURE) X { X syslog (LOG_ERR, "failed attempt by %s", user); X if (*emess == '\0') { X WriteToClient ("500 Unable to change password." ); X } else { X WriteToClient ("500 %s", emess); X } X exit(1); X } X X if ((wpid = waitpid (pid, &wstat, 0)) < 0) X { X syslog (LOG_ERR, "wait for /bin/passwd child failed: %m"); X WriteToClient ("500 Server error (wait failed), get help!"); X exit (1); X } X X if (pid != wpid) X { X syslog (LOG_ERR, "wrong child (/bin/passwd waited for!"); X WriteToClient ("500 Server error (wrong child), get help!"); X exit (1); X } X X if (WIFEXITED (wstat) == 0) X { X syslog (LOG_ERR, "child (/bin/passwd) killed?"); X WriteToClient ("500 Server error (funny wstat), get help!"); X exit (1); X } X X if (WEXITSTATUS (wstat) != 0) X { X syslog (LOG_ERR, "child (/bin/passwd) exited abnormally"); X WriteToClient ("500 Server error (abnormal exit), get help!"); X exit (1); X } X X syslog (LOG_ERR, "password changed for %s", user); X WriteToClient ("200 Password changed, thank-you."); X X ReadFromClient (line); X if (strncmp(line, "quit", 4) != 0) { X WriteToClient("500 Quit required."); X exit (1); X } X X WriteToClient("200 Bye."); X exit (0); X } X else /* Child */ X { X /* X * Become the user trying who's password is being changed. We're X * about to exec /bin/passwd with is setuid root anyway, but this X * way it looks to the child completely like it's being run by X * the normal user, which makes it do its own password verification X * before doing any thing. In theory, we've already verified the X * password, but this extra level of checking doesn't hurt. Besides, X * the way I do it here, if somebody manages to change somebody X * else's password, you can complain to your vendor about security X * holes, not to me! X */ X setuid (pw->pw_uid); X setgid (pw->pw_gid); X dochild (master, slavedev, user); X } X} X X/* X * dochild X * X * Do child stuff - set up slave pty and execl /bin/passwd. X * X * Code adapted from "Advanced Programming in the UNIX Environment" X * by W. Richard Stevens. X * X */ X Xdochild (master, slavedev, user) Xint master; Xchar *slavedev, *user; X{ X int slave; X struct termios stermios; X X /* Start new session - gets rid of controlling terminal. */ X X if (setsid() < 0) { X syslog(LOG_ERR, "setsid failed: %m"); X return(0); X } X X /* Open slave pty and acquire as new controlling terminal. */ X X if ((slave = open(slavedev, O_RDWR)) < 0) { X syslog(LOG_ERR, "can't open slave pty: %m"); X return(0); X } X X /* Close master. */ X X close(master); X X /* Make slave stdin/out/err of child. */ X X if (dup2(slave, STDIN_FILENO) != STDIN_FILENO) { X syslog(LOG_ERR, "dup2 error to stdin: %m"); X return(0); X } X if (dup2(slave, STDOUT_FILENO) != STDOUT_FILENO) { X syslog(LOG_ERR, "dup2 error to stdout: %m"); X return(0); X } X if (dup2(slave, STDERR_FILENO) != STDERR_FILENO) { X syslog(LOG_ERR, "dup2 error to stderr: %m"); X return(0); X } X if (slave > 2) close(slave); X X /* Set proper terminal attributes - no echo, canonical input processing, X no map NL to CR/NL on output. */ X X if (tcgetattr(0, &stermios) < 0) { X syslog(LOG_ERR, "tcgetattr error: %m"); X return(0); X } X stermios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); X stermios.c_lflag |= ICANON; X stermios.c_oflag &= ~(ONLCR); X if (tcsetattr(0, TCSANOW, &stermios) < 0) { X syslog(LOG_ERR, "tcsetattr error: %m"); X return(0); X } X X /* Fork /bin/passwd. */ X X if (execl("/bin/passwd", "passwd", user, (char*)0) < 0) { X syslog(LOG_ERR, "can't exec /bin/passwd: %m"); X return(0); X } X} X X X/* X * findpty() X * X * Finds the first available pseudo-terminal master/slave pair. The master X * side is opened and a fd returned as the function value. A pointer to the X * name of the slave side (i.e. "/dev/ttyp0") is returned in the argument, X * which should be a char**. The name itself is stored in a static buffer. X * X * A negative value is returned on any sort of error. X * X * Modified by Norstad to remove assumptions about number of pty's allocated X * on this UNIX box. X */ Xfindpty (slave) Xchar **slave; X{ X int master; X static char *line = "/dev/ptyXX"; X DIR *dirp; X struct dirent *dp; X X dirp = opendir("/dev"); X while ((dp = readdir(dirp)) != NULL) { X if (strncmp(dp->d_name, "pty", 3) == 0 && strlen(dp->d_name) == 5) { X line[8] = dp->d_name[3]; X line[9] = dp->d_name[4]; X if ((master = open(line, O_RDWR)) >= 0) { X line[5] = 't'; X *slave = line; X closedir(dirp); X return (master); X } X } X } X closedir(dirp); X return (-1); X} X X/* X * writestring() X * X * Write a string in a single write() system call. X */ Xwritestring (fd, s) Xchar *s; X{ X int l; X X l = strlen (s); X write (fd, s, l); X} X X/* X * talktochild() X * X * Handles the conversation between the parent and child (password program) X * processes. X * X * Returns SUCCESS is the conversation is completed without any problems, X * FAILURE if any errors are encountered (in which case, it can be assumed X * that the password wasn't changed). X */ Xtalktochild (master, user, oldpass, newpass, emess) Xint master; Xchar *user, *oldpass, *newpass, *emess; X{ X char buf[BUFSIZE]; X char pswd[BUFSIZE+1]; X int m, n; X X *emess = 0; X X if (!expect(master, P1, buf)) return FAILURE; X X sprintf(pswd, "%s\n", oldpass); X writestring(master, pswd); X X if (!expect(master, P2, buf)) return FAILURE; X X sprintf(pswd, "%s\n", newpass); X writestring(master, pswd); X X if (!expect(master, P3, buf)) { X getemess(master, P2, buf); X strcpy(emess, buf); X return FAILURE; X } X X writestring(master, pswd); X X if (!expect(master, P4, buf)) return FAILURE; X X return SUCCESS; X} X X/* X * match () X * X * Matches a string against a pattern. Wild-card characters '*' in X * the pattern match any sequence of 0 or more characters in the string. X * The match is case-insensitive. X * X * Entry: str = string. X * pat = pattern. X * X * Exit: function result = X * 0 if no match. X * 1 if the string matches some initial segment of X * the pattern. X * 2 if the string matches the full pattern. X */ Xmatch (str, pat) Xchar *str; Xchar *pat; X{ X int result; X X for (; *str && *pat && *pat != '*'; str++, pat++) X if (tolower(*str) != tolower(*pat)) return 0; X if (*str == 0) return *pat == 0 ? 2 : 1; X if (*pat == 0) return 0; X for (; *str; str++) if ((result = match(str, pat+1)) != 0) return result; X return 0; X} X X/* X * expect () X * X * Reads 'passwd' command output and compares it to expected output. X * X * Entry: master = fid of master pty. X * expected = pointer to array of pointers to alternate expected X * strings, terminated by an empty string. X * buf = pointer to buffer. X * X * Exit: function result = SUCCESS if output matched, FAILURE if not. X * buf = the text read from the slave. X * X * Text is read from the slave and accumulated in buf. As long as X * the text accumulated so far is an initial segment of at least X * one of the expected strings, the function continues the read. X * As soon as one of full expected strings has been read, the X * function returns SUCCESS. As soon as the text accumulated so far X * is not an initial segment of or exact match for at least one of X * the expected strings, the function returns FAILURE. X */ Xexpect (master, expected, buf) Xint master; Xchar **expected; Xchar *buf; X{ X int n, m; X char **s; X int initialSegment; X int result; X X n = 0; X buf[0] = 0; X while (1) { X if (n >= BUFSIZE-1) { X syslog(LOG_ERR, "buffer overflow on read from child"); X return FAILURE; X } X m = read(master, buf+n, BUFSIZE-1-n); X if (m < 0) { X syslog(LOG_ERR, "read error from child: %m"); X return FAILURE; X } X n += m; X buf[n] = 0; X initialSegment = 0; X for (s = expected; **s != 0; s++) { X result = match(buf, *s); X if (result == 2) return SUCCESS; X initialSegment = initialSegment || result == 1; X } X if (!initialSegment) return FAILURE; X } X} X X/* X * getemess() X * X * This function accumulates a 'passwd' command error message issued X * after the first copy of the password has been sent. X * X * Entry: master = fid of master pty. X * expected = pointer to array of pointers to alternate expected X * strings for first password prompt, terminated by an X * empty string. X * buf = pointer to buffer containing text read so far. X * X * Exit: buf = the error message read from the slave. X * X * Text is read from the slave and accumulated in buf until the text X * at the end of the buffer is an exact match for one of the expected X * prompt strings. The expected prompt string is removed from the buffer, X * returning just the error message text. Newlines in the error message X * text are replaced by spaces. X */ Xgetemess (master, expected, buf) Xint master; Xchar **expected; Xchar *buf; X{ X int n, m; X char **s; X char *p, *q; X X n = strlen(buf); X while (1) { X for (s = expected; **s != 0; s++) { X for (p = buf; *p; p++) { X if (match(p, *s) == 2) { X *p = 0; X for (q = buf; *q; q++) if (*q == '\n') *q = ' '; X return; X } X } X } X if (n >= BUFSIZE-1) { X syslog(LOG_ERR, "buffer overflow on read from child"); X return; X } X m = read(master, buf+n, BUFSIZE+1-n); X if (m < 0) { X syslog(LOG_ERR, "read error from child: %m"); X return; X } X n += m; X buf[n] = 0; X } X} X XWriteToClient (fmt, va_alist) Xchar *fmt; Xva_dcl X{ X va_list ap; X X va_start (ap); X vfprintf (stdout, fmt, ap); X fputs ("\r\n", stdout ); X fflush (stdout); X va_end (ap); X} X XReadFromClient (line) Xchar *line; X{ X char *sp; X int i; X X strcpy (line, ""); X fgets (line, BUFSIZE, stdin); X if ((sp = strchr(line, '\n')) != NULL) *sp = '\0'; X if ((sp = strchr(line, '\r')) != NULL) *sp = '\0'; X X /* convert initial keyword on line to lower case. */ X X for (sp = line; isalpha(*sp); sp++) *sp = tolower(*sp); X} X Xint chkPass (user, pass, pw) Xchar *user; Xchar *pass; Xstruct passwd *pw; X{ X /* Compare the supplied password with the password file entry */ X if (strcmp (crypt (pass, pw->pw_passwd), pw->pw_passwd) != 0) X return (FAILURE); X else X return (SUCCESS); X} SHAR_EOF $TOUCH -am 0128143394 poppassd/poppassd.c && chmod 0644 poppassd/poppassd.c || echo "restore of poppassd/poppassd.c failed" set `wc -c poppassd/poppassd.c`;Wc_c=$1 if test "$Wc_c" != "18598"; then echo original size 18598, current size $Wc_c fi exit 0