浏览代码

Merge branch 'tteras/mohqueue'

Timo Teräs 12 年之前
父节点
当前提交
ca060f98bb

+ 14 - 0
lib/srdb1/schema/kamailio-mohqueue.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE database PUBLIC "-//kamailio.org//DTD DBSchema V1.1//EN"
+  "http://kamailio.org/pub/kamailio/dbschema/dtd/1.1/dbschema.dtd" [
+
+<!ENTITY % entities SYSTEM "entities.xml">
+%entities;
+
+]>
+
+<database xmlns:xi="http://www.w3.org/2001/XInclude">
+    <name>mohqueue</name>
+    <xi:include href="mohqcalls.xml"/>
+    <xi:include href="mohqueues.xml"/>
+</database>

+ 76 - 0
lib/srdb1/schema/mohqcalls.xml

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE table PUBLIC "-//kamailio.org//DTD DBSchema V1.1//EN" 
+  "http://kamailio.org/pub/kamailio/dbschema/dtd/1.1/dbschema.dtd" [
+
+<!ENTITY % entities SYSTEM "entities.xml">
+%entities;
+
+]>
+
+<table id="mohqcalls" xmlns:db="http://docbook.org/ns/docbook">
+  <name>mohqcalls</name>
+  <version>1</version>
+  <type db="mysql">&MYSQL_TABLE_TYPE;</type>
+  <description>
+    <db:para>This table is used by the mohqueue module to store call information. This is a read-only table from the viewpoint of outside processes. More information about the mohqueue module can be found at: &KAMAILIO_MOD_DOC;mohqueue.html
+    </db:para>
+  </description>
+
+  <column id="id">
+    <name>id</name>
+    <type>unsigned int</type>
+    <size>&table_id_len;</size>
+    <autoincrement/>
+    <primary/>
+    <type db="dbtext">int,auto</type>
+    <description>Unique ID</description>
+  </column>
+
+  <column id="mohq_id">
+    <name>mohq_id</name>
+    <type>unsigned int</type>
+    <size>&table_id_len;</size>
+    <description>queue id</description>
+  </column>
+
+  <column id="call_id">
+    <name>call_id</name>
+    <type>string</type>
+    <size>100</size>
+    <description>Call-ID header</description>
+  </column>
+
+  <column id="call_status">
+    <name>call_status</name>
+    <type>unsigned int</type>
+    <description>status of call</description>
+  </column>
+
+  <column id="call_from">
+    <name>call_from</name>
+    <type>string</type>
+    <size>100</size>
+    <description>From header</description>
+  </column>
+
+  <column id="call_contact">
+    <name>call_contact</name>
+    <type>string</type>
+    <size>100</size>
+    <null/>
+    <description>Contact header</description>
+  </column>
+
+  <column id="call_time">
+    <name>call_time</name>
+    <type>datetime</type>
+    <default db="oracle">to_date('','yyyy-mm-dd hh24:mi:ss')</default>
+    <description>time when call first entered queue</description>
+  </column>
+
+  <index>
+    <name>mohqcalls_idx</name>
+    <colref linkend="call_id" />
+    <unique/>
+  </index>
+</table>

+ 75 - 0
lib/srdb1/schema/mohqueues.xml

@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE table PUBLIC "-//kamailio.org//DTD DBSchema V1.1//EN" 
+  "http://kamailio.org/pub/kamailio/dbschema/dtd/1.1/dbschema.dtd" [
+
+<!ENTITY % entities SYSTEM "entities.xml">
+%entities;
+
+]>
+
+<table id="mohqueues" xmlns:db="http://docbook.org/ns/docbook">
+  <name>mohqueues</name>
+  <version>1</version>
+  <type db="mysql">&MYSQL_TABLE_TYPE;</type>
+  <description>
+    <db:para>This table is used by the mohqueue module to store queue definitions. This is a read-only table from the viewpoint of the module. More information about the mohqueue module can be found at: &KAMAILIO_MOD_DOC;mohqueue.html
+    </db:para>
+  </description>
+
+  <column id="id">
+    <name>id</name>
+    <type>unsigned int</type>
+    <size>&table_id_len;</size>
+    <autoincrement/>
+    <primary/>
+    <type db="dbtext">int,auto</type>
+    <description>Unique ID</description>
+  </column>
+
+  <column id="name">
+    <name>name</name>
+    <type>string</type>
+    <size>25</size>
+    <description>queue name</description>
+  </column>
+
+  <column id="uri">
+    <name>uri</name>
+    <type>string</type>
+    <size>100</size>
+    <description>URI for the queue</description>
+  </column>
+
+  <column id="mohdir">
+    <name>mohdir</name>
+    <type>string</type>
+    <size>100</size>
+    <null/>
+    <description>directory for MOH files</description>
+  </column>
+
+  <column id="mohfile">
+    <name>mohfile</name>
+    <type>string</type>
+    <size>100</size>
+    <description>base name for the MOH file</description>
+  </column>
+
+  <column id="debug">
+    <name>debug</name>
+    <type>int</type>
+    <description>debug flag</description>
+  </column>
+
+  <index>
+    <name>mohqueue_uri_idx</name>
+    <colref linkend="uri" />
+    <unique/>
+  </index>
+
+  <index>
+    <name>mohqueue_name_idx</name>
+    <colref linkend="name" />
+    <unique/>
+  </index>
+</table>

+ 18 - 0
modules/mohqueue/Makefile

@@ -0,0 +1,18 @@
+# $Id$
+#
+# msgqueue module makefile
+#
+# 
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=mohqueue.so
+LIBS=
+
+DEFS+=-DKAMAILIO_MOD_INTERFACE
+
+SERLIBPATH=../../lib
+SER_LIBS+=$(SERLIBPATH)/kcore/kcore $(SERLIBPATH)/srdb1/srdb1
+SER_LIBS+=$(SERLIBPATH)/kmi/kmi
+include ../../Makefile.modules

+ 11 - 0
modules/mohqueue/NOTES

@@ -0,0 +1,11 @@
+Things to look into:
+
+* RFC3261, section 12.1.1 requires UAS to copy Record-Route
+
+* Check to see if any memory leaks.
+
+* Should probably respond when caller says BYE after queue says BYE.
+
+* check RAck number
+
+* check Require header for unsupported values

+ 4 - 0
modules/mohqueue/doc/Makefile

@@ -0,0 +1,4 @@
+docs = mohqueue.xml
+
+docbook_dir = ../../../docbook
+include $(docbook_dir)/Makefile.module

+ 31 - 0
modules/mohqueue/doc/mohqueue.xml

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
+%docentities;
+
+]>
+
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
+  <bookinfo>
+    <title>mohqueue Module</title>
+    <productname class="trade">&kamailioname;</productname>
+    <authorgroup>
+      <author>
+        <firstname>Robert</firstname>
+        <surname>Boisvert</surname>
+        <address>
+          <email>[email protected]</email>
+        </address>
+      </author>
+    </authorgroup>
+    <copyright>
+      <year>2013</year>
+      <holder>Robert Boisvert, [email protected]</holder>
+    </copyright>
+  </bookinfo>
+  <toc></toc>
+  <xi:include href="mohqueue_admin.xml" />
+</book>

+ 510 - 0
modules/mohqueue/doc/mohqueue_admin.xml

