/*
 * Kamailio osp module. 
 *
 * This module enables Kamailio to communicate with an Open Settlement 
 * Protocol (OSP) server.  The Open Settlement Protocol is an ETSI 
 * defined standard for Inter-Domain VoIP pricing, authorization
 * and usage exchange.  The technical specifications for OSP 
 * (ETSI TS 101 321 V4.1.1) are available at www.etsi.org.
 *
 * Uli Abend was the original contributor to this module.
 * 
 * Copyright (C) 2001-2005 Fhg Fokus
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * 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 <osp/osp.h>
#include <osp/ospb64.h>
#include "../../forward.h"
#include "../../parser/parse_from.h"
#include "../../parser/parse_rpid.h"
#include "../../parser/parse_rr.h"
#include "../../parser/parse_uri.h"
#include "../../data_lump.h"
#include "../../mem/mem.h"
#include "osp_mod.h"
#include "sipheader.h"

extern int _osp_use_rpid;

static void ospSkipPlus(char* e164);
static int ospAppendHeader(struct sip_msg* msg, str* header); 

/* 
 * Copy str to buffer and check overflow 
 * param source Str
 * param buffer Buffer
 * param buffersize Size of buffer
 */
void ospCopyStrToBuffer(
    str* source, 
    char* buffer, 
    int buffersize)
{
    int copybytes;

    if (source->len > buffersize - 1) {
        LM_ERR("buffer for copying '%.*s' is too small, will copy the first '%d' bytes\n",
            source->len,
            source->s, 
            buffersize);
        copybytes = buffersize - 1;
    } else {
        copybytes = source->len;
    }

    strncpy(buffer, source->s, copybytes);
    buffer[copybytes] = '\0';
}

/* 
 * Remove '+' in E164 string
 * param e164 E164 string
 */
static void ospSkipPlus(
    char* e164)
{
    if (*e164 == '+') {
        strncpy(e164, e164 + 1, strlen(e164) - 1);
        e164[strlen(e164) - 1] = '\0';
    }
}

/* 
 * Get calling number from From header
 * param msg SIP message
 * param fromuser User part of From header
 * param buffersize Size of fromuser buffer
 * return 0 success, -1 failure
 */
int ospGetFromUserpart(
    struct sip_msg* msg, 
    char* fromuser, 
    int buffersize)
{
    struct to_body* from;
    struct sip_uri uri;
    int result = -1;

    fromuser[0] = '\0';

    if (msg->from != NULL) {
        if (parse_from_header(msg) == 0) {
            from = get_from(msg);
            if (parse_uri(from->uri.s, from->uri.len, &uri) == 0) {
                ospCopyStrToBuffer(&uri.user, fromuser, buffersize);
                ospSkipPlus(fromuser);
                result = 0;
            } else {
                LM_ERR("failed to parse From uri\n");
            }
        } else {
            LM_ERR("failed to parse From header\n");
        }
    } else {
        LM_ERR("failed to find From header\n");
    }

    return result;
}

/* 
 * Get calling number from Remote-Party-ID header
 * param msg SIP message
 * param rpiduser User part of Remote-Party-ID header
 * param buffersize Size of fromuser buffer
 * return 0 success, -1 failure
 */
int ospGetRpidUserpart(
    struct sip_msg* msg, 
    char* rpiduser, 
    int buffersize)
{
    struct to_body* rpid;
    struct sip_uri uri;
    int result = -1;

    rpiduser[0] = '\0';

    if (_osp_use_rpid != 0) {
        if (msg->rpid != NULL) {
            if (parse_rpid_header(msg) == 0) {
                rpid = get_rpid(msg);
                if (parse_uri(rpid->uri.s, rpid->uri.len, &uri) == 0) {
                    ospCopyStrToBuffer(&uri.user, rpiduser, buffersize);
                    ospSkipPlus(rpiduser);
                    result = 0;
                } else {
                    LM_ERR("failed to parse RPID uri\n");
                }
            } else {
                LM_ERR("failed to parse RPID header\n");
            }
        } else {
            LM_DBG("without RPID header\n");
        }
    } else {
        LM_DBG("do not use RPID header\n");
    }

    return result;
}

/* 
 * Get called number from To header
 * param msg SIP message
 * param touser User part of To header
 * param buffersize Size of touser buffer
 * return 0 success, -1 failure
 */
