/* ** OSSP fsl - Faking/Flexible Syslog Library ** Copyright (c) 2002-2003 Ralf S. Engelschall ** Copyright (c) 2002-2003 The OSSP Project ** Copyright (c) 2002-2003 Cable & Wireless Deutschland ** ** 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 #include #include #include #include #include #include #include #include #include #include #include /* standard include we re-implement */ #include /* 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) { if ((*(const char **)str1 != NULL) && (*(const char **)str2 != NULL)) return strcmp(*(const char **)str1, *(const char **)str2); /* this must never happen but be prepared for the impossible */ if ((*(const char **)str1 != NULL) && (*(const char **)str2 == NULL)) return strcmp(*(const char **)str1, ""); if ((*(const char **)str1 == NULL) && (*(const char **)str2 != NULL)) return strcmp("", *(const char **)str1); 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 *filename = NULL; 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); while (fileidx < filecnt) { /* loop once for every string in sorted array */ if (appendfiletobuffer(buffer, filearr[fileidx]) == FSL_OK) rc = FSL_OK; free(filearr[fileidx]); filearr[fileidx] = NULL; fileidx++; } CU(rc); CUS: if (dp != NULL) closedir(dp); if (filename != NULL) free(filename); 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 : ""), 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, &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 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; } /* 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; }