xref: /freebsd/contrib/sendmail/libsm/mbdb.c (revision 5d3e7166f6a0187fa3f8831b16a06bd9955c21ff)
1 /*
2  * Copyright (c) 2001-2003,2009 Proofpoint, Inc. and its suppliers.
3  *      All rights reserved.
4  *
5  * By using this file, you agree to the terms and conditions set
6  * forth in the LICENSE file which can be found at the top level of
7  * the sendmail distribution.
8  */
9 
10 #include <sm/gen.h>
11 SM_RCSID("@(#)$Id: mbdb.c,v 1.43 2014-01-08 17:03:15 ca Exp $")
12 
13 #include <sys/param.h>
14 
15 #include <ctype.h>
16 #include <errno.h>
17 #include <pwd.h>
18 #include <stdlib.h>
19 #include <setjmp.h>
20 #include <unistd.h>
21 
22 #include <sm/limits.h>
23 #include <sm/conf.h>
24 #include <sm/assert.h>
25 #include <sm/bitops.h>
26 #include <sm/errstring.h>
27 #include <sm/heap.h>
28 #include <sm/mbdb.h>
29 #include <sm/string.h>
30 #include <sm/sysexits.h>
31 
32 #if LDAPMAP && _LDAP_EXAMPLE_
33 # include <sm/ldap.h>
34 #endif
35 
36 typedef struct
37 {
38 	char	*mbdb_typename;
39 	int	(*mbdb_initialize) __P((char *));
40 	int	(*mbdb_lookup) __P((char *name, SM_MBDB_T *user));
41 	void	(*mbdb_terminate) __P((void));
42 } SM_MBDB_TYPE_T;
43 
44 static int	mbdb_pw_initialize __P((char *));
45 static int	mbdb_pw_lookup __P((char *name, SM_MBDB_T *user));
46 static void	mbdb_pw_terminate __P((void));
47 
48 #if LDAPMAP && _LDAP_EXAMPLE_
49 static struct sm_ldap_struct LDAPLMAP;
50 static int	mbdb_ldap_initialize __P((char *));
51 static int	mbdb_ldap_lookup __P((char *name, SM_MBDB_T *user));
52 static void	mbdb_ldap_terminate __P((void));
53 #endif /* LDAPMAP && _LDAP_EXAMPLE_ */
54 
55 static SM_MBDB_TYPE_T SmMbdbTypes[] =
56 {
57 	{ "pw", mbdb_pw_initialize, mbdb_pw_lookup, mbdb_pw_terminate },
58 #if LDAPMAP && _LDAP_EXAMPLE_
59 	{ "ldap", mbdb_ldap_initialize, mbdb_ldap_lookup, mbdb_ldap_terminate },
60 #endif
61 	{ NULL, NULL, NULL, NULL }
62 };
63 
64 static SM_MBDB_TYPE_T *SmMbdbType = &SmMbdbTypes[0];
65 
66 /*
67 **  SM_MBDB_INITIALIZE -- specify which mailbox database to use
68 **
69 **	If this function is not called, then the "pw" implementation
70 **	is used by default; this implementation uses getpwnam().
71 **
72 **	Parameters:
73 **		mbdb -- Which mailbox database to use.
74 **			The argument has the form "name" or "name.arg".
75 **			"pw" means use getpwnam().
76 **
77 **	Results:
78 **		EX_OK on success, or an EX_* code on failure.
79 */
80 
81 int
82 sm_mbdb_initialize(mbdb)
83 	char *mbdb;
84 {
85 	size_t namelen;
86 	int err;
87 	char *name;
88 	char *arg;
89 	SM_MBDB_TYPE_T *t;
90 
91 	SM_REQUIRE(mbdb != NULL);
92 
93 	name = mbdb;
94 	arg = strchr(mbdb, '.');
95 	if (arg == NULL)
96 		namelen = strlen(name);
97 	else
98 	{
99 		namelen = arg - name;
100 		++arg;
101 	}
102 
103 	for (t = SmMbdbTypes; t->mbdb_typename != NULL; ++t)
104 	{
105 		if (strlen(t->mbdb_typename) == namelen &&
106 		    strncmp(name, t->mbdb_typename, namelen) == 0)
107 		{
108 			err = EX_OK;
109 			if (t->mbdb_initialize != NULL)
110 				err = t->mbdb_initialize(arg);
111 			if (err == EX_OK)
112 				SmMbdbType = t;
113 			return err;
114 		}
115 	}
116 	return EX_UNAVAILABLE;
117 }
118 
119 /*
120 **  SM_MBDB_TERMINATE -- terminate connection to the mailbox database
121 **
122 **	Because this function closes any cached file descriptors that
123 **	are being held open for the connection to the mailbox database,
124 **	it should be called for security reasons prior to dropping privileges
125 **	and execing another process.
126 **
127 **	Parameters:
128 **		none.
129 **
130 **	Results:
131 **		none.
132 */
133 
134 void
135 sm_mbdb_terminate()
136 {
137 	if (SmMbdbType->mbdb_terminate != NULL)
138 		SmMbdbType->mbdb_terminate();
139 }
140 
141 /*
142 **  SM_MBDB_LOOKUP -- look up a local mail recipient, given name
143 **
144 **	Parameters:
145 **		name -- name of local mail recipient
146 **		user -- pointer to structure to fill in on success
147 **
148 **	Results:
149 **		On success, fill in *user and return EX_OK.
150 **		If the user does not exist, return EX_NOUSER.
151 **		If a temporary failure (eg, a network failure) occurred,
152 **		return EX_TEMPFAIL.  Otherwise return EX_OSERR.
153 */
154 
155 int
156 sm_mbdb_lookup(name, user)
157 	char *name;
158 	SM_MBDB_T *user;
159 {
160 	int ret = EX_NOUSER;
161 
162 	if (SmMbdbType->mbdb_lookup != NULL)
163 		ret = SmMbdbType->mbdb_lookup(name, user);
164 	return ret;
165 }
166 
167 /*
168 **  SM_MBDB_FROMPW -- copy from struct pw to SM_MBDB_T
169 **
170 **	Parameters:
171 **		user -- destination user information structure
172 **		pw -- source passwd structure
173 **
174 **	Results:
175 **		none.
176 */
177 
178 void
179 sm_mbdb_frompw(user, pw)
180 	SM_MBDB_T *user;
181 	struct passwd *pw;
182 {
183 	SM_REQUIRE(user != NULL);
184 	(void) sm_strlcpy(user->mbdb_name, pw->pw_name,
185 			  sizeof(user->mbdb_name));
186 	user->mbdb_uid = pw->pw_uid;
187 	user->mbdb_gid = pw->pw_gid;
188 	sm_pwfullname(pw->pw_gecos, pw->pw_name, user->mbdb_fullname,
189 		      sizeof(user->mbdb_fullname));
190 	(void) sm_strlcpy(user->mbdb_homedir, pw->pw_dir,
191 			  sizeof(user->mbdb_homedir));
192 	(void) sm_strlcpy(user->mbdb_shell, pw->pw_shell,
193 			  sizeof(user->mbdb_shell));
194 }
195 
196 /*
197 **  SM_PWFULLNAME -- build full name of user from pw_gecos field.
198 **
199 **	This routine interprets the strange entry that would appear
200 **	in the GECOS field of the password file.
201 **
202 **	Parameters:
203 **		gecos -- name to build.
204 **		user -- the login name of this user (for &).
205 **		buf -- place to put the result.
206 **		buflen -- length of buf.
207 **
208 **	Returns:
209 **		none.
210 */
211 
212 #if _FFR_HANDLE_ISO8859_GECOS
213 static char Latin1ToASCII[128] =
214 {
215 	32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
216 	32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 33,
217 	99, 80, 36, 89, 124, 36, 34, 99, 97, 60, 45, 45, 114, 45, 111, 42,
218 	50, 51, 39, 117, 80, 46, 44, 49, 111, 62, 42, 42, 42, 63, 65, 65,
219 	65, 65, 65, 65, 65, 67, 69, 69, 69, 69, 73, 73, 73, 73, 68, 78, 79,
220 	79, 79, 79, 79, 88, 79, 85, 85, 85, 85, 89, 80, 66, 97, 97, 97, 97,
221 	97, 97, 97, 99, 101, 101, 101, 101, 105, 105, 105, 105, 100, 110,
222 	111, 111, 111, 111, 111, 47, 111, 117, 117, 117, 117, 121, 112, 121
223 };
224 #endif /* _FFR_HANDLE_ISO8859_GECOS */
225 
226 void
227 sm_pwfullname(gecos, user, buf, buflen)
228 	register char *gecos;
229 	char *user;
230 	char *buf;
231 	size_t buflen;
232 {
233 	register char *p;
234 	register char *bp = buf;
235 
236 	if (*gecos == '*')
237 		gecos++;
238 
239 	/* copy gecos, interpolating & to be full name */
240 	for (p = gecos; *p != '\0' && *p != ',' && *p != ';' && *p != '%'; p++)
241 	{
242 		if (bp >= &buf[buflen - 1])
243 		{
244 			/* buffer overflow -- just use login name */
245 			(void) sm_strlcpy(buf, user, buflen);
246 			return;
247 		}
248 		if (*p == '&')
249 		{
250 			/* interpolate full name */
251 			(void) sm_strlcpy(bp, user, buflen - (bp - buf));
252 			*bp = toupper(*bp);
253 			bp += strlen(bp);
254 		}
255 		else
256 		{
257 #if _FFR_HANDLE_ISO8859_GECOS
258 			if ((unsigned char) *p >= 128)
259 				*bp++ = Latin1ToASCII[(unsigned char) *p - 128];
260 			else
261 #endif
262 				/* "else" in #if code above */
263 				*bp++ = *p;
264 		}
265 	}
266 	*bp = '\0';
267 }
268 
269 /*
270 **  /etc/passwd implementation.
271 */
272 
273 /*
274 **  MBDB_PW_INITIALIZE -- initialize getpwnam() version
275 **
276 **	Parameters:
277 **		arg -- unused.
278 **
279 **	Results:
280 **		EX_OK.
281 */
282 
283 /* ARGSUSED0 */
284 static int
285 mbdb_pw_initialize(arg)
286 	char *arg;
287 {
288 	return EX_OK;
289 }
290 
291 /*
292 **  MBDB_PW_LOOKUP -- look up a local mail recipient, given name
293 **
294 **	Parameters:
295 **		name -- name of local mail recipient
296 **		user -- pointer to structure to fill in on success
297 **
298 **	Results:
299 **		On success, fill in *user and return EX_OK.
300 **		Failure: EX_NOUSER.
301 */
302 
303 static int
304 mbdb_pw_lookup(name, user)
305 	char *name;
306 	SM_MBDB_T *user;
307 {
308 	struct passwd *pw;
309 
310 #if HESIOD && !HESIOD_ALLOW_NUMERIC_LOGIN
311 	/* DEC Hesiod getpwnam accepts numeric strings -- short circuit it */
312 	{
313 		char *p;
314 
315 		for (p = name; *p != '\0'; p++)
316 			if (!isascii(*p) || !isdigit(*p))
317 				break;
318 		if (*p == '\0')
319 			return EX_NOUSER;
320 	}
321 #endif /* HESIOD && !HESIOD_ALLOW_NUMERIC_LOGIN */
322 
323 	errno = 0;
324 	pw = getpwnam(name);
325 	if (pw == NULL)
326 	{
327 #if _FFR_USE_GETPWNAM_ERRNO
328 		/*
329 		**  Only enable this code iff
330 		**  user unknown <-> getpwnam() == NULL && errno == 0
331 		**  (i.e., errno unchanged); see the POSIX spec.
332 		*/
333 
334 		if (errno != 0)
335 			return EX_TEMPFAIL;
336 #endif /* _FFR_USE_GETPWNAM_ERRNO */
337 		return EX_NOUSER;
338 	}
339 
340 	sm_mbdb_frompw(user, pw);
341 	return EX_OK;
342 }
343 
344 /*
345 **  MBDB_PW_TERMINATE -- terminate connection to the mailbox database
346 **
347 **	Parameters:
348 **		none.
349 **
350 **	Results:
351 **		none.
352 */
353 
354 static void
355 mbdb_pw_terminate()
356 {
357 	endpwent();
358 }
359 
360 #if LDAPMAP && _LDAP_EXAMPLE_
361 /*
362 **  LDAP example implementation based on RFC 2307, "An Approach for Using
363 **  LDAP as a Network Information Service":
364 **
365 **	( nisSchema.1.0 NAME 'uidNumber'
366 **	  DESC 'An integer uniquely identifying a user in an
367 **		administrative domain'
368 **	  EQUALITY integerMatch SYNTAX 'INTEGER' SINGLE-VALUE )
369 **
370 **	( nisSchema.1.1 NAME 'gidNumber'
371 **	  DESC 'An integer uniquely identifying a group in an
372 **		administrative domain'
373 **	  EQUALITY integerMatch SYNTAX 'INTEGER' SINGLE-VALUE )
374 **
375 **	( nisSchema.1.2 NAME 'gecos'
376 **	  DESC 'The GECOS field; the common name'
377 **	  EQUALITY caseIgnoreIA5Match
378 **	  SUBSTRINGS caseIgnoreIA5SubstringsMatch
379 **	  SYNTAX 'IA5String' SINGLE-VALUE )
380 **
381 **	( nisSchema.1.3 NAME 'homeDirectory'
382 **	  DESC 'The absolute path to the home directory'
383 **	  EQUALITY caseExactIA5Match
384 **	  SYNTAX 'IA5String' SINGLE-VALUE )
385 **
386 **	( nisSchema.1.4 NAME 'loginShell'
387 **	  DESC 'The path to the login shell'
388 **	  EQUALITY caseExactIA5Match
389 **	  SYNTAX 'IA5String' SINGLE-VALUE )
390 **
391 **	( nisSchema.2.0 NAME 'posixAccount' SUP top AUXILIARY
392 **	  DESC 'Abstraction of an account with POSIX attributes'
393 **	  MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory )
394 **	  MAY ( userPassword $ loginShell $ gecos $ description ) )
395 **
396 */
397 
398 # define MBDB_LDAP_LABEL		"MailboxDatabase"
399 
400 # ifndef MBDB_LDAP_FILTER
401 #  define MBDB_LDAP_FILTER		"(&(objectClass=posixAccount)(uid=%0))"
402 # endif
403 
404 # ifndef MBDB_DEFAULT_LDAP_BASEDN
405 #  define MBDB_DEFAULT_LDAP_BASEDN	NULL
406 # endif
407 
408 # ifndef MBDB_DEFAULT_LDAP_SERVER
409 #  define MBDB_DEFAULT_LDAP_SERVER	NULL
410 # endif
411 
412 /*
413 **  MBDB_LDAP_INITIALIZE -- initialize LDAP version
414 **
415 **	Parameters:
416 **		arg -- LDAP specification
417 **
418 **	Results:
419 **		EX_OK on success, or an EX_* code on failure.
420 */
421 
422 static int
423 mbdb_ldap_initialize(arg)
424 	char *arg;
425 {
426 	sm_ldap_clear(&LDAPLMAP);
427 	LDAPLMAP.ldap_base = MBDB_DEFAULT_LDAP_BASEDN;
428 	LDAPLMAP.ldap_host = MBDB_DEFAULT_LDAP_SERVER;
429 	LDAPLMAP.ldap_filter = MBDB_LDAP_FILTER;
430 
431 	/* Only want one match */
432 	LDAPLMAP.ldap_sizelimit = 1;
433 
434 	/* interpolate new ldap_base and ldap_host from arg if given */
435 	if (arg != NULL && *arg != '\0')
436 	{
437 		char *new;
438 		char *sep;
439 		size_t len;
440 
441 		len = strlen(arg) + 1;
442 		new = sm_malloc(len);
443 		if (new == NULL)
444 			return EX_TEMPFAIL;
445 		(void) sm_strlcpy(new, arg, len);
446 		sep = strrchr(new, '@');
447 		if (sep != NULL)
448 		{
449 			*sep++ = '\0';
450 			LDAPLMAP.ldap_host = sep;
451 		}
452 		LDAPLMAP.ldap_base = new;
453 	}
454 	return EX_OK;
455 }
456 
457 
458 /*
459 **  MBDB_LDAP_LOOKUP -- look up a local mail recipient, given name
460 **
461 **	Parameters:
462 **		name -- name of local mail recipient
463 **		user -- pointer to structure to fill in on success
464 **
465 **	Results:
466 **		On success, fill in *user and return EX_OK.
467 **		Failure: EX_NOUSER.
468 */
469 
470 #define NEED_FULLNAME	0x01
471 #define NEED_HOMEDIR	0x02
472 #define NEED_SHELL	0x04
473 #define NEED_UID	0x08
474 #define NEED_GID	0x10
475 
476 static int
477 mbdb_ldap_lookup(name, user)
478 	char *name;
479 	SM_MBDB_T *user;
480 {
481 	int msgid;
482 	int need;
483 	int ret;
484 	int save_errno;
485 	LDAPMessage *entry;
486 	BerElement *ber;
487 	char *attr = NULL;
488 
489 	if (strlen(name) >= sizeof(user->mbdb_name))
490 	{
491 		errno = EINVAL;
492 		return EX_NOUSER;
493 	}
494 
495 	if (LDAPLMAP.ldap_filter == NULL)
496 	{
497 		/* map not initialized, but don't have arg here */
498 		errno = EFAULT;
499 		return EX_TEMPFAIL;
500 	}
501 
502 	if (LDAPLMAP.ldap_pid != getpid())
503 	{
504 		/* re-open map in this child process */
505 		LDAPLMAP.ldap_ld = NULL;
506 	}
507 
508 	if (LDAPLMAP.ldap_ld == NULL)
509 	{
510 		/* map not open, try to open now */
511 		if (!sm_ldap_start(MBDB_LDAP_LABEL, &LDAPLMAP))
512 			return EX_TEMPFAIL;
513 	}
514 
515 	sm_ldap_setopts(LDAPLMAP.ldap_ld, &LDAPLMAP);
516 	msgid = sm_ldap_search(&LDAPLMAP, name);
517 	if (msgid == -1)
518 	{
519 		save_errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld) + E_LDAPBASE;
520 # ifdef LDAP_SERVER_DOWN
521 		if (errno == LDAP_SERVER_DOWN)
522 		{
523 			/* server disappeared, try reopen on next search */
524 			sm_ldap_close(&LDAPLMAP);
525 		}
526 # endif /* LDAP_SERVER_DOWN */
527 		errno = save_errno;
528 		return EX_TEMPFAIL;
529 	}
530 
531 	/* Get results */
532 	ret = ldap_result(LDAPLMAP.ldap_ld, msgid, 1,
533 			  (LDAPLMAP.ldap_timeout.tv_sec == 0 ? NULL :
534 			   &(LDAPLMAP.ldap_timeout)),
535 			  &(LDAPLMAP.ldap_res));
536 
537 	if (ret != LDAP_RES_SEARCH_RESULT &&
538 	    ret != LDAP_RES_SEARCH_ENTRY)
539 	{
540 		if (ret == 0)
541 			errno = ETIMEDOUT;
542 		else
543 			errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld);
544 		ret = EX_TEMPFAIL;
545 		goto abort;
546 	}
547 
548 	entry = ldap_first_entry(LDAPLMAP.ldap_ld, LDAPLMAP.ldap_res);
549 	if (entry == NULL)
550 	{
551 		int rc;
552 
553 		/*
554 		**  We may have gotten an LDAP_RES_SEARCH_RESULT response
555 		**  with an error inside it, so we have to extract that
556 		**  with ldap_parse_result().  This can happen when talking
557 		**  to an LDAP proxy whose backend has gone down.
558 		*/
559 
560 		save_errno = ldap_parse_result(LDAPLMAP.ldap_ld,
561 					       LDAPLMAP.ldap_res, &rc, NULL,
562 					       NULL, NULL, NULL, 0);
563 		if (save_errno == LDAP_SUCCESS)
564 			save_errno = rc;
565 		if (save_errno == LDAP_SUCCESS)
566 		{
567 			errno = ENOENT;
568 			ret = EX_NOUSER;
569 		}
570 		else
571 		{
572 			errno = save_errno;
573 			ret = EX_TEMPFAIL;
574 		}
575 		goto abort;
576 	}
577 
578 # if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
579 	/*
580 	**  Reset value to prevent lingering
581 	**  LDAP_DECODING_ERROR due to
582 	**  OpenLDAP 1.X's hack (see below)
583 	*/
584 
585 	LDAPLMAP.ldap_ld->ld_errno = LDAP_SUCCESS;
586 # endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
587 
588 	ret = EX_OK;
589 	need = NEED_FULLNAME|NEED_HOMEDIR|NEED_SHELL|NEED_UID|NEED_GID;
590 	for (attr = ldap_first_attribute(LDAPLMAP.ldap_ld, entry, &ber);
591 	     attr != NULL;
592 	     attr = ldap_next_attribute(LDAPLMAP.ldap_ld, entry, ber))
593 	{
594 		char **vals;
595 
596 		vals = ldap_get_values(LDAPLMAP.ldap_ld, entry, attr);
597 		if (vals == NULL)
598 		{
599 			errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld);
600 			if (errno == LDAP_SUCCESS)
601 			{
602 				ldap_memfree(attr);
603 				continue;
604 			}
605 
606 			/* Must be an error */
607 			errno += E_LDAPBASE;
608 			ret = EX_TEMPFAIL;
609 			goto abort;
610 		}
611 
612 # if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
613 		/*
614 		**  Reset value to prevent lingering
615 		**  LDAP_DECODING_ERROR due to
616 		**  OpenLDAP 1.X's hack (see below)
617 		*/
618 
619 		LDAPLMAP.ldap_ld->ld_errno = LDAP_SUCCESS;
620 # endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
621 
622 		if (vals[0] == NULL || vals[0][0] == '\0')
623 			goto skip;
624 
625 		if (strcasecmp(attr, "gecos") == 0)
626 		{
627 			if (!bitset(NEED_FULLNAME, need) ||
628 			    strlen(vals[0]) >= sizeof(user->mbdb_fullname))
629 				goto skip;
630 
631 			sm_pwfullname(vals[0], name, user->mbdb_fullname,
632 				      sizeof(user->mbdb_fullname));
633 			need &= ~NEED_FULLNAME;
634 		}
635 		else if (strcasecmp(attr, "homeDirectory") == 0)
636 		{
637 			if (!bitset(NEED_HOMEDIR, need) ||
638 			    strlen(vals[0]) >= sizeof(user->mbdb_homedir))
639 				goto skip;
640 
641 			(void) sm_strlcpy(user->mbdb_homedir, vals[0],
642 					  sizeof(user->mbdb_homedir));
643 			need &= ~NEED_HOMEDIR;
644 		}
645 		else if (strcasecmp(attr, "loginShell") == 0)
646 		{
647 			if (!bitset(NEED_SHELL, need) ||
648 			    strlen(vals[0]) >= sizeof(user->mbdb_shell))
649 				goto skip;
650 
651 			(void) sm_strlcpy(user->mbdb_shell, vals[0],
652 					  sizeof(user->mbdb_shell));
653 			need &= ~NEED_SHELL;
654 		}
655 		else if (strcasecmp(attr, "uidNumber") == 0)
656 		{
657 			char *p;
658 
659 			if (!bitset(NEED_UID, need))
660 				goto skip;
661 
662 			for (p = vals[0]; *p != '\0'; p++)
663 			{
664 				/* allow negative numbers */
665 				if (p == vals[0] && *p == '-')
666 				{
667 					/* but not simply '-' */
668 					if (*(p + 1) == '\0')
669 						goto skip;
670 				}
671 				else if (!isascii(*p) || !isdigit(*p))
672 					goto skip;
673 			}
674 			user->mbdb_uid = atoi(vals[0]);
675 			need &= ~NEED_UID;
676 		}
677 		else if (strcasecmp(attr, "gidNumber") == 0)
678 		{
679 			char *p;
680 
681 			if (!bitset(NEED_GID, need))
682 				goto skip;
683 
684 			for (p = vals[0]; *p != '\0'; p++)
685 			{
686 				/* allow negative numbers */
687 				if (p == vals[0] && *p == '-')
688 				{
689 					/* but not simply '-' */
690 					if (*(p + 1) == '\0')
691 						goto skip;
692 				}
693 				else if (!isascii(*p) || !isdigit(*p))
694 					goto skip;
695 			}
696 			user->mbdb_gid = atoi(vals[0]);
697 			need &= ~NEED_GID;
698 		}
699 
700 skip:
701 		ldap_value_free(vals);
702 		ldap_memfree(attr);
703 	}
704 
705 	errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld);
706 
707 	/*
708 	**  We check errno != LDAP_DECODING_ERROR since
709 	**  OpenLDAP 1.X has a very ugly *undocumented*
710 	**  hack of returning this error code from
711 	**  ldap_next_attribute() if the library freed the
712 	**  ber attribute.  See:
713 	**  http://www.openldap.org/lists/openldap-devel/9901/msg00064.html
714 	*/
715 
716 	if (errno != LDAP_SUCCESS &&
717 	    errno != LDAP_DECODING_ERROR)
718 	{
719 		/* Must be an error */
720 		errno += E_LDAPBASE;
721 		ret = EX_TEMPFAIL;
722 		goto abort;
723 	}
724 
725  abort:
726 	save_errno = errno;
727 	if (attr != NULL)
728 	{
729 		ldap_memfree(attr);
730 		attr = NULL;
731 	}
732 	if (LDAPLMAP.ldap_res != NULL)
733 	{
734 		ldap_msgfree(LDAPLMAP.ldap_res);
735 		LDAPLMAP.ldap_res = NULL;
736 	}
737 	if (ret == EX_OK)
738 	{
739 		if (need == 0)
740 		{
741 			(void) sm_strlcpy(user->mbdb_name, name,
742 					  sizeof(user->mbdb_name));
743 			save_errno = 0;
744 		}
745 		else
746 		{
747 			ret = EX_NOUSER;
748 			save_errno = EINVAL;
749 		}
750 	}
751 	errno = save_errno;
752 	return ret;
753 }
754 
755 /*
756 **  MBDB_LDAP_TERMINATE -- terminate connection to the mailbox database
757 **
758 **	Parameters:
759 **		none.
760 **
761 **	Results:
762 **		none.
763 */
764 
765 static void
766 mbdb_ldap_terminate()
767 {
768 	sm_ldap_close(&LDAPLMAP);
769 }
770 #endif /* LDAPMAP && _LDAP_EXAMPLE_ */
771