Example 1: setpwd, a Password-Resetting Utility

   

Example 1: setpwd , a Password-Resetting Utility

Because end users often forget their passwords, one of the most common tasks for Help Desk personnel is to reset or assign new passwords. In this section, a command-line utility named setpwd is presented that resets the password in a user 's directory entry (either to a randomly chosen value, or to a password value supplied on the setpwd command line).

Directory Use

The setpwd utility uses a directory service in a simple way. The LDAP service is always accessed by Help Desk personnel who have permission to modify the userPassword attribute in all person entries. Given a user ID that is supplied on its command line, the setpwd application locates a person's directory entry using an LDAP search operation. If exactly one entry is found, the existing userPassword values in the entry are replaced with a new password value.

Because this is a simple directory application, there are no significant application architecture issues to consider. The setpwd application relies on standard schemas and avoids reading unnecessary attributes from the user entries. In addition, setpwd uses only one LDAP connection even when it is asked to reset several users' passwords.

The Help Desk Staff's Experience

Because the setpwd application is meant for use by Help Desk staff, it is designed to be efficient and easy to use. This section provides some usage examples. Listing 21.3 shows one sample run of the setpwd application. In this example, Sam Carter's password is reset to the random password "marsh!sightseer8."

Listing 21.3 kvaughan Uses setpwd to Reset scarter 's Password
  setpwd scarter  LDAP password for kvaughan: secret Contacting LDAP server... Finding scarter's entry... Modifying scarter's entry... Password reset.  scarter's new password is: marsh!sightseer8. Don't forget to remind scarter to reset his/her password right away! 

Listing 21.4 shows another sample run of the setpwd utility. In this instance, seven people's passwords are reset with a single run of the utility. The -q (quiet) option is used to reduce the amount of output, the -h option is used to specify the LDAP server ( ldap2.example.com ), and the -n option is used to set the password to a specific value ( Wel,come43 ).

Listing 21.4 kvaughan Uses setpwd to Reset Several Users' Passwords
  setpwd q h ldap2.example.com n "Wel,come43" scarter tmorris abergin dmiller gfarmer  kwinters trigden  LDAP password for kvaughan:  secret  scarter's new password is: Wel,come43. tmorris's new password is: Wel,come43. abergin's new password is: Wel,come43. dmiller's new password is: Wel,come43. gfarmer's new password is: Wel,come43. kwinters's new password is: Wel,come43. trigden's new password is: Wel,come43. 

The Source Code

The setpwd utility is written in C, and it uses the Netscape/Mozilla 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 8 Unix operating system, as well as Microsoft Windows 2000, although it should be easy to port to other systems and implementations of the LDAP C API. The source code for setpwd consists of one file that is named, logically enough, setpwd.c . The code is presented here in pieces to aid understanding.

Listing 21.5 shows the beginning of the setpwd.c file. Notice the inclusion of the standard LDAP header file on line 10.

Listing 21.5 The setpwd.c Prelude
 1. /* -*- Mode: C; tab-width: 4; c-basic-offset: 4 -*- */  2. /*  3.  * A simple password resetting program for use by a help desk.  4.  */  5. #include <stdio.h>  6. #include <string.h>  7. #include <stdlib.h>  8. #include <ctype.h>  9. #include <time.h> 10. #include <ldap.h> 11. #ifdef _WINDOWS 12. #include <WINUSER.H> 13. #include <LMCONS.H> 14. #include "getopt.h" 15. #include "getpass.h" 16. #else 17. #include <unistd.h> 18. #include <pwd.h> 19. #endif 20. 21. 22. /* 23.  * Macros: 24.  */ 25. #define DEFAULT_SEARCHBASE  "ou=people,dc=example,dc=com" 26. #define DEFAULT_LDAPHOST    "directory.example.com" 27. #define DEFAULT_LDAPPORT    LDAP_PORT 28. #define DEFAULT_WORDFILE    "/usr/dict/words" 29. 30. #define LONGEST_WORD    100 31. 32. #ifdef sun 33. #define GETPASS getpassphrase   /* accepts a longer string */ 34. #else 35. #define GETPASS getpass         /* the old standby */ 36. #endif 37. 38. #ifdef _WINDOWS 39. #define SRANDOM(seed) srand(seed) 40. #define RANDOM()        rand() 41. #else 42. #define SRANDOM(seed) srandom(seed) 43. #define RANDOM()        random() 44. #endif 45. 46. 47. /* 48.  * Global variables: 49.  */ 50. static int  quiet     = 0;  /* use the -q flag for quiet mode */ 51. char        *wordfile = DEFAULT_WORDFILE; 52. char        *binddn = NULL; 53. char        *bindpwd = NULL; 54. 

