]> git.proxmox.com Git - pmg-api.git/blob - src/PMG/RuleDB/Notify.pm
dkim: add QID in warnings
[pmg-api.git] / src / PMG / RuleDB / Notify.pm
1 package PMG::RuleDB::Notify;
2
3 use strict;
4 use warnings;
5 use DBI;
6 use MIME::Body;
7 use MIME::Head;
8 use MIME::Entity;
9 use MIME::Words qw(encode_mimewords);
10 use Encode qw(decode encode);
11
12 use PVE::SafeSyslog;
13
14 use PMG::Utils;
15 use PMG::ModGroup;
16 use PMG::RuleDB::Object;
17 use PMG::MailQueue;
18
19 use base qw(PMG::RuleDB::Object);
20
21 sub otype {
22 return 4002;
23 }
24
25 sub oclass {
26 return 'action';
27 }
28
29 sub otype_text {
30 return 'Notification';
31 }
32
33 sub final {
34 return 0;
35 }
36
37 sub priority {
38 return 89;
39 }
40
41 sub new {
42 my ($type, $to, $subject, $body, $attach, $ogroup) = @_;
43
44 my $class = ref($type) || $type;
45
46 my $self = $class->SUPER::new($class->otype(), $ogroup);
47
48 $to //= '__ADMIN__';
49 $attach //= 'N';
50 $subject //= 'Notification: __SUBJECT__';
51
52 if (!defined($body)) {
53 $body = <<EOB;
54 Proxmox Notification:
55
56 Sender: __SENDER__
57 Receiver: __RECEIVERS__
58 Targets: __TARGETS__
59
60 Subject: __SUBJECT__
61
62 Matching Rule: __RULE__
63
64 __RULE_INFO__
65
66 __VIRUS_INFO__
67 __SPAM_INFO__
68 EOB
69 }
70 $self->{to} = $to;
71 $self->{subject} = $subject;
72 $self->{body} = $body;
73 $self->{attach} = $attach;
74
75 return $self;
76 }
77
78 sub load_attr {
79 my ($type, $ruledb, $id, $ogroup, $value) = @_;
80
81 my $class = ref($type) || $type;
82
83 defined($value) || die "undefined object attribute: ERROR";
84
85 my ($raw_subject, $raw_body, $attach);
86
87 my $sth = $ruledb->{dbh}->prepare(
88 "SELECT * FROM Attribut WHERE Object_ID = ?");
89
90 $sth->execute($id);
91
92 while (my $ref = $sth->fetchrow_hashref()) {
93 $raw_subject = $ref->{value} if $ref->{name} eq 'subject';
94 $raw_body = $ref->{value} if $ref->{name} eq 'body';
95 $attach = $ref->{value} if $ref->{name} eq 'attach';
96 }
97
98 $sth->finish();
99
100 # Note: db stores binary (ascii) data, so we need to convert to UTF-8 manually
101 my $obj = $class->new($value,
102 decode('UTF-8', $raw_subject),
103 decode('UTF-8', $raw_body),
104 $attach, $ogroup);
105 $obj->{id} = $id;
106
107 # Note: Digest::SHA does not work with wide UTF8 characters
108 $obj->{digest} = Digest::SHA::sha1_hex(
109 $id, $obj->{to}, $raw_subject, $raw_body, $obj->{attach}, $ogroup);
110
111 return $obj;
112 }
113
114 sub save {
115 my ($self, $ruledb, $no_trans) = @_;
116
117 defined($self->{ogroup}) || die "undefined object attribute: ERROR";
118 defined($self->{to}) || die "undefined object attribute: ERROR";
119 defined($self->{subject}) || die "undefined object attribute: ERROR";
120 defined($self->{body}) || die "undefined object attribute: ERROR";
121
122 $self->{attach} //= 'N';
123
124 # Note: db stores binary (ascii) data, so we need to convert from UTF-8 manually
125
126 if (defined ($self->{id})) {
127 # update
128
129 eval {
130 $ruledb->{dbh}->begin_work if !$no_trans;
131
132 $ruledb->{dbh}->do(
133 "UPDATE Object SET Value = ? WHERE ID = ?",
134 undef, $self->{to}, $self->{id});
135
136 $ruledb->{dbh}->do(
137 "UPDATE Attribut SET Value = ? " .
138 "WHERE Name = ? and Object_ID = ?",
139 undef, encode('UTF-8', $self->{subject}), 'subject', $self->{id});
140
141 $ruledb->{dbh}->do(
142 "UPDATE Attribut SET Value = ? " .
143 "WHERE Name = ? and Object_ID = ?",
144 undef, encode('UTF-8', $self->{body}), 'body', $self->{id});
145
146 $ruledb->{dbh}->do(
147 "UPDATE Attribut SET Value = ? " .
148 "WHERE Name = ? and Object_ID = ?",
149 undef, $self->{attach}, 'attach', $self->{id});
150
151 $ruledb->{dbh}->commit if !$no_trans;
152 };
153 if (my $err = $@) {
154 die $err if !$no_trans;
155 $ruledb->{dbh}->rollback;
156 syslog('err', $err);
157 return undef;
158 }
159
160 } else {
161 # insert
162
163 $ruledb->{dbh}->begin_work if !$no_trans;
164
165 eval {
166
167 my $sth = $ruledb->{dbh}->prepare(
168 "INSERT INTO Object (Objectgroup_ID, ObjectType, Value) " .
169 "VALUES (?, ?, ?);");
170
171 $sth->execute($self->ogroup, $self->otype, $self->{to});
172
173 $self->{id} = PMG::Utils::lastid($ruledb->{dbh}, 'object_id_seq');
174
175 $sth->finish();
176
177 $ruledb->{dbh}->do("INSERT INTO Attribut " .
178 "(Object_ID, Name, Value) " .
179 "VALUES (?, ?, ?)", undef,
180 $self->{id}, 'subject', encode('UTF-8', $self->{subject}));
181 $ruledb->{dbh}->do("INSERT INTO Attribut " .
182 "(Object_ID, Name, Value) " .
183 "VALUES (?, ?, ?)", undef,
184 $self->{id}, 'body', encode('UTF-8', $self->{body}));
185 $ruledb->{dbh}->do("INSERT INTO Attribut " .
186 "(Object_ID, Name, Value) " .
187 "VALUES (?, ?, ?)", undef,
188 $self->{id}, 'attach', $self->{attach});
189
190 $ruledb->{dbh}->commit if !$no_trans;
191 };
192 if (my $err = $@) {
193 die $err if !$no_trans;
194 $ruledb->{dbh}->rollback;
195 syslog('err', $err);
196 return undef;
197 }
198 }
199
200 return $self->{id};
201 }
202
203 sub execute {
204 my ($self, $queue, $ruledb, $mod_group, $targets,
205 $msginfo, $vars, $marks) = @_;
206
207 my $original;
208
209 my $from = 'postmaster';
210
211 my $rulename = $vars->{RULE} // 'unknown';
212
213 my $body = PMG::Utils::subst_values($self->{body}, $vars);
214 my $subject = PMG::Utils::subst_values($self->{subject}, $vars);
215 my $to = PMG::Utils::subst_values($self->{to}, $vars);
216
217 if ($to =~ m/^\s*$/) {
218 # this happens if a notification is triggered by bounce mails
219 # which notifies the sender <> - we just log and then ignore it
220 syslog('info', "%s: notify <> (rule: %s, ignored)", $queue->{logid}, $rulename);
221 return;
222 }
223
224 $to =~ s/[;,]/ /g;
225 $to =~ s/\s+/,/g;
226
227 my $top = MIME::Entity->build(
228 Encoding => 'quoted-printable',
229 Charset => 'UTF-8',
230 From => $from,
231 To => $to,
232 Subject => encode_mimewords(encode('UTF-8', $subject), "Charset" => "UTF-8"),
233 Data => encode('UTF-8', $body));
234
235 if ($self->{attach} eq 'O') {
236 # attach original mail
237 my $spooldir = $PMG::MailQueue::spooldir;
238 my $path = "$spooldir/active/$queue->{uid}";
239 $original = $top->attach(
240 Path => $path,
241 Filename => "original_message.eml",
242 Type => "message/rfc822",);
243 }
244
245 if ($msginfo->{testmode}) {
246 my $fh = $msginfo->{test_fh};
247 print $fh "notify: $self->{to}\n";
248 print $fh "notify content:\n";
249
250 if ($self->{attach} eq 'O') {
251 # make result reproducible for regression testing
252 $top->head->replace('content-type',
253 'multipart/mixed; boundary="---=_1234567"');
254 }
255 $top->print ($fh);
256 print $fh "notify end\n";
257 } else {
258 my @targets = split(/\s*,\s*/, $to);
259 my $qid = PMG::Utils::reinject_mail(
260 $top, $from, \@targets, undef, $msginfo->{fqdn});
261 foreach (@targets) {
262 if ($qid) {
263 syslog('info', "%s: notify <%s> (rule: %s, %s)", $queue->{logid}, $_, $rulename, $qid);
264 } else {
265 syslog ('err', "%s: notify <%s> (rule: %s) failed", $queue->{logid}, $_, $rulename);
266 }
267 }
268 }
269 }
270
271 sub short_desc {
272 my $self = shift;
273
274 return "notify $self->{to}";
275 }
276
277 sub properties {
278 my ($class) = @_;
279
280 return {
281 to => {
282 description => "The Receiver E-Mail address",
283 type => 'string',
284 maxLength => 200,
285 },
286 subject => {
287 description => "The Notification subject",
288 type => 'string',
289 maxLength => 100,
290 },
291 attach => {
292 description => "Attach original E-Mail",
293 type => 'boolean',
294 optional => 1,
295 default => 0,
296 },
297 body => {
298 description => "The Notification Body",
299 type => 'string',
300 maxLength => 2048
301 }
302 };
303 }
304
305 sub get {
306 my ($self) = @_;
307
308 return {
309 to => $self->{to},
310 subject => $self->{subject},
311 body => $self->{body},
312 attach => ($self->{attach} eq 'O') ? 1 : 0,
313 };
314 }
315
316 sub update {
317 my ($self, $param) = @_;
318
319 $self->{to} = $param->{to};
320 $self->{subject} = $param->{subject};
321 $self->{body} = $param->{body};
322 $self->{attach} = $param->{attach} ? 'O' : 'N';
323 }
324
325 1;
326
327 __END__
328
329 =head1 PMG::RuleDB::Notify
330
331 Notifications.