@@ -0,0 +1,510 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
+%docentities;
+
+]>
+<!-- Module Admin Guide -->
+
+<chapter xmlns:xi="http://www.w3.org/2001/XInclude">
+  <title>&adminguide;</title>
+
+  <section id="overview">
+    <title>Overview</title>
+    <para>
+The mohqueue module diverts INVITE requests into a
+<ulink url="http://en.wikipedia.org/wiki/Music_on_hold">Music On
+Hold (MOH)</ulink> queue where the caller can listen to recorded
+audio until an operator is available to take the call. When an
+operator is available, a function can be used to transfer the oldest
+call in a queue to an operator using an unattended transfer (REFER)
+to a specified URI. If successful, the call is removed from the queue.
+    </para>
+    <para>
+While in queue, recorded audio is streamed to the caller in an endless
+loop using the rtpproxy module and application. Each queue can be
+configured to use different audio files.
+    </para>
+    <para>
+The queues are defined in the database which allows for dynamic
+configuration of the queues. Each queue is assigned a specific
+URI to respond to and a location for the audio files.
+    </para>
+    <para>
+As each call arrives the database is updated to show the call
+status which allows outside processes to inspect the queue. It
+can also be inspected using a function to see how many calls are
+currently in queue.
+    </para>
+    <para>
+While in queue, all SIP messages for a call must pass through
+the mohqueue module so that it can accurately detect the call
+status.
+    </para>
+  </section>
+
+  <section id="dependencies">
+    <title>Dependencies</title>
+
+    <section id="mod.depends">
+      <title>Kamailio Modules</title>
+      <para>
+The following modules must be loaded before this module:
+        <itemizedlist>
+          <listitem><emphasis>a database module</emphasis></listitem>
+          <listitem><emphasis>sl module</emphasis></listitem>
+          <listitem><emphasis>tm module</emphasis></listitem>
+          <listitem><emphasis>rtpproxy module</emphasis></listitem>
+        </itemizedlist>
+      </para>
+    </section>
+
+    <section id="app.depends">
+      <title>External Libraries or Applications</title>
+      <para>
+The rtpproxy applications supported by the rtpproxy module (e.g.
+<ulink url="http://www.b2bua.org/wiki/RTPproxy">
+http://www.b2bua.org/wiki/RTPproxy</ulink>).
+      </para>
+    </section>
+
+  </section>
+
+  <section id="parameters">
+    <title>Parameters</title>
+
+    <section id="url.parms">
+      <title><varname>db_url</varname> (str)</title>
+      <para>
+The URL to connect to the database for the mohqueue tables.
+      </para>
+      <para>
+<emphasis>Default value for Kamailio.</emphasis>
+      </para>
+      <example>
+        <title>Set <varname>db_url</varname>:</title>
+        <programlisting format="linespecific">
+...
+modparam ("mohqueue", "db_url", "mysql://kamailio:kamailiorw@localhost/kamailio")
+...
+        </programlisting>
+      </example>
+    </section>
+
+    <section id="table.parms">
+      <title><varname>db_qtable</varname> and <varname>db_ctable</varname> (str)</title>
+      <para>
+<varname>db_qtable</varname> is the name of the table that defines
+the queues and <varname>db_ctable</varname> is the table that
+maintains the call status.
+      </para>
+      <para>
+<emphasis>"MOHQUEUES" for <varname>db_qtable</varname> and
+"MOHQCALLS" for <varname>db_ctable</varname>.</emphasis>
+      </para>
+      <example>
+        <title>Set table names:</title>
+        <programlisting format="linespecific">
+...
+modparam ("mohqueue", "db_qtable", "mqueues")
+modparam ("mohqueue", "db_ctable", "mcalls")
+...
+        </programlisting>
+      </example>
+    </section>
+
+    <section id="dir.parms">
+      <title><varname>mohdir</varname> (str)</title>
+      <para>
+Path to the directory where the audio files are stored. Audio files
+are usually relative to this directory although the value can be
+overridden by a directory specified in the queues table.
+      </para>
+      <para>
+<emphasis>None. If not set by the module it must be defined in the
+queues table.</emphasis>
+      </para>
+      <example>
+        <title>Set default directory for audio files:</title>
+        <programlisting format="linespecific">
+...
+modparam ("mohqueue", "mohdir", "/var/kamailio/MOH")
+...
+        </programlisting>
+      </example>
+    </section>
+
+    <section id="maxcalls.parms">
+      <title><varname>moh_maxcalls</varname> (integer)</title>
+      <para>
+Defines the maximum number of calls that can be placed in queue.
+It is the sum of all calls in all queues. It must be in the range
+of 1 to 5000. <emphasis>NOTE:</emphasis> it may be limited by the
+processing power of the server or the number of available rtpproxy
+ports.
+      </para>
+      <para>
+<emphasis>None. If not set by the module it must be defined in the
+queues table.</emphasis>
+      </para>
+      <example>
+        <title>Set default directory for audio files:</title>
+        <programlisting format="linespecific">
+...
+modparam ("mohqueue", "mohdir", "/var/kamailio/MOH")
+...
+        </programlisting>
+      </example>
+    </section>
+
+  </section>
+
+  <section id="functions">
+    <title>Functions</title>
+
+    <section id="proc.func">
+      <title>
+        <function moreinfo="none">mohq_process ()</function>
+      </title>
+      <para>
+Checks to see if the current SIP message involves a queue. If it
+does it will process the message and return a TRUE value.
+      </para>
+      <para>
+In order for mohqueue to detect changes in the call it is necessary
+that all messages involving the call be processed through this
+function. The easiest way is to accomplish this is to place it at
+the beginning of the main route of the script.
+      </para>
+      <para>
+mohqueue calls are identified by an RURI that matches a queue URI.
+Once a call is placed in queue it checks the <varname>To</varname>
+header field along with the RURI to find a match, except in the case
+of a CANCEL which matches only on the RURI.
+      </para>
+      <para>
+This function has no parameters and must be called from a request route.
+      </para>
+      <para>
+<emphasis>Return code:</emphasis>
+        <itemizedlist>
+          <listitem>
+            <emphasis>TRUE=successful and call in queue</emphasis>
+          </listitem>
+          <listitem>
+            <emphasis>FALSE=failed, unrecognized URI or unable to place in queue</emphasis>
+          </listitem>
+        </itemizedlist>
+      </para>
+      <example>
+        <title><function>mohq_process</function> usage:</title>
+        <programlisting format="linespecific">
+...
+request_route {
+  # main route with limited processing
+...
+  # MOH queue?
+  if (mohq_process ()) {
+    xlog ("L_DBG", "Handled by mohqueue");
+    exit;
+  }
+  # An error or not a MOH queue message; continue processing
+...
+}
+...
+        </programlisting>
+      </example>
+    </section>
+
+    <section id="send.func">
+      <title>
+        <function moreinfo="none">mohq_send (queue_name)</function>
+      </title>
+      <para>
+Normally calls enter the queue with an initial INVITE message that
+1) has a RURI that matches a queue URI and 2) is passed through
+<function>mohq_proc ()</function>, which is the preferred method.
+      </para>
+      <para>
+This function is used when you wish to send a call into a queue that
+does not match the queue URI.
+      </para>
+      <para>
+It has only one parameter, the name of the queue, and must be called
+from the request route with an initial INVITE message. The queue name
+can be passed as a literal or pseudo-variable.
+      </para>
+      <para>
+<emphasis>Return code:</emphasis>
+        <itemizedlist>
+          <listitem>
+            <emphasis>TRUE=successful and call in queue</emphasis>
+          </listitem>
+          <listitem>
+            <emphasis>FALSE=failed, unable to place in queue</emphasis>
+          </listitem>
+        </itemizedlist>
+      </para>
+      <example>
+        <title><function>mohq_send</function> usage:</title>
+        <programlisting format="linespecific">
+...
+  # call is initial INVITE and ready for queue?
+  if (some test) {
+    if (mohq_send ("main")) {
+      xlog ("L_DBG", "Sent call to main mohqueue");
+      exit;
+    }
+    # failed to enter queue!
+    ...
+  }
+...
+        </programlisting>
+      </example>
+    </section>
+
+    <section id="retrieve.func">
+      <title>
+        <function moreinfo="none">mohq_retrieve (queue_name, URI)</function>
+      </title>
+      <para>
+Retrieves the oldest call in a queue and redirects it to a URI.
+Although the function returns, the transfer of the call may not have
+completed since the new URI (operator) must answer the call.
+      </para>
+      <para>
+It has two parameters, the queue name and the URI to REFER the call
+to, both which can be passed as literals or pseudo-variables. It can
+be called from any route.
+      </para>
+      <para>
+<emphasis>Return code:</emphasis>
+        <itemizedlist>
+          <listitem>
+            <emphasis>TRUE=successful, transfer started</emphasis>
+          </listitem>
+          <listitem>
+            <emphasis>FALSE=failed, parameters are incorrect or there are no calls in queue</emphasis>
+          </listitem>
+        </itemizedlist>
+      </para>
+      <example>
+        <title><function>mohq_retrieve</function> usage:</title>
+        <programlisting format="linespecific">
+...
+#!define MOHQNAME "operators"
+#!define CGROUP   "sip:[email protected]"
+...
+  # redirect oldest call to operator call group
+  if (mohq_retrieve (MOHQNAME, CGROUP)) {
+      xlog ("L_DBG", "Retrieved call from mohqueue");
+      exit;
+    }
+  # queue is empty or something went wrong
+  }
+...
+        </programlisting>
+      </example>
+    </section>
+
+    <section id="count.func">
+      <title>
+        <function moreinfo="none">mohq_count (queue_name, pvar)</function>
+      </title>
+      <para>
+Finds the number of calls that are in a queue. It will not count
+calls that are in the process of entering or exiting the queue.
+      </para>
+      <para>
+The function has two parameters, the name of the queue and the
+pseudo-variable which receives the count. The queue name can be
+passed as a literal or a pseudo-variable. It can be called from
+any route.
+      </para>
+      <para>
+<emphasis>Return code:</emphasis>
+        <itemizedlist>
+          <listitem>
+            <emphasis>TRUE=successful, pseudo-variable contains count</emphasis>
+          </listitem>
+          <listitem>
+            <emphasis>FALSE=failed, parameters are incorrect</emphasis>
+          </listitem>
+        </itemizedlist>
+      </para>
+      <example>
+        <title><function>mohq_count</function> usage:</title>
+        <programlisting format="linespecific">
+...
+$var(mohq) = "operators";
+...
+  # more than 10 calls?
+  mohq_count ("$var(mohq)", "$var(mohqcnt)");
+  if ($var(mohqcnt) > 10) {
+    xlog ("L_WARN", "$var(mohq) queue has $var(mohqcnt) calls!");
+  }
+...
+        </programlisting>
+      </example>
+    </section>
+
+  </section>
+
+  <section id="database">
+    <title>Database Schema</title>
+      <para>
+mohqueue uses two external database tables to manage the queues and
+provide status information to outside processes. Internally, it keeps
+a volatile database in memory of call status. If the module is
+restarted it loses the internal database and clears the external
+one.
+      </para>
+      <para>
+On a reqular basis it checks the external table that defines the
+queues to see if the definition has changed. It makes this check
+under the following conditions: the queue has not been checked in the
+last 60 seconds <emphasis>AND</emphasis> no call is currently in
+queue or transitioning in or out. The last condition prevents
+existing calls from being adversely affected by queue redefinitions.
+      </para>
+
+    <section id="mohqueues.dbase">
+      <title>MOHQUEUES Table</title>
+      <para>
+This table controls the definition of the queue. The name is set by
+the <ulink url="#table.parms">db_qtable</ulink> parameter. There is
+no internal function to modify the table so it must be configured
+externally. It contains the following fields:
+        <itemizedlist>
+          <listitem>
+<emphasis>id</emphasis> (integer): unique identifier that is created
+automatically. <emphasis>Do not attempt to change this value.</emphasis>
+          </listitem>
+          <listitem>
+<emphasis>name</emphasis> (25-character string, required): the queue name.
+Duplicate names are not allowed.
+          </listitem>
+          <listitem>
+<emphasis>uri</emphasis> (100-character string, required): the URI of
+the queue. It should not include any parameters or headers (e.g.
+"sip:user@host;maddr=239.255.255.1" or "sip:user@host?subject=project")
+although it will match any RURI that contains this URI even if the
+RURI has parameters or headers. Duplicates are not allowed.
+          </listitem>
+          <listitem>
+<emphasis>mohdir</emphasis> (100-character string, optional): path to
+the directory where the audio files for the queue are stored. This path
+overrides the one provided by the <ulink url="#dir.parms">mohdir</ulink>
+parameter. If the directory is not accessible by the module the queue
+is not activated.
+          </listitem>
+          <listitem>
+<emphasis>mohfile</emphasis> (100-character string, required): the
+base name of the audio file. See the section about
+<ulink url="#audiofiles">audio files</ulink> for more information
+about file names. If no files matching this name are found in the
+directory the queue is not activated.
+          </listitem>
+          <listitem>
+<emphasis>debug</emphasis> (integer, required): enables debugging
+messages for the queue. If non-zero, it will send debugging messages
+to the log for conditions that involve the queue, whether or not
+Kamailio has logging enabled for debugging. If zero, it depends on
+Kamailio's log level.
+          </listitem>
+        </itemizedlist>
+      </para>
+    </section>
+
+    <section id="mohqcalls.dbase">
+      <title>MOHQCALLS Table</title>
+      <para>
+This table contains the status of calls that are in queue, or
+transitioning in or out of a queue. The name is set by the
+<ulink url="#table.parms">db_ctable</ulink> parameter. This table
+is read-only for external processes and its contents should
+<emphasis>not be modified</emphasis>. It contains the following
+fields:
+        <itemizedlist>
+          <listitem>
+<emphasis>id</emphasis> (integer): unique identifier that is created
+automatically.
+          </listitem>
+          <listitem>
+<emphasis>mohq_id</emphasis> (integer, required): the id value of the
+queue.
+          </listitem>
+          <listitem>
+<emphasis>call_status</emphasis> (integer, required): the status of
+the call. 1=entering; 2=in queue (listening to MOH); 3=leaving
+          </listitem>
+          <listitem>
+<emphasis>call_from</emphasis> (100-character string, required): the
+contents of the <varname>From</varname> header field.
+          </listitem>
+          <listitem>
+<emphasis>call_id</emphasis> (100-character string, required): the
+contents of the <varname>Call-ID</varname> header field.
+          </listitem>
+          <listitem>
+<emphasis>call_contact</emphasis> (100-character string, optional):
+the contents of the <varname>Contact</varname> header field, if it
+exists.
+          </listitem>
+          <listitem>
+<emphasis>call_time</emphasis> (datetime, required): time the call
+entered the queue. If a <ulink url="#retrieve.func">retrieve</ulink>
+fails this time is not changed.
+          </listitem>
+        </itemizedlist>
+      </para>
+    </section>
+
+  </section>
+  <section id="audiofiles">
+    <title>Audio Files</title>
+      <para>
+When rtpproxy negotiates to determine which media to use in the audio
+stream it uses the files in the MOH directory as defined by the
+<ulink url="#mohqueues.dbase">MOHQUEUES</ulink> table. The table
+defines the location of the files and the base name used to identify
+each. The actual stream type depends on the RTP payload number that
+is part of the name. The complete file name for each stream is
+composed of <varname>mohdir/mohfile.type</varname>. For example,
+<varname>/var/kamailio/MOH/HeWillCall.8</varname> would be the file
+for payload type 8 (PCMA/8000).
+      </para>
+      <para>
+The supported types and their order of preference are:
+        <itemizedlist>
+          <listitem><emphasis>9</emphasis>: G722/8000</listitem>
+          <listitem><emphasis>0</emphasis>: PCMU/8000</listitem>
+          <listitem><emphasis>8</emphasis>: PCMA/8000</listitem>
+          <listitem><emphasis>18</emphasis>: G729/8000</listitem>
+          <listitem><emphasis>3</emphasis>: GSM/8000</listitem>
+          <listitem><emphasis>4</emphasis>: G723/8000</listitem>
+          <listitem><emphasis>15</emphasis>: G728/8000</listitem>
+          <listitem><emphasis>5</emphasis>: DVI4/8000</listitem>
+          <listitem><emphasis>7</emphasis>: LPC/8000</listitem>
+          <listitem><emphasis>12</emphasis>: QCELP/8000</listitem>
+          <listitem><emphasis>13</emphasis>: CN/8000</listitem>
+          <listitem><emphasis>16</emphasis>: DVI4/11025</listitem>
+          <listitem><emphasis>6</emphasis>: DVI4/16000</listitem>
+          <listitem><emphasis>17</emphasis>: DVI4/22050</listitem>
+          <listitem><emphasis>10</emphasis>: L16/44100</listitem>
+          <listitem><emphasis>11</emphasis>: L16/44100</listitem>
+          <listitem><emphasis>14</emphasis>: MPA/90000</listitem>
+        </itemizedlist>
+      </para>
+      <para>
+See <ulink url="http://en.wikipedia.org/wiki/RTP_audio_video_profile">
+RTP Audio Video Profile</ulink> for more information about RTP
+payload types.
+      </para>
+
+  </section>
+
+</chapter>

+ 442 - 0
modules/mohqueue/mohq.c

