NAME

syslogd - receive syslog messages over UDP and write them to a CSV file

SYNOPSIS

/usr/local/etc/syslogd [--port 514] [--address 0.0.0.0] [--file /var/log/syslog/syslog.csv]
	[--no-resolve] [--language en]

# Without root, on a port above 1023, logging addresses only
/usr/local/etc/syslogd --port 5514 --file /var/log/remote.csv --no-resolve

# Under taint mode: the module must be installed or named with -I
perl -T -I/usr/local/lib /usr/local/etc/syslogd --port 5514

DESCRIPTION

A small wrapper around App::Syslogd. It turns the command line into options, opens the socket and the log, prints a "listening" line, and runs until SIGTERM or SIGINT. Then it prints how many messages it recorded.

Purpose

Run a syslog server from the command line.

Arguments

The command-line options below. Options that are not given are not passed on, so the defaults of App::Syslogd apply.

--port N - UDP port (a whole number, 0 to 65535; default 514).
--address A - local address to listen on (default 0.0.0.0).
--file F - the CSV log file (default /var/log/syslog/syslog.csv; the directory must exist, and on Debian and Ubuntu /var/log/syslog is a file, so give --file there).
--resolve / --no-resolve - write host names (the default) or addresses.
--language TAG - the language of the program's messages.

Returns

The exit status: 0 after a clean stop (SIGTERM or SIGINT); 1 if the server could not start or failed while running; 2 if it was started wrongly (see "EXIT STATUS").

Side Effects

Binds a UDP socket; creates or appends to the log file (mode 0600); prints a "listening" line and, on stopping, a "shutting down" line on standard output. It never reads standard input.

Usage

/usr/local/etc/syslogd --port 5514 --file /var/log/remote.csv

API SPECIFICATION

INPUT

The program takes no HTTP input. It reads no QUERY_STRING, PATH_INFO, cookies, other HTTP_* variables or request body, and if GATEWAY_INTERFACE or REQUEST_METHOD is set (it was started by a web server) it refuses to run. Its inputs are the command line:

{
	port => { type => 'integer', min => 0, max => 65535, optional => 1 },
	address => { type => 'string', min => 1, optional => 1 },
	file => { type => 'string', min => 1, optional => 1 },
	resolve => { type => 'boolean', optional => 1 },
	language => { type => 'string', min => 1, optional => 1 },
}

and the environment that App::Syslogd reads (App__Syslogd__*, LANG, LANGUAGE, LC_*).

OUTPUT

{
	exit_status => { type => 'integer', min => 0, max => 2 },
	stdout => { type => 'string', matches => qr/\ASyslog server listening on .+\n(?:Syslog server shutting down after recording .+\n)?\z/ },
}

FORMAL SPECIFICATION

[OPTION, VALUE, LINE]
argv? : seq STRING ; env? : NAME ⇸ VALUE
out! : seq LINE ; status! : 0 .. 2

SyslogdOk
  argv?, env?, out!, status!
  ─────────
  GATEWAY_INTERFACE ¬in; dom env? ∧ REQUEST_METHOD ¬in; dom env?
  parses(argv?)
  status! = 0
  out! = ⟨listening(address, port), shutdown(count)⟩

SyslogdRefused
  argv?, env?, out!, status!
  ─────────
  (GATEWAY_INTERFACE ∈ dom env? ∨ REQUEST_METHOD ∈ dom env?) ∨
  ¬ parses(argv?) ∨ ¬ startable(argv?, env?)
  out! = ⟨⟩
  status! = 2 ⇔ (GATEWAY_INTERFACE ∈ dom env? ∨ REQUEST_METHOD ∈ dom env? ∨ ¬ parses(argv?))
  status! = 1 ⇔ ¬ (status! = 2)

Syslogd ≙ SyslogdOk ∨ SyslogdRefused

-- Running as CGI is decided before the command line is read:
CgiFirst ≙ (GATEWAY_INTERFACE ∈ dom env? ∨ REQUEST_METHOD ∈ dom env?)
             ⇒ ¬ parsed ∧ ¬ bound ∧ ¬ logging

EXIT STATUS

+--------+----------------------------------------------------------------+
| Status | Meaning                                                        |
+--------+----------------------------------------------------------------+
| 0      | stopped cleanly by SIGTERM or SIGINT                           |
| 1      | could not start (the socket or the log could not be opened, a  |
|        | setting was invalid) or failed while running                   |
| 2      | started wrongly: a bad option (the usage message), or by a web |
|        | server                                                         |
+--------+----------------------------------------------------------------+

Errors that stop Perl before the program runs (for example, App::Syslogd cannot be found) are reported by Perl itself, with Perl's own status.

SIGNALS

SIGHUP reopens the log file (for log rotation); SIGTERM and SIGINT stop the server.

ENVIRONMENT

App__Syslogd__port, App__Syslogd__file and the other App__Syslogd__* variables set options, and win over the command line (see "new" in App::Syslogd). LANG, LANGUAGE and LC_* choose the language. GATEWAY_INTERFACE or REQUEST_METHOD make the program refuse to run.

SECURITY

  • It refuses to run from a web server: as a CGI program a query without "=" becomes its command line (RFC 3875, "ISINDEX"), so any visitor could start a listening server writing where they chose.

  • It never runs another program, and passes file names straight to the system, never to a shell.

  • It only adds to an empty file or to one of its own logs (a file that starts with the column-names line), so even as root a mistaken or hostile --file cannot append to, or change the permissions of, a file such as /etc/passwd.

  • Without -T it loads App::Syslogd from ../lib next to itself (/usr/local/lib once installed): keep that directory writable only by root. With -T that path is not used, because it is built from $0; the module must then be installed or named with -I. Under -T option values from the command line or the environment are tainted, so a --file is refused by taint mode.

  • Getopt::Long's "Unknown option" warning and the usage message show the option name and $0 as given; both come from whoever starts the program, so they cannot carry anyone else's text.

See also "SECURITY" in App::Syslogd.

AUTHOR

Nigel Horne, <njh at nigelhorne.com>

LICENSE AND COPYRIGHT

Copyright 2026 Nigel Horne.

This program is released under the GNU General Public License, version 2 (see the LICENSE file). If you use it, please let me know.