1 /* $NetBSD: ppm.c,v 1.2 2021/08/14 16:14:53 christos Exp $ */
2
3 /*
4 * ppm.c for OpenLDAP
5 *
6 * See LICENSE, README and INSTALL files
7 */
8
9
10 /*
11 password policy module is called with:
12 int check_password (char *pPasswd, char **ppErrStr, Entry *e, void *pArg)
13
14 *pPasswd: new password
15 **ppErrStr: pointer to the string containing the error message
16 *e: pointer to the current user entry
17 *pArg: pointer to a struct berval holding the value of pwdCheckModuleArg attr
18 */
19
20 #include <stdlib.h> // for type conversion, such as atoi...
21 #include <regex.h> // for matching allowedParameters / conf file
22 #include <string.h>
23 #include <ctype.h>
24 #include <portable.h>
25 #include <slap.h>
26 #include <stdarg.h> // for variable nb of arguments functions
27 #include "ppm.h"
28
29 #ifdef CRACKLIB
30 #include "crack.h" // use cracklib to check password
31 #endif
32
33 void
ppm_log(int priority,const char * format,...)34 ppm_log(int priority, const char *format, ...)
35 {
36 // if DEBUG flag is set
37 // logs into syslog (for OpenLDAP) or to stdout (for tests)
38 #if defined(DEBUG)
39 if(ppm_test != 1)
40 {
41 va_list syslog_args;
42 va_start(syslog_args, format);
43 vsyslog(priority, format, syslog_args);
44 va_end(syslog_args);
45 }
46 else
47 {
48 va_list stdout_args;
49 va_start(stdout_args, format);
50 vprintf(format, stdout_args);
51 printf("\n");
52 fflush(stdout);
53 va_end(stdout_args);
54 }
55 #endif
56 }
57
58 void
strcpy_safe(char * dest,char * src,int length_dest)59 strcpy_safe(char *dest, char *src, int length_dest)
60 {
61 if(src == NULL)
62 {
63 dest[0] = '\0';
64 }
65 else
66 {
67 int length_src = strlen(src);
68 int n = (length_dest < length_src) ? length_dest : length_src;
69 // Copy the string — don’t copy too many bytes.
70 strncpy(dest, src, n);
71 // Ensure null-termination.
72 dest[n] = '\0';
73 }
74 }
75
76 genValue*
getValue(conf * fileConf,int numParam,char * param)77 getValue(conf *fileConf, int numParam, char* param)
78 {
79 int i = 0;
80
81 // First scan parameters
82 for (i = 0; i < numParam; i++) {
83 if ((strlen(param) == strlen(fileConf[i].param))
84 && (strncmp(param, fileConf[i].param, strlen(fileConf[i].param))
85 == 0)) {
86 return &(fileConf[i].value);
87 }
88 }
89 return NULL;
90 }
91
maxConsPerClass(char * password,char * charClass)92 int maxConsPerClass(char *password, char *charClass)
93 {
94 // find maximum number of consecutive class characters in the password
95
96 int bestMax = 0;
97 int max = 0;
98 int i;
99
100 for(i=0 ; i<strlen(password) ; i++)
101 {
102 if(strchr(charClass,password[i]) != NULL)
103 {
104 // current character is in class
105 max++;
106 // is the new max a better candidate to maxConsecutivePerClass?
107 if(max > bestMax)
108 {
109 // found a better maxConsecutivePerClass
110 bestMax = max;
111 }
112 }
113 else
114 {
115 // current character is not in class
116 // reinitialize max
117 max=0;
118 }
119 }
120 return bestMax;
121 }
122
123 void
storeEntry(char * param,char * value,valueType valType,char * min,char * minForPoint,conf * fileConf,int * numParam)124 storeEntry(char *param, char *value, valueType valType,
125 char *min, char *minForPoint, conf * fileConf, int *numParam)
126 {
127 int i = 0;
128 int iMin;
129 int iMinForPoint;
130 if (min == NULL || strcmp(min,"") == 0)
131 iMin = 0;
132 else
133 iMin = atoi(min);
134
135 if (minForPoint == NULL || strcmp(minForPoint,"") == 0)
136 iMinForPoint = 0;
137 else
138 iMinForPoint = atoi(minForPoint);
139
140 // First scan parameters
141 for (i = 0; i < *numParam; i++) {
142 if ((strlen(param) == strlen(fileConf[i].param))
143 && (strncmp(param, fileConf[i].param, strlen(fileConf[i].param))
144 == 0)) {
145 // entry found, replace values
146 if(valType == typeInt)
147 fileConf[i].value.iVal = atoi(value);
148 else
149 strcpy_safe(fileConf[i].value.sVal, value, VALUE_MAX_LEN);
150 fileConf[i].min = iMin;
151 fileConf[i].minForPoint = iMinForPoint;
152 if(valType == typeInt)
153 ppm_log(LOG_NOTICE, "ppm: Accepted replaced value: %d",
154 fileConf[i].value.iVal);
155 else
156 ppm_log(LOG_NOTICE, "ppm: Accepted replaced value: %s",
157 fileConf[i].value.sVal);
158 return;
159 }
160 }
161 // entry not found, add values
162 strcpy_safe(fileConf[*numParam].param, param, PARAM_MAX_LEN);
163 fileConf[*numParam].iType = valType;
164 if(valType == typeInt)
165 fileConf[i].value.iVal = atoi(value);
166 else
167 strcpy_safe(fileConf[i].value.sVal, value, VALUE_MAX_LEN);
168 fileConf[*numParam].min = iMin;
169 fileConf[*numParam].minForPoint = iMinForPoint;
170 ++(*numParam);
171 if(valType == typeInt)
172 ppm_log(LOG_NOTICE, "ppm: Accepted new value: %d",
173 fileConf[*numParam].value.iVal);
174 else
175 ppm_log(LOG_NOTICE, "ppm: Accepted new value: %s",
176 fileConf[*numParam].value.sVal);
177 }
178
179 int
typeParam(char * param)180 typeParam(char* param)
181 {
182 int i;
183 int n = sizeof(allowedParameters)/sizeof(params);
184
185 regex_t regex;
186 int reti;
187
188 for(i = 0 ; i < n ; i++ )
189 {
190 // Compile regular expression
191 reti = regcomp(®ex, allowedParameters[i].param, 0);
192 if (reti) {
193 ppm_log(LOG_ERR, "ppm: Cannot compile regex: %s",
194 allowedParameters[i].param);
195 return n;
196 }
197
198 // Execute regular expression
199 reti = regexec(®ex, param, 0, NULL, 0);
200 if (!reti)
201 {
202 regfree(®ex);
203 return i;
204 }
205 regfree(®ex);
206 }
207 return n;
208 }
209
210 #ifndef PPM_READ_FILE
211
212 /*
213 * read configuration into pwdCheckModuleArg attribute
214 * */
215 static void
read_config_attr(conf * fileConf,int * numParam,char * ppm_config_attr)216 read_config_attr(conf * fileConf, int *numParam, char *ppm_config_attr)
217 {
218 int nParam = 0; // position of found parameter in allowedParameters
219 int sAllowedParameters = sizeof(allowedParameters)/sizeof(params);
220 char arg[260*256];
221 char *token;
222 char *saveptr1;
223 char *saveptr2;
224
225 strcpy_safe(arg, ppm_config_attr, 260*256);
226 ppm_log(LOG_NOTICE, "ppm: Parsing pwdCheckModuleArg attribute");
227 token = strtok_r(arg, "\n", &saveptr1);
228
229 while (token != NULL) {
230 ppm_log(LOG_NOTICE, "ppm: get line: %s",token);
231 char *start = token;
232 char *word, *value;
233 char *min, *minForPoint;;
234
235 while (isspace(*start) && isascii(*start))
236 start++;
237
238 if (!isascii(*start))
239 {
240 token = strtok_r(NULL, "\n", &saveptr1);
241 continue;
242 }
243 if (start[0] == '#')
244 {
245 token = strtok_r(NULL, "\n", &saveptr1);
246 continue;
247 }
248
249 if ((word = strtok_r(start, " \t", &saveptr2))) {
250 if ((value = strtok_r(NULL, " \t", &saveptr2)) == NULL)
251 {
252 saveptr2 = NULL;
253 ppm_log(LOG_NOTICE, "ppm: No value, goto next parameter");
254 token = strtok_r(NULL, "\n", &saveptr1);
255 continue;
256 }
257 if (strchr(value, '\n') != NULL)
258 strchr(value, '\n')[0] = '\0';
259 min = strtok_r(NULL, " \t", &saveptr2);
260 if (min != NULL)
261 if (strchr(min, '\n') != NULL)
262 strchr(min, '\n')[0] = '\0';
263 minForPoint = strtok_r(NULL, " \t", &saveptr2);
264 if (minForPoint != NULL)
265 if (strchr(minForPoint, '\n') != NULL)
266 strchr(minForPoint, '\n')[0] = '\0';
267
268
269 nParam = typeParam(word); // search for param in allowedParameters
270 if (nParam != sAllowedParameters) // param has been found
271 {
272 ppm_log(LOG_NOTICE,
273 "ppm: Param = %s, value = %s, min = %s, minForPoint= %s",
274 word, value, min, minForPoint);
275
276 storeEntry(word, value, allowedParameters[nParam].iType,
277 min, minForPoint, fileConf, numParam);
278 }
279 else
280 {
281 ppm_log(LOG_NOTICE,
282 "ppm: Parameter '%s' rejected", word);
283 }
284
285 }
286 token = strtok_r(NULL, "\n", &saveptr1);
287 }
288
289 }
290
291 #endif
292
293 #ifdef PPM_READ_FILE
294
295 /*
296 * read configuration file (DEPRECATED)
297 * */
298 static void
read_config_file(conf * fileConf,int * numParam,char * ppm_config_file)299 read_config_file(conf * fileConf, int *numParam, char *ppm_config_file)
300 {
301 FILE *config;
302 char line[260] = "";
303 int nParam = 0; // position of found parameter in allowedParameters
304 int sAllowedParameters = sizeof(allowedParameters)/sizeof(params);
305
306 ppm_log(LOG_NOTICE, "ppm: Opening file %s", ppm_config_file);
307 if ((config = fopen(ppm_config_file, "r")) == NULL) {
308 ppm_log(LOG_ERR, "ppm: Opening file %s failed", ppm_config_file);
309 exit(EXIT_FAILURE);
310 }
311
312 while (fgets(line, 256, config) != NULL) {
313 char *start = line;
314 char *word, *value;
315 char *min, *minForPoint;;
316
317 while (isspace(*start) && isascii(*start))
318 start++;
319
320 if (!isascii(*start))
321 continue;
322 if (start[0] == '#')
323 continue;
324
325 if ((word = strtok(start, " \t"))) {
326 if ((value = strtok(NULL, " \t")) == NULL)
327 continue;
328 if (strchr(value, '\n') != NULL)
329 strchr(value, '\n')[0] = '\0';
330 min = strtok(NULL, " \t");
331 if (min != NULL)
332 if (strchr(min, '\n') != NULL)
333 strchr(min, '\n')[0] = '\0';
334 minForPoint = strtok(NULL, " \t");
335 if (minForPoint != NULL)
336 if (strchr(minForPoint, '\n') != NULL)
337 strchr(minForPoint, '\n')[0] = '\0';
338
339
340 nParam = typeParam(word); // search for param in allowedParameters
341 if (nParam != sAllowedParameters) // param has been found
342 {
343 ppm_log(LOG_NOTICE,
344 "ppm: Param = %s, value = %s, min = %s, minForPoint= %s",
345 word, value, min, minForPoint);
346
347 storeEntry(word, value, allowedParameters[nParam].iType,
348 min, minForPoint, fileConf, numParam);
349 }
350 else
351 {
352 ppm_log(LOG_NOTICE,
353 "ppm: Parameter '%s' rejected", word);
354 }
355
356 }
357 }
358
359 fclose(config);
360 }
361
362 #endif
363
364 static int
realloc_error_message(char ** target,int curlen,int nextlen)365 realloc_error_message(char **target, int curlen, int nextlen)
366 {
367 if (curlen < nextlen + MEMORY_MARGIN) {
368 ppm_log(LOG_WARNING,
369 "ppm: Reallocating szErrStr from %d to %d", curlen,
370 nextlen + MEMORY_MARGIN);
371 ber_memfree(*target);
372 curlen = nextlen + MEMORY_MARGIN;
373 *target = (char *) ber_memalloc(curlen);
374 }
375
376 return curlen;
377 }
378
379 // Does the password contains a token from the RDN ?
380 int
containsRDN(char * passwd,char * DN)381 containsRDN(char* passwd, char* DN)
382 {
383 char lDN[DN_MAX_LEN];
384 char * tmpToken;
385 char * token;
386 regex_t regex;
387 int reti;
388
389 strcpy_safe(lDN, DN, DN_MAX_LEN);
390
391 // Extract the RDN from the DN
392 tmpToken = strtok(lDN, ",+");
393 tmpToken = strtok(tmpToken, "=");
394 tmpToken = strtok(NULL, "=");
395
396 // Search for each token in the password */
397 token = strtok(tmpToken, TOKENS_DELIMITERS);
398
399 while (token != NULL)
400 {
401 if (strlen(token) > 2)
402 {
403 ppm_log(LOG_NOTICE, "ppm: Checking if %s part of RDN matches the password", token);
404 // Compile regular expression
405 reti = regcomp(®ex, token, REG_ICASE);
406 if (reti) {
407 ppm_log(LOG_ERR, "ppm: Cannot compile regex: %s", token);
408 return 0;
409 }
410
411 // Execute regular expression
412 reti = regexec(®ex, passwd, 0, NULL, 0);
413 if (!reti)
414 {
415 regfree(®ex);
416 return 1;
417 }
418
419 regfree(®ex);
420 }
421 else
422 {
423 ppm_log(LOG_NOTICE, "ppm: %s part of RDN is too short to be checked", token);
424 }
425 token = strtok(NULL, TOKENS_DELIMITERS);
426 }
427
428 return 0;
429 }
430
431
432 int
check_password(char * pPasswd,char ** ppErrStr,Entry * e,void * pArg)433 check_password(char *pPasswd, char **ppErrStr, Entry *e, void *pArg)
434 {
435
436 Entry *pEntry = e;
437 ppm_log(LOG_NOTICE, "ppm: entry %s", pEntry->e_nname.bv_val);
438
439 struct berval *pwdCheckModuleArg = pArg;
440 /* Determine if config file is to be read (DEPRECATED) */
441 #ifdef PPM_READ_FILE
442 ppm_log(LOG_NOTICE, "ppm: Not reading pwdCheckModuleArg attribute");
443 ppm_log(LOG_NOTICE, "ppm: instead, read configuration file (deprecated)");
444 #else
445 ppm_log(LOG_NOTICE, "ppm: Reading pwdCheckModuleArg attribute");
446 ppm_log(LOG_NOTICE, "ppm: RAW configuration: %s",
447 (*(struct berval*)pwdCheckModuleArg).bv_val);
448 #endif
449
450 char *szErrStr = (char *) ber_memalloc(MEM_INIT_SZ);
451 int mem_len = MEM_INIT_SZ;
452 int numParam = 0; // Number of params in current configuration
453
454 int useCracklib;
455 char cracklibDict[VALUE_MAX_LEN];
456 char cracklibDictFiles[3][(VALUE_MAX_LEN+5)];
457 char const* cracklibExt[] = { ".hwm", ".pwd", ".pwi" };
458 FILE* fd;
459 char* res;
460 int minQuality;
461 int checkRDN;
462 char forbiddenChars[VALUE_MAX_LEN];
463 int nForbiddenChars = 0;
464 int nQuality = 0;
465 int maxConsecutivePerClass;
466 int nbInClass[CONF_MAX_SIZE];
467 int i,j;
468
469 /* Determine config file (DEPRECATED) */
470 #ifdef PPM_READ_FILE
471 char ppm_config_file[FILENAME_MAX_LEN];
472 strcpy_safe(ppm_config_file, getenv("PPM_CONFIG_FILE"), FILENAME_MAX_LEN);
473 if (ppm_config_file[0] == '\0') {
474 strcpy_safe(ppm_config_file, CONFIG_FILE, FILENAME_MAX_LEN);
475 }
476 ppm_log(LOG_NOTICE, "ppm: reading config file from %s", ppm_config_file);
477 #endif
478
479 for (i = 0; i < CONF_MAX_SIZE; i++)
480 nbInClass[i] = 0;
481
482 /* Set default values */
483 conf fileConf[CONF_MAX_SIZE] = {
484 {"minQuality", typeInt, {.iVal = DEFAULT_QUALITY}, 0, 0
485 }
486 ,
487 {"checkRDN", typeInt, {.iVal = 0}, 0, 0
488 }
489 ,
490 {"forbiddenChars", typeStr, {.sVal = ""}, 0, 0
491 }
492 ,
493 {"maxConsecutivePerClass", typeInt, {.iVal = 0}, 0, 0
494 }
495 ,
496 {"useCracklib", typeInt, {.iVal = 0}, 0, 0
497 }
498 ,
499 {"cracklibDict", typeStr, {.sVal = "/var/cache/cracklib/cracklib_dict"}, 0, 0
500 }
501 ,
502 {"class-upperCase", typeStr, {.sVal = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"}, 0, 1
503 }
504 ,
505 {"class-lowerCase", typeStr, {.sVal = "abcdefghijklmnopqrstuvwxyz"}, 0, 1
506 }
507 ,
508 {"class-digit", typeStr, {.sVal = "0123456789"}, 0, 1
509 }
510 ,
511 {"class-special", typeStr,
512 {.sVal = "<>,?;.:/!§ù%*µ^¨$£²&é~\"#'{([-|è`_\\ç^à@)]°=}+"}, 0, 1
513 }
514 };
515 numParam = 10;
516
517 #ifdef PPM_READ_FILE
518 /* Read configuration file (DEPRECATED) */
519 read_config_file(fileConf, &numParam, ppm_config_file);
520 #else
521 /* Read configuration attribute (pwdCheckModuleArg) */
522 read_config_attr(fileConf, &numParam, (*(struct berval*)pwdCheckModuleArg).bv_val);
523 #endif
524
525 minQuality = getValue(fileConf, numParam, "minQuality")->iVal;
526 checkRDN = getValue(fileConf, numParam, "checkRDN")->iVal;
527 strcpy_safe(forbiddenChars,
528 getValue(fileConf, numParam, "forbiddenChars")->sVal,
529 VALUE_MAX_LEN);
530 maxConsecutivePerClass = getValue(fileConf, numParam, "maxConsecutivePerClass")->iVal;
531 useCracklib = getValue(fileConf, numParam, "useCracklib")->iVal;
532 strcpy_safe(cracklibDict,
533 getValue(fileConf, numParam, "cracklibDict")->sVal,
534 VALUE_MAX_LEN);
535
536
537 /*The password must have at least minQuality strength points with one
538 * point granted if the password contains at least minForPoint characters for each class
539 * It must contains at least min chars of each class
540 * It must not contain any char in forbiddenChar */
541
542 for (i = 0; i < strlen(pPasswd); i++) {
543
544 int n;
545 for (n = 0; n < numParam; n++) {
546 if (strstr(fileConf[n].param, "class-") != NULL) {
547 if (strchr(fileConf[n].value.sVal, pPasswd[i]) != NULL) {
548 ++(nbInClass[n]);
549 }
550 }
551 }
552 if (strchr(forbiddenChars, pPasswd[i]) != NULL) {
553 nForbiddenChars++;
554 }
555 }
556
557 // Password checking done, now loocking for minForPoint criteria
558 for (i = 0; i < CONF_MAX_SIZE; i++) {
559 if (strstr(fileConf[i].param, "class-") != NULL) {
560 if ((nbInClass[i] >= fileConf[i].minForPoint)
561 && strlen(fileConf[i].value.sVal) != 0) {
562 // 1 point granted
563 ++nQuality;
564 ppm_log(LOG_NOTICE, "ppm: 1 point granted for class %s",
565 fileConf[i].param);
566 }
567 }
568 }
569
570 if (nQuality < minQuality) {
571 mem_len = realloc_error_message(&szErrStr, mem_len,
572 strlen(PASSWORD_QUALITY_SZ) +
573 strlen(pEntry->e_nname.bv_val) + 4);
574 sprintf(szErrStr, PASSWORD_QUALITY_SZ, pEntry->e_nname.bv_val,
575 nQuality, minQuality);
576 goto fail;
577 }
578 // Password checking done, now loocking for constraintClass criteria
579 for (i = 0; i < CONF_MAX_SIZE; i++) {
580 if (strstr(fileConf[i].param, "class-") != NULL) {
581 if ((nbInClass[i] < fileConf[i].min) &&
582 strlen(fileConf[i].value.sVal) != 0) {
583 // constraint is not satisfied... goto fail
584 mem_len = realloc_error_message(&szErrStr, mem_len,
585 strlen(PASSWORD_CRITERIA) +
586 strlen(pEntry->e_nname.bv_val) +
587 2 + PARAM_MAX_LEN);
588 sprintf(szErrStr, PASSWORD_CRITERIA, pEntry->e_nname.bv_val,
589 fileConf[i].min, fileConf[i].param);
590 goto fail;
591 }
592 }
593 }
594
595 // Password checking done, now loocking for forbiddenChars criteria
596 if (nForbiddenChars > 0) { // at least 1 forbidden char... goto fail
597 mem_len = realloc_error_message(&szErrStr, mem_len,
598 strlen(PASSWORD_FORBIDDENCHARS) +
599 strlen(pEntry->e_nname.bv_val) + 2 +
600 VALUE_MAX_LEN);
601 sprintf(szErrStr, PASSWORD_FORBIDDENCHARS, pEntry->e_nname.bv_val,
602 nForbiddenChars, forbiddenChars);
603 goto fail;
604 }
605
606 // Password checking done, now loocking for maxConsecutivePerClass criteria
607 for (i = 0; i < CONF_MAX_SIZE; i++) {
608 if (strstr(fileConf[i].param, "class-") != NULL) {
609 if ( maxConsecutivePerClass != 0 &&
610 (maxConsPerClass(pPasswd,fileConf[i].value.sVal)
611 > maxConsecutivePerClass)) {
612 // Too much consecutive characters of the same class
613 ppm_log(LOG_NOTICE, "ppm: Too much consecutive chars for class %s",
614 fileConf[i].param);
615 mem_len = realloc_error_message(&szErrStr, mem_len,
616 strlen(PASSWORD_MAXCONSECUTIVEPERCLASS) +
617 strlen(pEntry->e_nname.bv_val) + 2 +
618 PARAM_MAX_LEN);
619 sprintf(szErrStr, PASSWORD_MAXCONSECUTIVEPERCLASS, pEntry->e_nname.bv_val,
620 maxConsecutivePerClass, fileConf[i].param);
621 goto fail;
622 }
623 }
624 }
625 #ifdef CRACKLIB
626 // Password checking done, now loocking for cracklib criteria
627 if ( useCracklib > 0 ) {
628
629 for( j = 0 ; j < 3 ; j++) {
630 strcpy_safe(cracklibDictFiles[j], cracklibDict, VALUE_MAX_LEN);
631 strcat(cracklibDictFiles[j], cracklibExt[j]);
632 if (( fd = fopen ( cracklibDictFiles[j], "r")) == NULL ) {
633 ppm_log(LOG_NOTICE, "ppm: Error while reading %s file",
634 cracklibDictFiles[j]);
635 mem_len = realloc_error_message(&szErrStr, mem_len,
636 strlen(GENERIC_ERROR));
637 sprintf(szErrStr, GENERIC_ERROR);
638 goto fail;
639
640 }
641 else {
642 fclose (fd);
643 }
644 }
645 res = (char *) FascistCheck (pPasswd, cracklibDict);
646 if ( res != NULL ) {
647 ppm_log(LOG_NOTICE, "ppm: cracklib does not validate password for entry %s",
648 pEntry->e_nname.bv_val);
649 mem_len = realloc_error_message(&szErrStr, mem_len,
650 strlen(PASSWORD_CRACKLIB) +
651 strlen(pEntry->e_nname.bv_val));
652 sprintf(szErrStr, PASSWORD_CRACKLIB, pEntry->e_nname.bv_val);
653 goto fail;
654
655 }
656
657 }
658 #endif
659
660 // Password checking done, now looking for checkRDN criteria
661 if (checkRDN == 1 && containsRDN(pPasswd, pEntry->e_nname.bv_val))
662 // RDN check enabled and a token from RDN is found in password: goto fail
663 {
664 mem_len = realloc_error_message(&szErrStr, mem_len,
665 strlen(RDN_TOKEN_FOUND) +
666 strlen(pEntry->e_nname.bv_val));
667 sprintf(szErrStr, RDN_TOKEN_FOUND, pEntry->e_nname.bv_val);
668
669 goto fail;
670 }
671
672 *ppErrStr = strdup("");
673 ber_memfree(szErrStr);
674 return (LDAP_SUCCESS);
675
676 fail:
677 *ppErrStr = strdup(szErrStr);
678 ber_memfree(szErrStr);
679 return (EXIT_FAILURE);
680
681 }
682