/* **********************************************************
 * Copyright (c) 2017-2022 Google, Inc.  All rights reserved.
 * Copyright (c) 2003-2009 VMware, Inc.  All rights reserved.
 * **********************************************************/

/*
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * * Neither the name of VMware, Inc. nor the names of its contributors may be
 *   used to endorse or promote products derived from this software without
 *   specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */

/* Copyright (c) 2003-2007 Determina Corp. */

/* eventlog.c - Windows specific event logging issues */

#include "../globals.h"

#include <windows.h>
#include <tchar.h>
#include "../utils.h"

#include "events.h" /* generated from message file  */
#include "ntdll.h"

/* Types for Named pipe communication to the Event Log  */
#define NONCE_LENGTH 20

#define MAX_MESSAGE_SIZE 1024 /* total message size communicated to the eventlog */

/* Connection state */
typedef struct eventlog_state_t {
    HANDLE eventlog_pipe;
    HANDLE eventlog_completion; /* used for synchronization */
    uint message_seq;           /* message sequence number */
    char nonce[NONCE_LENGTH];   /* nonce received from server on handshake */
    mutex_t eventlog_mutex;     /* sync persistent thread shared logging connection */
    /* place buffers here to save stack space, used by [de]register and report
     * all of whom protect them with the above lock, this structure is single
     * instance static anyways so not wasting much memory doing it this way */
    char outbuf[MAX_MESSAGE_SIZE];
    size_t outlen;
    char buf[MAX_MESSAGE_SIZE];
    int request_length;
} eventlog_state_t;

/* writes a message to the Windows Event Log */
static void
os_eventlog(syslog_event_type_t priority, uint message_id, uint substitutions_num,
            char **arguments, size_t size_data, char *raw_data);

/* Custom logfile key and properties.
 * This enables administrators to control the size of the log file,
 * and we can attach SACLs for security purposes, without affecting other applications.
 */

// Make sure the registry key is all set up, maybe better done in the installer?
// The minimum we need:

/* addsource.reg:
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\Araksha]

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\Araksha\DynamoRIO]
"TypesSupported"=dword:00000007
"EventMessageFile"="C:\\cygwin\\home\\vlk\\exports\\x86_win32_dbg\\dynamorio.dll"
*/

/* sets the values for the already existing event source key */
static int
set_event_source_registry_values()
{
    int res;
    HANDLE heventsource = reg_open_key(L_EVENT_SOURCE_KEY, KEY_SET_VALUE);
    /* the message file is in our main dll */
    char *message_file = get_dynamorio_library_path();
    wchar_t wide_message_file[MAXIMUM_PATH];

    if (!heventsource) {
        return 0;
    }

    ASSERT(message_file && strlen(message_file) < MAXIMUM_PATH);
    _snwprintf(wide_message_file, MAXIMUM_PATH, L"%S", message_file);
    NULL_TERMINATE_BUFFER(wide_message_file);

    // FIXME: BUFOV? is the conversion locale dependent

    res = reg_set_dword_key_value(heventsource,
                                  L"TypesSupported", // which messages can go in
                                  EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE |
                                      EVENTLOG_INFORMATION_TYPE);

    res |= reg_set_dword_key_value(heventsource,
                                   L"CategoryCount", // # of event categories supported
                                   0);

    /* REG_EXPAND_SZ can contain a level of "indirection" in the form of a
       system variable that can be resolved at the time of use of the
       entry. For example: EventMessageFile="%WINDIR%\\dynamorio.dll"
    */

    // FIXME: I'd rather set the full REG_EXPAND_SZ to be prepared
    res |= reg_set_key_value(heventsource, L"EventMessageFile",
                             /* should be the name of our DLL (or RLL if we put in
                              * a separate file) */
                             wide_message_file);

    res |= reg_set_key_value(heventsource, L"CategoryMessageFile", wide_message_file);

    /* we don't use these
       DisplayNameFile
       DisplayNameID
       ParameterMessageFile
    */

    reg_close_key(heventsource);

    return 1;
}

