[Freeswitch-svn] [commit] r13997 - in freeswitch/trunk: contrib/jwehle/system25/park contrib/jwehle/system25/vmail scripts/s25vmail

FreeSWITCH SVN brian at freeswitch.org
Fri Jun 26 17:33:30 PDT 2009


Author: brian
Date: Fri Jun 26 19:33:29 2009
New Revision: 13997

Log:
 more clean up


Added:
   freeswitch/trunk/contrib/jwehle/system25/park/README
   freeswitch/trunk/contrib/jwehle/system25/park/s25park.js
   freeswitch/trunk/contrib/jwehle/system25/vmail/README
   freeswitch/trunk/contrib/jwehle/system25/vmail/s25vmail.js
   freeswitch/trunk/contrib/jwehle/system25/vmail/s25vmail_mwi.c
Removed:
   freeswitch/trunk/scripts/s25vmail/

Added: freeswitch/trunk/contrib/jwehle/system25/park/README
==============================================================================
--- (empty file)
+++ freeswitch/trunk/contrib/jwehle/system25/park/README	Fri Jun 26 19:33:29 2009
@@ -0,0 +1,72 @@
+This directory contains software for configuring FreeSWITCH
+to provide AT&T (aka Lucent aka Avaya) System 25 PBX compatible
+park and pickup park functions.  Specifically:
+
+  a) Putting a call on hold and then dialing *5 will park the
+     call on your phone.
+
+  b) Dialing *8 followed by an extension will pickup a call parked
+     on that extension.
+
+as a bonus:
+
+  c) Doing a blind transfer of a call to *5 will park the call
+     on your phone.
+
+  d) Doing a blind transfer of a call to *5 followed by an extension
+     will park the call on that extension.
+
+  e) Dialing *8 without an extension will prompt for an extension.
+
+s25park.js goes into the FreeSWITCH scripts directory.
+
+Configuration fragments look something like:
+
+  conf/dialplan/default.xml
+
+    <extension name="system25_park">
+      <condition field="source" expression="mod_sofia"/>
+      <condition field="destination_number" expression="^\*5$"/>
+      <condition field="${sip_h_Referred-By}" expression="^<sip:([0-9]{4})@.*$">
+        <action application="transfer" data="*5$1"/>
+        <anti-action application="javascript" data="s25park.js"/>
+      </condition>
+    </extension>
+
+    <extension name="system25_park_on_extension">
+      <condition field="destination_number" expression="^\*5([0-9]{4})$">
+        <action application="set" data="fifo_music=$${hold_music}"/>
+        <action application="set" data="fifo_orbit_exten=$1:120"/>
+        <action application="fifo" data="$1@$${domain} in"/>
+      </condition>
+    </extension>
+
+    <extension name="system25_pickup">
+      <condition field="destination_number" expression="^\*8$">
+        <action application="answer"/>
+        <action application="sleep" data="1"/>
+        <action application="read" data="3 5 $${base_dir}/sounds/en/us/callie/ivr/8000/ivr-enter_ext.wav ext 1000 #"/>
+        <action application="transfer" data="*8${ext}"/>
+      </condition>
+    </extension>
+
+    <extension name="system25_pickup_from_extension">
+      <condition field="destination_number" expression="^\*8[0-9]{3,4}$"/>
+      <condition field="destination_number" expression="^\*8([0-9]{4})$">
+        <action application="fifo" data="$1@$${domain} out nowait"/>
+        <anti-action application="bridge" data="openzap/5/a/${destination_number}"/>
+      </condition>
+    </extension>
+
+The system25 park and pickup dialplan patterns are designed
+to only consider four digit extensions for local parking.
+"system25_pickup_from_extension" recognizes three digit
+extensions as being parked on a foreign PBX ... modify
+as appropriate for your installation.
+
+Be aware that the default dialplan contains an extension
+called "group-intercept" which needs to be commented out
+in order for "system25_pickup" to work since they both
+match *8.
+
+Tested using FreeSWITCH SVN 13769 running on FreeBSD 6.4.

