/*
 * Asterisk -- An open source telephony toolkit.
 *
 * Copyright (C) 2009, Digium, Inc.
 *
 * Russell Bryant <russell@digium.com>
 *
 * See http://www.asterisk.org for more information about
 * the Asterisk project. Please do not directly contact
 * any of the maintainers of this project for assistance;
 * the project provides a web site, mailing lists and IRC
 * channels for your use.
 *
 * This program is free software, distributed under the terms of
 * the GNU General Public License Version 2. See the LICENSE file
 * at the top of the source tree.
 */

/*!
 * \file
 *
 * \brief Security Event Reporting Helpers
 *
 * \author Russell Bryant <russell@digium.com>
 */

/*** MODULEINFO
	<support_level>core</support_level>
 ***/

#include "asterisk.h"

ASTERISK_FILE_VERSION(__FILE__, "$Revision: 376469 $")

#include "asterisk/utils.h"
#include "asterisk/strings.h"
#include "asterisk/network.h"
#include "asterisk/security_events.h"

static const size_t TIMESTAMP_STR_LEN = 32;

static const struct {
	const char *name;
	uint32_t version;
	enum ast_security_event_severity severity;
#define MAX_SECURITY_IES 12
	struct ast_security_event_ie_type required_ies[MAX_SECURITY_IES];
	struct ast_security_event_ie_type optional_ies[MAX_SECURITY_IES];
#undef MAX_SECURITY_IES
} sec_events[AST_SECURITY_EVENT_NUM_TYPES] = {

#define SEC_EVT_FIELD(e, field) (offsetof(struct ast_security_event_##e, field))

[AST_SECURITY_EVENT_FAILED_ACL] = {
	.name     = "FailedACL",
	.version  = AST_SECURITY_EVENT_FAILED_ACL_VERSION,
	.severity = AST_SECURITY_EVENT_SEVERITY_ERROR,
	.required_ies = {
		{ AST_EVENT_IE_EVENT_TV, 0 },
		{ AST_EVENT_IE_SEVERITY, 0 },
		{ AST_EVENT_IE_SERVICE, SEC_EVT_FIELD(common, service) },
		{ AST_EVENT_IE_EVENT_VERSION, SEC_EVT_FIELD(common, version) },
		{ AST_EVENT_IE_ACCOUNT_ID, SEC_EVT_FIELD(common, account_id) },
		{ AST_EVENT_IE_SESSION_ID, SEC_EVT_FIELD(common, session_id) },
		{ AST_EVENT_IE_LOCAL_ADDR, SEC_EVT_FIELD(common, local_addr) },
		{ AST_EVENT_IE_REMOTE_ADDR, SEC_EVT_FIELD(common, remote_addr) },
		{ AST_EVENT_IE_END, 0 }
	},
	.optional_ies = {
		{ AST_EVENT_IE_MODULE, SEC_EVT_FIELD(common, module) },
		{ AST_EVENT_IE_ACL_NAME, SEC_EVT_FIELD(failed_acl, acl_name) },
		{ AST_EVENT_IE_SESSION_TV, SEC_EVT_FIELD(common, session_tv) },
		{ AST_EVENT_IE_END, 0 }
	},
},

[AST_SECURITY_EVENT_INVAL_ACCT_ID] = {
	.name     = "InvalidAccountID",
	.version  = AST_SECURITY_EVENT_INVAL_ACCT_ID_VERSION,
	.severity = AST_SECURITY_EVENT_SEVERITY_ERROR,
	.required_ies = {
		{ AST_EVENT_IE_EVENT_TV, 0 },
		{ AST_EVENT_IE_SEVERITY, 0 },
		{ AST_EVENT_IE_SERVICE, SEC_EVT_FIELD(common, service) },
		{ AST_EVENT_IE_EVENT_VERSION, SEC_EVT_FIELD(common, version) },
		{ AST_EVENT_IE_ACCOUNT_ID, SEC_EVT_FIELD(common, account_id) },
		{ AST_EVENT_IE_SESSION_ID, SEC_EVT_FIELD(common, session_id) },
		{ AST_EVENT_IE_LOCAL_ADDR, SEC_EVT_FIELD(common, local_addr) },
		{ AST_EVENT_IE_REMOTE_ADDR, SEC_EVT_FIELD(common, remote_addr) },
		{ AST_EVENT_IE_END, 0 }
	},
	.optional_ies = {
		{ AST_EVENT_IE_MODULE, SEC_EVT_FIELD(common, module) },
		{ AST_EVENT_IE_SESSION_TV, SEC_EVT_FIELD(common, session_tv) },
		{ AST_EVENT_IE_END, 0 }
	},
},

[AST_SECURITY_EVENT_SESSION_LIMIT] = {
	.name     = "SessionLimit",
	.version  = AST_SECURITY_EVENT_SESSION_LIMIT_VERSION,
	.severity = AST_SECURITY_EVENT_SEVERITY_ERROR,
	.required_ies = {
		{ AST_EVENT_IE_EVENT_TV, 0 },
		{ AST_EVENT_IE_SEVERITY, 0 },
		{ AST_EVENT_IE_SERVICE, SEC_EVT_FIELD(common, service) },
		{ AST_EVENT_IE_EVENT_VERSION, SEC_EVT_FIELD(common, version) },
		{ AST_EVENT_IE_ACCOUNT_ID, SEC_EVT_FIELD(common, account_id) },
		{ AST_EVENT_IE_SESSION_ID, SEC_EVT_FIELD(common, session_id) },
		{ AST_EVENT_IE_LOCAL_ADDR, SEC_EVT_FIELD(common, local_addr) },
		{ AST_EVENT_IE_REMOTE_ADDR, SEC_EVT_FIELD(common, remote_addr) },
		{ AST_EVENT_IE_END, 0 }
	},
	.optional_ies = {
		{ AST_EVENT_IE_MODULE, SEC_EVT_FIELD(common, module) },
		{ AST_EVENT_IE_SESSION_TV, SEC_EVT_FIELD(common, session_tv) },
		{ AST_EVENT_IE_END, 0 }
	},
},

[AST_SECURITY_EVENT_MEM_LIMIT] = {
	.name     = "MemoryLimit",
	.version  = AST_SECURITY_EVENT_MEM_LIMIT_VERSION,
	.severity = AST_SECURITY_EVENT_SEVERITY_ERROR,
	.required_ies = {
		{ AST_EVENT_IE_EVENT_TV, 0 },
		{ AST_EVENT_IE_SEVERITY, 0 },
		{ AST_EVENT_IE_SERVICE, SEC_EVT_FIELD(common, service) },
		{ AST_EVENT_IE_EVENT_VERSION, SEC_EVT_FIELD(common, version) },
		{ AST_EVENT_IE_ACCOUNT_ID, SEC_EVT_FIELD(common, account_id) },
		{ AST_EVENT_IE_SESSION_ID, SEC_EVT_FIELD(common, session_id) },
		{ AST_EVENT_IE_LOCAL_ADDR, SEC_EVT_FIELD(common, local_addr) },
		{ AST_EVENT_IE_REMOTE_ADDR, SEC_EVT_FIELD(common, remote_addr) },
		{ AST_EVENT_IE_END, 0 }
	},
	.optional_ies = {
		{ AST_EVENT_IE_MODULE, SEC_EVT_FIELD(common, module) },
		{ AST_EVENT_IE_SESSION_TV, SEC_EVT_FIELD(common, session_tv) },
		{ AST_EVENT_IE_END, 0 }
	},
},

[AST_SECURITY_EVENT_LOAD_AVG] = {
	.name     = "LoadAverageLimit",
	.version  = AST_SECURITY_EVENT_LOAD_AVG_VERSION,
	.severity = AST_SECURITY_EVENT_SEVERITY_ERROR,
	.required_ies = {
		{ AST_EVENT_IE_EVENT_TV, 0 },
		{ AST_EVENT_IE_SEVERITY, 0 },
		{ AST_EVENT_IE_SERVICE, SEC_EVT_FIELD(common, service) },
		{ AST_EVENT_IE_EVENT_VERSION, SEC_EVT_FIELD(common, version) },
		{ AST_EVENT_IE_ACCOUNT_ID, SEC_EVT_FIELD(common, account_id) },
		{ AST_EVENT_IE_SESSION_ID, SEC_EVT_FIELD(common, session_id) },
		{ AST_EVENT_IE_LOCAL_ADDR, SEC_EVT_FIELD(common, local_addr) },
		{ AST_EVENT_IE_REMOTE_ADDR, SEC_EVT_FIELD(common, remote_addr) },
		{ AST_EVENT_IE_END, 0 }
	},
	.optional_ies = {
		{ AST_EVENT_IE_MODULE, SEC_EVT_FIELD(common, module) },
		{ AST_EVENT_IE_SESSION_TV, SEC_EVT_FIELD(common, session_tv) },
		{ AST_EVENT_IE_END, 0 }
	},
},

[AST_SECURITY_EVENT_REQ_NO_SUPPORT] = {
	.name     = "RequestNotSupported",
	.version  = AST_SECURITY_EVENT_REQ_NO_SUPPORT_VERSION,
	.severity = AST_SECURITY_EVENT_SEVERITY_ERROR,
	.required_ies = {
		{ AST_EVENT_IE_EVENT_TV, 0 },
		{ AST_EVENT_IE_SEVERITY, 0 },
		{ AST_EVENT_IE_SERVICE, SEC_EVT_FIELD(common, service) },
		{ AST_EVENT_IE_EVENT_VERSION, SEC_EVT_FIELD(common, version) },
		{ AST_EVENT_IE_ACCOUNT_ID, SEC_EVT_FIELD(common, account_id) },
		{ AST_EVENT_IE_SESSION_ID, SEC_EVT_FIELD(common, session_id) },
		{ AST_EVENT_IE_LOCAL_ADDR, SEC_EVT_FIELD(common, local_addr) },
		{ AST_EVENT_IE_REMOTE_ADDR, SEC_EVT_FIELD(common, remote_addr) },
		{ AST_EVENT_IE_REQUEST_TYPE, SEC_EVT_FIELD(req_no_support, request_type) },
		{ AST_EVENT_IE_END, 0 }
	},
	.optional_ies = {
		{ AST_EVENT_IE_MODULE, SEC_EVT_FIELD(common, module) },
		{ AST_EVENT_IE_SESSION_TV, SEC_EVT_FIELD(common, session_tv) },
		{ AST_EVENT_IE_END, 0 }
	},
},

[AST_SECURITY_EVENT_REQ_NOT_ALLOWED] = {
	.name     = "RequestNotAllowed",
	.version  = AST_SECURITY_EVENT_REQ_NOT_ALLOWED_VERSION,
	.severity = AST_SECURITY_EVENT_SEVERITY_ERROR,
	.required_ies = {
		{ AST_EVENT_IE_EVENT_TV, 0 },
		{ AST_EVENT_IE_SEVERITY, 0 },
		{ AST_EVENT_IE_SERVICE, SEC_EVT_FIELD(common, service) },
		{ AST_EVENT_IE_EVENT_VERSION, SEC_EVT_FIELD(common, version) },
		{ AST_EVENT_IE_ACCOUNT_ID, SEC_EVT_FIELD(common, account_id) },
		{ AST_EVENT_IE_SESSION_ID, SEC_EVT_FIELD(common, session_id) },
		{ AST_EVENT_IE_LOCAL_ADDR, SEC_EVT_FIELD(common, local_addr) },
		{ AST_EVENT_IE_REMOTE_ADDR, SEC_EVT_FIELD(common, remote_addr) },
		{ AST_EVENT_IE_REQUEST_TYPE, SEC_EVT_FIELD(req_not_allowed, request_type) },
		{ AST_EVENT_IE_END, 0 }
	},
	.optional_ies = {
		{ AST_EVENT_IE_MODULE, SEC_EVT_FIELD(common, module) },
		{ AST_EVENT_IE_SESSION_TV, SEC_EVT_FIELD(common, session_tv) },
		{ AST_EVENT_IE_REQUEST_PARAMS, SEC_EVT_FIELD(req_not_allowed, request_params) },
		{ AST_EVENT_IE_END, 0 }
	},
},

[AST_SECURITY_EVENT_AUTH_METHOD_NOT_ALLOWED] = {
	.name     = "AuthMethodNotAllowed",
	.version  = AST_SECURITY_EVENT_AUTH_METHOD_NOT_ALLOWED_VERSION,
	.severity = AST_SECURITY_EVENT_SEVERITY_ERROR,
	.required_ies = {
		{ AST_EVENT_IE_EVENT_TV, 0 },
		{ AST_EVENT_IE_SEVERITY, 0 },
		{ AST_EVENT_IE_SERVICE, SEC_EVT_FIELD(common, service) },
		{ AST_EVENT_IE_EVENT_VERSION, SEC_EVT_FIELD(common, version) },
		{ AST_EVENT_IE_ACCOUNT_ID, SEC_EVT_FIELD(common, account_id) },
		{ AST_EVENT_IE_SESSION_ID, SEC_EVT_FIELD(common, session_id) },
		{ AST_EVENT_IE_LOCAL_ADDR, SEC_EVT_FIELD(common, local_addr) },
		{ AST_EVENT_IE_REMOTE_ADDR, SEC_EVT_FIELD(common, remote_addr) },
		{ AST_EVENT_IE_AUTH_METHOD, SEC_EVT_FIELD(auth_method_not_allowed, auth_method) },
		{ AST_EVENT_IE_END, 0 }
	},
	.optional_ies = {
		{ AST_EVENT_IE_MODULE, SEC_EVT_FIELD(common, module) },
		{ AST_EVENT_IE_SESSION_TV, SEC_EVT_FIELD(common, session_tv) },
		{ AST_EVENT_IE_END, 0 }
	},
},

[AST_SECURITY_EVENT_REQ_BAD_FORMAT] = {
	.name     = "RequestBadFormat",
	.version  = AST_SECURITY_EVENT_REQ_BAD_FORMAT_VERSION,
	.severity = AST_SECURITY_EVENT_SEVERITY_ERROR,
	.required_ies = {
		{ AST_EVENT_IE_EVENT_TV, 0 },
		{ AST_EVENT_IE_SEVERITY, 0 },
		{ AST_EVENT_IE_SERVICE, SEC_EVT_FIELD(common, service) },
		{ AST_EVENT_IE_EVENT_VERSION, SEC_EVT_FIELD(common, version) },
		{ AST_EVENT_IE_SESSION_ID, SEC_EVT_FIELD(common, session_id) },
		{ AST_EVENT_IE_LOCAL_ADDR, SEC_EVT_FIELD(common, local_addr) },
		{ AST_EVENT_IE_REMOTE_ADDR, SEC_EVT_FIELD(common, remote_addr) },
		{ AST_EVENT_IE_REQUEST_TYPE, SEC_EVT_FIELD(req_bad_format, request_type) },
		{ AST_EVENT_IE_END, 0 }
	},
	.optional_ies = {
		{ AST_EVENT_IE_MODULE, SEC_EVT_FIELD(common, module) },
		{ AST_EVENT_IE_SESSION_TV, SEC_EVT_FIELD(common, session_tv) },
		{ AST_EVENT_IE_ACCOUNT_ID, SEC_EVT_FIELD(common, account_id) },
		{ AST_EVENT_IE_REQUEST_PARAMS, SEC_EVT_FIELD(req_bad_format, request_params) },
		{ AST_EVENT_IE_END, 0 }
	},
},

[AST_SECURITY_EVENT_SUCCESSFUL_AUTH] = {
	.name     = "SuccessfulAuth",
	.version  = AST_SECURITY_EVENT_SUCCESSFUL_AUTH_VERSION,
	.severity = AST_SECURITY_EVENT_SEVERITY_INFO,
	.required_ies = {
		{ AST_EVENT_IE_EVENT_TV, 0 },
		{ AST_EVENT_IE_SEVERITY, 0 },
		{ AST_EVENT_IE_SERVICE, SEC_EVT_FIELD(common, service) },
		{ AST_EVENT_IE_EVENT_VERSION, SEC_EVT_FIELD(common, version) },
		{ AST_EVENT_IE_ACCOUNT_ID, SEC_EVT_FIELD(common, account_id) },
		{ AST_EVENT_IE_SESSION_ID, SEC_EVT_FIELD(common, session_id) },
		{ AST_EVENT_IE_LOCAL_ADDR, SEC_EVT_FIELD(common, local_addr) },
		{ AST_EVENT_IE_REMOTE_ADDR, SEC_EVT_FIELD(common, remote_addr) },
		{ AST_EVENT_IE_END, 0 }
	},
	.optional_ies = {
		{ AST_EVENT_IE_MODULE, SEC_EVT_FIELD(common, module) },
		{ AST_EVENT_IE_SESSION_TV, SEC_EVT_FIELD(common, session_tv) },
		{ AST_EVENT_IE_END, 0 }
	},
},

[AST_SECURITY_EVENT_UNEXPECTED_ADDR] = {
	.name     = "UnexpectedAddress",
	.version  = AST_SECURITY_EVENT_UNEXPECTED_ADDR_VERSION,
	.severity = AST_SECURITY_EVENT_SEVERITY_ERROR,
	.required_ies = {
		{ AST_EVENT_IE_EVENT_TV, 0 },
		{ AST_EVENT_IE_SEVERITY, 0 },
		{ AST_EVENT_IE_SERVICE, SEC_EVT_FIELD(common, service) },
		{ AST_EVENT_IE_EVENT_VERSION, SEC_EVT_FIELD(common, version) },
		{ AST_EVENT_IE_ACCOUNT_ID, SEC_EVT_FIELD(common, account_id) },
		{ AST_EVENT_IE_SESSION_ID, SEC_EVT_FIELD(common, session_id) },
		{ AST_EVENT_IE_LOCAL_ADDR, SEC_EVT_FIELD(common, local_addr) },
		{ AST_EVENT_IE_REMOTE_ADDR, SEC_EVT_FIELD(common, remote_addr) },
		{ AST_EVENT_IE_EXPECTED_ADDR, SEC_EVT_FIELD(unexpected_addr, expected_addr) },
		{ AST_EVENT_IE_END, 0 }
	},
	.optional_ies = {
		{ AST_EVENT_IE_MODULE, SEC_EVT_FIELD(common, module) },
		{ AST_EVENT_IE_SESSION_TV, SEC_EVT_FIELD(common, session_tv) },
		{ AST_EVENT_IE_END, 0 }
	},
},

[AST_SECURITY_EVENT_CHAL_RESP_FAILED] = {
	.name     = "ChallengeResponseFailed",
	.version  = AST_SECURITY_EVENT_CHAL_RESP_FAILED_VERSION,
	.severity = AST_SECURITY_EVENT_SEVERITY_ERROR,
	.required_ies = {
		{ AST_EVENT_IE_EVENT_TV, 0 },
		{ AST_EVENT_IE_SEVERITY, 0 },
		{ AST_EVENT_IE_SERVICE, SEC_EVT_FIELD(common, service) },
		{ AST_EVENT_IE_EVENT_VERSION, SEC_EVT_FIELD(common, version) },
		{ AST_EVENT_IE_ACCOUNT_ID, SEC_EVT_FIELD(common, account_id) },
		{ AST_EVENT_IE_SESSION_ID, SEC_EVT_FIELD(common, session_id) },
		{ AST_EVENT_IE_LOCAL_ADDR, SEC_EVT_FIELD(common, local_addr) },
		{ AST_EVENT_IE_REMOTE_ADDR, SEC_EVT_FIELD(common, remote_addr) },
		{ AST_EVENT_IE_CHALLENGE, SEC_EVT_FIELD(chal_resp_failed, challenge) },
		{ AST_EVENT_IE_RESPONSE, SEC_EVT_FIELD(chal_resp_failed, response) },
		{ AST_EVENT_IE_EXPECTED_RESPONSE, SEC_EVT_FIELD(chal_resp_failed, expected_response) },
		{ AST_EVENT_IE_END, 0 }
	},
	.optional_ies = {
		{ AST_EVENT_IE_MODULE, SEC_EVT_FIELD(common, module) },
		{ AST_EVENT_IE_SESSION_TV, SEC_EVT_FIELD(common, session_tv) },
		{ AST_EVENT_IE_END, 0 }
	},
},

[AST_SECURITY_EVENT_INVAL_PASSWORD] = {
	.name     = "InvalidPassword",
	.version  = AST_SECURITY_EVENT_INVAL_PASSWORD_VERSION,
	.severity = AST_SECURITY_EVENT_SEVERITY_ERROR,
	.required_ies = {
		{ AST_EVENT_IE_EVENT_TV, 0 },
		{ AST_EVENT_IE_SEVERITY, 0 },
		{ AST_EVENT_IE_SERVICE, SEC_EVT_FIELD(common, service) },
		{ AST_EVENT_IE_EVENT_VERSION, SEC_EVT_FIELD(common, version) },
		{ AST_EVENT_IE_ACCOUNT_ID, SEC_EVT_FIELD(common, account_id) },
		{ AST_EVENT_IE_SESSION_ID, SEC_EVT_FIELD(common, session_id) },
		{ AST_EVENT_IE_LOCAL_ADDR, SEC_EVT_FIELD(common, local_addr) },
		{ AST_EVENT_IE_REMOTE_ADDR, SEC_EVT_FIELD(common, remote_addr) },
		{ AST_EVENT_IE_END, 0 }
	},
	.optional_ies = {
		{ AST_EVENT_IE_MODULE, SEC_EVT_FIELD(common, module) },
		{ AST_EVENT_IE_SESSION_TV, SEC_EVT_FIELD(common, session_tv) },
		{ AST_EVENT_IE_END, 0 }
	},
},

#undef SEC_EVT_FIELD

};