/* returns 1 on successfully setting up the registry variables for an event source */
static int
init_registry_source(void)
{
    static int initialized = 0;
    /* FIXME: we assume no one should have access to modify after we check */
    /* FIXME: may want to register for notifications .. */
    /* FIXME: if we fail we'll do this over and over for each event .. */

    /* use our registry routines to avoid Win32 reentrancy issues */

    /* FIXME: let's do it with access rights only as needed- I got fed up at one point*/
    if (!initialized) {
        /* first make sure all keys are created */
        HANDLE heventsource = reg_open_key(L_EVENT_SOURCE_KEY, KEY_READ | KEY_WRITE);
        HANDLE heventlogroot = 0;
        HANDLE heventlog = 0;

        if (!heventsource) { /* we're not in */
            /* test and create eventlog key  */
            heventlog = reg_open_key(L_EVENT_LOG_KEY, KEY_READ | KEY_WRITE);
            // KEY_READ == KEY_QUERY_VALUE | KEY_NOTIFY |
            //             KEY_ENUMERATE_SUB_KEYS seems too strong

            if (!heventlog) {
                heventlogroot =
                    reg_open_key(L_EVENTLOG_REGISTRY_KEY, KEY_READ | KEY_WRITE);
                if (!heventlogroot) {
                    LOG(GLOBAL, LOG_TOP, 1,
                        "WARNING: Registration failure.  Could not open root %ls.",
                        L_EVENTLOG_REGISTRY_KEY);
                    return 0;
                }
                heventlog =
                    reg_create_key(heventlogroot, L_EVENT_LOG_NAME, KEY_ALL_ACCESS);
            }
            if (!heventlog) {
                LOG(GLOBAL, LOG_TOP, 1, "WARNING: Could not create event log key %s.",
                    EVENTLOG_NAME);
                return 0;
            }

            /* obviously we'll need SET_VALUE later but to keep the
             * logic simple we take minimal here
             */
            heventsource =
                reg_create_key(heventlog, L_EVENT_SOURCE_NAME, KEY_QUERY_VALUE);
            if (heventlog) {
                reg_close_key(heventlog);
            }
            if (heventlogroot) {
                reg_close_key(heventlogroot);
            }
        }
        if (!heventsource) {
            LOG(GLOBAL, LOG_TOP, 1, "WARNING: Could not create event source key %s.",
                EVENTSOURCE_NAME);
            return 0;
        }

        reg_close_key(heventsource);
        initialized = set_event_source_registry_values();
    }

    return initialized;
}

#define MAX_SYSLOG_ARGS 6 /* increase if necessary */
/* collect arguments in an array and pass along */
void
os_syslog(syslog_event_type_t priority, uint message_id, uint substitutions_num,
          va_list vargs)
{
    uint arg;
    char *arg_arr[MAX_SYSLOG_ARGS];

    // pointer to raw data TODO: SYSLOG_DATA entry point that also adds data arguments
    char *other_data = "";
    size_t size_data = strlen(other_data); /* 0 - for no data */

    ASSERT(substitutions_num < MAX_SYSLOG_ARGS);

    for (arg = 0; arg < substitutions_num; arg++) {
        arg_arr[arg] = va_arg(vargs, char *);
    }

    /* don't need to check syslog, mask, caller is responsible for
     * checking the mask and synchronizing the options
     */
    os_eventlog(priority, message_id, substitutions_num, arg_arr, size_data, other_data);
}

/* Here starts the gross hack for direct message passing to the EventLog service */

/* Macros for adding variable length fields to a message buffer
   p should point to the current position in the string appended to
   pend points to the first location after the end of the buffer  */
/* Modifies p */
#define FIELD(p, pend, type, val)                \
    do {                                         \
        if (!(p) || (p) + sizeof(type) > (pend)) \
            return 0;                            \
        *(type *)(p) = (type)(val);              \
        (p) += sizeof(type);                     \
    } while (0)

/* pass &p */
#define VARFIELD(pp, pend, val, len)              \
    do {                                          \
        if (!(*(pp)) || (*(pp)) + (len) > (pend)) \
            return 0;                             \
        memcpy((*(pp)), (val), (len));            \
        *(pp) += (len);                           \
    } while (0)

/* The advapi functions don't actually zero out the padding */
/* Modifies p (i.e. *pp) */
#define PADDING(pp, pend, len, boundary)           \
    do {                                           \
        int skip = (int)PAD((len), (boundary));    \
        if (!(*(pp)) || (*(pp)) + (skip) > (pend)) \
            return 0;                              \
        while (skip--)                             \
            *(*(pp))++ = '\0';                     \
    } while (0)

/* Encodes an ASCIIZ string into some weird format */
/* Example "\011\0\012\0""03B\0""\12\0\0\0DynamoRio\0\0\0"
   CHECK: Is this a documented M$ string representation?
   It seems like there is a lot of redundancy in the encoding,
   but it maybe useful in later reincarnations of the message.
   This is in fact a plain UNICODE_STRING.
*/
static inline char *
append_string(char **pp /* INOUT */, char *pend, char *str)
{
    size_t length = strlen(str) + 1;
    WORD len;

    ASSERT_TRUNCATE(len, ushort, length); /* ushort == WORD */
    len = (WORD)length;

    FIELD(*pp, pend, WORD, len - 1);
    FIELD(*pp, pend, WORD, len);
    FIELD(*pp, pend, void *, str);
    FIELD(*pp, pend, DWORD, len);
    VARFIELD(pp, pend, str, len);
    PADDING(pp, pend, len, sizeof(DWORD));
    return *pp;
}

