siproxd/src/readconf.c
2017-02-27 22:04:56 +00:00

397 lines
11 KiB
C

/*
Copyright (C) 2002-2008 Thomas Ries <tries@gmx.net>
This file is part of Siproxd.
Siproxd is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Siproxd is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Siproxd; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "config.h"
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <osipparser2/osip_parser.h>
#include "siproxd.h"
#include "log.h"
static char const ident[]="$Id$";
/* configuration storage */
extern struct siproxd_config configuration;
/* prototypes used locally only */
static int parse_config (FILE *configfile, cfgopts_t cfgopts[], char *filter);
/* load default values from cfgopts_t structure */
static int load_defaults(cfgopts_t cfgopts2[]);
/* try to open (whichever is found first):
* <name>
* $HOME/.<name>rc
* /etc/<name>.conf
* /usr/etc/<name>.conf
* /usr/local/etc/<name>.conf
*
* cfgopts: control array for the parser. defined keywors and types
* filter: passed to parse_config (read there for details)
*
* RETURNS
* STS_SUCCESS on success
* STS_FAILURE on error
*/
int read_config(char *name, int search, cfgopts_t cfgopts[], char *filter) {
int sts;
FILE *configfile=NULL;
int i;
char tmp[256];
const char *completion[] = {
"%s/.%src", /* 0: this one is special... */
SIPROXDCONFPATH "/%s.conf", /* 1 */
"/etc/%s.conf", /* 2 */
"/etc/%s/%s.conf", /* 3: this one, too */
"/usr/etc/%s.conf", /* 5 */
"/usr/local/etc/%s.conf", /* 6 */
NULL };
DEBUGC(DBCLASS_CONFIG,"setting default config values");
load_defaults(cfgopts);
DEBUGC(DBCLASS_CONFIG,"trying to read config file");
/* shall I search the config file myself ? */
if (search != 0) {
/* yup, try to find it */
for (i=0; completion[i]!=NULL; i++) {
switch (i) {
case 0:
snprintf(tmp,sizeof(tmp),completion[i],getenv("HOME"),name);
break;
case 3:
snprintf(tmp,sizeof(tmp),completion[i],name,name);
break;
default:
snprintf(tmp,sizeof(tmp),completion[i],name);
break;
}
tmp[sizeof(tmp)-1]='\0';
DEBUGC(DBCLASS_CONFIG,"... trying %s",tmp);
configfile = fopen(tmp,"r");
if (configfile==NULL) continue;
break; /* got config file */
}
} else {
/* don't search it, just try the one given file */
DEBUGC(DBCLASS_CONFIG,"... trying %s",name);
configfile = fopen(name,"r");
}
/* config file not found or unable to open for read */
if (configfile==NULL) {
ERROR ("could not open config file: %s", strerror(errno));
return STS_FAILURE;
}
sts = parse_config(configfile, cfgopts, filter);
fclose(configfile);
/*
* Post-process configuration variables that have conditions that
* must be met; warn if we have to adjust any.
*/
if (configuration.rtp_port_low & 0x01) {
/* rtp_port_low must be an even number... */
configuration.rtp_port_low = (configuration.rtp_port_low + 1) & ~0x01;
WARN("rtp_port_low should be an even number; it's been rounded up to %i",
configuration.rtp_port_low);
}
if (configuration.rtp_port_high & 0x01) {
/* rtp_high_port should be either the top RTP port allowed, */
/* or the top RTCP port allowed. If the latter, then reset */
/* to the former... Don't need a warning here. It's okay. */
configuration.rtp_port_high = configuration.rtp_port_high & ~0x01;
DEBUGC(DBCLASS_CONFIG, "rounded rtp_port_high down to %i",
configuration.rtp_port_high);
}
return sts;
}
/*
* parse configuration file
*
* configfile - file STREAM to open config file
*
* configoptions - control structure for config parser
*
* filter = NULL - no filtering done
* filter = "plugin_xxx" - only consider keywords starting
* with "plugin_xxx", skip the rest.
* PLugins set this to load their scope
* of config options
* filter = "" - read main configuration, skip everything
* starting with "plugin_" (hardwired).
*
* RETURNS
* STS_SUCCESS on success
* STS_FAILURE on error
*/
static int parse_config (FILE *configfile, cfgopts_t configoptions[],
char *filter) {
char buff[1024];
char *ptr=NULL;
int i, k;
int num;
size_t len;
char *tmpptr=NULL;
char *eqsign=NULL;
while (fgets(buff,sizeof(buff),configfile) != NULL) {
/* life insurance */
buff[sizeof(buff)-1]='\0';
/* strip New line & CR if present */
len=strlen(buff);
for (i=1; i<=2; i++) {
k=len-i;
if (k<0) { k=0; }
if ((buff[k]=='\n') || (buff[k]=='\r')) {
buff[k]='\0';
}
}
/* strip emtpy lines */
if (strlen(buff) == 0) continue;
/* strip comments and line with only whitespaces */
for (i=0;i<strlen(buff);i++) {
if ((buff[i] == ' ') || (buff[i] == '\t')) continue;
if (buff[i] =='#') i=strlen(buff);
break;
}
if (i == strlen(buff)) continue;
/* search for token separator '=' */
eqsign = strchr(buff, '=');
if (eqsign == NULL) {
ERROR("Syntax error in config file [%s]", buff);
continue;
}
/* keyword filtering:
* filter = NULL - no filtering done
* filter = "plugin_xxx" - only consider keywords starting
* with "plugin_xxx", skip the rest.
* PLugins set this to load their scope
* of config options
* filter = "" - read main configuration, skip everything
* starting with "plugin_" (hardwired).
*/
if (filter) {
/* filter == "": skip all plugin config entries */
if (filter[0] == '\0') {
ptr=strstr(buff,"plugin_");
if (ptr && (ptr < eqsign)) {
DEBUGC(DBCLASS_CONFIG,"skipped: \"%s\"",buff);
continue;
}
/* filter == "something": only consider "somethingxxxxx" */
} else {
ptr=strstr(buff, filter);
if ((ptr==NULL) || (ptr > eqsign)) {
DEBUGC(DBCLASS_CONFIG,"skipped: \"%s\"",buff);
continue;
}
}
} /* filter == NULL */
DEBUGC(DBCLASS_CONFIG,"cfg line:\"%s\"",buff);
/* scan for known keyword */
for (k=0; configoptions[k].keyword != NULL; k++) {
if ((ptr=strstr(buff, configoptions[k].keyword)) != NULL) {
ptr += strlen(configoptions[k].keyword);
DEBUGC(DBCLASS_CONFIG,"got keyword:\"%s\"",
configoptions[k].keyword);
/* check for argument separated by '=' */
if ((ptr=strchr(ptr,'=')) == NULL) {;
ERROR("argument missing to config parameter %s",
configoptions[k].keyword);
break;
}
do {ptr++;} while (*ptr == ' '); /* skip spaces after '=' */
DEBUGC(DBCLASS_CONFIG,"got argument:\"%s\"",ptr);
num=0;
if (strlen(ptr) <= 0) {
WARN("empty argument in config file, line:\"%s\"",buff);
break;
}
switch (configoptions[k].type) {
//
// Integer4
//
case TYP_INT4:
num=sscanf(ptr,"%i",(int*)configoptions[k].dest);
DEBUGC(DBCLASS_BABBLE,"INT4=%i",*(int*)configoptions[k].dest);
break;
//
// String
//
case TYP_STRING:
/* the %as within sscanf is not portable (%as is
* supposed to allocate the memory within sscanf)
* num=sscanf(ptr,"%as",(char**)configoptions[k].dest);
*/
/* figure out the amount of space we need */
len=strlen(ptr)+1; /* include terminating zero!*/
tmpptr=(char*)malloc(len);
memcpy(configoptions[k].dest, &tmpptr, sizeof(tmpptr));
/* get full string, until a "#" or end of line */
num=sscanf(ptr,"%[^#]",tmpptr);
tmpptr[len-1]='\0';
/* strip trailing spaces */
i = strlen(tmpptr);
do {i--;} while (i>0 && tmpptr[i] == ' ');
tmpptr[i+1]='\0';
DEBUGC(DBCLASS_BABBLE,"STRING=\"%s\"",
*(char**)configoptions[k].dest);
break;
//
// String array
//
case TYP_STRINGA:
{
/* figure out the amount of space we need */
char **dst;
int used=((stringa_t*)(configoptions[k].dest))->used;
/* do I hace space left? */
if (used<=CFG_STRARR_SIZE){
len=strlen(ptr)+1; /* include terminating zero!*/
tmpptr=(char*)malloc(len);
dst=&((stringa_t*)(configoptions[k].dest))->
string[used];
memcpy(dst, &tmpptr, sizeof(tmpptr));
/* get full string, until a "#" or end of line */
num=sscanf(ptr,"%[^#]",tmpptr);
tmpptr[len-1]='\0';
/* strip trailing spaces */
i = strlen(tmpptr);
do {i--;} while (i>0 && tmpptr[i] == ' ');
tmpptr[i+1]='\0';
DEBUGC(DBCLASS_BABBLE,"STRINGA[%i]=\"%s\"", used, (char*) (
((stringa_t*)(configoptions[k].dest))->string[used]) );
((stringa_t*)(configoptions[k].dest))->used++;
} else {
ERROR("no more space left in config string array %s",
configoptions[k].keyword);
}
break;
}
default:
break;
}
if (num == 0) {
ERROR("illegal format in config file, line:\"%s\"",buff);
}
break;
}
} // for configoptions
/*
* complain if we hit a unknown keyword
*/
if (configoptions[k].keyword == NULL) {
ERROR("unknown keyword in config file, line:\"%s\"",buff);
}
} // while
return STS_SUCCESS;
}
/* load_defaults
*
* Loads the default values as defined in the cfgopts structure
*
* cfgopts: control array for the parser. defined keywors and types
*
* RETURNS
* STS_SUCCESS on success
* STS_FAILURE on error
*/
static int load_defaults(cfgopts_t cfgopts2[]){
int k;
void *ptr;
/* loop through the config array */
for (k=0; cfgopts2[k].keyword != NULL; k++) {
switch (cfgopts2[k].type) {
//
// Integer4
//
case TYP_INT4:
ptr=cfgopts2[k].dest;
*(int*)ptr=cfgopts2[k].defval.int4;
break;
//
// String
// copy the pointer to a statically allocated string. If overridden
// by the config file, memory will be allocated dynamically and
// overwrites the destination pointer.
//
case TYP_STRING:
ptr=cfgopts2[k].dest;
memcpy(ptr, &cfgopts2[k].defval.string, sizeof (char *));
break;
//
// String array
//
case TYP_STRINGA:
memset(cfgopts2[k].dest, 0, sizeof (char *));
break;
default:
break;
}
} /* for k */
return STS_SUCCESS;
};