Page MenuHomePhorge

libconfig.c
No OneTemporary

Authored By
Unknown
Size
18 KB
Referenced Files
None
Subscribers
None

libconfig.c

/* 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 "hash.h"
#include "libconfig.h"
#include "imapopts.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 */
const char *config_filename= NULL; /* filename of configuration file */
const char *config_dir = NULL; /* ie /var/imap */
const char *config_defpartition = NULL; /* /var/spool/imap */
const char *config_servername= NULL; /* gethostname() */
enum enum_value config_serverinfo; /* on */
const char *config_mupdate_server = NULL;/* NULL */
const char *config_defdomain = NULL; /* NULL */
const char *config_ident = NULL; /* the service name */
int config_hashimapspool; /* f */
enum enum_value config_virtdomains; /* f */
enum enum_value config_mupdate_config; /* IMAP_ENUM_MUPDATE_CONFIG_STANDARD */
int config_auditlog;
unsigned config_maxword;
unsigned config_maxquoted;
int config_qosmarking;
/* declared in each binary that uses libconfig */
extern const int config_need_data;
extern void fatal(const char *fatal_message, int fatal_code)
__attribute__ ((noreturn));
/* prototype to allow for sane function ordering */
void config_read_file(const char *filename);
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;
}
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;
}
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;
}
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;
}
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;
}
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);
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;
}
void config_foreachoverflowstring(void (*func)(const char *, const char *, void *),
void *rock)
{
if (!config_filename) return;
hash_enumerate(&confighash, (void (*)(char *, void *, void *)) func, rock);
}
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);
}
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;
}
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
};
void config_read(const char *alt_config)
{
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))
fatal("defaultpartition option contains non-alphanumeric character",
EC_CONFIG);
if (Uisupper(*p)) *p = tolower((unsigned char) *p);
}
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 {
/* 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);
/* 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);
config_mupdate_server = config_getstring(IMAPOPT_MUPDATE_SERVER);
if (config_mupdate_server) {
config_mupdate_config = config_getenum(IMAPOPT_MUPDATE_CONFIG);
}
/* set some limits */
config_maxquoted = config_getint(IMAPOPT_MAXQUOTED);
config_maxword = config_getint(IMAPOPT_MAXWORD);
ival = config_getenum(IMAPOPT_QOSMARKING);
config_qosmarking = qos[ival];
}
#define GROWSIZE 4096
void config_read_file(const char *filename)
{
FILE *infile;
enum imapopt opt = IMAPOPT_ZERO;
int lineno = 0;
char *buf, errbuf[1024];
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 */
infile = fopen(filename, "r");
if (!infile) {
strlcpy(buf, CYRUS_PATH, bufsize);
strlcat(buf, filename, bufsize);
infile = fopen(buf, "r");
}
if (!infile) {
snprintf(buf, bufsize, "can't open configuration file %s: %s",
filename, error_message(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);
}

File Metadata

Mime Type
text/x-c
Expires
Fri, Apr 24, 10:47 AM (5 d, 1 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18840411
Default Alt Text
libconfig.c (18 KB)

Event Timeline