#define HEADER_SIZE 24
#define HEADER_OFFSET 28

static inline char *
prepend_header(char *p, char *pend, char *header, int length, int sequence, DWORD unknown)
{
    if (!p)
        return NULL;
    VARFIELD(&p, pend, header, 8);
    FIELD(p, pend, DWORD, length);
    FIELD(p, pend, DWORD, sequence);
    FIELD(p, pend, DWORD, length - HEADER_SIZE);
    FIELD(p, pend, DWORD, unknown);
    FIELD(p, pend, DWORD, 0);
    return p;
}

/* FIXME: this value needs to be decoded using Ethereal too. See case 5655. */
#define EVENTLOG "\20\0\0\0" /* always 16 */

/* The first byte of the hello_message string should be \x05, but this
 * triggers a false positive in McAfee. That's why that byte is set to
 * RPC_VERSION_BOGUS and replaced with RPC_VERSION_5 before it is used.
 * See case 5002 for more details.*/

#define RPC_VERSION_BOGUS "\xFF" /* must be a string */
#define RPC_VERSION_5 '\x05'     /* must be a character */

/* advapi sends this message for several days with different computer names,
   if I break the protocol then its hello request starts with H\0\0\0\5... */
static char hello_message[] = /* DCE RPC request, decoded by Ethereal */
    RPC_VERSION_BOGUS         /* Version: Should be 5, but we set it to a bogus value
                               * because of a false positive in McAfee. See case 5002 */
    "\x00"                    /* Version (minor): 0 */
    "\x0B"                    /* Packet type: Bind (11) */
    "\x03"                    /* Packet Flags: 0x03 */

    "\x10\x00\x00\x00" /* Data Representation: 10000000 */
    "\x48\x00"         /* Frag Length: 72 */
    "\x00\x00"         /* Auth Length: 0 */
    "\x01\x00\x00\x00" /* Call ID: 1 */
    "\xB8\x10"         /* Max Xmit Frag: 4280 */
    "\xB8\x10"         /* Max Recv Frag: 4280 */

    "\x00\x00\x00\x00"                 /* Assoc Group: 0x00000000 */
    "\x01\x00\x00\x00"                 /* Num Ctx Items: 1 */
    "\x00\x00"                         /* Context ID: 0 */
    "\x01\x00"                         /* Num Trans Items: 1 */
    "\xDC\x3F\x27\x82\x2A\xE3\xC3\x18" /* Interface UUID: */
    "\x3F\x78\x82\x79\x29\xDC\x23\xEA" /*   82273fdc-e32a-18c3-3f78-827929dc23ea */
    "\x00\x00"                         /* Interface Ver: 0 */
    "\x00\x00"                         /* Interface Ver Minor: 0 */

    "\x04\x5D\x88\x8A\xEB\x1C\xC9\x11" /* Transfer Syntax: */
    "\x9F\xE8\x08\x00\x2B\x10\x48\x60" /*   8a885d04-1ceb-11c9-9fe8-08002b104860 */
    "\x02\x00\x00\x00";

/* We ignore this response to the hello message:
 *
 *                      // DCE RPC response, decoded by Ethereal
 *  "\x05"              // Version: 5
 *  "\x00"              // Version (minor): 0
 *  "\x0C"              // Packet type: Bind_ack (12)
 *  "\x03"              // Packet Flags: 0x03
 *  "\x10\x00\x00\x00"  // Data Representation: 10000000
 *  "\x44\x00"          // Frag Length: 68
 *  "\x00\x00"          // Auth Length: 0
 *  "\x01\x00\x00\x00"  // Call ID: 1
 *  "\xB8\x10"          // Max Xmit Frag: 4280
 *  "\xB8\x10"          // Max Recv Frag: 4280
 *  "\x54\x13\x01\x00"  // Assoc Group: 0x00011354 (may vary)
 *  "\x0D\x00"          // Scndry Addr len: 13
 *  "\\PIPE\\ntsvcs\x00"    // Scndry Addr: \PIPE\ntsvcs
 *  "\x00"              // for alignment? (gets to 4 byte alignment)
 *  "\x01\x00\x00\x00"  // Num results: 1
 *  "\x00\x00\x00\x00"  // Ack result: Acceptance (0)
 *  "\x04\x5D\x88\x8A\xEB\x1C\xC9\x11" // Transfer Syntax:
 *  "\x9F\xE8\x08\x00\x2B\x10\x48\x60" //   8a885d04-1ceb-11c9-9fe8-08002b104860
 *  "\x02\x00\x00\x00"
 *
 * Note that the hello response has changed slightly in Vista (from hand
 * comparison) :
 * ...
 * ! "\x0F\x00"          // Scndry Addr len: 15
 * ! "\\PIPE\\eventlog\x00"    // Scndry Addr: \PIPE\eventlog
 * ! "\x00\x73\x00"      // for alignment? (gets to 4 byte alignment)
 * ...
 */

