catkin

configure_postfix.sh

Nov 5th, 2011
143
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #! /bin/bash
  2.  
  3. # Purpose:
  4. #    Configures postfix for sending via a relay (smart host). Suitable for
  5. #    sending mail from a postfix server behind a NATting router.
  6.  
  7. # Usage:
  8. #    See usage function below (search for "function usage") or use -h option.
  9.    
  10. # Environment:
  11. #    Developed and tested on Debian Squeeze with
  12. #    * bash 4.1.5
  13. #    * postfix 2.7.1
  14.  
  15. # History:
  16. #    23oct11 Charles
  17. #    * First version
  18.  
  19. # Wishlist (in approx descending order of importance/triviality):
  20. #    * None
  21.  
  22. # Programmers' notes: error and trap handling:
  23. #   * All errors are fatal and exit or finalise() is called.
  24. #   * At any time, a trapped event may transfer control to finalise().
  25.  
  26. # Programmers' notes: variable names and values
  27. #    * Directory name variables are called *_dir and have values ending in /
  28. #    * File name variables are called *_afn have values beginning with /
  29. #    * Logic flag variables are called *_flag and have values $true or $false
  30. #    * $buf is a localised scratch buffer.
  31. #    * $lf is a line feed.
  32.  
  33. # Programmers' notes: maximum line length ruler
  34. # -------+---------+---------+---------+---------+---------+---------+---------+
  35. #        10        20        30        40        50        60        70        80
  36.  
  37. # Programmers' notes: function call tree
  38. #    +
  39. #    |
  40. #    +-- initialise
  41. #    |   |
  42. #    |   +-- ck_email_address
  43. #    |   |
  44. #    |   +-- ck_ip_address
  45. #    |   |
  46. #    |   +-- usage
  47. #    |
  48. #    +-- configure
  49. #    |
  50. #    +-- finalise
  51. #
  52. # Utility functions called from various places:
  53. #     ck_file fct msg
  54.  
  55. # Function definitions in alphabetical order.  Execution begins after the last function definition.
  56.  
  57. #--------------------------
  58. # Name: ck_email_address
  59. # Purpose: checks argument is a valid email address
  60. # $1 - value to test
  61. #--------------------------
  62. function ck_email_address {
  63.  
  64.     fct "${FUNCNAME[ 0 ]}" "started with argument $1"
  65.  
  66.     if [[ $1 =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$ ]]; then
  67.         fct "${FUNCNAME[ 0 ]}" "returning 0"
  68.         return 0
  69.     else
  70.         fct "${FUNCNAME[ 0 ]}" "returning 1"
  71.         return 1
  72.     fi
  73.  
  74. }  # end of function ck_email_address
  75.  
  76. #--------------------------
  77. # Name: ck_ip_address
  78. # Purpose: checks argument is a valid IP address
  79. # $1 - value to test
  80. # Acknowledgments to Mitch Frazier and Linux Journal: http://www.linuxjournal.com/content/validating-ip-address-bash-script
  81. #--------------------------
  82. function ck_ip_address {
  83.  
  84.     fct "${FUNCNAME[ 0 ]}" "started with argument $1"
  85.  
  86.     local array oIFS retval
  87.  
  88.     retval=1
  89.  
  90.     if [[ $1 =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
  91.         oIFS=$IFS
  92.         IFS='.'
  93.         array=( $1 )
  94.         IFS=$oIFS
  95.         [[ ${array[0]} -le 255 && ${array[1]} -le 255 && ${array[2]} -le 255 && ${array[3]} -le 255 ]]
  96.         retval=$?
  97.     fi
  98.  
  99.     fct "${FUNCNAME[ 0 ]}" "returning $retval"
  100.     return $retval
  101.  
  102. }  # end of function ck_ip_address
  103.  
  104. #--------------------------
  105. # Name: fct
  106. # Purpose: function call trace (for debugging)
  107. # $1 - name of calling function
  108. # $2 - message.  If it starts with "started" or "returning" then the output is prettily indented
  109. #--------------------------
  110. function fct {
  111.  
  112.     if [[ ! $debugging_flag ]]; then
  113.         return 0
  114.     fi
  115.  
  116.     fct_ident="${fct_indent:=}"
  117.  
  118.     case $2 in
  119.         'started'* )
  120.             fct_indent="$fct_indent  "
  121.             msg 'I' "DEBUG: $fct_indent$1: $2"
  122.             ;;
  123.         'returning'* )
  124.             msg 'I' "DEBUG: $fct_indent$1: $2"
  125.             fct_indent="${fct_indent#  }"
  126.             ;;
  127.         * )
  128.             msg 'I' "DEBUG: $fct_indent$1: $2"
  129.     esac
  130.  
  131. }  # end of function fct
  132.  
  133. #--------------------------
  134. # Name: finalise
  135. # Purpose: cleans up and gets out of here
  136. #--------------------------
  137. function finalise {
  138.     fct "${FUNCNAME[ 0 ]}" 'started'
  139.  
  140.     local buf msg msgs my_retval retval
  141.  
  142.     # Set return value
  143.     # ~~~~~~~~~~~~~~~~
  144.     # Choose the highest and give message if finalising on a trapped signal
  145.     my_retval="${prgnam_retval:-0}"
  146.     if [[ $1 -gt $my_retval ]]; then
  147.         my_retval=$1
  148.     fi
  149.     case $my_retval in
  150.         129 | 130 | 131 | 143 )
  151.             case $my_retval in
  152.                 129 )
  153.                     buf='SIGHUP'
  154.                     ;;
  155.                 130 )
  156.                     buf='SIGINT'
  157.                     ;;
  158.                 131 )
  159.                     buf='SIGQUIT'
  160.                     ;;
  161.                 143 )
  162.                     buf='SIGTERM'
  163.                     ;;
  164.             esac
  165.             msg 'I' "finalising on $buf"
  166.             ;;
  167.     esac
  168.  
  169.     # Final log messages
  170.     # ~~~~~~~~~~~~~~~~~~
  171.     finalising_flag=$true   # Used to avoid infinite recursion
  172.     msgs=
  173.     if [[ $global_warning_flag ]]; then
  174.         msgs="$msgs${lf}There were WARNINGs"
  175.         if [[ $my_retval -eq 0 ]]; then
  176.             my_retval=1
  177.         fi
  178.     fi
  179.     if [[ "$msgs" != '' ]]; then
  180.         msgs="${msgs#$lf}"        # strip leading linefeed
  181.         msg 'E' "$msgs"
  182.     fi
  183.     msg 'I' "Exiting with return value $my_retval"
  184.  
  185.     # Exit
  186.     # ~~~~
  187.     fct "${FUNCNAME[ 0 ]}" 'exiting'
  188.     exit $my_retval
  189.  
  190. }  # end of function finalise
  191.  
  192. #--------------------------
  193. # Name: initialise
  194. # Purpose: sets up environment and parses command line
  195. #--------------------------
  196. function initialise {
  197.  
  198.     local alias args array i_opt_flag emsg r_opt_flag username
  199.  
  200.     # Configure shell environment
  201.     # ~~~~~~~~~~~~~~~~~~~~~~~~~~~
  202.     export PATH=/usr/sbin:/sbin:/usr/bin:/bin
  203.     IFS=$' \n\t'
  204.     set -o nounset
  205.     umask 0022
  206.     unalias -a
  207.    
  208.     # Configure traps
  209.     # ~~~~~~~~~~~~~~~
  210.     # Set up essentials for finalise() and what it may call before setting traps
  211.     # because finalise() may be called at any time after then
  212.     false=
  213.     true=true
  214.  
  215.     debugging_flag=$false
  216.     finalising_flag=$false
  217.     global_warning_flag=$false
  218.     prgnam=${0##*/}            # program name w/o path
  219.     trap 'finalise 129' 'HUP'
  220.     trap 'finalise 130' 'INT'
  221.     trap 'finalise 131' 'QUIT'
  222.     trap 'finalise 143' 'TERM'
  223.  
  224.     # Utility variables
  225.     # ~~~~~~~~~~~~~~~~~
  226.     lf=$'\n'                            # ASCII linefeed, a.k.a newline
  227.     prg_ver='0.0'
  228.  
  229.     # Parse command line
  230.     # ~~~~~~~~~~~~~~~~~~
  231.     aliases=
  232.     args="${@:-}"
  233.     emsg=''
  234.     i_opt_flag=$false
  235.     my_ips=
  236.     pf_fqdn=
  237.     r_opt_flag=$false
  238.     unactioned_aliases=
  239.     while getopts a:dhi:p:r:V opt 2>/dev/null
  240.     do
  241.         case $opt in
  242.             a )
  243.                 array=( $OPTARG )
  244.                 for (( i=0; i<${#array[*]}; i++ ))
  245.                 do
  246.                     username=${array[i]%:*}
  247.                     alias=${array[i]#*:}
  248.                     ck_email_address "$alias" || emsg="$emsg${lf}Option -a: invalid email address: $alias"
  249.                     grep -q "$username:$alias" /etc/aliases \
  250.                         && unactioned_aliases="$unactioned_aliases${lf}$username: $alias" \
  251.                         || aliases="$aliases${lf}$username: $alias"
  252.                 done
  253.                 aliases=${aliases#$lf}
  254.                 ;;
  255.             d )
  256.                 debugging_flag=$true
  257.                 ;;
  258.             h )
  259.                 usage verbose
  260.                 exit 0
  261.                 ;;
  262.             i )
  263.                 i_opt_flag=$true
  264.                 array=( $OPTARG )
  265.                 for (( i=0; i<${#array[*]}; i++ ))
  266.                 do
  267.                     ck_ip_address ${array[i]} || emsg="$emsg${lf}Option -i: invalid IP address: ${array[i]}"
  268.                     my_ips="$my_ips ${array[i]}"
  269.                 done
  270.                 my_ips=${my_ips# }
  271.                 ;;
  272.             p )
  273.                 pf_fqdn=$OPTARG
  274.                 [[ $pf_fqdn =~ ^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,12}$ ]] \
  275.                     || emsg="$emsg${lf}Option -p: invalid domain name: $pf_fqdn"
  276.                 ;;
  277.             r )
  278.                 r_opt_flag=$true
  279.                 IFS=':'
  280.                 array=( $OPTARG )
  281.                 IFS=
  282.                 if [[ ${#array[*]} -ne 4 ]]; then
  283.                     emsg="$emsg${lf}Option -r: incorrect number of relaying SMTP server info components: $OPTARG"
  284.                 else
  285.                     relay_fqdn=${array[0]}
  286.                     [[ $relay_fqdn =~ ^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}$ ]] \
  287.                         || emsg="$emsg${lf}Option -r: invalid domain name: $relay_fqdn"
  288.                     relay_port=${array[1]}
  289.                     [[ $relay_port =~ ^[0-9]+$ ]] || emsg="$emsg${lf}Option -r: relaying SMTP port not numeric: $relay_port"
  290.                     relay_username=${array[2]}
  291.                     relay_password=${array[3]}
  292.                 fi
  293.                 ;;
  294.             V )
  295.                 echo "$prgnam version $prg_ver"
  296.                 exit 0
  297.                 ;;
  298.             * )
  299.                 emsg="$emsg${lf}Invalid option '$opt'"
  300.         esac
  301.     done
  302.  
  303.     # Test for mandatory options not set
  304.     # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  305.     [[ $i_opt_flag = $false || $my_ips = '' ]] && emsg="$emsg${lf}Mandatory option -i not given or empty option argument"
  306.     [[ $pf_fqdn = '' ]] && emsg="$emsg${lf}Mandatory option -p not given or empty option argument"
  307.     if [[ $r_opt_flag = $false ]]; then
  308.         emsg="$emsg${lf}Mandatory option -r not given"
  309.     elif [[ ${#array[*]} -eq 4 ]]; then
  310.         [[ $relay_fqdn = '' ]] && emsg="$emsg${lf}Option -r: server_FQDN empty"
  311.         [[ $relay_port = '' ]] && emsg="$emsg${lf}Option -r: port empty"
  312.         [[ $relay_username = '' ]] && emsg="$emsg${lf}Option -r: username empty"
  313.         [[ $relay_password = '' ]] && emsg="$emsg${lf}Option -r: password empty"
  314.     fi
  315.  
  316.     # Test for extra arguments
  317.     # ~~~~~~~~~~~~~~~~~~~~~~~~
  318.     shift $(( $OPTIND-1 ))
  319.     if [[ $* != '' ]]; then
  320.         emsg="$emsg${lf}Invalid extra argument(s) '$*'"
  321.     fi
  322.  
  323.     # Report any errors
  324.     # ~~~~~~~~~~~~~~~~~
  325.     if [[ $emsg != '' ]]; then
  326.         echo "${emsg#$lf}" >&2
  327.         usage
  328.         exit 1
  329.     fi
  330.    
  331.     # Note whether being run from a terminal
  332.     # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  333.     # The "standard" test is to check $PS1 but this test is more reliable
  334.     buf="$( ps -p $$ -o tty 2>&1 )"
  335.     case $buf in
  336.         *TT*-* )
  337.             interactive_flag=$false
  338.             ;;
  339.         *TT* )
  340.             interactive_flag=$true
  341.             ;;
  342.         * )
  343.             echo "$prgnam: Unable to determine if being run interactively.  ps output was: $buf" >&2
  344.             exit 1
  345.     esac
  346.  
  347.     # Up to this point any messages have been given using echo followed by exit 1.  Now
  348.     # the essentials for _msg() and finalise() have been established, all future messages
  349.     # will be sent using _msg() and error mesages will then call finalise().
  350.  
  351.     fct "${FUNCNAME[ 0 ]}" 'started (this message delayed until messaging initialised)'
  352.  
  353.     msg 'I' "$prgnam version $prg_ver started with command line '$args'"
  354.     [[ $unactioned_aliases != '' ]] \
  355.        && msg W "Reqested aliases already in /etc/aliases. Ignored:$unactioned_aliases"
  356.  
  357.     # Exit if not running interactively
  358.     # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  359.     if [[ ! $interactive_flag ]]; then
  360.         msg 'E' 'Not running interactively'
  361.     fi
  362.  
  363.     fct "${FUNCNAME[ 0 ]}" "returning.
  364.  aliases: $aliases
  365.  my_ips: $my_ips
  366.  pf_fqdn: $pf_fqdn
  367.  relay_fqdn: $relay_fqdn
  368.  relay_port: $relay_port
  369.  relay_username: $relay_username
  370.  relay_password: $relay_password"
  371.  
  372. }  # end of function initialise
  373.  
  374. #--------------------------
  375. # Name: configure
  376. # Purpose: configures the postfix server
  377. #--------------------------
  378. function configure {
  379.  
  380.     fct "${FUNCNAME[ 0 ]}" 'started'
  381.  
  382.     # Sanity checks
  383.     # ~~~~~~~~~~~~~
  384.     type postfix >/dev/null 2>&1 || msg 'E' "postfix command not found.  Is postfix installed?"
  385.     msg 'I' "$prgnam: checking existing postfix installation ..."
  386.     postfix check && echo 'OK' || while true
  387.     do
  388.         read -p 'Continue?  Y to continue, Q to quit > '
  389.         case $REPLY in
  390.         y|Y ) break ;;
  391.                 q|Q ) finalise 0 ;;
  392.                 * ) echo "Invalid response '$REPLY'"
  393.         esac
  394.     done
  395.  
  396.     # Derive variables
  397.     # ~~~~~~~~~~~~~~~~
  398.     pf_uq_hostname=${pf_fqdn%%.*}  # uq = unqualified
  399.     pf_domain_name=${pf_fqdn#*.}
  400.     relay_uq_hostname=${relay_fqdn%%.*}
  401.     relay_domain_name=${relay_fqdn#*.}
  402.    
  403.     # Configure postfix
  404.     # ~~~~~~~~~~~~~~~~~
  405.     msg 'I' "$prgnam: configuring postfix ..."
  406.     set -e    # Let command errors terminate script
  407.     echo $pf_uq_hostname > /etc/mailname
  408.     cd /etc/postfix
  409.     postconf -e smtpd_tls_CAfile=/etc/postfix/cacert.pem
  410.     postconf -e smtpd_tls_cert_file=/etc/postfix/${pf_uq_hostname}-cert.pem
  411.     postconf -e smtpd_tls_key_file=/etc/postfix/${pf_uq_hostname}-key.pem
  412.     postconf -e smtp_tls_CAfile=/etc/postfix/cacert.pem
  413.     postconf -e smtp_use_tls=yes
  414.     postconf -e smtpd_use_tls=yes
  415.     postconf -e myhostname=$pf_fqdn
  416.     postconf -e myorigin=/etc/mailname
  417.     postconf -e mydestination=$pf_fqdn,localhost.$pf_domain_name,localhost
  418.     postconf -e relayhost=[$relay_fqdn]:$relay_port
  419.     postconf -e mynetworks="127.0.0.0/8 $my_ips"
  420.     postconf -e inet_interfaces=all
  421.     postconf -e default_transport=smtp
  422.     postconf -e relay_transport=smtp
  423.     postconf -e smtp_sasl_auth_enable=yes
  424.     postconf -e smtp_sasl_security_options=noanonymous
  425.     postconf -e smtp_sasl_tls_security_options=noanonymous
  426.     postconf -e smtp_sasl_password_maps=hash:/etc/postfix/sasl_passwd
  427.     postconf -e smtp_generic_maps=hash:/etc/postfix/generic
  428.     postconf -e transport_maps=hash:/etc/postfix/transport
  429.     postconf -e virtual_alias_maps=hash:/etc/postfix/virtual
  430.     echo "# this is for the sasl_passwd${lf}[$relay_fqdn]:$relay_port $relay_username:$relay_password" > /etc/postfix/sasl_passwd
  431.     echo "# this is for the transport${lf}$relay_domain_name smtp:[$relay_fqdn]:$relay_port" > /etc/postfix/transport
  432.     echo "# this is for the generic${lf}bli@$relay_fqdn $relay_username" > /etc/postfix/generic
  433.     echo "# this is for the virtual${lf}root root@localhost" > /etc/postfix/virtual
  434.     postmap virtual generic sasl_passwd transport
  435.     chmod 400 /etc/postfix/sasl_passwd
  436.     cat /usr/lib/ssl/certs/Equifax_Secure_CA.pem >> /etc/postfix/cacert.pem
  437.     if [[ $aliases != '' ]]; then
  438.         msg 'I' "$prgnam: setting up aliases ..."
  439.     echo "$aliases" >> /etc/aliases
  440.         newaliases
  441.     fi
  442.     set +e
  443.  
  444.     msg 'I' "$prgnam: restarting postfix ..."
  445.     /etc/init.d/postfix restart
  446.  
  447.     fct "${FUNCNAME[ 0 ]}" 'returning'
  448.     return 0
  449.  
  450. }  # end of function configure
  451.  
  452. #--------------------------
  453. # Name: msg
  454. # Purpose: generalised messaging interface
  455. # Usage: msg class msg_text
  456. #    class must be one of I, W or E indicating Information, Warning or Error
  457. #    msg_text is the text of the message
  458. # Return code:  always zero (exits on error)
  459. #--------------------------
  460. function msg {
  461.  
  462.     local buf indent line message_text preamble
  463.  
  464.     class="${1:-}"
  465.     case "$class" in
  466.         I | 'E' )
  467.             ;;
  468.         'W' )
  469.             global_warning_flag=$true
  470.             ;;
  471.         * )
  472.             echo "$prgnam: msg: invalid arguments: '$args'" >&2
  473.             exit 1
  474.     esac
  475.     message_text="$2"
  476.  
  477.     case "$class" in
  478.         I )
  479.             echo "$message_text" >&1
  480.             ;;
  481.         W )
  482.             echo "WARNING: $message_text" >&1
  483.             ;;
  484.         E )
  485.             echo "ERROR: $message_text" >&2
  486.             [[ ! $finalising_flag ]] && finalise 1
  487.             ;;
  488.     esac
  489.  
  490.     return 0
  491.  
  492. }  #  end of function msg
  493.  
  494. #--------------------------
  495. # Name: usage
  496. # Purpose: prints usage message
  497. #--------------------------
  498. function usage {
  499.     fct "${FUNCNAME[ 0 ]}" 'started'
  500.     echo "usage: $prgnam [-a alias_list] [-d] [-h] -i ip_address_list -p postfix_server_FQDN -r relay_info_list [-V]" >&2    
  501.     if [[ ${1:-} != 'verbose' ]]
  502.     then
  503.         echo "(use -h for help)" >&2
  504.     else
  505.         echo "  where:
  506.    -a A space separated list of local_user:alias items to be used as aliases.
  507.       Required by most relay servers when the local postfix server FQDN is
  508.       not publicly DNS resolvable.
  509.    -d turns debugging trace on.
  510.    -h prints this help and exits.
  511.    -i local IP address.  If more than one, a space-separated list of local IP addresses.
  512.    -p local postfix server fully qualified domain name (FQDN).
  513.    -r relaying SMTP server information as:
  514.         server_FQDN:port:username:password
  515.       The components must not include a : character
  516.    -V prints the program version and exits.
  517. " >&2
  518.     fi
  519.  
  520.     fct "${FUNCNAME[ 0 ]}" 'returning'
  521.  
  522. }  # end of function usage
  523.  
  524. #--------------------------
  525. # Name: main
  526. # Purpose: where it all happens
  527. #--------------------------
  528. initialise "${@:-}"
  529. configure
  530. finalise 0
  531.  
RAW Paste Data