/*
 * Copyright (C) 2013-21 Robert Boisvert
 *
 * This file is part of the mohqueue module for Kamailio, a free SIP server.
 *
 * The mohqueue module 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
 *
 * The mohqueue module 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 <stdarg.h>

#include "mohqueue_mod.h"
#include "mohq_db.h"
#include "mohq_funcs.h"

/**********
* definitions
**********/

#define ALLOWHDR "Allow: INVITE, ACK, BYE, CANCEL, NOTIFY, PRACK"
#define CLENHDR "Content-Length"
#define SIPEOL  "\r\n"
#define USRAGNT "Kamailio MOH Queue v1.8"

/**********
* local constants
**********/

str p100rel [1] = {STR_STATIC_INIT ("100rel")};
str pallq [1] = {STR_STATIC_INIT ("*")};
str paudio [1] = {STR_STATIC_INIT ("audio")};
str pbye [1] = {STR_STATIC_INIT ("BYE")};
str pinvite [1] = {STR_STATIC_INIT ("INVITE")};
str prefer [1] = {STR_STATIC_INIT ("REFER")};
str presp_busy [1] = {STR_STATIC_INIT ("Busy Here")};
str presp_noaccept [1] = {STR_STATIC_INIT ("Not Acceptable Here")};
str presp_noallow [1] = {STR_STATIC_INIT ("Method Not Allowed")};
str presp_nocall [1] = {STR_STATIC_INIT ("Call/Transaction Does Not Exist")};
str presp_ok [1] = {STR_STATIC_INIT ("OK")};
str presp_reqpend [1] = {STR_STATIC_INIT ("Request Pending")};
str presp_reqterm [1] = {STR_STATIC_INIT ("Request Terminated")};
str presp_ring [1] = {STR_STATIC_INIT ("Ringing")};
str psipfrag [1] = {STR_STATIC_INIT ("message/sipfrag")};
str presp_srverr [1] = {STR_STATIC_INIT ("Server Internal Error")};
str presp_trying [1] = {STR_STATIC_INIT ("Trying to enter MOH queue")};
str presp_unsupp [1] = {STR_STATIC_INIT ("Unsupported Media Type")};

rtpmap prtpmap [] =
  {
  {9, "G722/8000"},
  {0, "PCMU/8000"},
  {8, "PCMA/8000"},
  {18, "G729/8000"},
  {3, "GSM/8000"},
  {4, "G723/8000"},
  {15, "G728/8000"},
  {5, "DVI4/8000"},
  {7, "LPC/8000"},
  {12, "QCELP/8000"},
  {13, "CN/8000"},
  {16, "DVI4/11025"},
  {6, "DVI4/16000"},
  {17, "DVI4/22050"},
  {10, "L16/44100"},
  {11, "L16/44100"},
  {14, "MPA/90000"},
  {0, 0}
  };

rtpmap *pmohfiles [30]; // element count should be equal or greater than prtpmap

str pallowhdr [1] = { STR_STATIC_INIT (ALLOWHDR SIPEOL) };

char pbyemsg [] =
  {
  "%s"
  "%s"
  "Max-Forwards: 70" SIPEOL
  "Contact: <%s>" SIPEOL
  };

str pextrahdr [1] =
  {
  STR_STATIC_INIT (
  ALLOWHDR SIPEOL
  "Supported: 100rel" SIPEOL
  "Accept-Language: en" SIPEOL
  "Content-Type: application/sdp" SIPEOL
  )
  };

char pinvitesdp [] =
  {
  "v=0" SIPEOL
  "o=- %d %d IN %s" SIPEOL
  "s=" USRAGNT SIPEOL
  "c=IN %s" SIPEOL
  "t=0 0" SIPEOL
  "a=send%s" SIPEOL
  "m=audio %d RTP/AVP "
  };

char prefermsg [] =
  {
  "%s"
  "%s"
  "Contact: <%s>" SIPEOL
  "Max-Forwards: 70" SIPEOL
  "Refer-To: <%s>" SIPEOL
  "Referred-By: <%s>" SIPEOL
  };

char prtpsdp [] =
  {
  "v=0" SIPEOL
  // IP address and audio port faked since they will be replaced
  "o=- 1 1 IN IP4 1.1.1.1" SIPEOL
  "s=" USRAGNT SIPEOL
  "c=IN IP4 1.1.1.1" SIPEOL
  "t=0 0" SIPEOL
  "a=sendrecv" SIPEOL
  "m=audio 1 RTP/AVP"
  };

/**********
* local function declarations
**********/

void delete_call (call_lst *);
void end_RTP (sip_msg_t *, call_lst *);
dlg_t *form_dialog (call_lst *, struct to_body *);
int form_rtp_SDP (str *, call_lst *, char *);
static void invite_cb (struct cell *, int, struct tmcb_params *);
int refer_call (call_lst *, mohq_lock *);
static void refer_cb (struct cell *, int, struct tmcb_params *);
int send_prov_rsp (sip_msg_t *, call_lst *);
int send_rtp_answer (sip_msg_t *, call_lst *);
int search_hdr_ext (struct hdr_field *, str *);
int start_stream (sip_msg_t *, call_lst *, int);
int stop_stream (sip_msg_t *, call_lst *, int);

/**********
* local functions
**********/

/**********
* Process ACK Message
*
* INPUT:
*   Arg (1) = SIP message pointer
*   Arg (2) = call pointer
* OUTPUT: none
**********/

void ack_msg (sip_msg_t *pmsg, call_lst *pcall)

{
/**********
* part of INVITE?
**********/

char *pfncname = "ack_msg: ";
struct cell *ptrans;
tm_api_t *ptm = pmod_data->ptm;
if (pcall->call_state != CLSTA_INVITED)
  {
  /**********
  * ignore if from rejected re-INVITE
  **********/

  if (pcall->call_state != CLSTA_INQUEUE)
    { LM_ERR ("%sUnexpected ACK (%s)!\n", pfncname, pcall->call_from); }
  else
    {
    mohq_debug (pcall->pmohq, "%sACK from refused re-INVITE (%s)!\n",
      pfncname, pcall->call_from);
    }
  return;
  }

/**********
* release INVITE transaction
**********/

if (ptm->t_lookup_ident (&ptrans, pcall->call_hash, pcall->call_label) < 0)
  {
  LM_ERR ("%sINVITE transaction missing for call (%s)!\n",
    pfncname, pcall->call_from);
  return;
  }
else
  {
  /**********
  * create new transaction if current missing
  **********/

  tm_cell_t *ptcell;
  ptcell = ptm->t_gett ();
  if ((ptcell == NULL) || (ptcell == T_UNDEFINED))
    {
    if (ptm->t_newtran (pmsg) < 0)
      {
      LM_ERR ("%sUnable to create temporary transaction!\n", pfncname);
      return;
      }
    ptcell = ptm->t_gett ();
  if ((ptcell == NULL) || (ptcell == T_UNDEFINED))
      {
      LM_ERR ("%sUnable to find temporary transaction!\n", pfncname);
      return;
      }
    }
  if (ptm->t_release_transaction (ptcell) < 0)
    {
    LM_ERR ("%sRelease transaction failed for call (%s)!\n",
      pfncname, pcall->call_from);
    return;
    }
  }

/**********
* o save SDP address info
* o put in queue
**********/

pcall->call_hash = pcall->call_label = 0;
sprintf (pcall->call_addr, "%s %s",
  pmsg->rcv.dst_ip.af == AF_INET ? "IP4" : "IP6",
  ip_addr2a (&pmsg->rcv.dst_ip));
pcall->call_state = CLSTA_INQUEUE;
update_call_rec (pcall);
pcall->call_cseq = 1;
mohq_debug (pcall->pmohq,
  "%sACK received for call (%s); placed in queue (%s)",
  pfncname, pcall->call_from, pcall->pmohq->mohq_name);
return;
}

/**********
* Add String to Buffer
*
* INPUT:
*   Arg (1) = string pointer
*   Arg (2) = string length
*   Arg (3) = pointer to buffer pointer
*   Arg (4) = pointer to buffer size
*   Arg (5) = add NUL flag
* OUTPUT: =0 if insufficent space
*   bufpointer incremented, size decremented
**********/

int addstrbfr (char *pstr, size_t nlen, char **pbuf, size_t *nmax, int bnull)

{
/**********
* o enough space?
* o copy string
* o adjust position/size
**********/

size_t nsize = nlen;
if (bnull)
  { nsize++; }
if (nsize > *nmax)
  { return 0; }
if (nlen)
  {
  strncpy (*pbuf, pstr, nlen);
  *pbuf += nlen;
  }
if (bnull)
  {
  **pbuf = '\0';
  (*pbuf)++;
  }
*nmax -= nsize;
return 1;
}

/**********
* BYE Callback
*
* INPUT:
*   Arg (1) = cell pointer
*   Arg (2) = callback type
*   Arg (3) = callback parms
* OUTPUT: none
**********/

static void bye_cb
  (struct cell *ptrans, int ntype, struct tmcb_params *pcbp)

{
/**********
* o error means must have hung after REFER
* o delete the call
**********/

char *pfncname = "bye_cb: ";
call_lst *pcall = (call_lst *)*pcbp->param;
if (ntype == TMCB_ON_FAILURE)
  {
  LM_ERR ("%sCall (%s) did not respond to BYE!\n", pfncname,
    pcall->call_from);
  }
else
  {
  int nreply = pcbp->code;
  if ((nreply / 100) != 2)
    {
    LM_ERR ("%sCall (%s) BYE error (%d)!\n", pfncname,
      pcall->call_from, nreply);
    }
  else
    {
    mohq_debug (pcall->pmohq, "%sCall (%s) BYE reply=%d", pfncname,
      pcall->call_from, nreply);
    }
  }
delete_call (pcall);
return;
}

/**********
* Process BYE Message
*
* INPUT:
*   Arg (1) = SIP message pointer
*   Arg (2) = call pointer
* OUTPUT: none
**********/

void bye_msg (sip_msg_t *pmsg, call_lst *pcall)

{
/**********
* o responded?
* o teardown RTP
**********/

char *pfncname = "bye_msg: ";
if (pcall->call_state == CLSTA_BYEOK)
  { return; }
if (pcall->call_state >= CLSTA_INQUEUE)
  {
  pcall->call_state = CLSTA_BYEOK;
  end_RTP (pmsg, pcall);
  }
else
  {
  LM_ERR ("%sEnding call (%s) before placed in queue!\n",
    pfncname, pcall->call_from);
  }

/**********
* send OK and delete from queue
**********/

if (pmod_data->psl->freply (pmsg, 200, presp_ok) < 0)
  {
  LM_ERR ("%sUnable to create reply to call (%s)!\n", pfncname,
    pcall->call_from);
  return;
  }
delete_call (pcall);
return;
}

/**********
* Process CANCEL Message
*
* INPUT:
*   Arg (1) = SIP message pointer
*   Arg (2) = call pointer
* OUTPUT: none
**********/

