]> CyberLeo.Net >> Repos - FreeBSD/releng/7.2.git/blob - contrib/sendmail/src/map.c
Create releng/7.2 from stable/7 in preparation for 7.2-RELEASE.
[FreeBSD/releng/7.2.git] / contrib / sendmail / src / map.c
1 /*
2  * Copyright (c) 1998-2007 Sendmail, Inc. and its suppliers.
3  *      All rights reserved.
4  * Copyright (c) 1992, 1995-1997 Eric P. Allman.  All rights reserved.
5  * Copyright (c) 1992, 1993
6  *      The Regents of the University of California.  All rights reserved.
7  *
8  * By using this file, you agree to the terms and conditions set
9  * forth in the LICENSE file which can be found at the top level of
10  * the sendmail distribution.
11  *
12  */
13
14 #include <sendmail.h>
15
16 SM_RCSID("@(#)$Id: map.c,v 8.699 2007/10/10 00:06:45 ca Exp $")
17
18 #if LDAPMAP
19 # include <sm/ldap.h>
20 #endif /* LDAPMAP */
21
22 #if NDBM
23 # include <ndbm.h>
24 # ifdef R_FIRST
25   ERROR README: You are running the Berkeley DB version of ndbm.h.  See
26   ERROR README: the README file about tweaking Berkeley DB so it can
27   ERROR README: coexist with NDBM, or delete -DNDBM from the Makefile
28   ERROR README: and use -DNEWDB instead.
29 # endif /* R_FIRST */
30 #endif /* NDBM */
31 #if NEWDB
32 # include "sm/bdb.h"
33 #endif /* NEWDB */
34 #if NIS
35   struct dom_binding;   /* forward reference needed on IRIX */
36 # include <rpcsvc/ypclnt.h>
37 # if NDBM
38 #  define NDBM_YP_COMPAT        /* create YP-compatible NDBM files */
39 # endif /* NDBM */
40 #endif /* NIS */
41
42 #include "map.h"
43
44 #if NEWDB
45 # if DB_VERSION_MAJOR < 2
46 static bool     db_map_open __P((MAP *, int, char *, DBTYPE, const void *));
47 # endif /* DB_VERSION_MAJOR < 2 */
48 # if DB_VERSION_MAJOR == 2
49 static bool     db_map_open __P((MAP *, int, char *, DBTYPE, DB_INFO *));
50 # endif /* DB_VERSION_MAJOR == 2 */
51 # if DB_VERSION_MAJOR > 2
52 static bool     db_map_open __P((MAP *, int, char *, DBTYPE, void **));
53 # endif /* DB_VERSION_MAJOR > 2 */
54 #endif /* NEWDB */
55 static bool     extract_canonname __P((char *, char *, char *, char[], int));
56 static void     map_close __P((STAB *, int));
57 static void     map_init __P((STAB *, int));
58 #ifdef LDAPMAP
59 static STAB *   ldapmap_findconn __P((SM_LDAP_STRUCT *));
60 #endif /* LDAPMAP */
61 #if NISPLUS
62 static bool     nisplus_getcanonname __P((char *, int, int *));
63 #endif /* NISPLUS */
64 #if NIS
65 static bool     nis_getcanonname __P((char *, int, int *));
66 #endif /* NIS */
67 #if NETINFO
68 static bool     ni_getcanonname __P((char *, int, int *));
69 #endif /* NETINFO */
70 static bool     text_getcanonname __P((char *, int, int *));
71 #if SOCKETMAP
72 static STAB     *socket_map_findconn __P((const char*));
73
74 /* XXX arbitrary limit for sanity */
75 # define SOCKETMAP_MAXL 1000000
76 #endif /* SOCKETMAP */
77
78 /* default error message for trying to open a map in write mode */
79 #ifdef ENOSYS
80 # define SM_EMAPCANTWRITE       ENOSYS
81 #else /* ENOSYS */
82 # ifdef EFTYPE
83 #  define SM_EMAPCANTWRITE      EFTYPE
84 # else /* EFTYPE */
85 #  define SM_EMAPCANTWRITE      ENXIO
86 # endif /* EFTYPE */
87 #endif /* ENOSYS */
88
89 /*
90 **  MAP.C -- implementations for various map classes.
91 **
92 **      Each map class implements a series of functions:
93 **
94 **      bool map_parse(MAP *map, char *args)
95 **              Parse the arguments from the config file.  Return true
96 **              if they were ok, false otherwise.  Fill in map with the
97 **              values.
98 **
99 **      char *map_lookup(MAP *map, char *key, char **args, int *pstat)
100 **              Look up the key in the given map.  If found, do any
101 **              rewriting the map wants (including "args" if desired)
102 **              and return the value.  Set *pstat to the appropriate status
103 **              on error and return NULL.  Args will be NULL if called
104 **              from the alias routines, although this should probably
105 **              not be relied upon.  It is suggested you call map_rewrite
106 **              to return the results -- it takes care of null termination
107 **              and uses a dynamically expanded buffer as needed.
108 **
109 **      void map_store(MAP *map, char *key, char *value)
110 **              Store the key:value pair in the map.
111 **
112 **      bool map_open(MAP *map, int mode)
113 **              Open the map for the indicated mode.  Mode should
114 **              be either O_RDONLY or O_RDWR.  Return true if it
115 **              was opened successfully, false otherwise.  If the open
116 **              failed and the MF_OPTIONAL flag is not set, it should
117 **              also print an error.  If the MF_ALIAS bit is set
118 **              and this map class understands the @:@ convention, it
119 **              should call aliaswait() before returning.
120 **
121 **      void map_close(MAP *map)
122 **              Close the map.
123 **
124 **      This file also includes the implementation for getcanonname.
125 **      It is currently implemented in a pretty ad-hoc manner; it ought
126 **      to be more properly integrated into the map structure.
127 */
128
129 #if O_EXLOCK && HASFLOCK && !BOGUS_O_EXCL
130 # define LOCK_ON_OPEN   1       /* we can open/create a locked file */
131 #else /* O_EXLOCK && HASFLOCK && !BOGUS_O_EXCL */
132 # define LOCK_ON_OPEN   0       /* no such luck -- bend over backwards */
133 #endif /* O_EXLOCK && HASFLOCK && !BOGUS_O_EXCL */
134
135 /*
136 **  MAP_PARSEARGS -- parse config line arguments for database lookup
137 **
138 **      This is a generic version of the map_parse method.
139 **
140 **      Parameters:
141 **              map -- the map being initialized.
142 **              ap -- a pointer to the args on the config line.
143 **
144 **      Returns:
145 **              true -- if everything parsed OK.
146 **              false -- otherwise.
147 **
148 **      Side Effects:
149 **              null terminates the filename; stores it in map
150 */
151
152 bool
153 map_parseargs(map, ap)
154         MAP *map;
155         char *ap;
156 {
157         register char *p = ap;
158
159         /*
160         **  There is no check whether there is really an argument,
161         **  but that's not important enough to warrant extra code.
162         */
163
164         map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL;
165         map->map_spacesub = SpaceSub;   /* default value */
166         for (;;)
167         {
168                 while (isascii(*p) && isspace(*p))
169                         p++;
170                 if (*p != '-')
171                         break;
172                 switch (*++p)
173                 {
174                   case 'N':
175                         map->map_mflags |= MF_INCLNULL;
176                         map->map_mflags &= ~MF_TRY0NULL;
177                         break;
178
179                   case 'O':
180                         map->map_mflags &= ~MF_TRY1NULL;
181                         break;
182
183                   case 'o':
184                         map->map_mflags |= MF_OPTIONAL;
185                         break;
186
187                   case 'f':
188                         map->map_mflags |= MF_NOFOLDCASE;
189                         break;
190
191                   case 'm':
192                         map->map_mflags |= MF_MATCHONLY;
193                         break;
194
195                   case 'A':
196                         map->map_mflags |= MF_APPEND;
197                         break;
198
199                   case 'q':
200                         map->map_mflags |= MF_KEEPQUOTES;
201                         break;
202
203                   case 'a':
204                         map->map_app = ++p;
205                         break;
206
207                   case 'T':
208                         map->map_tapp = ++p;
209                         break;
210
211                   case 'k':
212                         while (isascii(*++p) && isspace(*p))
213                                 continue;
214                         map->map_keycolnm = p;
215                         break;
216
217                   case 'v':
218                         while (isascii(*++p) && isspace(*p))
219                                 continue;
220                         map->map_valcolnm = p;
221                         break;
222
223                   case 'z':
224                         if (*++p != '\\')
225                                 map->map_coldelim = *p;
226                         else
227                         {
228                                 switch (*++p)
229                                 {
230                                   case 'n':
231                                         map->map_coldelim = '\n';
232                                         break;
233
234                                   case 't':
235                                         map->map_coldelim = '\t';
236                                         break;
237
238                                   default:
239                                         map->map_coldelim = '\\';
240                                 }
241                         }
242                         break;
243
244                   case 't':
245                         map->map_mflags |= MF_NODEFER;
246                         break;
247
248
249                   case 'S':
250                         map->map_spacesub = *++p;
251                         break;
252
253                   case 'D':
254                         map->map_mflags |= MF_DEFER;
255                         break;
256
257                   default:
258                         syserr("Illegal option %c map %s", *p, map->map_mname);
259                         break;
260                 }
261                 while (*p != '\0' && !(isascii(*p) && isspace(*p)))
262                         p++;
263                 if (*p != '\0')
264                         *p++ = '\0';
265         }
266         if (map->map_app != NULL)
267                 map->map_app = newstr(map->map_app);
268         if (map->map_tapp != NULL)
269                 map->map_tapp = newstr(map->map_tapp);
270         if (map->map_keycolnm != NULL)
271                 map->map_keycolnm = newstr(map->map_keycolnm);
272         if (map->map_valcolnm != NULL)
273                 map->map_valcolnm = newstr(map->map_valcolnm);
274
275         if (*p != '\0')
276         {
277                 map->map_file = p;
278                 while (*p != '\0' && !(isascii(*p) && isspace(*p)))
279                         p++;
280                 if (*p != '\0')
281                         *p++ = '\0';
282                 map->map_file = newstr(map->map_file);
283         }
284
285         while (*p != '\0' && isascii(*p) && isspace(*p))
286                 p++;
287         if (*p != '\0')
288                 map->map_rebuild = newstr(p);
289
290         if (map->map_file == NULL &&
291             !bitset(MCF_OPTFILE, map->map_class->map_cflags))
292         {
293                 syserr("No file name for %s map %s",
294                         map->map_class->map_cname, map->map_mname);
295                 return false;
296         }
297         return true;
298 }
299 /*
300 **  MAP_REWRITE -- rewrite a database key, interpolating %n indications.
301 **
302 **      It also adds the map_app string.  It can be used as a utility
303 **      in the map_lookup method.
304 **
305 **      Parameters:
306 **              map -- the map that causes this.
307 **              s -- the string to rewrite, NOT necessarily null terminated.
308 **              slen -- the length of s.
309 **              av -- arguments to interpolate into buf.
310 **
311 **      Returns:
312 **              Pointer to rewritten result.  This is static data that
313 **              should be copied if it is to be saved!
314 */
315
316 char *
317 map_rewrite(map, s, slen, av)
318         register MAP *map;
319         register const char *s;
320         size_t slen;
321         char **av;
322 {
323         register char *bp;
324         register char c;
325         char **avp;
326         register char *ap;
327         size_t l;
328         size_t len;
329         static size_t buflen = 0;
330         static char *buf = NULL;
331
332         if (tTd(39, 1))
333         {
334                 sm_dprintf("map_rewrite(%.*s), av =", (int) slen, s);
335                 if (av == NULL)
336                         sm_dprintf(" (nullv)");
337                 else
338                 {
339                         for (avp = av; *avp != NULL; avp++)
340                                 sm_dprintf("\n\t%s", *avp);
341                 }
342                 sm_dprintf("\n");
343         }
344
345         /* count expected size of output (can safely overestimate) */
346         l = len = slen;
347         if (av != NULL)
348         {
349                 const char *sp = s;
350
351                 while (l-- > 0 && (c = *sp++) != '\0')
352                 {
353                         if (c != '%')
354                                 continue;
355                         if (l-- <= 0)
356                                 break;
357                         c = *sp++;
358                         if (!(isascii(c) && isdigit(c)))
359                                 continue;
360                         for (avp = av; --c >= '0' && *avp != NULL; avp++)
361                                 continue;
362                         if (*avp == NULL)
363                                 continue;
364                         len += strlen(*avp);
365                 }
366         }
367         if (map->map_app != NULL)
368                 len += strlen(map->map_app);
369         if (buflen < ++len)
370         {
371                 /* need to malloc additional space */
372                 buflen = len;
373                 if (buf != NULL)
374                         sm_free(buf);
375                 buf = sm_pmalloc_x(buflen);
376         }
377
378         bp = buf;
379         if (av == NULL)
380         {
381                 memmove(bp, s, slen);
382                 bp += slen;
383
384                 /* assert(len > slen); */
385                 len -= slen;
386         }
387         else
388         {
389                 while (slen-- > 0 && (c = *s++) != '\0')
390                 {
391                         if (c != '%')
392                         {
393   pushc:
394                                 if (len-- <= 1)
395                                      break;
396                                 *bp++ = c;
397                                 continue;
398                         }
399                         if (slen-- <= 0 || (c = *s++) == '\0')
400                                 c = '%';
401                         if (c == '%')
402                                 goto pushc;
403                         if (!(isascii(c) && isdigit(c)))
404                         {
405                                 if (len-- <= 1)
406                                      break;
407                                 *bp++ = '%';
408                                 goto pushc;
409                         }
410                         for (avp = av; --c >= '0' && *avp != NULL; avp++)
411                                 continue;
412                         if (*avp == NULL)
413                                 continue;
414
415                         /* transliterate argument into output string */
416                         for (ap = *avp; (c = *ap++) != '\0' && len > 0; --len)
417                                 *bp++ = c;
418                 }
419         }
420         if (map->map_app != NULL && len > 0)
421                 (void) sm_strlcpy(bp, map->map_app, len);
422         else
423                 *bp = '\0';
424         if (tTd(39, 1))
425                 sm_dprintf("map_rewrite => %s\n", buf);
426         return buf;
427 }
428 /*
429 **  INITMAPS -- rebuild alias maps
430 **
431 **      Parameters:
432 **              none.
433 **
434 **      Returns:
435 **              none.
436 */
437
438 void
439 initmaps()
440 {
441 #if XDEBUG
442         checkfd012("entering initmaps");
443 #endif /* XDEBUG */
444         stabapply(map_init, 0);
445 #if XDEBUG
446         checkfd012("exiting initmaps");
447 #endif /* XDEBUG */
448 }
449 /*
450 **  MAP_INIT -- rebuild a map
451 **
452 **      Parameters:
453 **              s -- STAB entry: if map: try to rebuild
454 **              unused -- unused variable
455 **
456 **      Returns:
457 **              none.
458 **
459 **      Side Effects:
460 **              will close already open rebuildable map.
461 */
462
463 /* ARGSUSED1 */
464 static void
465 map_init(s, unused)
466         register STAB *s;
467         int unused;
468 {
469         register MAP *map;
470
471         /* has to be a map */
472         if (s->s_symtype != ST_MAP)
473                 return;
474
475         map = &s->s_map;
476         if (!bitset(MF_VALID, map->map_mflags))
477                 return;
478
479         if (tTd(38, 2))
480                 sm_dprintf("map_init(%s:%s, %s)\n",
481                         map->map_class->map_cname == NULL ? "NULL" :
482                                 map->map_class->map_cname,
483                         map->map_mname == NULL ? "NULL" : map->map_mname,
484                         map->map_file == NULL ? "NULL" : map->map_file);
485
486         if (!bitset(MF_ALIAS, map->map_mflags) ||
487             !bitset(MCF_REBUILDABLE, map->map_class->map_cflags))
488         {
489                 if (tTd(38, 3))
490                         sm_dprintf("\tnot rebuildable\n");
491                 return;
492         }
493
494         /* if already open, close it (for nested open) */
495         if (bitset(MF_OPEN, map->map_mflags))
496         {
497                 map->map_mflags |= MF_CLOSING;
498                 map->map_class->map_close(map);
499                 map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
500         }
501
502         (void) rebuildaliases(map, false);
503         return;
504 }
505 /*
506 **  OPENMAP -- open a map
507 **
508 **      Parameters:
509 **              map -- map to open (it must not be open).
510 **
511 **      Returns:
512 **              whether open succeeded.
513 */
514
515 bool
516 openmap(map)
517         MAP *map;
518 {
519         bool restore = false;
520         bool savehold = HoldErrs;
521         bool savequick = QuickAbort;
522         int saveerrors = Errors;
523
524         if (!bitset(MF_VALID, map->map_mflags))
525                 return false;
526
527         /* better safe than sorry... */
528         if (bitset(MF_OPEN, map->map_mflags))
529                 return true;
530
531         /* Don't send a map open error out via SMTP */
532         if ((OnlyOneError || QuickAbort) &&
533             (OpMode == MD_SMTP || OpMode == MD_DAEMON))
534         {
535                 restore = true;
536                 HoldErrs = true;
537                 QuickAbort = false;
538         }
539
540         errno = 0;
541         if (map->map_class->map_open(map, O_RDONLY))
542         {
543                 if (tTd(38, 4))
544                         sm_dprintf("openmap()\t%s:%s %s: valid\n",
545                                 map->map_class->map_cname == NULL ? "NULL" :
546                                         map->map_class->map_cname,
547                                 map->map_mname == NULL ? "NULL" :
548                                         map->map_mname,
549                                 map->map_file == NULL ? "NULL" :
550                                         map->map_file);
551                 map->map_mflags |= MF_OPEN;
552                 map->map_pid = CurrentPid;
553         }
554         else
555         {
556                 if (tTd(38, 4))
557                         sm_dprintf("openmap()\t%s:%s %s: invalid%s%s\n",
558                                 map->map_class->map_cname == NULL ? "NULL" :
559                                         map->map_class->map_cname,
560                                 map->map_mname == NULL ? "NULL" :
561                                         map->map_mname,
562                                 map->map_file == NULL ? "NULL" :
563                                         map->map_file,
564                                 errno == 0 ? "" : ": ",
565                                 errno == 0 ? "" : sm_errstring(errno));
566                 if (!bitset(MF_OPTIONAL, map->map_mflags))
567                 {
568                         extern MAPCLASS BogusMapClass;
569
570                         map->map_orgclass = map->map_class;
571                         map->map_class = &BogusMapClass;
572                         map->map_mflags |= MF_OPEN|MF_OPENBOGUS;
573                         map->map_pid = CurrentPid;
574                 }
575                 else
576                 {
577                         /* don't try again */
578                         map->map_mflags &= ~MF_VALID;
579                 }
580         }
581
582         if (restore)
583         {
584                 Errors = saveerrors;
585                 HoldErrs = savehold;
586                 QuickAbort = savequick;
587         }
588
589         return bitset(MF_OPEN, map->map_mflags);
590 }
591 /*
592 **  CLOSEMAPS -- close all open maps opened by the current pid.
593 **
594 **      Parameters:
595 **              bogus -- only close bogus maps.
596 **
597 **      Returns:
598 **              none.
599 */
600
601 void
602 closemaps(bogus)
603         bool bogus;
604 {
605         stabapply(map_close, bogus);
606 }
607 /*
608 **  MAP_CLOSE -- close a map opened by the current pid.
609 **
610 **      Parameters:
611 **              s -- STAB entry: if map: try to close
612 **              bogus -- only close bogus maps or MCF_NOTPERSIST maps.
613 **
614 **      Returns:
615 **              none.
616 */
617
618 /* ARGSUSED1 */
619 static void
620 map_close(s, bogus)
621         register STAB *s;
622         int bogus;      /* int because of stabapply(), used as bool */
623 {
624         MAP *map;
625         extern MAPCLASS BogusMapClass;
626
627         if (s->s_symtype != ST_MAP)
628                 return;
629
630         map = &s->s_map;
631
632         /*
633         **  close the map iff:
634         **  it is valid and open and opened by this process
635         **  and (!bogus or it's a bogus map or it is not persistent)
636         **  negate this: return iff
637         **  it is not valid or it is not open or not opened by this process
638         **  or (bogus and it's not a bogus map and it's not not-persistent)
639         */
640
641         if (!bitset(MF_VALID, map->map_mflags) ||
642             !bitset(MF_OPEN, map->map_mflags) ||
643             bitset(MF_CLOSING, map->map_mflags) ||
644             map->map_pid != CurrentPid ||
645             (bogus && map->map_class != &BogusMapClass &&
646              !bitset(MCF_NOTPERSIST, map->map_class->map_cflags)))
647                 return;
648
649         if (map->map_class == &BogusMapClass && map->map_orgclass != NULL &&
650             map->map_orgclass != &BogusMapClass)
651                 map->map_class = map->map_orgclass;
652         if (tTd(38, 5))
653                 sm_dprintf("closemaps: closing %s (%s)\n",
654                         map->map_mname == NULL ? "NULL" : map->map_mname,
655                         map->map_file == NULL ? "NULL" : map->map_file);
656
657         if (!bitset(MF_OPENBOGUS, map->map_mflags))
658         {
659                 map->map_mflags |= MF_CLOSING;
660                 map->map_class->map_close(map);
661         }
662         map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_OPENBOGUS|MF_CLOSING);
663 }
664
665 #if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN)
666 extern int getdomainname();
667
668 /* this is mainly for backward compatibility in Sun environment */
669 static char *
670 sun_init_domain()
671 {
672         /*
673         **  Get the domain name from the kernel.
674         **  If it does not start with a leading dot, then remove
675         **  the first component.  Since leading dots are funny Unix
676         **  files, we treat a leading "+" the same as a leading dot.
677         **  Finally, force there to be at least one dot in the domain name
678         **  (i.e. top-level domains are not allowed, like "com", must be
679         **  something like "sun.com").
680         */
681
682         char buf[MAXNAME];
683         char *period, *autodomain;
684
685         if (getdomainname(buf, sizeof buf) < 0)
686                 return NULL;
687
688         if (buf[0] == '\0')
689                 return NULL;
690
691         if (tTd(0, 20))
692                 printf("domainname = %s\n", buf);
693
694         if (buf[0] == '+')
695                 buf[0] = '.';
696         period = strchr(buf, '.');
697         if (period == NULL)
698                 autodomain = buf;
699         else
700                 autodomain = period + 1;
701         if (strchr(autodomain, '.') == NULL)
702                 return newstr(buf);
703         else
704                 return newstr(autodomain);
705 }
706 #endif /* SUN_EXTENSIONS && SUN_INIT_DOMAIN */
707
708 /*
709 **  GETCANONNAME -- look up name using service switch
710 **
711 **      Parameters:
712 **              host -- the host name to look up.
713 **              hbsize -- the size of the host buffer.
714 **              trymx -- if set, try MX records.
715 **              pttl -- pointer to return TTL (can be NULL).
716 **
717 **      Returns:
718 **              true -- if the host was found.
719 **              false -- otherwise.
720 */
721
722 bool
723 getcanonname(host, hbsize, trymx, pttl)
724         char *host;
725         int hbsize;
726         bool trymx;
727         int *pttl;
728 {
729         int nmaps;
730         int mapno;
731         bool found = false;
732         bool got_tempfail = false;
733         auto int status;
734         char *maptype[MAXMAPSTACK];
735         short mapreturn[MAXMAPACTIONS];
736 #if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN)
737         bool should_try_nis_domain = false;
738         static char *nis_domain = NULL;
739 #endif
740
741         nmaps = switch_map_find("hosts", maptype, mapreturn);
742         if (pttl != 0)
743                 *pttl = SM_DEFAULT_TTL;
744         for (mapno = 0; mapno < nmaps; mapno++)
745         {
746                 int i;
747
748                 if (tTd(38, 20))
749                         sm_dprintf("getcanonname(%s), trying %s\n",
750                                 host, maptype[mapno]);
751                 if (strcmp("files", maptype[mapno]) == 0)
752                 {
753                         found = text_getcanonname(host, hbsize, &status);
754                 }
755 #if NIS
756                 else if (strcmp("nis", maptype[mapno]) == 0)
757                 {
758                         found = nis_getcanonname(host, hbsize, &status);
759 # if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN)
760                         if (nis_domain == NULL)
761                                 nis_domain = sun_init_domain();
762 # endif /* defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN) */
763                 }
764 #endif /* NIS */
765 #if NISPLUS
766                 else if (strcmp("nisplus", maptype[mapno]) == 0)
767                 {
768                         found = nisplus_getcanonname(host, hbsize, &status);
769 # if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN)
770                         if (nis_domain == NULL)
771                                 nis_domain = sun_init_domain();
772 # endif /* defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN) */
773                 }
774 #endif /* NISPLUS */
775 #if NAMED_BIND
776                 else if (strcmp("dns", maptype[mapno]) == 0)
777                 {
778                         found = dns_getcanonname(host, hbsize, trymx, &status,                                                   pttl);
779                 }
780 #endif /* NAMED_BIND */
781 #if NETINFO
782                 else if (strcmp("netinfo", maptype[mapno]) == 0)
783                 {
784                         found = ni_getcanonname(host, hbsize, &status);
785                 }
786 #endif /* NETINFO */
787                 else
788                 {
789                         found = false;
790                         status = EX_UNAVAILABLE;
791                 }
792
793                 /*
794                 **  Heuristic: if $m is not set, we are running during system
795                 **  startup.  In this case, when a name is apparently found
796                 **  but has no dot, treat is as not found.  This avoids
797                 **  problems if /etc/hosts has no FQDN but is listed first
798                 **  in the service switch.
799                 */
800
801                 if (found &&
802                     (macvalue('m', CurEnv) != NULL || strchr(host, '.') != NULL))
803                         break;
804
805 #if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN)
806                 if (found)
807                         should_try_nis_domain = true;
808                 /* but don't break, as we need to try all methods first */
809 #endif /* defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN) */
810
811                 /* see if we should continue */
812                 if (status == EX_TEMPFAIL)
813                 {
814                         i = MA_TRYAGAIN;
815                         got_tempfail = true;
816                 }
817                 else if (status == EX_NOTFOUND)
818                         i = MA_NOTFOUND;
819                 else
820                         i = MA_UNAVAIL;
821                 if (bitset(1 << mapno, mapreturn[i]))
822                         break;
823         }
824
825         if (found)
826         {
827                 char *d;
828
829                 if (tTd(38, 20))
830                         sm_dprintf("getcanonname(%s), found\n", host);
831
832                 /*
833                 **  If returned name is still single token, compensate
834                 **  by tagging on $m.  This is because some sites set
835                 **  up their DNS or NIS databases wrong.
836                 */
837
838                 if ((d = strchr(host, '.')) == NULL || d[1] == '\0')
839                 {
840                         d = macvalue('m', CurEnv);
841                         if (d != NULL &&
842                             hbsize > (int) (strlen(host) + strlen(d) + 1))
843                         {
844                                 if (host[strlen(host) - 1] != '.')
845                                         (void) sm_strlcat2(host, ".", d,
846                                                            hbsize);
847                                 else
848                                         (void) sm_strlcat(host, d, hbsize);
849                         }
850                         else
851                         {
852 #if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN)
853                                 if (VendorCode == VENDOR_SUN &&
854                                     should_try_nis_domain)
855                                 {
856                                         goto try_nis_domain;
857                                 }
858 #endif /* defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN) */
859                                 return false;
860                         }
861                 }
862                 return true;
863         }
864
865 #if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN)
866         if (VendorCode == VENDOR_SUN && should_try_nis_domain)
867         {
868   try_nis_domain:
869                 if (nis_domain != NULL &&
870                     strlen(nis_domain) + strlen(host) + 1 < hbsize)
871                 {
872                         (void) sm_strlcat2(host, ".", nis_domain, hbsize);
873                         return true;
874                 }
875         }
876 #endif /* defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN) */
877
878         if (tTd(38, 20))
879                 sm_dprintf("getcanonname(%s), failed, status=%d\n", host,
880                         status);
881
882         if (got_tempfail)
883                 SM_SET_H_ERRNO(TRY_AGAIN);
884         else
885                 SM_SET_H_ERRNO(HOST_NOT_FOUND);
886
887         return false;
888 }
889 /*
890 **  EXTRACT_CANONNAME -- extract canonical name from /etc/hosts entry
891 **
892 **      Parameters:
893 **              name -- the name against which to match.
894 **              dot -- where to reinsert '.' to get FQDN
895 **              line -- the /etc/hosts line.
896 **              cbuf -- the location to store the result.
897 **              cbuflen -- the size of cbuf.
898 **
899 **      Returns:
900 **              true -- if the line matched the desired name.
901 **              false -- otherwise.
902 */
903
904 static bool
905 extract_canonname(name, dot, line, cbuf, cbuflen)
906         char *name;
907         char *dot;
908         char *line;
909         char cbuf[];
910         int cbuflen;
911 {
912         int i;
913         char *p;
914         bool found = false;
915
916         cbuf[0] = '\0';
917         if (line[0] == '#')
918                 return false;
919
920         for (i = 1; ; i++)
921         {
922                 char nbuf[MAXNAME + 1];
923
924                 p = get_column(line, i, '\0', nbuf, sizeof(nbuf));
925                 if (p == NULL)
926                         break;
927                 if (*p == '\0')
928                         continue;
929                 if (cbuf[0] == '\0' ||
930                     (strchr(cbuf, '.') == NULL && strchr(p, '.') != NULL))
931                 {
932                         (void) sm_strlcpy(cbuf, p, cbuflen);
933                 }
934                 if (sm_strcasecmp(name, p) == 0)
935                         found = true;
936                 else if (dot != NULL)
937                 {
938                         /* try looking for the FQDN as well */
939                         *dot = '.';
940                         if (sm_strcasecmp(name, p) == 0)
941                                 found = true;
942                         *dot = '\0';
943                 }
944         }
945         if (found && strchr(cbuf, '.') == NULL)
946         {
947                 /* try to add a domain on the end of the name */
948                 char *domain = macvalue('m', CurEnv);
949
950                 if (domain != NULL &&
951                     strlen(domain) + (i = strlen(cbuf)) + 1 < (size_t) cbuflen)
952                 {
953                         p = &cbuf[i];
954                         *p++ = '.';
955                         (void) sm_strlcpy(p, domain, cbuflen - i - 1);
956                 }
957         }
958         return found;
959 }
960
961 /*
962 **  DNS modules
963 */
964
965 #if NAMED_BIND
966 # if DNSMAP
967
968 #  include "sm_resolve.h"
969 #  if NETINET || NETINET6
970 #   include <arpa/inet.h>
971 #  endif /* NETINET || NETINET6 */
972
973 /*
974 **  DNS_MAP_OPEN -- stub to check proper value for dns map type
975 */
976
977 bool
978 dns_map_open(map, mode)
979         MAP *map;
980         int mode;
981 {
982         if (tTd(38,2))
983                 sm_dprintf("dns_map_open(%s, %d)\n", map->map_mname, mode);
984
985         mode &= O_ACCMODE;
986         if (mode != O_RDONLY)
987         {
988                 /* issue a pseudo-error message */
989                 errno = SM_EMAPCANTWRITE;
990                 return false;
991         }
992         return true;
993 }
994
995 /*
996 **  DNS_MAP_PARSEARGS -- parse dns map definition args.
997 **
998 **      Parameters:
999 **              map -- pointer to MAP
1000 **              args -- pointer to the args on the config line.
1001 **
1002 **      Returns:
1003 **              true -- if everything parsed OK.
1004 **              false -- otherwise.
1005 */
1006
1007 #define map_sizelimit   map_lockfd      /* overload field */
1008
1009 struct dns_map
1010 {
1011         int dns_m_type;
1012 };
1013
1014 bool
1015 dns_map_parseargs(map,args)
1016         MAP *map;
1017         char *args;
1018 {
1019         register char *p = args;
1020         struct dns_map *map_p;
1021
1022         map_p = (struct dns_map *) xalloc(sizeof(*map_p));
1023         map_p->dns_m_type = -1;
1024         map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL;
1025
1026         for (;;)
1027         {
1028                 while (isascii(*p) && isspace(*p))
1029                         p++;
1030                 if (*p != '-')
1031                         break;
1032                 switch (*++p)
1033                 {
1034                   case 'N':
1035                         map->map_mflags |= MF_INCLNULL;
1036                         map->map_mflags &= ~MF_TRY0NULL;
1037                         break;
1038
1039                   case 'O':
1040                         map->map_mflags &= ~MF_TRY1NULL;
1041                         break;
1042
1043                   case 'o':
1044                         map->map_mflags |= MF_OPTIONAL;
1045                         break;
1046
1047                   case 'f':
1048                         map->map_mflags |= MF_NOFOLDCASE;
1049                         break;
1050
1051                   case 'm':
1052                         map->map_mflags |= MF_MATCHONLY;
1053                         break;
1054
1055                   case 'A':
1056                         map->map_mflags |= MF_APPEND;
1057                         break;
1058
1059                   case 'q':
1060                         map->map_mflags |= MF_KEEPQUOTES;
1061                         break;
1062
1063                   case 't':
1064                         map->map_mflags |= MF_NODEFER;
1065                         break;
1066
1067                   case 'a':
1068                         map->map_app = ++p;
1069                         break;
1070
1071                   case 'T':
1072                         map->map_tapp = ++p;
1073                         break;
1074
1075                   case 'd':
1076                         {
1077                                 char *h;
1078
1079                                 ++p;
1080                                 h = strchr(p, ' ');
1081                                 if (h != NULL)
1082                                         *h = '\0';
1083                                 map->map_timeout = convtime(p, 's');
1084                                 if (h != NULL)
1085                                         *h = ' ';
1086                         }
1087                         break;
1088
1089                   case 'r':
1090                         while (isascii(*++p) && isspace(*p))
1091                                 continue;
1092                         map->map_retry = atoi(p);
1093                         break;
1094
1095                   case 'z':
1096                         if (*++p != '\\')
1097                                 map->map_coldelim = *p;
1098                         else
1099                         {
1100                                 switch (*++p)
1101                                 {
1102                                   case 'n':
1103                                         map->map_coldelim = '\n';
1104                                         break;
1105
1106                                   case 't':
1107                                         map->map_coldelim = '\t';
1108                                         break;
1109
1110                                   default:
1111                                         map->map_coldelim = '\\';
1112                                 }
1113                         }
1114                         break;
1115
1116                   case 'Z':
1117                         while (isascii(*++p) && isspace(*p))
1118                                 continue;
1119                         map->map_sizelimit = atoi(p);
1120                         break;
1121
1122                         /* Start of dns_map specific args */
1123                   case 'R':             /* search field */
1124                         {
1125                                 char *h;
1126
1127                                 while (isascii(*++p) && isspace(*p))
1128                                         continue;
1129                                 h = strchr(p, ' ');
1130                                 if (h != NULL)
1131                                         *h = '\0';
1132                                 map_p->dns_m_type = dns_string_to_type(p);
1133                                 if (h != NULL)
1134                                         *h = ' ';
1135                                 if (map_p->dns_m_type < 0)
1136                                         syserr("dns map %s: wrong type %s",
1137                                                 map->map_mname, p);
1138                         }
1139                         break;
1140
1141                   case 'B':             /* base domain */
1142                         {
1143                                 char *h;
1144
1145                                 while (isascii(*++p) && isspace(*p))
1146                                         continue;
1147                                 h = strchr(p, ' ');
1148                                 if (h != NULL)
1149                                         *h = '\0';
1150
1151                                 /*
1152                                 **  slight abuse of map->map_file; it isn't
1153                                 **      used otherwise in this map type.
1154                                 */
1155
1156                                 map->map_file = newstr(p);
1157                                 if (h != NULL)
1158                                         *h = ' ';
1159                         }
1160                         break;
1161                 }
1162                 while (*p != '\0' && !(isascii(*p) && isspace(*p)))
1163                         p++;
1164                 if (*p != '\0')
1165                         *p++ = '\0';
1166         }
1167         if (map_p->dns_m_type < 0)
1168                 syserr("dns map %s: missing -R type", map->map_mname);
1169         if (map->map_app != NULL)
1170                 map->map_app = newstr(map->map_app);
1171         if (map->map_tapp != NULL)
1172                 map->map_tapp = newstr(map->map_tapp);
1173
1174         /*
1175         **  Assumption: assert(sizeof(int) <= sizeof(ARBPTR_T));
1176         **  Even if this assumption is wrong, we use only one byte,
1177         **  so it doesn't really matter.
1178         */
1179
1180         map->map_db1 = (ARBPTR_T) map_p;
1181         return true;
1182 }
1183
1184 /*
1185 **  DNS_MAP_LOOKUP -- perform dns map lookup.
1186 **
1187 **      Parameters:
1188 **              map -- pointer to MAP
1189 **              name -- name to lookup
1190 **              av -- arguments to interpolate into buf.
1191 **              statp -- pointer to status (EX_)
1192 **
1193 **      Returns:
1194 **              result of lookup if succeeded.
1195 **              NULL -- otherwise.
1196 */
1197
1198 char *
1199 dns_map_lookup(map, name, av, statp)
1200         MAP *map;
1201         char *name;
1202         char **av;
1203         int *statp;
1204 {
1205         int resnum = 0;
1206         char *vp = NULL, *result = NULL;
1207         size_t vsize;
1208         struct dns_map *map_p;
1209         RESOURCE_RECORD_T *rr = NULL;
1210         DNS_REPLY_T *r = NULL;
1211 #  if NETINET6
1212         static char buf6[INET6_ADDRSTRLEN];
1213 #  endif /* NETINET6 */
1214
1215         if (tTd(38, 20))
1216                 sm_dprintf("dns_map_lookup(%s, %s)\n",
1217                            map->map_mname, name);
1218
1219         map_p = (struct dns_map *)(map->map_db1);
1220         if (map->map_file != NULL && *map->map_file != '\0')
1221         {
1222                 size_t len;
1223                 char *appdomain;
1224
1225                 len = strlen(map->map_file) + strlen(name) + 2;
1226                 appdomain = (char *) sm_malloc(len);
1227                 if (appdomain == NULL)
1228                 {
1229                         *statp = EX_UNAVAILABLE;
1230                         return NULL;
1231                 }
1232                 (void) sm_strlcpyn(appdomain, len, 3, name, ".", map->map_file);
1233                 r = dns_lookup_int(appdomain, C_IN, map_p->dns_m_type,
1234                                    map->map_timeout, map->map_retry);
1235                 sm_free(appdomain);
1236         }
1237         else
1238         {
1239                 r = dns_lookup_int(name, C_IN, map_p->dns_m_type,
1240                                    map->map_timeout, map->map_retry);
1241         }
1242
1243         if (r == NULL)
1244         {
1245                 result = NULL;
1246                 if (h_errno == TRY_AGAIN || transienterror(errno))
1247                         *statp = EX_TEMPFAIL;
1248                 else
1249                         *statp = EX_NOTFOUND;
1250                 goto cleanup;
1251         }
1252         *statp = EX_OK;
1253         for (rr = r->dns_r_head; rr != NULL; rr = rr->rr_next)
1254         {
1255                 char *type = NULL;
1256                 char *value = NULL;
1257
1258                 switch (rr->rr_type)
1259                 {
1260                   case T_NS:
1261                         type = "T_NS";
1262                         value = rr->rr_u.rr_txt;
1263                         break;
1264                   case T_CNAME:
1265                         type = "T_CNAME";
1266                         value = rr->rr_u.rr_txt;
1267                         break;
1268                   case T_AFSDB:
1269                         type = "T_AFSDB";
1270                         value = rr->rr_u.rr_mx->mx_r_domain;
1271                         break;
1272                   case T_SRV:
1273                         type = "T_SRV";
1274                         value = rr->rr_u.rr_srv->srv_r_target;
1275                         break;
1276                   case T_PTR:
1277                         type = "T_PTR";
1278                         value = rr->rr_u.rr_txt;
1279                         break;
1280                   case T_TXT:
1281                         type = "T_TXT";
1282                         value = rr->rr_u.rr_txt;
1283                         break;
1284                   case T_MX:
1285                         type = "T_MX";
1286                         value = rr->rr_u.rr_mx->mx_r_domain;
1287                         break;
1288 #  if NETINET
1289                   case T_A:
1290                         type = "T_A";
1291                         value = inet_ntoa(*(rr->rr_u.rr_a));
1292                         break;
1293 #  endif /* NETINET */
1294 #  if NETINET6
1295                   case T_AAAA:
1296                         type = "T_AAAA";
1297                         value = anynet_ntop(rr->rr_u.rr_aaaa, buf6,
1298                                             sizeof(buf6));
1299                         break;
1300 #  endif /* NETINET6 */
1301                 }
1302
1303                 (void) strreplnonprt(value, 'X');
1304                 if (map_p->dns_m_type != rr->rr_type)
1305                 {
1306                         if (tTd(38, 40))
1307                                 sm_dprintf("\tskipping type %s (%d) value %s\n",
1308                                            type != NULL ? type : "<UNKNOWN>",
1309                                            rr->rr_type,
1310                                            value != NULL ? value : "<NO VALUE>");
1311                         continue;
1312                 }
1313
1314 #  if NETINET6
1315                 if (rr->rr_type == T_AAAA && value == NULL)
1316                 {
1317                         result = NULL;
1318                         *statp = EX_DATAERR;
1319                         if (tTd(38, 40))
1320                                 sm_dprintf("\tbad T_AAAA conversion\n");
1321                         goto cleanup;
1322                 }
1323 #  endif /* NETINET6 */
1324                 if (tTd(38, 40))
1325                         sm_dprintf("\tfound type %s (%d) value %s\n",
1326                                    type != NULL ? type : "<UNKNOWN>",
1327                                    rr->rr_type,
1328                                    value != NULL ? value : "<NO VALUE>");
1329                 if (value != NULL &&
1330                     (map->map_coldelim == '\0' ||
1331                      map->map_sizelimit == 1 ||
1332                      bitset(MF_MATCHONLY, map->map_mflags)))
1333                 {
1334                         /* Only care about the first match */
1335                         vp = newstr(value);
1336                         break;
1337                 }
1338                 else if (vp == NULL)
1339                 {
1340                         /* First result */
1341                         vp = newstr(value);
1342                 }
1343                 else
1344                 {
1345                         /* concatenate the results */
1346                         int sz;
1347                         char *new;
1348
1349                         sz = strlen(vp) + strlen(value) + 2;
1350                         new = xalloc(sz);
1351                         (void) sm_snprintf(new, sz, "%s%c%s",
1352                                            vp, map->map_coldelim, value);
1353                         sm_free(vp);
1354                         vp = new;
1355                         if (map->map_sizelimit > 0 &&
1356                             ++resnum >= map->map_sizelimit)
1357                                 break;
1358                 }
1359         }
1360         if (vp == NULL)
1361         {
1362                 result = NULL;
1363                 *statp = EX_NOTFOUND;
1364                 if (tTd(38, 40))
1365                         sm_dprintf("\tno match found\n");
1366                 goto cleanup;
1367         }
1368
1369         /* Cleanly truncate for rulesets */
1370         truncate_at_delim(vp, PSBUFSIZE / 2, map->map_coldelim);
1371
1372         vsize = strlen(vp);
1373
1374         if (LogLevel > 9)
1375                 sm_syslog(LOG_INFO, CurEnv->e_id, "dns %.100s => %s",
1376                           name, vp);
1377         if (bitset(MF_MATCHONLY, map->map_mflags))
1378                 result = map_rewrite(map, name, strlen(name), NULL);
1379         else
1380                 result = map_rewrite(map, vp, vsize, av);
1381
1382   cleanup:
1383         if (vp != NULL)
1384                 sm_free(vp);
1385         if (r != NULL)
1386                 dns_free_data(r);
1387         return result;
1388 }
1389 # endif /* DNSMAP */
1390 #endif /* NAMED_BIND */
1391
1392 /*
1393 **  NDBM modules
1394 */
1395
1396 #if NDBM
1397
1398 /*
1399 **  NDBM_MAP_OPEN -- DBM-style map open
1400 */
1401
1402 bool
1403 ndbm_map_open(map, mode)
1404         MAP *map;
1405         int mode;
1406 {
1407         register DBM *dbm;
1408         int save_errno;
1409         int dfd;
1410         int pfd;
1411         long sff;
1412         int ret;
1413         int smode = S_IREAD;
1414         char dirfile[MAXPATHLEN];
1415         char pagfile[MAXPATHLEN];
1416         struct stat st;
1417         struct stat std, stp;
1418
1419         if (tTd(38, 2))
1420                 sm_dprintf("ndbm_map_open(%s, %s, %d)\n",
1421                         map->map_mname, map->map_file, mode);
1422         map->map_lockfd = -1;
1423         mode &= O_ACCMODE;
1424
1425         /* do initial file and directory checks */
1426         if (sm_strlcpyn(dirfile, sizeof(dirfile), 2,
1427                         map->map_file, ".dir") >= sizeof(dirfile) ||
1428             sm_strlcpyn(pagfile, sizeof(pagfile), 2,
1429                         map->map_file, ".pag") >= sizeof(pagfile))
1430         {
1431                 errno = 0;
1432                 if (!bitset(MF_OPTIONAL, map->map_mflags))
1433                         syserr("dbm map \"%s\": map file %s name too long",
1434                                 map->map_mname, map->map_file);
1435                 return false;
1436         }
1437         sff = SFF_ROOTOK|SFF_REGONLY;
1438         if (mode == O_RDWR)
1439         {
1440                 sff |= SFF_CREAT;
1441                 if (!bitnset(DBS_WRITEMAPTOSYMLINK, DontBlameSendmail))
1442                         sff |= SFF_NOSLINK;
1443                 if (!bitnset(DBS_WRITEMAPTOHARDLINK, DontBlameSendmail))
1444                         sff |= SFF_NOHLINK;
1445                 smode = S_IWRITE;
1446         }
1447         else
1448         {
1449                 if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
1450                         sff |= SFF_NOWLINK;
1451         }
1452         if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail))
1453                 sff |= SFF_SAFEDIRPATH;
1454         ret = safefile(dirfile, RunAsUid, RunAsGid, RunAsUserName,
1455                        sff, smode, &std);
1456         if (ret == 0)
1457                 ret = safefile(pagfile, RunAsUid, RunAsGid, RunAsUserName,
1458                                sff, smode, &stp);
1459
1460         if (ret != 0)
1461         {
1462                 char *prob = "unsafe";
1463
1464                 /* cannot open this map */
1465                 if (ret == ENOENT)
1466                         prob = "missing";
1467                 if (tTd(38, 2))
1468                         sm_dprintf("\t%s map file: %d\n", prob, ret);
1469                 if (!bitset(MF_OPTIONAL, map->map_mflags))
1470                         syserr("dbm map \"%s\": %s map file %s",
1471                                 map->map_mname, prob, map->map_file);
1472                 return false;
1473         }
1474         if (std.st_mode == ST_MODE_NOFILE)
1475                 mode |= O_CREAT|O_EXCL;
1476
1477 # if LOCK_ON_OPEN
1478         if (mode == O_RDONLY)
1479                 mode |= O_SHLOCK;
1480         else
1481                 mode |= O_TRUNC|O_EXLOCK;
1482 # else /* LOCK_ON_OPEN */
1483         if ((mode & O_ACCMODE) == O_RDWR)
1484         {
1485 #  if NOFTRUNCATE
1486                 /*
1487                 **  Warning: race condition.  Try to lock the file as
1488                 **  quickly as possible after opening it.
1489                 **      This may also have security problems on some systems,
1490                 **      but there isn't anything we can do about it.
1491                 */
1492
1493                 mode |= O_TRUNC;
1494 #  else /* NOFTRUNCATE */
1495                 /*
1496                 **  This ugly code opens the map without truncating it,
1497                 **  locks the file, then truncates it.  Necessary to
1498                 **  avoid race conditions.
1499                 */
1500
1501                 int dirfd;
1502                 int pagfd;
1503                 long sff = SFF_CREAT|SFF_OPENASROOT;
1504
1505                 if (!bitnset(DBS_WRITEMAPTOSYMLINK, DontBlameSendmail))
1506                         sff |= SFF_NOSLINK;
1507                 if (!bitnset(DBS_WRITEMAPTOHARDLINK, DontBlameSendmail))
1508                         sff |= SFF_NOHLINK;
1509
1510                 dirfd = safeopen(dirfile, mode, DBMMODE, sff);
1511                 pagfd = safeopen(pagfile, mode, DBMMODE, sff);
1512
1513                 if (dirfd < 0 || pagfd < 0)
1514                 {
1515                         save_errno = errno;
1516                         if (dirfd >= 0)
1517                                 (void) close(dirfd);
1518                         if (pagfd >= 0)
1519                                 (void) close(pagfd);
1520                         errno = save_errno;
1521                         syserr("ndbm_map_open: cannot create database %s",
1522                                 map->map_file);
1523                         return false;
1524                 }
1525                 if (ftruncate(dirfd, (off_t) 0) < 0 ||
1526                     ftruncate(pagfd, (off_t) 0) < 0)
1527                 {
1528                         save_errno = errno;
1529                         (void) close(dirfd);
1530                         (void) close(pagfd);
1531                         errno = save_errno;
1532                         syserr("ndbm_map_open: cannot truncate %s.{dir,pag}",
1533                                 map->map_file);
1534                         return false;
1535                 }
1536
1537                 /* if new file, get "before" bits for later filechanged check */
1538                 if (std.st_mode == ST_MODE_NOFILE &&
1539                     (fstat(dirfd, &std) < 0 || fstat(pagfd, &stp) < 0))
1540                 {
1541                         save_errno = errno;
1542                         (void) close(dirfd);
1543                         (void) close(pagfd);
1544                         errno = save_errno;
1545                         syserr("ndbm_map_open(%s.{dir,pag}): cannot fstat pre-opened file",
1546                                 map->map_file);
1547                         return false;
1548                 }
1549
1550                 /* have to save the lock for the duration (bletch) */
1551                 map->map_lockfd = dirfd;
1552                 (void) close(pagfd);
1553
1554                 /* twiddle bits for dbm_open */
1555                 mode &= ~(O_CREAT|O_EXCL);
1556 #  endif /* NOFTRUNCATE */
1557         }
1558 # endif /* LOCK_ON_OPEN */
1559
1560         /* open the database */
1561         dbm = dbm_open(map->map_file, mode, DBMMODE);
1562         if (dbm == NULL)
1563         {
1564                 save_errno = errno;
1565                 if (bitset(MF_ALIAS, map->map_mflags) &&
1566                     aliaswait(map, ".pag", false))
1567                         return true;
1568 # if !LOCK_ON_OPEN && !NOFTRUNCATE
1569                 if (map->map_lockfd >= 0)
1570                         (void) close(map->map_lockfd);
1571 # endif /* !LOCK_ON_OPEN && !NOFTRUNCATE */
1572                 errno = save_errno;
1573                 if (!bitset(MF_OPTIONAL, map->map_mflags))
1574                         syserr("Cannot open DBM database %s", map->map_file);
1575                 return false;
1576         }
1577         dfd = dbm_dirfno(dbm);
1578         pfd = dbm_pagfno(dbm);
1579         if (dfd == pfd)
1580         {
1581                 /* heuristic: if files are linked, this is actually gdbm */
1582                 dbm_close(dbm);
1583 # if !LOCK_ON_OPEN && !NOFTRUNCATE
1584                 if (map->map_lockfd >= 0)
1585                         (void) close(map->map_lockfd);
1586 # endif /* !LOCK_ON_OPEN && !NOFTRUNCATE */
1587                 errno = 0;
1588                 syserr("dbm map \"%s\": cannot support GDBM",
1589                         map->map_mname);
1590                 return false;
1591         }
1592
1593         if (filechanged(dirfile, dfd, &std) ||
1594             filechanged(pagfile, pfd, &stp))
1595         {
1596                 save_errno = errno;
1597                 dbm_close(dbm);
1598 # if !LOCK_ON_OPEN && !NOFTRUNCATE
1599                 if (map->map_lockfd >= 0)
1600                         (void) close(map->map_lockfd);
1601 # endif /* !LOCK_ON_OPEN && !NOFTRUNCATE */
1602                 errno = save_errno;
1603                 syserr("ndbm_map_open(%s): file changed after open",
1604                         map->map_file);
1605                 return false;
1606         }
1607
1608         map->map_db1 = (ARBPTR_T) dbm;
1609
1610         /*
1611         **  Need to set map_mtime before the call to aliaswait()
1612         **  as aliaswait() will call map_lookup() which requires
1613         **  map_mtime to be set
1614         */
1615
1616         if (fstat(pfd, &st) >= 0)
1617                 map->map_mtime = st.st_mtime;
1618
1619         if (mode == O_RDONLY)
1620         {
1621 # if LOCK_ON_OPEN
1622                 if (dfd >= 0)
1623                         (void) lockfile(dfd, map->map_file, ".dir", LOCK_UN);
1624                 if (pfd >= 0)
1625                         (void) lockfile(pfd, map->map_file, ".pag", LOCK_UN);
1626 # endif /* LOCK_ON_OPEN */
1627                 if (bitset(MF_ALIAS, map->map_mflags) &&
1628                     !aliaswait(map, ".pag", true))
1629                         return false;
1630         }
1631         else
1632         {
1633                 map->map_mflags |= MF_LOCKED;
1634                 if (geteuid() == 0 && TrustedUid != 0)
1635                 {
1636 #  if HASFCHOWN
1637                         if (fchown(dfd, TrustedUid, -1) < 0 ||
1638                             fchown(pfd, TrustedUid, -1) < 0)
1639                         {
1640                                 int err = errno;
1641
1642                                 sm_syslog(LOG_ALERT, NOQID,
1643                                           "ownership change on %s failed: %s",
1644                                           map->map_file, sm_errstring(err));
1645                                 message("050 ownership change on %s failed: %s",
1646                                         map->map_file, sm_errstring(err));
1647                         }
1648 #  else /* HASFCHOWN */
1649                         sm_syslog(LOG_ALERT, NOQID,
1650                                   "no fchown(): cannot change ownership on %s",
1651                                   map->map_file);
1652                         message("050 no fchown(): cannot change ownership on %s",
1653                                 map->map_file);
1654 #  endif /* HASFCHOWN */
1655                 }
1656         }
1657         return true;
1658 }
1659
1660
1661 /*
1662 **  NDBM_MAP_LOOKUP -- look up a datum in a DBM-type map
1663 */
1664
1665 char *
1666 ndbm_map_lookup(map, name, av, statp)
1667         MAP *map;
1668         char *name;
1669         char **av;
1670         int *statp;
1671 {
1672         datum key, val;
1673         int dfd, pfd;
1674         char keybuf[MAXNAME + 1];
1675         struct stat stbuf;
1676
1677         if (tTd(38, 20))
1678                 sm_dprintf("ndbm_map_lookup(%s, %s)\n",
1679                         map->map_mname, name);
1680
1681         key.dptr = name;
1682         key.dsize = strlen(name);
1683         if (!bitset(MF_NOFOLDCASE, map->map_mflags))
1684         {
1685                 if (key.dsize > sizeof(keybuf) - 1)
1686                         key.dsize = sizeof(keybuf) - 1;
1687                 memmove(keybuf, key.dptr, key.dsize);
1688                 keybuf[key.dsize] = '\0';
1689                 makelower(keybuf);
1690                 key.dptr = keybuf;
1691         }
1692 lockdbm:
1693         dfd = dbm_dirfno((DBM *) map->map_db1);
1694         if (dfd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
1695                 (void) lockfile(dfd, map->map_file, ".dir", LOCK_SH);
1696         pfd = dbm_pagfno((DBM *) map->map_db1);
1697         if (pfd < 0 || fstat(pfd, &stbuf) < 0 ||
1698             stbuf.st_mtime > map->map_mtime)
1699         {
1700                 /* Reopen the database to sync the cache */
1701                 int omode = bitset(map->map_mflags, MF_WRITABLE) ? O_RDWR
1702                                                                  : O_RDONLY;
1703
1704                 if (dfd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
1705                         (void) lockfile(dfd, map->map_file, ".dir", LOCK_UN);
1706                 map->map_mflags |= MF_CLOSING;
1707                 map->map_class->map_close(map);
1708                 map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
1709                 if (map->map_class->map_open(map, omode))
1710                 {
1711                         map->map_mflags |= MF_OPEN;
1712                         map->map_pid = CurrentPid;
1713                         if ((omode && O_ACCMODE) == O_RDWR)
1714                                 map->map_mflags |= MF_WRITABLE;
1715                         goto lockdbm;
1716                 }
1717                 else
1718                 {
1719                         if (!bitset(MF_OPTIONAL, map->map_mflags))
1720                         {
1721                                 extern MAPCLASS BogusMapClass;
1722
1723                                 *statp = EX_TEMPFAIL;
1724                                 map->map_orgclass = map->map_class;
1725                                 map->map_class = &BogusMapClass;
1726                                 map->map_mflags |= MF_OPEN;
1727                                 map->map_pid = CurrentPid;
1728                                 syserr("Cannot reopen NDBM database %s",
1729                                         map->map_file);
1730                         }
1731                         return NULL;
1732                 }
1733         }
1734         val.dptr = NULL;
1735         if (bitset(MF_TRY0NULL, map->map_mflags))
1736         {
1737                 val = dbm_fetch((DBM *) map->map_db1, key);
1738                 if (val.dptr != NULL)
1739                         map->map_mflags &= ~MF_TRY1NULL;
1740         }
1741         if (val.dptr == NULL && bitset(MF_TRY1NULL, map->map_mflags))
1742         {
1743                 key.dsize++;
1744                 val = dbm_fetch((DBM *) map->map_db1, key);
1745                 if (val.dptr != NULL)
1746                         map->map_mflags &= ~MF_TRY0NULL;
1747         }
1748         if (dfd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
1749                 (void) lockfile(dfd, map->map_file, ".dir", LOCK_UN);
1750         if (val.dptr == NULL)
1751                 return NULL;
1752         if (bitset(MF_MATCHONLY, map->map_mflags))
1753                 return map_rewrite(map, name, strlen(name), NULL);
1754         else
1755                 return map_rewrite(map, val.dptr, val.dsize, av);
1756 }
1757
1758
1759 /*
1760 **  NDBM_MAP_STORE -- store a datum in the database
1761 */
1762
1763 void
1764 ndbm_map_store(map, lhs, rhs)
1765         register MAP *map;
1766         char *lhs;
1767         char *rhs;
1768 {
1769         datum key;
1770         datum data;
1771         int status;
1772         char keybuf[MAXNAME + 1];
1773
1774         if (tTd(38, 12))
1775                 sm_dprintf("ndbm_map_store(%s, %s, %s)\n",
1776                         map->map_mname, lhs, rhs);
1777
1778         key.dsize = strlen(lhs);
1779         key.dptr = lhs;
1780         if (!bitset(MF_NOFOLDCASE, map->map_mflags))
1781         {
1782                 if (key.dsize > sizeof(keybuf) - 1)
1783                         key.dsize = sizeof(keybuf) - 1;
1784                 memmove(keybuf, key.dptr, key.dsize);
1785                 keybuf[key.dsize] = '\0';
1786                 makelower(keybuf);
1787                 key.dptr = keybuf;
1788         }
1789
1790         data.dsize = strlen(rhs);
1791         data.dptr = rhs;
1792
1793         if (bitset(MF_INCLNULL, map->map_mflags))
1794         {
1795                 key.dsize++;
1796                 data.dsize++;
1797         }
1798
1799         status = dbm_store((DBM *) map->map_db1, key, data, DBM_INSERT);
1800         if (status > 0)
1801         {
1802                 if (!bitset(MF_APPEND, map->map_mflags))
1803                         message("050 Warning: duplicate alias name %s", lhs);
1804                 else
1805                 {
1806                         static char *buf = NULL;
1807                         static int bufsiz = 0;
1808                         auto int xstat;
1809                         datum old;
1810
1811                         old.dptr = ndbm_map_lookup(map, key.dptr,
1812                                                    (char **) NULL, &xstat);
1813                         if (old.dptr != NULL && *(char *) old.dptr != '\0')
1814                         {
1815                                 old.dsize = strlen(old.dptr);
1816                                 if (data.dsize + old.dsize + 2 > bufsiz)
1817                                 {
1818                                         if (buf != NULL)
1819                                                 (void) sm_free(buf);
1820                                         bufsiz = data.dsize + old.dsize + 2;
1821                                         buf = sm_pmalloc_x(bufsiz);
1822                                 }
1823                                 (void) sm_strlcpyn(buf, bufsiz, 3,
1824                                         data.dptr, ",", old.dptr);
1825                                 data.dsize = data.dsize + old.dsize + 1;
1826                                 data.dptr = buf;
1827                                 if (tTd(38, 9))
1828                                         sm_dprintf("ndbm_map_store append=%s\n",
1829                                                 data.dptr);
1830                         }
1831                 }
1832                 status = dbm_store((DBM *) map->map_db1,
1833                                    key, data, DBM_REPLACE);
1834         }
1835         if (status != 0)
1836                 syserr("readaliases: dbm put (%s): %d", lhs, status);
1837 }
1838
1839
1840 /*
1841 **  NDBM_MAP_CLOSE -- close the database
1842 */
1843
1844 void
1845 ndbm_map_close(map)
1846         register MAP  *map;
1847 {
1848         if (tTd(38, 9))
1849                 sm_dprintf("ndbm_map_close(%s, %s, %lx)\n",
1850                         map->map_mname, map->map_file, map->map_mflags);
1851
1852         if (bitset(MF_WRITABLE, map->map_mflags))
1853         {
1854 # ifdef NDBM_YP_COMPAT
1855                 bool inclnull;
1856                 char buf[MAXHOSTNAMELEN];
1857
1858                 inclnull = bitset(MF_INCLNULL, map->map_mflags);
1859                 map->map_mflags &= ~MF_INCLNULL;
1860
1861                 if (strstr(map->map_file, "/yp/") != NULL)
1862                 {
1863                         long save_mflags = map->map_mflags;
1864
1865                         map->map_mflags |= MF_NOFOLDCASE;
1866
1867                         (void) sm_snprintf(buf, sizeof(buf), "%010ld", curtime());
1868                         ndbm_map_store(map, "YP_LAST_MODIFIED", buf);
1869
1870                         (void) gethostname(buf, sizeof(buf));
1871                         ndbm_map_store(map, "YP_MASTER_NAME", buf);
1872
1873                         map->map_mflags = save_mflags;
1874                 }
1875
1876                 if (inclnull)
1877                         map->map_mflags |= MF_INCLNULL;
1878 # endif /* NDBM_YP_COMPAT */
1879
1880                 /* write out the distinguished alias */
1881                 ndbm_map_store(map, "@", "@");
1882         }
1883         dbm_close((DBM *) map->map_db1);
1884
1885         /* release lock (if needed) */
1886 # if !LOCK_ON_OPEN
1887         if (map->map_lockfd >= 0)
1888                 (void) close(map->map_lockfd);
1889 # endif /* !LOCK_ON_OPEN */
1890 }
1891
1892 #endif /* NDBM */
1893 /*
1894 **  NEWDB (Hash and BTree) Modules
1895 */
1896
1897 #if NEWDB
1898
1899 /*
1900 **  BT_MAP_OPEN, HASH_MAP_OPEN -- database open primitives.
1901 **
1902 **      These do rather bizarre locking.  If you can lock on open,
1903 **      do that to avoid the condition of opening a database that
1904 **      is being rebuilt.  If you don't, we'll try to fake it, but
1905 **      there will be a race condition.  If opening for read-only,
1906 **      we immediately release the lock to avoid freezing things up.
1907 **      We really ought to hold the lock, but guarantee that we won't
1908 **      be pokey about it.  That's hard to do.
1909 */
1910
1911 /* these should be K line arguments */
1912 # if DB_VERSION_MAJOR < 2
1913 #  define db_cachesize  cachesize
1914 #  define h_nelem       nelem
1915 #  ifndef DB_CACHE_SIZE
1916 #   define DB_CACHE_SIZE        (1024 * 1024)   /* database memory cache size */
1917 #  endif /* ! DB_CACHE_SIZE */
1918 #  ifndef DB_HASH_NELEM
1919 #   define DB_HASH_NELEM        4096            /* (starting) size of hash table */
1920 #  endif /* ! DB_HASH_NELEM */
1921 # endif /* DB_VERSION_MAJOR < 2 */
1922
1923 bool
1924 bt_map_open(map, mode)
1925         MAP *map;
1926         int mode;
1927 {
1928 # if DB_VERSION_MAJOR < 2
1929         BTREEINFO btinfo;
1930 # endif /* DB_VERSION_MAJOR < 2 */
1931 # if DB_VERSION_MAJOR == 2
1932         DB_INFO btinfo;
1933 # endif /* DB_VERSION_MAJOR == 2 */
1934 # if DB_VERSION_MAJOR > 2
1935         void *btinfo = NULL;
1936 # endif /* DB_VERSION_MAJOR > 2 */
1937
1938         if (tTd(38, 2))
1939                 sm_dprintf("bt_map_open(%s, %s, %d)\n",
1940                         map->map_mname, map->map_file, mode);
1941
1942 # if DB_VERSION_MAJOR < 3
1943         memset(&btinfo, '\0', sizeof(btinfo));
1944 #  ifdef DB_CACHE_SIZE
1945         btinfo.db_cachesize = DB_CACHE_SIZE;
1946 #  endif /* DB_CACHE_SIZE */
1947 # endif /* DB_VERSION_MAJOR < 3 */
1948
1949         return db_map_open(map, mode, "btree", DB_BTREE, &btinfo);
1950 }
1951
1952 bool
1953 hash_map_open(map, mode)
1954         MAP *map;
1955         int mode;
1956 {
1957 # if DB_VERSION_MAJOR < 2
1958         HASHINFO hinfo;
1959 # endif /* DB_VERSION_MAJOR < 2 */
1960 # if DB_VERSION_MAJOR == 2
1961         DB_INFO hinfo;
1962 # endif /* DB_VERSION_MAJOR == 2 */
1963 # if DB_VERSION_MAJOR > 2
1964         void *hinfo = NULL;
1965 # endif /* DB_VERSION_MAJOR > 2 */
1966
1967         if (tTd(38, 2))
1968                 sm_dprintf("hash_map_open(%s, %s, %d)\n",
1969                         map->map_mname, map->map_file, mode);
1970
1971 # if DB_VERSION_MAJOR < 3
1972         memset(&hinfo, '\0', sizeof(hinfo));
1973 #  ifdef DB_HASH_NELEM
1974         hinfo.h_nelem = DB_HASH_NELEM;
1975 #  endif /* DB_HASH_NELEM */
1976 #  ifdef DB_CACHE_SIZE
1977         hinfo.db_cachesize = DB_CACHE_SIZE;
1978 #  endif /* DB_CACHE_SIZE */
1979 # endif /* DB_VERSION_MAJOR < 3 */
1980
1981         return db_map_open(map, mode, "hash", DB_HASH, &hinfo);
1982 }
1983
1984 static bool
1985 db_map_open(map, mode, mapclassname, dbtype, openinfo)
1986         MAP *map;
1987         int mode;
1988         char *mapclassname;
1989         DBTYPE dbtype;
1990 # if DB_VERSION_MAJOR < 2
1991         const void *openinfo;
1992 # endif /* DB_VERSION_MAJOR < 2 */
1993 # if DB_VERSION_MAJOR == 2
1994         DB_INFO *openinfo;
1995 # endif /* DB_VERSION_MAJOR == 2 */
1996 # if DB_VERSION_MAJOR > 2
1997         void **openinfo;
1998 # endif /* DB_VERSION_MAJOR > 2 */
1999 {
2000         DB *db = NULL;
2001         int i;
2002         int omode;
2003         int smode = S_IREAD;
2004         int fd;
2005         long sff;
2006         int save_errno;
2007         struct stat st;
2008         char buf[MAXPATHLEN];
2009
2010         /* do initial file and directory checks */
2011         if (sm_strlcpy(buf, map->map_file, sizeof(buf)) >= sizeof(buf))
2012         {
2013                 errno = 0;
2014                 if (!bitset(MF_OPTIONAL, map->map_mflags))
2015                         syserr("map \"%s\": map file %s name too long",
2016                                 map->map_mname, map->map_file);
2017                 return false;
2018         }
2019         i = strlen(buf);
2020         if (i < 3 || strcmp(&buf[i - 3], ".db") != 0)
2021         {
2022                 if (sm_strlcat(buf, ".db", sizeof(buf)) >= sizeof(buf))
2023                 {
2024                         errno = 0;
2025                         if (!bitset(MF_OPTIONAL, map->map_mflags))
2026                                 syserr("map \"%s\": map file %s name too long",
2027                                         map->map_mname, map->map_file);
2028                         return false;
2029                 }
2030         }
2031
2032         mode &= O_ACCMODE;
2033         omode = mode;
2034
2035         sff = SFF_ROOTOK|SFF_REGONLY;
2036         if (mode == O_RDWR)
2037         {
2038                 sff |= SFF_CREAT;
2039                 if (!bitnset(DBS_WRITEMAPTOSYMLINK, DontBlameSendmail))
2040                         sff |= SFF_NOSLINK;
2041                 if (!bitnset(DBS_WRITEMAPTOHARDLINK, DontBlameSendmail))
2042                         sff |= SFF_NOHLINK;
2043                 smode = S_IWRITE;
2044         }
2045         else
2046         {
2047                 if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
2048                         sff |= SFF_NOWLINK;
2049         }
2050         if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail))
2051                 sff |= SFF_SAFEDIRPATH;
2052         i = safefile(buf, RunAsUid, RunAsGid, RunAsUserName, sff, smode, &st);
2053
2054         if (i != 0)
2055         {
2056                 char *prob = "unsafe";
2057
2058                 /* cannot open this map */
2059                 if (i == ENOENT)
2060                         prob = "missing";
2061                 if (tTd(38, 2))
2062                         sm_dprintf("\t%s map file: %s\n", prob, sm_errstring(i));
2063                 errno = i;
2064                 if (!bitset(MF_OPTIONAL, map->map_mflags))
2065                         syserr("%s map \"%s\": %s map file %s",
2066                                 mapclassname, map->map_mname, prob, buf);
2067                 return false;
2068         }
2069         if (st.st_mode == ST_MODE_NOFILE)
2070                 omode |= O_CREAT|O_EXCL;
2071
2072         map->map_lockfd = -1;
2073
2074 # if LOCK_ON_OPEN
2075         if (mode == O_RDWR)
2076                 omode |= O_TRUNC|O_EXLOCK;
2077         else
2078                 omode |= O_SHLOCK;
2079 # else /* LOCK_ON_OPEN */
2080         /*
2081         **  Pre-lock the file to avoid race conditions.  In particular,
2082         **  since dbopen returns NULL if the file is zero length, we
2083         **  must have a locked instance around the dbopen.
2084         */
2085
2086         fd = open(buf, omode, DBMMODE);
2087         if (fd < 0)
2088         {
2089                 if (!bitset(MF_OPTIONAL, map->map_mflags))
2090                         syserr("db_map_open: cannot pre-open database %s", buf);
2091                 return false;
2092         }
2093
2094         /* make sure no baddies slipped in just before the open... */
2095         if (filechanged(buf, fd, &st))
2096         {
2097                 save_errno = errno;
2098                 (void) close(fd);
2099                 errno = save_errno;
2100                 syserr("db_map_open(%s): file changed after pre-open", buf);
2101                 return false;
2102         }
2103
2104         /* if new file, get the "before" bits for later filechanged check */
2105         if (st.st_mode == ST_MODE_NOFILE && fstat(fd, &st) < 0)
2106         {
2107                 save_errno = errno;
2108                 (void) close(fd);
2109                 errno = save_errno;
2110                 syserr("db_map_open(%s): cannot fstat pre-opened file",
2111                         buf);
2112                 return false;
2113         }
2114
2115         /* actually lock the pre-opened file */
2116         if (!lockfile(fd, buf, NULL, mode == O_RDONLY ? LOCK_SH : LOCK_EX))
2117                 syserr("db_map_open: cannot lock %s", buf);
2118
2119         /* set up mode bits for dbopen */
2120         if (mode == O_RDWR)
2121                 omode |= O_TRUNC;
2122         omode &= ~(O_EXCL|O_CREAT);
2123 # endif /* LOCK_ON_OPEN */
2124
2125 # if DB_VERSION_MAJOR < 2
2126         db = dbopen(buf, omode, DBMMODE, dbtype, openinfo);
2127 # else /* DB_VERSION_MAJOR < 2 */
2128         {
2129                 int flags = 0;
2130 #  if DB_VERSION_MAJOR > 2
2131                 int ret;
2132 #  endif /* DB_VERSION_MAJOR > 2 */
2133
2134                 if (mode == O_RDONLY)
2135                         flags |= DB_RDONLY;
2136                 if (bitset(O_CREAT, omode))
2137                         flags |= DB_CREATE;
2138                 if (bitset(O_TRUNC, omode))
2139                         flags |= DB_TRUNCATE;
2140                 SM_DB_FLAG_ADD(flags);
2141
2142 #  if DB_VERSION_MAJOR > 2
2143                 ret = db_create(&db, NULL, 0);
2144 #  ifdef DB_CACHE_SIZE
2145                 if (ret == 0 && db != NULL)
2146                 {
2147                         ret = db->set_cachesize(db, 0, DB_CACHE_SIZE, 0);
2148                         if (ret != 0)
2149                         {
2150                                 (void) db->close(db, 0);
2151                                 db = NULL;
2152                         }
2153                 }
2154 #  endif /* DB_CACHE_SIZE */
2155 #  ifdef DB_HASH_NELEM
2156                 if (dbtype == DB_HASH && ret == 0 && db != NULL)
2157                 {
2158                         ret = db->set_h_nelem(db, DB_HASH_NELEM);
2159                         if (ret != 0)
2160                         {
2161                                 (void) db->close(db, 0);
2162                                 db = NULL;
2163                         }
2164                 }
2165 #  endif /* DB_HASH_NELEM */
2166                 if (ret == 0 && db != NULL)
2167                 {
2168                         ret = db->open(db,
2169                                         DBTXN   /* transaction for DB 4.1 */
2170                                         buf, NULL, dbtype, flags, DBMMODE);
2171                         if (ret != 0)
2172                         {
2173 #ifdef DB_OLD_VERSION
2174                                 if (ret == DB_OLD_VERSION)
2175                                         ret = EINVAL;
2176 #endif /* DB_OLD_VERSION */
2177                                 (void) db->close(db, 0);
2178                                 db = NULL;
2179                         }
2180                 }
2181                 errno = ret;
2182 #  else /* DB_VERSION_MAJOR > 2 */
2183                 errno = db_open(buf, dbtype, flags, DBMMODE,
2184                                 NULL, openinfo, &db);
2185 #  endif /* DB_VERSION_MAJOR > 2 */
2186         }
2187 # endif /* DB_VERSION_MAJOR < 2 */
2188         save_errno = errno;
2189
2190 # if !LOCK_ON_OPEN
2191         if (mode == O_RDWR)
2192                 map->map_lockfd = fd;
2193         else
2194                 (void) close(fd);
2195 # endif /* !LOCK_ON_OPEN */
2196
2197         if (db == NULL)
2198         {
2199                 if (mode == O_RDONLY && bitset(MF_ALIAS, map->map_mflags) &&
2200                     aliaswait(map, ".db", false))
2201                         return true;
2202 # if !LOCK_ON_OPEN
2203                 if (map->map_lockfd >= 0)
2204                         (void) close(map->map_lockfd);
2205 # endif /* !LOCK_ON_OPEN */
2206                 errno = save_errno;
2207                 if (!bitset(MF_OPTIONAL, map->map_mflags))
2208                         syserr("Cannot open %s database %s",
2209                                 mapclassname, buf);
2210                 return false;
2211         }
2212
2213 # if DB_VERSION_MAJOR < 2
2214         fd = db->fd(db);
2215 # else /* DB_VERSION_MAJOR < 2 */
2216         fd = -1;
2217         errno = db->fd(db, &fd);
2218 # endif /* DB_VERSION_MAJOR < 2 */
2219         if (filechanged(buf, fd, &st))
2220         {
2221                 save_errno = errno;
2222 # if DB_VERSION_MAJOR < 2
2223                 (void) db->close(db);
2224 # else /* DB_VERSION_MAJOR < 2 */
2225                 errno = db->close(db, 0);
2226 # endif /* DB_VERSION_MAJOR < 2 */
2227 # if !LOCK_ON_OPEN
2228                 if (map->map_lockfd >= 0)
2229                         (void) close(map->map_lockfd);
2230 # endif /* !LOCK_ON_OPEN */
2231                 errno = save_errno;
2232                 syserr("db_map_open(%s): file changed after open", buf);
2233                 return false;
2234         }
2235
2236         if (mode == O_RDWR)
2237                 map->map_mflags |= MF_LOCKED;
2238 # if LOCK_ON_OPEN
2239         if (fd >= 0 && mode == O_RDONLY)
2240         {
2241                 (void) lockfile(fd, buf, NULL, LOCK_UN);
2242         }
2243 # endif /* LOCK_ON_OPEN */
2244
2245         /* try to make sure that at least the database header is on disk */
2246         if (mode == O_RDWR)
2247         {
2248                 (void) db->sync(db, 0);
2249                 if (geteuid() == 0 && TrustedUid != 0)
2250                 {
2251 #  if HASFCHOWN
2252                         if (fchown(fd, TrustedUid, -1) < 0)
2253                         {
2254                                 int err = errno;
2255
2256                                 sm_syslog(LOG_ALERT, NOQID,
2257                                           "ownership change on %s failed: %s",
2258                                           buf, sm_errstring(err));
2259                                 message("050 ownership change on %s failed: %s",
2260                                         buf, sm_errstring(err));
2261                         }
2262 #  else /* HASFCHOWN */
2263                         sm_syslog(LOG_ALERT, NOQID,
2264                                   "no fchown(): cannot change ownership on %s",
2265                                   map->map_file);
2266                         message("050 no fchown(): cannot change ownership on %s",
2267                                 map->map_file);
2268 #  endif /* HASFCHOWN */
2269                 }
2270         }
2271
2272         map->map_db2 = (ARBPTR_T) db;
2273
2274         /*
2275         **  Need to set map_mtime before the call to aliaswait()
2276         **  as aliaswait() will call map_lookup() which requires
2277         **  map_mtime to be set
2278         */
2279
2280         if (fd >= 0 && fstat(fd, &st) >= 0)
2281                 map->map_mtime = st.st_mtime;
2282
2283         if (mode == O_RDONLY && bitset(MF_ALIAS, map->map_mflags) &&
2284             !aliaswait(map, ".db", true))
2285                 return false;
2286         return true;
2287 }
2288
2289
2290 /*
2291 **  DB_MAP_LOOKUP -- look up a datum in a BTREE- or HASH-type map
2292 */
2293
2294 char *
2295 db_map_lookup(map, name, av, statp)
2296         MAP *map;
2297         char *name;
2298         char **av;
2299         int *statp;
2300 {
2301         DBT key, val;
2302         register DB *db = (DB *) map->map_db2;
2303         int i;
2304         int st;
2305         int save_errno;
2306         int fd;
2307         struct stat stbuf;
2308         char keybuf[MAXNAME + 1];
2309         char buf[MAXPATHLEN];
2310
2311         memset(&key, '\0', sizeof(key));
2312         memset(&val, '\0', sizeof(val));
2313
2314         if (tTd(38, 20))
2315                 sm_dprintf("db_map_lookup(%s, %s)\n",
2316                         map->map_mname, name);
2317
2318         if (sm_strlcpy(buf, map->map_file, sizeof(buf)) >= sizeof(buf))
2319         {
2320                 errno = 0;
2321                 if (!bitset(MF_OPTIONAL, map->map_mflags))
2322                         syserr("map \"%s\": map file %s name too long",
2323                                 map->map_mname, map->map_file);
2324                 return NULL;
2325         }
2326         i = strlen(buf);
2327         if (i > 3 && strcmp(&buf[i - 3], ".db") == 0)
2328                 buf[i - 3] = '\0';
2329
2330         key.size = strlen(name);
2331         if (key.size > sizeof(keybuf) - 1)
2332                 key.size = sizeof(keybuf) - 1;
2333         key.data = keybuf;
2334         memmove(keybuf, name, key.size);
2335         keybuf[key.size] = '\0';
2336         if (!bitset(MF_NOFOLDCASE, map->map_mflags))
2337                 makelower(keybuf);
2338   lockdb:
2339 # if DB_VERSION_MAJOR < 2
2340         fd = db->fd(db);
2341 # else /* DB_VERSION_MAJOR < 2 */
2342         fd = -1;
2343         errno = db->fd(db, &fd);
2344 # endif /* DB_VERSION_MAJOR < 2 */
2345         if (fd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
2346                 (void) lockfile(fd, buf, ".db", LOCK_SH);
2347         if (fd < 0 || fstat(fd, &stbuf) < 0 || stbuf.st_mtime > map->map_mtime)
2348         {
2349                 /* Reopen the database to sync the cache */
2350                 int omode = bitset(map->map_mflags, MF_WRITABLE) ? O_RDWR
2351                                                                  : O_RDONLY;
2352
2353                 if (fd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
2354                         (void) lockfile(fd, buf, ".db", LOCK_UN);
2355                 map->map_mflags |= MF_CLOSING;
2356                 map->map_class->map_close(map);
2357                 map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
2358                 if (map->map_class->map_open(map, omode))
2359                 {
2360                         map->map_mflags |= MF_OPEN;
2361                         map->map_pid = CurrentPid;
2362                         if ((omode && O_ACCMODE) == O_RDWR)
2363                                 map->map_mflags |= MF_WRITABLE;
2364                         db = (DB *) map->map_db2;
2365                         goto lockdb;
2366                 }
2367                 else
2368                 {
2369                         if (!bitset(MF_OPTIONAL, map->map_mflags))
2370                         {
2371                                 extern MAPCLASS BogusMapClass;
2372
2373                                 *statp = EX_TEMPFAIL;
2374                                 map->map_orgclass = map->map_class;
2375                                 map->map_class = &BogusMapClass;
2376                                 map->map_mflags |= MF_OPEN;
2377                                 map->map_pid = CurrentPid;
2378                                 syserr("Cannot reopen DB database %s",
2379                                         map->map_file);
2380                         }
2381                         return NULL;
2382                 }
2383         }
2384
2385         st = 1;
2386         if (bitset(MF_TRY0NULL, map->map_mflags))
2387         {
2388 # if DB_VERSION_MAJOR < 2
2389                 st = db->get(db, &key, &val, 0);
2390 # else /* DB_VERSION_MAJOR < 2 */
2391                 errno = db->get(db, NULL, &key, &val, 0);
2392                 switch (errno)
2393                 {
2394                   case DB_NOTFOUND:
2395                   case DB_KEYEMPTY:
2396                         st = 1;
2397                         break;
2398
2399                   case 0:
2400                         st = 0;
2401                         break;
2402
2403                   default:
2404                         st = -1;
2405                         break;
2406                 }
2407 # endif /* DB_VERSION_MAJOR < 2 */
2408                 if (st == 0)
2409                         map->map_mflags &= ~MF_TRY1NULL;
2410         }
2411         if (st != 0 && bitset(MF_TRY1NULL, map->map_mflags))
2412         {
2413                 key.size++;
2414 # if DB_VERSION_MAJOR < 2
2415                 st = db->get(db, &key, &val, 0);
2416 # else /* DB_VERSION_MAJOR < 2 */
2417                 errno = db->get(db, NULL, &key, &val, 0);
2418                 switch (errno)
2419                 {
2420                   case DB_NOTFOUND:
2421                   case DB_KEYEMPTY:
2422                         st = 1;
2423                         break;
2424
2425                   case 0:
2426                         st = 0;
2427                         break;
2428
2429                   default:
2430                         st = -1;
2431                         break;
2432                 }
2433 # endif /* DB_VERSION_MAJOR < 2 */
2434                 if (st == 0)
2435                         map->map_mflags &= ~MF_TRY0NULL;
2436         }
2437         save_errno = errno;
2438         if (fd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
2439                 (void) lockfile(fd, buf, ".db", LOCK_UN);
2440         if (st != 0)
2441         {
2442                 errno = save_errno;
2443                 if (st < 0)
2444                         syserr("db_map_lookup: get (%s)", name);
2445                 return NULL;
2446         }
2447         if (bitset(MF_MATCHONLY, map->map_mflags))
2448                 return map_rewrite(map, name, strlen(name), NULL);
2449         else
2450                 return map_rewrite(map, val.data, val.size, av);
2451 }
2452
2453
2454 /*
2455 **  DB_MAP_STORE -- store a datum in the NEWDB database
2456 */
2457
2458 void
2459 db_map_store(map, lhs, rhs)
2460         register MAP *map;
2461         char *lhs;
2462         char *rhs;
2463 {
2464         int status;
2465         DBT key;
2466         DBT data;
2467         register DB *db = map->map_db2;
2468         char keybuf[MAXNAME + 1];
2469
2470         memset(&key, '\0', sizeof(key));
2471         memset(&data, '\0', sizeof(data));
2472
2473         if (tTd(38, 12))
2474                 sm_dprintf("db_map_store(%s, %s, %s)\n",
2475                         map->map_mname, lhs, rhs);
2476
2477         key.size = strlen(lhs);
2478         key.data = lhs;
2479         if (!bitset(MF_NOFOLDCASE, map->map_mflags))
2480         {
2481                 if (key.size > sizeof(keybuf) - 1)
2482                         key.size = sizeof(keybuf) - 1;
2483                 memmove(keybuf, key.data, key.size);
2484                 keybuf[key.size] = '\0';
2485                 makelower(keybuf);
2486                 key.data = keybuf;
2487         }
2488
2489         data.size = strlen(rhs);
2490         data.data = rhs;
2491
2492         if (bitset(MF_INCLNULL, map->map_mflags))
2493         {
2494                 key.size++;
2495                 data.size++;
2496         }
2497
2498 # if DB_VERSION_MAJOR < 2
2499         status = db->put(db, &key, &data, R_NOOVERWRITE);
2500 # else /* DB_VERSION_MAJOR < 2 */
2501         errno = db->put(db, NULL, &key, &data, DB_NOOVERWRITE);
2502         switch (errno)
2503         {
2504           case DB_KEYEXIST:
2505                 status = 1;
2506                 break;
2507
2508           case 0:
2509                 status = 0;
2510                 break;
2511
2512           default:
2513                 status = -1;
2514                 break;
2515         }
2516 # endif /* DB_VERSION_MAJOR < 2 */
2517         if (status > 0)
2518         {
2519                 if (!bitset(MF_APPEND, map->map_mflags))
2520                         message("050 Warning: duplicate alias name %s", lhs);
2521                 else
2522                 {
2523                         static char *buf = NULL;
2524                         static int bufsiz = 0;
2525                         DBT old;
2526
2527                         memset(&old, '\0', sizeof(old));
2528
2529                         old.data = db_map_lookup(map, key.data,
2530                                                  (char **) NULL, &status);
2531                         if (old.data != NULL)
2532                         {
2533                                 old.size = strlen(old.data);
2534                                 if (data.size + old.size + 2 > (size_t) bufsiz)
2535                                 {
2536                                         if (buf != NULL)
2537                                                 sm_free(buf);
2538                                         bufsiz = data.size + old.size + 2;
2539                                         buf = sm_pmalloc_x(bufsiz);
2540                                 }
2541                                 (void) sm_strlcpyn(buf, bufsiz, 3,
2542                                         (char *) data.data, ",",
2543                                         (char *) old.data);
2544                                 data.size = data.size + old.size + 1;
2545                                 data.data = buf;
2546                                 if (tTd(38, 9))
2547                                         sm_dprintf("db_map_store append=%s\n",
2548                                                 (char *) data.data);
2549                         }
2550                 }
2551 # if DB_VERSION_MAJOR < 2
2552                 status = db->put(db, &key, &data, 0);
2553 # else /* DB_VERSION_MAJOR < 2 */
2554                 status = errno = db->put(db, NULL, &key, &data, 0);
2555 # endif /* DB_VERSION_MAJOR < 2 */
2556         }
2557         if (status != 0)
2558                 syserr("readaliases: db put (%s)", lhs);
2559 }
2560
2561
2562 /*
2563 **  DB_MAP_CLOSE -- add distinguished entries and close the database
2564 */
2565
2566 void
2567 db_map_close(map)
2568         MAP *map;
2569 {
2570         register DB *db = map->map_db2;
2571
2572         if (tTd(38, 9))
2573                 sm_dprintf("db_map_close(%s, %s, %lx)\n",
2574                         map->map_mname, map->map_file, map->map_mflags);
2575
2576         if (bitset(MF_WRITABLE, map->map_mflags))
2577         {
2578                 /* write out the distinguished alias */
2579                 db_map_store(map, "@", "@");
2580         }
2581
2582         (void) db->sync(db, 0);
2583
2584 # if !LOCK_ON_OPEN
2585         if (map->map_lockfd >= 0)
2586                 (void) close(map->map_lockfd);
2587 # endif /* !LOCK_ON_OPEN */
2588
2589 # if DB_VERSION_MAJOR < 2
2590         if (db->close(db) != 0)
2591 # else /* DB_VERSION_MAJOR < 2 */
2592         /*
2593         **  Berkeley DB can use internal shared memory
2594         **  locking for its memory pool.  Closing a map
2595         **  opened by another process will interfere
2596         **  with the shared memory and locks of the parent
2597         **  process leaving things in a bad state.
2598         */
2599
2600         /*
2601         **  If this map was not opened by the current
2602         **  process, do not close the map but recover
2603         **  the file descriptor.
2604         */
2605
2606         if (map->map_pid != CurrentPid)
2607         {
2608                 int fd = -1;
2609
2610                 errno = db->fd(db, &fd);
2611                 if (fd >= 0)
2612                         (void) close(fd);
2613                 return;
2614         }
2615
2616         if ((errno = db->close(db, 0)) != 0)
2617 # endif /* DB_VERSION_MAJOR < 2 */
2618                 syserr("db_map_close(%s, %s, %lx): db close failure",
2619                         map->map_mname, map->map_file, map->map_mflags);
2620 }
2621 #endif /* NEWDB */
2622 /*
2623 **  NIS Modules
2624 */
2625
2626 #if NIS
2627
2628 # ifndef YPERR_BUSY
2629 #  define YPERR_BUSY    16
2630 # endif /* ! YPERR_BUSY */
2631
2632 /*
2633 **  NIS_MAP_OPEN -- open DBM map
2634 */
2635
2636 bool
2637 nis_map_open(map, mode)
2638         MAP *map;
2639         int mode;
2640 {
2641         int yperr;
2642         register char *p;
2643         auto char *vp;
2644         auto int vsize;
2645
2646         if (tTd(38, 2))
2647                 sm_dprintf("nis_map_open(%s, %s, %d)\n",
2648                         map->map_mname, map->map_file, mode);
2649
2650         mode &= O_ACCMODE;
2651         if (mode != O_RDONLY)
2652         {
2653                 /* issue a pseudo-error message */
2654                 errno = SM_EMAPCANTWRITE;
2655                 return false;
2656         }
2657
2658         p = strchr(map->map_file, '@');
2659         if (p != NULL)
2660         {
2661                 *p++ = '\0';
2662                 if (*p != '\0')
2663                         map->map_domain = p;
2664         }
2665
2666         if (*map->map_file == '\0')
2667                 map->map_file = "mail.aliases";
2668
2669         if (map->map_domain == NULL)
2670         {
2671                 yperr = yp_get_default_domain(&map->map_domain);
2672                 if (yperr != 0)
2673                 {
2674                         if (!bitset(MF_OPTIONAL, map->map_mflags))
2675                                 syserr("451 4.3.5 NIS map %s specified, but NIS not running",
2676                                        map->map_file);
2677                         return false;
2678                 }
2679         }
2680
2681         /* check to see if this map actually exists */
2682         vp = NULL;
2683         yperr = yp_match(map->map_domain, map->map_file, "@", 1,
2684                         &vp, &vsize);
2685         if (tTd(38, 10))
2686                 sm_dprintf("nis_map_open: yp_match(@, %s, %s) => %s\n",
2687                         map->map_domain, map->map_file, yperr_string(yperr));
2688         if (vp != NULL)
2689                 sm_free(vp);
2690
2691         if (yperr == 0 || yperr == YPERR_KEY || yperr == YPERR_BUSY)
2692         {
2693                 /*
2694                 **  We ought to be calling aliaswait() here if this is an
2695                 **  alias file, but powerful HP-UX NIS servers  apparently
2696                 **  don't insert the @:@ token into the alias map when it
2697                 **  is rebuilt, so aliaswait() just hangs.  I hate HP-UX.
2698                 */
2699
2700 # if 0
2701                 if (!bitset(MF_ALIAS, map->map_mflags) ||
2702                     aliaswait(map, NULL, true))
2703 # endif /* 0 */
2704                         return true;
2705         }
2706
2707         if (!bitset(MF_OPTIONAL, map->map_mflags))
2708         {
2709                 syserr("451 4.3.5 Cannot bind to map %s in domain %s: %s",
2710                         map->map_file, map->map_domain, yperr_string(yperr));
2711         }
2712
2713         return false;
2714 }
2715
2716
2717 /*
2718 **  NIS_MAP_LOOKUP -- look up a datum in a NIS map
2719 */
2720
2721 /* ARGSUSED3 */
2722 char *
2723 nis_map_lookup(map, name, av, statp)
2724         MAP *map;
2725         char *name;
2726         char **av;
2727         int *statp;
2728 {
2729         char *vp;
2730         auto int vsize;
2731         int buflen;
2732         int yperr;
2733         char keybuf[MAXNAME + 1];
2734         char *SM_NONVOLATILE result = NULL;
2735
2736         if (tTd(38, 20))
2737                 sm_dprintf("nis_map_lookup(%s, %s)\n",
2738                         map->map_mname, name);
2739
2740         buflen = strlen(name);
2741         if (buflen > sizeof(keybuf) - 1)
2742                 buflen = sizeof(keybuf) - 1;
2743         memmove(keybuf, name, buflen);
2744         keybuf[buflen] = '\0';
2745         if (!bitset(MF_NOFOLDCASE, map->map_mflags))
2746                 makelower(keybuf);
2747         yperr = YPERR_KEY;
2748         vp = NULL;
2749         if (bitset(MF_TRY0NULL, map->map_mflags))
2750         {
2751                 yperr = yp_match(map->map_domain, map->map_file, keybuf, buflen,
2752                              &vp, &vsize);
2753                 if (yperr == 0)
2754                         map->map_mflags &= ~MF_TRY1NULL;
2755         }
2756         if (yperr == YPERR_KEY && bitset(MF_TRY1NULL, map->map_mflags))
2757         {
2758                 SM_FREE_CLR(vp);
2759                 buflen++;
2760                 yperr = yp_match(map->map_domain, map->map_file, keybuf, buflen,
2761                              &vp, &vsize);
2762                 if (yperr == 0)
2763                         map->map_mflags &= ~MF_TRY0NULL;
2764         }
2765         if (yperr != 0)
2766         {
2767                 if (yperr != YPERR_KEY && yperr != YPERR_BUSY)
2768                         map->map_mflags &= ~(MF_VALID|MF_OPEN);
2769                 if (vp != NULL)
2770                         sm_free(vp);
2771                 return NULL;
2772         }
2773         SM_TRY
2774                 if (bitset(MF_MATCHONLY, map->map_mflags))
2775                         result = map_rewrite(map, name, strlen(name), NULL);
2776                 else
2777                         result = map_rewrite(map, vp, vsize, av);
2778         SM_FINALLY
2779                 if (vp != NULL)
2780                         sm_free(vp);
2781         SM_END_TRY
2782         return result;
2783 }
2784
2785
2786 /*
2787 **  NIS_GETCANONNAME -- look up canonical name in NIS
2788 */
2789
2790 static bool
2791 nis_getcanonname(name, hbsize, statp)
2792         char *name;
2793         int hbsize;
2794         int *statp;
2795 {
2796         char *vp;
2797         auto int vsize;
2798         int keylen;
2799         int yperr;
2800         static bool try0null = true;
2801         static bool try1null = true;
2802         static char *yp_domain = NULL;
2803         char host_record[MAXLINE];
2804         char cbuf[MAXNAME];
2805         char nbuf[MAXNAME + 1];
2806
2807         if (tTd(38, 20))
2808                 sm_dprintf("nis_getcanonname(%s)\n", name);
2809
2810         if (sm_strlcpy(nbuf, name, sizeof(nbuf)) >= sizeof(nbuf))
2811         {
2812                 *statp = EX_UNAVAILABLE;
2813                 return false;
2814         }
2815         (void) shorten_hostname(nbuf);
2816         keylen = strlen(nbuf);
2817
2818         if (yp_domain == NULL)
2819                 (void) yp_get_default_domain(&yp_domain);
2820         makelower(nbuf);
2821         yperr = YPERR_KEY;
2822         vp = NULL;
2823         if (try0null)
2824         {
2825                 yperr = yp_match(yp_domain, "hosts.byname", nbuf, keylen,
2826                              &vp, &vsize);
2827                 if (yperr == 0)
2828                         try1null = false;
2829         }
2830         if (yperr == YPERR_KEY && try1null)
2831         {
2832                 SM_FREE_CLR(vp);
2833                 keylen++;
2834                 yperr = yp_match(yp_domain, "hosts.byname", nbuf, keylen,
2835                              &vp, &vsize);
2836                 if (yperr == 0)
2837                         try0null = false;
2838         }
2839         if (yperr != 0)
2840         {
2841                 if (yperr == YPERR_KEY)
2842                         *statp = EX_NOHOST;
2843                 else if (yperr == YPERR_BUSY)
2844                         *statp = EX_TEMPFAIL;
2845                 else
2846                         *statp = EX_UNAVAILABLE;
2847                 if (vp != NULL)
2848                         sm_free(vp);
2849                 return false;
2850         }
2851         (void) sm_strlcpy(host_record, vp, sizeof(host_record));
2852         sm_free(vp);
2853         if (tTd(38, 44))
2854                 sm_dprintf("got record `%s'\n", host_record);
2855         vp = strpbrk(host_record, "#\n");
2856         if (vp != NULL)
2857                 *vp = '\0';
2858         if (!extract_canonname(nbuf, NULL, host_record, cbuf, sizeof(cbuf)))
2859         {
2860                 /* this should not happen, but.... */
2861                 *statp = EX_NOHOST;
2862                 return false;
2863         }
2864         if (sm_strlcpy(name, cbuf, hbsize) >= hbsize)
2865         {
2866                 *statp = EX_UNAVAILABLE;
2867                 return false;
2868         }
2869         *statp = EX_OK;
2870         return true;
2871 }
2872
2873 #endif /* NIS */
2874 /*
2875 **  NISPLUS Modules
2876 **
2877 **      This code donated by Sun Microsystems.
2878 */
2879
2880 #if NISPLUS
2881
2882 # undef NIS             /* symbol conflict in nis.h */
2883 # undef T_UNSPEC        /* symbol conflict in nis.h -> ... -> sys/tiuser.h */
2884 # include <rpcsvc/nis.h>
2885 # include <rpcsvc/nislib.h>
2886
2887 # define EN_col(col)    zo_data.objdata_u.en_data.en_cols.en_cols_val[(col)].ec_value.ec_value_val
2888 # define COL_NAME(res,i)        ((res->objects.objects_val)->TA_data.ta_cols.ta_cols_val)[i].tc_name
2889 # define COL_MAX(res)   ((res->objects.objects_val)->TA_data.ta_cols.ta_cols_len)
2890 # define PARTIAL_NAME(x)        ((x)[strlen(x) - 1] != '.')
2891
2892 /*
2893 **  NISPLUS_MAP_OPEN -- open nisplus table
2894 */
2895
2896 bool
2897 nisplus_map_open(map, mode)
2898         MAP *map;
2899         int mode;
2900 {
2901         nis_result *res = NULL;
2902         int retry_cnt, max_col, i;
2903         char qbuf[MAXLINE + NIS_MAXNAMELEN];
2904
2905         if (tTd(38, 2))
2906                 sm_dprintf("nisplus_map_open(%s, %s, %d)\n",
2907                         map->map_mname, map->map_file, mode);
2908
2909         mode &= O_ACCMODE;
2910         if (mode != O_RDONLY)
2911         {
2912                 errno = EPERM;
2913                 return false;
2914         }
2915
2916         if (*map->map_file == '\0')
2917                 map->map_file = "mail_aliases.org_dir";
2918
2919         if (PARTIAL_NAME(map->map_file) && map->map_domain == NULL)
2920         {
2921                 /* set default NISPLUS Domain to $m */
2922                 map->map_domain = newstr(nisplus_default_domain());
2923                 if (tTd(38, 2))
2924                         sm_dprintf("nisplus_map_open(%s): using domain %s\n",
2925                                 map->map_file, map->map_domain);
2926         }
2927         if (!PARTIAL_NAME(map->map_file))
2928         {
2929                 map->map_domain = newstr("");
2930                 (void) sm_strlcpy(qbuf, map->map_file, sizeof(qbuf));
2931         }
2932         else
2933         {
2934                 /* check to see if this map actually exists */
2935                 (void) sm_strlcpyn(qbuf, sizeof(qbuf), 3,
2936                                    map->map_file, ".", map->map_domain);
2937         }
2938
2939         retry_cnt = 0;
2940         while (res == NULL || res->status != NIS_SUCCESS)
2941         {
2942                 res = nis_lookup(qbuf, FOLLOW_LINKS);
2943                 switch (res->status)
2944                 {
2945                   case NIS_SUCCESS:
2946                         break;
2947
2948                   case NIS_TRYAGAIN:
2949                   case NIS_RPCERROR:
2950                   case NIS_NAMEUNREACHABLE:
2951                         if (retry_cnt++ > 4)
2952                         {
2953                                 errno = EAGAIN;
2954                                 return false;
2955                         }
2956                         /* try not to overwhelm hosed server */
2957                         sleep(2);
2958                         break;
2959
2960                   default:              /* all other nisplus errors */
2961 # if 0
2962                         if (!bitset(MF_OPTIONAL, map->map_mflags))
2963                                 syserr("451 4.3.5 Cannot find table %s.%s: %s",
2964                                         map->map_file, map->map_domain,
2965                                         nis_sperrno(res->status));
2966 # endif /* 0 */
2967                         errno = EAGAIN;
2968                         return false;
2969                 }
2970         }
2971
2972         if (NIS_RES_NUMOBJ(res) != 1 ||
2973             (NIS_RES_OBJECT(res)->zo_data.zo_type != TABLE_OBJ))
2974         {
2975                 if (tTd(38, 10))
2976                         sm_dprintf("nisplus_map_open: %s is not a table\n", qbuf);
2977 # if 0
2978                 if (!bitset(MF_OPTIONAL, map->map_mflags))
2979                         syserr("451 4.3.5 %s.%s: %s is not a table",
2980                                 map->map_file, map->map_domain,
2981                                 nis_sperrno(res->status));
2982 # endif /* 0 */
2983                 errno = EBADF;
2984                 return false;
2985         }
2986         /* default key column is column 0 */
2987         if (map->map_keycolnm == NULL)
2988                 map->map_keycolnm = newstr(COL_NAME(res,0));
2989
2990         max_col = COL_MAX(res);
2991
2992         /* verify the key column exist */
2993         for (i = 0; i < max_col; i++)
2994         {
2995                 if (strcmp(map->map_keycolnm, COL_NAME(res,i)) == 0)
2996                         break;
2997         }
2998         if (i == max_col)
2999         {
3000                 if (tTd(38, 2))
3001                         sm_dprintf("nisplus_map_open(%s): can not find key column %s\n",
3002                                 map->map_file, map->map_keycolnm);
3003                 errno = ENOENT;
3004                 return false;
3005         }
3006
3007         /* default value column is the last column */
3008         if (map->map_valcolnm == NULL)
3009         {
3010                 map->map_valcolno = max_col - 1;
3011                 return true;
3012         }
3013
3014         for (i = 0; i< max_col; i++)
3015         {
3016                 if (strcmp(map->map_valcolnm, COL_NAME(res,i)) == 0)
3017                 {
3018                         map->map_valcolno = i;
3019                         return true;
3020                 }
3021         }
3022
3023         if (tTd(38, 2))
3024                 sm_dprintf("nisplus_map_open(%s): can not find column %s\n",
3025                         map->map_file, map->map_keycolnm);
3026         errno = ENOENT;
3027         return false;
3028 }
3029
3030
3031 /*
3032 **  NISPLUS_MAP_LOOKUP -- look up a datum in a NISPLUS table
3033 */
3034
3035 char *
3036 nisplus_map_lookup(map, name, av, statp)
3037         MAP *map;
3038         char *name;
3039         char **av;
3040         int *statp;
3041 {
3042         char *p;
3043         auto int vsize;
3044         char *skp;
3045         int skleft;
3046         char search_key[MAXNAME + 4];
3047         char qbuf[MAXLINE + NIS_MAXNAMELEN];
3048         nis_result *result;
3049
3050         if (tTd(38, 20))
3051                 sm_dprintf("nisplus_map_lookup(%s, %s)\n",
3052                         map->map_mname, name);
3053
3054         if (!bitset(MF_OPEN, map->map_mflags))
3055         {
3056                 if (nisplus_map_open(map, O_RDONLY))
3057                 {
3058                         map->map_mflags |= MF_OPEN;
3059                         map->map_pid = CurrentPid;
3060                 }
3061                 else
3062                 {
3063                         *statp = EX_UNAVAILABLE;
3064                         return NULL;
3065                 }
3066         }
3067
3068         /*
3069         **  Copy the name to the key buffer, escaping double quote characters
3070         **  by doubling them and quoting "]" and "," to avoid having the
3071         **  NIS+ parser choke on them.
3072         */
3073
3074         skleft = sizeof(search_key) - 4;
3075         skp = search_key;
3076         for (p = name; *p != '\0' && skleft > 0; p++)
3077         {
3078                 switch (*p)
3079                 {
3080                   case ']':
3081                   case ',':
3082                         /* quote the character */
3083                         *skp++ = '"';
3084                         *skp++ = *p;
3085                         *skp++ = '"';
3086                         skleft -= 3;
3087                         break;
3088
3089                   case '"':
3090                         /* double the quote */
3091                         *skp++ = '"';
3092                         skleft--;
3093                         /* FALLTHROUGH */
3094
3095                   default:
3096                         *skp++ = *p;
3097                         skleft--;
3098                         break;
3099                 }
3100         }
3101         *skp = '\0';
3102         if (!bitset(MF_NOFOLDCASE, map->map_mflags))
3103                 makelower(search_key);
3104
3105         /* construct the query */
3106         if (PARTIAL_NAME(map->map_file))
3107                 (void) sm_snprintf(qbuf, sizeof(qbuf), "[%s=%s],%s.%s",
3108                         map->map_keycolnm, search_key, map->map_file,
3109                         map->map_domain);
3110         else
3111                 (void) sm_snprintf(qbuf, sizeof(qbuf), "[%s=%s],%s",
3112                         map->map_keycolnm, search_key, map->map_file);
3113
3114         if (tTd(38, 20))
3115                 sm_dprintf("qbuf=%s\n", qbuf);
3116         result = nis_list(qbuf, FOLLOW_LINKS | FOLLOW_PATH, NULL, NULL);
3117         if (result->status == NIS_SUCCESS)
3118         {
3119                 int count;
3120                 char *str;
3121
3122                 if ((count = NIS_RES_NUMOBJ(result)) != 1)
3123                 {
3124                         if (LogLevel > 10)
3125                                 sm_syslog(LOG_WARNING, CurEnv->e_id,
3126                                           "%s: lookup error, expected 1 entry, got %d",
3127                                           map->map_file, count);
3128
3129                         /* ignore second entry */
3130                         if (tTd(38, 20))
3131                                 sm_dprintf("nisplus_map_lookup(%s), got %d entries, additional entries ignored\n",
3132                                         name, count);
3133                 }
3134
3135                 p = ((NIS_RES_OBJECT(result))->EN_col(map->map_valcolno));
3136                 /* set the length of the result */
3137                 if (p == NULL)
3138                         p = "";
3139                 vsize = strlen(p);
3140                 if (tTd(38, 20))
3141                         sm_dprintf("nisplus_map_lookup(%s), found %s\n",
3142                                 name, p);
3143                 if (bitset(MF_MATCHONLY, map->map_mflags))
3144                         str = map_rewrite(map, name, strlen(name), NULL);
3145                 else
3146                         str = map_rewrite(map, p, vsize, av);
3147                 nis_freeresult(result);
3148                 *statp = EX_OK;
3149                 return str;
3150         }
3151         else
3152         {
3153                 if (result->status == NIS_NOTFOUND)
3154                         *statp = EX_NOTFOUND;
3155                 else if (result->status == NIS_TRYAGAIN)
3156                         *statp = EX_TEMPFAIL;
3157                 else
3158                 {
3159                         *statp = EX_UNAVAILABLE;
3160                         map->map_mflags &= ~(MF_VALID|MF_OPEN);
3161                 }
3162         }
3163         if (tTd(38, 20))
3164                 sm_dprintf("nisplus_map_lookup(%s), failed\n", name);
3165         nis_freeresult(result);
3166         return NULL;
3167 }
3168
3169
3170
3171 /*
3172 **  NISPLUS_GETCANONNAME -- look up canonical name in NIS+
3173 */
3174
3175 static bool
3176 nisplus_getcanonname(name, hbsize, statp)
3177         char *name;
3178         int hbsize;
3179         int *statp;
3180 {
3181         char *vp;
3182         auto int vsize;
3183         nis_result *result;
3184         char *p;
3185         char nbuf[MAXNAME + 1];
3186         char qbuf[MAXLINE + NIS_MAXNAMELEN];
3187
3188         if (sm_strlcpy(nbuf, name, sizeof(nbuf)) >= sizeof(nbuf))
3189         {
3190                 *statp = EX_UNAVAILABLE;
3191                 return false;
3192         }
3193         (void) shorten_hostname(nbuf);
3194
3195         p = strchr(nbuf, '.');
3196         if (p == NULL)
3197         {
3198                 /* single token */
3199                 (void) sm_snprintf(qbuf, sizeof(qbuf),
3200                         "[name=%s],hosts.org_dir", nbuf);
3201         }
3202         else if (p[1] != '\0')
3203         {
3204                 /* multi token -- take only first token in nbuf */
3205                 *p = '\0';
3206                 (void) sm_snprintf(qbuf, sizeof(qbuf),
3207                                    "[name=%s],hosts.org_dir.%s", nbuf, &p[1]);
3208         }
3209         else
3210         {
3211                 *statp = EX_NOHOST;
3212                 return false;
3213         }
3214
3215         if (tTd(38, 20))
3216                 sm_dprintf("\nnisplus_getcanonname(%s), qbuf=%s\n",
3217                            name, qbuf);
3218
3219         result = nis_list(qbuf, EXPAND_NAME|FOLLOW_LINKS|FOLLOW_PATH,
3220                           NULL, NULL);
3221
3222         if (result->status == NIS_SUCCESS)
3223         {
3224                 int count;
3225                 char *domain;
3226
3227                 if ((count = NIS_RES_NUMOBJ(result)) != 1)
3228                 {
3229                         if (LogLevel > 10)
3230                                 sm_syslog(LOG_WARNING, CurEnv->e_id,
3231                                           "nisplus_getcanonname: lookup error, expected 1 entry, got %d",
3232                                           count);
3233
3234                         /* ignore second entry */
3235                         if (tTd(38, 20))
3236                                 sm_dprintf("nisplus_getcanonname(%s), got %d entries, all but first ignored\n",
3237                                            name, count);
3238                 }
3239
3240                 if (tTd(38, 20))
3241                         sm_dprintf("nisplus_getcanonname(%s), found in directory \"%s\"\n",
3242                                    name, (NIS_RES_OBJECT(result))->zo_domain);
3243
3244
3245                 vp = ((NIS_RES_OBJECT(result))->EN_col(0));
3246                 vsize = strlen(vp);
3247                 if (tTd(38, 20))
3248                         sm_dprintf("nisplus_getcanonname(%s), found %s\n",
3249                                    name, vp);
3250                 if (strchr(vp, '.') != NULL)
3251                 {
3252                         domain = "";
3253                 }
3254                 else
3255                 {
3256                         domain = macvalue('m', CurEnv);
3257                         if (domain == NULL)
3258                                 domain = "";
3259                 }
3260                 if (hbsize > vsize + (int) strlen(domain) + 1)
3261                 {
3262                         if (domain[0] == '\0')
3263                                 (void) sm_strlcpy(name, vp, hbsize);
3264                         else
3265                                 (void) sm_snprintf(name, hbsize,
3266                                                    "%s.%s", vp, domain);
3267                         *statp = EX_OK;
3268                 }
3269                 else
3270                         *statp = EX_NOHOST;
3271                 nis_freeresult(result);
3272                 return true;
3273         }
3274         else
3275         {
3276                 if (result->status == NIS_NOTFOUND)
3277                         *statp = EX_NOHOST;
3278                 else if (result->status == NIS_TRYAGAIN)
3279                         *statp = EX_TEMPFAIL;
3280                 else
3281                         *statp = EX_UNAVAILABLE;
3282         }
3283         if (tTd(38, 20))
3284                 sm_dprintf("nisplus_getcanonname(%s), failed, status=%d, nsw_stat=%d\n",
3285                            name, result->status, *statp);
3286         nis_freeresult(result);
3287         return false;
3288 }
3289
3290 char *
3291 nisplus_default_domain()
3292 {
3293         static char default_domain[MAXNAME + 1] = "";
3294         char *p;
3295
3296         if (default_domain[0] != '\0')
3297                 return default_domain;
3298
3299         p = nis_local_directory();
3300         (void) sm_strlcpy(default_domain, p, sizeof(default_domain));
3301         return default_domain;
3302 }
3303
3304 #endif /* NISPLUS */
3305 /*
3306 **  LDAP Modules
3307 */
3308
3309 /*
3310 **  LDAPMAP_DEQUOTE - helper routine for ldapmap_parseargs
3311 */
3312
3313 #if defined(LDAPMAP) || defined(PH_MAP)
3314
3315 # if PH_MAP
3316 #  define ph_map_dequote ldapmap_dequote
3317 # endif /* PH_MAP */
3318
3319 static char *ldapmap_dequote __P((char *));
3320
3321 static char *
3322 ldapmap_dequote(str)
3323         char *str;
3324 {
3325         char *p;
3326         char *start;
3327
3328         if (str == NULL)
3329                 return NULL;
3330
3331         p = str;
3332         if (*p == '"')
3333         {
3334                 /* Should probably swallow initial whitespace here */
3335                 start = ++p;
3336         }
3337         else
3338                 return str;
3339         while (*p != '"' && *p != '\0')
3340                 p++;
3341         if (*p != '\0')
3342                 *p = '\0';
3343         return start;
3344 }
3345 #endif /* defined(LDAPMAP) || defined(PH_MAP) */
3346
3347 #if LDAPMAP
3348
3349 static SM_LDAP_STRUCT *LDAPDefaults = NULL;
3350
3351 /*
3352 **  LDAPMAP_OPEN -- open LDAP map
3353 **
3354 **      Connect to the LDAP server.  Re-use existing connections since a
3355 **      single server connection to a host (with the same host, port,
3356 **      bind DN, and secret) can answer queries for multiple maps.
3357 */
3358
3359 bool
3360 ldapmap_open(map, mode)
3361         MAP *map;
3362         int mode;
3363 {
3364         SM_LDAP_STRUCT *lmap;
3365         STAB *s;
3366         char *id;
3367
3368         if (tTd(38, 2))
3369                 sm_dprintf("ldapmap_open(%s, %d): ", map->map_mname, mode);
3370
3371 #if defined(SUN_EXTENSIONS) && defined(SUN_SIMPLIFIED_LDAP) && \
3372     HASLDAPGETALIASBYNAME
3373         if (VendorCode == VENDOR_SUN &&
3374             strcmp(map->map_mname, "aliases.ldap") == 0)
3375         {
3376                 return true;
3377         }
3378 #endif /* defined(SUN_EXTENSIONS) && defined(SUN_SIMPLIFIED_LDAP) && ... */
3379
3380         mode &= O_ACCMODE;
3381
3382         /* sendmail doesn't have the ability to write to LDAP (yet) */
3383         if (mode != O_RDONLY)
3384         {
3385                 /* issue a pseudo-error message */
3386                 errno = SM_EMAPCANTWRITE;
3387                 return false;
3388         }
3389
3390         lmap = (SM_LDAP_STRUCT *) map->map_db1;
3391
3392         s = ldapmap_findconn(lmap);
3393         if (s->s_lmap != NULL)
3394         {
3395                 /* Already have a connection open to this LDAP server */
3396                 lmap->ldap_ld = ((SM_LDAP_STRUCT *)s->s_lmap->map_db1)->ldap_ld;
3397                 lmap->ldap_pid = ((SM_LDAP_STRUCT *)s->s_lmap->map_db1)->ldap_pid;
3398
3399                 /* Add this map as head of linked list */
3400                 lmap->ldap_next = s->s_lmap;
3401                 s->s_lmap = map;
3402
3403                 if (tTd(38, 2))
3404                         sm_dprintf("using cached connection\n");
3405                 return true;
3406         }
3407
3408         if (tTd(38, 2))
3409                 sm_dprintf("opening new connection\n");
3410
3411         if (lmap->ldap_host != NULL)
3412                 id = lmap->ldap_host;
3413         else if (lmap->ldap_uri != NULL)
3414                 id = lmap->ldap_uri;
3415         else
3416                 id = "localhost";
3417
3418         /* No connection yet, connect */
3419         if (!sm_ldap_start(map->map_mname, lmap))
3420         {
3421                 if (errno == ETIMEDOUT)
3422                 {
3423                         if (LogLevel > 1)
3424                                 sm_syslog(LOG_NOTICE, CurEnv->e_id,
3425                                           "timeout conning to LDAP server %.100s",
3426                                           id);
3427                 }
3428
3429                 if (!bitset(MF_OPTIONAL, map->map_mflags))
3430                 {
3431                         if (bitset(MF_NODEFER, map->map_mflags))
3432                         {
3433                                 syserr("%s failed to %s in map %s",
3434 # if USE_LDAP_INIT
3435                                        "ldap_init/ldap_bind",
3436 # else /* USE_LDAP_INIT */
3437                                        "ldap_open",
3438 # endif /* USE_LDAP_INIT */
3439                                        id, map->map_mname);
3440                         }
3441                         else
3442                         {
3443                                 syserr("451 4.3.5 %s failed to %s in map %s",
3444 # if USE_LDAP_INIT
3445                                        "ldap_init/ldap_bind",
3446 # else /* USE_LDAP_INIT */
3447                                        "ldap_open",
3448 # endif /* USE_LDAP_INIT */
3449                                        id, map->map_mname);
3450                         }
3451                 }
3452                 return false;
3453         }
3454
3455         /* Save connection for reuse */
3456         s->s_lmap = map;
3457         return true;
3458 }
3459
3460 /*
3461 **  LDAPMAP_CLOSE -- close ldap map
3462 */
3463
3464 void
3465 ldapmap_close(map)
3466         MAP *map;
3467 {
3468         SM_LDAP_STRUCT *lmap;
3469         STAB *s;
3470
3471         if (tTd(38, 2))
3472                 sm_dprintf("ldapmap_close(%s)\n", map->map_mname);
3473
3474         lmap = (SM_LDAP_STRUCT *) map->map_db1;
3475
3476         /* Check if already closed */
3477         if (lmap->ldap_ld == NULL)
3478                 return;
3479
3480         /* Close the LDAP connection */
3481         sm_ldap_close(lmap);
3482
3483         /* Mark all the maps that share the connection as closed */
3484         s = ldapmap_findconn(lmap);
3485
3486         while (s->s_lmap != NULL)
3487         {
3488                 MAP *smap = s->s_lmap;
3489
3490                 if (tTd(38, 2) && smap != map)
3491                         sm_dprintf("ldapmap_close(%s): closed %s (shared LDAP connection)\n",
3492                                    map->map_mname, smap->map_mname);
3493                 smap->map_mflags &= ~(MF_OPEN|MF_WRITABLE);
3494                 lmap = (SM_LDAP_STRUCT *) smap->map_db1;
3495                 lmap->ldap_ld = NULL;
3496                 s->s_lmap = lmap->ldap_next;
3497                 lmap->ldap_next = NULL;
3498         }
3499 }
3500
3501 # ifdef SUNET_ID
3502 /*
3503 **  SUNET_ID_HASH -- Convert a string to its Sunet_id canonical form
3504 **  This only makes sense at Stanford University.
3505 */
3506
3507 static char *
3508 sunet_id_hash(str)
3509         char *str;
3510 {
3511         char *p, *p_last;
3512
3513         p = str;
3514         p_last = p;
3515         while (*p != '\0')
3516         {
3517                 if (islower(*p) || isdigit(*p))
3518                 {
3519                         *p_last = *p;
3520                         p_last++;
3521                 }
3522                 else if (isupper(*p))
3523                 {
3524                         *p_last = tolower(*p);
3525                         p_last++;
3526                 }
3527                 ++p;
3528         }
3529         if (*p_last != '\0')
3530                 *p_last = '\0';
3531         return str;
3532 }
3533 #  define SM_CONVERT_ID(str)    sunet_id_hash(str)
3534 # else /* SUNET_ID */
3535 #  define SM_CONVERT_ID(str)    makelower(str)
3536 # endif /* SUNET_ID */
3537
3538 /*
3539 **  LDAPMAP_LOOKUP -- look up a datum in a LDAP map
3540 */
3541
3542 char *
3543 ldapmap_lookup(map, name, av, statp)
3544         MAP *map;
3545         char *name;
3546         char **av;
3547         int *statp;
3548 {
3549         int flags;
3550         int i;
3551         int plen = 0;
3552         int psize = 0;
3553         int msgid;
3554         int save_errno;
3555         char *vp, *p;
3556         char *result = NULL;
3557         SM_RPOOL_T *rpool;
3558         SM_LDAP_STRUCT *lmap = NULL;
3559         char *argv[SM_LDAP_ARGS];
3560         char keybuf[MAXKEY];
3561 #if SM_LDAP_ARGS != MAX_MAP_ARGS
3562 # ERROR _SM_LDAP_ARGS must be the same as _MAX_MAP_ARGS
3563 #endif /* SM_LDAP_ARGS != MAX_MAP_ARGS */
3564
3565 #if defined(SUN_EXTENSIONS) && defined(SUN_SIMPLIFIED_LDAP) && \
3566     HASLDAPGETALIASBYNAME
3567         if (VendorCode == VENDOR_SUN &&
3568             strcmp(map->map_mname, "aliases.ldap") == 0)
3569         {
3570                 int rc;
3571 #if defined(GETLDAPALIASBYNAME_VERSION) && (GETLDAPALIASBYNAME_VERSION >= 2)
3572                 extern char *__getldapaliasbyname();
3573                 char *answer;
3574
3575                 answer = __getldapaliasbyname(name, &rc);
3576 #else
3577                 char answer[MAXNAME + 1];
3578
3579                 rc = __getldapaliasbyname(name, answer, sizeof(answer));
3580 #endif
3581                 if (rc != 0)
3582                 {
3583                         if (tTd(38, 20))
3584                                 sm_dprintf("getldapaliasbyname(%.100s) failed, errno=%d\n",
3585                                            name, errno);
3586                         *statp = EX_NOTFOUND;
3587                         return NULL;
3588                 }
3589                 *statp = EX_OK;
3590                 if (tTd(38, 20))
3591                         sm_dprintf("getldapaliasbyname(%.100s) => %s\n", name,
3592                                    answer);
3593                 if (bitset(MF_MATCHONLY, map->map_mflags))
3594                         result = map_rewrite(map, name, strlen(name), NULL);
3595                 else
3596                         result = map_rewrite(map, answer, strlen(answer), av);
3597 #if defined(GETLDAPALIASBYNAME_VERSION) && (GETLDAPALIASBYNAME_VERSION >= 2)
3598                 free(answer);
3599 #endif
3600                 return result;
3601         }
3602 #endif /* defined(SUN_EXTENSIONS) && defined(SUN_SIMPLIFIED_LDAP) && ... */
3603
3604         /* Get ldap struct pointer from map */
3605         lmap = (SM_LDAP_STRUCT *) map->map_db1;
3606         sm_ldap_setopts(lmap->ldap_ld, lmap);
3607
3608         if (lmap->ldap_multi_args)
3609         {
3610                 SM_REQUIRE(av != NULL);
3611                 memset(argv, '\0', sizeof(argv));
3612                 for (i = 0; i < SM_LDAP_ARGS && av[i] != NULL; i++)
3613                 {
3614                         argv[i] = sm_strdup(av[i]);
3615                         if (argv[i] == NULL)
3616                         {
3617                                 int save_errno, j;
3618
3619                                 save_errno = errno;
3620                                 for (j = 0; j < i && argv[j] != NULL; j++)
3621                                         SM_FREE(argv[j]);
3622                                 *statp = EX_TEMPFAIL;
3623                                 errno = save_errno;
3624                                 return NULL;
3625                         }
3626
3627                         if (!bitset(MF_NOFOLDCASE, map->map_mflags))
3628                                 SM_CONVERT_ID(av[i]);
3629                 }
3630         }
3631         else
3632         {
3633                 (void) sm_strlcpy(keybuf, name, sizeof(keybuf));
3634
3635                 if (!bitset(MF_NOFOLDCASE, map->map_mflags))
3636                         SM_CONVERT_ID(keybuf);
3637         }
3638
3639         if (tTd(38, 20))
3640         {
3641                 if (lmap->ldap_multi_args)
3642                 {
3643                         sm_dprintf("ldapmap_lookup(%s, argv)\n",
3644                                 map->map_mname);
3645                         for (i = 0; i < SM_LDAP_ARGS; i++)
3646                         {
3647                                 sm_dprintf("   argv[%d] = %s\n", i,
3648                                            argv[i] == NULL ? "NULL" : argv[i]);
3649                         }
3650                 }
3651                 else
3652                 {
3653                         sm_dprintf("ldapmap_lookup(%s, %s)\n",
3654                                    map->map_mname, name);
3655                 }
3656         }
3657
3658         if (lmap->ldap_multi_args)
3659         {
3660                 msgid = sm_ldap_search_m(lmap, argv);
3661
3662                 /* free the argv array and its content, no longer needed */
3663                 for (i = 0; i < SM_LDAP_ARGS && argv[i] != NULL; i++)
3664                         SM_FREE(argv[i]);
3665         }
3666         else
3667                 msgid = sm_ldap_search(lmap, keybuf);
3668         if (msgid == SM_LDAP_ERR)
3669         {
3670                 errno = sm_ldap_geterrno(lmap->ldap_ld) + E_LDAPBASE;
3671                 save_errno = errno;
3672                 if (!bitset(MF_OPTIONAL, map->map_mflags))
3673                 {
3674                         /*
3675                         **  Do not include keybuf as this error may be shown
3676                         **  to outsiders.
3677                         */
3678
3679                         if (bitset(MF_NODEFER, map->map_mflags))
3680                                 syserr("Error in ldap_search in map %s",
3681                                        map->map_mname);
3682                         else
3683                                 syserr("451 4.3.5 Error in ldap_search in map %s",
3684                                        map->map_mname);
3685                 }
3686                 *statp = EX_TEMPFAIL;
3687                 switch (save_errno - E_LDAPBASE)
3688                 {
3689 # ifdef LDAP_SERVER_DOWN
3690                   case LDAP_SERVER_DOWN:
3691 # endif /* LDAP_SERVER_DOWN */
3692                   case LDAP_TIMEOUT:
3693                   case LDAP_UNAVAILABLE:
3694                         /* server disappeared, try reopen on next search */
3695                         ldapmap_close(map);
3696                         break;
3697                 }
3698                 errno = save_errno;
3699                 return NULL;
3700         }
3701 #if SM_LDAP_ERROR_ON_MISSING_ARGS
3702         else if (msgid == SM_LDAP_ERR_ARG_MISS)
3703         {
3704                 if (bitset(MF_NODEFER, map->map_mflags))
3705                         syserr("Error in ldap_search in map %s, too few arguments",
3706                                map->map_mname);
3707                 else
3708                         syserr("554 5.3.5 Error in ldap_search in map %s, too few arguments",
3709                                map->map_mname);
3710                 *statp = EX_CONFIG;
3711                 return NULL;
3712         }
3713 #endif /* SM_LDAP_ERROR_ON_MISSING_ARGS */
3714
3715         *statp = EX_NOTFOUND;
3716         vp = NULL;
3717
3718         flags = 0;
3719         if (bitset(MF_SINGLEMATCH, map->map_mflags))
3720                 flags |= SM_LDAP_SINGLEMATCH;
3721         if (bitset(MF_MATCHONLY, map->map_mflags))
3722                 flags |= SM_LDAP_MATCHONLY;
3723 # if _FFR_LDAP_SINGLEDN
3724         if (bitset(MF_SINGLEDN, map->map_mflags))
3725                 flags |= SM_LDAP_SINGLEDN;
3726 # endif /* _FFR_LDAP_SINGLEDN */
3727
3728         /* Create an rpool for search related memory usage */
3729         rpool = sm_rpool_new_x(NULL);
3730
3731         p = NULL;
3732         *statp = sm_ldap_results(lmap, msgid, flags, map->map_coldelim,
3733                                  rpool, &p, &plen, &psize, NULL);
3734         save_errno = errno;
3735
3736         /* Copy result so rpool can be freed */
3737         if (*statp == EX_OK && p != NULL)
3738                 vp = newstr(p);
3739         sm_rpool_free(rpool);
3740
3741         /* need to restart LDAP connection? */
3742         if (*statp == EX_RESTART)
3743         {
3744                 *statp = EX_TEMPFAIL;
3745                 ldapmap_close(map);
3746         }
3747
3748         errno = save_errno;
3749         if (*statp != EX_OK && *statp != EX_NOTFOUND)
3750         {
3751                 if (!bitset(MF_OPTIONAL, map->map_mflags))
3752                 {
3753                         if (bitset(MF_NODEFER, map->map_mflags))
3754                                 syserr("Error getting LDAP results in map %s",
3755                                        map->map_mname);
3756                         else
3757                                 syserr("451 4.3.5 Error getting LDAP results in map %s",
3758                                        map->map_mname);
3759                 }
3760                 errno = save_errno;
3761                 return NULL;
3762         }
3763
3764         /* Did we match anything? */
3765         if (vp == NULL && !bitset(MF_MATCHONLY, map->map_mflags))
3766                 return NULL;
3767
3768         if (*statp == EX_OK)
3769         {
3770                 if (LogLevel > 9)
3771                         sm_syslog(LOG_INFO, CurEnv->e_id,
3772                                   "ldap %.100s => %s", name,
3773                                   vp == NULL ? "<NULL>" : vp);
3774                 if (bitset(MF_MATCHONLY, map->map_mflags))
3775                         result = map_rewrite(map, name, strlen(name), NULL);
3776                 else
3777                 {
3778                         /* vp != NULL according to test above */
3779                         result = map_rewrite(map, vp, strlen(vp), av);
3780                 }
3781                 if (vp != NULL)
3782                         sm_free(vp); /* XXX */
3783         }
3784         return result;
3785 }
3786
3787 /*
3788 **  LDAPMAP_FINDCONN -- find an LDAP connection to the server
3789 **
3790 **      Cache LDAP connections based on the host, port, bind DN,
3791 **      secret, and PID so we don't have multiple connections open to
3792 **      the same server for different maps.  Need a separate connection
3793 **      per PID since a parent process may close the map before the
3794 **      child is done with it.
3795 **
3796 **      Parameters:
3797 **              lmap -- LDAP map information
3798 **
3799 **      Returns:
3800 **              Symbol table entry for the LDAP connection.
3801 */
3802
3803 static STAB *
3804 ldapmap_findconn(lmap)
3805         SM_LDAP_STRUCT *lmap;
3806 {
3807         char *format;
3808         char *nbuf;
3809         char *id;
3810         STAB *SM_NONVOLATILE s = NULL;
3811
3812         if (lmap->ldap_host != NULL)
3813                 id = lmap->ldap_host;
3814         else if (lmap->ldap_uri != NULL)
3815                 id = lmap->ldap_uri;
3816         else
3817                 id = "localhost";
3818
3819         format = "%s%c%d%c%d%c%s%c%s%d";
3820         nbuf = sm_stringf_x(format,
3821                             id,
3822                             CONDELSE,
3823                             lmap->ldap_port,
3824                             CONDELSE,
3825                             lmap->ldap_version,
3826                             CONDELSE,
3827                             (lmap->ldap_binddn == NULL ? ""
3828                                                        : lmap->ldap_binddn),
3829                             CONDELSE,
3830                             (lmap->ldap_secret == NULL ? ""
3831                                                        : lmap->ldap_secret),
3832                             (int) CurrentPid);
3833         SM_TRY
3834                 s = stab(nbuf, ST_LMAP, ST_ENTER);
3835         SM_FINALLY
3836                 sm_free(nbuf);
3837         SM_END_TRY
3838         return s;
3839 }
3840 /*
3841 **  LDAPMAP_PARSEARGS -- parse ldap map definition args.
3842 */
3843
3844 static struct lamvalues LDAPAuthMethods[] =
3845 {
3846         {       "none",         LDAP_AUTH_NONE          },
3847         {       "simple",       LDAP_AUTH_SIMPLE        },
3848 # ifdef LDAP_AUTH_KRBV4
3849         {       "krbv4",        LDAP_AUTH_KRBV4         },
3850 # endif /* LDAP_AUTH_KRBV4 */
3851         {       NULL,           0                       }
3852 };
3853
3854 static struct ladvalues LDAPAliasDereference[] =
3855 {
3856         {       "never",        LDAP_DEREF_NEVER        },
3857         {       "always",       LDAP_DEREF_ALWAYS       },
3858         {       "search",       LDAP_DEREF_SEARCHING    },
3859         {       "find",         LDAP_DEREF_FINDING      },
3860         {       NULL,           0                       }
3861 };
3862
3863 static struct lssvalues LDAPSearchScope[] =
3864 {
3865         {       "base",         LDAP_SCOPE_BASE         },
3866         {       "one",          LDAP_SCOPE_ONELEVEL     },
3867         {       "sub",          LDAP_SCOPE_SUBTREE      },
3868         {       NULL,           0                       }
3869 };
3870
3871 bool
3872 ldapmap_parseargs(map, args)
3873         MAP *map;
3874         char *args;
3875 {
3876         bool secretread = true;
3877         bool attrssetup = false;
3878         int i;
3879         register char *p = args;
3880         SM_LDAP_STRUCT *lmap;
3881         struct lamvalues *lam;
3882         struct ladvalues *lad;
3883         struct lssvalues *lss;
3884         char ldapfilt[MAXLINE];
3885         char m_tmp[MAXPATHLEN + LDAPMAP_MAX_PASSWD];
3886
3887         /* Get ldap struct pointer from map */
3888         lmap = (SM_LDAP_STRUCT *) map->map_db1;
3889
3890         /* Check if setting the initial LDAP defaults */
3891         if (lmap == NULL || lmap != LDAPDefaults)
3892         {
3893                 /* We need to alloc an SM_LDAP_STRUCT struct */
3894                 lmap = (SM_LDAP_STRUCT *) xalloc(sizeof(*lmap));
3895                 if (LDAPDefaults == NULL)
3896                         sm_ldap_clear(lmap);
3897                 else
3898                         STRUCTCOPY(*LDAPDefaults, *lmap);
3899         }
3900
3901         /* there is no check whether there is really an argument */
3902         map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL;
3903         map->map_spacesub = SpaceSub;   /* default value */
3904
3905         /* Check if setting up an alias or file class LDAP map */
3906         if (bitset(MF_ALIAS, map->map_mflags))
3907         {
3908                 /* Comma separate if used as an alias file */
3909                 map->map_coldelim = ',';
3910                 if (*args == '\0')
3911                 {
3912                         int n;
3913                         char *lc;
3914                         char jbuf[MAXHOSTNAMELEN];
3915                         char lcbuf[MAXLINE];
3916
3917                         /* Get $j */
3918                         expand("\201j", jbuf, sizeof(jbuf), &BlankEnvelope);
3919                         if (jbuf[0] == '\0')
3920                         {
3921                                 (void) sm_strlcpy(jbuf, "localhost",
3922                                                   sizeof(jbuf));
3923                         }
3924
3925                         lc = macvalue(macid("{sendmailMTACluster}"), CurEnv);
3926                         if (lc == NULL)
3927                                 lc = "";
3928                         else
3929                         {
3930                                 expand(lc, lcbuf, sizeof(lcbuf), CurEnv);
3931                                 lc = lcbuf;
3932                         }
3933
3934                         n = sm_snprintf(ldapfilt, sizeof(ldapfilt),
3935                                         "(&(objectClass=sendmailMTAAliasObject)(sendmailMTAAliasGrouping=aliases)(|(sendmailMTACluster=%s)(sendmailMTAHost=%s))(sendmailMTAKey=%%0))",
3936                                         lc, jbuf);
3937                         if (n >= sizeof(ldapfilt))
3938                         {
3939                                 syserr("%s: Default LDAP string too long",
3940                                        map->map_mname);
3941                                 return false;
3942                         }
3943
3944                         /* default args for an alias LDAP entry */
3945                         lmap->ldap_filter = ldapfilt;
3946                         lmap->ldap_attr[0] = "objectClass";
3947                         lmap->ldap_attr_type[0] = SM_LDAP_ATTR_OBJCLASS;
3948                         lmap->ldap_attr_needobjclass[0] = NULL;
3949                         lmap->ldap_attr[1] = "sendmailMTAAliasValue";
3950                         lmap->ldap_attr_type[1] = SM_LDAP_ATTR_NORMAL;
3951                         lmap->ldap_attr_needobjclass[1] = NULL;
3952                         lmap->ldap_attr[2] = "sendmailMTAAliasSearch";
3953                         lmap->ldap_attr_type[2] = SM_LDAP_ATTR_FILTER;
3954                         lmap->ldap_attr_needobjclass[2] = "sendmailMTAMapObject";
3955                         lmap->ldap_attr[3] = "sendmailMTAAliasURL";
3956                         lmap->ldap_attr_type[3] = SM_LDAP_ATTR_URL;
3957                         lmap->ldap_attr_needobjclass[3] = "sendmailMTAMapObject";
3958                         lmap->ldap_attr[4] = NULL;
3959                         lmap->ldap_attr_type[4] = SM_LDAP_ATTR_NONE;
3960                         lmap->ldap_attr_needobjclass[4] = NULL;
3961                         attrssetup = true;
3962                 }
3963         }
3964         else if (bitset(MF_FILECLASS, map->map_mflags))
3965         {
3966                 /* Space separate if used as a file class file */
3967                 map->map_coldelim = ' ';
3968         }
3969
3970         for (;;)
3971         {
3972                 while (isascii(*p) && isspace(*p))
3973                         p++;
3974                 if (*p != '-')
3975                         break;
3976                 switch (*++p)
3977                 {
3978                   case 'A':
3979                         map->map_mflags |= MF_APPEND;
3980                         break;
3981
3982                   case 'a':
3983                         map->map_app = ++p;
3984                         break;
3985
3986                   case 'D':
3987                         map->map_mflags |= MF_DEFER;
3988                         break;
3989
3990                   case 'f':
3991                         map->map_mflags |= MF_NOFOLDCASE;
3992                         break;
3993
3994                   case 'm':
3995                         map->map_mflags |= MF_MATCHONLY;
3996                         break;
3997
3998                   case 'N':
3999                         map->map_mflags |= MF_INCLNULL;
4000                         map->map_mflags &= ~MF_TRY0NULL;
4001                         break;
4002
4003                   case 'O':
4004                         map->map_mflags &= ~MF_TRY1NULL;
4005                         break;
4006
4007                   case 'o':
4008                         map->map_mflags |= MF_OPTIONAL;
4009                         break;
4010
4011                   case 'q':
4012                         map->map_mflags |= MF_KEEPQUOTES;
4013                         break;
4014
4015                   case 'S':
4016                         map->map_spacesub = *++p;
4017                         break;
4018
4019                   case 'T':
4020                         map->map_tapp = ++p;
4021                         break;
4022
4023                   case 't':
4024                         map->map_mflags |= MF_NODEFER;
4025                         break;
4026
4027                   case 'z':
4028                         if (*++p != '\\')
4029                                 map->map_coldelim = *p;
4030                         else
4031                         {
4032                                 switch (*++p)
4033                                 {
4034                                   case 'n':
4035                                         map->map_coldelim = '\n';
4036                                         break;
4037
4038                                   case 't':
4039                                         map->map_coldelim = '\t';
4040                                         break;
4041
4042                                   default:
4043                                         map->map_coldelim = '\\';
4044                                 }
4045                         }
4046                         break;
4047
4048                         /* Start of ldapmap specific args */
4049                   case '1':
4050                         map->map_mflags |= MF_SINGLEMATCH;
4051                         break;
4052
4053 # if _FFR_LDAP_SINGLEDN
4054                   case '2':
4055                         map->map_mflags |= MF_SINGLEDN;
4056                         break;
4057 # endif /* _FFR_LDAP_SINGLEDN */
4058
4059                   case 'b':             /* search base */
4060                         while (isascii(*++p) && isspace(*p))
4061                                 continue;
4062                         lmap->ldap_base = p;
4063                         break;
4064
4065 # if _FFR_LDAP_NETWORK_TIMEOUT
4066                   case 'c':             /* network (connect) timeout */
4067                         while (isascii(*++p) && isspace(*p))
4068                                 continue;
4069                         lmap->ldap_networktmo.tv_sec = atoi(p);
4070                         break;
4071 # endif /* _FFR_LDAP_NETWORK_TIMEOUT */
4072
4073                   case 'd':             /* Dn to bind to server as */
4074                         while (isascii(*++p) && isspace(*p))
4075                                 continue;
4076                         lmap->ldap_binddn = p;
4077                         break;
4078
4079                   case 'H':             /* Use LDAP URI */
4080 #  if !USE_LDAP_INIT
4081                         syserr("Must compile with -DUSE_LDAP_INIT to use LDAP URIs (-H) in map %s",
4082                                map->map_mname);
4083                         return false;
4084 #   else /* !USE_LDAP_INIT */
4085                         if (lmap->ldap_host != NULL)
4086                         {
4087                                 syserr("Can not specify both an LDAP host and an LDAP URI in map %s",
4088                                        map->map_mname);
4089                                 return false;
4090                         }
4091                         while (isascii(*++p) && isspace(*p))
4092                                 continue;
4093                         lmap->ldap_uri = p;
4094                         break;
4095 #  endif /* !USE_LDAP_INIT */
4096
4097                   case 'h':             /* ldap host */
4098                         while (isascii(*++p) && isspace(*p))
4099                                 continue;
4100                         if (lmap->ldap_uri != NULL)
4101                         {
4102                                 syserr("Can not specify both an LDAP host and an LDAP URI in map %s",
4103                                        map->map_mname);
4104                                 return false;
4105                         }
4106                         lmap->ldap_host = p;
4107                         break;
4108
4109                   case 'K':
4110                         lmap->ldap_multi_args = true;
4111                         break;
4112
4113                   case 'k':             /* search field */
4114                         while (isascii(*++p) && isspace(*p))
4115                                 continue;
4116                         lmap->ldap_filter = p;
4117                         break;
4118
4119                   case 'l':             /* time limit */
4120                         while (isascii(*++p) && isspace(*p))
4121                                 continue;
4122                         lmap->ldap_timelimit = atoi(p);
4123                         lmap->ldap_timeout.tv_sec = lmap->ldap_timelimit;
4124                         break;
4125
4126                   case 'M':             /* Method for binding */
4127                         while (isascii(*++p) && isspace(*p))
4128                                 continue;
4129
4130                         if (sm_strncasecmp(p, "LDAP_AUTH_", 10) == 0)
4131                                 p += 10;
4132
4133                         for (lam = LDAPAuthMethods;
4134                              lam != NULL && lam->lam_name != NULL; lam++)
4135                         {
4136                                 if (sm_strncasecmp(p, lam->lam_name,
4137                                                    strlen(lam->lam_name)) == 0)
4138                                         break;
4139                         }
4140                         if (lam->lam_name != NULL)
4141                                 lmap->ldap_method = lam->lam_code;
4142                         else
4143                         {
4144                                 /* bad config line */
4145                                 if (!bitset(MCF_OPTFILE,
4146                                             map->map_class->map_cflags))
4147                                 {
4148                                         char *ptr;
4149
4150                                         if ((ptr = strchr(p, ' ')) != NULL)
4151                                                 *ptr = '\0';
4152                                         syserr("Method for binding must be [none|simple|krbv4] (not %s) in map %s",
4153                                                 p, map->map_mname);
4154                                         if (ptr != NULL)
4155                                                 *ptr = ' ';
4156                                         return false;
4157                                 }
4158                         }
4159                         break;
4160
4161                   case 'n':             /* retrieve attribute names only */
4162                         lmap->ldap_attrsonly = LDAPMAP_TRUE;
4163                         break;
4164
4165                         /*
4166                         **  This is a string that is dependent on the
4167                         **  method used defined by 'M'.
4168                         */
4169
4170                   case 'P':             /* Secret password for binding */
4171                          while (isascii(*++p) && isspace(*p))
4172                                 continue;
4173                         lmap->ldap_secret = p;
4174                         secretread = false;
4175                         break;
4176
4177                   case 'p':             /* ldap port */
4178                         while (isascii(*++p) && isspace(*p))
4179                                 continue;
4180                         lmap->ldap_port = atoi(p);
4181                         break;
4182
4183                         /* args stolen from ldapsearch.c */
4184                   case 'R':             /* don't auto chase referrals */
4185 # ifdef LDAP_REFERRALS
4186                         lmap->ldap_options &= ~LDAP_OPT_REFERRALS;
4187 # else /* LDAP_REFERRALS */
4188                         syserr("compile with -DLDAP_REFERRALS for referral support");
4189 # endif /* LDAP_REFERRALS */
4190                         break;
4191
4192                   case 'r':             /* alias dereferencing */
4193                         while (isascii(*++p) && isspace(*p))
4194                                 continue;
4195
4196                         if (sm_strncasecmp(p, "LDAP_DEREF_", 11) == 0)
4197                                 p += 11;
4198
4199                         for (lad = LDAPAliasDereference;
4200                              lad != NULL && lad->lad_name != NULL; lad++)
4201                         {
4202                                 if (sm_strncasecmp(p, lad->lad_name,
4203                                                    strlen(lad->lad_name)) == 0)
4204                                         break;
4205                         }
4206                         if (lad->lad_name != NULL)
4207                                 lmap->ldap_deref = lad->lad_code;
4208                         else
4209                         {
4210                                 /* bad config line */
4211                                 if (!bitset(MCF_OPTFILE,
4212                                             map->map_class->map_cflags))
4213                                 {
4214                                         char *ptr;
4215
4216                                         if ((ptr = strchr(p, ' ')) != NULL)
4217                                                 *ptr = '\0';
4218                                         syserr("Deref must be [never|always|search|find] (not %s) in map %s",
4219                                                 p, map->map_mname);
4220                                         if (ptr != NULL)
4221                                                 *ptr = ' ';
4222                                         return false;
4223                                 }
4224                         }
4225                         break;
4226
4227                   case 's':             /* search scope */
4228                         while (isascii(*++p) && isspace(*p))
4229                                 continue;
4230
4231                         if (sm_strncasecmp(p, "LDAP_SCOPE_", 11) == 0)
4232                                 p += 11;
4233
4234                         for (lss = LDAPSearchScope;
4235                              lss != NULL && lss->lss_name != NULL; lss++)
4236                         {
4237                                 if (sm_strncasecmp(p, lss->lss_name,
4238                                                    strlen(lss->lss_name)) == 0)
4239                                         break;
4240                         }
4241                         if (lss->lss_name != NULL)
4242                                 lmap->ldap_scope = lss->lss_code;
4243                         else
4244                         {
4245                                 /* bad config line */
4246                                 if (!bitset(MCF_OPTFILE,
4247                                             map->map_class->map_cflags))
4248                                 {
4249                                         char *ptr;
4250
4251                                         if ((ptr = strchr(p, ' ')) != NULL)
4252                                                 *ptr = '\0';
4253                                         syserr("Scope must be [base|one|sub] (not %s) in map %s",
4254                                                 p, map->map_mname);
4255                                         if (ptr != NULL)
4256                                                 *ptr = ' ';
4257                                         return false;
4258                                 }
4259                         }
4260                         break;
4261
4262                   case 'V':
4263                         if (*++p != '\\')
4264                                 lmap->ldap_attrsep = *p;
4265                         else
4266                         {
4267                                 switch (*++p)
4268                                 {
4269                                   case 'n':
4270                                         lmap->ldap_attrsep = '\n';
4271                                         break;
4272
4273                                   case 't':
4274                                         lmap->ldap_attrsep = '\t';
4275                                         break;
4276
4277                                   default:
4278                                         lmap->ldap_attrsep = '\\';
4279                                 }
4280                         }
4281                         break;
4282
4283                   case 'v':             /* attr to return */
4284                         while (isascii(*++p) && isspace(*p))
4285                                 continue;
4286                         lmap->ldap_attr[0] = p;
4287                         lmap->ldap_attr[1] = NULL;
4288                         break;
4289
4290                   case 'w':
4291                         /* -w should be for passwd, -P should be for version */
4292                         while (isascii(*++p) && isspace(*p))
4293                                 continue;
4294                         lmap->ldap_version = atoi(p);
4295 # ifdef LDAP_VERSION_MAX
4296                         if (lmap->ldap_version > LDAP_VERSION_MAX)
4297                         {
4298                                 syserr("LDAP version %d exceeds max of %d in map %s",
4299                                        lmap->ldap_version, LDAP_VERSION_MAX,
4300                                        map->map_mname);
4301                                 return false;
4302                         }
4303 # endif /* LDAP_VERSION_MAX */
4304 # ifdef LDAP_VERSION_MIN
4305                         if (lmap->ldap_version < LDAP_VERSION_MIN)
4306                         {
4307                                 syserr("LDAP version %d is lower than min of %d in map %s",
4308                                        lmap->ldap_version, LDAP_VERSION_MIN,
4309                                        map->map_mname);
4310                                 return false;
4311                         }
4312 # endif /* LDAP_VERSION_MIN */
4313                         break;
4314
4315                   case 'Z':
4316                         while (isascii(*++p) && isspace(*p))
4317                                 continue;
4318                         lmap->ldap_sizelimit = atoi(p);
4319                         break;
4320
4321                   default:
4322                         syserr("Illegal option %c map %s", *p, map->map_mname);
4323                         break;
4324                 }
4325
4326                 /* need to account for quoted strings here */
4327                 while (*p != '\0' && !(isascii(*p) && isspace(*p)))
4328                 {
4329                         if (*p == '"')
4330                         {
4331                                 while (*++p != '"' && *p != '\0')
4332                                         continue;
4333                                 if (*p != '\0')
4334                                         p++;
4335                         }
4336                         else
4337                                 p++;
4338                 }
4339
4340                 if (*p != '\0')
4341                         *p++ = '\0';
4342         }
4343
4344         if (map->map_app != NULL)
4345                 map->map_app = newstr(ldapmap_dequote(map->map_app));
4346         if (map->map_tapp != NULL)
4347                 map->map_tapp = newstr(ldapmap_dequote(map->map_tapp));
4348
4349         /*
4350         **  We need to swallow up all the stuff into a struct
4351         **  and dump it into map->map_dbptr1
4352         */
4353
4354         if (lmap->ldap_host != NULL &&
4355             (LDAPDefaults == NULL ||
4356              LDAPDefaults == lmap ||
4357              LDAPDefaults->ldap_host != lmap->ldap_host))
4358                 lmap->ldap_host = newstr(ldapmap_dequote(lmap->ldap_host));
4359         map->map_domain = lmap->ldap_host;
4360
4361         if (lmap->ldap_uri != NULL &&
4362             (LDAPDefaults == NULL ||
4363              LDAPDefaults == lmap ||
4364              LDAPDefaults->ldap_uri != lmap->ldap_uri))
4365                 lmap->ldap_uri = newstr(ldapmap_dequote(lmap->ldap_uri));
4366         map->map_domain = lmap->ldap_uri;
4367
4368         if (lmap->ldap_binddn != NULL &&
4369             (LDAPDefaults == NULL ||
4370              LDAPDefaults == lmap ||
4371              LDAPDefaults->ldap_binddn != lmap->ldap_binddn))
4372                 lmap->ldap_binddn = newstr(ldapmap_dequote(lmap->ldap_binddn));
4373
4374         if (lmap->ldap_secret != NULL &&
4375             (LDAPDefaults == NULL ||
4376              LDAPDefaults == lmap ||
4377              LDAPDefaults->ldap_secret != lmap->ldap_secret))
4378         {
4379                 SM_FILE_T *sfd;
4380                 long sff = SFF_OPENASROOT|SFF_ROOTOK|SFF_NOWLINK|SFF_NOWWFILES|SFF_NOGWFILES;
4381
4382                 if (DontLockReadFiles)
4383                         sff |= SFF_NOLOCK;
4384
4385                 /* need to use method to map secret to passwd string */
4386                 switch (lmap->ldap_method)
4387                 {
4388                   case LDAP_AUTH_NONE:
4389                         /* Do nothing */
4390                         break;
4391
4392                   case LDAP_AUTH_SIMPLE:
4393
4394                         /*
4395                         **  Secret is the name of a file with
4396                         **  the first line as the password.
4397                         */
4398
4399                         /* Already read in the secret? */
4400                         if (secretread)
4401                                 break;
4402
4403                         sfd = safefopen(ldapmap_dequote(lmap->ldap_secret),
4404                                         O_RDONLY, 0, sff);
4405                         if (sfd == NULL)
4406                         {
4407                                 syserr("LDAP map: cannot open secret %s",
4408                                        ldapmap_dequote(lmap->ldap_secret));
4409                                 return false;
4410                         }
4411                         lmap->ldap_secret = sfgets(m_tmp, sizeof(m_tmp),
4412                                                    sfd, TimeOuts.to_fileopen,
4413                                                    "ldapmap_parseargs");
4414                         (void) sm_io_close(sfd, SM_TIME_DEFAULT);
4415                         if (strlen(m_tmp) > LDAPMAP_MAX_PASSWD)
4416                         {
4417                                 syserr("LDAP map: secret in %s too long",
4418                                        ldapmap_dequote(lmap->ldap_secret));
4419                                 return false;
4420                         }
4421                         if (lmap->ldap_secret != NULL &&
4422                             strlen(m_tmp) > 0)
4423                         {
4424                                 /* chomp newline */
4425                                 if (m_tmp[strlen(m_tmp) - 1] == '\n')
4426                                         m_tmp[strlen(m_tmp) - 1] = '\0';
4427
4428                                 lmap->ldap_secret = m_tmp;
4429                         }
4430                         break;
4431
4432 # ifdef LDAP_AUTH_KRBV4
4433                   case LDAP_AUTH_KRBV4:
4434
4435                         /*
4436                         **  Secret is where the ticket file is
4437                         **  stashed
4438                         */
4439
4440                         (void) sm_snprintf(m_tmp, sizeof(m_tmp),
4441                                 "KRBTKFILE=%s",
4442                                 ldapmap_dequote(lmap->ldap_secret));
4443                         lmap->ldap_secret = m_tmp;
4444                         break;
4445 # endif /* LDAP_AUTH_KRBV4 */
4446
4447                   default:             /* Should NEVER get here */
4448                         syserr("LDAP map: Illegal value in lmap method");
4449                         return false;
4450                         /* NOTREACHED */
4451                         break;
4452                 }
4453         }
4454
4455         if (lmap->ldap_secret != NULL &&
4456             (LDAPDefaults == NULL ||
4457              LDAPDefaults == lmap ||
4458              LDAPDefaults->ldap_secret != lmap->ldap_secret))
4459                 lmap->ldap_secret = newstr(ldapmap_dequote(lmap->ldap_secret));
4460
4461         if (lmap->ldap_base != NULL &&
4462             (LDAPDefaults == NULL ||
4463              LDAPDefaults == lmap ||
4464              LDAPDefaults->ldap_base != lmap->ldap_base))
4465                 lmap->ldap_base = newstr(ldapmap_dequote(lmap->ldap_base));
4466
4467         /*
4468         **  Save the server from extra work.  If request is for a single
4469         **  match, tell the server to only return enough records to
4470         **  determine if there is a single match or not.  This can not
4471         **  be one since the server would only return one and we wouldn't
4472         **  know if there were others available.
4473         */
4474
4475         if (bitset(MF_SINGLEMATCH, map->map_mflags))
4476                 lmap->ldap_sizelimit = 2;
4477
4478         /* If setting defaults, don't process ldap_filter and ldap_attr */
4479         if (lmap == LDAPDefaults)
4480                 return true;
4481
4482         if (lmap->ldap_filter != NULL)
4483                 lmap->ldap_filter = newstr(ldapmap_dequote(lmap->ldap_filter));
4484         else
4485         {
4486                 if (!bitset(MCF_OPTFILE, map->map_class->map_cflags))
4487                 {
4488                         syserr("No filter given in map %s", map->map_mname);
4489                         return false;
4490                 }
4491         }
4492
4493         if (!attrssetup && lmap->ldap_attr[0] != NULL)
4494         {
4495                 bool recurse = false;
4496                 bool normalseen = false;
4497
4498                 i = 0;
4499                 p = ldapmap_dequote(lmap->ldap_attr[0]);
4500                 lmap->ldap_attr[0] = NULL;
4501
4502                 /* Prime the attr list with the objectClass attribute */
4503                 lmap->ldap_attr[i] = "objectClass";
4504                 lmap->ldap_attr_type[i] = SM_LDAP_ATTR_OBJCLASS;
4505                 lmap->ldap_attr_needobjclass[i] = NULL;
4506                 i++;
4507
4508                 while (p != NULL)
4509                 {
4510                         char *v;
4511
4512                         while (isascii(*p) && isspace(*p))
4513                                 p++;
4514                         if (*p == '\0')
4515                                 break;
4516                         v = p;
4517                         p = strchr(v, ',');
4518                         if (p != NULL)
4519                                 *p++ = '\0';
4520
4521                         if (i >= LDAPMAP_MAX_ATTR)
4522                         {
4523                                 syserr("Too many return attributes in %s (max %d)",
4524                                        map->map_mname, LDAPMAP_MAX_ATTR);
4525                                 return false;
4526                         }
4527                         if (*v != '\0')
4528                         {
4529                                 int j;
4530                                 int use;
4531                                 char *type;
4532                                 char *needobjclass;
4533
4534                                 type = strchr(v, ':');
4535                                 if (type != NULL)
4536                                 {
4537                                         *type++ = '\0';
4538                                         needobjclass = strchr(type, ':');
4539                                         if (needobjclass != NULL)
4540                                                 *needobjclass++ = '\0';
4541                                 }
4542                                 else
4543                                 {
4544                                         needobjclass = NULL;
4545                                 }
4546
4547                                 use = i;
4548
4549                                 /* allow override on "objectClass" type */
4550                                 if (sm_strcasecmp(v, "objectClass") == 0 &&
4551                                     lmap->ldap_attr_type[0] == SM_LDAP_ATTR_OBJCLASS)
4552                                 {
4553                                         use = 0;
4554                                 }
4555                                 else
4556                                 {
4557                                         /*
4558                                         **  Don't add something to attribute
4559                                         **  list twice.
4560                                         */
4561
4562                                         for (j = 1; j < i; j++)
4563                                         {
4564                                                 if (sm_strcasecmp(v, lmap->ldap_attr[j]) == 0)
4565                                                 {
4566                                                         syserr("Duplicate attribute (%s) in %s",
4567                                                                v, map->map_mname);
4568                                                         return false;
4569                                                 }
4570                                         }
4571
4572                                         lmap->ldap_attr[use] = newstr(v);
4573                                         if (needobjclass != NULL &&
4574                                             *needobjclass != '\0' &&
4575                                             *needobjclass != '*')
4576                                         {
4577                                                 lmap->ldap_attr_needobjclass[use] = newstr(needobjclass);
4578                                         }
4579                                         else
4580                                         {
4581                                                 lmap->ldap_attr_needobjclass[use] = NULL;
4582                                         }
4583
4584                                 }
4585
4586                                 if (type != NULL && *type != '\0')
4587                                 {
4588                                         if (sm_strcasecmp(type, "dn") == 0)
4589                                         {
4590                                                 recurse = true;
4591                                                 lmap->ldap_attr_type[use] = SM_LDAP_ATTR_DN;
4592                                         }
4593                                         else if (sm_strcasecmp(type, "filter") == 0)
4594                                         {
4595                                                 recurse = true;
4596                                                 lmap->ldap_attr_type[use] = SM_LDAP_ATTR_FILTER;
4597                                         }
4598                                         else if (sm_strcasecmp(type, "url") == 0)
4599                                         {
4600                                                 recurse = true;
4601                                                 lmap->ldap_attr_type[use] = SM_LDAP_ATTR_URL;
4602                                         }
4603                                         else if (sm_strcasecmp(type, "normal") == 0)
4604                                         {
4605                                                 lmap->ldap_attr_type[use] = SM_LDAP_ATTR_NORMAL;
4606                                                 normalseen = true;
4607                                         }
4608                                         else
4609                                         {
4610                                                 syserr("Unknown attribute type (%s) in %s",
4611                                                        type, map->map_mname);
4612                                                 return false;
4613                                         }
4614                                 }
4615                                 else
4616                                 {
4617                                         lmap->ldap_attr_type[use] = SM_LDAP_ATTR_NORMAL;
4618                                         normalseen = true;
4619                                 }
4620                                 i++;
4621                         }
4622                 }
4623                 lmap->ldap_attr[i] = NULL;
4624
4625                 /* Set in case needed in future code */
4626                 attrssetup = true;
4627
4628                 if (recurse && !normalseen)
4629                 {
4630                         syserr("LDAP recursion requested in %s but no returnable attribute given",
4631                                map->map_mname);
4632                         return false;
4633                 }
4634                 if (recurse && lmap->ldap_attrsonly == LDAPMAP_TRUE)
4635                 {
4636                         syserr("LDAP recursion requested in %s can not be used with -n",
4637                                map->map_mname);
4638                         return false;
4639                 }
4640         }
4641         map->map_db1 = (ARBPTR_T) lmap;
4642         return true;
4643 }
4644
4645 /*
4646 **  LDAPMAP_SET_DEFAULTS -- Read default map spec from LDAPDefaults in .cf
4647 **
4648 **      Parameters:
4649 **              spec -- map argument string from LDAPDefaults option
4650 **
4651 **      Returns:
4652 **              None.
4653 */
4654
4655 void
4656 ldapmap_set_defaults(spec)
4657         char *spec;
4658 {
4659         STAB *class;
4660         MAP map;
4661
4662         /* Allocate and set the default values */
4663         if (LDAPDefaults == NULL)
4664                 LDAPDefaults = (SM_LDAP_STRUCT *) xalloc(sizeof(*LDAPDefaults));
4665         sm_ldap_clear(LDAPDefaults);
4666
4667         memset(&map, '\0', sizeof(map));
4668
4669         /* look up the class */
4670         class = stab("ldap", ST_MAPCLASS, ST_FIND);
4671         if (class == NULL)
4672         {
4673                 syserr("readcf: LDAPDefaultSpec: class ldap not available");
4674                 return;
4675         }
4676         map.map_class = &class->s_mapclass;
4677         map.map_db1 = (ARBPTR_T) LDAPDefaults;
4678         map.map_mname = "O LDAPDefaultSpec";
4679
4680         (void) ldapmap_parseargs(&map, spec);
4681
4682         /* These should never be set in LDAPDefaults */
4683         if (map.map_mflags != (MF_TRY0NULL|MF_TRY1NULL) ||
4684             map.map_spacesub != SpaceSub ||
4685             map.map_app != NULL ||
4686             map.map_tapp != NULL)
4687         {
4688                 syserr("readcf: option LDAPDefaultSpec: Do not set non-LDAP specific flags");
4689                 SM_FREE_CLR(map.map_app);
4690                 SM_FREE_CLR(map.map_tapp);
4691         }
4692
4693         if (LDAPDefaults->ldap_filter != NULL)
4694         {
4695                 syserr("readcf: option LDAPDefaultSpec: Do not set the LDAP search filter");
4696
4697                 /* don't free, it isn't malloc'ed in parseargs */
4698                 LDAPDefaults->ldap_filter = NULL;
4699         }
4700
4701         if (LDAPDefaults->ldap_attr[0] != NULL)
4702         {
4703                 syserr("readcf: option LDAPDefaultSpec: Do not set the requested LDAP attributes");
4704                 /* don't free, they aren't malloc'ed in parseargs */
4705                 LDAPDefaults->ldap_attr[0] = NULL;
4706         }
4707 }
4708 #endif /* LDAPMAP */
4709 /*
4710 **  PH map
4711 */
4712
4713 #if PH_MAP
4714
4715 /*
4716 **  Support for the CCSO Nameserver (ph/qi).
4717 **  This code is intended to replace the so-called "ph mailer".
4718 **  Contributed by Mark D. Roth.  Contact him for support.
4719 */
4720
4721 /* what version of the ph map code we're running */
4722 static char phmap_id[128];
4723
4724 /* sendmail version for phmap id string */
4725 extern const char Version[];
4726
4727 /* assume we're using nph-1.2.x if not specified */
4728 # ifndef NPH_VERSION
4729 #  define NPH_VERSION           10200
4730 # endif
4731
4732 /* compatibility for versions older than nph-1.2.0 */
4733 # if NPH_VERSION < 10200
4734 #  define PH_OPEN_ROUNDROBIN    PH_ROUNDROBIN
4735 #  define PH_OPEN_DONTID        PH_DONTID
4736 #  define PH_CLOSE_FAST         PH_FASTCLOSE
4737 #  define PH_ERR_DATAERR        PH_DATAERR
4738 #  define PH_ERR_NOMATCH        PH_NOMATCH
4739 # endif /* NPH_VERSION < 10200 */
4740
4741 /*
4742 **  PH_MAP_PARSEARGS -- parse ph map definition args.
4743 */
4744
4745 bool
4746 ph_map_parseargs(map, args)
4747         MAP *map;
4748         char *args;
4749 {
4750         register bool done;
4751         register char *p = args;
4752         PH_MAP_STRUCT *pmap = NULL;
4753
4754         /* initialize version string */
4755         (void) sm_snprintf(phmap_id, sizeof(phmap_id),
4756                            "sendmail-%s phmap-20010529 libphclient-%s",
4757                            Version, libphclient_version);
4758
4759         pmap = (PH_MAP_STRUCT *) xalloc(sizeof(*pmap));
4760
4761         /* defaults */
4762         pmap->ph_servers = NULL;
4763         pmap->ph_field_list = NULL;
4764         pmap->ph = NULL;
4765         pmap->ph_timeout = 0;
4766         pmap->ph_fastclose = 0;
4767
4768         map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL;
4769         for (;;)
4770         {
4771                 while (isascii(*p) && isspace(*p))
4772                         p++;
4773                 if (*p != '-')
4774                         break;
4775                 switch (*++p)
4776                 {
4777                   case 'N':
4778                         map->map_mflags |= MF_INCLNULL;
4779                         map->map_mflags &= ~MF_TRY0NULL;
4780                         break;
4781
4782                   case 'O':
4783                         map->map_mflags &= ~MF_TRY1NULL;
4784                         break;
4785
4786                   case 'o':
4787                         map->map_mflags |= MF_OPTIONAL;
4788                         break;
4789
4790                   case 'f':
4791                         map->map_mflags |= MF_NOFOLDCASE;
4792                         break;
4793
4794                   case 'm':
4795                         map->map_mflags |= MF_MATCHONLY;
4796                         break;
4797
4798                   case 'A':
4799                         map->map_mflags |= MF_APPEND;
4800                         break;
4801
4802                   case 'q':
4803                         map->map_mflags |= MF_KEEPQUOTES;
4804                         break;
4805
4806                   case 't':
4807                         map->map_mflags |= MF_NODEFER;
4808                         break;
4809
4810                   case 'a':
4811                         map->map_app = ++p;
4812                         break;
4813
4814                   case 'T':
4815                         map->map_tapp = ++p;
4816                         break;
4817
4818                   case 'l':
4819                         while (isascii(*++p) && isspace(*p))
4820                                 continue;
4821                         pmap->ph_timeout = atoi(p);
4822                         break;
4823
4824                   case 'S':
4825                         map->map_spacesub = *++p;
4826                         break;
4827
4828                   case 'D':
4829                         map->map_mflags |= MF_DEFER;
4830                         break;
4831
4832                   case 'h':             /* PH server list */
4833                         while (isascii(*++p) && isspace(*p))
4834                                 continue;
4835                         pmap->ph_servers = p;
4836                         break;
4837
4838                   case 'k':             /* fields to search for */
4839                         while (isascii(*++p) && isspace(*p))
4840                                 continue;
4841                         pmap->ph_field_list = p;
4842                         break;
4843
4844                   default:
4845                         syserr("ph_map_parseargs: unknown option -%c", *p);
4846                 }
4847
4848                 /* try to account for quoted strings */
4849                 done = isascii(*p) && isspace(*p);
4850                 while (*p != '\0' && !done)
4851                 {
4852                         if (*p == '"')
4853                         {
4854                                 while (*++p != '"' && *p != '\0')
4855                                         continue;
4856                                 if (*p != '\0')
4857                                         p++;
4858                         }
4859                         else
4860                                 p++;
4861                         done = isascii(*p) && isspace(*p);
4862                 }
4863
4864                 if (*p != '\0')
4865                         *p++ = '\0';
4866         }
4867
4868         if (map->map_app != NULL)
4869                 map->map_app = newstr(ph_map_dequote(map->map_app));
4870         if (map->map_tapp != NULL)
4871                 map->map_tapp = newstr(ph_map_dequote(map->map_tapp));
4872
4873         if (pmap->ph_field_list != NULL)
4874                 pmap->ph_field_list = newstr(ph_map_dequote(pmap->ph_field_list));
4875
4876         if (pmap->ph_servers != NULL)
4877                 pmap->ph_servers = newstr(ph_map_dequote(pmap->ph_servers));
4878         else
4879         {
4880                 syserr("ph_map_parseargs: -h flag is required");
4881                 return false;
4882         }
4883
4884         map->map_db1 = (ARBPTR_T) pmap;
4885         return true;
4886 }
4887
4888 /*
4889 **  PH_MAP_CLOSE -- close the connection to the ph server
4890 */
4891
4892 void
4893 ph_map_close(map)
4894         MAP *map;
4895 {
4896         PH_MAP_STRUCT *pmap;
4897
4898         pmap = (PH_MAP_STRUCT *)map->map_db1;
4899         if (tTd(38, 9))
4900                 sm_dprintf("ph_map_close(%s): pmap->ph_fastclose=%d\n",
4901                            map->map_mname, pmap->ph_fastclose);
4902
4903
4904         if (pmap->ph != NULL)
4905         {
4906                 ph_set_sendhook(pmap->ph, NULL);
4907                 ph_set_recvhook(pmap->ph, NULL);
4908                 ph_close(pmap->ph, pmap->ph_fastclose);
4909         }
4910
4911         map->map_mflags &= ~(MF_OPEN|MF_WRITABLE);
4912 }
4913
4914 static jmp_buf  PHTimeout;
4915
4916 /* ARGSUSED */
4917 static void
4918 ph_timeout(unused)
4919         int unused;
4920 {
4921         /*
4922         **  NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
4923         **      ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
4924         **      DOING.
4925         */
4926
4927         errno = ETIMEDOUT;
4928         longjmp(PHTimeout, 1);
4929 }
4930
4931 static void
4932 #if NPH_VERSION >= 10200
4933 ph_map_send_debug(appdata, text)
4934         void *appdata;
4935 #else
4936 ph_map_send_debug(text)
4937 #endif
4938         char *text;
4939 {
4940         if (LogLevel > 9)
4941                 sm_syslog(LOG_NOTICE, CurEnv->e_id,
4942                           "ph_map_send_debug: ==> %s", text);
4943         if (tTd(38, 20))
4944                 sm_dprintf("ph_map_send_debug: ==> %s\n", text);
4945 }
4946
4947 static void
4948 #if NPH_VERSION >= 10200
4949 ph_map_recv_debug(appdata, text)
4950         void *appdata;
4951 #else
4952 ph_map_recv_debug(text)
4953 #endif
4954         char *text;
4955 {
4956         if (LogLevel > 10)
4957                 sm_syslog(LOG_NOTICE, CurEnv->e_id,
4958                           "ph_map_recv_debug: <== %s", text);
4959         if (tTd(38, 21))
4960                 sm_dprintf("ph_map_recv_debug: <== %s\n", text);
4961 }
4962
4963 /*
4964 **  PH_MAP_OPEN -- sub for opening PH map
4965 */
4966 bool
4967 ph_map_open(map, mode)
4968         MAP *map;
4969         int mode;
4970 {
4971         PH_MAP_STRUCT *pmap;
4972         register SM_EVENT *ev = NULL;
4973         int save_errno = 0;
4974         char *hostlist, *host;
4975
4976         if (tTd(38, 2))
4977                 sm_dprintf("ph_map_open(%s)\n", map->map_mname);
4978
4979         mode &= O_ACCMODE;
4980         if (mode != O_RDONLY)
4981         {
4982                 /* issue a pseudo-error message */
4983                 errno = SM_EMAPCANTWRITE;
4984                 return false;
4985         }
4986
4987         if (CurEnv != NULL && CurEnv->e_sendmode == SM_DEFER &&
4988             bitset(MF_DEFER, map->map_mflags))
4989         {
4990                 if (tTd(9, 1))
4991                         sm_dprintf("ph_map_open(%s) => DEFERRED\n",
4992                                    map->map_mname);
4993
4994                 /*
4995                 **  Unset MF_DEFER here so that map_lookup() returns
4996                 **  a temporary failure using the bogus map and
4997                 **  map->map_tapp instead of the default permanent error.
4998                 */
4999
5000                 map->map_mflags &= ~MF_DEFER;
5001                 return false;
5002         }
5003
5004         pmap = (PH_MAP_STRUCT *)map->map_db1;
5005         pmap->ph_fastclose = 0;         /* refresh field for reopen */
5006
5007         /* try each host in the list */
5008         hostlist = newstr(pmap->ph_servers);
5009         for (host = strtok(hostlist, " ");
5010              host != NULL;
5011              host = strtok(NULL, " "))
5012         {
5013                 /* set timeout */
5014                 if (pmap->ph_timeout != 0)
5015                 {
5016                         if (setjmp(PHTimeout) != 0)
5017                         {
5018                                 ev = NULL;
5019                                 if (LogLevel > 1)
5020                                         sm_syslog(LOG_NOTICE, CurEnv->e_id,
5021                                                   "timeout connecting to PH server %.100s",
5022                                                   host);
5023                                 errno = ETIMEDOUT;
5024                                 goto ph_map_open_abort;
5025                         }
5026                         ev = sm_setevent(pmap->ph_timeout, ph_timeout, 0);
5027                 }
5028
5029                 /* open connection to server */
5030                 if (ph_open(&(pmap->ph), host,
5031                             PH_OPEN_ROUNDROBIN|PH_OPEN_DONTID,
5032                             ph_map_send_debug, ph_map_recv_debug
5033 #if NPH_VERSION >= 10200
5034                             , NULL
5035 #endif
5036                             ) == 0
5037                     && ph_id(pmap->ph, phmap_id) == 0)
5038                 {
5039                         if (ev != NULL)
5040                                 sm_clrevent(ev);
5041                         sm_free(hostlist); /* XXX */
5042                         return true;
5043                 }
5044
5045   ph_map_open_abort:
5046                 save_errno = errno;
5047                 if (ev != NULL)
5048                         sm_clrevent(ev);
5049                 pmap->ph_fastclose = PH_CLOSE_FAST;
5050                 ph_map_close(map);
5051                 errno = save_errno;
5052         }
5053
5054         if (bitset(MF_NODEFER, map->map_mflags))
5055         {
5056                 if (errno == 0)
5057                         errno = EAGAIN;
5058                 syserr("ph_map_open: %s: cannot connect to PH server",
5059                        map->map_mname);
5060         }
5061         else if (!bitset(MF_OPTIONAL, map->map_mflags) && LogLevel > 1)
5062                 sm_syslog(LOG_NOTICE, CurEnv->e_id,
5063                           "ph_map_open: %s: cannot connect to PH server",
5064                           map->map_mname);
5065         sm_free(hostlist); /* XXX */
5066         return false;
5067 }
5068
5069 /*
5070 **  PH_MAP_LOOKUP -- look up key from ph server
5071 */
5072
5073 char *
5074 ph_map_lookup(map, key, args, pstat)
5075         MAP *map;
5076         char *key;
5077         char **args;
5078         int *pstat;
5079 {
5080         int i, save_errno = 0;
5081         register SM_EVENT *ev = NULL;
5082         PH_MAP_STRUCT *pmap;
5083         char *value = NULL;
5084
5085         pmap = (PH_MAP_STRUCT *)map->map_db1;
5086
5087         *pstat = EX_OK;
5088
5089         /* set timeout */
5090         if (pmap->ph_timeout != 0)
5091         {
5092                 if (setjmp(PHTimeout) != 0)
5093                 {
5094                         ev = NULL;
5095                         if (LogLevel > 1)
5096                                 sm_syslog(LOG_NOTICE, CurEnv->e_id,
5097                                           "timeout during PH lookup of %.100s",
5098                                           key);
5099                         errno = ETIMEDOUT;
5100                         *pstat = EX_TEMPFAIL;
5101                         goto ph_map_lookup_abort;
5102                 }
5103                 ev = sm_setevent(pmap->ph_timeout, ph_timeout, 0);
5104         }
5105
5106         /* perform lookup */
5107         i = ph_email_resolve(pmap->ph, key, pmap->ph_field_list, &value);
5108         if (i == -1)
5109                 *pstat = EX_TEMPFAIL;
5110         else if (i == PH_ERR_NOMATCH || i == PH_ERR_DATAERR)
5111                 *pstat = EX_UNAVAILABLE;
5112
5113   ph_map_lookup_abort:
5114         if (ev != NULL)
5115                 sm_clrevent(ev);
5116
5117         /*
5118         **  Close the connection if the timer popped
5119         **  or we got a temporary PH error
5120         */
5121
5122         if (*pstat == EX_TEMPFAIL)
5123         {
5124                 save_errno = errno;
5125                 pmap->ph_fastclose = PH_CLOSE_FAST;
5126                 ph_map_close(map);
5127                 errno = save_errno;
5128         }
5129
5130         if (*pstat == EX_OK)
5131         {
5132                 if (tTd(38,20))
5133                         sm_dprintf("ph_map_lookup: %s => %s\n", key, value);
5134
5135                 if (bitset(MF_MATCHONLY, map->map_mflags))
5136                         return map_rewrite(map, key, strlen(key), NULL);
5137                 else
5138                         return map_rewrite(map, value, strlen(value), args);
5139         }
5140
5141         return NULL;
5142 }
5143 #endif /* PH_MAP */
5144
5145 /*
5146 **  syslog map
5147 */
5148
5149 #define map_prio        map_lockfd      /* overload field */
5150
5151 /*
5152 **  SYSLOG_MAP_PARSEARGS -- check for priority level to syslog messages.
5153 */
5154
5155 bool
5156 syslog_map_parseargs(map, args)
5157         MAP *map;
5158         char *args;
5159 {
5160         char *p = args;
5161         char *priority = NULL;
5162
5163         /* there is no check whether there is really an argument */
5164         while (*p != '\0')
5165         {
5166                 while (isascii(*p) && isspace(*p))
5167                         p++;
5168                 if (*p != '-')
5169                         break;
5170                 ++p;
5171                 if (*p == 'D')
5172                 {
5173                         map->map_mflags |= MF_DEFER;
5174                         ++p;
5175                 }
5176                 else if (*p == 'S')
5177                 {
5178                         map->map_spacesub = *++p;
5179                         if (*p != '\0')
5180                                 p++;
5181                 }
5182                 else if (*p == 'L')
5183                 {
5184                         while (*++p != '\0' && isascii(*p) && isspace(*p))
5185                                 continue;
5186                         if (*p == '\0')
5187                                 break;
5188                         priority = p;
5189                         while (*p != '\0' && !(isascii(*p) && isspace(*p)))
5190                                 p++;
5191                         if (*p != '\0')
5192                                 *p++ = '\0';
5193                 }
5194                 else
5195                 {
5196                         syserr("Illegal option %c map syslog", *p);
5197                         ++p;
5198                 }
5199         }
5200
5201         if (priority == NULL)
5202                 map->map_prio = LOG_INFO;
5203         else
5204         {
5205                 if (sm_strncasecmp("LOG_", priority, 4) == 0)
5206                         priority += 4;
5207
5208 #ifdef LOG_EMERG
5209                 if (sm_strcasecmp("EMERG", priority) == 0)
5210                         map->map_prio = LOG_EMERG;
5211                 else
5212 #endif /* LOG_EMERG */
5213 #ifdef LOG_ALERT
5214                 if (sm_strcasecmp("ALERT", priority) == 0)
5215                         map->map_prio = LOG_ALERT;
5216                 else
5217 #endif /* LOG_ALERT */
5218 #ifdef LOG_CRIT
5219                 if (sm_strcasecmp("CRIT", priority) == 0)
5220                         map->map_prio = LOG_CRIT;
5221                 else
5222 #endif /* LOG_CRIT */
5223 #ifdef LOG_ERR
5224                 if (sm_strcasecmp("ERR", priority) == 0)
5225                         map->map_prio = LOG_ERR;
5226                 else
5227 #endif /* LOG_ERR */
5228 #ifdef LOG_WARNING
5229                 if (sm_strcasecmp("WARNING", priority) == 0)
5230                         map->map_prio = LOG_WARNING;
5231                 else
5232 #endif /* LOG_WARNING */
5233 #ifdef LOG_NOTICE
5234                 if (sm_strcasecmp("NOTICE", priority) == 0)
5235                         map->map_prio = LOG_NOTICE;
5236                 else
5237 #endif /* LOG_NOTICE */
5238 #ifdef LOG_INFO
5239                 if (sm_strcasecmp("INFO", priority) == 0)
5240                         map->map_prio = LOG_INFO;
5241                 else
5242 #endif /* LOG_INFO */
5243 #ifdef LOG_DEBUG
5244                 if (sm_strcasecmp("DEBUG", priority) == 0)
5245                         map->map_prio = LOG_DEBUG;
5246                 else
5247 #endif /* LOG_DEBUG */
5248                 {
5249                         syserr("syslog_map_parseargs: Unknown priority %s",
5250                                priority);
5251                         return false;
5252                 }
5253         }
5254         return true;
5255 }
5256
5257 /*
5258 **  SYSLOG_MAP_LOOKUP -- rewrite and syslog message.  Always return empty string
5259 */
5260
5261 char *
5262 syslog_map_lookup(map, string, args, statp)
5263         MAP *map;
5264         char *string;
5265         char **args;
5266         int *statp;
5267 {
5268         char *ptr = map_rewrite(map, string, strlen(string), args);
5269
5270         if (ptr != NULL)
5271         {
5272                 if (tTd(38, 20))
5273                         sm_dprintf("syslog_map_lookup(%s (priority %d): %s\n",
5274                                 map->map_mname, map->map_prio, ptr);
5275
5276                 sm_syslog(map->map_prio, CurEnv->e_id, "%s", ptr);
5277         }
5278
5279         *statp = EX_OK;
5280         return "";
5281 }
5282
5283 #if _FFR_DPRINTF_MAP
5284 /*
5285 **  dprintf map
5286 */
5287
5288 #define map_dbg_level   map_lockfd      /* overload field */
5289
5290 /*
5291 **  DPRINTF_MAP_PARSEARGS -- check for priority level to dprintf messages.
5292 */
5293
5294 bool
5295 dprintf_map_parseargs(map, args)
5296         MAP *map;
5297         char *args;
5298 {
5299         char *p = args;
5300         char *dbg_level = NULL;
5301
5302         /* there is no check whether there is really an argument */
5303         while (*p != '\0')
5304         {
5305                 while (isascii(*p) && isspace(*p))
5306                         p++;
5307                 if (*p != '-')
5308                         break;
5309                 ++p;
5310                 if (*p == 'D')
5311                 {
5312                         map->map_mflags |= MF_DEFER;
5313                         ++p;
5314                 }
5315                 else if (*p == 'S')
5316                 {
5317                         map->map_spacesub = *++p;
5318                         if (*p != '\0')
5319                                 p++;
5320                 }
5321                 else if (*p == 'd')
5322                 {
5323                         while (*++p != '\0' && isascii(*p) && isspace(*p))
5324                                 continue;
5325                         if (*p == '\0')
5326                                 break;
5327                         dbg_level = p;
5328                         while (*p != '\0' && !(isascii(*p) && isspace(*p)))
5329                                 p++;
5330                         if (*p != '\0')
5331                                 *p++ = '\0';
5332                 }
5333                 else
5334                 {
5335                         syserr("Illegal option %c map dprintf", *p);
5336                         ++p;
5337                 }
5338         }
5339
5340         if (dbg_level == NULL)
5341                 map->map_dbg_level = 0;
5342         else
5343         {
5344                 if (!(isascii(*dbg_level) && isdigit(*dbg_level)))
5345                 {
5346                         syserr("dprintf map \"%s\", file %s: -d should specify a number, not %s",
5347                                 map->map_mname, map->map_file,
5348                                 dbg_level);
5349                         return false;
5350                 }
5351                 map->map_dbg_level = atoi(dbg_level);
5352         }
5353         return true;
5354 }
5355
5356 /*
5357 **  DPRINTF_MAP_LOOKUP -- rewrite and print message.  Always return empty string
5358 */
5359
5360 char *
5361 dprintf_map_lookup(map, string, args, statp)
5362         MAP *map;
5363         char *string;
5364         char **args;
5365         int *statp;
5366 {
5367         char *ptr = map_rewrite(map, string, strlen(string), args);
5368
5369         if (ptr != NULL && tTd(85, map->map_dbg_level))
5370                 sm_dprintf("%s\n", ptr);
5371         *statp = EX_OK;
5372         return "";
5373 }
5374 #endif /* _FFR_DPRINTF_MAP */
5375
5376 /*
5377 **  HESIOD Modules
5378 */
5379
5380 #if HESIOD
5381
5382 bool
5383 hes_map_open(map, mode)
5384         MAP *map;
5385         int mode;
5386 {
5387         if (tTd(38, 2))
5388                 sm_dprintf("hes_map_open(%s, %s, %d)\n",
5389                         map->map_mname, map->map_file, mode);
5390
5391         if (mode != O_RDONLY)
5392         {
5393                 /* issue a pseudo-error message */
5394                 errno = SM_EMAPCANTWRITE;
5395                 return false;
5396         }
5397
5398 # ifdef HESIOD_INIT
5399         if (HesiodContext != NULL || hesiod_init(&HesiodContext) == 0)
5400                 return true;
5401
5402         if (!bitset(MF_OPTIONAL, map->map_mflags))
5403                 syserr("451 4.3.5 cannot initialize Hesiod map (%s)",
5404                         sm_errstring(errno));
5405         return false;
5406 # else /* HESIOD_INIT */
5407         if (hes_error() == HES_ER_UNINIT)
5408                 hes_init();
5409         switch (hes_error())
5410         {
5411           case HES_ER_OK:
5412           case HES_ER_NOTFOUND:
5413                 return true;
5414         }
5415
5416         if (!bitset(MF_OPTIONAL, map->map_mflags))
5417                 syserr("451 4.3.5 cannot initialize Hesiod map (%d)", hes_error());
5418
5419         return false;
5420 # endif /* HESIOD_INIT */
5421 }
5422
5423 char *
5424 hes_map_lookup(map, name, av, statp)
5425         MAP *map;
5426         char *name;
5427         char **av;
5428         int *statp;
5429 {
5430         char **hp;
5431
5432         if (tTd(38, 20))
5433                 sm_dprintf("hes_map_lookup(%s, %s)\n", map->map_file, name);
5434
5435         if (name[0] == '\\')
5436         {
5437                 char *np;
5438                 int nl;
5439                 int save_errno;
5440                 char nbuf[MAXNAME];
5441
5442                 nl = strlen(name);
5443                 if (nl < sizeof(nbuf) - 1)
5444                         np = nbuf;
5445                 else
5446                         np = xalloc(strlen(name) + 2);
5447                 np[0] = '\\';
5448                 (void) sm_strlcpy(&np[1], name, (sizeof(nbuf)) - 1);
5449 # ifdef HESIOD_INIT
5450                 hp = hesiod_resolve(HesiodContext, np, map->map_file);
5451 # else /* HESIOD_INIT */
5452                 hp = hes_resolve(np, map->map_file);
5453 # endif /* HESIOD_INIT */
5454                 save_errno = errno;
5455                 if (np != nbuf)
5456                         sm_free(np); /* XXX */
5457                 errno = save_errno;
5458         }
5459         else
5460         {
5461 # ifdef HESIOD_INIT
5462                 hp = hesiod_resolve(HesiodContext, name, map->map_file);
5463 # else /* HESIOD_INIT */
5464                 hp = hes_resolve(name, map->map_file);
5465 # endif /* HESIOD_INIT */
5466         }
5467 # ifdef HESIOD_INIT
5468         if (hp == NULL || *hp == NULL)
5469         {
5470                 switch (errno)
5471                 {
5472                   case ENOENT:
5473                           *statp = EX_NOTFOUND;
5474                           break;
5475                   case ECONNREFUSED:
5476                           *statp = EX_TEMPFAIL;
5477                           break;
5478                   case EMSGSIZE:
5479                   case ENOMEM:
5480                   default:
5481                           *statp = EX_UNAVAILABLE;
5482                           break;
5483                 }
5484                 if (hp != NULL)
5485                         hesiod_free_list(HesiodContext, hp);
5486                 return NULL;
5487         }
5488 # else /* HESIOD_INIT */
5489         if (hp == NULL || hp[0] == NULL)
5490         {
5491                 switch (hes_error())
5492                 {
5493                   case HES_ER_OK:
5494                         *statp = EX_OK;
5495                         break;
5496
5497                   case HES_ER_NOTFOUND:
5498                         *statp = EX_NOTFOUND;
5499                         break;
5500
5501                   case HES_ER_CONFIG:
5502                         *statp = EX_UNAVAILABLE;
5503                         break;
5504
5505                   case HES_ER_NET:
5506                         *statp = EX_TEMPFAIL;
5507                         break;
5508                 }
5509                 return NULL;
5510         }
5511 # endif /* HESIOD_INIT */
5512
5513         if (bitset(MF_MATCHONLY, map->map_mflags))
5514                 return map_rewrite(map, name, strlen(name), NULL);
5515         else
5516                 return map_rewrite(map, hp[0], strlen(hp[0]), av);
5517 }
5518
5519 /*
5520 **  HES_MAP_CLOSE -- free the Hesiod context
5521 */
5522
5523 void
5524 hes_map_close(map)
5525         MAP *map;
5526 {
5527         if (tTd(38, 20))
5528                 sm_dprintf("hes_map_close(%s)\n", map->map_file);
5529
5530 # ifdef HESIOD_INIT
5531         /* Free the hesiod context */
5532         if (HesiodContext != NULL)
5533         {
5534                 hesiod_end(HesiodContext);
5535                 HesiodContext = NULL;
5536         }
5537 # endif /* HESIOD_INIT */
5538 }
5539
5540 #endif /* HESIOD */
5541 /*
5542 **  NeXT NETINFO Modules
5543 */
5544
5545 #if NETINFO
5546
5547 # define NETINFO_DEFAULT_DIR            "/aliases"
5548 # define NETINFO_DEFAULT_PROPERTY       "members"
5549
5550 /*
5551 **  NI_MAP_OPEN -- open NetInfo Aliases
5552 */
5553
5554 bool
5555 ni_map_open(map, mode)
5556         MAP *map;
5557         int mode;
5558 {
5559         if (tTd(38, 2))
5560                 sm_dprintf("ni_map_open(%s, %s, %d)\n",
5561                         map->map_mname, map->map_file, mode);
5562         mode &= O_ACCMODE;
5563
5564         if (*map->map_file == '\0')
5565                 map->map_file = NETINFO_DEFAULT_DIR;
5566
5567         if (map->map_valcolnm == NULL)
5568                 map->map_valcolnm = NETINFO_DEFAULT_PROPERTY;
5569
5570         if (map->map_coldelim == '\0')
5571         {
5572                 if (bitset(MF_ALIAS, map->map_mflags))
5573                         map->map_coldelim = ',';
5574                 else if (bitset(MF_FILECLASS, map->map_mflags))
5575                         map->map_coldelim = ' ';
5576         }
5577         return true;
5578 }
5579
5580
5581 /*
5582 **  NI_MAP_LOOKUP -- look up a datum in NetInfo
5583 */
5584
5585 char *
5586 ni_map_lookup(map, name, av, statp)
5587         MAP *map;
5588         char *name;
5589         char **av;
5590         int *statp;
5591 {
5592         char *res;
5593         char *propval;
5594
5595         if (tTd(38, 20))
5596                 sm_dprintf("ni_map_lookup(%s, %s)\n", map->map_mname, name);
5597
5598         propval = ni_propval(map->map_file, map->map_keycolnm, name,
5599                              map->map_valcolnm, map->map_coldelim);
5600
5601         if (propval == NULL)
5602                 return NULL;
5603
5604         SM_TRY
5605                 if (bitset(MF_MATCHONLY, map->map_mflags))
5606                         res = map_rewrite(map, name, strlen(name), NULL);
5607                 else
5608                         res = map_rewrite(map, propval, strlen(propval), av);
5609         SM_FINALLY
5610                 sm_free(propval);
5611         SM_END_TRY
5612         return res;
5613 }
5614
5615
5616 static bool
5617 ni_getcanonname(name, hbsize, statp)
5618         char *name;
5619         int hbsize;
5620         int *statp;
5621 {
5622         char *vptr;
5623         char *ptr;
5624         char nbuf[MAXNAME + 1];
5625
5626         if (tTd(38, 20))
5627                 sm_dprintf("ni_getcanonname(%s)\n", name);
5628
5629         if (sm_strlcpy(nbuf, name, sizeof(nbuf)) >= sizeof(nbuf))
5630         {
5631                 *statp = EX_UNAVAILABLE;
5632                 return false;
5633         }
5634         (void) shorten_hostname(nbuf);
5635
5636         /* we only accept single token search key */
5637         if (strchr(nbuf, '.'))
5638         {
5639                 *statp = EX_NOHOST;
5640                 return false;
5641         }
5642
5643         /* Do the search */
5644         vptr = ni_propval("/machines", NULL, nbuf, "name", '\n');
5645
5646         if (vptr == NULL)
5647         {
5648                 *statp = EX_NOHOST;
5649                 return false;
5650         }
5651
5652         /* Only want the first machine name */
5653         if ((ptr = strchr(vptr, '\n')) != NULL)
5654                 *ptr = '\0';
5655
5656         if (sm_strlcpy(name, vptr, hbsize) >= hbsize)
5657         {
5658                 sm_free(vptr);
5659                 *statp = EX_UNAVAILABLE;
5660                 return true;
5661         }
5662         sm_free(vptr);
5663         *statp = EX_OK;
5664         return false;
5665 }
5666 #endif /* NETINFO */
5667 /*
5668 **  TEXT (unindexed text file) Modules
5669 **
5670 **      This code donated by Sun Microsystems.
5671 */
5672
5673 #define map_sff         map_lockfd      /* overload field */
5674
5675
5676 /*
5677 **  TEXT_MAP_OPEN -- open text table
5678 */
5679
5680 bool
5681 text_map_open(map, mode)
5682         MAP *map;
5683         int mode;
5684 {
5685         long sff;
5686         int i;
5687
5688         if (tTd(38, 2))
5689                 sm_dprintf("text_map_open(%s, %s, %d)\n",
5690                         map->map_mname, map->map_file, mode);
5691
5692         mode &= O_ACCMODE;
5693         if (mode != O_RDONLY)
5694         {
5695                 errno = EPERM;
5696                 return false;
5697         }
5698
5699         if (*map->map_file == '\0')
5700         {
5701                 syserr("text map \"%s\": file name required",
5702                         map->map_mname);
5703                 return false;
5704         }
5705
5706         if (map->map_file[0] != '/')
5707         {
5708                 syserr("text map \"%s\": file name must be fully qualified",
5709                         map->map_mname);
5710                 return false;
5711         }
5712
5713         sff = SFF_ROOTOK|SFF_REGONLY;
5714         if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
5715                 sff |= SFF_NOWLINK;
5716         if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail))
5717                 sff |= SFF_SAFEDIRPATH;
5718         if ((i = safefile(map->map_file, RunAsUid, RunAsGid, RunAsUserName,
5719                           sff, S_IRUSR, NULL)) != 0)
5720         {
5721                 int save_errno = errno;
5722
5723                 /* cannot open this map */
5724                 if (tTd(38, 2))
5725                         sm_dprintf("\tunsafe map file: %d\n", i);
5726                 errno = save_errno;
5727                 if (!bitset(MF_OPTIONAL, map->map_mflags))
5728                         syserr("text map \"%s\": unsafe map file %s",
5729                                 map->map_mname, map->map_file);
5730                 return false;
5731         }
5732
5733         if (map->map_keycolnm == NULL)
5734                 map->map_keycolno = 0;
5735         else
5736         {
5737                 if (!(isascii(*map->map_keycolnm) && isdigit(*map->map_keycolnm)))
5738                 {
5739                         syserr("text map \"%s\", file %s: -k should specify a number, not %s",
5740                                 map->map_mname, map->map_file,
5741                                 map->map_keycolnm);
5742                         return false;
5743                 }
5744                 map->map_keycolno = atoi(map->map_keycolnm);
5745         }
5746
5747         if (map->map_valcolnm == NULL)
5748                 map->map_valcolno = 0;
5749         else
5750         {
5751                 if (!(isascii(*map->map_valcolnm) && isdigit(*map->map_valcolnm)))
5752                 {
5753                         syserr("text map \"%s\", file %s: -v should specify a number, not %s",
5754                                         map->map_mname, map->map_file,
5755                                         map->map_valcolnm);
5756                         return false;
5757                 }
5758                 map->map_valcolno = atoi(map->map_valcolnm);
5759         }
5760
5761         if (tTd(38, 2))
5762         {
5763                 sm_dprintf("text_map_open(%s, %s): delimiter = ",
5764                         map->map_mname, map->map_file);
5765                 if (map->map_coldelim == '\0')
5766                         sm_dprintf("(white space)\n");
5767                 else
5768                         sm_dprintf("%c\n", map->map_coldelim);
5769         }
5770
5771         map->map_sff = sff;
5772         return true;
5773 }
5774
5775
5776 /*
5777 **  TEXT_MAP_LOOKUP -- look up a datum in a TEXT table
5778 */
5779
5780 char *
5781 text_map_lookup(map, name, av, statp)
5782         MAP *map;
5783         char *name;
5784         char **av;
5785         int *statp;
5786 {
5787         char *vp;
5788         auto int vsize;
5789         int buflen;
5790         SM_FILE_T *f;
5791         char delim;
5792         int key_idx;
5793         bool found_it;
5794         long sff = map->map_sff;
5795         char search_key[MAXNAME + 1];
5796         char linebuf[MAXLINE];
5797         char buf[MAXNAME + 1];
5798
5799         found_it = false;
5800         if (tTd(38, 20))
5801                 sm_dprintf("text_map_lookup(%s, %s)\n", map->map_mname,  name);
5802
5803         buflen = strlen(name);
5804         if (buflen > sizeof(search_key) - 1)
5805                 buflen = sizeof(search_key) - 1;        /* XXX just cut if off? */
5806         memmove(search_key, name, buflen);
5807         search_key[buflen] = '\0';
5808         if (!bitset(MF_NOFOLDCASE, map->map_mflags))
5809                 makelower(search_key);
5810
5811         f = safefopen(map->map_file, O_RDONLY, FileMode, sff);
5812         if (f == NULL)
5813         {
5814                 map->map_mflags &= ~(MF_VALID|MF_OPEN);
5815                 *statp = EX_UNAVAILABLE;
5816                 return NULL;
5817         }
5818         key_idx = map->map_keycolno;
5819         delim = map->map_coldelim;
5820         while (sm_io_fgets(f, SM_TIME_DEFAULT,
5821                            linebuf, sizeof(linebuf)) != NULL)
5822         {
5823                 char *p;
5824
5825                 /* skip comment line */
5826                 if (linebuf[0] == '#')
5827                         continue;
5828                 p = strchr(linebuf, '\n');
5829                 if (p != NULL)
5830                         *p = '\0';
5831                 p = get_column(linebuf, key_idx, delim, buf, sizeof(buf));
5832                 if (p != NULL && sm_strcasecmp(search_key, p) == 0)
5833                 {
5834                         found_it = true;
5835                         break;
5836                 }
5837         }
5838         (void) sm_io_close(f, SM_TIME_DEFAULT);
5839         if (!found_it)
5840         {
5841                 *statp = EX_NOTFOUND;
5842                 return NULL;
5843         }
5844         vp = get_column(linebuf, map->map_valcolno, delim, buf, sizeof(buf));
5845         if (vp == NULL)
5846         {
5847                 *statp = EX_NOTFOUND;
5848                 return NULL;
5849         }
5850         vsize = strlen(vp);
5851         *statp = EX_OK;
5852         if (bitset(MF_MATCHONLY, map->map_mflags))
5853                 return map_rewrite(map, name, strlen(name), NULL);
5854         else
5855                 return map_rewrite(map, vp, vsize, av);
5856 }
5857
5858 /*
5859 **  TEXT_GETCANONNAME -- look up canonical name in hosts file
5860 */
5861
5862 static bool
5863 text_getcanonname(name, hbsize, statp)
5864         char *name;
5865         int hbsize;
5866         int *statp;
5867 {
5868         bool found;
5869         char *dot;
5870         SM_FILE_T *f;
5871         char linebuf[MAXLINE];
5872         char cbuf[MAXNAME + 1];
5873         char nbuf[MAXNAME + 1];
5874
5875         if (tTd(38, 20))
5876                 sm_dprintf("text_getcanonname(%s)\n", name);
5877
5878         if (sm_strlcpy(nbuf, name, sizeof(nbuf)) >= sizeof(nbuf))
5879         {
5880                 *statp = EX_UNAVAILABLE;
5881                 return false;
5882         }
5883         dot = shorten_hostname(nbuf);
5884
5885         f = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, HostsFile, SM_IO_RDONLY,
5886                        NULL);
5887         if (f == NULL)
5888         {
5889                 *statp = EX_UNAVAILABLE;
5890                 return false;
5891         }
5892         found = false;
5893         while (!found &&
5894                 sm_io_fgets(f, SM_TIME_DEFAULT,
5895                             linebuf, sizeof(linebuf)) != NULL)
5896         {
5897                 char *p = strpbrk(linebuf, "#\n");
5898
5899                 if (p != NULL)
5900                         *p = '\0';
5901                 if (linebuf[0] != '\0')
5902                         found = extract_canonname(nbuf, dot, linebuf,
5903                                                   cbuf, sizeof(cbuf));
5904         }
5905         (void) sm_io_close(f, SM_TIME_DEFAULT);
5906         if (!found)
5907         {
5908                 *statp = EX_NOHOST;
5909                 return false;
5910         }
5911
5912         if (sm_strlcpy(name, cbuf, hbsize) >= hbsize)
5913         {
5914                 *statp = EX_UNAVAILABLE;
5915                 return false;
5916         }
5917         *statp = EX_OK;
5918         return true;
5919 }
5920 /*
5921 **  STAB (Symbol Table) Modules
5922 */
5923
5924
5925 /*
5926 **  STAB_MAP_LOOKUP -- look up alias in symbol table
5927 */
5928
5929 /* ARGSUSED2 */
5930 char *
5931 stab_map_lookup(map, name, av, pstat)
5932         register MAP *map;
5933         char *name;
5934         char **av;
5935         int *pstat;
5936 {
5937         register STAB *s;
5938
5939         if (tTd(38, 20))
5940                 sm_dprintf("stab_lookup(%s, %s)\n",
5941                         map->map_mname, name);
5942
5943         s = stab(name, ST_ALIAS, ST_FIND);
5944         if (s == NULL)
5945                 return NULL;
5946         if (bitset(MF_MATCHONLY, map->map_mflags))
5947                 return map_rewrite(map, name, strlen(name), NULL);
5948         else
5949                 return map_rewrite(map, s->s_alias, strlen(s->s_alias), av);
5950 }
5951
5952 /*
5953 **  STAB_MAP_STORE -- store in symtab (actually using during init, not rebuild)
5954 */
5955
5956 void
5957 stab_map_store(map, lhs, rhs)
5958         register MAP *map;
5959         char *lhs;
5960         char *rhs;
5961 {
5962         register STAB *s;
5963
5964         s = stab(lhs, ST_ALIAS, ST_ENTER);
5965         s->s_alias = newstr(rhs);
5966 }
5967
5968
5969 /*
5970 **  STAB_MAP_OPEN -- initialize (reads data file)
5971 **
5972 **      This is a wierd case -- it is only intended as a fallback for
5973 **      aliases.  For this reason, opens for write (only during a
5974 **      "newaliases") always fails, and opens for read open the
5975 **      actual underlying text file instead of the database.
5976 */
5977
5978 bool
5979 stab_map_open(map, mode)
5980         register MAP *map;
5981         int mode;
5982 {
5983         SM_FILE_T *af;
5984         long sff;
5985         struct stat st;
5986
5987         if (tTd(38, 2))
5988                 sm_dprintf("stab_map_open(%s, %s, %d)\n",
5989                         map->map_mname, map->map_file, mode);
5990
5991         mode &= O_ACCMODE;
5992         if (mode != O_RDONLY)
5993         {
5994                 errno = EPERM;
5995                 return false;
5996         }
5997
5998         sff = SFF_ROOTOK|SFF_REGONLY;
5999         if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
6000                 sff |= SFF_NOWLINK;
6001         if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail))
6002                 sff |= SFF_SAFEDIRPATH;
6003         af = safefopen(map->map_file, O_RDONLY, 0444, sff);
6004         if (af == NULL)
6005                 return false;
6006         readaliases(map, af, false, false);
6007
6008         if (fstat(sm_io_getinfo(af, SM_IO_WHAT_FD, NULL), &st) >= 0)
6009                 map->map_mtime = st.st_mtime;
6010         (void) sm_io_close(af, SM_TIME_DEFAULT);
6011
6012         return true;
6013 }
6014 /*
6015 **  Implicit Modules
6016 **
6017 **      Tries several types.  For back compatibility of aliases.
6018 */
6019
6020
6021 /*
6022 **  IMPL_MAP_LOOKUP -- lookup in best open database
6023 */
6024
6025 char *
6026 impl_map_lookup(map, name, av, pstat)
6027         MAP *map;
6028         char *name;
6029         char **av;
6030         int *pstat;
6031 {
6032         if (tTd(38, 20))
6033                 sm_dprintf("impl_map_lookup(%s, %s)\n",
6034                         map->map_mname, name);
6035
6036 #if NEWDB
6037         if (bitset(MF_IMPL_HASH, map->map_mflags))
6038                 return db_map_lookup(map, name, av, pstat);
6039 #endif /* NEWDB */
6040 #if NDBM
6041         if (bitset(MF_IMPL_NDBM, map->map_mflags))
6042                 return ndbm_map_lookup(map, name, av, pstat);
6043 #endif /* NDBM */
6044         return stab_map_lookup(map, name, av, pstat);
6045 }
6046
6047 /*
6048 **  IMPL_MAP_STORE -- store in open databases
6049 */
6050
6051 void
6052 impl_map_store(map, lhs, rhs)
6053         MAP *map;
6054         char *lhs;
6055         char *rhs;
6056 {
6057         if (tTd(38, 12))
6058                 sm_dprintf("impl_map_store(%s, %s, %s)\n",
6059                         map->map_mname, lhs, rhs);
6060 #if NEWDB
6061         if (bitset(MF_IMPL_HASH, map->map_mflags))
6062                 db_map_store(map, lhs, rhs);
6063 #endif /* NEWDB */
6064 #if NDBM
6065         if (bitset(MF_IMPL_NDBM, map->map_mflags))
6066                 ndbm_map_store(map, lhs, rhs);
6067 #endif /* NDBM */
6068         stab_map_store(map, lhs, rhs);
6069 }
6070
6071 /*
6072 **  IMPL_MAP_OPEN -- implicit database open
6073 */
6074
6075 bool
6076 impl_map_open(map, mode)
6077         MAP *map;
6078         int mode;
6079 {
6080         if (tTd(38, 2))
6081                 sm_dprintf("impl_map_open(%s, %s, %d)\n",
6082                         map->map_mname, map->map_file, mode);
6083
6084         mode &= O_ACCMODE;
6085 #if NEWDB
6086         map->map_mflags |= MF_IMPL_HASH;
6087         if (hash_map_open(map, mode))
6088         {
6089 # ifdef NDBM_YP_COMPAT
6090                 if (mode == O_RDONLY || strstr(map->map_file, "/yp/") == NULL)
6091 # endif /* NDBM_YP_COMPAT */
6092                         return true;
6093         }
6094         else
6095                 map->map_mflags &= ~MF_IMPL_HASH;
6096 #endif /* NEWDB */
6097 #if NDBM
6098         map->map_mflags |= MF_IMPL_NDBM;
6099         if (ndbm_map_open(map, mode))
6100         {
6101                 return true;
6102         }
6103         else
6104                 map->map_mflags &= ~MF_IMPL_NDBM;
6105 #endif /* NDBM */
6106
6107 #if defined(NEWDB) || defined(NDBM)
6108         if (Verbose)
6109                 message("WARNING: cannot open alias database %s%s",
6110                         map->map_file,
6111                         mode == O_RDONLY ? "; reading text version" : "");
6112 #else /* defined(NEWDB) || defined(NDBM) */
6113         if (mode != O_RDONLY)
6114                 usrerr("Cannot rebuild aliases: no database format defined");
6115 #endif /* defined(NEWDB) || defined(NDBM) */
6116
6117         if (mode == O_RDONLY)
6118                 return stab_map_open(map, mode);
6119         else
6120                 return false;
6121 }
6122
6123
6124 /*
6125 **  IMPL_MAP_CLOSE -- close any open database(s)
6126 */
6127
6128 void
6129 impl_map_close(map)
6130         MAP *map;
6131 {
6132         if (tTd(38, 9))
6133                 sm_dprintf("impl_map_close(%s, %s, %lx)\n",
6134                         map->map_mname, map->map_file, map->map_mflags);
6135 #if NEWDB
6136         if (bitset(MF_IMPL_HASH, map->map_mflags))
6137         {
6138                 db_map_close(map);
6139                 map->map_mflags &= ~MF_IMPL_HASH;
6140         }
6141 #endif /* NEWDB */
6142
6143 #if NDBM
6144         if (bitset(MF_IMPL_NDBM, map->map_mflags))
6145         {
6146                 ndbm_map_close(map);
6147                 map->map_mflags &= ~MF_IMPL_NDBM;
6148         }
6149 #endif /* NDBM */
6150 }
6151 /*
6152 **  User map class.
6153 **
6154 **      Provides access to the system password file.
6155 */
6156
6157 /*
6158 **  USER_MAP_OPEN -- open user map
6159 **
6160 **      Really just binds field names to field numbers.
6161 */
6162
6163 bool
6164 user_map_open(map, mode)
6165         MAP *map;
6166         int mode;
6167 {
6168         if (tTd(38, 2))
6169                 sm_dprintf("user_map_open(%s, %d)\n",
6170                         map->map_mname, mode);
6171
6172         mode &= O_ACCMODE;
6173         if (mode != O_RDONLY)
6174         {
6175                 /* issue a pseudo-error message */
6176                 errno = SM_EMAPCANTWRITE;
6177                 return false;
6178         }
6179         if (map->map_valcolnm == NULL)
6180                 /* EMPTY */
6181                 /* nothing */ ;
6182         else if (sm_strcasecmp(map->map_valcolnm, "name") == 0)
6183                 map->map_valcolno = 1;
6184         else if (sm_strcasecmp(map->map_valcolnm, "passwd") == 0)
6185                 map->map_valcolno = 2;
6186         else if (sm_strcasecmp(map->map_valcolnm, "uid") == 0)
6187                 map->map_valcolno = 3;
6188         else if (sm_strcasecmp(map->map_valcolnm, "gid") == 0)
6189                 map->map_valcolno = 4;
6190         else if (sm_strcasecmp(map->map_valcolnm, "gecos") == 0)
6191                 map->map_valcolno = 5;
6192         else if (sm_strcasecmp(map->map_valcolnm, "dir") == 0)
6193                 map->map_valcolno = 6;
6194         else if (sm_strcasecmp(map->map_valcolnm, "shell") == 0)
6195                 map->map_valcolno = 7;
6196         else
6197         {
6198                 syserr("User map %s: unknown column name %s",
6199                         map->map_mname, map->map_valcolnm);
6200                 return false;
6201         }
6202         return true;
6203 }
6204
6205
6206 /*
6207 **  USER_MAP_LOOKUP -- look up a user in the passwd file.
6208 */
6209
6210 /* ARGSUSED3 */
6211 char *
6212 user_map_lookup(map, key, av, statp)
6213         MAP *map;
6214         char *key;
6215         char **av;
6216         int *statp;
6217 {
6218         auto bool fuzzy;
6219         SM_MBDB_T user;
6220
6221         if (tTd(38, 20))
6222                 sm_dprintf("user_map_lookup(%s, %s)\n",
6223                         map->map_mname, key);
6224
6225         *statp = finduser(key, &fuzzy, &user);
6226         if (*statp != EX_OK)
6227                 return NULL;
6228         if (bitset(MF_MATCHONLY, map->map_mflags))
6229                 return map_rewrite(map, key, strlen(key), NULL);
6230         else
6231         {
6232                 char *rwval = NULL;
6233                 char buf[30];
6234
6235                 switch (map->map_valcolno)
6236                 {
6237                   case 0:
6238                   case 1:
6239                         rwval = user.mbdb_name;
6240                         break;
6241
6242                   case 2:
6243                         rwval = "x";    /* passwd no longer supported */
6244                         break;
6245
6246                   case 3:
6247                         (void) sm_snprintf(buf, sizeof(buf), "%d",
6248                                            (int) user.mbdb_uid);
6249                         rwval = buf;
6250                         break;
6251
6252                   case 4:
6253                         (void) sm_snprintf(buf, sizeof(buf), "%d",
6254                                            (int) user.mbdb_gid);
6255                         rwval = buf;
6256                         break;
6257
6258                   case 5:
6259                         rwval = user.mbdb_fullname;
6260                         break;
6261
6262                   case 6:
6263                         rwval = user.mbdb_homedir;
6264                         break;
6265
6266                   case 7:
6267                         rwval = user.mbdb_shell;
6268                         break;
6269                   default:
6270                         syserr("user_map %s: bogus field %d",
6271                                 map->map_mname, map->map_valcolno);
6272                         return NULL;
6273                 }
6274                 return map_rewrite(map, rwval, strlen(rwval), av);
6275         }
6276 }
6277 /*
6278 **  Program map type.
6279 **
6280 **      This provides access to arbitrary programs.  It should be used
6281 **      only very sparingly, since there is no way to bound the cost
6282 **      of invoking an arbitrary program.
6283 */
6284
6285 char *
6286 prog_map_lookup(map, name, av, statp)
6287         MAP *map;
6288         char *name;
6289         char **av;
6290         int *statp;
6291 {
6292         int i;
6293         int save_errno;
6294         int fd;
6295         int status;
6296         auto pid_t pid;
6297         register char *p;
6298         char *rval;
6299         char *argv[MAXPV + 1];
6300         char buf[MAXLINE];
6301
6302         if (tTd(38, 20))
6303                 sm_dprintf("prog_map_lookup(%s, %s) %s\n",
6304                         map->map_mname, name, map->map_file);
6305
6306         i = 0;
6307         argv[i++] = map->map_file;
6308         if (map->map_rebuild != NULL)
6309         {
6310                 (void) sm_strlcpy(buf, map->map_rebuild, sizeof(buf));
6311                 for (p = strtok(buf, " \t"); p != NULL; p = strtok(NULL, " \t"))
6312                 {
6313                         if (i >= MAXPV - 1)
6314                                 break;
6315                         argv[i++] = p;
6316                 }
6317         }
6318         argv[i++] = name;
6319         argv[i] = NULL;
6320         if (tTd(38, 21))
6321         {
6322                 sm_dprintf("prog_open:");
6323                 for (i = 0; argv[i] != NULL; i++)
6324                         sm_dprintf(" %s", argv[i]);
6325                 sm_dprintf("\n");
6326         }
6327         (void) sm_blocksignal(SIGCHLD);
6328         pid = prog_open(argv, &fd, CurEnv);
6329         if (pid < 0)
6330         {
6331                 if (!bitset(MF_OPTIONAL, map->map_mflags))
6332                         syserr("prog_map_lookup(%s) failed (%s) -- closing",
6333                                map->map_mname, sm_errstring(errno));
6334                 else if (tTd(38, 9))
6335                         sm_dprintf("prog_map_lookup(%s) failed (%s) -- closing",
6336                                    map->map_mname, sm_errstring(errno));
6337                 map->map_mflags &= ~(MF_VALID|MF_OPEN);
6338                 *statp = EX_OSFILE;
6339                 return NULL;
6340         }
6341         i = read(fd, buf, sizeof(buf) - 1);
6342         if (i < 0)
6343         {
6344                 syserr("prog_map_lookup(%s): read error %s",
6345                        map->map_mname, sm_errstring(errno));
6346                 rval = NULL;
6347         }
6348         else if (i == 0)
6349         {
6350                 if (tTd(38, 20))
6351                         sm_dprintf("prog_map_lookup(%s): empty answer\n",
6352                                    map->map_mname);
6353                 rval = NULL;
6354         }
6355         else
6356         {
6357                 buf[i] = '\0';
6358                 p = strchr(buf, '\n');
6359                 if (p != NULL)
6360                         *p = '\0';
6361
6362                 /* collect the return value */
6363                 if (bitset(MF_MATCHONLY, map->map_mflags))
6364                         rval = map_rewrite(map, name, strlen(name), NULL);
6365                 else
6366                         rval = map_rewrite(map, buf, strlen(buf), av);
6367
6368                 /* now flush any additional output */
6369                 while ((i = read(fd, buf, sizeof(buf))) > 0)
6370                         continue;
6371         }
6372
6373         /* wait for the process to terminate */
6374         (void) close(fd);
6375         status = waitfor(pid);
6376         save_errno = errno;
6377         (void) sm_releasesignal(SIGCHLD);
6378         errno = save_errno;
6379
6380         if (status == -1)
6381         {
6382                 syserr("prog_map_lookup(%s): wait error %s",
6383                        map->map_mname, sm_errstring(errno));
6384                 *statp = EX_SOFTWARE;
6385                 rval = NULL;
6386         }
6387         else if (WIFEXITED(status))
6388         {
6389                 if ((*statp = WEXITSTATUS(status)) != EX_OK)
6390                         rval = NULL;
6391         }
6392         else
6393         {
6394                 syserr("prog_map_lookup(%s): child died on signal %d",
6395                        map->map_mname, status);
6396                 *statp = EX_UNAVAILABLE;
6397                 rval = NULL;
6398         }
6399         return rval;
6400 }
6401 /*
6402 **  Sequenced map type.
6403 **
6404 **      Tries each map in order until something matches, much like
6405 **      implicit.  Stores go to the first map in the list that can
6406 **      support storing.
6407 **
6408 **      This is slightly unusual in that there are two interfaces.
6409 **      The "sequence" interface lets you stack maps arbitrarily.
6410 **      The "switch" interface builds a sequence map by looking
6411 **      at a system-dependent configuration file such as
6412 **      /etc/nsswitch.conf on Solaris or /etc/svc.conf on Ultrix.
6413 **
6414 **      We don't need an explicit open, since all maps are
6415 **      opened on demand.
6416 */
6417
6418 /*
6419 **  SEQ_MAP_PARSE -- Sequenced map parsing
6420 */
6421
6422 bool
6423 seq_map_parse(map, ap)
6424         MAP *map;
6425         char *ap;
6426 {
6427         int maxmap;
6428
6429         if (tTd(38, 2))
6430                 sm_dprintf("seq_map_parse(%s, %s)\n", map->map_mname, ap);
6431         maxmap = 0;
6432         while (*ap != '\0')
6433         {
6434                 register char *p;
6435                 STAB *s;
6436
6437                 /* find beginning of map name */
6438                 while (isascii(*ap) && isspace(*ap))
6439                         ap++;
6440                 for (p = ap;
6441                      (isascii(*p) && isalnum(*p)) || *p == '_' || *p == '.';
6442                      p++)
6443                         continue;
6444                 if (*p != '\0')
6445                         *p++ = '\0';
6446                 while (*p != '\0' && (!isascii(*p) || !isalnum(*p)))
6447                         p++;
6448                 if (*ap == '\0')
6449                 {
6450                         ap = p;
6451                         continue;
6452                 }
6453                 s = stab(ap, ST_MAP, ST_FIND);
6454                 if (s == NULL)
6455                 {
6456                         syserr("Sequence map %s: unknown member map %s",
6457                                 map->map_mname, ap);
6458                 }
6459                 else if (maxmap >= MAXMAPSTACK)
6460                 {
6461                         syserr("Sequence map %s: too many member maps (%d max)",
6462                                 map->map_mname, MAXMAPSTACK);
6463                         maxmap++;
6464                 }
6465                 else if (maxmap < MAXMAPSTACK)
6466                 {
6467                         map->map_stack[maxmap++] = &s->s_map;
6468                 }
6469                 ap = p;
6470         }
6471         return true;
6472 }
6473
6474 /*
6475 **  SWITCH_MAP_OPEN -- open a switched map
6476 **
6477 **      This looks at the system-dependent configuration and builds
6478 **      a sequence map that does the same thing.
6479 **
6480 **      Every system must define a switch_map_find routine in conf.c
6481 **      that will return the list of service types associated with a
6482 **      given service class.
6483 */
6484
6485 bool
6486 switch_map_open(map, mode)
6487         MAP *map;
6488         int mode;
6489 {
6490         int mapno;
6491         int nmaps;
6492         char *maptype[MAXMAPSTACK];
6493
6494         if (tTd(38, 2))
6495                 sm_dprintf("switch_map_open(%s, %s, %d)\n",
6496                         map->map_mname, map->map_file, mode);
6497
6498         mode &= O_ACCMODE;
6499         nmaps = switch_map_find(map->map_file, maptype, map->map_return);
6500         if (tTd(38, 19))
6501         {
6502                 sm_dprintf("\tswitch_map_find => %d\n", nmaps);
6503                 for (mapno = 0; mapno < nmaps; mapno++)
6504                         sm_dprintf("\t\t%s\n", maptype[mapno]);
6505         }
6506         if (nmaps <= 0 || nmaps > MAXMAPSTACK)
6507                 return false;
6508
6509         for (mapno = 0; mapno < nmaps; mapno++)
6510         {
6511                 register STAB *s;
6512                 char nbuf[MAXNAME + 1];
6513
6514                 if (maptype[mapno] == NULL)
6515                         continue;
6516                 (void) sm_strlcpyn(nbuf, sizeof(nbuf), 3,
6517                                    map->map_mname, ".", maptype[mapno]);
6518                 s = stab(nbuf, ST_MAP, ST_FIND);
6519                 if (s == NULL)
6520                 {
6521                         syserr("Switch map %s: unknown member map %s",
6522                                 map->map_mname, nbuf);
6523                 }
6524                 else
6525                 {
6526                         map->map_stack[mapno] = &s->s_map;
6527                         if (tTd(38, 4))
6528                                 sm_dprintf("\tmap_stack[%d] = %s:%s\n",
6529                                            mapno,
6530                                            s->s_map.map_class->map_cname,
6531                                            nbuf);
6532                 }
6533         }
6534         return true;
6535 }
6536
6537 #if 0
6538 /*
6539 **  SEQ_MAP_CLOSE -- close all underlying maps
6540 */
6541
6542 void
6543 seq_map_close(map)
6544         MAP *map;
6545 {
6546         int mapno;
6547
6548         if (tTd(38, 9))
6549                 sm_dprintf("seq_map_close(%s)\n", map->map_mname);
6550
6551         for (mapno = 0; mapno < MAXMAPSTACK; mapno++)
6552         {
6553                 MAP *mm = map->map_stack[mapno];
6554
6555                 if (mm == NULL || !bitset(MF_OPEN, mm->map_mflags))
6556                         continue;
6557                 mm->map_mflags |= MF_CLOSING;
6558                 mm->map_class->map_close(mm);
6559                 mm->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
6560         }
6561 }
6562 #endif /* 0 */
6563
6564 /*
6565 **  SEQ_MAP_LOOKUP -- sequenced map lookup
6566 */
6567
6568 char *
6569 seq_map_lookup(map, key, args, pstat)
6570         MAP *map;
6571         char *key;
6572         char **args;
6573         int *pstat;
6574 {
6575         int mapno;
6576         int mapbit = 0x01;
6577         bool tempfail = false;
6578
6579         if (tTd(38, 20))
6580                 sm_dprintf("seq_map_lookup(%s, %s)\n", map->map_mname, key);
6581
6582         for (mapno = 0; mapno < MAXMAPSTACK; mapbit <<= 1, mapno++)
6583         {
6584                 MAP *mm = map->map_stack[mapno];
6585                 char *rv;
6586
6587                 if (mm == NULL)
6588                         continue;
6589                 if (!bitset(MF_OPEN, mm->map_mflags) &&
6590                     !openmap(mm))
6591                 {
6592                         if (bitset(mapbit, map->map_return[MA_UNAVAIL]))
6593                         {
6594                                 *pstat = EX_UNAVAILABLE;
6595                                 return NULL;
6596                         }
6597                         continue;
6598                 }
6599                 *pstat = EX_OK;
6600                 rv = mm->map_class->map_lookup(mm, key, args, pstat);
6601                 if (rv != NULL)
6602                         return rv;
6603                 if (*pstat == EX_TEMPFAIL)
6604                 {
6605                         if (bitset(mapbit, map->map_return[MA_TRYAGAIN]))
6606                                 return NULL;
6607                         tempfail = true;
6608                 }
6609                 else if (bitset(mapbit, map->map_return[MA_NOTFOUND]))
6610                         break;
6611         }
6612         if (tempfail)
6613                 *pstat = EX_TEMPFAIL;
6614         else if (*pstat == EX_OK)
6615                 *pstat = EX_NOTFOUND;
6616         return NULL;
6617 }
6618
6619 /*
6620 **  SEQ_MAP_STORE -- sequenced map store
6621 */
6622
6623 void
6624 seq_map_store(map, key, val)
6625         MAP *map;
6626         char *key;
6627         char *val;
6628 {
6629         int mapno;
6630
6631         if (tTd(38, 12))
6632                 sm_dprintf("seq_map_store(%s, %s, %s)\n",
6633                         map->map_mname, key, val);
6634
6635         for (mapno = 0; mapno < MAXMAPSTACK; mapno++)
6636         {
6637                 MAP *mm = map->map_stack[mapno];
6638
6639                 if (mm == NULL || !bitset(MF_WRITABLE, mm->map_mflags))
6640                         continue;
6641
6642                 mm->map_class->map_store(mm, key, val);
6643                 return;
6644         }
6645         syserr("seq_map_store(%s, %s, %s): no writable map",
6646                 map->map_mname, key, val);
6647 }
6648 /*
6649 **  NULL stubs
6650 */
6651
6652 /* ARGSUSED */
6653 bool
6654 null_map_open(map, mode)
6655         MAP *map;
6656         int mode;
6657 {
6658         return true;
6659 }
6660
6661 /* ARGSUSED */
6662 void
6663 null_map_close(map)
6664         MAP *map;
6665 {
6666         return;
6667 }
6668
6669 char *
6670 null_map_lookup(map, key, args, pstat)
6671         MAP *map;
6672         char *key;
6673         char **args;
6674         int *pstat;
6675 {
6676         *pstat = EX_NOTFOUND;
6677         return NULL;
6678 }
6679
6680 /* ARGSUSED */
6681 void
6682 null_map_store(map, key, val)
6683         MAP *map;
6684         char *key;
6685         char *val;
6686 {
6687         return;
6688 }
6689
6690 /*
6691 **  BOGUS stubs
6692 */
6693
6694 char *
6695 bogus_map_lookup(map, key, args, pstat)
6696         MAP *map;
6697         char *key;
6698         char **args;
6699         int *pstat;
6700 {
6701         *pstat = EX_TEMPFAIL;
6702         return NULL;
6703 }
6704
6705 MAPCLASS        BogusMapClass =
6706 {
6707         "bogus-map",            NULL,                   0,
6708         NULL,                   bogus_map_lookup,       null_map_store,
6709         null_map_open,          null_map_close,
6710 };
6711 /*
6712 **  MACRO modules
6713 */
6714
6715 char *
6716 macro_map_lookup(map, name, av, statp)
6717         MAP *map;
6718         char *name;
6719         char **av;
6720         int *statp;
6721 {
6722         int mid;
6723
6724         if (tTd(38, 20))
6725                 sm_dprintf("macro_map_lookup(%s, %s)\n", map->map_mname,
6726                         name == NULL ? "NULL" : name);
6727
6728         if (name == NULL ||
6729             *name == '\0' ||
6730             (mid = macid(name)) == 0)
6731         {
6732                 *statp = EX_CONFIG;
6733                 return NULL;
6734         }
6735
6736         if (av[1] == NULL)
6737                 macdefine(&CurEnv->e_macro, A_PERM, mid, NULL);
6738         else
6739                 macdefine(&CurEnv->e_macro, A_TEMP, mid, av[1]);
6740
6741         *statp = EX_OK;
6742         return "";
6743 }
6744 /*
6745 **  REGEX modules
6746 */
6747
6748 #if MAP_REGEX
6749
6750 # include <regex.h>
6751
6752 # define DEFAULT_DELIM  CONDELSE
6753 # define END_OF_FIELDS  -1
6754 # define ERRBUF_SIZE    80
6755 # define MAX_MATCH      32
6756
6757 # define xnalloc(s)     memset(xalloc(s), '\0', s);
6758
6759 struct regex_map
6760 {
6761         regex_t *regex_pattern_buf;     /* xalloc it */
6762         int     *regex_subfields;       /* move to type MAP */
6763         char    *regex_delim;           /* move to type MAP */
6764 };
6765
6766 static int      parse_fields __P((char *, int *, int, int));
6767 static char     *regex_map_rewrite __P((MAP *, const char*, size_t, char **));
6768
6769 static int
6770 parse_fields(s, ibuf, blen, nr_substrings)
6771         char *s;
6772         int *ibuf;              /* array */
6773         int blen;               /* number of elements in ibuf */
6774         int nr_substrings;      /* number of substrings in the pattern */
6775 {
6776         register char *cp;
6777         int i = 0;
6778         bool lastone = false;
6779
6780         blen--;         /* for terminating END_OF_FIELDS */
6781         cp = s;
6782         do
6783         {
6784                 for (;; cp++)
6785                 {
6786                         if (*cp == ',')
6787                         {
6788                                 *cp = '\0';
6789                                 break;
6790                         }
6791                         if (*cp == '\0')
6792                         {
6793                                 lastone = true;
6794                                 break;
6795                         }
6796                 }
6797                 if (i < blen)
6798                 {
6799                         int val = atoi(s);
6800
6801                         if (val < 0 || val >= nr_substrings)
6802                         {
6803                                 syserr("field (%d) out of range, only %d substrings in pattern",
6804                                        val, nr_substrings);
6805                                 return -1;
6806                         }
6807                         ibuf[i++] = val;
6808                 }
6809                 else
6810                 {
6811                         syserr("too many fields, %d max", blen);
6812                         return -1;
6813                 }
6814                 s = ++cp;
6815         } while (!lastone);
6816         ibuf[i] = END_OF_FIELDS;
6817         return i;
6818 }
6819
6820 bool
6821 regex_map_init(map, ap)
6822         MAP *map;
6823         char *ap;
6824 {
6825         int regerr;
6826         struct regex_map *map_p;
6827         register char *p;
6828         char *sub_param = NULL;
6829         int pflags;
6830         static char defdstr[] = { (char) DEFAULT_DELIM, '\0' };
6831
6832         if (tTd(38, 2))
6833                 sm_dprintf("regex_map_init: mapname '%s', args '%s'\n",
6834                         map->map_mname, ap);
6835
6836         pflags = REG_ICASE | REG_EXTENDED | REG_NOSUB;
6837         p = ap;
6838         map_p = (struct regex_map *) xnalloc(sizeof(*map_p));
6839         map_p->regex_pattern_buf = (regex_t *)xnalloc(sizeof(regex_t));
6840
6841         for (;;)
6842         {
6843                 while (isascii(*p) && isspace(*p))
6844                         p++;
6845                 if (*p != '-')
6846                         break;
6847                 switch (*++p)
6848                 {
6849                   case 'n':     /* not */
6850                         map->map_mflags |= MF_REGEX_NOT;
6851                         break;
6852
6853                   case 'f':     /* case sensitive */
6854                         map->map_mflags |= MF_NOFOLDCASE;
6855                         pflags &= ~REG_ICASE;
6856                         break;
6857
6858                   case 'b':     /* basic regular expressions */
6859                         pflags &= ~REG_EXTENDED;
6860                         break;
6861
6862                   case 's':     /* substring match () syntax */
6863                         sub_param = ++p;
6864                         pflags &= ~REG_NOSUB;
6865                         break;
6866
6867                   case 'd':     /* delimiter */
6868                         map_p->regex_delim = ++p;
6869                         break;
6870
6871                   case 'a':     /* map append */
6872                         map->map_app = ++p;
6873                         break;
6874
6875                   case 'm':     /* matchonly */
6876                         map->map_mflags |= MF_MATCHONLY;
6877                         break;
6878
6879                   case 'q':
6880                         map->map_mflags |= MF_KEEPQUOTES;
6881                         break;
6882
6883                   case 'S':
6884                         map->map_spacesub = *++p;
6885                         break;
6886
6887                   case 'D':
6888                         map->map_mflags |= MF_DEFER;
6889                         break;
6890
6891                 }
6892                 while (*p != '\0' && !(isascii(*p) && isspace(*p)))
6893                         p++;
6894                 if (*p != '\0')
6895                         *p++ = '\0';
6896         }
6897         if (tTd(38, 3))
6898                 sm_dprintf("regex_map_init: compile '%s' 0x%x\n", p, pflags);
6899
6900         if ((regerr = regcomp(map_p->regex_pattern_buf, p, pflags)) != 0)
6901         {
6902                 /* Errorhandling */
6903                 char errbuf[ERRBUF_SIZE];
6904
6905                 (void) regerror(regerr, map_p->regex_pattern_buf,
6906                          errbuf, sizeof(errbuf));
6907                 syserr("pattern-compile-error: %s", errbuf);
6908                 sm_free(map_p->regex_pattern_buf); /* XXX */
6909                 sm_free(map_p); /* XXX */
6910                 return false;
6911         }
6912
6913         if (map->map_app != NULL)
6914                 map->map_app = newstr(map->map_app);
6915         if (map_p->regex_delim != NULL)
6916                 map_p->regex_delim = newstr(map_p->regex_delim);
6917         else
6918                 map_p->regex_delim = defdstr;
6919
6920         if (!bitset(REG_NOSUB, pflags))
6921         {
6922                 /* substring matching */
6923                 int substrings;
6924                 int *fields = (int *) xalloc(sizeof(int) * (MAX_MATCH + 1));
6925
6926                 substrings = map_p->regex_pattern_buf->re_nsub + 1;
6927
6928                 if (tTd(38, 3))
6929                         sm_dprintf("regex_map_init: nr of substrings %d\n",
6930                                 substrings);
6931
6932                 if (substrings >= MAX_MATCH)
6933                 {
6934                         syserr("too many substrings, %d max", MAX_MATCH);
6935                         sm_free(map_p->regex_pattern_buf); /* XXX */
6936                         sm_free(map_p); /* XXX */
6937                         return false;
6938                 }
6939                 if (sub_param != NULL && sub_param[0] != '\0')
6940                 {
6941                         /* optional parameter -sfields */
6942                         if (parse_fields(sub_param, fields,
6943                                          MAX_MATCH + 1, substrings) == -1)
6944                                 return false;
6945                 }
6946                 else
6947                 {
6948                         int i;
6949
6950                         /* set default fields */
6951                         for (i = 0; i < substrings; i++)
6952                                 fields[i] = i;
6953                         fields[i] = END_OF_FIELDS;
6954                 }
6955                 map_p->regex_subfields = fields;
6956                 if (tTd(38, 3))
6957                 {
6958                         int *ip;
6959
6960                         sm_dprintf("regex_map_init: subfields");
6961                         for (ip = fields; *ip != END_OF_FIELDS; ip++)
6962                                 sm_dprintf(" %d", *ip);
6963                         sm_dprintf("\n");
6964                 }
6965         }
6966         map->map_db1 = (ARBPTR_T) map_p;        /* dirty hack */
6967         return true;
6968 }
6969
6970 static char *
6971 regex_map_rewrite(map, s, slen, av)
6972         MAP *map;
6973         const char *s;
6974         size_t slen;
6975         char **av;
6976 {
6977         if (bitset(MF_MATCHONLY, map->map_mflags))
6978                 return map_rewrite(map, av[0], strlen(av[0]), NULL);
6979         else
6980                 return map_rewrite(map, s, slen, av);
6981 }
6982
6983 char *
6984 regex_map_lookup(map, name, av, statp)
6985         MAP *map;
6986         char *name;
6987         char **av;
6988         int *statp;
6989 {
6990         int reg_res;
6991         struct regex_map *map_p;
6992         regmatch_t pmatch[MAX_MATCH];
6993
6994         if (tTd(38, 20))
6995         {
6996                 char **cpp;
6997
6998                 sm_dprintf("regex_map_lookup: key '%s'\n", name);
6999                 for (cpp = av; cpp != NULL && *cpp != NULL; cpp++)
7000                         sm_dprintf("regex_map_lookup: arg '%s'\n", *cpp);
7001         }
7002
7003         map_p = (struct regex_map *)(map->map_db1);
7004         reg_res = regexec(map_p->regex_pattern_buf,
7005                           name, MAX_MATCH, pmatch, 0);
7006
7007         if (bitset(MF_REGEX_NOT, map->map_mflags))
7008         {
7009                 /* option -n */
7010                 if (reg_res == REG_NOMATCH)
7011                         return regex_map_rewrite(map, "", (size_t) 0, av);
7012                 else
7013                         return NULL;
7014         }
7015         if (reg_res == REG_NOMATCH)
7016                 return NULL;
7017
7018         if (map_p->regex_subfields != NULL)
7019         {
7020                 /* option -s */
7021                 static char retbuf[MAXNAME];
7022                 int fields[MAX_MATCH + 1];
7023                 bool first = true;
7024                 int anglecnt = 0, cmntcnt = 0, spacecnt = 0;
7025                 bool quotemode = false, bslashmode = false;
7026                 register char *dp, *sp;
7027                 char *endp, *ldp;
7028                 int *ip;
7029
7030                 dp = retbuf;
7031                 ldp = retbuf + sizeof(retbuf) - 1;
7032
7033                 if (av[1] != NULL)
7034                 {
7035                         if (parse_fields(av[1], fields, MAX_MATCH + 1,
7036                                          (int) map_p->regex_pattern_buf->re_nsub + 1) == -1)
7037                         {
7038                                 *statp = EX_CONFIG;
7039                                 return NULL;
7040                         }
7041                         ip = fields;
7042                 }
7043                 else
7044                         ip = map_p->regex_subfields;
7045
7046                 for ( ; *ip != END_OF_FIELDS; ip++)
7047                 {
7048                         if (!first)
7049                         {
7050                                 for (sp = map_p->regex_delim; *sp; sp++)
7051                                 {
7052                                         if (dp < ldp)
7053                                                 *dp++ = *sp;
7054                                 }
7055                         }
7056                         else
7057                                 first = false;
7058
7059                         if (*ip >= MAX_MATCH ||
7060                             pmatch[*ip].rm_so < 0 || pmatch[*ip].rm_eo < 0)
7061                                 continue;
7062
7063                         sp = name + pmatch[*ip].rm_so;
7064                         endp = name + pmatch[*ip].rm_eo;
7065                         for (; endp > sp; sp++)
7066                         {
7067                                 if (dp < ldp)
7068                                 {
7069                                         if (bslashmode)
7070                                         {
7071                                                 *dp++ = *sp;
7072                                                 bslashmode = false;
7073                                         }
7074                                         else if (quotemode && *sp != '"' &&
7075                                                 *sp != '\\')
7076                                         {
7077                                                 *dp++ = *sp;
7078                                         }
7079                                         else switch (*dp++ = *sp)
7080                                         {
7081                                           case '\\':
7082                                                 bslashmode = true;
7083                                                 break;
7084
7085                                           case '(':
7086                                                 cmntcnt++;
7087                                                 break;
7088
7089                                           case ')':
7090                                                 cmntcnt--;
7091                                                 break;
7092
7093                                           case '<':
7094                                                 anglecnt++;
7095                                                 break;
7096
7097                                           case '>':
7098                                                 anglecnt--;
7099                                                 break;
7100
7101                                           case ' ':
7102                                                 spacecnt++;
7103                                                 break;
7104
7105                                           case '"':
7106                                                 quotemode = !quotemode;
7107                                                 break;
7108                                         }
7109                                 }
7110                         }
7111                 }
7112                 if (anglecnt != 0 || cmntcnt != 0 || quotemode ||
7113                     bslashmode || spacecnt != 0)
7114                 {
7115                         sm_syslog(LOG_WARNING, NOQID,
7116                                   "Warning: regex may cause prescan() failure map=%s lookup=%s",
7117                                   map->map_mname, name);
7118                         return NULL;
7119                 }
7120
7121                 *dp = '\0';
7122
7123                 return regex_map_rewrite(map, retbuf, strlen(retbuf), av);
7124         }
7125         return regex_map_rewrite(map, "", (size_t)0, av);
7126 }
7127 #endif /* MAP_REGEX */
7128 /*
7129 **  NSD modules
7130 */
7131 #if MAP_NSD
7132
7133 # include <ndbm.h>
7134 # define _DATUM_DEFINED
7135 # include <ns_api.h>
7136
7137 typedef struct ns_map_list
7138 {
7139         ns_map_t                *map;           /* XXX ns_ ? */
7140         char                    *mapname;
7141         struct ns_map_list      *next;
7142 } ns_map_list_t;
7143
7144 static ns_map_t *
7145 ns_map_t_find(mapname)
7146         char *mapname;
7147 {
7148         static ns_map_list_t *ns_maps = NULL;
7149         ns_map_list_t *ns_map;
7150
7151         /* walk the list of maps looking for the correctly named map */
7152         for (ns_map = ns_maps; ns_map != NULL; ns_map = ns_map->next)
7153         {
7154                 if (strcmp(ns_map->mapname, mapname) == 0)
7155                         break;
7156         }
7157
7158         /* if we are looking at a NULL ns_map_list_t, then create a new one */
7159         if (ns_map == NULL)
7160         {
7161                 ns_map = (ns_map_list_t *) xalloc(sizeof(*ns_map));
7162                 ns_map->mapname = newstr(mapname);
7163                 ns_map->map = (ns_map_t *) xalloc(sizeof(*ns_map->map));
7164                 memset(ns_map->map, '\0', sizeof(*ns_map->map));
7165                 ns_map->next = ns_maps;
7166                 ns_maps = ns_map;
7167         }
7168         return ns_map->map;
7169 }
7170
7171 char *
7172 nsd_map_lookup(map, name, av, statp)
7173         MAP *map;
7174         char *name;
7175         char **av;
7176         int *statp;
7177 {
7178         int buflen, r;
7179         char *p;
7180         ns_map_t *ns_map;
7181         char keybuf[MAXNAME + 1];
7182         char buf[MAXLINE];
7183
7184         if (tTd(38, 20))
7185                 sm_dprintf("nsd_map_lookup(%s, %s)\n", map->map_mname, name);
7186
7187         buflen = strlen(name);
7188         if (buflen > sizeof(keybuf) - 1)
7189                 buflen = sizeof(keybuf) - 1;    /* XXX simply cut off? */
7190         memmove(keybuf, name, buflen);
7191         keybuf[buflen] = '\0';
7192         if (!bitset(MF_NOFOLDCASE, map->map_mflags))
7193                 makelower(keybuf);
7194
7195         ns_map = ns_map_t_find(map->map_file);
7196         if (ns_map == NULL)
7197         {
7198                 if (tTd(38, 20))
7199                         sm_dprintf("nsd_map_t_find failed\n");
7200                 *statp = EX_UNAVAILABLE;
7201                 return NULL;
7202         }
7203         r = ns_lookup(ns_map, NULL, map->map_file, keybuf, NULL,
7204                       buf, sizeof(buf));
7205         if (r == NS_UNAVAIL || r == NS_TRYAGAIN)
7206         {
7207                 *statp = EX_TEMPFAIL;
7208                 return NULL;
7209         }
7210         if (r == NS_BADREQ
7211 # ifdef NS_NOPERM
7212             || r == NS_NOPERM
7213 # endif /* NS_NOPERM */
7214             )
7215         {
7216                 *statp = EX_CONFIG;
7217                 return NULL;
7218         }
7219         if (r != NS_SUCCESS)
7220         {
7221                 *statp = EX_NOTFOUND;
7222                 return NULL;
7223         }
7224
7225         *statp = EX_OK;
7226
7227         /* Null out trailing \n */
7228         if ((p = strchr(buf, '\n')) != NULL)
7229                 *p = '\0';
7230
7231         return map_rewrite(map, buf, strlen(buf), av);
7232 }
7233 #endif /* MAP_NSD */
7234
7235 char *
7236 arith_map_lookup(map, name, av, statp)
7237         MAP *map;
7238         char *name;
7239         char **av;
7240         int *statp;
7241 {
7242         long r;
7243         long v[2];
7244         bool res = false;
7245         bool boolres;
7246         static char result[16];
7247         char **cpp;
7248
7249         if (tTd(38, 2))
7250         {
7251                 sm_dprintf("arith_map_lookup: key '%s'\n", name);
7252                 for (cpp = av; cpp != NULL && *cpp != NULL; cpp++)
7253                         sm_dprintf("arith_map_lookup: arg '%s'\n", *cpp);
7254         }
7255         r = 0;
7256         boolres = false;
7257         cpp = av;
7258         *statp = EX_OK;
7259
7260         /*
7261         **  read arguments for arith map
7262         **  - no check is made whether they are really numbers
7263         **  - just ignores args after the second
7264         */
7265
7266         for (++cpp; cpp != NULL && *cpp != NULL && r < 2; cpp++)
7267                 v[r++] = strtol(*cpp, NULL, 0);
7268
7269         /* operator and (at least) two operands given? */
7270         if (name != NULL && r == 2)
7271         {
7272                 switch (*name)
7273                 {
7274                   case '|':
7275                         r = v[0] | v[1];
7276                         break;
7277
7278                   case '&':
7279                         r = v[0] & v[1];
7280                         break;
7281
7282                   case '%':
7283                         if (v[1] == 0)
7284                                 return NULL;
7285                         r = v[0] % v[1];
7286                         break;
7287                   case '+':
7288                         r = v[0] + v[1];
7289                         break;
7290
7291                   case '-':
7292                         r = v[0] - v[1];
7293                         break;
7294
7295                   case '*':
7296                         r = v[0] * v[1];
7297                         break;
7298
7299                   case '/':
7300                         if (v[1] == 0)
7301                                 return NULL;
7302                         r = v[0] / v[1];
7303                         break;
7304
7305                   case 'l':
7306                         res = v[0] < v[1];
7307                         boolres = true;
7308                         break;
7309
7310                   case '=':
7311                         res = v[0] == v[1];
7312                         boolres = true;
7313                         break;
7314
7315                   case 'r':
7316                         r = v[1] - v[0] + 1;
7317                         if (r <= 0)
7318                                 return NULL;
7319                         r = get_random() % r + v[0];
7320                         break;
7321
7322                   default:
7323                         /* XXX */
7324                         *statp = EX_CONFIG;
7325                         if (LogLevel > 10)
7326                                 sm_syslog(LOG_WARNING, NOQID,
7327                                           "arith_map: unknown operator %c",
7328                                           isprint(*name) ? *name : '?');
7329                         return NULL;
7330                 }
7331                 if (boolres)
7332                         (void) sm_snprintf(result, sizeof(result),
7333                                 res ? "TRUE" : "FALSE");
7334                 else
7335                         (void) sm_snprintf(result, sizeof(result), "%ld", r);
7336                 return result;
7337         }
7338         *statp = EX_CONFIG;
7339         return NULL;
7340 }
7341
7342 #if SOCKETMAP
7343
7344 # if NETINET || NETINET6
7345 #  include <arpa/inet.h>
7346 # endif /* NETINET || NETINET6 */
7347
7348 # define socket_map_next map_stack[0]
7349
7350 /*
7351 **  SOCKET_MAP_OPEN -- open socket table
7352 */
7353
7354 bool
7355 socket_map_open(map, mode)
7356         MAP *map;
7357         int mode;
7358 {
7359         STAB *s;
7360         int sock = 0;
7361         SOCKADDR_LEN_T addrlen = 0;
7362         int addrno = 0;
7363         int save_errno;
7364         char *p;
7365         char *colon;
7366         char *at;
7367         struct hostent *hp = NULL;
7368         SOCKADDR addr;
7369
7370         if (tTd(38, 2))
7371                 sm_dprintf("socket_map_open(%s, %s, %d)\n",
7372                         map->map_mname, map->map_file, mode);
7373
7374         mode &= O_ACCMODE;
7375
7376         /* sendmail doesn't have the ability to write to SOCKET (yet) */
7377         if (mode != O_RDONLY)
7378         {
7379                 /* issue a pseudo-error message */
7380                 errno = SM_EMAPCANTWRITE;
7381                 return false;
7382         }
7383
7384         if (*map->map_file == '\0')
7385         {
7386                 syserr("socket map \"%s\": empty or missing socket information",
7387                         map->map_mname);
7388                 return false;
7389         }
7390
7391         s = socket_map_findconn(map->map_file);
7392         if (s->s_socketmap != NULL)
7393         {
7394                 /* Copy open connection */
7395                 map->map_db1 = s->s_socketmap->map_db1;
7396
7397                 /* Add this map as head of linked list */
7398                 map->socket_map_next = s->s_socketmap;
7399                 s->s_socketmap = map;
7400
7401                 if (tTd(38, 2))
7402                         sm_dprintf("using cached connection\n");
7403                 return true;
7404         }
7405
7406         if (tTd(38, 2))
7407                 sm_dprintf("opening new connection\n");
7408
7409         /* following code is ripped from milter.c */
7410         /* XXX It should be put in a library... */
7411
7412         /* protocol:filename or protocol:port@host */
7413         memset(&addr, '\0', sizeof(addr));
7414         p = map->map_file;
7415         colon = strchr(p, ':');
7416         if (colon != NULL)
7417         {
7418                 *colon = '\0';
7419
7420                 if (*p == '\0')
7421                 {
7422 # if NETUNIX
7423                         /* default to AF_UNIX */
7424                         addr.sa.sa_family = AF_UNIX;
7425 # else /* NETUNIX */
7426 #  if NETINET
7427                         /* default to AF_INET */
7428                         addr.sa.sa_family = AF_INET;
7429 #  else /* NETINET */
7430 #   if NETINET6
7431                         /* default to AF_INET6 */
7432                         addr.sa.sa_family = AF_INET6;
7433 #   else /* NETINET6 */
7434                         /* no protocols available */
7435                         syserr("socket map \"%s\": no valid socket protocols available",
7436                         map->map_mname);
7437                         return false;
7438 #   endif /* NETINET6 */
7439 #  endif /* NETINET */
7440 # endif /* NETUNIX */
7441                 }
7442 # if NETUNIX
7443                 else if (sm_strcasecmp(p, "unix") == 0 ||
7444                          sm_strcasecmp(p, "local") == 0)
7445                         addr.sa.sa_family = AF_UNIX;
7446 # endif /* NETUNIX */
7447 # if NETINET
7448                 else if (sm_strcasecmp(p, "inet") == 0)
7449                         addr.sa.sa_family = AF_INET;
7450 # endif /* NETINET */
7451 # if NETINET6
7452                 else if (sm_strcasecmp(p, "inet6") == 0)
7453                         addr.sa.sa_family = AF_INET6;
7454 # endif /* NETINET6 */
7455                 else
7456                 {
7457 # ifdef EPROTONOSUPPORT
7458                         errno = EPROTONOSUPPORT;
7459 # else /* EPROTONOSUPPORT */
7460                         errno = EINVAL;
7461 # endif /* EPROTONOSUPPORT */
7462                         syserr("socket map \"%s\": unknown socket type %s",
7463                                map->map_mname, p);
7464                         return false;
7465                 }
7466                 *colon++ = ':';
7467         }
7468         else
7469         {
7470                 colon = p;
7471 #if NETUNIX
7472                 /* default to AF_UNIX */
7473                 addr.sa.sa_family = AF_UNIX;
7474 #else /* NETUNIX */
7475 # if NETINET
7476                 /* default to AF_INET */
7477                 addr.sa.sa_family = AF_INET;
7478 # else /* NETINET */
7479 #  if NETINET6
7480                 /* default to AF_INET6 */
7481                 addr.sa.sa_family = AF_INET6;
7482 #  else /* NETINET6 */
7483                 syserr("socket map \"%s\": unknown socket type %s",
7484                        map->map_mname, p);
7485                 return false;
7486 #  endif /* NETINET6 */
7487 # endif /* NETINET */
7488 #endif /* NETUNIX */
7489         }
7490
7491 # if NETUNIX
7492         if (addr.sa.sa_family == AF_UNIX)
7493         {
7494                 long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_EXECOK;
7495
7496                 at = colon;
7497                 if (strlen(colon) >= sizeof(addr.sunix.sun_path))
7498                 {
7499                         syserr("socket map \"%s\": local socket name %s too long",
7500                                map->map_mname, colon);
7501                         return false;
7502                 }
7503                 errno = safefile(colon, RunAsUid, RunAsGid, RunAsUserName, sff,
7504                                  S_IRUSR|S_IWUSR, NULL);
7505
7506                 if (errno != 0)
7507                 {
7508                         /* if not safe, don't create */
7509                                 syserr("socket map \"%s\": local socket name %s unsafe",
7510                                map->map_mname, colon);
7511                         return false;
7512                 }
7513
7514                 (void) sm_strlcpy(addr.sunix.sun_path, colon,
7515                                sizeof(addr.sunix.sun_path));
7516                 addrlen = sizeof(struct sockaddr_un);
7517         }
7518         else
7519 # endif /* NETUNIX */
7520 # if NETINET || NETINET6
7521         if (false
7522 #  if NETINET
7523                  || addr.sa.sa_family == AF_INET
7524 #  endif /* NETINET */
7525 #  if NETINET6
7526                  || addr.sa.sa_family == AF_INET6
7527 #  endif /* NETINET6 */
7528                  )
7529         {
7530                 unsigned short port;
7531
7532                 /* Parse port@host */
7533                 at = strchr(colon, '@');
7534                 if (at == NULL)
7535                 {
7536                         syserr("socket map \"%s\": bad address %s (expected port@host)",
7537                                        map->map_mname, colon);
7538                         return false;
7539                 }
7540                 *at = '\0';
7541                 if (isascii(*colon) && isdigit(*colon))
7542                         port = htons((unsigned short) atoi(colon));
7543                 else
7544                 {
7545 #  ifdef NO_GETSERVBYNAME
7546                         syserr("socket map \"%s\": invalid port number %s",
7547                                        map->map_mname, colon);
7548                         return false;
7549 #  else /* NO_GETSERVBYNAME */
7550                         register struct servent *sp;
7551
7552                         sp = getservbyname(colon, "tcp");
7553                         if (sp == NULL)
7554                         {
7555                                 syserr("socket map \"%s\": unknown port name %s",
7556                                                map->map_mname, colon);
7557                                 return false;
7558                         }
7559                         port = sp->s_port;
7560 #  endif /* NO_GETSERVBYNAME */
7561                 }
7562                 *at++ = '@';
7563                 if (*at == '[')
7564                 {
7565                         char *end;
7566
7567                         end = strchr(at, ']');
7568                         if (end != NULL)
7569                         {
7570                                 bool found = false;
7571 #  if NETINET
7572                                 unsigned long hid = INADDR_NONE;
7573 #  endif /* NETINET */
7574 #  if NETINET6
7575                                 struct sockaddr_in6 hid6;
7576 #  endif /* NETINET6 */
7577
7578                                 *end = '\0';
7579 #  if NETINET
7580                                 if (addr.sa.sa_family == AF_INET &&
7581                                     (hid = inet_addr(&at[1])) != INADDR_NONE)
7582                                 {
7583                                         addr.sin.sin_addr.s_addr = hid;
7584                                         addr.sin.sin_port = port;
7585                                         found = true;
7586                                 }
7587 #  endif /* NETINET */
7588 #  if NETINET6
7589                                 (void) memset(&hid6, '\0', sizeof(hid6));
7590                                 if (addr.sa.sa_family == AF_INET6 &&
7591                                     anynet_pton(AF_INET6, &at[1],
7592                                                 &hid6.sin6_addr) == 1)
7593                                 {
7594                                         addr.sin6.sin6_addr = hid6.sin6_addr;
7595                                         addr.sin6.sin6_port = port;
7596                                         found = true;
7597                                 }
7598 #  endif /* NETINET6 */
7599                                 *end = ']';
7600                                 if (!found)
7601                                 {
7602                                         syserr("socket map \"%s\": Invalid numeric domain spec \"%s\"",
7603                                                map->map_mname, at);
7604                                         return false;
7605                                 }
7606                         }
7607                         else
7608                         {
7609                                 syserr("socket map \"%s\": Invalid numeric domain spec \"%s\"",
7610                                        map->map_mname, at);
7611                                 return false;
7612                         }
7613                 }
7614                 else
7615                 {
7616                         hp = sm_gethostbyname(at, addr.sa.sa_family);
7617                         if (hp == NULL)
7618                         {
7619                                 syserr("socket map \"%s\": Unknown host name %s",
7620                                         map->map_mname, at);
7621                                 return false;
7622                         }
7623                         addr.sa.sa_family = hp->h_addrtype;
7624                         switch (hp->h_addrtype)
7625                         {
7626 #  if NETINET
7627                           case AF_INET:
7628                                 memmove(&addr.sin.sin_addr,
7629                                         hp->h_addr, INADDRSZ);
7630                                 addr.sin.sin_port = port;
7631                                 addrlen = sizeof(struct sockaddr_in);
7632                                 addrno = 1;
7633                                 break;
7634 #  endif /* NETINET */
7635
7636 #  if NETINET6
7637                           case AF_INET6:
7638                                 memmove(&addr.sin6.sin6_addr,
7639                                         hp->h_addr, IN6ADDRSZ);
7640                                 addr.sin6.sin6_port = port;
7641                                 addrlen = sizeof(struct sockaddr_in6);
7642                                 addrno = 1;
7643                                 break;
7644 #  endif /* NETINET6 */
7645
7646                           default:
7647                                 syserr("socket map \"%s\": Unknown protocol for %s (%d)",
7648                                         map->map_mname, at, hp->h_addrtype);
7649 #  if NETINET6
7650                                 freehostent(hp);
7651 #  endif /* NETINET6 */
7652                                 return false;
7653                         }
7654                 }
7655         }
7656         else
7657 # endif /* NETINET || NETINET6 */
7658         {
7659                 syserr("socket map \"%s\": unknown socket protocol",
7660                         map->map_mname);
7661                 return false;
7662         }
7663
7664         /* nope, actually connecting */
7665         for (;;)
7666         {
7667                 sock = socket(addr.sa.sa_family, SOCK_STREAM, 0);
7668                 if (sock < 0)
7669                 {
7670                         save_errno = errno;
7671                         if (tTd(38, 5))
7672                                 sm_dprintf("socket map \"%s\": error creating socket: %s\n",
7673                                            map->map_mname,
7674                                            sm_errstring(save_errno));
7675 # if NETINET6
7676                         if (hp != NULL)
7677                                 freehostent(hp);
7678 # endif /* NETINET6 */
7679                         return false;
7680                 }
7681
7682                 if (connect(sock, (struct sockaddr *) &addr, addrlen) >= 0)
7683                         break;
7684
7685                 /* couldn't connect.... try next address */
7686                 save_errno = errno;
7687                 p = CurHostName;
7688                 CurHostName = at;
7689                 if (tTd(38, 5))
7690                         sm_dprintf("socket_open (%s): open %s failed: %s\n",
7691                                 map->map_mname, at, sm_errstring(save_errno));
7692                 CurHostName = p;
7693                 (void) close(sock);
7694
7695                 /* try next address */
7696                 if (hp != NULL && hp->h_addr_list[addrno] != NULL)
7697                 {
7698                         switch (addr.sa.sa_family)
7699                         {
7700 # if NETINET
7701                           case AF_INET:
7702                                 memmove(&addr.sin.sin_addr,
7703                                         hp->h_addr_list[addrno++],
7704                                         INADDRSZ);
7705                                 break;
7706 # endif /* NETINET */
7707
7708 # if NETINET6
7709                           case AF_INET6:
7710                                 memmove(&addr.sin6.sin6_addr,
7711                                         hp->h_addr_list[addrno++],
7712                                         IN6ADDRSZ);
7713                                 break;
7714 # endif /* NETINET6 */
7715
7716                           default:
7717                                 if (tTd(38, 5))
7718                                         sm_dprintf("socket map \"%s\": Unknown protocol for %s (%d)\n",
7719                                                    map->map_mname, at,
7720                                                    hp->h_addrtype);
7721 # if NETINET6
7722                                 freehostent(hp);
7723 # endif /* NETINET6 */
7724                                 return false;
7725                         }
7726                         continue;
7727                 }
7728                 p = CurHostName;
7729                 CurHostName = at;
7730                 if (tTd(38, 5))
7731                         sm_dprintf("socket map \"%s\": error connecting to socket map: %s\n",
7732                                    map->map_mname, sm_errstring(save_errno));
7733                 CurHostName = p;
7734 # if NETINET6
7735                 if (hp != NULL)
7736                         freehostent(hp);
7737 # endif /* NETINET6 */
7738                 return false;
7739         }
7740 # if NETINET6
7741         if (hp != NULL)
7742         {
7743                 freehostent(hp);
7744                 hp = NULL;
7745         }
7746 # endif /* NETINET6 */
7747         if ((map->map_db1 = (ARBPTR_T) sm_io_open(SmFtStdiofd,
7748                                                   SM_TIME_DEFAULT,
7749                                                   (void *) &sock,
7750                                                   SM_IO_RDWR,
7751                                                   NULL)) == NULL)
7752         {
7753                 close(sock);
7754                 if (tTd(38, 2))
7755                     sm_dprintf("socket_open (%s): failed to create stream: %s\n",
7756                                map->map_mname, sm_errstring(errno));
7757                 return false;
7758         }
7759
7760         /* Save connection for reuse */
7761         s->s_socketmap = map;
7762         return true;
7763 }
7764
7765 /*
7766 **  SOCKET_MAP_FINDCONN -- find a SOCKET connection to the server
7767 **
7768 **      Cache SOCKET connections based on the connection specifier
7769 **      and PID so we don't have multiple connections open to
7770 **      the same server for different maps.  Need a separate connection
7771 **      per PID since a parent process may close the map before the
7772 **      child is done with it.
7773 **
7774 **      Parameters:
7775 **              conn -- SOCKET map connection specifier
7776 **
7777 **      Returns:
7778 **              Symbol table entry for the SOCKET connection.
7779 */
7780
7781 static STAB *
7782 socket_map_findconn(conn)
7783         const char *conn;
7784 {
7785         char *nbuf;
7786         STAB *SM_NONVOLATILE s = NULL;
7787
7788         nbuf = sm_stringf_x("%s%c%d", conn, CONDELSE, (int) CurrentPid);
7789         SM_TRY
7790                 s = stab(nbuf, ST_SOCKETMAP, ST_ENTER);
7791         SM_FINALLY
7792                 sm_free(nbuf);
7793         SM_END_TRY
7794         return s;
7795 }
7796
7797 /*
7798 **  SOCKET_MAP_CLOSE -- close the socket
7799 */
7800
7801 void
7802 socket_map_close(map)
7803         MAP *map;
7804 {
7805         STAB *s;
7806         MAP *smap;
7807
7808         if (tTd(38, 20))
7809                 sm_dprintf("socket_map_close(%s), pid=%ld\n", map->map_file,
7810                         (long) CurrentPid);
7811
7812         /* Check if already closed */
7813         if (map->map_db1 == NULL)
7814         {
7815                 if (tTd(38, 20))
7816                         sm_dprintf("socket_map_close(%s) already closed\n",
7817                                 map->map_file);
7818                 return;
7819         }
7820         sm_io_close((SM_FILE_T *)map->map_db1, SM_TIME_DEFAULT);
7821
7822         /* Mark all the maps that share the connection as closed */
7823         s = socket_map_findconn(map->map_file);
7824         smap = s->s_socketmap;
7825         while (smap != NULL)
7826         {
7827                 MAP *next;
7828
7829                 if (tTd(38, 2) && smap != map)
7830                         sm_dprintf("socket_map_close(%s): closed %s (shared SOCKET connection)\n",
7831                                 map->map_mname, smap->map_mname);
7832
7833                 smap->map_mflags &= ~(MF_OPEN|MF_WRITABLE);
7834                 smap->map_db1 = NULL;
7835                 next = smap->socket_map_next;
7836                 smap->socket_map_next = NULL;
7837                 smap = next;
7838         }
7839         s->s_socketmap = NULL;
7840 }
7841
7842 /*
7843 ** SOCKET_MAP_LOOKUP -- look up a datum in a SOCKET table
7844 */
7845
7846 char *
7847 socket_map_lookup(map, name, av, statp)
7848         MAP *map;
7849         char *name;
7850         char **av;
7851         int *statp;
7852 {
7853         unsigned int nettolen, replylen, recvlen;
7854         char *replybuf, *rval, *value, *status, *key;
7855         SM_FILE_T *f;
7856         char keybuf[MAXNAME + 1];
7857
7858         replybuf = NULL;
7859         rval = NULL;
7860         f = (SM_FILE_T *)map->map_db1;
7861         if (tTd(38, 20))
7862                 sm_dprintf("socket_map_lookup(%s, %s) %s\n",
7863                         map->map_mname, name, map->map_file);
7864
7865         if (!bitset(MF_NOFOLDCASE, map->map_mflags))
7866         {
7867                 nettolen = strlen(name);
7868                 if (nettolen > sizeof(keybuf) - 1)
7869                         nettolen = sizeof(keybuf) - 1;
7870                 memmove(keybuf, name, nettolen);
7871                 keybuf[nettolen] = '\0';
7872                 makelower(keybuf);
7873                 key = keybuf;
7874         }
7875         else
7876                 key = name;
7877
7878         nettolen = strlen(map->map_mname) + 1 + strlen(key);
7879         SM_ASSERT(nettolen > strlen(map->map_mname));
7880         SM_ASSERT(nettolen > strlen(key));
7881         if ((sm_io_fprintf(f, SM_TIME_DEFAULT, "%u:%s %s,",
7882                            nettolen, map->map_mname, key) == SM_IO_EOF) ||
7883             (sm_io_flush(f, SM_TIME_DEFAULT) != 0) ||
7884             (sm_io_error(f)))
7885         {
7886                 syserr("451 4.3.0 socket_map_lookup(%s): failed to send lookup request",
7887                         map->map_mname);
7888                 *statp = EX_TEMPFAIL;
7889                 goto errcl;
7890         }
7891
7892         if (sm_io_fscanf(f, SM_TIME_DEFAULT, "%9u", &replylen) != 1)
7893         {
7894                 syserr("451 4.3.0 socket_map_lookup(%s): failed to read length parameter of reply",
7895                         map->map_mname);
7896                 *statp = EX_TEMPFAIL;
7897                 goto errcl;
7898         }
7899         if (replylen > SOCKETMAP_MAXL)
7900         {
7901                 syserr("451 4.3.0 socket_map_lookup(%s): reply too long: %u",
7902                            map->map_mname, replylen);
7903                 *statp = EX_TEMPFAIL;
7904                 goto errcl;
7905         }
7906         if (sm_io_getc(f, SM_TIME_DEFAULT) != ':')
7907         {
7908                 syserr("451 4.3.0 socket_map_lookup(%s): missing ':' in reply",
7909                         map->map_mname);
7910                 *statp = EX_TEMPFAIL;
7911                 goto error;
7912         }
7913
7914         replybuf = (char *) sm_malloc(replylen + 1);
7915         if (replybuf == NULL)
7916         {
7917                 syserr("451 4.3.0 socket_map_lookup(%s): can't allocate %u bytes",
7918                         map->map_mname, replylen + 1);
7919                 *statp = EX_OSERR;
7920                 goto error;
7921         }
7922
7923         recvlen = sm_io_read(f, SM_TIME_DEFAULT, replybuf, replylen);
7924         if (recvlen < replylen)
7925         {
7926                 syserr("451 4.3.0 socket_map_lookup(%s): received only %u of %u reply characters",
7927                            map->map_mname, recvlen, replylen);
7928                 *statp = EX_TEMPFAIL;
7929                 goto errcl;
7930         }
7931         if (sm_io_getc(f, SM_TIME_DEFAULT) != ',')
7932         {
7933                 syserr("451 4.3.0 socket_map_lookup(%s): missing ',' in reply",
7934                         map->map_mname);
7935                 *statp = EX_TEMPFAIL;
7936                 goto errcl;
7937         }
7938         status = replybuf;
7939         replybuf[recvlen] = '\0';
7940         value = strchr(replybuf, ' ');
7941         if (value != NULL)
7942         {
7943                 *value = '\0';
7944                 value++;
7945         }
7946         if (strcmp(status, "OK") == 0)
7947         {
7948                 *statp = EX_OK;
7949
7950                 /* collect the return value */
7951                 if (bitset(MF_MATCHONLY, map->map_mflags))
7952                         rval = map_rewrite(map, key, strlen(key), NULL);
7953                 else
7954                         rval = map_rewrite(map, value, strlen(value), av);
7955         }
7956         else if (strcmp(status, "NOTFOUND") == 0)
7957         {
7958                 *statp = EX_NOTFOUND;
7959                 if (tTd(38, 20))
7960                         sm_dprintf("socket_map_lookup(%s): %s not found\n",
7961                                 map->map_mname, key);
7962         }
7963         else
7964         {
7965                 if (tTd(38, 5))
7966                         sm_dprintf("socket_map_lookup(%s, %s): server returned error: type=%s, reason=%s\n",
7967                                 map->map_mname, key, status,
7968                                 value ? value : "");
7969                 if ((strcmp(status, "TEMP") == 0) ||
7970                     (strcmp(status, "TIMEOUT") == 0))
7971                         *statp = EX_TEMPFAIL;
7972                 else if(strcmp(status, "PERM") == 0)
7973                         *statp = EX_UNAVAILABLE;
7974                 else
7975                         *statp = EX_PROTOCOL;
7976         }
7977
7978         if (replybuf != NULL)
7979                 sm_free(replybuf);
7980         return rval;
7981
7982   errcl:
7983         socket_map_close(map);
7984   error:
7985         if (replybuf != NULL)
7986                 sm_free(replybuf);
7987         return rval;
7988 }
7989 #endif /* SOCKETMAP */