On Fri 2000-11-17 (14:37), Steven Champeon wrote:
>
> Does anyone here have a version of poppassd that runs under Linux, has
> support for shadow passwords and/or APOP auth (based on whether the user
> is listed in the pop.auth file)? If not, I'll hack my own, but I'd much
> rather start with a good base if possible. Everything I've found is
> six or seven years old, or doesn't support shaodw, or doesn't support
> APOP - I'd like to be able to use either.
Attached is a poppassd.c I hacked together two years ago, but actually
never used it in production. It is based on the version 1.2 for solaris
and supports modification of APOP secrets. Use at you own risk.
Regards,
-Juergen
/*
* poppassd.c
*
* A Eudora and NUPOP change password server for Solaris.
*
* Graziano Sommariva
* Ansaldo Sistemi Informatici Genova Italy.
* [EMAIL PROTECTED]
*
* Based on earlier versions by
* John Norstad
* [EMAIL PROTECTED]
*
* 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 under Solaris 2.3 and used by
* Eudora 2.0.3 .
*
* 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.
*
*
* I made changes on the retriving of the crypted password and about
* the assigning of the pseudo-ttys.
*
* 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
*
* 4/26/95 -
* Added AUTH_UID. This feature disables the ability of users below
* the specified UID from being able to change their passwords via
* this program.
*
* Changed #include <strings.h> to <string.h> and moved varargs.h
* above the syslog.h include.
*
* I also added a timeout during input. [[EMAIL PROTECTED]]
*
* 2/29/96 -
* Added string for Solaris2.5 passwd program. [[EMAIL PROTECTED]]
*
*/
/* 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
#define MAXUSERNAMELEN 65
#include <sys/conf.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <varargs.h>
#include <syslog.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>
#include <shadow.h>
#include <string.h>
#include <termios.h>
#include <stropts.h>
#include <dirent.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
/* 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.
*/
/* UIDs below this one are not authorized to change their password */
#ifndef AUTH_UID
#define AUTH_UID 10
#endif
#ifndef TIMEOUT
#define TIMEOUT 60
#endif
#ifdef APOP
#include <ndbm.h>
#ifndef POPAUTH
#define POPAUTH "/usr/local/bin/popauth"
#endif
int apop = 0;
#endif
static char *P1[] =
{"Old password:",
"Old password: ",
"Enter login password: ",
"Enter login(NIS) password: ",
"Changing password for *.\nOld password:",
"passwd: Changing password for *.\nEnter login password: ",
"Changing password for * on *.\nOld password:",
"Changing NIS password for * on *.\nOld password:",
"Changing password for *\n*'s Old password:",
#ifdef APOP
"Changing POP password for *.\nOld password:",
#endif
""};
static char *P2[] =
{"\nNew password:",
"\nNew password: ",
"\n*'s New password:",
""};
static char *P3[] =
{"\nRe-enter new password:",
"\nRe-enter new password:",
"\nRe-enter new password: ",
"\nRetype new password:",
"\nEnter the new password again:",
"\n*Re-enter *'s new password:",
"\nVerify:",
""};
static char *P4[] =
{"\n",
"NIS(YP) passwd/attributes changed on *\n",
"NIS entry changed on *\n",
"passwd (SYSTEM): passwd successfully changed for *\n",
""};
abortme()
{
WriteToClient ("500 Timeout. Input idle for too long.");
exit(1);
}
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();
struct spwd *sp, *getspnam();
int c, master;
pid_t pid, wpid;
int wstat;
(void)signal(SIGALRM, (void *)abortme);
*user = *oldpass = *newpass = 0;
openlog ("poppassd", LOG_PID, LOG_LOCAL0);
WhoAreYou();
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) || ((sp = getspnam (user)) == NULL) )
{
/* WriteToClient ("500 Unknown user, %s.", user); */
WriteToClient ("500 Old password is incorrect.");
exit(1);
}
if (pw->pw_uid <= AUTH_UID)
{
syslog (LOG_ERR, "Authorization error (uid <= %d): uname=%s, uid=%d.",
AUTH_UID, user, pw->pw_uid);
WriteToClient ("500 Old password is incorrect.");
exit(1);
}
#ifdef NONAUTHFILE
/* Is the user not authorized to use POP? */
if (checknonauthfile(user) != 0) {
WriteToClient ("500 Old password is incorrect.");
syslog (LOG_ERR, "Authorization error (uname in access file): uname=%s,
uid=%d.",
user, pw->pw_uid);
exit(1);
}
#endif
if (chkPass (user, oldpass, sp->sp_pwdp) == 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 pts to talk to password program */
if ((master = findpts (&slavedev)) < 0)
{
syslog (LOG_ERR, "can't find pts for master");
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);
}
#ifdef APOP
if ((apop != 0)) {
syslog (LOG_NOTICE, "APOP password changed for %s", user);
WriteToClient ("200 APOP password changed, thank-you.");
} else {
#endif
syslog (LOG_NOTICE, "password changed for %s", user);
WriteToClient ("200 Password changed, thank-you.");
#ifdef APOP
}
#endif
ReadFromClient (line);
if (strncmp(line, "quit", 4) != 0) {
WriteToClient("500 Quit required.");
exit (1);
}
WriteToClient("200 Bye.");
exit (0);
}
else /* Child */
{
dochild (master, slavedev, user, pw);
}
}
/*
* dochild
*
* Do child stuff - set up slave pts and execl /bin/passwd.
*
* Code adapted from "Advanced Programming in the UNIX Environment"
* by W. Richard Stevens.
*
*/
dochild (master, slavedev, user, pw)
int master;
char *slavedev, *user;
struct passwd *pw;
{
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 %s: %m");
return(0);
}
ioctl(slave, I_PUSH, "ptem");
ioctl(slave, I_PUSH, "ldterm");
/* 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);
}
/*
* 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);
/* Fork /bin/passwd. */
#ifdef APOP
if ((apop != 0)) {
syslog(LOG_NOTICE, "calling %", POPAUTH);
if (execl(POPAUTH, "popauth", (char*)0) < 0) {
syslog(LOG_ERR, "can't exec %s: %m", POPAUTH);
return(0);
}
}
else
#endif
if (execl("/bin/passwd", "passwd", user, (char*)0) < 0) {
syslog(LOG_ERR, "can't exec /bin/passwd: %m");
return(0);
}
}
/*
* findpts()
*
* 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/pts/N" N [1 N-1]) 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 pts's allocated
* on this UNIX box.
*/
findpts (slave)
char **slave;
{
int master;
extern char *ptsname();
if ((master = open("/dev/ptmx", O_RDWR)) >= 1) {
grantpt(master);
unlockpt(master);
*slave = ptsname(master);
return(master);
}
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 pts.
* 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;
char c;
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 pts.
* 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, "");
alarm(TIMEOUT);
fgets (line, BUFSIZE, stdin);
alarm(0);
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);
}
char *crypt(char *, char *);
#ifdef APOP
/*
* Obscure APOP password so a cleartext search doesn't come up with something
* interesting.
*
*/
char *
obscure(string)
char *string;
{
unsigned char *cp, *newstr;
cp = newstr = (char*) strdup(string);
while (*cp) {
*cp++ ^= 0xff;
}
return(newstr);
}
#endif
int chkPass (user, pass, sp)
char *user;
char *pass;
char *sp;
{
#ifdef APOP
datum key, value;
DBM *db;
int i;
if ((db = dbm_open(APOP, O_RDONLY, 0)) == NULL) {
WriteToClient("500 Server error (unable to open APOP authorization DB).");
syslog(LOG_ERR, "Unable to open POP authorization DB %s for %s",
APOP, user);
return (FAILURE);
}
key.dsize = strlen(key.dptr = user) + 1;
value = dbm_fetch(db, key);
dbm_close(db);
if (value.dptr != NULL) {
if (((i = strlen(pass)) == 0) ||
((value.dsize - 1) != i) ||
(strncmp(pass, value.dptr, i) &&
strncmp(pass, obscure(value.dptr), i))) {
return (FAILURE);
} else {
apop++;
return (SUCCESS);
}
} else
#endif
/* Compare the supplied password with the password file entry */
if (strcmp (crypt (pass, sp), sp) != 0)
return (FAILURE);
else
return (SUCCESS);
}
WhoAreYou()
{
int len;
int sp = 0; /* Socket pointer */
struct sockaddr_in cs;
struct hostent *ch; /* Client host information */
/* Get the address and socket of the client to whom I am speaking */
len = sizeof(cs);
if (getpeername(sp,(struct sockaddr *)&cs,&len) < 0){
syslog(LOG_NOTICE,
"Unable to obtain socket and address of client, err = %d",errno);
exit(1);
}
/* Get the canonical name of the host to whom I am speaking */
ch = gethostbyaddr((char *)&cs.sin_addr, sizeof(cs.sin_addr), AF_INET);
if (ch == NULL) {
syslog(LOG_NOTICE, "Connection from %s", inet_ntoa(cs.sin_addr));
syslog(LOG_ERR, "Unable to get canonical name of client, err = %d",
errno);
}
else {
syslog(LOG_NOTICE, "Connection from %s (%s)", (char *)ch->h_name,
inet_ntoa(cs.sin_addr));
}
}
#ifdef NONAUTHFILE
checknonauthfile(user)
char *user;
{
char buf[MAXUSERNAMELEN+1];
FILE *fp;
if ((fp = fopen(NONAUTHFILE, "r")) != NULL) {
while (fgets(buf, MAXUSERNAMELEN+1, fp)) {
buf[strlen(buf) -1] = '\0';
if (!strcmp(buf, user)) {
fclose(fp);
return(-1);
}
}
fclose(fp);
}
return(0);
}
#endif