Поставить закладку   сделать стартовой   Написать нам

Использование Milter API в sendmail для дублирования исходящей почты

Введение

  Во многих форумах встречаются треды с вопросом:
 "Как настроить систему копирования почты?"

  Одним из вариантов реализации такой системы - это использование
 sendmail в связке с Procmail, второй это использование дополнительного
 мэйлера на базе самого sendmail.

  Не один из этих методов нельзя однозначно назвать хорошим, так же как 
 и метод который будет описан в этой статье (мнение автора).

  Данная статья не претендует на оригинальность и данный метод не является 
 "ноу-хау", но все же подробные описания двух перечисленных выше методов 
 имеются, а вот использование Milter API как то позабыли.

  Подробнее о Milter API можно почитать на http://www.milter.org

  Итак приступим:

  При тестировании использовалась система FreeBSD 5.1-Relese и sendmail 8.12.9.
  Для реализации фильтра использовался язык Си, ниже приведен
 код фильтра:

 ----cpmail.c----
 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "libmilter/mfapi.h"

#define CP_FROM


//Адрес на который будет дублироваться почта
#define copy_to_addr "user@domain.ru"
//Файл содержащий e-mail адреса, исходящую
//почту с который необходимо дублировать
#define conf_file "/etc/mail/sm_copy_users"
// #define log_file "/var/log/copy_mail.log"

typedef struct 
  {
	char *e_mail;
	struct copy_users *next;
  }
copy_users;

copy_users *cp_users;
int copy;

void get_cp_users()
 {
	char line[256];
	copy_users *tmp, *head;
	FILE *file;
	
	cp_users = malloc(sizeof(copy_users));
	cp_users -> next = NULL; 
	head = cp_users;
	
	 if(!(file = fopen(conf_file,"r")))
	  {
fprintf(stderr,"Can't open config file: %s\nError: %s\n",conf_file, strerror(errno)); 
	   exit(1);
	  }

	  while(fgets(line,sizeof(line),file) != NULL)
  	   {
		//Ignore '#' character
	 	if(strncmp(line, "#", 1) == 0)
		 continue;
		
		cp_users -> e_mail = (char *)malloc(strlen(line));
		bzero(cp_users -> e_mail, strlen(line));
		strncpy(cp_users -> e_mail, line, strlen(line) - 1);

	 	tmp = cp_users;
		cp_users = malloc(sizeof(copy_users));
		cp_users -> next = NULL;
		(copy_users *)tmp -> next = cp_users;

	   }
	 fclose(file);
	
	 cp_users = head;
 }

int user_in(char *e_mail)
 {
	int res = 0;
	copy_users *tmp;

	 tmp = cp_users;
	  while(tmp -> next != NULL)
	   {
		if(strstr(e_mail, tmp -> e_mail) != NULL)
		 {
	
		  res = 1;
		  break;
		 }
		tmp = (copy_users *)tmp -> next;
	   }

	return res;
 }
sfsistat mlfi_header(SMFICTX *ctx, char *headerf, char *headerv)
 {
	return SMFIS_CONTINUE;
 }

sfsistat mlfi_envfrom(SMFICTX *ctx, char **argv)
 {
  #ifdef CP_FROM
     if(user_in(argv[0]))
      {
	copy = 1;
      }
    else 
	copy = 0; 
  #endif 
	return SMFIS_CONTINUE;
 }
sfsistat mlfi_close(SMFICTX *ctx)
 {
   return SMFIS_ACCEPT;
 }

sfsistat mlfi_eom(SMFICTX *ctx)
 {
	if(copy)
	 smfi_addrcpt(ctx, copy_to_addr);

	return SMFIS_CONTINUE;
 }


struct smfiDesc copy_mail_filter =
 {
	"CopyOutgoingMail",	//Filter name
	SMFI_VERSION,	
	SMFIF_ADDRCPT,		// This filter may add recipients
	NULL,			//Connection info filter	
	NULL,			//SMTP HELO command filter
	mlfi_envfrom,		//Message from
	NULL,			//Message To
	mlfi_header,		//Header filter. Neeed for change header.
	NULL,
	NULL,
	mlfi_eom,		//End of message
	NULL,
	mlfi_close
 };

int main(int argc, char *argv[])
 {
	get_cp_users();
	(void) smfi_setconn("inet:999@localhost");
	if (smfi_register(copy_mail_filter) == MI_FAILURE)
	 {
	  fprintf(stderr,"smfi_register_failed\n");
	  exit(1);
	 }
	
	return smfi_main();
 } 


 ---end of cpmail.c--- 

 Компилируется следующим образом:
 
