diff -U4 -r exim-4.62/doc/spec.txt exim-4.62+helo-cache/doc/spec.txt --- exim-4.62/doc/spec.txt 2006-04-28 11:32:23.000000000 +0100 +++ exim-4.62+helo-cache/doc/spec.txt 2006-07-19 23:03:40.000000000 +0100 @@ -7854,8 +7854,24 @@ expansion, for those headers that contain lists of addresses, a comma is also inserted at the junctions between lines. This does not happen for the rheader expansion. +${helocache{}{}} + + This function provides access to the HELO/EHLO cache data. + + For "count": + Returns a count of unique HELO/EHLO names for the duration specified in . + Specify "0" to get a count of all the names stored. + + For "name": + Returns the HELO/EHLO name with ID , numbered from 1 and in reverse + chronological order. + + For "time": + Returns the timestamp of the last use of the HELO/EHLO name with ID , + numbered from 1 and in reverse chronological order. + ${hmac{}{}{}} This function uses cryptographic hashing (either MD5 or SHA-1) to convert a shared secret and some text into a message authentication code, as @@ -11906,8 +11922,29 @@ helo_allow_chars = _ Note that the value is one string, not a list. ++-------------------------------------------------+ +|helo_cache|Use: main|Type: boolean|Default: false| ++-------------------------------------------------+ + +Enables or disables caching of HELO or EHLO names used per IP, making the +data available using ${helocache...}. + ++-------------------------------------------------+ +|helo_cache_max|Use: main|Type: integer|Default: 4| ++-------------------------------------------------+ + +Sets the maximum number of HELO or EHLO names to cache per IP. + ++------------------------------------------------+ +|helo_cache_time|Use: main|Type: time|Default: 1d| ++------------------------------------------------+ + +Sets the maximum time any specific HELO or EHLO name is cached per IP. + +Note that whole records older than this are not automatically removed. + +-----------------------------------------------------------------+ |helo_lookup_domains|Use: main|Type: domain list*|Default: "@:@[]"| +-----------------------------------------------------------------+ diff -U4 -r exim-4.62/src/dbstuff.h exim-4.62+helo-cache/src/dbstuff.h --- exim-4.62/src/dbstuff.h 2006-04-28 11:32:22.000000000 +0100 +++ exim-4.62+helo-cache/src/dbstuff.h 2006-07-19 16:52:19.000000000 +0100 @@ -641,5 +641,22 @@ double rate; /* Smoothed sending rate at that time */ } dbdata_ratelimit; +/* This structure records how hosts identify themselves by HELO/EHLO. The key +is the IP address of the connecting host. */ + +typedef struct { + time_t time_stamp; + /*************/ + uschar name[0]; +} dbdata_helo_cache_entry; + +typedef struct { + time_t time_stamp; + /*************/ + int count; /* Count of the helo names stored */ + dbdata_helo_cache_entry data[0]; +} dbdata_helo_cache; + /* End of dbstuff.h */ + diff -U4 -r exim-4.62/src/exim_dbutil.c exim-4.62+helo-cache/src/exim_dbutil.c --- exim-4.62/src/exim_dbutil.c 2006-04-28 11:32:22.000000000 +0100 +++ exim-4.62+helo-cache/src/exim_dbutil.c 2006-07-22 18:27:43.000000000 +0100 @@ -67,8 +67,9 @@ #define type_wait 2 #define type_misc 3 #define type_callout 4 #define type_ratelimit 5 +#define type_helocache 6 @@ -119,9 +120,9 @@ static void usage(uschar *name, uschar *options) { printf("Usage: exim_%s%s \n", name, options); -printf(" = retry | misc | wait- | callout | ratelimit\n"); +printf(" = retry | misc | wait- | callout | ratelimit | helocache\n"); exit(1); } @@ -142,8 +143,9 @@ if (Ustrcmp(argv[2], "misc") == 0) return type_misc; if (Ustrncmp(argv[2], "wait-", 5) == 0) return type_wait; if (Ustrcmp(argv[2], "callout") == 0) return type_callout; if (Ustrcmp(argv[2], "ratelimit") == 0) return type_ratelimit; + if (Ustrcmp(argv[2], "helocache") == 0) return type_helocache; } usage(name, options); return -1; /* Never obeyed */ } @@ -544,8 +546,9 @@ dbdata_retry *retry; dbdata_wait *wait; dbdata_callout_cache *callout; dbdata_ratelimit *ratelimit; + dbdata_helo_cache *helocache; int count_bad = 0; int i, length; uschar *t; uschar name[MESSAGE_ID_LENGTH + 1]; @@ -679,8 +682,35 @@ print_time(ratelimit->time_stamp), ratelimit->time_usec, ratelimit->rate, keybuffer); break; + + case type_helocache: + helocache = (dbdata_helo_cache *)value; + { + dbdata_helo_cache_entry *entry = helocache->data; + int count_min = 0, count_hour = 0, count_day = 0; + + for (i = 0; i < helocache->count; i++) + { + if (helocache->time_stamp - entry->time_stamp <= 60) count_min++; + if (helocache->time_stamp - entry->time_stamp <= 3600) count_hour++; + if (helocache->time_stamp - entry->time_stamp <= 86400) count_day++; + + /* Move to next entry */ + entry = (dbdata_helo_cache_entry*)((void*)entry + + sizeof(dbdata_helo_cache_entry) + sizeof(uschar) * (Ustrlen(entry->name) + 1)); + } + + printf("%s %s entries=%d min=%d hour=%d day=%d\n", + print_time(helocache->time_stamp), + keybuffer, + helocache->count, + count_min, + count_hour, + count_day); + } + break; } store_reset(value); } key = dbfn_scan(dbm, FALSE, &cursor); diff -U4 -r exim-4.62/src/expand.c exim-4.62+helo-cache/src/expand.c --- exim-4.62/src/expand.c 2006-04-28 11:32:22.000000000 +0100 +++ exim-4.62+helo-cache/src/expand.c 2006-07-22 12:05:38.000000000 +0100 @@ -50,8 +50,9 @@ static uschar *item_table[] = { US"dlfunc", US"extract", US"hash", + US"helocache", US"hmac", US"if", US"length", US"lookup", @@ -69,8 +70,9 @@ enum { EITEM_DLFUNC, EITEM_EXTRACT, EITEM_HASH, + EITEM_HELOCACHE, EITEM_HMAC, EITEM_IF, EITEM_LENGTH, EITEM_LOOKUP, @@ -4488,8 +4490,114 @@ goto EXPAND_FAILED; } } #endif /* EXPAND_DLFUNC */ + + /* Handle HELO cache checking */ + case EITEM_HELOCACHE: + { + uschar *sub[2]; + + switch (read_subs(sub, 2, 2, &s, skipping, TRUE, name)) + { + case 1: goto EXPAND_FAILED_CURLY; + case 2: goto EXPAND_FAILED; + } + + if (Ustrcmp(sub[0], "count") == 0) + { + int n = readconf_readtime(sub[1], 0, FALSE); + if (sub[1][0] == '0' && sub[1][1] == '\0') n = 0; + if (n < 0) + { + expand_string_message = string_sprintf("string \"%s\" is not an " + "Exim time interval in \"%s\" operator", sub[1], name); + goto EXPAND_FAILED; + } + else if (n == 0) + { + sprintf(CS var_buffer, "%d", helo_cache_count); + yield = string_cat(yield, &size, &ptr, var_buffer, Ustrlen(var_buffer)); + } + else + { + helo_cache_record_t *tmp = helo_cache_records; + time_t now = time(NULL); + int count = 0; + + while (tmp != NULL) + { + if (now - tmp->time <= n) count++; + tmp = tmp->next; + } + + sprintf(CS var_buffer, "%d", count); + yield = string_cat(yield, &size, &ptr, var_buffer, Ustrlen(var_buffer)); + } + } + else if (Ustrcmp(sub[0], "name") == 0) + { + int n; + uschar *t = read_number(&n, sub[1]); + if (*t != 0 || n < 1) + { + expand_string_message = string_sprintf("string \"%s\" is not a " + "positive number in \"%s\" operator", sub[1], name); + goto EXPAND_FAILED; + } + else + { + helo_cache_record_t *tmp = helo_cache_records; + int i; + + n--; + var_buffer[0] = 0; + + for (i = 0; i <= n && i < helo_cache_count && tmp != NULL; i++) + { + if (i == n) + yield = string_cat(yield, &size, &ptr, tmp->name, Ustrlen(tmp->name)); + tmp = tmp->next; + } + } + } + else if (Ustrcmp(sub[0], "time") == 0) + { + int n; + uschar *t = read_number(&n, sub[1]); + if (*t != 0 || n < 1) + { + expand_string_message = string_sprintf("string \"%s\" is not a " + "positive number in \"%s\" operator", sub[1], name); + goto EXPAND_FAILED; + } + else + { + helo_cache_record_t *tmp = helo_cache_records; + int i; + + n--; + var_buffer[0] = 0; + + for (i = 0; i <= n && i < helo_cache_count && tmp != NULL; i++) + { + if (i == n) + sprintf(CS var_buffer, "%d", tmp->time); + tmp = tmp->next; + } + + yield = string_cat(yield, &size, &ptr, var_buffer, Ustrlen(var_buffer)); + } + } + else + { + expand_string_message = + string_sprintf("helocache option \"%s\" is not recognised", sub[0]); + goto EXPAND_FAILED; + } + + continue; + } } /* Control reaches here if the name is not recognized as one of the more complicated expansion items. Check for the "operator" syntax (name terminated diff -U4 -r exim-4.62/src/globals.c exim-4.62+helo-cache/src/globals.c --- exim-4.62/src/globals.c 2006-04-28 11:32:22.000000000 +0100 +++ exim-4.62+helo-cache/src/globals.c 2006-07-19 22:27:44.000000000 +0100 @@ -580,8 +580,13 @@ BOOL header_rewritten = FALSE; uschar *helo_accept_junk_hosts = NULL; uschar *helo_allow_chars = US""; +BOOL helo_cache = FALSE; +int helo_cache_max = 4; +int helo_cache_time = 86400; +int helo_cache_count = 0; +helo_cache_record_t *helo_cache_records = NULL; uschar *helo_lookup_domains = US"@ : @[]"; uschar *helo_try_verify_hosts = NULL; BOOL helo_verified = FALSE; BOOL helo_verify_failed = FALSE; diff -U4 -r exim-4.62/src/globals.h exim-4.62+helo-cache/src/globals.h --- exim-4.62/src/globals.h 2006-04-28 11:32:22.000000000 +0100 +++ exim-4.62+helo-cache/src/globals.h 2006-07-19 19:42:10.000000000 +0100 @@ -348,8 +348,19 @@ extern int header_names_size; /* Number of entries */ extern BOOL header_rewritten; /* TRUE if header changed by router */ extern uschar *helo_accept_junk_hosts; /* Allowed to use junk arg */ extern uschar *helo_allow_chars; /* Rogue chars to allow in HELO/EHLO */ +extern BOOL helo_cache; +extern int helo_cache_max; +extern int helo_cache_time; +extern int helo_cache_count; +struct helo_cache_record { + uschar *name; + time_t time; + struct helo_cache_record *next; +}; +typedef struct helo_cache_record helo_cache_record_t; +extern helo_cache_record_t *helo_cache_records; extern uschar *helo_lookup_domains; /* If these given, lookup host name */ extern uschar *helo_try_verify_hosts; /* Soft check HELO argument for these */ extern BOOL helo_verified; /* True if HELO verified */ extern BOOL helo_verify_failed; /* True if attempt failed */ diff -U4 -r exim-4.62/src/readconf.c exim-4.62+helo-cache/src/readconf.c --- exim-4.62/src/readconf.c 2006-04-28 11:32:22.000000000 +0100 +++ exim-4.62+helo-cache/src/readconf.c 2006-07-19 19:27:21.000000000 +0100 @@ -221,8 +221,11 @@ { "header_maxsize", opt_int, &header_maxsize }, { "headers_charset", opt_stringptr, &headers_charset }, { "helo_accept_junk_hosts", opt_stringptr, &helo_accept_junk_hosts }, { "helo_allow_chars", opt_stringptr, &helo_allow_chars }, + { "helo_cache", opt_bool, &helo_cache }, + { "helo_cache_max", opt_int, &helo_cache_max }, + { "helo_cache_time", opt_time, &helo_cache_time }, { "helo_lookup_domains", opt_stringptr, &helo_lookup_domains }, { "helo_try_verify_hosts", opt_stringptr, &helo_try_verify_hosts }, { "helo_verify_hosts", opt_stringptr, &helo_verify_hosts }, { "hold_domains", opt_stringptr, &hold_domains }, diff -U4 -r exim-4.62/src/smtp_in.c exim-4.62+helo-cache/src/smtp_in.c --- exim-4.62/src/smtp_in.c 2006-04-28 11:32:23.000000000 +0100 +++ exim-4.62+helo-cache/src/smtp_in.c 2006-07-27 20:48:20.000000000 +0100 @@ -746,8 +746,231 @@ +/************************* +* Load helo cache data * +**************************/ + +/* This function makes a record of the helo cache. + +Arguments: + now Current time + helo_record Record to load + helo_name HELO/EHLO to add + +Returns: The hints record size of the data +*/ + +static int +load_helo_record(time_t now, dbdata_helo_cache *helo_record, uschar *helo_name) +{ +dbdata_helo_cache_entry *entry; +helo_cache_record_t *tmp = helo_cache_records, *prev = NULL; +int i, j, size = 0, found = 0; + +helo_cache_records = NULL; +while (tmp != NULL) + { + if (tmp->name != NULL) + store_free(tmp->name); + prev = tmp; + tmp = tmp->next; + store_free(prev); + } + +if (helo_record == NULL) + helo_cache_count = 0; +else + { + helo_cache_count = helo_record->count; + entry = helo_record->data; + } + +/* if there's a new record, lower the maximum by starting i at 1 */ +for (j = 0, i = (helo_name != NULL ? 1 : 0); j < helo_cache_count && i < helo_cache_max; j++, i++) + { + /* skip expired/invalid entries */ + if (now - entry->time_stamp > helo_cache_time || Ustrlen(entry->name) == 0) + { + helo_cache_count--; + j--; + } + else + { + if (helo_cache_records == NULL) + { + tmp = store_malloc(sizeof(helo_cache_record_t)); + helo_cache_records = tmp; + } + else + { + prev = tmp; + tmp = store_malloc(sizeof(helo_cache_record_t)); + prev->next = tmp; + } + + tmp->name = string_copy_malloc(entry->name); + size += sizeof(dbdata_helo_cache_entry) + sizeof(uschar) * (Ustrlen(tmp->name) + 1); + tmp->time = entry->time_stamp; + tmp->next = NULL; + + if (!found && helo_name != NULL && !Ustrncmp(tmp->name, helo_name, Ustrlen(helo_name))) + { + found = 1; + i--; /* raise the maximum to normal since no new entry will be needed */ + + tmp->time = now; + if (helo_cache_records != tmp) + { + if (prev != NULL) + prev->next = NULL; + tmp->next = helo_cache_records; + helo_cache_records = tmp; + tmp = prev; + } + } + } + entry = (dbdata_helo_cache_entry*)((void*)entry + + sizeof(dbdata_helo_cache_entry) + sizeof(uschar) * (Ustrlen(entry->name) + 1)); + } +helo_cache_count = j; + +if (!found && helo_name != NULL) + { + tmp = store_malloc(sizeof(helo_cache_record_t)); + tmp->name = string_copy_malloc(helo_name); + size += sizeof(dbdata_helo_cache_entry) + sizeof(uschar) * (Ustrlen(tmp->name) + 1); + tmp->time = now; + tmp->next = helo_cache_records; + helo_cache_records = tmp; + helo_cache_count++; + } + +return size; +} + + + + +/***************************** +* Read a helo cache record * +*****************************/ + +/* This function reads a helo cache record using the IP address as a string +as the key. + +Arguments: + address IP address of sender + +Returns: nothing +*/ + +static void +read_helo_cache(uschar *address) +{ +time_t now = time(NULL); +open_db dbblock; +open_db *dbm_file; +dbdata_helo_cache *helo_record; +helo_cache_record_t *tmp; + +if ((dbm_file = dbfn_open(US"helocache", O_RDONLY, &dbblock, FALSE)) == NULL) + { + DEBUG(D_receive) + debug_printf("no HELO/EHLO cache data available\n"); + return; + } + +helo_record = dbfn_read(dbm_file, address); + +if (helo_record != NULL && now - helo_record->time_stamp > helo_cache_time) + helo_record = NULL; + +load_helo_record(now, helo_record, NULL); + +if (helo_record != NULL) + DEBUG(D_receive) + { + debug_printf("Loaded helo data cache item for %s (%d entries)\n", address, helo_cache_count); + } + +dbfn_close(dbm_file); +} + + + + +/****************************** +* Store a helo cache record * +******************************/ + +/* This function stores a helo cache record using the IP address as a string +as the key and the argument to HELO/EHLO as the value. + +Arguments: + address IP address of sender + arg argument to HELO/EHLO + +Returns: nothing +*/ + +static void +write_helo_cache(uschar *address, uschar *arg) +{ +time_t now = time(NULL); +open_db dbblock; +open_db *dbm_file; +dbdata_helo_cache *helo_record; +dbdata_helo_cache_entry *entry; +helo_cache_record_t *tmp, *prev; +int i, size; + +if ((dbm_file = dbfn_open(US"helocache", O_RDWR, &dbblock, FALSE)) == NULL) + { + DEBUG(D_receive) + debug_printf("no HELO/EHLO cache data available\n"); + return; + } + +helo_record = dbfn_read(dbm_file, address); + +if (helo_record != NULL && now - helo_record->time_stamp > helo_cache_time) + { + dbfn_delete(dbm_file, address); + helo_record = NULL; + } + +size = load_helo_record(now, helo_record, arg); + +helo_record = store_malloc(sizeof(dbdata_helo_cache) + size); +helo_record->count = helo_cache_count; + +entry = helo_record->data; +tmp = helo_cache_records; +while (tmp != NULL) + { + Ustrcpy(entry->name, tmp->name); + entry->time_stamp = tmp->time; + entry = (dbdata_helo_cache_entry*)((void*)entry + + sizeof(dbdata_helo_cache_entry) + sizeof(uschar) * (Ustrlen(tmp->name) + 1)); + tmp = tmp->next; + } + +DEBUG(D_receive) + { + debug_printf("Writing helo data cache item for %s (%d entries)\n", address, helo_cache_count); + } + +(void)dbfn_write(dbm_file, address, helo_record, + sizeof(dbdata_helo_cache) + size); + +store_free(helo_record); +dbfn_close(dbm_file); +} + + + /************************************************* * Extract SMTP command option * *************************************************/ @@ -1565,8 +1788,12 @@ /* For batch SMTP input we are now done. */ if (smtp_batched_input) return TRUE; +/* Load HELO/EHLO cache. */ +if (sender_host_address && helo_cache) + read_helo_cache(sender_host_address); + /* Run the ACL if it exists */ if (acl_smtp_connect != NULL) { @@ -2390,8 +2617,13 @@ HELO_EHLO: /* Common code for HELO and EHLO */ cmd_list[CMD_LIST_HELO].is_mail_cmd = FALSE; cmd_list[CMD_LIST_EHLO].is_mail_cmd = FALSE; + /* Compare the current HELO/EHLO value from this host to what the host + * identified itself as last time. */ + if (sender_host_address && helo_cache) + write_helo_cache(sender_host_address, smtp_cmd_argument); + /* Reject the HELO if its argument was invalid or non-existent. A successful check causes the argument to be saved in malloc store. */ if (!check_helo(smtp_cmd_argument))