/*
 * Copyright (C) 2001-2003 FhG Fokus
 *
 * 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
 *
 */

/*!
 * \file
 * \brief Exec module:: Module interface
 * \ingroup exec
 * Module: \ref exec
 *
 * functions for creating environment variables out of a request's
 * header; known compact header field names are translated to
 * canonical form; multiple header field occurrences are merged
 * into a single variable
 *
 * known limitations:
 * - compact header field names unknown to parser will not be translated to
 *   canonical form. Thus, environment variables may have either name and
 *   users have to check for both of them.
 * - symbols in header field names will be translated to underscore
 *
 */

#include <stdlib.h>

#include "../../core/parser/msg_parser.h"
#include "../../core/parser/parse_to.h"
#include "../../core/parser/parse_via.h"
#include "../../core/parser/parse_uri.h"
#include "../../core/mem/mem.h"
#include "../../core/dprint.h"
#include "../../core/crypto/md5utils.h"
#include "../../core/char_msg_val.h"
#include "exec_hf.h"

extern int exec_bash_safety;

/* should be environment variables set by header fields ? */
unsigned int setvars = 1;

/* insert a new header field into the structure; */
static int insert_hf(struct hf_wrapper **list, struct hdr_field *hf)
{
	struct hf_wrapper *w; /* new wrapper */
	struct hf_wrapper *i;

	w = (struct hf_wrapper *)pkg_malloc(sizeof(struct hf_wrapper));
	if(!w) {
		LM_ERR("ran out of pkg mem\n");
		return 0;
	}
	memset(w, 0, sizeof(struct hf_wrapper));
	w->var_type = W_HF;
	w->u.hf = hf;
	w->prefix = HF_PREFIX;
	w->prefix_len = HF_PREFIX_LEN;

	/* is there another hf of the same type?... */
	for(i = *list; i; i = i->next_other) {
		if(i->var_type == W_HF && i->u.hf->type == hf->type) {
			/* if it is OTHER, check name too */
			if(hf->type == HDR_OTHER_T
					&& (hf->name.len != i->u.hf->name.len
							|| strncasecmp(i->u.hf->name.s, hf->name.s,
									   hf->name.len)
									   != 0))
				continue;
			/* yes, we found a hf of same type */
			w->next_same = i->next_same;
			w->next_other = i->next_other;
			i->next_same = w;
			break;
		}
	}
	/* ... no previous HF of the same type found */
	if(i == 0) {
		w->next_other = *list;
		*list = w;
	}
	return 1;
}

static void release_hf_struct(struct hf_wrapper *list)
{
	struct hf_wrapper *i, *j, *nexts, *nexto;

	i = list;
	while(i) {
		nexto = i->next_other;
		j = i->next_same;
		pkg_free(i);
		/* release list of same type hf */
		while(j) {
			nexts = j->next_same;
			pkg_free(j);
			j = nexts;
		}
		i = nexto;
	}
}

/* if that is some of well-known header fields which have compact
 * form, return canonical form ... returns 1 and sets params;
 * 0 is returned otherwise */
static int compacthdr_type2str(hdr_types_t type, char **hname, int *hlen)
{
	switch(type) {
		/* HDR_CONTENT_ENCODING: 'e' -- unsupported by parser */
		/* HDR_SUBJECT: 's' -- unsupported by parser */
		case HDR_VIA_T /* v */:
			*hname = VAR_VIA;
			*hlen = VAR_VIA_LEN;
			break;
		case HDR_CONTENTTYPE_T /* c */:
			*hname = VAR_CTYPE;
			*hlen = VAR_CTYPE_LEN;
			break;
		case HDR_FROM_T /* f */:
			*hname = VAR_FROM;
			*hlen = VAR_FROM_LEN;
			break;
		case HDR_CALLID_T /* i */:
			*hname = VAR_CALLID;
			*hlen = VAR_CALLID_LEN;
			break;
		case HDR_SUPPORTED_T /* k */:
			*hname = VAR_SUPPORTED;
			*hlen = VAR_SUPPORTED_LEN;
			break;
		case HDR_CONTENTLENGTH_T /* l */:
			*hname = VAR_CLEN;
			*hlen = VAR_CLEN_LEN;
			break;
		case HDR_CONTACT_T /* m */:
			*hname = VAR_CONTACT;
			*hlen = VAR_CONTACT_LEN;
			break;
		case HDR_TO_T /* t */:
			*hname = VAR_TO;
			*hlen = VAR_TO_LEN;
			break;
		case HDR_EVENT_T /* o */:
			*hname = VAR_EVENT;
			*hlen = VAR_EVENT_LEN;
			break;
		default:
			return 0;
	}
	return 1;
}