Added: freeswitch/trunk/contrib/jwehle/system25/park/s25park.js
==============================================================================
--- (empty file)
+++ freeswitch/trunk/contrib/jwehle/system25/park/s25park.js	Fri Jun 26 19:33:29 2009
@@ -0,0 +1,178 @@
+/*
+ *  File:    s25park.js
+ *  Purpose: Implement AT&T System 25 PBX style parking.
+ *  Machine:                      OS:
+ *  Author:  John Wehle           Date: June 9, 2009
+ */
+
+/*
+ *  Copyright (c)  2009  Feith Systems and Software, Inc.
+ *                      All Rights Reserved
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 
+ * * Neither the name of the original author; nor the names of any contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * 
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+/* RE to sanity check that the caller id is a valid extension */
+var extRE = /^[0-9]{3,4}$/g;
+
+
+var dtmf_digits;
+
+function on_dtmf (session, type, obj, arg)
+  {
+
+  if (type == "dtmf") {
+    dtmf_digits += obj.digit;
+    return false;
+    }
+
+  return true;
+  }
+
+
+function normalize_channel_name (name, direction, ip_addr)
+  {
+  var re = /^sofia\//g;
+  var length = name.search (re);
+  var new_name = name;
+  
+  if (length == -1)
+    return new_name;
+
+  if (direction == "inbound") {
+    re = /@.*$/g;
+
+    new_name = name.replace (re, "@" + ip_addr);
+    }
+  else if (direction == "outbound") {
+    re = /\/sip:(.*@[^:]*):.*$/g;
+
+    new_name = name.replace (re, "/$1");
+    }
+
+  return new_name;
+  }
+
+
+session.answer ();
+
+session.execute ("sleep", "1000");
+
+/*
+ * Figure out the normalized form of the requester's channel name.
+ */
+
+var requester_channel_name = normalize_channel_name (
+  session.getVariable ("channel_name"), "inbound",
+  session.getVariable ("network_addr"));
+
+/*
+ * Find the uuid for a call on the requester's phone.
+ */
+
+var channels = apiExecute ("show", "channels as xml");
+var re = /\s+$/g;
+var length = channels.search (re);
+
+if (length == -1)
+  length = channels.length;
+
+channels = channels.substring (0, length);
+
+var xchannels = new XML (channels);
+var our_uuid = session.getVariable ("uuid");
+var requester_uuid = "";
+
+for each (var channel in xchannels.row) {
+  if (channel.uuid.toString () == our_uuid)
+    continue;
+
+  var channel_name =  normalize_channel_name (channel.name.toString (),
+             channel.direction.toString (), channel.ip_addr.toString ());
+
+  if (channel_name == requester_channel_name) {
+    requester_uuid = channel.uuid.toString ();
+    break;
+    }
+  }
+
+if (requester_uuid == "") {
+  session.sayPhrase ("voicemail_invalid_extension", "#", "", on_dtmf, "");
+  session.hangup ();
+  exit ();
+  }
+
+/*
+ * Find the peer uuid.
+ */
+
+var udump = apiExecute ("uuid_dump", requester_uuid + " xml");
+var re = /\s+$/g;
+var length = udump.search (re);
+
+if (length == -1)
+  length = udump.length;
+
+udump = udump.substring (0, length);
+
+var xudump = new XML (udump);
+var uuid = xudump.headers['Other-Leg-Unique-ID'].toString ();
+
+if (uuid == "") {
+  session.sayPhrase ("voicemail_invalid_extension", "#", "", on_dtmf, "");
+  session.hangup ();
+  exit ();
+  }
+
+var requester_id_number = session.getVariable ("caller_id_number");
+
+if (requester_id_number.search (extRE) == -1) {
+  session.sayPhrase ("voicemail_invalid_extension", "#", "", on_dtmf, "");
+  session.hangup ();
+  exit ();
+  }
+
+apiExecute ("uuid_setvar", uuid + " hangup_after_bridge false");
+apiExecute ("uuid_transfer", uuid + " *5" + requester_id_number + " XML default");
+
+/*
+ * Provide confirmation beeps followed by some silence.
+ */
+
+var confirmation = "tone_stream://L=3;%(100,100,350,440)";
+
+session.execute ("playback", confirmation);
+
+var i;
+
+for (i = 0; session.ready () && i < 100; i++)
+  session.execute("sleep", "100");
+
+exit ();

Added: freeswitch/trunk/contrib/jwehle/system25/vmail/README
==============================================================================
--- (empty file)
+++ freeswitch/trunk/contrib/jwehle/system25/vmail/README	Fri Jun 26 19:33:29 2009
@@ -0,0 +1,63 @@
+This directory contains software used for interfacing the
+FreeSWITCH voicemail application with the AT&T (aka Lucent
+aka Avaya) System 25 PBX.  It's possible that System 75
+and Definity PBXs may also work.
+
+s25vmail.js goes into the FreeSWITCH scripts directory.
+s25vmail_mwi.c should be compiled and the resulting binary
+put into the FreeSWITCH bin directory.  Modify the FreeSWITCH
+rc.d script to also start / stop s25vmail_mwi.
+
+Configuration fragments look something like:
+
+  conf/openzap.conf:
+
+    [span zt]
+    ; A204DX
+    name => OpenZAP
+    dtmf_hangup = ##99
+
+    number => 551
+    fxo-channel => 49
+
+    number => 552
+    fxo-channel => 50
+
+    number => 553
+    fxo-channel => 51
+
+    number => 554
+    fxo-channel => 52
+
+  conf/dialplan/default.xml
+
+    <extension name="system25_vmail">
+      <condition field="destination_number" expression="^(55[1-4])$">
+        <action application="javascript" data="s25vmail.js"/>
+      </condition>
+    </extension>
+
+Tested using FreeSWITCH SVN 10428 running on FreeBSD 6.3
+with a Sangoma A200DX Analog Series w/ Echo Cancellation
+ports card containing 2 FXO modules.
+
+Note that the PBX is * very * sensitive to how long it takes
+for the line to be hung up after it sends the DTMF hangup
+command.  Failure to apply the following patch will cause
+the PBX to occasionally believe that some vmail lines were
+off hook for too long and are therfore out of service.
+
+Index: src/zap_io.c
+===================================================================
+--- src/zap_io.c	(revision 745)
++++ src/zap_io.c	(working copy)
+@@ -1728,7 +1728,8 @@
+ 			zchan->dtmf_hangup_buf[zchan->span->dtmf_hangup_len - 1] = *p;
+ 			if (!strcmp(zchan->dtmf_hangup_buf, zchan->span->dtmf_hangup)) {
+ 				zap_log(ZAP_LOG_DEBUG, "DTMF hangup detected.\n");
+-				zap_set_state_locked(zchan, ZAP_CHANNEL_STATE_HANGUP);
++				zchan->caller_data.hangup_cause = ZAP_CAUSE_NORMAL_CLEARING;
++				zap_set_state_locked(zchan, ZAP_CHANNEL_STATE_DOWN);
+ 				break;
+ 			}
+ 		}

