Хочу сказать сразу, что это было сделано буквально за
три часа на коленке. Так, что это руководство к действию, и если у кого-то
работать не будет, то просьба не обижаться. Просто конфигурация 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)
Из этого файла видно, что наш 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 } После того, как текст программы готов, можно сходить
перекурить, попить кофейку (или пивка), поприставать к женской половине,
а затем следует ввести две загадочные команды:
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:
4. запускаем AvpDaemon. Соответствующий скрипт для его
запуска каждый может придумать сам. У меня лично запускается так:
$ /usr/local/avp/AvpDaemon -- -m3
Если вдруг он не запускается, то плакать не стоит - попробуйте
еще раз. Он вообще запускается через раз. Здесь правда есть одна неприятность
в виде кривого имени файла с pid процесса, но мне кажется что разработчики
с этим справятся. Да, еще почему-то socket находится в /var/run, но на
самом деле это все фигня.
5. Перестартовываем sendmail
$ kill -HUP `head -1 /var/run/sendmail.pid`
Вывод: sendmail это конечно ужасная программа, но при
наличии некоторого опыта и желания ее всегда можно поставить раком !
Вот собственно и все. Наслаждайтесь жизнью ! Если вдруг
возникнут проблемы пишите мне сюда,
постараюсь ответить (хотя и не гарантирую), только просьба с дурными вопросами
не приставать, все равно отвечать не буду !