static const struct {
	enum ast_security_event_severity severity;
	const char *str;
} severities[] = {
	{ AST_SECURITY_EVENT_SEVERITY_INFO,  "Informational" },
	{ AST_SECURITY_EVENT_SEVERITY_ERROR, "Error" },
};

const char *ast_security_event_severity_get_name(
		const enum ast_security_event_severity severity)
{
	unsigned int i;

	for (i = 0; i < ARRAY_LEN(severities); i++) {
		if (severities[i].severity == severity) {
			return severities[i].str;
		}
	}

	return NULL;
}

static int check_event_type(const enum ast_security_event_type event_type)
{
	if (event_type < 0 || event_type >= AST_SECURITY_EVENT_NUM_TYPES) {
		ast_log(LOG_ERROR, "Invalid security event type %u\n", event_type);
		return -1;
	}

	return 0;
}

const char *ast_security_event_get_name(const enum ast_security_event_type event_type)
{
	if (check_event_type(event_type)) {
		return NULL;
	}

	return sec_events[event_type].name;
}

const struct ast_security_event_ie_type *ast_security_event_get_required_ies(
		const enum ast_security_event_type event_type)
{
	if (check_event_type(event_type)) {
		return NULL;
	}

	return sec_events[event_type].required_ies;
}

const struct ast_security_event_ie_type *ast_security_event_get_optional_ies(
		const enum ast_security_event_type event_type)
{
	if (check_event_type(event_type)) {
		return NULL;
	}

	return sec_events[event_type].optional_ies;
}

