/*
 * 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 <string.h>
#include <osp/osp.h>
#include "../../core/dset.h"
#include "../../core/usr_avp.h"
#include "../../core/mem/mem.h"
#include "../../modules/siputils/siputils.h"
#include "orig_transaction.h"
#include "destination.h"
#include "osptoolkit.h"
#include "sipheader.h"
#include "usage.h"

extern char* _osp_device_ip;
extern char* _osp_device_port;
extern int _osp_max_dests;
extern int _osp_redir_uri;
extern int_str _osp_snid_avpname;
extern unsigned short _osp_snid_avptype;
extern OSPTPROVHANDLE _osp_provider;
extern siputils_api_t osp_siputils;

const int OSP_FIRST_ROUTE = 1;
const int OSP_NEXT_ROUTE = 0;
const int OSP_MAIN_ROUTE = 1;
const int OSP_BRANCH_ROUTE = 0;
const str OSP_CALLING_NAME = {"_osp_calling_translated_", 24};

static int ospLoadRoutes(OSPTTRANHANDLE transaction, int destcount, char* source, char* sourcedev, char* origcalled, time_t authtime);
static int ospPrepareDestination(struct sip_msg* msg, int isfirst, int type, int format);
static int ospSetRpid(struct sip_msg* msg, osp_dest* dest);

/*
 * Get routes from AuthRsp
 * param transaction Transaction handle
 * param destcount Expected destination count
 * param source Source IP
 * param sourcedev Source device IP
 * param origcalled Original called number
 * param authtime Request authorization time
 * return 0 success, -1 failure
 */
