Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #! /bin/bash
- # Purpose:
- # Configures postfix for sending via a relay (smart host). Suitable for
- # sending mail from a postfix server behind a NATting router.
- # Usage:
- # See usage function below (search for "function usage") or use -h option.
- # Environment:
- # Developed and tested on Debian Squeeze with
- # * bash 4.1.5
- # * postfix 2.7.1
- # History:
- # 23oct11 Charles
- # * First version
- # Wishlist (in approx descending order of importance/triviality):
- # * None
- # Programmers' notes: error and trap handling:
- # * All errors are fatal and exit or finalise() is called.
- # * At any time, a trapped event may transfer control to finalise().
- # Programmers' notes: variable names and values
- # * Directory name variables are called *_dir and have values ending in /
- # * File name variables are called *_afn have values beginning with /
- # * Logic flag variables are called *_flag and have values $true or $false
- # * $buf is a localised scratch buffer.
- # * $lf is a line feed.
- # Programmers' notes: maximum line length ruler
- # -------+---------+---------+---------+---------+---------+---------+---------+
- # 10 20 30 40 50 60 70 80
- # Programmers' notes: function call tree
- # +
- # |
- # +-- initialise
- # | |
- # | +-- ck_email_address
- # | |
- # | +-- ck_ip_address
- # | |
- # | +-- usage
- # |
- # +-- configure
- # |
- # +-- finalise
- #
- # Utility functions called from various places:
- # ck_file fct msg
- # Function definitions in alphabetical order. Execution begins after the last function definition.
- #--------------------------
- # Name: ck_email_address
- # Purpose: checks argument is a valid email address
- # $1 - value to test
- #--------------------------
- function ck_email_address {
- fct "${FUNCNAME[ 0 ]}" "started with argument $1"
- if [[ $1 =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$ ]]; then
- fct "${FUNCNAME[ 0 ]}" "returning 0"
- return 0
- else
- fct "${FUNCNAME[ 0 ]}" "returning 1"
- return 1
- fi
- } # end of function ck_email_address
- #--------------------------
- # Name: ck_ip_address
- # Purpose: checks argument is a valid IP address
- # $1 - value to test
- # Acknowledgments to Mitch Frazier and Linux Journal: http://www.linuxjournal.com/content/validating-ip-address-bash-script
- #--------------------------
- function ck_ip_address {
- fct "${FUNCNAME[ 0 ]}" "started with argument $1"
- local array oIFS retval
- retval=1
- if [[ $1 =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
- oIFS=$IFS
- IFS='.'
- array=( $1 )
- IFS=$oIFS
- [[ ${array[0]} -le 255 && ${array[1]} -le 255 && ${array[2]} -le 255 && ${array[3]} -le 255 ]]
- retval=$?
- fi
- fct "${FUNCNAME[ 0 ]}" "returning $retval"
- return $retval
- } # end of function ck_ip_address
- #--------------------------
- # Name: fct
- # Purpose: function call trace (for debugging)
- # $1 - name of calling function
- # $2 - message. If it starts with "started" or "returning" then the output is prettily indented
- #--------------------------
- function fct {
- if [[ ! $debugging_flag ]]; then
- return 0
- fi
- fct_ident="${fct_indent:=}"
- case $2 in
- 'started'* )
- fct_indent="$fct_indent "
- msg 'I' "DEBUG: $fct_indent$1: $2"
- ;;
- 'returning'* )
- msg 'I' "DEBUG: $fct_indent$1: $2"
- fct_indent="${fct_indent# }"
- ;;
- * )
- msg 'I' "DEBUG: $fct_indent$1: $2"
- esac
- } # end of function fct
- #--------------------------
- # Name: finalise
- # Purpose: cleans up and gets out of here
- #--------------------------
- function finalise {
- fct "${FUNCNAME[ 0 ]}" 'started'
- local buf msg msgs my_retval retval
- # Set return value
- # ~~~~~~~~~~~~~~~~
- # Choose the highest and give message if finalising on a trapped signal
- my_retval="${prgnam_retval:-0}"
- if [[ $1 -gt $my_retval ]]; then
- my_retval=$1
- fi
- case $my_retval in
- 129 | 130 | 131 | 143 )
- case $my_retval in
- 129 )
- buf='SIGHUP'
- ;;
- 130 )
- buf='SIGINT'
- ;;
- 131 )
- buf='SIGQUIT'
- ;;
- 143 )
- buf='SIGTERM'
- ;;
- esac
- msg 'I' "finalising on $buf"
- ;;
- esac
- # Final log messages
- # ~~~~~~~~~~~~~~~~~~
- finalising_flag=$true # Used to avoid infinite recursion
- msgs=
- if [[ $global_warning_flag ]]; then
- msgs="$msgs${lf}There were WARNINGs"
- if [[ $my_retval -eq 0 ]]; then
- my_retval=1
- fi
- fi
- if [[ "$msgs" != '' ]]; then
- msgs="${msgs#$lf}" # strip leading linefeed
- msg 'E' "$msgs"
- fi
- msg 'I' "Exiting with return value $my_retval"
- # Exit
- # ~~~~
- fct "${FUNCNAME[ 0 ]}" 'exiting'
- exit $my_retval
- } # end of function finalise
- #--------------------------
- # Name: initialise
- # Purpose: sets up environment and parses command line
- #--------------------------
- function initialise {
- local alias args array i_opt_flag emsg r_opt_flag username
- # Configure shell environment
- # ~~~~~~~~~~~~~~~~~~~~~~~~~~~
- export PATH=/usr/sbin:/sbin:/usr/bin:/bin
- IFS=$' \n\t'
- set -o nounset
- umask 0022
- unalias -a
- # Configure traps
- # ~~~~~~~~~~~~~~~
- # Set up essentials for finalise() and what it may call before setting traps
- # because finalise() may be called at any time after then
- false=
- true=true
- debugging_flag=$false
- finalising_flag=$false
- global_warning_flag=$false
- prgnam=${0##*/} # program name w/o path
- trap 'finalise 129' 'HUP'
- trap 'finalise 130' 'INT'
- trap 'finalise 131' 'QUIT'
- trap 'finalise 143' 'TERM'
- # Utility variables
- # ~~~~~~~~~~~~~~~~~
- lf=$'\n' # ASCII linefeed, a.k.a newline
- prg_ver='0.0'
- # Parse command line
- # ~~~~~~~~~~~~~~~~~~
- aliases=
- args="${@:-}"
- emsg=''
- i_opt_flag=$false
- my_ips=
- pf_fqdn=
- r_opt_flag=$false
- unactioned_aliases=
- while getopts a:dhi:p:r:V opt 2>/dev/null
- do
- case $opt in
- a )
- array=( $OPTARG )
- for (( i=0; i<${#array[*]}; i++ ))
- do
- username=${array[i]%:*}
- alias=${array[i]#*:}
- ck_email_address "$alias" || emsg="$emsg${lf}Option -a: invalid email address: $alias"
- grep -q "$username:$alias" /etc/aliases \
- && unactioned_aliases="$unactioned_aliases${lf}$username: $alias" \
- || aliases="$aliases${lf}$username: $alias"
- done
- aliases=${aliases#$lf}
- ;;
- d )
- debugging_flag=$true
- ;;
- h )
- usage verbose
- exit 0
- ;;
- i )
- i_opt_flag=$true
- array=( $OPTARG )
- for (( i=0; i<${#array[*]}; i++ ))
- do
- ck_ip_address ${array[i]} || emsg="$emsg${lf}Option -i: invalid IP address: ${array[i]}"
- my_ips="$my_ips ${array[i]}"
- done
- my_ips=${my_ips# }
- ;;
- p )
- pf_fqdn=$OPTARG
- [[ $pf_fqdn =~ ^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,12}$ ]] \
- || emsg="$emsg${lf}Option -p: invalid domain name: $pf_fqdn"
- ;;
- r )
- r_opt_flag=$true
- IFS=':'
- array=( $OPTARG )
- IFS=
- if [[ ${#array[*]} -ne 4 ]]; then
- emsg="$emsg${lf}Option -r: incorrect number of relaying SMTP server info components: $OPTARG"
- else
- relay_fqdn=${array[0]}
- [[ $relay_fqdn =~ ^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}$ ]] \
- || emsg="$emsg${lf}Option -r: invalid domain name: $relay_fqdn"
- relay_port=${array[1]}
- [[ $relay_port =~ ^[0-9]+$ ]] || emsg="$emsg${lf}Option -r: relaying SMTP port not numeric: $relay_port"
- relay_username=${array[2]}
- relay_password=${array[3]}
- fi
- ;;
- V )
- echo "$prgnam version $prg_ver"
- exit 0
- ;;
- * )
- emsg="$emsg${lf}Invalid option '$opt'"
- esac
- done
- # Test for mandatory options not set
- # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- [[ $i_opt_flag = $false || $my_ips = '' ]] && emsg="$emsg${lf}Mandatory option -i not given or empty option argument"
- [[ $pf_fqdn = '' ]] && emsg="$emsg${lf}Mandatory option -p not given or empty option argument"
- if [[ $r_opt_flag = $false ]]; then
- emsg="$emsg${lf}Mandatory option -r not given"
- elif [[ ${#array[*]} -eq 4 ]]; then
- [[ $relay_fqdn = '' ]] && emsg="$emsg${lf}Option -r: server_FQDN empty"
- [[ $relay_port = '' ]] && emsg="$emsg${lf}Option -r: port empty"
- [[ $relay_username = '' ]] && emsg="$emsg${lf}Option -r: username empty"
- [[ $relay_password = '' ]] && emsg="$emsg${lf}Option -r: password empty"
- fi
- # Test for extra arguments
- # ~~~~~~~~~~~~~~~~~~~~~~~~
- shift $(( $OPTIND-1 ))
- if [[ $* != '' ]]; then
- emsg="$emsg${lf}Invalid extra argument(s) '$*'"
- fi
- # Report any errors
- # ~~~~~~~~~~~~~~~~~
- if [[ $emsg != '' ]]; then
- echo "${emsg#$lf}" >&2
- usage
- exit 1
- fi
- # Note whether being run from a terminal
- # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- # The "standard" test is to check $PS1 but this test is more reliable
- buf="$( ps -p $$ -o tty 2>&1 )"
- case $buf in
- *TT*-* )
- interactive_flag=$false
- ;;
- *TT* )
- interactive_flag=$true
- ;;
- * )
- echo "$prgnam: Unable to determine if being run interactively. ps output was: $buf" >&2
- exit 1
- esac
- # Up to this point any messages have been given using echo followed by exit 1. Now
- # the essentials for _msg() and finalise() have been established, all future messages
- # will be sent using _msg() and error mesages will then call finalise().
- fct "${FUNCNAME[ 0 ]}" 'started (this message delayed until messaging initialised)'
- msg 'I' "$prgnam version $prg_ver started with command line '$args'"
- [[ $unactioned_aliases != '' ]] \
- && msg W "Reqested aliases already in /etc/aliases. Ignored:$unactioned_aliases"
- # Exit if not running interactively
- # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- if [[ ! $interactive_flag ]]; then
- msg 'E' 'Not running interactively'
- fi
- fct "${FUNCNAME[ 0 ]}" "returning.
- aliases: $aliases
- my_ips: $my_ips
- pf_fqdn: $pf_fqdn
- relay_fqdn: $relay_fqdn
- relay_port: $relay_port
- relay_username: $relay_username
- relay_password: $relay_password"
- } # end of function initialise
- #--------------------------
- # Name: configure
- # Purpose: configures the postfix server
- #--------------------------
- function configure {
- fct "${FUNCNAME[ 0 ]}" 'started'
- # Sanity checks
- # ~~~~~~~~~~~~~
- type postfix >/dev/null 2>&1 || msg 'E' "postfix command not found. Is postfix installed?"
- msg 'I' "$prgnam: checking existing postfix installation ..."
- postfix check && echo 'OK' || while true
- do
- read -p 'Continue? Y to continue, Q to quit > '
- case $REPLY in
- y|Y ) break ;;
- q|Q ) finalise 0 ;;
- * ) echo "Invalid response '$REPLY'"
- esac
- done
- # Derive variables
- # ~~~~~~~~~~~~~~~~
- pf_uq_hostname=${pf_fqdn%%.*} # uq = unqualified
- pf_domain_name=${pf_fqdn#*.}
- relay_uq_hostname=${relay_fqdn%%.*}
- relay_domain_name=${relay_fqdn#*.}
- # Configure postfix
- # ~~~~~~~~~~~~~~~~~
- msg 'I' "$prgnam: configuring postfix ..."
- set -e # Let command errors terminate script
- echo $pf_uq_hostname > /etc/mailname
- cd /etc/postfix
- postconf -e smtpd_tls_CAfile=/etc/postfix/cacert.pem
- postconf -e smtpd_tls_cert_file=/etc/postfix/${pf_uq_hostname}-cert.pem
- postconf -e smtpd_tls_key_file=/etc/postfix/${pf_uq_hostname}-key.pem
- postconf -e smtp_tls_CAfile=/etc/postfix/cacert.pem
- postconf -e smtp_use_tls=yes
- postconf -e smtpd_use_tls=yes
- postconf -e myhostname=$pf_fqdn
- postconf -e myorigin=/etc/mailname
- postconf -e mydestination=$pf_fqdn,localhost.$pf_domain_name,localhost
- postconf -e relayhost=[$relay_fqdn]:$relay_port
- postconf -e mynetworks="127.0.0.0/8 $my_ips"
- postconf -e inet_interfaces=all
- postconf -e default_transport=smtp
- postconf -e relay_transport=smtp
- postconf -e smtp_sasl_auth_enable=yes
- postconf -e smtp_sasl_security_options=noanonymous
- postconf -e smtp_sasl_tls_security_options=noanonymous
- postconf -e smtp_sasl_password_maps=hash:/etc/postfix/sasl_passwd
- postconf -e smtp_generic_maps=hash:/etc/postfix/generic
- postconf -e transport_maps=hash:/etc/postfix/transport
- postconf -e virtual_alias_maps=hash:/etc/postfix/virtual
- echo "# this is for the sasl_passwd${lf}[$relay_fqdn]:$relay_port $relay_username:$relay_password" > /etc/postfix/sasl_passwd
- echo "# this is for the transport${lf}$relay_domain_name smtp:[$relay_fqdn]:$relay_port" > /etc/postfix/transport
- echo "# this is for the generic${lf}bli@$relay_fqdn $relay_username" > /etc/postfix/generic
- echo "# this is for the virtual${lf}root root@localhost" > /etc/postfix/virtual
- postmap virtual generic sasl_passwd transport
- chmod 400 /etc/postfix/sasl_passwd
- cat /usr/lib/ssl/certs/Equifax_Secure_CA.pem >> /etc/postfix/cacert.pem
- if [[ $aliases != '' ]]; then
- msg 'I' "$prgnam: setting up aliases ..."
- echo "$aliases" >> /etc/aliases
- newaliases
- fi
- set +e
- msg 'I' "$prgnam: restarting postfix ..."
- /etc/init.d/postfix restart
- fct "${FUNCNAME[ 0 ]}" 'returning'
- return 0
- } # end of function configure
- #--------------------------
- # Name: msg
- # Purpose: generalised messaging interface
- # Usage: msg class msg_text
- # class must be one of I, W or E indicating Information, Warning or Error
- # msg_text is the text of the message
- # Return code: always zero (exits on error)
- #--------------------------
- function msg {
- local buf indent line message_text preamble
- class="${1:-}"
- case "$class" in
- I | 'E' )
- ;;
- 'W' )
- global_warning_flag=$true
- ;;
- * )
- echo "$prgnam: msg: invalid arguments: '$args'" >&2
- exit 1
- esac
- message_text="$2"
- case "$class" in
- I )
- echo "$message_text" >&1
- ;;
- W )
- echo "WARNING: $message_text" >&1
- ;;
- E )
- echo "ERROR: $message_text" >&2
- [[ ! $finalising_flag ]] && finalise 1
- ;;
- esac
- return 0
- } # end of function msg
- #--------------------------
- # Name: usage
- # Purpose: prints usage message
- #--------------------------
- function usage {
- fct "${FUNCNAME[ 0 ]}" 'started'
- echo "usage: $prgnam [-a alias_list] [-d] [-h] -i ip_address_list -p postfix_server_FQDN -r relay_info_list [-V]" >&2
- if [[ ${1:-} != 'verbose' ]]
- then
- echo "(use -h for help)" >&2
- else
- echo " where:
- -a A space separated list of local_user:alias items to be used as aliases.
- Required by most relay servers when the local postfix server FQDN is
- not publicly DNS resolvable.
- -d turns debugging trace on.
- -h prints this help and exits.
- -i local IP address. If more than one, a space-separated list of local IP addresses.
- -p local postfix server fully qualified domain name (FQDN).
- -r relaying SMTP server information as:
- server_FQDN:port:username:password
- The components must not include a : character
- -V prints the program version and exits.
- " >&2
- fi
- fct "${FUNCNAME[ 0 ]}" 'returning'
- } # end of function usage
- #--------------------------
- # Name: main
- # Purpose: where it all happens
- #--------------------------
- initialise "${@:-}"
- configure
- finalise 0
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement