/*
 * siptrace module - helper module to trace sip messages
 *
 * Copyright (C) 2017 kamailio.org
 *
 * 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 <time.h>

#include "../../core/dprint.h"
#include "../../core/proxy.h"
#include "../../core/forward.h"
#include "../../core/parser/parse_content.h"
#include "../../core/parser/parse_from.h"
#include "../../core/parser/parse_cseq.h"

#include "siptrace_send.h"


extern int trace_xheaders_write;
extern int trace_xheaders_read;
extern str trace_dup_uri_str;
extern sip_uri_t *trace_dup_uri;
extern str trace_send_sock_str;
extern str trace_send_sock_name_str;
extern sip_uri_t *trace_send_sock_uri;
extern socket_info_t *trace_send_sock_info;

/**
 *
 */
int sip_trace_prepare(sip_msg_t *msg)
{
	if(parse_from_header(msg) == -1 || msg->from == NULL
			|| get_from(msg) == NULL) {
		LM_ERR("cannot parse FROM header\n");
		goto error;
	}

	if(parse_to_header(msg) == -1 || msg->to == NULL || get_to(msg) == NULL) {
		LM_ERR("cannot parse To header\n");
		goto error;
	}

	if(parse_headers(msg, HDR_CALLID_F, 0) != 0 || msg->callid == NULL
			|| msg->callid->body.s == NULL) {
		LM_ERR("cannot parse call-id\n");
		goto error;
	}

	if(msg->cseq == NULL
			&& ((parse_headers(msg, HDR_CSEQ_F, 0) == -1)
					|| (msg->cseq == NULL))) {
		LM_ERR("cannot parse cseq\n");
		goto error;
	}

	return 0;
error:
	return -1;
}

/**
 * Appends x-headers to the message in sto->body containing data from sto
 */
int sip_trace_xheaders_write(struct _siptrace_data *sto)
{
	char *buf = NULL;
	int bytes_written = 0;
	char *eoh = NULL;
	int eoh_offset = 0;
	char *new_eoh = NULL;

	if(trace_xheaders_write == 0) {
		return 0;
	}

	// Memory for the message with some additional headers.
	// It gets free()ed in sip_trace_xheaders_free().
	buf = pkg_malloc(sto->body.len + XHEADERS_BUFSIZE);
	if(buf == NULL) {
		LM_ERR("out of pkg memory\n");
		return -1;
	}

	// Copy the whole message to buf first; it must be \0-terminated for
	// strstr() to work. Then search for the end-of-header sequence.
	memcpy(buf, sto->body.s, sto->body.len);
	buf[sto->body.len] = '\0';
	eoh = strstr(buf, "\r\n\r\n");
	if(eoh == NULL) {
		LM_ERR("malformed message\n");
		goto error;
	}
	eoh += 2; // the first \r\n belongs to the last header => skip it

	// Write the new headers a the end-of-header position. This overwrites
	// the \r\n terminating the old headers and the beginning of the message
	// body. Both will be recovered later.
	bytes_written = snprintf(eoh, XHEADERS_BUFSIZE,
			"X-Siptrace-Fromip: %.*s\r\n"
			"X-Siptrace-Toip: %.*s\r\n"
			"X-Siptrace-Time: %llu %llu\r\n"
			"X-Siptrace-Method: %.*s\r\n"
			"X-Siptrace-Dir: %s\r\n",
			sto->fromip.len, sto->fromip.s, sto->toip.len, sto->toip.s,
			(unsigned long long)sto->tv.tv_sec,
			(unsigned long long)sto->tv.tv_usec, sto->method.len, sto->method.s,
			sto->dir);
	if(bytes_written >= XHEADERS_BUFSIZE) {
		LM_ERR("string too long\n");
		goto error;
	}

	// Copy the \r\n terminating the old headers and the message body from the
	// old buffer in sto->body.s to the new end-of-header in buf.
	eoh_offset = eoh - buf;
	new_eoh = eoh + bytes_written;
	memcpy(new_eoh, sto->body.s + eoh_offset, sto->body.len - eoh_offset);

	// Change sto to point to the new buffer.
	sto->body.s = buf;
	sto->body.len += bytes_written;
	sto->alloc_body = 1;
	return 0;
error:
	if(buf != NULL) {
		pkg_free(buf);
	}
	return -1;
}

