//
// RemoteSyslogChannel.cpp
//
// Library: Net
// Package: Logging
// Module:  RemoteSyslogChannel
//
// Copyright (c) 2006, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier:	BSL-1.0
//


#include "Poco/Net/RemoteSyslogChannel.h"
#include "Poco/Message.h"
#include "Poco/DateTimeFormatter.h"
#include "Poco/NumberFormatter.h"
#include "Poco/NumberParser.h"
#include "Poco/Net/SocketAddress.h"
#include "Poco/Net/DNS.h"
#include "Poco/LoggingFactory.h"
#include "Poco/Instantiator.h"
#include "Poco/String.h"


namespace Poco {
namespace Net {


const std::string RemoteSyslogChannel::BSD_TIMEFORMAT("%b %f %H:%M:%S");
const std::string RemoteSyslogChannel::SYSLOG_TIMEFORMAT("%Y-%m-%dT%H:%M:%S.%i%z");
const std::string RemoteSyslogChannel::PROP_NAME("name");
const std::string RemoteSyslogChannel::PROP_FACILITY("facility");
const std::string RemoteSyslogChannel::PROP_FORMAT("format");
const std::string RemoteSyslogChannel::PROP_LOGHOST("loghost");
const std::string RemoteSyslogChannel::PROP_HOST("host");
const std::string RemoteSyslogChannel::PROP_BUFFER("buffer");
const std::string RemoteSyslogChannel::STRUCTURED_DATA("structured-data");


RemoteSyslogChannel::RemoteSyslogChannel():
	_logHost("localhost"),
	_name("-"),
	_facility(SYSLOG_USER),
	_bsdFormat(false),
	_buffer(0),
	_open(false)
{
}


RemoteSyslogChannel::RemoteSyslogChannel(const std::string& address, const std::string& name, int facility, bool bsdFormat):
	_logHost(address),
	_name(name),
	_facility(facility),
	_bsdFormat(bsdFormat),
	_buffer(0),
	_open(false)
{
	if (_name.empty()) _name = "-";
}


RemoteSyslogChannel::~RemoteSyslogChannel()
{
	try
	{
		close();
	}
	catch (...)
	{
		poco_unexpected();
	}
}


void RemoteSyslogChannel::open()
{
	if (_open) return;

	if (_logHost.find(':') != std::string::npos)
		_socketAddress = SocketAddress(_logHost);
	else
		_socketAddress = SocketAddress(_logHost, SYSLOG_PORT);

	// reset socket for the case that it has been previously closed
	_socket = DatagramSocket(_socketAddress.family());

	if (_host.empty())
	{
		try
		{
			_host = DNS::thisHost().name();
		}
		catch (Poco::Exception&)
		{
			_host = _socket.address().host().toString();
		}
	}

	if (_buffer)
	{
		_socket.setSendBufferSize(_buffer);
	}

	_open = true;
}


void RemoteSyslogChannel::close()
{
	if (_open)
	{
		_socket.close();
		_open = false;
	}
}


void RemoteSyslogChannel::log(const Message& msg)
{
	Poco::FastMutex::ScopedLock lock(_mutex);

	if (!_open) open();

	std::string m;
	m.reserve(1024);
	m += '<';
	Poco::NumberFormatter::append(m, getPrio(msg) + _facility);
	m += '>';
	if (_bsdFormat)
	{
		Poco::DateTimeFormatter::append(m, msg.getTime(), BSD_TIMEFORMAT);
		m += ' ';
		m += _host;
	}
	else
	{
		m += "1 "; // version
		Poco::DateTimeFormatter::append(m, msg.getTime(), SYSLOG_TIMEFORMAT);
		m += ' ';
		m += _host;
		m += ' ';
		m += _name;
		m += ' ';
		Poco::NumberFormatter::append(m, msg.getPid());
		m += ' ';
		m += msg.getSource();
		m += ' ';
		if (msg.has(STRUCTURED_DATA))
		{
			m += msg.get(STRUCTURED_DATA);
		}
		else
		{
			m += "-";
		}
	}
	m += ' ';
	m += msg.getText();

	_socket.sendTo(m.data(), static_cast<int>(m.size()), _socketAddress);
}


void RemoteSyslogChannel::setProperty(const std::string& name, const std::string& value)
{
	if (name == PROP_NAME)
	{
		_name = value;
		if (_name.empty()) _name = "-";
	}
	else if (name == PROP_FACILITY)
	{
		std::string facility;
		if (Poco::icompare(value, 4, "LOG_") == 0)
			facility = Poco::toUpper(value.substr(4));
		else if (Poco::icompare(value, 7, "SYSLOG_") == 0)
			facility = Poco::toUpper(value.substr(7));
		else
			facility = Poco::toUpper(value);

		if (facility == "KERN")
			_facility = SYSLOG_KERN;
		else if (facility == "USER")
			_facility = SYSLOG_USER;
		else if (facility == "MAIL")
			_facility = SYSLOG_MAIL;
		else if (facility == "DAEMON")
			_facility = SYSLOG_DAEMON;
		else if (facility == "AUTH")
			_facility = SYSLOG_AUTH;
		else if (facility == "AUTHPRIV")
			_facility = SYSLOG_AUTHPRIV;
		else if (facility == "SYSLOG")
			_facility = SYSLOG_SYSLOG;
		else if (facility == "LPR")
			_facility = SYSLOG_LPR;
		else if (facility == "NEWS")
			_facility = SYSLOG_NEWS;
		else if (facility == "UUCP")
			_facility = SYSLOG_UUCP;
		else if (facility == "CRON")
			_facility = SYSLOG_CRON;
		else if (facility == "FTP")
			_facility = SYSLOG_FTP;
		else if (facility == "NTP")
			_facility = SYSLOG_NTP;
		else if (facility == "LOGAUDIT")
			_facility = SYSLOG_LOGAUDIT;
		else if (facility == "LOGALERT")
			_facility = SYSLOG_LOGALERT;
		else if (facility == "CLOCK")
			_facility = SYSLOG_CLOCK;
		else if (facility == "LOCAL0")
			_facility = SYSLOG_LOCAL0;
		else if (facility == "LOCAL1")
			_facility = SYSLOG_LOCAL1;
		else if (facility == "LOCAL2")
			_facility = SYSLOG_LOCAL2;
		else if (facility == "LOCAL3")
			_facility = SYSLOG_LOCAL3;
		else if (facility == "LOCAL4")
			_facility = SYSLOG_LOCAL4;
		else if (facility == "LOCAL5")
			_facility = SYSLOG_LOCAL5;
		else if (facility == "LOCAL6")
			_facility = SYSLOG_LOCAL6;
		else if (facility == "LOCAL7")
			_facility = SYSLOG_LOCAL7;
	}
	else if (name == PROP_LOGHOST)
	{
		_logHost = value;
	}
	else if (name == PROP_HOST)
	{
		_host = value;
	}
	else if (name == PROP_FORMAT)
	{
		_bsdFormat = (value == "bsd" || value == "rfc3164");
	}
	else if (name == PROP_BUFFER)
	{
		_buffer = Poco::NumberParser::parse(value);
	}
	else
	{
		Channel::setProperty(name, value);
	}
}


std::string RemoteSyslogChannel::getProperty(const std::string& name) const
{
	if (name == PROP_NAME)
	{
		if (_name != "-")
			return _name;
		else
			return "";
	}
	else if (name == PROP_FACILITY)
	{
		if (_facility == SYSLOG_KERN)
			return "KERN";
		else if (_facility == SYSLOG_USER)
			return "USER";
		else if (_facility == SYSLOG_MAIL)
			return "MAIL";
		else if (_facility == SYSLOG_DAEMON)
			return "DAEMON";
		else if (_facility == SYSLOG_AUTH)
			return "AUTH";
		else if (_facility == SYSLOG_AUTHPRIV)
			return "AUTHPRIV";
		else if (_facility == SYSLOG_SYSLOG)
			return "SYSLOG";
		else if (_facility == SYSLOG_LPR)
			return "LPR";
		else if (_facility == SYSLOG_NEWS)
			return "NEWS";
		else if (_facility == SYSLOG_UUCP)
			return "UUCP";
		else if (_facility == SYSLOG_CRON)
			return "CRON";
		else if (_facility == SYSLOG_FTP)
			return "FTP";
		else if (_facility == SYSLOG_NTP)
			return "NTP";
		else if (_facility == SYSLOG_LOGAUDIT)
			return "LOGAUDIT";
		else if (_facility == SYSLOG_LOGALERT)
			return "LOGALERT";
		else if (_facility == SYSLOG_CLOCK)
			return "CLOCK";
		else if (_facility == SYSLOG_LOCAL0)
			return "LOCAL0";
		else if (_facility == SYSLOG_LOCAL1)
			return "LOCAL1";
		else if (_facility == SYSLOG_LOCAL2)
			return "LOCAL2";
		else if (_facility == SYSLOG_LOCAL3)
			return "LOCAL3";
		else if (_facility == SYSLOG_LOCAL4)
			return "LOCAL4";
		else if (_facility == SYSLOG_LOCAL5)
			return "LOCAL5";
		else if (_facility == SYSLOG_LOCAL6)
			return "LOCAL6";
		else if (_facility == SYSLOG_LOCAL7)
			return "LOCAL7";
		else
			return "";
	}
	else if (name == PROP_LOGHOST)
	{
		return _logHost;
	}
	else if (name == PROP_HOST)
	{
		return _host;
	}
	else if (name == PROP_FORMAT)
	{
		return _bsdFormat ? "rfc3164" : "rfc5424";
	}
	else if (name == PROP_BUFFER)
	{
		return Poco::NumberFormatter::format(_buffer);
	}
	else
	{
		return Channel::getProperty(name);
	}
}


int RemoteSyslogChannel::getPrio(const Message& msg)
{
	switch (msg.getPriority())
	{
	case Message::PRIO_TRACE:
	case Message::PRIO_DEBUG:
		return SYSLOG_DEBUG;
	case Message::PRIO_INFORMATION:
		return SYSLOG_INFORMATIONAL;
	case Message::PRIO_NOTICE:
		return SYSLOG_NOTICE;
	case Message::PRIO_WARNING:
		return SYSLOG_WARNING;
	case Message::PRIO_ERROR:
		return SYSLOG_ERROR;
	case Message::PRIO_CRITICAL:
		return SYSLOG_CRITICAL;
	case Message::PRIO_FATAL:
		return SYSLOG_ALERT;
	default:
		return 0;
	}
}

const char* RemoteSyslogChannel::facilityToString(const Facility facility)
{
	switch(facility)
	{
	case RemoteSyslogChannel::SYSLOG_KERN:
		return "KERN";
	case RemoteSyslogChannel::SYSLOG_USER:
		return "USER";
	case RemoteSyslogChannel::SYSLOG_MAIL:
		return "MAIL";
	case RemoteSyslogChannel::SYSLOG_DAEMON:
		return "DAEMON";
	case RemoteSyslogChannel::SYSLOG_AUTH:
		return "AUTH";
	case RemoteSyslogChannel::SYSLOG_SYSLOG:
		return "SYSLOG";
	case RemoteSyslogChannel::SYSLOG_LPR:
		return "LPR";
	case RemoteSyslogChannel::SYSLOG_NEWS:
		return "NEWS";
	case RemoteSyslogChannel::SYSLOG_UUCP:
		return "UUCP";
	case RemoteSyslogChannel::SYSLOG_CRON:
		return "CRON";
	case RemoteSyslogChannel::SYSLOG_AUTHPRIV:
		return "AUTHPRIV";
	case RemoteSyslogChannel::SYSLOG_FTP:
		return "FTP";
	case RemoteSyslogChannel::SYSLOG_NTP:
		return "NTP";
	case RemoteSyslogChannel::SYSLOG_LOGAUDIT:
		return "LOGAUDIT";
	case RemoteSyslogChannel::SYSLOG_LOGALERT:
		return "LOGALERT";
	case RemoteSyslogChannel::SYSLOG_CLOCK:
		return "CLOCK";
	case RemoteSyslogChannel::SYSLOG_LOCAL0:
		return "LOCAL0";
	case RemoteSyslogChannel::SYSLOG_LOCAL1:
		return "LOCAL1";
	case RemoteSyslogChannel::SYSLOG_LOCAL2:
		return "LOCAL2";
	case RemoteSyslogChannel::SYSLOG_LOCAL3:
		return "LOCAL3";
	case RemoteSyslogChannel::SYSLOG_LOCAL4:
		return "LOCAL4";
	case RemoteSyslogChannel::SYSLOG_LOCAL5:
		return "LOCAL5";
	case RemoteSyslogChannel::SYSLOG_LOCAL6:
		return "LOCAL6";
	case RemoteSyslogChannel::SYSLOG_LOCAL7:
		return "LOCAL7";
	default:
		return "";
	}
}


void RemoteSyslogChannel::registerChannel()
{
	Poco::LoggingFactory::defaultFactory().registerChannelClass("RemoteSyslogChannel", new Poco::Instantiator<RemoteSyslogChannel, Poco::Channel>);
}


} } // namespace Poco::Net