static int ospLoadRoutes(
    OSPTTRANHANDLE transaction, 
    int destcount, 
    char* source, 
    char* sourcedev, 
    char* origcalled, 
    time_t authtime)
{
    int count;
    int errorcode;
    osp_dest* dest;
    osp_dest dests[OSP_DEF_DESTS];
    OSPE_DEST_PROT protocol;
    OSPE_DEST_OSP_ENABLED enabled;
    int result = 0;
    
    for (count = 0; count < destcount; count++) {
        /* This is necessary because we will save destinations in reverse order */
        dest = ospInitDestination(&dests[count]);

        if (dest == NULL) {
            result = -1;
            break;
        }

        dest->destinationCount = count + 1;
        strncpy(dest->origcalled, origcalled, sizeof(dest->origcalled) - 1);

        if (count == 0) {
            errorcode = OSPPTransactionGetFirstDestination(
                transaction,
                sizeof(dest->validafter),
                dest->validafter,
                dest->validuntil,
                &dest->timelimit,
                &dest->callidsize,
                (void*)dest->callid,
                sizeof(dest->called),
                dest->called,
                sizeof(dest->calling),
                dest->calling,
                sizeof(dest->host),
                dest->host,
                sizeof(dest->destdev),
                dest->destdev,
                &dest->tokensize,
                dest->token);
        } else {
            errorcode = OSPPTransactionGetNextDestination(
                transaction,
                0,
                sizeof(dest->validafter),
                dest->validafter,
                dest->validuntil,
                &dest->timelimit,
                &dest->callidsize,
                (void*)dest->callid,
                sizeof(dest->called),
                dest->called,
                sizeof(dest->calling),
                dest->calling,
                sizeof(dest->host),
                dest->host,
                sizeof(dest->destdev),
                dest->destdev,
                &dest->tokensize,
                dest->token);
        }
        
        if (errorcode != OSPC_ERR_NO_ERROR) {
            LM_ERR("failed to load routes (%d) expected '%d' current '%d'\n", 
                errorcode, 
                destcount, 
                count);
            result = -1;
            break;
        }

        errorcode = OSPPTransactionGetDestProtocol(transaction, &protocol);
        if (errorcode != OSPC_ERR_NO_ERROR) {
            /* This does not mean an ERROR. The OSP server may not support OSP 2.1.1 */
            LM_DBG("cannot get dest protocol (%d)\n", errorcode);
            protocol = OSPE_DEST_PROT_SIP;
        }
        switch (protocol) {
            case OSPE_DEST_PROT_H323_LRQ:
            case OSPE_DEST_PROT_H323_SETUP:
            case OSPE_DEST_PROT_IAX:
                dest->supported = 0;
                break;
            case OSPE_DEST_PROT_SIP:
            case OSPE_DEST_PROT_UNDEFINED:
            case OSPE_DEST_PROT_UNKNOWN:
            default:
                dest->supported = 1;
                break;
        }

        errorcode = OSPPTransactionIsDestOSPEnabled(transaction, &enabled);
        if (errorcode != OSPC_ERR_NO_ERROR) {
            /* This does not mean an ERROR. The OSP server may not support OSP 2.1.1 */
            LM_DBG("cannot get dest OSP version (%d)\n", errorcode);
        } else if (enabled == OSPE_OSP_FALSE) {
            /* Destination device does not support OSP. Do not send token to it */
            dest->token[0] = '\0';
            dest->tokensize = 0;
        }

        errorcode = OSPPTransactionGetDestNetworkId(transaction, dest->networkid);
        if (errorcode != OSPC_ERR_NO_ERROR) {
            /* This does not mean an ERROR. The OSP server may not support OSP 2.1.1 */
            LM_DBG("cannot get dest network ID (%d)\n", errorcode);
            dest->networkid[0] = '\0';
        }

        strncpy(dest->source, source, sizeof(dest->source) - 1);
        strncpy(dest->srcdev, sourcedev, sizeof(dest->srcdev) - 1);
        dest->type = OSPC_SOURCE;
        dest->transid = ospGetTransactionId(transaction);
        dest->authtime = authtime;

        LM_INFO("get destination '%d': "
            "valid after '%s' "
            "valid until '%s' "
            "time limit '%d' seconds "
            "call id '%.*s' "
            "calling '%s' "
            "called '%s' "
            "host '%s' "
            "supported '%d' "
            "network id '%s' "
            "token size '%d'\n",
            count, 
            dest->validafter, 
            dest->validuntil, 
            dest->timelimit, 
            dest->callidsize, 
            dest->callid, 
            dest->calling, 
            dest->called, 
            dest->host, 
            dest->supported,
            dest->networkid, 
            dest->tokensize);
    }

    /* 
     * Save destination in reverse order,
     * when we start searching avps the destinations
     * will be in order 
     */
    if (result == 0) {
        for(count = destcount -1; count >= 0; count--) {
            if (ospSaveOrigDestination(&dests[count]) == -1) {
                LM_ERR("failed to save originate destination\n");
                /* Report terminate CDR */
                ospRecordEvent(0, 500);
                result = -1;
                break;
            }
        }
    }

    return result;
}

/*
 * Request OSP authorization and routeing
 * param msg SIP message
 * param ignore1
 * param ignore2
 * return MODULE_RETURNCODE_TRUE success, MODULE_RETURNCODE_FALSE failure, MODULE_RETURNCODE_ERROR error
 */
