[pmg-devel] [PATCH pmg-api v2 09/10] API2/Quarantine: add attachment quarantine api paths

Stoiko Ivanov s.ivanov at proxmox.com
Thu Oct 3 20:02:52 CEST 2019


On Mon, 30 Sep 2019 14:55:33 +0200
Dominik Csapak <d.csapak at proxmox.com> wrote:

> this introduces 3 new api paths in /quarantine/
> 
> * GET attachment
>   lists all mails in the attachment quarantine, similar to the virus one
> 
> * GET listattachments
>   lists all attachments for a specific mail in the quarantine
> 
> * GET download
>   serves the attachment of a mail as a download to the user
> 
> also refactor the mail info gathering
> 
> Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
> ---
> changes from v1:
> * refactor get_and_check_mail better (give rpcenv instead of authuser/role),
>   and use it one more time than before
> * change dumpdir to '/var/run/pmgproxy/' (tmpfs)
nit: I'd rather use '/run/pmgproxy' (they should symlinked anyways)

> * adapt to send_file changes and open the file for download instead of
>   moving to another dir
> 
>  src/PMG/API2/Quarantine.pm | 267 ++++++++++++++++++++++++++++++++-----
>  1 file changed, 237 insertions(+), 30 deletions(-)
> 
> diff --git a/src/PMG/API2/Quarantine.pm b/src/PMG/API2/Quarantine.pm
> index ec93e80..aa33350 100644
> --- a/src/PMG/API2/Quarantine.pm
> +++ b/src/PMG/API2/Quarantine.pm
> @@ -6,6 +6,8 @@ use Time::Local;
>  use Time::Zone;
>  use Data::Dumper;
>  use Encode;
> +use File::Path;
> +use IO::File;
>  
>  use Mail::Header;
>  use Mail::SpamAssassin;
> @@ -24,6 +26,8 @@ use PMG::Config;
>  use PMG::DBTools;
>  use PMG::HTMLMail;
>  use PMG::Quarantine;
> +use PMG::MailQueue;
> +use PMG::MIMEUtils;
>  
>  use base qw(PVE::RESTHandler);
>  
> @@ -188,6 +192,9 @@ __PACKAGE__->register_method ({
>  	    { name => 'virus' },
>  	    { name => 'virusstatus' },
>  	    { name => 'quarusers' },
> +	    { name => 'attachment' },
> +	    { name => 'listattachments' },
> +	    { name => 'download' },
>  	];
>  
>  	return $result;
> @@ -698,6 +705,65 @@ __PACKAGE__->register_method ({
>  	return $quarantine_api->($param, 'V');
>      }});
>  
> +__PACKAGE__->register_method ({
> +    name => 'attachment',
> +    path => 'attachment',
> +    method => 'GET',
> +    permissions => { check => [ 'admin', 'qmanager', 'audit' ] },
> +    description => "Get a list of quarantined attachment mails in the given timeframe (default the last 24 hours).",
> +    parameters => {
> +	additionalProperties => 0,
> +	properties => {
> +	    starttime => get_standard_option('pmg-starttime'),
> +	    endtime => get_standard_option('pmg-endtime'),
> +	},
> +    },
> +    returns => {
> +	type => 'array',
> +	items => {
> +	    type => "object",
> +	    properties => {
> +		id => {
> +		    description => 'Unique ID',
> +		    type => 'string',
> +		},
> +		bytes => {
> +		    description => "Size of raw email.",
> +		    type => 'integer' ,
> +		},
> +		envelope_sender => {
> +		    description => "SMTP envelope sender.",
> +		    type => 'string',
> +		},
> +		from => {
> +		    description => "Header 'From' field.",
> +		    type => 'string',
> +		},
> +		sender => {
> +		    description => "Header 'Sender' field.",
> +		    type => 'string',
> +		    optional => 1,
> +		},
> +		receiver => {
> +		    description => "Receiver email address",
> +		    type => 'string',
> +		},
> +		subject => {
> +		    description => "Header 'Subject' field.",
> +		    type => 'string',
> +		},
> +		time => {
> +		    description => "Receive time stamp",
> +		    type => 'integer',
> +		},
> +	    },
> +	},
> +    },
> +    code => sub {
> +	my ($param) = @_;
> +	return $quarantine_api->($param, 'A');
> +    }});
> +
>  __PACKAGE__->register_method ({
>      name => 'virusstatus',
>      path => 'virusstatus',
> @@ -736,6 +802,32 @@ __PACKAGE__->register_method ({
>  	return $ref;
>      }});
>  
> +my $get_and_check_mail = sub {
> +    my ($id, $rpcenv, $dbh) = @_;
> +
> +    my ($cid, $rid, $tid) = $id =~ m/^C(\d+)R(\d+)T(\d+)$/;
> +    $cid = int($cid);
> +    $rid = int($rid);
> +    $tid = int($tid);
> +
> +    if (!$dbh) {
> +	$dbh = PMG::DBTools::open_ruledb();
> +    }
> +
> +    my $ref = PMG::DBTools::load_mail_data($dbh, $cid, $rid, $tid);
> +
> +    my $authuser = $rpcenv->get_user();
> +    my $role = $rpcenv->get_role();
> +
> +    if ($role eq 'quser') {
> +	my $quar_username = $ref->{pmail} . '@quarantine';
> +	raise_perm_exc("mail does not belong to user '$authuser' ($ref->{pmail})")
> +	if $authuser ne $quar_username;
> +    }
> +
> +    return $ref;
> +};
> +
>  __PACKAGE__->register_method ({
>      name => 'content',
>      path => 'content',
> @@ -817,26 +909,11 @@ __PACKAGE__->register_method ({
>  	my ($param) = @_;
>  
>  	my $rpcenv = PMG::RESTEnvironment->get();
> -	my $authuser = $rpcenv->get_user();
> -	my $role = $rpcenv->get_role();
>  	my $format = $rpcenv->get_format();
>  
>  	my $raw = $param->{raw} // 0;
>  
> -	my ($cid, $rid, $tid) = $param->{id} =~ m/^C(\d+)R(\d+)T(\d+)$/;
> -	$cid = int($cid);
> -	$rid = int($rid);
> -	$tid = int($tid);
> -
> -	my $dbh = PMG::DBTools::open_ruledb();
> -
> -	my $ref = PMG::DBTools::load_mail_data($dbh, $cid, $rid, $tid);
> -
> -	if ($role eq 'quser') {
> -	    my $quar_username = $ref->{pmail} . '@quarantine';
> -	    raise_perm_exc("mail does not belong to user '$authuser' ($ref->{pmail})")
> -		if $authuser ne $quar_username;
> -	}
> +	my $ref = $get_and_check_mail->($param->{id}, $rpcenv);
>  
>  	my $res = $parse_header_info->($ref);
>  
> @@ -872,6 +949,149 @@ __PACKAGE__->register_method ({
>  	    $res->{content} = $content;
>  	}
>  
> +	return $res;
> +
> +    }});
> +
> +my $get_attachments = sub {
> +    my ($mailid, $dumpdir, $with_path) = @_;
> +
> +    my $rpcenv = PMG::RESTEnvironment->get();
> +
> +    my $ref = $get_and_check_mail->($mailid, $rpcenv);
> +
> +    my $filename = $ref->{file};
> +    my $spooldir = $PMG::MailQueue::spooldir;
> +
> +    my $parser = PMG::MIMEUtils::new_mime_parser({
> +	nested => 1,
> +	decode_bodies => 0,
> +	extract_uuencode => 0,
> +	dumpdir => $dumpdir,
> +    });
> +
> +    my $entity = $parser->parse_open("$spooldir/$filename");
> +    PMG::MIMEUtils::fixup_multipart($entity);
> +    PMG::MailQueue::decode_entities($parser, 'attachmentquarantine', $entity);
> +
> +    my $res = [];
> +    my $id = 0;
> +
> +    PMG::MIMEUtils::traverse_mime_parts($entity, sub {
> +	my ($part) = @_;
> +	my $name = PMG::Utils::extract_filename($part->head) || "part-$id";
> +	my $attachment_path = $part->{PMX_decoded_path};
> +	return if !$attachment_path || ! -f $attachment_path;
> +	my $size = -s $attachment_path // 0;
> +	my $entry = {
> +	    id => $id,
> +	    name => $name,
> +	    size => $size,
> +	    'content-type' => $part->head->mime_attr('content-type'),
> +	};
> +	$entry->{path} = $attachment_path if $with_path;
> +	push @$res, $entry;
> +	$id++;
> +    });
> +
> +    return $res;
> +};
> +
> +__PACKAGE__->register_method ({
> +    name => 'listattachments',
> +    path => 'listattachments',
> +    method => 'GET',
> +    permissions => { check => [ 'admin', 'qmanager', 'audit'] },
> +    description => "Get Attachments for E-Mail in Quarantine.",
> +    parameters => {
> +	additionalProperties => 0,
> +	properties => {
> +	    id => {
> +		description => 'Unique ID',
> +		type => 'string',
> +		pattern => 'C\d+R\d+T\d+',
> +		maxLength => 60,
> +	    },
> +	},
> +    },
> +    returns => {
> +	type => "array",
> +	items => {
> +	    type => "object",
> +	    properties => {
> +		id => {
> +		    description => 'Attachment ID',
> +		    type => 'integer',
> +		},
> +		size => {
> +		    description => "Size of raw attachment in bytes.",
> +		    type => 'integer' ,
> +		},
> +		name => {
> +		    description => "Raw email header data.",
> +		    type => 'string',
> +		},
> +		'content-type' => {
> +		    description => "Raw email header data.",
> +		    type => 'string',
> +		},
> +	    },
> +	},
> +    },
> +    code => sub {
> +	my ($param) = @_;
> +
> +	my $dumpdir = "/var/run/pmgproxy/pmg-$param->{id}-$$";
> +	my $res = $get_attachments->($param->{id}, $dumpdir);
> +	rmtree $dumpdir;
> +
> +	return $res;
> +
> +    }});
> +
> +__PACKAGE__->register_method ({
> +    name => 'download',
> +    path => 'download',
> +    method => 'GET',
> +    permissions => { check => [ 'admin', 'qmanager', 'audit'] },
> +    description => "Download Attachment for E-Mail in Quarantine.",
> +    download => 1,
> +    parameters => {
> +	additionalProperties => 0,
> +	properties => {
> +	    mailid => {
> +		description => 'Unique ID',
> +		type => 'string',
> +		pattern => 'C\d+R\d+T\d+',
> +		maxLength => 60,
> +	    },
> +	    attachmentid => {
> +		description => "The Attachment ID for the mail.",
> +		type => 'integer',
> +	    },
> +	},
> +    },
> +    returns => {
> +	type => "object",
> +    },
> +    code => sub {
> +	my ($param) = @_;
> +
> +	my $mailid = $param->{mailid};
> +	my $attachmentid = $param->{attachmentid};
> +
> +	my $dumpdir = "/var/run/pmgproxy/pmg-$mailid-$$/";
> +	my $attachments = $get_attachments->($mailid, $dumpdir, 1);
> +
> +	my $res = $attachments->[$attachmentid];
> +	if (!$res) {
> +	    raise_param_exc({ attachmentid => "Invalid Attachment ID for Mail."});
> +	}
> +
> +	$res->{fh} = IO::File->new($res->{path}, '<') ||
> +	    die "unable to open file '$res->{path}' - $!\n";
> +
> +	rmtree $dumpdir;
>  
>  	return $res;
>  
> @@ -922,27 +1142,14 @@ __PACKAGE__->register_method ({
>  	my ($param) = @_;
>  
>  	my $rpcenv = PMG::RESTEnvironment->get();
> -	my $authuser = $rpcenv->get_user();
> -	my $role = $rpcenv->get_role();
>  	my $action = $param->{action};
>  	my @idlist = split(';', $param->{id});
>  
>  	my $dbh = PMG::DBTools::open_ruledb();
>  
>  	for my $id (@idlist) {
> -	    my ($cid, $rid, $tid) = $id =~ m/^C(\d+)R(\d+)T(\d+)$/;
> -	    $cid = int($cid);
> -	    $rid = int($rid);
> -	    $tid = int($tid);
> -
> -	    my $ref = PMG::DBTools::load_mail_data($dbh, $cid, $rid, $tid);
> -
> -	    if ($role eq 'quser') {
> -		my $quar_username = $ref->{pmail} . '@quarantine';
> -		raise_perm_exc("mail does not belong to user '$authuser' ($ref->{pmail})")
> -		if $authuser ne $quar_username;
> -	    }
>  
> +	    my $ref = $get_and_check_mail->($id, $rpcenv, $dbh);
>  	    my $sender = $get_real_sender->($ref);
>  
>  	    if ($action eq 'whitelist') {




More information about the pmg-devel mailing list