static int canonize_headername(str *orig, char **hname, int *hlen)
{
	char *c;
	int i;

	*hlen = orig->len;
	*hname = pkg_malloc(*hlen);
	if(!*hname) {
		LM_ERR("no pkg mem for hname\n");
		return 0;
	}
	for(c = orig->s, i = 0; i < *hlen; i++, c++) {
		/* lowercase to uppercase */
		if(*c >= 'a' && *c <= 'z')
			*((*hname) + i) = *c - ('a' - 'A');
		/* uppercase and numbers stay "as is" */
		else if((*c >= 'A' && *c <= 'Z') || (*c >= '0' && *c <= '9'))
			*((*hname) + i) = *c;
		/* legal symbols will be translated to underscore */
		else if(strchr(UNRESERVED_MARK HNV_UNRESERVED, *c) || (*c == ESCAPE))
			*((*hname) + i) = HFN_SYMBOL;
		else {
			LM_ERR("print_var unexpected char '%c' in hfname %.*s\n", *c, *hlen,
					orig->s);
			*((*hname) + i) = HFN_SYMBOL;
		}
	}
	return 1;
}


static int print_av_var(struct hf_wrapper *w)
{
	int env_len;
	char *env;
	char *c;

	env_len = w->u.av.attr.len + 1 /*assignment*/ + w->u.av.val.len + 1 /*ZT*/;
	env = pkg_malloc(env_len);
	if(!env) {
		LM_ERR("no pkg mem\n");
		return 0;
	}
	c = env;
	memcpy(c, w->u.av.attr.s, w->u.av.attr.len);
	c += w->u.av.attr.len;
	*c = EV_ASSIGN;
	c++;
	memcpy(c, w->u.av.val.s, w->u.av.val.len);
	c += w->u.av.val.len;
	*c = 0; /* zero termination */
	w->envvar = env;
	return 1;
}

/* creates a malloc-ed string with environment variable; returns 1 on success,
 * 0 on failure  */
static int print_hf_var(struct hf_wrapper *w, int offset)
{
	char *hname;
	int hlen;
	short canonical;
	char *envvar;
	int envvar_len;
	struct hf_wrapper *wi;
	char *c;

	/* make -Wall happy */
	hname = 0;
	hlen = 0;
	envvar = 0;

	/* Make sure header names with possible compact forms
	 * will be printed canonically
	 */
	canonical = compacthdr_type2str(w->u.hf->type, &hname, &hlen);
	/* header field has not been made canonical using a table;
	 * do it now by uppercasing header-field name */
	if(!canonical) {
		if(!canonize_headername(&w->u.hf->name, &hname, &hlen)) {
			LM_ERR("canonize_hn error\n");
			return 0;
		}
	}
	/* now we have a header name, let us generate the var */
	envvar_len = w->u.hf->body.len;
	for(wi = w->next_same; wi;
			wi = wi->next_same) { /* other values, separated */
		envvar_len += 1 /* separator */ + wi->u.hf->body.len;
	}
	envvar = pkg_malloc(
			w->prefix_len + hlen + 1 /*assignment*/ + envvar_len + 1 /*ZT*/);
	if(!envvar) {
		LM_ERR("no pkg mem\n");
		goto error00;
	}
	memcpy(envvar, w->prefix, w->prefix_len);
	c = envvar + w->prefix_len;
	memcpy(c, hname, hlen);
	c += hlen;
	*c = EV_ASSIGN;
	c++;
	if(exec_bash_safety && w->u.hf->body.len >= 4
			&& !strncmp(w->u.hf->body.s, "() {", 4)) {
		memcpy(c, w->u.hf->body.s + offset + 2, w->u.hf->body.len - 2);
		c += (w->u.hf->body.len - 2);
	} else {
		memcpy(c, w->u.hf->body.s + offset, w->u.hf->body.len);
		c += w->u.hf->body.len;
	}
	for(wi = w->next_same; wi; wi = wi->next_same) {
		*c = HF_SEPARATOR;
		c++;
		if(exec_bash_safety && wi->u.hf->body.len >= 4
				&& !strncmp(wi->u.hf->body.s, "() {", 4)) {
			memcpy(c, wi->u.hf->body.s + offset + 2, wi->u.hf->body.len - 2);
			c += (wi->u.hf->body.len - 2);
		} else {
			memcpy(c, wi->u.hf->body.s + offset, wi->u.hf->body.len);
			c += wi->u.hf->body.len;
		}
	}
	*c = 0; /* zero termination */
	LM_DBG("%s\n", envvar);

	w->envvar = envvar;
	if(!canonical)
		pkg_free(hname);
	return 1;

error00:
	if(!canonical)
		pkg_free(hname);
	return 0;
}