#define REPORT "\5\0\0\3"

#define REGISTER_UNKNOWN_HEADER *(int *)"\0\0\17\0"
#define REPORT_UNKNOWN_HEADER *(int *)"\0\0\22\0"
#define DEREGISTER_UNKNOWN_HEADER *(int *)"\0\0\3\0"

/* TODO: The client can talk to a named pipe server on a remote machine,
   then we will be able to get messages out even before the local services are started! */

#define EVENTLOG_NAMED_PIPE L"\\??\\PIPE\\EVENTLOG"

// debugging facility

#ifdef DEBUG
#    define PRINT(form, arg) LOG(GLOBAL, LOG_TOP, 3, form, arg)
static void
print_buffer_as_bytes(unsigned char *buf, size_t len)
{
    size_t i;
    int nonprint = 0;
    PRINT("%s", "\"");
    for (i = 0; i < len; i++) {
        if (isdigit(buf[i]) && nonprint) {
            PRINT("%s", "\"\""); // to make \01 into \0""1
        }
        if (buf[i] == '\\')
            PRINT("%s", "\\");

        if (isprint(buf[i])) {
            PRINT("%c", buf[i]);
            nonprint = 0;
        } else {
            PRINT("\\%o", buf[i]);
            nonprint = 1;
        }
    }
    PRINT("%s", "\"");
    PRINT("%s", ";\n");
}
#    undef PRINT
#endif /* DEBUG */

/* see comments above, the response message length changed in Vista */
#define HELLO_RESPONSE_LENGTH (get_os_version() < WINDOWS_VERSION_VISTA ? 68U : 72U)
#define REGISTER_RESPONSE_LENGTH 48
#define REPORT_RESPONSE_LENGTH 36

/* returns 0 on failure  */
/* must hold the eventlog mutex */
int
eventlog_register(eventlog_state_t *evconnection)
{
    char *p;

    ASSERT(evconnection != NULL);
    ASSERT_OWN_MUTEX(true, &evconnection->eventlog_mutex);

    evconnection->eventlog_completion = create_iocompletion();
    evconnection->eventlog_pipe =
        open_pipe(EVENTLOG_NAMED_PIPE, evconnection->eventlog_completion);

    if (!evconnection->eventlog_pipe) {
        LOG(GLOBAL, LOG_TOP, 1, "Couldn't open EVENTLOG\n");
        goto failed_to_register;
    }

    /* we can't do this in eventlog_init() b/c sometimes eventlog_register()
     * is called before eventlog_init()
     */
    SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
    /* Restore the first byte of hello_message before it is used. It was set
     * to a bogus value to avoid a McAfee false positive. See case 5002. */
    hello_message[0] = RPC_VERSION_5;
    SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);

    evconnection->request_length = sizeof(hello_message) - 1;
    evconnection->outlen =
        nt_pipe_transceive(evconnection->eventlog_pipe, hello_message,
                           evconnection->request_length, evconnection->outbuf,
                           sizeof(evconnection->outbuf), DYNAMO_OPTION(eventlog_timeout));
    DOLOG(2, LOG_TOP, {
        LOG(GLOBAL, LOG_TOP, 3, "inlen= %d; outlen = " SZFMT "\"\n",
            evconnection->request_length, evconnection->outlen);
        LOG(GLOBAL, LOG_TOP, 3, "char hello[] = ");
        print_buffer_as_bytes((byte *)hello_message, evconnection->request_length);
        LOG(GLOBAL, LOG_TOP, 3, "char hello_resp[] = ");
        print_buffer_as_bytes((byte *)evconnection->outbuf, evconnection->outlen);
    });

    if (evconnection->outlen != HELLO_RESPONSE_LENGTH) {
        LOG(GLOBAL, LOG_TOP, 1,
            "eventlog_register: Mismatch on HELLO_RESPONSE outlen=" SZFMT "\n",
            evconnection->outlen);
        goto failed_to_register;
    }

    /* the only expected message length, we're lenient on contents */

    evconnection->message_seq = 1; /* we start counting from source registration */
    p = evconnection->buf + HEADER_OFFSET;
    append_string(&p, evconnection->buf + sizeof(evconnection->buf), EVENTSOURCE_NAME);

