#include <config.h>
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <sys/un.h>
#include "backend.h"
#include "exitcodes.h"
#include "global.h"
#include "imap_err.h"
#include "mupdate-client.h"
#include "prot.h"
#include "proxy.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "xstrlcat.h"
void proxy_adddest(struct dest **dlist, const char *rcpt, int rcpt_num,
char *server, const char *authas)
{
struct dest *d;
for (d = *dlist; d != NULL; d = d->next) {
if (!strcmp(d->server, server) &&
!strcmp(d->authas, authas ? authas : "")) break;
}
if (d == NULL) {
d = xmalloc(sizeof(struct dest));
strlcpy(d->server, server, sizeof(d->server));
strlcpy(d->authas, authas ? authas : "", sizeof(d->authas));
d->rnum = 0;
d->to = NULL;
d->next = *dlist;
*dlist = d;
}
if (rcpt) {
struct rcpt *new_rcpt = xmalloc(sizeof(struct rcpt));
strlcpy(new_rcpt->rcpt, rcpt, sizeof(new_rcpt->rcpt));
new_rcpt->rcpt_num = rcpt_num;
d->rnum++;
new_rcpt->next = d->to;
d->to = new_rcpt;
}
}
void proxy_downserver(struct backend *s)
{
if (!s || (s->sock == -1)) {
return;
}
backend_disconnect(s);
if (s->inbox && (s == *(s->inbox))) *(s->inbox) = NULL;
if (s->current && (s == *(s->current))) *(s->current) = NULL;
s->inbox = s->current = NULL;
if (s->timeout) prot_removewaitevent(s->clientin, s->timeout);
s->timeout = NULL;
s->clientin = NULL;
}
static struct prot_waitevent *
backend_timeout(struct protstream *s __attribute__((unused)),
struct prot_waitevent *ev, void *rock)
{
struct backend *be = (struct backend *) rock;
int is_active = (be->context ? *((int *) be->context) : 0);
if ((!be->current || (be != *(be->current))) && !is_active) {
proxy_downserver(be);
return NULL;
}
else {
ev->mark = time(NULL) + IDLE_TIMEOUT;
return ev;
}
}
struct backend *
proxy_findserver(const char *server,
struct protocol_t *prot,
const char *userid,
struct backend ***cache,
struct backend **current,
struct backend **inbox,
struct protstream *clientin)
{
int i = 0;
struct backend *ret = NULL;
if (current && *current && !strcmp(server, (*current)->hostname)) {
return *current;
}
while (cache && *cache && (*cache)[i]) {
if (!strcmp(server, ((*cache)[i])->hostname)) {
ret = (*cache)[i];
if ((ret->sock != -1) && backend_ping(ret)) {
backend_disconnect(ret);
}
break;
}
i++;
}
if (!ret || (ret->sock == -1)) {
ret = backend_connect(ret, server, prot, userid, NULL, NULL);
if (!ret) return NULL;
if (clientin) {
ret->clientin = clientin;
ret->timeout = prot_addwaitevent(clientin,
time(NULL) + IDLE_TIMEOUT,
backend_timeout, ret);
ret->timeout->mark = time(NULL) + IDLE_TIMEOUT;
}
}
ret->current = current;
ret->inbox = inbox;
if (cache && (!*cache || !(*cache)[i])) {
*cache = (struct backend **)
xrealloc(*cache, (i + 2) * sizeof(struct backend *));
(*cache)[i] = ret;
(*cache)[i + 1] = NULL;
}
return ret;
}
int proxy_check_input(struct protgroup *protin,
struct protstream *clientin,
struct protstream *clientout,
struct protstream *serverin,
struct protstream *serverout,
unsigned long timeout_sec)
{
struct protgroup *protout = NULL;
struct timeval timeout = { timeout_sec, 0 };
int n, ret = 0;
n = prot_select(protin, PROT_NO_FD, &protout, NULL,
timeout_sec ? &timeout : NULL);
if (n == -1 && errno != EINTR) {
syslog(LOG_ERR, "prot_select() failed in proxy_check_input(): %m");
fatal("prot_select() failed in proxy_check_input()", EC_TEMPFAIL);
}
if (n && protout) {
for (; n; n--) {
struct protstream *pin = protgroup_getelement(protout, n-1);
struct protstream *pout = NULL;
if (pin == clientin) {
if (serverout) {
pout = serverout;
} else {
ret = 1;
}
}
else if (pin == serverin) {
pout = clientout;
}
else {
fatal("unknown protstream returned by prot_select in proxy_check_input()",
EC_SOFTWARE);
}
if (pout) {
const char *err;
char buf[4096];
int c;
do {
c = prot_read(pin, buf, sizeof(buf));
if (c == 0 || c < 0) break;
prot_write(pout, buf, c);
} while (c == sizeof(buf));
if ((err = prot_error(pin)) != NULL) {
if (serverout && !strcmp(err, PROT_EOF_STRING)) {
ret = -1;
}
else {
fatal("Lost connection to input stream",
EC_UNAVAILABLE);
}
}
}
}
protgroup_free(protout);
}
return ret;
}