#include <config.h>
#include <assert.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 <dirent.h>
#include "hash.h"
#include "libconfig.h"
#include "imapopts.h"
#include "xmalloc.h"
#define CONFIGHASHSIZE 30
#define INCLUDEHASHSIZE 5
static struct hash_table confighash, includehash;
const char *config_filename= NULL;
const char *config_dir = NULL;
const char *config_defpartition = NULL;
const char *config_servername= NULL;
const char *config_mupdate_server = NULL;
const char *config_defdomain = NULL;
const char *config_ident = NULL;
int config_hashimapspool;
enum enum_value config_virtdomains;
extern const int config_need_data;
extern void fatal(const char *fatal_message, int fatal_code)
__attribute__ ((noreturn));
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);
return imapopts[opt].val.i;
}
int config_getswitch(enum imapopt opt)
{
assert(opt > IMAPOPT_ZERO && opt < IMAPOPT_LAST);
assert(imapopts[opt].t == OPT_SWITCH);
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;
}
const char *config_getoverflowstring(const char *key, const char *def)
{
char buf[256];
char *ret = NULL;
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);
}
if(!ret)
ret = hash_lookup(key, &confighash);
return ret ? ret : def;
}
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);
}
void config_read(const char *alt_config)
{
enum opttype opt = IMAPOPT_ZERO;
char buf[4096];
char *p;
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);
if (!config_dir) {
fatal("configdirectory option not specified in configuration file",
EC_CONFIG);
}
DIR* dir = opendir( config_dir );
if ( !dir )
{
sleep(5);
snprintf( buf, sizeof( buf), "ERROR: Cannot start mail services, configuration directory does not exist: %s", config_dir );
fatal( buf, EC_CONFIG);
}
closedir( dir );
for(opt = IMAPOPT_ZERO; opt < IMAPOPT_LAST; opt++) {
if(!imapopts[opt].val.s ||
imapopts[opt].t != OPT_STRING ||
opt == IMAPOPT_CONFIGDIRECTORY) {
continue;
}
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;
if(imapopts[opt].seen)
freeme = (char *)str;
strcpy(newstring, config_dir);
strcat(newstring, str + 17);
imapopts[opt].val.s = newstring;
if(freeme) free(freeme);
}
}
config_defpartition = config_getstring(IMAPOPT_DEFAULTPARTITION);
for (p = (char *)config_defpartition; *p; p++) {
if (!isalnum((unsigned char) *p))
fatal("defaultpartition option contains non-alphanumeric character",
EC_CONFIG);
if (isupper((unsigned char) *p)) *p = tolower((unsigned char) *p);
}
if ((config_need_data & CONFIG_NEED_PARTITION_DATA) &&
(!config_defpartition || !config_partitiondir(config_defpartition))) {
snprintf(buf, sizeof(buf),
"partition-%s option not specified in configuration file",
config_defpartition);
fatal(buf, EC_CONFIG);
}
const char *tmpDir = config_partitiondir( config_defpartition );
dir = opendir( tmpDir );
if ( !dir )
{
sleep(5);
snprintf( buf, sizeof( buf), "ERROR: Cannot start mail services, mail partition does not exist: %s", tmpDir );
fatal( buf, EC_CONFIG);
}
closedir( dir );
config_hashimapspool = config_getswitch(IMAPOPT_HASHIMAPSPOOL);
config_virtdomains = config_getenum(IMAPOPT_VIRTDOMAINS);
config_defdomain = config_getstring(IMAPOPT_DEFAULTDOMAIN);
config_servername = config_getstring(IMAPOPT_SERVERNAME);
if (!config_servername) {
config_servername = xmalloc(sizeof(char) * 256);
gethostname((char *) config_servername, 256);
}
config_mupdate_server = config_getstring(IMAPOPT_MUPDATE_SERVER);
}
void config_read_file(const char *filename)
{
FILE *infile;
enum opttype opt = IMAPOPT_ZERO;
int lineno = 0;
char buf[4096], errbuf[1024];
char *p, *q, *key, *fullkey, *srvkey, *val, *newval;
int service_specific;
int idlen = (config_ident ? strlen(config_ident) : 0);
infile = fopen(filename, "r");
if (!infile) {
strlcpy(buf, CYRUS_PATH, sizeof(buf));
strlcat(buf, filename, sizeof(buf));
infile = fopen(buf, "r");
}
if (!infile) {
snprintf(buf, sizeof(buf), "can't open configuration file %s: %s",
filename, error_message(errno));
fatal(buf, EC_CONFIG);
}
if (hash_lookup(filename, &includehash)) {
snprintf(buf, sizeof(buf), "configuration file %s included twice",
filename);
fatal(buf, EC_CONFIG);
return;
}
else {
hash_insert(filename, (void*) 0xDEADBEEF, &includehash);
}
while (fgets(buf, sizeof(buf), infile)) {
lineno++;
service_specific = 0;
if (buf[0] && buf[strlen(buf)-1] == '\n') buf[strlen(buf)-1] = '\0';
for (p = buf; *p && isspace((int) *p); p++);
if (!*p || *p == '#') continue;
fullkey = key = p;
if (*p == '@') p++;
while (*p && (isalnum((int) *p) || *p == '-' || *p == '_')) {
if (isupper((unsigned char) *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';
while (*p && isspace((int) *p)) p++;
for (q = p + strlen(p) - 1; q > p && isspace((int) *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;
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);
}
}
if(config_ident && !strncasecmp(key, config_ident, idlen)
&& key[idlen] == '_') {
srvkey = key + idlen + 1;
}
if(srvkey) {
for (opt = IMAPOPT_ZERO; opt < IMAPOPT_LAST; opt++) {
if (!strcasecmp(imapopts[opt].optname, srvkey)) {
key = srvkey;
service_specific = 1;
break;
}
}
}
if(!service_specific) {
for (opt = IMAPOPT_ZERO; opt < IMAPOPT_LAST; opt++) {
if (!strcasecmp(imapopts[opt].optname, key)) {
break;
}
}
}
if (opt < IMAPOPT_LAST) {
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(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;
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') {
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 {
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:
{
const struct enum_option_s *e = imapopts[opt].enum_options;
if (imapopts[opt].t == OPT_ENUM) {
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";
}
}
while (e->name) {
if (!strcmp(e->name, p)) break;
e++;
}
if (e->name) {
if (imapopts[opt].t == OPT_ENUM)
imapopts[opt].val.e = e->val;
else
imapopts[opt].val.s = e->name;
} else {
sprintf(errbuf, "invalid value for %s in line %d",
imapopts[opt].optname, lineno);
fatal(errbuf, EC_CONFIG);
}
break;
}
case OPT_NOTOPT:
default:
abort();
}
} else {
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);
}