mimecut.pl
changeset 6 b58ecd25d1f1
parent 5 c43594b89549
equal deleted inserted replaced
5:c43594b89549 6:b58ecd25d1f1
     1 #!/usr/bin/perl
     1 #! /usr/bin/perl
     2 #$Id$
     2 # $Id$
     3 
     3 
     4 use strict;
     4 use strict;
     5 use warnings;
     5 use warnings;
     6 use MIME::Parser;
     6 use MIME::Parser;
     7 use MIME::Entity;
     7 use MIME::Entity;
     8 use MIME::Head;
     8 use MIME::Head;
     9 use Getopt::Long;
     9 use Getopt::Long;
    10 use File::Basename;
    10 use File::Basename;
    11 use FindBin qw($Bin);
    11 use FindBin qw($Bin);
       
    12 use if $ENV{DEBUG} => "Smart::Comments", $ENV{DEBUG};
       
    13 
       
    14 BEGIN {
       
    15     delete @ENV{ "LANG", grep /^LC_/, keys %ENV };
       
    16 }
    12 
    17 
    13 my $ME      = basename $0;
    18 my $ME      = basename $0;
    14 my $MEPID   = $$;
    19 my $MEPID   = $$;
    15 my $CONFDIR = -f "$Bin/.build" ? $Bin : "/etc/$0";
    20 my $CONFDIR = -f "$Bin/.build" ? $Bin : "/etc/$0";
    16 my $LOGDIR  = '.';
    21 my $LOGDIR  = '.';
    62 my $opt_help;
    67 my $opt_help;
    63 my $opt_pod;
    68 my $opt_pod;
    64 
    69 
    65 my $conf_mimes = "$CONFDIR/mimes.conf";
    70 my $conf_mimes = "$CONFDIR/mimes.conf";
    66 my $conf_vips  = "$CONFDIR/vips.conf";
    71 my $conf_vips  = "$CONFDIR/vips.conf";
    67 my $vips;
    72 my @vips;
    68 my $mimes;
    73 my @mimes;
    69 
    74 
    70 my $out_err;
       
    71 my $out_std;
       
    72 my $logfile;
    75 my $logfile;
    73 my $prefix = '';
       
    74 
    76 
    75 sub read_conf($);
    77 sub read_conf($);
    76 sub new_parser();
    78 sub new_parser();
    77 sub get_mail_data($);
    79 sub get_mail_header($);
    78 sub new_mail(%);
    80 sub new_mail(%);
    79 sub new_mail_send($);
    81 sub new_mail_send($);
    80 sub check_vip(%);
    82 sub check_vip($%);
    81 sub check_multipart($$);
    83 sub check_multipart($$$$);
    82 sub check_part($$$);
    84 sub check_part($$$$$);
    83 sub replace_part($$$);
    85 sub replace_part($$$);
    84 
    86 
    85 MAIN: {
    87 MAIN: {
    86     Getopt::Long::Configure("bundling");
    88     Getopt::Long::Configure("bundling");
    87     GetOptions("d|debug"  => \$opt_debug,
    89     GetOptions("d|debug"  => \$opt_debug,
   100     }
   102     }
   101     if ($opt_pod) {
   103     if ($opt_pod) {
   102         system("pod2usage -v 3 $0") and exit 0;
   104         system("pod2usage -v 3 $0") and exit 0;
   103     }
   105     }
   104 
   106 
   105     $out_std = *STDOUT;
       
   106 
       
   107     if ($opt_fake) { $opt_debug = 1 }
   107     if ($opt_fake) { $opt_debug = 1 }
   108     if ($opt_log) {
   108     if ($opt_log) {
   109         die "$ME: can't fake in log mode!\n"  if $opt_fake;
   109         die "$ME: can't fake in log mode!\n"  if $opt_fake;
   110         die "$ME: can't debug in log mode!\n" if $opt_debug;
   110         die "$ME: can't debug in log mode!\n" if $opt_debug;
   111         open(STDERR, ">> $LOGDIR/$ME.log")
   111         open(STDERR, ">> $LOGDIR/$ME.log")
   112             or die "$ME: can't open logfile!\n";
   112             or die "$ME: can't open logfile!\n";
   113         $opt_debug = 1;
   113         $opt_debug = 1;
   114     }
   114     }
   115 	elsif (!$opt_debug) { open(STDERR, ">/dev/null") }
   115     elsif (!$opt_debug) { open(STDERR, ">/dev/null") }
   116 
   116 
   117     if ($opt_strain) {
   117     if ($opt_strain) {
   118         if ($opt_fake) {
   118         if ($opt_fake) {
   119             print $out_err "$ME: can't fake in strain mode!\n";
   119             warn "$ME: can't fake in strain mode!\n";
   120             exit 0;
   120             exit 0;
   121         }
   121         }
   122         if ($opt_text) {
   122         if ($opt_text) {
   123             print $out_err "$ME: can't use text-only in strain mode!\n";
   123             warn "$ME: can't use text-only in strain mode!\n";
   124             exit 0;
   124             exit 0;
   125         }
   125         }
   126     }
   126     }
   127 
   127 
   128     $mimes = read_conf($conf_mimes) unless $opt_text;
   128     @mimes = read_conf($conf_mimes) unless $opt_text;
   129     $vips = read_conf($conf_vips);
   129     @vips = read_conf($conf_vips);
   130 
   130 
   131     if ($opt_mimes) {
   131     if ($opt_mimes) {
   132         $mimes =~ s/\|/\n/g;
   132         local $" = ", ";
   133         print "$mimes\n";
   133         print "mimes: @mimes\n";
   134         exit 0 unless $opt_vips;
   134         exit 0;
   135     }
   135     }
   136     if ($opt_vips) {
   136     if ($opt_vips) {
   137         $vips =~ s/\|/\n/g;
   137         local $" = ", ";
   138         print "$vips\n";
   138         print "vips: @vips\n";
   139         exit 0;
   139         exit 0;
   140     }
   140     }
   141 
   141 
   142     die "$ME: no mail on stdin!\n$!" if (-z *STDIN);
   142     die "$ME: no mail on stdin!\n$!" if (-z *STDIN);
   143 
   143 
   144     ###
   144     ###
   145 
   145 
   146     my $mail = new_parser();
   146     my $parser = new MIME::Parser;
   147     my %data = get_mail_data($mail);
   147     $parser->output_to_core(1);    # FIXME: was ist bei sehr großen Mails?
   148 
   148 
   149     print $out_err "\n$ME\[$MEPID\]: " . scalar localtime() . "\n";
   149     my $mail   = $parser->parse(\*STDIN);
   150     print $out_err "<< " . $data{from} . "\n>> " . $data{to} . "\n";
   150     my %header = get_mail_header($mail);
       
   151 
       
   152     warn "\n$ME\[$MEPID\]: @{[scalar localtime]}\n"
       
   153         . "<<$header{from}\n"
       
   154         . ">>$header{to}\n";
   151 
   155 
   152     if ($opt_strain) {
   156     if ($opt_strain) {
   153         print $out_err "   STRAIN MODE\n";
   157         warn "   STRAIN MODE\n";
   154         new_mail_send($mail);
   158         new_mail_send($mail);
   155         exit 0;
   159         exit 0;
   156     }
   160     }
   157 
   161 
   158     if (my $result = check_vip(%data)) {
   162     {
   159         print $out_err "   $result\n";
   163         #### checking vips: \@vips
   160         new_mail_send($mail);
   164         my $result;
   161         exit 0;
   165         if (@vips and $result = check_vip(\@vips, %header)) {
   162     }
   166             warn "   $result\n";
   163 
   167             new_mail_send($mail);
   164     if ($data{mtype} =~ /multipart/) {
   168             exit 0;
       
   169         }
       
   170     }
       
   171 
       
   172     if ($header{mtype} =~ /multipart/) {
   165 
   173 
   166         my $hl1 = '-' x 32;
   174         my $hl1 = '-' x 32;
   167         my $hl2 = '-' x 8;
   175         my $hl2 = '-' x 8;
   168         my $hl3 = '-' x 36;
   176         my $hl3 = '-' x 36;
   169         my $hl  = '-' x 78;
   177         my $hl  = '-' x 78;
   170 
   178 
   171         print $out_err ",$hl1.$hl2.$hl3.\n";
   179         warn ",$hl1.$hl2.$hl3.\n";
   172         printf $out_err "| %-30s | %-6s | %-34s |\n", "part [subparts]",
   180         warn sprintf "| %-30s | %-6s | %-34s |\n", "part [subparts]",
   173             "status", "filename";
   181             "status", "filename";
   174         print $out_err "+$hl1+$hl2+$hl3+\n";
   182         warn "+$hl1+$hl2+$hl3+\n";
   175 
   183 
   176         my $mail_new = new_mail(%data);
   184         my $mail_new = new_mail(%header);
   177         $mail_new = check_multipart($mail, $mail_new);
   185         $mail_new = check_multipart(\@mimes, $mail, $mail_new, 0);
   178 
   186 
   179         print $out_err "`$hl1'$hl2'$hl3'\n";
   187         warn "`$hl1'$hl2'$hl3'\n";
   180         new_mail_send($mail_new);
   188         new_mail_send($mail_new);
   181         exit 0;
   189         exit 0;
   182 
   190 
   183     }
   191     }
   184     else {
   192     else {
   185 
   193 
   186         print $out_err "** SINGLEPART\n";
   194         warn "** SINGLEPART\n";
   187         new_mail_send($mail);
   195         new_mail_send($mail);
   188         exit 0;
   196         exit 0;
   189     }
   197     }
   190 }
   198 }
   191 
   199 
   196 
   204 
   197     die "$ME: can't find $conf!\n" if (!-e $conf);
   205     die "$ME: can't find $conf!\n" if (!-e $conf);
   198 
   206 
   199     my $fh;
   207     my $fh;
   200     open($fh, "< $conf") or die "$ME: can't read $conf!\n";
   208     open($fh, "< $conf") or die "$ME: can't read $conf!\n";
   201     my $re = join('|', my @re = grep (!/(?:^\s{0,}#|^\s{0,}$)/, <$fh>));
   209 
   202     $re =~ s/(?:\n|\s)//g;
   210     return map { chomp; $_ } grep !/(?:^\s*#|^\s*$)/, <$fh>;
   203     return $re;
   211 }
   204 }
   212 
   205 
   213 sub get_mail_header($) {
   206 sub new_parser() {
   214     my $mail = shift;
   207     my $parser = new MIME::Parser;
   215 
   208     $parser->output_to_core(1);
   216     my %data;
   209     return $parser->parse(\*STDIN);
   217 
   210 }
   218     $data{mtype}    = $mail->mime_type;
   211 
   219     $data{preamble} = $mail->preamble || '';
   212 sub get_mail_data($) {
   220     $data{epilogue} = $mail->epilogue || '';
   213     my $mail = shift @_;
   221     $data{date}     = $mail->head->get('Date') || '';
   214 
   222     $data{subject}  = $mail->head->get('Subject') || '';
   215     my $mt = $mail->mime_type;
   223     $data{from}     = $mail->head->get('From') || '';
   216     my $pr = $mail->preamble || '';
   224     $data{to}       = $mail->head->get('To') || '';
   217     my $ep = $mail->epilogue || '';
   225     $data{cc}       = $mail->head->get('CC') || '';
   218     my $da = $mail->head->get('Date') || '';
   226     $data{bcc}      = $mail->head->get('BCC') || '';
   219     my $su = $mail->head->get('Subject') || '';
   227 
   220     my $fr = $mail->head->get('From') || '';
   228     map { chomp } values %data;
   221     my $to = $mail->head->get('To') || '';
       
   222     my $cc = $mail->head->get('CC') || '';
       
   223     my $bc = $mail->head->get('BCC') || '';
       
   224 
       
   225     chomp($mt, $pr, $ep, $da, $su, $fr, $to, $cc, $bc);
       
   226 
       
   227     my %data = (mtype    => $mt,
       
   228                 preamble => $pr,
       
   229                 epilogue => $ep,
       
   230                 date     => $da,
       
   231                 subject  => $su,
       
   232                 from     => $fr,
       
   233                 to       => $to,
       
   234                 cc       => $cc,
       
   235                 bcc      => $bc,
       
   236     );
       
   237 
   229 
   238     return (%data);
   230     return (%data);
   239 }
   231 }
   240 
   232 
   241 sub new_mail(%) {
   233 sub new_mail(%) {
   242     my %data = @_;
   234     my %data = @_;
   243     my $mail_new = MIME::Entity->build(Type    => $data{mtype},
   235     return
   244                                        Date    => $data{date},
   236         MIME::Entity->build(Type    => $data{mtype},
   245                                        From    => $data{from},
   237                             Date    => $data{date},
   246                                        To      => $data{to},
   238                             From    => $data{from},
   247                                        CC      => $data{cc},
   239                             To      => $data{to},
   248                                        BCC     => $data{bcc},
   240                             CC      => $data{cc},
   249                                        Subject => $data{subject},
   241                             BCC     => $data{bcc},
   250     );
   242                             Subject => $data{subject},
   251     return $mail_new;
   243         );
   252 }
   244 }
   253 
   245 
   254 sub new_mail_send($) {
   246 sub new_mail_send($) {
   255     my $mail = shift @_;
   247     my $mail = shift @_;
   256     if (!$opt_fake) {
   248     if (!$opt_fake) {
   257         print $out_std "From $ME " . scalar localtime() . "\n";
   249         print "From $ME " . scalar localtime() . "\n";
   258         $mail->print;
   250         $mail->print;
   259     }
   251     }
   260 }
   252 }
   261 
   253 
   262 sub check_multipart($$) {
   254 sub check_multipart($$$$) {
   263     my ($multipart, $mail_new) = @_;
   255     my ($mimes, $old, $new, $level) = @_;
   264     my $parts_count = $multipart->parts;
   256     my $parts_count = $old->parts;
   265 
   257 
   266     printf $out_err "| %-30s | %-6s | %-34s |\n",
   258     warn sprintf "| %-30s | %-6s | %-34s |\n",
   267         "" . $prefix . $multipart->mime_type . " [$parts_count]", '', '';
   259         " " x $level . $old->mime_type . " [$parts_count]", "", "";
   268     $prefix = $prefix . " ";
   260 
   269 
   261     my @parts = $old->parts;
   270     my @parts = $multipart->parts;
       
   271     foreach my $part (@parts) {
   262     foreach my $part (@parts) {
   272         my $mtype = $part->mime_type;
   263         my $mtype = $part->mime_type;
   273         check_part($part, $mtype, $mail_new) unless $mtype =~ /^multipart/;
   264 
   274         $mail_new = check_multipart($part, $mail_new)
   265 		if ($mtype =~ /multipart/) {
   275             if $mtype =~ /^multipart/;
   266 				$new = check_multipart($mimes, $part, $new, $level + 1);
   276     }
   267 		}
   277     return $mail_new;
   268 		else {
   278 }
   269 				check_part($mimes, $part, $mtype, $new, $level);
   279 
   270 		}
   280 sub check_vip(%) {
   271     }
   281     if ($vips) {
   272 
   282         my %data = @_;
   273     return $new;
   283         return "VIP FROM" if $data{from} =~ /<(?:$vips)>/i;
   274 }
   284         return "VIP RCPT" if $data{to}   =~ /<(?:$vips)>/i;
   275 
   285         return "VIP CC"   if $data{cc}   =~ /<(?:$vips)>/i;
   276 sub check_vip($%) {
   286         return "VIP BCC"  if $data{bcc}  =~ /<(?:$vips)>/i;
   277     my ($vips, %data) = @_;
   287     }
   278     my $re = join "|", map { $a = $_; $a =~ s/([^\da-z])/\\$1/gi; $a } @$vips;
   288 }
   279     my @matched;
   289 
   280 
   290 sub check_part($$$) {
   281     foreach (qw(from to cc bcc)) {
   291     my ($part, $mtype, $mail_new) = @_;
   282         push @matched, "VIP $_" if $data{$_} =~ /$re/o;
       
   283     }
       
   284 
       
   285     return @matched if wantarray;
       
   286     return join ", ", @matched;
       
   287 }
       
   288 
       
   289 sub check_part($$$$$) {
       
   290     my ($mimes, $part, $mtype, $mail_new, $level) = @_;
   292     my $status = 'cut';
   291     my $status = 'cut';
       
   292 
       
   293     my $re = join "|", map { $a = $_; $a =~ s/([^\da-z])/\\$1/gi; $a } @$mimes;
   293 
   294 
   294     my $disposition = ($part->get('Content-Disposition') =~ /filename="(.+)"/)
   295     my $disposition = ($part->get('Content-Disposition') =~ /filename="(.+)"/)
   295         and my $filename = $1
   296         and my $filename = $1
   296         if $part->get('Content-Disposition');
   297         if $part->get('Content-Disposition');
       
   298 
   297     $filename = 'n/a' unless $filename;
   299     $filename = 'n/a' unless $filename;
   298 
   300 
   299     if ($opt_text) {
   301     if ($opt_text) {
   300         if   ($mtype =~ m[text/plain]) { $status = 'kept' }
   302         if   ($mtype =~ m[text/plain]) { $status = 'kept' }
   301         else                           { $status = 'cut' }
   303         else                           { $status = 'cut' }
   302     }
   304     }
   303     elsif ($mimes) {
   305     elsif ($re) {
   304         if ($mtype =~ m[(?:$mimes)]) { $status = 'kept' }
   306         if ($mtype =~ /$re/) { $status = 'kept' }
   305     }
   307     }
   306 
   308 
   307     if ($status eq 'kept') {
   309     if ($status eq 'kept') {
   308         $mail_new->add_part($part);
   310         $mail_new->add_part($part);
   309     }
   311     }
   310     elsif ($status eq 'cut') { replace_part($part, $filename, $mail_new) }
   312     elsif ($status eq 'cut') { replace_part($part, $filename, $mail_new) }
   311 
   313 
   312     printf $out_err "| %-30s | %-6s | %34.34s |\n", "$prefix$mtype",
   314     warn sprintf "| %-30s | %-6s | %34.34s |\n", 
   313         "$status", "$filename";
   315 		" " x ($level+1) . $mtype, 
       
   316 		$status,
       
   317         $filename;
   314 }
   318 }
   315 
   319 
   316 sub replace_part($$$) {
   320 sub replace_part($$$) {
   317 
   321 
   318     # TODO	part ersetzen ohne boundary zu verlieren
   322     # TODO	part ersetzen ohne boundary zu verlieren