/* $Id: autowhite.c,v 1.51.2.1 2006/09/04 22:05:58 manu Exp $ */
/*
* Copyright (c) 2004 Emmanuel Dreyfus
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Emmanuel Dreyfus
*
* THIS SOFTWARE IS PROVIDED ``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 AUTHOR 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.
*/
#ifdef HAVE_SYS_CDEFS_H
#include <sys/cdefs.h>
#ifdef __RCSID
__RCSID("$Id: autowhite.c,v 1.51.2.1 2006/09/04 22:05:58 manu Exp $");
#endif
#endif
#include "config.h"
#ifdef HAVE_OLD_QUEUE_H
#include "queue.h"
#else
#include <sys/queue.h>
#endif
#include <stdlib.h>
#include <ctype.h>
#include <syslog.h>
#include <errno.h>
#include <sysexits.h>
#include <string.h>
#include <time.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "conf.h"
#include "pending.h"
#include "dump.h"
#include "autowhite.h"
#include "acl.h"
#include "sync.h"
struct autowhitelist autowhite_head;
struct autowhite_bucket *autowhite_buckets;
pthread_rwlock_t autowhite_lock;
pthread_mutex_t autowhite_change_lock;
void
autowhite_init(void) {
int error, i;
TAILQ_INIT(&autowhite_head);
if ((autowhite_buckets = calloc(AUTOWHITE_BUCKETS,
sizeof(struct autowhite_bucket))) == NULL) {
mg_log(LOG_ERR,
"Unable to allocate autowhite list buckets: %s",
strerror(errno));
exit(EX_OSERR);
}
if ((error = pthread_rwlock_init(&autowhite_lock, NULL)) != 0 ||
(error = pthread_mutex_init(&autowhite_change_lock, NULL)) != 0) {
mg_log(LOG_ERR, "pthread_rwlock_init failed: %s",
strerror(error));
exit(EX_OSERR);
}
for(i = 0; i < AUTOWHITE_BUCKETS; i++) {
TAILQ_INIT(&autowhite_buckets[i].b_autowhite_head);
if ((error =
pthread_mutex_init(&autowhite_buckets[i].bucket_mtx,
NULL)) != 0) {
mg_log(LOG_ERR,
"pthread_mutex_init failed: %s", strerror(error));
exit(EX_OSERR);
}
}
return;
}
int
autowhite_timeout(void)
{
struct autowhite *aw;
struct autowhite *next_aw;
struct timeval now;
int dirty = 0;
gettimeofday(&now, NULL);
AUTOWHITE_WRLOCK;
for (aw = TAILQ_FIRST(&autowhite_head); aw; aw = next_aw) {
next_aw = TAILQ_NEXT(aw, a_list);
/*
* Expiration
*/
if (aw->a_tv.tv_sec < now.tv_sec) {
char buf[IPADDRSTRLEN];
iptostring(aw->a_sa, aw->a_salen, buf, sizeof(buf));
mg_log(LOG_INFO, "(local): addr %s from %s rcpt %s: "
"autowhitelisted entry expired",
buf, aw->a_from, aw->a_rcpt);
autowhite_put(aw);
dirty++;
continue;
}
break;
}
AUTOWHITE_UNLOCK;
return dirty;
}
void
autowhite_add(sa, salen, from, rcpt, date, queueid)
struct sockaddr *sa;
socklen_t salen;
char *from;
char *rcpt;
time_t *date;
char *queueid;
{
struct autowhite *aw;
struct autowhite *next_aw;
struct timeval now;
char addr[IPADDRSTRLEN];
int h, mn, s;
int dirty = 0;
ipaddr *mask = NULL;
struct autowhite_bucket *b;
time_t autowhite;
gettimeofday(&now, NULL);
autowhite = *date - now.tv_sec;
h = autowhite / 3600;
mn = ((autowhite % 3600) / 60);
s = (autowhite % 3600) % 60;
if (!iptostring(sa, salen, addr, sizeof(addr)))
return;
switch (sa->sa_family) {
case AF_INET:
mask = (ipaddr *)&conf.c_match_mask;
break;
#ifdef AF_INET6
case AF_INET6:
mask = (ipaddr *)&conf.c_match_mask6;
break;
#endif
}
dirty = autowhite_timeout();
AUTOWHITE_RDLOCK;
b = &autowhite_buckets[BUCKET_HASH(sa, from, rcpt, AUTOWHITE_BUCKETS)];
pthread_mutex_lock(&b->bucket_mtx);
for (aw = TAILQ_FIRST(&b->b_autowhite_head); aw; aw = next_aw) {
next_aw = TAILQ_NEXT(aw, ab_list);
/*
* Expiration (left this one in too until the list gets sorted)
*/
if (aw->a_tv.tv_sec < now.tv_sec) {
char buf[IPADDRSTRLEN];
iptostring(aw->a_sa, aw->a_salen, buf, sizeof(buf));
mg_log(LOG_INFO, "(local): addr %s from %s rcpt %s: "
"autowhitelisted entry expired",
buf, aw->a_from, aw->a_rcpt);
autowhite_put(aw);
dirty++;
continue;
}
/*
* Look for an already existing entry
*/
if (ip_match(sa, aw->a_sa, mask) &&
((conf.c_lazyaw == 1) ||
((strcasecmp(from, aw->a_from) == 0) &&
(strcasecmp(rcpt, aw->a_rcpt) == 0)))) {
aw->a_tv.tv_sec = *date;
/* Push it at the back of the big queue */
pthread_mutex_lock(&autowhite_change_lock);
TAILQ_REMOVE(&autowhite_head, aw, a_list);
TAILQ_INSERT_TAIL(&autowhite_head, aw, a_list);
pthread_mutex_unlock(&autowhite_change_lock);
dirty++;
mg_log(LOG_INFO, "%s: addr %s from %s rcpt %s: "
"autowhitelisted for more %02d:%02d:%02d",
queueid, addr, from, rcpt, h, mn, s);
break;
}
}
/*
* Entry not found, create it
*/
if (aw == NULL) {
aw = autowhite_get(sa, salen, from, rcpt, *date);
dirty++;
mg_log(LOG_INFO, "%s: addr %s from %s rcpt %s: "
"autowhitelisted for %02d:%02d:%02d",
queueid, addr, from, rcpt, h, mn, s);
}
pthread_mutex_unlock(&b->bucket_mtx);
AUTOWHITE_UNLOCK;
if (dirty != 0)
dump_dirty += dirty;
return;
}
int
autowhite_check(sa, salen, from, rcpt, queueid, gldelay, autowhite)
struct sockaddr *sa;
socklen_t salen;
char *from;
char *rcpt;
char *queueid;
time_t gldelay;
time_t autowhite;
{
struct autowhite *aw;
struct autowhite *next_aw;
struct pending *pending;
struct timeval now, delay;
char addr[IPADDRSTRLEN];
int h, mn, s;
int dirty = 0;
ipaddr *mask = NULL;
struct autowhite_bucket *b;
if (autowhite == 0)
return EXF_NONE;
gettimeofday(&now, NULL);
delay.tv_sec = autowhite;
delay.tv_usec = 0;
h = autowhite / 3600;
mn = ((autowhite % 3600) / 60);
s = (autowhite % 3600) % 60;
if (!iptostring(sa, salen, addr, sizeof(addr)))
return EXF_NONE;
switch (sa->sa_family) {
case AF_INET:
mask = (ipaddr *)&conf.c_match_mask;
break;
#ifdef AF_INET6
case AF_INET6:
mask = (ipaddr *)&conf.c_match_mask6;
break;
#endif
}
dirty = autowhite_timeout();
AUTOWHITE_RDLOCK;
b = &autowhite_buckets[BUCKET_HASH(sa, from, rcpt, AUTOWHITE_BUCKETS)];
pthread_mutex_lock(&b->bucket_mtx);
for (aw = TAILQ_FIRST(&b->b_autowhite_head); aw; aw = next_aw) {
next_aw = TAILQ_NEXT(aw, ab_list);
/*
* Do expiration first as we don't want
* an outdated record to match
* I've left this one too until the lists
* gets sorted
*/
if (aw->a_tv.tv_sec < now.tv_sec) {
char buf[IPADDRSTRLEN];
iptostring(aw->a_sa, aw->a_salen, buf, sizeof(buf));
mg_log(LOG_INFO, "(local): addr %s from %s rcpt %s: "
"autowhitelisted entry expired",
buf, aw->a_from, aw->a_rcpt);
autowhite_put(aw);
aw = NULL;
dirty++;
continue;
}
/*
* Look for our record
*/
if (ip_match(sa, aw->a_sa, mask) &&
((conf.c_lazyaw == 1) ||
((strcasecmp(from, aw->a_from) == 0) &&
(strcasecmp(rcpt, aw->a_rcpt) == 0)))) {
timeradd(&now, &delay, &aw->a_tv);
/* Push it at the back of the big queue */
pthread_mutex_lock(&autowhite_change_lock);
TAILQ_REMOVE(&autowhite_head, aw, a_list);
TAILQ_INSERT_TAIL(&autowhite_head, aw, a_list);
pthread_mutex_unlock(&autowhite_change_lock);
dirty++;
break;
}
}
pthread_mutex_unlock(&b->bucket_mtx);
AUTOWHITE_UNLOCK;
if (dirty != 0)
dump_dirty += dirty;
if (aw == NULL)
return EXF_NONE;
mg_log(LOG_INFO, "%s: addr %s from %s rcpt %s: "
"autowhitelisted for more %02d:%02d:%02d",
queueid, addr, from, rcpt, h, mn, s);
/*
* We need to tell our peers about this, we use a
* fictive pending record
*/
PENDING_WRLOCK;
pending = pending_get(sa, salen, from, rcpt, now.tv_sec + gldelay);
if (pending != NULL) {
peer_delete(pending, now.tv_sec + autowhite);
pending_put(pending);
}
PENDING_UNLOCK;
return EXF_AUTO;
}
int
autowhite_textdump(stream)
FILE *stream;
{
struct autowhite *aw;
int done = 0;
char textdate[DATELEN + 1];
char textaddr[IPADDRSTRLEN];
struct tm tm;
fprintf(stream, "\n\n#\n# Auto-whitelisted tuples\n#\n");
fprintf(stream, "# Sender IP\t%s\t%s\tExpire\n",
"Sender e-mail", "Recipient e-mail");
AUTOWHITE_RDLOCK;
pthread_mutex_lock(&autowhite_change_lock);
TAILQ_FOREACH(aw, &autowhite_head, a_list) {
iptostring(aw->a_sa, aw->a_salen, textaddr, sizeof(textaddr));
if (conf.c_dump_no_time_translation) {
fprintf(stream,
"%s\t%s\t%s\t%ld AUTO\n",
textaddr, aw->a_from, aw->a_rcpt,
(long)aw->a_tv.tv_sec);
} else {
localtime_r((time_t *)&aw->a_tv.tv_sec, &tm);
strftime(textdate, DATELEN, "%Y-%m-%d %T", &tm);
fprintf(stream,
"%s\t%s\t%s\t%ld AUTO # %s\n",
textaddr, aw->a_from, aw->a_rcpt,
(long)aw->a_tv.tv_sec, textdate);
}
done++;
}
pthread_mutex_unlock(&autowhite_change_lock);
AUTOWHITE_UNLOCK;
return done;
}
struct autowhite *
autowhite_get(sa, salen, from, rcpt, date) /* autowhite list must be locked */
struct sockaddr *sa;
socklen_t salen;
char *from;
char *rcpt;
time_t date;
{
struct autowhite *aw;
if ((aw = malloc(sizeof(*aw))) == NULL) {
mg_log(LOG_ERR, "malloc failed: %s", strerror(errno));
exit(EX_OSERR);
}
bzero((void *)aw, sizeof(*aw));
if ((aw->a_sa = malloc(salen)) == NULL ||
(aw->a_from = strdup(from)) == NULL ||
(aw->a_rcpt = strdup(rcpt)) == NULL) {
mg_log(LOG_ERR, "malloc failed: %s", strerror(errno));
exit(EX_OSERR);
}
aw->a_tv.tv_sec = date;
memcpy(aw->a_sa, sa, salen);
aw->a_salen = salen;
pthread_mutex_lock(&autowhite_change_lock);
TAILQ_INSERT_TAIL(&autowhite_head, aw, a_list);
TAILQ_INSERT_TAIL(&autowhite_buckets[BUCKET_HASH(aw->a_sa,
from, rcpt, AUTOWHITE_BUCKETS)].b_autowhite_head, aw, ab_list);
pthread_mutex_unlock(&autowhite_change_lock);
return aw;
}
void
autowhite_put(aw) /* autowhite list must be write-locked */
struct autowhite *aw;
{
pthread_mutex_lock(&autowhite_change_lock);
TAILQ_REMOVE(&autowhite_head, aw, a_list);
TAILQ_REMOVE(&autowhite_buckets[BUCKET_HASH(aw->a_sa,
aw->a_from, aw->a_rcpt, AUTOWHITE_BUCKETS)].b_autowhite_head,
aw, ab_list);
pthread_mutex_unlock(&autowhite_change_lock);
free(aw->a_sa);
free(aw->a_from);
free(aw->a_rcpt);
free(aw);
return;
}
int
autowhite_del_addr(sa, salen)
struct sockaddr *sa;
socklen_t salen;
{
struct autowhite *aw;
struct autowhite *next_aw;
int count = 0;
AUTOWHITE_WRLOCK;
for (aw = TAILQ_FIRST(&autowhite_head); aw; aw = next_aw) {
next_aw = TAILQ_NEXT(aw, a_list);
if (memcmp(sa, aw->a_sa, salen) == 0) {
char buf[IPADDRSTRLEN];
iptostring(aw->a_sa, aw->a_salen, buf, sizeof(buf));
mg_log(LOG_INFO, "(local): addr %s from %s rcpt %s: "
"autowhitelisted entry expired",
buf, aw->a_from, aw->a_rcpt);
autowhite_put(aw);
count++;
dump_dirty++;
}
break;
}
AUTOWHITE_UNLOCK;
return count;
}
syntax highlighted by Code2HTML, v. 0.9.1