xref: /illumos-gate/usr/src/cmd/sendmail/libsm/mbdb.c (revision 598f4ceed9327d2d6c2325dd67cae3aa06f7fea6)
1 /*
2  * Copyright (c) 2001-2003,2009 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.41 2009/06/19 22:02:26 guenther 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 		int rc;
568 
569 		/*
570 		**  We may have gotten an LDAP_RES_SEARCH_RESULT response
571 		**  with an error inside it, so we have to extract that
572 		**  with ldap_parse_result().  This can happen when talking
573 		**  to an LDAP proxy whose backend has gone down.
574 		*/
575 
576 		save_errno = ldap_parse_result(LDAPLMAP.ldap_ld,
577 					       LDAPLMAP.ldap_res, &rc, NULL,
578 					       NULL, NULL, NULL, 0);
579 		if (save_errno == LDAP_SUCCESS)
580 			save_errno = rc;
581 		if (save_errno == LDAP_SUCCESS)
582 		{
583 			errno = ENOENT;
584 			ret = EX_NOUSER;
585 		}
586 		else
587 		{
588 			errno = save_errno;
589 			ret = EX_TEMPFAIL;
590 		}
591 		goto abort;
592 	}
593 
594 # if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
595 	/*
596 	**  Reset value to prevent lingering
597 	**  LDAP_DECODING_ERROR due to
598 	**  OpenLDAP 1.X's hack (see below)
599 	*/
600 
601 	LDAPLMAP.ldap_ld->ld_errno = LDAP_SUCCESS;
602 # endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
603 
604 	ret = EX_OK;
605 	need = NEED_FULLNAME|NEED_HOMEDIR|NEED_SHELL|NEED_UID|NEED_GID;
606 	for (attr = ldap_first_attribute(LDAPLMAP.ldap_ld, entry, &ber);
607 	     attr != NULL;
608 	     attr = ldap_next_attribute(LDAPLMAP.ldap_ld, entry, ber))
609 	{
610 		char **vals;
611 
612 		vals = ldap_get_values(LDAPLMAP.ldap_ld, entry, attr);
613 		if (vals == NULL)
614 		{
615 			errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld);
616 			if (errno == LDAP_SUCCESS)
617 			{
618 				ldap_memfree(attr);
619 				continue;
620 			}
621 
622 			/* Must be an error */
623 			errno += E_LDAPBASE;
624 			ret = EX_TEMPFAIL;
625 			goto abort;
626 		}
627 
628 # if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
629 		/*
630 		**  Reset value to prevent lingering
631 		**  LDAP_DECODING_ERROR due to
632 		**  OpenLDAP 1.X's hack (see below)
633 		*/
634 
635 		LDAPLMAP.ldap_ld->ld_errno = LDAP_SUCCESS;
636 # endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
637 
638 		if (vals[0] == NULL || vals[0][0] == '\0')
639 			goto skip;
640 
641 		if (strcasecmp(attr, "gecos") == 0)
642 		{
643 			if (!bitset(NEED_FULLNAME, need) ||
644 			    strlen(vals[0]) >= sizeof(user->mbdb_fullname))
645 				goto skip;
646 
647 			sm_pwfullname(vals[0], name, user->mbdb_fullname,
648 				      sizeof(user->mbdb_fullname));
649 			need &= ~NEED_FULLNAME;
650 		}
651 		else if (strcasecmp(attr, "homeDirectory") == 0)
652 		{
653 			if (!bitset(NEED_HOMEDIR, need) ||
654 			    strlen(vals[0]) >= sizeof(user->mbdb_homedir))
655 				goto skip;
656 
657 			(void) sm_strlcpy(user->mbdb_homedir, vals[0],
658 					  sizeof(user->mbdb_homedir));
659 			need &= ~NEED_HOMEDIR;
660 		}
661 		else if (strcasecmp(attr, "loginShell") == 0)
662 		{
663 			if (!bitset(NEED_SHELL, need) ||
664 			    strlen(vals[0]) >= sizeof(user->mbdb_shell))
665 				goto skip;
666 
667 			(void) sm_strlcpy(user->mbdb_shell, vals[0],
668 					  sizeof(user->mbdb_shell));
669 			need &= ~NEED_SHELL;
670 		}
671 		else if (strcasecmp(attr, "uidNumber") == 0)
672 		{
673 			char *p;
674 
675 			if (!bitset(NEED_UID, need))
676 				goto skip;
677 
678 			for (p = vals[0]; *p != '\0'; p++)
679 			{
680 				/* allow negative numbers */
681 				if (p == vals[0] && *p == '-')
682 				{
683 					/* but not simply '-' */
684 					if (*(p + 1) == '\0')
685 						goto skip;
686 				}
687 				else if (!isascii(*p) || !isdigit(*p))
688 					goto skip;
689 			}
690 			user->mbdb_uid = atoi(vals[0]);
691 			need &= ~NEED_UID;
692 		}
693 		else if (strcasecmp(attr, "gidNumber") == 0)
694 		{
695 			char *p;
696 
697 			if (!bitset(NEED_GID, need))
698 				goto skip;
699 
700 			for (p = vals[0]; *p != '\0'; p++)
701 			{
702 				/* allow negative numbers */
703 				if (p == vals[0] && *p == '-')
704 				{
705 					/* but not simply '-' */
706 					if (*(p + 1) == '\0')
707 						goto skip;
708 				}
709 				else if (!isascii(*p) || !isdigit(*p))
710 					goto skip;
711 			}
712 			user->mbdb_gid = atoi(vals[0]);
713 			need &= ~NEED_GID;
714 		}
715 
716 skip:
717 		ldap_value_free(vals);
718 		ldap_memfree(attr);
719 	}
720 
721 	errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld);
722 
723 	/*
724 	**  We check errno != LDAP_DECODING_ERROR since
725 	**  OpenLDAP 1.X has a very ugly *undocumented*
726 	**  hack of returning this error code from
727 	**  ldap_next_attribute() if the library freed the
728 	**  ber attribute.  See:
729 	**  http://www.openldap.org/lists/openldap-devel/9901/msg00064.html
730 	*/
731 
732 	if (errno != LDAP_SUCCESS &&
733 	    errno != LDAP_DECODING_ERROR)
734 	{
735 		/* Must be an error */
736 		errno += E_LDAPBASE;
737 		ret = EX_TEMPFAIL;
738 		goto abort;
739 	}
740 
741  abort:
742 	save_errno = errno;
743 	if (attr != NULL)
744 	{
745 		ldap_memfree(attr);
746 		attr = NULL;
747 	}
748 	if (LDAPLMAP.ldap_res != NULL)
749 	{
750 		ldap_msgfree(LDAPLMAP.ldap_res);
751 		LDAPLMAP.ldap_res = NULL;
752 	}
753 	if (ret == EX_OK)
754 	{
755 		if (need == 0)
756 		{
757 			(void) sm_strlcpy(user->mbdb_name, name,
758 					  sizeof(user->mbdb_name));
759 			save_errno = 0;
760 		}
761 		else
762 		{
763 			ret = EX_NOUSER;
764 			save_errno = EINVAL;
765 		}
766 	}
767 	errno = save_errno;
768 	return ret;
769 }
770 
771 /*
772 **  MBDB_LDAP_TERMINATE -- terminate connection to the mailbox database
773 **
774 **	Parameters:
775 **		none.
776 **
777 **	Results:
778 **		none.
779 */
780 
781 static void
782 mbdb_ldap_terminate()
783 {
784 	sm_ldap_close(&LDAPLMAP);
785 }
786 # endif /* _LDAP_EXAMPLE_ */
787 #endif /* LDAPMAP */
788