diff --git a/RELEASE_NOTES b/RELEASE_NOTES index 60c346e7..dfc9566a 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -2,6 +2,7 @@ ------- - Added security header feature +- Introduced the smtp acl list, and obsoleted the tcp_wrappers check 1.3.9: diff --git a/configure b/configure index 7eaab2d9..c195b24c 100755 --- a/configure +++ b/configure @@ -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" diff --git a/configure.in b/configure.in index 8c50891d..2e522ba1 100644 --- a/configure.in +++ b/configure.in @@ -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 diff --git a/etc/example.conf b/etc/example.conf index 4816aa9e..44e8ec57 100644 --- a/etc/example.conf +++ b/etc/example.conf @@ -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 diff --git a/piler-config.h.in b/piler-config.h.in index 416831b5..2352532a 100644 --- a/piler-config.h.in +++ b/piler-config.h.in @@ -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 diff --git a/src/Makefile.in b/src/Makefile.in index abc99656..6eed82b8 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -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) diff --git a/src/cfg.c b/src/cfg.c index 023d04e6..b348a81a 100644 --- a/src/cfg.c +++ b/src/cfg.c @@ -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)}, diff --git a/src/cfg.h b/src/cfg.h index 6932a6c4..77373869 100644 --- a/src/cfg.h +++ b/src/cfg.h @@ -99,6 +99,8 @@ struct config { int enable_folders; int debug; + + int smtp_access_list; }; diff --git a/src/config.h b/src/config.h index 7d110348..5810c5b0 100644 --- a/src/config.h +++ b/src/config.h @@ -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" diff --git a/src/defs.h b/src/defs.h index 6bfece2e..ff8be3a4 100644 --- a/src/defs.h +++ b/src/defs.h @@ -13,9 +13,6 @@ #include #include #endif -#ifdef HAVE_LIBWRAP - #include -#endif #include #include @@ -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; diff --git a/src/piler-smtp.c b/src/piler-smtp.c index 41cbca46..b5fd2623 100644 --- a/src/piler-smtp.c +++ b/src/piler-smtp.c @@ -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; diff --git a/src/piler.h b/src/piler.h index e41e2334..a38be51e 100644 --- a/src/piler.h +++ b/src/piler.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -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); diff --git a/src/screen.c b/src/screen.c new file mode 100644 index 00000000..0f3fda45 --- /dev/null +++ b/src/screen.c @@ -0,0 +1,237 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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; +} diff --git a/src/screen.h b/src/screen.h new file mode 100644 index 00000000..7574fd61 --- /dev/null +++ b/src/screen.h @@ -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 */ diff --git a/src/session.c b/src/session.c index b1cacb7e..d404e073 100644 --- a/src/session.c +++ b/src/session.c @@ -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); diff --git a/src/smtpcodes.h b/src/smtpcodes.h index ee0d2afc..2a9943d5 100644 --- a/src/smtpcodes.h +++ b/src/smtpcodes.h @@ -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