Added: freeswitch/trunk/contrib/jwehle/system25/vmail/s25vmail.js
==============================================================================
--- (empty file)
+++ freeswitch/trunk/contrib/jwehle/system25/vmail/s25vmail.js	Fri Jun 26 19:33:29 2009
@@ -0,0 +1,236 @@
+/*
+ *  File:    s25vmail.js
+ *  Purpose: Invoke voicemail based on AT&T System 25 PBX voicemail mode codes
+ *  Machine:                      OS:
+ *  Author:  John Wehle           Date: June 24, 2008
+ *
+ *  The message waiting indicator is handled by a separate program.
+ */
+
+/*
+ *  Copyright (c)  2008  Feith Systems and Software, Inc.
+ *                      All Rights Reserved
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 
+ * * Neither the name of the original author; nor the names of any contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * 
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+var id_digits_required = 3;
+
+var digitTimeOut = 3000;
+var interDigitTimeOut = 1000;
+var absoluteTimeOut = 10000;
+
+
+var dtmf_digits = "";
+ 
+function on_dtmf (session, type, obj, arg)
+  {
+
+  if (type == "dtmf") {
+    dtmf_digits += obj.digit;
+    }
+
+  return true;
+  }
+
+
+function prompt_for_id ()
+  {
+  var dto;
+  var id;
+  var index;
+  var repeat;
+  
+  dtmf_digits = "";
+  id = "";
+  repeat = 0;
+
+  while (session.ready () && repeat < 3) {
+    session.flushDigits ();
+  
+    /* play phrase - if digit keyed while playing callback will catch them*/
+    session.sayPhrase ("voicemail_enter_id", "#", "", on_dtmf, "");
+
+    if (! session.ready ())
+      return "";
+
+    id = dtmf_digits;
+
+    if (id.indexOf ('#') == -1) {
+      dto = digitTimeOut;
+      if (dtmf_digits.length != 0)
+        dto = interDigitTimeOut;
+      dtmf_digits = session.getDigits (5, '#', dto,
+                                       interDigitTimeOut, absoluteTimeOut);
+      id += dtmf_digits;
+      id += '#';
+      }
+
+    /* a valid id must meet the minimum length requirements */
+    if ((index = id.indexOf ('#')) >= id_digits_required) {
+      id = id.substring (0,index);
+      break;
+      }
+
+    dtmf_digits = "";
+    id = "";
+    repeat++;
+    }
+
+  return id;
+  }
+
+
+var start = "";
+var mode = "";
+var from = "";
+var to = "";
+
+var domain = session.getVariable ("domain");
+
+session.answer ();
+
+start = session.getDigits (1, '', digitTimeOut,
+                           interDigitTimeOut, absoluteTimeOut);
+
+if (start != "#") {
+  var destination_number = session.getVariable ("destination_number");
+
+  console_log ("err", destination_number + " received an invalid VMAIL start code from PBX\n");
+  if (session.ready ())
+    session.sayPhrase ("voicemail_goodbye", "#", "", on_dtmf, "");
+  else
+    console_log ("err", "Possibly due to early hangup from PBX\n");
+  session.hangup ();
+  exit();
+  }
+
+mode = session.getDigits (5, '#', digitTimeOut,
+                          interDigitTimeOut, absoluteTimeOut);
+
+from = session.getDigits (5, '#', digitTimeOut,
+                          interDigitTimeOut, absoluteTimeOut);
+
+to = session.getDigits (5, '#', digitTimeOut,
+                        interDigitTimeOut, absoluteTimeOut);
+
+session.execute("sleep", "1000");
+
+// Verify that the proper parameters are present
+switch (mode) {
+
+  // Direct Inside Access
+  case "00":
+    if (isNaN (parseInt (from, 10))) {
+      console_log ("err", "Invalid VMAIL calling PDC from PBX\n");
+      break;
+      }
+
+    session.setVariable ("voicemail_authorized", "false");
+    session.execute ("voicemail", "check default " + domain + " " + from);
+    break;
+
+  // Direct Dial Access
+  case "01":
+    from = prompt_for_id ();
+
+    if (! session.ready ()) {
+      session.hangup ();
+      exit();
+      }
+
+    if (isNaN (parseInt (from, 10))) {
+      console_log ("err", "Invalid VMAIL mailbox from caller\n");
+      break;
+      }
+
+    session.setVariable ("voicemail_authorized", "false");
+    session.execute ("voicemail", "check default " + domain + " " + from);
+    break;
+
+  // Coverage - caller is inside
+  case "02":
+    if (isNaN (parseInt (from, 10)) || isNaN (parseInt (to, 10))) {
+      console_log ("err", "Invalid VMAIL calling or called PDC from PBX\n");
+      break;
+      }
+
+    session.setVariable ("effective_caller_id_name", "inside caller");
+    session.setVariable ("effective_caller_id_number", from);
+
+    session.execute ("voicemail", "default " + domain + " " + to);
+    break;
+
+  // Coverage - caller is dial
+  case "03":
+    if (isNaN (parseInt (to, 10))) {
+      console_log ("err", "Invalid VMAIL called PDC from PBX\n");
+      break;
+      }
+
+    session.setVariable ("effective_caller_id_name", "outside caller");
+    session.setVariable ("effective_caller_id_number", "Unknown");
+
+    session.execute ("voicemail", "default " + domain + " " + to);
+    break;
+
+  // Coverage - not yet defined
+  case "04":
+    break;
+
+  // Leave Word Calling
+  case "05":
+    if (isNaN (parseInt (from, 10)) || isNaN (parseInt (to, 10))) {
+      console_log ("err", "Invalid VMAIL calling or called PDC from PBX\n");
+      break;
+      }
+    break;
+
+  // Refresh MW lamps
+  case "06":
+    break;
+
+  // Voice Port failed to answer
+  case "08":
+    if (isNaN (parseInt (to, 10))) {
+      console_log ("err", "Invalid VMAIL PDC from PBX\n");
+      break;
+      }
+
+    console_log ("err", "PBX reports problem with VMAIL PDC " + to + "\n");
+    break;
+
+  // Unknown
+  default:
+    console_log ("err", "Invalid VMAIL mode code from PBX\n");
+    break;
+  }
+
+exit();

