#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <syslog.h>
#include <string.h>
#include <sys/wait.h>
#include <errno.h>
#include <ctype.h>
#include <dirent.h>
#include <utime.h>
#include "global.h"
#include "assert.h"
#include "mboxlist.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "mailbox.h"
#include "quota.h"
#include "xmalloc.h"
#include "acl.h"
#include "seen.h"
#include "mboxname.h"
#include "map.h"
#include "imapd.h"
#include "imparse.h"
#include "message.h"
#include "util.h"
#include "retry.h"
#include "lock.h"
#include "prot.h"
#include "sync_support.h"
#include "sync_commit.h"
enum {
MAXQUOTED = 8192,
MAXWORD = 8192,
MAXLITERAL = INT_MAX / 20
};
#define BUFGROWSIZE 100
int sync_getline(struct protstream *in, struct buf *buf)
{
int len = 0;
int c;
if (buf->alloc == 0) {
buf->alloc = BUFGROWSIZE;
buf->s = xmalloc(buf->alloc+1);
}
for (;;) {
c = prot_getc(in);
if (c == EOF || (c == '\r') || (c == '\n')) {
if (c == '\r' && ((c = prot_getc(in)) != EOF && c != '\n')) {
prot_ungetc(c, in);
c = '\r';
}
buf->s[len] = '\0';
buf->len = len;
return c;
}
if (len == buf->alloc) {
buf->alloc += BUFGROWSIZE;
buf->s = xrealloc(buf->s, buf->alloc+1);
if (len > MAXWORD) {
fatal("word too long", EC_IOERR);
}
}
buf->s[len++] = c;
}
return(c);
}
int sync_eatlines_unsolicited(struct protstream *in, int c)
{
static struct buf response;
static struct buf line;
if (c != '\n') {
sync_getline(in, &line);
syslog(LOG_ERR, "Discarding: %s", line.s);
}
do {
if ((c = getword(in, &response)) == EOF)
return(IMAP_PROTOCOL_ERROR);
sync_getline(in, &line);
syslog(LOG_ERR, "Discarding: %s", line.s);
} while (response.s[0] == '*');
if (!strcmp(response.s, "OK") ||
!strcmp(response.s, "NO") ||
!strcmp(response.s, "BAD")) {
syslog(LOG_ERR, "sync_eatlines_unsolicited(): resynchronised okay");
return(0);
}
syslog(LOG_ERR, "sync_eatlines_unsolicited(): failed to resynchronise!");
return(IMAP_PROTOCOL_ERROR);
}
void sync_printstring(struct protstream *out, const char *s)
{
const char *p;
int len = 0;
for (p = s; *p && len < 1024; p++) {
len++;
if (*p & 0x80 || *p == '\r' || *p == '\n'
|| *p == '\"' || *p == '%' || *p == '\\') break;
}
if (*p || len >= 1024) {
prot_printf(out, "{%lu+}\r\n%s", strlen(s), s);
} else {
prot_printf(out, "\"%s\"", s);
}
}
void sync_printastring(struct protstream *out, const char *s)
{
const char *p;
int len = 0;
if (!s || !*s) {
prot_printf(out, "\"\"");
return;
}
if (imparse_isatom(s)) {
prot_printf(out, "%s", s);
return;
}
for (p = s; *p && len < 1024; p++) {
len++;
if (*p & 0x80 || *p == '\r' || *p == '\n'
|| *p == '\"' || *p == '%' || *p == '\\') break;
}
if (*p || len >= 1024) {
prot_printf(out, "{%lu+}\r\n%s", strlen(s), s);
} else {
prot_printf(out, "\"%s\"", s);
}
}
void sync_flag_print(struct protstream *output, int *have_onep, char *value)
{
if (*have_onep)
prot_putc(' ', output);
prot_printf(output, "%s", value);
*have_onep = 1;
}
int sync_parse_code(char *cmd, struct protstream *in, int eat,
int *unsolicitedp)
{
static struct buf response;
static struct buf errmsg;
int c;
char *s;
if (unsolicitedp) *unsolicitedp = 0;
if ((c = getword(in, &response)) == EOF)
return(IMAP_PROTOCOL_ERROR);
if (c != ' ') goto parse_err;
if (!strcmp(response.s, "OK")) {
if (eat == SYNC_PARSE_EAT_OKLINE) eatline(in, c);
return(0);
} else if (!strcmp(response.s, "NO")) {
sync_getline(in, &errmsg);
syslog(LOG_ERR, "%s received NO response: %s", cmd, errmsg.s);
if (!strncmp(errmsg.s, "IMAP_INVALID_USER ",
strlen("IMAP_INVALID_USER ")))
return(IMAP_INVALID_USER);
else if (!strncmp(errmsg.s, "IMAP_MAILBOX_NONEXISTENT ",
strlen("IMAP_MAILBOX_NONEXISTENT ")))
return(IMAP_MAILBOX_NONEXISTENT);
else
return(IMAP_REMOTE_DENIED);
} else if (response.s[0] != '*')
goto parse_err;
if (!unsolicitedp) goto parse_err;
for (s = response.s; *s ; s++)
if (*s != '*') goto parse_err;
*unsolicitedp = s - response.s;
return(0);
parse_err:
sync_getline(in, &errmsg);
syslog(LOG_ERR, "%s received %s response: %s",
cmd, response.s, errmsg.s);
return(IMAP_PROTOCOL_ERROR);
}
void sync_flags_clear(struct sync_flags *flags)
{
memset(flags, 0, sizeof(struct sync_flags));
}
void sync_flags_meta_clear(struct sync_flags_meta *meta)
{
memset(meta, 0, sizeof(struct sync_flags_meta));
}
void sync_flags_meta_free(struct sync_flags_meta *meta)
{
int n;
for (n = 0; n < MAX_USER_FLAGS; n++) {
if (meta->flagname[n])
free(meta->flagname[n]);
}
}
static void sync_flags_meta_from_list(struct sync_flags_meta *meta,
char **flagname)
{
int n;
for (n = 0; n < MAX_USER_FLAGS; n++) {
if (flagname[n])
meta->flagname[n] = xstrdup(flagname[n]);
else
meta->flagname[n] = NULL;
}
meta->newflags = 0;
}
void sync_flags_meta_to_list(struct sync_flags_meta *meta, char **flagname)
{
int n;
for (n = 0; n < MAX_USER_FLAGS; n++) {
if (flagname[n] && meta->flagname[n] &&
!strcmp(flagname[n], meta->flagname[n]))
continue;
if (meta->flagname[n])
flagname[n] = xstrdup(meta->flagname[n]);
else
flagname[n] = NULL;
}
meta->newflags = 0;
}
int sync_getflags(struct protstream *input,
struct sync_flags *flags, struct sync_flags_meta *meta)
{
static struct buf flagtoken;
int inlist = 0;
int flag = -1;
int empty = -1;
int c, i;
char *s;
sync_flags_clear(flags);
for (;;) {
if ((c = getword(input, &flagtoken)) == EOF)
return(EOF);
s = flagtoken.s;
if (c == '(' && !s[0] && !inlist) {
inlist = 1;
continue;
}
if (!s[0]) break;
if (s[0] == '\\') {
lcase(s);
if (!strcmp(s, "\\seen")) {
} else if (!strcasecmp(s, "\\answered")) {
flags->system_flags |= FLAG_ANSWERED;
} else if (!strcasecmp(s, "\\flagged")) {
flags->system_flags |= FLAG_FLAGGED;
} else if (!strcasecmp(s, "\\deleted")) {
flags->system_flags |= FLAG_DELETED;
} else if (!strcasecmp(s, "\\draft")) {
flags->system_flags |= FLAG_DRAFT;
} else {
syslog(LOG_ERR, "Unknown system flag: %s", s);
}
} else if (imparse_isatom(s)) {
flag = empty = (-1);
for (i = 0 ; i < MAX_USER_FLAGS ; i++) {
if (meta->flagname[i] && !strcmp(meta->flagname[i], s)) {
flag = i;
break;
}
if ((empty < 0) && (meta->flagname[i] == NULL))
empty = i;
}
if ((flag < 0) && (empty >= 0)) {
flag = empty;
meta->flagname[flag] = xstrdup(s);
meta->newflags = 1;
}
if (flag >= 0) {
flags->user_flags[flag/32] |= 1<<(flag&31);
} else {
syslog(LOG_ERR, "Unable to record user flag: %s", s);
}
} else
return('-');
if (c != ' ') break;
}
if (!inlist || (c != ')'))
return('-');
return(prot_getc(input));
}
struct sync_msg_list *sync_msg_list_create(char **flagname,
unsigned long last_uid)
{
struct sync_msg_list *l = xzmalloc(sizeof (struct sync_msg_list));
l->head = NULL;
l->tail = NULL;
l->count = 0;
l->last_uid = last_uid;
sync_flags_meta_clear(&l->meta);
if (flagname)
sync_flags_meta_from_list(&l->meta, flagname);
return(l);
}
struct sync_msg *sync_msg_list_add(struct sync_msg_list *l)
{
struct sync_msg *result = xzmalloc(sizeof(struct sync_msg));
if (l->tail)
l->tail = l->tail->next = result;
else
l->head = l->tail = result;
l->count++;
return(result);
}
void sync_msg_list_free(struct sync_msg_list **lp)
{
struct sync_msg_list *l = *lp;
struct sync_msg *current, *next;
current = l->head;
while (current) {
next = current->next;
free(current);
current = next;
}
sync_flags_meta_free(&l->meta);
free(l);
*lp = NULL;
}
struct sync_msgid_list *sync_msgid_list_create(int hash_size)
{
struct sync_msgid_list *l = xzmalloc(sizeof (struct sync_msgid_list));
if (hash_size == 0)
hash_size = 256;
l->head = NULL;
l->tail = NULL;
l->hash_size = hash_size;
l->hash = xzmalloc(hash_size * sizeof(struct sync_msgid *));
l->count = 0;
l->reserved = 0;
return(l);
}
struct sync_msgid *sync_msgid_add(struct sync_msgid_list *l,
struct message_uuid *uuid)
{
struct sync_msgid *result;
int offset;
if (message_uuid_isnull(uuid))
return(NULL);
result = xzmalloc(sizeof(struct sync_msgid));
offset = message_uuid_hash(uuid, l->hash_size);
message_uuid_copy(&result->uuid, uuid);
l->count++;
if (l->tail)
l->tail = l->tail->next = result;
else
l->head = l->tail = result;
result->hash_next = l->hash[offset];
l->hash[offset] = result;
return(result);
}
void sync_msgid_list_free(struct sync_msgid_list **lp)
{
struct sync_msgid_list *l = *lp;
struct sync_msgid *current, *next;
current = l->head;
while (current) {
next = current->next;
free(current);
current = next;
}
free(l->hash);
free(l);
*lp = NULL;
}
struct sync_msgid *sync_msgid_lookup(struct sync_msgid_list *l,
struct message_uuid *uuid)
{
int offset = message_uuid_hash(uuid, l->hash_size);
struct sync_msgid *msgid;
if (message_uuid_isnull(uuid))
return(NULL);
for (msgid = l->hash[offset] ; msgid ; msgid = msgid->hash_next) {
if (message_uuid_compare(&msgid->uuid, uuid))
return(msgid);
}
return(NULL);
}
struct sync_folder_list *sync_folder_list_create(void)
{
struct sync_folder_list *l = xzmalloc(sizeof (struct sync_folder_list));
l->head = NULL;
l->tail = NULL;
l->count = 0;
return(l);
}
struct sync_folder *sync_folder_list_add(struct sync_folder_list *l,
char *id, char *name, char *acl,
unsigned long options,
struct quota *quota)
{
struct sync_folder *result = xzmalloc(sizeof(struct sync_folder));
if (l->tail)
l->tail = l->tail->next = result;
else
l->head = l->tail = result;
l->count++;
result->next = NULL;
result->msglist = NULL;
result->id = (id) ? xstrdup(id) : NULL;
result->name = (name) ? xstrdup(name) : NULL;
result->acl = (acl) ? xstrdup(acl) : NULL;
result->options = options;
if (quota) {
result->quota.root = result->name;
result->quota.limit = quota->limit;
}
result->mark = 0;
result->reserve = 0;
return(result);
}
struct sync_folder *sync_folder_lookup(struct sync_folder_list *l, char *id)
{
struct sync_folder *p;
for (p = l->head ; p ; p = p->next) {
if (!strcmp(p->id, id))
return(p);
}
return(NULL);
}
struct sync_folder *sync_folder_lookup_byname(struct sync_folder_list *l,
char *name)
{
struct sync_folder *p;
for (p = l->head ; p ; p = p->next) {
if (!strcmp(p->name, name))
return(p);
}
return(NULL);
}
int sync_folder_mark(struct sync_folder_list *l, char *id)
{
struct sync_folder *p;
for (p = l->head ; p ; p = p->next) {
if (!strcmp(p->id, id)) {
p->mark = 1;
return(1);
}
}
return(0);
}
void sync_folder_list_free(struct sync_folder_list **lp)
{
struct sync_folder_list *l = *lp;
struct sync_folder *current, *next;
if (!l) return;
current = l->head;
while (current) {
next = current->next;
if (current->id) free(current->id);
if (current->name) free(current->name);
if (current->acl) free(current->acl);
if (current->msglist) sync_msg_list_free(¤t->msglist);
free(current);
current = next;
}
free(l);
*lp = NULL;
}
struct sync_rename_list *sync_rename_list_create(void)
{
struct sync_rename_list *l = xzmalloc(sizeof (struct sync_rename_list));
l->head = NULL;
l->tail = NULL;
l->count = 0;
l->done = 0;
return(l);
}
struct sync_rename_item *sync_rename_list_add(struct sync_rename_list *l,
char *id, char *oldname,
char *newname)
{
struct sync_rename_item *result
= xzmalloc(sizeof(struct sync_rename_item));
if (l->tail)
l->tail = l->tail->next = result;
else
l->head = l->tail = result;
l->count++;
result->next = NULL;
result->id = xstrdup(id);
result->oldname = xstrdup(oldname);
result->newname = xstrdup(newname);
result->done = 0;
return(result);
}
struct sync_rename_item *sync_rename_lookup(struct sync_rename_list *l,
char *oldname)
{
struct sync_rename_item *p;
for (p = l->head ; p ; p = p->next) {
if (!strcmp(p->oldname, oldname))
return(p);
}
return(NULL);
}
void sync_rename_list_free(struct sync_rename_list **lp)
{
struct sync_rename_list *l = *lp;
struct sync_rename_item *current, *next;
if (!l) return;
current = l->head;
while (current) {
next = current->next;
free(current->id);
free(current->oldname);
free(current->newname);
free(current);
current = next;
}
free(l);
*lp = NULL;
}
struct sync_user_list *sync_user_list_create(void)
{
struct sync_user_list *l = xzmalloc(sizeof (struct sync_user_list));
l->head = NULL;
l->tail = NULL;
l->count = 0;
return(l);
}
struct sync_user *sync_user_list_add(struct sync_user_list *l, char *userid)
{
struct sync_user *result = xzmalloc(sizeof(struct sync_user));
if (l->tail)
l->tail = l->tail->next = result;
else
l->head = l->tail = result;
l->count++;
result->next = NULL;
result->userid = xstrdup(userid);
result->folder_list = sync_folder_list_create();
return(result);
}
struct sync_user *sync_user_list_lookup(struct sync_user_list *l, char *userid)
{
struct sync_user *p;
for (p = l->head ; p ; p = p->next) {
if (!strcmp(p->userid, userid))
return(p);
}
return(NULL);
}
void sync_user_list_free(struct sync_user_list **lp)
{
struct sync_user_list *l = *lp;
struct sync_user *current, *next;
if (!l) return;
current = l->head;
while (current) {
next = current->next;
free(current->userid);
sync_folder_list_free(¤t->folder_list);
free(current);
current = next;
}
free(l);
*lp = NULL;
}
struct sync_message_list *sync_message_list_create(int hash_size, int file_max)
{
struct sync_message_list *l = xzmalloc(sizeof (struct sync_message_list));
const char *root;
if (hash_size == 0)
hash_size = 256;
l->head = NULL;
l->tail = NULL;
l->hash = xzmalloc(hash_size * sizeof(struct sync_msgid *));
l->hash_size = hash_size;
l->count = 0;
l->file = xzmalloc(file_max * sizeof(FILE *));
l->file_count = 0;
l->file_max = file_max;
root = config_partitiondir(config_defpartition);
snprintf(l->cache_name, sizeof(l->cache_name), "%s/sync./%lu.cache",
root, (unsigned long) getpid());
l->cache_fd = open(l->cache_name, O_RDWR|O_CREAT|O_TRUNC, 0666);
if (l->cache_fd < 0 && errno == ENOENT) {
if (!cyrus_mkdir(l->cache_name, 0755)) {
l->cache_fd = open(l->cache_name, O_RDWR|O_CREAT|O_TRUNC, 0666);
}
}
if (l->cache_fd < 0) {
syslog(LOG_ERR, "Failed to open %s: %m", l->cache_name);
return(NULL);
}
l->cache_base = 0;
l->cache_len = 0;
l->cache_buffer_size = 0;
l->cache_buffer_alloc = SYNC_MESSAGE_INIT_CACHE;
l->cache_buffer = xmalloc(l->cache_buffer_alloc);
return(l);
}
int sync_message_list_newstage(struct sync_message_list *l, char *mboxname)
{
int r;
const char *root;
char *partition;
r = mboxlist_detail(mboxname, NULL, NULL, NULL, &partition, NULL, NULL);
if (!r) {
root = config_partitiondir(partition);
if (!root) r = IMAP_PARTITION_UNKNOWN;
}
if (r) {
syslog(LOG_ERR, "couldn't find sync stage directory for mbox: '%s': %s",
mboxname, error_message(r));
return r;
}
snprintf(l->stage_dir, sizeof(l->stage_dir), "%s/sync./%lu",
root, (unsigned long) getpid());
if (cyrus_mkdir(l->stage_dir, 0755) == -1) return IMAP_IOERROR;
if (mkdir(l->stage_dir, 0755) == -1 && errno != EEXIST) {
syslog(LOG_ERR, "Failed to create %s:%m", l->stage_dir);
return IMAP_IOERROR;
}
return 0;
}
void sync_message_list_cache(struct sync_message_list *l, char *entry, int size)
{
if ((l->cache_buffer_size + size) > l->cache_buffer_alloc) {
if (size > l->cache_buffer_alloc)
l->cache_buffer_alloc = 2 * size;
else
l->cache_buffer_alloc *= 2;
l->cache_buffer = xrealloc(l->cache_buffer, l->cache_buffer_alloc);
}
memcpy(l->cache_buffer+l->cache_buffer_size, entry, size);
l->cache_buffer_size += size;
}
int sync_message_list_cache_flush(struct sync_message_list *l)
{
int n;
if (l->cache_buffer_size == 0)
return(0);
n = retry_write(l->cache_fd, l->cache_buffer, l->cache_buffer_size);
if (n < l->cache_buffer_size) {
syslog(LOG_ERR,
"sync_message_flush_cache(): failed to write %lu bytes: %m",
l->cache_buffer_size);
return(IMAP_IOERROR);
}
l->cache_buffer_size = 0;
return(0);
}
unsigned long sync_message_list_cache_offset(struct sync_message_list *l)
{
return(lseek(l->cache_fd, 0, SEEK_CUR) + l->cache_buffer_size);
}
char *sync_message_next_path(struct sync_message_list *l)
{
static char result[MAX_MAILBOX_PATH+1];
snprintf(result, sizeof(result), "%s/%lu.", l->stage_dir, l->count);
return(result);
}
struct sync_message *sync_message_add(struct sync_message_list *l,
struct message_uuid *uuid)
{
struct sync_message *result;
int offset;
result = xzmalloc(sizeof(struct sync_message));
message_uuid_set_null(&result->uuid);
result->msg_path = xzmalloc(5 * (MAX_MAILBOX_PATH+1) * sizeof(char));
result->msg_path_end = result->msg_path +
5 * (MAX_MAILBOX_PATH+1) * sizeof(char);
snprintf(result->stagename, sizeof(result->stagename), "%lu.", l->count);
snprintf(result->msg_path, MAX_MAILBOX_PATH,
"%s/%s", l->stage_dir, result->stagename);
result->msg_path[strlen(result->msg_path) + 1] = '\0';
l->count++;
if (l->tail)
l->tail = l->tail->next = result;
else
l->head = l->tail = result;
if (uuid && !message_uuid_isnull(uuid)) {
message_uuid_copy(&result->uuid, uuid);
offset = message_uuid_hash(uuid, l->hash_size);
result->hash_next = l->hash[offset];
l->hash[offset] = result;
}
return(result);
}
void sync_message_fsync(struct sync_message_list *l)
{
int i;
if (l->file_count == 0)
return;
for (i = (l->file_count-1) ; i >= 0 ; i--) {
fsync(fileno(l->file[i]));
fclose(l->file[i]);
l->file[i] = NULL;
}
l->file_count = 0;
}
FILE *sync_message_open(struct sync_message_list *l,
struct sync_message *message)
{
FILE *file;
if (l->file_count == l->file_max)
sync_message_fsync(l);
if ((file=fopen(message->msg_path, "w+")) == NULL) {
syslog(LOG_ERR, "sync_message_open(): Unable to open %s: %m",
message->msg_path);
return(NULL);
}
l->file[l->file_count++] = file;
return(file);
}
int sync_message_copy_fromstage(struct sync_message *message,
struct mailbox *mailbox,
unsigned long uid)
{
int r;
const char *root;
char *partition, stagefile[MAX_MAILBOX_PATH+1], *p;
size_t sflen;
char target[MAX_MAILBOX_PATH+1];
r = mboxlist_detail(mailbox->name, NULL, NULL, NULL, &partition, NULL, NULL);
if (!r) {
root = config_partitiondir(partition);
if (!root) r = IMAP_PARTITION_UNKNOWN;
}
if (r) {
syslog(LOG_ERR, "couldn't find sync stage directory for mbox: '%s': %s",
mailbox->name, error_message(r));
return r;
}
snprintf(stagefile, sizeof(stagefile), "%s/sync./%lu/%s",
root, (unsigned long) getpid(), message->stagename);
sflen = strlen(stagefile);
p = message->msg_path;
while (p < message->msg_path_end) {
int sl = strlen(p);
if (sl == 0) {
break;
}
if (!strcmp(stagefile, p)) {
break;
}
p += sl + 1;
}
if (*p == '\0') {
r = mailbox_copyfile(message->msg_path, stagefile, 0);
if (r) {
if (cyrus_mkdir(stagefile, 0755) == -1) {
syslog(LOG_ERR, "couldn't create sync stage directory for : %s: %m",
stagefile);
} else {
syslog(LOG_NOTICE, "created sync stage directory for %s",
stagefile);
r = mailbox_copyfile(message->msg_path, stagefile, 0);
}
}
if (r) {
syslog(LOG_ERR, "IOERROR: creating message file %s: %m",
stagefile);
unlink(stagefile);
return r;
}
if (p + sflen > message->msg_path_end - 5) {
int cursize = message->msg_path_end - message->msg_path;
int curp = p - message->msg_path;
message->msg_path = xrealloc(message->msg_path, 2 * cursize);
message->msg_path_end = message->msg_path + 2 * cursize;
p = message->msg_path + curp;
}
strcpy(p, stagefile);
p[sflen + 1] = '\0';
}
snprintf(target, MAX_MAILBOX_PATH, "%s/%lu.", mailbox->path, uid);
return mailbox_copyfile(p, target, 0);
}
void sync_message_list_free(struct sync_message_list **lp)
{
struct sync_message_list *l = *lp;
struct sync_message *current, *next;
for (current = l->head; current ; current = next) {
next = current->next;
if (current->msg_path) {
char *p = current->msg_path;
while (*p != '\0' && p < current->msg_path_end) {
if (unlink(p) != 0) {
syslog(LOG_ERR, "IOERROR, error unlinking file %s: %m", p);
}
p += strlen(p) + 1;
}
free(current->msg_path);
}
free(current);
}
if (l->cache_base && (l->cache_len > 0))
map_free(&l->cache_base, &l->cache_len);
if (l->cache_fd) {
close(l->cache_fd);
unlink(l->cache_name);
}
rmdir(l->stage_dir);
free(l->cache_buffer);
free(l->hash);
free(l->file);
free(l);
*lp = NULL;
}
struct sync_message *sync_message_find(struct sync_message_list *l,
struct message_uuid *uuid)
{
struct sync_message *current;
int offset = message_uuid_hash(uuid, l->hash_size);
if (message_uuid_isnull(uuid))
return(NULL);
for (current = l->hash[offset] ; current ; current = current->hash_next) {
if (message_uuid_compare(¤t->uuid, uuid))
return(current);
}
return(NULL);
}
int sync_message_list_need_restart(struct sync_message_list *l)
{
return((l->count > 1000) ||
lseek(l->cache_fd, 0, SEEK_CUR) >= SYNC_MESSAGE_LIST_MAX_CACHE);
}
static int sync_getliteral_size(struct protstream *input,
struct protstream *output,
unsigned long *sizep)
{
static struct buf arg;
unsigned long size = 0;
int sawdigit = 0;
int isnowait = 0;
int c = getword(input, &arg);
char *p = arg.s;
if (c == EOF) return(IMAP_IOERROR);
if ((p == NULL) || (*p != '{'))
return(IMAP_PROTOCOL_ERROR);
for (p = p + 1; *p && isdigit((int) *p); p++) {
sawdigit++;
size = (size*10) + *p - '0';
}
if (*p == '+') {
isnowait++;
p++;
}
if (c == '\r') c = prot_getc(input);
if (*p != '}' || p[1] || c != '\n' || !sawdigit)
return(IMAP_PROTOCOL_ERROR);
if (!isnowait) {
prot_printf(output, "+ go ahead\r\n");
prot_flush(output);
}
*sizep = size;
return(0);
}
int sync_getcache(struct protstream *input, struct protstream *output,
struct sync_message_list *list, struct sync_message *message)
{
static char *cache_entry = NULL;
static unsigned long max_cache_size = 0;
unsigned long cache_size, size;
int c, r = 0;
static struct buf version;
char *p;
int n;
if ((c = getastring(input, output, &version)) != ' ')
return(IMAP_IOERROR);
message->cache_version = sync_atoul(version.s);
if ((r = sync_getliteral_size(input, output, &cache_size)))
return(r);
if (cache_size > max_cache_size) {
cache_entry = xrealloc(cache_entry, cache_size);
max_cache_size = cache_size;
}
p = cache_entry;
size = cache_size;
while (size) {
n = prot_read(input, p, size);
if (!n) {
syslog(LOG_ERR,
"IOERROR: reading cache entry: unexpected end of file");
return(IMAP_IOERROR);
}
p += n;
size -=n;
}
message->cache_offset = sync_message_list_cache_offset(list);
message->cache_size = cache_size;
sync_message_list_cache(list, cache_entry, cache_size);
return(0);
}
int sync_getmessage(struct protstream *input, struct protstream *output,
struct sync_message_list *list,
struct sync_message *message)
{
FILE *file;
int r = 0;
unsigned long size;
char buf[8192+1];
int n;
if ((r = sync_getliteral_size(input, output, &message->msg_size)))
return(r);
if ((file=sync_message_open(list, message)) == NULL)
return(IMAP_IOERROR);
size = message->msg_size;
while (size) {
n = prot_read(input, buf, size > 8192 ? 8192 : size);
if (!n) {
syslog(LOG_ERR, "IOERROR: reading message: unexpected end of file");
r = IMAP_IOERROR;
break;
}
size -= n;
fwrite(buf, 1, n, file);
}
return(r);
}
int sync_getsimple(struct protstream *input, struct protstream *output,
struct sync_message_list *list,
struct sync_message *message)
{
FILE *file;
int r = 0;
unsigned long size;
const char *msg_base = 0;
unsigned long msg_len = 0;
struct index_record record;
char buf[8192+1];
int n;
if (list->cache_buffer_size > 0)
sync_message_list_cache_flush(list);
if ((r = sync_getliteral_size(input, output, &message->msg_size)))
return(r);
if ((file=fopen(message->msg_path, "w+")) == NULL) {
syslog(LOG_ERR, "sync_getsimple(): Unable to open %s: %m",
message->msg_path);
r = IMAP_IOERROR;
}
size = message->msg_size;
while (size) {
n = prot_read(input, buf, size > 8192 ? 8192 : size);
if (!n) {
syslog(LOG_ERR,
"IOERROR: reading message: unexpected end of file");
r = IMAP_IOERROR;
break;
}
size -= n;
fwrite(buf, 1, n, file);
}
if (r) {
fclose(file);
return(IMAP_IOERROR);
}
fflush(file);
if (ferror(file)) {
fclose(file);
return(IMAP_IOERROR);
}
if (fsync(fileno(file)) < 0) {
fclose(file);
return(IMAP_IOERROR);
}
map_refresh(fileno(file), 1, &msg_base, &msg_len, message->msg_size,
"new message", "unknown");
r = message_parse_mapped_async(msg_base, msg_len,
MAILBOX_FORMAT_NORMAL,
list->cache_fd, &record);
map_free(&msg_base, &msg_len);
message->hdr_size = record.header_size;
message->cache_offset = record.cache_offset;
message->cache_size
= lseek(list->cache_fd, 0, SEEK_CUR) - record.cache_offset;
fclose(file);
return(r);
}
struct sync_upload_list *sync_upload_list_create(unsigned long new_last_uid,
char **flagname)
{
struct sync_upload_list *l = xzmalloc(sizeof (struct sync_upload_list));
l->head = NULL;
l->tail = NULL;
l->count = 0;
l->new_last_uid = new_last_uid;
sync_flags_meta_clear(&l->meta);
sync_flags_meta_from_list(&l->meta, flagname);
return(l);
}
struct sync_upload_item *sync_upload_list_add(struct sync_upload_list *l)
{
struct sync_upload_item *result
= xzmalloc(sizeof(struct sync_upload_item));
if (l->tail)
l->tail = l->tail->next = result;
else
l->head = l->tail = result;
l->count++;
return(result);
}
void sync_upload_list_free(struct sync_upload_list **lp)
{
struct sync_upload_list *l = *lp;
struct sync_upload_item *current, *next;
current = l->head;
while (current) {
next = current->next;
free(current);
current = next;
}
sync_flags_meta_free(&l->meta);
free(l);
*lp = NULL;
}
struct sync_flag_list *sync_flag_list_create(char **flagname)
{
struct sync_flag_list *l = xzmalloc(sizeof (struct sync_flag_list));
sync_flags_meta_clear(&l->meta);
sync_flags_meta_from_list(&l->meta, flagname);
l->head = NULL;
l->tail = NULL;
l->count = 0;
return(l);
}
struct sync_flag_item *sync_flag_list_add(struct sync_flag_list *l)
{
struct sync_flag_item *result = xzmalloc(sizeof(struct sync_flag_item));
if (l->tail)
l->tail = l->tail->next = result;
else
l->head = l->tail = result;
l->count++;
return(result);
}
void sync_flag_list_free(struct sync_flag_list **lp)
{
struct sync_flag_list *l = *lp;
struct sync_flag_item *current, *next;
current = l->head;
while (current) {
next = current->next;
free(current);
current = next;
}
sync_flags_meta_free(&l->meta);
free(l);
*lp = NULL;
}
char *sync_sieve_get_path(char *userid, char *sieve_path, size_t psize)
{
char *domain;
if (config_getenum(IMAPOPT_VIRTDOMAINS) && (domain = strchr(userid, '@'))) {
char d = (char) dir_hash_c(domain+1);
*domain = '\0';
snprintf(sieve_path, psize, "%s%s%c/%s/%c/%s",
config_getstring(IMAPOPT_SIEVEDIR),
FNAME_DOMAINDIR, d, domain+1, dir_hash_c(userid), userid);
*domain = '@';
}
else {
snprintf(sieve_path, psize, "%s/%c/%s",
config_getstring(IMAPOPT_SIEVEDIR), dir_hash_c(userid), userid);
}
return sieve_path;
}
struct sync_sieve_list *sync_sieve_list_create()
{
struct sync_sieve_list *l = xzmalloc(sizeof (struct sync_sieve_list));
l->head = NULL;
l->tail = NULL;
l->count = 0;
return(l);
}
void sync_sieve_list_add(struct sync_sieve_list *l,
char *name, time_t last_update, int active)
{
struct sync_sieve_item *item = xzmalloc(sizeof(struct sync_sieve_item));
item->name = xstrdup(name);
item->last_update = last_update;
item->active = active;
item->mark = 0;
if (l->tail)
l->tail = l->tail->next = item;
else
l->head = l->tail = item;
l->count++;
}
struct sync_sieve_item *sync_sieve_lookup(struct sync_sieve_list *l, char *name)
{
struct sync_sieve_item *p;
for (p = l->head ; p ; p = p->next) {
if (!strcmp(p->name, name))
return(p);
}
return(NULL);
}
void sync_sieve_list_set_active(struct sync_sieve_list *l, char *name)
{
struct sync_sieve_item *item;
for (item = l->head ; item ; item = item->next) {
if (!strcmp(item->name, name)) {
item->active = 1;
break;
}
}
}
void sync_sieve_list_free(struct sync_sieve_list **lp)
{
struct sync_sieve_list *l = *lp;
struct sync_sieve_item *current, *next;
current = l->head;
while (current) {
next = current->next;
if (current->name)
free(current->name);
free(current);
current = next;
}
free(l);
*lp = NULL;
}
struct sync_sieve_list *sync_sieve_list_generate(char *userid)
{
struct sync_sieve_list *list = sync_sieve_list_create();
char sieve_path[2048];
char filename[2048];
char active[2048];
DIR *mbdir;
struct dirent *next = NULL;
struct stat sbuf;
int count;
list = sync_sieve_list_create();
sync_sieve_get_path(userid, sieve_path, sizeof(sieve_path));
if (!(mbdir = opendir(sieve_path)))
return(list);
active[0] = '\0';
while((next = readdir(mbdir)) != NULL) {
if(!strcmp(next->d_name, ".") || !strcmp(next->d_name, ".."))
continue;
snprintf(filename, sizeof(filename), "%s/%s",
sieve_path, next->d_name);
if (stat(filename, &sbuf) < 0)
continue;
if (!strcmp(next->d_name, "defaultbc")) {
if (sbuf.st_mode & S_IFLNK) {
count = readlink(filename, active, 2047);
if (count >= 0) {
active[count] = '\0';
} else {
}
}
continue;
}
sync_sieve_list_add(list, next->d_name, sbuf.st_mtime, 0);
}
closedir(mbdir);
if (active[0])
sync_sieve_list_set_active(list, active);
return(list);
}
char *sync_sieve_read(char *userid, char *name, unsigned long *sizep)
{
char sieve_path[2048];
char filename[2048];
FILE *file;
struct stat sbuf;
char *result, *s;
unsigned long count;
int c;
if (sizep)
*sizep = 0;
sync_sieve_get_path(userid, sieve_path, sizeof(sieve_path));
snprintf(filename, sizeof(filename), "%s/%s", sieve_path, name);
file=fopen(filename, "r");
if ((file == NULL) || (fstat(fileno(file), &sbuf) < 0))
return(NULL);
count = sbuf.st_size;
s = result = xmalloc(count+1);
if (sizep)
*sizep = count;
while (count > 0) {
if ((c=fgetc(file)) == EOF)
break;
*s++ = c;
count--;
}
fclose(file);
*s = '\0';
return(result);
}
int sync_sieve_upload(struct protstream *input, struct protstream *output,
char *userid, char *name, unsigned long last_update)
{
char sieve_path[2048];
char tmpname[2048];
char newname[2048];
FILE *file;
int r = 0;
unsigned long size;
struct stat sbuf;
struct utimbuf utimbuf;
char buf[8192+1];
int n;
sync_sieve_get_path(userid, sieve_path, sizeof(sieve_path));
if (stat(sieve_path, &sbuf) == -1 && errno == ENOENT) {
if (cyrus_mkdir(sieve_path, 0755) == -1) return IMAP_IOERROR;
if (mkdir(sieve_path, 0755) == -1 && errno != EEXIST) {
syslog(LOG_ERR, "Failed to create %s:%m", sieve_path);
return IMAP_IOERROR;
}
}
snprintf(tmpname, sizeof(tmpname), "%s/sync_tmp-%lu",
sieve_path, (unsigned long)getpid());
snprintf(newname, sizeof(newname), "%s/%s", sieve_path, name);
if ((r = sync_getliteral_size(input, output, &size)))
return(r);
if ((file=fopen(tmpname, "w")) == NULL) {
return(IMAP_IOERROR);
}
while (size) {
n = prot_read(input, buf, size > 8192 ? 8192 : size);
if (!n) {
syslog(LOG_ERR, "IOERROR: reading message: unexpected end of file");
r = IMAP_IOERROR;
break;
}
size -= n;
fwrite(buf, 1, n, file);
}
if ((fflush(file) != 0) || (fsync(fileno(file)) < 0))
r = IMAP_IOERROR;
fclose(file);
utimbuf.actime = time(NULL);
utimbuf.modtime = last_update;
if (!r && (utime(tmpname, &utimbuf) < 0))
r = IMAP_IOERROR;
if (!r && (rename(tmpname, newname) < 0))
r = IMAP_IOERROR;
return(r);
}
int sync_sieve_activate(char *userid, char *name)
{
char sieve_path[2048];
char target[2048];
char active[2048];
sync_sieve_get_path(userid, sieve_path, sizeof(sieve_path));
snprintf(target, sizeof(target), "%s", name);
snprintf(active, sizeof(active), "%s/%s", sieve_path, "defaultbc");
unlink(active);
if (symlink(target, active) < 0)
return(IMAP_IOERROR);
return(0);
}
int sync_sieve_deactivate(char *userid)
{
char sieve_path[2048];
char active[2048];
sync_sieve_get_path(userid, sieve_path, sizeof(sieve_path));
snprintf(active, sizeof(active), "%s/%s", sieve_path, "defaultbc");
unlink(active);
return(0);
}
int sync_sieve_delete(char *userid, char *name)
{
char sieve_path[2048];
char filename[2048];
char active[2048];
DIR *mbdir;
struct dirent *next = NULL;
struct stat sbuf;
int is_default = 0;
int count;
sync_sieve_get_path(userid, sieve_path, sizeof(sieve_path));
if (!(mbdir = opendir(sieve_path)))
return(IMAP_IOERROR);
while((next = readdir(mbdir)) != NULL) {
if(!strcmp(next->d_name, ".") || !strcmp(next->d_name, ".."))
continue;
snprintf(filename, sizeof(filename), "%s/%s",
sieve_path, next->d_name);
if (stat(filename, &sbuf) < 0)
continue;
if (!strcmp(next->d_name, "defaultbc")) {
if (sbuf.st_mode & S_IFLNK) {
count = readlink(filename, active, 2047);
if (count >= 0) {
active[count] = '\0';
if (!strcmp(active, name))
is_default = 1;
}
}
continue;
}
}
closedir(mbdir);
if (is_default) {
snprintf(filename, sizeof(filename), "%s/defaultbc", sieve_path);
unlink(filename);
}
snprintf(filename, sizeof(filename), "%s/%s", sieve_path, name);
unlink(filename);
return(0);
}
struct sync_annot_list *sync_annot_list_create()
{
struct sync_annot_list *l = xzmalloc(sizeof (struct sync_annot_list));
l->head = NULL;
l->tail = NULL;
l->count = 0;
return(l);
}
void sync_annot_list_add(struct sync_annot_list *l,
const char *entry, const char *userid,
const char *value)
{
struct sync_annot_item *item = xzmalloc(sizeof(struct sync_annot_item));
item->entry = xstrdup(entry);
item->userid = xstrdup(userid);
item->value = xstrdup(value);
item->mark = 0;
if (l->tail)
l->tail = l->tail->next = item;
else
l->head = l->tail = item;
l->count++;
}
void sync_annot_list_free(struct sync_annot_list **lp)
{
struct sync_annot_list *l = *lp;
struct sync_annot_item *current, *next;
current = l->head;
while (current) {
next = current->next;
if (current->entry) free(current->entry);
if (current->userid) free(current->userid);
if (current->value) free(current->value);
free(current);
current = next;
}
free(l);
*lp = NULL;
}
struct sync_action_list *sync_action_list_create(void)
{
struct sync_action_list *l = xzmalloc(sizeof (struct sync_action_list));
l->head = NULL;
l->tail = NULL;
l->count = 0;
return(l);
}
void sync_action_list_add(struct sync_action_list *l, char *name, char *user)
{
struct sync_action *current;
if (!name && !user) return;
for (current = l->head ; current ; current = current->next) {
if ((!name || (current->name && !strcmp(current->name, name))) &&
(!user || (current->user && !strcmp(current->user, user)))) {
current->active = 1;
return;
} else {
}
}
current = xzmalloc(sizeof(struct sync_action));
current->next = NULL;
current->name = (name) ? xstrdup(name) : NULL;
current->user = (user) ? xstrdup(user) : NULL;
current->active = 1;
if (l->tail)
l->tail = l->tail->next = current;
else
l->head = l->tail = current;
l->count++;
}
void sync_action_list_free(struct sync_action_list **lp)
{
struct sync_action_list *l = *lp;
struct sync_action *current, *next;
current = l->head;
while (current) {
next = current->next;
if (current->name) free(current->name);
if (current->user) free(current->user);
free(current);
current = next;
}
free(l);
*lp = NULL;
}
void sync_lock_reset(struct sync_lock *lock)
{
lock->fd = -1;
lock->count = 0;
}
int sync_unlock(struct sync_lock *lock)
{
assert(lock->fd >= 0);
assert(lock->count != 0);
if (--lock->count == 0) {
lock_unlock(lock->fd);
close(lock->fd);
lock->fd = -1;
}
return(0);
}
int sync_lock(struct sync_lock *lock)
{
static char lockfile[MAX_MAILBOX_PATH] = "";
int r = 0;
if (lock->count++) return 0;
if (!*lockfile) {
strlcpy(lockfile, config_dir, sizeof(lockfile));
strlcat(lockfile, "/sync/lock", sizeof(lockfile));
}
lock->fd = open(lockfile, O_WRONLY|O_CREAT, 0640);
if (lock->fd < 0 && errno == ENOENT) {
if (!cyrus_mkdir(lockfile, 0755)) {
lock->fd = open(lockfile, O_WRONLY|O_CREAT, 0640);
}
}
if (lock->fd < 0) {
syslog(LOG_ERR, "Unable to create file %s: %s",
lockfile, strerror(errno));
return(IMAP_IOERROR);
}
r = lock_blocking(lock->fd);
if (r) {
lock->count--;
syslog(LOG_ERR, "Unable to lock %s: %s", lockfile, strerror(errno));
}
return(r);
}