void cancel_msg (sip_msg_t *pmsg, call_lst *pcall)

{
/**********
* RFC 3261 section 9.2
* o still in INVITE dialog?
**********/

char *pfncname = "cancel_msg: ";
if (pcall->call_state < CLSTA_INQUEUE)
  {
  pcall->call_state = CLSTA_CANCEL;
  mohq_debug (pcall->pmohq, "%sCANCELed call (%s)",
    pfncname, pcall->call_from);
  if (pmod_data->psl->freply (pmsg, 487, presp_reqterm) < 0)
    { LM_ERR ("%sUnable to create reply!\n", pfncname); }
  }
else
  {
  LM_ERR ("%sUnable to CANCEL because accepted INVITE for call (%s)!\n",
    pfncname, pcall->call_from);
  if (pmod_data->psl->freply (pmsg, 481, presp_nocall) < 0)
    { LM_ERR ("%sUnable to create reply!\n", pfncname); }
  }
return;
}

/**********
* Check if RTP Still Active
*
* INPUT:
*   Arg (1) = SIP message pointer
* OUTPUT: =0 if inactive
**********/

int chk_rtpstat (sip_msg_t *pmsg)

{
pv_value_t pval [1];
memset (pval, 0, sizeof (pv_value_t));
if (pv_get_spec_value (pmsg, prtp_pv, pval))
  { return 0; }
if (pval->flags & PV_VAL_NULL)
  { return 0; }
return 1;
}

/**********
* Close the Call
*
* INPUT:
*   Arg (1) = SIP message pointer
*   Arg (2) = call pointer
* OUTPUT: none
**********/

void close_call (sip_msg_t *pmsg, call_lst *pcall)

{
/**********
* o destroy RTP connection
* o create dialog
**********/

char *pfncname = "close_call: ";
int bsent = 0;
char *phdr = 0;
end_RTP (pmsg, pcall);
struct to_body ptob [2];
dlg_t *pdlg = form_dialog (pcall, ptob);
if (!pdlg)
  { goto bye_err; }
pdlg->state = DLG_CONFIRMED;

/**********
* form BYE header
* o calculate size
* o create buffer
**********/

tm_api_t *ptm = pmod_data->ptm;
char *pquri = pcall->pmohq->mohq_uri;
int npos1 = sizeof (pbyemsg) // BYE template
  + strlen (pcall->call_via) // Via
  + strlen (pcall->call_route) // Route
  + strlen (pquri); // Contact
phdr = pkg_malloc (npos1);
if (!phdr)
  {
  LM_ERR ("%sNo more memory!\n", pfncname);
  goto bye_err;
  }
sprintf (phdr, pbyemsg,
  pcall->call_via, // Via
  pcall->call_route, // Route
  pquri); // Contact
str phdrs [1];
phdrs->s = phdr;
phdrs->len = strlen (phdr);

/**********
* send BYE request
**********/

uac_req_t puac [1];
set_uac_req (puac, pbye, phdrs, 0, pdlg,
  TMCB_LOCAL_COMPLETED | TMCB_ON_FAILURE, bye_cb, pcall);
pcall->call_state = CLSTA_BYE;
if (ptm->t_request_within (puac) < 0)
  {
  LM_ERR ("%sUnable to create BYE request for call (%s)!\n",
    pfncname, pcall->call_from);
  goto bye_err;
  }
mohq_debug (pcall->pmohq, "%sSent BYE request for call (%s)",
  pfncname, pcall->call_from);
bsent = 1;

/**********
* o free memory
* o delete call
**********/

bye_err:
if (pdlg)
  { pkg_free (pdlg); }
if (phdr)
  { pkg_free (phdr); }
if (!bsent)
  { delete_call (pcall); }
return;
}

/**********
* Create New Call Record
*
* INPUT:
*   Arg (1) = SIP message pointer
*   Arg (2) = call pointer
*   Arg (3) = call index
*   Arg (4) = queue index
* OUTPUT: initializes call record; =0 if failed
**********/

int
create_call (sip_msg_t *pmsg, call_lst *pcall, int ncall_idx, int mohq_idx)

{
char *pfncname = "create_call: ";
char *pbuf;
str *pstr;

/**********
* add values to new entry
* o call ID
* o from
**********/

pcall->pmohq = &pmod_data->pmohq_lst [mohq_idx];
pstr = &pmsg->callid->body;
pbuf = pcall->call_buffer;
pcall->call_buflen = sizeof (pcall->call_buffer);
pcall->call_id = pbuf;
if (!addstrbfr (pstr->s, pstr->len, &pbuf, &pcall->call_buflen, 1))
  { return 0; }
pstr = &pmsg->from->body;
pcall->call_from = pbuf;
if (!addstrbfr (pstr->s, pstr->len, &pbuf, &pcall->call_buflen, 1))
  { return 0; }
pcall->call_contact = pbuf;
if (pmsg->contact)
  {
  pstr = &pmsg->contact->body;
  if (!addstrbfr (pstr->s, pstr->len, &pbuf, &pcall->call_buflen, 0))
    { return 0; }
  }
if (!addstrbfr (0, 0, &pbuf, &pcall->call_buflen, 1))
  { return 0; }

/**********
* extract Via headers
**********/

pcall->call_via = pbuf;
hdr_field_t *phdr;
for (phdr = pmsg->h_via1; phdr; phdr = next_sibling_hdr (phdr))
  {
  struct via_body *pvia;
  char *pviabuf;
  int npos;
  for (pvia = (struct via_body *)phdr->parsed; pvia; pvia = pvia->next)
    {
    /**********
    * skip trailing whitespace
    **********/

    npos = pvia->bsize;
    pviabuf = pvia->name.s;
    while (npos)
      {
      --npos;
      if (pviabuf [npos] == ' ' || pviabuf [npos] == '\r'
        || pviabuf [npos] == '\n' || pviabuf [npos] == '\t'
        || pviabuf [npos] == ',')
        { continue; }
      break;
      }

    /**********
    * copy via
    **********/

    if (!addstrbfr ("Via: ", 5, &pbuf, &pcall->call_buflen, 0))
      { return 0; }
    if (!addstrbfr (pviabuf, npos + 1, &pbuf, &pcall->call_buflen, 0))
      { return 0; }
    if (!addstrbfr (SIPEOL, 2, &pbuf, &pcall->call_buflen, 0))
      { return 0; }
    }
  }
if (!addstrbfr (0, 0, &pbuf, &pcall->call_buflen, 1))
  { return 0; }

/**********
* extract Route headers
**********/

pcall->call_route = pbuf;
struct hdr_field *proute;
for (proute = pmsg->record_route; proute; proute = next_sibling_hdr (proute))
  {
  if (parse_rr (proute) < 0)
    { return 0; }
  rr_t *prouterr;
  for (prouterr = proute->parsed; prouterr; prouterr = prouterr->next)
    {
    if (!addstrbfr ("Route: ", 7, &pbuf, &pcall->call_buflen, 0))
      { return 0; }
    if (!addstrbfr (prouterr->nameaddr.name.s, prouterr->len,
      &pbuf, &pcall->call_buflen, 0))
      { return 0; }
    if (!addstrbfr (SIPEOL, 2, &pbuf, &pcall->call_buflen, 0))
      { return 0; }
    }
  }
if (!addstrbfr (0, 0, &pbuf, &pcall->call_buflen, 1))
  { return 0; }

/**********
* o place tag at the end
* o update DB
**********/

pcall->call_tag = pbuf;
if (!addstrbfr (0, 0, &pbuf, &pcall->call_buflen, 1))
  { return 0; }
pcall->call_state = CLSTA_ENTER;
add_call_rec (ncall_idx);
mohq_debug (pcall->pmohq, "%sAdded call (%s) to queue (%s)",
  pfncname, pcall->call_from, pcall->pmohq->mohq_name);
return 1;
}

/**********
* Delete Call
*
* INPUT:
*   Arg (1) = call pointer
* OUTPUT: none
**********/

void delete_call (call_lst *pcall)

{
/**********
* release transaction
**********/

char *pfncname = "delete_call: ";
struct cell *ptrans;
tm_api_t *ptm = pmod_data->ptm;
if (pcall->call_hash || pcall->call_label)
  {
  if (ptm->t_lookup_ident (&ptrans, pcall->call_hash, pcall->call_label) < 0)
    {
    LM_ERR ("%sLookup transaction failed for call (%s) from queue (%s)!\n",
      pfncname, pcall->call_from, pcall->pmohq->mohq_name);
    }
  else
    {
    if (ptm->t_release (pcall->call_pmsg) < 0)
      {
      LM_ERR ("%sRelease transaction failed for call (%s) from queue (%s)!\n",
        pfncname, pcall->call_from, pcall->pmohq->mohq_name);
      }
    }
  pcall->call_hash = pcall->call_label = 0;
  }

/**********
* o update DB
* o inactivate slot
**********/

if (!mohq_lock_set (pmod_data->pcall_lock, 1, 5000))
  {
  LM_ERR ("%sUnable to set call lock for call (%s) from queue (%s)!\n",
    pfncname, pcall->call_from, pcall->pmohq->mohq_name);
  }
else
  {
  mohq_debug (pcall->pmohq, "%sDeleting call (%s) from queue (%s)",
    pfncname, pcall->call_from, pcall->pmohq->mohq_name);
  delete_call_rec (pcall);
  mohq_lock_release (pmod_data->pcall_lock);
  }
pcall->call_state = 0;
return;
}

/**********
* Deny Method
*
* INPUT:
*   Arg (1) = SIP message pointer
*   Arg (2) = call pointer
* OUTPUT: none
**********/

void deny_method (sip_msg_t *pmsg, call_lst *pcall)

{
/**********
* RFC 3261 section 8.2.1
* o get transaction
* o respond with 405 and Allow header
**********/

char *pfncname = "deny_method: ";
tm_api_t *ptm = pmod_data->ptm;
if (ptm->t_newtran (pmsg) < 0)
  {
  LM_ERR ("%sUnable to create new transaction!\n", pfncname);
  if (pmod_data->psl->freply (pmsg, 500, presp_srverr) < 0)
    {
    LM_ERR ("%sUnable to create reply to %.*s!\n", pfncname,
      STR_FMT (&REQ_LINE (pmsg).method));
    }
  return;
  }
if (!add_lump_rpl2 (pmsg, pallowhdr->s, pallowhdr->len, LUMP_RPL_HDR))
  { LM_ERR ("%sUnable to add Allow header!\n", pfncname); }
LM_ERR ("%sRefused %.*s for call (%s)!\n", pfncname,
  STR_FMT (&REQ_LINE (pmsg).method), pcall->call_from);
if (ptm->t_reply (pmsg, 405, presp_noallow->s) < 0)
  {
  LM_ERR ("%sUnable to create reply to %.*s!\n", pfncname,
    STR_FMT (&REQ_LINE (pmsg).method));
  }
return;
}