int ospRequestRouting(
    struct sip_msg* msg, 
    char* ignore1, 
    char* ignore2)
{
    int errorcode;
    time_t authtime;
    char called[OSP_E164BUF_SIZE];
    char calling[OSP_E164BUF_SIZE];
    char sourcedev[OSP_STRBUF_SIZE];
    char deviceinfo[OSP_STRBUF_SIZE];
    struct usr_avp* snidavp = NULL;
    int_str snidval;
    char snid[OSP_STRBUF_SIZE];
    unsigned int callidnumber = 1;
    OSPTCALLID* callids[callidnumber];
    unsigned int logsize = 0;
    char* detaillog = NULL;
    const char** preferred = NULL;
    unsigned int destcount;
    OSPTTRANHANDLE transaction = -1;
    int result = MODULE_RETURNCODE_FALSE;

    authtime = time(NULL);

    destcount = _osp_max_dests;

    if ((errorcode = OSPPTransactionNew(_osp_provider, &transaction)) != OSPC_ERR_NO_ERROR) {
        LM_ERR("failed to create new OSP transaction (%d)\n", errorcode);
    } else if ((ospGetRpidUserpart(msg, calling, sizeof(calling)) != 0) &&
        (ospGetFromUserpart(msg, calling, sizeof(calling)) != 0)) 
    {
        LM_ERR("failed to extract calling number\n");
    } else if ((ospGetUriUserpart(msg, called, sizeof(called)) != 0) &&
        (ospGetToUserpart(msg, called, sizeof(called)) != 0)) 
    {
        LM_ERR("failed to extract called number\n");
    } else if (ospGetCallId(msg, &(callids[0])) != 0) {
        LM_ERR("failed to extract call id\n");
    } else if (ospGetSourceAddress(msg, sourcedev, sizeof(sourcedev)) != 0) {
        LM_ERR("failed to extract source deivce address\n");
    } else {
        ospConvertAddress(sourcedev, deviceinfo, sizeof(deviceinfo));

        if ((_osp_snid_avpname.n != 0) &&
            ((snidavp = search_first_avp(_osp_snid_avptype, _osp_snid_avpname, &snidval, 0)) != NULL) &&
            (snidavp->flags & AVP_VAL_STR) && (snidval.s.s && snidval.s.len)) 
        {
            snprintf(snid, sizeof(snid), "%.*s", snidval.s.len, snidval.s.s);
            OSPPTransactionSetNetworkIds(transaction, snid, "");
        } else {
            snid[0] = '\0';
        }

        LM_INFO("request auth and routing for: "
            "source_ip '%s' "
            "source_port '%s' "
            "source_dev '%s' "
            "source_networkid '%s' "
            "calling '%s' "
            "called '%s' "
            "call_id '%.*s' "
            "dest_count '%d'\n",
            _osp_device_ip,
            _osp_device_port,
            deviceinfo,         /* in "[x.x.x.x]" or host.domain format */
            snid,
            calling,
            called,
            callids[0]->ospmCallIdLen,
            callids[0]->ospmCallIdVal,
            destcount
        );    

        /* try to request authorization */
        errorcode = OSPPTransactionRequestAuthorisation(
            transaction,       /* transaction handle */
            _osp_device_ip,    /* from the configuration file */
            deviceinfo,        /* source device of call, protocol specific, in OSP format */
            calling,           /* calling number in nodotted e164 notation */
            OSPC_E164,         /* calling number format */
            called,            /* called number */
            OSPC_E164,         /* called number format */
            "",                /* optional username string, used if no number */
            callidnumber,      /* number of call ids, here always 1 */
            callids,           /* sized-1 array of call ids */
            preferred,         /* preferred destinations, here always NULL */
            &destcount,        /* max destinations, after call dest_count */
            &logsize,          /* size allocated for detaillog (next param) 0=no log */
            detaillog);        /* memory location for detaillog to be stored */

        if ((errorcode == OSPC_ERR_NO_ERROR) &&
            (ospLoadRoutes(transaction, destcount, _osp_device_ip, sourcedev, called, authtime) == 0))
        {
            LM_INFO("there are '%d' OSP routes, call_id '%.*s'\n",
                destcount,
                callids[0]->ospmCallIdLen,
                callids[0]->ospmCallIdVal);
            result = MODULE_RETURNCODE_TRUE;
        } else {
            LM_ERR("failed to request auth and routing (%d), call_id '%.*s'\n",
                errorcode,
                callids[0]->ospmCallIdLen,
                callids[0]->ospmCallIdVal);
            switch (errorcode) {
                case OSPC_ERR_TRAN_ROUTE_BLOCKED:
                    result = -403;
                    break;
                case OSPC_ERR_TRAN_ROUTE_NOT_FOUND:
                    result = -404;
                    break;
                case OSPC_ERR_NO_ERROR:
                    /* AuthRsp ok but ospLoadRoutes fails */
                    result = MODULE_RETURNCODE_ERROR;
                    break;
                default:
                    result = MODULE_RETURNCODE_FALSE;
                    break;
            }
        }
    }

    if (callids[0] != NULL) {
        OSPPCallIdDelete(&(callids[0]));
    }

    if (transaction != -1) {
        OSPPTransactionDelete(transaction);
    }
    
    return result;
}

