]>
diplodocus.org Git - minc/blob - minc
931dd269913a71ce2d746b0074cb652a63010ae9
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.
112 =item $HOME/.mincfilter
114 This file is Perl code (included via the 'require' directive) which is
115 expected to define the FILTERS hash.
117 =item `mhpath +`/logs/minc.log
119 Where minc logs what it does, unless in -n mode.
121 =item `mhpath +`/logs/dryrun.log
123 Where minc logs what it would do; used in -n mode.
128 require "$HOME/.mincfilter";
137 $logfile = $mh . '/logs/minc.log';
139 $logfile = $mh . '/logs/dryrun.log';
143 ###############################################################################
146 my $logger = new Log
::Dispatch
;
147 $logger->add(new Log
::Dispatch
::File
(name
=>'logfile',
152 # log alias, handles getting passed vars that are undef
156 my $timestamp = strftime
('%b %e %H:%M:%S', localtime);
158 foreach my $part (@_) {
159 if (defined($part)) {
163 # no newlines in the log message, thanks
165 $msg = sprintf("%s %s%s %s\n", $timestamp, $act, $act, $msg);
166 $logger->log(level
=>$level, message
=>$msg);
168 if ($act eq SAVECHAR
) {
169 $logger->log(level
=>$level, message
=>"\n");
173 sub logincoming
{ mylog
('info', INCOMINGCHAR
, @_); }
174 sub logsave
{ mylog
('notice', SAVECHAR
, @_); }
175 sub loginfo
{ mylog
('info', INFOCHAR
, @_); }
176 sub logdebug
{ mylog
('debug', DEBUGCHAR
, @_); }
177 sub logwarn
{ mylog
('warning', WARNCHAR
, @_); }
178 sub logerr
{ mylog
('error', ERRORCHAR
, @_); }
183 logincoming
('From: ', $headers{'Return-Path'});
184 logincoming
('To: ', $headers{'To'});
185 logincoming
('Subject: ', $headers{'Subject'});
186 logincoming
('Message-Id: ', $headers{'Message-Id'});
190 ###############################################################################
194 my $mhfolder = shift;
199 $folder = $mh . '/' . $mhfolder;
203 foreach $component (split('/', $folder)) {
204 if (defined($component) and length($component) >= 1) {
205 $target = $target . '/' . $component;
206 if (-d
$target or mkdir($target)) {
209 die("Failed to create +$mhfolder ($component): $!\n");
216 # We want to print the name of each list that has new mail only once,
217 # so use this hash to store the lists that have already been printed.
218 # Start the list out with SPAM already in it, since we don't care when
219 # new messages are added to it.
220 my %FOLDERS = ('SPAM'=>1);
224 my $mhfolder = shift;
230 $mhmsg = `mhpath +$mhfolder new`;
232 $msgnum = basename
($mhmsg);
234 logsave
("+$mhfolder");
237 if (not rename($msg, $mhmsg)) {
238 die("Rename failed: $!\n");
241 if ($mhfolder ne 'SPAM') {
242 system("mark +$mhfolder $msgnum -sequence unseen -add");
246 if (not $FOLDERS{$mhfolder}) {
247 print("+$mhfolder\n");
248 $FOLDERS{$mhfolder} = 1;
255 my $current; # current header, used for unfolding lines
265 # folded header continuation
267 if (!defined($current)) {
268 # XXX: Malformed message; log to stderr
272 $headers{$current} .= $_;
274 ($current) = split(/:/);
276 # XXX: Is it correct simply to eat all whitespace between
277 # the colon and the first text for the header?
278 # Furthermore, is any space at all required? Or is
279 # '^Subject:hey$' a perfectly valid header?
280 (undef, $headers{$current}) = split(/^$current:\s*/);
289 ###############################################################################
299 $err =~ s
|/new/|/err/|;
300 $sig =~ s
|/new/|/sig/|;
310 ($message, $!) = split(/:/, $line);
312 logerr
("$err: $message: $!");
314 if (unlink($err) != 1) {
315 die("Failed unlink($err): $!\n");
318 # This is supposed to be a signature created with razor-check
319 # directly after delivery. Currently this isn't supported
320 # because it isn't clear to me how to get that signature back
321 # into razor-check. For now, just unlink any sig files we
322 # find and proceed with full razor-check mode.
324 if (unlink($sig) != 1) {
325 die("Failed unlink($sig): $!\n");
329 #$status = system("razor-check < $msg");
330 $status = system('false');
331 if (WIFEXITED
($status) and WEXITSTATUS
($status) == 0) {
345 foreach $msg (@msglist) {
347 store_message
($msg, 'SPAM');
357 ###############################################################################
369 %headers = get_headers
($msg);
371 log_headers
(%headers);
373 foreach $header (keys(%FILTERS)) {
374 $contents = $headers{$header};
376 if (defined($contents)) {
377 foreach $regex (keys(%{$FILTERS{$header}})) {
378 $subst = $FILTERS{$header}->{$regex};
380 $try = '$contents =~';
381 $try .= " s|$regex|$subst|is";
397 foreach $msg (@msglist) {
398 $mhfolder = find_mh_folder
($msg);
399 store_message
($msg, $mhfolder);
408 $Data::Dumper
::Indent
= 1;
409 print(Dumper
(\
%FILTERS));
413 @msglist = kill_spam
(glob("$MAILDIR/new/*"));
414 filter_mail
(@msglist);
423 {'.*<([[:graph:]]+)-([[:graph:]]+)\.lists\.sourceforge\.net>.*'=>'l/$1/$2',
424 '.*<([[:graph:]]+?)(-list)?\.freedesktop\.org>.*'=>'l/freedesktop/$1',
425 '.*<swig\.cs\.uchicago\.edu>.*'=>'l/swig'},
428 {'<mailto:([[:graph:]]+)+@([[:graph:]]+)\.apache\.org>'=>'l/apache/$2/$1'},
433 The first List-Id filter is a surprisingly effective one which creates
434 appropriate folder names (such as l/htdig/updates and l/rox/devel) for
435 all Sourceforge lists to which i subscribe. Certainly there are lists
436 hosted at Sourceforge which do not work well with this, and this is
437 where it is important to remember that the first match is the one
438 uses. Simply put the more specific rules before this one.
440 The next List-Id example is simple. The swig example demonstrates
441 that the folder name does not have to use a portion of the matched
442 header; you can just hardcode the folder name.
444 The List-Post example is a nice one. Most ASF projects have their own
445 apache.org subdomain, with mailing lists hosted there. So, given a
446 list such as dev@httpd.apache.org, this filter will create the folder
447 name l/apache/httpd/dev.
451 Written by Eric Gillespie <epg@pretzelnet.org> with logging code
452 stolen from Adam Lazur <adam@lazur.org>.
454 Design by Eric Gillespie and Doug Porter <dsp@waterspout.com>.