A series of LDAP- related macros are defined on lines 25 to 27. The default LDAP host, port, and search base are compiled into this application, which is convenient for the Help Desk personnel. The DEFAULT_WORDFILE macro defined on line 28 has nothing to do with LDAP; it is used by setpwd's random password generation code (described later, in Listing 21.12). The remainder of the code in Listing 21.5 consists of some macros to hide platform differences and a few global variables.

Listing 21.6 includes prototypes for static functions, as well as the usage() function. The usage() function is straightforward. To display the usage, invoke setpwd without any command-line parameters or with the -H parameter.

Listing 21.6 Function Prototypes and usage()
 55. 56. /* 57.  * Function prototypes: 58.  */ 59. static int resetpwd(LDAP *ld, const char *base, 60.         const char *userid, const char *newpwd); 61. static char *userid2dn(LDAP *ld, const char *base, 62.         const char *userid); 63. static void print_ldap_error(LDAP *ld, int lderr, const char *s); 64. static char *randompwd(void); 65. static char *randomword(void); 66. static int LDAP_CALL LDAP_CALLBACK get_rebind_credentials(LDAP *ld, 67.         char **dnp, char **passwdp, int *authmethodp, 68.         int freeit, void *arg); 69. 70. 71. /* 72.  * Functions: 73.  */ 74. static void 75. usage(const char *progname) 76. { 77.     fprintf(stderr, "usage: %s [options] userid...\n", progname); 78.     fprintf(stderr, "where the options are:\n"); 79.     fprintf(stderr, "\t-H            help (display usage)\n"); 80.     fprintf(stderr, "\t-R            do not follow referrals\n"); 81.     fprintf(stderr, "\t-q            operate quietly\n"); 82.     fprintf(stderr, "\t-h host       LDAP server name\n"); 83.     fprintf(stderr, "\t-p port       LDAP server port\n"); 84.     fprintf(stderr, "\t-b basedn     base DN for searches\n"); 85.     fprintf(stderr, "\t-u bindid     user id to bind as\n"); 86.     fprintf(stderr, "\t-D binddn     DN to bind as\n"); 87.     fprintf(stderr, "\t-w passwd     bind password\n"); 88.     fprintf(stderr, "\t-n newpasswd  password to set\n"); 89.     fprintf(stderr, "\t-d file       words file, e.g. " 90.                 DEFAULT_WORDFILE "\n"); 91.     exit(2); 92. } 93. 

Listing 21.7 shows the first part of the setpwd main() function. The code on lines 108 to 121 retrieves the current user name from the operating system to use as the default user ID for authentication. The remaining code in Listing 21.7 uses the getopt() library function to process the command-line arguments (on Microsoft Windows, getopt() is not a standard library function, so in that case an open -source getopt() implementation is used).