/*
 * Check if there is a route
 * param msg SIP message
 * param ignore1
 * param ignore2
 * return MODULE_RETURNCODE_TRUE success, MODULE_RETURNCODE_FALSE failure
 */
int ospCheckRoute(
    struct sip_msg* msg, 
    char* ignore1, 
    char* ignore2)
{
    if (ospCheckOrigDestination() == 0) {
        return MODULE_RETURNCODE_TRUE;
    } else {
        return MODULE_RETURNCODE_FALSE;
    }
}

/*
 * Create RPID AVP
 * param msg SIP message
 * param dest Destination structure
 * return 0 success, 1 calling number same, -1 failure
 */
static int ospSetRpid(
    struct sip_msg* msg, 
    osp_dest* dest)
{
    str rpid;
    char calling[OSP_STRBUF_SIZE];
    char source[OSP_STRBUF_SIZE];
    char buffer[OSP_STRBUF_SIZE];
    int result = -1;

    if ((ospGetRpidUserpart(msg, calling, sizeof(calling)) != 0) &&
        (ospGetFromUserpart(msg, calling, sizeof(calling)) !=0))
    {
        LM_ERR("failed to extract calling number\n");
        return result;
    } 

    if (strcmp(calling, dest->calling) == 0) {
        /* Do nothing for this case */ 
        result = 1;
    } else if ((osp_siputils.rpid_avp.s.s == NULL)
				|| (osp_siputils.rpid_avp.s.len == 0)) {
        LM_WARN("rpid_avp is not foune, cannot set rpid avp\n");
        result = -1;
    } else {
        if (dest->source[0] == '[') {
            /* Strip "[]" */
            memset(source, 0, sizeof(source));
            strncpy(source, &dest->source[1], sizeof(source) - 1);
            source[strlen(source) - 1] = '\0';
        }
    
        snprintf(
            buffer, 
            sizeof(buffer), 
            "\"%s\" <sip:%s@%s>", 
            dest->calling, 
            dest->calling, 
            source);

        rpid.s = buffer;
        rpid.len = strlen(buffer);
        add_avp(osp_siputils.rpid_avp_type | AVP_VAL_STR,
				(int_str)osp_siputils.rpid_avp, (int_str)rpid);

        result = 0;
    }

    return result;
}

/*
 * Check if the calling number is translated.
 *     This function checks the avp set by ospPrepareDestination.
 * param msg SIP message
 * param ignore1
 * param ignore2
 * return MODULE_RETURNCODE_TRUE calling number translated MODULE_RETURNCODE_FALSE without transaltion
 */
int ospCheckTranslation(
    struct sip_msg* msg, 
    char* ignore1, 
    char* ignore2)
{
    int_str callingval;
    int result = MODULE_RETURNCODE_FALSE;

    if (search_first_avp(AVP_NAME_STR, (int_str)OSP_CALLING_NAME, &callingval, 0) != NULL) {
        if (callingval.n == 0) {
            LM_DBG("the calling number does not been translated\n");
        } else {
            LM_DBG("the calling number is translated\n");
            result = MODULE_RETURNCODE_TRUE;
        }
    } else {
        LM_ERR("there is not calling translation avp\n");
    }

    return result;
}

/*
 * Build SIP message for destination
 * param msg SIP message
 * param isfirst Is first destination
 * param type Main or branch route block
 * param format URI format
 * return MODULE_RETURNCODE_TRUE success MODULE_RETURNCODE_FALSE failure
 */
