Page MenuHomePhorge

lmtpengine.c
No OneTemporary

Authored By
Unknown
Size
60 KB
Referenced Files
None
Subscribers
None

lmtpengine.c

/* lmtpengine.c: LMTP protocol engine
* $Id: lmtpengine.c,v 1.73 2002/05/07 16:06:20 leg Exp $
*
* Copyright (c) 2000 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 other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@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 <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 <com_err.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 <time.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 "imapconf.h"
#include "iptostring.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "mupdate_err.h"
#include "xmalloc.h"
#include "version.h"
#include "lmtpengine.h"
#include "lmtpstats.h"
#include "tls.h"
#include "telemetry.h"
#define RCPT_GROW 30
/* data per message */
struct Header {
char *name;
int ncontents;
char *contents[1];
};
struct address_data {
char *user;
char *all;
int ignorequota;
int status;
};
struct clientdata {
struct protstream *pin;
struct protstream *pout;
int fd;
char clienthost[250];
char lhlo_param[250];
sasl_conn_t *conn;
#ifdef HAVE_SSL
SSL *tls_conn;
#endif /* HAVE_SSL */
int starttls_done;
};
/* defined in lmtpd.c or lmtpproxyd.c */
extern int deliver_logfd;
/* Enable the resetting of a sasl_conn_t */
static int reset_saslconn(sasl_conn_t **conn);
static struct
{
char *ipremoteport;
char *iplocalport;
sasl_ssf_t ssf;
char *authid;
} saslprops = {NULL,NULL,0,NULL};
/* a simple hash function for sasl mechanisms */
static int hash_simple (const char *str)
{
int value = 0;
int i;
if (!str)
return 0;
for (i = 0; *str; i++)
{
value ^= (*str++ << ((i & 3)*8));
}
return value;
}
/* round to nearest 1024 bytes and return number of Kbytes.
used for SNMP updates. */
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;
}
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",
POSTMASTER);
} else {
prot_printf(pout, "550 5.7.1 Permission denied\r\n");
}
break;
case IMAP_QUOTA_EXCEEDED:
if(config_getswitch("lmtp_overquota_perm_failure",0)) {
/* Not Default - Perm Failure */
prot_printf(pout, "552 5.2.2 Over quota\r\n");
} else {
/* Default - Temp Failure */
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:
/* XXX Might have been moved to other server */
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:
/* Some error we're not expecting. */
prot_printf(pout, "554 5.0.0 Unexpected internal error\r\n");
break;
}
}
/* ----- this section defines functions on message_data_t.
----- access functions and the like, etc. */
/* returns non-zero on failure */
int msg_new(message_data_t **m)
{
message_data_t *ret = (message_data_t *) xmalloc(sizeof(message_data_t));
int i;
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;
for (i = 0; i < HEADERCACHESIZE; i++)
ret->cache[i] = NULL;
*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);
if (m->rcpt[i]->user) free(m->rcpt[i]->user);
free(m->rcpt[i]);
}
free(m->rcpt);
}
if (m->authuser) {
free(m->authuser);
if (m->authstate) auth_freestate(m->authstate);
}
for (i = 0; i < HEADERCACHESIZE; i++) {
if (m->cache[i]) {
int j;
free(m->cache[i]->name);
for (j = 0; j < m->cache[i]->ncontents; j++) {
free(m->cache[i]->contents[j]);
}
free(m->cache[i]);
}
}
free(m);
}
/* hash function used for header cache in struct msg */
static int hashheader(char *header)
{
int x = 0;
/* any CHAR except ' ', :, or a ctrl char */
for (; !iscntrl((int) *header) && (*header != ' ') && (*header != ':');
header++) {
x *= 256;
x += *header;
x %= HEADERCACHESIZE;
}
return x;
}
const char **msg_getheader(message_data_t *m, const char *phead)
{
char *head;
const char **ret = NULL;
int clinit, cl;
assert(m && phead);
head = xstrdup(phead);
lcase(head);
/* check the cache */
clinit = cl = hashheader(head);
while (m->cache[cl] != NULL) {
if (!strcmp(head, m->cache[cl]->name)) {
ret = (const char **) m->cache[cl]->contents;
break;
}
cl++; /* try next hash bin */
cl %= HEADERCACHESIZE;
if (cl == clinit) break; /* gone all the way around */
}
free(head);
return ret;
}
int msg_getsize(message_data_t *m)
{
return m->size;
}
int msg_getnumrcpt(message_data_t *m)
{
return m->rcpt_num;
}
const char *msg_getrcpt(message_data_t *m, int rcpt_num)
{
assert(0 <= rcpt_num && rcpt_num < m->rcpt_num);
return m->rcpt[rcpt_num]->user;
}
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;
}
/* set a recipient status; 'r' should be an IMAP error code that will be
translated into an LMTP status code */
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;
}
/* return a malloc'd string representing the authorized user.
advance 'strp' over the parameter */
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++; /* we'll be liberal and accept "<foo>" */
while (1)
{
/* hexchar */
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!='=')) {
/* ascii char */
*str = *s;
str++;
} else {
/* bad char or end-of-line */
break;
}
s++;
}
*strp = s;
if (*s && (*s!=' ')) { free(ret); return NULL; }
*str = '\0';
/* take off trailing '>' */
if ((str!=ret) && ( *(str-1)=='>'))
{
*(str-1) = '\0';
}
return ret;
}
/* return malloc'd string containing the address */
static char *parseaddr(char *s)
{
char *p;
int len;
p = s;
if (*p++ != '<') return 0;
/* at-domain-list */
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;
}
/* local-part */
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++;
}
}
/* @domain */
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;
s = xstrdup(s);
s[len] = '\0';
return s;
}
/* clean off the <> from the return path */
void clean_retpath(char *rpath)
{
int i, sl;
/* Remove any angle brackets around return path */
if (*rpath == '<') {
sl = strlen(rpath);
for (i = 0; i < sl; i++) {
rpath[i] = rpath[i+1];
}
sl--; /* string is one shorter now */
if (rpath[sl-1] == '>') {
rpath[sl-1] = '\0';
}
}
}
/*
* Destructively remove any whitespace and 822 comments
* from string pointed to by 'buf'. Does not handle continuation header
* lines.
*/
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++;
/* FALL THROUGH */
default:
if (!commentlevel) *to++ = c;
break;
}
}
}
/* copies the message from fin to fout, massaging accordingly:
. newlines are fiddled to \r\n
. "." terminates
. embedded NULs are rejected
. bare \r are removed
*/
static int copy_msg(struct protstream *fin, FILE *fout)
{
char buf[8192], *p;
int r = 0;
while (prot_fgets(buf, sizeof(buf)-1, fin)) {
p = buf + strlen(buf) - 1;
if (p < buf) {
/* buffer start with a \0 */
r = IMAP_MESSAGE_CONTAINSNULL;
continue; /* need to eat the rest of the message */
}
else if (buf[0] == '\r' && buf[1] == '\0') {
/* The message contained \r\0, and fgets is confusing us. */
r = IMAP_MESSAGE_CONTAINSNULL;
continue; /* need to eat the rest of the message */
}
else if (p[0] == '\r') {
/*
* We were unlucky enough to get a CR just before we ran
* out of buffer--put it back.
*/
prot_ungetc('\r', fin);
*p = '\0';
}
else if (p[0] == '\n' && p[-1] != '\r') {
/* found an \n without a \r */
p[0] = '\r';
p[1] = '\n';
p[2] = '\0';
}
else if (p[0] != '\n') {
/* line contained a \0 not at the end */
r = IMAP_MESSAGE_CONTAINSNULL;
continue;
}
/* Remove any lone CR characters */
while ((p = strchr(buf, '\r')) && p[1] != '\n') {
strcpy(p, p+1);
}
if (buf[0] == '.') {
if (buf[1] == '\r' && buf[2] == '\n') {
/* End of message */
goto lmtpdot;
}
/* Remove the dot-stuffing */
fputs(buf+1, fout);
} else {
fputs(buf, fout);
}
}
/* wow, serious error---got a premature EOF. */
return IMAP_IOERROR;
lmtpdot:
return r;
}
/* take a list of headers, pull the first one out and return it in
name and contents.
copies fin to fout, massaging
returns 0 on success, negative on failure */
typedef enum {
NAME_START,
NAME,
COLON,
BODY_START,
BODY
} state;
enum {
NAMEINC = 128,
BODYINC = 1024
};
/* we don't have to worry about dotstuffing here, since it's illegal
for a header to begin with a dot!
returns 0 on success, filling in 'headname' and 'contents' with a static
pointer (blech).
on end of headers, returns 0 with NULL 'headname' and NULL 'contents'
on error, returns < 0
*/
static int parseheader(struct protstream *fin, FILE *fout,
char **headname, char **contents) {
int c;
static char *name = NULL, *body = NULL;
static int namelen = 0, bodylen = 0;
int off = 0;
state s = NAME_START;
int r = 0;
int reject8bit = config_getswitch("reject8bit", 0);
if (namelen == 0) {
namelen += NAMEINC;
name = (char *) xrealloc(name, namelen * sizeof(char));
}
if (bodylen == 0) {
bodylen += BODYINC;
body = (char *) xrealloc(body, bodylen * sizeof(char));
}
/* there are two ways out of this loop, both via gotos:
either we successfully read a header (got_header)
or we hit an error (ph_error) */
while ((c = prot_getc(fin)) != EOF) { /* examine each character */
switch (s) {
case NAME_START:
if (c == '.') {
int peek;
peek = prot_getc(fin);
prot_ungetc(peek, fin);
if (peek == '\r' || peek == '\n') {
/* just reached the end of message */
r = IMAP_MESSAGE_NOBLANKLINE;
goto ph_error;
}
}
if (c == '\r' || c == '\n') {
/* just reached the end of headers */
r = 0;
goto ph_error;
}
/* field-name = 1*ftext
ftext = %d33-57 / %d59-126
; Any character except
; controls, SP, and
; ":". */
if (!((c >= 33 && c <= 57) || (c >= 59 && c <= 126))) {
/* invalid header name */
r = IMAP_MESSAGE_BADHEADER;
goto ph_error;
}
name[0] = tolower(c);
off = 1;
s = NAME;
break;
case NAME:
if (c == ' ' || c == '\t' || c == ':') {
name[off] = '\0';
s = (c == ':' ? BODY_START : COLON);
break;
}
if (!((c >= 33 && c <= 57) || (c >= 59 && c <= 126))) {
r = IMAP_MESSAGE_BADHEADER;
goto ph_error;
}
name[off++] = tolower(c);
if (off >= namelen - 3) {
namelen += NAMEINC;
name = (char *) xrealloc(name, namelen);
}
break;
case COLON:
if (c == ':') {
s = BODY_START;
} else if (c != ' ' && c != '\t') {
/* i want to avoid confusing dot-stuffing later */
while (c == '.') {
fputc(c, fout);
c = prot_getc(fin);
}
r = IMAP_MESSAGE_BADHEADER;
goto ph_error;
}
break;
case BODY_START:
if (c == ' ' || c == '\t') /* eat the whitespace */
break;
off = 0;
s = BODY;
/* falls through! */
case BODY:
/* now we want to convert all newlines into \r\n */
if (c == '\r' || c == '\n') {
int peek;
peek = prot_getc(fin);
fputc('\r', fout);
fputc('\n', fout);
/* we should peek ahead to see if it's folded whitespace */
if (c == '\r' && peek == '\n') {
c = prot_getc(fin);
} else {
c = peek; /* single newline seperator */
}
if (c != ' ' && c != '\t') {
/* this is the end of the header */
body[off] = '\0';
prot_ungetc(c, fin);
goto got_header;
}
/* ignore this whitespace, but we'll copy all the rest in */
break;
} else {
if (c >= 0x80) {
if (reject8bit) {
/* We have been configured to reject all mail of this
form. */
r = IMAP_MESSAGE_CONTAINS8BIT;
goto ph_error;
} else {
/* We have been configured to munge all mail of this
form. */
c = 'X';
}
}
/* just an ordinary character */
body[off++] = c;
if (off >= bodylen - 3) {
bodylen += BODYINC;
body = (char *) xrealloc(body, bodylen);
}
}
}
/* copy this to the output */
fputc(c, fout);
}
/* if we fall off the end of the loop, we hit some sort of error
condition */
ph_error:
/* put the last character back; we'll copy it later */
prot_ungetc(c, fin);
/* and we didn't get a header */
if (headname != NULL) *headname = NULL;
if (contents != NULL) *contents = NULL;
return r;
got_header:
if (headname != NULL) *headname = xstrdup(name);
if (contents != NULL) *contents = xstrdup(body);
return 0;
}
static int fill_cache(struct protstream *fin, FILE *fout, message_data_t *m)
{
int r = 0;
/* let's fill that header cache */
for (;;) {
char *name, *body;
int cl, clinit;
if ((r = parseheader(fin, fout, &name, &body)) < 0) {
break;
}
if (!name) {
/* reached the end of headers */
break;
}
/* put it in the hash table */
clinit = cl = hashheader(name);
while (m->cache[cl] != NULL && strcmp(name, m->cache[cl]->name)) {
cl++; /* resolve collisions linearly */
cl %= HEADERCACHESIZE;
if (cl == clinit) break; /* gone all the way around, so bail */
}
/* found where to put it, so insert it into a list */
if (m->cache[cl]) {
/* add this body on */
m->cache[cl]->contents[m->cache[cl]->ncontents++] = body;
/* whoops, won't have room for the null at the end! */
if (!(m->cache[cl]->ncontents % 8)) {
/* increase the size */
m->cache[cl] = (header_t *)
xrealloc(m->cache[cl],sizeof(header_t) +
((8 + m->cache[cl]->ncontents) * sizeof(char *)));
}
/* have no need of this */
free(name);
} else {
/* create a new entry in the hash table */
m->cache[cl] = (header_t *) xmalloc(sizeof(header_t) +
8 * sizeof(char*));
m->cache[cl]->name = name;
m->cache[cl]->contents[0] = body;
m->cache[cl]->ncontents = 1;
}
/* we always want a NULL at the end */
m->cache[cl]->contents[m->cache[cl]->ncontents] = NULL;
}
if (r) {
/* got a bad header */
/* flush the remaining output */
copy_msg(fin, fout);
/* and return the error */
return r;
} else {
return copy_msg(fin, fout);
}
}
/*
* file in the message structure 'm' from 'pin', assuming a dot-stuffed
* stream a la lmtp.
*
* returns 0 on success, imap error code on failure
*/
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 t;
char datestr[80];
/* Copy to spool file */
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) { /* add the return path */
char *rpath = m->return_path;
const char *hostname = 0;
clean_retpath(rpath);
/* Append our hostname if there's no domain in address */
hostname = NULL;
if (!strchr(rpath, '@')) {
hostname = config_servername;
}
fprintf(f, "Return-Path: <%s%s%s>\r\n",
rpath, hostname ? "@" : "", hostname ? hostname : "");
}
/* add a received header */
t = time(NULL);
rfc822date_gen(datestr, sizeof(datestr), t);
fprintf(f, "Received: from %s (%s)",
cd->lhlo_param, cd->clienthost);
if (m->authuser) {
const int *ssfp;
sasl_getprop(cd->conn, SASL_SSF, (const void **) &ssfp);
fprintf(f, " (authenticated user=%s bits=%d)", m->authuser, *ssfp);
}
fprintf(f, "\r\n\tby %s (Cyrus %s) with LMTP",
config_servername, CYRUS_VERSION);
#ifdef HAVE_SSL
if (cd->tls_conn) {
char tls_info[250];
tls_info[0] = '\0';
/* grab TLS info for Received: header */
tls_get_info(cd->tls_conn, tls_info, sizeof(tls_info));
if (*tls_info) fprintf(f, " (%s)", tls_info);
}
#endif /* HAVE_SSL */
fprintf(f, "; %s\r\n", datestr);
/* add any requested headers */
if (func->addheaders) {
fputs(func->addheaders, f);
}
/* fill the cache */
r = fill_cache(cd->pin, f, m);
if (r) {
fclose(f);
if (func->removespool) {
/* remove the spool'd message */
func->removespool(m);
}
while (nrcpts--) {
send_lmtp_error(cd->pout, r);
}
return r;
}
/* now, using our header cache, fill in the data that we want */
/* first check resent-message-id */
if ((body = msg_getheader(m, "resent-message-id")) != NULL) {
m->id = xstrdup(body[0]);
} else if ((body = msg_getheader(m, "message-id")) != NULL) {
m->id = xstrdup(body[0]);
} else {
m->id = NULL; /* no message-id */
}
if (!m->return_path &&
(body = msg_getheader(m, "return-path"))) {
/* let's grab return_path */
m->return_path = xstrdup(body[0]);
clean822space(m->return_path);
clean_retpath(m->return_path);
}
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);
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);
func->removespool(m);
return IMAP_IOERROR;
}
m->size = sbuf.st_size;
m->f = f;
m->data = prot_new(fileno(f), 0);
return 0;
}
/* see if 'addr' exists. if so, fill in 'ad' appropriately.
on success, return NULL.
on failure, return the error. */
static int process_recipient(char *addr,
int ignorequota,
int (*verify_user)(const char *, long,
struct auth_state *),
message_data_t *msg)
{
char *dest;
char *user;
int r, sl;
address_data_t *ret = (address_data_t *) xmalloc(sizeof(address_data_t));
assert(addr != NULL && msg != NULL);
if (*addr == '<') addr++;
dest = user = addr;
/* preserve the entire address */
ret->all = xstrdup(addr);
sl = strlen(ret->all);
if (ret->all[sl-1] == '>')
ret->all[sl-1] = '\0';
/* now find just the user */
/* Skip at-domain-list */
if (*addr == '@') {
addr = strchr(addr, ':');
if (!addr) {
free(ret->all);
free(ret);
return IMAP_PROTOCOL_BAD_PARAMETERS;
}
addr++;
}
if (*addr == '\"') {
addr++;
while (*addr && *addr != '\"') {
if (*addr == '\\') addr++;
*dest++ = *addr++;
}
}
else {
while (*addr != '@' && *addr != '>') {
if (*addr == '\\') addr++;
*dest++ = *addr++;
}
}
*dest = '\0';
r = verify_user(user, ignorequota ? -1 : msg->size, msg->authstate);
if (r) {
/* we lost */
free(ret->all);
free(ret);
return r;
}
ret->user = xstrdup(user);
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 we are doing local auth, we only support EXTERNAL */
if (strcmp(option,"mech_list")==0)
{
*result = "EXTERNAL";
if (len)
*len = strlen(*result);
return SASL_OK;
}
/* if we don't find the option,
this should percolate to the global getopt */
return SASL_FAIL;
}
static struct sasl_callback localauth_override_cb[] = {
{ SASL_CB_GETOPT, &localauth_mechlist_override, NULL },
{ SASL_CB_LIST_END, NULL, NULL },
};
void lmtpmode(struct lmtp_func *func,
struct protstream *pin,
struct protstream *pout,
int fd)
{
message_data_t *msg = NULL;
char shutdownfilename[1024];
int shutdown_fd = -1;
int max_msgsize;
char buf[4096];
char *p;
int r;
struct clientdata cd;
struct sockaddr_in localaddr, remoteaddr;
int havelocal = 0, haveremote = 0;
char localip[60], remoteip[60];
socklen_t salen;
char clienthost[250];
sasl_ssf_t ssf;
char *auth_id;
int plaintext_result;
int secflags = 0;
sasl_security_properties_t *secprops = NULL;
enum {
EXTERNAL_AUTHED = -1, /* -1: external auth'd, but no AUTH issued */
NOAUTH = 0,
DIDAUTH = 1
} authenticated = NOAUTH;
/* setup the clientdata structure */
cd.pin = pin;
cd.pout = pout;
cd.fd = fd;
cd.clienthost[0] = '\0';
cd.lhlo_param[0] = '\0';
#ifdef HAVE_SSL
cd.tls_conn = NULL;
#endif
cd.starttls_done = 0;
sprintf(shutdownfilename, "%s/msg/shutdown", config_dir);
max_msgsize = config_getint("maxmessagesize", INT_MAX);
msg_new(&msg);
/* don't leak old connections */
if(saslprops.iplocalport) {
free(saslprops.iplocalport);
saslprops.iplocalport = NULL;
}
if(saslprops.ipremoteport) {
free(saslprops.ipremoteport);
saslprops.ipremoteport = NULL;
}
/* determine who we're talking to */
salen = sizeof(remoteaddr);
r = getpeername(fd, (struct sockaddr *)&remoteaddr, &salen);
if (!r && remoteaddr.sin_family == AF_INET) {
/* connected to an internet socket */
struct hostent *hp;
hp = gethostbyaddr((char *)&remoteaddr.sin_addr,
sizeof(remoteaddr.sin_addr), AF_INET);
if (hp != NULL) {
strlcpy(cd.clienthost, hp->h_name, sizeof(cd.clienthost) - 30);
} else {
strlcpy(cd.clienthost, inet_ntoa(remoteaddr.sin_addr),
sizeof(cd.clienthost) - 30);
}
strlcat(cd.clienthost, " [", sizeof(cd.clienthost));
strlcat(cd.clienthost, inet_ntoa(remoteaddr.sin_addr),
sizeof(cd.clienthost));
strlcat(cd.clienthost, "]", sizeof(cd.clienthost));
salen = sizeof(localaddr);
if (!getsockname(fd, (struct sockaddr *)&localaddr, &salen)) {
/* set the ip addresses here */
if(iptostring((struct sockaddr *)&localaddr,
sizeof(struct sockaddr_in), localip, 60) == 0) {
havelocal = 1;
saslprops.iplocalport = xstrdup(localip);
}
if(iptostring((struct sockaddr *)&remoteaddr,
sizeof(struct sockaddr_in), remoteip, 60) == 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 {
/* we're not connected to a internet socket! */
func->preauth = 1;
strcpy(cd.clienthost, "[unix socket]");
syslog(LOG_DEBUG, "lmtp connection preauth'd as postman");
}
/* Setup SASL to go. We need to do this *after* we decide if
* we are preauthed or not. */
if (sasl_server_new("lmtp", NULL, NULL, NULL,
NULL, (func->preauth ? localauth_override_cb : NULL),
0, &cd.conn) != SASL_OK) {
fatal("SASL failed initializing: sasl_server_new()", EC_TEMPFAIL);
}
/* set my allowable security properties */
/* ANONYMOUS is silly because we allow that anyway */
secflags = SASL_SEC_NOANONYMOUS;
plaintext_result = config_getswitch("allowplaintext",1);
if (!config_getswitch("lmtp_allowplaintext", plaintext_result)) {
secflags |= SASL_SEC_NOPLAINTEXT;
}
secprops = mysasl_secprops(secflags);
sasl_setprop(cd.conn, SASL_SEC_PROPS, secprops);
if (func->preauth) {
authenticated = EXTERNAL_AUTHED; /* we'll allow commands,
but we still accept
the AUTH command */
ssf = 2;
auth_id = "postman";
sasl_setprop(cd.conn, SASL_SSF_EXTERNAL, &ssf);
sasl_setprop(cd.conn, SASL_AUTH_EXTERNAL, auth_id);
} 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)-1, 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';
/* Only allow LHLO/NOOP/QUIT when there is a shutdown file */
if (!strchr("LlNnQq", buf[0]) &&
(shutdown_fd = open(shutdownfilename, O_RDONLY, 0)) != -1) {
struct protstream *shutdown_in = prot_new(shutdown_fd, 0);
prot_fgets(buf, sizeof(buf), shutdown_in);
if ((p = strchr(buf, '\r'))!=NULL) *p = 0;
if ((p = strchr(buf, '\n'))!=NULL) *p = 0;
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];
char *in = NULL;
const char *out = NULL;
unsigned int inlen, outlen;
const char *user;
if (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;
}
/* ok, what mechanism ? */
p = buf + 5;
while ((*p != ' ') && (*p != '\0')) {
p++;
}
if (*p == ' ') {
*p = '\0';
p++;
} else {
p = NULL;
}
strlcpy(mech, buf + 5, sizeof(mech));
if (p == NULL) {
in = NULL;
inlen = 0;
} else if (!strcmp(p, "=")) {
/* zero-length initial response */
in = xstrdup("");
inlen = 0;
} else {
unsigned len = strlen(p);
in = xmalloc(len+1);
r = sasl_decode64(p, len, in, len, &inlen);
if (r != SASL_OK) {
prot_printf(pout,
"501 5.5.4 cannot base64 decode\r\n");
if (in) { free(in); in = NULL; }
continue;
}
}
r = sasl_server_start(cd.conn, mech,
in, inlen,
&out, &outlen);
if (in) { free(in); in = NULL; }
if (r == SASL_NOMECH) {
prot_printf(pout,
"504 Unrecognized authentication type.\r\n");
continue;
}
while (r == SASL_CONTINUE) {
char inbase64[4096];
unsigned len;
if(out) {
r = sasl_encode64(out, outlen,
inbase64, sizeof(inbase64), NULL);
if (r != SASL_OK) break;
/* send out */
prot_printf(pout,"334 %s\r\n", inbase64);
}
/* read a line */
if (!prot_fgets(buf, sizeof(buf)-1, pin)) {
goto cleanup;
}
p = buf + strlen(buf) - 1;
if (p >= buf && *p == '\n') *p-- = '\0';
if (p >= buf && *p == '\r') *p-- = '\0';
if(buf[0] == '*') {
prot_printf(pout,
"501 5.5.4 client canceled authentication\r\n");
reset_saslconn(&cd.conn);
goto nextcmd;
}
len = strlen(buf);
in = xmalloc(len+1);
r = sasl_decode64(buf, len, in, len, &inlen);
if (r != SASL_OK) {
prot_printf(pout,
"501 5.5.4 cannot base64 decode\r\n");
reset_saslconn(&cd.conn);
goto nextcmd;
}
r = sasl_server_step(cd.conn,
in, inlen,
&out, &outlen);
if (in) { free(in); in = NULL; }
}
if (in) { free(in); in = NULL; }
if ((r != SASL_OK) && (r != SASL_CONTINUE)) {
sleep(3);
syslog(LOG_ERR, "badlogin: %s %s %s",
remoteaddr.sin_family == AF_INET ?
inet_ntoa(remoteaddr.sin_addr) :
"[unix socket]",
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;
}
/* Create telemetry log */
deliver_logfd = telemetry_log(user, pin, pout);
/* authenticated successfully! */
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");
authenticated += 2;
prot_printf(pout, "235 Authenticated!\r\n");
/* set protection layers */
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;
}
/* copy message from input to msg structure */
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);
/* do delivery, report status */
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)) {
unsigned 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("lmtp") && !func->preauth) {
prot_printf(pout, "250-STARTTLS\r\n");
}
if (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 (!authenticated) {
if (config_getswitch("soft_noauth", 1)) {
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);
/* is any other whitespace allow seperating? */
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, NULL);
} else {
/* do we want to bounce mail because of this? */
/* i guess not. accept with no auth user */
msg->authstate = NULL;
}
break;
case 'b': case 'B':
if (strncasecmp(tmp, "body=", 5) != 0) {
goto badparam;
}
tmp += 5;
/* just verify it's one of
body-value ::= "7BIT" / "8BITMIME" */
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;
/* make sure we have a value */
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;
/* make sure the value is in range */
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)) { /* time to alloc more */
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,
ignorequota,
func->verify_user,
msg);
if (rcpt) free(rcpt); /* malloc'd in parseaddr() */
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("lmtp") &&
!func->preauth) { /* don't need TLS for preauth'd connect */
int *layerp;
sasl_ssf_t ssf;
char *auth_id;
/* SASL and openssl have different ideas
about whether ssf is signed */
layerp = &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, /* depth to verify */
1, /* can client auth? */
0, /* require client to auth? */
1); /* TLS only? */
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");
/* must flush our buffers before starting tls */
prot_flush(pout);
r=tls_start_servertls(0, /* read */
1, /* write */
layerp,
&auth_id,
&(cd.tls_conn));
/* if error */
if (r==-1) {
prot_printf(pout, "454 4.3.3 STARTTLS failed\r\n");
syslog(LOG_NOTICE, "[lmtpd] STARTTLS failed: %s", clienthost);
continue;
}
/* tell SASL about the negotiated layer */
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);
/* tell the prot layer about our new layers */
prot_settls(pin, cd.tls_conn);
prot_settls(pout, cd.tls_conn);
cd.starttls_done = 1;
continue;
}
#endif /* HAVE_SSL*/
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:
/* free resources and return; this connection has been closed */
if (msg) msg_free(msg);
/* security */
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
}
/************** client-side LMTP ****************/
enum {
CAPA_PIPELINING = 1 << 0,
CAPA_AUTH = 1 << 1,
CAPA_IGNOREQUOTA = 1 << 2
};
struct lmtp_conn {
char *host;
int sock;
struct protstream *pin, *pout;
sasl_conn_t *saslconn;
/* lmtp specific properties */
int capability;
char *mechs;
};
#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 554:
return IMAP_MESSAGE_BADHEADER; /* sigh, pick one */
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;
/* check to make sure 0-2 are digits */
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 void chop(char *s)
{
char *p;
assert(s);
p = s + strlen(s) - 1;
if (p[0] == '\n') {
*p-- = '\0';
}
if (p >= s && p[0] == '\r') {
*p-- = '\0';
}
}
static int mysasl_getauthline(struct protstream *p, char **line,
unsigned int *linelen)
{
char buf[2096];
char *str = (char *) buf;
if (!prot_fgets(str, sizeof(buf), p)) {
return SASL_FAIL;
}
if (str[0] == '2') { return SASL_OK; }
if (str[0] == '5') { return SASL_BADAUTH; }
if (str[0] != '3') { return SASL_BADPROT; }
else {
size_t len;
str += 4; /* jump past the "334 " */
len = strlen(str) + 1;
*line = xmalloc(strlen(str) + 1);
if (*str != '\r') { /* decode it */
int r;
r = sasl_decode64(str, strlen(str), *line, len, linelen);
if (r != SASL_OK) {
return r;
}
return SASL_CONTINUE;
} else { /* blank challenge */
*line = NULL;
*linelen = 0;
return SASL_CONTINUE;
}
}
}
/* getlastresp reads from 'pin' until we get an LMTP that isn't a continuation.
it puts it in 'buf', which must be at least 'len' big.
'*code' will contain the integer three digit response code.
if a read failed, '*code == 400', a temporary failure.
returns an IMAP error code. */
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;
}
/* perform authentication against connection 'conn'
returns the SMTP error code from the AUTH attempt */
static int do_auth(struct lmtp_conn *conn)
{
int r;
const int AUTH_ERROR = 420, AUTH_OK = 250;
sasl_security_properties_t *secprops = NULL;
struct sockaddr_in saddr_l;
struct sockaddr_in saddr_r;
socklen_t addrsize;
char buf[2048];
char *in;
const char *out;
unsigned int inlen, outlen;
const char *mechusing;
unsigned b64len;
char localip[60], remoteip[60];
secprops = mysasl_secprops(0);
r = sasl_setprop(conn->saslconn, SASL_SEC_PROPS, secprops);
if (r != SASL_OK) {
return AUTH_ERROR;
}
/* set the IP addresses */
addrsize=sizeof(struct sockaddr_in);
if (getpeername(conn->sock, (struct sockaddr *)&saddr_r, &addrsize) != 0)
return AUTH_ERROR;
addrsize=sizeof(struct sockaddr_in);
if (getsockname(conn->sock, (struct sockaddr *)&saddr_l,&addrsize)!=0)
return AUTH_ERROR;
if (iptostring((struct sockaddr *)&saddr_r,
sizeof(struct sockaddr_in), remoteip, 60) != 0)
return AUTH_ERROR;
if (iptostring((struct sockaddr *)&saddr_l,
sizeof(struct sockaddr_in), localip, 60) != 0)
return AUTH_ERROR;
r = sasl_setprop(conn->saslconn, SASL_IPLOCALPORT, localip);
if (r != SASL_OK) return AUTH_ERROR;
r = sasl_setprop(conn->saslconn, SASL_IPREMOTEPORT, remoteip);
if (r != SASL_OK) return AUTH_ERROR;
/* we now do the actual SASL exchange */
r = sasl_client_start(conn->saslconn,
conn->mechs,
NULL, &out, &outlen, &mechusing);
if ((r != SASL_OK) && (r != SASL_CONTINUE))
return AUTH_ERROR;
if (out == NULL) {
prot_printf(conn->pout, "AUTH %s\r\n", mechusing);
} else {
/* send initial challenge */
r = sasl_encode64(out, outlen, buf, sizeof(buf), &b64len);
if (r != SASL_OK)
return AUTH_ERROR;
prot_printf(conn->pout, "AUTH %s %s\r\n", mechusing, buf);
}
in = NULL;
inlen = 0;
r = mysasl_getauthline(conn->pin, &in, &inlen);
while (r == SASL_CONTINUE) {
r = sasl_client_step(conn->saslconn, in, inlen, NULL, &out, &outlen);
if (in) {
free(in);
}
if (r != SASL_OK && r != SASL_CONTINUE) {
return AUTH_ERROR;
}
r = sasl_encode64(out, outlen, buf, sizeof(buf), &b64len);
if (r != SASL_OK) {
return AUTH_ERROR;
}
prot_write(conn->pout, buf, b64len);
prot_printf(conn->pout, "\r\n");
r = mysasl_getauthline(conn->pin, &in, &inlen);
}
if (r == SASL_OK) {
prot_setsasl(conn->pin, conn->saslconn);
prot_setsasl(conn->pout, conn->saslconn);
/* success */
return AUTH_OK;
} else {
/* don't bounce the message just because *we* can't authenticate */
return AUTH_ERROR;
}
}
/* establish connection, LHLO, and AUTH if possible */
int lmtp_connect(const char *phost,
sasl_callback_t *cb,
struct lmtp_conn **ret)
{
int sock = -1;
char *host = xstrdup(phost);
struct lmtp_conn *conn;
char buf[8192];
int code;
int unix_socket = 0;
assert(host);
assert(ret);
if (host[0] == '/') {
struct sockaddr_un addr;
/* open unix socket */
if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
syslog(LOG_ERR, "socket() failed %m");
goto donesock;
}
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, host);
if (connect(sock, (struct sockaddr *) &addr,
sizeof(addr.sun_family) + strlen(addr.sun_path) + 1) < 0) {
syslog(LOG_ERR, "connect(%s) failed: %m", addr.sun_path);
goto donesock;
}
/* set that we are preauthed */
unix_socket = 1;
/* change host to 'config_servername' */
free(host);
host = xstrdup(config_servername);
} else {
struct hostent *hp;
struct sockaddr_in addr;
struct servent *service;
char *p;
p = strchr(host, ':');
if (p) {
*p++ = '\0';
} else {
p = "lmtp";
}
if ((hp = gethostbyname(host)) == NULL) {
syslog(LOG_ERR, "gethostbyname(%s) failed", host);
goto donesock;
}
/* open inet socket */
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
syslog(LOG_ERR, "socket() failed: %m");
goto donesock;
}
addr.sin_family = AF_INET;
memcpy(&addr.sin_addr, hp->h_addr, hp->h_length);
service = getservbyname(p, "tcp");
if (service) {
addr.sin_port = service->s_port;
} else {
int pn = atoi(p);
if (pn == 0) {
syslog(LOG_ERR, "couldn't find valid lmtp port");
goto donesock;
}
addr.sin_port = htons(pn);
}
if (connect(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
syslog(LOG_ERR, "connect(%s:%s) failed: %m", host, p);
goto donesock;
}
}
donesock:
if (sock == -1) {
/* error during connection */
free(host);
return IMAP_IOERROR;
}
conn = xmalloc(sizeof(struct lmtp_conn));
conn->host = host;
conn->sock = sock;
conn->capability = 0;
conn->mechs = NULL;
conn->saslconn = NULL;
/* setup prot layers */
conn->pin = prot_new(sock, 0);
conn->pout = prot_new(sock, 1);
prot_setflushonread(conn->pin, conn->pout);
/* read greeting */
getlastresp(buf, sizeof(buf)-1, &code, conn->pin);
if (!ISGOOD(code)) goto done;
/* LHLO */
prot_printf(conn->pout, "LHLO %s\r\n", config_servername);
/* read responses */
for (;;) {
if (prot_fgets(buf, sizeof(buf)-1, conn->pin)) {
code = ask_code(buf);
if (code == 250) {
chop(buf);
/* check capability */
if (!strcasecmp(buf + 4, "PIPELINING")) {
conn->capability |= CAPA_PIPELINING;
}
if (!strncasecmp(buf + 4, "AUTH ", 5)) {
conn->capability |= CAPA_AUTH;
/* save mechanisms for later */
conn->mechs = xstrdup(buf + 9);
}
if (!strcasecmp(buf + 4, "IGNOREQUOTA")) {
conn->capability |= CAPA_IGNOREQUOTA;
}
}
if (ISCONT(buf) && ISGOOD(code)) {
continue;
} else {
break;
}
}
/* can't read response */
code = 400;
break;
}
/* check status code */
if (!ISGOOD(code)) goto done;
/* AUTH (but only if we're not preauthed as postman!) */
if (!unix_socket && (conn->capability & CAPA_AUTH) && (conn->mechs)) {
sasl_client_new("lmtp", host, NULL, NULL, cb, 0, &conn->saslconn);
code = do_auth(conn);
}
done:
if (ISGOOD(code)) {
/* return connection */
*ret = conn;
return 0;
} else {
/* not a successful connection; tear it down and return failure */
if (conn) {
if (conn->host) free(conn->host);
if (conn->mechs) free(conn->mechs);
if (conn->saslconn) sasl_dispose(&conn->saslconn);
if (conn->sock) close(conn->sock);
free(conn);
}
return IMAP_SERVER_UNAVAILABLE;
}
}
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)-1, in)) {
/* dot stuff */
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') {
/* The message contained \r\0, and fgets is confusing us.
XXX ignored
*/
lastline_hadendline = 1;
} else {
/*
* We were unlucky enough to get a CR just before we ran
* out of buffer--put it back.
*/
prot_ungetc('\r', in);
*p = '\0';
lastline_hadendline = 0;
}
} else {
lastline_hadendline = 0;
}
/* Remove any lone CR characters */
while ((p = strchr(buf, '\r')) && p[1] != '\n') {
strcpy(p, p+1);
}
prot_write(out, buf, strlen(buf));
}
if (!isdotstuffed) {
/* signify end of message */
prot_printf(out, "\r\n.\r\n");
}
}
int lmtp_runtxn(struct lmtp_conn *conn, struct lmtp_txn *txn)
{
int j, code, r = 0;
char buf[8192];
int onegood;
assert(conn && txn);
/* pipelining v. no pipelining? */
/* here's the straightforward non-pipelining version */
/* rset */
prot_printf(conn->pout, "RSET\r\n");
r = getlastresp(buf, sizeof(buf)-1, &code, conn->pin);
if (!ISGOOD(code)) {
goto failall;
}
/* mail from */
if (!txn->from) {
prot_printf(conn->pout, "MAIL FROM:<>");
} else if (txn->from[0] == '<') {
prot_printf(conn->pout, "MAIL FROM:%s", txn->from);
} else {
prot_printf(conn->pout, "MAIL FROM:<%s>", txn->from);
}
if (conn->capability & CAPA_AUTH) {
prot_printf(conn->pout, " AUTH=%s",
txn->auth && txn->auth[0] ? txn->auth : "<>");
}
prot_printf(conn->pout, "\r\n");
r = getlastresp(buf, sizeof(buf)-1, &code, conn->pin);
if (!ISGOOD(code)) {
goto failall;
}
/* rcpt to */
onegood = 0;
for (j = 0; j < txn->rcpt_num; j++) {
prot_printf(conn->pout, "RCPT TO:<%s>", txn->rcpt[j].addr);
if (txn->ignorequota && (conn->capability & CAPA_IGNOREQUOTA)) {
prot_printf(conn->pout, " IGNOREQUOTA");
}
prot_printf(conn->pout, "\r\n");
r = getlastresp(buf, sizeof(buf)-1, &code, conn->pin);
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 {
/* yikes?!? */
code = 400;
goto failall;
}
}
if (!onegood) {
/* all recipients failed! */
return 0;
}
/* data */
prot_printf(conn->pout, "DATA\r\n");
r = getlastresp(buf, sizeof(buf)-1, &code, conn->pin);
if (r) {
goto failall;
}
if (code != 354) {
/* erg? */
if (ISGOOD(code)) code = 400;
r = IMAP_PROTOCOL_ERROR;
goto failall;
}
/* send the data, dot-stuffing as needed */
pushmsg(txn->data, conn->pout, txn->isdotstuffed);
/* read the response codes, one for each accepted RCPT TO */
for (j = 0; j < txn->rcpt_num; j++) {
if (txn->rcpt[j].result == RCPT_GOOD) {
/* expecting a status code for this recipient */
r = getlastresp(buf, sizeof(buf)-1, &code, conn->pin);
if (r) {
/* technically, some recipients might've succeeded here,
but we'll be paranoid */
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 {
/* yikes?!? */
txn->rcpt[j].result = RCPT_TEMPFAIL;
}
}
}
/* done with txn */
return 0;
failall:
/* something fatal happened during the transaction; we should assign
'code' to all recipients and return */
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 {
/* code should have been a valid number */
abort();
}
}
/* return overall error code already set */
return r;
}
/* send a NOOP to the conn to verify it's still ok */
int lmtp_verify_conn(struct lmtp_conn *conn)
{
char buf[8192];
int r = 0;
int code = 0;
/* noop me */
prot_printf(conn->pout, "NOOP\r\n");
r = getlastresp(buf, sizeof(buf)-1, &code, conn->pin);
if (!r && !ISGOOD(code)) {
r = IMAP_SERVER_UNAVAILABLE;
}
return r;
}
int lmtp_disconnect(struct lmtp_conn *conn)
{
/* quit */
prot_printf(conn->pout, "QUIT\r\n");
/* wait for any response */
prot_getc(conn->pin);
/* close connection */
close(conn->sock);
/* free 'conn' */
free(conn->host);
prot_free(conn->pin);
prot_free(conn->pout);
if (conn->saslconn) sasl_dispose(&conn->saslconn);
if (conn->mechs) free(conn->mechs);
return 0;
}
/* Reset the given sasl_conn_t to a sane state */
static int reset_saslconn(sasl_conn_t **conn)
{
int ret, secflags, plaintext_result;
sasl_security_properties_t *secprops = NULL;
sasl_dispose(conn);
/* do initialization typical of service_main */
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;
plaintext_result = config_getswitch("allowplaintext", 1);
if (!config_getswitch("lmtp_allowplaintext", plaintext_result)) {
secflags |= SASL_SEC_NOPLAINTEXT;
}
secprops = mysasl_secprops(secflags);
ret = sasl_setprop(*conn, SASL_SEC_PROPS, secprops);
if(ret != SASL_OK) return ret;
/* end of service_main initialization excepting SSF */
/* If we have TLS/SSL info, set it */
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;
}
/* End TLS/SSL Info */
return SASL_OK;
}

File Metadata

Mime Type
text/x-c
Expires
Fri, Apr 24, 2:14 PM (1 w, 3 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18726768
Default Alt Text
lmtpengine.c (60 KB)

Event Timeline