]>
git.proxmox.com Git - pmg-api.git/blob - src/PMG/MailQueue.pm
1 package PMG
::MailQueue
;
13 use Time
::HiRes
qw(gettimeofday);
19 our $spooldir = "/var/spool/pmg";
21 my $fileseq = rand 1000;
23 sub create_spooldirs
{
24 my ($lcid, $cleanup) = @_;
26 # if requested, remove any stale date
27 File
::Path
::remove_tree
(
32 "$spooldir/attachment",
35 File
::Path
::make_path
(
39 "$spooldir/attachment",
43 mkpath
"$spooldir/cluster/$lcid/virus";
44 mkpath
"$spooldir/cluster/$lcid/spam";
45 mkpath
"$spooldir/cluster/$lcid/attachment";
49 # called on service startup to remove any stale files
52 while (my $file = <$spooldir/active/*>) {
59 my ($dir, $subdir) = @_;
61 # try to create a unique data file
63 my ($sec, $usec) = gettimeofday
();
64 my $fname = "$sec.$usec.$$." . $fileseq++;
65 my $path = "$dir/$subdir/$fname";
70 if (!($fh = IO
::File-
>new ($path, 'w+', 0600))) {
71 die "unable to create file '$path': $! : ERROR";
74 if (my $st = stat ($fh)) {
75 $uid = sprintf ("%X%X%05X", $st->ino, $sec, $usec);
76 if ($subdir ne 'active') {
77 $subsubdir .= sprintf ("%02X/", $usec % 256);
81 die "unable to stat file: $! : ERROR";
84 mkdir "$dir/$subdir/$subsubdir";
86 my $subpath = "$subdir/$subsubdir$uid";
88 if (!rename ($path, "$dir/$subpath")) {
90 die "unable to rename file: ERROR";
93 return ($fh, $uid, $subpath);
97 my ($type, $from, $to) = @_;
102 $self->{from
} = $from;
105 $self->{sa_score
} = undef;
106 $self->{sa_max
} = undef;
107 $self->{sa_data
} = undef;
109 $self->{vinfo
} = undef;
111 $self->{rtime
} = time;
112 $self->{ptime_spam
} = 0;
113 $self->{ptime_virus
} = 0;
115 my ($fh, $uid, $path) = new_fileid
($spooldir, 'active');
119 $self->{logid
} = $uid;
120 #$self->{logid} = sprintf ("%05X${uid}", $self->{rtime});
121 $self->{dataname
} = "$spooldir/$path";
123 $self->{dumpdir
} = "/tmp/.proxdump_${$}_$uid";
125 $self->set_status ($to, 'undelivered');
131 my ($self, $targets, $state, $code, $message) = @_;
133 foreach my $r (@$targets) {
134 $self->{status
}->{$r} = $state;
135 $self->{status_code
}->{$r} = $code;
136 $self->{status_message
}->{$r} = $message;
140 sub quarantinedb_insert
{
141 my ($self, $ruledb, $lcid, $ldap, $qtype, $header, $sender, $file, $targets, $vars) = @_;
144 my $dbh = $ruledb->{dbh
};
146 my $insert_cmds = "SELECT nextval ('cmailstore_id_seq'); INSERT INTO CMailStore " .
147 "(CID, RID, ID, Time, QType, Bytes, Spamlevel, Info, Header, Sender, File) VALUES (" .
148 "$lcid, currval ('cmailstore_id_seq'), currval ('cmailstore_id_seq'), ";
150 my $spaminfo = $vars->{__spaminfo
};
151 my $sa_score = $spaminfo->{sa_score
} || 0;
153 $insert_cmds .= $self->{rtime
} . ',';
154 $insert_cmds .= $dbh->quote ($qtype) . ',';
155 $insert_cmds .= $self->{bytes
} . ',';
156 $insert_cmds .= $sa_score . ',';
159 $insert_cmds .= $dbh->quote ($self->{vinfo
}) . ',';
162 my $sscores = $spaminfo->{sa_data
};
164 if (defined ($sscores) && @$sscores != -1) {
166 foreach my $s (@$sscores) {
167 $sainfo .= ',' if $sainfo;
168 $sainfo .= sprintf ("%s:%s", $s->{rule
}, $s->{score
});
170 $sainfo = $dbh->quote ($sainfo);
173 $insert_cmds .= $sainfo . ',';
176 $insert_cmds .= $dbh->quote ($header) . ',';
178 $insert_cmds .= $dbh->quote ($sender) . ',';
179 $insert_cmds .= $dbh->quote ($file) . ');';
183 my $tid = int(rand(0x0fffffff));
185 foreach my $r (@$targets) {
186 my $pmail = get_primary_mail
($ldap, $r);
188 if ($pmail eq lc ($r)) {
191 $receiver = $dbh->quote ($r);
195 $pmail = $dbh->quote ($pmail);
196 $insert_cmds .= "INSERT INTO CMSReceivers " .
197 "(CMailStore_CID, CMailStore_RID, PMail, Receiver, TicketID, Status, MTime) " .
198 "VALUES ($lcid, currval ('cmailstore_id_seq'), $pmail, $receiver, $tid, 'N', $now); ";
200 # Note: Tuple (CID, RID, TicketID) must be unique
201 $tid = ($tid + 1) & 0x0fffffff;
204 $dbh->do ($insert_cmds);
209 syslog
('err', "ERROR: $err") if $err;
212 sub get_primary_mail
{
213 my ($ldap, $mail) = @_;
217 return $mail if !$ldap;
219 if (my $info = $ldap->account_info ($mail)) {
220 return $info->{pmail
};
227 sub extract_header_text
{
230 my $subject = $entity->head->get ('subject', 0);
231 my $from = $entity->head->get ('from', 0);
232 my $sender = $entity->head->get ('sender', 0);
234 my $head = new Mail
::Header
;
235 $head->add ('subject', $subject) if $subject;
236 $head->add ('from', $from) if $from;
237 $head->add ('sender', $sender) if $sender;
239 my $header = $head->as_string();
244 sub fsync_file_and_dir
{
245 my $filename = shift;
248 my $fh = IO
::File-
>new($filename) || die "unable to open file '$filename'";
249 File
::Sync
::fsync
($fh) || die "fsync file '$filename' failed";
252 my $dirname = dirname
($filename);
253 my $dir = IO
::File-
>new($dirname) || die "open dir '$dirname' failed";
254 File
::Sync
::fsync
($dir) || die "fsync dir '$dirname' failed";
261 syslog
('err', "ERROR: $err");
272 sub quarantine_mail
{
273 my ($self, $ruledb, $qtype, $entity, $tg, $msginfo, $vars, $ldap) = @_;
275 my $sender = $msginfo->{sender
};
277 my $header = extract_header_text
($entity);
279 my $subpath = $subpath_map->{$qtype} // 'spam';
281 my $lcid = $msginfo->{lcid
};
283 my ($fh, $uid, $path);
287 my $subdir = "cluster/$lcid/$subpath";
289 ($fh, $uid, $path) = new_fileid
($spooldir, $subdir);
291 ($fh, $uid, $path) = new_fileid
($spooldir, $subpath);
294 # there must be only one Return-Path
295 $entity->head->delete ('Return-Path');
297 # prepend Delivered-To and Return-Path (like QMAIL MAILDIR FORMAT)
298 $entity->head->add ('Return-Path', join (',', $sender), 0);
299 $entity->head->add ('Delivered-To', join (',', @$tg), 0);
301 $entity->print ($fh);
305 fsync_file_and_dir
("$spooldir/$path"); # make sure the file is on disk
307 $self->quarantinedb_insert ($ruledb, $lcid, $ldap, $qtype, $header, $sender, $path, $tg, $vars);
314 unlink "$spooldir/$path" if $path;
315 syslog
('err', "ERROR: $err");
323 # my ($self, $targets);
325 # $self->set_status ($targets, 'quarantine');
329 # my ($self, $targets);
331 # $self->set_status ($targets, 'spam:report');
335 # my ($self, $targets, $hm);
337 # $self->set_status ($targets, "delay|$hm");
341 my ($self, $msgid) = @_;
343 if (defined ($msgid)) {
344 $self->{msgid
} = $msgid;
355 rmtree
$self->{dumpdir
};
357 unlink $self->{dataname
};
360 sub _new_mime_parser
{
361 my ($self, $maxfiles) = shift;
363 my $parser = PMG
::MIMEUtils
::new_mime_parser
({
366 extract_uuencode
=> 0,
368 maxfiles
=> $maxfiles,
369 dumpdir
=> $self->{dumpdir
},
376 my ($self, $maxfiles) = shift;
381 my $parser = $self->_new_mime_parser ($maxfiles);
383 $self->{fh
}->seek (0, 0);
386 if (!($entity = $parser->read($self->{fh
}))) {
387 die "$self->{logid}: unable to parse message: ERROR";
391 die "$self->{logid}: unable to parse message - $@" if $@;
393 PMG
::MIMEUtils
::fixup_multipart
($entity);
395 if ((my $idcount = $entity->head->count ('Message-Id')) > 0) {
396 $self->msgid ($entity->head->get ('Message-Id', $idcount - 1));
399 # fixme: add parse_time to statistic database
400 my $parse_time = time() - $ctime;
402 # also save decoded data
403 decode_entities
($parser, $self->{logid
}, $entity);
405 # we also remove all proxmox-marks from the mail and add an unique
406 # id to each attachment.
408 PMG
::Utils
::remove_marks
($entity, 1);
409 PMG
::Utils
::add_ct_marks
($entity);
414 sub decode_entities
{
415 my ($parser, $logid, $entity) = @_;
417 PMG
::MIMEUtils
::traverse_mime_parts
($entity, sub {
419 if ($part->bodyhandle && (my $path = $part->bodyhandle->path)) {
422 my $head = $part->head;
423 my $encoding = $head->mime_encoding;
424 my $decoder = new MIME
::Decoder
$encoding;
426 if (!$decoder || ($decoder eq 'none' || $decoder eq 'binary')) {
428 $part->{PMX_decoded_path
} = $path; # no need to decode
432 my $body = $parser->new_body_for ($head);
434 $body->is_encoded(0);
436 my $in = $part->bodyhandle->open ("r") ||
437 die "unable to read raw data '$path'";
439 my $decfh = $body->open ("w") ||
440 die "unable to open body: $!";
442 $decoder->decode ($in, $decfh);
447 die "can't close bodyhandle: $!";
449 $part->{PMX_decoded_path
} = $body->path;
456 syslog
('err', "$logid: $err");