static int ospPrepareDestination(
    struct sip_msg* msg, 
    int isfirst,
    int type,
    int format)
{
    int_str val;
    int res;
    str newuri = {NULL, 0};
    int result = MODULE_RETURNCODE_FALSE;

    osp_dest* dest = ospGetNextOrigDestination();

    if (dest != NULL) {
        ospRebuildDestionationUri(&newuri, dest->called, dest->host, "", format);

        LM_INFO("prepare route to URI '%.*s' for call_id '%.*s' transaction_id '%llu'\n",
            newuri.len,
            newuri.s,
            dest->callidsize,
            dest->callid,
            dest->transid);

        if (type == OSP_MAIN_ROUTE) {
            if (isfirst == OSP_FIRST_ROUTE) {
                rewrite_uri(msg, &newuri);
            } else {
                km_append_branch(msg, &newuri, NULL, NULL, Q_UNSPECIFIED, 0, NULL);
            }
            /* Do not add route specific OSP information */
            result = MODULE_RETURNCODE_TRUE;
        } else if (type == OSP_BRANCH_ROUTE) {
            /* For branch route, add route specific OSP information */

            /* Update the Request-Line */
            rewrite_uri(msg, &newuri);

            /* Add OSP token header */
            ospAddOspHeader(msg, dest->token, dest->tokensize);

            /* Add branch-specific OSP Cookie */
            ospRecordOrigTransaction(msg, dest->transid, dest->srcdev, dest->calling, dest->called, dest->authtime, dest->destinationCount);

            /* Add rpid avp for calling number translation */
            res = ospSetRpid(msg, dest);
            switch (res) {
                case 0:
                    /* Calling number is translated */
                    val.n = 1;
                    add_avp(AVP_NAME_STR, (int_str)OSP_CALLING_NAME, val);
                    break;
                default:
                    LM_DBG("cannot set rpid avp\n");
                    /* Just like without calling translation */
                case 1:
                    /* Calling number does not been translated */
                    val.n = 0;
                    add_avp(AVP_NAME_STR, (int_str)OSP_CALLING_NAME, val);
                    break;
            }

            result = MODULE_RETURNCODE_TRUE;
        } else {
            LM_ERR("unsupported route block type\n");
        }
    } else {
        LM_DBG("there is no more routes\n");
        ospReportOrigSetupUsage();
    }

    if (newuri.len > 0) {
        pkg_free(newuri.s);
    }
    
    return result;
}

/*
 * Prepare OSP route
 *     This function only works in branch route block.
 *     This function is only for Kamailio.
 * param msg SIP message
 * param ignore1
 * param ignore2
 * return MODULE_RETURNCODE_TRUE success, MODULE_RETURNCODE_FALSE failure
 */
int ospPrepareRoute(
    struct sip_msg* msg, 
    char* ignore1, 
    char* ignore2)
{
    int result = MODULE_RETURNCODE_TRUE;

    /* The first parameter will be ignored */
    result = ospPrepareDestination(msg, OSP_FIRST_ROUTE, OSP_BRANCH_ROUTE, 0);

    return result;
}

/*
 * Prepare all OSP routes
 *     This function does not work in branch route block.
 * param msg SIP message
 * param ignore1
 * param ignore2
 * return MODULE_RETURNCODE_TRUE success, MODULE_RETURNCODE_FALSE failure
 */
int ospPrepareAllRoutes(
    struct sip_msg* msg, 
    char* ignore1, 
    char* ignore2)
{
    int result = MODULE_RETURNCODE_TRUE;

    for(result = ospPrepareDestination(msg, OSP_FIRST_ROUTE, OSP_MAIN_ROUTE, _osp_redir_uri);
        result == MODULE_RETURNCODE_TRUE;
        result = ospPrepareDestination(msg, OSP_NEXT_ROUTE, OSP_MAIN_ROUTE, _osp_redir_uri))
    {
    }

    return MODULE_RETURNCODE_TRUE;
}

