diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2007-10-23 13:02:08 +0000 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2007-10-23 13:02:08 +0000 |
commit | 9e569139583178c7e1c35147cef3fd17493a11da (patch) | |
tree | 36defb2a0547176e1ad91b8fc653f5dc687c9da9 | |
parent | 80c8065d88f8c0621d6b690976dc675092db6509 (diff) | |
download | mailfromd-9e569139583178c7e1c35147cef3fd17493a11da.tar.gz mailfromd-9e569139583178c7e1c35147cef3fd17493a11da.tar.bz2 |
Reimplement syslog-async. Thanks Simon Kelley for relicensing it under GPLv3.
git-svn-id: file:///svnroot/mailfromd/trunk@1520 7a8a7f39-df28-0410-adc6-e0d955640f24
-rw-r--r-- | ChangeLog | 7 | ||||
-rw-r--r-- | NEWS | 24 | ||||
-rw-r--r-- | configure.ac | 25 | ||||
-rw-r--r-- | doc/mailfromd.texi | 83 | ||||
-rw-r--r-- | src/Makefile.am | 4 | ||||
-rw-r--r-- | src/main.c | 69 | ||||
-rw-r--r-- | src/syslog_async.c | 413 | ||||
-rw-r--r-- | src/syslog_async.h | 126 |
8 files changed, 719 insertions, 32 deletions
@@ -1,6 +1,13 @@ +2007-10-23 Sergey Poznyakoff <gray@gnu.org.ua> + + * configure.ac, src/syslog_async.c, src/syslog_async.h, + src/main.c, src/Makefile.am: Reimplement syslog-async. Thanks + Simon Kelley for relicensing it under GPLv3. + * doc/mailfromd.texi, NEWS: Update + 2007-10-21 Sergey Poznyakoff <gray@gnu.org.ua> * src/dnsbase.c (cname_loop_body): Return TXT only if explicitly required. Bug reported by Jan Rafaj. * src/engine.c: Fix timeout calculations @@ -1,40 +1,52 @@ -Mailfromd NEWS -- history of user-visible changes. 2007-09-11 +Mailfromd NEWS -- history of user-visible changes. 2007-10-23 Copyright (C) 2005, 2006, 2007 Sergey Poznyakoff See the end of file for copying conditions. Please send mailfromd bug reports to <bug-mailfromd@gnu.org.ua> -Version 4.1.1 (SVN) +Version 4.2 (SVN) * Licensed under the GPLv3 -* Syslog-async code has been withdrawn, due to its incompatibility with -the new license. The similar functionality will probably be introduced -again when I write it. +* New command line options --syslog-async and --no-syslog-async + +These options allow to select the implementation of syslog to use +at run time. * RFC 2821 compatibility The sender verification engine used to send whitespace character between `MAIL FROM:' and `RCPT TO:' commands and their argument. This violated RFC 2821, which requires the argument to follow the command without any intermediate whitespace. It is fixed in this release. * Sample threshold for `rate' function. The `rate' function takes an optional third argument allowing to -specify the minimum number of received mails needed to obtain +specify the minimum number of received mails needed to obtain the sending rate value. The default is 2, which is probably too conservative. The following example raises it to 10: if rate($f "-" ${client_addr}, interval("1 hour"), 10) > %maxrate ... fi +* mtasim is improved + +In particular, it accepts MAIL FROM: and RCPT TO: without extra space +after the colon, as required by RFC. The milter interface is also +improved. + +* The `resolve' function ignores TXT records. + +In other words, resolve and primitive_resolve are guaranteed to return +either an A or a PTR record. + Version 4.1, 2007-06-11 * National Language Support. The program includes National Language Support. Polish and Ukrainian diff --git a/configure.ac b/configure.ac index 885a804c..e49a654a 100644 --- a/configure.ac +++ b/configure.ac @@ -13,14 +13,14 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. AC_PREREQ(2.59) m4_define([MF_VERSION_MAJOR], 4) -m4_define([MF_VERSION_MINOR], 1) -m4_define([MF_VERSION_PATCH], 1) +m4_define([MF_VERSION_MINOR], 2) +dnl m4_define([MF_VERSION_PATCH], 0) AC_INIT([mailfromd], MF_VERSION_MAJOR.MF_VERSION_MINOR[]m4_ifdef([MF_VERSION_PATCH],.MF_VERSION_PATCH), [bug-mailfromd@gnu.org.ua]) AC_CONFIG_SRCDIR([src/main.c]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_HEADERS([config.h]) @@ -121,15 +121,28 @@ fi AC_SUBST(lisp_LISP) # Gettext. AM_GNU_GETTEXT([external], [need-formatstring-macros]) AM_GNU_GETTEXT_VERSION([0.16]) -# Syslog -- removed due to version incompatibility -if test "${enable_syslog_async+set}" = set; then - AC_MSG_WARN([The option --enable-syslog-async is no longer supported.]) +# Syslog +AC_ARG_ENABLE([syslog-async], + AC_HELP_STRING([--enable-syslog-async], + [use non-blocking version of syslog by default]), + [case "${enableval}" in + yes) syslog_async=yes ;; + no) syslog_async=no ;; + *) AC_MSG_ERROR([bad value ${enableval} for --enable-syslog-async]) ;; + esac],[syslog_async=no]) + +AH_TEMPLATE([DEFAULT_SYSLOG_ASYNC], + [Define to 1 if syslog-async is being used by default]) +if test $syslog_async = "yes"; then + AC_DEFINE([DEFAULT_SYSLOG_ASYNC], [1]) +else + AC_DEFINE([DEFAULT_SYSLOG_ASYNC], [0]) fi # Check for DBM flavor AH_TEMPLATE(BDB2_CURSOR_LASTARG, [Last argument to the cursor member of Berkeley 2 DB structure]) @@ -557,13 +570,13 @@ DBM version............................... $status_dbm Default user.............................. $user State directory........................... $stat_dir Socket.................................... $socket Expiration interval....................... $expire Negative DNS answer expiration interval... $negative_dns_expire Rates expire interval..................... $rates_expire -Syslog implementation..................... $syslog_flavor +Default syslog implementation............. $syslog_flavor Readline (for mtasim)..................... $usereadline Documentation rendition type.............. $rendition ******************************************************************* EOF ], diff --git a/doc/mailfromd.texi b/doc/mailfromd.texi index 078f722a..6aa44c6b 100644 --- a/doc/mailfromd.texi +++ b/doc/mailfromd.texi @@ -128,12 +128,13 @@ Introduction to @command{mailfromd} Sender Address Verification. * Limitations:: Building the Package +* 410-420:: Upgrading from 4.1 to 4.2 * 400-410:: Upgrading from 4.0 to 4.1 * 31x-400:: Upgrading from 3.1.x to 4.0 * 30x-31x:: Upgrading from 3.0.x to 3.1 * 2x-30x:: Upgrading from 2.x to 3.0.x * 1x-2x:: Upgrading from 1.x to 2.x @@ -882,37 +883,41 @@ mail rate database (@pxref{rate}). Expiration settings can be changed at run time using @samp{#pragma database} statement in the filter script file (@pxref{database}). @cindex enable-syslog-async, @option{--enable-syslog-async}, @command{configure} option @cindex syslog, non-blocking -@item Select @command{syslog} implementation to use. +@item Select a @command{syslog} implementation to use. @anchor{syslog-async} @command{Mailfromd} uses @code{syslog} for diagnostics output. The default @code{syslog} implementation on most systems (most notably, on GNU/Linux) uses blocking @code{AF_UNIX SOCK_DGRAM} sockets. As a result, when an application calls @code{syslog()}, and @command{syslogd} is not responding and the socket buffers get full, the application will hang. @cindex Simon Kelley - In the case of a daemon as @command{mailfromd}, it is more important + For @command{mailfromd}, as for any daemon, it is more important that it continue to run, than that it continue to log. For this purpose, @command{mailfromd} is shipped with a non-blocking @code{syslog} implementation by Simon Kelley. This implementation, instead of blocking, buffers log lines in memory. When the buffer log overflows, some lines are lost, but the daemon continues to run. When lines are lost, this fact is logged with a message of the form: @smallexample async_syslog overflow: 5 log entries lost @end smallexample - To enable non-blocking @command{syslog}, use the -@option{--enable-syslog-async} command line option: + You can select which implementation to run when starting +@command{mailfromd}, by using @option{--syslog-async} or +@option{--no-syslog-async} command line options +(@pxref{Logging and Debugging}). When configuring, you can set +the asynchronous syslog as the @emph{default} syslog implementation, +using the @option{--enable-syslog-async} command line option: @smallexample ./configure --enable-syslog-async @end smallexample @item Run @command{configure} with all the desired options. @@ -942,13 +947,13 @@ Default user.............................. mail State directory........................... $(localstatedir)/$(PACKAGE) Socket.................................... mailfrom Expiration interval....................... 86400 Negative DNS answer expiration interval... 3600 Rates expire interval..................... 300 -Syslog implementation..................... blocking +Default syslog implementation............. blocking Readline (for mtasim)..................... yes Documentation rendition type.............. PROOF ******************************************************************* @end group @end smallexample @@ -966,19 +971,36 @@ and mode. (@file{@var{sysconfdir}/mailfromd.etc}) and edit it, if necessary. If you are upgrading from an older version of @command{mailfromd}, see the corresponding section below. @end enumerate @menu +* 410-420:: Upgrading from 4.1 to 4.2 * 400-410:: Upgrading from 4.0 to 4.1 * 31x-400:: Upgrading from 3.1.x to 4.0 * 30x-31x:: Upgrading from 3.0.x to 3.1 * 2x-30x:: Upgrading from 2.x to 3.0.x * 1x-2x:: Upgrading from 1.x to 2.x @end menu +@node 410-420 +@section Upgrading from 4.1 to 4.2 +@cindex Upgrading from 4.1 to 4.2 +@UNREVISED{} + Upgrading to this version does not require any special efforts. You +can use your configuration files and filter scripts without any +changes. The only difference worth noticing is that starting from this +version @command{mailfromd} is always compiled with asynchronous +syslog implementation. The @option{--enable-syslog-async} +configuration file option is still available, but its meaning has +changed: it sets the @emph{default} syslog implementation to use +(@pxref{syslog-async}). Thus, it can be used the same way it was in +previous versions. You can also select the syslog implementation at +run time, see @ref{Logging and Debugging, --syslog-async option}, for +more detailed information. + @node 400-410 @section Upgrading from 4.0 to 4.1 @cindex Upgrading from 4.0 to 4.1 Upgrading to this version does not require any special efforts. You can use your configuration files and filter scripts without any changes. @@ -1008,13 +1030,13 @@ Pragma options: @code{retry}, @code{io-retry}, and @node 31x-400 @section Upgrading from 3.1.x to 4.0 @cindex upgrading from 3.1.x to 4.0 Before building this version, please re-read the chapter -@xref{Building}, especially the section @xref{syslog-async, Using +@xref{Building}, especially the section @ref{syslog-async, Using non-blocking syslog}. Starting from the version 4.0, @acronym{MFL} no longer uses the predefined symbolic names for exception codes (previous versions used the @samp{&} prefix to dereference them). Instead, it relies on constants defined in the include file @file{status.mfh} @@ -2153,21 +2175,21 @@ representing the average number of messages per second sent by this the sender email address can be used as a @code{key}, however we recommend to use a conjunction @var{email}-@var{sender_ip} instead, so the actual @var{email} owner won't be blocked by actions of some spammer abusing his/her address. To control and update sending rates, the @code{rate} function is -provided. It takes two arguments: @code{key}, whose meaning is -described above, and @code{interval}, or the number of seconds, to +provided. It takes two mandatory arguments: @code{key}, whose meaning +is described above, and @code{interval}, or the number of seconds, to which the actual sending rate value is converted. Remember, that it is stored internally as a floating point number, and thus cannot be used in @command{mailfromd} filters, which operate only on integer numbers. To use the rate value, it is first converted to messages per given interval, which is an integer number. For example, the rate @code{0.138888} brought to 1-hour interval gives @code{500} -(messages per hour). +(messages per hour). Wherever the @code{rate} function is called, it recomputes and updates the rate record for the given @var{key}, and returns its value, converted to messages per interval. For example, the following code limits the mail sending rate for each @samp{email address}-@samp{@acronym{IP}} combination to 180 per hour. If the @@ -2213,12 +2235,13 @@ with the given key value were detected: @smallexample rate($f "-" $@{client_addr@}, interval("1 hour"), 10) @end smallexample For additional information about @code{rate} function, see @ref{rate}. + @node Greylisting @section Greylisting Greylisting is a simple method of defending against the spam proposed by Evan Harris. In few words, it consists in recording the @samp{sender @acronym{IP}}-@samp{sender email}-@samp{recipient email} triplet of @@ -2417,13 +2440,13 @@ see a similar output: version: @value{VERSION} script file: /etc/mailfromd.rc user: mail statedir: /var/run/mailfromd socket: unix:/var/run/mailfromd/mailfrom pidfile: /var/run/mailfromd/mailfromd.pid -syslog: blocking +default syslog: blocking database format: Berkeley DB 2.x dns database: /var/run/mailfromd/dns.db dns negative expiration: 3600 cache database: /var/run/mailfromd/mailfromd.db cache expiration: 86400 cache negative expiration: 43200 @@ -2827,12 +2850,30 @@ standard error is used if the program is given one of the database management options (@pxref{Databases}, or @option{--test} option (@pxref{Testing Filter Scripts}. Otherwise, syslog is used. To alter these defaults, two command line options are provided: @option{--stderr} to print everything to standard error and @option{--syslog} to output all diagnostics to syslog. +@cindex syslog, default implementation +@cindex syslog, non-blocking +@cindex syslog, asynchronous +@cindex asynchronous syslog +@cindex non-blocking syslog +@xopindex{syslog-async, introduced} +@xopindex{no-syslog-async, introduced} + The data can be sent to syslog using two ways: the @code{syslog} +function from the system @file{libc} library, which is a @dfn{blocking} +implementation in most cases, and the internal, @dfn{asynchronous} +syslog implementation. @xref{syslog-async, Using non-blocking syslog}, +for more information on these implementations and for information on +how to select the default one. Two options are provided to select the +implementation to use at run time: the @option{--syslog-async} option +instructs @command{mailfromd} to use asynchronous version, and the +@option{--no-syslog-async} option instructs it to use the blocking +version. + @cindex syslog facility, selecting @cindex selecting syslog facility @cindex syslog facility, default @cindex default syslog facility The default syslog facility is @samp{mail}, and it can be changed by @option{--log-facility} option. Argument to this option is a valid @@ -10407,12 +10448,23 @@ debug the protocol. Output logs to syslog @var{facility}. @opsummary{log-tag} @item --log-tag=@var{string} Tag syslog entries with the given @var{string}, instead of the program name. +@opsummary{no-syslog-async} +@item --no-syslog-async +Use system @command{libc} syslog implementation. @xref{Logging and Debugging}, +for more information about two syslog flavors, and see @ref{syslog-async, +Using non-blocking syslog}, for information on how to set default +syslog implementation at compile time. + +To inspect the default syslog implementation, use the +@option{--show-defaults} command line option (@pxref{Databases, +show-defaults}). + @opsummary{stderr} @item -s @itemx --stderr Log to stderr (by default logging goes to syslog). @xref{Logging and Debugging}. @@ -10457,12 +10509,23 @@ channel. @xref{Logging and Debugging}. @opsummary{syslog} @item --syslog Output logs to syslog. This is the default, unless any of the following options is used: @option{--compact}, @option{--delete}, @option{--expire}, @option{--test}, and @option{--list}. +@opsummary{syslog-async} +@item --syslog-async +Use asynchronous syslog implementation. @xref{Logging and Debugging}, +for more information about two syslog flavors, and see @ref{syslog-async, +Using non-blocking syslog}, for information on how to set default +syslog implementation at compile time. + +To inspect the default syslog implementation, use the +@option{--show-defaults} command line option (@pxref{Databases, +show-defaults}). + @opsummary{xref} @item --xref Same as @option{--dump-xref}. @xref{Logging and Debugging}. @end table diff --git a/src/Makefile.am b/src/Makefile.am index fe86d2b5..ea504d53 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -60,17 +60,19 @@ mailfromd_SOURCES = \ rate.c\ $(M4_FILES:.m4=.c) noinst_LIBRARIES=libmf.a libmf_a_SOURCES=\ + syslog_async.c\ + syslog_async.h\ version.c libmf_a_LIBADD=$(LIBOBJS) -mailfromd_LDADD = ./libmf.a $(LDADD) $(SYSLOG_LIBS) +mailfromd_LDADD = ./libmf.a $(LDADD) mtasim_SOURCES = mtasim.c openat-die.c mtasim_LDADD = ./libmf.a $(LDADD) $(READLINE_LIBS) noinst_HEADERS = mailfromd.h mu_dbm.h builtin.h dns.h spf.h drivers.c debug.h EXTRA_DIST = \ @@ -36,12 +36,14 @@ #include <mailutils/mailutils.h> #include <mailutils/argp.h> #include "mailfromd.h" +#include "syslog_async.h" + /* Configurable options */ int mode = MAILFROMD_DAEMON; /* Default operation mode */ enum smtp_state test_state = smtp_state_envfrom; /* State for --test mode */ int need_config = 1;/* Set if the current mode requires reading the @@ -73,12 +75,14 @@ char *portspec; /* Communication socket specification. int force_remove; /* Remove local communication socket if it already exists */ int foreground; /* Stay in foreground */ int single_process_option; /* Run in single process mode. */ unsigned long source_address = INADDR_ANY; /* Source address for TCP connections */ +int use_syslog_async = DEFAULT_SYSLOG_ASYNC; + /* Use asynchronous syslog implementation */ char *syslog_tag; /* Tag to mark syslog entries with. */ char *mailfromd_state_dir; /* see init_names() */ char *pidfile; /* see init_names() */ char *user = DEFAULT_USER; /* Switch to this user privileges after startup */ mu_list_t retain_groups; /* List of group IDs to retain when switching @@ -118,22 +122,50 @@ time_t response_timeout = 30; /* Logging & debugging */ int syslog_printer (int prio, const char *fmt, va_list ap) { + if (use_syslog_async) { + vsyslog_async (prio, fmt, ap); + } else { #if HAVE_VSYSLOG - vsyslog (prio, fmt, ap); + vsyslog (prio, fmt, ap); #else - char buf[128]; - vsnprintf (buf, sizeof buf, fmt, ap); - syslog (prio, "%s", buf); + char buf[128]; + vsnprintf (buf, sizeof buf, fmt, ap); + syslog (prio, "%s", buf); #endif + } return 0; } +void +mf_gacopyz_syslog_async_log_printer(int level, char *fmt, va_list ap) +{ + switch (level) { + case SMI_LOG_DEBUG: + level = LOG_DEBUG; + break; + case SMI_LOG_INFO: + level = LOG_INFO; + break; + case SMI_LOG_WARN: + level = LOG_WARNING; + break; + case SMI_LOG_ERR: + level = LOG_ERR; + break; + + case SMI_LOG_FATAL: + default: + level = LOG_EMERG; + } + vsyslog_async(level, fmt, ap); +} + int syslog_error_printer (const char *fmt, va_list ap) { return syslog_printer(LOG_ERR, fmt, ap); } @@ -782,22 +814,24 @@ enum mailfromd_option { OPTION_LIST, OPTION_LOCK_RETRY_COUNT, OPTION_LOCK_RETRY_TIMEOUT, OPTION_MILTER_TIMEOUT, OPTION_MTASIM, OPTION_NO_PREPROCESSOR, + OPTION_NO_SYSLOG_ASYNC, OPTION_PIDFILE, OPTION_POSTMASTER_EMAIL, OPTION_PREDICT_NEXT, OPTION_PREPROCESSOR, OPTION_SHOW_DEFAULTS, OPTION_SINGLE_PROCESS, OPTION_STACK_TRACE, OPTION_STATE_DIRECTORY, OPTION_SOURCE_INFO, OPTION_SYSLOG, + OPTION_SYSLOG_ASYNC, OPTION_TIME_FORMAT, OPTION_TIMEOUT, OPTION_TRACE, OPTION_TRACE_PROGRAM, }; @@ -941,12 +975,16 @@ static struct argp_option options[] = { { "gacopyz-log", OPTION_GACOPYZ_LOG, N_("LEVEL"), 0, N_("Set Gacopyz log level"), GRP+1 }, { "stderr", 's', NULL, 0, N_("Log to stderr"), GRP+1 }, { "syslog", OPTION_SYSLOG, NULL, 0, N_("Log to syslog (default)"), GRP+1 }, + { "syslog-async", OPTION_SYSLOG_ASYNC, NULL, 0, + N_("Use asynchronous syslog"), GRP+1 }, + { "no-syslog-async", OPTION_NO_SYSLOG_ASYNC, NULL, 0, + N_("Use system syslog"), GRP+1 }, { "log-tag", OPTION_LOG_TAG, N_("STRING"), 0, N_("Set the identifier used in syslog messages to STRING"), GRP+1 }, { "source-info", OPTION_SOURCE_INFO, NULL, 0, N_("Debug messages include source information"), GRP+1 }, { "stack-trace", OPTION_STACK_TRACE, NULL, 0, N_("Enable stack traces on runtime errors"), GRP+1 }, @@ -1260,12 +1298,20 @@ parse_opt (int key, char *arg, struct argp_state *state) break; case OPTION_SYSLOG: log_to_stderr = 0; break; + case OPTION_SYSLOG_ASYNC: + use_syslog_async = 1; + break; + + case OPTION_NO_SYSLOG_ASYNC: + use_syslog_async = 0; + break; + case OPTION_TIMEOUT: set_option("timeout", arg, 1); break; case OPTION_TRACE: do_trace = 1; @@ -1653,16 +1699,16 @@ mailfromd_show_defaults() printf("script file: %s\n", script_file); printf("preprocessor: %s\n", ext_pp ? ext_pp : "none"); printf("user: %s\n", user); printf("statedir: %s\n", mailfromd_state_dir); printf("socket: %s\n", portspec); printf("pidfile: %s\n", pidfile); -#ifdef USE_SYSLOG_ASYNC - printf("syslog: non-blocking\n"); +#if DEFAULT_SYSLOG_ASYNC == 1 + printf("default syslog: non-blocking\n"); #else - printf("syslog: blocking\n"); + printf("default syslog: blocking\n"); #endif printf("database format: "); #if defined WITH_GDBM printf("GDBM"); #elif defined WITH_BDB printf("Berkeley DB %d.x", WITH_BDB); @@ -1674,14 +1720,19 @@ mailfromd_show_defaults() void log_setup(int want_stderr) { /* Set up logging */ if (!want_stderr) { - openlog(syslog_tag, LOG_PID, log_facility); - gacopyz_set_logger(gacopyz_syslog_log_printer); + if (use_syslog_async) { + openlog_async(syslog_tag, LOG_PID, log_facility); + gacopyz_set_logger(mf_gacopyz_syslog_async_log_printer); + } else { + openlog(syslog_tag, LOG_PID, log_facility); + gacopyz_set_logger(gacopyz_syslog_log_printer); + } mu_error_set_print(syslog_error_printer); } else { gacopyz_set_logger(gacopyz_stderr_log_printer); mu_error_set_print(stderr_error_printer); } } diff --git a/src/syslog_async.c b/src/syslog_async.c new file mode 100644 index 00000000..a168eac9 --- /dev/null +++ b/src/syslog_async.c @@ -0,0 +1,413 @@ +/* syslog_async is Copyright (c) 2007 Simon Kelley + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/file.h> +#include <sys/syslog.h> + +#include <sys/uio.h> +#include <sys/wait.h> +#include <netdb.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <errno.h> +#include <stdarg.h> +#include <stdlib.h> +#include <paths.h> +#include <stdio.h> +#include <ctype.h> + +#include "syslog_async.h" + +/* From RFC 3164 */ +#define MAX_MESSAGE 1024 + +#define DEF_BACKLOG 5 +#define DEF_DELAY 1000 /* doesn't come into effect until backlog > 10 */ + +static int log_fac = LOG_USER; +static int log_opts = LOG_ODELAY; +static const char *log_tag = "syslog"; +static int log_mask = 0xff; +static int log_backlog = DEF_BACKLOG; +static int log_delay = DEF_DELAY; + +static int log_fd = -1; +static int entries_alloced = 0; +static int entries_lost = 0; +static int connection_good = 1; + +struct log_entry { + int offset, length; + struct log_entry *next; + char payload[MAX_MESSAGE]; +}; + +static struct log_entry *entries = NULL; +static struct log_entry *free_entries = NULL; + +static int mksock(int type) +{ + int flags; + int fd = socket(AF_UNIX, type, 0); + + if (fd != -1) + { + if ((flags = fcntl(fd, F_GETFL)) == -1 || + fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1 || + (flags = fcntl(fd, F_GETFD)) == -1 || + fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) + { + close(fd); + fd = -1; + } + } + return fd; +} + +void openlog_async(const char *ident, int option, int facility) +{ + if (ident) + log_tag = ident; + + log_opts = option; + + if (facility != 0 && (facility &~ LOG_FACMASK) == 0) + log_fac = facility; + + if (log_opts & LOG_NDELAY) + log_fd = mksock(SOCK_DGRAM); +} + +int setlogmask_async(int mask) +{ + int old = log_mask; + + if (mask != 0) + log_mask = mask; + + return old; +} + +void tunelog_async(int backlog, int delay) +{ + /* we need at least one buffer, and the + delay calculations overflow for more than 99 */ + if (backlog < 1) + backlog = 1; + else if (backlog > 99) + backlog = 99; + + /* don't lose existing buffers */ + if (backlog < entries_alloced) + log_backlog = entries_alloced; + else + log_backlog = backlog; + + if (delay < 0) + log_delay = 0; + else if (delay > 1000) + log_delay = 1000; + else + log_delay = delay; +} + +void closelog_async(void) +{ + /* maybe last chance to flush */ + log_write_async(); + + if (log_fd != -1) + { + close(log_fd); + log_fd = -1; + } + + /* restore defaults */ + log_fac = LOG_USER; + log_opts = LOG_ODELAY; + log_tag = "syslog"; + log_mask = 0xff; + + log_delay = DEF_DELAY; + + if (entries_alloced < DEF_BACKLOG) + log_backlog = entries_alloced; + else + log_backlog = DEF_BACKLOG; +} + +int log_fd_async(void) +{ + if (!entries || !connection_good) + return -1; + + return log_fd; +} + +void log_write_async(void) +{ + ssize_t rc; + int fd, tried_stream = 0; + struct log_entry *tmp; + + while (entries) + { + if (log_fd == -1 && + (log_fd = mksock(SOCK_DGRAM)) == -1) + goto fail; + + connection_good = 1; + + if ((rc = send(log_fd, + entries->payload + entries->offset, + entries->length, + MSG_NOSIGNAL)) != -1) + { + entries->length -= rc; + entries->offset += rc; + connection_good = 1; + + if (entries->length == 0) + goto free; + + continue; + } + + if (errno == EINTR) + continue; + + if (errno == EAGAIN) + return; + + /* *BSD, returns this instead of blocking? */ + if (errno == ENOBUFS) + { + connection_good = 0; + return; + } + + /* A stream socket closed at the other end goes into EPIPE + forever, close and re-open. */ + if (errno == EPIPE) + goto reopen_stream; + + if (errno == ECONNREFUSED || + errno == ENOTCONN || + errno == EDESTADDRREQ || + errno == ECONNRESET) + { + /* socket went (syslogd down?), try and reconnect. If we fail, + stop trying until the next call to my_syslog() + ECONNREFUSED -> connection went down + ENOTCONN -> nobody listening + (ECONNRESET, EDESTADDRREQ are *BSD equivalents) */ + + struct sockaddr_un logaddr; + + logaddr.sun_family = AF_LOCAL; + strncpy(logaddr.sun_path, _PATH_LOG, sizeof(logaddr.sun_path)); + + /* Got connection back? try again. */ + if (connect(log_fd, (struct sockaddr *)&logaddr, sizeof(logaddr)) != -1) + continue; + + /* errors from connect which mean we should keep trying */ + if (errno == ENOENT || + errno == EALREADY || + errno == ECONNREFUSED || + errno == EISCONN || + errno == EINTR || + errno == EAGAIN) + { + /* try again on next syslog() call */ + connection_good = 0; + return; + } + + /* we start with a SOCK_DGRAM socket, but syslog may want SOCK_STREAM */ + if (!tried_stream && errno == EPROTOTYPE) + { + reopen_stream: + tried_stream = 1; + close(log_fd); + if ((log_fd = mksock(SOCK_STREAM)) != -1) + continue; + } + } + + fail: + tried_stream = 0; + + /* give up - try to write to console if we've been asked + take care not to block in open() or write() */ + if ((log_opts & LOG_CONS) && + (fd = open(_PATH_CONSOLE, O_WRONLY | O_NONBLOCK, 0)) != -1) + { + char *start = strchr(entries->payload, '>') + 1; + int flags = fcntl(fd, F_GETFL); + + if (flags != -1) + fcntl(fd, F_SETFL, flags | O_NONBLOCK); + + entries->length -= start - entries->payload; + /* move down to remove the tag, and make room for the \r\n */ + memmove(entries->payload, start, entries->length); + entries->payload[entries->length - 1] = '\r'; + entries->payload[entries->length] = '\n'; + write(fd, entries->payload, entries->length + 1); + close(fd); + } + + free: + tmp = entries; + entries = tmp->next; + tmp->next = free_entries; + free_entries = tmp; + + if (entries_lost != 0) + { + int e = entries_lost; + entries_lost = 0; /* avoid wild recursion */ + syslog_async(LOG_WARNING, "async_syslog overflow: %d log entries lost", e); + } + continue; + } +} + +void syslog_async(int priority, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vsyslog_async(priority, format, ap); + va_end(ap); +} + +void vsyslog_async(int priority, const char *format, va_list ap) +{ + struct log_entry *entry; + time_t time_now; + char *p, *q, *r; + size_t len; + + if (!(log_mask & LOG_MASK(LOG_PRI(priority))) || (priority &~ (LOG_PRIMASK|LOG_FACMASK))) + return; + + if ((entry = free_entries)) + free_entries = entry->next; + else if (entries_alloced < log_backlog && (entry = malloc(sizeof(struct log_entry)))) + entries_alloced++; + + if (!entry) + entries_lost++; + else + { + /* add to end of list, consumed from the start */ + entry->next = NULL; + if (!entries) + entries = entry; + else + { + struct log_entry *tmp; + for (tmp = entries; tmp->next; tmp = tmp->next); + tmp->next = entry; + } + + time(&time_now); + p = entry->payload; + p += sprintf(p, "<%d>", priority | log_fac); + + q = p; + + if (log_opts & LOG_PID) + p += sprintf(p, "%.15s %s[%d]: ", ctime(&time_now) + 4, log_tag, getpid()); + else + p += sprintf(p, "%.15s %s: ", ctime(&time_now) + 4, log_tag); + + len = p - entry->payload; + len += vsnprintf(p, MAX_MESSAGE - len, format, ap) + 1; /* include zero-terminator */ + entry->length = len > MAX_MESSAGE ? MAX_MESSAGE : len; + + /* remove trailing '\n's passed to us. */ + for (r = &entry->payload[entry->length - 2]; r >= ent |