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