Home | pfodApps/pfodDevices | WebStringTemplates | Java/J2EE | Unix | Torches | Superannuation | | About Us
 

Forward Logo (image)      

Freebees - Changing User passwords from a Webpage
Modifications to Eudora's password changing program

These mods are base on the Eudora's password changing program which listens on a port for connections from Eudora requesting a change of a user's password. The program uses a nifty trick to pass the burden of changing the user's password on to the system's passwd program.

My modified source merely takes its inputs from the command line and outputs its results to STDOUT instead of reading from and writing to the Eudora client. This program is called from a perl CGI program.

Modifed source file webpasswd.c is show below together with a simple Makefile.

File webpasswd.c

/* 
  Mod 30/9/97 to allow users to update password from web page
    input args are userid, oldpassword, newpassword
    Code that calls this program needs to check newpassword against retry

    write messages to stdout for collection by web page etc
  I have commented out the hand shaking that originally communicated with the Eudora client
  I also added code that writes out a log entry each time a user changes their password.
  The new password is NOT written to the log file.
*/


/*
 * poppassd.c
 *
 * A Eudora and NUPOP change password server for Solaris.
 *
 * Graziano Sommariva
 * Ansaldo Sistemi Informatici Genova Italy.
 * sommariva@ansaldo.it
 *
 * Based on earlier versions by 
 * John Norstad
 * j-norstad@nwu.edu
 * 
 * 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.
 *
 * Logs to the local2 facility. Should have an entry in /etc/syslog.conf
 * like the following:
 *
 * local2.err   /var/log/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.  [mark@qualcomm.com]
 *
 */
 
 
#define VERSION "1.2 For Solaris 2"

#define SUCCESS 1
#define FAILURE 0
#define BUFSIZE 1024

#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>


/* 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 */
#define AUTH_UID        10

static char *P1[] =
   {"Old password:",
    "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:",
    "passwd:  Changing password for a85011\nOld password: ",
    ""};

static char *P2[] =
   {"\nNew password:",
    "\n*'s New password:",
    ""};

static char *P3[] =
   {"\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 entry changed on *\n",
    ""};

abortme()
{
      WriteToClient ("500 Timeout.  Input idle for too long.");
      exit(1);
}

main (argc, argv)
int argc;
char *argv[];
{
     char line[BUFSIZE];
     char *user;
     char *oldpass;
     char *newpass;
     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 = NULL;
     
     openlog ("poppassd", LOG_PID, LOG_LOCAL2);

  /* *******   
     WriteToClient ("200 poppassd v%s hello, who are you?", VERSION);
     ReadFromClient (line);
     sscanf (line, "user %s", user) ;
  ***************/

     user = argv[1]; /* first arg is userid */
     if ((user == NULL) || (strlen (user) == 0))
     {
          WriteToClient ("Username required.");
          exit(1);
     }

  /***********************
     WriteToClient ("200 your password please.");
     ReadFromClient (line);
     sscanf (line, "pass %s", oldpass) ;
  ***********************/

     oldpass = argv[2];

    if ((oldpass == NULL) || (strlen (oldpass) == 0))
     {
          WriteToClient ("Password required.");
          exit(1);
     }
     
     if ( ((pw = getpwnam (user)) == NULL) || ((sp = getspnam (user)) == NULL) )
     {
          WriteToClient ("Invalid password. User IDs and passwords are case sensitive");
          exit(1);
     }


     if (pw->pw_uid <= AUTH_UID)
     {
          syslog (LOG_INFO|LOG_LOCAL2, "Authorization error, attempt to change password of %s", user);    
          WriteToClient ("Invalid password. User IDs and passwords are case sensitive");
          exit(1);
     }

     if (chkPass (user, oldpass, sp->sp_pwdp) == FAILURE)
     {
          WriteToClient ("Invalid password. User IDs and passwords are case sensitive");
          exit(1);
     }


/***********************
     WriteToClient ("200 your new password please.");
     ReadFromClient (line);
     sscanf (line, "newpass %s", newpass);
****************************/

     newpass = argv[3];
 
     /* new pass required */
     if ((newpass == NULL) || (strlen (newpass) == 0))
     {
          WriteToClient ("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("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 ("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_INFO|LOG_LOCAL2, "failed attempt by %s", user); 
               if (*emess == '\0') {
                  WriteToClient ("Unable to change password." );
               } else {
                  WriteToClient ("%s", emess);
               }
               exit(1);
          }

          if ((wpid = waitpid (pid, &wstat, 0)) < 0)
          {
               syslog (LOG_ERR, "wait for /bin/passwd child failed: %m");
               WriteToClient ("Server error (wait failed), get help!");
               exit (1);
          }

          if (pid != wpid)
          {
               syslog (LOG_ERR, "wrong child (/bin/passwd waited for!");
               WriteToClient ("Server error (wrong child), get help!");
               exit (1);
          }

          if (WIFEXITED (wstat) == 0)
          {
               syslog (LOG_ERR, "child (/bin/passwd) killed?");
               WriteToClient ("Server error (funny wstat), get help!");
               exit (1);
          }

          if (WEXITSTATUS (wstat) != 0)
          {
               syslog (LOG_ERR, "child (/bin/passwd) exited abnormally");
               WriteToClient ("Server error (abnormal exit), get help!");
               exit (1);
          }

          syslog (LOG_INFO|LOG_LOCAL2, "password changed for %s", user); 

          WriteToClient ("Password changed, thank-you.");

#ifdef OLD_CODE
          ReadFromClient (line);
          if (strncmp(line, "quit", 4) != 0) {
                WriteToClient("500 Quit required.");
        /*      exit (1);   remove to ensure process changed password below */
          }
          
          WriteToClient("200 Bye.");
#endif /* OLD_CODE */
          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. */

   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(60);
        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 *);

int chkPass (user, pass, sp)
char *user;
char *pass;
char *sp;
{

     /*  Compare the supplied password with the password file entry */
     if (strcmp (crypt (pass, sp), sp) != 0)
          return (FAILURE);
     else 
          return (SUCCESS);
}

Here is a simple Makefile for webpasswd

BINDIR = /usr/local/lib
LIBDIR = 
CFLAGS = -g
LFLAGS = -g
CC = cc
CCM = $(CC) -Em

OBJECTS = webpasswd.o
LIBS =

webpasswd: $(OBJECTS)
        $(CC) -o webpasswd $(LFLAGS) $(OBJECTS) $(LIBS)

install: webpasswd
        /usr/sbin/install -g bin -u root -m 500 webpasswd $(BINDIR)

clean:
        rm -f *.o *~* core Makefile.new Makefile.bak webpasswd

webpasswd.o: webpasswd.c
        $(CC) -c $(CFLAGS) webpasswd.c

Refer to Conditions of Use


Forward home page link (image)

Contact Forward Computing and Control by
©Copyright 1996-2012 Forward Computing and Control Pty. Ltd. ACN 003 669 994