static int print_var(struct hf_wrapper *w, int offset)
{
	switch(w->var_type) {
		case W_HF:
			return print_hf_var(w, offset);
		case W_AV:
			return print_av_var(w);
		default:
			LM_CRIT("unknown type: %d\n", w->var_type);
			return 0;
	}
}

static void release_vars(struct hf_wrapper *list)
{
	while(list) {
		if(list->envvar) {
			pkg_free(list->envvar);
			list->envvar = 0;
		}
		list = list->next_other;
	}
}

/* create ordered HF structure in pkg memory */
static int build_hf_struct(struct sip_msg *msg, struct hf_wrapper **list)
{
	struct hdr_field *h;

	*list = 0;
	/* create ordered header-field structure */
	for(h = msg->headers; h; h = h->next) {
		if(!insert_hf(list, h)) {
			LM_ERR("insert_hf failed\n");
			goto error00;
		}
	}
	return 1;
error00:
	release_hf_struct(*list);
	*list = 0;
	return 0;
}

/* create env vars in malloc memory */
static int create_vars(struct hf_wrapper *list, int offset)
{
	int var_cnt;
	struct hf_wrapper *w;

	/* create variables now */
	var_cnt = 0;
	for(w = list; w; w = w->next_other) {
		if(!print_var(w, offset)) {
			LM_ERR("create_vars failed\n");
			return 0;
		}
		var_cnt++;
	}

	return var_cnt;
}

environment_t *replace_env(struct hf_wrapper *list)
{
	int var_cnt;
	char **cp;
	struct hf_wrapper *w;
	char **new_env;
	int i;
	environment_t *backup_env;

	backup_env = (environment_t *)pkg_malloc(sizeof(environment_t));
	if(!backup_env) {
		LM_ERR("no pkg mem for backup env\n");
		return 0;
	}

	/* count length of current env list */
	var_cnt = 0;
	for(cp = environ; *cp; cp++)
		var_cnt++;
	backup_env->old_cnt = var_cnt;
	/* count length of our extensions */
	for(w = list; w; w = w->next_other)
		var_cnt++;
	new_env = pkg_malloc((var_cnt + 1) * sizeof(char *));
	if(!new_env) {
		LM_ERR("no pkg mem\n");
		pkg_free(backup_env);
		return 0;
	}
	/* put all var pointers into new environment */
	i = 0;
	for(cp = environ; *cp; cp++) { /* replicate old env */
		new_env[i] = *cp;
		i++;
	}
	for(w = list; w; w = w->next_other) { /* append new env */
		new_env[i] = w->envvar;
		i++;
	}
	new_env[i] = 0; /* zero termination */
	/* install new environment */
	backup_env->env = environ;
	environ = new_env;
	/* return previous environment */
	return backup_env;
}

void unset_env(environment_t *backup_env)
{
	char **cur_env, **cur_env0;
	int i;

	/* switch-over to backup environment */
	cur_env0 = cur_env = environ;
	environ = backup_env->env;
	i = 0;
	/* release environment */
	while(*cur_env) {
		/* leave previously existing vars alone */
		if(i >= backup_env->old_cnt) {
			pkg_free(*cur_env);
		}
		cur_env++;
		i++;
	}
	pkg_free(cur_env0);
	pkg_free(backup_env);
}

static int append_var(
		char *name, char *value, int len, struct hf_wrapper **list)
{
	struct hf_wrapper *w;

	w = (struct hf_wrapper *)pkg_malloc(sizeof(struct hf_wrapper));
	if(!w) {
		LM_ERR("ran out of pkg mem\n");
		return 0;
	}
	memset(w, 0, sizeof(struct hf_wrapper));
	w->var_type = W_AV;
	w->u.av.attr.s = name;
	w->u.av.attr.len = strlen(name);
	w->u.av.val.s = value;
	/* NULL strings considered empty, if len unknown, calculate it now */
	w->u.av.val.len = value == 0 ? 0 : (len == 0 ? strlen(value) : len);
	w->next_other = *list;
	*list = w;
	return 1;
}

