ossp-pkg/fsl/fsl.c
/*
** OSSP fsl - Faking/Flexible Syslog Library
** Copyright (c) 2002-2005 Ralf S. Engelschall <rse@engelschall.com>
** Copyright (c) 2002-2005 The OSSP Project <http://www.ossp.org/>
** Copyright (c) 2002-2005 Cable & Wireless <http://www.cw.com/>
**
** This file is part of OSSP fsl, a syslog(3) API faking library which
** can be found at http://www.ossp.org/pkg/lib/fsl/.
**
** Permission to use, copy, modify, and distribute this software for
** any purpose with or without fee is hereby granted, provided that
** the above copyright notice and this permission notice appear in all
** copies.
**
** THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 AUTHORS AND COPYRIGHT HOLDERS AND THEIR
** 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.
**
** fsl.c: OSSP fsl library
*/
/* standard includes we use */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
/* standard include we re-implement */
#ifdef __hpux
#define _XOPEN_SOURCE_EXTENDED
#endif
#include <syslog.h>
/* third party (linked in) */
#include "l2.h"
#include "cfg.h"
#include "pcre.h"
/* autoconfiguration */
#include "config.h"
/* use l2_ut_[v]sprintf() as a portable [v]snprintf() replacement for debugging */
#ifdef FSL_DEBUGLOGCODE
#include "l2_ut_format.h"
#endif
/* version */
#define _FSL_VERSION_C_AS_HEADER_
#include "fsl_version.c"
#undef _FSL_VERSION_C_AS_HEADER_
/* pcre static vector size */
#define OVECSIZE 30
/* maximum atomic write size, maximum log message size */
#define MAXWRITESIZE 512
/* backward compatibility */
#ifndef LOG_PRIMASK
#define LOG_PRIMASK (LOG_EMERG|LOG_ALERT|LOG_CRIT|LOG_ERR|\
LOG_WARNING|LOG_NOTICE|LOG_INFO|LOG_DEBUG)
#endif
#ifndef LOG_PRI
#define LOG_PRI(p) ((p) & LOG_PRIMASK)
#endif
/* cleanup sequence macros */
#define STMT(stuff) do { stuff } while (0)
#define CU(returncode) STMT( rc = returncode; goto CUS; )
#define VCU STMT( goto CUS; )
/* general return codes */
typedef enum {
FSL_OK = 0, /* everything ok */
FSL_NOIDENT, /* ok but no "ident" directive found or none matched */
FSL_ERR_ARG, /* invalid argument */
FSL_ERR_USE, /* invalid use */
FSL_ERR_MEM, /* no more memory available */
FSL_ERR_SYS, /* operating system error, see errno */
FSL_ERR_CUS, /* error handled and logged, just run clean up sequence and goodbye */
FSL_ERR_INT /* internal error */
} fsl_rc_t;
/* file buffer handling sub-API */
typedef struct {
char *base;
size_t used;
size_t size;
} buf_t;
/* mapping table for syslog(3) facilities to strings */
static struct {
int facility;
char *string;
} syslogfacility2string[] = {
{ LOG_AUTH, "auth" },
#ifdef LOG_AUTHPRIV
{ LOG_AUTHPRIV, "authpriv" },
#endif
#ifdef LOG_CONSOLE
{ LOG_CONSOLE, "console" },
#endif
{ LOG_CRON, "cron" },
{ LOG_DAEMON, "daemon" },
#ifdef LOG_FTP
{ LOG_FTP, "ftp" },
#endif
{ LOG_KERN, "kern" },
{ LOG_LOCAL0, "local0" },
{ LOG_LOCAL1, "local1" },
{ LOG_LOCAL2, "local2" },
{ LOG_LOCAL3, "local3" },
{ LOG_LOCAL4, "local4" },
{ LOG_LOCAL5, "local5" },
{ LOG_LOCAL6, "local6" },
{ LOG_LOCAL7, "local7" },
{ LOG_LPR, "lpr" },
{ LOG_MAIL, "mail" },
{ LOG_NEWS, "news" },
#ifdef LOG_NTP
{ LOG_NTP, "ntp" },
#endif
#ifdef LOG_SECURITY
{ LOG_SECURITY, "security" },
#endif
{ LOG_SYSLOG, "syslog" },
{ LOG_USER, "user" },
{ LOG_UUCP, "uucp" },
{ -1, NULL }
};
/* mapping table for syslog(3) levels/priorities strings/numbers to L2 levels */
static struct {
char *string;
int level;
l2_level_t deflevelmap;
} sysloglevel2string[] = {
{ "emerg", LOG_EMERG, L2_LEVEL_PANIC },
{ "alert", LOG_ALERT, L2_LEVEL_PANIC },
{ "crit", LOG_CRIT, L2_LEVEL_CRITICAL },
{ "err", LOG_ERR, L2_LEVEL_ERROR },
{ "warning", LOG_WARNING, L2_LEVEL_WARNING },
{ "notice", LOG_NOTICE, L2_LEVEL_NOTICE },
{ "info", LOG_INFO, L2_LEVEL_INFO },
{ "debug", LOG_DEBUG, L2_LEVEL_DEBUG },
{ NULL, -1, -1 }
};
/* mapping table for L2 level strings to L2 level numbers */
static struct {
char *string;
l2_level_t level;
} l2level2string[] = {
{ "none", 0, },
{ "panic", L2_LEVEL_PANIC, },
{ "critical", L2_LEVEL_CRITICAL, },
{ "error", L2_LEVEL_ERROR, },
{ "warning", L2_LEVEL_WARNING, },
{ "notice", L2_LEVEL_NOTICE, },
{ "info", L2_LEVEL_INFO, },
{ "trace", L2_LEVEL_TRACE, },
{ "debug", L2_LEVEL_DEBUG, },
{ NULL, -1 }
};
/* type of run-time syslog(3) to L2 level mapping table */
typedef struct {
int syslog;
l2_level_t l2;
} levelmap_t;
/* internal global context structure */
static struct {
l2_env_t *l2_env;
l2_channel_t *l2_nch;
char *cpISF;
levelmap_t *levelmap;
int maskpri;
int logopt;
int delayopen;
int triedopenlog;
#ifdef FSL_DEBUGLOGCODE
l2_env_t *fsldebug_l2_env;
l2_channel_t *fsldebug_l2_nch;
int fsldebug_transientproblem;
int fsldebug_permanentproblem;
#endif /* ifdef FSL_DEBUGLOGCODE */
char *fsl_config;
} ctx = {
NULL,
NULL,
NULL,
NULL,
LOG_UPTO(LOG_DEBUG),
0,
TRUE,
FALSE,
#ifdef FSL_DEBUGLOGCODE
NULL,
NULL,
FALSE,
FALSE,
#endif /* ifdef FSL_DEBUGLOGCODE */
"@(#)OSSP fsl config "
"cfgdir=" FSL_CFGDIR " "
"prefix=" FSL_PREFIX
"\n"
"@(#)OSSP fsl debug "
#ifdef FSL_DEBUGLOGCODE
"logfile=" FSL_DEBUGLOGFILE " "
"logmask=" FSL_DEBUGLOGMASK
#else /* ifdef FSL_DEBUGLOGCODE */
"code omitted"
#endif /* ifdef FSL_DEBUGLOGCODE */
"\n"
};
#ifdef FSL_DEBUGLOGCODE
static void vfsldebug(l2_level_t level, const char *message, va_list ap)
{
/* sizing cp temporary buffer is max out of
strlen "(panic,critical,error,warning,notice,info,trace,debug)" + terminating NUL and
strlen "bbb dd HH:MM:SS " + terminating NUL
= 56
*/
static char cp[56];
static char cpo[MAXWRITESIZE];
int cpn;
int n;
struct stat sb;
unsigned int levelmask;
time_t t;
struct tm *tm;
int fd = 0;
/* determine level mask */
if ((n = readlink(FSL_DEBUGLOGMASK, cp, sizeof(cp))) == -1)
VCU;
if (cp[0] == '(' && cp[n-1] == ')') { /* level mask in round brackets like l2spec */
if(l2_util_s2l(&cp[1], n-2, ',', &levelmask) != L2_OK)
VCU;
}
else if (strchr(cp, ',') != NULL) { /* level mask automatic because comma detected */
if(l2_util_s2l(&cp[0], n , ',', &levelmask) != L2_OK)
VCU;
}
else { /* upto level like l2spec */
if(l2_util_s2l(&cp[0], n , 0 , &levelmask) != L2_OK)
VCU;
levelmask = L2_LEVEL_UPTO(levelmask);
}
if (level & levelmask) {
cpn = 0;
/* date and time */
t = time(NULL);
tm = localtime(&t);
if ((n = strftime(cp, sizeof(cp), "%b %d %H:%M:%S ", tm)) == 0)
VCU;
l2_util_sprintf( &cpo[cpn], MAXWRITESIZE-cpn, "%s", cp);
cpn = strlen(cpo);
/* level */
l2_util_sprintf( &cpo[cpn], MAXWRITESIZE-cpn, "<%s> ", l2_util_l2s(cp, sizeof(cp), '\0', level) == L2_OK ? cp : "unknown");
cpn = strlen(cpo);
/* program "name" and pid */
l2_util_sprintf( &cpo[cpn], MAXWRITESIZE-cpn, "%s[%lu^%lu] ", ctx.cpISF != NULL ? ctx.cpISF : "unknown", (unsigned long)getpid(), (unsigned long)getppid());
cpn = strlen(cpo);
/* message */
l2_util_vsprintf(&cpo[cpn], MAXWRITESIZE-cpn, message, ap);
cpn = strlen(cpo);
l2_util_sprintf( &cpo[cpn], MAXWRITESIZE-cpn, "\n");
cpn = strlen(cpo);
/* write the log */
if (cpn >= 1) {
if ((fd = open(FSL_DEBUGLOGFILE, O_WRONLY|O_CREAT|O_APPEND|O_NONBLOCK, 0644)) == -1)
VCU;
if (fstat(fd, &sb) == -1)
VCU;
if (sb.st_size >= FSL_DEBUGLOGSTOP)
VCU;
write(fd, cpo, cpn);
}
}
CUS:
if (fd)
(void)close(fd);
}
static void fsldebug(l2_level_t level, const char *message, ...)
{
va_list ap;
va_start(ap, message);
vfsldebug(level, message, ap);
va_end(ap);
return;
}
#else /* ifdef FSL_DEBUGLOGCODE */
static void fsldebug(l2_level_t level, const char *message, ...)
{
return;
}
#endif /* ifdef FSL_DEBUGLOGCODE */
/* append contents of a file to buffer */
static fsl_rc_t appendfiletobuffer(buf_t *buffer, const char *filename)
{
fsl_rc_t rc;
int fd = -1;
int filesize;
int fileread;
int bufferneed;
if (filename == NULL || buffer == NULL)
CU(FSL_ERR_ARG);
fsldebug(L2_LEVEL_TRACE, "appendfiletobuffer() filename=\"%s\"", filename);
if ((fd = open(filename, O_RDONLY)) == -1)
CU(FSL_ERR_SYS);
if ((filesize = (int)lseek(fd, 0, SEEK_END)) == -1)
CU(FSL_ERR_SYS);
bufferneed = buffer->used + filesize;
if (bufferneed > buffer->size) {
if (buffer->base == NULL) {
if ((buffer->base = (char *)malloc(bufferneed)) == NULL)
CU(FSL_ERR_MEM);
buffer->size = bufferneed;
buffer->used = 0;
}
else {
if ((buffer->base = (char *)realloc(buffer->base, bufferneed)) == NULL)
CU(FSL_ERR_MEM);
buffer->size = bufferneed;
}
}
if (lseek(fd, 0, SEEK_SET) == -1)
CU(FSL_ERR_SYS);
if ((fileread = (int)read(fd, buffer->base + buffer->used, (size_t)filesize)) == -1)
CU(FSL_ERR_SYS);
if (fileread != filesize)
CU(FSL_ERR_USE);
buffer->used += filesize;
CU(FSL_OK);
CUS:
if (fd != -1)
close(fd);
return rc;
}
/* alphabetically compare two filenames, use with qsort(3) */
static int fnamecmp(const void *str1, const void *str2)
{
const char *s1 = *(const char **)str1;
const char *s2 = *(const char **)str2;
if ((s1 != NULL) && (s2 != NULL))
return strcmp(s1, s2);
/* this must never happen but be prepared for the impossible */
if ((s1 != NULL) && (s2 == NULL))
return strcmp(s1, "");
if ((s1 == NULL) && (s2 != NULL))
return strcmp("", s2);
return strcmp("", "");
}
/* read all possible files "fsl.*" into buffer */
static fsl_rc_t readallfiles(buf_t *buffer)
{
fsl_rc_t rc;
DIR *dp = NULL;
struct dirent *de;
char *cfgdir;
char *prefix;
char **filearr = NULL;
size_t filemem = 0;
int filecnt = 0;
int fileidx = 0;
int n = 0;
if (buffer == NULL)
CU(FSL_ERR_ARG);
cfgdir = FSL_CFGDIR;
prefix = FSL_PREFIX;
fsldebug(L2_LEVEL_TRACE, "readallfiles() globbing \"%s/%s*\"", cfgdir, prefix);
if ((dp = opendir(cfgdir)) == NULL)
CU(FSL_ERR_SYS);
rc = FSL_ERR_ARG;
while ((de = readdir(dp)) != NULL) {
if ( (strlen(de->d_name) >= strlen(prefix))
&& (strncmp(de->d_name, prefix, strlen(prefix)) == 0)) {
/* prepare to insert a new file string, so make room for one more */
if ((filearr = (char **)realloc(filearr, filemem + sizeof(char *))) == NULL)
CU(FSL_ERR_MEM);
else
filemem += sizeof(char *);
/* allocate for actual filename string from dirent and copy out */
n = strlen(cfgdir) + strlen("/") + strlen(de->d_name) + 1;
if ((filearr[filecnt] = (char *)malloc(n)) == NULL)
CU(FSL_ERR_MEM);
*filearr[filecnt] = '\0';
strcat(filearr[filecnt], cfgdir);
strcat(filearr[filecnt], "/");
strcat(filearr[filecnt], de->d_name);
filecnt++;
}
}
qsort((void *)filearr, (size_t)filecnt, sizeof(char *), fnamecmp);
for (fileidx = 0; fileidx < filecnt; fileidx++)
if (appendfiletobuffer(buffer, filearr[fileidx]) == FSL_OK)
rc = FSL_OK;
CU(rc);
CUS:
if (dp != NULL)
closedir(dp);
if (filearr != NULL) {
for (fileidx = 0; fileidx < filecnt; fileidx++)
free(filearr[fileidx]);
free(filearr);
}
return rc;
}
/* OSSP cfg node tree traversal function (recursive) */
static void traverse(cfg_t *cfg, cfg_node_t *cfgnode)
{
int rc;
cfg_node_t *cfgchld;
cfg_rc_t cfgrv;
cfg_node_type_t cfgtyp;
char *cp;
int cfgchilds;
while (cfgnode != NULL) {
if ((cfgrv = cfg_node_get(cfg, cfgnode, CFG_NODE_ATTR_TOKEN, &cp)) != CFG_OK) {
(void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "traverse: cfg_node_get(CFG_NODE_ATTR_TOKEN) failed: %s (%d)", cp, cfgrv); CU(1); }
if ((cfgrv = cfg_node_get(cfg, cfgnode, CFG_NODE_ATTR_TYPE, &cfgtyp)) != CFG_OK) {
(void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "traverse: cfg_node_get(CFG_NODE_ATTR_TYPE) failed: %s (%d)", cp, cfgrv); CU(1); }
if ((cfgrv = cfg_node_get(cfg, cfgnode, CFG_NODE_ATTR_CHILDS, &cfgchilds)) != CFG_OK) {
(void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "traverse: cfg_node_get(CFG_NODE_ATTR_CHILDS) failed: %s (%d)", cp, cfgrv); CU(1); }
fsldebug(L2_LEVEL_DEBUG, "traverse: cfgnode=0x%.8lx[%d], cp=\"%s\", type=%d", (unsigned long)cfgnode, cfgchilds, (cp != NULL ? cp : "<NULL>"), cfgtyp);
if ((cfgrv = cfg_node_get(cfg, cfgnode, CFG_NODE_ATTR_CHILD1, &cfgchld)) != CFG_OK) {
(void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "traverse: cfg_node_get(CFG_NODE_ATTR_CHILD1) failed: %s (%d)", cp, cfgrv); CU(1); }
if (cfgchld != NULL)
traverse(cfg, cfgchld);
if ((cfgrv = cfg_node_get(cfg, cfgnode, CFG_NODE_ATTR_RBROTH, &cfgnode)) != CFG_OK) {
(void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "traverse: cfg_node_get(CFG_NODE_ATTR_RBROTH) failed: %s (%d)", cp, cfgrv); CU(1); }
}
CU(0);
CUS:
return;
}
/* Substitute $[0-9] in inbuf[inlen] with captured strings passed through capary[nary].
* Returns the number of characters (to be) written to output. Output can be suppressed
* by passing a NULL outpbuf. The terminating NUL is not counted but will be written!
*/
static int substcapture(const char *inbuf, int inlen, const char **capary, int nary, char *outbuf)
{
int i; /* input cursor, current position */
int n; /* outbuf cursor, next possible write position */
int m; /* index to capary */
char c; /* scratch variable */
i = 0;
n = 0;
while (i < inlen - 1 /* last char cannot start a two-character sequence, skipping it allows safe look ahead */) {
c = inbuf[i];
if (c == '$') {
c = inbuf[i+1];
if (c == '$') {
if (outbuf != NULL)
outbuf[n] = '$';
i += 2;
n++;
}
else if ((c >= '0') && (c <= '9')) {
m = c - '0';
if (outbuf != NULL) {
outbuf[n] = '\0';
strcat(&outbuf[n], ((m < nary) && (capary[m] != NULL)) ? capary[m] : "");
}
i += 2;
n += ((m < nary) && (capary[m] != NULL)) ? strlen(capary[m]) : 0;
}
else {
if (outbuf != NULL)
outbuf[n] = '$';
i++;
n++;
if (outbuf != NULL)
outbuf[n] = c;
i++;
n++;
}
}
else {
if (outbuf != NULL)
outbuf[n] = c;
i++;
n++;
}
}
if (outbuf != NULL)
outbuf[n] = '\0';
return n;
}
/* process OSSP cfg node tree directives
mode=0 processes map/ident,
mode=1 processes default
*/
static fsl_rc_t processcfg(cfg_t *cfg, char *cpISF, int mode)
{
fsl_rc_t rc;
cfg_rc_t cfgrv;
cfg_node_t *cfgseq;
cfg_node_t *cfgdir;
cfg_node_t *cfgarg;
cfg_node_type_t cfgtype;
int cfgnumc;
int cfgnume;
char *cfgargtoka[3];
char *cfgargtok;
char *argident;
char *argmatch;
char *argl2spec;
char *cp; /* scratch variable */
int i; /* scratch variable */
int n; /* scratch variable */
int ovec[OVECSIZE];
const char **acpMatch;
l2_channel_t *ch; /* scratch variable */
l2_result_t l2rv;
int matchcount = 0;
pcre *pcreRegex = NULL;
const char *cpError;
int iError;
int nMatch;
if ((cfg == NULL) || (cpISF == NULL) || (strlen(cpISF) < 3) || (mode < 0) || (mode > 1))
return FSL_ERR_ARG;
fsldebug(L2_LEVEL_TRACE, "processcfg() ident/facility=\"%s\", mode=%s", cpISF, mode == 0 ? "map/ident" : "default");
/* find configuration root node and check if it is a sequence and has one or more directives below it */
if ((cfgrv = cfg_node_root(cfg, NULL, &cfgseq)) != CFG_OK) {
(void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_root() failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }
/* only dump once */
if (mode == 0)
traverse(cfg, cfgseq);
if ((cfgrv = cfg_node_get(cfg, cfgseq, CFG_NODE_ATTR_TYPE, &cfgtype)) != CFG_OK) {
(void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_get(CFG_NODE_ATTR_TYPE) failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }
if (cfgtype != CFG_NODE_TYPE_SEQ) {
fsldebug(L2_LEVEL_ERROR, "processcfg: expected sequence"); CU(FSL_ERR_CUS); }
if ((cfgrv = cfg_node_get(cfg, cfgseq, CFG_NODE_ATTR_CHILDS, &cfgnumc)) != CFG_OK) {
(void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_get(CFG_NODE_ATTR_CHILDS) failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }
if (cfgnumc < 1) {
fsldebug(L2_LEVEL_ERROR, "processcfg: sequence is missing directives, expected 1, got %d", cfgnumc); CU(FSL_ERR_CUS); }
/* get first directive below sequence */
if ((cfgrv = cfg_node_get(cfg, cfgseq, CFG_NODE_ATTR_CHILD1, &cfgdir)) != CFG_OK) {
(void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_get(CFG_NODE_ATTR_CHILD1) failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }
while (cfgdir != NULL) {
/* check if operating on a directive */
if ((cfgrv = cfg_node_get(cfg, cfgdir, CFG_NODE_ATTR_TYPE, &cfgtype)) != CFG_OK) {
(void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_get(CFG_NODE_ATTR_TYPE) failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }
if (cfgtype != CFG_NODE_TYPE_DIR) {
fsldebug(L2_LEVEL_ERROR, "processcfg: expected directive"); CU(FSL_ERR_CUS); }
if ((cfgrv = cfg_node_get(cfg, cfgdir, CFG_NODE_ATTR_CHILDS, &cfgnumc)) != CFG_OK) {
(void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_get(CFG_NODE_ATTR_CHILDS) failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }
/* process first child of directive, check if it is an argument and has a valid token attached */
if ((cfgrv = cfg_node_get(cfg, cfgdir, CFG_NODE_ATTR_CHILD1, &cfgarg)) != CFG_OK) {
(void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_get(CFG_NODE_ATTR_CHILD1) failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }
if (cfgarg == NULL) {
fsldebug(L2_LEVEL_ERROR, "processcfg: first argument is NULL"); CU(FSL_ERR_CUS); }
if ((cfgrv = cfg_node_get(cfg, cfgarg, CFG_NODE_ATTR_TYPE, &cfgtype)) != CFG_OK) {
(void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_get(CFG_NODE_ATTR_TYPE) failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }
if (cfgtype != CFG_NODE_TYPE_ARG) {
fsldebug(L2_LEVEL_ERROR, "processcfg: expected first argument, got %d", cfgtype); CU(FSL_ERR_CUS); }
if ((cfgrv = cfg_node_get(cfg, cfgarg, CFG_NODE_ATTR_TOKEN, &cfgargtok)) != CFG_OK) {
(void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_get(CFG_NODE_ATTR_TOKEN) failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }
if (cfgargtok == NULL) {
fsldebug(L2_LEVEL_ERROR, "processcfg: first argument has NULL data"); CU(FSL_ERR_CUS); }
cfgargtoka[0] = cfgargtok;
cfgnume = 0;
if (strcmp(cfgargtoka[0], "ident") == 0)
cfgnume = 3;
if (strcmp(cfgargtoka[0], "default") == 0)
cfgnume = 3;
if (strcmp(cfgargtoka[0], "map") == 0)
cfgnume = 3;
if (cfgnume == 0) {
fsldebug(L2_LEVEL_ERROR, "processcfg: syntax error, invalid argument \"%s\"", cfgargtoka[0]); CU(FSL_ERR_CUS); }
if (cfgnume != cfgnumc) {
fsldebug(L2_LEVEL_ERROR, "processcfg: invalid number of arguments for directive, expected %d, got %d", cfgnume, cfgnumc); CU(FSL_ERR_CUS); }
for (i = 1; i < cfgnume; i++) {
/* process right brother of argument, check if it is an argument and has a valid token attached */
if ((cfgrv = cfg_node_get(cfg, cfgarg, CFG_NODE_ATTR_RBROTH, &cfgarg)) != CFG_OK) {
(void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_get(CFG_NODE_ATTR_RBROTH) failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }
if (cfgarg == NULL) {
fsldebug(L2_LEVEL_ERROR, "processcfg: argument %d is NULL", i); CU(FSL_ERR_CUS); }
if ((cfgrv = cfg_node_get(cfg, cfgarg, CFG_NODE_ATTR_TYPE, &cfgtype)) != CFG_OK) {
(void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_get(CFG_NODE_ATTR_TYPE) failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }
if (cfgtype != CFG_NODE_TYPE_ARG) {
fsldebug(L2_LEVEL_ERROR, "processcfg: expected argument %d", i); CU(FSL_ERR_CUS); }
if ((cfgrv = cfg_node_get(cfg, cfgarg, CFG_NODE_ATTR_TOKEN, &cfgargtok)) != CFG_OK) {
(void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_get(CFG_NODE_ATTR_TOKEN) failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }
if (cfgargtok == NULL) {
fsldebug(L2_LEVEL_ERROR, "processcfg: argument %d has NULL data", i); CU(FSL_ERR_CUS); }
cfgargtoka[i] = cfgargtok;
}
if ((strcmp(cfgargtoka[0], "ident") == 0) || (strcmp(cfgargtoka[0], "default") == 0)) {
argident = cfgargtoka[0];
argmatch = cfgargtoka[1];
argl2spec = cfgargtoka[2];
if ( ((mode == 0) && (strcmp(cfgargtoka[0], "ident") == 0))
|| ((mode == 1) && (strcmp(cfgargtoka[0], "default") == 0))
) {
/* process the directive using the three arguments found */
fsldebug(L2_LEVEL_DEBUG, "processcfg: argident=%s, argmatch=%s, argl2spec=%s", argident, argmatch, argl2spec);
/* compile regular expression into finite state machine and optimize */
if ((pcreRegex = pcre_compile(argmatch, PCRE_ANCHORED|PCRE_CASELESS, &cpError, &iError, NULL)) == NULL) {
fsldebug(L2_LEVEL_ERROR, "processcfg: pcre_compile() failed with error %s (%d)", cpError, iError); CU(FSL_ERR_CUS); }
nMatch = pcre_exec(pcreRegex, NULL, cpISF, strlen(cpISF), 0, 0, ovec, OVECSIZE);
if (nMatch < 0)
fsldebug(L2_LEVEL_TRACE, "processcfg: matching ident/facility \"%s\" against section \"%s\" failed.", cpISF, argmatch);
else
if (nMatch == 0)
fsldebug(L2_LEVEL_TRACE, "processcfg: matching ident/facility \"%s\" against section \"%s\" succeeded, found $0", cpISF, argmatch);
else
fsldebug(L2_LEVEL_TRACE, "processcfg: matching ident/facility \"%s\" against section \"%s\" succeeded, found $0...$%d", cpISF, argmatch, (nMatch-1) > 9 ? 9 : (nMatch-1));
if (nMatch >= 1) {
pcre_get_substring_list(cpISF, ovec, nMatch, &acpMatch);
if (acpMatch != NULL)
for (i = 0; i < nMatch; i++)
fsldebug(L2_LEVEL_DEBUG, "processcfg: regex reference[%d]=\'%s\'", i, acpMatch[i] == NULL ? "(UNDEFINED)" : acpMatch[i]);
n = substcapture(argl2spec, strlen(argl2spec), acpMatch, nMatch, NULL);
if ((cp = (char *)malloc(n + 1)) == NULL) {
fsldebug(L2_LEVEL_ERROR, "processcfg: malloc() failed"); CU(FSL_ERR_CUS); }
if (substcapture(argl2spec, strlen(argl2spec), acpMatch, nMatch, cp) != n) {
fsldebug(L2_LEVEL_ERROR, "processcfg: substcapture() failed"); CU(FSL_ERR_CUS); }
argl2spec = cp;
fsldebug(L2_LEVEL_DEBUG, "processcfg: argident=%s, argmatch=%s, argl2spec=%s", argident, argmatch, argl2spec);
/* create L2 channel throuh spec and link into root channel */
if ((l2rv = l2_spec(&ch, ctx.l2_env, "%s", argl2spec)) != L2_OK) {
cp = l2_env_strerror(ctx.l2_env, l2rv); fsldebug(L2_LEVEL_ERROR, "processcfg: logging failed to create stream from spec %s(%d)", cp, l2rv); CU(FSL_ERR_CUS); }
if ((l2rv = l2_channel_link(ctx.l2_nch, L2_LINK_CHILD, ch, NULL)) != L2_OK) {
cp = l2_env_strerror(ctx.l2_env, l2rv); fsldebug(L2_LEVEL_ERROR, "processcfg: logging failed to link child channel %s(%d)", cp, l2rv); CU(FSL_ERR_CUS); }
matchcount++;
free(argl2spec);
}
if (pcreRegex != NULL) {
pcre_free(pcreRegex);
pcreRegex = NULL;
}
}
else
fsldebug(L2_LEVEL_DEBUG, "processcfg: ignoring %s in mode %d", cfgargtoka[0], mode);
}
else if (strcmp(cfgargtoka[0], "map") == 0) {
if (mode == 0) {
int mapfrom;
int mapto;
for (i = 0, mapfrom = -1; (mapfrom == -1) && (sysloglevel2string[i].string != NULL); i++) {
if (strcmp(sysloglevel2string[i].string, cfgargtoka[1]) == 0)
mapfrom = i;
}
if (mapfrom == -1) {
fsldebug(L2_LEVEL_ERROR, "processcfg: trying to map from unknown syslog level \"%s\"", cfgargtoka[1]); CU(FSL_ERR_CUS); }
for (i = 0, mapto = -1; (mapto == -1) && (l2level2string[i].string != NULL); i++) {
if (strcmp(l2level2string[i].string, cfgargtoka[2]) == 0)
mapto = i;
}
if (mapto == -1) {
fsldebug(L2_LEVEL_ERROR, "processcfg: trying to map to unknown l2 level \"%s\"", cfgargtoka[2]); CU(FSL_ERR_CUS); }
ctx.levelmap[mapfrom].l2 = l2level2string[mapto].level;
fsldebug(L2_LEVEL_DEBUG, "processcfg: map levelmap[%10s/%d].l2 = l2level2string[%10s/%d].level = 0x%.8lx", cfgargtoka[1], mapfrom, cfgargtoka[2], mapto, (unsigned long)l2level2string[mapto].level);
}
else
fsldebug(L2_LEVEL_DEBUG, "processcfg: ignoring %s in mode %d", cfgargtoka[0], mode);
}
else {
fsldebug(L2_LEVEL_ERROR, "processcfg: internal, argument \"%s\" not implemented", cfgargtoka[0]); CU(FSL_ERR_CUS); }
/* get right brother of current directive */
if ((cfgrv = cfg_node_get(cfg, cfgdir, CFG_NODE_ATTR_RBROTH, &cfgdir)) != CFG_OK) {
(void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "processcfg: cfg_node_get(CFG_NODE_ATTR_RBROTH) failed with error %s (%d)", cp, cfgrv); CU(FSL_ERR_CUS); }
}
fsldebug(L2_LEVEL_TRACE, "processcfg: matched %d sections while looking for %s sections", matchcount, mode == 0 ? "ident" : "default");
if (matchcount == 0)
CU(FSL_NOIDENT);
else
CU(FSL_OK);
CUS:
if (pcreRegex != NULL) {
pcre_free(pcreRegex);
pcreRegex = NULL;
}
return rc;
}
/* POSIX API function openlog(3) */
void openlog(const char *ident, int logopt, int facility)
{
int rc;
buf_t buf;
fsl_rc_t rv;
cfg_t *cfg;
cfg_rc_t cfgrv;
char *cp; /* scratch variable */
int i; /* scratch variable */
char *cpIdent;
char *cpFacility;
l2_result_t l2rv;
/* initialization */
cfg = NULL;
buf.base = NULL;
buf.used = 0;
buf.size = 0;
cpIdent = NULL;
/* properly prepare for repeated execution */
closelog();
ctx.triedopenlog = TRUE;
/* remember logopt */
ctx.logopt = logopt;
/* handle delayed open logopt value */
if (ctx.logopt & LOG_NDELAY)
ctx.delayopen = FALSE;
/* tracing */
fsldebug(L2_LEVEL_TRACE, "fsl in openlog(3) ident=%s%s%s, logopt=0x%.8lx, facility=0x%.8lx; caught by %s", ident == NULL ? "" : "\"", ident == NULL ? "NULL" : ident, ident == NULL ? "" : "\"" , logopt, facility, fsl_version.v_gnu);
/* handle unsupported logopt values */
if (ctx.logopt & LOG_CONS)
fsldebug(L2_LEVEL_WARNING, "openlog: ignore unsupported LOG_CONS");
#ifdef LOG_PERROR
if (ctx.logopt & LOG_PERROR)
fsldebug(L2_LEVEL_WARNING, "openlog: ignore unsupported LOG_PERROR (use OSSP l2 channel \"fd(filedescriptor=2)\" to emulate)");
#endif
if (ctx.logopt & LOG_PID)
fsldebug(L2_LEVEL_WARNING, "openlog: ignore unsupported LOG_PID (use OSSP l2 formatter %%P in prefix channel to emulate)");
/* create default sysloglevel to l2_level mapping */
fsldebug(L2_LEVEL_DEBUG, "openlog: create default syslog(3) to OSSP l2 level/priority mapping table");
for (i = 0; sysloglevel2string[i].string != NULL; i++)
;
if ((ctx.levelmap = (levelmap_t *)malloc(i * sizeof(levelmap_t))) == NULL) {
fsldebug(L2_LEVEL_ERROR, "openlog: malloc() failed"); CU(1); }
for (i = 0; sysloglevel2string[i].string != NULL; i++) {
ctx.levelmap[i].syslog = sysloglevel2string[i].level;
ctx.levelmap[i].l2 = sysloglevel2string[i].deflevelmap;
fsldebug(L2_LEVEL_DEBUG, "openlog: ctx.levelmap[%d].syslog = 0x%.8lx, ctx.levelmap[%d].l2 = 0x%.8lx",
i, (unsigned long)ctx.levelmap[i].syslog, i, (unsigned long)ctx.levelmap[i].l2);
}
/* create OSSP l2 environment for main application */
if ((l2rv = l2_env_create(&ctx.l2_env)) != L2_OK) {
cp = l2_env_strerror(ctx.l2_env, l2rv); fsldebug(L2_LEVEL_ERROR, "openlog: failed to create OSSP l2 environment: (%d)", l2rv); CU(1); }
if ((l2rv = l2_env_levels(ctx.l2_env, L2_LEVEL_ALL, L2_LEVEL_NONE)) != L2_OK) {
cp = l2_env_strerror(ctx.l2_env, l2rv); fsldebug(L2_LEVEL_ERROR, "openlog: logging failed to set global logging level defaults: %s(%d)", cp, l2rv); CU(1); }
if ((l2rv = l2_env_formatter(ctx.l2_env, 'D', l2_util_fmt_dump, NULL)) != L2_OK) {
cp = l2_env_strerror(ctx.l2_env, l2rv); fsldebug(L2_LEVEL_ERROR, "openlog: logging failed to register dump formatter: %s(%d)", cp, l2rv); CU(1); }
if ((l2rv = l2_env_formatter(ctx.l2_env, 'S', l2_util_fmt_string, NULL)) != L2_OK) {
cp = l2_env_strerror(ctx.l2_env, l2rv); fsldebug(L2_LEVEL_ERROR, "openlog: logging failed to register string formatter: %s(%d)", cp, l2rv); CU(1); }
if ((l2rv = l2_env_formatter(ctx.l2_env, 'm', l2_util_fmt_errno, NULL)) != L2_OK) {
cp = l2_env_strerror(ctx.l2_env, l2rv); fsldebug(L2_LEVEL_ERROR, "openlog: logging failed to register errno formatter: %s(%d)", cp, l2rv); CU(1); }
if ((l2rv = l2_channel_create(&ctx.l2_nch, ctx.l2_env, "noop")) != L2_OK) {
cp = l2_env_strerror(ctx.l2_env, l2rv); fsldebug(L2_LEVEL_ERROR, "openlog: logging failed to create noop channel: %s(%d)", cp, l2rv); CU(1); }
/* create IdentSlashFacility */
if ((cpIdent = strdup((ident != NULL) ? ident : "unknown")) == NULL) {
fsldebug(L2_LEVEL_ERROR, "openlog: strdup() failed"); CU(1); }
cpFacility = "unknown";
for (i = 0; syslogfacility2string[i].string != NULL; i++) {
if (facility == syslogfacility2string[i].facility) {
cpFacility = syslogfacility2string[i].string;
break;
}
}
if ((ctx.cpISF = (char *)malloc(strlen(cpIdent) + 1 + strlen(cpFacility) + 1)) == NULL) {
fsldebug(L2_LEVEL_ERROR, "openlog: malloc() failed"); CU(1); }
ctx.cpISF[0] = '\0';
strcat(ctx.cpISF, cpIdent);
strcat(ctx.cpISF, "/");
strcat(ctx.cpISF, cpFacility);
/* read configuration file(s) into buffer */
if ((rv = readallfiles(&buf)) != FSL_OK) {
fsldebug(L2_LEVEL_ERROR, "openlog: readallfiles() failed. Hint: last system error was \"%s\"(%d)", strerror(errno), errno); CU(1); }
/* import configuration buffer into OSSP cfg node tree */
if ((cfgrv = cfg_create(&cfg)) != CFG_OK) {
(void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "openlog: cfg_create() failed with error %s (%d)", cp, cfgrv); CU(1); }
if ((cfgrv = cfg_import(cfg, NULL, CFG_FMT_CFG, buf.base, buf.size)) != CFG_OK) {
(void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "openlog: cfg_import() failed with error %s (%d)", cp, cfgrv); CU(1); }
/* process OSSP cfg node tree "map" and "ident" directives */
if ((rv = processcfg(cfg, ctx.cpISF, 0)) != FSL_OK && (rv != FSL_NOIDENT)) {
fsldebug(L2_LEVEL_ERROR, "openlog: processcfg() failed with an unrecoverable error (%d)", rv); CU(1); }
/* optionally process OSSP cfg node tree "default" directives */
if ((rv == FSL_NOIDENT) && ((rv = processcfg(cfg, ctx.cpISF, 1)) != FSL_OK)) {
fsldebug(L2_LEVEL_ERROR, "openlog: processcfg() failed with an unrecoverable error (%d)", rv); CU(1); }
/* open logging now or prepare for delayed open */
if (~logopt & LOG_NDELAY) {
fsldebug(L2_LEVEL_TRACE, "openlog: logopt LOG_NDELAY delays open of L2 channel tree until first message is being logged");
}
else {
if ((l2rv = l2_channel_open(ctx.l2_nch)) != L2_OK) {
cp = l2_env_strerror(ctx.l2_env, l2rv); fsldebug(L2_LEVEL_ERROR, "openlog: logging failed to open channel stream %s(%d) immediately", cp, l2rv); CU(1); }
fsldebug(L2_LEVEL_TRACE, "openlog: logging succeeded to open channel stream immediately");
}
CU(0);
CUS:
if (ctx.cpISF != NULL) {
free(ctx.cpISF);
ctx.cpISF = NULL;
}
if (cpIdent != NULL)
free(cpIdent);
if (cfg != NULL)
if ((cfgrv = cfg_destroy(cfg)) != CFG_OK) {
(void)cfg_error(cfg, cfgrv, &cp); fsldebug(L2_LEVEL_ERROR, "openlog: cfg_destroy() failed with error %s (%d)", cp, cfgrv); CU(1); }
if (buf.base != NULL)
free(buf.base);
if (rc != 0)
closelog();
return;
}
/* faked POSIX API function closelog(3) */
void closelog(void)
{
/* tracing */
fsldebug(L2_LEVEL_TRACE, "fsl in closelog(3)");
if (ctx.l2_nch != NULL) {
l2_channel_destroy(ctx.l2_nch);
ctx.l2_nch = NULL;
}
if (ctx.l2_env != NULL) {
l2_env_destroy(ctx.l2_env);
ctx.l2_env = NULL;
}
if (ctx.levelmap != NULL) {
free(ctx.levelmap);
ctx.levelmap = NULL;
}
if (ctx.cpISF != NULL) {
free(ctx.cpISF);
ctx.cpISF = NULL;
}
ctx.maskpri = LOG_UPTO(LOG_DEBUG);
ctx.logopt = 0;
ctx.delayopen = TRUE;
ctx.triedopenlog = FALSE;
return;
}
/* faked POSIX API function setlogmask(3) */
int setlogmask(int maskpri)
{
int oldmask;
/* tracing */
fsldebug(L2_LEVEL_TRACE, "fsl in setlogmask(3) maskpri=0x%.8lx", maskpri);
oldmask = ctx.maskpri;
if (maskpri != 0)
ctx.maskpri = maskpri;
return oldmask;
}
/* faked POSIX API function vsyslog(3) */
#ifdef HAVE_VSYSLOG_USVALIST
void vsyslog(int priority, const char *fmt, __va_list args)
#else
void vsyslog(int priority, const char *fmt, va_list args)
#endif
{
unsigned int levelmask;
int i;
l2_result_t l2rv;
char *cp;
/* tracing */
fsldebug(L2_LEVEL_TRACE, "fsl in vsyslog(3) fmt=%s%s%s ...", fmt == NULL ? "" : "\"", fmt == NULL ? "NULL" : fmt, fmt == NULL ? "" : "\"");
/* check for omitted openlog(3) */
if (ctx.l2_nch == NULL && !ctx.triedopenlog)
openlog("fsl", 0, LOG_SYSLOG);
/* check for previous proper initialization */
if (ctx.l2_nch == NULL)
return;
/* check for delayed open */
if ((~ctx.logopt & LOG_NDELAY) && (ctx.delayopen == TRUE)) {
if ((l2rv = l2_channel_open(ctx.l2_nch)) != L2_OK) {
cp = l2_env_strerror(ctx.l2_env, l2rv); fsldebug(L2_LEVEL_ERROR, "vsyslog: logging failed to open channel stream %s(%d) delayed", cp, l2rv);
closelog();
return;
}
fsldebug(L2_LEVEL_TRACE, "vsyslog: logging succeeded to open channel stream delayed");
ctx.delayopen = FALSE;
}
/* strip off facility */
priority &= LOG_PRIMASK;
fsldebug(L2_LEVEL_DEBUG, "vsyslog: priority=0x%.8lx, ctx.maskpri=0x%.8lx ", (unsigned long)priority, (unsigned long)ctx.maskpri);
/* check against maskpri */
if ((LOG_MASK(priority) & ctx.maskpri) == 0) {
fsldebug(L2_LEVEL_DEBUG, "vsyslog: short circuit maskpri");
return;
}
levelmask = 0;
for (i = 0; sysloglevel2string[i].string != NULL; i++) {
fsldebug(L2_LEVEL_DEBUG, "vsyslog: ctx.levelmap[%d].syslog = 0x%.8lx, ctx.levelmap[%d].l2 = 0x%.8lx", i, (unsigned long)ctx.levelmap[i].syslog, i, (unsigned long)ctx.levelmap[i].l2);
if (ctx.levelmap[i].syslog == priority) {
levelmask = ctx.levelmap[i].l2;
break;
}
}
fsldebug(L2_LEVEL_DEBUG, "vsyslog: levelmask=0x%.8lx", (unsigned long)levelmask);
/* the heart of FSL */
if ((l2rv = l2_channel_vlog(ctx.l2_nch, levelmask, fmt, args)) != L2_OK) {
cp = l2_env_strerror(ctx.l2_env, l2rv);
fsldebug(L2_LEVEL_PANIC, "vsyslog: application logging failed: %s (%d)", cp, l2rv);
}
return;
}
/* faked POSIX API function syslog(3) */
void syslog(int priority, const char *message, ...)
{
va_list args;
/* tracing */
fsldebug(L2_LEVEL_TRACE, "fsl in syslog(3); go ahead using vsyslog(3)");
/* wrap around vsyslog(3) */
va_start(args, message);
vsyslog(priority, message, args);
va_end(args);
return;
}