diff -r 9755876da778 -r 5bdd42401211 mimecut.pl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mimecut.pl Mon Sep 03 12:22:41 2007 +0000 @@ -0,0 +1,436 @@ +#!/usr/bin/perl +#$Id$ + +use strict; +use warnings; +use MIME::Parser; +use MIME::Entity; +use MIME::Head; +use Getopt::Long; +use File::Basename; + +my $ME = basename $0; +my $MEPID = $$; +my $CONFDIR = '.'; +my $LOGDIR = '.'; +my $HELP = < \$opt_debug, + "l|log" => \$opt_log, + "t|text" => \$opt_text, + "s|strain" => \$opt_strain, + "f|fake" => \$opt_fake, + "m|mimes" => \$opt_mimes, + "v|vips" => \$opt_vips, + "h|help" => \$opt_help, + "p|pod" => \$opt_pod, + ) or die "$ME: try\n $ME --help\n"; + + if ($opt_help) { + print $HELP and exit 0; + } + if ($opt_pod) { + system("pod2usage -v 3 $0") and exit 0; + } + + $out_std = *STDOUT; + open($out_null, '>', '/dev/null') + or die "$ME can't trash output!\n$!"; + $out_err = $out_null; + + if ($opt_fake) { $opt_debug = 1 } + if ($opt_log) { + die "$ME: can't fake in log mode!\n" + if ($opt_fake); + die "$ME: can't debug in log mode!\n" + if ($opt_debug); + open($logfile, ">> $LOGDIR/$ME.log") + or die "$ME: can't open logfile!\n"; + $opt_debug = 1; + $out_err = $logfile; + } + elsif ($opt_debug) { $out_err = *STDERR } + + if ($opt_strain) { + if ($opt_fake) { + print $out_err "$ME: can't fake in strain mode!\n"; + exit 0; + } + if ($opt_text) { + print $out_err "$ME: can't use text-only in strain mode!\n"; + exit 0; + } + } + + $mimes = read_conf($conf_mimes) unless $opt_text; + $vips = read_conf($conf_vips); + + if ($opt_mimes) { + $mimes =~ s/\|/\n/g; + print "$mimes\n"; + exit 0 unless $opt_vips; + } + if ($opt_vips) { + $vips =~ s/\|/\n/g; + print "$vips\n"; + exit 0; + } + + die "$ME: no mail on stdin!\n$!" if (-z *STDIN); + + ### + + my $mail = new_parser(); + my %data = get_mail_data($mail); + + print $out_err "\n$ME\[$MEPID\]: " . scalar localtime() . "\n"; + print $out_err "<< " . $data{from} . "\n>> " . $data{to} . "\n"; + + if ($opt_strain) { + print $out_err " STRAIN MODE\n"; + new_mail_send($mail); + exit 0; + } + + if (my $result = check_vip(%data)) { + print $out_err " $result\n"; + new_mail_send($mail); + exit 0; + } + + if ($data{mtype} =~ /multipart/) { + + my $hl1 = '-' x 32; + my $hl2 = '-' x 8; + my $hl3 = '-' x 36; + my $hl = '-' x 78; + + print $out_err ",$hl1.$hl2.$hl3.\n"; + printf $out_err "| %-30s | %-6s | %-34s |\n", "part [subparts]", + "status", "filename"; + print $out_err "+$hl1+$hl2+$hl3+\n"; + + my $mail_new = new_mail(%data); + $mail_new = check_multipart($mail, $mail_new); + + print $out_err "`$hl1'$hl2'$hl3'\n"; + new_mail_send($mail_new); + exit 0; + + } + else { + + print $out_err "** SINGLEPART\n"; + new_mail_send($mail); + exit 0; + } +} + +### + +sub read_conf($) { + my $conf = shift @_; + + die "$ME: can't find $conf!\n" if (!-e $conf); + + my $fh; + open($fh, "< $conf") or die "$ME: can't read $conf!\n"; + my $re = join('|', my @re = grep (!/(?:^\s{0,}#|^\s{0,}$)/, <$fh>)); + $re =~ s/(?:\n|\s)//g; + return $re; +} + +sub new_parser() { + my $parser = new MIME::Parser; + $parser->output_to_core(1); + return $parser->parse(\*STDIN); +} + +sub get_mail_data($) { + my $mail = shift @_; + + my $mt = $mail->mime_type; + my $pr = $mail->preamble || ''; + my $ep = $mail->epilogue || ''; + my $da = $mail->head->get('Date') || ''; + my $su = $mail->head->get('Subject') || ''; + my $fr = $mail->head->get('From') || ''; + my $to = $mail->head->get('To') || ''; + my $cc = $mail->head->get('CC') || ''; + my $bc = $mail->head->get('BCC') || ''; + + chomp($mt, $pr, $ep, $da, $su, $fr, $to, $cc, $bc); + + my %data = (mtype => $mt, + preamble => $pr, + epilogue => $ep, + date => $da, + subject => $su, + from => $fr, + to => $to, + cc => $cc, + bcc => $bc, + ); + + return (%data); +} + +sub new_mail(%) { + my %data = @_; + my $mail_new = MIME::Entity->build(Type => $data{mtype}, + Date => $data{date}, + From => $data{from}, + To => $data{to}, + CC => $data{cc}, + BCC => $data{bcc}, + Subject => $data{subject}, + ); + return $mail_new; +} + +sub new_mail_send($) { + my $mail = shift @_; + if (!$opt_fake) { + print $out_std "From $ME " . scalar localtime() . "\n"; + $mail->print; + } +} + +sub check_multipart($$) { + my ($multipart, $mail_new) = @_; + my $parts_count = $multipart->parts; + + printf $out_err "| %-30s | %-6s | %-34s |\n", + "" . $prefix . $multipart->mime_type . " [$parts_count]", '', ''; + $prefix = $prefix . " "; + + my @parts = $multipart->parts; + foreach my $part (@parts) { + my $mtype = $part->mime_type; + check_part($part, $mtype, $mail_new) unless $mtype =~ /^multipart/; + $mail_new = check_multipart($part, $mail_new) + if $mtype =~ /^multipart/; + } + return $mail_new; +} + +sub check_vip(%) { + if ($vips) { + my %data = @_; + return "VIP FROM" if $data{from} =~ /<(?:$vips)>/i; + return "VIP RCPT" if $data{to} =~ /<(?:$vips)>/i; + return "VIP CC" if $data{cc} =~ /<(?:$vips)>/i; + return "VIP BCC" if $data{bcc} =~ /<(?:$vips)>/i; + } +} + +sub check_part($$$) { + my ($part, $mtype, $mail_new) = @_; + my $status = 'cut'; + + my $disposition = ($part->get('Content-Disposition') =~ /filename="(.+)"/) + and my $filename = $1 + if $part->get('Content-Disposition'); + $filename = 'n/a' unless $filename; + + if ($opt_text) { + if ($mtype =~ m[text/plain]) { $status = 'kept' } + else { $status = 'cut' } + } + elsif ($mimes) { + if ($mtype =~ m[(?:$mimes)]) { $status = 'kept' } + } + + if ($status eq 'kept') { + $mail_new->add_part($part); + } + elsif ($status eq 'cut') { replace_part($part, $filename, $mail_new) } + + printf $out_err "| %-30s | %-6s | %34.34s |\n", "$prefix$mtype", + "$status", "$filename"; +} + +sub replace_part($$$) { + + # TODO part ersetzen ohne boundary zu verlieren + # kurze info zu mime/filetype oder so +} + +=pod + +=head1 NAME + +mimecut -- entfernt oder ersetzt AnhEnge anhand MIME's + +=head1 SYNOPSIS + +=over + +=item B [OPTION] < Mail + +=item B [ACTION] + +=back + +=head1 DESCRIPTION + +Eine e-Mail, die Eber STDIN an mimecut Ebergeben wird, wird auf +Ihre Struktur untersucht. Ist sie keine multipart e-Mail erfolgt die Ausgabe +sofort und ungeEndert Eber STDOUT. + +Ist die e-Mail multipart, werden die einzelnen parts, gegebenenfalls rekursiv +durchlaufen und auf ihre mime-types hin analysiert. +mimecut benEtigt zwei Konfigurationsdateien: + +=over + +=item B + +Hier sind die mime-types erfasst, welche in der e-Mail enthalten bleiben sollen. + +=item B + +Die Liste der EmpfEnger/Sender/CC's/BCC's, welche ungekErzte e-Mails bekommen. + +=back + +Anhand der Konfiguration wird zunEchst der e-Mail header +EberprEft und falls keine VIP e-Mail vorliegt, werden die parts +mit den zu entfernenden mimes abgetrennt. + +=head1 OPTIONS + +=over + +=item B<-d, --debug> + +Gibt einen Statusbericht auf STDERR aus. + +=item B<-l, --logfile> + +Schreibt das debug in oder hEngt es an die Datei mimecut.log im Pfad von mimecut an. + +=item B<-t, --text> + +Entfernt alle AnhEnge mit einem anderen mimetype als plain/text. + +=item B<-f, --fake> + +Testet was passieren wErde, ohne jedoch eine e-Mail Eber STDOUT auszugeben. +(aktiviert automatisch B<-d>) + +=item B<-s, --strain> + +Gibt die e-Mail, ganz gleich VIP oder nicht, sofort und unverEndert Eber +STDOUT aus + +=back + +=head1 ACTIONS + +=over + +=item B<-m, --mimes> + +Ausgabe der aus der mimes.conf eingelesenen MIME's. + +=item B<-v, --vips> + +Ausgabe der aus der vips.conf eingelesenen VIP-Adressen. + +=back + +=head1 OTHER + +=over + +=item B<-h, --help> + +Kurze Hilfe mit SYNOPSIS, OPTIONS & ACTIONS. + +=item B<-p, --pod> + +Diese Dokumentation. + +=back + +=head1 FILES + +=over + +=item B, B, B + +=back + +=cut + +# vim:ft=perl:tw=78:ts=4