@@ -0,0 +1,442 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Robert Boisvert
+ *
+ * This file is part of the mohqueue module for sip-router, 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include <sys/types.h>
+#include <stdlib.h>
+
+#include "mohq.h"
+#include "mohq_db.h"
+#include "mohq_funcs.h"
+
+MODULE_VERSION
+
+/**********
+* local function declarations
+**********/
+
+int fixup_count (void **, int);
+static int mod_child_init (int);
+static void mod_destroy (void);
+static int mod_init (void);
+
+/**********
+* global varbs
+**********/
+
+mod_data *pmod_data;
+
+/**********
+* module exports
+**********/
+
+/* COMMANDS */
+static cmd_export_t mod_cmds [] = {
+  { "mohq_count", (cmd_function) mohq_count, 2, fixup_count, 0,
+    REQUEST_ROUTE | FAILURE_ROUTE | ONREPLY_ROUTE },
+  { "mohq_process", (cmd_function) mohq_process, 0, NULL, 0, REQUEST_ROUTE },
+  { "mohq_retrieve", (cmd_function) mohq_retrieve, 2, fixup_spve_spve, 0,
+    REQUEST_ROUTE | FAILURE_ROUTE | ONREPLY_ROUTE },
+  { "mohq_send", (cmd_function) mohq_send, 1, fixup_spve_spve, 0, REQUEST_ROUTE },
+  { NULL, NULL, -1, 0, 0 },
+};
+
+/* PARAMETERS */
+char *db_url = DEFAULT_DB_URL;
+char *db_ctable = "mohqcalls";
+char *db_qtable = "mohqueues";
+char *mohdir = "";
+int moh_maxcalls = 50;
+
+static param_export_t mod_parms [] = {
+  { "db_url", STR_PARAM, &db_url },
+  { "db_ctable", STR_PARAM, &db_ctable },
+  { "db_ctable", STR_PARAM, &db_qtable },
+  { "mohdir", STR_PARAM, &mohdir },
+  { "moh_maxcalls", INT_PARAM, &moh_maxcalls },
+  { NULL, 0, NULL },
+};
+
+/* MI COMMANDS */
+static mi_export_t mi_cmds [] = {
+  { "debug", mi_debug, 0, 0, 0 },
+  { "drop_call", mi_drop_call, 0, 0, 0 },
+  { 0, 0, 0, 0, 0 }
+};
+
+/* MODULE EXPORTS */
+struct module_exports exports = {
+  "mohqueue",       /* module name */
+  DEFAULT_DLFLAGS,  /* dlopen flags */
+  mod_cmds,         /* exported functions */
+  mod_parms,        /* exported parameters */
+  0,                /* statistics */
+  mi_cmds,          /* MI functions */
+  0,                /* exported pseudo-variables */
+  0,                /* extra processes */
+  mod_init,         /* module initialization function */
+  0,                /* response handling function */
+  mod_destroy,      /* destructor function */
+  mod_child_init,   /* per-child initialization function */
+};
+
+/**********
+* local functions
+**********/
+
+/**********
+* Fixup Count
+*
+* INPUT:
+*   Arg (1) = parameter array pointer
+*   Arg (1) = parameter number
+* OUTPUT: -1 if failed; 0 if saved as pv_elem_t
+**********/
+
+int fixup_count (void **param, int param_no)
+
+{
+if (param_no == 1)
+  { return fixup_spve_spve (param, 1); }
+if (param_no == 2)
+  { return fixup_pvar_null (param, 1); }
+return 0;
+}
+
+/**********
+* Configuration Initialization
+*
+* INPUT:
+*   pmod_data memory allocated
+*   configuration values set
+* OUTPUT: 0 if failed; else pmod_data has config values
+**********/
+
+static int init_cfg (void)
+
+{
+/**********
+* db_url, db_ctable, db_qtable exist?
+**********/
+
+if (!*db_url)
+  {
+  LM_ERR ("db_url parameter not set!");
+  return 0;
+  }
+pmod_data->pcfg->db_url.s = db_url;
+pmod_data->pcfg->db_url.len = strlen (db_url);
+if (!*db_ctable)
+  {
+  LM_ERR ("db_ctable parameter not set!");
+  return 0;
+  }
+pmod_data->pcfg->db_ctable.s = db_ctable;
+pmod_data->pcfg->db_ctable.len = strlen (db_ctable);
+if (!*db_qtable)
+  {
+  LM_ERR ("db_qtable parameter not set!");
+  return 0;
+  }
+pmod_data->pcfg->db_qtable.s = db_qtable;
+pmod_data->pcfg->db_qtable.len = strlen (db_qtable);
+
+/**********
+* mohdir
+* o exists?
+* o directory?
+**********/
+
+if (!*mohdir)
+  {
+  LM_ERR ("mohdir parameter not set!");
+  return 0;
+  }
+if (strlen (mohdir) > MOHDIRLEN)
+  {
+  LM_ERR ("mohdir too long!");
+  return 0;
+  }
+pmod_data->pcfg->mohdir = mohdir;
+int bfnd = 0;
+struct stat psb [1];
+if (!lstat (mohdir, psb))
+  {
+  if ((psb->st_mode & S_IFMT) == S_IFDIR)
+    { bfnd = 1; }
+  }
+if (!bfnd)
+  {
+  LM_ERR ("mohdir is not a directory!");
+  return 0;
+  }
+
+/**********
+* max calls
+* o valid count?
+* o alloc memory
+**********/
+
+if (moh_maxcalls < 1 || moh_maxcalls > 5000)
+  {
+  LM_ERR ("moh_maxcalls not in range of 1-5000!");
+  return 0;
+  }
+pmod_data->pcall_lst =
+  (call_lst *) shm_malloc (sizeof (call_lst) * moh_maxcalls);
+if (!pmod_data->pcall_lst)
+  {
+  LM_ERR ("Unable to allocate shared memory");
+  return -1;
+  }
+memset (pmod_data->pcall_lst, 0, sizeof (call_lst) * moh_maxcalls);
+pmod_data->call_cnt = moh_maxcalls;
+return -1;
+}
+
+/**********
+* DB Initialization
+*
+* INPUT:
+*   pmod_data memory allocated and cfg values set
+* OUTPUT: 0 if failed; else pmod_data has db_api
+**********/
+
+static int init_db (void)
+
+{
+/**********
+* o bind to DB
+* o check capabilities
+* o init DB
+**********/
+
+str *pdb_url = &pmod_data->pcfg->db_url;
+if (db_bind_mod (pdb_url, pmod_data->pdb))
+  {
+  LM_ERR ("Unable to bind DB API using %s", pdb_url->s);
+  return 0;
+  }
+db_func_t *pdb = pmod_data->pdb;
+if (!DB_CAPABILITY ((*pdb), DB_CAP_ALL))
+  {
+  LM_ERR ("Selected database %s lacks required capabilities", pdb_url->s);
+  return 0;
+  }
+db1_con_t *pconn = mohq_dbconnect ();
+if (!pconn)
+  { return 0; }
+
+/**********
+* o check schema
+* o remove all call recs
+* o load queue list
+**********/
+
+if (db_check_table_version (pdb, pconn,
+  &pmod_data->pcfg->db_ctable, MOHQ_CTABLE_VERSION) < 0)
+  {
+  LM_ERR ("%s table in DB %s not at version %d",
+    pmod_data->pcfg->db_ctable.s, pdb_url->s, MOHQ_CTABLE_VERSION);
+  goto dberr;
+  }
+if (db_check_table_version (pdb, pconn,
+  &pmod_data->pcfg->db_qtable, MOHQ_QTABLE_VERSION) < 0)
+  {
+  LM_ERR ("%s table in DB %s not at version %d",
+    pmod_data->pcfg->db_qtable.s, pdb_url->s, MOHQ_QTABLE_VERSION);
+  goto dberr;
+  }
+clear_calls (pconn);
+update_mohq_lst (pconn);
+pmod_data->mohq_update = time (0);
+mohq_dbdisconnect (pconn);
+return -1;
+
+/**********
+* close DB
+**********/
+
+dberr:
+pdb->close (pconn);
+return 0;
+}
+
+/**********
+* Child Module Initialization
+*
+* INPUT:
+*   Arg (1) = child type
+* OUTPUT: -1 if db_api not ready; else 0
+**********/
+
+int mod_child_init (int rank)
+
+{
+/**********
+* o seed random number generator
+* o make sure DB initialized
+**********/
+
+srand (getpid () + time (0));
+if (rank == PROC_INIT || rank == PROC_TCP_MAIN || rank == PROC_MAIN)
+  { return 0; }
+if (!pmod_data->pdb->init)
+  {
+  LM_CRIT ("DB API not loaded!");
+  return -1;
+  }
+return 0;
+}
+
+/**********
+* Module Teardown
+*
+* INPUT: none
+* OUTPUT: none
+**********/
+
+void mod_destroy (void)
+
+{
+/**********
+* o destroy MOH can call queue locks
+* o deallocate shared mem
+**********/
+
+if (!pmod_data)
+  { return; }
+if (pmod_data->pmohq_lock->plock)
+  { mohq_lock_destroy (pmod_data->pmohq_lock); }
+if (pmod_data->pcall_lock->plock)
+  { mohq_lock_destroy (pmod_data->pcall_lock); }
+if (pmod_data->pmohq_lst)
+  { shm_free (pmod_data->pmohq_lst); }
+if (pmod_data->pcall_lst)
+  { shm_free (pmod_data->pcall_lst); }
+shm_free (pmod_data);
+return;
+}
+
+/**********
+* Module Initialization
+*
+* INPUT: none
+* OUTPUT: -1 if failed; 0 if success
+**********/
+
+int mod_init (void)
+
+{
+/**********
+* o allocate shared mem and init
+* o init configuration data
+* o init DB
+**********/
+
+pmod_data = (mod_data *) shm_malloc (sizeof (mod_data));
+if (!pmod_data)
+  {
+  LM_ERR ("Unable to allocate shared memory");
+  return -1;
+  }
+memset (pmod_data, 0, sizeof (mod_data));
+if (!init_cfg ())
+  { goto initerr; }
+if (!init_db ())
+  { goto initerr; }
+
+/**********
+* o bind to SL/TM/RR modules
+* o bind to RTPPROXY functions
+**********/
+
+if (sl_load_api (pmod_data->psl))
+  {
+  LM_ERR ("Unable to load SL module");
+  goto initerr;
+  }
+if (load_tm_api (pmod_data->ptm))
+  {
+  LM_ERR ("Unable to load TM module");
+  goto initerr;
+  }
+if (load_rr_api (pmod_data->prr))
+  {
+  LM_ERR ("Unable to load RR module");
+  goto initerr;
+  }
+pmod_data->fn_rtp_answer = find_export ("rtpproxy_answer", 0, 0);
+if (!pmod_data->fn_rtp_answer)
+  {
+  LM_ERR ("Unable to load rtpproxy_answer");
+  goto initerr;
+  }
+pmod_data->fn_rtp_offer = find_export ("rtpproxy_offer", 0, 0);
+if (!pmod_data->fn_rtp_offer)
+  {
+  LM_ERR ("Unable to load rtpproxy_offer");
+  goto initerr;
+  }
+pmod_data->fn_rtp_stream_c = find_export ("rtpproxy_stream2uac", 2, 0);
+if (!pmod_data->fn_rtp_stream_c)
+  {
+  LM_ERR ("Unable to load rtpproxy_stream2uac");
+  goto initerr;
+  }
+pmod_data->fn_rtp_stream_s = find_export ("rtpproxy_stream2uas", 2, 0);
+if (!pmod_data->fn_rtp_stream_s)
+  {
+  LM_ERR ("Unable to load rtpproxy_stream2uas");
+  goto initerr;
+  }
+pmod_data->fn_rtp_destroy = find_export ("rtpproxy_destroy", 0, 0);
+if (!pmod_data->fn_rtp_destroy)
+  {
+  LM_ERR ("Unable to load rtpproxy_destroy");
+  goto initerr;
+  }
+
+/**********
+* init MOH and call queue locks
+**********/
+
+if (!mohq_lock_init (pmod_data->pmohq_lock))
+  { goto initerr; }
+if (!mohq_lock_init (pmod_data->pcall_lock))
+  { goto initerr; }
+return 0;
+
+/**********
+* o release shared mem
+* o exit with error
+**********/
+
+initerr:
+if (pmod_data->mohq_cnt)
+  { shm_free (pmod_data->pmohq_lst); }
+if (pmod_data->pcall_lock->plock)
+  { mohq_lock_destroy (pmod_data->pcall_lock); }
+shm_free (pmod_data);
+pmod_data = NULL;
+return -1;
+}

+ 132 - 0
modules/mohqueue/mohq.h

@@ -0,0 +1,132 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Robert Boisvert
+ *
+ * This file is part of the mohqueue module for sip-router, 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#ifndef MOHQ_H
+#define MOHQ_H
+
+#include "mohq_common.h"
+#include "mohq_locks.h"
+
+/**********
+* definitions
+**********/
+
+#define URI_LEN     100
+#define USLEEP_LEN  10
+#define MOHDIRLEN   100
+#define MOHFILELEN  100
+
+/**********
+* structures
+**********/
+
+typedef struct
+  {
+  int ntype;
+  char *pencode;
+  } rtpmap;
+
+/* mohq_flags values */
+#define MOHQF_ACT 0x01
+#define MOHQF_CHK 0x02
+#define MOHQF_DBG 0x04
+
+typedef struct
+  {
+  char mohq_name [26];
+  char mohq_uri [URI_LEN + 1];
+  char mohq_mohdir [MOHDIRLEN + 1];
+  char mohq_mohfile [MOHFILELEN + 1];
+  int mohq_flags;
+  int mohq_id;
+  } mohq_lst;
+
+/* call_state values */
+#define CLSTA_ENTER     100
+#define CLSTA_PRACKSTRT 101
+#define CLSTA_PRACKRPLY 102
+#define CLSTA_RINGING   103
+#define CLSTA_INVITED   104
+#define CLSTA_CANCEL    105
+#define CLSTA_INQUEUE   200
+#define CLSTA_REFER     301
+#define CLSTA_RFRWAIT   302
+#define CLSTA_BYE       305
+
+typedef struct
+  {
+  int call_active;
+  char call_id [101];
+  char call_from [URI_LEN + 1];
+  char call_referto [URI_LEN + 1];
+  char call_contact [URI_LEN + 1];
+  char call_tag [101];
+  char call_via [1024];
+  char call_addr [IP_ADDR_MAX_STR_SIZE + 4];
+  int call_state;
+  int call_cseq;
+  int call_aport;
+  mohq_lst *pmohq;
+  time_t call_time;
+  unsigned int call_hash;
+  unsigned int call_label;
+  sip_msg_t *call_pmsg;
+  } call_lst;
+
+typedef struct
+  {
+  char *mohdir;
+  str db_url;
+  str db_ctable;
+  str db_qtable;
+  } mod_cfg;
+
+typedef struct
+  {
+  mod_cfg pcfg [1];
+  time_t mohq_update;
+  int mohq_cnt;
+  mohq_lst *pmohq_lst;
+  mohq_lock pmohq_lock [1];
+  int call_cnt;
+  call_lst *pcall_lst;
+  mohq_lock pcall_lock [1];
+  db_func_t pdb [1];
+  tm_api_t ptm [1];
+  sl_api_t psl [1];
+  rr_api_t prr [1];
+  cmd_function fn_rtp_answer;
+  cmd_function fn_rtp_destroy;
+  cmd_function fn_rtp_offer;
+  cmd_function fn_rtp_stream_c;
+  cmd_function fn_rtp_stream_s;
+  } mod_data;
+
+/**********
+* global varb declarations
+**********/
+
+extern mod_data *pmod_data;
+extern rtpmap prtpmap [];
+
+#endif /* MOHQ_H */

