summaryrefslogtreecommitdiff
path: root/usr.sbin/sendmail/contrib/mailprio
blob: 58feba7c22e4af62218f5e401ef2a4517fd720e2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
Received: from austin.bsdi.com (root{9l9gVDC7v8t3dlv0OtXTlby6X1zBWd56}@austin.BSDI.COM [205.230.224.49]) by knecht.Sendmail.ORG (8.8.2/8.8.2) with ESMTP id JAA05023 for <eric@sendmail.org>; Thu, 31 Oct 1996 09:29:47 -0800 (PST)
Received: from austin.bsdi.com (localhost [127.0.0.1]) by austin.bsdi.com (8.7.4/8.7.3) with ESMTP id KAA19250; Thu, 31 Oct 1996 10:28:18 -0700 (MST)
Message-Id: <199610311728.KAA19250@austin.bsdi.com>
To: Eric Allman <eric@sendmail.org>
cc: marc@xfree86.org
Subject: Updated mailprio_0_93.shar
From: Tony Sanders <sanders@earth.com>
Organization: Berkeley Software Design, Inc.
Date: Thu, 31 Oct 1996 10:28:14 -0700
Sender: sanders@austin.bsdi.com

Eric, please update contrib/mailprio in the sendmail distribution
to this version at your convenience.  Thanks.

I've also made this available in:
	ftp://ftp.earth.com/pub/postmaster/

mailprio_0_93.shar follows...

#!/bin/sh
# This is a shell archive (produced by GNU sharutils 4.1).
# To extract the files from this archive, save it to some FILE, remove
# everything before the `!/bin/sh' line above, then type `sh FILE'.
#
# Made on 1996-10-31 10:07 MST by <sanders@earth.com>.
#
# Existing files will *not* be overwritten unless `-c' is specified.
#
# This shar contains:
# length mode       name
# ------ ---------- ------------------------------------------
#   8260 -rwxr-xr-x mailprio
#   3402 -rw-r--r-- mailprio.README
#   4182 -rwxr-xr-x mailprio_mkdb
#
touch -am 1231235999 $$.touch >/dev/null 2>&1
if test ! -f 1231235999 && test -f $$.touch; then
  shar_touch=touch
else
  shar_touch=:
  echo
  echo 'WARNING: not restoring timestamps.  Consider getting and'
  echo "installing GNU \`touch', distributed in GNU File Utilities..."
  echo
fi
rm -f 1231235999 $$.touch
#
# ============= mailprio ==============
if test -f 'mailprio' && test X"$1" != X"-c"; then
  echo 'x - skipping mailprio (file already exists)'
else
  echo 'x - extracting mailprio (text)'
  sed 's/^X//' << 'SHAR_EOF' > 'mailprio' &&
