/* * CONFIG - Server configuration handler * * Author: * Emile van Bergen, emile@evbergen.xs4all.nl * * Permission to redistribute an original or modified version of this program * in source, intermediate or object code form is hereby granted exclusively * under the terms of the GNU General Public License, version 2. Please see the * file COPYING for details, or refer to http://www.gnu.org/copyleft/gpl.html. * * History: * 2001/06/26 - EvB - Created * 2001/10/29 - EvB - Moved start_conf() from bigtest to here as * conf_start(). Not ideal, but still the best place IMHO. * 2002/03/01 - EvB - Made conf_del free interface's ACLs too * 2002/03/19 - EvB - Passed module base dir to subprocesses as current dir * 2002/03/21 - EvB - Oops. Make that the raddb dir, otherwise legacy/users * etc. would have to be stored outside raddb, which would * not be particularly user friendly. */ char config_id[] = "CONFIG - Copyright (C) 2001 Emile van Bergen."; /* * INCLUDES & DEFINES */ #include #include #include /* For memset() */ #include /* For close(), getpid() */ #include /* For struct in_addr */ #include /* For inet_ntoa() */ #include #include #include #include #include #include #include #include /* for job_del */ #include /* for chan_del_jobs */ /* * FUNCTIONS */ /* used by conf_new: adds source to configuration */ static int add_source(CONF *c, META *m, META_AV *data) { SOCK *sock, **head, **tail; META_AV *av; D1(msg(F_MISC, L_DEBUG, "Adding a source:\n")); D1(if (msg_thresh[F_MISC] >= L_DEBUG) meta_printavlist(m, data, 0)); /* Go to end of list first to allow adding multiple source sets */ head = &(c->sources); while(*head) head = &((*head)->next); tail = head; /* Walk through list, creating a socket for each instance of 'port' */ for(av = data; av; av = av->next) { if (av->i->nr != C_DI_PORT) continue; sock = (SOCK *)malloc(sizeof(SOCK)); if (!sock) { msg(F_MISC, L_ERR, "add_source: ERROR: Could not " "allocate new source!\n"); return -1; } memset(sock, 0, sizeof(SOCK)); sock->c = c; sock->port = av->l; sock->fd = -1; /* Add to list */ *tail = sock; tail = &(sock->next); } /* Now walk *both* lists a second time, putting in the IP addresses */ for(av = data, sock = *head; av && sock; av = av->next) { if (av->i->nr != C_DI_ADDR) continue; /* Only go to the next socket if we got an instance of 'addr' */ sock->ip = av->l; sock = sock->next; } return 0; } /* used by conf_new: adds interface to configuration */ static int add_iface(CONF *c, META *m, META_AV *data, char *modbasepath, char *progcwd) { IFACE *iface; META_SPEC spec; META_ITEM *i; META_AV *av, *aclav; CHAN *ch; PROC *p; D1(msg(F_MISC, L_DEBUG, "Adding an interface:\n")); D1(if (msg_thresh[F_MISC] >= L_DEBUG) meta_printavlist(m, data, 0)); /* Allocate interface */ iface = (IFACE *)malloc(sizeof(IFACE)); if (!iface) { msg(F_MISC, L_ERR, "add_source: ERROR: Could not allocate " "new interface!\n"); return -1; } memset(iface, 0, sizeof(IFACE)); iface->xfertimeout = PRT_XFERTIMEOUT; iface->window = 1; /* sync iface is default */ /* And add the interface to the configuration's list */ iface->next = c->ifaces; c->ifaces = iface; iface->c = c; /* Now walk through the list the first time, setting overall interface parameters: name, flags, xfertimeout, ACLs. */ for(av = data; av; av = av->next) { switch(av->i->nr) { case C_DI_NAME: setname_n(iface->name, av->p, av->l); break; case C_DI_FLAGS: iface->flags = av->l; break; case C_DI_TIMEOUT: iface->xfertimeout = av->l; break; case C_DI_WINDOW: iface->window = av->l; break; case C_DI_SENDATTR: case C_DI_RECVATTR: case C_DI_JOBTICKET: case C_DI_PIDATTR: setspec_n(spec, av->p, av->l); i = meta_getitembyspec(m, spec); if (!i) { msg(F_MISC, L_ERR, "add_iface: ERROR: Unknown ACL/jobticket attribute '%s'!\n", spec); return -1; } if (av->i->nr == C_DI_JOBTICKET) { if (i->val_type != MT_INTEGER && i->val_type != MT_STRING) { msg(F_MISC, L_ERR, "add_iface: ERROR: Job ticket attribute '%s' must be of string or integer type!\n", spec); return -1; } iface->jobticket = i; break; } if (av->i->nr == C_DI_PIDATTR) { if (i->val_type != MT_INTEGER) { msg(F_MISC, L_ERR, "add_iface: ERROR: Module PID attribute '%s' must be of integer type!\n", spec); return -1; } iface->pidattr = i; break; } /* Allocate AV item */ aclav = (META_AV *)malloc(sizeof(META_AV)); if (!aclav) { msg(F_MISC, L_ERR, "add_iface: ERROR: Could not allocate ACL AV!\n"); return -1; } memset(aclav, 0, sizeof(META_AV)); aclav->i = i; /* And add it to the right ACL of this interface */ if (av->i->nr == C_DI_SENDATTR) { aclav->next = iface->sendacl; iface->sendacl = aclav; } else { aclav->next = iface->recvacl; iface->recvacl = aclav; } break; } } /* If we have a send acl and a job ticket, add the job ticket to the send acl. A little dwimmy but I think it's worth it here. */ if (iface->sendacl && iface->jobticket) { aclav = (META_AV *)malloc(sizeof(META_AV)); if (!aclav) { msg(F_MISC, L_ERR, "add_iface: ERROR: Could not allocate ACL AV!\n"); return -1; } memset(aclav, 0, sizeof(META_AV)); aclav->i = iface->jobticket; aclav->next = iface->sendacl; iface->sendacl = aclav; } /* Check if we at least have a name now */ if (!iface->name[0]) { msg(F_MISC, L_ERR, "add_iface: ERROR: 'name' missing from " "interface!\n"); return -1; } /* Now add a channel + subprocess for each 'prog' item */ for(ch = 0, av = data; av; av = av->next) { if (av->i->nr != C_DI_PROG) continue; ch = (CHAN *)malloc(sizeof(CHAN)); if (!ch) { msg(F_MISC, L_ERR, "add_iface: ERROR: Could not " "allocate channel for interface " "'%s'!\n", iface->name); return -1; } memset(ch, 0, sizeof(CHAN)); /* Add channel to this interface's list */ ch->iface = iface; ch->next = iface->chans; iface->chans = ch; /* Create a subprocess that is associated with this channel */ p = proc_new(av->p, av->l, iface->flags, iface->xfertimeout, ch, modbasepath, progcwd); if (!p) { msg(F_MISC, L_ERR, "add_iface: ERROR: could not define " "subprocess '%s' for interface '%s'" "!\n", dbg_cvtstr(av->p, av->l), iface->name); return -1; } ch->proc = p; /* And add it to the overall list of subprocesses */ p->next = c->procs; c->procs = p; } /* Check if we at least have one channel for this interface */ if (!ch) { msg(F_MISC, L_ERR, "add_iface: ERROR: No program(s) specified " "for interface '%s'!\n", iface->name); return -1; } /* Initialise the iface's next round-robin channel to the first one */ iface->rrch = iface->chans; return 0; } CONF *conf_new(META *m, char *basepath, char *modbasepath) { META_AV *av; CONF *ret; IFACE *ifs; TEXT *t; VM *vm; int n; /* Initialise the local temp. objects to make error recovery simpler */ t = 0; ifs = 0; vm = 0; /* Allocate the configuration object */ ret = (CONF *)malloc(sizeof(CONF)); if (!ret) { msg(F_MISC, L_ERR, "conf_new: ERROR: Could not allocate " "new configuration!\n"); return 0; } memset(ret, 0, sizeof(CONF)); ret->m = m; if (!(ret->ds_ground = meta_getspcbynr(m, C_DS_GROUND)) || /* RAD-PKT */ !(ret->ds_rad_pkt = meta_getspcbynr(m, C_DS_RAD_PKT)) || !(ret->di_code = meta_getitembynr(m, ret->ds_rad_pkt, C_DI_CODE, META_VND_ANY)) || !(ret->di_authenticator = meta_getitembynr(m, ret->ds_rad_pkt, C_DI_AUTHENTICATOR, META_VND_ANY)) || /* INTERNAL */ !(ret->ds_internal = meta_getspcbynr(m, C_DS_INTERNAL)) || !(ret->di_timestamp = meta_getitembynr(m, ret->ds_internal, C_DI_TIMESTAMP, META_VND_ANY)) || !(ret->di_ip_source = meta_getitembynr(m, ret->ds_internal, C_DI_IP_SOURCE, META_VND_ANY)) || !(ret->di_ip_dest = meta_getitembynr(m, ret->ds_internal, C_DI_IP_DEST, META_VND_ANY)) || !(ret->di_udp_source = meta_getitembynr(m, ret->ds_internal, C_DI_UDP_SOURCE, META_VND_ANY)) || !(ret->di_udp_dest = meta_getitembynr(m, ret->ds_internal, C_DI_UDP_DEST, META_VND_ANY)) || !(ret->di_source = meta_getitembynr(m, ret->ds_internal, C_DI_SOURCE, META_VND_ANY)) || !(ret->di_dest = meta_getitembynr(m, ret->ds_internal, C_DI_DEST, META_VND_ANY)) || !(ret->di_secret = meta_getitembynr(m, ret->ds_internal, C_DI_SECRET, META_VND_ANY)) || !(ret->di_log_line = meta_getitembynr(m, ret->ds_internal, C_DI_LOG_LINE, META_VND_ANY)) || !(ret->di_server_pid = meta_getitembynr(m, ret->ds_internal, C_DI_SERVER_PID, META_VND_ANY)) || !(ret->di_request_nr = meta_getitembynr(m, ret->ds_internal, C_DI_REQUEST_NR, META_VND_ANY)) || /* RAD-ATR */ !(ret->ds_rad_atr = meta_getspcbynr(m, C_DS_RAD_ATR)) || !(ret->di_msg_auth = meta_getitembynr(m, ret->ds_rad_atr, C_DI_MSG_AUTH, META_VND_ANY)) || 0) { msg(F_MISC, L_ERR, "conf_new: ERROR: Dictionary does not " "define a required space or data item!\n"); free(ret); return 0; } /* Create two fake interfaces, 'source' and 'interface' */ ifs = (IFACE *)malloc(sizeof(IFACE) << 1); if (!ifs) { msg(F_MISC, L_ERR, "conf_new: ERROR: Could not allocate " "pseudo-interfaces!\n"); goto cn_cleanup; } memset(ifs, 0, sizeof(IFACE) << 1); strcpy(ifs[0].name, C_IF_SOURCE_NAME); ifs[0].next = ifs + 1; strcpy(ifs[1].name, C_IF_IFACE_NAME); ifs[1].next = 0; /* Get a text object, first for the configuration file */ t = text_new(basepath, CONF_MAX_IDENTLEN + 1); msg(F_MISC, L_NOTICE, "conf_new: Opening %s/" CONF_MAINFNAME "\n", basepath); if (!t) { msg(F_MISC, L_ERR, "Could not create text stream!\n"); goto cn_cleanup; } if (text_include(t, CONF_MAINFNAME) == -1) { if (basepath) msg(F_MISC, L_NOTICE, "conf_new: Warning: Could not open file '" CONF_MAINFNAME "' in directory '%s': %s!\n", basepath, strerror(errno)); else msg(F_MISC, L_NOTICE, "conf_new: ERRROR: Could not open file '" CONF_MAINFNAME "': %s!\n", strerror(errno)); goto cn_noconf; } /* Allocate space for the compiled configuration expression and compile it */ ret->confexpr = (INSN *)malloc(CONF_MAX_CODELEN); if (!ret->confexpr) { msg(F_MISC, L_ERR, "conf_new: ERROR: Could not " "allocate code buffer!\n"); goto cn_cleanup; } ret->confexprlen = lang_compile(m, ifs, t, ret->confexpr, CONF_MAX_CODELEN); if (ret->confexprlen == -1) { msg(F_MISC, L_ERR, "conf_new: ERROR: Couldn't compile " "configuration!\n"); goto cn_cleanup; } if (msg_thresh[F_LANG] >= L_DEBUG) lang_disassemble(m, ret->confexpr, ret->confexprlen); /* Create a VM and run the configuration expression */ vm = vm_new(ret->confexpr, ret->confexprlen, 32, 0, 0, 0, 0); if (!vm) { msg(F_MISC, L_ERR, "conf_new: ERROR: Could not allocate VM!" "\n"); goto cn_cleanup; } while((n = vm_run(m, vm)) == VM_IFACETRAP) { /* Act on pseudo interface call (either source or interface) */ if (vm_getiface(vm) == ifs) n = add_source(ret, m, vm->head[VM_REQ]); else { /* We use raddb (basepath) as prog's cwd as well */ n = add_iface(ret, m, vm->head[VM_REQ], modbasepath, basepath); } if (n) goto cn_cleanup; /* As a convenience, clear the VM's lists after each call */ meta_freeavlist(vm->head[VM_REQ]); vm->head[VM_REQ] = vm->tail[VM_REQ] = 0; meta_freeavlist(vm->head[VM_REP]); vm->head[VM_REP] = vm->tail[VM_REP] = 0; } if (n != VM_HALTED) { msg(F_MISC, L_ERR, "conf_new: ERROR: Could not " "run configuration!\n"); goto cn_cleanup; } /* Save the leftover request and reply lists to initialize the lists before processing the behaviour file */ ret->inithead[VM_REQ] = vm->head[VM_REQ]; ret->inittail[VM_REQ] = vm->tail[VM_REQ]; ret->inithead[VM_REP] = vm->head[VM_REP]; ret->inittail[VM_REP] = vm->tail[VM_REP]; /* Fixup REQ:Server-Pid, if given */ for(av = ret->inithead[VM_REQ]; av; av = av->next) { if (av->i != ret->di_server_pid) continue; av->l = getpid(); break; } /* Free the vm and the fake interfaces, as they're not needed anymore, and also close the configuration file. Note that the VM is not the owner of the request and reply lists. */ vm_del(vm); vm = 0; free(ifs); ifs = 0; text_endfile(t); /* Use the same text object to read in the behaviour file */ cn_noconf: msg(F_MISC, L_NOTICE, "conf_new: Opening %s/" EXPR_MAINFNAME "\n", basepath); if (text_include(t, EXPR_MAINFNAME) == -1) { if (basepath) msg(F_MISC, L_ERR, "conf_new: ERROR: Could not " "open file '" EXPR_MAINFNAME "' in directory '%s': %s!\n", basepath, strerror(errno)); else msg(F_MISC, L_ERR, "conf_new: ERRROR: Could not " "open file '" EXPR_MAINFNAME "': %s!\n", strerror(errno)); goto cn_cleanup; } /* Allocate space for the compiled behaviour file and compile it */ ret->expr = (INSN *)malloc(CONF_MAX_CODELEN); if (!ret->expr) { msg(F_MISC, L_ERR, "conf_new: ERROR: Could not " "allocate code buffer!\n"); goto cn_cleanup; } ret->exprlen = lang_compile(m,ret->ifaces,t,ret->expr,CONF_MAX_CODELEN); if (ret->exprlen == -1) { msg(F_MISC, L_ERR, "conf_new: ERROR: Couldn't" " compile behaviour file!\n"); goto cn_cleanup; } if (msg_thresh[F_LANG] >= L_DEBUG) lang_disassemble(m, ret->expr, ret->exprlen); /* Done. Free the text file and return the created configuration. */ text_del(t); return ret; cn_cleanup: if (vm) vm_del(vm); if (t) text_del(t); if (ifs) free(ifs); if (ret) conf_del(ret); return 0; } void conf_del(CONF *c) { SOCK *s; IFACE *i; CHAN *ch, *tmpch; #ifndef CONF_TEST JOB *j, *tmpj; #endif PROC *p; if (c) { /* Close and free sources */ while(c->sources) { if (c->sources->fd != -1) close(c->sources->fd); s = c->sources->next; free(c->sources); c->sources = s; } /* Stop and free interfaces */ while(c->ifaces) { #ifndef CONF_TEST /* Kill jobs on this interface's shared send queue */ for(j = c->ifaces->sendqh; j; j = tmpj) { tmpj = j->next; job_del(j); } c->ifaces->sendqh = c->ifaces->sendqt = 0; #endif /* Free channels */ for(ch = c->ifaces->chans; ch; ch = tmpch) { #ifndef CONF_TEST /* Kill jobs in this channel's recv. ring and free A/V pairs on receive list */ chan_flush(ch); #endif /* Free channel and go to next */ tmpch = ch->next; free(ch); } /* Free ACLs */ meta_freeavlist(c->ifaces->sendacl); meta_freeavlist(c->ifaces->recvacl); /* Free interface itself and go to next */ i = c->ifaces->next; free(c->ifaces); c->ifaces = i; } /* Delete subprocesses */ while(c->procs) { p = c->procs->next; proc_del(c->procs); c->procs = p; } /* Free the config- and behaviour expressions and the initial * request- and reply lists */ if (c->confexpr) free(c->confexpr); if (c->expr) free(c->expr); if (c->inithead[VM_REQ]) meta_freeavlist(c->inithead[VM_REQ]); if (c->inithead[VM_REP]) meta_freeavlist(c->inithead[VM_REP]); /* Finally, free the configuration object itself */ free(c); } } /* Creates sockets and runs subprocesses according to config */ int conf_start(CONF *c, time_t t) { SOCK *sock; PROC *proc; struct sockaddr_in sin; /* Activate the sources */ for(sock = c->sources; sock; sock = sock->next) { /* Create socket */ sock->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sock->fd == -1) { msg(F_RECV, L_ERR, "conf_start: Could not create " "socket: %s!\n", strerror(errno)); return -1; } /* Bind to the specified address */ memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(sock->port); /* sorry, but I don't carry ASCII addresses around, and I refuse to go from host order via ASCII through inet_aton as it officially should. The first one to report that this is broken on his/her system gets a beer on the house. */ putord((char *)&(sin.sin_addr), sizeof(sin.sin_addr), sock->ip); msg(F_RECV, L_NOTICE,"conf_start: Opening socket on %s, port " "%d\n", inet_ntoa(sin.sin_addr), sock->port); if (bind(sock->fd, (struct sockaddr *)&sin, sizeof(sin)) == -1){ msg(F_MISC, L_ERR, "conf_start: Could not bind to %s, " "port %d: %s!\n", inet_ntoa(sin.sin_addr),sock->port,strerror(errno)); return -1; } /* Set close on exec on them - we don't want the subprocesses to be able to access the sockets. */ fcntl(sock->fd, F_SETFD, 1); } /* Start the subprograms */ for(proc = c->procs; proc; proc = proc->next) { msg(F_PROC, L_NOTICE,"conf_start: Starting %s for %s\n", proc->argv[0], proc->chan->iface->name); proc_start(proc, t); } return 0; } #ifdef CONF_TEST /* * MAIN * * For testing only. Enable by defining CONF_TEST. * */ #include int main(int argc, char **argv) { META *m; CONF *c; SOCK *sock; IFACE *iface; PROC *proc; char **s; msg_setthresh(F_MISC, L_DEBUG); if (argc != 2) { msg(F_MISC, L_ERR, "Usage: %s raddb\n", argv[0]); return 1; } /* Open dictionary */ m = meta_newfromdict(argv[1]); if (!m) { msg(F_MISC, L_ERR, "main: ERROR: Could not open dictionary!\n"); return 1; } /* Open configuration */ c = conf_new(m, argv[1], MODULES); if (!c) { msg(F_MISC, L_ERR, "main: ERROR: Could not create " "configuration!\n"); return 1; } /* Show sockets */ for(sock = c->sources; sock; sock = sock->next) { msg(F_MISC, L_DEBUG, "Socket: ip=0x%08lx, port=%ld\n", sock->ip, sock->port); } /* Show interfaces */ for(iface = c->ifaces; iface; iface = iface->next) { msg(F_MISC, L_DEBUG, "Iface: name=%s, flags=%08x, timeout=%d\n", iface->name, iface->flags, iface->xfertimeout); if (iface->sendacl) { msg(F_MISC, L_DEBUG, " Send ACL:\n"); meta_printavlist(m, iface->sendacl, 0); } if (iface->recvacl) { msg(F_MISC, L_DEBUG, " Recv ACL:\n"); meta_printavlist(m, iface->recvacl, 0); } } /* Show subprograms */ for(proc = c->procs; proc; proc = proc->next) { msg(F_MISC, L_DEBUG, "Proc: file=%s, iface=%s\n", proc->argv[0], proc->chan->iface->name); msg(F_MISC, L_DEBUG, " Arguments:\n"); for(s = proc->argv; *s; s++) msg(F_MISC, L_DEBUG, "\t%s\n", *s); msg(F_MISC, L_DEBUG, " Environment:\n"); for(s = proc->envp; *s; s++) msg(F_MISC, L_DEBUG, "\t%s\n", *s); } /* Show initial request and reply lists */ msg(F_MISC, L_DEBUG, "Initial request list:\n"); if (c->inithead[VM_REQ]) meta_printavlist(m, c->inithead[VM_REQ], 0); msg(F_MISC, L_DEBUG, "Initial reply list:\n"); if (c->inithead[VM_REP]) meta_printavlist(m, c->inithead[VM_REP], 0); /* Show configuration expression */ msg(F_MISC, L_DEBUG, "Compiled configuration expression:\n"); lang_disassemble(m, c->confexpr, c->confexprlen); /* Show behaviour expression */ msg(F_MISC, L_DEBUG, "Compiled behaviour expression:\n"); lang_disassemble(m, c->expr, c->exprlen); conf_del(c); meta_del(m); return 0; } #endif