summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--etc/forward1
-rw-r--r--etc/procmailrc42
-rwxr-xr-xlibexec/filter-github-emails256
3 files changed, 298 insertions, 1 deletions
diff --git a/etc/forward b/etc/forward
deleted file mode 100644
index ece2155..0000000
--- a/etc/forward
+++ /dev/null
@@ -1 +0,0 @@
1admin@monitoring-plugins.org
diff --git a/etc/procmailrc b/etc/procmailrc
new file mode 100644
index 0000000..ea35845
--- /dev/null
+++ b/etc/procmailrc
@@ -0,0 +1,42 @@
1SHELL = /bin/sh
2PATH = /usr/bin:/bin
3LOGFILE = $HOME/log/procmail.log
4GITHUB_FILTER = $HOME/libexec/filter-github-emails
5DEFAULT_RECIPIENT = admin@monitoring-plugins.org
6EXTENSION = $1
7
8#
9# Handle emails sent to <plugins+github@monitoring-plugins.org>.
10#
11:0
12* EXTENSION ?? ^^github^^
13* ! ^X-Loop: plugins@monitoring-plugins\.org
14{
15 :0 fw
16 | $GITHUB_FILTER
17
18 :0 fhw
19 * ^To:(.*[^-a-zA-Z0-9_.])?devel@monitoring-plugins\.org
20 | formail -A 'X-Loop: plugins@monitoring-plugins.org'
21
22 :0 a
23 ! devel@monitoring-plugins.org
24}
25
26#
27# Handle emails that shouldn't be forwarded to the mailing list.
28#
29:0
30* ! ^X-Loop: plugins@monitoring-plugins\.org
31{
32 FROM = `formail -c -x 'From ' | cut -d ' ' -f 1`
33
34 :0 fhw
35 | formail -A 'X-Loop: plugins@monitoring-plugins.org' \
36 -A "X-Original-From: $FROM"
37
38 :0
39 ! $DEFAULT_RECIPIENT
40}
41
42# vim:set filetype=procmail:
diff --git a/libexec/filter-github-emails b/libexec/filter-github-emails
new file mode 100755
index 0000000..95a4141
--- /dev/null
+++ b/libexec/filter-github-emails
@@ -0,0 +1,256 @@
1#!/usr/bin/perl -T
2#
3# Copyright (c) 2014 Monitoring Plugins Development Team
4#
5# Originally written by Holger Weiss <holger@zedat.fu-berlin.de>.
6#
7# This program is free software; you may redistribute it and/or modify it under
8# the same terms as Perl itself.
9#
10
11#
12# This script receives GitHub email notifications and tries to distinguish
13# actual user comments from mere status change reports, so that they can be
14# filtered in different ways. While at it, the messages are also modified to
15# make them suitable for being forwarded to a mailing list.
16#
17# Note: If you edit this script, make sure to never use die() or exit().
18# Instead, call the following subroutine:
19#
20# - panic($format, @args);
21# Log an error message, print original message to standard output, exit
22# non-zero. Note that panic() expects printf(3)-style arguments. Don't
23# use variables within the format string (unless you're sure they will
24# never contain format specifications). Add them to the @args instead.
25#
26# Exceptions raised by imported modules should be cought by our $SIG{__DIE__}
27# handler.
28#
29# Also, never write output to STDERR. Instead, use one of the following
30# subroutines in order to write messages to syslog:
31#
32# - debug($format, @args);
33# Write a debug message to syslog, printf(3)-style.
34# - info($format, @args);
35# Write an informational message to syslog, printf(3)-style.
36# - notice($format, @args);
37# Write a notice to syslog, printf(3)-style.
38# - warning($format, @args);
39# Write a warning to syslog, printf(3)-style.
40# - error($format, @args);
41# Write an error to syslog, printf(3)-style.
42# - critical($format, @args);
43# Write a critical error to syslog, printf(3)-style.
44#
45
46use warnings;
47use strict;
48use Email::MIME;
49use File::Basename;
50use Sys::Syslog qw(:standard :macros);
51use Text::Wrap;
52
53use constant COMMENTS_TO =>
54 'Monitoring Plugins Development <devel@monitoring-plugins.org>';
55use constant STATUS_CHANGES_TO =>
56 'Monitoring Plugins Development <devel@monitoring-plugins.org>';
57use constant TIMEOUT => 15;
58
59$ENV{PATH} = '/usr/bin:/bin';
60$" = '';
61
62# Lines will have a length of no more than $columns - 1.
63$Text::Wrap::columns = 77;
64$Text::Wrap::huge = 'overflow';
65
66setlogmask(LOG_UPTO(LOG_INFO));
67openlog(basename($0), 'pid', 'mail');
68
69$SIG{__WARN__} = sub { panic('Caught warning: %s', $_[0] || '(null)') };
70$SIG{__DIE__} = sub { panic('Caught exception: %s', $_[0] || '(null)') };
71$SIG{ALRM} = sub { panic('Timeout after %d seconds', TIMEOUT) };
72$SIG{HUP} = sub { panic('Caught SIGHUP') };
73$SIG{INT} = sub { panic('Caught SIGINT') };
74$SIG{PIPE} = sub { panic('Caught SIGPIPE') };
75$SIG{TERM} = sub { panic('Caught SIGTERM') };
76$SIG{USR1} = sub { panic('Caught SIGUSR1') };
77$SIG{USR2} = sub { panic('Caught SIGUSR2') };
78
79alarm(TIMEOUT);
80
81my $MESSAGE_ID;
82my @MESSAGE = <>; # The complete email message.
83
84#
85# Log and exit.
86#
87
88sub _report {
89 my ($level, $format, @args) = @_;
90
91 chomp(@args);
92 syslog($level, "$format (%s)", @args, $MESSAGE_ID || 'null');
93}
94
95sub debug { _report(LOG_DEBUG, @_) }
96sub info { _report(LOG_INFO, @_) }
97sub notice { _report(LOG_NOTICE, @_) }
98sub warning { _report(LOG_WARNING, @_) }
99sub error { _report(LOG_ERR, @_) }
100sub critical { _report(LOG_CRIT, @_) }
101
102sub panic {
103 $SIG{$_} = 'DEFAULT' for keys %SIG;
104 critical(@_);
105 print @MESSAGE;
106 bye(1);
107}
108
109sub bye {
110 my $status = shift;
111
112 debug('Exiting with status %d', $status);
113 closelog;
114 exit($status);
115}
116
117#
118# Look up MIME parts.
119#
120
121sub get_part {
122 my ($email, $type) = @_;
123
124 debug('Searching for %s part in email', $type);
125
126 foreach my $part ($email->subparts) {
127 next if $part->subparts;
128 return $part if $part->content_type =~ /\Q$type\E/i;
129 }
130 panic('Cannot find %s part in email', $type);
131}
132
133#
134# Edit a message.
135#
136
137sub edit_header {
138 my ($header, $recipient, $description) = @_;
139
140 debug('Editing header');
141
142 $header->header_set('Lines');
143 $header->header_set('Content-Length');
144 $header->header_set('Reply-To'); # Remove GitHub's reply address.
145 $header->header_set('To' => $recipient);
146 $header->header_set('X-MP-Content' => $description);
147
148 return $header;
149}
150
151sub edit_text_body {
152 my $body = shift;
153 my ($s, $r);
154
155 debug('Editing text/plain body');
156
157 $body = fill('', '', $body); # While at it, wrap the text.
158
159 $s = '^--- Reply to this email directly or view it on GitHub:';
160 $r = "-- \nReply to this email on GitHub:";
161 $body =~ s/$s/$r/m;
162
163 return $body;
164}
165
166sub edit_html_body {
167 my $body = shift;
168 my ($s, $r);
169
170 debug('Editing text/html body');
171
172 $s = 'Reply to this email directly or ';
173 $r = '';
174 $body =~ s/$s/$r/;
175
176 $s = 'view it on GitHub';
177 $r = 'Reply to this email on GitHub';
178 $body =~ s/$s/$r/;
179
180 return $body;
181}
182
183#
184# Check message type.
185#
186
187sub is_github_mail {
188 my $email = shift;
189
190 return $email->subparts == 2
191 and defined($email->header('From'))
192 and defined($email->header('X-GitHub-Recipient'))
193 and index($email->header('From'), 'notifications@github.com') != -1
194 and $email->header('X-GitHub-Recipient') eq 'monitoring-user';
195}
196
197sub is_github_status_change {
198 my $email = shift;
199 my $body = get_part($email, 'text/plain')->body_str;
200
201 if ($body =~ tr/\n// == 5) {
202 return 1 if $body =~ /^(?:Closed|Reopened) #\d+\.$/m;
203 }
204 return 0;
205}
206
207#
208# Handle message type.
209#
210
211sub handle_github_mail {
212 my $email = shift;
213 my $text = get_part($email, 'text/plain');
214 my $html = get_part($email, 'text/html');
215 my $text_body = $text->body_str;
216 my $html_body = $html->body_str;
217
218 $text->body_str_set(edit_text_body($text_body));
219 $html->body_str_set(edit_html_body($html_body));
220 $email->parts_set([$text, $html]);
221
222 if (is_github_status_change($email)) {
223 info('Received a GitHub status change');
224 $email->header_obj_set(edit_header($email->header_obj,
225 STATUS_CHANGES_TO, 'GitHub status change'));
226 } else {
227 info('Received a GitHub comment');
228 $email->header_obj_set(edit_header($email->header_obj,
229 COMMENTS_TO, 'GitHub comment'));
230 }
231 print $email->as_string;
232}
233
234sub handle_non_github_mail {
235 my $email = shift;
236
237 notice('Received a non-GitHub message');
238 # Just spit out the email as-is.
239 print $email->as_string;
240}
241
242#
243# Action!
244#
245
246my $email = Email::MIME->new("@MESSAGE");
247
248$MESSAGE_ID = $email->header('Message-ID') || '(null)';
249$email->header_set('X-MP-Filter' => basename($0));
250
251if (is_github_mail($email)) {
252 handle_github_mail($email);
253} else {
254 handle_non_github_mail($email);
255}
256bye(0);