/*
 * $Id$
 *
 * Sanity Checks Module
 *
 * Copyright (C) 2006 iptelorg GbmH
 *
 * This file is part of ser, a free SIP server.
 *
 * ser is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 * For a license to use the ser software under conditions
 * other than those described here, or to purchase support for this
 * software, please contact iptel.org by e-mail at the following addresses:
 *    info@iptel.org
 *
 * ser is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include "mod_sanity.h"
#include "sanity.h"
#include "api.h"
#include "../../sr_module.h"
#include "../../ut.h"
#include "../../error.h"

MODULE_VERSION

#define PROXY_REQUIRE_DEF 	""

str pr_str 	= STR_STATIC_INIT(PROXY_REQUIRE_DEF);

int default_msg_checks = SANITY_DEFAULT_CHECKS;
int default_uri_checks = SANITY_DEFAULT_URI_CHECKS;
int _sanity_drop = 1;

strl* proxyrequire_list = NULL;

sl_api_t slb;

static int mod_init(void);
static int sanity_fixup(void** param, int param_no);
static int w_sanity_check(struct sip_msg* _msg, char* _foo, char* _bar);
static int bind_sanity(sanity_api_t* api);

/*
 * Exported functions
 */
static cmd_export_t cmds[] = {
	{"sanity_check", (cmd_function)w_sanity_check, 0, 0,
		REQUEST_ROUTE|ONREPLY_ROUTE},
	{"sanity_check", (cmd_function)w_sanity_check, 1, sanity_fixup,
		REQUEST_ROUTE|ONREPLY_ROUTE},
	{"sanity_check", (cmd_function)w_sanity_check, 2, sanity_fixup,
		REQUEST_ROUTE|ONREPLY_ROUTE},
	{"bind_sanity",  (cmd_function)bind_sanity,    0, 0, 0},
	{0, 0, 0, 0}
};

/*
 * Exported parameters
 */
static param_export_t params[] = {
	{"default_checks",	PARAM_INT,	&default_msg_checks	},
	{"uri_checks",		PARAM_INT,	&default_uri_checks	},
	{"proxy_require",	PARAM_STR,	&pr_str			},
	{"autodrop",		PARAM_INT,	&_sanity_drop	},
	{0, 0, 0}
};

/*
 * Module description
 */
struct module_exports exports = {
	"sanity",        /* Module name */
	cmds,            /* Exported functions */
	0,               /* RPC methods */
	params,          /* Exported parameters */
	mod_init,        /* Initialization function */
	0,               /* Response function */
	0,               /* Destroy function */
	0,               /* OnCancel function */
	0                /* Child init function */
};

/*
 * initialize module
 */
static int mod_init(void) {
	strl* ptr;

	DBG("sanity initializing\n");

	/* bind the SL API */
	if (sl_load_api(&slb)!=0) {
		LM_ERR("cannot bind to SL API\n");
		return -1;
	}

	DBG("parsing proxy requires string:\n");
	ptr = parse_str_list(&pr_str);

	proxyrequire_list = ptr;

	while (ptr != NULL) {
		DBG("string: '%.*s', next: %p\n", ptr->string.len, ptr->string.s, ptr->next);
		ptr = ptr->next;
	}

	return 0;
}

static int sanity_fixup(void** param, int param_no) {
	int checks;
	str in;

	if (param_no == 1) {
		in.s = (char*)*param;
		in.len = strlen(in.s);
		if (str2int(&in, (unsigned int*)&checks) < 0) {
			LOG(L_ERR, "sanity: failed to convert input integer\n");
			return E_UNSPEC;
		}
		if ((checks < 1) || (checks >= (SANITY_MAX_CHECKS))) {
			LOG(L_ERR, "sanity: input parameter (%i) outside of valid range <1-%i)\n", checks, SANITY_MAX_CHECKS);
			return E_UNSPEC;
		}
		*param = (void*)(long)checks;
	}
	if (param_no == 2) {
		in.s = (char*)*param;
		in.len = strlen(in.s);
		if (str2int(&in, (unsigned int*)&checks) < 0) {
			LOG(L_ERR, "sanity: failed to convert second integer argument\n");
			return E_UNSPEC;
		}
		if ((checks < 1) || (checks >= (SANITY_URI_MAX_CHECKS))) {
			LOG(L_ERR, "sanity: second input parameter (%i) outside of valid range <1-%i\n", checks, SANITY_URI_MAX_CHECKS);
			return E_UNSPEC;
		}
		*param = (void*)(long)checks;
	}
	return 0;
}

