Introduced smtp acl

Signed-off-by: Janos SUTO <sj@acts.hu>
This commit is contained in:
Janos SUTO 2020-12-27 23:40:39 +01:00
parent 48ec9b5c4a
commit f8a32dd025
16 changed files with 294 additions and 63 deletions

View File

@ -2,6 +2,7 @@
-------
- Added security header feature
- Introduced the smtp acl list, and obsoleted the tcp_wrappers check
1.3.9:

19
configure vendored
View File

@ -4714,23 +4714,6 @@ else
echo "zip library: no"
fi
if test "$have_tcpwrappers" = "yes"; then
echo "tcpwrappers support: yes"
cat >>confdefs.h <<_ACEOF
#define HAVE_LIBWRAP 1
_ACEOF
if test "$os" = "FreeBSD"; then
antispam_libs="$antispam_libs -lwrap"
else
antispam_libs="$antispam_libs -lwrap -lnsl"
fi
else
echo "tcpwrappers support: no"
fi
echo
@ -4860,7 +4843,7 @@ fi
CFLAGS="$static -std=c99 -O2 -fPIC -Wall -Wextra $extra_cflags -Wuninitialized -Wno-format-truncation -g"
LIBS="$antispam_libs $sunos_libs "
OBJS="dirs.o misc.o counters.o cfg.o sig.o decoder.o hash.o parser.o parser_utils.o rules.o smtp.o session.o bdat.o message.o attachment.o digest.o store.o archive.o tai.o import.o import_pilerexport.o import_maildir.o import_mailbox.o import_pop3.o import_imap.o imap.o pop3.o extract.o mydomains.o tokenizer.o $objs"
OBJS="dirs.o misc.o counters.o cfg.o sig.o decoder.o hash.o parser.o parser_utils.o rules.o smtp.o session.o bdat.o message.o attachment.o digest.o store.o archive.o tai.o import.o import_pilerexport.o import_maildir.o import_mailbox.o import_pop3.o import_imap.o imap.o pop3.o extract.o mydomains.o tokenizer.o screen.o $objs"
ac_config_files="$ac_config_files Makefile src/Makefile etc/Makefile util/Makefile init.d/Makefile systemd/Makefile unit_tests/Makefile webui/Makefile contrib/imap/Makefile"

View File

@ -433,19 +433,6 @@ else
echo "zip library: no"
fi
if test "$have_tcpwrappers" = "yes"; then
echo "tcpwrappers support: yes"
AC_DEFINE_UNQUOTED(HAVE_LIBWRAP, 1, [tcpwrappers support])
if test "$os" = "FreeBSD"; then
antispam_libs="$antispam_libs -lwrap"
else
antispam_libs="$antispam_libs -lwrap -lnsl"
fi
else
echo "tcpwrappers support: no"
fi
echo
@ -543,7 +530,7 @@ fi
CFLAGS="$static -std=c99 -O2 -fPIC -Wall -Wextra $extra_cflags -Wuninitialized -Wno-format-truncation -g"
LIBS="$antispam_libs $sunos_libs "
OBJS="dirs.o misc.o counters.o cfg.o sig.o decoder.o hash.o parser.o parser_utils.o rules.o smtp.o session.o bdat.o message.o attachment.o digest.o store.o archive.o tai.o import.o import_pilerexport.o import_maildir.o import_mailbox.o import_pop3.o import_imap.o imap.o pop3.o extract.o mydomains.o tokenizer.o $objs"
OBJS="dirs.o misc.o counters.o cfg.o sig.o decoder.o hash.o parser.o parser_utils.o rules.o smtp.o session.o bdat.o message.o attachment.o digest.o store.o archive.o tai.o import.o import_pilerexport.o import_maildir.o import_mailbox.o import_pop3.o import_imap.o imap.o pop3.o extract.o mydomains.o tokenizer.o screen.o $objs"
AC_CONFIG_FILES([Makefile src/Makefile etc/Makefile util/Makefile init.d/Makefile systemd/Makefile unit_tests/Makefile webui/Makefile contrib/imap/Makefile])
AC_OUTPUT

View File

@ -230,3 +230,13 @@ mmap_dedup_test=0
; mechanism against unwanted email in the archive if limiting smtp
; clients via an IP-address list is not feasible.
security_header=
; whether to enable (1) or not (0) an smtp access list similar to
; postfix's postscreen. If so, then create a text file %sysconfdir%/piler/smtp.acl
; An example for /usr/local/etc/piler/smtp.acl:
;
; 1.2.3.4/32 permit
; 10.0.1.0/24 reject
; 172.16.0.0/16 permit
;
smtp_access_list=0

View File

