Le samedi 27 juin 2009 à 15:28:34, Dmitry Samersoff, vous avez écrit :
> David,
>
> I think SQL for graylist is totally overkill.
[...]
I used 'greylistd' python script ( using Exim socket ), then decided to
reimplement its in purely within Exim ${dlfunc.
In RCPT or DATA :
defer condition = ${if ...}{1}{0}}
set acl_m_gris = ${dlfunc{/usr/lib/exim/exim-gris.so}{gris}}
condition = ${if >{$acl_m_gris}{0}}
message = Greylisting, please try again later
($acl_m_gris secondes)
This ${dlfunc use Exim functions for accessing Exim's hints database. This
module does not contain code for reading DBM files, all the relevant
functions are called as Exim macros (exim-4.69/src/dbstuff.h,
exim-4.69/src/dbfunctions.h). The DBM file has stored in Exim
spool_directory/db/greylisting
For maintaining greylisting database, this function auto remove obsolete data
(macro LAST_UPDATE 60*15).
To dump out the contents of the database:
% exim -be '${dlfunc{/usr/lib/exim/exim-gris.so}{dump}}'
#
# Greylisting Sat Jun 27 18:45:57 2009
#
{
key = "131.111.8/exim-users@???/recipient@???"
flag = W
last = Thu Jun 25 13:29:04 2009
first = Thu Jun 25 13:19:54 2009
count = 1
}
[...]
# Total: 83 (White: 57|Grey: 25)
% gcc -O2 -Wall -Werror -shared -fPIC -g \
-I./exim-4.69/build-Linux-i386/ \
-L/usr/lib \
-DLAST_UPDATE=60*15 \
-DMAX_GREY=60*60*8 \
-DRETRY_MAX=60*60*24*36 \
-DRETRY_MIN=60*5 \
-o /usr/lib/exim/exim-gris.so exim-gris.c \
&& strip /usr/lib/exim/exim-gris.so
-- I'm French. Sorry for my poor English.
--
Serge Demonchaux
/*
* Copyright (C) 2009 by Serge Demonchaux
* <serge(DOT)d3(AT)free(DOT)fr>
* $Id: exim-gris.c, v0.0.1 Jun 2009
*/
/**
- Build :
% gcc -O2 -Wall -Werror -shared -fPIC -g \
-I./exim-4.69/build-* \
-L/usr/lib \
-DLAST_UPDATE=60*15 \
-DMAX_GREY=60*60*8 \
-DRETRY_MAX=60*60*24*36 \
-DRETRY_MIN=60*5 \
-o /usr/lib/exim/exim-gris.so exim-gris.c \
&& strip /usr/lib/exim/exim-gris.so
- In RCPT or DATA :
defer condition = ${if ...}{1}{0}}
set acl_m_gris = ${dlfunc{/usr/lib/exim/exim-gris.so}{gris}}
condition = ${if >{$acl_m_gris}{0}}
message = Greylisting, please try again later
($acl_m_gris secondes)
- To dump out the contents of the database :
% exim -be '${dlfunc{/usr/lib/exim/exim-gris.so}{dump}}'
- Or :
% perl -e 'use DB_File;tie my %h, "DB_File", $ARGV[0], 0, 0, $DB_HASH;print "total: ".keys(%h)."\n";untie %h;' /var/spool/exim/db/greylisting
*/
#include <string.h>
#include <errno.h>
#include <time.h>
#include <fcntl.h>
/* Exim */
#include "local_scan.h"
#include "dbstuff.h"
#include "dbfunctions.h"
#define K_LAST_UPDATE "LAST_UPDATE" /* Nom de la clé du prochain */
/* temps de nétoyage */
#ifndef LAST_UPDATE
# define LAST_UPDATE 60*15 /* Interval de l'auto nétoyage */
#endif
#ifndef MAX_GREY
# define MAX_GREY 60*60*8 /* Temps max à l'état Grey avant */
#endif /* expiration */
#ifndef RETRY_MAX
# define RETRY_MAX 60*60*24*36 /* Temps max à l'état White avant */
#endif /* expiration */
#ifndef RETRY_MIN
# define RETRY_MIN 60*3 /* Temps iniciale du mécanisme */
#endif /* de grelistage */
#define DEBUG(x) if ((debug_selector & (x)) != 0)
/* Structure des données enregistées
* dans la base de données.
* ( dans l'ordre ).
* Cette structure reprend les consignes sur l'utilisation
* des db avec Exim -> dbfunctions.h + dbstuff.h
*/
typedef struct {
time_t time_stamp; /* timestamp dernier passage */
/*************/
time_t first; /* timestamp enregistrement */
unsigned int flag; /* drapeau 'W' ou 'G' ( le drapeu
* 'A' est réservé pour les clés
* d'administration du programme */
unsigned int count; /* Compteur des passages */
} GREY_CTX;
/* --- global --- */
/* temps des différents états */
static int retry_max = RETRY_MAX;
static int retry_min = RETRY_MIN;
static int max_grey = MAX_GREY;
/* db */
open_db dbblock, *dbm;
GREY_CTX *dbd;
int __greylisting_fn( unsigned char *recipient, char *relay_ip );
int __traverse_fn( int dump );
/* dump le contenu de la db dans un format humain
* de cette manière:
* exim -be '${dlfunc{/usr/lib/exim/exim-gris.so}{dump}}' > dump.txt
*/
int dump(void) {
if ( ( dbm = dbfn_open(
US"greylisting", O_RDWR, &dbblock, TRUE ) ) == NULL ) {
DEBUG(D_local_scan)
fprintf( stderr, "greylisting database not available!\n");
return -1;
};
/* Traverse la db et dump sur STDERR */
if ( __traverse_fn( 1 ) != 0 )
goto err;
(void) dbfn_close( dbm );
return 0;
err: (void) dbfn_close( dbm );
return -1;
}
/* defer condition = ${if ...}{1}{0}}
* set acl_m_gris = ${dlfunc{/usr/lib/exim/exim-gris.so}{gris}}
* condition = ${if >{$acl_m_gris}{0}}
* message = Greylisting, please try again later \
* ($acl_m_gris secondes)
*/
int gris( unsigned char **yield, int argc, unsigned char *argv[] ) {
char ipaddr[16];
char *mask;
int delay = 0;
/* ------ greylisting ------- */
if ( ( dbm = dbfn_open(
US"greylisting", O_RDWR, &dbblock, TRUE ) ) == NULL ) {
DEBUG(D_local_scan)
debug_printf("greylisting database not available!\n");
return OK;
};
DEBUG(D_local_scan) debug_printf("Using %s\n", EXIM_DBTYPE);
/* --- construction de la requête ---
* Elle prend la forme:
* ip/sender/recipient.
* L'ip est elle tronquée au dernier point,
* par exemple : 192.168.0.10 est transformée en 192.168.0
*/
strncpy( ipaddr, (sender_host_address)?
(char*)sender_host_address : "127.0.0.1", sizeof( ipaddr ) );
mask = ( strrchr( ipaddr, '.') ) ?
strrchr( ipaddr, '.') :
strrchr( ipaddr, ':');
ipaddr[(int)(mask - ipaddr)] = '\0';
/* acl_smtp_data */
if ( expand_string(US"${if def:recipients}") != NULL ) {
int i, ret;
for ( i = 0; i < recipients_count; i++ ) {
ret = __greylisting_fn( recipients_list[i].address, ipaddr );
if ( ret > 0 )
delay = ret;
};
} else {/* acl_smtp_rcpt */
unsigned char *recipient;
recipient = expand_string(US"$local_part@$domain");
delay = __greylisting_fn( recipient, ipaddr );
};
(void) __traverse_fn( 0 );
(void) dbfn_close( dbm );
*yield = string_sprintf( "%d", delay );
return OK;
}
/* clean db */
int __traverse_fn( int dump ) {
EXIM_CURSOR *cursor;
unsigned int total = 0, deleted = 0, white = 0, grey = 0;
unsigned char *key;
time_t now;
int r;
now = time( (time_t)NULL );
key = (unsigned char*)K_LAST_UPDATE;
if ( ( dbd = dbfn_read( dbm, key ) ) == NULL ) {
dbd = store_get( sizeof( GREY_CTX ) );
dbd->first = dbd->time_stamp = now;
dbd->count = 0;
dbd->flag = 'A';
};
if ( dump == 0 && dbd->time_stamp + LAST_UPDATE > now )
return 0;
dbd->count += 1;
if ( dbfn_write( dbm, key, dbd, sizeof(GREY_CTX) ) != 0 ) {
DEBUG(D_local_scan)
debug_printf("Failed: %s\n", strerror(errno));
return -1;
};
if ( dump == 1 )
fprintf( stderr, "#\n# Greylisting %s#\n", ctime(&now) );
key = dbfn_scan( dbm, TRUE, &cursor);
while ( key != NULL ) {
dbd = dbfn_read( dbm, key );
switch ( dbd->flag ) {
case 'G': /* Grey */
r = ( dbd->time_stamp + max_grey < now )? 1 : 0;
break;
case 'W': /* White */
r = ( dbd->time_stamp + retry_max < now )? 1 : 0;
break;
case 'A': /* Admin key */
r = 0;
break;
default: /* Inconnu */
r = 1;
};
if ( r != 0 ) {
(void) dbfn_delete( dbm, key );
++deleted;
};
if ( dump == 1 && r == 0 ) {
white += (dbd->flag == 'W')? 1 : 0;
grey += (dbd->flag == 'G')? 1 : 0;
fprintf( stderr, "{\nkey = \"%s\"\n", key );
fprintf( stderr, "\tflag = %c\n", dbd->flag );
fprintf( stderr, "\tlast = %s", ctime(&dbd->time_stamp));
fprintf( stderr, "\tfirst = %s", ctime(&dbd->first));
fprintf( stderr, "\tcount = %u\n}\n", dbd->count );
};
++total;
key = dbfn_scan( dbm, FALSE, &cursor );
};
DEBUG(D_local_scan)
debug_printf("total: %u deleted %u/%u (White: %u|Grey: %u)\n", total-deleted, deleted, total, white, grey );
if ( dump == 1 )
fprintf( stderr, "\n# Total: %u (White: %u|Grey: %u)", total-deleted, white, grey);
return 0;
}
int __greylisting_fn( unsigned char *recipient, char *relay_ip ) {
unsigned char query[256];
time_t now;
int delay;
int r;
now = time( (time_t)NULL );
/* key: 'ip/sender/recipient' */
if ( sender_address != NULL ) {
(void) snprintf( (char*)query, sizeof( query ),
"%s/%s/%s",
relay_ip,
sender_address,
recipient );
} else {
(void) snprintf( (char*)query, sizeof( query ),
"%s/MAILER-DAEMON@%s/%s",
relay_ip,
sender_host_name,
recipient );
};
dbd = dbfn_read( dbm, query );
/* Validation des informations récupérées directement
* dans la structure GREY_CTX de 'g'
*/
if ( dbd == NULL ) {/* initializing new key's data */
dbd = store_get( sizeof( GREY_CTX ) );
r = -1;
} else if ( dbd->flag == 'W' ) {
r = (dbd->time_stamp + retry_max < now)? -1 : 0;
} else {//if ( dbd->flag == 'G' ) {
r = (dbd->first + retry_min > now)? 1 ://encore grey
(dbd->time_stamp + max_grey < now)? -1 : 0;//expiré
};
switch ( r ) {
case 0:
dbd->flag = 'W';
delay = 0;
break;
case 1:
dbd->flag = 'G';
delay = (dbd->first + retry_min) - now;
break;
default:
dbd->time_stamp = dbd->first = now;
dbd->flag = 'G';
dbd->count = 0;
delay = retry_min;
DEBUG(D_local_scan)
debug_printf("greylisting initializing new key's\n");
};
/* compteur */
dbd->count += 1;
if ( dbfn_write( dbm, query, dbd, sizeof(GREY_CTX)) != 0 ) {
DEBUG(D_local_scan)
debug_printf("Failed: %s\n", strerror(errno));
} else {
DEBUG(D_local_scan)
debug_printf("greylisting db updated\n");
};
if ( delay > 0 )
log_write(0, LOG_MAIN, "${dlfunc greylisting "
"relay_ip=%s sender=<%s> recipient=<%s> delay=%dsecs",
relay_ip,
(sender_address)? (char*)sender_address : "MAILER-DAEMON",
recipient, delay );
return delay;
};