#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <syslog.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <errno.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <signal.h>
#include <fcntl.h>
#include "idled.h"
#include "global.h"
#include "mboxlist.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "xstrlcat.h"
#include "hash.h"
#include "exitcodes.h"
const int config_need_data = 0;
extern int optind;
extern char *optarg;
static int verbose = 0;
static int debugmode = 0;
static time_t idle_timeout;
struct ientry {
pid_t pid;
time_t itime;
struct ientry *next;
};
static struct hash_table itable;
static struct ientry *ifreelist;
static int itable_inc = 100;
void idle_done(char *mboxname, pid_t pid);
void fatal(const char *msg, int err)
{
if (debugmode) fprintf(stderr, "dying with %s %d\n",msg,err);
syslog(LOG_CRIT, "%s", msg);
syslog(LOG_NOTICE, "exiting");
cyrus_done();
exit(err);
}
static int mbox_count_cb(void *rockp,
const char *key __attribute__((unused)),
int keylen __attribute__((unused)),
const char *data __attribute__((unused)),
int datalen __attribute__((unused)))
{
int *ip = (int *) rockp;
(*ip)++;
return 0;
}
static struct ientry *get_ientry(void)
{
struct ientry *t;
if (!ifreelist) {
struct ientry *n;
int i;
n = xmalloc(itable_inc * sizeof(struct ientry));
ifreelist = n;
for (i = 0; i < itable_inc - 1; i++) {
n[i].next = n + (i + 1);
}
n[i].next = NULL;
}
t = ifreelist;
ifreelist = ifreelist->next;
return t;
}
void idle_done(char *mboxname, pid_t pid)
{
struct ientry *t, *p = NULL;
t = (struct ientry *) hash_lookup(mboxname, &itable);
while (t && t->pid != pid) {
p = t;
t = t->next;
}
if (t) {
if (!p) {
p = t->next;
hash_insert(mboxname, p, &itable);
}
else {
p->next = t->next;
}
t->next = ifreelist;
ifreelist = t;
}
}
void process_msg(idle_data_t *idledata)
{
struct ientry *t, *n;
switch (idledata->msg) {
case IDLE_INIT:
if (verbose || debugmode)
syslog(LOG_DEBUG, "imapd[%ld]: IDLE_INIT '%s'\n",
idledata->pid, idledata->mboxname);
t = (struct ientry *) hash_lookup(idledata->mboxname, &itable);
n = get_ientry();
n->pid = idledata->pid;
n->itime = time(NULL);
n->next = t;
hash_insert(idledata->mboxname, n, &itable);
break;
case IDLE_NOTIFY:
if (verbose || debugmode)
syslog(LOG_DEBUG, "IDLE_NOTIFY '%s'\n", idledata->mboxname);
t = (struct ientry *) hash_lookup(idledata->mboxname, &itable);
while (t) {
if ((t->itime + idle_timeout) < time(NULL)) {
if (verbose || debugmode)
syslog(LOG_DEBUG, " TIMEOUT %d\n", t->pid);
n = t;
t = t->next;
idle_done(idledata->mboxname, n->pid);
}
else {
if (verbose || debugmode)
syslog(LOG_DEBUG, " SIGUSR1 %d\n", t->pid);
kill(t->pid, SIGUSR1);
t = t->next;
}
}
break;
case IDLE_DONE:
if (verbose || debugmode)
syslog(LOG_DEBUG, "imapd[%ld]: IDLE_DONE '%s'\n",
idledata->pid, idledata->mboxname);
idle_done(idledata->mboxname, idledata->pid);
break;
case IDLE_NOOP:
break;
default:
syslog(LOG_ERR, "unrecognized message: %lx", idledata->msg);
break;
}
}
void idle_alert(char *key __attribute__((unused)),
void *data,
void *rock __attribute__((unused)))
{
struct ientry *t = (struct ientry *) data;
while (t) {
if (verbose || debugmode)
syslog(LOG_DEBUG, " SIGUSR2 %d\n", t->pid);
kill(t->pid, SIGUSR2);
t = t->next;
}
}
int main(int argc, char **argv)
{
char shutdownfilename[1024];
char *p = NULL;
int opt;
int nmbox = 0;
int s, len;
struct sockaddr_un local;
idle_data_t idledata;
struct sockaddr_un from;
socklen_t fromlen;
mode_t oldumask;
fd_set read_set, rset;
int nfds;
struct timeval timeout;
pid_t pid;
int fd;
char *alt_config = NULL;
const char *idle_sock;
p = getenv("CYRUS_VERBOSE");
if (p) verbose = atoi(p) + 1;
while ((opt = getopt(argc, argv, "C:d")) != EOF) {
switch (opt) {
case 'C':
alt_config = optarg;
break;
case 'd':
debugmode = 1;
break;
default:
fprintf(stderr, "invalid argument\n");
exit(EC_USAGE);
break;
}
}
cyrus_init(alt_config, "idled", 0);
snprintf(shutdownfilename, sizeof(shutdownfilename), "%s/msg/shutdown",
config_dir);
idle_timeout = config_getint(IMAPOPT_TIMEOUT);
if (idle_timeout < 30) idle_timeout = 30;
idle_timeout *= 60;
mboxlist_init(0);
mboxlist_open(NULL);
config_mboxlist_db->foreach(mbdb, "", 0, NULL, &mbox_count_cb,
&nmbox, NULL);
mboxlist_close();
mboxlist_done();
construct_hash_table(&itable, nmbox + 1, 1);
ifreelist = NULL;
if ((s = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) {
perror("socket");
cyrus_done();
exit(1);
}
local.sun_family = AF_UNIX;
idle_sock = config_getstring(IMAPOPT_IDLESOCKET);
if (idle_sock) {
strlcpy(local.sun_path, idle_sock, sizeof(local.sun_path));
}
else {
strlcpy(local.sun_path, config_dir, sizeof(local.sun_path));
strlcat(local.sun_path, FNAME_IDLE_SOCK, sizeof(local.sun_path));
}
unlink(local.sun_path);
len = sizeof(local.sun_family) + strlen(local.sun_path) + 1;
oldumask = umask((mode_t) 0);
if (bind(s, (struct sockaddr *)&local, len) == -1) {
perror("bind");
cyrus_done();
exit(1);
}
umask(oldumask);
chmod(local.sun_path, 0777);
if (debugmode == 0) {
pid = fork();
if (pid == -1) {
perror("fork");
cyrus_done();
exit(1);
}
if (pid != 0) {
cyrus_done();
exit(0);
}
}
FD_ZERO(&read_set);
FD_SET(s, &read_set);
nfds = s + 1;
for (;;) {
int n;
if ((fd = open(shutdownfilename, O_RDONLY, 0)) != -1) {
if (verbose || debugmode)
syslog(LOG_DEBUG, "IDLE_ALERT\n");
hash_enumerate(&itable, idle_alert, NULL);
}
timeout.tv_sec = 1;
timeout.tv_usec = 0;
rset = read_set;
n = select(nfds, &rset, NULL, NULL, &timeout);
if (n < 0 && errno == EAGAIN) continue;
if (n < 0 && errno == EINTR) continue;
if (n == -1) {
syslog(LOG_ERR, "select(): %m");
close(s);
fatal("select error",-1);
}
if (FD_ISSET(s, &rset)) {
fromlen = sizeof(from);
n = recvfrom(s, (void*) &idledata, sizeof(idle_data_t), 0,
(struct sockaddr *) &from, &fromlen);
if (n > 0) {
if (n <= IDLEDATA_BASE_SIZE ||
idledata.mboxname[n - 1 - IDLEDATA_BASE_SIZE] != '\0')
syslog(LOG_ERR, "Invalid message received, size=%d\n", n);
else
process_msg(&idledata);
}
} else {
}
}
cyrus_done();
exit(1);
}
void printstring(const char *s __attribute__((unused)))
{
fatal("printstring() executed, but its not used for POP3!",
EC_SOFTWARE);
}