Listing 21.7 The setpwd main() Function (Part 1 of 2)
 94. int  95. main(int argc, char **argv)  96. {  97.     LDAP            *ld;  98.     int             i, c, errflg, rc;  99.     int             ldapv3 = LDAP_VERSION3; 100.     void            *follow_referrals = LDAP_OPT_ON; 101.     const char      *host = DEFAULT_LDAPHOST; 102.     int             port = DEFAULT_LDAPPORT; 103.     const char      *base = DEFAULT_SEARCHBASE; 104.     const char      *binduserid = NULL; 105.     const char      *newpwd = NULL; 106.     const char      *bindidstring = NULL; 107. 108. #ifdef _WINDOWS 109.     char            useridbuf[ UNLEN + 1 ]; 110.     DWORD           useridlen = sizeof(useridbuf); 111. 112.     if (GetUserName(useridbuf, &useridlen)) { 113.         binduserid = useridbuf; 114.     } 115. #else 116.     struct passwd   *pwdinfo; 117. 118.     if (NULL != (pwdinfo = getpwuid(getuid()))) { 119.         binduserid = strdup(pwdinfo->pw_name); 120.     } 121. #endif 122. 123.     /* 124.      * Process command line arguments. 125.      */ 126.     errflg = 0; 127.     while ((c = getopt(argc, argv, "HRqh:p:b:u:D:w:n:d:")) != EOF) { 128.         switch (c) { 129.         case 'H':   /* help */ 130.             errflg = 1; 131.             break; 132.         case 'R':   /* do not follow referrals */ 133.             follow_referrals = LDAP_OPT_OFF; 134.             break; 135.         case 'q':   /* quiet */ 136.             quiet = 1; 137.             break; 138.         case 'h':   /* host */ 139.             host = optarg; 140.             break; 141.         case 'p':   /* port */ 142.             port = atoi(optarg); 143.             break; 144.         case 'b':   /* port */ 145.             base = optarg; 146.             break; 147.         case 'u':   /* bind user id */ 148.             binduserid = optarg; 149.             break; 150.         case 'D':   /* bind DN */ 151.             binddn = optarg; 152.             break; 153.         case 'w':   /* bind password */ 154.             bindpwd = optarg; 155.             break; 156.         case 'n':   /* new password */ 157.             newpwd = optarg; 158.             break; 159.         case 'd':   /* words file */ 160.             wordfile = optarg; 161.             break; 162.         default: 163.             errflg = 1; 164.         } 165.     } 166.     if (errflg  optind >= argc  167.                 (binduserid == NULL && binddn == NULL)) { 168.         usage(argv[ 0 ]); 169.     } 170. 171.     if (binddn != NULL) { 172.         bindidstring = binddn; 173.     } else { 174.         bindidstring = binduserid; 175.     } 

Tip

Although the setpwd program includes default values for all its LDAP-related settings, it also supports an extensive set of command-line options. These options ensure that the program is convenient for Help Desk staff to use, but also flexible enough to be used in other situations. Consider similar factors when writing your own LDAP applications.


Listing 21.8 shows the remainder of main() . The code on lines 177 to 195 prompts the user for a password if none was provided on the command line. Finally, the first LDAP API call is made on line 202. The ldap_init() call creates 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.

Listing 21.8 The setpwd main() Function (Part 2 of 2)
 176. 177.     /* 178.      * Prompt for password if not provided on command line. 179.      */ 180.     if (NULL == bindpwd  ' 
 176. 177. /* 178. * Prompt for password if not provided on command line. 179. */ 180. if (NULL == bindpwd  '\0' == *bindpwd) { 181. const char *p = "LDAP password for"; 182. char *prompt; 183. 184. prompt = malloc(strlen(p) + strlen(bindidstring) + 3); 185. if (prompt == NULL) { 186. perror("malloc"); 187. return 1; 188. } 189. sprintf(prompt, "%s %s: ", p, bindidstring); 190. bindpwd = GETPASS(prompt); 191. free(prompt); 192. if (NULL == bindpwd  '\0' == *bindpwd) { 193. return 0; 194. } 195. } 196. 197. /* 198. * Connect and bind to the directory server, looking up the bind DN 199. * if a bind user ID was provided. 200. */ 201. if (!quiet) puts("Contacting LDAP server..."); 202. if ((ld = ldap_init(host, port)) == NULL) { 203. perror(host); 204. return 1; 205. } 206. 207. if (ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, 208. &ldapv3) != 0) { 209. print_ldap_error(ld, ldap_get_lderrno(ld, NULL, NULL), 210. "set protocol version"); 211. ldap_unbind(ld); 212. return 1; 213. } 214. 215. if (ldap_set_option(ld, LDAP_OPT_REFERRALS, 216. follow_referrals) != 0) { 217. print_ldap_error(ld, ldap_get_lderrno(ld, NULL, NULL), 218. "set referral following option"); 219. ldap_unbind(ld); 220. return 1; 221. } 222. 223. if (follow_referrals == LDAP_OPT_ON) { 224. if (ldap_set_option(ld, LDAP_OPT_REBIND_FN, 225. (void *)get_rebind_credentials) != 0) { 226. print_ldap_error(ld, ldap_get_lderrno(ld, NULL, NULL), 227. "set referral rebind function"); 228. ldap_unbind(ld); 229. return 1; 230. } 231. } 232. 233. if (binduserid != NULL) { 234. binddn = userid2dn(ld, base, binduserid); 235. if (binddn == NULL) { 236. return 1; 237. } 238. } 239. 240. rc = ldap_simple_bind_s(ld, binddn, bindpwd); 241. if (rc != LDAP_SUCCESS) { 242. print_ldap_error(ld, rc, bindidstring); 243. ldap_unbind(ld); 244. return 1; 245. } 246. 247. 248. /* 249. * Process user ID arguments, setting random passwords 250. * for each one. 251. */ 252. rc = 0; 253. SRANDOM(time(NULL)); 254. for (i = optind; i < argc; ++i) { 255. if (!quiet && i > optind) { 256. putchar ('\n'); 257. } 258. rc = resetpwd(ld, base, argv[ i ], newpwd); 259. } 260. 261. /* 262. * Close LDAP server connection and exit. 263. */ 264. ldap_unbind(ld); 265. return rc; 266. } 267. 
