#!/usr/bin/sh ######################### # # # mailxauth # # # # send authenticated # # email without using # # sendmail # # # ######################### # Emulates mailx with -s [ -r reply ] # plus -v to log SMTP code # # Usage: mailxauth [ -e ] [ -t ] [ -s subject ] [ -r return_address ] [ -v ] email_address # # where: -s subject, enclose in "..." to keep spaces # -t test the config file for authorization # -e do nothing -- this is normally used for you-have-mail test # -r reply/return address (see CUSTOMIZE below) # -v verbose logging # # (uses stdin for the message text) # # email_address can be an alias -- will be expanded as necessary # # mailxauth will log activity in /var/adm/syslog/mail.log # # Requires: # ######### # # exported variables # # or # # /etc/mailauth.conf # # AUTHMAILSERVER=server.com (or IP address) # AUTHMAILPORT=587 # REPLYMAIL=somebody@someserver.com # AUTHPLAIN=encrypted(base64)login+password # LOGIN=username@relay-server.com # PW=plaintext_password # SUBJECT="(no subject)" # # Here is the order of precedence (1 overrides 2 overrides 3): # 1. command line (-s and -r) # 2. exported environment variables # 3. mailauth.conf # # Put another way: # # mailauth.conf sets the defaults, # environment variables override defaults, # command line overrides -r and -s when used # # The 3 methods are to control the 7 variables above. # # REPLYMAIL is to masquerade the sender so that a bounce or # reply will have a valid email destination. Most ISP's # reject email relays without a valid return (From:) addr # # For LOGIN and PW, # Two choices: # # 1. assign LOGIN and PW the correct values (less secure) and # the script will encrypt. # # 2. assign AUTHPLAIN=encrypted_string to avoid visible LOGIN/PW # (and LOGIN/PW are ignored) # # How to create the base-64 encryption: # # perl -MMIME::Base64 -e 'print encode_base64("\000userlogin\000password")' # where \000 is the separator, login then pw # # NOTE: Escape the @ (if used) in userlogin and/or pw (ie, abc\@mycpu.com) # Must have \000 as a separator in front of login and password. # This script does it automatically when AUTHPLAIN="" # Testing: # mailxauth -t # (will login and show the results) # # TRACEME=1 will trace script execution TRACEME=${TRACEME:-false} # TRACEME non-null = trace on [[ $TRACEME != false ]] && set -x && PS4='[$LINENO]: ' ####################### # # # F U N C T I O N S # # # ####################### ############################# function EscapeSpecial { # Usage: EscapeSpecial "string" # Displays: string with \@ escaped # TRACEME=${TRACEME:-false} # TRACEME non-null = trace on # [[ $TRACEME != false ]] && set -x && PS4='[$LINENO]: ' OLD="$1" OLDLEN=${#OLD} BACKSLASH="\\" [[ $OLDLEN -eq 0 ]] && echo "" && return COUNT=1 NEWSTRING="" while [ $COUNT -le $OLDLEN ] do CHR=$(echo "$OLD" | cut -c $COUNT) [[ "$CHR" = "@" ]] && NEWSTRING="$NEWSTRING$BACKSLASH" NEWSTRING="$NEWSTRING$CHR" COUNT=$((COUNT+1)) done echo "$NEWSTRING" return } ############################# function AliasExpander { ## sendmail alias expander ## ## Usage: AliasExpander
## ## Returns: email addresses from /etc/mail/aliases ## Restrictions: ## aliases are assumed to have *NO* @ character ## Only first alias occurence is matched (same as sendmail) ## Aliases are stored in /etc/mail/aliases ALIASFILE=/etc/mail/aliases [[ $# -lt 1 ]] && return ADDR=$1 # Read the aliases line by line. # -- Skip comments # -- Skip blank lines # -- Match an alias: # grab the addresses on the line # look for trailing \ # keep adding multiline until no trailing \ cat $ALIASFILE | while read TEXT do # Skip blank lines and comment lines [[ "$(echo "$TEXT" | grep -v "^#")" = "" ]] && continue [[ "$(echo "$TEXT" | awk NF)" = "" ]] && continue # The text to match is the first word before : # The first awk returns text before :, the second removes leading # and trailing spaces. ALIASTEXT=$(echo $TEXT | awk -F : '{print $1}' | awk '{print $1}') if [[ $ALIASTEXT = $ADDR ]] then # We have a match. Grap the text following : and remove spaces NEWADDRS=$(echo $TEXT | cut -d: -f2- | tr -d '[:space:]') # look for a traling \ and if true, keep appending # The aliases file is assumed to be valid with a comma between # multiple addresses and line extensions (\) while [ $(echo $NEWADDRS | awk '/\\$/' | wc -c) -gt 0 ] do NEWADDRS=$NEWADDRS$TEXT done echo $NEWADDRS return fi done echo "" return } ############################# # # # M A I N P R O G R A M # # # ############################# # environment setup set -u umask 077 export PATH=/usr/bin:/usr/sbin MYNAME=${0##*/} # tempfile to hold stdout and debug info TEMPDIR=/var/tmp/$MYNAME.$$ TEMPFILE=$TEMPDIR/verbose.log rm -rf $TEMPDIR mkdir $TEMPDIR trap 'rm -rf $TEMPDIR; exit' 0 1 2 3 6 11 15 MAILXAUTHLOG=/var/adm/syslog/mail.log CONFIGFILE=/etc/mailxauth.conf # variable Precedence: # # $CONFIGFILE sets the defaults, # environment variables override defaults, # command line overrides -r and -s if used # # Start by looking for environment variables that are # not assigned. This code sets undefined variables # without errors when set -u is in force. AUTHMAILSERVER=${AUTHMAILSERVER:-unassigned} AUTHMAILPORT=${AUTHMAILPORT:-unassigned} REPLYADDR=${REPLYADDR:-unassigned} AUTHPLAIN=${AUTHPLAIN:-unassigned} SUBJECT=${SUBJECT:-(no subject)} LOGIN=${LOGIN:-unassigned} PW=${PW:-unassigned} # Go through conf file looking for variables # that were not assigned in the current environment. # # If the file is not readable, all values must come # from current env and -s,-r options # # Once all vars have been scanned, look for missing # variables that cannot come from command line # and bail out if not found. # if [ -r $CONFIGFILE ] then for VAR in \ AUTHMAILSERVER \ AUTHMAILPORT \ REPLYADDR \ AUTHPLAIN \ LOGIN \ PW do # if VAR unassigned, search through conf file [[ $(eval echo \$$VAR) = "unassigned" ]] && eval $(grep $VAR $CONFIGFILE) done fi # Look for Required variables [[ "$AUTHMAILSERVER" = "unassigned" ]] && echo "\nAUTHMAILSERVER not configured\n" && exit 1 [[ "$AUTHMAILPORT" = "unassigned" ]] && echo "\nAUTHMAILPORT not configured\n" && exit 1 [[ "$AUTHPLAIN" = "" ]] && AUTHPLAIN=unassigned if [ "$AUTHPLAIN" = "unassigned" ] then [[ "$LOGIN" = "unassigned" ]] && echo "\nAUTHPLAIN empty and LOGIN not configured\n" && exit 1 [[ "$PW" = "unassigned" ]] && echo "\nAUTHPLAIN empty and PW not configured\n" && exit 1 fi # Finally, get command line options (if any) VERBOSE=false TESTAUTH=false while getopts ":etr:s:v" OPTCHR do case $OPTCHR in e) exit 0 ;; ## mailx -e tests for mail present - do nothing t) TESTAUTH=true ;; r) REPLYADDR="$OPTARG" ;; s) SUBJECT="$OPTARG" ;; v) VERBOSE=true ;; *) eval "ERROPT=\$$(($OPTIND-1))" echo "Invalid option(s): $ERROPT" echo "Usage: $MYNAME [ -n ] [ -v ]\n" logger -t $MYNAME -p mail.err "invalid option $ERROPT" exit 1 ;; esac done shift $(($OPTIND -1)) ######################################################### # encrypt LOGIN & PW if AUTHPLAIN = unassigned # escape special characters for perl processing if [[ "$AUTHPLAIN" = "unassigned" ]] # if blank, encrypt $LOGIN and $PW then LOGIN="$(EscapeSpecial "$LOGIN")" PW="$(EscapeSpecial "$PW")" AUTHPLAIN="$(perl -MMIME::Base64 -e 'print encode_base64("\000'$LOGIN'\000'$PW'")')" fi # Additional email addresses ############################ MAILFROM="MAIL From:<$REPLYADDR>" SENDINGSERVER=${REPLYADDR##*\@} REPLYNAME=$(echo $REPLYADDR | cut -f1 -d@) # Test authorization (only) ########################################################## if [[ $TESTAUTH = true ]] then TESTSTRING="ehlo $SENDINGSERVER\nAUTH PLAIN $AUTHPLAIN\nQUIT" echo "\nTesting authorization to $AUTHMAILSERVER, port $AUTHMAILPORT:" echo "$TESTSTRING" | while read TEXT do echo " $TEXT" done echo echo "$TESTSTRING\n" | telnet $AUTHMAILSERVER $AUTHMAILPORT echo exit fi ########################################################### # EMAILADDR assigned (and alias resolution) if [[ $# -lt 1 ]] then echo "\n$MYNAME: missing email address\n" logger -t $MYNAME -p mail.err "missing email address" exit 1 fi EMAILADDR="$1" # Since EMAILADDR might be an alias, use the AliasExpander to translate into first # level addresses. First level means that the alias defines nothing but valid # email addresses. In other words, root : bill@blh.com # is fine but root : sysadmin, dbas # will not work. Maybe someday... [[ $(echo $EMAILADDR | grep -c @) -lt 1 ]] && EMAILADDR=$(AliasExpander $EMAILADDR) # parse EMAILADDR ; or , into space for multiple addresses ########################################################## RCPTLIST="" TOLIST="" for ADDR in $(echo "$EMAILADDR" | tr -s ",;" " ") do [[ "$RCPTLIST" != "" ]] && RCPTLIST="$RCPTLIST\n" [[ "$TOLIST" != "" ]] && TOLIST="$TOLIST\n" RCPTLIST="${RCPTLIST}RCPT To:<$ADDR>" TOLIST="${TOLIST}To: \"$(echo $ADDR | cut -f1 -d@)\" <$ADDR>" done ## DEBUG in VERBOSE mode if [[ $VERBOSE = "true" ]] then echo "DEBUG: AUTHMAILSERVER=$AUTHMAILSERVER" >> $TEMPFILE 2>&1 echo "DEBUG: AUTHMAILPORT=$AUTHMAILPORT" >> $TEMPFILE 2>&1 echo "DEBUG: REPLYADDR=$REPLYADDR" >> $TEMPFILE 2>&1 echo "DEBUG: SENDINGSERVER=$SENDINGSERVER" >> $TEMPFILE 2>&1 echo "DEBUG: AUTHPLAIN=$AUTHPLAIN" >> $TEMPFILE 2>&1 echo "DEBUG: LOGIN=$LOGIN" >> $TEMPFILE 2>&1 echo "DEBUG: PW=$PW" >> $TEMPFILE 2>&1 echo "DEBUG: SUBJECT=$SUBJECT" >> $TEMPFILE 2>&1 echo "DEBUG: EMAILADDR=$EMAILADDR" >> $TEMPFILE 2>&1 echo "DEBUG: MAILFROM=$MAILFROM" >> $TEMPFILE 2>&1 echo "DEBUG: RCPTLIST=\n$RCPTLIST" >> $TEMPFILE 2>&1 echo "DEBUG: TOLIST=\n$TOLIST" >> $TEMPFILE 2>&1 fi # Form the SMTP dialog ###################### # Form the telnet message SMTP=\ "ehlo $SENDINGSERVER AUTH PLAIN $AUTHPLAIN MAIL From:<$REPLYADDR> $RCPTLIST DATA From: \"$REPLYNAME\" <$REPLYADDR> $TOLIST Date: $(date "+%a, %e %B %Y %H:%M:%S") Subject: $SUBJECT $(cat) . QUIT" # Send the file ############### [[ $VERBOSE = "true" ]] && echo "\n\nSMTP dialog:" >> $TEMPFILE [[ $VERBOSE = "true" ]] && echo "$SMTP" >> $TEMPFILE echo "$SMTP" | telnet $AUTHMAILSERVER $AUTHMAILPORT >> $TEMPFILE 2>&1 # $TEMPFILE has the debug info -- log it in VERBOSE mode if [ $VERBOSE = "true" ] then cat $TEMPFILE | while read TXT do logger -t $MYNAME -p mail.debug "$TXT" done fi