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 /*
15 * Copyright 1996-2007 Sun Microsystems, Inc. All rights reserved.
16 * Use is subject to license terms.
17 */
18
19 #include <sendmail.h>
20
21 SM_RCSID("@(#)$Id: map.c,v 8.705 2009/08/11 22:22:40 ca Exp $")
22
23 #if LDAPMAP
24 # include <sm/ldap.h>
25 #endif /* LDAPMAP */
26
27 #if NDBM
28 # include <ndbm.h>
29 # ifdef R_FIRST
30 ERROR README: You are running the Berkeley DB version of ndbm.h. See
31 ERROR README: the README file about tweaking Berkeley DB so it can
32 ERROR README: coexist with NDBM, or delete -DNDBM from the Makefile
33 ERROR README: and use -DNEWDB instead.
34 # endif /* R_FIRST */
35 #endif /* NDBM */
36 #if NEWDB
37 # include "sm/bdb.h"
38 #endif /* NEWDB */
39 #if NIS
40 struct dom_binding; /* forward reference needed on IRIX */
41 # include <rpcsvc/ypclnt.h>
42 # if NDBM
43 # define NDBM_YP_COMPAT /* create YP-compatible NDBM files */
44 # endif /* NDBM */
45 #endif /* NIS */
46
47 #include "map.h"
48
49 #if NEWDB
50 # if DB_VERSION_MAJOR < 2
51 static bool db_map_open __P((MAP *, int, char *, DBTYPE, const void *));
52 # endif /* DB_VERSION_MAJOR < 2 */
53 # if DB_VERSION_MAJOR == 2
54 static bool db_map_open __P((MAP *, int, char *, DBTYPE, DB_INFO *));
55 # endif /* DB_VERSION_MAJOR == 2 */
56 # if DB_VERSION_MAJOR > 2
57 static bool db_map_open __P((MAP *, int, char *, DBTYPE, void **));
58 # endif /* DB_VERSION_MAJOR > 2 */
59 #endif /* NEWDB */
60 static bool extract_canonname __P((char *, char *, char *, char[], int));
61 static void map_close __P((STAB *, int));
62 static void map_init __P((STAB *, int));
63 #ifdef LDAPMAP
64 static STAB * ldapmap_findconn __P((SM_LDAP_STRUCT *));
65 #endif /* LDAPMAP */
66 #if NISPLUS
67 static bool nisplus_getcanonname __P((char *, int, int *));
68 #endif /* NISPLUS */
69 #if NIS
70 static bool nis_getcanonname __P((char *, int, int *));
71 #endif /* NIS */
72 #if NETINFO
73 static bool ni_getcanonname __P((char *, int, int *));
74 #endif /* NETINFO */
75 static bool text_getcanonname __P((char *, int, int *));
76 #if SOCKETMAP
77 static STAB *socket_map_findconn __P((const char*));
78
79 /* XXX arbitrary limit for sanity */
80 # define SOCKETMAP_MAXL 1000000
81 #endif /* SOCKETMAP */
82
83 /* default error message for trying to open a map in write mode */
84 #ifdef ENOSYS
85 # define SM_EMAPCANTWRITE ENOSYS
86 #else /* ENOSYS */
87 # ifdef EFTYPE
88 # define SM_EMAPCANTWRITE EFTYPE
89 # else /* EFTYPE */
90 # define SM_EMAPCANTWRITE ENXIO
91 # endif /* EFTYPE */
92 #endif /* ENOSYS */
93
94 /*
95 ** MAP.C -- implementations for various map classes.
96 **
97 ** Each map class implements a series of functions:
98 **
99 ** bool map_parse(MAP *map, char *args)
100 ** Parse the arguments from the config file. Return true
101 ** if they were ok, false otherwise. Fill in map with the
102 ** values.
103 **
104 ** char *map_lookup(MAP *map, char *key, char **args, int *pstat)
105 ** Look up the key in the given map. If found, do any
106 ** rewriting the map wants (including "args" if desired)
107 ** and return the value. Set *pstat to the appropriate status
108 ** on error and return NULL. Args will be NULL if called
109 ** from the alias routines, although this should probably
110 ** not be relied upon. It is suggested you call map_rewrite
111 ** to return the results -- it takes care of null termination
112 ** and uses a dynamically expanded buffer as needed.
113 **
114 ** void map_store(MAP *map, char *key, char *value)
115 ** Store the key:value pair in the map.
116 **
117 ** bool map_open(MAP *map, int mode)
118 ** Open the map for the indicated mode. Mode should
119 ** be either O_RDONLY or O_RDWR. Return true if it
120 ** was opened successfully, false otherwise. If the open
121 ** failed and the MF_OPTIONAL flag is not set, it should
122 ** also print an error. If the MF_ALIAS bit is set
123 ** and this map class understands the @:@ convention, it
124 ** should call aliaswait() before returning.
125 **
126 ** void map_close(MAP *map)
127 ** Close the map.
128 **
129 ** This file also includes the implementation for getcanonname.
130 ** It is currently implemented in a pretty ad-hoc manner; it ought
131 ** to be more properly integrated into the map structure.
132 */
133
134 #if O_EXLOCK && HASFLOCK && !BOGUS_O_EXCL
135 # define LOCK_ON_OPEN 1 /* we can open/create a locked file */
136 #else /* O_EXLOCK && HASFLOCK && !BOGUS_O_EXCL */
137 # define LOCK_ON_OPEN 0 /* no such luck -- bend over backwards */
138 #endif /* O_EXLOCK && HASFLOCK && !BOGUS_O_EXCL */
139
140 /*
141 ** MAP_PARSEARGS -- parse config line arguments for database lookup
142 **
143 ** This is a generic version of the map_parse method.
144 **
145 ** Parameters:
146 ** map -- the map being initialized.
147 ** ap -- a pointer to the args on the config line.
148 **
149 ** Returns:
150 ** true -- if everything parsed OK.
151 ** false -- otherwise.
152 **
153 ** Side Effects:
154 ** null terminates the filename; stores it in map
155 */
156
157 bool
map_parseargs(map,ap)158 map_parseargs(map, ap)
159 MAP *map;
160 char *ap;
161 {
162 register char *p = ap;
163
164 /*
165 ** There is no check whether there is really an argument,
166 ** but that's not important enough to warrant extra code.
167 */
168
169 map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL;
170 map->map_spacesub = SpaceSub; /* default value */
171 for (;;)
172 {
173 while (isascii(*p) && isspace(*p))
174 p++;
175 if (*p != '-')
176 break;
177 switch (*++p)
178 {
179 case 'N':
180 map->map_mflags |= MF_INCLNULL;
181 map->map_mflags &= ~MF_TRY0NULL;
182 break;
183
184 case 'O':
185 map->map_mflags &= ~MF_TRY1NULL;
186 break;
187
188 case 'o':
189 map->map_mflags |= MF_OPTIONAL;
190 break;
191
192 case 'f':
193 map->map_mflags |= MF_NOFOLDCASE;
194 break;
195
196 case 'm':
197 map->map_mflags |= MF_MATCHONLY;
198 break;
199
200 case 'A':
201 map->map_mflags |= MF_APPEND;
202 break;
203
204 case 'q':
205 map->map_mflags |= MF_KEEPQUOTES;
206 break;
207
208 case 'a':
209 map->map_app = ++p;
210 break;
211
212 case 'T':
213 map->map_tapp = ++p;
214 break;
215
216 case 'k':
217 while (isascii(*++p) && isspace(*p))
218 continue;
219 map->map_keycolnm = p;
220 break;
221
222 case 'v':
223 while (isascii(*++p) && isspace(*p))
224 continue;
225 map->map_valcolnm = p;
226 break;
227
228 case 'z':
229 if (*++p != '\\')
230 map->map_coldelim = *p;
231 else
232 {
233 switch (*++p)
234 {
235 case 'n':
236 map->map_coldelim = '\n';
237 break;
238
239 case 't':
240 map->map_coldelim = '\t';
241 break;
242
243 default:
244 map->map_coldelim = '\\';
245 }
246 }
247 break;
248
249 case 't':
250 map->map_mflags |= MF_NODEFER;
251 break;
252
253
254 case 'S':
255 map->map_spacesub = *++p;
256 break;
257
258 case 'D':
259 map->map_mflags |= MF_DEFER;
260 break;
261
262 default:
263 syserr("Illegal option %c map %s", *p, map->map_mname);
264 break;
265 }
266 while (*p != '\0' && !(isascii(*p) && isspace(*p)))
267 p++;
268 if (*p != '\0')
269 *p++ = '\0';
270 }
271 if (map->map_app != NULL)
272 map->map_app = newstr(map->map_app);
273 if (map->map_tapp != NULL)
274 map->map_tapp = newstr(map->map_tapp);
275 if (map->map_keycolnm != NULL)
276 map->map_keycolnm = newstr(map->map_keycolnm);
277 if (map->map_valcolnm != NULL)
278 map->map_valcolnm = newstr(map->map_valcolnm);
279
280 if (*p != '\0')
281 {
282 map->map_file = p;
283 while (*p != '\0' && !(isascii(*p) && isspace(*p)))
284 p++;
285 if (*p != '\0')
286 *p++ = '\0';
287 map->map_file = newstr(map->map_file);
288 }
289
290 while (*p != '\0' && isascii(*p) && isspace(*p))
291 p++;
292 if (*p != '\0')
293 map->map_rebuild = newstr(p);
294
295 if (map->map_file == NULL &&
296 !bitset(MCF_OPTFILE, map->map_class->map_cflags))
297 {
298 syserr("No file name for %s map %s",
299 map->map_class->map_cname, map->map_mname);
300 return false;
301 }
302 return true;
303 }
304 /*
305 ** MAP_REWRITE -- rewrite a database key, interpolating %n indications.
306 **
307 ** It also adds the map_app string. It can be used as a utility
308 ** in the map_lookup method.
309 **
310 ** Parameters:
311 ** map -- the map that causes this.
312 ** s -- the string to rewrite, NOT necessarily null terminated.
313 ** slen -- the length of s.
314 ** av -- arguments to interpolate into buf.
315 **
316 ** Returns:
317 ** Pointer to rewritten result. This is static data that
318 ** should be copied if it is to be saved!
319 */
320
321 char *
map_rewrite(map,s,slen,av)322 map_rewrite(map, s, slen, av)
323 register MAP *map;
324 register const char *s;
325 size_t slen;
326 char **av;
327 {
328 register char *bp;
329 register char c;
330 char **avp;
331 register char *ap;
332 size_t l;
333 size_t len;
334 static size_t buflen = 0;
335 static char *buf = NULL;
336
337 if (tTd(39, 1))
338 {
339 sm_dprintf("map_rewrite(%.*s), av =", (int) slen, s);
340 if (av == NULL)
341 sm_dprintf(" (nullv)");
342 else
343 {
344 for (avp = av; *avp != NULL; avp++)
345 sm_dprintf("\n\t%s", *avp);
346 }
347 sm_dprintf("\n");
348 }
349
350 /* count expected size of output (can safely overestimate) */
351 l = len = slen;
352 if (av != NULL)
353 {
354 const char *sp = s;
355
356 while (l-- > 0 && (c = *sp++) != '\0')
357 {
358 if (c != '%')
359 continue;
360 if (l-- <= 0)
361 break;
362 c = *sp++;
363 if (!(isascii(c) && isdigit(c)))
364 continue;
365 for (avp = av; --c >= '0' && *avp != NULL; avp++)
366 continue;
367 if (*avp == NULL)
368 continue;
369 len += strlen(*avp);
370 }
371 }
372 if (map->map_app != NULL)
373 len += strlen(map->map_app);
374 if (buflen < ++len)
375 {
376 /* need to malloc additional space */
377 buflen = len;
378 if (buf != NULL)
379 sm_free(buf);
380 buf = sm_pmalloc_x(buflen);
381 }
382
383 bp = buf;
384 if (av == NULL)
385 {
386 memmove(bp, s, slen);
387 bp += slen;
388
389 /* assert(len > slen); */
390 len -= slen;
391 }
392 else
393 {
394 while (slen-- > 0 && (c = *s++) != '\0')
395 {
396 if (c != '%')
397 {
398 pushc:
399 if (len-- <= 1)
400 break;
401 *bp++ = c;
402 continue;
403 }
404 if (slen-- <= 0 || (c = *s++) == '\0')
405 c = '%';
406 if (c == '%')
407 goto pushc;
408 if (!(isascii(c) && isdigit(c)))
409 {
410 if (len-- <= 1)
411 break;
412 *bp++ = '%';
413 goto pushc;
414 }
415 for (avp = av; --c >= '0' && *avp != NULL; avp++)
416 continue;
417 if (*avp == NULL)
418 continue;
419
420 /* transliterate argument into output string */
421 for (ap = *avp; (c = *ap++) != '\0' && len > 0; --len)
422 *bp++ = c;
423 }
424 }
425 if (map->map_app != NULL && len > 0)
426 (void) sm_strlcpy(bp, map->map_app, len);
427 else
428 *bp = '\0';
429 if (tTd(39, 1))
430 sm_dprintf("map_rewrite => %s\n", buf);
431 return buf;
432 }
433 /*
434 ** INITMAPS -- rebuild alias maps
435 **
436 ** Parameters:
437 ** none.
438 **
439 ** Returns:
440 ** none.
441 */
442
443 void
initmaps()444 initmaps()
445 {
446 #if XDEBUG
447 checkfd012("entering initmaps");
448 #endif /* XDEBUG */
449 stabapply(map_init, 0);
450 #if XDEBUG
451 checkfd012("exiting initmaps");
452 #endif /* XDEBUG */
453 }
454 /*
455 ** MAP_INIT -- rebuild a map
456 **
457 ** Parameters:
458 ** s -- STAB entry: if map: try to rebuild
459 ** unused -- unused variable
460 **
461 ** Returns:
462 ** none.
463 **
464 ** Side Effects:
465 ** will close already open rebuildable map.
466 */
467
468 /* ARGSUSED1 */
469 static void
map_init(s,unused)470 map_init(s, unused)
471 register STAB *s;
472 int unused;
473 {
474 register MAP *map;
475
476 /* has to be a map */
477 if (s->s_symtype != ST_MAP)
478 return;
479
480 map = &s->s_map;
481 if (!bitset(MF_VALID, map->map_mflags))
482 return;
483
484 if (tTd(38, 2))
485 sm_dprintf("map_init(%s:%s, %s)\n",
486 map->map_class->map_cname == NULL ? "NULL" :
487 map->map_class->map_cname,
488 map->map_mname == NULL ? "NULL" : map->map_mname,
489 map->map_file == NULL ? "NULL" : map->map_file);
490
491 if (!bitset(MF_ALIAS, map->map_mflags) ||
492 !bitset(MCF_REBUILDABLE, map->map_class->map_cflags))
493 {
494 if (tTd(38, 3))
495 sm_dprintf("\tnot rebuildable\n");
496 return;
497 }
498
499 /* if already open, close it (for nested open) */
500 if (bitset(MF_OPEN, map->map_mflags))
501 {
502 map->map_mflags |= MF_CLOSING;
503 map->map_class->map_close(map);
504 map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
505 }
506
507 (void) rebuildaliases(map, false);
508 return;
509 }
510 /*
511 ** OPENMAP -- open a map
512 **
513 ** Parameters:
514 ** map -- map to open (it must not be open).
515 **
516 ** Returns:
517 ** whether open succeeded.
518 */
519
520 bool
openmap(map)521 openmap(map)
522 MAP *map;
523 {
524 bool restore = false;
525 bool savehold = HoldErrs;
526 bool savequick = QuickAbort;
527 int saveerrors = Errors;
528
529 if (!bitset(MF_VALID, map->map_mflags))
530 return false;
531
532 /* better safe than sorry... */
533 if (bitset(MF_OPEN, map->map_mflags))
534 return true;
535
536 /* Don't send a map open error out via SMTP */
537 if ((OnlyOneError || QuickAbort) &&
538 (OpMode == MD_SMTP || OpMode == MD_DAEMON))
539 {
540 restore = true;
541 HoldErrs = true;
542 QuickAbort = false;
543 }
544
545 errno = 0;
546 if (map->map_class->map_open(map, O_RDONLY))
547 {
548 if (tTd(38, 4))
549 sm_dprintf("openmap()\t%s:%s %s: valid\n",
550 map->map_class->map_cname == NULL ? "NULL" :
551 map->map_class->map_cname,
552 map->map_mname == NULL ? "NULL" :
553 map->map_mname,
554 map->map_file == NULL ? "NULL" :
555 map->map_file);
556 map->map_mflags |= MF_OPEN;
557 map->map_pid = CurrentPid;
558 }
559 else
560 {
561 if (tTd(38, 4))
562 sm_dprintf("openmap()\t%s:%s %s: invalid%s%s\n",
563 map->map_class->map_cname == NULL ? "NULL" :
564 map->map_class->map_cname,
565 map->map_mname == NULL ? "NULL" :
566 map->map_mname,
567 map->map_file == NULL ? "NULL" :
568 map->map_file,
569 errno == 0 ? "" : ": ",
570 errno == 0 ? "" : sm_errstring(errno));
571 if (!bitset(MF_OPTIONAL, map->map_mflags))
572 {
573 extern MAPCLASS BogusMapClass;
574
575 map->map_orgclass = map->map_class;
576 map->map_class = &BogusMapClass;
577 map->map_mflags |= MF_OPEN|MF_OPENBOGUS;
578 map->map_pid = CurrentPid;
579 }
580 else
581 {
582 /* don't try again */
583 map->map_mflags &= ~MF_VALID;
584 }
585 }
586
587 if (restore)
588 {
589 Errors = saveerrors;
590 HoldErrs = savehold;
591 QuickAbort = savequick;
592 }
593
594 return bitset(MF_OPEN, map->map_mflags);
595 }
596 /*
597 ** CLOSEMAPS -- close all open maps opened by the current pid.
598 **
599 ** Parameters:
600 ** bogus -- only close bogus maps.
601 **
602 ** Returns:
603 ** none.
604 */
605
606 void
closemaps(bogus)607 closemaps(bogus)
608 bool bogus;
609 {
610 stabapply(map_close, bogus);
611 }
612 /*
613 ** MAP_CLOSE -- close a map opened by the current pid.
614 **
615 ** Parameters:
616 ** s -- STAB entry: if map: try to close
617 ** bogus -- only close bogus maps or MCF_NOTPERSIST maps.
618 **
619 ** Returns:
620 ** none.
621 */
622
623 /* ARGSUSED1 */
624 static void
map_close(s,bogus)625 map_close(s, bogus)
626 register STAB *s;
627 int bogus; /* int because of stabapply(), used as bool */
628 {
629 MAP *map;
630 extern MAPCLASS BogusMapClass;
631
632 if (s->s_symtype != ST_MAP)
633 return;
634
635 map = &s->s_map;
636
637 /*
638 ** close the map iff:
639 ** it is valid and open and opened by this process
640 ** and (!bogus or it's a bogus map or it is not persistent)
641 ** negate this: return iff
642 ** it is not valid or it is not open or not opened by this process
643 ** or (bogus and it's not a bogus map and it's not not-persistent)
644 */
645
646 if (!bitset(MF_VALID, map->map_mflags) ||
647 !bitset(MF_OPEN, map->map_mflags) ||
648 bitset(MF_CLOSING, map->map_mflags) ||
649 map->map_pid != CurrentPid ||
650 (bogus && map->map_class != &BogusMapClass &&
651 !bitset(MCF_NOTPERSIST, map->map_class->map_cflags)))
652 return;
653
654 if (map->map_class == &BogusMapClass && map->map_orgclass != NULL &&
655 map->map_orgclass != &BogusMapClass)
656 map->map_class = map->map_orgclass;
657 if (tTd(38, 5))
658 sm_dprintf("closemaps: closing %s (%s)\n",
659 map->map_mname == NULL ? "NULL" : map->map_mname,
660 map->map_file == NULL ? "NULL" : map->map_file);
661
662 if (!bitset(MF_OPENBOGUS, map->map_mflags))
663 {
664 map->map_mflags |= MF_CLOSING;
665 map->map_class->map_close(map);
666 }
667 map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_OPENBOGUS|MF_CLOSING);
668 }
669
670 #if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN)
671 extern int getdomainname();
672
673 /* this is mainly for backward compatibility in Sun environment */
674 static char *
sun_init_domain()675 sun_init_domain()
676 {
677 /*
678 ** Get the domain name from the kernel.
679 ** If it does not start with a leading dot, then remove
680 ** the first component. Since leading dots are funny Unix
681 ** files, we treat a leading "+" the same as a leading dot.
682 ** Finally, force there to be at least one dot in the domain name
683 ** (i.e. top-level domains are not allowed, like "com", must be
684 ** something like "sun.com").
685 */
686
687 char buf[MAXNAME];
688 char *period, *autodomain;
689
690 if (getdomainname(buf, sizeof buf) < 0)
691 return NULL;
692
693 if (buf[0] == '\0')
694 return NULL;
695
696 if (tTd(0, 20))
697 printf("domainname = %s\n", buf);
698
699 if (buf[0] == '+')
700 buf[0] = '.';
701 period = strchr(buf, '.');
702 if (period == NULL)
703 autodomain = buf;
704 else
705 autodomain = period + 1;
706 if (strchr(autodomain, '.') == NULL)
707 return newstr(buf);
708 else
709 return newstr(autodomain);
710 }
711 #endif /* SUN_EXTENSIONS && SUN_INIT_DOMAIN */
712
713 /*
714 ** GETCANONNAME -- look up name using service switch
715 **
716 ** Parameters:
717 ** host -- the host name to look up.
718 ** hbsize -- the size of the host buffer.
719 ** trymx -- if set, try MX records.
720 ** pttl -- pointer to return TTL (can be NULL).
721 **
722 ** Returns:
723 ** true -- if the host was found.
724 ** false -- otherwise.
725 */
726
727 bool
getcanonname(host,hbsize,trymx,pttl)728 getcanonname(host, hbsize, trymx, pttl)
729 char *host;
730 int hbsize;
731 bool trymx;
732 int *pttl;
733 {
734 int nmaps;
735 int mapno;
736 bool found = false;
737 bool got_tempfail = false;
738 auto int status = EX_UNAVAILABLE;
739 char *maptype[MAXMAPSTACK];
740 short mapreturn[MAXMAPACTIONS];
741 #if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN)
742 bool should_try_nis_domain = false;
743 static char *nis_domain = NULL;
744 #endif
745
746 nmaps = switch_map_find("hosts", maptype, mapreturn);
747 if (pttl != 0)
748 *pttl = SM_DEFAULT_TTL;
749 for (mapno = 0; mapno < nmaps; mapno++)
750 {
751 int i;
752
753 if (tTd(38, 20))
754 sm_dprintf("getcanonname(%s), trying %s\n",
755 host, maptype[mapno]);
756 if (strcmp("files", maptype[mapno]) == 0)
757 {
758 found = text_getcanonname(host, hbsize, &status);
759 }
760 #if NIS
761 else if (strcmp("nis", maptype[mapno]) == 0)
762 {
763 found = nis_getcanonname(host, hbsize, &status);
764 # if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN)
765 if (nis_domain == NULL)
766 nis_domain = sun_init_domain();
767 # endif /* defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN) */
768 }
769 #endif /* NIS */
770 #if NISPLUS
771 else if (strcmp("nisplus", maptype[mapno]) == 0)
772 {
773 found = nisplus_getcanonname(host, hbsize, &status);
774 # if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN)
775 if (nis_domain == NULL)
776 nis_domain = sun_init_domain();
777 # endif /* defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN) */
778 }
779 #endif /* NISPLUS */
780 #if NAMED_BIND
781 else if (strcmp("dns", maptype[mapno]) == 0)
782 {
783 found = dns_getcanonname(host, hbsize, trymx, &status, pttl);
784 }
785 #endif /* NAMED_BIND */
786 #if NETINFO
787 else if (strcmp("netinfo", maptype[mapno]) == 0)
788 {
789 found = ni_getcanonname(host, hbsize, &status);
790 }
791 #endif /* NETINFO */
792 else
793 {
794 found = false;
795 status = EX_UNAVAILABLE;
796 }
797
798 /*
799 ** Heuristic: if $m is not set, we are running during system
800 ** startup. In this case, when a name is apparently found
801 ** but has no dot, treat is as not found. This avoids
802 ** problems if /etc/hosts has no FQDN but is listed first
803 ** in the service switch.
804 */
805
806 if (found &&
807 (macvalue('m', CurEnv) != NULL || strchr(host, '.') != NULL))
808 break;
809
810 #if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN)
811 if (found)
812 should_try_nis_domain = true;
813 /* but don't break, as we need to try all methods first */
814 #endif /* defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN) */
815
816 /* see if we should continue */
817 if (status == EX_TEMPFAIL)
818 {
819 i = MA_TRYAGAIN;
820 got_tempfail = true;
821 }
822 else if (status == EX_NOTFOUND)
823 i = MA_NOTFOUND;
824 else
825 i = MA_UNAVAIL;
826 if (bitset(1 << mapno, mapreturn[i]))
827 break;
828 }
829
830 if (found)
831 {
832 char *d;
833
834 if (tTd(38, 20))
835 sm_dprintf("getcanonname(%s), found\n", host);
836
837 /*
838 ** If returned name is still single token, compensate
839 ** by tagging on $m. This is because some sites set
840 ** up their DNS or NIS databases wrong.
841 */
842
843 if ((d = strchr(host, '.')) == NULL || d[1] == '\0')
844 {
845 d = macvalue('m', CurEnv);
846 if (d != NULL &&
847 hbsize > (int) (strlen(host) + strlen(d) + 1))
848 {
849 if (host[strlen(host) - 1] != '.')
850 (void) sm_strlcat2(host, ".", d,
851 hbsize);
852 else
853 (void) sm_strlcat(host, d, hbsize);
854 }
855 else
856 {
857 #if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN)
858 if (VendorCode == VENDOR_SUN &&
859 should_try_nis_domain)
860 {
861 goto try_nis_domain;
862 }
863 #endif /* defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN) */
864 return false;
865 }
866 }
867 return true;
868 }
869
870 #if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN)
871 if (VendorCode == VENDOR_SUN && should_try_nis_domain)
872 {
873 try_nis_domain:
874 if (nis_domain != NULL &&
875 strlen(nis_domain) + strlen(host) + 1 < hbsize)
876 {
877 (void) sm_strlcat2(host, ".", nis_domain, hbsize);
878 return true;
879 }
880 }
881 #endif /* defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN) */
882
883 if (tTd(38, 20))
884 sm_dprintf("getcanonname(%s), failed, status=%d\n", host,
885 status);
886
887 if (got_tempfail)
888 SM_SET_H_ERRNO(TRY_AGAIN);
889 else
890 SM_SET_H_ERRNO(HOST_NOT_FOUND);
891
892 return false;
893 }
894 /*
895 ** EXTRACT_CANONNAME -- extract canonical name from /etc/hosts entry
896 **
897 ** Parameters:
898 ** name -- the name against which to match.
899 ** dot -- where to reinsert '.' to get FQDN
900 ** line -- the /etc/hosts line.
901 ** cbuf -- the location to store the result.
902 ** cbuflen -- the size of cbuf.
903 **
904 ** Returns:
905 ** true -- if the line matched the desired name.
906 ** false -- otherwise.
907 */
908
909 static bool
extract_canonname(name,dot,line,cbuf,cbuflen)910 extract_canonname(name, dot, line, cbuf, cbuflen)
911 char *name;
912 char *dot;
913 char *line;
914 char cbuf[];
915 int cbuflen;
916 {
917 int i;
918 char *p;
919 bool found = false;
920
921 cbuf[0] = '\0';
922 if (line[0] == '#')
923 return false;
924
925 for (i = 1; ; i++)
926 {
927 char nbuf[MAXNAME + 1];
928
929 p = get_column(line, i, '\0', nbuf, sizeof(nbuf));
930 if (p == NULL)
931 break;
932 if (*p == '\0')
933 continue;
934 if (cbuf[0] == '\0' ||
935 (strchr(cbuf, '.') == NULL && strchr(p, '.') != NULL))
936 {
937 (void) sm_strlcpy(cbuf, p, cbuflen);
938 }
939 if (sm_strcasecmp(name, p) == 0)
940 found = true;
941 else if (dot != NULL)
942 {
943 /* try looking for the FQDN as well */
944 *dot = '.';
945 if (sm_strcasecmp(name, p) == 0)
946 found = true;
947 *dot = '\0';
948 }
949 }
950 if (found && strchr(cbuf, '.') == NULL)
951 {
952 /* try to add a domain on the end of the name */
953 char *domain = macvalue('m', CurEnv);
954
955 if (domain != NULL &&
956 strlen(domain) + (i = strlen(cbuf)) + 1 < (size_t) cbuflen)
957 {
958 p = &cbuf[i];
959 *p++ = '.';
960 (void) sm_strlcpy(p, domain, cbuflen - i - 1);
961 }
962 }
963 return found;
964 }
965
966 /*
967 ** DNS modules
968 */
969
970 #if NAMED_BIND
971 # if DNSMAP
972
973 # include "sm_resolve.h"
974 # if NETINET || NETINET6
975 # include <arpa/inet.h>
976 # endif /* NETINET || NETINET6 */
977
978 /*
979 ** DNS_MAP_OPEN -- stub to check proper value for dns map type
980 */
981
982 bool
dns_map_open(map,mode)983 dns_map_open(map, mode)
984 MAP *map;
985 int mode;
986 {
987 if (tTd(38,2))
988 sm_dprintf("dns_map_open(%s, %d)\n", map->map_mname, mode);
989
990 mode &= O_ACCMODE;
991 if (mode != O_RDONLY)
992 {
993 /* issue a pseudo-error message */
994 errno = SM_EMAPCANTWRITE;
995 return false;
996 }
997 return true;
998 }
999
1000 /*
1001 ** DNS_MAP_PARSEARGS -- parse dns map definition args.
1002 **
1003 ** Parameters:
1004 ** map -- pointer to MAP
1005 ** args -- pointer to the args on the config line.
1006 **
1007 ** Returns:
1008 ** true -- if everything parsed OK.
1009 ** false -- otherwise.
1010 */
1011
1012 #define map_sizelimit map_lockfd /* overload field */
1013
1014 struct dns_map
1015 {
1016 int dns_m_type;
1017 };
1018
1019 bool
dns_map_parseargs(map,args)1020 dns_map_parseargs(map,args)
1021 MAP *map;
1022 char *args;
1023 {
1024 register char *p = args;
1025 struct dns_map *map_p;
1026
1027 map_p = (struct dns_map *) xalloc(sizeof(*map_p));
1028 map_p->dns_m_type = -1;
1029 map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL;
1030
1031 for (;;)
1032 {
1033 while (isascii(*p) && isspace(*p))
1034 p++;
1035 if (*p != '-')
1036 break;
1037 switch (*++p)
1038 {
1039 case 'N':
1040 map->map_mflags |= MF_INCLNULL;
1041 map->map_mflags &= ~MF_TRY0NULL;
1042 break;
1043
1044 case 'O':
1045 map->map_mflags &= ~MF_TRY1NULL;
1046 break;
1047
1048 case 'o':
1049 map->map_mflags |= MF_OPTIONAL;
1050 break;
1051
1052 case 'f':
1053 map->map_mflags |= MF_NOFOLDCASE;
1054 break;
1055
1056 case 'm':
1057 map->map_mflags |= MF_MATCHONLY;
1058 break;
1059
1060 case 'A':
1061 map->map_mflags |= MF_APPEND;
1062 break;
1063
1064 case 'q':
1065 map->map_mflags |= MF_KEEPQUOTES;
1066 break;
1067
1068 case 't':
1069 map->map_mflags |= MF_NODEFER;
1070 break;
1071
1072 case 'a':
1073 map->map_app = ++p;
1074 break;
1075
1076 case 'T':
1077 map->map_tapp = ++p;
1078 break;
1079
1080 case 'd':
1081 {
1082 char *h;
1083
1084 ++p;
1085 h = strchr(p, ' ');
1086 if (h != NULL)
1087 *h = '\0';
1088 map->map_timeout = convtime(p, 's');
1089 if (h != NULL)
1090 *h = ' ';
1091 }
1092 break;
1093
1094 case 'r':
1095 while (isascii(*++p) && isspace(*p))
1096 continue;
1097 map->map_retry = atoi(p);
1098 break;
1099
1100 case 'z':
1101 if (*++p != '\\')
1102 map->map_coldelim = *p;
1103 else
1104 {
1105 switch (*++p)
1106 {
1107 case 'n':
1108 map->map_coldelim = '\n';
1109 break;
1110
1111 case 't':
1112 map->map_coldelim = '\t';
1113 break;
1114
1115 default:
1116 map->map_coldelim = '\\';
1117 }
1118 }
1119 break;
1120
1121 case 'Z':
1122 while (isascii(*++p) && isspace(*p))
1123 continue;
1124 map->map_sizelimit = atoi(p);
1125 break;
1126
1127 /* Start of dns_map specific args */
1128 case 'R': /* search field */
1129 {
1130 char *h;
1131
1132 while (isascii(*++p) && isspace(*p))
1133 continue;
1134 h = strchr(p, ' ');
1135 if (h != NULL)
1136 *h = '\0';
1137 map_p->dns_m_type = dns_string_to_type(p);
1138 if (h != NULL)
1139 *h = ' ';
1140 if (map_p->dns_m_type < 0)
1141 syserr("dns map %s: wrong type %s",
1142 map->map_mname, p);
1143 }
1144 break;
1145
1146 case 'B': /* base domain */
1147 {
1148 char *h;
1149
1150 while (isascii(*++p) && isspace(*p))
1151 continue;
1152 h = strchr(p, ' ');
1153 if (h != NULL)
1154 *h = '\0';
1155
1156 /*
1157 ** slight abuse of map->map_file; it isn't
1158 ** used otherwise in this map type.
1159 */
1160
1161 map->map_file = newstr(p);
1162 if (h != NULL)
1163 *h = ' ';
1164 }
1165 break;
1166 }
1167 while (*p != '\0' && !(isascii(*p) && isspace(*p)))
1168 p++;
1169 if (*p != '\0')
1170 *p++ = '\0';
1171 }
1172 if (map_p->dns_m_type < 0)
1173 syserr("dns map %s: missing -R type", map->map_mname);
1174 if (map->map_app != NULL)
1175 map->map_app = newstr(map->map_app);
1176 if (map->map_tapp != NULL)
1177 map->map_tapp = newstr(map->map_tapp);
1178
1179 /*
1180 ** Assumption: assert(sizeof(int) <= sizeof(ARBPTR_T));
1181 ** Even if this assumption is wrong, we use only one byte,
1182 ** so it doesn't really matter.
1183 */
1184
1185 map->map_db1 = (ARBPTR_T) map_p;
1186 return true;
1187 }
1188
1189 /*
1190 ** DNS_MAP_LOOKUP -- perform dns map lookup.
1191 **
1192 ** Parameters:
1193 ** map -- pointer to MAP
1194 ** name -- name to lookup
1195 ** av -- arguments to interpolate into buf.
1196 ** statp -- pointer to status (EX_)
1197 **
1198 ** Returns:
1199 ** result of lookup if succeeded.
1200 ** NULL -- otherwise.
1201 */
1202
1203 char *
dns_map_lookup(map,name,av,statp)1204 dns_map_lookup(map, name, av, statp)
1205 MAP *map;
1206 char *name;
1207 char **av;
1208 int *statp;
1209 {
1210 int resnum = 0;
1211 char *vp = NULL, *result = NULL;
1212 size_t vsize;
1213 struct dns_map *map_p;
1214 RESOURCE_RECORD_T *rr = NULL;
1215 DNS_REPLY_T *r = NULL;
1216 # if NETINET6
1217 static char buf6[INET6_ADDRSTRLEN];
1218 # endif /* NETINET6 */
1219
1220 if (tTd(38, 20))
1221 sm_dprintf("dns_map_lookup(%s, %s)\n",
1222 map->map_mname, name);
1223
1224 map_p = (struct dns_map *)(map->map_db1);
1225 if (map->map_file != NULL && *map->map_file != '\0')
1226 {
1227 size_t len;
1228 char *appdomain;
1229
1230 len = strlen(map->map_file) + strlen(name) + 2;
1231 appdomain = (char *) sm_malloc(len);
1232 if (appdomain == NULL)
1233 {
1234 *statp = EX_UNAVAILABLE;
1235 return NULL;
1236 }
1237 (void) sm_strlcpyn(appdomain, len, 3, name, ".", map->map_file);
1238 r = dns_lookup_int(appdomain, C_IN, map_p->dns_m_type,
1239 map->map_timeout, map->map_retry);
1240 sm_free(appdomain);
1241 }
1242 else
1243 {
1244 r = dns_lookup_int(name, C_IN, map_p->dns_m_type,
1245 map->map_timeout, map->map_retry);
1246 }
1247
1248 if (r == NULL)
1249 {
1250 result = NULL;
1251 if (h_errno == TRY_AGAIN || transienterror(errno))
1252 *statp = EX_TEMPFAIL;
1253 else
1254 *statp = EX_NOTFOUND;
1255 goto cleanup;
1256 }
1257 *statp = EX_OK;
1258 for (rr = r->dns_r_head; rr != NULL; rr = rr->rr_next)
1259 {
1260 char *type = NULL;
1261 char *value = NULL;
1262
1263 switch (rr->rr_type)
1264 {
1265 case T_NS:
1266 type = "T_NS";
1267 value = rr->rr_u.rr_txt;
1268 break;
1269 case T_CNAME:
1270 type = "T_CNAME";
1271 value = rr->rr_u.rr_txt;
1272 break;
1273 case T_AFSDB:
1274 type = "T_AFSDB";
1275 value = rr->rr_u.rr_mx->mx_r_domain;
1276 break;
1277 case T_SRV:
1278 type = "T_SRV";
1279 value = rr->rr_u.rr_srv->srv_r_target;
1280 break;
1281 case T_PTR:
1282 type = "T_PTR";
1283 value = rr->rr_u.rr_txt;
1284 break;
1285 case T_TXT:
1286 type = "T_TXT";
1287 value = rr->rr_u.rr_txt;
1288 break;
1289 case T_MX:
1290 type = "T_MX";
1291 value = rr->rr_u.rr_mx->mx_r_domain;
1292 break;
1293 # if NETINET
1294 case T_A:
1295 type = "T_A";
1296 value = inet_ntoa(*(rr->rr_u.rr_a));
1297 break;
1298 # endif /* NETINET */
1299 # if NETINET6
1300 case T_AAAA:
1301 type = "T_AAAA";
1302 value = anynet_ntop(rr->rr_u.rr_aaaa, buf6,
1303 sizeof(buf6));
1304 break;
1305 # endif /* NETINET6 */
1306 }
1307
1308 (void) strreplnonprt(value, 'X');
1309 if (map_p->dns_m_type != rr->rr_type)
1310 {
1311 if (tTd(38, 40))
1312 sm_dprintf("\tskipping type %s (%d) value %s\n",
1313 type != NULL ? type : "<UNKNOWN>",
1314 rr->rr_type,
1315 value != NULL ? value : "<NO VALUE>");
1316 continue;
1317 }
1318
1319 # if NETINET6
1320 if (rr->rr_type == T_AAAA && value == NULL)
1321 {
1322 result = NULL;
1323 *statp = EX_DATAERR;
1324 if (tTd(38, 40))
1325 sm_dprintf("\tbad T_AAAA conversion\n");
1326 goto cleanup;
1327 }
1328 # endif /* NETINET6 */
1329 if (tTd(38, 40))
1330 sm_dprintf("\tfound type %s (%d) value %s\n",
1331 type != NULL ? type : "<UNKNOWN>",
1332 rr->rr_type,
1333 value != NULL ? value : "<NO VALUE>");
1334 if (value != NULL &&
1335 (map->map_coldelim == '\0' ||
1336 map->map_sizelimit == 1 ||
1337 bitset(MF_MATCHONLY, map->map_mflags)))
1338 {
1339 /* Only care about the first match */
1340 vp = newstr(value);
1341 break;
1342 }
1343 else if (vp == NULL)
1344 {
1345 /* First result */
1346 vp = newstr(value);
1347 }
1348 else
1349 {
1350 /* concatenate the results */
1351 int sz;
1352 char *new;
1353
1354 sz = strlen(vp) + strlen(value) + 2;
1355 new = xalloc(sz);
1356 (void) sm_snprintf(new, sz, "%s%c%s",
1357 vp, map->map_coldelim, value);
1358 sm_free(vp);
1359 vp = new;
1360 if (map->map_sizelimit > 0 &&
1361 ++resnum >= map->map_sizelimit)
1362 break;
1363 }
1364 }
1365 if (vp == NULL)
1366 {
1367 result = NULL;
1368 *statp = EX_NOTFOUND;
1369 if (tTd(38, 40))
1370 sm_dprintf("\tno match found\n");
1371 goto cleanup;
1372 }
1373
1374 /* Cleanly truncate for rulesets */
1375 truncate_at_delim(vp, PSBUFSIZE / 2, map->map_coldelim);
1376
1377 vsize = strlen(vp);
1378
1379 if (LogLevel > 9)
1380 sm_syslog(LOG_INFO, CurEnv->e_id, "dns %.100s => %s",
1381 name, vp);
1382 if (bitset(MF_MATCHONLY, map->map_mflags))
1383 result = map_rewrite(map, name, strlen(name), NULL);
1384 else
1385 result = map_rewrite(map, vp, vsize, av);
1386
1387 cleanup:
1388 if (vp != NULL)
1389 sm_free(vp);
1390 if (r != NULL)
1391 dns_free_data(r);
1392 return result;
1393 }
1394 # endif /* DNSMAP */
1395 #endif /* NAMED_BIND */
1396
1397 /*
1398 ** NDBM modules
1399 */
1400
1401 #if NDBM
1402
1403 /*
1404 ** NDBM_MAP_OPEN -- DBM-style map open
1405 */
1406
1407 bool
ndbm_map_open(map,mode)1408 ndbm_map_open(map, mode)
1409 MAP *map;
1410 int mode;
1411 {
1412 register DBM *dbm;
1413 int save_errno;
1414 int dfd;
1415 int pfd;
1416 long sff;
1417 int ret;
1418 int smode = S_IREAD;
1419 char dirfile[MAXPATHLEN];
1420 char pagfile[MAXPATHLEN];
1421 struct stat st;
1422 struct stat std, stp;
1423
1424 if (tTd(38, 2))
1425 sm_dprintf("ndbm_map_open(%s, %s, %d)\n",
1426 map->map_mname, map->map_file, mode);
1427 map->map_lockfd = -1;
1428 mode &= O_ACCMODE;
1429
1430 /* do initial file and directory checks */
1431 if (sm_strlcpyn(dirfile, sizeof(dirfile), 2,
1432 map->map_file, ".dir") >= sizeof(dirfile) ||
1433 sm_strlcpyn(pagfile, sizeof(pagfile), 2,
1434 map->map_file, ".pag") >= sizeof(pagfile))
1435 {
1436 errno = 0;
1437 if (!bitset(MF_OPTIONAL, map->map_mflags))
1438 syserr("dbm map \"%s\": map file %s name too long",
1439 map->map_mname, map->map_file);
1440 return false;
1441 }
1442 sff = SFF_ROOTOK|SFF_REGONLY;
1443 if (mode == O_RDWR)
1444 {
1445 sff |= SFF_CREAT;
1446 if (!bitnset(DBS_WRITEMAPTOSYMLINK, DontBlameSendmail))
1447 sff |= SFF_NOSLINK;
1448 if (!bitnset(DBS_WRITEMAPTOHARDLINK, DontBlameSendmail))
1449 sff |= SFF_NOHLINK;
1450 smode = S_IWRITE;
1451 }
1452 else
1453 {
1454 if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
1455 sff |= SFF_NOWLINK;
1456 }
1457 if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail))
1458 sff |= SFF_SAFEDIRPATH;
1459 ret = safefile(dirfile, RunAsUid, RunAsGid, RunAsUserName,
1460 sff, smode, &std);
1461 if (ret == 0)
1462 ret = safefile(pagfile, RunAsUid, RunAsGid, RunAsUserName,
1463 sff, smode, &stp);
1464
1465 if (ret != 0)
1466 {
1467 char *prob = "unsafe";
1468
1469 /* cannot open this map */
1470 if (ret == ENOENT)
1471 prob = "missing";
1472 if (tTd(38, 2))
1473 sm_dprintf("\t%s map file: %d\n", prob, ret);
1474 if (!bitset(MF_OPTIONAL, map->map_mflags))
1475 syserr("dbm map \"%s\": %s map file %s",
1476 map->map_mname, prob, map->map_file);
1477 return false;
1478 }
1479 if (std.st_mode == ST_MODE_NOFILE)
1480 mode |= O_CREAT|O_EXCL;
1481
1482 # if LOCK_ON_OPEN
1483 if (mode == O_RDONLY)
1484 mode |= O_SHLOCK;
1485 else
1486 mode |= O_TRUNC|O_EXLOCK;
1487 # else /* LOCK_ON_OPEN */
1488 if ((mode & O_ACCMODE) == O_RDWR)
1489 {
1490 # if NOFTRUNCATE
1491 /*
1492 ** Warning: race condition. Try to lock the file as
1493 ** quickly as possible after opening it.
1494 ** This may also have security problems on some systems,
1495 ** but there isn't anything we can do about it.
1496 */
1497
1498 mode |= O_TRUNC;
1499 # else /* NOFTRUNCATE */
1500 /*
1501 ** This ugly code opens the map without truncating it,
1502 ** locks the file, then truncates it. Necessary to
1503 ** avoid race conditions.
1504 */
1505
1506 int dirfd;
1507 int pagfd;
1508 long sff = SFF_CREAT|SFF_OPENASROOT;
1509
1510 if (!bitnset(DBS_WRITEMAPTOSYMLINK, DontBlameSendmail))
1511 sff |= SFF_NOSLINK;
1512 if (!bitnset(DBS_WRITEMAPTOHARDLINK, DontBlameSendmail))
1513 sff |= SFF_NOHLINK;
1514
1515 dirfd = safeopen(dirfile, mode, DBMMODE, sff);
1516 pagfd = safeopen(pagfile, mode, DBMMODE, sff);
1517
1518 if (dirfd < 0 || pagfd < 0)
1519 {
1520 save_errno = errno;
1521 if (dirfd >= 0)
1522 (void) close(dirfd);
1523 if (pagfd >= 0)
1524 (void) close(pagfd);
1525 errno = save_errno;
1526 syserr("ndbm_map_open: cannot create database %s",
1527 map->map_file);
1528 return false;
1529 }
1530 if (ftruncate(dirfd, (off_t) 0) < 0 ||
1531 ftruncate(pagfd, (off_t) 0) < 0)
1532 {
1533 save_errno = errno;
1534 (void) close(dirfd);
1535 (void) close(pagfd);
1536 errno = save_errno;
1537 syserr("ndbm_map_open: cannot truncate %s.{dir,pag}",
1538 map->map_file);
1539 return false;
1540 }
1541
1542 /* if new file, get "before" bits for later filechanged check */
1543 if (std.st_mode == ST_MODE_NOFILE &&
1544 (fstat(dirfd, &std) < 0 || fstat(pagfd, &stp) < 0))
1545 {
1546 save_errno = errno;
1547 (void) close(dirfd);
1548 (void) close(pagfd);
1549 errno = save_errno;
1550 syserr("ndbm_map_open(%s.{dir,pag}): cannot fstat pre-opened file",
1551 map->map_file);
1552 return false;
1553 }
1554
1555 /* have to save the lock for the duration (bletch) */
1556 map->map_lockfd = dirfd;
1557 (void) close(pagfd);
1558
1559 /* twiddle bits for dbm_open */
1560 mode &= ~(O_CREAT|O_EXCL);
1561 # endif /* NOFTRUNCATE */
1562 }
1563 # endif /* LOCK_ON_OPEN */
1564
1565 /* open the database */
1566 dbm = dbm_open(map->map_file, mode, DBMMODE);
1567 if (dbm == NULL)
1568 {
1569 save_errno = errno;
1570 if (bitset(MF_ALIAS, map->map_mflags) &&
1571 aliaswait(map, ".pag", false))
1572 return true;
1573 # if !LOCK_ON_OPEN && !NOFTRUNCATE
1574 if (map->map_lockfd >= 0)
1575 (void) close(map->map_lockfd);
1576 # endif /* !LOCK_ON_OPEN && !NOFTRUNCATE */
1577 errno = save_errno;
1578 if (!bitset(MF_OPTIONAL, map->map_mflags))
1579 syserr("Cannot open DBM database %s", map->map_file);
1580 return false;
1581 }
1582 dfd = dbm_dirfno(dbm);
1583 pfd = dbm_pagfno(dbm);
1584 if (dfd == pfd)
1585 {
1586 /* heuristic: if files are linked, this is actually gdbm */
1587 dbm_close(dbm);
1588 # if !LOCK_ON_OPEN && !NOFTRUNCATE
1589 if (map->map_lockfd >= 0)
1590 (void) close(map->map_lockfd);
1591 # endif /* !LOCK_ON_OPEN && !NOFTRUNCATE */
1592 errno = 0;
1593 syserr("dbm map \"%s\": cannot support GDBM",
1594 map->map_mname);
1595 return false;
1596 }
1597
1598 if (filechanged(dirfile, dfd, &std) ||
1599 filechanged(pagfile, pfd, &stp))
1600 {
1601 save_errno = errno;
1602 dbm_close(dbm);
1603 # if !LOCK_ON_OPEN && !NOFTRUNCATE
1604 if (map->map_lockfd >= 0)
1605 (void) close(map->map_lockfd);
1606 # endif /* !LOCK_ON_OPEN && !NOFTRUNCATE */
1607 errno = save_errno;
1608 syserr("ndbm_map_open(%s): file changed after open",
1609 map->map_file);
1610 return false;
1611 }
1612
1613 map->map_db1 = (ARBPTR_T) dbm;
1614
1615 /*
1616 ** Need to set map_mtime before the call to aliaswait()
1617 ** as aliaswait() will call map_lookup() which requires
1618 ** map_mtime to be set
1619 */
1620
1621 if (fstat(pfd, &st) >= 0)
1622 map->map_mtime = st.st_mtime;
1623
1624 if (mode == O_RDONLY)
1625 {
1626 # if LOCK_ON_OPEN
1627 if (dfd >= 0)
1628 (void) lockfile(dfd, map->map_file, ".dir", LOCK_UN);
1629 if (pfd >= 0)
1630 (void) lockfile(pfd, map->map_file, ".pag", LOCK_UN);
1631 # endif /* LOCK_ON_OPEN */
1632 if (bitset(MF_ALIAS, map->map_mflags) &&
1633 !aliaswait(map, ".pag", true))
1634 return false;
1635 }
1636 else
1637 {
1638 map->map_mflags |= MF_LOCKED;
1639 if (geteuid() == 0 && TrustedUid != 0)
1640 {
1641 # if HASFCHOWN
1642 if (fchown(dfd, TrustedUid, -1) < 0 ||
1643 fchown(pfd, TrustedUid, -1) < 0)
1644 {
1645 int err = errno;
1646
1647 sm_syslog(LOG_ALERT, NOQID,
1648 "ownership change on %s failed: %s",
1649 map->map_file, sm_errstring(err));
1650 message("050 ownership change on %s failed: %s",
1651 map->map_file, sm_errstring(err));
1652 }
1653 # else /* HASFCHOWN */
1654 sm_syslog(LOG_ALERT, NOQID,
1655 "no fchown(): cannot change ownership on %s",
1656 map->map_file);
1657 message("050 no fchown(): cannot change ownership on %s",
1658 map->map_file);
1659 # endif /* HASFCHOWN */
1660 }
1661 }
1662 return true;
1663 }
1664
1665
1666 /*
1667 ** NDBM_MAP_LOOKUP -- look up a datum in a DBM-type map
1668 */
1669
1670 char *
ndbm_map_lookup(map,name,av,statp)1671 ndbm_map_lookup(map, name, av, statp)
1672 MAP *map;
1673 char *name;
1674 char **av;
1675 int *statp;
1676 {
1677 datum key, val;
1678 int dfd, pfd;
1679 char keybuf[MAXNAME + 1];
1680 struct stat stbuf;
1681
1682 if (tTd(38, 20))
1683 sm_dprintf("ndbm_map_lookup(%s, %s)\n",
1684 map->map_mname, name);
1685
1686 key.dptr = name;
1687 key.dsize = strlen(name);
1688 if (!bitset(MF_NOFOLDCASE, map->map_mflags))
1689 {
1690 if (key.dsize > sizeof(keybuf) - 1)
1691 key.dsize = sizeof(keybuf) - 1;
1692 memmove(keybuf, key.dptr, key.dsize);
1693 keybuf[key.dsize] = '\0';
1694 makelower(keybuf);
1695 key.dptr = keybuf;
1696 }
1697 lockdbm:
1698 dfd = dbm_dirfno((DBM *) map->map_db1);
1699 if (dfd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
1700 (void) lockfile(dfd, map->map_file, ".dir", LOCK_SH);
1701 pfd = dbm_pagfno((DBM *) map->map_db1);
1702 if (pfd < 0 || fstat(pfd, &stbuf) < 0 ||
1703 stbuf.st_mtime > map->map_mtime)
1704 {
1705 /* Reopen the database to sync the cache */
1706 int omode = bitset(map->map_mflags, MF_WRITABLE) ? O_RDWR
1707 : O_RDONLY;
1708
1709 if (dfd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
1710 (void) lockfile(dfd, map->map_file, ".dir", LOCK_UN);
1711 map->map_mflags |= MF_CLOSING;
1712 map->map_class->map_close(map);
1713 map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
1714 if (map->map_class->map_open(map, omode))
1715 {
1716 map->map_mflags |= MF_OPEN;
1717 map->map_pid = CurrentPid;
1718 if ((omode & O_ACCMODE) == O_RDWR)
1719 map->map_mflags |= MF_WRITABLE;
1720 goto lockdbm;
1721 }
1722 else
1723 {
1724 if (!bitset(MF_OPTIONAL, map->map_mflags))
1725 {
1726 extern MAPCLASS BogusMapClass;
1727
1728 *statp = EX_TEMPFAIL;
1729 map->map_orgclass = map->map_class;
1730 map->map_class = &BogusMapClass;
1731 map->map_mflags |= MF_OPEN;
1732 map->map_pid = CurrentPid;
1733 syserr("Cannot reopen NDBM database %s",
1734 map->map_file);
1735 }
1736 return NULL;
1737 }
1738 }
1739 val.dptr = NULL;
1740 if (bitset(MF_TRY0NULL, map->map_mflags))
1741 {
1742 val = dbm_fetch((DBM *) map->map_db1, key);
1743 if (val.dptr != NULL)
1744 map->map_mflags &= ~MF_TRY1NULL;
1745 }
1746 if (val.dptr == NULL && bitset(MF_TRY1NULL, map->map_mflags))
1747 {
1748 key.dsize++;
1749 val = dbm_fetch((DBM *) map->map_db1, key);
1750 if (val.dptr != NULL)
1751 map->map_mflags &= ~MF_TRY0NULL;
1752 }
1753 if (dfd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
1754 (void) lockfile(dfd, map->map_file, ".dir", LOCK_UN);
1755 if (val.dptr == NULL)
1756 return NULL;
1757 if (bitset(MF_MATCHONLY, map->map_mflags))
1758 return map_rewrite(map, name, strlen(name), NULL);
1759 else
1760 return map_rewrite(map, val.dptr, val.dsize, av);
1761 }
1762
1763
1764 /*
1765 ** NDBM_MAP_STORE -- store a datum in the database
1766 */
1767
1768 void
ndbm_map_store(map,lhs,rhs)1769 ndbm_map_store(map, lhs, rhs)
1770 register MAP *map;
1771 char *lhs;
1772 char *rhs;
1773 {
1774 datum key;
1775 datum data;
1776 int status;
1777 char keybuf[MAXNAME + 1];
1778
1779 if (tTd(38, 12))
1780 sm_dprintf("ndbm_map_store(%s, %s, %s)\n",
1781 map->map_mname, lhs, rhs);
1782
1783 key.dsize = strlen(lhs);
1784 key.dptr = lhs;
1785 if (!bitset(MF_NOFOLDCASE, map->map_mflags))
1786 {
1787 if (key.dsize > sizeof(keybuf) - 1)
1788 key.dsize = sizeof(keybuf) - 1;
1789 memmove(keybuf, key.dptr, key.dsize);
1790 keybuf[key.dsize] = '\0';
1791 makelower(keybuf);
1792 key.dptr = keybuf;
1793 }
1794
1795 data.dsize = strlen(rhs);
1796 data.dptr = rhs;
1797
1798 if (bitset(MF_INCLNULL, map->map_mflags))
1799 {
1800 key.dsize++;
1801 data.dsize++;
1802 }
1803
1804 status = dbm_store((DBM *) map->map_db1, key, data, DBM_INSERT);
1805 if (status > 0)
1806 {
1807 if (!bitset(MF_APPEND, map->map_mflags))
1808 message("050 Warning: duplicate alias name %s", lhs);
1809 else
1810 {
1811 static char *buf = NULL;
1812 static int bufsiz = 0;
1813 auto int xstat;
1814 datum old;
1815
1816 old.dptr = ndbm_map_lookup(map, key.dptr,
1817 (char **) NULL, &xstat);
1818 if (old.dptr != NULL && *(char *) old.dptr != '\0')
1819 {
1820 old.dsize = strlen(old.dptr);
1821 if (data.dsize + old.dsize + 2 > bufsiz)
1822 {
1823 if (buf != NULL)
1824 (void) sm_free(buf);
1825 bufsiz = data.dsize + old.dsize + 2;
1826 buf = sm_pmalloc_x(bufsiz);
1827 }
1828 (void) sm_strlcpyn(buf, bufsiz, 3,
1829 data.dptr, ",", old.dptr);
1830 data.dsize = data.dsize + old.dsize + 1;
1831 data.dptr = buf;
1832 if (tTd(38, 9))
1833 sm_dprintf("ndbm_map_store append=%s\n",
1834 data.dptr);
1835 }
1836 }
1837 status = dbm_store((DBM *) map->map_db1,
1838 key, data, DBM_REPLACE);
1839 }
1840 if (status != 0)
1841 syserr("readaliases: dbm put (%s): %d", lhs, status);
1842 }
1843
1844
1845 /*
1846 ** NDBM_MAP_CLOSE -- close the database
1847 */
1848
1849 void
ndbm_map_close(map)1850 ndbm_map_close(map)
1851 register MAP *map;
1852 {
1853 if (tTd(38, 9))
1854 sm_dprintf("ndbm_map_close(%s, %s, %lx)\n",
1855 map->map_mname, map->map_file, map->map_mflags);
1856
1857 if (bitset(MF_WRITABLE, map->map_mflags))
1858 {
1859 # ifdef NDBM_YP_COMPAT
1860 bool inclnull;
1861 char buf[MAXHOSTNAMELEN];
1862
1863 inclnull = bitset(MF_INCLNULL, map->map_mflags);
1864 map->map_mflags &= ~MF_INCLNULL;
1865
1866 if (strstr(map->map_file, "/yp/") != NULL)
1867 {
1868 long save_mflags = map->map_mflags;
1869
1870 map->map_mflags |= MF_NOFOLDCASE;
1871
1872 (void) sm_snprintf(buf, sizeof(buf), "%010ld", curtime());
1873 ndbm_map_store(map, "YP_LAST_MODIFIED", buf);
1874
1875 (void) gethostname(buf, sizeof(buf));
1876 ndbm_map_store(map, "YP_MASTER_NAME", buf);
1877
1878 map->map_mflags = save_mflags;
1879 }
1880
1881 if (inclnull)
1882 map->map_mflags |= MF_INCLNULL;
1883 # endif /* NDBM_YP_COMPAT */
1884
1885 /* write out the distinguished alias */
1886 ndbm_map_store(map, "@", "@");
1887 }
1888 dbm_close((DBM *) map->map_db1);
1889
1890 /* release lock (if needed) */
1891 # if !LOCK_ON_OPEN
1892 if (map->map_lockfd >= 0)
1893 (void) close(map->map_lockfd);
1894 # endif /* !LOCK_ON_OPEN */
1895 }
1896
1897 #endif /* NDBM */
1898 /*
1899 ** NEWDB (Hash and BTree) Modules
1900 */
1901
1902 #if NEWDB
1903
1904 /*
1905 ** BT_MAP_OPEN, HASH_MAP_OPEN -- database open primitives.
1906 **
1907 ** These do rather bizarre locking. If you can lock on open,
1908 ** do that to avoid the condition of opening a database that
1909 ** is being rebuilt. If you don't, we'll try to fake it, but
1910 ** there will be a race condition. If opening for read-only,
1911 ** we immediately release the lock to avoid freezing things up.
1912 ** We really ought to hold the lock, but guarantee that we won't
1913 ** be pokey about it. That's hard to do.
1914 */
1915
1916 /* these should be K line arguments */
1917 # if DB_VERSION_MAJOR < 2
1918 # define db_cachesize cachesize
1919 # define h_nelem nelem
1920 # ifndef DB_CACHE_SIZE
1921 # define DB_CACHE_SIZE (1024 * 1024) /* database memory cache size */
1922 # endif /* ! DB_CACHE_SIZE */
1923 # ifndef DB_HASH_NELEM
1924 # define DB_HASH_NELEM 4096 /* (starting) size of hash table */
1925 # endif /* ! DB_HASH_NELEM */
1926 # endif /* DB_VERSION_MAJOR < 2 */
1927
1928 bool
bt_map_open(map,mode)1929 bt_map_open(map, mode)
1930 MAP *map;
1931 int mode;
1932 {
1933 # if DB_VERSION_MAJOR < 2
1934 BTREEINFO btinfo;
1935 # endif /* DB_VERSION_MAJOR < 2 */
1936 # if DB_VERSION_MAJOR == 2
1937 DB_INFO btinfo;
1938 # endif /* DB_VERSION_MAJOR == 2 */
1939 # if DB_VERSION_MAJOR > 2
1940 void *btinfo = NULL;
1941 # endif /* DB_VERSION_MAJOR > 2 */
1942
1943 if (tTd(38, 2))
1944 sm_dprintf("bt_map_open(%s, %s, %d)\n",
1945 map->map_mname, map->map_file, mode);
1946
1947 # if DB_VERSION_MAJOR < 3
1948 memset(&btinfo, '\0', sizeof(btinfo));
1949 # ifdef DB_CACHE_SIZE
1950 btinfo.db_cachesize = DB_CACHE_SIZE;
1951 # endif /* DB_CACHE_SIZE */
1952 # endif /* DB_VERSION_MAJOR < 3 */
1953
1954 return db_map_open(map, mode, "btree", DB_BTREE, &btinfo);
1955 }
1956
1957 bool
hash_map_open(map,mode)1958 hash_map_open(map, mode)
1959 MAP *map;
1960 int mode;
1961 {
1962 # if DB_VERSION_MAJOR < 2
1963 HASHINFO hinfo;
1964 # endif /* DB_VERSION_MAJOR < 2 */
1965 # if DB_VERSION_MAJOR == 2
1966 DB_INFO hinfo;
1967 # endif /* DB_VERSION_MAJOR == 2 */
1968 # if DB_VERSION_MAJOR > 2
1969 void *hinfo = NULL;
1970 # endif /* DB_VERSION_MAJOR > 2 */
1971
1972 if (tTd(38, 2))
1973 sm_dprintf("hash_map_open(%s, %s, %d)\n",
1974 map->map_mname, map->map_file, mode);
1975
1976 # if DB_VERSION_MAJOR < 3
1977 memset(&hinfo, '\0', sizeof(hinfo));
1978 # ifdef DB_HASH_NELEM
1979 hinfo.h_nelem = DB_HASH_NELEM;
1980 # endif /* DB_HASH_NELEM */
1981 # ifdef DB_CACHE_SIZE
1982 hinfo.db_cachesize = DB_CACHE_SIZE;
1983 # endif /* DB_CACHE_SIZE */
1984 # endif /* DB_VERSION_MAJOR < 3 */
1985
1986 return db_map_open(map, mode, "hash", DB_HASH, &hinfo);
1987 }
1988
1989 static bool
1990 db_map_open(map, mode, mapclassname, dbtype, openinfo)
1991 MAP *map;
1992 int mode;
1993 char *mapclassname;
1994 DBTYPE dbtype;
1995 # if DB_VERSION_MAJOR < 2
1996 const void *openinfo;
1997 # endif /* DB_VERSION_MAJOR < 2 */
1998 # if DB_VERSION_MAJOR == 2
1999 DB_INFO *openinfo;
2000 # endif /* DB_VERSION_MAJOR == 2 */
2001 # if DB_VERSION_MAJOR > 2
2002 void **openinfo;
2003 # endif /* DB_VERSION_MAJOR > 2 */
2004 {
2005 DB *db = NULL;
2006 int i;
2007 int omode;
2008 int smode = S_IREAD;
2009 int fd;
2010 long sff;
2011 int save_errno;
2012 struct stat st;
2013 char buf[MAXPATHLEN];
2014
2015 /* do initial file and directory checks */
2016 if (sm_strlcpy(buf, map->map_file, sizeof(buf)) >= sizeof(buf))
2017 {
2018 errno = 0;
2019 if (!bitset(MF_OPTIONAL, map->map_mflags))
2020 syserr("map \"%s\": map file %s name too long",
2021 map->map_mname, map->map_file);
2022 return false;
2023 }
2024 i = strlen(buf);
2025 if (i < 3 || strcmp(&buf[i - 3], ".db") != 0)
2026 {
2027 if (sm_strlcat(buf, ".db", sizeof(buf)) >= sizeof(buf))
2028 {
2029 errno = 0;
2030 if (!bitset(MF_OPTIONAL, map->map_mflags))
2031 syserr("map \"%s\": map file %s name too long",
2032 map->map_mname, map->map_file);
2033 return false;
2034 }
2035 }
2036
2037 mode &= O_ACCMODE;
2038 omode = mode;
2039
2040 sff = SFF_ROOTOK|SFF_REGONLY;
2041 if (mode == O_RDWR)
2042 {
2043 sff |= SFF_CREAT;
2044 if (!bitnset(DBS_WRITEMAPTOSYMLINK, DontBlameSendmail))
2045 sff |= SFF_NOSLINK;
2046 if (!bitnset(DBS_WRITEMAPTOHARDLINK, DontBlameSendmail))
2047 sff |= SFF_NOHLINK;
2048 smode = S_IWRITE;
2049 }
2050 else
2051 {
2052 if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
2053 sff |= SFF_NOWLINK;
2054 }
2055 if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail))
2056 sff |= SFF_SAFEDIRPATH;
2057 i = safefile(buf, RunAsUid, RunAsGid, RunAsUserName, sff, smode, &st);
2058
2059 if (i != 0)
2060 {
2061 char *prob = "unsafe";
2062
2063 /* cannot open this map */
2064 if (i == ENOENT)
2065 prob = "missing";
2066 if (tTd(38, 2))
2067 sm_dprintf("\t%s map file: %s\n", prob, sm_errstring(i));
2068 errno = i;
2069 if (!bitset(MF_OPTIONAL, map->map_mflags))
2070 syserr("%s map \"%s\": %s map file %s",
2071 mapclassname, map->map_mname, prob, buf);
2072 return false;
2073 }
2074 if (st.st_mode == ST_MODE_NOFILE)
2075 omode |= O_CREAT|O_EXCL;
2076
2077 map->map_lockfd = -1;
2078
2079 # if LOCK_ON_OPEN
2080 if (mode == O_RDWR)
2081 omode |= O_TRUNC|O_EXLOCK;
2082 else
2083 omode |= O_SHLOCK;
2084 # else /* LOCK_ON_OPEN */
2085 /*
2086 ** Pre-lock the file to avoid race conditions. In particular,
2087 ** since dbopen returns NULL if the file is zero length, we
2088 ** must have a locked instance around the dbopen.
2089 */
2090
2091 fd = open(buf, omode, DBMMODE);
2092 if (fd < 0)
2093 {
2094 if (!bitset(MF_OPTIONAL, map->map_mflags))
2095 syserr("db_map_open: cannot pre-open database %s", buf);
2096 return false;
2097 }
2098
2099 /* make sure no baddies slipped in just before the open... */
2100 if (filechanged(buf, fd, &st))
2101 {
2102 save_errno = errno;
2103 (void) close(fd);
2104 errno = save_errno;
2105 syserr("db_map_open(%s): file changed after pre-open", buf);
2106 return false;
2107 }
2108
2109 /* if new file, get the "before" bits for later filechanged check */
2110 if (st.st_mode == ST_MODE_NOFILE && fstat(fd, &st) < 0)
2111 {
2112 save_errno = errno;
2113 (void) close(fd);
2114 errno = save_errno;
2115 syserr("db_map_open(%s): cannot fstat pre-opened file",
2116 buf);
2117 return false;
2118 }
2119
2120 /* actually lock the pre-opened file */
2121 if (!lockfile(fd, buf, NULL, mode == O_RDONLY ? LOCK_SH : LOCK_EX))
2122 syserr("db_map_open: cannot lock %s", buf);
2123
2124 /* set up mode bits for dbopen */
2125 if (mode == O_RDWR)
2126 omode |= O_TRUNC;
2127 omode &= ~(O_EXCL|O_CREAT);
2128 # endif /* LOCK_ON_OPEN */
2129
2130 # if DB_VERSION_MAJOR < 2
2131 db = dbopen(buf, omode, DBMMODE, dbtype, openinfo);
2132 # else /* DB_VERSION_MAJOR < 2 */
2133 {
2134 int flags = 0;
2135 # if DB_VERSION_MAJOR > 2
2136 int ret;
2137 # endif /* DB_VERSION_MAJOR > 2 */
2138
2139 if (mode == O_RDONLY)
2140 flags |= DB_RDONLY;
2141 if (bitset(O_CREAT, omode))
2142 flags |= DB_CREATE;
2143 if (bitset(O_TRUNC, omode))
2144 flags |= DB_TRUNCATE;
2145 SM_DB_FLAG_ADD(flags);
2146
2147 # if DB_VERSION_MAJOR > 2
2148 ret = db_create(&db, NULL, 0);
2149 # ifdef DB_CACHE_SIZE
2150 if (ret == 0 && db != NULL)
2151 {
2152 ret = db->set_cachesize(db, 0, DB_CACHE_SIZE, 0);
2153 if (ret != 0)
2154 {
2155 (void) db->close(db, 0);
2156 db = NULL;
2157 }
2158 }
2159 # endif /* DB_CACHE_SIZE */
2160 # ifdef DB_HASH_NELEM
2161 if (dbtype == DB_HASH && ret == 0 && db != NULL)
2162 {
2163 ret = db->set_h_nelem(db, DB_HASH_NELEM);
2164 if (ret != 0)
2165 {
2166 (void) db->close(db, 0);
2167 db = NULL;
2168 }
2169 }
2170 # endif /* DB_HASH_NELEM */
2171 if (ret == 0 && db != NULL)
2172 {
2173 ret = db->open(db,
2174 DBTXN /* transaction for DB 4.1 */
2175 buf, NULL, dbtype, flags, DBMMODE);
2176 if (ret != 0)
2177 {
2178 #ifdef DB_OLD_VERSION
2179 if (ret == DB_OLD_VERSION)
2180 ret = EINVAL;
2181 #endif /* DB_OLD_VERSION */
2182 (void) db->close(db, 0);
2183 db = NULL;
2184 }
2185 }
2186 errno = ret;
2187 # else /* DB_VERSION_MAJOR > 2 */
2188 errno = db_open(buf, dbtype, flags, DBMMODE,
2189 NULL, openinfo, &db);
2190 # endif /* DB_VERSION_MAJOR > 2 */
2191 }
2192 # endif /* DB_VERSION_MAJOR < 2 */
2193 save_errno = errno;
2194
2195 # if !LOCK_ON_OPEN
2196 if (mode == O_RDWR)
2197 map->map_lockfd = fd;
2198 else
2199 (void) close(fd);
2200 # endif /* !LOCK_ON_OPEN */
2201
2202 if (db == NULL)
2203 {
2204 if (mode == O_RDONLY && bitset(MF_ALIAS, map->map_mflags) &&
2205 aliaswait(map, ".db", false))
2206 return true;
2207 # if !LOCK_ON_OPEN
2208 if (map->map_lockfd >= 0)
2209 (void) close(map->map_lockfd);
2210 # endif /* !LOCK_ON_OPEN */
2211 errno = save_errno;
2212 if (!bitset(MF_OPTIONAL, map->map_mflags))
2213 syserr("Cannot open %s database %s",
2214 mapclassname, buf);
2215 return false;
2216 }
2217
2218 # if DB_VERSION_MAJOR < 2
2219 fd = db->fd(db);
2220 # else /* DB_VERSION_MAJOR < 2 */
2221 fd = -1;
2222 errno = db->fd(db, &fd);
2223 # endif /* DB_VERSION_MAJOR < 2 */
2224 if (filechanged(buf, fd, &st))
2225 {
2226 save_errno = errno;
2227 # if DB_VERSION_MAJOR < 2
2228 (void) db->close(db);
2229 # else /* DB_VERSION_MAJOR < 2 */
2230 errno = db->close(db, 0);
2231 # endif /* DB_VERSION_MAJOR < 2 */
2232 # if !LOCK_ON_OPEN
2233 if (map->map_lockfd >= 0)
2234 (void) close(map->map_lockfd);
2235 # endif /* !LOCK_ON_OPEN */
2236 errno = save_errno;
2237 syserr("db_map_open(%s): file changed after open", buf);
2238 return false;
2239 }
2240
2241 if (mode == O_RDWR)
2242 map->map_mflags |= MF_LOCKED;
2243 # if LOCK_ON_OPEN
2244 if (fd >= 0 && mode == O_RDONLY)
2245 {
2246 (void) lockfile(fd, buf, NULL, LOCK_UN);
2247 }
2248 # endif /* LOCK_ON_OPEN */
2249
2250 /* try to make sure that at least the database header is on disk */
2251 if (mode == O_RDWR)
2252 {
2253 (void) db->sync(db, 0);
2254 if (geteuid() == 0 && TrustedUid != 0)
2255 {
2256 # if HASFCHOWN
2257 if (fchown(fd, TrustedUid, -1) < 0)
2258 {
2259 int err = errno;
2260
2261 sm_syslog(LOG_ALERT, NOQID,
2262 "ownership change on %s failed: %s",
2263 buf, sm_errstring(err));
2264 message("050 ownership change on %s failed: %s",
2265 buf, sm_errstring(err));
2266 }
2267 # else /* HASFCHOWN */
2268 sm_syslog(LOG_ALERT, NOQID,
2269 "no fchown(): cannot change ownership on %s",
2270 map->map_file);
2271 message("050 no fchown(): cannot change ownership on %s",
2272 map->map_file);
2273 # endif /* HASFCHOWN */
2274 }
2275 }
2276
2277 map->map_db2 = (ARBPTR_T) db;
2278
2279 /*
2280 ** Need to set map_mtime before the call to aliaswait()
2281 ** as aliaswait() will call map_lookup() which requires
2282 ** map_mtime to be set
2283 */
2284
2285 if (fd >= 0 && fstat(fd, &st) >= 0)
2286 map->map_mtime = st.st_mtime;
2287
2288 if (mode == O_RDONLY && bitset(MF_ALIAS, map->map_mflags) &&
2289 !aliaswait(map, ".db", true))
2290 return false;
2291 return true;
2292 }
2293
2294
2295 /*
2296 ** DB_MAP_LOOKUP -- look up a datum in a BTREE- or HASH-type map
2297 */
2298
2299 char *
db_map_lookup(map,name,av,statp)2300 db_map_lookup(map, name, av, statp)
2301 MAP *map;
2302 char *name;
2303 char **av;
2304 int *statp;
2305 {
2306 DBT key, val;
2307 register DB *db = (DB *) map->map_db2;
2308 int i;
2309 int st;
2310 int save_errno;
2311 int fd;
2312 struct stat stbuf;
2313 char keybuf[MAXNAME + 1];
2314 char buf[MAXPATHLEN];
2315
2316 memset(&key, '\0', sizeof(key));
2317 memset(&val, '\0', sizeof(val));
2318
2319 if (tTd(38, 20))
2320 sm_dprintf("db_map_lookup(%s, %s)\n",
2321 map->map_mname, name);
2322
2323 if (sm_strlcpy(buf, map->map_file, sizeof(buf)) >= sizeof(buf))
2324 {
2325 errno = 0;
2326 if (!bitset(MF_OPTIONAL, map->map_mflags))
2327 syserr("map \"%s\": map file %s name too long",
2328 map->map_mname, map->map_file);
2329 return NULL;
2330 }
2331 i = strlen(buf);
2332 if (i > 3 && strcmp(&buf[i - 3], ".db") == 0)
2333 buf[i - 3] = '\0';
2334
2335 key.size = strlen(name);
2336 if (key.size > sizeof(keybuf) - 1)
2337 key.size = sizeof(keybuf) - 1;
2338 key.data = keybuf;
2339 memmove(keybuf, name, key.size);
2340 keybuf[key.size] = '\0';
2341 if (!bitset(MF_NOFOLDCASE, map->map_mflags))
2342 makelower(keybuf);
2343 lockdb:
2344 # if DB_VERSION_MAJOR < 2
2345 fd = db->fd(db);
2346 # else /* DB_VERSION_MAJOR < 2 */
2347 fd = -1;
2348 errno = db->fd(db, &fd);
2349 # endif /* DB_VERSION_MAJOR < 2 */
2350 if (fd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
2351 (void) lockfile(fd, buf, ".db", LOCK_SH);
2352 if (fd < 0 || fstat(fd, &stbuf) < 0 || stbuf.st_mtime > map->map_mtime)
2353 {
2354 /* Reopen the database to sync the cache */
2355 int omode = bitset(map->map_mflags, MF_WRITABLE) ? O_RDWR
2356 : O_RDONLY;
2357
2358 if (fd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
2359 (void) lockfile(fd, buf, ".db", LOCK_UN);
2360 map->map_mflags |= MF_CLOSING;
2361 map->map_class->map_close(map);
2362 map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
2363 if (map->map_class->map_open(map, omode))
2364 {
2365 map->map_mflags |= MF_OPEN;
2366 map->map_pid = CurrentPid;
2367 if ((omode & O_ACCMODE) == O_RDWR)
2368 map->map_mflags |= MF_WRITABLE;
2369 db = (DB *) map->map_db2;
2370 goto lockdb;
2371 }
2372 else
2373 {
2374 if (!bitset(MF_OPTIONAL, map->map_mflags))
2375 {
2376 extern MAPCLASS BogusMapClass;
2377
2378 *statp = EX_TEMPFAIL;
2379 map->map_orgclass = map->map_class;
2380 map->map_class = &BogusMapClass;
2381 map->map_mflags |= MF_OPEN;
2382 map->map_pid = CurrentPid;
2383 syserr("Cannot reopen DB database %s",
2384 map->map_file);
2385 }
2386 return NULL;
2387 }
2388 }
2389
2390 st = 1;
2391 if (bitset(MF_TRY0NULL, map->map_mflags))
2392 {
2393 # if DB_VERSION_MAJOR < 2
2394 st = db->get(db, &key, &val, 0);
2395 # else /* DB_VERSION_MAJOR < 2 */
2396 errno = db->get(db, NULL, &key, &val, 0);
2397 switch (errno)
2398 {
2399 case DB_NOTFOUND:
2400 case DB_KEYEMPTY:
2401 st = 1;
2402 break;
2403
2404 case 0:
2405 st = 0;
2406 break;
2407
2408 default:
2409 st = -1;
2410 break;
2411 }
2412 # endif /* DB_VERSION_MAJOR < 2 */
2413 if (st == 0)
2414 map->map_mflags &= ~MF_TRY1NULL;
2415 }
2416 if (st != 0 && bitset(MF_TRY1NULL, map->map_mflags))
2417 {
2418 key.size++;
2419 # if DB_VERSION_MAJOR < 2
2420 st = db->get(db, &key, &val, 0);
2421 # else /* DB_VERSION_MAJOR < 2 */
2422 errno = db->get(db, NULL, &key, &val, 0);
2423 switch (errno)
2424 {
2425 case DB_NOTFOUND:
2426 case DB_KEYEMPTY:
2427 st = 1;
2428 break;
2429
2430 case 0:
2431 st = 0;
2432 break;
2433
2434 default:
2435 st = -1;
2436 break;
2437 }
2438 # endif /* DB_VERSION_MAJOR < 2 */
2439 if (st == 0)
2440 map->map_mflags &= ~MF_TRY0NULL;
2441 }
2442 save_errno = errno;
2443 if (fd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
2444 (void) lockfile(fd, buf, ".db", LOCK_UN);
2445 if (st != 0)
2446 {
2447 errno = save_errno;
2448 if (st < 0)
2449 syserr("db_map_lookup: get (%s)", name);
2450 return NULL;
2451 }
2452 if (bitset(MF_MATCHONLY, map->map_mflags))
2453 return map_rewrite(map, name, strlen(name), NULL);
2454 else
2455 return map_rewrite(map, val.data, val.size, av);
2456 }
2457
2458
2459 /*
2460 ** DB_MAP_STORE -- store a datum in the NEWDB database
2461 */
2462
2463 void
db_map_store(map,lhs,rhs)2464 db_map_store(map, lhs, rhs)
2465 register MAP *map;
2466 char *lhs;
2467 char *rhs;
2468 {
2469 int status;
2470 DBT key;
2471 DBT data;
2472 register DB *db = map->map_db2;
2473 char keybuf[MAXNAME + 1];
2474
2475 memset(&key, '\0', sizeof(key));
2476 memset(&data, '\0', sizeof(data));
2477
2478 if (tTd(38, 12))
2479 sm_dprintf("db_map_store(%s, %s, %s)\n",
2480 map->map_mname, lhs, rhs);
2481
2482 key.size = strlen(lhs);
2483 key.data = lhs;
2484 if (!bitset(MF_NOFOLDCASE, map->map_mflags))
2485 {
2486 if (key.size > sizeof(keybuf) - 1)
2487 key.size = sizeof(keybuf) - 1;
2488 memmove(keybuf, key.data, key.size);
2489 keybuf[key.size] = '\0';
2490 makelower(keybuf);
2491 key.data = keybuf;
2492 }
2493
2494 data.size = strlen(rhs);
2495 data.data = rhs;
2496
2497 if (bitset(MF_INCLNULL, map->map_mflags))
2498 {
2499 key.size++;
2500 data.size++;
2501 }
2502
2503 # if DB_VERSION_MAJOR < 2
2504 status = db->put(db, &key, &data, R_NOOVERWRITE);
2505 # else /* DB_VERSION_MAJOR < 2 */
2506 errno = db->put(db, NULL, &key, &data, DB_NOOVERWRITE);
2507 switch (errno)
2508 {
2509 case DB_KEYEXIST:
2510 status = 1;
2511 break;
2512
2513 case 0:
2514 status = 0;
2515 break;
2516
2517 default:
2518 status = -1;
2519 break;
2520 }
2521 # endif /* DB_VERSION_MAJOR < 2 */
2522 if (status > 0)
2523 {
2524 if (!bitset(MF_APPEND, map->map_mflags))
2525 message("050 Warning: duplicate alias name %s", lhs);
2526 else
2527 {
2528 static char *buf = NULL;
2529 static int bufsiz = 0;
2530 DBT old;
2531
2532 memset(&old, '\0', sizeof(old));
2533
2534 old.data = db_map_lookup(map, key.data,
2535 (char **) NULL, &status);
2536 if (old.data != NULL)
2537 {
2538 old.size = strlen(old.data);
2539 if (data.size + old.size + 2 > (size_t) bufsiz)
2540 {
2541 if (buf != NULL)
2542 sm_free(buf);
2543 bufsiz = data.size + old.size + 2;
2544 buf = sm_pmalloc_x(bufsiz);
2545 }
2546 (void) sm_strlcpyn(buf, bufsiz, 3,
2547 (char *) data.data, ",",
2548 (char *) old.data);
2549 data.size = data.size + old.size + 1;
2550 data.data = buf;
2551 if (tTd(38, 9))
2552 sm_dprintf("db_map_store append=%s\n",
2553 (char *) data.data);
2554 }
2555 }
2556 # if DB_VERSION_MAJOR < 2
2557 status = db->put(db, &key, &data, 0);
2558 # else /* DB_VERSION_MAJOR < 2 */
2559 status = errno = db->put(db, NULL, &key, &data, 0);
2560 # endif /* DB_VERSION_MAJOR < 2 */
2561 }
2562 if (status != 0)
2563 syserr("readaliases: db put (%s)", lhs);
2564 }
2565
2566
2567 /*
2568 ** DB_MAP_CLOSE -- add distinguished entries and close the database
2569 */
2570
2571 void
db_map_close(map)2572 db_map_close(map)
2573 MAP *map;
2574 {
2575 register DB *db = map->map_db2;
2576
2577 if (tTd(38, 9))
2578 sm_dprintf("db_map_close(%s, %s, %lx)\n",
2579 map->map_mname, map->map_file, map->map_mflags);
2580
2581 if (bitset(MF_WRITABLE, map->map_mflags))
2582 {
2583 /* write out the distinguished alias */
2584 db_map_store(map, "@", "@");
2585 }
2586
2587 (void) db->sync(db, 0);
2588
2589 # if !LOCK_ON_OPEN
2590 if (map->map_lockfd >= 0)
2591 (void) close(map->map_lockfd);
2592 # endif /* !LOCK_ON_OPEN */
2593
2594 # if DB_VERSION_MAJOR < 2
2595 if (db->close(db) != 0)
2596 # else /* DB_VERSION_MAJOR < 2 */
2597 /*
2598 ** Berkeley DB can use internal shared memory
2599 ** locking for its memory pool. Closing a map
2600 ** opened by another process will interfere
2601 ** with the shared memory and locks of the parent
2602 ** process leaving things in a bad state.
2603 */
2604
2605 /*
2606 ** If this map was not opened by the current
2607 ** process, do not close the map but recover
2608 ** the file descriptor.
2609 */
2610
2611 if (map->map_pid != CurrentPid)
2612 {
2613 int fd = -1;
2614
2615 errno = db->fd(db, &fd);
2616 if (fd >= 0)
2617 (void) close(fd);
2618 return;
2619 }
2620
2621 if ((errno = db->close(db, 0)) != 0)
2622 # endif /* DB_VERSION_MAJOR < 2 */
2623 syserr("db_map_close(%s, %s, %lx): db close failure",
2624 map->map_mname, map->map_file, map->map_mflags);
2625 }
2626 #endif /* NEWDB */
2627 /*
2628 ** NIS Modules
2629 */
2630
2631 #if NIS
2632
2633 # ifndef YPERR_BUSY
2634 # define YPERR_BUSY 16
2635 # endif /* ! YPERR_BUSY */
2636
2637 /*
2638 ** NIS_MAP_OPEN -- open DBM map
2639 */
2640
2641 bool
nis_map_open(map,mode)2642 nis_map_open(map, mode)
2643 MAP *map;
2644 int mode;
2645 {
2646 int yperr;
2647 register char *p;
2648 auto char *vp;
2649 auto int vsize;
2650
2651 if (tTd(38, 2))
2652 sm_dprintf("nis_map_open(%s, %s, %d)\n",
2653 map->map_mname, map->map_file, mode);
2654
2655 mode &= O_ACCMODE;
2656 if (mode != O_RDONLY)
2657 {
2658 /* issue a pseudo-error message */
2659 errno = SM_EMAPCANTWRITE;
2660 return false;
2661 }
2662
2663 p = strchr(map->map_file, '@');
2664 if (p != NULL)
2665 {
2666 *p++ = '\0';
2667 if (*p != '\0')
2668 map->map_domain = p;
2669 }
2670
2671 if (*map->map_file == '\0')
2672 map->map_file = "mail.aliases";
2673
2674 if (map->map_domain == NULL)
2675 {
2676 yperr = yp_get_default_domain(&map->map_domain);
2677 if (yperr != 0)
2678 {
2679 if (!bitset(MF_OPTIONAL, map->map_mflags))
2680 syserr("451 4.3.5 NIS map %s specified, but NIS not running",
2681 map->map_file);
2682 return false;
2683 }
2684 }
2685
2686 /* check to see if this map actually exists */
2687 vp = NULL;
2688 yperr = yp_match(map->map_domain, map->map_file, "@", 1,
2689 &vp, &vsize);
2690 if (tTd(38, 10))
2691 sm_dprintf("nis_map_open: yp_match(@, %s, %s) => %s\n",
2692 map->map_domain, map->map_file, yperr_string(yperr));
2693 if (vp != NULL)
2694 sm_free(vp);
2695
2696 if (yperr == 0 || yperr == YPERR_KEY || yperr == YPERR_BUSY)
2697 {
2698 /*
2699 ** We ought to be calling aliaswait() here if this is an
2700 ** alias file, but powerful HP-UX NIS servers apparently
2701 ** don't insert the @:@ token into the alias map when it
2702 ** is rebuilt, so aliaswait() just hangs. I hate HP-UX.
2703 */
2704
2705 # if 0
2706 if (!bitset(MF_ALIAS, map->map_mflags) ||
2707 aliaswait(map, NULL, true))
2708 # endif /* 0 */
2709 return true;
2710 }
2711
2712 if (!bitset(MF_OPTIONAL, map->map_mflags))
2713 {
2714 syserr("451 4.3.5 Cannot bind to map %s in domain %s: %s",
2715 map->map_file, map->map_domain, yperr_string(yperr));
2716 }
2717
2718 return false;
2719 }
2720
2721
2722 /*
2723 ** NIS_MAP_LOOKUP -- look up a datum in a NIS map
2724 */
2725
2726 /* ARGSUSED3 */
2727 char *
nis_map_lookup(map,name,av,statp)2728 nis_map_lookup(map, name, av, statp)
2729 MAP *map;
2730 char *name;
2731 char **av;
2732 int *statp;
2733 {
2734 char *vp;
2735 auto int vsize;
2736 int buflen;
2737 int yperr;
2738 char keybuf[MAXNAME + 1];
2739 char *SM_NONVOLATILE result = NULL;
2740
2741 if (tTd(38, 20))
2742 sm_dprintf("nis_map_lookup(%s, %s)\n",
2743 map->map_mname, name);
2744
2745 buflen = strlen(name);
2746 if (buflen > sizeof(keybuf) - 1)
2747 buflen = sizeof(keybuf) - 1;
2748 memmove(keybuf, name, buflen);
2749 keybuf[buflen] = '\0';
2750 if (!bitset(MF_NOFOLDCASE, map->map_mflags))
2751 makelower(keybuf);
2752 yperr = YPERR_KEY;
2753 vp = NULL;
2754 if (bitset(MF_TRY0NULL, map->map_mflags))
2755 {
2756 yperr = yp_match(map->map_domain, map->map_file, keybuf, buflen,
2757 &vp, &vsize);
2758 if (yperr == 0)
2759 map->map_mflags &= ~MF_TRY1NULL;
2760 }
2761 if (yperr == YPERR_KEY && bitset(MF_TRY1NULL, map->map_mflags))
2762 {
2763 SM_FREE_CLR(vp);
2764 buflen++;
2765 yperr = yp_match(map->map_domain, map->map_file, keybuf, buflen,
2766 &vp, &vsize);
2767 if (yperr == 0)
2768 map->map_mflags &= ~MF_TRY0NULL;
2769 }
2770 if (yperr != 0)
2771 {
2772 if (yperr != YPERR_KEY && yperr != YPERR_BUSY)
2773 map->map_mflags &= ~(MF_VALID|MF_OPEN);
2774 if (vp != NULL)
2775 sm_free(vp);
2776 return NULL;
2777 }
2778 SM_TRY
2779 if (bitset(MF_MATCHONLY, map->map_mflags))
2780 result = map_rewrite(map, name, strlen(name), NULL);
2781 else
2782 result = map_rewrite(map, vp, vsize, av);
2783 SM_FINALLY
2784 if (vp != NULL)
2785 sm_free(vp);
2786 SM_END_TRY
2787 return result;
2788 }
2789
2790
2791 /*
2792 ** NIS_GETCANONNAME -- look up canonical name in NIS
2793 */
2794
2795 static bool
nis_getcanonname(name,hbsize,statp)2796 nis_getcanonname(name, hbsize, statp)
2797 char *name;
2798 int hbsize;
2799 int *statp;
2800 {
2801 char *vp;
2802 auto int vsize;
2803 int keylen;
2804 int yperr;
2805 static bool try0null = true;
2806 static bool try1null = true;
2807 static char *yp_domain = NULL;
2808 char host_record[MAXLINE];
2809 char cbuf[MAXNAME];
2810 char nbuf[MAXNAME + 1];
2811
2812 if (tTd(38, 20))
2813 sm_dprintf("nis_getcanonname(%s)\n", name);
2814
2815 if (sm_strlcpy(nbuf, name, sizeof(nbuf)) >= sizeof(nbuf))
2816 {
2817 *statp = EX_UNAVAILABLE;
2818 return false;
2819 }
2820 (void) shorten_hostname(nbuf);
2821 keylen = strlen(nbuf);
2822
2823 if (yp_domain == NULL)
2824 (void) yp_get_default_domain(&yp_domain);
2825 makelower(nbuf);
2826 yperr = YPERR_KEY;
2827 vp = NULL;
2828 if (try0null)
2829 {
2830 yperr = yp_match(yp_domain, "hosts.byname", nbuf, keylen,
2831 &vp, &vsize);
2832 if (yperr == 0)
2833 try1null = false;
2834 }
2835 if (yperr == YPERR_KEY && try1null)
2836 {
2837 SM_FREE_CLR(vp);
2838 keylen++;
2839 yperr = yp_match(yp_domain, "hosts.byname", nbuf, keylen,
2840 &vp, &vsize);
2841 if (yperr == 0)
2842 try0null = false;
2843 }
2844 if (yperr != 0)
2845 {
2846 if (yperr == YPERR_KEY)
2847 *statp = EX_NOHOST;
2848 else if (yperr == YPERR_BUSY)
2849 *statp = EX_TEMPFAIL;
2850 else
2851 *statp = EX_UNAVAILABLE;
2852 if (vp != NULL)
2853 sm_free(vp);
2854 return false;
2855 }
2856 (void) sm_strlcpy(host_record, vp, sizeof(host_record));
2857 sm_free(vp);
2858 if (tTd(38, 44))
2859 sm_dprintf("got record `%s'\n", host_record);
2860 vp = strpbrk(host_record, "#\n");
2861 if (vp != NULL)
2862 *vp = '\0';
2863 if (!extract_canonname(nbuf, NULL, host_record, cbuf, sizeof(cbuf)))
2864 {
2865 /* this should not happen, but.... */
2866 *statp = EX_NOHOST;
2867 return false;
2868 }
2869 if (sm_strlcpy(name, cbuf, hbsize) >= hbsize)
2870 {
2871 *statp = EX_UNAVAILABLE;
2872 return false;
2873 }
2874 *statp = EX_OK;
2875 return true;
2876 }
2877
2878 #endif /* NIS */
2879 /*
2880 ** NISPLUS Modules
2881 **
2882 ** This code donated by Sun Microsystems.
2883 */
2884
2885 #if NISPLUS
2886
2887 # undef NIS /* symbol conflict in nis.h */
2888 # undef T_UNSPEC /* symbol conflict in nis.h -> ... -> sys/tiuser.h */
2889 # include <rpcsvc/nis.h>
2890 # include <rpcsvc/nislib.h>
2891
2892 # define EN_col(col) zo_data.objdata_u.en_data.en_cols.en_cols_val[(col)].ec_value.ec_value_val
2893 # define COL_NAME(res,i) ((res->objects.objects_val)->TA_data.ta_cols.ta_cols_val)[i].tc_name
2894 # define COL_MAX(res) ((res->objects.objects_val)->TA_data.ta_cols.ta_cols_len)
2895 # define PARTIAL_NAME(x) ((x)[strlen(x) - 1] != '.')
2896
2897 /*
2898 ** NISPLUS_MAP_OPEN -- open nisplus table
2899 */
2900
2901 bool
nisplus_map_open(map,mode)2902 nisplus_map_open(map, mode)
2903 MAP *map;
2904 int mode;
2905 {
2906 nis_result *res = NULL;
2907 int retry_cnt, max_col, i;
2908 char qbuf[MAXLINE + NIS_MAXNAMELEN];
2909
2910 if (tTd(38, 2))
2911 sm_dprintf("nisplus_map_open(%s, %s, %d)\n",
2912 map->map_mname, map->map_file, mode);
2913
2914 mode &= O_ACCMODE;
2915 if (mode != O_RDONLY)
2916 {
2917 errno = EPERM;
2918 return false;
2919 }
2920
2921 if (*map->map_file == '\0')
2922 map->map_file = "mail_aliases.org_dir";
2923
2924 if (PARTIAL_NAME(map->map_file) && map->map_domain == NULL)
2925 {
2926 /* set default NISPLUS Domain to $m */
2927 map->map_domain = newstr(nisplus_default_domain());
2928 if (tTd(38, 2))
2929 sm_dprintf("nisplus_map_open(%s): using domain %s\n",
2930 map->map_file, map->map_domain);
2931 }
2932 if (!PARTIAL_NAME(map->map_file))
2933 {
2934 map->map_domain = newstr("");
2935 (void) sm_strlcpy(qbuf, map->map_file, sizeof(qbuf));
2936 }
2937 else
2938 {
2939 /* check to see if this map actually exists */
2940 (void) sm_strlcpyn(qbuf, sizeof(qbuf), 3,
2941 map->map_file, ".", map->map_domain);
2942 }
2943
2944 retry_cnt = 0;
2945 while (res == NULL || res->status != NIS_SUCCESS)
2946 {
2947 res = nis_lookup(qbuf, FOLLOW_LINKS);
2948 switch (res->status)
2949 {
2950 case NIS_SUCCESS:
2951 break;
2952
2953 case NIS_TRYAGAIN:
2954 case NIS_RPCERROR:
2955 case NIS_NAMEUNREACHABLE:
2956 if (retry_cnt++ > 4)
2957 {
2958 errno = EAGAIN;
2959 return false;
2960 }
2961 /* try not to overwhelm hosed server */
2962 sleep(2);
2963 break;
2964
2965 default: /* all other nisplus errors */
2966 # if 0
2967 if (!bitset(MF_OPTIONAL, map->map_mflags))
2968 syserr("451 4.3.5 Cannot find table %s.%s: %s",
2969 map->map_file, map->map_domain,
2970 nis_sperrno(res->status));
2971 # endif /* 0 */
2972 errno = EAGAIN;
2973 return false;
2974 }
2975 }
2976
2977 if (NIS_RES_NUMOBJ(res) != 1 ||
2978 (NIS_RES_OBJECT(res)->zo_data.zo_type != TABLE_OBJ))
2979 {
2980 if (tTd(38, 10))
2981 sm_dprintf("nisplus_map_open: %s is not a table\n", qbuf);
2982 # if 0
2983 if (!bitset(MF_OPTIONAL, map->map_mflags))
2984 syserr("451 4.3.5 %s.%s: %s is not a table",
2985 map->map_file, map->map_domain,
2986 nis_sperrno(res->status));
2987 # endif /* 0 */
2988 errno = EBADF;
2989 return false;
2990 }
2991 /* default key column is column 0 */
2992 if (map->map_keycolnm == NULL)
2993 map->map_keycolnm = newstr(COL_NAME(res,0));
2994
2995 max_col = COL_MAX(res);
2996
2997 /* verify the key column exist */
2998 for (i = 0; i < max_col; i++)
2999 {
3000 if (strcmp(map->map_keycolnm, COL_NAME(res,i)) == 0)
3001 break;
3002 }
3003 if (i == max_col)
3004 {
3005 if (tTd(38, 2))
3006 sm_dprintf("nisplus_map_open(%s): can not find key column %s\n",
3007 map->map_file, map->map_keycolnm);
3008 errno = ENOENT;
3009 return false;
3010 }
3011
3012 /* default value column is the last column */
3013 if (map->map_valcolnm == NULL)
3014 {
3015 map->map_valcolno = max_col - 1;
3016 return true;
3017 }
3018
3019 for (i = 0; i< max_col; i++)
3020 {
3021 if (strcmp(map->map_valcolnm, COL_NAME(res,i)) == 0)
3022 {
3023 map->map_valcolno = i;
3024 return true;
3025 }
3026 }
3027
3028 if (tTd(38, 2))
3029 sm_dprintf("nisplus_map_open(%s): can not find column %s\n",
3030 map->map_file, map->map_keycolnm);
3031 errno = ENOENT;
3032 return false;
3033 }
3034
3035
3036 /*
3037 ** NISPLUS_MAP_LOOKUP -- look up a datum in a NISPLUS table
3038 */
3039
3040 char *
nisplus_map_lookup(map,name,av,statp)3041 nisplus_map_lookup(map, name, av, statp)
3042 MAP *map;
3043 char *name;
3044 char **av;
3045 int *statp;
3046 {
3047 char *p;
3048 auto int vsize;
3049 char *skp;
3050 int skleft;
3051 char search_key[MAXNAME + 4];
3052 char qbuf[MAXLINE + NIS_MAXNAMELEN];
3053 nis_result *result;
3054
3055 if (tTd(38, 20))
3056 sm_dprintf("nisplus_map_lookup(%s, %s)\n",
3057 map->map_mname, name);
3058
3059 if (!bitset(MF_OPEN, map->map_mflags))
3060 {
3061 if (nisplus_map_open(map, O_RDONLY))
3062 {
3063 map->map_mflags |= MF_OPEN;
3064 map->map_pid = CurrentPid;
3065 }
3066 else
3067 {
3068 *statp = EX_UNAVAILABLE;
3069 return NULL;
3070 }
3071 }
3072
3073 /*
3074 ** Copy the name to the key buffer, escaping double quote characters
3075 ** by doubling them and quoting "]" and "," to avoid having the
3076 ** NIS+ parser choke on them.
3077 */
3078
3079 skleft = sizeof(search_key) - 4;
3080 skp = search_key;
3081 for (p = name; *p != '\0' && skleft > 0; p++)
3082 {
3083 switch (*p)
3084 {
3085 case ']':
3086 case ',':
3087 /* quote the character */
3088 *skp++ = '"';
3089 *skp++ = *p;
3090 *skp++ = '"';
3091 skleft -= 3;
3092 break;
3093
3094 case '"':
3095 /* double the quote */
3096 *skp++ = '"';
3097 skleft--;
3098 /* FALLTHROUGH */
3099
3100 default:
3101 *skp++ = *p;
3102 skleft--;
3103 break;
3104 }
3105 }
3106 *skp = '\0';
3107 if (!bitset(MF_NOFOLDCASE, map->map_mflags))
3108 makelower(search_key);
3109
3110 /* construct the query */
3111 if (PARTIAL_NAME(map->map_file))
3112 (void) sm_snprintf(qbuf, sizeof(qbuf), "[%s=%s],%s.%s",
3113 map->map_keycolnm, search_key, map->map_file,
3114 map->map_domain);
3115 else
3116 (void) sm_snprintf(qbuf, sizeof(qbuf), "[%s=%s],%s",
3117 map->map_keycolnm, search_key, map->map_file);
3118
3119 if (tTd(38, 20))
3120 sm_dprintf("qbuf=%s\n", qbuf);
3121 result = nis_list(qbuf, FOLLOW_LINKS | FOLLOW_PATH, NULL, NULL);
3122 if (result->status == NIS_SUCCESS)
3123 {
3124 int count;
3125 char *str;
3126
3127 if ((count = NIS_RES_NUMOBJ(result)) != 1)
3128 {
3129 if (LogLevel > 10)
3130 sm_syslog(LOG_WARNING, CurEnv->e_id,
3131 "%s: lookup error, expected 1 entry, got %d",
3132 map->map_file, count);
3133
3134 /* ignore second entry */
3135 if (tTd(38, 20))
3136 sm_dprintf("nisplus_map_lookup(%s), got %d entries, additional entries ignored\n",
3137 name, count);
3138 }
3139
3140 p = ((NIS_RES_OBJECT(result))->EN_col(map->map_valcolno));
3141 /* set the length of the result */
3142 if (p == NULL)
3143 p = "";
3144 vsize = strlen(p);
3145 if (tTd(38, 20))
3146 sm_dprintf("nisplus_map_lookup(%s), found %s\n",
3147 name, p);
3148 if (bitset(MF_MATCHONLY, map->map_mflags))
3149 str = map_rewrite(map, name, strlen(name), NULL);
3150 else
3151 str = map_rewrite(map, p, vsize, av);
3152 nis_freeresult(result);
3153 *statp = EX_OK;
3154 return str;
3155 }
3156 else
3157 {
3158 if (result->status == NIS_NOTFOUND)
3159 *statp = EX_NOTFOUND;
3160 else if (result->status == NIS_TRYAGAIN)
3161 *statp = EX_TEMPFAIL;
3162 else
3163 {
3164 *statp = EX_UNAVAILABLE;
3165 map->map_mflags &= ~(MF_VALID|MF_OPEN);
3166 }
3167 }
3168 if (tTd(38, 20))
3169 sm_dprintf("nisplus_map_lookup(%s), failed\n", name);
3170 nis_freeresult(result);
3171 return NULL;
3172 }
3173
3174
3175
3176 /*
3177 ** NISPLUS_GETCANONNAME -- look up canonical name in NIS+
3178 */
3179
3180 static bool
nisplus_getcanonname(name,hbsize,statp)3181 nisplus_getcanonname(name, hbsize, statp)
3182 char *name;
3183 int hbsize;
3184 int *statp;
3185 {
3186 char *vp;
3187 auto int vsize;
3188 nis_result *result;
3189 char *p;
3190 char nbuf[MAXNAME + 1];
3191 char qbuf[MAXLINE + NIS_MAXNAMELEN];
3192
3193 if (sm_strlcpy(nbuf, name, sizeof(nbuf)) >= sizeof(nbuf))
3194 {
3195 *statp = EX_UNAVAILABLE;
3196 return false;
3197 }
3198 (void) shorten_hostname(nbuf);
3199
3200 p = strchr(nbuf, '.');
3201 if (p == NULL)
3202 {
3203 /* single token */
3204 (void) sm_snprintf(qbuf, sizeof(qbuf),
3205 "[name=%s],hosts.org_dir", nbuf);
3206 }
3207 else if (p[1] != '\0')
3208 {
3209 /* multi token -- take only first token in nbuf */
3210 *p = '\0';
3211 (void) sm_snprintf(qbuf, sizeof(qbuf),
3212 "[name=%s],hosts.org_dir.%s", nbuf, &p[1]);
3213 }
3214 else
3215 {
3216 *statp = EX_NOHOST;
3217 return false;
3218 }
3219
3220 if (tTd(38, 20))
3221 sm_dprintf("\nnisplus_getcanonname(%s), qbuf=%s\n",
3222 name, qbuf);
3223
3224 result = nis_list(qbuf, EXPAND_NAME|FOLLOW_LINKS|FOLLOW_PATH,
3225 NULL, NULL);
3226
3227 if (result->status == NIS_SUCCESS)
3228 {
3229 int count;
3230 char *domain;
3231
3232 if ((count = NIS_RES_NUMOBJ(result)) != 1)
3233 {
3234 if (LogLevel > 10)
3235 sm_syslog(LOG_WARNING, CurEnv->e_id,
3236 "nisplus_getcanonname: lookup error, expected 1 entry, got %d",
3237 count);
3238
3239 /* ignore second entry */
3240 if (tTd(38, 20))
3241 sm_dprintf("nisplus_getcanonname(%s), got %d entries, all but first ignored\n",
3242 name, count);
3243 }
3244
3245 if (tTd(38, 20))
3246 sm_dprintf("nisplus_getcanonname(%s), found in directory \"%s\"\n",
3247 name, (NIS_RES_OBJECT(result))->zo_domain);
3248
3249
3250 vp = ((NIS_RES_OBJECT(result))->EN_col(0));
3251 vsize = strlen(vp);
3252 if (tTd(38, 20))
3253 sm_dprintf("nisplus_getcanonname(%s), found %s\n",
3254 name, vp);
3255 if (strchr(vp, '.') != NULL)
3256 {
3257 domain = "";
3258 }
3259 else
3260 {
3261 domain = macvalue('m', CurEnv);
3262 if (domain == NULL)
3263 domain = "";
3264 }
3265 if (hbsize > vsize + (int) strlen(domain) + 1)
3266 {
3267 if (domain[0] == '\0')
3268 (void) sm_strlcpy(name, vp, hbsize);
3269 else
3270 (void) sm_snprintf(name, hbsize,
3271 "%s.%s", vp, domain);
3272 *statp = EX_OK;
3273 }
3274 else
3275 *statp = EX_NOHOST;
3276 nis_freeresult(result);
3277 return true;
3278 }
3279 else
3280 {
3281 if (result->status == NIS_NOTFOUND)
3282 *statp = EX_NOHOST;
3283 else if (result->status == NIS_TRYAGAIN)
3284 *statp = EX_TEMPFAIL;
3285 else
3286 *statp = EX_UNAVAILABLE;
3287 }
3288 if (tTd(38, 20))
3289 sm_dprintf("nisplus_getcanonname(%s), failed, status=%d, nsw_stat=%d\n",
3290 name, result->status, *statp);
3291 nis_freeresult(result);
3292 return false;
3293 }
3294
3295 char *
nisplus_default_domain()3296 nisplus_default_domain()
3297 {
3298 static char default_domain[MAXNAME + 1] = "";
3299 char *p;
3300
3301 if (default_domain[0] != '\0')
3302 return default_domain;
3303
3304 p = nis_local_directory();
3305 (void) sm_strlcpy(default_domain, p, sizeof(default_domain));
3306 return default_domain;
3307 }
3308
3309 #endif /* NISPLUS */
3310 /*
3311 ** LDAP Modules
3312 */
3313
3314 /*
3315 ** LDAPMAP_DEQUOTE - helper routine for ldapmap_parseargs
3316 */
3317
3318 #if defined(LDAPMAP) || defined(PH_MAP)
3319
3320 # if PH_MAP
3321 # define ph_map_dequote ldapmap_dequote
3322 # endif /* PH_MAP */
3323
3324 static char *ldapmap_dequote __P((char *));
3325
3326 static char *
ldapmap_dequote(str)3327 ldapmap_dequote(str)
3328 char *str;
3329 {
3330 char *p;
3331 char *start;
3332
3333 if (str == NULL)
3334 return NULL;
3335
3336 p = str;
3337 if (*p == '"')
3338 {
3339 /* Should probably swallow initial whitespace here */
3340 start = ++p;
3341 }
3342 else
3343 return str;
3344 while (*p != '"' && *p != '\0')
3345 p++;
3346 if (*p != '\0')
3347 *p = '\0';
3348 return start;
3349 }
3350 #endif /* defined(LDAPMAP) || defined(PH_MAP) */
3351
3352 #if LDAPMAP
3353
3354 static SM_LDAP_STRUCT *LDAPDefaults = NULL;
3355
3356 /*
3357 ** LDAPMAP_OPEN -- open LDAP map
3358 **
3359 ** Connect to the LDAP server. Re-use existing connections since a
3360 ** single server connection to a host (with the same host, port,
3361 ** bind DN, and secret) can answer queries for multiple maps.
3362 */
3363
3364 bool
ldapmap_open(map,mode)3365 ldapmap_open(map, mode)
3366 MAP *map;
3367 int mode;
3368 {
3369 SM_LDAP_STRUCT *lmap;
3370 STAB *s;
3371 char *id;
3372
3373 if (tTd(38, 2))
3374 sm_dprintf("ldapmap_open(%s, %d): ", map->map_mname, mode);
3375
3376 #if defined(SUN_EXTENSIONS) && defined(SUN_SIMPLIFIED_LDAP) && \
3377 HASLDAPGETALIASBYNAME
3378 if (VendorCode == VENDOR_SUN &&
3379 strcmp(map->map_mname, "aliases.ldap") == 0)
3380 {
3381 return true;
3382 }
3383 #endif /* defined(SUN_EXTENSIONS) && defined(SUN_SIMPLIFIED_LDAP) && ... */
3384
3385 mode &= O_ACCMODE;
3386
3387 /* sendmail doesn't have the ability to write to LDAP (yet) */
3388 if (mode != O_RDONLY)
3389 {
3390 /* issue a pseudo-error message */
3391 errno = SM_EMAPCANTWRITE;
3392 return false;
3393 }
3394
3395 lmap = (SM_LDAP_STRUCT *) map->map_db1;
3396
3397 s = ldapmap_findconn(lmap);
3398 if (s->s_lmap != NULL)
3399 {
3400 /* Already have a connection open to this LDAP server */
3401 lmap->ldap_ld = ((SM_LDAP_STRUCT *)s->s_lmap->map_db1)->ldap_ld;
3402 lmap->ldap_pid = ((SM_LDAP_STRUCT *)s->s_lmap->map_db1)->ldap_pid;
3403
3404 /* Add this map as head of linked list */
3405 lmap->ldap_next = s->s_lmap;
3406 s->s_lmap = map;
3407
3408 if (tTd(38, 2))
3409 sm_dprintf("using cached connection\n");
3410 return true;
3411 }
3412
3413 if (tTd(38, 2))
3414 sm_dprintf("opening new connection\n");
3415
3416 if (lmap->ldap_host != NULL)
3417 id = lmap->ldap_host;
3418 else if (lmap->ldap_uri != NULL)
3419 id = lmap->ldap_uri;
3420 else
3421 id = "localhost";
3422
3423 if (tTd(74, 104))
3424 {
3425 extern MAPCLASS NullMapClass;
3426
3427 /* debug mode: don't actually open an LDAP connection */
3428 map->map_orgclass = map->map_class;
3429 map->map_class = &NullMapClass;
3430 map->map_mflags |= MF_OPEN;
3431 map->map_pid = CurrentPid;
3432 return true;
3433 }
3434
3435 /* No connection yet, connect */
3436 if (!sm_ldap_start(map->map_mname, lmap))
3437 {
3438 if (errno == ETIMEDOUT)
3439 {
3440 if (LogLevel > 1)
3441 sm_syslog(LOG_NOTICE, CurEnv->e_id,
3442 "timeout conning to LDAP server %.100s",
3443 id);
3444 }
3445
3446 if (!bitset(MF_OPTIONAL, map->map_mflags))
3447 {
3448 if (bitset(MF_NODEFER, map->map_mflags))
3449 {
3450 syserr("%s failed to %s in map %s",
3451 # if USE_LDAP_INIT
3452 "ldap_init/ldap_bind",
3453 # else /* USE_LDAP_INIT */
3454 "ldap_open",
3455 # endif /* USE_LDAP_INIT */
3456 id, map->map_mname);
3457 }
3458 else
3459 {
3460 syserr("451 4.3.5 %s failed to %s in map %s",
3461 # if USE_LDAP_INIT
3462 "ldap_init/ldap_bind",
3463 # else /* USE_LDAP_INIT */
3464 "ldap_open",
3465 # endif /* USE_LDAP_INIT */
3466 id, map->map_mname);
3467 }
3468 }
3469 return false;
3470 }
3471
3472 /* Save connection for reuse */
3473 s->s_lmap = map;
3474 return true;
3475 }
3476
3477 /*
3478 ** LDAPMAP_CLOSE -- close ldap map
3479 */
3480
3481 void
ldapmap_close(map)3482 ldapmap_close(map)
3483 MAP *map;
3484 {
3485 SM_LDAP_STRUCT *lmap;
3486 STAB *s;
3487
3488 if (tTd(38, 2))
3489 sm_dprintf("ldapmap_close(%s)\n", map->map_mname);
3490
3491 lmap = (SM_LDAP_STRUCT *) map->map_db1;
3492
3493 /* Check if already closed */
3494 if (lmap->ldap_ld == NULL)
3495 return;
3496
3497 /* Close the LDAP connection */
3498 sm_ldap_close(lmap);
3499
3500 /* Mark all the maps that share the connection as closed */
3501 s = ldapmap_findconn(lmap);
3502
3503 while (s->s_lmap != NULL)
3504 {
3505 MAP *smap = s->s_lmap;
3506
3507 if (tTd(38, 2) && smap != map)
3508 sm_dprintf("ldapmap_close(%s): closed %s (shared LDAP connection)\n",
3509 map->map_mname, smap->map_mname);
3510 smap->map_mflags &= ~(MF_OPEN|MF_WRITABLE);
3511 lmap = (SM_LDAP_STRUCT *) smap->map_db1;
3512 lmap->ldap_ld = NULL;
3513 s->s_lmap = lmap->ldap_next;
3514 lmap->ldap_next = NULL;
3515 }
3516 }
3517
3518 # ifdef SUNET_ID
3519 /*
3520 ** SUNET_ID_HASH -- Convert a string to its Sunet_id canonical form
3521 ** This only makes sense at Stanford University.
3522 */
3523
3524 static char *
sunet_id_hash(str)3525 sunet_id_hash(str)
3526 char *str;
3527 {
3528 char *p, *p_last;
3529
3530 p = str;
3531 p_last = p;
3532 while (*p != '\0')
3533 {
3534 if (isascii(*p) && (islower(*p) || isdigit(*p)))
3535 {
3536 *p_last = *p;
3537 p_last++;
3538 }
3539 else if (isascii(*p) && isupper(*p))
3540 {
3541 *p_last = tolower(*p);
3542 p_last++;
3543 }
3544 ++p;
3545 }
3546 if (*p_last != '\0')
3547 *p_last = '\0';
3548 return str;
3549 }
3550 # define SM_CONVERT_ID(str) sunet_id_hash(str)
3551 # else /* SUNET_ID */
3552 # define SM_CONVERT_ID(str) makelower(str)
3553 # endif /* SUNET_ID */
3554
3555 /*
3556 ** LDAPMAP_LOOKUP -- look up a datum in a LDAP map
3557 */
3558
3559 char *
ldapmap_lookup(map,name,av,statp)3560 ldapmap_lookup(map, name, av, statp)
3561 MAP *map;
3562 char *name;
3563 char **av;
3564 int *statp;
3565 {
3566 int flags;
3567 int i;
3568 int plen = 0;
3569 int psize = 0;
3570 int msgid;
3571 int save_errno;
3572 char *vp, *p;
3573 char *result = NULL;
3574 SM_RPOOL_T *rpool;
3575 SM_LDAP_STRUCT *lmap = NULL;
3576 char *argv[SM_LDAP_ARGS];
3577 char keybuf[MAXKEY];
3578 #if SM_LDAP_ARGS != MAX_MAP_ARGS
3579 # ERROR _SM_LDAP_ARGS must be the same as _MAX_MAP_ARGS
3580 #endif /* SM_LDAP_ARGS != MAX_MAP_ARGS */
3581
3582 #if defined(SUN_EXTENSIONS) && defined(SUN_SIMPLIFIED_LDAP) && \
3583 HASLDAPGETALIASBYNAME
3584 if (VendorCode == VENDOR_SUN &&
3585 strcmp(map->map_mname, "aliases.ldap") == 0)
3586 {
3587 int rc;
3588 #if defined(GETLDAPALIASBYNAME_VERSION) && (GETLDAPALIASBYNAME_VERSION >= 2)
3589 extern char *__getldapaliasbyname();
3590 char *answer;
3591
3592 answer = __getldapaliasbyname(name, &rc);
3593 #else
3594 char answer[MAXNAME + 1];
3595
3596 rc = __getldapaliasbyname(name, answer, sizeof(answer));
3597 #endif
3598 if (rc != 0)
3599 {
3600 if (tTd(38, 20))