' == *bindpwd) { 181. const char *p = "LDAP password for"; 182. char *prompt; 183. 184. prompt = malloc(strlen(p) + strlen(bindidstring) + 3); 185. if (prompt == NULL) { 186. perror("malloc"); 187. return 1; 188. } 189. sprintf(prompt, "%s %s: ", p, bindidstring); 190. bindpwd = GETPASS(prompt); 191. free(prompt); 192. if (NULL == bindpwd '
 176. 177. /* 178. * Prompt for password if not provided on command line. 179. */ 180. if (NULL == bindpwd  '\0' == *bindpwd) { 181. const char *p = "LDAP password for"; 182. char *prompt; 183. 184. prompt = malloc(strlen(p) + strlen(bindidstring) + 3); 185. if (prompt == NULL) { 186. perror("malloc"); 187. return 1; 188. } 189. sprintf(prompt, "%s %s: ", p, bindidstring); 190. bindpwd = GETPASS(prompt); 191. free(prompt); 192. if (NULL == bindpwd  '\0' == *bindpwd) { 193. return 0; 194. } 195. } 196. 197. /* 198. * Connect and bind to the directory server, looking up the bind DN 199. * if a bind user ID was provided. 200. */ 201. if (!quiet) puts("Contacting LDAP server..."); 202. if ((ld = ldap_init(host, port)) == NULL) { 203. perror(host); 204. return 1; 205. } 206. 207. if (ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, 208. &ldapv3) != 0) { 209. print_ldap_error(ld, ldap_get_lderrno(ld, NULL, NULL), 210. "set protocol version"); 211. ldap_unbind(ld); 212. return 1; 213. } 214. 215. if (ldap_set_option(ld, LDAP_OPT_REFERRALS, 216. follow_referrals) != 0) { 217. print_ldap_error(ld, ldap_get_lderrno(ld, NULL, NULL), 218. "set referral following option"); 219. ldap_unbind(ld); 220. return 1; 221. } 222. 223. if (follow_referrals == LDAP_OPT_ON) { 224. if (ldap_set_option(ld, LDAP_OPT_REBIND_FN, 225. (void *)get_rebind_credentials) != 0) { 226. print_ldap_error(ld, ldap_get_lderrno(ld, NULL, NULL), 227. "set referral rebind function"); 228. ldap_unbind(ld); 229. return 1; 230. } 231. } 232. 233. if (binduserid != NULL) { 234. binddn = userid2dn(ld, base, binduserid); 235. if (binddn == NULL) { 236. return 1; 237. } 238. } 239. 240. rc = ldap_simple_bind_s(ld, binddn, bindpwd); 241. if (rc != LDAP_SUCCESS) { 242. print_ldap_error(ld, rc, bindidstring); 243. ldap_unbind(ld); 244. return 1; 245. } 246. 247. 248. /* 249. * Process user ID arguments, setting random passwords 250. * for each one. 251. */ 252. rc = 0; 253. SRANDOM(time(NULL)); 254. for (i = optind; i < argc; ++i) { 255. if (!quiet && i > optind) { 256. putchar ('\n'); 257. } 258. rc = resetpwd(ld, base, argv[ i ], newpwd); 259. } 260. 261. /* 262. * Close LDAP server connection and exit. 263. */ 264. ldap_unbind(ld); 265. return rc; 266. } 267. 
