]>
git.proxmox.com Git - pmg-api.git/blob - PMG/API2/Quarantine.pm
1 package PMG
:: API2
:: Quarantine
;
11 use Mail
:: SpamAssassin
;
14 use PVE
:: Exception
qw(raise_param_exc raise_perm_exc) ;
15 use PVE
:: Tools
qw(extract_param) ;
16 use PVE
:: JSONSchema
qw(get_standard_option) ;
19 use PVE
:: APIServer
:: Formatter
;
22 use PMG
:: AccessControl
;
28 use base
qw(PVE::RESTHandler) ;
32 my $extract_pmail = sub {
33 my ( $authuser, $role ) = @_ ;
35 if ( $authuser =~ m/^(.+)\@quarantine$/ ) {
38 raise_param_exc
({ pmail
=> "got unexpected authuser ' $authuser ' with role ' $role '" });
41 my $verify_optional_pmail = sub {
42 my ( $authuser, $role, $pmail_param ) = @_ ;
45 if ( $role eq 'quser' ) {
46 $pmail = $extract_pmail ->( $authuser, $role );
47 raise_param_exc
({ pmail
=> "parameter not allwed with role ' $role '" })
48 if defined ( $pmail_param ) && ( $pmail ne $pmail_param );
50 raise_param_exc
({ pmail
=> "parameter required with role ' $role '" })
51 if ! defined ( $pmail_param );
52 $pmail = $pmail_param ;
60 my $saversion = Mail
:: SpamAssassin-
> VERSION ;
62 my $salocaldir = "/var/lib/spamassassin/ $saversion/updates_spamassassin_org " ;
64 $spamdesc = PMG
:: Utils
:: load_sa_descriptions
([ $salocaldir ]) if ! $spamdesc ;
68 foreach my $test ( split ( ',' , $info )) {
69 my ( $name, $score ) = split ( ':' , $test );
71 my $info = { name
=> $name, score
=> $score + 0 , desc
=> '-' };
72 if ( my $si = $spamdesc ->{ $name }) {
73 $info ->{ desc
} = $si ->{ desc
};
74 $info ->{ url
} = $si ->{ url
} if defined ( $si ->{ url
});
82 my $extract_email = sub {
85 return $data if ! $data ;
87 if ( $data =~ m/^.*\s(\S+)\s*$/ ) {
91 if ( $data =~ m/^<([^<>\s]+)>$/ ) {
95 if ( $data !~ m/[\s><]/ && $data =~ m/^(.+\@[^\.]+\..*[^\.]+)$/ ) {
104 my $get_real_sender = sub {
107 my @lines = split ( ' \n ' , $ref ->{ header
});
108 my $head = Mail
:: Header-
> new ( \
@lines );
110 my @fromarray = split ( '\s*,\s*' , $head -> get ( 'from' ) || $ref ->{ sender
});
111 my $from = $extract_email ->( $fromarray [ 0 ]) || $ref ->{ sender
};;
112 my $sender = $extract_email ->( $head -> get ( 'sender' ));
114 return $sender if $sender ;
119 my $parse_header_info = sub {
122 my $res = { subject
=> '' , from
=> '' };
124 my @lines = split ( ' \n ' , $ref ->{ header
});
125 my $head = Mail
:: Header-
> new ( \
@lines );
127 $res ->{ subject
} = PMG
:: Utils
:: decode_rfc1522
( PVE
:: Tools
:: trim
( $head -> get ( 'subject' ))) // '' ;
129 my @fromarray = split ( '\s*,\s*' , $head -> get ( 'from' ) || $ref ->{ sender
});
131 $res ->{ from
} = PMG
:: Utils
:: decode_rfc1522
( PVE
:: Tools
:: trim
( $fromarray [ 0 ])) // '' ;
133 my $sender = PMG
:: Utils
:: decode_rfc1522
( PVE
:: Tools
:: trim
( $head -> get ( 'sender' )));
134 $res ->{ sender
} = $sender if $sender && ( $sender ne $res ->{ from
});
136 $res ->{ envelope_sender
} = $ref ->{ sender
};
137 $res ->{ receiver
} = $ref ->{ receiver
} // $ref ->{ pmail
};
138 $res ->{ id
} = 'C' . $ref ->{ cid
} . 'R' . $ref ->{ rid
} . 'T' . $ref ->{ ticketid
};
139 $res ->{ time } = $ref ->{ time };
140 $res ->{ bytes
} = $ref ->{ bytes
};
142 my $qtype = $ref ->{ qtype
};
145 $res ->{ virusname
} = $ref ->{ info
};
146 $res ->{ spamlevel
} = 0 ;
147 } elsif ( $qtype eq 'S' ) {
148 $res ->{ spamlevel
} = $ref ->{ spamlevel
} // 0 ;
154 my $pmail_param_type = {
155 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." ,
156 type
=> 'string' , format
=> 'email' ,
160 __PACKAGE__-
> register_method ({
164 permissions
=> { user
=> 'all' },
165 description
=> "Directory index." ,
167 additionalProperties
=> 0 ,
176 links
=> [ { rel
=> 'child' , href
=> "{name}" } ],
182 { name
=> 'whitelist' },
183 { name
=> 'blacklist' },
184 { name
=> 'content' },
186 { name
=> 'spamusers' },
187 { name
=> 'spamstatus' },
189 { name
=> 'virusstatus' },
190 { name
=> 'quarusers' },
197 my $read_or_modify_user_bw_list = sub {
198 my ( $listname, $param, $addrs, $delete ) = @_ ;
200 my $rpcenv = PMG
:: RESTEnvironment-
> get ();
201 my $authuser = $rpcenv -> get_user ();
202 my $role = $rpcenv -> get_role ();
204 my $pmail = $verify_optional_pmail ->( $authuser, $role, $param ->{ pmail
});
206 my $dbh = PMG
:: DBTools
:: open_ruledb
();
208 my $list = PMG
:: Quarantine
:: add_to_blackwhite
(
209 $dbh, $pmail, $listname, $addrs, $delete );
212 foreach my $a ( @$list ) { push @$res, { address
=> $a }; }
216 __PACKAGE__-
> register_method ({
220 permissions
=> { check
=> [ 'admin' , 'qmanager' , 'audit' , 'quser' ] },
221 description
=> "Show user whitelist." ,
223 additionalProperties
=> 0 ,
225 pmail
=> $pmail_param_type,
242 return $read_or_modify_user_bw_list ->( 'WL' , $param );
245 __PACKAGE__-
> register_method ({
246 name
=> 'whitelist_add' ,
249 description
=> "Add user whitelist entries." ,
250 permissions
=> { check
=> [ 'admin' , 'qmanager' , 'audit' , 'quser' ] },
253 additionalProperties
=> 0 ,
255 pmail
=> $pmail_param_type,
256 address
=> get_standard_option
( 'pmg-whiteblacklist-entry-list' , {
257 description
=> "The address you want to add." ,
261 returns
=> { type
=> 'null' },
265 my $addresses = [ split ( ',' , $param ->{ address
})];
266 $read_or_modify_user_bw_list ->( 'WL' , $param, $addresses );
271 __PACKAGE__-
> register_method ({
272 name
=> 'whitelist_delete' ,
273 path
=> 'whitelist/{address}' ,
275 description
=> "Delete user whitelist entries." ,
276 permissions
=> { check
=> [ 'admin' , 'qmanager' , 'audit' , 'quser' ] },
279 additionalProperties
=> 0 ,
281 pmail
=> $pmail_param_type,
282 address
=> get_standard_option
( 'pmg-whiteblacklist-entry-list' , {
283 description
=> "The address you want to remove." ,
287 returns
=> { type
=> 'null' },
291 my $addresses = [ split ( ',' , $param ->{ address
})];
292 $read_or_modify_user_bw_list ->( 'WL' , $param, $addresses, 1 );
297 __PACKAGE__-
> register_method ({
301 permissions
=> { check
=> [ 'admin' , 'qmanager' , 'audit' , 'quser' ] },
302 description
=> "Show user blacklist." ,
304 additionalProperties
=> 0 ,
306 pmail
=> $pmail_param_type,
323 return $read_or_modify_user_bw_list ->( 'BL' , $param );
326 __PACKAGE__-
> register_method ({
327 name
=> 'blacklist_add' ,
330 description
=> "Add user blacklist entries." ,
331 permissions
=> { check
=> [ 'admin' , 'qmanager' , 'audit' , 'quser' ] },
334 additionalProperties
=> 0 ,
336 pmail
=> $pmail_param_type,
337 address
=> get_standard_option
( 'pmg-whiteblacklist-entry-list' , {
338 description
=> "The address you want to add." ,
342 returns
=> { type
=> 'null' },
346 my $addresses = [ split ( ',' , $param ->{ address
})];
347 $read_or_modify_user_bw_list ->( 'BL' , $param, $addresses );
352 __PACKAGE__-
> register_method ({
353 name
=> 'blacklist_delete' ,
354 path
=> 'blacklist/{address}' ,
356 description
=> "Delete user blacklist entries." ,
357 permissions
=> { check
=> [ 'admin' , 'qmanager' , 'audit' , 'quser' ] },
360 additionalProperties
=> 0 ,
362 pmail
=> $pmail_param_type,
363 address
=> get_standard_option
( 'pmg-whiteblacklist-entry-list' , {
364 description
=> "The address you want to remove." ,
368 returns
=> { type
=> 'null' },
372 my $addresses = [ split ( ',' , $param ->{ address
})];
373 $read_or_modify_user_bw_list ->( 'BL' , $param, $addresses, 1 );
378 __PACKAGE__-
> register_method ({
382 permissions
=> { check
=> [ 'admin' , 'qmanager' , 'audit' ] },
383 description
=> "Get a list of receivers of spam in the given timespan (Default the last 24 hours)." ,
385 additionalProperties
=> 0 ,
387 starttime
=> get_standard_option
( 'pmg-starttime' ),
388 endtime
=> get_standard_option
( 'pmg-endtime' ),
397 description
=> 'the receiving email' ,
406 my $rpcenv = PMG
:: RESTEnvironment-
> get ();
407 my $authuser = $rpcenv -> get_user ();
411 my $dbh = PMG
:: DBTools
:: open_ruledb
();
413 my $start = $param ->{ starttime
} // ( time - 86400 );
414 my $end = $param ->{ endtime
} // ( $start + 86400 );
416 my $sth = $dbh -> prepare (
417 "SELECT DISTINCT pmail " .
418 "FROM CMailStore, CMSReceivers WHERE " .
419 "time >= $start AND time < $end AND " .
420 "QType = 'S' AND CID = CMailStore_CID AND RID = CMailStore_RID " .
421 "AND Status = 'N' ORDER BY pmail" );
425 while ( my $ref = $sth -> fetchrow_hashref ()) {
426 push @$res, { mail
=> $ref ->{ pmail
} };
432 __PACKAGE__-
> register_method ({
433 name
=> 'spamstatus' ,
434 path
=> 'spamstatus' ,
436 permissions
=> { check
=> [ 'admin' , 'qmanager' , 'audit' ] },
437 description
=> "Get Spam Quarantine Status" ,
439 additionalProperties
=> 0 ,
446 description
=> 'Number of stored mails.' ,
450 description
=> "Estimated disk space usage in MByte." ,
454 description
=> "Average size of stored mails in bytes." ,
458 description
=> "Average spam level." ,
466 my $dbh = PMG
:: DBTools
:: open_ruledb
();
467 my $ref = PMG
:: DBTools
:: get_quarantine_count
( $dbh, 'S' );
472 __PACKAGE__-
> register_method ({
476 permissions
=> { check
=> [ 'admin' , 'qmanager' , 'audit' ] },
477 description
=> "Get a list of users with whitelist/blacklist setttings." ,
479 additionalProperties
=> 0 ,
483 description
=> 'If set, limits the result to the given list.' ,
484 enum
=> [ 'BL' , 'WL' ],
495 description
=> 'the receiving email' ,
504 my $rpcenv = PMG
:: RESTEnvironment-
> get ();
505 my $authuser = $rpcenv -> get_user ();
509 my $dbh = PMG
:: DBTools
:: open_ruledb
();
512 if ( $param ->{ list
}) {
513 $sth = $dbh -> prepare ( "SELECT DISTINCT pmail FROM UserPrefs WHERE name = ? ORDER BY pmail" );
514 $sth -> execute ( $param ->{ list
});
516 $sth = $dbh -> prepare ( "SELECT DISTINCT pmail FROM UserPrefs ORDER BY pmail" );
520 while ( my $ref = $sth -> fetchrow_hashref ()) {
521 push @$res, { mail
=> $ref ->{ pmail
} };
527 __PACKAGE__-
> register_method ({
531 permissions
=> { check
=> [ 'admin' , 'qmanager' , 'audit' , 'quser' ] },
532 description
=> "Get a list of quarantined spam mails in the given timeframe (default the last 24 hours) for the given user." ,
534 additionalProperties
=> 0 ,
536 starttime
=> get_standard_option
( 'pmg-starttime' ),
537 endtime
=> get_standard_option
( 'pmg-endtime' ),
538 pmail
=> $pmail_param_type,
547 description
=> 'Unique ID' ,
551 description
=> "Size of raw email." ,
555 description
=> "SMTP envelope sender." ,
559 description
=> "Header 'From' field." ,
563 description
=> "Header 'Sender' field." ,
568 description
=> "Receiver email address" ,
572 description
=> "Header 'Subject' field." ,
576 description
=> "Receive time stamp" ,
580 description
=> "Spam score." ,
589 my $rpcenv = PMG
:: RESTEnvironment-
> get ();
590 my $authuser = $rpcenv -> get_user ();
591 my $role = $rpcenv -> get_role ();
593 my $pmail = $verify_optional_pmail ->( $authuser, $role, $param ->{ pmail
});
597 my $dbh = PMG
:: DBTools
:: open_ruledb
();
599 my $start = $param ->{ starttime
} // ( time - 86400 );
600 my $end = $param ->{ endtime
} // ( $start + 86400 );
602 my $sth = $dbh -> prepare (
604 "FROM CMailStore, CMSReceivers WHERE " .
605 "pmail = ? AND time >= $start AND time < $end AND " .
606 "QType = 'S' AND CID = CMailStore_CID AND RID = CMailStore_RID " .
607 "AND Status = 'N' ORDER BY pmail, time, receiver" );
609 $sth -> execute ( $pmail );
611 while ( my $ref = $sth -> fetchrow_hashref ()) {
612 my $data = $parse_header_info ->( $ref );
619 __PACKAGE__-
> register_method ({
623 permissions
=> { check
=> [ 'admin' , 'qmanager' , 'audit' ] },
624 description
=> "Get a list of quarantined virus mails in the given timeframe (default the last 24 hours)." ,
626 additionalProperties
=> 0 ,
628 starttime
=> get_standard_option
( 'pmg-starttime' ),
629 endtime
=> get_standard_option
( 'pmg-endtime' ),
638 description
=> 'Unique ID' ,
642 description
=> "Size of raw email." ,
646 description
=> "SMTP envelope sender." ,
650 description
=> "Header 'From' field." ,
654 description
=> "Header 'Sender' field." ,
659 description
=> "Receiver email address" ,
663 description
=> "Header 'Subject' field." ,
667 description
=> "Receive time stamp" ,
671 description
=> "Virus name." ,
680 my $rpcenv = PMG
:: RESTEnvironment-
> get ();
681 my $authuser = $rpcenv -> get_user ();
685 my $dbh = PMG
:: DBTools
:: open_ruledb
();
687 my $start = $param ->{ starttime
} // ( time - 86400 );
688 my $end = $param ->{ endtime
} // ( $start + 86400 );
690 my $sth = $dbh -> prepare (
692 "FROM CMailStore, CMSReceivers WHERE " .
693 "time >= $start AND time < $end AND " .
694 "QType = 'V' AND CID = CMailStore_CID AND RID = CMailStore_RID " .
695 "AND Status = 'N' ORDER BY time, receiver" );
699 while ( my $ref = $sth -> fetchrow_hashref ()) {
700 my $data = $parse_header_info ->( $ref );
707 __PACKAGE__-
> register_method ({
708 name
=> 'virusstatus' ,
709 path
=> 'virusstatus' ,
711 permissions
=> { check
=> [ 'admin' , 'qmanager' , 'audit' ] },
712 description
=> "Get Virus Quarantine Status" ,
714 additionalProperties
=> 0 ,
721 description
=> 'Number of stored mails.' ,
725 description
=> "Estimated disk space usage in MByte." ,
729 description
=> "Average size of stored mails in bytes." ,
737 my $dbh = PMG
:: DBTools
:: open_ruledb
();
738 my $ref = PMG
:: DBTools
:: get_quarantine_count
( $dbh, 'V' );
740 delete $ref ->{ avgspam
};
745 __PACKAGE__-
> register_method ({
749 permissions
=> { check
=> [ 'admin' , 'qmanager' , 'audit' , 'quser' ] },
750 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)." ,
752 additionalProperties
=> 0 ,
755 description
=> 'Unique ID' ,
757 pattern
=> 'C\d+R\d+T\d+' ,
761 description
=> "Display 'raw' eml data. Deactivates size limit." ,
772 description
=> 'Unique ID' ,
776 description
=> "Size of raw email." ,
780 description
=> "SMTP envelope sender." ,
784 description
=> "Header 'From' field." ,
788 description
=> "Header 'Sender' field." ,
793 description
=> "Receiver email address" ,
797 description
=> "Header 'Subject' field." ,
801 description
=> "Receive time stamp" ,
805 description
=> "Spam score." ,
809 description
=> "Information about matched spam tests (name, score, desc, url)." ,
813 description
=> "Raw email header data." ,
817 description
=> "Raw email data (first 4096 bytes). Useful for preview. NOTE: The 'htmlmail' formatter displays the whole email." ,
825 my $rpcenv = PMG
:: RESTEnvironment-
> get ();
826 my $authuser = $rpcenv -> get_user ();
827 my $role = $rpcenv -> get_role ();
828 my $format = $rpcenv -> get_format ();
830 my $raw = $param ->{ raw
} // 0 ;
832 my ( $cid, $rid, $tid ) = $param ->{ id
} =~ m/^C(\d+)R(\d+)T(\d+)$/ ;
837 my $dbh = PMG
:: DBTools
:: open_ruledb
();
839 my $ref = PMG
:: DBTools
:: load_mail_data
( $dbh, $cid, $rid, $tid );
841 if ( $role eq 'quser' ) {
842 my $quar_username = $ref ->{ pmail
} . ' @quarantine ' ;
843 raise_perm_exc
( "mail does not belong to user ' $authuser ' ( $ref ->{pmail})" )
844 if $authuser ne $quar_username ;
847 my $res = $parse_header_info ->( $ref );
849 my $filename = $ref ->{ file
};
850 my $spooldir = $PMG :: MailQueue
:: spooldir
;
852 my $path = " $spooldir/$filename " ;
854 if ( $format eq 'htmlmail' ) {
856 my $cfg = PMG
:: Config-
> new ();
857 my $viewimages = $cfg -> get ( 'spamquar' , 'viewimages' );
858 my $allowhref = $cfg -> get ( 'spamquar' , 'allowhrefs' );
860 $res ->{ content
} = PMG
:: HTMLMail
:: email_to_html
( $path, $raw, $viewimages, $allowhref );
862 # to make result verification happy
865 $res ->{ spamlevel
} = 0 ;
866 $res ->{ spaminfo
} = [];
868 # include additional details
870 # we want to get the whole email in raw mode
871 my $maxbytes = (! $raw ) ?
4096 : undef ;
873 my ( $header, $content ) = PMG
:: HTMLMail
:: read_raw_email
( $path, $maxbytes );
875 $res ->{ file
} = $ref ->{ file
};
876 $res ->{ spaminfo
} = decode_spaminfo
( $ref ->{ info
});
877 $res ->{ header
} = $header ;
878 $res ->{ content
} = $content ;
886 PVE
:: APIServer
:: Formatter
:: register_page_formatter
(
887 'format' => 'htmlmail' ,
889 path
=> '/quarantine/content' ,
891 my ( $res, $data, $param, $path, $auth, $config ) = @_ ;
893 if (! HTTP
:: Status
:: is_success
( $res ->{ status
})) {
894 return ( "Error $res ->{status}: $res ->{message}" , "text/plain" );
897 my $ct = "text/html;charset=UTF-8" ;
899 my $raw = $data ->{ content
};
901 return ( encode
( 'UTF-8' , $raw ), $ct, 1 );
904 __PACKAGE__-
> register_method ({
908 description
=> "Execute quarantine actions." ,
909 permissions
=> { check
=> [ 'admin' , 'qmanager' , 'quser' ] },
912 additionalProperties
=> 0 ,
915 description
=> 'Unique IDs, seperate with ;' ,
917 pattern
=> 'C\d+R\d+T\d+(;C\d+R\d+T\d+)*' ,
920 description
=> 'Action - specify what you want to do with the mail.' ,
922 enum
=> [ 'whitelist' , 'blacklist' , 'deliver' , 'delete' ],
926 returns
=> { type
=> "null" },
930 my $rpcenv = PMG
:: RESTEnvironment-
> get ();
931 my $authuser = $rpcenv -> get_user ();
932 my $role = $rpcenv -> get_role ();
933 my $action = $param ->{ action
};
934 my @idlist = split ( ';' , $param ->{ id
});
936 my $dbh = PMG
:: DBTools
:: open_ruledb
();
938 for my $id ( @idlist ) {
939 my ( $cid, $rid, $tid ) = $id =~ m/^C(\d+)R(\d+)T(\d+)$/ ;
944 my $ref = PMG
:: DBTools
:: load_mail_data
( $dbh, $cid, $rid, $tid );
946 if ( $role eq 'quser' ) {
947 my $quar_username = $ref ->{ pmail
} . ' @quarantine ' ;
948 raise_perm_exc
( "mail does not belong to user ' $authuser ' ( $ref ->{pmail})" )
949 if $authuser ne $quar_username ;
952 my $sender = $get_real_sender ->( $ref );
954 if ( $action eq 'whitelist' ) {
955 PMG
:: Quarantine
:: add_to_blackwhite
( $dbh, $ref ->{ pmail
}, 'WL' , [ $sender ]);
956 } elsif ( $action eq 'blacklist' ) {
957 PMG
:: Quarantine
:: add_to_blackwhite
( $dbh, $ref ->{ pmail
}, 'BL' , [ $sender ]);
958 } elsif ( $action eq 'deliver' ) {
959 PMG
:: Quarantine
:: deliver_quarantined_mail
( $dbh, $ref, $ref ->{ receiver
} // $ref ->{ pmail
});
960 } elsif ( $action eq 'delete' ) {
961 PMG
:: Quarantine
:: delete_quarantined_mail
( $dbh, $ref );
963 die "internal error" ; # should not be reached