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