static void encode_timestamp(struct ast_str **str, const struct timeval *tv)
{
	ast_str_set(str, 0, "%u-%u",
			(unsigned int) tv->tv_sec,
			(unsigned int) tv->tv_usec);
}

static struct ast_event *alloc_event(const struct ast_security_event_common *sec)
{
	struct ast_str *str = ast_str_alloca(TIMESTAMP_STR_LEN);
	struct timeval tv = ast_tvnow();
	const char *severity_str;

	if (check_event_type(sec->event_type)) {
		return NULL;
	}

	encode_timestamp(&str, &tv);

	severity_str = S_OR(
		ast_security_event_severity_get_name(sec_events[sec->event_type].severity),
		"Unknown"
	);

	return ast_event_new(AST_EVENT_SECURITY,
		AST_EVENT_IE_SECURITY_EVENT, AST_EVENT_IE_PLTYPE_UINT, sec->event_type,
		AST_EVENT_IE_EVENT_VERSION, AST_EVENT_IE_PLTYPE_UINT, sec->version,
		AST_EVENT_IE_EVENT_TV, AST_EVENT_IE_PLTYPE_STR, ast_str_buffer(str),
		AST_EVENT_IE_SERVICE, AST_EVENT_IE_PLTYPE_STR, sec->service,
		AST_EVENT_IE_SEVERITY, AST_EVENT_IE_PLTYPE_STR, severity_str,
		AST_EVENT_IE_END);
}