' == *bindpwd) { 193. return 0; 194. } 195. } 196. 197. /* 198. * Connect and bind to the directory server, looking up the bind DN 199. * if a bind user ID was provided. 200. */ 201. if (!quiet) puts("Contacting LDAP server..."); 202. if ((ld = ldap_init(host, port)) == NULL) { 203. perror(host); 204. return 1; 205. } 206. 207. if (ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, 208. &ldapv3) != 0) { 209. print_ldap_error(ld, ldap_get_lderrno(ld, NULL, NULL), 210. "set protocol version"); 211. ldap_unbind(ld); 212. return 1; 213. } 214. 215. if (ldap_set_option(ld, LDAP_OPT_REFERRALS, 216. follow_referrals) != 0) { 217. print_ldap_error(ld, ldap_get_lderrno(ld, NULL, NULL), 218. "set referral following option"); 219. ldap_unbind(ld); 220. return 1; 221. } 222. 223. if (follow_referrals == LDAP_OPT_ON) { 224. if (ldap_set_option(ld, LDAP_OPT_REBIND_FN, 225. (void *)get_rebind_credentials) != 0) { 226. print_ldap_error(ld, ldap_get_lderrno(ld, NULL, NULL), 227. "set referral rebind function"); 228. ldap_unbind(ld); 229. return 1; 230. } 231. } 232. 233. if (binduserid != NULL) { 234. binddn = userid2dn(ld, base, binduserid); 235. if (binddn == NULL) { 236. return 1; 237. } 238. } 239. 240. rc = ldap_simple_bind_s(ld, binddn, bindpwd); 241. if (rc != LDAP_SUCCESS) { 242. print_ldap_error(ld, rc, bindidstring); 243. ldap_unbind(ld); 244. return 1; 245. } 246. 247. 248. /* 249. * Process user ID arguments, setting random passwords 250. * for each one. 251. */ 252. rc = 0; 253. SRANDOM(time(NULL)); 254. for (i = optind; i < argc; ++i) { 255. if (!quiet && i > optind) { 256. putchar('\n'); 257. } 258. rc = resetpwd(ld, base, argv[ i ], newpwd); 259. } 260. 261. /* 262. * Close LDAP server connection and exit. 263. */ 264. ldap_unbind(ld); 265. return rc; 266. } 267.

The code on lines 207 to 231 calls the ldap_set_option() LDAP API function three times to set various options that affect the LDAP session:

  1. The LDAP_OPT_PROTOCOL_VERSION value is set to ensure that LDAPv3 is used.

  2. The LDAP_OPT_REFERRALS option is set to the value in the follow_referrals local variable, which is either LDAP_OPT_ON (the default) or LDAP_OPT_OFF (if the -R command-line option is used). This LDAP session option controls whether LDAP referrals and references are automatically followed by the underlying LDAP API implementation.

  3. If referral following is enabled, the LDAP_OPT_REBIND_FN option is set to the address of a function named get_rebind_credentials() . The LDAP API implementation calls this rebind function when it needs to obtain authentication credentials while following a referral to another LDAP server. The code for the get_rebind_ credentials() function is shown later (in Listing 21.11).

If a bind DN was not provided, the code on lines 233 to 238 uses the userid2dn() function (described later, in Listing 21.10) to search the LDAP server for a person entry matching the user ID. The ldap_simple_bind_s() call on line 240 connects to the directory server, 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 248 to 259 moves through the arguments provided on the command line and passes each one to the resetpwd() function, which will be described next , in Listing 21.9. The ldap_unbind() call included in the cleanup code on lines 261 to 265 does two things: It closes the LDAP connection, and it disposes of the memory and any other resources consumed by the LDAP session handle.

Tip

The code in Listing 21.8 demonstrates proper handling of zero-length passwords (requiring that the user enter a password that is not empty, which ensures that a zero-length password is never sent to the LDAP server) and proper installation of a rebind function. Make sure that the LDAP application code you write addresses these issues correctly.


Listing 21.9 shows the code for the resetpwd() function, which is responsible for resetting one user's password. 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 283 to 289 calls the userid2dn() utility function (described next, in Listing 21.10) to find and return the user's DN, given her user ID. The code on lines 291 to 295 optionally calls another utility function, named randompwd() (described later, in Listing 21.12), which generates a new password for the user. The randompwd() function is not used if a password was provided on the command line through use of the -n option.

Listing 21.9 The setpwd resetpwd() Function
 268. 269. /* 270.  * resetpwd(): set an entry's password. 271.  * If newpwd is NULL, a random password is used. 272.  * 273.  * Returns 0 on success and 1 on failure. 274.  */ 275. static int 276. resetpwd(LDAP *ld, const char *base, 277.         const char *userid, const char *newpwd) 278. { 279.     char    *dn, *pwd, *vals[2]; 280.     int     rc; 281.     LDAPMod mod, *mods[2]; 282. 283.     /* 284.      * Find the entry. 285.      */ 286.     if (!quiet) printf("Finding %s's entry...\n", userid); 287.     if ((dn = userid2dn(ld, base, userid)) == NULL) { 288.         return 1; 289.     } 290. 291.     if (newpwd != NULL) { 292.         pwd = (char *)newpwd; 293.     } else if ((pwd = randompwd()) == NULL) { 294.         return 1; 295.     } 296. 297.     /* 298.      * Replace the userPassword attribute. 299.      */ 300.     if (!quiet) printf("Modifying %s's entry...\n", userid); 301.     vals[0] = pwd; 302.     vals[1] = NULL; 303.     mod.mod_values = vals; 304.     mod.mod_type = "userPassword"; 305.     mod.mod_op = LDAP_MOD_REPLACE; 306.     mods[0] = &mod; 307.     mods[1] = NULL; 308. 309.     if ((rc = ldap_modify_s(ld, dn, mods)) == LDAP_SUCCESS) { 310.         rc = 0; 311.         if (quiet) { 312.             printf("%s's new password is: %s.\n", userid, pwd); 313.         } else { 314.             printf("Password reset.  %s's new password is: %s.\n", 315.                         userid, pwd); 316.             printf("Don't forget to remind %s to reset " 317.                 "his/her password right away!\n", userid); 318.         } 319.     } else { 320.         print_ldap_error(ld, rc, "modify"); 321.         rc = 1; 322.     } 323. 324.     if (newpwd == NULL) { 325.         free(pwd); 326.     } 327.     ldap_memfree(dn); 328.     return rc; 329. } 330. 

