socat/error.c
2008-10-22 02:33:23 +02:00

295 lines
7.8 KiB
C

/* source: error.c */
/* Copyright Gerhard Rieger 2001-2008 */
/* Published under the GNU General Public License V.2, see file COPYING */
/* the logging subsystem */
#include "config.h"
#include <stdarg.h>
#include <stdlib.h>
#include <errno.h>
#if HAVE_SYSLOG_H
#include <syslog.h>
#endif
#include <sys/utsname.h>
#include <time.h> /* time_t, strftime() */
#include <sys/time.h> /* gettimeofday() */
#include <stdio.h>
#include <string.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include "mytypes.h"
#include "compat.h"
#include "utils.h"
#include "error.h"
/* translate MSG level to SYSLOG level */
int syslevel[] = {
LOG_DEBUG,
LOG_INFO,
LOG_NOTICE,
LOG_WARNING,
LOG_ERR,
LOG_CRIT };
struct diag_opts {
const char *progname;
int msglevel;
int exitlevel;
int syslog;
FILE *logfile;
int logfacility;
bool micros;
int exitstatus; /* pass signal number to error exit */
bool withhostname; /* in custom logs add hostname */
char *hostname;
} ;
struct diag_opts diagopts =
{ NULL, E_ERROR, E_ERROR, 0, NULL, LOG_DAEMON, false, 0 } ;
static void _msg(int level, const char *buff, const char *syslp);
static struct wordent facilitynames[] = {
{"auth", (void *)LOG_AUTH},
#ifdef LOG_AUTHPRIV
{"authpriv", (void *)LOG_AUTHPRIV},
#endif
#ifdef LOG_CONSOLE
{"console", (void *)LOG_CONSOLE},
#endif
{"cron", (void *)LOG_CRON},
{"daemon", (void *)LOG_DAEMON},
#ifdef LOG_FTP
{"ftp", (void *)LOG_FTP},
#endif
{"kern", (void *)LOG_KERN},
{"local0", (void *)LOG_LOCAL0},
{"local1", (void *)LOG_LOCAL1},
{"local2", (void *)LOG_LOCAL2},
{"local3", (void *)LOG_LOCAL3},
{"local4", (void *)LOG_LOCAL4},
{"local5", (void *)LOG_LOCAL5},
{"local6", (void *)LOG_LOCAL6},
{"local7", (void *)LOG_LOCAL7},
{"lpr", (void *)LOG_LPR},
{"mail", (void *)LOG_MAIL},
{"news", (void *)LOG_NEWS},
#ifdef LOG_SECURITY
{"security", (void *)LOG_SECURITY},
#endif
{"syslog", (void *)LOG_SYSLOG},
{"user", (void *)LOG_USER},
{"uucp", (void *)LOG_UUCP}
} ;
static int diaginitialized;
static int diag_init(void) {
if (diaginitialized) {
return 0;
}
diaginitialized = 1;
/* gcc with GNU libc refuses to set this in the initializer */
diagopts.logfile = stderr;
return 0;
}
#define DIAG_INIT ((void)(diaginitialized || diag_init()))
void diag_set(char what, const char *arg) {
DIAG_INIT;
switch (what) {
const struct wordent *keywd;
case 'y': diagopts.syslog = true;
if (arg && arg[0]) {
if ((keywd =
keyw(facilitynames, arg,
sizeof(facilitynames)/sizeof(struct wordent))) == NULL) {
Error1("unknown syslog facility \"%s\"", arg);
} else {
diagopts.logfacility = (int)(size_t)keywd->desc;
}
}
openlog(diagopts.progname, LOG_PID, diagopts.logfacility);
if (diagopts.logfile != NULL && diagopts.logfile != stderr) {
fclose(diagopts.logfile);
}
diagopts.logfile = NULL;
break;
case 'f':
if (diagopts.logfile != NULL && diagopts.logfile != stderr) {
fclose(diagopts.logfile);
}
if ((diagopts.logfile = fopen(arg, "a")) == NULL) {
Error2("cannot open log file \"%s\": %s", arg, strerror(errno));
}
break;
case 's':
if (diagopts.logfile != NULL && diagopts.logfile != stderr) {
fclose(diagopts.logfile);
}
diagopts.logfile = stderr; break; /* logging to stderr is default */
case 'p': diagopts.progname = arg;
openlog(diagopts.progname, LOG_PID, diagopts.logfacility);
break;
case 'd': --diagopts.msglevel; break;
case 'u': diagopts.micros = true; break;
default: msg(E_ERROR, "unknown diagnostic option %c", what);
}
}
void diag_set_int(char what, int arg) {
DIAG_INIT;
switch (what) {
case 'D': diagopts.msglevel = arg; break;
case 'e': diagopts.exitlevel = arg; break;
case 'x': diagopts.exitstatus = arg; break;
case 'h': diagopts.withhostname = arg;
if ((diagopts.hostname = getenv("HOSTNAME")) == NULL) {
struct utsname ubuf;
uname(&ubuf);
diagopts.hostname = strdup(ubuf.nodename);
}
break;
default: msg(E_ERROR, "unknown diagnostic option %c", what);
}
}
int diag_get_int(char what) {
DIAG_INIT;
switch (what) {
case 'y': return diagopts.syslog;
case 's': return diagopts.logfile == stderr;
case 'd': case 'D': return diagopts.msglevel;
case 'e': return diagopts.exitlevel;
}
return -1;
}
const char *diag_get_string(char what) {
DIAG_INIT;
switch (what) {
case 'p': return diagopts.progname;
}
return NULL;
}
/* Linux and AIX syslog format:
Oct 4 17:10:37 hostname socat[52798]: D signal(13, 1)
*/
void msg(int level, const char *format, ...) {
#if HAVE_GETTIMEOFDAY || 1
struct timeval now;
int result;
time_t nowt;
#else /* !HAVE_GETTIMEOFDAY */
time_t now;
#endif /* !HAVE_GETTIMEOFDAY */
#define BUFLEN 512
char buff[BUFLEN], *bufp, *syslp;
size_t bytes;
va_list ap;
DIAG_INIT;
if (level < diagopts.msglevel) return;
va_start(ap, format);
#if HAVE_GETTIMEOFDAY || 1
result = gettimeofday(&now, NULL);
if (result < 0) {
/* invoking msg() might create endless recursion; by hand instead */
sprintf(buff, "cannot read time: %s["F_pid".%lu] E %s",
diagopts.progname, getpid(), (unsigned long)pthread_self(), strerror(errno));
_msg(LOG_ERR, buff, strstr(buff, " E "+1));
strcpy(buff, "unknown time "); bytes = 20;
} else {
nowt = now.tv_sec;
#if HAVE_STRFTIME
if (diagopts.micros) {
bytes = strftime(buff, 20, "%Y/%m/%d %H:%M:%S", localtime(&nowt));
bytes += sprintf(buff+19, "."F_tv_usec" ", now.tv_usec);
} else {
bytes =
strftime(buff, 21, "%Y/%m/%d %H:%M:%S ", localtime(&nowt));
}
#else
strcpy(buff, ctime(&nowt));
bytes = strlen(buff);
#endif
}
#else /* !HAVE_GETTIMEOFDAY */
now = time(NULL); if (now == (time_t)-1) {
/* invoking msg() might create endless recursion; by hand instead */
sprintf(buff, "cannot read time: %s["F_pid"] E %s",
diagopts.progname, getpid(), strerror(errno));
_msg(LOG_ERR, buff, strstr(buff, " E "+1));
strcpy(buff, "unknown time "); bytes = 20;
} else {
#if HAVE_STRFTIME
bytes = strftime(buff, 21, "%Y/%m/%d %H:%M:%S ", localtime(&now));
#else
strcpy(buff, ctime(&now));
bytes = strlen(buff);
#endif
}
#endif /* !HAVE_GETTIMEOFDAY */
bufp = buff + bytes;
if (diagopts.withhostname) {
bytes = sprintf(bufp, "%s ", diagopts.hostname), bufp+=bytes;
}
bytes = sprintf(bufp, "%s["F_pid".%lu] ",
diagopts.progname, getpid(), (unsigned long)pthread_self());
bufp += bytes;
syslp = bufp;
*bufp++ = "DINWEF"[level];
*bufp++ = ' ';
vsnprintf(bufp, BUFLEN-(bufp-buff)-1, format, ap);
strcat(bufp, "\n");
_msg(level, buff, syslp);
if (level >= diagopts.exitlevel) {
va_end(ap);
if (E_NOTICE >= diagopts.msglevel) {
sprintf(syslp, "N exit(1)\n");
_msg(E_NOTICE, buff, syslp);
}
exit(diagopts.exitstatus ? diagopts.exitstatus : 1);
}
va_end(ap);
}
static void _msg(int level, const char *buff, const char *syslp) {
if (diagopts.syslog) {
/* prevent format string attacks (thanks to CoKi) */
syslog(syslevel[level], "%s", syslp);
}
if (diagopts.logfile) {
fputs(buff, diagopts.logfile); fflush(diagopts.logfile);
}
}
/* use a new log output file descriptor that is dup'ed from the current one.
this is useful when socat logs to stderr but fd 2 should be redirected to
serve other purposes */
int diag_dup(void) {
int newfd;
DIAG_INIT;
if (diagopts.logfile == NULL) {
return -1;
}
newfd = dup(fileno(diagopts.logfile));
if (diagopts.logfile != stderr) {
fclose(diagopts.logfile);
}
if (newfd >= 0) {
diagopts.logfile = fdopen(newfd, "w");
}
return newfd;
}