int ospGetToUserpart(
    struct sip_msg* msg, 
    char* touser, 
    int buffersize)
{
    struct to_body* to;
    struct sip_uri uri;
    int result = -1;

    touser[0] = '\0';

    if (msg->to != NULL) {
        if (parse_headers(msg, HDR_TO_F, 0) == 0) {
            to = get_to(msg);
            if (parse_uri(to->uri.s, to->uri.len, &uri) == 0) {
                ospCopyStrToBuffer(&uri.user, touser, buffersize);
                ospSkipPlus(touser);
                result = 0;
            } else {
                LM_ERR("failed to parse To uri\n");
            }
        } else {
            LM_ERR("failed to parse To header\n");
        }
    } else {
        LM_ERR("failed to find To header\n");
    }

    return result;
}

/* 
 * Get called number from Request-Line header
 * param msg SIP message
 * param touser User part of To header
 * param buffersize Size of touser buffer
 * return 0 success, -1 failure
 */
int ospGetUriUserpart(
    struct sip_msg* msg, 
    char* uriuser, 
    int buffersize)
{
    int result = -1;

    uriuser[0] = '\0';

    if (parse_sip_msg_uri(msg) >= 0) {
        ospCopyStrToBuffer(&msg->parsed_uri.user, uriuser, buffersize);
        ospSkipPlus(uriuser);
        result = 0;
    } else {
        LM_ERR("failed to parse Request-Line URI\n");
    }

    return result;
}

/* 
 * Append header to SIP message
 * param msg SIP message
 * param header Header to be appended
 * return 0 success, -1 failure
 */
static int ospAppendHeader(
    struct sip_msg* msg, 
    str* header)
{
    char* s;
    struct lump* anchor;
    
    if((msg == 0) || (header == 0) || (header->s == 0) || (header->len <= 0)) {
        LM_ERR("bad parameters for appending header\n");
        return -1;
    }

    if (parse_headers(msg, HDR_EOH_F, 0) == -1) {
        LM_ERR("failed to parse message\n");
        return -1;
    }

    anchor = anchor_lump(msg, msg->unparsed - msg->buf, 0, 0);
    if (anchor == 0) {
        LM_ERR("failed to get anchor\n");
        return -1;
    }

    s = (char*)pkg_malloc(header->len);
    if (s == 0) {
        LM_ERR("no pkg memory\n");
        return -1;
    }

    memcpy(s, header->s, header->len);

    if (insert_new_lump_before(anchor, s, header->len, 0) == 0) {
        LM_ERR("failed to insert lump\n");
        pkg_free(s);
        return -1;
    }
    
    return 0;
}

/* 
 * Add OSP token header to SIP message
 * param msg SIP message
 * param token OSP authorization token
 * param tokensize Size of OSP authorization token
 * return 0 success, -1 failure
 */
int ospAddOspHeader(
    struct sip_msg* msg, 
    unsigned char* token, 
    unsigned int tokensize)
{
    str headerval;
    char buffer[OSP_HEADERBUF_SIZE];
    unsigned char encodedtoken[OSP_TOKENBUF_SIZE];
    unsigned int encodedtokensize = sizeof(encodedtoken);
    int  result = -1;

    if (tokensize == 0) {
        LM_DBG("destination is not OSP device\n");
        result = 0;
    } else {
        if (OSPPBase64Encode(token, tokensize, encodedtoken, &encodedtokensize) == 0) {
            snprintf(buffer,
                sizeof(buffer),
                "%s%.*s\r\n", 
                OSP_TOKEN_HEADER,
                encodedtokensize,
                encodedtoken);
    
            headerval.s = buffer;
            headerval.len = strlen(buffer);
    
            LM_DBG("setting osp token header field '%s'\n", buffer);
    
            if (ospAppendHeader(msg, &headerval) == 0) {
                result = 0;
            } else {
                LM_ERR("failed to append osp header\n");
            }
        } else {
            LM_ERR("failed to base64 encode token\n");
        }
    }

    return result;
}

/* 
 * Get OSP token from SIP message
 * param msg SIP message
 * param token OSP authorization token
 * param tokensize Size of OSP authorization token
 * return 0 success, -1 failure
 */
