#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 <sys/wait.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 "acl.h"
#include "assert.h"
#include "util.h"
#include "prot.h"
#include "global.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "mailbox.h"
#include "xmalloc.h"
#include "version.h"
#include "mboxname.h"
#include "mupdate-client.h"
#include "backend.h"
#include "lmtpengine.h"
#include "lmtpstats.h"
const int config_need_data = 0;
struct protstream *deliver_out = NULL, *deliver_in = NULL;
extern int optind;
extern char *optarg;
struct backend **backend_cached = NULL;
struct rcpt {
char rcpt[MAX_MAILBOX_NAME+1];
int rcpt_num;
struct rcpt *next;
};
struct dest {
char server[MAX_MAILBOX_NAME+1];
char authas[MAX_MAILBOX_NAME+1];
int rnum;
struct rcpt *to;
struct dest *next;
};
enum pending {
s_wait,
s_err,
s_done,
nosieve,
done,
};
struct mydata {
int cur_rcpt;
const char *temp[2];
char *authuser;
struct dest *dlist;
enum pending *pend;
};
typedef struct mydata mydata_t;
static int adddest(struct mydata *mydata, const char *rcpt,
char *mailbox, const char *authas,
struct auth_state *authstate, int acloverride);
typedef struct script_data {
char *username;
char *mailboxname;
} script_data_t;
static int deliver(message_data_t *msgdata, char *authuser,
struct auth_state *authstate);
static int verify_user(const char *user, const char *domain, const char *mailbox,
long quotacheck, struct auth_state *authstate);
FILE *proxy_spoolfile(message_data_t *msgdata);
void shut_down(int code);
static void usage();
static struct namespace lmtpd_namespace;
struct lmtp_func mylmtp = { &deliver, &verify_user, &shut_down,
&proxy_spoolfile, NULL, &lmtpd_namespace,
0, 0, 0 };
static int quotaoverride = 0;
const char *BB = "";
static mupdate_handle *mhandle = NULL;
int deliver_logfd = -1;
static struct sasl_callback mysasl_cb[] = {
{ SASL_CB_GETOPT, &mysasl_config, NULL },
{ SASL_CB_PROXY_POLICY, &mysasl_proxy_policy, NULL },
{ SASL_CB_CANON_USER, &mysasl_canon_user, NULL },
{ SASL_CB_LIST_END, NULL, NULL }
};
int service_init(int argc __attribute__((unused)),
char **argv __attribute__((unused)),
char **envp __attribute__((unused)))
{
int r;
if (geteuid() == 0) return 1;
signals_set_shutdown(&shut_down);
signal(SIGPIPE, SIG_IGN);
BB = config_getstring(IMAPOPT_POSTUSER);
global_sasl_init(1, 1, mysasl_cb);
if ((r = mboxname_init_namespace(&lmtpd_namespace, 0)) != 0) {
syslog(LOG_ERR, error_message(r));
fatal(error_message(r), EC_CONFIG);
}
if (!config_mupdate_server) {
syslog(LOG_ERR, "no mupdate_server defined");
return EC_CONFIG;
}
mhandle = NULL;
backend_cached = xmalloc(sizeof(struct backend *));
backend_cached[0] = NULL;
return 0;
}
static int mupdate_ignore_cb(struct mupdate_mailboxdata *mdata __attribute__((unused)),
const char *cmd __attribute__((unused)),
void *context __attribute__((unused)))
{
return MUPDATE_FAIL;
}
int service_main(int argc __attribute__((unused)),
char **argv __attribute__((unused)),
char **envp __attribute__((unused)))
{
int opt;
int r;
deliver_in = prot_new(0, 0);
deliver_out = prot_new(1, 1);
prot_setflushonread(deliver_in, deliver_out);
prot_settimeout(deliver_in, 300);
while ((opt = getopt(argc, argv, "q")) != EOF) {
switch(opt) {
case 'q':
quotaoverride = 1;
break;
default:
usage();
}
}
r = 0;
if (mhandle) {
r = mupdate_noop(mhandle, mupdate_ignore_cb, NULL);
if(r) {
mupdate_disconnect(&mhandle);
}
}
if (!mhandle) {
r = mupdate_connect(config_mupdate_server, NULL, &mhandle, NULL);
}
if (!r) {
lmtpmode(&mylmtp, deliver_in, deliver_out, 0);
} else {
mhandle = NULL;
syslog(LOG_ERR, "couldn't connect to %s: %s", config_mupdate_server,
error_message(r));
prot_printf(deliver_out, "451 %s LMTP Cyrus %s %s\r\n",
config_servername, CYRUS_VERSION, error_message(r));
}
if (deliver_in) prot_free(deliver_in);
if (deliver_out) prot_free(deliver_out);
deliver_in = deliver_out = NULL;
if (deliver_logfd != -1) {
close(deliver_logfd);
deliver_logfd = -1;
}
cyrus_close_sock(0);
cyrus_close_sock(1);
cyrus_close_sock(2);
return 0;
}
void service_abort(int error)
{
shut_down(error);
}
static void usage()
{
fprintf(stderr, "421-4.3.0 usage: lmtpproxyd [-C <alt_config>]\r\n");
fprintf(stderr, "421 4.3.0 %s\n", CYRUS_VERSION);
exit(EC_USAGE);
}
static struct backend *proxyd_findserver(const char *server)
{
int i = 0;
struct backend *ret = NULL;
while (backend_cached && backend_cached[i]) {
if (!strcmp(server, backend_cached[i]->hostname)) {
ret = backend_cached[i];
if ((ret->sock > -1) &&
backend_ping(ret, &protocol[PROTOCOL_LMTP])) {
backend_disconnect(ret, &protocol[PROTOCOL_LMTP]);
}
break;
}
i++;
}
if (!ret || (ret->sock == -1)) {
ret = backend_connect(ret, server, &protocol[PROTOCOL_LMTP], "", NULL);
if (!ret) return NULL;
}
if (!backend_cached[i]) {
backend_cached = (struct backend **)
xrealloc(backend_cached, (i + 2) * sizeof(struct backend *));
backend_cached[i] = ret;
backend_cached[i + 1] = NULL;
}
return ret;
}
static int adddest(struct mydata *mydata, const char *rcpt,
char *mailbox, const char *authas,
struct auth_state *authstate, int acloverride)
{
struct rcpt *new_rcpt = xmalloc(sizeof(struct rcpt));
struct dest *d;
struct mupdate_mailboxdata *mailboxdata;
int r;
int rights;
strlcpy(new_rcpt->rcpt, rcpt, sizeof(new_rcpt->rcpt));
new_rcpt->rcpt_num = mydata->cur_rcpt;
r = mupdate_find(mhandle, mailbox, &mailboxdata);
if (r == MUPDATE_MAILBOX_UNKNOWN) {
r = IMAP_MAILBOX_NONEXISTENT;
} else if (r) {
fatal("error communicating with MUPDATE server", EC_TEMPFAIL);
}
if(!r && !acloverride) {
char *tmp = xstrdup(mailboxdata->acl);
rights = cyrus_acl_myrights(authstate, tmp);
free(tmp);
if(!(rights & ACL_POST)) {
r = (rights & ACL_LOOKUP) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
}
}
if (r) {
free(new_rcpt);
return r;
}
assert(mailboxdata != NULL);
if(mailboxdata->server) {
char *c;
c = strchr(mailboxdata->server, '!');
if(c) *c = '\0';
}
d = mydata->dlist;
for (d = mydata->dlist; d != NULL; d = d->next) {
if (!strcmp(d->server, mailboxdata->server) &&
!strcmp(d->authas, authas ? authas : "")) break;
}
if (d == NULL) {
d = xmalloc(sizeof(struct dest));
strlcpy(d->server, mailboxdata->server, sizeof(d->server));
strlcpy(d->authas, authas ? authas : "", sizeof(d->authas));
d->rnum = 0;
d->to = NULL;
d->next = mydata->dlist;
mydata->dlist = d;
}
d->rnum++;
new_rcpt->next = d->to;
d->to = new_rcpt;
return 0;
}
static void runme(struct mydata *mydata, message_data_t *msgdata)
{
struct dest *d;
d = mydata->dlist;
while (d) {
struct lmtp_txn *lt = LMTP_TXN_ALLOC(d->rnum);
struct rcpt *rc;
struct backend *remote;
int i = 0;
int r = 0;
lt->from = msgdata->return_path;
lt->auth = d->authas[0] ? d->authas : NULL;
lt->isdotstuffed = 0;
lt->tempfail_unknown_mailbox = 1;
prot_rewind(msgdata->data);
lt->data = msgdata->data;
lt->rcpt_num = d->rnum;
rc = d->to;
for (rc = d->to; rc != NULL; rc = rc->next, i++) {
assert(i < d->rnum);
lt->rcpt[i].addr = rc->rcpt;
lt->rcpt[i].ignorequota =
msg_getrcpt_ignorequota(msgdata, rc->rcpt_num);
}
assert(i == d->rnum);
remote = proxyd_findserver(d->server);
if (remote) {
r = lmtp_runtxn(remote, lt);
} else {
for (rc = d->to, i = 0; i < d->rnum; i++) {
lt->rcpt[i].result = RCPT_TEMPFAIL;
lt->rcpt[i].r = IMAP_SERVER_UNAVAILABLE;
}
}
for (rc = d->to, i = 0; rc != NULL; rc = rc->next, i++) {
int j = rc->rcpt_num;
switch (mydata->pend[j]) {
case s_wait:
if (lt->rcpt[i].result != RCPT_GOOD) {
mydata->pend[j] = s_err;
}
break;
case s_err:
break;
case nosieve:
msg_setrcpt_status(msgdata, j, lt->rcpt[i].r);
mydata->pend[j] = done;
break;
case done:
case s_done:
abort();
break;
}
}
free(lt);
d = d->next;
}
}
int deliver(message_data_t *msgdata, char *authuser,
struct auth_state *authstate)
{
int n, nrcpts;
mydata_t mydata;
struct dest *d;
assert(msgdata);
nrcpts = msg_getnumrcpt(msgdata);
assert(nrcpts);
mydata.temp[0] = mydata.temp[1] = NULL;
mydata.authuser = authuser;
mydata.dlist = NULL;
mydata.pend = xzmalloc(sizeof(enum pending) * nrcpts);
for (n = 0; n < nrcpts; n++) {
char namebuf[MAX_MAILBOX_NAME+1] = "";
const char *rcpt, *user, *domain, *mailbox;
int r = 0;
rcpt = msg_getrcptall(msgdata, n);
msg_getrcpt(msgdata, n, &user, &domain, &mailbox);
mydata.cur_rcpt = n;
if (domain) snprintf(namebuf, sizeof(namebuf), "%s!", domain);
if (!user) {
strlcat(namebuf, mailbox, sizeof(namebuf));
r = adddest(&mydata, rcpt, namebuf, mydata.authuser, authstate, 0);
if (r) {
msg_setrcpt_status(msgdata, n, r);
mydata.pend[n] = done;
} else {
mydata.pend[n] = nosieve;
}
}
else {
strlcat(namebuf, "user.", sizeof(namebuf));
strlcat(namebuf, user, sizeof(namebuf));
r = adddest(&mydata, rcpt, namebuf, authuser, authstate, 1);
if (r) {
msg_setrcpt_status(msgdata, n, r);
mydata.pend[n] = done;
} else {
mydata.pend[n] = nosieve;
}
}
}
runme(&mydata, msgdata);
d = mydata.dlist;
while (d) {
struct dest *nextd = d->next;
struct rcpt *rc = d->to;
while (rc) {
struct rcpt *nextrc = rc->next;
free(rc);
rc = nextrc;
}
free(d);
d = nextd;
}
mydata.dlist = NULL;
for (n = 0; n < nrcpts; n++) {
switch (mydata.pend[n]) {
case s_wait:
case s_err:
case s_done:
syslog(LOG_CRIT,
"sieve states reached, but we don't implement sieve");
abort();
break;
case nosieve:
syslog(LOG_CRIT, "still waiting for response to rcpt %d",
n);
abort();
break;
case done:
break;
}
}
runme(&mydata, msgdata);
for (n = 0; n < nrcpts; n++) {
assert(mydata.pend[n] == done || mydata.pend[n] == s_done);
}
free(mydata.pend);
return 0;
}
void fatal(const char* s, int code)
{
if(deliver_out) {
prot_printf(deliver_out,"421 4.3.0 deliver: %s\r\n", s);
prot_flush(deliver_out);
} else {
syslog(LOG_ERR, "FATAL: %s", s);
}
exit(code);
}
void shut_down(int code) __attribute__((noreturn));
void shut_down(int code)
{
int i;
i = 0;
while (backend_cached && backend_cached[i]) {
backend_disconnect(backend_cached[i], &protocol[PROTOCOL_LMTP]);
free(backend_cached[i]);
i++;
}
if (backend_cached) free(backend_cached);
if (deliver_out) prot_flush(deliver_out);
if (mhandle) {
mupdate_disconnect(&mhandle);
}
cyrus_done();
exit(code);
}
static int verify_user(const char *user, const char *domain, const char *mailbox,
long quotacheck __attribute__((unused)),
struct auth_state *authstate __attribute__((unused)))
{
char namebuf[MAX_MAILBOX_NAME+1] = "";
int r = 0;
if ((!user && !mailbox) ||
(domain && (strlen(domain) + 1 > sizeof(namebuf)))) {
r = IMAP_MAILBOX_NONEXISTENT;
} else {
if (domain) snprintf(namebuf, sizeof(namebuf), "%s!", domain);
if (!user) {
if (strlen(namebuf) + strlen(mailbox) > sizeof(namebuf)) {
r = IMAP_MAILBOX_NONEXISTENT;
} else {
strlcat(namebuf, mailbox, sizeof(namebuf));
}
} else {
if (strlen(namebuf) + 5 + strlen(user) > sizeof(namebuf)) {
r = IMAP_MAILBOX_NONEXISTENT;
} else {
strlcat(namebuf, "user.", sizeof(namebuf));
strlcat(namebuf, user, sizeof(namebuf));
}
}
}
#ifdef CHECK_MUPDATE_EARLY
if (!r) {
struct mupdate_mailboxdata *mailboxdata;
r = mupdate_find(mhandle, namebuf, &mailboxdata);
if (r == MUPDATE_NOCONN) {
}
}
if (!r) {
}
if (!r) {
}
#endif
if (r) syslog(LOG_DEBUG, "verify_user(%s) failed: %s", namebuf,
error_message(r));
return r;
}
FILE *proxy_spoolfile(message_data_t *msgdata __attribute__((unused)))
{
return tmpfile();
}