]> git.madduck.net Git - etc/mailfilter.git/commitdiff

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

replace ytnef with killtnef
authormartin f. krafft <madduck@madduck.net>
Wed, 26 May 2010 17:58:41 +0000 (19:58 +0200)
committermartin f. krafft <madduck@madduck.net>
Wed, 26 May 2010 17:58:41 +0000 (19:58 +0200)
bin/killtnef [new file with mode: 0755]
procmail/tnef-filter

diff --git a/bin/killtnef b/bin/killtnef
new file mode 100755 (executable)
index 0000000..8d8fcc8
--- /dev/null
@@ -0,0 +1,286 @@
+#!/usr/bin/perl
+
+############
+# killtnef #
+############
+
+use strict;
+use Convert::TNEF;
+use MIME::Parser;
+use MIME::Types;
+
+my $VERSION = 1.0.3;
+my $mimetypes = MIME::Types->new;
+
+my $message;
+while (defined(my $line = <STDIN>)) {
+  my $msg_bound;
+  if ($line =~ /^(From\s+.+)\r?\n/ and length($message) or eof) {
+    parse_message(\$message, $msg_bound); # All the action happens here...
+    $message = "";
+    $msg_bound=$line;
+  } elsif ($line =~ /^(From\s+.+)\r?\n/) {
+    $msg_bound=$line;  # The first 
+  } 
+  $message.=$line;
+}
+exit;
+
+
+# Subroutines ###############################################################
+
+sub parse_message {
+  my $msg_body = shift @_;
+  my $msg_bound = shift @_;
+  my $mime_parser = new MIME::Parser;
+
+  # This module likes to use tmp files, but I try to stop it here.
+  $mime_parser->use_inner_files(1);
+  $mime_parser->output_to_core(1);
+  my $ent = $mime_parser->parse_data($$msg_body);
+  my $num_parts=$ent->parts;
+
+  # Determine if we have a MIME w/ms-tnef and act accordingly.
+  if ( ($num_parts < 1) || ($$msg_body !~ /ms-tnef/i) ) {
+    print "$$msg_body";
+  } else {
+    # Get the head info
+    my $head = $ent->head;
+    my $ReturnPath = $head->get('Return-Path');
+    my @all_received = $head->get('Received');
+    my $Date = $head->get('Date');
+    my $From = $head->get('From');
+    my $XSender = $head->get('X-Sender');
+    my $To = $head->get('To');
+    my $Subject = $head->get('Subject');
+    my $MessageID = $head->get('Message-ID');
+    my $boundary = $head->multipart_boundary;
+
+    # Build a new MIME message based on the one we are examining
+    # - LHH: it would probably be better to build this $new_ent
+    #        using $ent->head as the basis, thus getting *all* of
+    #        the headers, instead of just these few.  We only needed
+    #        these few headers for the project this script was
+    #        originally written for, but if someone wants to change
+    #        this and submit a patch, that would be great.
+    my $new_ent = MIME::Entity->build(
+                               'Type'        => "multipart/mixed",
+                               'Boundary'    => $boundary,
+                               'X-Mailer'    => undef
+                                     );
+    my $new_head=$new_ent->head;
+    # Try to preserve the order of headers in the original message by
+    # extracting it from the original formatted header.
+    my(%did_tag);
+    foreach my $tag (@{$head->header}, $head->tags) {
+       $tag =~ s/:.*//s;
+       next if ($did_tag{lc $tag}++);
+       next if ($new_head->count($tag));
+       foreach my $value ($head->get_all($tag)) {
+           $new_head->add($tag, $value);
+       }
+    }
+
+    # Loop over each MIME part adding each to the new message
+    foreach my $mime_part_i (0 .. ($num_parts - 1)) {
+      my $ent_part=$ent->parts($mime_part_i);
+      if ($ent_part->mime_type =~ /ms-tnef/i )  {
+        add_tnef_parts($ent_part, $new_ent);
+      } else {
+        $new_ent->add_part($ent_part);
+      } 
+    }
+
+    # Set the preamble and epilogue equal to the original
+    $new_ent->preamble($ent->preamble);
+    $new_ent->epilogue($ent->epilogue);
+
+    # Print the newly constructed MIME message
+    print "$msg_bound"; 
+    print STDOUT $new_ent->stringify;
+  }
+}
+
+sub add_tnef_parts {
+  my $ent = shift;
+  my $new = shift;
+
+  ## Create a tnef object
+  my %TnefOpts=('output_to_core' => '4194304', 'output_dir' => '/tmp');
+  my $tnef = Convert::TNEF->read_ent($ent, \%TnefOpts);
+  my $head=$new->head; # Get the header object from the new message
+  if (! $tnef) {
+    warn "TNEF CONVERT DID NOT WORK: " . $Convert::TNEF::errstr . "\n";
+    warn "  - Failed on msg w/subj: " . $head->get('Subject') . "\n";
+    return '';
+  }
+
+  #############################################################################
+  # This section of code smokes lots of crack, and tries to dig the From:
+  # header out of the $tnef->message if the new message we are appending
+  # this attachment to does not already have a "From" header.  This is
+  # required on most of the Outlook emails that never touch SMTP, only
+  # Exchange servers, and never had valid SMTP From headers placed!
+  #############################################################################
+  my $msg=$tnef->message;
+  my $mapi_props=$msg->data('MAPIProps');
+  #warn join(", ", keys %{$msg->{MAPIProps}}) . "\n";
+  #warn $msg->{MAPIProps}->{MBS_Data} . "\n\n----------------------------\n\n";
+  #warn "$mapi_props\n\n---------------------------------\n\n";
+  my $test=0x0024;
+  #if ($mapi_props =~ m/(\0\0\0\xf8.{20})/) { warn "MATCHED a prop $1\n"; }
+  #if (0) {
+  if (! length($head->get('From')) ) {
+    my $from='';
+    my $cntrl_chars='[\c' . join('\c', ('A' .. 'Z')) . ']';
+    if ($mapi_props =~ m/From:\s+([^\s\@]+\@[^\s]+)/) {
+      $from=$1;
+    } elsif ($mapi_props =~ m/\xf8\?\cA\0\0\0$cntrl_chars\0\0\0([^\0]+)\0+\cB\cA/) {
+      $from=$1;
+    } else {
+      if ($mapi_props =~ m/(\xf8\?\cA.{30})/) { warn "MATCH: $1\n"; }
+      #$from="Unknown Sender";
+    }
+    if( length($from)) { $head->replace('from', $from); }
+  }
+  #############################################################################
+
+  for ($tnef->attachments) {
+    $_->longname=~/^[\w\W]+\.(\w+)$/;
+    my $ext = $1;
+    my $type = $mimetypes->mimeTypeOf($ext);
+    if (! $type) {
+      warn "No MIME type for (" . $_->longname . "/" . $_->name . ")\n";
+      $type = "Application/OCTET-STREAM";
+    }
+    my $encoding;
+    if ($type) {
+       if ($type =~ m,^text/,) {
+           if ($_->data =~ /[^\001-\177]/) {
+               $encoding = '8bit';
+           }
+           else {
+               $encoding = '7bit';
+           }
+       }
+       else {
+           $encoding = 'base64';
+       }
+    }
+    elsif ($_->data =~ /[^\t\n\r\f\040-\177]/) {
+       $encoding = 'base64';
+    }
+    else {
+       $encoding = '7bit';
+    }
+
+    $new->attach( 
+                   Type => $type,
+                   Encoding => $encoding,
+                   Data => $_->data, 
+                   Disposition => 'attachment',
+                   Filename => $_->longname
+                 );
+  }
+  # If you want to delete the working files
+  $tnef->purge;
+}
+
+
+# POD documentation
+
+=head1 SYNOPSIS
+
+cat mbox_msg_w_tnef | killtnef > mbox_msg_mime_compliant
+
+=head1 README
+
+killtnef - Converts emails with MS-TNEF, Microsoft's proprietary
+Transport Neutral Encapsulated Format, attachments into standard
+MIME-compliant emails.
+
+This script reads an mbox, or a single email message, from STDIN,
+extracts data from any ms-tnef attachments that may exist, and writes
+a new mbox (or a single email message) to STDOUT which has each of the
+files that were encoded in any ms-tnef attachments attached separately,
+as RFC-822/RFC-1521 compliant MIME emails/attachments.
+
+Any email(s) containing no ms-tnef MIME attachments are passed through
+this script untouched.
+
+=head1 DESCRIPTION
+
+This script was originally written to convert about 35,000 emails from
+some Microsoft Outlook *.pst (post office) files, almost all of which
+had ms-tnef encoded attachments, into MIME-compliant emails so that
+they could be imported into an email-archiving system that 10East
+supplies to some of its customers.  If anyone is curious, an imapd
+was used to move the emails from the *.pst files to mbox format using
+Outlook 2000 as an IMAP client.
+
+This script can also be used as an incoming mail filter which will
+automatically convert ms-tnef attachments into MIME-compliant
+attachments.
+
+=head1 AUTHORSHIP
+
+Andrew Orr <aorr@10east.com> (no longer a maintainer)
+
+Lester Hightower <hightowe@10east.com> (maintainer)
+
+=head1 LICENSE
+
+This software is licensed under the terms of the GNU Public License,
+which is available for review at http://www.gnu.org/copyleft/gpl.html
+
+=head1 CHANGE LOG
+
+Feb-22-2002: Originally created by Andy Orr
+
+Feb-26-2002: A few enhancements and bug-fixes by Lester Hightower.
+
+Mar-06-2002: Documentation and a few comments added by Lester Hightower
+in preparation for submitting this script to CPAN.
+
+Mar-07-2002: After realizing that a POD README section is needed for the
+HTML pages generated for the script index in CPAN, LHH added one and
+submitted this as killtnef-1.0.1.pl.
+
+Sep-20-2005: Applied a patch provided by Jonathan Kamens and released that
+as  killtnef-1.0.2.pl.  The patch did:
+  * Use /usr/bin/perl instead of /usr/local/bin/perl.
+  * Use MIME::Types instead of hard-coded list of
+    extensions and MIME types.
+  * Preserve MIME boundary and headers from original
+    message.
+  * Try to use 7bit or 8bit encoding instead of base64
+    whenever possible.  This makes resulting messages
+    smaller and easier to full-text index.
+
+May-30-2007: Applied a patch provided by Stefan Bertels and released that
+as  killtnef-1.0.3.pl.  The patch uses any "\w" characters after the last
+dot in each attachments' longname as the file extenstion, instead of just
+"([A-Za-z]{2,4})" and also defaults the type to "Application/OCTET-STREAM"
+if no mimetype can be found for the file extenstion.
+
+=head1 PREREQUISITES
+
+This script requires the C<strict>, C<Convert::TNEF 0.16>,
+C<MIME::Parser 5.406>, and C<MIME::Types 1.15> modules.
+
+=head1 COREQUISITES
+
+None.
+
+=pod OSNAMES
+
+Any Unix-like.
+
+=pod SCRIPT CATEGORIES
+
+Mail
+Mail/Converters
+Mail/Filters
+
+=cut
+
index b1d52dadd2a65e5aae038681ab92815a2514d990..790a25c5687e80437ac213d783c85f106da21190 100644 (file)
@@ -1,4 +1,9 @@
-:0 fw
-* ^X-MS-TNEF-Correlator:
-* B ?? winmail.dat
-|ytnef-filter
+:0
+* ^X-MS-TNEF-Correlator: <
+* B ?? ^Content-Type: application/ms-tnef
+{
+  :0 fw
+  |$MAILFILT/bin/killtnef | $FORMAIL -IX-MS-Has-Attach: -IX-MS-Tnef-Correlator: \
+    -IX-MimeOLE: -IContent-Class: -I'X-Tnef: killed'
+  LOG="tnef-filter: converted TNEF attachment$NL"
+}