]>
diplodocus.org Git - minc/blob - minc
cd0b8d62ed089f8ca7fdfe73a02482eef52f252c
7 B<minc> - Incorporate mail from a maildir into mh folders.
15 B<minc> is a program for incorporating mail from a maildir to a mh
16 folder hierarchy. It takes mail from a maildir folder (not a maildir
17 folder hierarchy), checks for spam with razor (XXX crossref), and
18 optionally filters mail into separate mh folders.
20 The filtering is quite sophisticated, as it is done using real Perl
21 substitution (s//) commands.
28 require 'sysexits.ph';
34 use Log
::Dispatch
::File
;
35 use POSIX
qw(strftime WEXITSTATUS WIFEXITED);
43 Dump (using Data::Dumper) the FILTERS hash and exit. This is useful
44 for testing the syntax of .mincfilter.
52 Dry run; do not actually incorporate the mail, but log and report to
53 stdout/stderr as normal.
57 Process SPAM only, then exit, leaving all non-spam messages in the
71 our $opt_s; # ;; # stupid cperl-mode
73 if (!getopts
('dhns')) {
80 print("Sorry bub, no help.\n");
86 if ($opt_s) { # ))){ # stupid cperl-mode
96 Where configuration files (.mincfilter) are found.
100 Where mail is delivered.
106 use Env
qw(HOME MAILDIR);
109 die("HOME environment variable must be set.\n");
112 $MAILDIR = "$HOME/Maildir";
119 =item $HOME/.mincfilter
121 This file is Perl code (included via the 'require' directive) which is
122 expected to define the FILTERS hash.
124 =item `mhpath +`/logs/minc.log
126 Where minc logs what it does, unless in -n mode.
128 =item `mhpath +`/logs/dryrun.log
130 Where minc logs what it would do; used in -n mode.
135 require "$HOME/.mincfilter";
144 $logfile = $mh . '/logs/minc.log';
146 $logfile = $mh . '/logs/dryrun.log';
150 ###############################################################################
153 # debuglevels for the logger
154 use constant LOG_DEBUGLEVEL
=> 'info';
155 use constant SCREEN_DEBUGLEVEL
=> 'debug';
157 # characters used to wrap around the id field in the log so I can more
158 # easily parse the msg flow
159 use constant DEBUGCHAR
=> '%';
160 use constant INFOCHAR
=> '=';
161 use constant INCOMINGCHAR
=> '<';
162 use constant SAVECHAR
=> '>';
163 use constant WARNCHAR
=> 'W';
164 use constant ERRORCHAR
=> 'E';
166 my $logger = new Log
::Dispatch
;
167 $logger->add(new Log
::Dispatch
::File
(name
=>'logfile',
172 # log alias, handles getting passed vars that are undef
176 my $timestamp = strftime
('%b %e %H:%M:%S', localtime);
178 foreach my $part (@_) {
179 if (defined($part)) {
183 # no newlines in the log message, thanks
185 $msg = sprintf("%s %s%s %s\n", $timestamp, $act, $act, $msg);
186 $logger->log(level
=>$level, message
=>$msg);
188 if ($act eq SAVECHAR
) {
189 $logger->log(level
=>$level, message
=>"\n");
193 sub logincoming
{ mylog
('info', INCOMINGCHAR
, @_); }
194 sub logsave
{ mylog
('notice', SAVECHAR
, @_); }
195 sub loginfo
{ mylog
('info', INFOCHAR
, @_); }
196 sub logdebug
{ mylog
('debug', DEBUGCHAR
, @_); }
197 sub logwarn
{ mylog
('warning', WARNCHAR
, @_); }
198 sub logerr
{ mylog
('error', ERRORCHAR
, @_); }
203 logincoming
('From: ', $headers{'return-path'});
204 logincoming
('To: ', $headers{'to'});
205 logincoming
('Subject: ', $headers{'subject'});
206 logincoming
('Message-Id: ', $headers{'message-id'});
210 ###############################################################################
214 printf('%s:%s', __FILE__
, __LINE__
);
243 my $mhfolder = shift;
248 $folder = $mh . '/' . $mhfolder;
252 foreach $component (split('/', $folder)) {
253 if (defined($component) and length($component) >= 1) {
254 $target = $target . '/' . $component;
255 if (-d
$target or mkdir($target)) {
259 "Failed to create +$mhfolder ($component)");
266 # We want to print the name of each list that has new mail only once,
267 # so use this hash to store the lists that have already been printed.
268 # Start the list out with SPAM already in it, since we don't care when
269 # new messages are added to it.
270 my %FOLDERS = ('SPAM'=>1);
274 my $mhfolder = shift;
279 # We must do this even in -n mode because later steps fail without
280 # it. This should be harmless.
283 $mhmsg = `mhpath +$mhfolder new`;
285 $msgnum = basename
($mhmsg);
287 logsave
("+$mhfolder");
290 if (not rename($msg, $mhmsg)) {
291 err
(&EX_TEMPFAIL
, "Failed rename($msg, $mhmsg)");
294 if ($mhfolder ne 'SPAM') {
295 $status = system("mark +$mhfolder $msgnum -sequence unseen -add");
296 if (not (WIFEXITED
($status) and WEXITSTATUS
($status) == 0)) {
297 errx
(&EX_TEMPFAIL
, "Failed to mark message unseen");
302 if (not $FOLDERS{$mhfolder}) {
303 print("+$mhfolder\n");
304 $FOLDERS{$mhfolder} = 1;
311 my $current; # current header, used for unfolding lines
312 my $fieldname; # unmolested header name
322 # folded header continuation
324 if (!defined($current)) {
325 print("Malformed message, cannot parse headers.\n");
329 $headers{$current} .= $_;
331 ($fieldname) = split(/:/);
332 $current = lc($fieldname);
333 (undef, $headers{$current}) = split(/^$fieldname:\s*/);
342 ###############################################################################
352 $err =~ s
|/new/|/err/|;
353 $sig =~ s
|/new/|/sig/|;
363 ($message, $!) = split(/:/, $line);
365 logerr
("$err: $message: $!");
368 if (unlink($err) != 1) {
369 err
(&EX_TEMPFAIL
, "Failed unlink($err)");
373 # This is supposed to be a signature created with razor-check
374 # directly after delivery. Currently this isn't supported
375 # because it isn't clear to me how to get that signature back
376 # into razor-check. For now, just unlink any sig files we
377 # find and proceed with full razor-check mode.
380 if (unlink($sig) != 1) {
381 err
(&EX_TEMPFAIL
, "Failed unlink($sig)");
387 $status = system("razor-check < $msg");
388 if (not WIFEXITED
($status)) {
389 err
(&EX_TEMPFAIL
, "Failed to run razor-check < $msg");
390 } elsif (WEXITSTATUS
($status) == 0) {
409 foreach $msg (@msglist) {
410 printf('%sChecking for spam... %6d/%d',
411 "\r", ++$i, scalar(@msglist));
414 store_message
($msg, 'SPAM');
419 print("\nDone: ", scalar(@result), " survivors\n");
425 ###############################################################################
437 %headers = get_headers
($msg);
442 log_headers
(%headers);
444 foreach $header (keys(%FILTERS)) {
445 $contents = $headers{lc($header)};
447 if (defined($contents)) {
448 foreach $regex (keys(%{$FILTERS{$header}})) {
449 $subst = $FILTERS{$header}->{$regex};
451 $try = '$contents =~';
452 $try .= " s|$regex|$subst|is";
468 foreach $msg (@msglist) {
469 $mhfolder = find_mh_folder
($msg);
470 store_message
($msg, $mhfolder);
479 $Data::Dumper
::Indent
= 1;
480 print(Dumper
(\
%FILTERS));
484 @msglist = kill_spam
(glob("$MAILDIR/new/*"));
487 filter_mail
(@msglist);
497 {'.*<([[:graph:]]+)-([[:graph:]]+)\.lists\.sourceforge\.net>.*'=>'l/$1/$2',
498 '.*<([[:graph:]]+?)(-list)?\.freedesktop\.org>.*'=>'l/freedesktop/$1',
499 '.*<swig\.cs\.uchicago\.edu>.*'=>'l/swig'},
502 {'<mailto:([[:graph:]]+)+@([[:graph:]]+)\.apache\.org>'=>'l/apache/$2/$1'},
507 The first List-Id filter is a surprisingly effective one which creates
508 appropriate folder names (such as l/htdig/updates and l/rox/devel) for
509 all Sourceforge lists to which i subscribe. Certainly there are lists
510 hosted at Sourceforge which do not work well with this, and this is
511 where it is important to remember that the first match is the one
512 uses. Simply put the more specific rules before this one.
514 The next List-Id example is simple. The swig example demonstrates
515 that the folder name does not have to use a portion of the matched
516 header; you can just hardcode the folder name.
518 The List-Post example is a nice one. Most ASF projects have their own
519 apache.org subdomain, with mailing lists hosted there. So, given a
520 list such as dev@httpd.apache.org, this filter will create the folder
521 name l/apache/httpd/dev.
525 Written by Eric Gillespie <epg@pretzelnet.org> with logging code
526 stolen from Adam Lazur <adam@lazur.org>.
528 Design by Eric Gillespie and Doug Porter <dsp@waterspout.com>.