Email - bash-postfix-encrypt-filter.sh (encryption content filter)
Email Solution · Config document · referenced from Automatic email encryption
The in-house Postfix content filter, published whole. Postfix pipes every message that arrived over SMTP or SMTPS into it, one recipient at a time; the script sends it on unchanged, encrypted with S/MIME or PGP/MIME, or replaced by an information message, by calling sendmail. The first 185 lines are its own installation notes.
| Item | Value |
|---|---|
| Path on the server | /usr/local/bin/bash-postfix-encrypt-filter.sh |
| Shown here | DC2-A-VCMSX001 |
| Also on | DC2-B-VCMSX001, identical (diff is empty) |
| Owner and mode | bash-postfix-encrypt-filter:root, 500 |
| Runs as | bash-postfix-encrypt-filter, started by the Postfix pipe service of the same name |
| Activated with | content_filter=bash-postfix-encrypt-filter: on the smtp and smtps services in master.cf, then postfix reload |
| Version | 0.1, 2018, the only version |
| Needs | openssl, gnupg2, procmail (for formail), awk, uuidgen |
The script
#!/bin/bash ################################################################################## # BASH Postfix Encrypt Filter ################################################################################## # # FILE : bash-postfix-encrypt-filter.sh # AUTHOR : admin01 # DATE : 2018 # VERSION : 0.1 # HISTORY : # 0.1 - Initial version with S/MIME and PGP/MIME support # # TODO : # - Exception list with grep (need to perform benchmark). Cons of using grep is new dependency. # ################################################################################## # INSTALLATION ################################################################################## # # # 0> Install prerequizities/dependencies # ------------------------------------------------------------------ # # yum install openssl gnupg2 procmail awk # # # 1> Upload the script to email server and copy to desired directory # ------------------------------------------------------------------ # # cp /tmp/bash-postfix-encrypt-filter /usr/local/bin/ # # # 2> Add user for content filter # ------------------------------ # # useradd bash-postfix-encrypt-filter # # # 3> Create directory structure for content filter + set correct owner and permissions # ------------------------------------------------------------------------------------ # # mkdir -p /var/spool/postfix/bash-postfix-encrypt-filter # # mkdir -p /var/spool/postfix/bash-postfix-encrypt-filter/archive # # mkdir -p /var/spool/postfix/bash-postfix-encrypt-filter/log # # mkdir -p /var/spool/postfix/bash-postfix-encrypt-filter/pgp # # mkdir -p /var/spool/postfix/bash-postfix-encrypt-filter/smime # # mkdir -p /var/spool/postfix/bash-postfix-encrypt-filter/work # # chown -R bash-postfix-encrypt-filter:root /var/spool/postfix/bash-postfix-encrypt-filter/ # # chmod -R 700 /var/spool/postfix/bash-postfix-encrypt-filter # # # 4> Create file with sender (MAIL FROM:) email addresses as encryption exception list + set correct owner and permissions # ---------------------------------------------------------------------------------------------------- # # vi /var/spool/postfix/bash-postfix-encrypt-filter/exceptions_encrypt_from.txt # ---------------------------------------------------------------------------------------------------- # mailer-daemon@ad.example.net # notification.env3@ad.example.net # notification.env3-tes@ad.example.net # notification_ad@ad.example.net # notification.env1@ad.example.net # notification.env1-tes@ad.example.net # notification.env2@ad.example.net # notification.env2-tes@ad.example.net # notification-test@ad.example.net # p1.secondlevel@ad.example.net # p2.secondlevel@ad.example.net # p3.secondlevel@ad.example.net # training.secondlevel@ad.example.net # ---------------------------------------------------------------------------------------------------- # # chown -R bash-postfix-encrypt-filter:root /var/spool/postfix/bash-postfix-encrypt-filter/exceptions_encrypt_from.txt # # chmod -R 400 /var/spool/postfix/bash-postfix-encrypt-filter/exceptions_encrypt_from.txt # # # 5> Create file with recipient (RCPT TO:) email addresses as encryption exception list + set correct owner and permissions # ---------------------------------------------------------------------------------------------------- # # vi /var/spool/postfix/bash-postfix-encrypt-filter/exceptions_encrypt_to.txt # @ad.example.net # ---------------------------------------------------------------------------------------------------- # # chown -R bash-postfix-encrypt-filter:root /var/spool/postfix/bash-postfix-encrypt-filter/exceptions_encrypt_to.txt # # chmod -R 400 /var/spool/postfix/bash-postfix-encrypt-filter/exceptions_encrypt_to.txt # # # 6> Define and enable encrypt filter # ----------------------------------- # # vi /etc/postfix/master.cf # --------------------------- # # Definition of BASH postfix encrypt filter # bash-postfix-encrypt-filter unix - n n - - pipe # flags=Rq user=bash-postfix-encrypt-filter null_sender= # argv=/usr/local/bin/bash-postfix-encrypt-filter.sh -f ${sender} -- ${recipient} # # # Enable BASH postfix encrypt filter for SMTP only # smtp inet n - n - - smtpd # -o content_filter=bash-postfix-encrypt-filter: # # Enable BASH postfix encrypt filter for SMTPS only # smtps inet n - n - - smtpd # -o syslog_name=postfix/smtps # -o smtpd_tls_wrappermode=yes # -o smtpd_sasl_auth_enable=yes # -o smtpd_reject_unlisted_recipient=no # -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject # -o milter_macro_daemon_name=ORIGINATING # -o content_filter=bash-postfix-encrypt-filter: # ------------------------- # # # 7> Set maximal number of recipiemts per message delivery for encrypt filter transport # ------------------------------------------------------------------------------------- # # vi /etc/postfix/main.cf # -------------------------- # # Set the maximal number of recipients per message delivery for transport "bash-encrypt-content-filter". # # Setting this parameter to a value of 1 affects email deliveries as follows: # # - It changes the meaning of the corresponding per-destination concurrency limit, from concurrency # # of deliveries to the same domain into concurrency of deliveries to the same recipient. Different # # recipients are delivered in parallel, subject to the process limits specified in master.cf. # # - It changes the meaning of the corresponding per-destination rate delay, from the delay between # # deliveries to the same domain into the delay between deliveries to the same recipient. Again, # # different recipients are delivered in parallel, subject to the process limits specified in master.cf. # # - It changes the meaning of other corresponding per-destination settings in a similar manner, from # # settings for delivery to the same domain into settings for delivery to the same recipient. # # # # REF1: http://www.postfix.org/postconf.5.html#default_destination_recipient_limit # # REF2: http://www.postfix.org/postconf.5.html#transport_destination_recipient_limit # bash-postfix-encrypt-filter_destination_recipient_limit=1 # ----------------------- # # # 8> Deploy SeLinux policy for postfix daemon with rules for this content filter # ------------------------------------------------------------------------------ # # vi /etc/selinux/postfix-local.te # ------------------------------- # module postfix-local 1.0; # # require { # type var_t; # type system_mail_t; # type postfix_spool_t; # type postfix_pipe_t; # type gpg_exec_t; # type gpg_agent_exec_t; # class dir { create add_name remove_name write rmdir }; # class file { create execute execute_no_trans getattr link lock open read rename setattr unlink write }; # } # # #============= postfix_pipe_t ============== # allow postfix_pipe_t gpg_agent_exec_t:file { execute read }; # allow postfix_pipe_t gpg_exec_t:file { execute execute_no_trans open read }; # allow postfix_pipe_t postfix_spool_t:dir { create add_name remove_name write rmdir }; # allow postfix_pipe_t postfix_spool_t:file { create open write read getattr unlink }; # allow postfix_pipe_t var_t:dir { add_name remove_name write }; # # #!!!! WARNING: 'var_t' is a base type. # allow postfix_pipe_t var_t:file { create getattr link lock open read rename setattr unlink write }; # # #============= system_mail_t ============== # allow system_mail_t postfix_spool_t:file { getattr read }; # ------------------------------- # # # 9> Convert type enforcement file (.te) into a policy module (.mod) # ----------------------------------------------------------------- # # cd /etc/selinux # # checkmodule -M -m -o postfix-local.mod postfix-local.te # # # 10> Compile policy module (.mod) into policy package (.pp) # ---------------------------------------------------------- # # semodule_package -o postfix-local.pp -m postfix-local.mod # # # 11> Load SeLinux Policy Package module "systemd-machined-local.pp" into running system # ----------------------------------------------------------------------------------- # # semodule -i postfix-local.pp # # # 12> Upload SMIME certificates in the form "email@address.com.cer" to SMIME directory # (/var/spool/postfix/bash-postfix-encrypt-filter/pgp) for each recipient email address for which # is needed to encrypt email messsage. # -------------------------------------------------------------------------------------------- # # cp /tmp/email@address.com.cer /var/spool/postfix/bash-postfix-encrypt-filter/smime/ # # # 13> Upload PGP/GPG public key in the form "email@address.com.asc" to PGP directory # (/var/spool/postfix/bash-postfix-encrypt-filter/pgp) for each recipient email address for which # is needed to encrypt email messsage. # -------------------------------------------------------------------------------------------- # # cp /tmp/email@address.com.asc /var/spool/postfix/bash-postfix-encrypt-filter/pgp/ # # ################################################################################## # VARIABLES ################################################################################## # # ARCHIVING # # ARCHIVE_ORIGINAL - Enable (1) or disable (0) archiving of input clear email messages # ARCHIVE_ENCRYPTED - Enable (1) or disable (0) archiving of output encrypted email messages # ARCHIVE_RETENTION - Specify (in days) retention of archived email messages. # ARCHIVE_ORIGINAL=1 ARCHIVE_ENCRYPTED=1 ARCHIVE_RETENTION=1 # # ENCRYPTION # # ENCRYPT_PREFERENCE - Set encryption preference to SMIME (smime) or PGP (pgp). # ENCRYPT_EXCEPTIONS_FROM - List of sender/source email addresses as exceptions for encryption. # ENCRYPT_EXCEPTIONS_TO - List of recipient/destination email addresses as exceptions for encryption. # ENCRYPT_PREFERENCE="smime" ENCRYPT_EXCEPTIONS_FROM="/var/spool/postfix/bash-postfix-encrypt-filter/exceptions_encrypt_from.txt" ENCRYPT_EXCEPTIONS_TO="/var/spool/postfix/bash-postfix-encrypt-filter/exceptions_encrypt_to.txt" # # LOGGING # # LOGGING_ENABLED - Enable (1) or disable (0) logging to file specified by LOGGING_FILE variable. # LOGGING_DATE_FORMAT - Logging date format (for syntax -> man date) # LOGGING_ENABLED=1 LOGGING_DATE_FORMAT="%Y-%m-%d %H:%M:%S (%Z)" ################################################################################## # INITIALIZATION ################################################################################## # # Email variables # INSPECT_EMAIL_ID=$$ INSPECT_EMAIL_FROM=$2 INSPECT_EMAIL_TO=$4 # # Encryption # ENCRYPT_SMIME_POSSIBLE=0 ENCRYPT_PGP_POSSIBLE=0 ################################################################################## # SCRIPT INTERNAL SETTINGS ################################################################################## # # SCRIPT DIRECTORIES # DIRECTORY_BASE="/var/spool/postfix/bash-postfix-encrypt-filter" DIRECTORY_WORK="$DIRECTORY_BASE/work" DIRECTORY_PGP="$DIRECTORY_BASE/pgp" DIRECTORY_SMIME="$DIRECTORY_BASE/smime" DIRECTORY_ARCHIVE="$DIRECTORY_BASE/archive" DIRECTORY_LOGGING="$DIRECTORY_BASE/log" # # SCRIPT LOGGING FILE # LOGGING_FILE="$DIRECTORY_LOGGING/bash-postfix-encrypt-filter.log" # # INITIALIZATION OF TEMPORARY VARIABLES FOR INSPECTION # INSPECT_DIRECTORY="$DIRECTORY_WORK/$INSPECT_EMAIL_ID" INSPECT_EMAIL_ORIGINAL="$INSPECT_DIRECTORY/in.$INSPECT_EMAIL_ID" INSPECT_EMAIL_BACKUP="$INSPECT_EMAIL_ORIGINAL.bck" INSPECT_EMAIL_ENCRYPTED="$INSPECT_DIRECTORY/in.$INSPECT_EMAIL_ID.enc" INSPECT_EMAIL_INFORMATION="$INSPECT_DIRECTORY/in.$INSPECT_EMAIL_ID.inf" INSPECT_EMAIL_TO_SMIME="$DIRECTORY_SMIME/$INSPECT_EMAIL_TO.cer" INSPECT_EMAIL_TO_PGP="$DIRECTORY_PGP/$INSPECT_EMAIL_TO.asc" # # SCRIPT MESSAGES # MESSAGE_INSPECT_DIR_CANNOT_CREATE="Cannot create inspection directory '$INSPECT_DIRECTORY'." MESSAGE_INSPECT_DIR_NOEXIST="Inspection directory '$INSPECT_DIRECTORY' does not exist." MESSAGE_INSPECT_FILE_CANNOT_SAVE="Cannot save mail to file." MESSAGE_INSPECT_FILE_CANNOT_BACKUP="Cannot create backup of original message." MESSAGE_INSPECT_ENCRYPT_SMIME_PROBLEM="Cannot encrypt original message with SMIME." MESSAGE_INSPECT_ENCRYPT_PGP_PROBLEM="Cannot encrypt original message with PGP/GPG." MESSAGE_FAIL_SENDING_ORIGINAL_MAIL="MAIL-ID=$INSPECT_EMAIL_ID:Original email message for $INSPECT_EMAIL_TO failed to send." MESSAGE_FAIL_SENDING_ENCRYPTED_MAIL="MAIL-ID=$INSPECT_EMAIL_ID:Encrypted email message for $INSPECT_EMAIL_TO failed to send." MESSAGE_FAIL_SENDING_INFORMATION_MAIL="MAIL-ID=$INSPECT_EMAIL_ID:Information email message for $INSPECT_EMAIL_TO failed to send." MESSAGE_PGP_KEY_CANNOT_IMPORT="MAIL-ID=$INSPECT_EMAIL_ID:PGP/GPG public key for $INSPECT_EMAIL_TO cannot be imported." # # LOGGING MESSAGES # LOGGING_MESSAGE_INSPECT_DIR_CANNOT_CREATE="MAIL-ID=$INSPECT_EMAIL_ID:Cannot create inspection directory '$INSPECT_DIRECTORY'." LOGGING_MESSAGE_INSPECT_DIR_NOEXIST="MAIL-ID=$INSPECT_EMAIL_ID:Inspection directory '$INSPECT_DIRECTORY' does not exist." LOGGING_MESSAGE_INSPECT_FILE_CANNOT_SAVE="MAIL-ID=$INSPECT_EMAIL_ID:Cannot save mail to file." LOGGING_MESSAGE_INSPECT_FILE_CANNOT_BACKUP="MAIL-ID=$INSPECT_EMAIL_ID:Cannot create backup of original message." LOGGING_MESSAGE_INSPECT_ENCRYPT_SMIME_PROBLEM="MAIL-ID=$INSPECT_EMAIL_ID:Cannot encrypt original message with SMIME." LOGGING_MESSAGE_INSPECT_ENCRYPT_PGP_PROBLEM="MAIL-ID=$INSPECT_EMAIL_ID:Cannot encrypt original message with PGP/GPG." LOGGING_MESSAGE_MAIL_INPUT="MAIL-ID=$INSPECT_EMAIL_ID:Email message entered the filter." LOGGING_MESSAGE_MAIL_BACKUP="MAIL-ID=$INSPECT_EMAIL_ID:Creating backup of the original email message." LOGGING_MESSAGE_MAIL_SENDER_ENCRYPTION_EXCEPTION="MAIL-ID=$INSPECT_EMAIL_ID:Sender/Source email address $INSPECT_EMAIL_FROM is listed in encryption exception list = no encryption will be applied." LOGGING_MESSAGE_MAIL_RECIPIENT_ENCRYPTION_EXCEPTION="MAIL-ID=$INSPECT_EMAIL_ID:Recipient/Destination email address $INSPECT_EMAIL_TO is listed in encryption exception list = no encryption will be applied." LOGGING_MESSAGE_MAIL_RECIPIENT_CHANGE="MAIL-ID=$INSPECT_EMAIL_ID:Recipient changed to $INSPECT_EMAIL_TO." LOGGING_MESSAGE_MAIL_ENCRYPT_PREFERENCE="MAIL-ID=$INSPECT_EMAIL_ID:Global encryption preference is $ENCRYPT_PREFERENCE." LOGGING_MESSAGE_MAIL_ENCRYPT_PGP_POSSIBLE="MAIL-ID=$INSPECT_EMAIL_ID:PGP/GPG encryption for $INSPECT_EMAIL_TO is possible." LOGGING_MESSAGE_MAIL_ENCRYPT_PGP_NOT_POSSIBLE="MAIL-ID=$INSPECT_EMAIL_ID:PGP/GPG encryption for $INSPECT_EMAIL_TO is not possible!" LOGGING_MESSAGE_MAIL_ENCRYPT_SMIME_POSSIBLE="MAIL-ID=$INSPECT_EMAIL_ID:SMIME encryption for $INSPECT_EMAIL_TO is possible." LOGGING_MESSAGE_MAIL_ENCRYPT_SMIME_NOT_POSSIBLE="MAIL-ID=$INSPECT_EMAIL_ID:SMIME encryption for $INSPECT_EMAIL_TO is not possible!" LOGGING_MESSAGE_MAIL_ENCRYPTED_SMIME="MAIL-ID=$INSPECT_EMAIL_ID:Email message for $INSPECT_EMAIL_TO was successfully encrypted with SMIME." LOGGING_MESSAGE_MAIL_ENCRYPTED_PGP="MAIL-ID=$INSPECT_EMAIL_ID:Email message for $INSPECT_EMAIL_TO was successfully encrypted with PGP/GPG." LOGGING_MESSAGE_MAIL_ENCRYPTION_NOT_POSSIBLE="MAIL-ID=$INSPECT_EMAIL_ID:Email message for $INSPECT_EMAIL_TO is not possible to encrypt with SMIME or PGP/GPG." LOGGING_MESSAGE_FAIL_SENDING_ORIGINAL_MAIL="MAIL-ID=$INSPECT_EMAIL_ID:Original email message for $INSPECT_EMAIL_TO failed to send." LOGGING_MESSAGE_FAIL_SENDING_ENCRYPTED_MAIL="MAIL-ID=$INSPECT_EMAIL_ID:Encrypted email message for $INSPECT_EMAIL_TO failed to send." LOGGING_MESSAGE_FAIL_SENDING_INFORMATION_MAIL="MAIL-ID=$INSPECT_EMAIL_ID:Information email message for $INSPECT_EMAIL_TO failed to send." LOGGING_MESSAGE_SENDING_ORIGINAL_MAIL_FROM="MAIL-ID=$INSPECT_EMAIL_ID:Original email message for $INSPECT_EMAIL_TO was successfully sended (source email address is listed in encryption exception list)." LOGGING_MESSAGE_SENDING_ORIGINAL_MAIL_TO="MAIL-ID=$INSPECT_EMAIL_ID:Original email message for $INSPECT_EMAIL_TO was successfully sended (destination email address is listed in encryption exception list)." LOGGING_MESSAGE_SENDING_INFORMATION_MAIL="MAIL-ID=$INSPECT_EMAIL_ID:Information email message for $INSPECT_EMAIL_TO was successfully sended." LOGGING_MESSAGE_SENDING_ENCRYPTED_MAIL="MAIL-ID=$INSPECT_EMAIL_ID:Encrypted email message for $INSPECT_EMAIL_TO was successfully sended." LOGGING_MESSAGE_PGP_KEY_CANNOT_IMPORT="MAIL-ID=$INSPECT_EMAIL_ID:PGP/GPG public key for $INSPECT_EMAIL_TO cannot be imported." # # INFORMATION EMAIL # INFORMATION_EMAIL_FROM="postmaster@ad.example.net" INFORMATION_EMAIL_BODY="Dear recipient,\n" INFORMATION_EMAIL_BODY+="\n" INFORMATION_EMAIL_BODY+="email message from address '$INSPECT_EMAIL_FROM' was targeted to your address '$INSPECT_EMAIL_TO', but our email system dont have your S/MIME certificate or PGP/GPG public key. If you want to receive encrypted messages please send your S/MIME certificate or PGP/GPG public key to email address user01@example.net.\n" INFORMATION_EMAIL_BODY+="\n" INFORMATION_EMAIL_BODY+="Thank you for your understanding and cooperation.\n" INFORMATION_EMAIL_BODY+="\n" INFORMATION_EMAIL_BODY+="Best regards\n" INFORMATION_EMAIL_BODY+="<ORGANISATION> team\n" # # EXIT CODES FROM <sysexits.h> # EX_TEMPFAIL=75 EX_UNAVAILABLE=69 # # COMMANDS # SENDMAIL="/usr/sbin/sendmail -G -i" # NEVER NEVER NEVER use "-t" here. The -G option does nothing before Postfix 2.3. FORMAIL="/bin/formail" GPG="/bin/gpg2" DATE="/bin/date" OPENSSL="/bin/openssl" CD="/bin/cd" CAT="/bin/cat" CP="/bin/cp" RM="/bin/rm" RMDIR="/bin/rmdir" AWK="/bin/awk" MKDIR="/bin/mkdir" ECHO="/bin/echo" PRINTF="/bin/printf" FIND="/bin/find" ################################################################################## # FUNCTIONS ################################################################################## function logger_date () { local MSG_DATE MSG_DATE=$("$DATE" "+$LOGGING_DATE_FORMAT") echo "$MSG_DATE" } function logger () { local MSG_DATE=$1 local MSG_OUTPUT=$2 echo "$MSG_DATE $MSG_OUTPUT" >> $LOGGING_FILE } function encrypt_pgp_possible () { if [ -e "$INSPECT_EMAIL_TO_PGP" ]; then ENCRYPT_PGP_POSSIBLE=1 else ENCRYPT_PGP_POSSIBLE=0 fi } function encrypt_smime_possible () { if [ -e "$INSPECT_EMAIL_TO_SMIME" ]; then ENCRYPT_SMIME_POSSIBLE=1 else ENCRYPT_SMIME_POSSIBLE=0 fi } function encrypt_smime (){ local EMAIL_ORIGINAL="$1" local EMAIL_ENCRYPTED="$2" local EMAIL_FROM="$3" local EMAIL_TO="$4" local EMAIL_SUBJECT="$5" local EMAIL_TO_CERT="$6" $OPENSSL smime -encrypt -in "$EMAIL_ORIGINAL" -out "$EMAIL_ENCRYPTED" -from "$EMAIL_FROM" -to "$EMAIL_TO" -subject "$EMAIL_SUBJECT" "$EMAIL_TO_CERT" || { echo "$MESSAGE_INSPECT_ENCRYPT_SMIME_PROBLEM"; exit $EX_TEMPFAIL; } } function encrypt_pgp_mime (){ local EMAIL_ORIGINAL="$1" local EMAIL_ENCRYPTED="$2" local EMAIL_TO="$3" local EMAIL_TO_KEY="$4" local BOUNDARY local PGP_MIME_HEADER_BOUNDARY local PGP_MIME_HEADER_START local PGP_MIME_HEADER_END local PGP_MIME_EMAIL_HEADER=$INSPECT_DIRECTORY/pgp_mime_email_header local PGP_MIME_EMAIL_BODY=$INSPECT_DIRECTORY/pgp_mime_email_body # Generate MIME boundary BOUNDARY=$(uuidgen -t) PGP_MIME_HEADER_BOUNDARY="--" PGP_MIME_HEADER_BOUNDARY+=$(echo $BOUNDARY) # Define PGP/MIME start PGP_MIME_HEADER_START=`echo "$PGP_MIME_HEADER_BOUNDARY\n"` PGP_MIME_HEADER_START+="Content-Type: application/pgp-encrypted\n" PGP_MIME_HEADER_START+="Content-Description: PGP/MIME version identification\n" PGP_MIME_HEADER_START+="\n" PGP_MIME_HEADER_START+="Version: 1\n" PGP_MIME_HEADER_START+="\n" PGP_MIME_HEADER_START+=`echo "$PGP_MIME_HEADER_BOUNDARY\n"` PGP_MIME_HEADER_START+="Content-Type: application/octet-stream; name=\"encrypted.asc\"\n" PGP_MIME_HEADER_START+="Content-Description: OpenPGP encrypted message\n" PGP_MIME_HEADER_START+="Content-Disposition: inline; filename=\"encrypted.asc\"\n" # Define PGP/MIME end PGP_MIME_HEADER_END="\n" PGP_MIME_HEADER_END+=`echo "$PGP_MIME_HEADER_BOUNDARY"` # Extract "From:", "To:", "Subject:", "Date:", "MIME-Version:" from email header of the original email to temporary file "pgp_mime_email_header" $CAT $EMAIL_ORIGINAL | $FORMAIL -c -X "From:" -X "To:" -X "Subject:" -X "Date:" -X "MIME-Version:" > $PGP_MIME_EMAIL_HEADER # Add "Content-Type:" email header to temporary file "pgp_mime_email_header" $ECHO -e "Content-Type: multipart/encrypted; boundary=$BOUNDARY; protocol=\"application/pgp-encrypted\"\n" >> $PGP_MIME_EMAIL_HEADER # Add PGP/MIME start (Content-Types: application/pgp-encrypted and application/octet-stream) to temporary file "pgp_mime_email_header" $ECHO -e $PGP_MIME_HEADER_START >> $PGP_MIME_EMAIL_HEADER # Extract "Content-Type" from email header to temporary file "pgp_mime_email_body" $CAT $EMAIL_ORIGINAL | $FORMAIL -X "Content-Type:" > $PGP_MIME_EMAIL_BODY # Extract body of the email to temporary file "pgp_mime_email_body" $CAT $EMAIL_ORIGINAL | $FORMAIL -I "" >> $PGP_MIME_EMAIL_BODY # Add PGP/MIME header to the new PGP/MIME encrypted email $CAT $INSPECT_DIRECTORY/pgp_mime_email_header > $EMAIL_ENCRYPTED # Check if PGP public key is imported in GPG keyring # If not import public key to GPG keyring $GPG --list-keys $EMAIL_TO_KEY if [ $? -ne 0 ]; then { $GPG --import $EMAIL_TO_KEY || { echo $MESSAGE_PGP_KEY_CANNOT_IMPORT; if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_PGP_KEY_CANNOT_IMPORT"; fi exit $EX_TEMPFAIL; } } fi # Encrypt PGP/MIME email body and paste ("argument --output -") to the new PGP/MIME encrypted email $GPG --no-verbose --no-tty --quiet --batch --yes --encrypt --output - --armor --textmode --always-trust -r $EMAIL_TO $PGP_MIME_EMAIL_BODY >> $EMAIL_ENCRYPTED # Add PGP/MIME end (Boundary of PGP/MIME email) $ECHO -e $PGP_MIME_HEADER_END >> $EMAIL_ENCRYPTED # Remove temporary files $RM -rf $PGP_MIME_EMAIL_HEADER $RM -rf $PGP_MIME_EMAIL_BODY } function purge_archive (){ # Remove old archived email messages and archive directories local RETENTION="+$ARCHIVE_RETENTION" $CD $DIRECTORY_ARCHIVE $FIND "$DIRECTORY_ARCHIVE" -type f -mtime $RETENTION -exec $RM -rf {} + $FIND "$DIRECTORY_ARCHIVE" -type d -mtime $RETENTION -exec $RM -rf {} + } function cleaning (){ # Remove working files and directory $RM -rf "$INSPECT_EMAIL_ORIGINAL" $RM -rf "$INSPECT_EMAIL_BACKUP" $RM -rf "$INSPECT_EMAIL_ENCRYPTED" $RM -rf "$INSPECT_EMAIL_INFORMATION" $RMDIR --ignore-fail-on-non-empty "$INSPECT_DIRECTORY" } ################################################################################## # MAIN ################################################################################## # Clean up when done or when aborting trap "rm -f in.$$" 0 1 2 3 15 # Create temporary working directory for specific message $MKDIR -p $INSPECT_DIRECTORY || { echo $MESSAGE_INSPECT_DIR_CANNOT_CREATE; if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_INSPECT_DIR_CANNOT_CREATE"; fi exit $EX_TEMPFAIL; } # Change working directory to INSPECT_DIRECTORY $CD $INSPECT_DIRECTORY || { echo $MESSAGE_INSPECT_DIR_NOEXIST; if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_INSPECT_DIR_NOEXIST"; fi exit $EX_TEMPFAIL; } # Save original (non-filtered) message to inspect directory $CAT > $INSPECT_EMAIL_ORIGINAL || { echo $MESSAGE_INSPECT_FILE_CANNOT_SAVE; if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_INSPECT_FILE_CANNOT_SAVE"; fi exit $EX_TEMPFAIL; } if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_MAIL_INPUT"; fi # Create backup of the original message $CP $INSPECT_EMAIL_ORIGINAL $INSPECT_EMAIL_BACKUP || { echo $MESSAGE_INSPECT_FILE_CANNOT_BACKUP; if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$MESSAGE_INSPECT_FILE_CANNOT_BACKUP"; fi exit $EX_TEMPFAIL; } if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_MAIL_BACKUP"; fi # Replace multiple recipients in Header "To:" to single recipient in ORIGINAL message $CAT $INSPECT_EMAIL_BACKUP | formail -I "To: $INSPECT_EMAIL_TO" > $INSPECT_EMAIL_ORIGINAL || { echo $MESSAGE_INSPECT_FILE_CANNOT_BACKUP; if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$MESSAGE_INSPECT_FILE_CANNOT_BACKUP"; fi exit $EX_TEMPFAIL; } if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_MAIL_RECIPIENT_CHANGE"; fi # Check encryption exception list (MAIL FROM:) # If sender email address is listed in the list then send original email message EXCEPTIONS_LIST=`cat $ENCRYPT_EXCEPTIONS_FROM` EXCEPTIONS=($EXCEPTIONS_LIST) for i in "${EXCEPTIONS[@]}" do if [ $i == "$INSPECT_EMAIL_FROM" ]; then # Log information that recipient email address is listed in encryption exception list if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_MAIL_SENDER_ENCRYPTION_EXCEPTION"; fi # Send original unencrypted and unmodified email message $SENDMAIL "$@" <$INSPECT_EMAIL_ORIGINAL || { echo $MESSAGE_FAIL_SENDING_ORIGINAL_MAIL; if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_FAIL_SENDING_ORIGINAL_MAIL"; fi exit $EX_TEMPFAIL; } if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_SENDING_ORIGINAL_MAIL_FROM"; fi # Remove old archived email messages and archive directories purge_archive # Remove working files and directory cleaning # Exit after sending email exit $? fi done # Unset working variables unset EXCEPTIONS_LIST unset EXCEPTIONS # Check encryption exception list (RCPT TO:) # If recipient email address is listed in the list then send original email message EXCEPTIONS_LIST=`cat $ENCRYPT_EXCEPTIONS_TO` EXCEPTIONS=($EXCEPTIONS_LIST) for i in "${EXCEPTIONS[@]}" do if [[ $INSPECT_EMAIL_TO == *$i ]]; then # Log information that recipient email address is listed in encryption exception list if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_MAIL_RECIPIENT_ENCRYPTION_EXCEPTION"; fi # Send original unencrypted and unmodified email message $SENDMAIL "$@" <$INSPECT_EMAIL_ORIGINAL || { echo $MESSAGE_FAIL_SENDING_ORIGINAL_MAIL; if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_FAIL_SENDING_ORIGINAL_MAIL"; fi exit $EX_TEMPFAIL; } if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_SENDING_ORIGINAL_MAIL_TO"; fi # Remove old archived email messages and archive directories purge_archive # Remove working files and directory cleaning # Exit after sending email exit $? fi done # Store original email subject INSPECT_EMAIL_SUBJECT=`$CAT $INSPECT_EMAIL_ORIGINAL | $FORMAIL -x Subject` ################################################################################## # MAIN - ENCRYPTION ################################################################################## # Check if possible to encrypt email with SMIME encrypt_smime_possible if [ $ENCRYPT_SMIME_POSSIBLE -eq 1 ]; then if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_MAIL_ENCRYPT_SMIME_POSSIBLE"; fi else if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_MAIL_ENCRYPT_SMIME_NOT_POSSIBLE"; fi fi # Check if possible to encrypt email with PGP/GPG encrypt_pgp_possible if [ $ENCRYPT_PGP_POSSIBLE -eq 1 ]; then if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_MAIL_ENCRYPT_PGP_POSSIBLE"; fi else if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_MAIL_ENCRYPT_PGP_NOT_POSSIBLE"; fi fi # If is possible to encrypt email message with SMIME or PGP/GPG if [ $ENCRYPT_SMIME_POSSIBLE -eq 1 -o $ENCRYPT_PGP_POSSIBLE -eq 1 ]; then # If is possible to encrypt with SMIME and encryption preference is SMIME then encrypt with SMIME. if [ $ENCRYPT_SMIME_POSSIBLE -eq 1 -a $ENCRYPT_PREFERENCE == "smime" ] then # Encrypt original email message with SMIME certificate encrypt_smime "$INSPECT_EMAIL_ORIGINAL" \ "$INSPECT_EMAIL_ENCRYPTED" \ "$INSPECT_EMAIL_FROM" \ "$INSPECT_EMAIL_TO" \ "$INSPECT_EMAIL_SUBJECT" \ "$INSPECT_EMAIL_TO_SMIME" || { echo $MESSAGE_MAIL_ENCRYPT_SMIME_PROBLEM; if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_MAIL_ENCRYPT_SMIME_PROBLEM"; fi exit $EX_TEMPFAIL; } if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_MAIL_ENCRYPTED_SMIME"; fi # Send encrypted email message $SENDMAIL "$@" <$INSPECT_EMAIL_ENCRYPTED || { echo $MESSAGE_FAIL_SENDING_ENCRYPTED_MAIL; if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_FAIL_SENDING_ENCRYPTED_MAIL"; fi exit $EX_TEMPFAIL; } if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_SENDING_ENCRYPTED_MAIL"; fi fi # If is possible to encrypt with PGP/GPG and encryption preference is PGP then encrypt with PGP/GPG. if [ $ENCRYPT_PGP_POSSIBLE -eq 1 -a $ENCRYPT_PREFERENCE == "pgp" ] then # Encrypt body of the message with PGP/GPG public key encrypt_pgp_mime "$INSPECT_EMAIL_ORIGINAL" \ "$INSPECT_EMAIL_ENCRYPTED" \ "$INSPECT_EMAIL_TO" \ "$INSPECT_EMAIL_TO_PGP" || { echo $MESSAGE_MAIL_ENCRYPT_PGP_PROBLEM; if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_MAIL_ENCRYPT_PGP_PROBLEM"; fi exit $EX_TEMPFAIL; } if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_MAIL_ENCRYPTED_PGP"; fi # Send encrypted email message $SENDMAIL "$@" <$INSPECT_EMAIL_ENCRYPTED || { echo $MESSAGE_FAIL_SENDING_ENCRYPTED_MAIL; if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_FAIL_SENDING_ENCRYPTED_MAIL"; fi exit $EX_TEMPFAIL; } if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_SENDING_ENCRYPTED_MAIL"; fi fi # If is possible to encrypt with SMIME but not for PGP/GPG and encryption preference is PGP then encrypt with SMIME. if [ $ENCRYPT_SMIME_POSSIBLE -eq 1 -a $ENCRYPT_PREFERENCE == "pgp" -a $ENCRYPT_PGP_POSSIBLE -ne 1 ] then # Encrypt original email message with SMIME certificate encrypt_smime "$INSPECT_EMAIL_ORIGINAL" \ "$INSPECT_EMAIL_ENCRYPTED" \ "$INSPECT_EMAIL_FROM" \ "$INSPECT_EMAIL_TO" \ "$INSPECT_EMAIL_SUBJECT" \ "$INSPECT_EMAIL_TO_SMIME" || { echo $MESSAGE_MAIL_ENCRYPT_SMIME_PROBLEM; if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_MAIL_ENCRYPT_SMIME_PROBLEM"; fi exit $EX_TEMPFAIL; } if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_MAIL_ENCRYPTED_SMIME"; fi # Send encrypted email message $SENDMAIL "$@" <$INSPECT_EMAIL_ENCRYPTED || { echo $MESSAGE_FAIL_SENDING_ENCRYPTED_MAIL; if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_FAIL_SENDING_ENCRYPTED_MAIL"; fi exit $EX_TEMPFAIL; } if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_SENDING_ENCRYPTED_MAIL"; fi fi # If is possible to encrypt with PGP/GPG but not for SMIME and encryption preference is SMIME then encrypt with PGP/GPG. if [ $ENCRYPT_PGP_POSSIBLE -eq 1 -a $ENCRYPT_PREFERENCE == "smime" -a $ENCRYPT_SMIME_POSSIBLE -ne 1 ] then # Encrypt body of the message with PGP/GPG public key encrypt_pgp_mime "$INSPECT_EMAIL_ORIGINAL" \ "$INSPECT_EMAIL_ENCRYPTED" \ "$INSPECT_EMAIL_TO" \ "$INSPECT_EMAIL_TO_PGP" || { echo $MESSAGE_MAIL_ENCRYPT_PGP_PROBLEM; if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_MAIL_ENCRYPT_PGP_PROBLEM"; fi exit $EX_TEMPFAIL; } if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_MAIL_ENCRYPTED_PGP"; fi # Send encrypted email message $SENDMAIL "$@" <$INSPECT_EMAIL_ENCRYPTED || { echo $MESSAGE_FAIL_SENDING_ENCRYPTED_MAIL; if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_FAIL_SENDING_ENCRYPTED_MAIL"; fi exit $EX_TEMPFAIL; } if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_SENDING_ENCRYPTED_MAIL"; fi fi else # Log if is NOT possible to encrypt email message with SMIME or PGP/GPG if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_MAIL_ENCRYPTION_NOT_POSSIBLE"; fi # Extract original email header + add information text as email body $CAT $INSPECT_EMAIL_ORIGINAL | $FORMAIL -X "" > $INSPECT_EMAIL_INFORMATION $PRINTF "\n" >> $INSPECT_EMAIL_INFORMATION $ECHO -e $INFORMATION_EMAIL_BODY >> $INSPECT_EMAIL_INFORMATION # Send information email to recipient for which is not possible to send encrypted email $SENDMAIL "$@" <$INSPECT_EMAIL_INFORMATION || { echo $MESSAGE_FAIL_SENDING_INFORMATION_MAIL; if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGIMG_MESSAGE_FAIL_SENDING_INFORMATION_MAIL"; fi exit $EX_TEMPFAIL; } if [ $LOGGING_ENABLED ]; then logger "$(logger_date)" "$LOGGING_MESSAGE_SENDING_INFORMATION_MAIL"; fi fi ################################################################################## # MAIN - ARCHIVING ################################################################################## ARCH_DATE=$("$DATE" "+%Z-%Y-%m-%d") ARCH_TIME=$("$DATE" "+%H-%M") # Archiving of original email message if [ $ARCHIVE_ORIGINAL -eq 1 ]; then # Create directory for archiving original email message INSPECT_EMAIL_ARCHIVE_DIRECTORY="$DIRECTORY_ARCHIVE/$ARCH_DATE/$ARCH_TIME/$INSPECT_EMAIL_TO" $MKDIR -p "$DIRECTORY_ARCHIVE/$ARCH_DATE/$ARCH_TIME/$INSPECT_EMAIL_TO" # Copy original (backuped, non modified) email message to archiving directory for this message $CP $INSPECT_EMAIL_BACKUP $INSPECT_EMAIL_ARCHIVE_DIRECTORY fi # Archiving of encrypted email message or information email message if [ $ARCHIVE_ENCRYPTED -eq 1 ]; then # Create directory for archiving original email message INSPECT_EMAIL_ARCHIVE_DIRECTORY="$DIRECTORY_ARCHIVE/$ARCH_DATE/$ARCH_TIME/$INSPECT_EMAIL_TO" $MKDIR -p "$DIRECTORY_ARCHIVE/$ARCH_DATE/$ARCH_TIME/$INSPECT_EMAIL_TO" # Copy encrypted email message to archiving directory for this message $CP $INSPECT_EMAIL_ENCRYPTED $INSPECT_EMAIL_ARCHIVE_DIRECTORY # Copy information email message to archiving directory for this message $CP $INSPECT_EMAIL_INFORMATION $INSPECT_EMAIL_ARCHIVE_DIRECTORY fi ################################################################################## # MAIN - ARCHIVING - PURGE OLD ARCHIVED EMAIL MESSAGES ################################################################################## # Remove old archived email messages and archive directories purge_archive ################################################################################## # MAIN - CLEANING ################################################################################## # Remove working files and directory cleaning ################################################################################## # MAIN - EXIT ################################################################################## exit $?
Differences between the hosts
None. The script on DC2-B-VCMSX001 is byte for byte the same. The examples in the header comments (the exception lists, the SELinux module) are the site 1 values and an earlier state of the module; the files that were really on the servers are in Exceptions list, senders, Exceptions list, recipients and SELinux module postfix-local.
Reading it today
- One change against the Source. The signature line of the information message carried the name of the organisation; it is
<ORGANISATION>here. Nothing else was touched. - Names that do not match.
$LOGGIMG_MESSAGE_FAIL_SENDING_INFORMATION_MAIL,$MESSAGE_MAIL_ENCRYPT_SMIME_PROBLEM,$MESSAGE_MAIL_ENCRYPT_PGP_PROBLEMand theirLOGGING_twins are used but never defined, so those error paths print and log an empty text.INFORMATION_EMAIL_FROM,EX_UNAVAILABLEandAWKare defined and never used. if [ $LOGGING_ENABLED ]is true for any non-empty value, soLOGGING_ENABLED=0does not switch logging off.CD="/bin/cd"is an external program and cannot change the directory of the script. Nothing breaks, because every path is absolute, but thetrap "rm -f in.$$"taken from the Postfix example then removes nothing.- The result of the
gpg --encryptcall is not checked, and the closing MIME boundary is written without the two trailing dashes. Both are explained in Automatic email encryption.
Checked against OpenSSL 3.5 and GnuPG 2.5
| As built | Today |
|---|---|
openssl smime -encrypt with no cipher option, OpenSSL 1.0.2: triple DES in CBC mode | The command still exists. The default stayed triple DES through OpenSSL 3.4 and is AES-256-CBC since 3.5, so the same script produces a different cipher on a current system |
openssl smime | Its manual still says it can only handle S/MIME v2 messages. openssl cms -encrypt is the current tool and the only one that can produce authenticated encryption (-aes-256-gcm, AuthEnvelopedData) |
| S/MIME 3.2 (RFC 5751) | S/MIME 4.0 (RFC 8551, April 2019) requires AES-GCM support, adds AuthEnvelopedData and marks triple DES historic |
| CBC without authentication | EFAIL (2018) showed that a mail client can be made to leak the plaintext of CBC-encrypted S/MIME mail. The weakness is exploited in the recipient's client, but what this filter produces is the vulnerable format |
gpg2 from GnuPG 2.0 | The 2.0 branch reached end of life on 2017-12-31, before the filter went into service. Stable is 2.5 |
--batch, --yes, --armor, -r, --always-trust | All still documented; --always-trust is the same as --trust-model always |
--textmode | Listed under deprecated options |
| PGP/MIME built by hand after RFC 3156 | RFC 3156 is still the PGP/MIME specification. OpenPGP itself is now RFC 9580 (July 2024) |
Keyring files, --import per recipient | From GnuPG 2.1 on, gpg-agent handles all secret-key operations, and with use-keyboxd there are no keyring files at all |
Subject: in clear with both standards | Header protection for encrypted mail is specified in RFC 9788 (August 2025) |
formail from procmail | procmail 3.24 is still a RHEL 10 package |
I expect the script, run unchanged on a current system, to still work; that was not tested. With OpenSSL 3.5 it would silently switch from triple DES to AES-256-CBC. It would still not produce authenticated encryption; for that the call has to become openssl cms -encrypt -aes-256-gcm.