diff options
Diffstat (limited to 'sendmail/src/alias.c')
-rw-r--r-- | sendmail/src/alias.c | 1015 |
1 files changed, 1015 insertions, 0 deletions
diff --git a/sendmail/src/alias.c b/sendmail/src/alias.c new file mode 100644 index 0000000..3eae4ba --- /dev/null +++ b/sendmail/src/alias.c @@ -0,0 +1,1015 @@ +/* + * Copyright (c) 1998-2003 Sendmail, Inc. and its suppliers. + * All rights reserved. + * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved. + * Copyright (c) 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the sendmail distribution. + * + */ + +#include <sendmail.h> + +SM_RCSID("@(#)$Id: alias.c,v 8.219 2006/10/24 18:04:09 ca Exp $") + +#define SEPARATOR ':' +# define ALIAS_SPEC_SEPARATORS " ,/:" + +static MAP *AliasFileMap = NULL; /* the actual aliases.files map */ +static int NAliasFileMaps; /* the number of entries in AliasFileMap */ + +static char *aliaslookup __P((char *, int *, char *)); + +/* +** ALIAS -- Compute aliases. +** +** Scans the alias file for an alias for the given address. +** If found, it arranges to deliver to the alias list instead. +** Uses libdbm database if -DDBM. +** +** Parameters: +** a -- address to alias. +** sendq -- a pointer to the head of the send queue +** to put the aliases in. +** aliaslevel -- the current alias nesting depth. +** e -- the current envelope. +** +** Returns: +** none +** +** Side Effects: +** Aliases found are expanded. +** +** Deficiencies: +** It should complain about names that are aliased to +** nothing. +*/ + +void +alias(a, sendq, aliaslevel, e) + register ADDRESS *a; + ADDRESS **sendq; + int aliaslevel; + register ENVELOPE *e; +{ + register char *p; + char *owner; + auto int status = EX_OK; + char obuf[MAXNAME + 7]; + + if (tTd(27, 1)) + sm_dprintf("alias(%s)\n", a->q_user); + + /* don't realias already aliased names */ + if (!QS_IS_OK(a->q_state)) + return; + + if (NoAlias) + return; + + e->e_to = a->q_paddr; + + /* + ** Look up this name. + ** + ** If the map was unavailable, we will queue this message + ** until the map becomes available; otherwise, we could + ** bounce messages inappropriately. + */ + +#if _FFR_REDIRECTEMPTY + /* + ** envelope <> can't be sent to mailing lists, only owner- + ** send spam of this type to owner- of the list + ** ---- to stop spam from going to mailing lists! + */ + + if (e->e_sender != NULL && *e->e_sender == '\0') + { + /* Look for owner of alias */ + (void) sm_strlcpyn(obuf, sizeof(obuf), 2, "owner-", a->q_user); + if (aliaslookup(obuf, &status, a->q_host) != NULL) + { + if (LogLevel > 8) + sm_syslog(LOG_WARNING, e->e_id, + "possible spam from <> to list: %s, redirected to %s\n", + a->q_user, obuf); + a->q_user = sm_rpool_strdup_x(e->e_rpool, obuf); + } + } +#endif /* _FFR_REDIRECTEMPTY */ + + p = aliaslookup(a->q_user, &status, a->q_host); + if (status == EX_TEMPFAIL || status == EX_UNAVAILABLE) + { + a->q_state = QS_QUEUEUP; + if (e->e_message == NULL) + e->e_message = sm_rpool_strdup_x(e->e_rpool, + "alias database unavailable"); + + /* XXX msg only per recipient? */ + if (a->q_message == NULL) + a->q_message = "alias database unavailable"; + return; + } + if (p == NULL) + return; + + /* + ** Match on Alias. + ** Deliver to the target list. + */ + + if (tTd(27, 1)) + sm_dprintf("%s (%s, %s) aliased to %s\n", + a->q_paddr, a->q_host, a->q_user, p); + if (bitset(EF_VRFYONLY, e->e_flags)) + { + a->q_state = QS_VERIFIED; + return; + } + message("aliased to %s", shortenstring(p, MAXSHORTSTR)); + if (LogLevel > 10) + sm_syslog(LOG_INFO, e->e_id, + "alias %.100s => %s", + a->q_paddr, shortenstring(p, MAXSHORTSTR)); + a->q_flags &= ~QSELFREF; + if (tTd(27, 5)) + { + sm_dprintf("alias: QS_EXPANDED "); + printaddr(sm_debug_file(), a, false); + } + a->q_state = QS_EXPANDED; + + /* + ** Always deliver aliased items as the default user. + ** Setting q_gid to 0 forces deliver() to use DefUser + ** instead of the alias name for the call to initgroups(). + */ + + a->q_uid = DefUid; + a->q_gid = 0; + a->q_fullname = NULL; + a->q_flags |= QGOODUID|QALIAS; + + (void) sendtolist(p, a, sendq, aliaslevel + 1, e); + + if (bitset(QSELFREF, a->q_flags) && QS_IS_EXPANDED(a->q_state)) + a->q_state = QS_OK; + + /* + ** Look for owner of alias + */ + + if (strncmp(a->q_user, "owner-", 6) == 0 || + strlen(a->q_user) > sizeof(obuf) - 7) + (void) sm_strlcpy(obuf, "owner-owner", sizeof(obuf)); + else + (void) sm_strlcpyn(obuf, sizeof(obuf), 2, "owner-", a->q_user); + owner = aliaslookup(obuf, &status, a->q_host); + if (owner == NULL) + return; + + /* reflect owner into envelope sender */ + if (strpbrk(owner, ",:/|\"") != NULL) + owner = obuf; + a->q_owner = sm_rpool_strdup_x(e->e_rpool, owner); + + /* announce delivery to this alias; NORECEIPT bit set later */ + if (e->e_xfp != NULL) + (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT, + "Message delivered to mailing list %s\n", + a->q_paddr); + e->e_flags |= EF_SENDRECEIPT; + a->q_flags |= QDELIVERED|QEXPANDED; +} +/* +** ALIASLOOKUP -- look up a name in the alias file. +** +** Parameters: +** name -- the name to look up. +** pstat -- a pointer to a place to put the status. +** av -- argument for %1 expansion. +** +** Returns: +** the value of name. +** NULL if unknown. +** +** Side Effects: +** none. +** +** Warnings: +** The return value will be trashed across calls. +*/ + +static char * +aliaslookup(name, pstat, av) + char *name; + int *pstat; + char *av; +{ + static MAP *map = NULL; +#if _FFR_ALIAS_DETAIL + int i; + char *argv[4]; +#endif /* _FFR_ALIAS_DETAIL */ + + if (map == NULL) + { + STAB *s = stab("aliases", ST_MAP, ST_FIND); + + if (s == NULL) + return NULL; + map = &s->s_map; + } + DYNOPENMAP(map); + + /* special case POstMastER -- always use lower case */ + if (sm_strcasecmp(name, "postmaster") == 0) + name = "postmaster"; + +#if _FFR_ALIAS_DETAIL + i = 0; + argv[i++] = name; + argv[i++] = av; + + /* XXX '+' is hardwired here as delimiter! */ + if (av != NULL && *av == '+') + argv[i++] = av + 1; + argv[i++] = NULL; + return (*map->map_class->map_lookup)(map, name, argv, pstat); +#else /* _FFR_ALIAS_DETAIL */ + return (*map->map_class->map_lookup)(map, name, NULL, pstat); +#endif /* _FFR_ALIAS_DETAIL */ +} +/* +** SETALIAS -- set up an alias map +** +** Called when reading configuration file. +** +** Parameters: +** spec -- the alias specification +** +** Returns: +** none. +*/ + +void +setalias(spec) + char *spec; +{ + register char *p; + register MAP *map; + char *class; + STAB *s; + + if (tTd(27, 8)) + sm_dprintf("setalias(%s)\n", spec); + + for (p = spec; p != NULL; ) + { + char buf[50]; + + while (isascii(*p) && isspace(*p)) + p++; + if (*p == '\0') + break; + spec = p; + + if (NAliasFileMaps >= MAXMAPSTACK) + { + syserr("Too many alias databases defined, %d max", + MAXMAPSTACK); + return; + } + if (AliasFileMap == NULL) + { + (void) sm_strlcpy(buf, "aliases.files sequence", + sizeof(buf)); + AliasFileMap = makemapentry(buf); + if (AliasFileMap == NULL) + { + syserr("setalias: cannot create aliases.files map"); + return; + } + } + (void) sm_snprintf(buf, sizeof(buf), "Alias%d", NAliasFileMaps); + s = stab(buf, ST_MAP, ST_ENTER); + map = &s->s_map; + memset(map, '\0', sizeof(*map)); + map->map_mname = s->s_name; + p = strpbrk(p, ALIAS_SPEC_SEPARATORS); + if (p != NULL && *p == SEPARATOR) + { + /* map name */ + *p++ = '\0'; + class = spec; + spec = p; + } + else + { + class = "implicit"; + map->map_mflags = MF_INCLNULL; + } + + /* find end of spec */ + if (p != NULL) + { + bool quoted = false; + + for (; *p != '\0'; p++) + { + /* + ** Don't break into a quoted string. + ** Needed for ldap maps which use + ** commas in their specifications. + */ + + if (*p == '"') + quoted = !quoted; + else if (*p == ',' && !quoted) + break; + } + + /* No more alias specifications follow */ + if (*p == '\0') + p = NULL; + } + if (p != NULL) + *p++ = '\0'; + + if (tTd(27, 20)) + sm_dprintf(" map %s:%s %s\n", class, s->s_name, spec); + + /* look up class */ + s = stab(class, ST_MAPCLASS, ST_FIND); + if (s == NULL) + { + syserr("setalias: unknown alias class %s", class); + } + else if (!bitset(MCF_ALIASOK, s->s_mapclass.map_cflags)) + { + syserr("setalias: map class %s can't handle aliases", + class); + } + else + { + map->map_class = &s->s_mapclass; + map->map_mflags |= MF_ALIAS; + if (map->map_class->map_parse(map, spec)) + { + map->map_mflags |= MF_VALID; + AliasFileMap->map_stack[NAliasFileMaps++] = map; + } + } + } +} +/* +** ALIASWAIT -- wait for distinguished @:@ token to appear. +** +** This can decide to reopen or rebuild the alias file +** +** Parameters: +** map -- a pointer to the map descriptor for this alias file. +** ext -- the filename extension (e.g., ".db") for the +** database file. +** isopen -- if set, the database is already open, and we +** should check for validity; otherwise, we are +** just checking to see if it should be created. +** +** Returns: +** true -- if the database is open when we return. +** false -- if the database is closed when we return. +*/ + +bool +aliaswait(map, ext, isopen) + MAP *map; + char *ext; + bool isopen; +{ + bool attimeout = false; + time_t mtime; + struct stat stb; + char buf[MAXPATHLEN]; + + if (tTd(27, 3)) + sm_dprintf("aliaswait(%s:%s)\n", + map->map_class->map_cname, map->map_file); + if (bitset(MF_ALIASWAIT, map->map_mflags)) + return isopen; + map->map_mflags |= MF_ALIASWAIT; + + if (SafeAlias > 0) + { + auto int st; + unsigned int sleeptime = 2; + unsigned int loopcount = 0; /* only used for debugging */ + time_t toolong = curtime() + SafeAlias; + + while (isopen && + map->map_class->map_lookup(map, "@", NULL, &st) == NULL) + { + if (curtime() > toolong) + { + /* we timed out */ + attimeout = true; + break; + } + + /* + ** Close and re-open the alias database in case + ** the one is mv'ed instead of cp'ed in. + */ + + if (tTd(27, 2)) + { + loopcount++; + sm_dprintf("aliaswait: sleeping for %u seconds (loopcount = %u)\n", + sleeptime, loopcount); + } + + map->map_mflags |= MF_CLOSING; + map->map_class->map_close(map); + map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING); + (void) sleep(sleeptime); + sleeptime *= 2; + if (sleeptime > 60) + sleeptime = 60; + isopen = map->map_class->map_open(map, O_RDONLY); + } + } + + /* see if we need to go into auto-rebuild mode */ + if (!bitset(MCF_REBUILDABLE, map->map_class->map_cflags)) + { + if (tTd(27, 3)) + sm_dprintf("aliaswait: not rebuildable\n"); + map->map_mflags &= ~MF_ALIASWAIT; + return isopen; + } + if (stat(map->map_file, &stb) < 0) + { + if (tTd(27, 3)) + sm_dprintf("aliaswait: no source file\n"); + map->map_mflags &= ~MF_ALIASWAIT; + return isopen; + } + mtime = stb.st_mtime; + if (sm_strlcpyn(buf, sizeof(buf), 2, + map->map_file, ext == NULL ? "" : ext) >= sizeof(buf)) + { + if (LogLevel > 3) + sm_syslog(LOG_INFO, NOQID, + "alias database %s%s name too long", + map->map_file, ext == NULL ? "" : ext); + message("alias database %s%s name too long", + map->map_file, ext == NULL ? "" : ext); + } + + if (stat(buf, &stb) < 0 || stb.st_mtime < mtime || attimeout) + { + if (LogLevel > 3) + sm_syslog(LOG_INFO, NOQID, + "alias database %s out of date", buf); + message("Warning: alias database %s out of date", buf); + } + map->map_mflags &= ~MF_ALIASWAIT; + return isopen; +} +/* +** REBUILDALIASES -- rebuild the alias database. +** +** Parameters: +** map -- the database to rebuild. +** automatic -- set if this was automatically generated. +** +** Returns: +** true if successful; false otherwise. +** +** Side Effects: +** Reads the text version of the database, builds the +** DBM or DB version. +*/ + +bool +rebuildaliases(map, automatic) + register MAP *map; + bool automatic; +{ + SM_FILE_T *af; + bool nolock = false; + bool success = false; + long sff = SFF_OPENASROOT|SFF_REGONLY|SFF_NOLOCK; + sigfunc_t oldsigint, oldsigquit; +#ifdef SIGTSTP + sigfunc_t oldsigtstp; +#endif /* SIGTSTP */ + + if (!bitset(MCF_REBUILDABLE, map->map_class->map_cflags)) + return false; + + if (!bitnset(DBS_LINKEDALIASFILEINWRITABLEDIR, DontBlameSendmail)) + sff |= SFF_NOWLINK; + if (!bitnset(DBS_GROUPWRITABLEALIASFILE, DontBlameSendmail)) + sff |= SFF_NOGWFILES; + if (!bitnset(DBS_WORLDWRITABLEALIASFILE, DontBlameSendmail)) + sff |= SFF_NOWWFILES; + + /* try to lock the source file */ + if ((af = safefopen(map->map_file, O_RDWR, 0, sff)) == NULL) + { + struct stat stb; + + if ((errno != EACCES && errno != EROFS) || automatic || + (af = safefopen(map->map_file, O_RDONLY, 0, sff)) == NULL) + { + int saveerr = errno; + + if (tTd(27, 1)) + sm_dprintf("Can't open %s: %s\n", + map->map_file, sm_errstring(saveerr)); + if (!automatic && !bitset(MF_OPTIONAL, map->map_mflags)) + message("newaliases: cannot open %s: %s", + map->map_file, sm_errstring(saveerr)); + errno = 0; + return false; + } + nolock = true; + if (tTd(27, 1) || + fstat(sm_io_getinfo(af, SM_IO_WHAT_FD, NULL), &stb) < 0 || + bitset(S_IWUSR|S_IWGRP|S_IWOTH, stb.st_mode)) + message("warning: cannot lock %s: %s", + map->map_file, sm_errstring(errno)); + } + + /* see if someone else is rebuilding the alias file */ + if (!nolock && + !lockfile(sm_io_getinfo(af, SM_IO_WHAT_FD, NULL), map->map_file, + NULL, LOCK_EX|LOCK_NB)) + { + /* yes, they are -- wait until done */ + message("Alias file %s is locked (maybe being rebuilt)", + map->map_file); + if (OpMode != MD_INITALIAS) + { + /* wait for other rebuild to complete */ + (void) lockfile(sm_io_getinfo(af, SM_IO_WHAT_FD, NULL), + map->map_file, NULL, LOCK_EX); + } + (void) sm_io_close(af, SM_TIME_DEFAULT); + errno = 0; + return false; + } + + oldsigint = sm_signal(SIGINT, SIG_IGN); + oldsigquit = sm_signal(SIGQUIT, SIG_IGN); +#ifdef SIGTSTP + oldsigtstp = sm_signal(SIGTSTP, SIG_IGN); +#endif /* SIGTSTP */ + + if (map->map_class->map_open(map, O_RDWR)) + { + if (LogLevel > 7) + { + sm_syslog(LOG_NOTICE, NOQID, + "alias database %s %srebuilt by %s", + map->map_file, automatic ? "auto" : "", + username()); + } + map->map_mflags |= MF_OPEN|MF_WRITABLE; + map->map_pid = CurrentPid; + readaliases(map, af, !automatic, true); + success = true; + } + else + { + if (tTd(27, 1)) + sm_dprintf("Can't create database for %s: %s\n", + map->map_file, sm_errstring(errno)); + if (!automatic) + syserr("Cannot create database for alias file %s", + map->map_file); + } + + /* close the file, thus releasing locks */ + (void) sm_io_close(af, SM_TIME_DEFAULT); + + /* add distinguished entries and close the database */ + 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); + } + + /* restore the old signals */ + (void) sm_signal(SIGINT, oldsigint); + (void) sm_signal(SIGQUIT, oldsigquit); +#ifdef SIGTSTP + (void) sm_signal(SIGTSTP, oldsigtstp); +#endif /* SIGTSTP */ + return success; +} +/* +** READALIASES -- read and process the alias file. +** +** This routine implements the part of initaliases that occurs +** when we are not going to use the DBM stuff. +** +** Parameters: +** map -- the alias database descriptor. +** af -- file to read the aliases from. +** announcestats -- announce statistics regarding number of +** aliases, longest alias, etc. +** logstats -- lot the same info. +** +** Returns: +** none. +** +** Side Effects: +** Reads aliasfile into the symbol table. +** Optionally, builds the .dir & .pag files. +*/ + +void +readaliases(map, af, announcestats, logstats) + register MAP *map; + SM_FILE_T *af; + bool announcestats; + bool logstats; +{ + register char *p; + char *rhs; + bool skipping; + long naliases, bytes, longest; + ADDRESS al, bl; + char line[BUFSIZ]; + + /* + ** Read and interpret lines + */ + + FileName = map->map_file; + LineNumber = 0; + naliases = bytes = longest = 0; + skipping = false; + while (sm_io_fgets(af, SM_TIME_DEFAULT, line, sizeof(line)) != NULL) + { + int lhssize, rhssize; + int c; + + LineNumber++; + p = strchr(line, '\n'); + + /* XXX what if line="a\\" ? */ + while (p != NULL && p > line && p[-1] == '\\') + { + p--; + if (sm_io_fgets(af, SM_TIME_DEFAULT, p, + SPACELEFT(line, p)) == NULL) + break; + LineNumber++; + p = strchr(p, '\n'); + } + if (p != NULL) + *p = '\0'; + else if (!sm_io_eof(af)) + { + errno = 0; + syserr("554 5.3.0 alias line too long"); + + /* flush to end of line */ + while ((c = sm_io_getc(af, SM_TIME_DEFAULT)) != + SM_IO_EOF && c != '\n') + continue; + + /* skip any continuation lines */ + skipping = true; + continue; + } + switch (line[0]) + { + case '#': + case '\0': + skipping = false; + continue; + + case ' ': + case '\t': + if (!skipping) + syserr("554 5.3.5 Non-continuation line starts with space"); + skipping = true; + continue; + } + skipping = false; + + /* + ** Process the LHS + ** Find the colon separator, and parse the address. + ** It should resolve to a local name -- this will + ** be checked later (we want to optionally do + ** parsing of the RHS first to maximize error + ** detection). + */ + + for (p = line; *p != '\0' && *p != ':' && *p != '\n'; p++) + continue; + if (*p++ != ':') + { + syserr("554 5.3.5 missing colon"); + continue; + } + if (parseaddr(line, &al, RF_COPYALL, ':', NULL, CurEnv, true) + == NULL) + { + syserr("554 5.3.5 %.40s... illegal alias name", line); + continue; + } + + /* + ** Process the RHS. + ** 'al' is the internal form of the LHS address. + ** 'p' points to the text of the RHS. + */ + + while (isascii(*p) && isspace(*p)) + p++; + rhs = p; + for (;;) + { + register char *nlp; + + nlp = &p[strlen(p)]; + if (nlp > p && nlp[-1] == '\n') + *--nlp = '\0'; + + if (CheckAliases) + { + /* do parsing & compression of addresses */ + while (*p != '\0') + { + auto char *delimptr; + + while ((isascii(*p) && isspace(*p)) || + *p == ',') + p++; + if (*p == '\0') + break; + if (parseaddr(p, &bl, RF_COPYNONE, ',', + &delimptr, CurEnv, true) + == NULL) + usrerr("553 5.3.5 %s... bad address", p); + p = delimptr; + } + } + else + { + p = nlp; + } + + /* see if there should be a continuation line */ + c = sm_io_getc(af, SM_TIME_DEFAULT); + if (!sm_io_eof(af)) + (void) sm_io_ungetc(af, SM_TIME_DEFAULT, c); + if (c != ' ' && c != '\t') + break; + + /* read continuation line */ + if (sm_io_fgets(af, SM_TIME_DEFAULT, p, + sizeof(line) - (p-line)) == NULL) + break; + LineNumber++; + + /* check for line overflow */ + if (strchr(p, '\n') == NULL && !sm_io_eof(af)) + { + usrerr("554 5.3.5 alias too long"); + while ((c = sm_io_getc(af, SM_TIME_DEFAULT)) + != SM_IO_EOF && c != '\n') + continue; + skipping = true; + break; + } + } + + if (skipping) + continue; + + if (!bitnset(M_ALIASABLE, al.q_mailer->m_flags)) + { + syserr("554 5.3.5 %s... cannot alias non-local names", + al.q_paddr); + continue; + } + + /* + ** Insert alias into symbol table or database file. + ** + ** Special case pOStmaStER -- always make it lower case. + */ + + if (sm_strcasecmp(al.q_user, "postmaster") == 0) + makelower(al.q_user); + + lhssize = strlen(al.q_user); + rhssize = strlen(rhs); + if (rhssize > 0) + { + /* is RHS empty (just spaces)? */ + p = rhs; + while (isascii(*p) && isspace(*p)) + p++; + } + if (rhssize == 0 || *p == '\0') + { + syserr("554 5.3.5 %.40s... missing value for alias", + line); + + } + else + { + map->map_class->map_store(map, al.q_user, rhs); + + /* statistics */ + naliases++; + bytes += lhssize + rhssize; + if (rhssize > longest) + longest = rhssize; + } + +#if 0 + /* + ** address strings are now stored in the envelope rpool, + ** and therefore cannot be freed. + */ + if (al.q_paddr != NULL) + sm_free(al.q_paddr); /* disabled */ + if (al.q_host != NULL) + sm_free(al.q_host); /* disabled */ + if (al.q_user != NULL) + sm_free(al.q_user); /* disabled */ +#endif /* 0 */ + } + + CurEnv->e_to = NULL; + FileName = NULL; + if (Verbose || announcestats) + message("%s: %ld aliases, longest %ld bytes, %ld bytes total", + map->map_file, naliases, longest, bytes); + if (LogLevel > 7 && logstats) + sm_syslog(LOG_INFO, NOQID, + "%s: %ld aliases, longest %ld bytes, %ld bytes total", + map->map_file, naliases, longest, bytes); +} +/* +** FORWARD -- Try to forward mail +** +** This is similar but not identical to aliasing. +** +** Parameters: +** user -- the name of the user who's mail we would like +** to forward to. It must have been verified -- +** i.e., the q_home field must have been filled +** in. +** sendq -- a pointer to the head of the send queue to +** put this user's aliases in. +** aliaslevel -- the current alias nesting depth. +** e -- the current envelope. +** +** Returns: +** none. +** +** Side Effects: +** New names are added to send queues. +*/ + +void +forward(user, sendq, aliaslevel, e) + ADDRESS *user; + ADDRESS **sendq; + int aliaslevel; + register ENVELOPE *e; +{ + char *pp; + char *ep; + bool got_transient; + + if (tTd(27, 1)) + sm_dprintf("forward(%s)\n", user->q_paddr); + + if (!bitnset(M_HASPWENT, user->q_mailer->m_flags) || + !QS_IS_OK(user->q_state)) + return; + if (ForwardPath != NULL && *ForwardPath == '\0') + return; + if (user->q_home == NULL) + { + syserr("554 5.3.0 forward: no home"); + user->q_home = "/no/such/directory"; + } + + /* good address -- look for .forward file in home */ + macdefine(&e->e_macro, A_PERM, 'z', user->q_home); + macdefine(&e->e_macro, A_PERM, 'u', user->q_user); + macdefine(&e->e_macro, A_PERM, 'h', user->q_host); + if (ForwardPath == NULL) + ForwardPath = newstr("\201z/.forward"); + + got_transient = false; + for (pp = ForwardPath; pp != NULL; pp = ep) + { + int err; + char buf[MAXPATHLEN]; + struct stat st; + + ep = strchr(pp, SEPARATOR); + if (ep != NULL) + *ep = '\0'; + expand(pp, buf, sizeof(buf), e); + if (ep != NULL) + *ep++ = SEPARATOR; + if (buf[0] == '\0') + continue; + if (tTd(27, 3)) + sm_dprintf("forward: trying %s\n", buf); + + err = include(buf, true, user, sendq, aliaslevel, e); + if (err == 0) + break; + else if (transienterror(err)) + { + /* we may have to suspend this message */ + got_transient = true; + if (tTd(27, 2)) + sm_dprintf("forward: transient error on %s\n", + buf); + if (LogLevel > 2) + { + char *curhost = CurHostName; + + CurHostName = NULL; + sm_syslog(LOG_ERR, e->e_id, + "forward %s: transient error: %s", + buf, sm_errstring(err)); + CurHostName = curhost; + } + + } + else + { + switch (err) + { + case ENOENT: + break; + + case E_SM_WWDIR: + case E_SM_GWDIR: + /* check if it even exists */ + if (stat(buf, &st) < 0 && errno == ENOENT) + { + if (bitnset(DBS_DONTWARNFORWARDFILEINUNSAFEDIRPATH, + DontBlameSendmail)) + break; + } + /* FALLTHROUGH */ + +#if _FFR_FORWARD_SYSERR + case E_SM_NOSLINK: + case E_SM_NOHLINK: + case E_SM_REGONLY: + case E_SM_ISEXEC: + case E_SM_WWFILE: + case E_SM_GWFILE: + syserr("forward: %s: %s", buf, sm_errstring(err)); + break; +#endif /* _FFR_FORWARD_SYSERR */ + + default: + if (LogLevel > (RunAsUid == 0 ? 2 : 10)) + sm_syslog(LOG_WARNING, e->e_id, + "forward %s: %s", buf, + sm_errstring(err)); + if (Verbose) + message("forward: %s: %s", + buf, sm_errstring(err)); + break; + } + } + } + if (pp == NULL && got_transient) + { + /* + ** There was no successful .forward open and at least one + ** transient open. We have to defer this address for + ** further delivery. + */ + + message("transient .forward open error: message queued"); + user->q_state = QS_QUEUEUP; + return; + } +} |