Как прикрутить AvpDaemon к sendmail

Автор: Vadim V Zotov



Хочу сказать сразу, что это было сделано буквально за три часа на коленке. Так, что это руководство к действию, и если у кого-то работать не будет, то просьба не обижаться. Просто конфигурация sendmail штука достаточно сложная и никогда не знаешь точно за какую ниточку ты потянул, после чего все перестало работать.
Руками лопатить sendmail.cf занятие, конечно для общего развития полезное, но неблагодарное, поэтому у кого нет m4, то просьба им обзавестись. В дальнейшем все потуги сконфигурировать sendmail будут производиться исключительно при помощи m4. Так же понадобится компилятор C и текстовый редактор. Более того, желателен некоторый опыт борьбы с sendmail и понимание unix ipc, т.к. объяснять, что такое unix socket или необходимость разделения sendmail relesets табуляциями здесь я не буду. Для того, кто этим заинтересуется могу только порекомендовать две прекрасные книги: В Москве есть организация под названием "Фольком", которая занимается продажей подобной литературы. Связаться с ней можно по этому адресу.
Кроме того не лишне зайти на этот сайт, многие вещи там достаточно подробно описаны.

После того, как лирические отступления закончены, переходим к телу.

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. Теперь по параметрам: В принципе $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