/**********
* End RTP
*
* INPUT:
*   Arg (1) = SIP message pointer
*   Arg (2) = call pointer
* OUTPUT: none
**********/

void end_RTP (sip_msg_t *pmsg, call_lst *pcall)

{
/**********
* destroy RTP connection
**********/

char *pfncname = "end_RTP: ";
if ((pmsg != FAKED_REPLY) && (pcall->call_state != CLSTA_ENTER))
  {
  mohq_debug (pcall->pmohq, "%sDestroying RTP link for call (%s)",
    pfncname, pcall->call_from);
  if (pmod_data->fn_rtp_destroy (pmsg, 0, 0) != 1)
    {
    LM_ERR ("%srtpproxy_destroy refused for call (%s)!\n",
      pfncname, pcall->call_from);
    }
  }
return;
}

/**********
* Find Call
*
* INPUT:
*   Arg (1) = SIP message pointer
*   Arg (2) = queue index
* OUTPUT: call pointer; =0 if unable to find/create
**********/

call_lst *find_call (sip_msg_t *pmsg, int mohq_idx)

{
/**********
* o get to tag
* o get callID
* o search calls
**********/

char *pfncname = "find_call: ";
str *ptotag = &(get_to (pmsg)->tag_value);
if (!ptotag->len)
  { ptotag = 0; }
if (!pmsg->callid)
  {
  LM_ERR ("%sNo call ID!\n", pfncname);
  return 0;
  }
str *pcallid = &pmsg->callid->body;
if (!pcallid)
  {
  LM_ERR ("%sNo call ID!\n", pfncname);
  return 0;
  }
int nopen = -1;
int nidx;
call_lst *pcall;
for (nidx = 0; nidx < pmod_data->call_cnt; nidx++)
  {
  /**********
  * o call active?
  * o call timed out on ACK?
  * o callID matches?
  * o to tag matches?
  **********/

  pcall = &pmod_data->pcall_lst [nidx];
  if (!pcall->call_state)
    {
    nopen = nidx;
    continue;
    }
#if 0 /* ??? need to handle */
  if (pcall->call_time && (pcall->call_state < CLSTA_INQUEUE))
    {
    if ((pcall->call_time + 32) < time (0))
      {
      LM_ERR ("%sNo ACK response for call (%s)!\n", pfncname, pcall->call_from);
      delete_call (pcall);
      continue;
      }
    }
#endif /* ??? */
  str tmpstr [1];
  tmpstr->s = pcall->call_id;
  tmpstr->len = strlen (tmpstr->s);
  if (!STR_EQ (*tmpstr, *pcallid))
    { continue; }
  if (ptotag)
    {
    tmpstr->s = pcall->call_tag;
    tmpstr->len = strlen (tmpstr->s);
    if (!STR_EQ (*tmpstr, *ptotag))
      { continue; }
    }
  else
    {
    /**********
    * match not allowed for INVITE
    **********/

    if (pmsg->REQ_METHOD == METHOD_INVITE)
      { return 0; }
    }
  return pcall;
  }

/**********
* o first INVITE?
* o create a new call record
**********/

if (pmsg->REQ_METHOD != METHOD_INVITE)
  { return 0; }
if (ptotag)
  { return 0; }
if (nopen < 0)
  {
  LM_ERR ("%sNo call slots available!\n", pfncname);
  return 0;
  }
pcall = &pmod_data->pcall_lst [nopen];
if (!create_call (pmsg, pcall, nopen, mohq_idx))
  { return 0; }
return pcall;
}

/**********
* Find Queue
*
* INPUT:
*   Arg (1) = SIP message pointer
* OUTPUT: queue index; -1 if unable to find
**********/

int find_queue (sip_msg_t *pmsg)

{
/**********
* o find current RURI
* o strip off parms or headers
* o search queues
**********/

str *pruri =
  pmsg->new_uri.s ? &pmsg->new_uri : &pmsg->first_line.u.request.uri;
int nidx;
str pstr [1];
pstr->s = pruri->s;
pstr->len = pruri->len;
for (nidx = 0; nidx < pruri->len; nidx++)
  {
  if (pstr->s [nidx] == ';' || pstr->s [nidx] == '?')
    {
    pstr->len = nidx;
    break;
    }
  }
mohq_lst *pqlst = pmod_data->pmohq_lst;
int nqidx;
for (nqidx = 0; nqidx < pmod_data->mohq_cnt; nqidx++)
  {
  str pmohstr [1];
  pmohstr->s = pqlst [nqidx].mohq_uri;
  pmohstr->len = strlen (pmohstr->s);
  if (STR_EQ (*pmohstr, *pstr))
    { break; }
  }
if (nqidx == pmod_data->mohq_cnt)
  { return -1;}
return nqidx;
}

/**********
* Find Queue Name
*
* INPUT:
*   Arg (1) = queue name str pointer
* OUTPUT: queue index; -1 if unable to find
**********/

int find_qname (str *pqname)

{
char *pfncname = "find_qname: ";
int nidx;
str tmpstr;
if (!mohq_lock_set (pmod_data->pmohq_lock, 0, 500))
  {
  LM_ERR ("%sUnable to lock queues!\n", pfncname);
  return -1;
  }
for (nidx = 0; nidx < pmod_data->mohq_cnt; nidx++)
  {
  tmpstr.s = pmod_data->pmohq_lst [nidx].mohq_name;
  tmpstr.len = strlen (tmpstr.s);
  if (STR_EQ (tmpstr, *pqname))
    { break; }
  }
if (nidx == pmod_data->mohq_cnt)
  {
  LM_ERR ("%sUnable to find queue (%.*s)!\n", pfncname, STR_FMT (pqname));
  nidx = -1;
  }
mohq_lock_release (pmod_data->pmohq_lock);
return nidx;
}

/**********
* Find Referred Call
*
* INPUT:
*   Arg (1) = referred-by value
* OUTPUT: call index; -1 if unable to find
**********/

int find_referred_call (str *pvalue)

{
/**********
* get URI
**********/

char *pfncname = "find_referred_call: ";
struct to_body pref [1];
parse_to (pvalue->s, &pvalue->s [pvalue->len + 1], pref);
if (pref->error != PARSE_OK)
  {
  // should never happen
  LM_ERR ("%sInvalid Referred-By URI (%.*s)!\n", pfncname, STR_FMT (pvalue));
  return -1;
  }
if (pref->param_lst)
  { free_to_params (pref); }

/**********
* search calls for matching
**********/

int nidx;
str tmpstr;
struct to_body pfrom [1];
for (nidx = 0; nidx < pmod_data->call_cnt; nidx++)
  {
  if (!pmod_data->pcall_lst [nidx].call_state)
    { continue; }
  tmpstr.s = pmod_data->pcall_lst [nidx].call_from;
  tmpstr.len = strlen (tmpstr.s);
  parse_to (tmpstr.s, &tmpstr.s [tmpstr.len + 1], pfrom);
  if (pfrom->error != PARSE_OK)
    {
    // should never happen
    LM_ERR ("%sInvalid From URI (%.*s)!\n", pfncname, STR_FMT (&tmpstr));
    continue;
    }
  if (pfrom->param_lst)
    { free_to_params (pfrom); }
  if (STR_EQ (pfrom->uri, pref->uri))
    { return nidx; }
  }
return -1;
}

/**********
* Process First INVITE Message
*
* INPUT:
*   Arg (1) = SIP message pointer
*   Arg (2) = call pointer
* OUTPUT: none
**********/

void first_invite_msg (sip_msg_t *pmsg, call_lst *pcall)

{
char *pfncname = "first_invite_msg: ";

/**********
* o SDP exists?
* o accepts REFER?
**********/

if (!(pmsg->msg_flags & FL_SDP_BODY))
  {
  if (parse_sdp (pmsg))
    {
    if (pmod_data->psl->freply (pmsg, 488, presp_noaccept) < 0)
      {
      LM_ERR ("%sUnable to create reply!\n", pfncname);
      return;
      }
    LM_ERR ("%sINVITE lacks SDP (%s) from queue (%s)!\n",
      pfncname, pcall->call_from, pcall->pmohq->mohq_name);
    delete_call (pcall);
    return;
    }
  }
if (pmsg->allow)
  {
  if (!search_hdr_ext (pmsg->allow, prefer))
    {
    if (pmod_data->psl->freply (pmsg, 488, presp_noaccept) < 0)
      {
      LM_ERR ("%sUnable to create reply!\n", pfncname);
      return;
      }
    LM_ERR ("%sMissing REFER support (%s) from queue (%s)!\n",
      pfncname, pcall->call_from, pcall->pmohq->mohq_name);
    delete_call (pcall);
    return;
    }
  }

/**********
* send RTP offer
**********/

fparam_t rflag = {.orig="", .type=FPARAM_STRING, {.asciiz="r"}, .fixed=0};
mohq_debug (pcall->pmohq,
  "%sMaking offer for RTP link for call (%s) from queue (%s)",
  pfncname, pcall->call_from, pcall->pmohq->mohq_name);
if (pmod_data->fn_rtp_offer (pmsg, (char *) &rflag, 0) != 1)
  {
  if (pmod_data->psl->freply (pmsg, 486, presp_busy) < 0)
    {
    LM_ERR ("%sUnable to create reply!\n", pfncname);
    return;
    }
  LM_ERR ("%srtpproxy_offer refused for call (%s)!\n",
      pfncname, pcall->call_from);
  delete_call (pcall);
  return;
  }

/**********
* o create new transaction
* o save To tag
* o catch failures
* o save transaction data
**********/

tm_api_t *ptm = pmod_data->ptm;
if (ptm->t_newtran (pmsg) < 0)
  {
  LM_ERR ("%sUnable to create new transaction for call (%s)!\n",
    pfncname, pcall->call_from);
  end_RTP (pmsg, pcall);
  delete_call (pcall);
  return;
  }
struct cell *ptrans = ptm->t_gett ();
pcall->call_hash = ptrans->hash_index;
pcall->call_label = ptrans->label;
str ptotag [1];
if (ptm->t_get_reply_totag (pmsg, ptotag) != 1)
  {
  LM_ERR ("%sUnable to create totag for call (%s)!\n",
    pfncname, pcall->call_from);
  if (ptm->t_reply (pmsg, 500, presp_srverr->s) < 0)
    { LM_ERR ("%sUnable to reply to INVITE!\n", pfncname); }
  end_RTP (pmsg, pcall);
  delete_call (pcall);
  return;
  }
char *pbuf = pcall->call_tag;
if (!addstrbfr (ptotag->s, ptotag->len, &pbuf, &pcall->call_buflen, 1))
  {
  LM_ERR ("%sInsufficient buffer space for call (%s)!\n",
    pfncname, pcall->call_from);
  if (ptm->t_reply (pmsg, 500, presp_srverr->s) < 0)
    { LM_ERR ("%sUnable to reply to INVITE!\n", pfncname); }
  end_RTP (pmsg, pcall);
  delete_call (pcall);
  return;
  }
pcall->call_cseq = 1;
if (ptm->register_tmcb (pmsg, 0, TMCB_DESTROY | TMCB_ON_FAILURE,
  invite_cb, pcall, 0) < 0)
  {
  LM_ERR ("%sUnable to set callback for call (%s)!\n",
    pfncname, pcall->call_from);
  if (ptm->t_reply (pmsg, 500, presp_srverr->s) < 0)
    { LM_ERR ("%sUnable to reply to INVITE!\n", pfncname); }
  end_RTP (pmsg, pcall);
  delete_call (pcall);
  return;
  }

/**********
* reply with trying
**********/

if (ptm->t_reply (pmsg, 100, presp_trying->s) < 0)
  {
  LM_ERR ("%sUnable to create reply!\n", pfncname);
  end_RTP (pmsg, pcall);
  delete_call (pcall);
  return;
  }
pcall->call_state = CLSTA_TRYING;

/**********
* o add contact to reply
* o supports/requires PRACK? (RFC 3262 section 3)
* o exit if not ringing
**********/

str pcontact [1];
char *pcontacthdr = "Contact: <%s>" SIPEOL;
pcontact->s
  = pkg_malloc (strlen (pcall->pmohq->mohq_uri) + strlen (pcontacthdr));
if (!pcontact->s)
  {
  LM_ERR ("%sNo more memory!\n", pfncname);
  end_RTP (pmsg, pcall);
  delete_call (pcall);
  return;
  }
sprintf (pcontact->s, pcontacthdr, pcall->pmohq->mohq_uri);
pcontact->len = strlen (pcontact->s);
if (!add_lump_rpl2 (pmsg, pcontact->s, pcontact->len, LUMP_RPL_HDR))
  {
  LM_ERR ("%sUnable to add contact (%s) to call (%s)!\n",
    pfncname, pcontact->s, pcall->call_from);
  }
pkg_free (pcontact->s);
pcall->call_pmsg = pmsg;
if (search_hdr_ext (pmsg->require, p100rel))
  {
  if (!send_prov_rsp (pmsg, pcall))
    {
    end_RTP (pmsg, pcall);
    delete_call (pcall);
    return;
    }
  if (pcall->call_state == CLSTA_CANCEL)
    {
    end_RTP (pmsg, pcall);
    delete_call (pcall);
    return;
    }
  }

/**********
* accept call with RTP
**********/

if (!send_rtp_answer (pmsg, pcall))
  {
  if (pmod_data->psl->freply (pmsg, 500, presp_srverr) < 0)
    { LM_ERR ("%sUnable to create reply!\n", pfncname); }
  end_RTP (pmsg, pcall);
  delete_call (pcall);
  }
return;
}

