Wenton's Programming Page

Collecting passwords

One of the silliest things in the world to have to struggle with is the collection of passwords.  I unsderstand that it is necessary to have passwords and use them to authenticate a person's rights to acces a certain computer.  I just think it's a trmendous load of crap to so many people want to steal rights to a machine they don't have rights to.  Then again, if I could have free access to an array of super-computers, I have a few research projects I'd like to get into.

That being said, my opinion doesn't matter - passwords are a necessary evil - deal with it! So, how to deal with thes nasty little things in our programs.  It's easy enough to collect a user name with a simple printf() and scanf() pair.  But when you try to get their password, you probably would like them to be able to type their password without the whole world being able to see it! It's fine to let everyone see the username - usually either the first name or the first initial, last name, or some similar combination... something anyone would be able to guess fairly quickly.  But the password is for security - or for their own protection... not something to display to the whole world, as happens with the scanf() function.  This was recently (September 2006) a problem for me.

I did some digging, and it was really nice to find that I was not the only person dealing with this issue.  One of the most common solutions was, "Use getch()!" Well, OK, fine, except, you little crack-head M$-WinDoze programmer, getch() is not a unix function, it exists only in the Micro$oft world, and even there, it is not a function, it's a macro, using the getchar() function, simply reading from the input stream.  In the real world, you still have the same problem.  What I did finally find, however, was a few random discussions on turning off the local keyboard echo.  Most of these discussions were in MUD gaming arenas, and people were talking about using terminal special character commands, which were still not going to be particularly useful.  Then I found a short code clipping that was perfect...

Adjust the terminal settings! This can work! Clear the local echo flag during the time that you want to collect the password, then turn the echo back on, or in this code, just restore the original terminal settings.  I have a few modifications I'd make to the code, but because the original author actually did a pretty decent job, I wanted to include his code, unmolested:

/*
 * testpasswd.c
 *
 * A password checking program. Use at your own risk!
 *
 * Author: eric_r_turner(at)yahoo(dot)com
 */

#include <crypt.h> // compile with -lcrypt
#include <shadow.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <termios.h>
#define _XOPEN_SOURCE
#include <unistd.h>

#define INPUT_LENGTH 256
#define SALT_LENGTH 12

int main() {

   int            authenticated = 0;
   char*          cryptedPassword = NULL;
   char*          newLine = NULL;
   struct termios newTerminalSettings;
   struct termios oldTerminalSettings;
   struct spwd*   passwordEntry = NULL;
   char           plainPassword[INPUT_LENGTH + 1];
   char           salt[SALT_LENGTH + 1];
   char           userName[INPUT_LENGTH + 1];

   strcpy( plainPassword , "" );
   strcpy( salt , "" );
   strcpy( userName , "" );

   /*
    * Get the user login name.
    *
    * fgets captures the newline character, so replace
    * the newline with a null termination character.
    */

   printf( "Login: " );
   fgets( userName , INPUT_LENGTH , stdin );

   if( ( newLine = strrchr( userName , '\n' ) ) != NULL ) {
      *newLine = '\0';
      newLine = NULL;
   }


   /*
    * Get the user password.
    *
    * We don't want to echo the user's password to the
    * terminal, so temporarily disable ECHO in the
    * terminal settings.
    *
    * fgets captures the newline character, so replace
    * the newline with a null termination character.
    */

   printf( "Password: " );

   tcgetattr( fileno( stdin ) , &oldTerminalSettings );
   newTerminalSettings = oldTerminalSettings;
   newTerminalSettings.c_lflag &= ~ECHO;
   tcsetattr( fileno( stdin ) , 0 , &newTerminalSettings );

   fgets( plainPassword , INPUT_LENGTH , stdin );
   printf( "\n" );

   if( ( newLine = strrchr( plainPassword , '\n' ) ) != NULL ) {
      *newLine = '\0';
      newLine = NULL;
   }

   tcsetattr( fileno( stdin ) , 0 , &oldTerminalSettings );


  /*
    * strcmp will  match if one of its arguments
    * has a length of zero! This is BAD when authenticating
    * users, so make sure the user hasn't just hit Enter
    * when supplying their username or password.
    */

   if ( ( strlen( userName ) == 0 ) ||
        ( strlen( plainPassword ) == 0 ) ) {
      printf( "You must enter a username or password.\n" );
      return( -1 );
   }


   /*
    * Compare the password entered by the user with the
    * one listed in /etc/shadow.
    */

   lckpwdf();
   setspent();

   if ( ( passwordEntry = getspnam( userName ) ) != NULL ) {

      /*
       * Use the the first 12 characters of the user's
       * crypted password in /etc/shadow as the salt to crypt().
       */

      strncpy( salt , passwordEntry->sp_pwdp , SALT_LENGTH );
      cryptedPassword = crypt( plainPassword , salt );

      if ( strcmp( passwordEntry->sp_pwdp , cryptedPassword ) == 0 ) {
         authenticated = 1;
      }

   }

   endspent();
   ulckpwdf();


   if ( authenticated ) {
      printf( "Valid username and password.\n" );
   } else {
      printf( "Invalid username or password.\n" );
   }

   return( 0 );
}

There it is, fairly eloquent, really.  This program also has the cool feature of encrypting the password and checking it against the local system's shadow password file to authenticate the user's access priviledges.  This program compiled quite nicely, right out of the box, except I had to link the libcrypt.a library against it to get the crypt() function, but that's pretty minor, really.

Now, there are a few things I would change.  First, there is a line copying oldTerminalSettings to newTerminalSettings with a simple = statement.  In C++, you can get away with things like that, and even the newer 'C' compilers seem to allow this.  HOWEVER, a short tour through the /usr/include/termios.h file (leading to the /usr/include/bits/termios.h file) shows us a structure containing 6 unsigned integers, an unsigned character, and an array of unsigned characters.  You can't use a simple = statment here... bad programmer, no cookie! The "safe" way to do this is with something like:

  memcpy( &newTerminalSettings, &oldTerminalSettings, sizeof(struct termios ) );

... or copy each element of the structure, but that would also suck big hairy left toes.

Another thing that should be changed, although this doesn't show up until you actually run the program, is a missing carriage return.  Seems silly, I know, but after the prompt to enter the password, remember that local screen echo is turned off, so the user types in their password, hits return, all without echos... the cursor is currently located at the end of the line asking for the password, so the next character printed will appear at that point, not at the beginning of the next line like you'd probably prefer.  The fix is simply, really; once you turn local echo back on (or before would probably work, too), just print an extra \n.  no problem!

WARNING!!!DON'T FORGET THIS PART!!! Hackers love it when you keep unencrypted copies of passwords in memory.  While attacking, they can access system memory and steal words from it.  After the plainPassword is encrypted, destroy it! Use something like:

memset( plainPassword, 0, <size of password buffer> );

to erase the password area!

home

Wenton's email (wenton@ieee.org)