Skip to content

Commit 97e273d

Browse files
authored
Merge pull request #76 from CESNET/hutak-syslog
Hutak syslog
2 parents 36bab73 + b1755dd commit 97e273d

File tree

8 files changed

+1295
-0
lines changed

8 files changed

+1295
-0
lines changed

src/plugins/output/json/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ add_library(json-output MODULE
1515
src/Server.hpp
1616
src/Sender.cpp
1717
src/Server.hpp
18+
src/Syslog.cpp
19+
src/Syslog.hpp
20+
src/SyslogSocket.cpp
21+
src/SyslogSocket.hpp
1822
)
1923

2024
find_package(LibRDKafka 0.9.3 REQUIRED)

src/plugins/output/json/src/Config.cpp

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
*
4040
*/
4141

42+
#include <algorithm>
4243
#include <cstdio>
4344
#include <memory>
4445
#include <set>
@@ -52,6 +53,16 @@
5253

5354
#include "Config.hpp"
5455

56+
#define SYSLOG_FACILITY_MIN 0
57+
#define SYSLOG_FACILITY_MAX 23
58+
#define SYSLOG_FACILITY_DEF 16
59+
60+
#define SYSLOG_SEVERITY_MIN 0
61+
#define SYSLOG_SEVERITY_MAX 7
62+
#define SYSLOG_SEVERITY_DEF 6
63+
64+
#define SYSLOG_APPNAME_MAX_LEN 48
65+
5566
/** XML nodes */
5667
enum params_xml_nodes {
5768
// Formatting parameters
@@ -73,6 +84,7 @@ enum params_xml_nodes {
7384
OUTPUT_SERVER, /**< Provide as server */
7485
OUTPUT_FILE, /**< Store to file */
7586
OUTPUT_KAFKA, /**< Store to Kafka */
87+
OUTPUT_SYSLOG, /**< Store to syslog */
7688
// Standard output
7789
PRINT_NAME, /**< Printer name */
7890
// Send output
@@ -103,6 +115,22 @@ enum params_xml_nodes {
103115
KAFKA_PROPERTY, /**< Additional librdkafka property */
104116
KAFKA_PROP_KEY, /**< Property key */
105117
KAFKA_PROP_VALUE, /**< Property value */
118+
// Syslog output
119+
SYSLOG_NAME, /**< Name of the output */
120+
SYSLOG_PRI, /**< Priority */
121+
SYSLOG_PRI_FACILITY, /**< Priority facility */
122+
SYSLOG_PRI_SEVERITY, /**< Priority severity */
123+
SYSLOG_HOSTNAME, /**< Hostname */
124+
SYSLOG_PROGRAM, /**< Application name */
125+
SYSLOG_PROCID, /**< Application PID */
126+
SYSLOG_TRANSPORT, /**< Transport configuration */
127+
SYSLOG_TCP, /**< TCP socket configuration */
128+
SYSLOG_TCP_HOST, /**< Destination host (TCP) */
129+
SYSLOG_TCP_PORT, /**< Destination port (TCP) */
130+
SYSLOG_TCP_BLOCK, /**< Blocking connection (TCP) */
131+
SYSLOG_UDP, /**< UDP socket configuration */
132+
SYSLOG_UDP_HOST, /**< Destination host (UDP) */
133+
SYSLOG_UDP_PORT, /**< Destination port (UDP) */
106134
};
107135

108136
/** Definition of the \<print\> node */
@@ -160,13 +188,54 @@ static const struct fds_xml_args args_kafka[] = {
160188
FDS_OPTS_END
161189
};
162190

191+
/** Definition of \<priority\> of \<syslog> node */
192+
static const struct fds_xml_args args_syslog_priority[] = {
193+
FDS_OPTS_ELEM(SYSLOG_PRI_FACILITY, "facility", FDS_OPTS_T_UINT, 0),
194+
FDS_OPTS_ELEM(SYSLOG_PRI_SEVERITY, "severity", FDS_OPTS_T_UINT, 0),
195+
FDS_OPTS_END
196+
};
197+
198+
/** Definition of \<udp\> of \<syslog>\<transport> node */
199+
static const struct fds_xml_args args_syslog_udp[] = {
200+
FDS_OPTS_ELEM(SYSLOG_UDP_HOST, "hostname", FDS_OPTS_T_STRING, 0),
201+
FDS_OPTS_ELEM(SYSLOG_UDP_PORT, "port", FDS_OPTS_T_UINT, 0),
202+
FDS_OPTS_END
203+
};
204+
205+
/** Definition of \<tcp\> of \<syslog>\<transport> node */
206+
static const struct fds_xml_args args_syslog_tcp[] = {
207+
FDS_OPTS_ELEM(SYSLOG_TCP_HOST, "hostname", FDS_OPTS_T_STRING, 0),
208+
FDS_OPTS_ELEM(SYSLOG_TCP_PORT, "port", FDS_OPTS_T_UINT, 0),
209+
FDS_OPTS_ELEM(SYSLOG_TCP_BLOCK, "blocking", FDS_OPTS_T_BOOL, 0),
210+
FDS_OPTS_END
211+
};
212+
213+
/** Definition of \<transport\> of \<syslog> node */
214+
static const struct fds_xml_args args_syslog_transport[] = {
215+
FDS_OPTS_NESTED(SYSLOG_TCP, "tcp", args_syslog_tcp, FDS_OPTS_P_OPT),
216+
FDS_OPTS_NESTED(SYSLOG_UDP, "udp", args_syslog_udp, FDS_OPTS_P_OPT),
217+
FDS_OPTS_END
218+
};
219+
220+
/** Definition of the \<syslog\> node */
221+
static const struct fds_xml_args args_syslog[] = {
222+
FDS_OPTS_ELEM(SYSLOG_NAME, "name", FDS_OPTS_T_STRING, 0),
223+
FDS_OPTS_ELEM(SYSLOG_HOSTNAME, "hostname", FDS_OPTS_T_STRING, FDS_OPTS_P_OPT),
224+
FDS_OPTS_ELEM(SYSLOG_PROGRAM, "program", FDS_OPTS_T_STRING, FDS_OPTS_P_OPT),
225+
FDS_OPTS_ELEM(SYSLOG_PROCID, "procId", FDS_OPTS_T_BOOL, FDS_OPTS_P_OPT),
226+
FDS_OPTS_NESTED(SYSLOG_PRI, "priority", args_syslog_priority, FDS_OPTS_P_OPT),
227+
FDS_OPTS_NESTED(SYSLOG_TRANSPORT, "transport", args_syslog_transport, 0),
228+
FDS_OPTS_END
229+
};
230+
163231
/** Definition of the \<outputs\> node */
164232
static const struct fds_xml_args args_outputs[] = {
165233
FDS_OPTS_NESTED(OUTPUT_PRINT, "print", args_print, FDS_OPTS_P_OPT | FDS_OPTS_P_MULTI),
166234
FDS_OPTS_NESTED(OUTPUT_SERVER, "server", args_server, FDS_OPTS_P_OPT | FDS_OPTS_P_MULTI),
167235
FDS_OPTS_NESTED(OUTPUT_SEND, "send", args_send, FDS_OPTS_P_OPT | FDS_OPTS_P_MULTI),
168236
FDS_OPTS_NESTED(OUTPUT_FILE, "file", args_file, FDS_OPTS_P_OPT | FDS_OPTS_P_MULTI),
169237
FDS_OPTS_NESTED(OUTPUT_KAFKA, "kafka", args_kafka, FDS_OPTS_P_OPT | FDS_OPTS_P_MULTI),
238+
FDS_OPTS_NESTED(OUTPUT_SYSLOG, "syslog", args_syslog, FDS_OPTS_P_OPT | FDS_OPTS_P_MULTI),
170239
FDS_OPTS_END
171240
};
172241

@@ -230,6 +299,15 @@ Config::check_or(const std::string &elem, const char *value, const std::string &
230299
+ val_true + "' or '" + val_false + "')");
231300
}
232301

302+
bool
303+
Config::is_syslog_ascii(const std::string &str)
304+
{
305+
// Only printable characters as mentioned in RFC 5424, Section 6.
306+
const auto isValid = [](char ch){ return ch >= 33 && ch <= 126; };
307+
const auto result = std::find_if_not(str.begin(), str.end(), isValid);
308+
return result == str.end();
309+
}
310+
233311
/**
234312
* \brief Parse "print" output parameters
235313
*
@@ -560,6 +638,214 @@ Config::parse_kafka(fds_xml_ctx_t *kafka)
560638
outputs.kafkas.push_back(output);
561639
}
562640

641+
std::unique_ptr<UdpSyslogSocket>
642+
Config::parse_syslog_udp(fds_xml_ctx_t *socket)
643+
{
644+
std::string hostname;
645+
uint16_t port;
646+
647+
const struct fds_xml_cont *content;
648+
while (fds_xml_next(socket, &content) != FDS_EOC) {
649+
switch (content->id) {
650+
case SYSLOG_UDP_HOST:
651+
assert(content->type == FDS_OPTS_T_STRING);
652+
hostname = content->ptr_string;
653+
break;
654+
case SYSLOG_UDP_PORT:
655+
assert(content->type == FDS_OPTS_T_UINT);
656+
if (content->val_uint > UINT16_MAX || content->val_uint == 0) {
657+
throw std::invalid_argument("Invalid port number of a <udp> syslog!");
658+
}
659+
660+
port = static_cast<uint16_t>(content->val_uint);
661+
break;
662+
default:
663+
throw std::invalid_argument("Unexpected element within <udp> syslog!");
664+
}
665+
}
666+
667+
return std::unique_ptr<UdpSyslogSocket>(new UdpSyslogSocket(hostname, port));
668+
}
669+
670+
std::unique_ptr<TcpSyslogSocket>
671+
Config::parse_syslog_tcp(fds_xml_ctx_t *socket)
672+
{
673+
std::string hostname;
674+
uint16_t port;
675+
bool blocking;
676+
677+
const struct fds_xml_cont *content;
678+
while (fds_xml_next(socket, &content) != FDS_EOC) {
679+
switch (content->id) {
680+
case SYSLOG_TCP_HOST:
681+
assert(content->type == FDS_OPTS_T_STRING);
682+
hostname = content->ptr_string;
683+
break;
684+
case SYSLOG_TCP_PORT:
685+
assert(content->type == FDS_OPTS_T_UINT);
686+
if (content->val_uint > UINT16_MAX || content->val_uint == 0) {
687+
throw std::invalid_argument("Invalid port number of a <tcp> syslog!");
688+
}
689+
690+
port = static_cast<uint16_t>(content->val_uint);
691+
break;
692+
case SYSLOG_TCP_BLOCK:
693+
assert(content->type == FDS_OPTS_T_BOOL);
694+
blocking = content->val_bool;
695+
break;
696+
default:
697+
throw std::invalid_argument("Unexpected element within <tcp> syslog!");
698+
}
699+
}
700+
701+
return std::unique_ptr<TcpSyslogSocket>(new TcpSyslogSocket(hostname, port, blocking));
702+
}
703+
704+
void
705+
Config::parse_syslog_transport(struct cfg_syslog &syslog, fds_xml_ctx_t *transport)
706+
{
707+
std::unique_ptr<SyslogSocket> socket;
708+
709+
const struct fds_xml_cont *content;
710+
while (fds_xml_next(transport, &content) != FDS_EOC) {
711+
if (socket != nullptr) {
712+
throw std::invalid_argument("Multiple syslog transport types are not allowed!");
713+
}
714+
715+
switch (content->id) {
716+
case SYSLOG_TCP:
717+
assert(content->type == FDS_OPTS_T_CONTEXT);
718+
socket = parse_syslog_tcp(content->ptr_ctx);
719+
break;
720+
case SYSLOG_UDP:
721+
assert(content->type == FDS_OPTS_T_CONTEXT);
722+
socket = parse_syslog_udp(content->ptr_ctx);
723+
break;
724+
default:
725+
throw std::invalid_argument("Unexpected element within <transport>!");
726+
}
727+
}
728+
729+
syslog.transport = std::move(socket);
730+
}
731+
732+
void
733+
Config::parse_syslog_priority(struct cfg_syslog &syslog, fds_xml_ctx_t *priority)
734+
{
735+
struct syslog_prority values;
736+
bool isFacilitySet = false;
737+
bool isSeveritySet = false;
738+
739+
const struct fds_xml_cont *content;
740+
while (fds_xml_next(priority, &content) != FDS_EOC) {
741+
switch (content->id) {
742+
case SYSLOG_PRI_FACILITY:
743+
assert(content->type == FDS_OPTS_T_UINT);
744+
values.facility = content->val_uint;
745+
isFacilitySet = true;
746+
break;
747+
case SYSLOG_PRI_SEVERITY:
748+
assert(content->type == FDS_OPTS_T_UINT);
749+
values.severity= content->val_uint;
750+
isSeveritySet = true;
751+
break;
752+
default:
753+
throw std::invalid_argument("Unexpected element within <priority>!");
754+
}
755+
}
756+
757+
if (!isFacilitySet || !isSeveritySet) {
758+
throw std::invalid_argument("Both syslog facility and severity must be set!");
759+
}
760+
761+
if (values.facility > SYSLOG_FACILITY_MAX) {
762+
std::string str_min = std::to_string(SYSLOG_FACILITY_MIN);
763+
std::string str_max = std::to_string(SYSLOG_FACILITY_MAX);
764+
std::string range = "[" + str_min + ".." + str_max + "]";
765+
throw std::invalid_argument("Syslog facility is out of range " + range);
766+
}
767+
768+
if (values.severity > SYSLOG_SEVERITY_MAX) {
769+
std::string str_min = std::to_string(SYSLOG_SEVERITY_MIN);
770+
std::string str_max = std::to_string(SYSLOG_SEVERITY_MAX);
771+
std::string range = "[" + str_min + ".." + str_max + "]";
772+
throw std::invalid_argument("Syslog severity is out of range " + range);
773+
}
774+
775+
syslog.priority = values;
776+
}
777+
778+
/**
779+
* \brief Parse "syslog" output parameters
780+
*
781+
* Successfully parsed output is added to the vector of outputs
782+
* \param[in] syslog Parsed XML context
783+
* \throw invalid_argument or runtime_error
784+
*/
785+
void
786+
Config::parse_syslog(fds_xml_ctx_t *syslog)
787+
{
788+
// Prepare default values
789+
struct cfg_syslog output;
790+
output.priority.facility = SYSLOG_FACILITY_DEF;
791+
output.priority.severity = SYSLOG_SEVERITY_DEF;
792+
output.hostname = syslog_hostname::NONE;
793+
output.proc_id = false;
794+
795+
const struct fds_xml_cont *content;
796+
while (fds_xml_next(syslog, &content) != FDS_EOC) {
797+
switch (content->id) {
798+
case SYSLOG_NAME:
799+
assert(content->type == FDS_OPTS_T_STRING);
800+
output.name = content->ptr_string;
801+
break;
802+
case SYSLOG_HOSTNAME:
803+
assert(content->type == FDS_OPTS_T_STRING);
804+
if (strcasecmp(content->ptr_string, "none") == 0) {
805+
output.hostname = syslog_hostname::NONE;
806+
} else if (strcasecmp(content->ptr_string, "local") == 0) {
807+
output.hostname = syslog_hostname::LOCAL;
808+
} else {
809+
const std::string inv_str = content->ptr_string;
810+
throw std::invalid_argument("Unknown syslog hostname type '" + inv_str + "'");
811+
}
812+
break;
813+
case SYSLOG_PROGRAM:
814+
assert(content->type == FDS_OPTS_T_STRING);
815+
output.program = content->ptr_string;
816+
break;
817+
case SYSLOG_PROCID:
818+
assert(content->type == FDS_OPTS_T_BOOL);
819+
output.proc_id = content->val_bool;
820+
break;
821+
case SYSLOG_PRI:
822+
assert(content->type == FDS_OPTS_T_CONTEXT);
823+
parse_syslog_priority(output, content->ptr_ctx);
824+
break;
825+
case SYSLOG_TRANSPORT:
826+
assert(content->type == FDS_OPTS_T_CONTEXT);
827+
parse_syslog_transport(output, content->ptr_ctx);
828+
break;
829+
default:
830+
throw std::invalid_argument("Unexpected element within <syslog>!");
831+
}
832+
}
833+
834+
if (!output.transport) {
835+
throw std::invalid_argument("Syslog transport type must be defined!");
836+
}
837+
838+
if (!is_syslog_ascii(output.program)) {
839+
throw std::invalid_argument("Invalid syslog identifier '" + output.name + "'");
840+
}
841+
842+
if (output.program.size() > SYSLOG_APPNAME_MAX_LEN) {
843+
throw std::invalid_argument("Too long syslog identifier '" + output.name + "'");
844+
}
845+
846+
outputs.syslogs.emplace_back(std::move(output));
847+
}
848+
563849
/**
564850
* \brief Parse list of outputs
565851
* \param[in] outputs Parsed XML context
@@ -587,6 +873,9 @@ Config::parse_outputs(fds_xml_ctx_t *outputs)
587873
case OUTPUT_KAFKA:
588874
parse_kafka(content->ptr_ctx);
589875
break;
876+
case OUTPUT_SYSLOG:
877+
parse_syslog(content->ptr_ctx);
878+
break;
590879
default:
591880
throw std::invalid_argument("Unexpected element within <outputs>!");
592881
}
@@ -684,6 +973,7 @@ Config::default_set()
684973
outputs.servers.clear();
685974
outputs.sends.clear();
686975
outputs.kafkas.clear();
976+
outputs.syslogs.clear();
687977
}
688978

689979
/**
@@ -699,6 +989,7 @@ Config::check_validity()
699989
output_cnt += outputs.sends.size();
700990
output_cnt += outputs.files.size();
701991
output_cnt += outputs.kafkas.size();
992+
output_cnt += outputs.syslogs.size();
702993
if (output_cnt == 0) {
703994
throw std::invalid_argument("At least one output must be defined!");
704995
}
@@ -730,6 +1021,9 @@ Config::check_validity()
7301021
for (const auto &kafka : outputs.kafkas) {
7311022
check_and_add(kafka.name);
7321023
}
1024+
for (const auto &syslog : outputs.syslogs) {
1025+
check_and_add(syslog.name);
1026+
}
7331027
}
7341028

7351029
Config::Config(const char *params)

0 commit comments

Comments
 (0)