/*
 * RemoteSyslogAppender.cpp
 *
 * Copyright 2001, LifeLine Networks BV (www.lifeline.nl). All rights reserved.
 * Copyright 2001, Walter Stroebel. All rights reserved.
 *
 * See the COPYING file for the terms of usage and distribution.
 */

#include "PortabilityImpl.hh"

#ifdef LOG4CPP_HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <log4cpp/FactoryParams.hh>
#include <log4cpp/RemoteSyslogAppender.hh>
#include <memory>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>

#ifdef WIN32
#include <winsock2.h>
#else
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#endif

namespace log4cpp {

    int RemoteSyslogAppender::toSyslogPriority(Priority::Value priority) {
        static int priorities[8] = {LOG_EMERG,   LOG_ALERT,  LOG_CRIT, LOG_ERR,
                                    LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG};
        int result;

        priority++;
        priority /= 100;

        if (priority < 0) {
            result = LOG_EMERG;
        } else if (priority > 7) {
            result = LOG_DEBUG;
        } else {
            result = priorities[priority];
        }

        return result;
    }

    RemoteSyslogAppender::RemoteSyslogAppender(const std::string& name, const std::string& syslogName,
                                               const std::string& relayer, int facility, int portNumber)
        : LayoutAppender(name), _syslogName(syslogName), _relayer(relayer),
          _facility((facility == -1) ? LOG_USER : facility), _portNumber((portNumber == -1) ? 514 : portNumber),
          _socket(0), _ipAddr(0), _cludge(0) {
        open();
    }

    RemoteSyslogAppender::~RemoteSyslogAppender() {
        close();
#ifdef WIN32
        if (_cludge) {
            // we started it, we end it.
            WSACleanup();
        }
#endif
    }

    void RemoteSyslogAppender::open() {
        if (!_ipAddr) {
            struct hostent* pent = gethostbyname(_relayer.c_str());
#ifdef WIN32
            if (pent == NULL) {
                if (WSAGetLastError() == WSANOTINITIALISED) {
                    WSADATA wsaData;
                    int err;

                    err = WSAStartup(0x101, &wsaData);
                    if (err) {
                        // loglog("RemoteSyslogAppender: WSAStartup returned %d", err);
                        return; // fail silently
                    }
                    pent = gethostbyname(_relayer.c_str());
                    _cludge = 1;
                } else {
                    // loglog("RemoteSyslogAppender: gethostbyname returned error");
                    return; // fail silently
                }
            }
#endif
            if (pent == NULL) {
                in_addr_t ip = inet_addr(_relayer.c_str());
                pent = gethostbyaddr((const char*)&ip, sizeof(in_addr_t), AF_INET);
                if (pent == NULL) {
                    // loglog("RemoteSyslogAppender: failed to resolve host %s", _relayer.c_str());
                    return; // fail silently
                }
            }
            _ipAddr = *(in_addr_t*)(pent->h_addr); // fixed bug #1579890
        }
        // Get a datagram socket.
        _socket = socket(AF_INET, SOCK_DGRAM, 0);
        if
#ifdef WIN32
            (_socket == INVALID_SOCKET)
#else
            (_socket < 0)
#endif
        {
            // loglog("RemoteSyslogAppender: failed to open socket");
            return; // fail silently
        }
    }

    void RemoteSyslogAppender::close() {
        if (_socket) {
#ifdef WIN32
            closesocket(_socket);
#else
            ::close(_socket);
#endif
            _socket = 0;
        }
    }

    void RemoteSyslogAppender::_append(const LoggingEvent& event) {
        const std::string message(_getLayout().format(event));
        size_t messageLength = message.length();
        char* buf = new char[messageLength + 16];
        int priority = _facility + toSyslogPriority(event.priority);
        int preambleLength = sprintf(buf, "<%d>", priority);
        memcpy(buf + preambleLength, message.data(), messageLength);

        sockaddr_in sain;
        sain.sin_family = AF_INET;
        sain.sin_port = htons(_portNumber);
        // NO, do NOT use htonl on _ipAddr. Is already in network order.
        sain.sin_addr.s_addr = _ipAddr;

        while (messageLength > 0) {
            /* if packet larger than maximum (900 bytes), split
               into two or more syslog packets. */
            if (preambleLength + messageLength > 900) {
                sendto(_socket, buf, 900, 0, (struct sockaddr*)&sain, sizeof(sain));
                messageLength -= (900 - preambleLength);
                std::memmove(buf + preambleLength, buf + 900, messageLength);
                // note: we might need to sleep a bit here
            } else {
                sendto(_socket, buf, preambleLength + messageLength, 0, (struct sockaddr*)&sain, sizeof(sain));
                break;
            }
        }

        delete[] buf;
    }

    bool RemoteSyslogAppender::reopen() {
        close();
        open();
        return true;
    }

    std::LOG4CPP_UNIQUE_PTR<Appender> create_remote_syslog_appender(const FactoryParams& params) {
        std::string name, syslog_name, relayer;
        int facility = -1, port_number = -1;
        params.get_for("remote syslog appender")
            .required("name", name)("syslog_name", syslog_name)("relayer", relayer)
            .optional("facility", facility)("port", port_number);
        return std::LOG4CPP_UNIQUE_PTR<Appender>(
            new RemoteSyslogAppender(name, syslog_name, relayer, facility, port_number));
    }
} // namespace log4cpp
