Skip to content

Commit 4a72744

Browse files
committed
Add SMTP TLS and authentication
Enable authenticated and TLS SMTP for ossec-maild when built with USE_CURL=yes (off by default). Uses libcurl for SMTP AUTH (PLAIN/LOGIN) and TLS/STARTTLS; credentials and TLS are validated and sanitized. Security hardening: header/envelope CR/LF sanitization, hostname validation for smtp_server, timeouts, mandatory TLS when AUTH is on, post-parse credential validation, and secure clearing of password in config and at exit. CA bundle and chroot ossec-maild runs inside a chroot (e.g. /var/ossec). libcurl uses CURLOPT_SSL_VERIFYPEER=1 and by default looks for the system CA bundle (e.g. /etc/ssl/certs/ca-certificates.crt). After chroot, that path is not visible, so TLS verification fails (CURLE_PEER_FAILED_VERIFICATION) and mail is dropped unless the CA bundle is available inside the chroot. Installation (or the admin) must copy or symlink the system CA bundle into the chroot (e.g. <chroot>/etc/ssl/certs/ca-certificates.crt) and either set CURLOPT_CAINFO to that path in code or ensure the default path resolves inside the chroot. Do not disable VERIFYPEER. Original idea and initial implementation from alexbartlow via Allow TLS Email sends as a compile-time option ossec#1360 Credit: alexbartlow (PR ossec#1360) Signed-off-by: Scott R. Shinn <scott@atomicorp.com>
1 parent ddcd096 commit 4a72744

File tree

9 files changed

+493
-2
lines changed

9 files changed

+493
-2
lines changed

src/Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ INSTALL_RESOLVCONF?=yes
2727
USE_PRELUDE?=no
2828
USE_ZEROMQ?=no
2929
USE_GEOIP?=no
30+
USE_CURL?=no
3031
USE_INOTIFY=no
3132
USE_PCRE2_JIT=yes
3233
USE_SYSTEMD?=yes
@@ -259,6 +260,11 @@ ifneq (,$(filter ${USE_GEOIP},auto yes y Y 1))
259260
OSSEC_LDFLAGS+=-lGeoIP
260261
endif # USE_GEOIP
261262

263+
ifneq (,$(filter ${USE_CURL},yes y Y 1))
264+
DEFINES+=-DUSE_SMTP_CURL
265+
OSSEC_LDFLAGS+=-lcurl
266+
endif # USE_CURL
267+
262268
ifneq (,$(filter ${USE_SQLITE},auto yes y Y 1))
263269
DEFINES+=-DSQLITE_ENABLED
264270
ANALYSISD_FLAGS="-lsqlite3"
@@ -618,6 +624,7 @@ settings:
618624
@echo "USE settings:"
619625
@echo " USE_ZEROMQ: ${USE_ZEROMQ}"
620626
@echo " USE_GEOIP: ${USE_GEOIP}"
627+
@echo " USE_CURL: ${USE_CURL}"
621628
@echo " USE_PRELUDE: ${USE_PRELUDE}"
622629
@echo " USE_OPENSSL: ${USE_OPENSSL}"
623630
@echo " USE_INOTIFY: ${USE_INOTIFY}"

src/config/global-config.c

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ int Read_Global(XML_NODE node, void *configp, void *mailp)
122122
const char *xml_smtpserver = "smtp_server";
123123
const char *xml_heloserver = "helo_server";
124124
const char *xml_mailmaxperhour = "email_maxperhour";
125+
const char *xml_auth_smtp = "auth_smtp";
126+
const char *xml_smtp_user = "smtp_user";
127+
const char *xml_smtp_password = "smtp_password";
128+
const char *xml_secure_smtp = "secure_smtp";
129+
const char *xml_smtp_port = "smtp_port";
125130