/**********
* Form Dialog
*
* INPUT:
*   Arg (1) = call pointer
*   Arg (2) = to_body [2] pointer
* OUTPUT: dlg_t * if successful; 0=if not
**********/

dlg_t *form_dialog (call_lst *pcall, struct to_body *pto_body)

{
/**********
* get from/to values
**********/

char *pfncname = "form_dialog: ";
str pdsturi [1], ptarget [1];
int index;
name_addr_t pname [1];
struct to_body *ptob = &pto_body [0];
struct to_body *pcontact = &pto_body [1];
parse_to (pcall->call_from,
  &pcall->call_from [strlen (pcall->call_from) + 1], ptob);
if (ptob->error != PARSE_OK)
  {
  // should never happen
  LM_ERR ("%sInvalid from URI (%s)!\n", pfncname, pcall->call_from);
  return 0;
  }
if (ptob->param_lst)
  { free_to_params (ptob); }

/**********
* form dest URI from record route
**********/

if (!*pcall->call_route)
  { pdsturi->s = 0; }
else
  {
  /**********
  * o find first route URI
  * o strip off parameter
  **********/

  pdsturi->s = pcall->call_route;
  pdsturi->len = strlen (pcall->call_route);
  if (parse_nameaddr (pdsturi, pname) < 0)
    {
    // should never happen
    LM_ERR ("%sUnable to parse route (%s)!\n", pfncname, pcall->call_from);
    return 0;
    }
  pdsturi->s = pname->uri.s;
  pdsturi->len = pname->uri.len;
  for (index = 1; index < pdsturi->len; index++)
    {
    if (pdsturi->s [index] == ';')
      {
      pdsturi->len = index;
      break;
      }
    }
  }

/**********
* form target URI
**********/

if (!*pcall->call_contact)
  {
  ptarget->s = ptob->uri.s;
  ptarget->len = ptob->uri.len;
  }
else
  {
  parse_to (pcall->call_contact,
    &pcall->call_contact [strlen (pcall->call_contact) + 1], pcontact);
  if (pcontact->error != PARSE_OK)
    {
    // should never happen
    LM_ERR ("%sInvalid contact (%s) for call (%s)!\n", pfncname,
      pcall->call_contact, pcall->call_from);
    return 0;
    }
  if (pcontact->param_lst)
    { free_to_params (pcontact); }
  ptarget->s = pcontact->uri.s;
  ptarget->len = pcontact->uri.len;
  }

/**********
* create dialog
**********/

dlg_t *pdlg = (dlg_t *)pkg_malloc (sizeof (dlg_t));
if (!pdlg)
  {
  LM_ERR ("%sNo more memory!\n", pfncname);
  return 0;
  }
memset (pdlg, 0, sizeof (dlg_t));
pdlg->loc_seq.value = pcall->call_cseq++;
pdlg->loc_seq.is_set = 1;
pdlg->id.call_id.s = pcall->call_id;
pdlg->id.call_id.len = strlen (pcall->call_id);
pdlg->id.loc_tag.s = pcall->call_tag;
pdlg->id.loc_tag.len = strlen (pcall->call_tag);
pdlg->id.rem_tag.s = ptob->tag_value.s;
pdlg->id.rem_tag.len = ptob->tag_value.len;
pdlg->rem_target.s = ptarget->s;
pdlg->rem_target.len = ptarget->len;
pdlg->loc_uri.s = pcall->pmohq->mohq_uri;
pdlg->loc_uri.len = strlen (pdlg->loc_uri.s);
pdlg->rem_uri.s = ptob->uri.s;
pdlg->rem_uri.len = ptob->uri.len;
if (pdsturi->s)
  {
  pdlg->dst_uri.s = pdsturi->s;
  pdlg->dst_uri.len = pdsturi->len;
  }
return pdlg;
}

/**********
* Form RTP SDP String
*
* INPUT:
*   Arg (1) = string pointer
*   Arg (2) = call pointer
*   Arg (3) = SDP body pointer
* OUTPUT: 0 if failed
**********/

int form_rtp_SDP (str *pstr, call_lst *pcall, char *pSDP)

{
/**********
* o find available files
* o calculate size of SDP
**********/

char *pfncname = "form_rtp_SDP: ";
rtpmap **pmohfiles = find_MOH (pcall->pmohq->mohq_mohdir,
  pcall->pmohq->mohq_mohfile);
if (!pmohfiles [0])
  {
  LM_ERR ("%sUnable to find any MOH files for queue (%s)!\n", pfncname,
    pcall->pmohq->mohq_name);
  return 0;
  }
int nsize = strlen (pSDP) + 2;
int nidx;
for (nidx = 0; pmohfiles [nidx]; nidx++)
  {
  nsize += strlen (pmohfiles [nidx]->pencode) // encode length
    + 19; // space, type number, "a=rtpmap:%d ", EOL
  }

/**********
* o allocate memory
* o form SDP
**********/

pstr->s = pkg_malloc (nsize + 1);
if (!pstr->s)
  {
  LM_ERR ("%sNo more memory!\n", pfncname);
  return 0;
  }
strcpy (pstr->s, pSDP);
nsize = strlen (pstr->s);
for (nidx = 0; pmohfiles [nidx]; nidx++)
  {
  /**********
  * add payload types to media description
  **********/

  sprintf (&pstr->s [nsize], " %d", pmohfiles [nidx]->ntype);
  nsize += strlen (&pstr->s [nsize]);
  }
strcpy (&pstr->s [nsize], SIPEOL);
nsize += 2;
for (nidx = 0; pmohfiles [nidx]; nidx++)
  {
  /**********
  * add rtpmap attributes
  **********/

  sprintf (&pstr->s [nsize], "a=rtpmap:%d %s %s",
    pmohfiles [nidx]->ntype, pmohfiles [nidx]->pencode, SIPEOL);
  nsize += strlen (&pstr->s [nsize]);
  }
pstr->len = nsize;
return 1;
}

/**********
* Invite Callback
*
* INPUT:
*   Arg (1) = cell pointer
*   Arg (2) = callback type
*   Arg (3) = callback parms
* OUTPUT: none
**********/

static void
  invite_cb (struct cell *ptrans, int ntype, struct tmcb_params *pcbp)

{
call_lst *pcall = (call_lst *)*pcbp->param;
if (pcall->call_state >= CLSTA_INQUEUE)
  { return; }
LM_ERR ("invite_cb: INVITE failed for call (%s), code=%x, callstate=%x!\n",
  pcall->call_from, ntype, pcall->call_state);
if (ntype == TMCB_DESTROY)
  { pcall->call_hash = pcall->call_label = 0; }
delete_call (pcall);
return;
}

/**********
* Process NOTIFY Message
*
* INPUT:
*   Arg (1) = SIP message pointer
*   Arg (2) = call pointer
* OUTPUT: none
**********/

void notify_msg (sip_msg_t *pmsg, call_lst *pcall)

