Initial import of binutils 2.22 on the new vendor branch
[dragonfly.git] / contrib / sendmail-8.14 / sendmail / map.c
1 /*
2  * Copyright (c) 1998-2008 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.706 2010/07/27 03:35:42 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 = EX_UNAVAILABLE;
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         if (tTd(74, 104))
3419         {
3420                 extern MAPCLASS NullMapClass;
3421
3422                 /* debug mode: don't actually open an LDAP connection */
3423                 map->map_orgclass = map->map_class;
3424                 map->map_class = &NullMapClass;
3425                 map->map_mflags |= MF_OPEN;
3426                 map->map_pid = CurrentPid;
3427                 return true;
3428         }
3429
3430         /* No connection yet, connect */
3431         if (!sm_ldap_start(map->map_mname, lmap))
3432         {
3433                 if (errno == ETIMEDOUT)
3434                 {
3435                         if (LogLevel > 1)
3436                                 sm_syslog(LOG_NOTICE, CurEnv->e_id,
3437                                           "timeout conning to LDAP server %.100s",
3438                                           id);
3439                 }
3440
3441                 if (!bitset(MF_OPTIONAL, map->map_mflags))
3442                 {
3443                         if (bitset(MF_NODEFER, map->map_mflags))
3444                         {
3445                                 syserr("%s failed to %s in map %s",
3446 # if USE_LDAP_INIT
3447                                        "ldap_init/ldap_bind",
3448 # else /* USE_LDAP_INIT */
3449                                        "ldap_open",
3450 # endif /* USE_LDAP_INIT */
3451                                        id, map->map_mname);
3452                         }
3453                         else
3454                         {
3455                                 syserr("451 4.3.5 %s failed to %s in map %s",
3456 # if USE_LDAP_INIT
3457                                        "ldap_init/ldap_bind",
3458 # else /* USE_LDAP_INIT */
3459                                        "ldap_open",
3460 # endif /* USE_LDAP_INIT */
3461                                        id, map->map_mname);
3462                         }
3463                 }
3464                 return false;
3465         }
3466
3467         /* Save connection for reuse */
3468         s->s_lmap = map;
3469         return true;
3470 }
3471
3472 /*
3473 **  LDAPMAP_CLOSE -- close ldap map
3474 */
3475
3476 void
3477 ldapmap_close(map)
3478         MAP *map;
3479 {
3480         SM_LDAP_STRUCT *lmap;
3481         STAB *s;
3482
3483         if (tTd(38, 2))
3484                 sm_dprintf("ldapmap_close(%s)\n", map->map_mname);
3485
3486         lmap = (SM_LDAP_STRUCT *) map->map_db1;
3487
3488         /* Check if already closed */
3489         if (lmap->ldap_ld == NULL)
3490                 return;
3491
3492         /* Close the LDAP connection */
3493         sm_ldap_close(lmap);
3494
3495         /* Mark all the maps that share the connection as closed */
3496         s = ldapmap_findconn(lmap);
3497
3498         while (s->s_lmap != NULL)
3499         {
3500                 MAP *smap = s->s_lmap;
3501
3502                 if (tTd(38, 2) && smap != map)
3503                         sm_dprintf("ldapmap_close(%s): closed %s (shared LDAP connection)\n",
3504                                    map->map_mname, smap->map_mname);
3505                 smap->map_mflags &= ~(MF_OPEN|MF_WRITABLE);
3506                 lmap = (SM_LDAP_STRUCT *) smap->map_db1;
3507                 lmap->ldap_ld = NULL;
3508                 s->s_lmap = lmap->ldap_next;
3509                 lmap->ldap_next = NULL;
3510         }
3511 }
3512
3513 # ifdef SUNET_ID
3514 /*
3515 **  SUNET_ID_HASH -- Convert a string to its Sunet_id canonical form
3516 **  This only makes sense at Stanford University.
3517 */
3518
3519 static char *
3520 sunet_id_hash(str)
3521         char *str;
3522 {
3523         char *p, *p_last;
3524
3525         p = str;
3526         p_last = p;
3527         while (*p != '\0')
3528         {
3529                 if (isascii(*p) && (islower(*p) || isdigit(*p)))
3530                 {
3531                         *p_last = *p;
3532                         p_last++;
3533                 }
3534                 else if (isascii(*p) && isupper(*p))
3535                 {
3536                         *p_last = tolower(*p);
3537                         p_last++;
3538                 }
3539                 ++p;
3540         }
3541         if (*p_last != '\0')
3542                 *p_last = '\0';
3543         return str;
3544 }
3545 #  define SM_CONVERT_ID(str)    sunet_id_hash(str)
3546 # else /* SUNET_ID */
3547 #  define SM_CONVERT_ID(str)    makelower(str)
3548 # endif /* SUNET_ID */
3549
3550 /*
3551 **  LDAPMAP_LOOKUP -- look up a datum in a LDAP map
3552 */
3553
3554 char *
3555 ldapmap_lookup(map, name, av, statp)
3556         MAP *map;
3557         char *name;
3558         char **av;
3559         int *statp;
3560 {
3561         int flags;
3562         int i;
3563         int plen = 0;
3564         int psize = 0;
3565         int msgid;
3566         int save_errno;
3567         char *vp, *p;
3568         char *result = NULL;
3569         SM_RPOOL_T *rpool;
3570         SM_LDAP_STRUCT *lmap = NULL;
3571         char *argv[SM_LDAP_ARGS];
3572         char keybuf[MAXKEY];
3573 #if SM_LDAP_ARGS != MAX_MAP_ARGS
3574 # ERROR _SM_LDAP_ARGS must be the same as _MAX_MAP_ARGS
3575 #endif /* SM_LDAP_ARGS != MAX_MAP_ARGS */
3576
3577 #if defined(SUN_EXTENSIONS) && defined(SUN_SIMPLIFIED_LDAP) && \
3578     HASLDAPGETALIASBYNAME
3579         if (VendorCode == VENDOR_SUN &&
3580             strcmp(map->map_mname, "aliases.ldap") == 0)
3581         {
3582                 int rc;
3583 #if defined(GETLDAPALIASBYNAME_VERSION) && (GETLDAPALIASBYNAME_VERSION >= 2)
3584                 extern char *__getldapaliasbyname();
3585                 char *answer;
3586
3587                 answer = __getldapaliasbyname(name, &rc);
3588 #else
3589                 char answer[MAXNAME + 1];
3590
3591                 rc = __getldapaliasbyname(name, answer, sizeof(answer));
3592 #endif
3593                 if (rc != 0)
3594                 {
3595                         if (tTd(38, 20))
3596                                 sm_dprintf("getldapaliasbyname(%.100s) failed, errno=%d\n",
3597                                            name, errno);
3598                         *statp = EX_NOTFOUND;
3599                         return NULL;
3600                 }
3601                 *statp = EX_OK;
3602                 if (tTd(38, 20))
3603                         sm_dprintf("getldapaliasbyname(%.100s) => %s\n", name,
3604                                    answer);
3605                 if (bitset(MF_MATCHONLY, map->map_mflags))
3606                         result = map_rewrite(map, name, strlen(name), NULL);
3607                 else
3608                         result = map_rewrite(map, answer, strlen(answer), av);
3609 #if defined(GETLDAPALIASBYNAME_VERSION) && (GETLDAPALIASBYNAME_VERSION >= 2)
3610                 free(answer);
3611 #endif
3612                 return result;
3613         }
3614 #endif /* defined(SUN_EXTENSIONS) && defined(SUN_SIMPLIFIED_LDAP) && ... */
3615
3616         /* Get ldap struct pointer from map */
3617         lmap = (SM_LDAP_STRUCT *) map->map_db1;
3618         sm_ldap_setopts(lmap->ldap_ld, lmap);
3619
3620         if (lmap->ldap_multi_args)
3621         {
3622                 SM_REQUIRE(av != NULL);
3623                 memset(argv, '\0', sizeof(argv));
3624                 for (i = 0; i < SM_LDAP_ARGS && av[i] != NULL; i++)
3625                 {
3626                         argv[i] = sm_strdup(av[i]);
3627                         if (argv[i] == NULL)
3628                         {
3629                                 int save_errno, j;
3630
3631                                 save_errno = errno;
3632                                 for (j = 0; j < i && argv[j] != NULL; j++)
3633                                         SM_FREE(argv[j]);
3634                                 *statp = EX_TEMPFAIL;
3635                                 errno = save_errno;
3636                                 return NULL;
3637                         }
3638
3639                         if (!bitset(MF_NOFOLDCASE, map->map_mflags))
3640                                 SM_CONVERT_ID(av[i]);
3641                 }
3642         }
3643         else
3644         {
3645                 (void) sm_strlcpy(keybuf, name, sizeof(keybuf));
3646
3647                 if (!bitset(MF_NOFOLDCASE, map->map_mflags))
3648                         SM_CONVERT_ID(keybuf);
3649         }
3650
3651         if (tTd(38, 20))
3652         {
3653                 if (lmap->ldap_multi_args)
3654                 {
3655                         sm_dprintf("ldapmap_lookup(%s, argv)\n",
3656                                 map->map_mname);
3657                         for (i = 0; i < SM_LDAP_ARGS; i++)
3658                         {
3659                                 sm_dprintf("   argv[%d] = %s\n", i,
3660                                            argv[i] == NULL ? "NULL" : argv[i]);
3661                         }
3662                 }
3663                 else
3664                 {
3665                         sm_dprintf("ldapmap_lookup(%s, %s)\n",
3666                                    map->map_mname, name);
3667                 }
3668         }
3669
3670         if (lmap->ldap_multi_args)
3671         {
3672                 msgid = sm_ldap_search_m(lmap, argv);
3673
3674                 /* free the argv array and its content, no longer needed */
3675                 for (i = 0; i < SM_LDAP_ARGS && argv[i] != NULL; i++)
3676                         SM_FREE(argv[i]);
3677         }
3678         else
3679                 msgid = sm_ldap_search(lmap, keybuf);
3680         if (msgid == SM_LDAP_ERR)
3681         {
3682                 errno = sm_ldap_geterrno(lmap->ldap_ld) + E_LDAPBASE;
3683                 save_errno = errno;
3684                 if (!bitset(MF_OPTIONAL, map->map_mflags))
3685                 {
3686                         /*
3687                         **  Do not include keybuf as this error may be shown
3688                         **  to outsiders.
3689                         */
3690
3691                         if (bitset(MF_NODEFER, map->map_mflags))
3692                                 syserr("Error in ldap_search in map %s",
3693                                        map->map_mname);
3694                         else
3695                                 syserr("451 4.3.5 Error in ldap_search in map %s",
3696                                        map->map_mname);
3697                 }
3698                 *statp = EX_TEMPFAIL;
3699                 switch (save_errno - E_LDAPBASE)
3700                 {
3701 # ifdef LDAP_SERVER_DOWN
3702                   case LDAP_SERVER_DOWN:
3703 # endif /* LDAP_SERVER_DOWN */
3704                   case LDAP_TIMEOUT:
3705                   case LDAP_UNAVAILABLE:
3706                         /* server disappeared, try reopen on next search */
3707                         ldapmap_close(map);
3708                         break;
3709                 }
3710                 errno = save_errno;
3711                 return NULL;
3712         }
3713 #if SM_LDAP_ERROR_ON_MISSING_ARGS
3714         else if (msgid == SM_LDAP_ERR_ARG_MISS)
3715         {
3716                 if (bitset(MF_NODEFER, map->map_mflags))
3717                         syserr("Error in ldap_search in map %s, too few arguments",
3718                                map->map_mname);
3719                 else
3720                         syserr("554 5.3.5 Error in ldap_search in map %s, too few arguments",
3721                                map->map_mname);
3722                 *statp = EX_CONFIG;
3723                 return NULL;
3724         }
3725 #endif /* SM_LDAP_ERROR_ON_MISSING_ARGS */
3726
3727         *statp = EX_NOTFOUND;
3728         vp = NULL;
3729
3730         flags = 0;
3731         if (bitset(MF_SINGLEMATCH, map->map_mflags))
3732                 flags |= SM_LDAP_SINGLEMATCH;
3733         if (bitset(MF_MATCHONLY, map->map_mflags))
3734                 flags |= SM_LDAP_MATCHONLY;
3735 # if _FFR_LDAP_SINGLEDN
3736         if (bitset(MF_SINGLEDN, map->map_mflags))
3737                 flags |= SM_LDAP_SINGLEDN;
3738 # endif /* _FFR_LDAP_SINGLEDN */
3739
3740         /* Create an rpool for search related memory usage */
3741         rpool = sm_rpool_new_x(NULL);
3742
3743         p = NULL;
3744         *statp = sm_ldap_results(lmap, msgid, flags, map->map_coldelim,
3745                                  rpool, &p, &plen, &psize, NULL);
3746         save_errno = errno;
3747
3748         /* Copy result so rpool can be freed */
3749         if (*statp == EX_OK && p != NULL)
3750                 vp = newstr(p);
3751         sm_rpool_free(rpool);
3752
3753         /* need to restart LDAP connection? */
3754         if (*statp == EX_RESTART)
3755         {
3756                 *statp = EX_TEMPFAIL;
3757                 ldapmap_close(map);
3758         }
3759
3760         errno = save_errno;
3761         if (*statp != EX_OK && *statp != EX_NOTFOUND)
3762         {
3763                 if (!bitset(MF_OPTIONAL, map->map_mflags))
3764                 {
3765                         if (bitset(MF_NODEFER, map->map_mflags))
3766                                 syserr("Error getting LDAP results in map %s",
3767                                        map->map_mname);
3768                         else
3769                                 syserr("451 4.3.5 Error getting LDAP results in map %s",
3770                                        map->map_mname);
3771                 }
3772                 errno = save_errno;
3773                 return NULL;
3774         }
3775
3776         /* Did we match anything? */
3777         if (vp == NULL && !bitset(MF_MATCHONLY, map->map_mflags))
3778                 return NULL;
3779
3780         if (*statp == EX_OK)
3781         {
3782                 if (LogLevel > 9)
3783                         sm_syslog(LOG_INFO, CurEnv->e_id,
3784                                   "ldap %.100s => %s", name,
3785                                   vp == NULL ? "<NULL>" : vp);
3786                 if (bitset(MF_MATCHONLY, map->map_mflags))
3787                         result = map_rewrite(map, name, strlen(name), NULL);
3788                 else
3789                 {
3790                         /* vp != NULL according to test above */
3791                         result = map_rewrite(map, vp, strlen(vp), av);
3792                 }
3793                 if (vp != NULL)
3794                         sm_free(vp); /* XXX */
3795         }
3796         return result;
3797 }
3798
3799 /*
3800 **  LDAPMAP_FINDCONN -- find an LDAP connection to the server
3801 **
3802 **      Cache LDAP connections based on the host, port, bind DN,
3803 **      secret, and PID so we don't have multiple connections open to
3804 **      the same server for different maps.  Need a separate connection
3805 **      per PID since a parent process may close the map before the
3806 **      child is done with it.
3807 **
3808 **      Parameters:
3809 **              lmap -- LDAP map information
3810 **
3811 **      Returns:
3812 **              Symbol table entry for the LDAP connection.
3813 */
3814
3815 static STAB *
3816 ldapmap_findconn(lmap)
3817         SM_LDAP_STRUCT *lmap;
3818 {
3819         char *format;
3820         char *nbuf;
3821         char *id;
3822         STAB *SM_NONVOLATILE s = NULL;
3823
3824         if (lmap->ldap_host != NULL)
3825                 id = lmap->ldap_host;
3826         else if (lmap->ldap_uri != NULL)
3827                 id = lmap->ldap_uri;
3828         else
3829                 id = "localhost";
3830
3831         format = "%s%c%d%c%d%c%s%c%s%d";
3832         nbuf = sm_stringf_x(format,
3833                             id,
3834                             CONDELSE,
3835                             lmap->ldap_port,
3836                             CONDELSE,
3837                             lmap->ldap_version,
3838                             CONDELSE,
3839                             (lmap->ldap_binddn == NULL ? ""
3840                                                        : lmap->ldap_binddn),
3841                             CONDELSE,
3842                             (lmap->ldap_secret == NULL ? ""
3843                                                        : lmap->ldap_secret),
3844                             (int) CurrentPid);
3845         SM_TRY
3846                 s = stab(nbuf, ST_LMAP, ST_ENTER);
3847         SM_FINALLY
3848                 sm_free(nbuf);
3849         SM_END_TRY
3850         return s;
3851 }
3852 /*
3853 **  LDAPMAP_PARSEARGS -- parse ldap map definition args.
3854 */
3855
3856 static struct lamvalues LDAPAuthMethods[] =
3857 {
3858         {       "none",         LDAP_AUTH_NONE          },
3859         {       "simple",       LDAP_AUTH_SIMPLE        },
3860 # ifdef LDAP_AUTH_KRBV4
3861         {       "krbv4",        LDAP_AUTH_KRBV4         },
3862 # endif /* LDAP_AUTH_KRBV4 */
3863         {       NULL,           0                       }
3864 };
3865
3866 static struct ladvalues LDAPAliasDereference[] =
3867 {
3868         {       "never",        LDAP_DEREF_NEVER        },
3869         {       "always",       LDAP_DEREF_ALWAYS       },
3870         {       "search",       LDAP_DEREF_SEARCHING    },
3871         {       "find",         LDAP_DEREF_FINDING      },
3872         {       NULL,           0                       }
3873 };
3874
3875 static struct lssvalues LDAPSearchScope[] =
3876 {
3877         {       "base",         LDAP_SCOPE_BASE         },
3878         {       "one",          LDAP_SCOPE_ONELEVEL     },
3879         {       "sub",          LDAP_SCOPE_SUBTREE      },
3880         {       NULL,           0                       }
3881 };
3882
3883 bool
3884 ldapmap_parseargs(map, args)
3885         MAP *map;
3886         char *args;
3887 {
3888         bool secretread = true;
3889         bool attrssetup = false;
3890         int i;
3891         register char *p = args;
3892         SM_LDAP_STRUCT *lmap;
3893         struct lamvalues *lam;
3894         struct ladvalues *lad;
3895         struct lssvalues *lss;
3896         char ldapfilt[MAXLINE];
3897         char m_tmp[MAXPATHLEN + LDAPMAP_MAX_PASSWD];
3898
3899         /* Get ldap struct pointer from map */
3900         lmap = (SM_LDAP_STRUCT *) map->map_db1;
3901
3902         /* Check if setting the initial LDAP defaults */
3903         if (lmap == NULL || lmap != LDAPDefaults)
3904         {
3905                 /* We need to alloc an SM_LDAP_STRUCT struct */
3906                 lmap = (SM_LDAP_STRUCT *) xalloc(sizeof(*lmap));
3907                 if (LDAPDefaults == NULL)
3908                         sm_ldap_clear(lmap);
3909                 else
3910                         STRUCTCOPY(*LDAPDefaults, *lmap);
3911         }
3912
3913         /* there is no check whether there is really an argument */
3914         map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL;
3915         map->map_spacesub = SpaceSub;   /* default value */
3916
3917         /* Check if setting up an alias or file class LDAP map */
3918         if (bitset(MF_ALIAS, map->map_mflags))
3919         {
3920                 /* Comma separate if used as an alias file */
3921                 map->map_coldelim = ',';
3922                 if (*args == '\0')
3923                 {
3924                         int n;
3925                         char *lc;
3926                         char jbuf[MAXHOSTNAMELEN];
3927                         char lcbuf[MAXLINE];
3928
3929                         /* Get $j */
3930                         expand("\201j", jbuf, sizeof(jbuf), &BlankEnvelope);
3931                         if (jbuf[0] == '\0')
3932                         {
3933                                 (void) sm_strlcpy(jbuf, "localhost",
3934                                                   sizeof(jbuf));
3935                         }
3936
3937                         lc = macvalue(macid("{sendmailMTACluster}"), CurEnv);
3938                         if (lc == NULL)
3939                                 lc = "";
3940                         else
3941                         {
3942                                 expand(lc, lcbuf, sizeof(lcbuf), CurEnv);
3943                                 lc = lcbuf;
3944                         }
3945
3946                         n = sm_snprintf(ldapfilt, sizeof(ldapfilt),
3947                                         "(&(objectClass=sendmailMTAAliasObject)(sendmailMTAAliasGrouping=aliases)(|(sendmailMTACluster=%s)(sendmailMTAHost=%s))(sendmailMTAKey=%%0))",
3948                                         lc, jbuf);
3949                         if (n >= sizeof(ldapfilt))
3950                         {
3951                                 syserr("%s: Default LDAP string too long",
3952                                        map->map_mname);
3953                                 return false;
3954                         }
3955
3956                         /* default args for an alias LDAP entry */
3957                         lmap->ldap_filter = ldapfilt;
3958                         lmap->ldap_attr[0] = "objectClass";
3959                         lmap->ldap_attr_type[0] = SM_LDAP_ATTR_OBJCLASS;
3960                         lmap->ldap_attr_needobjclass[0] = NULL;
3961                         lmap->ldap_attr[1] = "sendmailMTAAliasValue";
3962                         lmap->ldap_attr_type[1] = SM_LDAP_ATTR_NORMAL;
3963                         lmap->ldap_attr_needobjclass[1] = NULL;
3964                         lmap->ldap_attr[2] = "sendmailMTAAliasSearch";
3965                         lmap->ldap_attr_type[2] = SM_LDAP_ATTR_FILTER;
3966                         lmap->ldap_attr_needobjclass[2] = "sendmailMTAMapObject";
3967                         lmap->ldap_attr[3] = "sendmailMTAAliasURL";
3968                         lmap->ldap_attr_type[3] = SM_LDAP_ATTR_URL;
3969                         lmap->ldap_attr_needobjclass[3] = "sendmailMTAMapObject";
3970                         lmap->ldap_attr[4] = NULL;
3971                         lmap->ldap_attr_type[4] = SM_LDAP_ATTR_NONE;
3972                         lmap->ldap_attr_needobjclass[4] = NULL;
3973                         attrssetup = true;
3974                 }
3975         }
3976         else if (bitset(MF_FILECLASS, map->map_mflags))
3977         {
3978                 /* Space separate if used as a file class file */
3979                 map->map_coldelim = ' ';
3980         }
3981
3982 # if _FFR_LDAP_NETWORK_TIMEOUT
3983         lmap->ldap_networktmo = 120;
3984 # endif /* _FFR_LDAP_NETWORK_TIMEOUT */
3985
3986         for (;;)
3987         {
3988                 while (isascii(*p) && isspace(*p))
3989                         p++;
3990                 if (*p != '-')
3991                         break;
3992                 switch (*++p)
3993                 {
3994                   case 'A':
3995                         map->map_mflags |= MF_APPEND;
3996                         break;
3997
3998                   case 'a':
3999                         map->map_app = ++p;
4000                         break;
4001
4002                   case 'D':
4003                         map->map_mflags |= MF_DEFER;
4004                         break;
4005
4006                   case 'f':
4007                         map->map_mflags |= MF_NOFOLDCASE;
4008                         break;
4009
4010                   case 'm':
4011                         map->map_mflags |= MF_MATCHONLY;
4012                         break;
4013
4014                   case 'N':
4015                         map->map_mflags |= MF_INCLNULL;
4016                         map->map_mflags &= ~MF_TRY0NULL;
4017                         break;
4018
4019                   case 'O':
4020                         map->map_mflags &= ~MF_TRY1NULL;
4021                         break;
4022
4023                   case 'o':
4024                         map->map_mflags |= MF_OPTIONAL;
4025                         break;
4026
4027                   case 'q':
4028                         map->map_mflags |= MF_KEEPQUOTES;
4029                         break;
4030
4031                   case 'S':
4032                         map->map_spacesub = *++p;
4033                         break;
4034
4035                   case 'T':
4036                         map->map_tapp = ++p;
4037                         break;
4038
4039                   case 't':
4040                         map->map_mflags |= MF_NODEFER;
4041                         break;
4042
4043                   case 'z':
4044                         if (*++p != '\\')
4045                                 map->map_coldelim = *p;
4046                         else
4047                         {
4048                                 switch (*++p)
4049                                 {
4050                                   case 'n':
4051                                         map->map_coldelim = '\n';
4052                                         break;
4053
4054                                   case 't':
4055                                         map->map_coldelim = '\t';
4056                                         break;
4057
4058                                   default:
4059                                         map->map_coldelim = '\\';
4060                                 }
4061                         }
4062                         break;
4063
4064                         /* Start of ldapmap specific args */
4065                   case '1':
4066                         map->map_mflags |= MF_SINGLEMATCH;
4067                         break;
4068
4069 # if _FFR_LDAP_SINGLEDN
4070                   case '2':
4071                         map->map_mflags |= MF_SINGLEDN;
4072                         break;
4073 # endif /* _FFR_LDAP_SINGLEDN */
4074
4075                   case 'b':             /* search base */
4076                         while (isascii(*++p) && isspace(*p))
4077                                 continue;
4078                         lmap->ldap_base = p;
4079                         break;
4080
4081 # if _FFR_LDAP_NETWORK_TIMEOUT
4082                   case 'c':             /* network (connect) timeout */
4083                         while (isascii(*++p) && isspace(*p))
4084                                 continue;
4085                         lmap->ldap_networktmo = atoi(p);
4086                         break;
4087 # endif /* _FFR_LDAP_NETWORK_TIMEOUT */
4088
4089                   case 'd':             /* Dn to bind to server as */
4090                         while (isascii(*++p) && isspace(*p))
4091                                 continue;
4092                         lmap->ldap_binddn = p;
4093                         break;
4094
4095                   case 'H':             /* Use LDAP URI */
4096 #  if !USE_LDAP_INIT
4097                         syserr("Must compile with -DUSE_LDAP_INIT to use LDAP URIs (-H) in map %s",
4098                                map->map_mname);
4099                         return false;
4100 #   else /* !USE_LDAP_INIT */
4101                         if (lmap->ldap_host != NULL)
4102                         {
4103                                 syserr("Can not specify both an LDAP host and an LDAP URI in map %s",
4104                                        map->map_mname);
4105                                 return false;
4106                         }
4107                         while (isascii(*++p) && isspace(*p))
4108                                 continue;
4109                         lmap->ldap_uri = p;
4110                         break;
4111 #  endif /* !USE_LDAP_INIT */
4112
4113                   case 'h':             /* ldap host */
4114                         while (isascii(*++p) && isspace(*p))
4115                                 continue;
4116                         if (lmap->ldap_uri != NULL)
4117                         {
4118                                 syserr("Can not specify both an LDAP host and an LDAP URI in map %s",
4119                                        map->map_mname);
4120                                 return false;
4121                         }
4122                         lmap->ldap_host = p;
4123                         break;
4124
4125                   case 'K':
4126                         lmap->ldap_multi_args = true;
4127                         break;
4128
4129                   case 'k':             /* search field */
4130                         while (isascii(*++p) && isspace(*p))
4131                                 continue;
4132                         lmap->ldap_filter = p;
4133                         break;
4134
4135                   case 'l':             /* time limit */
4136                         while (isascii(*++p) && isspace(*p))
4137                                 continue;
4138                         lmap->ldap_timelimit = atoi(p);
4139                         lmap->ldap_timeout.tv_sec = lmap->ldap_timelimit;
4140                         break;
4141
4142                   case 'M':             /* Method for binding */
4143                         while (isascii(*++p) && isspace(*p))
4144                                 continue;
4145
4146                         if (sm_strncasecmp(p, "LDAP_AUTH_", 10) == 0)
4147                                 p += 10;
4148
4149                         for (lam = LDAPAuthMethods;
4150                              lam != NULL && lam->lam_name != NULL; lam++)
4151                         {
4152                                 if (sm_strncasecmp(p, lam->lam_name,
4153                                                    strlen(lam->lam_name)) == 0)
4154                                         break;
4155                         }
4156                         if (lam->lam_name != NULL)
4157                                 lmap->ldap_method = lam->lam_code;
4158                         else
4159                         {
4160                                 /* bad config line */
4161                                 if (!bitset(MCF_OPTFILE,
4162                                             map->map_class->map_cflags))
4163                                 {
4164                                         char *ptr;
4165
4166                                         if ((ptr = strchr(p, ' ')) != NULL)
4167                                                 *ptr = '\0';
4168                                         syserr("Method for binding must be [none|simple|krbv4] (not %s) in map %s",
4169                                                 p, map->map_mname);
4170                                         if (ptr != NULL)
4171                                                 *ptr = ' ';
4172                                         return false;
4173                                 }
4174                         }
4175                         break;
4176
4177                   case 'n':             /* retrieve attribute names only */
4178                         lmap->ldap_attrsonly = LDAPMAP_TRUE;
4179                         break;
4180
4181                         /*
4182                         **  This is a string that is dependent on the
4183                         **  method used defined by 'M'.
4184                         */
4185
4186                   case 'P':             /* Secret password for binding */
4187                          while (isascii(*++p) && isspace(*p))
4188                                 continue;
4189                         lmap->ldap_secret = p;
4190                         secretread = false;
4191                         break;
4192
4193                   case 'p':             /* ldap port */
4194                         while (isascii(*++p) && isspace(*p))
4195                                 continue;
4196                         lmap->ldap_port = atoi(p);
4197                         break;
4198
4199                         /* args stolen from ldapsearch.c */
4200                   case 'R':             /* don't auto chase referrals */
4201 # ifdef LDAP_REFERRALS
4202                         lmap->ldap_options &= ~LDAP_OPT_REFERRALS;
4203 # else /* LDAP_REFERRALS */
4204                         syserr("compile with -DLDAP_REFERRALS for referral support");
4205 # endif /* LDAP_REFERRALS */
4206                         break;
4207
4208                   case 'r':             /* alias dereferencing */
4209                         while (isascii(*++p) && isspace(*p))
4210                                 continue;
4211
4212                         if (sm_strncasecmp(p, "LDAP_DEREF_", 11) == 0)
4213                                 p += 11;
4214
4215                         for (lad = LDAPAliasDereference;
4216                              lad != NULL && lad->lad_name != NULL; lad++)
4217                         {
4218                                 if (sm_strncasecmp(p, lad->lad_name,
4219                                                    strlen(lad->lad_name)) == 0)
4220                                         break;
4221                         }
4222                         if (lad->lad_name != NULL)
4223                                 lmap->ldap_deref = lad->lad_code;
4224                         else
4225                         {
4226                                 /* bad config line */
4227                                 if (!bitset(MCF_OPTFILE,
4228                                             map->map_class->map_cflags))
4229                                 {
4230                                         char *ptr;
4231
4232                                         if ((ptr = strchr(p, ' ')) != NULL)
4233                                                 *ptr = '\0';
4234                                         syserr("Deref must be [never|always|search|find] (not %s) in map %s",
4235                                                 p, map->map_mname);
4236                                         if (ptr != NULL)
4237                                                 *ptr = ' ';
4238                                         return false;
4239                                 }
4240                         }
4241                         break;
4242
4243                   case 's':             /* search scope */
4244                         while (isascii(*++p) && isspace(*p))
4245                                 continue;
4246
4247                         if (sm_strncasecmp(p, "LDAP_SCOPE_", 11) == 0)
4248                                 p += 11;
4249
4250                         for (lss = LDAPSearchScope;
4251                              lss != NULL && lss->lss_name != NULL; lss++)
4252                         {
4253                                 if (sm_strncasecmp(p, lss->lss_name,
4254                                                    strlen(lss->lss_name)) == 0)
4255                                         break;
4256                         }
4257                         if (lss->lss_name != NULL)
4258                                 lmap->ldap_scope = lss->lss_code;
4259                         else
4260                         {
4261                                 /* bad config line */
4262                                 if (!bitset(MCF_OPTFILE,
4263                                             map->map_class->map_cflags))
4264                                 {
4265                                         char *ptr;
4266
4267                                         if ((ptr = strchr(p, ' ')) != NULL)
4268                                                 *ptr = '\0';
4269                                         syserr("Scope must be [base|one|sub] (not %s) in map %s",
4270                                                 p, map->map_mname);
4271                                         if (ptr != NULL)
4272                                                 *ptr = ' ';
4273                                         return false;
4274                                 }
4275                         }
4276                         break;
4277
4278                   case 'V':
4279                         if (*++p != '\\')
4280                                 lmap->ldap_attrsep = *p;
4281                         else
4282                         {
4283                                 switch (*++p)
4284                                 {
4285                                   case 'n':
4286                                         lmap->ldap_attrsep = '\n';
4287                                         break;
4288
4289                                   case 't':
4290                                         lmap->ldap_attrsep = '\t';
4291                                         break;
4292
4293                                   default:
4294                                         lmap->ldap_attrsep = '\\';
4295                                 }
4296                         }
4297                         break;
4298
4299                   case 'v':             /* attr to return */
4300                         while (isascii(*++p) && isspace(*p))
4301                                 continue;
4302                         lmap->ldap_attr[0] = p;
4303                         lmap->ldap_attr[1] = NULL;
4304                         break;
4305
4306                   case 'w':
4307                         /* -w should be for passwd, -P should be for version */
4308                         while (isascii(*++p) && isspace(*p))
4309                                 continue;
4310                         lmap->ldap_version = atoi(p);
4311 # ifdef LDAP_VERSION_MAX
4312                         if (lmap->ldap_version > LDAP_VERSION_MAX)
4313                         {
4314                                 syserr("LDAP version %d exceeds max of %d in map %s",
4315                                        lmap->ldap_version, LDAP_VERSION_MAX,
4316                                        map->map_mname);
4317                                 return false;
4318                         }
4319 # endif /* LDAP_VERSION_MAX */
4320 # ifdef LDAP_VERSION_MIN
4321                         if (lmap->ldap_version < LDAP_VERSION_MIN)
4322                         {
4323                                 syserr("LDAP version %d is lower than min of %d in map %s",
4324                                        lmap->ldap_version, LDAP_VERSION_MIN,
4325                                        map->map_mname);
4326                                 return false;
4327                         }
4328 # endif /* LDAP_VERSION_MIN */
4329                         break;
4330
4331                   case 'Z':
4332                         while (isascii(*++p) && isspace(*p))
4333                                 continue;
4334                         lmap->ldap_sizelimit = atoi(p);
4335                         break;
4336
4337                   default:
4338                         syserr("Illegal option %c map %s", *p, map->map_mname);
4339                         break;
4340                 }
4341
4342                 /* need to account for quoted strings here */
4343                 while (*p != '\0' && !(isascii(*p) && isspace(*p)))
4344                 {
4345                         if (*p == '"')
4346                         {
4347                                 while (*++p != '"' && *p != '\0')
4348                                         continue;
4349                                 if (*p != '\0')
4350                                         p++;
4351                         }
4352                         else
4353                                 p++;
4354                 }
4355
4356                 if (*p != '\0')
4357                         *p++ = '\0';
4358         }
4359
4360         if (map->map_app != NULL)
4361                 map->map_app = newstr(ldapmap_dequote(map->map_app));
4362         if (map->map_tapp != NULL)
4363                 map->map_tapp = newstr(ldapmap_dequote(map->map_tapp));
4364
4365         /*
4366         **  We need to swallow up all the stuff into a struct
4367         **  and dump it into map->map_dbptr1
4368         */
4369
4370         if (lmap->ldap_host != NULL &&
4371             (LDAPDefaults == NULL ||
4372              LDAPDefaults == lmap ||
4373              LDAPDefaults->ldap_host != lmap->ldap_host))
4374                 lmap->ldap_host = newstr(ldapmap_dequote(lmap->ldap_host));
4375         map->map_domain = lmap->ldap_host;
4376
4377         if (lmap->ldap_uri != NULL &&
4378             (LDAPDefaults == NULL ||
4379              LDAPDefaults == lmap ||
4380              LDAPDefaults->ldap_uri != lmap->ldap_uri))
4381                 lmap->ldap_uri = newstr(ldapmap_dequote(lmap->ldap_uri));
4382         map->map_domain = lmap->ldap_uri;
4383
4384         if (lmap->ldap_binddn != NULL &&
4385             (LDAPDefaults == NULL ||
4386              LDAPDefaults == lmap ||
4387              LDAPDefaults->ldap_binddn != lmap->ldap_binddn))
4388                 lmap->ldap_binddn = newstr(ldapmap_dequote(lmap->ldap_binddn));
4389
4390         if (lmap->ldap_secret != NULL &&
4391             (LDAPDefaults == NULL ||
4392              LDAPDefaults == lmap ||
4393              LDAPDefaults->ldap_secret != lmap->ldap_secret))
4394         {
4395                 SM_FILE_T *sfd;
4396                 long sff = SFF_OPENASROOT|SFF_ROOTOK|SFF_NOWLINK|SFF_NOWWFILES|SFF_NOGWFILES;
4397
4398                 if (DontLockReadFiles)
4399                         sff |= SFF_NOLOCK;
4400
4401                 /* need to use method to map secret to passwd string */
4402                 switch (lmap->ldap_method)
4403                 {
4404                   case LDAP_AUTH_NONE:
4405                         /* Do nothing */
4406                         break;
4407
4408                   case LDAP_AUTH_SIMPLE:
4409
4410                         /*
4411                         **  Secret is the name of a file with
4412                         **  the first line as the password.
4413                         */
4414
4415                         /* Already read in the secret? */
4416                         if (secretread)
4417                                 break;
4418
4419                         sfd = safefopen(ldapmap_dequote(lmap->ldap_secret),
4420                                         O_RDONLY, 0, sff);
4421                         if (sfd == NULL)
4422                         {
4423                                 syserr("LDAP map: cannot open secret %s",
4424                                        ldapmap_dequote(lmap->ldap_secret));
4425                                 return false;
4426                         }
4427                         lmap->ldap_secret = sfgets(m_tmp, sizeof(m_tmp),
4428                                                    sfd, TimeOuts.to_fileopen,
4429                                                    "ldapmap_parseargs");
4430                         (void) sm_io_close(sfd, SM_TIME_DEFAULT);
4431                         if (strlen(m_tmp) > LDAPMAP_MAX_PASSWD)
4432                         {
4433                                 syserr("LDAP map: secret in %s too long",
4434                                        ldapmap_dequote(lmap->ldap_secret));
4435                                 return false;
4436                         }
4437                         if (lmap->ldap_secret != NULL &&
4438                             strlen(m_tmp) > 0)
4439                         {
4440                                 /* chomp newline */
4441                                 if (m_tmp[strlen(m_tmp) - 1] == '\n')
4442                                         m_tmp[strlen(m_tmp) - 1] = '\0';
4443
4444                                 lmap->ldap_secret = m_tmp;
4445                         }
4446                         break;
4447
4448 # ifdef LDAP_AUTH_KRBV4
4449                   case LDAP_AUTH_KRBV4:
4450
4451                         /*
4452                         **  Secret is where the ticket file is
4453                         **  stashed
4454                         */
4455
4456                         (void) sm_snprintf(m_tmp, sizeof(m_tmp),
4457                                 "KRBTKFILE=%s",
4458                                 ldapmap_dequote(lmap->ldap_secret));
4459                         lmap->ldap_secret = m_tmp;
4460                         break;
4461 # endif /* LDAP_AUTH_KRBV4 */
4462
4463                   default:             /* Should NEVER get here */
4464                         syserr("LDAP map: Illegal value in lmap method");
4465                         return false;
4466                         /* NOTREACHED */
4467                         break;
4468                 }
4469         }
4470
4471         if (lmap->ldap_secret != NULL &&
4472             (LDAPDefaults == NULL ||
4473              LDAPDefaults == lmap ||
4474              LDAPDefaults->ldap_secret != lmap->ldap_secret))
4475                 lmap->ldap_secret = newstr(ldapmap_dequote(lmap->ldap_secret));
4476
4477         if (lmap->ldap_base != NULL &&
4478             (LDAPDefaults == NULL ||
4479              LDAPDefaults == lmap ||
4480              LDAPDefaults->ldap_base != lmap->ldap_base))
4481                 lmap->ldap_base = newstr(ldapmap_dequote(lmap->ldap_base));
4482
4483         /*
4484         **  Save the server from extra work.  If request is for a single
4485         **  match, tell the server to only return enough records to
4486         **  determine if there is a single match or not.  This can not
4487         **  be one since the server would only return one and we wouldn't
4488         **  know if there were others available.
4489         */
4490
4491         if (bitset(MF_SINGLEMATCH, map->map_mflags))
4492                 lmap->ldap_sizelimit = 2;
4493
4494         /* If setting defaults, don't process ldap_filter and ldap_attr */
4495         if (lmap == LDAPDefaults)
4496                 return true;
4497
4498         if (lmap->ldap_filter != NULL)
4499                 lmap->ldap_filter = newstr(ldapmap_dequote(lmap->ldap_filter));
4500         else
4501         {
4502                 if (!bitset(MCF_OPTFILE, map->map_class->map_cflags))
4503                 {
4504                         syserr("No filter given in map %s", map->map_mname);
4505                         return false;
4506                 }
4507         }
4508
4509         if (!attrssetup && lmap->ldap_attr[0] != NULL)
4510         {
4511                 bool recurse = false;
4512                 bool normalseen = false;
4513
4514                 i = 0;
4515                 p = ldapmap_dequote(lmap->ldap_attr[0]);
4516                 lmap->ldap_attr[0] = NULL;
4517
4518                 /* Prime the attr list with the objectClass attribute */
4519                 lmap->ldap_attr[i] = "objectClass";
4520                 lmap->ldap_attr_type[i] = SM_LDAP_ATTR_OBJCLASS;
4521                 lmap->ldap_attr_needobjclass[i] = NULL;
4522                 i++;
4523
4524                 while (p != NULL)
4525                 {
4526                         char *v;
4527
4528                         while (isascii(*p) && isspace(*p))
4529                                 p++;
4530                         if (*p == '\0')
4531                                 break;
4532                         v = p;
4533                         p = strchr(v, ',');
4534                         if (p != NULL)
4535                                 *p++ = '\0';
4536
4537                         if (i >= LDAPMAP_MAX_ATTR)
4538                         {
4539                                 syserr("Too many return attributes in %s (max %d)",
4540                                        map->map_mname, LDAPMAP_MAX_ATTR);
4541                                 return false;
4542                         }
4543                         if (*v != '\0')
4544                         {
4545                                 int j;
4546                                 int use;
4547                                 char *type;
4548                                 char *needobjclass;
4549
4550                                 type = strchr(v, ':');
4551                                 if (type != NULL)
4552                                 {
4553                                         *type++ = '\0';
4554                                         needobjclass = strchr(type, ':');
4555                                         if (needobjclass != NULL)
4556                                                 *needobjclass++ = '\0';
4557                                 }
4558                                 else
4559                                 {
4560                                         needobjclass = NULL;
4561                                 }
4562
4563                                 use = i;
4564
4565                                 /* allow override on "objectClass" type */
4566                                 if (sm_strcasecmp(v, "objectClass") == 0 &&
4567                                     lmap->ldap_attr_type[0] == SM_LDAP_ATTR_OBJCLASS)
4568                                 {
4569                                         use = 0;
4570                                 }
4571                                 else
4572                                 {
4573                                         /*
4574                                         **  Don't add something to attribute
4575                                         **  list twice.
4576                                         */
4577
4578                                         for (j = 1; j < i; j++)
4579                                         {
4580                                                 if (sm_strcasecmp(v, lmap->ldap_attr[j]) == 0)
4581                                                 {
4582                                                         syserr("Duplicate attribute (%s) in %s",
4583                                                                v, map->map_mname);
4584                                                         return false;
4585                                                 }
4586                                         }
4587
4588                                         lmap->ldap_attr[use] = newstr(v);
4589                                         if (needobjclass != NULL &&
4590                                             *needobjclass != '\0' &&
4591                                             *needobjclass != '*')
4592                                         {
4593                                                 lmap->ldap_attr_needobjclass[use] = newstr(needobjclass);
4594                                         }
4595                                         else
4596                                         {
4597                                                 lmap->ldap_attr_needobjclass[use] = NULL;
4598                                         }
4599
4600                                 }
4601
4602                                 if (type != NULL && *type != '\0')
4603                                 {
4604                                         if (sm_strcasecmp(type, "dn") == 0)
4605                                         {
4606                                                 recurse = true;
4607                                                 lmap->ldap_attr_type[use] = SM_LDAP_ATTR_DN;
4608                                         }
4609                                         else if (sm_strcasecmp(type, "filter") == 0)
4610                                         {
4611                                                 recurse = true;
4612                                                 lmap->ldap_attr_type[use] = SM_LDAP_ATTR_FILTER;
4613                                         }
4614                                         else if (sm_strcasecmp(type, "url") == 0)
4615                                         {
4616                                                 recurse = true;
4617                                                 lmap->ldap_attr_type[use] = SM_LDAP_ATTR_URL;
4618                                         }
4619                                         else if (sm_strcasecmp(type, "normal") == 0)
4620                                         {
4621                                                 lmap->ldap_attr_type[use] = SM_LDAP_ATTR_NORMAL;
4622                                                 normalseen = true;
4623                                         }
4624                                         else
4625                                         {
4626                                                 syserr("Unknown attribute type (%s) in %s",
4627                                                        type, map->map_mname);
4628                                                 return false;
4629                                         }
4630                                 }
4631                                 else
4632                                 {
4633                                         lmap->ldap_attr_type[use] = SM_LDAP_ATTR_NORMAL;
4634                                         normalseen = true;
4635                                 }
4636                                 i++;
4637                         }
4638                 }
4639                 lmap->ldap_attr[i] = NULL;
4640
4641                 /* Set in case needed in future code */
4642                 attrssetup = true;
4643
4644                 if (recurse && !normalseen)
4645                 {
4646                         syserr("LDAP recursion requested in %s but no returnable attribute given",
4647                                map->map_mname);
4648                         return false;
4649                 }
4650                 if (recurse && lmap->ldap_attrsonly == LDAPMAP_TRUE)
4651                 {
4652                         syserr("LDAP recursion requested in %s can not be used with -n",
4653                                map->map_mname);
4654                         return false;
4655                 }
4656         }
4657         map->map_db1 = (ARBPTR_T) lmap;
4658         return true;
4659 }
4660
4661 /*
4662 **  LDAPMAP_SET_DEFAULTS -- Read default map spec from LDAPDefaults in .cf
4663 **
4664 **      Parameters:
4665 **              spec -- map argument string from LDAPDefaults option
4666 **
4667 **      Returns:
4668 **              None.
4669 */
4670
4671 void
4672 ldapmap_set_defaults(spec)
4673         char *spec;
4674 {
4675         STAB *class;
4676         MAP map;
4677
4678         /* Allocate and set the default values */
4679         if (LDAPDefaults == NULL)
4680                 LDAPDefaults = (SM_LDAP_STRUCT *) xalloc(sizeof(*LDAPDefaults));
4681         sm_ldap_clear(LDAPDefaults);
4682
4683         memset(&map, '\0', sizeof(map));
4684
4685         /* look up the class */
4686         class = stab("ldap", ST_MAPCLASS, ST_FIND);
4687         if (class == NULL)
4688         {
4689                 syserr("readcf: LDAPDefaultSpec: class ldap not available");
4690                 return;
4691         }
4692         map.map_class = &class->s_mapclass;
4693         map.map_db1 = (ARBPTR_T) LDAPDefaults;
4694         map.map_mname = "O LDAPDefaultSpec";
4695
4696         (void) ldapmap_parseargs(&map, spec);
4697
4698         /* These should never be set in LDAPDefaults */
4699         if (map.map_mflags != (MF_TRY0NULL|MF_TRY1NULL) ||
4700             map.map_spacesub != SpaceSub ||
4701             map.map_app != NULL ||
4702             map.map_tapp != NULL)
4703         {
4704                 syserr("readcf: option LDAPDefaultSpec: Do not set non-LDAP specific flags");
4705                 SM_FREE_CLR(map.map_app);
4706                 SM_FREE_CLR(map.map_tapp);
4707         }
4708
4709         if (LDAPDefaults->ldap_filter != NULL)
4710         {
4711                 syserr("readcf: option LDAPDefaultSpec: Do not set the LDAP search filter");
4712
4713                 /* don't free, it isn't malloc'ed in parseargs */
4714                 LDAPDefaults->ldap_filter = NULL;
4715         }
4716
4717         if (LDAPDefaults->ldap_attr[0] != NULL)
4718         {
4719                 syserr("readcf: option LDAPDefaultSpec: Do not set the requested LDAP attributes");
4720                 /* don't free, they aren't malloc'ed in parseargs */
4721                 LDAPDefaults->ldap_attr[0] = NULL;
4722         }
4723 }
4724 #endif /* LDAPMAP */
4725 /*
4726 **  PH map
4727 */
4728
4729 #if PH_MAP
4730
4731 /*
4732 **  Support for the CCSO Nameserver (ph/qi).
4733 **  This code is intended to replace the so-called "ph mailer".
4734 **  Contributed by Mark D. Roth.  Contact him for support.
4735 */
4736
4737 /* what version of the ph map code we're running */
4738 static char phmap_id[128];
4739
4740 /* sendmail version for phmap id string */
4741 extern const char Version[];
4742
4743 /* assume we're using nph-1.2.x if not specified */
4744 # ifndef NPH_VERSION
4745 #  define NPH_VERSION           10200
4746 # endif
4747
4748 /* compatibility for versions older than nph-1.2.0 */
4749 # if NPH_VERSION < 10200
4750 #  define PH_OPEN_ROUNDROBIN    PH_ROUNDROBIN
4751 #  define PH_OPEN_DONTID        PH_DONTID
4752 #  define PH_CLOSE_FAST         PH_FASTCLOSE
4753 #  define PH_ERR_DATAERR        PH_DATAERR
4754 #  define PH_ERR_NOMATCH        PH_NOMATCH
4755 # endif /* NPH_VERSION < 10200 */
4756
4757 /*
4758 **  PH_MAP_PARSEARGS -- parse ph map definition args.
4759 */
4760
4761 bool
4762 ph_map_parseargs(map, args)
4763         MAP *map;
4764         char *args;
4765 {
4766         register bool done;
4767         register char *p = args;
4768         PH_MAP_STRUCT *pmap = NULL;
4769
4770         /* initialize version string */
4771         (void) sm_snprintf(phmap_id, sizeof(phmap_id),
4772                            "sendmail-%s phmap-20010529 libphclient-%s",
4773                            Version, libphclient_version);
4774
4775         pmap = (PH_MAP_STRUCT *) xalloc(sizeof(*pmap));
4776
4777         /* defaults */
4778         pmap->ph_servers = NULL;
4779         pmap->ph_field_list = NULL;
4780         pmap->ph = NULL;
4781         pmap->ph_timeout = 0;
4782         pmap->ph_fastclose = 0;
4783
4784         map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL;
4785         for (;;)
4786         {
4787                 while (isascii(*p) && isspace(*p))
4788                         p++;
4789                 if (*p != '-')
4790                         break;
4791                 switch (*++p)
4792                 {
4793                   case 'N':
4794                         map->map_mflags |= MF_INCLNULL;
4795                         map->map_mflags &= ~MF_TRY0NULL;
4796                         break;
4797
4798                   case 'O':
4799                         map->map_mflags &= ~MF_TRY1NULL;
4800                         break;
4801
4802                   case 'o':
4803                         map->map_mflags |= MF_OPTIONAL;
4804                         break;
4805
4806                   case 'f':
4807                         map->map_mflags |= MF_NOFOLDCASE;
4808                         break;
4809
4810                   case 'm':
4811                         map->map_mflags |= MF_MATCHONLY;
4812                         break;
4813
4814                   case 'A':
4815                         map->map_mflags |= MF_APPEND;
4816                         break;
4817
4818                   case 'q':
4819                         map->map_mflags |= MF_KEEPQUOTES;
4820                         break;
4821
4822                   case 't':
4823                         map->map_mflags |= MF_NODEFER;
4824                         break;
4825
4826                   case 'a':
4827                         map->map_app = ++p;
4828                         break;
4829
4830                   case 'T':
4831                         map->map_tapp = ++p;
4832                         break;
4833
4834                   case 'l':
4835                         while (isascii(*++p) && isspace(*p))
4836                                 continue;
4837                         pmap->ph_timeout = atoi(p);
4838                         break;
4839
4840                   case 'S':
4841                         map->map_spacesub = *++p;
4842                         break;
4843
4844                   case 'D':
4845                         map->map_mflags |= MF_DEFER;
4846                         break;
4847
4848                   case 'h':             /* PH server list */
4849                         while (isascii(*++p) && isspace(*p))
4850                                 continue;
4851                         pmap->ph_servers = p;
4852                         break;
4853
4854                   case 'k':             /* fields to search for */
4855                         while (isascii(*++p) && isspace(*p))
4856                                 continue;
4857                         pmap->ph_field_list = p;
4858                         break;
4859
4860                   default:
4861                         syserr("ph_map_parseargs: unknown option -%c", *p);
4862                 }
4863
4864                 /* try to account for quoted strings */
4865                 done = isascii(*p) && isspace(*p);
4866                 while (*p != '\0' && !done)
4867                 {
4868                         if (*p == '"')
4869                         {
4870                                 while (*++p != '"' && *p != '\0')
4871                                         continue;
4872                                 if (*p != '\0')
4873                                         p++;
4874                         }
4875                         else
4876                                 p++;
4877                         done = isascii(*p) && isspace(*p);
4878                 }
4879
4880                 if (*p != '\0')
4881                         *p++ = '\0';
4882         }
4883
4884         if (map->map_app != NULL)
4885                 map->map_app = newstr(ph_map_dequote(map->map_app));
4886         if (map->map_tapp != NULL)
4887                 map->map_tapp = newstr(ph_map_dequote(map->map_tapp));
4888
4889         if (pmap->ph_field_list != NULL)
4890                 pmap->ph_field_list = newstr(ph_map_dequote(pmap->ph_field_list));
4891
4892         if (pmap->ph_servers != NULL)
4893                 pmap->ph_servers = newstr(ph_map_dequote(pmap->ph_servers));
4894         else
4895         {
4896                 syserr("ph_map_parseargs: -h flag is required");
4897                 return false;
4898         }
4899
4900         map->map_db1 = (ARBPTR_T) pmap;
4901         return true;
4902 }
4903
4904 /*
4905 **  PH_MAP_CLOSE -- close the connection to the ph server
4906 */
4907
4908 void
4909 ph_map_close(map)
4910         MAP *map;
4911 {
4912         PH_MAP_STRUCT *pmap;
4913
4914         pmap = (PH_MAP_STRUCT *)map->map_db1;
4915         if (tTd(38, 9))
4916                 sm_dprintf("ph_map_close(%s): pmap->ph_fastclose=%d\n",
4917                            map->map_mname, pmap->ph_fastclose);
4918
4919
4920         if (pmap->ph != NULL)
4921         {
4922                 ph_set_sendhook(pmap->ph, NULL);
4923                 ph_set_recvhook(pmap->ph, NULL);
4924                 ph_close(pmap->ph, pmap->ph_fastclose);
4925         }
4926
4927         map->map_mflags &= ~(MF_OPEN|MF_WRITABLE);
4928 }
4929
4930 static jmp_buf  PHTimeout;
4931
4932 /* ARGSUSED */
4933 static void
4934 ph_timeout(unused)
4935         int unused;
4936 {
4937         /*
4938         **  NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
4939         **      ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
4940         **      DOING.
4941         */
4942
4943         errno = ETIMEDOUT;
4944         longjmp(PHTimeout, 1);
4945 }
4946
4947 static void
4948 #if NPH_VERSION >= 10200
4949 ph_map_send_debug(appdata, text)
4950         void *appdata;
4951 #else
4952 ph_map_send_debug(text)
4953 #endif
4954         char *text;
4955 {
4956         if (LogLevel > 9)
4957                 sm_syslog(LOG_NOTICE, CurEnv->e_id,
4958                           "ph_map_send_debug: ==> %s", text);
4959         if (tTd(38, 20))
4960                 sm_dprintf("ph_map_send_debug: ==> %s\n", text);
4961 }
4962
4963 static void
4964 #if NPH_VERSION >= 10200
4965 ph_map_recv_debug(appdata, text)
4966         void *appdata;
4967 #else
4968 ph_map_recv_debug(text)
4969 #endif
4970         char *text;
4971 {
4972         if (LogLevel > 10)
4973                 sm_syslog(LOG_NOTICE, CurEnv->e_id,
4974                           "ph_map_recv_debug: <== %s", text);
4975         if (tTd(38, 21))
4976                 sm_dprintf("ph_map_recv_debug: <== %s\n", text);
4977 }
4978
4979 /*
4980 **  PH_MAP_OPEN -- sub for opening PH map
4981 */
4982 bool
4983 ph_map_open(map, mode)
4984         MAP *map;
4985         int mode;
4986 {
4987         PH_MAP_STRUCT *pmap;
4988         register SM_EVENT *ev = NULL;
4989         int save_errno = 0;
4990         char *hostlist, *host;
4991
4992         if (tTd(38, 2))
4993                 sm_dprintf("ph_map_open(%s)\n", map->map_mname);
4994
4995         mode &= O_ACCMODE;
4996         if (mode != O_RDONLY)
4997         {
4998                 /* issue a pseudo-error message */
4999                 errno = SM_EMAPCANTWRITE;
5000                 return false;
5001         }
5002
5003         if (CurEnv != NULL && CurEnv->e_sendmode == SM_DEFER &&
5004             bitset(MF_DEFER, map->map_mflags))
5005         {
5006                 if (tTd(9, 1))
5007                         sm_dprintf("ph_map_open(%s) => DEFERRED\n",
5008                                    map->map_mname);
5009
5010                 /*
5011                 **  Unset MF_DEFER here so that map_lookup() returns
5012                 **  a temporary failure using the bogus map and
5013                 **  map->map_tapp instead of the default permanent error.
5014                 */
5015
5016                 map->map_mflags &= ~MF_DEFER;
5017                 return false;
5018         }
5019
5020         pmap = (PH_MAP_STRUCT *)map->map_db1;
5021         pmap->ph_fastclose = 0;         /* refresh field for reopen */
5022
5023         /* try each host in the list */
5024         hostlist = newstr(pmap->ph_servers);
5025         for (host = strtok(hostlist, " ");
5026              host != NULL;
5027              host = strtok(NULL, " "))
5028         {
5029                 /* set timeout */
5030                 if (pmap->ph_timeout != 0)
5031                 {
5032                         if (setjmp(PHTimeout) != 0)
5033                         {
5034                                 ev = NULL;
5035                                 if (LogLevel > 1)
5036                                         sm_syslog(LOG_NOTICE, CurEnv->e_id,
5037                                                   "timeout connecting to PH server %.100s",
5038                                                   host);
5039                                 errno = ETIMEDOUT;
5040                                 goto ph_map_open_abort;
5041                         }
5042                         ev = sm_setevent(pmap->ph_timeout, ph_timeout, 0);
5043                 }
5044
5045                 /* open connection to server */
5046                 if (ph_open(&(pmap->ph), host,
5047                             PH_OPEN_ROUNDROBIN|PH_OPEN_DONTID,
5048                             ph_map_send_debug, ph_map_recv_debug
5049 #if NPH_VERSION >= 10200
5050                             , NULL
5051 #endif
5052                             ) == 0
5053                     && ph_id(pmap->ph, phmap_id) == 0)
5054                 {
5055                         if (ev != NULL)
5056                                 sm_clrevent(ev);
5057                         sm_free(hostlist); /* XXX */
5058                         return true;
5059                 }
5060
5061   ph_map_open_abort:
5062                 save_errno = errno;
5063                 if (ev != NULL)
5064                         sm_clrevent(ev);
5065                 pmap->ph_fastclose = PH_CLOSE_FAST;
5066                 ph_map_close(map);
5067                 errno = save_errno;
5068         }
5069
5070         if (bitset(MF_NODEFER, map->map_mflags))
5071         {
5072                 if (errno == 0)
5073                         errno = EAGAIN;
5074                 syserr("ph_map_open: %s: cannot connect to PH server",
5075                        map->map_mname);
5076         }
5077         else if (!bitset(MF_OPTIONAL, map->map_mflags) && LogLevel > 1)
5078                 sm_syslog(LOG_NOTICE, CurEnv->e_id,
5079                           "ph_map_open: %s: cannot connect to PH server",
5080                           map->map_mname);
5081         sm_free(hostlist); /* XXX */
5082         return false;
5083 }
5084
5085 /*
5086 **  PH_MAP_LOOKUP -- look up key from ph server
5087 */
5088
5089 char *
5090 ph_map_lookup(map, key, args, pstat)
5091         MAP *map;
5092         char *key;
5093         char **args;
5094         int *pstat;
5095 {
5096         int i, save_errno = 0;
5097         register SM_EVENT *ev = NULL;
5098         PH_MAP_STRUCT *pmap;
5099         char *value = NULL;
5100
5101         pmap = (PH_MAP_STRUCT *)map->map_db1;
5102
5103         *pstat = EX_OK;
5104
5105         /* set timeout */
5106         if (pmap->ph_timeout != 0)
5107         {
5108                 if (setjmp(PHTimeout) != 0)
5109                 {
5110                         ev = NULL;
5111                         if (LogLevel > 1)
5112                                 sm_syslog(LOG_NOTICE, CurEnv->e_id,
5113                                           "timeout during PH lookup of %.100s",
5114                                           key);
5115                         errno = ETIMEDOUT;
5116                         *pstat = EX_TEMPFAIL;
5117                         goto ph_map_lookup_abort;
5118                 }
5119                 ev = sm_setevent(pmap->ph_timeout, ph_timeout, 0);
5120         }
5121
5122         /* perform lookup */
5123         i = ph_email_resolve(pmap->ph, key, pmap->ph_field_list, &value);
5124         if (i == -1)
5125                 *pstat = EX_TEMPFAIL;
5126         else if (i == PH_ERR_NOMATCH || i == PH_ERR_DATAERR)
5127                 *pstat = EX_UNAVAILABLE;
5128
5129   ph_map_lookup_abort:
5130         if (ev != NULL)
5131                 sm_clrevent(ev);
5132
5133         /*
5134         **  Close the connection if the timer popped
5135         **  or we got a temporary PH error
5136         */
5137
5138         if (*pstat == EX_TEMPFAIL)
5139         {
5140                 save_errno = errno;
5141                 pmap->ph_fastclose = PH_CLOSE_FAST;
5142                 ph_map_close(map);
5143                 errno = save_errno;
5144         }
5145
5146         if (*pstat == EX_OK)
5147         {
5148                 if (tTd(38,20))
5149                         sm_dprintf("ph_map_lookup: %s => %s\n", key, value);
5150
5151                 if (bitset(MF_MATCHONLY, map->map_mflags))
5152                         return map_rewrite(map, key, strlen(key), NULL);
5153                 else
5154                         return map_rewrite(map, value, strlen(value), args);
5155         }
5156
5157         return NULL;
5158 }
5159 #endif /* PH_MAP */
5160
5161 /*
5162 **  syslog map
5163 */
5164
5165 #define map_prio        map_lockfd      /* overload field */
5166
5167 /*
5168 **  SYSLOG_MAP_PARSEARGS -- check for priority level to syslog messages.
5169 */
5170
5171 bool
5172 syslog_map_parseargs(map, args)
5173         MAP *map;
5174         char *args;
5175 {
5176         char *p = args;
5177         char *priority = NULL;
5178
5179         /* there is no check whether there is really an argument */
5180         while (*p != '\0')
5181         {
5182                 while (isascii(*p) && isspace(*p))
5183                         p++;
5184                 if (*p != '-')
5185                         break;
5186                 ++p;
5187                 if (*p == 'D')
5188                 {
5189                         map->map_mflags |= MF_DEFER;
5190                         ++p;
5191                 }
5192                 else if (*p == 'S')
5193                 {
5194                         map->map_spacesub = *++p;
5195                         if (*p != '\0')
5196                                 p++;
5197                 }
5198                 else if (*p == 'L')
5199                 {
5200                         while (*++p != '\0' && isascii(*p) && isspace(*p))
5201                                 continue;
5202                         if (*p == '\0')
5203                                 break;
5204                         priority = p;
5205                         while (*p != '\0' && !(isascii(*p) && isspace(*p)))
5206                                 p++;
5207                         if (*p != '\0')
5208                                 *p++ = '\0';
5209                 }
5210                 else
5211                 {
5212                         syserr("Illegal option %c map syslog", *p);
5213                         ++p;
5214                 }
5215         }
5216
5217         if (priority == NULL)
5218                 map->map_prio = LOG_INFO;
5219         else
5220         {
5221                 if (sm_strncasecmp("LOG_", priority, 4) == 0)
5222                         priority += 4;
5223
5224 #ifdef LOG_EMERG
5225                 if (sm_strcasecmp("EMERG", priority) == 0)
5226                         map->map_prio = LOG_EMERG;
5227                 else
5228 #endif /* LOG_EMERG */
5229 #ifdef LOG_ALERT
5230                 if (sm_strcasecmp("ALERT", priority) == 0)
5231                         map->map_prio = LOG_ALERT;
5232                 else
5233 #endif /* LOG_ALERT */
5234 #ifdef LOG_CRIT
5235                 if (sm_strcasecmp("CRIT", priority) == 0)
5236                         map->map_prio = LOG_CRIT;
5237                 else
5238 #endif /* LOG_CRIT */
5239 #ifdef LOG_ERR
5240                 if (sm_strcasecmp("ERR", priority) == 0)
5241                         map->map_prio = LOG_ERR;
5242                 else
5243 #endif /* LOG_ERR */
5244 #ifdef LOG_WARNING
5245                 if (sm_strcasecmp("WARNING", priority) == 0)
5246                         map->map_prio = LOG_WARNING;
5247                 else
5248 #endif /* LOG_WARNING */
5249 #ifdef LOG_NOTICE
5250                 if (sm_strcasecmp("NOTICE", priority) == 0)
5251                         map->map_prio = LOG_NOTICE;
5252                 else
5253 #endif /* LOG_NOTICE */
5254 #ifdef LOG_INFO
5255                 if (sm_strcasecmp("INFO", priority) == 0)
5256                         map->map_prio = LOG_INFO;
5257                 else
5258 #endif /* LOG_INFO */
5259 #ifdef LOG_DEBUG
5260                 if (sm_strcasecmp("DEBUG", priority) == 0)
5261                         map->map_prio = LOG_DEBUG;
5262                 else
5263 #endif /* LOG_DEBUG */
5264                 {
5265                         syserr("syslog_map_parseargs: Unknown priority %s",
5266                                priority);
5267                         return false;
5268                 }
5269         }
5270         return true;
5271 }
5272
5273 /*
5274 **  SYSLOG_MAP_LOOKUP -- rewrite and syslog message.  Always return empty string
5275 */
5276
5277 char *
5278 syslog_map_lookup(map, string, args, statp)
5279         MAP *map;
5280         char *string;
5281         char **args;
5282         int *statp;
5283 {
5284         char *ptr = map_rewrite(map, string, strlen(string), args);
5285
5286         if (ptr != NULL)
5287         {
5288                 if (tTd(38, 20))
5289                         sm_dprintf("syslog_map_lookup(%s (priority %d): %s\n",
5290                                 map->map_mname, map->map_prio, ptr);
5291
5292                 sm_syslog(map->map_prio, CurEnv->e_id, "%s", ptr);
5293         }
5294
5295         *statp = EX_OK;
5296         return "";
5297 }
5298
5299 #if _FFR_DPRINTF_MAP
5300 /*
5301 **  dprintf map
5302 */
5303
5304 #define map_dbg_level   map_lockfd      /* overload field */
5305
5306 /*
5307 **  DPRINTF_MAP_PARSEARGS -- check for priority level to dprintf messages.
5308 */
5309
5310 bool
5311 dprintf_map_parseargs(map, args)
5312         MAP *map;
5313         char *args;
5314 {
5315         char *p = args;
5316         char *dbg_level = NULL;
5317
5318         /* there is no check whether there is really an argument */
5319         while (*p != '\0')
5320         {
5321                 while (isascii(*p) && isspace(*p))
5322                         p++;
5323                 if (*p != '-')
5324                         break;
5325                 ++p;
5326                 if (*p == 'D')
5327                 {
5328                         map->map_mflags |= MF_DEFER;
5329                         ++p;
5330                 }
5331                 else if (*p == 'S')
5332                 {
5333                         map->map_spacesub = *++p;
5334                         if (*p != '\0')
5335                                 p++;
5336                 }
5337                 else if (*p == 'd')
5338                 {
5339                         while (*++p != '\0' && isascii(*p) && isspace(*p))
5340                                 continue;
5341                         if (*p == '\0')
5342                                 break;
5343                         dbg_level = p;
5344                         while (*p != '\0' && !(isascii(*p) && isspace(*p)))
5345                                 p++;
5346                         if (*p != '\0')
5347                                 *p++ = '\0';
5348                 }
5349                 else
5350                 {
5351                         syserr("Illegal option %c map dprintf", *p);
5352                         ++p;
5353                 }
5354         }
5355
5356         if (dbg_level == NULL)
5357                 map->map_dbg_level = 0;
5358         else
5359         {
5360                 if (!(isascii(*dbg_level) && isdigit(*dbg_level)))
5361                 {
5362                         syserr("dprintf map \"%s\", file %s: -d should specify a number, not %s",
5363                                 map->map_mname, map->map_file,
5364                                 dbg_level);
5365                         return false;
5366                 }
5367                 map->map_dbg_level = atoi(dbg_level);
5368         }
5369         return true;
5370 }
5371
5372 /*
5373 **  DPRINTF_MAP_LOOKUP -- rewrite and print message.  Always return empty string
5374 */
5375
5376 char *
5377 dprintf_map_lookup(map, string, args, statp)
5378         MAP *map;
5379         char *string;
5380         char **args;
5381         int *statp;
5382 {
5383         char *ptr = map_rewrite(map, string, strlen(string), args);
5384
5385         if (ptr != NULL && tTd(85, map->map_dbg_level))
5386                 sm_dprintf("%s\n", ptr);
5387         *statp = EX_OK;
5388         return "";
5389 }
5390 #endif /* _FFR_DPRINTF_MAP */
5391
5392 /*
5393 **  HESIOD Modules
5394 */
5395
5396 #if HESIOD
5397
5398 bool
5399 hes_map_open(map, mode)
5400         MAP *map;
5401         int mode;
5402 {
5403         if (tTd(38, 2))
5404                 sm_dprintf("hes_map_open(%s, %s, %d)\n",
5405                         map->map_mname, map->map_file, mode);
5406
5407         if (mode != O_RDONLY)
5408         {
5409                 /* issue a pseudo-error message */
5410                 errno = SM_EMAPCANTWRITE;
5411                 return false;
5412         }
5413
5414 # ifdef HESIOD_INIT
5415         if (HesiodContext != NULL || hesiod_init(&HesiodContext) == 0)
5416                 return true;
5417
5418         if (!bitset(MF_OPTIONAL, map->map_mflags))
5419                 syserr("451 4.3.5 cannot initialize Hesiod map (%s)",
5420                         sm_errstring(errno));
5421         return false;
5422 # else /* HESIOD_INIT */
5423         if (hes_error() == HES_ER_UNINIT)
5424                 hes_init();
5425         switch (hes_error())
5426         {
5427           case HES_ER_OK:
5428           case HES_ER_NOTFOUND:
5429                 return true;
5430         }
5431
5432         if (!bitset(MF_OPTIONAL, map->map_mflags))
5433                 syserr("451 4.3.5 cannot initialize Hesiod map (%d)", hes_error());
5434
5435         return false;
5436 # endif /* HESIOD_INIT */
5437 }
5438
5439 char *
5440 hes_map_lookup(map, name, av, statp)
5441         MAP *map;
5442         char *name;
5443         char **av;
5444         int *statp;
5445 {
5446         char **hp;
5447
5448         if (tTd(38, 20))
5449                 sm_dprintf("hes_map_lookup(%s, %s)\n", map->map_file, name);
5450
5451         if (name[0] == '\\')
5452         {
5453                 char *np;
5454                 int nl;
5455                 int save_errno;
5456                 char nbuf[MAXNAME];
5457
5458                 nl = strlen(name);
5459                 if (nl < sizeof(nbuf) - 1)
5460                         np = nbuf;
5461                 else
5462                         np = xalloc(strlen(name) + 2);
5463                 np[0] = '\\';
5464                 (void) sm_strlcpy(&np[1], name, (sizeof(nbuf)) - 1);
5465 # ifdef HESIOD_INIT
5466                 hp = hesiod_resolve(HesiodContext, np, map->map_file);
5467 # else /* HESIOD_INIT */
5468                 hp = hes_resolve(np, map->map_file);
5469 # endif /* HESIOD_INIT */
5470                 save_errno = errno;
5471                 if (np != nbuf)
5472                         sm_free(np); /* XXX */
5473                 errno = save_errno;
5474         }
5475         else
5476         {
5477 # ifdef HESIOD_INIT
5478                 hp = hesiod_resolve(HesiodContext, name, map->map_file);
5479 # else /* HESIOD_INIT */
5480                 hp = hes_resolve(name, map->map_file);
5481 # endif /* HESIOD_INIT */
5482         }
5483 # ifdef HESIOD_INIT
5484         if (hp == NULL || *hp == NULL)
5485         {
5486                 switch (errno)
5487                 {
5488                   case ENOENT:
5489                           *statp = EX_NOTFOUND;
5490                           break;
5491                   case ECONNREFUSED:
5492                           *statp = EX_TEMPFAIL;
5493                           break;
5494                   case EMSGSIZE:
5495                   case ENOMEM:
5496                   default:
5497                           *statp = EX_UNAVAILABLE;
5498                           break;
5499                 }
5500                 if (hp != NULL)
5501                         hesiod_free_list(HesiodContext, hp);
5502                 return NULL;
5503         }
5504 # else /* HESIOD_INIT */
5505         if (hp == NULL || hp[0] == NULL)
5506         {
5507                 switch (hes_error())
5508                 {
5509                   case HES_ER_OK:
5510                         *statp = EX_OK;
5511                         break;
5512
5513                   case HES_ER_NOTFOUND:
5514                         *statp = EX_NOTFOUND;
5515                         break;
5516
5517                   case HES_ER_CONFIG:
5518                         *statp = EX_UNAVAILABLE;
5519                         break;
5520
5521                   case HES_ER_NET:
5522                         *statp = EX_TEMPFAIL;
5523                         break;
5524                 }
5525                 return NULL;
5526         }
5527 # endif /* HESIOD_INIT */
5528
5529         if (bitset(MF_MATCHONLY, map->map_mflags))
5530                 return map_rewrite(map, name, strlen(name), NULL);
5531         else
5532                 return map_rewrite(map, hp[0], strlen(hp[0]), av);
5533 }
5534
5535 /*
5536 **  HES_MAP_CLOSE -- free the Hesiod context
5537 */
5538
5539 void
5540 hes_map_close(map)
5541         MAP *map;
5542 {
5543         if (tTd(38, 20))
5544                 sm_dprintf("hes_map_close(%s)\n", map->map_file);
5545
5546 # ifdef HESIOD_INIT
5547         /* Free the hesiod context */
5548         if (HesiodContext != NULL)
5549         {
5550                 hesiod_end(HesiodContext);
5551                 HesiodContext = NULL;
5552         }
5553 # endif /* HESIOD_INIT */
5554 }
5555
5556 #endif /* HESIOD */
5557 /*
5558 **  NeXT NETINFO Modules
5559 */
5560
5561 #if NETINFO
5562
5563 # define NETINFO_DEFAULT_DIR            "/aliases"
5564 # define NETINFO_DEFAULT_PROPERTY       "members"
5565
5566 /*
5567 **  NI_MAP_OPEN -- open NetInfo Aliases
5568 */
5569
5570 bool
5571 ni_map_open(map, mode)
5572         MAP *map;
5573         int mode;
5574 {
5575         if (tTd(38, 2))
5576                 sm_dprintf("ni_map_open(%s, %s, %d)\n",
5577                         map->map_mname, map->map_file, mode);
5578         mode &= O_ACCMODE;
5579
5580         if (*map->map_file == '\0')
5581                 map->map_file = NETINFO_DEFAULT_DIR;
5582
5583         if (map->map_valcolnm == NULL)
5584                 map->map_valcolnm = NETINFO_DEFAULT_PROPERTY;
5585
5586         if (map->map_coldelim == '\0')
5587         {
5588                 if (bitset(MF_ALIAS, map->map_mflags))
5589                         map->map_coldelim = ',';
5590                 else if (bitset(MF_FILECLASS, map->map_mflags))
5591                         map->map_coldelim = ' ';
5592         }
5593         return true;
5594 }
5595
5596
5597 /*
5598 **  NI_MAP_LOOKUP -- look up a datum in NetInfo
5599 */
5600
5601 char *
5602 ni_map_lookup(map, name, av, statp)
5603         MAP *map;
5604         char *name;
5605         char **av;
5606         int *statp;
5607 {
5608         char *res;
5609         char *propval;
5610
5611         if (tTd(38, 20))
5612                 sm_dprintf("ni_map_lookup(%s, %s)\n", map->map_mname, name);
5613
5614         propval = ni_propval(map->map_file, map->map_keycolnm, name,
5615                              map->map_valcolnm, map->map_coldelim);
5616
5617         if (propval == NULL)
5618                 return NULL;
5619
5620         SM_TRY
5621                 if (bitset(MF_MATCHONLY, map->map_mflags))
5622                         res = map_rewrite(map, name, strlen(name), NULL);
5623                 else
5624                         res = map_rewrite(map, propval, strlen(propval), av);
5625         SM_FINALLY
5626                 sm_free(propval);
5627         SM_END_TRY
5628         return res;
5629 }
5630
5631
5632 static bool
5633 ni_getcanonname(name, hbsize, statp)
5634         char *name;
5635         int hbsize;
5636         int *statp;
5637 {
5638         char *vptr;
5639         char *ptr;
5640         char nbuf[MAXNAME + 1];
5641
5642         if (tTd(38, 20))
5643                 sm_dprintf("ni_getcanonname(%s)\n", name);
5644
5645         if (sm_strlcpy(nbuf, name, sizeof(nbuf)) >= sizeof(nbuf))
5646         {
5647                 *statp = EX_UNAVAILABLE;
5648                 return false;
5649         }
5650         (void) shorten_hostname(nbuf);
5651
5652         /* we only accept single token search key */
5653         if (strchr(nbuf, '.'))
5654         {
5655                 *statp = EX_NOHOST;
5656                 return false;
5657         }
5658
5659         /* Do the search */
5660         vptr = ni_propval("/machines", NULL, nbuf, "name", '\n');
5661
5662         if (vptr == NULL)
5663         {
5664                 *statp = EX_NOHOST;
5665                 return false;
5666         }
5667
5668         /* Only want the first machine name */
5669         if ((ptr = strchr(vptr, '\n')) != NULL)
5670                 *ptr = '\0';
5671
5672         if (sm_strlcpy(name, vptr, hbsize) >= hbsize)
5673         {
5674                 sm_free(vptr);
5675                 *statp = EX_UNAVAILABLE;
5676                 return true;
5677         }
5678         sm_free(vptr);
5679         *statp = EX_OK;
5680         return false;
5681 }
5682 #endif /* NETINFO */
5683 /*
5684 **  TEXT (unindexed text file) Modules
5685 **
5686 **      This code donated by Sun Microsystems.
5687 */
5688
5689 #define map_sff         map_lockfd      /* overload field */
5690
5691
5692 /*
5693 **  TEXT_MAP_OPEN -- open text table
5694 */
5695
5696 bool
5697 text_map_open(map, mode)
5698         MAP *map;
5699         int mode;
5700 {
5701         long sff;
5702         int i;
5703
5704         if (tTd(38, 2))
5705                 sm_dprintf("text_map_open(%s, %s, %d)\n",
5706                         map->map_mname, map->map_file, mode);
5707
5708         mode &= O_ACCMODE;
5709         if (mode != O_RDONLY)
5710         {
5711                 errno = EPERM;
5712                 return false;
5713         }
5714
5715         if (*map->map_file == '\0')
5716         {
5717                 syserr("text map \"%s\": file name required",
5718                         map->map_mname);
5719                 return false;
5720         }
5721
5722         if (map->map_file[0] != '/')
5723         {
5724                 syserr("text map \"%s\": file name must be fully qualified",
5725                         map->map_mname);
5726                 return false;
5727         }
5728
5729         sff = SFF_ROOTOK|SFF_REGONLY;
5730         if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
5731                 sff |= SFF_NOWLINK;
5732         if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail))
5733                 sff |= SFF_SAFEDIRPATH;
5734         if ((i = safefile(map->map_file, RunAsUid, RunAsGid, RunAsUserName,
5735                           sff, S_IRUSR, NULL)) != 0)
5736         {
5737                 int save_errno = errno;
5738
5739                 /* cannot open this map */
5740                 if (tTd(38, 2))
5741                         sm_dprintf("\tunsafe map file: %d\n", i);
5742                 errno = save_errno;
5743                 if (!bitset(MF_OPTIONAL, map->map_mflags))
5744                         syserr("text map \"%s\": unsafe map file %s",
5745                                 map->map_mname, map->map_file);
5746                 return false;
5747         }
5748
5749         if (map->map_keycolnm == NULL)
5750                 map->map_keycolno = 0;
5751         else
5752         {
5753                 if (!(isascii(*map->map_keycolnm) && isdigit(*map->map_keycolnm)))
5754                 {
5755                         syserr("text map \"%s\", file %s: -k should specify a number, not %s",
5756                                 map->map_mname, map->map_file,
5757                                 map->map_keycolnm);
5758                         return false;
5759                 }
5760                 map->map_keycolno = atoi(map->map_keycolnm);
5761         }
5762
5763         if (map->map_valcolnm == NULL)
5764                 map->map_valcolno = 0;
5765         else
5766         {
5767                 if (!(isascii(*map->map_valcolnm) && isdigit(*map->map_valcolnm)))
5768                 {
5769                         syserr("text map \"%s\", file %s: -v should specify a number, not %s",
5770                                         map->map_mname, map->map_file,
5771                                         map->map_valcolnm);
5772                         return false;
5773                 }
5774                 map->map_valcolno = atoi(map->map_valcolnm);
5775         }
5776
5777         if (tTd(38, 2))
5778         {
5779                 sm_dprintf("text_map_open(%s, %s): delimiter = ",
5780                         map->map_mname, map->map_file);
5781                 if (map->map_coldelim == '\0')
5782                         sm_dprintf("(white space)\n");
5783                 else
5784                         sm_dprintf("%c\n", map->map_coldelim);
5785         }
5786
5787         map->map_sff = sff;
5788         return true;
5789 }
5790
5791
5792 /*
5793 **  TEXT_MAP_LOOKUP -- look up a datum in a TEXT table
5794 */
5795
5796 char *
5797 text_map_lookup(map, name, av, statp)
5798         MAP *map;
5799         char *name;
5800         char **av;
5801         int *statp;
5802 {
5803         char *vp;
5804         auto int vsize;
5805         int buflen;
5806         SM_FILE_T *f;
5807         char delim;
5808         int key_idx;
5809         bool found_it;
5810         long sff = map->map_sff;
5811         char search_key[MAXNAME + 1];
5812         char linebuf[MAXLINE];
5813         char buf[MAXNAME + 1];
5814
5815         found_it = false;
5816         if (tTd(38, 20))
5817                 sm_dprintf("text_map_lookup(%s, %s)\n", map->map_mname,  name);
5818
5819         buflen = strlen(name);
5820         if (buflen > sizeof(search_key) - 1)
5821                 buflen = sizeof(search_key) - 1;        /* XXX just cut if off? */
5822         memmove(search_key, name, buflen);
5823         search_key[buflen] = '\0';
5824         if (!bitset(MF_NOFOLDCASE, map->map_mflags))
5825                 makelower(search_key);
5826
5827         f = safefopen(map->map_file, O_RDONLY, FileMode, sff);
5828         if (f == NULL)
5829         {
5830                 map->map_mflags &= ~(MF_VALID|MF_OPEN);
5831                 *statp = EX_UNAVAILABLE;
5832                 return NULL;
5833         }
5834         key_idx = map->map_keycolno;
5835         delim = map->map_coldelim;
5836         while (sm_io_fgets(f, SM_TIME_DEFAULT,
5837                            linebuf, sizeof(linebuf)) != NULL)
5838         {
5839                 char *p;
5840
5841                 /* skip comment line */
5842                 if (linebuf[0] == '#')
5843                         continue;
5844                 p = strchr(linebuf, '\n');
5845                 if (p != NULL)
5846                         *p = '\0';
5847                 p = get_column(linebuf, key_idx, delim, buf, sizeof(buf));
5848                 if (p != NULL && sm_strcasecmp(search_key, p) == 0)
5849                 {
5850                         found_it = true;
5851                         break;
5852                 }
5853         }
5854         (void) sm_io_close(f, SM_TIME_DEFAULT);
5855         if (!found_it)
5856         {
5857                 *statp = EX_NOTFOUND;
5858                 return NULL;
5859         }
5860         vp = get_column(linebuf, map->map_valcolno, delim, buf, sizeof(buf));
5861         if (vp == NULL)
5862         {
5863                 *statp = EX_NOTFOUND;
5864                 return NULL;
5865         }
5866         vsize = strlen(vp);
5867         *statp = EX_OK;
5868         if (bitset(MF_MATCHONLY, map->map_mflags))
5869                 return map_rewrite(map, name, strlen(name), NULL);
5870         else
5871                 return map_rewrite(map, vp, vsize, av);
5872 }
5873
5874 /*
5875 **  TEXT_GETCANONNAME -- look up canonical name in hosts file
5876 */
5877
5878 static bool
5879 text_getcanonname(name, hbsize, statp)
5880         char *name;
5881         int hbsize;
5882         int *statp;
5883 {
5884         bool found;
5885         char *dot;
5886         SM_FILE_T *f;
5887         char linebuf[MAXLINE];
5888         char cbuf[MAXNAME + 1];
5889         char nbuf[MAXNAME + 1];
5890
5891         if (tTd(38, 20))
5892                 sm_dprintf("text_getcanonname(%s)\n", name);
5893
5894         if (sm_strlcpy(nbuf, name, sizeof(nbuf)) >= sizeof(nbuf))
5895         {
5896                 *statp = EX_UNAVAILABLE;
5897                 return false;
5898         }
5899         dot = shorten_hostname(nbuf);
5900
5901         f = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, HostsFile, SM_IO_RDONLY,
5902                        NULL);
5903         if (f == NULL)
5904         {
5905                 *statp = EX_UNAVAILABLE;
5906                 return false;
5907         }
5908         found = false;
5909         while (!found &&
5910                 sm_io_fgets(f, SM_TIME_DEFAULT,
5911                             linebuf, sizeof(linebuf)) != NULL)
5912         {
5913                 char *p = strpbrk(linebuf, "#\n");
5914
5915                 if (p != NULL)
5916                         *p = '\0';
5917                 if (linebuf[0] != '\0')
5918                         found = extract_canonname(nbuf, dot, linebuf,
5919                                                   cbuf, sizeof(cbuf));
5920         }
5921         (void) sm_io_close(f, SM_TIME_DEFAULT);
5922         if (!found)
5923         {
5924                 *statp = EX_NOHOST;
5925                 return false;
5926         }
5927
5928         if (sm_strlcpy(name, cbuf, hbsize) >= hbsize)
5929         {
5930                 *statp = EX_UNAVAILABLE;
5931                 return false;
5932         }
5933         *statp = EX_OK;
5934         return true;
5935 }
5936 /*
5937 **  STAB (Symbol Table) Modules
5938 */
5939
5940
5941 /*
5942 **  STAB_MAP_LOOKUP -- look up alias in symbol table
5943 */
5944
5945 /* ARGSUSED2 */
5946 char *
5947 stab_map_lookup(map, name, av, pstat)
5948         register MAP *map;
5949         char *name;
5950         char **av;
5951         int *pstat;
5952 {
5953         register STAB *s;
5954
5955         if (tTd(38, 20))
5956                 sm_dprintf("stab_lookup(%s, %s)\n",
5957                         map->map_mname, name);
5958
5959         s = stab(name, ST_ALIAS, ST_FIND);
5960         if (s == NULL)
5961                 return NULL;
5962         if (bitset(MF_MATCHONLY, map->map_mflags))
5963                 return map_rewrite(map, name, strlen(name), NULL);
5964         else
5965                 return map_rewrite(map, s->s_alias, strlen(s->s_alias), av);
5966 }
5967
5968 /*
5969 **  STAB_MAP_STORE -- store in symtab (actually using during init, not rebuild)
5970 */
5971
5972 void
5973 stab_map_store(map, lhs, rhs)
5974         register MAP *map;
5975         char *lhs;
5976         char *rhs;
5977 {
5978         register STAB *s;
5979
5980         s = stab(lhs, ST_ALIAS, ST_ENTER);
5981         s->s_alias = newstr(rhs);
5982 }
5983
5984
5985 /*
5986 **  STAB_MAP_OPEN -- initialize (reads data file)
5987 **
5988 **      This is a weird case -- it is only intended as a fallback for
5989 **      aliases.  For this reason, opens for write (only during a
5990 **      "newaliases") always fails, and opens for read open the
5991 **      actual underlying text file instead of the database.
5992 */
5993
5994 bool
5995 stab_map_open(map, mode)
5996         register MAP *map;
5997         int mode;
5998 {
5999         SM_FILE_T *af;
6000         long sff;
6001         struct stat st;
6002
6003         if (tTd(38, 2))
6004                 sm_dprintf("stab_map_open(%s, %s, %d)\n",
6005                         map->map_mname, map->map_file, mode);
6006
6007         mode &= O_ACCMODE;
6008         if (mode != O_RDONLY)
6009         {
6010                 errno = EPERM;
6011                 return false;
6012         }
6013
6014         sff = SFF_ROOTOK|SFF_REGONLY;
6015         if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
6016                 sff |= SFF_NOWLINK;
6017         if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail))
6018                 sff |= SFF_SAFEDIRPATH;
6019         af = safefopen(map->map_file, O_RDONLY, 0444, sff);
6020         if (af == NULL)
6021                 return false;
6022         readaliases(map, af, false, false);
6023
6024         if (fstat(sm_io_getinfo(af, SM_IO_WHAT_FD, NULL), &st) >= 0)
6025                 map->map_mtime = st.st_mtime;
6026         (void) sm_io_close(af, SM_TIME_DEFAULT);
6027
6028         return true;
6029 }
6030 /*
6031 **  Implicit Modules
6032 **
6033 **      Tries several types.  For back compatibility of aliases.
6034 */
6035
6036
6037 /*
6038 **  IMPL_MAP_LOOKUP -- lookup in best open database
6039 */
6040
6041 char *
6042 impl_map_lookup(map, name, av, pstat)
6043         MAP *map;
6044         char *name;
6045         char **av;
6046         int *pstat;
6047 {
6048         if (tTd(38, 20))
6049                 sm_dprintf("impl_map_lookup(%s, %s)\n",
6050                         map->map_mname, name);
6051
6052 #if NEWDB
6053         if (bitset(MF_IMPL_HASH, map->map_mflags))
6054                 return db_map_lookup(map, name, av, pstat);
6055 #endif /* NEWDB */
6056 #if NDBM
6057         if (bitset(MF_IMPL_NDBM, map->map_mflags))
6058                 return ndbm_map_lookup(map, name, av, pstat);
6059 #endif /* NDBM */
6060         return stab_map_lookup(map, name, av, pstat);
6061 }
6062
6063 /*
6064 **  IMPL_MAP_STORE -- store in open databases
6065 */
6066
6067 void
6068 impl_map_store(map, lhs, rhs)
6069         MAP *map;
6070         char *lhs;
6071         char *rhs;
6072 {
6073         if (tTd(38, 12))
6074                 sm_dprintf("impl_map_store(%s, %s, %s)\n",
6075                         map->map_mname, lhs, rhs);
6076 #if NEWDB
6077         if (bitset(MF_IMPL_HASH, map->map_mflags))
6078                 db_map_store(map, lhs, rhs);
6079 #endif /* NEWDB */
6080 #if NDBM
6081         if (bitset(MF_IMPL_NDBM, map->map_mflags))
6082                 ndbm_map_store(map, lhs, rhs);
6083 #endif /* NDBM */
6084         stab_map_store(map, lhs, rhs);
6085 }
6086
6087 /*
6088 **  IMPL_MAP_OPEN -- implicit database open
6089 */
6090
6091 bool
6092 impl_map_open(map, mode)
6093         MAP *map;
6094         int mode;
6095 {
6096         if (tTd(38, 2))
6097                 sm_dprintf("impl_map_open(%s, %s, %d)\n",
6098                         map->map_mname, map->map_file, mode);
6099
6100         mode &= O_ACCMODE;
6101 #if NEWDB
6102         map->map_mflags |= MF_IMPL_HASH;
6103         if (hash_map_open(map, mode))
6104         {
6105 # ifdef NDBM_YP_COMPAT
6106                 if (mode == O_RDONLY || strstr(map->map_file, "/yp/") == NULL)
6107 # endif /* NDBM_YP_COMPAT */
6108                         return true;
6109         }
6110         else
6111                 map->map_mflags &= ~MF_IMPL_HASH;
6112 #endif /* NEWDB */
6113 #if NDBM
6114         map->map_mflags |= MF_IMPL_NDBM;
6115         if (ndbm_map_open(map, mode))
6116         {
6117                 return true;
6118         }
6119         else
6120                 map->map_mflags &= ~MF_IMPL_NDBM;
6121 #endif /* NDBM */
6122
6123 #if defined(NEWDB) || defined(NDBM)
6124         if (Verbose)
6125                 message("WARNING: cannot open alias database %s%s",
6126                         map->map_file,
6127                         mode == O_RDONLY ? "; reading text version" : "");
6128 #else /* defined(NEWDB) || defined(NDBM) */
6129         if (mode != O_RDONLY)
6130                 usrerr("Cannot rebuild aliases: no database format defined");
6131 #endif /* defined(NEWDB) || defined(NDBM) */
6132
6133         if (mode == O_RDONLY)
6134                 return stab_map_open(map, mode);
6135         else
6136                 return false;
6137 }
6138
6139
6140 /*
6141 **  IMPL_MAP_CLOSE -- close any open database(s)
6142 */
6143
6144 void
6145 impl_map_close(map)
6146         MAP *map;
6147 {
6148         if (tTd(38, 9))
6149                 sm_dprintf("impl_map_close(%s, %s, %lx)\n",
6150                         map->map_mname, map->map_file, map->map_mflags);
6151 #if NEWDB
6152         if (bitset(MF_IMPL_HASH, map->map_mflags))
6153         {
6154                 db_map_close(map);
6155                 map->map_mflags &= ~MF_IMPL_HASH;
6156         }
6157 #endif /* NEWDB */
6158
6159 #if NDBM
6160         if (bitset(MF_IMPL_NDBM, map->map_mflags))
6161         {
6162                 ndbm_map_close(map);
6163                 map->map_mflags &= ~MF_IMPL_NDBM;
6164         }
6165 #endif /* NDBM */
6166 }
6167 /*
6168 **  User map class.
6169 **
6170 **      Provides access to the system password file.
6171 */
6172
6173 /*
6174 **  USER_MAP_OPEN -- open user map
6175 **
6176 **      Really just binds field names to field numbers.
6177 */
6178
6179 bool
6180 user_map_open(map, mode)
6181         MAP *map;
6182         int mode;
6183 {
6184         if (tTd(38, 2))
6185                 sm_dprintf("user_map_open(%s, %d)\n",
6186                         map->map_mname, mode);
6187
6188         mode &= O_ACCMODE;
6189         if (mode != O_RDONLY)
6190         {
6191                 /* issue a pseudo-error message */
6192                 errno = SM_EMAPCANTWRITE;
6193                 return false;
6194         }
6195         if (map->map_valcolnm == NULL)
6196                 /* EMPTY */
6197                 /* nothing */ ;
6198         else if (sm_strcasecmp(map->map_valcolnm, "name") == 0)
6199                 map->map_valcolno = 1;
6200         else if (sm_strcasecmp(map->map_valcolnm, "passwd") == 0)
6201                 map->map_valcolno = 2;
6202         else if (sm_strcasecmp(map->map_valcolnm, "uid") == 0)
6203                 map->map_valcolno = 3;
6204         else if (sm_strcasecmp(map->map_valcolnm, "gid") == 0)
6205                 map->map_valcolno = 4;
6206         else if (sm_strcasecmp(map->map_valcolnm, "gecos") == 0)
6207                 map->map_valcolno = 5;
6208         else if (sm_strcasecmp(map->map_valcolnm, "dir") == 0)
6209                 map->map_valcolno = 6;
6210         else if (sm_strcasecmp(map->map_valcolnm, "shell") == 0)
6211                 map->map_valcolno = 7;
6212         else
6213         {
6214                 syserr("User map %s: unknown column name %s",
6215                         map->map_mname, map->map_valcolnm);
6216                 return false;
6217         }
6218         return true;
6219 }
6220
6221
6222 /*
6223 **  USER_MAP_LOOKUP -- look up a user in the passwd file.
6224 */
6225
6226 /* ARGSUSED3 */
6227 char *
6228 user_map_lookup(map, key, av, statp)
6229         MAP *map;
6230         char *key;
6231         char **av;
6232         int *statp;
6233 {
6234         auto bool fuzzy;
6235         SM_MBDB_T user;
6236
6237         if (tTd(38, 20))
6238                 sm_dprintf("user_map_lookup(%s, %s)\n",
6239                         map->map_mname, key);
6240
6241         *statp = finduser(key, &fuzzy, &user);
6242         if (*statp != EX_OK)
6243                 return NULL;
6244         if (bitset(MF_MATCHONLY, map->map_mflags))
6245                 return map_rewrite(map, key, strlen(key), NULL);
6246         else
6247         {
6248                 char *rwval = NULL;
6249                 char buf[30];
6250
6251                 switch (map->map_valcolno)
6252                 {
6253                   case 0:
6254                   case 1:
6255                         rwval = user.mbdb_name;
6256                         break;
6257
6258                   case 2:
6259                         rwval = "x";    /* passwd no longer supported */
6260                         break;
6261
6262                   case 3:
6263                         (void) sm_snprintf(buf, sizeof(buf), "%d",
6264                                            (int) user.mbdb_uid);
6265                         rwval = buf;
6266                         break;
6267
6268                   case 4:
6269                         (void) sm_snprintf(buf, sizeof(buf), "%d",
6270                                            (int) user.mbdb_gid);
6271                         rwval = buf;
6272                         break;
6273
6274                   case 5:
6275                         rwval = user.mbdb_fullname;
6276                         break;
6277
6278                   case 6:
6279                         rwval = user.mbdb_homedir;
6280                         break;
6281
6282                   case 7:
6283                         rwval = user.mbdb_shell;
6284                         break;
6285                   default:
6286                         syserr("user_map %s: bogus field %d",
6287                                 map->map_mname, map->map_valcolno);
6288                         return NULL;
6289                 }
6290                 return map_rewrite(map, rwval, strlen(rwval), av);
6291         }
6292 }
6293 /*
6294 **  Program map type.
6295 **
6296 **      This provides access to arbitrary programs.  It should be used
6297 **      only very sparingly, since there is no way to bound the cost
6298 **      of invoking an arbitrary program.
6299 */
6300
6301 char *
6302 prog_map_lookup(map, name, av, statp)
6303         MAP *map;
6304         char *name;
6305         char **av;
6306         int *statp;
6307 {
6308         int i;
6309         int save_errno;
6310         int fd;
6311         int status;
6312         auto pid_t pid;
6313         register char *p;
6314         char *rval;
6315         char *argv[MAXPV + 1];
6316         char buf[MAXLINE];
6317
6318         if (tTd(38, 20))
6319                 sm_dprintf("prog_map_lookup(%s, %s) %s\n",
6320                         map->map_mname, name, map->map_file);
6321
6322         i = 0;
6323         argv[i++] = map->map_file;
6324         if (map->map_rebuild != NULL)
6325         {
6326                 (void) sm_strlcpy(buf, map->map_rebuild, sizeof(buf));
6327                 for (p = strtok(buf, " \t"); p != NULL; p = strtok(NULL, " \t"))
6328                 {
6329                         if (i >= MAXPV - 1)
6330                                 break;
6331                         argv[i++] = p;
6332                 }
6333         }
6334         argv[i++] = name;
6335         argv[i] = NULL;
6336         if (tTd(38, 21))
6337         {
6338                 sm_dprintf("prog_open:");
6339                 for (i = 0; argv[i] != NULL; i++)
6340                         sm_dprintf(" %s", argv[i]);
6341                 sm_dprintf("\n");
6342         }
6343         (void) sm_blocksignal(SIGCHLD);
6344         pid = prog_open(argv, &fd, CurEnv);
6345         if (pid < 0)
6346         {
6347                 if (!bitset(MF_OPTIONAL, map->map_mflags))
6348                         syserr("prog_map_lookup(%s) failed (%s) -- closing",
6349                                map->map_mname, sm_errstring(errno));
6350                 else if (tTd(38, 9))
6351                         sm_dprintf("prog_map_lookup(%s) failed (%s) -- closing",
6352                                    map->map_mname, sm_errstring(errno));
6353                 map->map_mflags &= ~(MF_VALID|MF_OPEN);
6354                 *statp = EX_OSFILE;
6355                 return NULL;
6356         }
6357         i = read(fd, buf, sizeof(buf) - 1);
6358         if (i < 0)
6359         {
6360                 syserr("prog_map_lookup(%s): read error %s",
6361                        map->map_mname, sm_errstring(errno));
6362                 rval = NULL;
6363         }
6364         else if (i == 0)
6365         {
6366                 if (tTd(38, 20))
6367                         sm_dprintf("prog_map_lookup(%s): empty answer\n",
6368                                    map->map_mname);
6369                 rval = NULL;
6370         }
6371         else
6372         {
6373                 buf[i] = '\0';
6374                 p = strchr(buf, '\n');
6375                 if (p != NULL)
6376                         *p = '\0';
6377
6378                 /* collect the return value */
6379                 if (bitset(MF_MATCHONLY, map->map_mflags))
6380                         rval = map_rewrite(map, name, strlen(name), NULL);
6381                 else
6382                         rval = map_rewrite(map, buf, strlen(buf), av);
6383
6384                 /* now flush any additional output */
6385                 while ((i = read(fd, buf, sizeof(buf))) > 0)
6386                         continue;
6387         }
6388
6389         /* wait for the process to terminate */
6390         (void) close(fd);
6391         status = waitfor(pid);
6392         save_errno = errno;
6393         (void) sm_releasesignal(SIGCHLD);
6394         errno = save_errno;
6395
6396         if (status == -1)
6397         {
6398                 syserr("prog_map_lookup(%s): wait error %s",
6399                        map->map_mname, sm_errstring(errno));
6400                 *statp = EX_SOFTWARE;
6401                 rval = NULL;
6402         }
6403         else if (WIFEXITED(status))
6404         {
6405                 if ((*statp = WEXITSTATUS(status)) != EX_OK)
6406                         rval = NULL;
6407         }
6408         else
6409         {
6410                 syserr("prog_map_lookup(%s): child died on signal %d",
6411                        map->map_mname, status);
6412                 *statp = EX_UNAVAILABLE;
6413                 rval = NULL;
6414         }
6415         return rval;
6416 }
6417 /*
6418 **  Sequenced map type.
6419 **
6420 **      Tries each map in order until something matches, much like
6421 **      implicit.  Stores go to the first map in the list that can
6422 **      support storing.
6423 **
6424 **      This is slightly unusual in that there are two interfaces.
6425 **      The "sequence" interface lets you stack maps arbitrarily.
6426 **      The "switch" interface builds a sequence map by looking
6427 **      at a system-dependent configuration file such as
6428 **      /etc/nsswitch.conf on Solaris or /etc/svc.conf on Ultrix.
6429 **
6430 **      We don't need an explicit open, since all maps are
6431 **      opened on demand.
6432 */
6433
6434 /*
6435 **  SEQ_MAP_PARSE -- Sequenced map parsing
6436 */
6437
6438 bool
6439 seq_map_parse(map, ap)
6440         MAP *map;
6441         char *ap;
6442 {
6443         int maxmap;
6444
6445         if (tTd(38, 2))
6446                 sm_dprintf("seq_map_parse(%s, %s)\n", map->map_mname, ap);
6447         maxmap = 0;
6448         while (*ap != '\0')
6449         {
6450                 register char *p;
6451                 STAB *s;
6452
6453                 /* find beginning of map name */
6454                 while (isascii(*ap) && isspace(*ap))
6455                         ap++;
6456                 for (p = ap;
6457                      (isascii(*p) && isalnum(*p)) || *p == '_' || *p == '.';
6458                      p++)
6459                         continue;
6460                 if (*p != '\0')
6461                         *p++ = '\0';
6462                 while (*p != '\0' && (!isascii(*p) || !isalnum(*p)))
6463                         p++;
6464                 if (*ap == '\0')
6465                 {
6466                         ap = p;
6467                         continue;
6468                 }
6469                 s = stab(ap, ST_MAP, ST_FIND);
6470                 if (s == NULL)
6471                 {
6472                         syserr("Sequence map %s: unknown member map %s",
6473                                 map->map_mname, ap);
6474                 }
6475                 else if (maxmap >= MAXMAPSTACK)
6476                 {
6477                         syserr("Sequence map %s: too many member maps (%d max)",
6478                                 map->map_mname, MAXMAPSTACK);
6479                         maxmap++;
6480                 }
6481                 else if (maxmap < MAXMAPSTACK)
6482                 {
6483                         map->map_stack[maxmap++] = &s->s_map;
6484                 }
6485                 ap = p;
6486         }
6487         return true;
6488 }
6489
6490 /*
6491 **  SWITCH_MAP_OPEN -- open a switched map
6492 **
6493 **      This looks at the system-dependent configuration and builds
6494 **      a sequence map that does the same thing.
6495 **
6496 **      Every system must define a switch_map_find routine in conf.c
6497 **      that will return the list of service types associated with a
6498 **      given service class.
6499 */
6500
6501 bool
6502 switch_map_open(map, mode)
6503         MAP *map;
6504         int mode;
6505 {
6506         int mapno;
6507         int nmaps;
6508         char *maptype[MAXMAPSTACK];
6509
6510         if (tTd(38, 2))
6511                 sm_dprintf("switch_map_open(%s, %s, %d)\n",
6512                         map->map_mname, map->map_file, mode);
6513
6514         mode &= O_ACCMODE;
6515         nmaps = switch_map_find(map->map_file, maptype, map->map_return);
6516         if (tTd(38, 19))
6517         {
6518                 sm_dprintf("\tswitch_map_find => %d\n", nmaps);
6519                 for (mapno = 0; mapno < nmaps; mapno++)
6520                         sm_dprintf("\t\t%s\n", maptype[mapno]);
6521         }
6522         if (nmaps <= 0 || nmaps > MAXMAPSTACK)
6523                 return false;
6524
6525         for (mapno = 0; mapno < nmaps; mapno++)
6526         {
6527                 register STAB *s;
6528                 char nbuf[MAXNAME + 1];
6529
6530                 if (maptype[mapno] == NULL)
6531                         continue;
6532                 (void) sm_strlcpyn(nbuf, sizeof(nbuf), 3,
6533                                    map->map_mname, ".", maptype[mapno]);
6534                 s = stab(nbuf, ST_MAP, ST_FIND);
6535                 if (s == NULL)
6536                 {
6537                         syserr("Switch map %s: unknown member map %s",
6538                                 map->map_mname, nbuf);
6539                 }
6540                 else
6541                 {
6542                         map->map_stack[mapno] = &s->s_map;
6543                         if (tTd(38, 4))
6544                                 sm_dprintf("\tmap_stack[%d] = %s:%s\n",
6545                                            mapno,
6546                                            s->s_map.map_class->map_cname,
6547                                            nbuf);
6548                 }
6549         }
6550         return true;
6551 }
6552
6553 #if 0
6554 /*
6555 **  SEQ_MAP_CLOSE -- close all underlying maps
6556 */
6557
6558 void
6559 seq_map_close(map)
6560         MAP *map;
6561 {
6562         int mapno;
6563
6564         if (tTd(38, 9))
6565                 sm_dprintf("seq_map_close(%s)\n", map->map_mname);
6566
6567         for (mapno = 0; mapno < MAXMAPSTACK; mapno++)
6568         {
6569                 MAP *mm = map->map_stack[mapno];
6570
6571                 if (mm == NULL || !bitset(MF_OPEN, mm->map_mflags))
6572                         continue;
6573                 mm->map_mflags |= MF_CLOSING;
6574                 mm->map_class->map_close(mm);
6575                 mm->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
6576         }
6577 }
6578 #endif /* 0 */
6579
6580 /*
6581 **  SEQ_MAP_LOOKUP -- sequenced map lookup
6582 */
6583
6584 char *
6585 seq_map_lookup(map, key, args, pstat)
6586         MAP *map;
6587         char *key;
6588         char **args;
6589         int *pstat;
6590 {
6591         int mapno;
6592         int mapbit = 0x01;
6593         bool tempfail = false;
6594
6595         if (tTd(38, 20))
6596                 sm_dprintf("seq_map_lookup(%s, %s)\n", map->map_mname, key);
6597
6598         for (mapno = 0; mapno < MAXMAPSTACK; mapbit <<= 1, mapno++)
6599         {
6600                 MAP *mm = map->map_stack[mapno];
6601                 char *rv;
6602
6603                 if (mm == NULL)
6604                         continue;
6605                 if (!bitset(MF_OPEN, mm->map_mflags) &&
6606                     !openmap(mm))
6607                 {
6608                         if (bitset(mapbit, map->map_return[MA_UNAVAIL]))
6609                         {
6610                                 *pstat = EX_UNAVAILABLE;
6611                                 return NULL;
6612                         }
6613                         continue;
6614                 }
6615                 *pstat = EX_OK;
6616                 rv = mm->map_class->map_lookup(mm, key, args, pstat);
6617                 if (rv != NULL)
6618                         return rv;
6619                 if (*pstat == EX_TEMPFAIL)
6620                 {
6621                         if (bitset(mapbit, map->map_return[MA_TRYAGAIN]))
6622                                 return NULL;
6623                         tempfail = true;
6624                 }
6625                 else if (bitset(mapbit, map->map_return[MA_NOTFOUND]))
6626                         break;
6627         }
6628         if (tempfail)
6629                 *pstat = EX_TEMPFAIL;
6630         else if (*pstat == EX_OK)
6631                 *pstat = EX_NOTFOUND;
6632         return NULL;
6633 }
6634
6635 /*
6636 **  SEQ_MAP_STORE -- sequenced map store
6637 */
6638
6639 void
6640 seq_map_store(map, key, val)
6641         MAP *map;
6642         char *key;
6643         char *val;
6644 {
6645         int mapno;
6646
6647         if (tTd(38, 12))
6648                 sm_dprintf("seq_map_store(%s, %s, %s)\n",
6649                         map->map_mname, key, val);
6650
6651         for (mapno = 0; mapno < MAXMAPSTACK; mapno++)
6652         {
6653                 MAP *mm = map->map_stack[mapno];
6654
6655                 if (mm == NULL || !bitset(MF_WRITABLE, mm->map_mflags))
6656                         continue;
6657
6658                 mm->map_class->map_store(mm, key, val);
6659                 return;
6660         }
6661         syserr("seq_map_store(%s, %s, %s): no writable map",
6662                 map->map_mname, key, val);
6663 }
6664 /*
6665 **  NULL stubs
6666 */
6667
6668 /* ARGSUSED */
6669 bool
6670 null_map_open(map, mode)
6671         MAP *map;
6672         int mode;
6673 {
6674         return true;
6675 }
6676
6677 /* ARGSUSED */
6678 void
6679 null_map_close(map)
6680         MAP *map;
6681 {
6682         return;
6683 }
6684
6685 char *
6686 null_map_lookup(map, key, args, pstat)
6687         MAP *map;
6688         char *key;
6689         char **args;
6690         int *pstat;
6691 {
6692         *pstat = EX_NOTFOUND;
6693         return NULL;
6694 }
6695
6696 /* ARGSUSED */
6697 void
6698 null_map_store(map, key, val)
6699         MAP *map;
6700         char *key;
6701         char *val;
6702 {
6703         return;
6704 }
6705
6706 MAPCLASS        NullMapClass =
6707 {
6708         "null-map",             NULL,                   0,
6709         NULL,                   null_map_lookup,        null_map_store,
6710         null_map_open,          null_map_close,
6711 };
6712
6713 /*
6714 **  BOGUS stubs
6715 */
6716
6717 char *
6718 bogus_map_lookup(map, key, args, pstat)
6719         MAP *map;
6720         char *key;
6721         char **args;
6722         int *pstat;
6723 {
6724         *pstat = EX_TEMPFAIL;
6725         return NULL;
6726 }
6727
6728 MAPCLASS        BogusMapClass =
6729 {
6730         "bogus-map",            NULL,                   0,
6731         NULL,                   bogus_map_lookup,       null_map_store,
6732         null_map_open,          null_map_close,
6733 };
6734 /*
6735 **  MACRO modules
6736 */
6737
6738 char *
6739 macro_map_lookup(map, name, av, statp)
6740         MAP *map;
6741         char *name;
6742         char **av;
6743         int *statp;
6744 {
6745         int mid;
6746
6747         if (tTd(38, 20))
6748                 sm_dprintf("macro_map_lookup(%s, %s)\n", map->map_mname,
6749                         name == NULL ? "NULL" : name);
6750
6751         if (name == NULL ||
6752             *name == '\0' ||
6753             (mid = macid(name)) == 0)
6754         {
6755                 *statp = EX_CONFIG;
6756                 return NULL;
6757         }
6758
6759         if (av[1] == NULL)
6760                 macdefine(&CurEnv->e_macro, A_PERM, mid, NULL);
6761         else
6762                 macdefine(&CurEnv->e_macro, A_TEMP, mid, av[1]);
6763
6764         *statp = EX_OK;
6765         return "";
6766 }
6767 /*
6768 **  REGEX modules
6769 */
6770
6771 #if MAP_REGEX
6772
6773 # include <regex.h>
6774
6775 # define DEFAULT_DELIM  CONDELSE
6776 # define END_OF_FIELDS  -1
6777 # define ERRBUF_SIZE    80
6778 # define MAX_MATCH      32
6779
6780 # define xnalloc(s)     memset(xalloc(s), '\0', s);
6781
6782 struct regex_map
6783 {
6784         regex_t *regex_pattern_buf;     /* xalloc it */
6785         int     *regex_subfields;       /* move to type MAP */
6786         char    *regex_delim;           /* move to type MAP */
6787 };
6788
6789 static int      parse_fields __P((char *, int *, int, int));
6790 static char     *regex_map_rewrite __P((MAP *, const char*, size_t, char **));
6791
6792 static int
6793 parse_fields(s, ibuf, blen, nr_substrings)
6794         char *s;
6795         int *ibuf;              /* array */
6796         int blen;               /* number of elements in ibuf */
6797         int nr_substrings;      /* number of substrings in the pattern */
6798 {
6799         register char *cp;
6800         int i = 0;
6801         bool lastone = false;
6802
6803         blen--;         /* for terminating END_OF_FIELDS */
6804         cp = s;
6805         do
6806         {
6807                 for (;; cp++)
6808                 {
6809                         if (*cp == ',')
6810                         {
6811                                 *cp = '\0';
6812                                 break;
6813                         }
6814                         if (*cp == '\0')
6815                         {
6816                                 lastone = true;
6817                                 break;
6818                         }
6819                 }
6820                 if (i < blen)
6821                 {
6822                         int val = atoi(s);
6823
6824                         if (val < 0 || val >= nr_substrings)
6825                         {
6826                                 syserr("field (%d) out of range, only %d substrings in pattern",
6827                                        val, nr_substrings);
6828                                 return -1;
6829                         }
6830                         ibuf[i++] = val;
6831                 }
6832                 else
6833                 {
6834                         syserr("too many fields, %d max", blen);
6835                         return -1;
6836                 }
6837                 s = ++cp;
6838         } while (!lastone);
6839         ibuf[i] = END_OF_FIELDS;
6840         return i;
6841 }
6842
6843 bool
6844 regex_map_init(map, ap)
6845         MAP *map;
6846         char *ap;
6847 {
6848         int regerr;
6849         struct regex_map *map_p;
6850         register char *p;
6851         char *sub_param = NULL;
6852         int pflags;
6853         static char defdstr[] = { (char) DEFAULT_DELIM, '\0' };
6854
6855         if (tTd(38, 2))
6856                 sm_dprintf("regex_map_init: mapname '%s', args '%s'\n",
6857                         map->map_mname, ap);
6858
6859         pflags = REG_ICASE | REG_EXTENDED | REG_NOSUB;
6860         p = ap;
6861         map_p = (struct regex_map *) xnalloc(sizeof(*map_p));
6862         map_p->regex_pattern_buf = (regex_t *)xnalloc(sizeof(regex_t));
6863
6864         for (;;)
6865         {
6866                 while (isascii(*p) && isspace(*p))
6867                         p++;
6868                 if (*p != '-')
6869                         break;
6870                 switch (*++p)
6871                 {
6872                   case 'n':     /* not */
6873                         map->map_mflags |= MF_REGEX_NOT;
6874                         break;
6875
6876                   case 'f':     /* case sensitive */
6877                         map->map_mflags |= MF_NOFOLDCASE;
6878                         pflags &= ~REG_ICASE;
6879                         break;
6880
6881                   case 'b':     /* basic regular expressions */
6882                         pflags &= ~REG_EXTENDED;
6883                         break;
6884
6885                   case 's':     /* substring match () syntax */
6886                         sub_param = ++p;
6887                         pflags &= ~REG_NOSUB;
6888                         break;
6889
6890                   case 'd':     /* delimiter */
6891                         map_p->regex_delim = ++p;
6892                         break;
6893
6894                   case 'a':     /* map append */
6895                         map->map_app = ++p;
6896                         break;
6897
6898                   case 'm':     /* matchonly */
6899                         map->map_mflags |= MF_MATCHONLY;
6900                         break;
6901
6902                   case 'q':
6903                         map->map_mflags |= MF_KEEPQUOTES;
6904                         break;
6905
6906                   case 'S':
6907                         map->map_spacesub = *++p;
6908                         break;
6909
6910                   case 'D':
6911                         map->map_mflags |= MF_DEFER;
6912                         break;
6913
6914                 }
6915                 while (*p != '\0' && !(isascii(*p) && isspace(*p)))
6916                         p++;
6917                 if (*p != '\0')
6918                         *p++ = '\0';
6919         }
6920         if (tTd(38, 3))
6921                 sm_dprintf("regex_map_init: compile '%s' 0x%x\n", p, pflags);
6922
6923         if ((regerr = regcomp(map_p->regex_pattern_buf, p, pflags)) != 0)
6924         {
6925                 /* Errorhandling */
6926                 char errbuf[ERRBUF_SIZE];
6927
6928                 (void) regerror(regerr, map_p->regex_pattern_buf,
6929                          errbuf, sizeof(errbuf));
6930                 syserr("pattern-compile-error: %s", errbuf);
6931                 sm_free(map_p->regex_pattern_buf); /* XXX */
6932                 sm_free(map_p); /* XXX */
6933                 return false;
6934         }
6935
6936         if (map->map_app != NULL)
6937                 map->map_app = newstr(map->map_app);
6938         if (map_p->regex_delim != NULL)
6939                 map_p->regex_delim = newstr(map_p->regex_delim);
6940         else
6941                 map_p->regex_delim = defdstr;
6942
6943         if (!bitset(REG_NOSUB, pflags))
6944         {
6945                 /* substring matching */
6946                 int substrings;
6947                 int *fields = (int *) xalloc(sizeof(int) * (MAX_MATCH + 1));
6948
6949                 substrings = map_p->regex_pattern_buf->re_nsub + 1;
6950
6951                 if (tTd(38, 3))
6952                         sm_dprintf("regex_map_init: nr of substrings %d\n",
6953                                 substrings);
6954
6955                 if (substrings >= MAX_MATCH)
6956                 {
6957                         syserr("too many substrings, %d max", MAX_MATCH);
6958                         sm_free(map_p->regex_pattern_buf); /* XXX */
6959                         sm_free(map_p); /* XXX */
6960                         return false;
6961                 }
6962                 if (sub_param != NULL && sub_param[0] != '\0')
6963                 {
6964                         /* optional parameter -sfields */
6965                         if (parse_fields(sub_param, fields,
6966                                          MAX_MATCH + 1, substrings) == -1)
6967                                 return false;
6968                 }
6969                 else
6970                 {
6971                         int i;
6972
6973                         /* set default fields */
6974                         for (i = 0; i < substrings; i++)
6975                                 fields[i] = i;
6976                         fields[i] = END_OF_FIELDS;
6977                 }
6978                 map_p->regex_subfields = fields;
6979                 if (tTd(38, 3))
6980                 {
6981                         int *ip;
6982
6983                         sm_dprintf("regex_map_init: subfields");
6984                         for (ip = fields; *ip != END_OF_FIELDS; ip++)
6985                                 sm_dprintf(" %d", *ip);
6986                         sm_dprintf("\n");
6987                 }
6988         }
6989         map->map_db1 = (ARBPTR_T) map_p;        /* dirty hack */
6990         return true;
6991 }
6992
6993 static char *
6994 regex_map_rewrite(map, s, slen, av)
6995         MAP *map;
6996         const char *s;
6997         size_t slen;
6998         char **av;
6999 {
7000         if (bitset(MF_MATCHONLY, map->map_mflags))
7001                 return map_rewrite(map, av[0], strlen(av[0]), NULL);
7002         else
7003                 return map_rewrite(map, s, slen, av);
7004 }
7005
7006 char *
7007 regex_map_lookup(map, name, av, statp)
7008         MAP *map;
7009         char *name;
7010         char **av;
7011         int *statp;
7012 {
7013         int reg_res;
7014         struct regex_map *map_p;
7015         regmatch_t pmatch[MAX_MATCH];
7016
7017         if (tTd(38, 20))
7018         {
7019                 char **cpp;
7020
7021                 sm_dprintf("regex_map_lookup: key '%s'\n", name);
7022                 for (cpp = av; cpp != NULL && *cpp != NULL; cpp++)
7023                         sm_dprintf("regex_map_lookup: arg '%s'\n", *cpp);
7024         }
7025
7026         map_p = (struct regex_map *)(map->map_db1);
7027         reg_res = regexec(map_p->regex_pattern_buf,
7028                           name, MAX_MATCH, pmatch, 0);
7029
7030         if (bitset(MF_REGEX_NOT, map->map_mflags))
7031         {
7032                 /* option -n */
7033                 if (reg_res == REG_NOMATCH)
7034                         return regex_map_rewrite(map, "", (size_t) 0, av);
7035                 else
7036                         return NULL;
7037         }
7038         if (reg_res == REG_NOMATCH)
7039                 return NULL;
7040
7041         if (map_p->regex_subfields != NULL)
7042         {
7043                 /* option -s */
7044                 static char retbuf[MAXNAME];
7045                 int fields[MAX_MATCH + 1];
7046                 bool first = true;
7047                 int anglecnt = 0, cmntcnt = 0, spacecnt = 0;
7048                 bool quotemode = false, bslashmode = false;
7049                 register char *dp, *sp;
7050                 char *endp, *ldp;
7051                 int *ip;
7052
7053                 dp = retbuf;
7054                 ldp = retbuf + sizeof(retbuf) - 1;
7055
7056                 if (av[1] != NULL)
7057                 {
7058                         if (parse_fields(av[1], fields, MAX_MATCH + 1,
7059                                          (int) map_p->regex_pattern_buf->re_nsub + 1) == -1)
7060                         {
7061                                 *statp = EX_CONFIG;
7062                                 return NULL;
7063                         }
7064                         ip = fields;
7065                 }
7066                 else
7067                         ip = map_p->regex_subfields;
7068
7069                 for ( ; *ip != END_OF_FIELDS; ip++)
7070                 {
7071                         if (!first)
7072                         {
7073                                 for (sp = map_p->regex_delim; *sp; sp++)
7074                                 {
7075                                         if (dp < ldp)
7076                                                 *dp++ = *sp;
7077                                 }
7078                         }
7079                         else
7080                                 first = false;
7081
7082                         if (*ip >= MAX_MATCH ||
7083                             pmatch[*ip].rm_so < 0 || pmatch[*ip].rm_eo < 0)
7084                                 continue;
7085
7086                         sp = name + pmatch[*ip].rm_so;
7087                         endp = name + pmatch[*ip].rm_eo;
7088                         for (; endp > sp; sp++)
7089                         {
7090                                 if (dp < ldp)
7091                                 {
7092                                         if (bslashmode)
7093                                         {
7094                                                 *dp++ = *sp;
7095                                                 bslashmode = false;
7096                                         }
7097                                         else if (quotemode && *sp != '"' &&
7098                                                 *sp != '\\')
7099                                         {
7100                                                 *dp++ = *sp;
7101                                         }
7102                                         else switch (*dp++ = *sp)
7103                                         {
7104                                           case '\\':
7105                                                 bslashmode = true;
7106                                                 break;
7107
7108                                           case '(':
7109                                                 cmntcnt++;
7110                                                 break;
7111
7112                                           case ')':
7113                                                 cmntcnt--;
7114                                                 break;
7115
7116                                           case '<':
7117                                                 anglecnt++;
7118                                                 break;
7119
7120                                           case '>':
7121                                                 anglecnt--;
7122                                                 break;
7123
7124                                           case ' ':
7125                                                 spacecnt++;
7126                                                 break;
7127
7128                                           case '"':
7129                                                 quotemode = !quotemode;
7130                                                 break;
7131                                         }
7132                                 }
7133                         }
7134                 }
7135                 if (anglecnt != 0 || cmntcnt != 0 || quotemode ||
7136                     bslashmode || spacecnt != 0)
7137                 {
7138                         sm_syslog(LOG_WARNING, NOQID,
7139                                   "Warning: regex may cause prescan() failure map=%s lookup=%s",
7140                                   map->map_mname, name);
7141                         return NULL;
7142                 }
7143
7144                 *dp = '\0';
7145
7146                 return regex_map_rewrite(map, retbuf, strlen(retbuf), av);
7147         }
7148         return regex_map_rewrite(map, "", (size_t)0, av);
7149 }
7150 #endif /* MAP_REGEX */
7151 /*
7152 **  NSD modules
7153 */
7154 #if MAP_NSD
7155
7156 # include <ndbm.h>
7157 # define _DATUM_DEFINED
7158 # include <ns_api.h>
7159
7160 typedef struct ns_map_list
7161 {
7162         ns_map_t                *map;           /* XXX ns_ ? */
7163         char                    *mapname;
7164         struct ns_map_list      *next;
7165 } ns_map_list_t;
7166
7167 static ns_map_t *
7168 ns_map_t_find(mapname)
7169         char *mapname;
7170 {
7171         static ns_map_list_t *ns_maps = NULL;
7172         ns_map_list_t *ns_map;
7173
7174         /* walk the list of maps looking for the correctly named map */
7175         for (ns_map = ns_maps; ns_map != NULL; ns_map = ns_map->next)
7176         {
7177                 if (strcmp(ns_map->mapname, mapname) == 0)
7178                         break;
7179         }
7180
7181         /* if we are looking at a NULL ns_map_list_t, then create a new one */
7182         if (ns_map == NULL)
7183         {
7184                 ns_map = (ns_map_list_t *) xalloc(sizeof(*ns_map));
7185                 ns_map->mapname = newstr(mapname);
7186                 ns_map->map = (ns_map_t *) xalloc(sizeof(*ns_map->map));
7187                 memset(ns_map->map, '\0', sizeof(*ns_map->map));
7188                 ns_map->next = ns_maps;
7189                 ns_maps = ns_map;
7190         }
7191         return ns_map->map;
7192 }
7193
7194 char *
7195 nsd_map_lookup(map, name, av, statp)
7196         MAP *map;
7197         char *name;
7198         char **av;
7199         int *statp;
7200 {
7201         int buflen, r;
7202         char *p;
7203         ns_map_t *ns_map;
7204         char keybuf[MAXNAME + 1];
7205         char buf[MAXLINE];
7206
7207         if (tTd(38, 20))
7208                 sm_dprintf("nsd_map_lookup(%s, %s)\n", map->map_mname, name);
7209
7210         buflen = strlen(name);
7211         if (buflen > sizeof(keybuf) - 1)
7212                 buflen = sizeof(keybuf) - 1;    /* XXX simply cut off? */
7213         memmove(keybuf, name, buflen);
7214         keybuf[buflen] = '\0';
7215         if (!bitset(MF_NOFOLDCASE, map->map_mflags))
7216                 makelower(keybuf);
7217
7218         ns_map = ns_map_t_find(map->map_file);
7219         if (ns_map == NULL)
7220         {
7221                 if (tTd(38, 20))
7222                         sm_dprintf("nsd_map_t_find failed\n");
7223                 *statp = EX_UNAVAILABLE;
7224                 return NULL;
7225         }
7226         r = ns_lookup(ns_map, NULL, map->map_file, keybuf, NULL,
7227                       buf, sizeof(buf));
7228         if (r == NS_UNAVAIL || r == NS_TRYAGAIN)
7229         {
7230                 *statp = EX_TEMPFAIL;
7231                 return NULL;
7232         }
7233         if (r == NS_BADREQ
7234 # ifdef NS_NOPERM
7235             || r == NS_NOPERM
7236 # endif /* NS_NOPERM */
7237             )
7238         {
7239                 *statp = EX_CONFIG;
7240                 return NULL;
7241         }
7242         if (r != NS_SUCCESS)
7243         {
7244                 *statp = EX_NOTFOUND;
7245                 return NULL;
7246         }
7247
7248         *statp = EX_OK;
7249
7250         /* Null out trailing \n */
7251         if ((p = strchr(buf, '\n')) != NULL)
7252                 *p = '\0';
7253
7254         return map_rewrite(map, buf, strlen(buf), av);
7255 }
7256 #endif /* MAP_NSD */
7257
7258 char *
7259 arith_map_lookup(map, name, av, statp)
7260         MAP *map;
7261         char *name;
7262         char **av;
7263         int *statp;
7264 {
7265         long r;
7266         long v[2];
7267         bool res = false;
7268         bool boolres;
7269         static char result[16];
7270         char **cpp;
7271
7272         if (tTd(38, 2))
7273         {
7274                 sm_dprintf("arith_map_lookup: key '%s'\n", name);
7275                 for (cpp = av; cpp != NULL && *cpp != NULL; cpp++)
7276                         sm_dprintf("arith_map_lookup: arg '%s'\n", *cpp);
7277         }
7278         r = 0;
7279         boolres = false;
7280         cpp = av;
7281         *statp = EX_OK;
7282
7283         /*
7284         **  read arguments for arith map
7285         **  - no check is made whether they are really numbers
7286         **  - just ignores args after the second
7287         */
7288
7289         for (++cpp; cpp != NULL && *cpp != NULL && r < 2; cpp++)
7290                 v[r++] = strtol(*cpp, NULL, 0);
7291
7292         /* operator and (at least) two operands given? */
7293         if (name != NULL && r == 2)
7294         {
7295                 switch (*name)
7296                 {
7297                   case '|':
7298                         r = v[0] | v[1];
7299                         break;
7300
7301                   case '&':
7302                         r = v[0] & v[1];
7303                         break;
7304
7305                   case '%':
7306                         if (v[1] == 0)
7307                                 return NULL;
7308                         r = v[0] % v[1];
7309                         break;
7310                   case '+':
7311                         r = v[0] + v[1];
7312                         break;
7313
7314                   case '-':
7315                         r = v[0] - v[1];
7316                         break;
7317
7318                   case '*':
7319                         r = v[0] * v[1];
7320                         break;
7321
7322                   case '/':
7323                         if (v[1] == 0)
7324                                 return NULL;
7325                         r = v[0] / v[1];
7326                         break;
7327
7328                   case 'l':
7329                         res = v[0] < v[1];
7330                         boolres = true;
7331                         break;
7332
7333                   case '=':
7334                         res = v[0] == v[1];
7335                         boolres = true;
7336                         break;
7337
7338                   case 'r':
7339                         r = v[1] - v[0] + 1;
7340                         if (r <= 0)
7341                                 return NULL;
7342                         r = get_random() % r + v[0];
7343                         break;
7344
7345                   default:
7346                         /* XXX */
7347                         *statp = EX_CONFIG;
7348                         if (LogLevel > 10)
7349                                 sm_syslog(LOG_WARNING, NOQID,
7350                                           "arith_map: unknown operator %c",
7351                                           (isascii(*name) && isprint(*name)) ?
7352                                           *name : '?');
7353                         return NULL;
7354                 }
7355                 if (boolres)
7356                         (void) sm_snprintf(result, sizeof(result),
7357                                 res ? "TRUE" : "FALSE");
7358                 else
7359                         (void) sm_snprintf(result, sizeof(result), "%ld", r);
7360                 return result;
7361         }
7362         *statp = EX_CONFIG;
7363         return NULL;
7364 }
7365
7366 #if SOCKETMAP
7367
7368 # if NETINET || NETINET6
7369 #  include <arpa/inet.h>
7370 # endif /* NETINET || NETINET6 */
7371
7372 # define socket_map_next map_stack[0]
7373
7374 /*
7375 **  SOCKET_MAP_OPEN -- open socket table
7376 */
7377
7378 bool
7379 socket_map_open(map, mode)
7380         MAP *map;
7381         int mode;
7382 {
7383         STAB *s;
7384         int sock = 0;
7385         SOCKADDR_LEN_T addrlen = 0;
7386         int addrno = 0;
7387         int save_errno;
7388         char *p;
7389         char *colon;
7390         char *at;
7391         struct hostent *hp = NULL;
7392         SOCKADDR addr;
7393
7394         if (tTd(38, 2))
7395                 sm_dprintf("socket_map_open(%s, %s, %d)\n",
7396                         map->map_mname, map->map_file, mode);
7397
7398         mode &= O_ACCMODE;
7399
7400         /* sendmail doesn't have the ability to write to SOCKET (yet) */
7401         if (mode != O_RDONLY)
7402         {
7403                 /* issue a pseudo-error message */
7404                 errno = SM_EMAPCANTWRITE;
7405                 return false;
7406         }
7407
7408         if (*map->map_file == '\0')
7409         {
7410                 syserr("socket map \"%s\": empty or missing socket information",
7411                         map->map_mname);
7412                 return false;
7413         }
7414
7415         s = socket_map_findconn(map->map_file);
7416         if (s->s_socketmap != NULL)
7417         {
7418                 /* Copy open connection */
7419                 map->map_db1 = s->s_socketmap->map_db1;
7420
7421                 /* Add this map as head of linked list */
7422                 map->socket_map_next = s->s_socketmap;
7423                 s->s_socketmap = map;
7424
7425                 if (tTd(38, 2))
7426                         sm_dprintf("using cached connection\n");
7427                 return true;
7428         }
7429
7430         if (tTd(38, 2))
7431                 sm_dprintf("opening new connection\n");
7432
7433         /* following code is ripped from milter.c */
7434         /* XXX It should be put in a library... */
7435
7436         /* protocol:filename or protocol:port@host */
7437         memset(&addr, '\0', sizeof(addr));
7438         p = map->map_file;
7439         colon = strchr(p, ':');
7440         if (colon != NULL)
7441         {
7442                 *colon = '\0';
7443
7444                 if (*p == '\0')
7445                 {
7446 # if NETUNIX
7447                         /* default to AF_UNIX */
7448                         addr.sa.sa_family = AF_UNIX;
7449 # else /* NETUNIX */
7450 #  if NETINET
7451                         /* default to AF_INET */
7452                         addr.sa.sa_family = AF_INET;
7453 #  else /* NETINET */
7454 #   if NETINET6
7455                         /* default to AF_INET6 */
7456                         addr.sa.sa_family = AF_INET6;
7457 #   else /* NETINET6 */
7458                         /* no protocols available */
7459                         syserr("socket map \"%s\": no valid socket protocols available",
7460                         map->map_mname);
7461                         return false;
7462 #   endif /* NETINET6 */
7463 #  endif /* NETINET */
7464 # endif /* NETUNIX */
7465                 }
7466 # if NETUNIX
7467                 else if (sm_strcasecmp(p, "unix") == 0 ||
7468                          sm_strcasecmp(p, "local") == 0)
7469                         addr.sa.sa_family = AF_UNIX;
7470 # endif /* NETUNIX */
7471 # if NETINET
7472                 else if (sm_strcasecmp(p, "inet") == 0)
7473                         addr.sa.sa_family = AF_INET;
7474 # endif /* NETINET */
7475 # if NETINET6
7476                 else if (sm_strcasecmp(p, "inet6") == 0)
7477                         addr.sa.sa_family = AF_INET6;
7478 # endif /* NETINET6 */
7479                 else
7480                 {
7481 # ifdef EPROTONOSUPPORT
7482                         errno = EPROTONOSUPPORT;
7483 # else /* EPROTONOSUPPORT */
7484                         errno = EINVAL;
7485 # endif /* EPROTONOSUPPORT */
7486                         syserr("socket map \"%s\": unknown socket type %s",
7487                                map->map_mname, p);
7488                         return false;
7489                 }
7490                 *colon++ = ':';
7491         }
7492         else
7493         {
7494                 colon = p;
7495 #if NETUNIX
7496                 /* default to AF_UNIX */
7497                 addr.sa.sa_family = AF_UNIX;
7498 #else /* NETUNIX */
7499 # if NETINET
7500                 /* default to AF_INET */
7501                 addr.sa.sa_family = AF_INET;
7502 # else /* NETINET */
7503 #  if NETINET6
7504                 /* default to AF_INET6 */
7505                 addr.sa.sa_family = AF_INET6;
7506 #  else /* NETINET6 */
7507                 syserr("socket map \"%s\": unknown socket type %s",
7508                        map->map_mname, p);
7509                 return false;
7510 #  endif /* NETINET6 */
7511 # endif /* NETINET */
7512 #endif /* NETUNIX */
7513         }
7514
7515 # if NETUNIX
7516         if (addr.sa.sa_family == AF_UNIX)
7517         {
7518                 long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_EXECOK;
7519
7520                 at = colon;
7521                 if (strlen(colon) >= sizeof(addr.sunix.sun_path))
7522                 {
7523                         syserr("socket map \"%s\": local socket name %s too long",
7524                                map->map_mname, colon);
7525                         return false;
7526                 }
7527                 errno = safefile(colon, RunAsUid, RunAsGid, RunAsUserName, sff,
7528                                  S_IRUSR|S_IWUSR, NULL);
7529
7530                 if (errno != 0)
7531                 {
7532                         /* if not safe, don't create */
7533                                 syserr("socket map \"%s\": local socket name %s unsafe",
7534                                map->map_mname, colon);
7535                         return false;
7536                 }
7537
7538                 (void) sm_strlcpy(addr.sunix.sun_path, colon,
7539                                sizeof(addr.sunix.sun_path));
7540                 addrlen = sizeof(struct sockaddr_un);
7541         }
7542         else
7543 # endif /* NETUNIX */
7544 # if NETINET || NETINET6
7545         if (false
7546 #  if NETINET
7547                  || addr.sa.sa_family == AF_INET
7548 #  endif /* NETINET */
7549 #  if NETINET6
7550                  || addr.sa.sa_family == AF_INET6
7551 #  endif /* NETINET6 */
7552                  )
7553         {
7554                 unsigned short port;
7555
7556                 /* Parse port@host */
7557                 at = strchr(colon, '@');
7558                 if (at == NULL)
7559                 {
7560                         syserr("socket map \"%s\": bad address %s (expected port@host)",
7561                                        map->map_mname, colon);
7562                         return false;
7563                 }
7564                 *at = '\0';
7565                 if (isascii(*colon) && isdigit(*colon))
7566                         port = htons((unsigned short) atoi(colon));
7567                 else
7568                 {
7569 #  ifdef NO_GETSERVBYNAME
7570                         syserr("socket map \"%s\": invalid port number %s",
7571                                        map->map_mname, colon);
7572                         return false;
7573 #  else /* NO_GETSERVBYNAME */
7574                         register struct servent *sp;
7575
7576                         sp = getservbyname(colon, "tcp");
7577                         if (sp == NULL)
7578                         {
7579                                 syserr("socket map \"%s\": unknown port name %s",
7580                                                map->map_mname, colon);
7581                                 return false;
7582                         }
7583                         port = sp->s_port;
7584 #  endif /* NO_GETSERVBYNAME */
7585                 }
7586                 *at++ = '@';
7587                 if (*at == '[')
7588                 {
7589                         char *end;
7590
7591                         end = strchr(at, ']');
7592                         if (end != NULL)
7593                         {
7594                                 bool found = false;
7595 #  if NETINET
7596                                 unsigned long hid = INADDR_NONE;
7597 #  endif /* NETINET */
7598 #  if NETINET6
7599                                 struct sockaddr_in6 hid6;
7600 #  endif /* NETINET6 */
7601
7602                                 *end = '\0';
7603 #  if NETINET
7604                                 if (addr.sa.sa_family == AF_INET &&
7605                                     (hid = inet_addr(&at[1])) != INADDR_NONE)
7606                                 {
7607                                         addr.sin.sin_addr.s_addr = hid;
7608                                         addr.sin.sin_port = port;
7609                                         found = true;
7610                                 }
7611 #  endif /* NETINET */
7612 #  if NETINET6
7613                                 (void) memset(&hid6, '\0', sizeof(hid6));
7614                                 if (addr.sa.sa_family == AF_INET6 &&
7615                                     anynet_pton(AF_INET6, &at[1],
7616                                                 &hid6.sin6_addr) == 1)
7617                                 {
7618                                         addr.sin6.sin6_addr = hid6.sin6_addr;
7619                                         addr.sin6.sin6_port = port;
7620                                         found = true;
7621                                 }
7622 #  endif /* NETINET6 */
7623                                 *end = ']';
7624                                 if (!found)
7625                                 {
7626                                         syserr("socket map \"%s\": Invalid numeric domain spec \"%s\"",
7627                                                map->map_mname, at);
7628                                         return false;
7629                                 }
7630                         }
7631                         else
7632                         {
7633                                 syserr("socket map \"%s\": Invalid numeric domain spec \"%s\"",
7634                                        map->map_mname, at);
7635                                 return false;
7636                         }
7637                 }
7638                 else
7639                 {
7640                         hp = sm_gethostbyname(at, addr.sa.sa_family);
7641                         if (hp == NULL)
7642                         {
7643                                 syserr("socket map \"%s\": Unknown host name %s",
7644                                         map->map_mname, at);
7645                                 return false;
7646                         }
7647                         addr.sa.sa_family = hp->h_addrtype;
7648                         switch (hp->h_addrtype)
7649                         {
7650 #  if NETINET
7651                           case AF_INET:
7652                                 memmove(&addr.sin.sin_addr,
7653                                         hp->h_addr, INADDRSZ);
7654                                 addr.sin.sin_port = port;
7655                                 addrlen = sizeof(struct sockaddr_in);
7656                                 addrno = 1;
7657                                 break;
7658 #  endif /* NETINET */
7659
7660 #  if NETINET6
7661                           case AF_INET6:
7662                                 memmove(&addr.sin6.sin6_addr,
7663                                         hp->h_addr, IN6ADDRSZ);
7664                                 addr.sin6.sin6_port = port;
7665                                 addrlen = sizeof(struct sockaddr_in6);
7666                                 addrno = 1;
7667                                 break;
7668 #  endif /* NETINET6 */
7669
7670                           default:
7671                                 syserr("socket map \"%s\": Unknown protocol for %s (%d)",
7672                                         map->map_mname, at, hp->h_addrtype);
7673 #  if NETINET6
7674                                 freehostent(hp);
7675 #  endif /* NETINET6 */
7676                                 return false;
7677                         }
7678                 }
7679         }
7680         else
7681 # endif /* NETINET || NETINET6 */
7682         {
7683                 syserr("socket map \"%s\": unknown socket protocol",
7684                         map->map_mname);
7685                 return false;
7686         }
7687
7688         /* nope, actually connecting */
7689         for (;;)
7690         {
7691                 sock = socket(addr.sa.sa_family, SOCK_STREAM, 0);
7692                 if (sock < 0)
7693                 {
7694                         save_errno = errno;
7695                         if (tTd(38, 5))
7696                                 sm_dprintf("socket map \"%s\": error creating socket: %s\n",
7697                                            map->map_mname,
7698                                            sm_errstring(save_errno));
7699 # if NETINET6
7700                         if (hp != NULL)
7701                                 freehostent(hp);
7702 # endif /* NETINET6 */
7703                         return false;
7704                 }
7705
7706                 if (connect(sock, (struct sockaddr *) &addr, addrlen) >= 0)
7707                         break;
7708
7709                 /* couldn't connect.... try next address */
7710                 save_errno = errno;
7711                 p = CurHostName;
7712                 CurHostName = at;
7713                 if (tTd(38, 5))
7714                         sm_dprintf("socket_open (%s): open %s failed: %s\n",
7715                                 map->map_mname, at, sm_errstring(save_errno));
7716                 CurHostName = p;
7717                 (void) close(sock);
7718
7719                 /* try next address */
7720                 if (hp != NULL && hp->h_addr_list[addrno] != NULL)
7721                 {
7722                         switch (addr.sa.sa_family)
7723                         {
7724 # if NETINET
7725                           case AF_INET:
7726                                 memmove(&addr.sin.sin_addr,
7727                                         hp->h_addr_list[addrno++],
7728                                         INADDRSZ);
7729                                 break;
7730 # endif /* NETINET */
7731
7732 # if NETINET6
7733                           case AF_INET6:
7734                                 memmove(&addr.sin6.sin6_addr,
7735                                         hp->h_addr_list[addrno++],
7736                                         IN6ADDRSZ);
7737                                 break;
7738 # endif /* NETINET6 */
7739
7740                           default:
7741                                 if (tTd(38, 5))
7742                                         sm_dprintf("socket map \"%s\": Unknown protocol for %s (%d)\n",
7743                                                    map->map_mname, at,
7744                                                    hp->h_addrtype);
7745 # if NETINET6
7746                                 freehostent(hp);
7747 # endif /* NETINET6 */
7748                                 return false;
7749                         }
7750                         continue;
7751                 }
7752                 p = CurHostName;
7753                 CurHostName = at;
7754                 if (tTd(38, 5))
7755                         sm_dprintf("socket map \"%s\": error connecting to socket map: %s\n",
7756                                    map->map_mname, sm_errstring(save_errno));
7757                 CurHostName = p;
7758 # if NETINET6
7759                 if (hp != NULL)
7760                         freehostent(hp);
7761 # endif /* NETINET6 */
7762                 return false;
7763         }
7764 # if NETINET6
7765         if (hp != NULL)
7766         {
7767                 freehostent(hp);
7768                 hp = NULL;
7769         }
7770 # endif /* NETINET6 */
7771         if ((map->map_db1 = (ARBPTR_T) sm_io_open(SmFtStdiofd,
7772                                                   SM_TIME_DEFAULT,
7773                                                   (void *) &sock,
7774                                                   SM_IO_RDWR,
7775                                                   NULL)) == NULL)
7776         {
7777                 close(sock);
7778                 if (tTd(38, 2))
7779                     sm_dprintf("socket_open (%s): failed to create stream: %s\n",
7780                                map->map_mname, sm_errstring(errno));
7781                 return false;
7782         }
7783
7784         /* Save connection for reuse */
7785         s->s_socketmap = map;
7786         return true;
7787 }
7788
7789 /*
7790 **  SOCKET_MAP_FINDCONN -- find a SOCKET connection to the server
7791 **
7792 **      Cache SOCKET connections based on the connection specifier
7793 **      and PID so we don't have multiple connections open to
7794 **      the same server for different maps.  Need a separate connection
7795 **      per PID since a parent process may close the map before the
7796 **      child is done with it.
7797 **
7798 **      Parameters:
7799 **              conn -- SOCKET map connection specifier
7800 **
7801 **      Returns:
7802 **              Symbol table entry for the SOCKET connection.
7803 */
7804
7805 static STAB *
7806 socket_map_findconn(conn)
7807         const char *conn;
7808 {
7809         char *nbuf;
7810         STAB *SM_NONVOLATILE s = NULL;
7811
7812         nbuf = sm_stringf_x("%s%c%d", conn, CONDELSE, (int) CurrentPid);
7813         SM_TRY
7814                 s = stab(nbuf, ST_SOCKETMAP, ST_ENTER);
7815         SM_FINALLY
7816                 sm_free(nbuf);
7817         SM_END_TRY
7818         return s;
7819 }
7820
7821 /*
7822 **  SOCKET_MAP_CLOSE -- close the socket
7823 */
7824
7825 void
7826 socket_map_close(map)
7827         MAP *map;
7828 {
7829         STAB *s;
7830         MAP *smap;
7831
7832         if (tTd(38, 20))
7833                 sm_dprintf("socket_map_close(%s), pid=%ld\n", map->map_file,
7834                         (long) CurrentPid);
7835
7836         /* Check if already closed */
7837         if (map->map_db1 == NULL)
7838         {
7839                 if (tTd(38, 20))
7840                         sm_dprintf("socket_map_close(%s) already closed\n",
7841                                 map->map_file);
7842                 return;
7843         }
7844         sm_io_close((SM_FILE_T *)map->map_db1, SM_TIME_DEFAULT);
7845
7846         /* Mark all the maps that share the connection as closed */
7847         s = socket_map_findconn(map->map_file);
7848         smap = s->s_socketmap;
7849         while (smap != NULL)
7850         {
7851                 MAP *next;
7852
7853                 if (tTd(38, 2) && smap != map)
7854                         sm_dprintf("socket_map_close(%s): closed %s (shared SOCKET connection)\n",
7855                                 map->map_mname, smap->map_mname);
7856
7857                 smap->map_mflags &= ~(MF_OPEN|MF_WRITABLE);
7858                 smap->map_db1 = NULL;
7859                 next = smap->socket_map_next;
7860                 smap->socket_map_next = NULL;
7861                 smap = next;
7862         }
7863         s->s_socketmap = NULL;
7864 }
7865
7866 /*
7867 ** SOCKET_MAP_LOOKUP -- look up a datum in a SOCKET table
7868 */
7869
7870 char *
7871 socket_map_lookup(map, name, av, statp)
7872         MAP *map;
7873         char *name;
7874         char **av;
7875         int *statp;
7876 {
7877         unsigned int nettolen, replylen, recvlen;
7878         char *replybuf, *rval, *value, *status, *key;
7879         SM_FILE_T *f;
7880         char keybuf[MAXNAME + 1];
7881
7882         replybuf = NULL;
7883         rval = NULL;
7884         f = (SM_FILE_T *)map->map_db1;
7885         if (tTd(38, 20))
7886                 sm_dprintf("socket_map_lookup(%s, %s) %s\n",
7887                         map->map_mname, name, map->map_file);
7888
7889         if (!bitset(MF_NOFOLDCASE, map->map_mflags))
7890         {
7891                 nettolen = strlen(name);
7892                 if (nettolen > sizeof(keybuf) - 1)
7893                         nettolen = sizeof(keybuf) - 1;
7894                 memmove(keybuf, name, nettolen);
7895                 keybuf[nettolen] = '\0';
7896                 makelower(keybuf);
7897                 key = keybuf;
7898         }
7899         else
7900                 key = name;
7901
7902         nettolen = strlen(map->map_mname) + 1 + strlen(key);
7903         SM_ASSERT(nettolen > strlen(map->map_mname));
7904         SM_ASSERT(nettolen > strlen(key));
7905         if ((sm_io_fprintf(f, SM_TIME_DEFAULT, "%u:%s %s,",
7906                            nettolen, map->map_mname, key) == SM_IO_EOF) ||
7907             (sm_io_flush(f, SM_TIME_DEFAULT) != 0) ||
7908             (sm_io_error(f)))
7909         {
7910                 syserr("451 4.3.0 socket_map_lookup(%s): failed to send lookup request",
7911                         map->map_mname);
7912                 *statp = EX_TEMPFAIL;
7913                 goto errcl;
7914         }
7915
7916         if (sm_io_fscanf(f, SM_TIME_DEFAULT, "%9u", &replylen) != 1)
7917         {
7918                 syserr("451 4.3.0 socket_map_lookup(%s): failed to read length parameter of reply",
7919                         map->map_mname);
7920                 *statp = EX_TEMPFAIL;
7921                 goto errcl;
7922         }
7923         if (replylen > SOCKETMAP_MAXL)
7924         {
7925                 syserr("451 4.3.0 socket_map_lookup(%s): reply too long: %u",
7926                            map->map_mname, replylen);
7927                 *statp = EX_TEMPFAIL;
7928                 goto errcl;
7929         }
7930         if (sm_io_getc(f, SM_TIME_DEFAULT) != ':')
7931         {
7932                 syserr("451 4.3.0 socket_map_lookup(%s): missing ':' in reply",
7933                         map->map_mname);
7934                 *statp = EX_TEMPFAIL;
7935                 goto error;
7936         }
7937
7938         replybuf = (char *) sm_malloc(replylen + 1);
7939         if (replybuf == NULL)
7940         {
7941                 syserr("451 4.3.0 socket_map_lookup(%s): can't allocate %u bytes",
7942                         map->map_mname, replylen + 1);
7943                 *statp = EX_OSERR;
7944                 goto error;
7945         }
7946
7947         recvlen = sm_io_read(f, SM_TIME_DEFAULT, replybuf, replylen);
7948         if (recvlen < replylen)
7949         {
7950                 syserr("451 4.3.0 socket_map_lookup(%s): received only %u of %u reply characters",
7951                            map->map_mname, recvlen, replylen);
7952                 *statp = EX_TEMPFAIL;
7953                 goto errcl;
7954         }
7955         if (sm_io_getc(f, SM_TIME_DEFAULT) != ',')
7956         {
7957                 syserr("451 4.3.0 socket_map_lookup(%s): missing ',' in reply",
7958                         map->map_mname);
7959                 *statp = EX_TEMPFAIL;
7960                 goto errcl;
7961         }
7962         status = replybuf;
7963         replybuf[recvlen] = '\0';
7964         value = strchr(replybuf, ' ');
7965         if (value != NULL)
7966         {
7967                 *value = '\0';
7968                 value++;
7969         }
7970         if (strcmp(status, "OK") == 0)
7971         {
7972                 *statp = EX_OK;
7973
7974                 /* collect the return value */
7975                 if (bitset(MF_MATCHONLY, map->map_mflags))
7976                         rval = map_rewrite(map, key, strlen(key), NULL);
7977                 else
7978                         rval = map_rewrite(map, value, strlen(value), av);
7979         }
7980         else if (strcmp(status, "NOTFOUND") == 0)
7981         {
7982                 *statp = EX_NOTFOUND;
7983                 if (tTd(38, 20))
7984                         sm_dprintf("socket_map_lookup(%s): %s not found\n",
7985                                 map->map_mname, key);
7986         }
7987         else
7988         {
7989                 if (tTd(38, 5))
7990                         sm_dprintf("socket_map_lookup(%s, %s): server returned error: type=%s, reason=%s\n",
7991                                 map->map_mname, key, status,
7992                                 value ? value : "");
7993                 if ((strcmp(status, "TEMP") == 0) ||
7994                     (strcmp(status, "TIMEOUT") == 0))
7995                         *statp = EX_TEMPFAIL;
7996                 else if(strcmp(status, "PERM") == 0)
7997                         *statp = EX_UNAVAILABLE;
7998                 else
7999                         *statp = EX_PROTOCOL;
8000         }
8001
8002         if (replybuf != NULL)
8003                 sm_free(replybuf);
8004         return rval;
8005
8006   errcl:
8007         socket_map_close(map);
8008   error:
8009         if (replybuf != NULL)
8010                 sm_free(replybuf);
8011         return rval;
8012 }
8013 #endif /* SOCKETMAP */