/*
 * mangler module
 *
 * 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
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <regex.h>

#include "sdp_mangler.h"
#include "ip_helper.h"
#include "utils.h"
#include "common.h"
#include "../../core/mem/mem.h"
#include "../../core/data_lump.h"
#include "../../core/parser/hf.h"
#include "../../core/parser/parse_content.h"
#include "../../core/parser/parse_uri.h"
#include "../../core/parser/contact/parse_contact.h"
#include "../../core/ut.h"
#include "../../core/parser/msg_parser.h" /* struct sip_msg */

regex_t *portExpression = NULL;
regex_t *ipExpression = NULL;


int sdp_mangle_port(struct sip_msg *msg, char *offset, char *unused)
{
	int oldContentLength, newContentLength, oldlen, err, oldPort, newPort, diff,
			offsetValue, len, off, ret, needToDeallocate;
	struct lump *l;
	regmatch_t pmatch;
	regex_t *re;
	char *s, *pos, *begin, *key;
	char buf[6];


	key = PORT_REGEX;
	/*
	 * Checking if msg has a payload
	 */
	if(msg == NULL) {
		LOG(L_ERR, "ERROR: sdp_mangle_port: Received NULL for msg \n");
		return -1;
	}

	if((msg->content_length == 0)
			&& ((parse_headers(msg, HDR_CONTENTLENGTH_F, 0) == -1)
					|| (msg->content_length == 0))) {
		LOG(L_ERR, "ERROR: sdp_mangle_port: bad or missing "
				   "Content-Length \n");
		return -2;
	}

	oldContentLength = get_content_length(msg);

	if(oldContentLength <= 0) {
		LOG(L_ERR,
				"ERROR: sdp_mangle_port: Received <= 0 for Content-Length \n");
		return -2;
	}

	if(offset == NULL)
		return -14;
	if(sscanf(offset, "%d", &offsetValue) != 1) {
		LOG(L_ERR, "ERROR: sdp_mangle_port: Invalid value for offset \n");
		return -13;
	}

//offsetValue = (int)offset;
#ifdef EXTRA_DEBUG
	fprintf(stdout, "---START--------MANGLE PORT-----------------\n");
	fprintf(stdout, "===============OFFSET = %d\n", offsetValue);
#endif

	if((offsetValue < MIN_OFFSET_VALUE) || (offsetValue > MAX_OFFSET_VALUE)) {
		LOG(L_ERR, "ERROR: sdp_mangle_port: Invalid value %d for offset \n",
				offsetValue);
		return -3;
	}
	begin = get_body(msg);
	ret = -1;

	/* try to use pre-compiled expressions */
	needToDeallocate = 0;
	if(portExpression != NULL) {
		re = portExpression;
#ifdef EXTRA_DEBUG
		fprintf(stdout, "Using PRECOMPILED expression for port ...\n");
#endif
	} else {
		/* we are not using pre-compiled expressions */
		re = pkg_malloc(sizeof(regex_t));
		if(re == NULL) {
			LOG(L_ERR, "ERROR: sdp_mangle_port: Unable to allocate re\n");
			return -4;
		}
		needToDeallocate = 1;
		if((regcomp(re, key, REG_EXTENDED)) != 0) {
			LOG(L_ERR, "ERROR: sdp_mangle_port: Unable to compile %s \n", key);
			pkg_free(re);
			return -5;
		}
#ifdef EXTRA_DEBUG
		fprintf(stdout, "Using ALLOCATED expression for port ...\n");
#endif
	}

	diff = 0;
	while((begin < msg->buf + msg->len)
			&& (regexec(re, begin, 1, &pmatch, 0) == 0)) {
		off = begin - msg->buf;
		if(pmatch.rm_so == -1) {
			LOG(L_ERR, "ERROR: sdp_mangle_port: offset unknown\n");
			if(needToDeallocate) {
				regfree(re);
				pkg_free(re);
			}
			return -6;
		}

#ifdef STRICT_CHECK
		pmatch.rm_eo--; /* return with one space */
#endif

		/*
                for BSD and Solaris we avoid memrchr
                pos = (char *) memrchr (begin + pmatch.rm_so, ' ',pmatch.rm_eo - pmatch.rm_so);
                */
		pos = begin + pmatch.rm_eo;
#ifdef EXTRA_DEBUG
		printf("begin=%c pos=%c rm_so=%d rm_eo=%d\n", *begin, *pos,
				pmatch.rm_so, pmatch.rm_eo);
#endif
		do
			pos--;
		while(*pos
				!= ' '); /* we should find ' ' because we matched m=audio port */

		pos++; /* jumping over space */
		oldlen = (pmatch.rm_eo - pmatch.rm_so)
				 - (pos - (begin + pmatch.rm_so)); /* port length */

		/* convert port to int */
		oldPort = str2s(pos, oldlen, &err);
#ifdef EXTRA_DEBUG
		printf("port to convert [%.*s] to int\n", oldlen, pos);
#endif
		if(err) {
			LOG(L_ERR,
					"ERROR: sdp_mangle_port: Error converting [%.*s] to int\n",
					oldlen, pos);
#ifdef STRICT_CHECK
			if(needToDeallocate) {
				regfree(re);
				pkg_free(re);
			}
			return -7;
#else
			goto continue1;
#endif
		}
		if((oldPort < MIN_ORIGINAL_PORT)
				|| (oldPort
						> MAX_ORIGINAL_PORT)) /* we silently fail,we ignore this match or return -11 */
		{
#ifdef EXTRA_DEBUG
			printf("WARNING: sdp_mangle_port: Silent fail for not matching old "
				   "port %d\n",
					oldPort);
#endif

			LOG(L_WARN,
					"WARNING: sdp_mangle_port: Silent fail for not "
					"matching old port %d\n",
					oldPort);
#ifdef STRICT_CHECK
			if(needToDeallocate) {
				regfree(re);
				pkg_free(re);
			}
			return -8;
#else
			goto continue1;
#endif
		}
		if((offset[0] != '+') && (offset[0] != '-'))
			newPort = offsetValue; //fix value
		else
			newPort = oldPort + offsetValue;
		/* new port is between 1 and 65536, or so should be */
		if((newPort < MIN_MANGLED_PORT)
				|| (newPort
						> MAX_MANGLED_PORT)) /* we silently fail,we ignore this match */
		{
#ifdef EXTRA_DEBUG
			printf("WARNING: sdp_mangle_port: Silent fail for not matching new "
				   "port %d\n",
					newPort);
#endif

			LOG(L_WARN,
					"WARNING: sdp_mangle_port: Silent fail for not "
					"matching new port %d\n",
					newPort);
#ifdef STRICT_CHECK
			if(needToDeallocate) {
				regfree(re);
				pkg_free(re);
			}
			return -9;
#else
			goto continue1;
#endif
		}

#ifdef EXTRA_DEBUG
		fprintf(stdout, "Extracted port is %d and mangling to %d\n", oldPort,
				newPort);
#endif

		/*
		len = 1;
		while ((newPort = (newPort / 10)) != 0)	len++;
		newPort = oldPort + offsetValue;
		*/
		if(newPort >= 10000)
			len = 5;
		else if(newPort >= 1000)
			len = 4;
		else if(newPort >= 100)
			len = 3;
		else if(newPort >= 10)
			len = 2;
		else
			len = 1;

		/* replaced five div's + 1 add with most probably 1 comparison or 2 */

		/* deleting old port */
		if((l = del_lump(msg,
					pmatch.rm_so + off + (pos - (begin + pmatch.rm_so)), oldlen,
					0))
				== 0) {
			LOG(L_ERR, "ERROR: sdp_mangle_port: del_lump failed\n");
			if(needToDeallocate) {
				regfree(re);
				pkg_free(re);
			}
			return -10;
		}
		s = pkg_malloc(len);
		if(s == 0) {
			LOG(L_ERR, "ERROR: sdp_mangle_port : memory allocation failure\n");
			if(needToDeallocate) {
				regfree(re);
				pkg_free(re);
			}
			return -11;
		}
		snprintf(buf, len + 1, "%u", newPort); /* converting to string */
		memcpy(s, buf, len);

		if(insert_new_lump_after(l, s, len, 0) == 0) {
			LOG(L_ERR, "ERROR: sdp_mangle_port: could not insert new lump\n");
			pkg_free(s);
			if(needToDeallocate) {
				regfree(re);
				pkg_free(re);
			}
			return -12;
		}
		diff = diff + len /*new length */ - oldlen;
		/* new cycle */
		ret++;
#ifndef STRICT_CHECK
	continue1:
#endif
		begin = begin + pmatch.rm_eo;

	} /* while  */
	if(needToDeallocate) {
		regfree(re);
		pkg_free(re);
#ifdef EXTRA_DEBUG
		fprintf(stdout, "Deallocating expression for port ...\n");
#endif
	}

	if(diff != 0) {
		newContentLength = oldContentLength + diff;
		patch_content_length(msg, newContentLength);
	}

#ifdef EXTRA_DEBUG
	fprintf(stdout, "---END--------MANGLE PORT-----------------\n");
#endif

	return ret + 2;
}