{
/**********
* waiting on REFER?
**********/

char *pfncname = "notify_msg: ";
if (pcall->call_state != CLSTA_RFRWAIT)
  {
  LM_ERR ("%sNot waiting on a REFER for call (%s)!\n", pfncname,
    pcall->call_from);
  if (pmod_data->psl->freply (pmsg, 481, presp_nocall) < 0)
    { LM_ERR ("%sUnable to create reply!\n", pfncname); }
  return;
  }

/**********
* o sipfrag?
* o get status from body
* o add CRLF so parser can go beyond first line
**********/

if (!search_hdr_ext (pmsg->content_type, psipfrag))
  {
  LM_ERR ("%sNot a %s type for call (%s)!\n", pfncname,
    psipfrag->s, pcall->call_from);
  if (pmod_data->psl->freply (pmsg, 415, presp_unsupp) < 0)
    { LM_ERR ("%sUnable to create reply!\n", pfncname); }
  return;
  }
char *pfrag = get_body (pmsg);
if (!pfrag)
  {
  LM_ERR ("%s%s body missing for call (%s)!\n", pfncname,
    psipfrag->s, pcall->call_from);
  if (pmod_data->psl->freply (pmsg, 415, presp_unsupp) < 0)
    { LM_ERR ("%sUnable to create reply!\n", pfncname); }
  return;
  }
str pbody [1];
pbody->len = pmsg->len - (int)(pfrag - pmsg->buf);
pbody->s = pkg_malloc (pbody->len + 2);
if (!pbody->s)
  {
  LM_ERR ("%sNo more memory!\n", pfncname);
  return;
  }
memcpy (pbody->s, pfrag, pbody->len);
if (pbody->s [pbody->len - 1] != '\n')
  {
  memcpy (&pbody->s [pbody->len], SIPEOL, 2);
  pbody->len += 2;
  }
struct msg_start pstart [1];
parse_first_line (pbody->s, pbody->len + 1, pstart);
pkg_free (pbody->s);
if (pstart->type != SIP_REPLY)
  {
  LM_ERR ("%sReply missing for call (%s)!\n", pfncname, pcall->call_from);
  if (pmod_data->psl->freply (pmsg, 415, presp_unsupp) < 0)
    { LM_ERR ("%sUnable to create reply!\n", pfncname); }
  return;
  }

/**********
* o send OK
* o REFER done?
**********/

int nreply = pstart->u.reply.statuscode;
if (pmod_data->psl->freply (pmsg, 200, presp_ok) < 0)
  {
  LM_ERR ("%sUnable to create reply for call (%s)!\n",
    pfncname, pcall->call_from);
  return;
  }
mohq_debug (pcall->pmohq, "%sNOTIFY received reply (%d) for call (%s)",
  pfncname, nreply, pcall->call_from);
switch (nreply / 100)
  {
  case 1:
    pcall->refer_time = time (0);
    break;
  case 2:
    close_call (pmsg, pcall);
    break;
  default:
    LM_WARN ("%sUnable to redirect call (%s)\n", pfncname, pcall->call_from);
    if (nreply == 487)
      {
      /**********
      * call was cancelled
      **********/

      end_RTP (pmsg, pcall);
      delete_call (pcall);
      return;
      }

    /**********
    * return call to queue
    **********/

    pcall->call_state = CLSTA_INQUEUE;
    update_call_rec (pcall);
    break;
  }
return;
}

/**********
* Process PRACK Message
*
* INPUT:
*   Arg (1) = SIP message pointer
*   Arg (2) = call pointer
* OUTPUT: none
**********/

void prack_msg (sip_msg_t *pmsg, call_lst *pcall)

{
/**********
* waiting on PRACK?
**********/

char *pfncname = "prack_msg: ";
tm_api_t *ptm = pmod_data->ptm;
if (pcall->call_state != CLSTA_PRACKSTRT)
  {
  LM_ERR ("%sUnexpected PRACK (%s)!\n", pfncname, pcall->call_from);
  if (pmod_data->psl->freply (pmsg, 481, presp_nocall) < 0)
    { LM_ERR ("%sUnable to create reply!\n", pfncname); }
  return;
  }

/**********
* o check RAck ??? need to check
* o accept PRACK
**********/

if (ptm->t_newtran (pmsg) < 0)
  {
  LM_ERR ("%sUnable to create new transaction for call (%s)!\n",
    pfncname, pcall->call_from);
  if (pmod_data->psl->freply (pmsg, 500, presp_srverr) < 0)
    { LM_ERR ("%sUnable to create reply!\n", pfncname); }
  return;
  }
if (ptm->t_reply (pmsg, 200, presp_ok->s) < 0)
  {
  LM_ERR ("%sUnable to reply to PRACK for call (%s)!\n",
    pfncname, pcall->call_from);
  return;
  }
pcall->call_state = CLSTA_PRACKRPLY;
return;
}

/**********
* Refer Call
*
* INPUT:
*   Arg (1) = call pointer
*   Arg (2) = lock pointer
* OUTPUT: 0 if failed
**********/

int refer_call (call_lst *pcall, mohq_lock *plock)

{
/**********
* create dialog
**********/

char *pfncname = "refer_call: ";
int nret = 0;
struct to_body ptob [2];
dlg_t *pdlg = form_dialog (pcall, ptob);
if (!pdlg)
  {
  mohq_lock_release (plock);
  return 0;
  }
pdlg->state = DLG_CONFIRMED;

/**********
* form REFER message
* o calculate basic size
* o create buffer
**********/

str puri [1];
puri->s = pcall->call_referto;
puri->len = strlen (puri->s);
int npos1 = sizeof (prefermsg) // REFER template
  + strlen (pcall->call_via) // Via
  + strlen (pcall->call_route) // Route
  + strlen (pcall->pmohq->mohq_uri) // Contact
  + puri->len // Refer-To
  + strlen (pcall->pmohq->mohq_uri); // Referred-By
char *pbuf = pkg_malloc (npos1);
if (!pbuf)
  {
  LM_ERR ("%sNo more memory!\n", pfncname);
  goto refererr;
  }
sprintf (pbuf, prefermsg,
  pcall->call_via, // Via
  pcall->call_route, // Route
  pcall->pmohq->mohq_uri, // Contact
  puri->s, // Refer-To
  pcall->pmohq->mohq_uri); // Referred-By

/**********
* send REFER request
**********/

tm_api_t *ptm = pmod_data->ptm;
uac_req_t puac [1];
str phdrs [1];
phdrs->s = pbuf;
phdrs->len = strlen (pbuf);
set_uac_req (puac, prefer, phdrs, 0, pdlg,
  TMCB_LOCAL_COMPLETED | TMCB_ON_FAILURE, refer_cb, pcall);
pcall->refer_time = time (0);
pcall->call_state = CLSTA_REFER;
update_call_rec (pcall);
mohq_lock_release (plock);
if (ptm->t_request_within (puac) < 0)
  {
  pcall->call_state = CLSTA_INQUEUE;
  LM_ERR ("%sUnable to create REFER request for call (%s)!\n",
    pfncname, pcall->call_from);
  update_call_rec (pcall);
  goto refererr;
  }
mohq_debug (pcall->pmohq, "%sSent REFER request for call (%s) to %s",
  pfncname, pcall->call_from, pcall->call_referto);
nret = -1;

refererr:
if (pdlg)
  { pkg_free (pdlg); }
pkg_free (pbuf);
return nret;
}

/**********
* REFER Callback
*
* INPUT:
*   Arg (1) = cell pointer
*   Arg (2) = callback type
*   Arg (3) = callback parms
* OUTPUT: none
**********/

static void refer_cb
  (struct cell *ptrans, int ntype, struct tmcb_params *pcbp)
{
char *pfncname = "refer_cb: ";
call_lst *pcall = (call_lst *)*pcbp->param;
if (pcall->call_state != CLSTA_REFER)
  {
  if (!pcall->call_state)
    {
    LM_ERR
      ("%sREFER response ignored because call not in queue!\n", pfncname);
    }
  else
    {
    LM_ERR ("%sCall (%s) ignored because not in REFER state!\n", pfncname,
      pcall->call_from);
    }
  return;
  }
if ((ntype == TMCB_ON_FAILURE) || (pcbp->req == FAKED_REPLY))
  {
  LM_ERR ("%sCall (%s) did not respond to REFER!\n", pfncname,
    pcall->call_from);
  end_RTP (pcbp->req, pcall);
  delete_call (pcall);
  return;
  }

/**********
* check reply
**********/

int nreply = pcbp->code;
if ((nreply / 100) == 2)
  {
  pcall->refer_time = time (0);
  pcall->call_state = CLSTA_RFRWAIT;
  mohq_debug (pcall->pmohq, "%sCall (%s) REFER reply=%d",
    pfncname, pcall->call_from, nreply);
  }
else
  {
  LM_ERR ("%sCall (%s) REFER error (%d)!\n", pfncname,
    pcall->call_from, nreply);
  if (nreply == 481)
    { delete_call (pcall); }
  else
    {
    if (!chk_rtpstat (pcbp->req))
      {
      LM_ERR ("%sRTP for call (%s) no longer active!\n",
        pfncname, pcall->call_from);
      delete_call (pcall);
      }
    else
      {
      pcall->call_state = CLSTA_INQUEUE;
      update_call_rec (pcall);
      }
    }
  }
return;
}

/**********
* Process re-INVITE Message
*
* INPUT:
*   Arg (1) = SIP message pointer
*   Arg (2) = call pointer
* OUTPUT: none
**********/

void reinvite_msg (sip_msg_t *pmsg, call_lst *pcall)

{
/**********
* RFC 3261 section 14.2
* o dialog pending?
* o get SDP
**********/

char *pfncname = "reinvite_msg: ";
if ((pcall->call_state / 100) < 2)
  {
  mohq_debug (pcall->pmohq, "%sINVITE still pending for call (%s)",
    pfncname, pcall->call_from);
  if (pmod_data->psl->freply (pmsg, 491, presp_reqpend) < 0)
    { LM_ERR ("%sUnable to create reply!\n", pfncname); }
  return;
  }
if (!(pmsg->msg_flags & FL_SDP_BODY))
  {
  if (parse_sdp (pmsg))
    {
    LM_ERR ("%sre-INVITE lacks SDP (%s)!\n", pfncname, pcall->call_from);
    if (pmod_data->psl->freply (pmsg, 488, presp_noaccept) < 0)
      { LM_ERR ("%sUnable to create reply!\n", pfncname); }
    return;
    }
  }

/**********
* o find available MOH files
* o look for hold condition and matching payload type
**********/

rtpmap **pmohfiles = find_MOH (pcall->pmohq->mohq_mohdir,
  pcall->pmohq->mohq_mohfile);
int bhold = 0;
int bmatch = 0;
int nsession;
sdp_session_cell_t *psession;
for (nsession = 0; (psession = get_sdp_session (pmsg, nsession)); nsession++)
  {
  int nstream;
  sdp_stream_cell_t *pstream;
  for (nstream = 0; (pstream = get_sdp_stream (pmsg, nsession, nstream));
    nstream++)
    {
    /**********
    * o RTP?
    * o audio?
    * o hold?
    * o at least one payload matches?
    **********/

    if (!pstream->is_rtp)
      { continue; }
    if (!STR_EQ (*paudio, pstream->media))
      { continue; }
    if (pstream->is_on_hold)
      {
      bhold = 1;
      break;
      }
    if (bmatch)
      { continue; }

    /**********
    * check payload types for a match
    **********/

    sdp_payload_attr_t *ppayload;
    for (ppayload = pstream->payload_attr; ppayload; ppayload = ppayload->next)
      {
      int ntype = atoi (ppayload->rtp_payload.s);
      int nidx;
      for (nidx = 0; pmohfiles [nidx]; nidx++)
        {
        if (pmohfiles [nidx]->ntype == ntype)
          {
          bmatch = 1;
          break;
          }
        }
      }
    }
  }

/**********
* if no hold, allow re-INVITE if matching file
**********/

if (!bhold)
  {
  if (!bmatch)
    {
    LM_ERR ("%sre-INVITE refused because no matching payload for call (%s)!\n",
      pfncname, pcall->call_from);
    if (pmod_data->psl->freply (pmsg, 488, presp_noaccept) < 0)
      {
      LM_ERR ("%sUnable to create reply!\n", pfncname);
      return;
      }
    }
  else
    {
    mohq_debug (pcall->pmohq, "%sAccepted re-INVITE for call (%s)",
      pfncname, pcall->call_from);
    if (pmod_data->psl->freply (pmsg, 200, presp_ok) < 0)
      {
      LM_ERR ("%sUnable to create reply!\n", pfncname);
      return;
      }
    }
  return;
  }

/**********
* hold not allowed, say good-bye
**********/

LM_ERR ("%sTerminating call (%s) because hold not allowed!\n",
  pfncname, pcall->call_from);
if (pmod_data->psl->freply (pmsg, 200, presp_ok) < 0)
  {
  LM_ERR ("%sUnable to create reply!\n", pfncname);
  return;
  }
close_call (pmsg, pcall);
return;
}

