/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Event Log Service RPC (LOGR) interface definition.
 */
#include <sys/utsname.h>
#include <unistd.h>
#include <strings.h>
#include <smbsrv/libsmb.h>
#include <smbsrv/libmlrpc.h>
#include <smbsrv/ntstatus.h>
#include <smbsrv/nmpipes.h>
#include <smbsrv/libmlsvc.h>
#include <smbsrv/ndl/eventlog.ndl>
#include <smbsrv/nterror.h>


#define	LOGR_FWD		+1
#define	LOGR_REW		-1
#define	LOGR_RECORD_SIGNATURE	0x654C664C

#define	LOGR_PRI(p)		((p) & LOG_PRIMASK)
#define	LOGR_WNSTRLEN(S)	((strlen((S)) + 1) * sizeof (smb_wchar_t))

#define	LOGR_MSG_DWORD_OFFSET	12
#define	LOGR_MSG_WORD_OFFSET	4

/*
 * READ flags for EventLogRead
 *
 * EVENTLOG_SEEK_READ
 * The read operation proceeds from the record specified by the
 * dwRecordOffset parameter. This flag cannot be used with
 * EVENTLOG_SEQUENTIAL_READ.
 *
 * EVENTLOG_SEQUENTIAL_READ
 * The read operation proceeds sequentially from the last call to the
 * ReadEventLog function using this handle. This flag cannot be used
 * with EVENTLOG_SEEK_READ.
 *
 * If the buffer is large enough, more than one record can be read at
 * the specified seek position; you must specify one of the following
 * flags to indicate the direction for successive read operations.
 *
 * EVENTLOG_FORWARDS_READ
 * The log is read in chronological order. This flag cannot be used
 * with EVENTLOG_BACKWARDS_READ.
 *
 * EVENTLOG_BACKWARDS_READ
 * The log is read in reverse chronological order. This flag cannot be
 * used with EVENTLOG_FORWARDS_READ.
 */
#define	EVENTLOG_SEQUENTIAL_READ	0x0001
#define	EVENTLOG_SEEK_READ		0x0002
#define	EVENTLOG_FORWARDS_READ		0x0004
#define	EVENTLOG_BACKWARDS_READ		0x0008

/*
 * The types of events that can be logged.
 */
#define	EVENTLOG_SUCCESS		0x0000
#define	EVENTLOG_ERROR_TYPE		0x0001
#define	EVENTLOG_WARNING_TYPE		0x0002
#define	EVENTLOG_INFORMATION_TYPE	0x0004
#define	EVENTLOG_AUDIT_SUCCESS		0x0008
#define	EVENTLOG_AUDIT_FAILURE		0x0010

/*
 * Event Identifiers
 *
 * Event identifiers uniquely identify a particular event. Each event
 * source can define its own numbered events and the description strings
 * to which they are mapped. Event viewers can present these strings to
 * the user. They should help the user understand what went wrong and
 * suggest what actions to take. Direct the description at users solving
 * their own problems, not at administrators or support technicians.
 * Make the description clear and concise and avoid culture-specific
 * phrases.
 *
 * The following diagram illustrates the format of an event identifier.
 *
 *   3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
 *   1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
 *  +---+-+-+-----------------------+-------------------------------+
 *  |Sev|C|R|     Facility          |               Code            |
 *  +---+-+-+-----------------------+-------------------------------+
 *
 *  Sev
 *        Indicates the severity. This is one of the following values:
 *        00 - Success
 *        01 - Informational
 *        10 - Warning
 *        11 - Error
 *
 *  C
 *        Indicates a customer code (1) or a system code (0).
 *  R
 *        Reserved bit.
 *  Facility
 *        Facility code.
 *  Code
 *        Status code for the facility.
 */
#define	EVENTID_SEVERITY_SUCCESS	0x00000000
#define	EVENTID_SEVERITY_INFO		0x40000000
#define	EVENTID_SEVERITY_WARNING	0x80000000
#define	EVENTID_SEVERITY_ERROR		0xC0000000

