]> git.proxmox.com Git - pmg-api.git/blob - PMG/Utils.pm
new helper analyze_virus()
[pmg-api.git] / PMG / Utils.pm
1 package PMG::Utils;
2
3 use strict;
4 use warnings;
5 use Carp;
6 use DBI;
7 use Net::Cmd;
8 use Net::SMTP;
9 use File::stat;
10 use MIME::Words;
11 use MIME::Parser;
12 use Time::HiRes qw (gettimeofday);
13
14 use PVE::SafeSyslog;
15 use PMG::MailQueue;
16
17 sub msgquote {
18 my $msg = shift || '';
19 $msg =~ s/%/%%/g;
20 return $msg;
21 }
22
23 sub lastid {
24 my ($dbh, $seq) = @_;
25
26 return $dbh->last_insert_id(
27 undef, undef, undef, undef, { sequence => $seq});
28 }
29
30 sub file_older_than {
31 my ($filename, $lasttime) = @_;
32
33 my $st = stat($filename);
34
35 return 0 if !defined($st);
36
37 return ($lasttime >= $st->ctime);
38 }
39
40 sub extract_filename {
41 my ($head) = @_;
42
43 if (my $value = $head->recommended_filename()) {
44 chomp $value;
45 if (my $decvalue = MIME::Words::decode_mimewords($value)) {
46 $decvalue =~ s/\0/ /g;
47 $decvalue = trim ($decvalue);
48 return $decvalue;
49 }
50 }
51
52 return undef;
53 }
54
55 sub remove_marks {
56 my ($entity, $add_id, $id) = @_;
57
58 $id //= 1;
59
60 foreach my $tag (grep {/^x-proxmox-tmp/i} $entity->head->tags) {
61 $entity->head->delete ($tag);
62 }
63
64 $entity->head->replace('X-Proxmox-tmp-AID', $id) if $add_id;
65
66 foreach my $part ($entity->parts) {
67 $id = remove_marks($part, $add_id, $id + 1);
68 }
69
70 return $id;
71 }
72
73 sub subst_values {
74 my ($body, $dh) = @_;
75
76 return if !$body;
77
78 foreach my $k (keys %$dh) {
79 my $v = $dh->{$k};
80 if (defined ($v)) {
81 $body =~ s/__${k}__/$v/gs;
82 }
83 }
84
85 return $body;
86 }
87
88 sub reinject_mail {
89 my ($entity, $sender, $targets, $xforward, $me, $nodsn) = @_;
90
91 my $smtp;
92 my $resid;
93 my $rescode;
94 my $resmess;
95
96 eval {
97 my $smtp = Net::SMTP->new('127.0.0.1', Port => 10025, Hello => $me) ||
98 die "unable to connect to localhost at port 10025";
99
100 if (defined($xforward)) {
101 my $xfwd;
102
103 foreach my $attr (keys %{$xforward}) {
104 $xfwd .= " $attr=$xforward->{$attr}";
105 }
106
107 if ($xfwd && $smtp->command("XFORWARD", $xfwd)->response() != CMD_OK) {
108 syslog('err', "xforward error - got: %s %s", $smtp->code, scalar($smtp->message));
109 }
110 }
111
112 if (!$smtp->mail($sender)) {
113 syslog('err', "smtp error - got: %s %s", $smtp->code, scalar ($smtp->message));
114 die "smtp from: ERROR";
115 }
116
117 my $dsnopts = $nodsn ? {Notify => ['NEVER']} : {};
118
119 if (!$smtp->to (@$targets, $dsnopts)) {
120 syslog ('err', "smtp error - got: %s %s", $smtp->code, scalar($smtp->message));
121 die "smtp to: ERROR";
122 }
123
124 # Output the head:
125 #$entity->sync_headers ();
126 $smtp->data();
127
128 my $out = PMG::SMTPPrinter->new($smtp);
129 $entity->print($out);
130
131 # make sure we always have a newline at the end of the mail
132 # else dataend() fails
133 $smtp->datasend("\n");
134
135 if ($smtp->dataend()) {
136 my @msgs = $smtp->message;
137 $resmess = $msgs[$#msgs];
138 ($resid) = $resmess =~ m/Ok: queued as ([0-9A-Z]+)/;
139 $rescode = $smtp->code;
140 if (!$resid) {
141 die sprintf("unexpected SMTP result - got: %s %s : WARNING", $smtp->code, $resmess);
142 }
143 } else {
144 my @msgs = $smtp->message;
145 $resmess = $msgs[$#msgs];
146 $rescode = $smtp->code;
147 die sprintf("sending data failed - got: %s %s : ERROR", $smtp->code, $resmess);
148 }
149 };
150 my $err = $@;
151
152 $smtp->quit if $smtp;
153
154 if ($err) {
155 syslog ('err', $err);
156 }
157
158 return wantarray ? ($resid, $rescode, $resmess) : $resid;
159 }
160
161 sub analyze_virus_clam {
162 my ($queue, $dname, $pmg_cfg) = @_;
163
164 my $timeout = 60*5;
165 my $vinfo;
166
167 my $clamdscan_opts = "--stdout";
168
169 my ($csec, $usec) = gettimeofday();
170
171 my $previous_alarm;
172
173 eval {
174
175 $previous_alarm = alarm($timeout);
176
177 $SIG{ALRM} = sub {
178 die "$queue->{logid}: Maximum time ($timeout sec) exceeded. " .
179 "virus analyze (clamav) failed: ERROR";
180 };
181
182 open(CMD, "/usr/bin/clamdscan $clamdscan_opts '$dname'|") ||
183 die "$queue->{logid}: can't exec clamdscan: $! : ERROR";
184
185 my $ifiles;
186 my $res;
187
188 while (<CMD>) {
189 if (m/^$dname.*:\s+([^ :]*)\s+FOUND$/) {
190 # we just use the first detected virus name
191 $vinfo = $1 if !$vinfo;
192 }
193 if (m/^Infected files:\s(\d*)$/i) {
194 $ifiles = $1;
195 }
196
197 $res .= $_;
198 }
199
200 close(CMD);
201
202 alarm(0); # avoid race conditions
203
204 if (!defined($ifiles)) {
205 die "$queue->{logid}: got undefined output from " .
206 "virus detector: $res : ERROR";
207 }
208
209 if ($vinfo) {
210 syslog ('info', "$queue->{logid}: virus detected: $vinfo (clamav)");
211 }
212 };
213 my $err = $@;
214
215 alarm($previous_alarm);
216
217 my ($csec_end, $usec_end) = gettimeofday();
218 $queue->{ptime_clam} =
219 int (($csec_end-$csec)*1000 + ($usec_end - $usec)/1000);
220
221 if ($err) {
222 syslog ('err', $err);
223 $vinfo = undef;
224 $queue->{errors} = 1;
225 }
226
227 $queue->{vinfo_clam} = $vinfo;
228
229 return $vinfo ? "$vinfo (clamav)" : undef;
230 }
231
232 sub analyze_virus {
233 my ($queue, $filename, $pmg_cfg, $testmode) = @_;
234
235 # TODO: support other virus scanners?
236
237 # always scan with clamav
238 return analyze_virus_clam($queue, $filename, $pmg_cfg);
239 }
240
241
242 1;