# -*- perl -*- =pod =head1 SUMMARY mailman_mimedefang_fix_footer - mimedefang filter code for moving a mailman footer from a separate attachment into the text and HTML body parts of the message. =head1 DESCRIPTION GNU Mailman L is the most popular free software package for managing electronic mailing lists. While it is quite good at what it does, there is one functional deficiency that people stumble over frequently: it doesn't handle message footers well for multipart MIME messages. Nowadays, most mail programs generate email messages containing both text and HTML versions of their content. When a Mailman list is configured to append a footer to outgoing messages and it encounters a message with both text and HTML content versions, it adds the footer as a separate section of the message entirely. Some mail programs display the resulting footer properly, but others do not, with the result that users may not see the footer as desired by the list administrators. This problem is discussed in the Mailman FAQ at L, which essentially says that the maintainers of Mailman consider this problem to be unsolveable. Not everyone is convinced, and some of the dissenters have put their money where their mouth is. For example, see L, which describes enhancements to Mailman implemented at one site to allow separate text and HTML headers to be configured and appended directly to the text and HTML content versions of outgoing messages. Unfortunately, the maintainers of Mailman rejected these enhancements, and they are obsolete, i.e., incompatible with the current stable version of Mailman. Others have pointed out that the popular mimedefang L free software package also knows how to add footers to text and HTML message parts, so if mimedefang can do it, then Mailman should be able to as well. It is also frequently mentioned that mimedefang can be used in conjunction with Mailman to implement a workaround for this problem, but it is not straightforward, and it seems like no one who mentions this solution ever explains exactly how to do it. Until now, that is... This script can be used to embed Mailman message footers directly in the text and HTML content versions of your outgoing messages. It does not require any code changes to Mailman; it can used with standard versions of Mailman and mimedefang. Read on to find out exactly how. =head1 USAGE Usage is simple: =over =item 1. Install and configure Mailman (consult Mailman documentation for details). =item 2. Install and configure mimedefang normally (consult mimedefang documentation for details). This script is tested with mimedefang version 2.70. =item 3. Add the code below (everything after "=cut") at the end of /etc/mail/mimedefang-filter, or wherever the mimedefang filter script lives on your system, right before the final "1;". =item 4. Add "&mailman_footer_munge($entity);" to the filter_end function, right after "return if message_rejected();". =item 5. For the Mailman lists for which you want to use this, change the "msg_footer" setting on the "Non-digest options" configuration page so that it starts and ends with a line containing only "MAILMAN_MIMEDEFANG". That is, add lines containing that string to the beginning and end of the setting, in addition to the footer text that's already there. If you don't want any footer at all, just put two "MAILMAN_MIMEDEFANG" lines, one right after the other, in the header. =item 6. Reload your mimedefang configuration. =item 7. Send an email message and check your mail syslog to make sure you didn't introduce a syntax error which is breaking mail delivery. =back That's it! The footers generated by Mailman for outgoing messages to the lists you've reconfigured will now be detected by mimedefang and moved into the text and HTML body parts of the messages. =head2 Word wrapping If you want the text version of the footer to be word-wrapped automatically, put a space and then "WRAP" on the first line of the footer, after "MAILMAN_MIMEDEFANG". =head2 Fixed-width HTML If you want the HTML version of the footer to be displayed in a fixed-width font, put a space and then "FIXED_WIDTH" on the first line of the footer, after "MAILMAN_MIMEDEFANG". You can do "MAILMAN_MIMEDEFANG WRAP FIXED_WIDTH" to get both of these features. =head1 COPYRIGHT Copyright (c) 2010-2011 Jonathan Kamens. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. See L. =head1 CONTACT I love to hear from people who are using my software, so please email me and let me know! Please also email me if you have any questions, suggestions, bug reports, fixes or enhancements to share. This is version $Revision: 1.16 $ of this script. The current version can always be downloaded from L. This Documentation is available on-line at L. =head1 CHANGES =over =item December 14, 2011 -- Clarify how to format the footer setting in the mailman list configuration. Update the copyright to include 2011. Update the link to my email address in the documentation. =item December 6, 2011 -- Clarify in the documentation where exactly to add the code to filter_end. =item November 4, 2011 -- If the message is multipart and has only one part, then work with that one part rather than with the entire message. This is necessary became mimedefang has a nasty habit of turning text/plain messages into multipart/mixed messages. =item June 14, 2010 -- Fixed to properly handle mailing list messages with attachments. =item May 24, 2010 -- Initial release =back =cut use MIME::Body; use MIME::Parser; use Text::Wrap; # Uncomment the next line for debugging, in which case you use this # file as a standalone filter script as follows: feed a message on # STDIN and it will print the possibly reformatted message on stdout. # Note that in this case the message may not look *exactly* like it # will look when mimedefang is done with it, but it'll be pretty # close. #$standalone_debug = 1; if ($standalone_debug) { $parser = new MIME::Parser; $parser->output_under("/tmp"); $entity = $parser->parse(\*STDIN); &mailman_footer_munge($entity); print $entity->as_string, "\n"; } sub mailman_footer_munge { my($top) = @_; my $entity = $top; md_syslog('debug', 'mailman_footer_munge: start'); if ($entity->is_multipart and $entity->parts == 1) { $entity = $entity->parts(0); md_syslog('debug', 'mailman_footer_munge: multipart with one part'); } md_syslog('debug', 'mailman_footer_munge: type=' . $entity->effective_type); if ($entity->effective_type eq 'text/plain') { my $body = $entity->bodyhandle->as_string; return if ($body !~ s/\n[ \t]*MAILMAN_MIMEDEFANG\s*$/\n/); return if ($body !~ s/\n[ \t]*MAILMAN_MIMEDEFANG((?:\s+(?:FIXED_WIDTH|WRAP))+)?[ \t]*\n((?:.*\n)*)$/\n/); my $footer = $2; $entity->bodyhandle(new MIME::Body::InCore([$body . $footer])); if (! $standalone_debug) { replace_entire_message($top); } return; } return if ($entity->effective_type ne 'multipart/mixed'); return if ($entity->parts < 2); my $last = $entity->parts($entity->parts-1); return if ($last->effective_type ne 'text/plain'); my $footer_bodyhandle = $last->bodyhandle; return if (! $footer_bodyhandle); my $footer = $footer_bodyhandle->as_string; return if ($footer !~ s/^[ \t]*MAILMAN_MIMEDEFANG((?:[ \t]+(?:FIXED_WIDTH|WRAP))+)?[ \t]*\n//); my $args = $1; return if ($footer !~ s/\n[ \t]*MAILMAN_MIMEDEFANG\s*$//); $args =~ s/^\s+//; $args = lc $args; my %args; map($args{$_}++, split(' ', $args)); my $html_footer = $footer; $html_footer =~ s/\&/\&/g; $html_footer =~ s/\/\>/g; $html_footer =~ s,\n,
,g; if ($args{'fixed_width'}) { $html_footer = '' . $html_footer . ''; } $html_footer = "

