/*
 * Copyright (C) 2012 Smile Communications, jason.penton@smilecoms.com
 * Copyright (C) 2012 Smile Communications, richard.good@smilecoms.com
 *
 * The initial version of this code was written by Dragos Vingarzan
 * (dragos(dot)vingarzan(at)fokus(dot)fraunhofer(dot)de and the
 * Fraunhofer FOKUS Institute. It was and still is maintained in a separate
 * branch of the original SER. We are therefore migrating it to
 * Kamailio/SR and look forward to maintaining it from here on out.
 * 2011/2012 Smile Communications, Pty. Ltd.
 * ported/maintained/improved by
 * Jason Penton (jason(dot)penton(at)smilecoms.com and
 * Richard Good (richard(dot)good(at)smilecoms.com) as part of an
 * effort to add full IMS support to Kamailio/SR using a new and
 * improved architecture
 *
 * NB: A lot of this code was originally part of OpenIMSCore,
 * FhG Fokus.
 * Copyright (C) 2004-2006 FhG Fokus
 * Thanks for great work! This is an effort to
 * break apart the various CSCF functions into logically separate
 * components. We hope this will drive wider use. We also feel
 * that in this way the architecture is more complete and thereby easier
 * to manage in the Kamailio/SR environment
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * Kamailio 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
 *
 * Kamailio 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */


#include "utils.h"

/*
 * Find credentials with given realm in a SIP message header
 */
int ims_find_credentials(struct sip_msg *_m, str *_realm, hdr_types_t _hftype,
		struct hdr_field **_h)
{
	struct hdr_field **hook, *ptr, *prev;
	hdr_flags_t hdr_flags;
	int res;
	str *r;

	LM_DBG("Searching credentials in realm [%.*s]\n", _realm->len, _realm->s);

	/*
	 * Determine if we should use WWW-Authorization or
	 * Proxy-Authorization header fields, this parameter
	 * is set in www_authorize and proxy_authorize
	 */
	switch(_hftype) {
		case HDR_AUTHORIZATION_T:
			hook = &(_m->authorization);
			hdr_flags = HDR_AUTHORIZATION_F;
			break;
		case HDR_PROXYAUTH_T:
			hook = &(_m->proxy_auth);
			hdr_flags = HDR_PROXYAUTH_F;
			break;
		default:
			LM_WARN("unexpected header type %d - using authorization\n",
					_hftype);
			hook = &(_m->authorization);
			hdr_flags = HDR_AUTHORIZATION_F;
			break;
	}

	/*
	 * If the credentials haven't been parsed yet, do it now
	 */
	if(*hook == 0) {
		/* No credentials parsed yet */
		LM_DBG("*hook == 0, No credentials parsed yet\n");
		if(parse_headers(_m, hdr_flags, 0) == -1) {
			LM_ERR("Error while parsing headers\n");
			return -1;
		}
	}

	ptr = *hook;
	LM_DBG("*hook = %p\n", ptr);
	/*
	 * Iterate through the credentials in the message and
	 * find credentials with given realm
	 */
	while(ptr) {
		res = parse_credentials(ptr);
		if(res < 0) {
			LM_ERR("Error while parsing credentials\n");
			return (res == -1) ? -2 : -3;
		} else if(res == 0) {
			LM_DBG("Credential parsed successfully\n");
			if(_realm->len) {
				r = &(((auth_body_t *)(ptr->parsed))->digest.realm);
				LM_DBG("Comparing realm <%.*s> and <%.*s>\n", _realm->len,
						_realm->s, r->len, r->s);
				if(r->len == _realm->len) {
					if(!strncasecmp(_realm->s, r->s, r->len)) {
						*_h = ptr;
						return 0;
					}
				}
			} else {
				*_h = ptr;
				return 0;
			}
		}

		prev = ptr;
		if(parse_headers(_m, hdr_flags, 1) == -1) {
			LM_ERR("Error while parsing headers\n");
			return -4;
		} else {
			if(prev != _m->last_header) {
				if(_m->last_header->type == _hftype)
					ptr = _m->last_header;
				else
					break;
			} else
				break;
		}
	}

	/*
	 * Credentials with given realm not found
	 */
	LM_DBG("Credentials with given realm not found\n");
	return 1;
}

/**
 * Looks for the nonce and response parameters in the Authorization header and returns them
 * @param msg - the SIP message
 * @param realm - realm to match the right Authorization header
 * @param nonce - param to fill with the nonce found
 * @param response - param to fill with the response
 * @returns 1 if found, 0 if not
 */