#define REPORT_IN_LOG "Application"
    /* CHECK: I don't quite get how the log name here matters for Event Viewer,
       since the source is registered only under EVENTLOG_NAME subtree,
       TODO: yet we may want to have our own event file, and it may matter then.*/
    append_string(&p, evconnection->buf + sizeof(evconnection->buf), REPORT_IN_LOG);
    VARFIELD(&p, evconnection->buf + sizeof(evconnection->buf), "\1\0\0\0\1\0\0\0",
             8); /*UNKNOWN*/
    ASSERT(p);   // our buffer should be large enough

    IF_X64(ASSERT_TRUNCATE(evconnection->request_length, int, p - evconnection->buf));
    evconnection->request_length = (int)(p - evconnection->buf);
    p = prepend_header(evconnection->buf, evconnection->buf + sizeof(evconnection->buf),
                       REPORT EVENTLOG, evconnection->request_length,
                       evconnection->message_seq, REGISTER_UNKNOWN_HEADER);
    ASSERT(p);
    evconnection->message_seq++;

    evconnection->outlen =
        nt_pipe_transceive(evconnection->eventlog_pipe, evconnection->buf,
                           evconnection->request_length, evconnection->outbuf,
                           sizeof(evconnection->outbuf), DYNAMO_OPTION(eventlog_timeout));
    DOLOG(2, LOG_TOP, {
        LOG(GLOBAL, LOG_TOP, 3, "inlen= %d; outlen = " SZFMT "\n",
            evconnection->request_length, evconnection->outlen);
        LOG(GLOBAL, LOG_TOP, 3, "char reg[] = ");
        print_buffer_as_bytes((byte *)evconnection->buf, evconnection->request_length);
        LOG(GLOBAL, LOG_TOP, 3, "char reg_resp[] = ");
        print_buffer_as_bytes((byte *)evconnection->outbuf, evconnection->outlen);
    });

    /* the only expected message length, we're lenient on contents */
    if (evconnection->outlen != REGISTER_RESPONSE_LENGTH) {
        LOG(GLOBAL, LOG_TOP, 1,
            "eventlog_register: Mismatch on REGISTER_RESPONSE outlen=" SZFMT "\n",
            evconnection->outlen);
        goto failed_to_register;
    }

    // we can parse the output to verify its contents, yet we care only about the nonce
    memcpy(evconnection->nonce, &evconnection->outbuf[HEADER_OFFSET], NONCE_LENGTH);

    return 1;
    /* on error */
failed_to_register:
    if (evconnection->eventlog_completion) {
        close_handle(evconnection->eventlog_completion);
        evconnection->eventlog_completion = 0;
    }
    if (evconnection->eventlog_pipe) {
        close_file(evconnection->eventlog_pipe);
        evconnection->eventlog_pipe = 0;
    }
    return 0;
}

/* get computer name, (cached) */
char *
get_computer_name()
{
    static char computer_name[MAX_COMPUTERNAME_LENGTH + 5]; /* 15 + 5 */

    /* returns the Windows name of the current computer just like GetComputerName() */
    if (!computer_name[0]) {
        char buf[sizeof(KEY_VALUE_PARTIAL_INFORMATION) +
                 sizeof(wchar_t) * (MAX_COMPUTERNAME_LENGTH + 1)]; // wide
        KEY_VALUE_PARTIAL_INFORMATION *kvpi = (KEY_VALUE_PARTIAL_INFORMATION *)buf;

        if (reg_query_value(L"\\Registry\\Machine\\System\\CurrentControlSet"
                            L"\\Control\\ComputerName\\ActiveComputerName",
                            L"ComputerName", KeyValuePartialInformation, kvpi,
                            sizeof(buf), 0) == REG_QUERY_SUCCESS) {
            /* Case 8185: this reg key may not be set until after winlogon
             * starts up, and our first event may be post-init as well once
             * the eventlog service is up.  So we may need to unprotect
             * .data here.
             */
            if (dynamo_initialized) {
                ASSERT(check_should_be_protected(DATASEC_RARELY_PROT));
                SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
            } else
                ASSERT(!DATASEC_PROTECTED(DATASEC_RARELY_PROT));
            snprintf(computer_name, sizeof(computer_name) - 1, "%*ls",
                     kvpi->DataLength / sizeof(wchar_t) - 1, (wchar_t *)kvpi->Data);
            /* computer_name is static so last element is zeroed */
            if (dynamo_initialized)
                SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
        }
    }
    return computer_name;
}