static int append_fixed_vars(struct sip_msg *msg, struct hf_wrapper **list)
{
	static char tid[MD5_LEN];
	str *uri;
	struct sip_uri parsed_uri, oparsed_uri;
	char *val;
	int val_len;

	/* source ip */
	if(!append_var(EV_SRCIP, ip_addr2a(&msg->rcv.src_ip), 0, list)) {
		LM_ERR("append_var SRCIP failed \n");
		return 0;
	}
	/* request URI */
	uri = msg->new_uri.s && msg->new_uri.len ? &msg->new_uri
											 : &msg->first_line.u.request.uri;
	if(!append_var(EV_RURI, uri->s, uri->len, list)) {
		LM_ERR("append_var URI failed\n");
		return 0;
	}
	/* userpart of request URI */
	if(parse_uri(uri->s, uri->len, &parsed_uri) < 0) {
		LM_WARN("uri not parsed (%.*s)\n", uri->len, ZSW(uri->s));
	} else {
		if(!append_var(EV_USER, parsed_uri.user.s, parsed_uri.user.len, list)) {
			LM_ERR("append_var USER failed\n");
			goto error;
		}
	}
	/* original URI */
	if(!append_var(EV_ORURI, msg->first_line.u.request.uri.s,
			   msg->first_line.u.request.uri.len, list)) {
		LM_ERR("append_var O-URI failed\n");
		goto error;
	}
	/* userpart of request URI */
	if(parse_uri(msg->first_line.u.request.uri.s,
			   msg->first_line.u.request.uri.len, &oparsed_uri)
			< 0) {
		LM_WARN("orig URI not parsed (%.*s)\n",
				msg->first_line.u.request.uri.len,
				ZSW(msg->first_line.u.request.uri.s));
	} else {
		if(!append_var(
				   EV_OUSER, oparsed_uri.user.s, oparsed_uri.user.len, list)) {
			LM_ERR("append_var OUSER failed\n");
			goto error;
		}
	}
	/* tid, transaction id == via/branch */
	if(!char_msg_val(msg, tid)) {
		LM_WARN("no tid can be determined\n");
		val = 0;
		val_len = 0;
	} else {
		val = tid;
		val_len = MD5_LEN;
	}
	if(!append_var(EV_TID, val, val_len, list)) {
		LM_ERR("append_var TID failed\n");
		goto error;
	}

	/* did, dialogue id == To-tag */
	if(!(msg->to && get_to(msg))) {
		LM_ERR("no to-tag\n");
		val = 0;
		val_len = 0;
	} else {
		val = get_to(msg)->tag_value.s;
		val_len = get_to(msg)->tag_value.len;
	}
	if(!append_var(EV_DID, val, val_len, list)) {
		LM_ERR("append_var DID failed\n");
		goto error;
	}
	return 1;
error:
	return 0;
}

environment_t *set_env(struct sip_msg *msg)
{
	struct hf_wrapper *hf_list;
	environment_t *backup_env;

	/* parse all so that we can pass all header fields to script */
	if(parse_headers(msg, HDR_EOH_F, 0) == -1) {
		LM_ERR("parsing failed\n");
		return 0;
	}

	hf_list = 0;
	/* create a temporary structure with ordered header fields
	 * and create environment variables out of it */
	if(!build_hf_struct(msg, &hf_list)) {
		LM_ERR("build_hf_struct failed\n");
		return 0;
	}
	if(!append_fixed_vars(msg, &hf_list)) {
		LM_ERR("append_fixed_vars failed\n");
		goto error01;
	}
	/* create now the strings for environment variables */
	if(!create_vars(hf_list, 0)) {
		LM_ERR("create_vars failed\n");
		goto error00;
	}
	/* install the variables in current environment */
	backup_env = replace_env(hf_list);
	if(!backup_env) {
		LM_ERR("replace_env failed\n");
		goto error00;
	}
	/* release the ordered HF structure -- we only need the vars now */
	release_hf_struct(hf_list);
	return backup_env;

error00:
	release_vars(hf_list); /* release variables */
error01:
	release_hf_struct(hf_list); /* release temporary ordered HF struct */
	return 0;
}
