ossp-pkg/l2/l2_ch_syslog.c
/*
** OSSP l2 - Flexible Logging
** Copyright (c) 2001-2005 Cable & Wireless <http://www.cw.com/>
** Copyright (c) 2001-2005 The OSSP Project <http://www.ossp.org/>
** Copyright (c) 2001-2005 Ralf S. Engelschall <rse@engelschall.com>
**
** This file is part of OSSP l2, a flexible logging library which
** can be found at http://www.ossp.org/pkg/lib/l2/.
**
** 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.
**
** l2_ch_syslog.c: syslog(3) channel implementation
*/
#include "l2.h"
#include "l2_p.h"
#include <syslog.h>
#include <time.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <unistd.h>
/* declare private channel configuration */
typedef struct {
char *szTarget;
char *szRemoteHost;
int nRemotePort;
char *szLocalHost;
char *szFacility;
int nFacility;
char *szIdent;
int bLogPid;
sa_t *saRemoteSock;
sa_addr_t *saaRemoteAddr;
} l2_ch_syslog_t;
/* mapping from L2 log levels to syslog(3) log levels */
static struct {
int levelL2;
int levelSL;
} l2_ch_syslog_L2toSL[] = {
{ L2_LEVEL_PANIC, LOG_EMERG },
{ L2_LEVEL_CRITICAL, LOG_CRIT },
{ L2_LEVEL_ERROR, LOG_ERR },
{ L2_LEVEL_WARNING, LOG_WARNING },
{ L2_LEVEL_NOTICE, LOG_NOTICE },
{ L2_LEVEL_INFO, LOG_INFO },
{ L2_LEVEL_TRACE, LOG_INFO },
{ L2_LEVEL_DEBUG, LOG_DEBUG },
{ -1, -1 }
};
/* Syslog Facility Table */
static struct {
char *name; /* the common name */
int numREMOTE; /* number according to RFC3164 */
int numLOCAL; /* local encoding */
} l2_ch_syslog_SLfac[] = {
{ "kern", 0, LOG_KERN },
{ "user", 1, LOG_USER },
{ "mail", 2, LOG_MAIL },
{ "daemon", 3, LOG_DAEMON },
{ "auth", 4, LOG_AUTH },
#ifndef LOG_SYSLOG
#define LOG_SYSLOG (5 << 3)
#endif
{ "syslog", 5, LOG_SYSLOG },
{ "lpr", 6, LOG_LPR },
{ "news", 7, LOG_NEWS },
{ "uucp", 8, LOG_UUCP },
{ "cron", 9, LOG_CRON },
#ifndef LOG_AUTHPRIV
#define LOG_AUTHPRIV (10 << 3)
#endif
{ "authpriv", 10, LOG_AUTHPRIV },
#ifndef LOG_FTP
#define LOG_FTP (11 << 3)
#endif
{ "ftp", 11, LOG_FTP },
#ifndef LOG_NTP
#define LOG_NTP (12 << 3)
#endif
{ "ntp", 12, LOG_NTP },
#ifndef LOG_SECURITY
#define LOG_SECURITY (13 << 3)
#endif
{ "security", 13, LOG_SECURITY },
#ifndef LOG_CONSOLE
#define LOG_CONSOLE (14 << 3)
#endif
{ "console", 14, LOG_CONSOLE },
#ifndef LOG_CLOCK
#define LOG_CLOCK (15 << 3)
#endif
{ "clock", 15, LOG_CLOCK },
{ "local0", 16, LOG_LOCAL0 },
{ "local1", 17, LOG_LOCAL1 },
{ "local2", 18, LOG_LOCAL2 },
{ "local3", 19, LOG_LOCAL3 },
{ "local4", 20, LOG_LOCAL4 },
{ "local5", 21, LOG_LOCAL5 },
{ "local6", 22, LOG_LOCAL6 },
{ "local7", 23, LOG_LOCAL7 },
{ NULL, 0, 0 }
};
/* create channel */
static l2_result_t hook_create(l2_context_t *ctx, l2_channel_t *ch)
{
l2_ch_syslog_t *cfg;
struct utsname uts;
char *cp;
/* allocate private channel configuration */
if ((cfg = (l2_ch_syslog_t *)malloc(sizeof(l2_ch_syslog_t))) == NULL)
return L2_ERR_MEM;
/* initialize configuration with reasonable defaults */
cfg->szTarget = strdup("local");
cfg->szRemoteHost = NULL;
cfg->nRemotePort = 514; /*FIXME[thl] better use getservbyname()*/
if (uname(&uts) == 0) {
cfg->szLocalHost = strdup(uts.nodename);
if ((cp = strchr(cfg->szLocalHost, '.')) != NULL)
*cp = '\0';
}
else
cfg->szLocalHost = strdup("localhost");
cfg->szFacility = strdup("user");
cfg->nFacility = LOG_USER;
cfg->szIdent = NULL;
cfg->bLogPid = FALSE;
cfg->saRemoteSock = NULL;
cfg->saaRemoteAddr = NULL;
/* link private channel configuration into channel context */
ctx->vp = cfg;
return L2_OK;
}
/* configure channel */
static l2_result_t hook_configure(l2_context_t *ctx, l2_channel_t *ch, const char *fmt, va_list ap)
{
l2_ch_syslog_t *cfg = (l2_ch_syslog_t *)ctx->vp;
l2_param_t pa[8];
l2_result_t rv;
l2_env_t *env;
int i;
/* feed and call generic parameter parsing engine */
L2_PARAM_SET(pa[0], target, STR, &cfg->szTarget);
L2_PARAM_SET(pa[1], remotehost, STR, &cfg->szRemoteHost);
L2_PARAM_SET(pa[2], remoteport, INT, &cfg->nRemotePort);
L2_PARAM_SET(pa[3], localhost, STR, &cfg->szLocalHost);
L2_PARAM_SET(pa[4], facility, STR, &cfg->szFacility);
L2_PARAM_SET(pa[5], ident, STR, &cfg->szIdent);
L2_PARAM_SET(pa[6], logpid, INT, &cfg->bLogPid);
L2_PARAM_END(pa[7]);
/* sanity checking & post-processing */
l2_channel_env(ch, &env);
rv = l2_util_setparams(env, pa, fmt, ap);
if (cfg->szTarget == NULL || cfg->szFacility == NULL)
return L2_ERR_USE;
if (!( strcmp(cfg->szTarget, "local") == 0
|| strcmp(cfg->szTarget, "remote") == 0))
return L2_ERR_USE;
for (i = 0; l2_ch_syslog_SLfac[i].name != NULL; i++)
if (strcmp(l2_ch_syslog_SLfac[i].name, cfg->szFacility) == 0)
break;
if (l2_ch_syslog_SLfac[i].name == NULL)
return L2_ERR_USE;
if (strcmp(cfg->szTarget, "local") == 0)
cfg->nFacility = l2_ch_syslog_SLfac[i].numLOCAL;
else
cfg->nFacility = (l2_ch_syslog_SLfac[i].numREMOTE << 3);
if ( strcmp(cfg->szTarget, "remote") == 0
&& (cfg->szRemoteHost == NULL
|| (cfg->nRemotePort <= 0 || cfg->nRemotePort >= 65536)))
return L2_ERR_USE;
if ( cfg->szLocalHost == NULL
|| strchr(cfg->szLocalHost, '.') != NULL)
return L2_ERR_USE;
if (cfg->szIdent != NULL && strlen(cfg->szIdent) > (32-(1+5+1)))
return L2_ERR_USE;
return rv;
}
/* open channel */
static l2_result_t hook_open(l2_context_t *ctx, l2_channel_t *ch)
{
l2_ch_syslog_t *cfg = (l2_ch_syslog_t *)ctx->vp;
int opt;
sa_rc_t rc;
sa_addr_t *la;
if (cfg->szIdent == NULL)
return L2_ERR_USE;
if (strcmp(cfg->szTarget, "local") == 0) {
/* open local syslog connection via syslog(3) */
opt = 0;
if (cfg->bLogPid)
opt |= LOG_PID;
openlog(cfg->szIdent, opt, cfg->nFacility);
/* setlogmask(0); */
}
else {
/* open remote syslog connection via UDP socket */
if (cfg->szRemoteHost == NULL)
return L2_ERR_USE;
if ((rc = sa_addr_create(&cfg->saaRemoteAddr)) != SA_OK)
return (rc == SA_ERR_SYS ? L2_ERR_SYS : L2_ERR_INT);
if ((rc = sa_addr_u2a(cfg->saaRemoteAddr, "inet://%s:%d",
cfg->szRemoteHost, cfg->nRemotePort)) != SA_OK)
return (rc == SA_ERR_SYS ? L2_ERR_SYS : L2_ERR_INT);
if ((rc = sa_create(&cfg->saRemoteSock)) != SA_OK)
return (rc == SA_ERR_SYS ? L2_ERR_SYS : L2_ERR_INT);
sa_type(cfg->saRemoteSock, SA_TYPE_DATAGRAM);
sa_timeout(cfg->saRemoteSock, SA_TIMEOUT_ALL, 10, 0);
if ((rc = sa_addr_create(&la)) != SA_OK)
return (rc == SA_ERR_SYS ? L2_ERR_SYS : L2_ERR_INT);
/* FIXME: if uid == 0 -> use port 514 */
if ((rc = sa_addr_u2a(la, "inet://0.0.0.0:0")) != SA_OK)
return (rc == SA_ERR_SYS ? L2_ERR_SYS : L2_ERR_INT);
if ((rc = sa_bind(cfg->saRemoteSock, la)) != SA_OK)
return (rc == SA_ERR_SYS ? L2_ERR_SYS : L2_ERR_INT);
sa_addr_destroy(la);
}
return L2_OK;
}
/* write to channel */
static l2_result_t hook_write(l2_context_t *ctx, l2_channel_t *ch,
l2_level_t level, const char *buf, size_t buf_size)
{
l2_ch_syslog_t *cfg = (l2_ch_syslog_t *)ctx->vp;
int prio;
int i;
char caTime[15+1];
char caBuf[2048];
time_t t;
struct tm *tm;
size_t n;
sa_rc_t rc;
/* determine syslog priority */
prio = 0;
for (i = 0; l2_ch_syslog_L2toSL[i].levelL2 != -1; i++) {
if (l2_ch_syslog_L2toSL[i].levelL2 == level) {
prio = l2_ch_syslog_L2toSL[i].levelSL;
break;
}
}
if (l2_ch_syslog_L2toSL[i].levelL2 == -1)
return L2_ERR_USE;
/* FIXME: nul-terminate buf? */
if (strcmp(cfg->szTarget, "local") == 0) {
/* send to local syslogd via syslog(3) */
syslog(prio, "%s", buf);
}
else {
/* send to remote syslogd via UDP */
if (strlen(buf) > 1024)
return L2_ERR_MEM;
prio += cfg->nFacility;
t = time(NULL);
tm = localtime(&t);
strftime(caTime, sizeof(caTime), "%b %d %H:%M:%S", tm);
if (caTime[4] == '0')
caTime[4] = ' ';
if (cfg->bLogPid)
n = l2_util_sprintf(caBuf, sizeof(caBuf), "<%d>%s %s %s[%lu]: %s",
prio, caTime, cfg->szLocalHost,
cfg->szIdent, (unsigned long)getpid(), buf);
else
n = l2_util_sprintf(caBuf, sizeof(caBuf), "<%d>%s %s %s: %s",
prio, caTime, cfg->szLocalHost,
cfg->szIdent, buf);
if ((n = strlen(caBuf)) > 1024)
return L2_ERR_IO;
if ((rc = sa_send(cfg->saRemoteSock, cfg->saaRemoteAddr,
caBuf, n, NULL)) != SA_OK)
return (rc == SA_ERR_SYS ? L2_ERR_SYS : L2_ERR_IO);
}
return L2_OK;
}
/* close channel */
static l2_result_t hook_close(l2_context_t *ctx, l2_channel_t *ch)
{
l2_ch_syslog_t *cfg = (l2_ch_syslog_t *)ctx->vp;
if (strcmp(cfg->szTarget, "local") == 0) {
closelog();
}
else {
if (cfg->saRemoteSock != NULL) {
sa_destroy(cfg->saRemoteSock);
cfg->saRemoteSock = NULL;
}
if (cfg->saaRemoteAddr != NULL) {
sa_addr_destroy(cfg->saaRemoteAddr);
cfg->saaRemoteAddr = NULL;
}
}
return L2_OK;
}
/* destroy channel */
static l2_result_t hook_destroy(l2_context_t *ctx, l2_channel_t *ch)
{
l2_ch_syslog_t *cfg = (l2_ch_syslog_t *)ctx->vp;
/* destroy channel configuration */
if (cfg->szTarget != NULL)
free(cfg->szTarget);
if (cfg->szRemoteHost != NULL)
free(cfg->szRemoteHost);
if (cfg->szLocalHost != NULL)
free(cfg->szLocalHost);
if (cfg->szFacility != NULL)
free(cfg->szFacility);
if (cfg->szIdent != NULL)
free(cfg->szIdent);
if (cfg->saRemoteSock != NULL)
sa_destroy(cfg->saRemoteSock);
if (cfg->saaRemoteAddr != NULL)
sa_addr_destroy(cfg->saaRemoteAddr);
free(cfg);
return L2_OK;
}
/* exported channel handler structure */
l2_handler_t l2_handler_syslog = {
"syslog",
L2_CHANNEL_OUTPUT,
hook_create,
hook_configure,
hook_open,
hook_write,
NULL,
hook_close,
hook_destroy
};