mirror of
https://repo.or.cz/socat.git
synced 2024-12-22 23:42:34 +00:00
540 lines
15 KiB
C
540 lines
15 KiB
C
/* source: error.c */
|
|
/* Copyright Gerhard Rieger and contributors (see file CHANGES) */
|
|
/* Published under the GNU General Public License V.2, see file COPYING */
|
|
|
|
/* the logging subsystem */
|
|
|
|
#include "config.h"
|
|
#include "sysincludes.h"
|
|
|
|
#include "mytypes.h"
|
|
#include "compat.h"
|
|
#include "utils.h"
|
|
#include "vsnprintf_r.h"
|
|
#include "snprinterr.h"
|
|
|
|
#include "error.h"
|
|
#include "sycls.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;
|
|
bool signalsafe;
|
|
} ;
|
|
|
|
|
|
static void _diag_exit(int status);
|
|
|
|
|
|
struct diag_opts diagopts =
|
|
{ NULL, E_ERROR, E_ERROR, 0, NULL, LOG_DAEMON, false, 0, false, NULL, true } ;
|
|
|
|
static void msg2(
|
|
#if HAVE_CLOCK_GETTIME
|
|
struct timespec *now,
|
|
#elif HAVE_PROTOTYPE_LIB_gettimeofday
|
|
struct timeval *now,
|
|
#else
|
|
time_t *now,
|
|
#endif
|
|
int level, int exitcode, int handler, const char *text);
|
|
static void _msg(int level, const char *buff, const char *syslp);
|
|
|
|
sig_atomic_t diag_in_handler; /* !=0 indicates to msg() that in signal handler */
|
|
sig_atomic_t diag_immediate_msg; /* !=0 prints messages even from within signal handler instead of deferring them */
|
|
sig_atomic_t diag_immediate_exit; /* !=0 calls exit() from diag_exit() even when in signal handler. For system() */
|
|
|
|
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}
|
|
} ;
|
|
|
|
/* serialize message for sending from signal handlers */
|
|
struct sermsg {
|
|
int severity;
|
|
#if HAVE_CLOCK_GETTIME
|
|
struct timespec ts;
|
|
#else
|
|
struct timeval tv;
|
|
#endif
|
|
} ;
|
|
|
|
static int diaginitialized;
|
|
static int diag_sock_send = -1;
|
|
static int diag_sock_recv = -1;
|
|
static int diag_msg_avail = 0; /* !=0: messages from within signal handler may be waiting */
|
|
|
|
|
|
static int diag_sock_pair(void) {
|
|
int handlersocks[2];
|
|
|
|
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, handlersocks) < 0) {
|
|
diag_sock_send = -1;
|
|
diag_sock_recv = -1;
|
|
return -1;
|
|
}
|
|
fcntl(handlersocks[0], F_SETFD, FD_CLOEXEC);
|
|
fcntl(handlersocks[1], F_SETFD, FD_CLOEXEC);
|
|
diag_sock_send = handlersocks[1];
|
|
diag_sock_recv = handlersocks[0];
|
|
#if !defined(MSG_DONTWAIT)
|
|
fcntl(diag_sock_send, F_SETFL, O_NONBLOCK);
|
|
fcntl(diag_sock_recv, F_SETFL, O_NONBLOCK);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
if (diagopts.signalsafe) {
|
|
if (diag_sock_pair() < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
#define DIAG_INIT ((void)(diaginitialized || diag_init()))
|
|
|
|
|
|
void diag_set(char what, const char *arg) {
|
|
switch (what) {
|
|
case 'I':
|
|
if (diagopts.signalsafe) {
|
|
if (diag_sock_send >= 0) { Close(diag_sock_send); diag_sock_send = -1; }
|
|
if (diag_sock_recv >= 0) { Close(diag_sock_recv); diag_sock_recv = -1; }
|
|
}
|
|
diagopts.signalsafe = false;
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* make sure that the diag_sock fds do not have this num */
|
|
int diag_reserve_fd(int fd) {
|
|
DIAG_INIT;
|
|
if (diag_sock_send == fd) {
|
|
diag_sock_send = Dup(fd);
|
|
Close(fd);
|
|
}
|
|
if (diag_sock_recv == fd) {
|
|
diag_sock_recv = Dup(fd);
|
|
Close(fd);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* call this after a fork() from the child process to separate master/parent
|
|
sockets from child sockets */
|
|
int diag_fork() {
|
|
Close(diag_sock_send);
|
|
Close(diag_sock_recv);
|
|
if (diagopts.signalsafe) {
|
|
return diag_sock_pair();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Linux and AIX syslog format:
|
|
Oct 4 17:10:37 hostname socat[52798]: D signal(13, 1)
|
|
*/
|
|
void msg(int level, const char *format, ...) {
|
|
struct diag_dgram diag_dgram;
|
|
va_list ap;
|
|
|
|
/* does not perform a system call if nothing todo, thanks diag_msg_avail */
|
|
|
|
diag_dgram._errno = errno; /* keep for passing from signal handler to sock.
|
|
reason is that strerror is definitely not
|
|
async-signal-safe */
|
|
DIAG_INIT;
|
|
|
|
/* in normal program flow (not in signal handler) */
|
|
/* first flush the queue of datagrams from the socket */
|
|
if (diag_msg_avail && !diag_in_handler) {
|
|
diag_msg_avail = 0; /* _before_ flush to prevent inconsistent state when signal occurs inbetween */
|
|
diag_flush();
|
|
}
|
|
|
|
if (level < diagopts.msglevel) { return; }
|
|
va_start(ap, format);
|
|
|
|
/* we do only a minimum in the outer parts which may run in a signal handler
|
|
these are: get actual time, level, serialized message and write them to socket
|
|
*/
|
|
diag_dgram.op = DIAG_OP_MSG;
|
|
#if HAVE_CLOCK_GETTIME
|
|
clock_gettime(CLOCK_REALTIME, &diag_dgram.now);
|
|
#elif HAVE_PROTOTYPE_LIB_gettimeofday
|
|
gettimeofday(&diag_dgram.now, NULL);
|
|
#else
|
|
diag_dgram.now = time(NULL);
|
|
#endif
|
|
diag_dgram.level = level;
|
|
diag_dgram.exitcode = diagopts.exitstatus;
|
|
vsnprintf_r(diag_dgram.text, sizeof(diag_dgram.text), format, ap);
|
|
if (diagopts.signalsafe && diag_in_handler && !diag_immediate_msg) {
|
|
send(diag_sock_send, &diag_dgram, sizeof(diag_dgram)-TEXTLEN + strlen(diag_dgram.text)+1,
|
|
0 /* for canonical reasons */
|
|
#ifdef MSG_DONTWAIT
|
|
|MSG_DONTWAIT
|
|
#endif
|
|
#ifdef MSG_NOSIGNAL
|
|
|MSG_NOSIGNAL
|
|
#endif
|
|
);
|
|
diag_msg_avail = 1;
|
|
va_end(ap);
|
|
return;
|
|
}
|
|
|
|
msg2(&diag_dgram.now, diag_dgram.level, diagopts.exitstatus, 0, diag_dgram.text);
|
|
va_end(ap); return;
|
|
}
|
|
|
|
void msg2(
|
|
#if HAVE_CLOCK_GETTIME
|
|
struct timespec *now,
|
|
#elif HAVE_PROTOTYPE_LIB_gettimeofday
|
|
struct timeval *now,
|
|
#else
|
|
time_t *now,
|
|
#endif
|
|
int level, /* E_INFO... */
|
|
int exitcode, /* on exit use this exit code */
|
|
int handler, /* message comes from signal handler */
|
|
const char *text) {
|
|
time_t epoch;
|
|
unsigned long micros;
|
|
#if HAVE_STRFTIME
|
|
struct tm struct_tm;
|
|
#endif
|
|
#define MSGLEN 512
|
|
char buff[MSGLEN+2], *bufp = buff, *syslp;
|
|
size_t bytes;
|
|
|
|
#if HAVE_CLOCK_GETTIME
|
|
epoch = now->tv_sec;
|
|
#elif HAVE_PROTOTYPE_LIB_gettimeofday
|
|
epoch = now->tv_sec;
|
|
#else
|
|
epoch = *now;
|
|
#endif
|
|
/*! consider caching instead of recalculating many times per second */
|
|
#if HAVE_STRFTIME
|
|
bytes = strftime(bufp, 20, "%Y/%m/%d %H:%M:%S", localtime_r(&epoch, &struct_tm));
|
|
#else
|
|
bytes = snprintf(bufp, 11, F_time, epoch);
|
|
#endif
|
|
bufp += bytes;
|
|
*bufp = '\0';
|
|
if (diagopts.micros) {
|
|
#if HAVE_CLOCK_GETTIME
|
|
micros = now->tv_nsec/1000;
|
|
#elif HAVE_PROTOTYPE_LIB_gettimeofday
|
|
micros = now->tv_usec;
|
|
#else
|
|
micros = 0;
|
|
#endif
|
|
bufp += sprintf(bufp, ".%06lu ", micros);
|
|
} else {
|
|
*bufp++ = ' ';
|
|
*bufp = '\0';
|
|
}
|
|
|
|
if (diagopts.withhostname) {
|
|
bytes = snprintf(bufp, MSGLEN-(bufp-buff), "%s ", diagopts.hostname);
|
|
if (bytes >= MSGLEN-(bufp-buff))
|
|
bytes = MSGLEN-(bufp-buff)-1;
|
|
bufp += bytes;
|
|
}
|
|
bytes = snprintf(bufp, MSGLEN-(bufp-buff), "%s["F_pid"] ", diagopts.progname, getpid());
|
|
if (bytes >= MSGLEN-(bufp-buff))
|
|
bytes = MSGLEN-(bufp-buff)-1;
|
|
bufp += bytes;
|
|
syslp = bufp; /* syslog prefixes with time etc.itself */
|
|
if (bufp < buff+MSGLEN)
|
|
*bufp++ = "DINWEF"[level];
|
|
#if 0 /* only for debugging socat */
|
|
if (handler) bufp[-1] = tolower((unsigned char)bufp[-1]); /* for debugging, low chars indicate messages from signal handlers */
|
|
#endif
|
|
if (bufp < buff+MSGLEN)
|
|
*bufp++ = ' ';
|
|
strncpy(bufp, text, MSGLEN-(bufp-buff));
|
|
bufp = strchr(bufp, '\0');
|
|
strcpy(bufp, "\n");
|
|
_msg(level, buff, syslp);
|
|
if (level >= diagopts.exitlevel) {
|
|
if (E_NOTICE >= diagopts.msglevel) {
|
|
if ((syslp - buff) + 16 > MSGLEN+1)
|
|
syslp = buff + MSGLEN - 15;
|
|
snprintf_r(syslp, 16, "N exit(%d)\n", exitcode?exitcode:(diagopts.exitstatus?diagopts.exitstatus:1));
|
|
_msg(E_NOTICE, buff, syslp);
|
|
}
|
|
exit(exitcode?exitcode:(diagopts.exitstatus?diagopts.exitstatus:1));
|
|
}
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
|
|
/* handle the messages in the queue */
|
|
void diag_flush(void) {
|
|
struct diag_dgram recv_dgram;
|
|
char exitmsg[20];
|
|
|
|
if (!diagopts.signalsafe) {
|
|
return;
|
|
}
|
|
|
|
while (recv(diag_sock_recv, &recv_dgram, sizeof(recv_dgram)-1,
|
|
0 /* for canonical reasons */
|
|
#ifdef MSG_DONTWAIT
|
|
|MSG_DONTWAIT
|
|
#endif
|
|
) > 0) {
|
|
recv_dgram.text[TEXTLEN-1] = '\0';
|
|
switch (recv_dgram.op) {
|
|
case DIAG_OP_EXIT:
|
|
/* we want the actual time, not when this dgram was sent */
|
|
#if HAVE_CLOCK_GETTIME
|
|
clock_gettime(CLOCK_REALTIME, &recv_dgram.now);
|
|
#elif HAVE_PROTOTYPE_LIB_gettimeofday
|
|
gettimeofday(&recv_dgram.now, NULL);
|
|
#else
|
|
recv_dgram.now = time(NULL);
|
|
#endif
|
|
if (E_NOTICE >= diagopts.msglevel) {
|
|
snprintf_r(exitmsg, sizeof(exitmsg), "exit(%d)", recv_dgram.exitcode?recv_dgram.exitcode:1);
|
|
msg2(&recv_dgram.now, E_NOTICE, recv_dgram.exitcode?recv_dgram.exitcode:1, 1, exitmsg);
|
|
}
|
|
exit(recv_dgram.exitcode?recv_dgram.exitcode:1);
|
|
case DIAG_OP_MSG:
|
|
if (recv_dgram._errno) {
|
|
/* there might be a %m control in the string (glibc compatible,
|
|
replace with strerror(...errno) ) */
|
|
char text[TEXTLEN];
|
|
errno = recv_dgram._errno;
|
|
snprinterr(text, TEXTLEN, recv_dgram.text);
|
|
msg2(&recv_dgram.now, recv_dgram.level, recv_dgram.exitcode, 1, text);
|
|
} else {
|
|
msg2(&recv_dgram.now, recv_dgram.level, recv_dgram.exitcode, 1, recv_dgram.text);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* 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));
|
|
Fcntl_l(newfd, F_SETFD, FD_CLOEXEC);
|
|
if (diagopts.logfile != stderr) {
|
|
fclose(diagopts.logfile);
|
|
}
|
|
if (newfd >= 0) {
|
|
diagopts.logfile = fdopen(newfd, "w");
|
|
}
|
|
return newfd;
|
|
}
|
|
|
|
|
|
/* this function is kind of async-signal-safe exit(). When invoked from signal
|
|
handler it defers exit. */
|
|
void diag_exit(int status) {
|
|
struct diag_dgram diag_dgram;
|
|
|
|
if (diag_in_handler && !diag_immediate_exit) {
|
|
diag_dgram.op = DIAG_OP_EXIT;
|
|
diag_dgram.exitcode = status;
|
|
send(diag_sock_send, &diag_dgram, sizeof(diag_dgram)-TEXTLEN,
|
|
0 /* for canonical reasons */
|
|
#ifdef MSG_DONTWAIT
|
|
|MSG_DONTWAIT
|
|
#endif
|
|
#ifdef MSG_NOSIGNAL
|
|
|MSG_NOSIGNAL
|
|
#endif
|
|
);
|
|
return;
|
|
}
|
|
_diag_exit(status);
|
|
}
|
|
|
|
static void _diag_exit(int status) {
|
|
Exit(status);
|
|
}
|
|
|
|
|
|
/* a function that appears to the application like select() but that also
|
|
monitors the diag socket diag_sock_recv and processes its messages.
|
|
Do not call from within a signal handler. */
|
|
int diag_select(int nfds, fd_set *readfds, fd_set *writefds,
|
|
fd_set *exceptfds, struct timeval *timeout) {
|
|
int result;
|
|
fd_set save_readfds, save_writefds, save_exceptfds;
|
|
|
|
if (readfds) { memcpy(&save_readfds, readfds, sizeof(*readfds)); }
|
|
if (writefds) { memcpy(&save_writefds, writefds, sizeof(*writefds)); }
|
|
if (exceptfds) { memcpy(&save_exceptfds, exceptfds, sizeof(*exceptfds)); }
|
|
|
|
while (1) {
|
|
FD_SET(diag_sock_recv, readfds);
|
|
result = Select(nfds, readfds, writefds,
|
|
exceptfds, timeout);
|
|
if (!FD_ISSET(diag_sock_recv, readfds)) {
|
|
/* select terminated not due to diag_sock_recv, normalt continuation */
|
|
break;
|
|
}
|
|
diag_flush();
|
|
if (readfds) { memcpy(readfds, &save_readfds, sizeof(*readfds)); }
|
|
if (writefds) { memcpy(writefds, &save_writefds, sizeof(*writefds)); }
|
|
if (exceptfds) { memcpy(exceptfds, &save_exceptfds, sizeof(*exceptfds)); }
|
|
}
|
|
return result;
|
|
}
|
|
|