Added: freeswitch/trunk/contrib/jwehle/system25/vmail/s25vmail_mwi.c
==============================================================================
--- (empty file)
+++ freeswitch/trunk/contrib/jwehle/system25/vmail/s25vmail_mwi.c	Fri Jun 26 19:33:29 2009
@@ -0,0 +1,983 @@
+/*
+ *  File:    s25vmail_mwi.c
+ *  Purpose: Send AT&T System 25 PBX MWI DTMF based on MWI events
+ *  Machine:                      OS:
+ *  Author:  John Wehle           Date: July 24, 2008
+ *
+ *  Tested using a Zyxel U90e configured using:
+ *
+ *    at OK at&f OK at&d3&y2q2 OK ats0=0s2=255s15.7=0s18=4s35.1=0 OK
+ *    ats38.3=1s42.3=1s42.6=1 OK atl0 OK at&w OK at&v
+ *
+ *  though just about any modem should work.  Preferred settings are
+ *
+ *    DTR OFF causes hangup and reset from profile 0
+ *    RTS / CTS flow control
+ *    allow abort during modem handshake
+ *    auto answer off
+ *    ring message off
+ */
+
+/*
+ *  Copyright (c)  2008  Feith Systems and Software, Inc.
+ *                      All Rights Reserved
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 
+ * * Neither the name of the original author; nor the names of any contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * 
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include <ctype.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <memory.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <termios.h>
+
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <sys/param.h>
+#include <unistd.h>
+
+
+#define LOGFILE                    "/var/log/s25vmail_mwi.log"
+
+
+static const char *MyName = "s25vmail_mwi";
+
+static int daimon = 0;
+static int error_msg_throttle = 0;
+static volatile int shutdown_server = 0;
+
+
+static void
+debugmsg (const char *fmt, ...)
+  {
+  char message[256];
+  va_list args;
+
+  if (daimon)
+    return;
+
+  va_start (args, fmt);
+  vsprintf (message, fmt, args);
+  va_end (args);
+
+  fprintf (stderr, "%s: %s", MyName, message);
+  if ( !strchr (message, '\n'))
+    fprintf (stderr, "\n");
+  fflush (stderr);
+  }
+
+
+static void
+errmsg (const char *fmt, ...)
+  {
+  char time_stamp[256];
+  struct tm *tmp;
+  time_t now;
+  va_list args;
+
+  if (! daimon) {
+    fprintf (stderr, "%s: ", MyName);
+
+    va_start (args, fmt);
+    vfprintf (stderr, fmt, args);
+    va_end (args);
+
+    if (! strchr (fmt, '\n'))
+      fputc ('\n', stderr);
+
+    fflush (stderr);
+    return;
+    }
+
+  if (error_msg_throttle)
+    return;
+
+  time (&now);
+
+  if ( !(tmp = localtime (&now)) ) {
+    fprintf (stderr, "%s: errmsg -- localtime failed.\n", MyName);
+    perror (MyName);
+    fflush (stderr);
+    return;
+    }
+
+  strftime (time_stamp, sizeof (time_stamp), "%b %d %H:%M:%S", tmp);
+  fprintf (stderr, "%s %s[%d]: ", time_stamp, MyName, (int)getpid ());
+
+  va_start (args, fmt);
+  vfprintf (stderr, fmt, args);
+  va_end (args);
+
+  if (! strchr (fmt, '\n'))
+    fputc ('\n', stderr);
+
+  fflush (stderr);
+  }
+
+
+static void
+catch_signal ()
+  {
+
+  shutdown_server = 1;
+  }
+
+
+static void
+daemonize()
+  {
+
+#ifdef SIGTSTP
+  (void)signal(SIGTSTP, SIG_IGN);
+#endif
+#ifdef SIGTTIN
+  (void)signal(SIGTTIN, SIG_IGN);
+#endif
+#ifdef SIGTTOU
+  (void)signal(SIGTTOU, SIG_IGN);
+#endif
+
+  switch (fork ()) {
+    case 0:
+      break;
+
+    case -1:
+      fprintf (stderr, "%s: daemonize -- fork failed.", MyName);
+      perror (MyName);
+      exit (1);
+    /* NOTREACHED */
+      break;
+
+    default:
+      exit (0);
+    /* NOTREACHED */
+      break;
+    }
+
+  setsid();
+
+  close (0);
+  close (1);
+  close (2);
+
+  (void)open ("/dev/null", O_RDWR);
+  (void)open ("/dev/null", O_RDWR);
+  (void)open (LOGFILE, O_WRONLY | O_APPEND | O_CREAT, 0644);
+
+  daimon = 1;
+  }
+
+
+static void
+install_signal_handlers ()
+  {
+  struct sigaction act;
+
+  memset (&act, '\0', sizeof (act));
+
+  act.sa_handler = catch_signal;
+  sigemptyset (&act.sa_mask);
+  act.sa_flags = 0;
+
+  if (signal (SIGHUP, SIG_IGN) != SIG_IGN)
+    sigaction (SIGHUP, &act, NULL);
+  if (signal (SIGINT, SIG_IGN) != SIG_IGN)
+    sigaction (SIGINT, &act, NULL);
+  (void)sigaction (SIGTERM, &act, NULL);
+  }
+
+
+static int
+connect_to_service (const char *hostname, const char *port)
+  {
+  int sock;
+  struct hostent *hp;
+  struct in_addr address;
+  struct servent *servp;
+  struct sockaddr_in sin;
+  
+  if ((sock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
+    char *errstr = strerror (errno);
+
+    errmsg ("socket failed\n");
+    errmsg (errstr);
+    return -1;
+    }
+
+  memset (&sin, 0, sizeof (sin));
+
+  if (isalpha (hostname[0])) {
+    if ( !(hp = gethostbyname (hostname))) {
+      char *errstr = strerror (errno);
+
+      errmsg ("gethostbyname failed\n");
+      errmsg (errstr);
+      close (sock);
+      return -1;
+      }
+    if (hp->h_addrtype != AF_INET) {
+      errmsg ("gethostbyname returned unsupported family\n");
+      close (sock);
+      return -1;
+      }
+    memcpy (&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
+    }
+  else {
+    address.s_addr = inet_addr (hostname);
+
+    if ((long)address.s_addr == -1) {
+      char *errstr = strerror (errno);
+
+      errmsg ("inet_addr failed\n");
+      errmsg (errstr);
+      close (sock);
+      return -1;
+      }
+    sin.sin_addr.s_addr = address.s_addr;
+    }
+
+  if (isalpha (*port)) {
+    if ( !(servp = getservbyname(port, "tcp"))) {
+      char *errstr = strerror (errno);
+
+      errmsg ("getservbyname failed\n");
+      errmsg (errstr);
+      close (sock);
+      return -1;
+      }
+    sin.sin_port = servp->s_port;
+    }
+  else
+    sin.sin_port = htons ((unsigned short)atoi (port));
+
+  sin.sin_family = AF_INET;
+
+  if (connect (sock, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
+    char *errstr = strerror (errno);
+
+    errmsg ("connect failed\n");
+    errmsg (errstr);
+    close (sock);
+    return -1;
+    }
+
+  debugmsg ("Connected to service\n");
+
+  return sock;
+  }
+
+
+static ssize_t
+read_line (int fd, char *buf, size_t buf_len)
+  {
+  size_t l;
+  ssize_t nbytes_read;
+
+  l = 0;
+
+  for ( ; ; ) {
+    nbytes_read = read (fd, &buf[l], 1);
+
+    if (nbytes_read < 0) {
+      char *errstr = strerror (errno);
+
+      errmsg ("read failed in middle of line\n");
+      errmsg (errstr);
+      return -1;
+      }
+
+    if (nbytes_read == 0) {
+      if (l)
+        errmsg ("EOF in middle of line\n");
+      return l ? -1 : 0;
+      }
+
+    if (buf[l] == '\n') {
+      while (l && buf[l - 1] == '\r')
+        l--;
+      buf[l++] = '\0';
+      break;
+      }
+
+    l++;
+
+    if (l == buf_len) {
+      errmsg ("line too long\n");
+      return -1;
+      }
+    }
+
+  return l;
+  }
+
+
+static int
+read_trailing_newline(int fd)
+  {
+  char c;
+  ssize_t nbytes_read;
+
+  nbytes_read = read (fd, &c, 1);
+
+  if (nbytes_read < 0) {
+    char *errstr = strerror (errno);
+
+    errmsg ("read failed in trailing newline\n");
+    errmsg (errstr);
+    return -1;
+    }
+
+  if (nbytes_read == 0) {
+    errmsg ("EOF in trailing newline\n");
+    return -1;
+    }
+
+  if (c != '\n') {
+    errmsg ("missing trailing newline\n");
+    return -1;
+    }
+
+  return 0;
+  }
+
+
+static char *
+retrieve_message (int fd)
+  {
+  char cl_buf[64];
+  char ct_buf[64];
+  char *h;
+  char *m;
+  ssize_t cl;
+  ssize_t nbytes_read;
+  size_t l;
+  size_t nbytes_to_read;
+
+  if (shutdown_server)
+    return NULL;
+
+  /*
+   * Read / parse Content-Length and Content-Type.
+   */
+
+  nbytes_read = read_line (fd, cl_buf, sizeof (cl_buf));
+
+  if (nbytes_read < 0) {
+    errmsg ("read_line failed\n");
+    return NULL;
+    }
+
+  if (nbytes_read == 0) {
+
+    /*
+     * EOF
+     */
+
+    return NULL;
+    }
+
+  nbytes_read = read_line (fd, ct_buf, sizeof (ct_buf));
+
+  if (nbytes_read < 0) {
+    errmsg ("read_line failed\n");
+    return NULL;
+    }
+
+  if (nbytes_read == 0) {
+    errmsg ("EOF in middle of headers\n");
+    return NULL;
+    }
+
+  h = "Content-Length: ";
+  l = strlen (h);
+
+  if (strncmp (cl_buf, h, l) != 0) {
+
+    /*
+     * If the message header doesn't being with Content-Length,
+     * then it needs to be a Content-Type we understand.
+     */
+
+    h = "Content-Type: ";
+    l = strlen (h);
+
+    if (strncmp (cl_buf, h, l) != 0) {
+      errmsg ("missing Content-Type\n");
+      return NULL;
+      }
+
+    if (strcmp (&cl_buf[l], "auth/request") != 0
+        && strcmp (&cl_buf[l], "command/reply") != 0) {
+      errmsg ("Unsupported Content-Type\n");
+      return NULL;
+      }
+
+    if (ct_buf[0])
+      if (read_trailing_newline (fd) < 0) {
+        return NULL;
+        }
+
+    m = malloc (strlen (cl_buf) + 1 + strlen (ct_buf) + 1 + 1);
+
+    if (! m) {
+      char *errstr = strerror (errno);
+
+      errmsg ("malloc failed\n");
+      errmsg (errstr);
+      return NULL;
+      }
+
+    sprintf (m, "%s\n%s\n", cl_buf, ct_buf);
+
+    return m;
+    }
+
+  cl = atoi (&cl_buf[l]);
+
+  if (cl <= 0) {
+    errmsg ("Content-Length must be greater than zero\n");
+    return NULL;
+    }
+
+  h = "Content-Type: ";
+  l = strlen (h);
+
+  if (strncmp (ct_buf, h, l) != 0) {
+    errmsg ("missing Content-Type\n");
+    return NULL;
+    }
+
+  if (strcmp (&ct_buf[l], "text/event-plain") != 0) {
+    errmsg ("Unsupported Content-Type\n");
+    return NULL;
+    }
+
+  if (read_trailing_newline (fd) < 0) {
+    return NULL;
+    }
+
+  /*
+   * Read the event.
+   */
+
+  m = malloc (cl);
+
+  if (! m) {
+    char *errstr = strerror (errno);
+
+    errmsg ("malloc failed\n");
+    errmsg (errstr);
+    return NULL;
+    }
+
+  for (nbytes_to_read = cl; nbytes_to_read; nbytes_to_read -= nbytes_read) {
+    nbytes_read = read (fd, m + (cl - nbytes_to_read), nbytes_to_read);
+
+    if (nbytes_read < 0) {
+      char *errstr = strerror (errno);
+
+      errmsg ("read failed in middle of message\n");
+      errmsg (errstr);
+      free (m);
+      return NULL;
+      }
+
+    if (nbytes_read == 0) {
+      errmsg ("EOF in middle of message\n");
+      free (m);
+      return NULL;
+      }
+    }
+
+  if (m[cl - 2] != '\n' || m[cl - 1] != '\n') {
+    errmsg ("Message is missing trailing newlines\n");
+    free (m);
+    return NULL;
+    }
+
+  return m;
+  }
+
+
+static int
+send_password (int fd, const char *passwd)
+  {
+  char *h;
+  char *last;
+  char *m;
+  char *p;
+  int l;
+  size_t ml;
+
+  m = retrieve_message (fd);
+  if (! m)
+    return -1;
+
+  p = strtok_r (m, "\n", &last);
+
+  h = "Content-Type: auth/request";
+
+  if (strcmp (p, h) != 0) {
+    errmsg ("Content-Type wasn't auth/request\n");
+    free (m);
+    return -1;
+    }
+
+  free (m);
+
+  l = snprintf (NULL, 0, "auth %s\n\n", passwd);
+  if (l <= 0) {
+    errmsg ("snprintf failed\n");
+    return -1;
+    }
+  l++;
+
+  m = malloc (l);
+  if (! m) {
+    char *errstr = strerror (errno);
+
+    errmsg ("malloc failed\n");
+    errmsg (errstr);
+    return -1;
+    }
+
+  ml = snprintf (m, l, "auth %s\n\n", passwd);
+  if ((ml + 1) != l) {
+    errmsg ("snprintf failed\n");
+    free (m);
+    return -1;
+    }
+
+  if (write (fd, m, ml) != ml) {
+    char *errstr = strerror (errno);
+
+    errmsg ("write failed\n");
+    errmsg (errstr);
+    free (m);
+    return -1;
+    }
+
+  m = retrieve_message (fd);
+  if (! m )
+    return -1;
+
+  p = strtok_r (m, "\n", &last);
+
+  h = "Content-Type: command/reply";
+
+  if (! p || strcmp (p, h) != 0) {
+    errmsg ("Content-Type wasn't command/reply\n");
+    free (m);
+    return -1;
+    }
+
+  p = strtok_r (NULL, "\n", &last);
+
+  h = "Reply-Text: +OK accepted";
+
+  if (! p || strcmp (p, h) != 0) {
+    errmsg ("auth wasn't accepted\n");
+    free (m);
+    return -1;
+    }
+
+  free (m);
+
+  debugmsg ("Logged into service\n");
+
+  return 0;
+  }
+
+
+static int
+enable_mwi_event (int fd)
+  {
+  char *h;
+  char *last;
+  char *m;
+  char *p;
+  size_t ml;
+
+  m = "event plain MESSAGE_WAITING\n\n";
+  ml = strlen (m);
+
+  if (write (fd, m, ml) != ml) {
+    char *errstr = strerror (errno);
+
+    errmsg ("write failed\n");
+    errmsg (errstr);
+    return -1;
+    }
+
+  m = retrieve_message (fd);
+  if (! m )
+    return -1;
+
+  p = strtok_r (m, "\n", &last);
+
+  h = "Content-Type: command/reply";
+
+  if (! p || strcmp (p, h) != 0) {
+    errmsg ("Content-Type wasn't command/reply\n");
+    free (m);
+    return -1;
+    }
+
+  p = strtok_r (NULL, "\n", &last);
+
+  h = "Reply-Text: +OK event listener enabled plain";
+
+  if (! p || strcmp (p, h) != 0) {
+    errmsg ("event wasn't enabled\n");
+    free (m);
+    return -1;
+    }
+
+  free (m);
+
+  debugmsg ("Enabled message waiting event\n");
+
+  return 0;
+  }
+
+
+static int
+process_mwi_event (char *m, const char *device)
+  {
+  char cbuf[64];
+  char rbuf[64];
+  char *h;
+  char *last;
+  char *ma;
+  char *mw;
+  char *p;
+  int fd;
+  int mwi_off;
+  int mwi_on;
+  int r;
+  int w;
+  size_t l;
+  ssize_t ml;
+  ssize_t nbytes_read;
+  struct termios tio;
+
+  debugmsg ("Processing MWI event\n");
+
+  ma = NULL;
+  mw = NULL;
+
+  p = m;
+
+  while ( (p = strtok_r (p, "\n", &last)) ) {
+    h = "MWI-Messages-Waiting: ";
+    l = strlen (h);
+
+    if (strncmp (p, h, l) == 0)
+      mw = p + l;
+
+    h = "MWI-Message-Account: ";
+    l = strlen (h);
+
+    if (strncmp (p, h, l) == 0)
+      ma = p + l;
+
+    p = NULL;
+    }
+
+  if (! (ma && mw) ) {
+    errmsg ("message account or message waiting missing\n");
+    return -1;
+    }
+
+  p = strchr (ma, '\n');
+  if (p)
+    *p = '\n';
+
+  p = strchr (mw, '\n');
+  if (p)
+    *p = '\n';
+
+  /*
+   * The account is considered to be a System 25 extension if
+   * it's of the form:
+   *
+   *   numeric_string at host
+   */
+
+  p = strchr (ma, '%');
+  if (! p)
+    p = strchr (ma, '@');
+
+  if (! p || (strncmp (p, "%40", 3) != 0 && strncmp (p, "@", 1) != 0)) {
+    debugmsg ("  %s is not a System 25 extension\n", ma);
+    return 0;
+    }
+
+  *p = '\0';
+
+  for (p = ma; *p; p++)
+    if (! isdigit (*p)) {
+      debugmsg ("  %s is not a System 25 extension\n", ma);
+      return 0;
+      }
+
+  mwi_off = strcasecmp (mw, "no") == 0;
+  mwi_on = strcasecmp (mw, "yes") == 0;
+
+  if (mwi_off == mwi_on) {
+    errmsg ("Unsupported Messages-Waiting\n");
+    return 0;
+    }
+
+  for (r = 0; r < 3; r++) {
+    if ((fd = open (device, O_RDWR)) < 0) {
+      char *errstr = strerror (errno);
+
+      errmsg ("open failed for device node <%s>.\n", device);
+      errmsg (errstr);
+      return -1;
+      }
+
+    cfmakeraw (&tio);
+
+    tio.c_cflag = CS8 | CREAD | HUPCL | CCTS_OFLOW | CRTS_IFLOW;
+
+    tio.c_cc[VMIN] = 0;
+    tio.c_cc[VTIME] = 50;
+
+    cfsetispeed (&tio, B9600);
+    cfsetospeed (&tio, B9600);
+
+    if (tcsetattr (fd, TCSAFLUSH, &tio) < 0) {
+      char *errstr = strerror (errno);
+
+      errmsg ("tcsetattr failed\n");
+      errmsg (errstr);
+      close (fd);
+      return -1;
+      }
+
+    m = "AT";
+    ml = strlen (m);
+
+    if (write (fd, m, ml) != ml
+        || write (fd, "\r\n", 2) != 2) {
+      char *errstr = strerror (errno);
+
+      errmsg ("write failed\n");
+      errmsg (errstr);
+      close (fd);
+      return -1;
+      }
+
+    for (w = 0; w < 2; w++) {
+      nbytes_read = read_line (fd, rbuf, sizeof (rbuf));
+      if (nbytes_read > 0 && (rbuf[0] == '\0' || strcmp (rbuf, m) == 0))
+        continue;
+      break;
+      }
+
+    if (nbytes_read < 0) {
+      errmsg ("read_line failed\n");
+      close (fd);
+      return -1;
+      }
+
+    if (nbytes_read == 0
+        || strcmp (rbuf, "OK") != 0) {
+      errmsg ("modem failed to wake up\n");
+      close (fd);
+      continue;
+      }
+
+    m = cbuf;
+    ml  = snprintf (cbuf, sizeof (cbuf),
+                    "ATDT%s%s", (mwi_on ? "#90" : "#91"),  ma);
+    if (ml <= 0 || ml >= sizeof (cbuf)) {
+      errmsg ("snprintf failed.\n");
+      close (fd);
+      return -1;
+      }
+
+    if (write (fd, m, ml) != ml
+        || write (fd, "\r\n", 2) != 2) {
+      char *errstr = strerror (errno);
+
+      errmsg ("write failed\n");
+      errmsg (errstr);
+      close (fd);
+      return -1;
+      }
+
+    sleep (5);
+
+    if (write (fd, "\r\n", 2) != 2) {
+      char *errstr = strerror (errno);
+
+      errmsg ("write failed\n");
+      errmsg (errstr);
+      close (fd);
+      return -1;
+      }
+
+    for (w = 0; w < 2; w++) {
+      nbytes_read = read_line (fd, rbuf, sizeof (rbuf));
+      if (nbytes_read > 0 && (rbuf[0] == '\0' || strcmp (rbuf, m) == 0))
+        continue;
+      break;
+      }
+
+    if (nbytes_read < 0) {
+      errmsg ("read_line failed\n");
+      close (fd);
+      return -1;
+      }
+
+    if (nbytes_read > 0 && strcmp (rbuf, "NO DIALTONE") == 0) {
+      errmsg ("modem failed to detect dialtone\n");
+      close (fd);
+      return -1;
+      }
+
+    if (nbytes_read == 0
+        || strcmp (rbuf, "NO CARRIER") != 0) {
+      errmsg ("modem failed to update MWI\n");
+      close (fd);
+      continue;
+      }
+
+    close (fd);
+
+    debugmsg ("  message waiting indicator updated for %s\n", ma);
+    return 0;
+    }
+
+  errmsg (" failed to update message waiting indicator for %s\n", ma);
+
+  return -1;
+  }
+
+
+int
+main (int argc, char **argv)
+  {
+  const char *device = "/dev/cuad0";
+  const char *machine = "localhost";
+  const char *port = "8021";
+  const char *passwd = "ClueCon";
+  char *m;
+  int c;
+  int debug;
+  int fd;
+  struct stat statbuf;
+
+  debug = 0;
+
+  while ((c = getopt (argc, argv, "dm:p:w:")) != -1)
+    switch (c) {
+      case 'd':
+        debug = 1;
+        break;
+
+      case 'm':
+        machine = optarg;
+        break;
+
+      case 'p':
+        port = optarg;
+        break;
+
+      case 'w':
+        passwd = optarg;
+        break;
+
+      case 'l':
+        device = optarg;
+        break;
+
+      default:
+        fprintf (stderr,
+          "Usage: %s [-d] [-m machine] [-p port] [-w passwd] [-l device]\n",
+                 MyName);
+        exit(1);
+      /* NOTREACHED */
+        break;
+      }
+
+  if (stat (device, &statbuf) < 0 || ! S_ISCHR (statbuf.st_mode)) {
+    fprintf (stderr, "%s: stat failed for path <%s>\n", MyName, device);
+    fprintf (stderr, "%s: or the path isn't a character special file.\n",
+             MyName);
+    perror (MyName);
+    exit (1);
+    }
+
+  install_signal_handlers ();
+
+  if (! debug)
+    daemonize ();
+
+  while (! shutdown_server) {
+    sleep (5);
+
+    fd = connect_to_service (machine, port);
+    if (fd < 0) {
+      error_msg_throttle = 1;
+      continue;
+      }
+
+    if (send_password (fd, passwd) < 0
+        || enable_mwi_event (fd) < 0) {
+      error_msg_throttle = 1;
+      close (fd);
+      continue;
+      }
+
+    error_msg_throttle = 0;
+
+    while (m = retrieve_message (fd)) {
+      process_mwi_event (m, device);
+      free (m);
+      }
+
+    close (fd);
+    }
+
+  exit (0);
+  }



More information about the Freeswitch-svn mailing list