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/[email protected]/[email protected]"
        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_p...@$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-dae...@%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;
};
-- 
## List details at http://lists.exim.org/mailman/listinfo/exim-users 
## Exim details at http://www.exim.org/
## Please use the Wiki with this list - http://wiki.exim.org/

Reply via email to