Book Home Perl for System AdministrationSearch this book

10.5. Preventing Suspicious Activities

The very last attribute of a night watchman that we will consider is an eye towards prevention. This is the voice that says "You know, you shouldn't leave those fresh-baked pies on the window sill to cool."

We're going to conclude this chapter with an example that, when properly deployed, could positively impact a single machine, or an entire computing infrastructure. As a symbolic gesture to close this book, we'll build our own module instead of showing you how to make use of other people's.

The goal I have in mind is the prevention, or at least reduction, of bad passwords. Good security mechanisms have been thwarted by the selection of bad passwords since the dawn of time. Oog's password to get back into the clan's cave was probably "oog." Nowadays, the situation is exacerbated by the widespread availability of sophisticated password cracking programs like John the Ripper by Solar Designer, L0phtCrack by Mudge and Weld Pond, and Alec Muffett's Crack.

The only way to prevent the vulnerability in your systems these programs expose is to avoid bad passwords in the first place. You need to help your users choose and retain hard-to-crack passwords. One way to do this on Unix machines (though the code could easily be ported to NT or MacOS) is to use libcrack, also by Alec Muffett. In the process of writing Crack, Muffett did the system administration community a great service by taking some of the methods used in Crack and distilling them to a single password-checking library written in C.

This library has exactly one function for its user interface: FascistCheck( ). FascistCheck( ) takes two arguments: a string to check and the full pathname prefix of the dictionary file created when installing libcrack. It returns either NULL if the string would be a "safe" password, or an explanatory piece of text, e.g., "is a dictionary word," if it is vulnerable to cracking. It would be extremely handy to be able to use this functionality as part of any Perl program that sets or changes a password,[2] so let's look at how we would build a module that would incorporate this function. This foray will require a very brief peek at some C code, but I promise it will be quick and painless.

[2]A similar example where libcrack has been put to good use is npasswd (found at http://www.utexas.edu/cc/unix/software/npasswd/), Clyde Hoover's superb replacement for the Unix changing program passwd.

Our first step is to build the libcrack package at http://www.users.dircon.co.uk/~crypto/. The process detailed in the distribution is straightforward. Let me offer two hints:

Once we have the C library libcrack.a built, we need to pick a method for calling the FascistCheck( ) function in that library from within Perl. There are two popular methods for creating this sort of binding, XS and SWIG. We'll be using XS because it is easy to use for simple jobs and all of the necessary tools ship with the Perl distribution. For an in depth comparison of the two methods, see Advanced Perl Programming by Sriram Srinivasan (O'Reilly).

The easiest way to begin with XS is to use the h2xs program to create a proto-module for you:

$ h2xs -A -n Cracklib
Writing Cracklib/Cracklib.pm
Writing Cracklib/Cracklib.xs
Writing Cracklib/Makefile.PL
Writing Cracklib/test.pl
Writing Cracklib/Changes
Writing Cracklib/MANIFEST

Table 10-2 describes the files created by this command.

Table 10.2. Files Created by h2xs -A -n Cracklib

Filename

Description

Cracklib/Cracklib.pm

Prototype Perl stub and documentation

Cracklib/Cracklib.xs

C code glue

Cracklib/Makefile.PL

Makefile-generating Perl code

Cracklib/test.pl

Prototype test code

Cracklib/Changes

Version documentation

Cracklib/MANIFEST

List of files shipped with module

We only need to change two of these files to get the functionality we seek. Let's take on the hardest part first: the C code glue. Here's how the function is defined in the libcrack documentation:

char *FascistCheck(char *pw, char *dictpath);

In our Cracklib/Cracklib.xs glue file, we repeat this definition:

PROTOTYPES: ENABLE
 
char *
FascistCheck(pw,dictpath)
      char *pw
      char *dictpath

The PROTOTYPES directive will create Perl prototypes for the functions in our glue file. This isn't an issue for the code we're writing, but we include the directive to stifle a warning message in the build process.

Right after the function definition, we describe how it's called and what it returns:

CODE:
      RETVAL = (char *)FascistCheck(pw,dictpath);
      OUTPUT:
      RETVAL