+ 104 - 0
modules/mohqueue/mohq_common.h

@@ -0,0 +1,104 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Robert Boisvert
+ *
+ * This file is part of the mohqueue module for sip-router, 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#ifndef MOHQ_COMMON_H
+#define MOHQ_COMMON_H
+
+#include <assert.h>
+#include <sys/stat.h>
+
+#include "../rr/api.h"
+
+#include "../../data_lump.h"
+#include "../../data_lump_rpl.h"
+#include "../../dprint.h"
+#include "../../dset.h"
+#include "../../flags.h"
+#include "../../hashes.h"
+#include "../../locking.h"
+#include "../../lvalue.h"
+#include "../../mod_fix.h"
+#include "../../sr_module.h"
+#include "../../str.h"
+
+#include "../../lib/kcore/cmpapi.h"
+#include "../../lib/kmi/mi.h"
+#include "../../lib/srdb1/db.h"
+#include "../../mem/mem.h"
+#include "../../mem/shm_mem.h"
+#include "../../modules/sl/sl.h"
+#include "../../modules/tm/tm_load.h"
+#include "../../parser/hf.h"
+#include "../../parser/msg_parser.h"
+#include "../../parser/contact/parse_contact.h"
+#include "../../parser/parse_expires.h"
+#include "../../parser/parse_from.h"
+#include "../../parser/sdp/sdp.h"
+
+/* convenience macros */
+#define MOHQ_STRUCT_PTR_OFFSET( struct1, cast1, offset1 ) \
+	(cast1)(struct1) + (offset1)
+	
+#define MOHQ_STR_COPY( str1, str2 ) \
+	memcpy((str1)->s, (str2)->s, (str2)->len ); \
+	(str1)->len = (str2)->len;
+
+#define MOHQ_STR_APPEND( str1, str2 ) \
+	memcpy((str1)->s + (str1)->len, (str2)->s, (str2)->len); \
+	(str1)->len += (str2)->len;
+
+#define MOHQ_STR_APPEND_L( str1, str1_lim, s2, s2_len ) \
+	if ((str1)->len + (s2_len) >= (str1_lim)) { \
+	    LM_ERR( "Failed to append to str: too long" ); \
+	} else { \
+	    MOHQ_STR_APPEND((str1), (s2), (s2_len)); \
+	    (str1_lim) -= (s2_len); \
+	}
+
+#define MOHQ_STR_COPY_CSTR( str1, cstr1 ) \
+	memcpy((str1)->s + (str1)->len, (cstr1), strlen((cstr1))); \
+	(str1)->len += strlen((cstr1));
+
+#define MOHQ_STR_APPEND_CSTR( str1, cstr1 ) \
+	MOHQ_STR_COPY_CSTR((str1), (cstr1))
+
+#define MOHQ_STR_APPEND_CSTR_L( str1, str1_lim, cstr1 ) \
+	if ((str1)->len + strlen(cstr1) >= (str1_lim)) { \
+	    LM_ERR( "Failed to append to str: too long" ); \
+	} else { \
+	    MOHQ_STR_APPEND_CSTR((str1), (cstr1)); \
+	}
+
+/* STR_EQ assumes we're not using str pointers, which is obnoxious */
+#define MOHQ_STR_EQ( str1, str2 ) \
+	(((str1)->len == (str2)->len) && \
+		memcmp((str1)->s, (str2)->s, (str1)->len) == 0)
+
+#define MOHQ_STR_EMPTY( str1 ) \
+	(((str1) != NULL && ((str1)->s == NULL || (str1)->len <= 0 )) \
+		|| (str1) == NULL )
+
+#define MOHQ_HEADER_EMPTY( hdr1 ) \
+	((hdr1) == NULL || MOHQ_STR_EMPTY( &(hdr1)->body ))
+
+#endif /* MOHQ_COMMON_H */

+ 626 - 0
modules/mohqueue/mohq_db.c

