This is route.c in view mode; [Download] [Up]
/* @(#)src/route.c 1.2 24 Oct 1990 05:24:17 */ /* * Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll * * See the file COPYING, distributed with smail, for restriction * and warranty information. */ /* * route.c: * Compute route to target hosts and fill in the transport, * next host and next address for a recipient address. * * external functions: route_remote_addrs, verify_remote_addrs, * cache_routers, finish_routers, * match_end_domain, route_driver_finish, * find_router, find_route_driver, * read_router_file, read_method_file */ #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include "defs.h" #include "smail.h" #include "route.h" #include "transport.h" #include "addr.h" #include "dys.h" #include "log.h" #include "exitcodes.h" #include "parse.h" #include "smailconf.h" #ifndef DEPEND # include "extern.h" # include "debug.h" # include "error.h" #endif /* variables exported from this file */ int cached_routers = FALSE; /* TRUE if cache_routers() called */ /* functions local to this file */ static void premunge_remote_addrs(); static void compute_transport(); static void compute_next_addr(); static char *rebuild_work_addr(); static char *router_read_method(); static char *router_driv_function(); /* * route_remote_addrs - route addrs to transport, next host and next_addr */ void route_remote_addrs(in, out, retry, defer, fail) struct addr *in; /* input local-form addrs */ struct addr **out; /* output resolved addrs */ struct addr **retry; /* addr structures to reparse */ struct addr **defer; /* addrs to defer to a later time */ struct addr **fail; /* unresolvable addrs */ { struct router *rp; /* temp for stepping thru routers */ register struct addr *cur; /* temp for stepping through addrs */ struct addr *next; /* next value for cur */ DEBUG(DBG_ROUTE_HI, "route_remote_addrs called\n"); /* first munge the addr structure as required */ premunge_remote_addrs(in); /* * give the complete input list to each router in turn. * router drivers must obey the rules about overriding * of routes produced by previous routers. At the end * this routine will cleanup the addr structures. */ for (rp = routers; rp; rp = rp->succ) { struct addr *new_in; /* addrs to put through next router */ /* look up the router driver by name */ struct route_driver *driver = find_route_driver(rp->driver); if (driver == NULL) { /* * ERR_109 - router driver not found * * DESCRIPTION * A driver name was not in the table of router drivers. * * ACTIONS * Defer all input addresses with configuration errors. * Since it cannot be known if this router would have * overridden previous routers, all input addresses must be * deferred. * * RESOLUTION * The postmaster must check the router configuration * before deliver can be performed. */ insert_addr_list(in, defer, note_error(ERR_CONFERR|ERR_109, xprintf( "router %s: driver %s not found", rp->name, rp->driver))); return; } /* call the driver */ new_in = NULL; (*driver->driver)(rp, in, &new_in, defer, fail); in = new_in; } /* * now that all routers have had their chance we cleanup * the input list to form the output list of resolved * addresses, and note addresses that no router could * route to. */ for (cur = in; cur; cur = next) { next = cur->succ; if (cur->match_count == -1 || (cur->flags & (ADDR_PARTLOCAL|ADDR_FULLMATCH)) == ADDR_PARTLOCAL) { /* no router could match the target, fail address */ exitvalue = EX_NOHOST; /* set exit status */ /* * ERR_101 - unknown host * * DESCRIPTION * The target in this addr structure was not matched by any * of the routers. * * ACTIONS * A message is sent to the owner of the address, or to the * sender if the address has no owner. * * RESOLUTION * The address owner or sender should determine the correct * domain name. If the owner or sender believes the * message was in error, he or she should send mail to the * postmaster. */ cur->error = note_error(ERR_NSOWNER|ERR_101, "unknown host"); cur->succ = *fail; *fail = cur; continue; } if (cur->next_host == NULL) { /* routed to local host, reparse */ (void) strcpy(cur->work_addr, cur->remainder); cur->remainder = NULL; cur->succ = *retry; *retry = cur; continue; } if (cur->transport == NULL) { /* router did not compute a transport, we must */ compute_transport(cur); if (cur->error && (cur->error->info & ERR_CONFERR)) { /* defer configuration errors */ cur->succ = *defer; *defer = cur; continue; } } if (cur->next_addr == NULL) { /* apply rules from Section 3.3 for next_addr */ compute_next_addr(cur); } if (cur->error) { /* got an error, fail the address */ cur->succ = *fail; *fail = cur; } else { /* link into the output queue if things went okay */ if (cur->next_host) { DEBUG3(DBG_ROUTE_LO, " routed %s --> %s at %s\n", cur->in_addr, cur->next_addr, cur->next_host); } else { DEBUG2(DBG_ROUTE_LO, " routed %s --> %s on the local host\n", cur->in_addr, cur->next_addr); } cur->succ = *out; *out = cur; } } } /* * verify_remote_addrs - perform quick verify of remote addresses * * form a list of okay (verified) addrs, plus deferred (not currently * determinable) addrs and failed (not deliverable) addrs. */ void verify_remote_addrs(in, okay, defer, fail) struct addr *in; /* input remote addr list */ struct addr **okay; /* output list of verified addrs */ struct addr **defer; /* temporariliy unverifiable addrs */ struct addr **fail; /* unverified addrs */ { struct router *rp; /* temp for stepping thru routers */ register struct addr *cur; /* temp for stepping through addrs */ DEBUG(DBG_ROUTE_HI, "verify_remote_addrs called\n"); /* first munge the addr structure as required */ premunge_remote_addrs(in); /* * give the complete input list to each router in turn. * router drivers must obey the rules about overriding * of routes produced by previous routers. At the end * this routine will cleanup the addr structures. */ for (rp = routers; in && rp; rp = rp->succ) { struct addr *retry; /* addrs to put through next router */ /* look up the router driver by name */ struct route_driver *driver = find_route_driver(rp->driver); if (driver == NULL) { /* * ERR_109 - router driver not found * * DESCRIPTION * A driver name was not in the table of router drivers. * * ACTIONS * Defer all input addresses with configuration errors. * Since it cannot be known if this router would have * overridden previous routers, all input addresses must be * deferred. * * RESOLUTION * The postmaster must check the router configuration * before delivery can be performed. */ insert_addr_list(in, defer, note_error(ERR_CONFERR|ERR_109, xprintf( "router %s: driver %s not found", rp->name, rp->driver))); return; } /* call the driver */ retry = NULL; (*driver->verify)(rp, in, &retry, okay, defer, fail); in = retry; } /* * some cleanup is required on the the addr list */ for (cur = in; cur; cur = cur->succ) { if (cur->flags&ADDR_PUTDOT) { /* a dot was removed from the end of the target, put it back */ cur->target[strlen(cur->target)] = '.'; } else if (cur->flags&ADDR_MOVEDOT) { /* move a dot back to the end of the target, from the front */ (void) strcpy(cur->target, cur->target + 1); cur->target[strlen(cur->target)] = '.'; } } } /* * cache_routers - call cache entrypoints for all routers * * cache information used by router drivers. This can be called when * it is determined that there will be an attempt to deliver more than * one mail message, to increase the overall efficiency of the mailer. * * Daemons can call this periodically to recache stale data. */ void cache_routers() { struct router *rp; /* temp for stepping thru routers */ struct route_driver *driver; for (rp = routers; rp; rp = rp->succ) { driver = find_route_driver(rp->driver); if (driver && driver->cache) { (*driver->cache)(rp); } } cached_routers = TRUE; } #ifdef notyet /* * finish_routers - free resources used by all routers * * free information that was cached by routers or used by routers in * the process of routing. Routers can cache data for efficiency, or * can maintain state between invocations. This function is called * when routers will no longer be needed, allowing routers to free any * resources that they were using that will no longer be needed. For * example, it is a good idea for routers to close any files that they * opened, as file descriptors are a precious resource in some * machines. */ void finish_routers() { struct router *rp; /* temp for stepping thru routers */ struct route_driver *driver; for (rp = routers; rp; rp = rp->succ) { driver = find_route_driver(rp->driver); if (driver && driver->finish) { (*driver->finish)(rp); } } cached_routers = FALSE; } #endif /* * premunge_remote_addrs - pre-routing munging on remote addr structures * * do a premunge on the target, by taking a dot at the end and * putting it at the front (if one is not already at the front). * Also, initialize the flags field, to remove any extraneously * set flags. */ static void premunge_remote_addrs(list) struct addr *list; /* list of remote addrs to premunge */ { register struct addr *cur; /* current address being processed */ for (cur = list; cur; cur = cur->succ) { register int len = strlen(cur->target); cur->flags &= ~(ADDR_PUTDOT | ADDR_MOVEDOT | ADDR_ERROR | ADDR_FINISHED | ADDR_FULLMATCH | ADDR_NOTUSER | ADDR_ISUSER); if (cur->target[len-1] == '.') { if (cur->target[0] == '.') { cur->target[len-1] = '\0'; cur->flags |= ADDR_PUTDOT; } else { /* have to move the target so that a dot can be inserted */ register char *p = cur->target; while (--len) { p[len] = p[len - 1]; } p[0] = '.'; cur->flags |= ADDR_MOVEDOT; } } } } /* * match_end_domain - try to match one of a list of domains against a target * * given a list of domains separated by colons, determine if the given * target ends in one of those domains. If so, return a pointer to the * `.' that precedes the domain that matched, else return NULL. The * list is scanned from left to right, with the first match returned. */ char * match_end_domain(domains, target) char *domains; /* colon separated list of domains */ char *target; /* target to test against */ { register char *cur; /* current domain being checked */ int len = strlen(target); /* length of target */ for (cur = strcolon(domains); cur; cur = strcolon((char *)NULL)) { register int domlen = strlen(cur); if (len > domlen && target[len - domlen - 1] == '.' && EQIC(target + len-domlen, cur)) { return target + len-domlen - 1; } } /* did not end in one of the domains */ return NULL; } /* * route_driver_finish - fill in addr structures for a route driver * * route drivers may call this routine for each addr structure they * have computed a route to. This routine completes the addr structure * if it is determined that the computed route has precedence over any * previously computed route. In the case that the computed route has * precedence over future routers, the ADDR_FINISHED flag is set in * the addr.flags struct element. */ void route_driver_finish(rp, addr, match_count, next_host, route, transport) struct router *rp; /* the calling router */ struct addr *addr; /* addr structure in question */ int match_count; /* target match count */ char *next_host; /* computed next_host value */ char *route; /* computed route value */ struct transport *transport; /* optional transport from driver */ { if (addr->flags&ADDR_FINISHED) { /* * a router erroneously found a new route for a finished addr, * ignore it */ return; } if (match_count > addr->match_count) { if (strlen(addr->target) == match_count || (addr->target[0] == '.' && strlen(addr->target) == match_count - 1)) { addr->flags |= ADDR_FINISHED|ADDR_FULLMATCH; } else { if (next_host == NULL) { /* partial match to local host not considered a match */ return; } if (rp->flags&USE_ALWAYS) { addr->flags |= ADDR_FINISHED; } } /* this route has precedence over previous routes */ addr->router = rp; addr->match_count = match_count; if (addr->next_host) { xfree(addr->next_host); } if (next_host) { addr->next_host = COPY_STRING(next_host); } else { addr->next_host = NULL; } if (addr->route) { xfree(addr->route); } if (route) { addr->route = COPY_STRING(route); } else { addr->route = NULL; } addr->transport = transport; if (addr->next_addr) { /* * cleanup after a previous router that computed the * next_addr itself */ xfree(addr->next_addr); addr->next_addr = NULL; } DEBUG3(DBG_ROUTE_LO, "%s: %s matched by %s:\n", addr->in_addr, addr->target, addr->router->name); } } /* * compute_next_addr - compute the next_addr for a routed recipient address */ static void compute_next_addr(addr) struct addr *addr; /* address to be completed */ { if (addr->error) { /* ignore the addr if it already has a pending error condition */ return; } if (addr->next_addr) { /* free a previously allocated next_addr */ xfree(addr->next_addr); addr->next_addr = NULL; } if (addr->flags&ADDR_PUTDOT) { /* a dot was removed from the end of the target, put it back */ addr->target[strlen(addr->target)] = '.'; } else if (addr->flags&ADDR_MOVEDOT) { /* move a dot back to the end of the target, from the front */ (void) strcpy(addr->target, addr->target+1); addr->target[strlen(addr->target)] = '.'; } /* * TODO: perhaps for STRICT_TPORT we should build an ARPA route-addr */ if ((addr->transport->flags&UUCP_ONLY) || addr->route && (addr->flags&ADDR_FULLMATCH) == 0 || addr->route != NULL && index(addr->route, '!')) { /* we need to construct a !-route to accomplish delivery */ char *error; char *uucp_route = build_partial_uucp_route(addr->remainder, &error); if (uucp_route == NULL) { /* build_partial_uucp_route failed, fail the address */ /* * ERR_108 - error building path * * DESCRIPTION * build_partial_uucp_route() returned an error while * attempting to build a UUCP-path from the remainder in * the address. The specific error was returned in * `error'. * * ACTIONS * Notify the sender or the owner of the address of the * problem. * * RESOLUTION * The sender or owner should correct the supplied address * to use acceptible addressing forms. */ addr->error = note_error(ERR_NSOWNER|ERR_108, xprintf("error building path: %s", error)); exitvalue = EX_DATAERR; /* set the exit status */ return; } if (addr->route == NULL) { if (addr->flags&ADDR_FULLMATCH) { addr->next_addr = uucp_route; } else { /* not a full match, so we need to add in the target */ addr->next_addr = xmalloc((unsigned)(strlen(uucp_route) + strlen(addr->target) + 2)); (void) sprintf(addr->next_addr, "%s!%s", addr->target, uucp_route); xfree(uucp_route); } } else { /* we need to prepend the route to build the next_addr */ addr->next_addr = xmalloc((unsigned)(strlen(addr->route) + strlen(uucp_route) + ((addr->flags&ADDR_FULLMATCH)? 2: strlen(addr->target) + 3))); if (addr->flags&ADDR_FULLMATCH) { (void) sprintf(addr->next_addr, "%s!%s", addr->route, uucp_route); xfree(uucp_route); } else { /* * if we didn't match target completely we need to * append it to the route, so that, presumably, a * gateway will route it. */ (void) sprintf(addr->next_addr, "%s!%s!%s", addr->route, addr->target, uucp_route); xfree(uucp_route); } } } else if (addr->route) { char *error; switch (parse_address(addr->remainder, (char **)NULL, &error)) { case UUCP_ROUTE: addr->next_addr = xprintf("%s!%s", addr->route, addr->remainder); break; case RFC_ROUTE: case RFC_ENDROUTE: addr->next_addr = xprintf("@%s,%s", addr->route, addr->remainder); break; case MAILBOX: addr->next_addr = xprintf("@%s:%s", addr->route, addr->remainder); break; default: addr->next_addr = xprintf("%s@%s", addr->remainder, addr->route); break; } } else { if (addr->flags&ADDR_FULLMATCH) { addr->next_addr = COPY_STRING(addr->remainder); } else { addr->next_addr = rebuild_work_addr((int)(addr->flags&ADDR_FORM_MASK), addr->target, addr->remainder); } } return; } /* * rebuild_work_addr - recompute work_addr from target and remainder * * we need to reconstruct the work_addr used to form the * target and remainder. Remember that work_addr is * modified in computing target and remainder. */ static char * rebuild_work_addr(form, target, remainder) int form; /* method used to parse address */ char *target; /* target from a work_addr */ char *remainder; /* remainder from the same work_addr */ { register char *work_addr; register int len = strlen(target) + strlen(remainder); switch(form) { case RFC_ROUTE: /* "@target,remainder" */ work_addr = xmalloc(len + sizeof("@,")); (void) sprintf(work_addr, "@%s,%s", target, remainder); break; case RFC_ENDROUTE: /* "@target:remainder" '@' should still be there */ work_addr = xmalloc(len + sizeof("@:")); (void) sprintf(work_addr, "@%s:%s", target, remainder); break; case MAILBOX: /* "remainder@target" */ work_addr = xmalloc(len + sizeof("@")); (void) sprintf(work_addr, "%s@%s", remainder, target); break; case UUCP_ROUTE: /* "target!remainder" */ work_addr = xmalloc(len + sizeof("!")); (void) sprintf(work_addr, "%s!%s", target, remainder); break; case BERKENET: /* "target:remainder" */ work_addr = xmalloc(len + sizeof(":")); (void) sprintf(work_addr, "%s:%s", target, remainder); break; case DECNET: /* "target::remainder" */ work_addr = xmalloc(len + sizeof("::")); (void) sprintf(work_addr, "%s::%s", target, remainder); break; case PCT_MAILBOX: /* "remainder%target" */ work_addr = xmalloc(len + sizeof("%")); (void) sprintf(work_addr, "%s%%%s", remainder, target); break; default: /* internal error? */ write_log(LOG_PANIC, "internal error: rebuild_work_addr: unknown form: target=%s, remainder=%s, in_addr=%s", target, remainder, work_addr); /* use @host:addr, just for fun */ work_addr = xmalloc(len + sizeof("@:")); (void) sprintf(work_addr, "@%s:%s", target, remainder); break; } return work_addr; } /* * compute_transport - compute the transport from any available defaults * * some router drivers do not assign transports themselves but rely * on defaults in the router structure. These defaults can either be * a single default transport or a table of transports associated with * host names, where the special name `*' is a catchall. */ static void compute_transport(addr) struct addr *addr; { struct router *rp = addr->router; DEBUG2(DBG_ROUTE_HI, "compute_transport called: host=%s, router=%s\n", addr->next_host, rp->name); /* no transport yet assigned, look around for defaults */ if (rp->method) { /* there is a "method", table of possible transports */ struct method *mp; mp = rp->method; while (mp->host) { if (EQIC(addr->next_host, mp->host) || EQ(mp->host, "*")) { /* found the right transport, use it */ addr->transport = find_transport(mp->transport); if (addr->transport == NULL) { /* configuration error, the transport does not exist */ /* * ERR_102 - method transport not found * * DESCRIPTION * The transport to be used for the method was not * found in the table of transports. * * ACTIONS * Defer the message as a configuration error. * * RESOLUTION * The postmaster needs to correct the eror in the * method file. */ addr->error = note_error(ERR_CONFERR|ERR_102, xprintf( "router %s: method transport %s not found for host %s", rp->name, mp->transport, mp->host)); return; } DEBUG2(DBG_ROUTE_MID, "use transport %s for %s\n", addr->transport->name, addr->in_addr); return; } mp++; } } if (rp->default_transport) { /* there is a default, use it */ addr->transport = find_transport(rp->default_transport); if (addr->transport == NULL) { /* * ERR_103 - default transport not found * * DESCRIPTION * The default transport for the matching router is not in the * list of transports. * * ACTIONS * Defer the message as a configuration error. * * RESOLUTION * The postmaster should correct the router file. */ addr->error = note_error(ERR_CONFERR|ERR_103, xprintf( "router %s: default transport %s not found", rp->name, rp->default_transport)); return; } DEBUG2(DBG_ROUTE_MID, "use default transport %s for %s\n", addr->transport->name, addr->in_addr); return; } else { /* * ERR_110 - no transport for router * * DESCRIPTION * No method or default transport was given for the router that * produced this address. * * ACTIONS * Defer the address as a configuration error. * * RESOLUTION * The postmaster must check the router configuration before * delivery to the address can be performed. */ addr->error = note_error(ERR_CONFERR|ERR_110, xprintf("router %s: no transport found", rp->name)); } } /* * find_router - given a router's name, return the router structure * * return NULL if no router of that name exists. */ struct router * find_router(name) register char *name; /* search key */ { register struct router *rp; /* temp for stepping thru routers */ /* loop through all the routers */ for (rp = routers; rp; rp = rp->succ) { if (EQ(rp->name, name)) { /* found the router in question */ return rp; } } return NULL; /* router not found */ } /* * find_route_driver - given a driver's name, return the driver structure * * return NULL if driver does not exist. */ struct route_driver * find_route_driver(name) register char *name; /* search key */ { register struct route_driver *rdp; /* pointer to table of drivers */ for (rdp = route_drivers; rdp->name; rdp++) { if (EQ(rdp->name, name)) { return rdp; /* found the driver */ } } return NULL; /* driver not found */ } /* * read_router_file - read router file * * read the router file and build a router list describing the * entries. Return an error message or NULL. */ char * read_router_file() { FILE *f; /* open router file */ char *error; /* error from read_standard_file() */ struct stat statbuf; static struct attr_table router_generic[] = { { "driver", t_string, NULL, NULL, OFFSET(router, driver) }, { "transport", t_string, NULL, NULL, OFFSET(router, default_transport) }, { "method", t_proc, NULL, (tup *)router_read_method, 0 }, { "always", t_boolean, NULL, NULL, USE_ALWAYS }, }; struct attr_table *end_router_generic = ENDTABLE(router_generic); static struct router router_template = { NULL, /* name */ "pathalias", /* driver, a reasonable default */ NULL, /* succ will be assigned */ 0, /* flags */ NULL, /* method */ NULL, /* default_transport */ NULL, /* private */ }; /* * try to open router file, note file stat if possible */ if (router_file == NULL || EQ(router_file, "-")) { return NULL; } f = fopen(router_file, "r"); if (f == NULL) { if (require_configs) { return xprintf("%s: %s", router_file, strerrno()); } add_config_stat(router_file, (struct stat *)NULL); return NULL; } (void)fstat(fileno(f), &statbuf); add_config_stat(router_file, &statbuf); /* call read_standard_file to do the real work */ error = read_standard_file(f, (char *)&router_template, sizeof(struct router), OFFSET(router, name), OFFSET(router, flags), OFFSET(router, succ), router_generic, end_router_generic, router_driv_function, (char **)&routers); /* finish up */ (void) fclose(f); /* return any error message */ if (error) { return xprintf("%s: %s", router_file, error); } return NULL; } /* * router_read_method - handle method attribute for routers * * the method attribute specifies a file which contains a method table * associating hosts with transports. Use of this attribute causes * the table to be read and turned into an in-core method table. */ static char * router_read_method(struct_p, attr) char *struct_p; /* passed router structure */ struct attribute *attr; /* parsed attribute */ { struct router *rp = (struct router *)struct_p; char *error; if (attr->value == off) { rp->method = NULL; } else if (attr->value == on) { return xprintf("%s: boolean form for non-boolean attribute", attr->name); } else { int free_method_fn = FALSE; char *method_fn = attr->value; if (method_fn[0] != '/') { if (method_dir == NULL) { return xprintf("%s: %s not absolute and no method_dir", attr->name, method_fn); } method_fn = xmalloc(strlen(method_dir) + strlen(method_fn) + sizeof("/")); (void) sprintf(method_fn, "%s/%s", method_dir, attr->value); free_method_fn = TRUE; } rp->method = read_method_file(method_fn, &error); if (rp->method == NULL) { return xprintf("%s %s: %s", attr->name, attr->value, error); } if (free_method_fn) { xfree(method_fn); } } /* everything went okay */ return NULL; } /* * read_method_file - read a method file and return an in-core method table * * return NULL if the method file could not be opened. */ struct method * read_method_file(fn, error) char *fn; /* name of method file */ char **error; /* store error message here */ { FILE *f = fopen(fn, "r"); struct method *methods; /* method table */ int ct = 0; /* count of methods */ char *entry; /* text of file entry */ struct stat statbuf; if (f == NULL) { *error = xprintf("open failed: %s", strerrno()); return NULL; } (void)fstat(fileno(f), &statbuf); add_config_stat(fn, &statbuf); /* allocate space for at least the ending record */ methods = (struct method *)xmalloc(sizeof(*methods)); /* loop and read all of the table entries in the method file */ while (entry = read_entry(f)) { char *error; struct attribute *new; new = parse_table(entry, &error); if (new == NULL) { return NULL; } ct++; methods = (struct method *) xrealloc((char *)methods, (ct + 1)*sizeof(*methods)); methods[ct - 1].host = new->name; methods[ct - 1].transport = new->value; } /* zero out the ending record */ methods[ct].host = NULL; methods[ct].transport = NULL; return methods; } static char * router_driv_function(struct_p, driver_attrs) char *struct_p; /* passed router structure */ struct attribute *driver_attrs; /* driver-specific attributes */ { struct router *rp = (struct router *)struct_p; struct route_driver *drv; if (rp->driver == NULL) { return xprintf("router %s: no driver attribute", rp->name); } drv = find_route_driver(rp->driver); if (drv == NULL) { return xprintf("router %s: unknown driver: %s", rp->name, rp->driver); } if (drv->builder) { return (*drv->builder)(rp, driver_attrs); } return NULL; }
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.