aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/lua/maillist.lua
blob: 65d1c82c162f3b6f55d1f8c2df2fff21591aba17 (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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
-- Module for checking mail list headers

local symbol = 'MAILLIST'

-- EZMLM
-- Mailing-List: .*run by ezmlm
-- Precedence: bulk
-- List-Post: <mailto:
-- List-Help: <mailto:
-- List-Unsubscribe: <mailto:[a-zA-Z\.-]+-unsubscribe@
-- List-Subscribe: <mailto:[a-zA-Z\.-]+-subscribe@
function check_ml_ezmlm(task)
	local message = task:get_message()
	-- Mailing-List
	local header = message:get_header('mailing-list')
	if not header or not string.find(header[1], 'ezmlm$') then
		return false
	end
	-- Precedence
	header = message:get_header('precedence')
	if not header or not string.match(header[1], '^bulk$') then
		return false
	end
	-- Other headers
	header = message:get_header('list-post')
	if not header or not string.find(header[1], '^<mailto:') then
		return false
	end
	header = message:get_header('list-help')
	if not header or not string.find(header[1], '^<mailto:') then
		return false
	end
	-- Subscribe and unsubscribe
	header = message:get_header('list-subscribe')
	if not header or not string.find(header[1], '<mailto:[a-zA-Z.-]+-subscribe@') then
		return false
	end
	header = message:get_header('list-unsubscribe')
	if not header or not string.find(header[1], '<mailto:[a-zA-Z.-]+-unsubscribe@') then
		return false
	end

	return true
end

-- MailMan (the gnu mailing list manager)
-- Precedence: bulk [or list for v2]
-- List-Help: <mailto:
-- List-Post: <mailto:
-- List-Subscribe: .*<mailto:.*=subscribe>
-- List-Id: 
-- List-Unsubscribe: .*<mailto:.*=unsubscribe>
-- List-Archive: 
-- X-Mailman-Version: \d
function check_ml_mailman(task)
	local message = task:get_message()
	-- Mailing-List
	local header = message:get_header('x-mailman-version')
	if not header or not string.find(header[1], '^%d') then
		return false
	end
	-- Precedence
	header = message:get_header('precedence')
	if not header or (not string.match(header[1], '^bulk$') and not string.match(header[1], '^list$')) then
		return false
	end
	-- For reminders we have other headers than for normal messages
	header = message:get_header('x-list-administrivia')
	local subject = message:get_header('subject')
	if (header and string.find(header[1], 'yes')) or (subject and string.find(subject[1], 'mailing list memberships reminder$')) then
		if not message:get_header('errors-to') or not message:get_header('x-beenthere') then
			return false
		end
		header = message:get_header('x-no-archive')
		if not header or not string.find(header[1], 'yes') then
			return false
		end
		return true
	end

	-- Other headers
	header = message:get_header('list-id')
	if not header then
		return false
	end
	header = message:get_header('list-post')
	if not header or not string.find(header[1], '^<mailto:') then
		return false
	end
	header = message:get_header('list-help')
	if not header or not string.find(header[1], '^<mailto:') then
		return false
	end
	-- Subscribe and unsubscribe
	header = message:get_header('list-subscribe')
	if not header or not string.find(header[1], '<mailto:.*=subscribe>') then
		return false
	end
	header = message:get_header('list-unsubscribe')
	if not header or not string.find(header[1], '<mailto:.*=unsubscribe>') then
		return false
	end

	return true

end

-- Subscribe.ru
-- Precedence: normal
-- List-Id: <.*.subscribe.ru>
-- List-Help: <http://subscribe.ru/catalog/.*>
-- List-Subscribe: <mailto:.*-sub@subscribe.ru>
-- List-Unsubscribe: <mailto:.*-unsub@subscribe.ru>
-- List-Archive:  <http://subscribe.ru/archive/.*>
-- List-Owner: <mailto:.*-owner@subscribe.ru>
-- List-Post: NO
function check_ml_subscriberu(task)
	local message = task:get_message()
	-- List-Id
	local header = message:get_header('list-id')
	if not header or not string.find(header[1], '^<.*%.subscribe%.ru>$') then
		return false
	end
	-- Precedence
	header = message:get_header('precedence')
	if not header or not string.match(header[1], '^normal$') then
		return false
	end
	-- Other headers
	header = message:get_header('list-archive')
	if not header or not string.find(header[1], '^<http://subscribe.ru/archive/.*>$') then
		return false
	end
	header = message:get_header('list-owner')
	if not header or not string.find(header[1], '^<mailto:.*-owner@subscribe.ru>$') then
		return false
	end
	header = message:get_header('list-help')
	if not header or not string.find(header[1], '^<http://subscribe.ru/catalog/.*>$') then
		return false
	end
	-- Subscribe and unsubscribe
	header = message:get_header('list-subscribe')
	if not header or not string.find(header[1], '^<mailto:.*-sub@subscribe.ru>$') then
		return false
	end
	header = message:get_header('list-unsubscribe')
	if not header or not string.find(header[1], '^<mailto:.*-unsub@subscribe.ru>$') then
		return false
	end

	return true

end

-- RFC 2369 headers
function check_rfc2369(task)
	local message = task:get_message()
	local header = message:get_header('List-Id')
	if not header then
		return false
	end
	header = message:get_header('List-Unsubscribe')
	if not header or not string.find(header[1], '^^<.+>$') then
		return false
	end
	header = message:get_header('List-Subscribe')
	if not header or not string.find(header[1], '^^<.+>$') then
		return false
	end
	
	return true
end

-- RFC 2919 headers
function check_rfc2919(task)
	local message = task:get_message()
	local header = message:get_header('List-Id')
	if not header or not string.find(header[1], '^<.+>$') then
		return false
	end
	
	return check_rfc2369(task)
end

-- Google groups detector
-- header exists X-Google-Loop
-- RFC 2919 headers exist
--
function check_ml_googlegroup(task)
	local message = task:get_message()
	local header = message:get_header('X-Google-Loop')
	
	if not header then
		return false
	end

	return check_rfc2919(task)
end

-- Majordomo detector
-- Check Sender for owner- or -owner
-- Check Precendence for 'Bulk' or 'List'
--
-- And nothing more can be extracted :(
function check_ml_majordomo(task)
	local message = task:get_message()
	local header = message:get_header('Sender')
	if not header or (not string.find(header[1], '^owner-.*$') and not string.find(header[1], '^.*-owner$')) then
		return false
	end
	
	local header = message:get_header('Precedence')
	if not header or (header[1] ~= 'list' and header[1] ~= 'bulk') then
		return false
	end

	return true
end

-- CGP detector
-- X-Listserver = CommuniGate Pro LIST
-- RFC 2919 headers exist
--
function check_ml_cgp(task)
	local message = task:get_message()
	local header = message:get_header('X-Listserver')
	
	if not header or header ~= 'CommuniGate Pro LIST' then
		return false
	end

	return check_rfc2919(task)
end

function check_maillist(task)
	if check_ml_ezmlm(task) then
		task:insert_result(symbol, 1, 'ezmlm')
	elseif check_ml_mailman(task) then
		task:insert_result(symbol, 1, 'mailman')
	elseif check_ml_subscriberu(task) then
		task:insert_result(symbol, 1, 'subscribe.ru')
	elseif check_ml_googlegroup(task) then
		task:insert_result(symbol, 1, 'googlegroups')
	elseif check_ml_majordomo(task) then
		task:insert_result(symbol, 1, 'majordomo')
	elseif check_ml_cgp(task) then
		task:insert_result(symbol, 1, 'cgp')
	end
end
-- Registration
if type(rspamd_config.get_api_version) ~= 'nil' then
	if rspamd_config:get_api_version() >= 1 then
		rspamd_config:register_module_option('maillist', 'symbol', 'string')
	end
end
-- Configuration
local opts =  rspamd_config:get_all_opt('maillist')if opts then
	if opts['symbol'] then
		symbol = opts['symbol'] 
		rspamd_config:register_symbol(symbol, 1.0, 'check_maillist')
	end
end