Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117747624
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
227 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
R111 cyrus-imapd
Attached
Detach File
Event Timeline