RETVAL is the actual glue here. It represents the transfer point between the C code and the Perl interpreter. Here we tell Perl that it should receive a string of characters returned from the FascistCheck( ) C library function and make that available as the return value (i.e., OUTPUT) of the Perl Cracklib::FascistCheck( ) function. That's all the C code we'll need to touch.

The other file we need to modify needs only a single line changed. We need to add another argument to the WriteMakefile( ) call in Makefile.PL to be sure Perl can find the libcrack.a file. Here's that new line in context:

'LIBS'      => [''],   # e.g., '-lm'
 'MYEXTLIB' => '/usr/local/lib/libcrack$(LIB_EXT)' # location of cracklib
 'DEFINE'    => '',     # e.g., '-DHAVE_SOMETHING'

That's the bare minimum we need to do to make this module work. If we type:

perl Makefile.PL
make
make install

we could begin to use our new module like this:

use Cracklib;
use Term::ReadKey;    # for reading of password

$dictpath = "/usr/local/etc/cracklib/pw_dict";

print "Please enter a password: ";
ReadMode 2;           # turn off echo
chomp($pw = ReadLine);# read password
ReadMode 0;           # return tty to prev state
print "\n";

$result = Cracklib::FascistCheck($pw,$dictpath);
if (defined $result){
    print "That is not a valid password because $result.\n";
}
else {
    print "That password is peachy, thanks!\n";
}

Don't skip right to using the module yet. Let's make this a professional-grade module before we install it.

First, add a script to test that the module is working correctly. This script needs to call our function with some known values and report back in a very specific way if it received the correct known responses. At the start of our tests, we need to print a range of test numbers. For example, if we were going to provide 10 tests, we would first print 1..10. Then, for every test we perform, we need to print either "ok" or "not ok" and the test number. The standard Perl module building code will then interpret this output and present the user with a nice summary of the test results.

h2xs was kind enough to provide a sample test script we can modify. Let's make a t directory (the standard default directory for a module test suite) and rename test.pl to t/cracklib.t. Here's some code we can tag on to the end of t/cracklib.t to perform a set of tests:

# location of our cracklib dictionary files
$dictpath = "/usr/local/etc/pw_dict"; 

# test strings and their known cracklib responses
%test = 
  ("happy"        => "it is too short",
   "a"            => "it's WAY too short",
   "asdfasdf"     => "it does not contain enough DIFFERENT characters",
   "minicomputer" => "it is based on a dictionary word",
   "1ftm2tgr3fts" => "");

# Cycle through all of the keys in our mapping, checking to see if 
# cracklib returns the expected response. If it does, print "ok",
# otherwise print "not ok"
$testnum = 2;
foreach $pw (keys %test){
    my ($result) = Cracklib::FascistCheck($pw,$dictpath);
    if ((defined $result and $result eq $test{$pw}) or
       (!defined $result and $test{$pw} eq "")){
          print "ok ",$testnum++,"\n";
    }
    else {
	    print "not ok ",$testnum++,"\n";
    }
}

There are six tests being made (the previous five from the %test hash and a module load test), so we need to change the line in t/cracklib.t that says:

BEGIN { $| = 1; print "1..1\n"; }

to:

BEGIN { $| = 1; print "1..6\n"; }

Now, we can type make test and Makefile will run the test code to check that our module is working properly.

A test script is certainly important, but our script won't be nearly respectable if we omit this crucial component: documentation. Take some time and flesh out the stub information in the Cracklib.pm and Changes files. It is also a good idea to add a README or INSTALL file describing how to build the module, where to get the component parts like libcrack, example code, etc. These new files and the earlier renaming of the test.pl file should be noted in the MANIFEST file to keep the generic module-building code happy.

Finally, install your module everywhere in your infrastructure. Sprinkle calls to Cracklib::FascistCheck( ) everywhere you need to set or change passwords. As the number of bad passwords diminishes in your environment, so shall the night watchman smile kindly upon you.



Library Navigation Links

Copyright © 2001 O'Reilly & Associates. All rights reserved.