Understanding and Deploying LDAP Directory Services > 20. Developing New Applications > Example 1: A Password-Resetting Utility |
Example 1: A Password-Resetting UtilityBecause end users often forget their passwords, one of the most common tasks for help desk personnel is to assign new passwords. Organizations that have deployed a directory service need to enable their help desk personnel to easily reset passwords stored in the directory. In this section, we present a command-line utility called setpwd that resets the password in a user 's directory entry to a randomly chosen one. Directory UseThe setpwd utility uses the directory service in a simple way. All interaction with the LDAP service is performed using a special help desk identity that is given permission to modify the userPassword attribute in all person entries. Given a person's user ID that is supplied on its command line, the setpwd application locates the person's directory entry via an LDAP search operation. If one entry is found, a random password is generated and the existing userPassword values in the entry are replaced with the new password. Because this is a simple directory application, there are no significant application architecture issues to consider. The setpwd application does, however, avoid reading unnecessary attributes from the user entries. In addition, setpwd uses only one LDAP connection even when it resets several users' passwords. The Source CodeThe setpwd utility is written in C, and it uses the Netscape Directory SDK for C (which provides a standard LDAPv3 C API). Nearly all the functions that are part of the LDAP C API begin with the prefix ldap_ . The code was compiled and tested under the Sun Solaris 2.6 UNIX operating system, although it should be easy to port to other systems and implementations of the LDAP C API. The entire source code for setpwd consists of one file that is called, logically enough, setpwd.c . We present the code in pieces to aid understanding. The beginning of the setpwd.c file is shown in Listing 20.2. Notice the inclusion of the standard LDAP header file on line 9. Listing 20.2 The setpwd.c prelude1. /* 2. * A simple password resetting program for use by a helpdesk. 3. */ 4. #include <stdio.h> 5. #include <string.h> 6. #include <stdlib.h> 7. #include < ctype .h> 8. #include <time.h> 9. #include <ldap.h> 10. #define LONGEST_WORD 100 11. /* 12. * Global variables : 13. */ 14. char *base = "dc=airius,dc=com"; 15. char *host = "directory.airius.com"; 16. int port = LDAP_PORT; 17. char *binddn = "cn=HelpDesk, dc=airius, dc=com"; 18. char *bindpwd = "secret#password"; 19. char *wordfile = "/usr/dict/words"; 20. int quiet = 0; /* setenv QUIET to suppress chatter */ 21. /* 22. * Function prototypes : 23. */ 24. static int resetpwd( LDAP *ld, char *base, char *userid ); 25. static char *userid2dn( LDAP *ld, char *base, char * userid ); 26. static void print_ldap_error( LDAP *ld, int lderr, char *s ); 27. static char *randompwd( void ); 28. static char * randomword( void ); Some of LDAP- related global variables are defined and set on lines 14 “19. The distinguished name (DN) and password used to bind to the directory s ervice are hard-coded into this application (lines 17 and 18), which is unusual but convenient for the help desk personnel who are the audience for this application. The wordfile variable defined on line 19 has nothing to do with LDAP; it is used by setpwd 's random password generation code (described later). The setpwd main() function is shown in Listing 20.3. Listing 20.3 The setpwd.c main () function1. int 2. main( int argc, char **argv ) 3. { 4. LDAP *ld; 5. int i, rc; 6. quiet = (( getenv( "QUIET" ) != NULL )); 7. if ( argc < 2 ) { 8. fprintf( stderr, "usage: %s userid \n", argv[ 0 ] ); 9. return( 2 ); 10. } 11. /* 12. * Connect and bind to the directory server. 13. */ 14. if ( !quiet ) puts( "Contacting LDAP server " ); 15. if (( ld = ldap_init( host, port )) == NULL ) { 16. perror( host ); 17. return( 1 ); 18. } 19. if (( rc = ldap_simple_bind_s( ld, binddn, bindpwd )) 20. != LDAP_SUCCESS ) { 21. print_ldap_error( ld, rc, binddn ); 22. ldap_unbind( ld ); 23. return( 1 ); 24. } 25. /* 26. * Process userid arguments, setting random passwords 27. * for each one. 28. */ 29. rc = 0; 30. srandom( time( NULL )); 31. for ( i = 1; i < argc; ++i ) { 32. if ( !quiet && i > 1 ) { 33. putchar ( '\n' ); 34. } 35. rc = resetpwd( ld, base, argv[ i ] ); 36. } 37. /* 38. * close LDAP server connection and exit. 39. */ 40. ldap_unbind( ld ); 41. return( rc ); 42. } After some simple argument checking and initialization, the code begins to interact with the LDAP SDK and the directory service. The code on lines 15 “18 obtains an LDAP session handle for our LDAP server host and port. Note that the ldap_init() function call does not open a connection to the LDAP server. A connection to the LDAP server is opened the first time an LDAP request is made. Such a request is made by the code on lines 19 “24, which binds to the LDAP server using the help desk DN and password. The ldap_simple_bind_s() call connects to the directory server (if the session handle is not already connected, as is the case here), issues an LDAP bind request, and waits for the server response. A return code of LDAP_SUCCESS indicates that the DN and password were accepted and that the authentication was successful. The code on lines 25 “36 moves through the arguments provided on the command line and passes each one to the resetpwd() function, which is described next . The ldap_unbind() call included in the clean-up code on lines 37 “42 does two things: It closes the LDAP connection and disposes of the memory and any other resources consumed by the LDAP session handle. The code for the resetpwd() function, which is responsible for resetting one user's password, is shown in Listing 20.4. Listing 20.4 The setpwd.c resetpwd () function1. /* 2. * resetpwd(): set an entry's password to a random string. 3. * 4. * Returns 0 on success and 1 on failure. 5. */ 6. static int 7. resetpwd( LDAP *ld, char *base, char *userid ) 8. { 9. char *dn, *pwd, *vals[2]; 10. int rc; 11. LDAPMod mod, *mods[2]; 12. /* 13. * Find the entry. 14. */ 15. if ( !quiet ) printf( "Finding entry %s \n", userid ); 16. if (( dn = userid2dn( ld, base, userid )) == NULL ) { 17. return( 1 ); 18. } 19. if (( pwd = randompwd() == NULL ) { 20. return( 1 ); 21. } 22. /* 23. * Replace the userPassword attribute. 24. */ 25. if ( !quiet ) printf( "Modifying entry %s \n", userid ); 26. vals[0] = pwd; 27. vals[1] = NULL; 28. mod.mod_values = vals; 29. mod.mod_type = "userPassword"; 30. mod.mod_op = LDAP_MOD_REPLACE; 31. mods[0] = &mod; 32. mods[1] = NULL; 33. if (( rc = ldap_modify_s( ld, dn, mods )) == LDAP_SUCCESS ) { 34. rc = 0; 35. printf( "Password reset. %s's new password is: %s\n", 36. userid, pwd ); 37. if ( !quiet ) { 38. printf( "Don't forget to remind %s to reset " 39. "his/her password right away!\n", userid ); 40. } 41. } else { 42. rc = 1; 43. print_ldap_error( ld, rc, "modify" ); 44. } 45. free( pwd ); 46. ldap_memfree( dn ); 47. return( rc ); 48. } The first task performed by this code is to find the user's directory entry (we need to know the DN to be able to modify the user's entry). The code on lines 12 “18 calls a utility function named userid2dn() (described next) to find and return the user's DN given his or her user ID. The code on lines 19 “21 calls another utility function called randompwd() (described later), which generates a new password for the user. The remainder of the resetpwd() code (lines 22 “48) accomplishes the task of replacing the userPassword attribute in the user's entry with the newly generated password. After setting up a one-element array of modifications, the ldap_modify_s() LDAP SDK function is called to direct the server to make the password change. The source code for the userid2dn() utility function is shown in Listing 20.5. This function uses an LDAP subtree search operation to find a user's directory entry given his or her user ID. Listing 20.5 The setpwd.c userid2dn () function1. /* 2. * userid2dn(): Given an entry's userid, return its DN. 3. * 4. * The DN returned should be freed by passing it to 5. * ldap_memfree(). If no entry is found, NULL is returned. 6. */ 7. static char * 8. userid2dn( LDAP *ld, char *base, char *userid ) 9. { 10. int rc; 11. char *filter, *dn = NULL; 12. char *attrs[] = { LDAP_NO_ATTRS, NULL }; 13. LDAPMessage *e, *res = NULL; 14. if (( filter = (char *)malloc( strlen( userid ) + 7 )) 15. == NULL ) { 16. perror( "malloc" ); 17. return( NULL ); 18. } 19. sprintf( filter, "(uid=%s)", userid ); 20. rc = ldap_search_s( ld, base, LDAP_SCOPE_SUBTREE, 21. filter, attrs, 0, &res ); 22. free( filter ); 23. if ( rc != LDAP_SUCCESS ) { 24. print_ldap_error( ld, rc, "search" ); 25. } else if (( e = ldap_first_entry( ld, res )) == NULL ) { 26. fprintf( stderr, "No match for %s\n", userid ); 27. } else if ( ldap_next_entry( ld, e ) != NULL ) { 28. fprintf( stderr, "%d matches for %s\n", 29. ldap_count_entries( ld, res ), userid ); 30. } else { 31. dn = ldap_get_dn( ld, e ); /* found just one! */ 32. } 33. ldap_msgfree( res ); 34. return( dn ); 35. } The code on lines 14 “19 creates an LDAP search filter that looks like ( uid= ID ), where ID is the user ID provided on the setpwd command line. The actual search is done by the ldap_search_s() LDAP SDK call that appears on lines 20 and 21. Notice that the attrs parameter, which is an array of attribute types to be returned with each entry found during the search, contains one type called LDAP_NO_ATTRS ( attrs is initialized on line 12). The special LDAP_NO_ATTRS macro, defined by the LDAP API, is used to indicate that no attribute types should be returned. This is desirable because it is wasteful to ask for attributes we do not need, and in this instance we are interested in obtaining only the DN of the entry. If we were to pass NULL for the attrs parameter, every attribute in the entry would be returned. The code on lines 23 “35 checks to see if exactly one entry was found; if so, it returns the DN of the entry. If more than one entry is found, or if no entries are found, an error is reported . The print_ldap_error() utility function is shown in Listing 20.6. This simple function is called from several places in setpwd.c to report LDAP-related errors in a consistent way. Listing 20.6 The setpwd.c print_ldap_error () function1. /* 2. * Display an LDAP error message. 3. */ 4. void 5. print_ldap_error( LDAP *ld, int lderr, char *s ) 6. { 7. fprintf( stderr, "%s: %s\n", s, ldap_err2string( lderr )); 8. } The randompwd() function, which is called from the resetpwd() function we already looked at, is shown in Listing 20.7. This code does not perform any LDAP-related functions. It uses a file that contains a series of words (one per line) and the standard random() number generator function to select a new password. The actual algorithm used could easily be altered to match a specific organization's security policy or the preferences of help desk technical staff. Listing 20.7 The setpwd.c randompwd () function1. /* 2. * randompwd(): generate a reasonably good password using some 3. * random words from a file. The password should be easy to 4. * remember and able to be passed on verbally to a user. 5. * 6. * The algorithm we use is this: 7. * a) select two random words from the words file (w1 and w2). 8. * b) pick a random punctuation character (c). 9. * c) pick a random digit 0-9 (d) 10. * d) construct a candidate password as: w1 c w2 d 11. * e) change the case of one of the letters at random 12. */ 13. static char * 14. randompwd( void ) 15. { 16. static char *punctuation = "!@#$%&*-+=,.?"; 17. char *pwd, *w1, *w2, *p; 18. if (( w1 = randomword() == NULL ( w2 = randomword() == NULL ) { 19. if ( w1 != NULL ) { 20. free( w1 ); 21. } 22. return( NULL ); 23. } 24. if (( pwd = (char *)malloc( LONGEST_WORD * 2 + 3 )) != NULL ) { 25. sprintf( pwd, "%s%c%s%d", w1, 26. punctuation[ random() % strlen( punctuation ) ], w2,random() % 10 ); 27. 28. p = pwd + ( random() % strlen( pwd )); 29. if ( isalpha ( *p )) { 30. if ( isupper ( *p )) { 31. *p = tolower ( *p ); 32. } else { 33. *p = toupper( *p ); 34. } 35. } 36. } 37. free( w1 ); 38. free( w2 ); 39. return( pwd ); 40. } The randompwd() function uses a utility function called randomword() to pick the two words that is uses to construct the password value. The first part of the randomword() function, which consists mainly of initialization tasks, is shown in Listing 20.8. Listing 20.8 The setpwd.c randomword () function (part 1 of 2)1. /* 2. * randomword(): return a random, lowercase word. 3. * 4. * Returns a malloc'd string if successful. 5. * Returns NULL on error, e.g., if unable to access 6. * the words file. 7. * 8. * This function assumes that all words in the file are 9. * less than LONGEST_WORD characters long. 10. */ 11. static char * 12. randomword( void ) 13. { 14. static FILE *fp = NULL; 15. static long filesize = 0L; 16. char *word, *p; 17. int len; 18. if ( fp == NULL ) { 19. /* 20. * initialize: open the words file and 21. * determine its size 22. */ 23. if (( fp = fopen( wordfile, "r" )) == NULL 24. fseek( fp, 0L, SEEK_END ) == -1 25. ( filesize = ftell ( fp )) == -1 ) { 26. perror( wordfile ); 27. if ( fp != NULL ) { 28. fclose( fp ); 29. } 30. return( NULL ); 31. } 32. filesize -= LONGEST_WORD; 33. } The remainder of the randomword() function, which randomly seeks to a location in the words file and returns a word, is shown in Listing 20.9. Listing 20.9 The setpwd.c randomword () function (part 2 of 2)1. /* 2. * seek to a random location in the file. 3. */ 4. if ( fseek( fp, random() % filesize, SEEK_SET ) == -1 ) { 5. perror( wordfile ); 6. return( NULL ); 7. } 8. if (( word = (char *)malloc( LONGEST_WORD )) == NULL ) { 9. perror( "malloc" ); 10. return( NULL ); 11. } 12. /* 13. * read the tail (remainder) of one word and then 14. * the entire next word, which is what we will return. 15. */ 16. fgets( word, LONGEST_WORD - 1, fp ); 17. if ( fgets( word, LONGEST_WORD - 1, fp ) == NULL ) { 18. perror( wordfile ); 19. free( word ); 20. return( NULL ); 21. } 22. len = strlen( word ) - 1; 23. if ( word[ len ] == '\n' ) { 24. word[ len ] = '\0'; 25. } 26. for ( p = word; *p != '\0'; ++p ) { 27. if ( isupper( *p )) { 28. *p = tolower( *p ); 29. } 30. } 31. return( word ); 32. } The Help Desk Staff's ExperienceListing 20.10 shows a sample run of the setpwd application. Listing 20.10 Using setpwd to reset one user's password% setpwd scarter Contacting LDAP server Finding entry scarter Modifying entry scarter Password reset. scarter's new password is: Knife,attentive7 Don't forget to remind scarter to reset his/her password right away! Sam Carter's password was reset to the random password Knife,attentive7 . Listing 20.11 shows another sample run of the setpwd utility. Listing 20.11 Using setpwd to reset several users' passwords% setenv QUIET % setpwd scarter tmorris kvaughan abergin dmiller gfarmer kwinters trigden cschmith jwallace Password reset. scarter's new password is: define+chaRd5 Password reset. tmorris's new password is: cyclades@eT0 Password reset. kvaughan's new password is: sorghum#releVant5 Password reset. abergin's new password is: shinGle?appellant0 Password reset. dmiller's new password is: grackLe,polish2 Password reset. gfarmer's new password is: periClean+problematic5 Password reset. kwinters's new password is: deforest*articulatoRy6 Password reset. trigden's new password is: bombard+decontrolling2 Password reset. cschmith's new password is: pessimist+lIne9 Password reset. jwallace's new password is: paulSon,battle3 In this instance, 10 people's passwords were reset with a single run of the utility. The QUIET environment variable is set prior to running setpwd to reduce the amount of output. Ideas for ImprovementThe setpwd application could be improved in many ways. Here are a few ideas:
|
Index terms contained in this sectionapplicationsdeveloping setpwd example 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th 26th 27th 28th 29th 30th 31st 32nd 33rd 34th 35th 36th 37th 38th 39th 40th developing applications setpwd example 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th 26th 27th 28th 29th 30th 31st 32nd 33rd 34th 35th 36th 37th 38th 39th 40th directories applications developing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th 26th 27th 28th 29th 30th 31st 32nd 33rd 34th 35th 36th 37th 38th 39th 40th listings setpwd utility prelude (source code) 2nd 3rd 4th sample run 2nd 3rd 4th setpwd.c main() function 2nd 3rd 4th 5th 6th 7th setpwd.c print_ldap_error() function 2nd setpwd.c randompwd() function 2nd 3rd 4th setpwd.c randomword() function 2nd 3rd 4th 5th 6th setpwd.c resetpwd() function 2nd 3rd 4th 5th setpwd.c userid2dn() function 2nd 3rd 4th passwords setpwd example improvement ideas 2nd prelude (listing) 2nd 3rd 4th sample run 2nd 3rd 4th setpwd.c main() function 2nd 3rd 4th 5th 6th 7th setpwd.c print_ldap_error() function 2nd setpwd.c randompwd() function 2nd 3rd 4th setpwd.c randomword() function 2nd 3rd 4th 5th 6th setpwd.c resetpwd() function 2nd 3rd 4th 5th setpwd.c userid2dn() function 2nd 3rd 4th userPassword attribute setpwd utility example improvement ideas 2nd prelude (listing) 2nd 3rd 4th sample run 2nd 3rd 4th setpwd.c main() function 2nd 3rd 4th 5th 6th 7th setpwd.c print_ldap_error() function 2nd setpwd.c randompwd() function 2nd 3rd 4th setpwd.c randomword() function 2nd 3rd 4th 5th 6th setpwd.c resetpwd() function 2nd 3rd 4th 5th setpwd.c userid2dn() function 2nd 3rd 4th userPassword attribute setpwd.c main() function setpwd utility 2nd 3rd 4th 5th setpwd.c print_ldap_error() function setpwd utility 2nd setpwd.c randompwd() function setpwd utility 2nd 3rd 4th setpwd.c randomword() function setpwd utility 2nd 3rd 4th 5th 6th setpwd.c resetpwd() function setpwd utility 2nd 3rd 4th 5th setpwd.c userid2dn() function setpwd utility 2nd 3rd 4th userPassword attribute setpwd utility example |
2002, O'Reilly & Associates, Inc. |