/**
 * Parses x-headers, saves the data back to sto, and removes the x-headers
 * from the message in sto->buf
 */
int sip_trace_xheaders_read(struct _siptrace_data *sto)
{
	char *searchend = NULL;
	char *eoh = NULL;
	char *xheaders = NULL;
	long long unsigned int tv_sec = 0, tv_usec = 0;
	int rv = 0;

	if(trace_xheaders_read == 0) {
		return 0;
	}

	// Find the end-of-header marker \r\n\r\n
	searchend = sto->body.s + sto->body.len - 3;
	eoh = memchr(sto->body.s, '\r', searchend - eoh);
	while(eoh != NULL && eoh < searchend) {
		if(memcmp(eoh, "\r\n\r\n", 4) == 0)
			break;
		eoh = memchr(eoh + 1, '\r', searchend - eoh);
	}
	if(eoh == NULL) {
		LM_ERR("malformed message\n");
		return -1;
	}

	// Find x-headers: eoh will be overwritten by \0 to allow the use of
	// strstr(). The byte at eoh will later be recovered, when the
	// message body is shifted towards the beginning of the message
	// to remove the x-headers.
	*eoh = '\0';
	xheaders = strstr(sto->body.s, "\r\nX-Siptrace-Fromip: ");
	if(xheaders == NULL) {
		LM_ERR("message without x-headers "
			   "from %.*s, callid %.*s\n",
				sto->fromip.len, sto->fromip.s, sto->callid.len, sto->callid.s);
		return -1;
	}

	// Allocate memory for new strings in sto
	// (gets free()ed in sip_trace_xheaders_free() )
	sto->fromip.s = pkg_malloc(51);
	sto->toip.s = pkg_malloc(51);
	sto->method.s = pkg_malloc(51);
	sto->dir = pkg_malloc(4);
	if(!(sto->fromip.s && sto->toip.s && sto->method.s && sto->dir)) {
		LM_ERR("out of pkg memory\n");
		goto erroraftermalloc;
	}

	// Parse the x-headers: scanf()
	rv = sscanf(xheaders,
			"\r\n"
			"X-Siptrace-Fromip: %50s\r\n"
			"X-Siptrace-Toip: %50s\r\n"
			"X-Siptrace-Time: %llu %llu\r\n"
			"X-Siptrace-Method: %50s\r\n"
			"X-Siptrace-Dir: %3s",
			sto->fromip.s, sto->toip.s, &tv_sec, &tv_usec, sto->method.s,
			sto->dir);

	if(rv == EOF || rv < 6) {
		LM_ERR("malformed x-headers\n");
		goto erroraftermalloc;
	}
	sto->fromip.len = strlen(sto->fromip.s);
	sto->toip.len = strlen(sto->toip.s);
	sto->tv.tv_sec = (time_t)tv_sec;
	sto->tv.tv_usec = (suseconds_t)tv_usec;
	sto->method.len = strlen(sto->method.s);

	// Remove the x-headers: the message body is shifted towards the beginning
	// of the message, overwriting the x-headers. Before that, the byte at eoh
	// is recovered.
	*eoh = '\r';
	memmove(xheaders, eoh, sto->body.len - (eoh - sto->body.s));
	sto->body.len -= eoh - xheaders;
	sto->alloc_headers = 1;

	return 0;

erroraftermalloc:
	if(sto->fromip.s) {
		pkg_free(sto->fromip.s);
		sto->fromip.s = 0;
	}
	if(sto->toip.s) {
		pkg_free(sto->toip.s);
		sto->toip.s = 0;
	}
	if(sto->method.s) {
		pkg_free(sto->method.s);
		sto->method.s = 0;
	}
	if(sto->dir) {
		pkg_free(sto->dir);
		sto->dir = 0;
	}
	return -1;
}

/**
 * Frees the memory allocated by sip_trace_xheaders_{write,read}
 */
