root/tags/FuzzyOcr-3.5.1/FuzzyOcr.pm

Revision 125, 37.5 kB (checked in by decoder, 2 years ago)

FuzzyOcr? 3.5.1 tag dir and tarball

Line 
1 # FuzzyOcr plugin, version 3.4
2 #
3 # written by Christian Holler (decoder_at_own-hero_dot_net)
4 #   and Jorge Valdes (jorge_at_joval_dot_info)
5
6 package FuzzyOcr;
7
8 use strict;
9 use warnings;
10 use Mail::SpamAssassin;
11 use Mail::SpamAssassin::Logger;
12 use Mail::SpamAssassin::Util;
13 use Mail::SpamAssassin::Timeout;
14 use Mail::SpamAssassin::Plugin;
15
16 use Time::HiRes qw( gettimeofday tv_interval );
17 use String::Approx 'adistr';
18 use FileHandle;
19 use Fcntl ':flock';
20 use POSIX;
21
22 use lib qw(/etc/mail/spamassassin); # Allow placing of FuzzyOcr in siteconfigdir
23
24 use FuzzyOcr::Logging qw(debuglog errorlog warnlog infolog);
25 use FuzzyOcr::Config qw(kill_pid
26     get_tmpdir
27     set_tmpdir
28     get_all_tmpdirs
29     get_pms
30     save_pms
31     get_timeout
32     get_mysql_ddb
33     get_scansets
34     get_wordlist
35     set_config
36     get_config
37     parse_config
38     finish_parsing_end
39     read_words);
40 use FuzzyOcr::Hashing qw(check_image_hash_db add_image_hash_db calc_image_hash);
41 use FuzzyOcr::Deanimate qw(deanimate);
42 use FuzzyOcr::Scoring qw(wrong_ctype wrong_extension corrupt_img known_img_hash);
43 use FuzzyOcr::Misc qw(max removedir removedirs save_execute);
44
45 our @ISA = qw(Mail::SpamAssassin::Plugin);
46
47 # constructor: register the eval rule
48 sub new {
49     my ( $class, $mailsa ) = @_;
50     $class = ref($class) || $class;
51     my $self = $class->SUPER::new($mailsa);
52     bless( $self, $class );
53     $self->register_eval_rule("fuzzyocr_check");
54     $self->register_eval_rule("dummy_check");
55     $self->set_config($mailsa->{conf});
56     return $self;
57 }
58
59 sub dummy_check {
60     return 0;
61 }
62
63 sub fuzzyocr_check {
64     my ( $self, $pms ) = @_;
65     my $conf = get_config();
66
67     save_pms($pms);
68
69     my $end;
70     my $begin = [gettimeofday];
71     if ($conf->{focr_global_timeout}) {
72         my $t = get_timeout();
73         debuglog("Global Timeout set at ".$conf->{focr_timeout}." sec.");
74         $t->run(sub {
75             $end = fuzzyocr_do( $self, $conf, $pms );
76         });
77         if ($t->timed_out()) {
78             infolog("Scan timed out after $conf->{focr_timeout} seconds.");
79             infolog("Killing possibly running pid...");
80             my ($ret, $pid) = kill_pid();
81             if ($ret > 0) {
82                     infolog("Successfully killed PID $pid");
83             } elsif ($ret < 0) {
84                 infolog("No processes left... exiting");
85             } else {
86                 infolog("Failed to kill PID $pid, stale process!");
87             }
88             infolog("Removing possibly leftover tempdirs...");
89             removedirs(get_all_tmpdirs());
90             return 0;
91         }
92     } else {
93         $end = fuzzyocr_do( $self, $conf, $pms );
94     }
95     debuglog("Processed in ".
96         sprintf("%.6f",tv_interval($begin, [gettimeofday]))
97         ." sec.");
98     return $end;
99 }
100
101 sub fuzzyocr_do {
102     my ( $self, $conf, $pms ) = @_;
103
104     my $internal_score = 0;
105     my $current_score = $pms->get_score();
106     my $score = $conf->{focr_autodisable_score} || 100;
107
108     if ( $current_score > $score ) {
109         infolog("Scan canceled, message has already more than $score points ($current_score).");
110         return 0;
111     }
112
113     my $nscore = $conf->{focr_autodisable_negative_score} || -100;
114     if ( $current_score < $nscore ) {
115         infolog("Scan canceled, message has less than $nscore points ($current_score).");
116         return 0;
117     }
118
119     my $imgdir;
120     my %imgfiles = ();
121     my @found    = ();
122     my @hashes   = ();
123     my $cnt      = 0;
124     my $imgerr   = 0;
125     my $main     = $self->{main};
126
127     debuglog("Starting FuzzyOcr...");
128    
129     #Show PMS info if asked to
130     if ($conf->{focr_log_pmsinfo}) {
131         my $msgid = $pms->get('Message-Id') ? $pms->get('Message-Id') : "<no messageid>";
132         my $from = $pms->get('From') ? $pms->get('From') : "<no sender>";
133         my $to = $pms->get('To') ? $pms->get('To') : "<no receipients>";
134         chomp($from, $to, $msgid);
135         infolog("Processing Message with ID \"$msgid\" ($from -> $to)");
136     }
137
138     foreach my $p (
139         $pms->{msg}->find_parts(qr(^image\b)i),
140         $pms->{msg}->find_parts(qr(Application/Octet-Stream)i)
141     ) {
142         my $ctype = $p->{'type'};
143         my $fname = $p->{'name'} || 'unknown';
144         if (($fname eq 'unknown') and
145             (defined $p->{'headers'}->{'content-id'})
146             ){
147             $fname = join('',@{$p->{'headers'}->{'content-id'}});
148             $fname =~ s/[<>]//g;
149             $fname =~ tr/\@\$\%\&/_/s;
150         }
151
152         my $filename = $fname; $filename =~ tr{a-zA-Z0-9\-.}{_}cs;
153         debuglog("fname: \"$fname\" => \"$filename\"");
154         my $pdata = $p->decode();
155         my $pdatalen = length($pdata);
156         my $w = 0; my $h = 0;
157
158         if ( substr($pdata,0,3) eq "\x47\x49\x46" ) {
159             ## GIF File
160             $imgfiles{$filename}{ftype} = 1;
161             ($w,$h) = unpack("vv",substr($pdata,6,4));
162             infolog("GIF: [${h}x${w}] $filename ($pdatalen)");
163             $imgfiles{$filename}{width}  = $w;
164             $imgfiles{$filename}{height} = $h;
165         } elsif ( substr($pdata,0,2) eq "\xff\xd8" ) {
166             ## JPEG File
167             my @Markers = (0xC0,0xC1,0xC2,0xC3,0xC5,0xC6,0xC7,0xC9,0xCA,0xCB,0xCD,0xCE,0xCF);
168             my $pos = 2;
169             while ($pos < $pdatalen) {
170                 my ($b,$m) = unpack("CC",substr($pdata,$pos,2)); $pos += 2;
171                 if ($b != 0xff) {
172                    infolog("Invalid JPEG image");
173                    $pos = $pdatalen + 1;
174                    last;
175                 }
176                 my $skip = 0;
177                 foreach my $mm (@Markers) {
178                     if ($mm == $m) {
179                         $skip++; last;
180                     }
181                 }
182                 last if ($skip);
183                 $pos += unpack("n",substr($pdata,$pos,2));
184             }
185             if ($pos > $pdatalen) {
186                 errorlog("Cannot find image dimensions");
187             } else {
188                 ($h,$w) = unpack("nn",substr($pdata,$pos+3,4));
189                 infolog("JPEG: [${h}x${w}] $filename ($pdatalen)");
190                 $imgfiles{$filename}{ftype} = 2;
191                 $imgfiles{$filename}{height} = $h;
192                 $imgfiles{$filename}{width}  = $w;
193             }
194         } elsif ( substr($pdata,0,4) eq "\x89\x50\x4e\x47" ) {
195             # PNG File
196             ($w,$h) = unpack("NN",substr($pdata,16,8));
197             $imgfiles{$filename}{ftype}  = 3;
198             $imgfiles{$filename}{width}  = $w;
199             $imgfiles{$filename}{height} = $h;
200             infolog("PNG: [${h}x${w}] $filename ($pdatalen)");
201         } elsif ( substr($pdata,0,2) eq "BM" ) {
202             ## BMP File
203             ($w,$h) = unpack("VV",substr($pdata,18,8));
204             $imgfiles{$filename}{ftype}  = 4;
205             $imgfiles{$filename}{width}  = $w;
206             $imgfiles{$filename}{height} = $h;
207             infolog("BMP: [${h}x${w}] $filename ($pdatalen)");
208         } elsif (
209             ## TIFF File
210             (substr($pdata,0,4) eq "\x4d\x4d\x00\x2a") or
211             (substr($pdata,0,4) eq "\x49\x49\x2a\x00")
212                 ) {
213             my $worder = (substr($pdata,0,2) eq "\x4d\x4d") ? 0 : 1;
214             my $offset = unpack($worder?"V":"N",substr($pdata,4,4));
215             my $number = unpack($worder?"v":"n",substr($pdata,$offset,2)) - 1;
216             foreach my $n (0 .. $number) {
217                 my $add = 2 + ($n * 12);
218                 my ($id,$tag,$cnt,$val)  = unpack($worder?"vvVV":"nnNN",substr($pdata,$offset+$add,12));
219                 $h = $val if ($id == 256);
220                 $w = $val if ($id == 257);
221                 last if ($h != 0 and $w != 0);
222             }
223             infolog("TIFF: [${h}x${w}] $filename ($pdatalen) ($worder)");
224             infolog("Cannot determine size of TIFF image, setting to '1x1'") if ($h == 0 and $w == 0);
225             $imgfiles{$filename}{ftype}  = 5;
226             $imgfiles{$filename}{width}  = $w ? $w : 1;
227             $imgfiles{$filename}{height} = $h ? $h : 1;
228         }
229
230         #Skip unless we found the right header
231         unless (defined $imgfiles{$filename}{ftype}) {
232             infolog("Skipping file with content-type=\"$ctype\" name=\"$fname\"");
233             delete $imgfiles{$filename};
234             next;
235         }
236
237         #Skip images that cannot contain text
238         if ($imgfiles{$filename}{height} < $conf->{focr_min_height}) {
239             infolog("Skipping image: height < $conf->{focr_min_height}");
240             delete $imgfiles{$filename};
241             next;
242         }
243
244         #Skip images that cannot contain text
245         if ($imgfiles{$filename}{width} < $conf->{focr_min_width}) {
246             infolog("Skipping image: width < $conf->{focr_min_width}");
247             delete $imgfiles{$filename};
248             next;
249         }
250
251         #Skip too big images, screenshots etc
252         if ($imgfiles{$filename}{height} > $conf->{focr_max_height}) {
253             infolog("Skipping image: height > $conf->{focr_max_height}");
254             delete $imgfiles{$filename};
255             next;
256         }
257
258         #Skip too big images, screenshots etc
259         if ($imgfiles{$filename}{width} > $conf->{focr_max_width}) {
260             infolog("Skipping image: width > $conf->{focr_max_width}");
261             delete $imgfiles{$filename};
262             next;
263         }
264
265         #Found Image!! Get a temporary dir to save image
266         $imgdir = Mail::SpamAssassin::Util::secure_tmpdir();
267         unless ($imgdir) {
268             errorlog("Scan canceled, cannot create Image TMPDIR.");
269             return 0;
270         }
271         set_tmpdir($imgdir);
272
273         #Generete unique filename to store image
274         my $imgfilename = Mail::SpamAssassin::Util::untaint_file_path(
275             $imgdir . "/" . $filename
276         );
277         my $unique = 0;
278         while (-e $imgfilename) {
279             $imgfilename = Mail::SpamAssassin::Util::untaint_file_path(
280                 $imgdir . "/" . chr(65+$unique) . "." . $filename
281             );
282             $unique++;
283         }
284
285         #Save important constants
286         $imgfiles{$filename}{fname} = $fname;
287         $imgfiles{$filename}{ctype} = $ctype;
288         $imgfiles{$filename}{fsize} = $pdatalen;
289         $imgfiles{$filename}{fpath} = $imgfilename;
290
291         #Save Image to disk.
292         unless (open PICT, ">$imgfilename") {
293             errorlog("Cannot write \"$imgfilename\", skipping...");
294             delete $imgfiles{$filename};
295             removedir($imgdir);
296             next;
297         }
298         binmode PICT;
299         print PICT $pdata;
300         close PICT;
301         debuglog("Saved: $imgfilename");
302
303         #Increment valid image file counter
304         $cnt++;
305
306         #keep raw email for debugging later
307         my $rawfilename = $imgdir . "/raw.eml";
308         if (open RAW, ">$rawfilename") {
309             print RAW $pms->{msg}->get_pristine();
310             close RAW;
311             debuglog("Saved: $rawfilename");
312         }
313
314     }
315
316     if ($cnt == 0) {
317         debuglog("Skipping OCR, no image files found...");
318         return 0;
319     }
320     infolog("Found: $cnt images"); $cnt = 0;
321     if ($conf->{focr_enable_image_hashing} == 3) {
322         $conf->{focr_mysql_ddb} = get_mysql_ddb();
323     }
324
325     # Try to load personal wordlist
326     unless ($conf->{focr_no_homedirs}) {
327         if ($conf->{focr_personal_wordlist} =~ m/^\//) {
328             read_words( $conf->{focr_personal_wordlist} );
329         } else {
330             my $peruserlist = $main->sed_path($conf->{focr_personal_wordlist});
331             if ( -r $peruserlist ) {
332                 read_words( $peruserlist );
333             } else {
334                 # Only complain if the file exists
335                 if ( -e $peruserlist ) {
336                     errorlog("Cannot read personal_wordlist: $peruserlist, skipping...");
337                 }
338             }
339         }
340     }
341     my $haserr;
342     foreach my $filename (keys %imgfiles) {
343         my $pic = $imgfiles{$filename};
344         #infolog("Analyzing file with content-type=\"$$pic{ctype}\"");
345         my @used_scansets = ();
346         my $corrupt = 0;
347         my $suffix = 0;
348         my $generic_ctype = 0;
349         my $digest;
350         my $file  = $$pic{fpath};
351         my $tfile = $file;
352         my $pfile = $file . ".pnm";
353         my $efile = $file . ".err";
354         debuglog("pfile => $pfile");
355         debuglog("efile => $efile");
356
357         #Open ERRORLOG
358         $haserr = $Mail::SpamAssassin::Logger::LOG_SA{level} == 3;
359
360         if ($haserr) {
361             $haserr = open RAWERR, ">$imgdir/raw.err";
362             debuglog("Errors to: $imgdir/raw.err") if ($haserr>0);
363         }
364
365         my $mimetype = $$pic{ctype};
366         if($mimetype =~ m'application/octet-stream'i) {
367             $generic_ctype = 1;
368         }
369
370         if($$pic{fname} =~ /\.([\w-]+)$/) {
371             $suffix = $1;
372         }
373         if ($suffix) {
374             debuglog("File has Content-Type \"$mimetype\" and File Extension \"$suffix\"");
375         } else {
376             debuglog("File has Content-Type \"$mimetype\" and no File Extension");
377         }
378
379         if ( $$pic{ftype} == 1 ) {
380             infolog("Found GIF header name=\"$$pic{fname}\"");
381             if ($conf->{focr_skip_gif}) {
382                 infolog("Skipping image check");
383                 next;
384             }
385             if (defined($conf->{focr_max_size_gif}) and ($$pic{fsize} > $conf->{focr_max_size_gif})) {
386                 infolog("GIF file size ($$pic{fsize}) exceeds maximum file size for this format, skipping...");
387                 next;
388             }
389
390             if ( ($$pic{ctype} !~ /gif/i) and not $generic_ctype) {
391                 wrong_ctype( "GIF", $$pic{ctype} );
392                 $internal_score += $conf->{'focr_wrongctype_score'};
393             }
394
395             if ( $suffix and $suffix !~ /gif/i) {
396                 wrong_extension( "GIF", $suffix);
397                 $internal_score += $conf->{'focr_wrongext_score'};
398             }
399
400             my $interlaced_gif = 0;
401             my $image_count = 0;
402
403             foreach my $a (qw/gifsicle giftext giffix gifinter giftopnm/) {
404                 unless (defined $conf->{"focr_bin_$a"}) {
405                     errorlog("Cannot exec $a, skipping image");
406                     next;
407                 }
408             }
409
410             my @stderr_data;
411             my ($retcode, @stdout_data) = save_execute(
412                 "$conf->{focr_bin_giftext} $file",
413                 undef,
414                 ">$imgdir/giftext.info",
415                 ">>$imgdir/giftext.err", 1);
416
417             if ($retcode<0) { # only care if we timed out
418                 chomp $retcode;
419                 errorlog("$conf->{focr_bin_giftext} Timed out [$retcode], skipping...");
420                 ++$imgerr if $conf->{focr_keep_bad_images}>0; next;
421             }
422
423             foreach (@stdout_data) {
424                 unless ($interlaced_gif) {
425                     if ( $_ =~ /Image is Interlaced/i ) {
426                         $interlaced_gif = 1;
427                     }
428                 }
429                 if ( $_ =~ /^Image #/ ) {
430                     $image_count++;
431                 }
432             }
433             if ($interlaced_gif or ($image_count > 1)) {
434                 infolog("Image is interlaced or animated...");
435             }
436             else {
437                 infolog("Image is single non-interlaced...");
438                 $tfile .= "-fixed.gif";
439                 printf RAWERR "## $conf->{focr_bin_giffix} $file >$tfile 2>>$efile\n" if ($haserr>0);
440
441                 $retcode = save_execute("$conf->{focr_bin_giffix} $file", undef, ">$tfile", ">>$efile");
442
443                 if ($retcode<0) { # only care if we timed out
444                     chomp $retcode;
445                     errorlog("$conf->{focr_bin_giffix}: Timed out [$retcode], skipping...");
446                     printf RAWERR "?? Timed out > $retcode\n" if ($haserr>0);
447                     ++$imgerr if $conf->{focr_keep_bad_images}>0; next;
448                 }
449
450                 if (open ERR, $efile) {
451                     @stderr_data = <ERR>;
452                     close ERR;
453                     foreach (@stderr_data) {
454                         if ( $_ =~ /GIF-LIB error/i ) {
455                             $corrupt = $_;
456                             last;
457                         }
458                     }
459                 }
460             }
461             my $fixedsize = (stat($tfile))[7];
462             if (defined($conf->{focr_max_size_gif}) and ($fixedsize > $conf->{focr_max_size_gif})) {
463                 infolog("Fixed GIF file size ($fixedsize) exceeds maximum file size for this format, skipping...");
464                 next;
465             }
466
467             if ($corrupt) {
468                 if ($interlaced_gif or ($image_count > 1)) {
469                     infolog("Skipping corrupted interlaced image...");
470                     corrupt_img($conf->{focr_corrupt_unfixable_score}, $corrupt);
471                     $internal_score += $conf->{focr_corrupt_unfixable_score};
472                     next;
473                 }
474                 if (-z $tfile) {
475                     infolog("Uncorrectable corruption detected, skipping non-interlaced image...");
476                     corrupt_img($conf->{focr_corrupt_unfixable_score}, $corrupt);
477                     $internal_score += $conf->{focr_corrupt_unfixable_score};
478                     next;
479                 }
480                 infolog("Image is corrupt, but seems fixable, continuing...");
481                 corrupt_img($conf->{focr_corrupt_score}, $corrupt);
482                 $internal_score += $conf->{focr_corrupt_score};
483             }
484
485             if ($image_count > 1) {
486                 infolog("File contains <$image_count> images, deanimating...");
487                 $tfile = deanimate($tfile);
488             }
489
490             if ($interlaced_gif) {
491                 infolog("Processing interlaced_gif $tfile...");
492                 my $cfile = $tfile;
493                 if ($tfile =~ m/\.gif$/i) {
494                     $tfile =~ s/\.gif$/-fixed.gif/i;
495                 } else {
496                     $tfile .= ".gif";
497                 }
498                 printf RAWERR qq(## $conf->{focr_bin_gifinter} $cfile >$tfile 2>>$efile\n) if ($haserr>0);
499         
500                 $retcode = save_execute("$conf->{focr_bin_gifinter} $cfile", undef, ">$tfile", ">>$efile");
501
502                 if ($retcode<0) {
503                     chomp $retcode;
504                     printf RAWERR "?? Timed out > $retcode\n" if ($haserr>0);
505                     errorlog("$conf->{focr_bin_gifinter}: Timed out [$retcode], skipping...");
506                     ++$imgerr if $conf->{focr_keep_bad_images}>0; next;
507                 } elsif ($retcode>0) {
508                     chomp $retcode;
509                     printf RAWERR "?? [$retcode] returned from $conf->{focr_bin_gifinter}\n" if ($haserr>0);
510                     errorlog("$conf->{focr_bin_gifinter}: Returned [$retcode], skipping...");
511                     ++$imgerr if $conf->{focr_keep_bad_images}>0; next;
512                 }
513             }
514
515             printf RAWERR qq(## $conf->{focr_bin_giftopnm} $tfile >$pfile 2>>$efile\n) if ($haserr>0);
516
517             $retcode = save_execute("$conf->{focr_bin_giftopnm} $tfile", undef, ">$pfile", ">>$efile");
518
519             if ($retcode<0) {
520                 chomp $retcode;
521                 printf RAWERR "?? Timed out > $retcode\n" if ($haserr>0);
522                 errorlog("$conf->{focr_bin_giftopnm}: Timed out [$retcode], skipping...");
523                 ++$imgerr if $conf->{focr_keep_bad_images}>0; next;
524             } elsif ($retcode>0) {
525                 chomp $retcode;
526                 printf RAWERR "?? [$retcode] returned from $conf->{focr_bin_giftopnm}\n" if ($haserr>0);
527                 errorlog("$conf->{focr_bin_giftopnm}: Returned [$retcode], skipping...");
528                 ++$imgerr if $conf->{focr_keep_bad_images}>0; next;
529             }
530         }
531         elsif ( $$pic{ftype} == 2 ) {
532             infolog("Found JPEG header name=\"$$pic{fname}\"");
533             if ($conf->{focr_skip_jpeg}) {
534                 infolog("Skipping image check");
535                 next;
536             }
537
538             if (defined($conf->{focr_max_size_jpeg}) and ($$pic{fsize} > $conf->{focr_max_size_jpeg})) {
539                 infolog("JPEG file size ($$pic{fsize}) exceeds maximum file size for this format, skipping...");
540                 next;
541             }
542             if ( ($$pic{ctype} !~ /(jpeg|jpg)/i) and not $generic_ctype) {
543                 wrong_ctype( "JPEG", $$pic{ctype} );
544                 $internal_score += $conf->{'focr_wrongctype_score'};
545             }
546
547             if ( $suffix and $suffix !~ /(jpeg|jpg|jfif)/i) {
548                 wrong_extension( "JPEG", $suffix);
549                 $internal_score += $conf->{'focr_wrongext_score'};
550             }
551
552             foreach my $a (qw/jpegtopnm/) {
553                 unless (defined $conf->{"focr_bin_$a"}) {
554                     errorlog("Cannot exec $a, skipping image");
555                     next;
556                 }
557             }
558             printf RAWERR qq(## $conf->{focr_bin_jpegtopnm} $file >$pfile 2>>$efile\n) if ($haserr>0);
559             my $retcode = save_execute("$conf->{focr_bin_jpegtopnm} $file", undef, ">$pfile", ">>$efile");
560
561             if ($retcode<0) {
562                 chomp $retcode;
563                 printf RAWERR "?? Timed out > $retcode\n" if ($haserr>0);
564                 errorlog("$conf->{focr_bin_jpegtopnm}: Timed out [$retcode], skipping...");
565                 ++$imgerr if $conf->{focr_keep_bad_images}>0; next;
566             } elsif ($retcode>0) {
567                 chomp $retcode;
568                 printf RAWERR "?? [$retcode] returned from $conf->{focr_bin_jpegtopnm}\n" if ($haserr>0);
569                 errorlog("$conf->{focr_bin_jpegtopnm}: Returned [$retcode], skipping...");
570                 ++$imgerr if $conf->{focr_keep_bad_images}>0; next;
571             }
572         }
573         elsif ( $$pic{ftype} == 3 ) {
574             infolog("Found PNG header name=\"$$pic{fname}\"");
575             if ($conf->{focr_skip_png}) {
576                 infolog("Skipping image check");
577                 next;
578             }
579             if (defined($conf->{focr_max_size_png}) and ($$pic{fsize} > $conf->{focr__max_size_png})) {
580                 infolog("PNG file size ($$pic{fsize}) exceeds maximum file size for this format, skipping...");
581                 next;
582             }
583             if ( ($$pic{ctype} !~ /png/i) and not $generic_ctype) {
584                 wrong_ctype( "PNG", $$pic{ctype} );
585                 $internal_score += $conf->{'focr_wrongctype_score'};
586             }
587             if ( $suffix and $suffix !~ /(png)/i) {
588                 wrong_extension( "PNG", $suffix);
589                 $internal_score += $conf->{'focr_wrongext_score'};
590             }
591             foreach my $a (qw/pngtopnm/) {
592                 unless (defined $conf->{"focr_bin_$a"}) {
593                     errorlog("Cannot exec $a, skipping image");
594                     next;
595                 }
596             }
597
598             printf RAWERR qq(## $conf->{focr_bin_pngtopnm} $file >$pfile 2>>$efile\n) if ($haserr>0);
599             my $retcode = save_execute("$conf->{focr_bin_pngtopnm} $file", undef, ">$pfile", ">>$efile");
600
601             if ($retcode<0) {
602                 chomp $retcode;
603                 printf RAWERR "?? Timed out > $retcode\n" if ($haserr>0);
604                 errorlog("$conf->{focr_bin_pngtopnm}: Timed out [$retcode], skipping...");
605                 ++$imgerr if $conf->{focr_keep_bad_images}>0; next;
606             } elsif ($retcode>0) {
607                 chomp $retcode;
608                 printf RAWERR "?? [$retcode] returned from $conf->{focr_bin_pngtopnm}\n" if ($haserr>0);
609                 errorlog("$conf->{focr_bin_pngtopnm}: Returned [$retcode], skipping...");
610                 ++$imgerr if $conf->{focr_keep_bad_images}>0; next;
611             }
612         }
613         elsif ( $$pic{ftype} == 4 ) {
614             infolog("Found BMP header name=\"$$pic{fname}\"");
615             if ($conf->{focr_skip_bmp}) {
616                 infolog("Skipping image check");
617                 next;
618             }
619             if (defined($conf->{focr_max_size_bmp}) and ($$pic{fsize} > $conf->{focr_max_size_bmp})) {
620                 infolog("BMP file size ($$pic{fsize}) exceeds maximum file size for this format, skipping...");
621                 next;
622             }
623             if ( ($$pic{ctype} !~ /bmp/i) and not $generic_ctype) {
624                 wrong_ctype( "BMP", $$pic{ctype} );
625                 $internal_score += $conf->{'focr_wrongctype_score'};
626             }
627             if ( $suffix and $suffix !~ /(bmp)/i) {
628                 wrong_extension( "BMP", $suffix);
629                 $internal_score += $conf->{'focr_wrongext_score'};
630             }
631             foreach my $a (qw/bmptopnm/) {
632                 unless (defined $conf->{"focr_bin_$a"}) {
633                     errorlog("Cannot exec $a, skipping image");
634                     next;
635                 }
636             }
637             printf RAWERR qq(## $conf->{focr_bin_bmptopnm} $file >$pfile 2>>$efile\n) if ($haserr>0);
638
639             my $retcode = save_execute("$conf->{focr_bin_bmptopnm} $file", undef, ">$pfile", ">>$efile");
640             if ($retcode<0) {
641                 chomp $retcode;
642                 printf RAWERR "?? Timed out > $retcode\n" if ($haserr>0);
643                 errorlog("$conf->{focr_bin_bmptopnm}: Timed out [$retcode], skipping...");
644                 ++$imgerr if $conf->{focr_keep_bad_images}>0; next;
645             } elsif ($retcode>0) {
646                 chomp $retcode;
647                 printf RAWERR "?? [$retcode] returned from $conf->{focr_bin_bmptopnm}\n" if ($haserr>0);
648                 errorlog("$conf->{focr_bin_bmptopnm}: Returned [$retcode], skipping...");
649                 ++$imgerr if $conf->{focr_keep_bad_images}>0; next;
650             }
651         }
652         elsif ( $$pic{ftype} == 5 ) {
653             infolog("Found TIFF header name=\"$$pic{fname}\"");
654             if ($conf->{focr_skip_tiff}) {
655                 infolog("Skipping image check");
656                 next;
657             }
658             if (defined($conf->{focr_max_size_tiff}) and ($$pic{fsize} > $conf->{focr_max_size_tiff})) {
659                 infolog("TIFF file size ($$pic{fsize}) exceeds maximum file size for this format, skipping...");
660                 next;
661             }
662             if ( ($$pic{ctype} !~ /tif/i) and not $generic_ctype) {
663                 wrong_ctype( "TIFF", $$pic{ctype} );
664                 $internal_score += $conf->{'focr_wrongctype_score'};
665             }
666             if ( $suffix and $suffix !~ /tif/i) {
667                 wrong_extension( "TIFF", $suffix);
668                 $internal_score += $conf->{'focr_wrongext_score'};
669             }
670
671             foreach my $a (qw/tifftopnm/) {
672                 unless (defined $conf->{"focr_bin_$a"}) {
673                     errorlog("Cannot exec $a, skipping image");
674                     next;
675                 }
676             }
677             printf RAWERR qq(## $conf->{focr_bin_tifftopnm} $file >$pfile 2>>$efile\n) if ($haserr>0);
678             my $retcode = save_execute("$conf->{focr_bin_tifftopnm} $file", undef, ">$pfile", ">>$efile");
679
680             if ($retcode<0) {
681                 chomp $retcode;
682                 printf RAWERR "?? Timed out > $retcode\n" if ($haserr>0);
683                 errorlog("$conf->{focr_bin_tifftopnm}: Timed out [$retcode], skipping...");
684                 ++$imgerr if $conf->{focr_keep_bad_images}>0; next;
685             } elsif ($retcode>0) {
686                 chomp $retcode;
687                 printf RAWERR "?? [$retcode] returned from $conf->{focr_bin_tifftopnm}\n" if ($haserr>0);
688                 errorlog("$conf->{focr_bin_tifftopnm}: Returned [$retcode], skipping...");
689                 ++$imgerr if $conf->{focr_keep_bad_images}>0; next;
690             }
691         }
692         else {
693             errorlog("Image type not recognized, unknown format. Skipping this image...");
694             next;
695         }
696
697         if($conf->{focr_enable_image_hashing}) {
698             infolog("Calculating image hash for: $pfile");
699             ($corrupt, $digest) = calc_image_hash($pfile,$pic);
700             if ($corrupt) {
701                 infolog("Error calculating the image hash, skipping hash check...");
702             } else {
703                 my ($score, $dinfo, $whash);
704                 $whash = <