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