[Freeswitch-trunk] [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:29 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-trunk
mailing list