/*
* Copyright (c) 2001, 2002, 2003, 2004, 2005 Netli, Inc.
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: ncnf.c,v 1.2 2005/05/26 20:17:32 vlm Exp $
*/
/*
* This file is mostly a compatibility layer between
* external ncnf_obj and internal struct ncnf_obj_s representations.
* Internal functions provide virtually no fool-proofing.
*/
#include "headers.h"
#include "ncnf_int.h"
#include "ncnf_cr.h"
#include "ncnf_vr.h"
#include "ncnf_policy.h"
#include "ncnf.h"
#include "ncnf_app_int.h"
/*
* All this overridable debug crap.
*/
static void _ncnf_default_debug_print_function(int, const char *, va_list)
__attribute__ ((format (printf, 2, 0) ));
static void
_ncnf_default_debug_print_function(int critical, const char *fmt, va_list ap) {
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
}
void (*ncnf_debug_print_function)(int is_critical, const char *fmt, va_list)
= _ncnf_default_debug_print_function;
void
_ncnf_debug_print(int critical, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
if(ncnf_debug_print_function)
ncnf_debug_print_function(critical, fmt, ap);
va_end(ap);
}
/*
* Callback to destroy inserts shadow structures when configuration
* file is fully read.
*/
static int
_ncnf_remove_inserts_callback(struct ncnf_obj_s *obj, void *key) {
(void)key;
obj->mark = 0;
if(_NOBJ_CONTAINER(obj))
_ncnf_coll_clear(obj->mr,
&obj->m_collection[COLLECTION_INSERTS], 1);
return 0;
}
static struct asynchronous_validation_state {
enum {
AVS_NOSTATE,
AVS_INPROGRESS,
AVS_FAILED,
AVS_SUCCEEDED
} state;
pid_t validator_pid; /* Valid if(state == AVS_INPROGRESS). */
struct sigaction old_sigchld;
} _asyncval;
static int _fire_async_validation(const char *validator, const char *fname);
ncnf_obj *
ncnf_read(const char *config_filename) {
return ncnf_Read(config_filename, NCNF_ST_FILENAME);
}
ncnf_obj *
ncnf_Read(const char *data, enum ncnf_source_type stype, ...) {
struct ncnf_obj_s *root = NULL;
int no_dynamic_validation = (stype & NCNF_FL_NODYN);
int no_embedded_validation = (stype & NCNF_FL_NOEMB);
int async_validation = (stype & NCNF_FL_ASYNCVAL);
int relaxed_ns = (stype & NCNF_FL_RELNS);
int strip_with_ncql = (stype & NCNF_FL_EXTNCQL);
char *ncql_qfile = 0;
char *ncql_proc = 0;
char *ncql_conf = 0;
va_list ap;
int ret;
/* Get rid of NCNF_FL stuff from the source type indicator */
stype &= ~(NCNF_FL_NODYN | NCNF_FL_NOEMB
| NCNF_FL_ASYNCVAL | NCNF_FL_RELNS | NCNF_FL_EXTNCQL);
/* BGZ#1988 */
if(strip_with_ncql) {
va_start(ap, stype);
ncql_qfile = va_arg(ap, char *);
ncql_proc = va_arg(ap, char *);
ncql_conf = va_arg(ap, char *);
va_end(ap);
}
/*
* Fire asynchronous validation.
*/
if((stype == NCNF_ST_FILENAME)
&& (async_validation
|| (strip_with_ncql && _param_reload_ncnf_validator_ncql))) {
/*
* Check if the previous validation has finished properly.
*/
switch(_asyncval.state) {
case AVS_NOSTATE:
if(_fire_async_validation(
ncql_proc ? ncql_proc :
_param_reload_ncnf_validator,
data))
break; /* Proceed as it weren't enabled */
/* Fall through */
case AVS_INPROGRESS:
errno = EAGAIN; /* Try again later */
return NULL;
case AVS_FAILED:
/* Validation failed! */
_asyncval.state = AVS_NOSTATE;
errno = EINVAL;
return NULL;
case AVS_SUCCEEDED:
_asyncval.state = AVS_NOSTATE;
/* Disable internal validation */
no_dynamic_validation = NCNF_FL_NODYN;
no_embedded_validation = NCNF_FL_NOEMB;
break;
}
/* Fall through */
}
/*
* Attempt to build the configuration tree
* by reading configuration file.
*/
do {
if(ncql_conf && _asyncval.state == AVS_SUCCEEDED) {
/* Read in the processed file */
ret = _ncnf_cr_read(ncql_conf, NCNF_ST_FILENAME,
&root, relaxed_ns);
if(ret == 0) {
no_dynamic_validation = NCNF_FL_NODYN;
no_embedded_validation = NCNF_FL_NOEMB;
break;
}
_ncnf_debug_print(0,
"Warning: %s cannot be read, "
"falling back to %s",
ncql_conf, data);
/* Fall back into the full configuration file reading */
}
ret = _ncnf_cr_read(data, stype, &root, relaxed_ns);
if(ret != 0)
return NULL;
} while(0);
/*
* Scan down the tree resolving all references.
*/
if(_ncnf_cr_resolve(root, relaxed_ns) == -1) {
_ncnf_obj_destroy(root);
return NULL;
}
/*
* Remove temporary inserts structures.
*/
_ncnf_walk_tree((struct ncnf_obj_s *)root,
_ncnf_remove_inserts_callback, NULL);
/*
* Dynamic validation against .vr (validator rules) file.
*/
while(!no_dynamic_validation) {
char *filename;
struct vr_config *vc;
filename = ncnf_get_attr((ncnf_obj *)root,
"_validator-rules");
if(filename == NULL) {
break;
} else if(*filename != '/'
&& stype == NCNF_ST_FILENAME
&& strchr(data, '/')) {
char *newfname;
char *p;
/*
* Prepend configuration path to the relative
* validator-rules filename, so
* ../path/config.conf will become
* ../path/config.vr
*/
newfname = alloca(strlen(data)
+ strlen(filename) + 1);
strcpy(newfname, data);
p = strrchr(newfname, '/');
p++;
strcpy(p, filename);
filename = newfname;
}
vc = ncnf_vr_read(filename);
if(vc == NULL) {
if(errno == ENOENT) {
_ncnf_debug_print(0,
"Warning: File with validator rules %s not found: %s",
filename, strerror(errno));
break;
}
_ncnf_debug_print(1,
"Can't parse validation rules in file %s",
filename);
ncnf_destroy((ncnf_obj *)root);
return NULL;
}
ret = ncnf_validate(root, vc);
ncnf_vr_destroy(vc);
vc = NULL;
if(ret != 0) {
_ncnf_debug_print(1,
"%s validation against %s failed",
stype==NCNF_ST_TEXT?"NCNF data":data,
filename);
ncnf_destroy((ncnf_obj *)root);
return NULL;
}
break;
}
/*
* Validation against embedded policies.
*/
if(!no_embedded_validation) {
int yes;
/*
* Check if embedded validator is not disabled.
*/
if(ncnf_get_attr_int(root, "_validator-embedded", &yes) == 0
&& yes) {
/*
* Only invoke embedded validator if
* there is a "_validator-embedded"
* attribute defined to a non-zero value.
*/
if(ncnf_policy(root)) {
_ncnf_debug_print(1,
"Failed to check the configuration "
"against the hardcoded policy");
ncnf_destroy((ncnf_obj *)root);
return NULL;
}
}
}
return (ncnf_obj *)root;
}
ncnf_obj *
ncnf_obj_parent(ncnf_obj *objp) {
struct ncnf_obj_s *obj = objp;
if(obj == NULL) {
errno = EINVAL;
return NULL;
}
if(obj->parent == NULL)
errno = 0;
return obj->parent;
}
void
ncnf_destroy(ncnf_obj *obj_p) {
struct ncnf_obj_s *obj = (struct ncnf_obj_s *)obj_p;
if(obj == NULL)
return;
_ncnf_notify_everyone(obj, NCNF_OBJ_DESTROY);
_ncnf_obj_destroy(obj);
}
/*
* Objects and iterators
*/
ncnf_obj *
ncnf_get_obj(ncnf_obj *root,
const char *opt_type, const char *opt_name,
enum ncnf_get_style style) {
if(root == NULL) {
errno = EINVAL;
return NULL;
}
return (ncnf_obj *)_ncnf_get_obj((struct ncnf_obj_s *)root,
opt_type, opt_name, style, _NGF_NOFLAGS);
}
ncnf_obj *
ncnf_obj_real(ncnf_obj *ref_obj_p) {
struct ncnf_obj_s *ref_obj = ref_obj_p;
if(ref_obj == NULL)
return NULL;
/*
* Resolve reference.
*/
ref_obj = _ncnf_real_object(ref_obj);
return (ncnf_obj *)ref_obj;
}
void
ncnf_iter_rewind(ncnf_obj *iter) {
if(iter == NULL)
return;
_ncnf_iter_rewind((struct ncnf_obj_s *)iter);
}
ncnf_obj *
ncnf_iter_next(ncnf_obj *iter) {
if(iter == NULL) {
errno = EINVAL;
return NULL;
}
return (ncnf_obj *)_ncnf_iter_next(iter);
}
int
ncnf_walk_tree(ncnf_obj *objp, int (*callback)(ncnf_obj *, void *),
void *key) {
if(objp == NULL || callback == NULL) {
errno = EINVAL;
return -1;
}
return _ncnf_walk_tree((struct ncnf_obj_s *)objp,
(int (*)(struct ncnf_obj_s *, void *))(callback),
key);
}
int
ncnf_construct_path(ncnf_obj *obj, char *delim,
int rev_order, char *(*name_func)(ncnf_obj *obj),
char *buf, int size) {
int wrote = 0;
ncnf_obj *o;
char *name;
if(obj == NULL || delim == NULL || buf == NULL || size <= 0) {
errno = EINVAL;
return -1;
}
if(!name_func)
name_func = ncnf_obj_name;
#define PUSH(ch) do{if(size>1){*buf++=ch;size--;}wrote++;}while(0)
#define PUSH2(ch) do{if(shift<size)buf[shift]=ch;shift++;}while(0)
if(rev_order) {
for(o = obj;
o && (name = name_func(o));
o = ncnf_obj_parent(o)) {
if(o != obj) {
char *dlm;
for(dlm = delim; *dlm; dlm++)
PUSH(*dlm);
}
for(; *name; name++)
PUSH(*name);
}
*buf = '\0'; /* NUL-terminate buffer */
} else {
int shift = 0;
int dlen = strlen(delim);
int nlen;
/*
* Compute the length of the future path string.
*/
for(o = obj;
o && (name = name_func(o));
o = ncnf_obj_parent(o)) {
if(o != obj)
shift += dlen;
shift += strlen(name);
}
wrote = shift;
if(shift < size)
buf[shift] = '\0';
/*
* Fill the string from the end using computed length.
* Althoug it is filled from the right to the left,
* separate tokens are filled from the left to the right, * that's why we double "shift -= xxx" stuff.
*/
for(o = obj;
o && (name = name_func(o));
o = ncnf_obj_parent(o)) {
if(o != obj) {
char *dlm;
shift -= dlen;
for(dlm = delim; *dlm; dlm++)
PUSH2(*dlm);
shift -= dlen;
}
nlen = strlen(name);
shift -= nlen;
for(; *name; name++)
PUSH2(*name);
shift -= nlen;
}
buf[size - 1] = '\0'; /* NUL-terminate buffer */
}
return wrote;
}
/*
* Names
*/
char *
ncnf_obj_type(ncnf_obj *objp) {
struct ncnf_obj_s *obj = objp;
if(obj == NULL) {
errno = EINVAL;
return NULL;
}
if(obj->type == NULL)
errno = 0; /* No error */
return obj->type;
}
char *
ncnf_obj_name(ncnf_obj *objp) {
struct ncnf_obj_s *obj = objp;
if(obj == NULL) {
errno = EINVAL;
return NULL;
}
if(obj->value == NULL)
errno = 0; /* No error */
return obj->value;
}
/*
* Attributes
*/
char *
ncnf_get_attr(ncnf_obj *objp, const char *opt_type) {
struct ncnf_obj_s *obj = objp;
if(obj == NULL) {
errno = EINVAL;
return NULL;
}
if(opt_type == NULL)
return obj->type;
return _ncnf_get_attr(obj, opt_type);
}
int
ncnf_get_attr_int(ncnf_obj *obj, const char *type, int *r) {
char *s;
if(type == NULL || r == NULL) {
errno = EINVAL;
return -1;
}
s = ncnf_get_attr(obj, type);
if(s == NULL)
return -1;
if((*s >= '0' && *s <= '9') || *s == '-') {
*r = atoi(s);
} else {
if(strcmp(s, "on") == 0
|| strcmp(s, "yes") == 0
|| strcmp(s, "true") == 0
) {
*r = 1;
} else if(strcmp(s, "off") == 0
|| strcmp(s, "no") == 0
|| strcmp(s, "false") == 0
) {
*r = 0;
} else {
return -1;
}
}
return 0;
}
long
ncnf_get_attr_long(ncnf_obj *obj, const char *type, long *r) {
char *s;
if(type == NULL || r == NULL) {
errno = EINVAL;
return -1;
}
s = ncnf_get_attr(obj, type);
if(s == NULL)
return -1;
if((*s >= '0' && *s <= '9') || *s == '-') {
*r = atol(s);
}
return 0;
}
int
ncnf_get_attr_double(ncnf_obj *obj, const char *type, double *r) {
char *s;
if(type == NULL || r == NULL) {
errno = EINVAL;
return -1;
}
s = ncnf_get_attr(obj, type);
if(s == NULL)
return -1;
*r = atof(s);
return 0;
}
int
ncnf_get_attr_ip(ncnf_obj *obj, const char *type, struct in_addr *r) {
char *s;
if(type == NULL || r == NULL) {
errno = EINVAL;
return -1;
}
s = ncnf_get_attr(obj, type);
if(s == NULL)
return -1;
if(inet_aton(s, r) != 1)
return -1;
return 0;
}
int
ncnf_get_attr_ipport(ncnf_obj *obj, const char *type, struct in_addr *rip, unsigned short *rhport) {
char *s;
char *port;
int ret;
if(type == NULL || rip == NULL || rhport == NULL) {
errno = EINVAL;
return -1;
}
s = ncnf_get_attr(obj, type);
if(s == NULL)
/* ESRCH */
return -1;
port = strchr(s, ':');
*rhport = port ? atoi(port + 1) : 0;
if(port) *port = '\0'; /* mask ':' */
ret = inet_aton(s, rip);
if(port) *port = ':'; /* unmask ':' */
if(!ret) {
errno = EINVAL;
return -1;
}
return 0;
}
/*
* User data
*/
/* Attach userdata, detaching previous */
int
ncnf_udata_attach(ncnf_obj *objp, void *user_data) {
struct ncnf_obj_s *obj = (struct ncnf_obj_s *)objp;
void *old_data;
if(obj == NULL) {
errno = EINVAL;
return -1;
}
/*
* Notify the notificator that the data gone, if there was some data.
*/
if(obj->user_data && obj->notify) {
if(obj->notify(objp,
NCNF_UDATA_DETACH,
obj->notify_key) == -1) {
errno = EPERM;
return -1;
}
}
old_data = obj->user_data;
obj->user_data = user_data;
/*
* Notify the notificator that the new data is attached.
*/
if(user_data && obj->notify) {
if(obj->notify(objp,
NCNF_UDATA_ATTACH,
obj->notify_key) == -1) {
obj->user_data = old_data;
errno = EPERM;
return -1;
}
}
return 0;
}
int
ncnf_notificator_attach(ncnf_obj *objp,
int (*notify)(ncnf_obj *obj, enum ncnf_notify_event, void *key),
void *key) {
struct ncnf_obj_s *obj = (struct ncnf_obj_s *)objp;
int (*old_notify)(ncnf_obj *, enum ncnf_notify_event, void *);
void *old_notify_key;
if(obj == NULL) {
errno = EINVAL;
return -1;
}
old_notify = obj->notify;
old_notify_key = obj->notify_key;
obj->notify = NULL;
if(old_notify) {
/* Report detach to the previous notificator */
if(old_notify(objp, NCNF_NOTIF_DETACH, old_notify_key) == -1) {
/* DETACH denied */
obj->notify = old_notify;
obj->notify_key = old_notify_key;
errno = EPERM;
return -1;
}
}
obj->notify = notify;
obj->notify_key = key;
/* Report attach to the new notificator */
if(obj->notify) {
if(notify(objp, NCNF_NOTIF_ATTACH, key) == -1) {
obj->notify = NULL;
obj->notify_key = NULL;
errno = EPERM;
return -1;
}
}
return 0;
}
int
ncnf_lazy_notificator(ncnf_obj *objp, char *watchfor,
int (*notify)(ncnf_obj *obj, enum ncnf_notify_event, void *key),
void *key) {
if(objp == NULL) {
errno = EINVAL;
return -1;
}
return _ncnf_lazy_notificator((struct ncnf_obj_s *)objp,
watchfor, notify, key);
}
/* Get the attached user data */
void *
ncnf_udata_get(const ncnf_obj *objp) {
if(objp) {
return ((const struct ncnf_obj_s *)objp)->user_data;
} else {
errno = EINVAL;
return NULL;
}
}
/*
* Diff the trees.
*/
int
ncnf_diff(ncnf_obj *old_treep, ncnf_obj *new_treep) {
struct ncnf_obj_s *old_tree = (struct ncnf_obj_s *)old_treep;
struct ncnf_obj_s *new_tree = (struct ncnf_obj_s *)new_treep;
if(old_tree == NULL || new_tree == NULL) {
errno = EINVAL;
return -1;
}
return _ncnf_diff(old_tree, new_tree);
}
/*
* Dump the whole tree.
*/
void
ncnf_dump(FILE *f, ncnf_obj *obj, const char *flatten_type, int marked_only, int verbose, int indent) {
int recursive_size = 0;
if(obj == NULL)
return;
if(f == NULL)
f = stdout;
_ncnf_obj_dump_recursive(f, (struct ncnf_obj_s *)obj,
flatten_type, marked_only,
verbose, 0, indent, 0, &recursive_size);
if(verbose)
fprintf(f, "# TOTAL RSIZE=%d\n", recursive_size);
}
int
ncnf_obj_marked(ncnf_obj *obj) {
if(obj) {
return obj->mark?1:0;
} else {
errno = EINVAL;
return -1;
}
}
int
ncnf_obj_line(ncnf_obj *obj) {
if(obj) {
return obj->config_line;
} else {
return 0;
}
}
static void
_async_sigchld_callback(int sig) {
int status;
pid_t pid;
assert(sig == SIGCHLD);
assert(_asyncval.state == AVS_INPROGRESS);
do {
pid = waitpid(_asyncval.validator_pid, &status,
WNOHANG | WUNTRACED);
} while(pid == -1 && errno == EINTR);
if(pid != _asyncval.validator_pid) return;
/* Restore old handler */
sigaction(sig, &_asyncval.old_sigchld, 0);
if(WIFEXITED(status)) {
if(WEXITSTATUS(status) == 0) {
_asyncval.state = AVS_SUCCEEDED;
} else {
_asyncval.state = AVS_FAILED;
}
} else {
_asyncval.state = AVS_FAILED;
if(WIFSTOPPED(status)) kill(pid, SIGKILL); /* Terminate */
}
/*
* Maybe we have missed an important child,
* let the parent gather its dead relatives.
*/
(void)raise(SIGCHLD);
/* Issue a "config reload" command again. */
(void)raise(SIGHUP);
}
static int
_fire_async_validation(const char *validator_proc, const char *fname) {
ncnf_sf_svect *sv;
struct sigaction new_sigchld;
sigset_t set, oset;
size_t i;
assert(_asyncval.state == AVS_NOSTATE);
if(_asyncval.state != AVS_NOSTATE) /* Just in case */
return -1;
sv = ncnf_sf_split(validator_proc, " \n\r\t", 0);
if(!sv) return -1;
if(sv->count == 0) {
ncnf_sf_sfree(sv);
return -1;
}
_asyncval.validator_pid = 0;
_asyncval.state = AVS_INPROGRESS;
/*
* Block SIGCHLD temporarily.
*/
sigemptyset(&set);
sigaddset(&set, SIGCHLD);
sigprocmask(SIG_BLOCK, &set, &oset);
/*
* Install our own child signal handler.
*/
memset(&new_sigchld, 0, sizeof(new_sigchld));
new_sigchld.sa_handler = _async_sigchld_callback;
new_sigchld.sa_flags = SA_RESTART;
if(sigaction(SIGCHLD, &new_sigchld, &_asyncval.old_sigchld)) {
_asyncval.state = AVS_NOSTATE;
ncnf_sf_sfree(sv);
return -1;
}
/*
* Fire the validator.
*/
_asyncval.validator_pid = fork();
switch(_asyncval.validator_pid) {
case -1:
(void)sigaction(SIGCHLD, &_asyncval.old_sigchld, 0);
_asyncval.state = AVS_NOSTATE;
sigprocmask(SIG_SETMASK, &oset, 0);
ncnf_sf_sfree(sv);
return -1;
case 0: /* Child (validator) process */
for(i = 0; i < sv->count; i++) {
if(strcmp(sv->list[i], "$config_file") == 0) {
sv->list[i] = strdup(fname);
if(!sv->list[i]) _exit(127);
}
}
execv(sv->list[0], sv->list);
_exit(127);
/* UNREACHABLE */
default: /* Parent */
sigprocmask(SIG_SETMASK, &oset, 0);
ncnf_sf_sfree(sv);
return 0;
}
}
syntax highlighted by Code2HTML, v. 0.9.1