I'm creating a tool to parse an input file to re-construct a mailx command (server that creates the data for mailx cannot send emails, so I need to store data into a file so another server can rebuild the command and send it). I could output the whole command to a file and execute the file on the other server, but that's hardly secure / safe.... anyone could intercept the file and insert malicious stuff that would be run as root - this parsing tool is checking every minute for any files to parse and email using a systemd timer and service.
I have created the file, using 'markers / separators' with this format:
-------MESSAGE START-------
Email Body Text
Goes Here
-------MESSAGE END-------
-------SUBJECT START-------
Email Subject
-------SUBJECT END-------
-------ATTACHEMENT START-------
path to file to attach if supplied
-------ATTACHEMENT END-------
-------S OPTS START-------
list of mailx '-S' options eg from=EMAILNAME <[email protected]> or sendwait etc each one on a new line
-------S OPTS END-------
-------EMAIL LIST START-------
string of recipient emails comma separated eg. email1,email2,email3 etc..
-------EMAIL LIST END-------
And I have a program to parse this file and rebuild, and run the mailx command:
#!/bin/bash
## Using systemd logging to journal for this as its now being called as part of a service
## See: https://serverfault.com/questions/573946/how-can-i-send-a-message-to-the-systemd-journal-froma-the-command-line (kkm answer)
start_time="$(date +[%c])"
exec 4>&2 2> >(while read -r REPLY; do printf >&4 '<3>%s\n' "$REPLY"; done)
echo >&4 "<5>$start_time -- Started gpfs_flag_email.sh"
trap_exit(){
exec >2&
}
trap 'trap_exit' EXIT
email_flag_path="<PATH TO LOCATION>/email_flags/"
mailx_message_start="-------MESSAGE START-------"
mailx_message_end="-------MESSAGE END-------"
mailx_subject_start="-------SUBJECT START-------"
mailx_subject_end="-------SUBJECT END-------"
mailx_attachement_start="-------ATTACHEMENT START-------"
mailx_attachement_end="-------ATTACHEMENT END-------"
mailx_s_opts_start="-------S OPTS START-------"
mailx_s_opts_end="-------S OPTS END-------"
mailx_to_email_start="-------EMAIL LIST START-------"
mailx_to_email_end="-------EMAIL LIST END-------"
no_attachment=false
no_additional_opts=false
additional_args_switch="-S "
num_files_in_flag_path="$(find $email_flag_path -type f | wc -l)"
if [[ $num_files_in_flag_path -gt 0 ]]; then
for file in $email_flag_path*; do
email_message="$(awk "/$mailx_message_start/,/$mailx_message_end/" $file | egrep -v -- "$mailx_message_start|$mailx_message_end")"
email_subject="$(awk "/$mailx_subject_start/,/$mailx_subject_end/" $file | egrep -v -- "$mailx_subject_start|$mailx_subject_end")"
email_attachment="$(awk "/$mailx_attachement_start/,/$mailx_attachement_end/" $file | egrep -v -- "$mailx_attachement_start|$mailx_attachement_end")"
email_additional_opts="$(awk "/$mailx_s_opts_start/,/$mailx_s_opts_end/" $file | egrep -v -- "$mailx_s_opts_start|$mailx_s_opts_end")"
email_addresses="$(awk "/$mailx_to_email_start/,/$mailx_to_email_end/" $file | egrep -v -- "$mailx_to_email_start|$mailx_to_email_end" | tr -d '\n')"
if [[ -z "$email_message" || -z "$email_subject" || -z "$email_addresses" ]]; then
echo >&2 "MISSING DETAILS IN INPUT FILE $file.... Exiting With Error"
exit 1
fi
if [[ -z "$email_attachment" ]]; then
no_attachment=true
fi
if [[ -z "$email_additional_opts" ]]; then
no_additional_opts=true
else
additional_opts_string=""
while read -r line; do
if [[ ! $line =~ [^[:space:]] ]]; then
continue
else
additional_opts_string="$additional_opts_string \"${additional_args_switch} '$line'\""
fi
done <<<"$(echo "$email_additional_opts")"
additional_opts_string="$(echo ${additional_opts_string:1} | tr -d '\n')"
fi
if [[ $no_attachment = true ]]; then
if [[ $no_additional_opts = true ]]; then
echo "$email_message" | mailx -s "$email_subject" $email_addresses
else
echo "$email_message" | mailx -s "$email_subject" $additional_opts_string $email_addresses
fi
else
if [[ $no_additional_opts = true ]]; then
echo "$email_message" | mailx -s "$email_subject" -a $email_attachment $email_addresses
else
echo "$email_message" | mailx -s "$email_subject" -a $email_attachment $additional_opts_string $email_addresses
fi
fi
done
fi
find $email_flag_path -type f -delete
exit 0
There is however an issue with the above that I just can work out..... the -S opts completely screw up the email headers and I end up with emails being sent to the wrong people (I have set a reply-to and from options, but the email header is jumbled and the reply-to email ends up in the to: field) like this:
To: Me <[email protected]>, [email protected] , [email protected] <[email protected]>, another-email <[email protected]>
All I'm trying to do is rebuild the command as if I'd typed it in the CLI:
echo "EMAIL BODY MESSAGE" | mailx -s "EMAIL SUBJECT" -S "from=EMAILNAME <[email protected]>" -S "replyto=EMAILNAME <[email protected]>" -S sendwait [email protected]
I've tried quoting in ' ' and " " quoting the other mailx parameters around it etc etc... I have written other tools that pass variables as input arguments so I just cannot understand how I'm screwing this up.....
Any help would be appreciated...
EDIT
Thanks to Gordon Davisson's really helpful comments I was able to not only fix it but understand the fix as well using an array and appropriately quoting the variables... the tip about using printf was really really helpful in helping me understand what I was doing wrong and how to correct it :P
declare -a mailx_args_array
...
num_files_in_flag_path="$(find $email_flag_path -type f | wc -l)"
if [[ $num_files_in_flag_path -gt 0 ]]; then
for file in $email_flag_path*; do
....
mailx_args_array+=( -s "$email_subject" )
if [[ ! -z "$email_attachment" ]]; then
mailx_args_array+=( -a "$email_attachment" )
fi
if [[ ! -z "$email_additional_s_opts" ]]; then
while read -r s_opt_line; do
mailx_args_array+=( -S "$s_opt_line" )
done < <(echo "$email_additional_s_opts")
fi
mailx_args_array+=( "$email_addresses" )
echo "$email_message" | mailx "${mailx_args_array[@]}"
done
fi