Как прикрутить AvpDaemon к sendmail
Хочу сказать сразу, что это было сделано буквально за
три часа на коленке. Так, что это руководство к действию, и если у кого-то
работать не будет, то просьба не обижаться. Просто конфигурация sendmail
штука достаточно сложная и никогда не знаешь точно за какую ниточку ты
потянул, после чего все перестало работать.
Руками лопатить sendmail.cf занятие, конечно для общего
развития полезное, но неблагодарное, поэтому у кого нет m4, то просьба
им обзавестись. В дальнейшем все потуги сконфигурировать sendmail будут
производиться исключительно при помощи m4. Так же понадобится компилятор
C и текстовый редактор. Более того, желателен некоторый опыт борьбы с sendmail
и понимание unix ipc, т.к. объяснять, что такое unix socket или необходимость
разделения sendmail relesets табуляциями здесь я не буду. Для того, кто
этим заинтересуется могу только порекомендовать две прекрасные книги:
-
"Sendmail", Bryan Costales and Eric Allman, second ed., 1050pp.,
O'Reilly and Associates, ISBN 1-56592-222-0
-
"Unix Network Programming Volume 1: Networking Apis - Sockets
and XTI", second ed., W.Richard Stevens, 1240 pp., Prentice Hall PTR, ISBN
0-13-490012-X
В Москве есть организация под названием "Фольком", которая
занимается продажей подобной литературы. Связаться с ней можно по этому
адресу.
Кроме того не лишне зайти на этот сайт,
многие вещи там достаточно подробно описаны.
После того, как лирические отступления закончены, переходим
к телу.
1. Мы должны создать свой собственный delivery agent.
Назовем его avp.
В каталоге /usr/src/sendmail/cf/mailer (для RH /usr/lib/sendmail-cf/mailer)
создаем файл под названием avp.m4 следующего содержания:
#
# Copyright (c) 1999 Vadim Zotov. All rights reserved.
#
# By using this file, you agree to the terms and
conditions set
# forth in the LICENSE file which can be found at
the top level of
# the sendmail distribution.
#
#
PUSHDIVERT(-1)
ifdef(`AVP_MAILER_PATH',,
`ifdef(`AVP_PATH',
`define(`AVP_MAILER_PATH', AVP_PATH)',
`define(`AVP_MAILER_PATH', /usr/local/bin/avp-link)')')
ifdef(`AVP_MAILER_FLAGS',,
`define(`AVP_MAILER_FLAGS',
`SPhnu9')')
ifdef(`AVP_MAILER_ARGS',,
`define(`AVP_MAILER_ARGS',
`avp-link soft procmail ${AVP} $h $f $u')')
POPDIVERT
######################*****##############
### AVP Mailer specification
###
##################*****##################
VERSIONID(`@(#)avp.m4
1.0 (Vadim Zotov) 6/18/1999')
Mavp,
P=AVP_MAILER_PATH, F=CONCAT(`DFM', AVP_MAILER_FLAGS), S=0, R=0, T=DNS/RFC822/X-Unix,
ifdef(`AVP_MAILER_MAX', `M=AVP_MAILER_MAX, ')A=AVP_MAILER_ARGS
Из этого файла видно, что наш delivery agent будет называться
avp-link и находиться в каталоге /usr/local/bin. Вызываться он будет с
параметрами: "soft", "procmail", ${AVP}, $h , $f, $u. Теперь по параметрам:
-
"soft" - указывает как будет
себя вести delivery argent при различных ошибках (например если
AvpDaemon не запущен): продолжать доставлять почту или отваливаться с ошибкой,
-
"procmail" - avp будет вызывать procmail вместо sendmail,
-
${AVP} - смотри пункт 3,
-
$h -
host part of recipient address. Для procmail вместо него подставляется
файл сценария,
-
$f -
sender's address,
-
$u -
recpipient's username
В принципе $h имеет значение для ruleset 0 и поэтому его
лучше оставить. Если что не нравится, то потом можно будет все переопределить
(см. ниже).
2. Теперь берем текстовый редактор и набиваем примерно
следующую программу. Дебаги расставляются по желанию. Предупреждаю сразу,
что программа недописана.
1 /*
2
Copyright (c) 1999 Vadim Zotov
3
4 This program is
free software; you can redistribute it and/or modify
5 it under the terms
of the GNU General Public License as published by
6 the Free Software
Foundation; either version 2 of the License, or (at
7 your option) any
later version.
8
9 This program is
distributed in the hope that it will be useful, but
10 WITHOUT ANY WARRANTY;
without even the implied warranty of
11 MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 General Public License
for more details.
13
14 You should have
received a copy of the GNU General Public License
15 along with this
program; if not, write to the Free Software
16 Foundation, Inc.,
675 Mass Ave, Cambridge, MA 02139, USA.
17
18 If you have any
questions you can contact me at <vadim.zotov@zenit.ru>
19
20 */
21 #include <errno.h>
22 #include <limits.h>
23 #include <fcntl.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <stdarg.h>
28 #include <stdio.h>
29 #include <syslog.h>
30 #include <time.h>
31 #include <unistd.h>
32 #include <sys/types.h>
33 #include <sys/socket.h>
34 #include <sys/stat.h>
35 #include <sys/un.h>
36 #include <sys/wait.h>
37
38 /* codes that AvpDaemon
returns */
39
40 #define AVP_OK
0 /* Everything is Ok
*/
41 #define AVP_SCANINCOMPLETE
1 /* Scan was not complete
*/
42 #define AVP_SUSPICIOUS
3 /* Suspicious objects were found
*/
43 #define AVP_VIRUSDETECT
4 /* Known viruses were detected
*/
44 #define AVP_VIRUSDELETE
5 /* All detected viruses were deleted
*/
45 #define AVP_FILECORRUPTED
7 /* File AvpLinux is corrupted
*/
46
47 /* sendmail error
codes */
48
49 #define EX_OK
0 /* successful termination */
50 #define EX_QUIT
22 /* drop out of server immediately */
51 #define EX__BASE
64 /* base value for error messages */
52 #define EX_USAGE
64 /* command line usage error */
53 #define EX_DATAERR
65 /* data format error */
54 #define EX_NOINPUT
66 /* cannot open input */
55 #define EX_NOUSER
67 /* addressee unknown */
56 #define EX_NOHOST
68 /* host name unknown */
57 #define EX_UNAVAILABLE
69 /* service unavailable */
58 #define EX_SOFTWARE
70 /* internal software error */
59 #define EX_OSERR
71 /* system error (e.g., can't fork) */
60 #define EX_OSFILE
72 /* critical OS file missing */
61 #define EX_CANTCREAT
73 /* can't create (user) output file */
62 #define EX_IOERR
74 /* input/output error */
63 #define EX_TEMPFAIL
75 /* temp failure; user is invited to retry
*/
64 #define EX_PROTOCOL
76 /* remote error in protocol */
65 #define EX_NOPERM
77 /* permission denied */
66 #define EX_CONFIG
78 /* configuration error */
67 #define EX__MAX
78 /* maximum listed value */
68
69 const char *USOCK_PATH
= "/var/run/AvpCtl";
70 const char *PROCMAIL_PATH=
"/usr/bin/procmail";
71 const char *SENDMAIL_PATH=
"/usr/sbin/sendmail";
72 const char *version
= "$Id: avp-link.c,v 0.1 1999/06/19 10:55:40
root Exp root $";
73
74 #if defined(DEBUG)
75 #define DBG(arg...)
syslog(LOG_DEBUG,##arg);
76 #else
77 #define DBG(arg...)
;
78 #endif
79
80 int sock;
81 struct sockaddr_un
addr;
82 int pri = 0;
/* do not ask me about this variable. I suspect that this is priority */
83 int soft,procmail;
84
85 char *sender,*recip,*recipient,*postfix,*hostname;
86 char tmpfn[PATH_MAX+1];
87
88
89 void error(const
char *p, ... )
90 {
91
va_list ap;
92
va_start(ap,p);
93
vsyslog(LOG_NOTICE,p,ap);
94
va_end(ap);
95 }
96
97 int conn()
98 {
99
bzero((char *)&addr,sizeof(addr));
100
addr.sun_family = AF_UNIX;
101
strcpy(addr.sun_path,USOCK_PATH);
102
if ( (sock=socket(AF_UNIX,SOCK_STREAM,0)) < 0 )
103
{
104
error("SOCKET(2) ERROR: %s",sys_errlist[errno]);
105
return -1;
106
}
107
if ( connect(sock,(struct sockaddr *)&addr,sizeof(addr.sun_family)+strlen(USOCK_PATH))
< 0 )
108
{
109
error("CONNECT(2) ERROR: %s",sys_errlist[errno]);
110
return -1;
111
}
112
return sock;
113 }
114
115 void deltmp()
116 {
117
unlink(tmpfn);
118 }
119
120 int forward()
121 {
122
int fds;
123
int status;
124
pid_t pid;
125
126
close(0);
127
if ( (fds=open(tmpfn,0)) < 0 )
128
{
129
error("OPEN(2) ERROR: %s",sys_errlist[errno]);
130
return EX_NOINPUT;
131
}
132
if ( fds != 0 && dup2(fds,0) < 0 )
133
{
134
error("DUP2(2) ERROR: %s",sys_errlist[errno]);
135
return EX_NOINPUT;
136
}
137
if ( fds != 0 ) close(fds);
138
139
if ( (pid=fork()) > 0 )
140
{
141
wait(&status);
142
if ( WIFEXITED(status) != 0 ) return WEXITSTATUS(status);
143
else return EX_OSERR;
144
}
145
else if ( pid == 0 )
146
{
147
closelog();
148
if ( procmail == 1 )
149
execle(PROCMAIL_PATH,"procmail","-Y","-m",hostname,sender,recip,NULL,environ);
150
else
151
execle(SENDMAIL_PATH,SENDMAIL_PATH,"-oi","-f",sender,recip,NULL,environ);
152
}
153
else
154
{
155
error("FORK(2) ERROR: %s",sys_errlist[errno]);
156
return EX_OSERR;
157
}
158
return EX_OK;
159 }
160
161 void swarning( const
char *p )
162 {
163
/*
164
Return virus warning to the sender.
165
166
This procedure does not work yet. I am still reading RFC822.
167
*/
168
return;
169 }
170
171 /**************************************************************************************
172
173
Arguments:
174
- $1 - "-soft"/"-hard" what we shall do if AvpDaemon is not running
175
- $2 - delivery agent "-procmail"/"-sendmail"
176
- $3 - address postfix
177
- $4 - host name ($h)
178
- $5 - sender's address relative to recipient ($g)
179
- $6 - recipient's username ($u) in form aa@bb.cc.$2
180
All arguments are mandatory
181
182 **************************************************************************************/
183
184 int main (int argc, char
**argv)
185 {
186
187
int len,c;
188
time_t now;
189
char buf[PATH_MAX+30];
190
FILE *t;
191
192
openlog("avp-link",LOG_PID|LOG_NDELAY,LOG_DAEMON);
193
atexit(closelog);
194
195
/* Check for arguments */
196
197
if ( argc != 7 )
198
{
199
error("Insufficient number of arguments");
200
exit(EX_USAGE);
201
}
202
soft = procmail = 0;
203
if ( strcmp(argv[1],"soft") == 0 ) soft
= 1;
204
if ( strcmp(argv[2],"procmail") == 0 ) procmail = 1;
205
postfix = argv[3];
206
hostname = argv[4];
207
sender = argv[5];
208
recip = argv[6];
209
recipient = (char *)malloc(strlen(recip)+1);
210
strcpy(recipient,recip);
211
recipient[strlen(recip)-strlen(postfix)] = '\0';
212
213
/* Make tempopary file */
214
215
strcpy(tmpfn,tmpnam(NULL));
216
if ( (t=fopen(tmpfn,"w+")) == NULL )
217
{
218
error("Tempopary file creation failure: %s",sys_errlist[errno]);
219
exit(EX_TEMPFAIL); /* we will try later */
220
}
221
atexit(deltmp);
222
while ( (c=fgetc(stdin)) != EOF) putc(c,t);
223
fclose(stdin); close(0);
224
fclose(t);
225
226
/* connect to unix socket, send request for scan and read response */
227
228
if ( conn() < 0 )
229
{
230
if ( soft ) goto SKIP_TEST;
231
else
exit(EX_TEMPFAIL);
232
}
233
sprintf(buf,"<%d>%.15s:%s",pri,ctime(&now),tmpfn);
234
write(sock,buf,strlen(buf));
235
if ( (len = read(sock,buf,0x200)) < 0 )
236
{
237
error("READ(2) ERROR: %s",sys_errlist[errno]);
238
if ( soft ) goto SKIP_TEST;
239
else
exit(EX_SOFTWARE);
240
}
241
buf[len] = 0;
242
close(sock);
243
244
/* now we will analyze error codes */
245
246
switch (atoi(buf))
247
{
248
case AVP_OK:
249
break;
250
case AVP_SCANINCOMPLETE:
251
error("Virus scan was not complete");
252
break;
253
case AVP_SUSPICIOUS:
254
error("Suspicious object was found: sender %s, recipient %s",sender,recipient);
255
swarning("Ssuspicious object were found");
256
break;
257
case AVP_VIRUSDETECT:
258
error("Known viruses were detected: sender %s, recipient %s",sender,recipient);
259
swarning("VIRUS was detected");
260
exit(EX_OK);
261
break;
262
case AVP_VIRUSDELETE:
263
error("All detected viruses were deleted: sender %s, recipient %s",sender,recipient);
264
swarning("virus was detected");
265
break;
266
case AVP_FILECORRUPTED:
267
error("File AvpLinux is corrupted");
268
break;
269
default:
270
error("Unknown error code from AvpDaemon");
271
break;
272
}
273 SKIP_TEST:
274
exit(forward());
275 }
После того, как текст программы готов, можно сходить
перекурить, попить кофейку (или пивка), поприставать к женской половине,
а затем следует ввести две загадочные команды:
$ cc -Wall -D_GNU_SOURCE=1 avp-link.c -o avp-link
$ install -m 755 -s ./avp-link /usr/local/bin/avp-link
3. Заключительным этапом является создание нового файла
конфигурации /etc/sendmail.cf. Опять берем тесктовый редактор
и делаем файл для последующего препроцессинга m4 (например
avp.mc):
include (`../m4/m4.cf')
VESIONID(`linux with avp support smtp-only')dnl
OSTYPE(linux)
. . . .
define(`AVP_MAILER_PATH',`/usr/sbin/avp-link')dnl
как я обещал, показываю пример как переопределить
define(`AVP_MAILER_ARGS',`avp-link soft procmail
${AVP} $h $f $u')dnl маршрут и аргументы
delivery agent
. . . .
FEATURE(always_add_domain) dnl
если есть желание, чтобы локальная почта тоже проверялась
. . . .
MAILER(avp)dnl
. . . .
LOCAL_CONFIG
D{AVP} ANTIVIRUS
C{ANT} ${AVP}
LOCAL_RULE_0
R $* < @ $+ . $~{ANT} . > $*
$# avp $@ /etc/some.rc $: $1 @ $2. $3 . ${AVP} $4
отправить всю почту на avp
R $* < @ $* . ${AVP} . >
$* $1 < @ $2 . > $3
уже проверялось, откатиться назад
LOCAL_RULE_2
R $* @ $+ . ${AVP}
$1 < @ $2 . ${AVP} >
Rewrite hacked address
Для кого непонятно, постараюсь объяснить. Смысл
приведенного здесь куска кода заключается в том, что мы переписываем в
конверте сообщения адрес получателя из recipient<@somwhere.com.>
в recipient@somwhere.com.ANTIVIRUS и передаем этот адрес агенту
avp. Текст сообщения будет передаваться avp-link на стандартный вход. Вместо
имени хоста фигурирует файл /etc/some.rc по причине того, что я
использую procmail для маршрутизации сообщений, а это как раз скрипт к
procmail. После того как avp отработает, он передаст сообщение sendmail
с recipient address recipient@somwhere.com.ANTIVIRUS, после чего
sendmail преобразует этот адрес в начальный вид и будет пытаться доставить
его при помощи первого же подходящего delivery agent.
После того, как файл набрали и сохранили, делаем sendmail.cf:
$ m4 avp.mc > /etc/sendmail.cf
Теперь будем проверять, что мы натворили:
$ sendmail -bt
> 3,0 zotermann@hotmail.com
rewrite: ruleset 3 input:
zotermann @ hotmail . com
rewrite: ruleset 96 input: zotermann
< @ hotmail . com >
rewrite: ruleset 96 returns: zotermann <
@ hotmail . com . >
rewrite: ruleset 3 returns: zotermann
< @ hotmail . com . >
rewrite: ruleset 0 input:
zotermann < @ hotmail . com . >
rewrite: ruleset 196 input: zotermann
< @ hotmail . com . >
rewrite: ruleset 196 returns: zotermann < @ hotmail
. com . >
rewrite: ruleset 98 input: zotermann
< @ hotmail . com . >
rewrite: ruleset 98 returns: $# avp $@ /etc/some
. rc $: zotermann @ hotmail . com . ANTIVIRUS
rewrite: ruleset 0 returns: $# avp $@
/etc/some . rc $: zotermann @ hotmail . com . ANTIVIRUS
> 3,0 zotermann@hotmail.com.ANTIVIRUS
rewrite: ruleset 3 input:
zotermann @ hotmail . com . ANTIVIRUS
rewrite: ruleset 96 input: zotermann
< @ hotmail . com . ANTIVIRUS >
rewrite: ruleset 96 returns: zotermann <
@ hotmail . com . ANTIVIRUS . >
rewrite: ruleset 3 returns: zotermann
< @ hotmail . com . ANTIVIRUS . >
rewrite: ruleset 0 input:
zotermann < @ hotmail . com . ANTIVIRUS . >
rewrite: ruleset 196 input: zotermann
< @ hotmail . com . ANTIVIRUS . >
rewrite: ruleset 196 returns: zotermann < @ hotmail
. com . ANTIVIRUS . >
rewrite: ruleset 98 input: zotermann
< @ hotmail . com . ANTIVIRUS . >
rewrite: ruleset 98 returns: zotermann <
@ hotmail . com . >
rewrite: ruleset 195 input: zotermann
< @ hotmail . com . >
rewrite: ruleset 90 input: <
hotmail . com > zotermann < @ hotmail . com . >
rewrite: ruleset 90 input: hotmail
. < com > zotermann < @ hotmail . com . >
rewrite: ruleset 90 returns: zotermann <
@ hotmail . com . >
rewrite: ruleset 90 returns: zotermann <
@ hotmail . com . >
rewrite: ruleset 95 input: <
> zotermann < @ hotmail . com . >
rewrite: ruleset 95 returns: zotermann <
@ hotmail . com . >
rewrite: ruleset 195 returns: $# esmtp $@ hotmail
. com . $: zotermann < @ hotmail . com . >
rewrite: ruleset 0 returns: $# esmtp
$@ hotmail . com . $: zotermann < @ hotmail . com . >
> ^D
ЗАЕ..СЬ ! РАБОТАЕТ !
4. запускаем AvpDaemon. Соответствующий скрипт для его
запуска каждый может придумать сам. У меня лично запускается так:
$ /usr/local/avp/AvpDaemon -- -m3
Если вдруг он не запускается, то плакать не стоит - попробуйте
еще раз. Он вообще запускается через раз. Здесь правда есть одна неприятность
в виде кривого имени файла с pid процесса, но мне кажется что разработчики
с этим справятся. Да, еще почему-то socket находится в /var/run, но на
самом деле это все фигня.
5. Перестартовываем sendmail
$ kill -HUP `head -1 /var/run/sendmail.pid`
Вывод: sendmail это конечно ужасная программа, но при
наличии некоторого опыта и желания ее всегда можно поставить раком !
Вот собственно и все. Наслаждайтесь жизнью ! Если вдруг
возникнут проблемы пишите мне сюда,
постараюсь ответить (хотя и не гарантирую), только просьба с дурными вопросами
не приставать, все равно отвечать не буду !
ZotermaNN