int ospGetOspHeader(
    struct sip_msg* msg, 
    unsigned char* token, 
    unsigned int* tokensize)
{
    struct hdr_field* hf;
    int errorcode;
    int result = -1;

    parse_headers(msg, HDR_EOH_F, 0);

    for (hf = msg->headers; hf; hf = hf->next) {
        if ((hf->type == HDR_OTHER_T) && (hf->name.len == OSP_HEADER_SIZE - 2)) {
            // possible hit
            if (strncasecmp(hf->name.s, OSP_TOKEN_HEADER, OSP_HEADER_SIZE) == 0) {
                if ((errorcode = OSPPBase64Decode(hf->body.s, hf->body.len, token, tokensize)) == OSPC_ERR_NO_ERROR) {
                    result = 0;
                } else {
                    LM_ERR("failed to base64 decode token (%d)\n", errorcode);
                    LM_ERR("header '%.*s' length %d\n", hf->body.len, hf->body.s, hf->body.len);
                }
                break;
            }        
        } 
    }

    return result;
}

/* 
 * Get first VIA header and use the IP or host name
 * param msg SIP message
 * param sourceaddress Source address
 * param buffersize Size of sourceaddress
 * return 0 success, -1 failure
 */
int ospGetSourceAddress(
    struct sip_msg* msg, 
    char* sourceaddress, 
    int buffersize)
{
    struct hdr_field* hf;
    struct via_body* via;
    int result = -1;

    /* 
     * No need to call parse_headers, called already and VIA is parsed
     * anyway by default 
     */
    for (hf = msg->headers; hf; hf = hf->next) {
        if (hf->type == HDR_VIA_T) {
            // found first VIA
            via = (struct via_body*)hf->parsed;    
            ospCopyStrToBuffer(&via->host, sourceaddress, buffersize);

            LM_DBG("source address '%s'\n", sourceaddress);

            result = 0;
            break;
        } 
    }

    return result;
}

/* 
 * Get Call-ID header from SIP message
 * param msg SIP message
 * param callid Call ID
 * return 0 success, -1 failure
 */
int ospGetCallId(
    struct sip_msg* msg, 
    OSPTCALLID** callid)
{
    struct hdr_field* hf;
    int result = -1;

    hf = (struct hdr_field*)msg->callid;
    if (hf != NULL) {
        *callid = OSPPCallIdNew(hf->body.len, (unsigned char*)hf->body.s);
        if (*callid) {
            result = 0;
        } else {
            LM_ERR("failed to allocate OSPCALLID object for '%.*s'\n", hf->body.len, hf->body.s);
        }
    } else {
        LM_ERR("failed to find Call-ID header\n");
    }    

    return result;
}

/* 
 * Get route parameters from the 1st Route or Request-Line
 * param msg SIP message
 * param routeparameters Route parameters
 * param buffersize Size of routeparameters
 * return 0 success, -1 failure
 */
int ospGetRouteParameters(
    struct sip_msg* msg, 
    char* routeparameters, 
    int buffersize)
{
    struct hdr_field* hf;
    rr_t* rt;
    struct sip_uri uri;
    int result = -1;

    LM_DBG("parsed uri host '%.*s' port '%d' vars '%.*s'\n",
        msg->parsed_uri.host.len,
        msg->parsed_uri.host.s,
        msg->parsed_uri.port_no,
        msg->parsed_uri.params.len,
        msg->parsed_uri.params.s);

    if (!(hf = msg->route)) {
        LM_DBG("there is no Route headers\n");
    } else if (!(rt = (rr_t*)hf->parsed)) {
        LM_ERR("route headers are not parsed\n");
    } else if (parse_uri(rt->nameaddr.uri.s, rt->nameaddr.uri.len, &uri) != 0) {
        LM_ERR("failed to parse the Route uri '%.*s'\n", rt->nameaddr.uri.len, rt->nameaddr.uri.s);
    } else if (check_self(&uri.host, uri.port_no ? uri.port_no : SIP_PORT, PROTO_NONE) != 1) {
        LM_DBG("the Route uri is NOT mine\n");
        LM_DBG("host '%.*s' port '%d'\n", uri.host.len, uri.host.s, uri.port_no);
        LM_DBG("params '%.*s'\n", uri.params.len, uri.params.s);
    } else {
        LM_DBG("the Route uri IS mine - '%.*s'\n", uri.params.len, uri.params.s);
        LM_DBG("host '%.*s' port '%d'\n", uri.host.len, uri.host.s, uri.port_no);
        ospCopyStrToBuffer(&uri.params, routeparameters, buffersize);
        result = 0;
    }

    if ((result == -1) && (msg->parsed_uri.params.len > 0)) {
        LM_DBG("using route parameters from Request-Line uri\n");
        ospCopyStrToBuffer(&msg->parsed_uri.params, routeparameters, buffersize);
        routeparameters[msg->parsed_uri.params.len] = '\0';
        result = 0;
    }

    return result;
}

