xref: /freebsd/contrib/sendmail/libsm/mbdb.c (revision 0b3105a37d7adcadcb720112fed4dc4e8040be99)
1 /*
2  * Copyright (c) 2001-2003,2009 Proofpoint, 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.43 2014-01-08 17:03:15 ca 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 #if HESIOD && !HESIOD_ALLOW_NUMERIC_LOGIN
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 && !HESIOD_ALLOW_NUMERIC_LOGIN */
330 
331 	errno = 0;
332 	pw = getpwnam(name);
333 	if (pw == NULL)
334 	{
335 #if _FFR_USE_GETPWNAM_ERRNO
336 		/*
337 		**  Only enable this code iff
338 		**  user unknown <-> getpwnam() == NULL && errno == 0
339 		**  (i.e., errno unchanged); see the POSIX spec.
340 		*/
341 
342 		if (errno != 0)
343 			return EX_TEMPFAIL;
344 #endif /* _FFR_USE_GETPWNAM_ERRNO */
345 		return EX_NOUSER;
346 	}
347 
348 	sm_mbdb_frompw(user, pw);
349 	return EX_OK;
350 }
351 
352 /*
353 **  MBDB_PW_TERMINATE -- terminate connection to the mailbox database
354 **
355 **	Parameters:
356 **		none.
357 **
358 **	Results:
359 **		none.
360 */
361 
362 static void
363 mbdb_pw_terminate()
364 {
365 	endpwent();
366 }
367 
368 #if LDAPMAP
369 # if _LDAP_EXAMPLE_
370 /*
371 **  LDAP example implementation based on RFC 2307, "An Approach for Using
372 **  LDAP as a Network Information Service":
373 **
374 **	( nisSchema.1.0 NAME 'uidNumber'
375 **	  DESC 'An integer uniquely identifying a user in an
376 **		administrative domain'
377 **	  EQUALITY integerMatch SYNTAX 'INTEGER' SINGLE-VALUE )
378 **
379 **	( nisSchema.1.1 NAME 'gidNumber'
380 **	  DESC 'An integer uniquely identifying a group in an
381 **		administrative domain'
382 **	  EQUALITY integerMatch SYNTAX 'INTEGER' SINGLE-VALUE )
383 **
384 **	( nisSchema.1.2 NAME 'gecos'
385 **	  DESC 'The GECOS field; the common name'
386 **	  EQUALITY caseIgnoreIA5Match
387 **	  SUBSTRINGS caseIgnoreIA5SubstringsMatch
388 **	  SYNTAX 'IA5String' SINGLE-VALUE )
389 **
390 **	( nisSchema.1.3 NAME 'homeDirectory'
391 **	  DESC 'The absolute path to the home directory'
392 **	  EQUALITY caseExactIA5Match
393 **	  SYNTAX 'IA5String' SINGLE-VALUE )
394 **
395 **	( nisSchema.1.4 NAME 'loginShell'
396 **	  DESC 'The path to the login shell'
397 **	  EQUALITY caseExactIA5Match
398 **	  SYNTAX 'IA5String' SINGLE-VALUE )
399 **
400 **	( nisSchema.2.0 NAME 'posixAccount' SUP top AUXILIARY
401 **	  DESC 'Abstraction of an account with POSIX attributes'
402 **	  MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory )
403 **	  MAY ( userPassword $ loginShell $ gecos $ description ) )
404 **
405 */
406 
407 #  define MBDB_LDAP_LABEL		"MailboxDatabase"
408 
409 #  ifndef MBDB_LDAP_FILTER
410 #   define MBDB_LDAP_FILTER		"(&(objectClass=posixAccount)(uid=%0))"
411 #  endif /* MBDB_LDAP_FILTER */
412 
413 #  ifndef MBDB_DEFAULT_LDAP_BASEDN
414 #   define MBDB_DEFAULT_LDAP_BASEDN	NULL
415 #  endif /* MBDB_DEFAULT_LDAP_BASEDN */
416 
417 #  ifndef MBDB_DEFAULT_LDAP_SERVER
418 #   define MBDB_DEFAULT_LDAP_SERVER	NULL
419 #  endif /* MBDB_DEFAULT_LDAP_SERVER */
420 
421 /*
422 **  MBDB_LDAP_INITIALIZE -- initialize LDAP version
423 **
424 **	Parameters:
425 **		arg -- LDAP specification
426 **
427 **	Results:
428 **		EX_OK on success, or an EX_* code on failure.
429 */
430 
431 static int
432 mbdb_ldap_initialize(arg)
433 	char *arg;
434 {
435 	sm_ldap_clear(&LDAPLMAP);
436 	LDAPLMAP.ldap_base = MBDB_DEFAULT_LDAP_BASEDN;
437 	LDAPLMAP.ldap_host = MBDB_DEFAULT_LDAP_SERVER;
438 	LDAPLMAP.ldap_filter = MBDB_LDAP_FILTER;
439 
440 	/* Only want one match */
441 	LDAPLMAP.ldap_sizelimit = 1;
442 
443 	/* interpolate new ldap_base and ldap_host from arg if given */
444 	if (arg != NULL && *arg != '\0')
445 	{
446 		char *new;
447 		char *sep;
448 		size_t len;
449 
450 		len = strlen(arg) + 1;
451 		new = sm_malloc(len);
452 		if (new == NULL)
453 			return EX_TEMPFAIL;
454 		(void) sm_strlcpy(new, arg, len);
455 		sep = strrchr(new, '@');
456 		if (sep != NULL)
457 		{
458 			*sep++ = '\0';
459 			LDAPLMAP.ldap_host = sep;
460 		}
461 		LDAPLMAP.ldap_base = new;
462 	}
463 	return EX_OK;
464 }
465 
466 
467 /*
468 **  MBDB_LDAP_LOOKUP -- look up a local mail recipient, given name
469 **
470 **	Parameters:
471 **		name -- name of local mail recipient
472 **		user -- pointer to structure to fill in on success
473 **
474 **	Results:
475 **		On success, fill in *user and return EX_OK.
476 **		Failure: EX_NOUSER.
477 */
478 
479 #define NEED_FULLNAME	0x01
480 #define NEED_HOMEDIR	0x02
481 #define NEED_SHELL	0x04
482 #define NEED_UID	0x08
483 #define NEED_GID	0x10
484 
485 static int
486 mbdb_ldap_lookup(name, user)
487 	char *name;
488 	SM_MBDB_T *user;
489 {
490 	int msgid;
491 	int need;
492 	int ret;
493 	int save_errno;
494 	LDAPMessage *entry;
495 	BerElement *ber;
496 	char *attr = NULL;
497 
498 	if (strlen(name) >= sizeof(user->mbdb_name))
499 	{
500 		errno = EINVAL;
501 		return EX_NOUSER;
502 	}
503 
504 	if (LDAPLMAP.ldap_filter == NULL)
505 	{
506 		/* map not initialized, but don't have arg here */
507 		errno = EFAULT;
508 		return EX_TEMPFAIL;
509 	}
510 
511 	if (LDAPLMAP.ldap_pid != getpid())
512 	{
513 		/* re-open map in this child process */
514 		LDAPLMAP.ldap_ld = NULL;
515 	}
516 
517 	if (LDAPLMAP.ldap_ld == NULL)
518 	{
519 		/* map not open, try to open now */
520 		if (!sm_ldap_start(MBDB_LDAP_LABEL, &LDAPLMAP))
521 			return EX_TEMPFAIL;
522 	}
523 
524 	sm_ldap_setopts(LDAPLMAP.ldap_ld, &LDAPLMAP);
525 	msgid = sm_ldap_search(&LDAPLMAP, name);
526 	if (msgid == -1)
527 	{
528 		save_errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld) + E_LDAPBASE;
529 #  ifdef LDAP_SERVER_DOWN
530 		if (errno == LDAP_SERVER_DOWN)
531 		{
532 			/* server disappeared, try reopen on next search */
533 			sm_ldap_close(&LDAPLMAP);
534 		}
535 #  endif /* LDAP_SERVER_DOWN */
536 		errno = save_errno;
537 		return EX_TEMPFAIL;
538 	}
539 
540 	/* Get results */
541 	ret = ldap_result(LDAPLMAP.ldap_ld, msgid, 1,
542 			  (LDAPLMAP.ldap_timeout.tv_sec == 0 ? NULL :
543 			   &(LDAPLMAP.ldap_timeout)),
544 			  &(LDAPLMAP.ldap_res));
545 
546 	if (ret != LDAP_RES_SEARCH_RESULT &&
547 	    ret != LDAP_RES_SEARCH_ENTRY)
548 	{
549 		if (ret == 0)
550 			errno = ETIMEDOUT;
551 		else
552 			errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld);
553 		ret = EX_TEMPFAIL;
554 		goto abort;
555 	}
556 
557 	entry = ldap_first_entry(LDAPLMAP.ldap_ld, LDAPLMAP.ldap_res);
558 	if (entry == NULL)
559 	{
560 		int rc;
561 
562 		/*
563 		**  We may have gotten an LDAP_RES_SEARCH_RESULT response
564 		**  with an error inside it, so we have to extract that
565 		**  with ldap_parse_result().  This can happen when talking
566 		**  to an LDAP proxy whose backend has gone down.
567 		*/
568 
569 		save_errno = ldap_parse_result(LDAPLMAP.ldap_ld,
570 					       LDAPLMAP.ldap_res, &rc, NULL,
571 					       NULL, NULL, NULL, 0);
572 		if (save_errno == LDAP_SUCCESS)
573 			save_errno = rc;
574 		if (save_errno == LDAP_SUCCESS)
575 		{
576 			errno = ENOENT;
577 			ret = EX_NOUSER;
578 		}
579 		else
580 		{
581 			errno = save_errno;
582 			ret = EX_TEMPFAIL;
583 		}
584 		goto abort;
585 	}
586 
587 # if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
588 	/*
589 	**  Reset value to prevent lingering
590 	**  LDAP_DECODING_ERROR due to
591 	**  OpenLDAP 1.X's hack (see below)
592 	*/
593 
594 	LDAPLMAP.ldap_ld->ld_errno = LDAP_SUCCESS;
595 # endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
596 
597 	ret = EX_OK;
598 	need = NEED_FULLNAME|NEED_HOMEDIR|NEED_SHELL|NEED_UID|NEED_GID;
599 	for (attr = ldap_first_attribute(LDAPLMAP.ldap_ld, entry, &ber);
600 	     attr != NULL;
601 	     attr = ldap_next_attribute(LDAPLMAP.ldap_ld, entry, ber))
602 	{
603 		char **vals;
604 
605 		vals = ldap_get_values(LDAPLMAP.ldap_ld, entry, attr);
606 		if (vals == NULL)
607 		{
608 			errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld);
609 			if (errno == LDAP_SUCCESS)
610 			{
611 				ldap_memfree(attr);
612 				continue;
613 			}
614 
615 			/* Must be an error */
616 			errno += E_LDAPBASE;
617 			ret = EX_TEMPFAIL;
618 			goto abort;
619 		}
620 
621 # if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
622 		/*
623 		**  Reset value to prevent lingering
624 		**  LDAP_DECODING_ERROR due to
625 		**  OpenLDAP 1.X's hack (see below)
626 		*/
627 
628 		LDAPLMAP.ldap_ld->ld_errno = LDAP_SUCCESS;
629 # endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
630 
631 		if (vals[0] == NULL || vals[0][0] == '\0')
632 			goto skip;
633 
634 		if (strcasecmp(attr, "gecos") == 0)
635 		{
636 			if (!bitset(NEED_FULLNAME, need) ||
637 			    strlen(vals[0]) >= sizeof(user->mbdb_fullname))
638 				goto skip;
639 
640 			sm_pwfullname(vals[0], name, user->mbdb_fullname,
641 				      sizeof(user->mbdb_fullname));
642 			need &= ~NEED_FULLNAME;
643 		}
644 		else if (strcasecmp(attr, "homeDirectory") == 0)
645 		{
646 			if (!bitset(NEED_HOMEDIR, need) ||
647 			    strlen(vals[0]) >= sizeof(user->mbdb_homedir))
648 				goto skip;
649 
650 			(void) sm_strlcpy(user->mbdb_homedir, vals[0],
651 					  sizeof(user->mbdb_homedir));
652 			need &= ~NEED_HOMEDIR;
653 		}
654 		else if (strcasecmp(attr, "loginShell") == 0)
655 		{
656 			if (!bitset(NEED_SHELL, need) ||
657 			    strlen(vals[0]) >= sizeof(user->mbdb_shell))
658 				goto skip;
659 
660 			(void) sm_strlcpy(user->mbdb_shell, vals[0],
661 					  sizeof(user->mbdb_shell));
662 			need &= ~NEED_SHELL;
663 		}
664 		else if (strcasecmp(attr, "uidNumber") == 0)
665 		{
666 			char *p;
667 
668 			if (!bitset(NEED_UID, need))
669 				goto skip;
670 
671 			for (p = vals[0]; *p != '\0'; p++)
672 			{
673 				/* allow negative numbers */
674 				if (p == vals[0] && *p == '-')
675 				{
676 					/* but not simply '-' */
677 					if (*(p + 1) == '\0')
678 						goto skip;
679 				}
680 				else if (!isascii(*p) || !isdigit(*p))
681 					goto skip;
682 			}
683 			user->mbdb_uid = atoi(vals[0]);
684 			need &= ~NEED_UID;
685 		}
686 		else if (strcasecmp(attr, "gidNumber") == 0)
687 		{
688 			char *p;
689 
690 			if (!bitset(NEED_GID, need))
691 				goto skip;
692 
693 			for (p = vals[0]; *p != '\0'; p++)
694 			{
695 				/* allow negative numbers */
696 				if (p == vals[0] && *p == '-')
697 				{
698 					/* but not simply '-' */
699 					if (*(p + 1) == '\0')
700 						goto skip;
701 				}
702 				else if (!isascii(*p) || !isdigit(*p))
703 					goto skip;
704 			}
705 			user->mbdb_gid = atoi(vals[0]);
706 			need &= ~NEED_GID;
707 		}
708 
709 skip:
710 		ldap_value_free(vals);
711 		ldap_memfree(attr);
712 	}
713 
714 	errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld);
715 
716 	/*
717 	**  We check errno != LDAP_DECODING_ERROR since
718 	**  OpenLDAP 1.X has a very ugly *undocumented*
719 	**  hack of returning this error code from
720 	**  ldap_next_attribute() if the library freed the
721 	**  ber attribute.  See:
722 	**  http://www.openldap.org/lists/openldap-devel/9901/msg00064.html
723 	*/
724 
725 	if (errno != LDAP_SUCCESS &&
726 	    errno != LDAP_DECODING_ERROR)
727 	{
728 		/* Must be an error */
729 		errno += E_LDAPBASE;
730 		ret = EX_TEMPFAIL;
731 		goto abort;
732 	}
733 
734  abort:
735 	save_errno = errno;
736 	if (attr != NULL)
737 	{
738 		ldap_memfree(attr);
739 		attr = NULL;
740 	}
741 	if (LDAPLMAP.ldap_res != NULL)
742 	{
743 		ldap_msgfree(LDAPLMAP.ldap_res);
744 		LDAPLMAP.ldap_res = NULL;
745 	}
746 	if (ret == EX_OK)
747 	{
748 		if (need == 0)
749 		{
750 			(void) sm_strlcpy(user->mbdb_name, name,
751 					  sizeof(user->mbdb_name));
752 			save_errno = 0;
753 		}
754 		else
755 		{
756 			ret = EX_NOUSER;
757 			save_errno = EINVAL;
758 		}
759 	}
760 	errno = save_errno;
761 	return ret;
762 }
763 
764 /*
765 **  MBDB_LDAP_TERMINATE -- terminate connection to the mailbox database
766 **
767 **	Parameters:
768 **		none.
769 **
770 **	Results:
771 **		none.
772 */
773 
774 static void
775 mbdb_ldap_terminate()
776 {
777 	sm_ldap_close(&LDAPLMAP);
778 }
779 # endif /* _LDAP_EXAMPLE_ */
780 #endif /* LDAPMAP */
781