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