#gcc -Wall -o cpmail cpmail.c /usr/lib/libmilter.a -pthread  

 И запускается:
 
#cd dir
#./cpmail &

 Т.о. наша программа будет слушать 999 порт на который sendmail'ом будет
передаваться необходимая нам информация.

 Для того что бы это дело заработало необходимо в sendmail.mc внести
следующие изменения и пересобрать sendmail.cf:

 INPUT_MAIL_FILTER(`copy_mail_filter', `S=inet:999@localhost')
 define(`confINPUT_MAIL_FILTERS', `copy_mail_filter')

Далее перезапустить sendmail (killall -HUP sendmail).

Вот и все, дублирование исходящей почты работает.

PS/ При желании можно внести небольшие изменения в код фильтра и организовать 
    систему управления почтой "заточенную" под конкретную конфигурацию.
итак есть необходимость читать исходящую почту проходящую через smtp сервер выстроенный на sendmail linux от конкретных пользователей.

сначала читаем /usr/share/doc/sendmail/README.libmilter

на этом этапе можно заканчивать. т.к. тот кто сможет прочитать и осмыслить пример внизу этого README- может смело внедрять его в жизнь. и радоваться результатам.

кому читать и понимать лень, делают следующее:

1) идут сюда
http://freeunix.unicor.ru/content.php?page=%CF%EE%F7%F2%E0&a mp;id=344
файлы в include
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "libmilter/mfapi.h"


2) немножно исправим строку компиляции чтобы получилось в нужном месте и с нужными библиотеками компиляции (т.к. судя по ошибкам libmilter.a- во FreeBSD, для которой написана статья [1], эта библиотека несколько другая)

gcc -Wall -o /usr/bin/cpmail cpmail.c /usr/lib/libmilter.a /usr/lib/libsmutils.a -pthread

NB: по поводу редактирования Sendmail.mc- Если у вас несколько фильтров в sendmail то в строке define они пишутся через запятую
define(`confINPUT_MAIL_FILTERS', `drweb-filter,copy_mail_filter')dnl

3) как видно из текста прог-мы, записи в sm_copy_users должны стоять каждый адрес на отдельной строке, без знаков препинания.

4) чтобы запускать как демон пишем файл /etc/init.d/cpmaild

#!/bin/sh
#
# chkconfig: - 69 39
# description: cpmail is sendmail filter for copy esmtp mail (outgoing mail from domain users)
# processname: /usr/bin/cpmail
# copy userlist: /etc/mail/sm_copy_users

# Source function library.
. /etc/init.d/functions

start() {
if [ -n "`/sbin/pidof cpmail`" ]; then
echo -n $"cpmail is already running..."
return 1
fi
echo -n $"Starting up cpmail filter: "
cpmail &
RETVAL=$?
if [ -z "`/sbin/pidof cpmail`" ]; then
RETVAL=1
fi
[ $RETVAL -ne 0 ] && failure $"cpmail startup"
[ $RETVAL -eq 0 ] && touch /var/lock/subsys/cpmail && success $"cpmail startup"
echo
return $RETVAL
}

stop() {
echo -n $"Shutting down cpmail filter: "
killproc cpmail
RETVAL=$?
[ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/cpmail
echo
return $RETVAL
}

restart() {
stop
[ $RETVAL -eq 0 ] && start
}

cpstatus() {
cpm_pid=`/sbin/pidof cpmail`
if [ -z "$cpm_pid" ]; then
echo "... cpmail not started"
else
echo "cpmail started with pid=$cpm_pid ..."
fi
echo
}

# See how we were called.
case "$1" in
start)
start
;;
stop)
stop
;;
status)
cpstatus
;;
restart)
restart
;;
*)
echo "Usage: $0 {start|stop|status|restart}"
exit 1
esac

exit $RETVAL



затем пишем

chkconfig --add cpmaild

и после этого c ним можно работать как с обычным service- запускать, перезапускать, останавливать (не забывая делать это каждый раз после внесения изменений в список пользователей sm_copy_users)

// для тех, кому интересно как выглядит хедер письма - нигде , ни в копии письма , ни в оригинале у истинного получателя - не будет никаких следов. не будет лишних заголовочных пометок и пр. т.е. с чисто эстетической точки зрения перлюстрация письма нигде в письме не отражается, что в определенной ситуации- "некрасиво". поэтому немного подработав код программы можно заставить ее вставлять в хедер строки типа "X-CopiedMail: this mail was auto copied to user@domain.ru"

Удачи.





HitUA.net
© Copyright 2005 SysAdmin