/* must hold the eventlog mutex */
static int
eventlog_report(eventlog_state_t *evconnection, WORD severity, WORD category,
                DWORD message_id, void *pSID, uint substitutions_num,
                size_t raw_data_size, char **substitutions, char *raw_data)
{
    char *p;
    uint i;

    ULONG sec = query_time_seconds();

    ASSERT_OWN_MUTEX(true, &evconnection->eventlog_mutex);

    p = evconnection->buf + HEADER_OFFSET;
    VARFIELD(&p, evconnection->buf + sizeof(evconnection->buf), evconnection->nonce,
             NONCE_LENGTH);
    p -= 4; /* overwrite timestamp? */
    FIELD(p, evconnection->buf + sizeof(evconnection->buf), DWORD, sec);
    FIELD(p, evconnection->buf + sizeof(evconnection->buf), WORD, severity);
    FIELD(p, evconnection->buf + sizeof(evconnection->buf), WORD, category);
    FIELD(p, evconnection->buf + sizeof(evconnection->buf), DWORD, message_id);
    FIELD(p, evconnection->buf + sizeof(evconnection->buf), WORD, substitutions_num);

    /* FIXME: should write this constant in hex instead - \333 is not meaningful anyways
     * with the following broken code we've been writing
     * $SG23701 DB     0dbH, 'w', 00H
     *   which is 0x77db.
     * We should either keep using the magic value that has worked,
     * or figure out what should have really been written there.
     */
    FIELD(p, evconnection->buf + sizeof(evconnection->buf), WORD,
          *(WORD *)"\333w"); /* FIXME: ReservedFlags? */
    IF_X64(ASSERT(CHECK_TRUNCATE_TYPE_uint(raw_data_size)));
    FIELD(p, evconnection->buf + sizeof(evconnection->buf), DWORD, (DWORD)raw_data_size);
    append_string(&p, evconnection->buf + sizeof(evconnection->buf), get_computer_name());

    /* FIXME: This used to be type DWORD: I'm guessing that it should be widened */
    IF_X64(ASSERT_NOT_TESTED());
    FIELD(p, evconnection->buf + sizeof(evconnection->buf), void *, pSID);
    if (pSID) {
        // FIXME: dump a SID in binary format
        // FIXME: the actual structure order seems to be
        // WORD(sub_authorities_num),
        // 48 bit authority value,
        // sub_authorities_num * ( 48 bit sub-authority values)
    }

    // FIXME: these don't seem to be either offsets nor pointers
    // but are some function of the number of substitutions

    FIELD(p, evconnection->buf + sizeof(evconnection->buf), DWORD,
          *(DWORD *)"\230y\23\0"); /* FIXME pointer placeholder */
    FIELD(p, evconnection->buf + sizeof(evconnection->buf), DWORD, substitutions_num);
    for (i = 0; i < substitutions_num; i++) {
        /* FIXME unknown pointer placeholder */
        FIELD(p, evconnection->buf + sizeof(evconnection->buf), DWORD,
              *(DWORD *)"\210y\23\0");
    }

    for (i = 0; i < substitutions_num; i++) {
        append_string(&p, evconnection->buf + sizeof(evconnection->buf),
                      substitutions[i]);
    }

    /* just the pointer */
    /* FIXME: This used to be type DWORD: I'm guessing that it should be widened */
    IF_X64(ASSERT_NOT_TESTED());
    FIELD(p, evconnection->buf + sizeof(evconnection->buf), char *, raw_data);
    FIELD(p, evconnection->buf + sizeof(evconnection->buf), DWORD, raw_data_size);
    LOG(GLOBAL, LOG_TOP, 3, "datalen=%d data= %*s\n", raw_data_size, raw_data_size,
        raw_data);
    if (raw_data_size) {
        VARFIELD(&p, evconnection->buf + sizeof(evconnection->buf), raw_data,
                 raw_data_size); /* now the data */
        PADDING(&p, evconnection->buf + sizeof(evconnection->buf), raw_data_size,
                sizeof(DWORD));
    }

    /* FIXME: extra padding
       It seems like the server can handle more but not less padding */
    VARFIELD(&p, evconnection->buf + sizeof(evconnection->buf), "\0\0\0\0", 4);
    VARFIELD(&p, evconnection->buf + sizeof(evconnection->buf), "\0\0\0\0", 4);
    VARFIELD(&p, evconnection->buf + sizeof(evconnection->buf), "\0\0\0\0", 4);

    IF_X64(ASSERT_TRUNCATE(evconnection->request_length, int, p - evconnection->buf));
    evconnection->request_length = (int)(p - evconnection->buf);
    p = prepend_header(evconnection->buf, evconnection->buf + sizeof(evconnection->buf),
                       REPORT EVENTLOG, evconnection->request_length,
                       evconnection->message_seq, REPORT_UNKNOWN_HEADER);
    ASSERT(p);

    evconnection->message_seq++;
    evconnection->outlen =
        nt_pipe_transceive(evconnection->eventlog_pipe, evconnection->buf,
                           evconnection->request_length, evconnection->outbuf,
                           sizeof(evconnection->outbuf), DYNAMO_OPTION(eventlog_timeout));

    DOLOG(2, LOG_TOP, {
        LOG(GLOBAL, LOG_TOP, 3, "inlen= %d; outlen = " SZFMT "\n",
            evconnection->request_length, evconnection->outlen);
        LOG(GLOBAL, LOG_TOP, 3, "char report[] = ");
        print_buffer_as_bytes((byte *)evconnection->buf, evconnection->request_length);
        LOG(GLOBAL, LOG_TOP, 3, "char report_resp[] = ");
        print_buffer_as_bytes((byte *)evconnection->outbuf, evconnection->outlen);
        if (evconnection->outbuf[2] == '\3') {
            LOG(GLOBAL, LOG_TOP, 2, "//5 0 3 3 is bad news\n");
        }
    });

    /* the only expected message length, we're lenient on contents */
    if (evconnection->outlen != REPORT_RESPONSE_LENGTH) {
        LOG(GLOBAL, LOG_TOP, 1,
            "WARNING: Mismatch on REPORT_RESPONSE outlen=:" SZFMT "\n",
            evconnection->outlen);
        return 0;
    }

    return 1;
}

