Skip to content

Commit

Permalink
Merge pull request #74 from jcbf/feature/ClientIPNAT
Browse files Browse the repository at this point in the history
Feature/client ipnat
  • Loading branch information
jcbf committed Jul 14, 2020
2 parents 543dad7 + adcdcda commit c0e06e3
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 0 deletions.
18 changes: 18 additions & 0 deletions smf-spf-tests-natip.conf
@@ -0,0 +1,18 @@
LogTo /dev/stdout
WhitelistIP 192.168.0.0/16
RefuseFail off # (on|off)
TTL 1D
relaxedlocalpart on
tagsubject off
ClientIPNAT 127.0.0.ss:54.154.127.152
ClientIPNAT 127.0.0.3:192.168.0.3
ClientIPNAT 127.0.0.2:192.168.0.1
ClientIPNAT 127.0.0.10:5aa4.154.126.152
ClientIPNAT 127.0.0.10
ClientIPNAT 127.0.0.1:54.154.127.152
RefuseFail on
User nobody
Socket inet:2424@127.0.0.1
Syslog none
Daemonize off # (on|off)
AuthservID mail.example.com
73 changes: 73 additions & 0 deletions smf-spf.c
Expand Up @@ -126,6 +126,12 @@ typedef struct CIDR {
struct CIDR *next;
} CIDR;

typedef struct IPNAT {
unsigned long srcip;
unsigned long destip;
struct IPNAT *next;
} IPNAT;