static int add_timeval_ie(struct ast_event **event, enum ast_event_ie_type ie_type,
		const struct timeval *tv)
{
	struct ast_str *str = ast_str_alloca(TIMESTAMP_STR_LEN);

	encode_timestamp(&str, tv);

	return ast_event_append_ie_str(event, ie_type, ast_str_buffer(str));
}

static int add_ipv4_ie(struct ast_event **event, enum ast_event_ie_type ie_type,
		const struct ast_security_event_ipv4_addr *addr)
{
	struct ast_str *str = ast_str_alloca(64);

	ast_str_set(&str, 0, "IPV4/");

	switch (addr->transport) {
	case AST_SECURITY_EVENT_TRANSPORT_UDP:
		ast_str_append(&str, 0, "UDP/");
		break;
	case AST_SECURITY_EVENT_TRANSPORT_TCP:
		ast_str_append(&str, 0, "TCP/");
		break;
	case AST_SECURITY_EVENT_TRANSPORT_TLS:
		ast_str_append(&str, 0, "TLS/");
		break;
	}

	ast_str_append(&str, 0, "%s/%hu",
			ast_inet_ntoa(addr->sin->sin_addr),
			ntohs(addr->sin->sin_port));

	return ast_event_append_ie_str(event, ie_type, ast_str_buffer(str));
}

