/* * Copyright (c) 1998-2008 Proofpoint, 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 SM_RCSID("@(#)$Id: map.c,v 8.713 2013-11-22 20:51:55 ca Exp $") #include #if LDAPMAP # include #endif #if NDBM # include # 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 #if NIS struct dom_binding; /* forward reference needed on IRIX */ # include # if NDBM # define NDBM_YP_COMPAT /* create YP-compatible NDBM files */ # endif #endif /* NIS */ #if CDB # include #endif #include "map.h" #if NEWDB # if DB_VERSION_MAJOR < 2 static bool db_map_open __P((MAP *, int, char *, DBTYPE, const void *)); # endif # if DB_VERSION_MAJOR == 2 static bool db_map_open __P((MAP *, int, char *, DBTYPE, DB_INFO *)); # endif # if DB_VERSION_MAJOR > 2 static bool db_map_open __P((MAP *, int, char *, DBTYPE, void **)); # endif #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 #if NISPLUS static bool nisplus_getcanonname __P((char *, int, int *)); #endif #if NIS static bool nis_getcanonname __P((char *, int, int *)); #endif #if NETINFO static bool ni_getcanonname __P((char *, int, int *)); #endif 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 # define SM_EMAPCANTWRITE ENXIO # endif #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 # define LOCK_ON_OPEN 0 /* no such luck -- bend over backwards */ #endif /* ** 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 (SM_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 'd': { char *h; ++p; h = strchr(p, ' '); if (h != NULL) *h = '\0'; map->map_timeout = convtime(p, 's'); if (h != NULL) *h = ' '; } break; case 'f': map->map_mflags |= MF_NOFOLDCASE; break; case 'k': while (isascii(*++p) && isspace(*p)) continue; map->map_keycolnm = p; 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 '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; default: syserr("Illegal option %c map %s", *p, map->map_mname); break; } while (*p != '\0' && !(SM_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' && !(SM_ISSPACE(*p))) p++; if (*p != '\0') *p++ = '\0'; map->map_file = newstr(map->map_file); } while (*p != '\0' && SM_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; 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 _FFR_8BITENVADDR if (!bitset(MF_KEEPXFMT, map->map_mflags)) { int newlen; char *quoted; newlen = 0; quoted = quote_internal_chars(buf, NULL, &newlen, NULL); if (newlen > buflen) { buflen = newlen; SM_FREE(buf); buf = sm_pmalloc_x(buflen); } (void) sm_strlcpy(buf, quoted, buflen); SM_FREE(quoted); } #endif 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 stabapply(map_init, 0); #if XDEBUG checkfd012("exiting initmaps"); #endif } /* ** 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. */ 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]; /* EAI:ok (domainname) */ 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: ** >0 -- if the host was found. ** 0 -- otherwise. */ int 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 bool secure = true; /* consider all maps secure by default */ nmaps = switch_map_find("hosts", maptype, mapreturn); if (pttl != NULL) *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 } #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 } #endif /* NISPLUS */ #if NAMED_BIND else if (strcmp("dns", maptype[mapno]) == 0) { int r; r = dns_getcanonname(host, hbsize, trymx, &status, pttl); secure = HOST_SECURE == r; found = r > 0; } #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, ad=%d\n", host, secure); /* ** 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 HOST_NOTFOUND; } } return secure ? HOST_SECURE : HOST_OK; } #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 HOST_OK; } } #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 HOST_NOTFOUND; } /* ** 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]; /* EAI:hostname */ 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_STRCASEEQ(name, p)) found = true; else if (dot != NULL) { /* try looking for the FQDN as well */ *dot = '.'; if (SM_STRCASEEQ(name, p)) 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 # endif /* ** 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; unsigned int dns_m_options; }; 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)); memset(map_p, '\0', sizeof(*map_p)); map_p->dns_m_type = -1; map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL; for (;;) { while (SM_ISSPACE(*p)) p++; if (*p != '-') break; switch (*++p) { # if DNSSEC_TEST case '@': ++p; if (nsportip(p) < 0) syserr("dns map %s: nsportip(%s)=failed", map->map_mname, p); break; # endif /* DNSSEC_TEST */ case 'A': map->map_mflags |= MF_APPEND; break; case 'a': map->map_app = ++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; 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 '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': # if defined(RES_USE_EDNS0) && defined(RES_USE_DNSSEC) map_p->dns_m_options |= SM_RES_DNSSEC; # endif break; case 'r': while (isascii(*++p) && isspace(*p)) continue; map->map_retry = atoi(p); break; case 't': map->map_mflags |= MF_NODEFER; break; case 'T': map->map_tapp = ++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; } while (*p != '\0' && !(SM_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; unsigned int options; # if NETINET6 static char buf6[INET6_ADDRSTRLEN]; # endif if (tTd(38, 20)) sm_dprintf("dns_map_lookup(%s, %s)\n", map->map_mname, name); map_p = (struct dns_map *)(map->map_db1); options = map_p->dns_m_options; 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_map(appdomain, C_IN, map_p->dns_m_type, map->map_timeout, map->map_retry, options); sm_free(appdomain); } else { r = dns_lookup_map(name, C_IN, map_p->dns_m_type, map->map_timeout, map->map_retry, options); } 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 */ # ifdef T_TLSA case T_TLSA: type = "T_TLSA"; value = rr->rr_u.rr_txt; break; # endif /* T_TLSA */ } (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 : "", rr->rr_type, value != NULL ? 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 : "", rr->rr_type, value != NULL ? 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: SM_FREE(vp); 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 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 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 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]; /* EAI:ok */ 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_buf(keybuf, keybuf, sizeof(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]; /* EAI:ok */ 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_buf(keybuf, keybuf, sizeof(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) { 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 } #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 # ifndef DB_HASH_NELEM # define DB_HASH_NELEM 4096 /* (starting) size of hash table */ # endif # endif /* DB_VERSION_MAJOR < 2 */ bool bt_map_open(map, mode) MAP *map; int mode; { # if DB_VERSION_MAJOR < 2 BTREEINFO btinfo; # endif # if DB_VERSION_MAJOR == 2 DB_INFO btinfo; # endif # if DB_VERSION_MAJOR > 2 void *btinfo = NULL; # endif 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 # 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 # if DB_VERSION_MAJOR == 2 DB_INFO hinfo; # endif # if DB_VERSION_MAJOR > 2 void *hinfo = NULL; # endif 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 # ifdef DB_CACHE_SIZE hinfo.db_cachesize = DB_CACHE_SIZE; # endif # 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 # if DB_VERSION_MAJOR == 2 DB_INFO *openinfo; # endif # if DB_VERSION_MAJOR > 2 void **openinfo; # endif { 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 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 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 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 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 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]; /* EAI:ok */ 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_buf(keybuf, keybuf, sizeof(keybuf)); lockdb: # if DB_VERSION_MAJOR < 2 fd = db->fd(db); # else 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]; /* EAI:ok */ 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_buf(keybuf, keybuf, sizeof(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) { 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 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 */ #if CDB /* ** CDB Modules */ static bool smdb_add_extension __P((char *, int, char *, char *)); /* ** SMDB_ADD_EXTENSION -- Adds an extension to a file name. ** ** Just adds a . followed by a string to a db_name if there ** is room and the db_name does not already have that extension. ** ** Parameters: ** full_name -- The final file name. ** max_full_name_len -- The max length for full_name. ** db_name -- The name of the db. ** extension -- The extension to add. ** ** Returns: ** SMDBE_OK -- Success. ** Anything else is an error. Look up more info about the ** error in the comments for the specific open() used. */ static bool smdb_add_extension(full_name, max_full_name_len, db_name, extension) char *full_name; int max_full_name_len; char *db_name; char *extension; { int extension_len; int db_name_len; if (full_name == NULL || db_name == NULL || extension == NULL) return false; /* SMDBE_INVALID_PARAMETER; */ extension_len = strlen(extension); db_name_len = strlen(db_name); if (extension_len + db_name_len + 2 > max_full_name_len) return false; /* SMDBE_DB_NAME_TOO_LONG; */ if (db_name_len < extension_len + 1 || db_name[db_name_len - extension_len - 1] != '.' || strcmp(&db_name[db_name_len - extension_len], extension) != 0) (void) sm_snprintf(full_name, max_full_name_len, "%s.%s", db_name, extension); else (void) sm_strlcpy(full_name, db_name, max_full_name_len); return true; } bool cdb_map_open(map, mode) MAP *map; int mode; { int fd, status, omode, smode; long sff; struct stat st; struct cdb *cdbp; char buf[MAXPATHLEN]; if (tTd(38, 2)) sm_dprintf("cdb_map_open(%s, %s, %d)\n", map->map_mname, map->map_file, mode); map->map_db1 = (ARBPTR_T)NULL; map->map_db2 = (ARBPTR_T)NULL; mode &= O_ACCMODE; omode = mode; /* ** Note: ** The code to add the extension and to set up safefile() ** and open() should be in a common function ** (it would be nice to re-use libsmdb?) */ if (!smdb_add_extension(buf, sizeof(buf), map->map_file, CDBext)) { errno = 0; if (!bitset(MF_OPTIONAL, map->map_mflags)) syserr("cdb 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; map->map_mflags |= MF_LOCKED; } else { smode = S_IREAD; if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail)) sff |= SFF_NOWLINK; } if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail)) sff |= SFF_SAFEDIRPATH; status = safefile(buf, RunAsUid, RunAsGid, RunAsUserName, sff, smode, &st); if (status != 0) { char *prob = "unsafe"; /* cannot open this map */ if (status == ENOENT) prob = "missing"; errno = status; if (tTd(38, 2)) sm_dprintf("\t%s map file: %s\n", prob, sm_errstring(status)); if (!bitset(MF_OPTIONAL, map->map_mflags)) syserr("%s map \"%s\": %s map file %s", map->map_mname, prob, buf, sm_errstring(status)); return false; } if (st.st_mode == ST_MODE_NOFILE) omode |= O_CREAT|O_EXCL; # if LOCK_ON_OPEN if (mode == O_RDWR) omode |= O_TRUNC|O_EXLOCK; else omode |= O_SHLOCK; # else if (mode == O_RDWR) omode |= O_TRUNC; # endif /* LOCK_ON_OPEN */ fd = open(buf, omode, DBMMODE); if (fd < 0) { if (!bitset(MF_OPTIONAL, map->map_mflags)) syserr("cdb_map_open: cannot open database %s", buf); return false; } # if !LOCK_ON_OPEN /* make sure no baddies slipped in just before the open... */ if (filechanged(buf, fd, &st)) { int save_errno; save_errno = errno; (void) close(fd); errno = save_errno; syserr("cdb_map_open(%s): file changed after open", buf); return false; } /* actually lock the opened file */ if (!lockfile(fd, buf, NULL, mode == O_RDONLY ? LOCK_SH : LOCK_EX)) syserr("cdb_map_open: cannot lock %s", buf); # endif /* !LOCK_ON_OPEN */ map->map_lockfd = fd; if (fd >= 0 && fstat(fd, &st) >= 0) map->map_mtime = st.st_mtime; /* only for aliases! */ if (mode == O_RDWR) { struct cdb_make *cdbmp; cdbmp = (struct cdb_make *) xalloc(sizeof(*cdbmp)); status = cdb_make_start(cdbmp, fd); if (status != 0) { close(fd); if (!bitset(MF_OPTIONAL, map->map_mflags)) syserr("initialization of cdb map (make) failed"); return false; } map->map_db2 = (ARBPTR_T)cdbmp; return true; } cdbp = (struct cdb *) xalloc(sizeof(*cdbp)); status = cdb_init(cdbp, fd); if (status != 0) { close(fd); if (!bitset(MF_OPTIONAL, map->map_mflags)) syserr("initialization of cdb map failed"); return false; } map->map_db1 = (ARBPTR_T)cdbp; return true; } char * cdb_map_lookup(map, name, av, statp) MAP *map; char *name; char **av; int *statp; { char *data; struct cdb *cdbmap; unsigned int klen, dlen; int st, fd; char key[MAXNAME + 1]; /* EAI:ok */ char buf[MAXPATHLEN]; struct stat stbuf; data = NULL; cdbmap = map->map_db1; if (tTd(38, 20)) sm_dprintf("cdb_map_lookup(%s, %s)\n", map->map_mname, name); if (!smdb_add_extension(buf, sizeof(buf), map->map_file, CDBext)) { errno = 0; if (!bitset(MF_OPTIONAL, map->map_mflags)) syserr("cdb map \"%s\": map file %s name too long", map->map_mname, map->map_file); return false; } klen = strlen(name); if (klen > sizeof(key) - 1) klen = sizeof(key) - 1; memmove(key, name, klen); key[klen] = '\0'; if (!bitset(MF_NOFOLDCASE, map->map_mflags)) makelower_buf(key, key, sizeof(key)); lockdb: fd = map->map_lockfd; if (fd >= 0 && !bitset(MF_LOCKED, map->map_mflags)) (void) lockfile(fd, buf, NULL, 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, NULL, 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; if ((omode & O_ACCMODE) == O_RDWR) map->map_mflags |= MF_WRITABLE; cdbmap = map->map_db1; 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; syserr("Cannot reopen CDB database %s", map->map_file); } return NULL; } } st = 0; if (bitset(MF_TRY0NULL, map->map_mflags)) { st = cdb_find(cdbmap, key, klen); if (st == 1) map->map_mflags &= ~MF_TRY1NULL; } if (st != 1 && bitset(MF_TRY1NULL, map->map_mflags)) { st = cdb_find(cdbmap, key, klen + 1); if (st == 1) map->map_mflags &= ~MF_TRY0NULL; } if (fd >= 0 && !bitset(MF_LOCKED, map->map_mflags)) (void) lockfile(fd, buf, NULL, LOCK_UN); if (st != 1) { if (st < 0) syserr("cdb_map_lookup: get (%s)", name); return NULL; } else { dlen = cdb_datalen(cdbmap); data = malloc(dlen + 1); cdb_read(cdbmap, data, dlen, cdb_datapos(cdbmap)); data[dlen] = '\0'; } if (bitset(MF_MATCHONLY, map->map_mflags)) return map_rewrite(map, name, strlen(name), NULL); else return map_rewrite(map, data, dlen, av); } /* ** CDB_MAP_STORE -- store a datum in the CDB database */ void cdb_map_store(map, lhs, rhs) MAP *map; char *lhs; char *rhs; { struct cdb_make *cdbmp; size_t klen; size_t vlen; int status; char keybuf[MAXNAME + 1]; /* EAI:ok */ cdbmp = map->map_db2; if (cdbmp == NULL) return; /* XXX */ klen = strlen(lhs); vlen = strlen(rhs); if (!bitset(MF_NOFOLDCASE, map->map_mflags)) { if (klen > sizeof(keybuf) - 1) klen = sizeof(keybuf) - 1; memmove(keybuf, lhs, klen); keybuf[klen] = '\0'; makelower_buf(keybuf, keybuf, sizeof(keybuf)); lhs = keybuf; } if (bitset(MF_INCLNULL, map->map_mflags)) { klen++; vlen++; } /* flags? */ status = cdb_make_put(cdbmp, lhs, klen, rhs, vlen, 0); /* and now? */ } void cdb_map_close(map) MAP * map; { struct cdb *cdbp; struct cdb_make *cdbmp; int fd; fd = -1; cdbp = map->map_db1; if (cdbp != NULL) { if (tTd(38, 20)) sm_dprintf("cdb_map_close(%p): cdbp\n", (void *)cdbp); fd = cdb_fileno(cdbp); cdb_free(cdbp); SM_FREE(cdbp); } cdbmp = map->map_db2; if (cdbmp != NULL) { if (tTd(38, 20)) sm_dprintf("cdb_map_close(%p): cdmbp\n", (void *)cdbmp); fd = cdb_fileno(cdbmp); /* write out the distinguished alias */ /* XXX Why isn't this in a common place? */ cdb_map_store(map, "@", "@"); if (cdb_make_finish(cdbmp) != 0) syserr("cdb: cdb_make_finish(%s) failed", map->map_file); if (fd >= 0) { if (fsync(fd) == -1) syserr("cdb: fsync(%s) failed", map->map_file); if (close(fd) == -1) syserr("cdb: close(%s) failed", map->map_file); fd = -1; } SM_FREE(cdbmp); } if (fd >=0) close(fd); } #endif /* CDB */ /* ** NIS Modules */ #if NIS # ifndef YPERR_BUSY # define YPERR_BUSY 16 # endif /* ** 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)); 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 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]; /* EAI:ok */ 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_buf(keybuf, keybuf, sizeof(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(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); 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 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]; /* EAI:hostname */ char nbuf[MAXNAME + 1]; /* EAI:hostname */ 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_buf(nbuf, nbuf, sizeof(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(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; 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 # include # ifndef NIS_TABLE_OBJ # define NIS_TABLE_OBJ TABLE_OBJ # endif # 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 != NIS_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]; /* EAI:ok */ 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_buf(search_key, search_key, sizeof(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]; /* EAI:hostname */ char qbuf[MAXLINE + NIS_MAXNAMELEN]; /* EAI:hostname */ 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] = ""; /* EAI:hostname */ 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 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 connecting 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 "ldap_open", # endif 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 "ldap_open", # endif 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 # define AV_FREE(av) \ do \ { \ int ai; \ for (ai = 0; ai < SM_LDAP_ARGS && av[ai] != NULL; ai++) \ SM_FREE(av[ai]); \ } while (0) # if USE_EAI bool allascii; char *largv[SM_LDAP_ARGS]; char **largs; # define LARGV_FREE AV_FREE(largv) # else # define largs av # define LARGV_FREE # endif # 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]; /* EAI:Sun only, ignore */ 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); /* initialize first element so AV_FREE can work */ argv[0] = NULL; # if USE_EAI largv[0] = NULL; # endif if (lmap->ldap_multi_args) { SM_REQUIRE(av != NULL); memset(argv, '\0', sizeof(argv)); # if USE_EAI largs = av; memset(largv, '\0', sizeof(largv)); /* this is ugly - can we merge it with the next loop? */ allascii = true; if (!bitset(MF_MATCHONLY, map->map_mflags)) { for (i = 0, allascii = true; i < SM_LDAP_ARGS && av[i] != NULL; i++) { if (!addr_is_ascii(av[i])) { allascii = false; largs = largv; break; } } } # endif /* USE_EAI */ for (i = 0; i < SM_LDAP_ARGS && av[i] != NULL; i++) { argv[i] = sm_strdup(av[i]); if (argv[i] == NULL) { *statp = EX_TEMPFAIL; goto none; } if (!bitset(MF_NOFOLDCASE, map->map_mflags)) /* && !bitset(MF_MATCHONLY, map->map_mflags)) */ /* see below: av[]/largs onluy used if !MF_MATCHONLY !? */ { # if USE_EAI if (!allascii) { char *lower; lower = makelower(av[i]); largv[i] = sm_strdup(lower); if (largv[i] == NULL) { *statp = EX_TEMPFAIL; goto none; } } else # endif /* USE_EAI */ /* NOTE: see else above! */ 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 */ AV_FREE(argv); } 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 case LDAP_TIMEOUT: case LDAP_UNAVAILABLE: /* server disappeared, try reopen on next search */ ldapmap_close(map); break; } errno = save_errno; goto none; } # 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; goto none; } # 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 /* 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, map=%s, name=%s", map->map_mname, name); else syserr("451 4.3.5 Error getting LDAP results, map=%s, name=%s", map->map_mname, name); } errno = save_errno; goto none; } /* Did we match anything? */ if (vp == NULL && !bitset(MF_MATCHONLY, map->map_mflags)) goto none; if (*statp == EX_OK) { if (LogLevel > 9) sm_syslog(LOG_INFO, CurEnv->e_id, "ldap=%s, %.100s=>%s", map->map_mname, name, vp == 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), largs); } SM_FREE(vp); /* XXX */ } LARGV_FREE; return result; none: /* other cleanup? */ save_errno = errno; AV_FREE(argv); LARGV_FREE; errno = save_errno; return NULL; } /* ** 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 { 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; SM_LDAP_STRUCT *lmap_alloc; 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_alloc = lmap = (SM_LDAP_STRUCT *) xalloc(sizeof(*lmap)); if (LDAPDefaults == NULL) sm_ldap_clear(lmap); else STRUCTCOPY(*LDAPDefaults, *lmap); } else lmap_alloc = NULL; /* 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); goto fail; } /* 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 LDAP_NETWORK_TIMEOUT if (0 == lmap->ldap_networktmo) lmap->ldap_networktmo = (LDAP_NETWORK_TIMEOUT > 1) ? LDAP_NETWORK_TIMEOUT : 60; # endif for (;;) { while (SM_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 LDAP_NETWORK_TIMEOUT case 'c': /* network (connect) timeout */ while (isascii(*++p) && isspace(*p)) continue; lmap->ldap_networktmo = atoi(p); break; # endif /* 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); goto fail; # 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); goto fail; } 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); goto fail; } 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 = ' '; goto fail; } } 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 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 = ' '; goto fail; } } 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 = ' '; goto fail; } } 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); goto fail; } # 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); goto fail; } # endif /* LDAP_VERSION_MIN */ break; case 'x': # if _FFR_SM_LDAP_DBG while (isascii(*++p) && isspace(*p)) continue; lmap->ldap_debug = atoi(p); # endif 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' && !(SM_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)); goto fail; } 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)); goto fail; } 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"); goto fail; /* 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); goto fail; } } 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 (SM_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); goto fail; } 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_STRCASEEQ(v, "objectClass") && 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_STRCASEEQ(v, lmap->ldap_attr[j])) { syserr("Duplicate attribute (%s) in %s", v, map->map_mname); goto fail; } } 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_STRCASEEQ(type, "dn")) { recurse = true; lmap->ldap_attr_type[use] = SM_LDAP_ATTR_DN; } else if (SM_STRCASEEQ(type, "filter")) { recurse = true; lmap->ldap_attr_type[use] = SM_LDAP_ATTR_FILTER; } else if (SM_STRCASEEQ(type, "url")) { recurse = true; lmap->ldap_attr_type[use] = SM_LDAP_ATTR_URL; } else if (SM_STRCASEEQ(type, "normal")) { lmap->ldap_attr_type[use] = SM_LDAP_ATTR_NORMAL; normalseen = true; } else { syserr("Unknown attribute type (%s) in %s", type, map->map_mname); goto fail; } } 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); goto fail; } if (recurse && lmap->ldap_attrsonly == LDAPMAP_TRUE) { syserr("LDAP recursion requested in %s can not be used with -n", map->map_mname); goto fail; } } map->map_db1 = (ARBPTR_T) lmap; return true; fail: SM_FREE(lmap_alloc); return false; } /* ** 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(map.map_app); SM_FREE(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 (SM_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 = SM_ISSPACE(*p); while (*p != '\0' && !done) { if (*p == '"') { while (*++p != '"' && *p != '\0') continue; if (*p != '\0') p++; } else p++; done = SM_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 (SM_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' && SM_ISSPACE(*p)) continue; if (*p == '\0') break; priority = p; while (*p != '\0' && !(SM_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_STRCASEEQ("EMERG", priority)) map->map_prio = LOG_EMERG; else #endif #ifdef LOG_ALERT if (SM_STRCASEEQ("ALERT", priority)) map->map_prio = LOG_ALERT; else #endif #ifdef LOG_CRIT if (SM_STRCASEEQ("CRIT", priority)) map->map_prio = LOG_CRIT; else #endif #ifdef LOG_ERR if (SM_STRCASEEQ("ERR", priority)) map->map_prio = LOG_ERR; else #endif #ifdef LOG_WARNING if (SM_STRCASEEQ("WARNING", priority)) map->map_prio = LOG_WARNING; else #endif #ifdef LOG_NOTICE if (SM_STRCASEEQ("NOTICE", priority)) map->map_prio = LOG_NOTICE; else #endif #ifdef LOG_INFO if (SM_STRCASEEQ("INFO", priority)) map->map_prio = LOG_INFO; else #endif #ifdef LOG_DEBUG if (SM_STRCASEEQ("DEBUG", priority)) map->map_prio = LOG_DEBUG; else #endif { syserr("syslog_map_parseargs: Unknown priority %s", priority); return false; } } #if _FFR_8BITENVADDR map->map_mflags |= MF_KEEPXFMT; #endif 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 (SM_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' && SM_ISSPACE(*p)) continue; if (*p == '\0') break; dbg_level = p; while (*p != '\0' && !(SM_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); } # if _FFR_8BITENVADDR map->map_mflags |= MF_KEEPXFMT; # endif 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]; /* EAI:ok - larger buffer used if needed */ nl = strlen(name); if (nl < sizeof(nbuf) - 1) np = nbuf; else np = xalloc(nl + 2); np[0] = '\\'; (void) sm_strlcpy(&np[1], name, sizeof(nbuf) - 1); # ifdef HESIOD_INIT hp = hesiod_resolve(HesiodContext, np, map->map_file); # else hp = hes_resolve(np, map->map_file); # endif 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 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]; /* EAI:hostname */ 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]; /* EAI:ok */ char linebuf[MAXLINE]; char buf[MAXNAME + 1]; /* EAI:ok */ 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_buf(search_key, search_key, sizeof(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)) >= 0) { 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_STRCASEEQ(search_key, p)) { 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]; /* EAI:hostname */ char nbuf[MAXNAME + 1]; /* EAI:hostname */ 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)) >= 0) { 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 weird 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 #if NDBM if (bitset(MF_IMPL_NDBM, map->map_mflags)) return ndbm_map_lookup(map, name, av, pstat); #endif #if CDB if (bitset(MF_IMPL_CDB, map->map_mflags)) return cdb_map_lookup(map, name, av, pstat); #endif 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 #if NDBM if (bitset(MF_IMPL_NDBM, map->map_mflags)) ndbm_map_store(map, lhs, rhs); #endif #if CDB if (bitset(MF_IMPL_CDB, map->map_mflags)) cdb_map_store(map, lhs, rhs); #endif stab_map_store(map, lhs, rhs); } /* ** IMPL_MAP_OPEN -- implicit database open */ bool impl_map_open(map, mode) MAP *map; int mode; { bool wasopt; if (tTd(38, 2)) sm_dprintf("impl_map_open(%s, %s, %d)\n", map->map_mname, map->map_file, mode); mode &= O_ACCMODE; wasopt = bitset(MF_OPTIONAL, map->map_mflags); /* suppress error msgs */ map->map_mflags |= MF_OPTIONAL; #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 goto ok; } else map->map_mflags &= ~MF_IMPL_HASH; #endif /* NEWDB */ #if NDBM map->map_mflags |= MF_IMPL_NDBM; if (ndbm_map_open(map, mode)) goto ok; else map->map_mflags &= ~MF_IMPL_NDBM; #endif /* NDBM */ #if CDB map->map_mflags |= MF_IMPL_CDB; if (cdb_map_open(map, mode)) goto ok; else map->map_mflags &= ~MF_IMPL_CDB; #endif /* CDB */ if (!bitset(MF_ALIAS, map->map_mflags)) goto fail; #if NEWDB || NDBM || CDB if (Verbose) message("WARNING: cannot open alias database %s%s", map->map_file, mode == O_RDONLY ? "; reading text version" : ""); #else if (mode != O_RDONLY) usrerr("Cannot rebuild aliases: no database format defined"); #endif if (mode == O_RDONLY && stab_map_open(map, mode)) goto ok; fail: if (!wasopt) map->map_mflags &= ~MF_OPTIONAL; return false; ok: if (!wasopt) map->map_mflags &= ~MF_OPTIONAL; return true; } /* ** 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 */ #if CDB if (bitset(MF_IMPL_CDB, map->map_mflags)) { cdb_map_close(map); map->map_mflags &= ~MF_IMPL_CDB; } #endif /* CDB */ } /* ** 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_STRCASEEQ(map->map_valcolnm, "name")) map->map_valcolno = 1; else if (SM_STRCASEEQ(map->map_valcolnm, "passwd")) map->map_valcolno = 2; else if (SM_STRCASEEQ(map->map_valcolnm, "uid")) map->map_valcolno = 3; else if (SM_STRCASEEQ(map->map_valcolnm, "gid")) map->map_valcolno = 4; else if (SM_STRCASEEQ(map->map_valcolnm, "gecos")) map->map_valcolno = 5; else if (SM_STRCASEEQ(map->map_valcolnm, "dir")) map->map_valcolno = 6; else if (SM_STRCASEEQ(map->map_valcolnm, "shell")) 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 (SM_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]; /* EAI:not relevant (map name) */ 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 # 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 (SM_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' && !(SM_ISSPACE(*p))) p++; if (*p != '\0') *p++ = '\0'; } # if _FFR_8BITENVADDR (void) dequote_internal_chars(p, p, strlen(p) + 1); # endif 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 */ SM_FREE(fields); return false; } if (sub_param != NULL && sub_param[0] != '\0') { /* optional parameter -sfields */ if (parse_fields(sub_param, fields, MAX_MATCH + 1, substrings) == -1) { SM_FREE(map_p->regex_pattern_buf); /* XXX */ SM_FREE(map_p); /* XXX */ SM_FREE(fields); 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: name=%s, key='%s'\n", map->map_mname, 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]; /* EAI:not relevant */ 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 # define _DATUM_DEFINED # include 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]; /* EAI:ok */ 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_buf(keybuf, keybuf, sizeof(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 ) { *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; } char * arpa_map_lookup(map, name, av, statp) MAP *map; char *name; char **av; int *statp; { int r; char *rval; char result[128]; /* IPv6: 64 + 10 + 1 would be enough */ if (tTd(38, 2)) sm_dprintf("arpa_map_lookup: key '%s'\n", name); *statp = EX_DATAERR; r = 1; memset(result, '\0', sizeof(result)); rval = NULL; #if NETINET6 if (sm_strncasecmp(name, "IPv6:", 5) == 0) { struct in6_addr in6_addr; r = anynet_pton(AF_INET6, name, &in6_addr); if (r == 1) { static char hex_digits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; unsigned char *src; char *dst; int i; src = (unsigned char *) &in6_addr; dst = result; for (i = 15; i >= 0; i--) { *dst++ = hex_digits[src[i] & 0x0f]; *dst++ = '.'; *dst++ = hex_digits[(src[i] >> 4) & 0x0f]; if (i > 0) *dst++ = '.'; } *statp = EX_OK; } } else #endif /* NETINET6 */ #if NETINET { struct in_addr in_addr; r = inet_pton(AF_INET, name, &in_addr); if (r == 1) { unsigned char *src; src = (unsigned char *) &in_addr; (void) snprintf(result, sizeof(result), "%u.%u.%u.%u", src[3], src[2], src[1], src[0]); *statp = EX_OK; } } #endif /* NETINET */ if (r < 0) *statp = EX_UNAVAILABLE; if (tTd(38, 2)) sm_dprintf("arpa_map_lookup: r=%d, result='%s'\n", r, result); if (*statp == EX_OK) { if (bitset(MF_MATCHONLY, map->map_mflags)) rval = map_rewrite(map, name, strlen(name), NULL); else rval = map_rewrite(map, result, strlen(result), av); } return rval; } #if _FFR_SETDEBUG_MAP char * setdebug_map_lookup(map, name, av, statp) MAP *map; char *name; char **av; int *statp; { if (tTd(38, 2)) { char **cpp; sm_dprintf("setdebug_map_lookup: key '%s'\n", name); for (cpp = av; cpp != NULL && *cpp != NULL; cpp++) sm_dprintf("setdebug_map_lookup: arg '%s'\n", *cpp); } *statp = EX_OK; tTflag(name); return NULL; } #endif #if _FFR_SETOPT_MAP char * setopt_map_lookup(map, name, av, statp) MAP *map; char *name; char **av; int *statp; { # if !_FFR_SETANYOPT int val; # endif char **cpp; if (tTd(38, 2)) { sm_dprintf("setopt_map_lookup: key '%s'\n", name); for (cpp = av; cpp != NULL && *cpp != NULL; cpp++) sm_dprintf("setopt_map_lookup: arg '%s'\n", *cpp); } # if _FFR_SETANYOPT /* ** API screwed up... ** first arg is the "short" name and second is the entire string... */ sm_dprintf("setoption: name=%s\n", name); setoption(' ', name, true, false, CurEnv); *statp = EX_OK; return NULL; # else /* _FFR_SETANYOPT */ *statp = EX_CONFIG; cpp = av; if (cpp == NULL || ++cpp == NULL || *cpp == NULL) return NULL; *statp = EX_OK; errno = 0; val = strtol(*cpp, NULL, 0); /* check for valid number? */ /* use a table? */ if (SM_STRCASEEQ(name, "LogLevel")) { LogLevel = val; sm_dprintf("LogLevel=%d\n", val); return NULL; } # endif /* _FFR_SETANYOPT */ *statp = EX_CONFIG; return NULL; } #endif #if SOCKETMAP # if NETINET || NETINET6 # include # endif # 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; int tmo; 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_STRCASEEQ(p, "unix") || SM_STRCASEEQ(p, "local")) addr.sa.sa_family = AF_UNIX; # endif /* NETUNIX */ # if NETINET else if (SM_STRCASEEQ(p, "inet")) addr.sa.sa_family = AF_INET; # endif /* NETINET */ # if NETINET6 else if (SM_STRCASEEQ(p, "inet6")) addr.sa.sa_family = AF_INET6; # endif /* NETINET6 */ else { # ifdef EPROTONOSUPPORT errno = EPROTONOSUPPORT; # else 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 # if NETINET6 || addr.sa.sa_family == AF_INET6 # endif ) { 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 # if NETINET6 struct sockaddr_in6 hid6; # endif *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 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 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 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; } tmo = map->map_timeout; if (tmo == 0) tmo = 30000; /* default: 30s */ else tmo *= 1000; /* s -> ms */ sm_io_setinfo(map->map_db1, SM_IO_WHAT_TIMEOUT, &tmo); /* 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; int ret; char *replybuf, *rval, *value, *status, *key; SM_FILE_T *f; char keybuf[MAXNAME + 1]; /* EAI:ok */ 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_buf(keybuf, keybuf, sizeof(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 ((ret = sm_io_fscanf(f, SM_TIME_DEFAULT, "%9u", &replylen)) != 1) { if (errno == EAGAIN) { syserr("451 4.3.0 socket_map_lookup(%s): read timeout", map->map_mname); } else if (SM_IO_EOF == ret) { if (LogLevel > 9) sm_syslog(LOG_INFO, CurEnv->e_id, "socket_map_lookup(%s): EOF", map->map_mname); } else { syserr("451 4.3.0 socket_map_lookup(%s): failed to read length parameter of reply %d", map->map_mname, errno); } *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; } SM_FREE(replybuf); return rval; errcl: socket_map_close(map); error: SM_FREE(replybuf); return rval; } #endif /* SOCKETMAP */