/* must hold the eventlog mutex */
uint
eventlog_deregister(eventlog_state_t *evconnection)
{
    char *p;
    int res;

    ASSERT_OWN_MUTEX(true, &evconnection->eventlog_mutex);

    p = evconnection->buf + HEADER_OFFSET;
    VARFIELD(&p, evconnection->buf + sizeof(evconnection->buf), evconnection->nonce,
             NONCE_LENGTH);
    IF_X64(ASSERT_TRUNCATE(evconnection->request_length, int, p - evconnection->buf));
    evconnection->request_length = (int)(p - evconnection->buf);
    p = prepend_header(evconnection->buf, evconnection->buf + sizeof(evconnection->buf),
                       REPORT EVENTLOG, evconnection->request_length,
                       evconnection->message_seq, DEREGISTER_UNKNOWN_HEADER);
    ASSERT(p);

    evconnection->outlen =
        nt_pipe_transceive(evconnection->eventlog_pipe, evconnection->buf,
                           evconnection->request_length, evconnection->outbuf,
                           sizeof(evconnection->outbuf), DYNAMO_OPTION(eventlog_timeout));

    if (evconnection->outlen != /*DE*/ REGISTER_RESPONSE_LENGTH) {
        LOG(GLOBAL, LOG_TOP, 1,
            "WARNING: Mismatch on DEREGISTER_RESPONSE outlen=" SZFMT "\n",
            evconnection->outlen);
    }

    if (evconnection->eventlog_completion) {
        close_handle(evconnection->eventlog_completion);
        evconnection->eventlog_completion = 0;
    }

    ASSERT(evconnection->eventlog_pipe);
    res = close_file(evconnection->eventlog_pipe);
    evconnection->eventlog_pipe = 0;
    return res;
}

/* Getting a new handle may be not very performant, and also may fail
 * at unexpected times we cache session state across messages and across
 * threads */
static eventlog_state_t *shared_eventlog_connection;
/* we use this if we have to syslog prior to heap being initialized */
static eventlog_state_t temp_shared_eventlog_connection;

/* separate so it can be called for pre-eventlog_init() syslogs */
static void
eventlog_alloc()
{
    /* Shouldn't come in here later when would need multi-thread synch.
     * Sometimes eventlog registration fails until post-init (for lsass, e.g.)
     * but the alloc should happen during init regardless.
     */
    ASSERT(!dynamo_initialized);
    if (shared_eventlog_connection != NULL &&
        shared_eventlog_connection != &temp_shared_eventlog_connection) {
        /* an early syslog was post-heap-init and we are fully initialized */
        return;
    }
    if (!dynamo_heap_initialized) {
        /* No heap available, so we use our temp static struct.
         * eventlog_init() will call this routine again and we'll copy
         * to the heap (else condition below).
         */
        ASSERT(shared_eventlog_connection == NULL);
        shared_eventlog_connection = &temp_shared_eventlog_connection;
        ASSIGN_INIT_LOCK_FREE(shared_eventlog_connection->eventlog_mutex, eventlog_mutex);
    } else {
        eventlog_state_t *alloc =
            HEAP_TYPE_ALLOC(GLOBAL_DCONTEXT, eventlog_state_t, ACCT_OTHER, PROTECTED);
        if (shared_eventlog_connection == &temp_shared_eventlog_connection) {
            /* transfer from the temp static structure to the heap */
            memcpy(alloc, &temp_shared_eventlog_connection,
                   sizeof(temp_shared_eventlog_connection));
        } else {
            memset(alloc, 0, sizeof(*shared_eventlog_connection));
            ASSIGN_INIT_LOCK_FREE(alloc->eventlog_mutex, eventlog_mutex);
        }
        shared_eventlog_connection = alloc;
    }
}

