NetBSD/dist/pf/libexec/spamlogd/spamlogd.c

263 lines
5.6 KiB
C

/* $NetBSD: spamlogd.c,v 1.5 2004/11/16 05:14:12 yamt Exp $ */
/* $OpenBSD: spamlogd.c,v 1.9 2004/08/10 16:06:01 otto Exp $ */
/*
* Copyright (c) 2004 Bob Beck. All rights reserved.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* watch pf log for mail connections, update whitelist entries. */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <db.h>
#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
#include <string.h>
#include <unistd.h>
#include "grey.h"
#define PATH_TCPDUMP "/usr/sbin/tcpdump"
struct syslog_data sdata = SYSLOG_DATA_INIT;
int inbound; /* do we only whitelist inbound smtp? */
extern char *__progname;
int dbupdate(char *, char *);
int
dbupdate(char *dbname, char *ip)
{
BTREEINFO btreeinfo;
DBT dbk, dbd;
DB *db;
struct gdata gd;
time_t now;
int r;
struct in_addr ia;
now = time(NULL);
memset(&btreeinfo, 0, sizeof(btreeinfo));
db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_BTREE, &btreeinfo);
if (db == NULL)
return(-1);
if (inet_pton(AF_INET, ip, &ia) != 1) {
syslog_r(LOG_NOTICE, &sdata, "invalid ip address %s", ip);
goto bad;
}
memset(&dbk, 0, sizeof(dbk));
dbk.size = strlen(ip);
dbk.data = ip;
memset(&dbd, 0, sizeof(dbd));
/* add or update whitelist entry */
r = db->get(db, &dbk, &dbd, 0);
if (r == -1) {
syslog_r(LOG_NOTICE, &sdata, "db->get failed (%m)");
goto bad;
}
if (r) {
/* new entry */
memset(&gd, 0, sizeof(gd));
gd.first = now;
gd.bcount = 1;
gd.pass = now;
gd.expire = now + WHITEEXP;
memset(&dbk, 0, sizeof(dbk));
dbk.size = strlen(ip);
dbk.data = ip;
memset(&dbd, 0, sizeof(dbd));
dbd.size = sizeof(gd);
dbd.data = &gd;
r = db->put(db, &dbk, &dbd, 0);
if (r) {
syslog_r(LOG_NOTICE, &sdata, "db->put failed (%m)");
goto bad;
}
} else {
if (dbd.size != sizeof(gd)) {
/* whatever this is, it doesn't belong */
db->del(db, &dbk, 0);
goto bad;
}
memcpy(&gd, dbd.data, sizeof(gd));
gd.pcount++;
gd.expire = now + WHITEEXP;
memset(&dbk, 0, sizeof(dbk));
dbk.size = strlen(ip);
dbk.data = ip;
memset(&dbd, 0, sizeof(dbd));
dbd.size = sizeof(gd);
dbd.data = &gd;
r = db->put(db, &dbk, &dbd, 0);
if (r) {
syslog_r(LOG_NOTICE, &sdata, "db->put failed (%m)");
goto bad;
}
}
db->close(db);
db = NULL;
return (0);
bad:
db->close(db);
db = NULL;
return (-1);
}
static int
usage(void)
{
fprintf(stderr, "usage: %s [-I] [-i interface]\n", __progname);
exit(1);
}
char *targv[17] = {
"tcpdump", "-l", "-n", "-e", "-i", "pflog0", "-q",
"-t", "port", "25", "and", "action", "pass",
NULL, NULL, NULL, NULL
};
int
main(int argc, char **argv)
{
int ch, p[2];
char *buf, *lbuf;
size_t len;
pid_t pid;
FILE *f;
while ((ch = getopt(argc, argv, "i:I")) != -1) {
switch (ch) {
case 'i':
if (targv[15]) /* may only set once */
usage();
targv[13] = "and";
targv[14] = "on";
targv[15] = optarg;
break;
case 'I':
inbound = 1;
break;
default:
usage();
break;
}
}
if (daemon(1, 1) == -1)
err(1, "daemon");
if (pipe(p) == -1)
err(1, "pipe");
switch (pid = fork()) {
case -1:
err(1, "fork");
case 0:
/* child */
close(p[0]);
close(STDERR_FILENO);
if (dup2(p[1], STDOUT_FILENO) == -1) {
warn("dup2");
_exit(1);
}
close(p[1]);
execvp(PATH_TCPDUMP, targv);
warn("exec of %s failed", PATH_TCPDUMP);
_exit(1);
}
/* parent */
close(p[1]);
f = fdopen(p[0], "r");
if (f == NULL)
err(1, "fdopen");
tzset();
openlog_r("spamlogd", LOG_PID | LOG_NDELAY, LOG_DAEMON, &sdata);
lbuf = NULL;
while ((buf = fgetln(f, &len))) {
char *cp = NULL;
char *buf2;
if ((buf2 = malloc(len + 1)) == NULL) {
syslog_r(LOG_ERR, &sdata, "malloc failed");
exit(1);
}
if (buf[len - 1] == '\n')
buf[len - 1] = '\0';
else {
if ((lbuf = (char *)malloc(len + 1)) == NULL) {
syslog_r(LOG_ERR, &sdata, "malloc failed");
exit(1);
}
memcpy(lbuf, buf, len);
lbuf[len] = '\0';
buf = lbuf;
}
if (!inbound && strstr(buf, "pass out") != NULL) {
/*
* this is outbound traffic - we whitelist
* the destination address, because we assume
* that a reply may come to this outgoing mail
* we are sending.
*/
if ((cp = (strchr(buf, '>'))) != NULL) {
if (sscanf(cp, "> %s", buf2) == 1) {
cp = strrchr(buf2, '.');
if (cp != NULL) {
*cp = '\0';
cp = buf2;
syslog_r(LOG_DEBUG, &sdata,
"outbound %s\n", cp);
}
} else
cp = NULL;
}
} else {
/*
* this is inbound traffic - we whitelist
* the source address, because this is
* traffic presumably to our real MTA
*/
if ((cp = (strchr(buf, '>'))) != NULL) {
while (*cp != '.' && cp >= buf) {
*cp = '\0';
cp--;
}
*cp ='\0';
while (*cp != ' ' && cp >= buf)
cp--;
cp++;
syslog_r(LOG_DEBUG, &sdata,
"inbound %s\n", cp);
}
}
if (cp != NULL)
dbupdate(PATH_SPAMD_DB, cp);
free(lbuf);
lbuf = NULL;
free(buf2);
}
exit(0);
}