mirror of
https://github.com/donl/rmilter.git
synced 2026-06-30 06:12:17 -06:00
- Enable spamd_soft_fail and spamd_greylist options by default; - Add README file; - Fix runtime directory installation (wrong username); - Increase version to 1.5.44.
938 lines
22 KiB
C
938 lines
22 KiB
C
/*
|
|
* Copyright (c) 2007-2012, Vsevolod Stakhov
|
|
* All rights reserved.
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
* Redistributions of source code must retain the above copyright notice, this
|
|
* list of conditions and the following disclaimer. Redistributions in binary form
|
|
* must reproduce the above copyright notice, this list of conditions and the
|
|
* following disclaimer in the documentation and/or other materials provided with
|
|
* the distribution. Neither the name of the author nor the names of its
|
|
* contributors may be used to endorse or promote products derived from this
|
|
* software without specific prior written permission.
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <libmilter/mfapi.h>
|
|
#include <sys/un.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <syslog.h>
|
|
#include <netdb.h>
|
|
#include <math.h>
|
|
#include <sys/mman.h>
|
|
#include <stdarg.h>
|
|
|
|
#include "pcre.h"
|
|
#include "cfg_file.h"
|
|
#include "spf.h"
|
|
#include "rmilter.h"
|
|
|
|
extern int yylineno;
|
|
extern char *yytext;
|
|
|
|
void
|
|
parse_err (const char *fmt, ...)
|
|
{
|
|
va_list aq;
|
|
char logbuf[BUFSIZ], readbuf[32];
|
|
int r;
|
|
|
|
va_start (aq, fmt);
|
|
strlcpy (readbuf, yytext, sizeof (readbuf));
|
|
|
|
r = snprintf (logbuf, sizeof (logbuf), "config file parse error! line: %d, text: %s, reason: ", yylineno, readbuf);
|
|
r += vsnprintf (logbuf + r, sizeof (logbuf) - r, fmt, aq);
|
|
|
|
va_end (aq);
|
|
fprintf (stderr,"%s\n", logbuf);
|
|
syslog (LOG_ERR, "%s", logbuf);
|
|
}
|
|
|
|
void
|
|
parse_warn (const char *fmt, ...)
|
|
{
|
|
va_list aq;
|
|
char logbuf[BUFSIZ], readbuf[32];
|
|
int r;
|
|
|
|
va_start (aq, fmt);
|
|
strlcpy (readbuf, yytext, sizeof (readbuf));
|
|
|
|
r = snprintf (logbuf, sizeof (logbuf), "config file parse warning! line: %d, text: %s, reason: ", yylineno, readbuf);
|
|
r += vsnprintf (logbuf + r, sizeof (logbuf) - r, fmt, aq);
|
|
|
|
va_end (aq);
|
|
syslog (LOG_ERR, "%s", logbuf);
|
|
}
|
|
|
|
|
|
static size_t
|
|
copy_regexp (char **dst, const char *src)
|
|
{
|
|
size_t len;
|
|
if (!src || *src == '\0') return 0;
|
|
|
|
len = strlen (src);
|
|
|
|
/* Skip slashes */
|
|
if (*src == '/') {
|
|
src++;
|
|
len--;
|
|
}
|
|
if (src[len - 1] == '/') {
|
|
len--;
|
|
}
|
|
|
|
*dst = malloc (len + 1);
|
|
if (!*dst) return 0;
|
|
|
|
return strlcpy (*dst, src, len + 1);
|
|
}
|
|
|
|
int
|
|
add_memcached_server (struct config_file *cf, char *str, char *str2, int type)
|
|
{
|
|
char *cur_tok, *err_str;
|
|
struct memcached_server *mc = NULL;
|
|
struct hostent *he;
|
|
uint16_t port;
|
|
|
|
if (str == NULL) return 0;
|
|
|
|
if (type == MEMCACHED_SERVER_GREY) {
|
|
if(cf->memcached_servers_grey_num == MAX_MEMCACHED_SERVERS) {
|
|
yywarn ("yyparse: maximum number of memcached servers is reached %d", MAX_MEMCACHED_SERVERS);
|
|
return 0;
|
|
}
|
|
|
|
mc = &cf->memcached_servers_grey[cf->memcached_servers_grey_num];
|
|
}
|
|
else if (type == MEMCACHED_SERVER_WHITE) {
|
|
if(cf->memcached_servers_white_num == MAX_MEMCACHED_SERVERS) {
|
|
yywarn ("yyparse: maximum number of whitelist memcached servers is reached %d", MAX_MEMCACHED_SERVERS);
|
|
return 0;
|
|
}
|
|
|
|
mc = &cf->memcached_servers_white[cf->memcached_servers_white_num];
|
|
}
|
|
else if (type == MEMCACHED_SERVER_LIMITS) {
|
|
if(cf->memcached_servers_limits_num == MAX_MEMCACHED_SERVERS) {
|
|
yywarn ("yyparse: maximum number of limits memcached servers is reached %d", MAX_MEMCACHED_SERVERS);
|
|
return 0;
|
|
}
|
|
|
|
mc = &cf->memcached_servers_limits[cf->memcached_servers_limits_num];
|
|
}
|
|
else if (type == MEMCACHED_SERVER_ID) {
|
|
if(cf->memcached_servers_id_num == MAX_MEMCACHED_SERVERS) {
|
|
yywarn ("yyparse: maximum number of id memcached servers is reached %d", MAX_MEMCACHED_SERVERS);
|
|
return 0;
|
|
}
|
|
|
|
mc = &cf->memcached_servers_id[cf->memcached_servers_id_num];
|
|
}
|
|
if (mc == NULL) return 0;
|
|
|
|
cur_tok = strsep (&str, ":");
|
|
|
|
if (cur_tok == NULL || *cur_tok == '\0') return 0;
|
|
|
|
/* cur_tok - server name, str - server port */
|
|
if (str == NULL) {
|
|
port = htons(DEFAULT_MEMCACHED_PORT);
|
|
}
|
|
else {
|
|
port = htons ((uint16_t)strtoul (str, &err_str, 10));
|
|
if (*err_str != '\0') {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!inet_aton (cur_tok, &mc->addr[0])) {
|
|
/* Try to call gethostbyname */
|
|
he = gethostbyname (cur_tok);
|
|
if (he == NULL) {
|
|
return 0;
|
|
}
|
|
else {
|
|
memcpy((char *)&mc->addr[0], he->h_addr, sizeof(struct in_addr));
|
|
}
|
|
}
|
|
mc->port[0] = port;
|
|
|
|
if (str2 == NULL) {
|
|
mc->num = 1;
|
|
}
|
|
else {
|
|
mc->num = 2;
|
|
cur_tok = strsep (&str2, ":");
|
|
|
|
if (cur_tok == NULL || *cur_tok == '\0') return 0;
|
|
|
|
/* cur_tok - server name, str - server port */
|
|
if (str2 == NULL) {
|
|
port = htons(DEFAULT_MEMCACHED_PORT);
|
|
}
|
|
else {
|
|
port = htons ((uint16_t)strtoul (str2, &err_str, 10));
|
|
if (*err_str != '\0') {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!inet_aton (cur_tok, &mc->addr[1])) {
|
|
/* Try to call gethostbyname */
|
|
he = gethostbyname (cur_tok);
|
|
if (he == NULL) {
|
|
return 0;
|
|
}
|
|
else {
|
|
memcpy((char *)&mc->addr[1], he->h_addr, sizeof(struct in_addr));
|
|
}
|
|
}
|
|
mc->port[1] = port;
|
|
}
|
|
|
|
mc->alive[0] = 1;
|
|
mc->alive[1] = 1;
|
|
|
|
if (type == MEMCACHED_SERVER_GREY) {
|
|
cf->memcached_servers_grey_num++;
|
|
}
|
|
else if (type == MEMCACHED_SERVER_WHITE) {
|
|
cf->memcached_servers_white_num++;
|
|
}
|
|
else if (type == MEMCACHED_SERVER_LIMITS) {
|
|
cf->memcached_servers_limits_num++;
|
|
}
|
|
else if (type == MEMCACHED_SERVER_ID) {
|
|
cf->memcached_servers_id_num++;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
add_clamav_server (struct config_file *cf, char *str)
|
|
{
|
|
char *cur_tok, *err_str;
|
|
struct clamav_server *srv;
|
|
struct hostent *he;
|
|
|
|
if (str == NULL) return 0;
|
|
|
|
cur_tok = strsep (&str, ":");
|
|
|
|
if (cur_tok == NULL || *cur_tok == '\0') return 0;
|
|
|
|
if (cf->clamav_servers_num == MAX_CLAMAV_SERVERS) {
|
|
yywarn ("yyparse: maximum number of clamav servers is reached %d", MAX_CLAMAV_SERVERS);
|
|
}
|
|
|
|
srv = &cf->clamav_servers[cf->clamav_servers_num];
|
|
|
|
if (srv == NULL) return 0;
|
|
|
|
if (cur_tok[0] == '/' || cur_tok[0] == '.') {
|
|
srv->sock.unix_path = strdup (cur_tok);
|
|
srv->sock_type = AF_UNIX;
|
|
srv->name = srv->sock.unix_path;
|
|
if (str != NULL && *str != '\0') {
|
|
srv->up.priority = strtoul (str, &err_str, 10);
|
|
if (*err_str != '\0') {
|
|
return 0;
|
|
}
|
|
cf->weighted_clamav = 1;
|
|
}
|
|
|
|
cf->clamav_servers_num++;
|
|
return 1;
|
|
|
|
} else {
|
|
if (str == '\0') {
|
|
srv->sock.inet.port = htons (DEFAULT_CLAMAV_PORT);
|
|
}
|
|
else {
|
|
srv->sock.inet.port = htons ((uint16_t)strtoul (str, &err_str, 10));
|
|
if (*err_str != '\0') {
|
|
return 0;
|
|
}
|
|
|
|
}
|
|
|
|
if (!inet_aton (cur_tok, &srv->sock.inet.addr)) {
|
|
/* Try to call gethostbyname */
|
|
he = gethostbyname (cur_tok);
|
|
if (he == NULL) {
|
|
return 0;
|
|
}
|
|
else {
|
|
srv->name = strdup (cur_tok);
|
|
memcpy((char *)&srv->sock.inet.addr, he->h_addr, sizeof(struct in_addr));
|
|
}
|
|
}
|
|
/* Try to parse priority */
|
|
cur_tok = strsep (&str, ":");
|
|
if (str != NULL && *str != '\0') {
|
|
srv->up.priority = strtoul (str, &err_str, 10);
|
|
if (*err_str != '\0') {
|
|
return 0;
|
|
}
|
|
cf->weighted_clamav = 1;
|
|
}
|
|
|
|
srv->sock_type = AF_INET;
|
|
cf->clamav_servers_num++;
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
add_spamd_server (struct config_file *cf, char *str, int is_extra)
|
|
{
|
|
char *cur_tok, *err_str;
|
|
struct spamd_server *srv;
|
|
struct hostent *he;
|
|
|
|
if (str == NULL) return 0;
|
|
|
|
if (is_extra) {
|
|
if (cf->extra_spamd_servers_num == MAX_SPAMD_SERVERS) {
|
|
yywarn ("yyparse: maximum number of spamd servers is reached %d", MAX_SPAMD_SERVERS);
|
|
return -1;
|
|
}
|
|
}
|
|
else {
|
|
if (cf->spamd_servers_num == MAX_SPAMD_SERVERS) {
|
|
yywarn ("yyparse: maximum number of spamd servers is reached %d", MAX_SPAMD_SERVERS);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (is_extra) {
|
|
srv = &cf->extra_spamd_servers[cf->extra_spamd_servers_num];
|
|
}
|
|
else {
|
|
srv = &cf->spamd_servers[cf->spamd_servers_num];
|
|
}
|
|
|
|
if (*str == 'r' && *(str + 1) == ':') {
|
|
srv->type = SPAMD_RSPAMD;
|
|
str += 2;
|
|
}
|
|
else {
|
|
srv->type = SPAMD_SPAMASSASSIN;
|
|
}
|
|
|
|
|
|
cur_tok = strsep (&str, ":");
|
|
|
|
if (cur_tok == NULL || *cur_tok == '\0') return 0;
|
|
|
|
if (cur_tok[0] == '/' || cur_tok[0] == '.') {
|
|
srv->sock.unix_path = strdup (cur_tok);
|
|
srv->sock_type = AF_UNIX;
|
|
srv->name = srv->sock.unix_path;
|
|
|
|
if (is_extra) {
|
|
cf->extra_spamd_servers_num++;
|
|
}
|
|
else {
|
|
cf->spamd_servers_num++;
|
|
}
|
|
return 1;
|
|
|
|
} else {
|
|
if (str == '\0') {
|
|
srv->sock.inet.port = htons (DEFAULT_SPAMD_PORT);
|
|
}
|
|
else {
|
|
srv->sock.inet.port = htons ((uint16_t)strtoul (str, &err_str, 10));
|
|
if (*err_str != '\0') {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!inet_aton (cur_tok, &srv->sock.inet.addr)) {
|
|
/* Try to call gethostbyname */
|
|
he = gethostbyname (cur_tok);
|
|
if (he == NULL) {
|
|
return 0;
|
|
}
|
|
else {
|
|
memcpy((char *)&srv->sock.inet.addr, he->h_addr, sizeof(struct in_addr));
|
|
}
|
|
}
|
|
srv->name = strdup (cur_tok);
|
|
|
|
srv->sock_type = AF_INET;
|
|
if (is_extra) {
|
|
cf->extra_spamd_servers_num++;
|
|
}
|
|
else {
|
|
cf->spamd_servers_num++;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
add_beanstalk_server (struct config_file *cf, char *str, int type)
|
|
{
|
|
char *cur_tok, *err_str;
|
|
struct beanstalk_server *srv;
|
|
struct hostent *he;
|
|
|
|
if (str == NULL) return 0;
|
|
|
|
cur_tok = strsep (&str, ":");
|
|
|
|
if (cur_tok == NULL || *cur_tok == '\0') return 0;
|
|
|
|
if (type == 1) {
|
|
cf->copy_server = malloc (sizeof (struct beanstalk_server));
|
|
srv = cf->copy_server;
|
|
}
|
|
else if (type == 2) {
|
|
cf->spam_server = malloc (sizeof (struct beanstalk_server));
|
|
srv = cf->spam_server;
|
|
}
|
|
else {
|
|
if (cf->beanstalk_servers_num == MAX_BEANSTALK_SERVERS) {
|
|
yywarn ("yyparse: maximum number of beanstalk servers is reached %d", MAX_BEANSTALK_SERVERS);
|
|
}
|
|
|
|
srv = &cf->beanstalk_servers[cf->beanstalk_servers_num];
|
|
}
|
|
|
|
if (srv == NULL) return 0;
|
|
|
|
if (str == '\0') {
|
|
srv->port = htons (DEFAULT_BEANSTALK_PORT);
|
|
}
|
|
else {
|
|
srv->port = htons ((uint16_t)strtoul (str, &err_str, 10));
|
|
if (*err_str != '\0') {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!inet_aton (cur_tok, &srv->addr)) {
|
|
/* Try to call gethostbyname */
|
|
he = gethostbyname (cur_tok);
|
|
if (he == NULL) {
|
|
return 0;
|
|
}
|
|
else {
|
|
srv->name = strdup (cur_tok);
|
|
memcpy((char *)&srv->addr, he->h_addr, sizeof(struct in_addr));
|
|
}
|
|
if (type == 0) {
|
|
cf->beanstalk_servers_num ++;
|
|
}
|
|
return 1;
|
|
}
|
|
else {
|
|
srv->name = strdup (cur_tok);
|
|
if (type == 0) {
|
|
cf->beanstalk_servers_num ++;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct action *
|
|
create_action (enum action_type type, const char *message)
|
|
{
|
|
struct action *new;
|
|
size_t len = strlen (message);
|
|
|
|
if (message == NULL) return NULL;
|
|
|
|
new = (struct action *)malloc (sizeof (struct action));
|
|
|
|
if (new == NULL) return NULL;
|
|
|
|
new->type = type;
|
|
/* Trim quotes */
|
|
if (*message == '"') {
|
|
message++;
|
|
len--;
|
|
}
|
|
if (message[len - 1] == '"') {
|
|
len--;
|
|
}
|
|
|
|
new->message = (char *)malloc (len + 1);
|
|
|
|
if (new->message == NULL) return NULL;
|
|
|
|
strlcpy (new->message, message, len + 1);
|
|
|
|
return new;
|
|
}
|
|
|
|
struct condition *
|
|
create_cond (enum condition_type type, const char *arg1, const char *arg2)
|
|
{
|
|
struct condition *new;
|
|
int offset;
|
|
const char *read_err;
|
|
|
|
new = (struct condition *)malloc (sizeof (struct condition));
|
|
bzero (new, sizeof (struct condition));
|
|
|
|
if (new == NULL) return NULL;
|
|
|
|
if (arg1 == NULL || *arg1 == '\0') {
|
|
new->args[0].empty = 1;
|
|
}
|
|
else {
|
|
if (!copy_regexp (&new->args[0].src, arg1)) {
|
|
new->args[0].empty = 1;
|
|
}
|
|
else {
|
|
new->args[0].re = pcre_compile (new->args[0].src, 0, &read_err, &offset, NULL);
|
|
if (new->args[0].re == NULL) {
|
|
new->args[0].empty = 1;
|
|
}
|
|
}
|
|
}
|
|
if (arg2 == NULL || *arg2 == '\0') {
|
|
new->args[1].empty = 1;
|
|
}
|
|
else {
|
|
if (!copy_regexp (&new->args[1].src, arg2)) {
|
|
new->args[1].empty = 1;
|
|
}
|
|
else {
|
|
new->args[1].re = pcre_compile (new->args[1].src, 0, &read_err, &offset, NULL);
|
|
if (new->args[1].re == NULL) {
|
|
new->args[1].empty = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
new->type = type;
|
|
|
|
return new;
|
|
}
|
|
|
|
int
|
|
add_spf_domain (struct config_file *cfg, char *domain)
|
|
{
|
|
if (!domain) return 0;
|
|
|
|
if (cfg->spf_domains_num > MAX_SPF_DOMAINS) {
|
|
return 0;
|
|
}
|
|
|
|
cfg->spf_domains[cfg->spf_domains_num] = domain;
|
|
cfg->spf_domains_num ++;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
add_ip_radix (radix_tree_t *tree, char *ipnet)
|
|
{
|
|
uint32_t mask = 0xFFFFFFFF;
|
|
uint32_t ip;
|
|
char *token;
|
|
struct in_addr ina;
|
|
int k;
|
|
|
|
token = strsep (&ipnet, "/");
|
|
|
|
if (ipnet != NULL) {
|
|
k = atoi (ipnet);
|
|
if (k > 32 || k < 0) {
|
|
yywarn ("add_ip_radix: invalid netmask value: %d", k);
|
|
k = 32;
|
|
}
|
|
k = 32 - k;
|
|
mask = mask << k;
|
|
}
|
|
|
|
if (inet_aton (token, &ina) == 0) {
|
|
yyerror ("add_ip_radix: invalid ip address: %s", token);
|
|
return 0;
|
|
}
|
|
|
|
ip = ntohl((uint32_t)ina.s_addr);
|
|
k = radix32tree_insert (tree, ip, mask, 1);
|
|
if (k == -1) {
|
|
yyerror ("add_ip_radix: cannot insert ip to tree: %s, mask %X", inet_ntoa (ina), mask);
|
|
return 0;
|
|
}
|
|
else if (k == 1) {
|
|
yywarn ("add_ip_radix: ip %s, mask %X, value already exists", inet_ntoa (ina), mask);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
add_hashed_header (const char *name, struct dkim_hash_entry **hash)
|
|
{
|
|
struct dkim_hash_entry *new;
|
|
|
|
new = malloc (sizeof (struct dkim_hash_entry));
|
|
new->name = strdup (name);
|
|
HASH_ADD_KEYPTR (hh, *hash, new->name, strlen (new->name), new);
|
|
}
|
|
|
|
void
|
|
init_defaults (struct config_file *cfg)
|
|
{
|
|
LIST_INIT (&cfg->rules);
|
|
cfg->wlist_rcpt_global = NULL;
|
|
cfg->wlist_rcpt_limit = NULL;
|
|
LIST_INIT (&cfg->bounce_addrs);
|
|
|
|
cfg->clamav_connect_timeout = DEFAULT_CLAMAV_CONNECT_TIMEOUT;
|
|
cfg->clamav_port_timeout = DEFAULT_CLAMAV_PORT_TIMEOUT;
|
|
cfg->clamav_results_timeout = DEFAULT_CLAMAV_RESULTS_TIMEOUT;
|
|
cfg->memcached_connect_timeout = DEFAULT_MEMCACHED_CONNECT_TIMEOUT;
|
|
cfg->beanstalk_connect_timeout = DEFAULT_MEMCACHED_CONNECT_TIMEOUT;
|
|
cfg->spamd_connect_timeout = DEFAULT_SPAMD_CONNECT_TIMEOUT;
|
|
cfg->spamd_results_timeout = DEFAULT_SPAMD_RESULTS_TIMEOUT;
|
|
|
|
cfg->clamav_error_time = DEFAULT_UPSTREAM_ERROR_TIME;
|
|
cfg->clamav_dead_time = DEFAULT_UPSTREAM_DEAD_TIME;
|
|
cfg->clamav_maxerrors = DEFAULT_UPSTREAM_MAXERRORS;
|
|
|
|
cfg->spamd_error_time = DEFAULT_UPSTREAM_ERROR_TIME;
|
|
cfg->spamd_dead_time = DEFAULT_UPSTREAM_DEAD_TIME;
|
|
cfg->spamd_maxerrors = DEFAULT_UPSTREAM_MAXERRORS;
|
|
cfg->spamd_reject_message = strdup (DEFAUL_SPAMD_REJECT);
|
|
cfg->rspamd_metric = strdup (DEFAULT_RSPAMD_METRIC);
|
|
cfg->spam_header = strdup (DEFAULT_SPAM_HEADER);
|
|
|
|
cfg->memcached_error_time = DEFAULT_UPSTREAM_ERROR_TIME;
|
|
cfg->memcached_dead_time = DEFAULT_UPSTREAM_DEAD_TIME;
|
|
cfg->memcached_maxerrors = DEFAULT_UPSTREAM_MAXERRORS;
|
|
cfg->memcached_protocol = UDP_TEXT;
|
|
|
|
cfg->beanstalk_error_time = DEFAULT_UPSTREAM_ERROR_TIME;
|
|
cfg->beanstalk_dead_time = DEFAULT_UPSTREAM_DEAD_TIME;
|
|
cfg->beanstalk_maxerrors = DEFAULT_UPSTREAM_MAXERRORS;
|
|
cfg->beanstalk_protocol = BEANSTALK_TCP_TEXT;
|
|
cfg->beanstalk_lifetime = DEFAULT_BEANSTALK_LIFETIME;
|
|
cfg->copy_server = NULL;
|
|
cfg->spam_server = NULL;
|
|
|
|
cfg->grey_whitelist_tree = radix_tree_create ();
|
|
cfg->limit_whitelist_tree = radix_tree_create ();
|
|
cfg->spamd_whitelist = radix_tree_create ();
|
|
cfg->greylisted_message = strdup (DEFAULT_GREYLISTED_MESSAGE);
|
|
|
|
cfg->spf_domains = (char **) calloc (MAX_SPF_DOMAINS, sizeof (char *));
|
|
cfg->awl_enable = 0;
|
|
cfg->beanstalk_copy_prob = 100.0;
|
|
|
|
cfg->spamd_soft_fail = 1;
|
|
cfg->spamd_greylist = 1;
|
|
|
|
#if 0
|
|
/* Init static defaults */
|
|
white_from_abuse.addr = "abuse";
|
|
white_from_abuse.len = sizeof ("abuse") - 1;
|
|
white_from_postmaster.addr = "postmaster";
|
|
white_from_postmaster.len = sizeof ("postmaster") - 1;
|
|
LIST_INSERT_HEAD (&cfg->whitelist_static, &white_from_abuse, next);
|
|
LIST_INSERT_HEAD (&cfg->whitelist_static, &white_from_postmaster, next);
|
|
#endif
|
|
|
|
#ifdef ENABLE_DKIM
|
|
cfg->dkim_lib = dkim_init (NULL, NULL);
|
|
/* Add recommended by rfc headers */
|
|
add_hashed_header ("from", &cfg->headers);
|
|
add_hashed_header ("sender", &cfg->headers);
|
|
add_hashed_header ("reply-to", &cfg->headers);
|
|
add_hashed_header ("subject", &cfg->headers);
|
|
add_hashed_header ("date", &cfg->headers);
|
|
add_hashed_header ("message-id", &cfg->headers);
|
|
add_hashed_header ("to", &cfg->headers);
|
|
add_hashed_header ("cc", &cfg->headers);
|
|
add_hashed_header ("date", &cfg->headers);
|
|
add_hashed_header ("mime-version", &cfg->headers);
|
|
add_hashed_header ("content-type", &cfg->headers);
|
|
add_hashed_header ("content-transfer-encoding", &cfg->headers);
|
|
add_hashed_header ("resent-to", &cfg->headers);
|
|
add_hashed_header ("resent-cc", &cfg->headers);
|
|
add_hashed_header ("resent-from", &cfg->headers);
|
|
add_hashed_header ("resent-sender", &cfg->headers);
|
|
add_hashed_header ("resent-message-id", &cfg->headers);
|
|
add_hashed_header ("in-reply-to", &cfg->headers);
|
|
add_hashed_header ("references", &cfg->headers);
|
|
add_hashed_header ("list-id", &cfg->headers);
|
|
add_hashed_header ("list-owner", &cfg->headers);
|
|
add_hashed_header ("list-unsubscribe", &cfg->headers);
|
|
add_hashed_header ("list-subscribe", &cfg->headers);
|
|
add_hashed_header ("list-post", &cfg->headers);
|
|
/* TODO: make it configurable */
|
|
#endif
|
|
}
|
|
|
|
void
|
|
free_config (struct config_file *cfg)
|
|
{
|
|
unsigned int i;
|
|
struct rule *cur, *tmp_rule;
|
|
struct condition *cond, *tmp_cond;
|
|
struct addr_list_entry *addr_cur, *addr_tmp;
|
|
struct whitelisted_rcpt_entry *rcpt_cur, *rcpt_tmp;
|
|
|
|
if (cfg->pid_file) {
|
|
free (cfg->pid_file);
|
|
}
|
|
if (cfg->temp_dir) {
|
|
free (cfg->temp_dir);
|
|
}
|
|
if (cfg->sock_cred) {
|
|
free (cfg->sock_cred);
|
|
}
|
|
|
|
if (cfg->spf_domains) {
|
|
for (i = 0; i < MAX_SPF_DOMAINS; i++) {
|
|
free (cfg->spf_domains[i]);
|
|
}
|
|
free (cfg->spf_domains);
|
|
}
|
|
|
|
if (cfg->special_mid_re) {
|
|
pcre_free (cfg->special_mid_re);
|
|
}
|
|
|
|
for (i = 0; i < cfg->clamav_servers_num; i++) {
|
|
free (cfg->clamav_servers[i].name);
|
|
}
|
|
for (i = 0; i < cfg->spamd_servers_num; i++) {
|
|
free (cfg->spamd_servers[i].name);
|
|
}
|
|
/* Free rules list */
|
|
LIST_FOREACH_SAFE (cur, &cfg->rules, next, tmp_rule) {
|
|
LIST_FOREACH_SAFE (cond, cur->conditions, next, tmp_cond) {
|
|
if (!cond->args[0].empty) {
|
|
if (cond->args[0].re != NULL) {
|
|
pcre_free (cond->args[0].re);
|
|
}
|
|
if (cond->args[0].src != NULL) {
|
|
free (cond->args[0].src);
|
|
}
|
|
}
|
|
if (!cond->args[1].empty) {
|
|
if (cond->args[1].re != NULL) {
|
|
pcre_free (cond->args[1].re);
|
|
}
|
|
if (cond->args[1].src != NULL) {
|
|
free (cond->args[1].src);
|
|
}
|
|
}
|
|
LIST_REMOVE (cond, next);
|
|
free (cond);
|
|
}
|
|
if (cur->act->message) {
|
|
free (cur->act->message);
|
|
}
|
|
free (cur->act);
|
|
LIST_REMOVE (cur, next);
|
|
free (cur);
|
|
}
|
|
/* Free whitelists and bounce list*/
|
|
HASH_ITER (hh, cfg->wlist_rcpt_global, rcpt_cur, rcpt_tmp) {
|
|
HASH_DEL (cfg->wlist_rcpt_global, rcpt_cur);
|
|
free (rcpt_cur->rcpt);
|
|
free (rcpt_cur);
|
|
}
|
|
HASH_ITER (hh, cfg->wlist_rcpt_limit, rcpt_cur, rcpt_tmp) {
|
|
HASH_DEL (cfg->wlist_rcpt_limit, rcpt_cur);
|
|
free (rcpt_cur->rcpt);
|
|
free (rcpt_cur);
|
|
}
|
|
LIST_FOREACH_SAFE (addr_cur, &cfg->bounce_addrs, next, addr_tmp) {
|
|
if (addr_cur->addr) {
|
|
free (addr_cur->addr);
|
|
}
|
|
LIST_REMOVE (addr_cur, next);
|
|
free (addr_cur);
|
|
}
|
|
|
|
radix_tree_free (cfg->grey_whitelist_tree);
|
|
free (cfg->grey_whitelist_tree);
|
|
|
|
radix_tree_free (cfg->spamd_whitelist);
|
|
free (cfg->spamd_whitelist);
|
|
|
|
radix_tree_free (cfg->limit_whitelist_tree);
|
|
free (cfg->limit_whitelist_tree);
|
|
|
|
if (cfg->spamd_reject_message) {
|
|
free (cfg->spamd_reject_message);
|
|
}
|
|
if (cfg->rspamd_metric) {
|
|
free (cfg->rspamd_metric);
|
|
}
|
|
if (cfg->spam_header) {
|
|
free (cfg->spam_header);
|
|
}
|
|
if (cfg->id_prefix) {
|
|
free (cfg->id_prefix);
|
|
}
|
|
if (cfg->grey_prefix) {
|
|
free (cfg->grey_prefix);
|
|
}
|
|
if (cfg->white_prefix) {
|
|
free (cfg->white_prefix);
|
|
}
|
|
if (cfg->copy_server) {
|
|
free (cfg->copy_server);
|
|
}
|
|
if (cfg->spam_server) {
|
|
free (cfg->spam_server);
|
|
}
|
|
if (cfg->greylisted_message) {
|
|
free (cfg->greylisted_message);
|
|
}
|
|
|
|
if (cfg->awl_enable && cfg->awl_hash != NULL) {
|
|
free (cfg->awl_hash->pool);
|
|
free (cfg->awl_hash);
|
|
}
|
|
|
|
|
|
#ifdef ENABLE_DKIM
|
|
struct dkim_hash_entry *curh, *tmph;
|
|
struct dkim_domain_entry *curd, *tmpd;
|
|
|
|
if (cfg->dkim_lib) {
|
|
dkim_close (cfg->dkim_lib);
|
|
}
|
|
HASH_ITER (hh, cfg->headers, curh, tmph) {
|
|
HASH_DEL (cfg->headers, curh); /* delete; users advances to next */
|
|
free (curh->name);
|
|
free (curh);
|
|
}
|
|
HASH_ITER (hh, cfg->dkim_domains, curd, tmpd) {
|
|
HASH_DEL (cfg->dkim_domains, curd); /* delete; users advances to next */
|
|
if (curd->key != MAP_FAILED && curd->key != NULL) {
|
|
munmap (curd->key, curd->keylen);
|
|
}
|
|
if (curd->domain) {
|
|
free (curd->domain);
|
|
}
|
|
if (curd->selector) {
|
|
free (curd->selector);
|
|
}
|
|
if (curd->keyfile) {
|
|
free (curd->keyfile);
|
|
}
|
|
free (curd);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void
|
|
add_rcpt_whitelist (struct config_file *cfg, const char *rcpt, int is_global)
|
|
{
|
|
struct whitelisted_rcpt_entry *t;
|
|
t = (struct whitelisted_rcpt_entry *)malloc (sizeof (struct whitelisted_rcpt_entry));
|
|
if (*rcpt == '@') {
|
|
t->type = WLIST_RCPT_DOMAIN;
|
|
rcpt ++;
|
|
}
|
|
else if (strchr (rcpt, '@') != NULL) {
|
|
t->type = WLIST_RCPT_USERDOMAIN;
|
|
}
|
|
else {
|
|
t->type = WLIST_RCPT_USER;
|
|
}
|
|
t->rcpt = strdup (rcpt);
|
|
t->len = strlen (t->rcpt);
|
|
if (is_global) {
|
|
HASH_ADD_KEYPTR (hh, cfg->wlist_rcpt_global, t->rcpt, t->len, t);
|
|
}
|
|
else {
|
|
HASH_ADD_KEYPTR (hh, cfg->wlist_rcpt_limit, t->rcpt, t->len, t);
|
|
}
|
|
}
|
|
|
|
int
|
|
is_whitelisted_rcpt (struct config_file *cfg, const char *str, int is_global)
|
|
{
|
|
int len;
|
|
struct whitelisted_rcpt_entry *entry, *list;
|
|
char rcptbuf[ADDRLEN + 1], *domain;
|
|
|
|
if (*str == '<') {
|
|
str ++;
|
|
}
|
|
|
|
len = strcspn (str, ">");
|
|
strlcpy (rcptbuf, str, MIN (len + 1, sizeof (rcptbuf)));
|
|
if (len > 0) {
|
|
if (is_global) {
|
|
list = cfg->wlist_rcpt_global;
|
|
}
|
|
else {
|
|
list = cfg->wlist_rcpt_limit;
|
|
}
|
|
/* Initially search for userdomain */
|
|
HASH_FIND_STR (list, rcptbuf, entry, strncasecmp);
|
|
if (entry != NULL && entry->type == WLIST_RCPT_USERDOMAIN) {
|
|
return 1;
|
|
}
|
|
domain = strchr (rcptbuf, '@');
|
|
if (domain == NULL && entry != NULL && entry->type == WLIST_RCPT_USER) {
|
|
return 1;
|
|
}
|
|
/* Search for user */
|
|
if (domain != NULL) {
|
|
*domain = '\0';
|
|
}
|
|
HASH_FIND_STR (list, rcptbuf, entry, strncasecmp);
|
|
if (entry != NULL && entry->type == WLIST_RCPT_USER) {
|
|
return 1;
|
|
}
|
|
if (domain != NULL) {
|
|
/* Search for domain */
|
|
domain ++;
|
|
HASH_FIND_STR (list, domain, entry, strncasecmp);
|
|
if (entry != NULL && entry->type == WLIST_RCPT_DOMAIN) {
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* vi:ts=4
|
|
*/
|