/* smtpclient.c -- Routines for sending a message via SMTP
*
* Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <assert.h>
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sysexits.h>
#include <syslog.h>
#include "backend.h"
#include "xmalloc.h"
#include "global.h"
#include "smtpclient.h"
#include "telemetry.h"
/* generated headers are not necessarily in current directory */
#include "imap/imap_err.h"
typedef struct {
char code[3];
struct buf text; /* Zero-terminated reply text, excluding CRLF. OK to overwrite. */
int is_last;
} smtp_resp_t;
struct smtpclient {
/* SMTP backend. Client implementations can store
* their context data in backend->context. */
struct backend *backend;
/* Client implementations can free their context
* stored in backend->context. */
int (*free_context)(void *ctx);
/* Telemetry log */
int logfd;
/* Internal state */
hash_table *have_exts;
struct buf buf;
char *authid;
char *notify;
char *ret;
char *by;
unsigned long msgsize;
smtp_resp_t resp;
};
enum {
SMTPCLIENT_CAPA_DSN = (1 << 3),
SMTPCLIENT_CAPA_DELIVERBY = (1 << 4),
SMTPCLIENT_CAPA_SIZE = (1 << 5),
SMTPCLIENT_CAPA_STATUS = (1 << 6),
SMTPCLIENT_CAPA_FUTURE = (1 << 7),
SMTPCLIENT_CAPA_PRIORITY = (1 << 8),
SMTPCLIENT_CAPA_SENDCHECK = (1 << 9)
};
static struct protocol_t smtp_protocol =
{ "smtp", "smtp", TYPE_STD,
{ { { 0, "220 " },
{ "EHLO", "localhost", "250 ", NULL,
CAPAF_ONE_PER_LINE|CAPAF_SKIP_FIRST_WORD|CAPAF_DASH_STUFFING,
{ { "AUTH", CAPA_AUTH },
{ "STARTTLS", CAPA_STARTTLS },
{ "DSN", SMTPCLIENT_CAPA_DSN },
{ "DELIVERYBY", SMTPCLIENT_CAPA_DELIVERBY },
{ "SIZE", SMTPCLIENT_CAPA_SIZE },
{ "ENHANCEDSTATUSCODES", SMTPCLIENT_CAPA_STATUS },
{ "FUTURERELEASE", SMTPCLIENT_CAPA_FUTURE },
{ "MT-PRIORITY", SMTPCLIENT_CAPA_PRIORITY },
{ "SENDCHECK", SMTPCLIENT_CAPA_SENDCHECK },
{ NULL, 0 } } },
{ "STARTTLS", "220", "454", 0 },
{ "AUTH", 512, 0, "235", "5", "334 ", "*", NULL, 0 },
{ NULL, NULL, NULL },
{ "NOOP", NULL, "250" },
{ "QUIT", NULL, "221" } } }
};
/* SMTP protocol implementation */
typedef int smtp_readcb_t(smtpclient_t *sm, void *rock);
static int smtpclient_read(smtpclient_t *sm, smtp_readcb_t *cb, void *rock);
static int smtpclient_writebuf(smtpclient_t *sm, struct buf *buf, int flush);
static int smtpclient_ehlo(smtpclient_t *sm);
static int smtpclient_rset(smtpclient_t *sm);
static int smtpclient_quit(smtpclient_t *sm);
static int smtpclient_from(smtpclient_t *sm, smtp_addr_t *addr);
static int smtpclient_rcpt_to(smtpclient_t *sm, ptrarray_t *rcpt);
static int smtpclient_data(smtpclient_t *sm, struct protstream *data);
static void smtpclient_logerror(smtpclient_t *sm, const char *cmd, int r);
EXPORTED int smtpclient_open(smtpclient_t **smp)
{
int r = 0;
const char *backend = config_getstring(IMAPOPT_SMTP_BACKEND);
if (!strcmp(backend, "sendmail")) {
r = smtpclient_open_sendmail(smp);
}
else if (!strcmp(backend, "host")) {
r = smtpclient_open_host(config_getstring(IMAPOPT_SMTP_HOST), smp);
}
else {
syslog(LOG_ERR, "smtpclient_open: unknown backend: %s", backend);
r = IMAP_INTERNAL;
}
return r;
}
EXPORTED int smtpclient_close(smtpclient_t **smp)
{
if (!smp || !*smp) {
return 0;
}
int r = 0;
smtpclient_t *sm = *smp;
/* Close backend */
backend_disconnect(sm->backend);
if (sm->free_context) {
r = sm->free_context(sm->backend->context);
}
free(sm->backend);
sm->backend = NULL;
/* Close log */
if (sm->logfd != -1) {
close(sm->logfd);
}
sm->logfd = -1;
/* Free internal state */
if (sm->have_exts) {
free_hash_table(sm->have_exts, free);
free(sm->have_exts);
sm->have_exts = NULL;
}
buf_free(&sm->buf);
free(sm->by);
free(sm->ret);
free(sm->notify);
free(sm->authid);
buf_free(&sm->resp.text);
free(sm);
*smp = NULL;
return r;
}
/* Match the response code to an expected return code defined in rock.
*
* rock->code must be a zero-terminated string up to length 3 (excluding the
* NUL byte). Matching is performed by string matching the SMTP return
* code to the expected code, stopping at the zero byte.
*
* E.g. return code "250", "251" both would match a "2" or "25" rock->code.
*
* Also see Daniel Bernstein's site https://cr.yp.to/smtp/request.html:
* "I recommend that clients avoid looking past the first digit of the
* code, either 2, 3, 4, or 5. The other two digits and the text are
* primarily for human consumption. (Exception: See EHLO.)"
*
* Return IMAP_PROTOCOL_ERROR on mismatch, 0 on success.
*
* rock->cmd should describe the command whose response you're checking.
* If set, it'll be included in the error log on mismatch.
*
*/
struct expect_code_rock {
const char *cmd;
const char *code;
};
static int expect_code_cb(smtpclient_t *sm, void *rock)
{
size_t i;
const struct expect_code_rock *ecrock = (const struct expect_code_rock *) rock;
smtp_resp_t *resp = &sm->resp;
for (i = 0; i < 3 && ecrock->code[i]; i++) {
if (ecrock->code[i] != resp->code[i]) {
const char *text = buf_cstring(&resp->text);
syslog(LOG_ERR, "smtpclient: unexpected response%s%s: code=%c%c%c text=%s",
ecrock->cmd ? " to " : "",
ecrock->cmd ? ecrock->cmd : "",
resp->code[0], resp->code[1], resp->code[2], text);
/* Try to glean specific error from response */
if (CAPA(sm->backend, SMTPCLIENT_CAPA_STATUS)) {
if (text[2] == '1' && text[4] >= '1' && text[4] <= '3')
return IMAP_MAILBOX_NONEXISTENT;
else if (text[2] == '3' && text[4] == '4')
return IMAP_MESSAGE_TOO_LARGE;
else if (text[2] == '5' && text[4] == '3')
return IMAP_MAILBOX_DISABLED;
else if (text[2] == '3' && text[4] == '0')
return IMAP_REMOTE_DENIED;
}
else {
switch (atoi(resp->code)) {
case 421:
case 451:
case 554:
return IMAP_REMOTE_DENIED;
case 450:
case 550:
return IMAP_MAILBOX_DISABLED;
case 452:
case 552:
return IMAP_MESSAGE_TOO_LARGE;
case 553:
return IMAP_MAILBOX_NONEXISTENT;
}
}
return IMAP_PROTOCOL_ERROR;
}
}
return 0;
}
static int smtpclient_read(smtpclient_t *sm, smtp_readcb_t *cb, void *rock)
{
char buf[513]; /* Maximum length of reply line, see RFC 5321, 4.5.3.1.5. */
int r = IMAP_IOERROR;
do {
/* Read next reply line. */
if (!prot_fgets(buf, 513, sm->backend->in)) {
r = IMAP_IOERROR;
return r;
}
buf[512] = '\0';
/* Parse reply line. */
if (!isdigit(buf[0]) || !isdigit(buf[1]) || !isdigit(buf[2])) {
return IMAP_PROTOCOL_ERROR;
}
if (buf[3] != '-' && !isspace(buf[3])) {
return IMAP_PROTOCOL_ERROR;
}
char *p = memchr(buf + 4, '\n', 508);
if (p == NULL) {
return IMAP_PROTOCOL_ERROR;
}
if (*(p-1) == '\r') {
--p;
}
*p = '\0';
/* Call callback. */
memcpy(sm->resp.code, buf, 3);
buf_setcstr(&sm->resp.text, buf + 4);
sm->resp.is_last = isspace(buf[3]);
r = cb(sm, rock);
} while (!r && !sm->resp.is_last);
return r;
}
static int ehlo_cb(smtpclient_t *sm, void *rock __attribute__((unused)))
{
smtp_resp_t *resp = &sm->resp;
/* Is this the first response line? */
if (sm->have_exts == NULL) {
sm->have_exts = xzmalloc(sizeof(struct hash_table));
construct_hash_table(sm->have_exts, 16, 0);
return 0;
}
/* Add the extension */
const char *p = buf_cstring(&resp->text);
while (*p && !isspace(*p)) {
p++;
}
const char *args = isspace(*p) ? p + 1 : "";
buf_truncate(&resp->text, p - buf_base(&resp->text));
hash_insert(buf_cstring(&resp->text), xstrdup(args), sm->have_exts);
return 0;
}
static int smtpclient_writebuf(smtpclient_t *sm, struct buf *buf, int flush)
{
if (prot_putbuf(sm->backend->out, buf)) {
return IMAP_IOERROR;
}
if (flush && prot_flush(sm->backend->out)) {
return IMAP_IOERROR;
}
return 0;
}
static void smtpclient_logerror(smtpclient_t *sm, const char *cmd, int r)
{
if (r == IMAP_IOERROR) {
/* try to dig a more specific error out of the prot streams */
const char *errstr = prot_error(sm->backend->out);
if (!errstr) errstr = prot_error(sm->backend->in);
if (!errstr) errstr = error_message(r);
syslog(LOG_ERR, "smtpclient: %s during %s", errstr, cmd);
}
else {
syslog(LOG_ERR, "smtpclient: %s during %s", error_message(r), cmd);
}
}
static int smtpclient_ehlo(smtpclient_t *sm)
{
int r = 0;
/* Say EHLO */
buf_setcstr(&sm->buf, "EHLO localhost\r\n");
r = smtpclient_writebuf(sm, &sm->buf, 1);
if (r) goto done;
buf_reset(&sm->buf);
/* Process response */
r = smtpclient_read(sm, ehlo_cb, NULL);
done:
if (r) smtpclient_logerror(sm, "EHLO", r);
return r;
}
static int smtpclient_rset(smtpclient_t *sm)
{
int r = 0;
/* Say RSET */
buf_setcstr(&sm->buf, "RSET\r\n");
r = smtpclient_writebuf(sm, &sm->buf, 1);
if (r) goto done;
buf_reset(&sm->buf);
/* Process response */
struct expect_code_rock rock = { "RSET", "2" };
r = smtpclient_read(sm, expect_code_cb, &rock);
done:
if (r) smtpclient_logerror(sm, "RSET", r);
return r;
}
static int smtpclient_schk(smtpclient_t *sm)
{
int r = 0;
/* Say SCHK */
buf_setcstr(&sm->buf, "SCHK\r\n");
r = smtpclient_writebuf(sm, &sm->buf, 1);
if (r) goto done;
buf_reset(&sm->buf);
/* Process response */
struct expect_code_rock rock = { "SCHK", "2" };
r = smtpclient_read(sm, expect_code_cb, &rock);
done:
if (r) smtpclient_logerror(sm, "SCHK", r);
return r;
}
__attribute__((unused)) static int smtpclient_quit(smtpclient_t *sm)
{
int r = 0;
/* Say QUIT */
buf_setcstr(&sm->buf, "QUIT\r\n");
r = smtpclient_writebuf(sm, &sm->buf, 1);
if (r) goto done;
/* Don't insist on an answer */
struct expect_code_rock rock = { "QUIT", "2" };
r = smtpclient_read(sm, expect_code_cb, &rock);
if (r) {
syslog(LOG_INFO, "smtpclient: QUIT without reply: %s", error_message(r));
return r;
}
done:
if (r) smtpclient_logerror(sm, "QUIT", r);
return r;
}
static int write_addr(smtpclient_t *sm,
const char *cmd,
const smtp_addr_t *addr,
const ptrarray_t *extra_params)
{
int i, r = 0;
buf_reset(&sm->buf);
buf_printf(&sm->buf, "%s:<%s>", cmd, addr->addr);
for (i = 0; i < addr->params.count; i++) {
smtp_param_t *param = ptrarray_nth(&addr->params, i);
buf_appendcstr(&sm->buf, " ");
buf_appendcstr(&sm->buf, param->key);
if (!param->val) {
continue;
}
buf_appendcstr(&sm->buf, "=");
buf_appendcstr(&sm->buf, param->val);
}
for (i = 0; i < extra_params->count; i++) {
smtp_param_t *param = ptrarray_nth(extra_params, i);
buf_appendcstr(&sm->buf, " ");
buf_appendcstr(&sm->buf, param->key);
if (!param->val) {
continue;
}
buf_appendcstr(&sm->buf, "=");
buf_appendcstr(&sm->buf, param->val);
}
buf_appendcstr(&sm->buf, "\r\n");
r = smtpclient_writebuf(sm, &sm->buf, 1);
if (r) goto done;
struct expect_code_rock rock = { cmd, "2" };
r = smtpclient_read(sm, expect_code_cb, &rock);
if (r) goto done;
done:
if (r) smtpclient_logerror(sm, cmd, r);
buf_reset(&sm->buf);
return r;
}
/* Add key/value to extra, if it isn't defined in params */
static void smtp_params_set_extra(ptrarray_t *params, ptrarray_t *extra,
const char *key, const char *val)
{
int i;
for (i = 0; i < params->count; i++) {
smtp_param_t *param = ptrarray_nth(params, i);
if (!strcasecmp(param->key, key)) {
break;
}
}
if (i == params->count) {
smtp_param_t *param = xzmalloc(sizeof(smtp_param_t));
param->key = xstrdup(key);
param->val = xstrdup(val);
ptrarray_add(extra, param);
}
}
static void smtp_params_fini(ptrarray_t *params)
{
int i;
for (i = 0; i < params->count; i++) {
smtp_param_t *param = ptrarray_nth(params, i);
free(param->key);
free(param->val);
free(param);
}
ptrarray_fini(params);
}
/* Write a MAIL FROM command for address addr. */
static int smtpclient_from(smtpclient_t *sm, smtp_addr_t *addr)
{
ptrarray_t extra_params = PTRARRAY_INITIALIZER;
if (sm->authid && CAPA(sm->backend, CAPA_AUTH)) {
smtp_params_set_extra(&addr->params, &extra_params, "AUTH", sm->authid);
}
if (sm->ret && CAPA(sm->backend, SMTPCLIENT_CAPA_DSN)) {
smtp_params_set_extra(&addr->params, &extra_params, "RET", sm->ret);
}
if (sm->by && CAPA(sm->backend, SMTPCLIENT_CAPA_DELIVERBY)) {
smtp_params_set_extra(&addr->params, &extra_params, "BY", sm->by);
}
if (sm->msgsize && CAPA(sm->backend, SMTPCLIENT_CAPA_SIZE)) {
char szbuf[21];
snprintf(szbuf, sizeof(szbuf), "%lu", sm->msgsize);
smtp_params_set_extra(&addr->params, &extra_params, "SIZE", szbuf);
}
int r = write_addr(sm, "MAIL FROM", addr, &extra_params);
if (r) smtpclient_logerror(sm, "MAIL FROM", r);
smtp_params_fini(&extra_params);
return r;
}
/* Write a RCPT TO command for all addresses in rcpt. */
static int smtpclient_rcpt_to(smtpclient_t *sm, ptrarray_t *rcpts)
{
int i, r = 0;
for (i = 0; i < rcpts->count; i++) {
smtp_addr_t *addr = ptrarray_nth(rcpts, i);
ptrarray_t extra_params = PTRARRAY_INITIALIZER;
if (sm->notify && CAPA(sm->backend, SMTPCLIENT_CAPA_DSN)) {
smtp_params_set_extra(&addr->params, &extra_params, "NOTIFY", sm->notify);
}
int r1 = write_addr(sm, "RCPT TO", addr, &extra_params);
smtp_params_fini(&extra_params);
if (!r1) addr->completed = 1;
else if (!r) r = r1;
}
if (r) smtpclient_logerror(sm, "RCPT TO", r);
return r;
}
/* Write a DATA command using data as input. Data is dot-escaped
* before it is written to the SMTP backend. */
static int smtpclient_data(smtpclient_t *sm, struct protstream *data)
{
int r = 0;
/* Write DATA */
buf_setcstr(&sm->buf, "DATA\r\n");
r = smtpclient_writebuf(sm, &sm->buf, 1);
if (r) goto done;
buf_reset(&sm->buf);
/* Expect Start Input */
struct expect_code_rock rock = { "DATA", "3" };
r = smtpclient_read(sm, expect_code_cb, &rock);
if (r) goto done;
/* Write message, escaping dot characters. */
buf_ensure(&sm->buf, 4096);
int c;
int prev1 = 256, prev2 = 256;
int at_start = 1;
const char *eot = "\r\n.\r\n";
while ((c = prot_getc(data)) != EOF) {
if (c == '.' && at_start) {
prot_ungetc(c, data);
c = '.';
}
at_start = 0;
sm->buf.s[sm->buf.len++] = (unsigned char) c;
if (sm->buf.len == 4096) {
r = smtpclient_writebuf(sm, &sm->buf, 0);
if (r) goto done;
buf_reset(&sm->buf);
}
if (c == '\n') {
at_start = 1;
}
prev2 = prev1;
prev1 = c;
}
r = smtpclient_writebuf(sm, &sm->buf, 1);
if (r) goto done;
buf_reset(&sm->buf);
/* If message ends with CRLF, omit pre-dot CRLF. */
if (prev2 == '\r' && prev1 == '\n')
eot = ".\r\n";
/* Write end-of-text. */
buf_setcstr(&sm->buf, eot);
r = smtpclient_writebuf(sm, &sm->buf, 1);
if (r) goto done;
buf_reset(&sm->buf);
/* Expect OK */
rock.cmd = "EOT";
rock.code = "2";
r = smtpclient_read(sm, expect_code_cb, &rock);
if (r) goto done;
done:
if (r) smtpclient_logerror(sm, "DATA", r);
return r;
}
static int validate_envelope_params(ptrarray_t *params)
{
if (!params) return 0;
int i;
for (i = 0; i < ptrarray_size(params); i++) {
smtp_param_t *param = ptrarray_nth(params, i);
if (!smtp_is_valid_esmtp_keyword(param->key)) {
syslog(LOG_ERR, "smtpclient: invalid estmp keyword: \"%s\"", param->key);
return IMAP_PROTOCOL_ERROR;
}
if (!strcasecmp(param->key, "AUTH")) {
syslog(LOG_ERR, "smptclient: rejecting AUTH parameter in envelope");
return IMAP_PERMISSION_DENIED;
}
if (param->val && !smtp_is_valid_esmtp_value(param->val)) {
syslog(LOG_ERR, "smtpclient: invalid estmp value: \"%s\"", param->val);
return IMAP_PROTOCOL_ERROR;
}
}
return 0;
}
static int validate_envelope(smtp_envelope_t *env)
{
int i, r;
if (!env->from.addr) {
syslog(LOG_ERR, "smtpclient: envelope missing sender");
return IMAP_PROTOCOL_ERROR;
}
r = validate_envelope_params(&env->from.params);
if (r) return r;
if (!env->rcpts.count) {
syslog(LOG_ERR, "smtpclient: envelope missing recipients");
return IMAP_PROTOCOL_ERROR;
}
for (i = 0; i < env->rcpts.count; i++) {
smtp_addr_t *addr = ptrarray_nth(&env->rcpts, i);
if (!addr->addr) {
syslog(LOG_ERR, "smtpclient: invalid recipient at position %d", i);
return IMAP_PROTOCOL_ERROR;
}
r = validate_envelope_params(&addr->params);
if (r) return r;
}
return 0;
}
EXPORTED int smtpclient_sendprot(smtpclient_t *sm, smtp_envelope_t *env, struct protstream *data)
{
int r = 0;
if (sm->msgsize) {
unsigned long maxsize = smtpclient_get_maxsize(sm);
if (maxsize && maxsize < sm->msgsize) {
r = IMAP_MESSAGE_TOO_LARGE;
goto done;
}
}
r = validate_envelope(env);
if (r) goto done;
r = smtpclient_from(sm, &env->from);
if (r) goto done;
r = smtpclient_rcpt_to(sm, &env->rcpts);
if (r) goto done;
if (data) {
r = smtpclient_data(sm, data);
if (r) goto done;
}
else {
/* simply pre-flighting the envelope */
if (CAPA(sm->backend, SMTPCLIENT_CAPA_SENDCHECK)) {
r = smtpclient_schk(sm);
}
else {
r = smtpclient_rset(sm);
}
if (r) goto done;
}
done:
return r;
}
EXPORTED int smtpclient_send(smtpclient_t *sm, smtp_envelope_t *env, struct buf *data)
{
struct protstream *p = prot_readmap(buf_base(data), buf_len(data));
smtpclient_set_size(sm, buf_len(data));
int r = smtpclient_sendprot(sm, env, p);
prot_free(p);
return r;
}
EXPORTED void smtpclient_set_auth(smtpclient_t *sm, const char *authid)
{
free(sm->authid);
sm->authid = xstrdupnull(authid);
}
EXPORTED void smtpclient_set_notify(smtpclient_t *sm, const char *value)
{
free(sm->notify);
sm->notify = xstrdupnull(value);
}
EXPORTED void smtpclient_set_ret(smtpclient_t *sm, const char *value)
{
free(sm->ret);
sm->ret = xstrdupnull(value);
}
EXPORTED void smtpclient_set_by(smtpclient_t *sm, const char *value)
{
free(sm->by);
sm->by = xstrdupnull(value);
}
EXPORTED void smtpclient_set_size(smtpclient_t *sm, unsigned long value)
{
sm->msgsize = value;
}
EXPORTED unsigned long smtpclient_get_maxsize(smtpclient_t *sm)
{
unsigned long maxsize = 0;
if (CAPA(sm->backend, SMTPCLIENT_CAPA_SIZE)) {
const char *sizestr = smtpclient_has_ext(sm, "SIZE");
if (sizestr) maxsize = strtoul(sizestr, NULL, 10);
}
return maxsize;
}
EXPORTED const char *smtpclient_get_resp_text(smtpclient_t *sm)
{
return buf_cstring(&sm->resp.text);
}
/* SMTP backend implementations */
static int smtpclient_new(smtpclient_t **smp,
struct backend *backend,
int (*freectx)(void *), int logfd)
{
smtpclient_t *sm = xzmalloc(sizeof(smtpclient_t));
sm->backend = backend;
sm->free_context = freectx;
sm->logfd = logfd;
*smp = sm;
return 0;
}
EXPORTED const char *smtpclient_has_ext(smtpclient_t *sm, const char *name)
{
if (!sm->have_exts) {
int r = smtpclient_ehlo(sm);
if (r) {
syslog(LOG_ERR, "smtpclient: can't EHLO for extensions: %s",
error_message(r));
return NULL;
}
}
return hash_lookup(name, sm->have_exts);
}
/* TCP host backend */
typedef struct {
int sockfd;
} smtpclient_host_t;
EXPORTED int smtpclient_open_host(const char *addr, smtpclient_t **smp)
{
struct backend *bk = NULL;
int r = 0;
char *myaddr = NULL;
int logfd = -1;
/* Setup SASL for authentication, if any */
sasl_callback_t *sasl_cb = NULL;
if (config_getstring(IMAPOPT_SMTP_AUTH_AUTHNAME)) {
sasl_cb = mysasl_callbacks(NULL /*userid*/,
config_getstring(IMAPOPT_SMTP_AUTH_AUTHNAME),
config_getstring(IMAPOPT_SMTP_AUTH_REALM),
config_getstring(IMAPOPT_SMTP_AUTH_PASSWORD));
} else {
myaddr = strconcat(addr, "/noauth", NULL);
}
logfd = telemetry_log("smtpclient.host", NULL, NULL, 0);
/* Connect to backend */
const char *host = myaddr ? myaddr : addr;
syslog(LOG_DEBUG, "smtpclient_open: connecting to host: %s", host);
bk = backend_connect(NULL, host, &smtp_protocol, NULL, sasl_cb, NULL, logfd);
if (sasl_cb) free_callbacks(sasl_cb);
if (!bk) {
syslog(LOG_ERR, "smptclient_open: can't connect to host: %s", host);
if (logfd != -1) close(logfd);
r = IMAP_INTERNAL;
goto done;
}
r = smtpclient_new(smp, bk, /*freectx*/NULL, logfd);
done:
free(myaddr);
return r;
}
EXPORTED int smtp_is_valid_esmtp_keyword(const char *val)
{
if (!isascii(*val) || !isalnum(*val)) {
return 0;
}
for (val = val + 1; *val; val++) {
if (!isascii(*val) || (*val != '-' && !isalnum(*val))) {
return 0;
}
}
return 1;
}
EXPORTED int smtp_is_valid_esmtp_value(const char *val)
{
if (*val == '\0') return 0;
for ( ; *val; val++) {
if (*val == '=' || *val < '!' || *val > '~') {
return 0;
}
}
return 1;
}
EXPORTED smtp_addr_t *smtp_envelope_set_from(smtp_envelope_t *env, const char *addr)
{
smtp_params_fini(&env->from.params);
free(env->from.addr);
env->from.addr = xstrdup(addr);
return &env->from;
}
EXPORTED smtp_addr_t *smtp_envelope_add_rcpt(smtp_envelope_t *env, const char *addr)
{
smtp_addr_t *rcpt = xzmalloc(sizeof(smtp_addr_t));
rcpt->addr = xstrdup(addr);
ptrarray_append(&env->rcpts, rcpt);
return rcpt;
}
EXPORTED void smtp_envelope_fini(smtp_envelope_t *env)
{
smtp_params_fini(&env->from.params);
free(env->from.addr);
smtp_addr_t *a;
while ((a = ptrarray_pop(&env->rcpts))) {
smtp_params_fini(&a->params);
free(a->addr);
free(a);
}
ptrarray_fini(&env->rcpts);
}
/* Sendmail process backend */
typedef struct {
int infd;
int outfd;
pid_t pid;
} smtpclient_sendmail_ctx_t;
static int smtpclient_sendmail_freectx(smtpclient_sendmail_ctx_t *ctx)
{
if (!ctx) return 0;
if (ctx->pid && waitpid(ctx->pid, NULL, 0) < 0) {
syslog(LOG_ERR, "waitpid(): %m");
}
if (ctx->infd >= 0)
close(ctx->infd);
if (ctx->outfd >= 0)
close(ctx->outfd);
free(ctx);
return 0;
}
EXPORTED int smtpclient_open_sendmail(smtpclient_t **smp)
{
struct backend *bk = NULL;
int r = 0;
int p_child[2];
int p_parent[2];
int logfd = -1;
/* Create the pipes and fork */
r = pipe(p_child);
if (!r) {
r = pipe(p_parent);
}
if (r < 0) {
syslog(LOG_ERR, "smtpclient_open: can't create pipe: %m");
r = IMAP_SYS_ERROR;
goto done;
}
pid_t pid = fork();
if (pid < 0) {
syslog(LOG_ERR, "smtpclient_open: can't fork: %m");
r = IMAP_SYS_ERROR;
goto done;
}
if (pid == 0) {
/* child process */
close(p_child[1]);
dup2(p_child[0], STDIN_FILENO);
close(p_child[0]);
close(p_parent[0]);
dup2(p_parent[1], STDOUT_FILENO);
close(p_parent[1]);
execl(config_getstring(IMAPOPT_SENDMAIL), "sendmail", "-bs", (char *)NULL);
syslog(LOG_ERR, "smtpclient_open: can't exec sendmail: %m");
exit(1);
}
/* parent process */
close(p_child[0]);
p_child[0] = -1;
close(p_parent[1]);
p_parent[1] = -1;
smtpclient_sendmail_ctx_t *ctx = xmalloc(sizeof(smtpclient_sendmail_ctx_t));
ctx->infd = p_parent[0]; /* reader */
ctx->outfd = p_child[1]; /* writer */
ctx->pid = pid;
logfd = telemetry_log("smtpclient.sendmail", NULL, NULL, 0);
/* Create backend and setup context */
bk = backend_connect_pipe(ctx->infd, ctx->outfd, &smtp_protocol, 0, logfd);
if (!bk) {
syslog(LOG_ERR, "smptclient_open: can't open sendmail backend");
r = IMAP_INTERNAL;
smtpclient_sendmail_freectx(ctx);
if (logfd != -1) close(logfd);
goto done;
}
bk->context = ctx;
r = smtpclient_new(smp, bk, (int (*)(void *)) smtpclient_sendmail_freectx, logfd);
done:
return r;
}