The remainder of the resetpwd() code (lines 297 “329) replaces the userPassword attribute in the user's entry with the newly generated password. After setting up a single-element array of modifications, the ldap_modify_s() LDAP SDK function (line 309) is called to direct the server to make the password change.

Listing 21.10 shows the source code for the userid2dn() utility function, which is called from main() and from setpwd() . The userid2dn() function uses an LDAP subtree search to find a user's directory entry, given her user ID.

Listing 21.10 The setpwd userid2dn() Function
 331. 332. /* 333.  * userid2dn(): Given an entry's user ID, return its DN. 334.  * 335.  * The DN returned should be freed by being passed to 336.  * ldap_memfree(). If no entry is found, NULL is returned. 337.  */ 338. static char * 339. userid2dn(LDAP *ld, const char *base, const char *userid) 340. { 341.     int         rc; 342.     const char  *filterpattern = "(&(objectClass=person)(uid=%s))"; 343.     char        *filter, *dn = NULL; 344.     char        *attrs[] = { LDAP_NO_ATTRS, NULL }; 345.     LDAPMessage *e, *res = NULL; 346. 347.     if ((filter = (char *)malloc(strlen(userid) 348.                 + strlen(filterpattern) + 1)) == NULL) { 349.         perror("malloc"); 350.         return NULL; 351.     } 352. 353.     sprintf(filter, filterpattern, userid); 354.     rc = ldap_search_s(ld, base, LDAP_SCOPE_SUBTREE, 355.                 filter, attrs, 0, &res); 356.     free(filter); 357. 358.     if (rc != LDAP_SUCCESS) { 359.         print_ldap_error(ld, rc, "search"); 360.     } else if ((e = ldap_first_entry(ld, res)) == NULL) { 361.         fprintf(stderr, "No match for user id %s.\n", userid); 362.     } else if (ldap_next_entry(ld, e) != NULL) { 363.         fprintf(stderr, "%d matches for %s.\n", 364.                     ldap_count_entries(ld, res), userid); 365.     } else { 366.         dn = ldap_get_dn(ld, e);  /* found just one! */ 367.     } 368. 369.     ldap_msgfree(res); 370.     return dn; 371. } 372. 

The code on lines 347 to 353 creates an LDAP search filter that looks like (&(objectClass= person)(uid=ID)) , where ID is the user ID provided on the setpwd command line. This search filter will match all entries that have the person object class and have the correct user ID. The actual search is done by the ldap_search_s() LDAP SDK call that appears on lines 354 and 355. 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 named LDAP_NO_ATTRS ( attrs is initialized on line 344).

The special LDAP_NO_ATTRS macro, defined by the LDAP API, is used to indicate that no attribute types should be returned. Using LDAP_NO_ATTRS is desirable because it is wasteful to ask for attributes we do not need, and in this instance we are interested only in obtaining the DN of the entry. If we were to pass NULL for the attrs parameter, every attribute in the entry would be returned, which would be less efficient. The code on lines 358 to 370 checks if exactly one entry was found; if so, it returns the DN of the entry. If more than one entry was found, or if no entries were found, an error is reported .

Listing 21.11 shows the code for the get_rebind_credentials() rebind function and the print_ldap_error() utility function.