#!/usr/bin/perl
#
# mailprio,v 1.4 1996/10/31 17:03:52 sanders Exp
# Version 0.93 -- Thu Oct 31 09:42:25 MST 1996
#
# mailprio -- setup mail priorities for a mailing list
#
# Copyright 1994, 1996, Tony Sanders <sanders@earth.com>
# Rights are hereby granted to download, use, modify, sell, copy, and
# redistribute this software so long as the original copyright notice
# and this list of conditions remain intact and modified versions are
# noted as such.
#
# I would also very much appreciate it if you could send me a copy of
# any changes you make so I can possibly integrate them into my version.
#
# Options:
#     -p priority_database      -- Specify database to use if not default
#     -q                        -- Process sendmail V8.8.X queue format files
#
# Sort mailing lists or sendmail queue files by mailprio database.
# Files listed on the command line are locked and then sorted in place, in
# the absence of any file arguments it will read STDIN and write STDOUT.
#
# Examples:
#     mailprio < mailing-list > sorted_list
#     mailprio mailing-list1 mailing-list2 mailing-list3 ...
#     mailprio -q /var/spool/mqueue/qf*
# To double check results:
#     sort sorted_list > checkit; sort orig-mailing-list | diff - checkit
#
# To get the maximum value from a transaction delay based priority
# function you need to reorder the distribution list (and the mail
# queue files for that matter) fairly often; you could even have
# your mailing list software reorder the list before each outgoing
# message.
#
$usage = "Usage: mailprio [-p priodb] [-q] [mailinglists ...]\n";
$home = "/home/sanders/lists";
$priodb = "$home/mailprio";
$locking = "flock";     # "flock" or "fcntl"
X
# In shell, it would go more or less like this:
#     old_mailprio > /tmp/a
#     fgrep -f lists/inet-access /tmp/a | sed -e 's/^.......//' > /tmp/b
#         ; /tmp/b contains list of known users, faster delivery first
#     fgrep -v -f /tmp/b lists/inet-access > /tmp/c
#         ; put all unknown stuff at the top of new list for now
#     echo '# -----' >> /tmp/c
#     cat /tmp/b >> /tmp/c
X
$qflag = 0;
while ($main'ARGV[0] =~ /^-/) {
X        $args = shift;
X        if ($args =~ m/\?/) { print $usage; exit 0; }
X        if ($args =~ m/q/) { $qflag = 1; }
X        if ($args =~ m/p/) {
X            $priodb = shift || die $usage, "-p requires argument\n"; }
}
X
push(@main'ARGV, '-') if ($#ARGV < 0);
while ($file = shift @ARGV) {
X    if ($file eq "-") {
X        $source = "main'STDIN";
X        $sink = "main'STDOUT";
X    } else {
X        $sink = $source = "FH";
X        open($source, "+< $file") || do { warn "$file: $!\n"; next; };
X        if (!defined &seize($source, &LOCK_EX | &LOCK_NB)) {
X            # couldn't get lock, just skip it
X            close($source);
X            next;
X        }
X    }
X
X    local(*list);
X    &process($source, *list);
X
X    # setup to write output
X    if ($file ne "-") {
X	# zero the file (FH is hardcoded because truncate requires it, sigh)
X        seek(FH, 0, 0) || die "$file: seek: $!\n";
X        truncate(FH, 0) || die "$file: truncate: $!\n";
X    }
X
X    # do the dirty work
X    &output($sink, *list);
X
X    close($sink) || warn "$file: $!\n";         # close clears the lock
X    close($source);
}
X
sub process {
X    # Setup %list and @list
X    local($source, *list) = @_;
X    local($addr, $canon);
X    while ($addr = <$source>) {
X        chop $addr;
X        next if $addr =~ /^# ----- /;                   # that's our line
X        push(@list, $addr), next if $addr =~ /^\s*#/;   # save comments
X	if ($qflag) {
X	    next if $addr =~ m/^\./;
X	    push(@list, $addr), next if !($addr =~ s/^(R[^:]*:)//);
X	    $Rflags = $1;
X	}
X        $canon = &canonicalize((&simplify_address($addr))[0]);
X        unless (defined $canon) {
X            warn "$file: no address found: $addr\n";
X            push(@list, ($qflag?$Rflags:'') . $addr);       # save it as is
X            next;
X        }
X        if (defined $list{$canon}) {
X            warn "$file: duplicate: ``$addr -> $canon''\n";
X            push(@list, ($qflag?$Rflags:'') . $addr);       # save it as is
X            next;
X        }
X        $list{$canon} = $addr;
X    }
}
X
sub output {
X    local($sink, *list) = @_;
X
X    local($to, *prio, *userprio, *useracct);
X    dbmopen(%prio, $priodb, 0644) || die "$priodb: $!\n";
X    foreach $to (keys %list) {
X        if (defined $prio{$to}) {
X            # add to list of found users (%userprio) and remove from %list
X            # so that we know what users were not yet prioritized
X            $userprio{$to} = $prio{$to};        # priority
X            $useracct{$to} = $list{$to};        # string
X            delete $list{$to};
X        }
X    }
X    dbmclose(%prio);
X
X    # Put all the junk we found at the very top
X    # (this might not always be a feature)
X    print $sink join("\n", @list), "\n" if int(@list);
X
X    # prioritized list of users
X    if (int(keys %userprio)) {
X        print $sink '# ----- prioritized users', "\n" unless $qflag;
X        foreach $to (sort by_userprio keys %userprio) {
X            die "Opps! Something is seriously wrong with useracct: $to\n"
X                unless defined $useracct{$to};
X	    print $sink 'RFD:' if $qflag;
X            print $sink $useracct{$to}, "\n";
X        }
X    }
X
X    # unprioritized users go last, fast accounts will get moved up eventually
X    # XXX: should go before the "really slow" prioritized users?
X    if (int(keys %list)) {
X        print $sink '# ----- unprioritized users', "\n" unless $qflag;
X        foreach $to (keys %list) {
X            print $sink 'RFD:' if $qflag;
X            print $sink $list{$to}, "\n";
X        }
X    }
X
X    print $sink ".\n" if $qflag;
}
X
sub by_userprio {
X    # sort first by priority, then by key.
X    $userprio{$a} <=> $userprio{$b} || $a cmp $b;
}
X
# REPL-LIB ---------------------------------------------------------------
X
sub canonicalize {
X    local($addr) = @_;
X    # lowercase, strip leading/trailing whitespace
X    $addr =~ y/A-Z/a-z/; $addr =~ s/^\s+//; $addr =~ s/\s+$//; $addr;
}
X
# @addrs = simplify_address($addr);
sub simplify_address {
X    local($_) = shift;
X    1 while s/\([^\(\)]*\)//g;          # strip comments
X    1 while s/"[^"]*"//g;               # strip comments
X    split(/,/);                         # split into parts
X    foreach (@_) {
X        1 while s/.*<(.*)>.*/\1/;
X        s/^\s+//;
X        s/\s+$//;
X    }
X    @_;
}
X
### ---- ###
#
# Error codes
#
do 'errno.ph';
eval 'sub ENOENT {2;}'          unless defined &ENOENT;
eval 'sub EINTR {4;}'           unless defined &EINTR;
eval 'sub EINVAL {22;}'         unless defined &EINVAL;
X
#
# File locking
#
do 'sys/unistd.ph';
eval 'sub SEEK_SET {0;}'        unless defined &SEEK_SET;
X
do 'sys/file.ph';
eval 'sub LOCK_SH {0x01;}'      unless defined &LOCK_SH;
eval 'sub LOCK_EX {0x02;}'      unless defined &LOCK_EX;
eval 'sub LOCK_NB {0x04;}'      unless defined &LOCK_NB;
eval 'sub LOCK_UN {0x08;}'      unless defined &LOCK_UN;
X
do 'fcntl.ph';
eval 'sub F_GETFD {1;}'         unless defined &F_GETFD;
eval 'sub F_SETFD {2;}'         unless defined &F_SETFD;
eval 'sub F_GETFL {3;}'         unless defined &F_GETFL;
eval 'sub F_SETFL {4;}'         unless defined &F_SETFL;
eval 'sub O_NONBLOCK {0x0004;}' unless defined &O_NONBLOCK;
eval 'sub F_SETLK {8;}'         unless defined &F_SETLK;        # nonblocking
eval 'sub F_SETLKW {9;}'        unless defined &F_SETLKW;       # lockwait
eval 'sub F_RDLCK {1;}'         unless defined &F_RDLCK;
eval 'sub F_UNLCK {2;}'         unless defined &F_UNLCK;
eval 'sub F_WRLCK {3;}'         unless defined &F_WRLCK;
$s_flock = "sslll";             # struct flock {type, whence, start, len, pid}
X
# return undef on failure
sub seize {
X    local ($FH, $lock) = @_;
X    local ($ret);
X    if ($locking eq "flock") {
X        $ret = flock($FH, $lock);
X	return ($ret == 0 ? undef : 1);
X    } else {
X        local ($flock, $type) = 0;
X        if ($lock & &LOCK_SH) { $type = &F_RDLCK; }
X        elsif ($lock & &LOCK_EX) { $type = &F_WRLCK; }
X        elsif ($lock & &LOCK_UN) { $type = &F_UNLCK; }
X        else { $! = &EINVAL; return undef; }
X        $flock = pack($s_flock, $type, &SEEK_SET, 0, 0, 0);
X        $ret = fcntl($FH, ($lock & &LOCK_NB) ? &F_SETLK : &F_SETLKW, $flock);
X	return ($ret == -1 ? undef : 1);
X    }
}
SHAR_EOF
  $shar_touch -am 1031100396 'mailprio' &&
  chmod 0755 'mailprio' ||
  echo 'restore of mailprio failed'
  shar_count="`wc -c < 'mailprio'`"
  test 8260 -eq "$shar_count" ||
    echo "mailprio: original size 8260, current size $shar_count"
fi
# ============= mailprio.README ==============
if test -f 'mailprio.README' && test X"$1" != X"-c"; then
  echo 'x - skipping mailprio.README (file already exists)'
else
  echo 'x - extracting mailprio.README (text)'
  sed 's/^X//' << 'SHAR_EOF' > 'mailprio.README' &&
mailprio README
X
mailprio.README,v 1.2 1996/10/31 17:03:54 sanders Exp
Version 0.93 -- Thu Oct 31 09:42:25 MST 1996
X
Copyright 1994, 1996, Tony Sanders <sanders@earth.com>
Rights are hereby granted to download, use, modify, sell, copy, and
redistribute this software so long as the original copyright notice
and this list of conditions remain intact and modified versions are
noted as such.
X
I would also very much appreciate it if you could send me a copy of
any changes you make so I can possibly integrate them into my version.
X
The current version of this and other related mail tools are available in:
X	ftp://ftp.earth.com/pub/postmaster/
X
Even with the new persistent host status in sendmail V8.8.X this
function can still reduce the lag time distributing mail to a large
group of people.  It also makes it a little more likely that everyone
will get mailing list mail in the order sent which can help reduce
duplicate postings.  Basically, the goal is to put slow hosts at
the bottom of the list so that as many fast hosts are delivered
as quickly as possible.
X
CONTENTS
========
X
X    mailprio.README		-- simple docs
X    mailprio			-- the address sorter
X    mailprio_mkdb		-- builds the database for the sorter
X
X
CHANGES
=======
X    Version 0.92
X	Initial public release.
X
X    Version 0.93
X	Updated to make use of the (somewhat) new xdelay statistic.
X	Changed -q flag to support new sendmail queue file format (RFD:<addr>).
X	Fixed argument parsing bug.
X	Fixed bug with database getting "garbage" in it.
X
X
CONFIGURATION
=============
X
X    You need to edit each script and ensure proper configuration.
X
X    In mailprio check:        #!perl path, $home, $priodb, $locking
X
X    In mailprio_mkdb check:   #!perl path, $home, $priodb, $maillog
X
X
USAGE: mailprio
===============
X
X    Usage: mailprio [-p priodb] [-q] [mailinglists ...]
X	-p priority_database   -- Specify database to use if not default
X	-q                     -- Process sendmail queue format files
X				  [USE WITH CAUTION]
X
X    Sort mailing lists or sendmail V8 queue files by mailprio database.
X    Files listed on the command line are locked and then sorted in place, in
X    the absence of any file arguments it will read STDIN and write STDOUT.
X
X    Examples:
X	mailprio < mailing-list > sorted_list
X	mailprio mailing-list1 mailing-list2 mailing-list3 ...
X	mailprio -q /var/spool/mqueue/qf*	[not recommended]
X    To double check results:
X	sort sorted_list > checkit; sort orig-mailing-list | diff - checkit
X
X    NOTE:
X	To get the maximum value from a transaction delay based priority
X	function you need to reorder the distribution list (and the mail
X	queue files for that matter) fairly often; you could even have
X	your mailing list software reorder the list before each outgoing
X	message.
X
X
USAGE: mailprio_mkdb
====================
X
X    Usage: mailprio_mkdb [-l maillog] [-p priodb]
X	-l maillog             -- Specify maillog to process if not default
X	-p priority_database   -- Specify database to use if not default
X
X    Builds the mail priority database using information from the maillog.
X
X    Run at least nightly before you rotate the maillog.  If you are
X    going to run mailprio more often than that then you will need to
X    load the current maillog information before that will do any good
X    (and to keep from reloading the same information you will need
X    some kind of incremental maillog information to load from).
SHAR_EOF
  $shar_touch -am 1031100396 'mailprio.README' &&
  chmod 0644 'mailprio.README' ||
  echo 'restore of mailprio.README failed'
  shar_count="`wc -c < 'mailprio.README'`"
  test 3402 -eq "$shar_count" ||
    echo "mailprio.README: original size 3402, current size $shar_count"
fi
# ============= mailprio_mkdb ==============
if test -f 'mailprio_mkdb' && test X"$1" != X"-c"; then
  echo 'x - skipping mailprio_mkdb (file already exists)'
else
  echo 'x - extracting mailprio_mkdb (text)'
  sed 's/^X//' << 'SHAR_EOF' > 'mailprio_mkdb' &&
#!/usr/bin/perl
#
# mailprio_mkdb,v 1.5 1996/10/31 17:03:53 sanders Exp
# Version 0.93 -- Thu Oct 31 09:42:25 MST 1996
#
# mailprio_mkdb -- make mail priority database based on delay times
#
# Copyright 1994, 1996, Tony Sanders <sanders@earth.com>
# Rights are hereby granted to download, use, modify, sell, copy, and
# redistribute this software so long as the original copyright notice 
# and this list of conditions remain intact and modified versions are
# noted as such.
#
# I would also very much appreciate it if you could send me a copy of
# any changes you make so I can possibly integrate them into my version.
#
# The average function moves the value around quite rapidly (half-steps)
# which may or may not be a feature.  This version uses the new xdelay
# statistic (new as of sendmail V8) which is per transaction.  We also
# weight the result based on the overall delay.
#
# Something that might be worth doing for systems that don't support
# xdelay would be to compute an approximation of the transaction delay
# by sorting by messages-id and delay then computing the difference
# between adjacent delay values.
#
# To get the maximum value from a transaction delay based priority
# function you need to reorder the distribution list (and the mail
# queue files for that matter) fairly often; you could even have
# your mailing list software reorder the list before each outgoing
# message.
X
$usage = "Usage: mailprio_mkdb [-l maillog] [-p priodb]\n";
$home = "/home/sanders/lists";
$maillog = "/var/log/maillog";
$priodb = "$home/mailprio";
X
while ($ARGV[0] =~ /^-/) {
X	$args = shift;
X	if ($args =~ m/\?/) { print $usage; exit 0; }
X	if ($args =~ m/l/) {
X	    $maillog = shift || die $usage, "-l requires argument\n"; }
X	if ($args =~ m/p/) {
X	    $priodb = shift || die $usage, "-p requires argument\n"; }
}
X
$SIG{'PIPE'} = 'handle_pipe';
X
# will merge with existing information
dbmopen(%prio, $priodb, 0644) || die "$priodb: $!\n";
&getlog_stats($maillog, *prio);
dbmclose(%prio);
exit(0);
X
sub handle_pipe {
X    dbmclose(%prio);
}
X
sub getlog_stats {
X    local($maillog, *stats) = @_;
X    local($to, $delay);
X    local($h, $m, $s);
X    open(MAILLOG, "< $maillog") || die "$maillog: $!\n";
X    while (<MAILLOG>) {
X	next unless / to=/ && / stat=/;
X	next if / stat=queued/;
X	if (/ stat=sent/i) {
X	    # read delay and xdelay and convert to seconds
X	    ($delay) = (m/ delay=([^,]*),/);
X	    next unless $delay;
X	    ($h, $m, $s) = split(/:/, $delay);
X	    $delay = ($h * 60 * 60) + ($m * 60) + $s;
X
X	    ($xdelay) = (m/ xdelay=([^,]*),/);
X	    next unless $xdelay;
X	    ($h, $m, $s) = split(/:/, $xdelay);
X	    $xdelay = ($h * 60 * 60) + ($m * 60) + $s;
X
X	    # Now weight the delay factor by the transaction delay (xdelay).
X	    $xdelay /= 300;			# [0 - 1(@5 min)]
X	    $xdelay += 0.5;			# [0.5 - 1.5]
X	    $xdelay = 1.5 if $xdelay > 1.5;	# clamp
X	    $delay *= $xdelay;			# weight delay by xdelay
X	}
X	elsif (/, stat=/) {
X	    # delivery failure of some sort (i.e. bad)
X	    $delay = 432000;		# force 5 days
X	}
X	$delay = 1000000 if $delay > 1000000;
X
X	# filter the address(es); isn't perfect but is "good enough"
X	$to = $_; $to =~ s/^.* to=//;
X	1 while $to =~ s/\([^\(\)]*\)//g;	# strip comments
X	1 while $to =~ s/"[^"]*"//g;		# strip comments
X	$to =~ s/, .*//;			# remove other stat info
X	foreach $addr (&simplify_address($to)) {
X	    next unless $addr;
X	    $addr = &canonicalize($addr);
X	    $stats{$addr} = $delay unless defined $stats{$addr};	# init
X	    # pseudo-average in the new delay (half-steps)
X	    # simple, moving average
X	    $stats{$addr} = int(($stats{$addr} + $delay) / 2);
X	}
X    }
X    close(MAILLOG);
}
X
# REPL-LIB ---------------------------------------------------------------
X
sub canonicalize {
X    local($addr) = @_;
X    # lowercase, strip leading/trailing whitespace
X    $addr =~ y/A-Z/a-z/; $addr =~ s/^\s+//; $addr =~ s/\s+$//; $addr;
}
X
# @addrs = simplify_address($addr);
sub simplify_address {
X    local($_) = shift;
X    1 while s/\([^\(\)]*\)//g; 		# strip comments
X    1 while s/"[^"]*"//g;		# strip comments
X    split(/,/);				# split into parts
X    foreach (@_) {
X	1 while s/.*<(.*)>.*/\1/;
X	s/^\s+//;
X	s/\s+$//;
X    }
X    @_;
}
SHAR_EOF
  $shar_touch -am 1031100396 'mailprio_mkdb' &&
  chmod 0755 'mailprio_mkdb' ||
  echo 'restore of mailprio_mkdb failed'
  shar_count="`wc -c < 'mailprio_mkdb'`"
  test 4182 -eq "$shar_count" ||
    echo "mailprio_mkdb: original size 4182, current size $shar_count"
fi
exit 0