]>
diplodocus.org Git - minc/blob - minc
3cbd090aef8aa02ed438164d30f05ae637229b93
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);
37 # debuglevels for the logger
38 use constant LOG_DEBUGLEVEL
=> 'info';
39 use constant SCREEN_DEBUGLEVEL
=> 'debug';
41 # characters used to wrap around the id field in the log so I can more
42 # easily parse the msg flow
43 use constant DEBUGCHAR
=> '%';
44 use constant INFOCHAR
=> '=';
45 use constant INCOMINGCHAR
=> '<';
46 use constant SAVECHAR
=> '>';
47 use constant WARNCHAR
=> 'W';
48 use constant ERRORCHAR
=> 'E';
56 Dump (using Data::Dumper) the FILTERS hash and exit. This is useful
57 for testing the syntax of .mincfilter.
65 Dry run; do not actually incorporate the mail, but log and report to
66 stdout/stderr as normal.
77 if (!getopts
('dhn')) {
83 print("Sorry bub, no help.\n");
95 Where configuration files (.mincfilter) are found.
99 Where mail is delivered.
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 my $logger = new Log
::Dispatch
;
154 $logger->add(new Log
::Dispatch
::File
(name
=>'logfile',
159 # log alias, handles getting passed vars that are undef
163 my $timestamp = strftime
('%b %e %H:%M:%S', localtime);
165 foreach my $part (@_) {
166 if (defined($part)) {
170 # no newlines in the log message, thanks
172 $msg = sprintf("%s %s%s %s\n", $timestamp, $act, $act, $msg);
173 $logger->log(level
=>$level, message
=>$msg);
175 if ($act eq SAVECHAR
) {
176 $logger->log(level
=>$level, message
=>"\n");
180 sub logincoming
{ mylog
('info', INCOMINGCHAR
, @_); }
181 sub logsave
{ mylog
('notice', SAVECHAR
, @_); }
182 sub loginfo
{ mylog
('info', INFOCHAR
, @_); }
183 sub logdebug
{ mylog
('debug', DEBUGCHAR
, @_); }
184 sub logwarn
{ mylog
('warning', WARNCHAR
, @_); }
185 sub logerr
{ mylog
('error', ERRORCHAR
, @_); }
190 logincoming
('From: ', $headers{'Return-Path'});
191 logincoming
('To: ', $headers{'To'});
192 logincoming
('Subject: ', $headers{'Subject'});
193 logincoming
('Message-Id: ', $headers{'Message-Id'});
197 ###############################################################################
201 printf("%s:%s", __FILE__
, __LINE__
);
230 my $mhfolder = shift;
235 $folder = $mh . '/' . $mhfolder;
239 foreach $component (split('/', $folder)) {
240 if (defined($component) and length($component) >= 1) {
241 $target = $target . '/' . $component;
242 if (-d
$target or mkdir($target)) {
246 "Failed to create +$mhfolder ($component)");
253 # We want to print the name of each list that has new mail only once,
254 # so use this hash to store the lists that have already been printed.
255 # Start the list out with SPAM already in it, since we don't care when
256 # new messages are added to it.
257 my %FOLDERS = ('SPAM'=>1);
261 my $mhfolder = shift;
267 $mhmsg = `mhpath +$mhfolder new`;
269 $msgnum = basename
($mhmsg);
271 logsave
("+$mhfolder");
274 if (not rename($msg, $mhmsg)) {
275 err
(&EX_TEMPFAIL
, "Failed rename($msg, $mhmsg)");
278 if ($mhfolder ne 'SPAM') {
279 system("mark +$mhfolder $msgnum -sequence unseen -add");
283 if (not $FOLDERS{$mhfolder}) {
284 print("+$mhfolder\n");
285 $FOLDERS{$mhfolder} = 1;
292 my $current; # current header, used for unfolding lines
302 # folded header continuation
304 if (!defined($current)) {
305 # XXX: Malformed message; log to stderr
309 $headers{$current} .= $_;
311 ($current) = split(/:/);
313 # XXX: Is it correct simply to eat all whitespace between
314 # the colon and the first text for the header?
315 # Furthermore, is any space at all required? Or is
316 # '^Subject:hey$' a perfectly valid header?
317 (undef, $headers{$current}) = split(/^$current:\s*/);
326 ###############################################################################
336 $err =~ s
|/new/|/err/|;
337 $sig =~ s
|/new/|/sig/|;
347 ($message, $!) = split(/:/, $line);
349 logerr
("$err: $message: $!");
351 if (unlink($err) != 1) {
352 err
(&EX_TEMPFAIL
, "Failed unlink($err)");
355 # This is supposed to be a signature created with razor-check
356 # directly after delivery. Currently this isn't supported
357 # because it isn't clear to me how to get that signature back
358 # into razor-check. For now, just unlink any sig files we
359 # find and proceed with full razor-check mode.
361 if (unlink($sig) != 1) {
362 err
(&EX_TEMPFAIL
, "Failed unlink($sig)");
366 #$status = system("razor-check < $msg");
367 $status = system('false');
368 if (WIFEXITED
($status) and WEXITSTATUS
($status) == 0) {
382 foreach $msg (@msglist) {
384 store_message
($msg, 'SPAM');
394 ###############################################################################
406 %headers = get_headers
($msg);
408 log_headers
(%headers);
410 foreach $header (keys(%FILTERS)) {
411 $contents = $headers{$header};
413 if (defined($contents)) {
414 foreach $regex (keys(%{$FILTERS{$header}})) {
415 $subst = $FILTERS{$header}->{$regex};
417 $try = '$contents =~';
418 $try .= " s|$regex|$subst|is";
434 foreach $msg (@msglist) {
435 $mhfolder = find_mh_folder
($msg);
436 store_message
($msg, $mhfolder);
445 $Data::Dumper
::Indent
= 1;
446 print(Dumper
(\
%FILTERS));
450 @msglist = kill_spam
(glob("$MAILDIR/new/*"));
451 filter_mail
(@msglist);
460 {'.*<([[:graph:]]+)-([[:graph:]]+)\.lists\.sourceforge\.net>.*'=>'l/$1/$2',
461 '.*<([[:graph:]]+?)(-list)?\.freedesktop\.org>.*'=>'l/freedesktop/$1',
462 '.*<swig\.cs\.uchicago\.edu>.*'=>'l/swig'},
465 {'<mailto:([[:graph:]]+)+@([[:graph:]]+)\.apache\.org>'=>'l/apache/$2/$1'},
470 The first List-Id filter is a surprisingly effective one which creates
471 appropriate folder names (such as l/htdig/updates and l/rox/devel) for
472 all Sourceforge lists to which i subscribe. Certainly there are lists
473 hosted at Sourceforge which do not work well with this, and this is
474 where it is important to remember that the first match is the one
475 uses. Simply put the more specific rules before this one.
477 The next List-Id example is simple. The swig example demonstrates
478 that the folder name does not have to use a portion of the matched
479 header; you can just hardcode the folder name.
481 The List-Post example is a nice one. Most ASF projects have their own
482 apache.org subdomain, with mailing lists hosted there. So, given a
483 list such as dev@httpd.apache.org, this filter will create the folder
484 name l/apache/httpd/dev.
488 Written by Eric Gillespie <epg@pretzelnet.org> with logging code
489 stolen from Adam Lazur <adam@lazur.org>.
491 Design by Eric Gillespie and Doug Porter <dsp@waterspout.com>.