/**********
* Search Header for Extension
*
* INPUT:
*   Arg (1) = header field pointer
*   Arg (2) = extension str pointer
* OUTPUT: 0=not found
**********/

int search_hdr_ext (struct hdr_field *phdr, str *pext)

{
if (!phdr)
  { return 0; }
str *pstr = &phdr->body;
int npos1, npos2;
for (npos1 = 0; npos1 < pstr->len; npos1++)
  {
  /**********
  * o find non-space
  * o search to end, space, semicolon or comma
  * o same size?
  * o same name?
  **********/

  if (pstr->s [npos1] == ' ')
    { continue; }
  for (npos2 = npos1++; npos1 < pstr->len; npos1++)
    {
    if (pstr->s [npos1] == ' ' || pstr->s [npos1] == ';'
      || pstr->s [npos1] == ',')
      { break; }
    }
  if (npos1 - npos2 != pext->len)
    { continue; }
  if (!strncasecmp (&pstr->s [npos2], pext->s, pext->len))
    { return 1; }
  }
return 0;
}

/**********
* Send Provisional Response
*
* INPUT:
*   Arg (1) = SIP message pointer
*   Arg (2) = call pointer
* OUTPUT: 0=unable to process; 1=processed
**********/

int send_prov_rsp (sip_msg_t *pmsg, call_lst *pcall)

{
/**********
* o send ringing response with require
* o update record
**********/

char *pfncname = "send_prov_rsp: ";
tm_api_t *ptm = pmod_data->ptm;
pcall->call_cseq = rand ();
char phdrtmp [200];
char *phdrtmplt =
  "Accept-Language: en" SIPEOL
  "Require: 100rel" SIPEOL
  "RSeq: %d" SIPEOL
  ;
sprintf (phdrtmp, phdrtmplt, pcall->call_cseq);
struct lump_rpl **phdrlump = add_lump_rpl2 (pmsg, phdrtmp,
  strlen (phdrtmp), LUMP_RPL_HDR);
if (!phdrlump)
  {
  LM_ERR ("%sUnable to create new header for call (%s)!\n",
    pfncname, pcall->call_from);
  if (pmod_data->psl->freply (pmsg, 500, presp_srverr) < 0)
    { LM_ERR ("%sUnable to create reply!\n", pfncname); }
  return 0;
  }
if (ptm->t_reply (pmsg, 180, presp_ring->s) < 0)
  {
  LM_ERR ("%sUnable to reply to INVITE for call (%s)!\n",
    pfncname, pcall->call_from);
  return 0;
  }
pcall->call_state = CLSTA_PRACKSTRT;
mohq_debug (pcall->pmohq, "%sSent PRACK RINGING for call (%s)",
  pfncname, pcall->call_from);

/**********
* o wait until PRACK (64*T1 RFC 3261 section 7.1.1)
* o remove header lump
**********/

time_t nstart = time (0) + 32;
while (1)
  {
  usleep (USLEEP_LEN);
  if (pcall->call_state != CLSTA_PRACKSTRT)
    { break; }
  if (nstart < time (0))
    {
    LM_ERR ("%sNo PRACK response for call (%s)!\n",
      pfncname, pcall->call_from);
    break;
    }
  }
unlink_lump_rpl (pmsg, *phdrlump);
if (pcall->call_state != CLSTA_PRACKRPLY)
  { return 0; }
return 1;
}

/**********
* Send RTPProxy Answer
*
* INPUT:
*   Arg (1) = SIP message pointer
*   Arg (2) = call pointer
* OUTPUT: 0=unable to process; 1=processed
**********/

int send_rtp_answer (sip_msg_t *pmsg, call_lst *pcall)

{
/**********
* build response from request
**********/

char *pfncname = "send_rtp_answer: ";
int nret = 0;
tm_api_t *ptm = pmod_data->ptm;
struct cell *ptrans = ptm->t_gett ();
str ptotag [1];
ptotag->s = pcall->call_tag;
ptotag->len = strlen (pcall->call_tag);
str pbuf [1];
struct bookmark pBM [1];
pbuf->s = build_res_buf_from_sip_req (200, presp_ok, ptotag, ptrans->uas.request,
  (unsigned int *)&pbuf->len, pBM);
if (!pbuf->s || !pbuf->len)
  {
  LM_ERR ("%sUnable to create SDP response for call (%s)!\n",
    pfncname, pcall->call_from);
  return 0;
  }

/**********
* parse out first line and headers
**********/

char *pclenhdr = CLENHDR;
str pparse [20];
int npos1, npos2;
int nhdrcnt = 0;
for (npos1 = 0; npos1 < pbuf->len; npos1++)
  {
  /**********
  * find EOL
  **********/

  for (npos2 = npos1++; npos1 < pbuf->len; npos1++)
    {
    /**********
    * o not EOL? (CRLF assumed)
    * o next line a continuation? (RFC 3261 section 7.3.1)
    **********/

    if (pbuf->s [npos1] != '\n')
      { continue; }
    if (npos1 + 1 == pbuf->len)
      { break; }
    if (pbuf->s [npos1 + 1] == ' '
      || pbuf->s [npos1 + 1] == '\t')
      { continue; }
    break;
    }

  /**********
  * o blank is end of header (RFC 3261 section 7)
  * o ignore Content-Length (assume followed by colon)
  * o save header
  **********/

  if (npos1 - npos2 == 1)
    { break; }
  if (npos1 - npos2 > 14)
    {
    if (!strncasecmp (&pbuf->s [npos2], pclenhdr, 14))
      { continue; }
    }
  pparse [nhdrcnt].s = &pbuf->s [npos2];
  pparse [nhdrcnt++].len = npos1 - npos2 + 1;
  }

/**********
* recreate buffer with extra headers and SDP
* o form SDP
* o count hdrs, extra hdrs, content-length hdr, SDP
* o alloc new buffer
* o form new buffer
* o replace orig buffer
**********/

str pSDP [1] = {STR_NULL};
if (!form_rtp_SDP (pSDP, pcall, prtpsdp))
  { goto answer_done; }
for (npos1 = npos2 = 0; npos2 < nhdrcnt; npos2++)
  { npos1 += pparse [npos2].len; }
char pbodylen [30];
sprintf (pbodylen, "%s: %d\r\n\r\n", pclenhdr, pSDP->len);
npos1 += pextrahdr->len + strlen (pbodylen) + pSDP->len + 1;
char *pnewbuf = pkg_malloc (npos1);
if (!pnewbuf)
  {
  LM_ERR ("%sNo more memory!\n", pfncname);
  goto answer_done;
  }
for (npos1 = npos2 = 0; npos2 < nhdrcnt; npos2++)
  {
  memcpy (&pnewbuf [npos1], pparse [npos2].s, pparse [npos2].len);
  npos1 += pparse [npos2].len;
  }
npos2 = pextrahdr->len;
memcpy (&pnewbuf [npos1], pextrahdr->s, npos2);
npos1 += npos2;
npos2 = strlen (pbodylen);
memcpy (&pnewbuf [npos1], pbodylen, npos2);
npos1 += npos2;
npos2 = pSDP->len;
memcpy (&pnewbuf [npos1], pSDP->s, npos2);
npos1 += npos2;
pkg_free (pbuf->s);
pbuf->s = pnewbuf;
pbuf->len = npos1;

/**********
* build SIP msg
**********/

struct sip_msg pnmsg [1];
build_sip_msg_from_buf (pnmsg, pbuf->s, pbuf->len, 0);
memcpy (&pnmsg->rcv, &pmsg->rcv, sizeof (struct receive_info));

/**********
* ptime set?
**********/

int nsession;
sdp_session_cell_t *psession;
char pflagbuf [5];
strcpy (pflagbuf, "z20");
fparam_t zflag = {.orig="", .type=FPARAM_STRING, {.asciiz=pflagbuf}, .fixed=0};
fparam_t *pzflag = &zflag;
for (nsession = 0; (psession = get_sdp_session (pmsg, nsession)); nsession++)
  {
  int nstream;
  sdp_stream_cell_t *pstream;
  for (nstream = 0; (pstream = get_sdp_stream (pmsg, nsession, nstream));
    nstream++)
    {
    /**********
    * ptime set?
    **********/

    if ((pstream->ptime.len < 1) || (pstream->ptime.len > 3))
      { continue; }
    strncpy (&pzflag->v.asciiz [1], pstream->ptime.s, pstream->ptime.len);
    pzflag->v.asciiz [pstream->ptime.len + 1] = 0;
    mohq_debug (pcall->pmohq,
      "%sSet ptime (%s) for RTP link for call (%s) from queue (%s)",
      pfncname,
      &pzflag->v.asciiz [1], pcall->call_from, pcall->pmohq->mohq_name);
    break;
    }
  }

/**********
* o send RTP answer
* o form stream file
* o send stream
**********/

mohq_debug (pcall->pmohq, "%sAnswering RTP link for call (%s)",
  pfncname, pcall->call_from);
if (pmod_data->fn_rtp_answer (pnmsg, (char *) pzflag, 0) != 1)
  {
  LM_ERR ("%srtpproxy_answer refused for call (%s)!\n",
    pfncname, pcall->call_from);
  goto answer_done;
  }
if (!start_stream (pnmsg, pcall, 0))
  { goto answer_done; }

/**********
* o create buffer from response
* o find SDP
**********/

pbuf->s = build_res_buf_from_sip_res (pnmsg, (unsigned int *)&pbuf->len);
pkg_free (pnewbuf);
free_sip_msg (pnmsg);
if (!pbuf->s || !pbuf->len)
  {
  LM_ERR ("%sUnable to create SDP response for call (%s)!\n",
    pfncname, pcall->call_from);
  goto answer_done;
  }
str pnewSDP [1];
for (npos1 = 0; npos1 < pbuf->len; npos1++)
  {
  if (pbuf->s [npos1] != '\n')
    { continue; }
  if (pbuf->s [npos1 - 3] == '\r')
    { break; }
  }
pnewSDP->s = &pbuf->s [npos1 + 1];
pnewSDP->len = pbuf->len - npos1 - 1;

/**********
* o save media port number
* o send adjusted reply
**********/

char *pfnd = strstr (pnewSDP->s, "m=audio ");
if (!pfnd)
  {
  // should not happen
  LM_ERR ("%sUnable to find audio port for call (%s)!\n",
    pfncname, pcall->call_from);
  goto answer_done;
  }
pcall->call_aport = strtol (pfnd + 8, NULL, 10);
if (!add_lump_rpl2 (pmsg, pextrahdr->s, pextrahdr->len, LUMP_RPL_HDR))
  {
  LM_ERR ("%sUnable to add header for call (%s)!\n",
    pfncname, pcall->call_from);
  goto answer_done;
  }
if (!add_lump_rpl2 (pmsg, pnewSDP->s, pnewSDP->len, LUMP_RPL_BODY))
  {
  LM_ERR ("%sUnable to add SDP body for call (%s)!\n",
    pfncname, pcall->call_from);
  goto answer_done;
  }
if (ptm->t_reply (pmsg, 200, presp_ok->s) < 0)
  {
  LM_ERR ("%sUnable to reply to INVITE for call (%s)!\n",
    pfncname, pcall->call_from);
  goto answer_done;
  }
pcall->call_state = CLSTA_INVITED;
mohq_debug (pcall->pmohq, "%sResponded to INVITE with RTP for call (%s)",
  pfncname, pcall->call_from);
nret = 1;

/**********
* free buffer and return
**********/

answer_done:
if (pSDP->s)
  { pkg_free (pSDP->s); }
pkg_free (pbuf->s);
return nret;
}