#define	EVENTID_SYSTEM_CODE		0x00000000
#define	EVENTID_CUSTOMER_CODE		0x20000000

static int logr_s_EventLogClose(void *, ndr_xa_t *);
static int logr_s_EventLogQueryCount(void *, ndr_xa_t *);
static int logr_s_EventLogGetOldestRec(void *, ndr_xa_t *);
static int logr_s_EventLogOpen(void *, ndr_xa_t *);
static int logr_s_EventLogRead(void *, ndr_xa_t *);

static ndr_stub_table_t logr_stub_table[] = {
	{ logr_s_EventLogClose,		LOGR_OPNUM_EventLogClose },
	{ logr_s_EventLogQueryCount,	LOGR_OPNUM_EventLogQueryCount },
	{ logr_s_EventLogGetOldestRec,	LOGR_OPNUM_EventLogGetOldestRec },
	{ logr_s_EventLogOpen,		LOGR_OPNUM_EventLogOpen },
	{ logr_s_EventLogRead,		LOGR_OPNUM_EventLogRead },
	{0}
};

static ndr_service_t logr_service = {
	"LOGR",				/* name */
	"Event Log Service",		/* desc */
	"\\eventlog",			/* endpoint */
	PIPE_NTSVCS,			/* sec_addr_port */
	"82273fdc-e32a-18c3-3f78-827929dc23ea", 0,	/* abstract */
	NDR_TRANSFER_SYNTAX_UUID,		2,	/* transfer */
	0,				/* no bind_instance_size */
	0,				/* no bind_req() */
	0,				/* no unbind_and_close() */
	0,				/* use generic_call_stub() */
	&TYPEINFO(logr_interface),	/* interface ti */
	logr_stub_table			/* stub_table */
};

/*
 * logr_initialize
 *
 * This function registers the LOGR RPC interface with the RPC runtime
 * library. It must be called in order to use either the client side
 * or the server side functions.
 */
void
logr_initialize(void)
{
	(void) ndr_svc_register(&logr_service);
	logr_init();
}

void
logr_finalize(void)
{
	logr_fini();
}

/*
 * logr_hdlookup
 *
 * Handle lookup wrapper to validate the local service and/or manager context.
 */
static ndr_handle_t *
logr_hdlookup(ndr_xa_t *mxa, ndr_hdid_t *id)
{
	ndr_handle_t *hd;
	logr_context_t *ctx;

	if ((hd = ndr_hdlookup(mxa, id)) == NULL)
		return (NULL);

	if ((ctx = (logr_context_t *)hd->nh_data) == NULL)
		return (NULL);

	if (ctx->lc_source_name == NULL)
		return (NULL);

	return (hd);
}

/*
 * logr_context_data_free
 *
 * Callback to free the context data associated with local service
 * and/or manager context.
 */
static void
logr_context_data_free(void *ctxp)
{
	logr_context_t *ctx = (logr_context_t *)ctxp;

	if (ctx == NULL)
		return;

	free(ctx->lc_source_name);
	free(ctx->lc_cached_read_data->rd_log);
	free(ctx->lc_cached_read_data);
	free(ctx);
	ctx = NULL;
}

/*
 * logr_hdalloc
 *
 * Handle allocation wrapper to setup the local manager context.
 */
static ndr_hdid_t *
logr_hdalloc(ndr_xa_t *mxa, char *logname)
{
	logr_context_t *ctx;

	if ((ctx = malloc(sizeof (logr_context_t))) == NULL)
		return (NULL);
	bzero(ctx, sizeof (logr_context_t));

	ctx->lc_source_name = strdup(logname);
	if (ctx->lc_source_name == NULL) {
		free(ctx);
		return (NULL);
	}

	if (logr_get_snapshot(ctx) != 0) {
		free(ctx->lc_source_name);
		free(ctx);
		return (NULL);
	}

	return (ndr_hdalloc(mxa, ctx));
}

