]>
git.proxmox.com Git - pmg-api.git/blob - src/PMG/API2/MailTracker.pm
1 package PMG
::API2
::MailTracker
;
14 use PVE
::JSONSchema
qw(get_standard_option);
16 use PMG
::RESTEnvironment
;
18 use base
qw(PVE::RESTHandler);
31 my $run_pmg_log_tracker = sub {
32 my ($args, $includelog) = @_;
36 my $timezone = tz_local_offset
();;
38 if (defined(my $id = $includelog)) {
39 if ($id =~ m/^Q([a-f0-9]+)R([a-f0-9]+)$/i) {
42 push @$args, '-q', $1, '-q', $2;
45 push @$args, '-q', $id;
59 # assume syslog is UTF-8 encoded
60 $line = decode
('UTF-8', $line);
62 if ($state eq 'start') {
64 return if $line =~ m/^\#/;
65 return if $line =~ m/^\s*$/;
67 if ($line =~ m/^STATUS: (.*)$/) {
73 if ($line =~ m/^SMTPD:\s+(T[0-9A-F]+L[0-9A-F]+)$/) {
75 $entry = { id
=> $1 };
79 if ($line =~ m/^QENTRY:\s+([0-9A-F]+)$/) {
81 $entry = { qid
=> $1 };
85 die "got unexpected data: $line";
86 } elsif ($state eq 'end') {
87 die "got unexpected data after status: $line";
88 } elsif ($state eq 'skiplogs') {
89 if ($line =~ m/^\s*$/) {
95 } elsif ($state eq 'logs') {
96 if ($line =~ m/^\s*$/) {
99 } elsif ($line =~ m/^(SMTP|FILTER|QMGR):/) {
101 } elsif ($line =~ m/^(L[A-F0-9]+)\s(.*)$/) {
102 push @$logs, { linenr
=> $1, text
=> $2 };
104 die "got unexpected data: $line";
106 } elsif ($state eq 'qentry') {
107 if ($line =~ m/^\s*$/) {
110 } elsif ($line =~ m/^SIZE:\s+(\d+)$/) {
112 } elsif ($line =~ m/^CLIENT:\s+(\S+)$/) {
113 $entry->{client
} = $1;
114 } elsif ($line =~ m/^MSGID:\s+(\S+)$/) {
115 $entry->{msgid
} = $1;
116 } elsif ($line =~ m/^CTIME:\s+([0-9A-F]+)$/) {
118 } elsif ($line =~ m/^TO:([0-9A-F]+):([0-9A-F]+):([0-9A-Z]):\s+from <([^>]*)>\s+to\s+<([^>]+)>\s+\((\S+)\)$/) {
120 $new->{size
} = $entry->{size
} // 0,
121 $new->{client
} = $entry->{client
} if defined($entry->{client
});
122 $new->{msgid
} = $entry->{msgid
} if defined($entry->{msgid
});
123 $new->{time} = hex($1) - $timezone;
125 $new->{dstatus
} = $3;
131 $lookup_hash->{$2}->{$5} = $new;
132 } elsif ($line =~ m/^(SMTP|FILTER|QMGR):/) {
133 if ($logids->{$entry->{qid
}}) {
139 die "got unexpected data: $line";
141 } elsif ($state eq 'smtp') {
143 if ($line =~ m/^\s*$/) {
146 } elsif ($line =~ m/^CLIENT:\s+(\S+)$/) {
147 $entry->{client
} = $1;
148 } elsif ($line =~ m/^CTIME:\s+([0-9A-F]+)$/) {
150 } elsif ($line =~ m/^TO:([0-9A-F]+):(T[0-9A-F]+L[0-9A-F]+):([0-9A-Z]):\s+from <([^>]*)>\s+to\s+<([^>]+)>$/) {
152 $e->{client
} = $entry->{client
} if defined($entry->{client
});
153 $e->{time} = hex($1) - $timezone;
159 } elsif ($line =~ m/^LOGS:$/) {
160 if ($logids->{$entry->{id
}}) {
166 die "got unexpected data: $line";
169 die "unknown state '$state'\n";
173 my $cmd = ['/usr/bin/pmg-log-tracker', '-v', '-l', 2000];
175 PVE
::Tools
::run_command
([@$cmd, @$args], timeout
=> 25, outfunc
=> $parser);
177 my $sorted_logs = [];
178 foreach my $le (sort {$a->{linenr
} cmp $b->{linenr
}} @$logs) {
179 push @$sorted_logs, $le->{text
};
182 foreach my $e (@$list) {
183 if (my $id = $e->{qid
}) {
184 if (my $relay = $e->{relay
}) {
185 if (my $ref = $lookup_hash->{$relay}->{$e->{to
}}) {
186 $ref->{is_relay
} = 1;
187 $id = 'Q' . $e->{qid
} . 'R' . $e->{relay
};
188 if ($e->{dstatus
} eq 'A') {
189 $e->{rstatus
} = $ref->{dstatus
};
195 if ($includelog && ($e->{id
} eq $includelog)) {
196 $e->{logs
} = $sorted_logs;
200 return wantarray ?
($list, $status) : $list;
203 my $email_log_property_desc = {
205 description
=> "Unique ID.",
209 description
=> "Sender email address.",
213 description
=> "Receiver email address.",
217 description
=> "Postfix qmgr ID.",
222 description
=> "Delivery timestamp.",
226 description
=> "Delivery status.",
232 description
=> "Delivery status of relayed mail.",
239 description
=> "ID of relayed mail.",
244 description
=> "The size of the raw email.",
249 description
=> "Client address",
254 description
=> "SMTP message ID.",
260 __PACKAGE__-
>register_method({
261 name
=> 'list_mails',
264 description
=> "Read mail list.",
267 permissions
=> { check
=> [ 'admin', 'audit' ] },
269 additionalProperties
=> 0,
271 node
=> get_standard_option
('pve-node'),
272 starttime
=> get_standard_option
('pmg-starttime'),
273 endtime
=> get_standard_option
('pmg-endtime'),
275 description
=> "Only include mails containing this filter string.",
282 description
=> "Sender email address filter.",
289 description
=> "Receiver email address filter.",
296 description
=> "Include NDRs (non delivery reports).",
302 description
=> "Include Greylisted entries.",
313 properties
=> $email_log_property_desc,
315 links
=> [ { rel
=> 'child', href
=> "{id}" } ],
320 my $restenv = PMG
::RESTEnvironment-
>get();
324 my $start = $param->{starttime
} // (time - 86400);
325 my $end = $param->{endtime
} // ($start + 86400);
327 push @$args, '-s', $start;
328 push @$args, '-e', $end;
330 push @$args, '-n' if !$param->{ndr
};
332 push @$args, '-g' if !$param->{greylist
};
334 push @$args, '-x', $param->{xfilter
} if defined($param->{xfilter
});
336 if (defined($param->{from
})) {
337 push @$args, '-f', $param->{from
};
339 if (defined($param->{target
})) {
340 push @$args, '-t', $param->{target
};
343 my ($list, $status) = $run_pmg_log_tracker->($args);
346 foreach my $e (@$list) {
347 push @$res, $e if !$e->{is_relay
};
350 # hack: return status message in 'changes' attribute
351 $restenv->set_result_attrib('changes', $status) if defined($status);
356 __PACKAGE__-
>register_method({
360 description
=> "Get the detailed syslog entries for a specific mail ID.",
363 permissions
=> { check
=> [ 'admin', 'audit' ] },
365 additionalProperties
=> 0,
367 node
=> get_standard_option
('pve-node'),
368 starttime
=> get_standard_option
('pmg-starttime'),
369 endtime
=> get_standard_option
('pmg-endtime'),
371 description
=> "Mail ID (as returend by the list API).",
381 %$email_log_property_desc,
384 items
=> { type
=> "string" },
391 my $restenv = PMG
::RESTEnvironment-
>get();
395 my $start = $param->{starttime
} // (time - 86400);
396 my $end = $param->{endtime
} // ($start + 86400);
398 push @$args, '-s', $start;
399 push @$args, '-e', $end;
401 my $list = $run_pmg_log_tracker->($args, $param->{id
});
404 foreach my $e (@$list) {
405 $res = $e if $e->{id
} eq $param->{id
};
408 die "entry '$param->{id}' not found\n" if !defined($res);