int sdp_mangle_ip(struct sip_msg *msg, char *oldip, char *newip)
{
	int i, oldContentLength, newContentLength, diff, oldlen, len, off, ret,
			needToDeallocate;
	unsigned int mask, address, locatedIp;
	struct lump *l;
	regmatch_t pmatch;
	regex_t *re;
	char *s, *pos, *begin, *key;
	char buffer[16]; /* 123.456.789.123\0 */

#ifdef EXTRA_DEBUG
	fprintf(stdout, "---START--------MANGLE IP-----------------\n");
#endif

	pos = NULL;
	key = IP_REGEX;

	/*
	 * Checking if msg has a payload
	 */
	if(msg == NULL) {
		LOG(L_ERR, "ERROR: sdp_mangle_ip: Received NULL for msg\n");
		return -1;
	}
	if((msg->content_length == 0)
			&& ((parse_headers(msg, HDR_CONTENTLENGTH_F, 0) == -1)
					|| (msg->content_length == 0))) {
		LOG(L_ERR, "ERROR: sdp_mangle_port: bad or missing "
				   "Content-Length \n");
		return -2;
	}
	oldContentLength = get_content_length(msg);

	if(oldContentLength <= 0) {
		LOG(L_ERR, "ERROR: sdp_mangle_ip: Received <= for Content-Length\n");
		return -2;
	}

	/* checking oldip */
	if(oldip == NULL) {
		LOG(L_ERR, "ERROR: sdp_mangle_ip: Received NULL for oldip\n");
		return -3;
	}
	/* checking newip */
	if(newip == NULL) {
		LOG(L_ERR, "ERROR: sdp_mangle_ip: Received NULL for newip\n");
		return -4;
	}
	i = parse_ip_netmask(oldip, &pos, &mask);

	if(i == -1 || pos == NULL) {
		/* invalid value for the netmask specified in oldip */
		LOG(L_ERR, "ERROR: sdp_mangle_ip: invalid value for the netmask "
				   "specified in oldip\n");
		if(pos)
			free(pos);
		return -5;
	} else {
		i = parse_ip_address(pos, &address);
		free(pos);
		if(i == 0) {
			LOG(L_ERR, "ERROR: sdp_mangle_ip: invalid value for the ip "
					   "specified in oldip\n");
			return -6; /* parse error in ip */
		}
	}

	/* now we have in address/netmask binary values */

	begin = get_body(
			msg); //msg->buf + msg->first_line.len;	// inlocuiesc cu begin = getbody */
	ret = -1;
	len = strlen(newip);

	/* try to use pre-compiled expressions */
	needToDeallocate = 0;
	if(ipExpression != NULL) {
		re = ipExpression;
#ifdef EXTRA_DEBUG
		fprintf(stdout, "Using PRECOMPILED expression for ip ...\n");
#endif

	} else /* we are not using pre-compiled expressions */
	{
		re = pkg_malloc(sizeof(regex_t));
		if(re == NULL) {
			LOG(L_ERR, "ERROR: sdp_mangle_ip: Unable to allocate re\n");
			return -7;
		}
		needToDeallocate = 1;
		if((regcomp(re, key, REG_EXTENDED)) != 0) {
			LOG(L_ERR, "ERROR: sdp_mangle_ip: Unable to compile %s \n", key);
			pkg_free(re);
			return -8;
		}
#ifdef EXTRA_DEBUG
		fprintf(stdout, "Using ALLOCATED expression for ip ...\n");
#endif
	}

	diff = 0;
	while((begin < msg->buf + msg->len)
			&& (regexec(re, begin, 1, &pmatch, 0) == 0)) {
		off = begin - msg->buf;
		if(pmatch.rm_so == -1) {
			LOG(L_ERR, "ERROR: sdp_mangler_ip: offset unknown\n");
			pkg_free(re);
			return -9;
		}

#ifdef STRICT_CHECK
		pmatch.rm_eo--; /* return with one space,\n,\r */
#endif

		/*
                for BSD and Solaris we avoid memrchr
                pos = (char *) memrchr (begin + pmatch.rm_so, ' ',pmatch.rm_eo - pmatch.rm_so);
                */
		pos = begin + pmatch.rm_eo;
		do {
			pos--;
		} while(*pos
				!= ' '); /* we should find ' ' because we matched c=IN IP4 ip */

		pos++; /* jumping over space */
		oldlen = (pmatch.rm_eo - pmatch.rm_so)
				 - (pos - (begin + pmatch.rm_so)); /* ip length */
		if(oldlen > 15) {
			LOG(L_WARN, "WARNING: sdp_mangle_ip: Silent fail because oldlen > "
						"15\n");
#ifdef STRICT_CHECK
			pkg_free(re);
			return -10;
#else
			goto continue2; /* silent fail return -10; invalid ip format ,probably like 1000.3.12341.2 */
#endif
		}
		buffer[0] = '\0';
		strncat((char *)buffer, pos, oldlen);
		buffer[oldlen] = '\0';
		i = parse_ip_address(buffer, &locatedIp);
		if(i == 0) {
			LOG(L_WARN, "WARNING: sdp_mangle_ip: Silent fail on parsing "
						"matched address \n");

#ifdef STRICT_CHECK
			pkg_free(re);
			return -11;
#else
			goto continue2;
#endif
		}
		if(same_net(locatedIp, address, mask) == 0) {
			LOG(L_WARN, "WARNING: sdp_mangle_ip: Silent fail because matched "
						"address is not in network\n");
#ifdef EXTRA_DEBUG
			fprintf(stdout, "Extracted ip is %s and not mangling \n", buffer);
#endif
			goto continue2; /* not in the same net, skipping */
		}
#ifdef EXTRA_DEBUG
		fprintf(stdout, "Extracted ip is %s and mangling to %s\n", buffer,
				newip);
#endif

		/* replacing ip */

		/* deleting old ip */
		if((l = del_lump(msg,
					pmatch.rm_so + off + (pos - (begin + pmatch.rm_so)), oldlen,
					0))
				== 0) {
			LOG(L_ERR, "ERROR: sdp_mangle_ip: del_lump failed\n");
			pkg_free(re);
			return -12;
		}
		s = pkg_malloc(len);
		if(s == 0) {
			LOG(L_ERR, "ERROR: sdp_mangle_ip: mem. allocation failure\n");
			pkg_free(re);
			return -13;
		}
		memcpy(s, newip, len);

		if(insert_new_lump_after(l, s, len, 0) == 0) {
			LOG(L_ERR, "ERROR: sdp_mangle_ip: could not insert new lump\n");
			pkg_free(re);
			pkg_free(s);
			return -14;
		}
		diff = diff + len /*new length */ - oldlen;
		/* new cycle */
		ret++;
	continue2:
		begin = begin + pmatch.rm_eo;

	} /* while */
	if(needToDeallocate) {
		regfree(re); /* if I am going to use pre-compiled expressions to be removed */
		pkg_free(re);
#ifdef EXTRA_DEBUG
		fprintf(stdout, "Deallocating expression for ip ...\n");
#endif
	}

	if(diff != 0) {
		newContentLength = oldContentLength + diff;
		patch_content_length(msg, newContentLength);
	}

#ifdef EXTRA_DEBUG
	fprintf(stdout, "---END--------MANGLE IP-----------------\n");
#endif

	return ret + 2;
}