/*
 * logr_s_EventLogClose
 *
 * This is a request to close the LOGR interface specified by handle.
 * Free the handle and associated resources, and zero out the result
 * handle for the client.
 */
static int
logr_s_EventLogClose(void *arg, ndr_xa_t *mxa)
{
	struct logr_EventLogClose *param = arg;
	ndr_hdid_t *id = (ndr_hdid_t *)&param->handle;
	ndr_handle_t *hd;

	if ((hd = ndr_hdlookup(mxa, id)) == NULL) {
		bzero(&param->result_handle, sizeof (logr_handle_t));
		param->status = NT_SC_ERROR(NT_STATUS_INVALID_HANDLE);
		return (NDR_DRC_OK);
	}
	logr_context_data_free(hd->nh_data);
	ndr_hdfree(mxa, id);

	bzero(&param->result_handle, sizeof (logr_handle_t));
	param->status = NT_STATUS_SUCCESS;

	return (NDR_DRC_OK);
}

/*
 * logr_s_EventLogOpen
 *
 * Open the event log. Not supported yet.
 */
/*ARGSUSED*/
static int
logr_s_EventLogOpen(void *arg, ndr_xa_t *mxa)
{
	struct logr_EventLogOpen *param = arg;
	ndr_hdid_t *id = NULL;
	ndr_handle_t *hd;
	char *log_name = NULL;

	if (!ndr_is_admin(mxa)) {
		bzero(&param->handle, sizeof (logr_handle_t));
		param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED);
		return (NDR_DRC_OK);
	}

	if (param->log_name.length != 0)
		log_name = (char *)param->log_name.str;

	if (!logr_is_supported(log_name)) {
		bzero(&param->handle, sizeof (logr_handle_t));
		param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED);
		return (NDR_DRC_OK);
	}

	id = logr_hdalloc(mxa, log_name);
	if (id && ((hd = logr_hdlookup(mxa, id)) != NULL)) {
		hd->nh_data_free = logr_context_data_free;
		bcopy(id, &param->handle, sizeof (logr_handle_t));
		param->status = NT_STATUS_SUCCESS;
	} else {
		bzero(&param->handle, sizeof (logr_handle_t));
		param->status = NT_SC_ERROR(NT_STATUS_ACCESS_DENIED);
	}

	return (NDR_DRC_OK);
}

/*
 * logr_s_EventLogQueryCount
 *
 * take a snapshot from system log, assign it to the given handle.
 * return number of log entries in the snapshot as result of RPC
 * call.
 */
static int
logr_s_EventLogQueryCount(void *arg, ndr_xa_t *mxa)
{
	struct logr_EventLogQueryCount *param = arg;
	ndr_hdid_t *id = (ndr_hdid_t *)&param->handle;
	ndr_handle_t *hd;
	logr_context_t *ctx;
	logr_read_data_t *data;

	if ((hd = logr_hdlookup(mxa, id)) == NULL) {
		param->status = NT_SC_ERROR(NT_STATUS_INVALID_HANDLE);
		return (NDR_DRC_OK);
	}

	ctx = (logr_context_t *)hd->nh_data;
	data = ctx->lc_cached_read_data;

	param->rec_num = data->rd_tot_recnum;
	param->status = NT_STATUS_SUCCESS;
	return (NDR_DRC_OK);
}

/*
 * logr_s_EventLogGetOldestRec
 *
 * Return oldest record number in the snapshot as result of RPC call.
 */