/**********
* Start Streaming
*
* INPUT:
*   Arg (1) = SIP message pointer
*   Arg (2) = call pointer
*   Arg (3) = server flag
* OUTPUT: 0 if failed
**********/

int start_stream (sip_msg_t *pmsg, call_lst *pcall, int bserver)

{
char *pfncname = "start_stream: ";
char pfile [MOHDIRLEN + MOHFILELEN + 2];
strcpy (pfile, pcall->pmohq->mohq_mohdir);
int npos = strlen (pfile);
pfile [npos++] = '/';
strcpy (&pfile [npos], pcall->pmohq->mohq_mohfile);
npos += strlen (&pfile [npos]);
str pMOH [1] = {{pfile, npos}};
pv_elem_t *pmodel;
if (pv_parse_format (pMOH, &pmodel) < 0)
  {
  LM_ERR("failed to parse pv format string\n");
  return 0;
  }
cmd_function fn_stream = bserver ? pmod_data->fn_rtp_stream_s
  : pmod_data->fn_rtp_stream_c;
mohq_debug (pcall->pmohq, "%sStarting RTP link for call (%s)",
  pfncname, pcall->call_from);
if (fn_stream (pmsg, (char *)pmodel, (char *)-1) != 1)
  {
  LM_ERR ("%srtpproxy_stream refused for call (%s)!\n",
    pfncname, pcall->call_from);
  return 0;
  }
return 1;
}

/**********
* Stop Streaming
*
* INPUT:
*   Arg (1) = SIP message pointer
*   Arg (2) = call pointer
*   Arg (3) = server flag
* OUTPUT: 0 if failed
**********/

int stop_stream (sip_msg_t *pmsg, call_lst *pcall, int bserver)

{
char *pfncname = "stop_stream: ";
cmd_function fn_stop = bserver ? pmod_data->fn_rtp_stop_s
  : pmod_data->fn_rtp_stop_c;
mohq_debug (pcall->pmohq, "%sStopping RTP link for call (%s)",
  pfncname, pcall->call_from);
if (fn_stop (pmsg, (char *)-1, (char *)-1) != 1)
  {
  LM_ERR ("%srtpproxy_stop refused for call (%s)!\n",
    pfncname, pcall->call_from);
  return 0;
  }
return 1;
}

/**********
* Form Char Array from STR
*
* INPUT:
*   Arg (1) = str pointer
* OUTPUT: char pointer; NULL if unable to allocate
**********/

char *form_tmpstr (str *pstr)

{
char *pcstr = malloc (pstr->len + 1);
if (!pcstr)
  {
  LM_ERR ("No more memory!\n");
  return NULL;
  }
memcpy (pcstr, pstr->s, pstr->len);
pcstr [pstr->len] = 0;
return pcstr;
}

/**********
* Release Char Array
*
* INPUT:
*   Arg (1) = char pointer
* OUTPUT: none
**********/

void free_tmpstr (char *pcstr)

{
if (pcstr)
  { free (pcstr); }
return;
}

/**********
* external functions
**********/

/**********
* Find MOH Files
*
* INPUT:
*   Arg (1) = mohdir pointer
*   Arg (2) = mohfile pointer
* OUTPUT: array of pointers for matching files; last element=0
**********/

rtpmap **find_MOH (char *pmohdir, char *pmohfile)

{
/**********
* form base file name
**********/

char pfile [MOHDIRLEN + MOHFILELEN + 6];
strcpy (pfile, pmohdir);
int nflen = strlen (pfile);
pfile [nflen++] = '/';
strcpy (&pfile [nflen], pmohfile);
nflen += strlen (&pfile [nflen]);
pfile [nflen++] = '.';

/**********
* find available files based on RTP payload type
**********/

int nidx;
int nfound = 0;
for (nidx = 0; prtpmap [nidx].pencode; nidx++)
  {
  /**********
  * o form file name based on payload type
  * o exists?
  **********/

  sprintf (&pfile [nflen], "%d", prtpmap [nidx].ntype);
  struct stat psb [1];
  if (lstat (pfile, psb))
    { continue; }
  pmohfiles [nfound++] = &prtpmap [nidx];
  }
pmohfiles [nfound] = 0;
return pmohfiles;
}

/**********
* RPC Debug
*
* PARAMETERS:
* queue name = queue to use
* state = 0=off, <>0=on
*
* INPUT:
*   Arg (1) = rpc handle
*   Arg (2) = rpc context pointer
* OUTPUT: none
**********/

void mohqueue_rpc_debug (rpc_t *prpc, void *pctx)

{
/**********
* o parm count correct?
* o find queue
* o lock queue
**********/

str pqname [1];
int bdebug;
if (prpc->scan (pctx, "Sd", pqname, &bdebug) != 2)
  {
  prpc->fault (pctx, 400, "Too few parameters!");
  return;
  }
int nq_idx = find_qname (pqname);
if (nq_idx == -1)
  {
  prpc->fault (pctx, 401, "No such queue (%.*s)!", STR_FMT (pqname));
  return;
  }
if (!mohq_lock_set (pmod_data->pcall_lock, 0, 5000))
  {
  prpc->fault
    (pctx, 402, "Unable to lock the queue (%.*s)!", STR_FMT (pqname));
  return;
  }

/**********
* o set flag
* o update queue table
* o release lock
**********/

mohq_lst *pqueue = &pmod_data->pmohq_lst [nq_idx];
if (bdebug)
  { pqueue->mohq_flags |= MOHQF_DBG; }
else
  { pqueue->mohq_flags &= ~MOHQF_DBG; }
update_debug (pqueue, bdebug);
mohq_lock_release (pmod_data->pmohq_lock);
return;
}

/**********
* RPC Drop Call
*
* PARAMETERS:
* queue name = queue to use
* callID = *=all, otherwise callID
*
* INPUT:
*   Arg (1) = rpc handle
*   Arg (2) = rpc context pointer
* OUTPUT: none
**********/

void mohqueue_rpc_drop_call (rpc_t *prpc, void *pctx)

{
/**********
* o parm count correct?
* o find queue
* o lock calls
**********/

str pcallid [1], pqname [1];
if (prpc->scan (pctx, "SS", pqname, pcallid) != 2)
  {
  prpc->fault (pctx, 400, "Too few parameters!");
  return;
  }
int nq_idx = find_qname (pqname);
if (nq_idx == -1)
  {
  prpc->fault (pctx, 401, "No such queue (%.*s)!", STR_FMT (pqname));
  return;
  }
if (!mohq_lock_set (pmod_data->pcall_lock, 0, 5000))
  {
  prpc->fault
    (pctx, 402, "Unable to lock the queue (%.*s)!", STR_FMT (pqname));
  return;
  }

/**********
* o find matching calls
* o release lock
**********/

mohq_lst *pqueue = &pmod_data->pmohq_lst [nq_idx];
int nidx;
for (nidx = 0; nidx < pmod_data->call_cnt; nidx++)
  {
  /**********
  * o call active?
  * o callID matches?
  * o close call
  **********/

  call_lst *pcall = &pmod_data->pcall_lst [nidx];
  if (!pcall->call_state)
    { continue; }
  if (pqueue->mohq_id != pcall->pmohq->mohq_id)
    { continue; }
  str tmpstr [1];
  if (!STR_EQ (*pcallid, *pallq))
    {
    tmpstr->s = pcall->call_id;
    tmpstr->len = strlen (tmpstr->s);
    if (!STR_EQ (*tmpstr, *pcallid))
      { continue; }
    }
  close_call (FAKED_REPLY, pcall);
  }
mohq_lock_release (pmod_data->pcall_lock);
return;
}

/**********
* Count Messages
*
* INPUT:
*   Arg (1) = SIP message pointer
*   Arg (2) = queue name
*   Arg (3) = pv result name
* OUTPUT: -1 if no items in queue; else result = count
**********/

int mohq_count (sip_msg_t *pmsg, char *pqueue, pv_spec_t *presult)

{
/**********
* get queue and pv names
**********/

char *pfncname = "mohq_count: ";
str pqname [1];
if (!pqueue || !presult)
  {
  LM_ERR ("%sParameters missing!\n", pfncname);
  return -1;
  }
if (fixup_get_svalue (pmsg, (gparam_p)pqueue, pqname))
  {
  LM_ERR ("%sInvalid queue name!\n", pfncname);
  return -1;
  }

/**********
* o find queue
* o lock calls
* o count items in queue
**********/

int nq_idx = find_qname (pqname);
int ncount = 0;
call_lst *pcalls = pmod_data->pcall_lst;
int ncall_idx, mohq_id;
if (!mohq_lock_set (pmod_data->pcall_lock, 0, 200))
  { LM_ERR ("%sUnable to lock calls!\n", pfncname); }
else
  {
  if (nq_idx != -1)
    {
    mohq_id = pmod_data->pmohq_lst [nq_idx].mohq_id;
    for (ncall_idx = 0; ncall_idx < pmod_data->call_cnt; ncall_idx++)
      {
      if (!pcalls [ncall_idx].call_state)
        { continue; }
      if (pcalls [ncall_idx].pmohq->mohq_id == mohq_id
        && pcalls [ncall_idx].call_state == CLSTA_INQUEUE)
        { ncount++; }
      }
    }
  mohq_lock_release (pmod_data->pcall_lock);
  }

/**********
* o set pv result
* o exit with result
**********/

pv_value_t pavp_val [1];
memset (pavp_val, 0, sizeof (pv_value_t));
pavp_val->ri = ncount;
pavp_val->flags = PV_TYPE_INT | PV_VAL_INT;
if (presult->setf (pmsg, &presult->pvp, (int)EQ_T, pavp_val) < 0)
  {
  LM_ERR ("%sUnable to set pv value for mohq_count ()!\n", pfncname);
  return -1;
  }
return 1;
}

