aboutsummaryrefslogtreecommitdiffstats
path: root/script
diff options
context:
space:
mode:
Diffstat (limited to 'script')
-rwxr-xr-xscript/handle-mail-replies149
-rwxr-xr-xscript/mailin24
-rwxr-xr-xscript/rails-post-deploy4
3 files changed, 164 insertions, 13 deletions
diff --git a/script/handle-mail-replies b/script/handle-mail-replies
new file mode 100755
index 000000000..9b1fb5b29
--- /dev/null
+++ b/script/handle-mail-replies
@@ -0,0 +1,149 @@
+#!/usr/bin/ruby
+
+# Handle email responses sent to us.
+#
+# This script is invoked as a pipe command, i.e. with the raw email message on stdin.
+# - If a message is identified as a permanent bounce, the user is marked as having a
+# bounced address, and will not be sent any more messages.
+# - If a message is identified as an out-of-office autoreply, it is discarded.
+# - Any other messages are forwarded to config.get("FORWARD_NONBOUNCE_RESPONSES_TO")
+
+
+# We want to avoid loading rails unless we need it, so we start by just loading the
+# config file ourselves.
+$alaveteli_dir = File.join(File.dirname(__FILE__), '..')
+$:.push(File.join($alaveteli_dir, "commonlib", "rblib"))
+load "config.rb"
+MySociety::Config.set_file(File.join($alaveteli_dir, 'config', 'general'), true)
+MySociety::Config.load_default
+
+$:.push(File.join($alaveteli_dir, "vendor", "rails", "actionmailer", "lib", "action_mailer", "vendor", "tmail-1.2.7"))
+require 'tmail'
+
+def main(in_test_mode)
+ Dir.chdir($alaveteli_dir) do
+ raw_message = $stdin.read
+ begin
+ message = TMail::Mail.parse(raw_message)
+ rescue
+ # Error parsing message. Just pass it on, to be on the safe side.
+ forward_on(raw_message) unless in_test_mode
+ return 0
+ end
+
+ pfas = permanently_failed_addresses(message)
+ if !pfas.empty?
+ if in_test_mode
+ puts pfas
+ else
+ pfas.each do |pfa|
+ record_bounce(pfa, raw_message)
+ end
+ end
+ return 1
+ end
+
+ if is_oof? message
+ # Discard out-of-office messages
+ return 2
+ end
+
+ # Otherwise forward the message on
+ forward_on(raw_message) unless in_test_mode
+ return 0
+ end
+end
+
+def permanently_failed_addresses(message)
+ if message.header_string("Return-Path") == "<>"
+ # Some sort of auto-response
+
+ # Check for Exim’s X-Failed-Recipients header
+ failed_recipients = message.header_string("X-Failed-Recipients")
+ if !failed_recipients.nil?
+ # The X-Failed-Recipients header contains the email address that failed
+ # Check for the words "This is a permanent error." in the body, to indicate
+ # a permanent failure
+ if message.body =~ /This is a permanent error./
+ return failed_recipients.split(/,\s*/)
+ end
+ end
+
+ # Next, look for multipart/report
+ if message.content_type == "multipart/report"
+ permanently_failed_recipients = []
+ message.parts.each do |part|
+ if part.content_type == "message/delivery-status"
+ sections = part.body.split(/\r?\n\r?\n/)
+ # The first section is a generic header; subsequent sections
+ # represent a particular recipient. Since we
+ sections[1..-1].each do |section|
+ if section !~ /^Status: (\d)/ || $1 != '5'
+ # Either we couldn’t find the Status field, or it was a transient failure
+ break
+ end
+ if section =~ /^Final-Recipient: rfc822;(.+)/
+ permanently_failed_recipients.push($1)
+ end
+ end
+ end
+ end
+ if !permanently_failed_recipients.empty?
+ return permanently_failed_recipients
+ end
+ end
+ end
+
+ return []
+end
+
+def is_oof?(message)
+ # Check for out-of-office
+
+ if message.header_string("X-POST-MessageClass") == "9; Autoresponder"
+ return true
+ end
+
+ subject = message.header_string("Subject").downcase
+ if message.header_string("Return-Path") == "<>"
+ if subject.start_with? "out of office: "
+ return true
+ end
+ if subject.start_with? "automatic reply: "
+ return true
+ end
+ end
+
+ if subject.start_with? "out of office autoreply:"
+ return true
+ end
+ if subject == "out of office"
+ return true
+ end
+ if subject.end_with? "is out of the office"
+ return true
+ end
+ return false
+end
+
+def forward_on(raw_message)
+ forward_non_bounces_to = MySociety::Config.get("FORWARD_NONBOUNCE_RESPONSES_TO", "user-support@localhost")
+ IO.popen("/usr/sbin/sendmail -i #{forward_non_bounces_to}", "w") do |f|
+ f.write(raw_message);
+ f.close;
+ end
+end
+
+def load_rails
+ require File.join('config', 'boot')
+ require RAILS_ROOT + '/config/environment'
+end
+
+def record_bounce(email_address, bounce_message)
+ load_rails
+ User.record_bounce_for_email(email_address, bounce_message)
+end
+
+in_test_mode = (ARGV[0] == "--test")
+status = main(in_test_mode)
+exit(status) if in_test_mode
diff --git a/script/mailin b/script/mailin
index 733eaff3d..b4d77f7a4 100755
--- a/script/mailin
+++ b/script/mailin
@@ -2,18 +2,18 @@
# Wire this script to receive incoming email for request responses.
-INPUT=/tmp/foi-mailin-mail-$RANDOM$RANDOM$RANDOM$RANDOM.txt
-OUTPUT=/tmp/foi-mailin-output-$RANDOM$RANDOM$RANDOM$RANDOM.txt
+INPUT=$(mktemp -t foi-mailin-mail-XXXXXXXX)
+OUTPUT=$(mktemp -t foi-mailin-output-XXXXXXXX)
-cat >$INPUT
+# Read the email message from stdin, and write it to the file $INPUT
+cat >"$INPUT"
-cd `dirname $0`
-cd ../
+cd "$(dirname "$0")"/..
source commonlib/shlib/deployfns
read_conf config/general
-(cat $INPUT | ./script/runner 'RequestMailer.receive(STDIN.read)') >$OUTPUT 2>&1
+script/runner 'RequestMailer.receive(STDIN.read)' <"$INPUT" >"$OUTPUT" 2>&1
ERROR_CODE=$?
if [ ! "$ERROR_CODE" = "0" ]
then
@@ -21,17 +21,15 @@ then
# send error to administators (use mutt for MIME encoding)
SUBJ="Mail import error for $OPTION_DOMAIN"
- BODY="There was an error code $ERROR_CODE returned by the RequestMailer.receive command in script/mailin. See attached for details. This might be quite serious, such as the database was down, or might be an email with corrupt headers that Rails is choking on. The email was returned with an exit code 75, which for Exim at least means the MTA will try again later. A well configured installation of this code will separately have had Exim make a backup copy of the email in a separate mailbox, just in case."
- echo "$BODY" | /usr/bin/mutt -s "$SUBJ" -a $OUTPUT $INPUT -- $OPTION_CONTACT_EMAIL
+ BODY="There was an error code $ERROR_CODE returned by the RequestMailer.receive command in script/mailin. See attached for details. This might be quite serious, such as the database was down, or might be an email with corrupt headers that Rails is choking on. We returned the email with an exit code 75, which for Exim at least instructs the MTA to try again later. A well configured installation of this code will separately have had Exim make a backup copy of the email in a separate mailbox, just in case."
+ /usr/bin/mutt -s "$SUBJ" -a "$OUTPUT" "$INPUT" -- "$OPTION_CONTACT_EMAIL" <<<"$BODY"
# tell exim error was temporary, so try again later (no point bouncing message to authority)
- rm -f $OUTPUT
- rm -f $INPUT
+ rm -f "$INPUT" "$OUTPUT"
exit 75
fi
-cat $OUTPUT
-rm -f $OUTPUT
-rm -f $INPUT
+cat "$OUTPUT"
+rm -f "$INPUT" "$OUTPUT"
exit 0
diff --git a/script/rails-post-deploy b/script/rails-post-deploy
index 1fe56212c..437b7255a 100755
--- a/script/rails-post-deploy
+++ b/script/rails-post-deploy
@@ -44,6 +44,10 @@ else
fi
mkdir -p log
fi
+# link the "downloads" directory in the cache to somewhere it can be served
+mkdir -p $APP_DIR/cache/zips/download
+ln -s $APP_DIR/cache/zips/download $APP_DIR/public/
+
cd log
touch development.log fastcgi.crash.log production.log test.log
cd ..