Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
227 KB
Referenced Files
None
Subscribers
None
diff --git a/imap/backend.c b/imap/backend.c
index 0704920c8..c72daa988 100644
--- a/imap/backend.c
+++ b/imap/backend.c
@@ -1,1020 +1,1021 @@
/* backend.c -- IMAP server proxy for Cyrus Murder
*
* Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* $Id: backend.c,v 1.63 2010/08/04 18:57:36 wescraig Exp $
*/
#include <config.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <fcntl.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <syslog.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <sasl/sasl.h>
#include <sasl/saslutil.h>
#include "backend.h"
+#include "exitcodes.h"
#include "global.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "xstrlcat.h"
#include "iptostring.h"
#include "util.h"
enum {
AUTO_CAPA_BANNER = -1,
AUTO_CAPA_NO = 0,
};
static void forget_capabilities(struct backend *s)
{
int i;
for (i = 0 ; i < s->num_cap_params ; i++)
free(s->cap_params[i].params);
s->capability = 0;
free(s->cap_params);
s->cap_params = NULL;
s->num_cap_params = 0;
}
static char *append_word(char *w1, const char *w2)
{
int len1 = strlen(w1);
int len2 = strlen(w2);
w1 = xrealloc(w1, len1 + len2 + 2);
w1[len1] = ' ';
strcpy(w1+len1+1, w2);
return w1;
}
/*
* Save the capability. Updates @s->capability so that the CAPA() macro
* will find the flag. Saves the @param string if not NULL.
*/
static void save_capability(struct backend *s,
const struct capa_t *c,
const char *param)
{
int i;
s->capability |= c->flag;
if (param) {
/* find the matching cap_params entry */
for (i = 0 ; i < s->num_cap_params ; i++)
if (s->cap_params[i].capa == c->flag)
break;
if (i == s->num_cap_params) {
/* not found, expand the array and add a params */
s->num_cap_params++;
s->cap_params = xrealloc(s->cap_params,
sizeof(*s->cap_params) * s->num_cap_params);
s->cap_params[i].capa = c->flag;
s->cap_params[i].params = xstrdup(param);
} else {
/* append to the existing params */
s->cap_params[i].params = append_word(s->cap_params[i].params, param);
}
}
}
/*
* We have been given a @name and optionally a @value by the server,
* check to see if the protocol_t knows about this specific capability.
* If known, save the capability.
*/
static int match_capability(struct backend *s,
const char *name, const char *value)
{
const struct capa_t *c;
const char *cend;
for (c = s->prot->capa_cmd.capa; c->str; c++) {
cend = strchr(c->str, '=');
if (cend) {
/* c->str is of the form NAME=VALUE, we want to match
* @name against the the NAME part and @value against
* the VALUE part */
if (strlen(name) == (unsigned)(cend - c->str) &&
!strncasecmp(name, c->str, (int)(cend - c->str)) &&
value &&
!strcasecmp(value, cend+1)) {
save_capability(s, c, NULL);
return 1; /* full match, stop calling me with this @name */
}
} else {
/* c->str is a bare NAME, just try to match it against @name */
if (!strcasecmp(c->str, name)) {
save_capability(s, c, value);
return 2; /* partial match, keep calling with new @value
* for this @name */
}
}
}
return 0; /* no match, stop calling me with this @name */
}
/*
* Given a line buffer @buf, find the IMAP response code named by @code,
* isolate it and return the start of it, or NULL if not found.
*/
static char *find_response_code(char *buf, const char *code)
{
char *start;
char *end;
int codelen = strlen(code);
/* Try to find the first response code */
start = strchr(buf, '[');
if (!start)
return NULL; /* no response codes */
start++;
for (;;) {
while (*start && Uisspace(*start))
start++;
if (!*start)
break; /* nothing to see here */
/* response codes are delineated by [] */
if (!(end = strchr(start, ']')))
break; /* unbalanced [response code] */
if (!strncasecmp(start, code, codelen) && Uisspace(start[codelen])) {
*end = '\0';
start += codelen+1;
return start;
} else {
start = end+1;
}
}
return NULL;
}
/* Tokenize on whitespace, for parse_capability */
static char *ws_tok(char *buf)
{
return strtok(buf, " \t\r\n");
}
/* Tokenize on whitespace OR dash, for parse_capability */
static char *dash_tok(char *buf)
{
return strtok(buf, " \t\r\n-");
}
/* Tokenize on alternate "quoted-words", for parse_capability.
* Note that we probably don't need the general case with escapes. */
static char *quote_tok(char *buf)
{
char *p;
static const char sep[] = "\"";
p = strtok(buf, sep);
if (p)
strtok(NULL, sep);
return p;
}
/*
* Parse a line of text from the wire which might contain
* capabilities, using various details in the capa_cmd field of
* the protocol_t to decode capabilities. Only capabilities
* explicitly named in an entry in the capa_cmd.capa[] array
* are detected; any others present on the wire are ignored.
* All string matches are case-insensitive. Entries are
* matched thus:
*
* { "NAME", FLAG }
* If a capability named NAME is present on the wire,
* the corresponding FLAG will be set where the CAPA()
* macro will test it. Furthermore, if any parameters
* are present on the wire they will be saved where
* the backend_get_cap_params() function will find them.
* If multiple parameters are present on the wire, all
* of them will be saved, separated by space characters.
*
* { "NAME=VALUE", FLAG }
* If a capability named NAME is present on the wire,
* *and* a parameter which matches VALUE is also present,
* the corresponding FLAG will be set where the CAPA()
* macro will test it. VALUE is not saved anywhere and
* backend_get_cap_params() will not return it.
*
* Returns: 1 if any capabilities were found in the string,
* 0 otherwise.
*/
static int parse_capability(struct backend *s, const char *str)
{
char *buf;
char *word;
char *param;
int matches = 0;
char *(*tok)(char *) = ws_tok;
static const char code[] = "CAPABILITY";
/* save the buffer, we're going to be destructively parsing it */
buf = xstrdup(str);
if ((s->prot->capa_cmd.formatflags & CAPAF_ONE_PER_LINE)) {
/*
* POP3, LMTP and sync protocol style: one capability per line.
*/
if ((s->prot->capa_cmd.formatflags & CAPAF_QUOTE_WORDS))
tok = quote_tok;
if ((s->prot->capa_cmd.formatflags & CAPAF_DASH_STUFFING))
word = dash_tok(buf);
else
word = tok(buf);
/* Ignore the first word of the line. Used for LMTP and POP3 */
if (word && (s->prot->capa_cmd.formatflags & CAPAF_SKIP_FIRST_WORD))
word = tok(NULL);
if (!word)
goto out;
/* @word is the capability name. Any remaining atoms are parameters */
param = tok(NULL);
if (!param) {
/* no parameters */
matches |= match_capability(s, word, NULL);
} else {
/* 1 or more parameters */
for ( ; param ; param = tok(NULL)) {
int r = match_capability(s, word, param);
matches |= r;
if (r != 2)
break;
}
}
} else {
/*
* IMAP style: one humungous line with a list of atoms
* of the form NAME or NAME=PARAM, preceeded by the atom
* CAPABILITY, and either surrounded by [] or being an
* untagged response like "* CAPABILITY ...atoms... CRLF"
*/
char *start;
if ((start = find_response_code(buf, code))) {
/* The line is probably a PREAUTH or OK response, possibly
* containing a CAPABILITY response code, and possibly
* containing some other response codes we don't care about. */
word = tok(start);
} else {
/* The line is probably an untagged response to a CAPABILITY
* command. Tokenize until we find the CAPABILITY atom */
for (word = tok(buf) ;
word && strcasecmp(word, code) ;
word = tok(NULL))
;
if (word)
word = tok(NULL); /* skip the CAPABILITY atom itself */
}
/* `word' now points to the first capability; parse it and
* each remaining word as a NAME or NAME=VALUE capability */
for ( ; word ; word = tok(NULL)) {
param = strchr(word, '=');
if (param)
*param++ = '\0';
matches |= match_capability(s, word, param);
}
}
out:
free(buf);
return !!matches;
}
static void post_parse_capability(struct backend *s)
{
if (s->prot->capa_cmd.postcapability)
s->prot->capa_cmd.postcapability(s);
}
/*
* Get capabilities from the server, and parse them according to
* details in the protocol_t, so that the CAPA() macro and perhaps
* the backend_get_cap_params() function will notice them. Any
* capabilities previously parsed are forgotten.
*
* The server might give us capabilities for free just because we
* connected (or did a STARTTLS or logged in); in this case, call
* with a non-zero value for @automatic. Otherwise, we send a
* protocol-specific command to the server to tickle it into
* disgorging some capabilities.
*
* Returns: 1 if any capabilities were found, 0 otherwise.
*/
static int ask_capability(struct backend *s, int dobanner, int automatic)
{
struct protstream *pout = s->out, *pin = s->in;
const struct protocol_t *prot = s->prot;
int matches = 0;
char str[4096];
const char *resp;
resp = (automatic == AUTO_CAPA_BANNER) ?
prot->banner.resp : prot->capa_cmd.resp;
if (!automatic) {
/* no capability command */
if (!prot->capa_cmd.cmd) return -1;
/* request capabilities of server */
prot_printf(pout, "%s", prot->capa_cmd.cmd);
if (prot->capa_cmd.arg) prot_printf(pout, " %s", prot->capa_cmd.arg);
prot_printf(pout, "\r\n");
prot_flush(pout);
}
forget_capabilities(s);
do {
if (prot_fgets(str, sizeof(str), pin) == NULL) break;
matches |= parse_capability(s, str);
if (!resp) {
/* multiline response with no distinct end (IMAP banner) */
prot_NONBLOCK(pin);
}
if (dobanner) xstrncpy(s->banner, str, sizeof(s->banner));
/* look for the end of the capabilities */
} while (!resp || strncasecmp(str, resp, strlen(resp)));
prot_BLOCK(pin);
post_parse_capability(s);
return matches;
}
/*
* Return the parameters reported by the server for the given
* capability. @capa must be a single capability flag, as given in the
* protocol_t. Return value is a string, comprising all the parameters
* for the given capability, in their original string form, in the order
* seen on the wire, separated by a single space character. If the
* capability was not reported by the server, or was reported with no
* parameters, NULL is returned.
*/
EXPORTED char *backend_get_cap_params(const struct backend *s, unsigned long capa)
{
int i;
if (!(s->capability & capa))
return NULL;
for (i = 0 ; i < s->num_cap_params ; i++) {
if (s->cap_params[i].capa == capa) {
return xstrdup(s->cap_params[i].params);
}
}
return NULL;
}
static int do_compress(struct backend *s, struct simple_cmd_t *compress_cmd)
{
#ifndef HAVE_ZLIB
return -1;
#else
char buf[1024];
/* send compress command */
prot_printf(s->out, "%s\r\n", compress_cmd->cmd);
prot_flush(s->out);
/* check response */
if (!prot_fgets(buf, sizeof(buf), s->in) ||
strncmp(buf, compress_cmd->ok, strlen(compress_cmd->ok)))
return -1;
prot_setcompress(s->in);
prot_setcompress(s->out);
return 0;
#endif /* HAVE_ZLIB */
}
static int do_starttls(struct backend *s)
{
#ifndef HAVE_SSL
return -1;
#else
const struct tls_cmd_t *tls_cmd = &s->prot->tls_cmd;
char buf[2048];
int r;
int *layerp;
char *auth_id;
sasl_ssf_t ssf;
/* send starttls command */
prot_printf(s->out, "%s\r\n", tls_cmd->cmd);
prot_flush(s->out);
/* check response */
if (!prot_fgets(buf, sizeof(buf), s->in) ||
strncmp(buf, tls_cmd->ok, strlen(tls_cmd->ok)))
return -1;
r = tls_init_clientengine(5, "", "");
if (r == -1) return -1;
/* SASL and openssl have different ideas about whether ssf is signed */
layerp = (int *) &ssf;
r = tls_start_clienttls(s->in->fd, s->out->fd, layerp, &auth_id,
&s->tlsconn, &s->tlssess);
if (r == -1) return -1;
r = sasl_setprop(s->saslconn, SASL_SSF_EXTERNAL, &ssf);
if (r == SASL_OK)
r = sasl_setprop(s->saslconn, SASL_AUTH_EXTERNAL, auth_id);
if (auth_id) free(auth_id);
if (r != SASL_OK) return -1;
prot_settls(s->in, s->tlsconn);
prot_settls(s->out, s->tlsconn);
ask_capability(s, /*dobanner*/1, s->prot->tls_cmd.auto_capa);
return 0;
#endif /* HAVE_SSL */
}
static char *intersect_mechlists( char *config, char *server )
{
char *newmechlist = xzmalloc( strlen( config ) + 1 );
char *cmech = NULL, *smech = NULL, *s;
int count = 0;
char csave, ssave;
do {
if ( isalnum( *config ) || *config == '_' || *config == '-' ) {
if ( cmech == NULL ) {
cmech = config;
}
} else {
if ( cmech != NULL ) {
csave = *config;
*config = '\0';
s = server;
do {
if ( isalnum( *s ) || *s == '_' || *s == '-' ) {
if ( smech == NULL ) {
smech = s;
}
} else {
if ( smech != NULL ) {
ssave = *s;
*s = '\0';
if ( strcasecmp( cmech, smech ) == 0 ) {
if ( count > 0 ) {
strcat( newmechlist, " " );
}
strcat( newmechlist, cmech );
count++;
*s = ssave;
smech = NULL;
break;
}
*s = ssave;
smech = NULL;
}
}
} while ( *s++ );
*config = csave;
cmech = NULL;
}
}
} while ( *config++ );
if ( count == 0 ) {
free( newmechlist );
return( NULL );
}
return( newmechlist );
}
static int backend_authenticate(struct backend *s, const char *userid,
sasl_callback_t *cb, const char **status)
{
struct protocol_t *prot = s->prot;
int r;
char *mechlist;
sasl_security_properties_t secprops =
{ 0, 0xFF, PROT_BUFSIZE, 0, NULL, NULL }; /* default secprops */
struct sockaddr_storage saddr_l, saddr_r;
char remoteip[60], localip[60];
socklen_t addrsize;
int local_cb = 0;
char buf[2048], optstr[128], *p;
const char *mech_conf, *pass;
/* set the IP addresses */
addrsize=sizeof(struct sockaddr_storage);
if (getpeername(s->sock, (struct sockaddr *)&saddr_r, &addrsize) != 0)
return SASL_FAIL;
if(iptostring((struct sockaddr *)&saddr_r, addrsize, remoteip, 60) != 0)
return SASL_FAIL;
addrsize=sizeof(struct sockaddr_storage);
if (getsockname(s->sock, (struct sockaddr *)&saddr_l, &addrsize)!=0)
return SASL_FAIL;
if(iptostring((struct sockaddr *)&saddr_l, addrsize, localip, 60) != 0)
return SASL_FAIL;
if (!cb) {
local_cb = 1;
strlcpy(optstr, s->hostname, sizeof(optstr));
p = strchr(optstr, '.');
if (p) *p = '\0';
strlcat(optstr, "_password", sizeof(optstr));
pass = config_getoverflowstring(optstr, NULL);
if(!pass) pass = config_getstring(IMAPOPT_PROXY_PASSWORD);
cb = mysasl_callbacks(userid,
config_getstring(IMAPOPT_PROXY_AUTHNAME),
config_getstring(IMAPOPT_PROXY_REALM),
pass);
}
/* Require proxying if we have an "interesting" userid (authzid) */
r = sasl_client_new(prot->sasl_service, s->hostname, localip, remoteip, cb,
(userid && *userid ? SASL_NEED_PROXY : 0) |
(prot->sasl_cmd.parse_success ? SASL_SUCCESS_DATA : 0),
&s->saslconn);
if (r != SASL_OK)
goto out;
r = sasl_setprop(s->saslconn, SASL_SEC_PROPS, &secprops);
if (r != SASL_OK)
goto out;
/* Get SASL mechanism list. We can force a particular
mechanism using a <shorthost>_mechs option */
strcpy(buf, s->hostname);
p = strchr(buf, '.');
if (p) *p = '\0';
strcat(buf, "_mechs");
mech_conf = config_getoverflowstring(buf, NULL);
if (!mech_conf) {
mech_conf = config_getstring(IMAPOPT_FORCE_SASL_CLIENT_MECH);
}
mechlist = backend_get_cap_params(s, CAPA_AUTH);
do {
/* If we have a mech_conf, use it */
if (mech_conf && mechlist) {
char *conf = xstrdup(mech_conf);
char *newmechlist = intersect_mechlists( conf, mechlist );
if ( newmechlist == NULL ) {
syslog( LOG_INFO, "%s did not offer %s", s->hostname,
mech_conf );
}
free(conf);
free(mechlist);
mechlist = newmechlist;
}
if (mechlist) {
/* we now do the actual SASL exchange */
saslclient(s->saslconn, &prot->sasl_cmd, mechlist,
s->in, s->out, &r, status);
/* garbage collect */
free(mechlist);
mechlist = NULL;
}
else r = SASL_NOMECH;
/* If we don't have a usable mech, do TLS and try again */
} while (r == SASL_NOMECH &&
CAPA(s, CAPA_STARTTLS) &&
do_starttls(s) != -1 &&
(mechlist = backend_get_cap_params(s, CAPA_AUTH)));
if (r == SASL_OK) {
prot_setsasl(s->in, s->saslconn);
prot_setsasl(s->out, s->saslconn);
}
if (mechlist) free(mechlist);
out:
/* r == SASL_OK on success */
if (local_cb) free_callbacks(cb);
return r;
}
static volatile sig_atomic_t timedout = 0;
static void timed_out(int sig)
{
if (sig == SIGALRM) {
timedout = 1;
} else {
fatal("Bad signal in timed_out", EC_SOFTWARE);
}
}
EXPORTED struct backend *backend_connect(struct backend *ret_backend, const char *server,
struct protocol_t *prot, const char *userid,
sasl_callback_t *cb, const char **auth_status,
int logfd)
{
/* need to (re)establish connection to server or create one */
int sock = -1;
int r;
int err = -1;
int ask = 1; /* should we explicitly ask for capabilities? */
struct addrinfo hints, *res0 = NULL, *res;
struct sockaddr_un sunsock;
char buf[2048];
struct sigaction action;
struct backend *ret;
char rsessionid[MAX_SESSIONID_SIZE];
if (!ret_backend) {
ret = xzmalloc(sizeof(struct backend));
strlcpy(ret->hostname, server, sizeof(ret->hostname));
ret->timeout = NULL;
}
else
ret = ret_backend;
if (server[0] == '/') { /* unix socket */
res0 = &hints;
memset(res0, 0, sizeof(struct addrinfo));
res0->ai_family = PF_UNIX;
res0->ai_socktype = SOCK_STREAM;
res0->ai_addr = (struct sockaddr *) &sunsock;
res0->ai_addrlen = sizeof(sunsock.sun_family) + strlen(server) + 1;
#ifdef SIN6_LEN
res0->ai_addrlen += sizeof(sunsock.sun_len);
sunsock.sun_len = res0->ai_addrlen;
#endif
sunsock.sun_family = AF_UNIX;
strlcpy(sunsock.sun_path, server, sizeof(sunsock.sun_path));
/* XXX set that we are preauthed */
/* change hostname to 'config_servername' */
strlcpy(ret->hostname, config_servername, sizeof(ret->hostname));
}
else { /* inet socket */
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
err = getaddrinfo(server, prot->service, &hints, &res0);
if (err) {
syslog(LOG_ERR, "getaddrinfo(%s) failed: %s",
server, gai_strerror(err));
goto error;
}
}
/* Setup timeout */
timedout = 0;
action.sa_flags = 0;
action.sa_handler = timed_out;
sigemptyset(&action.sa_mask);
if(sigaction(SIGALRM, &action, NULL) < 0)
{
syslog(LOG_ERR, "Setting timeout in backend_connect failed: sigaction: %m");
/* continue anyway */
}
for (res = res0; res; res = res->ai_next) {
sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sock < 0)
continue;
alarm(config_getint(IMAPOPT_CLIENT_TIMEOUT));
if (connect(sock, res->ai_addr, res->ai_addrlen) >= 0)
break;
if(errno == EINTR && timedout == 1)
errno = ETIMEDOUT;
close(sock);
sock = -1;
}
/* Remove timeout code */
alarm(0);
signal(SIGALRM, SIG_IGN);
if (sock < 0) {
if (res0 != &hints)
freeaddrinfo(res0);
syslog(LOG_ERR, "connect(%s) failed: %m", server);
goto error;
}
memcpy(&ret->addr, res->ai_addr, res->ai_addrlen);
if (res0 != &hints)
freeaddrinfo(res0);
ret->in = prot_new(sock, 0);
ret->out = prot_new(sock, 1);
ret->sock = sock;
prot_setflushonread(ret->in, ret->out);
ret->prot = prot;
/* use literal+ to send literals */
prot_setisclient(ret->in, 1);
prot_setisclient(ret->out, 1);
if (logfd >= 0) {
prot_setlog(ret->in, logfd);
prot_setlog(ret->out, logfd);
}
if (prot->banner.auto_capa) {
/* try to get the capabilities from the banner */
r = ask_capability(ret, /*dobanner*/1, AUTO_CAPA_BANNER);
if (r) {
/* found capabilities in banner -> don't ask */
ask = 0;
}
}
else {
do { /* read the initial greeting */
if (!prot_fgets(buf, sizeof(buf), ret->in)) {
syslog(LOG_ERR,
"backend_connect(): couldn't read initial greeting: %s",
ret->in->error ? ret->in->error : "(null)");
goto error;
}
} while (strncasecmp(buf, prot->banner.resp,
strlen(prot->banner.resp)));
xstrncpy(ret->banner, buf, 2048);
}
if (ask) {
/* get the capabilities */
ask_capability(ret, /*dobanner*/0, AUTO_CAPA_NO);
}
/* now need to authenticate to backend server,
unless we're doing LMTP/CSYNC on a UNIX socket (deliver/sync_client) */
if ((server[0] != '/') ||
(strcmp(prot->sasl_service, "lmtp") &&
strcmp(prot->sasl_service, "csync"))) {
char *old_mechlist = backend_get_cap_params(ret, CAPA_AUTH);
const char *my_status;
if ((r = backend_authenticate(ret, userid, cb, &my_status))) {
syslog(LOG_ERR, "couldn't authenticate to backend server: %s",
sasl_errstring(r, NULL, NULL));
free(old_mechlist);
goto error;
}
else {
const void *ssf;
sasl_getprop(ret->saslconn, SASL_SSF, &ssf);
if (*((sasl_ssf_t *) ssf)) {
/* if we have a SASL security layer, compare SASL mech lists
before/after AUTH to check for a MITM attack */
char *new_mechlist;
int auto_capa = (prot->sasl_cmd.auto_capa == AUTO_CAPA_AUTH_SSF);
if (!strcmp(prot->service, "sieve")) {
/* XXX Hack to handle ManageSieve servers.
* No way to tell from protocol if server will
* automatically send capabilities, so we treat it
* as optional.
*/
char ch;
/* wait and probe for possible auto-capability response */
usleep(250000);
prot_NONBLOCK(ret->in);
if ((ch = prot_getc(ret->in)) != EOF) {
prot_ungetc(ch, ret->in);
} else {
auto_capa = AUTO_CAPA_AUTH_NO;
}
prot_BLOCK(ret->in);
}
ask_capability(ret, /*dobanner*/0, auto_capa);
new_mechlist = backend_get_cap_params(ret, CAPA_AUTH);
if (new_mechlist &&
old_mechlist &&
strcmp(new_mechlist, old_mechlist)) {
syslog(LOG_ERR, "possible MITM attack:"
"list of available SASL mechanisms changed");
free(new_mechlist);
free(old_mechlist);
goto error;
}
free(new_mechlist);
}
else if (prot->sasl_cmd.auto_capa == AUTO_CAPA_AUTH_OK) {
/* try to get the capabilities from the AUTH success response */
forget_capabilities(ret);
parse_capability(ret, my_status);
post_parse_capability(ret);
}
if (!(strcmp(prot->service, "imap") &&
(strcmp(prot->service, "pop3")))) {
parse_sessionid(my_status, rsessionid);
syslog(LOG_NOTICE, "proxy %s sessionid=<%s> remote=<%s>", userid, session_id(), rsessionid);
}
}
if (auth_status) *auth_status = my_status;
free(old_mechlist);
}
/* start compression if requested and both client/server support it */
if (config_getswitch(IMAPOPT_PROXY_COMPRESS) && ret &&
CAPA(ret, CAPA_COMPRESS) &&
prot->compress_cmd.cmd &&
do_compress(ret, &prot->compress_cmd)) {
syslog(LOG_ERR, "couldn't enable compression on backend server");
goto error;
}
return ret;
error:
forget_capabilities(ret);
if (ret->in) {
prot_free(ret->in);
ret->in = NULL;
}
if (ret->out) {
prot_free(ret->out);
ret->out = NULL;
}
if (sock >= 0)
close(sock);
if (ret->saslconn) {
sasl_dispose(&ret->saslconn);
ret->saslconn = NULL;
}
if (!ret_backend)
free(ret);
return NULL;
}
EXPORTED int backend_ping(struct backend *s)
{
char buf[1024];
if (!s || !s->prot->ping_cmd.cmd) return 0;
if (s->sock == -1) return -1; /* Disconnected Socket */
prot_printf(s->out, "%s\r\n", s->prot->ping_cmd.cmd);
prot_flush(s->out);
for (;;) {
if (!prot_fgets(buf, sizeof(buf), s->in)) {
/* connection closed? */
return -1;
} else if (s->prot->ping_cmd.unsol &&
!strncmp(s->prot->ping_cmd.unsol, buf,
strlen(s->prot->ping_cmd.unsol))) {
/* unsolicited response */
continue;
} else {
/* success/fail response */
return strncmp(s->prot->ping_cmd.ok, buf,
strlen(s->prot->ping_cmd.ok));
}
}
}
EXPORTED void backend_disconnect(struct backend *s)
{
char buf[1024];
if (!s || s->sock == -1) return;
if (!prot_error(s->in)) {
if (s->prot->logout_cmd.cmd) {
prot_printf(s->out, "%s\r\n", s->prot->logout_cmd.cmd);
prot_flush(s->out);
for (;;) {
if (!prot_fgets(buf, sizeof(buf), s->in)) {
/* connection closed? */
break;
} else if (s->prot->logout_cmd.unsol &&
!strncmp(s->prot->logout_cmd.unsol, buf,
strlen(s->prot->logout_cmd.unsol))) {
/* unsolicited response */
continue;
} else {
/* success/fail response -- don't care either way */
break;
}
}
}
}
/* Flush the incoming buffer */
prot_NONBLOCK(s->in);
prot_fill(s->in);
#ifdef HAVE_SSL
/* Free tlsconn */
if (s->tlsconn) {
tls_reset_servertls(&s->tlsconn);
s->tlsconn = NULL;
}
#endif /* HAVE_SSL */
/* close/free socket & prot layer */
cyrus_close_sock(s->sock);
s->sock = -1;
prot_free(s->in);
prot_free(s->out);
s->in = s->out = NULL;
/* Free saslconn */
if(s->saslconn) {
sasl_dispose(&(s->saslconn));
s->saslconn = NULL;
}
/* free last_result buffer */
buf_free(&s->last_result);
forget_capabilities(s);
}
diff --git a/imap/cyr_sequence.c b/imap/cyr_sequence.c
index 7804b5c89..3fcdedd32 100644
--- a/imap/cyr_sequence.c
+++ b/imap/cyr_sequence.c
@@ -1,196 +1,195 @@
/* cyr_sequence.c -- manipulate sequences
*
* Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* $Id: cyr_dbtool.c,v 1.8 2010/01/06 17:01:31 murch Exp $
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <ctype.h>
#include <syslog.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include "sequence.h"
-#include "exitcodes.h"
#include "imap/imap_err.h"
#include "global.h"
#include "util.h"
static void usage(const char *name)
{
fprintf(stderr, "Usage: %s [-C altconfig] [-m maxval] command sequence [args]\n", name);
fprintf(stderr, "\n");
fprintf(stderr, " - parsed => dump a parsed view of the list structure\n");
fprintf(stderr, " - compress => dump a compressed list\n");
fprintf(stderr, " - ismember [num...] => is num in the list for each num\n");
fprintf(stderr, " - members => all list members in order\n");
fprintf(stderr, " - create [-s] [items] => generate a new list from the items\n");
fprintf(stderr, " - prefix numbers with '~' for remove\n");
exit(-1);
}
int main(int argc, char *argv[])
{
const char *alt_config = NULL;
unsigned maxval = 0;
int flags = SEQ_MERGE;
struct seqset *seq = NULL;
int opt;
unsigned num;
char *res;
const char *origlist = NULL;
while ((opt = getopt(argc, argv, "C:m:o:s")) != EOF) {
switch (opt) {
case 'C': /* alt config file */
alt_config = optarg;
break;
case 'm': /* maxval */
parseuint32(optarg, NULL, &maxval);
break;
case 'o':
origlist = optarg;
break;
case 's':
flags = SEQ_SPARSE;
}
}
if ((argc - optind) < 1) usage(argv[0]);
cyrus_init(alt_config, "cyr_sequence", 0, 0);
/* special case */
if (!strcmp(argv[optind], "create")) {
int i;
seq = seqset_init(maxval, flags);
for (i = optind + 1; i < argc; i++) {
char *ptr = argv[i];
int isadd = 1;
if (*ptr == '~') {
isadd = 0;
ptr++;
}
if (parseuint32(ptr, NULL, &num))
printf("%s NAN\n", argv[i]);
else
seqset_add(seq, num, isadd);
}
if (origlist) {
unsigned oldmax = seq_lastnum(origlist, NULL);
if (oldmax > maxval) {
struct seqset *origseq = seqset_parse(origlist, NULL, oldmax);
unsigned val;
for (val = maxval + 1; val <= oldmax; val++)
seqset_add(seq, val, seqset_ismember(origseq, val));
seqset_free(origseq);
}
}
res = seqset_cstring(seq);
printf("%s\n", res);
free(res);
}
else if (!strcmp(argv[optind], "parsed")) {
unsigned i;
seq = seqset_parse(argv[optind+1], NULL, maxval);
printf("Sections: " SIZE_T_FMT "\n", seq->len);
for (i = 0; i < seq->len; i++) {
if (seq->set[i].high == UINT_MAX)
printf(" [%u, *]\n", seq->set[i].low);
else
printf(" [%u, %u]\n", seq->set[i].low, seq->set[i].high);
}
}
else if (!strcmp(argv[optind], "compress")) {
seq = seqset_parse(argv[optind+1], NULL, maxval);
res = seqset_cstring(seq);
printf("%s\n", res);
free(res);
}
else if (!strcmp(argv[optind], "members")) {
seq = seqset_parse(argv[optind+1], NULL, maxval);
while ((num = seqset_getnext(seq))) {
printf("%u\n", num);
}
}
else if (!strcmp(argv[optind], "join")) {
struct seqset *seq2;
seq = seqset_parse(argv[optind+1], NULL, maxval);
seq2 = seqset_parse(argv[optind+2], NULL, maxval);
seqset_join(seq, seq2);
res = seqset_cstring(seq);
printf("%s\n", res);
free(res);
}
else if (!strcmp(argv[optind], "ismember")) {
int i;
seq = seqset_parse(argv[optind+1], NULL, maxval);
for (i = optind + 2; i < argc; i++) {
if (parseuint32(argv[i], NULL, &num))
printf("%s NAN\n", argv[i]);
else
printf("%d %s\n", num, seqset_ismember(seq, num) ? "Yes" : "No");
}
}
else {
printf("Unknown command %s", argv[optind]);
}
seqset_free(seq);
cyrus_done();
return 0;
}
diff --git a/imap/cyr_synclog.c b/imap/cyr_synclog.c
index a471544bc..973b931c8 100644
--- a/imap/cyr_synclog.c
+++ b/imap/cyr_synclog.c
@@ -1,174 +1,175 @@
/* cyr_synclog.c -- add a line to the sync log file for replication
*
* Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* $Id: cyr_synclog.c,v 1.7 2010/01/06 17:01:31 murch Exp $
*
* Originally written by Bron Gondwana <brong@fastmail.fm>
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
+#include "exitcodes.h"
#include "global.h"
#include "sync_log.h"
#include "util.h"
#include "xmalloc.h"
int main(int argc, char *argv[])
{
char *alt_config = NULL;
char cmd = '\0';
char opt;
if ((geteuid()) == 0 && (become_cyrus(/*is_master*/0) != 0)) {
fatal("must run as the Cyrus user", EC_USAGE);
}
while ((opt = getopt(argc, argv, "C:uUvmMacqnsb")) != EOF) {
switch (opt) {
case 'C': /* alt config file */
alt_config = optarg;
break;
case 'u': /* User */
cmd = 'u';
break;
case 'U': /* UnUser */
cmd = 'U';
break;
case 'v': /* sieVe */
cmd = 'v';
break;
case 'm': /* Mailbox */
cmd = 'm';
break;
case 'M': /* UnMailbox */
cmd = 'M';
break;
case 'a': /* Append */
cmd = 'a';
break;
case 'c': /* aCl */
cmd = 'c';
break;
case 'q': /* Quota */
cmd = 'q';
break;
case 'n': /* aNnotation */
cmd = 'n';
break;
case 's': /* Seen */
cmd = 's';
break;
case 'b': /* suBscription */
cmd = 'b';
break;
}
}
if((argc - optind) < 1) {
fprintf(stderr, "Usage: %s [-C altconfig] [-{type}] value\n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "\n");
fprintf(stderr, "types:\n");
fprintf(stderr, " -u USER\n");
fprintf(stderr, " -U UNUSER\n");
fprintf(stderr, " -v SIEVE\n");
fprintf(stderr, " -m MAILBOX\n");
fprintf(stderr, " -M UNMAILBOX\n");
fprintf(stderr, " -q QUOTA\n");
fprintf(stderr, " -n ANNOTATION\n");
fprintf(stderr, " -s SEEN\n");
fprintf(stderr, " -b SUBSCRIPTION\n");
fprintf(stderr, "\n");
fprintf(stderr,
"You may omit the type flag and just specify a complete log line\n");
exit(-1);
}
cyrus_init(alt_config, "cyr_synclog", 0, 0);
sync_log_init();
switch(cmd) {
case 'u': /* User */
sync_log_user(argv[optind]);
break;
case 'U': /* UnUser */
sync_log_unuser(argv[optind]);
break;
case 'v': /* sieVe */
sync_log_sieve(argv[optind]);
break;
case 'm': /* Mailbox */
sync_log_mailbox(argv[optind]);
break;
case 'M': /* UnMailbox */
sync_log_unmailbox(argv[optind]);
break;
case 'q': /* Quota */
sync_log_quota(argv[optind]);
break;
case 'n': /* aNnotation */
sync_log_annotation(argv[optind]);
break;
case 's': /* Seen */
sync_log_seen(argv[optind], argv[optind+1]);
break;
case 'b': /* suBscription */
sync_log_subscribe(argv[optind], argv[optind+1]);
break;
default:
/* just as is! */
sync_log(argv[optind]);
break;
}
sync_log_done();
cyrus_done();
return 0;
}
diff --git a/imap/dlist.c b/imap/dlist.c
index 5b1f7e07e..a41b0f687 100644
--- a/imap/dlist.c
+++ b/imap/dlist.c
@@ -1,1207 +1,1206 @@
/* dlist.c - list protocol for dump and sync
*
* Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* $Id: sync_support.c,v 1.25 2010/01/06 17:01:41 murch Exp $
*/
#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/imap_err.h"
#include "mailbox.h"
#include "quota.h"
#include "xmalloc.h"
#include "seen.h"
#include "mboxname.h"
#include "map.h"
#include "imapd.h"
#include "message.h"
#include "util.h"
#include "prot.h"
#include "dlist.h"
/* Parse routines */
static const char *lastkey = NULL;
static void printfile(struct protstream *out, const struct dlist *dl)
{
struct stat sbuf;
FILE *f;
unsigned long size;
struct message_guid guid2;
const char *msg_base = NULL;
size_t msg_len = 0;
assert(dlist_isfile(dl));
f = fopen(dl->sval, "r");
if (!f) {
syslog(LOG_ERR, "IOERROR: Failed to read file %s", dl->sval);
prot_printf(out, "NIL");
return;
}
if (fstat(fileno(f), &sbuf) == -1) {
syslog(LOG_ERR, "IOERROR: Failed to stat file %s", dl->sval);
prot_printf(out, "NIL");
fclose(f);
return;
}
size = sbuf.st_size;
if (size != dl->nval) {
syslog(LOG_ERR, "IOERROR: Size mismatch %s (%lu != " MODSEQ_FMT ")",
dl->sval, size, dl->nval);
prot_printf(out, "NIL");
fclose(f);
return;
}
map_refresh(fileno(f), 1, &msg_base, &msg_len, sbuf.st_size,
"new message", 0);
message_guid_generate(&guid2, msg_base, msg_len);
if (!message_guid_equal(&guid2, dl->gval)) {
syslog(LOG_ERR, "IOERROR: GUID mismatch %s",
dl->sval);
prot_printf(out, "NIL");
fclose(f);
map_free(&msg_base, &msg_len);
return;
}
prot_printf(out, "%%{");
prot_printastring(out, dl->part);
prot_printf(out, " ");
prot_printastring(out, message_guid_encode(dl->gval));
prot_printf(out, " %lu}\r\n", size);
prot_write(out, msg_base, msg_len);
fclose(f);
map_free(&msg_base, &msg_len);
}
/* XXX - these two functions should be out in append.c or reserve.c
* or something more general */
EXPORTED const char *dlist_reserve_path(const char *part, struct message_guid *guid)
{
static char buf[MAX_MAILBOX_PATH];
snprintf(buf, MAX_MAILBOX_PATH, "%s/sync./%lu/%s",
config_partitiondir(part), (unsigned long)getpid(),
message_guid_encode(guid));
/* gotta make sure we can create files */
if (cyrus_mkdir(buf, 0755)) {
/* it's going to fail later, but at least this will help */
syslog(LOG_ERR, "IOERROR: failed to create %s/%lu/ for reserve: %m",
config_partitiondir(part), (unsigned long)getpid());
}
return buf;
}
static int reservefile(struct protstream *in, const char *part,
struct message_guid *guid, unsigned long size,
const char **fname)
{
FILE *file;
char buf[8192+1];
int r = 0, n;
/* XXX - write to a temporary file then move in to place! */
*fname = dlist_reserve_path(part, guid);
/* remove any duplicates if they're still here */
unlink(*fname);
file = fopen(*fname, "w+");
if (!file) {
syslog(LOG_ERR, "IOERROR: failed to upload file %s", message_guid_encode(guid));
r = IMAP_IOERROR;
/* Note: we still read the file's data from the wire,
* to avoid losing protocol sync */
}
/* XXX - calculate sha1 on the fly? */
while (size) {
n = prot_read(in, buf, size > 8192 ? 8192 : size);
if (!n) {
syslog(LOG_ERR,
"IOERROR: reading message: unexpected end of file");
r = IMAP_IOERROR;
break;
}
size -= n;
if (!r) fwrite(buf, 1, n, file);
}
if (r)
goto error;
/* Make sure that message flushed to disk just incase mmap has problems */
fflush(file);
if (ferror(file)) {
r = IMAP_IOERROR;
goto error;
}
if (fsync(fileno(file)) < 0) {
r = IMAP_IOERROR;
goto error;
}
fclose(file);
return 0;
error:
if (file) {
fclose(file);
unlink(*fname);
*fname = NULL;
}
return r;
}
/* DLIST STUFF */
EXPORTED void dlist_stitch(struct dlist *parent, struct dlist *child)
{
assert(!child->next);
if (parent->tail) {
parent->tail->next = child;
parent->tail = child;
}
else {
parent->head = parent->tail = child;
}
}
void dlist_unstitch(struct dlist *parent, struct dlist *child)
{
struct dlist *prev = NULL;
struct dlist *replace = NULL;
/* find old record */
for (replace = parent->head; replace; replace = replace->next) {
if (replace == child) break;
prev = replace;
}
assert(replace);
if (prev) prev->next = child->next;
else (parent->head) = child->next;
if (parent->tail == child) parent->tail = prev;
child->next = NULL;
}
static struct dlist *dlist_child(struct dlist *dl, const char *name)
{
struct dlist *i = xzmalloc(sizeof(struct dlist));
if (name) i->name = xstrdup(name);
i->type = DL_NIL;
if (dl)
dlist_stitch(dl, i);
return i;
}
static void _dlist_free_children(struct dlist *dl)
{
struct dlist *next;
struct dlist *i;
if (!dl) return;
i = dl->head;
while (i) {
next = i->next;
dlist_free(&i);
i = next;
}
dl->head = dl->tail = NULL;
}
static void _dlist_clean(struct dlist *dl)
{
if (!dl) return;
/* remove any children */
_dlist_free_children(dl);
/* clean out values */
free(dl->part);
dl->part = NULL;
free(dl->sval);
dl->sval = NULL;
free(dl->gval);
dl->gval = NULL;
dl->nval = 0;
}
void dlist_makeatom(struct dlist *dl, const char *val)
{
if (!dl) return;
_dlist_clean(dl);
if (val) {
dl->type = DL_ATOM;
dl->sval = xstrdup(val);
dl->nval = strlen(val);
}
else
dl->type = DL_NIL;
}
void dlist_makeflag(struct dlist *dl, const char *val)
{
if (!dl) return;
_dlist_clean(dl);
if (val) {
dl->type = DL_FLAG;
dl->sval = xstrdup(val);
dl->nval = strlen(val);
}
else
dl->type = DL_NIL;
}
void dlist_makenum32(struct dlist *dl, uint32_t val)
{
if (!dl) return;
_dlist_clean(dl);
dl->type = DL_NUM;
dl->nval = val;
}
void dlist_makenum64(struct dlist *dl, bit64 val)
{
if (!dl) return;
_dlist_clean(dl);
dl->type = DL_NUM;
dl->nval = val;
}
void dlist_makedate(struct dlist *dl, time_t val)
{
if (!dl) return;
_dlist_clean(dl);
dl->type = DL_DATE;
dl->nval = val;
}
void dlist_makehex64(struct dlist *dl, bit64 val)
{
if (!dl) return;
_dlist_clean(dl);
dl->type = DL_HEX;
dl->nval = val;
}
void dlist_makeguid(struct dlist *dl, struct message_guid *guid)
{
if (!dl) return;
_dlist_clean(dl);
if (guid) {
dl->type = DL_GUID,
dl->gval = xzmalloc(sizeof(struct message_guid));
message_guid_copy(dl->gval, guid);
}
else
dl->type = DL_NIL;
}
void dlist_makefile(struct dlist *dl,
const char *part, struct message_guid *guid,
unsigned long size, const char *fname)
{
if (!dl) return;
_dlist_clean(dl);
if (part && guid && fname) {
dl->type = DL_FILE;
dl->gval = xzmalloc(sizeof(struct message_guid));
message_guid_copy(dl->gval, guid);
dl->sval = xstrdup(fname);
dl->nval = size;
dl->part = xstrdup(part);
}
else
dl->type = DL_NIL;
}
void dlist_makemap(struct dlist *dl, const char *val, size_t len)
{
if (!dl) return;
_dlist_clean(dl);
if (val) {
dl->type = DL_BUF;
/* WARNING - DO NOT replace this with xstrndup - the
* data may be binary, and xstrndup does not copy
* binary data correctly - but we still want to NULL
* terminate for non-binary data */
dl->sval = xmalloc(len+1);
memcpy(dl->sval, val, len);
dl->sval[len] = '\0'; /* make it string safe too */
dl->nval = len;
}
else
dl->type = DL_NIL;
}
EXPORTED struct dlist *dlist_newkvlist(struct dlist *parent, const char *name)
{
struct dlist *dl = dlist_child(parent, name);
dl->type = DL_KVLIST;
return dl;
}
EXPORTED struct dlist *dlist_newlist(struct dlist *parent, const char *name)
{
struct dlist *dl = dlist_child(parent, name);
dl->type = DL_ATOMLIST;
return dl;
}
struct dlist *dlist_newpklist(struct dlist *parent, const char *name)
{
struct dlist *dl = dlist_child(parent, name);
dl->type = DL_ATOMLIST;
dl->nval = 1;
return dl;
}
EXPORTED struct dlist *dlist_setatom(struct dlist *parent, const char *name, const char *val)
{
struct dlist *dl = dlist_child(parent, name);
dlist_makeatom(dl, val);
return dl;
}
EXPORTED struct dlist *dlist_setflag(struct dlist *parent, const char *name, const char *val)
{
struct dlist *dl = dlist_child(parent, name);
dlist_makeflag(dl, val);
return dl;
}
EXPORTED struct dlist *dlist_setnum64(struct dlist *parent, const char *name, bit64 val)
{
struct dlist *dl = dlist_child(parent, name);
dlist_makenum64(dl, val);
return dl;
}
EXPORTED struct dlist *dlist_setnum32(struct dlist *parent, const char *name, uint32_t val)
{
struct dlist *dl = dlist_child(parent, name);
dlist_makenum32(dl, val);
return dl;
}
EXPORTED struct dlist *dlist_setdate(struct dlist *parent, const char *name, time_t val)
{
struct dlist *dl = dlist_child(parent, name);
dlist_makedate(dl, val);
return dl;
}
struct dlist *dlist_sethex64(struct dlist *parent, const char *name, bit64 val)
{
struct dlist *dl = dlist_child(parent, name);
dlist_makehex64(dl, val);
return dl;
}
EXPORTED struct dlist *dlist_setmap(struct dlist *parent, const char *name,
const char *val, size_t len)
{
struct dlist *dl = dlist_child(parent, name);
dlist_makemap(dl, val, len);
return dl;
}
EXPORTED struct dlist *dlist_setguid(struct dlist *parent, const char *name,
struct message_guid *guid)
{
struct dlist *dl = dlist_child(parent, name);
dlist_makeguid(dl, guid);
return dl;
}
EXPORTED struct dlist *dlist_setfile(struct dlist *parent, const char *name,
const char *part, struct message_guid *guid,
size_t size, const char *fname)
{
struct dlist *dl = dlist_child(parent, name);
dlist_makefile(dl, part, guid, size, fname);
return dl;
}
static struct dlist *dlist_updatechild(struct dlist *parent, const char *name)
{
struct dlist *dl = dlist_getchild(parent, name);
if (!dl) dl = dlist_child(parent, name);
return dl;
}
struct dlist *dlist_updateatom(struct dlist *parent, const char *name, const char *val)
{
struct dlist *dl = dlist_updatechild(parent, name);
dlist_makeatom(dl, val);
return dl;
}
struct dlist *dlist_updateflag(struct dlist *parent, const char *name, const char *val)
{
struct dlist *dl = dlist_updatechild(parent, name);
dlist_makeflag(dl, val);
return dl;
}
struct dlist *dlist_updatenum64(struct dlist *parent, const char *name, bit64 val)
{
struct dlist *dl = dlist_updatechild(parent, name);
dlist_makenum64(dl, val);
return dl;
}
struct dlist *dlist_updatenum32(struct dlist *parent, const char *name, uint32_t val)
{
struct dlist *dl = dlist_updatechild(parent, name);
dlist_makenum32(dl, val);
return dl;
}
struct dlist *dlist_updatedate(struct dlist *parent, const char *name, time_t val)
{
struct dlist *dl = dlist_updatechild(parent, name);
dlist_makedate(dl, val);
return dl;
}
struct dlist *dlist_updatehex64(struct dlist *parent, const char *name, bit64 val)
{
struct dlist *dl = dlist_updatechild(parent, name);
dlist_makehex64(dl, val);
return dl;
}
struct dlist *dlist_updatemap(struct dlist *parent, const char *name,
const char *val, size_t len)
{
struct dlist *dl = dlist_updatechild(parent, name);
dlist_makemap(dl, val, len);
return dl;
}
struct dlist *dlist_updateguid(struct dlist *parent, const char *name,
struct message_guid *guid)
{
struct dlist *dl = dlist_updatechild(parent, name);
dlist_makeguid(dl, guid);
return dl;
}
struct dlist *dlist_updatefile(struct dlist *parent, const char *name,
const char *part, struct message_guid *guid,
size_t size, const char *fname)
{
struct dlist *dl = dlist_updatechild(parent, name);
dlist_makefile(dl, part, guid, size, fname);
return dl;
}
EXPORTED void dlist_print(const struct dlist *dl, int printkeys,
struct protstream *out)
{
struct dlist *di;
if (printkeys)
prot_printf(out, "%s ", dl->name);
switch (dl->type) {
case DL_NIL:
prot_printf(out, "NIL");
break;
case DL_ATOM:
prot_printastring(out, dl->sval);
break;
case DL_FLAG:
prot_printf(out, "%s", dl->sval);
break;
case DL_NUM:
case DL_DATE: /* for now, we will format it later */
prot_printf(out, "%llu", dl->nval);
break;
case DL_FILE:
printfile(out, dl);
break;
case DL_BUF:
prot_printliteral(out, dl->sval, dl->nval);
break;
case DL_GUID:
prot_printf(out, "%s", message_guid_encode(dl->gval));
break;
case DL_HEX:
{
char buf[17];
snprintf(buf, 17, "%016llx", dl->nval);
prot_printf(out, "%s", buf);
}
break;
case DL_KVLIST:
prot_printf(out, "%%(");
for (di = dl->head; di; di = di->next) {
dlist_print(di, 1, out);
if (di->next) {
prot_printf(out, " ");
}
}
prot_printf(out, ")");
break;
case DL_ATOMLIST:
prot_printf(out, "(");
for (di = dl->head; di; di = di->next) {
dlist_print(di, dl->nval, out);
if (di->next)
prot_printf(out, " ");
}
prot_printf(out, ")");
break;
}
}
EXPORTED void dlist_printbuf(const struct dlist *dl, int printkeys, struct buf *outbuf)
{
struct protstream *outstream;
outstream = prot_writebuf(outbuf);
dlist_print(dl, printkeys, outstream);
prot_flush(outstream);
prot_free(outstream);
}
EXPORTED void dlist_free(struct dlist **dlp)
{
if (!*dlp) return;
_dlist_clean(*dlp);
free((*dlp)->name);
free(*dlp);
*dlp = NULL;
}
static char next_nonspace(struct protstream *in, char c)
{
while (Uisspace(c)) c = prot_getc(in);
return c;
}
EXPORTED char dlist_parse(struct dlist **dlp, int parsekey, struct protstream *in)
{
struct dlist *dl = NULL;
static struct buf kbuf;
static struct buf vbuf;
int c;
/* handle the key if wanted */
if (parsekey) {
c = getword(in, &kbuf);
c = next_nonspace(in, c);
}
else {
buf_setcstr(&kbuf, "");
c = prot_getc(in);
}
/* connection dropped? */
if (c == EOF) goto fail;
/* check what sort of value we have */
if (c == '(') {
dl = dlist_newlist(NULL, kbuf.s);
c = next_nonspace(in, ' ');
while (c != ')') {
struct dlist *di = NULL;
prot_ungetc(c, in);
c = dlist_parse(&di, 0, in);
if (di) dlist_stitch(dl, di);
c = next_nonspace(in, c);
if (c == EOF) goto fail;
}
c = prot_getc(in);
}
else if (c == '%') {
/* no whitespace allowed here */
c = prot_getc(in);
if (c == '(') {
dl = dlist_newkvlist(NULL, kbuf.s);
c = next_nonspace(in, ' ');
while (c != ')') {
struct dlist *di = NULL;
prot_ungetc(c, in);
c = dlist_parse(&di, 1, in);
if (di) dlist_stitch(dl, di);
c = next_nonspace(in, c);
if (c == EOF) goto fail;
}
}
else if (c == '{') {
struct message_guid tmp_guid;
static struct buf pbuf, gbuf;
unsigned size = 0;
const char *fname;
c = getastring(in, NULL, &pbuf);
if (c != ' ') goto fail;
c = getastring(in, NULL, &gbuf);
if (c != ' ') goto fail;
c = getuint32(in, &size);
if (c != '}') goto fail;
c = prot_getc(in);
if (c == '\r') c = prot_getc(in);
if (c != '\n') goto fail;
if (!message_guid_decode(&tmp_guid, gbuf.s)) goto fail;
if (reservefile(in, pbuf.s, &tmp_guid, size, &fname)) goto fail;
dl = dlist_setfile(NULL, kbuf.s, pbuf.s, &tmp_guid, size, fname);
/* file literal */
}
else {
/* unknown percent type */
goto fail;
}
c = prot_getc(in);
}
else if (c == '{') {
prot_ungetc(c, in);
/* could be binary in a literal */
c = getbastring(in, NULL, &vbuf);
dl = dlist_setmap(NULL, kbuf.s, vbuf.s, vbuf.len);
}
else if (c == '\\') { /* special case for flags */
prot_ungetc(c, in);
c = getastring(in, NULL, &vbuf);
dl = dlist_setflag(NULL, kbuf.s, vbuf.s);
}
else {
prot_ungetc(c, in);
c = getnastring(in, NULL, &vbuf);
dl = dlist_setatom(NULL, kbuf.s, vbuf.s);
}
/* success */
*dlp = dl;
return c;
fail:
dlist_free(&dl);
return EOF;
}
char dlist_parse_asatomlist(struct dlist **dlp, int parsekey,
struct protstream *in)
{
char c = dlist_parse(dlp, parsekey, in);
/* make a list with one item */
if (*dlp && !dlist_isatomlist(*dlp)) {
struct dlist *tmp = dlist_newlist(NULL, "");
dlist_stitch(tmp, *dlp);
*dlp = tmp;
}
return c;
}
EXPORTED int dlist_parsemap(struct dlist **dlp, int parsekey,
const char *base, unsigned len)
{
struct protstream *stream;
char c;
struct dlist *dl = NULL;
stream = prot_readmap(base, len);
prot_setisclient(stream, 1); /* don't sync literals */
c = dlist_parse(&dl, parsekey, stream);
prot_free(stream);
if (c != EOF) {
dlist_free(&dl);
return IMAP_IOERROR; /* failed to slurp entire buffer */
}
*dlp = dl;
return 0;
}
HIDDEN struct dlist *dlist_getchild(struct dlist *dl, const char *name)
{
struct dlist *i;
if (!dl) return NULL;
for (i = dl->head; i; i = i->next) {
if (i->name && !strcmp(name, i->name))
return i;
}
lastkey = name;
return NULL;
}
EXPORTED struct dlist *dlist_getchildn(struct dlist *dl, int num)
{
struct dlist *i;
if (!dl) return NULL;
for (i = dl->head; i && num; i = i->next)
num--;
return i;
}
/* duplicate the parent list as a new list, and then move @num
* of the children from the parent onto the new list */
EXPORTED struct dlist *dlist_splice(struct dlist *dl, int num)
{
struct dlist *ret = dlist_newlist(NULL, dl->name);
/* clone exact type */
ret->type = dl->type;
ret->nval = dl->nval;
if (num > 0) {
struct dlist *end = dlist_getchildn(dl, num - 1);
/* take the start of the list */
ret->head = dl->head;
/* leave the end (if any) */
if (end) {
ret->tail = end;
dl->head = end->next;
end->next = NULL;
}
else {
ret->tail = dl->tail;
dl->head = NULL;
dl->tail = NULL;
}
}
return ret;
}
struct dlist *dlist_getkvchild_bykey(struct dlist *dl,
const char *key, const char *val)
{
struct dlist *i;
struct dlist *tmp;
if (!dl) return NULL;
for (i = dl->head; i; i = i->next) {
tmp = dlist_getchild(i, key);
if (tmp && !strcmp(tmp->sval, val))
return i;
}
return NULL;
}
int dlist_toatom(struct dlist *dl, const char **valp)
{
const char *str;
size_t len;
/* tomap always adds a trailing \0 */
if (!dlist_tomap(dl, &str, &len))
return 0;
/* got NULLs? */
if (dl->type == DL_BUF && strlen(str) != len)
return 0;
if (valp) *valp = str;
return 1;
}
HIDDEN int dlist_tomap(struct dlist *dl, const char **valp, size_t *lenp)
{
char tmp[30];
if (!dl) return 0;
switch (dl->type) {
case DL_NUM:
case DL_DATE:
snprintf(tmp, 30, "%llu", dl->nval);
dlist_makeatom(dl, tmp);
break;
case DL_HEX:
snprintf(tmp, 30, "%016llx", dl->nval);
dlist_makeatom(dl, tmp);
break;
case DL_GUID:
dlist_makeatom(dl, message_guid_encode(dl->gval));
break;
case DL_ATOM:
case DL_FLAG:
case DL_BUF:
break;
default:
return 0;
}
if (valp) *valp = dl->sval;
if (lenp) *lenp = dl->nval;
return 1;
}
/* ensure value is exactly one number */
static int dlist_tonum64(struct dlist *dl, bit64 *valp)
{
const char *end;
bit64 newval;
if (!dl) return 0;
switch (dl->type) {
case DL_ATOM:
case DL_BUF:
if (parsenum(dl->sval, &end, dl->nval, &newval))
return 0;
if (end - dl->sval != (int)dl->nval)
return 0;
/* successfully parsed - switch to a numeric value */
dlist_makenum64(dl, newval);
break;
case DL_NUM:
case DL_HEX:
case DL_DATE:
break;
default:
return 0;
}
if (valp) *valp = dl->nval;
return 1;
}
EXPORTED int dlist_tonum32(struct dlist *dl, uint32_t *valp)
{
bit64 v;
if (dlist_tonum64(dl, &v)) {
if (valp) *valp = (uint32_t)v;
return 1;
}
return 0;
}
int dlist_todate(struct dlist *dl, time_t *valp)
{
bit64 v;
if (dlist_tonum64(dl, &v)) {
if (valp) *valp = (time_t)v;
dl->type = DL_DATE;
return 1;
}
return 0;
}
static int dlist_tohex64(struct dlist *dl, bit64 *valp)
{
const char *end = NULL;
bit64 newval;
if (!dl) return 0;
switch (dl->type) {
case DL_ATOM:
case DL_BUF:
if (parsehex(dl->sval, &end, dl->nval, &newval))
return 0;
if (end - dl->sval != (int)dl->nval)
return 0;
/* successfully parsed - switch to a numeric value */
dlist_makehex64(dl, newval);
break;
case DL_NUM:
case DL_HEX:
case DL_DATE:
dl->type = DL_HEX;
break;
default:
return 0;
}
if (valp) *valp = dl->nval;
return 1;
}
EXPORTED int dlist_toguid(struct dlist *dl, struct message_guid **valp)
{
struct message_guid tmpguid;
if (!dl) return 0;
switch (dl->type) {
case DL_ATOM:
case DL_BUF:
if (dl->nval != 40)
return 0;
if (!message_guid_decode(&tmpguid, dl->sval))
return 0;
/* successfully parsed - switch to guid value */
dlist_makeguid(dl, &tmpguid);
break;
case DL_GUID:
break;
default:
return 0;
}
if (valp) *valp = dl->gval;
return 1;
}
EXPORTED int dlist_tofile(struct dlist *dl,
const char **partp, struct message_guid **guidp,
unsigned long *sizep, const char **fnamep)
{
if (!dlist_isfile(dl)) return 0;
if (guidp) *guidp = dl->gval;
if (sizep) *sizep = dl->nval;
if (fnamep) *fnamep = dl->sval;
if (partp) *partp = dl->part;
return 1;
}
int dlist_isatomlist(const struct dlist *dl)
{
if (!dl) return 0;
return (dl->type == DL_ATOMLIST);
}
int dlist_iskvlist(const struct dlist *dl)
{
if (!dl) return 0;
return (dl->type == DL_KVLIST);
}
int dlist_isfile(const struct dlist *dl)
{
if (!dl) return 0;
return (dl->type == DL_FILE);
}
/* XXX - these ones aren't const, because they can change
* things... */
int dlist_isnum(struct dlist *dl)
{
bit64 tmp;
if (!dl) return 0;
/* see if it can be parsed as a number */
return dlist_tonum64(dl, &tmp);
}
/* XXX - these ones aren't const, because they can change
* things... */
int dlist_ishex64(struct dlist *dl)
{
bit64 tmp;
if (!dl) return 0;
/* see if it can be parsed as a number */
return dlist_tohex64(dl, &tmp);
}
/* XXX - these ones aren't const, because they can change
* things... */
int dlist_isguid(struct dlist *dl)
{
struct message_guid *tmp = NULL;
if (!dl) return 0;
return dlist_toguid(dl, &tmp);
}
/* XXX - this stuff is all shitty, rationalise later */
EXPORTED bit64 dlist_num(struct dlist *dl)
{
bit64 v;
if (!dl) return 0;
if (dlist_tonum64(dl, &v))
return v;
return 0;
}
/* XXX - this stuff is all shitty, rationalise later */
HIDDEN const char *dlist_cstring(struct dlist *dl)
{
static char zerochar = '\0';
if (dl) {
const char *res = NULL;
dlist_toatom(dl, &res);
if (res) return res;
}
return &zerochar;
}
EXPORTED int dlist_getatom(struct dlist *parent, const char *name, const char **valp)
{
struct dlist *child = dlist_getchild(parent, name);
return dlist_toatom(child, valp);
}
EXPORTED int dlist_getnum32(struct dlist *parent, const char *name, uint32_t *valp)
{
struct dlist *child = dlist_getchild(parent, name);
return dlist_tonum32(child, valp);
}
EXPORTED int dlist_getnum64(struct dlist *parent, const char *name, bit64 *valp)
{
struct dlist *child = dlist_getchild(parent, name);
return dlist_tonum64(child, valp);
}
EXPORTED int dlist_getdate(struct dlist *parent, const char *name, time_t *valp)
{
struct dlist *child = dlist_getchild(parent, name);
return dlist_todate(child, valp);
}
int dlist_gethex64(struct dlist *parent, const char *name, bit64 *valp)
{
struct dlist *child = dlist_getchild(parent, name);
return dlist_tohex64(child, valp);
}
EXPORTED int dlist_getguid(struct dlist *parent, const char *name,
struct message_guid **valp)
{
struct dlist *child = dlist_getchild(parent, name);
return dlist_toguid(child, valp);
}
EXPORTED int dlist_getmap(struct dlist *parent, const char *name,
const char **valp, size_t *lenp)
{
struct dlist *child = dlist_getchild(parent, name);
return dlist_tomap(child, valp, lenp);
}
int dlist_getfile(struct dlist *parent, const char *name,
const char **partp,
struct message_guid **guidp,
unsigned long *sizep,
const char **fnamep)
{
struct dlist *child = dlist_getchild(parent, name);
return dlist_tofile(child, partp, guidp, sizep, fnamep);
}
EXPORTED int dlist_getlist(struct dlist *dl, const char *name, struct dlist **valp)
{
struct dlist *i = dlist_getchild(dl, name);
if (!i) return 0;
*valp = i;
return 1;
}
EXPORTED const char *dlist_lastkey(void)
{
return lastkey;
}
diff --git a/imap/mboxevent.c b/imap/mboxevent.c
index 5e0f777e0..db45821d8 100644
--- a/imap/mboxevent.c
+++ b/imap/mboxevent.c
@@ -1,1220 +1,1221 @@
/*
* Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Author: Sébastien Michel from Atos Worldline
*/
#include <config.h>
#ifdef ENABLE_MBOXEVENT
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <syslog.h>
#include <stdlib.h>
#include <jansson.h>
#include "assert.h"
+#include "exitcodes.h"
#include "imapurl.h"
#include "libconfig.h"
#include "times.h"
#include "xmalloc.h"
#include "map.h"
#include "mboxevent.h"
#include "mboxname.h"
#include "notify.h"
#define MESSAGE_EVENTS (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_EXPIRE|\
EVENT_MESSAGE_EXPUNGE|EVENT_MESSAGE_NEW|\
EVENT_MESSAGE_COPY|EVENT_MESSAGE_MOVE)
#define FLAGS_EVENTS (EVENT_FLAGS_SET|EVENT_FLAGS_CLEAR|EVENT_MESSAGE_READ|\
EVENT_MESSAGE_TRASH)
#define MAILBOX_EVENTS (EVENT_MAILBOX_CREATE|EVENT_MAILBOX_DELETE|\
EVENT_MAILBOX_RENAME|EVENT_MAILBOX_SUBSCRIBE|\
EVENT_MAILBOX_UNSUBSCRIBE)
#define QUOTA_EVENTS (EVENT_QUOTA_EXCEED|EVENT_QUOTA_WITHIN|EVENT_QUOTA_CHANGE)
#define FILL_STRING_PARAM(e,p,v) e->params[p].value = (uint64_t)v; \
e->params[p].type = EVENT_PARAM_STRING; \
e->params[p].filled = 1
#define FILL_ARRAY_PARAM(e,p,v) e->params[p].value = (uint64_t)v; \
e->params[p].type = EVENT_PARAM_ARRAY; \
e->params[p].filled = 1
#define FILL_UNSIGNED_PARAM(e,p,v) e->params[p].value = (uint64_t)v; \
e->params[p].type = EVENT_PARAM_INT; \
e->params[p].filled = 1
static const char *notifier = NULL;
static struct namespace namespace;
static strarray_t *excluded_flags;
static strarray_t *excluded_specialuse;
static int enable_subfolder = 1;
static int enabled_events = 0;
static unsigned long extra_params;
static struct mboxevent event_template =
{ 0,
/* ordered to optimize the parsing of the notification message */
{ { EVENT_TIMESTAMP, "timestamp", EVENT_PARAM_STRING, 0, 0 },
{ EVENT_SERVICE, "service", EVENT_PARAM_STRING, 0, 0 },
{ EVENT_SERVER_ADDRESS, "serverAddress", EVENT_PARAM_STRING, 0, 0 },
{ EVENT_CLIENT_ADDRESS, "clientAddress", EVENT_PARAM_STRING, 0, 0 },
{ EVENT_OLD_MAILBOX_ID, "oldMailboxID", EVENT_PARAM_STRING, 0, 0 },
{ EVENT_OLD_UIDSET, "vnd.cmu.oldUidset", EVENT_PARAM_STRING, 0, 0 },
{ EVENT_MAILBOX_ID, "mailboxID", EVENT_PARAM_STRING, 0, 0 },
{ EVENT_URI, "uri", EVENT_PARAM_STRING, 0, 0 },
{ EVENT_MODSEQ, "modseq", EVENT_PARAM_INT, 0, 0 },
{ EVENT_DISK_QUOTA, "diskQuota", EVENT_PARAM_INT, 0, 0 },
{ EVENT_DISK_USED, "diskUsed", EVENT_PARAM_INT, 0, 0 },
{ EVENT_MAX_MESSAGES, "maxMessages", EVENT_PARAM_INT, 0, 0 },
{ EVENT_MESSAGES, "messages", EVENT_PARAM_INT, 0, 0 },
{ EVENT_UNSEEN_MESSAGES, "vnd.cmu.unseenMessages", EVENT_PARAM_INT, 0, 0 },
{ EVENT_UIDNEXT, "uidnext", EVENT_PARAM_INT, 0, 0 },
{ EVENT_UIDSET, "uidset", EVENT_PARAM_STRING, 0, 0 },
{ EVENT_MIDSET, "vnd.cmu.midset", EVENT_PARAM_STRING, 0, 0 },
{ EVENT_FLAG_NAMES, "flagNames", EVENT_PARAM_STRING, 0, 0 },
{ EVENT_USER, "user", EVENT_PARAM_STRING, 0, 0 },
{ EVENT_MESSAGE_SIZE, "messageSize", EVENT_PARAM_INT, 0, 0 },
/* always at end to let the parser to easily truncate this part */
{ EVENT_BODYSTRUCTURE, "bodyStructure", EVENT_PARAM_STRING, 0, 0 },
{ EVENT_MESSAGE_CONTENT, "messageContent", EVENT_PARAM_STRING, 0, 0 }
},
STRARRAY_INITIALIZER, { 0, 0 }, NULL, STRARRAY_INITIALIZER, NULL, NULL, NULL
};
static char *json_formatter(enum event_type type, struct event_parameter params[]);
#ifndef NDEBUG
static int filled_params(enum event_type type, struct mboxevent *mboxevent);
#endif
static int mboxevent_expected_param(enum event_type type, enum event_param param);
EXPORTED void mboxevent_init(void)
{
const char *options;
int groups;
if (!(notifier = config_getstring(IMAPOPT_EVENT_NOTIFIER)))
return;
/* some don't want to notify events for some IMAP flags */
options = config_getstring(IMAPOPT_EVENT_EXCLUDE_FLAGS);
excluded_flags = strarray_split(options, NULL, 0);
/* some don't want to notify events on some folders (ie. Sent, Spam) */
/* identify those folders with IMAP SPECIAL-USE */
options = config_getstring(IMAPOPT_EVENT_EXCLUDE_SPECIALUSE);
excluded_specialuse = strarray_split(options, NULL, 0);
/* special meaning to disable event notification on all sub folders */
if (strarray_find_case(excluded_specialuse, "ALL", 0) >= 0)
enable_subfolder = 0;
/* get event types's extra parameters */
extra_params = config_getbitfield(IMAPOPT_EVENT_EXTRA_PARAMS);
/* groups of related events to turn on notification */
groups = config_getbitfield(IMAPOPT_EVENT_GROUPS);
if (groups & IMAP_ENUM_EVENT_GROUPS_MESSAGE)
enabled_events |= MESSAGE_EVENTS;
if (groups & IMAP_ENUM_EVENT_GROUPS_QUOTA)
enabled_events |= QUOTA_EVENTS;
if (groups & IMAP_ENUM_EVENT_GROUPS_FLAGS)
enabled_events |= FLAGS_EVENTS;
if (groups & IMAP_ENUM_EVENT_GROUPS_ACCESS)
enabled_events |= (EVENT_LOGIN|EVENT_LOGOUT);
if (groups & IMAP_ENUM_EVENT_GROUPS_MAILBOX)
enabled_events |= MAILBOX_EVENTS;
}
EXPORTED void mboxevent_setnamespace(struct namespace *n)
{
namespace = *n;
/* standardize IMAP URL format */
namespace.isadmin = 0;
}
static int mboxevent_enabled_for_mailbox(struct mailbox *mailbox)
{
int i = 0;
strarray_t *specialuse = NULL;
int r = 1;
if (!enable_subfolder && (mboxname_isusermailbox(mailbox->name, 1)) == NULL) {
return 0;
}
/* test if the mailbox has a special-use attribute in the exclude list */
if (strarray_size(excluded_specialuse) > 0) {
const char *attribute;
/* get info and set flags */
specialuse = strarray_split(mailbox->specialuse, NULL, 0);
for (i = 0; i < strarray_size(specialuse) ; i++) {
attribute = strarray_nth(specialuse, i);
if (strarray_find(excluded_specialuse, attribute, 0) >= 0) {
r = 0;
break;
}
}
}
strarray_free(specialuse);
return r;
}
EXPORTED struct mboxevent *mboxevent_new(enum event_type type)
{
struct mboxevent *mboxevent = NULL;
/* event notification is completely disabled */
if (!notifier)
return NULL;
/* the group to which belong the event is not enabled */
if (!(enabled_events & type))
return NULL;
mboxevent = xmalloc(sizeof(struct mboxevent));
memcpy(mboxevent, &event_template, sizeof(struct mboxevent));
mboxevent->type = type;
/* From RFC 5423:
* the time at which the event occurred that triggered the notification
* (...). This MAY be an approximate time.
*
* so it seems appropriate here */
if (mboxevent_expected_param(type, EVENT_TIMESTAMP))
gettimeofday(&mboxevent->timestamp, NULL);
return mboxevent;
}
struct mboxevent *mboxevent_enqueue(enum event_type type,
struct mboxevent **mboxevents)
{
struct mboxevent *mboxevent = NULL;
struct mboxevent *ptr;
if (!(mboxevent = mboxevent_new(type)))
return NULL;
if (mboxevents) {
if (*mboxevents == NULL)
*mboxevents = mboxevent;
else {
/* append the newly created event at end of the chained list */
ptr = *mboxevents;
while (ptr->next)
ptr = ptr->next;
ptr->next = mboxevent;
mboxevent->prev = ptr;
}
}
return mboxevent;
}
EXPORTED void mboxevent_free(struct mboxevent **mboxevent)
{
struct mboxevent *event = *mboxevent;
int i;
if (!event)
return;
seqset_free(event->uidset);
seqset_free(event->olduidset);
strarray_fini(&event->midset);
for (i = 0; i <= MAX_PARAM; i++) {
if (event->params[i].filled && event->params[i].type == EVENT_PARAM_STRING)
free((char *)event->params[i].value);
}
if (event->prev)
event->prev->next = event->next;
if (event->next)
event->next->prev = event->prev;
free(event);
*mboxevent = NULL;
}
void mboxevent_freequeue(struct mboxevent **mboxevent)
{
struct mboxevent *next, *event = *mboxevent;
if (!event)
return;
do {
next = event->next;
mboxevent_free(&event);
event = next;
}
while (event);
*mboxevent = NULL;
}
static int mboxevent_expected_param(enum event_type type, enum event_param param)
{
switch (param) {
case EVENT_BODYSTRUCTURE:
return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_BODYSTRUCTURE) &&
(type & (EVENT_MESSAGE_NEW|EVENT_MESSAGE_APPEND));
case EVENT_CLIENT_ADDRESS:
return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_CLIENTADDRESS) &&
(type & EVENT_LOGIN);
case EVENT_DISK_QUOTA:
return type & QUOTA_EVENTS;
case EVENT_DISK_USED:
return (type & (EVENT_QUOTA_EXCEED|EVENT_QUOTA_WITHIN) ||
/* quota usage is not known on event MessageNew, MessageAppend,
* MessageCopy and MessageExpunge.
* Thus, some code refactoring is needed to support diskUsed
* extra parameter */
((extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_DISKUSED) &&
(type & (EVENT_QUOTA_CHANGE))));
case EVENT_FLAG_NAMES:
return (type & (EVENT_FLAGS_SET|EVENT_FLAGS_CLEAR)) ||
((extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_FLAGNAMES) &&
(type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW)));
case EVENT_MAILBOX_ID:
return (type & MAILBOX_EVENTS);
case EVENT_MAX_MESSAGES:
return type & QUOTA_EVENTS;
case EVENT_MESSAGE_CONTENT:
return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_MESSAGECONTENT) &&
(type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW));
case EVENT_MESSAGE_SIZE:
return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_MESSAGESIZE) &&
(type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW));
case EVENT_MESSAGES:
if (type & (EVENT_QUOTA_EXCEED|EVENT_QUOTA_WITHIN))
return 1;
if (!(extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_MESSAGES))
return 0;
break;
case EVENT_MODSEQ:
if (!(extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_MODSEQ))
return 0;
break;
case EVENT_OLD_MAILBOX_ID:
return type & (EVENT_MESSAGE_COPY|EVENT_MESSAGE_MOVE|EVENT_MAILBOX_RENAME);
case EVENT_SERVER_ADDRESS:
return type & (EVENT_LOGIN|EVENT_LOGOUT);
case EVENT_SERVICE:
return extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_SERVICE;
case EVENT_TIMESTAMP:
return extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_TIMESTAMP;
case EVENT_UIDNEXT:
if (!(extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_UIDNEXT))
return 0;
break;
case EVENT_UIDSET:
if (type & (EVENT_MESSAGE_NEW|EVENT_MESSAGE_APPEND))
return 0;
break;
case EVENT_URI:
return 1;
case EVENT_USER:
return type & (EVENT_MAILBOX_SUBSCRIBE|EVENT_MAILBOX_UNSUBSCRIBE|\
EVENT_LOGIN|EVENT_LOGOUT);
case EVENT_MIDSET:
if (!(extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_CMU_MIDSET))
return 0;
break;
case EVENT_UNSEEN_MESSAGES:
if (!(extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_CMU_UNSEENMESSAGES))
return 0;
break;
case EVENT_OLD_UIDSET:
return type & (EVENT_MESSAGE_COPY|EVENT_MESSAGE_MOVE);
}
/* test if the parameter is related to a message event */
return type & (MESSAGE_EVENTS|FLAGS_EVENTS);
}
#define TIMESTAMP_MAX 32
EXPORTED void mboxevent_notify(struct mboxevent *mboxevents)
{
enum event_type type;
struct mboxevent *event, *next;
char stimestamp[TIMESTAMP_MAX+1];
char *formatted_message;
/* nothing to notify */
if (!mboxevents)
return;
event = mboxevents;
/* swap FlagsSet and FlagsClear notification order depending the presence of
* the \Seen flag because it changes the value of vnd.cmu.unseenMessages */
if (event->type == EVENT_FLAGS_SET &&
event->next &&
event->next->type == EVENT_FLAGS_CLEAR &&
strarray_find_case(&event->next->flagnames, "\\Seen", 0) >= 0) {
next = event->next;
event->next = next->next;
next->next = event;
event = next;
}
/* loop over the chained list of events */
do {
if (event->type == EVENT_CANCELLED)
goto next;
/* verify that at least one message has been added depending the event type */
if (event->type & (MESSAGE_EVENTS|FLAGS_EVENTS)) {
if (event->type & (EVENT_MESSAGE_NEW|EVENT_MESSAGE_APPEND)) {
if (!event->params[EVENT_URI].filled)
goto next;
}
else
if (event->uidset == NULL)
goto next;
}
/* others quota are not supported by RFC 5423 */
if ((event->type & QUOTA_EVENTS) &&
!event->params[EVENT_DISK_QUOTA].filled &&
!event->params[EVENT_MAX_MESSAGES].filled)
goto next;
/* finish to fill event parameters structure */
if (mboxevent_expected_param(event->type, EVENT_SERVICE)) {
FILL_STRING_PARAM(event, EVENT_SERVICE, xstrdup(config_ident));
}
if (mboxevent_expected_param(event->type, EVENT_TIMESTAMP)) {
timeval_to_iso8601(&event->timestamp, timeval_ms,
stimestamp, sizeof(stimestamp));
FILL_STRING_PARAM(event, EVENT_TIMESTAMP, xstrdup(stimestamp));
}
if (event->uidset) {
FILL_STRING_PARAM(event, EVENT_UIDSET, seqset_cstring(event->uidset));
}
if (strarray_size(&event->midset) > 0) {
FILL_ARRAY_PARAM(event, EVENT_MIDSET, &event->midset);
}
if (event->olduidset) {
FILL_STRING_PARAM(event, EVENT_OLD_UIDSET, seqset_cstring(event->olduidset));
}
/* may split FlagsSet event in several event notifications */
do {
type = event->type;
/* prefer MessageRead and MessageTrash to FlagsSet as advised in the RFC */
if (type == EVENT_FLAGS_SET) {
int i;
if ((i = strarray_find(&event->flagnames, "\\Deleted", 0)) >= 0) {
type = EVENT_MESSAGE_TRASH;
strarray_remove(&event->flagnames, i);
}
else if ((i = strarray_find(&event->flagnames, "\\Seen", 0)) >= 0) {
type = EVENT_MESSAGE_READ;
strarray_remove(&event->flagnames, i);
}
}
if (strarray_size(&event->flagnames) > 0) {
/* don't send flagNames parameter for those events */
if (type != EVENT_MESSAGE_TRASH && type != EVENT_MESSAGE_READ) {
char *flagnames = strarray_join(&event->flagnames, " ");
FILL_STRING_PARAM(event, EVENT_FLAG_NAMES, flagnames);
/* stop to loop for flagsSet event here */
strarray_fini(&event->flagnames);
}
}
/* check if expected event parameters are filled */
assert(filled_params(type, event));
/* notification is ready to send */
formatted_message = json_formatter(type, event->params);
notify(notifier, "EVENT", NULL, NULL, NULL, 0, NULL, formatted_message);
free(formatted_message);
}
while (strarray_size(&event->flagnames) > 0);
next:
event = event->next;
}
while (event);
return;
}
void mboxevent_add_flags(struct mboxevent *event, char *flagnames[MAX_USER_FLAGS],
bit32 system_flags, bit32 user_flags[MAX_USER_FLAGS/32])
{
unsigned flag, flagmask = 0;
if (!event)
return;
/* add system flags */
if (system_flags & FLAG_DELETED) {
if (strarray_find_case(excluded_flags, "\\Deleted", 0) < 0)
strarray_add_case(&event->flagnames, "\\Deleted");
}
if (system_flags & FLAG_ANSWERED) {
if (strarray_find_case(excluded_flags, "\\Answered", 0) < 0)
strarray_add_case(&event->flagnames, "\\Answered");
}
if (system_flags & FLAG_FLAGGED) {
if (strarray_find_case(excluded_flags, "\\Flagged", 0) < 0)
strarray_add_case(&event->flagnames, "\\Flagged");
}
if (system_flags & FLAG_DRAFT) {
if (strarray_find_case(excluded_flags, "\\Draft", 0) < 0)
strarray_add_case(&event->flagnames, "\\Draft");
}
if (system_flags & FLAG_SEEN) {
if (strarray_find_case(excluded_flags, "\\Seen", 0) < 0)
strarray_add_case(&event->flagnames, "\\Seen");
}
/* add user flags */
for (flag = 0; flag < MAX_USER_FLAGS; flag++) {
if ((flag & 31) == 0) {
flagmask = user_flags[flag/32];
}
if (!(flagnames[flag] && (flagmask & (1<<(flag & 31)))))
continue;
if (strarray_find_case(excluded_flags, flagnames[flag], 0) < 0)
strarray_add_case(&event->flagnames, flagnames[flag]);
}
}
void mboxevent_add_flag(struct mboxevent *event, const char *flag)
{
if (!event)
return;
if (mboxevent_expected_param(event->type, EVENT_FLAG_NAMES))
strarray_add_case(&event->flagnames, flag);
}
EXPORTED void mboxevent_set_access(struct mboxevent *event,
const char *serveraddr, const char *clientaddr,
const char *userid, const char *mailboxname)
{
char url[MAX_MAILBOX_PATH+1];
struct imapurl imapurl;
char extname[MAX_MAILBOX_NAME];
char *userbuf;
if (!event)
return;
/* only notify Logout after successful Login */
if (!userid && event->type & EVENT_LOGOUT) {
event->type = EVENT_CANCELLED;
return;
}
/* all events needs uri parameter */
if (!event->params[EVENT_URI].filled) {
memset(&imapurl, 0, sizeof(struct imapurl));
imapurl.server = config_servername;
if (mailboxname != NULL) {
/* translate internal mailbox name to external */
assert(namespace.mboxname_toexternal != NULL);
(*namespace.mboxname_toexternal)(&namespace, mailboxname,
mboxname_to_userid(mailboxname),
extname);
/* translate any separators in user */
userbuf = (char *)mboxname_to_userid(mailboxname);
mboxname_hiersep_toexternal(&namespace, userbuf,
config_virtdomains ? strcspn(userbuf, "@") : 0);
imapurl.mailbox = extname;
imapurl.user = userbuf;
}
imapurl_toURL(url, &imapurl);
FILL_STRING_PARAM(event, EVENT_URI, xstrdup(url));
if (event->type & MAILBOX_EVENTS) {
FILL_STRING_PARAM(event, EVENT_MAILBOX_ID, xstrdup(url));
}
}
if (serveraddr && mboxevent_expected_param(event->type, EVENT_SERVER_ADDRESS)) {
FILL_STRING_PARAM(event, EVENT_SERVER_ADDRESS, xstrdup(serveraddr));
}
if (clientaddr && mboxevent_expected_param(event->type, EVENT_CLIENT_ADDRESS)) {
FILL_STRING_PARAM(event, EVENT_CLIENT_ADDRESS, xstrdup(clientaddr));
}
if (userid && mboxevent_expected_param(event->type, EVENT_USER)) {
/* translate any separators in user */
userbuf = xstrdup(userid);
mboxname_hiersep_toexternal(&namespace, userbuf,
config_virtdomains ? strcspn(userbuf, "@") : 0);
FILL_STRING_PARAM(event, EVENT_USER, userbuf);
}
}
EXPORTED void mboxevent_extract_record(struct mboxevent *event, struct mailbox *mailbox,
struct index_record *record)
{
char *msgid = NULL;
if (!event)
return;
/* add modseq only on first call, cancel otherwise */
if (mboxevent_expected_param(event->type, EVENT_MODSEQ)) {
if (event->uidset == NULL || (seqset_first(event->uidset) == seqset_last(event->uidset))) {
FILL_UNSIGNED_PARAM(event, EVENT_MODSEQ, record->modseq);
}
else {
/* From RFC 5423:
* modseq May be included with any notification referring
* to one message.
*
* thus cancel inclusion of modseq parameter
*/
event->params[EVENT_MODSEQ].filled = 0;
}
}
/* add UID to uidset */
if (event->uidset == NULL)
event->uidset = seqset_init(0, SEQ_SPARSE);
seqset_add(event->uidset, record->uid, 1);
if (event->type == EVENT_CANCELLED)
return;
/* add Message-Id to midset or NIL if doesn't exists */
if (mboxevent_expected_param(event->type, (EVENT_MIDSET))) {
msgid = mailbox_cache_get_msgid(mailbox, record);
strarray_add(&event->midset, msgid ? msgid : "NIL");
if (msgid)
free(msgid);
}
/* add message size */
if (mboxevent_expected_param(event->type, EVENT_MESSAGE_SIZE)) {
FILL_UNSIGNED_PARAM(event, EVENT_MESSAGE_SIZE, record->size);
}
/* add bodyStructure */
if (mboxevent_expected_param(event->type, EVENT_BODYSTRUCTURE)) {
FILL_STRING_PARAM(event, EVENT_BODYSTRUCTURE,
xstrndup(cacheitem_base(record, CACHE_BODYSTRUCTURE),
cacheitem_size(record, CACHE_BODYSTRUCTURE)));
}
}
void mboxevent_extract_copied_record(struct mboxevent *event,
const struct mailbox *mailbox, uint32_t uid)
{
int first = 0;
if (!event)
return;
/* add the source message's UID to oldUidset */
if (event->olduidset == NULL) {
event->olduidset = seqset_init(0, SEQ_SPARSE);
first = 1;
}
seqset_add(event->olduidset, uid, 1);
/* generate an IMAP URL to reference the old mailbox */
if (first)
mboxevent_extract_old_mailbox(event, mailbox);
}
void mboxevent_extract_content(struct mboxevent *event,
const struct index_record *record, FILE* content)
{
const char *base = NULL;
unsigned long len = 0;
size_t offset, size, truncate;
if (!event)
return;
if (!mboxevent_expected_param(event->type, EVENT_MESSAGE_CONTENT))
return;
truncate = config_getint(IMAPOPT_EVENT_CONTENT_SIZE);
switch (config_getenum(IMAPOPT_EVENT_CONTENT_INCLUSION_MODE)) {
/* include message up to 'truncate' in size with the notification */
case IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_STANDARD:
if (!truncate || record->size <= truncate) {
offset = 0;
size = record->size;
}
else {
/* XXX RFC 5423 suggests to include a URLAUTH [RFC4467] reference
* for larger messages. IMAP URL of mailboxID seems enough though */
return;
}
break;
/* include message truncated to a size of 'truncate' */
case IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_MESSAGE:
offset = 0;
size = (truncate && (record->size > truncate)) ?
truncate : record->size;
break;
/* include headers truncated to a size of 'truncate' */
case IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_HEADER:
offset = 0;
size = (truncate && (record->header_size > truncate)) ?
truncate : record->header_size;
break;
/* include body truncated to a size of 'truncate' */
case IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_BODY:
offset = record->header_size;
size = (truncate && ((record->size - record->header_size) > truncate)) ?
truncate : record->size - record->header_size;
break;
/* include full headers and body truncated to a size of 'truncate' */
case IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_HEADERBODY:
offset = 0;
size = (truncate && ((record->size - record->header_size) > truncate)) ?
record->header_size + truncate : record->size;
break;
/* never happen */
default:
return;
}
map_refresh(fileno(content), 1, &base, &len, record->size, "new message", 0);
FILL_STRING_PARAM(event, EVENT_MESSAGE_CONTENT, xstrndup(base+offset, size));
map_free(&base, &len);
}
void mboxevent_extract_quota(struct mboxevent *event, const struct quota *quota,
enum quota_resource res)
{
struct imapurl imapurl;
char url[MAX_MAILBOX_PATH+1];
char extname[MAX_MAILBOX_NAME];
char *userbuf;
if (!event)
return;
switch(res) {
case QUOTA_STORAGE:
if (mboxevent_expected_param(event->type, EVENT_DISK_QUOTA)) {
if (quota->limits[res] >= 0)
FILL_UNSIGNED_PARAM(event, EVENT_DISK_QUOTA, quota->limits[res]);
}
if (mboxevent_expected_param(event->type, EVENT_DISK_USED)) {
FILL_UNSIGNED_PARAM(event, EVENT_DISK_USED,
quota->useds[res] / quota_units[res]);
}
break;
case QUOTA_MESSAGE:
if (quota->limits[res] >= 0)
FILL_UNSIGNED_PARAM(event, EVENT_MAX_MESSAGES, quota->limits[res]);
FILL_UNSIGNED_PARAM(event, EVENT_MESSAGES, quota->useds[res]);
break;
default:
/* others quota are not supported by RFC 5423 */
break;
}
/* From RFC 5423 :
* The parameters SHOULD include at least the relevant user
* and quota and, optionally, the mailbox.
*
* It seems that it does not correspond to the concept of
* quota root specified in RFC 2087. Thus we fill uri with quota root
*/
if (!event->params[EVENT_URI].filled && event->type & QUOTA_EVENTS) {
/* translate internal mailbox name to external */
assert(namespace.mboxname_toexternal != NULL);
(*namespace.mboxname_toexternal)(&namespace, quota->root,
mboxname_to_userid(quota->root),
extname);
/* translate any separators in user */
userbuf = (char *)mboxname_to_userid(quota->root);
mboxname_hiersep_toexternal(&namespace, userbuf,
config_virtdomains ? strcspn(userbuf, "@") : 0);
memset(&imapurl, 0, sizeof(struct imapurl));
imapurl.server = config_servername;
imapurl.mailbox = extname;
imapurl.user = userbuf;
imapurl_toURL(url, &imapurl);
FILL_STRING_PARAM(event, EVENT_URI, xstrdup(url));
}
}
EXPORTED void mboxevent_set_numunseen(struct mboxevent *event,
struct mailbox *mailbox, int numunseen)
{
if (!event)
return;
if (mboxevent_expected_param(event->type, EVENT_UNSEEN_MESSAGES)) {
unsigned count = (numunseen >= 0) ? (unsigned)numunseen
: mailbox_count_unseen(mailbox);
/* as event notification is focused on mailbox, we don't care about the
* authenticated user but the mailbox's owner.
* it could be a problem only if it is a shared or public folder */
FILL_UNSIGNED_PARAM(event, EVENT_UNSEEN_MESSAGES, count);
}
}
EXPORTED void mboxevent_extract_mailbox(struct mboxevent *event,
struct mailbox *mailbox)
{
struct imapurl imapurl;
char url[MAX_MAILBOX_PATH+1];
char extname[MAX_MAILBOX_NAME];
char *userbuf;
if (!event)
return;
/* mboxevent_extract_mailbox should be called only once */
if (event->params[EVENT_URI].filled)
return;
/* verify if event notification should be disabled for this mailbox */
if (!mboxevent_enabled_for_mailbox(mailbox)) {
event->type = EVENT_CANCELLED;
return;
}
/* translate internal mailbox name to external */
assert(namespace.mboxname_toexternal != NULL);
(*namespace.mboxname_toexternal)(&namespace, mailbox->name,
mboxname_to_userid(mailbox->name), extname);
/* translate any separators in user */
userbuf = (char *)mboxname_to_userid(mailbox->name);
mboxname_hiersep_toexternal(&namespace, userbuf,
config_virtdomains ? strcspn(userbuf, "@") : 0);
/* all events needs uri parameter */
memset(&imapurl, 0, sizeof(struct imapurl));
imapurl.server = config_servername;
imapurl.mailbox = extname;
imapurl.user = userbuf;
imapurl.uidvalidity = mailbox->i.uidvalidity;
if (event->type & (EVENT_MESSAGE_NEW|EVENT_MESSAGE_APPEND) && event->uidset) {
imapurl.uid = seqset_first(event->uidset);
/* don't add uidset parameter to MessageNew and MessageAppend events */
seqset_free(event->uidset);
event->uidset = NULL;
}
imapurl_toURL(url, &imapurl);
FILL_STRING_PARAM(event, EVENT_URI, xstrdup(url));
/* mailbox related events also require mailboxID */
if (event->type & MAILBOX_EVENTS) {
FILL_STRING_PARAM(event, EVENT_MAILBOX_ID, xstrdup(url));
}
if (mboxevent_expected_param(event->type, EVENT_UIDNEXT)) {
FILL_UNSIGNED_PARAM(event, EVENT_UIDNEXT, mailbox->i.last_uid+1);
}
/* From RFC 5423 :
* messages
* Included with QuotaExceed and QuotaWithin notifications relating
* to a user or mailbox message count quota. May be included with
* other notifications.
*
* Number of messages in the mailbox. This is typically included
* with message addition and deletion events.
*
* we are in case messages is relative to the number of messages in the
* mailbox and not the message count quota.
*/
if (mboxevent_expected_param(event->type, EVENT_MESSAGES)) {
FILL_UNSIGNED_PARAM(event, EVENT_MESSAGES, mailbox->i.exists);
}
}
void mboxevent_extract_old_mailbox(struct mboxevent *event,
const struct mailbox *mailbox)
{
struct imapurl imapurl;
char url[MAX_MAILBOX_PATH+1];
char extname[MAX_MAILBOX_NAME];
char *userbuf;
if (!event)
return;
/* translate internal mailbox name to external */
assert(namespace.mboxname_toexternal != NULL);
(*namespace.mboxname_toexternal)(&namespace, mailbox->name,
mboxname_to_userid(mailbox->name), extname);
/* translate any separators in user */
userbuf = (char *)mboxname_to_userid(mailbox->name);
mboxname_hiersep_toexternal(&namespace, userbuf,
config_virtdomains ? strcspn(userbuf, "@") : 0);
memset(&imapurl, 0, sizeof(struct imapurl));
imapurl.server = config_servername;
imapurl.mailbox = extname;
imapurl.user = userbuf;
imapurl.uidvalidity = mailbox->i.uidvalidity;
imapurl_toURL(url, &imapurl);
FILL_STRING_PARAM(event, EVENT_OLD_MAILBOX_ID, xstrdup(url));
}
static const char *event_to_name(enum event_type type)
{
switch (type) {
case EVENT_MESSAGE_APPEND:
return "MessageAppend";
case EVENT_MESSAGE_EXPIRE:
return "MessageExpire";
case EVENT_MESSAGE_EXPUNGE:
return "MessageExpunge";
case EVENT_MESSAGE_NEW:
return "MessageNew";
case EVENT_MESSAGE_COPY:
return "vnd.cmu.MessageCopy";
case EVENT_MESSAGE_MOVE:
return "vnd.cmu.MessageMove";
case EVENT_QUOTA_EXCEED:
return "QuotaExceed";
case EVENT_QUOTA_WITHIN:
return "QuotaWithin";
case EVENT_QUOTA_CHANGE:
return "QuotaChange";
case EVENT_MESSAGE_READ:
return "MessageRead";
case EVENT_MESSAGE_TRASH:
return "MessageTrash";
case EVENT_FLAGS_SET:
return "FlagsSet";
case EVENT_FLAGS_CLEAR:
return "FlagsClear";
case EVENT_LOGIN:
return "Login";
case EVENT_LOGOUT:
return "Logout";
case EVENT_MAILBOX_CREATE:
return "MailboxCreate";
case EVENT_MAILBOX_DELETE:
return "MailboxDelete";
case EVENT_MAILBOX_RENAME:
return "MailboxRename";
case EVENT_MAILBOX_SUBSCRIBE:
return "MailboxSubscribe";
case EVENT_MAILBOX_UNSUBSCRIBE:
return "MailboxUnSubscribe";
default:
fatal("Unknown message event", EC_SOFTWARE);
}
/* never happen */
return NULL;
}
static char *json_formatter(enum event_type type, struct event_parameter params[])
{
int param, ival;
char *val, *ptr, *result;
json_t *event_json = json_object();
json_t *jarray;
json_object_set_new(event_json, "event", json_string(event_to_name(type)));
for (param = 0; param <= MAX_PARAM; param++) {
if (!params[param].filled)
continue;
switch (params[param].id) {
case EVENT_CLIENT_ADDRESS:
/* come from saslprops structure */
val = strdup((char *)params[param].value);
ptr = strchr(val, ';');
*ptr++ = '\0';
json_object_set_new(event_json, "clientIP", json_string(val));
if (parseint32(ptr, (const char **)&ptr, &ival) >= 0)
json_object_set_new(event_json, "clientPort", json_integer(ival));
free(val);
break;
case EVENT_SERVER_ADDRESS:
/* come from saslprops structure */
val = strdup((char *)params[param].value);
ptr = strchr(val, ';');
*ptr++ = '\0';
json_object_set_new(event_json, "serverDomain", json_string(val));
if (parseint32(ptr, (const char **)&ptr, &ival) >= 0)
json_object_set_new(event_json, "serverPort", json_integer(ival));
free(val);
break;
default:
switch (params[param].type) {
case EVENT_PARAM_INT:
json_object_set_new(event_json, params[param].name,
json_integer(params[param].value));
break;
case EVENT_PARAM_STRING:
json_object_set_new(event_json, params[param].name,
json_string((char *)params[param].value));
break;
case EVENT_PARAM_ARRAY:
jarray = json_array();
strarray_t *sarray = (strarray_t *)params[param].value;
int i;
for (i = 0; i < strarray_size(sarray); i++) {
json_array_append_new(jarray, json_string(strarray_nth(sarray, i)));
}
json_object_set_new(event_json, params[param].name, jarray);
break;
}
break;
}
}
result = json_dumps(event_json, JSON_PRESERVE_ORDER|JSON_COMPACT);
json_decref(event_json);
return result;
}
#ifndef NDEBUG
/* overrides event->type with event_type because FlagsSet may be derived to
* MessageTrash or MessageRead */
static int filled_params(enum event_type type, struct mboxevent *event)
{
struct buf missing = BUF_INITIALIZER;
int param, ret = 1;
if (!event)
return 0;
for (param = 0; param <= MAX_PARAM; param++) {
if (mboxevent_expected_param(type, param) &&
!event->params[param].filled) {
switch (event->params[param].id) {
case EVENT_DISK_QUOTA:
return event->params[EVENT_MAX_MESSAGES].filled;
case EVENT_DISK_USED:
return event->params[EVENT_MESSAGES].filled;
case EVENT_FLAG_NAMES:
/* flagNames may be included with MessageAppend and MessageNew
* also we don't expect it here. */
if (!(type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW)))
buf_appendcstr(&missing, " flagNames");
break;
case EVENT_MAX_MESSAGES:
return event->params[EVENT_DISK_QUOTA].filled;
case EVENT_MESSAGE_CONTENT:
/* messageContent is not included in standard mode if the size
* of the message exceed the limit */
if (config_getenum(IMAPOPT_EVENT_CONTENT_INCLUSION_MODE) !=
IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_STANDARD)
buf_appendcstr(&missing, " messageContent");
break;
case EVENT_MESSAGES:
return event->params[EVENT_DISK_USED].filled;
case EVENT_MODSEQ:
/* modseq is not included if notification refers to several
* messages */
if (!event->uidset || (seqset_first(event->uidset) == seqset_last(event->uidset)))
buf_appendcstr(&missing, " modseq");
break;
default:
buf_appendcstr(&missing, " ");
buf_appendcstr(&missing, event->params[param].name);
break;
}
}
}
if (buf_len(&missing)) {
syslog(LOG_ALERT, "Cannot notify event %s: missing parameters:%s",
event_to_name(type), buf_cstring(&missing));
ret = 0;
}
buf_free(&missing);
return ret;
}
#endif /* NDEBUG */
#else /* !ENABLE_MBOXEVENT */
#include "mboxevent.h"
EXPORTED void mboxevent_init(void)
{
}
EXPORTED void mboxevent_setnamespace(struct namespace *n __attribute__((unused)))
{
}
EXPORTED struct mboxevent *mboxevent_new(enum event_type type __attribute__((unused)))
{
return NULL;
}
struct mboxevent *mboxevent_enqueue(enum event_type type __attribute__((unused)),
struct mboxevent **mboxevents __attribute__((unused)))
{
return NULL;
}
EXPORTED void mboxevent_free(struct mboxevent **mboxevent __attribute__((unused)))
{
}
void mboxevent_freequeue(struct mboxevent **mboxevent __attribute__((unused)))
{
}
EXPORTED void mboxevent_notify(struct mboxevent *mboxevents __attribute__((unused)))
{
}
void mboxevent_add_flags(struct mboxevent *event __attribute__((unused)),
char *flagnames[MAX_USER_FLAGS] __attribute__((unused)),
bit32 system_flags __attribute__((unused)),
bit32 user_flags[MAX_USER_FLAGS/32] __attribute__((unused)))
{
}
void mboxevent_add_flag(struct mboxevent *event __attribute__((unused)),
const char *flag __attribute__((unused)))
{
}
EXPORTED void mboxevent_set_access(struct mboxevent *event __attribute__((unused)),
const char *serveraddr __attribute__((unused)),
const char *clientaddr __attribute__((unused)),
const char *userid __attribute__((unused)),
const char *mailboxname __attribute__((unused)))
{
}
EXPORTED void mboxevent_extract_record(struct mboxevent *event __attribute__((unused)),
struct mailbox *mailbox __attribute__((unused)),
struct index_record *record __attribute__((unused)))
{
}
void mboxevent_extract_copied_record(struct mboxevent *event __attribute__((unused)),
const struct mailbox *mailbox __attribute__((unused)),
uint32_t uid __attribute__((unused)))
{
}
void mboxevent_extract_content(struct mboxevent *event __attribute__((unused)),
const struct index_record *record __attribute__((unused)),
FILE* content __attribute__((unused)))
{
}
void mboxevent_extract_quota(struct mboxevent *event __attribute__((unused)),
const struct quota *quota __attribute__((unused)),
enum quota_resource res __attribute__((unused)))
{
}
EXPORTED void mboxevent_set_numunseen(struct mboxevent *event __attribute__((unused)),
struct mailbox *mailbox __attribute__((unused)),
int numunseen __attribute__((unused)))
{
}
EXPORTED void mboxevent_extract_mailbox(struct mboxevent *event __attribute__((unused)),
struct mailbox *mailbox __attribute__((unused)))
{
}
void mboxevent_extract_old_mailbox(struct mboxevent *event __attribute__((unused)),
const struct mailbox *mailbox __attribute__((unused)))
{
}
#endif /* !ENABLE_MBOXEVENT */
diff --git a/imap/spool.c b/imap/spool.c
index 495745ba7..4b7169291 100644
--- a/imap/spool.c
+++ b/imap/spool.c
@@ -1,406 +1,405 @@
/* spool.c -- Routines for spooling/parsing messages from a prot stream
*
* Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* $Id: spool.c,v 1.16 2010/01/06 17:01:40 murch Exp $
*/
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "assert.h"
#include "spool.h"
#include "util.h"
#include "xmalloc.h"
-#include "exitcodes.h"
#include "imap/imap_err.h"
#include "imap/nntp_err.h"
#include "global.h"
#include "strarray.h"
hdrcache_t spool_new_hdrcache(void)
{
hash_table *ht = xmalloc(sizeof(*ht));
return construct_hash_table(ht, 4000, 0);
}
/* take a list of headers, pull the first one out and return it in
name and contents.
copies fin to fout, massaging
returns 0 on success, negative on failure */
typedef enum {
NAME_START,
NAME,
COLON,
BODY_START,
BODY
} state;
/* we don't have to worry about dotstuffing here, since it's illegal
for a header to begin with a dot!
returns 0 on success, filling in 'headname' and 'contents' with a static
pointer (blech).
on end of headers, returns 0 with NULL 'headname' and NULL 'contents'
on error, returns < 0
*/
static int parseheader(struct protstream *fin, FILE *fout,
char **headname, char **contents,
const char **skipheaders)
{
int c;
static struct buf name = BUF_INITIALIZER;
static struct buf body = BUF_INITIALIZER;
state s = NAME_START;
int r = 0;
int reject8bit = config_getswitch(IMAPOPT_REJECT8BIT);
int munge8bit = config_getswitch(IMAPOPT_MUNGE8BIT);
const char **skip = NULL;
buf_reset(&name);
buf_reset(&body);
/* there are two ways out of this loop, both via gotos:
either we successfully read a header (got_header)
or we hit an error (ph_error) */
while ((c = prot_getc(fin)) != EOF) { /* examine each character */
/* reject \0 */
if (!c) {
r = IMAP_MESSAGE_CONTAINSNULL;
goto ph_error;
}
switch (s) {
case NAME_START:
if (c == '.') {
int peek;
peek = prot_getc(fin);
prot_ungetc(peek, fin);
if (peek == '\r' || peek == '\n') {
/* just reached the end of message */
r = 0;
goto ph_error;
}
}
if (c == '\r' || c == '\n') {
/* just reached the end of headers */
r = 0;
goto ph_error;
}
/* field-name = 1*ftext
ftext = %d33-57 / %d59-126
; Any character except
; controls, SP, and
; ":". */
if (!((c >= 33 && c <= 57) || (c >= 59 && c <= 126))) {
/* invalid header name */
r = IMAP_MESSAGE_BADHEADER;
goto ph_error;
}
buf_putc(&name, c);
s = NAME;
break;
case NAME:
if (c == ' ' || c == '\t' || c == ':') {
buf_cstring(&name);
/* see if this header is in our skip list */
for (skip = skipheaders;
skip && *skip && strcasecmp(name.s, *skip); skip++);
if (!skip || !*skip) {
/* write the header name to the output */
if (fout) fputs(name.s, fout);
skip = NULL;
}
s = (c == ':' ? BODY_START : COLON);
break;
}
if (!((c >= 33 && c <= 57) || (c >= 59 && c <= 126))) {
r = IMAP_MESSAGE_BADHEADER;
goto ph_error;
}
buf_putc(&name, c);
break;
case COLON:
if (c == ':') {
s = BODY_START;
} else if (c != ' ' && c != '\t') {
/* i want to avoid confusing dot-stuffing later */
while (c == '.') {
if (fout && !skip) fputc(c, fout);
c = prot_getc(fin);
}
r = IMAP_MESSAGE_BADHEADER;
goto ph_error;
}
break;
case BODY_START:
if (c == ' ' || c == '\t') /* eat the whitespace */
break;
buf_reset(&body);
s = BODY;
/* falls through! */
case BODY:
/* now we want to convert all newlines into \r\n */
if (c == '\r' || c == '\n') {
int peek;
peek = prot_getc(fin);
if (fout && !skip) {
fputc('\r', fout);
fputc('\n', fout);
}
/* we should peek ahead to see if it's folded whitespace */
if (c == '\r' && peek == '\n') {
c = prot_getc(fin);
} else {
c = peek; /* single newline seperator */
}
if (c != ' ' && c != '\t') {
/* this is the end of the header */
buf_cstring(&body);
prot_ungetc(c, fin);
goto got_header;
}
}
if (c >= 0x80) {
if (reject8bit) {
/* We have been configured to reject all mail of this
form. */
r = IMAP_MESSAGE_CONTAINS8BIT;
goto ph_error;
} else if (munge8bit) {
/* We have been configured to munge all mail of this
form. */
c = 'X';
}
}
/* just an ordinary character */
buf_putc(&body, c);
}
/* copy this to the output */
if (fout && s != NAME && !skip) fputc(c, fout);
}
/* if we fall off the end of the loop, we hit some sort of error
condition */
ph_error:
/* put the last character back; we'll copy it later */
if (c != EOF) prot_ungetc(c, fin);
/* and we didn't get a header */
if (headname != NULL) *headname = NULL;
if (contents != NULL) *contents = NULL;
return r;
got_header:
/* Note: xstrdup()ing the string ensures we return
* a minimal length string with no allocation slack
* at the end */
if (headname != NULL) *headname = xstrdup(name.s);
if (contents != NULL) *contents = xstrdup(body.s);
return 0;
}
void spool_cache_header(char *name, char *body, hdrcache_t cache)
{
strarray_t *contents;
lcase(name);
contents = (strarray_t *)hash_lookup(name, cache);
if (!contents)
contents = hash_insert(name, strarray_new(), cache);
strarray_appendm(contents, body);
free(name);
}
void spool_replace_header(char *name, char *body, hdrcache_t cache)
{
strarray_t *contents;
lcase(name);
contents = (strarray_t *)hash_lookup(name, cache);
if (!contents)
contents = hash_insert(name, strarray_new(), cache);
else
strarray_truncate(contents, 0);
strarray_appendm(contents, body);
free(name);
}
int spool_fill_hdrcache(struct protstream *fin, FILE *fout, hdrcache_t cache,
const char **skipheaders)
{
int r = 0;
/* let's fill that header cache */
for (;;) {
char *name = NULL, *body = NULL;
if ((r = parseheader(fin, fout, &name, &body, skipheaders)) < 0) {
break;
}
if (!name) {
/* reached the end of headers */
free(body);
break;
}
/* put it in the hash table */
spool_cache_header(name, body, cache);
}
return r;
}
const char **spool_getheader(hdrcache_t cache, const char *phead)
{
char *head;
strarray_t *contents;
assert(cache && phead);
head = xstrdup(phead);
lcase(head);
/* check the cache */
contents = (strarray_t *)hash_lookup(head, cache);
free(head);
return contents ? (const char **)contents->data : NULL;
}
void spool_free_hdrcache(hdrcache_t cache)
{
if (!cache) return;
free_hash_table(cache, (void (*)(void *))strarray_free);
free(cache);
}
/* copies the message from fin to fout, massaging accordingly:
. newlines are fiddled to \r\n
. "." terminates
. embedded NULs are rejected
. bare \r are removed
*/
int spool_copy_msg(struct protstream *fin, FILE *fout)
{
char buf[8192], *p;
int r = 0;
/* -2: Might need room to add a \r\n\0 set */
while (prot_fgets(buf, sizeof(buf)-2, fin)) {
p = buf + strlen(buf) - 1;
if (p < buf) {
/* buffer start with a \0 */
r = IMAP_MESSAGE_CONTAINSNULL;
continue; /* need to eat the rest of the message */
}
else if (buf[0] == '\r' && buf[1] == '\0') {
/* The message contained \r\0, and fgets is confusing us. */
r = IMAP_MESSAGE_CONTAINSNULL;
continue; /* need to eat the rest of the message */
}
else if (p[0] == '\r') {
/*
* We were unlucky enough to get a CR just before we ran
* out of buffer--put it back.
*/
prot_ungetc('\r', fin);
*p = '\0';
}
else if (p[0] == '\n' && (p == buf || p[-1] != '\r')) {
/* found an \n without a \r */
p[0] = '\r';
p[1] = '\n';
p[2] = '\0';
}
else if (p[0] != '\n' && (strlen(buf) < sizeof(buf)-3)) {
/* line contained a \0 not at the end */
r = IMAP_MESSAGE_CONTAINSNULL;
continue;
}
/* Remove any lone CR characters */
while ((p = strchr(buf, '\r')) && p[1] != '\n') {
/* Src/Target overlap, use memmove */
/* strlen(p) will result in copying the NUL byte as well */
memmove(p, p+1, strlen(p));
}
if (buf[0] == '.') {
if (buf[1] == '\r' && buf[2] == '\n') {
/* End of message */
goto dot;
}
/* Remove the dot-stuffing */
if (fout) fputs(buf+1, fout);
} else {
if (fout) fputs(buf, fout);
}
}
/* wow, serious error---got a premature EOF. */
return IMAP_IOERROR;
dot:
return r;
}
diff --git a/imap/statuscache_db.c b/imap/statuscache_db.c
index 38c347bef..b9d6cdd22 100644
--- a/imap/statuscache_db.c
+++ b/imap/statuscache_db.c
@@ -1,432 +1,431 @@
/* statuscache_db.c -- Status caching routines
*
* Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* $Id: statuscache_db.c,v 1.8 2010/01/06 17:01:41 murch Exp $
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <ctype.h>
#include <syslog.h>
#include "assert.h"
#include "cyrusdb.h"
-#include "exitcodes.h"
#include "imapd.h"
#include "global.h"
#include "imap/imap_err.h"
#include "mboxlist.h"
#include "mailbox.h"
#include "seen.h"
#include "util.h"
#include "xstrlcpy.h"
#include "statuscache.h"
#define DB config_statuscache_db
static struct db *statuscachedb;
static int statuscache_dbopen = 0;
EXPORTED void statuscache_open(void)
{
const char *fname = NULL;
int ret;
char *tofree = NULL;
if (!fname)
fname = config_getstring(IMAPOPT_STATUSCACHE_DB_PATH);
/* create db file name */
if (!fname) {
tofree = strconcat(config_dir, FNAME_STATUSCACHEDB, (char *)NULL);
fname = tofree;
}
ret = cyrusdb_open(DB, fname, CYRUSDB_CREATE, &statuscachedb);
if (ret != 0) {
syslog(LOG_ERR, "DBERROR: opening %s: %s", fname,
cyrusdb_strerror(ret));
syslog(LOG_ERR, "statuscache in degraded mode");
return;
}
free(tofree);
statuscache_dbopen = 1;
}
EXPORTED void statuscache_close(void)
{
int r;
if (statuscache_dbopen) {
r = cyrusdb_close(statuscachedb);
if (r) {
syslog(LOG_ERR, "DBERROR: error closing statuscache: %s",
cyrusdb_strerror(r));
}
statuscache_dbopen = 0;
}
}
HIDDEN void statuscache_fill(struct statusdata *sdata, const char *userid,
struct mailbox *mailbox, unsigned statusitems,
unsigned numrecent, unsigned numunseen)
{
assert(sdata);
assert(mailbox);
sdata->userid = userid;
sdata->statusitems = statusitems;
sdata->messages = mailbox->i.exists;
sdata->recent = numrecent;
sdata->uidnext = mailbox->i.last_uid+1;
sdata->uidvalidity = mailbox->i.uidvalidity;
sdata->unseen = numunseen;
sdata->highestmodseq = mailbox->i.highestmodseq;
}
EXPORTED void statuscache_done(void)
{
/* DB->done() handled by cyrus_done() */
}
static char *statuscache_buildkey(const char *mailboxname, const char *userid,
size_t *keylen)
{
static char key[MAX_MAILBOX_BUFFER];
size_t len;
/* Build statuscache key */
len = strlcpy(key, mailboxname, sizeof(key));
key[len++] = '%';
key[len++] = '%';
len += strlcpy(key + len, userid, sizeof(key) - len);
*keylen = len;
return key;
}
/*
* Performs a STATUS command - note: state MAY be NULL here.
*/
EXPORTED int status_lookup(const char *mboxname, const char *userid,
unsigned statusitems, struct statusdata *sdata)
{
struct mailbox *mailbox = NULL;
unsigned numrecent = 0;
unsigned numunseen = 0;
unsigned c_statusitems;
int r;
/* Check status cache if possible */
if (config_getswitch(IMAPOPT_STATUSCACHE)) {
/* Do actual lookup of cache item. */
r = statuscache_lookup(mboxname, userid, statusitems, sdata);
/* Seen/recent status uses "push" invalidation events from
* seen_db.c. This avoids needing to open cyrus.header to get
* the mailbox uniqueid to open the seen db and get the
* unseen_mtime and recentuid.
*/
if (!r) {
syslog(LOG_DEBUG, "statuscache, '%s', '%s', '0x%02x', 'yes'",
mboxname, userid, statusitems);
return 0;
}
syslog(LOG_DEBUG, "statuscache, '%s', '%s', '0x%02x', 'no'",
mboxname, userid, statusitems);
}
/* Missing or invalid cache entry */
r = mailbox_open_irl(mboxname, &mailbox);
if (r) return r;
/* We always have message count, uidnext,
uidvalidity, and highestmodseq for cache */
c_statusitems = STATUS_MESSAGES | STATUS_UIDNEXT |
STATUS_UIDVALIDITY | STATUS_HIGHESTMODSEQ;
if (!mailbox->i.exists) {
/* no messages, so these two must also be zero */
c_statusitems |= STATUS_RECENT | STATUS_UNSEEN;
}
else if (statusitems & (STATUS_RECENT | STATUS_UNSEEN)) {
/* Read \Seen state */
struct seqset *seq = NULL;
uint32_t recno;
struct index_record record;
int internalseen = mailbox_internal_seen(mailbox, userid);
unsigned recentuid;
if (internalseen) {
recentuid = mailbox->i.recentuid;
} else {
struct seen *seendb = NULL;
struct seendata sd = SEENDATA_INITIALIZER;
r = seen_open(userid, SEEN_CREATE, &seendb);
if (!r) r = seen_read(seendb, mailbox->uniqueid, &sd);
seen_close(&seendb);
if (r) goto done;
recentuid = sd.lastuid;
seq = seqset_parse(sd.seenuids, NULL, recentuid);
seen_freedata(&sd);
}
for (recno = 1; recno <= mailbox->i.num_records; recno++) {
if (mailbox_read_index_record(mailbox, recno, &record))
continue;
if (record.system_flags & FLAG_EXPUNGED)
continue;
if (record.uid > recentuid)
numrecent++;
if (internalseen) {
if (!(record.system_flags & FLAG_SEEN))
numunseen++;
}
else {
if (!seqset_ismember(seq, record.uid))
numunseen++;
}
}
/* we've calculated the correct values for both */
c_statusitems |= STATUS_RECENT | STATUS_UNSEEN;
}
statuscache_fill(sdata, userid, mailbox, c_statusitems,
numrecent, numunseen);
/* cache the new value while unlocking */
mailbox_unlock_index(mailbox, sdata);
done:
mailbox_close(&mailbox);
return r;
}
EXPORTED int statuscache_lookup(const char *mboxname, const char *userid,
unsigned statusitems, struct statusdata *sdata)
{
size_t keylen, datalen;
int r = 0;
const char *data = NULL, *dend;
char *p, *key = statuscache_buildkey(mboxname, userid, &keylen);
unsigned version;
/* Don't access DB if it hasn't been opened */
if (!statuscache_dbopen)
return IMAP_NO_NOSUCHMSG;
memset(sdata, 0, sizeof(struct statusdata));
/* Check if there is an entry in the database */
do {
r = cyrusdb_fetch(statuscachedb, key, keylen, &data, &datalen, NULL);
} while (r == CYRUSDB_AGAIN);
if (r || !data || ((size_t) datalen < sizeof(unsigned))) {
return IMAP_NO_NOSUCHMSG;
}
dend = data + datalen;
version = (unsigned) strtoul(data, &p, 10);
if (version != (unsigned) STATUSCACHE_VERSION) {
/* Wrong version */
return IMAP_NO_NOSUCHMSG;
}
if (p < dend) sdata->statusitems = strtoul(p, &p, 10);
if (p < dend) sdata->messages = strtoul(p, &p, 10);
if (p < dend) sdata->recent = strtoul(p, &p, 10);
if (p < dend) sdata->uidnext = strtoul(p, &p, 10);
if (p < dend) sdata->uidvalidity = strtoul(p, &p, 10);
if (p < dend) sdata->unseen = strtoul(p, &p, 10);
if (p < dend) sdata->highestmodseq = strtoull(p, &p, 10);
/* Sanity check the data */
if (!sdata->statusitems || !sdata->uidnext || !sdata->uidvalidity) {
return IMAP_NO_NOSUCHMSG;
}
if ((sdata->statusitems & statusitems) != statusitems) {
/* Don't have all of the requested information */
return IMAP_NO_NOSUCHMSG;
}
return 0;
}
static int statuscache_store(const char *mboxname,
struct statusdata *sdata,
struct txn **tidptr)
{
char data[250]; /* enough room for 11*(UULONG + SP) */
size_t keylen, datalen;
char *key = statuscache_buildkey(mboxname, sdata->userid, &keylen);
int r;
/* Don't access DB if it hasn't been opened */
if (!statuscache_dbopen)
return 0;
/* The trailing whitespace is necessary because we
* use non-length-based functions to parse the values.
* Any non-digit char would be fine, but whitespace
* looks less ugly in dbtool output */
datalen = snprintf(data, sizeof(data),
"%u %u %u %u %u %u %u " MODSEQ_FMT " ",
STATUSCACHE_VERSION,
sdata->statusitems, sdata->messages,
sdata->recent, sdata->uidnext,
sdata->uidvalidity, sdata->unseen,
sdata->highestmodseq);
r = cyrusdb_store(statuscachedb, key, keylen, data, datalen, tidptr);
if (r != CYRUSDB_OK) {
syslog(LOG_ERR, "DBERROR: error updating database: %s (%s)",
mboxname, cyrusdb_strerror(r));
}
return r;
}
struct statuscache_deleterock {
struct db *db;
struct txn *tid;
};
static int delete_cb(void *rockp,
const char *key, size_t keylen,
const char *data __attribute__((unused)),
size_t datalen __attribute__((unused)))
{
int r;
char buf[4096];
struct statuscache_deleterock *rp = (struct statuscache_deleterock *)rockp;
/* error if it's too big */
if (keylen > 4096)
return 1;
/* we need to cache a copy, because the delete might re-map
* the mmap space */
memcpy(buf, key, keylen);
/* Delete db entry */
r = cyrusdb_delete(rp->db, buf, keylen, &rp->tid, 1);
if (r != CYRUSDB_OK) {
syslog(LOG_ERR, "DBERROR: error deleting from database: %s",
cyrusdb_strerror(r));
}
return 0;
}
HIDDEN int statuscache_invalidate(const char *mboxname, struct statusdata *sdata)
{
size_t keylen;
char *key;
int r;
int doclose = 0;
struct statuscache_deleterock drock;
/* if it's disabled then skip */
if (!config_getswitch(IMAPOPT_STATUSCACHE))
return 0;
/* Open DB if it hasn't been opened */
if (!statuscache_dbopen) {
statuscache_open();
doclose = 1;
}
drock.db = statuscachedb;
drock.tid = NULL;
key = statuscache_buildkey(mboxname, "", &keylen);
/* strip off the second NULL that buildkey added, so we match
* the entries for all users */
r = cyrusdb_foreach(drock.db, key, keylen - 1, NULL, delete_cb,
&drock, &drock.tid);
if (r != CYRUSDB_OK) {
syslog(LOG_ERR, "DBERROR: error invalidating: %s (%s)",
mboxname, cyrusdb_strerror(r));
}
if (!r && sdata) {
r = statuscache_store(mboxname, sdata, &drock.tid);
}
if (r == CYRUSDB_OK) {
cyrusdb_commit(drock.db, drock.tid);
}
else {
if (drock.tid) cyrusdb_abort(drock.db, drock.tid);
}
if (doclose)
statuscache_close();
return 0;
}
diff --git a/imap/sync_log.c b/imap/sync_log.c
index 4ad50dd96..da5466df9 100644
--- a/imap/sync_log.c
+++ b/imap/sync_log.c
@@ -1,311 +1,312 @@
/* sync_log.c -- Cyrus synchonization logging functions
*
* Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* $Id: sync_log.c,v 1.7 2010/01/06 17:01:41 murch Exp $
*
* Original version written by David Carter <dpc22@cam.ac.uk>
* Rewritten and integrated into Cyrus by Ken Murchison <ken@oceana.com>
*/
/* YYY Need better quoting for obscure filenames: use literals? */
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <errno.h>
#include "assert.h"
+#include "exitcodes.h"
#include "sync_log.h"
#include "global.h"
#include "cyr_lock.h"
#include "mailbox.h"
#include "retry.h"
#include "util.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
static int sync_log_enabled = 0;
static const char *suppressed_channel = NULL;
static char *channel_base;
static char *channel_end;
EXPORTED void sync_log_init(void)
{
char *p;
sync_log_enabled = config_getswitch(IMAPOPT_SYNC_LOG);
channel_base = NULL;
channel_end = NULL;
if (config_getstring(IMAPOPT_SYNC_LOG_CHANNELS)) {
channel_base = xstrdup(config_getstring(IMAPOPT_SYNC_LOG_CHANNELS));
channel_end = channel_base + strlen(channel_base);
p = channel_base;
while (*p) {
if (*p == ' ') *p = '\0';
p++;
}
}
}
EXPORTED void sync_log_suppress(void)
{
sync_log_enabled = 0;
}
void sync_log_suppress_channel(const char *channelname)
{
if (channelname) {
/* there can only be one */
assert (!suppressed_channel);
suppressed_channel = channelname;
}
else {
suppressed_channel = NULL;
}
}
EXPORTED void sync_log_done(void)
{
free(channel_base);
channel_base = NULL;
}
EXPORTED char *sync_log_fname(const char *channel)
{
static char buf[MAX_MAILBOX_PATH];
if (channel)
snprintf(buf, MAX_MAILBOX_PATH,
"%s/sync/%s/log", config_dir, channel);
else
snprintf(buf, MAX_MAILBOX_PATH,
"%s/sync/log", config_dir);
return buf;
}
static void sync_log_base(const char *channel, const char *string)
{
int fd;
struct stat sbuffile, sbuffd;
int retries = 0;
const char *fname;
/* are we being supressed? */
if (!sync_log_enabled) return;
if (channel && suppressed_channel && !strcmp(channel, suppressed_channel))
return;
fname = sync_log_fname(channel);
while (retries++ < SYNC_LOG_RETRIES) {
fd = open(fname, O_WRONLY|O_APPEND|O_CREAT, 0640);
if (fd < 0 && errno == ENOENT) {
if (!cyrus_mkdir(fname, 0755)) {
fd = open(fname, O_WRONLY|O_APPEND|O_CREAT, 0640);
}
}
if (fd < 0) {
syslog(LOG_ERR, "sync_log(): Unable to write to log file %s: %s",
fname, strerror(errno));
return;
}
if (lock_blocking(fd) == -1) {
syslog(LOG_ERR, "sync_log(): Failed to lock %s for %s: %m",
fname, string);
xclose(fd);
return;
}
/* Check that the file wasn't renamed after it was opened above */
if ((fstat(fd, &sbuffd) == 0) &&
(stat(fname, &sbuffile) == 0) &&
(sbuffd.st_ino == sbuffile.st_ino))
break;
xclose(fd);
}
if (retries >= SYNC_LOG_RETRIES) {
xclose(fd);
syslog(LOG_ERR,
"sync_log(): Failed to lock %s for %s after %d attempts",
fname, string, retries);
return;
}
if (retry_write(fd, string, strlen(string)) < 0)
syslog(LOG_ERR, "write() to %s failed: %s",
fname, strerror(errno));
(void)fsync(fd); /* paranoia */
xclose(fd);
}
static const char *sync_quote_name(const char *name)
{
static char buf[MAX_MAILBOX_BUFFER+3]; /* "x2 plus \0 */
char c;
int src;
int dst = 0;
int need_quote = 0;
/* initial quote */
buf[dst++] = '"';
/* degenerate case - no name is the empty string, quote it */
if (!name || !*name) {
need_quote = 1;
goto end;
}
for (src = 0; name[src]; src++) {
c = name[src];
if ((c == '\r') || (c == '\n'))
fatal("Illegal line break in folder name", EC_IOERR);
/* quoteable characters */
if ((c == '\\') || (c == '\"') || (c == '{') || (c == '}')) {
need_quote = 1;
buf[dst++] = '\\';
}
/* non-atom characters */
else if ((c == ' ') || (c == '\t') || (c == '(') || (c == ')')) {
need_quote = 1;
}
buf[dst++] = c;
if (dst > MAX_MAILBOX_BUFFER)
fatal("word too long", EC_IOERR);
}
end:
if (need_quote) {
buf[dst++] = '\"';
buf[dst] = '\0';
return buf;
}
else {
buf[dst] = '\0';
return buf + 1; /* skip initial quote */
}
}
#define BUFSIZE 4096
static char *va_format(const char *fmt, va_list ap)
{
static char buf[BUFSIZE+1];
size_t len;
int ival;
const char *sval;
const char *p;
for (len = 0, p = fmt; *p && len < BUFSIZE; p++) {
if (*p != '%') {
buf[len++] = *p;
continue;
}
switch (*++p) {
case 'd':
ival = va_arg(ap, int);
len += snprintf(buf+len, BUFSIZE-len, "%d", ival);
break;
case 's':
sval = va_arg(ap, const char *);
sval = sync_quote_name(sval);
strlcpy(buf+len, sval, BUFSIZE-len);
len += strlen(sval);
break;
default:
buf[len++] = *p;
break;
}
}
if (buf[len-1] != '\n') buf[len++] = '\n';
buf[len] = '\0';
return buf;
}
void sync_log_channel(const char *channel, const char *fmt, ...)
{
va_list ap;
const char *val;
va_start(ap, fmt);
val = va_format(fmt, ap);
va_end(ap);
sync_log_base(channel, val);
}
EXPORTED void sync_log(const char *fmt, ...)
{
va_list ap;
const char *val;
const char *ch;
va_start(ap, fmt);
val = va_format(fmt, ap);
va_end(ap);
if (channel_base) {
for (ch = channel_base; ch < channel_end; ch += strlen(ch) + 1) {
sync_log_base(ch, val);
}
}
else {
/* just the regular log path */
sync_log_base(NULL, val);
}
}
diff --git a/imap/upgrade_index.c b/imap/upgrade_index.c
index 248a44d8a..fb5611796 100644
--- a/imap/upgrade_index.c
+++ b/imap/upgrade_index.c
@@ -1,428 +1,427 @@
/* upgrade_index.c -- Mailbox upgrade routines
*
* Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* $Id: mailbox.c,v 1.199 2010/01/06 17:01:36 murch Exp $
*/
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>
#include <utime.h>
#ifdef HAVE_DIRENT_H
# include <dirent.h>
# define NAMLEN(dirent) strlen((dirent)->d_name)
#else
# define dirent direct
# define NAMLEN(dirent) (dirent)->d_namlen
# if HAVE_SYS_NDIR_H
# include <sys/ndir.h>
# endif
# if HAVE_SYS_DIR_H
# include <sys/dir.h>
# endif
# if HAVE_NDIR_H
# include <ndir.h>
# endif
#endif
#include "annotate.h"
#include "assert.h"
#include "crc32.h"
-#include "exitcodes.h"
#include "global.h"
#include "imap/imap_err.h"
#include "mailbox.h"
#include "message.h"
#include "map.h"
#include "seen.h"
#include "util.h"
#include "sequence.h"
#include "xmalloc.h"
struct expunge_data {
uint32_t uid;
const char *base;
};
static int sort_expunge(const void *a, const void *b)
{
struct expunge_data *ea = (struct expunge_data *)a;
struct expunge_data *eb = (struct expunge_data *)b;
return ea->uid - eb->uid;
}
static void upgrade_index_record(struct mailbox *mailbox,
const char *buf,
struct index_record *record,
int record_size,
int oldversion)
{
char recordbuf[INDEX_RECORD_SIZE];
struct utimbuf settime;
const char *fname;
assert(record_size <= INDEX_RECORD_SIZE);
memset(recordbuf, 0, INDEX_RECORD_SIZE);
memcpy(recordbuf, buf, record_size);
/* do the initial parse. Ignore the result, crc32 will mismatch
* for sure */
mailbox_buf_to_index_record(recordbuf, record);
if (oldversion == 12) {
/* avoid re-parsing the message by copying the old cache_crc,
* but only if the old RECORD_CRC matches */
if (crc32_map(buf, 92) == ntohl(*((bit32 *)(buf+92)))) {
record->cache_crc = ntohl(*((bit32 *)(buf+88)));
/* we need to read the cache record in here, so that repack
* will write it out to a new cache file */
if (!mailbox_cacherecord(mailbox, record))
return;
/* record failed, drop through */
record->cache_offset = 0;
}
/* CRC failed, drop through */
}
fname = mailbox_message_fname(mailbox, record->uid);
if (message_parse(fname, record)) {
/* failed to create, don't try to write */
record->crec.len = 0;
/* and the record is expunged too! */
record->system_flags |= FLAG_EXPUNGED | FLAG_UNLINKED;
syslog(LOG_ERR, "IOERROR: FATAL - failed to parse "
"file %s for upgrade, expunging", fname);
return;
}
/* update the mtime to match the internaldate */
settime.actime = settime.modtime = record->internaldate;
utime(fname, &settime);
}
/*
* Upgrade an index/expunge file for 'mailbox'
*/
HIDDEN int upgrade_index(struct mailbox *mailbox)
{
uint32_t recno, erecno;
uint32_t uid, euid;
unsigned long oldmapnum;
unsigned long oldnum_records;
unsigned long expunge_num = 0;
uint32_t oldminor_version, oldstart_offset, oldrecord_size;
indexbuffer_t headerbuf;
const char *bufp = NULL;
char *hbuf = (char *)headerbuf.buf;
const char *fname;
const char *datadirname;
struct stat sbuf;
struct seqset *seq = NULL;
struct index_record record;
int expunge_fd = -1;
const char *expunge_base = NULL;
size_t expunge_len = 0; /* mapped size */
unsigned long emapnum;
bit32 eversion = 0, eoffset = 0, expungerecord_size = 0;
struct expunge_data *expunge_data = NULL;
struct mailbox_repack *repack = NULL;
int r;
if (mailbox->index_size < OFFSET_NUM_RECORDS)
return IMAP_MAILBOX_BADFORMAT;
oldminor_version = ntohl(*((bit32 *)(mailbox->index_base+OFFSET_MINOR_VERSION)));
oldstart_offset = ntohl(*((bit32 *)(mailbox->index_base+OFFSET_START_OFFSET)));
oldrecord_size = ntohl(*((bit32 *)(mailbox->index_base+OFFSET_RECORD_SIZE)));
oldnum_records = ntohl(*((bit32 *)(mailbox->index_base+OFFSET_NUM_RECORDS)));
/* bogus data at the start of the index file? */
if (!oldstart_offset || !oldrecord_size)
return IMAP_MAILBOX_BADFORMAT;
oldmapnum = (mailbox->index_size - oldstart_offset) / oldrecord_size;
if (oldmapnum < oldnum_records) {
syslog(LOG_ERR, "upgrade: %s map doesn't fit, shrinking index %lu to %lu",
mailbox->name, oldnum_records, oldmapnum);
oldnum_records = oldmapnum;
}
/* check if someone else already upgraded the index! */
if (oldminor_version == MAILBOX_MINOR_VERSION)
goto done;
/* check that the data directory exists. If not, it may be that
* something isn't correctly mounted. We don't want to wipe out
* all the index records due to IOERRORs just because the admin
* made a temporary mistake */
datadirname = mailbox_message_fname(mailbox, 0);
if (stat(datadirname, &sbuf)) {
syslog(LOG_ERR, "IOERROR: unable to find data directory %s "
"for mailbox %s, refusing to upgrade",
datadirname, mailbox->name);
return IMAP_IOERROR;
}
/* Copy existing header so we can upgrade it */
memset(hbuf, 0, INDEX_HEADER_SIZE);
if (oldstart_offset > INDEX_HEADER_SIZE)
memcpy(hbuf, mailbox->index_base, INDEX_HEADER_SIZE);
else
memcpy(hbuf, mailbox->index_base, oldstart_offset);
/* QUOTA_MAILBOX_USED changed to 64 bit added with minor version 6 */
if (oldminor_version < 6) {
/* upgrade quota to 64-bits (bump existing fields) */
memmove(hbuf+OFFSET_QUOTA_MAILBOX_USED + 4, hbuf+OFFSET_QUOTA_MAILBOX_USED,
INDEX_HEADER_SIZE - (OFFSET_QUOTA_MAILBOX_USED + 4));
/* zero the unused 32-bits */
*((bit32 *)(hbuf+OFFSET_QUOTA_MAILBOX_USED)) = htonl(0);
}
/* ignore the result - we EXPECT a CRC32 mismatch */
mailbox_buf_to_index_header(hbuf, &mailbox->i);
/* new version fields */
mailbox->i.minor_version = MAILBOX_MINOR_VERSION;
mailbox->i.start_offset = INDEX_HEADER_SIZE;
mailbox->i.record_size = INDEX_RECORD_SIZE;
/* upgrade other fields as necessary */
if (!mailbox->i.highestmodseq)
mailbox->i.highestmodseq = 1;
if (!mailbox->i.uidvalidity)
mailbox->i.uidvalidity = time(0);
/* minor version wasn't updated religiously in the early days,
* so we need to use the old offset instead */
if (oldstart_offset < OFFSET_MAILBOX_OPTIONS)
mailbox->i.options = config_getint(IMAPOPT_MAILBOX_DEFAULT_OPTIONS);
if (oldminor_version < 12) {
struct seendata sd = SEENDATA_INITIALIZER;
const char *owner_userid;
/* remove the CONDSTORE option - it's implicit now */
mailbox->i.options &= ~OPT_IMAP_CONDSTORE;
if (mailbox->i.options & OPT_IMAP_SHAREDSEEN)
owner_userid = "anyone";
else
owner_userid = mboxname_to_userid(mailbox->name);
r = mailbox_read_header(mailbox, NULL);
if (r) goto fail;
/* NEW HEADER FIELDS */
/* we'll set this if there are expunged records */
mailbox->i.first_expunged = 0;
/* we can't know about deletions before the current modseq */
mailbox->i.deletedmodseq = mailbox->i.highestmodseq;
/* bootstrap CRC matching */
mailbox->i.header_file_crc = mailbox->header_file_crc;
/* set up seen tracking for user inside the mailbox */
if (!owner_userid) {
r = IMAP_MAILBOX_NONEXISTENT;
} else {
struct seen *seendb = NULL;
r = seen_open(owner_userid, SEEN_SILENT, &seendb);
if (!r) r = seen_read(seendb, mailbox->uniqueid, &sd);
seen_close(&seendb);
}
if (r) { /* no seen data? */
mailbox->i.recentuid = 0;
mailbox->i.recenttime = 0;
}
else {
mailbox->i.recentuid = sd.lastuid;
mailbox->i.recenttime = sd.lastchange;
seq = seqset_parse(sd.seenuids, NULL, sd.lastuid);
seen_freedata(&sd);
}
/* check for expunge */
fname = mailbox_meta_fname(mailbox, META_EXPUNGE);
expunge_fd = open(fname, O_RDWR, 0666);
if (expunge_fd == -1) goto no_expunge;
r = fstat(expunge_fd, &sbuf);
if (r == -1) goto no_expunge;
if (sbuf.st_size < INDEX_HEADER_SIZE) goto no_expunge;
map_refresh(expunge_fd, 1, &expunge_base,
&expunge_len, sbuf.st_size, "expunge",
mailbox->name);
/* use the expunge file's header information just in case
* versions are skewed for some reason */
eversion = ntohl(*((bit32 *)(expunge_base+OFFSET_MINOR_VERSION)));
eoffset = ntohl(*((bit32 *)(expunge_base+OFFSET_START_OFFSET)));
expungerecord_size = ntohl(*((bit32 *)(expunge_base+OFFSET_RECORD_SIZE)));
/* bogus data at the start of the expunge file? */
if (!eoffset || !expungerecord_size)
goto no_expunge;
expunge_num = ntohl(*((bit32 *)(expunge_base+OFFSET_NUM_RECORDS)));
emapnum = (sbuf.st_size - eoffset) / expungerecord_size;
if (emapnum < expunge_num) {
syslog(LOG_ERR, "IOERROR: %s map doesn't fit, shrinking expunge %lu to %lu",
mailbox->name, expunge_num, emapnum);
expunge_num = emapnum;
}
/* make sure there's space for them */
expunge_data = xmalloc(expunge_num * sizeof(struct expunge_data));
/* find the start offset for each record, and the UID */
for (erecno = 1; erecno <= expunge_num; erecno++) {
bufp = expunge_base + eoffset + (erecno-1)*expungerecord_size;
expunge_data[erecno-1].uid = ntohl(*((bit32 *)(bufp+OFFSET_UID)));
expunge_data[erecno-1].base = bufp;
}
/* expunge files were not sorted. So sort them now for easier
* interleaving */
qsort(expunge_data, expunge_num,
sizeof(struct expunge_data), &sort_expunge);
}
no_expunge:
mailbox_repack_setup(mailbox, &repack);
/* Write the rest of new index */
recno = 1;
erecno = 1;
while (recno <= oldnum_records || erecno <= expunge_num) {
/* read the uid */
if (recno <= oldnum_records) {
bufp = mailbox->index_base + oldstart_offset + (recno-1)*oldrecord_size;
uid = ntohl(*((bit32 *)(bufp+OFFSET_UID)));
}
else {
uid = UINT32_MAX;
}
if (erecno <= expunge_num) {
euid = expunge_data[erecno-1].uid;
}
else {
euid = UINT32_MAX;
}
/* case: index UID is first, or the same */
if (uid <= euid) {
upgrade_index_record(mailbox, bufp, &record, oldrecord_size,
oldminor_version);
recno++;
if (uid == euid) /* duplicate in both, skip expunged */
erecno++;
}
else {
upgrade_index_record(mailbox, expunge_data[erecno-1].base,
&record, expungerecord_size, eversion);
record.system_flags |= FLAG_EXPUNGED;
erecno++;
}
/* user seen was merged into the index with version 12 */
if (oldminor_version < 12 && seqset_ismember(seq, record.uid))
record.system_flags |= FLAG_SEEN;
/* CID was added with version 13 */
if (oldminor_version < 13)
record.cid = 0;
r = mailbox_repack_add(repack, &record);
if (r) goto fail;
}
/* older than 13 didn't count MESSAGE or NUMFOLDERS usage for quota */
if (oldminor_version < 13 && mailbox->quotaroot) {
quota_t qdiffs[QUOTA_NUMRESOURCES] = QUOTA_DIFFS_INITIALIZER;
qdiffs[QUOTA_MESSAGE] = repack->i.exists;
qdiffs[QUOTA_NUMFOLDERS] = 1;
quota_update_useds(mailbox->quotaroot, qdiffs, mailbox->name);
}
r = mailbox_repack_commit(&repack);
if (r) goto fail;
mailbox->i.dirty = 0;
/* don't need this file any more! */
unlink(mailbox_meta_fname(mailbox, META_EXPUNGE));
/* XXX - remove seen record */
syslog(LOG_INFO, "Index upgrade: %s (%d -> %d)", mailbox->name,
oldminor_version, MAILBOX_MINOR_VERSION);
done:
if (expunge_fd != -1) close(expunge_fd);
if (expunge_base) map_free(&expunge_base, &expunge_len);
seqset_free(seq);
free(expunge_data);
return 0;
fail:
if (expunge_fd != -1) close(expunge_fd);
if (expunge_base) map_free(&expunge_base, &expunge_len);
seqset_free(seq);
free(expunge_data);
mailbox_repack_abort(&repack);
syslog(LOG_ERR, "Index upgrade failed: %s", mailbox->name);
return IMAP_IOERROR;
}
diff --git a/lib/cyrusdb_flat.c b/lib/cyrusdb_flat.c
index 46313fd7a..77a41f2db 100644
--- a/lib/cyrusdb_flat.c
+++ b/lib/cyrusdb_flat.c
@@ -1,857 +1,856 @@
/* cyrusdb_flat: a sorted flat textfile backend
*
* Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* $Id: cyrusdb_flat.c,v 1.40 2010/01/06 17:01:45 murch Exp $
*/
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <fcntl.h>
#include "assert.h"
#include "cyrusdb.h"
-#include "exitcodes.h"
#include "map.h"
#include "bsearch.h"
#include "cyr_lock.h"
#include "retry.h"
#include "util.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "xstrlcat.h"
/* we have the file locked iff we have an outstanding transaction */
struct dbengine {
char *fname;
struct dbengine *next;
int refcount;
int fd; /* current file open */
ino_t ino;
const char *base; /* contents of file */
size_t size; /* actual size */
size_t len; /* mapped size */
struct buf data; /* returned storage for fetch */
};
#define DATA(db) ((db)->data.s ? (db)->data.s : "")
#define DATALEN(db) ((db)->data.len)
struct txn {
char *fnamenew;
int fd;
};
static struct dbengine *alldbs;
/*
* We choose an escape character which is an invalid UTF-8 encoding and
* thus unlikely to appear in the key or data unless they are completely
* non-textual.
*/
#define ESCAPE 0xff
static void encode(const char *ps, int len, struct buf *buf)
{
const unsigned char *p = (const unsigned char *)ps;
buf_reset(buf);
/* allocate enough space plus a little slop to cover
* escaping a few characters */
buf_ensure(buf, len+10);
for ( ; len > 0 ; len--, p++) {
switch (*p) {
case '\0':
case '\t':
case '\r':
case '\n':
buf_putc(buf, ESCAPE);
buf_putc(buf, 0x80|(*p));
break;
case ESCAPE:
buf_putc(buf, ESCAPE);
buf_putc(buf, ESCAPE);
break;
default:
buf_putc(buf, *p);
break;
}
}
/* ensure the buf is NUL-terminated; we pass the buf's data to
* bsearch_mem(), which expects a C string, several times */
buf_cstring(buf);
}
static void decode(const char *ps, int len, struct buf *buf)
{
const unsigned char *p = (const unsigned char *)ps;
buf_reset(buf);
/* allocate enough space; we don't need slop because
* decoding can only shrink the result */
buf_ensure(buf, len);
for ( ; len > 0 ; len--, p++) {
if (*p == ESCAPE) {
if (len < 2) {
/* invalid encoding, silently ignore */
continue;
}
len--;
p++;
if (*p == ESCAPE)
buf_putc(buf, ESCAPE);
else
buf_putc(buf, (*p) & ~0x80);
}
else
buf_putc(buf, *p);
}
/* Note: buf is not NUL-terminated. It happens that neither
* skiplist nor berkeley backends guarantee any such thing,
* and so code that depends on it is quite broken anyway */
}
/* other routines call this one when they fail */
static int abort_txn(struct dbengine *db, struct txn *tid)
{
int r = CYRUSDB_OK;
int rw = 0;
struct stat sbuf;
assert(db && tid);
/* cleanup done while lock is held */
if (tid->fnamenew) {
unlink(tid->fnamenew);
free(tid->fnamenew);
rw = 1;
}
/* release lock */
r = lock_unlock(db->fd);
if (r == -1) {
syslog(LOG_ERR, "IOERROR: unlocking db %s: %m", db->fname);
r = CYRUSDB_IOERROR;
}
if (rw) {
/* return to our normally scheduled fd */
if (!r && fstat(db->fd, &sbuf) == -1) {
syslog(LOG_ERR, "IOERROR: fstat on %s: %m", db->fname);
r = CYRUSDB_IOERROR;
}
if (!r) {
map_free(&db->base, &db->len);
map_refresh(db->fd, 0, &db->base, &db->len, sbuf.st_size,
db->fname, 0);
db->size = sbuf.st_size;
}
}
free(tid);
return 0;
}
static void free_db(struct dbengine *db)
{
if (db) {
free(db->fname);
buf_free(&db->data);
free(db);
}
}
static struct dbengine *find_db(const char *fname)
{
struct dbengine *db;
for (db = alldbs ; db ; db = db->next) {
if (!strcmp(fname, db->fname)) {
db->refcount++;
return db;
}
}
return NULL;
}
static struct txn *new_txn(void)
{
struct txn *ret = (struct txn *) xmalloc(sizeof(struct txn));
ret->fnamenew = NULL;
ret->fd = 0;
return ret;
}
static int myopen(const char *fname, int flags, struct dbengine **ret)
{
struct dbengine *db;
struct stat sbuf;
assert(fname && ret);
db = find_db(fname);
if (db)
goto out; /* new reference to existing db */
db = (struct dbengine *) xzmalloc(sizeof(struct dbengine));
db->fd = open(fname, O_RDWR, 0644);
if (db->fd == -1 && errno == ENOENT) {
if (!(flags & CYRUSDB_CREATE)) {
free_db(db);
return CYRUSDB_NOTFOUND;
}
if (cyrus_mkdir(fname, 0755) == -1) {
free_db(db);
return CYRUSDB_IOERROR;
}
db->fd = open(fname, O_RDWR | O_CREAT, 0644);
}
if (db->fd == -1) {
syslog(LOG_ERR, "IOERROR: opening %s: %m", fname);
free_db(db);
return CYRUSDB_IOERROR;
}
if (fstat(db->fd, &sbuf) == -1) {
syslog(LOG_ERR, "IOERROR: fstat on %s: %m", fname);
close(db->fd);
free_db(db);
return CYRUSDB_IOERROR;
}
db->ino = sbuf.st_ino;
map_refresh(db->fd, 0, &db->base, &db->len, sbuf.st_size,
fname, 0);
db->size = sbuf.st_size;
db->fname = xstrdup(fname);
db->refcount = 1;
/* prepend to the list */
db->next = alldbs;
alldbs = db;
out:
*ret = db;
return 0;
}
static int myclose(struct dbengine *db)
{
struct dbengine **prevp;
assert(db);
if (--db->refcount > 0)
return 0;
/* now we are dropping the last reference */
/* detach from the list of all dbs */
for (prevp = &alldbs ;
*prevp && *prevp != db ;
prevp = &(*prevp)->next)
;
assert(*prevp == db); /* this struct must be in the list */
*prevp = db->next;
/* clean up the internals */
map_free(&db->base, &db->len);
close(db->fd);
free_db(db);
return 0;
}
static int starttxn_or_refetch(struct dbengine *db, struct txn **mytid)
{
int r = 0;
struct stat sbuf;
assert(db);
if (mytid && !*mytid) {
const char *lockfailaction;
/* start txn; grab lock */
r = lock_reopen(db->fd, db->fname, &sbuf, &lockfailaction);
if (r < 0) {
syslog(LOG_ERR, "IOERROR: %s %s: %m", lockfailaction, db->fname);
return CYRUSDB_IOERROR;
}
*mytid = new_txn();
if (db->ino != sbuf.st_ino) {
map_free(&db->base, &db->len);
}
map_refresh(db->fd, 0, &db->base, &db->len, sbuf.st_size,
db->fname, 0);
/* we now have the latest & greatest open */
db->size = sbuf.st_size;
db->ino = sbuf.st_ino;
}
if (!mytid) {
/* no txn, but let's try to be reasonably up-to-date */
if (stat(db->fname, &sbuf) == -1) {
syslog(LOG_ERR, "IOERROR: stating flat %s: %m", db->fname);
return CYRUSDB_IOERROR;
}
if (sbuf.st_ino != db->ino) {
/* reopen */
int newfd = open(db->fname, O_RDWR);
if (newfd == -1) {
/* fail! */
syslog(LOG_ERR, "couldn't reopen %s: %m", db->fname);
return CYRUSDB_IOERROR;
}
dup2(newfd, db->fd);
close(newfd);
if (stat(db->fname, &sbuf) == -1) {
syslog(LOG_ERR, "IOERROR: stating flat %s: %m", db->fname);
return CYRUSDB_IOERROR;
}
db->ino = sbuf.st_ino;
map_free(&db->base, &db->len);
}
map_refresh(db->fd, 0, &db->base, &db->len,
sbuf.st_size, db->fname, 0);
db->size = sbuf.st_size;
}
return 0;
}
static int myfetch(struct dbengine *db,
const char *key, size_t keylen,
const char **data, size_t *datalen,
struct txn **mytid)
{
int r = 0;
int offset;
unsigned long len;
struct buf keybuf = BUF_INITIALIZER;
assert(db);
if (data) *data = NULL;
if (datalen) *datalen = 0;
r = starttxn_or_refetch(db, mytid);
if (r) return r;
encode(key, keylen, &keybuf);
offset = bsearch_mem_mbox(keybuf.s, db->base, db->size, 0, &len);
if (len) {
if (data) {
decode(db->base + offset + keybuf.len + 1,
/* subtract one for \t, and one for the \n */
len - keybuf.len - 2,
&db->data);
if (data) *data = DATA(db);
if (datalen) *datalen = DATALEN(db);
}
} else {
r = CYRUSDB_NOTFOUND;
}
buf_free(&keybuf);
return r;
}
static int fetch(struct dbengine *mydb,
const char *key, size_t keylen,
const char **data, size_t *datalen,
struct txn **mytid)
{
return myfetch(mydb, key, keylen, data, datalen, mytid);
}
static int fetchlock(struct dbengine *db,
const char *key, size_t keylen,
const char **data, size_t *datalen,
struct txn **mytid)
{
return myfetch(db, key, keylen, data, datalen, mytid);
}
static int getentry(struct dbengine *db, const char *p,
struct buf *keybuf, const char **dataendp)
{
const char *key;
int keylen;
const char *data;
const char *dataend;
int datalen;
key = p;
data = strchr(p, '\t');
if (!data) {
/* huh, might be corrupted? */
return CYRUSDB_IOERROR;
}
keylen = data - key;
data++; /* skip the \t */
dataend = strchr(data, '\n');
if (!dataend) {
/* huh, might be corrupted? */
return CYRUSDB_IOERROR;
}
datalen = dataend - data;
decode(data, datalen, &db->data);
decode(key, keylen, keybuf);
*dataendp = dataend;
return 0;
}
#define GETENTRY(p) \
r = getentry(db, p, &keybuf, &dataend); \
if (r) break;
static int foreach(struct dbengine *db,
const char *prefix, size_t prefixlen,
foreach_p *goodp,
foreach_cb *cb, void *rock,
struct txn **mytid)
{
int r = CYRUSDB_OK;
int offset;
unsigned long len;
const char *p, *pend;
const char *dataend;
/* for use inside the loop, but we need the values to be retained
* from loop to loop */
struct buf keybuf = BUF_INITIALIZER;
int dontmove = 0;
/* For when we have a transaction running */
struct buf savebuf = BUF_INITIALIZER;
/* for the local iteration so that the db can change out from under us */
const char *dbbase = NULL;
size_t dblen = 0;
int dbfd = -1;
struct buf prefixbuf = BUF_INITIALIZER;
r = starttxn_or_refetch(db, mytid);
if (r) return r;
if(!mytid) {
/* No transaction, use the fast method to avoid stomping on our
* memory map if changes happen */
dbfd = dup(db->fd);
if(dbfd == -1) return CYRUSDB_IOERROR;
map_refresh(dbfd, 1, &dbbase, &dblen, db->size, db->fname, 0);
/* drop our read lock on the file, since we don't really care
* if it gets replaced out from under us, our mmap stays on the
* old version */
lock_unlock(db->fd);
} else {
/* use the same variables as in the no transaction case, just to
* get things set up */
dbbase = db->base;
dblen = db->len;
}
if (prefix) {
encode(prefix, prefixlen, &prefixbuf);
offset = bsearch_mem_mbox(prefixbuf.s, dbbase, db->size, 0, &len);
} else {
offset = 0;
}
p = dbbase + offset;
pend = dbbase + db->size;
while (p < pend) {
if(!dontmove) {
GETENTRY(p)
}
else dontmove = 0;
/* does it still match prefix? */
if (keybuf.len < (size_t) prefixbuf.len) break;
if (prefixbuf.len && memcmp(keybuf.s, prefixbuf.s, prefixbuf.len)) break;
if (!goodp || goodp(rock, keybuf.s, keybuf.len, DATA(db), DATALEN(db))) {
unsigned long ino = db->ino;
unsigned long sz = db->size;
if(mytid) {
/* transaction present, this means we do the slow way */
buf_copy(&savebuf, &keybuf);
}
/* make callback */
r = cb(rock, keybuf.s, keybuf.len, DATA(db), DATALEN(db));
if (r) break;
if(mytid) {
/* reposition? (we made a change) */
if (!(ino == db->ino && sz == db->size)) {
/* something changed in the file; reseek */
buf_cstring(&savebuf);
offset = bsearch_mem_mbox(savebuf.s, db->base, db->size,
0, &len);
p = db->base + offset;
GETENTRY(p);
/* 'key' might not equal 'savebuf'. if it's different,
we want to stay where we are. if it's the same, we
should move on to the next one */
if (!buf_cmp(&savebuf, &keybuf)) {
p = dataend + 1;
} else {
/* 'savebuf' got deleted, so we're now pointing at the
right thing */
dontmove = 1;
}
}
}
}
p = dataend + 1;
}
if(!mytid) {
/* cleanup the fast method */
map_free(&dbbase, &dblen);
close(dbfd);
}
buf_free(&savebuf);
buf_free(&keybuf);
buf_free(&prefixbuf);
return r;
}
#undef GETENTRY
static int mystore(struct dbengine *db,
const char *key, size_t keylen,
const char *data, size_t datalen,
struct txn **mytid, int overwrite)
{
int r = 0;
char fnamebuf[1024];
int offset;
unsigned long len;
const char *lockfailaction;
int writefd;
struct iovec iov[10];
int niov;
struct stat sbuf;
struct buf keybuf = BUF_INITIALIZER;
struct buf databuf = BUF_INITIALIZER;
/* lock file, if needed */
if (!mytid || !*mytid) {
r = lock_reopen(db->fd, db->fname, &sbuf, &lockfailaction);
if (r < 0) {
syslog(LOG_ERR, "IOERROR: %s %s: %m", lockfailaction, db->fname);
return CYRUSDB_IOERROR;
}
if (sbuf.st_ino != db->ino) {
db->ino = sbuf.st_ino;
map_free(&db->base, &db->len);
map_refresh(db->fd, 0, &db->base, &db->len,
sbuf.st_size, db->fname, 0);
db->size = sbuf.st_size;
}
if (mytid) {
*mytid = new_txn();
}
}
encode(key, keylen, &keybuf);
/* find entry, if it exists */
offset = bsearch_mem_mbox(keybuf.s, db->base, db->size, 0, &len);
/* overwrite? */
if (len && !overwrite) {
if (mytid) abort_txn(db, *mytid);
buf_free(&keybuf);
buf_free(&databuf);
return CYRUSDB_EXISTS;
}
/* write new file */
if (mytid && (*mytid)->fnamenew) {
strlcpy(fnamebuf, (*mytid)->fnamenew, sizeof(fnamebuf));
} else {
strlcpy(fnamebuf, db->fname, sizeof(fnamebuf));
strlcat(fnamebuf, ".NEW", sizeof(fnamebuf));
}
unlink(fnamebuf);
r = writefd = open(fnamebuf, O_RDWR | O_CREAT, 0666);
if (r < 0) {
syslog(LOG_ERR, "opening %s for writing failed: %m", fnamebuf);
if (mytid) abort_txn(db, *mytid);
buf_free(&keybuf);
buf_free(&databuf);
return CYRUSDB_IOERROR;
}
niov = 0;
if (offset) {
WRITEV_ADD_TO_IOVEC(iov, niov, (char *) db->base, offset);
}
if (data) {
/* new entry */
encode(data, datalen, &databuf);
WRITEV_ADD_TO_IOVEC(iov, niov, keybuf.s, keybuf.len);
WRITEV_ADD_TO_IOVEC(iov, niov, "\t", 1);
WRITEV_ADD_TO_IOVEC(iov, niov, databuf.s, databuf.len);
WRITEV_ADD_TO_IOVEC(iov, niov, "\n", 1);
}
if (db->size - (offset + len) > 0) {
WRITEV_ADD_TO_IOVEC(iov, niov, (char *) db->base + offset + len,
db->size - (offset + len));
}
/* do the write */
r = retry_writev(writefd, iov, niov);
if (r == -1) {
syslog(LOG_ERR, "IOERROR: writing %s: %m", fnamebuf);
close(writefd);
if (mytid) abort_txn(db, *mytid);
buf_free(&keybuf);
buf_free(&databuf);
return CYRUSDB_IOERROR;
}
r = 0;
if (mytid) {
/* setup so further accesses will be against fname.NEW */
if (fstat(writefd, &sbuf) == -1) {
/* xxx ? */
}
if (!(*mytid)->fnamenew) (*mytid)->fnamenew = xstrdup(fnamebuf);
if ((*mytid)->fd) close((*mytid)->fd);
(*mytid)->fd = writefd;
map_free(&db->base, &db->len);
map_refresh(writefd, 0, &db->base, &db->len, sbuf.st_size,
fnamebuf, 0);
db->size = sbuf.st_size;
} else {
/* commit immediately */
if (fsync(writefd) ||
fstat(writefd, &sbuf) == -1 ||
rename(fnamebuf, db->fname) == -1) {
syslog(LOG_ERR, "IOERROR: writing %s: %m", fnamebuf);
close(writefd);
buf_free(&keybuf);
buf_free(&databuf);
return CYRUSDB_IOERROR;
}
close(db->fd);
db->fd = writefd;
/* release lock */
r = lock_unlock(db->fd);
if (r == -1) {
syslog(LOG_ERR, "IOERROR: unlocking db %s: %m", db->fname);
r = CYRUSDB_IOERROR;
}
db->ino = sbuf.st_ino;
map_free(&db->base, &db->len);
map_refresh(writefd, 0, &db->base, &db->len, sbuf.st_size,
db->fname, 0);
db->size = sbuf.st_size;
}
buf_free(&keybuf);
buf_free(&databuf);
return r;
}
static int create(struct dbengine *db,
const char *key, size_t keylen,
const char *data, size_t datalen,
struct txn **tid)
{
if (!data) {
data = "";
datalen = 0;
}
return mystore(db, key, keylen, data, datalen, tid, 0);
}
static int store(struct dbengine *db,
const char *key, size_t keylen,
const char *data, size_t datalen,
struct txn **tid)
{
if (!data) {
data = "";
datalen = 0;
}
return mystore(db, key, keylen, data, datalen, tid, 1);
}
static int delete(struct dbengine *db,
const char *key, size_t keylen,
struct txn **mytid, int force __attribute__((unused)))
{
return mystore(db, key, keylen, NULL, 0, mytid, 1);
}
static int commit_txn(struct dbengine *db, struct txn *tid)
{
int writefd;
int r = 0;
struct stat sbuf;
assert(db && tid);
if (tid->fnamenew) {
/* we wrote something */
writefd = tid->fd;
if (fsync(writefd) ||
fstat(writefd, &sbuf) == -1 ||
rename(tid->fnamenew, db->fname) == -1) {
syslog(LOG_ERR, "IOERROR: writing %s: %m", tid->fnamenew);
close(writefd);
r = CYRUSDB_IOERROR;
} else {
/* successful */
/* we now deal exclusively with our new fd */
close(db->fd);
db->fd = writefd;
db->ino = sbuf.st_ino;
}
free(tid->fnamenew);
} else {
/* read-only txn */
/* release lock */
r = lock_unlock(db->fd);
if (r == -1) {
syslog(LOG_ERR, "IOERROR: unlocking db %s: %m", db->fname);
r = CYRUSDB_IOERROR;
}
}
free(tid);
return r;
}
/* flat database is always mbox sort order */
static int mycompar(struct dbengine *db __attribute__((unused)),
const char *a, int alen,
const char *b, int blen)
{
return bsearch_ncompare_mbox(a, alen, b, blen);
}
EXPORTED struct cyrusdb_backend cyrusdb_flat =
{
"flat", /* name */
&cyrusdb_generic_init,
&cyrusdb_generic_done,
&cyrusdb_generic_sync,
&cyrusdb_generic_archive,
&myopen,
&myclose,
&fetch,
&fetchlock,
NULL,
&foreach,
&create,
&store,
&delete,
&commit_txn,
&abort_txn,
NULL,
NULL,
NULL,
&mycompar
};
diff --git a/lib/hash.c b/lib/hash.c
index 47ec190b8..1ec222733 100644
--- a/lib/hash.c
+++ b/lib/hash.c
@@ -1,318 +1,317 @@
/* +++Date last modified: 05-Jul-1997 */
/* $Id: hash.c,v 1.13 2006/11/30 17:11:22 murch Exp $ */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <stdlib.h>
#include "assert.h"
#include "hash.h"
#include "mpool.h"
#include "strhash.h"
#include "xmalloc.h"
-#include "exitcodes.h"
/*
** public domain code by Jerry Coffin, with improvements by HenkJan Wolthuis.
**
** Tested with Visual C 1.0 and Borland C 3.1.
** Compiles without warnings, and seems like it should be pretty
** portable.
**
** Modified for use with libcyrus by Ken Murchison.
** - prefixed functions with 'hash_' to avoid symbol clashing
** - use xmalloc() and xstrdup()
** - cleaned up free_hash_table(), doesn't use enumerate anymore
** - added 'rock' to hash_enumerate()
**
** Further modified by Rob Siemborski.
** - xmalloc can never return NULL, so don't worry about it
** - sort the buckets for faster searching
** - actually, we'll just use a memory pool for this sucker
** (atleast, in the cases where it is advantageous to do so)
*/
/* Initialize the hash_table to the size asked for. Allocates space
** for the correct number of pointers and sets them to NULL. If it
** can't allocate sufficient memory, signals error by setting the size
** of the table to 0.
*/
EXPORTED hash_table *construct_hash_table(hash_table *table, size_t size, int use_mpool)
{
assert(table);
assert(size);
table->size = size;
/* Allocate the table -- different for using memory pools and not */
if(use_mpool) {
/* Allocate an initial memory pool for 32 byte keys + the hash table
* + the buckets themselves */
table->pool =
new_mpool(size * (32 + sizeof(bucket*) + sizeof(bucket)));
table->table =
(bucket **)mpool_malloc(table->pool,sizeof(bucket *) * size);
} else {
table->pool = NULL;
table->table = xmalloc(sizeof(bucket *) * size);
}
/* Allocate the table and initilize it */
memset(table->table, 0, sizeof(bucket *) * size);
return table;
}
/*
** Insert 'key' into hash table.
** Returns a non-NULL pointer which is either the passed @data pointer
** or, if there was already an entry for @key, the old data pointer.
*/
EXPORTED void *hash_insert(const char *key, void *data, hash_table *table)
{
unsigned val = strhash(key) % table->size;
bucket *ptr, *newptr;
bucket **prev;
/*
** NULL means this bucket hasn't been used yet. We'll simply
** allocate space for our new bucket and put our data there, with
** the table pointing at it.
*/
if (!((table->table)[val]))
{
if(table->pool) {
(table->table)[val] =
(bucket *)mpool_malloc(table->pool, sizeof(bucket));
(table->table)[val] -> key = mpool_strdup(table->pool, key);
} else {
(table->table)[val] = (bucket *)xmalloc(sizeof(bucket));
(table->table)[val] -> key = xstrdup(key);
}
(table->table)[val] -> next = NULL;
(table->table)[val] -> data = data;
return (table->table)[val] -> data;
}
/*
** This spot in the table is already in use. See if the current string
** has already been inserted, and if so, increment its count.
*/
for (prev = &((table->table)[val]), ptr=(table->table)[val];
ptr;
prev=&(ptr->next),ptr=ptr->next) {
int cmpresult = strcmp(key,ptr->key);
if (!cmpresult) {
/* Match! Replace this value and return the old */
void *old_data;
old_data = ptr->data;
ptr -> data = data;
return old_data;
} else if (cmpresult < 0) {
/* The new key is smaller than the current key--
* insert a node and return this data */
if(table->pool) {
newptr = (bucket *)mpool_malloc(table->pool, sizeof(bucket));
newptr->key = mpool_strdup(table->pool, key);
} else {
newptr = (bucket *)xmalloc(sizeof(bucket));
newptr->key = xstrdup(key);
}
newptr->data = data;
newptr->next = ptr;
*prev = newptr;
return data;
}
}
/*
** This key is the largest one so far. Add it to the end
** of the list (*prev should be correct)
*/
if(table->pool) {
newptr=(bucket *)mpool_malloc(table->pool,sizeof(bucket));
newptr->key = mpool_strdup(table->pool,key);
} else {
newptr=(bucket *)xmalloc(sizeof(bucket));
newptr->key = xstrdup(key);
}
newptr->data = data;
newptr->next = NULL;
*prev = newptr;
return data;
}
/*
** Look up a key and return the associated data. Returns NULL if
** the key is not in the table.
*/
EXPORTED void *hash_lookup(const char *key, hash_table *table)
{
unsigned val = strhash(key) % table->size;
bucket *ptr;
if (!(table->table)[val])
return NULL;
for ( ptr = (table->table)[val];NULL != ptr; ptr = ptr->next )
{
int cmpresult = strcmp(key, ptr->key);
if (!cmpresult)
return ptr->data;
else if(cmpresult < 0) /* key < ptr->key -- we passed it */
return NULL;
}
return NULL;
}
/*
** Delete a key from the hash table and return associated
** data, or NULL if not present.
*/
/* Warning: use this function judiciously if you are using memory pools,
* since it will leak memory until you get rid of the entire hash table */
EXPORTED void *hash_del(const char *key, hash_table *table)
{
unsigned val = strhash(key) % table->size;
void *data;
bucket *ptr, *last = NULL;
if (!(table->table)[val])
return NULL;
/*
** Traverse the list, keeping track of the previous node in the list.
** When we find the node to delete, we set the previous node's next
** pointer to point to the node after ourself instead. We then delete
** the key from the present node, and return a pointer to the data it
** contains.
*/
for (last = NULL, ptr = (table->table)[val];
NULL != ptr;
last = ptr, ptr = ptr->next)
{
int cmpresult = strcmp(key, ptr->key);
if (!cmpresult)
{
if (last != NULL )
{
data = ptr -> data;
last -> next = ptr -> next;
if(!table->pool) {
free(ptr->key);
free(ptr);
}
return data;
}
/*
** If 'last' still equals NULL, it means that we need to
** delete the first node in the list. This simply consists
** of putting our own 'next' pointer in the array holding
** the head of the list. We then dispose of the current
** node as above.
*/
else
{
data = ptr->data;
(table->table)[val] = ptr->next;
if(!table->pool) {
free(ptr->key);
free(ptr);
}
return data;
}
} else if (cmpresult < 0) {
/* its not here! */
return NULL;
}
}
/*
** If we get here, it means we didn't find the item in the table.
** Signal this by returning NULL.
*/
return NULL;
}
/*
** Frees a complete table by iterating over it and freeing each node.
** the second parameter is the address of a function it will call with a
** pointer to the data associated with each node. This function is
** responsible for freeing the data, or doing whatever is needed with
** it.
*/
EXPORTED void free_hash_table(hash_table *table, void (*func)(void *))
{
unsigned i;
bucket *ptr, *temp;
/* If we have a function to free the data, apply it everywhere */
/* We also need to traverse this anyway if we aren't using a memory
* pool */
if(func || !table->pool) {
for (i=0;i<table->size; i++)
{
ptr = (table->table)[i];
while (ptr)
{
temp = ptr;
ptr = ptr->next;
if (func)
func(temp->data);
if(!table->pool) {
free(temp->key);
free(temp);
}
}
}
}
/* Free the main structures */
if(table->pool) {
free_mpool(table->pool);
table->pool = NULL;
} else {
free(table->table);
}
table->table = NULL;
table->size = 0;
}
/*
** Simply invokes the function given as the second parameter for each
** node in the table, passing it the key, the associated data and 'rock'.
*/
EXPORTED void hash_enumerate(hash_table *table, void (*func)(const char *, void *, void *),
void *rock)
{
unsigned i;
bucket *temp, *temp_next;
for (i=0;i<table->size; i++)
{
if ((table->table)[i] != NULL)
{
for (temp = (table->table)[i];
NULL != temp;
temp = temp_next)
{
temp_next = temp->next;
func(temp -> key, temp->data, rock);
}
}
}
}
diff --git a/lib/hashu64.c b/lib/hashu64.c
index 75cc51b2d..2d70eb995 100644
--- a/lib/hashu64.c
+++ b/lib/hashu64.c
@@ -1,319 +1,318 @@
/* +++Date last modified: 05-Jul-1997 */
/* $Id: hash.c,v 1.13 2006/11/30 17:11:22 murch Exp $ */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <stdlib.h>
#include "assert.h"
#include "hashu64.h"
#include "mpool.h"
#include "xmalloc.h"
-#include "exitcodes.h"
/*
** public domain code by Jerry Coffin, with improvements by HenkJan Wolthuis.
**
** Tested with Visual C 1.0 and Borland C 3.1.
** Compiles without warnings, and seems like it should be pretty
** portable.
**
** Modified for use with libcyrus by Ken Murchison.
** - prefixed functions with 'hash_' to avoid symbol clashing
** - use xmalloc() and xstrdup()
** - cleaned up free_hash_table(), doesn't use enumerate anymore
** - added 'rock' to hash_enumerate()
**
** Further modified by Rob Siemborski.
** - xmalloc can never return NULL, so don't worry about it
** - sort the buckets for faster searching
** - actually, we'll just use a memory pool for this sucker
** (atleast, in the cases where it is advantageous to do so)
*/
/* Initialize the hashu64_table to the size asked for. Allocates space
** for the correct number of pointers and sets them to NULL. If it
** can't allocate sufficient memory, signals error by setting the size
** of the table to 0.
*/
static inline int u64cmp(uint64_t a, uint64_t b)
{
return (a < b ? -1 : (a > b ? 1 : 0));
}
hashu64_table *construct_hashu64_table(hashu64_table *table, size_t size, int use_mpool)
{
assert(table);
assert(size);
table->size = size;
/* Allocate the table -- different for using memory pools and not */
if(use_mpool) {
/* Allocate an initial memory pool for 32 byte keys + the hash table
* + the buckets themselves */
table->pool =
new_mpool(size * (32 + sizeof(bucketu64*) + sizeof(bucketu64)));
table->table =
(bucketu64 **)mpool_malloc(table->pool,sizeof(bucketu64 *) * size);
} else {
table->pool = NULL;
table->table = xmalloc(sizeof(bucketu64 *) * size);
}
/* Allocate the table and initilize it */
memset(table->table, 0, sizeof(bucketu64 *) * size);
return table;
}
/*
** Insert 'key' into hashu64 table.
** Returns a non-NULL pointer which is either the passed @data pointer
** or, if there was already an entry for @key, the old data pointer.
*/
void *hashu64_insert(uint64_t key, void *data, hashu64_table *table)
{
unsigned val = key % table->size;
bucketu64 *ptr, *newptr;
bucketu64 **prev;
/*
** NULL means this bucket hasn't been used yet. We'll simply
** allocate space for our new bucket and put our data there, with
** the table pointing at it.
*/
if (!((table->table)[val]))
{
if(table->pool) {
(table->table)[val] =
(bucketu64 *)mpool_malloc(table->pool, sizeof(bucketu64));
(table->table)[val] -> key = key;
} else {
(table->table)[val] = (bucketu64 *)xmalloc(sizeof(bucketu64));
(table->table)[val] -> key = key;
}
(table->table)[val] -> next = NULL;
(table->table)[val] -> data = data;
return (table->table)[val] -> data;
}
/*
** This spot in the table is already in use. See if the current string
** has already been inserted, and if so, increment its count.
*/
for (prev = &((table->table)[val]), ptr=(table->table)[val];
ptr;
prev=&(ptr->next),ptr=ptr->next) {
int cmpresult = u64cmp(key, ptr->key);
if (!cmpresult) {
/* Match! Replace this value and return the old */
void *old_data;
old_data = ptr->data;
ptr -> data = data;
return old_data;
} else if (cmpresult < 0) {
/* The new key is smaller than the current key--
* insert a node and return this data */
if(table->pool) {
newptr = (bucketu64 *)mpool_malloc(table->pool, sizeof(bucketu64));
newptr->key = key;
} else {
newptr = (bucketu64 *)xmalloc(sizeof(bucketu64));
newptr->key = key;
}
newptr->data = data;
newptr->next = ptr;
*prev = newptr;
return data;
}
}
/*
** This key is the largest one so far. Add it to the end
** of the list (*prev should be correct)
*/
if(table->pool) {
newptr=(bucketu64 *)mpool_malloc(table->pool,sizeof(bucketu64));
newptr->key = key;
} else {
newptr=(bucketu64 *)xmalloc(sizeof(bucketu64));
newptr->key = key;
}
newptr->data = data;
newptr->next = NULL;
*prev = newptr;
return data;
}
/*
** Look up a key and return the associated data. Returns NULL if
** the key is not in the table.
*/
void *hashu64_lookup(uint64_t key, hashu64_table *table)
{
unsigned val = key % table->size;
bucketu64 *ptr;
if (!(table->table)[val])
return NULL;
for ( ptr = (table->table)[val];NULL != ptr; ptr = ptr->next )
{
int cmpresult = u64cmp(key, ptr->key);
if (!cmpresult)
return ptr->data;
else if(cmpresult < 0) /* key < ptr->key -- we passed it */
return NULL;
}
return NULL;
}
/*
** Delete a key from the hashu64 table and return associated
** data, or NULL if not present.
*/
/* Warning: use this function judiciously if you are using memory pools,
* since it will leak memory until you get rid of the entire hash table */
void *hashu64_del(uint64_t key, hashu64_table *table)
{
unsigned val = key % table->size;
void *data;
bucketu64 *ptr, *last = NULL;
if (!(table->table)[val])
return NULL;
/*
** Traverse the list, keeping track of the previous node in the list.
** When we find the node to delete, we set the previous node's next
** pointer to point to the node after ourself instead. We then delete
** the key from the present node, and return a pointer to the data it
** contains.
*/
for (last = NULL, ptr = (table->table)[val];
NULL != ptr;
last = ptr, ptr = ptr->next)
{
int cmpresult = u64cmp(key, ptr->key);
if (!cmpresult)
{
if (last != NULL )
{
data = ptr -> data;
last -> next = ptr -> next;
if(!table->pool) {
free(ptr);
}
return data;
}
/*
** If 'last' still equals NULL, it means that we need to
** delete the first node in the list. This simply consists
** of putting our own 'next' pointer in the array holding
** the head of the list. We then dispose of the current
** node as above.
*/
else
{
data = ptr->data;
(table->table)[val] = ptr->next;
if(!table->pool) {
free(ptr);
}
return data;
}
} else if (cmpresult < 0) {
/* its not here! */
return NULL;
}
}
/*
** If we get here, it means we didn't find the item in the table.
** Signal this by returning NULL.
*/
return NULL;
}
/*
** Frees a complete table by iterating over it and freeing each node.
** the second parameter is the address of a function it will call with a
** pointer to the data associated with each node. This function is
** responsible for freeing the data, or doing whatever is needed with
** it.
*/
void free_hashu64_table(hashu64_table *table, void (*func)(void *))
{
unsigned i;
bucketu64 *ptr, *temp;
/* If we have a function to free the data, apply it everywhere */
/* We also need to traverse this anyway if we aren't using a memory
* pool */
if(func || !table->pool) {
for (i=0;i<table->size; i++)
{
ptr = (table->table)[i];
while (ptr)
{
temp = ptr;
ptr = ptr->next;
if (func)
func(temp->data);
if(!table->pool) {
free(temp);
}
}
}
}
/* Free the main structures */
if(table->pool) {
free_mpool(table->pool);
table->pool = NULL;
} else {
free(table->table);
}
table->table = NULL;
table->size = 0;
}
/*
** Simply invokes the function given as the second parameter for each
** node in the table, passing it the key, the associated data and 'rock'.
*/
void hashu64_enumerate(hashu64_table *table, void (*func)(uint64_t, void *, void *),
void *rock)
{
unsigned i;
bucketu64 *temp, *temp_next;
for (i=0;i<table->size; i++)
{
if ((table->table)[i] != NULL)
{
for (temp = (table->table)[i];
NULL != temp;
temp = temp_next)
{
temp_next = temp->next;
func(temp -> key, temp->data, rock);
}
}
}
}
diff --git a/lib/libconfig.c b/lib/libconfig.c
index 002263528..0651f86b6 100644
--- a/lib/libconfig.c
+++ b/lib/libconfig.c
@@ -1,735 +1,736 @@
/* libconfig.c -- imapd.conf handling
*
* Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* $Id: libconfig.c,v 1.26 2010/04/19 19:54:26 murch Exp $
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <syslog.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include "assert.h"
+#include "exitcodes.h"
#include "hash.h"
#include "libconfig.h"
#include "xmalloc.h"
#include "xstrlcat.h"
#include "xstrlcpy.h"
#include "util.h"
#define CONFIGHASHSIZE 30 /* relatively small,
* because it is for overflow only */
#define INCLUDEHASHSIZE 5 /* relatively small,
* but how many includes are reasonable? */
static struct hash_table confighash, includehash;
/* cached configuration variables accessible to the external world */
EXPORTED const char *config_filename= NULL; /* filename of configuration file */
EXPORTED const char *config_dir = NULL; /* ie /var/imap */
EXPORTED const char *config_defpartition = NULL; /* /var/spool/imap */
EXPORTED const char *config_servername= NULL; /* gethostname() */
EXPORTED enum enum_value config_serverinfo; /* on */
EXPORTED const char *config_mupdate_server = NULL;/* NULL */
EXPORTED const char *config_defdomain = NULL; /* NULL */
EXPORTED const char *config_ident = NULL; /* the service name */
EXPORTED int config_hashimapspool; /* f */
EXPORTED enum enum_value config_virtdomains; /* f */
EXPORTED enum enum_value config_mupdate_config; /* IMAP_ENUM_MUPDATE_CONFIG_STANDARD */
EXPORTED int config_auditlog;
EXPORTED int config_iolog;
EXPORTED unsigned config_maxword;
EXPORTED unsigned config_maxquoted;
EXPORTED int config_qosmarking;
EXPORTED int config_debug;
extern void fatal(const char *fatal_message, int fatal_code)
__attribute__ ((noreturn));
/* prototype to allow for sane function ordering */
static void config_read_file(const char *filename);
EXPORTED const char *config_getstring(enum imapopt opt)
{
assert(opt > IMAPOPT_ZERO && opt < IMAPOPT_LAST);
assert((imapopts[opt].t == OPT_STRING) ||
(imapopts[opt].t == OPT_STRINGLIST));
return imapopts[opt].val.s;
}
EXPORTED int config_getint(enum imapopt opt)
{
assert(opt > IMAPOPT_ZERO && opt < IMAPOPT_LAST);
assert(imapopts[opt].t == OPT_INT);
#if (SIZEOF_LONG != 4)
if ((imapopts[opt].val.i > 0x7fffffff)||
(imapopts[opt].val.i < -0x7fffffff)) {
syslog(LOG_ERR, "config_getint: %s: %ld too large for type",
imapopts[opt].optname, imapopts[opt].val.i);
}
#endif
return imapopts[opt].val.i;
}
EXPORTED int config_getswitch(enum imapopt opt)
{
assert(opt > IMAPOPT_ZERO && opt < IMAPOPT_LAST);
assert(imapopts[opt].t == OPT_SWITCH);
#if (SIZEOF_LONG != 4)
if ((imapopts[opt].val.b > 0x7fffffff)||
(imapopts[opt].val.b < -0x7fffffff)) {
syslog(LOG_ERR, "config_getswitch: %s: %ld too large for type",
imapopts[opt].optname, imapopts[opt].val.b);
}
#endif
return imapopts[opt].val.b;
}
EXPORTED enum enum_value config_getenum(enum imapopt opt)
{
assert(opt > IMAPOPT_ZERO && opt < IMAPOPT_LAST);
assert(imapopts[opt].t == OPT_ENUM);
return imapopts[opt].val.e;
}
EXPORTED unsigned long config_getbitfield(enum imapopt opt)
{
assert(opt > IMAPOPT_ZERO && opt < IMAPOPT_LAST);
assert(imapopts[opt].t == OPT_BITFIELD);
return imapopts[opt].val.x;
}
EXPORTED const char *config_getoverflowstring(const char *key, const char *def)
{
char buf[256];
char *ret = NULL;
if (!config_filename) return 0;
/* First lookup <ident>_key, to see if we have a service-specific
* override */
if(config_ident) {
if(snprintf(buf,sizeof(buf),"%s_%s",config_ident,key) == -1)
fatal("key too long in config_getoverflowstring", EC_TEMPFAIL);
lcase(buf);
ret = hash_lookup(buf, &confighash);
}
/* No service-specific override, check the actual key */
if(!ret)
ret = hash_lookup(key, &confighash);
/* Return what we got or the default */
return ret ? ret : def;
}
EXPORTED void config_foreachoverflowstring(void (*func)(const char *, const char *, void *),
void *rock)
{
if (!config_filename) return;
hash_enumerate(&confighash, (void (*)(const char *, void *, void *)) func, rock);
}
EXPORTED const char *config_partitiondir(const char *partition)
{
char buf[80];
if(strlcpy(buf, "partition-", sizeof(buf)) >= sizeof(buf))
return 0;
if(strlcat(buf, partition, sizeof(buf)) >= sizeof(buf))
return 0;
return config_getoverflowstring(buf, NULL);
}
EXPORTED const char *config_metapartitiondir(const char *partition)
{
char buf[80];
if(strlcpy(buf, "metapartition-", sizeof(buf)) >= sizeof(buf))
return 0;
if(strlcat(buf, partition, sizeof(buf)) >= sizeof(buf))
return 0;
return config_getoverflowstring(buf, NULL);
}
static void config_ispartition(const char *key,
const char *val __attribute__((unused)),
void *rock)
{
int *found = (int *) rock;
if (!strncmp("partition-", key, 10)) *found = 1;
}
/*
* Reset the global configuration to a virginal state. This is
* only useful for unit tests.
*/
EXPORTED void config_reset(void)
{
enum imapopt opt;
if (!config_filename)
return;
free((char *)config_filename);
config_filename = NULL;
if (config_servername != config_getstring(IMAPOPT_SERVERNAME))
free((char *)config_servername);
config_servername = NULL;
config_defpartition = NULL;
config_mupdate_server = NULL;
config_mupdate_config = 0;
config_hashimapspool = 0;
config_virtdomains = 0;
config_defdomain = NULL;
config_auditlog = 0;
config_serverinfo = 0;
config_maxquoted = 0;
config_maxword = 0;
config_qosmarking = 0;
config_debug = 0;
/* reset all the options */
for (opt = IMAPOPT_ZERO; opt < IMAPOPT_LAST; opt++) {
if (imapopts[opt].t == OPT_STRING &&
(imapopts[opt].seen ||
(imapopts[opt].def.s &&
!strncasecmp(imapopts[opt].def.s, "{configdirectory}", 17))))
free((char *)imapopts[opt].val.s);
memcpy(&imapopts[opt].val,
&imapopts[opt].def,
sizeof(imapopts[opt].val));
imapopts[opt].seen = 0;
}
config_dir = NULL;
/* free the overflow table */
free_hash_table(&confighash, free);
}
static const unsigned char qos[] = {
/* cs0..cs7 */ 0x00, 0x20, 0x40, 0x60, 0x80, 0xa0, 0xc0, 0xe0,
/* af11..af13 */ 0x28, 0x30, 0x38,
/* af21..af23 */ 0x48, 0x50, 0x58,
/* af31..af33 */ 0x68, 0x70, 0x78,
/* af41..af43 */ 0x88, 0x90, 0x98,
/* ef */ 0xb8
};
EXPORTED void config_read(const char *alt_config, const int config_need_data)
{
enum imapopt opt = IMAPOPT_ZERO;
char buf[4096];
char *p;
int ival;
/* xxx this is leaked, this may be able to be better in 2.2 (cyrus_done) */
if(alt_config) config_filename = xstrdup(alt_config);
else config_filename = xstrdup(CONFIG_FILENAME);
if(!construct_hash_table(&confighash, CONFIGHASHSIZE, 1)) {
fatal("could not construct configuration hash table", EC_CONFIG);
}
if(!construct_hash_table(&includehash, INCLUDEHASHSIZE, 1)) {
fatal("could not construct include file hash table", EC_CONFIG);
}
config_read_file(config_filename);
free_hash_table(&includehash, NULL);
/* Check configdirectory config option */
if (!config_dir) {
fatal("configdirectory option not specified in configuration file",
EC_CONFIG);
}
/* Scan options to see if we need to replace {configdirectory} */
/* xxx need to scan overflow options as well! */
for(opt = IMAPOPT_ZERO; opt < IMAPOPT_LAST; opt++) {
if(!imapopts[opt].val.s ||
imapopts[opt].t != OPT_STRING ||
opt == IMAPOPT_CONFIGDIRECTORY) {
/* Skip options that have a NULL value, aren't strings, or
* are the configdirectory option */
continue;
}
/* We use some magic numbers here,
* 17 is the length of "{configdirectory}",
* 16 is one less than that length, so that the replacement string
* that is malloced has room for the '\0' */
if(!strncasecmp(imapopts[opt].val.s,"{configdirectory}",17)) {
const char *str = imapopts[opt].val.s;
char *newstring =
xmalloc(strlen(config_dir) + strlen(str) - 16);
char *freeme = NULL;
/* we need to replace this string, will we need to free
* the current value? -- only if we've actually seen it in
* the config file. */
if(imapopts[opt].seen)
freeme = (char *)str;
/* Build replacement string from configdirectory option */
strcpy(newstring, config_dir);
strcat(newstring, str + 17);
imapopts[opt].val.s = newstring;
if(freeme) free(freeme);
}
}
/* Look up default partition */
config_defpartition = config_getstring(IMAPOPT_DEFAULTPARTITION);
for (p = (char *)config_defpartition; p && *p; p++) {
if (!Uisalnum(*p)) {
syslog(LOG_ERR, "INVALID defaultpartition: %s",
config_defpartition);
fatal("defaultpartition option contains non-alnum character",
EC_CONFIG);
}
if (Uisupper(*p)) *p = tolower((unsigned char) *p);
}
config_mupdate_server = config_getstring(IMAPOPT_MUPDATE_SERVER);
if (config_mupdate_server) {
config_mupdate_config = config_getenum(IMAPOPT_MUPDATE_CONFIG);
}
if (config_need_data & CONFIG_NEED_PARTITION_DATA) {
int found = 0;
if (config_defpartition) {
/* see if defaultpartition is specified properly */
if (config_partitiondir(config_defpartition)) found = 1;
}
else if ((config_mupdate_config == IMAP_ENUM_MUPDATE_CONFIG_STANDARD)
&& !config_getstring(IMAPOPT_PROXYSERVERS)) {
found = 1; /* don't need partitions on the frontend */
}
else {
/* see if we have ANY partition-<name> options */
config_foreachoverflowstring(config_ispartition, &found);
}
if (!found) {
snprintf(buf, sizeof(buf),
"partition-%s option not specified in configuration file",
config_defpartition ? config_defpartition : "<name>");
fatal(buf, EC_CONFIG);
}
}
/* look up mailbox hashing */
config_hashimapspool = config_getswitch(IMAPOPT_HASHIMAPSPOOL);
/* are we supporting virtual domains? */
config_virtdomains = config_getenum(IMAPOPT_VIRTDOMAINS);
config_defdomain = config_getstring(IMAPOPT_DEFAULTDOMAIN);
/* are we auditlogging */
config_auditlog = config_getswitch(IMAPOPT_AUDITLOG);
/* are we doing I/O logging */
config_iolog = config_getswitch(IMAPOPT_IOLOG);
if (config_iolog) {
if (access("/proc/self/io", R_OK)) {
config_iolog = 0;
syslog(LOG_WARNING,"iolog directive need a kernel builded with I/O account");
}
}
/* look up the hostname and info we should present to the user */
config_servername = config_getstring(IMAPOPT_SERVERNAME);
if (!config_servername) {
config_servername = xmalloc(sizeof(char) * 256);
gethostname((char *) config_servername, 256);
}
config_serverinfo = config_getenum(IMAPOPT_SERVERINFO);
/* set some limits */
config_maxquoted = config_getint(IMAPOPT_MAXQUOTED);
config_maxword = config_getint(IMAPOPT_MAXWORD);
ival = config_getenum(IMAPOPT_QOSMARKING);
config_qosmarking = qos[ival];
/* allow debug logging */
config_debug = config_getswitch(IMAPOPT_DEBUG);
}
#define GROWSIZE 4096
static void config_read_file(const char *filename)
{
FILE *infile = NULL;
enum imapopt opt = IMAPOPT_ZERO;
int lineno = 0;
char *buf, errbuf[1024];
const char *cyrus_path;
unsigned bufsize, len;
char *p, *q, *key, *fullkey, *srvkey, *val, *newval;
int service_specific;
int idlen = (config_ident ? strlen(config_ident) : 0);
bufsize = GROWSIZE;
buf = xmalloc(bufsize);
/* read in config file
Check if we have CYRUS_PREFIX defined, and then use that config */
cyrus_path = getenv("CYRUS_PREFIX");
if (cyrus_path) {
strlcpy(buf, cyrus_path, bufsize);
strlcat(buf, filename, bufsize);
infile = fopen(buf, "r");
}
if (!infile)
infile = fopen(filename, "r");
if (!infile) {
snprintf(buf, bufsize, "can't open configuration file %s: %s",
filename, strerror(errno));
fatal(buf, EC_CONFIG);
}
/* check to see if we've already read this file */
if (hash_lookup(filename, &includehash)) {
snprintf(buf, bufsize, "configuration file %s included twice",
filename);
fatal(buf, EC_CONFIG);
}
else {
hash_insert(filename, (void*) 0xDEADBEEF, &includehash);
}
len = 0;
while (fgets(buf+len, bufsize-len, infile)) {
if (buf[len]) {
len = strlen(buf);
if (buf[len-1] == '\n') {
/* end of line */
buf[--len] = '\0';
if (len && buf[len-1] == '\\') {
/* line continuation */
len--;
lineno++;
continue;
}
}
else if (!feof(infile) && len == bufsize-1) {
/* line is longer than the buffer */
bufsize += GROWSIZE;
buf = xrealloc(buf, bufsize);
continue;
}
}
len = 0;
lineno++;
service_specific = 0;
/* remove leading whitespace */
for (p = buf; *p && Uisspace(*p); p++);
/* skip comments */
if (!*p || *p == '#') continue;
fullkey = key = p;
if (*p == '@') p++; /* allow @ as the first char (for directives) */
while (*p && (Uisalnum(*p) || *p == '-' || *p == '_')) {
if (Uisupper(*p)) *p = tolower((unsigned char) *p);
p++;
}
if (*p != ':') {
snprintf(errbuf, sizeof(errbuf),
"invalid option name on line %d of configuration file %s",
lineno, filename);
fatal(errbuf, EC_CONFIG);
}
*p++ = '\0';
/* remove leading whitespace */
while (*p && Uisspace(*p)) p++;
/* remove trailing whitespace */
for (q = p + strlen(p) - 1; q > p && Uisspace(*q); q--) {
*q = '\0';
}
if (!*p) {
snprintf(errbuf, sizeof(errbuf),
"empty option value on line %d of configuration file",
lineno);
fatal(errbuf, EC_CONFIG);
}
srvkey = NULL;
/* Look for directives */
if (key[0] == '@') {
if (!strcasecmp(key, "@include")) {
config_read_file(p);
continue;
}
else {
snprintf(errbuf, sizeof(errbuf),
"invalid directive on line %d of configuration file %s",
lineno, filename);
fatal(errbuf, EC_CONFIG);
}
}
/* Find if there is a <service>_ prefix */
if(config_ident && !strncasecmp(key, config_ident, idlen)
&& key[idlen] == '_') {
/* skip service_ prefix */
srvkey = key + idlen + 1;
}
/* look for a service_ prefix match in imapopts */
if(srvkey) {
for (opt = IMAPOPT_ZERO; opt < IMAPOPT_LAST; opt++) {
if (!strcasecmp(imapopts[opt].optname, srvkey)) {
key = srvkey;
service_specific = 1;
break;
}
}
}
/* Did not find a service_ specific match, try looking for an
* exact match */
if(!service_specific) {
for (opt = IMAPOPT_ZERO; opt < IMAPOPT_LAST; opt++) {
if (!strcasecmp(imapopts[opt].optname, key)) {
break;
}
}
}
/* If both of those loops failed, it goes verbatim into the
* overflow hash table. */
if (opt < IMAPOPT_LAST) {
/* Okay, we know about this configure option.
* So first check that we have either
* 1. not seen it
* 2. seen its generic form, but this is a service specific form
*
* If we have already seen a service-specific form, and this is
* a generic form, just skip it and don't moan.
*/
if((imapopts[opt].seen == 1 && !service_specific)
||(imapopts[opt].seen == 2 && service_specific)) {
sprintf(errbuf,
"option '%s' was specified twice in config file (second occurance on line %d)",
fullkey, lineno);
fatal(errbuf, EC_CONFIG);
} else if(imapopts[opt].seen == 2 && !service_specific) {
continue;
}
/* If we've seen it already, we're replacing it, so we need
* to free the current string if there is one */
if(imapopts[opt].seen && imapopts[opt].t == OPT_STRING)
free((char *)imapopts[opt].val.s);
if(service_specific)
imapopts[opt].seen = 2;
else
imapopts[opt].seen = 1;
/* this is a known option */
switch (imapopts[opt].t) {
case OPT_STRING:
{
imapopts[opt].val.s = xstrdup(p);
if(opt == IMAPOPT_CONFIGDIRECTORY)
config_dir = imapopts[opt].val.s;
break;
}
case OPT_INT:
{
long val;
char *ptr;
val = strtol(p, &ptr, 0);
if (!ptr || *ptr != '\0') {
/* error during conversion */
sprintf(errbuf, "non-integer value for %s in line %d",
imapopts[opt].optname, lineno);
fatal(errbuf, EC_CONFIG);
}
imapopts[opt].val.i = val;
break;
}
case OPT_SWITCH:
{
if (*p == '0' || *p == 'n' ||
(*p == 'o' && p[1] == 'f') || *p == 'f') {
imapopts[opt].val.b = 0;
}
else if (*p == '1' || *p == 'y' ||
(*p == 'o' && p[1] == 'n') || *p == 't') {
imapopts[opt].val.b = 1;
}
else {
/* error during conversion */
sprintf(errbuf, "non-switch value for %s in line %d",
imapopts[opt].optname, lineno);
fatal(errbuf, EC_CONFIG);
}
break;
}
case OPT_ENUM:
case OPT_STRINGLIST:
case OPT_BITFIELD:
{
const struct enum_option_s *e;
/* zero the value */
memset(&imapopts[opt].val, 0, sizeof(imapopts[opt].val));
/* q is already at EOS so we'll process entire the string
as one value unless told otherwise */
if (imapopts[opt].t == OPT_ENUM) {
/* normalize on/off values */
if (!strcmp(p, "1") || !strcmp(p, "yes") ||
!strcmp(p, "t") || !strcmp(p, "true")) {
p = "on";
} else if (!strcmp(p, "0") || !strcmp(p, "no") ||
!strcmp(p, "f") || !strcmp(p, "false")) {
p = "off";
}
} else if (imapopts[opt].t == OPT_BITFIELD) {
/* split the string into separate values */
q = p;
}
while (*p) {
/* find the end of the first value */
for (; *q && !Uisspace(*q); q++);
if (*q) *q++ = '\0';
/* see if its a legal value */
for (e = imapopts[opt].enum_options;
e->name && strcmp(e->name, p); e++);
if (!e->name) {
/* error during conversion */
sprintf(errbuf, "invalid value '%s' for %s in line %d",
p, imapopts[opt].optname, lineno);
fatal(errbuf, EC_CONFIG);
}
else if (imapopts[opt].t == OPT_STRINGLIST)
imapopts[opt].val.s = e->name;
else if (imapopts[opt].t == OPT_ENUM)
imapopts[opt].val.e = e->val;
else
imapopts[opt].val.x |= e->val;
/* find the start of the next value */
for (p = q; *p && Uisspace(*p); p++);
q = p;
}
break;
}
case OPT_NOTOPT:
default:
abort();
}
} else {
/* check to make sure it's valid for overflow */
/* that is, partition names and anything that might be
* used by SASL */
/*
xxx this would be nice if it wasn't for other services who might be
sharing this config file and whose names we cannot predict
if(strncasecmp(key,"sasl_",5)
&& strncasecmp(key,"partition-",10)) {
sprintf(errbuf,
"option '%s' is unknown on line %d of config file",
fullkey, lineno);
fatal(errbuf, EC_CONFIG);
}
*/
/* Put it in the overflow hash table */
newval = xstrdup(p);
val = hash_insert(key, newval, &confighash);
if(val != newval) {
snprintf(errbuf, sizeof(errbuf),
"option '%s' was specified twice in config file (second occurance on line %d)",
fullkey, lineno);
fatal(errbuf, EC_CONFIG);
}
}
}
fclose(infile);
free(buf);
}
diff --git a/lib/libconfig.h b/lib/libconfig.h
index 9c1135a52..be11681ab 100644
--- a/lib/libconfig.h
+++ b/lib/libconfig.h
@@ -1,92 +1,91 @@
/* libconfig.h -- Header for imapd.conf processing
*
* Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* $Id: libconfig.h,v 1.11 2010/04/19 19:54:26 murch Exp $
*/
#ifndef INCLUDED_LIBCONFIG_H
#define INCLUDED_LIBCONFIG_H
-#include "exitcodes.h"
#include "imapopts.h"
/* these will assert() if they're called on the wrong type of
option (imapopts.c) */
extern void config_reset(void);
extern void config_read(const char *alt_config, const int config_need_data);
extern const char *config_getstring(enum imapopt opt);
extern int config_getint(enum imapopt opt);
extern int config_getswitch(enum imapopt opt);
extern enum enum_value config_getenum(enum imapopt opt);
extern unsigned long config_getbitfield(enum imapopt opt);
/* these work on additional strings that are not defined in the
* imapoptions table */
extern const char *config_getoverflowstring(const char *key, const char *def);
extern void config_foreachoverflowstring(
void (*func)(const char *, const char *, void *), void *rock);
extern const char *config_partitiondir(const char *partition);
extern const char *config_metapartitiondir(const char *partition);
/* cached configuration variables accessable to external world */
extern const char *config_filename;
extern const char *config_dir;
extern const char *config_defpartition;
extern const char *config_servername;
extern enum enum_value config_serverinfo;
extern const char *config_mupdate_server;
extern const char *config_defdomain;
extern const char *config_ident;
extern int config_hashimapspool;
extern int config_implicitrights;
extern enum enum_value config_virtdomains;
extern enum enum_value config_mupdate_config;
extern int config_auditlog;
extern int config_iolog;
extern unsigned config_maxquoted;
extern unsigned config_maxword;
extern int config_qosmarking;
extern int config_debug;
/* config requirement flags */
#define CONFIG_NEED_PARTITION_DATA (1<<0)
#endif /* INCLUDED_LIBCONFIG_H */
diff --git a/lib/test/cyrusdb.c b/lib/test/cyrusdb.c
index d094a8b6e..3d27eceb9 100644
--- a/lib/test/cyrusdb.c
+++ b/lib/test/cyrusdb.c
@@ -1,150 +1,149 @@
#include <stdio.h>
#include <string.h>
#include "../cyrusdb.h"
#include "../xmalloc.h"
-#include "../exitcodes.h"
#ifdef BACKEND
struct cyrusdb_backend *DB = &(BACKEND);
#else
struct cyrusdb_backend *DB = &cyrusdb_flat;
#endif
#define TRY(s) { r = s; \
if (r && r != CYRUSDB_NOTFOUND) { \
printf("%s failed: %d\n", #s, r); exit(1); } }
void fatal(const char *msg, int code)
{
printf("fatal: %s\n", msg);
exit(code);
}
int yes(void *rock,
const char *key, int keylen,
const char *data, int datalen)
{
return 1;
}
int appkey(void *rock,
const char *key, int keylen,
const char *data, int datalen)
{
char *r = *(char **) rock;
int newlen;
if (r) {
newlen = strlen(r) + keylen + 2;
r = xrealloc(r, newlen);
strcat(r, " ");
strncpy(r + strlen(r), key, keylen);
r[newlen-1] = '\0';
} else {
r = xmalloc(keylen + 1);
strncpy(r, key, keylen);
r[keylen] = '\0';
}
*(char **)rock = r;
return 0;
}
int main(int argc, char *argv[])
{
char buf[1024];
struct db *db = NULL;
struct txn *txn = NULL;
int txnp = 0;
int r;
printf("Initing enviornment in '.'...\n");
TRY(DB->init(".", 0));
printf("Ready!\n");
for (;;) {
if (fgets(buf, sizeof buf, stdin) == NULL) break;
buf[strlen(buf)-1] = '\0';
if (!strncasecmp(buf, "file ", 5)) {
char *fname = buf + 5;
if (db) { /* close it */
TRY(cyrusdb_close(db));
}
TRY(cyrusdb_open(DB, fname, 1, &db));
printf("ok\n");
} else if (!db) {
TRY(db == NULL);
} else if (!strncasecmp(buf, "close", 5)) {
TRY(cyrusdb_close(db));
db = NULL;
printf("ok\n");
} else if (!strncasecmp(buf, "put ", 4)) {
char *key = buf + 4;
char *data = strchr(key, ' ');
if (!data) goto bad;
*data++ = '\0';
TRY(cyrusdb_store(db, key, strlen(key), data, strlen(data), (txnp ? &txn : NULL)));
printf("ok\n");
} else if (!strncasecmp(buf, "del ", 4)) {
char *key = buf + 4;
TRY(cyrusdb_delete(db, key, strlen(key), (txnp ? &txn : NULL), 0));
printf("ok\n");
} else if (!strncasecmp(buf, "get ", 4)) {
char *key = buf + 4;
const char *data;
int datalen;
TRY(cyrusdb_fetch(db, key, strlen(key), &data, &datalen, (txnp ? &txn : NULL)));
printf("ok {%d} ", datalen);
while (datalen--) printf("%c", *data++);
printf("\n");
} else if (!strncasecmp(buf, "list", 4)) {
char *keys = NULL;
TRY(cyrusdb_foreach(db, NULL, 0, yes, appkey, &keys, (txnp ? &txn : NULL)));
if (keys) {
printf("ok {%d} %s", strlen(keys), keys);
free(keys);
} else {
printf("ok {0} ");
}
printf("\n");
} else if (!strncasecmp(buf, "dump", 4)) {
if (cyrusdb_dump) {
TRY(cyrusdb_dump(db, 0));
printf("ok\n");
} else {
printf("no\n");
}
} else if (!strncasecmp(buf, "check", 4)) {
if (cyrusdb_consistent) {
TRY(cyrusdb_consistent(db));
printf("ok\n");
} else {
printf("no\n");
}
} else if (!strncasecmp(buf, "txn", 3)) {
if (txnp) {
printf("no\n");
} else {
printf("ok\n");
txnp = 1;
}
} else if (!strncasecmp(buf, "commit", 6)) {
TRY(cyrusdb_commit(db, txn));
txnp = 0;
txn = NULL;
printf("ok\n");
} else if (!strncasecmp(buf, "abort", 5)) {
TRY(cyrusdb_abort(db, txn));
txnp = 0;
txn = NULL;
printf("ok\n");
} else {
bad:
printf("?syntax error\n");
}
}
}
diff --git a/lib/test/rnddb.c b/lib/test/rnddb.c
index 7d96d5e98..715da319c 100644
--- a/lib/test/rnddb.c
+++ b/lib/test/rnddb.c
@@ -1,299 +1,298 @@
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include "../cyrusdb.h"
#include "../xmalloc.h"
-#include "../exitcodes.h"
#include "../assert.h"
#ifdef BACKEND
struct cyrusdb_backend *DB = &(BACKEND);
#else
struct cyrusdb_backend *DB = &cyrusdb_skiplist;
#endif
#define TRY(s) { r = s; \
if (r && r != CYRUSDB_NOTFOUND) { \
printf("%s failed (i=%d): %d\n", #s, i, r); exit(1); } }
char *victim;
int count;
int verbose = 0;
struct timeval t_add = { 0, 0 };
struct timeval t_mod = { 0, 0 };
struct timeval t_del = { 0, 0 };
struct timeval t_find = { 0, 0 };
int c_add = 0;
int c_mod = 0;
int c_del = 0;
int c_find = 0;
#define ADDDIFF(a, b, c) do { a.tv_sec += (c.tv_sec - b.tv_sec); \
a.tv_usec += (c.tv_usec - b.tv_usec); \
while (a.tv_usec < 0) \
{ a.tv_sec--; a.tv_usec += 1000000; } \
while (a.tv_usec > 1000000) \
{ a.tv_sec++; a.tv_usec -= 1000000; } } while (0)
int countem(void *rock,
const char *key, int keylen,
const char *data, int datalen)
{
count++;
return 0;
}
int findvictim(void *rock,
const char *key, int keylen,
const char *data, int datalen)
{
if (!victim) {
if ((rand() % count) == 0) {
victim = xstrdup(key);
}
}
count--;
return 0;
}
char *genrand(int len)
{
char *ret = xmalloc(len + 1);
char *p = ret;
while (len--) {
*p++ = 'a' + (rand() % 26);
}
*p = '\0';
return ret;
}
void fatal(const char *msg, int code)
{
printf("fatal: %s\n", msg);
exit(code);
}
void do_report(void)
{
printf("\n");
printf("*** add %ld.%ld %d\n", t_add.tv_sec, t_add.tv_usec, c_add);
printf("*** mod %ld.%ld %d\n", t_mod.tv_sec, t_mod.tv_usec, c_mod);
printf("*** del %ld.%ld %d\n", t_del.tv_sec, t_del.tv_usec, c_del);
printf("*** find %ld.%ld %d\n", t_find.tv_sec, t_find.tv_usec, c_find);
printf("\n");
printf("*** add %lf\n", ((double) t_add.tv_sec +
((double) t_add.tv_usec) / 1000000) /
(double) c_add);
printf("*** mod %lf\n", ((double) t_mod.tv_sec +
((double) t_mod.tv_usec) / 1000000) /
(double) c_mod);
printf("*** del %lf\n", ((double) t_del.tv_sec +
((double) t_del.tv_usec) / 1000000) /
(double) c_del);
printf("*** find %lf\n", ((double) t_find.tv_sec +
((double) t_find.tv_usec) / 1000000) /
(double) c_find);
}
int main(int argc, char *argv[])
{
int iter;
int seed;
int i;
char *key;
char *val;
struct db *db;
int r;
struct txn *txn;
const char *data;
int datalen;
struct timeval t1, t2;
int initsize;
if (argc > 1) {
iter = atoi(argv[1]);
} else {
printf("%s [iterations] [rndseed] [initsize]\n", argv[0]);
printf("if iterations is negative, run forever and report every -iter\n");
exit(1);
}
TRY(DB->init(".", 0));
if (argc > 2) {
srand(atoi(argv[2]));
}
TRY(cyrusdb_open(DB, "scratch", &db));
if (cyrusdb_consistent) {
TRY(cyrusdb_consistent(db));
}
if (argc > 3) {
initsize = atoi(argv[3]);
txn = NULL;
for (i = 0; i < initsize; i++) {
/* generate a random key */
key = genrand(10 + (rand() % 10));
/* generate a random value */
val = genrand(10 + (rand() % 100));
TRY(cyrusdb_store(db, key, strlen(key), val, strlen(val), &txn));
}
TRY(cyrusdb_commit(db, txn));
if (cyrusdb_consistent) {
TRY(cyrusdb_consistent(db));
}
}
printf("starting...\n");
/* repeat for ever if iter < 0 */
for (i = 0; iter > 0 ? (i < iter) : 1; i++) {
int oper = rand() % 10;
if (i > 0 && iter < 0 && ((i % -iter) == 0)) {
do_report();
}
switch (oper) {
case 0:
/* do an ADD */
if (verbose) printf("A");
/* insert it */
gettimeofday(&t1, NULL);
/* generate a random key */
key = genrand(10 + (rand() % 10));
/* generate a random value */
val = genrand(10 + (rand() % 100));
txn = NULL;
TRY(cyrusdb_store(db, key, strlen(key), val, strlen(val), &txn));
TRY(cyrusdb_commit(db, txn));
gettimeofday(&t2, NULL);
ADDDIFF(t_add, t1, t2);
c_add++;
free(key);
free(val);
break;
case 1: /* do a modify */
if (verbose) printf("M");
gettimeofday(&t1, NULL);
/* pick a random victim */
count = 0;
victim = NULL;
txn = NULL;
TRY(cyrusdb_foreach(db, NULL, 0, &countem, NULL, NULL, &txn));
if (count == 0) continue;
TRY(cyrusdb_foreach(db, NULL, 0, &findvictim, NULL, NULL, &txn));
assert(victim != NULL);
/* generate a random value */
val = genrand(10 + (rand() % 100));
/* do an add */
TRY(cyrusdb_store(db, victim, strlen(victim), val, strlen(val), &txn));
free(val);
TRY(cyrusdb_commit(db, txn));
free(victim); victim = NULL;
gettimeofday(&t2, NULL);
ADDDIFF(t_mod, t1, t2);
c_mod++;
break;
case 2: /* do a delete */
if (verbose) printf("D");
gettimeofday(&t1, NULL);
/* pick a random victim */
count = 0;
victim = NULL;
txn = NULL;
TRY(cyrusdb_foreach(db, NULL, 0, &countem, NULL, NULL, &txn));
if (count == 0) continue;
TRY(cyrusdb_foreach(db, NULL, 0, &findvictim, NULL, NULL, &txn));
assert(victim != NULL);
/* delete it */
TRY(cyrusdb_delete(db, victim, strlen(victim), &txn, 0));
TRY(cyrusdb_commit(db, txn));
free(victim); victim = NULL;
gettimeofday(&t2, NULL);
ADDDIFF(t_del, t1, t2);
c_del++;
break;
default:
/* do a "read" */
if (verbose) printf("R");
gettimeofday(&t1, NULL);
/* generate a random key */
key = genrand(10 + (rand() % 10));
txn = NULL;
TRY(cyrusdb_fetch(db, key, strlen(key), &data, &datalen, &txn));
TRY(cyrusdb_commit(db, txn));
gettimeofday(&t2, NULL);
ADDDIFF(t_find, t1, t2);
c_find++;
free(key);
}
fflush(stdout);
#if 0
/* run the consistency function, if any */
if (cyrusdb_consistent) {
TRY(cyrusdb_consistent(db));
}
#endif
}
TRY(cyrusdb_close(db));
TRY(DB->done());
do_report();
return 0;
}
diff --git a/lib/xstrlcpy.c b/lib/xstrlcpy.c
index 42f33e95f..596826c9b 100644
--- a/lib/xstrlcpy.c
+++ b/lib/xstrlcpy.c
@@ -1,74 +1,72 @@
/* xmalloc.c -- Allocation package that calls fatal() when out of memory
*
* Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* $Id: xstrlcpy.c,v 1.4 2010/01/06 17:01:48 murch Exp $
*/
#include "xstrlcpy.h"
-#include "exitcodes.h"
-
#ifndef HAVE_STRLCPY
/* strlcpy -- copy string smartly.
*
* i believe/hope this is compatible with the BSD strlcpy().
*/
EXPORTED size_t strlcpy(char *dst, const char *src, size_t len)
{
size_t n;
if (len <= 0) {
/* we can't do anything ! */
return strlen(src);
}
/* assert(len >= 1); */
for (n = 0; n < len-1; n++) {
if ((dst[n] = src[n]) == '\0') break;
}
if (n >= len-1) {
/* ran out of space */
dst[n] = '\0';
while(src[n]) n++;
}
return n;
}
#endif

File Metadata

Mime Type
text/x-diff
Expires
Sat, Apr 4, 12:29 AM (4 w, 8 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18821694
Default Alt Text
(227 KB)

Event Timeline