static int
logr_s_EventLogGetOldestRec(void *arg, ndr_xa_t *mxa)
{
	struct logr_EventLogGetOldestRec *param = arg;
	ndr_hdid_t *id = (ndr_hdid_t *)&param->handle;
	ndr_handle_t *hd;
	logr_context_t *ctx;
	logr_read_data_t *data;

	if ((hd = logr_hdlookup(mxa, id)) == NULL) {
		param->status = NT_SC_ERROR(NT_STATUS_INVALID_HANDLE);
		return (NDR_DRC_OK);
	}

	ctx = (logr_context_t *)hd->nh_data;
	data = ctx->lc_cached_read_data;

	param->oldest_rec = data->rd_log->li_idx - data->rd_tot_recnum + 1;

	param->status = NT_STATUS_SUCCESS;
	return (NDR_DRC_OK);
}

/*
 * logr_set_event_typeid
 *
 * Map the local system log priority to the event type and event ID
 * for Windows events.
 */
void
logr_set_event_typeid(int le_pri, WORD *etype, DWORD *eid)
{
	switch (LOGR_PRI(le_pri)) {
	case LOG_EMERG:
	case LOG_ALERT:
	case LOG_CRIT:
	case LOG_ERR:
		*eid   = EVENTID_SEVERITY_ERROR;
		*etype = EVENTLOG_ERROR_TYPE;
		break;
	case LOG_WARNING:
		*eid   = EVENTID_SEVERITY_WARNING;
		*etype = EVENTLOG_WARNING_TYPE;
		break;
	case LOG_NOTICE:
	case LOG_INFO:
	case LOG_DEBUG:
		*eid   = EVENTID_SEVERITY_INFO;
		*etype = EVENTLOG_INFORMATION_TYPE;
		break;
	default:
		*eid   = EVENTID_SEVERITY_SUCCESS;
		*etype = EVENTLOG_SUCCESS;
	}
}

/*
 * logr_get_entry
 *
 * Gets a log entry.
 */
static logr_entry_t *
logr_get_entry(logr_info_t *linfo, int entno)
{
	return (&linfo->li_entry[entno]);
}

/*
 * logr_set_logrecord
 *
 * Fill a Windows event record based on a local system log record.
 */
static void
logr_set_logrecord(char *src_name, logr_entry_t *le,
    DWORD recno, logr_record_t *rec)
{
	int srcname_len = 0, hostname_len = 0, len;
	int str_offs, sh_len;
	smb_wchar_t wcs_hostname[MAXHOSTNAMELEN];
	smb_wchar_t wcs_srcname[SYS_NMLN * 2];

	(void) smb_mbstowcs(wcs_srcname, src_name,
	    strlen(src_name) + 1);
	srcname_len = LOGR_WNSTRLEN(src_name);

	/* Because, Solaris allows remote logging, need to get hostname here */
	(void) smb_mbstowcs(wcs_hostname, le->le_hostname,
	    strlen(le->le_hostname) + 1);
	hostname_len = LOGR_WNSTRLEN(le->le_hostname);

	sh_len = srcname_len + hostname_len;
	str_offs = LOGR_MSG_DWORD_OFFSET * sizeof (DWORD) +
	    LOGR_MSG_WORD_OFFSET * sizeof (WORD) + sh_len;

	rec->Length1 = sizeof (logr_record_t);
	rec->Reserved = LOGR_RECORD_SIGNATURE;
	rec->RecordNumber = recno;
	rec->TimeGenerated = le->le_timestamp.tv_sec;
	rec->TimeWritten = le->le_timestamp.tv_sec;
	logr_set_event_typeid(le->le_pri, &rec->EventType, &rec->EventID);
	rec->NumStrings = 1;
	rec->EventCategory = 0;
	rec->ReservedFlags = 0;
	rec->ClosingRecordNumber = 0;
	rec->StringOffset = str_offs;
	rec->UserSidLength = 0;
	rec->UserSidOffset = 0;
	rec->DataLength = 0;
	rec->DataOffset = 0;

	bzero(rec->info, LOGR_MAXENTRYLEN);
	(void) memcpy(rec->info, wcs_srcname, srcname_len);
	(void) memcpy(rec->info + srcname_len, wcs_hostname, hostname_len);

	len = strlen(le->le_msg) + 1;
	if (len > 0)
		/*LINTED E_BAD_PTR_CAST_ALIGN*/
		(void) smb_mbstowcs((smb_wchar_t *)(rec->info + sh_len),
		    le->le_msg, len);

	rec->Length2 = sizeof (logr_record_t);
}

