blob: d2ca599f65243c521f18b0192dc04b06ce73e017 [file] [log] [blame]
/*
* Copyright (C) Tildeslash Ltd. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
*
* You must obey the GNU Affero General Public License in all respects
* for all of the code used other than OpenSSL.
*/
#include "config.h"
#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STDARG_H
#include <stdarg.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_SETJMP_H
#include <setjmp.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#include "monit.h"
#include "net.h"
#include "socket.h"
#include "base64.h"
// libmonit
#include "system/Time.h"
/**
* Connect to a SMTP server and send mail.
*
* @file
*/
/* ------------------------------------------------------------- Definitions */
typedef struct {
Socket_T socket;
sigjmp_buf error;
const char *server;
int port;
const char *username;
const char *password;
Ssl_T ssl;
char localhost[STRLEN];
} SendMail_T;
/* -------------------------------------------------------------- Prototypes */
static void do_status(SendMail_T *S);
static void open_server(SendMail_T *S);
static void do_send(SendMail_T *S, const char *, ...);
/* ------------------------------------------------------------------ Public */
/**
* Send mail messages via SMTP
* @param mail A Mail object
* @return FALSE if failed, TRUE if succeeded
*/
int sendmail(Mail_T mail) {
int i;
int rv;
Mail_T m;
SendMail_T S;
char *b64 = NULL;
char now[STRLEN];
ASSERT(mail);
S.socket = NULL;
if(sigsetjmp(S.error, TRUE)) {
rv = FALSE;
goto exit;
} else {
rv = TRUE;
}
open_server(&S);
Time_gmtstring(Time_now(), now);
snprintf(S.localhost, sizeof(S.localhost), "%s", Run.mail_hostname ? Run.mail_hostname : Run.localhostname);
do_status(&S);
/* Use EHLO if TLS or Authentication is requested */
if((S.ssl.use_ssl && S.ssl.version == SSL_VERSION_TLS) || S.username) {
do_send(&S, "EHLO %s\r\n", S.localhost);
} else {
do_send(&S, "HELO %s\r\n", S.localhost);
}
do_status(&S);
/* Switch to TLS now if configured */
if(S.ssl.use_ssl && S.ssl.version == SSL_VERSION_TLS) {
do_send(&S, "STARTTLS\r\n");
do_status(&S);
if(!socket_switch2ssl(S.socket, S.ssl)) {
rv = FALSE;
goto exit;
}
/* After starttls, send ehlo again: RFC 3207: 4.2 Result of the STARTTLS Command */
do_send(&S, "EHLO %s\r\n", S.localhost);
do_status(&S);
}
/* Authenticate if possible */
if(S.username) {
unsigned char buffer[STRLEN];
int len;
len = snprintf((char *)buffer, STRLEN, "%c%s%c%s", '\0', S.username, '\0', S.password?S.password:"");
b64 = encode_base64(len, buffer);
do_send(&S, "AUTH PLAIN %s\r\n", b64);
do_status(&S);
}
for(i = 0, m= mail; m; m= m->next, i++) {
do_send(&S, "MAIL FROM: <%s>\r\n", m->from);
do_status(&S);
do_send(&S, "RCPT TO: <%s>\r\n", m->to);
do_status(&S);
do_send(&S, "DATA\r\n");
do_status(&S);
do_send(&S, "From: %s\r\n", m->from);
if (m->replyto)
do_send(&S, "Reply-To: %s\r\n", m->replyto);
do_send(&S, "To: %s\r\n", m->to);
do_send(&S, "Subject: %s\r\n", m->subject);
do_send(&S, "Date: %s\r\n", now);
do_send(&S, "X-Mailer: %s %s\r\n", prog, VERSION);
do_send(&S, "Mime-Version: 1.0\r\n");
do_send(&S, "Content-Type: text/plain; charset=\"iso-8859-1\"\r\n");
do_send(&S, "Content-Transfer-Encoding: 8bit\r\n");
do_send(&S, "Message-id: <%ld.%lu@%s>\r\n", time(NULL), random(), S.localhost);
do_send(&S, "\r\n");
do_send(&S, "%s\r\n", m->message);
do_send(&S, ".\r\n");
do_status(&S);
}
do_send(&S, "QUIT\r\n");
do_status(&S);
exit:
if(S.socket)
socket_free(&S.socket);
FREE(b64);
return rv;
}
/* ----------------------------------------------------------------- Private */
void do_send(SendMail_T *S, const char *s, ...) {
va_list ap;
char *msg= NULL;
va_start(ap,s);
msg= Str_vcat(s, ap);
va_end(ap);
if (socket_write(S->socket, msg, strlen(msg)) <= 0) {
FREE(msg);
LogError("Sendmail: error sending data to the server '%s' -- %s\n",
S->server, STRERROR);
siglongjmp(S->error, TRUE);
}
FREE(msg);
}
static void do_status(SendMail_T *S) {
int status;
char buf[STRLEN];
if(!socket_readln(S->socket, buf, sizeof(buf))) {
LogError("Sendmail: error receiving data from the mailserver '%s' -- %s\n",
S->server, STRERROR);
siglongjmp(S->error, TRUE);
}
Str_chomp(buf);
sscanf(buf, "%d", &status);
if(status >= 400) {
LogError("Sendmail error: %s\n", buf);
siglongjmp(S->error, TRUE);
}
}
static void open_server(SendMail_T *S) {
MailServer_T mta= Run.mailservers;
if(mta) {
S->server = mta->host;
S->port = mta->port;
S->username = mta->username;
S->password = mta->password;
S->ssl = mta->ssl;
} else {
LogError("No mail servers are defined -- see manual for 'set mailserver' statement\n");
siglongjmp(S->error, TRUE);
}
do {
/* wait with ssl-connect if SSL_VERSION_TLS is set (rfc2487) */
if(!S->ssl.use_ssl || S->ssl.version == SSL_VERSION_TLS) {
S->socket= socket_new(S->server, S->port, SOCKET_TCP, FALSE,
Run.mailserver_timeout);
} else {
S->socket= socket_create_t(S->server, S->port, SOCKET_TCP,
S->ssl, Run.mailserver_timeout);
}
if(S->socket)
break;
LogError("Cannot open a connection to the mailserver '%s:%i' -- %s\n",
S->server, S->port, STRERROR);
if(mta && (mta= mta->next)) {
S->server = mta->host;
S->port = mta->port;
S->username = mta->username;
S->password = mta->password;
S->ssl = mta->ssl;
LogInfo("Trying the next mail server '%s:%i'\n", S->server, S->port);
continue;
} else {
LogError("No mail servers are available\n");
siglongjmp(S->error, TRUE);
}
} while(TRUE);
}