# These functions are used by the smtpserver to do synchronous address checking. # Arrange to set up routing/relaying policy checking functions as # necessary. if [ "$RELAYCHECK" ] then . relay-check.cf else # Stub these functions out if we're not doing antirelaying # and other policy checking. rcheck_init () { return 0 } rcheck_check () { return 0 } rcheck_rfc821 () { return 0 } fi quadprint (quad) { #| This is a prettyprinter for address quads. What it prints is what someone #| doing a VRFY or EXPN query to the SMTP server will see. local text t case $(channel $quad) in local) text="local delivery for" case $(user $quad) in /*|\|*) ;; *) if t="$(fullname $(user $quad))"; then text="local delivery for $t" fi ;; esac ;; usenet) text="local delivery to newsgroup" ;; *) text="$(channel $quad) delivery" case "$(host $quad)" in -|'') text="$text for" ;; *) text="$text to $(host $quad) for" ;; esac ;; esac echo "$text <$(user $quad)>" } server (key) { #| The smtpserver program starts the router in interactive mode and runs this #| function to interface with the rest of the configuration code. The key #| specifies the semantics of the additional arguments provided by the #| smtpserver. The key values are: init, to, from, verify, and expand. local a A A=$(newattribute default_attributes type sender) #| In doing a VRFY operation, it is not desired to do any alias expansion. #| This can be avoided by modifying the attributes of default_attributes to #| shortcircuit alias expansion. It is assumed that default_attributes is #| otherwise proper for the purpose. The value specified for the privilege #| level should be appropriate. case $key in to|from|verify|expand) a="$1" sift "$a" in <(.+)> a="\1" ;; tfis if rfc822syntax "$a"; then ; else echo "554 illegal address syntax: <$a>" return fi ;; esac case $key in init) # If you want to log incoming connections, it can be done here #echo server "$@" >> /tmp/server # call the relay/policy initialization function rcheck_init smtp "$1" "$2" # redefine the log function log () { } ;; to|from) # We perform address verification in a number of steps. # We have already verified that this is a parseable RFC821/2 # address (up above). # The basic approach is to do as many cheap checks as possible # before we perform expensive steps, and then to reject a # message hard instead of soft if possible. The most expensive # step possible is rrouter. # Check and possibly reject that it conforms to RFC 821. # RFC 821 calls for present and fully qualified host and # domain names. We check before calling the router, because # the router will canonicalize and otherwise twitch us around. # This is a policy decision because we may accept them from # some sources. if rcheck_rfc821 $key "$1"; then ; else echo "554 address not canonicalized: <$1>" return fi # We perform flu immunizations *before* rejecting bad # addresses, since most of the good immunization targets # aren't legitimate email addresses. flu_maybeshot "$1" # This should be part of the routing policy stuff, but this # is a bad hack I don't want to canonicalize in there just # yet. Hopefully this stage is cheap; save the expensive ones # for bad_userexp. badusererror=nodelivery if bad_username $key "$1"; then echo "554 $(smtp_etext $badusererror): <$1>" return; fi # Perform a routing attempt. Output to stderr is things like # DNS lookup failures and doesn't have the right format for # SMTP responses, so toss it. a="$(router "$1" default_attributes 2>/dev/null)" # determine if the address was deliverable in a useful sense. # The simple address may be a mailing list (or a .forward, or # some other thing) and so have been exploded to many addresses, # some of which may be bad and some of which may have failed # to resolve. # An address is OK as long as it has at least one deliverable # final address. If it has no deliverable addresses and at # least one that is held, we generate a temporary failure; # the held address may become deliverable in the future. # Only if all addresses are hard-undeliverable do we fail # hard (this has a side effect that a mailing list with all # undeliverable addresses will fail a RCPT TO check, although # from the omiscient perspective the address is actually # mailable to). # scan the addresses for deliverable addresses and holds, and # save a specific error (the last, as it works out) to give # a somewhat detailed diagnostic about. isnodeliver=1; ishold=""; errorcase="" for i in $(elements $a); do for j in $(elements $i); do case $(channel $j) in error) errorcase=$(host $j);; hold) ishold=1;; *) isnodeliver="";; esac done; done # Give an opening for more expensive checks after we've # determined that the address will not bounce. badusererror=nodelivery # The following condition encodes the conditions when # we would not fail with a 554 already; either delivery, # or 'ishold' on. [ -z "$isnodeliver" -o -n "$ishold" ] && if bad_userexp $key "$1"; then echo "554 $(smtp_etext $badusererror): <$1>" return; fi # Hook into an optional relaying policy module. # if rcheck_check is successful, the message should be # accepted. If it fails, it may alter rcheckerror, which # should be printed as the failure. # Failures should *normally* be hard failures, but we # will take steps to cope. # Relay checking is cheap and can yield terminal rejections # (and pointed ones), so we perform it before doing anything # that may give a temporary failure. rcheckerror="554 we do not relay to: <$1>" rcheckreject="" if rcheck_check $key "$1"; then ; else case "$rcheckerror" in 5*) echo "$rcheckerror"; return;; # force us into 'temporary error' mode. 4*) isnodeliver=1; ishold=1; rcheckreject=1;; 2*) rcheckreject="";; *) echo "454 panic: rcheck_check '$rcheckerror' for <$1>"; return;; esac fi # $isnodeliver is true only if there are no deliverables. # $ishold is true if there was at least one hold. # analyze the scan results and act appropriately. [ $isnodeliver ] && if [ $ishold ]; then case "$rcheckreject" in 1) echo "$rcheckerror";; *) echo "454 temporarily unresolvable address: <$1>";; esac return else echo "554 $(smtp_etext $errorcase): <$1>" return fi # Check for flu immunizations. Since these are susceptible to # denial of service attacks, return temporary failures only. if flu_immunized $key "$1"; then echo "454 Temporary failure: potential spam origin <$1>" return fi # We have finished all our checks, so it must be OK. echo "250 Ok (verified)" ;; verify) a="$(router "$1" $A)" #| Invoke the router function with alias expansion turned off. for i in $(elements $a) do for j in $(elements $i) do quadprint $j done done ;; expand) a="$(router "$1" default_attributes)" #| Invoke the router function with alias expansion enabled. for i in $(elements $a) do for j in $(elements $i) do quadprint $j done done ;; # Verify that the name given to us in the HELO greeting actually # exists as something that can be routed to. # Actually using this requires a modification to smtpserver.c to # call us in the appropriate circumstances. helo) a="$(can_route "$1")" case "$a" in ok) echo 200 Looks OK to me.;; hold) echo 454 Try again later: cannot currently look up "$1".;; no) echo "501 Unresolvable HELO name: $1";; *) echo 501 internal can_route error "$a" for "$1".;; esac smtp_heloname="$1" ;; esac } # give us a text string appropriate for explaining a particuular error # channel's failure in an SMTP session - cks # since this depends on what errors you have configured, you may have # to tune it for your local situation (assuming you care about giving # specific error messages as opposed to the generic one). smtp_etext (reason) { case "$reason" in bounce|nodelivery|noperm) echo "undeliverable address";; nosuchuser|err.nosuchuser|nonewsgroup|nodotsname) echo "no such local user";; expansion) echo "error in expanding";; bitnetgw) echo "unroutable address";; defunct) echo "defunct address";; localhost|nomail|wrongname) echo "bad address";; blockeddns) echo "DNS service from a blocked source";; badsource) echo "not accepted from $smtp_heloname";; *) echo "unresolvable address";; esac } # Pull in our bad hack. # This is a bad hack because we could do it in the relay policy stuff, # but this is basically hardcoded and so we do it here, since I'm # pretending to keep hardcoding out of relay-policy.cf. # At the UofT, the primary use is to discard all-numeric user IDs from # various domains. . badusers.cf # Flu shots. . flushot.cf # Determine if a particular host or domain name actually really exists. # Most of this code is researched from rrouter.cf and may represent being # too chummy with things. # We could subvert router/rrouter, but doing it straight skips undesired # canonicalization and problems with usernames. require smtpdb can_route (host) { defer="" # checked for holds later on res="no" # Handle IP addresses # and some other special cases. case "$host" in "["*"]") echo ok; return;; "doe.utoronto.ca") echo ok; return;; mtigwc05.worldnet.att.net) echo ok; return;; esac # Is it us? # Arguably we should skip this check, but... if [ $(deliver "$host") ] ; then echo ok; return; elif [ "$host" = $hostname ] ; then echo ok; return; fi # Can we route it in general? # This is arguably cheating with the insides of [pi]-smtp.cf and # we should do it in the more complicated way that rrouter.cf # determines routability. However, I feel that HELO greetings # should be *SMTP* resolvable, not just generally routable. # Note that we check for canonical form; non-canonical names in # HELO greetings will not be expanded. host_neighbour "$host". 2>/dev/null && res=ok; # $defer is only useful if it doesn't resolve at all. case "$res" in no) if [ "$defer" ]; then echo hold; else echo $res; fi;; *) echo $res;; esac }