/**
 * perform SIP message sanity check
 * @param _msg - SIP message structure
 * @param msg_checks - bitmask of sanity tests to perform over message
 * @param uri_checks - bitmask of sanity tests to perform over uri
 * @return -1 on error, 0 on tests failure, 1 on success
 */
int sanity_check(struct sip_msg* _msg, int msg_checks, int uri_checks)
{
	int ret;

	ret = SANITY_CHECK_PASSED;
	if (SANITY_RURI_SIP_VERSION & msg_checks &&
			(ret = check_ruri_sip_version(_msg)) != SANITY_CHECK_PASSED) {
		goto done;
	}
	if (SANITY_RURI_SCHEME & msg_checks &&
			(ret = check_ruri_scheme(_msg)) != SANITY_CHECK_PASSED) {
		goto done;
	}
	if (SANITY_REQUIRED_HEADERS & msg_checks &&
			(ret = check_required_headers(_msg)) != SANITY_CHECK_PASSED) {
		goto done;
	}
	if (SANITY_VIA_SIP_VERSION & msg_checks &&
			(ret = check_via_sip_version(_msg)) != SANITY_CHECK_PASSED) {
		goto done;
	}
	if (SANITY_VIA_PROTOCOL & msg_checks &&
			(ret = check_via_protocol(_msg)) != SANITY_CHECK_PASSED) {
		goto done;
	}
	if (SANITY_CSEQ_METHOD & msg_checks &&
			(ret = check_cseq_method(_msg)) != SANITY_CHECK_PASSED) {
		goto done;
	}
	if (SANITY_CSEQ_VALUE & msg_checks &&
			(ret = check_cseq_value(_msg)) != SANITY_CHECK_PASSED) {
		goto done;
	}
	if (SANITY_CL & msg_checks &&
			(ret = check_cl(_msg)) != SANITY_CHECK_PASSED) {
		goto done;
	}
	if (SANITY_EXPIRES_VALUE & msg_checks &&
			(ret = check_expires_value(_msg)) != SANITY_CHECK_PASSED) {
		goto done;
	}
	if (SANITY_PROXY_REQUIRE & msg_checks &&
			(ret = check_proxy_require(_msg)) != SANITY_CHECK_PASSED) {
		goto done;
	}
	if (SANITY_PARSE_URIS & msg_checks &&
			(ret = check_parse_uris(_msg, uri_checks)) != SANITY_CHECK_PASSED) {
		goto done;
	}

	if (SANITY_CHECK_DIGEST & msg_checks &&
			(ret = check_digest(_msg, uri_checks)) != SANITY_CHECK_PASSED) {
		goto done;
	}
	if (SANITY_CHECK_DUPTAGS & msg_checks &&
			(ret = check_duptags(_msg)) != SANITY_CHECK_PASSED) {
		goto done;
	}

done:
	return ret;
}

/**
 * do default checks
 */
int sanity_check_defaults(struct sip_msg* msg)
{
	return sanity_check(msg, default_msg_checks, default_uri_checks);
}

/**
 * wrapper for sanity_check() to be used from config file
 */
static int w_sanity_check(struct sip_msg* _msg, char* _number, char* _arg) {
	int ret, check, arg;

	if (_number == NULL) {
		check = default_msg_checks;
	}
	else {
		check = (int)(long)_number;
	}
	if (_arg == NULL) {
		arg = default_uri_checks;
	}
	else {
		arg = (int)(long)_arg;
	}
	ret = sanity_check(_msg, check, arg);

	DBG("sanity checks result: %d\n", ret);
	if(_sanity_drop!=0)
		return ret;
	return (ret==SANITY_CHECK_FAILED)?-1:ret;
}

/**
 * load sanity module API
 */
static int bind_sanity(sanity_api_t* api)
{
	if (!api) {
		ERR("Invalid parameter value\n");
		return -1;
	}
	api->check          = sanity_check;
	api->check_defaults = sanity_check_defaults;

	return 0;
}
