#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <syslog.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <pwd.h>
#include "assert.h"
#include "mboxlist.h"
#include "global.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "mailbox.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "xstrlcat.h"
#include "acl.h"
#include "seen.h"
#include "mboxname.h"
#include "map.h"
#define REQ_OK 0
#define REQ_DENY 1
#define REQ_UNK 2
extern int optind;
static struct namespace fud_namespace;
const int config_need_data = 0;
extern void setproctitle_init(int argc, char **argv, char **envp);
int handle_request(const char *who, const char *name,
struct sockaddr_storage sfrom, socklen_t sfromsiz);
void send_reply(struct sockaddr_storage sfrom, socklen_t sfromsiz, int status,
const char *user, const char *mbox,
int numrecent, time_t lastread, time_t lastarrived);
int soc = 0;
char who[16];
#define MAXLOGNAME 16
#define MAXDOMNAME 20
int begin_handling(void)
{
struct sockaddr_storage sfrom;
socklen_t sfromsiz = sizeof(sfrom);
int r;
char buf[MAXLOGNAME + MAXDOMNAME+ MAX_MAILBOX_NAME + 1];
char username[MAXLOGNAME + MAXDOMNAME];
char mbox[MAX_MAILBOX_NAME+1];
char *q;
int off;
int maxuserlen = MAXLOGNAME + config_virtdomains ? MAXDOMNAME : 0;
while(1) {
memset(username,'\0',MAXLOGNAME + MAXDOMNAME);
memset(mbox,'\0',MAX_MAILBOX_NAME+1);
memset(buf, '\0', MAXLOGNAME + MAX_MAILBOX_NAME + 1);
if (signals_poll() == SIGHUP) {
return 0;
}
r = recvfrom(soc, buf, 511, 0,
(struct sockaddr *) &sfrom, &sfromsiz);
if (r == -1) {
return(errno);
}
for(off = 0; buf[off] != '|' && off < maxuserlen; off++);
if(off > 0 && off < maxuserlen) {
strncpy(username,buf,off);
username[off] = '\0';
} else {
continue;
}
q = buf + off + 1;
strlcpy(mbox, q, sizeof(mbox));
handle_request(username,mbox,sfrom,sfromsiz);
}
}
void shut_down(int code) __attribute__((noreturn));
void shut_down(int code)
{
seen_done();
mboxlist_close();
mboxlist_done();
closelog();
cyrus_done();
exit(code);
}
int service_init(int argc, char **argv, char **envp)
{
if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
setproctitle_init(argc, argv, envp);
signals_set_shutdown(&shut_down);
mboxlist_init(0);
mboxlist_open(NULL);
mailbox_initialize();
return 0;
}
void service_abort(int error)
{
shut_down(error);
}
int service_main(int argc __attribute__((unused)),
char **argv __attribute__((unused)),
char **envp __attribute__((unused)))
{
int r = 0;
if ((r = mboxname_init_namespace(&fud_namespace, 1)) != 0) {
syslog(LOG_ERR, error_message(r));
fatal(error_message(r), EC_CONFIG);
}
r = begin_handling();
shut_down(r);
return 0;
}
static void cyrus_timeout(int signo __attribute__((unused)))
{
return;
}
static int setsigalrm(int enable)
{
struct sigaction action;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
if(enable) {
action.sa_handler = cyrus_timeout;
} else {
action.sa_handler = SIG_IGN;
}
if (sigaction(SIGALRM, &action, NULL) < 0) {
syslog(LOG_ERR, "installing SIGALRM handler: sigaction: %m");
return -1;
}
return 0;
}
int do_proxy_request(const char *who, const char *name,
const char *backend_host,
struct sockaddr_storage sfrom, socklen_t sfromsiz)
{
char tmpbuf[1024];
int rc;
int csoc = -1;
int error;
struct sockaddr_storage cin, cout;
struct addrinfo hints, *res0, *res;
socklen_t cinsiz, coutsiz;
static char *backend_port = NULL;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
if (!backend_port) {
backend_port = "fud";
error = getaddrinfo(backend_host, backend_port, &hints, &res0);
if (error == EAI_SERVICE) {
backend_port = "4201";
error = getaddrinfo(backend_host, backend_port, &hints, &res0);
}
} else
error = getaddrinfo(backend_host, backend_port, &hints, &res0);
if (error != 0) {
send_reply(sfrom, sfromsiz, REQ_UNK, who, name, 0, 0, 0);
rc = IMAP_SERVER_UNAVAILABLE;
goto done;
}
csoc = -1;
for (res = res0; res; res = res->ai_next) {
if (res->ai_family == sfrom.ss_family) {
csoc = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
break;
}
}
if (csoc < 0) {
for (res = res0; res; res = res->ai_next) {
if (res->ai_family != sfrom.ss_family &&
(res->ai_family == AF_INET || res->ai_family == AF_INET6)) {
csoc = socket(res->ai_family, res->ai_socktype,
res->ai_protocol);
break;
}
}
}
if (csoc < 0) {
send_reply(sfrom, sfromsiz, REQ_UNK, who, name, 0, 0, 0);
rc = IMAP_SERVER_UNAVAILABLE;
freeaddrinfo(res0);
goto done;
}
memcpy(&cin, res->ai_addr, res->ai_addrlen);
cinsiz = res->ai_addrlen;
freeaddrinfo(res0);
memset (tmpbuf, '\0', sizeof(tmpbuf));
snprintf (tmpbuf, sizeof(tmpbuf), "%s|%s", who, name);
sendto (csoc, tmpbuf, strlen (tmpbuf), 0,
(struct sockaddr *) &cin, cinsiz);
memset (tmpbuf, '\0', strlen (tmpbuf));
if (setsigalrm(1) < 0) {
rc = IMAP_SERVER_UNAVAILABLE;
send_reply(sfrom, sfromsiz, REQ_UNK, who, name, 0, 0, 0);
goto done;
}
rc = 0;
coutsiz = sizeof(cout);
alarm (1);
rc = recvfrom (csoc, tmpbuf, sizeof(tmpbuf), 0,
(struct sockaddr *) &cout, &coutsiz);
alarm (0);
setsigalrm(0);
if (rc < 1) {
rc = IMAP_SERVER_UNAVAILABLE;
send_reply(sfrom, sfromsiz, REQ_UNK, who, name, 0, 0, 0);
goto done;
}
sendto(soc, tmpbuf, rc, 0, (struct sockaddr *) &sfrom, sfromsiz);
rc = 0;
done:
if(csoc != -1) close(csoc);
return rc;
}
int handle_request(const char *who, const char *name,
struct sockaddr_storage sfrom, socklen_t sfromsiz)
{
int r;
struct mailbox mailbox;
struct seen *seendb;
time_t lastread;
time_t lastarrived;
unsigned recentuid;
char *seenuids;
unsigned numrecent;
char mboxname[MAX_MAILBOX_NAME+1];
char *location, *acl;
int mbflag;
numrecent = 0;
lastread = 0;
lastarrived = 0;
r = (*fud_namespace.mboxname_tointernal)(&fud_namespace,name,who,mboxname);
if (r) return r;
r = mboxlist_detail(mboxname, &mbflag, &location, NULL, NULL, &acl, NULL);
if(r || mbflag & MBTYPE_RESERVE) {
send_reply(sfrom, sfromsiz, REQ_UNK, who, name, 0, 0, 0);
return r;
}
if(mbflag & MBTYPE_REMOTE) {
struct auth_state *mystate;
char *p = NULL;
p = strchr(location, '!');
if(p) *p = '\0';
mystate = auth_newstate("anonymous");
if(cyrus_acl_myrights(mystate, acl) & ACL_USER0) {
auth_freestate(mystate);
return do_proxy_request(who, name, location, sfrom, sfromsiz);
} else {
auth_freestate(mystate);
send_reply(sfrom, sfromsiz, REQ_DENY, who, name, 0, 0, 0);
return 0;
}
}
r = mailbox_open_header(mboxname, NULL, &mailbox);
if (r) {
send_reply(sfrom, sfromsiz, REQ_UNK, who, name, 0, 0, 0);
return r;
}
r = mailbox_open_index(&mailbox);
if (r) {
mailbox_close(&mailbox);
send_reply(sfrom, sfromsiz, REQ_UNK, who, name, 0, 0, 0);
return r;
}
if (mboxname_isusermailbox(mboxname, 0) && !(mailbox.myrights & ACL_USER0)) {
mailbox_close(&mailbox);
send_reply(sfrom, sfromsiz, REQ_DENY, who, name, 0, 0, 0);
return 0;
}
r = seen_open(&mailbox, who, 0, &seendb);
if (!r) {
seenuids = NULL;
r = seen_read(seendb, &lastread, &recentuid, &lastarrived, &seenuids);
if (seenuids) free(seenuids);
seen_close(seendb);
if (r) {
mailbox_close(&mailbox);
send_reply(sfrom, sfromsiz, REQ_UNK, who, name, 0, 0, 0);
return r;
}
} else {
lastread = 0;
recentuid = 0;
lastarrived = 0;
seenuids = "";
}
lastarrived = mailbox.last_appenddate;
{
const char *base;
unsigned long len = 0;
unsigned int msg;
unsigned uid;
map_refresh(mailbox.index_fd, 0, &base, &len,
mailbox.start_offset +
mailbox.exists * mailbox.record_size,
"index", mailbox.name);
for (msg = 0; msg < mailbox.exists; msg++) {
uid = ntohl(*((bit32 *)(base + mailbox.start_offset +
msg * mailbox.record_size +
OFFSET_UID)));
if (uid > recentuid) numrecent++;
}
map_free(&base, &len);
}
mailbox_close(&mailbox);
send_reply(sfrom, sfromsiz, REQ_OK, who, name, numrecent,
lastread, lastarrived);
return(0);
}
void
send_reply(struct sockaddr_storage sfrom, socklen_t sfromsiz,
int status, const char *user, const char *mbox,
int numrecent, time_t lastread, time_t lastarrived)
{
char buf[MAX_MAILBOX_PATH + 16 + 9];
int siz;
switch(status) {
case REQ_DENY:
sendto(soc,"PERMDENY",9,0,(struct sockaddr *) &sfrom, sfromsiz);
break;
case REQ_OK:
siz = snprintf(buf, sizeof(buf), "%s|%s|%d|%d|%d",
user,mbox,numrecent,(int) lastread,
(int)lastarrived);
sendto(soc,buf,siz,0,(struct sockaddr *) &sfrom, sfromsiz);
break;
case REQ_UNK:
sendto(soc,"UNKNOWN",8,0,(struct sockaddr *) &sfrom, sfromsiz);
break;
}
}
void fatal(const char* s, int code)
{
static int recurse_code = 0;
if (recurse_code) {
exit(code);
}
recurse_code = code;
syslog(LOG_ERR, "Fatal error: %s", s);
shut_down(code);
}
void printstring(const char *s __attribute__((unused)))
{
fatal("printstring() executed, but its not used for FUD!",
EC_SOFTWARE);
}