home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
ftp.qualcomm.com
/
2014.06.ftp.qualcomm.com.tar
/
ftp.qualcomm.com
/
eudora
/
servers
/
unix
/
password
/
pwserve-3
/
poppassd.c
< prev
Wrap
C/C++ Source or Header
|
1997-03-26
|
16KB
|
583 lines
/*
* poppassd.c
*
* Daemon to service Eudora "Change Password" requests. This program
* doesn't actually change any passwords itself. It simply listens for
* incomming 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).
*
* Note that unencrypted passwords are transmitted over the network. If
* this bothers you, think hard about whether you want to implement Eudora's
* 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.
*
* This is an adaptation of a server by the same name from dll@mitre.org
* (Daniel L. Leavitt of The MITRE Corporation). The front-end code (which
* talks to the Eudora client) is directly descended from his original
* version. The back-end stuff (which talks to /bin/password) is mine.
*
* 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 /usr/etc/poppassd poppassd
*
* and in /etc/services:
*
* ppassd 106/tcp
*
* Roy Smith <roy@nyu.edu>
* Department of Microbiology,
* NYU School of Medicine
* 550 First Avenue
* New York, NY 10016
*/
/* 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 SUCCESS 1
#define FAILURE 0
#define BUFSIZE 512
#define MAXARGS 32
#define SUPPRESS 1
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <syslog.h>
#include <sgtty.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <strings.h>
#include <errno.h>
#include <varargs.h>
#include <pwd.h>
main (argc, argv)
int argc;
char *argv[];
{
char line[BUFSIZE];
char user[BUFSIZE];
char oldpass[BUFSIZE];
char newpass[BUFSIZE];
char logFile[BUFSIZE];
char *slavedev;
struct passwd *pw, *getpwnam();
int c, master;
pid_t pid, wpid;
union wait wstat;
chdir ("/etc" );
strcpy (user, "" );
strcpy (oldpass, "" );
strcpy (newpass, "" );
if (openlog ("poppassd", LOG_PID) < 0)
{
WriteToClient ("500 Can't open syslog.");
exit (1);
}
WriteToClient ("200 hello, who are you?");
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 Authentication failure.");
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);
}
/* new pass must be 6 char or longer */
if (strlen (newpass) < 6 )
{
WriteToClient ("500 New password too short");
exit(1);
}
/* new pass must be different from old pass */
if (!strcmp (oldpass, newpass))
{
WriteToClient ("500 New password must be different.");
exit(1);
}
/* get pty to talk to password program */
if ((master = findpty (&slavedev)) < 0)
{
syslog (LOG_ERR, "can't find pty");
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!");
return (0);
}
if (pid) /* Parent */
{
sleep (1); /* Make sure child is ready. Is this really needed? */
if (talktochild (master, user, oldpass, newpass) == FAILURE)
{
syslog (LOG_ERR, "failed attempt by %s", user);
WriteToClient ("500 Unable to change password." );
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!");
return (0);
}
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);
}
if (hashpass() == 0)
exit (1);
syslog (LOG_ERR, "password changed for %s", user);
WriteToClient ("200 Password changed, thank-you.");
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 (slavedev, user);
}
}
dochild (slavedev, user)
char *slavedev, *user;
{
int fd, fdmax, slave;
struct sgttyb sgbuf;
/*
* Get rid of control terminal and
* close all open file descriptors.
*/
fd = open ("/dev/tty", O_RDWR, 0);
ioctl (fd, TIOCNOTTY, 0);
fdmax = sysconf (_SC_OPEN_MAX);
for (fd = 0; fd < fdmax; fd++)
close (fd);
/*
* Open slave side of pty (which now becomes the control terminal).
* Set appropriate tty modes and make sure stdin, stdout, and stderr
* are all connected to it.
*/
if ((slave = open (slavedev, O_RDWR)) < 0)
{
syslog (LOG_ERR, "Can't open slave pty %s: %m", slavedev);
return (0);
}
ioctl (slave, TIOCGETP, &sgbuf);
sgbuf.sg_flags = ANYP;
ioctl (slave, TIOCSETP, &sgbuf);
dup2 (slave, 0);
dup2 (slave, 1);
dup2 (slave, 2);
/*
* This is probably just paranoia; if all fds are closed, slave
* should be fd 0. In that case, the dup2 (slave, 0) above is
* also just being paranoid.
*/
if (slave > 2)
(void) close(slave);
/*
* Execute the "real" password program, which should now think
* it is talking to a normal user on a normal tty port.
*/
execl ("/bin/passwd", "passwd", user, (char *) 0);
/*
* We only reach here if execl fails! It's not 100% clear what
* to do at this point. We can no longer talk to the client since
* we no longer have the client socket open. Just log a syslog
* message (and hope its gets logged, since it's not clear what
* evil things closing the syslog fd has done!) and count on the
* parent process to realize we've died.
*/
openlog ("poppassd (child)", LOG_PID);
syslog (LOG_ERR, "can't execl /bin/passwd: %m");
exit (1);
}
/*
* 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.
*/
findpty (slave)
char **slave;
{
int c, i, master;
static char *line;
struct stat statbuf;
for (c = 'p'; c <= 's'; c++)
{
line = "/dev/ptyXX";
line[sizeof("/dev/pty")-1] = c;
line[sizeof("/dev/ptyp")-1] = '0';
if (stat (line, &statbuf) < 0)
break;
for (i = 0; i < 16; i++)
{
line[sizeof("/dev/ptyp")-1] = "0123456789abcdef"[i];
if ((master = open(line, O_RDWR)) >= 0)
goto found_pty;
}
}
syslog(LOG_ERR, "All network ports in use");
return (-1);
found_pty:
line[sizeof("/dev/")-1] = 't'; /* "/dev/ptyp0" --> "/dev/ttyp0" */
*slave = line;
return (master);
}
/*
* 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. This is where you want to customize the conversation script.
*
* 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)
int master;
char *user, *oldpass, *newpass;
{
int n;
char buf[500];
sprintf (buf, "Changing password for %s\n", user);
if (!expect (master, buf))
return (FAILURE);
if (!expect (master, "Old password: "))
return (FAILURE);
sprintf (buf, "%s\n", oldpass);
writestring (master, buf);
if (!expect (master, "\n") || !expect (master, "Enter new password: "))
return (FAILURE);
sprintf (buf, "%s\n", newpass);
writestring (master, buf);
if (!expect (master, "\n") || !expect (master, "Verify: "))
return (FAILURE);
writestring (master, buf);
if (!expect (master, "\n") || !expect (master, ""))
return (FAILURE);
return (SUCCESS);
}
/*
* expect ()
*
* Read the next line and check to make sure it is an exact match for
* the string expected, including newlines. If the expected string is
* empty, it's considered to match either EOF, or getting an EWOULDBLOCK
* error, which would typically occur if the child has already exited.
*
* A syslog error message is generated and expect returns 0 on any error
* or mismatch. If the expected string is found, expect() returns 1.
*/
expect (fd, s)
int fd;
char *s;
{
int n;
char buf[1000];
extern int errno;
if ((n = read (fd, buf, 999)) < 0)
{
if (errno == EWOULDBLOCK && *s == NULL)
return (1);
syslog (LOG_ERR, "read error from child: %m");
return (0);
}
buf[n] = 0;
if (strcmp (s, buf) != 0)
{
syslog (LOG_ERR, "wrong response from child --");
syslog (LOG_ERR, "expected %s", s);
syslog (LOG_ERR, "got %s", buf);
return (0);
}
return (1);
}
WriteToClient (va_alist)
va_dcl
{
va_list ap;
char *fmt;
char *args[MAXARGS];
int argno = 0;
char string[BUFSIZE];
va_start (ap);
fmt = va_arg (ap, char *);
while ((args[argno++] = va_arg(ap, char *)) != (char *)0)
;
vfprintf (stdout, fmt, args);
fputs ("\r\n", stdout );
fflush (stdout);
vsprintf (string, fmt, args);
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';
}
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);
}
/*
* hashpass()
*
* Execute mkpasswd to build the hashed (dbx) passwd files.
*/
hashpass()
{
pid_t pid, wpid;
union wait wstat;
int fd;
/* fork child process to talk to run mkpasswd */
if ((pid = fork()) < 0) /* Error, can't fork */
{
syslog (LOG_ERR, "can't fork for mkpassd: %m");
WriteToClient ("500 Server error (can't fork mkpasswd), get help!");
return (0);
}
if (pid) /* Parent */
{
if ((wpid = waitpid (pid, &wstat, 0)) < 0)
{
syslog (LOG_ERR, "wait for mkpasswd child failed: %m");
WriteToClient ("500 Server error (wait failed), get help!");
return (0);
}
if (pid != wpid)
{
syslog (LOG_ERR, "wrong child waited for in hashpass()!");
WriteToClient ("500 Server error (wrong child), get help!");
return (0);
}
if (WIFEXITED (wstat) == 0)
{
syslog (LOG_ERR, "child (mkpasswd) killed?");
WriteToClient ("500 Server error (funny wstat), get help!");
return (0);
}
if (WEXITSTATUS (wstat) != 0)
{
syslog (LOG_ERR, "child (mkpasswd) exited abnormally");
WriteToClient ("500 Server error (abnormal exit), get help!");
return (0);
}
return (1); /* mkpasswd executed normally */
}
else /* Child */
{
/*
* Making stdout and stderr /dev/null keeps mkpasswd messages (such
* as the one that says "669 password entries, maximum length 125")
* from being visible to the client. If you are doing debugging,
* just change /dev/null to /tmp/mkpasswd.std{out,err}, or whatever.
*/
close (1);
fd = open ("/dev/null", O_RDWR, 0);
if (fd != 1)
{
dup2 (fd, 1);
close (fd);
}
close (2);
fd = open ("/dev/null", O_RDWR, 0);
if (fd != 2)
{
dup2 (fd, 2);
close (fd);
}
execl ("/etc/mkpasswd", "mkpasswd", "/etc/passwd", (char *) 0);
/*
* We only reach here if execl fails!
*/
syslog (LOG_ERR, "can't execl /etc/mkpasswd: %m");
WriteToClient ("500 Server error (can't exec mkpasswd), get help!");
exit (1);
}
}