3939 *
4040 */
4141
42+ #include < algorithm>
4243#include < cstdio>
4344#include < memory>
4445#include < set>
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 */
5667enum 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 */
164232static 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
7351029Config::Config (const char *params)
0 commit comments