summaryrefslogtreecommitdiff
path: root/split-mailman-mails-and-discard-and-save
blob: aae4932e1a7c61d1dd73dc7e2df75118c8d5d6ab (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#!/usr/bin/ruby

require 'rmail'

if ARGV[0] == "spam"
	MAILIN="mail/spam-in"
	MAILLEARN="mail/spam-learn"
	OUTBOX="mail/outbox"
	SEND_DISCARD_MAIL = true
elsif ARGV[0] == "ham"
	MAILIN="mail/ham-in"
	MAILLEARN="mail/ham-learn"
	OUTBOX="mail/outbox"
	SEND_DISCARD_MAIL = false
else
	STDERR.puts "Usage: #{$0} ham|spam"
	exit 1
end

DOMAIN='lists.oftc.net'
FROM="listmod@blackhole.oftc.net"
HOSTNAME=`hostname`.chop

class Counter
	@@counter = 0

	def Counter.value
		@@counter += 1
	end
end

def uniqueName
	"%d.%d_%d.%s"%[
		Time.now.to_i,
		$$,
		Counter.value,
		HOSTNAME]
end

def check_maildir(d)
	throw "#{d} is not a maildir" unless FileTest.directory?(d)
	throw "#{d} is not a maildir" unless FileTest.directory?(d+"/new")
	throw "#{d} is not a maildir" unless FileTest.directory?(d+"/cur")
	throw "#{d} is not a maildir" unless FileTest.directory?(d+"/tmp")
	true
end

def store_in_maildir(md, msg)
	fn = uniqueName
	File.open(md+"/tmp/"+fn, "w", 0600) do |f|
		f.puts msg
	end
	File.link(md+"/tmp/"+fn, md+"/new/"+fn)
	File.unlink(md+"/tmp/"+fn)
	md+"/new/"+fn
end

def process_mail(filename)
	message = File.open(filename) { |f| RMail::Parser.read(f) }
	# some sanity checks
	throw "Mailman moderation mails are expected to have 3 mime parts" unless message.body.length == 3
	throw "Mime Part 0 does have an unexpected content type: #{message.body[0].header['Content-Type']}" unless message.body[0].header['Content-Type'] == 'text/plain; charset="us-ascii"'
	throw "Mime Part 1 does have an unexpected content type: #{message.body[1].header['Content-Type']}" unless message.body[1].header['Content-Type'] == 'message/rfc822'
	throw "Mime Part 2 does have an unexpected content type: #{message.body[2].header['Content-Type']}" unless message.body[2].header['Content-Type'] == 'message/rfc822'
	explanation_body = message.body[0].body
	held_part = RMail::Parser.read( message.body[1].body )
	discard_part = RMail::Parser.read( message.body[2].body )

	# more sanity checks
	throw "Did not find 'As list administrator, your..' boilerplate in mail" unless explanation_body =~ /^As list administrator, your authorization is requested for the/
	throw "Did not find listname in mail" unless explanation_body =~ /^ *List: *.*@#{DOMAIN}/
	throw "discard_part does have an unexpected content type: #{discard_part.header['Content-Type']}" unless discard_part.header['Content-Type'] == 'text/plain; charset="us-ascii"'

	request_address = discard_part.header['From']
	throw "discard_part does not have a from address" unless request_address

	matchdata = /confirm ([0-9a-f]*)/.match discard_part.header['Subject']
	throw "Could not find cookie in discard_part" unless matchdata and matchdata[1]
	cookie = matchdata[1]

	store_in_maildir(MAILLEARN, held_part)
	
	if SEND_DISCARD_MAIL
		mail_request = RMail::Message.new()
		mail_request.header['From'] = FROM
		mail_request.header['To'] = request_address
		mail_request.header['Subject'] = "Re: confirm #{cookie}"
		mail_request.body = ''
		store_in_maildir(OUTBOX, mail_request)
	end
end

check_maildir MAILIN
check_maildir MAILLEARN
check_maildir OUTBOX

Dir[MAILIN+"/new/*"].each do |filename|
	begin
		process_mail filename
		File.unlink filename
	rescue Exception => e
		STDERR.puts "Error when processing #{filename}: #{e}"
	end
end