rsyslog/runtime/cfsysline.c
Rainer Gerhards cbcaf2c7e5
core: add global parameter "security.abortOnIDResolutionFail"
This parameter controls whether or not rsyslog aborts when a name ID
lookup fails (for user and group names). This is necessary as a security
measure, as otherwise the wrong permissions can be assigned or privileges
are not dropped.

CHANGE OF BEHAVIOR
The default for this parameter is "on". In previous versions, the default
was "off" (by virtue) of this parameter not existing. As such, existing
configurations may now error out.

We have decided to accept this change of behavior because of the potential
security implications.

closes https://github.com/rsyslog/rsyslog/issues/4164
2020-02-11 12:54:19 +01:00

1085 lines
28 KiB
C

/* cfsysline.c
* Implementation of the configuration system line object.
*
* File begun on 2007-07-30 by RGerhards
*
* Copyright (C) 2007-2020 Adiscon GmbH.
*
* This file is part of rsyslog.
*
* This file is part of the rsyslog runtime library.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* -or-
* see COPYING.ASL20 in the source distribution
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <pwd.h>
#include <grp.h>
#include "rsyslog.h"
#include "cfsysline.h"
#include "obj.h"
#include "conf.h"
#include "errmsg.h"
#include "srUtils.h"
#include "unicode-helper.h"
#include "rsconf.h"
#include "parserif.h"
/* static data */
DEFobjCurrIf(obj)
linkedList_t llCmdList; /* this is NOT a pointer - no typo here ;) */
/* --------------- START functions for handling canned syntaxes --------------- */
/* parse a character from the config line
* added 2007-07-17 by rgerhards
* TODO: enhance this function to handle different classes of characters
* HINT: check if char is ' and, if so, use 'c' where c may also be things
* like \t etc.
*/
static rsRetVal doGetChar(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *pVal)
{
DEFiRet;
assert(pp != NULL);
assert(*pp != NULL);
skipWhiteSpace(pp); /* skip over any whitespace */
/* if we are not at a '\0', we have our new char - no validity checks here... */
if(**pp == '\0') {
LogError(0, RS_RET_NOT_FOUND, "No character available");
iRet = RS_RET_NOT_FOUND;
} else {
if(pSetHdlr == NULL) {
/* we should set value directly to var */
*((uchar*)pVal) = **pp;
} else {
/* we set value via a set function */
CHKiRet(pSetHdlr(pVal, **pp));
}
++(*pp); /* eat processed char */
}
finalize_it:
RETiRet;
}
/* Parse a number from the configuration line. This is more or less
* a shell to call the custom handler.
* rgerhards, 2007-07-31
*/
static rsRetVal doCustomHdlr(uchar **pp, rsRetVal (*pSetHdlr)(uchar**, void*), void *pVal)
{
DEFiRet;
assert(pp != NULL);
assert(*pp != NULL);
CHKiRet(pSetHdlr(pp, pVal));
finalize_it:
RETiRet;
}
/* Parse a number from the configuration line. This functions just parses
* the number and does NOT call any handlers or set any values. It is just
* for INTERNAL USE by other parse functions!
* rgerhards, 2008-01-08
*/
static rsRetVal parseIntVal(uchar **pp, int64 *pVal)
{
DEFiRet;
uchar *p;
int64 i;
int bWasNegative;
assert(pp != NULL);
assert(*pp != NULL);
assert(pVal != NULL);
skipWhiteSpace(pp); /* skip over any whitespace */
p = *pp;
if(*p == '-') {
bWasNegative = 1;
++p; /* eat it */
} else {
bWasNegative = 0;
}
if(!isdigit((int) *p)) {
errno = 0;
LogError(0, RS_RET_INVALID_INT, "invalid number");
ABORT_FINALIZE(RS_RET_INVALID_INT);
}
/* pull value */
for(i = 0 ; *p && (isdigit((int) *p) || *p == '.' || *p == ',') ; ++p) {
if(isdigit((int) *p)) {
i = i * 10 + *p - '0';
}
}
if(bWasNegative)
i *= -1;
*pVal = i;
*pp = p;
finalize_it:
RETiRet;
}
/* Parse a size from the configuration line. This is basically an integer
* syntax, but modifiers may be added after the integer (e.g. 1k to mean
* 1024). The size must immediately follow the number. Note that the
* param value must be int64!
* rgerhards, 2008-01-09
*/
static rsRetVal doGetSize(uchar **pp, rsRetVal (*pSetHdlr)(void*, int64), void *pVal)
{
DEFiRet;
int64 i;
assert(pp != NULL);
assert(*pp != NULL);
CHKiRet(parseIntVal(pp, &i));
/* we now check if the next character is one of our known modifiers.
* If so, we accept it as such. If not, we leave it alone. tera and
* above does not make any sense as that is above a 32-bit int value.
*/
switch(**pp) {
/* traditional binary-based definitions */
case 'k': i *= 1024; ++(*pp); break;
case 'm': i *= 1024 * 1024; ++(*pp); break;
case 'g': i *= 1024 * 1024 * 1024; ++(*pp); break;
case 't': i *= (int64) 1024 * 1024 * 1024 * 1024; ++(*pp); break; /* tera */
case 'p': i *= (int64) 1024 * 1024 * 1024 * 1024 * 1024; ++(*pp); break; /* peta */
case 'e': i *= (int64) 1024 * 1024 * 1024 * 1024 * 1024 * 1024; ++(*pp); break; /* exa */
/* and now the "new" 1000-based definitions */
case 'K': i *= 1000; ++(*pp); break;
case 'M': i *= 1000000; ++(*pp); break;
case 'G': i *= 1000000000; ++(*pp); break;
/* we need to use the multiplication below because otherwise
* the compiler gets an error during constant parsing */
case 'T': i *= (int64) 1000 * 1000000000; ++(*pp); break; /* tera */
case 'P': i *= (int64) 1000000 * 1000000000; ++(*pp); break; /* peta */
case 'E': i *= (int64) 1000000000 * 1000000000; ++(*pp); break; /* exa */
}
/* done */
if(pSetHdlr == NULL) {
/* we should set value directly to var */
*((int64*)pVal) = i;
} else {
/* we set value via a set function */
CHKiRet(pSetHdlr(pVal, i));
}
finalize_it:
RETiRet;
}
/* Parse a number from the configuration line.
* rgerhards, 2007-07-31
*/
static rsRetVal doGetInt(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *pVal)
{
uchar *p;
DEFiRet;
int64 i;
assert(pp != NULL);
assert(*pp != NULL);
CHKiRet(doGetSize(pp, NULL,&i));
p = *pp;
if(i > 2147483648ll) { /*2^31*/
LogError(0, RS_RET_INVALID_VALUE,
"value %lld too large for integer argument.", i);
ABORT_FINALIZE(RS_RET_INVALID_VALUE);
}
if(pSetHdlr == NULL) {
/* we should set value directly to var */
*((int*)pVal) = (int) i;
} else {
/* we set value via a set function */
CHKiRet(pSetHdlr(pVal, (int) i));
}
*pp = p;
finalize_it:
RETiRet;
}
/* Parse and interpret a $FileCreateMode and $umask line. This function
* pulls the creation mode and, if successful, stores it
* into the global variable so that the rest of rsyslogd
* opens files with that mode. Any previous value will be
* overwritten.
* HINT: if we store the creation mode in selector_t, we
* can even specify multiple modes simply be virtue of
* being placed in the right section of rsyslog.conf
* rgerhards, 2007-07-4 (happy independence day to my US friends!)
* Parameter **pp has a pointer to the current config line.
* On exit, it will be updated to the processed position.
*/
static rsRetVal doFileCreateMode(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *pVal)
{
uchar *p;
DEFiRet;
int iVal;
assert(pp != NULL);
assert(*pp != NULL);
skipWhiteSpace(pp); /* skip over any whitespace */
p = *pp;
/* for now, we parse and accept only octal numbers
* Sequence of tests is important, we are using boolean shortcuts
* to avoid addressing invalid memory!
*/
if(!( (*p == '0')
&& (*(p+1) && *(p+1) >= '0' && *(p+1) <= '7')
&& (*(p+2) && *(p+2) >= '0' && *(p+2) <= '7')
&& (*(p+3) && *(p+3) >= '0' && *(p+3) <= '7') ) ) {
LogError(0, RS_RET_INVALID_VALUE, "value must be octal (e.g 0644).");
ABORT_FINALIZE(RS_RET_INVALID_VALUE);
}
/* we reach this code only if the octal number is ok - so we can now
* compute the value.
*/
iVal = (*(p+1)-'0') * 64 + (*(p+2)-'0') * 8 + (*(p+3)-'0');
if(pSetHdlr == NULL) {
/* we should set value directly to var */
*((int*)pVal) = iVal;
} else {
/* we set value via a set function */
CHKiRet(pSetHdlr(pVal, iVal));
}
p += 4; /* eat the octal number */
*pp = p;
finalize_it:
RETiRet;
}
/* Parse and interpret an on/off inside a config file line. This is most
* often used for boolean options, but of course it may also be used
* for other things. The passed-in pointer is updated to point to
* the first unparsed character on exit. Function emits error messages
* if the value is neither on or off. It returns 0 if the option is off,
* 1 if it is on and another value if there was an error.
* rgerhards, 2007-07-15
*/
static int doParseOnOffOption(uchar **pp)
{
uchar *pOptStart;
uchar szOpt[32];
assert(pp != NULL);
assert(*pp != NULL);
pOptStart = *pp;
skipWhiteSpace(pp); /* skip over any whitespace */
if(getSubString(pp, (char*) szOpt, sizeof(szOpt), ' ') != 0) {
LogError(0, NO_ERRCODE, "Invalid $-configline - could not extract on/off option");
return -1;
}
if(!strcmp((char*)szOpt, "on")) {
return 1;
} else if(!strcmp((char*)szOpt, "off")) {
return 0;
} else {
LogError(0, NO_ERRCODE, "Option value must be on or off, but is '%s'", (char*)pOptStart);
return -1;
}
}
/* extract a groupname and return its gid.
* rgerhards, 2007-07-17
*/
static rsRetVal doGetGID(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *pVal)
{
struct group *pgBuf = NULL;
struct group gBuf;
DEFiRet;
uchar szName[256];
int bufSize = 1024;
char * stringBuf = NULL;
int err;
assert(pp != NULL);
assert(*pp != NULL);
if(getSubString(pp, (char*) szName, sizeof(szName), ' ') != 0) {
if(loadConf->globals.abortOnIDResolutionFail) {
fprintf(stderr, "could not extract group name: %s\n", (char*)szName);
exit(1); /* good exit */
} else {
LogError(0, RS_RET_NOT_FOUND, "could not extract group name");
ABORT_FINALIZE(RS_RET_NOT_FOUND);
}
}
do {
char *p;
/* Increase bufsize and try again.*/
bufSize *= 2;
CHKmalloc(p = realloc(stringBuf, bufSize));
stringBuf = p;
err = getgrnam_r((char*)szName, &gBuf, stringBuf, bufSize, &pgBuf);
} while((pgBuf == NULL) && (err == ERANGE));
if(pgBuf == NULL) {
if (err != 0) {
LogError(err, RS_RET_NOT_FOUND, "Query for group '%s' resulted in an error",
szName);
} else {
LogError(0, RS_RET_NOT_FOUND, "ID for group '%s' could not be found", szName);
}
iRet = RS_RET_NOT_FOUND;
if(loadConf->globals.abortOnIDResolutionFail) {
fprintf(stderr, "ID for group '%s' could not be found or error\n", szName);
exit(1); /* good exit */
}
} else {
if(pSetHdlr == NULL) {
/* we should set value directly to var */
*((gid_t*)pVal) = pgBuf->gr_gid;
} else {
/* we set value via a set function */
CHKiRet(pSetHdlr(pVal, pgBuf->gr_gid));
}
dbgprintf("gid %d obtained for group '%s'\n", (int) pgBuf->gr_gid, szName);
}
skipWhiteSpace(pp); /* skip over any whitespace */
finalize_it:
free(stringBuf);
RETiRet;
}
/* extract a username and return its uid.
* rgerhards, 2007-07-17
*/
static rsRetVal doGetUID(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *pVal)
{
struct passwd *ppwBuf;
struct passwd pwBuf;
DEFiRet;
uchar szName[256];
char stringBuf[2048]; /* I hope this is large enough... */
assert(pp != NULL);
assert(*pp != NULL);
if(getSubString(pp, (char*) szName, sizeof(szName), ' ') != 0) {
if(loadConf->globals.abortOnIDResolutionFail) {
fprintf(stderr, "could not extract user name: %s\n", (char*)szName);
exit(1); /* good exit */
} else {
LogError(0, RS_RET_NOT_FOUND, "could not extract user name");
ABORT_FINALIZE(RS_RET_NOT_FOUND);
}
}
getpwnam_r((char*)szName, &pwBuf, stringBuf, sizeof(stringBuf), &ppwBuf);
if(ppwBuf == NULL) {
if(loadConf->globals.abortOnIDResolutionFail) {
fprintf(stderr, "ID for user '%s' could not be found or error\n", (char*)szName);
exit(1); /* good exit */
} else {
LogError(0, RS_RET_NOT_FOUND, "ID for user '%s' could not be found or error", (char*)szName);
iRet = RS_RET_NOT_FOUND;
}
} else {
if(pSetHdlr == NULL) {
/* we should set value directly to var */
*((uid_t*)pVal) = ppwBuf->pw_uid;
} else {
/* we set value via a set function */
CHKiRet(pSetHdlr(pVal, ppwBuf->pw_uid));
}
dbgprintf("uid %d obtained for user '%s'\n", (int) ppwBuf->pw_uid, szName);
}
skipWhiteSpace(pp); /* skip over any whitespace */
finalize_it:
RETiRet;
}
/* Parse and process an binary cofig option. pVal must be
* a pointer to an integer which is to receive the option
* value.
* rgerhards, 2007-07-15
*/
static rsRetVal doBinaryOptionLine(uchar **pp, rsRetVal (*pSetHdlr)(void*, int), void *pVal)
{
int iOption;
DEFiRet;
assert(pp != NULL);
assert(*pp != NULL);
if((iOption = doParseOnOffOption(pp)) == -1)
return RS_RET_ERR; /* nothing left to do */
if(pSetHdlr == NULL) {
/* we should set value directly to var */
*((int*)pVal) = iOption;
} else {
/* we set value via a set function */
CHKiRet(pSetHdlr(pVal, iOption));
}
skipWhiteSpace(pp); /* skip over any whitespace */
finalize_it:
RETiRet;
}
/* parse a whitespace-delimited word from the provided string. This is a
* helper function for a number of syntaxes. The parsed value is returned
* in ppStrB (which must be provided by caller).
* rgerhards, 2008-02-14
*/
static rsRetVal
getWord(uchar **pp, cstr_t **ppStrB)
{
DEFiRet;
uchar *p;
assert(pp != NULL);
assert(*pp != NULL);
assert(ppStrB != NULL);
CHKiRet(cstrConstruct(ppStrB));
skipWhiteSpace(pp); /* skip over any whitespace */
/* parse out the word */
p = *pp;
while(*p && !isspace((int) *p)) {
CHKiRet(cstrAppendChar(*ppStrB, *p++));
}
cstrFinalize(*ppStrB);
*pp = p;
finalize_it:
RETiRet;
}
/* Parse and a word config line option. A word is a consequtive
* sequence of non-whitespace characters. pVal must be
* a pointer to a string which is to receive the option
* value. The returned string must be freed by the caller.
* rgerhards, 2007-09-07
* To facilitate multiple instances of the same command line
* directive, doGetWord() now checks if pVal is already a
* non-NULL pointer. If so, we assume it was created by a previous
* incarnation and is automatically freed. This happens only when
* no custom handler is defined. If it is, the customer handler
* must do the cleanup. I have checked and this was al also memory
* leak with some code. Obviously, not a large one. -- rgerhards, 2007-12-20
* Just to clarify: if pVal is parsed to a custom handler, this handler
* is responsible for freeing pVal. -- rgerhards, 2008-03-20
*/
static rsRetVal doGetWord(uchar **pp, rsRetVal (*pSetHdlr)(void*, uchar*), void *pVal)
{
DEFiRet;
cstr_t *pStrB = NULL;
uchar *pNewVal;
assert(pp != NULL);
assert(*pp != NULL);
CHKiRet(getWord(pp, &pStrB));
CHKiRet(cstrConvSzStrAndDestruct(&pStrB, &pNewVal, 0));
DBGPRINTF("doGetWord: get newval '%s' (len %d), hdlr %p\n",
pNewVal, (int) ustrlen(pNewVal), pSetHdlr);
/* we got the word, now set it */
if(pSetHdlr == NULL) {
/* we should set value directly to var */
if(*((uchar**)pVal) != NULL)
free(*((uchar**)pVal)); /* free previous entry */
*((uchar**)pVal) = pNewVal; /* set new one */
} else {
/* we set value via a set function */
CHKiRet(pSetHdlr(pVal, pNewVal));
}
skipWhiteSpace(pp); /* skip over any whitespace */
finalize_it:
if(iRet != RS_RET_OK) {
if(pStrB != NULL)
cstrDestruct(&pStrB);
}
RETiRet;
}
/* parse a syslog name from the string. This is the generic code that is
* called by the facility/severity functions. Note that we do not check the
* validity of numerical values, something that should probably change over
* time (TODO). -- rgerhards, 2008-02-14
*/
static rsRetVal
doSyslogName(uchar **pp, rsRetVal (*pSetHdlr)(void*, int),
void *pVal, syslogName_t *pNameTable)
{
DEFiRet;
cstr_t *pStrB;
int iNewVal;
assert(pp != NULL);
assert(*pp != NULL);
CHKiRet(getWord(pp, &pStrB)); /* get word */
iNewVal = decodeSyslogName(cstrGetSzStrNoNULL(pStrB), pNameTable);
if(pSetHdlr == NULL) {
/* we should set value directly to var */
*((int*)pVal) = iNewVal; /* set new one */
} else {
/* we set value via a set function */
CHKiRet(pSetHdlr(pVal, iNewVal));
}
skipWhiteSpace(pp); /* skip over any whitespace */
finalize_it:
if(pStrB != NULL)
rsCStrDestruct(&pStrB);
RETiRet;
}
/* Implements the facility syntax.
* rgerhards, 2008-02-14
*/
static rsRetVal
doFacility(uchar **pp, rsRetVal (*pSetHdlr)(void*, int), void *pVal)
{
DEFiRet;
iRet = doSyslogName(pp, pSetHdlr, pVal, syslogFacNames);
RETiRet;
}
static rsRetVal
doGoneAway(__attribute__((unused)) uchar **pp,
__attribute__((unused)) rsRetVal (*pSetHdlr)(void*, int),
__attribute__((unused)) void *pVal)
{
parser_warnmsg("config directive is no longer supported -- ignored");
return RS_RET_CMD_GONE_AWAY;
}
/* Implements the severity syntax.
* rgerhards, 2008-02-14
*/
static rsRetVal
doSeverity(uchar **pp, rsRetVal (*pSetHdlr)(void*, int), void *pVal)
{
DEFiRet;
iRet = doSyslogName(pp, pSetHdlr, pVal, syslogPriNames);
RETiRet;
}
/* --------------- END functions for handling canned syntaxes --------------- */
/* destructor for cslCmdHdlr
* pThis is actually a cslCmdHdlr_t, but we do not cast it as all we currently
* need to do is free it.
*/
static rsRetVal cslchDestruct(void *pThis)
{
assert(pThis != NULL);
free(pThis);
return RS_RET_OK;
}
/* constructor for cslCmdHdlr
*/
static rsRetVal cslchConstruct(cslCmdHdlr_t **ppThis)
{
cslCmdHdlr_t *pThis;
DEFiRet;
assert(ppThis != NULL);
if((pThis = calloc(1, sizeof(cslCmdHdlr_t))) == NULL) {
ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
}
finalize_it:
*ppThis = pThis;
RETiRet;
}
/* destructor for linked list keys. As we do not use any dynamic memory,
* we simply return. However, this entry point must be defined for the
* linkedList class to make sure we have not forgotten a destructor.
* rgerhards, 2007-11-21
*/
static rsRetVal cslchKeyDestruct(void __attribute__((unused)) *pData)
{
return RS_RET_OK;
}
/* Key compare operation for linked list class. This compares two
* owner cookies (void *).
* rgerhards, 2007-11-21
*/
static int cslchKeyCompare(void *pKey1, void *pKey2)
{
if(pKey1 == pKey2)
return 0;
else
if(pKey1 < pKey2)
return -1;
else
return 1;
}
/* set data members for this object
*/
static rsRetVal cslchSetEntry(cslCmdHdlr_t *pThis, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(), void *pData,
int *permitted)
{
assert(pThis != NULL);
assert(eType != eCmdHdlrInvalid);
pThis->eType = eType;
pThis->cslCmdHdlr = pHdlr;
pThis->pData = pData;
pThis->permitted = permitted;
return RS_RET_OK;
}
/* call the specified handler
*/
static rsRetVal cslchCallHdlr(cslCmdHdlr_t *pThis, uchar **ppConfLine)
{
DEFiRet;
rsRetVal (*pHdlr)() = NULL;
assert(pThis != NULL);
assert(ppConfLine != NULL);
switch(pThis->eType) {
case eCmdHdlrCustomHandler:
pHdlr = doCustomHdlr;
break;
case eCmdHdlrUID:
pHdlr = doGetUID;
break;
case eCmdHdlrGID:
pHdlr = doGetGID;
break;
case eCmdHdlrBinary:
pHdlr = doBinaryOptionLine;
break;
case eCmdHdlrFileCreateMode:
pHdlr = doFileCreateMode;
break;
case eCmdHdlrInt:
pHdlr = doGetInt;
break;
case eCmdHdlrSize:
pHdlr = doGetSize;
break;
case eCmdHdlrGetChar:
pHdlr = doGetChar;
break;
case eCmdHdlrFacility:
pHdlr = doFacility;
break;
case eCmdHdlrSeverity:
pHdlr = doSeverity;
break;
case eCmdHdlrGetWord:
pHdlr = doGetWord;
break;
case eCmdHdlrGoneAway:
pHdlr = doGoneAway;
break;
/* some non-legacy handler (used in v6+ solely) */
case eCmdHdlrInvalid:
case eCmdHdlrNonNegInt:
case eCmdHdlrPositiveInt:
case eCmdHdlrString:
case eCmdHdlrArray:
case eCmdHdlrQueueType:
default:
dbgprintf("error: command handler type %d not implemented in legacy system\n", pThis->eType);
iRet = RS_RET_NOT_IMPLEMENTED;
goto finalize_it;
}
/* we got a pointer to the handler, so let's call it */
assert(pHdlr != NULL);
CHKiRet(pHdlr(ppConfLine, pThis->cslCmdHdlr, pThis->pData));
finalize_it:
RETiRet;
}
/* ---------------------------------------------------------------------- *
* now come the handlers for cslCmd_t
* ---------------------------------------------------------------------- */
/* destructor for a cslCmd list key (a string as of now)
*/
static rsRetVal cslcKeyDestruct(void *pData)
{
free(pData); /* we do not need to cast as all we do is free it anyway... */
return RS_RET_OK;
}
/* destructor for cslCmd
*/
static rsRetVal cslcDestruct(void *pData)
{
cslCmd_t *pThis = (cslCmd_t*) pData;
assert(pThis != NULL);
llDestroy(&pThis->llCmdHdlrs);
free(pThis);
return RS_RET_OK;
}
/* constructor for cslCmd
*/
static rsRetVal cslcConstruct(cslCmd_t **ppThis, int bChainingPermitted)
{
cslCmd_t *pThis;
DEFiRet;
assert(ppThis != NULL);
if((pThis = calloc(1, sizeof(cslCmd_t))) == NULL) {
ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
}
pThis->bChainingPermitted = bChainingPermitted;
CHKiRet(llInit(&pThis->llCmdHdlrs, cslchDestruct, cslchKeyDestruct, cslchKeyCompare));
finalize_it:
*ppThis = pThis;
RETiRet;
}
/* add a handler entry to a known command
*/
static rsRetVal cslcAddHdlr(cslCmd_t *pThis, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(), void *pData,
void *pOwnerCookie, int *permitted)
{
DEFiRet;
cslCmdHdlr_t *pCmdHdlr = NULL;
assert(pThis != NULL);
CHKiRet(cslchConstruct(&pCmdHdlr));
CHKiRet(cslchSetEntry(pCmdHdlr, eType, pHdlr, pData, permitted));
CHKiRet(llAppend(&pThis->llCmdHdlrs, pOwnerCookie, pCmdHdlr));
finalize_it:
if(iRet != RS_RET_OK) {
if(pHdlr != NULL)
cslchDestruct(pCmdHdlr);
}
RETiRet;
}
/* function that registers cfsysline handlers.
* The supplied pCmdName is copied and a new buffer is allocated. This
* buffer is automatically destroyed when the element is freed, the
* caller does not need to take care of that. The caller must, however,
* free pCmdName if he allocated it dynamically! -- rgerhards, 2007-08-09
* Parameter permitted has been added to support the v2 config system. With it,
* we can tell the legacy system (us here!) to check if a config directive is
* still permitted. For example, the v2 system will disable module global
* parameters if the are supplied via the native v2 callbacks. In order not
* to break exisiting modules, we have renamed the rgCfSysLinHdlr routine to
* version 2 and added a new one with the original name. It just calls the
* v2 function and supplies a "don't care (NULL)" pointer as this argument.
* rgerhards, 2012-06-26
*/
rsRetVal regCfSysLineHdlr2(const uchar *pCmdName, int bChainingPermitted, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(),
void *pData, void *pOwnerCookie, int *permitted)
{
DEFiRet;
cslCmd_t *pThis;
uchar *pMyCmdName;
iRet = llFind(&llCmdList, (void *) pCmdName, (void*) &pThis);
if(iRet == RS_RET_NOT_FOUND) {
/* new command */
CHKiRet(cslcConstruct(&pThis, bChainingPermitted));
CHKiRet_Hdlr(cslcAddHdlr(pThis, eType, pHdlr, pData, pOwnerCookie, permitted)) {
cslcDestruct(pThis);
FINALIZE;
}
/* important: add to list, AFTER everything else is OK. Else
* we mess up things in the error case.
*/
if((pMyCmdName = (uchar*) strdup((char*)pCmdName)) == NULL) {
cslcDestruct(pThis);
ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
}
CHKiRet_Hdlr(llAppend(&llCmdList, pMyCmdName, (void*) pThis)) {
cslcDestruct(pThis);
FINALIZE;
}
} else {
/* command already exists, are we allowed to chain? */
if(pThis->bChainingPermitted == 0 || bChainingPermitted == 0) {
ABORT_FINALIZE(RS_RET_CHAIN_NOT_PERMITTED);
}
CHKiRet_Hdlr(cslcAddHdlr(pThis, eType, pHdlr, pData, pOwnerCookie, permitted)) {
cslcDestruct(pThis);
FINALIZE;
}
}
finalize_it:
RETiRet;
}
rsRetVal regCfSysLineHdlr(const uchar *pCmdName, int bChainingPermitted, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(),
void *pData, void *pOwnerCookie)
{
DEFiRet;
iRet = regCfSysLineHdlr2(pCmdName, bChainingPermitted, eType, pHdlr, pData, pOwnerCookie, NULL);
RETiRet;
}
rsRetVal unregCfSysLineHdlrs(void)
{
return llDestroy(&llCmdList);
}
/* helper function for unregCfSysLineHdlrs4Owner(). This is used to see if there is
* a handler of this owner inside the element and, if so, remove it. Please note that
* it keeps track of a pointer to the last linked list entry, as this is needed to
* remove an entry from the list.
* rgerhards, 2007-11-21
*/
DEFFUNC_llExecFunc(unregHdlrsHeadExec)
{
DEFiRet;
cslCmd_t *pListHdr = (cslCmd_t*) pData;
int iNumElts;
/* first find element */
CHKiRet(llFindAndDelete(&(pListHdr->llCmdHdlrs), pParam));
/* now go back and check how many elements are left */
CHKiRet(llGetNumElts(&(pListHdr->llCmdHdlrs), &iNumElts));
if(iNumElts == 0) {
/* nothing left in header, so request to delete it */
iRet = RS_RET_OK_DELETE_LISTENTRY;
}
finalize_it:
RETiRet;
}
/* unregister and destroy cfSysLineHandlers for a specific owner. This method is
* most importantly used before unloading a loadable module providing some handlers.
* The full list of handlers is searched. If the to-be removed handler was the only
* handler for a directive name, the directive header, too, is deleted.
* rgerhards, 2007-11-21
*/
rsRetVal unregCfSysLineHdlrs4Owner(void *pOwnerCookie)
{
DEFiRet;
/* we need to walk through all directive names, as the linked list
* class does not provide a way to just search the lower-level handlers.
*/
iRet = llExecFunc(&llCmdList, unregHdlrsHeadExec, pOwnerCookie);
if(iRet == RS_RET_NOT_FOUND) {
/* It is not considered an error if a module had no
hanlers registered. */
iRet = RS_RET_OK;
}
RETiRet;
}
/* process a cfsysline command (based on handler structure)
* param "p" is a pointer to the command line after the command. Should be
* updated.
*/
rsRetVal processCfSysLineCommand(uchar *pCmdName, uchar **p)
{
DEFiRet;
rsRetVal iRetLL; /* for linked list handling */
cslCmd_t *pCmd;
cslCmdHdlr_t *pCmdHdlr;
linkedListCookie_t llCookieCmdHdlr;
uchar *pHdlrP; /* the handler's private p (else we could only call one handler) */
int bWasOnceOK; /* was the result of an handler at least once RS_RET_OK? */
uchar *pOKp = NULL; /* returned conf line pointer when it was OK */
iRet = llFind(&llCmdList, (void *) pCmdName, (void*) &pCmd);
if(iRet == RS_RET_NOT_FOUND) {
LogError(0, RS_RET_NOT_FOUND, "invalid or yet-unknown config file command '%s' - "
"have you forgotten to load a module?", pCmdName);
}
if(iRet != RS_RET_OK)
goto finalize_it;
llCookieCmdHdlr = NULL;
bWasOnceOK = 0;
while((iRetLL = llGetNextElt(&pCmd->llCmdHdlrs, &llCookieCmdHdlr, (void*)&pCmdHdlr)) == RS_RET_OK) {
/* for the time being, we ignore errors during handlers. The
* reason is that handlers are independent. An error in one
* handler does not necessarily mean that another one will
* fail, too. Later, we might add a config variable to control
* this behaviour (but I am not sure if that is really
* necessary). -- rgerhards, 2007-07-31
*/
pHdlrP = *p;
if(pCmdHdlr->permitted != NULL && !*(pCmdHdlr->permitted)) {
LogError(0, RS_RET_PARAM_NOT_PERMITTED, "command '%s' is currently not "
"permitted - did you already set it via a RainerScript command (v6+ config)?",
pCmdName);
ABORT_FINALIZE(RS_RET_PARAM_NOT_PERMITTED);
} else if((iRet = cslchCallHdlr(pCmdHdlr, &pHdlrP)) == RS_RET_OK) {
bWasOnceOK = 1;
pOKp = pHdlrP;
}
}
if(bWasOnceOK == 1) {
*p = pOKp;
iRet = RS_RET_OK;
}
if(iRetLL != RS_RET_END_OF_LINKEDLIST)
iRet = iRetLL;
finalize_it:
RETiRet;
}
/* debug print the command handler structure
*/
void dbgPrintCfSysLineHandlers(void)
{
cslCmd_t *pCmd;
cslCmdHdlr_t *pCmdHdlr;
linkedListCookie_t llCookieCmd;
linkedListCookie_t llCookieCmdHdlr;
uchar *pKey;
dbgprintf("Sytem Line Configuration Commands:\n");
llCookieCmd = NULL;
while(llGetNextElt(&llCmdList, &llCookieCmd, (void*)&pCmd) == RS_RET_OK) {
llGetKey(llCookieCmd, (void*) &pKey); /* TODO: using the cookie is NOT clean! */
dbgprintf("\tCommand '%s':\n", pKey);
llCookieCmdHdlr = NULL;
while(llGetNextElt(&pCmd->llCmdHdlrs, &llCookieCmdHdlr, (void*)&pCmdHdlr) == RS_RET_OK) {
dbgprintf("\t\ttype : %d\n", pCmdHdlr->eType);
dbgprintf("\t\tpData: 0x%lx\n", (unsigned long) pCmdHdlr->pData);
dbgprintf("\t\tHdlr : 0x%lx\n", (unsigned long) pCmdHdlr->cslCmdHdlr);
dbgprintf("\t\tOwner: 0x%lx\n", (unsigned long) llCookieCmdHdlr->pKey);
dbgprintf("\n");
}
}
dbgprintf("\n");
}
/* our init function. TODO: remove once converted to a class
*/
rsRetVal
cfsyslineInit(void)
{
DEFiRet;
CHKiRet(objGetObjInterface(&obj));
CHKiRet(llInit(&llCmdList, cslcDestruct, cslcKeyDestruct, strcasecmp));
finalize_it:
RETiRet;
}
/* vim:set ai:
*/