@@ -0,0 +1,626 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Robert Boisvert
+ *
+ * This file is part of the mohqueue module for sip-router, 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include "mohq.h"
+#include "mohq_db.h"
+#include "mohq_funcs.h"
+
+/**********
+* mohqueue definitions
+**********/
+
+str MOHQCSTR_ID = STR_STATIC_INIT ("id");
+str MOHQCSTR_URI = STR_STATIC_INIT ("uri");
+str MOHQCSTR_MDIR = STR_STATIC_INIT ("mohdir");
+str MOHQCSTR_MFILE = STR_STATIC_INIT ("mohfile");
+str MOHQCSTR_NAME = STR_STATIC_INIT ("name");
+str MOHQCSTR_DEBUG = STR_STATIC_INIT ("debug");
+
+static str *mohq_columns [] =
+  {
+  &MOHQCSTR_ID,
+  &MOHQCSTR_URI,
+  &MOHQCSTR_MDIR,
+  &MOHQCSTR_MFILE,
+  &MOHQCSTR_NAME,
+  &MOHQCSTR_DEBUG,
+  NULL
+  };
+
+/**********
+* mohqcalls definitions
+**********/
+
+str CALLCSTR_CALL = STR_STATIC_INIT ("call_id");
+str CALLCSTR_CNTCT = STR_STATIC_INIT ("call_contact");
+str CALLCSTR_FROM = STR_STATIC_INIT ("call_from");
+str CALLCSTR_MOHQ = STR_STATIC_INIT ("mohq_id");
+str CALLCSTR_STATE = STR_STATIC_INIT ("call_status");
+str CALLCSTR_TIME = STR_STATIC_INIT ("call_time");
+
+static str *call_columns [] =
+  {
+  &CALLCSTR_STATE,
+  &CALLCSTR_CALL,
+  &CALLCSTR_MOHQ,
+  &CALLCSTR_FROM,
+  &CALLCSTR_CNTCT,
+  &CALLCSTR_TIME,
+  NULL
+  };
+
+/**********
+* local function declarations
+**********/
+
+void set_call_key (db_key_t *, int, int);
+void set_call_val (db_val_t *, int, int, void *);
+
+/**********
+* local functions
+**********/
+
+/**********
+* Fill Call Keys
+*
+* INPUT:
+*   Arg (1) = row pointer
+*   Arg (2) = column count
+* OUTPUT: none
+**********/
+
+void fill_call_keys (db_key_t *prkeys, int ncnt)
+
+{
+int nidx;
+for (nidx = 0; nidx < ncnt; nidx++)
+  { set_call_key (prkeys, nidx, nidx); }
+return;
+}
+
+/**********
+* Fill Call Values
+*
+* INPUT:
+*   Arg (1) = row pointer
+*   Arg (2) = call struct pointer
+*   Arg (3) = column count
+* OUTPUT: none
+**********/
+
+void fill_call_vals (db_val_t *prvals, call_lst *pcall, int ncnt)
+
+{
+int nstate = pcall->call_state / 100;
+set_call_val (prvals, CALLCOL_STATE, CALLCOL_STATE, &nstate);
+if (!ncnt)
+  { return; }
+set_call_val (prvals, CALLCOL_MOHQ, CALLCOL_MOHQ, &pcall->pmohq->mohq_id);
+set_call_val (prvals, CALLCOL_CALL, CALLCOL_CALL, pcall->call_id);
+set_call_val (prvals, CALLCOL_FROM, CALLCOL_FROM, pcall->call_from);
+set_call_val (prvals, CALLCOL_CNTCT, CALLCOL_CNTCT, pcall->call_contact);
+set_call_val (prvals, CALLCOL_TIME, CALLCOL_TIME, &pcall->call_time);
+return;
+}
+
+/**********
+* Set Call Column Key
+*
+* INPUT:
+*   Arg (1) = row pointer
+*   Arg (2) = column number
+*   Arg (3) = column id
+* OUTPUT: none
+**********/
+
+void set_call_key (db_key_t *prkeys, int ncol, int ncolid)
+
+{
+prkeys [ncol] = call_columns [ncolid];
+return;
+}
+
+/**********
+* Set Call Column Value
+*
+* INPUT:
+*   Arg (1) = row pointer
+*   Arg (2) = column number
+*   Arg (3) = column id
+*   Arg (4) = value pointer
+* OUTPUT: none
+**********/
+
+void set_call_val (db_val_t *prvals, int ncol, int ncolid, void *pdata)
+
+{
+/**********
+* fill based on column
+**********/
+
+switch (ncolid)
+  {
+  case CALLCOL_MOHQ:
+  case CALLCOL_STATE:
+    prvals [ncol].val.int_val = *((int *)pdata);
+    prvals [ncol].type = DB1_INT;
+    prvals [ncol].nul = 0;
+    break;
+  case CALLCOL_CALL:
+  case CALLCOL_CNTCT:
+  case CALLCOL_FROM:
+    prvals [ncol].val.string_val = (char *)pdata;
+    prvals [ncol].type = DB1_STRING;
+    prvals [ncol].nul = 0;
+    break;
+  case CALLCOL_TIME:
+    prvals [ncol].val.time_val = *((time_t *)pdata);
+    prvals [ncol].type = DB1_DATETIME;
+    prvals [ncol].nul = 0;
+    break;
+  }
+return;
+}
+
+/**********
+* external functions
+**********/
+
+/**********
+* Add Call Record
+*
+* INPUT:
+*   Arg (1) = call index
+* OUTPUT: none
+**********/
+
+void add_call_rec (int ncall_idx)
+
+{
+/**********
+* o fill column names and values
+* o insert new record
+**********/
+
+char *pfncname = "add_call_rec: ";
+db1_con_t *pconn = mohq_dbconnect ();
+if (!pconn)
+  { return; }
+db_func_t *pdb = pmod_data->pdb;
+pdb->use_table (pconn, &pmod_data->pcfg->db_ctable);
+db_key_t prkeys [CALL_COLCNT];
+fill_call_keys (prkeys, CALL_COLCNT);
+db_val_t prvals [CALL_COLCNT];
+call_lst *pcall = &pmod_data->pcall_lst [ncall_idx];
+pcall->call_time = time (0);
+fill_call_vals (prvals, pcall, CALL_COLCNT);
+if (pdb->insert (pconn, prkeys, prvals, CALL_COLCNT) < 0)
+  {
+  LM_WARN ("%sUnable to add new row to %s", pfncname,
+    pmod_data->pcfg->db_ctable.s);
+  }
+mohq_dbdisconnect (pconn);
+return;
+}
+
+/**********
+* Clear Call Records
+*
+* INPUT:
+*   Arg (1) = connection pointer
+* OUTPUT: none
+**********/
+
+void clear_calls (db1_con_t *pconn)
+
+{
+/**********
+* delete all records
+**********/
+
+char *pfncname = "clear_calls: ";
+db_func_t *pdb = pmod_data->pdb;
+pdb->use_table (pconn, &pmod_data->pcfg->db_ctable);
+if (pdb->delete (pconn, 0, 0, 0, 0) < 0)
+  {
+  LM_WARN ("%sUnable to delete all rows from %s", pfncname,
+    pmod_data->pcfg->db_ctable.s);
+  }
+return;
+}
+
+/**********
+* Delete Call Record
+*
+* INPUT:
+*   Arg (1) = call pointer
+* OUTPUT: none
+**********/
+
+void delete_call_rec (call_lst *pcall)
+
+{
+/**********
+* o setup to delete based on call ID
+* o delete record
+**********/
+
+char *pfncname = "delete_call_rec: ";
+db1_con_t *pconn = mohq_dbconnect ();
+if (!pconn)
+  { return; }
+db_func_t *pdb = pmod_data->pdb;
+pdb->use_table (pconn, &pmod_data->pcfg->db_ctable);
+db_key_t prkeys [1];
+set_call_key (prkeys, 0, CALLCOL_CALL);
+db_val_t prvals [1];
+set_call_val (prvals, 0, CALLCOL_CALL, pcall->call_id);
+if (pdb->delete (pconn, prkeys, 0, prvals, 1) < 0)
+  {
+  LM_WARN ("%sUnable to delete row from %s", pfncname,
+    pmod_data->pcfg->db_ctable.s);
+  }
+mohq_dbdisconnect (pconn);
+return;
+}
+
+/**********
+* Connect to DB
+*
+* INPUT: none
+* OUTPUT: DB connection pointer; NULL=failed
+**********/
+
+db1_con_t *mohq_dbconnect (void)
+
+{
+str *pdb_url = &pmod_data->pcfg->db_url;
+db1_con_t *pconn = pmod_data->pdb->init (pdb_url);
+if (!pconn)
+  { LM_ERR ("Unable to connect to DB %s", pdb_url->s); }
+return pconn;
+}
+
+/**********
+* Disconnect from DB
+*
+* INPUT:
+*   Arg (1) = connection pointer
+* OUTPUT: none
+**********/
+
+void mohq_dbdisconnect (db1_con_t *pconn)
+
+{
+pmod_data->pdb->close (pconn);
+return;
+}
+
+/**********
+* Update Call Record
+*
+* INPUT:
+*   Arg (1) = call pointer
+* OUTPUT: none
+**********/
+
+void update_call_rec (call_lst *pcall)
+
+{
+/**********
+* o setup to update based on call ID
+* o update record
+**********/
+
+char *pfncname = "update_call_rec: ";
+db1_con_t *pconn = mohq_dbconnect ();
+if (!pconn)
+  { return; }
+db_func_t *pdb = pmod_data->pdb;
+pdb->use_table (pconn, &pmod_data->pcfg->db_ctable);
+db_key_t pqkeys [1];
+set_call_key (pqkeys, 0, CALLCOL_CALL);
+db_val_t pqvals [1];
+set_call_val (pqvals, 0, CALLCOL_CALL, pcall->call_id);
+db_key_t pukeys [1];
+set_call_key (pukeys, 0, CALLCOL_STATE);
+db_val_t puvals [1];
+fill_call_vals (puvals, pcall, CALLCOL_STATE);
+if (pdb->update (pconn, pqkeys, 0, pqvals, pukeys, puvals, 1, 1) < 0)
+  {
+  LM_WARN ("%sUnable to update row in %s", pfncname,
+    pmod_data->pcfg->db_ctable.s);
+  }
+mohq_dbdisconnect (pconn);
+return;
+}
+
+/**********
+* Update Debug Record
+*
+* INPUT:
+*   Arg (1) = MOH queue pointer
+*   Arg (2) = debug flag
+* OUTPUT: none
+**********/
+
+void update_debug (mohq_lst *pqueue, int bdebug)
+
+{
+/**********
+* o setup to update based on queue name
+* o update record
+**********/
+
+char *pfncname = "update_debug: ";
+db1_con_t *pconn = mohq_dbconnect ();
+if (!pconn)
+  { return; }
+db_func_t *pdb = pmod_data->pdb;
+pdb->use_table (pconn, &pmod_data->pcfg->db_qtable);
+db_key_t pqkeys [1] = { mohq_columns [MOHQCOL_NAME] };
+db_val_t pqvals [1];
+pqvals->val.string_val = pqueue->mohq_name;
+pqvals->type = DB1_STRING;
+pqvals->nul = 0;
+db_key_t pukeys [1] = { mohq_columns [MOHQCOL_DEBUG] };
+db_val_t puvals [1];
+puvals->val.int_val = bdebug;
+puvals->type = DB1_INT;
+puvals->nul = 0;
+if (pdb->update (pconn, pqkeys, 0, pqvals, pukeys, puvals, 1, 1) < 0)
+  {
+  LM_WARN ("%sUnable to update row in %s", pfncname,
+    pmod_data->pcfg->db_qtable.s);
+  }
+mohq_dbdisconnect (pconn);
+return;
+}
+
+/**********
+* Update Message Queue List
+*
+* INPUT:
+*   Arg (1) = connection pointer
+* OUTPUT: none
+**********/
+
+void update_mohq_lst (db1_con_t *pconn)
+
+{
+/**********
+* o reset checked flag on all queues
+* o read queues from table
+**********/
+
+char *pfncname = "update_mohq_lst: ";
+if (!pconn)
+  { return; }
+db_func_t *pdb = pmod_data->pdb;
+mohq_lst *pqlst = pmod_data->pmohq_lst;
+int nidx;
+for (nidx = 0; nidx < pmod_data->mohq_cnt; nidx++)
+  { pqlst [nidx].mohq_flags &= ~MOHQF_CHK; }
+pdb->use_table (pconn, &pmod_data->pcfg->db_qtable);
+db_key_t prkeys [MOHQ_COLCNT];
+for (nidx = 0; nidx < MOHQ_COLCNT; nidx++)
+  { prkeys [nidx] = mohq_columns [nidx]; }
+db1_res_t *presult = NULL;
+if (pdb->query (pconn, 0, 0, 0, prkeys, 0, MOHQ_COLCNT, 0, &presult))
+  {
+  LM_ERR ("%stable query (%s) failed!", pfncname,
+    pmod_data->pcfg->db_qtable.s);
+  return;
+  }
+db_row_t *prows = RES_ROWS (presult);
+int nrows = RES_ROW_N (presult);
+db_val_t *prowvals = NULL;
+char *ptext, *puri;
+mohq_lst *pnewlst;
+for (nidx = 0; nidx < nrows; nidx++)
+  {
+  /**********
+  * check URI
+  **********/
+
+  prowvals = ROW_VALUES (prows + nidx);
+  char *pqname = (char *)VAL_STRING (prowvals + MOHQCOL_NAME);
+  puri = (char *)VAL_STRING (prowvals + MOHQCOL_URI);
+  struct sip_uri puri_parsed [1];
+  if (parse_uri (puri, strlen (puri), puri_parsed))
+    {
+    LM_ERR ("Queue,Field (%s,%.*s): %s is not a valid URI!", pqname,
+      STR_FMT (&MOHQCSTR_URI), puri);
+    continue;
+    }
+
+  /**********
+  * check MOHDIR
+  **********/
+
+  char *pmohdir;
+  if (VAL_NULL (prowvals + MOHQCOL_MDIR))
+    { pmohdir = pmod_data->pcfg->mohdir; }
+  else
+    {
+    pmohdir = (char *)VAL_STRING (prowvals + MOHQCOL_MDIR);
+    if (!*pmohdir)
+      { pmohdir = pmod_data->pcfg->mohdir; }
+    else
+      {
+      /**********
+      * mohdir
+      * o exists?
+      * o directory?
+      **********/
+
+      struct stat psb [1];
+      if (lstat (pmohdir, psb))
+        {
+        LM_ERR ("Queue,Field (%s,%.*s): Unable to find %s!", pqname,
+          STR_FMT (&MOHQCSTR_MDIR), pmohdir);
+        continue;
+        }
+      else
+        {
+        if ((psb->st_mode & S_IFMT) != S_IFDIR)
+          {
+          LM_ERR ("Queue,Field (%s,%.*s): %s is not a directory!", pqname,
+            STR_FMT (&MOHQCSTR_MDIR), pmohdir);
+          continue;
+          }
+        }
+      }
+    }
+
+  /**********
+  * check for MOH files
+  **********/
+
+  rtpmap **pmohfiles = find_MOH (pmohdir,
+    (char *)VAL_STRING (prowvals + MOHQCOL_MFILE));
+  if (!pmohfiles [0])
+    {
+    LM_ERR ("Queue,Field (%s,%.*s): Unable to find MOH files (%s/%s.*)!",
+      pqname, STR_FMT (&MOHQCSTR_MDIR), pmohdir,
+      (char *)VAL_STRING (prowvals + MOHQCOL_MFILE));
+    continue;
+    }
+
+  /**********
+  * find matching queues
+  **********/
+
+  int bfnd = 0;
+  int nidx2;
+  for (nidx2 = 0; nidx2 < pmod_data->mohq_cnt; nidx2++)
+    {
+    if (!strcasecmp (pqlst [nidx2].mohq_uri, puri))
+      {
+      /**********
+      * o data the same?
+      * o mark as found
+      **********/
+
+      if (strcmp (pqlst [nidx2].mohq_mohdir, pmohdir))
+        {
+        strcpy (pqlst [nidx2].mohq_mohdir, pmohdir);
+        LM_INFO ("Queue,Field (%s,%.*s): Changed", pqname,
+          STR_FMT (&MOHQCSTR_MDIR));
+        }
+      ptext = (char *)VAL_STRING (prowvals + MOHQCOL_MFILE);
+      if (strcmp (pqlst [nidx2].mohq_mohfile, ptext))
+        {
+        strcpy (pqlst [nidx2].mohq_mohfile, ptext);
+        LM_INFO ("Queue,Field (%s,%.*s): Changed", pqname,
+          STR_FMT (&MOHQCSTR_MFILE));
+        }
+      ptext = (char *)VAL_STRING (prowvals + MOHQCOL_NAME);
+      if (strcmp (pqlst [nidx2].mohq_name, ptext))
+        {
+        strcpy (pqlst [nidx2].mohq_name, ptext);
+        LM_INFO ("Queue,Field (%s,%.*s): Changed", pqname,
+          STR_FMT (&MOHQCSTR_NAME));
+        }
+      int bdebug = VAL_INT (prowvals + MOHQCOL_DEBUG) ? MOHQF_DBG : 0;
+      if ((pqlst [nidx2].mohq_flags & MOHQF_DBG) != bdebug)
+        {
+        if (bdebug)
+          { pqlst [nidx2].mohq_flags |= MOHQF_DBG; }
+        else
+          { pqlst [nidx2].mohq_flags &= ~MOHQF_DBG; }
+        LM_INFO ("Queue,Field (%s,%.*s): Changed", pqname,
+          STR_FMT (&MOHQCSTR_DEBUG));
+        }
+      bfnd = -1;
+      pqlst [nidx2].mohq_flags |= MOHQF_CHK;
+      break;
+      }
+    }
+
+  /**********
+  * add new queue
+  **********/
+
+  if (!bfnd)
+    {
+    /**********
+    * o allocate new list
+    * o copy old list
+    * o add new row
+    * o release old list
+    * o adjust pointers to new list
+    **********/
+
+    int nsize = pmod_data->mohq_cnt + 1;
+    pnewlst = (mohq_lst *) shm_malloc (sizeof (mohq_lst) * nsize);
+    if (!pnewlst)
+      {
+      LM_ERR ("%sUnable to allocate shared memory!", pfncname);
+      return;
+      }
+    pmod_data->mohq_cnt = nsize;
+    if (--nsize)
+      { memcpy (pnewlst, pqlst, sizeof (mohq_lst) * nsize); }
+    pnewlst [nsize].mohq_id = prowvals [MOHQCOL_ID].val.int_val;
+    pnewlst [nsize].mohq_flags = MOHQF_CHK;
+    strcpy (pnewlst [nsize].mohq_uri, puri);
+    strcpy (pnewlst [nsize].mohq_mohdir, pmohdir);
+    strcpy (pnewlst [nsize].mohq_mohfile,
+      (char *)VAL_STRING (prowvals + MOHQCOL_MFILE));
+    strcpy (pnewlst [nsize].mohq_name,
+      (char *)VAL_STRING (prowvals + MOHQCOL_NAME));
+    if (VAL_INT (prowvals + MOHQCOL_DEBUG))
+      { pnewlst [nsize].mohq_flags |= MOHQF_DBG; }
+    LM_INFO ("Added new queue (%s)", pnewlst [nsize].mohq_name);
+    if (nsize)
+      { shm_free (pmod_data->pmohq_lst); }
+    pmod_data->pmohq_lst = pnewlst;
+    pqlst = pnewlst;
+    }
+  }
+
+/**********
+* find deleted queues
+**********/
+
+for (nidx = 0; nidx < pmod_data->mohq_cnt; nidx++)
+  {
+  /**********
+  * o exists?
+  * o if not last, replace current with last queue
+  **********/
+
+  if (pqlst [nidx].mohq_flags & MOHQF_CHK)
+    { continue; }
+  LM_INFO ("Removed queue (%s)", pqlst [nidx].mohq_name);
+  if (nidx != (pmod_data->mohq_cnt - 1))
+    {
+    memcpy (&pqlst [nidx], &pqlst [pmod_data->mohq_cnt - 1],
+      sizeof (mohq_lst));
+    }
+  --pmod_data->mohq_cnt;
+  --nidx;
+  }
+return;
+}

+ 66 - 0
modules/mohqueue/mohq_db.h

@@ -0,0 +1,66 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Robert Boisvert
+ *
+ * This file is part of the mohqueue module for sip-router, 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#ifndef MOHQ_DB_H
+#define MOHQ_DB_H
+
+/**********
+* DB definitions
+**********/
+
+/* table versions */
+#define MOHQ_CTABLE_VERSION  1
+#define MOHQ_QTABLE_VERSION  1
+
+/* mohqueues columns */
+#define MOHQ_COLCNT   6
+#define MOHQCOL_ID    0
+#define MOHQCOL_URI   1
+#define MOHQCOL_MDIR  2
+#define MOHQCOL_MFILE 3
+#define MOHQCOL_NAME  4
+#define MOHQCOL_DEBUG 5
+
+/* mohqcalls columns */
+#define CALL_COLCNT   6
+#define CALLCOL_STATE 0
+#define CALLCOL_CALL  1
+#define CALLCOL_MOHQ  2
+#define CALLCOL_FROM  3
+#define CALLCOL_CNTCT 4
+#define CALLCOL_TIME  5
+
+/**********
+* DB function declarations
+**********/
+
+void add_call_rec (int);
+void clear_calls (db1_con_t *);
+void delete_call_rec (call_lst *);
+db1_con_t *mohq_dbconnect (void);
+void mohq_dbdisconnect (db1_con_t *);
+void update_call_rec (call_lst *);
+void update_debug (mohq_lst *, int);
+void update_mohq_lst (db1_con_t *pconn);
+
+#endif /* MOHQ_DB_H */

+ 2631 - 0
modules/mohqueue/mohq_funcs.c

