action_replace_with_url
and patches by Jeremy Mates
(http://lists.roaringpenguin.com/pipermail/mimedefang/2003-June/014908.html).
You'll need a server capable of serving files, usually a Webserver,
but a FTP server will do as well. MIMEDefang and the server must share a
directory, into which MIMEDefang will write and the server will read from.
I will proceed assuming you use a Web-Server :-).
The security implications are such:
MIMEDefang will remove the attachment from the mail and save it
into a file named «sha1_from_content».«original_extension».
That means:
a) that the original filename except the extension is lost (but can be preserved, see below), and
b) that it is unlikely that someones can guess the filename
unless he already got hold of the content. That's why there is no strong
need to protect the shared file except for:
drwxr-x--- 2 mdefang www-data 8192 Jun 16 12:16 /var/spool/MIMEDefang_Captured_FilesThe directory is owned by the mimedefang user, which can read and write it, the webserver can read its content, all others are denied.
# MIME Defang captured files Alias /captured_mailfiles/ /var/spool/MIMEDefang_Captured_Files/ <Directory "/var/spool/MIMEDefang_Captured_Files"> AddDefaultCharset Off DefaultType application/octet-stream Options None AllowOverride None Order allow,deny Allow from all </Directory>Note: It is strongly advisable to not allow directory indexing, otherwise you might leak information, because anyone can easily read any file your server recieved and you've ordered MIMEDefang to capture.
Surf to http://url_server/captured_mailfiles/, you should see a denial error.
Then create a test file there, e.g. named test, and try to access it, it must work.
Attachments are available in the function filter only. Hence,
you'll have to wind up some logic to decide which attachements
are entitled for distributing via Web rather than email. My filter
contains this code: Note: $entity
is the
first argument of the sub filter, $lcType
is the
lc()
of the forth argument (the supposed content type),
$friendlyName
is a humanized and securified version of the
file name (the second argument), all other variables are computed by
the filter.
# We are a leaf, replace non-text/* MIME Attachments if($oneRcptIsLargeList || $oneRcptIsList && !$isAuthed) { if($lcType eq 'message/delivery-status') { md_graphdefang_log('dsn_to_list', $lcType, ''); return action_bounce("Protect list against wrongly routed DSNs - rejected"); } if($lcType =~ /^text\// || $lcType eq 'application/x-pkcs7-signature') { skahead "Keeping MIME attachment '$friendlyName' of $lcType"; } else { my $size = (stat($entity->bodyhandle->path))[7]; if($size >= 10*1024) { # larger than 10KB if($size > 1024) { $size = int($size / 1024); if($size > 1024) { $size = int($size / 1024); $size .= 'MB'; } else { $size .= 'KB'; } } else { $size .= "B"; } skahead "Replace $lcType '$friendlyName' ($size) with URL"; return action_replace_with_url($entity, "/var/spool/MIMEDefang_Captured_Files", "http://url_server/captured_mailfiles", "Der Anhang '$friendlyName' wurde aufgrund seiner Groesse aus dieser Nachricht entfernt, kann jedoch ueber folgende URL abgerufen werden:\n The attachment '$friendlyName' was removed from this mail, but may be accessed at this URL (approx. $size):\n" . "\t_URL_\n" , "$lcType $friendlyName\n", $skaCfg{urlsalt}); } else { skahead "Keeping MIME attachment '$friendlyName' of $lcType size: $size"; } } }What's all this about?
if($oneRcptIsLargeList || $oneRcptIsList && !$isAuthed) {
if($lcType eq 'message/delivery-status')
if($lcType =~ /^text\// || $lcType eq 'application/x-pkcs7-signature')
return action_replace_with_url(
My function skahead logs the message to syslog and, if running in verbose mode, adds a header line. These are my debug-in-production fallbacks.
To generate the $friendlyName I use the function:
sub skaFriendlyName ($) { my $fname = $_[0]; $fname =~ s/[^a-zA-z0-9_.-]+/_/g; $fname = "unnamed_item" if length($fname) < 1 || $fname eq '_'; return $fname; }on the 2nd argument of sub filter.
Check out the mail, the spool directory and if you can access the link.
Note: Because of the 5th argument you ought to find a hidden file with
the same name (well, and the dot of course) in the directory.
E.g. use: wget http://url_server/captured_mailfiles/XYZ.ext
The fifth argument of the action_replace_with_url function instructs MIMEDefang to store arbitary data into a file named .«sha1_from_content».«original_extension» (with the leading dot!!) into the same directory as the captured file, /var/spool/MIMEDefang_Captured_Files in my case. I decided to store the original content type as well as the original filename.
Now you have to configure the Webserver to pass this information back to the client, which then has to decide what to do with it. Normally the user gets this filename, when he tries to save the file.
Jeremy implemented a perl script callable by mod_perl, I took it and added some extra stuff for my needs.
# Apache 1.3 mod_perl PerlTypeHandler to add the Content-Disposition # header to outgoing files from a ".filename" file in the same directory # containing the real file. This allows files with names such as # "341ee566.gif" to have a human-friendly name associated with them. # # See RFC 2183 for more information on the Content-Disposition header. # # Enable this module with a PerlTypeHandler directive for the # <Directory> or similar areas in question after installing this file # under an Apache directory in @INC. # # PerlTypeHandler Apache::AddContentDisposition # # The author disclaims all copyrights and releases this module into the # public domain. package Apache::AddContentDisposition; use Apache::Constants qw(OK DECLINED); # to determine the Content-Type of the file use File::MMagic; # rename from => to my %map = ( 'image/pjpeg' => 'image/jpeg' ); my %needMagic = ( 'application/octet-stream' => 1 , 'application/binary' => 1 ); sub handler { my $r = shift; #LOG! $r->warn("In AddContentDisposition" . $r->filename); # /foo/bar file from Apache -> /foo/.bar metafile (my $metafile = $r->filename) =~ s,/([^/]+)$,/.$1,; # first line of data should contain mime type, tab, then the # recommended filename unless(open(FILE, $metafile)) { $r->log_reason("No such metafile \"$metafile\": $! " . $r->filename); return DECLINED; } my $filename = <FILE>; close FILE; #LOG! $r->warn("$metafile -> $filename"); return DECLINED unless $filename; my $mime_type = undef; if($filename =~ s!^([a-z0-9-]+/\S+)\s+!!i) { $mime_type = lc($1); } # sanitize out characters not listed and .. runs to mitigate potential # security problems on clients $filename =~ s/[^\w.-]//g; $filename =~ s/\.\.+/\./g; # Lowercase extension $filename =~ s/\.([^\.]*[A-Z][^\.]*)$/\.\L$1/g; #LOG! $r->warn("1: MIME type = '$mime_type', filename = '$filename'"); return DECLINED unless $filename; if(!$mime_type || defined $needMagic{$mime_type}) { # need to set Content-Type as is set to text/plain once our custom # Content-Disposition header is set, which trips up Mozilla my $type = File::MMagic->new->checktype_filename($r->filename); $mime_type = $type if $type; #LOG! $r->warn("File/MMagic: MIME type = '$mime_type', filename = '$filename'"); } return DECLINED unless $mime_type; if(my $type = $map{lc($mime_type)}) { $mime_type = $type; } $r->content_type($mime_type); $r->headers_out->set("Content-Disposition" => "inline; filename=$filename"); return OK; } 1;What's all this about?
The map hash renames some MIME types.
# MIME Defang captured files Alias /captured_mailfiles/ /var/spool/MIMEDefang_Captured_Files/ <Directory "/var/spool/MIMEDefang_Captured_Files"> SetHandler perl-script PerlTypeHandler Apache::AddContentDisposition AddDefaultCharset Off DefaultType application/octet-stream Options None AllowOverride None Order allow,deny Allow from all </Directory>The two new lines instruct Apache to pass any request to the directory through a Perl script (actually a Perl module) named Apache::AddContentDisposition.
wget -S http://url_server/captured_mailfiles/XYZ.ext [snip] HTTP request sent, awaiting response... 1 HTTP/1.1 200 OK 2 Date: Fri, 16 Jun 2006 14:27:41 GMT 3 Server: Apache/1.3.26 (Unix) Debian GNU/Linux mod_auth_pam/1.0a mod_ssl/2.8.9 OpenSSL/0.9.6c mod_perl/1.26 4 Content-Disposition: inline; filename=FILENAME.pdf 5 Last-Modified: Thu, 01 Jun 2006 19:07:40 GMT 6 ETag: "b8bc5-34e8-447f3afc" 7 Accept-Ranges: bytes 8 Content-Length: 13544 9 Keep-Alive: timeout=15, max=100 10 Connection: Keep-Alive 11 Content-Type: application/pdf [snip]You ought to see the proper content type and filename in the particular lines.