/*
#ident "@(#)smail/src:RELEASE-3_2_0_121:resolve.c,v 1.47 2005/06/26 21:32:34 woods Exp"
*/
/*
* Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
* Copyright (C) 1992 Ronald S. Karr
*
* See the file COPYING, distributed with smail, for restriction
* and warranty information.
*/
/*
* resolve.c:
* resolve addresses to completed addr structures with transports.
*
* external functions: resolve_addr_list, islocalhost
*/
#include "defs.h"
#include <sys/types.h>
#include <stdio.h>
#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
# include <stdlib.h>
# endif
#endif
#ifdef HAVE_STRING_H
# if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H)
# include <memory.h>
# endif
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#ifdef __STDC__
# include <stdarg.h>
#else
# include <varargs.h>
#endif
#if defined(HAVE_UNISTD_H)
# include <unistd.h>
#endif
#include "smail.h"
#include "smailsock.h"
#include "main.h"
#include "parse.h"
#include "addr.h"
#include "hash.h"
#include "direct.h"
#include "route.h"
#include "alloc.h"
#include "list.h"
#include "smailstring.h"
#include "dys.h"
#include "log.h"
#include "transport.h"
#include "exitcodes.h"
#include "extern.h"
#include "debug.h"
#include "error.h"
#include "smailport.h"
/* exported variables */
struct hash_table *hit_table = NULL; /* table to recognize address hits */
struct block *hit_table_block = NULL; /* block() for bmalloc() et al */
/*
* resolve_addr_list - resolve addresses to transports and next hosts
*
* given a list of user-supplied addresses on input, produce resolved
* and unresolvable addresses on output.
*
* inputs:
* in - the list of input address structures
*
* hash_addrs - set to TRUE to ignore duplicate addresses
*
* outputs:
* out - the list of completely resolved address structures.
* transport, next_host and next_addr will be properly
* filled in for all of these structures.
* defer - a list of temporarily unresolvable address structures.
* an error structure is stored in the error element.
* These addresses should be retried at a later time. If
* ERR_CONFERR is set in the error->info element, the
* problem is a configuration error.
* fail - a list of unresolvable address structures. An error
* structure is stored in the error element. If
* ERR_NSENDER is set, a note is returned to the sender.
* If ERR_NPOSTMASTER is set, then a note is mailed to
* the postmaster. If ERR_NSOWNER is set then a note is
* sent to an owner for an address, or to the sender if
* the address has no owner. If ERR_NPOWNER is set then
* a note is sent to an owner or to the postmaster if the
* address has no owner.
*/
void
resolve_addr_list(in, out, defer, fail, hash_addrs)
struct addr *in; /* the address list to resolve */
struct addr **out; /* produced addr list w/transports */
struct addr **defer; /* addrs to defer to a later time */
struct addr **fail; /* unresolvable addrs */
int hash_addrs; /* TRUE to prevent duplicate addrs */
{
struct addr *cur; /* current address being processed */
struct addr *local; /* addrs that parsed local */
struct addr *remote; /* addrs that parsed remote */
struct addr *next; /* next value for cur */
remote = NULL;
local = NULL;
DEBUG1(DBG_RESOLVE_HI, "resolve_addr_list(): called [will%s check for dups]\n", hash_addrs ? "" : " NOT");
/*
* Resolve all addresses in our input queue.
*
* Each step through the loop advances the progress
* in resolving all addresses to a transport.
*
* The loop is done when nothing remains to be processed.
*
* As an optimization, remote form processing is not
* done unless/until no local processing was required.
*/
while (in || remote) {
/*
* split the input list into local and remote forms.
*/
for (cur = in, in = NULL; cur; cur = next) {
int form; /* address form from parse_address() */
DEBUG1(DBG_RESOLVE_HI, "resolve_addr_list working on <%v>.\n", cur->work_addr);
next = cur->succ;
/* First, if allowed, we try adding the current address to the
* master hash table to ensure we don't do extra work checking
* obviously identical addresses. Note that since we don't
* re-parse anything that looks anything like a remote address we
* don't protect from multiple deliveries to different addresses
* that really do resolve to the same adress. We leave final
* duplicate stripping to a final scan of the out list.
*/
if (cur->flags & ADDR_DONTHASH) {
DEBUG1(DBG_RESOLVE_MID, "<%v>: not checking for dups because of ADDR_DONTHASH.\n", cur->work_addr);
} else if (hash_addrs && add_to_hash(cur->work_addr, (char *) NULL, (size_t) 0, hit_table) == ALREADY_HASHED) {
DEBUG1(DBG_RESOLVE_LO, "<%v>: has already been seen -- skipping.\n", cur->work_addr);
continue;
}
form = parse_address(cur->work_addr, &cur->target,
&cur->remainder, &cur->parseflags);
switch (form) {
case FAIL:
/*
* ERR_111 - address parse error
*
* DESCRIPTION
* parse_address() encountered an error while parsing
* the work_addr for this address. The error is stored
* in cur->remainder.
*
* ACTIONS
* A message about the parse error should be returned
* to the owner of the address or to the sender.
*
* RESOLUTION
* The owner or sender should correct the address and
* resubmit the message.
*/
cur->error = note_error(ERR_NSOWNER|ERR_111, cur->remainder);
cur->flags &= ~ADDR_FORM_MASK;
cur->succ = *fail;
*fail = cur;
continue;
case LOCAL:
/*
* first we strip any quotes, etc., i.e. to turn any
* quoted-string into a raw C string that can be matched by a
* director.
*/
(void) strip(cur->remainder);
if (!cur->local_name) {
cur->local_name = COPY_STRING(visible_name);
}
cur->succ = local;
local = cur;
DEBUG1(DBG_RESOLVE_HI, "moved local form <%v> to be directed.\n", cur->remainder);
break;
default: /* anything else is a remote-form address */
/* determine if the target host is actually a local host */
if (islocalhost(cur->target)) {
DEBUG2(DBG_RESOLVE_LO, "target domain %s is a local host, will reparse remainder (%v).\n", cur->target, cur->remainder);
/* remember the local name */
if (cur->local_name) {
xfree(cur->local_name);
}
cur->local_name = COPY_STRING(cur->target);
/* it is a local host, but save the name for virtual host processing */
#if 0 /* XXX is this safe to do here? */
if (cur->work_addr) {
xfree(cur->work_addr);
}
#endif
cur->work_addr = COPY_STRING(cur->remainder);
next = cur; /* erni: must parse again, remainder could be remote */
continue;
}
cur->flags &= ~(ADDR_FORM_MASK);
cur->flags |= form;
cur->succ = remote;
remote = cur;
DEBUG1(DBG_RESOLVE_HI, "moved target %s to be routed.\n", cur->target);
break;
}
}
/*
* either process local or remote addresses.
*/
if (local) {
direct_local_addrs(local, out, &in, defer, fail);
local = NULL;
} else {
route_remote_addrs(remote, out, &in, defer, fail);
remote = NULL;
}
}
if (hash_addrs) {
struct addr *prev;
struct hash_table *hit_out_table; /* table to recognize address hits */
struct block *hit_out_table_block; /* block for bmalloc() et al */
char *hashval;
hit_out_table_block = malloc_block();
hit_out_table = new_hash_table(hit_table_len,
hit_out_table_block,
HASH_DEFAULT);
/*
* Re-scan the out list for duplicates. Note that a duplicate is
* one that has the same destination *and* the same transport.
*/
for (cur = *out, prev = NULL; cur; cur = next) {
next = cur->succ;
hashval = xprintf("<%v> at %s via %s",
cur->next_addr,
cur->next_host ? cur->next_host : "(localhost)",
cur->transport->name);
if (!(cur->flags & ADDR_DONTHASH) && add_to_hash(hashval, (char *) NULL, (size_t) 0, hit_out_table) == ALREADY_HASHED) {
DEBUG1(DBG_RESOLVE_LO, "%s: has already been seen -- skipping during rescan.\n", hashval);
/* we always get around the loop at least once without entering
* this side of the 'if', so 'prev' will never be NULL
*/
prev->succ = next;
cur->succ = NULL;
/* XXX should probably garbage collect the duplicate */
} else {
prev = cur;
}
xfree(hashval);
}
/* free everything */
free_block(hit_out_table_block);
}
return;
}
/*
* islocalhost - determine if the given target name is a local one
*
* Given the currently known names for the local system, determine if the given
* target name, or if a literal address target matches the address the message
* arrived on, or if any name resolved from an IP literal matches one of these
* known names.
*
* return TRUE or FALSE.
*
* Note: a target domain which looks like a literal IP address (i.e. is
* surrounded by square brackets "[]"), but which doesn't match smtp_local_addr
* and which the inet library claims is not a valid IP address, will be treated
* as a non-local target domain. This should be OK because we test IP literals
* last.
*
* WARNING: If no hostname for a literal IP address (i.e. those found via
* gethostbyaddr()) matches any local domain name, and assuming the literal
* addrss was also not the address the connection came in on (i.e. did not
* match smtp_local_addr) then it will be treated as a non-local target even
* though smail may be listening on that address on a local interface. However
* this should be sufficient for the most critical uses of literal IP
* addresses. Going any further (i.e. discovering and remembering all local
* addresses smail was listening on at the time the message arrived) would be
* very difficult to do portably. We make one exception for a literal IP
* address with the value INADDR_LOOPBACK and accept it as a local target even
* if the smtp daemon is not accepting connections on the loopback interface.
*/
int
islocalhost(target)
register char *target; /* name to match */
{
#ifdef HAVE_BSD_NETWORKING
int len;
#endif /* HAVE_BSD_NETWORKING */
if ((uucp_name && EQIC(target, uucp_name)) ||
(hostnames && is_string_in_list(target, hostnames)) ||
(more_hostnames && is_string_in_list(target, more_hostnames)))
{
DEBUG1(DBG_RESOLVE_HI, "islocalhost: target %s is a local host\n", target);
return TRUE;
}
#ifdef HAVE_BSD_NETWORKING
/*
* Check if this looks like a [xxx.xxx.xxx.xxx] address...
*/
len = strlen(target);
if (target[0] == '[' && target[len-1] == ']') {
char *hostip;
struct in_addr inet_s;
struct hostent *hostentp;
hostip = COPY_STRING(target+1); /* make a copy so we don't have to modify target. */
len = strlen(hostip);
hostip[len-1] = '\0';
/*
* Check whether or not it appears to be the address we answered the
* connection on, which is the most common case for anyone doing
* testing via SMTP over TCP, but which may not work for messages
* arriving over some other non-IP connection, such as on the
* command-line (for those the gethostbyaddr() lookup below will have
* to work). This will also help avoid a DNS lookup for the most
* common cases too.
*/
if (smtp_local_addr && EQ(hostip, smtp_local_addr)) {
DEBUG1(DBG_RESOLVE_HI, "islocalhost: address literal target %s matches smtp_local_addr\n", target);
xfree(hostip);
return TRUE;
}
/*
* do a reverse query to get the hostname and then do a recursive check
* on the hostname(s)...
*/
if (!inet_aton(hostip, &inet_s)) {
DEBUG1(DBG_RESOLVE_HI, "islocalhost: address literal target %s is not a valid IP address\n", target);
xfree(hostip);
return FALSE; /* not a valid IP number.... */
}
xfree(hostip); /* done with this */
/* XXX should we really allow non-local clients to address local users by our own loobpack ? */
if (inet_s.s_addr == htonl(INADDR_LOOPBACK)) {
return TRUE; /* obviously it must be us.... */
}
if ((hostentp = gethostbyaddr((char *) &(inet_s.s_addr), (socklen_t) sizeof(inet_s.s_addr), AF_INET))) {
DEBUG2(DBG_RESOLVE_HI, "islocalhost: address literal target %s resolves to '%v'\n", target, hostentp->h_name);
if (islocalhost(hostentp->h_name)) {
return TRUE;
} else {
char *p;
int i;
for (i = 0; (p = (hostentp->h_aliases)[i]); i++) {
DEBUG2(DBG_RESOLVE_HI, "islocalhost: address literal target %s has alias '%s'\n", target, p);
if (islocalhost(p)) {
return TRUE;
}
}
}
}
}
#endif /* HAVE_BSD_NETWORKING */
DEBUG1(DBG_RESOLVE_HI, "islocalhost: target %s is a not local\n", target);
return FALSE;
}
/*
* Local Variables:
* c-file-style: "smail"
* End:
*/
syntax highlighted by Code2HTML, v. 0.9.1