/* zoneinfo_db.c -- zoneinfo DB routines
*
* Copyright (c) 1994-2013 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.
*
*/
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include "assert.h"
#include "cyrusdb.h"
#include "exitcodes.h"
#include "global.h"
#include "tok.h"
#include "util.h"
#include "xmalloc.h"
#include "zoneinfo_db.h"
#define DB config_zoneinfo_db
struct db *zoneinfodb;
static int zoneinfo_dbopen = 0;
static struct buf databuf = BUF_INITIALIZER;
EXPORTED int zoneinfo_open(const char *fname)
{
int ret;
char *tofree = NULL;
if (!fname) fname = config_getstring(IMAPOPT_ZONEINFO_DB_PATH);
/* create db file name */
if (!fname) {
tofree = strconcat(config_dir, FNAME_ZONEINFODB, (char *)NULL);
fname = tofree;
}
ret = cyrusdb_open(DB, fname, CYRUSDB_CREATE, &zoneinfodb);
if (ret != 0) {
syslog(LOG_ERR, "DBERROR: opening %s: %s", fname,
cyrusdb_strerror(ret));
}
else zoneinfo_dbopen = 1;
free(tofree);
return ret;
}
EXPORTED void zoneinfo_close(struct txn *tid)
{
if (zoneinfo_dbopen) {
int r;
if (tid) {
r = cyrusdb_commit(zoneinfodb, tid);
if (r) {
syslog(LOG_ERR, "DBERROR: error committing zoneinfo: %s",
cyrusdb_strerror(r));
}
}
r = cyrusdb_close(zoneinfodb);
if (r) {
syslog(LOG_ERR, "DBERROR: error closing zoneinfo: %s",
cyrusdb_strerror(r));
}
zoneinfo_dbopen = 0;
buf_free(&databuf);
}
}
void zoneinfo_done(void)
{
/* DB->done() handled by cyrus_done() */
}
static int parse_zoneinfo(const char *data, int datalen,
struct zoneinfo *zi, int all)
{
const char *dend = data + datalen;
unsigned version;
char *p;
memset(zi, 0, sizeof(struct zoneinfo));
/* version SP type SP dtstamp SP (string *(TAB string)) */
version = strtoul(data, &p, 10);
if (version != ZONEINFO_VERSION) return CYRUSDB_IOERROR;
if (p < dend) zi->type = strtoul(p, &p, 10);
if (p < dend) zi->dtstamp = strtol(p, &p, 10);
if (all && p < dend) {
size_t len = dend - ++p;
char *str = xstrndup(p, len);
tok_t tok;
tok_initm(&tok, str, "\t", TOK_FREEBUFFER);
while ((str = tok_next(&tok))) appendstrlist(&zi->data, str);
tok_fini(&tok);
}
return 0;
}
EXPORTED int zoneinfo_lookup(const char *tzid, struct zoneinfo *zi)
{
const char *data = NULL;
size_t datalen;
int r;
/* Don't access DB if it hasn't been opened */
if (!zoneinfo_dbopen) return CYRUSDB_INTERNAL;
assert(tzid);
/* Check if there is an entry in the database */
do {
r = cyrusdb_fetch(zoneinfodb, tzid, strlen(tzid), &data, &datalen, NULL);
} while (r == CYRUSDB_AGAIN);
if (r || !data || (datalen < 6)) return r ? r : CYRUSDB_IOERROR;
if (!zi) return 0;
return parse_zoneinfo(data, datalen, zi, 1);
}
EXPORTED int zoneinfo_store(const char *tzid, struct zoneinfo *zi, struct txn **tid)
{
struct strlist *sl;
const char *sep;
int r;
/* Don't access DB if it hasn't been opened */
if (!zoneinfo_dbopen) return CYRUSDB_INTERNAL;
assert(tzid && zi);
/* version SP type SP dtstamp SP (string *(TAB string)) */
buf_reset(&databuf);
buf_printf(&databuf, "%u %u %ld ", ZONEINFO_VERSION, zi->type, zi->dtstamp);
for (sl = zi->data, sep = ""; sl; sl = sl->next, sep = "\t")
buf_printf(&databuf, "%s%s", sep, sl->s);
r = cyrusdb_store(zoneinfodb, tzid, strlen(tzid),
buf_cstring(&databuf), buf_len(&databuf), tid);
if (r != CYRUSDB_OK) {
syslog(LOG_ERR, "DBERROR: error updating zoneinfo: %s (%s)",
tzid, cyrusdb_strerror(r));
}
return r;
}
static int tzmatch(const char *str, const char *pat)
{
for ( ; *pat; str++, pat++) {
/* End of string and more pattern */
if (!*str && *pat != '*') return 0;
switch (*pat) {
case '*':
/* Collapse consecutive stars */
while (*++pat == '*') continue;
/* Trailing star matches anything */
if (!*pat) return 1;
while (*str) if (tzmatch(str++, pat)) return 1;
return 0;
case ' ':
case '_':
/* Treat ' ' == '_' */
if (*str != ' ' && *str != '_') return 0;
break;
default:
/* Case-insensitive comparison */
if (tolower(*str) != tolower(*pat)) return 0;
break;
}
}
/* Did we reach end of string? */
return (!*str);
}
struct findrock {
const char *find;
int tzid_only;
time_t changedsince;
int (*proc)();
void *rock;
};
static int find_p(void *rock,
const char *tzid, size_t tzidlen,
const char *data, size_t datalen)
{
struct findrock *frock = (struct findrock *) rock;
struct zoneinfo zi;
if (parse_zoneinfo(data, datalen, &zi, 0)) return 0;
switch (zi.type) {
case ZI_INFO: return 0;
case ZI_LINK:
if (frock->tzid_only) return 0;
break;
case ZI_ZONE:
if (zi.dtstamp <= frock->changedsince) return 0;
break;
}
if (!frock->find) return 1;
if (frock->tzid_only) return (tzidlen == strlen(frock->find));
return tzmatch(tzid, frock->find);
}
static int find_cb(void *rock,
const char *tzid, size_t tzidlen,
const char *data, size_t datalen)
{
struct findrock *frock = (struct findrock *) rock;
struct zoneinfo zi;
int r;
r = parse_zoneinfo(data, datalen, &zi, 1);
if (!r) {
struct strlist *linkto = NULL;
if (zi.type == ZI_LINK) {
linkto = zi.data;
zi.data = NULL;
tzid = linkto->s;
tzidlen = strlen(tzid);
r = zoneinfo_lookup(tzid, &zi);
}
if (!r) r = (*frock->proc)(tzid, tzidlen, &zi, frock->rock);
freestrlist(zi.data);
freestrlist(linkto);
}
return r;
}
EXPORTED int zoneinfo_find(const char *find, int tzid_only, time_t changedsince,
int (*proc)(), void *rock)
{
struct findrock frock;
/* Don't access DB if it hasn't been opened */
if (!zoneinfo_dbopen) return CYRUSDB_INTERNAL;
assert(proc);
frock.find = find;
frock.tzid_only = tzid_only;
frock.changedsince = changedsince;
frock.proc = proc;
frock.rock = rock;
if (!find || !tzid_only) find = "";
/* process each matching entry in our database */
return cyrusdb_foreach(zoneinfodb, (char *) find, strlen(find),
&find_p, &find_cb, &frock, NULL);
}