enum ie_required {
	NOT_REQUIRED,
	REQUIRED
};

static int add_ie(struct ast_event **event, const struct ast_security_event_common *sec,
		const struct ast_security_event_ie_type *ie_type, enum ie_required req)
{
	int res = 0;

	switch (ie_type->ie_type) {
	case AST_EVENT_IE_SERVICE:
	case AST_EVENT_IE_ACCOUNT_ID:
	case AST_EVENT_IE_SESSION_ID:
	case AST_EVENT_IE_MODULE:
	case AST_EVENT_IE_ACL_NAME:
	case AST_EVENT_IE_REQUEST_TYPE:
	case AST_EVENT_IE_REQUEST_PARAMS:
	case AST_EVENT_IE_AUTH_METHOD:
	case AST_EVENT_IE_CHALLENGE:
	case AST_EVENT_IE_RESPONSE:
	case AST_EVENT_IE_EXPECTED_RESPONSE:
	{
		const char *str;

		str = *((const char **)(((const char *) sec) + ie_type->offset));

		if (req && !str) {
			ast_log(LOG_WARNING, "Required IE '%d' for security event "
					"type '%d' not present\n", ie_type->ie_type,
					sec->event_type);
			res = -1;
		}

		if (str) {
			res = ast_event_append_ie_str(event, ie_type->ie_type, str);
		}

		break;
	}
	case AST_EVENT_IE_EVENT_VERSION:
	{
		uint32_t val;
		val = *((const uint32_t *)(((const char *) sec) + ie_type->offset));
		res = ast_event_append_ie_uint(event, ie_type->ie_type, val);
		break;
	}
	case AST_EVENT_IE_LOCAL_ADDR:
	case AST_EVENT_IE_REMOTE_ADDR:
	case AST_EVENT_IE_EXPECTED_ADDR:
	{
		const struct ast_security_event_ipv4_addr *addr;

		addr = (const struct ast_security_event_ipv4_addr *)(((const char *) sec) + ie_type->offset);

		if (req && !addr->sin) {
			ast_log(LOG_WARNING, "Required IE '%d' for security event "
					"type '%d' not present\n", ie_type->ie_type,
					sec->event_type);
			res = -1;
		}

		if (addr->sin) {
			res = add_ipv4_ie(event, ie_type->ie_type, addr);
		}
		break;
	}
	case AST_EVENT_IE_SESSION_TV:
	{
		const struct timeval *tval;

		tval = *((const struct timeval **)(((const char *) sec) + ie_type->offset));

		if (req && !tval) {
			ast_log(LOG_WARNING, "Required IE '%d' for security event "
					"type '%d' not present\n", ie_type->ie_type,
					sec->event_type);
			res = -1;
		}

		if (tval) {
			add_timeval_ie(event, ie_type->ie_type, tval);
		}

		break;
	}
	case AST_EVENT_IE_EVENT_TV:
	case AST_EVENT_IE_SEVERITY:
		/* Added automatically, nothing to do here. */
		break;
	default:
		ast_log(LOG_WARNING, "Unhandled IE type '%d', this security event "
				"will be missing data.\n", ie_type->ie_type);
		break;
	}

	return res;
}