@ -26,8 +26,6 @@
#undef HAVE_TNEF
#undef HAVE_ZIP
#undef HAVE_LIBWRAP
#undef HAVE_TWEAK_SENT_TIME
#undef HAVE_SUPPORT_FOR_COMPAT_STORAGE_LAYOUT

View File

@ -48,7 +48,7 @@ libpiler.a: $(OBJS) $(SQL_OBJS)
ln -sf libpiler.so.$(LIBPILER_VERSION) libpiler.so.$(PILER_VERSION)
piler-smtp: piler-smtp.c libpiler.a
$(CC) $(CFLAGS) $(INCDIR) $(DEFS) -o $@ $< cfg.o misc.o tai.o smtp.o session.o dirs.o sig.o bdat.o $(LIBS) $(LIBDIR)
$(CC) $(CFLAGS) $(INCDIR) $(DEFS) -o $@ $< cfg.o misc.o tai.o smtp.o session.o dirs.o sig.o bdat.o screen.o $(LIBS) $(LIBDIR)
pilerget: pilerget.c libpiler.a
$(CC) $(CFLAGS) $(INCDIR) $(DEFS) -o $@ $< -lpiler $(LIBS) $(LIBDIR)

View File

@ -86,6 +86,7 @@ struct _parse_rule config_parse_rules[] =
{ "queuedir", "string", (void*) string_parser, offsetof(struct config, queuedir), QUEUE_DIR, MAXVAL-1},
{ "security_header", "string", (void*) string_parser, offsetof(struct config, security_header), "", MAXVAL-1},
{ "server_id", "integer", (void*) int_parser, offsetof(struct config, server_id), "0", sizeof(int)},
{ "smtp_access_list", "integer", (void*) int_parser, offsetof(struct config, smtp_access_list), "0", sizeof(int)},
{ "smtp_timeout", "integer", (void*) int_parser, offsetof(struct config, smtp_timeout), "60", sizeof(int)},
{ "spam_header_line", "string", (void*) string_parser, offsetof(struct config, spam_header_line), "", MAXVAL-1},
{ "syslog_recipients", "integer", (void*) int_parser, offsetof(struct config, syslog_recipients), "0", sizeof(int)},

View File

@ -99,6 +99,8 @@ struct config {
int enable_folders;
int debug;
int smtp_access_list;
};

View File

@ -14,6 +14,7 @@
#define HOSTID "mailarchiver"
#define CONFIG_FILE CONFDIR "/piler/piler.conf"
#define SMTP_ACL_FILE CONFDIR "/piler/smtp.acl"
#define WORK_DIR DATADIR "/piler/tmp"
#define QUEUE_DIR DATADIR "/piler/store"
#define ERROR_DIR DATADIR "/piler/error"

View File

