#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <syslog.h>
#include <errno.h>
#include <sys/types.h>
#include <limits.h>
#include <sys/wait.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sasl/sasl.h>
#include <sasl/saslutil.h>
#include "assert.h"
#include "util.h"
#include "auth.h"
#include "prot.h"
#include "rfc822date.h"
#include "global.h"
#include "iptostring.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "mupdate_err.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "xstrlcat.h"
#include "version.h"
#include "lmtpengine.h"
#include "lmtpstats.h"
#include "tls.h"
#include "telemetry.h"
#ifdef APPLE_OS_X_SERVER
#include "AppleOD.h"
#endif
#define RCPT_GROW 30
struct address_data {
char *all;
char *rcpt;
#ifdef APPLE_OS_X_SERVER
char *virt;
char *afwd;
#endif
char *user;
char *domain;
char *mailbox;
int ignorequota;
int status;
};
struct clientdata {
struct protstream *pin;
struct protstream *pout;
int fd;
char clienthost[NI_MAXHOST*2+1];
char lhlo_param[250];
sasl_conn_t *conn;
enum {
EXTERNAL_AUTHED = -1,
NOAUTH = 0,
DIDAUTH = 1
} authenticated;
#ifdef HAVE_SSL
SSL *tls_conn;
#endif
int starttls_done;
};
#ifdef APPLE_OS_X_SERVER
struct od_user_opts *gUserOpts = NULL;
char *gLUser_relay_str = NULL;
#endif
extern int deliver_logfd;
extern int saslserver(sasl_conn_t *conn, const char *mech,
const char *init_resp, const char *resp_prefix,
const char *continuation, const char *empty_chal,
struct protstream *pin, struct protstream *pout,
int *sasl_result, char **success_data);
static struct {
char *ipremoteport;
char *iplocalport;
sasl_ssf_t ssf;
char *authid;
} saslprops = {NULL,NULL,0,NULL};
void printstring(const char *s __attribute__((unused)))
{
fatal("printstring() executed, but its not used for LMTP!",
EC_SOFTWARE);
}
#ifdef USING_SNMPGEN
static int roundToK(int x)
{
double rd = (x*1.0)/1024.0;
int ri = x/1024;
if (rd-ri < 0.5)
return ri;
else
return ri+1;
}
#else
#define roundToK(x)
#endif
static void send_lmtp_error(struct protstream *pout, int r)
{
switch (r) {
case 0:
prot_printf(pout, "250 2.1.5 Ok\r\n");
break;
case IMAP_IOERROR:
prot_printf(pout, "451 4.3.0 System I/O error\r\n");
break;
case IMAP_SERVER_UNAVAILABLE:
case MUPDATE_NOCONN:
case MUPDATE_NOAUTH:
case MUPDATE_TIMEOUT:
case MUPDATE_PROTOCOL_ERROR:
prot_printf(pout, "451 4.4.3 Remote server unavailable\r\n");
break;
case IMAP_NOSPACE:
prot_printf(pout, "451 4.3.1 cannot create file: out of space\r\n");
break;
case IMAP_AGAIN:
prot_printf(pout, "451 4.3.0 transient system error\r\n");
break;
case IMAP_PERMISSION_DENIED:
if (LMTP_LONG_ERROR_MSGS) {
prot_printf(pout,
"550-You do not have permission to post a message to this mailbox.\r\n"
"550-Please contact the owner of this mailbox in order to submit\r\n"
"550-your message, or %s if you believe you\r\n"
"550-received this message in error.\r\n"
"550 5.7.1 Permission denied\r\n",
config_getstring(IMAPOPT_POSTMASTER));
} else {
prot_printf(pout, "550 5.7.1 Permission denied\r\n");
}
break;
case IMAP_QUOTA_EXCEEDED:
if(config_getswitch(IMAPOPT_LMTP_OVER_QUOTA_PERM_FAILURE)) {
prot_printf(pout, "552 5.2.2 Over quota\r\n");
} else {
prot_printf(pout, "452 4.2.2 Over quota\r\n");
}
break;
case IMAP_MAILBOX_BADFORMAT:
case IMAP_MAILBOX_NOTSUPPORTED:
prot_printf(pout, "451 4.2.0 Mailbox has an invalid format\r\n");
break;
case IMAP_MAILBOX_MOVED:
prot_printf(pout, "451 4.2.1 Mailbox Moved\r\n");
break;
case IMAP_MESSAGE_CONTAINSNULL:
prot_printf(pout, "554 5.6.0 Message contains NUL characters\r\n");
break;
case IMAP_MESSAGE_CONTAINSNL:
prot_printf(pout, "554 5.6.0 Message contains bare newlines\r\n");
break;
case IMAP_MESSAGE_CONTAINS8BIT:
prot_printf(pout, "554 5.6.0 Message contains non-ASCII characters in headers\r\n");
break;
case IMAP_MESSAGE_BADHEADER:
prot_printf(pout, "554 5.6.0 Message contains invalid header\r\n");
break;
case IMAP_MESSAGE_NOBLANKLINE:
prot_printf(pout,
"554 5.6.0 Message has no header/body separator\r\n");
break;
case IMAP_MAILBOX_NONEXISTENT:
if (LMTP_LONG_ERROR_MSGS) {
prot_printf(pout,
"550-Mailbox unknown. Either there is no mailbox associated with this\r\n"
"550-name or you do not have authorization to see it.\r\n"
"550 5.1.1 User unknown\r\n");
} else {
prot_printf(pout, "550 5.1.1 User unknown\r\n");
}
break;
case IMAP_PROTOCOL_BAD_PARAMETERS:
prot_printf(pout, "501 5.5.4 Syntax error in parameters\r\n");
break;
case MUPDATE_BADPARAM:
default:
prot_printf(pout, "554 5.0.0 Unexpected internal error\r\n");
break;
}
}
int msg_new(message_data_t **m)
{
message_data_t *ret = (message_data_t *) xmalloc(sizeof(message_data_t));
ret->data = NULL;
ret->f = NULL;
ret->id = NULL;
ret->size = 0;
ret->return_path = NULL;
ret->rcpt = NULL;
ret->rcpt_num = 0;
ret->authuser = NULL;
ret->authstate = NULL;
ret->rock = NULL;
ret->hdrcache = spool_new_hdrcache();
*m = ret;
return 0;
}
void msg_free(message_data_t *m)
{
int i;
if (m->data) {
prot_free(m->data);
}
if (m->f) {
fclose(m->f);
}
if (m->id) {
free(m->id);
}
if (m->return_path) {
free(m->return_path);
}
if (m->rcpt) {
for (i = 0; i < m->rcpt_num; i++) {
if (m->rcpt[i]->all) free(m->rcpt[i]->all);
#ifdef APPLE_OS_X_SERVER
if (m->rcpt[i]->virt) free(m->rcpt[i]->virt);
if (m->rcpt[i]->afwd) free(m->rcpt[i]->afwd);
#endif
if (m->rcpt[i]->rcpt) free(m->rcpt[i]->rcpt);
free(m->rcpt[i]);
}
free(m->rcpt);
}
if (m->authuser) {
free(m->authuser);
if (m->authstate) auth_freestate(m->authstate);
}
#ifdef APPLE_OS_X_SERVER
if (gUserOpts) {
odFreeUserOpts(gUserOpts, 0);
}
#endif
spool_free_hdrcache(m->hdrcache);
free(m);
}
const char **msg_getheader(message_data_t *m, const char *phead)
{
assert(m && phead);
return spool_getheader(m->hdrcache, phead);
}
int msg_getsize(message_data_t *m)
{
return m->size;
}
int msg_getnumrcpt(message_data_t *m)
{
return m->rcpt_num;
}
void msg_getrcpt(message_data_t *m, int rcpt_num,
#ifdef APPLE_OS_X_SERVER
const char **user, const char **domain, const char **mailbox,
const char **auto_fwd)
#else
const char **user, const char **domain, const char **mailbox)
#endif
{
assert(0 <= rcpt_num && rcpt_num < m->rcpt_num);
if (user) *user = m->rcpt[rcpt_num]->user;
if (domain) *domain = m->rcpt[rcpt_num]->domain;
if (mailbox) *mailbox = m->rcpt[rcpt_num]->mailbox;
#ifdef APPLE_OS_X_SERVER
if (auto_fwd) *auto_fwd = m->rcpt[rcpt_num]->afwd;
#endif
}
const char *msg_getrcptall(message_data_t *m, int rcpt_num)
{
assert(0 <= rcpt_num && rcpt_num < m->rcpt_num);
return m->rcpt[rcpt_num]->all;
}
int msg_getrcpt_ignorequota(message_data_t *m, int rcpt_num)
{
assert(0 <= rcpt_num && rcpt_num < m->rcpt_num);
return m->rcpt[rcpt_num]->ignorequota;
}
void msg_setrcpt_status(message_data_t *m, int rcpt_num, int r)
{
assert(0 <= rcpt_num && rcpt_num < m->rcpt_num);
m->rcpt[rcpt_num]->status = r;
}
void *msg_getrock(message_data_t *m)
{
return m->rock;
}
void msg_setrock(message_data_t *m, void *rock)
{
m->rock = rock;
}
static char *parseautheq(char **strp)
{
char *ret;
char *str;
char *s = *strp;
if (!strcmp(s, "<>")) {
*strp = s + 2;
return NULL;
}
ret = (char *) xmalloc(strlen(s)+1);
ret[0]='\0';
str = ret;
if (*s == '<') s++;
while (1)
{
if (*s == '+')
{
int lup;
*str = '\0';
s++;
for (lup=0;lup<2;lup++)
{
if ((*s>='0') && (*s<='9'))
(*str) = (*str) & (*s - '0');
else if ((*s>='A') && (*s<='F'))
(*str) = (*str) & (*s - 'A' + 10);
else {
free(ret);
*strp = s;
return NULL;
}
if (lup==0)
{
(*str) = (*str) << 4;
s++;
}
}
str++;
} else if ((*s >= '!') && (*s <='~') && (*s!='+') && (*s!='=')) {
*str = *s;
str++;
} else {
break;
}
s++;
}
*strp = s;
if (*s && (*s!=' ')) { free(ret); return NULL; }
*str = '\0';
if ((str!=ret) && ( *(str-1)=='>'))
{
*(str-1) = '\0';
}
return ret;
}
static char *parseaddr(char *s)
{
char *p, *ret;
int len;
p = s;
if (*p++ != '<') return 0;
while (*p == '@') {
p++;
if (*p == '[') {
p++;
while (isdigit((int) *p) || *p == '.') p++;
if (*p++ != ']') return 0;
}
else {
while (isalnum((int) *p) || *p == '.' || *p == '-') p++;
}
if (*p == ',' && p[1] == '@') p++;
else if (*p == ':' && p[1] != '@') p++;
else return 0;
}
if (*p == '\"') {
p++;
while (*p && *p != '\"') {
if (*p == '\\') {
if (!*++p) return 0;
}
p++;
}
if (!*p++) return 0;
}
else {
while (*p && *p != '@' && *p != '>') {
if (*p == '\\') {
if (!*++p) return 0;
}
else {
if (*p <= ' ' || (*p & 128) ||
strchr("<>()[]\\,;:\"", *p)) return 0;
}
p++;
}
}
if (*p == '@') {
p++;
if (*p == '[') {
p++;
while (isdigit((int) *p) || *p == '.') p++;
if (*p++ != ']') return 0;
}
else {
while (isalnum((int) *p) || *p == '.' || *p == '-') p++;
}
}
if (*p++ != '>') return 0;
if (*p && *p != ' ') return 0;
len = p - s;
ret = xmalloc(len + 1);
memcpy(ret, s, len);
ret[len] = '\0';
return ret;
}
void clean_retpath(char *rpath)
{
int sl;
if (*rpath == '<') {
sl = strlen(rpath);
memmove(rpath, rpath+1, sl);
sl--;
if (rpath[sl-1] == '>') {
rpath[sl-1] = '\0';
}
}
}
void
clean822space(buf)
char *buf;
{
char *from=buf, *to=buf;
int c;
int commentlevel = 0;
while ((c = *from++)!=0) {
switch (c) {
case '\r':
case '\n':
case '\0':
*to = '\0';
return;
case ' ':
case '\t':
continue;
case '(':
commentlevel++;
break;
case ')':
if (commentlevel) commentlevel--;
break;
case '\\':
if (commentlevel && *from) from++;
default:
if (!commentlevel) *to++ = c;
break;
}
}
}
static int savemsg(struct clientdata *cd,
const struct lmtp_func *func,
message_data_t *m)
{
FILE *f;
struct stat sbuf;
const char **body;
int r;
int nrcpts = m->rcpt_num;
time_t now = time(NULL);
static unsigned msgid_count = 0;
char datestr[80], tls_info[250] = "";
const char *skipheaders[] = {
"Return-Path",
NULL
};
char *addbody, *fold[5], *p;
int addlen, nfold, i;
f = func->spoolfile(m);
if (!f) {
prot_printf(cd->pout,
"451 4.3.%c cannot create temporary file: %s\r\n",
(
#ifdef EDQUOT
errno == EDQUOT ||
#endif
errno == ENOSPC) ? '1' : '2',
error_message(errno));
return IMAP_IOERROR;
}
prot_printf(cd->pout, "354 go ahead\r\n");
if (m->return_path && func->addretpath) {
char *rpath = m->return_path;
const char *hostname = 0;
clean_retpath(rpath);
hostname = NULL;
if (!strchr(rpath, '@') && strlen(rpath) > 0) {
hostname = config_servername;
}
addlen = 2 + strlen(rpath) + (hostname ? 1 + strlen(hostname) : 0);
addbody = xmalloc(addlen + 1);
sprintf(addbody, "<%s%s%s>",
rpath, hostname ? "@" : "", hostname ? hostname : "");
fprintf(f, "Return-Path: %s\r\n", addbody);
spool_cache_header(xstrdup("Return-Path"), addbody, m->hdrcache);
}
rfc822date_gen(datestr, sizeof(datestr), now);
addlen = 8 + strlen(cd->lhlo_param) + strlen(cd->clienthost);
if (m->authuser) addlen += 28 + strlen(m->authuser) + 5;
addlen += 25 + strlen(config_servername) + strlen(CYRUS_VERSION);
#ifdef HAVE_SSL
if (cd->tls_conn) {
addlen += 3 + tls_get_info(cd->tls_conn, tls_info, sizeof(tls_info));
}
#endif
addlen += 2 + strlen(datestr);
p = addbody = xmalloc(addlen + 1);
nfold = 0;
p += sprintf(p, "from %s (%s)", cd->lhlo_param, cd->clienthost);
fold[nfold++] = p;
if (m->authuser) {
const int *ssfp;
sasl_getprop(cd->conn, SASL_SSF, (const void **) &ssfp);
p += sprintf(p, " (authenticated user=%s bits=%d)",
m->authuser, *ssfp);
fold[nfold++] = p;
}
p += sprintf(p, " by %s (Cyrus %s) with LMTP%s%s",
config_servername,
CYRUS_VERSION,
cd->starttls_done ? "S" : "",
cd->authenticated != NOAUTH ? "A" : "");
if (*tls_info) {
fold[nfold++] = p;
p += sprintf(p, " (%s)", tls_info);
}
strcat(p++, ";");
fold[nfold++] = p;
p += sprintf(p, " %s", datestr);
fprintf(f, "Received: ");
for (i = 0, p = addbody; i < nfold; p = fold[i], i++) {
fprintf(f, "%.*s\r\n\t", fold[i] - p, p);
}
fprintf(f, "%s\r\n", p);
spool_cache_header(xstrdup("Received"), addbody, m->hdrcache);
if (func->addheaders) {
struct addheader *h;
for (h = func->addheaders; h && h->name; h++) {
fprintf(f, "%s: %s\r\n", h->name, h->body);
spool_cache_header(xstrdup(h->name), xstrdup(h->body), m->hdrcache);
}
}
r = spool_fill_hdrcache(cd->pin, f, m->hdrcache, skipheaders);
if ((body = msg_getheader(m, "resent-message-id")) && body[0][0]) {
m->id = xstrdup(body[0]);
} else if ((body = msg_getheader(m, "message-id")) && body[0][0]) {
m->id = xstrdup(body[0]);
} else if (body) {
r = IMAP_MESSAGE_BADHEADER;
} else {
pid_t p = getpid();
m->id = xmalloc(40 + strlen(config_servername));
sprintf(m->id, "<cmu-lmtpd-%d-%d-%u@%s>", p, (int) now,
msgid_count++, config_servername);
fprintf(f, "Message-ID: %s\r\n", m->id);
spool_cache_header(xstrdup("Message-ID"), xstrdup(m->id), m->hdrcache);
}
if (!(body = spool_getheader(m->hdrcache, "date"))) {
addbody = xstrdup(datestr);
fprintf(f, "Date: %s\r\n", addbody);
spool_cache_header(xstrdup("Date"), addbody, m->hdrcache);
}
if (!m->return_path &&
(body = msg_getheader(m, "return-path"))) {
m->return_path = xstrdup(body[0]);
clean822space(m->return_path);
clean_retpath(m->return_path);
}
r |= spool_copy_msg(cd->pin, f);
if (r) {
fclose(f);
if (func->removespool) {
func->removespool(m);
}
while (nrcpts--) {
send_lmtp_error(cd->pout, r);
}
return r;
}
fflush(f);
if (ferror(f)) {
while (nrcpts--) {
prot_printf(cd->pout,
"451 4.3.%c cannot copy message to temporary file: %s\r\n",
(
#ifdef EDQUOT
errno == EDQUOT ||
#endif
errno == ENOSPC) ? '1' : '2',
error_message(errno));
}
fclose(f);
if (func->removespool) func->removespool(m);
return IMAP_IOERROR;
}
if (fstat(fileno(f), &sbuf) == -1) {
while (nrcpts--) {
prot_printf(cd->pout,
"451 4.3.2 cannot stat message temporary file: %s\r\n",
error_message(errno));
}
fclose(f);
if (func->removespool) func->removespool(m);
return IMAP_IOERROR;
}
m->size = sbuf.st_size;
m->f = f;
m->data = prot_new(fileno(f), 0);
return 0;
}
static int process_recipient(char *addr, struct namespace *namespace,
int ignorequota,
int (*verify_user)(const char *, const char *,
char *, long,
struct auth_state *),
message_data_t *msg)
{
char *dest;
char *rcpt;
#ifdef APPLE_OS_X_SERVER
const char *luser_relay = NULL;
int has_luser_relay = 0;
#endif
int r, sl;
address_data_t *ret = (address_data_t *) xmalloc(sizeof(address_data_t));
int forcedowncase = config_getswitch(IMAPOPT_LMTP_DOWNCASE_RCPT);
int quoted, detail;
#ifdef APPLE_OS_X_SERVER
struct od_user_opts *pUserOpts = NULL;
#endif
assert(addr != NULL && msg != NULL);
if (*addr == '<') addr++;
dest = rcpt = addr;
ret->all = xstrdup(addr);
sl = strlen(ret->all);
if (ret->all[sl-1] == '>')
ret->all[sl-1] = '\0';
if (*addr == '@') {
addr = strchr(addr, ':');
if (!addr) {
free(ret->all);
free(ret);
return IMAP_PROTOCOL_BAD_PARAMETERS;
}
addr++;
}
quoted = detail = 0;
while (*addr &&
(quoted ||
((config_virtdomains || *addr != '@') && *addr != '>'))) {
if (*addr == '\"') {
quoted = !quoted;
addr++;
continue;
}
if (*addr == '\\') {
addr++;
if (!*addr) break;
} else {
if (*addr == '+') detail = 1;
if (*addr == '@' && !quoted) detail = 0;
}
if (forcedowncase && !detail)
*dest++ = TOLOWER(*addr++);
else
*dest++ = *addr++;
}
*dest = '\0';
#ifdef APPLE_OS_X_SERVER
luser_relay = config_getstring(IMAPOPT_LMTP_LUSER_RELAY);
if ( luser_relay )
{
if ( gLUser_relay_str == NULL )
{
if ( pUserOpts == NULL )
{
pUserOpts = xzmalloc( sizeof(struct od_user_opts) );
}
odGetUserOpts( luser_relay, pUserOpts );
if ( pUserOpts->fRecNamePtr != NULL )
{
if( !verify_user( pUserOpts->fRecNamePtr, NULL, "", ignorequota ? -1 : msg->size, msg->authstate) )
{
has_luser_relay = 1;
gLUser_relay_str = xstrdup( pUserOpts->fRecNamePtr );
}
}
odFreeUserOpts( pUserOpts, 1 );
free( pUserOpts );
pUserOpts = NULL;
}
else
{
has_luser_relay = 1;
}
}
else
{
if ( gLUser_relay_str != NULL )
{
free( gLUser_relay_str );
gLUser_relay_str = NULL;
}
}
#endif
ret->user = ret->rcpt = xstrdup(rcpt);
#ifdef APPLE_OS_X_SERVER
ret->virt = NULL;
ret->afwd = NULL;
if ( gUserOpts == NULL )
{
gUserOpts = xzmalloc( sizeof(struct od_user_opts) );
}
char *p = NULL;
p = strchr( ret->user, '+' );
if ( p != NULL )
{
*p = '\0';
odGetUserOpts( ret->user, gUserOpts );
*p = '+';
}
else
{
odGetUserOpts( ret->user, gUserOpts );
}
if ( gUserOpts->fRecNamePtr != NULL )
{
if ( forcedowncase )
{
lcase( gUserOpts->fRecNamePtr );
}
ret->virt = xstrdup( gUserOpts->fRecNamePtr );
ret->user = ret->virt;
}
#endif
ret->domain = NULL;
if (config_virtdomains && (ret->domain = strrchr(ret->rcpt, '@'))) {
*(ret->domain)++ = '\0';
if (config_defdomain && !strcasecmp(config_defdomain, ret->domain))
ret->domain = NULL;
#ifdef APPLE_OS_X_SERVER
} else if ( strrchr(ret->all, '@') ) {
odGetUserOpts( ret->user, gUserOpts );
if ( gUserOpts->fRecNamePtr != NULL ) {
if (ret->virt) free(ret->virt);
ret->virt = xstrdup( gUserOpts->fRecNamePtr );
ret->user = ret->virt;
}
#endif
}
mboxname_hiersep_tointernal(namespace, ret->rcpt, 0);
if ((ret->mailbox = strchr(ret->rcpt, '+'))) *(ret->mailbox)++ = '\0';
if (!strcmp(ret->user, config_getstring(IMAPOPT_POSTUSER)))
ret->user = NULL;
r = verify_user(ret->user, ret->domain, ret->mailbox,
ignorequota ? -1 : msg->size, msg->authstate);
#ifdef APPLE_OS_X_SERVER
if ( r == IMAP_AUTO_FORWARD_USER )
{
ret->afwd = xstrdup( gUserOpts->fAutoFwdPtr );
}
else if ( r == IMAP_MAILBOX_NONEXISTENT && has_luser_relay )
{
if (ret->virt) free(ret->virt);
ret->virt = xstrdup( gLUser_relay_str );
ret->user = ret->virt;
}
else if ( r == IMAP_MAILBOX_NONEXISTENT && !has_luser_relay )
{
if (ret->all) free(ret->all);
if (ret->virt) free(ret->virt);
if (ret->rcpt) free(ret->rcpt);
if (ret) free(ret);
if (gUserOpts) {
odFreeUserOpts(gUserOpts, 1);
free(gUserOpts);
gUserOpts = NULL;
}
return r;
}
if (ret->user != NULL)
mboxname_hiersep_tointernal( namespace, ret->user, 0 );
#else
if (r) {
free(ret->all);
free(ret->rcpt);
free(ret);
return r;
}
#endif
ret->ignorequota = ignorequota;
msg->rcpt[msg->rcpt_num] = ret;
return 0;
}
static int localauth_mechlist_override(
void *context __attribute__((unused)),
const char *plugin_name __attribute__((unused)),
const char *option,
const char **result,
unsigned *len)
{
if (strcmp(option,"mech_list")==0)
{
*result = "EXTERNAL";
if (len)
*len = strlen(*result);
return SASL_OK;
}
return SASL_FAIL;
}
static struct sasl_callback localauth_override_cb[] = {
{ SASL_CB_GETOPT, &localauth_mechlist_override, NULL },
{ SASL_CB_LIST_END, NULL, NULL },
};
static int reset_saslconn(sasl_conn_t **conn)
{
int ret, secflags;
sasl_security_properties_t *secprops = NULL;
sasl_dispose(conn);
ret = sasl_server_new("lmtp", config_servername,
NULL, NULL, NULL,
NULL, 0, conn);
if(ret != SASL_OK) return ret;
if(saslprops.ipremoteport)
ret = sasl_setprop(*conn, SASL_IPREMOTEPORT,
saslprops.ipremoteport);
if(ret != SASL_OK) return ret;
if(saslprops.iplocalport)
ret = sasl_setprop(*conn, SASL_IPLOCALPORT,
saslprops.iplocalport);
if(ret != SASL_OK) return ret;
secflags = SASL_SEC_NOANONYMOUS;
if (!config_getswitch(IMAPOPT_ALLOWPLAINTEXT)) {
secflags |= SASL_SEC_NOPLAINTEXT;
}
secprops = mysasl_secprops(secflags);
ret = sasl_setprop(*conn, SASL_SEC_PROPS, secprops);
if(ret != SASL_OK) return ret;
if(saslprops.ssf) {
ret = sasl_setprop(*conn, SASL_SSF_EXTERNAL, &saslprops.ssf);
}
if(ret != SASL_OK) return ret;
if(saslprops.authid) {
ret = sasl_setprop(*conn, SASL_AUTH_EXTERNAL, saslprops.authid);
if(ret != SASL_OK) return ret;
}
return SASL_OK;
}
void lmtpmode(struct lmtp_func *func,
struct protstream *pin,
struct protstream *pout,
int fd)
{
message_data_t *msg = NULL;
int max_msgsize;
char buf[4096];
char *p;
int r;
struct clientdata cd;
struct sockaddr_storage localaddr, remoteaddr;
int havelocal = 0, haveremote = 0;
char localip[60], remoteip[60];
socklen_t salen;
char hbuf[NI_MAXHOST];
int niflags = 0;
sasl_ssf_t ssf;
char *auth_id;
int secflags = 0;
sasl_security_properties_t *secprops = NULL;
cd.pin = pin;
cd.pout = pout;
cd.fd = fd;
cd.clienthost[0] = '\0';
cd.lhlo_param[0] = '\0';
cd.authenticated = NOAUTH;
#ifdef HAVE_SSL
cd.tls_conn = NULL;
#endif
cd.starttls_done = 0;
max_msgsize = config_getint(IMAPOPT_MAXMESSAGESIZE);
if(!max_msgsize) max_msgsize = INT_MAX;
msg_new(&msg);
if(saslprops.iplocalport) {
free(saslprops.iplocalport);
saslprops.iplocalport = NULL;
}
if(saslprops.ipremoteport) {
free(saslprops.ipremoteport);
saslprops.ipremoteport = NULL;
}
salen = sizeof(remoteaddr);
r = getpeername(fd, (struct sockaddr *)&remoteaddr, &salen);
if (!r &&
(remoteaddr.ss_family == AF_INET ||
remoteaddr.ss_family == AF_INET6)) {
if (getnameinfo((struct sockaddr *)&remoteaddr, salen,
hbuf, sizeof(hbuf), NULL, 0, NI_NAMEREQD) == 0) {
strncpy(cd.clienthost, hbuf, sizeof(hbuf));
strlcat(cd.clienthost, " ", sizeof(cd.clienthost));
cd.clienthost[sizeof(cd.clienthost)-30] = '\0';
} else {
cd.clienthost[0] = '\0';
}
niflags = NI_NUMERICHOST;
#ifdef NI_WITHSCOPEID
if (((struct sockaddr *)&remoteaddr)->sa_family == AF_INET6)
niflags |= NI_WITHSCOPEID;
#endif
if (getnameinfo((struct sockaddr *)&remoteaddr, salen,
hbuf, sizeof(hbuf), NULL, 0, niflags) != 0)
strlcpy(hbuf, "unknown", sizeof(hbuf));
strlcat(cd.clienthost, "[", sizeof(cd.clienthost));
strlcat(cd.clienthost, hbuf, sizeof(cd.clienthost));
strlcat(cd.clienthost, "]", sizeof(cd.clienthost));
salen = sizeof(localaddr);
if (!getsockname(fd, (struct sockaddr *)&localaddr, &salen)) {
if(iptostring((struct sockaddr *)&localaddr, salen,
localip, sizeof(localip)) == 0) {
havelocal = 1;
saslprops.iplocalport = xstrdup(localip);
}
if(iptostring((struct sockaddr *)&remoteaddr, salen,
remoteip, sizeof(remoteip)) == 0) {
haveremote = 1;
saslprops.ipremoteport = xstrdup(remoteip);
}
} else {
fatal("can't get local addr", EC_SOFTWARE);
}
syslog(LOG_DEBUG, "connection from %s%s",
cd.clienthost,
func->preauth ? " preauth'd as postman" : "");
} else {
func->preauth = 1;
strcpy(cd.clienthost, "[unix socket]");
syslog(LOG_DEBUG, "lmtp connection preauth'd as postman");
}
if (sasl_server_new("lmtp", config_servername, NULL, NULL,
NULL, (func->preauth ? localauth_override_cb : NULL),
0, &cd.conn) != SASL_OK) {
fatal("SASL failed initializing: sasl_server_new()", EC_TEMPFAIL);
}
secflags = SASL_SEC_NOANONYMOUS;
if (!config_getswitch(IMAPOPT_ALLOWPLAINTEXT)) {
secflags |= SASL_SEC_NOPLAINTEXT;
}
secprops = mysasl_secprops(secflags);
sasl_setprop(cd.conn, SASL_SEC_PROPS, secprops);
if (func->preauth) {
cd.authenticated = EXTERNAL_AUTHED;
ssf = 2;
auth_id = "postman";
sasl_setprop(cd.conn, SASL_SSF_EXTERNAL, &ssf);
sasl_setprop(cd.conn, SASL_AUTH_EXTERNAL, auth_id);
deliver_logfd = telemetry_log(auth_id, pin, pout, 0);
} else {
if(havelocal) sasl_setprop(cd.conn, SASL_IPLOCALPORT, &localip );
if(haveremote) sasl_setprop(cd.conn, SASL_IPREMOTEPORT, &remoteip);
}
prot_printf(pout, "220 %s LMTP Cyrus %s ready\r\n",
config_servername,
CYRUS_VERSION);
for (;;) {
nextcmd:
signals_poll();
if (!prot_fgets(buf, sizeof(buf), pin)) {
const char *err = prot_error(pin);
if (err != NULL) {
prot_printf(pout, "421 4.4.1 bye %s\r\n", err);
prot_flush(pout);
}
goto cleanup;
}
p = buf + strlen(buf) - 1;
if (p >= buf && *p == '\n') *p-- = '\0';
if (p >= buf && *p == '\r') *p-- = '\0';
if (!strchr("LlNnQq", buf[0]) &&
shutdown_file(buf, sizeof(buf))) {
prot_printf(pout, "421 4.3.2 %s\r\n", buf);
prot_flush(pout);
func->shutdown(0);
}
switch (buf[0]) {
case 'a':
case 'A':
if (!strncasecmp(buf, "auth ", 5)) {
char mech[128];
int sasl_result;
const char *user;
if (cd.authenticated > 0) {
prot_printf(pout,
"503 5.5.0 already authenticated\r\n");
continue;
}
if (msg->rcpt_num != 0) {
prot_printf(pout,
"503 5.5.0 AUTH not permitted now\r\n");
continue;
}
p = buf + 5;
while ((*p != ' ') && (*p != '\0')) {
p++;
}
if (*p == ' ') {
*p = '\0';
p++;
} else {
p = NULL;
}
strlcpy(mech, buf + 5, sizeof(mech));
r = saslserver(cd.conn, mech, p, "", "334 ", "",
pin, pout, &sasl_result, NULL);
if (r) {
const char *errorstring = NULL;
switch (r) {
case IMAP_SASL_CANCEL:
prot_printf(pout,
"501 5.5.4 client canceled authentication\r\n");
break;
case IMAP_SASL_PROTERR:
errorstring = prot_error(pin);
prot_printf(pout,
"501 5.5.4 Error reading client response: %s\r\n",
errorstring ? errorstring : "");
break;
default:
if (sasl_result == SASL_NOMECH) {
prot_printf(pout,
"504 Unrecognized authentication type.\r\n");
continue;
}
else {
sleep(3);
if (remoteaddr.ss_family == AF_INET ||
remoteaddr.ss_family == AF_INET6) {
niflags = NI_NUMERICHOST;
#ifdef NI_WITHSCOPEID
if (remoteaddr.ss_family == AF_INET6)
niflags |= NI_WITHSCOPEID;
#endif
if (getnameinfo((struct sockaddr *)&remoteaddr,
salen, hbuf, sizeof(hbuf),
NULL, 0, niflags) != 0)
strlcpy(hbuf, "[unknown]", sizeof(hbuf));
}
else
strlcpy(hbuf, "[unix socket]", sizeof(hbuf));
syslog(LOG_ERR, "badlogin: %s %s %s",
hbuf, mech, sasl_errdetail(cd.conn));
snmp_increment_args(AUTHENTICATION_NO, 1,
VARIABLE_AUTH, hash_simple(mech),
VARIABLE_LISTEND);
prot_printf(pout, "501 5.5.4 %s\r\n",
sasl_errstring((r == SASL_NOUSER ?
SASL_BADAUTH : r),
NULL, NULL));
}
}
reset_saslconn(&cd.conn);
continue;
}
r = sasl_getprop(cd.conn, SASL_USERNAME, (const void **) &user);
if (r != SASL_OK) {
prot_printf(pout, "501 5.5.4 SASL Error\r\n");
reset_saslconn(&cd.conn);
goto nextcmd;
}
deliver_logfd = telemetry_log(user, pin, pout, 0);
snmp_increment_args(AUTHENTICATION_YES,1,
VARIABLE_AUTH, hash_simple(mech),
VARIABLE_LISTEND);
syslog(LOG_NOTICE, "login: %s %s %s%s %s",
cd.clienthost, user, mech,
cd.starttls_done ? "+TLS" : "", "User logged in");
cd.authenticated = DIDAUTH;
prot_printf(pout, "235 Authenticated!\r\n");
prot_setsasl(pin, cd.conn);
prot_setsasl(pout, cd.conn);
continue;
}
goto syntaxerr;
case 'd':
case 'D':
if (!strcasecmp(buf, "data")) {
int delivered = 0;
int j;
if (!msg->rcpt_num) {
prot_printf(pout, "503 5.5.1 No recipients\r\n");
continue;
}
r = savemsg(&cd, func, msg);
if (r) {
goto rset;
}
if (msg->size > max_msgsize) {
prot_printf(pout,
"552 5.2.3 Message size (%d) exceeds fixed "
"maximum message size (%d)\r\n",
msg->size, max_msgsize);
continue;
}
snmp_increment(mtaReceivedMessages, 1);
snmp_increment(mtaReceivedVolume, roundToK(msg->size));
snmp_increment(mtaReceivedRecipients, msg->rcpt_num);
r = func->deliver(msg, msg->authuser, msg->authstate);
for (j = 0; j < msg->rcpt_num; j++) {
if (!msg->rcpt[j]->status) delivered++;
send_lmtp_error(pout, msg->rcpt[j]->status);
}
snmp_increment(mtaTransmittedMessages, delivered);
snmp_increment(mtaTransmittedVolume,
roundToK(delivered * msg->size));
goto rset;
}
goto syntaxerr;
case 'l':
case 'L':
if (!strncasecmp(buf, "lhlo ", 5)) {
int mechcount;
const char *mechs;
prot_printf(pout, "250-%s\r\n"
"250-8BITMIME\r\n"
"250-ENHANCEDSTATUSCODES\r\n"
"250-PIPELINING\r\n",
config_servername);
if (max_msgsize < INT_MAX)
prot_printf(pout, "250-SIZE %d\r\n", max_msgsize);
else
prot_printf(pout, "250-SIZE\r\n");
if (tls_enabled() && !cd.starttls_done && !cd.authenticated) {
prot_printf(pout, "250-STARTTLS\r\n");
}
if (cd.authenticated <= NOAUTH &&
sasl_listmech(cd.conn, NULL, "AUTH ", " ", "", &mechs,
NULL, &mechcount) == SASL_OK &&
mechcount > 0) {
prot_printf(pout,"250-%s\r\n", mechs);
}
prot_printf(pout, "250 IGNOREQUOTA\r\n");
strlcpy(cd.lhlo_param, buf + 5, sizeof(cd.lhlo_param));
continue;
}
goto syntaxerr;
case 'm':
case 'M':
if (!cd.authenticated) {
if (config_getswitch(IMAPOPT_SOFT_NOAUTH)) {
prot_printf(pout, "430 Authentication required\r\n");
} else {
prot_printf(pout, "530 Authentication required\r\n");
}
continue;
}
if (!strncasecmp(buf, "mail ", 5)) {
char *tmp;
if (msg->return_path) {
prot_printf(pout,
"503 5.5.1 Nested MAIL command\r\n");
continue;
}
if (strncasecmp(buf+5, "from:", 5) != 0 ||
!(msg->return_path = parseaddr(buf+10))) {
prot_printf(pout,
"501 5.5.4 Syntax error in parameters\r\n");
continue;
}
tmp = buf+10+strlen(msg->return_path);
while (*tmp == ' ') {
tmp++;
switch (*tmp) {
case 'a': case 'A':
if (strncasecmp(tmp, "auth=", 5) != 0) {
goto badparam;
}
tmp += 5;
msg->authuser = parseautheq(&tmp);
if (msg->authuser) {
msg->authstate = auth_newstate(msg->authuser);
} else {
msg->authstate = NULL;
}
break;
case 'b': case 'B':
if (strncasecmp(tmp, "body=", 5) != 0) {
goto badparam;
}
tmp += 5;
if (!strncasecmp(tmp, "7bit", 4)) {
tmp += 4;
} else if (!strncasecmp(tmp, "8bitmime", 8)) {
tmp += 8;
} else {
prot_printf(pout,
"501 5.5.4 Unrecognized BODY type\r\n");
goto nextcmd;
}
break;
case 's': case 'S':
if (strncasecmp(tmp, "size=", 5) != 0) {
goto badparam;
}
tmp += 5;
if (!isdigit((int) *tmp)) {
prot_printf(pout,
"501 5.5.2 SIZE requires a value\r\n");
goto nextcmd;
}
msg->size = strtoul(tmp, &p, 10);
tmp = p;
if (errno == ERANGE || msg->size < 0 ||
msg->size > max_msgsize) {
prot_printf(pout,
"552 5.2.3 Message SIZE exceeds fixed "
"maximum message size (%d)\r\n",
max_msgsize);
goto nextcmd;
}
break;
default:
badparam:
prot_printf(pout,
"501 5.5.4 Unrecognized parameters\r\n");
goto nextcmd;
}
}
if (*tmp != '\0') {
prot_printf(pout,
"501 5.5.4 Syntax error in parameters\r\n");
continue;
}
prot_printf(pout, "250 2.1.0 ok\r\n");
continue;
}
goto syntaxerr;
case 'n':
case 'N':
if (!strcasecmp(buf, "noop")) {
prot_printf(pout,"250 2.0.0 ok\r\n");
continue;
}
goto syntaxerr;
case 'q':
case 'Q':
if (!strcasecmp(buf, "quit")) {
prot_printf(pout,"221 2.0.0 bye\r\n");
prot_flush(pout);
goto cleanup;
}
goto syntaxerr;
case 'r':
case 'R':
if (!strncasecmp(buf, "rcpt ", 5)) {
char *rcpt = NULL;
int ignorequota = 0;
char *tmp;
if (!msg->return_path) {
prot_printf(pout, "503 5.5.1 Need MAIL command\r\n");
continue;
}
if (!(msg->rcpt_num % RCPT_GROW)) {
msg->rcpt = (address_data_t **)
xrealloc(msg->rcpt, (msg->rcpt_num + RCPT_GROW + 1) *
sizeof(address_data_t *));
}
if (strncasecmp(buf+5, "to:", 3) != 0 ||
!(rcpt = parseaddr(buf+8))) {
prot_printf(pout,
"501 5.5.4 Syntax error in parameters\r\n");
continue;
}
tmp = buf+8+strlen(rcpt);
while (*tmp == ' ') {
tmp++;
switch (*tmp) {
case 'i': case 'I':
if (strncasecmp(tmp, "ignorequota", 12) != 0) {
goto badrparam;
}
tmp += 12;
ignorequota = 1;
break;
default:
badrparam:
prot_printf(pout,
"501 5.5.4 Unrecognized parameters\r\n");
goto nextcmd;
}
}
if (*tmp != '\0') {
prot_printf(pout,
"501 5.5.4 Syntax error in parameters\r\n");
continue;
}
r = process_recipient(rcpt,
func->namespace,
ignorequota,
func->verify_user,
msg);
if (rcpt) free(rcpt);
if (r) {
send_lmtp_error(pout, r);
continue;
}
msg->rcpt_num++;
msg->rcpt[msg->rcpt_num] = NULL;
prot_printf(pout, "250 2.1.5 ok\r\n");
continue;
}
else if (!strcasecmp(buf, "rset")) {
prot_printf(pout, "250 2.0.0 ok\r\n");
rset:
if (msg) msg_free(msg);
msg_new(&msg);
continue;
}
goto syntaxerr;
case 's':
case 'S':
#ifdef HAVE_SSL
if (!strcasecmp(buf, "starttls") && tls_enabled() &&
!func->preauth) {
int *layerp;
sasl_ssf_t ssf;
char *auth_id;
layerp = (int *) &ssf;
if (cd.starttls_done == 1) {
prot_printf(pout, "454 4.3.3 %s\r\n",
"Already successfully executed STARTTLS");
continue;
}
if (msg->rcpt_num != 0) {
prot_printf(pout,
"503 5.5.0 STARTTLS not permitted now\r\n");
continue;
}
r=tls_init_serverengine("lmtp",
5,
1,
1);
if (r == -1) {
syslog(LOG_ERR, "[lmtpd] error initializing TLS");
prot_printf(pout, "454 4.3.3 %s\r\n", "Error initializing TLS");
continue;
}
prot_printf(pout, "220 %s\r\n", "Begin TLS negotiation now");
prot_flush(pout);
r=tls_start_servertls(0,
1,
layerp,
&auth_id,
&(cd.tls_conn));
if (r==-1) {
prot_printf(pout, "454 4.3.3 STARTTLS failed\r\n");
syslog(LOG_NOTICE, "[lmtpd] STARTTLS failed: %s", cd.clienthost);
continue;
}
r=sasl_setprop(cd.conn, SASL_SSF_EXTERNAL, &ssf);
if (r != SASL_OK)
fatal("sasl_setprop(SASL_SSF_EXTERNAL) failed: STARTTLS",
EC_TEMPFAIL);
saslprops.ssf = ssf;
r=sasl_setprop(cd.conn, SASL_AUTH_EXTERNAL, auth_id);
if (r != SASL_OK)
fatal("sasl_setprop(SASL_AUTH_EXTERNAL) failed: STARTTLS",
EC_TEMPFAIL);
if(saslprops.authid) {
free(saslprops.authid);
saslprops.authid = NULL;
}
if(auth_id)
saslprops.authid = xstrdup(auth_id);
prot_settls(pin, cd.tls_conn);
prot_settls(pout, cd.tls_conn);
cd.starttls_done = 1;
continue;
}
#endif
goto syntaxerr;
case 'v':
case 'V':
if (!strncasecmp(buf, "vrfy ", 5)) {
prot_printf(pout,
"252 2.3.3 try RCPT to attempt delivery\r\n");
continue;
}
goto syntaxerr;
default:
syntaxerr:
prot_printf(pout, "500 5.5.2 Syntax error\r\n");
continue;
}
}
cleanup:
if (msg) msg_free(msg);
if (cd.conn) sasl_dispose(&cd.conn);
cd.starttls_done = 0;
#ifdef HAVE_SSL
if (cd.tls_conn) {
tls_reset_servertls(&cd.tls_conn);
cd.tls_conn = NULL;
}
#endif
}
#define ISGOOD(r) (((r) / 100) == 2)
#define TEMPFAIL(r) (((r) / 100) == 4)
#define PERMFAIL(r) (((r) / 100) == 5)
#define ISCONT(s) (s && (s[3] == '-'))
static int revconvert_lmtp(const char *code)
{
int c = atoi(code);
switch (c) {
case 250:
case 251:
return 0;
case 451:
if (code[4] == '4' && code[6] == '3') {
if (code[8] == '0') {
return IMAP_IOERROR;
} else if (code[8] == '1') {
return IMAP_NOSPACE;
} else {
return IMAP_IOERROR;
}
}
else if (code[4] == '4' && code [6] == '4') {
return IMAP_SERVER_UNAVAILABLE;
}
else {
return IMAP_IOERROR;
}
case 452:
return IMAP_QUOTA_EXCEEDED;
case 550:
if (code[4] == '5' && code[6] == '7') {
return IMAP_PERMISSION_DENIED;
} else if (code[4] == '5' && code[6] == '1') {
return IMAP_MAILBOX_NONEXISTENT;
}
return IMAP_PERMISSION_DENIED;
case 552:
if (code[6] == '2') {
return IMAP_QUOTA_EXCEEDED;
} else if (code[6] == '3') {
return IMAP_MESSAGE_TOO_LARGE;
}
return IMAP_QUOTA_EXCEEDED;
case 554:
return IMAP_MESSAGE_BADHEADER;
default:
if (ISGOOD(c)) return 0;
else if (TEMPFAIL(c)) return IMAP_AGAIN;
else if (PERMFAIL(c)) return IMAP_PROTOCOL_ERROR;
else return IMAP_AGAIN;
}
}
static int ask_code(const char *s)
{
int ret = 0;
if (s==NULL) return -1;
if (strlen(s) < 3) return -1;
if ((isdigit((int) s[0])==0) ||
(isdigit((int) s[1])==0) ||
(isdigit((int) s[2])==0))
{
return -1;
}
ret = ((s[0]-'0')*100)+((s[1]-'0')*10)+(s[2]-'0');
return ret;
}
static int getlastresp(char *buf, int len, int *code, struct protstream *pin)
{
do {
if (!prot_fgets(buf, len, pin)) {
*code = 400;
return IMAP_SERVER_UNAVAILABLE;
}
} while (ISCONT(buf));
*code = ask_code(buf);
return 0;
}
static void pushmsg(struct protstream *in, struct protstream *out,
int isdotstuffed)
{
char buf[8192], *p;
int lastline_hadendline = 1;
while (prot_fgets(buf, sizeof(buf)-2, in)) {
if (!isdotstuffed && (lastline_hadendline == 1) && (buf[0]=='.')) {
prot_putc('.', out);
}
p = buf + strlen(buf) - 1;
if (*p == '\n') {
if (p == buf || p[-1] != '\r') {
p[0] = '\r';
p[1] = '\n';
p[2] = '\0';
}
lastline_hadendline = 1;
}
else if (*p == '\r') {
if (buf[0] == '\r' && buf[1] == '\0') {
lastline_hadendline = 1;
} else {
prot_ungetc('\r', in);
*p = '\0';
lastline_hadendline = 0;
}
} else {
lastline_hadendline = 0;
}
while ((p = strchr(buf, '\r')) && p[1] != '\n') {
memmove(p, p+1, strlen(p));
}
prot_write(out, buf, strlen(buf));
}
if (!isdotstuffed) {
prot_printf(out, "\r\n.\r\n");
}
}
int lmtp_runtxn(struct backend *conn, struct lmtp_txn *txn)
{
int j, code, r = 0;
char buf[8192];
int onegood;
assert(conn && txn);
prot_printf(conn->out, "RSET\r\n");
r = getlastresp(buf, sizeof(buf)-1, &code, conn->in);
if (!ISGOOD(code)) {
goto failall;
}
if (!txn->from) {
prot_printf(conn->out, "MAIL FROM:<>");
} else if (txn->from[0] == '<') {
prot_printf(conn->out, "MAIL FROM:%s", txn->from);
} else {
prot_printf(conn->out, "MAIL FROM:<%s>", txn->from);
}
if (CAPA(conn, CAPA_AUTH)) {
prot_printf(conn->out, " AUTH=%s",
txn->auth && txn->auth[0] ? txn->auth : "<>");
}
prot_printf(conn->out, "\r\n");
r = getlastresp(buf, sizeof(buf)-1, &code, conn->in);
if (!ISGOOD(code)) {
goto failall;
}
onegood = 0;
for (j = 0; j < txn->rcpt_num; j++) {
prot_printf(conn->out, "RCPT TO:<%s>", txn->rcpt[j].addr);
if (txn->rcpt[j].ignorequota && CAPA(conn, CAPA_IGNOREQUOTA)) {
prot_printf(conn->out, " IGNOREQUOTA");
}
prot_printf(conn->out, "\r\n");
r = getlastresp(buf, sizeof(buf)-1, &code, conn->in);
if (r) {
goto failall;
}
txn->rcpt[j].r = revconvert_lmtp(buf);
if (ISGOOD(code)) {
onegood = 1;
txn->rcpt[j].result = RCPT_GOOD;
} else if (TEMPFAIL(code)) {
txn->rcpt[j].result = RCPT_TEMPFAIL;
} else if (PERMFAIL(code)) {
if(txn->tempfail_unknown_mailbox &&
txn->rcpt[j].r == IMAP_MAILBOX_NONEXISTENT) {
txn->rcpt[j].result = RCPT_TEMPFAIL;
txn->rcpt[j].r = IMAP_AGAIN;
} else {
txn->rcpt[j].result = RCPT_PERMFAIL;
}
} else {
code = 400;
goto failall;
}
}
if (!onegood) {
return 0;
}
prot_printf(conn->out, "DATA\r\n");
r = getlastresp(buf, sizeof(buf)-1, &code, conn->in);
if (r) {
goto failall;
}
if (code != 354) {
if (ISGOOD(code)) code = 400;
r = IMAP_PROTOCOL_ERROR;
goto failall;
}
pushmsg(txn->data, conn->out, txn->isdotstuffed);
for (j = 0; j < txn->rcpt_num; j++) {
if (txn->rcpt[j].result == RCPT_GOOD) {
r = getlastresp(buf, sizeof(buf)-1, &code, conn->in);
if (r) {
goto failall;
}
txn->rcpt[j].r = revconvert_lmtp(buf);
if (ISGOOD(code)) {
onegood = 1;
txn->rcpt[j].result = RCPT_GOOD;
} else if (TEMPFAIL(code)) {
txn->rcpt[j].result = RCPT_TEMPFAIL;
} else if (PERMFAIL(code)) {
txn->rcpt[j].result = RCPT_PERMFAIL;
} else {
txn->rcpt[j].result = RCPT_TEMPFAIL;
}
}
}
return 0;
failall:
for (j = 0; j < txn->rcpt_num; j++) {
if (ISGOOD(code)) {
txn->rcpt[j].r = 0;
txn->rcpt[j].result = RCPT_GOOD;
} else if (TEMPFAIL(code)) {
txn->rcpt[j].r = IMAP_AGAIN;
txn->rcpt[j].result = RCPT_TEMPFAIL;
} else if (PERMFAIL(code)) {
txn->rcpt[j].r = IMAP_PROTOCOL_ERROR;
txn->rcpt[j].result = RCPT_PERMFAIL;
} else {
abort();
}
}
return r;
}