14 use Time
::HiRes qw
(gettimeofday
);
27 my $msg = shift || '';
35 return $dbh->last_insert_id(
36 undef, undef, undef, undef, { sequence
=> $seq});
40 my ($filename, $lasttime) = @_;
42 my $st = stat($filename);
44 return 0 if !defined($st);
46 return ($lasttime >= $st->ctime);
49 sub extract_filename
{
52 if (my $value = $head->recommended_filename()) {
54 if (my $decvalue = MIME
::Words
::decode_mimewords
($value)) {
55 $decvalue =~ s/\0/ /g;
56 $decvalue = PVE
::Tools
::trim
($decvalue);
65 my ($entity, $add_id, $id) = @_;
69 foreach my $tag (grep {/^x-proxmox-tmp/i} $entity->head->tags) {
70 $entity->head->delete ($tag);
73 $entity->head->replace('X-Proxmox-tmp-AID', $id) if $add_id;
75 foreach my $part ($entity->parts) {
76 $id = remove_marks
($part, $add_id, $id + 1);
87 foreach my $k (keys %$dh) {
90 $body =~ s/__\Q${k}\E__/$v/gs;
98 my ($entity, $sender, $targets, $xforward, $me, $nodsn) = @_;
106 my $smtp = Net
::SMTP-
>new('127.0.0.1', Port
=> 10025, Hello
=> $me) ||
107 die "unable to connect to localhost at port 10025";
109 if (defined($xforward)) {
112 foreach my $attr (keys %{$xforward}) {
113 $xfwd .= " $attr=$xforward->{$attr}";
116 if ($xfwd && $smtp->command("XFORWARD", $xfwd)->response() != CMD_OK
) {
117 syslog
('err', "xforward error - got: %s %s", $smtp->code, scalar($smtp->message));
121 if (!$smtp->mail($sender)) {
122 syslog
('err', "smtp error - got: %s %s", $smtp->code, scalar ($smtp->message));
123 die "smtp from: ERROR";
126 my $dsnopts = $nodsn ?
{Notify
=> ['NEVER']} : {};
128 if (!$smtp->to (@$targets, $dsnopts)) {
129 syslog
('err', "smtp error - got: %s %s", $smtp->code, scalar($smtp->message));
130 die "smtp to: ERROR";
134 #$entity->sync_headers ();
137 my $out = PMG
::SMTPPrinter-
>new($smtp);
138 $entity->print($out);
140 # make sure we always have a newline at the end of the mail
141 # else dataend() fails
142 $smtp->datasend("\n");
144 if ($smtp->dataend()) {
145 my @msgs = $smtp->message;
146 $resmess = $msgs[$#msgs];
147 ($resid) = $resmess =~ m/Ok: queued as ([0-9A-Z]+)/;
148 $rescode = $smtp->code;
150 die sprintf("unexpected SMTP result - got: %s %s : WARNING", $smtp->code, $resmess);
153 my @msgs = $smtp->message;
154 $resmess = $msgs[$#msgs];
155 $rescode = $smtp->code;
156 die sprintf("sending data failed - got: %s %s : ERROR", $smtp->code, $resmess);
161 $smtp->quit if $smtp;
164 syslog
('err', $err);
167 return wantarray ?
($resid, $rescode, $resmess) : $resid;
170 sub analyze_virus_clam
{
171 my ($queue, $dname, $pmg_cfg) = @_;
176 my $clamdscan_opts = "--stdout";
178 my ($csec, $usec) = gettimeofday
();
184 $previous_alarm = alarm($timeout);
187 die "$queue->{logid}: Maximum time ($timeout sec) exceeded. " .
188 "virus analyze (clamav) failed: ERROR";
191 open(CMD
, "/usr/bin/clamdscan $clamdscan_opts '$dname'|") ||
192 die "$queue->{logid}: can't exec clamdscan: $! : ERROR";
197 while (defined(my $line = <CMD
>)) {
198 if ($line =~ m/^$dname.*:\s+([^ :]*)\s+FOUND$/) {
199 # we just use the first detected virus name
200 $vinfo = $1 if !$vinfo;
201 } elsif ($line =~ m/^Infected files:\s(\d*)$/i) {
210 alarm(0); # avoid race conditions
212 if (!defined($ifiles)) {
213 die "$queue->{logid}: got undefined output from " .
214 "virus detector: $response : ERROR";
218 syslog
('info', "$queue->{logid}: virus detected: $vinfo (clamav)");
223 alarm($previous_alarm);
225 my ($csec_end, $usec_end) = gettimeofday
();
226 $queue->{ptime_clam
} =
227 int (($csec_end-$csec)*1000 + ($usec_end - $usec)/1000);
230 syslog
('err', $err);
232 $queue->{errors
} = 1;
235 $queue->{vinfo_clam
} = $vinfo;
237 return $vinfo ?
"$vinfo (clamav)" : undef;
241 my ($queue, $filename, $pmg_cfg, $testmode) = @_;
243 # TODO: support other virus scanners?
245 # always scan with clamav
246 return analyze_virus_clam
($queue, $filename, $pmg_cfg);
249 sub magic_mime_type_for_file
{
252 # we do not use get_mime_type_for_file, because that considers
253 # filename extensions - we only want magic type detection
255 my $bufsize = Xdgmime
::xdg_mime_get_max_buffer_extents
();
256 die "got strange value for max_buffer_extents" if $bufsize > 4096*10;
258 my $ct = "application/octet-stream";
260 my $fh = IO
::File-
>new("<$filename") ||
261 die "unable to open file '$filename' - $!";
264 if (($len = $fh->read($buf, $bufsize)) > 0) {
265 $ct = xdg_mime_get_mime_type_for_data
($buf, $len);
269 die "unable to read file '$filename' - $!" if ($len < 0);
277 if (my $path = $entity->{PMX_decoded_path
}) {
279 # set a reasonable default if magic does not give a result
280 $entity->{PMX_magic_ct
} = $entity->head->mime_attr('content-type');
282 if (my $ct = magic_mime_type_for_file
($path)) {
283 if ($ct ne 'application/octet-stream' || !$entity->{PMX_magic_ct
}) {
284 $entity->{PMX_magic_ct
} = $ct;
288 my $filename = $entity->head->recommended_filename;
289 $filename = basename
($path) if !defined($filename) || $filename eq '';
291 if (my $ct = xdg_mime_get_mime_type_from_file_name
($filename)) {
292 $entity->{PMX_glob_ct
} = $ct;
296 foreach my $part ($entity->parts) {
297 add_ct_marks
($part);
301 # x509 certificate utils
303 my $proxmox_tls_cert_fn = "/etc/proxmox/proxmox-tls.pem";
305 sub gen_proxmox_tls_cert
{
306 my ($force, $company, $cn) = @_;
308 return if !$force && -f
$proxmox_tls_cert_fn;
310 my $sslconf = <<__EOD__;
311 RANDFILE = /root/.rnd
316 distinguished_name = req_distinguished_name
317 req_extensions = v3_req
319 string_mask = nombstr
321 [ req_distinguished_name ]
322 organizationalUnitName = Proxmox Mail Gateway
323 organizationName = $company
327 basicConstraints = CA:FALSE
329 keyUsage = nonRepudiation, digitalSignature, keyEncipherment
332 my $cfgfn = "/tmp/proxmoxtlsconf-$$.tmp";
333 my $fh = IO
::File-
>new ($cfgfn, "w");
338 PVE
::Tools
::run_command
(['openssl', 'req', '-batch', '-x509', '-new', '-sha256',
339 '-config', $cfgfn, '-days', 3650, '-nodes',
340 '-out', $proxmox_tls_cert_fn,
341 '-keyout', $proxmox_tls_cert_fn]);
345 unlink $proxmox_tls_cert_fn;
347 die "unable to generate proxmox certificate request:\n$err";
353 sub find_local_network_for_ip
{
356 my $testip = Net
::IP-
>new($ip);
358 my $isv6 = $testip->version == 6;
360 PVE
::ProcFSTools
::read_proc_net_ipv6_route
() :
361 PVE
::ProcFSTools
::read_proc_net_route
();
363 foreach my $entry (@$routes) {
366 $mask = $entry->{prefix
};
367 next if !$mask; # skip the default route...
369 $mask = $PVE::Network
::ipv4_mask_hash_localnet-
>{$entry->{mask
}};
370 next if !defined($mask);
372 my $cidr = "$entry->{dest}/$mask";
373 my $testnet = Net
::IP-
>new($cidr);
374 my $overlap = $testnet->overlaps($testip);
375 if ($overlap == $Net::IP
::IP_B_IN_A_OVERLAP
||
376 $overlap == $Net::IP
::IP_IDENTICAL
)
382 die "unable to detect local network for ip '$ip'\n";