Listing 21.11 The setpwd get_rebind_credentials and print_ldap_error() Functions
 373. 374. /* 375.  * Callback function for LDAP bind credentials; used when 376.  * rebinding during referral and reference following. 377.  */ 378. static int LDAP_CALL LDAP_CALLBACK 379. get_rebind_credentials(LDAP *ld, char **dnp, char **passwdp, 380.         int *authmethodp, int freeit, void *arg) 381. { 382.     if (!freeit) { 383.         *dnp = binddn; 384.         *passwdp = bindpwd; 385.         *authmethodp = LDAP_AUTH_SIMPLE; 386.     } 387. 388.     return LDAP_SUCCESS; 389. } 390. 391. 392. /* 393.  * Display an LDAP error message. 394.  */ 395. void 396. print_ldap_error(LDAP *ld, int lderr, const char *s) 397. { 398.     fprintf(stderr, "%s: %s\n", s, ldap_err2string(lderr)); 399. } 400. 

Recall that the get_rebind_credentials() function is called by the LDAP C API implementation when it needs to obtain bind credentials while following a referral. The code simply sets the dnp , passwdp , and authmethodp return parameters to appropriate values ( binddn and bindpwd are global variables that are set in the setpwd main() function). This rebind function is actually called twice by the LDAP API implementation each time bind credentials are needed ”once with the freeit parameter set to zero (to request the credentials), and a second time with freeit set to a nonzero value (to give the application an opportunity to dispose of any memory allocated during the first call).

The print_ldap_error() function is a simple utility function that is called from several places in setpwd.c . It reports LDAP-related errors to the user in a consistent way.

Listing 21.12 shows the randompwd() function, which the resetpwd() function that we already examined calls if a password must be generated. 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 algorithm could easily be altered to match a specific organization's security policy or the preferences of Help Desk technical staff.

Listing 21.12 The setpwd randompwd() Function
 401. 402. /* 403.  * randompwd(): generate a reasonably good password using some 404.  * random words from a file.  The password should be easy to 405.  * remember and able to be passed on verbally to a user. 406.  * 407.  * The algorithm we use is this: 408.  *  a) select two random words from the words file (w1 and w2). 409.  *  b) pick a random punctuation character (c). 410.  *  c) pick a random digit 0-9 (d). 411.  *  d) construct a candidate password as:  w1 c w2 d. 412.  *  e) change the case of one of the letters at random. 413.  */ 414. static char * 415. randompwd(void) 416. { 417.     static char *punctuation = "!@#$%&*-+=,.?"; 418.     char    *pwd, *w1, *w2, *p; 419. 420.     if ((w1 = randomword()) == NULL 421.                  (w2 = randomword()) == NULL) { 422.         if (w1 != NULL) { 423.             free(w1); 424.         } 425.         return NULL; 426.     } 427. 428.     if ((pwd = (char *)malloc(LONGEST_WORD * 2 + 3)) != NULL) { 429.         sprintf(pwd, "%s%c%s%d", w1, 430.                 punctuation[ RANDOM() % strlen(punctuation) ], w2, 431.                 RANDOM() % 10); 432.         p = pwd + (RANDOM() % strlen(pwd)); 433.         if (isalpha(*p)) { 434.             if (isupper(*p)) { 435.                 *p = tolower(*p); 436.             } else { 437.                 *p = toupper(*p); 438.             } 439.         } 440.     } 441. 442.     free(w1); 443.     free(w2); 444.     return pwd; 445. } 446. 

The randompwd() function uses a utility function named randomword() to pick the two words that it uses to construct the password value. Listing 21.13 shows the first part of the randomword() function, which consists mainly of initialization tasks. The randomword() function reads the random words from a text file that contains one word per line. By default, the standard Unix /usr/dict/words file is used, but any file is acceptable.