/*
 * logr_s_EventLogRead
 *
 * Reads a whole number of entries from system log. The function can
 * read log entries in chronological or reverse chronological order.
 */
static int
logr_s_EventLogRead(void *arg, ndr_xa_t *mxa)
{
	struct logr_EventLogRead *param = arg;
	ndr_hdid_t *id = (ndr_hdid_t *)&param->handle;
	ndr_handle_t *hd;
	logr_read_data_t *rdata;
	logr_entry_t *le;
	DWORD ent_no, ent_num, ent_remain;
	logr_record_t *rec;
	BYTE *buf;
	int dir, ent_per_req, iter;
	logr_context_t *ctx;

	if ((hd = logr_hdlookup(mxa, id)) == NULL) {
		param->status = NT_SC_ERROR(NT_STATUS_INVALID_HANDLE);
		return (NDR_DRC_OK);
	}

	ctx = (logr_context_t *)hd->nh_data;
	rdata = ctx->lc_cached_read_data;
	if (rdata == NULL) {
		param->status = NT_SC_ERROR(NT_STATUS_NO_MEMORY);
		return (NDR_DRC_OK);
	}

	dir = (param->read_flags & EVENTLOG_FORWARDS_READ) ?
	    LOGR_FWD : LOGR_REW;

	if (param->read_flags & EVENTLOG_SEEK_READ)
		rdata->rd_last_sentrec = param->rec_offset;
	else if (rdata->rd_first_read)
		/*
		 * set last record number which is read for
		 * the first iteration of sequential read.
		 */
		rdata->rd_last_sentrec = (dir == LOGR_FWD)
		    ? (rdata->rd_log->li_idx - rdata->rd_tot_recnum)
		    : rdata->rd_log->li_idx;

	ent_remain = (dir == LOGR_FWD)
	    ? (rdata->rd_tot_recnum - rdata->rd_last_sentrec)
	    : rdata->rd_last_sentrec;

	/*
	 * function should return as many whole log entries as
	 * will fit in the buffer; it should not return partial
	 * entries, even if there is room in the buffer.
	 */
	ent_per_req = param->nbytes_to_read / sizeof (logr_record_t);
	if (ent_remain > ent_per_req)
		ent_remain = ent_per_req;

	if (ent_remain == 0) {
		/*
		 * Send this error to Windows client so that it
		 * can figure out that there is no more record
		 * to read.
		 */
		param->buf = NDR_STRDUP(mxa, "");
		param->sent_size = 0;
		param->status = NT_SC_ERROR(NT_STATUS_END_OF_FILE);
		return (NDR_DRC_OK);
	}

	param->buf = NDR_MALLOC(mxa, param->nbytes_to_read);
	buf = (BYTE *)param->buf;

	for (ent_num = 0, ent_no = rdata->rd_last_sentrec;
	    ent_num < ent_remain; ent_num++, ent_no += dir) {

		iter = ent_no & LOGR_NMSGMASK;
		if (dir == LOGR_REW)
			iter = (ent_no - 1) & LOGR_NMSGMASK;

		le = logr_get_entry(rdata->rd_log, iter);

		/*LINTED E_BAD_PTR_CAST_ALIGN*/
		rec = (logr_record_t *)buf;
		logr_set_logrecord(ctx->lc_source_name, le, ent_no, rec);
		buf += sizeof (logr_record_t);
	}

	rdata->rd_last_sentrec = ent_no;
	rdata->rd_first_read = 0;

	param->sent_size = sizeof (logr_record_t) * ent_remain;
	param->status = NT_STATUS_SUCCESS;

	return (NDR_DRC_OK);
}