static int handle_security_event(const struct ast_security_event_common *sec)
{
	struct ast_event *event;
	const struct ast_security_event_ie_type *ies;
	unsigned int i;

	if (!(event = alloc_event(sec))) {
		return -1;
	}

	for (ies = ast_security_event_get_required_ies(sec->event_type), i = 0;
			ies[i].ie_type != AST_EVENT_IE_END;
			i++) {
		if (add_ie(&event, sec, ies + i, REQUIRED)) {
			goto return_error;
		}
	}

	for (ies = ast_security_event_get_optional_ies(sec->event_type), i = 0;
			ies[i].ie_type != AST_EVENT_IE_END;
			i++) {
		if (add_ie(&event, sec, ies + i, NOT_REQUIRED)) {
			goto return_error;
		}
	}


	if (ast_event_queue(event)) {
		goto return_error;
	}

	return 0;

return_error:
	if (event) {
		ast_event_destroy(event);
	}

	return -1;
}

int ast_security_event_report(const struct ast_security_event_common *sec)
{
	int res;

	if (sec->event_type < 0 || sec->event_type >= AST_SECURITY_EVENT_NUM_TYPES) {
		ast_log(LOG_ERROR, "Invalid security event type\n");
		return -1;
	}

	if (!sec_events[sec->event_type].name) {
		ast_log(LOG_WARNING, "Security event type %u not handled\n",
				sec->event_type);
		return -1;
	}

	if (sec->version != sec_events[sec->event_type].version) {
		ast_log(LOG_WARNING, "Security event %u version mismatch\n",
				sec->event_type);
		return -1;
	}

	res = handle_security_event(sec);

	return res;
}