" . $html_footer . "

"; if ($args{'wrap'}) { $footer = wrap('', '', $footer); } if ($entity->parts == 2) { my $inner_head = $entity->parts(0)->head; my %mime_tags; foreach my $tag ($inner_head->tags) { if ($tag =~ /^content-/i) { $mime_tags{$tag} = $inner_head->get($tag); } } if ($entity->parts(0)->is_multipart) { $entity->preamble($entity->parts(0)->preamble); $entity->epilogue($entity->parts(0)->epilogue); $entity->parts([$entity->parts(0)->parts]); map { $entity->head->delete($_); $entity->head->add($_, $mime_tags{$_}); } keys %mime_tags; } else { my $inner_bodyhandle = $entity->parts(0)->bodyhandle; $entity->parts([]); map { $entity->head->delete($_); $entity->head->add($_, $mime_tags{$_}); } keys %mime_tags; $entity->bodyhandle($inner_bodyhandle); } } else { my(@parts) = $entity->parts; pop @parts; $entity->parts(\@parts); } if ($standalone_debug) { my($did_text, $did_html); my(@parts) = $entity->parts; while (my $part = shift @parts) { if ($part->is_multipart) { unshift(@parts, $part->parts); next; } if (! $did_text and $part->effective_type eq 'text/plain') { $part->bodyhandle(new MIME::Body::InCore([$part->bodyhandle->as_string . $footer])); last if ($did_html); $did_text++; } if (! $did_html and $part->effective_type eq 'text/html') { $part->bodyhandle(new MIME::Body::InCore([$part->bodyhandle->as_string . $html_footer])); last if ($did_text); $did_html++; } } } else { append_text_boilerplate($entity, $footer, 0); append_html_boilerplate($entity, $html_footer, 0); replace_entire_message($top); } }