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