typedef struct STR {
char *str;
struct STR *next;
Expand All @@ -137,6 +143,7 @@ typedef struct config {
FILE *log_file;
char *run_as_user;
char *sendmail_socket;
IPNAT *ipnats;
CIDR *cidrs;
STR *ptrs;
STR *froms;
Expand Down Expand Up @@ -372,6 +379,15 @@ static void free_config(void) {
SAFE_FREE(conf.reject_reason);
if (conf.log_file != NULL)
fclose(conf.log_file);

if (conf.ipnats) {
IPNAT *it = conf.ipnats, *it_next;
while (it) {
it_next = it->next;
SAFE_FREE(it);
it = it_next;
}
}
if (conf.cidrs) {
CIDR *it = conf.cidrs, *it_next;

Expand Down Expand Up @@ -474,6 +490,48 @@ static int load_config(void) {
}
continue;
}
if (!strcasecmp(key, "clientipnat")) {
char *sep = NULL;
unsigned long d_ip;

if ((sep = strchr(val, ':'))) {
*sep++ = '\0';
if (*sep && !regexec(&re_ipv4, sep, 0, NULL, 0)) {
if ((d_ip = inet_addr(sep)) == 0xffffffff) {
log_message(LOG_ERR, "[CONFIG ERROR] ignore nat dest error:%s (entry:%s)", sep, buf);
continue;
}
} else {
log_message(LOG_ERR, "[CONFIG ERROR] invalid destination ip (%s)", sep);
continue;
}
} else {
log_message(LOG_ERR, "[CONFIG ERROR] invalid entry(%s). Must be src_ip:dest:ip", val);
continue;
}
if (val[0] && !regexec(&re_ipv4, val, 0, NULL, 0)) {
IPNAT *it = NULL;
unsigned long s_ip;

if ((s_ip = inet_addr(val)) == 0xffffffff) {
log_message(LOG_ERR, "[CONFIG ERROR] ignore nat src error:%s (entry:%s/int:%li)", val, buf,s_ip);
continue;
}
if (!conf.ipnats)
conf.ipnats = (IPNAT *) calloc(1, sizeof(IPNAT));
else
if ((it = (IPNAT *) calloc(1, sizeof(IPNAT)))) {
it->next = conf.ipnats;
conf.ipnats = it;
}
if (conf.ipnats) {
conf.ipnats->srcip = s_ip;
conf.ipnats->destip = d_ip;
}
} else
log_message(LOG_ERR, "[CONFIG ERROR] entry(%s) is not a valid IP address", val);
continue;
}
if (!strcasecmp(key, "whitelistptr")) {
STR *it = NULL;

Expand Down Expand Up @@ -646,6 +704,15 @@ static int ip_check(const unsigned long checkip) {
return 0;
}

static unsigned long natip_check(const unsigned long checkip) {
IPNAT *it = conf.ipnats;
while (it) {
if (it->srcip == checkip) return it->destip;
it = it->next;
}
return 0;
}

static int ptr_check(const char *ptr) {
STR *it = conf.ptrs;

Expand Down Expand Up @@ -729,6 +796,7 @@ static void add_rcpt(struct context *context) {
static sfsistat smf_connect(SMFICTX *ctx, char *name, _SOCK_ADDR *sa) {
struct context *context = NULL;
char host[64];
unsigned long int d_ip;

if (authserv_id == NULL) {
char* p = NULL;
Expand Down Expand Up @@ -770,6 +838,11 @@ static sfsistat smf_connect(SMFICTX *ctx, char *name, _SOCK_ADDR *sa) {
return SMFIS_ACCEPT; // LCOV_EXCL_LINE
}
smfi_setpriv(ctx, context);
if (conf.ipnats && (d_ip = natip_check(inet_addr(host)))) {
snprintf(context->addr, sizeof(context->addr), "%li.%li.%li.%li",
d_ip >> 24 & 0xff, d_ip >> 16 & 0xff, d_ip >> 8 & 0xff, d_ip & 0xff);
log_message(LOG_INFO, "Found NAT IP address. Original: %s . Final %li (%s)", host, d_ip,context->addr);
}
if (conf.fixed_ip)
strscpy(context->addr, conf.fixed_ip, sizeof(context->addr) - 1);
else
Expand Down
9 changes: 9 additions & 0 deletions smf-spf.conf
Expand Up @@ -44,6 +44,15 @@ WhitelistIP 192.168.0.0/16
#
#FixedClientIP 192.0.0.1

# ClientIPNAT allows IP address translation of the connecting IP
# This is particular useful when you have internal email flows
# and still have a SPF evaluation
#
# Default: none
#
#ClientIPNAT 10.0.0.1:192.0.0.1
#ClientIPNAT 127.0.0.1:192.0.0.3

# RejectReason specifies the message that will be return to milter client
# You can use %s placeholders where :
# 1st %s - sender address or postmaster@<helo name> if empty sender
Expand Down
81 changes: 81 additions & 0 deletions tests/04-fulltest-nat-pass.lua
@@ -0,0 +1,81 @@
-- Copyright (c) 2009-2013, The Trusted Domain Project. All rights reserved.
mt.echo("SPF ClientIPNAT pass. Using ./smf-spf-tests-natip.conf")

-- try to start the filter
mt.startfilter("./smf-spf", "-f", "-c","./smf-spf-tests-natip.conf")

-- try to connect to it
conn = mt.connect("inet:2424@127.0.0.1", 40, 0.25)
if conn == nil then
error("mt.connect() failed")
end

-- send connection information
-- mt.negotiate() is called implicitly
mt.macro(conn, SMFIC_CONNECT, "j", "mta.name.local")
if mt.conninfo(conn, "localhost", "127.0.0.1") ~= nil then
error("mt.conninfo() failed")
end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error("mt.conninfo() unexpected reply")
end

-- send envelope macros and sender data
-- mt.helo() is called implicitly
mt.macro(conn, SMFIC_MAIL, "i", "t-verify-malformed")
if mt.mailfrom(conn, "<user@underspell.com>") ~= nil then
error("mt.mailfrom() failed")
end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error("mt.mailfrom() unexpected reply")
end

mt.macro(conn, SMFIC_RCPT, "i", "t-verify-malformed")
if mt.rcptto(conn, "<user@example.net>") ~= nil then
error("mt.rcptto() failed")
end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error("mt.rcptto() unexpected reply")
end

-- send headers
-- mt.rcptto() is called implicitly
if mt.header(conn, "From", "user") ~= nil then
error("mt.header(From) failed")
end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error("mt.header(From) unexpected reply")
end
if mt.header(conn, "Date", "Tue, 22 Dec 2009 13:04:12 -0800") ~= nil then
error("mt.header(Date) failed")
end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error("mt.header(Date) unexpected reply")
end
if mt.header(conn, "Subject", "Signing test") ~= nil then
error("mt.header(Subject) failed")
end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error("mt.header(Subject) unexpected reply")
end

-- end of message; let the filter react
if mt.eom(conn) ~= nil then
error("mt.eom() failed")
end

-- verify that the right Authentication-Results header field got added
if mt.eom_check(conn, MT_HDRINSERT, "Authentication-Results") or
mt.eom_check(conn, MT_HDRADD, "Authentication-Results") then
ar = mt.getheader(conn, "Authentication-Results", 0)
if string.find(ar, "spf=fail", 1) then
error("incorrect Authentication-Results field : " .. ar)
else
mt.echo("SPF pass ")
end
else
mt.echo ("Got header Authentication-Results: " .. ar)
error("missing Authentication-Results field")
end

mt.disconnect(conn)

0 comments on commit c0e06e3

Please sign in to comment.