Listing 21.13 The setpwd randomword() Function (Part 1 of 2)
 447. 448. /* 449.  * randomword(): return a random, lowercase word. 450.  * 451.  * Returns a malloc'd string if successful. 452.  * Returns NULL on error, e.g., if unable to access 453.  * the words file. 454.  * 455.  * This function assumes that all words in the file are 456.  * less than LONGEST_WORD characters long. 457.  */ 458. static char * 459. randomword(void) 460. { 461.     static FILE *fp = NULL; 462.     static long filesize = 0L; 463.     char    *word, *p; 464.     int     len; 465. 466.     if (fp == NULL) { 467.         /* 468.          * initialize: open the words file and 469.          * determine its size 470.          */ 471.         if ((fp = fopen(wordfile, "r")) == NULL 472.                      fseek(fp, 0L, SEEK_END) == -1 473.                      (filesize = ftell(fp)) == -1) { 474.             perror(wordfile); 475.             if (fp != NULL) { 476.                 fclose(fp); 477.             } 478.             return NULL; 479.         } 480. 481.         if (filesize < LONGEST_WORD) { 482.             fprintf(stderr, "%s: the words file must be at" 483.                     " least %d bytes long.\n", 484.                     wordfile, LONGEST_WORD); 485.             if (fp != NULL) { 486.                 fclose(fp); 487.             } 488.             return NULL; 489.         } 490. 491.         filesize -= LONGEST_WORD; 492.     } 493. 

Listing 21.14 shows the remainder of the randomword() function, which seeks to a random location within the words file and returns one word.

Listing 21.14 The setpwd randomword() Function (Part 2 of 2)
 494.     /* 495.      * Seek to a random location in the file. 496.      */ 497.     if (fseek(fp, RANDOM() % filesize, SEEK_SET) == -1) { 498.         perror(wordfile); 499.         return NULL; 500.     } 501. 502.     if ((word = (char *)malloc(LONGEST_WORD)) == NULL) { 503.         perror("malloc"); 504.         return NULL; 505.     } 506. 507.     /* 508.      * Read the tail (remainder) of one word and then 509.      * the entire next word, which is what we will return. 510.      */ 511.     fgets(word, LONGEST_WORD - 1, fp); 512.     if (fgets(word, LONGEST_WORD - 1, fp) == NULL) { 513.         perror(wordfile); 514.         free(word); 515.         return NULL; 516.     } 517. 518.     len = strlen(word) - 1; 519.     if (word[ len ] == '\n') { 520.         word[ len ] = ' 
 494. /* 495. * Seek to a random location in the file. 496. */ 497. if (fseek(fp, RANDOM() % filesize, SEEK_SET) == -1) { 498. perror(wordfile); 499. return NULL; 500. } 501. 502. if ((word = (char *)malloc(LONGEST_WORD)) == NULL) { 503. perror("malloc"); 504. return NULL; 505. } 506. 507. /* 508. * Read the tail (remainder) of one word and then 509. * the entire next word, which is what we will return. 510. */ 511. fgets(word, LONGEST_WORD - 1, fp); 512. if (fgets(word, LONGEST_WORD - 1, fp) == NULL) { 513. perror(wordfile); 514. free(word); 515. return NULL; 516. } 517. 518. len = strlen(word) - 1; 519. if (word[ len ] == '\n') { 520. word[ len ] = '\0'; 521. } 522. 523. for (p = word; *p != '\0'; ++p) { 524. if (isupper(*p)) { 525. *p = tolower(*p); 526. } 527. } 528. 529. return(word); 530. } 
'; 521. } 522. 523. for (p = word; *p != '
 494. /* 495. * Seek to a random location in the file. 496. */ 497. if (fseek(fp, RANDOM() % filesize, SEEK_SET) == -1) { 498. perror(wordfile); 499. return NULL; 500. } 501. 502. if ((word = (char *)malloc(LONGEST_WORD)) == NULL) { 503. perror("malloc"); 504. return NULL; 505. } 506. 507. /* 508. * Read the tail (remainder) of one word and then 509. * the entire next word, which is what we will return. 510. */ 511. fgets(word, LONGEST_WORD - 1, fp); 512. if (fgets(word, LONGEST_WORD - 1, fp) == NULL) { 513. perror(wordfile); 514. free(word); 515. return NULL; 516. } 517. 518. len = strlen(word) - 1; 519. if (word[ len ] == '\n') { 520. word[ len ] = '\0'; 521. } 522. 523. for (p = word; *p != '\0'; ++p) { 524. if (isupper(*p)) { 525. *p = tolower(*p); 526. } 527. } 528. 529. return(word); 530. } 
'; ++p) { 524. if (isupper(*p)) { 525. *p = tolower(*p); 526. } 527. } 528. 529. return(word); 530. }

Ideas for Improvement

The setpwd application could be improved in many ways. Here are a few ideas:

  • Increase security by using a different form of authentication (such as Kerberos or public key certificates) and by protecting the LDAP traffic using SSL or TLS.

  • Increase security by taking advantage of your directory service's password policy features (if available) to force users to choose a new password the next time they bind to the directory after their password is reset using setpwd.

  • Expand the application's role by adding features to disable a user's account and change other information in user entries, such as e-mail addresses.

  • Create reusable, shared libraries that contain some of the LDAP-related code included in setpwd. Good candidates include the userid2dn() and print_ldap_error() functions.

   


Understanding and Deploying LDAP Directory Services
Understanding and Deploying LDAP Directory Services (2nd Edition)
ISBN: 0672323168
EAN: 2147483647
Year: 2002
Pages: 242

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net