int compile_expressions(char *port, char *ip)
{
	portExpression = NULL;
	portExpression = pkg_malloc(sizeof(regex_t));
	if(portExpression != NULL) {
		if((regcomp(portExpression, port, REG_EXTENDED)) != 0) {
			LOG(L_ERR,
					"ERROR: compile_expressions: Unable to compile "
					"portExpression [%s]\n",
					port);
			pkg_free(portExpression);
			portExpression = NULL;
		}
	} else {
		LOG(L_ERR, "ERROR: compile_expressions: Unable to alloc portExpression "
				   "\n");
	}

	ipExpression = NULL;
	ipExpression = pkg_malloc(sizeof(regex_t));
	if(ipExpression != NULL) {
		if((regcomp(ipExpression, ip, REG_EXTENDED)) != 0) {
			LOG(L_ERR,
					"ERROR: compile_expressions: Unable to compile "
					"ipExpression [%s]\n",
					ip);
			pkg_free(ipExpression);
			ipExpression = NULL;
		}
	} else {
		LOG(L_ERR,
				"ERROR: compile_expressions: Unable to alloc ipExpression \n");
	}

	return 0;
}

int free_compiled_expressions()
{
	if(portExpression != NULL) {
		regfree(portExpression);
		pkg_free(portExpression);
		portExpression = NULL;
	}
	if(ipExpression != NULL) {
		regfree(ipExpression);
		pkg_free(ipExpression);
		ipExpression = NULL;
	}
	return 0;
}