@ -13,9 +13,6 @@
#include <tre/tre.h>
#include <tre/regex.h>
#endif
#ifdef HAVE_LIBWRAP
#include <tcpd.h>
#endif
#include <openssl/sha.h>
#include <openssl/ssl.h>
@ -98,6 +95,15 @@ struct node {
};
struct smtp_acl {
char network_str[BUFLEN];
in_addr_t low, high;
int prefix;
int rejected;
struct smtp_acl *r;
};
struct net {
int socket;
int use_ssl;

View File

@ -38,6 +38,7 @@ char *configfile = CONFIG_FILE;
struct config cfg;
struct passwd *pwd;
struct smtp_session *session, **sessions=NULL;
struct smtp_acl *smtp_acl[MAXHASH];
void usage(){
@ -66,6 +67,8 @@ void p_clean_exit(int sig){
if(events) free(events);
clear_smtp_acl(smtp_acl);
syslog(LOG_PRIORITY, "%s has been terminated", PROGNAME);
ERR_free_strings();
@ -120,6 +123,8 @@ void initialise_configuration(){
setlocale(LC_MESSAGES, cfg.locale);
setlocale(LC_CTYPE, cfg.locale);
load_smtp_acl(smtp_acl);
syslog(LOG_PRIORITY, "reloaded config: %s", configfile);
}
@ -270,7 +275,7 @@ int main(int argc, char **argv){
break;
}
start_new_session(sessions, client_sockfd, &num_connections, &cfg);
start_new_session(sessions, client_sockfd, &num_connections, smtp_acl, hbuf, &cfg);
}
continue;

View File

@ -17,6 +17,7 @@
#include <sig.h>
#include <av.h>
#include <rules.h>
#include <screen.h>
#include <sql.h>
#include <import.h>
#include <smtp.h>
@ -68,7 +69,7 @@ int retrieve_file_from_archive(char *filename, int mode, char **buffer, FILE *de
void load_mydomains(struct session_data *sdata, struct data *data, struct config *cfg);
int is_email_address_on_my_domains(char *email, struct data *data);
int start_new_session(struct smtp_session **sessions, int socket, int *num_connections, struct config *cfg);
int start_new_session(struct smtp_session **sessions, int socket, int *num_connections, struct smtp_acl *smtp_acl[], char *client_addr, struct config *cfg);
void tear_down_session(struct smtp_session **sessions, int slot, int *num_connections);
struct smtp_session *get_session_by_socket(struct smtp_session **sessions, int max_connections, int socket);
void write_envelope_addresses(struct smtp_session *session, struct config *cfg);

237
src/screen.c Normal file
View File

@ -0,0 +1,237 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <netdb.h>
#include <piler.h>
void init_smtp_acl(struct smtp_acl *smtp_acl[]){
smtp_acl[0] = NULL;
}
void clear_smtp_acl(struct smtp_acl *smtp_acl[]){
struct smtp_acl *q;
q = smtp_acl[0];
while(q){
struct smtp_acl *p = q;
q = q->r;
free(p);
}
smtp_acl[0] = NULL;
}
int add_smtp_acl(struct smtp_acl *smtp_acl[], char *network_str, struct smtp_acl *acl){
struct smtp_acl *q, *p=NULL, *node;
if((node = malloc(sizeof(struct smtp_acl))) == NULL) return 0;
memset(node, 0, sizeof(struct smtp_acl));
node->low = acl->low;
node->high = acl->high;
node->prefix = acl->prefix;
node->rejected = acl->rejected;
snprintf(node->network_str, sizeof(node->network_str)-1, "%s", network_str);
node->r = NULL;
q = smtp_acl[0];
while(q){
p = q;
q = q->r;
}
if(!p){
smtp_acl[0] = node;
} else {
p->r = node;
}
return 1;
}
int is_valid_line(char *line){
// Skip comments
if(line[0] == ';' || line[0] == '#') return 0;
trimBuffer(line);
// Skip empty line
if(line[0] == 0) return 0;
// Currently we support ipv4 stuff only, ie. valid characters are: 0-9./
// and a line should look like "1.2.3.4/24 permit" or similar (without quotes)
if(!strchr(line, '.') || !strchr(line, '/') || (!strchr(line, ' ') && !strchr(line, '\t')) ){
return -1;
}
// ascii values:
// 46: .
// 47: /
// 48-57: 0-9
// 65-90: A-Z
// 97-122: a-z
for(; *line; line++){
if(isalnum(*line) == 0 && isblank(*line) == 0 && *line != 46 && *line != 47) return -1;
}
return 1;
}
int a_to_hl(char *ipstr, in_addr_t *addr){
struct in_addr in;
if(inet_aton(ipstr, &in) == 1){
*addr = ntohl(in.s_addr);
return 1;
}
return 0;
}
in_addr_t netmask(int prefix){
if(prefix == 0)
return( ~((in_addr_t) -1) );
else
return ~((1 << (32 - prefix)) - 1);
}
in_addr_t network(in_addr_t addr, int prefix){
return addr & netmask(prefix);
}
in_addr_t broadcast(in_addr_t addr, int prefix){
return addr | ~netmask(prefix);
}
int str_to_net_range(char *network_addr_prefix, struct smtp_acl *smtp_acl){
in_addr_t net = 0;
int prefix = 0;
smtp_acl->low = 0;
smtp_acl->high = 0;
// By default we permit unless you specify "reject" (without quotes)
// To be on the safer side we permit even if you misspell the word "reject"
smtp_acl->rejected = 0;
if(strcasestr(network_addr_prefix, "reject")){
smtp_acl->rejected = 1;
}
char *p = strchr(network_addr_prefix, '/');
if(!p) return 0;
if(strlen(network_addr_prefix) > sizeof(smtp_acl->network_str)){
syslog(LOG_PRIORITY, "line *%s* is longer than %ld bytes, discarded", network_addr_prefix, sizeof(smtp_acl->network_str));
return 0;
}
char buf[SMALLBUFSIZE];
snprintf(buf, sizeof(buf)-1, "%s", network_addr_prefix);
*p = '\0';
prefix = atoi(++p);
if(a_to_hl(network_addr_prefix, &net)){
smtp_acl->low = network(net, prefix);
smtp_acl->high = broadcast(net, prefix);
smtp_acl->prefix = prefix;
syslog(LOG_PRIORITY, "info: parsed acl *%s* to low: %u, high: %u, prefix: %d, reject: %d", buf, smtp_acl->low, smtp_acl->high, smtp_acl->prefix, smtp_acl->rejected);
return 1;
}
return 0;
}
void load_smtp_acl(struct smtp_acl *smtp_acl[]){
int count=0;
clear_smtp_acl(smtp_acl);
init_smtp_acl(smtp_acl);
FILE *f = fopen(SMTP_ACL_FILE, "r");
if(!f){
syslog(LOG_PRIORITY, "info: cannot open %s, piler-smtp accepts smtp connections from everywhere", SMTP_ACL_FILE);
return;
}
char line[SMALLBUFSIZE];
struct smtp_acl acl;
while(fgets(line, sizeof(line)-1, f)){
int rc = is_valid_line(line);
if(rc < 0){
syslog(LOG_PRIORITY, "warn: invalid network range: *%s*", line);
}
if(rc == 1 && str_to_net_range(line, &acl) == 1){
add_smtp_acl(smtp_acl, line, &acl);
count++;
}
}
fclose(f);
// If we have entries on the smtp acl list, then add 127.0.0.1/8
if(count){
snprintf(line, sizeof(line)-1, "127.0.0.1/8 permit");
if(str_to_net_range(line, &acl) == 1){
add_smtp_acl(smtp_acl, line, &acl);
}
}
}
int is_blocked_by_pilerscreen(struct smtp_acl *smtp_acl[], char *ipaddr, struct config *cfg){
struct smtp_acl *q;
in_addr_t addr = 0;
if(a_to_hl(ipaddr, &addr) == 0){
syslog(LOG_PRIORITY, "error: invalid smtp client address: *%s*", ipaddr);
return 1;
}
q = smtp_acl[0];
// Empty network ranges list
if(!q){
syslog(LOG_PRIORITY, "info: empty network ranges list, pass");
return 0;
}
while(q){
if(addr >= q->low && addr <= q->high){
if(cfg->verbosity >= _LOG_DEBUG) syslog(LOG_PRIORITY, "info: smtp client %s is on %s/%d rejected: %d", ipaddr, q->network_str, q->prefix, q->rejected);
return q->rejected;
}
q = q->r;
}
return 1;
}

16
src/screen.h Normal file
View File

@ -0,0 +1,16 @@
/*
* rules.h, SJ
*/
#ifndef _NETRANGE_H
#define _NETRANGE_H
#include "defs.h"
void init_smtp_acl(struct smtp_acl *smtp_acl[]);
void clear_smtp_acl(struct smtp_acl *smtp_acl[]);
int add_smtp_acl(struct smtp_acl *smtp_acl[], char *network_str, struct smtp_acl *acl);
void load_smtp_acl(struct smtp_acl *smtp_acl[]);
int is_blocked_by_pilerscreen(struct smtp_acl *smtp_acl[], char *ipaddr, struct config *cfg);
#endif /* _NETRANGE_H */

View File

@ -10,26 +10,7 @@ int get_session_slot(struct smtp_session **sessions, int max_connections);
void init_smtp_session(struct smtp_session *session, int slot, int sd, struct config *cfg);
#ifdef HAVE_LIBWRAP
int is_blocked_by_tcp_wrappers(int sd){
struct request_info req;
request_init(&req, RQ_DAEMON, "piler", RQ_FILE, sd, 0);
fromhost(&req);
if(!hosts_access(&req)){
send(sd, SMTP_RESP_550_ERR_YOU_ARE_BANNED_BY_LOCAL_POLICY, strlen(SMTP_RESP_550_ERR_YOU_ARE_BANNED_BY_LOCAL_POLICY), 0);
syslog(LOG_PRIORITY, "denied connection from %s by tcp_wrappers", eval_client(&req));
return 1;
}
return 0;
}
#endif
int start_new_session(struct smtp_session **sessions, int socket, int *num_connections, struct config *cfg){
int start_new_session(struct smtp_session **sessions, int socket, int *num_connections, struct smtp_acl *smtp_acl[], char *client_addr, struct config *cfg){
int slot;
/*
@ -43,12 +24,13 @@ int start_new_session(struct smtp_session **sessions, int socket, int *num_conne
return -1;
}
#ifdef HAVE_LIBWRAP
if(is_blocked_by_tcp_wrappers(socket) == 1){
// Check remote client against the allowed network ranges
if(cfg->smtp_access_list && is_blocked_by_pilerscreen(smtp_acl, client_addr, cfg)){
send(socket, SMTP_RESP_550_ERR, strlen(SMTP_RESP_550_ERR), 0);
close(socket);
syslog(LOG_PRIORITY, "denied connection from %s by %s", client_addr, SMTP_ACL_FILE);
return -1;
}
#endif
slot = get_session_slot(sessions, cfg->max_connections);

View File

@ -55,6 +55,7 @@
#define SMTP_RESP_502_ERR "502 Command not implemented\r\n"
#define SMTP_RESP_503_ERR "503 Bad command sequence\r\n"
#define SMTP_RESP_550_ERR_YOU_ARE_BANNED_BY_LOCAL_POLICY "550 You are banned by local policy\r\n"
#define SMTP_RESP_550_ERR "550 Service currently unavailable\r\n"
// LMTP commands