126131
#ifdef LIBGEOIP_ENABLED
127132
const char *xml_geoip_db_path = "geoip_db_path";
@@ -453,17 +458,82 @@ int Read_Global(XML_NODE node, void *configp, void *mailp)
453458
}
454459
os_strdup(node[i]->content, Mail->idsname);
455460
}
461+
} else if (strcmp(node[i]->element, xml_auth_smtp) == 0) {
462+
if (strcmp(node[i]->content, "yes") == 0) {
463+
if (Config) {
464+
Config->authsmtp = 1;
465+
}
466+
if (Mail) {
467+
Mail->authsmtp = 1;
468+
}
469+
} else if (strcmp(node[i]->content, "no") == 0) {
470+
if (Config) {
471+
Config->authsmtp = 0;
472+
}
473+
if (Mail) {
474+
Mail->authsmtp = 0;
475+
}
476+
} else {
477+
return (OS_INVALID);
478+
}
479+
} else if (strcmp(node[i]->element, xml_secure_smtp) == 0) {
480+
if (strcmp(node[i]->content, "yes") == 0) {
481+
if (Config) {
482+
Config->securesmtp = 1;
483+
}
484+
if (Mail) {
485+
Mail->securesmtp = 1;
486+
}
487+
} else if (strcmp(node[i]->content, "no") == 0) {
488+
if (Config) {
489+
Config->securesmtp = 0;
490+
}
491+
if (Mail) {
492+
Mail->securesmtp = 0;
493+
}
494+
} else {
495+
return (OS_INVALID);
496+
}
497+
} else if (strcmp(node[i]->element, xml_smtp_user) == 0) {
498+
if (Mail) {
499+
if (Mail->smtp_user) {
500+
free(Mail->smtp_user);
501+
}
502+
os_strdup(node[i]->content, Mail->smtp_user);
503+
}
504+
} else if (strcmp(node[i]->element, xml_smtp_password) == 0) {
505+
if (Mail) {
506+
if (Mail->smtp_pass) {
507+
memset_secure(Mail->smtp_pass, 0, strlen(Mail->smtp_pass));
508+
free(Mail->smtp_pass);
509+
}
510+
os_strdup(node[i]->content, Mail->smtp_pass);
511+
}
456512
} else if (strcmp(node[i]->element, xml_smtpserver) == 0) {
457513
#ifndef WIN32
458514
if (Mail && (Mail->mn)) {
459515
if (node[i]->content[0] == '/') {
460516
os_strdup(node[i]->content, Mail->smtpserver);
461517
} else {
518+
#ifdef USE_SMTP_CURL
519+
/* Pre-resolve for CURLOPT_RESOLVE; DNS is unavailable after chroot */
520+
if (Mail->smtpserver_resolved) {
521+
free(Mail->smtpserver_resolved);
522+
Mail->smtpserver_resolved = NULL;
523+
}
524+
Mail->smtpserver_resolved = OS_GetHost(node[i]->content, 5);
525+
if (!Mail->smtpserver_resolved) {
526+
merror(INVALID_SMTP, __local_name, node[i]->content);
527+
return (OS_INVALID);
528+
}
529+
/* Hostname as-is for libcurl; common free + os_strdup below */
530+
#else
462531
Mail->smtpserver = OS_GetHost(node[i]->content, 5);
463532
if (!Mail->smtpserver) {
464533
merror(INVALID_SMTP, __local_name, node[i]->content);
465534
return (OS_INVALID);
466535
}
536+
#endif
467537
}
468538
free(Mail->smtpserver);
469539
os_strdup(node[i]->content, Mail->smtpserver);
@@ -473,6 +543,18 @@ int Read_Global(XML_NODE node, void *configp, void *mailp)
473543
if (Mail && (Mail->mn)) {
474544
os_strdup(node[i]->content, Mail->heloserver);
475545
}
546+
} else if (strcmp(node[i]->element, xml_smtp_port) == 0) {
547+
if (Mail && (Mail->mn)) {
548+
if (!OS_StrIsNum(node[i]->content)) {
549+
merror(XML_VALUEERR, __local_name, node[i]->element, node[i]->content);
550+
return (OS_INVALID);
551+
}
552+
Mail->smtp_port = atoi(node[i]->content);
553+
if (Mail->smtp_port < 1 || Mail->smtp_port > 65535) {
554+
merror(XML_VALUEERR, __local_name, node[i]->element, node[i]->content);
555+
return (OS_INVALID);
556+
}
557+
}
476558
} else if (strcmp(node[i]->element, xml_mailmaxperhour) == 0) {
477559
if (Mail) {
478560
if (!OS_StrIsNum(node[i]->content)) {

src/config/global-config.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ typedef struct __Config {
5353
/* Mail alerting */
5454
short int mailnotify;
5555

56+
/* SMTP auth (USE_CURL build only) */
57+
short int authsmtp;
58+
short int securesmtp;
59+
5660
/* Custom Alert output*/
5761
short int custom_alert_output;
5862
char *custom_alert_output_format;

src/config/mail-config.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ typedef struct _MailConfig {
3434
char *idsname;
3535
char *smtpserver;
3636
char *heloserver;
37+
char *smtpserver_resolved; /* pre-resolved IP for CURLOPT_RESOLVE when chrooted (USE_SMTP_CURL) */
38+
39+
/* SMTP auth (USE_CURL build only) */
40+
int authsmtp; /* 0 = off (default), 1 = on */
41+
int securesmtp; /* 0 = off (default), 1 = on */
42+
int smtp_port; /* 0 = use default per mode (465/587/25); else override */
43+
char *smtp_user;
44+
char *smtp_pass;
3745

3846
/* Granular e-mail options */
3947
unsigned int *gran_level;

src/monitord/main.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,11 @@ int main(int argc, char **argv)
145145
mond.emailidsname = OS_GetOneContentforElement(&xml, xml_idsname);
146146

147147
if (tmpsmtp && mond.emailfrom) {
148+
#ifdef USE_SMTP_CURL
149+
os_strdup(tmpsmtp, mond.smtpserver);
150+
#else
148151
mond.smtpserver = OS_GetHost(tmpsmtp, 5);
152+
#endif
149153
if (!mond.smtpserver) {
150154
merror(INVALID_SMTP, ARGV0, tmpsmtp);
151155
if (mond.emailfrom) {
@@ -154,6 +158,8 @@ int main(int argc, char **argv)
154158
mond.emailfrom = NULL;
155159
merror("%s: Invalid SMTP server. Disabling email reports.", ARGV0);
156160
}
161+
free(tmpsmtp);
162+
tmpsmtp = NULL;
157163
} else {
158164
if (tmpsmtp) {
159165
free(tmpsmtp);

src/os_maild/config.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ int MailConf(int test_config, const char *cfgfile, MailConfig *Mail)
2525
Mail->idsname = NULL;
2626
Mail->smtpserver = NULL;
2727
Mail->heloserver = NULL;
28+
Mail->smtpserver_resolved = NULL;
29+
Mail->authsmtp = 0;
30+
Mail->securesmtp = 0;
31+
Mail->smtp_port = 0;
32+
Mail->smtp_user = NULL;
33+
Mail->smtp_pass = NULL;
2834
Mail->mn = 0;
2935
Mail->priority = 0;
3036
Mail->maxperhour = 12;
@@ -45,6 +51,18 @@ int MailConf(int test_config, const char *cfgfile, MailConfig *Mail)
4551
return (OS_INVALID);
4652
}
4753

54+
#ifndef USE_SMTP_CURL
55+
if (Mail->authsmtp) {
56+
merror("%s: SMTP authentication (auth_smtp=yes) requires building with USE_CURL=yes.", ARGV0);
57+
return (OS_INVALID);
58+
}
59+
#else
60+
if (Mail->authsmtp && (!Mail->smtp_user || !Mail->smtp_pass)) {
61+
merror("%s: auth_smtp=yes requires both smtp_user and smtp_password to be set.", ARGV0);
62+
return (OS_INVALID);
63+
}
64+
#endif
65+
4866
if (!Mail->mn) {
4967
if (!test_config) {
5068
verbose(MAIL_DIS, ARGV0);

0 commit comments

Comments
 (0)