]>
Commit | Line | Data |
---|---|---|
b66faa68 DM |
1 | package PMG::API2::Quarantine; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | use Time::Local; | |
6 | use Time::Zone; | |
7 | use Data::Dumper; | |
34db0c3f | 8 | use Encode; |
b66faa68 | 9 | |
34db0c3f | 10 | use Mail::Header; |
dae021a8 | 11 | |
b66faa68 | 12 | use PVE::SafeSyslog; |
6e8886d4 | 13 | use PVE::Exception qw(raise_param_exc raise_perm_exc); |
b66faa68 DM |
14 | use PVE::Tools qw(extract_param); |
15 | use PVE::JSONSchema qw(get_standard_option); | |
16 | use PVE::RESTHandler; | |
17 | use PVE::INotify; | |
34db0c3f | 18 | use PVE::APIServer::Formatter; |
b66faa68 | 19 | |
dae021a8 | 20 | use PMG::Utils; |
b66faa68 | 21 | use PMG::AccessControl; |
34db0c3f | 22 | use PMG::Config; |
b66faa68 | 23 | use PMG::DBTools; |
34db0c3f | 24 | use PMG::HTMLMail; |
b66faa68 DM |
25 | |
26 | use base qw(PVE::RESTHandler); | |
27 | ||
28 | ||
dae021a8 DM |
29 | my $parse_header_info = sub { |
30 | my ($ref) = @_; | |
31 | ||
32 | my $res = { subject => '', from => '' }; | |
33 | ||
34 | my @lines = split('\n', $ref->{header}); | |
35 | my $head = Mail::Header->new(\@lines); | |
36 | ||
37 | $res->{subject} = PMG::Utils::decode_rfc1522(PVE::Tools::trim($head->get('subject'))) // ''; | |
38 | ||
39 | my @fromarray = split('\s*,\s*', $head->get('from') || $ref->{sender}); | |
40 | ||
41 | $res->{from} = PMG::Utils::decode_rfc1522(PVE::Tools::trim ($fromarray[0])) // ''; | |
42 | ||
43 | my $sender = PMG::Utils::decode_rfc1522(PVE::Tools::trim($head->get('sender'))); | |
44 | $res->{sender} = $sender if $sender; | |
45 | ||
46 | $res->{envelope_sender} = $ref->{sender}; | |
47 | $res->{receiver} = $ref->{receiver}; | |
6e8886d4 | 48 | $res->{id} = 'C' . $ref->{cid} . 'R' . $ref->{rid}; |
dae021a8 DM |
49 | $res->{time} = $ref->{time}; |
50 | $res->{bytes} = $ref->{bytes}; | |
51 | ||
52 | return $res; | |
53 | }; | |
54 | ||
6e8886d4 | 55 | |
b66faa68 DM |
56 | __PACKAGE__->register_method ({ |
57 | name => 'index', | |
58 | path => '', | |
59 | method => 'GET', | |
60 | permissions => { user => 'all' }, | |
61 | description => "Directory index.", | |
62 | parameters => { | |
63 | additionalProperties => 0, | |
64 | properties => {}, | |
65 | }, | |
66 | returns => { | |
67 | type => 'array', | |
68 | items => { | |
69 | type => "object", | |
70 | properties => {}, | |
71 | }, | |
72 | links => [ { rel => 'child', href => "{name}" } ], | |
73 | }, | |
74 | code => sub { | |
75 | my ($param) = @_; | |
76 | ||
77 | my $result = [ | |
78 | { name => 'deliver' }, | |
6e8886d4 | 79 | { name => 'content' }, |
b66faa68 DM |
80 | { name => 'spam' }, |
81 | { name => 'virus' }, | |
82 | ]; | |
83 | ||
84 | return $result; | |
85 | }}); | |
86 | ||
87 | __PACKAGE__->register_method ({ | |
88 | name => 'spam', | |
89 | path => 'spam', | |
90 | method => 'GET', | |
91 | permissions => { check => [ 'admin', 'qmanager', 'audit', 'quser'] }, | |
92 | description => "Show spam mails distribution (per day).", | |
93 | parameters => { | |
94 | additionalProperties => 0, | |
95 | properties => { | |
96 | starttime => { | |
97 | description => "Only consider entries newer than 'startime' (unix epoch).", | |
98 | type => 'integer', | |
99 | minimum => 0, | |
100 | optional => 1, | |
101 | }, | |
102 | endtime => { | |
103 | description => "Only consider entries older than 'endtime' (unix epoch).", | |
104 | type => 'integer', | |
105 | minimum => 1, | |
106 | optional => 1, | |
107 | }, | |
108 | pmail => { | |
ded33c7c | 109 | description => "List entries for the user with this primary email address. Quarantine users cannot speficy this parameter, but it is required for all other roles.", |
b66faa68 DM |
110 | type => 'string', format => 'email', |
111 | optional => 1, | |
112 | }, | |
113 | }, | |
114 | }, | |
115 | returns => { | |
116 | type => 'array', | |
117 | items => { | |
118 | type => "object", | |
119 | properties => { | |
120 | day => { | |
121 | description => "Day (as unix epoch).", | |
122 | type => 'integer', | |
123 | }, | |
bc1ebe25 | 124 | count => { |
b66faa68 DM |
125 | description => "Number of quarantine entries.", |
126 | type => 'integer', | |
127 | }, | |
128 | spamavg => { | |
129 | description => "Average spam level.", | |
130 | type => 'number', | |
bc1ebe25 | 131 | }, |
b66faa68 DM |
132 | }, |
133 | }, | |
ded33c7c | 134 | links => [ { rel => 'child', href => "{day}" } ], |
b66faa68 DM |
135 | }, |
136 | code => sub { | |
137 | my ($param) = @_; | |
138 | ||
139 | my $rpcenv = PMG::RESTEnvironment->get(); | |
140 | my $authuser = $rpcenv->get_user(); | |
141 | my $role = $rpcenv->get_role(); | |
142 | ||
143 | my $pmail = $param->{pmail}; | |
144 | ||
145 | if ($role eq 'quser') { | |
146 | raise_param_exc({ pmail => "paramater not allwed with role '$role'"}) | |
147 | if defined($pmail); | |
148 | $pmail = $authuser; | |
ded33c7c DM |
149 | } else { |
150 | raise_param_exc({ pmail => "paramater required with role '$role'"}) | |
151 | if !defined($pmail); | |
b66faa68 DM |
152 | } |
153 | ||
154 | my $res = []; | |
bc1ebe25 | 155 | |
b66faa68 DM |
156 | my $dbh = PMG::DBTools::open_ruledb(); |
157 | ||
ec7035c2 | 158 | my $start = $param->{starttime}; |
b66faa68 DM |
159 | my $end = $param->{endtime}; |
160 | ||
161 | my $timezone = tz_local_offset(); | |
162 | ||
163 | my $sth = $dbh->prepare( | |
164 | "SELECT " . | |
165 | "((time + $timezone) / 86400) * 86400 - $timezone as day, " . | |
166 | "count (ID) as count, avg (Spamlevel) as spamavg " . | |
167 | "FROM CMailStore, CMSReceivers WHERE " . | |
ec7035c2 DM |
168 | (defined($start) ? "time >= $start AND " : '') . |
169 | (defined($end) ? "time < $end AND " : '') . | |
ded33c7c | 170 | "pmail = ? AND " . |
b66faa68 DM |
171 | "QType = 'S' AND CID = CMailStore_CID AND RID = CMailStore_RID " . |
172 | "AND Status = 'N' " . | |
173 | "GROUP BY day " . | |
174 | "ORDER BY day DESC"); | |
175 | ||
ded33c7c DM |
176 | $sth->execute($pmail); |
177 | ||
178 | while (my $ref = $sth->fetchrow_hashref()) { | |
179 | push @$res, $ref; | |
180 | } | |
181 | ||
182 | return $res; | |
183 | }}); | |
184 | ||
185 | __PACKAGE__->register_method ({ | |
186 | name => 'spamlist', | |
187 | path => 'spam/{starttime}', | |
188 | method => 'GET', | |
189 | permissions => { check => [ 'admin', 'qmanager', 'audit', 'quser'] }, | |
190 | description => "Show spam mails distribution (per day).", | |
191 | parameters => { | |
192 | additionalProperties => 0, | |
193 | properties => { | |
194 | starttime => { | |
195 | description => "Only consider entries newer than 'starttime' (unix epoch).", | |
196 | type => 'integer', | |
197 | minimum => 0, | |
198 | }, | |
199 | endtime => { | |
200 | description => "Only consider entries older than 'endtime' (unix epoch). This is set to '<start> + 1day' by default.", | |
201 | type => 'integer', | |
202 | minimum => 1, | |
203 | optional => 1, | |
204 | }, | |
205 | pmail => { | |
206 | description => "List entries for the user with this primary email address. Quarantine users cannot speficy this parameter, but it is required for all other roles.", | |
207 | type => 'string', format => 'email', | |
208 | optional => 1, | |
209 | }, | |
210 | }, | |
211 | }, | |
212 | returns => { | |
213 | type => 'array', | |
214 | items => { | |
215 | type => "object", | |
dae021a8 DM |
216 | properties => { |
217 | id => { | |
218 | description => 'Unique ID', | |
219 | type => 'string', | |
220 | }, | |
221 | bytes => { | |
222 | description => "Size of raw email.", | |
223 | type => 'integer' , | |
224 | }, | |
225 | envelope_sender => { | |
226 | description => "SMTP envelope sender.", | |
227 | type => 'string', | |
228 | }, | |
229 | from => { | |
230 | description => "Header 'From' field.", | |
231 | type => 'string', | |
232 | }, | |
233 | sender => { | |
234 | description => "Header 'Sender' field.", | |
235 | type => 'string', | |
236 | optional => 1, | |
237 | }, | |
238 | receiver => { | |
239 | description => "Receiver email address", | |
240 | type => 'string', | |
241 | }, | |
242 | subject => { | |
243 | description => "Header 'Subject' field.", | |
244 | type => 'string', | |
245 | }, | |
246 | time => { | |
247 | description => "Receive time stamp", | |
248 | type => 'integer', | |
249 | }, | |
250 | }, | |
ded33c7c DM |
251 | }, |
252 | }, | |
253 | code => sub { | |
254 | my ($param) = @_; | |
255 | ||
256 | my $rpcenv = PMG::RESTEnvironment->get(); | |
257 | my $authuser = $rpcenv->get_user(); | |
258 | my $role = $rpcenv->get_role(); | |
259 | ||
260 | my $pmail = $param->{pmail}; | |
261 | ||
262 | if ($role eq 'quser') { | |
263 | raise_param_exc({ pmail => "paramater not allwed with role '$role'"}) | |
264 | if defined($pmail); | |
265 | $pmail = $authuser; | |
b66faa68 | 266 | } else { |
ded33c7c DM |
267 | raise_param_exc({ pmail => "paramater required with role '$role'"}) |
268 | if !defined($pmail); | |
b66faa68 DM |
269 | } |
270 | ||
ded33c7c DM |
271 | my $res = []; |
272 | ||
273 | my $dbh = PMG::DBTools::open_ruledb(); | |
274 | ||
275 | my $start = $param->{starttime}; | |
276 | my $end = $param->{endtime} // ($start + 86400); | |
277 | ||
278 | my $sth = $dbh->prepare( | |
279 | "SELECT * " . | |
280 | "FROM CMailStore, CMSReceivers WHERE " . | |
281 | "pmail = ? AND time >= $start AND time < $end AND " . | |
282 | "QType = 'S' AND CID = CMailStore_CID AND RID = CMailStore_RID " . | |
283 | "AND Status = 'N' ORDER BY pmail, time, receiver"); | |
284 | ||
285 | $sth->execute($pmail); | |
286 | ||
b66faa68 | 287 | while (my $ref = $sth->fetchrow_hashref()) { |
dae021a8 DM |
288 | my $data = $parse_header_info->($ref); |
289 | push @$res, $data; | |
b66faa68 DM |
290 | } |
291 | ||
292 | return $res; | |
293 | }}); | |
294 | ||
6e8886d4 DM |
295 | __PACKAGE__->register_method ({ |
296 | name => 'content', | |
297 | path => 'content', | |
298 | method => 'GET', | |
299 | permissions => { check => [ 'admin', 'qmanager', 'audit', 'quser'] }, | |
34db0c3f | 300 | description => "Get email data. There is a special formatter called 'htmlmail' to get sanitized html view of the mail content (use the '/api2/htmlmail/quarantine/content' url).", |
6e8886d4 DM |
301 | parameters => { |
302 | additionalProperties => 0, | |
303 | properties => { | |
304 | id => { | |
305 | description => 'Unique ID', | |
306 | type => 'string', | |
307 | pattern => 'C\d+R\d+', | |
308 | maxLength => 40, | |
309 | }, | |
34db0c3f DM |
310 | raw => { |
311 | description => "Display 'raw' eml data. This is only used with the 'htmlmail' formatter.", | |
312 | type => 'boolean', | |
313 | optional => 1, | |
314 | default => 0, | |
315 | }, | |
6e8886d4 DM |
316 | }, |
317 | }, | |
318 | returns => { | |
319 | type => "object", | |
320 | properties => {}, | |
321 | }, | |
322 | code => sub { | |
323 | my ($param) = @_; | |
324 | ||
325 | my $rpcenv = PMG::RESTEnvironment->get(); | |
326 | my $authuser = $rpcenv->get_user(); | |
327 | my $role = $rpcenv->get_role(); | |
34db0c3f | 328 | my $format = $rpcenv->get_format(); |
6e8886d4 DM |
329 | |
330 | my ($cid, $rid) = $param->{id} =~ m/^C(\d+)R(\d+)$/; | |
331 | $cid = int($cid); | |
332 | $rid = int($rid); | |
333 | ||
334 | my $dbh = PMG::DBTools::open_ruledb(); | |
335 | ||
336 | my $ref = PMG::DBTools::load_mail_data($dbh, $cid, $rid); | |
337 | ||
338 | if ($role eq 'quser') { | |
339 | raise_perm_exc("mail does not belong to user '$authuser'") | |
340 | if $authuser ne $ref->{pmail}; | |
341 | } | |
342 | ||
343 | my $res = $parse_header_info->($ref); | |
344 | ||
345 | foreach my $k (qw(info file spamlevel)) { | |
346 | $res->{$k} = $ref->{$k} if defined($ref->{$k}); | |
347 | } | |
348 | ||
349 | my $filename = $ref->{file}; | |
350 | my $spooldir = $PMG::MailQueue::spooldir; | |
351 | ||
352 | my $path = "$spooldir/$filename"; | |
353 | ||
34db0c3f DM |
354 | if ($format eq 'htmlmail') { |
355 | ||
356 | my $cfg = PMG::Config->new(); | |
357 | my $viewimages = $cfg->get('spamquar', 'viewimages'); | |
358 | my $allowhref = $cfg->get('spamquar', 'allowhrefs'); | |
359 | ||
360 | $res->{header} = ''; # not required | |
361 | $res->{content} = PMG::HTMLMail::email_to_html($path, $param->{raw}, $viewimages, $allowhref); | |
362 | ||
363 | } else { | |
364 | my ($header, $content) = PMG::HTMLMail::read_raw_email($path, 4096); | |
365 | ||
366 | $res->{header} = $header; | |
367 | $res->{content} = $content; | |
368 | } | |
6e8886d4 | 369 | |
6e8886d4 DM |
370 | |
371 | return $res; | |
372 | ||
373 | }}); | |
374 | ||
34db0c3f DM |
375 | PVE::APIServer::Formatter::register_page_formatter( |
376 | 'format' => 'htmlmail', | |
377 | method => 'GET', | |
378 | path => '/quarantine/content', | |
379 | code => sub { | |
380 | my ($res, $data, $param, $path, $auth, $config) = @_; | |
381 | ||
382 | if(!HTTP::Status::is_success($res->{status})) { | |
383 | return ("Error $res->{status}: $res->{message}", "text/plain"); | |
384 | } | |
385 | ||
386 | my $ct = "text/html;charset=UTF-8"; | |
387 | ||
388 | my $raw = $data->{content}; | |
389 | ||
390 | return (encode('UTF-8', $raw), $ct, 1); | |
391 | }); | |
392 | ||
b66faa68 | 393 | 1; |