Appendix A. Sample Router Configuration Scripts
Text to be inserted here.
The following are examples of the router configuration scripts SMTP+UUCP.cf, crossbar.cf, process.cf, and
rrouter.cf.
ZMailer 2 configuration file for a generic SMTP host (with UUCP links)
ZCONFIG=@ZMAILERCFGFILE@
. $ZCONFIG
PATH=.:$MAILSHARE/cf:$MAILBIN/bin ; export PATH
PS1=z$PS1
|
Configure error logging (squirrel)
squirrel -breakin
squirrel badheader
|
Domains with these toplevels will not be canonicalized via DNS lookup. This list is from ISOC table of
16-April-95.
The quoted string {\tt "ad...zw"} should be on one line in the actual SMTP+UUCP.cf file.
toplevels="ad ae af ag ai al am an ao aq ar as at au aw az ba bb bd
be bf bg bh bi bj bm bn bo br bs bt bv bw by bz ca cc cf
cg ch ci ck cl cm cn co com cr cu cv cx cy cz de dj dk dm
do dz ec edu ee eg eh es et fi fj fk fm fo fr ga gb gd ge
gf gh gi gl gm gn gov gp gq gr gt gu gw gy hk hm hn hr ht
hu id ie il in int io iq ir is it jm jo jp ke kg kh ki km
kn kp kr kw ky kz la lb lc li lk lr ls lt lu lv ly ma mc
md mg mh mil ml mm mn mo mp mq mr ms mt mu mv mw mx my mz
na nc ne net nf ng ni nl no np nr nt nu nz om org pa pe pf
pg ph pk pl pm pn pr pt pw py qa re ro ru rw sa sb sc sd se
sg sh si sj sk sl sm sn so sr st sv sy sz tc td tf tg th tj
tk tm tn to tp tr tt tv tw tz ua ug uk um us uy uz va vc ve
vg vi vn vu wf ws ye yu za zm zr zw"
|
The transport preference order
protocols='routes smtp uucp'
|
Will the MAILVAR/lists/listname show out sender identity as either: owner-listname, or: listname-owner?
if true ; then # Change to "false" to get "pre-owner" mode
preowner=""
postowner="-owner"
else
preowner="owner-"
postowner=""
fi
|
Does our ``local'' channel accept domain (@) at the user part? ZMailer's mailbox does accept. If you use something
else, and it doesn't accept, comment this away.
We may want .forward and mailing list files to be private, i.e., we ignore the current privileges when checking the
privileges of such files. Don't add `include' to this list, since anyone can :include: any file.
private='.forward maillist'
|
Set up dependency checking.
. consist.cf
require siteinfo router crossbar process server
|
The following are standard setup files and must be loaded in this order
. standard.cf
. trusted.cf
|
Load the databases so they and the variables defined (e.g. network-specific node names for this host) can be used in
the site specific configuration.
for method in $protocols
do
test -f $MAILSHARE/cf/i-${method}.cf && . i-${method}.cf
done
mailconf () {
local hname
# My official hostname
if [ -f /bin/hostname ]; then
rawhostname=$(/bin/hostname)
elif [ -f /etc/sys_id ]; then
read rawhostname < /etc/sys_id
else
rawhostname=$(/bin/uname -n)
fi
hname=$(canon $rawhostname)
|
Try to discover the organizational domain name.
orgdomain=$hname
sift $hname in
$rawhostname\.(.+)
orgdomain=\1
;;
tfis
hostname=$hname
# This is what it will say on out mail
mydomain=$hostname
}
orgdomains=x
: ${MAILCONF:=/etc/mail.conf}
if [ ! -r $MAILCONF ]; then
echo "$0: missing $MAILCONF: using the following values:"
mailconf
echo orgdomain=$orgdomain
echo hostname=$hostname
echo mydomain=$mydomain
provide siteinfo
else
. $MAILCONF && provide siteinfo
fi
[ "$orgdomains" = x ] && orgdomains=$orgdomain
|
Set hostname to enable message-id generation and checking.
hostname $hostname
. aliases.cf
. canon.cf
. rrouter.cf
. crossbar.cf
for method in $protocols
do
. p-${method}.cf
done
. process.cf
. server.cf
consist || exit 1
|
The crossbar function makes the policy decisions of how the instance of a message between a particular sender and
recipient should be treated. The 'from' and 'to' parameters are quads, i.e., in the form
(channel host user attributes)
The function may modify any of these elements of both the from and to addresses, and must select a message header
address rewriting function to be applied to this message instance. If the return value is nil or empty, the instance is
completely ignored, to the point that if there are no other recipients specified a complaint will be generated saying
there are {\bf no} recipients specified.
crossbar (from, to) {
local rewrite destination tmp
|
Count them... (in {\tt process.cf}) (we could use this as an ultimate duplicate remover too...)
db add recipients "$(user $to)" "$(user $from)"
|
Intercept (drop, redirect, bounce, save) the message
tmp=$(intercept "$(user $from)") &&
case "$(car $tmp)" in
|
Dropping error types for from addresses is necessary to avoid mail loops.
drop|error)
return ;;
file) LOGMSG="$LOGMSG $(car $(cdr $tmp))" ;;
esac
|
Only intercept mail that is not from the local postmaster, so that error messages can find their way back.
[ "$(channel $from)" = local -a "$(user $from)" = postmaster ] ||
tmp=$(intercept "$(user $to)") &&
case "$(car $tmp)" in
drop) return ;;
error) setf $(channel $to) error
setf $(host $to) $(car $(cdr $tmp))
;;
file) LOGMSG="$LOGMSG $(car $(cdr $tmp))" ;;
esac
|
If we do any alias expansion from the crossbar, we should do this:
Determine which rewrite function (for message header addresses) to use.
case $(channel $to) in
smtp|smtpx)
#case "$(channel $from)" in
#smtp|smtpx) # Address should be forwarded the way the arrive
# rewrite=null ;;
#*) rewrite=internet ;;
#esac
rewrite=internet
;;
error) rewrite=null ;;
local) case "$(channel $from)" in
local) #rewrite=intramachine
rewrite=internet ;;
*) # addresses should be saved the way they arrive
rewrite=null ;;
esac
;;
usenet) rewrite=internet ;;
ean) rewrite=ean_useratdomain ;;
*) # This is usually UUCP or BITNET
# We want to determine the final destination host/domain
destination="$(uucproute "$(user $to)")"
if [ "$(host $to)" ]; then
destination="$(host $to)"!"$destination"
fi
sift "$destination" in
.*!([^!]+)![^!]+
destination="\1" ;; # destination domain
.*\.(bitnet|netnorth|earn|cdn)
rewrite=smtp_useratdomain
break ;; # reply to user@domain
.* rewrite=internet ; break ;; # default sensible thing
tfis
;;
esac
|
The alias expansion might want to modify the envelope sender of the message instance. Here we cooperate in the scheme
which is to set the 'sender' attribute of the destination address.
tmp="$(get $(attributes $to) sender)" && [ x"$tmp" != x ] &&
from=(local "$tmp" "$tmp" $(attributes $from))
case "$(channel $from)" in
defrt1*)
setf "$(user $from)" "$(bitnetroute "$(user $from)")"
if [ $rewrite = internet ]; then
rewrite=bitnet2internet
fi
;;
esac
|
Rewrite the envelope addresses appropriately.
case "$(channel $to)" in
# uucp|local)
uucp)
|
Local destination on a system that delivers in UCB Mail compatible mail spool files means that the From_ line must be
in all-! form, which is the same as the UUCP transport requirement.
setf "$(user $from)" "$(uucproute "$(user $from)")"
setf "$(user $to)" "$(uucproute "$(user $to)")"
sift "$(user $to)" in
(.)!(.*) if [ \1 = $(host $to) ]; then
setf "$(user $to)" \2
fi
;;
tfis
sift "$(user $to)" in
(.)\.uucp!(.*) setf "$(user $to)" \1!\2 ;;
tfis
;;
# smtp)
smtp|smtpx|local|bsmtp3*)
tmp="$(smtproute "$(user $from)")"
sift "$tmp" in
(@$hostname[:,].*)|([^@:,]+@$hostname)
break ;;
.*
# tmp="@$hostname:$tmp" # <-- that creates RFC-822
# source-routing, AVOID!
tmp="$tmp"
;;
@(.+):(.+:.+)
tmp="@\1,\2" ; continue ;;
tfis
setf "$(user $from)" "$tmp"
sift "$(user $to)" in
(^/).* setf "$(user $to)" "$(smtproute "$(user $to)")" ;;
tfis
;;
ean)
setf $(user $from) "$(ean_useratdomain "$(user $from)")"
setf "$(user $to)" "$(ean_useratdomain "$(user $to)")"
;;
usenet)
setf $(user $from) "$(uucproute "$(user $from)")"
sift $(user $from) in
$hostname!.* ;;
.* setf $(user $from) $hostname!$(user $from) ;;
tfis
# newsgroup name only
setf "$(user $to)" $(localpart "$(user $to)")
;;
# bsmtp3|bsmtp3nd)
# setf $(user $from) "$(bitnetroute "$(user $from)")"
# tmp="$(bitnetroute "$(user $to)")"
# sift "$tmp" in
# .*@([^.]).uucp
# tmp="$(bitnetShortroute "$(user $to)")" ;;
# tfis
# setf "$(user $to)" "$tmp"
# rewrite=bitnetShortroute
# ;;
defrt1)
setf $(user $from) "$(bitnetroute "$(user $from)")"
setf $(user $to) "$(bitnetroute "$(user $to)")"
rewrite=bitnetroute
sift "$(user $to)" in
(.*)[!%](.+)@(.*)
to=(error bitnetgw "\3" $(attributes $to))
rewrite=null
;;
tfis
;;
esac
#log recipient: "$(channel $to)" "$(host $to)" "$(user $to)"
return ($rewrite $from $to)
} # end of crossbar
|
If you want to intercept specific mail messages, this function and the associated code in the crossbar and process
functions will let you do it. There are three possible actions:
drop - completely ignore this address
error - return the specified error message
file - append the message file to the specified file
|
Both the file and error actions require an argument, which necessitates the use of multiple-value return (i.e., return
a list) in all cases.
If you don't want to intercept anything, this function should return failure. The stub defined here is the usual case,
you can override it in the host- specific cf file.
intercept (address) {
# case "$(smtp_useratdomain "$address")" in
# *@pdq*) return (file /var/scr/pdq) ;;
# rayan@csri.*) return (drop) ;;
# bitftp*@*) return (error bounce) ;;
# esac
return 1
}
|
On mail from one local user to another, we don't want to see all the long domain name extensions. This can cause
problems with silly UAs, if it does you can just redefine {\tt intramachine} to call {\tt null} in your site or
host-specific configuration files.
intramachine (address) { # strip hostname if it came from here
sift "$address" in
(.*)@($hostname|$mydomain)
address="$(condquote "\1")" ;;
tfis
return "$address"
} # end of intramachine
null (address) {
return "$address" # surprise!
}
|
This is usually the default message-header address rewriting function. It is responsible for hostname hiding and
qualification.
internet (address) {
address="$(canonicalize "$address")" # Canonicalize does local
# hostname hiding...
sift "$address" in
(.*)<@(.+)>(.*)
#if [ $(deliver \2) ]; then # hostname hiding
# address="\1@${mydomain}\3"
# break
#fi
address="\1@\2\3" # No hostname hiding...
;;
(.*)<(.+)>(.*) address="\1\2\3" ;; # defocus
[^@]+
# This is a local part address w/o any domains!
address="$(condquote "$address")"
address="$address@$mydomain" # add our hostname
;;
tfis
return "$address"
} # end of internet
|
This is the protocol switch function. It keys off the form of the filename to determine how to process a particular
class of messages. It is expected that an internal function will be called to orchestrate the processing of the message
and enforce proper semantics.
The file argument is the name of a file in the {\tt \$POSTOFFICE/router} directory.
process (file) {
db flush pwuid
db flush pwnam
db flush fullname
db flush hostexpansions
db flush recipients
|
Since we cannot detect that the password database has been updated under our feet, we flush the cached information every
once in a while (in this case, before every message).
The LOGMSG variable is used by the intercept facility (in {\tt crossbar.cf}) to make sure only a single copy of a message
is saved when required. Each sender - recipient address pair can cause an intercept which can specify a file to save the
message to. This variable is appended to elsewhere, and processed at the end of this function.
case "$file" in
# [0-9]*.x400) x400 "$file" ;;
# [0-9]*.uucp) uucpfilter "$file" > /tmp/X.$$
# cat /tmp/X.$$ > "$file"
# rfc822 "$file" ;;
[0-9]*) rfc822 "$file" ;;
core*) /bin/mv "$file" ../$file.router.$$
return
;;
*) /bin/mv "$file" ../postman/rtr."$file".$$
return
;;
esac
[ $? ] && return 0 # Leave when they returned failure..
|
The file names in the {\tt \$POSTOFFICE/router} directory are determined by the parameter to the mail\_open() C library
routine. This case statement knows about the various message file types needed on your system, and arranges appropriate
processing of each. The internal function {\tt rfc822} expects a file name as argument, and determines the semantics of
the message and of the configuration code. For example, the {\tt router}, {\tt crossbar}, and {\tt header\_defer}
functions have semantics only because the {\tt rfc822} function knows about them. There are no other message formats
supported in this distribution.
log info: recipients $(db count recipients) $(elements $(cdr $(cdr $(cdr $(cdr $envelopeinfo))))) '
'
|
For statistics gathering we print out the envelope information property list in its entirety, except for the file name,
and the message id, both of which were logged earlier (in C code).
for f in $LOGMSG
do
{ echo "==${file}==$(rfc822date)==" ;
/bin/cat ../queue/"$file" } >> $f && log saved "$file" in $f
done
}
|
This does the saving of intercepted messages into archive files.
Most of the address routing processing is done here.
. fqdnalias.cf # Pick that set of tools into here!
envelopeinfo=(message-id "<$USER.interactive@$hostname>" now 0)
: ${UNRESOLVABLEACTION:='error unresolvable'}
relation -bt selfmatch selfmatch
rrouter (address, origaddr, A, plustail, domain) {
local tmp tee didhostexpand priv nattr a
# local seenuucp seenbitnet
# seenuucp=false
# seenbitnet=false
didhostexpand="";
# echo "rrouter: address=$address, origaddr=$origaddr" >> /dev/tty
tmp=$(fqdn_neighbour "$origaddr" "$address" $A) &&
return $tmp
address="$(condquote "$address")"
|
We have troublesome addresses coming here...
# "|pipe-program"
# "|quoted string"@domain
# "foo > faa"@domain
# "fii < fuu"@domain
# "foo @ faa"@domain
# "|foo @ faa"
|
and we want to do correct focusing...
ssift "$address" in
# Now make canonical
'"'(.*)'"'<(.*)
address="\1\2" # defocus
;;
'"'(.*)'"'>(.*)
address="\1\2" # defocus
break ;;
([\'"'].*[\'"'])<(.*)
address="\1\2" ;; # defocus
([\'"'].*[\'"'])>(.*)
address="\1\2" ;; # defocus
# See that it does not start with a pipe ...
# \|.+ # Looks like a pipe... Don't mutilate it!
# break ;;
# '"'[|].+ # Quoted pipe?? What the ...??
# break ;;
tfiss
address=$(canonicalize "$address")
ssift "$address" in
# <in%>(.*)
# return (((error vms-in-pros "in%\1" $A))) ;;
# (.*)<@(.+)\.uucp>(.*)
# seenuucp=true
# address="\1$plustail<@\2>\3" ;; # fix host.uucp!route
# (.*)<@(.+)\.(bitnet|earn|netnorth)>(.*)
# seenbitnet=true # Strip off the (bitnet|netnorth|earn)
# address="\1<@\2>\4" ;; # fix host.bitnet!route
# handle special cases.....
# \\(.) return (((local - "$address" $A))) ;;
@ # handle <> form???
tmp=(local user postmaster $A)
return $(routeuser $tmp "")
;;
|
The following two are two approaches to the same problem, generally speaking we should use the SECOND one, but your
mileage may vary... (Problems exist when WE are the target..)
# (.*)<@\[(.)\]>(.*)
# address="\1$plustail<@$(gethostbyaddr \2)>\3"
# ;;
(.*)<@\[(.)\]>(.*)
# numeric internet spec
if [ $(selfmatch "\2") ]; then
address="\1$plustail<@>\3"
domain="@[\2]"
plustail=""
else
return (((smtp "[\2]" "\1$plustail@$(gethostbyaddr \2)\3" $A)))
fi
;;
|
This is the end of the $1.2.3.4$ address case...
(.*)<@(.*)\.>(.*)
address="\1$plustail<@\2>\3"
plustail=""
;;
|
Now massage the local info.
(.*)<@(.*)($orgdomains)>(.*)
address="\1$plustail<@\2$orgdomain>\4"
domain="@\2$orgdomain"
plustail=""
;;
<@(.*)>[:,](.+)@(.+)
if [ $(deliver "\1") ]; then # Source routed to our name?
return $(rrouter "\2$plustail@\3" "$origaddr" $A "" "")
fi
;;
<@($orgdomains)>[:,](.+)@(.+)
return $(rrouter "\2$plustail@\3" "$origaddr" $A "" "")
;; # strip organization
(.+)<@(.+)>(.*)
if [ $(deliver "\2") ]; then # Do we handle this?
address="\1$plustail<@>\3"
domain="@\2"
plustail=""
elif [ "\2" = "$hostname" ]; then # Is it at local host?
address="\1$plustail<@>\3" # (this is a backup test)
domain="@\2"
plustail=""
fi ;;
<@>.(.+) # This plustail is propably wrong...
return $(rrouter "\1$plustail" "$origaddr" $A "" "$domain") ;; # try after route strip
(.+)<@>
if [ -z "$domain" ]; then
domain="$mydomain"
fi
return $(rrouter "\1$plustail" "$origaddr" $A "" "$domain") ;; # strip trash & retry
tfiss
#log "BITNET name=$bitnetname, address=$address"
case $bitnetname in
?*) tsift "$address" in
(.*)<@(.*)\.(bitnet|netnorth|earn)>(.*)
address="\1<@\2>\4" ;;
# Strip off the (bitnet|netnorth|earn)
tfist
;;
esac
#log "BITNET name=$bitnetname, address=$address"
|
Resolve names to routes, get the actual channel name mostly from an external database.
ssift "$address" in
(.*)<@(.+)>(.*)
#log "neighbourg test: domain: \2, addr: $address"
address="\1$plustail@\2\3"
plustail=""
|
If you want to have the SMARTHOST to pick the routing for all non-local stuff, enable the following test case..
# if [ "$SMARTHOST" ]; then
# return $(rrouter "$SMARTHOST!$(uucproute "$address")$plustail" "$origaddr" $A "" "$domain")
# else
# return ((($UNRESOLVABLEACTION "$address" $A)))
# fi
#if [ x$seenbitnet = xtrue ]; then
# address="\1@\2.bitnet"
#fi
didhostexpand=$(hostexpansions "\2")
for method in $protocols
do
tmp=$(${method}_neighbour "\2" "$address" $A) &&
return $tmp
done
#if [ x$seenuucp = xtrue ]; then
# if [ "$UUCPACTION" != "" ]; then
# return ((($UUCPACTION "\1@\2.uucp" $A)))
# fi
# tmp=$(routes_neighbour "\2.uucp" "$address" $A) &&
# return $tmp
#fi
#if [ x$seenbitnet = xtrue ]; then
# if [ "$BITNETACTION" != "" ]; then
# return ((($BITNETACTION "\1@\2.BITNET" $A)))
# fi
#fi
if [ "$SMARTHOST" ]; then
return $(rrouter "$SMARTHOST!$(uucproute "$address")" "$origaddr" $A "" "$domain")
else
return ((($UNRESOLVABLEACTION "$address" $A)))
fi
;;
\\(.+) # A back-quote prefixed userid (most likely)
return $(rrouter "\1" "$origaddr" $A "$plustail" "")
;;
/.+ # file
|
Well, it could be a slash-notated X.400 address too..
return (((local "file.$origaddr" "$address" $A)))
;;
\|.+ # pipe
return (((local "pipe.$origaddr" "$address" $A)))
;;
:include:.+ # ":include:" -alias
|
We must test this here, because the file-path after this prefix may have a dot.
tmp=(local "$origaddr" "$address" $A)
return $(routeuser $tmp "")
;;
|
Ok, from now on if we don't have a domain set, we use {\tt \$mydomain}
.* if [ -z "$domain" ] ; then
domain="@$mydomain"
fi
;;
(.+\.[^+]+)(\+.+) # Dotfull name with a plus!
plustail="\2"
address="\1"
|
Fall forward for the dotfull processing.
;;
.+\..+ # A dotfull name
tmp="$(fullnamemap "$address")" && \
return $(rrouter "$tmp" "$origaddr" $A "$plustail" "$domain")
if [ $(newsgroup "$address") ]; then
return (((usenet - "$address" $A)))
fi
|
Okay... Not in our special fullname/newsgroup-files, lets see if it is in the traditional one?
if [ $(aliases "$address") ]; then
|
It can be found from the normal aliases, run the alias processing.
tmp=(local "$origaddr" "$address" $A)
return $(routeuser $tmp "$domain")
fi
return (((error norealname "$address" $A)))
return (((error nonewsgroup "$address" $A)))
;;
.* # Now all the rest of the cases..
tmp=(local "$origaddr" "$address$plustail" $A)
return $(routeuser $tmp "$domain")
;;
tfiss
} # end of rrouter
routes_spec (domain, address, A) {
local tmp channel rscshost
sift "$domain" in
# (bsmtp3nd|bsmtp3|bitnet2|bitnet2deliver2)!(.)!(.)
(bsmtp3nd|bsmtp3|bsmtp3nd|bsmtp3rfc|bsmtp3ndrfc)!(.)!(.)
return (((\1 "\2@\3" "$address" $A))) ;;
(defrt1)!(.)
channel=\1
rscshost=\2
tmp="$(uucproute "$address")"
sift "$tmp" in
.+!([^!]+)!([^!]+)
|
We are trying to gateway through a DEFRT1 domain(!)
#return (((error bitnetgw "$address" $A))) ;;
|
This will usually work anyway, sigh...
return (((bsmtp3 "mailer@$rscshost" "\2@\1" $A))) ;;
([^!]+)!([^!]+)
|
The destination domain is the next hop, so we're all happy.
return ((($channel "\2@$rscshost" "\2@\1" $A))) ;;
tfis
;;
ignore!.*
break
;;
smtp!
ssift "$address" in
(.*)@(.+)
return (((smtp "\2" "$address" $A)))
;;
tfiss
;;
dns!
ssift "$address" in
(.*)@(.+)
return (((smtp "\2" "$address" $A)))
;;
tfiss
;;
(.?)!
return ((("\1" - "$address" $A)))
;;
delay!(.)
|
NB! envelope info must also be defined in interactive mode.
tmp="$(/bin/expr $(get envelopeinfo now) + "\1")"
return (((hold "$tmp" "$address" $A))) ;;
(.?)!([^!]+)
return ((("\1" "\2" "$address" $A))) ;;
(.?)!(.+)
# BEWARE LOOPS
return $(rrouter "\2!$(uucproute "$address")" "$address" $A "" "$domain")
;;
tfis
return 1
}
uucproute (address) {
|
This function turns any address into a pure-! form. It should not call any other functions, since random other functions
call it. In particular it should not use rfc822route which itself uses uucproute.
sift "$address" in
(.*)<(.*)>(.*) address=\1\2\3 ;; # defocus
(.+!)@(.+) address=\1$(uucproute "@\2") ;;
(.+)([,:]@)(.+) address=\1!\3 ; continue ;;
:include:[^!]+ return $address ;;
@(.+:)([^:]+) address=\1$(uucproute "\2") ;;
(.+):(.+) address=\1!\2 ; continue ;;
|
This won't work properly for e.g. utzoo!bar@gpu.utcs.toronto.edu because gpu.utcs also has an active uucp connection with
utzoo. It will work properly in other cases though, so if we have to guess...
#([^!])!(.+)@(.+) if [ $(ldotsys \1) ]; then
# address=\1!\3!\2
# else
# address=\3!\1!\2
# fi ;;
(.+)!([^!]+)%([^!%]+)@(.+) # route!a%b@c -> route!c!a@b
address=\1!\4!\2@\3 ; continue ;;
([^@]+)@(.+) address=\2!\1 ;;
@(.+) address=\1 ;;
(.+)!([^!]+)[%@](.+) address=\1!\3!\2 ;;
tfis
return "$address"
} # end of uucproute
|