Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

SysLog CEF Library

0.00/5 (No votes)
5 Mar 2020CPOL2 min read 17.7K  
Source code for managing messages and sending them to SysLog in CEF format (ArcSight)
This tip contains code to easily convert general messages in CEF format and how to send them to SysLog server. The CEF format was obtained reading ArcSight guidelines.

Introduction

This code offers the way to send messages in CEF format for logging events such as Login, Logout, insert, modify or delete records in a table with fields values. It can be a useful starting point for writing more complex CEF messages if you need to.

Background

CEF messages to SysLog servers are intended for use with device (i.e., IoT devices) but a customer asked me to use it for logging actions of users.

In ArcSight guide, there are a lot of keys where to store information, but the problem is: how to send it for a table record?

Fortunately, ArcSight offers custom key/values you can set and send, with some limitations: data will be retained from syslog but will not appear in reports; however customer can export data, so we used it this way.

No CEF libraries found on the internet, so I started to write my own code by looking at the ArcSight guidelines. Also, I had to search for how to send CEF messages, (that by SysLog are intended as files), to SysLog server via TCP.

Using the Code

The small library is composed of three classes and one enum (for severity level messages).

The first class is CEFClient, it is public and exposes the methods for send messages.

In the constructor, it stores the Vendor and Product keys for use in the flow, then it builds information about the local server which will be the client of SysLog server.

Then CEFClient presents two public methods to send messages in sync or async mode.

In this method, there is a parameter "fieldList" which is a container of a list of pair couples formed by name/value which represents the name and value (always in string format) of a table record field.

The second class is CEFParser which is the core to transform messages in CEF format.

The third class is CEFField which creates the custom field needed to send table record fields information.

C#
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Threading;

/*
    =============================================================================
    Generic use:
    =============================================================================
    CEFClient c = new CEFClient("YourName", "YourProduct");

    try
    {
        c.Port = Utils.ToNumber(YourNumberPort);
        c.SysLogServerIp = YourSysLogServerIp;        

        string user = "DonaldDuck";
        string message = "Login success";
        string action = "login";
        
        c.SendAsync(message, action, user, Level.Information, new List<Field>());

    }
    catch // (Exception ex)
    {
        //Console.WriteLine(ex);
    }

    =============================================================================
    Use for database tables:
    =============================================================================
    
    CEFClient c = new CEFClient("YourName", "YourProduct");

    try
    {
        c.Port = Utils.ToNumber(YourNumberPort);
        c.SysLogServerIp = YourSysLogServerIp;        

        List<SysLog.Field> fields = new List<SysLog.Field>()

        string user = "DonaldDuck";
        string message = "tPerson";
        string action = "insert";
        
        fields.Add(new Field() { FieldName = "Id", FieldValue = "1" });
        fields.Add(new Field() { FieldName = "SurName", FieldValue = "Smith" });
        fields.Add(new Field() { FieldName = "Name", FieldValue = "John" });
        
        c.SendAsync(message, action, user, Level.Warning, fields);

    }
    catch // (Exception ex)
    {
        //Console.WriteLine(ex);
    }

*/

namespace SysLog
{
    public class CEFClient
    {
        private IPHostEntry _ipHostInfo;
        private IPAddress _ipAddress;
        private IPEndPoint _ipLocalEndPoint;
        private string _sysLogServerIp = null;
        private int _port = 2000;
        private string _vendor;
        private string _product;

        public CEFClient(string Vendor, string Product)
        {
            _vendor = Vendor;
            _product = Product;
            _ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
            _ipAddress = _ipHostInfo.AddressList[0];
            _ipLocalEndPoint = new IPEndPoint(_ipAddress, 0);            
        }

        public int Port
        {
            set { _port = value; }
            get { return _port; }
        }

        public string SysLogServerIp
        {
            get { return _sysLogServerIp; }
            set { _sysLogServerIp = value; }
        }

        /// <summary>
        /// Send to SYSLOG server in async mode
        /// </summary>
        /// <param name="message">Free text</param>
        /// <param name="action">Action category(i.e. "login", "logout", "insert", "delete"...)</param>
        /// <param name="user">User</param>
        /// <param name="level">Severity</param>
        /// <param name="fields">Table record fields to log</param>
        public void SendAsync(string message, string action, string user, 
                              Level level, List<Field> fields)
        {
            new Thread(() =>
            {
                sendAlg(message, action, user, level, fields);

            }).Start();
        }

        /// <summary> 
        /// Send to SYSLOG server in syncronous mode 
        /// </summary> 
        /// <param name="message">Free text</param> 
        /// <param name="action">Action category(i.e. "login", "logout", "insert", "delete"...)</param> 
        /// <param name="user">User</param>
        /// <param name="level">Severity</param>
        /// <param name="fields">Table record fields to log</param>
        public void Send(string message, string action, string user, 
                         Level level, List<Field> fields)
        {
            sendAlg(message, action, user, level, fields);
        }

