diff options
Diffstat (limited to 'contrib/sendmail/src/map.c')
-rw-r--r-- | contrib/sendmail/src/map.c | 8013 |
1 files changed, 8013 insertions, 0 deletions
diff --git a/contrib/sendmail/src/map.c b/contrib/sendmail/src/map.c new file mode 100644 index 0000000..be88685 --- /dev/null +++ b/contrib/sendmail/src/map.c @@ -0,0 +1,8013 @@ +/* + * Copyright (c) 1998-2008 Sendmail, Inc. and its suppliers. + * All rights reserved. + * Copyright (c) 1992, 1995-1997 Eric P. Allman. All rights reserved. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the sendmail distribution. + * + */ + +#include <sendmail.h> + +SM_RCSID("@(#)$Id: map.c,v 8.705 2009/08/11 22:22:40 ca Exp $") + +#if LDAPMAP +# include <sm/ldap.h> +#endif /* LDAPMAP */ + +#if NDBM +# include <ndbm.h> +# ifdef R_FIRST + ERROR README: You are running the Berkeley DB version of ndbm.h. See + ERROR README: the README file about tweaking Berkeley DB so it can + ERROR README: coexist with NDBM, or delete -DNDBM from the Makefile + ERROR README: and use -DNEWDB instead. +# endif /* R_FIRST */ +#endif /* NDBM */ +#if NEWDB +# include "sm/bdb.h" +#endif /* NEWDB */ +#if NIS + struct dom_binding; /* forward reference needed on IRIX */ +# include <rpcsvc/ypclnt.h> +# if NDBM +# define NDBM_YP_COMPAT /* create YP-compatible NDBM files */ +# endif /* NDBM */ +#endif /* NIS */ + +#include "map.h" + +#if NEWDB +# if DB_VERSION_MAJOR < 2 +static bool db_map_open __P((MAP *, int, char *, DBTYPE, const void *)); +# endif /* DB_VERSION_MAJOR < 2 */ +# if DB_VERSION_MAJOR == 2 +static bool db_map_open __P((MAP *, int, char *, DBTYPE, DB_INFO *)); +# endif /* DB_VERSION_MAJOR == 2 */ +# if DB_VERSION_MAJOR > 2 +static bool db_map_open __P((MAP *, int, char *, DBTYPE, void **)); +# endif /* DB_VERSION_MAJOR > 2 */ +#endif /* NEWDB */ +static bool extract_canonname __P((char *, char *, char *, char[], int)); +static void map_close __P((STAB *, int)); +static void map_init __P((STAB *, int)); +#ifdef LDAPMAP +static STAB * ldapmap_findconn __P((SM_LDAP_STRUCT *)); +#endif /* LDAPMAP */ +#if NISPLUS +static bool nisplus_getcanonname __P((char *, int, int *)); +#endif /* NISPLUS */ +#if NIS +static bool nis_getcanonname __P((char *, int, int *)); +#endif /* NIS */ +#if NETINFO +static bool ni_getcanonname __P((char *, int, int *)); +#endif /* NETINFO */ +static bool text_getcanonname __P((char *, int, int *)); +#if SOCKETMAP +static STAB *socket_map_findconn __P((const char*)); + +/* XXX arbitrary limit for sanity */ +# define SOCKETMAP_MAXL 1000000 +#endif /* SOCKETMAP */ + +/* default error message for trying to open a map in write mode */ +#ifdef ENOSYS +# define SM_EMAPCANTWRITE ENOSYS +#else /* ENOSYS */ +# ifdef EFTYPE +# define SM_EMAPCANTWRITE EFTYPE +# else /* EFTYPE */ +# define SM_EMAPCANTWRITE ENXIO +# endif /* EFTYPE */ +#endif /* ENOSYS */ + +/* +** MAP.C -- implementations for various map classes. +** +** Each map class implements a series of functions: +** +** bool map_parse(MAP *map, char *args) +** Parse the arguments from the config file. Return true +** if they were ok, false otherwise. Fill in map with the +** values. +** +** char *map_lookup(MAP *map, char *key, char **args, int *pstat) +** Look up the key in the given map. If found, do any +** rewriting the map wants (including "args" if desired) +** and return the value. Set *pstat to the appropriate status +** on error and return NULL. Args will be NULL if called +** from the alias routines, although this should probably +** not be relied upon. It is suggested you call map_rewrite +** to return the results -- it takes care of null termination +** and uses a dynamically expanded buffer as needed. +** +** void map_store(MAP *map, char *key, char *value) +** Store the key:value pair in the map. +** +** bool map_open(MAP *map, int mode) +** Open the map for the indicated mode. Mode should +** be either O_RDONLY or O_RDWR. Return true if it +** was opened successfully, false otherwise. If the open +** failed and the MF_OPTIONAL flag is not set, it should +** also print an error. If the MF_ALIAS bit is set +** and this map class understands the @:@ convention, it +** should call aliaswait() before returning. +** +** void map_close(MAP *map) +** Close the map. +** +** This file also includes the implementation for getcanonname. +** It is currently implemented in a pretty ad-hoc manner; it ought +** to be more properly integrated into the map structure. +*/ + +#if O_EXLOCK && HASFLOCK && !BOGUS_O_EXCL +# define LOCK_ON_OPEN 1 /* we can open/create a locked file */ +#else /* O_EXLOCK && HASFLOCK && !BOGUS_O_EXCL */ +# define LOCK_ON_OPEN 0 /* no such luck -- bend over backwards */ +#endif /* O_EXLOCK && HASFLOCK && !BOGUS_O_EXCL */ + +/* +** MAP_PARSEARGS -- parse config line arguments for database lookup +** +** This is a generic version of the map_parse method. +** +** Parameters: +** map -- the map being initialized. +** ap -- a pointer to the args on the config line. +** +** Returns: +** true -- if everything parsed OK. +** false -- otherwise. +** +** Side Effects: +** null terminates the filename; stores it in map +*/ + +bool +map_parseargs(map, ap) + MAP *map; + char *ap; +{ + register char *p = ap; + + /* + ** There is no check whether there is really an argument, + ** but that's not important enough to warrant extra code. + */ + + map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL; + map->map_spacesub = SpaceSub; /* default value */ + for (;;) + { + while (isascii(*p) && isspace(*p)) + p++; + if (*p != '-') + break; + switch (*++p) + { + case 'N': + map->map_mflags |= MF_INCLNULL; + map->map_mflags &= ~MF_TRY0NULL; + break; + + case 'O': + map->map_mflags &= ~MF_TRY1NULL; + break; + + case 'o': + map->map_mflags |= MF_OPTIONAL; + break; + + case 'f': + map->map_mflags |= MF_NOFOLDCASE; + break; + + case 'm': + map->map_mflags |= MF_MATCHONLY; + break; + + case 'A': + map->map_mflags |= MF_APPEND; + break; + + case 'q': + map->map_mflags |= MF_KEEPQUOTES; + break; + + case 'a': + map->map_app = ++p; + break; + + case 'T': + map->map_tapp = ++p; + break; + + case 'k': + while (isascii(*++p) && isspace(*p)) + continue; + map->map_keycolnm = p; + break; + + case 'v': + while (isascii(*++p) && isspace(*p)) + continue; + map->map_valcolnm = p; + break; + + case 'z': + if (*++p != '\\') + map->map_coldelim = *p; + else + { + switch (*++p) + { + case 'n': + map->map_coldelim = '\n'; + break; + + case 't': + map->map_coldelim = '\t'; + break; + + default: + map->map_coldelim = '\\'; + } + } + break; + + case 't': + map->map_mflags |= MF_NODEFER; + break; + + + case 'S': + map->map_spacesub = *++p; + break; + + case 'D': + map->map_mflags |= MF_DEFER; + break; + + default: + syserr("Illegal option %c map %s", *p, map->map_mname); + break; + } + while (*p != '\0' && !(isascii(*p) && isspace(*p))) + p++; + if (*p != '\0') + *p++ = '\0'; + } + if (map->map_app != NULL) + map->map_app = newstr(map->map_app); + if (map->map_tapp != NULL) + map->map_tapp = newstr(map->map_tapp); + if (map->map_keycolnm != NULL) + map->map_keycolnm = newstr(map->map_keycolnm); + if (map->map_valcolnm != NULL) + map->map_valcolnm = newstr(map->map_valcolnm); + + if (*p != '\0') + { + map->map_file = p; + while (*p != '\0' && !(isascii(*p) && isspace(*p))) + p++; + if (*p != '\0') + *p++ = '\0'; + map->map_file = newstr(map->map_file); + } + + while (*p != '\0' && isascii(*p) && isspace(*p)) + p++; + if (*p != '\0') + map->map_rebuild = newstr(p); + + if (map->map_file == NULL && + !bitset(MCF_OPTFILE, map->map_class->map_cflags)) + { + syserr("No file name for %s map %s", + map->map_class->map_cname, map->map_mname); + return false; + } + return true; +} +/* +** MAP_REWRITE -- rewrite a database key, interpolating %n indications. +** +** It also adds the map_app string. It can be used as a utility +** in the map_lookup method. +** +** Parameters: +** map -- the map that causes this. +** s -- the string to rewrite, NOT necessarily null terminated. +** slen -- the length of s. +** av -- arguments to interpolate into buf. +** +** Returns: +** Pointer to rewritten result. This is static data that +** should be copied if it is to be saved! +*/ + +char * +map_rewrite(map, s, slen, av) + register MAP *map; + register const char *s; + size_t slen; + char **av; +{ + register char *bp; + register char c; + char **avp; + register char *ap; + size_t l; + size_t len; + static size_t buflen = 0; + static char *buf = NULL; + + if (tTd(39, 1)) + { + sm_dprintf("map_rewrite(%.*s), av =", (int) slen, s); + if (av == NULL) + sm_dprintf(" (nullv)"); + else + { + for (avp = av; *avp != NULL; avp++) + sm_dprintf("\n\t%s", *avp); + } + sm_dprintf("\n"); + } + + /* count expected size of output (can safely overestimate) */ + l = len = slen; + if (av != NULL) + { + const char *sp = s; + + while (l-- > 0 && (c = *sp++) != '\0') + { + if (c != '%') + continue; + if (l-- <= 0) + break; + c = *sp++; + if (!(isascii(c) && isdigit(c))) + continue; + for (avp = av; --c >= '0' && *avp != NULL; avp++) + continue; + if (*avp == NULL) + continue; + len += strlen(*avp); + } + } + if (map->map_app != NULL) + len += strlen(map->map_app); + if (buflen < ++len) + { + /* need to malloc additional space */ + buflen = len; + if (buf != NULL) + sm_free(buf); + buf = sm_pmalloc_x(buflen); + } + + bp = buf; + if (av == NULL) + { + memmove(bp, s, slen); + bp += slen; + + /* assert(len > slen); */ + len -= slen; + } + else + { + while (slen-- > 0 && (c = *s++) != '\0') + { + if (c != '%') + { + pushc: + if (len-- <= 1) + break; + *bp++ = c; + continue; + } + if (slen-- <= 0 || (c = *s++) == '\0') + c = '%'; + if (c == '%') + goto pushc; + if (!(isascii(c) && isdigit(c))) + { + if (len-- <= 1) + break; + *bp++ = '%'; + goto pushc; + } + for (avp = av; --c >= '0' && *avp != NULL; avp++) + continue; + if (*avp == NULL) + continue; + + /* transliterate argument into output string */ + for (ap = *avp; (c = *ap++) != '\0' && len > 0; --len) + *bp++ = c; + } + } + if (map->map_app != NULL && len > 0) + (void) sm_strlcpy(bp, map->map_app, len); + else + *bp = '\0'; + if (tTd(39, 1)) + sm_dprintf("map_rewrite => %s\n", buf); + return buf; +} +/* +** INITMAPS -- rebuild alias maps +** +** Parameters: +** none. +** +** Returns: +** none. +*/ + +void +initmaps() +{ +#if XDEBUG + checkfd012("entering initmaps"); +#endif /* XDEBUG */ + stabapply(map_init, 0); +#if XDEBUG + checkfd012("exiting initmaps"); +#endif /* XDEBUG */ +} +/* +** MAP_INIT -- rebuild a map +** +** Parameters: +** s -- STAB entry: if map: try to rebuild +** unused -- unused variable +** +** Returns: +** none. +** +** Side Effects: +** will close already open rebuildable map. +*/ + +/* ARGSUSED1 */ +static void +map_init(s, unused) + register STAB *s; + int unused; +{ + register MAP *map; + + /* has to be a map */ + if (s->s_symtype != ST_MAP) + return; + + map = &s->s_map; + if (!bitset(MF_VALID, map->map_mflags)) + return; + + if (tTd(38, 2)) + sm_dprintf("map_init(%s:%s, %s)\n", + map->map_class->map_cname == NULL ? "NULL" : + map->map_class->map_cname, + map->map_mname == NULL ? "NULL" : map->map_mname, + map->map_file == NULL ? "NULL" : map->map_file); + + if (!bitset(MF_ALIAS, map->map_mflags) || + !bitset(MCF_REBUILDABLE, map->map_class->map_cflags)) + { + if (tTd(38, 3)) + sm_dprintf("\tnot rebuildable\n"); + return; + } + + /* if already open, close it (for nested open) */ + if (bitset(MF_OPEN, map->map_mflags)) + { + map->map_mflags |= MF_CLOSING; + map->map_class->map_close(map); + map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING); + } + + (void) rebuildaliases(map, false); + return; +} +/* +** OPENMAP -- open a map +** +** Parameters: +** map -- map to open (it must not be open). +** +** Returns: +** whether open succeeded. +*/ + +bool +openmap(map) + MAP *map; +{ + bool restore = false; + bool savehold = HoldErrs; + bool savequick = QuickAbort; + int saveerrors = Errors; + + if (!bitset(MF_VALID, map->map_mflags)) + return false; + + /* better safe than sorry... */ + if (bitset(MF_OPEN, map->map_mflags)) + return true; + + /* Don't send a map open error out via SMTP */ + if ((OnlyOneError || QuickAbort) && + (OpMode == MD_SMTP || OpMode == MD_DAEMON)) + { + restore = true; + HoldErrs = true; + QuickAbort = false; + } + + errno = 0; + if (map->map_class->map_open(map, O_RDONLY)) + { + if (tTd(38, 4)) + sm_dprintf("openmap()\t%s:%s %s: valid\n", + map->map_class->map_cname == NULL ? "NULL" : + map->map_class->map_cname, + map->map_mname == NULL ? "NULL" : + map->map_mname, + map->map_file == NULL ? "NULL" : + map->map_file); + map->map_mflags |= MF_OPEN; + map->map_pid = CurrentPid; + } + else + { + if (tTd(38, 4)) + sm_dprintf("openmap()\t%s:%s %s: invalid%s%s\n", + map->map_class->map_cname == NULL ? "NULL" : + map->map_class->map_cname, + map->map_mname == NULL ? "NULL" : + map->map_mname, + map->map_file == NULL ? "NULL" : + map->map_file, + errno == 0 ? "" : ": ", + errno == 0 ? "" : sm_errstring(errno)); + if (!bitset(MF_OPTIONAL, map->map_mflags)) + { + extern MAPCLASS BogusMapClass; + + map->map_orgclass = map->map_class; + map->map_class = &BogusMapClass; + map->map_mflags |= MF_OPEN|MF_OPENBOGUS; + map->map_pid = CurrentPid; + } + else + { + /* don't try again */ + map->map_mflags &= ~MF_VALID; + } + } + + if (restore) + { + Errors = saveerrors; + HoldErrs = savehold; + QuickAbort = savequick; + } + + return bitset(MF_OPEN, map->map_mflags); +} +/* +** CLOSEMAPS -- close all open maps opened by the current pid. +** +** Parameters: +** bogus -- only close bogus maps. +** +** Returns: +** none. +*/ + +void +closemaps(bogus) + bool bogus; +{ + stabapply(map_close, bogus); +} +/* +** MAP_CLOSE -- close a map opened by the current pid. +** +** Parameters: +** s -- STAB entry: if map: try to close +** bogus -- only close bogus maps or MCF_NOTPERSIST maps. +** +** Returns: +** none. +*/ + +/* ARGSUSED1 */ +static void +map_close(s, bogus) + register STAB *s; + int bogus; /* int because of stabapply(), used as bool */ +{ + MAP *map; + extern MAPCLASS BogusMapClass; + + if (s->s_symtype != ST_MAP) + return; + + map = &s->s_map; + + /* + ** close the map iff: + ** it is valid and open and opened by this process + ** and (!bogus or it's a bogus map or it is not persistent) + ** negate this: return iff + ** it is not valid or it is not open or not opened by this process + ** or (bogus and it's not a bogus map and it's not not-persistent) + */ + + if (!bitset(MF_VALID, map->map_mflags) || + !bitset(MF_OPEN, map->map_mflags) || + bitset(MF_CLOSING, map->map_mflags) || + map->map_pid != CurrentPid || + (bogus && map->map_class != &BogusMapClass && + !bitset(MCF_NOTPERSIST, map->map_class->map_cflags))) + return; + + if (map->map_class == &BogusMapClass && map->map_orgclass != NULL && + map->map_orgclass != &BogusMapClass) + map->map_class = map->map_orgclass; + if (tTd(38, 5)) + sm_dprintf("closemaps: closing %s (%s)\n", + map->map_mname == NULL ? "NULL" : map->map_mname, + map->map_file == NULL ? "NULL" : map->map_file); + + if (!bitset(MF_OPENBOGUS, map->map_mflags)) + { + map->map_mflags |= MF_CLOSING; + map->map_class->map_close(map); + } + map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_OPENBOGUS|MF_CLOSING); +} + +#if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN) +extern int getdomainname(); + +/* this is mainly for backward compatibility in Sun environment */ +static char * +sun_init_domain() +{ + /* + ** Get the domain name from the kernel. + ** If it does not start with a leading dot, then remove + ** the first component. Since leading dots are funny Unix + ** files, we treat a leading "+" the same as a leading dot. + ** Finally, force there to be at least one dot in the domain name + ** (i.e. top-level domains are not allowed, like "com", must be + ** something like "sun.com"). + */ + + char buf[MAXNAME]; + char *period, *autodomain; + + if (getdomainname(buf, sizeof buf) < 0) + return NULL; + + if (buf[0] == '\0') + return NULL; + + if (tTd(0, 20)) + printf("domainname = %s\n", buf); + + if (buf[0] == '+') + buf[0] = '.'; + period = strchr(buf, '.'); + if (period == NULL) + autodomain = buf; + else + autodomain = period + 1; + if (strchr(autodomain, '.') == NULL) + return newstr(buf); + else + return newstr(autodomain); +} +#endif /* SUN_EXTENSIONS && SUN_INIT_DOMAIN */ + +/* +** GETCANONNAME -- look up name using service switch +** +** Parameters: +** host -- the host name to look up. +** hbsize -- the size of the host buffer. +** trymx -- if set, try MX records. +** pttl -- pointer to return TTL (can be NULL). +** +** Returns: +** true -- if the host was found. +** false -- otherwise. +*/ + +bool +getcanonname(host, hbsize, trymx, pttl) + char *host; + int hbsize; + bool trymx; + int *pttl; +{ + int nmaps; + int mapno; + bool found = false; + bool got_tempfail = false; + auto int status = EX_UNAVAILABLE; + char *maptype[MAXMAPSTACK]; + short mapreturn[MAXMAPACTIONS]; +#if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN) + bool should_try_nis_domain = false; + static char *nis_domain = NULL; +#endif + + nmaps = switch_map_find("hosts", maptype, mapreturn); + if (pttl != 0) + *pttl = SM_DEFAULT_TTL; + for (mapno = 0; mapno < nmaps; mapno++) + { + int i; + + if (tTd(38, 20)) + sm_dprintf("getcanonname(%s), trying %s\n", + host, maptype[mapno]); + if (strcmp("files", maptype[mapno]) == 0) + { + found = text_getcanonname(host, hbsize, &status); + } +#if NIS + else if (strcmp("nis", maptype[mapno]) == 0) + { + found = nis_getcanonname(host, hbsize, &status); +# if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN) + if (nis_domain == NULL) + nis_domain = sun_init_domain(); +# endif /* defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN) */ + } +#endif /* NIS */ +#if NISPLUS + else if (strcmp("nisplus", maptype[mapno]) == 0) + { + found = nisplus_getcanonname(host, hbsize, &status); +# if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN) + if (nis_domain == NULL) + nis_domain = sun_init_domain(); +# endif /* defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN) */ + } +#endif /* NISPLUS */ +#if NAMED_BIND + else if (strcmp("dns", maptype[mapno]) == 0) + { + found = dns_getcanonname(host, hbsize, trymx, &status, pttl); + } +#endif /* NAMED_BIND */ +#if NETINFO + else if (strcmp("netinfo", maptype[mapno]) == 0) + { + found = ni_getcanonname(host, hbsize, &status); + } +#endif /* NETINFO */ + else + { + found = false; + status = EX_UNAVAILABLE; + } + + /* + ** Heuristic: if $m is not set, we are running during system + ** startup. In this case, when a name is apparently found + ** but has no dot, treat is as not found. This avoids + ** problems if /etc/hosts has no FQDN but is listed first + ** in the service switch. + */ + + if (found && + (macvalue('m', CurEnv) != NULL || strchr(host, '.') != NULL)) + break; + +#if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN) + if (found) + should_try_nis_domain = true; + /* but don't break, as we need to try all methods first */ +#endif /* defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN) */ + + /* see if we should continue */ + if (status == EX_TEMPFAIL) + { + i = MA_TRYAGAIN; + got_tempfail = true; + } + else if (status == EX_NOTFOUND) + i = MA_NOTFOUND; + else + i = MA_UNAVAIL; + if (bitset(1 << mapno, mapreturn[i])) + break; + } + + if (found) + { + char *d; + + if (tTd(38, 20)) + sm_dprintf("getcanonname(%s), found\n", host); + + /* + ** If returned name is still single token, compensate + ** by tagging on $m. This is because some sites set + ** up their DNS or NIS databases wrong. + */ + + if ((d = strchr(host, '.')) == NULL || d[1] == '\0') + { + d = macvalue('m', CurEnv); + if (d != NULL && + hbsize > (int) (strlen(host) + strlen(d) + 1)) + { + if (host[strlen(host) - 1] != '.') + (void) sm_strlcat2(host, ".", d, + hbsize); + else + (void) sm_strlcat(host, d, hbsize); + } + else + { +#if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN) + if (VendorCode == VENDOR_SUN && + should_try_nis_domain) + { + goto try_nis_domain; + } +#endif /* defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN) */ + return false; + } + } + return true; + } + +#if defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN) + if (VendorCode == VENDOR_SUN && should_try_nis_domain) + { + try_nis_domain: + if (nis_domain != NULL && + strlen(nis_domain) + strlen(host) + 1 < hbsize) + { + (void) sm_strlcat2(host, ".", nis_domain, hbsize); + return true; + } + } +#endif /* defined(SUN_EXTENSIONS) && defined(SUN_INIT_DOMAIN) */ + + if (tTd(38, 20)) + sm_dprintf("getcanonname(%s), failed, status=%d\n", host, + status); + + if (got_tempfail) + SM_SET_H_ERRNO(TRY_AGAIN); + else + SM_SET_H_ERRNO(HOST_NOT_FOUND); + + return false; +} +/* +** EXTRACT_CANONNAME -- extract canonical name from /etc/hosts entry +** +** Parameters: +** name -- the name against which to match. +** dot -- where to reinsert '.' to get FQDN +** line -- the /etc/hosts line. +** cbuf -- the location to store the result. +** cbuflen -- the size of cbuf. +** +** Returns: +** true -- if the line matched the desired name. +** false -- otherwise. +*/ + +static bool +extract_canonname(name, dot, line, cbuf, cbuflen) + char *name; + char *dot; + char *line; + char cbuf[]; + int cbuflen; +{ + int i; + char *p; + bool found = false; + + cbuf[0] = '\0'; + if (line[0] == '#') + return false; + + for (i = 1; ; i++) + { + char nbuf[MAXNAME + 1]; + + p = get_column(line, i, '\0', nbuf, sizeof(nbuf)); + if (p == NULL) + break; + if (*p == '\0') + continue; + if (cbuf[0] == '\0' || + (strchr(cbuf, '.') == NULL && strchr(p, '.') != NULL)) + { + (void) sm_strlcpy(cbuf, p, cbuflen); + } + if (sm_strcasecmp(name, p) == 0) + found = true; + else if (dot != NULL) + { + /* try looking for the FQDN as well */ + *dot = '.'; + if (sm_strcasecmp(name, p) == 0) + found = true; + *dot = '\0'; + } + } + if (found && strchr(cbuf, '.') == NULL) + { + /* try to add a domain on the end of the name */ + char *domain = macvalue('m', CurEnv); + + if (domain != NULL && + strlen(domain) + (i = strlen(cbuf)) + 1 < (size_t) cbuflen) + { + p = &cbuf[i]; + *p++ = '.'; + (void) sm_strlcpy(p, domain, cbuflen - i - 1); + } + } + return found; +} + +/* +** DNS modules +*/ + +#if NAMED_BIND +# if DNSMAP + +# include "sm_resolve.h" +# if NETINET || NETINET6 +# include <arpa/inet.h> +# endif /* NETINET || NETINET6 */ + +/* +** DNS_MAP_OPEN -- stub to check proper value for dns map type +*/ + +bool +dns_map_open(map, mode) + MAP *map; + int mode; +{ + if (tTd(38,2)) + sm_dprintf("dns_map_open(%s, %d)\n", map->map_mname, mode); + + mode &= O_ACCMODE; + if (mode != O_RDONLY) + { + /* issue a pseudo-error message */ + errno = SM_EMAPCANTWRITE; + return false; + } + return true; +} + +/* +** DNS_MAP_PARSEARGS -- parse dns map definition args. +** +** Parameters: +** map -- pointer to MAP +** args -- pointer to the args on the config line. +** +** Returns: +** true -- if everything parsed OK. +** false -- otherwise. +*/ + +#define map_sizelimit map_lockfd /* overload field */ + +struct dns_map +{ + int dns_m_type; +}; + +bool +dns_map_parseargs(map,args) + MAP *map; + char *args; +{ + register char *p = args; + struct dns_map *map_p; + + map_p = (struct dns_map *) xalloc(sizeof(*map_p)); + map_p->dns_m_type = -1; + map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL; + + for (;;) + { + while (isascii(*p) && isspace(*p)) + p++; + if (*p != '-') + break; + switch (*++p) + { + case 'N': + map->map_mflags |= MF_INCLNULL; + map->map_mflags &= ~MF_TRY0NULL; + break; + + case 'O': + map->map_mflags &= ~MF_TRY1NULL; + break; + + case 'o': + map->map_mflags |= MF_OPTIONAL; + break; + + case 'f': + map->map_mflags |= MF_NOFOLDCASE; + break; + + case 'm': + map->map_mflags |= MF_MATCHONLY; + break; + + case 'A': + map->map_mflags |= MF_APPEND; + break; + + case 'q': + map->map_mflags |= MF_KEEPQUOTES; + break; + + case 't': + map->map_mflags |= MF_NODEFER; + break; + + case 'a': + map->map_app = ++p; + break; + + case 'T': + map->map_tapp = ++p; + break; + + case 'd': + { + char *h; + + ++p; + h = strchr(p, ' '); + if (h != NULL) + *h = '\0'; + map->map_timeout = convtime(p, 's'); + if (h != NULL) + *h = ' '; + } + break; + + case 'r': + while (isascii(*++p) && isspace(*p)) + continue; + map->map_retry = atoi(p); + break; + + case 'z': + if (*++p != '\\') + map->map_coldelim = *p; + else + { + switch (*++p) + { + case 'n': + map->map_coldelim = '\n'; + break; + + case 't': + map->map_coldelim = '\t'; + break; + + default: + map->map_coldelim = '\\'; + } + } + break; + + case 'Z': + while (isascii(*++p) && isspace(*p)) + continue; + map->map_sizelimit = atoi(p); + break; + + /* Start of dns_map specific args */ + case 'R': /* search field */ + { + char *h; + + while (isascii(*++p) && isspace(*p)) + continue; + h = strchr(p, ' '); + if (h != NULL) + *h = '\0'; + map_p->dns_m_type = dns_string_to_type(p); + if (h != NULL) + *h = ' '; + if (map_p->dns_m_type < 0) + syserr("dns map %s: wrong type %s", + map->map_mname, p); + } + break; + + case 'B': /* base domain */ + { + char *h; + + while (isascii(*++p) && isspace(*p)) + continue; + h = strchr(p, ' '); + if (h != NULL) + *h = '\0'; + + /* + ** slight abuse of map->map_file; it isn't + ** used otherwise in this map type. + */ + + map->map_file = newstr(p); + if (h != NULL) + *h = ' '; + } + break; + } + while (*p != '\0' && !(isascii(*p) && isspace(*p))) + p++; + if (*p != '\0') + *p++ = '\0'; + } + if (map_p->dns_m_type < 0) + syserr("dns map %s: missing -R type", map->map_mname); + if (map->map_app != NULL) + map->map_app = newstr(map->map_app); + if (map->map_tapp != NULL) + map->map_tapp = newstr(map->map_tapp); + + /* + ** Assumption: assert(sizeof(int) <= sizeof(ARBPTR_T)); + ** Even if this assumption is wrong, we use only one byte, + ** so it doesn't really matter. + */ + + map->map_db1 = (ARBPTR_T) map_p; + return true; +} + +/* +** DNS_MAP_LOOKUP -- perform dns map lookup. +** +** Parameters: +** map -- pointer to MAP +** name -- name to lookup +** av -- arguments to interpolate into buf. +** statp -- pointer to status (EX_) +** +** Returns: +** result of lookup if succeeded. +** NULL -- otherwise. +*/ + +char * +dns_map_lookup(map, name, av, statp) + MAP *map; + char *name; + char **av; + int *statp; +{ + int resnum = 0; + char *vp = NULL, *result = NULL; + size_t vsize; + struct dns_map *map_p; + RESOURCE_RECORD_T *rr = NULL; + DNS_REPLY_T *r = NULL; +# if NETINET6 + static char buf6[INET6_ADDRSTRLEN]; +# endif /* NETINET6 */ + + if (tTd(38, 20)) + sm_dprintf("dns_map_lookup(%s, %s)\n", + map->map_mname, name); + + map_p = (struct dns_map *)(map->map_db1); + if (map->map_file != NULL && *map->map_file != '\0') + { + size_t len; + char *appdomain; + + len = strlen(map->map_file) + strlen(name) + 2; + appdomain = (char *) sm_malloc(len); + if (appdomain == NULL) + { + *statp = EX_UNAVAILABLE; + return NULL; + } + (void) sm_strlcpyn(appdomain, len, 3, name, ".", map->map_file); + r = dns_lookup_int(appdomain, C_IN, map_p->dns_m_type, + map->map_timeout, map->map_retry); + sm_free(appdomain); + } + else + { + r = dns_lookup_int(name, C_IN, map_p->dns_m_type, + map->map_timeout, map->map_retry); + } + + if (r == NULL) + { + result = NULL; + if (h_errno == TRY_AGAIN || transienterror(errno)) + *statp = EX_TEMPFAIL; + else + *statp = EX_NOTFOUND; + goto cleanup; + } + *statp = EX_OK; + for (rr = r->dns_r_head; rr != NULL; rr = rr->rr_next) + { + char *type = NULL; + char *value = NULL; + + switch (rr->rr_type) + { + case T_NS: + type = "T_NS"; + value = rr->rr_u.rr_txt; + break; + case T_CNAME: + type = "T_CNAME"; + value = rr->rr_u.rr_txt; + break; + case T_AFSDB: + type = "T_AFSDB"; + value = rr->rr_u.rr_mx->mx_r_domain; + break; + case T_SRV: + type = "T_SRV"; + value = rr->rr_u.rr_srv->srv_r_target; + break; + case T_PTR: + type = "T_PTR"; + value = rr->rr_u.rr_txt; + break; + case T_TXT: + type = "T_TXT"; + value = rr->rr_u.rr_txt; + break; + case T_MX: + type = "T_MX"; + value = rr->rr_u.rr_mx->mx_r_domain; + break; +# if NETINET + case T_A: + type = "T_A"; + value = inet_ntoa(*(rr->rr_u.rr_a)); + break; +# endif /* NETINET */ +# if NETINET6 + case T_AAAA: + type = "T_AAAA"; + value = anynet_ntop(rr->rr_u.rr_aaaa, buf6, + sizeof(buf6)); + break; +# endif /* NETINET6 */ + } + + (void) strreplnonprt(value, 'X'); + if (map_p->dns_m_type != rr->rr_type) + { + if (tTd(38, 40)) + sm_dprintf("\tskipping type %s (%d) value %s\n", + type != NULL ? type : "<UNKNOWN>", + rr->rr_type, + value != NULL ? value : "<NO VALUE>"); + continue; + } + +# if NETINET6 + if (rr->rr_type == T_AAAA && value == NULL) + { + result = NULL; + *statp = EX_DATAERR; + if (tTd(38, 40)) + sm_dprintf("\tbad T_AAAA conversion\n"); + goto cleanup; + } +# endif /* NETINET6 */ + if (tTd(38, 40)) + sm_dprintf("\tfound type %s (%d) value %s\n", + type != NULL ? type : "<UNKNOWN>", + rr->rr_type, + value != NULL ? value : "<NO VALUE>"); + if (value != NULL && + (map->map_coldelim == '\0' || + map->map_sizelimit == 1 || + bitset(MF_MATCHONLY, map->map_mflags))) + { + /* Only care about the first match */ + vp = newstr(value); + break; + } + else if (vp == NULL) + { + /* First result */ + vp = newstr(value); + } + else + { + /* concatenate the results */ + int sz; + char *new; + + sz = strlen(vp) + strlen(value) + 2; + new = xalloc(sz); + (void) sm_snprintf(new, sz, "%s%c%s", + vp, map->map_coldelim, value); + sm_free(vp); + vp = new; + if (map->map_sizelimit > 0 && + ++resnum >= map->map_sizelimit) + break; + } + } + if (vp == NULL) + { + result = NULL; + *statp = EX_NOTFOUND; + if (tTd(38, 40)) + sm_dprintf("\tno match found\n"); + goto cleanup; + } + + /* Cleanly truncate for rulesets */ + truncate_at_delim(vp, PSBUFSIZE / 2, map->map_coldelim); + + vsize = strlen(vp); + + if (LogLevel > 9) + sm_syslog(LOG_INFO, CurEnv->e_id, "dns %.100s => %s", + name, vp); + if (bitset(MF_MATCHONLY, map->map_mflags)) + result = map_rewrite(map, name, strlen(name), NULL); + else + result = map_rewrite(map, vp, vsize, av); + + cleanup: + if (vp != NULL) + sm_free(vp); + if (r != NULL) + dns_free_data(r); + return result; +} +# endif /* DNSMAP */ +#endif /* NAMED_BIND */ + +/* +** NDBM modules +*/ + +#if NDBM + +/* +** NDBM_MAP_OPEN -- DBM-style map open +*/ + +bool +ndbm_map_open(map, mode) + MAP *map; + int mode; +{ + register DBM *dbm; + int save_errno; + int dfd; + int pfd; + long sff; + int ret; + int smode = S_IREAD; + char dirfile[MAXPATHLEN]; + char pagfile[MAXPATHLEN]; + struct stat st; + struct stat std, stp; + + if (tTd(38, 2)) + sm_dprintf("ndbm_map_open(%s, %s, %d)\n", + map->map_mname, map->map_file, mode); + map->map_lockfd = -1; + mode &= O_ACCMODE; + + /* do initial file and directory checks */ + if (sm_strlcpyn(dirfile, sizeof(dirfile), 2, + map->map_file, ".dir") >= sizeof(dirfile) || + sm_strlcpyn(pagfile, sizeof(pagfile), 2, + map->map_file, ".pag") >= sizeof(pagfile)) + { + errno = 0; + if (!bitset(MF_OPTIONAL, map->map_mflags)) + syserr("dbm map \"%s\": map file %s name too long", + map->map_mname, map->map_file); + return false; + } + sff = SFF_ROOTOK|SFF_REGONLY; + if (mode == O_RDWR) + { + sff |= SFF_CREAT; + if (!bitnset(DBS_WRITEMAPTOSYMLINK, DontBlameSendmail)) + sff |= SFF_NOSLINK; + if (!bitnset(DBS_WRITEMAPTOHARDLINK, DontBlameSendmail)) + sff |= SFF_NOHLINK; + smode = S_IWRITE; + } + else + { + if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail)) + sff |= SFF_NOWLINK; + } + if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail)) + sff |= SFF_SAFEDIRPATH; + ret = safefile(dirfile, RunAsUid, RunAsGid, RunAsUserName, + sff, smode, &std); + if (ret == 0) + ret = safefile(pagfile, RunAsUid, RunAsGid, RunAsUserName, + sff, smode, &stp); + + if (ret != 0) + { + char *prob = "unsafe"; + + /* cannot open this map */ + if (ret == ENOENT) + prob = "missing"; + if (tTd(38, 2)) + sm_dprintf("\t%s map file: %d\n", prob, ret); + if (!bitset(MF_OPTIONAL, map->map_mflags)) + syserr("dbm map \"%s\": %s map file %s", + map->map_mname, prob, map->map_file); + return false; + } + if (std.st_mode == ST_MODE_NOFILE) + mode |= O_CREAT|O_EXCL; + +# if LOCK_ON_OPEN + if (mode == O_RDONLY) + mode |= O_SHLOCK; + else + mode |= O_TRUNC|O_EXLOCK; +# else /* LOCK_ON_OPEN */ + if ((mode & O_ACCMODE) == O_RDWR) + { +# if NOFTRUNCATE + /* + ** Warning: race condition. Try to lock the file as + ** quickly as possible after opening it. + ** This may also have security problems on some systems, + ** but there isn't anything we can do about it. + */ + + mode |= O_TRUNC; +# else /* NOFTRUNCATE */ + /* + ** This ugly code opens the map without truncating it, + ** locks the file, then truncates it. Necessary to + ** avoid race conditions. + */ + + int dirfd; + int pagfd; + long sff = SFF_CREAT|SFF_OPENASROOT; + + if (!bitnset(DBS_WRITEMAPTOSYMLINK, DontBlameSendmail)) + sff |= SFF_NOSLINK; + if (!bitnset(DBS_WRITEMAPTOHARDLINK, DontBlameSendmail)) + sff |= SFF_NOHLINK; + + dirfd = safeopen(dirfile, mode, DBMMODE, sff); + pagfd = safeopen(pagfile, mode, DBMMODE, sff); + + if (dirfd < 0 || pagfd < 0) + { + save_errno = errno; + if (dirfd >= 0) + (void) close(dirfd); + if (pagfd >= 0) + (void) close(pagfd); + errno = save_errno; + syserr("ndbm_map_open: cannot create database %s", + map->map_file); + return false; + } + if (ftruncate(dirfd, (off_t) 0) < 0 || + ftruncate(pagfd, (off_t) 0) < 0) + { + save_errno = errno; + (void) close(dirfd); + (void) close(pagfd); + errno = save_errno; + syserr("ndbm_map_open: cannot truncate %s.{dir,pag}", + map->map_file); + return false; + } + + /* if new file, get "before" bits for later filechanged check */ + if (std.st_mode == ST_MODE_NOFILE && + (fstat(dirfd, &std) < 0 || fstat(pagfd, &stp) < 0)) + { + save_errno = errno; + (void) close(dirfd); + (void) close(pagfd); + errno = save_errno; + syserr("ndbm_map_open(%s.{dir,pag}): cannot fstat pre-opened file", + map->map_file); + return false; + } + + /* have to save the lock for the duration (bletch) */ + map->map_lockfd = dirfd; + (void) close(pagfd); + + /* twiddle bits for dbm_open */ + mode &= ~(O_CREAT|O_EXCL); +# endif /* NOFTRUNCATE */ + } +# endif /* LOCK_ON_OPEN */ + + /* open the database */ + dbm = dbm_open(map->map_file, mode, DBMMODE); + if (dbm == NULL) + { + save_errno = errno; + if (bitset(MF_ALIAS, map->map_mflags) && + aliaswait(map, ".pag", false)) + return true; +# if !LOCK_ON_OPEN && !NOFTRUNCATE + if (map->map_lockfd >= 0) + (void) close(map->map_lockfd); +# endif /* !LOCK_ON_OPEN && !NOFTRUNCATE */ + errno = save_errno; + if (!bitset(MF_OPTIONAL, map->map_mflags)) + syserr("Cannot open DBM database %s", map->map_file); + return false; + } + dfd = dbm_dirfno(dbm); + pfd = dbm_pagfno(dbm); + if (dfd == pfd) + { + /* heuristic: if files are linked, this is actually gdbm */ + dbm_close(dbm); +# if !LOCK_ON_OPEN && !NOFTRUNCATE + if (map->map_lockfd >= 0) + (void) close(map->map_lockfd); +# endif /* !LOCK_ON_OPEN && !NOFTRUNCATE */ + errno = 0; + syserr("dbm map \"%s\": cannot support GDBM", + map->map_mname); + return false; + } + + if (filechanged(dirfile, dfd, &std) || + filechanged(pagfile, pfd, &stp)) + { + save_errno = errno; + dbm_close(dbm); +# if !LOCK_ON_OPEN && !NOFTRUNCATE + if (map->map_lockfd >= 0) + (void) close(map->map_lockfd); +# endif /* !LOCK_ON_OPEN && !NOFTRUNCATE */ + errno = save_errno; + syserr("ndbm_map_open(%s): file changed after open", + map->map_file); + return false; + } + + map->map_db1 = (ARBPTR_T) dbm; + + /* + ** Need to set map_mtime before the call to aliaswait() + ** as aliaswait() will call map_lookup() which requires + ** map_mtime to be set + */ + + if (fstat(pfd, &st) >= 0) + map->map_mtime = st.st_mtime; + + if (mode == O_RDONLY) + { +# if LOCK_ON_OPEN + if (dfd >= 0) + (void) lockfile(dfd, map->map_file, ".dir", LOCK_UN); + if (pfd >= 0) + (void) lockfile(pfd, map->map_file, ".pag", LOCK_UN); +# endif /* LOCK_ON_OPEN */ + if (bitset(MF_ALIAS, map->map_mflags) && + !aliaswait(map, ".pag", true)) + return false; + } + else + { + map->map_mflags |= MF_LOCKED; + if (geteuid() == 0 && TrustedUid != 0) + { +# if HASFCHOWN + if (fchown(dfd, TrustedUid, -1) < 0 || + fchown(pfd, TrustedUid, -1) < 0) + { + int err = errno; + + sm_syslog(LOG_ALERT, NOQID, + "ownership change on %s failed: %s", + map->map_file, sm_errstring(err)); + message("050 ownership change on %s failed: %s", + map->map_file, sm_errstring(err)); + } +# else /* HASFCHOWN */ + sm_syslog(LOG_ALERT, NOQID, + "no fchown(): cannot change ownership on %s", + map->map_file); + message("050 no fchown(): cannot change ownership on %s", + map->map_file); +# endif /* HASFCHOWN */ + } + } + return true; +} + + +/* +** NDBM_MAP_LOOKUP -- look up a datum in a DBM-type map +*/ + +char * +ndbm_map_lookup(map, name, av, statp) + MAP *map; + char *name; + char **av; + int *statp; +{ + datum key, val; + int dfd, pfd; + char keybuf[MAXNAME + 1]; + struct stat stbuf; + + if (tTd(38, 20)) + sm_dprintf("ndbm_map_lookup(%s, %s)\n", + map->map_mname, name); + + key.dptr = name; + key.dsize = strlen(name); + if (!bitset(MF_NOFOLDCASE, map->map_mflags)) + { + if (key.dsize > sizeof(keybuf) - 1) + key.dsize = sizeof(keybuf) - 1; + memmove(keybuf, key.dptr, key.dsize); + keybuf[key.dsize] = '\0'; + makelower(keybuf); + key.dptr = keybuf; + } +lockdbm: + dfd = dbm_dirfno((DBM *) map->map_db1); + if (dfd >= 0 && !bitset(MF_LOCKED, map->map_mflags)) + (void) lockfile(dfd, map->map_file, ".dir", LOCK_SH); + pfd = dbm_pagfno((DBM *) map->map_db1); + if (pfd < 0 || fstat(pfd, &stbuf) < 0 || + stbuf.st_mtime > map->map_mtime) + { + /* Reopen the database to sync the cache */ + int omode = bitset(map->map_mflags, MF_WRITABLE) ? O_RDWR + : O_RDONLY; + + if (dfd >= 0 && !bitset(MF_LOCKED, map->map_mflags)) + (void) lockfile(dfd, map->map_file, ".dir", LOCK_UN); + map->map_mflags |= MF_CLOSING; + map->map_class->map_close(map); + map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING); + if (map->map_class->map_open(map, omode)) + { + map->map_mflags |= MF_OPEN; + map->map_pid = CurrentPid; + if ((omode & O_ACCMODE) == O_RDWR) + map->map_mflags |= MF_WRITABLE; + goto lockdbm; + } + else + { + if (!bitset(MF_OPTIONAL, map->map_mflags)) + { + extern MAPCLASS BogusMapClass; + + *statp = EX_TEMPFAIL; + map->map_orgclass = map->map_class; + map->map_class = &BogusMapClass; + map->map_mflags |= MF_OPEN; + map->map_pid = CurrentPid; + syserr("Cannot reopen NDBM database %s", + map->map_file); + } + return NULL; + } + } + val.dptr = NULL; + if (bitset(MF_TRY0NULL, map->map_mflags)) + { + val = dbm_fetch((DBM *) map->map_db1, key); + if (val.dptr != NULL) + map->map_mflags &= ~MF_TRY1NULL; + } + if (val.dptr == NULL && bitset(MF_TRY1NULL, map->map_mflags)) + { + key.dsize++; + val = dbm_fetch((DBM *) map->map_db1, key); + if (val.dptr != NULL) + map->map_mflags &= ~MF_TRY0NULL; + } + if (dfd >= 0 && !bitset(MF_LOCKED, map->map_mflags)) + (void) lockfile(dfd, map->map_file, ".dir", LOCK_UN); + if (val.dptr == NULL) + return NULL; + if (bitset(MF_MATCHONLY, map->map_mflags)) + return map_rewrite(map, name, strlen(name), NULL); + else + return map_rewrite(map, val.dptr, val.dsize, av); +} + + +/* +** NDBM_MAP_STORE -- store a datum in the database +*/ + +void +ndbm_map_store(map, lhs, rhs) + register MAP *map; + char *lhs; + char *rhs; +{ + datum key; + datum data; + int status; + char keybuf[MAXNAME + 1]; + + if (tTd(38, 12)) + sm_dprintf("ndbm_map_store(%s, %s, %s)\n", + map->map_mname, lhs, rhs); + + key.dsize = strlen(lhs); + key.dptr = lhs; + if (!bitset(MF_NOFOLDCASE, map->map_mflags)) + { + if (key.dsize > sizeof(keybuf) - 1) + key.dsize = sizeof(keybuf) - 1; + memmove(keybuf, key.dptr, key.dsize); + keybuf[key.dsize] = '\0'; + makelower(keybuf); + key.dptr = keybuf; + } + + data.dsize = strlen(rhs); + data.dptr = rhs; + + if (bitset(MF_INCLNULL, map->map_mflags)) + { + key.dsize++; + data.dsize++; + } + + status = dbm_store((DBM *) map->map_db1, key, data, DBM_INSERT); + if (status > 0) + { + if (!bitset(MF_APPEND, map->map_mflags)) + message("050 Warning: duplicate alias name %s", lhs); + else + { + static char *buf = NULL; + static int bufsiz = 0; + auto int xstat; + datum old; + + old.dptr = ndbm_map_lookup(map, key.dptr, + (char **) NULL, &xstat); + if (old.dptr != NULL && *(char *) old.dptr != '\0') + { + old.dsize = strlen(old.dptr); + if (data.dsize + old.dsize + 2 > bufsiz) + { + if (buf != NULL) + (void) sm_free(buf); + bufsiz = data.dsize + old.dsize + 2; + buf = sm_pmalloc_x(bufsiz); + } + (void) sm_strlcpyn(buf, bufsiz, 3, + data.dptr, ",", old.dptr); + data.dsize = data.dsize + old.dsize + 1; + data.dptr = buf; + if (tTd(38, 9)) + sm_dprintf("ndbm_map_store append=%s\n", + data.dptr); + } + } + status = dbm_store((DBM *) map->map_db1, + key, data, DBM_REPLACE); + } + if (status != 0) + syserr("readaliases: dbm put (%s): %d", lhs, status); +} + + +/* +** NDBM_MAP_CLOSE -- close the database +*/ + +void +ndbm_map_close(map) + register MAP *map; +{ + if (tTd(38, 9)) + sm_dprintf("ndbm_map_close(%s, %s, %lx)\n", + map->map_mname, map->map_file, map->map_mflags); + + if (bitset(MF_WRITABLE, map->map_mflags)) + { +# ifdef NDBM_YP_COMPAT + bool inclnull; + char buf[MAXHOSTNAMELEN]; + + inclnull = bitset(MF_INCLNULL, map->map_mflags); + map->map_mflags &= ~MF_INCLNULL; + + if (strstr(map->map_file, "/yp/") != NULL) + { + long save_mflags = map->map_mflags; + + map->map_mflags |= MF_NOFOLDCASE; + + (void) sm_snprintf(buf, sizeof(buf), "%010ld", curtime()); + ndbm_map_store(map, "YP_LAST_MODIFIED", buf); + + (void) gethostname(buf, sizeof(buf)); + ndbm_map_store(map, "YP_MASTER_NAME", buf); + + map->map_mflags = save_mflags; + } + + if (inclnull) + map->map_mflags |= MF_INCLNULL; +# endif /* NDBM_YP_COMPAT */ + + /* write out the distinguished alias */ + ndbm_map_store(map, "@", "@"); + } + dbm_close((DBM *) map->map_db1); + + /* release lock (if needed) */ +# if !LOCK_ON_OPEN + if (map->map_lockfd >= 0) + (void) close(map->map_lockfd); +# endif /* !LOCK_ON_OPEN */ +} + +#endif /* NDBM */ +/* +** NEWDB (Hash and BTree) Modules +*/ + +#if NEWDB + +/* +** BT_MAP_OPEN, HASH_MAP_OPEN -- database open primitives. +** +** These do rather bizarre locking. If you can lock on open, +** do that to avoid the condition of opening a database that +** is being rebuilt. If you don't, we'll try to fake it, but +** there will be a race condition. If opening for read-only, +** we immediately release the lock to avoid freezing things up. +** We really ought to hold the lock, but guarantee that we won't +** be pokey about it. That's hard to do. +*/ + +/* these should be K line arguments */ +# if DB_VERSION_MAJOR < 2 +# define db_cachesize cachesize +# define h_nelem nelem +# ifndef DB_CACHE_SIZE +# define DB_CACHE_SIZE (1024 * 1024) /* database memory cache size */ +# endif /* ! DB_CACHE_SIZE */ +# ifndef DB_HASH_NELEM +# define DB_HASH_NELEM 4096 /* (starting) size of hash table */ +# endif /* ! DB_HASH_NELEM */ +# endif /* DB_VERSION_MAJOR < 2 */ + +bool +bt_map_open(map, mode) + MAP *map; + int mode; +{ +# if DB_VERSION_MAJOR < 2 + BTREEINFO btinfo; +# endif /* DB_VERSION_MAJOR < 2 */ +# if DB_VERSION_MAJOR == 2 + DB_INFO btinfo; +# endif /* DB_VERSION_MAJOR == 2 */ +# if DB_VERSION_MAJOR > 2 + void *btinfo = NULL; +# endif /* DB_VERSION_MAJOR > 2 */ + + if (tTd(38, 2)) + sm_dprintf("bt_map_open(%s, %s, %d)\n", + map->map_mname, map->map_file, mode); + +# if DB_VERSION_MAJOR < 3 + memset(&btinfo, '\0', sizeof(btinfo)); +# ifdef DB_CACHE_SIZE + btinfo.db_cachesize = DB_CACHE_SIZE; +# endif /* DB_CACHE_SIZE */ +# endif /* DB_VERSION_MAJOR < 3 */ + + return db_map_open(map, mode, "btree", DB_BTREE, &btinfo); +} + +bool +hash_map_open(map, mode) + MAP *map; + int mode; +{ +# if DB_VERSION_MAJOR < 2 + HASHINFO hinfo; +# endif /* DB_VERSION_MAJOR < 2 */ +# if DB_VERSION_MAJOR == 2 + DB_INFO hinfo; +# endif /* DB_VERSION_MAJOR == 2 */ +# if DB_VERSION_MAJOR > 2 + void *hinfo = NULL; +# endif /* DB_VERSION_MAJOR > 2 */ + + if (tTd(38, 2)) + sm_dprintf("hash_map_open(%s, %s, %d)\n", + map->map_mname, map->map_file, mode); + +# if DB_VERSION_MAJOR < 3 + memset(&hinfo, '\0', sizeof(hinfo)); +# ifdef DB_HASH_NELEM + hinfo.h_nelem = DB_HASH_NELEM; +# endif /* DB_HASH_NELEM */ +# ifdef DB_CACHE_SIZE + hinfo.db_cachesize = DB_CACHE_SIZE; +# endif /* DB_CACHE_SIZE */ +# endif /* DB_VERSION_MAJOR < 3 */ + + return db_map_open(map, mode, "hash", DB_HASH, &hinfo); +} + +static bool +db_map_open(map, mode, mapclassname, dbtype, openinfo) + MAP *map; + int mode; + char *mapclassname; + DBTYPE dbtype; +# if DB_VERSION_MAJOR < 2 + const void *openinfo; +# endif /* DB_VERSION_MAJOR < 2 */ +# if DB_VERSION_MAJOR == 2 + DB_INFO *openinfo; +# endif /* DB_VERSION_MAJOR == 2 */ +# if DB_VERSION_MAJOR > 2 + void **openinfo; +# endif /* DB_VERSION_MAJOR > 2 */ +{ + DB *db = NULL; + int i; + int omode; + int smode = S_IREAD; + int fd; + long sff; + int save_errno; + struct stat st; + char buf[MAXPATHLEN]; + + /* do initial file and directory checks */ + if (sm_strlcpy(buf, map->map_file, sizeof(buf)) >= sizeof(buf)) + { + errno = 0; + if (!bitset(MF_OPTIONAL, map->map_mflags)) + syserr("map \"%s\": map file %s name too long", + map->map_mname, map->map_file); + return false; + } + i = strlen(buf); + if (i < 3 || strcmp(&buf[i - 3], ".db") != 0) + { + if (sm_strlcat(buf, ".db", sizeof(buf)) >= sizeof(buf)) + { + errno = 0; + if (!bitset(MF_OPTIONAL, map->map_mflags)) + syserr("map \"%s\": map file %s name too long", + map->map_mname, map->map_file); + return false; + } + } + + mode &= O_ACCMODE; + omode = mode; + + sff = SFF_ROOTOK|SFF_REGONLY; + if (mode == O_RDWR) + { + sff |= SFF_CREAT; + if (!bitnset(DBS_WRITEMAPTOSYMLINK, DontBlameSendmail)) + sff |= SFF_NOSLINK; + if (!bitnset(DBS_WRITEMAPTOHARDLINK, DontBlameSendmail)) + sff |= SFF_NOHLINK; + smode = S_IWRITE; + } + else + { + if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail)) + sff |= SFF_NOWLINK; + } + if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail)) + sff |= SFF_SAFEDIRPATH; + i = safefile(buf, RunAsUid, RunAsGid, RunAsUserName, sff, smode, &st); + + if (i != 0) + { + char *prob = "unsafe"; + + /* cannot open this map */ + if (i == ENOENT) + prob = "missing"; + if (tTd(38, 2)) + sm_dprintf("\t%s map file: %s\n", prob, sm_errstring(i)); + errno = i; + if (!bitset(MF_OPTIONAL, map->map_mflags)) + syserr("%s map \"%s\": %s map file %s", + mapclassname, map->map_mname, prob, buf); + return false; + } + if (st.st_mode == ST_MODE_NOFILE) + omode |= O_CREAT|O_EXCL; + + map->map_lockfd = -1; + +# if LOCK_ON_OPEN + if (mode == O_RDWR) + omode |= O_TRUNC|O_EXLOCK; + else + omode |= O_SHLOCK; +# else /* LOCK_ON_OPEN */ + /* + ** Pre-lock the file to avoid race conditions. In particular, + ** since dbopen returns NULL if the file is zero length, we + ** must have a locked instance around the dbopen. + */ + + fd = open(buf, omode, DBMMODE); + if (fd < 0) + { + if (!bitset(MF_OPTIONAL, map->map_mflags)) + syserr("db_map_open: cannot pre-open database %s", buf); + return false; + } + + /* make sure no baddies slipped in just before the open... */ + if (filechanged(buf, fd, &st)) + { + save_errno = errno; + (void) close(fd); + errno = save_errno; + syserr("db_map_open(%s): file changed after pre-open", buf); + return false; + } + + /* if new file, get the "before" bits for later filechanged check */ + if (st.st_mode == ST_MODE_NOFILE && fstat(fd, &st) < 0) + { + save_errno = errno; + (void) close(fd); + errno = save_errno; + syserr("db_map_open(%s): cannot fstat pre-opened file", + buf); + return false; + } + + /* actually lock the pre-opened file */ + if (!lockfile(fd, buf, NULL, mode == O_RDONLY ? LOCK_SH : LOCK_EX)) + syserr("db_map_open: cannot lock %s", buf); + + /* set up mode bits for dbopen */ + if (mode == O_RDWR) + omode |= O_TRUNC; + omode &= ~(O_EXCL|O_CREAT); +# endif /* LOCK_ON_OPEN */ + +# if DB_VERSION_MAJOR < 2 + db = dbopen(buf, omode, DBMMODE, dbtype, openinfo); +# else /* DB_VERSION_MAJOR < 2 */ + { + int flags = 0; +# if DB_VERSION_MAJOR > 2 + int ret; +# endif /* DB_VERSION_MAJOR > 2 */ + + if (mode == O_RDONLY) + flags |= DB_RDONLY; + if (bitset(O_CREAT, omode)) + flags |= DB_CREATE; + if (bitset(O_TRUNC, omode)) + flags |= DB_TRUNCATE; + SM_DB_FLAG_ADD(flags); + +# if DB_VERSION_MAJOR > 2 + ret = db_create(&db, NULL, 0); +# ifdef DB_CACHE_SIZE + if (ret == 0 && db != NULL) + { + ret = db->set_cachesize(db, 0, DB_CACHE_SIZE, 0); + if (ret != 0) + { + (void) db->close(db, 0); + db = NULL; + } + } +# endif /* DB_CACHE_SIZE */ +# ifdef DB_HASH_NELEM + if (dbtype == DB_HASH && ret == 0 && db != NULL) + { + ret = db->set_h_nelem(db, DB_HASH_NELEM); + if (ret != 0) + { + (void) db->close(db, 0); + db = NULL; + } + } +# endif /* DB_HASH_NELEM */ + if (ret == 0 && db != NULL) + { + ret = db->open(db, + DBTXN /* transaction for DB 4.1 */ + buf, NULL, dbtype, flags, DBMMODE); + if (ret != 0) + { +#ifdef DB_OLD_VERSION + if (ret == DB_OLD_VERSION) + ret = EINVAL; +#endif /* DB_OLD_VERSION */ + (void) db->close(db, 0); + db = NULL; + } + } + errno = ret; +# else /* DB_VERSION_MAJOR > 2 */ + errno = db_open(buf, dbtype, flags, DBMMODE, + NULL, openinfo, &db); +# endif /* DB_VERSION_MAJOR > 2 */ + } +# endif /* DB_VERSION_MAJOR < 2 */ + save_errno = errno; + +# if !LOCK_ON_OPEN + if (mode == O_RDWR) + map->map_lockfd = fd; + else + (void) close(fd); +# endif /* !LOCK_ON_OPEN */ + + if (db == NULL) + { + if (mode == O_RDONLY && bitset(MF_ALIAS, map->map_mflags) && + aliaswait(map, ".db", false)) + return true; +# if !LOCK_ON_OPEN + if (map->map_lockfd >= 0) + (void) close(map->map_lockfd); +# endif /* !LOCK_ON_OPEN */ + errno = save_errno; + if (!bitset(MF_OPTIONAL, map->map_mflags)) + syserr("Cannot open %s database %s", + mapclassname, buf); + return false; + } + +# if DB_VERSION_MAJOR < 2 + fd = db->fd(db); +# else /* DB_VERSION_MAJOR < 2 */ + fd = -1; + errno = db->fd(db, &fd); +# endif /* DB_VERSION_MAJOR < 2 */ + if (filechanged(buf, fd, &st)) + { + save_errno = errno; +# if DB_VERSION_MAJOR < 2 + (void) db->close(db); +# else /* DB_VERSION_MAJOR < 2 */ + errno = db->close(db, 0); +# endif /* DB_VERSION_MAJOR < 2 */ +# if !LOCK_ON_OPEN + if (map->map_lockfd >= 0) + (void) close(map->map_lockfd); +# endif /* !LOCK_ON_OPEN */ + errno = save_errno; + syserr("db_map_open(%s): file changed after open", buf); + return false; + } + + if (mode == O_RDWR) + map->map_mflags |= MF_LOCKED; +# if LOCK_ON_OPEN + if (fd >= 0 && mode == O_RDONLY) + { + (void) lockfile(fd, buf, NULL, LOCK_UN); + } +# endif /* LOCK_ON_OPEN */ + + /* try to make sure that at least the database header is on disk */ + if (mode == O_RDWR) + { + (void) db->sync(db, 0); + if (geteuid() == 0 && TrustedUid != 0) + { +# if HASFCHOWN + if (fchown(fd, TrustedUid, -1) < 0) + { + int err = errno; + + sm_syslog(LOG_ALERT, NOQID, + "ownership change on %s failed: %s", + buf, sm_errstring(err)); + message("050 ownership change on %s failed: %s", + buf, sm_errstring(err)); + } +# else /* HASFCHOWN */ + sm_syslog(LOG_ALERT, NOQID, + "no fchown(): cannot change ownership on %s", + map->map_file); + message("050 no fchown(): cannot change ownership on %s", + map->map_file); +# endif /* HASFCHOWN */ + } + } + + map->map_db2 = (ARBPTR_T) db; + + /* + ** Need to set map_mtime before the call to aliaswait() + ** as aliaswait() will call map_lookup() which requires + ** map_mtime to be set + */ + + if (fd >= 0 && fstat(fd, &st) >= 0) + map->map_mtime = st.st_mtime; + + if (mode == O_RDONLY && bitset(MF_ALIAS, map->map_mflags) && + !aliaswait(map, ".db", true)) + return false; + return true; +} + + +/* +** DB_MAP_LOOKUP -- look up a datum in a BTREE- or HASH-type map +*/ + +char * +db_map_lookup(map, name, av, statp) + MAP *map; + char *name; + char **av; + int *statp; +{ + DBT key, val; + register DB *db = (DB *) map->map_db2; + int i; + int st; + int save_errno; + int fd; + struct stat stbuf; + char keybuf[MAXNAME + 1]; + char buf[MAXPATHLEN]; + + memset(&key, '\0', sizeof(key)); + memset(&val, '\0', sizeof(val)); + + if (tTd(38, 20)) + sm_dprintf("db_map_lookup(%s, %s)\n", + map->map_mname, name); + + if (sm_strlcpy(buf, map->map_file, sizeof(buf)) >= sizeof(buf)) + { + errno = 0; + if (!bitset(MF_OPTIONAL, map->map_mflags)) + syserr("map \"%s\": map file %s name too long", + map->map_mname, map->map_file); + return NULL; + } + i = strlen(buf); + if (i > 3 && strcmp(&buf[i - 3], ".db") == 0) + buf[i - 3] = '\0'; + + key.size = strlen(name); + if (key.size > sizeof(keybuf) - 1) + key.size = sizeof(keybuf) - 1; + key.data = keybuf; + memmove(keybuf, name, key.size); + keybuf[key.size] = '\0'; + if (!bitset(MF_NOFOLDCASE, map->map_mflags)) + makelower(keybuf); + lockdb: +# if DB_VERSION_MAJOR < 2 + fd = db->fd(db); +# else /* DB_VERSION_MAJOR < 2 */ + fd = -1; + errno = db->fd(db, &fd); +# endif /* DB_VERSION_MAJOR < 2 */ + if (fd >= 0 && !bitset(MF_LOCKED, map->map_mflags)) + (void) lockfile(fd, buf, ".db", LOCK_SH); + if (fd < 0 || fstat(fd, &stbuf) < 0 || stbuf.st_mtime > map->map_mtime) + { + /* Reopen the database to sync the cache */ + int omode = bitset(map->map_mflags, MF_WRITABLE) ? O_RDWR + : O_RDONLY; + + if (fd >= 0 && !bitset(MF_LOCKED, map->map_mflags)) + (void) lockfile(fd, buf, ".db", LOCK_UN); + map->map_mflags |= MF_CLOSING; + map->map_class->map_close(map); + map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING); + if (map->map_class->map_open(map, omode)) + { + map->map_mflags |= MF_OPEN; + map->map_pid = CurrentPid; + if ((omode & O_ACCMODE) == O_RDWR) + map->map_mflags |= MF_WRITABLE; + db = (DB *) map->map_db2; + goto lockdb; + } + else + { + if (!bitset(MF_OPTIONAL, map->map_mflags)) + { + extern MAPCLASS BogusMapClass; + + *statp = EX_TEMPFAIL; + map->map_orgclass = map->map_class; + map->map_class = &BogusMapClass; + map->map_mflags |= MF_OPEN; + map->map_pid = CurrentPid; + syserr("Cannot reopen DB database %s", + map->map_file); + } + return NULL; + } + } + + st = 1; + if (bitset(MF_TRY0NULL, map->map_mflags)) + { +# if DB_VERSION_MAJOR < 2 + st = db->get(db, &key, &val, 0); +# else /* DB_VERSION_MAJOR < 2 */ + errno = db->get(db, NULL, &key, &val, 0); + switch (errno) + { + case DB_NOTFOUND: + case DB_KEYEMPTY: + st = 1; + break; + + case 0: + st = 0; + break; + + default: + st = -1; + break; + } +# endif /* DB_VERSION_MAJOR < 2 */ + if (st == 0) + map->map_mflags &= ~MF_TRY1NULL; + } + if (st != 0 && bitset(MF_TRY1NULL, map->map_mflags)) + { + key.size++; +# if DB_VERSION_MAJOR < 2 + st = db->get(db, &key, &val, 0); +# else /* DB_VERSION_MAJOR < 2 */ + errno = db->get(db, NULL, &key, &val, 0); + switch (errno) + { + case DB_NOTFOUND: + case DB_KEYEMPTY: + st = 1; + break; + + case 0: + st = 0; + break; + + default: + st = -1; + break; + } +# endif /* DB_VERSION_MAJOR < 2 */ + if (st == 0) + map->map_mflags &= ~MF_TRY0NULL; + } + save_errno = errno; + if (fd >= 0 && !bitset(MF_LOCKED, map->map_mflags)) + (void) lockfile(fd, buf, ".db", LOCK_UN); + if (st != 0) + { + errno = save_errno; + if (st < 0) + syserr("db_map_lookup: get (%s)", name); + return NULL; + } + if (bitset(MF_MATCHONLY, map->map_mflags)) + return map_rewrite(map, name, strlen(name), NULL); + else + return map_rewrite(map, val.data, val.size, av); +} + + +/* +** DB_MAP_STORE -- store a datum in the NEWDB database +*/ + +void +db_map_store(map, lhs, rhs) + register MAP *map; + char *lhs; + char *rhs; +{ + int status; + DBT key; + DBT data; + register DB *db = map->map_db2; + char keybuf[MAXNAME + 1]; + + memset(&key, '\0', sizeof(key)); + memset(&data, '\0', sizeof(data)); + + if (tTd(38, 12)) + sm_dprintf("db_map_store(%s, %s, %s)\n", + map->map_mname, lhs, rhs); + + key.size = strlen(lhs); + key.data = lhs; + if (!bitset(MF_NOFOLDCASE, map->map_mflags)) + { + if (key.size > sizeof(keybuf) - 1) + key.size = sizeof(keybuf) - 1; + memmove(keybuf, key.data, key.size); + keybuf[key.size] = '\0'; + makelower(keybuf); + key.data = keybuf; + } + + data.size = strlen(rhs); + data.data = rhs; + + if (bitset(MF_INCLNULL, map->map_mflags)) + { + key.size++; + data.size++; + } + +# if DB_VERSION_MAJOR < 2 + status = db->put(db, &key, &data, R_NOOVERWRITE); +# else /* DB_VERSION_MAJOR < 2 */ + errno = db->put(db, NULL, &key, &data, DB_NOOVERWRITE); + switch (errno) + { + case DB_KEYEXIST: + status = 1; + break; + + case 0: + status = 0; + break; + + default: + status = -1; + break; + } +# endif /* DB_VERSION_MAJOR < 2 */ + if (status > 0) + { + if (!bitset(MF_APPEND, map->map_mflags)) + message("050 Warning: duplicate alias name %s", lhs); + else + { + static char *buf = NULL; + static int bufsiz = 0; + DBT old; + + memset(&old, '\0', sizeof(old)); + + old.data = db_map_lookup(map, key.data, + (char **) NULL, &status); + if (old.data != NULL) + { + old.size = strlen(old.data); + if (data.size + old.size + 2 > (size_t) bufsiz) + { + if (buf != NULL) + sm_free(buf); + bufsiz = data.size + old.size + 2; + buf = sm_pmalloc_x(bufsiz); + } + (void) sm_strlcpyn(buf, bufsiz, 3, + (char *) data.data, ",", + (char *) old.data); + data.size = data.size + old.size + 1; + data.data = buf; + if (tTd(38, 9)) + sm_dprintf("db_map_store append=%s\n", + (char *) data.data); + } + } +# if DB_VERSION_MAJOR < 2 + status = db->put(db, &key, &data, 0); +# else /* DB_VERSION_MAJOR < 2 */ + status = errno = db->put(db, NULL, &key, &data, 0); +# endif /* DB_VERSION_MAJOR < 2 */ + } + if (status != 0) + syserr("readaliases: db put (%s)", lhs); +} + + +/* +** DB_MAP_CLOSE -- add distinguished entries and close the database +*/ + +void +db_map_close(map) + MAP *map; +{ + register DB *db = map->map_db2; + + if (tTd(38, 9)) + sm_dprintf("db_map_close(%s, %s, %lx)\n", + map->map_mname, map->map_file, map->map_mflags); + + if (bitset(MF_WRITABLE, map->map_mflags)) + { + /* write out the distinguished alias */ + db_map_store(map, "@", "@"); + } + + (void) db->sync(db, 0); + +# if !LOCK_ON_OPEN + if (map->map_lockfd >= 0) + (void) close(map->map_lockfd); +# endif /* !LOCK_ON_OPEN */ + +# if DB_VERSION_MAJOR < 2 + if (db->close(db) != 0) +# else /* DB_VERSION_MAJOR < 2 */ + /* + ** Berkeley DB can use internal shared memory + ** locking for its memory pool. Closing a map + ** opened by another process will interfere + ** with the shared memory and locks of the parent + ** process leaving things in a bad state. + */ + + /* + ** If this map was not opened by the current + ** process, do not close the map but recover + ** the file descriptor. + */ + + if (map->map_pid != CurrentPid) + { + int fd = -1; + + errno = db->fd(db, &fd); + if (fd >= 0) + (void) close(fd); + return; + } + + if ((errno = db->close(db, 0)) != 0) +# endif /* DB_VERSION_MAJOR < 2 */ + syserr("db_map_close(%s, %s, %lx): db close failure", + map->map_mname, map->map_file, map->map_mflags); +} +#endif /* NEWDB */ +/* +** NIS Modules +*/ + +#if NIS + +# ifndef YPERR_BUSY +# define YPERR_BUSY 16 +# endif /* ! YPERR_BUSY */ + +/* +** NIS_MAP_OPEN -- open DBM map +*/ + +bool +nis_map_open(map, mode) + MAP *map; + int mode; +{ + int yperr; + register char *p; + auto char *vp; + auto int vsize; + + if (tTd(38, 2)) + sm_dprintf("nis_map_open(%s, %s, %d)\n", + map->map_mname, map->map_file, mode); + + mode &= O_ACCMODE; + if (mode != O_RDONLY) + { + /* issue a pseudo-error message */ + errno = SM_EMAPCANTWRITE; + return false; + } + + p = strchr(map->map_file, '@'); + if (p != NULL) + { + *p++ = '\0'; + if (*p != '\0') + map->map_domain = p; + } + + if (*map->map_file == '\0') + map->map_file = "mail.aliases"; + + if (map->map_domain == NULL) + { + yperr = yp_get_default_domain(&map->map_domain); + if (yperr != 0) + { + if (!bitset(MF_OPTIONAL, map->map_mflags)) + syserr("451 4.3.5 NIS map %s specified, but NIS not running", + map->map_file); + return false; + } + } + + /* check to see if this map actually exists */ + vp = NULL; + yperr = yp_match(map->map_domain, map->map_file, "@", 1, + &vp, &vsize); + if (tTd(38, 10)) + sm_dprintf("nis_map_open: yp_match(@, %s, %s) => %s\n", + map->map_domain, map->map_file, yperr_string(yperr)); + if (vp != NULL) + sm_free(vp); + + if (yperr == 0 || yperr == YPERR_KEY || yperr == YPERR_BUSY) + { + /* + ** We ought to be calling aliaswait() here if this is an + ** alias file, but powerful HP-UX NIS servers apparently + ** don't insert the @:@ token into the alias map when it + ** is rebuilt, so aliaswait() just hangs. I hate HP-UX. + */ + +# if 0 + if (!bitset(MF_ALIAS, map->map_mflags) || + aliaswait(map, NULL, true)) +# endif /* 0 */ + return true; + } + + if (!bitset(MF_OPTIONAL, map->map_mflags)) + { + syserr("451 4.3.5 Cannot bind to map %s in domain %s: %s", + map->map_file, map->map_domain, yperr_string(yperr)); + } + + return false; +} + + +/* +** NIS_MAP_LOOKUP -- look up a datum in a NIS map +*/ + +/* ARGSUSED3 */ +char * +nis_map_lookup(map, name, av, statp) + MAP *map; + char *name; + char **av; + int *statp; +{ + char *vp; + auto int vsize; + int buflen; + int yperr; + char keybuf[MAXNAME + 1]; + char *SM_NONVOLATILE result = NULL; + + if (tTd(38, 20)) + sm_dprintf("nis_map_lookup(%s, %s)\n", + map->map_mname, name); + + buflen = strlen(name); + if (buflen > sizeof(keybuf) - 1) + buflen = sizeof(keybuf) - 1; + memmove(keybuf, name, buflen); + keybuf[buflen] = '\0'; + if (!bitset(MF_NOFOLDCASE, map->map_mflags)) + makelower(keybuf); + yperr = YPERR_KEY; + vp = NULL; + if (bitset(MF_TRY0NULL, map->map_mflags)) + { + yperr = yp_match(map->map_domain, map->map_file, keybuf, buflen, + &vp, &vsize); + if (yperr == 0) + map->map_mflags &= ~MF_TRY1NULL; + } + if (yperr == YPERR_KEY && bitset(MF_TRY1NULL, map->map_mflags)) + { + SM_FREE_CLR(vp); + buflen++; + yperr = yp_match(map->map_domain, map->map_file, keybuf, buflen, + &vp, &vsize); + if (yperr == 0) + map->map_mflags &= ~MF_TRY0NULL; + } + if (yperr != 0) + { + if (yperr != YPERR_KEY && yperr != YPERR_BUSY) + map->map_mflags &= ~(MF_VALID|MF_OPEN); + if (vp != NULL) + sm_free(vp); + return NULL; + } + SM_TRY + if (bitset(MF_MATCHONLY, map->map_mflags)) + result = map_rewrite(map, name, strlen(name), NULL); + else + result = map_rewrite(map, vp, vsize, av); + SM_FINALLY + if (vp != NULL) + sm_free(vp); + SM_END_TRY + return result; +} + + +/* +** NIS_GETCANONNAME -- look up canonical name in NIS +*/ + +static bool +nis_getcanonname(name, hbsize, statp) + char *name; + int hbsize; + int *statp; +{ + char *vp; + auto int vsize; + int keylen; + int yperr; + static bool try0null = true; + static bool try1null = true; + static char *yp_domain = NULL; + char host_record[MAXLINE]; + char cbuf[MAXNAME]; + char nbuf[MAXNAME + 1]; + + if (tTd(38, 20)) + sm_dprintf("nis_getcanonname(%s)\n", name); + + if (sm_strlcpy(nbuf, name, sizeof(nbuf)) >= sizeof(nbuf)) + { + *statp = EX_UNAVAILABLE; + return false; + } + (void) shorten_hostname(nbuf); + keylen = strlen(nbuf); + + if (yp_domain == NULL) + (void) yp_get_default_domain(&yp_domain); + makelower(nbuf); + yperr = YPERR_KEY; + vp = NULL; + if (try0null) + { + yperr = yp_match(yp_domain, "hosts.byname", nbuf, keylen, + &vp, &vsize); + if (yperr == 0) + try1null = false; + } + if (yperr == YPERR_KEY && try1null) + { + SM_FREE_CLR(vp); + keylen++; + yperr = yp_match(yp_domain, "hosts.byname", nbuf, keylen, + &vp, &vsize); + if (yperr == 0) + try0null = false; + } + if (yperr != 0) + { + if (yperr == YPERR_KEY) + *statp = EX_NOHOST; + else if (yperr == YPERR_BUSY) + *statp = EX_TEMPFAIL; + else + *statp = EX_UNAVAILABLE; + if (vp != NULL) + sm_free(vp); + return false; + } + (void) sm_strlcpy(host_record, vp, sizeof(host_record)); + sm_free(vp); + if (tTd(38, 44)) + sm_dprintf("got record `%s'\n", host_record); + vp = strpbrk(host_record, "#\n"); + if (vp != NULL) + *vp = '\0'; + if (!extract_canonname(nbuf, NULL, host_record, cbuf, sizeof(cbuf))) + { + /* this should not happen, but.... */ + *statp = EX_NOHOST; + return false; + } + if (sm_strlcpy(name, cbuf, hbsize) >= hbsize) + { + *statp = EX_UNAVAILABLE; + return false; + } + *statp = EX_OK; + return true; +} + +#endif /* NIS */ +/* +** NISPLUS Modules +** +** This code donated by Sun Microsystems. +*/ + +#if NISPLUS + +# undef NIS /* symbol conflict in nis.h */ +# undef T_UNSPEC /* symbol conflict in nis.h -> ... -> sys/tiuser.h */ +# include <rpcsvc/nis.h> +# include <rpcsvc/nislib.h> + +# define EN_col(col) zo_data.objdata_u.en_data.en_cols.en_cols_val[(col)].ec_value.ec_value_val +# define COL_NAME(res,i) ((res->objects.objects_val)->TA_data.ta_cols.ta_cols_val)[i].tc_name +# define COL_MAX(res) ((res->objects.objects_val)->TA_data.ta_cols.ta_cols_len) +# define PARTIAL_NAME(x) ((x)[strlen(x) - 1] != '.') + +/* +** NISPLUS_MAP_OPEN -- open nisplus table +*/ + +bool +nisplus_map_open(map, mode) + MAP *map; + int mode; +{ + nis_result *res = NULL; + int retry_cnt, max_col, i; + char qbuf[MAXLINE + NIS_MAXNAMELEN]; + + if (tTd(38, 2)) + sm_dprintf("nisplus_map_open(%s, %s, %d)\n", + map->map_mname, map->map_file, mode); + + mode &= O_ACCMODE; + if (mode != O_RDONLY) + { + errno = EPERM; + return false; + } + + if (*map->map_file == '\0') + map->map_file = "mail_aliases.org_dir"; + + if (PARTIAL_NAME(map->map_file) && map->map_domain == NULL) + { + /* set default NISPLUS Domain to $m */ + map->map_domain = newstr(nisplus_default_domain()); + if (tTd(38, 2)) + sm_dprintf("nisplus_map_open(%s): using domain %s\n", + map->map_file, map->map_domain); + } + if (!PARTIAL_NAME(map->map_file)) + { + map->map_domain = newstr(""); + (void) sm_strlcpy(qbuf, map->map_file, sizeof(qbuf)); + } + else + { + /* check to see if this map actually exists */ + (void) sm_strlcpyn(qbuf, sizeof(qbuf), 3, + map->map_file, ".", map->map_domain); + } + + retry_cnt = 0; + while (res == NULL || res->status != NIS_SUCCESS) + { + res = nis_lookup(qbuf, FOLLOW_LINKS); + switch (res->status) + { + case NIS_SUCCESS: + break; + + case NIS_TRYAGAIN: + case NIS_RPCERROR: + case NIS_NAMEUNREACHABLE: + if (retry_cnt++ > 4) + { + errno = EAGAIN; + return false; + } + /* try not to overwhelm hosed server */ + sleep(2); + break; + + default: /* all other nisplus errors */ +# if 0 + if (!bitset(MF_OPTIONAL, map->map_mflags)) + syserr("451 4.3.5 Cannot find table %s.%s: %s", + map->map_file, map->map_domain, + nis_sperrno(res->status)); +# endif /* 0 */ + errno = EAGAIN; + return false; + } + } + + if (NIS_RES_NUMOBJ(res) != 1 || + (NIS_RES_OBJECT(res)->zo_data.zo_type != TABLE_OBJ)) + { + if (tTd(38, 10)) + sm_dprintf("nisplus_map_open: %s is not a table\n", qbuf); +# if 0 + if (!bitset(MF_OPTIONAL, map->map_mflags)) + syserr("451 4.3.5 %s.%s: %s is not a table", + map->map_file, map->map_domain, + nis_sperrno(res->status)); +# endif /* 0 */ + errno = EBADF; + return false; + } + /* default key column is column 0 */ + if (map->map_keycolnm == NULL) + map->map_keycolnm = newstr(COL_NAME(res,0)); + + max_col = COL_MAX(res); + + /* verify the key column exist */ + for (i = 0; i < max_col; i++) + { + if (strcmp(map->map_keycolnm, COL_NAME(res,i)) == 0) + break; + } + if (i == max_col) + { + if (tTd(38, 2)) + sm_dprintf("nisplus_map_open(%s): can not find key column %s\n", + map->map_file, map->map_keycolnm); + errno = ENOENT; + return false; + } + + /* default value column is the last column */ + if (map->map_valcolnm == NULL) + { + map->map_valcolno = max_col - 1; + return true; + } + + for (i = 0; i< max_col; i++) + { + if (strcmp(map->map_valcolnm, COL_NAME(res,i)) == 0) + { + map->map_valcolno = i; + return true; + } + } + + if (tTd(38, 2)) + sm_dprintf("nisplus_map_open(%s): can not find column %s\n", + map->map_file, map->map_keycolnm); + errno = ENOENT; + return false; +} + + +/* +** NISPLUS_MAP_LOOKUP -- look up a datum in a NISPLUS table +*/ + +char * +nisplus_map_lookup(map, name, av, statp) + MAP *map; + char *name; + char **av; + int *statp; +{ + char *p; + auto int vsize; + char *skp; + int skleft; + char search_key[MAXNAME + 4]; + char qbuf[MAXLINE + NIS_MAXNAMELEN]; + nis_result *result; + + if (tTd(38, 20)) + sm_dprintf("nisplus_map_lookup(%s, %s)\n", + map->map_mname, name); + + if (!bitset(MF_OPEN, map->map_mflags)) + { + if (nisplus_map_open(map, O_RDONLY)) + { + map->map_mflags |= MF_OPEN; + map->map_pid = CurrentPid; + } + else + { + *statp = EX_UNAVAILABLE; + return NULL; + } + } + + /* + ** Copy the name to the key buffer, escaping double quote characters + ** by doubling them and quoting "]" and "," to avoid having the + ** NIS+ parser choke on them. + */ + + skleft = sizeof(search_key) - 4; + skp = search_key; + for (p = name; *p != '\0' && skleft > 0; p++) + { + switch (*p) + { + case ']': + case ',': + /* quote the character */ + *skp++ = '"'; + *skp++ = *p; + *skp++ = '"'; + skleft -= 3; + break; + + case '"': + /* double the quote */ + *skp++ = '"'; + skleft--; + /* FALLTHROUGH */ + + default: + *skp++ = *p; + skleft--; + break; + } + } + *skp = '\0'; + if (!bitset(MF_NOFOLDCASE, map->map_mflags)) + makelower(search_key); + + /* construct the query */ + if (PARTIAL_NAME(map->map_file)) + (void) sm_snprintf(qbuf, sizeof(qbuf), "[%s=%s],%s.%s", + map->map_keycolnm, search_key, map->map_file, + map->map_domain); + else + (void) sm_snprintf(qbuf, sizeof(qbuf), "[%s=%s],%s", + map->map_keycolnm, search_key, map->map_file); + + if (tTd(38, 20)) + sm_dprintf("qbuf=%s\n", qbuf); + result = nis_list(qbuf, FOLLOW_LINKS | FOLLOW_PATH, NULL, NULL); + if (result->status == NIS_SUCCESS) + { + int count; + char *str; + + if ((count = NIS_RES_NUMOBJ(result)) != 1) + { + if (LogLevel > 10) + sm_syslog(LOG_WARNING, CurEnv->e_id, + "%s: lookup error, expected 1 entry, got %d", + map->map_file, count); + + /* ignore second entry */ + if (tTd(38, 20)) + sm_dprintf("nisplus_map_lookup(%s), got %d entries, additional entries ignored\n", + name, count); + } + + p = ((NIS_RES_OBJECT(result))->EN_col(map->map_valcolno)); + /* set the length of the result */ + if (p == NULL) + p = ""; + vsize = strlen(p); + if (tTd(38, 20)) + sm_dprintf("nisplus_map_lookup(%s), found %s\n", + name, p); + if (bitset(MF_MATCHONLY, map->map_mflags)) + str = map_rewrite(map, name, strlen(name), NULL); + else + str = map_rewrite(map, p, vsize, av); + nis_freeresult(result); + *statp = EX_OK; + return str; + } + else + { + if (result->status == NIS_NOTFOUND) + *statp = EX_NOTFOUND; + else if (result->status == NIS_TRYAGAIN) + *statp = EX_TEMPFAIL; + else + { + *statp = EX_UNAVAILABLE; + map->map_mflags &= ~(MF_VALID|MF_OPEN); + } + } + if (tTd(38, 20)) + sm_dprintf("nisplus_map_lookup(%s), failed\n", name); + nis_freeresult(result); + return NULL; +} + + + +/* +** NISPLUS_GETCANONNAME -- look up canonical name in NIS+ +*/ + +static bool +nisplus_getcanonname(name, hbsize, statp) + char *name; + int hbsize; + int *statp; +{ + char *vp; + auto int vsize; + nis_result *result; + char *p; + char nbuf[MAXNAME + 1]; + char qbuf[MAXLINE + NIS_MAXNAMELEN]; + + if (sm_strlcpy(nbuf, name, sizeof(nbuf)) >= sizeof(nbuf)) + { + *statp = EX_UNAVAILABLE; + return false; + } + (void) shorten_hostname(nbuf); + + p = strchr(nbuf, '.'); + if (p == NULL) + { + /* single token */ + (void) sm_snprintf(qbuf, sizeof(qbuf), + "[name=%s],hosts.org_dir", nbuf); + } + else if (p[1] != '\0') + { + /* multi token -- take only first token in nbuf */ + *p = '\0'; + (void) sm_snprintf(qbuf, sizeof(qbuf), + "[name=%s],hosts.org_dir.%s", nbuf, &p[1]); + } + else + { + *statp = EX_NOHOST; + return false; + } + + if (tTd(38, 20)) + sm_dprintf("\nnisplus_getcanonname(%s), qbuf=%s\n", + name, qbuf); + + result = nis_list(qbuf, EXPAND_NAME|FOLLOW_LINKS|FOLLOW_PATH, + NULL, NULL); + + if (result->status == NIS_SUCCESS) + { + int count; + char *domain; + + if ((count = NIS_RES_NUMOBJ(result)) != 1) + { + if (LogLevel > 10) + sm_syslog(LOG_WARNING, CurEnv->e_id, + "nisplus_getcanonname: lookup error, expected 1 entry, got %d", + count); + + /* ignore second entry */ + if (tTd(38, 20)) + sm_dprintf("nisplus_getcanonname(%s), got %d entries, all but first ignored\n", + name, count); + } + + if (tTd(38, 20)) + sm_dprintf("nisplus_getcanonname(%s), found in directory \"%s\"\n", + name, (NIS_RES_OBJECT(result))->zo_domain); + + + vp = ((NIS_RES_OBJECT(result))->EN_col(0)); + vsize = strlen(vp); + if (tTd(38, 20)) + sm_dprintf("nisplus_getcanonname(%s), found %s\n", + name, vp); + if (strchr(vp, '.') != NULL) + { + domain = ""; + } + else + { + domain = macvalue('m', CurEnv); + if (domain == NULL) + domain = ""; + } + if (hbsize > vsize + (int) strlen(domain) + 1) + { + if (domain[0] == '\0') + (void) sm_strlcpy(name, vp, hbsize); + else + (void) sm_snprintf(name, hbsize, + "%s.%s", vp, domain); + *statp = EX_OK; + } + else + *statp = EX_NOHOST; + nis_freeresult(result); + return true; + } + else + { + if (result->status == NIS_NOTFOUND) + *statp = EX_NOHOST; + else if (result->status == NIS_TRYAGAIN) + *statp = EX_TEMPFAIL; + else + *statp = EX_UNAVAILABLE; + } + if (tTd(38, 20)) + sm_dprintf("nisplus_getcanonname(%s), failed, status=%d, nsw_stat=%d\n", + name, result->status, *statp); + nis_freeresult(result); + return false; +} + +char * +nisplus_default_domain() +{ + static char default_domain[MAXNAME + 1] = ""; + char *p; + + if (default_domain[0] != '\0') + return default_domain; + + p = nis_local_directory(); + (void) sm_strlcpy(default_domain, p, sizeof(default_domain)); + return default_domain; +} + +#endif /* NISPLUS */ +/* +** LDAP Modules +*/ + +/* +** LDAPMAP_DEQUOTE - helper routine for ldapmap_parseargs +*/ + +#if defined(LDAPMAP) || defined(PH_MAP) + +# if PH_MAP +# define ph_map_dequote ldapmap_dequote +# endif /* PH_MAP */ + +static char *ldapmap_dequote __P((char *)); + +static char * +ldapmap_dequote(str) + char *str; +{ + char *p; + char *start; + + if (str == NULL) + return NULL; + + p = str; + if (*p == '"') + { + /* Should probably swallow initial whitespace here */ + start = ++p; + } + else + return str; + while (*p != '"' && *p != '\0') + p++; + if (*p != '\0') + *p = '\0'; + return start; +} +#endif /* defined(LDAPMAP) || defined(PH_MAP) */ + +#if LDAPMAP + +static SM_LDAP_STRUCT *LDAPDefaults = NULL; + +/* +** LDAPMAP_OPEN -- open LDAP map +** +** Connect to the LDAP server. Re-use existing connections since a +** single server connection to a host (with the same host, port, +** bind DN, and secret) can answer queries for multiple maps. +*/ + +bool +ldapmap_open(map, mode) + MAP *map; + int mode; +{ + SM_LDAP_STRUCT *lmap; + STAB *s; + char *id; + + if (tTd(38, 2)) + sm_dprintf("ldapmap_open(%s, %d): ", map->map_mname, mode); + +#if defined(SUN_EXTENSIONS) && defined(SUN_SIMPLIFIED_LDAP) && \ + HASLDAPGETALIASBYNAME + if (VendorCode == VENDOR_SUN && + strcmp(map->map_mname, "aliases.ldap") == 0) + { + return true; + } +#endif /* defined(SUN_EXTENSIONS) && defined(SUN_SIMPLIFIED_LDAP) && ... */ + + mode &= O_ACCMODE; + + /* sendmail doesn't have the ability to write to LDAP (yet) */ + if (mode != O_RDONLY) + { + /* issue a pseudo-error message */ + errno = SM_EMAPCANTWRITE; + return false; + } + + lmap = (SM_LDAP_STRUCT *) map->map_db1; + + s = ldapmap_findconn(lmap); + if (s->s_lmap != NULL) + { + /* Already have a connection open to this LDAP server */ + lmap->ldap_ld = ((SM_LDAP_STRUCT *)s->s_lmap->map_db1)->ldap_ld; + lmap->ldap_pid = ((SM_LDAP_STRUCT *)s->s_lmap->map_db1)->ldap_pid; + + /* Add this map as head of linked list */ + lmap->ldap_next = s->s_lmap; + s->s_lmap = map; + + if (tTd(38, 2)) + sm_dprintf("using cached connection\n"); + return true; + } + + if (tTd(38, 2)) + sm_dprintf("opening new connection\n"); + + if (lmap->ldap_host != NULL) + id = lmap->ldap_host; + else if (lmap->ldap_uri != NULL) + id = lmap->ldap_uri; + else + id = "localhost"; + + if (tTd(74, 104)) + { + extern MAPCLASS NullMapClass; + + /* debug mode: don't actually open an LDAP connection */ + map->map_orgclass = map->map_class; + map->map_class = &NullMapClass; + map->map_mflags |= MF_OPEN; + map->map_pid = CurrentPid; + return true; + } + + /* No connection yet, connect */ + if (!sm_ldap_start(map->map_mname, lmap)) + { + if (errno == ETIMEDOUT) + { + if (LogLevel > 1) + sm_syslog(LOG_NOTICE, CurEnv->e_id, + "timeout conning to LDAP server %.100s", + id); + } + + if (!bitset(MF_OPTIONAL, map->map_mflags)) + { + if (bitset(MF_NODEFER, map->map_mflags)) + { + syserr("%s failed to %s in map %s", +# if USE_LDAP_INIT + "ldap_init/ldap_bind", +# else /* USE_LDAP_INIT */ + "ldap_open", +# endif /* USE_LDAP_INIT */ + id, map->map_mname); + } + else + { + syserr("451 4.3.5 %s failed to %s in map %s", +# if USE_LDAP_INIT + "ldap_init/ldap_bind", +# else /* USE_LDAP_INIT */ + "ldap_open", +# endif /* USE_LDAP_INIT */ + id, map->map_mname); + } + } + return false; + } + + /* Save connection for reuse */ + s->s_lmap = map; + return true; +} + +/* +** LDAPMAP_CLOSE -- close ldap map +*/ + +void +ldapmap_close(map) + MAP *map; +{ + SM_LDAP_STRUCT *lmap; + STAB *s; + + if (tTd(38, 2)) + sm_dprintf("ldapmap_close(%s)\n", map->map_mname); + + lmap = (SM_LDAP_STRUCT *) map->map_db1; + + /* Check if already closed */ + if (lmap->ldap_ld == NULL) + return; + + /* Close the LDAP connection */ + sm_ldap_close(lmap); + + /* Mark all the maps that share the connection as closed */ + s = ldapmap_findconn(lmap); + + while (s->s_lmap != NULL) + { + MAP *smap = s->s_lmap; + + if (tTd(38, 2) && smap != map) + sm_dprintf("ldapmap_close(%s): closed %s (shared LDAP connection)\n", + map->map_mname, smap->map_mname); + smap->map_mflags &= ~(MF_OPEN|MF_WRITABLE); + lmap = (SM_LDAP_STRUCT *) smap->map_db1; + lmap->ldap_ld = NULL; + s->s_lmap = lmap->ldap_next; + lmap->ldap_next = NULL; + } +} + +# ifdef SUNET_ID +/* +** SUNET_ID_HASH -- Convert a string to its Sunet_id canonical form +** This only makes sense at Stanford University. +*/ + +static char * +sunet_id_hash(str) + char *str; +{ + char *p, *p_last; + + p = str; + p_last = p; + while (*p != '\0') + { + if (isascii(*p) && (islower(*p) || isdigit(*p))) + { + *p_last = *p; + p_last++; + } + else if (isascii(*p) && isupper(*p)) + { + *p_last = tolower(*p); + p_last++; + } + ++p; + } + if (*p_last != '\0') + *p_last = '\0'; + return str; +} +# define SM_CONVERT_ID(str) sunet_id_hash(str) +# else /* SUNET_ID */ +# define SM_CONVERT_ID(str) makelower(str) +# endif /* SUNET_ID */ + +/* +** LDAPMAP_LOOKUP -- look up a datum in a LDAP map +*/ + +char * +ldapmap_lookup(map, name, av, statp) + MAP *map; + char *name; + char **av; + int *statp; +{ + int flags; + int i; + int plen = 0; + int psize = 0; + int msgid; + int save_errno; + char *vp, *p; + char *result = NULL; + SM_RPOOL_T *rpool; + SM_LDAP_STRUCT *lmap = NULL; + char *argv[SM_LDAP_ARGS]; + char keybuf[MAXKEY]; +#if SM_LDAP_ARGS != MAX_MAP_ARGS +# ERROR _SM_LDAP_ARGS must be the same as _MAX_MAP_ARGS +#endif /* SM_LDAP_ARGS != MAX_MAP_ARGS */ + +#if defined(SUN_EXTENSIONS) && defined(SUN_SIMPLIFIED_LDAP) && \ + HASLDAPGETALIASBYNAME + if (VendorCode == VENDOR_SUN && + strcmp(map->map_mname, "aliases.ldap") == 0) + { + int rc; +#if defined(GETLDAPALIASBYNAME_VERSION) && (GETLDAPALIASBYNAME_VERSION >= 2) + extern char *__getldapaliasbyname(); + char *answer; + + answer = __getldapaliasbyname(name, &rc); +#else + char answer[MAXNAME + 1]; + + rc = __getldapaliasbyname(name, answer, sizeof(answer)); +#endif + if (rc != 0) + { + if (tTd(38, 20)) + sm_dprintf("getldapaliasbyname(%.100s) failed, errno=%d\n", + name, errno); + *statp = EX_NOTFOUND; + return NULL; + } + *statp = EX_OK; + if (tTd(38, 20)) + sm_dprintf("getldapaliasbyname(%.100s) => %s\n", name, + answer); + if (bitset(MF_MATCHONLY, map->map_mflags)) + result = map_rewrite(map, name, strlen(name), NULL); + else + result = map_rewrite(map, answer, strlen(answer), av); +#if defined(GETLDAPALIASBYNAME_VERSION) && (GETLDAPALIASBYNAME_VERSION >= 2) + free(answer); +#endif + return result; + } +#endif /* defined(SUN_EXTENSIONS) && defined(SUN_SIMPLIFIED_LDAP) && ... */ + + /* Get ldap struct pointer from map */ + lmap = (SM_LDAP_STRUCT *) map->map_db1; + sm_ldap_setopts(lmap->ldap_ld, lmap); + + if (lmap->ldap_multi_args) + { + SM_REQUIRE(av != NULL); + memset(argv, '\0', sizeof(argv)); + for (i = 0; i < SM_LDAP_ARGS && av[i] != NULL; i++) + { + argv[i] = sm_strdup(av[i]); + if (argv[i] == NULL) + { + int save_errno, j; + + save_errno = errno; + for (j = 0; j < i && argv[j] != NULL; j++) + SM_FREE(argv[j]); + *statp = EX_TEMPFAIL; + errno = save_errno; + return NULL; + } + + if (!bitset(MF_NOFOLDCASE, map->map_mflags)) + SM_CONVERT_ID(av[i]); + } + } + else + { + (void) sm_strlcpy(keybuf, name, sizeof(keybuf)); + + if (!bitset(MF_NOFOLDCASE, map->map_mflags)) + SM_CONVERT_ID(keybuf); + } + + if (tTd(38, 20)) + { + if (lmap->ldap_multi_args) + { + sm_dprintf("ldapmap_lookup(%s, argv)\n", + map->map_mname); + for (i = 0; i < SM_LDAP_ARGS; i++) + { + sm_dprintf(" argv[%d] = %s\n", i, + argv[i] == NULL ? "NULL" : argv[i]); + } + } + else + { + sm_dprintf("ldapmap_lookup(%s, %s)\n", + map->map_mname, name); + } + } + + if (lmap->ldap_multi_args) + { + msgid = sm_ldap_search_m(lmap, argv); + + /* free the argv array and its content, no longer needed */ + for (i = 0; i < SM_LDAP_ARGS && argv[i] != NULL; i++) + SM_FREE(argv[i]); + } + else + msgid = sm_ldap_search(lmap, keybuf); + if (msgid == SM_LDAP_ERR) + { + errno = sm_ldap_geterrno(lmap->ldap_ld) + E_LDAPBASE; + save_errno = errno; + if (!bitset(MF_OPTIONAL, map->map_mflags)) + { + /* + ** Do not include keybuf as this error may be shown + ** to outsiders. + */ + + if (bitset(MF_NODEFER, map->map_mflags)) + syserr("Error in ldap_search in map %s", + map->map_mname); + else + syserr("451 4.3.5 Error in ldap_search in map %s", + map->map_mname); + } + *statp = EX_TEMPFAIL; + switch (save_errno - E_LDAPBASE) + { +# ifdef LDAP_SERVER_DOWN + case LDAP_SERVER_DOWN: +# endif /* LDAP_SERVER_DOWN */ + case LDAP_TIMEOUT: + case LDAP_UNAVAILABLE: + /* server disappeared, try reopen on next search */ + ldapmap_close(map); + break; + } + errno = save_errno; + return NULL; + } +#if SM_LDAP_ERROR_ON_MISSING_ARGS + else if (msgid == SM_LDAP_ERR_ARG_MISS) + { + if (bitset(MF_NODEFER, map->map_mflags)) + syserr("Error in ldap_search in map %s, too few arguments", + map->map_mname); + else + syserr("554 5.3.5 Error in ldap_search in map %s, too few arguments", + map->map_mname); + *statp = EX_CONFIG; + return NULL; + } +#endif /* SM_LDAP_ERROR_ON_MISSING_ARGS */ + + *statp = EX_NOTFOUND; + vp = NULL; + + flags = 0; + if (bitset(MF_SINGLEMATCH, map->map_mflags)) + flags |= SM_LDAP_SINGLEMATCH; + if (bitset(MF_MATCHONLY, map->map_mflags)) + flags |= SM_LDAP_MATCHONLY; +# if _FFR_LDAP_SINGLEDN + if (bitset(MF_SINGLEDN, map->map_mflags)) + flags |= SM_LDAP_SINGLEDN; +# endif /* _FFR_LDAP_SINGLEDN */ + + /* Create an rpool for search related memory usage */ + rpool = sm_rpool_new_x(NULL); + + p = NULL; + *statp = sm_ldap_results(lmap, msgid, flags, map->map_coldelim, + rpool, &p, &plen, &psize, NULL); + save_errno = errno; + + /* Copy result so rpool can be freed */ + if (*statp == EX_OK && p != NULL) + vp = newstr(p); + sm_rpool_free(rpool); + + /* need to restart LDAP connection? */ + if (*statp == EX_RESTART) + { + *statp = EX_TEMPFAIL; + ldapmap_close(map); + } + + errno = save_errno; + if (*statp != EX_OK && *statp != EX_NOTFOUND) + { + if (!bitset(MF_OPTIONAL, map->map_mflags)) + { + if (bitset(MF_NODEFER, map->map_mflags)) + syserr("Error getting LDAP results in map %s", + map->map_mname); + else + syserr("451 4.3.5 Error getting LDAP results in map %s", + map->map_mname); + } + errno = save_errno; + return NULL; + } + + /* Did we match anything? */ + if (vp == NULL && !bitset(MF_MATCHONLY, map->map_mflags)) + return NULL; + + if (*statp == EX_OK) + { + if (LogLevel > 9) + sm_syslog(LOG_INFO, CurEnv->e_id, + "ldap %.100s => %s", name, + vp == NULL ? "<NULL>" : vp); + if (bitset(MF_MATCHONLY, map->map_mflags)) + result = map_rewrite(map, name, strlen(name), NULL); + else + { + /* vp != NULL according to test above */ + result = map_rewrite(map, vp, strlen(vp), av); + } + if (vp != NULL) + sm_free(vp); /* XXX */ + } + return result; +} + +/* +** LDAPMAP_FINDCONN -- find an LDAP connection to the server +** +** Cache LDAP connections based on the host, port, bind DN, +** secret, and PID so we don't have multiple connections open to +** the same server for different maps. Need a separate connection +** per PID since a parent process may close the map before the +** child is done with it. +** +** Parameters: +** lmap -- LDAP map information +** +** Returns: +** Symbol table entry for the LDAP connection. +*/ + +static STAB * +ldapmap_findconn(lmap) + SM_LDAP_STRUCT *lmap; +{ + char *format; + char *nbuf; + char *id; + STAB *SM_NONVOLATILE s = NULL; + + if (lmap->ldap_host != NULL) + id = lmap->ldap_host; + else if (lmap->ldap_uri != NULL) + id = lmap->ldap_uri; + else + id = "localhost"; + + format = "%s%c%d%c%d%c%s%c%s%d"; + nbuf = sm_stringf_x(format, + id, + CONDELSE, + lmap->ldap_port, + CONDELSE, + lmap->ldap_version, + CONDELSE, + (lmap->ldap_binddn == NULL ? "" + : lmap->ldap_binddn), + CONDELSE, + (lmap->ldap_secret == NULL ? "" + : lmap->ldap_secret), + (int) CurrentPid); + SM_TRY + s = stab(nbuf, ST_LMAP, ST_ENTER); + SM_FINALLY + sm_free(nbuf); + SM_END_TRY + return s; +} +/* +** LDAPMAP_PARSEARGS -- parse ldap map definition args. +*/ + +static struct lamvalues LDAPAuthMethods[] = +{ + { "none", LDAP_AUTH_NONE }, + { "simple", LDAP_AUTH_SIMPLE }, +# ifdef LDAP_AUTH_KRBV4 + { "krbv4", LDAP_AUTH_KRBV4 }, +# endif /* LDAP_AUTH_KRBV4 */ + { NULL, 0 } +}; + +static struct ladvalues LDAPAliasDereference[] = +{ + { "never", LDAP_DEREF_NEVER }, + { "always", LDAP_DEREF_ALWAYS }, + { "search", LDAP_DEREF_SEARCHING }, + { "find", LDAP_DEREF_FINDING }, + { NULL, 0 } +}; + +static struct lssvalues LDAPSearchScope[] = +{ + { "base", LDAP_SCOPE_BASE }, + { "one", LDAP_SCOPE_ONELEVEL }, + { "sub", LDAP_SCOPE_SUBTREE }, + { NULL, 0 } +}; + +bool +ldapmap_parseargs(map, args) + MAP *map; + char *args; +{ + bool secretread = true; + bool attrssetup = false; + int i; + register char *p = args; + SM_LDAP_STRUCT *lmap; + struct lamvalues *lam; + struct ladvalues *lad; + struct lssvalues *lss; + char ldapfilt[MAXLINE]; + char m_tmp[MAXPATHLEN + LDAPMAP_MAX_PASSWD]; + + /* Get ldap struct pointer from map */ + lmap = (SM_LDAP_STRUCT *) map->map_db1; + + /* Check if setting the initial LDAP defaults */ + if (lmap == NULL || lmap != LDAPDefaults) + { + /* We need to alloc an SM_LDAP_STRUCT struct */ + lmap = (SM_LDAP_STRUCT *) xalloc(sizeof(*lmap)); + if (LDAPDefaults == NULL) + sm_ldap_clear(lmap); + else + STRUCTCOPY(*LDAPDefaults, *lmap); + } + + /* there is no check whether there is really an argument */ + map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL; + map->map_spacesub = SpaceSub; /* default value */ + + /* Check if setting up an alias or file class LDAP map */ + if (bitset(MF_ALIAS, map->map_mflags)) + { + /* Comma separate if used as an alias file */ + map->map_coldelim = ','; + if (*args == '\0') + { + int n; + char *lc; + char jbuf[MAXHOSTNAMELEN]; + char lcbuf[MAXLINE]; + + /* Get $j */ + expand("\201j", jbuf, sizeof(jbuf), &BlankEnvelope); + if (jbuf[0] == '\0') + { + (void) sm_strlcpy(jbuf, "localhost", + sizeof(jbuf)); + } + + lc = macvalue(macid("{sendmailMTACluster}"), CurEnv); + if (lc == NULL) + lc = ""; + else + { + expand(lc, lcbuf, sizeof(lcbuf), CurEnv); + lc = lcbuf; + } + + n = sm_snprintf(ldapfilt, sizeof(ldapfilt), + "(&(objectClass=sendmailMTAAliasObject)(sendmailMTAAliasGrouping=aliases)(|(sendmailMTACluster=%s)(sendmailMTAHost=%s))(sendmailMTAKey=%%0))", + lc, jbuf); + if (n >= sizeof(ldapfilt)) + { + syserr("%s: Default LDAP string too long", + map->map_mname); + return false; + } + + /* default args for an alias LDAP entry */ + lmap->ldap_filter = ldapfilt; + lmap->ldap_attr[0] = "objectClass"; + lmap->ldap_attr_type[0] = SM_LDAP_ATTR_OBJCLASS; + lmap->ldap_attr_needobjclass[0] = NULL; + lmap->ldap_attr[1] = "sendmailMTAAliasValue"; + lmap->ldap_attr_type[1] = SM_LDAP_ATTR_NORMAL; + lmap->ldap_attr_needobjclass[1] = NULL; + lmap->ldap_attr[2] = "sendmailMTAAliasSearch"; + lmap->ldap_attr_type[2] = SM_LDAP_ATTR_FILTER; + lmap->ldap_attr_needobjclass[2] = "sendmailMTAMapObject"; + lmap->ldap_attr[3] = "sendmailMTAAliasURL"; + lmap->ldap_attr_type[3] = SM_LDAP_ATTR_URL; + lmap->ldap_attr_needobjclass[3] = "sendmailMTAMapObject"; + lmap->ldap_attr[4] = NULL; + lmap->ldap_attr_type[4] = SM_LDAP_ATTR_NONE; + lmap->ldap_attr_needobjclass[4] = NULL; + attrssetup = true; + } + } + else if (bitset(MF_FILECLASS, map->map_mflags)) + { + /* Space separate if used as a file class file */ + map->map_coldelim = ' '; + } + +# if _FFR_LDAP_NETWORK_TIMEOUT + lmap->ldap_networktmo = 120; +# endif /* _FFR_LDAP_NETWORK_TIMEOUT */ + + for (;;) + { + while (isascii(*p) && isspace(*p)) + p++; + if (*p != '-') + break; + switch (*++p) + { + case 'A': + map->map_mflags |= MF_APPEND; + break; + + case 'a': + map->map_app = ++p; + break; + + case 'D': + map->map_mflags |= MF_DEFER; + break; + + case 'f': + map->map_mflags |= MF_NOFOLDCASE; + break; + + case 'm': + map->map_mflags |= MF_MATCHONLY; + break; + + case 'N': + map->map_mflags |= MF_INCLNULL; + map->map_mflags &= ~MF_TRY0NULL; + break; + + case 'O': + map->map_mflags &= ~MF_TRY1NULL; + break; + + case 'o': + map->map_mflags |= MF_OPTIONAL; + break; + + case 'q': + map->map_mflags |= MF_KEEPQUOTES; + break; + + case 'S': + map->map_spacesub = *++p; + break; + + case 'T': + map->map_tapp = ++p; + break; + + case 't': + map->map_mflags |= MF_NODEFER; + break; + + case 'z': + if (*++p != '\\') + map->map_coldelim = *p; + else + { + switch (*++p) + { + case 'n': + map->map_coldelim = '\n'; + break; + + case 't': + map->map_coldelim = '\t'; + break; + + default: + map->map_coldelim = '\\'; + } + } + break; + + /* Start of ldapmap specific args */ + case '1': + map->map_mflags |= MF_SINGLEMATCH; + break; + +# if _FFR_LDAP_SINGLEDN + case '2': + map->map_mflags |= MF_SINGLEDN; + break; +# endif /* _FFR_LDAP_SINGLEDN */ + + case 'b': /* search base */ + while (isascii(*++p) && isspace(*p)) + continue; + lmap->ldap_base = p; + break; + +# if _FFR_LDAP_NETWORK_TIMEOUT + case 'c': /* network (connect) timeout */ + while (isascii(*++p) && isspace(*p)) + continue; + lmap->ldap_networktmo = atoi(p); + break; +# endif /* _FFR_LDAP_NETWORK_TIMEOUT */ + + case 'd': /* Dn to bind to server as */ + while (isascii(*++p) && isspace(*p)) + continue; + lmap->ldap_binddn = p; + break; + + case 'H': /* Use LDAP URI */ +# if !USE_LDAP_INIT + syserr("Must compile with -DUSE_LDAP_INIT to use LDAP URIs (-H) in map %s", + map->map_mname); + return false; +# else /* !USE_LDAP_INIT */ + if (lmap->ldap_host != NULL) + { + syserr("Can not specify both an LDAP host and an LDAP URI in map %s", + map->map_mname); + return false; + } + while (isascii(*++p) && isspace(*p)) + continue; + lmap->ldap_uri = p; + break; +# endif /* !USE_LDAP_INIT */ + + case 'h': /* ldap host */ + while (isascii(*++p) && isspace(*p)) + continue; + if (lmap->ldap_uri != NULL) + { + syserr("Can not specify both an LDAP host and an LDAP URI in map %s", + map->map_mname); + return false; + } + lmap->ldap_host = p; + break; + + case 'K': + lmap->ldap_multi_args = true; + break; + + case 'k': /* search field */ + while (isascii(*++p) && isspace(*p)) + continue; + lmap->ldap_filter = p; + break; + + case 'l': /* time limit */ + while (isascii(*++p) && isspace(*p)) + continue; + lmap->ldap_timelimit = atoi(p); + lmap->ldap_timeout.tv_sec = lmap->ldap_timelimit; + break; + + case 'M': /* Method for binding */ + while (isascii(*++p) && isspace(*p)) + continue; + + if (sm_strncasecmp(p, "LDAP_AUTH_", 10) == 0) + p += 10; + + for (lam = LDAPAuthMethods; + lam != NULL && lam->lam_name != NULL; lam++) + { + if (sm_strncasecmp(p, lam->lam_name, + strlen(lam->lam_name)) == 0) + break; + } + if (lam->lam_name != NULL) + lmap->ldap_method = lam->lam_code; + else + { + /* bad config line */ + if (!bitset(MCF_OPTFILE, + map->map_class->map_cflags)) + { + char *ptr; + + if ((ptr = strchr(p, ' ')) != NULL) + *ptr = '\0'; + syserr("Method for binding must be [none|simple|krbv4] (not %s) in map %s", + p, map->map_mname); + if (ptr != NULL) + *ptr = ' '; + return false; + } + } + break; + + case 'n': /* retrieve attribute names only */ + lmap->ldap_attrsonly = LDAPMAP_TRUE; + break; + + /* + ** This is a string that is dependent on the + ** method used defined by 'M'. + */ + + case 'P': /* Secret password for binding */ + while (isascii(*++p) && isspace(*p)) + continue; + lmap->ldap_secret = p; + secretread = false; + break; + + case 'p': /* ldap port */ + while (isascii(*++p) && isspace(*p)) + continue; + lmap->ldap_port = atoi(p); + break; + + /* args stolen from ldapsearch.c */ + case 'R': /* don't auto chase referrals */ +# ifdef LDAP_REFERRALS + lmap->ldap_options &= ~LDAP_OPT_REFERRALS; +# else /* LDAP_REFERRALS */ + syserr("compile with -DLDAP_REFERRALS for referral support"); +# endif /* LDAP_REFERRALS */ + break; + + case 'r': /* alias dereferencing */ + while (isascii(*++p) && isspace(*p)) + continue; + + if (sm_strncasecmp(p, "LDAP_DEREF_", 11) == 0) + p += 11; + + for (lad = LDAPAliasDereference; + lad != NULL && lad->lad_name != NULL; lad++) + { + if (sm_strncasecmp(p, lad->lad_name, + strlen(lad->lad_name)) == 0) + break; + } + if (lad->lad_name != NULL) + lmap->ldap_deref = lad->lad_code; + else + { + /* bad config line */ + if (!bitset(MCF_OPTFILE, + map->map_class->map_cflags)) + { + char *ptr; + + if ((ptr = strchr(p, ' ')) != NULL) + *ptr = '\0'; + syserr("Deref must be [never|always|search|find] (not %s) in map %s", + p, map->map_mname); + if (ptr != NULL) + *ptr = ' '; + return false; + } + } + break; + + case 's': /* search scope */ + while (isascii(*++p) && isspace(*p)) + continue; + + if (sm_strncasecmp(p, "LDAP_SCOPE_", 11) == 0) + p += 11; + + for (lss = LDAPSearchScope; + lss != NULL && lss->lss_name != NULL; lss++) + { + if (sm_strncasecmp(p, lss->lss_name, + strlen(lss->lss_name)) == 0) + break; + } + if (lss->lss_name != NULL) + lmap->ldap_scope = lss->lss_code; + else + { + /* bad config line */ + if (!bitset(MCF_OPTFILE, + map->map_class->map_cflags)) + { + char *ptr; + + if ((ptr = strchr(p, ' ')) != NULL) + *ptr = '\0'; + syserr("Scope must be [base|one|sub] (not %s) in map %s", + p, map->map_mname); + if (ptr != NULL) + *ptr = ' '; + return false; + } + } + break; + + case 'V': + if (*++p != '\\') + lmap->ldap_attrsep = *p; + else + { + switch (*++p) + { + case 'n': + lmap->ldap_attrsep = '\n'; + break; + + case 't': + lmap->ldap_attrsep = '\t'; + break; + + default: + lmap->ldap_attrsep = '\\'; + } + } + break; + + case 'v': /* attr to return */ + while (isascii(*++p) && isspace(*p)) + continue; + lmap->ldap_attr[0] = p; + lmap->ldap_attr[1] = NULL; + break; + + case 'w': + /* -w should be for passwd, -P should be for version */ + while (isascii(*++p) && isspace(*p)) + continue; + lmap->ldap_version = atoi(p); +# ifdef LDAP_VERSION_MAX + if (lmap->ldap_version > LDAP_VERSION_MAX) + { + syserr("LDAP version %d exceeds max of %d in map %s", + lmap->ldap_version, LDAP_VERSION_MAX, + map->map_mname); + return false; + } +# endif /* LDAP_VERSION_MAX */ +# ifdef LDAP_VERSION_MIN + if (lmap->ldap_version < LDAP_VERSION_MIN) + { + syserr("LDAP version %d is lower than min of %d in map %s", + lmap->ldap_version, LDAP_VERSION_MIN, + map->map_mname); + return false; + } +# endif /* LDAP_VERSION_MIN */ + break; + + case 'Z': + while (isascii(*++p) && isspace(*p)) + continue; + lmap->ldap_sizelimit = atoi(p); + break; + + default: + syserr("Illegal option %c map %s", *p, map->map_mname); + break; + } + + /* need to account for quoted strings here */ + while (*p != '\0' && !(isascii(*p) && isspace(*p))) + { + if (*p == '"') + { + while (*++p != '"' && *p != '\0') + continue; + if (*p != '\0') + p++; + } + else + p++; + } + + if (*p != '\0') + *p++ = '\0'; + } + + if (map->map_app != NULL) + map->map_app = newstr(ldapmap_dequote(map->map_app)); + if (map->map_tapp != NULL) + map->map_tapp = newstr(ldapmap_dequote(map->map_tapp)); + + /* + ** We need to swallow up all the stuff into a struct + ** and dump it into map->map_dbptr1 + */ + + if (lmap->ldap_host != NULL && + (LDAPDefaults == NULL || + LDAPDefaults == lmap || + LDAPDefaults->ldap_host != lmap->ldap_host)) + lmap->ldap_host = newstr(ldapmap_dequote(lmap->ldap_host)); + map->map_domain = lmap->ldap_host; + + if (lmap->ldap_uri != NULL && + (LDAPDefaults == NULL || + LDAPDefaults == lmap || + LDAPDefaults->ldap_uri != lmap->ldap_uri)) + lmap->ldap_uri = newstr(ldapmap_dequote(lmap->ldap_uri)); + map->map_domain = lmap->ldap_uri; + + if (lmap->ldap_binddn != NULL && + (LDAPDefaults == NULL || + LDAPDefaults == lmap || + LDAPDefaults->ldap_binddn != lmap->ldap_binddn)) + lmap->ldap_binddn = newstr(ldapmap_dequote(lmap->ldap_binddn)); + + if (lmap->ldap_secret != NULL && + (LDAPDefaults == NULL || + LDAPDefaults == lmap || + LDAPDefaults->ldap_secret != lmap->ldap_secret)) + { + SM_FILE_T *sfd; + long sff = SFF_OPENASROOT|SFF_ROOTOK|SFF_NOWLINK|SFF_NOWWFILES|SFF_NOGWFILES; + + if (DontLockReadFiles) + sff |= SFF_NOLOCK; + + /* need to use method to map secret to passwd string */ + switch (lmap->ldap_method) + { + case LDAP_AUTH_NONE: + /* Do nothing */ + break; + + case LDAP_AUTH_SIMPLE: + + /* + ** Secret is the name of a file with + ** the first line as the password. + */ + + /* Already read in the secret? */ + if (secretread) + break; + + sfd = safefopen(ldapmap_dequote(lmap->ldap_secret), + O_RDONLY, 0, sff); + if (sfd == NULL) + { + syserr("LDAP map: cannot open secret %s", + ldapmap_dequote(lmap->ldap_secret)); + return false; + } + lmap->ldap_secret = sfgets(m_tmp, sizeof(m_tmp), + sfd, TimeOuts.to_fileopen, + "ldapmap_parseargs"); + (void) sm_io_close(sfd, SM_TIME_DEFAULT); + if (strlen(m_tmp) > LDAPMAP_MAX_PASSWD) + { + syserr("LDAP map: secret in %s too long", + ldapmap_dequote(lmap->ldap_secret)); + return false; + } + if (lmap->ldap_secret != NULL && + strlen(m_tmp) > 0) + { + /* chomp newline */ + if (m_tmp[strlen(m_tmp) - 1] == '\n') + m_tmp[strlen(m_tmp) - 1] = '\0'; + + lmap->ldap_secret = m_tmp; + } + break; + +# ifdef LDAP_AUTH_KRBV4 + case LDAP_AUTH_KRBV4: + + /* + ** Secret is where the ticket file is + ** stashed + */ + + (void) sm_snprintf(m_tmp, sizeof(m_tmp), + "KRBTKFILE=%s", + ldapmap_dequote(lmap->ldap_secret)); + lmap->ldap_secret = m_tmp; + break; +# endif /* LDAP_AUTH_KRBV4 */ + + default: /* Should NEVER get here */ + syserr("LDAP map: Illegal value in lmap method"); + return false; + /* NOTREACHED */ + break; + } + } + + if (lmap->ldap_secret != NULL && + (LDAPDefaults == NULL || + LDAPDefaults == lmap || + LDAPDefaults->ldap_secret != lmap->ldap_secret)) + lmap->ldap_secret = newstr(ldapmap_dequote(lmap->ldap_secret)); + + if (lmap->ldap_base != NULL && + (LDAPDefaults == NULL || + LDAPDefaults == lmap || + LDAPDefaults->ldap_base != lmap->ldap_base)) + lmap->ldap_base = newstr(ldapmap_dequote(lmap->ldap_base)); + + /* + ** Save the server from extra work. If request is for a single + ** match, tell the server to only return enough records to + ** determine if there is a single match or not. This can not + ** be one since the server would only return one and we wouldn't + ** know if there were others available. + */ + + if (bitset(MF_SINGLEMATCH, map->map_mflags)) + lmap->ldap_sizelimit = 2; + + /* If setting defaults, don't process ldap_filter and ldap_attr */ + if (lmap == LDAPDefaults) + return true; + + if (lmap->ldap_filter != NULL) + lmap->ldap_filter = newstr(ldapmap_dequote(lmap->ldap_filter)); + else + { + if (!bitset(MCF_OPTFILE, map->map_class->map_cflags)) + { + syserr("No filter given in map %s", map->map_mname); + return false; + } + } + + if (!attrssetup && lmap->ldap_attr[0] != NULL) + { + bool recurse = false; + bool normalseen = false; + + i = 0; + p = ldapmap_dequote(lmap->ldap_attr[0]); + lmap->ldap_attr[0] = NULL; + + /* Prime the attr list with the objectClass attribute */ + lmap->ldap_attr[i] = "objectClass"; + lmap->ldap_attr_type[i] = SM_LDAP_ATTR_OBJCLASS; + lmap->ldap_attr_needobjclass[i] = NULL; + i++; + + while (p != NULL) + { + char *v; + + while (isascii(*p) && isspace(*p)) + p++; + if (*p == '\0') + break; + v = p; + p = strchr(v, ','); + if (p != NULL) + *p++ = '\0'; + + if (i >= LDAPMAP_MAX_ATTR) + { + syserr("Too many return attributes in %s (max %d)", + map->map_mname, LDAPMAP_MAX_ATTR); + return false; + } + if (*v != '\0') + { + int j; + int use; + char *type; + char *needobjclass; + + type = strchr(v, ':'); + if (type != NULL) + { + *type++ = '\0'; + needobjclass = strchr(type, ':'); + if (needobjclass != NULL) + *needobjclass++ = '\0'; + } + else + { + needobjclass = NULL; + } + + use = i; + + /* allow override on "objectClass" type */ + if (sm_strcasecmp(v, "objectClass") == 0 && + lmap->ldap_attr_type[0] == SM_LDAP_ATTR_OBJCLASS) + { + use = 0; + } + else + { + /* + ** Don't add something to attribute + ** list twice. + */ + + for (j = 1; j < i; j++) + { + if (sm_strcasecmp(v, lmap->ldap_attr[j]) == 0) + { + syserr("Duplicate attribute (%s) in %s", + v, map->map_mname); + return false; + } + } + + lmap->ldap_attr[use] = newstr(v); + if (needobjclass != NULL && + *needobjclass != '\0' && + *needobjclass != '*') + { + lmap->ldap_attr_needobjclass[use] = newstr(needobjclass); + } + else + { + lmap->ldap_attr_needobjclass[use] = NULL; + } + + } + + if (type != NULL && *type != '\0') + { + if (sm_strcasecmp(type, "dn") == 0) + { + recurse = true; + lmap->ldap_attr_type[use] = SM_LDAP_ATTR_DN; + } + else if (sm_strcasecmp(type, "filter") == 0) + { + recurse = true; + lmap->ldap_attr_type[use] = SM_LDAP_ATTR_FILTER; + } + else if (sm_strcasecmp(type, "url") == 0) + { + recurse = true; + lmap->ldap_attr_type[use] = SM_LDAP_ATTR_URL; + } + else if (sm_strcasecmp(type, "normal") == 0) + { + lmap->ldap_attr_type[use] = SM_LDAP_ATTR_NORMAL; + normalseen = true; + } + else + { + syserr("Unknown attribute type (%s) in %s", + type, map->map_mname); + return false; + } + } + else + { + lmap->ldap_attr_type[use] = SM_LDAP_ATTR_NORMAL; + normalseen = true; + } + i++; + } + } + lmap->ldap_attr[i] = NULL; + + /* Set in case needed in future code */ + attrssetup = true; + + if (recurse && !normalseen) + { + syserr("LDAP recursion requested in %s but no returnable attribute given", + map->map_mname); + return false; + } + if (recurse && lmap->ldap_attrsonly == LDAPMAP_TRUE) + { + syserr("LDAP recursion requested in %s can not be used with -n", + map->map_mname); + return false; + } + } + map->map_db1 = (ARBPTR_T) lmap; + return true; +} + +/* +** LDAPMAP_SET_DEFAULTS -- Read default map spec from LDAPDefaults in .cf +** +** Parameters: +** spec -- map argument string from LDAPDefaults option +** +** Returns: +** None. +*/ + +void +ldapmap_set_defaults(spec) + char *spec; +{ + STAB *class; + MAP map; + + /* Allocate and set the default values */ + if (LDAPDefaults == NULL) + LDAPDefaults = (SM_LDAP_STRUCT *) xalloc(sizeof(*LDAPDefaults)); + sm_ldap_clear(LDAPDefaults); + + memset(&map, '\0', sizeof(map)); + + /* look up the class */ + class = stab("ldap", ST_MAPCLASS, ST_FIND); + if (class == NULL) + { + syserr("readcf: LDAPDefaultSpec: class ldap not available"); + return; + } + map.map_class = &class->s_mapclass; + map.map_db1 = (ARBPTR_T) LDAPDefaults; + map.map_mname = "O LDAPDefaultSpec"; + + (void) ldapmap_parseargs(&map, spec); + + /* These should never be set in LDAPDefaults */ + if (map.map_mflags != (MF_TRY0NULL|MF_TRY1NULL) || + map.map_spacesub != SpaceSub || + map.map_app != NULL || + map.map_tapp != NULL) + { + syserr("readcf: option LDAPDefaultSpec: Do not set non-LDAP specific flags"); + SM_FREE_CLR(map.map_app); + SM_FREE_CLR(map.map_tapp); + } + + if (LDAPDefaults->ldap_filter != NULL) + { + syserr("readcf: option LDAPDefaultSpec: Do not set the LDAP search filter"); + + /* don't free, it isn't malloc'ed in parseargs */ + LDAPDefaults->ldap_filter = NULL; + } + + if (LDAPDefaults->ldap_attr[0] != NULL) + { + syserr("readcf: option LDAPDefaultSpec: Do not set the requested LDAP attributes"); + /* don't free, they aren't malloc'ed in parseargs */ + LDAPDefaults->ldap_attr[0] = NULL; + } +} +#endif /* LDAPMAP */ +/* +** PH map +*/ + +#if PH_MAP + +/* +** Support for the CCSO Nameserver (ph/qi). +** This code is intended to replace the so-called "ph mailer". +** Contributed by Mark D. Roth. Contact him for support. +*/ + +/* what version of the ph map code we're running */ +static char phmap_id[128]; + +/* sendmail version for phmap id string */ +extern const char Version[]; + +/* assume we're using nph-1.2.x if not specified */ +# ifndef NPH_VERSION +# define NPH_VERSION 10200 +# endif + +/* compatibility for versions older than nph-1.2.0 */ +# if NPH_VERSION < 10200 +# define PH_OPEN_ROUNDROBIN PH_ROUNDROBIN +# define PH_OPEN_DONTID PH_DONTID +# define PH_CLOSE_FAST PH_FASTCLOSE +# define PH_ERR_DATAERR PH_DATAERR +# define PH_ERR_NOMATCH PH_NOMATCH +# endif /* NPH_VERSION < 10200 */ + +/* +** PH_MAP_PARSEARGS -- parse ph map definition args. +*/ + +bool +ph_map_parseargs(map, args) + MAP *map; + char *args; +{ + register bool done; + register char *p = args; + PH_MAP_STRUCT *pmap = NULL; + + /* initialize version string */ + (void) sm_snprintf(phmap_id, sizeof(phmap_id), + "sendmail-%s phmap-20010529 libphclient-%s", + Version, libphclient_version); + + pmap = (PH_MAP_STRUCT *) xalloc(sizeof(*pmap)); + + /* defaults */ + pmap->ph_servers = NULL; + pmap->ph_field_list = NULL; + pmap->ph = NULL; + pmap->ph_timeout = 0; + pmap->ph_fastclose = 0; + + map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL; + for (;;) + { + while (isascii(*p) && isspace(*p)) + p++; + if (*p != '-') + break; + switch (*++p) + { + case 'N': + map->map_mflags |= MF_INCLNULL; + map->map_mflags &= ~MF_TRY0NULL; + break; + + case 'O': + map->map_mflags &= ~MF_TRY1NULL; + break; + + case 'o': + map->map_mflags |= MF_OPTIONAL; + break; + + case 'f': + map->map_mflags |= MF_NOFOLDCASE; + break; + + case 'm': + map->map_mflags |= MF_MATCHONLY; + break; + + case 'A': + map->map_mflags |= MF_APPEND; + break; + + case 'q': + map->map_mflags |= MF_KEEPQUOTES; + break; + + case 't': + map->map_mflags |= MF_NODEFER; + break; + + case 'a': + map->map_app = ++p; + break; + + case 'T': + map->map_tapp = ++p; + break; + + case 'l': + while (isascii(*++p) && isspace(*p)) + continue; + pmap->ph_timeout = atoi(p); + break; + + case 'S': + map->map_spacesub = *++p; + break; + + case 'D': + map->map_mflags |= MF_DEFER; + break; + + case 'h': /* PH server list */ + while (isascii(*++p) && isspace(*p)) + continue; + pmap->ph_servers = p; + break; + + case 'k': /* fields to search for */ + while (isascii(*++p) && isspace(*p)) + continue; + pmap->ph_field_list = p; + break; + + default: + syserr("ph_map_parseargs: unknown option -%c", *p); + } + + /* try to account for quoted strings */ + done = isascii(*p) && isspace(*p); + while (*p != '\0' && !done) + { + if (*p == '"') + { + while (*++p != '"' && *p != '\0') + continue; + if (*p != '\0') + p++; + } + else + p++; + done = isascii(*p) && isspace(*p); + } + + if (*p != '\0') + *p++ = '\0'; + } + + if (map->map_app != NULL) + map->map_app = newstr(ph_map_dequote(map->map_app)); + if (map->map_tapp != NULL) + map->map_tapp = newstr(ph_map_dequote(map->map_tapp)); + + if (pmap->ph_field_list != NULL) + pmap->ph_field_list = newstr(ph_map_dequote(pmap->ph_field_list)); + + if (pmap->ph_servers != NULL) + pmap->ph_servers = newstr(ph_map_dequote(pmap->ph_servers)); + else + { + syserr("ph_map_parseargs: -h flag is required"); + return false; + } + + map->map_db1 = (ARBPTR_T) pmap; + return true; +} + +/* +** PH_MAP_CLOSE -- close the connection to the ph server +*/ + +void +ph_map_close(map) + MAP *map; +{ + PH_MAP_STRUCT *pmap; + + pmap = (PH_MAP_STRUCT *)map->map_db1; + if (tTd(38, 9)) + sm_dprintf("ph_map_close(%s): pmap->ph_fastclose=%d\n", + map->map_mname, pmap->ph_fastclose); + + + if (pmap->ph != NULL) + { + ph_set_sendhook(pmap->ph, NULL); + ph_set_recvhook(pmap->ph, NULL); + ph_close(pmap->ph, pmap->ph_fastclose); + } + + map->map_mflags &= ~(MF_OPEN|MF_WRITABLE); +} + +static jmp_buf PHTimeout; + +/* ARGSUSED */ +static void +ph_timeout(unused) + int unused; +{ + /* + ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD + ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE + ** DOING. + */ + + errno = ETIMEDOUT; + longjmp(PHTimeout, 1); +} + +static void +#if NPH_VERSION >= 10200 +ph_map_send_debug(appdata, text) + void *appdata; +#else +ph_map_send_debug(text) +#endif + char *text; +{ + if (LogLevel > 9) + sm_syslog(LOG_NOTICE, CurEnv->e_id, + "ph_map_send_debug: ==> %s", text); + if (tTd(38, 20)) + sm_dprintf("ph_map_send_debug: ==> %s\n", text); +} + +static void +#if NPH_VERSION >= 10200 +ph_map_recv_debug(appdata, text) + void *appdata; +#else +ph_map_recv_debug(text) +#endif + char *text; +{ + if (LogLevel > 10) + sm_syslog(LOG_NOTICE, CurEnv->e_id, + "ph_map_recv_debug: <== %s", text); + if (tTd(38, 21)) + sm_dprintf("ph_map_recv_debug: <== %s\n", text); +} + +/* +** PH_MAP_OPEN -- sub for opening PH map +*/ +bool +ph_map_open(map, mode) + MAP *map; + int mode; +{ + PH_MAP_STRUCT *pmap; + register SM_EVENT *ev = NULL; + int save_errno = 0; + char *hostlist, *host; + + if (tTd(38, 2)) + sm_dprintf("ph_map_open(%s)\n", map->map_mname); + + mode &= O_ACCMODE; + if (mode != O_RDONLY) + { + /* issue a pseudo-error message */ + errno = SM_EMAPCANTWRITE; + return false; + } + + if (CurEnv != NULL && CurEnv->e_sendmode == SM_DEFER && + bitset(MF_DEFER, map->map_mflags)) + { + if (tTd(9, 1)) + sm_dprintf("ph_map_open(%s) => DEFERRED\n", + map->map_mname); + + /* + ** Unset MF_DEFER here so that map_lookup() returns + ** a temporary failure using the bogus map and + ** map->map_tapp instead of the default permanent error. + */ + + map->map_mflags &= ~MF_DEFER; + return false; + } + + pmap = (PH_MAP_STRUCT *)map->map_db1; + pmap->ph_fastclose = 0; /* refresh field for reopen */ + + /* try each host in the list */ + hostlist = newstr(pmap->ph_servers); + for (host = strtok(hostlist, " "); + host != NULL; + host = strtok(NULL, " ")) + { + /* set timeout */ + if (pmap->ph_timeout != 0) + { + if (setjmp(PHTimeout) != 0) + { + ev = NULL; + if (LogLevel > 1) + sm_syslog(LOG_NOTICE, CurEnv->e_id, + "timeout connecting to PH server %.100s", + host); + errno = ETIMEDOUT; + goto ph_map_open_abort; + } + ev = sm_setevent(pmap->ph_timeout, ph_timeout, 0); + } + + /* open connection to server */ + if (ph_open(&(pmap->ph), host, + PH_OPEN_ROUNDROBIN|PH_OPEN_DONTID, + ph_map_send_debug, ph_map_recv_debug +#if NPH_VERSION >= 10200 + , NULL +#endif + ) == 0 + && ph_id(pmap->ph, phmap_id) == 0) + { + if (ev != NULL) + sm_clrevent(ev); + sm_free(hostlist); /* XXX */ + return true; + } + + ph_map_open_abort: + save_errno = errno; + if (ev != NULL) + sm_clrevent(ev); + pmap->ph_fastclose = PH_CLOSE_FAST; + ph_map_close(map); + errno = save_errno; + } + + if (bitset(MF_NODEFER, map->map_mflags)) + { + if (errno == 0) + errno = EAGAIN; + syserr("ph_map_open: %s: cannot connect to PH server", + map->map_mname); + } + else if (!bitset(MF_OPTIONAL, map->map_mflags) && LogLevel > 1) + sm_syslog(LOG_NOTICE, CurEnv->e_id, + "ph_map_open: %s: cannot connect to PH server", + map->map_mname); + sm_free(hostlist); /* XXX */ + return false; +} + +/* +** PH_MAP_LOOKUP -- look up key from ph server +*/ + +char * +ph_map_lookup(map, key, args, pstat) + MAP *map; + char *key; + char **args; + int *pstat; +{ + int i, save_errno = 0; + register SM_EVENT *ev = NULL; + PH_MAP_STRUCT *pmap; + char *value = NULL; + + pmap = (PH_MAP_STRUCT *)map->map_db1; + + *pstat = EX_OK; + + /* set timeout */ + if (pmap->ph_timeout != 0) + { + if (setjmp(PHTimeout) != 0) + { + ev = NULL; + if (LogLevel > 1) + sm_syslog(LOG_NOTICE, CurEnv->e_id, + "timeout during PH lookup of %.100s", + key); + errno = ETIMEDOUT; + *pstat = EX_TEMPFAIL; + goto ph_map_lookup_abort; + } + ev = sm_setevent(pmap->ph_timeout, ph_timeout, 0); + } + + /* perform lookup */ + i = ph_email_resolve(pmap->ph, key, pmap->ph_field_list, &value); + if (i == -1) + *pstat = EX_TEMPFAIL; + else if (i == PH_ERR_NOMATCH || i == PH_ERR_DATAERR) + *pstat = EX_UNAVAILABLE; + + ph_map_lookup_abort: + if (ev != NULL) + sm_clrevent(ev); + + /* + ** Close the connection if the timer popped + ** or we got a temporary PH error + */ + + if (*pstat == EX_TEMPFAIL) + { + save_errno = errno; + pmap->ph_fastclose = PH_CLOSE_FAST; + ph_map_close(map); + errno = save_errno; + } + + if (*pstat == EX_OK) + { + if (tTd(38,20)) + sm_dprintf("ph_map_lookup: %s => %s\n", key, value); + + if (bitset(MF_MATCHONLY, map->map_mflags)) + return map_rewrite(map, key, strlen(key), NULL); + else + return map_rewrite(map, value, strlen(value), args); + } + + return NULL; +} +#endif /* PH_MAP */ + +/* +** syslog map +*/ + +#define map_prio map_lockfd /* overload field */ + +/* +** SYSLOG_MAP_PARSEARGS -- check for priority level to syslog messages. +*/ + +bool +syslog_map_parseargs(map, args) + MAP *map; + char *args; +{ + char *p = args; + char *priority = NULL; + + /* there is no check whether there is really an argument */ + while (*p != '\0') + { + while (isascii(*p) && isspace(*p)) + p++; + if (*p != '-') + break; + ++p; + if (*p == 'D') + { + map->map_mflags |= MF_DEFER; + ++p; + } + else if (*p == 'S') + { + map->map_spacesub = *++p; + if (*p != '\0') + p++; + } + else if (*p == 'L') + { + while (*++p != '\0' && isascii(*p) && isspace(*p)) + continue; + if (*p == '\0') + break; + priority = p; + while (*p != '\0' && !(isascii(*p) && isspace(*p))) + p++; + if (*p != '\0') + *p++ = '\0'; + } + else + { + syserr("Illegal option %c map syslog", *p); + ++p; + } + } + + if (priority == NULL) + map->map_prio = LOG_INFO; + else + { + if (sm_strncasecmp("LOG_", priority, 4) == 0) + priority += 4; + +#ifdef LOG_EMERG + if (sm_strcasecmp("EMERG", priority) == 0) + map->map_prio = LOG_EMERG; + else +#endif /* LOG_EMERG */ +#ifdef LOG_ALERT + if (sm_strcasecmp("ALERT", priority) == 0) + map->map_prio = LOG_ALERT; + else +#endif /* LOG_ALERT */ +#ifdef LOG_CRIT + if (sm_strcasecmp("CRIT", priority) == 0) + map->map_prio = LOG_CRIT; + else +#endif /* LOG_CRIT */ +#ifdef LOG_ERR + if (sm_strcasecmp("ERR", priority) == 0) + map->map_prio = LOG_ERR; + else +#endif /* LOG_ERR */ +#ifdef LOG_WARNING + if (sm_strcasecmp("WARNING", priority) == 0) + map->map_prio = LOG_WARNING; + else +#endif /* LOG_WARNING */ +#ifdef LOG_NOTICE + if (sm_strcasecmp("NOTICE", priority) == 0) + map->map_prio = LOG_NOTICE; + else +#endif /* LOG_NOTICE */ +#ifdef LOG_INFO + if (sm_strcasecmp("INFO", priority) == 0) + map->map_prio = LOG_INFO; + else +#endif /* LOG_INFO */ +#ifdef LOG_DEBUG + if (sm_strcasecmp("DEBUG", priority) == 0) + map->map_prio = LOG_DEBUG; + else +#endif /* LOG_DEBUG */ + { + syserr("syslog_map_parseargs: Unknown priority %s", + priority); + return false; + } + } + return true; +} + +/* +** SYSLOG_MAP_LOOKUP -- rewrite and syslog message. Always return empty string +*/ + +char * +syslog_map_lookup(map, string, args, statp) + MAP *map; + char *string; + char **args; + int *statp; +{ + char *ptr = map_rewrite(map, string, strlen(string), args); + + if (ptr != NULL) + { + if (tTd(38, 20)) + sm_dprintf("syslog_map_lookup(%s (priority %d): %s\n", + map->map_mname, map->map_prio, ptr); + + sm_syslog(map->map_prio, CurEnv->e_id, "%s", ptr); + } + + *statp = EX_OK; + return ""; +} + +#if _FFR_DPRINTF_MAP +/* +** dprintf map +*/ + +#define map_dbg_level map_lockfd /* overload field */ + +/* +** DPRINTF_MAP_PARSEARGS -- check for priority level to dprintf messages. +*/ + +bool +dprintf_map_parseargs(map, args) + MAP *map; + char *args; +{ + char *p = args; + char *dbg_level = NULL; + + /* there is no check whether there is really an argument */ + while (*p != '\0') + { + while (isascii(*p) && isspace(*p)) + p++; + if (*p != '-') + break; + ++p; + if (*p == 'D') + { + map->map_mflags |= MF_DEFER; + ++p; + } + else if (*p == 'S') + { + map->map_spacesub = *++p; + if (*p != '\0') + p++; + } + else if (*p == 'd') + { + while (*++p != '\0' && isascii(*p) && isspace(*p)) + continue; + if (*p == '\0') + break; + dbg_level = p; + while (*p != '\0' && !(isascii(*p) && isspace(*p))) + p++; + if (*p != '\0') + *p++ = '\0'; + } + else + { + syserr("Illegal option %c map dprintf", *p); + ++p; + } + } + + if (dbg_level == NULL) + map->map_dbg_level = 0; + else + { + if (!(isascii(*dbg_level) && isdigit(*dbg_level))) + { + syserr("dprintf map \"%s\", file %s: -d should specify a number, not %s", + map->map_mname, map->map_file, + dbg_level); + return false; + } + map->map_dbg_level = atoi(dbg_level); + } + return true; +} + +/* +** DPRINTF_MAP_LOOKUP -- rewrite and print message. Always return empty string +*/ + +char * +dprintf_map_lookup(map, string, args, statp) + MAP *map; + char *string; + char **args; + int *statp; +{ + char *ptr = map_rewrite(map, string, strlen(string), args); + + if (ptr != NULL && tTd(85, map->map_dbg_level)) + sm_dprintf("%s\n", ptr); + *statp = EX_OK; + return ""; +} +#endif /* _FFR_DPRINTF_MAP */ + +/* +** HESIOD Modules +*/ + +#if HESIOD + +bool +hes_map_open(map, mode) + MAP *map; + int mode; +{ + if (tTd(38, 2)) + sm_dprintf("hes_map_open(%s, %s, %d)\n", + map->map_mname, map->map_file, mode); + + if (mode != O_RDONLY) + { + /* issue a pseudo-error message */ + errno = SM_EMAPCANTWRITE; + return false; + } + +# ifdef HESIOD_INIT + if (HesiodContext != NULL || hesiod_init(&HesiodContext) == 0) + return true; + + if (!bitset(MF_OPTIONAL, map->map_mflags)) + syserr("451 4.3.5 cannot initialize Hesiod map (%s)", + sm_errstring(errno)); + return false; +# else /* HESIOD_INIT */ + if (hes_error() == HES_ER_UNINIT) + hes_init(); + switch (hes_error()) + { + case HES_ER_OK: + case HES_ER_NOTFOUND: + return true; + } + + if (!bitset(MF_OPTIONAL, map->map_mflags)) + syserr("451 4.3.5 cannot initialize Hesiod map (%d)", hes_error()); + + return false; +# endif /* HESIOD_INIT */ +} + +char * +hes_map_lookup(map, name, av, statp) + MAP *map; + char *name; + char **av; + int *statp; +{ + char **hp; + + if (tTd(38, 20)) + sm_dprintf("hes_map_lookup(%s, %s)\n", map->map_file, name); + + if (name[0] == '\\') + { + char *np; + int nl; + int save_errno; + char nbuf[MAXNAME]; + + nl = strlen(name); + if (nl < sizeof(nbuf) - 1) + np = nbuf; + else + np = xalloc(strlen(name) + 2); + np[0] = '\\'; + (void) sm_strlcpy(&np[1], name, (sizeof(nbuf)) - 1); +# ifdef HESIOD_INIT + hp = hesiod_resolve(HesiodContext, np, map->map_file); +# else /* HESIOD_INIT */ + hp = hes_resolve(np, map->map_file); +# endif /* HESIOD_INIT */ + save_errno = errno; + if (np != nbuf) + sm_free(np); /* XXX */ + errno = save_errno; + } + else + { +# ifdef HESIOD_INIT + hp = hesiod_resolve(HesiodContext, name, map->map_file); +# else /* HESIOD_INIT */ + hp = hes_resolve(name, map->map_file); +# endif /* HESIOD_INIT */ + } +# ifdef HESIOD_INIT + if (hp == NULL || *hp == NULL) + { + switch (errno) + { + case ENOENT: + *statp = EX_NOTFOUND; + break; + case ECONNREFUSED: + *statp = EX_TEMPFAIL; + break; + case EMSGSIZE: + case ENOMEM: + default: + *statp = EX_UNAVAILABLE; + break; + } + if (hp != NULL) + hesiod_free_list(HesiodContext, hp); + return NULL; + } +# else /* HESIOD_INIT */ + if (hp == NULL || hp[0] == NULL) + { + switch (hes_error()) + { + case HES_ER_OK: + *statp = EX_OK; + break; + + case HES_ER_NOTFOUND: + *statp = EX_NOTFOUND; + break; + + case HES_ER_CONFIG: + *statp = EX_UNAVAILABLE; + break; + + case HES_ER_NET: + *statp = EX_TEMPFAIL; + break; + } + return NULL; + } +# endif /* HESIOD_INIT */ + + if (bitset(MF_MATCHONLY, map->map_mflags)) + return map_rewrite(map, name, strlen(name), NULL); + else + return map_rewrite(map, hp[0], strlen(hp[0]), av); +} + +/* +** HES_MAP_CLOSE -- free the Hesiod context +*/ + +void +hes_map_close(map) + MAP *map; +{ + if (tTd(38, 20)) + sm_dprintf("hes_map_close(%s)\n", map->map_file); + +# ifdef HESIOD_INIT + /* Free the hesiod context */ + if (HesiodContext != NULL) + { + hesiod_end(HesiodContext); + HesiodContext = NULL; + } +# endif /* HESIOD_INIT */ +} + +#endif /* HESIOD */ +/* +** NeXT NETINFO Modules +*/ + +#if NETINFO + +# define NETINFO_DEFAULT_DIR "/aliases" +# define NETINFO_DEFAULT_PROPERTY "members" + +/* +** NI_MAP_OPEN -- open NetInfo Aliases +*/ + +bool +ni_map_open(map, mode) + MAP *map; + int mode; +{ + if (tTd(38, 2)) + sm_dprintf("ni_map_open(%s, %s, %d)\n", + map->map_mname, map->map_file, mode); + mode &= O_ACCMODE; + + if (*map->map_file == '\0') + map->map_file = NETINFO_DEFAULT_DIR; + + if (map->map_valcolnm == NULL) + map->map_valcolnm = NETINFO_DEFAULT_PROPERTY; + + if (map->map_coldelim == '\0') + { + if (bitset(MF_ALIAS, map->map_mflags)) + map->map_coldelim = ','; + else if (bitset(MF_FILECLASS, map->map_mflags)) + map->map_coldelim = ' '; + } + return true; +} + + +/* +** NI_MAP_LOOKUP -- look up a datum in NetInfo +*/ + +char * +ni_map_lookup(map, name, av, statp) + MAP *map; + char *name; + char **av; + int *statp; +{ + char *res; + char *propval; + + if (tTd(38, 20)) + sm_dprintf("ni_map_lookup(%s, %s)\n", map->map_mname, name); + + propval = ni_propval(map->map_file, map->map_keycolnm, name, + map->map_valcolnm, map->map_coldelim); + + if (propval == NULL) + return NULL; + + SM_TRY + if (bitset(MF_MATCHONLY, map->map_mflags)) + res = map_rewrite(map, name, strlen(name), NULL); + else + res = map_rewrite(map, propval, strlen(propval), av); + SM_FINALLY + sm_free(propval); + SM_END_TRY + return res; +} + + +static bool +ni_getcanonname(name, hbsize, statp) + char *name; + int hbsize; + int *statp; +{ + char *vptr; + char *ptr; + char nbuf[MAXNAME + 1]; + + if (tTd(38, 20)) + sm_dprintf("ni_getcanonname(%s)\n", name); + + if (sm_strlcpy(nbuf, name, sizeof(nbuf)) >= sizeof(nbuf)) + { + *statp = EX_UNAVAILABLE; + return false; + } + (void) shorten_hostname(nbuf); + + /* we only accept single token search key */ + if (strchr(nbuf, '.')) + { + *statp = EX_NOHOST; + return false; + } + + /* Do the search */ + vptr = ni_propval("/machines", NULL, nbuf, "name", '\n'); + + if (vptr == NULL) + { + *statp = EX_NOHOST; + return false; + } + + /* Only want the first machine name */ + if ((ptr = strchr(vptr, '\n')) != NULL) + *ptr = '\0'; + + if (sm_strlcpy(name, vptr, hbsize) >= hbsize) + { + sm_free(vptr); + *statp = EX_UNAVAILABLE; + return true; + } + sm_free(vptr); + *statp = EX_OK; + return false; +} +#endif /* NETINFO */ +/* +** TEXT (unindexed text file) Modules +** +** This code donated by Sun Microsystems. +*/ + +#define map_sff map_lockfd /* overload field */ + + +/* +** TEXT_MAP_OPEN -- open text table +*/ + +bool +text_map_open(map, mode) + MAP *map; + int mode; +{ + long sff; + int i; + + if (tTd(38, 2)) + sm_dprintf("text_map_open(%s, %s, %d)\n", + map->map_mname, map->map_file, mode); + + mode &= O_ACCMODE; + if (mode != O_RDONLY) + { + errno = EPERM; + return false; + } + + if (*map->map_file == '\0') + { + syserr("text map \"%s\": file name required", + map->map_mname); + return false; + } + + if (map->map_file[0] != '/') + { + syserr("text map \"%s\": file name must be fully qualified", + map->map_mname); + return false; + } + + sff = SFF_ROOTOK|SFF_REGONLY; + if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail)) + sff |= SFF_NOWLINK; + if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail)) + sff |= SFF_SAFEDIRPATH; + if ((i = safefile(map->map_file, RunAsUid, RunAsGid, RunAsUserName, + sff, S_IRUSR, NULL)) != 0) + { + int save_errno = errno; + + /* cannot open this map */ + if (tTd(38, 2)) + sm_dprintf("\tunsafe map file: %d\n", i); + errno = save_errno; + if (!bitset(MF_OPTIONAL, map->map_mflags)) + syserr("text map \"%s\": unsafe map file %s", + map->map_mname, map->map_file); + return false; + } + + if (map->map_keycolnm == NULL) + map->map_keycolno = 0; + else + { + if (!(isascii(*map->map_keycolnm) && isdigit(*map->map_keycolnm))) + { + syserr("text map \"%s\", file %s: -k should specify a number, not %s", + map->map_mname, map->map_file, + map->map_keycolnm); + return false; + } + map->map_keycolno = atoi(map->map_keycolnm); + } + + if (map->map_valcolnm == NULL) + map->map_valcolno = 0; + else + { + if (!(isascii(*map->map_valcolnm) && isdigit(*map->map_valcolnm))) + { + syserr("text map \"%s\", file %s: -v should specify a number, not %s", + map->map_mname, map->map_file, + map->map_valcolnm); + return false; + } + map->map_valcolno = atoi(map->map_valcolnm); + } + + if (tTd(38, 2)) + { + sm_dprintf("text_map_open(%s, %s): delimiter = ", + map->map_mname, map->map_file); + if (map->map_coldelim == '\0') + sm_dprintf("(white space)\n"); + else + sm_dprintf("%c\n", map->map_coldelim); + } + + map->map_sff = sff; + return true; +} + + +/* +** TEXT_MAP_LOOKUP -- look up a datum in a TEXT table +*/ + +char * +text_map_lookup(map, name, av, statp) + MAP *map; + char *name; + char **av; + int *statp; +{ + char *vp; + auto int vsize; + int buflen; + SM_FILE_T *f; + char delim; + int key_idx; + bool found_it; + long sff = map->map_sff; + char search_key[MAXNAME + 1]; + char linebuf[MAXLINE]; + char buf[MAXNAME + 1]; + + found_it = false; + if (tTd(38, 20)) + sm_dprintf("text_map_lookup(%s, %s)\n", map->map_mname, name); + + buflen = strlen(name); + if (buflen > sizeof(search_key) - 1) + buflen = sizeof(search_key) - 1; /* XXX just cut if off? */ + memmove(search_key, name, buflen); + search_key[buflen] = '\0'; + if (!bitset(MF_NOFOLDCASE, map->map_mflags)) + makelower(search_key); + + f = safefopen(map->map_file, O_RDONLY, FileMode, sff); + if (f == NULL) + { + map->map_mflags &= ~(MF_VALID|MF_OPEN); + *statp = EX_UNAVAILABLE; + return NULL; + } + key_idx = map->map_keycolno; + delim = map->map_coldelim; + while (sm_io_fgets(f, SM_TIME_DEFAULT, + linebuf, sizeof(linebuf)) != NULL) + { + char *p; + + /* skip comment line */ + if (linebuf[0] == '#') + continue; + p = strchr(linebuf, '\n'); + if (p != NULL) + *p = '\0'; + p = get_column(linebuf, key_idx, delim, buf, sizeof(buf)); + if (p != NULL && sm_strcasecmp(search_key, p) == 0) + { + found_it = true; + break; + } + } + (void) sm_io_close(f, SM_TIME_DEFAULT); + if (!found_it) + { + *statp = EX_NOTFOUND; + return NULL; + } + vp = get_column(linebuf, map->map_valcolno, delim, buf, sizeof(buf)); + if (vp == NULL) + { + *statp = EX_NOTFOUND; + return NULL; + } + vsize = strlen(vp); + *statp = EX_OK; + if (bitset(MF_MATCHONLY, map->map_mflags)) + return map_rewrite(map, name, strlen(name), NULL); + else + return map_rewrite(map, vp, vsize, av); +} + +/* +** TEXT_GETCANONNAME -- look up canonical name in hosts file +*/ + +static bool +text_getcanonname(name, hbsize, statp) + char *name; + int hbsize; + int *statp; +{ + bool found; + char *dot; + SM_FILE_T *f; + char linebuf[MAXLINE]; + char cbuf[MAXNAME + 1]; + char nbuf[MAXNAME + 1]; + + if (tTd(38, 20)) + sm_dprintf("text_getcanonname(%s)\n", name); + + if (sm_strlcpy(nbuf, name, sizeof(nbuf)) >= sizeof(nbuf)) + { + *statp = EX_UNAVAILABLE; + return false; + } + dot = shorten_hostname(nbuf); + + f = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, HostsFile, SM_IO_RDONLY, + NULL); + if (f == NULL) + { + *statp = EX_UNAVAILABLE; + return false; + } + found = false; + while (!found && + sm_io_fgets(f, SM_TIME_DEFAULT, + linebuf, sizeof(linebuf)) != NULL) + { + char *p = strpbrk(linebuf, "#\n"); + + if (p != NULL) + *p = '\0'; + if (linebuf[0] != '\0') + found = extract_canonname(nbuf, dot, linebuf, + cbuf, sizeof(cbuf)); + } + (void) sm_io_close(f, SM_TIME_DEFAULT); + if (!found) + { + *statp = EX_NOHOST; + return false; + } + + if (sm_strlcpy(name, cbuf, hbsize) >= hbsize) + { + *statp = EX_UNAVAILABLE; + return false; + } + *statp = EX_OK; + return true; +} +/* +** STAB (Symbol Table) Modules +*/ + + +/* +** STAB_MAP_LOOKUP -- look up alias in symbol table +*/ + +/* ARGSUSED2 */ +char * +stab_map_lookup(map, name, av, pstat) + register MAP *map; + char *name; + char **av; + int *pstat; +{ + register STAB *s; + + if (tTd(38, 20)) + sm_dprintf("stab_lookup(%s, %s)\n", + map->map_mname, name); + + s = stab(name, ST_ALIAS, ST_FIND); + if (s == NULL) + return NULL; + if (bitset(MF_MATCHONLY, map->map_mflags)) + return map_rewrite(map, name, strlen(name), NULL); + else + return map_rewrite(map, s->s_alias, strlen(s->s_alias), av); +} + +/* +** STAB_MAP_STORE -- store in symtab (actually using during init, not rebuild) +*/ + +void +stab_map_store(map, lhs, rhs) + register MAP *map; + char *lhs; + char *rhs; +{ + register STAB *s; + + s = stab(lhs, ST_ALIAS, ST_ENTER); + s->s_alias = newstr(rhs); +} + + +/* +** STAB_MAP_OPEN -- initialize (reads data file) +** +** This is a wierd case -- it is only intended as a fallback for +** aliases. For this reason, opens for write (only during a +** "newaliases") always fails, and opens for read open the +** actual underlying text file instead of the database. +*/ + +bool +stab_map_open(map, mode) + register MAP *map; + int mode; +{ + SM_FILE_T *af; + long sff; + struct stat st; + + if (tTd(38, 2)) + sm_dprintf("stab_map_open(%s, %s, %d)\n", + map->map_mname, map->map_file, mode); + + mode &= O_ACCMODE; + if (mode != O_RDONLY) + { + errno = EPERM; + return false; + } + + sff = SFF_ROOTOK|SFF_REGONLY; + if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail)) + sff |= SFF_NOWLINK; + if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail)) + sff |= SFF_SAFEDIRPATH; + af = safefopen(map->map_file, O_RDONLY, 0444, sff); + if (af == NULL) + return false; + readaliases(map, af, false, false); + + if (fstat(sm_io_getinfo(af, SM_IO_WHAT_FD, NULL), &st) >= 0) + map->map_mtime = st.st_mtime; + (void) sm_io_close(af, SM_TIME_DEFAULT); + + return true; +} +/* +** Implicit Modules +** +** Tries several types. For back compatibility of aliases. +*/ + + +/* +** IMPL_MAP_LOOKUP -- lookup in best open database +*/ + +char * +impl_map_lookup(map, name, av, pstat) + MAP *map; + char *name; + char **av; + int *pstat; +{ + if (tTd(38, 20)) + sm_dprintf("impl_map_lookup(%s, %s)\n", + map->map_mname, name); + +#if NEWDB + if (bitset(MF_IMPL_HASH, map->map_mflags)) + return db_map_lookup(map, name, av, pstat); +#endif /* NEWDB */ +#if NDBM + if (bitset(MF_IMPL_NDBM, map->map_mflags)) + return ndbm_map_lookup(map, name, av, pstat); +#endif /* NDBM */ + return stab_map_lookup(map, name, av, pstat); +} + +/* +** IMPL_MAP_STORE -- store in open databases +*/ + +void +impl_map_store(map, lhs, rhs) + MAP *map; + char *lhs; + char *rhs; +{ + if (tTd(38, 12)) + sm_dprintf("impl_map_store(%s, %s, %s)\n", + map->map_mname, lhs, rhs); +#if NEWDB + if (bitset(MF_IMPL_HASH, map->map_mflags)) + db_map_store(map, lhs, rhs); +#endif /* NEWDB */ +#if NDBM + if (bitset(MF_IMPL_NDBM, map->map_mflags)) + ndbm_map_store(map, lhs, rhs); +#endif /* NDBM */ + stab_map_store(map, lhs, rhs); +} + +/* +** IMPL_MAP_OPEN -- implicit database open +*/ + +bool +impl_map_open(map, mode) + MAP *map; + int mode; +{ + if (tTd(38, 2)) + sm_dprintf("impl_map_open(%s, %s, %d)\n", + map->map_mname, map->map_file, mode); + + mode &= O_ACCMODE; +#if NEWDB + map->map_mflags |= MF_IMPL_HASH; + if (hash_map_open(map, mode)) + { +# ifdef NDBM_YP_COMPAT + if (mode == O_RDONLY || strstr(map->map_file, "/yp/") == NULL) +# endif /* NDBM_YP_COMPAT */ + return true; + } + else + map->map_mflags &= ~MF_IMPL_HASH; +#endif /* NEWDB */ +#if NDBM + map->map_mflags |= MF_IMPL_NDBM; + if (ndbm_map_open(map, mode)) + { + return true; + } + else + map->map_mflags &= ~MF_IMPL_NDBM; +#endif /* NDBM */ + +#if defined(NEWDB) || defined(NDBM) + if (Verbose) + message("WARNING: cannot open alias database %s%s", + map->map_file, + mode == O_RDONLY ? "; reading text version" : ""); +#else /* defined(NEWDB) || defined(NDBM) */ + if (mode != O_RDONLY) + usrerr("Cannot rebuild aliases: no database format defined"); +#endif /* defined(NEWDB) || defined(NDBM) */ + + if (mode == O_RDONLY) + return stab_map_open(map, mode); + else + return false; +} + + +/* +** IMPL_MAP_CLOSE -- close any open database(s) +*/ + +void +impl_map_close(map) + MAP *map; +{ + if (tTd(38, 9)) + sm_dprintf("impl_map_close(%s, %s, %lx)\n", + map->map_mname, map->map_file, map->map_mflags); +#if NEWDB + if (bitset(MF_IMPL_HASH, map->map_mflags)) + { + db_map_close(map); + map->map_mflags &= ~MF_IMPL_HASH; + } +#endif /* NEWDB */ + +#if NDBM + if (bitset(MF_IMPL_NDBM, map->map_mflags)) + { + ndbm_map_close(map); + map->map_mflags &= ~MF_IMPL_NDBM; + } +#endif /* NDBM */ +} +/* +** User map class. +** +** Provides access to the system password file. +*/ + +/* +** USER_MAP_OPEN -- open user map +** +** Really just binds field names to field numbers. +*/ + +bool +user_map_open(map, mode) + MAP *map; + int mode; +{ + if (tTd(38, 2)) + sm_dprintf("user_map_open(%s, %d)\n", + map->map_mname, mode); + + mode &= O_ACCMODE; + if (mode != O_RDONLY) + { + /* issue a pseudo-error message */ + errno = SM_EMAPCANTWRITE; + return false; + } + if (map->map_valcolnm == NULL) + /* EMPTY */ + /* nothing */ ; + else if (sm_strcasecmp(map->map_valcolnm, "name") == 0) + map->map_valcolno = 1; + else if (sm_strcasecmp(map->map_valcolnm, "passwd") == 0) + map->map_valcolno = 2; + else if (sm_strcasecmp(map->map_valcolnm, "uid") == 0) + map->map_valcolno = 3; + else if (sm_strcasecmp(map->map_valcolnm, "gid") == 0) + map->map_valcolno = 4; + else if (sm_strcasecmp(map->map_valcolnm, "gecos") == 0) + map->map_valcolno = 5; + else if (sm_strcasecmp(map->map_valcolnm, "dir") == 0) + map->map_valcolno = 6; + else if (sm_strcasecmp(map->map_valcolnm, "shell") == 0) + map->map_valcolno = 7; + else + { + syserr("User map %s: unknown column name %s", + map->map_mname, map->map_valcolnm); + return false; + } + return true; +} + + +/* +** USER_MAP_LOOKUP -- look up a user in the passwd file. +*/ + +/* ARGSUSED3 */ +char * +user_map_lookup(map, key, av, statp) + MAP *map; + char *key; + char **av; + int *statp; +{ + auto bool fuzzy; + SM_MBDB_T user; + + if (tTd(38, 20)) + sm_dprintf("user_map_lookup(%s, %s)\n", + map->map_mname, key); + + *statp = finduser(key, &fuzzy, &user); + if (*statp != EX_OK) + return NULL; + if (bitset(MF_MATCHONLY, map->map_mflags)) + return map_rewrite(map, key, strlen(key), NULL); + else + { + char *rwval = NULL; + char buf[30]; + + switch (map->map_valcolno) + { + case 0: + case 1: + rwval = user.mbdb_name; + break; + + case 2: + rwval = "x"; /* passwd no longer supported */ + break; + + case 3: + (void) sm_snprintf(buf, sizeof(buf), "%d", + (int) user.mbdb_uid); + rwval = buf; + break; + + case 4: + (void) sm_snprintf(buf, sizeof(buf), "%d", + (int) user.mbdb_gid); + rwval = buf; + break; + + case 5: + rwval = user.mbdb_fullname; + break; + + case 6: + rwval = user.mbdb_homedir; + break; + + case 7: + rwval = user.mbdb_shell; + break; + default: + syserr("user_map %s: bogus field %d", + map->map_mname, map->map_valcolno); + return NULL; + } + return map_rewrite(map, rwval, strlen(rwval), av); + } +} +/* +** Program map type. +** +** This provides access to arbitrary programs. It should be used +** only very sparingly, since there is no way to bound the cost +** of invoking an arbitrary program. +*/ + +char * +prog_map_lookup(map, name, av, statp) + MAP *map; + char *name; + char **av; + int *statp; +{ + int i; + int save_errno; + int fd; + int status; + auto pid_t pid; + register char *p; + char *rval; + char *argv[MAXPV + 1]; + char buf[MAXLINE]; + + if (tTd(38, 20)) + sm_dprintf("prog_map_lookup(%s, %s) %s\n", + map->map_mname, name, map->map_file); + + i = 0; + argv[i++] = map->map_file; + if (map->map_rebuild != NULL) + { + (void) sm_strlcpy(buf, map->map_rebuild, sizeof(buf)); + for (p = strtok(buf, " \t"); p != NULL; p = strtok(NULL, " \t")) + { + if (i >= MAXPV - 1) + break; + argv[i++] = p; + } + } + argv[i++] = name; + argv[i] = NULL; + if (tTd(38, 21)) + { + sm_dprintf("prog_open:"); + for (i = 0; argv[i] != NULL; i++) + sm_dprintf(" %s", argv[i]); + sm_dprintf("\n"); + } + (void) sm_blocksignal(SIGCHLD); + pid = prog_open(argv, &fd, CurEnv); + if (pid < 0) + { + if (!bitset(MF_OPTIONAL, map->map_mflags)) + syserr("prog_map_lookup(%s) failed (%s) -- closing", + map->map_mname, sm_errstring(errno)); + else if (tTd(38, 9)) + sm_dprintf("prog_map_lookup(%s) failed (%s) -- closing", + map->map_mname, sm_errstring(errno)); + map->map_mflags &= ~(MF_VALID|MF_OPEN); + *statp = EX_OSFILE; + return NULL; + } + i = read(fd, buf, sizeof(buf) - 1); + if (i < 0) + { + syserr("prog_map_lookup(%s): read error %s", + map->map_mname, sm_errstring(errno)); + rval = NULL; + } + else if (i == 0) + { + if (tTd(38, 20)) + sm_dprintf("prog_map_lookup(%s): empty answer\n", + map->map_mname); + rval = NULL; + } + else + { + buf[i] = '\0'; + p = strchr(buf, '\n'); + if (p != NULL) + *p = '\0'; + + /* collect the return value */ + if (bitset(MF_MATCHONLY, map->map_mflags)) + rval = map_rewrite(map, name, strlen(name), NULL); + else + rval = map_rewrite(map, buf, strlen(buf), av); + + /* now flush any additional output */ + while ((i = read(fd, buf, sizeof(buf))) > 0) + continue; + } + + /* wait for the process to terminate */ + (void) close(fd); + status = waitfor(pid); + save_errno = errno; + (void) sm_releasesignal(SIGCHLD); + errno = save_errno; + + if (status == -1) + { + syserr("prog_map_lookup(%s): wait error %s", + map->map_mname, sm_errstring(errno)); + *statp = EX_SOFTWARE; + rval = NULL; + } + else if (WIFEXITED(status)) + { + if ((*statp = WEXITSTATUS(status)) != EX_OK) + rval = NULL; + } + else + { + syserr("prog_map_lookup(%s): child died on signal %d", + map->map_mname, status); + *statp = EX_UNAVAILABLE; + rval = NULL; + } + return rval; +} +/* +** Sequenced map type. +** +** Tries each map in order until something matches, much like +** implicit. Stores go to the first map in the list that can +** support storing. +** +** This is slightly unusual in that there are two interfaces. +** The "sequence" interface lets you stack maps arbitrarily. +** The "switch" interface builds a sequence map by looking +** at a system-dependent configuration file such as +** /etc/nsswitch.conf on Solaris or /etc/svc.conf on Ultrix. +** +** We don't need an explicit open, since all maps are +** opened on demand. +*/ + +/* +** SEQ_MAP_PARSE -- Sequenced map parsing +*/ + +bool +seq_map_parse(map, ap) + MAP *map; + char *ap; +{ + int maxmap; + + if (tTd(38, 2)) + sm_dprintf("seq_map_parse(%s, %s)\n", map->map_mname, ap); + maxmap = 0; + while (*ap != '\0') + { + register char *p; + STAB *s; + + /* find beginning of map name */ + while (isascii(*ap) && isspace(*ap)) + ap++; + for (p = ap; + (isascii(*p) && isalnum(*p)) || *p == '_' || *p == '.'; + p++) + continue; + if (*p != '\0') + *p++ = '\0'; + while (*p != '\0' && (!isascii(*p) || !isalnum(*p))) + p++; + if (*ap == '\0') + { + ap = p; + continue; + } + s = stab(ap, ST_MAP, ST_FIND); + if (s == NULL) + { + syserr("Sequence map %s: unknown member map %s", + map->map_mname, ap); + } + else if (maxmap >= MAXMAPSTACK) + { + syserr("Sequence map %s: too many member maps (%d max)", + map->map_mname, MAXMAPSTACK); + maxmap++; + } + else if (maxmap < MAXMAPSTACK) + { + map->map_stack[maxmap++] = &s->s_map; + } + ap = p; + } + return true; +} + +/* +** SWITCH_MAP_OPEN -- open a switched map +** +** This looks at the system-dependent configuration and builds +** a sequence map that does the same thing. +** +** Every system must define a switch_map_find routine in conf.c +** that will return the list of service types associated with a +** given service class. +*/ + +bool +switch_map_open(map, mode) + MAP *map; + int mode; +{ + int mapno; + int nmaps; + char *maptype[MAXMAPSTACK]; + + if (tTd(38, 2)) + sm_dprintf("switch_map_open(%s, %s, %d)\n", + map->map_mname, map->map_file, mode); + + mode &= O_ACCMODE; + nmaps = switch_map_find(map->map_file, maptype, map->map_return); + if (tTd(38, 19)) + { + sm_dprintf("\tswitch_map_find => %d\n", nmaps); + for (mapno = 0; mapno < nmaps; mapno++) + sm_dprintf("\t\t%s\n", maptype[mapno]); + } + if (nmaps <= 0 || nmaps > MAXMAPSTACK) + return false; + + for (mapno = 0; mapno < nmaps; mapno++) + { + register STAB *s; + char nbuf[MAXNAME + 1]; + + if (maptype[mapno] == NULL) + continue; + (void) sm_strlcpyn(nbuf, sizeof(nbuf), 3, + map->map_mname, ".", maptype[mapno]); + s = stab(nbuf, ST_MAP, ST_FIND); + if (s == NULL) + { + syserr("Switch map %s: unknown member map %s", + map->map_mname, nbuf); + } + else + { + map->map_stack[mapno] = &s->s_map; + if (tTd(38, 4)) + sm_dprintf("\tmap_stack[%d] = %s:%s\n", + mapno, + s->s_map.map_class->map_cname, + nbuf); + } + } + return true; +} + +#if 0 +/* +** SEQ_MAP_CLOSE -- close all underlying maps +*/ + +void +seq_map_close(map) + MAP *map; +{ + int mapno; + + if (tTd(38, 9)) + sm_dprintf("seq_map_close(%s)\n", map->map_mname); + + for (mapno = 0; mapno < MAXMAPSTACK; mapno++) + { + MAP *mm = map->map_stack[mapno]; + + if (mm == NULL || !bitset(MF_OPEN, mm->map_mflags)) + continue; + mm->map_mflags |= MF_CLOSING; + mm->map_class->map_close(mm); + mm->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING); + } +} +#endif /* 0 */ + +/* +** SEQ_MAP_LOOKUP -- sequenced map lookup +*/ + +char * +seq_map_lookup(map, key, args, pstat) + MAP *map; + char *key; + char **args; + int *pstat; +{ + int mapno; + int mapbit = 0x01; + bool tempfail = false; + + if (tTd(38, 20)) + sm_dprintf("seq_map_lookup(%s, %s)\n", map->map_mname, key); + + for (mapno = 0; mapno < MAXMAPSTACK; mapbit <<= 1, mapno++) + { + MAP *mm = map->map_stack[mapno]; + char *rv; + + if (mm == NULL) + continue; + if (!bitset(MF_OPEN, mm->map_mflags) && + !openmap(mm)) + { + if (bitset(mapbit, map->map_return[MA_UNAVAIL])) + { + *pstat = EX_UNAVAILABLE; + return NULL; + } + continue; + } + *pstat = EX_OK; + rv = mm->map_class->map_lookup(mm, key, args, pstat); + if (rv != NULL) + return rv; + if (*pstat == EX_TEMPFAIL) + { + if (bitset(mapbit, map->map_return[MA_TRYAGAIN])) + return NULL; + tempfail = true; + } + else if (bitset(mapbit, map->map_return[MA_NOTFOUND])) + break; + } + if (tempfail) + *pstat = EX_TEMPFAIL; + else if (*pstat == EX_OK) + *pstat = EX_NOTFOUND; + return NULL; +} + +/* +** SEQ_MAP_STORE -- sequenced map store +*/ + +void +seq_map_store(map, key, val) + MAP *map; + char *key; + char *val; +{ + int mapno; + + if (tTd(38, 12)) + sm_dprintf("seq_map_store(%s, %s, %s)\n", + map->map_mname, key, val); + + for (mapno = 0; mapno < MAXMAPSTACK; mapno++) + { + MAP *mm = map->map_stack[mapno]; + + if (mm == NULL || !bitset(MF_WRITABLE, mm->map_mflags)) + continue; + + mm->map_class->map_store(mm, key, val); + return; + } + syserr("seq_map_store(%s, %s, %s): no writable map", + map->map_mname, key, val); +} +/* +** NULL stubs +*/ + +/* ARGSUSED */ +bool +null_map_open(map, mode) + MAP *map; + int mode; +{ + return true; +} + +/* ARGSUSED */ +void +null_map_close(map) + MAP *map; +{ + return; +} + +char * +null_map_lookup(map, key, args, pstat) + MAP *map; + char *key; + char **args; + int *pstat; +{ + *pstat = EX_NOTFOUND; + return NULL; +} + +/* ARGSUSED */ +void +null_map_store(map, key, val) + MAP *map; + char *key; + char *val; +{ + return; +} + +MAPCLASS NullMapClass = +{ + "null-map", NULL, 0, + NULL, null_map_lookup, null_map_store, + null_map_open, null_map_close, +}; + +/* +** BOGUS stubs +*/ + +char * +bogus_map_lookup(map, key, args, pstat) + MAP *map; + char *key; + char **args; + int *pstat; +{ + *pstat = EX_TEMPFAIL; + return NULL; +} + +MAPCLASS BogusMapClass = +{ + "bogus-map", NULL, 0, + NULL, bogus_map_lookup, null_map_store, + null_map_open, null_map_close, +}; +/* +** MACRO modules +*/ + +char * +macro_map_lookup(map, name, av, statp) + MAP *map; + char *name; + char **av; + int *statp; +{ + int mid; + + if (tTd(38, 20)) + sm_dprintf("macro_map_lookup(%s, %s)\n", map->map_mname, + name == NULL ? "NULL" : name); + + if (name == NULL || + *name == '\0' || + (mid = macid(name)) == 0) + { + *statp = EX_CONFIG; + return NULL; + } + + if (av[1] == NULL) + macdefine(&CurEnv->e_macro, A_PERM, mid, NULL); + else + macdefine(&CurEnv->e_macro, A_TEMP, mid, av[1]); + + *statp = EX_OK; + return ""; +} +/* +** REGEX modules +*/ + +#if MAP_REGEX + +# include <regex.h> + +# define DEFAULT_DELIM CONDELSE +# define END_OF_FIELDS -1 +# define ERRBUF_SIZE 80 +# define MAX_MATCH 32 + +# define xnalloc(s) memset(xalloc(s), '\0', s); + +struct regex_map +{ + regex_t *regex_pattern_buf; /* xalloc it */ + int *regex_subfields; /* move to type MAP */ + char *regex_delim; /* move to type MAP */ +}; + +static int parse_fields __P((char *, int *, int, int)); +static char *regex_map_rewrite __P((MAP *, const char*, size_t, char **)); + +static int +parse_fields(s, ibuf, blen, nr_substrings) + char *s; + int *ibuf; /* array */ + int blen; /* number of elements in ibuf */ + int nr_substrings; /* number of substrings in the pattern */ +{ + register char *cp; + int i = 0; + bool lastone = false; + + blen--; /* for terminating END_OF_FIELDS */ + cp = s; + do + { + for (;; cp++) + { + if (*cp == ',') + { + *cp = '\0'; + break; + } + if (*cp == '\0') + { + lastone = true; + break; + } + } + if (i < blen) + { + int val = atoi(s); + + if (val < 0 || val >= nr_substrings) + { + syserr("field (%d) out of range, only %d substrings in pattern", + val, nr_substrings); + return -1; + } + ibuf[i++] = val; + } + else + { + syserr("too many fields, %d max", blen); + return -1; + } + s = ++cp; + } while (!lastone); + ibuf[i] = END_OF_FIELDS; + return i; +} + +bool +regex_map_init(map, ap) + MAP *map; + char *ap; +{ + int regerr; + struct regex_map *map_p; + register char *p; + char *sub_param = NULL; + int pflags; + static char defdstr[] = { (char) DEFAULT_DELIM, '\0' }; + + if (tTd(38, 2)) + sm_dprintf("regex_map_init: mapname '%s', args '%s'\n", + map->map_mname, ap); + + pflags = REG_ICASE | REG_EXTENDED | REG_NOSUB; + p = ap; + map_p = (struct regex_map *) xnalloc(sizeof(*map_p)); + map_p->regex_pattern_buf = (regex_t *)xnalloc(sizeof(regex_t)); + + for (;;) + { + while (isascii(*p) && isspace(*p)) + p++; + if (*p != '-') + break; + switch (*++p) + { + case 'n': /* not */ + map->map_mflags |= MF_REGEX_NOT; + break; + + case 'f': /* case sensitive */ + map->map_mflags |= MF_NOFOLDCASE; + pflags &= ~REG_ICASE; + break; + + case 'b': /* basic regular expressions */ + pflags &= ~REG_EXTENDED; + break; + + case 's': /* substring match () syntax */ + sub_param = ++p; + pflags &= ~REG_NOSUB; + break; + + case 'd': /* delimiter */ + map_p->regex_delim = ++p; + break; + + case 'a': /* map append */ + map->map_app = ++p; + break; + + case 'm': /* matchonly */ + map->map_mflags |= MF_MATCHONLY; + break; + + case 'q': + map->map_mflags |= MF_KEEPQUOTES; + break; + + case 'S': + map->map_spacesub = *++p; + break; + + case 'D': + map->map_mflags |= MF_DEFER; + break; + + } + while (*p != '\0' && !(isascii(*p) && isspace(*p))) + p++; + if (*p != '\0') + *p++ = '\0'; + } + if (tTd(38, 3)) + sm_dprintf("regex_map_init: compile '%s' 0x%x\n", p, pflags); + + if ((regerr = regcomp(map_p->regex_pattern_buf, p, pflags)) != 0) + { + /* Errorhandling */ + char errbuf[ERRBUF_SIZE]; + + (void) regerror(regerr, map_p->regex_pattern_buf, + errbuf, sizeof(errbuf)); + syserr("pattern-compile-error: %s", errbuf); + sm_free(map_p->regex_pattern_buf); /* XXX */ + sm_free(map_p); /* XXX */ + return false; + } + + if (map->map_app != NULL) + map->map_app = newstr(map->map_app); + if (map_p->regex_delim != NULL) + map_p->regex_delim = newstr(map_p->regex_delim); + else + map_p->regex_delim = defdstr; + + if (!bitset(REG_NOSUB, pflags)) + { + /* substring matching */ + int substrings; + int *fields = (int *) xalloc(sizeof(int) * (MAX_MATCH + 1)); + + substrings = map_p->regex_pattern_buf->re_nsub + 1; + + if (tTd(38, 3)) + sm_dprintf("regex_map_init: nr of substrings %d\n", + substrings); + + if (substrings >= MAX_MATCH) + { + syserr("too many substrings, %d max", MAX_MATCH); + sm_free(map_p->regex_pattern_buf); /* XXX */ + sm_free(map_p); /* XXX */ + return false; + } + if (sub_param != NULL && sub_param[0] != '\0') + { + /* optional parameter -sfields */ + if (parse_fields(sub_param, fields, + MAX_MATCH + 1, substrings) == -1) + return false; + } + else + { + int i; + + /* set default fields */ + for (i = 0; i < substrings; i++) + fields[i] = i; + fields[i] = END_OF_FIELDS; + } + map_p->regex_subfields = fields; + if (tTd(38, 3)) + { + int *ip; + + sm_dprintf("regex_map_init: subfields"); + for (ip = fields; *ip != END_OF_FIELDS; ip++) + sm_dprintf(" %d", *ip); + sm_dprintf("\n"); + } + } + map->map_db1 = (ARBPTR_T) map_p; /* dirty hack */ + return true; +} + +static char * +regex_map_rewrite(map, s, slen, av) + MAP *map; + const char *s; + size_t slen; + char **av; +{ + if (bitset(MF_MATCHONLY, map->map_mflags)) + return map_rewrite(map, av[0], strlen(av[0]), NULL); + else + return map_rewrite(map, s, slen, av); +} + +char * +regex_map_lookup(map, name, av, statp) + MAP *map; + char *name; + char **av; + int *statp; +{ + int reg_res; + struct regex_map *map_p; + regmatch_t pmatch[MAX_MATCH]; + + if (tTd(38, 20)) + { + char **cpp; + + sm_dprintf("regex_map_lookup: key '%s'\n", name); + for (cpp = av; cpp != NULL && *cpp != NULL; cpp++) + sm_dprintf("regex_map_lookup: arg '%s'\n", *cpp); + } + + map_p = (struct regex_map *)(map->map_db1); + reg_res = regexec(map_p->regex_pattern_buf, + name, MAX_MATCH, pmatch, 0); + + if (bitset(MF_REGEX_NOT, map->map_mflags)) + { + /* option -n */ + if (reg_res == REG_NOMATCH) + return regex_map_rewrite(map, "", (size_t) 0, av); + else + return NULL; + } + if (reg_res == REG_NOMATCH) + return NULL; + + if (map_p->regex_subfields != NULL) + { + /* option -s */ + static char retbuf[MAXNAME]; + int fields[MAX_MATCH + 1]; + bool first = true; + int anglecnt = 0, cmntcnt = 0, spacecnt = 0; + bool quotemode = false, bslashmode = false; + register char *dp, *sp; + char *endp, *ldp; + int *ip; + + dp = retbuf; + ldp = retbuf + sizeof(retbuf) - 1; + + if (av[1] != NULL) + { + if (parse_fields(av[1], fields, MAX_MATCH + 1, + (int) map_p->regex_pattern_buf->re_nsub + 1) == -1) + { + *statp = EX_CONFIG; + return NULL; + } + ip = fields; + } + else + ip = map_p->regex_subfields; + + for ( ; *ip != END_OF_FIELDS; ip++) + { + if (!first) + { + for (sp = map_p->regex_delim; *sp; sp++) + { + if (dp < ldp) + *dp++ = *sp; + } + } + else + first = false; + + if (*ip >= MAX_MATCH || + pmatch[*ip].rm_so < 0 || pmatch[*ip].rm_eo < 0) + continue; + + sp = name + pmatch[*ip].rm_so; + endp = name + pmatch[*ip].rm_eo; + for (; endp > sp; sp++) + { + if (dp < ldp) + { + if (bslashmode) + { + *dp++ = *sp; + bslashmode = false; + } + else if (quotemode && *sp != '"' && + *sp != '\\') + { + *dp++ = *sp; + } + else switch (*dp++ = *sp) + { + case '\\': + bslashmode = true; + break; + + case '(': + cmntcnt++; + break; + + case ')': + cmntcnt--; + break; + + case '<': + anglecnt++; + break; + + case '>': + anglecnt--; + break; + + case ' ': + spacecnt++; + break; + + case '"': + quotemode = !quotemode; + break; + } + } + } + } + if (anglecnt != 0 || cmntcnt != 0 || quotemode || + bslashmode || spacecnt != 0) + { + sm_syslog(LOG_WARNING, NOQID, + "Warning: regex may cause prescan() failure map=%s lookup=%s", + map->map_mname, name); + return NULL; + } + + *dp = '\0'; + + return regex_map_rewrite(map, retbuf, strlen(retbuf), av); + } + return regex_map_rewrite(map, "", (size_t)0, av); +} +#endif /* MAP_REGEX */ +/* +** NSD modules +*/ +#if MAP_NSD + +# include <ndbm.h> +# define _DATUM_DEFINED +# include <ns_api.h> + +typedef struct ns_map_list +{ + ns_map_t *map; /* XXX ns_ ? */ + char *mapname; + struct ns_map_list *next; +} ns_map_list_t; + +static ns_map_t * +ns_map_t_find(mapname) + char *mapname; +{ + static ns_map_list_t *ns_maps = NULL; + ns_map_list_t *ns_map; + + /* walk the list of maps looking for the correctly named map */ + for (ns_map = ns_maps; ns_map != NULL; ns_map = ns_map->next) + { + if (strcmp(ns_map->mapname, mapname) == 0) + break; + } + + /* if we are looking at a NULL ns_map_list_t, then create a new one */ + if (ns_map == NULL) + { + ns_map = (ns_map_list_t *) xalloc(sizeof(*ns_map)); + ns_map->mapname = newstr(mapname); + ns_map->map = (ns_map_t *) xalloc(sizeof(*ns_map->map)); + memset(ns_map->map, '\0', sizeof(*ns_map->map)); + ns_map->next = ns_maps; + ns_maps = ns_map; + } + return ns_map->map; +} + +char * +nsd_map_lookup(map, name, av, statp) + MAP *map; + char *name; + char **av; + int *statp; +{ + int buflen, r; + char *p; + ns_map_t *ns_map; + char keybuf[MAXNAME + 1]; + char buf[MAXLINE]; + + if (tTd(38, 20)) + sm_dprintf("nsd_map_lookup(%s, %s)\n", map->map_mname, name); + + buflen = strlen(name); + if (buflen > sizeof(keybuf) - 1) + buflen = sizeof(keybuf) - 1; /* XXX simply cut off? */ + memmove(keybuf, name, buflen); + keybuf[buflen] = '\0'; + if (!bitset(MF_NOFOLDCASE, map->map_mflags)) + makelower(keybuf); + + ns_map = ns_map_t_find(map->map_file); + if (ns_map == NULL) + { + if (tTd(38, 20)) + sm_dprintf("nsd_map_t_find failed\n"); + *statp = EX_UNAVAILABLE; + return NULL; + } + r = ns_lookup(ns_map, NULL, map->map_file, keybuf, NULL, + buf, sizeof(buf)); + if (r == NS_UNAVAIL || r == NS_TRYAGAIN) + { + *statp = EX_TEMPFAIL; + return NULL; + } + if (r == NS_BADREQ +# ifdef NS_NOPERM + || r == NS_NOPERM +# endif /* NS_NOPERM */ + ) + { + *statp = EX_CONFIG; + return NULL; + } + if (r != NS_SUCCESS) + { + *statp = EX_NOTFOUND; + return NULL; + } + + *statp = EX_OK; + + /* Null out trailing \n */ + if ((p = strchr(buf, '\n')) != NULL) + *p = '\0'; + + return map_rewrite(map, buf, strlen(buf), av); +} +#endif /* MAP_NSD */ + +char * +arith_map_lookup(map, name, av, statp) + MAP *map; + char *name; + char **av; + int *statp; +{ + long r; + long v[2]; + bool res = false; + bool boolres; + static char result[16]; + char **cpp; + + if (tTd(38, 2)) + { + sm_dprintf("arith_map_lookup: key '%s'\n", name); + for (cpp = av; cpp != NULL && *cpp != NULL; cpp++) + sm_dprintf("arith_map_lookup: arg '%s'\n", *cpp); + } + r = 0; + boolres = false; + cpp = av; + *statp = EX_OK; + + /* + ** read arguments for arith map + ** - no check is made whether they are really numbers + ** - just ignores args after the second + */ + + for (++cpp; cpp != NULL && *cpp != NULL && r < 2; cpp++) + v[r++] = strtol(*cpp, NULL, 0); + + /* operator and (at least) two operands given? */ + if (name != NULL && r == 2) + { + switch (*name) + { + case '|': + r = v[0] | v[1]; + break; + + case '&': + r = v[0] & v[1]; + break; + + case '%': + if (v[1] == 0) + return NULL; + r = v[0] % v[1]; + break; + case '+': + r = v[0] + v[1]; + break; + + case '-': + r = v[0] - v[1]; + break; + + case '*': + r = v[0] * v[1]; + break; + + case '/': + if (v[1] == 0) + return NULL; + r = v[0] / v[1]; + break; + + case 'l': + res = v[0] < v[1]; + boolres = true; + break; + + case '=': + res = v[0] == v[1]; + boolres = true; + break; + + case 'r': + r = v[1] - v[0] + 1; + if (r <= 0) + return NULL; + r = get_random() % r + v[0]; + break; + + default: + /* XXX */ + *statp = EX_CONFIG; + if (LogLevel > 10) + sm_syslog(LOG_WARNING, NOQID, + "arith_map: unknown operator %c", + (isascii(*name) && isprint(*name)) ? + *name : '?'); + return NULL; + } + if (boolres) + (void) sm_snprintf(result, sizeof(result), + res ? "TRUE" : "FALSE"); + else + (void) sm_snprintf(result, sizeof(result), "%ld", r); + return result; + } + *statp = EX_CONFIG; + return NULL; +} + +#if SOCKETMAP + +# if NETINET || NETINET6 +# include <arpa/inet.h> +# endif /* NETINET || NETINET6 */ + +# define socket_map_next map_stack[0] + +/* +** SOCKET_MAP_OPEN -- open socket table +*/ + +bool +socket_map_open(map, mode) + MAP *map; + int mode; +{ + STAB *s; + int sock = 0; + SOCKADDR_LEN_T addrlen = 0; + int addrno = 0; + int save_errno; + char *p; + char *colon; + char *at; + struct hostent *hp = NULL; + SOCKADDR addr; + + if (tTd(38, 2)) + sm_dprintf("socket_map_open(%s, %s, %d)\n", + map->map_mname, map->map_file, mode); + + mode &= O_ACCMODE; + + /* sendmail doesn't have the ability to write to SOCKET (yet) */ + if (mode != O_RDONLY) + { + /* issue a pseudo-error message */ + errno = SM_EMAPCANTWRITE; + return false; + } + + if (*map->map_file == '\0') + { + syserr("socket map \"%s\": empty or missing socket information", + map->map_mname); + return false; + } + + s = socket_map_findconn(map->map_file); + if (s->s_socketmap != NULL) + { + /* Copy open connection */ + map->map_db1 = s->s_socketmap->map_db1; + + /* Add this map as head of linked list */ + map->socket_map_next = s->s_socketmap; + s->s_socketmap = map; + + if (tTd(38, 2)) + sm_dprintf("using cached connection\n"); + return true; + } + + if (tTd(38, 2)) + sm_dprintf("opening new connection\n"); + + /* following code is ripped from milter.c */ + /* XXX It should be put in a library... */ + + /* protocol:filename or protocol:port@host */ + memset(&addr, '\0', sizeof(addr)); + p = map->map_file; + colon = strchr(p, ':'); + if (colon != NULL) + { + *colon = '\0'; + + if (*p == '\0') + { +# if NETUNIX + /* default to AF_UNIX */ + addr.sa.sa_family = AF_UNIX; +# else /* NETUNIX */ +# if NETINET + /* default to AF_INET */ + addr.sa.sa_family = AF_INET; +# else /* NETINET */ +# if NETINET6 + /* default to AF_INET6 */ + addr.sa.sa_family = AF_INET6; +# else /* NETINET6 */ + /* no protocols available */ + syserr("socket map \"%s\": no valid socket protocols available", + map->map_mname); + return false; +# endif /* NETINET6 */ +# endif /* NETINET */ +# endif /* NETUNIX */ + } +# if NETUNIX + else if (sm_strcasecmp(p, "unix") == 0 || + sm_strcasecmp(p, "local") == 0) + addr.sa.sa_family = AF_UNIX; +# endif /* NETUNIX */ +# if NETINET + else if (sm_strcasecmp(p, "inet") == 0) + addr.sa.sa_family = AF_INET; +# endif /* NETINET */ +# if NETINET6 + else if (sm_strcasecmp(p, "inet6") == 0) + addr.sa.sa_family = AF_INET6; +# endif /* NETINET6 */ + else + { +# ifdef EPROTONOSUPPORT + errno = EPROTONOSUPPORT; +# else /* EPROTONOSUPPORT */ + errno = EINVAL; +# endif /* EPROTONOSUPPORT */ + syserr("socket map \"%s\": unknown socket type %s", + map->map_mname, p); + return false; + } + *colon++ = ':'; + } + else + { + colon = p; +#if NETUNIX + /* default to AF_UNIX */ + addr.sa.sa_family = AF_UNIX; +#else /* NETUNIX */ +# if NETINET + /* default to AF_INET */ + addr.sa.sa_family = AF_INET; +# else /* NETINET */ +# if NETINET6 + /* default to AF_INET6 */ + addr.sa.sa_family = AF_INET6; +# else /* NETINET6 */ + syserr("socket map \"%s\": unknown socket type %s", + map->map_mname, p); + return false; +# endif /* NETINET6 */ +# endif /* NETINET */ +#endif /* NETUNIX */ + } + +# if NETUNIX + if (addr.sa.sa_family == AF_UNIX) + { + long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_EXECOK; + + at = colon; + if (strlen(colon) >= sizeof(addr.sunix.sun_path)) + { + syserr("socket map \"%s\": local socket name %s too long", + map->map_mname, colon); + return false; + } + errno = safefile(colon, RunAsUid, RunAsGid, RunAsUserName, sff, + S_IRUSR|S_IWUSR, NULL); + + if (errno != 0) + { + /* if not safe, don't create */ + syserr("socket map \"%s\": local socket name %s unsafe", + map->map_mname, colon); + return false; + } + + (void) sm_strlcpy(addr.sunix.sun_path, colon, + sizeof(addr.sunix.sun_path)); + addrlen = sizeof(struct sockaddr_un); + } + else +# endif /* NETUNIX */ +# if NETINET || NETINET6 + if (false +# if NETINET + || addr.sa.sa_family == AF_INET +# endif /* NETINET */ +# if NETINET6 + || addr.sa.sa_family == AF_INET6 +# endif /* NETINET6 */ + ) + { + unsigned short port; + + /* Parse port@host */ + at = strchr(colon, '@'); + if (at == NULL) + { + syserr("socket map \"%s\": bad address %s (expected port@host)", + map->map_mname, colon); + return false; + } + *at = '\0'; + if (isascii(*colon) && isdigit(*colon)) + port = htons((unsigned short) atoi(colon)); + else + { +# ifdef NO_GETSERVBYNAME + syserr("socket map \"%s\": invalid port number %s", + map->map_mname, colon); + return false; +# else /* NO_GETSERVBYNAME */ + register struct servent *sp; + + sp = getservbyname(colon, "tcp"); + if (sp == NULL) + { + syserr("socket map \"%s\": unknown port name %s", + map->map_mname, colon); + return false; + } + port = sp->s_port; +# endif /* NO_GETSERVBYNAME */ + } + *at++ = '@'; + if (*at == '[') + { + char *end; + + end = strchr(at, ']'); + if (end != NULL) + { + bool found = false; +# if NETINET + unsigned long hid = INADDR_NONE; +# endif /* NETINET */ +# if NETINET6 + struct sockaddr_in6 hid6; +# endif /* NETINET6 */ + + *end = '\0'; +# if NETINET + if (addr.sa.sa_family == AF_INET && + (hid = inet_addr(&at[1])) != INADDR_NONE) + { + addr.sin.sin_addr.s_addr = hid; + addr.sin.sin_port = port; + found = true; + } +# endif /* NETINET */ +# if NETINET6 + (void) memset(&hid6, '\0', sizeof(hid6)); + if (addr.sa.sa_family == AF_INET6 && + anynet_pton(AF_INET6, &at[1], + &hid6.sin6_addr) == 1) + { + addr.sin6.sin6_addr = hid6.sin6_addr; + addr.sin6.sin6_port = port; + found = true; + } +# endif /* NETINET6 */ + *end = ']'; + if (!found) + { + syserr("socket map \"%s\": Invalid numeric domain spec \"%s\"", + map->map_mname, at); + return false; + } + } + else + { + syserr("socket map \"%s\": Invalid numeric domain spec \"%s\"", + map->map_mname, at); + return false; + } + } + else + { + hp = sm_gethostbyname(at, addr.sa.sa_family); + if (hp == NULL) + { + syserr("socket map \"%s\": Unknown host name %s", + map->map_mname, at); + return false; + } + addr.sa.sa_family = hp->h_addrtype; + switch (hp->h_addrtype) + { +# if NETINET + case AF_INET: + memmove(&addr.sin.sin_addr, + hp->h_addr, INADDRSZ); + addr.sin.sin_port = port; + addrlen = sizeof(struct sockaddr_in); + addrno = 1; + break; +# endif /* NETINET */ + +# if NETINET6 + case AF_INET6: + memmove(&addr.sin6.sin6_addr, + hp->h_addr, IN6ADDRSZ); + addr.sin6.sin6_port = port; + addrlen = sizeof(struct sockaddr_in6); + addrno = 1; + break; +# endif /* NETINET6 */ + + default: + syserr("socket map \"%s\": Unknown protocol for %s (%d)", + map->map_mname, at, hp->h_addrtype); +# if NETINET6 + freehostent(hp); +# endif /* NETINET6 */ + return false; + } + } + } + else +# endif /* NETINET || NETINET6 */ + { + syserr("socket map \"%s\": unknown socket protocol", + map->map_mname); + return false; + } + + /* nope, actually connecting */ + for (;;) + { + sock = socket(addr.sa.sa_family, SOCK_STREAM, 0); + if (sock < 0) + { + save_errno = errno; + if (tTd(38, 5)) + sm_dprintf("socket map \"%s\": error creating socket: %s\n", + map->map_mname, + sm_errstring(save_errno)); +# if NETINET6 + if (hp != NULL) + freehostent(hp); +# endif /* NETINET6 */ + return false; + } + + if (connect(sock, (struct sockaddr *) &addr, addrlen) >= 0) + break; + + /* couldn't connect.... try next address */ + save_errno = errno; + p = CurHostName; + CurHostName = at; + if (tTd(38, 5)) + sm_dprintf("socket_open (%s): open %s failed: %s\n", + map->map_mname, at, sm_errstring(save_errno)); + CurHostName = p; + (void) close(sock); + + /* try next address */ + if (hp != NULL && hp->h_addr_list[addrno] != NULL) + { + switch (addr.sa.sa_family) + { +# if NETINET + case AF_INET: + memmove(&addr.sin.sin_addr, + hp->h_addr_list[addrno++], + INADDRSZ); + break; +# endif /* NETINET */ + +# if NETINET6 + case AF_INET6: + memmove(&addr.sin6.sin6_addr, + hp->h_addr_list[addrno++], + IN6ADDRSZ); + break; +# endif /* NETINET6 */ + + default: + if (tTd(38, 5)) + sm_dprintf("socket map \"%s\": Unknown protocol for %s (%d)\n", + map->map_mname, at, + hp->h_addrtype); +# if NETINET6 + freehostent(hp); +# endif /* NETINET6 */ + return false; + } + continue; + } + p = CurHostName; + CurHostName = at; + if (tTd(38, 5)) + sm_dprintf("socket map \"%s\": error connecting to socket map: %s\n", + map->map_mname, sm_errstring(save_errno)); + CurHostName = p; +# if NETINET6 + if (hp != NULL) + freehostent(hp); +# endif /* NETINET6 */ + return false; + } +# if NETINET6 + if (hp != NULL) + { + freehostent(hp); + hp = NULL; + } +# endif /* NETINET6 */ + if ((map->map_db1 = (ARBPTR_T) sm_io_open(SmFtStdiofd, + SM_TIME_DEFAULT, + (void *) &sock, + SM_IO_RDWR, + NULL)) == NULL) + { + close(sock); + if (tTd(38, 2)) + sm_dprintf("socket_open (%s): failed to create stream: %s\n", + map->map_mname, sm_errstring(errno)); + return false; + } + + /* Save connection for reuse */ + s->s_socketmap = map; + return true; +} + +/* +** SOCKET_MAP_FINDCONN -- find a SOCKET connection to the server +** +** Cache SOCKET connections based on the connection specifier +** and PID so we don't have multiple connections open to +** the same server for different maps. Need a separate connection +** per PID since a parent process may close the map before the +** child is done with it. +** +** Parameters: +** conn -- SOCKET map connection specifier +** +** Returns: +** Symbol table entry for the SOCKET connection. +*/ + +static STAB * +socket_map_findconn(conn) + const char *conn; +{ + char *nbuf; + STAB *SM_NONVOLATILE s = NULL; + + nbuf = sm_stringf_x("%s%c%d", conn, CONDELSE, (int) CurrentPid); + SM_TRY + s = stab(nbuf, ST_SOCKETMAP, ST_ENTER); + SM_FINALLY + sm_free(nbuf); + SM_END_TRY + return s; +} + +/* +** SOCKET_MAP_CLOSE -- close the socket +*/ + +void +socket_map_close(map) + MAP *map; +{ + STAB *s; + MAP *smap; + + if (tTd(38, 20)) + sm_dprintf("socket_map_close(%s), pid=%ld\n", map->map_file, + (long) CurrentPid); + + /* Check if already closed */ + if (map->map_db1 == NULL) + { + if (tTd(38, 20)) + sm_dprintf("socket_map_close(%s) already closed\n", + map->map_file); + return; + } + sm_io_close((SM_FILE_T *)map->map_db1, SM_TIME_DEFAULT); + + /* Mark all the maps that share the connection as closed */ + s = socket_map_findconn(map->map_file); + smap = s->s_socketmap; + while (smap != NULL) + { + MAP *next; + + if (tTd(38, 2) && smap != map) + sm_dprintf("socket_map_close(%s): closed %s (shared SOCKET connection)\n", + map->map_mname, smap->map_mname); + + smap->map_mflags &= ~(MF_OPEN|MF_WRITABLE); + smap->map_db1 = NULL; + next = smap->socket_map_next; + smap->socket_map_next = NULL; + smap = next; + } + s->s_socketmap = NULL; +} + +/* +** SOCKET_MAP_LOOKUP -- look up a datum in a SOCKET table +*/ + +char * +socket_map_lookup(map, name, av, statp) + MAP *map; + char *name; + char **av; + int *statp; +{ + unsigned int nettolen, replylen, recvlen; + char *replybuf, *rval, *value, *status, *key; + SM_FILE_T *f; + char keybuf[MAXNAME + 1]; + + replybuf = NULL; + rval = NULL; + f = (SM_FILE_T *)map->map_db1; + if (tTd(38, 20)) + sm_dprintf("socket_map_lookup(%s, %s) %s\n", + map->map_mname, name, map->map_file); + + if (!bitset(MF_NOFOLDCASE, map->map_mflags)) + { + nettolen = strlen(name); + if (nettolen > sizeof(keybuf) - 1) + nettolen = sizeof(keybuf) - 1; + memmove(keybuf, name, nettolen); + keybuf[nettolen] = '\0'; + makelower(keybuf); + key = keybuf; + } + else + key = name; + + nettolen = strlen(map->map_mname) + 1 + strlen(key); + SM_ASSERT(nettolen > strlen(map->map_mname)); + SM_ASSERT(nettolen > strlen(key)); + if ((sm_io_fprintf(f, SM_TIME_DEFAULT, "%u:%s %s,", + nettolen, map->map_mname, key) == SM_IO_EOF) || + (sm_io_flush(f, SM_TIME_DEFAULT) != 0) || + (sm_io_error(f))) + { + syserr("451 4.3.0 socket_map_lookup(%s): failed to send lookup request", + map->map_mname); + *statp = EX_TEMPFAIL; + goto errcl; + } + + if (sm_io_fscanf(f, SM_TIME_DEFAULT, "%9u", &replylen) != 1) + { + syserr("451 4.3.0 socket_map_lookup(%s): failed to read length parameter of reply", + map->map_mname); + *statp = EX_TEMPFAIL; + goto errcl; + } + if (replylen > SOCKETMAP_MAXL) + { + syserr("451 4.3.0 socket_map_lookup(%s): reply too long: %u", + map->map_mname, replylen); + *statp = EX_TEMPFAIL; + goto errcl; + } + if (sm_io_getc(f, SM_TIME_DEFAULT) != ':') + { + syserr("451 4.3.0 socket_map_lookup(%s): missing ':' in reply", + map->map_mname); + *statp = EX_TEMPFAIL; + goto error; + } + + replybuf = (char *) sm_malloc(replylen + 1); + if (replybuf == NULL) + { + syserr("451 4.3.0 socket_map_lookup(%s): can't allocate %u bytes", + map->map_mname, replylen + 1); + *statp = EX_OSERR; + goto error; + } + + recvlen = sm_io_read(f, SM_TIME_DEFAULT, replybuf, replylen); + if (recvlen < replylen) + { + syserr("451 4.3.0 socket_map_lookup(%s): received only %u of %u reply characters", + map->map_mname, recvlen, replylen); + *statp = EX_TEMPFAIL; + goto errcl; + } + if (sm_io_getc(f, SM_TIME_DEFAULT) != ',') + { + syserr("451 4.3.0 socket_map_lookup(%s): missing ',' in reply", + map->map_mname); + *statp = EX_TEMPFAIL; + goto errcl; + } + status = replybuf; + replybuf[recvlen] = '\0'; + value = strchr(replybuf, ' '); + if (value != NULL) + { + *value = '\0'; + value++; + } + if (strcmp(status, "OK") == 0) + { + *statp = EX_OK; + + /* collect the return value */ + if (bitset(MF_MATCHONLY, map->map_mflags)) + rval = map_rewrite(map, key, strlen(key), NULL); + else + rval = map_rewrite(map, value, strlen(value), av); + } + else if (strcmp(status, "NOTFOUND") == 0) + { + *statp = EX_NOTFOUND; + if (tTd(38, 20)) + sm_dprintf("socket_map_lookup(%s): %s not found\n", + map->map_mname, key); + } + else + { + if (tTd(38, 5)) + sm_dprintf("socket_map_lookup(%s, %s): server returned error: type=%s, reason=%s\n", + map->map_mname, key, status, + value ? value : ""); + if ((strcmp(status, "TEMP") == 0) || + (strcmp(status, "TIMEOUT") == 0)) + *statp = EX_TEMPFAIL; + else if(strcmp(status, "PERM") == 0) + *statp = EX_UNAVAILABLE; + else + *statp = EX_PROTOCOL; + } + + if (replybuf != NULL) + sm_free(replybuf); + return rval; + + errcl: + socket_map_close(map); + error: + if (replybuf != NULL) + sm_free(replybuf); + return rval; +} +#endif /* SOCKETMAP */ |