#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <sys/types.h>
#include <syslog.h>
#include <sys/stat.h>
#include "acl.h"
#include "assert.h"
#include "imap_err.h"
#include "mailbox.h"
#include "message.h"
#include "append.h"
#include "global.h"
#include "prot.h"
#include "xmalloc.h"
#include "mboxlist.h"
#include "seen.h"
#include "retry.h"
#include "quota.h"
struct stagemsg {
char fname[1024];
char *parts;
char *partend;
};
static int buf_len = 0;
static char *tmp_buf = NULL;
static int append_addseen(struct mailbox *mailbox, const char *userid,
const char *msgrange);
static void addme(char **msgrange, int *alloced, long uid);
#define zero_index(i) { memset(&i, 0, sizeof(struct index_record)); }
int append_check(const char *name, int format,
struct auth_state *auth_state,
long aclcheck, long quotacheck)
{
struct mailbox m;
int r;
int mbflags;
r = mboxlist_detail(name, &mbflags, NULL, NULL, NULL, NULL);
if (!r) {
if(mbflags & MBTYPE_MOVING) return IMAP_MAILBOX_MOVED;
} else {
return r;
}
r = mailbox_open_header(name, auth_state, &m);
if (r) return r;
if ((m.myrights & aclcheck) != aclcheck) {
r = (m.myrights & ACL_LOOKUP) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
mailbox_close(&m);
return r;
}
r = mailbox_open_index(&m);
if (r) {
mailbox_close(&m);
return r;
}
if (m.format != format) {
mailbox_close(&m);
return IMAP_MAILBOX_NOTSUPPORTED;
}
r = quota_read(&m.quota, NULL, 0);
if (!r) {
if (m.quota.limit >= 0 && quotacheck >= 0 &&
m.quota.used + quotacheck >
((unsigned) m.quota.limit * QUOTA_UNITS)) {
r = IMAP_QUOTA_EXCEEDED;
}
}
else if (r == IMAP_QUOTAROOT_NONEXISTENT) r = 0;
mailbox_close(&m);
return r;
}
int append_setup(struct appendstate *as, const char *name,
int format,
const char *userid, struct auth_state *auth_state,
long aclcheck, long quotacheck)
{
int r;
r = mailbox_open_header(name, auth_state, &as->m);
if (r) return r;
if ((as->m.myrights & aclcheck) != aclcheck) {
r = (as->m.myrights & ACL_LOOKUP) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
mailbox_close(&as->m);
return r;
}
r = mailbox_lock_header(&as->m);
if (r) {
mailbox_close(&as->m);
return r;
}
r = mailbox_open_index(&as->m);
if (r) {
mailbox_close(&as->m);
return r;
}
if (as->m.format != format) {
mailbox_close(&as->m);
return IMAP_MAILBOX_NOTSUPPORTED;
}
r = mailbox_lock_index(&as->m);
if (r) {
mailbox_close(&as->m);
return r;
}
as->tid = NULL;
r = quota_read(&as->m.quota, &as->tid, 1);
if (!r) {
if (as->m.quota.limit >= 0 && quotacheck >= 0 &&
as->m.quota.used + quotacheck >
((unsigned) as->m.quota.limit * QUOTA_UNITS)) {
quota_abort(&as->tid);
mailbox_close(&as->m);
r = IMAP_QUOTA_EXCEEDED;
}
}
else if (r == IMAP_QUOTAROOT_NONEXISTENT) r = 0;
if (r) {
mailbox_close(&as->m);
return r;
}
if (userid) {
strlcpy(as->userid, userid, sizeof(as->userid));
} else {
as->userid[0] = '\0';
}
as->orig_cache_len = as->m.cache_len;
as->nummsg = as->numanswered =
as->numdeleted = as->numflagged = 0;
as->quota_used = 0;
as->writeheader = 0;
as->seen_msgrange = NULL;
as->seen_alloced = 0;
as->s = APPEND_READY;
return 0;
}
int append_commit(struct appendstate *as,
long quotacheck __attribute__((unused)),
unsigned long *uidvalidity,
unsigned long *start,
unsigned long *num)
{
int r = 0;
if (as->s == APPEND_DONE) return 0;
if (start) *start = as->m.last_uid + 1;
if (num) *num = as->nummsg;
if (uidvalidity) *uidvalidity = as->m.uidvalidity;
if (as->writeheader && (r = mailbox_write_header(&as->m))) {
syslog(LOG_ERR, "IOERROR: writing header for %s: %s",
as->m.name, error_message(r));
append_abort(as);
return r;
}
if (fsync(as->m.cache_fd)) {
syslog(LOG_ERR, "IOERROR: writing cache file for %s: %m",
as->m.name);
append_abort(as);
return IMAP_IOERROR;
}
if (fsync(as->m.index_fd)) {
syslog(LOG_ERR, "IOERROR: writing index records for %s: %m",
as->m.name);
append_abort(as);
return IMAP_IOERROR;
}
as->m.exists += as->nummsg;
as->m.last_uid += as->nummsg;
as->m.answered += as->numanswered;
as->m.deleted += as->numdeleted;
as->m.flagged += as->numflagged;
as->m.last_appenddate = time(0);
as->m.quota_mailbox_used += as->quota_used;
if (as->m.minor_version > MAILBOX_MINOR_VERSION) {
as->m.minor_version = MAILBOX_MINOR_VERSION;
}
r = mailbox_write_index_header(&as->m);
if (r) {
syslog(LOG_ERR, "IOERROR: writing index header for %s: %s",
as->m.name, error_message(r));
append_abort(as);
return r;
}
as->m.quota.used += as->quota_used;
r = quota_write(&as->m.quota, &as->tid);
if (!r) quota_commit(&as->tid);
else {
quota_abort(&as->tid);
syslog(LOG_ERR,
"LOSTQUOTA: unable to record use of %u bytes in quota file %s",
as->quota_used, as->m.quota.root);
}
if (as->seen_msgrange && as->userid[0]) {
append_addseen(&as->m, as->userid, as->seen_msgrange);
}
if (as->seen_msgrange) {
free(as->seen_msgrange);
}
mailbox_unlock_index(&as->m);
mailbox_unlock_header(&as->m);
mailbox_close(&as->m);
as->s = APPEND_DONE;
return 0;
}
int append_abort(struct appendstate *as)
{
int r = 0;
unsigned long uid;
if (as->s == APPEND_DONE) return 0;
as->s = APPEND_DONE;
for (uid = as->m.last_uid + 1; uid <= as->m.last_uid + as->nummsg; uid++) {
char fname[MAX_MAILBOX_PATH+1];
strlcpy(fname, as->m.path, sizeof(fname));
strlcat(fname, "/", sizeof(fname));
mailbox_message_get_fname(&as->m, uid, fname + strlen(fname),
sizeof(fname) - strlen(fname));
if (unlink(fname) < 0) {
}
}
ftruncate(as->m.cache_fd, as->orig_cache_len);
mailbox_unlock_index(&as->m);
mailbox_unlock_header(&as->m);
mailbox_close(&as->m);
quota_abort(&as->tid);
if (as->seen_msgrange) {
free(as->seen_msgrange);
}
return r;
}
int append_stageparts(struct stagemsg *stagep)
{
if (!stagep) return 0;
return -1;
}
FILE *append_newstage(const char *mailboxname, time_t internaldate,
int msgnum, struct stagemsg **stagep)
{
struct stagemsg *stage;
char stagedir[MAX_MAILBOX_PATH+1], stagefile[MAX_MAILBOX_PATH+1];
FILE *f;
int r;
assert(mailboxname != NULL);
assert(stagep != NULL);
stage = xmalloc(sizeof(struct stagemsg));
stage->parts = xzmalloc(5 * (MAX_MAILBOX_PATH+1) * sizeof(char));
stage->partend = stage->parts + 5 * (MAX_MAILBOX_PATH+1) * sizeof(char);
snprintf(stage->fname, sizeof(stage->fname), "%d-%d-%d",
(int) getpid(), (int) internaldate, msgnum);
r = mboxlist_findstage(mailboxname, stagedir, sizeof(stagedir));
if (r) {
syslog(LOG_ERR, "couldn't find stage directory for mbox: '%s': %s",
mailboxname, error_message(r));
return NULL;
}
strlcpy(stagefile, stagedir, sizeof(stagefile));
strlcat(stagefile, stage->fname, sizeof(stagefile));
f = fopen(stagefile, "w+");
if (!f) {
if (mkdir(stagedir, 0755) != 0) {
syslog(LOG_ERR, "couldn't create stage directory: %s: %m",
stagedir);
} else {
syslog(LOG_NOTICE, "created stage directory %s",
stagedir);
f = fopen(stagefile, "w+");
}
}
if (!f) {
syslog(LOG_ERR, "IOERROR: creating message file %s: %m",
stagefile);
*stagep = NULL;
return NULL;
}
strlcpy(stage->parts, stagefile, MAX_MAILBOX_PATH+1);
stage->parts[strlen(stagefile) + 1] = '\0';
*stagep = stage;
return f;
}
int append_fromstage(struct appendstate *as,
struct stagemsg *stage, time_t internaldate,
const char **flag, int nflags, int nolink)
{
struct mailbox *mailbox = &as->m;
struct index_record message_index;
char fname[MAX_MAILBOX_PATH+1];
FILE *destfile;
int i, r;
int userflag, emptyflag;
char stagefile[MAX_MAILBOX_PATH+1];
int sflen;
char *p;
assert(stage != NULL && stage->parts[0] != '\0');
assert(mailbox->format == MAILBOX_FORMAT_NORMAL);
zero_index(message_index);
mboxlist_findstage(mailbox->name, stagefile, sizeof(stagefile));
strlcat(stagefile, stage->fname, sizeof(stagefile));
sflen = strlen(stagefile);
p = stage->parts;
while (p < stage->partend) {
int sl = strlen(p);
if (sl == 0) {
break;
}
if (!strcmp(stagefile, p)) {
break;
}
p += sl + 1;
}
if (*p == '\0') {
r = mailbox_copyfile(stage->parts, stagefile, 0);
if (r) {
char stagedir[MAX_MAILBOX_PATH+1];
mboxlist_findstage(mailbox->name, stagedir, sizeof(stagedir));
if (mkdir(stagedir, 0755) != 0) {
syslog(LOG_ERR, "couldn't create stage directory: %s: %m",
stagedir);
} else {
syslog(LOG_NOTICE, "created stage directory %s",
stagedir);
r = mailbox_copyfile(stage->parts, stagefile, 0);
}
}
if (r) {
syslog(LOG_ERR, "IOERROR: creating message file %s: %m",
stagefile);
unlink(stagefile);
return r;
}
if (p + sflen > stage->partend - 5) {
int cursize = stage->partend - stage->parts;
int curp = p - stage->parts;
stage->parts = xrealloc(stage->parts, 2 * cursize);
stage->partend = stage->parts + 2 * cursize;
p = stage->parts + curp;
}
strcpy(p, stagefile);
p[sflen + 1] = '\0';
}
message_index.uid = mailbox->last_uid + as->nummsg + 1;
message_index.last_updated = time(0);
message_index.internaldate = internaldate;
lseek(mailbox->cache_fd, 0L, SEEK_END);
as->nummsg++;
strlcpy(fname, mailbox->path, sizeof(fname));
strlcat(fname, "/", sizeof(fname));
mailbox_message_get_fname(mailbox, message_index.uid,
fname + strlen(fname),
sizeof(fname) - strlen(fname));
r = mailbox_copyfile(p, fname, nolink);
destfile = fopen(fname, "r");
if (!r && destfile) {
r = message_parse_file(destfile, mailbox, &message_index);
}
if (destfile) {
fsync(fileno(destfile));
fclose(destfile);
}
if (r) {
append_abort(as);
return r;
}
for (i = 0; i < nflags; i++) {
if (!strcmp(flag[i], "\\seen")) {
addme(&as->seen_msgrange, &as->seen_alloced, message_index.uid);
}
else if (!strcmp(flag[i], "\\deleted")) {
if (mailbox->myrights & ACL_REMOVE) {
message_index.system_flags |= FLAG_DELETED;
as->numdeleted++;
}
}
else if (!strcmp(flag[i], "\\draft")) {
if (mailbox->myrights & ACL_WRITE) {
message_index.system_flags |= FLAG_DRAFT;
}
}
else if (!strcmp(flag[i], "\\flagged")) {
if (mailbox->myrights & ACL_WRITE) {
message_index.system_flags |= FLAG_FLAGGED;
as->numflagged++;
}
}
else if (!strcmp(flag[i], "\\answered")) {
if (mailbox->myrights & ACL_WRITE) {
message_index.system_flags |= FLAG_ANSWERED;
as->numanswered++;
}
}
else if (mailbox->myrights & ACL_WRITE) {
emptyflag = -1;
for (userflag = 0; userflag < MAX_USER_FLAGS; userflag++) {
if (mailbox->flagname[userflag]) {
if (!strcasecmp(flag[i], mailbox->flagname[userflag]))
break;
}
else if (emptyflag == -1) emptyflag = userflag;
}
if (userflag == MAX_USER_FLAGS && emptyflag != -1) {
userflag = emptyflag;
mailbox->flagname[userflag] = xstrdup(flag[i]);
as->writeheader++;
}
if (userflag != MAX_USER_FLAGS) {
message_index.user_flags[userflag/32] |= 1<<(userflag&31);
}
}
}
r = mailbox_append_index(mailbox, &message_index,
mailbox->exists + as->nummsg - 1, 1, 0);
if (r) {
append_abort(as);
return r;
}
as->quota_used += message_index.size;
return 0;
}
int append_removestage(struct stagemsg *stage)
{
char *p;
if (stage == NULL) return 0;
p = stage->parts;
while (*p != '\0' && p < stage->partend) {
if (unlink(p) != 0) {
syslog(LOG_ERR, "IOERROR: error unlinking file %s: %m", p);
}
p += strlen(p) + 1;
}
free(stage->parts);
free(stage);
return 0;
}
int append_fromstream(struct appendstate *as,
struct protstream *messagefile,
unsigned long size,
time_t internaldate,
const char **flag,
int nflags)
{
struct mailbox *mailbox = &as->m;
struct index_record message_index;
char fname[MAX_MAILBOX_PATH+1];
FILE *destfile;
int i, r;
int userflag, emptyflag;
assert(mailbox->format == MAILBOX_FORMAT_NORMAL);
assert(size != 0);
zero_index(message_index);
message_index.uid = mailbox->last_uid + as->nummsg + 1;
message_index.last_updated = time(0);
message_index.internaldate = internaldate;
lseek(mailbox->cache_fd, 0L, SEEK_END);
strlcpy(fname, mailbox->path, sizeof(fname));
strlcat(fname, "/", sizeof(fname));
mailbox_message_get_fname(mailbox, message_index.uid,
fname + strlen(fname),
sizeof(fname) - strlen(fname));
as->nummsg++;
unlink(fname);
destfile = fopen(fname, "w+");
if (!destfile) {
syslog(LOG_ERR, "IOERROR: creating message file %s: %m", fname);
append_abort(as);
return IMAP_IOERROR;
}
r = message_copy_strict(messagefile, destfile, size);
if (!r) {
r = message_parse_file(destfile, mailbox, &message_index);
}
fclose(destfile);
if (r) {
append_abort(as);
return r;
}
for (i = 0; i < nflags; i++) {
if (!strcmp(flag[i], "\\seen")) {
addme(&as->seen_msgrange, &as->seen_alloced, message_index.uid);
}
else if (!strcmp(flag[i], "\\deleted")) {
if (mailbox->myrights & ACL_REMOVE) {
message_index.system_flags |= FLAG_DELETED;
as->numdeleted++;
}
}
else if (!strcmp(flag[i], "\\draft")) {
if (mailbox->myrights & ACL_WRITE) {
message_index.system_flags |= FLAG_DRAFT;
}
}
else if (!strcmp(flag[i], "\\flagged")) {
if (mailbox->myrights & ACL_WRITE) {
message_index.system_flags |= FLAG_FLAGGED;
as->numflagged++;
}
}
else if (!strcmp(flag[i], "\\answered")) {
if (mailbox->myrights & ACL_WRITE) {
message_index.system_flags |= FLAG_ANSWERED;
as->numanswered++;
}
}
else if (mailbox->myrights & ACL_WRITE) {
emptyflag = -1;
for (userflag = 0; userflag < MAX_USER_FLAGS; userflag++) {
if (mailbox->flagname[userflag]) {
if (!strcasecmp(flag[i], mailbox->flagname[userflag]))
break;
}
else if (emptyflag == -1) emptyflag = userflag;
}
if (userflag == MAX_USER_FLAGS && emptyflag != -1) {
userflag = emptyflag;
mailbox->flagname[userflag] = xstrdup(flag[i]);
as->writeheader++;
}
if (userflag != MAX_USER_FLAGS) {
message_index.user_flags[userflag/32] |= 1<<(userflag&31);
}
}
}
r = mailbox_append_index(mailbox, &message_index,
mailbox->exists + as->nummsg - 1, 1, 0);
if (r) {
append_abort(as);
return r;
}
as->quota_used += message_index.size;
return 0;
}
int append_copy(struct mailbox *mailbox,
struct appendstate *as,
int nummsg,
struct copymsg *copymsg)
{
struct mailbox *append_mailbox = &as->m;
int msg;
struct index_record *message_index;
char fname[MAX_MAILBOX_PATH+1];
const char *src_base;
unsigned long src_size;
const char *startline, *endline;
FILE *destfile;
int r, n;
int flag, userflag, emptyflag;
assert(append_mailbox->format == MAILBOX_FORMAT_NORMAL);
if (!nummsg) {
append_abort(as);
return 0;
}
lseek(append_mailbox->cache_fd, 0L, SEEK_END);
message_index = (struct index_record *)
xmalloc(nummsg * sizeof(struct index_record));
for (msg = 0; msg < nummsg; msg++) {
zero_index(message_index[msg]);
message_index[msg].uid = append_mailbox->last_uid + 1 + as->nummsg;
message_index[msg].last_updated = time(0);
message_index[msg].internaldate = copymsg[msg].internaldate;
as->nummsg++;
strlcpy(fname, append_mailbox->path, sizeof(fname));
strlcat(fname, "/", sizeof(fname));
mailbox_message_get_fname(append_mailbox, message_index[msg].uid,
fname + strlen(fname),
sizeof(fname) - strlen(fname));
if (copymsg[msg].cache_len) {
char fnamebuf[MAILBOX_FNAME_LEN];
mailbox_message_get_fname(mailbox, copymsg[msg].uid, fnamebuf,
sizeof(fnamebuf));
r = mailbox_copyfile(fnamebuf, fname, 0);
if (r) goto fail;
message_index[msg].cache_offset =
lseek(append_mailbox->cache_fd, 0L, SEEK_CUR);
message_index[msg].sentdate = copymsg[msg].sentdate;
message_index[msg].size = copymsg[msg].size;
message_index[msg].header_size = copymsg[msg].header_size;
message_index[msg].content_offset = copymsg[msg].header_size;
message_index[msg].content_lines = copymsg[msg].content_lines;
message_index[msg].cache_version = copymsg[msg].cache_version;
if ( (copymsg[msg].cache_len > buf_len) || (tmp_buf == NULL) )
{
buf_len = copymsg[msg].cache_len;
syslog( LOG_DEBUG, "append_copy: alloc temp buff: %d", buf_len );
tmp_buf = xrealloc( tmp_buf, buf_len );
}
if ( tmp_buf != NULL )
{
memcpy( tmp_buf, copymsg[msg].cache_begin, copymsg[msg].cache_len );
n = retry_write(append_mailbox->cache_fd, tmp_buf,
copymsg[msg].cache_len);
}
else
{
n = retry_write(append_mailbox->cache_fd, copymsg[msg].cache_begin,
copymsg[msg].cache_len);
}
if (n == -1) {
syslog(LOG_ERR, "IOERROR: writing cache file for %s: %m",
append_mailbox->name);
r = IMAP_IOERROR;
goto fail;
}
} else {
r = 0;
unlink(fname);
destfile = fopen(fname, "w+");
if (!destfile) {
syslog(LOG_ERR, "IOERROR: writing message file %s: %m", fname);
r = IMAP_IOERROR;
goto fail;
}
if (mailbox_map_message(mailbox, 0, copymsg[msg].uid,
&src_base, &src_size) != 0) {
fclose(destfile);
syslog(LOG_ERR, "IOERROR: opening message file %lu of %s: %m",
copymsg[msg].uid, mailbox->name);
r = IMAP_IOERROR;
goto fail;
}
startline = src_base;
while ( (endline = memchr(startline, '\n',
src_size - (startline - src_base))) ) {
fwrite(startline, 1, (endline - startline), destfile);
if (endline == startline || endline[-1] != '\r') {
putc('\r', destfile);
}
putc('\n', destfile);
startline = endline+1;
}
fwrite(startline, 1, src_size - (startline - src_base), destfile);
fflush(destfile);
if (ferror(destfile) || fsync(fileno(destfile))) {
syslog(LOG_ERR, "IOERROR: writing message: %m");
r = IMAP_IOERROR;
}
mailbox_unmap_message(mailbox, copymsg[msg].uid,
&src_base, &src_size);
if (!r) r = message_parse_file(destfile, append_mailbox,
&message_index[msg]);
fclose(destfile);
if (r) goto fail;
}
as->quota_used += message_index[msg].size;
if (append_mailbox->myrights & ACL_WRITE) {
message_index[msg].system_flags =
copymsg[msg].system_flags & ~FLAG_DELETED;
for (flag = 0; copymsg[msg].flag[flag]; flag++) {
emptyflag = -1;
for (userflag = 0; userflag < MAX_USER_FLAGS; userflag++) {
if (append_mailbox->flagname[userflag]) {
if (!strcasecmp(copymsg[msg].flag[flag],
append_mailbox->flagname[userflag]))
break;
}
else if (emptyflag == -1) emptyflag = userflag;
}
if (userflag == MAX_USER_FLAGS && emptyflag != -1) {
userflag = emptyflag;
append_mailbox->flagname[userflag] =
xstrdup(copymsg[msg].flag[flag]);
as->writeheader++;
}
if (userflag != MAX_USER_FLAGS) {
message_index[msg].user_flags[userflag/32] |=
1<<(userflag&31);
}
}
}
if (append_mailbox->myrights & ACL_REMOVE) {
message_index[msg].system_flags |=
copymsg[msg].system_flags & FLAG_DELETED;
}
if (message_index[msg].system_flags & FLAG_DELETED) as->numdeleted++;
if (message_index[msg].system_flags & FLAG_ANSWERED) as->numanswered++;
if (message_index[msg].system_flags & FLAG_FLAGGED) as->numflagged++;
if (copymsg[msg].seen) {
addme(&as->seen_msgrange, &as->seen_alloced,
message_index[msg].uid);
}
}
r = mailbox_append_index(append_mailbox, message_index,
append_mailbox->exists + as->nummsg - nummsg,
nummsg, 0);
fail:
if (r) append_abort(as);
free(message_index);
return r;
}
static void addme(char **msgrange, int *alloced, long uid)
{
char *p;
int wasrange;
int len;
assert(msgrange != NULL);
len = *msgrange ? strlen(*msgrange) : 0;
if (*alloced < len + 40) {
*alloced += 40;
*msgrange = (char *) xrealloc(*msgrange, sizeof(char *) * (*alloced));
}
p = *msgrange;
if (!len) {
sprintf(*msgrange, "%ld", uid);
} else {
wasrange = 0;
p = *msgrange + len - 1;
while (isdigit((int) *p) && p > *msgrange) p--;
if (*p == ':') wasrange = 1;
p++;
if (atoi(p) == uid - 1) {
if (!wasrange) {
p = *msgrange + len;
*p++ = ':';
} else {
}
} else {
p = *msgrange + len;
*p++ = ',';
}
sprintf(p, "%ld", uid);
return;
}
}
static int append_addseen(struct mailbox *mailbox,
const char *userid,
const char *msgrange)
{
int r;
struct seen *seendb;
time_t last_read, last_change;
unsigned last_uid;
char *seenuids;
int last_seen;
char *tail, *p;
int oldlen, newlen;
int start;
start = atoi(msgrange);
r = seen_open(mailbox, userid, SEEN_CREATE, &seendb);
if (r) return r;
r = seen_lockread(seendb, &last_read, &last_uid, &last_change, &seenuids);
if (r) return r;
oldlen = strlen(seenuids);
newlen = oldlen + strlen(msgrange) + 10;
seenuids = xrealloc(seenuids, newlen);
tail = seenuids + oldlen;
while (tail > seenuids && isdigit((int) tail[-1])) tail--;
for (p = tail, last_seen=0; *p; p++) last_seen = last_seen * 10 + *p - '0';
if (last_seen && last_seen >= start-1) {
if (tail > seenuids && tail[-1] == ':') p = tail - 1;
*p++ = ':';
}
else {
if (p > seenuids) *p++ = ',';
}
strlcpy(p, msgrange, newlen-(p-seenuids+1));
r = seen_write(seendb, last_read, last_uid, time(NULL), seenuids);
seen_close(seendb);
free(seenuids);
return r;
}