@@ -0,0 +1,2631 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Robert Boisvert
+ *
+ * This file is part of the mohqueue module for sip-router, 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include <stdarg.h>
+
+#include "mohq.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.0"
+
+/**********
+* 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 pmi_nolock [1] = {STR_STATIC_INIT ("Unable to lock queue")};
+str pmi_noqueue [1] = {STR_STATIC_INIT ("No matching queue name found")};
+str prefer [1] = {STR_STATIC_INIT ("REFER")};
+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_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"
+  "Max-Forwards: 70" SIPEOL
+  "Contact: <%s>" SIPEOL
+  "User-Agent: " USRAGNT SIPEOL
+  };
+
+str pextrahdr [1] =
+  {
+  STR_STATIC_INIT (
+  ALLOWHDR SIPEOL
+  "Supported: 100rel" SIPEOL
+  "Accept-Language: en" SIPEOL
+  "Content-Type: application/sdp" SIPEOL
+  "User-Agent: " USRAGNT 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"
+  "Max-Forwards: 70" SIPEOL
+  "Refer-To: <%s>" SIPEOL
+  "Referred-By: <%.*s>" SIPEOL
+  };
+
+char preinvitemsg [] =
+  {
+  "%s"
+  "Max-Forwards: 70" SIPEOL
+  "Contact: <%s>" SIPEOL
+  ALLOWHDR SIPEOL
+  "Supported: 100rel" SIPEOL
+  "User-Agent: " USRAGNT SIPEOL
+  "Accept-Language: en" SIPEOL
+  "Content-Type: application/sdp" 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 drop_call (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);
+
+/**********
+* local functions
+**********/
+
+/**********
+* Process ACK Message
+*
+* INPUT:
+*   Arg (1) = SIP message pointer
+*   Arg (2) = call pointer
+* OUTPUT: 0=failed
+**********/
+
+int 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)!", pfncname, pcall->call_from); }
+  else
+    {
+    mohq_debug (pcall->pmohq, "%sACK from refused re-INVITE (%s)!",
+      pfncname, pcall->call_from);
+    }
+  return 0;
+  }
+
+/**********
+* o release INVITE transaction
+* o save SDP address info
+* o put in queue
+**********/
+
+if (ptm->t_lookup_ident (&ptrans, pcall->call_hash, pcall->call_label) < 0)
+  {
+  LM_ERR ("%sINVITE transaction missing for call (%s)!",
+    pfncname, pcall->call_from);
+  return 0;
+  }
+else
+  {
+  if (ptm->t_release (pcall->call_pmsg) < 0)
+    {
+    LM_ERR ("%sRelease transaction failed for call (%s)!",
+      pfncname, pcall->call_from);
+    return 0;
+    }
+  }
+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 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", pfncname,
+    pcall->call_from);
+  }
+else
+  {
+  int nreply = pcbp->rpl->first_line.u.reply.statuscode;
+  if ((nreply / 100) != 2)
+    {
+    LM_ERR ("%sCall (%s) BYE error (%d)", 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: 0=failed
+**********/
+
+int bye_msg (sip_msg_t *pmsg, call_lst *pcall)
+
+{
+/**********
+* o send OK
+* o teardown call
+**********/
+
+char *pfncname = "bye_msg: ";
+if (pmod_data->psl->freply (pmsg, 200, presp_ok) < 0)
+  {
+  LM_ERR ("%sUnable to create reply to call (%s)", pfncname,
+    pcall->call_from);
+  return 0;
+  }
+if (pcall->call_state >= CLSTA_INQUEUE)
+  { drop_call (pmsg, pcall); }
+else
+  {
+  LM_ERR ("%sEnding call (%s) before placed in queue!",
+    pfncname, pcall->call_from);
+  delete_call (pcall);
+  return 0;
+  }
+return 1;
+}
+
+/**********
+* Process CANCEL Message
+*
+* INPUT:
+*   Arg (1) = SIP message pointer
+*   Arg (2) = call pointer
+* OUTPUT: 0=failed
+**********/
+
+int cancel_msg (sip_msg_t *pmsg, call_lst *pcall)
+
+{
+/**********
+* 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!", pfncname);
+    return 0;
+    }
+  }
+else
+  {
+  LM_ERR ("%sUnable to CANCEL because accepted INVITE for call (%s)!",
+    pfncname, pcall->call_from);
+  if (pmod_data->psl->freply (pmsg, 481, presp_nocall) < 0)
+    {
+    LM_ERR ("%sUnable to create reply!", pfncname);
+    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 proxy connection
+* o create dialog
+**********/
+
+char *pfncname = "close_call: ";
+int bsent = 0;
+char *phdr = 0;
+if (pmsg != FAKED_REPLY)
+  {
+  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)!",
+      pfncname, pcall->call_from);
+    }
+  }
+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 (pquri); // Contact
+phdr = pkg_malloc (npos1);
+if (!phdr)
+  {
+  LM_ERR ("%sNo more memory!", pfncname);
+  goto bye_err;
+  }
+sprintf (phdr, pbyemsg,
+  pcall->call_via, // Via
+  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)!",
+    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) = queue index
+*   Arg (2) = SIP message pointer
+*   Arg (3) = totag str pointer
+* OUTPUT: call index; -1 if unable to create
+**********/
+
+int create_call (int mohq_idx, sip_msg_t *pmsg, str *ptotag)
+
+{
+/**********
+* o lock calls
+* o find inactive slot
+**********/
+
+char *pfncname = "create_call: ";
+int ncall_idx = pmod_data->call_cnt;
+if (!mohq_lock_set (pmod_data->pcall_lock, 1, 2000))
+  {
+  LM_ERR ("%sUnable to lock calls!", pfncname);
+  return -1;
+  }
+for (ncall_idx = 0; ncall_idx < pmod_data->call_cnt; ncall_idx++)
+  {
+  if (!pmod_data->pcall_lst [ncall_idx].call_active)
+    { break; }
+  }
+if (ncall_idx == pmod_data->call_cnt)
+  {
+  mohq_lock_release (pmod_data->pcall_lock);
+  LM_ERR ("%sNo call slots available!", pfncname);
+  return -1;
+  }
+
+/**********
+* o release call lock
+* o add values to new entry
+**********/
+
+call_lst *pcall = &pmod_data->pcall_lst [ncall_idx];
+pcall->call_active = 1;
+mohq_lock_release (pmod_data->pcall_lock);
+pcall->pmohq = &pmod_data->pmohq_lst [mohq_idx];
+pcall->call_state = 0;
+str *pstr = &pmsg->callid->body;
+strncpy (pcall->call_id, pstr->s, pstr->len);
+pcall->call_id [pstr->len] = '\0';
+pstr = &pmsg->from->body;
+strncpy (pcall->call_from, pstr->s, pstr->len);
+pcall->call_from [pstr->len] = '\0';
+strncpy (pcall->call_tag, ptotag->s, ptotag->len);
+pcall->call_tag [ptotag->len] = '\0';
+if (!pmsg->contact)
+  { *pcall->call_contact = '\0'; }
+else
+  {
+  pstr = &pmsg->contact->body;
+  strncpy (pcall->call_contact, pstr->s, pstr->len);
+  pcall->call_contact [pstr->len] = '\0';
+  }
+
+/**********
+* extract Via headers
+**********/
+
+hdr_field_t *phdr = pmsg->h_via1;
+if (phdr)
+  {
+  int npos1 = 0;
+  while ((phdr = next_sibling_hdr (phdr)))
+    {
+    struct via_body *pvia;
+    char *pviabuf;
+    int bovrflow = 0;
+    int npos2;
+    int nvia_max = sizeof (pcall->call_via);
+    for (pvia = (struct via_body *)phdr->parsed; pvia; pvia = pvia->next)
+      {
+      /**********
+      * o skip trailing whitespace
+      * o check if overflow
+      **********/
+
+      npos2 = pvia->bsize;
+      pviabuf = pvia->name.s;
+      while (npos2)
+        {
+        --npos2;
+        if (pviabuf [npos2] == ' ' || pviabuf [npos2] == '\r'
+          || pviabuf [npos2] == '\n' || pviabuf [npos2] == '\t' || pviabuf [npos2] == ',')
+          { continue; }
+        break;
+        }
+      if ((npos2 + npos1 + 7) >= nvia_max)
+        {
+        LM_WARN ("%sVia buffer overflowed!", pfncname);
+        bovrflow = 1;
+        break;
+        }
+
+      /**********
+      * copy via
+      **********/
+
+      strcpy (&pcall->call_via [npos1], "Via: ");
+      npos1 += 5;
+      strncpy (&pcall->call_via [npos1], pviabuf, npos2);
+      npos1 += npos2;
+      strcpy (&pcall->call_via [npos1], SIPEOL);
+      npos1 += 2;
+      }
+    if (bovrflow)
+      { break; }
+    }
+  }
+
+/**********
+* o update DB
+* o lock MOH queue
+**********/
+
+pcall->call_state = CLSTA_ENTER;
+add_call_rec (ncall_idx);
+mohq_lock_set (pmod_data->pmohq_lock, 0, 0);
+mohq_debug (pcall->pmohq, "%sAdded call (%s) to queue (%s)",
+  pfncname, pcall->call_from, pcall->pmohq->mohq_name);
+return ncall_idx;
+}
+
+/**********
+* Delete Call
+*
+* INPUT:
+*   Arg (1) = call pointer
+* OUTPUT: none
+**********/
+
+void delete_call (call_lst *pcall)
+
+{
+/**********
+* o update DB
+* o inactivate slot
+* o release MOH queue
+**********/
+
+mohq_debug (pcall->pmohq, "delete_call: Deleting call (%s) from queue (%s)",
+  pcall->call_from, pcall->pmohq->mohq_name);
+delete_call_rec (pcall);
+pcall->call_active = 0;
+mohq_lock_release (pmod_data->pmohq_lock);
+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!", pfncname);
+  if (pmod_data->psl->freply (pmsg, 500, presp_srverr) < 0)
+    {
+    LM_ERR ("%sUnable to create reply to %.*s!", 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!", pfncname); }
+LM_ERR ("%sRefused %.*s for call (%s)!", 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!", pfncname,
+    STR_FMT (&REQ_LINE (pmsg).method));
+  }
+return;
+}
+
+/**********
+* Drop the Call
+*
+* INPUT:
+*   Arg (1) = SIP message pointer
+*   Arg (2) = call pointer
+* OUTPUT: none
+**********/
+
+void drop_call (sip_msg_t *pmsg, call_lst *pcall)
+
+{
+/**********
+* o destroy proxy connection
+* o delete call
+**********/
+
+char *pfncname = "drop_call: ";
+if (pmsg != FAKED_REPLY)
+  {
+  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)!",
+      pfncname, pcall->call_from);
+    }
+  }
+delete_call (pcall);
+return;
+}
+
+/**********
+* Find Call
+*
+* INPUT:
+*   Arg (1) = SIP message pointer
+*   Arg (2) = pointer to call pointer
+* OUTPUT: queue index; -1 if unable to find
+**********/
+
+int find_call (sip_msg_t *pmsg, call_lst **ppcall)
+
+{
+/**********
+* o find current RURI
+* o strip off parms or headers
+* o search MOH queue
+**********/
+
+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; }
+  }
+*ppcall = 0;
+if (nqidx == pmod_data->mohq_cnt)
+  { return -1;}
+
+/**********
+* o get to tag
+* o first INVITE?
+* o get callID
+* o ignore to tag if CANCEL on first INVITE
+* o search call queue
+**********/
+
+str *ptotag = &(get_to (pmsg)->tag_value);
+if (pmsg->REQ_METHOD == METHOD_INVITE)
+  {
+  if (!ptotag->len)
+    { return nqidx; }
+  }
+if (!pmsg->callid)
+  { return -1; }
+str *pcallid = &pmsg->callid->body;
+if (!pcallid)
+  { return -1; }
+if (pmsg->REQ_METHOD == METHOD_CANCEL)
+  {
+  if (!ptotag->len)
+    { ptotag = 0; }
+  }
+for (nidx = 0; nidx < pmod_data->call_cnt; nidx++)
+  {
+  /**********
+  * o call active?
+  * o callID matches?
+  * o to tag matches?
+  * o return call pointer
+  **********/
+
+  call_lst *pcall = &pmod_data->pcall_lst [nidx];
+  if (!pcall->call_active)
+    { continue; }
+  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; }
+    }
+  *ppcall = pcall;
+  return nqidx;
+  }
+return -1;
+}
+
+/**********
+* Find Queue
+*
+* INPUT:
+*   Arg (1) = queue name str pointer
+* OUTPUT: queue index; -1 if unable to find
+**********/
+
+int find_queue (str *pqname)
+
+{
+char *pfncname = "find_queue: ";
+int nidx;
+str tmpstr;
+if (!mohq_lock_set (pmod_data->pmohq_lock, 0, 500))
+  {
+  LM_ERR ("%sUnable to lock queues!", 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)!", 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)!", 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_active)
+    { 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)!", 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) = queue index
+* OUTPUT: 0=failed
+**********/
+
+int first_invite_msg (sip_msg_t *pmsg, int mohq_idx)
+
+{
+/**********
+* o create new transaction
+* o SDP exists?
+* o accepts REFER?
+* o send rtpproxy offer
+**********/
+
+char *pfncname = "first_invite_msg: ";
+tm_api_t *ptm = pmod_data->ptm;
+if (ptm->t_newtran (pmsg) < 0)
+  {
+  LM_ERR ("%sUnable to create new transaction for call (%.*s)!",
+    pfncname, STR_FMT (&pmsg->callid->body));
+  if (pmod_data->psl->freply (pmsg, 500, presp_srverr) < 0)
+    { LM_ERR ("%sUnable to create reply!", pfncname); }
+  return 0;
+  }
+if (!(pmsg->msg_flags & FL_SDP_BODY))
+  {
+  if (parse_sdp (pmsg))
+    {
+    LM_ERR ("%sINVITE lacks SDP (%.*s)!", pfncname,
+      STR_FMT (&pmsg->callid->body));
+    if (ptm->t_reply (pmsg, 606, "INVITE lacks SDP") < 0)
+      { LM_ERR ("%sUnable to reply to INVITE!", pfncname); }
+    return 0;
+    }
+  }
+LM_DBG ("%sMaking offer for RTP link for call (%.*s)",
+  pfncname, STR_FMT (&pmsg->callid->body));
+if (pmod_data->fn_rtp_offer (pmsg, 0, 0) != 1)
+  {
+  LM_ERR ("%srtpproxy_offer refused for call (%.*s)!",
+    pfncname, STR_FMT (&pmsg->callid->body));
+  if (ptm->t_reply (pmsg, 500, presp_srverr->s) < 0)
+    { LM_ERR ("%sUnable to reply to INVITE!", pfncname); }
+  return 0;
+  }
+
+/**********
+* create call record
+**********/
+
+str ptotag [1];
+if (ptm->t_get_reply_totag (pmsg, ptotag) != 1)
+  {
+  LM_ERR ("%sUnable to create totag for call (%.*s)!",
+    pfncname, STR_FMT (&pmsg->callid->body));
+  if (ptm->t_reply (pmsg, 500, presp_srverr->s) < 0)
+    { LM_ERR ("%sUnable to reply to INVITE!", pfncname); }
+  return 0;
+  }
+int ncall_idx = create_call (mohq_idx, pmsg, ptotag);
+if (ncall_idx == -1)
+  { return 0; }
+
+/**********
+* o catch failures
+* o send working response
+* o add contact to reply
+* o supports/requires PRACK? (RFC 3262 section 3)
+* o exit if not ringing
+**********/
+
+call_lst *pcall = &pmod_data->pcall_lst [ncall_idx];
+pcall->call_cseq = 1;
+if (ptm->register_tmcb (pmsg, 0, TMCB_ON_FAILURE | TMCB_DESTROY,
+  invite_cb, pcall, 0) < 0)
+  {
+  LM_ERR ("%sUnable to set callback for call (%.*s)!",
+    pfncname, STR_FMT (&pmsg->callid->body));
+  if (ptm->t_reply (pmsg, 500, presp_srverr->s) < 0)
+    { LM_ERR ("%sUnable to reply to INVITE!", pfncname); }
+  delete_call (pcall);
+  return 0;
+  }
+if (ptm->t_reply (pmsg, 100, "Your call is important to us") < 0)
+  {
+  LM_ERR ("%sUnable to reply to INVITE!", pfncname);
+  delete_call (pcall);
+  return 0;
+  }
+str pcontact [1];
+char *pcontacthdr = "Contact: <%s>" SIPEOL;
+pcontact->s = pkg_malloc (strlen (pmod_data->pmohq_lst [mohq_idx].mohq_uri)
+  + strlen (pcontacthdr));
+if (!pcontact->s)
+  {
+  LM_ERR ("%sNo more memory!", pfncname);
+  delete_call (pcall);
+  return 0;
+  }
+sprintf (pcontact->s, pcontacthdr, pmod_data->pmohq_lst [mohq_idx].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)!",
+    pfncname, pcontact->s, pcall->call_from);
+  }
+pkg_free (pcontact->s);
+pcall->call_pmsg = pmsg;
+struct cell *ptrans = ptm->t_gett ();
+pcall->call_hash = ptrans->hash_index;
+pcall->call_label = ptrans->label;
+if (search_hdr_ext (pmsg->supported, p100rel)
+  || search_hdr_ext (pmsg->require, p100rel))
+  {
+  if (!send_prov_rsp (pmsg, pcall))
+    {
+    delete_call (pcall);
+    return 0;
+    }
+  }
+else
+  {
+  if (ptm->t_reply (pmsg, 180, presp_ring->s) < 0)
+    {
+    LM_ERR ("%sUnable to reply to INVITE!", pfncname);
+    return 0;
+    }
+  else
+    {
+    pcall->call_state = CLSTA_RINGING;
+    mohq_debug (pcall->pmohq, "%sSent RINGING for call (%s)",
+      pfncname, pcall->call_from);
+    }
+  }
+
+/**********
+* o call cancelled?
+* o accept call with RTP
+**********/
+
+if (pcall->call_state == CLSTA_CANCEL)
+  {
+  if (ptm->t_release (pmsg) < 0)
+    {
+    LM_ERR ("%sRelease transaction failed for call (%s)!",
+      pfncname, pcall->call_from);
+    }
+  delete_call (pcall);
+  return 0;
+  }
+if (!send_rtp_answer (pmsg, pcall))
+  {
+  if (pmod_data->psl->freply (pmsg, 500, presp_srverr) < 0)
+    { LM_ERR ("%sUnable to create reply!", pfncname); }
+  delete_call (pcall);
+  return 0;
+  }
+return 1;
+}
+
+/**********
+* Form RTP SDP String
+*
+* 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: ";
+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)!", pfncname, pcall->call_from);
+  return 0;
+  }
+if (ptob->param_lst)
+  { free_to_params (ptob); }
+str ptarget [1];
+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)!", 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!", 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;
+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)!", 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!", 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)
+
+{
+char *pfncname = "invite_cb: ";
+call_lst *pcall = (call_lst *)*pcbp->param;
+switch (pcall->call_state)
+  {
+  case CLSTA_PRACKSTRT:
+    LM_ERR ("%sNo provisional response received for call (%s)!",
+      pfncname, pcall->call_from);
+    pcall->call_state = CLSTA_CANCEL;
+    break;
+  case CLSTA_INVITED:
+    LM_ERR ("%sINVITE failed for call (%s)!", pfncname, pcall->call_from);
+    close_call (FAKED_REPLY, pcall);
+    break;
+  }
+}
+
+/**********
+* Process NOTIFY Message
+*
+* INPUT:
+*   Arg (1) = SIP message pointer
+*   Arg (2) = call pointer
+* OUTPUT: 0=failed
+**********/
+
+int 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)!", pfncname,
+    pcall->call_from);
+  if (pmod_data->psl->freply (pmsg, 481, presp_nocall) < 0)
+    { LM_ERR ("%sUnable to create reply!", pfncname); }
+  return 0;
+  }
+
+/**********
+* 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)!", pfncname,
+    psipfrag->s, pcall->call_from);
+  if (pmod_data->psl->freply (pmsg, 415, presp_unsupp) < 0)
+    { LM_ERR ("%sUnable to create reply!", pfncname); }
+  return 0;
+  }
+char *pfrag = get_body (pmsg);
+if (!pfrag)
+  {
+  LM_ERR ("%s%s body missing for call (%s)!", pfncname,
+    psipfrag->s, pcall->call_from);
+  if (pmod_data->psl->freply (pmsg, 415, presp_unsupp) < 0)
+    { LM_ERR ("%sUnable to create reply!", pfncname); }
+  return 0;
+  }
+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!", pfncname);
+  return 0;
+  }
+strncpy (pbody->s, pfrag, pbody->len);
+if (pbody->s [pbody->len - 1] != '\n')
+  {
+  strncpy (&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)!", pfncname, pcall->call_from);
+  if (pmod_data->psl->freply (pmsg, 415, presp_unsupp) < 0)
+    { LM_ERR ("%sUnable to create reply!", pfncname); }
+  return 0;
+  }
+
+/**********
+* o send OK
+* o REFER done?
+**********/
+
+if (pmod_data->psl->freply (pmsg, 200, presp_ok) < 0)
+  {
+  LM_ERR ("%sUnable to create reply for call (%s)!",
+    pfncname, pcall->call_from);
+  return 0;
+  }
+int nreply = pstart->u.reply.statuscode;
+mohq_debug (pcall->pmohq, "%sNOTIFY received reply (%d) for call (%s)",
+  pfncname, nreply, pcall->call_from);
+switch (nreply / 100)
+  {
+  case 1:
+    break;
+  case 2:
+    close_call (pmsg, pcall);
+    break;
+  default:
+    LM_WARN ("%sUnable to redirect call (%s)!", pfncname, pcall->call_from);
+    if (nreply == 487)
+      {
+      /**********
+      * call was canceled
+      **********/
+
+      drop_call (pmsg, pcall);
+      return 0;
+      }
+
+    /**********
+    * return call to queue
+    **********/
+
+    pcall->call_state = CLSTA_INQUEUE;
+    update_call_rec (pcall);
+    break;
+  }
+return 1;
+}
+
+/**********
+* Process PRACK Message
+*
+* INPUT:
+*   Arg (1) = SIP message pointer
+*   Arg (2) = call pointer
+* OUTPUT: 0=failed
+**********/
+
+int 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)!", pfncname, pcall->call_from);
+  if (pmod_data->psl->freply (pmsg, 481, presp_nocall) < 0)
+    { LM_ERR ("%sUnable to create reply!", pfncname); }
+  return 0;
+  }
+
+/**********
+* 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)!",
+    pfncname, pcall->call_from);
+  if (pmod_data->psl->freply (pmsg, 500, presp_srverr) < 0)
+    { LM_ERR ("%sUnable to create reply!", pfncname); }
+  return 0;
+  }
+if (ptm->t_reply (pmsg, 200, presp_ok->s) < 0)
+  {
+  LM_ERR ("%sUnable to reply to PRACK for call (%s)!",
+    pfncname, pcall->call_from);
+  return 0;
+  }
+pcall->call_state = CLSTA_PRACKRPLY;
+return 1;
+}
+
+/**********
+* 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: ";
+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
+  + puri->len // Refer-To
+  + ptob->uri.len; // Referred-By
+char *pbuf = pkg_malloc (npos1);
+if (!pbuf)
+  {
+  LM_ERR ("%sNo more memory!", pfncname);
+  goto refererr;
+  }
+sprintf (pbuf, prefermsg,
+  pcall->call_via, // Via
+  puri->s, // Refer-To
+  STR_FMT (&ptob->uri)); // Referred-By
+
+/**********
+* send REFER request
+**********/
+
+tm_api_t *ptm = pmod_data->ptm;
+int nret = 0;
+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->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)!",
+    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 ((ntype == TMCB_ON_FAILURE) || (pcbp->req == FAKED_REPLY))
+  {
+  LM_ERR ("%sCall (%s) did not respond to REFER", pfncname,
+    pcall->call_from);
+  drop_call (pcbp->req, pcall);
+  return;
+  }
+int nreply = pcbp->rpl->first_line.u.reply.statuscode;
+if ((nreply / 100) == 2)
+  {
+  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)", pfncname,
+    pcall->call_from, nreply);
+  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: 0=failed
+**********/
+
+int 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!", pfncname); }
+  return 1;
+  }
+if (!(pmsg->msg_flags & FL_SDP_BODY))
+  {
+  if (parse_sdp (pmsg))
+    {
+    LM_ERR ("%sre-INVITE lacks SDP (%s)!", pfncname, pcall->call_from);
+    if (pmod_data->psl->freply (pmsg, 488, presp_noaccept) < 0)
+      { LM_ERR ("%sUnable to create reply!", pfncname); }
+    return 1;
+    }
+  }
+
+/**********
+* 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)!",
+      pfncname, pcall->call_from);
+    if (pmod_data->psl->freply (pmsg, 488, presp_noaccept) < 0)
+      {
+      LM_ERR ("%sUnable to create reply!", pfncname);
+      return 1;
+      }
+    }
+  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!", pfncname);
+      return 1;
+      }
+    }
+  return 1;
+  }
+
+/**********
+* hold not allowed, say good-bye
+**********/
+
+LM_ERR ("%sTerminating call (%s) because hold not allowed!",
+  pfncname, pcall->call_from);
+if (pmod_data->psl->freply (pmsg, 200, presp_ok) < 0)
+  {
+  LM_ERR ("%sUnable to create reply!", pfncname);
+  return 1;
+  }
+close_call (pmsg, pcall);
+return 1;
+}
+
+/**********
+* 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 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] == ',')
+      { 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
+  "User-Agent: " USRAGNT 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)!",
+    pfncname, pcall->call_from);
+  if (pmod_data->psl->freply (pmsg, 500, presp_srverr) < 0)
+    { LM_ERR ("%sUnable to create reply!", pfncname); }
+  return 0;
+  }
+if (ptm->t_reply (pmsg, 180, presp_ring->s) < 0)
+  {
+  LM_ERR ("%sUnable to reply to INVITE for call (%s)",
+    pfncname, pcall->call_from);
+  return 0;
+  }
+pcall->call_state = CLSTA_PRACKSTRT;
+mohq_debug (pcall->pmohq, "%sSent RINGING for call (%s)",
+  pfncname, pcall->call_from);
+
+/**********
+* o wait until PRACK
+* o remove header lump
+**********/
+
+while (1)
+  {
+  usleep (USLEEP_LEN);
+  if (pcall->call_state != CLSTA_PRACKSTRT)
+    { break; }
+  }
+unlink_lump_rpl (pmsg, *phdrlump);
+if (pcall->call_state != CLSTA_PRACKRPLY)
+  {
+  if (ptm->t_release (pmsg) < 0)
+    {
+    LM_ERR ("%sRelease transaction failed for call (%s)!",
+      pfncname, pcall->call_from);
+    }
+  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)!",
+    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!", 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));
+
+/**********
+* o send rtpproxy 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, 0, 0) != 1)
+  {
+  LM_ERR ("%srtpproxy_answer refused for call (%s)!",
+    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)!",
+    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)!",
+    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)!",
+    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)!",
+    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)!",
+    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;
+pv_parse_format (pMOH, &pmodel);
+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)!",
+    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!");
+  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;
+}
+
+/**********
+* MI Debug
+*
+* PARAMETERS:
+* queue name = queue to use
+* state = 0=off, <>0=on
+*
+* INPUT:
+*   Arg (1) = command tree pointer
+*   Arg (2) = parms pointer
+* OUTPUT: root pointer
+**********/
+
+struct mi_root *mi_debug (struct mi_root *pcmd_tree, void *parms)
+
+{
+/**********
+* o parm count correct?
+* o find queue
+* o lock queue
+**********/
+
+struct mi_node *pnode = pcmd_tree->node.kids;
+if (!pnode || !pnode->next || pnode->next->next)
+  { return init_mi_tree (400, MI_MISSING_PARM_S, MI_MISSING_PARM_LEN); }
+int nq_idx = find_queue (&pnode->value);
+if (nq_idx == -1)
+  { return init_mi_tree (400, pmi_noqueue->s, pmi_noqueue->len); }
+char pint [20];
+int nsize = (pnode->next->value.len >= sizeof (pint))
+  ? sizeof (pint) - 1 : pnode->next->value.len;
+strncpy (pint, pnode->next->value.s, nsize);
+pint [nsize] = '\0';
+int bdebug = atoi (pint) ? 1 : 0;
+if (!mohq_lock_set (pmod_data->pmohq_lock, 0, 5000))
+  { return init_mi_tree (400, pmi_nolock->s, pmi_nolock->len); }
+
+/**********
+* 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 init_mi_tree (200, MI_OK_S, MI_OK_LEN);
+}
+
+/**********
+* MI Drop Call
+*
+* PARAMETERS:
+* queue name = queue to use
+* callID = *=all, otherwise callID
+*
+* INPUT:
+*   Arg (1) = command tree pointer
+*   Arg (2) = parms pointer
+* OUTPUT: root pointer
+**********/
+
+struct mi_root *mi_drop_call (struct mi_root *pcmd_tree, void *parms)
+
+{
+/**********
+* o parm count correct?
+* o find queue
+* o lock calls
+**********/
+
+struct mi_node *pnode = pcmd_tree->node.kids;
+if (!pnode || !pnode->next || pnode->next->next)
+  { return init_mi_tree (400, MI_MISSING_PARM_S, MI_MISSING_PARM_LEN); }
+int nq_idx = find_queue (&pnode->value);
+if (nq_idx == -1)
+  { return init_mi_tree (400, pmi_noqueue->s, pmi_noqueue->len); }
+if (!mohq_lock_set (pmod_data->pcall_lock, 0, 5000))
+  { return init_mi_tree (400, pmi_nolock->s, pmi_nolock->len); }
+
+/**********
+* o find matching calls
+* o release lock
+**********/
+
+mohq_lst *pqueue = &pmod_data->pmohq_lst [nq_idx];
+int nidx;
+str *pcallid = &pnode->next->value;
+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_active)
+    { 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 init_mi_tree (200, MI_OK_S, MI_OK_LEN);
+}
+
+/**********
+* 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!", pfncname);
+  return -1;
+  }
+if (fixup_get_svalue (pmsg, (gparam_p)pqueue, pqname))
+  {
+  LM_ERR ("%sInvalid queue name!", pfncname);
+  return -1;
+  }
+
+/**********
+* o find queue
+* o lock calls
+* o count items in queue
+**********/
+
+int nq_idx = find_queue (pqname);
+int ncount = 0;
+call_lst *pcalls = pmod_data->pcall_lst;
+int ncall_idx, mohq_id;
+if (!mohq_lock_set (pmod_data->pcall_lock, 1, 200))
+  { LM_ERR ("%sUnable to lock calls!", 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_active)
+        { 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 ()!", 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 ();
+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", 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; 1=successfully processed
+**********/
+
+int mohq_process (sip_msg_t *pmsg)
+
+{
+/**********
+* o parse headers
+* o lock MOH queue
+* o directed to message queue?
+* o connect to database
+**********/
+
+char *pfncname = "mohq_process: ";
+if (parse_headers (pmsg, HDR_EOH_F, 0) < 0)
+  {
+  LM_ERR ("%sUnable to parse header!", pfncname);
+  return -1;
+  }
+if (!mohq_lock_set (pmod_data->pmohq_lock, 0, 2000))
+  { return -1; }
+call_lst *pcall;
+int mohq_idx = find_call (pmsg, &pcall);
+db1_con_t *pconn = mohq_dbconnect ();
+if (pconn)
+  {
+  /**********
+  * o last update older than 1 minute?
+  * o exclusively lock MOH queue
+  * o update 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);
+  }
+if (mohq_idx < 0)
+  {
+  mohq_lock_release (pmod_data->pmohq_lock);
+  return -1;
+  }
+
+/**********
+* o process message
+* o release MOH queue
+**********/
+
+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);
+int ret;
+switch (pmsg->REQ_METHOD)
+  {
+  case METHOD_INVITE:
+    /**********
+    * initial INVITE?
+    **********/
+
+    if (!pcall)
+      { ret = first_invite_msg (pmsg, mohq_idx); }
+    else
+      { ret = reinvite_msg (pmsg, pcall); }
+    break;
+  case METHOD_NOTIFY:
+    ret = notify_msg (pmsg, pcall);
+    break;
+  case METHOD_PRACK:
+    ret = prack_msg (pmsg, pcall);
+    break;
+  case METHOD_ACK:
+    ret = ack_msg (pmsg, pcall);
+    break;
+  case METHOD_BYE:
+    ret = bye_msg (pmsg, pcall);
+    break;
+  case METHOD_CANCEL:
+    ret = cancel_msg (pmsg, pcall);
+    break;
+  default:
+    deny_method (pmsg, pcall);
+    ret = 0;
+    break;
+  }
+mohq_lock_release (pmod_data->pmohq_lock);
+return ret ? 1 : -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!", pfncname);
+  return -1;
+  }
+if (fixup_get_svalue (pmsg, (gparam_p)pqueue, pqname))
+  {
+  LM_ERR ("%sInvalid queue name!", pfncname);
+  return -1;
+  }
+if (fixup_get_svalue (pmsg, (gparam_p)pURI, puri))
+  {
+  LM_ERR ("%sInvalid URI!", pfncname);
+  return -1;
+  }
+if (puri->len > URI_LEN)
+  {
+  LM_ERR ("%sURI too long!", pfncname);
+  return -1;
+  }
+struct sip_uri puri_parsed [1];
+if (parse_uri (puri->s, puri->len, puri_parsed))
+  {
+  LM_ERR ("%sInvalid URI (%.*s)!", pfncname, STR_FMT (puri));
+  return -1;
+  }
+
+/**********
+* o find queue
+* o lock calls
+* o find oldest call
+**********/
+
+int nq_idx = find_queue (pqname);
+if (nq_idx == -1)
+  { return -1; }
+if (!mohq_lock_set (pmod_data->pcall_lock, 1, 200))
+  {
+  LM_ERR ("%sUnable to lock calls!", 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 in queue?
+  * o check age
+  **********/
+
+  pcall = &pmod_data->pcall_lst [ncall_idx];
+  if (!pcall->call_active)
+    { continue; }
+  if (pcall->pmohq->mohq_id != mohq_id)
+    { continue; }
+  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)", 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)!", 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!", pfncname);
+  return -1;
+  }
+to_body_t *pto_body = get_to (pmsg);
+if (pto_body->tag_value.len)
+  {
+  LM_ERR ("%sNot a first INVITE message!", pfncname);
+  return -1;
+  }
+str pqname [1];
+if (!pqueue)
+  {
+  LM_ERR ("%sParameters missing!", pfncname);
+  return -1;
+  }
+if (fixup_get_svalue (pmsg, (gparam_p)pqueue, pqname))
+  {
+  LM_ERR ("%sInvalid queue name!", pfncname);
+  return -1;
+  }
+
+/**********
+* o find queue
+* o change RURI
+* o relay message
+**********/
+
+int nq_idx = find_queue (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!", 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!", pfncname);
+  return -1;
+  }
+return 1;
+}

+ 40 - 0
modules/mohqueue/mohq_funcs.h

@@ -0,0 +1,40 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Robert Boisvert
+ *
+ * This file is part of the mohqueue module for sip-router, 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#ifndef MOHQ_FUNCS_H
+#define MOHQ_FUNCS_H
+
+/**********
+* module function declarations
+**********/
+
+rtpmap **find_MOH (char *, char *);
+struct mi_root *mi_debug (struct mi_root *, void *);
+struct mi_root *mi_drop_call (struct mi_root *, void *);
+int mohq_count (sip_msg_t *, char *, pv_spec_t *);
+void mohq_debug (mohq_lst *, char *, ...);
+int mohq_process (sip_msg_t *);
+int mohq_retrieve (sip_msg_t *, char *, char *);
+int mohq_send (sip_msg_t *, char *);
+
+#endif /* MOHQ_FUNCS_H */

+ 199 - 0
modules/mohqueue/mohq_locks.c

@@ -0,0 +1,199 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Robert Boisvert
+ *
+ * This file is part of the mohqueue module for sip-router, 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include "mohq.h"
+#include "mohq_locks.h"
+
+/**********
+* external functions
+**********/
+
+/**********
+* Change Lock
+*
+* INPUT:
+*   Arg (1) = lock pointer
+*   Arg (2) = exclusive flag
+* OUTPUT: 0 if failed
+**********/
+
+int mohq_lock_change (mohq_lock *plock, int bexcl)
+
+{
+/**********
+* o lock memory
+* o check set type
+* o unlock memory
+**********/
+
+int nret = 0;
+lock_get (plock->plock);
+if (bexcl)
+  {
+  if (plock->lock_cnt == 1)
+    {
+    plock->lock_cnt = -1;
+    nret = 1;
+    }
+  }
+else
+  {
+  if (plock->lock_cnt == -1)
+    {
+    plock->lock_cnt = 1;
+    nret = 1;
+    }
+  }
+lock_release (plock->plock);
+return nret;
+}
+
+/**********
+* Destroy Lock Record
+*
+* INPUT:
+*   Arg (1) = lock pointer
+* OUTPUT: none
+**********/
+
+void mohq_lock_destroy (mohq_lock *plock)
+
+{
+lock_destroy (plock->plock);
+lock_dealloc (plock->plock);
+return;
+}
+
+/**********
+* Init Lock Record
+*
+* INPUT:
+*   Arg (1) = lock pointer
+* OUTPUT: 0 if failed
+**********/
+
+int mohq_lock_init (mohq_lock *plock)
+
+{
+/**********
+* alloc memory and initialize
+**********/
+
+char *pfncname = "mohq_lock_init: ";
+plock->plock = lock_alloc ();
+if (!plock->plock)
+  {
+  LM_ERR ("%sUnable to allocate lock memory!", pfncname);
+  return 0;
+  }
+if (!lock_init (plock->plock))
+  {
+  LM_ERR ("%sUnable to init lock!", pfncname);
+  lock_dealloc (plock->plock);
+  return 0;
+  }
+plock->lock_cnt = 0;
+return -1;
+}
+
+/**********
+* Release Lock
+*
+* INPUT:
+*   Arg (1) = lock pointer
+* OUTPUT: none
+**********/
+
+void mohq_lock_release (mohq_lock *plock)
+
+{
+/**********
+* o lock memory
+* o reduce count
+* o unlock memory
+**********/
+
+lock_get (plock->plock);
+switch (plock->lock_cnt)
+  {
+  case -1:
+    plock->lock_cnt = 0;
+    break;
+  case 0:
+    LM_WARN ("mohq_lock_release: Lock was not set");
+    break;
+  default:
+    plock->lock_cnt--;
+    break;
+  }
+lock_release (plock->plock);
+return;
+}
+
+/**********
+* Set Lock
+*
+* INPUT:
+*   Arg (1) = lock pointer
+*   Arg (2) = exclusive flag
+*   Arg (3) = milliseconds to try; 0 = try once
+* OUTPUT: 0 if failed
+**********/
+
+int mohq_lock_set (mohq_lock *plock, int bexcl, int nms_cnt)
+
+{
+int nret = 0;
+do
+  {
+  /**********
+  * o lock memory
+  * o check set type
+  * o unlock memory
+  * o sleep if failed
+  **********/
+
+  lock_get (plock->plock);
+  if (bexcl)
+    {
+    if (!plock->lock_cnt)
+      {
+      plock->lock_cnt = -1;
+      nret = 1;
+      }
+    }
+  else
+    {
+    if (plock->lock_cnt != -1)
+      {
+      plock->lock_cnt++;
+      nret = 1;
+      }
+    }
+  lock_release (plock->plock);
+  if (!nret)
+    { usleep (1); }
+  }
+while (!nret && --nms_cnt >= 0);
+return nret;
+}

+ 49 - 0
modules/mohqueue/mohq_locks.h

@@ -0,0 +1,49 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2013 Robert Boisvert
+ *
+ * This file is part of the mohqueue module for sip-router, 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#ifndef MOHQ_LOCKS_H
+#define MOHQ_LOCKS_H
+
+#include "../../locking.h"
+
+/**********
+* lock record
+**********/
+
+typedef struct
+  {
+  gen_lock_t *plock;
+  int lock_cnt;
+  } mohq_lock;
+
+/**********
+* function declarations
+**********/
+
+int mohq_lock_change (mohq_lock *, int);
+void mohq_lock_destroy (mohq_lock *);
+int mohq_lock_init (mohq_lock *);
+void mohq_lock_release (mohq_lock *);
+int mohq_lock_set (mohq_lock *, int, int);
+
+#endif /* MOHQ_LOCKS_H */