/**********
* Log Debug Statement
*
* INPUT:
*   Arg (1) = MOH queue pointer
*   Arg (2) = format pointer
*   Arg (...) = optional format values
* OUTPUT: outputs debugging values
**********/

void mohq_debug (mohq_lst *pmohq, char *pfmt, ...)

{
/**********
* o get system and MOHQ log level
* o exit if no debug printing
* o force local debug
* o form message and log
* o reset log level
**********/

int nsys_log = get_debug_level (LOG_MNAME, LOG_MNAME_LEN);
int nmohq_log = (pmohq->mohq_flags & MOHQF_DBG) ? L_DBG : L_INFO;
if (nmohq_log < L_DBG && nsys_log < L_DBG)
  { return; }
if (nsys_log < nmohq_log)
  { set_local_debug_level (nmohq_log); }
char ptext [1024];
va_list ap;
va_start (ap, pfmt);
vsnprintf (ptext, sizeof (ptext), pfmt, ap);
va_end (ap);
LM_DBG ("%s\n", ptext);
if (nsys_log < nmohq_log)
  { reset_local_debug_level (); }
return;
}

/**********
* Process Message
*
* INPUT:
*   Arg (1) = SIP message pointer
* OUTPUT: -1=not directed to queue or other error; 1=processed
**********/

int mohq_process (sip_msg_t *pmsg)

{
/**********
* read lock queue and check for updates
**********/

char *pfncname = "mohq_process: ";
if (!mohq_lock_set (pmod_data->pmohq_lock, 0, 500))
  {
  LM_ERR ("%sUnable to read lock queue!\n", pfncname);
  return -1;
  }
db1_con_t *pconn = mohq_dbconnect ();
if (pconn)
  {
  /**********
  * o last update older than 1 minute?
  * o update write locked queue
  **********/

  if (pmod_data->mohq_update + 60 < time (0))
    {
    if (mohq_lock_change (pmod_data->pmohq_lock, 1))
      {
      update_mohq_lst (pconn);
      mohq_lock_change (pmod_data->pmohq_lock, 0);
      pmod_data->mohq_update = time (0);
      }
    }
  mohq_dbdisconnect (pconn);
  }

/**********
* o parse headers
* o directed to message queue?
* o write lock calls
* o search for call
* o release call lock
**********/

if (parse_headers (pmsg, HDR_EOH_F, 0) < 0)
  {
  mohq_lock_release (pmod_data->pmohq_lock);
  LM_ERR ("%sUnable to parse header!\n", pfncname);
  return -1;
  }
int mohq_idx = find_queue (pmsg);
if (mohq_idx < 0)
  {
  mohq_lock_release (pmod_data->pmohq_lock);
  return -1;
  }
if (!mohq_lock_set (pmod_data->pcall_lock, 1, 500))
  {
  mohq_lock_release (pmod_data->pmohq_lock);
  LM_ERR ("%sUnable to write lock calls!\n", pfncname);
  return 1;
  }
call_lst *pcall = find_call (pmsg, mohq_idx);
mohq_lock_release (pmod_data->pcall_lock);
if (!pcall)
  {
  mohq_lock_release (pmod_data->pmohq_lock);
  return 1;
  }

/**********
* o process message
* o release queue lock
**********/

mohq_debug (&pmod_data->pmohq_lst [mohq_idx],
  "%sProcessing %.*s, queue (%s)", pfncname,
  STR_FMT (&REQ_LINE (pmsg).method),
  pmod_data->pmohq_lst [mohq_idx].mohq_name);
str *ptotag;
switch (pmsg->REQ_METHOD)
  {
  case METHOD_INVITE:
    /**********
    * initial INVITE?
    **********/

    ptotag = &(get_to (pmsg)->tag_value);
    if (!ptotag->len)
      { ptotag = 0; }
    if (!ptotag)
      { first_invite_msg (pmsg, pcall); }
    else
      { reinvite_msg (pmsg, pcall); }
    break;
  case METHOD_NOTIFY:
    notify_msg (pmsg, pcall);
    break;
  case METHOD_PRACK:
    prack_msg (pmsg, pcall);
    break;
  case METHOD_ACK:
    ack_msg (pmsg, pcall);
    break;
  case METHOD_BYE:
    bye_msg (pmsg, pcall);
    break;
  case METHOD_CANCEL:
    cancel_msg (pmsg, pcall);
    break;
  default:
    deny_method (pmsg, pcall);
    break;
  }
mohq_lock_release (pmod_data->pmohq_lock);
return 1;
}

/**********
* Retrieve Oldest Queued Call
*
* INPUT:
*   Arg (1) = SIP message pointer
*   Arg (2) = queue name
*   Arg (3) = redirect URI
* OUTPUT: -1 if no items in queue or error; 1 redirects oldest call
**********/

int mohq_retrieve (sip_msg_t *pmsg, char *pqueue, char *pURI)

{
/**********
* o get queue name and URI
* o check URI
**********/

char *pfncname = "mohq_retrieve: ";
str puri [1], pqname [1];
if (!pqueue || !pURI)
  {
  LM_ERR ("%sParameters missing!\n", pfncname);
  return -1;
  }
if (fixup_get_svalue (pmsg, (gparam_p)pqueue, pqname))
  {
  LM_ERR ("%sInvalid queue name!\n", pfncname);
  return -1;
  }
if (fixup_get_svalue (pmsg, (gparam_p)pURI, puri))
  {
  LM_ERR ("%sInvalid URI!\n", pfncname);
  return -1;
  }
if (puri->len > URI_LEN)
  {
  LM_ERR ("%sURI too long!\n", pfncname);
  return -1;
  }
struct sip_uri puri_parsed [1];
if (parse_uri (puri->s, puri->len, puri_parsed))
  {
  LM_ERR ("%sInvalid URI (%.*s)!\n", pfncname, STR_FMT (puri));
  return -1;
  }

/**********
* o find queue
* o lock calls
* o find oldest call
**********/

int nq_idx = find_qname (pqname);
if (nq_idx == -1)
  { return -1; }
if (!mohq_lock_set (pmod_data->pcall_lock, 0, 200))
  {
  LM_ERR ("%sUnable to lock calls!\n", pfncname);
  return -1;
  }
call_lst *pcall = 0;
int ncall_idx;
time_t ntime = 0;
int nfound = -1;
int mohq_id = pmod_data->pmohq_lst [nq_idx].mohq_id;
for (ncall_idx = 0; ncall_idx < pmod_data->call_cnt; ncall_idx++)
  {
  /**********
  * o active call?
  * o matching queue?
  * o refer stuck?
  * o in queue?
  * o check age
  **********/

  pcall = &pmod_data->pcall_lst [ncall_idx];
  if (!pcall->call_state)
    { continue; }
  if (pcall->pmohq->mohq_id != mohq_id)
    { continue; }
  if ((pcall->call_state == CLSTA_REFER)
    || (pcall->call_state == CLSTA_RFRWAIT))
    {
    if ((pcall->refer_time + 32) < time (0))
      {
      LM_ERR
        ("%sDropping call because no response to REFER for call (%s)!\n",
        pfncname, pcall->call_from);
      close_call (FAKED_REPLY, pcall);
      }
    }
  if (pcall->call_state != CLSTA_INQUEUE)
    { continue; }
  if (!ntime)
    {
    nfound = ncall_idx;
    ntime = pcall->call_time;
    }
  else
    {
    if (pcall->call_time < ntime)
      {
      nfound = ncall_idx;
      ntime = pcall->call_time;
      }
    }
  }
if (nfound == -1)
  {
  LM_WARN ("%sNo calls in queue (%.*s)\n", pfncname, STR_FMT (pqname));
  mohq_lock_release (pmod_data->pcall_lock);
  return -1;
  }
pcall = &pmod_data->pcall_lst [nfound];

/**********
* o save refer-to URI
* o send refer
**********/

strncpy (pcall->call_referto, puri->s, puri->len);
pcall->call_referto [puri->len] = '\0';
if (refer_call (pcall, pmod_data->pcall_lock))
  { return 1; }
LM_ERR ("%sUnable to refer call (%s)!\n", pfncname, pcall->call_from);
return -1;
}

/**********
* Send Message to Queue
*
* INPUT:
*   Arg (1) = SIP message pointer
*   Arg (2) = queue name
* OUTPUT: -1 if no items in queue; 1 if successfull
**********/

int mohq_send (sip_msg_t *pmsg, char *pqueue)

{
/**********
* o first INVITE?
* o get queue name
**********/

char *pfncname = "mohq_send: ";
if (pmsg->REQ_METHOD != METHOD_INVITE)
  {
  LM_ERR ("%sNot an INVITE message!\n", pfncname);
  return -1;
  }
to_body_t *pto_body = get_to (pmsg);
if (pto_body->tag_value.len)
  {
  LM_ERR ("%sNot a first INVITE message!\n", pfncname);
  return -1;
  }
str pqname [1];
if (!pqueue)
  {
  LM_ERR ("%sParameters missing!\n", pfncname);
  return -1;
  }
if (fixup_get_svalue (pmsg, (gparam_p)pqueue, pqname))
  {
  LM_ERR ("%sInvalid queue name!\n", pfncname);
  return -1;
  }

/**********
* o find queue
* o change RURI
* o relay message
**********/

int nq_idx = find_qname (pqname);
if (nq_idx == -1)
  { return -1; }
str pruri [1] = {{0, strlen (pmod_data->pmohq_lst [nq_idx].mohq_uri)}};
pruri->s = pkg_malloc (pruri->len + 1);
if (!pruri->s)
  {
  LM_ERR ("%sNo more memory!\n", pfncname);
  return -1;
  }
strcpy (pruri->s, pmod_data->pmohq_lst [nq_idx].mohq_uri);
if (pmsg->new_uri.s)
  { pkg_free (pmsg->new_uri.s); }
pmsg->new_uri.s = pruri->s;
pmsg->new_uri.len = pruri->len;
pmsg->parsed_uri_ok = 0;
pmsg->parsed_orig_ruri_ok = 0;
if (pmod_data->ptm->t_relay (pmsg, 0, 0) < 0)
  {
  LM_ERR ("%sUnable to relay INVITE!\n", pfncname);
  return -1;
  }
return 1;
}