xref: /freebsd/contrib/sendmail/libsm/mbdb.c (revision 51a9219f5780e61e1437d25220bf8750d9df7f8b)
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.38 2002/04/05 22:59:56 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 = EX_OK;
117 			if (t->mbdb_initialize != NULL)
118 				err = t->mbdb_initialize(arg);
119 			if (err == EX_OK)
120 				SmMbdbType = t;
121 			return err;
122 		}
123 	}
124 	return EX_UNAVAILABLE;
125 }
126 
127 /*
128 **  SM_MBDB_TERMINATE -- terminate connection to the mailbox database
129 **
130 **	Because this function closes any cached file descriptors that
131 **	are being held open for the connection to the mailbox database,
132 **	it should be called for security reasons prior to dropping privileges
133 **	and execing another process.
134 **
135 **	Parameters:
136 **		none.
137 **
138 **	Results:
139 **		none.
140 */
141 
142 void
143 sm_mbdb_terminate()
144 {
145 	if (SmMbdbType->mbdb_terminate != NULL)
146 		SmMbdbType->mbdb_terminate();
147 }
148 
149 /*
150 **  SM_MBDB_LOOKUP -- look up a local mail recipient, given name
151 **
152 **	Parameters:
153 **		name -- name of local mail recipient
154 **		user -- pointer to structure to fill in on success
155 **
156 **	Results:
157 **		On success, fill in *user and return EX_OK.
158 **		If the user does not exist, return EX_NOUSER.
159 **		If a temporary failure (eg, a network failure) occurred,
160 **		return EX_TEMPFAIL.  Otherwise return EX_OSERR.
161 */
162 
163 int
164 sm_mbdb_lookup(name, user)
165 	char *name;
166 	SM_MBDB_T *user;
167 {
168 	int ret = EX_NOUSER;
169 
170 	if (SmMbdbType->mbdb_lookup != NULL)
171 		ret = SmMbdbType->mbdb_lookup(name, user);
172 	return ret;
173 }
174 
175 /*
176 **  SM_MBDB_FROMPW -- copy from struct pw to SM_MBDB_T
177 **
178 **	Parameters:
179 **		user -- destination user information structure
180 **		pw -- source passwd structure
181 **
182 **	Results:
183 **		none.
184 */
185 
186 void
187 sm_mbdb_frompw(user, pw)
188 	SM_MBDB_T *user;
189 	struct passwd *pw;
190 {
191 	SM_REQUIRE(user != NULL);
192 	(void) sm_strlcpy(user->mbdb_name, pw->pw_name,
193 			  sizeof(user->mbdb_name));
194 	user->mbdb_uid = pw->pw_uid;
195 	user->mbdb_gid = pw->pw_gid;
196 	sm_pwfullname(pw->pw_gecos, pw->pw_name, user->mbdb_fullname,
197 		      sizeof(user->mbdb_fullname));
198 	(void) sm_strlcpy(user->mbdb_homedir, pw->pw_dir,
199 			  sizeof(user->mbdb_homedir));
200 	(void) sm_strlcpy(user->mbdb_shell, pw->pw_shell,
201 			  sizeof(user->mbdb_shell));
202 }
203 
204 /*
205 **  SM_PWFULLNAME -- build full name of user from pw_gecos field.
206 **
207 **	This routine interprets the strange entry that would appear
208 **	in the GECOS field of the password file.
209 **
210 **	Parameters:
211 **		gecos -- name to build.
212 **		user -- the login name of this user (for &).
213 **		buf -- place to put the result.
214 **		buflen -- length of buf.
215 **
216 **	Returns:
217 **		none.
218 */
219 
220 #if _FFR_HANDLE_ISO8859_GECOS
221 static char Latin1ToASCII[128] =
222 {
223 	32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
224 	32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 33,
225 	99, 80, 36, 89, 124, 36, 34, 99, 97, 60, 45, 45, 114, 45, 111, 42,
226 	50, 51, 39, 117, 80, 46, 44, 49, 111, 62, 42, 42, 42, 63, 65, 65,
227 	65, 65, 65, 65, 65, 67, 69, 69, 69, 69, 73, 73, 73, 73, 68, 78, 79,
228 	79, 79, 79, 79, 88, 79, 85, 85, 85, 85, 89, 80, 66, 97, 97, 97, 97,
229 	97, 97, 97, 99, 101, 101, 101, 101, 105, 105, 105, 105, 100, 110,
230 	111, 111, 111, 111, 111, 47, 111, 117, 117, 117, 117, 121, 112, 121
231 };
232 #endif /* _FFR_HANDLE_ISO8859_GECOS */
233 
234 void
235 sm_pwfullname(gecos, user, buf, buflen)
236 	register char *gecos;
237 	char *user;
238 	char *buf;
239 	size_t buflen;
240 {
241 	register char *p;
242 	register char *bp = buf;
243 
244 	if (*gecos == '*')
245 		gecos++;
246 
247 	/* copy gecos, interpolating & to be full name */
248 	for (p = gecos; *p != '\0' && *p != ',' && *p != ';' && *p != '%'; p++)
249 	{
250 		if (bp >= &buf[buflen - 1])
251 		{
252 			/* buffer overflow -- just use login name */
253 			(void) sm_strlcpy(buf, user, buflen);
254 			return;
255 		}
256 		if (*p == '&')
257 		{
258 			/* interpolate full name */
259 			(void) sm_strlcpy(bp, user, buflen - (bp - buf));
260 			*bp = toupper(*bp);
261 			bp += strlen(bp);
262 		}
263 		else
264 		{
265 #if _FFR_HANDLE_ISO8859_GECOS
266 			if ((unsigned char) *p >= 128)
267 				*bp++ = Latin1ToASCII[(unsigned char) *p - 128];
268 			else
269 #endif /* _FFR_HANDLE_ISO8859_GECOS */
270 				*bp++ = *p;
271 		}
272 	}
273 	*bp = '\0';
274 }
275 
276 /*
277 **  /etc/passwd implementation.
278 */
279 
280 /*
281 **  MBDB_PW_INITIALIZE -- initialize getpwnam() version
282 **
283 **	Parameters:
284 **		arg -- unused.
285 **
286 **	Results:
287 **		EX_OK.
288 */
289 
290 /* ARGSUSED0 */
291 static int
292 mbdb_pw_initialize(arg)
293 	char *arg;
294 {
295 	return EX_OK;
296 }
297 
298 /*
299 **  MBDB_PW_LOOKUP -- look up a local mail recipient, given name
300 **
301 **	Parameters:
302 **		name -- name of local mail recipient
303 **		user -- pointer to structure to fill in on success
304 **
305 **	Results:
306 **		On success, fill in *user and return EX_OK.
307 **		Failure: EX_NOUSER.
308 */
309 
310 static int
311 mbdb_pw_lookup(name, user)
312 	char *name;
313 	SM_MBDB_T *user;
314 {
315 	struct passwd *pw;
316 
317 #ifdef HESIOD
318 	/* DEC Hesiod getpwnam accepts numeric strings -- short circuit it */
319 	{
320 		char *p;
321 
322 		for (p = name; *p != '\0'; p++)
323 			if (!isascii(*p) || !isdigit(*p))
324 				break;
325 		if (*p == '\0')
326 			return EX_NOUSER;
327 	}
328 #endif /* HESIOD */
329 
330 	errno = 0;
331 	pw = getpwnam(name);
332 	if (pw == NULL)
333 	{
334 #if 0
335 		/*
336 		**  getpwnam() isn't advertised as setting errno.
337 		**  In fact, under FreeBSD, non-root getpwnam() on
338 		**  non-existant users returns NULL with errno = EPERM.
339 		**  This test won't work.
340 		*/
341 		switch (errno)
342 		{
343 		  case 0:
344 			return EX_NOUSER;
345 		  case EIO:
346 			return EX_OSERR;
347 		  default:
348 			return EX_TEMPFAIL;
349 		}
350 #endif /* 0 */
351 		return EX_NOUSER;
352 	}
353 
354 	sm_mbdb_frompw(user, pw);
355 	return EX_OK;
356 }
357 
358 /*
359 **  MBDB_PW_TERMINATE -- terminate connection to the mailbox database
360 **
361 **	Parameters:
362 **		none.
363 **
364 **	Results:
365 **		none.
366 */
367 
368 static void
369 mbdb_pw_terminate()
370 {
371 	endpwent();
372 }
373 
374 #if LDAPMAP
375 # if _LDAP_EXAMPLE_
376 /*
377 **  LDAP example implementation based on RFC 2307, "An Approach for Using
378 **  LDAP as a Network Information Service":
379 **
380 **	( nisSchema.1.0 NAME 'uidNumber'
381 **	  DESC 'An integer uniquely identifying a user in an
382 **		administrative domain'
383 **	  EQUALITY integerMatch SYNTAX 'INTEGER' SINGLE-VALUE )
384 **
385 **	( nisSchema.1.1 NAME 'gidNumber'
386 **	  DESC 'An integer uniquely identifying a group in an
387 **		administrative domain'
388 **	  EQUALITY integerMatch SYNTAX 'INTEGER' SINGLE-VALUE )
389 **
390 **	( nisSchema.1.2 NAME 'gecos'
391 **	  DESC 'The GECOS field; the common name'
392 **	  EQUALITY caseIgnoreIA5Match
393 **	  SUBSTRINGS caseIgnoreIA5SubstringsMatch
394 **	  SYNTAX 'IA5String' SINGLE-VALUE )
395 **
396 **	( nisSchema.1.3 NAME 'homeDirectory'
397 **	  DESC 'The absolute path to the home directory'
398 **	  EQUALITY caseExactIA5Match
399 **	  SYNTAX 'IA5String' SINGLE-VALUE )
400 **
401 **	( nisSchema.1.4 NAME 'loginShell'
402 **	  DESC 'The path to the login shell'
403 **	  EQUALITY caseExactIA5Match
404 **	  SYNTAX 'IA5String' SINGLE-VALUE )
405 **
406 **	( nisSchema.2.0 NAME 'posixAccount' SUP top AUXILIARY
407 **	  DESC 'Abstraction of an account with POSIX attributes'
408 **	  MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory )
409 **	  MAY ( userPassword $ loginShell $ gecos $ description ) )
410 **
411 */
412 
413 #  define MBDB_LDAP_LABEL		"MailboxDatabase"
414 
415 #  ifndef MBDB_LDAP_FILTER
416 #   define MBDB_LDAP_FILTER		"(&(objectClass=posixAccount)(uid=%0))"
417 #  endif /* MBDB_LDAP_FILTER */
418 
419 #  ifndef MBDB_DEFAULT_LDAP_BASEDN
420 #   define MBDB_DEFAULT_LDAP_BASEDN	NULL
421 #  endif /* MBDB_DEFAULT_LDAP_BASEDN */
422 
423 #  ifndef MBDB_DEFAULT_LDAP_SERVER
424 #   define MBDB_DEFAULT_LDAP_SERVER	NULL
425 #  endif /* MBDB_DEFAULT_LDAP_SERVER */
426 
427 /*
428 **  MBDB_LDAP_INITIALIZE -- initialize LDAP version
429 **
430 **	Parameters:
431 **		arg -- LDAP specification
432 **
433 **	Results:
434 **		EX_OK on success, or an EX_* code on failure.
435 */
436 
437 static int
438 mbdb_ldap_initialize(arg)
439 	char *arg;
440 {
441 	sm_ldap_clear(&LDAPLMAP);
442 	LDAPLMAP.ldap_base = MBDB_DEFAULT_LDAP_BASEDN;
443 	LDAPLMAP.ldap_target = MBDB_DEFAULT_LDAP_SERVER;
444 	LDAPLMAP.ldap_filter = MBDB_LDAP_FILTER;
445 
446 	/* Only want one match */
447 	LDAPLMAP.ldap_sizelimit = 1;
448 
449 	/* interpolate new ldap_base and ldap_target from arg if given */
450 	if (arg != NULL && *arg != '\0')
451 	{
452 		char *new;
453 		char *sep;
454 		size_t len;
455 
456 		len = strlen(arg) + 1;
457 		new = sm_malloc(len);
458 		if (new == NULL)
459 			return EX_TEMPFAIL;
460 		(void) sm_strlcpy(new, arg, len);
461 		sep = strrchr(new, '@');
462 		if (sep != NULL)
463 		{
464 			*sep++ = '\0';
465 			LDAPLMAP.ldap_target = sep;
466 		}
467 		LDAPLMAP.ldap_base = new;
468 	}
469 
470 	/* No connection yet, connect */
471 	if (!sm_ldap_start(MBDB_LDAP_LABEL, &LDAPLMAP))
472 		return EX_UNAVAILABLE;
473 	return EX_OK;
474 }
475 
476 
477 /*
478 **  MBDB_LDAP_LOOKUP -- look up a local mail recipient, given name
479 **
480 **	Parameters:
481 **		name -- name of local mail recipient
482 **		user -- pointer to structure to fill in on success
483 **
484 **	Results:
485 **		On success, fill in *user and return EX_OK.
486 **		Failure: EX_NOUSER.
487 */
488 
489 #define NEED_FULLNAME	0x01
490 #define NEED_HOMEDIR	0x02
491 #define NEED_SHELL	0x04
492 #define NEED_UID	0x08
493 #define NEED_GID	0x10
494 
495 static int
496 mbdb_ldap_lookup(name, user)
497 	char *name;
498 	SM_MBDB_T *user;
499 {
500 	int msgid;
501 	int need;
502 	int ret;
503 	int save_errno;
504 	LDAPMessage *entry;
505 	BerElement *ber;
506 	char *attr = NULL;
507 
508 	if (strlen(name) >= sizeof(user->mbdb_name))
509 	{
510 		errno = EINVAL;
511 		return EX_NOUSER;
512 	}
513 
514 	if (LDAPLMAP.ldap_filter == NULL)
515 	{
516 		/* map not initialized, but don't have arg here */
517 		errno = EFAULT;
518 		return EX_TEMPFAIL;
519 	}
520 
521 	if (LDAPLMAP.ldap_ld == NULL)
522 	{
523 		/* map not open, try to open now */
524 		if (!sm_ldap_start(MBDB_LDAP_LABEL, &LDAPLMAP))
525 			return EX_TEMPFAIL;
526 	}
527 
528 	sm_ldap_setopts(LDAPLMAP.ldap_ld, &LDAPLMAP);
529 	msgid = sm_ldap_search(&LDAPLMAP, name);
530 	if (msgid == -1)
531 	{
532 		save_errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld) + E_LDAPBASE;
533 #  ifdef LDAP_SERVER_DOWN
534 		if (errno == LDAP_SERVER_DOWN)
535 		{
536 			/* server disappeared, try reopen on next search */
537 			sm_ldap_close(&LDAPLMAP);
538 		}
539 #  endif /* LDAP_SERVER_DOWN */
540 		errno = save_errno;
541 		return EX_TEMPFAIL;
542 	}
543 
544 	/* Get results */
545 	ret = ldap_result(LDAPLMAP.ldap_ld, msgid, 1,
546 			  (LDAPLMAP.ldap_timeout.tv_sec == 0 ? NULL :
547 			   &(LDAPLMAP.ldap_timeout)),
548 			  &(LDAPLMAP.ldap_res));
549 
550 	if (ret != LDAP_RES_SEARCH_RESULT &&
551 	    ret != LDAP_RES_SEARCH_ENTRY)
552 	{
553 		if (ret == 0)
554 			errno = ETIMEDOUT;
555 		else
556 			errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld);
557 		ret = EX_TEMPFAIL;
558 		goto abort;
559 	}
560 
561 	entry = ldap_first_entry(LDAPLMAP.ldap_ld, LDAPLMAP.ldap_res);
562 	if (entry == NULL)
563 	{
564 		save_errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld);
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 /* _LDAP_EXAMPLE_ */
771 #endif /* LDAPMAP */
772