void
eventlog_init()
{
    uint res;

    /* TODO: Check a persistent (registry) counter for the current application
     whether to report to the system log on this run, decrement it if present */

    /* syslog_mask is dynamic, so even if 0 now we init in case it changes later */

    /* we call get computer name to make sure it's static buffer is initialized
     * while we are still single threaded */
    get_computer_name();

    /* FIXME: We don't actually get our own log as intended  */
    /* on error we just go in the Application EventLog */
    if (DYNAMO_OPTION(syslog_init) &&
        !init_registry_source()) { /* update registry keys */
        DOLOG_ONCE(1, LOG_TOP, {
            LOG(GLOBAL, LOG_TOP, 1,
                "WARNING: Could not add the event source registry keys."
                "Events are reported with no message files.\n");
        });
    }

    /* may have already been allocated for early syslogs */
    eventlog_alloc();

    d_r_mutex_lock(&shared_eventlog_connection->eventlog_mutex);
    if (!shared_eventlog_connection->eventlog_pipe) {
        /* initialize thread shared connection */
        res = eventlog_register(shared_eventlog_connection);
        if (!res) {
            LOG(GLOBAL, LOG_TOP, 1, "WARNING: Could not register event source.\n");
        }
    }
    d_r_mutex_unlock(&shared_eventlog_connection->eventlog_mutex);
}

void
eventlog_fast_exit(void)
{
    uint res = 1; /* maybe nothing to do */
    d_r_mutex_lock(&shared_eventlog_connection->eventlog_mutex);
    if (shared_eventlog_connection->eventlog_pipe)
        res = eventlog_deregister(shared_eventlog_connection);
    shared_eventlog_connection->eventlog_pipe = 0;
    d_r_mutex_unlock(&shared_eventlog_connection->eventlog_mutex);
    DOLOG(
        1, LOG_TOP, if (!res) {
            LOG(GLOBAL, LOG_TOP, 1, "WARNING: DeregisterEventSource failed.\n");
        });
}

void
eventlog_slow_exit()
{
    ASSERT_CURIOSITY(shared_eventlog_connection->eventlog_pipe == 0 &&
                     "call after eventlog_fast_exit");
    /* syslog_mask is dynamic, so even if 0 now we init in case it changes later */
    DELETE_LOCK(shared_eventlog_connection->eventlog_mutex);
    ASSERT(shared_eventlog_connection != NULL &&
           shared_eventlog_connection != &temp_shared_eventlog_connection);
    HEAP_TYPE_FREE(GLOBAL_DCONTEXT, shared_eventlog_connection, eventlog_state_t,
                   ACCT_OTHER, PROTECTED);
    /* try to let syslogs during later cleanup go through
     * FIXME: won't re-deregister in that case
     */
    shared_eventlog_connection = &temp_shared_eventlog_connection;
    ASSIGN_INIT_LOCK_FREE(shared_eventlog_connection->eventlog_mutex, eventlog_mutex);
}

/* writes a message to the Windows Event Log */
static void
os_eventlog(syslog_event_type_t priority, uint message_id, uint substitutions_num,
            char **arguments, size_t size_data, char *raw_data)
{
    WORD native_priority = 0;
    WORD category = 0; /* we don't use any */
    uint res = 0;

    /* check mask on event_type whether to log this type of message */
    if (!TEST(priority, dynamo_options.syslog_mask))
        return;

    switch (priority) {
    case SYSLOG_INFORMATION: native_priority = EVENTLOG_INFORMATION_TYPE; break;
    case SYSLOG_WARNING: native_priority = EVENTLOG_WARNING_TYPE; break;
    case SYSLOG_CRITICAL: /* report as error */
    case SYSLOG_ERROR: native_priority = EVENTLOG_ERROR_TYPE; break;
    default: ASSERT_NOT_REACHED();
    }

    if (shared_eventlog_connection == NULL)
        eventlog_alloc();
    d_r_mutex_lock(&shared_eventlog_connection->eventlog_mutex);
    if (!shared_eventlog_connection->eventlog_pipe) {
        /* Retry to open connection, since may have been unable
           to do that early on for system services started before EventLog */
        res = eventlog_register(shared_eventlog_connection);
        if (!res) {
            DOLOG_ONCE(1, LOG_TOP, {
                LOG(GLOBAL, LOG_TOP, 1,
                    "WARNING: Could not register event source on second attempt.\n");
            });
        } else {
            LOG(GLOBAL, LOG_TOP, 1,
                "Registered event source after program started. "
                "Events may be missing. --ok\n");
        }
    }

    if (shared_eventlog_connection->eventlog_pipe) {
        // TODO: add current user SID (thread maybe impersonated)
        res = eventlog_report(shared_eventlog_connection, (WORD)native_priority, category,
                              message_id, NULL, /* pSID */
                              (WORD)substitutions_num, size_data, arguments, raw_data);
    }
    d_r_mutex_unlock(&shared_eventlog_connection->eventlog_mutex);

    DOLOG(
        1, LOG_TOP, if (!res) {
            LOG(GLOBAL, LOG_TOP, 1, "WARNING: Could not report event 0x%x. \n",
                message_id);
        });
}