int get_nonce_response(struct sip_msg *msg, str *username, str realm,
		str *nonce, str *response, enum qop_type *qop, str *qop_str, str *nc,
		str *cnonce, str *uri, int is_proxy_auth)
{
	struct hdr_field *h = 0;
	int ret;


	ret = parse_headers(
			msg, is_proxy_auth ? HDR_PROXYAUTH_F : HDR_AUTHORIZATION_F, 0);

	if(ret != 0) {
		return 0;
	}

	if((!is_proxy_auth && !msg->authorization)
			|| (is_proxy_auth && !msg->proxy_auth)) {
		return 0;
	}

	LM_DBG("Calling find_credentials with realm [%.*s]\n", realm.len, realm.s);
	ret = ims_find_credentials(msg, &realm,
			is_proxy_auth ? HDR_PROXYAUTH_T : HDR_AUTHORIZATION_T, &h);
	if(ret < 0) {
		return 0;
	} else if(ret > 0) {
		LM_DBG("ret > 0");
		return 0;
	}

	if(h && h->parsed) {
		if(nonce)
			*nonce = ((auth_body_t *)h->parsed)->digest.nonce;
		if(response)
			*response = ((auth_body_t *)h->parsed)->digest.response;
		if(qop)
			*qop = ((auth_body_t *)h->parsed)->digest.qop.qop_parsed;
		if(qop_str)
			*qop_str = ((auth_body_t *)h->parsed)->digest.qop.qop_str;
		if(nc)
			*nc = ((auth_body_t *)h->parsed)->digest.nc;
		if(cnonce)
			*cnonce = ((auth_body_t *)h->parsed)->digest.cnonce;
		if(uri)
			*uri = ((auth_body_t *)h->parsed)->digest.uri;
		if(username)
			*username = ((auth_body_t *)h->parsed)->digest.username.whole;
	}
	LM_DBG("Found nonce response\n");
	return 1;
}

str ims_get_body(struct sip_msg *msg)
{
	str x = {0, 0};

	if(parse_headers(msg, HDR_CONTENTLENGTH_F, 0) != 0) {
		LM_DBG("Error parsing until header Content-Length: \n");
		return x;
	}
	if(msg->content_length)
		// Content-Length header might be missing
		x.len = (int)(long)msg->content_length->parsed;

	if(x.len > 0)
		x.s = get_body(msg);

	return x;
}


/**
 * Looks for the auts parameter in the Authorization header and returns its value.
 * @param msg - the SIP message
 * @param realm - realm to match the right Authorization header
 * @returns the auts value or an empty string if not found
 */
str ims_get_auts(struct sip_msg *msg, str realm, int is_proxy_auth)
{
	str name = {"auts=\"", 6};
	struct hdr_field *h = 0;
	int i, ret;
	str auts = {0, 0};

	if(parse_headers(
			   msg, is_proxy_auth ? HDR_PROXYAUTH_F : HDR_AUTHORIZATION_F, 0)
			!= 0) {
		LM_ERR("Error parsing until header Authorization: \n");
		return auts;
	}

	if((!is_proxy_auth && !msg->authorization)
			|| (is_proxy_auth && !msg->proxy_auth)) {
		LM_ERR("Message does not contain Authorization nor Proxy-Authorization "
			   "header.\n");
		return auts;
	}

	ret = find_credentials(msg, &realm,
			is_proxy_auth ? HDR_PROXYAUTH_T : HDR_AUTHORIZATION_T, &h);
	if(ret < 0) {
		LM_ERR("Error while looking for credentials.\n");
		return auts;
	} else if(ret > 0) {
		LM_ERR("No credentials for this realm found.\n");
		return auts;
	}

	if(h) {
		for(i = 0; i < h->body.len - name.len; i++)
			if(strncasecmp(h->body.s + i, name.s, name.len) == 0) {
				auts.s = h->body.s + i + name.len;
				while(i + auts.len < h->body.len && auts.s[auts.len] != '\"')
					auts.len++;
			}
	}

	return auts;
}

/**
 * Looks for the nonce parameter in the Authorization header and returns its value.
 * @param msg - the SIP message
 * @param realm - realm to match the right Authorization header
 * @returns the nonce or an empty string if none found
 */
str ims_get_nonce(struct sip_msg *msg, str realm)
{
	struct hdr_field *h = 0;
	int ret;
	str nonce = {0, 0};

	if(parse_headers(msg, HDR_AUTHORIZATION_F, 0) != 0) {
		LM_ERR("Error parsing until header Authorization: \n");
		return nonce;
	}

	if(!msg->authorization) {
		LM_ERR("Message does not contain Authorization header.\n");
		return nonce;
	}

	ret = find_credentials(msg, &realm, HDR_AUTHORIZATION_T, &h);
	if(ret < 0) {
		LM_ERR("Error while looking for credentials.\n");
		return nonce;
	} else if(ret > 0) {
		LM_ERR("No credentials for this realm found.\n");
		return nonce;
	}

	if(h && h->parsed) {
		nonce = ((auth_body_t *)h->parsed)->digest.nonce;
	}

	return nonce;
}

/**
 * Adds a header to the reply message
 * @param msg - the request to add a header to its reply
 * @param content - the str containing the new header
 * @returns 1 on success, 0 on failure
 */
int ims_add_header_rpl(struct sip_msg *msg, str *hdr)
{
	if(add_lump_rpl(msg, hdr->s, hdr->len, LUMP_RPL_HDR) == 0) {
		LM_ERR("Can't add header <%.*s>\n", hdr->len, hdr->s);
		return 0;
	}
	return 1;
}