/* 
 * Rebuild URI using called number, destination IP, and port
 * param newuri URI to be built
 * param called Called number
 * param dest Destination IP
 * param port Destination port
 * param format URI format
 * return 0 success, -1 failure
 */
int ospRebuildDestionationUri(
    str* newuri, 
    char* called,
    char* dest, 
    char* port,
    int format) 
{
    static const str TRANS = {";transport=tcp", 14};
    char* buffer;
    int calledsize;
    int destsize;
    int portsize;

    calledsize = strlen(called);
    destsize = strlen(dest);
    portsize = strlen(port);

    LM_DBG("'%s'(%d) '%s'(%d) '%s'(%d) '%d'\n",
        called, 
        calledsize,
        dest, 
        destsize, 
        port, 
        portsize,
        format); 

    /* "sip:" + called + "@" + dest + : + port + " SIP/2.0" for URI format 0 */
    /* "<sip:" + called + "@" + dest + : + port> + " SIP/2.0" for URI format 1 */
    newuri->s = (char*)pkg_malloc(1 + 4 + calledsize + 1 + destsize + 1 + portsize + 1 + 1 + 16 + TRANS.len);
    if (newuri == NULL) {
        LM_ERR("no pkg memory\n");
        return -1;
    }    
    buffer = newuri->s;

    if (format == 1) {
      *buffer++ = '<';
    }
    *buffer++ = 's';
    *buffer++ = 'i';
    *buffer++ = 'p';
    *buffer++ = ':';

    memcpy(buffer, called, calledsize);
    buffer += calledsize;
    *buffer++ = '@';
    
    if (*dest == '[') {
        /* leave out annoying [] */
        memcpy(buffer, dest + 1, destsize - 2);
        buffer += destsize - 2;
    } else {
        memcpy(buffer, dest, destsize);
        buffer += destsize;
    }
    
    if (portsize > 0) {
        *buffer++ = ':';
        memcpy(buffer, port, portsize);
        buffer += portsize;
    }

    if (format == 1) {
      *buffer++ = '>';
    }

/*    
    *buffer++ = ' ';
    *buffer++ = 'S';
    *buffer++ = 'I';
    *buffer++ = 'P';
    *buffer++ = '/';
    *buffer++ = '2';
    *buffer++ = '.';
    *buffer++ = '0';

    memcpy(buffer, TRANS.s, TRANS.len);
    buffer += TRANS.len;
    *buffer = '\0';
*/

    newuri->len = buffer - newuri->s;

    LM_DBG("new uri '%.*s'\n", newuri->len, newuri->s);

    return 0;
}

/* 
 * Get next hop using the first Route not generated by this proxy or URI from the Request-Line
 * param msg SIP message
 * param nexthop Next hop IP
 * param buffersize Size of nexthop
 */
void ospGetNextHop(
    struct sip_msg* msg, 
    char* nexthop, 
    int buffersize)
{
    struct hdr_field* hf;
    struct sip_uri uri;
    rr_t* rt;
    int found = 0;

    for (hf = msg->headers; hf; hf = hf->next) {
        if (hf->type == HDR_ROUTE_T) {
            for (rt = (rr_t*)hf->parsed; rt; rt = rt->next) {
                if (parse_uri(rt->nameaddr.uri.s, rt->nameaddr.uri.len, &uri) == 0) {
                    LM_DBG("host '%.*s' port '%d'\n", uri.host.len, uri.host.s, uri.port_no);

                    if (check_self(&uri.host, uri.port_no ? uri.port_no : SIP_PORT, PROTO_NONE) != 1) {
                        LM_DBG("it is NOT me, FOUND!\n");

                        ospCopyStrToBuffer(&uri.host, nexthop, buffersize);
                        found = 1;
                        break;
                    } else {
                        LM_DBG("it IS me, keep looking\n");
                    }
                } else {
                    LM_ERR("failed to parse route uri '%.*s'\n",  
                        rt->nameaddr.uri.len, 
                        rt->nameaddr.uri.s);
                }
            }
            if (found == 1) {
                break;
            }
        }
    }

    if (!found) {
        LM_DBG("using the Request-Line instead host '%.*s' port '%d'\n",
             msg->parsed_uri.host.len,
             msg->parsed_uri.host.s,
             msg->parsed_uri.port_no);

        ospCopyStrToBuffer(&msg->parsed_uri.host, nexthop, buffersize);
        found = 1;
    }
}