mbdb.c revision 266692
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>
11SM_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
41typedef 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
49static int	mbdb_pw_initialize __P((char *));
50static int	mbdb_pw_lookup __P((char *name, SM_MBDB_T *user));
51static void	mbdb_pw_terminate __P((void));
52
53#if LDAPMAP
54# if _LDAP_EXAMPLE_
55static struct sm_ldap_struct LDAPLMAP;
56static int	mbdb_ldap_initialize __P((char *));
57static int	mbdb_ldap_lookup __P((char *name, SM_MBDB_T *user));
58static void	mbdb_ldap_terminate __P((void));
59# endif /* _LDAP_EXAMPLE_ */
60#endif /* LDAPMAP */
61
62static 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
73static 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
90int
91sm_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
143void
144sm_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
164int
165sm_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
187void
188sm_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
222static 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
235void
236sm_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 */
292static int
293mbdb_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
311static int
312mbdb_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 _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
362static void
363mbdb_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
431static int
432mbdb_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
485static int
486mbdb_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
709skip:
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
774static void
775mbdb_ldap_terminate()
776{
777	sm_ldap_close(&LDAPLMAP);
778}
779# endif /* _LDAP_EXAMPLE_ */
780#endif /* LDAPMAP */
781