int sip_trace_xheaders_free(struct _siptrace_data *sto)
{
	if(sto->alloc_body != 0) {
		if(sto->body.s) {
			pkg_free(sto->body.s);
			sto->body.s = 0;
		}
		sto->alloc_body = 0;
	}

	if(sto->alloc_headers != 0) {
		if(sto->fromip.s) {
			pkg_free(sto->fromip.s);
			sto->fromip.s = 0;
		}
		if(sto->toip.s) {
			pkg_free(sto->toip.s);
			sto->toip.s = 0;
		}
		if(sto->dir) {
			pkg_free(sto->dir);
			sto->dir = 0;
		}
		sto->alloc_headers = 0;
	}

	return 0;
}


/**
 *
 */
int trace_send_duplicate(char *buf, int len, dest_info_t *dst2)
{
	dest_info_t dst;
	dest_info_t *pdst = NULL;
	proxy_l_t *p = NULL;

	if(buf == NULL || len <= 0) {
		return -1;
	}

	/* either modparam dup_uri or siptrace param dst2 */
	if((trace_dup_uri_str.s == 0 || trace_dup_uri == NULL) && (dst2 == NULL)) {
		LM_WARN("Neither dup_uri modparam or siptrace destination uri param "
				"used!\n");
		return 0;
	}

	init_dest_info(&dst);

	if(!dst2) {
		/* create a temporary proxy from dst param */
		dst.proto = trace_dup_uri->proto;
		p = mk_proxy(&trace_dup_uri->host,
				(trace_dup_uri->port_no) ? trace_dup_uri->port_no : SIP_PORT,
				dst.proto);
		if(p == 0) {
			LM_ERR("bad host name in uri\n");
			return -1;
		}
		hostent2su(
				&dst.to, &p->host, p->addr_idx, (p->port) ? p->port : SIP_PORT);
		pdst = &dst;
	} else {
		pdst = dst2;
	}

	if(pdst->send_sock == NULL) {
		if(trace_send_sock_name_str.s) {
			pdst->send_sock = trace_send_sock_info;
		} else if(trace_send_sock_str.s) {
			LM_DBG("send sock activated, grep for the sock_info\n");
			if(trace_send_sock_info) {
				pdst->send_sock = trace_send_sock_info;
			} else {
				pdst->send_sock = grep_sock_info(&trace_send_sock_uri->host,
						trace_send_sock_uri->port_no,
						trace_send_sock_uri->proto);
			}
			if(!pdst->send_sock) {
				LM_WARN("local socket not found for: [%.*s]\n",
						trace_send_sock_str.len, trace_send_sock_str.s);
			} else {
				LM_DBG("using local send socket: [%.*s] [%.*s]\n",
						pdst->send_sock->name.len, pdst->send_sock->name.s,
						pdst->send_sock->address_str.len,
						pdst->send_sock->address_str.s);
			}
		}
	}

	if(pdst->send_sock == NULL) {
		pdst->send_sock = get_send_socket(0, &pdst->to, pdst->proto);
		if(pdst->send_sock == 0) {
			LM_ERR("cannot forward to af %d, proto %d - no corresponding"
				   " listening socket\n",
					pdst->to.s.sa_family, pdst->proto);
			goto error;
		}
	} else {
		pdst->send_flags.f |= SND_F_FORCE_SOCKET;
	}

	if(msg_send_buffer(pdst, buf, len, 1) < 0) {
		LM_ERR("cannot send duplicate message\n");
		goto error;
	}

	if(p) {
		free_proxy(p); /* frees only p content, not p itself */
		pkg_free(p);
	}
	return 0;
error:
	if(p) {
		free_proxy(p); /* frees only p content, not p itself */
		pkg_free(p);
	}
	return -1;
}

/**
 *
 */
char *siptrace_proto_name(int vproto)
{
	switch(vproto) {
		case PROTO_TCP:
			return "tcp";
		case PROTO_TLS:
			return "tls";
		case PROTO_SCTP:
			return "sctp";
		case PROTO_WS:
			return "ws";
		case PROTO_WSS:
			return "wss";
		default:
			return "udp";
	}
}
