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