aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/multidrop
blob: 37f87b4c6d815751680a8dbe7f504967a6f47c95 (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
From mlievaart@orion.nl  Mon Jan 10 10:46:33 2000
From: Martijn Lievaart <mlievaart@orion.nl>
To: Eric S. Raymond <esr@thyrsus.com>
Date: zondag 9 januari 2000 0:38
Subject: Re: Thanks for fetchmail and a solution to the multidrop problem (I
Status: O
Content-Length: 8086
Lines: 226

think)

Hello Eric,

Let me first state that I'm no sendmail nor unix guru, so although this
seems to work, I certainly would not say this is the "best" solution. In
fact I would welcome all comments to make this better. In particular, it
seems that that the mailertable feature was made just for this, but I'm
still studying that.

Also, This mail will have lines wrapped. I will put up this on a website
asap, so people can download the relevant portions. In the meantime, I'm
using (stuck on) Outlook, so I won't even attempt to format this mail.
Accept my apoligies and try to mentally reconnect the lines.

Finally, this mail is a bit lengthy, but I guess it is better to get all
information in, so please bear with me.

After some very frustrating attempts to get multidrop to work reliably, it
suddenly hit me. When sendmail has translated the recipient to the mailbox,
the recipient is gone (in the cases we're talking about). So the solution is
not to let sendmail do this translation (completely).

The trick is to let a custom MDA be called with both the mailbox and the
full recipient name. This MDA then just stuffs it in the correct mailbox
after adding the appropriate headers. Luckily I hit on the formail utility.
It reformats a mailmessage and does just what I wanted. Specifically my
script uses it to:
- add a custom header (default: "Delivered-To:") with the recipient
- rewrite the message-ID, so fetchmail will download the same message
multiple times.
- add another header, just for fun.

The rewriting of the message-ID is needed because fetchmail will suppress
multiple messages with the same ID, normally a good idea, but now it gets in
the way. A switch on fetchmail to suppress this behaviour would be great.

At first I hardcoded the domains in the sendmail.cf, but I quickly set out
to do one better and came up with the following solution. In sendmail.cf,
add the following line somewhere at the top.

Kmultidroptable hash -o /etc/mail/multidroptable

this defines a table for all domains we want to use multidrop for. The
format of this file is multiple lines of the format:
<domain>    <mailbox>

e.g:
mailtest.orion.nl       mailtest
mailtest2.orion.nl      mailtest
mailtest3.orion.nl      mailtest
bvh-communicatie.nl     b.bvh
krakatau.nl             b.bvh
personeelzaak.nl        b.bvh
maslowassociates.nl     b.bvh
rtij.nl                 rtij

Of course, create a .db file with makemap. Also, the domains must be added
to class w, so they should be added to your sendmail.cw or RelayTo file, or
whatever you use.

Now add to sendmail.cf:

R$+ < @ $* . >                          $: <MULTIDROP> $(multidroptable $2
$: <NO> $) <?> $1 < @ $2 . >
R<MULTIDROP> <NO> <?> $*                $: $1
R<MULTIDROP> $+ <?> $+ < @ $* . >       $#drop $@ $2 @ $3 $: $1

These lines should be above the existing lines that read:

# short circuit local delivery so forwarded email works
R$=L < @ $=w . >        $#local $: @ $1         special local names
R$+ < @ $=w . >         $#local $: $1                   regular local name

This works as follows (in fact these comments are above my modification in
our sendmail.cf).
#
# MLI. Any drop host gets passed to the drop script
#
# The first rule looks up the domain in the multidrop table.
# The input at this point is always:
#       user@<dom.ain.>
#  If found, the resulting line looks like this:
#       <MULTIDROP> mailbox <?> user@<dom.ain.>
# if not found, the resulting line will be:
#       <MULTIDROP> <NO> <?> user@<dom.ain.>
# The second line restores the "not found" case back to user@<dom.ain.>
# So if this domain was found in the multidroptable, we still have a line
starting with <MULTIDROP>
# as shown above. The third line hands this to the drop script.
#
# Note that the user ($:) is the mailbox this message should be stuffed in,
the host ($@) is the full
# user@<dom.ain>. This is how the dropscript expects it.
#

I guess sendmail guru's are now laughing their pants off, and I hope someone
will show me a better way to achieve this. For now, it works.

Next, we need to define mailer drop (somewhere in the sendmail.cf)

#
# multidrop pop3 support.
#

Mdrop,          P=/usr/local/bin/dropmail, F=lFS,
                T=X-Unix,
                A=dropmail $u $h

The S flag here is crucial, otherwise the dropmail script won't run as root,
and under linux (==bash) suid scripts are not permited. I gather most unices
now disalow suid scripts, so this would be necessary on most unices. There
probably are other flags that would make this better, but this works, so I
decided to divert my attention to other tasks at hand (busy, busy, busy....
;^>).

Now we only need the dropmail script, /usr/local/bin/dropmail, mode 700. It
looks big, but effectively one pipeline does the real work. The rest is
configuration, error checking and locking the mailbox.

#!/bin/bash

#
# Script to force a mail message in a format that fetchmail will recognise.
# use as a MDA from sendmail. Must be executed with F=S.
#

#
# Configuration:
#
maildir=/var/spool/mail
envelope=Delivered-To:

#
# set PATH to a known value to avoid some security issues
#
export PATH=/bin:/usr/bin

#
#
#
to=$2
user=$1
mbox=$maildir/$user

#
# If the mailbox does not exist, create it. Note that we act pretty
paranoid, this is hopefully
# resistant to symlink attacks
#
if [ ! -f $mbox ]
then
        oldumask=`umask`
        umask 077
        touch $mbox
        chmod 660 $mbox || exit 1
        chown $user $mbox || exit 1
        chgrp mail $mbox || exit 1
        umask $oldumask
fi

# First lock the mailbox, if this doesn't succeed in 64 seconds, give up and
send
# mail to postmaster.
# If this period is to short, increase the retries (-r flag to lockfile)
#
# Then run the message through formail to get it into the right mailbox
format with the
# right headers added.
#
# Delivered-To will make fetchmail propagate this mail to the correct user
when
# run with '-E "Delivered-To"'. Set this in the advanced settings of the
TeamInternet f.i.
# (if you changed the envelope at the start of this script, adapt this
accordingly)
#
# We also muck up the messageid, so fetchmail will never skip a message on
the basis of
# duplicate messageIDs. The -i "Message-ID" will rename the old message ID,
the -a will
# add a new one.
#
# Lastly, we add a header indicating which host did the rewriting.
#

if lockfile -r 8 $mbox.lock >/dev/null 2>&1
then
        cat - | formail -i "$envelope <$to>" -i "Message-ID:" -a
"Message-ID:" -i "X-Multidrop-Processing: <`hostname`>" >>$mbox
        rm -f $mbox.lock
else
        (echo "Subject: Cannot lock mailbox for $user" & cat -) |
/usr/lib/sendmail postmaster
fi

#
# EOF
#

This obviously is very Linux (even RedHat?) dependant, locking mailboxes,
creating mailboxes with the right permissions, probably even bash dependent.
I would say that it should be fairly easy to port to other systems, but
alas, my unix knowledge is lacking for that. I'll also rewrite it someday,
a.o. that umask handling can be done much better and the location of the
sendmail binairy should not be fixed.

Now the only thing left to do is to retrieve the mail with fetchmail, using
'envelope "Delivered-To:"' in the poll line. The above script has added this
line, so this is all that fetchmail needs.

All parts of this solution need carefull examination. In particular I think
the new rule lines may not catch all cases, although they worked for
everything I threw at them and work satisfactorily in production. I'm also
wondering if there is a more standard way to drop something in a mailbox. I
yet have to investigate procmail, but all other MDA's mucked with the
message and effectively undid my carefully added header. I'll experiment
some more and rethink it all as I learn more.

I'm still wondering, if I can get formail to include another received
line.... "Received from localhost by dropmail for <user>...." to make it
work without the envelope flag. Well I'll have to experiment. Do you know if
there is a header I can add so fetchmail works out-of-the-box?

Regards,
Martijn Lievaart
an class="nb">echo $1 | sed 's/-t=//'` shift continue;; -b=*) transformbasename=`echo $1 | sed 's/-b=//'` shift continue;; *) if [ x"$src" = x ] then src=$1 else # this colon is to work around a 386BSD /bin/sh bug : dst=$1 fi shift continue;; esac done if [ x"$src" = x ] then echo "install: no input file specified" exit 1 else true fi if [ x"$dir_arg" != x ]; then dst=$src src="" if [ -d $dst ]; then instcmd=: else instcmd=mkdir fi else # Waiting for this to be detected by the "$instcmd $src $dsttmp" command # might cause directories to be created, which would be especially bad # if $src (and thus $dsttmp) contains '*'. if [ -f $src -o -d $src ] then true else echo "install: $src does not exist" exit 1 fi if [ x"$dst" = x ] then echo "install: no destination specified" exit 1 else true fi # If destination is a directory, append the input filename; if your system # does not like double slashes in filenames, you may need to add some logic if [ -d $dst ] then dst="$dst"/`basename $src` else true fi fi ## this sed command emulates the dirname command dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` # Make sure that the destination directory exists. # this part is taken from Noah Friedman's mkinstalldirs script # Skip lots of stat calls in the usual case. if [ ! -d "$dstdir" ]; then defaultIFS=' ' IFS="${IFS-${defaultIFS}}" oIFS="${IFS}" # Some sh's can't handle IFS=/ for some reason. IFS='%' set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` IFS="${oIFS}" pathcomp='' while [ $# -ne 0 ] ; do pathcomp="${pathcomp}${1}" shift if [ ! -d "${pathcomp}" ] ; then $mkdirprog "${pathcomp}" else true fi pathcomp="${pathcomp}/" done fi if [ x"$dir_arg" != x ] then $doit $instcmd $dst && if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi else # If we're going to rename the final executable, determine the name now. if [ x"$transformarg" = x ] then dstfile=`basename $dst` else dstfile=`basename $dst $transformbasename | sed $transformarg`$transformbasename fi # don't allow the sed command to completely eliminate the filename if [ x"$dstfile" = x ] then dstfile=`basename $dst` else true fi # Make a temp file name in the proper directory. dsttmp=$dstdir/#inst.$$# # Move or copy the file name to the temp name $doit $instcmd $src $dsttmp && trap "rm -f ${dsttmp}" 0 && # and set any options; do chmod last to preserve setuid bits # If any of these fail, we abort the whole thing. If we want to # ignore errors from any of these, just make sure not to ignore # errors from the above "$doit $instcmd $src $dsttmp" command. if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && # Now rename the file to the real destination. $doit $rmcmd -f $dstdir/$dstfile && $doit $mvcmd $dsttmp $dstdir/$dstfile fi && exit 0