        private void sendAlg(string message, string action, string user, 
                             Level level, List<Field> fields)
        {
            string msg = "";

            try
            {
                IPAddress serverAddr = IPAddress.Parse(_sysLogServerIp);
                IPEndPoint endPoint = new IPEndPoint(serverAddr, _port);

                List<CEFField> cefFields = new List<CEFField>();

                for (int i = 0; i < fields.Count; i++)
                {
                    cefFields.Add(new CEFField(_vendor, _product, i, fields[i]));
                }

                CEFParser parser = new CEFParser(
                    "1.0",                  //    Version = "1.0",
                    _ipHostInfo.HostName,   //    Host = ipHostInfo.HostName,
                    _vendor,                //    Vendor = "CompanyName",
                    _product,               //    Product = "ProductName",
                    action,                 //    SignatureID = action,
                    message,                //    Text = message,
                    (int)level,             //    Severity = (int)level,
                    user,                   //    UserName = user,
                    _ipAddress.ToString(),  //    SourceIp = ipAddress.ToString()
                    cefFields
                );

                msg = parser.CEFMessage;

                byte[] bytes = System.Text.Encoding.UTF8.GetBytes(msg);

                using (var client = new TcpClient())
                {
                    client.Connect(endPoint);

                    if (client.Connected)
                    {
                        using (NetworkStream stream = client.GetStream())
                        {
                            stream.Write(bytes, 0, bytes.Length);
                            stream.Flush();
                            stream.Close();
                        }
                                
                        client.Close();
                    }
                    else
                    {
                        throw new Exception("Could not connect to server");
                    }
                }
            }
            catch (Exception ex)
            {
                throw (ex);
            }
        }        
    }

    public enum Level
    {
        Test = 0,
        Debug = 1,
        Information = 2,
        Notice = 3,
        Warning = 4,
        Suspect = 5,
        Error = 6,
        Critical = 7,
        Alert = 8,
        Emergency = 9,
        Crash = 10
    }

    internal class CEFParser
    {
        private string _version { get; set; }
        private string _host { get; set; }
        private string _vendor { get; set; }
        private string _product { get; set; }
        private string _signatureID { get; set; }
        private string _text { get; set; }
        private int _severity { get; set; }
        private string _userName { get; set; }
        private string _sourceIp { get; set; }
        private List<CEFField> _fields { get; set; }
        
        public CEFParser(
            string Version,
            string Host,
            string Vendor,
            string Product,
            string SignatureId,
            string Text,
            int Severity,
            string Username,
            string SourceIp,
            List<CEFField> Fields)
        {
            _version = Version;
            _host = Host;
            _vendor = Vendor;
            _product = Product;
            _signatureID = SignatureId;
            _text = Text;
            _severity = Severity;
            _userName = Username;
            _sourceIp = SourceIp;
            _fields = Fields;
        }

        public string Header
        {
            get
            {
                string formattedDate = DateTime.Now.ToString
                ("MMM dd HH:mm:ss", CultureInfo.CreateSpecificCulture("en-GB"));

                return System.String.Format("{0} {1}",
                                            formattedDate,
                                            _host);
            }
        }

        public string Message
        {
            get
            {
                string ret = "CEF:0|" +
                    _vendor + "|" +
                    _product + "|" +
                    _version + "|" +
                    _signatureID + "|" +
                    _text + "|" +
                    _severity.ToString() + "|" +
                    "src=" + _sourceIp + " " +
                    "suser=" + _userName;

                foreach (CEFField item in _fields)
                {
                    ret += " " + item.Key + "=" + item.Value;
                }

                ret += Environment.NewLine;

                return ret;
            }
        }
        
        public string CEFMessage
        {
            get
            {
                return Header + " " + Message;
            }
        }
    }

    public class Field
    {
        public string FieldName { get; set; }
        public string FieldValue { get; set; }
    }

    internal class CEFField
    {
        private string _id;
        private Field _field;
        private string _vendor;
        private string _product;

        public CEFField(string Vendor, string Product, int Id, Field Field)
        {
            _vendor = Vendor;
            _product = Product;
            _id = Id.ToString().PadLeft(4,'0');
            _field = Field;
        }

        public string Key
        {
            get
            {
                return _vendor + _product + _id + _field.FieldName;
            }
        }

        public string Value
        {
            get
            {
                return getNormalizeCEFMessage(_field.FieldValue);
            }
        }

        private string getNormalizeCEFMessage(string message)
        {
            string ret = message.Replace("|", "\\|");

            ret = ret.Replace("\\", "\\\\");
            ret = ret.Replace("=", "\\=");

            return ret;
        }
    }
}

A customer asked me to send message via TCP in security certificate mode. So I've made some changes to pack certificate and send messages in cryptography mode. If you need help to do so, leave me a comment below and I will be happy to help you.

History

  • 4th March, 2020: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)