]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/sendmail/libsm/mbdb.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / contrib / sendmail / libsm / mbdb.c
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 */