aboutsummaryrefslogtreecommitdiffstats
path: root/1.4.23-rc4/res
diff options
context:
space:
mode:
Diffstat (limited to '1.4.23-rc4/res')
-rw-r--r--1.4.23-rc4/res/Makefile39
-rw-r--r--1.4.23-rc4/res/res_adsi.c1132
-rw-r--r--1.4.23-rc4/res/res_agi.c2226
-rw-r--r--1.4.23-rc4/res/res_clioriginate.c175
-rw-r--r--1.4.23-rc4/res/res_config_odbc.c564
-rw-r--r--1.4.23-rc4/res/res_config_pgsql.c842
-rw-r--r--1.4.23-rc4/res/res_convert.c224
-rw-r--r--1.4.23-rc4/res/res_crypto.c631
-rw-r--r--1.4.23-rc4/res/res_features.c2755
-rw-r--r--1.4.23-rc4/res/res_indications.c406
-rw-r--r--1.4.23-rc4/res/res_jabber.c2518
-rw-r--r--1.4.23-rc4/res/res_monitor.c714
-rw-r--r--1.4.23-rc4/res/res_musiconhold.c1480
-rw-r--r--1.4.23-rc4/res/res_odbc.c741
-rw-r--r--1.4.23-rc4/res/res_smdi.c1384
-rw-r--r--1.4.23-rc4/res/res_snmp.c115
-rw-r--r--1.4.23-rc4/res/res_speech.c404
-rw-r--r--1.4.23-rc4/res/snmp/agent.c823
-rw-r--r--1.4.23-rc4/res/snmp/agent.h40
19 files changed, 17213 insertions, 0 deletions
diff --git a/1.4.23-rc4/res/Makefile b/1.4.23-rc4/res/Makefile
new file mode 100644
index 000000000..b289cbfff
--- /dev/null
+++ b/1.4.23-rc4/res/Makefile
@@ -0,0 +1,39 @@
+#
+# Asterisk -- A telephony toolkit for Linux.
+#
+# Makefile for resource modules
+#
+# Copyright (C) 1999-2006, Digium, Inc.
+#
+# This program is free software, distributed under the terms of
+# the GNU General Public License
+#
+
+-include ../menuselect.makeopts ../menuselect.makedeps
+
+MENUSELECT_CATEGORY=RES
+MENUSELECT_DESCRIPTION=Resource Modules
+
+ALL_C_MODS:=$(patsubst %.c,%,$(wildcard res_*.c))
+ALL_CC_MODS:=$(patsubst %.cc,%,$(wildcard res_*.cc))
+
+C_MODS:=$(filter-out $(MENUSELECT_RES),$(ALL_C_MODS))
+CC_MODS:=$(filter-out $(MENUSELECT_RES),$(ALL_CC_MODS))
+
+LOADABLE_MODS:=$(C_MODS) $(CC_MODS)
+
+ifneq ($(findstring res,$(MENUSELECT_EMBED)),)
+ EMBEDDED_MODS:=$(LOADABLE_MODS)
+ LOADABLE_MODS:=
+endif
+
+all: _all
+
+include $(ASTTOPDIR)/Makefile.moddir_rules
+
+snmp/agent.o: ASTCFLAGS+=$(MENUSELECT_OPTS_res_snmp:%=-D%) $(foreach dep,$(MENUSELECT_DEPENDS_res_snmp),$(value $(dep)_INCLUDE))
+
+$(if $(filter res_snmp,$(EMBEDDED_MODS)),modules.link,res_snmp.so): snmp/agent.o
+
+clean::
+ rm -f snmp/*.o snmp/*.i
diff --git a/1.4.23-rc4/res/res_adsi.c b/1.4.23-rc4/res/res_adsi.c
new file mode 100644
index 000000000..38648a8d4
--- /dev/null
+++ b/1.4.23-rc4/res/res_adsi.c
@@ -0,0 +1,1132 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * Includes code and algorithms from the Zapata library.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief ADSI support
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \note this module is required by app_voicemail and app_getcpeid
+ * \todo Move app_getcpeid into this module
+ * \todo Create a core layer so that app_voicemail does not require
+ * res_adsi to load
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <time.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <math.h>
+#include <errno.h>
+
+#include "asterisk/ulaw.h"
+#include "asterisk/alaw.h"
+#include "asterisk/callerid.h"
+#include "asterisk/logger.h"
+#include "asterisk/fskmodem.h"
+#include "asterisk/channel.h"
+#include "asterisk/adsi.h"
+#include "asterisk/module.h"
+#include "asterisk/config.h"
+#include "asterisk/file.h"
+
+#define DEFAULT_ADSI_MAX_RETRIES 3
+
+#define ADSI_MAX_INTRO 20
+#define ADSI_MAX_SPEED_DIAL 6
+
+#define ADSI_FLAG_DATAMODE (1 << 8)
+
+static int maxretries = DEFAULT_ADSI_MAX_RETRIES;
+
+/* Asterisk ADSI button definitions */
+#define ADSI_SPEED_DIAL 10 /* 10-15 are reserved for speed dial */
+
+static char intro[ADSI_MAX_INTRO][20];
+static int aligns[ADSI_MAX_INTRO];
+
+static char speeddial[ADSI_MAX_SPEED_DIAL][3][20];
+
+static int alignment = 0;
+
+static int adsi_generate(unsigned char *buf, int msgtype, unsigned char *msg, int msglen, int msgnum, int last, int codec)
+{
+ int sum;
+ int x;
+ int bytes=0;
+ /* Initial carrier (imaginary) */
+ float cr = 1.0;
+ float ci = 0.0;
+ float scont = 0.0;
+
+ if (msglen > 255)
+ msglen = 255;
+
+ /* If first message, Send 150ms of MARK's */
+ if (msgnum == 1) {
+ for (x=0;x<150;x++) /* was 150 */
+ PUT_CLID_MARKMS;
+ }
+ /* Put message type */
+ PUT_CLID(msgtype);
+ sum = msgtype;
+
+ /* Put message length (plus one for the message number) */
+ PUT_CLID(msglen + 1);
+ sum += msglen + 1;
+
+ /* Put message number */
+ PUT_CLID(msgnum);
+ sum += msgnum;
+
+ /* Put actual message */
+ for (x=0;x<msglen;x++) {
+ PUT_CLID(msg[x]);
+ sum += msg[x];
+ }
+
+ /* Put 2's compliment of sum */
+ PUT_CLID(256-(sum & 0xff));
+
+#if 0
+ if (last) {
+ /* Put trailing marks */
+ for (x=0;x<50;x++)
+ PUT_CLID_MARKMS;
+ }
+#endif
+ return bytes;
+
+}
+
+static int adsi_careful_send(struct ast_channel *chan, unsigned char *buf, int len, int *remainder)
+{
+ /* Sends carefully on a full duplex channel by using reading for
+ timing */
+ struct ast_frame *inf, outf;
+ int amt;
+
+ /* Zero out our outgoing frame */
+ memset(&outf, 0, sizeof(outf));
+
+ if (remainder && *remainder) {
+ amt = len;
+
+ /* Send remainder if provided */
+ if (amt > *remainder)
+ amt = *remainder;
+ else
+ *remainder = *remainder - amt;
+ outf.frametype = AST_FRAME_VOICE;
+ outf.subclass = AST_FORMAT_ULAW;
+ outf.data = buf;
+ outf.datalen = amt;
+ outf.samples = amt;
+ if (ast_write(chan, &outf)) {
+ ast_log(LOG_WARNING, "Failed to carefully write frame\n");
+ return -1;
+ }
+ /* Update pointers and lengths */
+ buf += amt;
+ len -= amt;
+ }
+
+ while(len) {
+ amt = len;
+ /* If we don't get anything at all back in a second, forget
+ about it */
+ if (ast_waitfor(chan, 1000) < 1)
+ return -1;
+ inf = ast_read(chan);
+ /* Detect hangup */
+ if (!inf)
+ return -1;
+ if (inf->frametype == AST_FRAME_VOICE) {
+ /* Read a voice frame */
+ if (inf->subclass != AST_FORMAT_ULAW) {
+ ast_log(LOG_WARNING, "Channel not in ulaw?\n");
+ ast_frfree(inf);
+ return -1;
+ }
+ /* Send no more than they sent us */
+ if (amt > inf->datalen)
+ amt = inf->datalen;
+ else if (remainder)
+ *remainder = inf->datalen - amt;
+ outf.frametype = AST_FRAME_VOICE;
+ outf.subclass = AST_FORMAT_ULAW;
+ outf.data = buf;
+ outf.datalen = amt;
+ outf.samples = amt;
+ if (ast_write(chan, &outf)) {
+ ast_log(LOG_WARNING, "Failed to carefully write frame\n");
+ ast_frfree(inf);
+ return -1;
+ }
+ /* Update pointers and lengths */
+ buf += amt;
+ len -= amt;
+ }
+ ast_frfree(inf);
+ }
+ return 0;
+}
+
+static int __adsi_transmit_messages(struct ast_channel *chan, unsigned char **msg, int *msglen, int *msgtype)
+{
+ /* msglen must be no more than 256 bits, each */
+ unsigned char buf[24000 * 5];
+ int pos = 0, res;
+ int x;
+ int start=0;
+ int retries = 0;
+
+ char ack[3];
+
+ /* Wait up to 500 ms for initial ACK */
+ int waittime;
+ struct ast_frame *f;
+ int rem = 0;
+ int def;
+
+ if (chan->adsicpe == AST_ADSI_UNAVAILABLE) {
+ /* Don't bother if we know they don't support ADSI */
+ errno = ENOSYS;
+ return -1;
+ }
+
+ while(retries < maxretries) {
+ if (!(chan->adsicpe & ADSI_FLAG_DATAMODE)) {
+ /* Generate CAS (no SAS) */
+ ast_gen_cas(buf, 0, 680, AST_FORMAT_ULAW);
+
+ /* Send CAS */
+ if (adsi_careful_send(chan, buf, 680, NULL)) {
+ ast_log(LOG_WARNING, "Unable to send CAS\n");
+ }
+ /* Wait For DTMF result */
+ waittime = 500;
+ for(;;) {
+ if (((res = ast_waitfor(chan, waittime)) < 1)) {
+ /* Didn't get back DTMF A in time */
+ ast_log(LOG_DEBUG, "No ADSI CPE detected (%d)\n", res);
+ if (!chan->adsicpe)
+ chan->adsicpe = AST_ADSI_UNAVAILABLE;
+ errno = ENOSYS;
+ return -1;
+ }
+ waittime = res;
+ f = ast_read(chan);
+ if (!f) {
+ ast_log(LOG_DEBUG, "Hangup in ADSI\n");
+ return -1;
+ }
+ if (f->frametype == AST_FRAME_DTMF) {
+ if (f->subclass == 'A') {
+ /* Okay, this is an ADSI CPE. Note this for future reference, too */
+ if (!chan->adsicpe)
+ chan->adsicpe = AST_ADSI_AVAILABLE;
+ break;
+ } else {
+ if (f->subclass == 'D') {
+ ast_log(LOG_DEBUG, "Off-hook capable CPE only, not ADSI\n");
+ } else
+ ast_log(LOG_WARNING, "Unknown ADSI response '%c'\n", f->subclass);
+ if (!chan->adsicpe)
+ chan->adsicpe = AST_ADSI_UNAVAILABLE;
+ errno = ENOSYS;
+ ast_frfree(f);
+ return -1;
+ }
+ }
+ ast_frfree(f);
+ }
+
+ ast_log(LOG_DEBUG, "ADSI Compatible CPE Detected\n");
+ } else
+ ast_log(LOG_DEBUG, "Already in data mode\n");
+
+ x = 0;
+ pos = 0;
+#if 1
+ def= ast_channel_defer_dtmf(chan);
+#endif
+ while((x < 6) && msg[x]) {
+ res = adsi_generate(buf + pos, msgtype[x], msg[x], msglen[x], x+1 - start, (x == 5) || !msg[x+1], AST_FORMAT_ULAW);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Failed to generate ADSI message %d on channel %s\n", x + 1, chan->name);
+ return -1;
+ }
+ ast_log(LOG_DEBUG, "Message %d, of %d input bytes, %d output bytes\n",
+ x + 1, msglen[x], res);
+ pos += res;
+ x++;
+ }
+
+
+ rem = 0;
+ res = adsi_careful_send(chan, buf, pos, &rem);
+ if (!def)
+ ast_channel_undefer_dtmf(chan);
+ if (res)
+ return -1;
+
+ ast_log(LOG_DEBUG, "Sent total spill of %d bytes\n", pos);
+
+ memset(ack, 0, sizeof(ack));
+ /* Get real result */
+ res = ast_readstring(chan, ack, 2, 1000, 1000, "");
+ /* Check for hangup */
+ if (res < 0)
+ return -1;
+ if (ack[0] == 'D') {
+ ast_log(LOG_DEBUG, "Acked up to message %d\n", atoi(ack + 1));
+ start += atoi(ack + 1);
+ if (start >= x)
+ break;
+ else {
+ retries++;
+ ast_log(LOG_DEBUG, "Retransmitting (%d), from %d\n", retries, start + 1);
+ }
+ } else {
+ retries++;
+ ast_log(LOG_WARNING, "Unexpected response to ack: %s (retry %d)\n", ack, retries);
+ }
+ }
+ if (retries >= maxretries) {
+ ast_log(LOG_WARNING, "Maximum ADSI Retries (%d) exceeded\n", maxretries);
+ errno = ETIMEDOUT;
+ return -1;
+ }
+ return 0;
+
+}
+
+int ast_adsi_begin_download(struct ast_channel *chan, char *service, unsigned char *fdn, unsigned char *sec, int version)
+{
+ int bytes;
+ unsigned char buf[256];
+ char ack[2];
+ bytes = 0;
+ /* Setup the resident soft key stuff, a piece at a time */
+ /* Upload what scripts we can for voicemail ahead of time */
+ bytes += ast_adsi_download_connect(buf + bytes, service, fdn, sec, version);
+ if (ast_adsi_transmit_message_full(chan, buf, bytes, ADSI_MSG_DOWNLOAD, 0))
+ return -1;
+ if (ast_readstring(chan, ack, 1, 10000, 10000, ""))
+ return -1;
+ if (ack[0] == 'B')
+ return 0;
+ ast_log(LOG_DEBUG, "Download was denied by CPE\n");
+ return -1;
+}
+
+int ast_adsi_end_download(struct ast_channel *chan)
+{
+ int bytes;
+ unsigned char buf[256];
+ bytes = 0;
+ /* Setup the resident soft key stuff, a piece at a time */
+ /* Upload what scripts we can for voicemail ahead of time */
+ bytes += ast_adsi_download_disconnect(buf + bytes);
+ if (ast_adsi_transmit_message_full(chan, buf, bytes, ADSI_MSG_DOWNLOAD, 0))
+ return -1;
+ return 0;
+}
+
+int ast_adsi_transmit_message_full(struct ast_channel *chan, unsigned char *msg, int msglen, int msgtype, int dowait)
+{
+ unsigned char *msgs[5] = { NULL, NULL, NULL, NULL, NULL };
+ int msglens[5];
+ int msgtypes[5];
+ int newdatamode;
+ int res;
+ int x;
+ int writeformat, readformat;
+ int waitforswitch = 0;
+
+ writeformat = chan->writeformat;
+ readformat = chan->readformat;
+
+ newdatamode = chan->adsicpe & ADSI_FLAG_DATAMODE;
+
+ for (x=0;x<msglen;x+=(msg[x+1]+2)) {
+ if (msg[x] == ADSI_SWITCH_TO_DATA) {
+ ast_log(LOG_DEBUG, "Switch to data is sent!\n");
+ waitforswitch++;
+ newdatamode = ADSI_FLAG_DATAMODE;
+ }
+
+ if (msg[x] == ADSI_SWITCH_TO_VOICE) {
+ ast_log(LOG_DEBUG, "Switch to voice is sent!\n");
+ waitforswitch++;
+ newdatamode = 0;
+ }
+ }
+ msgs[0] = msg;
+
+ msglens[0] = msglen;
+ msgtypes[0] = msgtype;
+
+ if (msglen > 253) {
+ ast_log(LOG_WARNING, "Can't send ADSI message of %d bytes, too large\n", msglen);
+ return -1;
+ }
+
+ ast_stopstream(chan);
+
+ if (ast_set_write_format(chan, AST_FORMAT_ULAW)) {
+ ast_log(LOG_WARNING, "Unable to set write format to ULAW\n");
+ return -1;
+ }
+
+ if (ast_set_read_format(chan, AST_FORMAT_ULAW)) {
+ ast_log(LOG_WARNING, "Unable to set read format to ULAW\n");
+ if (writeformat) {
+ if (ast_set_write_format(chan, writeformat))
+ ast_log(LOG_WARNING, "Unable to restore write format to %d\n", writeformat);
+ }
+ return -1;
+ }
+ res = __adsi_transmit_messages(chan, msgs, msglens, msgtypes);
+
+ if (dowait) {
+ ast_log(LOG_DEBUG, "Wait for switch is '%d'\n", waitforswitch);
+ while(waitforswitch-- && ((res = ast_waitfordigit(chan, 1000)) > 0)) { res = 0; ast_log(LOG_DEBUG, "Waiting for 'B'...\n"); }
+ }
+
+ if (!res)
+ chan->adsicpe = (chan->adsicpe & ~ADSI_FLAG_DATAMODE) | newdatamode;
+
+ if (writeformat)
+ ast_set_write_format(chan, writeformat);
+ if (readformat)
+ ast_set_read_format(chan, readformat);
+
+ if (!res)
+ res = ast_safe_sleep(chan, 100 );
+ return res;
+}
+
+int ast_adsi_transmit_message(struct ast_channel *chan, unsigned char *msg, int msglen, int msgtype)
+{
+ return ast_adsi_transmit_message_full(chan, msg, msglen, msgtype, 1);
+}
+
+static inline int ccopy(unsigned char *dst, const unsigned char *src, int max)
+{
+ int x=0;
+ /* Carefully copy the requested data */
+ while ((x < max) && src[x] && (src[x] != 0xff)) {
+ dst[x] = src[x];
+ x++;
+ }
+ return x;
+}
+
+int ast_adsi_load_soft_key(unsigned char *buf, int key, const char *llabel, const char *slabel, const char *ret, int data)
+{
+ int bytes=0;
+
+ /* Abort if invalid key specified */
+ if ((key < 2) || (key > 33))
+ return -1;
+ buf[bytes++] = ADSI_LOAD_SOFTKEY;
+ /* Reserve for length */
+ bytes++;
+ /* Which key */
+ buf[bytes++] = key;
+
+ /* Carefully copy long label */
+ bytes += ccopy(buf + bytes, (const unsigned char *)llabel, 18);
+
+ /* Place delimiter */
+ buf[bytes++] = 0xff;
+
+ /* Short label */
+ bytes += ccopy(buf + bytes, (const unsigned char *)slabel, 7);
+
+
+ /* If specified, copy return string */
+ if (ret) {
+ /* Place delimiter */
+ buf[bytes++] = 0xff;
+ if (data)
+ buf[bytes++] = ADSI_SWITCH_TO_DATA2;
+ /* Carefully copy return string */
+ bytes += ccopy(buf + bytes, (const unsigned char *)ret, 20);
+
+ }
+ /* Replace parameter length */
+ buf[1] = bytes - 2;
+ return bytes;
+
+}
+
+int ast_adsi_connect_session(unsigned char *buf, unsigned char *fdn, int ver)
+{
+ int bytes=0;
+ int x;
+
+ /* Message type */
+ buf[bytes++] = ADSI_CONNECT_SESSION;
+
+ /* Reserve space for length */
+ bytes++;
+
+ if (fdn) {
+ for (x=0;x<4;x++)
+ buf[bytes++] = fdn[x];
+ if (ver > -1)
+ buf[bytes++] = ver & 0xff;
+ }
+
+ buf[1] = bytes - 2;
+ return bytes;
+
+}
+
+int ast_adsi_download_connect(unsigned char *buf, char *service, unsigned char *fdn, unsigned char *sec, int ver)
+{
+ int bytes=0;
+ int x;
+
+ /* Message type */
+ buf[bytes++] = ADSI_DOWNLOAD_CONNECT;
+
+ /* Reserve space for length */
+ bytes++;
+
+ /* Primary column */
+ bytes+= ccopy(buf + bytes, (unsigned char *)service, 18);
+
+ /* Delimiter */
+ buf[bytes++] = 0xff;
+
+ for (x=0;x<4;x++) {
+ buf[bytes++] = fdn[x];
+ }
+ for (x=0;x<4;x++)
+ buf[bytes++] = sec[x];
+ buf[bytes++] = ver & 0xff;
+
+ buf[1] = bytes - 2;
+
+ return bytes;
+
+}
+
+int ast_adsi_disconnect_session(unsigned char *buf)
+{
+ int bytes=0;
+
+ /* Message type */
+ buf[bytes++] = ADSI_DISC_SESSION;
+
+ /* Reserve space for length */
+ bytes++;
+
+ buf[1] = bytes - 2;
+ return bytes;
+
+}
+
+int ast_adsi_query_cpeid(unsigned char *buf)
+{
+ int bytes = 0;
+ buf[bytes++] = ADSI_QUERY_CPEID;
+ /* Reserve space for length */
+ bytes++;
+ buf[1] = bytes - 2;
+ return bytes;
+}
+
+int ast_adsi_query_cpeinfo(unsigned char *buf)
+{
+ int bytes = 0;
+ buf[bytes++] = ADSI_QUERY_CONFIG;
+ /* Reserve space for length */
+ bytes++;
+ buf[1] = bytes - 2;
+ return bytes;
+}
+
+int ast_adsi_read_encoded_dtmf(struct ast_channel *chan, unsigned char *buf, int maxlen)
+{
+ int bytes = 0;
+ int res;
+ unsigned char current = 0;
+ int gotstar = 0;
+ int pos = 0;
+ memset(buf, 0, sizeof(buf));
+ while(bytes <= maxlen) {
+ /* Wait up to a second for a digit */
+ res = ast_waitfordigit(chan, 1000);
+ if (!res)
+ break;
+ if (res == '*') {
+ gotstar = 1;
+ continue;
+ }
+ /* Ignore anything other than a digit */
+ if ((res < '0') || (res > '9'))
+ continue;
+ res -= '0';
+ if (gotstar)
+ res += 9;
+ if (pos) {
+ pos = 0;
+ buf[bytes++] = (res << 4) | current;
+ } else {
+ pos = 1;
+ current = res;
+ }
+ gotstar = 0;
+ }
+ return bytes;
+}
+
+int ast_adsi_get_cpeid(struct ast_channel *chan, unsigned char *cpeid, int voice)
+{
+ unsigned char buf[256];
+ int bytes = 0;
+ int res;
+ bytes += ast_adsi_data_mode(buf);
+ ast_adsi_transmit_message_full(chan, buf, bytes, ADSI_MSG_DISPLAY, 0);
+
+ bytes = 0;
+ bytes += ast_adsi_query_cpeid(buf);
+ ast_adsi_transmit_message_full(chan, buf, bytes, ADSI_MSG_DISPLAY, 0);
+
+ /* Get response */
+ memset(buf, 0, sizeof(buf));
+ res = ast_adsi_read_encoded_dtmf(chan, cpeid, 4);
+ if (res != 4) {
+ ast_log(LOG_WARNING, "Got %d bytes back of encoded DTMF, expecting 4\n", res);
+ res = 0;
+ } else {
+ res = 1;
+ }
+
+ if (voice) {
+ bytes = 0;
+ bytes += ast_adsi_voice_mode(buf, 0);
+ ast_adsi_transmit_message_full(chan, buf, bytes, ADSI_MSG_DISPLAY, 0);
+ /* Ignore the resulting DTMF B announcing it's in voice mode */
+ ast_waitfordigit(chan, 1000);
+ }
+ return res;
+}
+
+int ast_adsi_get_cpeinfo(struct ast_channel *chan, int *width, int *height, int *buttons, int voice)
+{
+ unsigned char buf[256];
+ int bytes = 0;
+ int res;
+ bytes += ast_adsi_data_mode(buf);
+ ast_adsi_transmit_message_full(chan, buf, bytes, ADSI_MSG_DISPLAY, 0);
+
+ bytes = 0;
+ bytes += ast_adsi_query_cpeinfo(buf);
+ ast_adsi_transmit_message_full(chan, buf, bytes, ADSI_MSG_DISPLAY, 0);
+
+ /* Get width */
+ memset(buf, 0, sizeof(buf));
+ res = ast_readstring(chan, (char *)buf, 2, 1000, 500, "");
+ if (res < 0)
+ return res;
+ if (strlen((char *)buf) != 2) {
+ ast_log(LOG_WARNING, "Got %d bytes of width, expecting 2\n", res);
+ res = 0;
+ } else {
+ res = 1;
+ }
+ if (width)
+ *width = atoi((char *)buf);
+ /* Get height */
+ memset(buf, 0, sizeof(buf));
+ if (res) {
+ res = ast_readstring(chan, (char *)buf, 2, 1000, 500, "");
+ if (res < 0)
+ return res;
+ if (strlen((char *)buf) != 2) {
+ ast_log(LOG_WARNING, "Got %d bytes of height, expecting 2\n", res);
+ res = 0;
+ } else {
+ res = 1;
+ }
+ if (height)
+ *height= atoi((char *)buf);
+ }
+ /* Get buttons */
+ memset(buf, 0, sizeof(buf));
+ if (res) {
+ res = ast_readstring(chan, (char *)buf, 1, 1000, 500, "");
+ if (res < 0)
+ return res;
+ if (strlen((char *)buf) != 1) {
+ ast_log(LOG_WARNING, "Got %d bytes of buttons, expecting 1\n", res);
+ res = 0;
+ } else {
+ res = 1;
+ }
+ if (buttons)
+ *buttons = atoi((char *)buf);
+ }
+ if (voice) {
+ bytes = 0;
+ bytes += ast_adsi_voice_mode(buf, 0);
+ ast_adsi_transmit_message_full(chan, buf, bytes, ADSI_MSG_DISPLAY, 0);
+ /* Ignore the resulting DTMF B announcing it's in voice mode */
+ ast_waitfordigit(chan, 1000);
+ }
+ return res;
+}
+
+int ast_adsi_data_mode(unsigned char *buf)
+{
+ int bytes=0;
+
+ /* Message type */
+ buf[bytes++] = ADSI_SWITCH_TO_DATA;
+
+ /* Reserve space for length */
+ bytes++;
+
+ buf[1] = bytes - 2;
+ return bytes;
+
+}
+
+int ast_adsi_clear_soft_keys(unsigned char *buf)
+{
+ int bytes=0;
+
+ /* Message type */
+ buf[bytes++] = ADSI_CLEAR_SOFTKEY;
+
+ /* Reserve space for length */
+ bytes++;
+
+ buf[1] = bytes - 2;
+ return bytes;
+
+}
+
+int ast_adsi_clear_screen(unsigned char *buf)
+{
+ int bytes=0;
+
+ /* Message type */
+ buf[bytes++] = ADSI_CLEAR_SCREEN;
+
+ /* Reserve space for length */
+ bytes++;
+
+ buf[1] = bytes - 2;
+ return bytes;
+
+}
+
+int ast_adsi_voice_mode(unsigned char *buf, int when)
+{
+ int bytes=0;
+
+ /* Message type */
+ buf[bytes++] = ADSI_SWITCH_TO_VOICE;
+
+ /* Reserve space for length */
+ bytes++;
+
+ buf[bytes++] = when & 0x7f;
+
+ buf[1] = bytes - 2;
+ return bytes;
+
+}
+
+int ast_adsi_available(struct ast_channel *chan)
+{
+ int cpe = chan->adsicpe & 0xff;
+ if ((cpe == AST_ADSI_AVAILABLE) ||
+ (cpe == AST_ADSI_UNKNOWN))
+ return 1;
+ return 0;
+}
+
+int ast_adsi_download_disconnect(unsigned char *buf)
+{
+ int bytes=0;
+
+ /* Message type */
+ buf[bytes++] = ADSI_DOWNLOAD_DISC;
+
+ /* Reserve space for length */
+ bytes++;
+
+ buf[1] = bytes - 2;
+ return bytes;
+
+}
+
+int ast_adsi_display(unsigned char *buf, int page, int line, int just, int wrap,
+ char *col1, char *col2)
+{
+ int bytes=0;
+
+ /* Sanity check line number */
+
+ if (page) {
+ if (line > 4) return -1;
+ } else {
+ if (line > 33) return -1;
+ }
+
+ if (line < 1)
+ return -1;
+ /* Parameter type */
+ buf[bytes++] = ADSI_LOAD_VIRTUAL_DISP;
+
+ /* Reserve space for size */
+ bytes++;
+
+ /* Page and wrap indicator */
+ buf[bytes++] = ((page & 0x1) << 7) | ((wrap & 0x1) << 6) | (line & 0x3f);
+
+ /* Justification */
+ buf[bytes++] = (just & 0x3) << 5;
+
+ /* Omit highlight mode definition */
+ buf[bytes++] = 0xff;
+
+ /* Primary column */
+ bytes+= ccopy(buf + bytes, (unsigned char *)col1, 20);
+
+ /* Delimiter */
+ buf[bytes++] = 0xff;
+
+ /* Secondary column */
+ bytes += ccopy(buf + bytes, (unsigned char *)col2, 20);
+
+ /* Update length */
+ buf[1] = bytes - 2;
+
+ return bytes;
+
+}
+
+int ast_adsi_input_control(unsigned char *buf, int page, int line, int display, int format, int just)
+{
+ int bytes=0;
+
+ if (page) {
+ if (line > 4) return -1;
+ } else {
+ if (line > 33) return -1;
+ }
+
+ if (line < 1)
+ return -1;
+
+ buf[bytes++] = ADSI_INPUT_CONTROL;
+ bytes++;
+ buf[bytes++] = ((page & 1) << 7) | (line & 0x3f);
+ buf[bytes++] = ((display & 1) << 7) | ((just & 0x3) << 4) | (format & 0x7);
+
+ buf[1] = bytes - 2;
+ return bytes;
+
+}
+
+int ast_adsi_input_format(unsigned char *buf, int num, int dir, int wrap, char *format1, char *format2)
+{
+ int bytes = 0;
+
+ if (!strlen((char *)format1))
+ return -1;
+
+ buf[bytes++] = ADSI_INPUT_FORMAT;
+ bytes++;
+ buf[bytes++] = ((dir & 1) << 7) | ((wrap & 1) << 6) | (num & 0x7);
+ bytes += ccopy(buf + bytes, (unsigned char *)format1, 20);
+ buf[bytes++] = 0xff;
+ if (format2 && strlen((char *)format2)) {
+ bytes += ccopy(buf + bytes, (unsigned char *)format2, 20);
+ }
+ buf[1] = bytes - 2;
+ return bytes;
+}
+
+int ast_adsi_set_keys(unsigned char *buf, unsigned char *keys)
+{
+ int bytes=0;
+ int x;
+ /* Message type */
+ buf[bytes++] = ADSI_INIT_SOFTKEY_LINE;
+ /* Space for size */
+ bytes++;
+ /* Key definitions */
+ for (x=0;x<6;x++)
+ buf[bytes++] = (keys[x] & 0x3f) ? keys[x] : (keys[x] | 0x1);
+ buf[1] = bytes - 2;
+ return bytes;
+}
+
+int ast_adsi_set_line(unsigned char *buf, int page, int line)
+{
+ int bytes=0;
+
+ /* Sanity check line number */
+
+ if (page) {
+ if (line > 4) return -1;
+ } else {
+ if (line > 33) return -1;
+ }
+
+ if (line < 1)
+ return -1;
+ /* Parameter type */
+ buf[bytes++] = ADSI_LINE_CONTROL;
+
+ /* Reserve space for size */
+ bytes++;
+
+ /* Page and line */
+ buf[bytes++] = ((page & 0x1) << 7) | (line & 0x3f);
+
+ buf[1] = bytes - 2;
+ return bytes;
+
+};
+
+static int total = 0;
+static int speeds = 0;
+
+int ast_adsi_channel_restore(struct ast_channel *chan)
+{
+ unsigned char dsp[256];
+ int bytes;
+ int x;
+ unsigned char keyd[6];
+
+ memset(dsp, 0, sizeof(dsp));
+
+ /* Start with initial display setup */
+ bytes = 0;
+ bytes += ast_adsi_set_line(dsp + bytes, ADSI_INFO_PAGE, 1);
+
+ /* Prepare key setup messages */
+
+ if (speeds) {
+ memset(keyd, 0, sizeof(keyd));
+ for (x=0;x<speeds;x++) {
+ keyd[x] = ADSI_SPEED_DIAL + x;
+ }
+ bytes += ast_adsi_set_keys(dsp + bytes, keyd);
+ }
+ ast_adsi_transmit_message_full(chan, dsp, bytes, ADSI_MSG_DISPLAY, 0);
+ return 0;
+
+}
+
+int ast_adsi_print(struct ast_channel *chan, char **lines, int *aligns, int voice)
+{
+ unsigned char buf[4096];
+ int bytes=0;
+ int res;
+ int x;
+ for(x=0;lines[x];x++)
+ bytes += ast_adsi_display(buf + bytes, ADSI_INFO_PAGE, x+1, aligns[x], 0, lines[x], "");
+ bytes += ast_adsi_set_line(buf + bytes, ADSI_INFO_PAGE, 1);
+ if (voice) {
+ bytes += ast_adsi_voice_mode(buf + bytes, 0);
+ }
+ res = ast_adsi_transmit_message_full(chan, buf, bytes, ADSI_MSG_DISPLAY, 0);
+ if (voice) {
+ /* Ignore the resulting DTMF B announcing it's in voice mode */
+ ast_waitfordigit(chan, 1000);
+ }
+ return res;
+}
+
+int ast_adsi_load_session(struct ast_channel *chan, unsigned char *app, int ver, int data)
+{
+ unsigned char dsp[256];
+ int bytes;
+ int res;
+ char resp[2];
+
+ memset(dsp, 0, sizeof(dsp));
+
+ /* Connect to session */
+ bytes = 0;
+ bytes += ast_adsi_connect_session(dsp + bytes, app, ver);
+
+ if (data)
+ bytes += ast_adsi_data_mode(dsp + bytes);
+
+ /* Prepare key setup messages */
+ if (ast_adsi_transmit_message_full(chan, dsp, bytes, ADSI_MSG_DISPLAY, 0))
+ return -1;
+ if (app) {
+ res = ast_readstring(chan, resp, 1, 1200, 1200, "");
+ if (res < 0)
+ return -1;
+ if (res) {
+ ast_log(LOG_DEBUG, "No response from CPE about version. Assuming not there.\n");
+ return 0;
+ }
+ if (!strcmp(resp, "B")) {
+ ast_log(LOG_DEBUG, "CPE has script '%s' version %d already loaded\n", app, ver);
+ return 1;
+ } else if (!strcmp(resp, "A")) {
+ ast_log(LOG_DEBUG, "CPE hasn't script '%s' version %d already loaded\n", app, ver);
+ } else {
+ ast_log(LOG_WARNING, "Unexpected CPE response to script query: %s\n", resp);
+ }
+ } else
+ return 1;
+ return 0;
+
+}
+
+int ast_adsi_unload_session(struct ast_channel *chan)
+{
+ unsigned char dsp[256];
+ int bytes;
+
+ memset(dsp, 0, sizeof(dsp));
+
+ /* Connect to session */
+ bytes = 0;
+ bytes += ast_adsi_disconnect_session(dsp + bytes);
+ bytes += ast_adsi_voice_mode(dsp + bytes, 0);
+
+ /* Prepare key setup messages */
+ if (ast_adsi_transmit_message_full(chan, dsp, bytes, ADSI_MSG_DISPLAY, 0))
+ return -1;
+ return 0;
+}
+
+static int str2align(char *s)
+{
+ if (!strncasecmp(s, "l", 1))
+ return ADSI_JUST_LEFT;
+ else if (!strncasecmp(s, "r", 1))
+ return ADSI_JUST_RIGHT;
+ else if (!strncasecmp(s, "i", 1))
+ return ADSI_JUST_IND;
+ else
+ return ADSI_JUST_CENT;
+}
+
+static void init_state(void)
+{
+ int x;
+
+ for (x=0;x<ADSI_MAX_INTRO;x++)
+ aligns[x] = ADSI_JUST_CENT;
+ ast_copy_string(intro[0], "Welcome to the", sizeof(intro[0]));
+ ast_copy_string(intro[1], "Asterisk", sizeof(intro[1]));
+ ast_copy_string(intro[2], "Open Source PBX", sizeof(intro[2]));
+ total = 3;
+ speeds = 0;
+ for (x=3;x<ADSI_MAX_INTRO;x++)
+ intro[x][0] = '\0';
+ memset(speeddial, 0, sizeof(speeddial));
+ alignment = ADSI_JUST_CENT;
+}
+
+static void adsi_load(void)
+{
+ int x;
+ struct ast_config *conf;
+ struct ast_variable *v;
+ char *name, *sname;
+ init_state();
+ conf = ast_config_load("adsi.conf");
+ if (conf) {
+ x=0;
+ for (v = ast_variable_browse(conf, "intro"); v; v = v->next) {
+ if (!strcasecmp(v->name, "alignment"))
+ alignment = str2align(v->value);
+ else if (!strcasecmp(v->name, "greeting")) {
+ if (x < ADSI_MAX_INTRO) {
+ aligns[x] = alignment;
+ ast_copy_string(intro[x], v->value, sizeof(intro[x]));
+ x++;
+ }
+ } else if (!strcasecmp(v->name, "maxretries")) {
+ if (atoi(v->value) > 0)
+ maxretries = atoi(v->value);
+ }
+ }
+ if (x)
+ total = x;
+ x = 0;
+ for (v = ast_variable_browse(conf, "speeddial"); v; v = v->next) {
+ char *stringp = v->value;
+ name = strsep(&stringp, ",");
+ sname = strsep(&stringp, ",");
+ if (!sname)
+ sname = name;
+ if (x < ADSI_MAX_SPEED_DIAL) {
+ ast_copy_string(speeddial[x][0], v->name, sizeof(speeddial[x][0]));
+ ast_copy_string(speeddial[x][1], name, 18);
+ ast_copy_string(speeddial[x][2], sname, 7);
+ x++;
+ }
+ }
+ if (x)
+ speeds = x;
+ ast_config_destroy(conf);
+ }
+}
+
+static int reload(void)
+{
+ adsi_load();
+ return 0;
+}
+
+static int load_module(void)
+{
+ adsi_load();
+ return 0;
+}
+
+static int unload_module(void)
+{
+ /* Can't unload this once we're loaded */
+ return -1;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "ADSI Resource",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
diff --git a/1.4.23-rc4/res/res_agi.c b/1.4.23-rc4/res/res_agi.c
new file mode 100644
index 000000000..a80198bf3
--- /dev/null
+++ b/1.4.23-rc4/res/res_agi.c
@@ -0,0 +1,2226 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2006, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief AGI - the Asterisk Gateway Interface
+ *
+ * \author Mark Spencer <markster@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sys/types.h>
+#include <netdb.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <math.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/wait.h>
+
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/astdb.h"
+#include "asterisk/callerid.h"
+#include "asterisk/cli.h"
+#include "asterisk/logger.h"
+#include "asterisk/options.h"
+#include "asterisk/image.h"
+#include "asterisk/say.h"
+#include "asterisk/app.h"
+#include "asterisk/dsp.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/utils.h"
+#include "asterisk/lock.h"
+#include "asterisk/strings.h"
+#include "asterisk/agi.h"
+#include "asterisk/features.h"
+
+#define MAX_ARGS 128
+#define MAX_COMMANDS 128
+#define AGI_NANDFS_RETRY 3
+#define AGI_BUF_LEN 2048
+
+/* Recycle some stuff from the CLI interface */
+#define fdprintf agi_debug_cli
+
+static char *app = "AGI";
+
+static char *eapp = "EAGI";
+
+static char *deadapp = "DeadAGI";
+
+static char *synopsis = "Executes an AGI compliant application";
+static char *esynopsis = "Executes an EAGI compliant application";
+static char *deadsynopsis = "Executes AGI on a hungup channel";
+
+static char *descrip =
+" [E|Dead]AGI(command|args): Executes an Asterisk Gateway Interface compliant\n"
+"program on a channel. AGI allows Asterisk to launch external programs\n"
+"written in any language to control a telephony channel, play audio,\n"
+"read DTMF digits, etc. by communicating with the AGI protocol on stdin\n"
+"and stdout.\n"
+" This channel will stop dialplan execution on hangup inside of this\n"
+"application, except when using DeadAGI. Otherwise, dialplan execution\n"
+"will continue normally.\n"
+" A locally executed AGI script will receive SIGHUP on hangup from the channel\n"
+"except when using DeadAGI. This can be disabled by setting the AGISIGHUP channel\n"
+"variable to \"no\" before executing the AGI application.\n"
+" Using 'EAGI' provides enhanced AGI, with incoming audio available out of band\n"
+"on file descriptor 3\n\n"
+" Use the CLI command 'agi show' to list available agi commands\n"
+" This application sets the following channel variable upon completion:\n"
+" AGISTATUS The status of the attempt to the run the AGI script\n"
+" text string, one of SUCCESS | FAILURE | HANGUP\n";
+
+static int agidebug = 0;
+
+#define TONE_BLOCK_SIZE 200
+
+/* Max time to connect to an AGI remote host */
+#define MAX_AGI_CONNECT 2000
+
+#define AGI_PORT 4573
+
+enum agi_result {
+ AGI_RESULT_FAILURE = -1,
+ AGI_RESULT_SUCCESS,
+ AGI_RESULT_SUCCESS_FAST,
+ AGI_RESULT_HANGUP
+};
+
+static int __attribute__((format(printf, 2, 3))) agi_debug_cli(int fd, char *fmt, ...)
+{
+ char *stuff;
+ int res = 0;
+
+ va_list ap;
+ va_start(ap, fmt);
+ res = vasprintf(&stuff, fmt, ap);
+ va_end(ap);
+ if (res == -1) {
+ ast_log(LOG_ERROR, "Out of memory\n");
+ } else {
+ if (agidebug)
+ ast_verbose("AGI Tx >> %s", stuff); /* \n provided by caller */
+ res = ast_carefulwrite(fd, stuff, strlen(stuff), 100);
+ free(stuff);
+ }
+
+ return res;
+}
+
+/* launch_netscript: The fastagi handler.
+ FastAGI defaults to port 4573 */
+static enum agi_result launch_netscript(char *agiurl, char *argv[], int *fds, int *efd, int *opid)
+{
+ int s;
+ int flags;
+ struct pollfd pfds[1];
+ char *host;
+ char *c; int port = AGI_PORT;
+ char *script="";
+ struct sockaddr_in sin;
+ struct hostent *hp;
+ struct ast_hostent ahp;
+ int res;
+
+ /* agiusl is "agi://host.domain[:port][/script/name]" */
+ host = ast_strdupa(agiurl + 6); /* Remove agi:// */
+ /* Strip off any script name */
+ if ((c = strchr(host, '/'))) {
+ *c = '\0';
+ c++;
+ script = c;
+ }
+ if ((c = strchr(host, ':'))) {
+ *c = '\0';
+ c++;
+ port = atoi(c);
+ }
+ if (efd) {
+ ast_log(LOG_WARNING, "AGI URI's don't support Enhanced AGI yet\n");
+ return -1;
+ }
+ hp = ast_gethostbyname(host, &ahp);
+ if (!hp) {
+ ast_log(LOG_WARNING, "Unable to locate host '%s'\n", host);
+ return -1;
+ }
+ s = socket(AF_INET, SOCK_STREAM, 0);
+ if (s < 0) {
+ ast_log(LOG_WARNING, "Unable to create socket: %s\n", strerror(errno));
+ return -1;
+ }
+ flags = fcntl(s, F_GETFL);
+ if (flags < 0) {
+ ast_log(LOG_WARNING, "Fcntl(F_GETFL) failed: %s\n", strerror(errno));
+ close(s);
+ return -1;
+ }
+ if (fcntl(s, F_SETFL, flags | O_NONBLOCK) < 0) {
+ ast_log(LOG_WARNING, "Fnctl(F_SETFL) failed: %s\n", strerror(errno));
+ close(s);
+ return -1;
+ }
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(port);
+ memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
+ if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) && (errno != EINPROGRESS)) {
+ ast_log(LOG_WARNING, "Connect failed with unexpected error: %s\n", strerror(errno));
+ close(s);
+ return AGI_RESULT_FAILURE;
+ }
+
+ pfds[0].fd = s;
+ pfds[0].events = POLLOUT;
+ while ((res = poll(pfds, 1, MAX_AGI_CONNECT)) != 1) {
+ if (errno != EINTR) {
+ if (!res) {
+ ast_log(LOG_WARNING, "FastAGI connection to '%s' timed out after MAX_AGI_CONNECT (%d) milliseconds.\n",
+ agiurl, MAX_AGI_CONNECT);
+ } else
+ ast_log(LOG_WARNING, "Connect to '%s' failed: %s\n", agiurl, strerror(errno));
+ close(s);
+ return AGI_RESULT_FAILURE;
+ }
+ }
+
+ if (fdprintf(s, "agi_network: yes\n") < 0) {
+ if (errno != EINTR) {
+ ast_log(LOG_WARNING, "Connect to '%s' failed: %s\n", agiurl, strerror(errno));
+ close(s);
+ return AGI_RESULT_FAILURE;
+ }
+ }
+
+ /* If we have a script parameter, relay it to the fastagi server */
+ if (!ast_strlen_zero(script))
+ fdprintf(s, "agi_network_script: %s\n", script);
+
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "Wow, connected!\n");
+ fds[0] = s;
+ fds[1] = s;
+ *opid = -1;
+ return AGI_RESULT_SUCCESS_FAST;
+}
+
+static enum agi_result launch_script(char *script, char *argv[], int *fds, int *efd, int *opid)
+{
+ char tmp[256];
+ int pid;
+ int toast[2];
+ int fromast[2];
+ int audio[2];
+ int x;
+ int res;
+ sigset_t signal_set, old_set;
+
+ if (!strncasecmp(script, "agi://", 6))
+ return launch_netscript(script, argv, fds, efd, opid);
+
+ if (script[0] != '/') {
+ snprintf(tmp, sizeof(tmp), "%s/%s", (char *)ast_config_AST_AGI_DIR, script);
+ script = tmp;
+ }
+ if (pipe(toast)) {
+ ast_log(LOG_WARNING, "Unable to create toast pipe: %s\n",strerror(errno));
+ return AGI_RESULT_FAILURE;
+ }
+ if (pipe(fromast)) {
+ ast_log(LOG_WARNING, "unable to create fromast pipe: %s\n", strerror(errno));
+ close(toast[0]);
+ close(toast[1]);
+ return AGI_RESULT_FAILURE;
+ }
+ if (efd) {
+ if (pipe(audio)) {
+ ast_log(LOG_WARNING, "unable to create audio pipe: %s\n", strerror(errno));
+ close(fromast[0]);
+ close(fromast[1]);
+ close(toast[0]);
+ close(toast[1]);
+ return AGI_RESULT_FAILURE;
+ }
+ res = fcntl(audio[1], F_GETFL);
+ if (res > -1)
+ res = fcntl(audio[1], F_SETFL, res | O_NONBLOCK);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "unable to set audio pipe parameters: %s\n", strerror(errno));
+ close(fromast[0]);
+ close(fromast[1]);
+ close(toast[0]);
+ close(toast[1]);
+ close(audio[0]);
+ close(audio[1]);
+ return AGI_RESULT_FAILURE;
+ }
+ }
+
+ /* Block SIGHUP during the fork - prevents a race */
+ sigfillset(&signal_set);
+ pthread_sigmask(SIG_BLOCK, &signal_set, &old_set);
+ pid = fork();
+ if (pid < 0) {
+ ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno));
+ pthread_sigmask(SIG_SETMASK, &old_set, NULL);
+ return AGI_RESULT_FAILURE;
+ }
+ if (!pid) {
+ /* Pass paths to AGI via environmental variables */
+ setenv("AST_CONFIG_DIR", ast_config_AST_CONFIG_DIR, 1);
+ setenv("AST_CONFIG_FILE", ast_config_AST_CONFIG_FILE, 1);
+ setenv("AST_MODULE_DIR", ast_config_AST_MODULE_DIR, 1);
+ setenv("AST_SPOOL_DIR", ast_config_AST_SPOOL_DIR, 1);
+ setenv("AST_MONITOR_DIR", ast_config_AST_MONITOR_DIR, 1);
+ setenv("AST_VAR_DIR", ast_config_AST_VAR_DIR, 1);
+ setenv("AST_DATA_DIR", ast_config_AST_DATA_DIR, 1);
+ setenv("AST_LOG_DIR", ast_config_AST_LOG_DIR, 1);
+ setenv("AST_AGI_DIR", ast_config_AST_AGI_DIR, 1);
+ setenv("AST_KEY_DIR", ast_config_AST_KEY_DIR, 1);
+ setenv("AST_RUN_DIR", ast_config_AST_RUN_DIR, 1);
+
+ /* Don't run AGI scripts with realtime priority -- it causes audio stutter */
+ ast_set_priority(0);
+
+ /* Redirect stdin and out, provide enhanced audio channel if desired */
+ dup2(fromast[0], STDIN_FILENO);
+ dup2(toast[1], STDOUT_FILENO);
+ if (efd) {
+ dup2(audio[0], STDERR_FILENO + 1);
+ } else {
+ close(STDERR_FILENO + 1);
+ }
+
+ /* Before we unblock our signals, return our trapped signals back to the defaults */
+ signal(SIGHUP, SIG_DFL);
+ signal(SIGCHLD, SIG_DFL);
+ signal(SIGINT, SIG_DFL);
+ signal(SIGURG, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGPIPE, SIG_DFL);
+ signal(SIGXFSZ, SIG_DFL);
+
+ /* unblock important signal handlers */
+ if (pthread_sigmask(SIG_UNBLOCK, &signal_set, NULL)) {
+ ast_log(LOG_WARNING, "unable to unblock signals for AGI script: %s\n", strerror(errno));
+ _exit(1);
+ }
+
+ /* Close everything but stdin/out/error */
+ for (x=STDERR_FILENO + 2;x<1024;x++)
+ close(x);
+
+ /* Execute script */
+ execv(script, argv);
+ /* Can't use ast_log since FD's are closed */
+ fprintf(stdout, "verbose \"Failed to execute '%s': %s\" 2\n", script, strerror(errno));
+ /* Special case to set status of AGI to failure */
+ fprintf(stdout, "failure\n");
+ fflush(stdout);
+ _exit(1);
+ }
+ pthread_sigmask(SIG_SETMASK, &old_set, NULL);
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Launched AGI Script %s\n", script);
+ fds[0] = toast[0];
+ fds[1] = fromast[1];
+ if (efd) {
+ *efd = audio[1];
+ }
+ /* close what we're not using in the parent */
+ close(toast[1]);
+ close(fromast[0]);
+
+ if (efd)
+ close(audio[0]);
+
+ *opid = pid;
+ return AGI_RESULT_SUCCESS;
+}
+
+static void setup_env(struct ast_channel *chan, char *request, int fd, int enhanced)
+{
+ /* Print initial environment, with agi_request always being the first
+ thing */
+ fdprintf(fd, "agi_request: %s\n", request);
+ fdprintf(fd, "agi_channel: %s\n", chan->name);
+ fdprintf(fd, "agi_language: %s\n", chan->language);
+ fdprintf(fd, "agi_type: %s\n", chan->tech->type);
+ fdprintf(fd, "agi_uniqueid: %s\n", chan->uniqueid);
+
+ /* ANI/DNIS */
+ fdprintf(fd, "agi_callerid: %s\n", S_OR(chan->cid.cid_num, "unknown"));
+ fdprintf(fd, "agi_calleridname: %s\n", S_OR(chan->cid.cid_name, "unknown"));
+ fdprintf(fd, "agi_callingpres: %d\n", chan->cid.cid_pres);
+ fdprintf(fd, "agi_callingani2: %d\n", chan->cid.cid_ani2);
+ fdprintf(fd, "agi_callington: %d\n", chan->cid.cid_ton);
+ fdprintf(fd, "agi_callingtns: %d\n", chan->cid.cid_tns);
+ fdprintf(fd, "agi_dnid: %s\n", S_OR(chan->cid.cid_dnid, "unknown"));
+ fdprintf(fd, "agi_rdnis: %s\n", S_OR(chan->cid.cid_rdnis, "unknown"));
+
+ /* Context information */
+ fdprintf(fd, "agi_context: %s\n", chan->context);
+ fdprintf(fd, "agi_extension: %s\n", chan->exten);
+ fdprintf(fd, "agi_priority: %d\n", chan->priority);
+ fdprintf(fd, "agi_enhanced: %s\n", enhanced ? "1.0" : "0.0");
+
+ /* User information */
+ fdprintf(fd, "agi_accountcode: %s\n", chan->accountcode ? chan->accountcode : "");
+
+ /* End with empty return */
+ fdprintf(fd, "\n");
+}
+
+static int handle_answer(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
+{
+ int res;
+ res = 0;
+ if (chan->_state != AST_STATE_UP) {
+ /* Answer the chan */
+ res = ast_answer(chan);
+ }
+ fdprintf(agi->fd, "200 result=%d\n", res);
+ return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
+}
+
+static int handle_waitfordigit(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
+{
+ int res;
+ int to;
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+ if (sscanf(argv[3], "%d", &to) != 1)
+ return RESULT_SHOWUSAGE;
+ res = ast_waitfordigit_full(chan, to, agi->audio, agi->ctrl);
+ fdprintf(agi->fd, "200 result=%d\n", res);
+ return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
+}
+
+static int handle_sendtext(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
+{
+ int res;
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ /* At the moment, the parser (perhaps broken) returns with
+ the last argument PLUS the newline at the end of the input
+ buffer. This probably needs to be fixed, but I wont do that
+ because other stuff may break as a result. The right way
+ would probably be to strip off the trailing newline before
+ parsing, then here, add a newline at the end of the string
+ before sending it to ast_sendtext --DUDE */
+ res = ast_sendtext(chan, argv[2]);
+ fdprintf(agi->fd, "200 result=%d\n", res);
+ return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
+}
+
+static int handle_recvchar(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
+{
+ int res;
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ res = ast_recvchar(chan,atoi(argv[2]));
+ if (res == 0) {
+ fdprintf(agi->fd, "200 result=%d (timeout)\n", res);
+ return RESULT_SUCCESS;
+ }
+ if (res > 0) {
+ fdprintf(agi->fd, "200 result=%d\n", res);
+ return RESULT_SUCCESS;
+ }
+ else {
+ fdprintf(agi->fd, "200 result=%d (hangup)\n", res);
+ return RESULT_FAILURE;
+ }
+}
+
+static int handle_recvtext(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
+{
+ char *buf;
+
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ buf = ast_recvtext(chan,atoi(argv[2]));
+ if (buf) {
+ fdprintf(agi->fd, "200 result=1 (%s)\n", buf);
+ free(buf);
+ } else {
+ fdprintf(agi->fd, "200 result=-1\n");
+ }
+ return RESULT_SUCCESS;
+}
+
+static int handle_tddmode(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
+{
+ int res,x;
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ if (!strncasecmp(argv[2],"on",2))
+ x = 1;
+ else
+ x = 0;
+ if (!strncasecmp(argv[2],"mate",4))
+ x = 2;
+ if (!strncasecmp(argv[2],"tdd",3))
+ x = 1;
+ res = ast_channel_setoption(chan, AST_OPTION_TDD, &x, sizeof(char), 0);
+ if (res != RESULT_SUCCESS)
+ fdprintf(agi->fd, "200 result=0\n");
+ else
+ fdprintf(agi->fd, "200 result=1\n");
+ return RESULT_SUCCESS;
+}
+
+static int handle_sendimage(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
+{
+ int res;
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ res = ast_send_image(chan, argv[2]);
+ if (!ast_check_hangup(chan))
+ res = 0;
+ fdprintf(agi->fd, "200 result=%d\n", res);
+ return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
+}
+
+static int handle_controlstreamfile(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
+{
+ int res = 0;
+ int skipms = 3000;
+ char *fwd = NULL;
+ char *rev = NULL;
+ char *pause = NULL;
+ char *stop = NULL;
+
+ if (argc < 5 || argc > 9)
+ return RESULT_SHOWUSAGE;
+
+ if (!ast_strlen_zero(argv[4]))
+ stop = argv[4];
+ else
+ stop = NULL;
+
+ if ((argc > 5) && (sscanf(argv[5], "%d", &skipms) != 1))
+ return RESULT_SHOWUSAGE;
+
+ if (argc > 6 && !ast_strlen_zero(argv[6]))
+ fwd = argv[6];
+ else
+ fwd = "#";
+
+ if (argc > 7 && !ast_strlen_zero(argv[7]))
+ rev = argv[7];
+ else
+ rev = "*";
+
+ if (argc > 8 && !ast_strlen_zero(argv[8]))
+ pause = argv[8];
+ else
+ pause = NULL;
+
+ res = ast_control_streamfile(chan, argv[3], fwd, rev, stop, pause, NULL, skipms);
+
+ fdprintf(agi->fd, "200 result=%d\n", res);
+
+ return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
+}
+
+static int handle_streamfile(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
+{
+ int res;
+ int vres;
+ struct ast_filestream *fs;
+ struct ast_filestream *vfs;
+ long sample_offset = 0;
+ long max_length;
+ char *edigits = "";
+
+ if (argc < 4 || argc > 5)
+ return RESULT_SHOWUSAGE;
+
+ if (argv[3])
+ edigits = argv[3];
+
+ if ((argc > 4) && (sscanf(argv[4], "%ld", &sample_offset) != 1))
+ return RESULT_SHOWUSAGE;
+
+ fs = ast_openstream(chan, argv[2], chan->language);
+
+ if (!fs) {
+ fdprintf(agi->fd, "200 result=%d endpos=%ld\n", 0, sample_offset);
+ return RESULT_SUCCESS;
+ }
+ vfs = ast_openvstream(chan, argv[2], chan->language);
+ if (vfs)
+ ast_log(LOG_DEBUG, "Ooh, found a video stream, too\n");
+
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Playing '%s' (escape_digits=%s) (sample_offset %ld)\n", argv[2], edigits, sample_offset);
+
+ ast_seekstream(fs, 0, SEEK_END);
+ max_length = ast_tellstream(fs);
+ ast_seekstream(fs, sample_offset, SEEK_SET);
+ res = ast_applystream(chan, fs);
+ if (vfs)
+ vres = ast_applystream(chan, vfs);
+ ast_playstream(fs);
+ if (vfs)
+ ast_playstream(vfs);
+
+ res = ast_waitstream_full(chan, argv[3], agi->audio, agi->ctrl);
+ /* this is to check for if ast_waitstream closed the stream, we probably are at
+ * the end of the stream, return that amount, else check for the amount */
+ sample_offset = (chan->stream) ? ast_tellstream(fs) : max_length;
+ ast_stopstream(chan);
+ if (res == 1) {
+ /* Stop this command, don't print a result line, as there is a new command */
+ return RESULT_SUCCESS;
+ }
+ fdprintf(agi->fd, "200 result=%d endpos=%ld\n", res, sample_offset);
+ return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
+}
+
+/* get option - really similar to the handle_streamfile, but with a timeout */
+static int handle_getoption(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
+{
+ int res;
+ int vres;
+ struct ast_filestream *fs;
+ struct ast_filestream *vfs;
+ long sample_offset = 0;
+ long max_length;
+ int timeout = 0;
+ char *edigits = "";
+
+ if ( argc < 4 || argc > 5 )
+ return RESULT_SHOWUSAGE;
+
+ if ( argv[3] )
+ edigits = argv[3];
+
+ if ( argc == 5 )
+ timeout = atoi(argv[4]);
+ else if (chan->pbx->dtimeout) {
+ /* by default dtimeout is set to 5sec */
+ timeout = chan->pbx->dtimeout * 1000; /* in msec */
+ }
+
+ fs = ast_openstream(chan, argv[2], chan->language);
+ if (!fs) {
+ fdprintf(agi->fd, "200 result=%d endpos=%ld\n", 0, sample_offset);
+ ast_log(LOG_WARNING, "Unable to open %s\n", argv[2]);
+ return RESULT_SUCCESS;
+ }
+ vfs = ast_openvstream(chan, argv[2], chan->language);
+ if (vfs)
+ ast_log(LOG_DEBUG, "Ooh, found a video stream, too\n");
+
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Playing '%s' (escape_digits=%s) (timeout %d)\n", argv[2], edigits, timeout);
+
+ ast_seekstream(fs, 0, SEEK_END);
+ max_length = ast_tellstream(fs);
+ ast_seekstream(fs, sample_offset, SEEK_SET);
+ res = ast_applystream(chan, fs);
+ if (vfs)
+ vres = ast_applystream(chan, vfs);
+ ast_playstream(fs);
+ if (vfs)
+ ast_playstream(vfs);
+
+ res = ast_waitstream_full(chan, argv[3], agi->audio, agi->ctrl);
+ /* this is to check for if ast_waitstream closed the stream, we probably are at
+ * the end of the stream, return that amount, else check for the amount */
+ sample_offset = (chan->stream)?ast_tellstream(fs):max_length;
+ ast_stopstream(chan);
+ if (res == 1) {
+ /* Stop this command, don't print a result line, as there is a new command */
+ return RESULT_SUCCESS;
+ }
+
+ /* If the user didnt press a key, wait for digitTimeout*/
+ if (res == 0 ) {
+ res = ast_waitfordigit_full(chan, timeout, agi->audio, agi->ctrl);
+ /* Make sure the new result is in the escape digits of the GET OPTION */
+ if ( !strchr(edigits,res) )
+ res=0;
+ }
+
+ fdprintf(agi->fd, "200 result=%d endpos=%ld\n", res, sample_offset);
+ return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
+}
+
+
+
+
+/*--- handle_saynumber: Say number in various language syntaxes ---*/
+/* Need to add option for gender here as well. Coders wanted */
+/* While waiting, we're sending a (char *) NULL. */
+static int handle_saynumber(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
+{
+ int res;
+ int num;
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+ if (sscanf(argv[2], "%d", &num) != 1)
+ return RESULT_SHOWUSAGE;
+ res = ast_say_number_full(chan, num, argv[3], chan->language, (char *) NULL, agi->audio, agi->ctrl);
+ if (res == 1)
+ return RESULT_SUCCESS;
+ fdprintf(agi->fd, "200 result=%d\n", res);
+ return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
+}
+
+static int handle_saydigits(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
+{
+ int res;
+ int num;
+
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+ if (sscanf(argv[2], "%d", &num) != 1)
+ return RESULT_SHOWUSAGE;
+
+ res = ast_say_digit_str_full(chan, argv[2], argv[3], chan->language, agi->audio, agi->ctrl);
+ if (res == 1) /* New command */
+ return RESULT_SUCCESS;
+ fdprintf(agi->fd, "200 result=%d\n", res);
+ return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
+}
+
+static int handle_sayalpha(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
+{
+ int res;
+
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+
+ res = ast_say_character_str_full(chan, argv[2], argv[3], chan->language, agi->audio, agi->ctrl);
+ if (res == 1) /* New command */
+ return RESULT_SUCCESS;
+ fdprintf(agi->fd, "200 result=%d\n", res);
+ return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
+}
+
+static int handle_saydate(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
+{
+ int res;
+ int num;
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+ if (sscanf(argv[2], "%d", &num) != 1)
+ return RESULT_SHOWUSAGE;
+ res = ast_say_date(chan, num, argv[3], chan->language);
+ if (res == 1)
+ return RESULT_SUCCESS;
+ fdprintf(agi->fd, "200 result=%d\n", res);
+ return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
+}
+
+static int handle_saytime(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
+{
+ int res;
+ int num;
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+ if (sscanf(argv[2], "%d", &num) != 1)
+ return RESULT_SHOWUSAGE;
+ res = ast_say_time(chan, num, argv[3], chan->language);
+ if (res == 1)
+ return RESULT_SUCCESS;
+ fdprintf(agi->fd, "200 result=%d\n", res);
+ return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
+}
+
+static int handle_saydatetime(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
+{
+ int res=0;
+ time_t unixtime;
+ char *format, *zone=NULL;
+
+ if (argc < 4)
+ return RESULT_SHOWUSAGE;
+
+ if (argc > 4) {
+ format = argv[4];
+ } else {
+ /* XXX this doesn't belong here, but in the 'say' module */
+ if (!strcasecmp(chan->language, "de")) {
+ format = "A dBY HMS";
+ } else {
+ format = "ABdY 'digits/at' IMp";
+ }
+ }
+
+ if (argc > 5 && !ast_strlen_zero(argv[5]))
+ zone = argv[5];
+
+ if (ast_get_time_t(argv[2], &unixtime, 0, NULL))
+ return RESULT_SHOWUSAGE;
+
+ res = ast_say_date_with_format(chan, unixtime, argv[3], chan->language, format, zone);
+ if (res == 1)
+ return RESULT_SUCCESS;
+
+ fdprintf(agi->fd, "200 result=%d\n", res);
+ return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
+}
+
+static int handle_sayphonetic(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
+{
+ int res;
+
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+
+ res = ast_say_phonetic_str_full(chan, argv[2], argv[3], chan->language, agi->audio, agi->ctrl);
+ if (res == 1) /* New command */
+ return RESULT_SUCCESS;
+ fdprintf(agi->fd, "200 result=%d\n", res);
+ return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
+}
+
+static int handle_getdata(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
+{
+ int res;
+ char data[1024];
+ int max;
+ int timeout;
+
+ if (argc < 3)
+ return RESULT_SHOWUSAGE;
+ if (argc >= 4)
+ timeout = atoi(argv[3]);
+ else
+ timeout = 0;
+ if (argc >= 5)
+ max = atoi(argv[4]);
+ else
+ max = 1024;
+ res = ast_app_getdata_full(chan, argv[2], data, max, timeout, agi->audio, agi->ctrl);
+ if (res == 2) /* New command */
+ return RESULT_SUCCESS;
+ else if (res == 1)
+ fdprintf(agi->fd, "200 result=%s (timeout)\n", data);
+ else if (res < 0 )
+ fdprintf(agi->fd, "200 result=-1\n");
+ else
+ fdprintf(agi->fd, "200 result=%s\n", data);
+ return RESULT_SUCCESS;
+}
+
+static int handle_setcontext(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
+{
+
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ ast_copy_string(chan->context, argv[2], sizeof(chan->context));
+ fdprintf(agi->fd, "200 result=0\n");
+ return RESULT_SUCCESS;
+}
+
+static int handle_setextension(struct ast_channel *chan, AGI *agi, int argc, char **argv)
+{
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ ast_copy_string(chan->exten, argv[2], sizeof(chan->exten));
+ fdprintf(agi->fd, "200 result=0\n");
+ return RESULT_SUCCESS;
+}
+
+static int handle_setpriority(struct ast_channel *chan, AGI *agi, int argc, char **argv)
+{
+ int pri;
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+
+ if (sscanf(argv[2], "%d", &pri) != 1) {
+ if ((pri = ast_findlabel_extension(chan, chan->context, chan->exten, argv[2], chan->cid.cid_num)) < 1)
+ return RESULT_SHOWUSAGE;
+ }
+
+ ast_explicit_goto(chan, NULL, NULL, pri);
+ fdprintf(agi->fd, "200 result=0\n");
+ return RESULT_SUCCESS;
+}
+
+static int handle_recordfile(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
+{
+ struct ast_filestream *fs;
+ struct ast_frame *f;
+ struct timeval start;
+ long sample_offset = 0;
+ int res = 0;
+ int ms;
+
+ struct ast_dsp *sildet=NULL; /* silence detector dsp */
+ int totalsilence = 0;
+ int dspsilence = 0;
+ int silence = 0; /* amount of silence to allow */
+ int gotsilence = 0; /* did we timeout for silence? */
+ char *silencestr=NULL;
+ int rfmt=0;
+
+
+ /* XXX EAGI FIXME XXX */
+
+ if (argc < 6)
+ return RESULT_SHOWUSAGE;
+ if (sscanf(argv[5], "%d", &ms) != 1)
+ return RESULT_SHOWUSAGE;
+
+ if (argc > 6)
+ silencestr = strchr(argv[6],'s');
+ if ((argc > 7) && (!silencestr))
+ silencestr = strchr(argv[7],'s');
+ if ((argc > 8) && (!silencestr))
+ silencestr = strchr(argv[8],'s');
+
+ if (silencestr) {
+ if (strlen(silencestr) > 2) {
+ if ((silencestr[0] == 's') && (silencestr[1] == '=')) {
+ silencestr++;
+ silencestr++;
+ if (silencestr)
+ silence = atoi(silencestr);
+ if (silence > 0)
+ silence *= 1000;
+ }
+ }
+ }
+
+ if (silence > 0) {
+ rfmt = chan->readformat;
+ res = ast_set_read_format(chan, AST_FORMAT_SLINEAR);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to set to linear mode, giving up\n");
+ return -1;
+ }
+ sildet = ast_dsp_new();
+ if (!sildet) {
+ ast_log(LOG_WARNING, "Unable to create silence detector :(\n");
+ return -1;
+ }
+ ast_dsp_set_threshold(sildet, 256);
+ }
+
+ /* backward compatibility, if no offset given, arg[6] would have been
+ * caught below and taken to be a beep, else if it is a digit then it is a
+ * offset */
+ if ((argc >6) && (sscanf(argv[6], "%ld", &sample_offset) != 1) && (!strchr(argv[6], '=')))
+ res = ast_streamfile(chan, "beep", chan->language);
+
+ if ((argc > 7) && (!strchr(argv[7], '=')))
+ res = ast_streamfile(chan, "beep", chan->language);
+
+ if (!res)
+ res = ast_waitstream(chan, argv[4]);
+ if (res) {
+ fdprintf(agi->fd, "200 result=%d (randomerror) endpos=%ld\n", res, sample_offset);
+ } else {
+ fs = ast_writefile(argv[2], argv[3], NULL, O_CREAT | O_WRONLY | (sample_offset ? O_APPEND : 0), 0, 0644);
+ if (!fs) {
+ res = -1;
+ fdprintf(agi->fd, "200 result=%d (writefile)\n", res);
+ if (sildet)
+ ast_dsp_free(sildet);
+ return RESULT_FAILURE;
+ }
+
+ /* Request a video update */
+ ast_indicate(chan, AST_CONTROL_VIDUPDATE);
+
+ chan->stream = fs;
+ ast_applystream(chan,fs);
+ /* really should have checks */
+ ast_seekstream(fs, sample_offset, SEEK_SET);
+ ast_truncstream(fs);
+
+ start = ast_tvnow();
+ while ((ms < 0) || ast_tvdiff_ms(ast_tvnow(), start) < ms) {
+ res = ast_waitfor(chan, ms - ast_tvdiff_ms(ast_tvnow(), start));
+ if (res < 0) {
+ ast_closestream(fs);
+ fdprintf(agi->fd, "200 result=%d (waitfor) endpos=%ld\n", res,sample_offset);
+ if (sildet)
+ ast_dsp_free(sildet);
+ return RESULT_FAILURE;
+ }
+ f = ast_read(chan);
+ if (!f) {
+ fdprintf(agi->fd, "200 result=%d (hangup) endpos=%ld\n", -1, sample_offset);
+ ast_closestream(fs);
+ if (sildet)
+ ast_dsp_free(sildet);
+ return RESULT_FAILURE;
+ }
+ switch(f->frametype) {
+ case AST_FRAME_DTMF:
+ if (strchr(argv[4], f->subclass)) {
+ /* This is an interrupting chracter, so rewind to chop off any small
+ amount of DTMF that may have been recorded
+ */
+ ast_stream_rewind(fs, 200);
+ ast_truncstream(fs);
+ sample_offset = ast_tellstream(fs);
+ fdprintf(agi->fd, "200 result=%d (dtmf) endpos=%ld\n", f->subclass, sample_offset);
+ ast_closestream(fs);
+ ast_frfree(f);
+ if (sildet)
+ ast_dsp_free(sildet);
+ return RESULT_SUCCESS;
+ }
+ break;
+ case AST_FRAME_VOICE:
+ ast_writestream(fs, f);
+ /* this is a safe place to check progress since we know that fs
+ * is valid after a write, and it will then have our current
+ * location */
+ sample_offset = ast_tellstream(fs);
+ if (silence > 0) {
+ dspsilence = 0;
+ ast_dsp_silence(sildet, f, &dspsilence);
+ if (dspsilence) {
+ totalsilence = dspsilence;
+ } else {
+ totalsilence = 0;
+ }
+ if (totalsilence > silence) {
+ /* Ended happily with silence */
+ gotsilence = 1;
+ break;
+ }
+ }
+ break;
+ case AST_FRAME_VIDEO:
+ ast_writestream(fs, f);
+ default:
+ /* Ignore all other frames */
+ break;
+ }
+ ast_frfree(f);
+ if (gotsilence)
+ break;
+ }
+
+ if (gotsilence) {
+ ast_stream_rewind(fs, silence-1000);
+ ast_truncstream(fs);
+ sample_offset = ast_tellstream(fs);
+ }
+ fdprintf(agi->fd, "200 result=%d (timeout) endpos=%ld\n", res, sample_offset);
+ ast_closestream(fs);
+ }
+
+ if (silence > 0) {
+ res = ast_set_read_format(chan, rfmt);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to restore read format on '%s'\n", chan->name);
+ ast_dsp_free(sildet);
+ }
+ return RESULT_SUCCESS;
+}
+
+static int handle_autohangup(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
+{
+ int timeout;
+
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ if (sscanf(argv[2], "%d", &timeout) != 1)
+ return RESULT_SHOWUSAGE;
+ if (timeout < 0)
+ timeout = 0;
+ if (timeout)
+ chan->whentohangup = time(NULL) + timeout;
+ else
+ chan->whentohangup = 0;
+ fdprintf(agi->fd, "200 result=0\n");
+ return RESULT_SUCCESS;
+}
+
+static int handle_hangup(struct ast_channel *chan, AGI *agi, int argc, char **argv)
+{
+ struct ast_channel *c;
+ if (argc == 1) {
+ /* no argument: hangup the current channel */
+ ast_softhangup(chan,AST_SOFTHANGUP_EXPLICIT);
+ fdprintf(agi->fd, "200 result=1\n");
+ return RESULT_SUCCESS;
+ } else if (argc == 2) {
+ /* one argument: look for info on the specified channel */
+ c = ast_get_channel_by_name_locked(argv[1]);
+ if (c) {
+ /* we have a matching channel */
+ ast_softhangup(c,AST_SOFTHANGUP_EXPLICIT);
+ fdprintf(agi->fd, "200 result=1\n");
+ ast_channel_unlock(c);
+ return RESULT_SUCCESS;
+ }
+ /* if we get this far no channel name matched the argument given */
+ fdprintf(agi->fd, "200 result=-1\n");
+ return RESULT_SUCCESS;
+ } else {
+ return RESULT_SHOWUSAGE;
+ }
+}
+
+static int handle_exec(struct ast_channel *chan, AGI *agi, int argc, char **argv)
+{
+ int res;
+ struct ast_app *app;
+
+ if (argc < 2)
+ return RESULT_SHOWUSAGE;
+
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "AGI Script Executing Application: (%s) Options: (%s)\n", argv[1], argv[2]);
+
+ app = pbx_findapp(argv[1]);
+
+ if (app) {
+ if(!strcasecmp(argv[1], PARK_APP_NAME)) {
+ ast_masq_park_call(chan, NULL, 0, NULL);
+ }
+ res = pbx_exec(chan, app, argv[2]);
+ } else {
+ ast_log(LOG_WARNING, "Could not find application (%s)\n", argv[1]);
+ res = -2;
+ }
+ fdprintf(agi->fd, "200 result=%d\n", res);
+
+ /* Even though this is wrong, users are depending upon this result. */
+ return res;
+}
+
+static int handle_setcallerid(struct ast_channel *chan, AGI *agi, int argc, char **argv)
+{
+ char tmp[256]="";
+ char *l = NULL, *n = NULL;
+
+ if (argv[2]) {
+ ast_copy_string(tmp, argv[2], sizeof(tmp));
+ ast_callerid_parse(tmp, &n, &l);
+ if (l)
+ ast_shrink_phone_number(l);
+ else
+ l = "";
+ if (!n)
+ n = "";
+ ast_set_callerid(chan, l, n, NULL);
+ }
+
+ fdprintf(agi->fd, "200 result=1\n");
+ return RESULT_SUCCESS;
+}
+
+static int handle_channelstatus(struct ast_channel *chan, AGI *agi, int argc, char **argv)
+{
+ struct ast_channel *c;
+ if (argc == 2) {
+ /* no argument: supply info on the current channel */
+ fdprintf(agi->fd, "200 result=%d\n", chan->_state);
+ return RESULT_SUCCESS;
+ } else if (argc == 3) {
+ /* one argument: look for info on the specified channel */
+ c = ast_get_channel_by_name_locked(argv[2]);
+ if (c) {
+ fdprintf(agi->fd, "200 result=%d\n", c->_state);
+ ast_channel_unlock(c);
+ return RESULT_SUCCESS;
+ }
+ /* if we get this far no channel name matched the argument given */
+ fdprintf(agi->fd, "200 result=-1\n");
+ return RESULT_SUCCESS;
+ } else {
+ return RESULT_SHOWUSAGE;
+ }
+}
+
+static int handle_setvariable(struct ast_channel *chan, AGI *agi, int argc, char **argv)
+{
+ if (argv[3])
+ pbx_builtin_setvar_helper(chan, argv[2], argv[3]);
+
+ fdprintf(agi->fd, "200 result=1\n");
+ return RESULT_SUCCESS;
+}
+
+static int handle_getvariable(struct ast_channel *chan, AGI *agi, int argc, char **argv)
+{
+ char *ret;
+ char tempstr[1024];
+
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+
+ /* check if we want to execute an ast_custom_function */
+ if (!ast_strlen_zero(argv[2]) && (argv[2][strlen(argv[2]) - 1] == ')')) {
+ ret = ast_func_read(chan, argv[2], tempstr, sizeof(tempstr)) ? NULL : tempstr;
+ } else {
+ pbx_retrieve_variable(chan, argv[2], &ret, tempstr, sizeof(tempstr), NULL);
+ }
+
+ if (ret)
+ fdprintf(agi->fd, "200 result=1 (%s)\n", ret);
+ else
+ fdprintf(agi->fd, "200 result=0\n");
+
+ return RESULT_SUCCESS;
+}
+
+static int handle_getvariablefull(struct ast_channel *chan, AGI *agi, int argc, char **argv)
+{
+ char tmp[4096] = "";
+ struct ast_channel *chan2=NULL;
+
+ if ((argc != 4) && (argc != 5))
+ return RESULT_SHOWUSAGE;
+ if (argc == 5) {
+ chan2 = ast_get_channel_by_name_locked(argv[4]);
+ } else {
+ chan2 = chan;
+ }
+ if (chan2) {
+ pbx_substitute_variables_helper(chan2, argv[3], tmp, sizeof(tmp) - 1);
+ fdprintf(agi->fd, "200 result=1 (%s)\n", tmp);
+ } else {
+ fdprintf(agi->fd, "200 result=0\n");
+ }
+ if (chan2 && (chan2 != chan))
+ ast_channel_unlock(chan2);
+ return RESULT_SUCCESS;
+}
+
+static int handle_verbose(struct ast_channel *chan, AGI *agi, int argc, char **argv)
+{
+ int level = 0;
+ char *prefix;
+
+ if (argc < 2)
+ return RESULT_SHOWUSAGE;
+
+ if (argv[2])
+ sscanf(argv[2], "%d", &level);
+
+ switch (level) {
+ case 4:
+ prefix = VERBOSE_PREFIX_4;
+ break;
+ case 3:
+ prefix = VERBOSE_PREFIX_3;
+ break;
+ case 2:
+ prefix = VERBOSE_PREFIX_2;
+ break;
+ case 1:
+ default:
+ prefix = VERBOSE_PREFIX_1;
+ break;
+ }
+
+ if (level <= option_verbose)
+ ast_verbose("%s %s: %s\n", prefix, chan->data, argv[1]);
+
+ fdprintf(agi->fd, "200 result=1\n");
+
+ return RESULT_SUCCESS;
+}
+
+static int handle_dbget(struct ast_channel *chan, AGI *agi, int argc, char **argv)
+{
+ int res;
+ size_t bufsize = 16;
+ char *buf, *tmp;
+
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+
+ if (!(buf = ast_malloc(bufsize))) {
+ fdprintf(agi->fd, "200 result=-1\n");
+ return RESULT_SUCCESS;
+ }
+
+ do {
+ res = ast_db_get(argv[2], argv[3], buf, bufsize);
+ if (strlen(buf) < bufsize - 1) {
+ break;
+ }
+ bufsize *= 2;
+ if (!(tmp = ast_realloc(buf, bufsize))) {
+ break;
+ }
+ buf = tmp;
+ } while (1);
+
+ if (res)
+ fdprintf(agi->fd, "200 result=0\n");
+ else
+ fdprintf(agi->fd, "200 result=1 (%s)\n", buf);
+
+ ast_free(buf);
+ return RESULT_SUCCESS;
+}
+
+static int handle_dbput(struct ast_channel *chan, AGI *agi, int argc, char **argv)
+{
+ int res;
+
+ if (argc != 5)
+ return RESULT_SHOWUSAGE;
+ res = ast_db_put(argv[2], argv[3], argv[4]);
+ fdprintf(agi->fd, "200 result=%c\n", res ? '0' : '1');
+ return RESULT_SUCCESS;
+}
+
+static int handle_dbdel(struct ast_channel *chan, AGI *agi, int argc, char **argv)
+{
+ int res;
+
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+ res = ast_db_del(argv[2], argv[3]);
+ fdprintf(agi->fd, "200 result=%c\n", res ? '0' : '1');
+ return RESULT_SUCCESS;
+}
+
+static int handle_dbdeltree(struct ast_channel *chan, AGI *agi, int argc, char **argv)
+{
+ int res;
+ if ((argc < 3) || (argc > 4))
+ return RESULT_SHOWUSAGE;
+ if (argc == 4)
+ res = ast_db_deltree(argv[2], argv[3]);
+ else
+ res = ast_db_deltree(argv[2], NULL);
+
+ fdprintf(agi->fd, "200 result=%c\n", res ? '0' : '1');
+ return RESULT_SUCCESS;
+}
+
+static char debug_usage[] =
+"Usage: agi debug\n"
+" Enables dumping of AGI transactions for debugging purposes\n";
+
+static char no_debug_usage[] =
+"Usage: agi debug off\n"
+" Disables dumping of AGI transactions for debugging purposes\n";
+
+static int agi_do_debug(int fd, int argc, char *argv[])
+{
+ if (argc != 2)
+ return RESULT_SHOWUSAGE;
+ agidebug = 1;
+ ast_cli(fd, "AGI Debugging Enabled\n");
+ return RESULT_SUCCESS;
+}
+
+static int agi_no_debug_deprecated(int fd, int argc, char *argv[])
+{
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ agidebug = 0;
+ ast_cli(fd, "AGI Debugging Disabled\n");
+ return RESULT_SUCCESS;
+}
+
+static int agi_no_debug(int fd, int argc, char *argv[])
+{
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ agidebug = 0;
+ ast_cli(fd, "AGI Debugging Disabled\n");
+ return RESULT_SUCCESS;
+}
+
+static int handle_noop(struct ast_channel *chan, AGI *agi, int arg, char *argv[])
+{
+ fdprintf(agi->fd, "200 result=0\n");
+ return RESULT_SUCCESS;
+}
+
+static int handle_setmusic(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
+{
+ if (!strncasecmp(argv[2], "on", 2))
+ ast_moh_start(chan, argc > 3 ? argv[3] : NULL, NULL);
+ else if (!strncasecmp(argv[2], "off", 3))
+ ast_moh_stop(chan);
+ fdprintf(agi->fd, "200 result=0\n");
+ return RESULT_SUCCESS;
+}
+
+static char usage_setmusic[] =
+" Usage: SET MUSIC ON <on|off> <class>\n"
+" Enables/Disables the music on hold generator. If <class> is\n"
+" not specified, then the default music on hold class will be used.\n"
+" Always returns 0.\n";
+
+static char usage_dbput[] =
+" Usage: DATABASE PUT <family> <key> <value>\n"
+" Adds or updates an entry in the Asterisk database for a\n"
+" given family, key, and value.\n"
+" Returns 1 if successful, 0 otherwise.\n";
+
+static char usage_dbget[] =
+" Usage: DATABASE GET <family> <key>\n"
+" Retrieves an entry in the Asterisk database for a\n"
+" given family and key.\n"
+" Returns 0 if <key> is not set. Returns 1 if <key>\n"
+" is set and returns the variable in parentheses.\n"
+" Example return code: 200 result=1 (testvariable)\n";
+
+static char usage_dbdel[] =
+" Usage: DATABASE DEL <family> <key>\n"
+" Deletes an entry in the Asterisk database for a\n"
+" given family and key.\n"
+" Returns 1 if successful, 0 otherwise.\n";
+
+static char usage_dbdeltree[] =
+" Usage: DATABASE DELTREE <family> [keytree]\n"
+" Deletes a family or specific keytree within a family\n"
+" in the Asterisk database.\n"
+" Returns 1 if successful, 0 otherwise.\n";
+
+static char usage_verbose[] =
+" Usage: VERBOSE <message> <level>\n"
+" Sends <message> to the console via verbose message system.\n"
+" <level> is the the verbose level (1-4)\n"
+" Always returns 1.\n";
+
+static char usage_getvariable[] =
+" Usage: GET VARIABLE <variablename>\n"
+" Returns 0 if <variablename> is not set. Returns 1 if <variablename>\n"
+" is set and returns the variable in parentheses.\n"
+" example return code: 200 result=1 (testvariable)\n";
+
+static char usage_getvariablefull[] =
+" Usage: GET FULL VARIABLE <variablename> [<channel name>]\n"
+" Returns 0 if <variablename> is not set or channel does not exist. Returns 1\n"
+"if <variablename> is set and returns the variable in parenthesis. Understands\n"
+"complex variable names and builtin variables, unlike GET VARIABLE.\n"
+" example return code: 200 result=1 (testvariable)\n";
+
+static char usage_setvariable[] =
+" Usage: SET VARIABLE <variablename> <value>\n";
+
+static char usage_channelstatus[] =
+" Usage: CHANNEL STATUS [<channelname>]\n"
+" Returns the status of the specified channel.\n"
+" If no channel name is given the returns the status of the\n"
+" current channel. Return values:\n"
+" 0 Channel is down and available\n"
+" 1 Channel is down, but reserved\n"
+" 2 Channel is off hook\n"
+" 3 Digits (or equivalent) have been dialed\n"
+" 4 Line is ringing\n"
+" 5 Remote end is ringing\n"
+" 6 Line is up\n"
+" 7 Line is busy\n";
+
+static char usage_setcallerid[] =
+" Usage: SET CALLERID <number>\n"
+" Changes the callerid of the current channel.\n";
+
+static char usage_exec[] =
+" Usage: EXEC <application> <options>\n"
+" Executes <application> with given <options>.\n"
+" Returns whatever the application returns, or -2 on failure to find application\n";
+
+static char usage_hangup[] =
+" Usage: HANGUP [<channelname>]\n"
+" Hangs up the specified channel.\n"
+" If no channel name is given, hangs up the current channel\n";
+
+static char usage_answer[] =
+" Usage: ANSWER\n"
+" Answers channel if not already in answer state. Returns -1 on\n"
+" channel failure, or 0 if successful.\n";
+
+static char usage_waitfordigit[] =
+" Usage: WAIT FOR DIGIT <timeout>\n"
+" Waits up to 'timeout' milliseconds for channel to receive a DTMF digit.\n"
+" Returns -1 on channel failure, 0 if no digit is received in the timeout, or\n"
+" the numerical value of the ascii of the digit if one is received. Use -1\n"
+" for the timeout value if you desire the call to block indefinitely.\n";
+
+static char usage_sendtext[] =
+" Usage: SEND TEXT \"<text to send>\"\n"
+" Sends the given text on a channel. Most channels do not support the\n"
+" transmission of text. Returns 0 if text is sent, or if the channel does not\n"
+" support text transmission. Returns -1 only on error/hangup. Text\n"
+" consisting of greater than one word should be placed in quotes since the\n"
+" command only accepts a single argument.\n";
+
+static char usage_recvchar[] =
+" Usage: RECEIVE CHAR <timeout>\n"
+" Receives a character of text on a channel. Specify timeout to be the\n"
+" maximum time to wait for input in milliseconds, or 0 for infinite. Most channels\n"
+" do not support the reception of text. Returns the decimal value of the character\n"
+" if one is received, or 0 if the channel does not support text reception. Returns\n"
+" -1 only on error/hangup.\n";
+
+static char usage_recvtext[] =
+" Usage: RECEIVE TEXT <timeout>\n"
+" Receives a string of text on a channel. Specify timeout to be the\n"
+" maximum time to wait for input in milliseconds, or 0 for infinite. Most channels\n"
+" do not support the reception of text. Returns -1 for failure or 1 for success, and the string in parentheses.\n";
+
+static char usage_tddmode[] =
+" Usage: TDD MODE <on|off>\n"
+" Enable/Disable TDD transmission/reception on a channel. Returns 1 if\n"
+" successful, or 0 if channel is not TDD-capable.\n";
+
+static char usage_sendimage[] =
+" Usage: SEND IMAGE <image>\n"
+" Sends the given image on a channel. Most channels do not support the\n"
+" transmission of images. Returns 0 if image is sent, or if the channel does not\n"
+" support image transmission. Returns -1 only on error/hangup. Image names\n"
+" should not include extensions.\n";
+
+static char usage_streamfile[] =
+" Usage: STREAM FILE <filename> <escape digits> [sample offset]\n"
+" Send the given file, allowing playback to be interrupted by the given\n"
+" digits, if any. Use double quotes for the digits if you wish none to be\n"
+" permitted. If sample offset is provided then the audio will seek to sample\n"
+" offset before play starts. Returns 0 if playback completes without a digit\n"
+" being pressed, or the ASCII numerical value of the digit if one was pressed,\n"
+" or -1 on error or if the channel was disconnected. Remember, the file\n"
+" extension must not be included in the filename.\n";
+
+static char usage_controlstreamfile[] =
+" Usage: CONTROL STREAM FILE <filename> <escape digits> [skipms] [ffchar] [rewchr] [pausechr]\n"
+" Send the given file, allowing playback to be controled by the given\n"
+" digits, if any. Use double quotes for the digits if you wish none to be\n"
+" permitted. Returns 0 if playback completes without a digit\n"
+" being pressed, or the ASCII numerical value of the digit if one was pressed,\n"
+" or -1 on error or if the channel was disconnected. Remember, the file\n"
+" extension must not be included in the filename.\n\n"
+" Note: ffchar and rewchar default to * and # respectively.\n";
+
+static char usage_getoption[] =
+" Usage: GET OPTION <filename> <escape_digits> [timeout]\n"
+" Behaves similar to STREAM FILE but used with a timeout option.\n";
+
+static char usage_saynumber[] =
+" Usage: SAY NUMBER <number> <escape digits>\n"
+" Say a given number, returning early if any of the given DTMF digits\n"
+" are received on the channel. Returns 0 if playback completes without a digit\n"
+" being pressed, or the ASCII numerical value of the digit if one was pressed or\n"
+" -1 on error/hangup.\n";
+
+static char usage_saydigits[] =
+" Usage: SAY DIGITS <number> <escape digits>\n"
+" Say a given digit string, returning early if any of the given DTMF digits\n"
+" are received on the channel. Returns 0 if playback completes without a digit\n"
+" being pressed, or the ASCII numerical value of the digit if one was pressed or\n"
+" -1 on error/hangup.\n";
+
+static char usage_sayalpha[] =
+" Usage: SAY ALPHA <number> <escape digits>\n"
+" Say a given character string, returning early if any of the given DTMF digits\n"
+" are received on the channel. Returns 0 if playback completes without a digit\n"
+" being pressed, or the ASCII numerical value of the digit if one was pressed or\n"
+" -1 on error/hangup.\n";
+
+static char usage_saydate[] =
+" Usage: SAY DATE <date> <escape digits>\n"
+" Say a given date, returning early if any of the given DTMF digits are\n"
+" received on the channel. <date> is number of seconds elapsed since 00:00:00\n"
+" on January 1, 1970, Coordinated Universal Time (UTC). Returns 0 if playback\n"
+" completes without a digit being pressed, or the ASCII numerical value of the\n"
+" digit if one was pressed or -1 on error/hangup.\n";
+
+static char usage_saytime[] =
+" Usage: SAY TIME <time> <escape digits>\n"
+" Say a given time, returning early if any of the given DTMF digits are\n"
+" received on the channel. <time> is number of seconds elapsed since 00:00:00\n"
+" on January 1, 1970, Coordinated Universal Time (UTC). Returns 0 if playback\n"
+" completes without a digit being pressed, or the ASCII numerical value of the\n"
+" digit if one was pressed or -1 on error/hangup.\n";
+
+static char usage_saydatetime[] =
+" Usage: SAY DATETIME <time> <escape digits> [format] [timezone]\n"
+" Say a given time, returning early if any of the given DTMF digits are\n"
+" received on the channel. <time> is number of seconds elapsed since 00:00:00\n"
+" on January 1, 1970, Coordinated Universal Time (UTC). [format] is the format\n"
+" the time should be said in. See voicemail.conf (defaults to \"ABdY\n"
+" 'digits/at' IMp\"). Acceptable values for [timezone] can be found in\n"
+" /usr/share/zoneinfo. Defaults to machine default. Returns 0 if playback\n"
+" completes without a digit being pressed, or the ASCII numerical value of the\n"
+" digit if one was pressed or -1 on error/hangup.\n";
+
+static char usage_sayphonetic[] =
+" Usage: SAY PHONETIC <string> <escape digits>\n"
+" Say a given character string with phonetics, returning early if any of the\n"
+" given DTMF digits are received on the channel. Returns 0 if playback\n"
+" completes without a digit pressed, the ASCII numerical value of the digit\n"
+" if one was pressed, or -1 on error/hangup.\n";
+
+static char usage_getdata[] =
+" Usage: GET DATA <file to be streamed> [timeout] [max digits]\n"
+" Stream the given file, and recieve DTMF data. Returns the digits received\n"
+"from the channel at the other end.\n";
+
+static char usage_setcontext[] =
+" Usage: SET CONTEXT <desired context>\n"
+" Sets the context for continuation upon exiting the application.\n";
+
+static char usage_setextension[] =
+" Usage: SET EXTENSION <new extension>\n"
+" Changes the extension for continuation upon exiting the application.\n";
+
+static char usage_setpriority[] =
+" Usage: SET PRIORITY <priority>\n"
+" Changes the priority for continuation upon exiting the application.\n"
+" The priority must be a valid priority or label.\n";
+
+static char usage_recordfile[] =
+" Usage: RECORD FILE <filename> <format> <escape digits> <timeout> \\\n"
+" [offset samples] [BEEP] [s=silence]\n"
+" Record to a file until a given dtmf digit in the sequence is received\n"
+" Returns -1 on hangup or error. The format will specify what kind of file\n"
+" will be recorded. The timeout is the maximum record time in milliseconds, or\n"
+" -1 for no timeout. \"Offset samples\" is optional, and, if provided, will seek\n"
+" to the offset without exceeding the end of the file. \"silence\" is the number\n"
+" of seconds of silence allowed before the function returns despite the\n"
+" lack of dtmf digits or reaching timeout. Silence value must be\n"
+" preceeded by \"s=\" and is also optional.\n";
+
+static char usage_autohangup[] =
+" Usage: SET AUTOHANGUP <time>\n"
+" Cause the channel to automatically hangup at <time> seconds in the\n"
+" future. Of course it can be hungup before then as well. Setting to 0 will\n"
+" cause the autohangup feature to be disabled on this channel.\n";
+
+static char usage_noop[] =
+" Usage: NoOp\n"
+" Does nothing.\n";
+
+static agi_command commands[MAX_COMMANDS] = {
+ { { "answer", NULL }, handle_answer, "Answer channel", usage_answer },
+ { { "channel", "status", NULL }, handle_channelstatus, "Returns status of the connected channel", usage_channelstatus },
+ { { "database", "del", NULL }, handle_dbdel, "Removes database key/value", usage_dbdel },
+ { { "database", "deltree", NULL }, handle_dbdeltree, "Removes database keytree/value", usage_dbdeltree },
+ { { "database", "get", NULL }, handle_dbget, "Gets database value", usage_dbget },
+ { { "database", "put", NULL }, handle_dbput, "Adds/updates database value", usage_dbput },
+ { { "exec", NULL }, handle_exec, "Executes a given Application", usage_exec },
+ { { "get", "data", NULL }, handle_getdata, "Prompts for DTMF on a channel", usage_getdata },
+ { { "get", "full", "variable", NULL }, handle_getvariablefull, "Evaluates a channel expression", usage_getvariablefull },
+ { { "get", "option", NULL }, handle_getoption, "Stream file, prompt for DTMF, with timeout", usage_getoption },
+ { { "get", "variable", NULL }, handle_getvariable, "Gets a channel variable", usage_getvariable },
+ { { "hangup", NULL }, handle_hangup, "Hangup the current channel", usage_hangup },
+ { { "noop", NULL }, handle_noop, "Does nothing", usage_noop },
+ { { "receive", "char", NULL }, handle_recvchar, "Receives one character from channels supporting it", usage_recvchar },
+ { { "receive", "text", NULL }, handle_recvtext, "Receives text from channels supporting it", usage_recvtext },
+ { { "record", "file", NULL }, handle_recordfile, "Records to a given file", usage_recordfile },
+ { { "say", "alpha", NULL }, handle_sayalpha, "Says a given character string", usage_sayalpha },
+ { { "say", "digits", NULL }, handle_saydigits, "Says a given digit string", usage_saydigits },
+ { { "say", "number", NULL }, handle_saynumber, "Says a given number", usage_saynumber },
+ { { "say", "phonetic", NULL }, handle_sayphonetic, "Says a given character string with phonetics", usage_sayphonetic },
+ { { "say", "date", NULL }, handle_saydate, "Says a given date", usage_saydate },
+ { { "say", "time", NULL }, handle_saytime, "Says a given time", usage_saytime },
+ { { "say", "datetime", NULL }, handle_saydatetime, "Says a given time as specfied by the format given", usage_saydatetime },
+ { { "send", "image", NULL }, handle_sendimage, "Sends images to channels supporting it", usage_sendimage },
+ { { "send", "text", NULL }, handle_sendtext, "Sends text to channels supporting it", usage_sendtext },
+ { { "set", "autohangup", NULL }, handle_autohangup, "Autohangup channel in some time", usage_autohangup },
+ { { "set", "callerid", NULL }, handle_setcallerid, "Sets callerid for the current channel", usage_setcallerid },
+ { { "set", "context", NULL }, handle_setcontext, "Sets channel context", usage_setcontext },
+ { { "set", "extension", NULL }, handle_setextension, "Changes channel extension", usage_setextension },
+ { { "set", "music", NULL }, handle_setmusic, "Enable/Disable Music on hold generator", usage_setmusic },
+ { { "set", "priority", NULL }, handle_setpriority, "Set channel dialplan priority", usage_setpriority },
+ { { "set", "variable", NULL }, handle_setvariable, "Sets a channel variable", usage_setvariable },
+ { { "stream", "file", NULL }, handle_streamfile, "Sends audio file on channel", usage_streamfile },
+ { { "control", "stream", "file", NULL }, handle_controlstreamfile, "Sends audio file on channel and allows the listner to control the stream", usage_controlstreamfile },
+ { { "tdd", "mode", NULL }, handle_tddmode, "Toggles TDD mode (for the deaf)", usage_tddmode },
+ { { "verbose", NULL }, handle_verbose, "Logs a message to the asterisk verbose log", usage_verbose },
+ { { "wait", "for", "digit", NULL }, handle_waitfordigit, "Waits for a digit to be pressed", usage_waitfordigit },
+};
+
+static int help_workhorse(int fd, char *match[])
+{
+ char fullcmd[80];
+ char matchstr[80];
+ int x;
+ struct agi_command *e;
+ if (match)
+ ast_join(matchstr, sizeof(matchstr), match);
+ for (x=0;x<sizeof(commands)/sizeof(commands[0]);x++) {
+ e = &commands[x];
+ if (!e->cmda[0])
+ break;
+ /* Hide commands that start with '_' */
+ if ((e->cmda[0])[0] == '_')
+ continue;
+ ast_join(fullcmd, sizeof(fullcmd), e->cmda);
+ if (match && strncasecmp(matchstr, fullcmd, strlen(matchstr)))
+ continue;
+ ast_cli(fd, "%20.20s %s\n", fullcmd, e->summary);
+ }
+ return 0;
+}
+
+int ast_agi_register(agi_command *agi)
+{
+ int x;
+ for (x=0; x<MAX_COMMANDS - 1; x++) {
+ if (commands[x].cmda[0] == agi->cmda[0]) {
+ ast_log(LOG_WARNING, "Command already registered!\n");
+ return -1;
+ }
+ }
+ for (x=0; x<MAX_COMMANDS - 1; x++) {
+ if (!commands[x].cmda[0]) {
+ commands[x] = *agi;
+ return 0;
+ }
+ }
+ ast_log(LOG_WARNING, "No more room for new commands!\n");
+ return -1;
+}
+
+void ast_agi_unregister(agi_command *agi)
+{
+ int x;
+ for (x=0; x<MAX_COMMANDS - 1; x++) {
+ if (commands[x].cmda[0] == agi->cmda[0]) {
+ memset(&commands[x], 0, sizeof(agi_command));
+ }
+ }
+}
+
+static agi_command *find_command(char *cmds[], int exact)
+{
+ int x;
+ int y;
+ int match;
+
+ for (x=0; x < sizeof(commands) / sizeof(commands[0]); x++) {
+ if (!commands[x].cmda[0])
+ break;
+ /* start optimistic */
+ match = 1;
+ for (y=0; match && cmds[y]; y++) {
+ /* If there are no more words in the command (and we're looking for
+ an exact match) or there is a difference between the two words,
+ then this is not a match */
+ if (!commands[x].cmda[y] && !exact)
+ break;
+ /* don't segfault if the next part of a command doesn't exist */
+ if (!commands[x].cmda[y])
+ return NULL;
+ if (strcasecmp(commands[x].cmda[y], cmds[y]))
+ match = 0;
+ }
+ /* If more words are needed to complete the command then this is not
+ a candidate (unless we're looking for a really inexact answer */
+ if ((exact > -1) && commands[x].cmda[y])
+ match = 0;
+ if (match)
+ return &commands[x];
+ }
+ return NULL;
+}
+
+
+static int parse_args(char *s, int *max, char *argv[])
+{
+ int x=0;
+ int quoted=0;
+ int escaped=0;
+ int whitespace=1;
+ char *cur;
+
+ cur = s;
+ while(*s) {
+ switch(*s) {
+ case '"':
+ /* If it's escaped, put a literal quote */
+ if (escaped)
+ goto normal;
+ else
+ quoted = !quoted;
+ if (quoted && whitespace) {
+ /* If we're starting a quote, coming off white space start a new word, too */
+ argv[x++] = cur;
+ whitespace=0;
+ }
+ escaped = 0;
+ break;
+ case ' ':
+ case '\t':
+ if (!quoted && !escaped) {
+ /* If we're not quoted, mark this as whitespace, and
+ end the previous argument */
+ whitespace = 1;
+ *(cur++) = '\0';
+ } else
+ /* Otherwise, just treat it as anything else */
+ goto normal;
+ break;
+ case '\\':
+ /* If we're escaped, print a literal, otherwise enable escaping */
+ if (escaped) {
+ goto normal;
+ } else {
+ escaped=1;
+ }
+ break;
+ default:
+normal:
+ if (whitespace) {
+ if (x >= MAX_ARGS -1) {
+ ast_log(LOG_WARNING, "Too many arguments, truncating\n");
+ break;
+ }
+ /* Coming off of whitespace, start the next argument */
+ argv[x++] = cur;
+ whitespace=0;
+ }
+ *(cur++) = *s;
+ escaped=0;
+ }
+ s++;
+ }
+ /* Null terminate */
+ *(cur++) = '\0';
+ argv[x] = NULL;
+ *max = x;
+ return 0;
+}
+
+static int agi_handle_command(struct ast_channel *chan, AGI *agi, char *buf)
+{
+ char *argv[MAX_ARGS];
+ int argc = MAX_ARGS;
+ int res;
+ agi_command *c;
+
+ parse_args(buf, &argc, argv);
+ c = find_command(argv, 0);
+ if (c) {
+ /* If the AGI command being executed is an actual application (using agi exec)
+ the app field will be updated in pbx_exec via handle_exec */
+ if (chan->cdr && !ast_check_hangup(chan) && strcasecmp(argv[0], "EXEC"))
+ ast_cdr_setapp(chan->cdr, "AGI", buf);
+
+ res = c->handler(chan, agi, argc, argv);
+ switch(res) {
+ case RESULT_SHOWUSAGE:
+ fdprintf(agi->fd, "520-Invalid command syntax. Proper usage follows:\n");
+ fdprintf(agi->fd, "%s", c->usage);
+ fdprintf(agi->fd, "520 End of proper usage.\n");
+ break;
+ case AST_PBX_KEEPALIVE:
+ /* We've been asked to keep alive, so do so */
+ return AST_PBX_KEEPALIVE;
+ break;
+ case RESULT_FAILURE:
+ /* They've already given the failure. We've been hung up on so handle this
+ appropriately */
+ return -1;
+ }
+ } else {
+ fdprintf(agi->fd, "510 Invalid or unknown command\n");
+ }
+ return 0;
+}
+static enum agi_result run_agi(struct ast_channel *chan, char *request, AGI *agi, int pid, int *status, int dead)
+{
+ struct ast_channel *c;
+ int outfd;
+ int ms;
+ enum agi_result returnstatus = AGI_RESULT_SUCCESS;
+ struct ast_frame *f;
+ char buf[AGI_BUF_LEN];
+ char *res = NULL;
+ FILE *readf;
+ /* how many times we'll retry if ast_waitfor_nandfs will return without either
+ channel or file descriptor in case select is interrupted by a system call (EINTR) */
+ int retry = AGI_NANDFS_RETRY;
+
+ if (!(readf = fdopen(agi->ctrl, "r"))) {
+ ast_log(LOG_WARNING, "Unable to fdopen file descriptor\n");
+ if (pid > -1)
+ kill(pid, SIGHUP);
+ close(agi->ctrl);
+ return AGI_RESULT_FAILURE;
+ }
+ setlinebuf(readf);
+ setup_env(chan, request, agi->fd, (agi->audio > -1));
+ for (;;) {
+ ms = -1;
+ c = ast_waitfor_nandfds(&chan, dead ? 0 : 1, &agi->ctrl, 1, NULL, &outfd, &ms);
+ if (c) {
+ retry = AGI_NANDFS_RETRY;
+ /* Idle the channel until we get a command */
+ f = ast_read(c);
+ if (!f) {
+ ast_log(LOG_DEBUG, "%s hungup\n", chan->name);
+ returnstatus = AGI_RESULT_HANGUP;
+ break;
+ } else {
+ /* If it's voice, write it to the audio pipe */
+ if ((agi->audio > -1) && (f->frametype == AST_FRAME_VOICE)) {
+ /* Write, ignoring errors */
+ if (write(agi->audio, f->data, f->datalen) < 0) {
+ }
+ }
+ ast_frfree(f);
+ }
+ } else if (outfd > -1) {
+ size_t len = sizeof(buf);
+ size_t buflen = 0;
+
+ retry = AGI_NANDFS_RETRY;
+ buf[0] = '\0';
+
+ while (buflen < (len - 1)) {
+ res = fgets(buf + buflen, len, readf);
+ if (feof(readf))
+ break;
+ if (ferror(readf) && ((errno != EINTR) && (errno != EAGAIN)))
+ break;
+ if (res != NULL && !agi->fast)
+ break;
+ buflen = strlen(buf);
+ if (buflen && buf[buflen - 1] == '\n')
+ break;
+ len -= buflen;
+ if (agidebug)
+ ast_verbose( "AGI Rx << temp buffer %s - errno %s\n", buf, strerror(errno));
+ }
+
+ if (!buf[0]) {
+ /* Program terminated */
+ if (returnstatus)
+ returnstatus = -1;
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "AGI Script %s completed, returning %d\n", request, returnstatus);
+ if (pid > 0)
+ waitpid(pid, status, 0);
+ /* No need to kill the pid anymore, since they closed us */
+ pid = -1;
+ break;
+ }
+
+ /* Special case for inability to execute child process */
+ if (*buf && strncasecmp(buf, "failure", 7) == 0) {
+ returnstatus = AGI_RESULT_FAILURE;
+ break;
+ }
+
+ /* get rid of trailing newline, if any */
+ if (*buf && buf[strlen(buf) - 1] == '\n')
+ buf[strlen(buf) - 1] = 0;
+ if (agidebug)
+ ast_verbose("AGI Rx << %s\n", buf);
+ returnstatus |= agi_handle_command(chan, agi, buf);
+ /* If the handle_command returns -1, we need to stop */
+ if ((returnstatus < 0) || (returnstatus == AST_PBX_KEEPALIVE)) {
+ break;
+ }
+ } else {
+ if (--retry <= 0) {
+ ast_log(LOG_WARNING, "No channel, no fd?\n");
+ returnstatus = AGI_RESULT_FAILURE;
+ break;
+ }
+ }
+ }
+ /* Notify process */
+ if (pid > -1) {
+ const char *sighup = pbx_builtin_getvar_helper(chan, "AGISIGHUP");
+ if (ast_strlen_zero(sighup) || !ast_false(sighup)) {
+ if (kill(pid, SIGHUP)) {
+ ast_log(LOG_WARNING, "unable to send SIGHUP to AGI process %d: %s\n", pid, strerror(errno));
+ } else { /* Give the process a chance to die */
+ usleep(1);
+ }
+ }
+ waitpid(pid, status, WNOHANG);
+ }
+ fclose(readf);
+ return returnstatus;
+}
+
+static int handle_showagi(int fd, int argc, char *argv[])
+{
+ struct agi_command *e;
+ char fullcmd[80];
+ if ((argc < 2))
+ return RESULT_SHOWUSAGE;
+ if (argc > 2) {
+ e = find_command(argv + 2, 1);
+ if (e)
+ ast_cli(fd, "%s", e->usage);
+ else {
+ if (find_command(argv + 2, -1)) {
+ return help_workhorse(fd, argv + 1);
+ } else {
+ ast_join(fullcmd, sizeof(fullcmd), argv+1);
+ ast_cli(fd, "No such command '%s'.\n", fullcmd);
+ }
+ }
+ } else {
+ return help_workhorse(fd, NULL);
+ }
+ return RESULT_SUCCESS;
+}
+
+static int handle_agidumphtml(int fd, int argc, char *argv[])
+{
+ struct agi_command *e;
+ char fullcmd[80];
+ int x;
+ FILE *htmlfile;
+
+ if ((argc < 3))
+ return RESULT_SHOWUSAGE;
+
+ if (!(htmlfile = fopen(argv[2], "wt"))) {
+ ast_cli(fd, "Could not create file '%s'\n", argv[2]);
+ return RESULT_SHOWUSAGE;
+ }
+
+ fprintf(htmlfile, "<HTML>\n<HEAD>\n<TITLE>AGI Commands</TITLE>\n</HEAD>\n");
+ fprintf(htmlfile, "<BODY>\n<CENTER><B><H1>AGI Commands</H1></B></CENTER>\n\n");
+
+
+ fprintf(htmlfile, "<TABLE BORDER=\"0\" CELLSPACING=\"10\">\n");
+
+ for (x=0;x<sizeof(commands)/sizeof(commands[0]);x++) {
+ char *stringp, *tempstr;
+
+ e = &commands[x];
+ if (!e->cmda[0]) /* end ? */
+ break;
+ /* Hide commands that start with '_' */
+ if ((e->cmda[0])[0] == '_')
+ continue;
+ ast_join(fullcmd, sizeof(fullcmd), e->cmda);
+
+ fprintf(htmlfile, "<TR><TD><TABLE BORDER=\"1\" CELLPADDING=\"5\" WIDTH=\"100%%\">\n");
+ fprintf(htmlfile, "<TR><TH ALIGN=\"CENTER\"><B>%s - %s</B></TH></TR>\n", fullcmd,e->summary);
+
+ stringp=e->usage;
+ tempstr = strsep(&stringp, "\n");
+
+ fprintf(htmlfile, "<TR><TD ALIGN=\"CENTER\">%s</TD></TR>\n", tempstr);
+
+ fprintf(htmlfile, "<TR><TD ALIGN=\"CENTER\">\n");
+ while ((tempstr = strsep(&stringp, "\n")) != NULL)
+ fprintf(htmlfile, "%s<BR>\n",tempstr);
+ fprintf(htmlfile, "</TD></TR>\n");
+ fprintf(htmlfile, "</TABLE></TD></TR>\n\n");
+
+ }
+
+ fprintf(htmlfile, "</TABLE>\n</BODY>\n</HTML>\n");
+ fclose(htmlfile);
+ ast_cli(fd, "AGI HTML Commands Dumped to: %s\n", argv[2]);
+ return RESULT_SUCCESS;
+}
+
+static int agi_exec_full(struct ast_channel *chan, void *data, int enhanced, int dead)
+{
+ enum agi_result res;
+ struct ast_module_user *u;
+ char *argv[MAX_ARGS];
+ char buf[AGI_BUF_LEN] = "";
+ char *tmp = (char *)buf;
+ int argc = 0;
+ int fds[2];
+ int efd = -1;
+ int pid;
+ char *stringp;
+ AGI agi;
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "AGI requires an argument (script)\n");
+ return -1;
+ }
+ ast_copy_string(buf, data, sizeof(buf));
+
+ memset(&agi, 0, sizeof(agi));
+ while ((stringp = strsep(&tmp, "|")) && argc < MAX_ARGS-1)
+ argv[argc++] = stringp;
+ argv[argc] = NULL;
+
+ u = ast_module_user_add(chan);
+#if 0
+ /* Answer if need be */
+ if (chan->_state != AST_STATE_UP) {
+ if (ast_answer(chan)) {
+ LOCAL_USER_REMOVE(u);
+ return -1;
+ }
+ }
+#endif
+ ast_replace_sigchld();
+ res = launch_script(argv[0], argv, fds, enhanced ? &efd : NULL, &pid);
+ if (res == AGI_RESULT_SUCCESS || res == AGI_RESULT_SUCCESS_FAST) {
+ int status = 0;
+ agi.fd = fds[1];
+ agi.ctrl = fds[0];
+ agi.audio = efd;
+ agi.fast = (res == AGI_RESULT_SUCCESS_FAST) ? 1 : 0;
+ res = run_agi(chan, argv[0], &agi, pid, &status, dead);
+ /* If the fork'd process returns non-zero, set AGISTATUS to FAILURE */
+ if ((res == AGI_RESULT_SUCCESS || res == AGI_RESULT_SUCCESS_FAST) && status)
+ res = AGI_RESULT_FAILURE;
+ if (fds[1] != fds[0])
+ close(fds[1]);
+ if (efd > -1)
+ close(efd);
+ }
+ ast_unreplace_sigchld();
+ ast_module_user_remove(u);
+
+ switch (res) {
+ case AGI_RESULT_SUCCESS:
+ case AGI_RESULT_SUCCESS_FAST:
+ pbx_builtin_setvar_helper(chan, "AGISTATUS", "SUCCESS");
+ break;
+ case AGI_RESULT_FAILURE:
+ pbx_builtin_setvar_helper(chan, "AGISTATUS", "FAILURE");
+ break;
+ case AGI_RESULT_HANGUP:
+ pbx_builtin_setvar_helper(chan, "AGISTATUS", "HANGUP");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int agi_exec(struct ast_channel *chan, void *data)
+{
+ if (chan->_softhangup)
+ ast_log(LOG_WARNING, "If you want to run AGI on hungup channels you should use DeadAGI!\n");
+ return agi_exec_full(chan, data, 0, 0);
+}
+
+static int eagi_exec(struct ast_channel *chan, void *data)
+{
+ int readformat;
+ int res;
+
+ if (chan->_softhangup)
+ ast_log(LOG_WARNING, "If you want to run AGI on hungup channels you should use DeadAGI!\n");
+ readformat = chan->readformat;
+ if (ast_set_read_format(chan, AST_FORMAT_SLINEAR)) {
+ ast_log(LOG_WARNING, "Unable to set channel '%s' to linear mode\n", chan->name);
+ return -1;
+ }
+ res = agi_exec_full(chan, data, 1, 0);
+ if (!res) {
+ if (ast_set_read_format(chan, readformat)) {
+ ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n", chan->name, ast_getformatname(readformat));
+ }
+ }
+ return res;
+}
+
+static int deadagi_exec(struct ast_channel *chan, void *data)
+{
+ if (!ast_check_hangup(chan))
+ ast_log(LOG_WARNING,"Running DeadAGI on a live channel will cause problems, please use AGI\n");
+ return agi_exec_full(chan, data, 0, 1);
+}
+
+static char showagi_help[] =
+"Usage: agi show [topic]\n"
+" When called with a topic as an argument, displays usage\n"
+" information on the given command. If called without a\n"
+" topic, it provides a list of AGI commands.\n";
+
+
+static char dumpagihtml_help[] =
+"Usage: agi dumphtml <filename>\n"
+" Dumps the agi command list in html format to given filename\n";
+
+static struct ast_cli_entry cli_show_agi_deprecated = {
+ { "show", "agi", NULL },
+ handle_showagi, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_dump_agihtml_deprecated = {
+ { "dump", "agihtml", NULL },
+ handle_agidumphtml, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_agi_no_debug_deprecated = {
+ { "agi", "no", "debug", NULL },
+ agi_no_debug_deprecated, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_agi[] = {
+ { { "agi", "debug", NULL },
+ agi_do_debug, "Enable AGI debugging",
+ debug_usage },
+
+ { { "agi", "debug", "off", NULL },
+ agi_no_debug, "Disable AGI debugging",
+ no_debug_usage, NULL, &cli_agi_no_debug_deprecated },
+
+ { { "agi", "show", NULL },
+ handle_showagi, "List AGI commands or specific help",
+ showagi_help, NULL, &cli_show_agi_deprecated },
+
+ { { "agi", "dumphtml", NULL },
+ handle_agidumphtml, "Dumps a list of agi commands in html format",
+ dumpagihtml_help, NULL, &cli_dump_agihtml_deprecated },
+};
+
+static int unload_module(void)
+{
+ ast_module_user_hangup_all();
+ ast_cli_unregister_multiple(cli_agi, sizeof(cli_agi) / sizeof(struct ast_cli_entry));
+ ast_unregister_application(eapp);
+ ast_unregister_application(deadapp);
+ return ast_unregister_application(app);
+}
+
+static int load_module(void)
+{
+ ast_cli_register_multiple(cli_agi, sizeof(cli_agi) / sizeof(struct ast_cli_entry));
+ ast_register_application(deadapp, deadagi_exec, deadsynopsis, descrip);
+ ast_register_application(eapp, eagi_exec, esynopsis, descrip);
+ return ast_register_application(app, agi_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Asterisk Gateway Interface (AGI)",
+ .load = load_module,
+ .unload = unload_module,
+ );
diff --git a/1.4.23-rc4/res/res_clioriginate.c b/1.4.23-rc4/res/res_clioriginate.c
new file mode 100644
index 000000000..f272d79e8
--- /dev/null
+++ b/1.4.23-rc4/res/res_clioriginate.c
@@ -0,0 +1,175 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2005 - 2006, Digium, Inc.
+ *
+ * Russell Bryant <russell@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \author Russell Bryant <russell@digium.com>
+ *
+ * \brief Originate calls via the CLI
+ *
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$");
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/logger.h"
+#include "asterisk/module.h"
+#include "asterisk/cli.h"
+#include "asterisk/utils.h"
+#include "asterisk/frame.h"
+
+/*! The timeout for originated calls, in seconds */
+#define TIMEOUT 30
+
+static char orig_help[] =
+" There are two ways to use this command. A call can be originated between a\n"
+"channel and a specific application, or between a channel and an extension in\n"
+"the dialplan. This is similar to call files or the manager originate action.\n"
+"Calls originated with this command are given a timeout of 30 seconds.\n\n"
+
+"Usage1: originate <tech/data> application <appname> [appdata]\n"
+" This will originate a call between the specified channel tech/data and the\n"
+"given application. Arguments to the application are optional. If the given\n"
+"arguments to the application include spaces, all of the arguments to the\n"
+"application need to be placed in quotation marks.\n\n"
+
+"Usage2: originate <tech/data> extension [exten@][context]\n"
+" This will originate a call between the specified channel tech/data and the\n"
+"given extension. If no context is specified, the 'default' context will be\n"
+"used. If no extension is given, the 's' extension will be used.\n";
+
+static int handle_orig(int fd, int argc, char *argv[]);
+static char *complete_orig(const char *line, const char *word, int pos, int state);
+
+struct ast_cli_entry cli_cliorig[] = {
+ { { "originate", NULL },
+ handle_orig, "Originate a call",
+ orig_help, complete_orig },
+};
+
+static int orig_app(int fd, const char *chan, const char *app, const char *appdata)
+{
+ char *chantech;
+ char *chandata;
+ int reason = 0;
+
+ if (ast_strlen_zero(app))
+ return RESULT_SHOWUSAGE;
+
+ chandata = ast_strdupa(chan);
+
+ chantech = strsep(&chandata, "/");
+ if (!chandata) {
+ ast_cli(fd, "*** No data provided after channel type! ***\n");
+ return RESULT_SHOWUSAGE;
+ }
+
+ ast_pbx_outgoing_app(chantech, AST_FORMAT_SLINEAR, chandata, TIMEOUT * 1000, app, appdata, &reason, 0, NULL, NULL, NULL, NULL, NULL);
+
+ return RESULT_SUCCESS;
+}
+
+static int orig_exten(int fd, const char *chan, const char *data)
+{
+ char *chantech;
+ char *chandata;
+ char *exten = NULL;
+ char *context = NULL;
+ int reason = 0;
+
+ chandata = ast_strdupa(chan);
+
+ chantech = strsep(&chandata, "/");
+ if (!chandata) {
+ ast_cli(fd, "*** No data provided after channel type! ***\n");
+ return RESULT_SHOWUSAGE;
+ }
+
+ if (!ast_strlen_zero(data)) {
+ context = ast_strdupa(data);
+ exten = strsep(&context, "@");
+ }
+
+ if (ast_strlen_zero(exten))
+ exten = "s";
+ if (ast_strlen_zero(context))
+ context = "default";
+
+ ast_pbx_outgoing_exten(chantech, AST_FORMAT_SLINEAR, chandata, TIMEOUT * 1000, context, exten, 1, &reason, 0, NULL, NULL, NULL, NULL, NULL);
+
+ return RESULT_SUCCESS;
+}
+
+static int handle_orig(int fd, int argc, char *argv[])
+{
+ int res;
+
+ if (ast_strlen_zero(argv[1]) || ast_strlen_zero(argv[2]))
+ return RESULT_SHOWUSAGE;
+
+ /* ugly, can be removed when CLI entries have ast_module pointers */
+ ast_module_ref(ast_module_info->self);
+
+ if (!strcasecmp("application", argv[2])) {
+ res = orig_app(fd, argv[1], argv[3], argv[4]);
+ } else if (!strcasecmp("extension", argv[2])) {
+ res = orig_exten(fd, argv[1], argv[3]);
+ } else
+ res = RESULT_SHOWUSAGE;
+
+ ast_module_unref(ast_module_info->self);
+
+ return res;
+}
+
+static char *complete_orig(const char *line, const char *word, int pos, int state)
+{
+ static char *choices[] = { "application", "extension", NULL };
+ char *ret;
+
+ if (pos != 2)
+ return NULL;
+
+ /* ugly, can be removed when CLI entries have ast_module pointers */
+ ast_module_ref(ast_module_info->self);
+ ret = ast_cli_complete(word, choices, state);
+ ast_module_unref(ast_module_info->self);
+
+ return ret;
+}
+
+static int unload_module(void)
+{
+ ast_cli_unregister_multiple(cli_cliorig, sizeof(cli_cliorig) / sizeof(struct ast_cli_entry));
+ return 0;
+}
+
+static int load_module(void)
+{
+ ast_cli_register_multiple(cli_cliorig, sizeof(cli_cliorig) / sizeof(struct ast_cli_entry));
+ return 0;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Call origination from the CLI");
diff --git a/1.4.23-rc4/res/res_config_odbc.c b/1.4.23-rc4/res/res_config_odbc.c
new file mode 100644
index 000000000..7f11b5db3
--- /dev/null
+++ b/1.4.23-rc4/res/res_config_odbc.c
@@ -0,0 +1,564 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * Copyright (C) 2004 - 2005 Anthony Minessale II <anthmct@yahoo.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief odbc+odbc plugin for portable configuration engine
+ *
+ * \author Mark Spencer <markster@digium.com>
+ * \author Anthony Minessale II <anthmct@yahoo.com>
+ *
+ * \arg http://www.unixodbc.org
+ */
+
+/*** MODULEINFO
+ <depend>unixodbc</depend>
+ <depend>ltdl</depend>
+ <depend>res_odbc</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/lock.h"
+#include "asterisk/options.h"
+#include "asterisk/res_odbc.h"
+#include "asterisk/utils.h"
+
+struct custom_prepare_struct {
+ const char *sql;
+ const char *extra;
+ va_list ap;
+};
+
+static SQLHSTMT custom_prepare(struct odbc_obj *obj, void *data)
+{
+ int res, x = 1;
+ struct custom_prepare_struct *cps = data;
+ const char *newparam, *newval;
+ SQLHSTMT stmt;
+ va_list ap;
+
+ va_copy(ap, cps->ap);
+
+ res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
+ return NULL;
+ }
+
+ res = SQLPrepare(stmt, (unsigned char *)cps->sql, SQL_NTS);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", cps->sql);
+ SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+ return NULL;
+ }
+
+ while ((newparam = va_arg(ap, const char *))) {
+ newval = va_arg(ap, const char *);
+ SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(newval), 0, (void *)newval, 0, NULL);
+ }
+ va_end(ap);
+
+ if (!ast_strlen_zero(cps->extra))
+ SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(cps->extra), 0, (void *)cps->extra, 0, NULL);
+ return stmt;
+}
+
+static struct ast_variable *realtime_odbc(const char *database, const char *table, va_list ap)
+{
+ struct odbc_obj *obj;
+ SQLHSTMT stmt;
+ char sql[1024];
+ char coltitle[256];
+ char rowdata[2048];
+ char *op;
+ const char *newparam, *newval;
+ char *stringp;
+ char *chunk;
+ SQLSMALLINT collen;
+ int res;
+ int x;
+ struct ast_variable *var=NULL, *prev=NULL;
+ SQLULEN colsize;
+ SQLSMALLINT colcount=0;
+ SQLSMALLINT datatype;
+ SQLSMALLINT decimaldigits;
+ SQLSMALLINT nullable;
+ SQLLEN indicator;
+ va_list aq;
+ struct custom_prepare_struct cps = { .sql = sql };
+
+ va_copy(cps.ap, ap);
+ va_copy(aq, ap);
+
+ if (!table)
+ return NULL;
+
+ obj = ast_odbc_request_obj(database, 0);
+
+ if (!obj) {
+ ast_log(LOG_ERROR, "No database handle available with the name of '%s' (check res_odbc.conf)\n", database);
+ return NULL;
+ }
+
+ newparam = va_arg(aq, const char *);
+ if (!newparam) {
+ ast_odbc_release_obj(obj);
+ return NULL;
+ }
+ newval = va_arg(aq, const char *);
+ op = !strchr(newparam, ' ') ? " =" : "";
+ snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s ?%s", table, newparam, op,
+ strcasestr(newparam, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\'" : "");
+ while((newparam = va_arg(aq, const char *))) {
+ op = !strchr(newparam, ' ') ? " =" : "";
+ snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s ?%s", newparam, op,
+ strcasestr(newparam, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\'" : "");
+ newval = va_arg(aq, const char *);
+ }
+ va_end(aq);
+
+ stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps);
+
+ if (!stmt) {
+ ast_odbc_release_obj(obj);
+ return NULL;
+ }
+
+ res = SQLNumResultCols(stmt, &colcount);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
+ SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ return NULL;
+ }
+
+ res = SQLFetch(stmt);
+ if (res == SQL_NO_DATA) {
+ SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ return NULL;
+ }
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
+ SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ return NULL;
+ }
+ for (x = 0; x < colcount; x++) {
+ rowdata[0] = '\0';
+ collen = sizeof(coltitle);
+ res = SQLDescribeCol(stmt, x + 1, (unsigned char *)coltitle, sizeof(coltitle), &collen,
+ &datatype, &colsize, &decimaldigits, &nullable);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
+ if (var)
+ ast_variables_destroy(var);
+ ast_odbc_release_obj(obj);
+ return NULL;
+ }
+
+ indicator = 0;
+ res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), &indicator);
+ if (indicator == SQL_NULL_DATA)
+ continue;
+
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
+ if (var)
+ ast_variables_destroy(var);
+ ast_odbc_release_obj(obj);
+ return NULL;
+ }
+ stringp = rowdata;
+ while(stringp) {
+ chunk = strsep(&stringp, ";");
+ if (!ast_strlen_zero(ast_strip(chunk))) {
+ if (prev) {
+ prev->next = ast_variable_new(coltitle, chunk);
+ if (prev->next)
+ prev = prev->next;
+ } else
+ prev = var = ast_variable_new(coltitle, chunk);
+ }
+ }
+ }
+
+
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ return var;
+}
+
+static struct ast_config *realtime_multi_odbc(const char *database, const char *table, va_list ap)
+{
+ struct odbc_obj *obj;
+ SQLHSTMT stmt;
+ char sql[1024];
+ char coltitle[256];
+ char rowdata[2048];
+ const char *initfield=NULL;
+ char *op;
+ const char *newparam, *newval;
+ char *stringp;
+ char *chunk;
+ SQLSMALLINT collen;
+ int res;
+ int x;
+ struct ast_variable *var=NULL;
+ struct ast_config *cfg=NULL;
+ struct ast_category *cat=NULL;
+ struct ast_realloca ra;
+ SQLULEN colsize;
+ SQLSMALLINT colcount=0;
+ SQLSMALLINT datatype;
+ SQLSMALLINT decimaldigits;
+ SQLSMALLINT nullable;
+ SQLLEN indicator;
+ struct custom_prepare_struct cps = { .sql = sql };
+ va_list aq;
+
+ va_copy(cps.ap, ap);
+ va_copy(aq, ap);
+
+ if (!table)
+ return NULL;
+ memset(&ra, 0, sizeof(ra));
+
+ obj = ast_odbc_request_obj(database, 0);
+ if (!obj)
+ return NULL;
+
+ newparam = va_arg(aq, const char *);
+ if (!newparam) {
+ ast_odbc_release_obj(obj);
+ return NULL;
+ }
+ initfield = ast_strdupa(newparam);
+ if ((op = strchr(initfield, ' ')))
+ *op = '\0';
+ newval = va_arg(aq, const char *);
+ op = !strchr(newparam, ' ') ? " =" : "";
+ snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s ?%s", table, newparam, op,
+ strcasestr(newparam, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\'" : "");
+ while((newparam = va_arg(aq, const char *))) {
+ op = !strchr(newparam, ' ') ? " =" : "";
+ snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s ?%s", newparam, op,
+ strcasestr(newparam, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\'" : "");
+ newval = va_arg(aq, const char *);
+ }
+ if (initfield)
+ snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " ORDER BY %s", initfield);
+ va_end(aq);
+
+ stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps);
+
+ if (!stmt) {
+ ast_odbc_release_obj(obj);
+ return NULL;
+ }
+
+ res = SQLNumResultCols(stmt, &colcount);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ return NULL;
+ }
+
+ cfg = ast_config_new();
+ if (!cfg) {
+ ast_log(LOG_WARNING, "Out of memory!\n");
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ return NULL;
+ }
+
+ while ((res=SQLFetch(stmt)) != SQL_NO_DATA) {
+ var = NULL;
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
+ continue;
+ }
+ cat = ast_category_new("");
+ if (!cat) {
+ ast_log(LOG_WARNING, "Out of memory!\n");
+ continue;
+ }
+ for (x=0;x<colcount;x++) {
+ rowdata[0] = '\0';
+ collen = sizeof(coltitle);
+ res = SQLDescribeCol(stmt, x + 1, (unsigned char *)coltitle, sizeof(coltitle), &collen,
+ &datatype, &colsize, &decimaldigits, &nullable);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
+ ast_category_destroy(cat);
+ continue;
+ }
+
+ indicator = 0;
+ res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), &indicator);
+ if (indicator == SQL_NULL_DATA)
+ continue;
+
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
+ ast_category_destroy(cat);
+ continue;
+ }
+ stringp = rowdata;
+ while(stringp) {
+ chunk = strsep(&stringp, ";");
+ if (!ast_strlen_zero(ast_strip(chunk))) {
+ if (initfield && !strcmp(initfield, coltitle))
+ ast_category_rename(cat, chunk);
+ var = ast_variable_new(coltitle, chunk);
+ ast_variable_append(cat, var);
+ }
+ }
+ }
+ ast_category_append(cfg, cat);
+ }
+
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ return cfg;
+}
+
+static int update_odbc(const char *database, const char *table, const char *keyfield, const char *lookup, va_list ap)
+{
+ struct odbc_obj *obj;
+ SQLHSTMT stmt;
+ char sql[256];
+ SQLLEN rowcount=0;
+ const char *newparam, *newval;
+ int res;
+ va_list aq;
+ struct custom_prepare_struct cps = { .sql = sql, .extra = lookup };
+
+ va_copy(cps.ap, ap);
+ va_copy(aq, ap);
+
+ if (!table)
+ return -1;
+
+ obj = ast_odbc_request_obj(database, 0);
+ if (!obj)
+ return -1;
+
+ newparam = va_arg(aq, const char *);
+ if (!newparam) {
+ ast_odbc_release_obj(obj);
+ return -1;
+ }
+ newval = va_arg(aq, const char *);
+ snprintf(sql, sizeof(sql), "UPDATE %s SET %s=?", table, newparam);
+ while((newparam = va_arg(aq, const char *))) {
+ snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ", %s=?", newparam);
+ newval = va_arg(aq, const char *);
+ }
+ va_end(aq);
+ snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " WHERE %s=?", keyfield);
+
+ stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps);
+
+ if (!stmt) {
+ ast_odbc_release_obj(obj);
+ return -1;
+ }
+
+ res = SQLRowCount(stmt, &rowcount);
+ SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "SQL Row Count error!\n[%s]\n\n", sql);
+ return -1;
+ }
+
+ if (rowcount >= 0)
+ return (int)rowcount;
+
+ return -1;
+}
+
+struct config_odbc_obj {
+ char *sql;
+ unsigned long cat_metric;
+ char category[128];
+ char var_name[128];
+ char var_val[1024]; /* changed from 128 to 1024 via bug 8251 */
+ SQLLEN err;
+};
+
+static SQLHSTMT config_odbc_prepare(struct odbc_obj *obj, void *data)
+{
+ struct config_odbc_obj *q = data;
+ SQLHSTMT sth;
+ int res;
+
+ res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &sth);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ if (option_verbose > 3)
+ ast_verbose( VERBOSE_PREFIX_4 "Failure in AllocStatement %d\n", res);
+ return NULL;
+ }
+
+ res = SQLPrepare(sth, (unsigned char *)q->sql, SQL_NTS);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ if (option_verbose > 3)
+ ast_verbose( VERBOSE_PREFIX_4 "Error in PREPARE %d\n", res);
+ SQLFreeHandle(SQL_HANDLE_STMT, sth);
+ return NULL;
+ }
+
+ SQLBindCol(sth, 1, SQL_C_ULONG, &q->cat_metric, sizeof(q->cat_metric), &q->err);
+ SQLBindCol(sth, 2, SQL_C_CHAR, q->category, sizeof(q->category), &q->err);
+ SQLBindCol(sth, 3, SQL_C_CHAR, q->var_name, sizeof(q->var_name), &q->err);
+ SQLBindCol(sth, 4, SQL_C_CHAR, q->var_val, sizeof(q->var_val), &q->err);
+
+ return sth;
+}
+
+static struct ast_config *config_odbc(const char *database, const char *table, const char *file, struct ast_config *cfg, int withcomments)
+{
+ struct ast_variable *new_v;
+ struct ast_category *cur_cat;
+ int res = 0;
+ struct odbc_obj *obj;
+ char sqlbuf[1024] = "";
+ char *sql = sqlbuf;
+ size_t sqlleft = sizeof(sqlbuf);
+ unsigned int last_cat_metric = 0;
+ SQLSMALLINT rowcount = 0;
+ SQLHSTMT stmt;
+ char last[128] = "";
+ struct config_odbc_obj q;
+
+ memset(&q, 0, sizeof(q));
+
+ if (!file || !strcmp (file, "res_config_odbc.conf"))
+ return NULL; /* cant configure myself with myself ! */
+
+ obj = ast_odbc_request_obj(database, 0);
+ if (!obj)
+ return NULL;
+
+ ast_build_string(&sql, &sqlleft, "SELECT cat_metric, category, var_name, var_val FROM %s ", table);
+ ast_build_string(&sql, &sqlleft, "WHERE filename='%s' AND commented=0 ", file);
+ ast_build_string(&sql, &sqlleft, "ORDER BY cat_metric DESC, var_metric ASC, category, var_name ");
+ q.sql = sqlbuf;
+
+ stmt = ast_odbc_prepare_and_execute(obj, config_odbc_prepare, &q);
+
+ if (!stmt) {
+ ast_log(LOG_WARNING, "SQL select error!\n[%s]\n\n", sql);
+ ast_odbc_release_obj(obj);
+ return NULL;
+ }
+
+ res = SQLNumResultCols(stmt, &rowcount);
+
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "SQL NumResultCols error!\n[%s]\n\n", sql);
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ return NULL;
+ }
+
+ if (!rowcount) {
+ ast_log(LOG_NOTICE, "found nothing\n");
+ ast_odbc_release_obj(obj);
+ return cfg;
+ }
+
+ cur_cat = ast_config_get_current_category(cfg);
+
+ while ((res = SQLFetch(stmt)) != SQL_NO_DATA) {
+ if (!strcmp (q.var_name, "#include")) {
+ if (!ast_config_internal_load(q.var_val, cfg, 0)) {
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ return NULL;
+ }
+ continue;
+ }
+ if (strcmp(last, q.category) || last_cat_metric != q.cat_metric) {
+ cur_cat = ast_category_new(q.category);
+ if (!cur_cat) {
+ ast_log(LOG_WARNING, "Out of memory!\n");
+ break;
+ }
+ strcpy(last, q.category);
+ last_cat_metric = q.cat_metric;
+ ast_category_append(cfg, cur_cat);
+ }
+
+ new_v = ast_variable_new(q.var_name, q.var_val);
+ ast_variable_append(cur_cat, new_v);
+ }
+
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ return cfg;
+}
+
+static struct ast_config_engine odbc_engine = {
+ .name = "odbc",
+ .load_func = config_odbc,
+ .realtime_func = realtime_odbc,
+ .realtime_multi_func = realtime_multi_odbc,
+ .update_func = update_odbc
+};
+
+static int unload_module (void)
+{
+ ast_module_user_hangup_all();
+ ast_config_engine_deregister(&odbc_engine);
+ if (option_verbose)
+ ast_verbose("res_config_odbc unloaded.\n");
+ return 0;
+}
+
+static int load_module (void)
+{
+ ast_config_engine_register(&odbc_engine);
+ if (option_verbose)
+ ast_verbose("res_config_odbc loaded.\n");
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "ODBC Configuration",
+ .load = load_module,
+ .unload = unload_module,
+ );
diff --git a/1.4.23-rc4/res/res_config_pgsql.c b/1.4.23-rc4/res/res_config_pgsql.c
new file mode 100644
index 000000000..494497033
--- /dev/null
+++ b/1.4.23-rc4/res/res_config_pgsql.c
@@ -0,0 +1,842 @@
+/*
+ * Asterisk -- A telephony toolkit for Linux.
+ *
+ * Copyright (C) 1999-2005, Digium, Inc.
+ *
+ * Manuel Guesdon <mguesdon@oxymium.net> - Postgresql RealTime Driver Author/Adaptor
+ * Mark Spencer <markster@digium.com> - Asterisk Author
+ * Matthew Boehm <mboehm@cytelcom.com> - MySQL RealTime Driver Author
+ *
+ * res_config_pgsql.c <Postgresql plugin for RealTime configuration engine>
+ *
+ * v1.0 - (07-11-05) - Initial version based on res_config_mysql v2.0
+ */
+
+/*! \file
+ *
+ * \brief Postgresql plugin for Asterisk RealTime Architecture
+ *
+ * \author Mark Spencer <markster@digium.com>
+ * \author Manuel Guesdon <mguesdon@oxymium.net> - Postgresql RealTime Driver Author/Adaptor
+ *
+ * \arg http://www.postgresql.org
+ */
+
+/*** MODULEINFO
+ <depend>pgsql</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <libpq-fe.h> /* PostgreSQL */
+
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/lock.h"
+#include "asterisk/options.h"
+#include "asterisk/utils.h"
+#include "asterisk/cli.h"
+
+AST_MUTEX_DEFINE_STATIC(pgsql_lock);
+
+#define RES_CONFIG_PGSQL_CONF "res_pgsql.conf"
+
+PGconn *pgsqlConn = NULL;
+
+#define MAX_DB_OPTION_SIZE 64
+
+static char dbhost[MAX_DB_OPTION_SIZE] = "";
+static char dbuser[MAX_DB_OPTION_SIZE] = "";
+static char dbpass[MAX_DB_OPTION_SIZE] = "";
+static char dbname[MAX_DB_OPTION_SIZE] = "";
+static char dbsock[MAX_DB_OPTION_SIZE] = "";
+static int dbport = 5432;
+static time_t connect_time = 0;
+
+static int parse_config(void);
+static int pgsql_reconnect(const char *database);
+static int realtime_pgsql_status(int fd, int argc, char **argv);
+
+static char cli_realtime_pgsql_status_usage[] =
+ "Usage: realtime pgsql status\n"
+ " Shows connection information for the Postgresql RealTime driver\n";
+
+static struct ast_cli_entry cli_realtime[] = {
+ { { "realtime", "pgsql", "status", NULL },
+ realtime_pgsql_status, "Shows connection information for the Postgresql RealTime driver",
+ cli_realtime_pgsql_status_usage },
+};
+
+static struct ast_variable *realtime_pgsql(const char *database, const char *table, va_list ap)
+{
+ PGresult *result = NULL;
+ int num_rows = 0, pgerror;
+ char sql[256], escapebuf[513];
+ char *stringp;
+ char *chunk;
+ char *op;
+ const char *newparam, *newval;
+ struct ast_variable *var = NULL, *prev = NULL;
+
+ if (!table) {
+ ast_log(LOG_WARNING, "Postgresql RealTime: No table specified.\n");
+ return NULL;
+ }
+
+ /* Get the first parameter and first value in our list of passed paramater/value pairs */
+ newparam = va_arg(ap, const char *);
+ newval = va_arg(ap, const char *);
+ if (!newparam || !newval) {
+ ast_log(LOG_WARNING,
+ "Postgresql RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
+ if (pgsqlConn) {
+ PQfinish(pgsqlConn);
+ pgsqlConn = NULL;
+ };
+ return NULL;
+ }
+
+ /* Create the first part of the query using the first parameter/value pairs we just extracted
+ If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
+ op = strchr(newparam, ' ') ? "" : " =";
+
+ PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror);
+ if (pgerror) {
+ ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
+ va_end(ap);
+ return NULL;
+ }
+
+ snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op,
+ escapebuf);
+ while ((newparam = va_arg(ap, const char *))) {
+ newval = va_arg(ap, const char *);
+ if (!strchr(newparam, ' '))
+ op = " =";
+ else
+ op = "";
+
+ PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror);
+ if (pgerror) {
+ ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
+ va_end(ap);
+ return NULL;
+ }
+
+ snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s '%s'", newparam,
+ op, escapebuf);
+ }
+ va_end(ap);
+
+ /* We now have our complete statement; Lets connect to the server and execute it. */
+ ast_mutex_lock(&pgsql_lock);
+ if (!pgsql_reconnect(database)) {
+ ast_mutex_unlock(&pgsql_lock);
+ return NULL;
+ }
+
+ if (!(result = PQexec(pgsqlConn, sql))) {
+ ast_log(LOG_WARNING,
+ "Postgresql RealTime: Failed to query database. Check debug for more info.\n");
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Query: %s\n", sql);
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Query Failed because: %s\n",
+ PQerrorMessage(pgsqlConn));
+ ast_mutex_unlock(&pgsql_lock);
+ return NULL;
+ } else {
+ ExecStatusType result_status = PQresultStatus(result);
+ if (result_status != PGRES_COMMAND_OK
+ && result_status != PGRES_TUPLES_OK
+ && result_status != PGRES_NONFATAL_ERROR) {
+ ast_log(LOG_WARNING,
+ "Postgresql RealTime: Failed to query database. Check debug for more info.\n");
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Query: %s\n", sql);
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Query Failed because: %s (%s)\n",
+ PQresultErrorMessage(result), PQresStatus(result_status));
+ ast_mutex_unlock(&pgsql_lock);
+ return NULL;
+ }
+ }
+
+ ast_log(LOG_DEBUG, "1Postgresql RealTime: Result=%p Query: %s\n", result, sql);
+
+ if ((num_rows = PQntuples(result)) > 0) {
+ int i = 0;
+ int rowIndex = 0;
+ int numFields = PQnfields(result);
+ char **fieldnames = NULL;
+
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Found %d rows.\n", num_rows);
+
+ if (!(fieldnames = ast_calloc(1, numFields * sizeof(char *)))) {
+ ast_mutex_unlock(&pgsql_lock);
+ PQclear(result);
+ return NULL;
+ }
+ for (i = 0; i < numFields; i++)
+ fieldnames[i] = PQfname(result, i);
+ for (rowIndex = 0; rowIndex < num_rows; rowIndex++) {
+ for (i = 0; i < numFields; i++) {
+ stringp = PQgetvalue(result, rowIndex, i);
+ while (stringp) {
+ chunk = strsep(&stringp, ";");
+ if (chunk && !ast_strlen_zero(ast_strip(chunk))) {
+ if (prev) {
+ prev->next = ast_variable_new(fieldnames[i], chunk);
+ if (prev->next) {
+ prev = prev->next;
+ }
+ } else {
+ prev = var = ast_variable_new(fieldnames[i], chunk);
+ }
+ }
+ }
+ }
+ }
+ ast_free(fieldnames);
+ } else {
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Could not find any rows in table %s.\n", table);
+ }
+
+ ast_mutex_unlock(&pgsql_lock);
+ PQclear(result);
+
+ return var;
+}
+
+static struct ast_config *realtime_multi_pgsql(const char *database, const char *table, va_list ap)
+{
+ PGresult *result = NULL;
+ int num_rows = 0, pgerror;
+ char sql[256], escapebuf[513];
+ const char *initfield = NULL;
+ char *stringp;
+ char *chunk;
+ char *op;
+ const char *newparam, *newval;
+ struct ast_realloca ra;
+ struct ast_variable *var = NULL;
+ struct ast_config *cfg = NULL;
+ struct ast_category *cat = NULL;
+
+ if (!table) {
+ ast_log(LOG_WARNING, "Postgresql RealTime: No table specified.\n");
+ return NULL;
+ }
+
+ memset(&ra, 0, sizeof(ra));
+
+ if (!(cfg = ast_config_new()))
+ return NULL;
+
+ /* Get the first parameter and first value in our list of passed paramater/value pairs */
+ newparam = va_arg(ap, const char *);
+ newval = va_arg(ap, const char *);
+ if (!newparam || !newval) {
+ ast_log(LOG_WARNING,
+ "Postgresql RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
+ if (pgsqlConn) {
+ PQfinish(pgsqlConn);
+ pgsqlConn = NULL;
+ };
+ return NULL;
+ }
+
+ initfield = ast_strdupa(newparam);
+ if ((op = strchr(initfield, ' '))) {
+ *op = '\0';
+ }
+
+ /* Create the first part of the query using the first parameter/value pairs we just extracted
+ If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
+
+ if (!strchr(newparam, ' '))
+ op = " =";
+ else
+ op = "";
+
+ PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror);
+ if (pgerror) {
+ ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
+ va_end(ap);
+ return NULL;
+ }
+
+ snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op,
+ escapebuf);
+ while ((newparam = va_arg(ap, const char *))) {
+ newval = va_arg(ap, const char *);
+ if (!strchr(newparam, ' '))
+ op = " =";
+ else
+ op = "";
+
+ PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror);
+ if (pgerror) {
+ ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
+ va_end(ap);
+ return NULL;
+ }
+
+ snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s '%s'", newparam,
+ op, escapebuf);
+ }
+
+ if (initfield) {
+ snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " ORDER BY %s", initfield);
+ }
+
+ va_end(ap);
+
+ /* We now have our complete statement; Lets connect to the server and execute it. */
+ ast_mutex_lock(&pgsql_lock);
+ if (!pgsql_reconnect(database)) {
+ ast_mutex_unlock(&pgsql_lock);
+ return NULL;
+ }
+
+ if (!(result = PQexec(pgsqlConn, sql))) {
+ ast_log(LOG_WARNING,
+ "Postgresql RealTime: Failed to query database. Check debug for more info.\n");
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Query: %s\n", sql);
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Query Failed because: %s\n",
+ PQerrorMessage(pgsqlConn));
+ ast_mutex_unlock(&pgsql_lock);
+ return NULL;
+ } else {
+ ExecStatusType result_status = PQresultStatus(result);
+ if (result_status != PGRES_COMMAND_OK
+ && result_status != PGRES_TUPLES_OK
+ && result_status != PGRES_NONFATAL_ERROR) {
+ ast_log(LOG_WARNING,
+ "Postgresql RealTime: Failed to query database. Check debug for more info.\n");
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Query: %s\n", sql);
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Query Failed because: %s (%s)\n",
+ PQresultErrorMessage(result), PQresStatus(result_status));
+ ast_mutex_unlock(&pgsql_lock);
+ return NULL;
+ }
+ }
+
+ ast_log(LOG_DEBUG, "2Postgresql RealTime: Result=%p Query: %s\n", result, sql);
+
+ if ((num_rows = PQntuples(result)) > 0) {
+ int numFields = PQnfields(result);
+ int i = 0;
+ int rowIndex = 0;
+ char **fieldnames = NULL;
+
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Found %d rows.\n", num_rows);
+
+ if (!(fieldnames = ast_calloc(1, numFields * sizeof(char *)))) {
+ ast_mutex_unlock(&pgsql_lock);
+ PQclear(result);
+ return NULL;
+ }
+ for (i = 0; i < numFields; i++)
+ fieldnames[i] = PQfname(result, i);
+
+ for (rowIndex = 0; rowIndex < num_rows; rowIndex++) {
+ var = NULL;
+ if (!(cat = ast_category_new("")))
+ continue;
+ for (i = 0; i < numFields; i++) {
+ stringp = PQgetvalue(result, rowIndex, i);
+ while (stringp) {
+ chunk = strsep(&stringp, ";");
+ if (chunk && !ast_strlen_zero(ast_strip(chunk))) {
+ if (initfield && !strcmp(initfield, fieldnames[i])) {
+ ast_category_rename(cat, chunk);
+ }
+ var = ast_variable_new(fieldnames[i], chunk);
+ ast_variable_append(cat, var);
+ }
+ }
+ }
+ ast_category_append(cfg, cat);
+ }
+ ast_free(fieldnames);
+ } else {
+ ast_log(LOG_WARNING,
+ "Postgresql RealTime: Could not find any rows in table %s.\n", table);
+ }
+
+ ast_mutex_unlock(&pgsql_lock);
+ PQclear(result);
+
+ return cfg;
+}
+
+static int update_pgsql(const char *database, const char *table, const char *keyfield,
+ const char *lookup, va_list ap)
+{
+ PGresult *result = NULL;
+ int numrows = 0, pgerror;
+ char sql[256], escapebuf[513];
+ const char *newparam, *newval;
+
+ if (!table) {
+ ast_log(LOG_WARNING, "Postgresql RealTime: No table specified.\n");
+ return -1;
+ }
+
+ /* Get the first parameter and first value in our list of passed paramater/value pairs */
+ newparam = va_arg(ap, const char *);
+ newval = va_arg(ap, const char *);
+ if (!newparam || !newval) {
+ ast_log(LOG_WARNING,
+ "Postgresql RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
+ if (pgsqlConn) {
+ PQfinish(pgsqlConn);
+ pgsqlConn = NULL;
+ };
+ return -1;
+ }
+
+ /* Create the first part of the query using the first parameter/value pairs we just extracted
+ If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
+
+ PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror);
+ if (pgerror) {
+ ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
+ va_end(ap);
+ return -1;
+ }
+ snprintf(sql, sizeof(sql), "UPDATE %s SET %s = '%s'", table, newparam, escapebuf);
+
+ while ((newparam = va_arg(ap, const char *))) {
+ newval = va_arg(ap, const char *);
+
+ PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror);
+ if (pgerror) {
+ ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval);
+ va_end(ap);
+ return -1;
+ }
+
+ snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ", %s = '%s'", newparam,
+ escapebuf);
+ }
+ va_end(ap);
+
+ PQescapeStringConn(pgsqlConn, escapebuf, lookup, (sizeof(escapebuf) - 1) / 2, &pgerror);
+ if (pgerror) {
+ ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", lookup);
+ va_end(ap);
+ return -1;
+ }
+
+ snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " WHERE %s = '%s'", keyfield,
+ escapebuf);
+
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Update SQL: %s\n", sql);
+
+ /* We now have our complete statement; Lets connect to the server and execute it. */
+ ast_mutex_lock(&pgsql_lock);
+ if (!pgsql_reconnect(database)) {
+ ast_mutex_unlock(&pgsql_lock);
+ return -1;
+ }
+
+ if (!(result = PQexec(pgsqlConn, sql))) {
+ ast_log(LOG_WARNING,
+ "Postgresql RealTime: Failed to query database. Check debug for more info.\n");
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Query: %s\n", sql);
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Query Failed because: %s\n",
+ PQerrorMessage(pgsqlConn));
+ ast_mutex_unlock(&pgsql_lock);
+ return -1;
+ } else {
+ ExecStatusType result_status = PQresultStatus(result);
+ if (result_status != PGRES_COMMAND_OK
+ && result_status != PGRES_TUPLES_OK
+ && result_status != PGRES_NONFATAL_ERROR) {
+ ast_log(LOG_WARNING,
+ "Postgresql RealTime: Failed to query database. Check debug for more info.\n");
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Query: %s\n", sql);
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Query Failed because: %s (%s)\n",
+ PQresultErrorMessage(result), PQresStatus(result_status));
+ ast_mutex_unlock(&pgsql_lock);
+ return -1;
+ }
+ }
+
+ numrows = atoi(PQcmdTuples(result));
+ ast_mutex_unlock(&pgsql_lock);
+
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Updated %d rows on table: %s\n", numrows,
+ table);
+
+ /* From http://dev.pgsql.com/doc/pgsql/en/pgsql-affected-rows.html
+ * An integer greater than zero indicates the number of rows affected
+ * Zero indicates that no records were updated
+ * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
+ */
+
+ if (numrows >= 0)
+ return (int) numrows;
+
+ return -1;
+}
+
+static struct ast_config *config_pgsql(const char *database, const char *table,
+ const char *file, struct ast_config *cfg,
+ int withcomments)
+{
+ PGresult *result = NULL;
+ long num_rows;
+ struct ast_variable *new_v;
+ struct ast_category *cur_cat = NULL;
+ char sqlbuf[1024] = "";
+ char *sql = sqlbuf;
+ size_t sqlleft = sizeof(sqlbuf);
+ char last[80] = "";
+ int last_cat_metric = 0;
+
+ last[0] = '\0';
+
+ if (!file || !strcmp(file, RES_CONFIG_PGSQL_CONF)) {
+ ast_log(LOG_WARNING, "Postgresql RealTime: Cannot configure myself.\n");
+ return NULL;
+ }
+
+ ast_build_string(&sql, &sqlleft, "SELECT category, var_name, var_val, cat_metric FROM %s ", table);
+ ast_build_string(&sql, &sqlleft, "WHERE filename='%s' and commented=0", file);
+ ast_build_string(&sql, &sqlleft, "ORDER BY cat_metric DESC, var_metric ASC, category, var_name ");
+
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Static SQL: %s\n", sqlbuf);
+
+ /* We now have our complete statement; Lets connect to the server and execute it. */
+ ast_mutex_lock(&pgsql_lock);
+ if (!pgsql_reconnect(database)) {
+ ast_mutex_unlock(&pgsql_lock);
+ return NULL;
+ }
+
+ if (!(result = PQexec(pgsqlConn, sqlbuf))) {
+ ast_log(LOG_WARNING,
+ "Postgresql RealTime: Failed to query database. Check debug for more info.\n");
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Query: %s\n", sql);
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Query Failed because: %s\n",
+ PQerrorMessage(pgsqlConn));
+ ast_mutex_unlock(&pgsql_lock);
+ return NULL;
+ } else {
+ ExecStatusType result_status = PQresultStatus(result);
+ if (result_status != PGRES_COMMAND_OK
+ && result_status != PGRES_TUPLES_OK
+ && result_status != PGRES_NONFATAL_ERROR) {
+ ast_log(LOG_WARNING,
+ "Postgresql RealTime: Failed to query database. Check debug for more info.\n");
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Query: %s\n", sql);
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Query Failed because: %s (%s)\n",
+ PQresultErrorMessage(result), PQresStatus(result_status));
+ ast_mutex_unlock(&pgsql_lock);
+ return NULL;
+ }
+ }
+
+ if ((num_rows = PQntuples(result)) > 0) {
+ int rowIndex = 0;
+
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Found %ld rows.\n", num_rows);
+
+ for (rowIndex = 0; rowIndex < num_rows; rowIndex++) {
+ char *field_category = PQgetvalue(result, rowIndex, 0);
+ char *field_var_name = PQgetvalue(result, rowIndex, 1);
+ char *field_var_val = PQgetvalue(result, rowIndex, 2);
+ char *field_cat_metric = PQgetvalue(result, rowIndex, 3);
+ if (!strcmp(field_var_name, "#include")) {
+ if (!ast_config_internal_load(field_var_val, cfg, 0)) {
+ PQclear(result);
+ ast_mutex_unlock(&pgsql_lock);
+ return NULL;
+ }
+ continue;
+ }
+
+ if (strcmp(last, field_category) || last_cat_metric != atoi(field_cat_metric)) {
+ cur_cat = ast_category_new(field_category);
+ if (!cur_cat)
+ break;
+ strcpy(last, field_category);
+ last_cat_metric = atoi(field_cat_metric);
+ ast_category_append(cfg, cur_cat);
+ }
+ new_v = ast_variable_new(field_var_name, field_var_val);
+ ast_variable_append(cur_cat, new_v);
+ }
+ } else {
+ ast_log(LOG_WARNING,
+ "Postgresql RealTime: Could not find config '%s' in database.\n", file);
+ }
+
+ PQclear(result);
+ ast_mutex_unlock(&pgsql_lock);
+
+ return cfg;
+}
+
+static struct ast_config_engine pgsql_engine = {
+ .name = "pgsql",
+ .load_func = config_pgsql,
+ .realtime_func = realtime_pgsql,
+ .realtime_multi_func = realtime_multi_pgsql,
+ .update_func = update_pgsql
+};
+
+static int load_module(void)
+{
+ if(!parse_config())
+ return AST_MODULE_LOAD_DECLINE;
+
+ ast_mutex_lock(&pgsql_lock);
+
+ if (!pgsql_reconnect(NULL)) {
+ ast_log(LOG_WARNING,
+ "Postgresql RealTime: Couldn't establish connection. Check debug.\n");
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Cannot Connect: %s\n",
+ PQerrorMessage(pgsqlConn));
+ }
+
+ ast_config_engine_register(&pgsql_engine);
+ if (option_verbose) {
+ ast_verbose("Postgresql RealTime driver loaded.\n");
+ }
+ ast_cli_register_multiple(cli_realtime, sizeof(cli_realtime) / sizeof(struct ast_cli_entry));
+
+ ast_mutex_unlock(&pgsql_lock);
+
+ return 0;
+}
+
+static int unload_module(void)
+{
+ /* Aquire control before doing anything to the module itself. */
+ ast_mutex_lock(&pgsql_lock);
+
+ if (pgsqlConn) {
+ PQfinish(pgsqlConn);
+ pgsqlConn = NULL;
+ };
+ ast_cli_unregister_multiple(cli_realtime, sizeof(cli_realtime) / sizeof(struct ast_cli_entry));
+ ast_config_engine_deregister(&pgsql_engine);
+ if (option_verbose) {
+ ast_verbose("Postgresql RealTime unloaded.\n");
+ }
+
+ ast_module_user_hangup_all();
+
+ /* Unlock so something else can destroy the lock. */
+ ast_mutex_unlock(&pgsql_lock);
+
+ return 0;
+}
+
+static int reload(void)
+{
+ /* Aquire control before doing anything to the module itself. */
+ ast_mutex_lock(&pgsql_lock);
+
+ if (pgsqlConn) {
+ PQfinish(pgsqlConn);
+ pgsqlConn = NULL;
+ };
+ parse_config();
+
+ if (!pgsql_reconnect(NULL)) {
+ ast_log(LOG_WARNING,
+ "Postgresql RealTime: Couldn't establish connection. Check debug.\n");
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Cannot Connect: %s\n",
+ PQerrorMessage(pgsqlConn));
+ }
+
+ ast_verbose(VERBOSE_PREFIX_2 "Postgresql RealTime reloaded.\n");
+
+ /* Done reloading. Release lock so others can now use driver. */
+ ast_mutex_unlock(&pgsql_lock);
+
+ return 0;
+}
+
+static int parse_config(void)
+{
+ struct ast_config *config;
+ const char *s;
+
+ config = ast_config_load(RES_CONFIG_PGSQL_CONF);
+
+ if (!config) {
+ ast_log(LOG_WARNING, "Unable to load config %s\n",RES_CONFIG_PGSQL_CONF);
+ return 0;
+ }
+ if (!(s = ast_variable_retrieve(config, "general", "dbuser"))) {
+ ast_log(LOG_WARNING,
+ "Postgresql RealTime: No database user found, using 'asterisk' as default.\n");
+ strcpy(dbuser, "asterisk");
+ } else {
+ ast_copy_string(dbuser, s, sizeof(dbuser));
+ }
+
+ if (!(s = ast_variable_retrieve(config, "general", "dbpass"))) {
+ ast_log(LOG_WARNING,
+ "Postgresql RealTime: No database password found, using 'asterisk' as default.\n");
+ strcpy(dbpass, "asterisk");
+ } else {
+ ast_copy_string(dbpass, s, sizeof(dbpass));
+ }
+
+ if (!(s = ast_variable_retrieve(config, "general", "dbhost"))) {
+ ast_log(LOG_WARNING,
+ "Postgresql RealTime: No database host found, using localhost via socket.\n");
+ dbhost[0] = '\0';
+ } else {
+ ast_copy_string(dbhost, s, sizeof(dbhost));
+ }
+
+ if (!(s = ast_variable_retrieve(config, "general", "dbname"))) {
+ ast_log(LOG_WARNING,
+ "Postgresql RealTime: No database name found, using 'asterisk' as default.\n");
+ strcpy(dbname, "asterisk");
+ } else {
+ ast_copy_string(dbname, s, sizeof(dbname));
+ }
+
+ if (!(s = ast_variable_retrieve(config, "general", "dbport"))) {
+ ast_log(LOG_WARNING,
+ "Postgresql RealTime: No database port found, using 5432 as default.\n");
+ dbport = 5432;
+ } else {
+ dbport = atoi(s);
+ }
+
+ if (!ast_strlen_zero(dbhost)) {
+ /* No socket needed */
+ } else if (!(s = ast_variable_retrieve(config, "general", "dbsock"))) {
+ ast_log(LOG_WARNING,
+ "Postgresql RealTime: No database socket found, using '/tmp/pgsql.sock' as default.\n");
+ strcpy(dbsock, "/tmp/pgsql.sock");
+ } else {
+ ast_copy_string(dbsock, s, sizeof(dbsock));
+ }
+ ast_config_destroy(config);
+
+ if (!ast_strlen_zero(dbhost)) {
+ ast_log(LOG_DEBUG, "Postgresql RealTime Host: %s\n", dbhost);
+ ast_log(LOG_DEBUG, "Postgresql RealTime Port: %i\n", dbport);
+ } else {
+ ast_log(LOG_DEBUG, "Postgresql RealTime Socket: %s\n", dbsock);
+ }
+ ast_log(LOG_DEBUG, "Postgresql RealTime User: %s\n", dbuser);
+ ast_log(LOG_DEBUG, "Postgresql RealTime Password: %s\n", dbpass);
+ ast_log(LOG_DEBUG, "Postgresql RealTime DBName: %s\n", dbname);
+
+ return 1;
+}
+
+static int pgsql_reconnect(const char *database)
+{
+ char my_database[50];
+
+ ast_copy_string(my_database, S_OR(database, dbname), sizeof(my_database));
+
+ /* mutex lock should have been locked before calling this function. */
+
+ if (pgsqlConn && PQstatus(pgsqlConn) != CONNECTION_OK) {
+ PQfinish(pgsqlConn);
+ pgsqlConn = NULL;
+ }
+
+ if ((!pgsqlConn) && (!ast_strlen_zero(dbhost) || !ast_strlen_zero(dbsock)) && !ast_strlen_zero(dbuser) && !ast_strlen_zero(dbpass) && !ast_strlen_zero(my_database)) {
+ char *connInfo = NULL;
+ unsigned int size = 100 + strlen(dbhost)
+ + strlen(dbuser)
+ + strlen(dbpass)
+ + strlen(my_database);
+
+ if (!(connInfo = ast_malloc(size)))
+ return 0;
+
+ sprintf(connInfo, "host=%s port=%d dbname=%s user=%s password=%s",
+ dbhost, dbport, my_database, dbuser, dbpass);
+ ast_log(LOG_DEBUG, "%u connInfo=%s\n", size, connInfo);
+ pgsqlConn = PQconnectdb(connInfo);
+ ast_log(LOG_DEBUG, "%u connInfo=%s\n", size, connInfo);
+ ast_free(connInfo);
+ connInfo = NULL;
+ ast_log(LOG_DEBUG, "pgsqlConn=%p\n", pgsqlConn);
+ if (pgsqlConn && PQstatus(pgsqlConn) == CONNECTION_OK) {
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Successfully connected to database.\n");
+ connect_time = time(NULL);
+ return 1;
+ } else {
+ ast_log(LOG_ERROR,
+ "Postgresql RealTime: Failed to connect database server %s on %s. Check debug for more info.\n",
+ dbname, dbhost);
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Cannot Connect: %s\n",
+ PQresultErrorMessage(NULL));
+ return 0;
+ }
+ } else {
+ ast_log(LOG_DEBUG, "Postgresql RealTime: Everything is fine.\n");
+ return 1;
+ }
+}
+
+static int realtime_pgsql_status(int fd, int argc, char **argv)
+{
+ char status[256], status2[100] = "";
+ int ctime = time(NULL) - connect_time;
+
+ if (pgsqlConn && PQstatus(pgsqlConn) == CONNECTION_OK) {
+ if (!ast_strlen_zero(dbhost)) {
+ snprintf(status, 255, "Connected to %s@%s, port %d", dbname, dbhost, dbport);
+ } else if (!ast_strlen_zero(dbsock)) {
+ snprintf(status, 255, "Connected to %s on socket file %s", dbname, dbsock);
+ } else {
+ snprintf(status, 255, "Connected to %s@%s", dbname, dbhost);
+ }
+
+ if (!ast_strlen_zero(dbuser)) {
+ snprintf(status2, 99, " with username %s", dbuser);
+ }
+
+ if (ctime > 31536000) {
+ ast_cli(fd, "%s%s for %d years, %d days, %d hours, %d minutes, %d seconds.\n",
+ status, status2, ctime / 31536000, (ctime % 31536000) / 86400,
+ (ctime % 86400) / 3600, (ctime % 3600) / 60, ctime % 60);
+ } else if (ctime > 86400) {
+ ast_cli(fd, "%s%s for %d days, %d hours, %d minutes, %d seconds.\n", status,
+ status2, ctime / 86400, (ctime % 86400) / 3600, (ctime % 3600) / 60,
+ ctime % 60);
+ } else if (ctime > 3600) {
+ ast_cli(fd, "%s%s for %d hours, %d minutes, %d seconds.\n", status, status2,
+ ctime / 3600, (ctime % 3600) / 60, ctime % 60);
+ } else if (ctime > 60) {
+ ast_cli(fd, "%s%s for %d minutes, %d seconds.\n", status, status2, ctime / 60,
+ ctime % 60);
+ } else {
+ ast_cli(fd, "%s%s for %d seconds.\n", status, status2, ctime);
+ }
+
+ return RESULT_SUCCESS;
+ } else {
+ return RESULT_FAILURE;
+ }
+}
+
+/* needs usecount semantics defined */
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "PostgreSQL RealTime Configuration Driver",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload
+ );
diff --git a/1.4.23-rc4/res/res_convert.c b/1.4.23-rc4/res/res_convert.c
new file mode 100644
index 000000000..7c4e04e83
--- /dev/null
+++ b/1.4.23-rc4/res/res_convert.c
@@ -0,0 +1,224 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2005, 2006, Digium, Inc.
+ *
+ * redice li <redice_li@yahoo.com>
+ * Russell Bryant <russell@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief file format conversion CLI command using Asterisk formats and translators
+ *
+ * \author redice li <redice_li@yahoo.com>
+ * \author Russell Bryant <russell@digium.com>
+ *
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "asterisk/channel.h"
+#include "asterisk/logger.h"
+#include "asterisk/module.h"
+#include "asterisk/cli.h"
+#include "asterisk/file.h"
+
+/*! \brief Split the filename to basename and extension */
+static int split_ext(char *filename, char **name, char **ext)
+{
+ *name = *ext = filename;
+
+ if ((*ext = strrchr(filename, '.'))) {
+ **ext = '\0';
+ (*ext)++;
+ }
+
+ if (ast_strlen_zero(*name) || ast_strlen_zero(*ext))
+ return -1;
+
+ return 0;
+}
+
+/*! \brief Convert a file from one format to another */
+static int cli_audio_convert_deprecated(int fd, int argc, char *argv[])
+{
+ int ret = RESULT_FAILURE;
+ struct ast_filestream *fs_in = NULL, *fs_out = NULL;
+ struct ast_frame *f;
+ struct timeval start;
+ int cost;
+ char *file_in = NULL, *file_out = NULL;
+ char *name_in, *ext_in, *name_out, *ext_out;
+
+ /* ugly, can be removed when CLI entries have ast_module pointers */
+ ast_module_ref(ast_module_info->self);
+
+ if (argc != 3 || ast_strlen_zero(argv[1]) || ast_strlen_zero(argv[2])) {
+ ret = RESULT_SHOWUSAGE;
+ goto fail_out;
+ }
+
+ file_in = ast_strdupa(argv[1]);
+ file_out = ast_strdupa(argv[2]);
+
+ if (split_ext(file_in, &name_in, &ext_in)) {
+ ast_cli(fd, "'%s' is an invalid filename!\n", argv[1]);
+ goto fail_out;
+ }
+ if (!(fs_in = ast_readfile(name_in, ext_in, NULL, O_RDONLY, 0, 0))) {
+ ast_cli(fd, "Unable to open input file: %s\n", argv[1]);
+ goto fail_out;
+ }
+
+ if (split_ext(file_out, &name_out, &ext_out)) {
+ ast_cli(fd, "'%s' is an invalid filename!\n", argv[2]);
+ goto fail_out;
+ }
+ if (!(fs_out = ast_writefile(name_out, ext_out, NULL, O_CREAT|O_TRUNC|O_WRONLY, 0, 0644))) {
+ ast_cli(fd, "Unable to open output file: %s\n", argv[2]);
+ goto fail_out;
+ }
+
+ start = ast_tvnow();
+
+ while ((f = ast_readframe(fs_in))) {
+ if (ast_writestream(fs_out, f)) {
+ ast_cli(fd, "Failed to convert %s.%s to %s.%s!\n", name_in, ext_in, name_out, ext_out);
+ goto fail_out;
+ }
+ }
+
+ cost = ast_tvdiff_ms(ast_tvnow(), start);
+ ast_cli(fd, "Converted %s.%s to %s.%s in %dms\n", name_in, ext_in, name_out, ext_out, cost);
+ ret = RESULT_SUCCESS;
+
+fail_out:
+ if (fs_out) {
+ ast_closestream(fs_out);
+ if (ret != RESULT_SUCCESS)
+ ast_filedelete(name_out, ext_out);
+ }
+
+ if (fs_in)
+ ast_closestream(fs_in);
+
+ ast_module_unref(ast_module_info->self);
+
+ return ret;
+}
+
+static int cli_audio_convert(int fd, int argc, char *argv[])
+{
+ int ret = RESULT_FAILURE;
+ struct ast_filestream *fs_in = NULL, *fs_out = NULL;
+ struct ast_frame *f;
+ struct timeval start;
+ int cost;
+ char *file_in = NULL, *file_out = NULL;
+ char *name_in, *ext_in, *name_out, *ext_out;
+
+ /* ugly, can be removed when CLI entries have ast_module pointers */
+ ast_module_ref(ast_module_info->self);
+
+ if (argc != 4 || ast_strlen_zero(argv[2]) || ast_strlen_zero(argv[3])) {
+ ret = RESULT_SHOWUSAGE;
+ goto fail_out;
+ }
+
+ file_in = ast_strdupa(argv[2]);
+ file_out = ast_strdupa(argv[3]);
+
+ if (split_ext(file_in, &name_in, &ext_in)) {
+ ast_cli(fd, "'%s' is an invalid filename!\n", argv[2]);
+ goto fail_out;
+ }
+ if (!(fs_in = ast_readfile(name_in, ext_in, NULL, O_RDONLY, 0, 0))) {
+ ast_cli(fd, "Unable to open input file: %s\n", argv[2]);
+ goto fail_out;
+ }
+
+ if (split_ext(file_out, &name_out, &ext_out)) {
+ ast_cli(fd, "'%s' is an invalid filename!\n", argv[3]);
+ goto fail_out;
+ }
+ if (!(fs_out = ast_writefile(name_out, ext_out, NULL, O_CREAT|O_TRUNC|O_WRONLY, 0, 0644))) {
+ ast_cli(fd, "Unable to open output file: %s\n", argv[3]);
+ goto fail_out;
+ }
+
+ start = ast_tvnow();
+
+ while ((f = ast_readframe(fs_in))) {
+ if (ast_writestream(fs_out, f)) {
+ ast_cli(fd, "Failed to convert %s.%s to %s.%s!\n", name_in, ext_in, name_out, ext_out);
+ goto fail_out;
+ }
+ }
+
+ cost = ast_tvdiff_ms(ast_tvnow(), start);
+ ast_cli(fd, "Converted %s.%s to %s.%s in %dms\n", name_in, ext_in, name_out, ext_out, cost);
+ ret = RESULT_SUCCESS;
+
+fail_out:
+ if (fs_out) {
+ ast_closestream(fs_out);
+ if (ret != RESULT_SUCCESS)
+ ast_filedelete(name_out, ext_out);
+ }
+
+ if (fs_in)
+ ast_closestream(fs_in);
+
+ ast_module_unref(ast_module_info->self);
+
+ return ret;
+}
+
+static char usage_audio_convert[] =
+"Usage: file convert <file_in> <file_out>\n"
+" Convert from file_in to file_out. If an absolute path is not given, the\n"
+"default Asterisk sounds directory will be used.\n\n"
+"Example:\n"
+" file convert tt-weasels.gsm tt-weasels.ulaw\n";
+
+static struct ast_cli_entry cli_convert_deprecated = {
+ { "convert" , NULL },
+ cli_audio_convert_deprecated, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_convert[] = {
+ { { "file", "convert" , NULL },
+ cli_audio_convert, "Convert audio file",
+ usage_audio_convert, NULL, &cli_convert_deprecated },
+};
+
+static int unload_module(void)
+{
+ ast_cli_unregister_multiple(cli_convert, sizeof(cli_convert) / sizeof(struct ast_cli_entry));
+ return 0;
+}
+
+static int load_module(void)
+{
+ ast_cli_register_multiple(cli_convert, sizeof(cli_convert) / sizeof(struct ast_cli_entry));
+ return 0;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "File format conversion CLI command");
diff --git a/1.4.23-rc4/res/res_crypto.c b/1.4.23-rc4/res/res_crypto.c
new file mode 100644
index 000000000..c2c33d015
--- /dev/null
+++ b/1.4.23-rc4/res/res_crypto.c
@@ -0,0 +1,631 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2006, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Provide Cryptographic Signature capability
+ *
+ * \author Mark Spencer <markster@digium.com>
+ */
+
+/*** MODULEINFO
+ <depend>ssl</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sys/types.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <stdio.h>
+#include <dirent.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "asterisk/file.h"
+#include "asterisk/channel.h"
+#include "asterisk/logger.h"
+#include "asterisk/say.h"
+#include "asterisk/module.h"
+#include "asterisk/options.h"
+#include "asterisk/crypto.h"
+#include "asterisk/md5.h"
+#include "asterisk/cli.h"
+#include "asterisk/io.h"
+#include "asterisk/lock.h"
+#include "asterisk/utils.h"
+
+/*
+ * Asterisk uses RSA keys with SHA-1 message digests for its
+ * digital signatures. The choice of RSA is due to its higher
+ * throughput on verification, and the choice of SHA-1 based
+ * on the recently discovered collisions in MD5's compression
+ * algorithm and recommendations of avoiding MD5 in new schemes
+ * from various industry experts.
+ *
+ * We use OpenSSL to provide our crypto routines, although we never
+ * actually use full-up SSL
+ *
+ */
+
+/*
+ * XXX This module is not very thread-safe. It is for everyday stuff
+ * like reading keys and stuff, but there are all kinds of weird
+ * races with people running reload and key init at the same time
+ * for example
+ *
+ * XXXX
+ */
+
+AST_MUTEX_DEFINE_STATIC(keylock);
+
+#define KEY_NEEDS_PASSCODE (1 << 16)
+
+struct ast_key {
+ /* Name of entity */
+ char name[80];
+ /* File name */
+ char fn[256];
+ /* Key type (AST_KEY_PUB or AST_KEY_PRIV, along with flags from above) */
+ int ktype;
+ /* RSA structure (if successfully loaded) */
+ RSA *rsa;
+ /* Whether we should be deleted */
+ int delme;
+ /* FD for input (or -1 if no input allowed, or -2 if we needed input) */
+ int infd;
+ /* FD for output */
+ int outfd;
+ /* Last MD5 Digest */
+ unsigned char digest[16];
+ struct ast_key *next;
+};
+
+static struct ast_key *keys = NULL;
+
+
+#if 0
+static int fdprint(int fd, char *s)
+{
+ return write(fd, s, strlen(s) + 1);
+}
+#endif
+static int pw_cb(char *buf, int size, int rwflag, void *userdata)
+{
+ struct ast_key *key = (struct ast_key *)userdata;
+ char prompt[256];
+ int res;
+ int tmp;
+ if (key->infd > -1) {
+ snprintf(prompt, sizeof(prompt), ">>>> passcode for %s key '%s': ",
+ key->ktype == AST_KEY_PRIVATE ? "PRIVATE" : "PUBLIC", key->name);
+ if (write(key->outfd, prompt, strlen(prompt)) < 0) {
+ /* Note that we were at least called */
+ key->infd = -2;
+ return -1;
+ }
+ memset(buf, 0, sizeof(buf));
+ tmp = ast_hide_password(key->infd);
+ memset(buf, 0, size);
+ res = read(key->infd, buf, size);
+ ast_restore_tty(key->infd, tmp);
+ if (buf[strlen(buf) -1] == '\n')
+ buf[strlen(buf) - 1] = '\0';
+ return strlen(buf);
+ } else {
+ /* Note that we were at least called */
+ key->infd = -2;
+ }
+ return -1;
+}
+
+static struct ast_key *__ast_key_get(const char *kname, int ktype)
+{
+ struct ast_key *key;
+ ast_mutex_lock(&keylock);
+ key = keys;
+ while(key) {
+ if (!strcmp(kname, key->name) &&
+ (ktype == key->ktype))
+ break;
+ key = key->next;
+ }
+ ast_mutex_unlock(&keylock);
+ return key;
+}
+
+static struct ast_key *try_load_key (char *dir, char *fname, int ifd, int ofd, int *not2)
+{
+ int ktype = 0;
+ char *c = NULL;
+ char ffname[256];
+ unsigned char digest[16];
+ FILE *f;
+ struct MD5Context md5;
+ struct ast_key *key;
+ static int notice = 0;
+ int found = 0;
+
+ /* Make sure its name is a public or private key */
+
+ if ((c = strstr(fname, ".pub")) && !strcmp(c, ".pub")) {
+ ktype = AST_KEY_PUBLIC;
+ } else if ((c = strstr(fname, ".key")) && !strcmp(c, ".key")) {
+ ktype = AST_KEY_PRIVATE;
+ } else
+ return NULL;
+
+ /* Get actual filename */
+ snprintf(ffname, sizeof(ffname), "%s/%s", dir, fname);
+
+ ast_mutex_lock(&keylock);
+ key = keys;
+ while(key) {
+ /* Look for an existing version already */
+ if (!strcasecmp(key->fn, ffname))
+ break;
+ key = key->next;
+ }
+ ast_mutex_unlock(&keylock);
+
+ /* Open file */
+ f = fopen(ffname, "r");
+ if (!f) {
+ ast_log(LOG_WARNING, "Unable to open key file %s: %s\n", ffname, strerror(errno));
+ return NULL;
+ }
+ MD5Init(&md5);
+ while(!feof(f)) {
+ /* Calculate a "whatever" quality md5sum of the key */
+ char buf[256];
+ memset(buf, 0, 256);
+ if (fgets(buf, sizeof(buf), f)) {
+ MD5Update(&md5, (unsigned char *) buf, strlen(buf));
+ }
+ }
+ MD5Final(digest, &md5);
+ if (key) {
+ /* If the MD5 sum is the same, and it isn't awaiting a passcode
+ then this is far enough */
+ if (!memcmp(digest, key->digest, 16) &&
+ !(key->ktype & KEY_NEEDS_PASSCODE)) {
+ fclose(f);
+ key->delme = 0;
+ return NULL;
+ } else {
+ /* Preserve keytype */
+ ktype = key->ktype;
+ /* Recycle the same structure */
+ found++;
+ }
+ }
+
+ /* Make fname just be the normal name now */
+ *c = '\0';
+ if (!key) {
+ if (!(key = ast_calloc(1, sizeof(*key)))) {
+ fclose(f);
+ return NULL;
+ }
+ }
+ /* At this point we have a key structure (old or new). Time to
+ fill it with what we know */
+ /* Gotta lock if this one already exists */
+ if (found)
+ ast_mutex_lock(&keylock);
+ /* First the filename */
+ ast_copy_string(key->fn, ffname, sizeof(key->fn));
+ /* Then the name */
+ ast_copy_string(key->name, fname, sizeof(key->name));
+ key->ktype = ktype;
+ /* Yes, assume we're going to be deleted */
+ key->delme = 1;
+ /* Keep the key type */
+ memcpy(key->digest, digest, 16);
+ /* Can I/O takes the FD we're given */
+ key->infd = ifd;
+ key->outfd = ofd;
+ /* Reset the file back to the beginning */
+ rewind(f);
+ /* Now load the key with the right method */
+ if (ktype == AST_KEY_PUBLIC)
+ key->rsa = PEM_read_RSA_PUBKEY(f, NULL, pw_cb, key);
+ else
+ key->rsa = PEM_read_RSAPrivateKey(f, NULL, pw_cb, key);
+ fclose(f);
+ if (key->rsa) {
+ if (RSA_size(key->rsa) == 128) {
+ /* Key loaded okay */
+ key->ktype &= ~KEY_NEEDS_PASSCODE;
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Loaded %s key '%s'\n", key->ktype == AST_KEY_PUBLIC ? "PUBLIC" : "PRIVATE", key->name);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Key '%s' loaded OK\n", key->name);
+ key->delme = 0;
+ } else
+ ast_log(LOG_NOTICE, "Key '%s' is not expected size.\n", key->name);
+ } else if (key->infd != -2) {
+ ast_log(LOG_WARNING, "Key load %s '%s' failed\n",key->ktype == AST_KEY_PUBLIC ? "PUBLIC" : "PRIVATE", key->name);
+ if (ofd > -1) {
+ ERR_print_errors_fp(stderr);
+ } else
+ ERR_print_errors_fp(stderr);
+ } else {
+ ast_log(LOG_NOTICE, "Key '%s' needs passcode.\n", key->name);
+ key->ktype |= KEY_NEEDS_PASSCODE;
+ if (!notice) {
+ if (!ast_opt_init_keys)
+ ast_log(LOG_NOTICE, "Add the '-i' flag to the asterisk command line if you want to automatically initialize passcodes at launch.\n");
+ notice++;
+ }
+ /* Keep it anyway */
+ key->delme = 0;
+ /* Print final notice about "init keys" when done */
+ *not2 = 1;
+ }
+ if (found)
+ ast_mutex_unlock(&keylock);
+ if (!found) {
+ ast_mutex_lock(&keylock);
+ key->next = keys;
+ keys = key;
+ ast_mutex_unlock(&keylock);
+ }
+ return key;
+}
+
+#if 0
+
+static void dump(unsigned char *src, int len)
+{
+ int x;
+ for (x=0;x<len;x++)
+ printf("%02x", *(src++));
+ printf("\n");
+}
+
+static char *binary(int y, int len)
+{
+ static char res[80];
+ int x;
+ memset(res, 0, sizeof(res));
+ for (x=0;x<len;x++) {
+ if (y & (1 << x))
+ res[(len - x - 1)] = '1';
+ else
+ res[(len - x - 1)] = '0';
+ }
+ return res;
+}
+
+#endif
+
+static int __ast_sign_bin(struct ast_key *key, const char *msg, int msglen, unsigned char *dsig)
+{
+ unsigned char digest[20];
+ unsigned int siglen = 128;
+ int res;
+
+ if (key->ktype != AST_KEY_PRIVATE) {
+ ast_log(LOG_WARNING, "Cannot sign with a public key\n");
+ return -1;
+ }
+
+ /* Calculate digest of message */
+ SHA1((unsigned char *)msg, msglen, digest);
+
+ /* Verify signature */
+ res = RSA_sign(NID_sha1, digest, sizeof(digest), dsig, &siglen, key->rsa);
+
+ if (!res) {
+ ast_log(LOG_WARNING, "RSA Signature (key %s) failed\n", key->name);
+ return -1;
+ }
+
+ if (siglen != 128) {
+ ast_log(LOG_WARNING, "Unexpected signature length %d, expecting %d\n", (int)siglen, (int)128);
+ return -1;
+ }
+
+ return 0;
+
+}
+
+static int __ast_decrypt_bin(unsigned char *dst, const unsigned char *src, int srclen, struct ast_key *key)
+{
+ int res;
+ int pos = 0;
+ if (key->ktype != AST_KEY_PRIVATE) {
+ ast_log(LOG_WARNING, "Cannot decrypt with a public key\n");
+ return -1;
+ }
+
+ if (srclen % 128) {
+ ast_log(LOG_NOTICE, "Tried to decrypt something not a multiple of 128 bytes\n");
+ return -1;
+ }
+ while(srclen) {
+ /* Process chunks 128 bytes at a time */
+ res = RSA_private_decrypt(128, src, dst, key->rsa, RSA_PKCS1_OAEP_PADDING);
+ if (res < 0)
+ return -1;
+ pos += res;
+ src += 128;
+ srclen -= 128;
+ dst += res;
+ }
+ return pos;
+}
+
+static int __ast_encrypt_bin(unsigned char *dst, const unsigned char *src, int srclen, struct ast_key *key)
+{
+ int res;
+ int bytes;
+ int pos = 0;
+ if (key->ktype != AST_KEY_PUBLIC) {
+ ast_log(LOG_WARNING, "Cannot encrypt with a private key\n");
+ return -1;
+ }
+
+ while(srclen) {
+ bytes = srclen;
+ if (bytes > 128 - 41)
+ bytes = 128 - 41;
+ /* Process chunks 128-41 bytes at a time */
+ res = RSA_public_encrypt(bytes, src, dst, key->rsa, RSA_PKCS1_OAEP_PADDING);
+ if (res != 128) {
+ ast_log(LOG_NOTICE, "How odd, encrypted size is %d\n", res);
+ return -1;
+ }
+ src += bytes;
+ srclen -= bytes;
+ pos += res;
+ dst += res;
+ }
+ return pos;
+}
+
+static int __ast_sign(struct ast_key *key, char *msg, char *sig)
+{
+ unsigned char dsig[128];
+ int siglen = sizeof(dsig);
+ int res;
+ res = ast_sign_bin(key, msg, strlen(msg), dsig);
+ if (!res)
+ /* Success -- encode (256 bytes max as documented) */
+ ast_base64encode(sig, dsig, siglen, 256);
+ return res;
+
+}
+
+static int __ast_check_signature_bin(struct ast_key *key, const char *msg, int msglen, const unsigned char *dsig)
+{
+ unsigned char digest[20];
+ int res;
+
+ if (key->ktype != AST_KEY_PUBLIC) {
+ /* Okay, so of course you really *can* but for our purposes
+ we're going to say you can't */
+ ast_log(LOG_WARNING, "Cannot check message signature with a private key\n");
+ return -1;
+ }
+
+ /* Calculate digest of message */
+ SHA1((unsigned char *)msg, msglen, digest);
+
+ /* Verify signature */
+ res = RSA_verify(NID_sha1, digest, sizeof(digest), (unsigned char *)dsig, 128, key->rsa);
+
+ if (!res) {
+ ast_log(LOG_DEBUG, "Key failed verification: %s\n", key->name);
+ return -1;
+ }
+ /* Pass */
+ return 0;
+}
+
+static int __ast_check_signature(struct ast_key *key, const char *msg, const char *sig)
+{
+ unsigned char dsig[128];
+ int res;
+
+ /* Decode signature */
+ res = ast_base64decode(dsig, sig, sizeof(dsig));
+ if (res != sizeof(dsig)) {
+ ast_log(LOG_WARNING, "Signature improper length (expect %d, got %d)\n", (int)sizeof(dsig), (int)res);
+ return -1;
+ }
+ res = ast_check_signature_bin(key, msg, strlen(msg), dsig);
+ return res;
+}
+
+static void crypto_load(int ifd, int ofd)
+{
+ struct ast_key *key, *nkey, *last;
+ DIR *dir = NULL;
+ struct dirent *ent;
+ int note = 0;
+ /* Mark all keys for deletion */
+ ast_mutex_lock(&keylock);
+ key = keys;
+ while(key) {
+ key->delme = 1;
+ key = key->next;
+ }
+ ast_mutex_unlock(&keylock);
+ /* Load new keys */
+ dir = opendir((char *)ast_config_AST_KEY_DIR);
+ if (dir) {
+ while((ent = readdir(dir))) {
+ try_load_key((char *)ast_config_AST_KEY_DIR, ent->d_name, ifd, ofd, &note);
+ }
+ closedir(dir);
+ } else
+ ast_log(LOG_WARNING, "Unable to open key directory '%s'\n", (char *)ast_config_AST_KEY_DIR);
+ if (note) {
+ ast_log(LOG_NOTICE, "Please run the command 'init keys' to enter the passcodes for the keys\n");
+ }
+ ast_mutex_lock(&keylock);
+ key = keys;
+ last = NULL;
+ while(key) {
+ nkey = key->next;
+ if (key->delme) {
+ ast_log(LOG_DEBUG, "Deleting key %s type %d\n", key->name, key->ktype);
+ /* Do the delete */
+ if (last)
+ last->next = nkey;
+ else
+ keys = nkey;
+ if (key->rsa)
+ RSA_free(key->rsa);
+ free(key);
+ } else
+ last = key;
+ key = nkey;
+ }
+ ast_mutex_unlock(&keylock);
+}
+
+static void md52sum(char *sum, unsigned char *md5)
+{
+ int x;
+ for (x=0;x<16;x++)
+ sum += sprintf(sum, "%02x", *(md5++));
+}
+
+static int show_keys(int fd, int argc, char *argv[])
+{
+ struct ast_key *key;
+ char sum[16 * 2 + 1];
+ int count_keys = 0;
+
+ ast_mutex_lock(&keylock);
+ key = keys;
+ ast_cli(fd, "%-18s %-8s %-16s %-33s\n", "Key Name", "Type", "Status", "Sum");
+ while(key) {
+ md52sum(sum, key->digest);
+ ast_cli(fd, "%-18s %-8s %-16s %-33s\n", key->name,
+ (key->ktype & 0xf) == AST_KEY_PUBLIC ? "PUBLIC" : "PRIVATE",
+ key->ktype & KEY_NEEDS_PASSCODE ? "[Needs Passcode]" : "[Loaded]", sum);
+
+ key = key->next;
+ count_keys++;
+ }
+ ast_mutex_unlock(&keylock);
+ ast_cli(fd, "%d known RSA keys.\n", count_keys);
+ return RESULT_SUCCESS;
+}
+
+static int init_keys(int fd, int argc, char *argv[])
+{
+ struct ast_key *key;
+ int ign;
+ char *kn;
+ char tmp[256] = "";
+
+ key = keys;
+ while(key) {
+ /* Reload keys that need pass codes now */
+ if (key->ktype & KEY_NEEDS_PASSCODE) {
+ kn = key->fn + strlen(ast_config_AST_KEY_DIR) + 1;
+ ast_copy_string(tmp, kn, sizeof(tmp));
+ try_load_key((char *)ast_config_AST_KEY_DIR, tmp, fd, fd, &ign);
+ }
+ key = key->next;
+ }
+ return RESULT_SUCCESS;
+}
+
+static char show_key_usage[] =
+"Usage: keys show\n"
+" Displays information about RSA keys known by Asterisk\n";
+
+static char init_keys_usage[] =
+"Usage: keys init\n"
+" Initializes private keys (by reading in pass code from the user)\n";
+
+static struct ast_cli_entry cli_show_keys_deprecated = {
+ { "show", "keys", NULL },
+ show_keys, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_init_keys_deprecated = {
+ { "init", "keys", NULL },
+ init_keys, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_crypto[] = {
+ { { "keys", "show", NULL },
+ show_keys, "Displays RSA key information",
+ show_key_usage, NULL, &cli_show_keys_deprecated },
+
+ { { "keys", "init", NULL },
+ init_keys, "Initialize RSA key passcodes",
+ init_keys_usage, NULL, &cli_init_keys_deprecated },
+};
+
+static int crypto_init(void)
+{
+ SSL_library_init();
+ ERR_load_crypto_strings();
+ ast_cli_register_multiple(cli_crypto, sizeof(cli_crypto) / sizeof(struct ast_cli_entry));
+
+ /* Install ourselves into stubs */
+ ast_key_get = __ast_key_get;
+ ast_check_signature = __ast_check_signature;
+ ast_check_signature_bin = __ast_check_signature_bin;
+ ast_sign = __ast_sign;
+ ast_sign_bin = __ast_sign_bin;
+ ast_encrypt_bin = __ast_encrypt_bin;
+ ast_decrypt_bin = __ast_decrypt_bin;
+ return 0;
+}
+
+static int reload(void)
+{
+ crypto_load(-1, -1);
+ return 0;
+}
+
+static int load_module(void)
+{
+ crypto_init();
+ if (ast_opt_init_keys)
+ crypto_load(STDIN_FILENO, STDOUT_FILENO);
+ else
+ crypto_load(-1, -1);
+ return 0;
+}
+
+static int unload_module(void)
+{
+ /* Can't unload this once we're loaded */
+ return -1;
+}
+
+/* needs usecount semantics defined */
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Cryptographic Digital Signatures",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload
+ );
diff --git a/1.4.23-rc4/res/res_features.c b/1.4.23-rc4/res/res_features.c
new file mode 100644
index 000000000..aaa1ff593
--- /dev/null
+++ b/1.4.23-rc4/res/res_features.c
@@ -0,0 +1,2755 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2006, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Routines implementing call features as call pickup, parking and transfer
+ *
+ * \author Mark Spencer <markster@digium.com>
+ */
+
+/*** MODULEINFO
+ <depend>chan_local</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <pthread.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/signal.h>
+#include <netinet/in.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/options.h"
+#include "asterisk/causes.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/app.h"
+#include "asterisk/say.h"
+#include "asterisk/features.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/config.h"
+#include "asterisk/cli.h"
+#include "asterisk/manager.h"
+#include "asterisk/utils.h"
+#include "asterisk/adsi.h"
+#include "asterisk/devicestate.h"
+#include "asterisk/monitor.h"
+#include "asterisk/global_datastores.h"
+
+#define DEFAULT_PARK_TIME 45000
+#define DEFAULT_TRANSFER_DIGIT_TIMEOUT 3000
+#define DEFAULT_FEATURE_DIGIT_TIMEOUT 500
+#define DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER 15000
+
+#define AST_MAX_WATCHERS 256
+
+enum {
+ AST_FEATURE_FLAG_NEEDSDTMF = (1 << 0),
+ AST_FEATURE_FLAG_ONPEER = (1 << 1),
+ AST_FEATURE_FLAG_ONSELF = (1 << 2),
+ AST_FEATURE_FLAG_BYCALLEE = (1 << 3),
+ AST_FEATURE_FLAG_BYCALLER = (1 << 4),
+ AST_FEATURE_FLAG_BYBOTH = (3 << 3),
+};
+
+static char *parkedcall = "ParkedCall";
+
+static int parkaddhints = 0; /*!< Add parking hints automatically */
+static int parkingtime = DEFAULT_PARK_TIME; /*!< No more than 45 seconds parked before you do something with them */
+static char parking_con[AST_MAX_EXTENSION]; /*!< Context for which parking is made accessible */
+static char parking_con_dial[AST_MAX_EXTENSION]; /*!< Context for dialback for parking (KLUDGE) */
+static char parking_ext[AST_MAX_EXTENSION]; /*!< Extension you type to park the call */
+static char pickup_ext[AST_MAX_EXTENSION]; /*!< Call pickup extension */
+static char parkmohclass[MAX_MUSICCLASS]; /*!< Music class used for parking */
+static int parking_start; /*!< First available extension for parking */
+static int parking_stop; /*!< Last available extension for parking */
+
+static int parkedcalltransfers; /*!< Who can REDIRECT after picking up a parked a call */
+
+static char courtesytone[256]; /*!< Courtesy tone */
+static int parkedplay = 0; /*!< Who to play the courtesy tone to */
+static char xfersound[256]; /*!< Call transfer sound */
+static char xferfailsound[256]; /*!< Call transfer failure sound */
+
+static int parking_offset;
+static int parkfindnext;
+
+static int adsipark;
+
+static int transferdigittimeout;
+static int featuredigittimeout;
+
+static int atxfernoanswertimeout;
+
+static char *registrar = "res_features"; /*!< Registrar for operations */
+
+/* module and CLI command definitions */
+static char *synopsis = "Answer a parked call";
+
+static char *descrip = "ParkedCall(exten):"
+"Used to connect to a parked call. This application is always\n"
+"registered internally and does not need to be explicitly added\n"
+"into the dialplan, although you should include the 'parkedcalls'\n"
+"context.\n";
+
+static char *parkcall = PARK_APP_NAME;
+
+static char *synopsis2 = "Park yourself";
+
+static char *descrip2 = "Park():"
+"Used to park yourself (typically in combination with a supervised\n"
+"transfer to know the parking space). This application is always\n"
+"registered internally and does not need to be explicitly added\n"
+"into the dialplan, although you should include the 'parkedcalls'\n"
+"context (or the context specified in features.conf).\n\n"
+"If you set the PARKINGEXTEN variable to an extension in your\n"
+"parking context, park() will park the call on that extension, unless\n"
+"it already exists. In that case, execution will continue at next\n"
+"priority.\n" ;
+
+static struct ast_app *monitor_app = NULL;
+static int monitor_ok = 1;
+
+struct parkeduser {
+ struct ast_channel *chan; /*!< Parking channel */
+ struct timeval start; /*!< Time the parking started */
+ int parkingnum; /*!< Parking lot */
+ char parkingexten[AST_MAX_EXTENSION]; /*!< If set beforehand, parking extension used for this call */
+ char context[AST_MAX_CONTEXT]; /*!< Where to go if our parking time expires */
+ char exten[AST_MAX_EXTENSION];
+ int priority;
+ int parkingtime; /*!< Maximum length in parking lot before return */
+ int notquiteyet;
+ char peername[1024];
+ unsigned char moh_trys;
+ struct parkeduser *next;
+};
+
+static struct parkeduser *parkinglot;
+
+AST_MUTEX_DEFINE_STATIC(parking_lock); /*!< protects all static variables above */
+
+static pthread_t parking_thread;
+
+char *ast_parking_ext(void)
+{
+ return parking_ext;
+}
+
+char *ast_pickup_ext(void)
+{
+ return pickup_ext;
+}
+
+struct ast_bridge_thread_obj
+{
+ struct ast_bridge_config bconfig;
+ struct ast_channel *chan;
+ struct ast_channel *peer;
+};
+
+/*! \brief store context, priority and extension */
+static void set_c_e_p(struct ast_channel *chan, const char *context, const char *ext, int pri)
+{
+ ast_copy_string(chan->context, context, sizeof(chan->context));
+ ast_copy_string(chan->exten, ext, sizeof(chan->exten));
+ chan->priority = pri;
+}
+
+static void check_goto_on_transfer(struct ast_channel *chan)
+{
+ struct ast_channel *xferchan;
+ const char *val = pbx_builtin_getvar_helper(chan, "GOTO_ON_BLINDXFR");
+ char *x, *goto_on_transfer;
+ struct ast_frame *f;
+
+ if (ast_strlen_zero(val))
+ return;
+
+ goto_on_transfer = ast_strdupa(val);
+
+ if (!(xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "%s", chan->name)))
+ return;
+
+ for (x = goto_on_transfer; x && *x; x++) {
+ if (*x == '^')
+ *x = '|';
+ }
+ /* Make formats okay */
+ xferchan->readformat = chan->readformat;
+ xferchan->writeformat = chan->writeformat;
+ ast_channel_masquerade(xferchan, chan);
+ ast_parseable_goto(xferchan, goto_on_transfer);
+ xferchan->_state = AST_STATE_UP;
+ ast_clear_flag(xferchan, AST_FLAGS_ALL);
+ xferchan->_softhangup = 0;
+ if ((f = ast_read(xferchan))) {
+ ast_frfree(f);
+ f = NULL;
+ ast_pbx_start(xferchan);
+ } else {
+ ast_hangup(xferchan);
+ }
+}
+
+static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *caller, const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name, const char *language);
+
+
+static void *ast_bridge_call_thread(void *data)
+{
+ struct ast_bridge_thread_obj *tobj = data;
+
+ tobj->chan->appl = "Transferred Call";
+ tobj->chan->data = tobj->peer->name;
+ tobj->peer->appl = "Transferred Call";
+ tobj->peer->data = tobj->chan->name;
+
+ ast_bridge_call(tobj->peer, tobj->chan, &tobj->bconfig);
+ ast_hangup(tobj->chan);
+ ast_hangup(tobj->peer);
+ bzero(tobj, sizeof(*tobj)); /*! \todo XXX for safety */
+ free(tobj);
+ return NULL;
+}
+
+static void ast_bridge_call_thread_launch(void *data)
+{
+ pthread_t thread;
+ pthread_attr_t attr;
+ struct sched_param sched;
+
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ ast_pthread_create(&thread, &attr,ast_bridge_call_thread, data);
+ pthread_attr_destroy(&attr);
+ memset(&sched, 0, sizeof(sched));
+ pthread_setschedparam(thread, SCHED_RR, &sched);
+}
+
+static int adsi_announce_park(struct ast_channel *chan, char *parkingexten)
+{
+ int res;
+ int justify[5] = {ADSI_JUST_CENT, ADSI_JUST_CENT, ADSI_JUST_CENT, ADSI_JUST_CENT};
+ char tmp[256];
+ char *message[5] = {NULL, NULL, NULL, NULL, NULL};
+
+ snprintf(tmp, sizeof(tmp), "Parked on %s", parkingexten);
+ message[0] = tmp;
+ res = ast_adsi_load_session(chan, NULL, 0, 1);
+ if (res == -1)
+ return res;
+ return ast_adsi_print(chan, message, justify, 1);
+}
+
+/*! \brief Notify metermaids that we've changed an extension */
+static void notify_metermaids(char *exten, char *context)
+{
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "Notification of state change to metermaids %s@%s\n", exten, context);
+
+ /* Send notification to devicestate subsystem */
+ ast_device_state_changed("park:%s@%s", exten, context);
+ return;
+}
+
+/*! \brief metermaids callback from devicestate.c */
+static int metermaidstate(const char *data)
+{
+ int res = AST_DEVICE_INVALID;
+ char *context = ast_strdupa(data);
+ char *exten;
+
+ exten = strsep(&context, "@");
+ if (!context)
+ return res;
+
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "Checking state of exten %s in context %s\n", exten, context);
+
+ res = ast_exists_extension(NULL, context, exten, 1, NULL);
+
+ if (!res)
+ return AST_DEVICE_NOT_INUSE;
+ else
+ return AST_DEVICE_INUSE;
+}
+
+static int park_call_full(struct ast_channel *chan, struct ast_channel *peer, int timeout, int *extout, char *orig_chan_name)
+{
+ struct parkeduser *pu, *cur;
+ int i, x = -1, parking_range, parkingnum_copy;
+ struct ast_context *con;
+ const char *parkingexten;
+
+ /* Allocate memory for parking data */
+ if (!(pu = ast_calloc(1, sizeof(*pu))))
+ return -1;
+
+ /* Lock parking lot */
+ ast_mutex_lock(&parking_lock);
+ /* Check for channel variable PARKINGEXTEN */
+ parkingexten = pbx_builtin_getvar_helper(chan, "PARKINGEXTEN");
+ if (!ast_strlen_zero(parkingexten)) {
+ /*!\note The API forces us to specify a numeric parking slot, even
+ * though the architecture would tend to support non-numeric extensions
+ * (as are possible with SIP, for example). Hence, we enforce that
+ * limitation here. If extout was not numeric, we could permit
+ * arbitrary non-numeric extensions.
+ */
+ if (sscanf(parkingexten, "%d", &x) != 1 || x < 0) {
+ ast_log(LOG_WARNING, "PARKINGEXTEN does not indicate a valid parking slot: '%s'.\n", parkingexten);
+ ast_mutex_unlock(&parking_lock);
+ free(pu);
+ return 1; /* Continue execution if possible */
+ }
+ snprintf(pu->parkingexten, sizeof(pu->parkingexten), "%d", x);
+
+ if (ast_exists_extension(NULL, parking_con, pu->parkingexten, 1, NULL)) {
+ ast_mutex_unlock(&parking_lock);
+ free(pu);
+ ast_log(LOG_WARNING, "Requested parking extension already exists: %s@%s\n", parkingexten, parking_con);
+ return 1; /* Continue execution if possible */
+ }
+ } else {
+ /* Select parking space within range */
+ parking_range = parking_stop - parking_start+1;
+ for (i = 0; i < parking_range; i++) {
+ x = (i + parking_offset) % parking_range + parking_start;
+ cur = parkinglot;
+ while(cur) {
+ if (cur->parkingnum == x)
+ break;
+ cur = cur->next;
+ }
+ if (!cur)
+ break;
+ }
+
+ if (!(i < parking_range)) {
+ ast_log(LOG_WARNING, "No more parking spaces\n");
+ free(pu);
+ ast_mutex_unlock(&parking_lock);
+ return -1;
+ }
+ /* Set pointer for next parking */
+ if (parkfindnext)
+ parking_offset = x - parking_start + 1;
+ snprintf(pu->parkingexten, sizeof(pu->parkingexten), "%d", x);
+ }
+
+ chan->appl = "Parked Call";
+ chan->data = NULL;
+
+ pu->chan = chan;
+
+ /* Put the parked channel on hold if we have two different channels */
+ if (chan != peer) {
+ ast_indicate_data(pu->chan, AST_CONTROL_HOLD,
+ S_OR(parkmohclass, NULL),
+ !ast_strlen_zero(parkmohclass) ? strlen(parkmohclass) + 1 : 0);
+ }
+
+ pu->start = ast_tvnow();
+ pu->parkingnum = x;
+ pu->parkingtime = (timeout > 0) ? timeout : parkingtime;
+ if (extout)
+ *extout = x;
+
+ if (peer) {
+ /* This is so ugly that it hurts, but implementing get_base_channel() on local channels
+ could have ugly side effects. We could have transferer<->local,1<->local,2<->parking
+ and we need the callback name to be that of transferer. Since local,1/2 have the same
+ name we can be tricky and just grab the bridged channel from the other side of the local
+ */
+ if (!strcasecmp(peer->tech->type, "Local")) {
+ struct ast_channel *tmpchan, *base_peer;
+ char other_side[AST_CHANNEL_NAME];
+ char *c;
+ ast_copy_string(other_side, peer->name, sizeof(other_side));
+ if ((c = strrchr(other_side, ','))) {
+ *++c = '1';
+ }
+ if ((tmpchan = ast_get_channel_by_name_locked(other_side))) {
+ if ((base_peer = ast_bridged_channel(tmpchan))) {
+ ast_copy_string(pu->peername, base_peer->name, sizeof(pu->peername));
+ }
+ ast_channel_unlock(tmpchan);
+ }
+ } else {
+ ast_copy_string(pu->peername, peer->name, sizeof(pu->peername));
+ }
+ }
+
+ /* Remember what had been dialed, so that if the parking
+ expires, we try to come back to the same place */
+ ast_copy_string(pu->context, S_OR(chan->macrocontext, chan->context), sizeof(pu->context));
+ ast_copy_string(pu->exten, S_OR(chan->macroexten, chan->exten), sizeof(pu->exten));
+ pu->priority = chan->macropriority ? chan->macropriority : chan->priority;
+ pu->next = parkinglot;
+ parkinglot = pu;
+ parkingnum_copy = pu->parkingnum;
+ /* If parking a channel directly, don't quite yet get parking running on it */
+ if (peer == chan)
+ pu->notquiteyet = 1;
+
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "Parked %s on %d@%s. Will timeout back to extension [%s] %s, %d in %d seconds\n", pu->chan->name, pu->parkingnum, parking_con, pu->context, pu->exten, pu->priority, (pu->parkingtime/1000));
+
+ manager_event(EVENT_FLAG_CALL, "ParkedCall",
+ "Exten: %s\r\n"
+ "Channel: %s\r\n"
+ "From: %s\r\n"
+ "Timeout: %ld\r\n"
+ "CallerID: %s\r\n"
+ "CallerIDName: %s\r\n",
+ pu->parkingexten, pu->chan->name, peer ? peer->name : "",
+ (long)pu->start.tv_sec + (long)(pu->parkingtime/1000) - (long)time(NULL),
+ S_OR(pu->chan->cid.cid_num, "<unknown>"),
+ S_OR(pu->chan->cid.cid_name, "<unknown>")
+ );
+
+ if (peer && adsipark && ast_adsi_available(peer)) {
+ adsi_announce_park(peer, pu->parkingexten); /* Only supports parking numbers */
+ ast_adsi_unload_session(peer);
+ }
+
+ con = ast_context_find(parking_con);
+ if (!con)
+ con = ast_context_create(NULL, parking_con, registrar);
+ if (!con) /* Still no context? Bad */
+ ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n", parking_con);
+ if (con) {
+ if (!ast_add_extension2(con, 1, pu->parkingexten, 1, NULL, NULL, parkedcall, strdup(pu->parkingexten), ast_free, registrar)) {
+ notify_metermaids(pu->parkingexten, parking_con);
+ }
+ }
+
+ ast_mutex_unlock(&parking_lock);
+ /* Wake up the (presumably select()ing) thread */
+ pthread_kill(parking_thread, SIGURG);
+
+ /* Only say number if it's a number and the channel hasn't been masqueraded away */
+ if (peer && (ast_strlen_zero(orig_chan_name) || !strcasecmp(peer->name, orig_chan_name))) {
+ /* Make sure we don't start saying digits to the channel being parked */
+ ast_set_flag(peer, AST_FLAG_MASQ_NOSTREAM);
+ /* Tell the peer channel the number of the parking space */
+ ast_say_digits(peer, parkingnum_copy, "", peer->language);
+ ast_clear_flag(peer, AST_FLAG_MASQ_NOSTREAM);
+ }
+
+ if (peer == chan) { /* pu->notquiteyet = 1 */
+ /* Wake up parking thread if we're really done */
+ ast_indicate_data(pu->chan, AST_CONTROL_HOLD,
+ S_OR(parkmohclass, NULL),
+ !ast_strlen_zero(parkmohclass) ? strlen(parkmohclass) + 1 : 0);
+ pu->notquiteyet = 0;
+ pthread_kill(parking_thread, SIGURG);
+ }
+ return 0;
+}
+
+/*! \brief Park a call
+ \note We put the user in the parking list, then wake up the parking thread to be sure it looks
+ after these channels too */
+int ast_park_call(struct ast_channel *chan, struct ast_channel *peer, int timeout, int *extout)
+{
+ return park_call_full(chan, peer, timeout, extout, NULL);
+}
+
+static int masq_park_call(struct ast_channel *rchan, struct ast_channel *peer, int timeout, int *extout, int play_announcement)
+{
+ struct ast_channel *chan;
+ struct ast_frame *f;
+ char *orig_chan_name = NULL;
+ int park_status;
+
+ /* Make a new, fake channel that we'll use to masquerade in the real one */
+ if (!(chan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, rchan->accountcode, rchan->exten, rchan->context, rchan->amaflags, "Parked/%s",rchan->name))) {
+ ast_log(LOG_WARNING, "Unable to create parked channel\n");
+ return -1;
+ }
+
+ /* Make formats okay */
+ chan->readformat = rchan->readformat;
+ chan->writeformat = rchan->writeformat;
+ ast_channel_masquerade(chan, rchan);
+
+ /* Setup the extensions and such */
+ set_c_e_p(chan, rchan->context, rchan->exten, rchan->priority);
+
+ /* Make the masq execute */
+ if ((f = ast_read(chan))) {
+ ast_frfree(f);
+ }
+
+ if (peer == rchan) {
+ peer = chan;
+ }
+
+ if (!play_announcement) {
+ orig_chan_name = ast_strdupa(chan->name);
+ }
+
+ park_status = park_call_full(chan, peer, timeout, extout, orig_chan_name);
+ if (park_status == 1) {
+ /* would be nice to play: "invalid parking extension" */
+ ast_hangup(chan);
+ return -1;
+ }
+
+ return 0;
+}
+
+int ast_masq_park_call(struct ast_channel *rchan, struct ast_channel *peer, int timeout, int *extout)
+{
+ return masq_park_call(rchan, peer, timeout, extout, 0);
+}
+
+static int masq_park_call_announce(struct ast_channel *rchan, struct ast_channel *peer, int timeout, int *extout)
+{
+ return masq_park_call(rchan, peer, timeout, extout, 1);
+}
+#define FEATURE_RETURN_HANGUP -1
+#define FEATURE_RETURN_SUCCESSBREAK 0
+#define FEATURE_RETURN_PASSDIGITS 21
+#define FEATURE_RETURN_STOREDIGITS 22
+#define FEATURE_RETURN_SUCCESS 23
+#define FEATURE_RETURN_KEEPTRYING 24
+
+#define FEATURE_SENSE_CHAN (1 << 0)
+#define FEATURE_SENSE_PEER (1 << 1)
+
+/*! \brief
+ * set caller and callee according to the direction
+ */
+static void set_peers(struct ast_channel **caller, struct ast_channel **callee,
+ struct ast_channel *peer, struct ast_channel *chan, int sense)
+{
+ if (sense == FEATURE_SENSE_PEER) {
+ *caller = peer;
+ *callee = chan;
+ } else {
+ *callee = peer;
+ *caller = chan;
+ }
+}
+
+/*! \brief support routing for one touch call parking */
+static int builtin_parkcall(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense, void *data)
+{
+ struct ast_channel *parker;
+ struct ast_channel *parkee;
+ int res = 0;
+ struct ast_module_user *u;
+
+ u = ast_module_user_add(chan);
+
+ set_peers(&parker, &parkee, peer, chan, sense);
+ /* we used to set chan's exten and priority to "s" and 1
+ here, but this generates (in some cases) an invalid
+ extension, and if "s" exists, could errantly
+ cause execution of extensions you don't expect It
+ makes more sense to let nature take its course
+ when chan finishes, and let the pbx do its thing
+ and hang up when the park is over.
+ */
+ if (chan->_state != AST_STATE_UP)
+ res = ast_answer(chan);
+ if (!res)
+ res = ast_safe_sleep(chan, 1000);
+
+ if (!res) { /* one direction used to call park_call.... */
+ masq_park_call_announce(parkee, parker, 0, NULL);
+ res = 0; /* PBX should hangup zombie channel */
+ }
+
+ ast_module_user_remove(u);
+ return res;
+
+}
+
+static int builtin_automonitor(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense, void *data)
+{
+ char *caller_chan_id = NULL, *callee_chan_id = NULL, *args = NULL, *touch_filename = NULL;
+ int x = 0;
+ size_t len;
+ struct ast_channel *caller_chan, *callee_chan;
+
+ if (!monitor_ok) {
+ ast_log(LOG_ERROR,"Cannot record the call. The monitor application is disabled.\n");
+ return -1;
+ }
+
+ if (!monitor_app && !(monitor_app = pbx_findapp("Monitor"))) {
+ monitor_ok = 0;
+ ast_log(LOG_ERROR,"Cannot record the call. The monitor application is disabled.\n");
+ return -1;
+ }
+
+ set_peers(&caller_chan, &callee_chan, peer, chan, sense);
+
+ if (!ast_strlen_zero(courtesytone)) {
+ if (ast_autoservice_start(callee_chan))
+ return -1;
+ if (ast_stream_and_wait(caller_chan, courtesytone, caller_chan->language, "")) {
+ ast_log(LOG_WARNING, "Failed to play courtesy tone!\n");
+ ast_autoservice_stop(callee_chan);
+ return -1;
+ }
+ if (ast_autoservice_stop(callee_chan))
+ return -1;
+ }
+
+ if (callee_chan->monitor) {
+ if (option_verbose > 3)
+ ast_verbose(VERBOSE_PREFIX_3 "User hit '%s' to stop recording call.\n", code);
+ ast_monitor_stop(callee_chan, 1);
+ return FEATURE_RETURN_SUCCESS;
+ }
+
+ if (caller_chan && callee_chan) {
+ const char *touch_format = pbx_builtin_getvar_helper(caller_chan, "TOUCH_MONITOR_FORMAT");
+ const char *touch_monitor = pbx_builtin_getvar_helper(caller_chan, "TOUCH_MONITOR");
+
+ if (!touch_format)
+ touch_format = pbx_builtin_getvar_helper(callee_chan, "TOUCH_MONITOR_FORMAT");
+
+ if (!touch_monitor)
+ touch_monitor = pbx_builtin_getvar_helper(callee_chan, "TOUCH_MONITOR");
+
+ if (touch_monitor) {
+ len = strlen(touch_monitor) + 50;
+ args = alloca(len);
+ touch_filename = alloca(len);
+ snprintf(touch_filename, len, "auto-%ld-%s", (long)time(NULL), touch_monitor);
+ snprintf(args, len, "%s|%s|m", (touch_format) ? touch_format : "wav", touch_filename);
+ } else {
+ caller_chan_id = ast_strdupa(S_OR(caller_chan->cid.cid_num, caller_chan->name));
+ callee_chan_id = ast_strdupa(S_OR(callee_chan->cid.cid_num, callee_chan->name));
+ len = strlen(caller_chan_id) + strlen(callee_chan_id) + 50;
+ args = alloca(len);
+ touch_filename = alloca(len);
+ snprintf(touch_filename, len, "auto-%ld-%s-%s", (long)time(NULL), caller_chan_id, callee_chan_id);
+ snprintf(args, len, "%s|%s|m", S_OR(touch_format, "wav"), touch_filename);
+ }
+
+ for( x = 0; x < strlen(args); x++) {
+ if (args[x] == '/')
+ args[x] = '-';
+ }
+
+ if (option_verbose > 3)
+ ast_verbose(VERBOSE_PREFIX_3 "User hit '%s' to record call. filename: %s\n", code, args);
+
+ pbx_exec(callee_chan, monitor_app, args);
+ pbx_builtin_setvar_helper(callee_chan, "TOUCH_MONITOR_OUTPUT", touch_filename);
+ pbx_builtin_setvar_helper(caller_chan, "TOUCH_MONITOR_OUTPUT", touch_filename);
+
+ return FEATURE_RETURN_SUCCESS;
+ }
+
+ ast_log(LOG_NOTICE,"Cannot record the call. One or both channels have gone away.\n");
+ return -1;
+}
+
+static int builtin_disconnect(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense, void *data)
+{
+ if (option_verbose > 3)
+ ast_verbose(VERBOSE_PREFIX_3 "User hit '%s' to disconnect call.\n", code);
+ return FEATURE_RETURN_HANGUP;
+}
+
+static int finishup(struct ast_channel *chan)
+{
+ ast_indicate(chan, AST_CONTROL_UNHOLD);
+
+ return ast_autoservice_stop(chan);
+}
+
+/*! \brief Find the context for the transfer */
+static const char *real_ctx(struct ast_channel *transferer, struct ast_channel *transferee)
+{
+ const char *s = pbx_builtin_getvar_helper(transferer, "TRANSFER_CONTEXT");
+ if (ast_strlen_zero(s))
+ s = pbx_builtin_getvar_helper(transferee, "TRANSFER_CONTEXT");
+ if (ast_strlen_zero(s)) /* Use the non-macro context to transfer the call XXX ? */
+ s = transferer->macrocontext;
+ if (ast_strlen_zero(s))
+ s = transferer->context;
+ return s;
+}
+
+static int builtin_blindtransfer(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense, void *data)
+{
+ struct ast_channel *transferer;
+ struct ast_channel *transferee;
+ const char *transferer_real_context;
+ char xferto[256];
+ int res;
+
+ set_peers(&transferer, &transferee, peer, chan, sense);
+ transferer_real_context = real_ctx(transferer, transferee);
+ /* Start autoservice on chan while we talk to the originator */
+ ast_autoservice_start(transferee);
+ ast_indicate(transferee, AST_CONTROL_HOLD);
+
+ memset(xferto, 0, sizeof(xferto));
+
+ /* Transfer */
+ res = ast_stream_and_wait(transferer, "pbx-transfer", transferer->language, AST_DIGIT_ANY);
+ if (res < 0) {
+ finishup(transferee);
+ return -1; /* error ? */
+ }
+ if (res > 0) /* If they've typed a digit already, handle it */
+ xferto[0] = (char) res;
+
+ ast_stopstream(transferer);
+ res = ast_app_dtget(transferer, transferer_real_context, xferto, sizeof(xferto), 100, transferdigittimeout);
+ if (res < 0) { /* hangup, would be 0 for invalid and 1 for valid */
+ finishup(transferee);
+ return res;
+ }
+ if (!strcmp(xferto, ast_parking_ext())) {
+ res = finishup(transferee);
+ if (res)
+ res = -1;
+ else if (!masq_park_call_announce(transferee, transferer, 0, NULL)) { /* success */
+ /* We return non-zero, but tell the PBX not to hang the channel when
+ the thread dies -- We have to be careful now though. We are responsible for
+ hanging up the channel, else it will never be hung up! */
+ return 0;
+ } else {
+ ast_log(LOG_WARNING, "Unable to park call %s\n", transferee->name);
+ }
+ /*! \todo XXX Maybe we should have another message here instead of invalid extension XXX */
+ } else if (ast_exists_extension(transferee, transferer_real_context, xferto, 1, transferer->cid.cid_num)) {
+ pbx_builtin_setvar_helper(transferer, "BLINDTRANSFER", transferee->name);
+ pbx_builtin_setvar_helper(transferee, "BLINDTRANSFER", transferer->name);
+ res=finishup(transferee);
+ if (!transferer->cdr) {
+ transferer->cdr=ast_cdr_alloc();
+ if (transferer) {
+ ast_cdr_init(transferer->cdr, transferer); /* initilize our channel's cdr */
+ ast_cdr_start(transferer->cdr);
+ }
+ }
+ if (transferer->cdr) {
+ ast_cdr_setdestchan(transferer->cdr, transferee->name);
+ ast_cdr_setapp(transferer->cdr, "BLINDTRANSFER","");
+ }
+ if (!transferee->pbx) {
+ /* Doh! Use our handy async_goto functions */
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Transferring %s to '%s' (context %s) priority 1\n"
+ ,transferee->name, xferto, transferer_real_context);
+ if (ast_async_goto(transferee, transferer_real_context, xferto, 1))
+ ast_log(LOG_WARNING, "Async goto failed :-(\n");
+ res = -1;
+ } else {
+ /* Set the channel's new extension, since it exists, using transferer context */
+ set_c_e_p(transferee, transferer_real_context, xferto, 0);
+ }
+ check_goto_on_transfer(transferer);
+ return res;
+ } else {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Unable to find extension '%s' in context '%s'\n", xferto, transferer_real_context);
+ }
+ if (ast_stream_and_wait(transferer, xferfailsound, transferer->language, AST_DIGIT_ANY) < 0 ) {
+ finishup(transferee);
+ return -1;
+ }
+ ast_stopstream(transferer);
+ res = finishup(transferee);
+ if (res) {
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "Hungup during autoservice stop on '%s'\n", transferee->name);
+ return res;
+ }
+ return FEATURE_RETURN_SUCCESS;
+}
+
+static int check_compat(struct ast_channel *c, struct ast_channel *newchan)
+{
+ if (ast_channel_make_compatible(c, newchan) < 0) {
+ ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n",
+ c->name, newchan->name);
+ ast_hangup(newchan);
+ return -1;
+ }
+ return 0;
+}
+
+static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense, void *data)
+{
+ struct ast_channel *transferer;
+ struct ast_channel *transferee;
+ const char *transferer_real_context;
+ char xferto[256] = "";
+ int res;
+ int outstate=0;
+ struct ast_channel *newchan;
+ struct ast_channel *xferchan;
+ struct ast_bridge_thread_obj *tobj;
+ struct ast_bridge_config bconfig;
+ struct ast_frame *f;
+ int l;
+ struct ast_datastore *features_datastore;
+ struct ast_dial_features *dialfeatures = NULL;
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Executing Attended Transfer %s, %s (sense=%d) \n", chan->name, peer->name, sense);
+ set_peers(&transferer, &transferee, peer, chan, sense);
+ transferer_real_context = real_ctx(transferer, transferee);
+ /* Start autoservice on chan while we talk to the originator */
+ ast_autoservice_start(transferee);
+ ast_indicate(transferee, AST_CONTROL_HOLD);
+
+ /* Transfer */
+ res = ast_stream_and_wait(transferer, "pbx-transfer", transferer->language, AST_DIGIT_ANY);
+ if (res < 0) {
+ finishup(transferee);
+ return res;
+ }
+ if (res > 0) /* If they've typed a digit already, handle it */
+ xferto[0] = (char) res;
+
+ /* this is specific of atxfer */
+ res = ast_app_dtget(transferer, transferer_real_context, xferto, sizeof(xferto), 100, transferdigittimeout);
+ if (res < 0) { /* hangup, would be 0 for invalid and 1 for valid */
+ finishup(transferee);
+ return res;
+ }
+ if (res == 0) {
+ ast_log(LOG_WARNING, "Did not read data.\n");
+ finishup(transferee);
+ if (ast_stream_and_wait(transferer, "beeperr", transferer->language, ""))
+ return -1;
+ return FEATURE_RETURN_SUCCESS;
+ }
+
+ /* valid extension, res == 1 */
+ if (!ast_exists_extension(transferer, transferer_real_context, xferto, 1, transferer->cid.cid_num)) {
+ ast_log(LOG_WARNING, "Extension %s does not exist in context %s\n",xferto,transferer_real_context);
+ finishup(transferee);
+ if (ast_stream_and_wait(transferer, "beeperr", transferer->language, ""))
+ return -1;
+ return FEATURE_RETURN_SUCCESS;
+ }
+
+ l = strlen(xferto);
+ snprintf(xferto + l, sizeof(xferto) - l, "@%s", transferer_real_context); /* append context */
+ newchan = ast_feature_request_and_dial(transferer, "Local", ast_best_codec(transferer->nativeformats),
+ xferto, atxfernoanswertimeout, &outstate, transferer->cid.cid_num, transferer->cid.cid_name, transferer->language);
+
+ /* If we are the callee and we are being transferred, after the masquerade
+ * caller features will really be the original callee features */
+ ast_channel_lock(transferee);
+ if ((features_datastore = ast_channel_datastore_find(transferee, &dial_features_info, NULL))) {
+ dialfeatures = features_datastore->data;
+ }
+ ast_channel_unlock(transferee);
+
+ if (dialfeatures && !dialfeatures->is_caller) {
+ ast_copy_flags(&(config->features_caller), &(dialfeatures->features_callee), AST_FLAGS_ALL);
+ }
+
+ ast_indicate(transferer, -1);
+ if (!newchan) {
+ finishup(transferee);
+ /* any reason besides user requested cancel and busy triggers the failed sound */
+ if (outstate != AST_CONTROL_UNHOLD && outstate != AST_CONTROL_BUSY &&
+ ast_stream_and_wait(transferer, xferfailsound, transferer->language, ""))
+ return -1;
+ return FEATURE_RETURN_SUCCESS;
+ }
+
+ if (check_compat(transferer, newchan)) {
+ /* we do mean transferee here, NOT transferer */
+ finishup(transferee);
+ return -1;
+ }
+ memset(&bconfig,0,sizeof(struct ast_bridge_config));
+ ast_set_flag(&(bconfig.features_caller), AST_FEATURE_DISCONNECT);
+ ast_set_flag(&(bconfig.features_callee), AST_FEATURE_DISCONNECT);
+ res = ast_bridge_call(transferer, newchan, &bconfig);
+ if (newchan->_softhangup || !transferer->_softhangup) {
+ ast_hangup(newchan);
+ if (ast_stream_and_wait(transferer, xfersound, transferer->language, ""))
+ ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
+ finishup(transferee);
+ transferer->_softhangup = 0;
+ return FEATURE_RETURN_SUCCESS;
+ }
+
+ if (check_compat(transferee, newchan)) {
+ finishup(transferee);
+ return -1;
+ }
+
+ ast_indicate(transferee, AST_CONTROL_UNHOLD);
+
+ if ((ast_autoservice_stop(transferee) < 0)
+ || (ast_waitfordigit(transferee, 100) < 0)
+ || (ast_waitfordigit(newchan, 100) < 0)
+ || ast_check_hangup(transferee)
+ || ast_check_hangup(newchan)) {
+ ast_hangup(newchan);
+ return -1;
+ }
+
+ xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "Transfered/%s", transferee->name);
+ if (!xferchan) {
+ ast_hangup(newchan);
+ return -1;
+ }
+ /* Make formats okay */
+ xferchan->visible_indication = transferer->visible_indication;
+ xferchan->readformat = transferee->readformat;
+ xferchan->writeformat = transferee->writeformat;
+ ast_channel_masquerade(xferchan, transferee);
+ ast_explicit_goto(xferchan, transferee->context, transferee->exten, transferee->priority);
+ xferchan->_state = AST_STATE_UP;
+ ast_clear_flag(xferchan, AST_FLAGS_ALL);
+ xferchan->_softhangup = 0;
+
+ if ((f = ast_read(xferchan)))
+ ast_frfree(f);
+
+ newchan->_state = AST_STATE_UP;
+ ast_clear_flag(newchan, AST_FLAGS_ALL);
+ newchan->_softhangup = 0;
+
+ tobj = ast_calloc(1, sizeof(struct ast_bridge_thread_obj));
+ if (!tobj) {
+ ast_hangup(xferchan);
+ ast_hangup(newchan);
+ return -1;
+ }
+
+ /* For the case where the transfer target is being connected with the original
+ caller store the target's original features, and apply to the bridge */
+ ast_channel_lock(newchan);
+ if ((features_datastore = ast_channel_datastore_find(newchan, &dial_features_info, NULL))) {
+ dialfeatures = features_datastore->data;
+ }
+ ast_channel_unlock(newchan);
+
+ if (dialfeatures) {
+ ast_copy_flags(&(config->features_callee), &(dialfeatures->features_callee), AST_FLAGS_ALL);
+ }
+
+ tobj->chan = newchan;
+ tobj->peer = xferchan;
+ tobj->bconfig = *config;
+
+ if (tobj->bconfig.end_bridge_callback_data_fixup) {
+ tobj->bconfig.end_bridge_callback_data_fixup(&tobj->bconfig, tobj->peer, tobj->chan);
+ }
+
+ if (ast_stream_and_wait(newchan, xfersound, newchan->language, ""))
+ ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
+ ast_bridge_call_thread_launch(tobj);
+ return -1; /* XXX meaning the channel is bridged ? */
+}
+
+
+/* add atxfer and automon as undefined so you can only use em if you configure them */
+#define FEATURES_COUNT (sizeof(builtin_features) / sizeof(builtin_features[0]))
+
+AST_RWLOCK_DEFINE_STATIC(features_lock);
+
+static struct ast_call_feature builtin_features[] =
+ {
+ { AST_FEATURE_REDIRECT, "Blind Transfer", "blindxfer", "#", "#", builtin_blindtransfer, AST_FEATURE_FLAG_NEEDSDTMF, "" },
+ { AST_FEATURE_REDIRECT, "Attended Transfer", "atxfer", "", "", builtin_atxfer, AST_FEATURE_FLAG_NEEDSDTMF, "" },
+ { AST_FEATURE_AUTOMON, "One Touch Monitor", "automon", "", "", builtin_automonitor, AST_FEATURE_FLAG_NEEDSDTMF, "" },
+ { AST_FEATURE_DISCONNECT, "Disconnect Call", "disconnect", "*", "*", builtin_disconnect, AST_FEATURE_FLAG_NEEDSDTMF, "" },
+ { AST_FEATURE_PARKCALL, "Park Call", "parkcall", "", "", builtin_parkcall, AST_FEATURE_FLAG_NEEDSDTMF, "" },
+};
+
+
+static AST_RWLIST_HEAD_STATIC(feature_list, ast_call_feature);
+
+/*! \brief register new feature into feature_list*/
+void ast_register_feature(struct ast_call_feature *feature)
+{
+ if (!feature) {
+ ast_log(LOG_NOTICE,"You didn't pass a feature!\n");
+ return;
+ }
+
+ AST_RWLIST_WRLOCK(&feature_list);
+ AST_RWLIST_INSERT_HEAD(&feature_list, feature, feature_entry);
+ AST_RWLIST_UNLOCK(&feature_list);
+
+ if (option_verbose >= 2) {
+ ast_verbose(VERBOSE_PREFIX_2 "Registered Feature '%s'\n",feature->sname);
+ }
+}
+
+/*! \brief unregister feature from feature_list */
+void ast_unregister_feature(struct ast_call_feature *feature)
+{
+ if (!feature)
+ return;
+
+ AST_RWLIST_WRLOCK(&feature_list);
+ AST_RWLIST_REMOVE(&feature_list, feature, feature_entry);
+ AST_RWLIST_UNLOCK(&feature_list);
+
+ free(feature);
+}
+
+/*! \brief Remove all features in the list */
+static void ast_unregister_features(void)
+{
+ struct ast_call_feature *feature;
+
+ AST_RWLIST_WRLOCK(&feature_list);
+ while ((feature = AST_LIST_REMOVE_HEAD(&feature_list, feature_entry))) {
+ free(feature);
+ }
+ AST_RWLIST_UNLOCK(&feature_list);
+}
+
+/*! \brief find a feature by name */
+static struct ast_call_feature *find_dynamic_feature(const char *name)
+{
+ struct ast_call_feature *tmp;
+
+ AST_RWLIST_TRAVERSE(&feature_list, tmp, feature_entry) {
+ if (!strcasecmp(tmp->sname, name)) {
+ break;
+ }
+ }
+
+ return tmp;
+}
+
+/*! \brief exec an app by feature */
+static int feature_exec_app(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense, void *data)
+{
+ struct ast_app *app;
+ struct ast_call_feature *feature = data;
+ struct ast_channel *work, *idle;
+ int res;
+
+ if (!feature) { /* shouldn't ever happen! */
+ ast_log(LOG_NOTICE, "Found feature before, but at execing we've lost it??\n");
+ return -1;
+ }
+
+ if (sense == FEATURE_SENSE_CHAN) {
+ if (!ast_test_flag(feature, AST_FEATURE_FLAG_BYCALLER))
+ return FEATURE_RETURN_KEEPTRYING;
+ if (ast_test_flag(feature, AST_FEATURE_FLAG_ONSELF)) {
+ work = chan;
+ idle = peer;
+ } else {
+ work = peer;
+ idle = chan;
+ }
+ } else {
+ if (!ast_test_flag(feature, AST_FEATURE_FLAG_BYCALLEE))
+ return FEATURE_RETURN_KEEPTRYING;
+ if (ast_test_flag(feature, AST_FEATURE_FLAG_ONSELF)) {
+ work = peer;
+ idle = chan;
+ } else {
+ work = chan;
+ idle = peer;
+ }
+ }
+
+ if (!(app = pbx_findapp(feature->app))) {
+ ast_log(LOG_WARNING, "Could not find application (%s)\n", feature->app);
+ return -2;
+ }
+
+ ast_autoservice_start(idle);
+
+ if (!ast_strlen_zero(feature->moh_class))
+ ast_moh_start(idle, feature->moh_class, NULL);
+
+ res = pbx_exec(work, app, feature->app_args);
+
+ if (!ast_strlen_zero(feature->moh_class))
+ ast_moh_stop(idle);
+
+ ast_autoservice_stop(idle);
+
+ if (res)
+ return FEATURE_RETURN_SUCCESSBREAK;
+
+ return FEATURE_RETURN_SUCCESS; /*! \todo XXX should probably return res */
+}
+
+static void unmap_features(void)
+{
+ int x;
+
+ ast_rwlock_wrlock(&features_lock);
+ for (x = 0; x < FEATURES_COUNT; x++)
+ strcpy(builtin_features[x].exten, builtin_features[x].default_exten);
+ ast_rwlock_unlock(&features_lock);
+}
+
+static int remap_feature(const char *name, const char *value)
+{
+ int x, res = -1;
+
+ ast_rwlock_wrlock(&features_lock);
+ for (x = 0; x < FEATURES_COUNT; x++) {
+ if (strcasecmp(builtin_features[x].sname, name))
+ continue;
+
+ ast_copy_string(builtin_features[x].exten, value, sizeof(builtin_features[x].exten));
+ res = 0;
+ break;
+ }
+ ast_rwlock_unlock(&features_lock);
+
+ return res;
+}
+
+static int ast_feature_interpret(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
+{
+ int x;
+ struct ast_flags features;
+ struct ast_call_feature *feature;
+ const char *dynamic_features;
+ char *tmp, *tok;
+ int res = FEATURE_RETURN_PASSDIGITS;
+ int feature_detected = 0;
+
+ if (sense == FEATURE_SENSE_CHAN) {
+ ast_copy_flags(&features, &(config->features_caller), AST_FLAGS_ALL);
+ dynamic_features = pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES");
+ } else {
+ ast_copy_flags(&features, &(config->features_callee), AST_FLAGS_ALL);
+ dynamic_features = pbx_builtin_getvar_helper(peer, "DYNAMIC_FEATURES");
+ }
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Feature interpret: chan=%s, peer=%s, code=%s, sense=%d, features=%d dynamic=%s\n", chan->name, peer->name, code, sense, features.flags, dynamic_features);
+
+ ast_rwlock_rdlock(&features_lock);
+ for (x = 0; x < FEATURES_COUNT; x++) {
+ if ((ast_test_flag(&features, builtin_features[x].feature_mask)) &&
+ !ast_strlen_zero(builtin_features[x].exten)) {
+ /* Feature is up for consideration */
+ if (!strcmp(builtin_features[x].exten, code)) {
+ res = builtin_features[x].operation(chan, peer, config, code, sense, NULL);
+ feature_detected = 1;
+ break;
+ } else if (!strncmp(builtin_features[x].exten, code, strlen(code))) {
+ if (res == FEATURE_RETURN_PASSDIGITS)
+ res = FEATURE_RETURN_STOREDIGITS;
+ }
+ }
+ }
+ ast_rwlock_unlock(&features_lock);
+
+ if (ast_strlen_zero(dynamic_features) || feature_detected)
+ return res;
+
+ tmp = ast_strdupa(dynamic_features);
+
+ while ((tok = strsep(&tmp, "#"))) {
+ AST_RWLIST_RDLOCK(&feature_list);
+ if (!(feature = find_dynamic_feature(tok))) {
+ AST_RWLIST_UNLOCK(&feature_list);
+ continue;
+ }
+
+ /* Feature is up for consideration */
+ if (!strcmp(feature->exten, code)) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 " Feature Found: %s exten: %s\n",feature->sname, tok);
+ res = feature->operation(chan, peer, config, code, sense, feature);
+ if (res != FEATURE_RETURN_KEEPTRYING) {
+ AST_RWLIST_UNLOCK(&feature_list);
+ break;
+ }
+ res = FEATURE_RETURN_PASSDIGITS;
+ } else if (!strncmp(feature->exten, code, strlen(code)))
+ res = FEATURE_RETURN_STOREDIGITS;
+
+ AST_RWLIST_UNLOCK(&feature_list);
+ }
+
+ return res;
+}
+
+static void set_config_flags(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config)
+{
+ int x;
+
+ ast_clear_flag(config, AST_FLAGS_ALL);
+
+ ast_rwlock_rdlock(&features_lock);
+ for (x = 0; x < FEATURES_COUNT; x++) {
+ if (!ast_test_flag(builtin_features + x, AST_FEATURE_FLAG_NEEDSDTMF))
+ continue;
+
+ if (ast_test_flag(&(config->features_caller), builtin_features[x].feature_mask))
+ ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0);
+
+ if (ast_test_flag(&(config->features_callee), builtin_features[x].feature_mask))
+ ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
+ }
+ ast_rwlock_unlock(&features_lock);
+
+ if (chan && peer && !(ast_test_flag(config, AST_BRIDGE_DTMF_CHANNEL_0) && ast_test_flag(config, AST_BRIDGE_DTMF_CHANNEL_1))) {
+ const char *dynamic_features = pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES");
+
+ if (dynamic_features) {
+ char *tmp = ast_strdupa(dynamic_features);
+ char *tok;
+ struct ast_call_feature *feature;
+
+ /* while we have a feature */
+ while ((tok = strsep(&tmp, "#"))) {
+ AST_RWLIST_RDLOCK(&feature_list);
+ if ((feature = find_dynamic_feature(tok)) && ast_test_flag(feature, AST_FEATURE_FLAG_NEEDSDTMF)) {
+ if (ast_test_flag(feature, AST_FEATURE_FLAG_BYCALLER))
+ ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0);
+ if (ast_test_flag(feature, AST_FEATURE_FLAG_BYCALLEE))
+ ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
+ }
+ AST_RWLIST_UNLOCK(&feature_list);
+ }
+ }
+ }
+}
+
+/*! \todo XXX Check - this is very similar to the code in channel.c */
+static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *caller, const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name, const char *language)
+{
+ int state = 0;
+ int cause = 0;
+ int to;
+ struct ast_channel *chan;
+ struct ast_channel *monitor_chans[2];
+ struct ast_channel *active_channel;
+ int res = 0, ready = 0;
+
+ if ((chan = ast_request(type, format, data, &cause))) {
+ ast_set_callerid(chan, cid_num, cid_name, cid_num);
+ ast_string_field_set(chan, language, language);
+ ast_channel_inherit_variables(caller, chan);
+ pbx_builtin_setvar_helper(chan, "TRANSFERERNAME", caller->name);
+
+ if (!ast_call(chan, data, timeout)) {
+ struct timeval started;
+ int x, len = 0;
+ char *disconnect_code = NULL, *dialed_code = NULL;
+
+ ast_indicate(caller, AST_CONTROL_RINGING);
+ /* support dialing of the featuremap disconnect code while performing an attended tranfer */
+ ast_rwlock_rdlock(&features_lock);
+ for (x = 0; x < FEATURES_COUNT; x++) {
+ if (strcasecmp(builtin_features[x].sname, "disconnect"))
+ continue;
+
+ disconnect_code = builtin_features[x].exten;
+ len = strlen(disconnect_code) + 1;
+ dialed_code = alloca(len);
+ memset(dialed_code, 0, len);
+ break;
+ }
+ ast_rwlock_unlock(&features_lock);
+ x = 0;
+ started = ast_tvnow();
+ to = timeout;
+ while (!ast_check_hangup(caller) && timeout && (chan->_state != AST_STATE_UP)) {
+ struct ast_frame *f = NULL;
+
+ monitor_chans[0] = caller;
+ monitor_chans[1] = chan;
+ active_channel = ast_waitfor_n(monitor_chans, 2, &to);
+
+ /* see if the timeout has been violated */
+ if(ast_tvdiff_ms(ast_tvnow(), started) > timeout) {
+ state = AST_CONTROL_UNHOLD;
+ ast_log(LOG_NOTICE, "We exceeded our AT-timeout\n");
+ break; /*doh! timeout*/
+ }
+
+ if (!active_channel)
+ continue;
+
+ if (chan && (chan == active_channel)){
+ f = ast_read(chan);
+ if (f == NULL) { /*doh! where'd he go?*/
+ state = AST_CONTROL_HANGUP;
+ res = 0;
+ break;
+ }
+
+ if (f->frametype == AST_FRAME_CONTROL || f->frametype == AST_FRAME_DTMF || f->frametype == AST_FRAME_TEXT) {
+ if (f->subclass == AST_CONTROL_RINGING) {
+ state = f->subclass;
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "%s is ringing\n", chan->name);
+ ast_indicate(caller, AST_CONTROL_RINGING);
+ } else if ((f->subclass == AST_CONTROL_BUSY) || (f->subclass == AST_CONTROL_CONGESTION)) {
+ state = f->subclass;
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "%s is busy\n", chan->name);
+ ast_indicate(caller, AST_CONTROL_BUSY);
+ ast_frfree(f);
+ f = NULL;
+ break;
+ } else if (f->subclass == AST_CONTROL_ANSWER) {
+ /* This is what we are hoping for */
+ state = f->subclass;
+ ast_frfree(f);
+ f = NULL;
+ ready=1;
+ break;
+ } else if (f->subclass != -1) {
+ ast_log(LOG_NOTICE, "Don't know what to do about control frame: %d\n", f->subclass);
+ }
+ /* else who cares */
+ }
+
+ } else if (caller && (active_channel == caller)) {
+ f = ast_read(caller);
+ if (f == NULL) { /*doh! where'd he go?*/
+ if (caller->_softhangup && !chan->_softhangup) {
+ /* make this a blind transfer */
+ ready = 1;
+ break;
+ }
+ state = AST_CONTROL_HANGUP;
+ res = 0;
+ break;
+ }
+
+ if (f->frametype == AST_FRAME_DTMF) {
+ dialed_code[x++] = f->subclass;
+ dialed_code[x] = '\0';
+ if (strlen(dialed_code) == len) {
+ x = 0;
+ } else if (x && strncmp(dialed_code, disconnect_code, x)) {
+ x = 0;
+ dialed_code[x] = '\0';
+ }
+ if (*dialed_code && !strcmp(dialed_code, disconnect_code)) {
+ /* Caller Canceled the call */
+ state = AST_CONTROL_UNHOLD;
+ ast_frfree(f);
+ f = NULL;
+ break;
+ }
+ }
+ }
+ if (f)
+ ast_frfree(f);
+ } /* end while */
+ } else
+ ast_log(LOG_NOTICE, "Unable to call channel %s/%s\n", type, (char *)data);
+ } else {
+ ast_log(LOG_NOTICE, "Unable to request channel %s/%s\n", type, (char *)data);
+ switch(cause) {
+ case AST_CAUSE_BUSY:
+ state = AST_CONTROL_BUSY;
+ break;
+ case AST_CAUSE_CONGESTION:
+ state = AST_CONTROL_CONGESTION;
+ break;
+ }
+ }
+
+ ast_indicate(caller, -1);
+ if (chan && ready) {
+ if (chan->_state == AST_STATE_UP)
+ state = AST_CONTROL_ANSWER;
+ res = 0;
+ } else if(chan) {
+ res = -1;
+ ast_hangup(chan);
+ chan = NULL;
+ } else {
+ res = -1;
+ }
+
+ if (outstate)
+ *outstate = state;
+
+ return chan;
+}
+
+static struct ast_cdr *pick_unlocked_cdr(struct ast_cdr *cdr)
+{
+ struct ast_cdr *cdr_orig = cdr;
+ while (cdr) {
+ if (!ast_test_flag(cdr,AST_CDR_FLAG_LOCKED))
+ return cdr;
+ cdr = cdr->next;
+ }
+ return cdr_orig; /* everybody LOCKED or some other weirdness, like a NULL */
+}
+
+
+int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast_bridge_config *config)
+{
+ /* Copy voice back and forth between the two channels. Give the peer
+ the ability to transfer calls with '#<extension' syntax. */
+ struct ast_frame *f;
+ struct ast_channel *who;
+ char chan_featurecode[FEATURE_MAX_LEN + 1]="";
+ char peer_featurecode[FEATURE_MAX_LEN + 1]="";
+ char orig_channame[AST_MAX_EXTENSION];
+ char orig_peername[AST_MAX_EXTENSION];
+
+ int res;
+ int diff;
+ int hasfeatures=0;
+ int hadfeatures=0;
+ int autoloopflag;
+ struct ast_option_header *aoh;
+ struct ast_bridge_config backup_config;
+ struct ast_cdr *bridge_cdr = NULL;
+ struct ast_cdr *orig_peer_cdr = NULL;
+ struct ast_cdr *chan_cdr = pick_unlocked_cdr(chan->cdr); /* the proper chan cdr, if there are forked cdrs */
+ struct ast_cdr *peer_cdr = pick_unlocked_cdr(peer->cdr); /* the proper chan cdr, if there are forked cdrs */
+ struct ast_cdr *new_chan_cdr = NULL; /* the proper chan cdr, if there are forked cdrs */
+ struct ast_cdr *new_peer_cdr = NULL; /* the proper chan cdr, if there are forked cdrs */
+
+ memset(&backup_config, 0, sizeof(backup_config));
+
+ config->start_time = ast_tvnow();
+
+ if (chan && peer) {
+ pbx_builtin_setvar_helper(chan, "BRIDGEPEER", peer->name);
+ pbx_builtin_setvar_helper(peer, "BRIDGEPEER", chan->name);
+ } else if (chan) {
+ pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", NULL);
+ }
+
+ /* This is an interesting case. One example is if a ringing channel gets redirected to
+ * an extension that picks up a parked call. This will make sure that the call taken
+ * out of parking gets told that the channel it just got bridged to is still ringing. */
+ if (chan->_state == AST_STATE_RINGING && peer->visible_indication != AST_CONTROL_RINGING) {
+ ast_indicate(peer, AST_CONTROL_RINGING);
+ }
+
+ if (monitor_ok) {
+ const char *monitor_exec;
+ struct ast_channel *src = NULL;
+ if (!monitor_app) {
+ if (!(monitor_app = pbx_findapp("Monitor")))
+ monitor_ok=0;
+ }
+ if ((monitor_exec = pbx_builtin_getvar_helper(chan, "AUTO_MONITOR")))
+ src = chan;
+ else if ((monitor_exec = pbx_builtin_getvar_helper(peer, "AUTO_MONITOR")))
+ src = peer;
+ if (monitor_app && src) {
+ char *tmp = ast_strdupa(monitor_exec);
+ pbx_exec(src, monitor_app, tmp);
+ }
+ }
+
+ set_config_flags(chan, peer, config);
+ config->firstpass = 1;
+
+ /* Answer if need be */
+ if (ast_answer(chan))
+ return -1;
+
+ ast_copy_string(orig_channame,chan->name,sizeof(orig_channame));
+ ast_copy_string(orig_peername,peer->name,sizeof(orig_peername));
+ orig_peer_cdr = peer_cdr;
+
+ if (!chan_cdr || (chan_cdr && !ast_test_flag(chan_cdr, AST_CDR_FLAG_POST_DISABLED))) {
+
+ if (chan_cdr) {
+ ast_set_flag(chan_cdr, AST_CDR_FLAG_MAIN);
+ ast_cdr_update(chan);
+ bridge_cdr = ast_cdr_dup(chan_cdr);
+ ast_copy_string(bridge_cdr->lastapp, chan->appl, sizeof(bridge_cdr->lastapp));
+ ast_copy_string(bridge_cdr->lastdata, chan->data, sizeof(bridge_cdr->lastdata));
+ } else {
+ /* better yet, in a xfer situation, find out why the chan cdr got zapped (pun unintentional) */
+ bridge_cdr = ast_cdr_alloc(); /* this should be really, really rare/impossible? */
+ ast_copy_string(bridge_cdr->channel, chan->name, sizeof(bridge_cdr->channel));
+ ast_copy_string(bridge_cdr->dstchannel, peer->name, sizeof(bridge_cdr->dstchannel));
+ ast_copy_string(bridge_cdr->uniqueid, chan->uniqueid, sizeof(bridge_cdr->uniqueid));
+ ast_copy_string(bridge_cdr->lastapp, chan->appl, sizeof(bridge_cdr->lastapp));
+ ast_copy_string(bridge_cdr->lastdata, chan->data, sizeof(bridge_cdr->lastdata));
+ ast_cdr_setcid(bridge_cdr, chan);
+ bridge_cdr->disposition = (chan->_state == AST_STATE_UP) ? AST_CDR_ANSWERED : AST_CDR_NULL;
+ bridge_cdr->amaflags = chan->amaflags ? chan->amaflags : ast_default_amaflags;
+ ast_copy_string(bridge_cdr->accountcode, chan->accountcode, sizeof(bridge_cdr->accountcode));
+ /* Destination information */
+ ast_copy_string(bridge_cdr->dst, chan->exten, sizeof(bridge_cdr->dst));
+ ast_copy_string(bridge_cdr->dcontext, chan->context, sizeof(bridge_cdr->dcontext));
+ if (peer_cdr) {
+ bridge_cdr->start = peer_cdr->start;
+ ast_copy_string(bridge_cdr->userfield, peer_cdr->userfield, sizeof(bridge_cdr->userfield));
+ } else {
+ ast_cdr_start(bridge_cdr);
+ }
+ }
+ /* peer_cdr->answer will be set when a macro runs on the peer;
+ in that case, the bridge answer will be delayed while the
+ macro plays on the peer channel. The peer answered the call
+ before the macro started playing. To the phone system,
+ this is billable time for the call, even tho the caller
+ hears nothing but ringing while the macro does its thing. */
+ if (peer_cdr && !ast_tvzero(peer_cdr->answer)) {
+ bridge_cdr->answer = peer_cdr->answer;
+ chan_cdr->answer = peer_cdr->answer;
+ bridge_cdr->disposition = peer_cdr->disposition;
+ chan_cdr->disposition = peer_cdr->disposition;
+ } else {
+ ast_cdr_answer(bridge_cdr);
+ ast_cdr_answer(chan_cdr); /* for the sake of cli status checks */
+ }
+ ast_set_flag(chan_cdr, AST_CDR_FLAG_BRIDGED);
+ if (peer_cdr) {
+ ast_set_flag(peer_cdr, AST_CDR_FLAG_BRIDGED);
+ }
+ }
+
+ for (;;) {
+ struct ast_channel *other; /* used later */
+
+ res = ast_channel_bridge(chan, peer, config, &f, &who);
+
+ if (config->feature_timer) {
+ /* Update time limit for next pass */
+ diff = ast_tvdiff_ms(ast_tvnow(), config->start_time);
+ config->feature_timer -= diff;
+ if (hasfeatures) {
+ /* Running on backup config, meaning a feature might be being
+ activated, but that's no excuse to keep things going
+ indefinitely! */
+ if (backup_config.feature_timer && ((backup_config.feature_timer -= diff) <= 0)) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Timed out, realtime this time!\n");
+ config->feature_timer = 0;
+ who = chan;
+ if (f)
+ ast_frfree(f);
+ f = NULL;
+ res = 0;
+ } else if (config->feature_timer <= 0) {
+ /* Not *really* out of time, just out of time for
+ digits to come in for features. */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Timed out for feature!\n");
+ if (!ast_strlen_zero(peer_featurecode)) {
+ ast_dtmf_stream(chan, peer, peer_featurecode, 0);
+ memset(peer_featurecode, 0, sizeof(peer_featurecode));
+ }
+ if (!ast_strlen_zero(chan_featurecode)) {
+ ast_dtmf_stream(peer, chan, chan_featurecode, 0);
+ memset(chan_featurecode, 0, sizeof(chan_featurecode));
+ }
+ if (f)
+ ast_frfree(f);
+ hasfeatures = !ast_strlen_zero(chan_featurecode) || !ast_strlen_zero(peer_featurecode);
+ if (!hasfeatures) {
+ /* Restore original (possibly time modified) bridge config */
+ memcpy(config, &backup_config, sizeof(struct ast_bridge_config));
+ memset(&backup_config, 0, sizeof(backup_config));
+ }
+ hadfeatures = hasfeatures;
+ /* Continue as we were */
+ continue;
+ } else if (!f) {
+ /* The bridge returned without a frame and there is a feature in progress.
+ * However, we don't think the feature has quite yet timed out, so just
+ * go back into the bridge. */
+ continue;
+ }
+ } else {
+ if (config->feature_timer <=0) {
+ /* We ran out of time */
+ config->feature_timer = 0;
+ who = chan;
+ if (f)
+ ast_frfree(f);
+ f = NULL;
+ res = 0;
+ }
+ }
+ }
+ if (res < 0) {
+ if (!ast_test_flag(chan, AST_FLAG_ZOMBIE) && !ast_test_flag(peer, AST_FLAG_ZOMBIE) && !ast_check_hangup(chan) && !ast_check_hangup(peer))
+ ast_log(LOG_WARNING, "Bridge failed on channels %s and %s\n", chan->name, peer->name);
+ goto before_you_go;
+ }
+
+ if (!f || (f->frametype == AST_FRAME_CONTROL &&
+ (f->subclass == AST_CONTROL_HANGUP || f->subclass == AST_CONTROL_BUSY ||
+ f->subclass == AST_CONTROL_CONGESTION ) ) ) {
+ res = -1;
+ break;
+ }
+ /* many things should be sent to the 'other' channel */
+ other = (who == chan) ? peer : chan;
+ if (f->frametype == AST_FRAME_CONTROL) {
+ switch (f->subclass) {
+ case AST_CONTROL_RINGING:
+ case AST_CONTROL_FLASH:
+ case -1:
+ ast_indicate(other, f->subclass);
+ break;
+ case AST_CONTROL_HOLD:
+ case AST_CONTROL_UNHOLD:
+ ast_indicate_data(other, f->subclass, f->data, f->datalen);
+ break;
+ case AST_CONTROL_OPTION:
+ aoh = f->data;
+ /* Forward option Requests */
+ if (aoh && aoh->flag == AST_OPTION_FLAG_REQUEST) {
+ ast_channel_setoption(other, ntohs(aoh->option), aoh->data,
+ f->datalen - sizeof(struct ast_option_header), 0);
+ }
+ break;
+ }
+ } else if (f->frametype == AST_FRAME_DTMF_BEGIN) {
+ /* eat it */
+ } else if (f->frametype == AST_FRAME_DTMF) {
+ char *featurecode;
+ int sense;
+
+ hadfeatures = hasfeatures;
+ /* This cannot overrun because the longest feature is one shorter than our buffer */
+ if (who == chan) {
+ sense = FEATURE_SENSE_CHAN;
+ featurecode = chan_featurecode;
+ } else {
+ sense = FEATURE_SENSE_PEER;
+ featurecode = peer_featurecode;
+ }
+ /*! append the event to featurecode. we rely on the string being zero-filled, and
+ * not overflowing it.
+ * \todo XXX how do we guarantee the latter ?
+ */
+ featurecode[strlen(featurecode)] = f->subclass;
+ /* Get rid of the frame before we start doing "stuff" with the channels */
+ ast_frfree(f);
+ f = NULL;
+ config->feature_timer = backup_config.feature_timer;
+ res = ast_feature_interpret(chan, peer, config, featurecode, sense);
+ switch(res) {
+ case FEATURE_RETURN_PASSDIGITS:
+ ast_dtmf_stream(other, who, featurecode, 0);
+ /* Fall through */
+ case FEATURE_RETURN_SUCCESS:
+ memset(featurecode, 0, sizeof(chan_featurecode));
+ break;
+ }
+ if (res >= FEATURE_RETURN_PASSDIGITS) {
+ res = 0;
+ } else
+ break;
+ hasfeatures = !ast_strlen_zero(chan_featurecode) || !ast_strlen_zero(peer_featurecode);
+ if (hadfeatures && !hasfeatures) {
+ /* Restore backup */
+ memcpy(config, &backup_config, sizeof(struct ast_bridge_config));
+ memset(&backup_config, 0, sizeof(struct ast_bridge_config));
+ } else if (hasfeatures) {
+ if (!hadfeatures) {
+ /* Backup configuration */
+ memcpy(&backup_config, config, sizeof(struct ast_bridge_config));
+ /* Setup temporary config options */
+ config->play_warning = 0;
+ ast_clear_flag(&(config->features_caller), AST_FEATURE_PLAY_WARNING);
+ ast_clear_flag(&(config->features_callee), AST_FEATURE_PLAY_WARNING);
+ config->warning_freq = 0;
+ config->warning_sound = NULL;
+ config->end_sound = NULL;
+ config->start_sound = NULL;
+ config->firstpass = 0;
+ }
+ config->start_time = ast_tvnow();
+ config->feature_timer = featuredigittimeout;
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Set time limit to %ld\n", config->feature_timer);
+ }
+ }
+ if (f)
+ ast_frfree(f);
+
+ }
+ before_you_go:
+ if (config->end_bridge_callback) {
+ config->end_bridge_callback(config->end_bridge_callback_data);
+ }
+
+ autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
+ ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
+ if (!ast_test_flag(&(config->features_caller),AST_FEATURE_NO_H_EXTEN) && ast_exists_extension(chan, chan->context, "h", 1, chan->cid.cid_num)) {
+ struct ast_cdr *swapper = NULL;
+ char savelastapp[AST_MAX_EXTENSION];
+ char savelastdata[AST_MAX_EXTENSION];
+ char save_exten[AST_MAX_EXTENSION];
+ int save_prio;
+
+ if (bridge_cdr && ast_opt_end_cdr_before_h_exten) {
+ ast_cdr_end(bridge_cdr);
+ }
+ /* swap the bridge cdr and the chan cdr for a moment, and let the endbridge
+ dialplan code operate on it */
+ ast_channel_lock(chan);
+ if (bridge_cdr) {
+ swapper = chan->cdr;
+ ast_copy_string(savelastapp, bridge_cdr->lastapp, sizeof(bridge_cdr->lastapp));
+ ast_copy_string(savelastdata, bridge_cdr->lastdata, sizeof(bridge_cdr->lastdata));
+ chan->cdr = bridge_cdr;
+ }
+ ast_copy_string(save_exten, chan->exten, sizeof(save_exten));
+ ast_copy_string(chan->exten, "h", sizeof(chan->exten));
+ save_prio = chan->priority;
+ chan->priority = 1;
+ ast_channel_unlock(chan);
+ while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
+ if (ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
+ /* Something bad happened, or a hangup has been requested. */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Spawn h extension (%s,%s,%d) exited non-zero on '%s'\n", chan->context, chan->exten, chan->priority, chan->name);
+ if (option_verbose > 1)
+ ast_verbose( VERBOSE_PREFIX_2 "Spawn h extension (%s, %s, %d) exited non-zero on '%s'\n", chan->context, chan->exten, chan->priority, chan->name);
+ break;
+ }
+ chan->priority++;
+ }
+ /* swap it back */
+ ast_channel_lock(chan);
+ ast_copy_string(chan->exten, save_exten, sizeof(chan->exten));
+ chan->priority = save_prio;
+ if (bridge_cdr)
+ chan->cdr = swapper;
+ ast_set_flag(chan, AST_FLAG_BRIDGE_HANGUP_RUN);
+ ast_channel_unlock(chan);
+ /* protect the lastapp/lastdata against the effects of the hangup/dialplan code */
+ if (bridge_cdr) {
+ ast_copy_string(bridge_cdr->lastapp, savelastapp, sizeof(bridge_cdr->lastapp));
+ ast_copy_string(bridge_cdr->lastdata, savelastdata, sizeof(bridge_cdr->lastdata));
+ }
+ }
+ ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
+
+ /* obey the NoCDR() wishes. -- move the DISABLED flag to the bridge CDR if it was set on the channel during the bridge... */
+ new_chan_cdr = pick_unlocked_cdr(chan->cdr); /* the proper chan cdr, if there are forked cdrs */
+ if (bridge_cdr && new_chan_cdr && ast_test_flag(new_chan_cdr, AST_CDR_FLAG_POST_DISABLED)) {
+ ast_set_flag(bridge_cdr, AST_CDR_FLAG_POST_DISABLED);
+ }
+
+ /* we can post the bridge CDR at this point */
+ if (bridge_cdr) {
+ ast_cdr_end(bridge_cdr);
+ ast_cdr_detach(bridge_cdr);
+ }
+
+ /* do a specialized reset on the beginning channel
+ CDR's, if they still exist, so as not to mess up
+ issues in future bridges;
+
+ Here are the rules of the game:
+ 1. The chan and peer channel pointers will not change
+ during the life of the bridge.
+ 2. But, in transfers, the channel names will change.
+ between the time the bridge is started, and the
+ time the channel ends.
+ Usually, when a channel changes names, it will
+ also change CDR pointers.
+ 3. Usually, only one of the two channels (chan or peer)
+ will change names.
+ 4. Usually, if a channel changes names during a bridge,
+ it is because of a transfer. Usually, in these situations,
+ it is normal to see 2 bridges running simultaneously, and
+ it is not unusual to see the two channels that change
+ swapped between bridges.
+ 5. After a bridge occurs, we have 2 or 3 channels' CDRs
+ to attend to; if the chan or peer changed names,
+ we have the before and after attached CDR's.
+ */
+
+ if (new_chan_cdr) {
+ struct ast_channel *chan_ptr = NULL;
+
+ if (strcasecmp(orig_channame, chan->name) != 0) {
+ /* old channel */
+ chan_ptr = ast_get_channel_by_name_locked(orig_channame);
+ if (chan_ptr) {
+ if (!ast_bridged_channel(chan_ptr)) {
+ struct ast_cdr *cur;
+ for (cur = chan_ptr->cdr; cur; cur = cur->next) {
+ if (cur == chan_cdr) {
+ break;
+ }
+ }
+ if (cur)
+ ast_cdr_specialized_reset(chan_cdr,0);
+ }
+ ast_channel_unlock(chan_ptr);
+ }
+ /* new channel */
+ ast_cdr_specialized_reset(new_chan_cdr,0);
+ } else {
+ ast_cdr_specialized_reset(chan_cdr,0); /* nothing changed, reset the chan_cdr */
+ }
+ }
+
+ {
+ struct ast_channel *chan_ptr = NULL;
+ new_peer_cdr = pick_unlocked_cdr(peer->cdr); /* the proper chan cdr, if there are forked cdrs */
+ if (new_chan_cdr && ast_test_flag(new_chan_cdr, AST_CDR_FLAG_POST_DISABLED) && new_peer_cdr && !ast_test_flag(new_peer_cdr, AST_CDR_FLAG_POST_DISABLED))
+ ast_set_flag(new_peer_cdr, AST_CDR_FLAG_POST_DISABLED); /* DISABLED is viral-- it will propagate across a bridge */
+ if (strcasecmp(orig_peername, peer->name) != 0) {
+ /* old channel */
+ chan_ptr = ast_get_channel_by_name_locked(orig_peername);
+ if (chan_ptr) {
+ if (!ast_bridged_channel(chan_ptr)) {
+ struct ast_cdr *cur;
+ for (cur = chan_ptr->cdr; cur; cur = cur->next) {
+ if (cur == peer_cdr) {
+ break;
+ }
+ }
+ if (cur)
+ ast_cdr_specialized_reset(peer_cdr,0);
+ }
+ ast_channel_unlock(chan_ptr);
+ }
+ /* new channel */
+ ast_cdr_specialized_reset(new_peer_cdr,0);
+ } else {
+ ast_cdr_specialized_reset(peer_cdr,0); /* nothing changed, reset the peer_cdr */
+ }
+ }
+ return res;
+}
+
+static void post_manager_event(const char *s, char *parkingexten, struct ast_channel *chan)
+{
+ manager_event(EVENT_FLAG_CALL, s,
+ "Exten: %s\r\n"
+ "Channel: %s\r\n"
+ "CallerID: %s\r\n"
+ "CallerIDName: %s\r\n\r\n",
+ parkingexten,
+ chan->name,
+ S_OR(chan->cid.cid_num, "<unknown>"),
+ S_OR(chan->cid.cid_name, "<unknown>")
+ );
+}
+
+/*! \brief Take care of parked calls and unpark them if needed */
+static void *do_parking_thread(void *ignore)
+{
+ fd_set rfds, efds; /* results from previous select, to be preserved across loops. */
+ FD_ZERO(&rfds);
+ FD_ZERO(&efds);
+
+ for (;;) {
+ struct parkeduser *pu, *pl, *pt = NULL;
+ int ms = -1; /* select timeout, uninitialized */
+ int max = -1; /* max fd, none there yet */
+ fd_set nrfds, nefds; /* args for the next select */
+ FD_ZERO(&nrfds);
+ FD_ZERO(&nefds);
+
+ ast_mutex_lock(&parking_lock);
+ pl = NULL;
+ pu = parkinglot;
+ /* navigate the list with prev-cur pointers to support removals */
+ while (pu) {
+ struct ast_channel *chan = pu->chan; /* shorthand */
+ int tms; /* timeout for this item */
+ int x; /* fd index in channel */
+ struct ast_context *con;
+
+ if (pu->notquiteyet) { /* Pretend this one isn't here yet */
+ pl = pu;
+ pu = pu->next;
+ continue;
+ }
+ tms = ast_tvdiff_ms(ast_tvnow(), pu->start);
+ if (tms > pu->parkingtime) {
+ ast_indicate(chan, AST_CONTROL_UNHOLD);
+ /* Get chan, exten from derived kludge */
+ if (pu->peername[0]) {
+ char *peername = ast_strdupa(pu->peername);
+ char *cp = strrchr(peername, '-');
+ if (cp)
+ *cp = 0;
+ con = ast_context_find(parking_con_dial);
+ if (!con) {
+ con = ast_context_create(NULL, parking_con_dial, registrar);
+ if (!con)
+ ast_log(LOG_ERROR, "Parking dial context '%s' does not exist and unable to create\n", parking_con_dial);
+ }
+ if (con) {
+ char returnexten[AST_MAX_EXTENSION];
+ struct ast_datastore *features_datastore;
+ struct ast_dial_features *dialfeatures = NULL;
+
+ ast_channel_lock(chan);
+
+ if ((features_datastore = ast_channel_datastore_find(chan, &dial_features_info, NULL)))
+ dialfeatures = features_datastore->data;
+
+ ast_channel_unlock(chan);
+
+ if (!strncmp(peername, "Parked/", 7)) {
+ peername += 7;
+ }
+
+ if (dialfeatures)
+ snprintf(returnexten, sizeof(returnexten), "%s|30|%s", peername, dialfeatures->options);
+ else /* Existing default */
+ snprintf(returnexten, sizeof(returnexten), "%s|30|t", peername);
+
+ ast_add_extension2(con, 1, peername, 1, NULL, NULL, "Dial", strdup(returnexten), ast_free, registrar);
+ }
+ set_c_e_p(chan, parking_con_dial, peername, 1);
+ } else {
+ /* They've been waiting too long, send them back to where they came. Theoretically they
+ should have their original extensions and such, but we copy to be on the safe side */
+ set_c_e_p(chan, pu->context, pu->exten, pu->priority);
+ }
+
+ post_manager_event("ParkedCallTimeOut", pu->parkingexten, chan);
+
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "Timeout for %s parked on %d. Returning to %s,%s,%d\n", chan->name, pu->parkingnum, chan->context, chan->exten, chan->priority);
+ /* Start up the PBX, or hang them up */
+ if (ast_pbx_start(chan)) {
+ ast_log(LOG_WARNING, "Unable to restart the PBX for user on '%s', hanging them up...\n", chan->name);
+ ast_hangup(chan);
+ }
+ /* And take them out of the parking lot */
+ if (pl)
+ pl->next = pu->next;
+ else
+ parkinglot = pu->next;
+ pt = pu;
+ pu = pu->next;
+ con = ast_context_find(parking_con);
+ if (con) {
+ if (ast_context_remove_extension2(con, pt->parkingexten, 1, NULL))
+ ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n");
+ else
+ notify_metermaids(pt->parkingexten, parking_con);
+ } else
+ ast_log(LOG_WARNING, "Whoa, no parking context?\n");
+ free(pt);
+ } else { /* still within parking time, process descriptors */
+ for (x = 0; x < AST_MAX_FDS; x++) {
+ struct ast_frame *f;
+
+ if (chan->fds[x] == -1 || (!FD_ISSET(chan->fds[x], &rfds) && !FD_ISSET(chan->fds[x], &efds)))
+ continue; /* nothing on this descriptor */
+
+ if (FD_ISSET(chan->fds[x], &efds))
+ ast_set_flag(chan, AST_FLAG_EXCEPTION);
+ else
+ ast_clear_flag(chan, AST_FLAG_EXCEPTION);
+ chan->fdno = x;
+
+ /* See if they need servicing */
+ f = ast_read(chan);
+ if (!f || (f->frametype == AST_FRAME_CONTROL && f->subclass == AST_CONTROL_HANGUP)) {
+ if (f)
+ ast_frfree(f);
+ post_manager_event("ParkedCallGiveUp", pu->parkingexten, chan);
+
+ /* There's a problem, hang them up*/
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "%s got tired of being parked\n", chan->name);
+ ast_hangup(chan);
+ /* And take them out of the parking lot */
+ if (pl)
+ pl->next = pu->next;
+ else
+ parkinglot = pu->next;
+ pt = pu;
+ pu = pu->next;
+ con = ast_context_find(parking_con);
+ if (con) {
+ if (ast_context_remove_extension2(con, pt->parkingexten, 1, NULL))
+ ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n");
+ else {
+ notify_metermaids(pt->parkingexten, parking_con);
+ }
+ } else
+ ast_log(LOG_WARNING, "Whoa, no parking context?\n");
+ free(pt);
+ break;
+ } else {
+ /*! \todo XXX Maybe we could do something with packets, like dial "0" for operator or something XXX */
+ ast_frfree(f);
+ if (pu->moh_trys < 3 && !chan->generatordata) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "MOH on parked call stopped by outside source. Restarting.\n");
+ ast_indicate_data(pu->chan, AST_CONTROL_HOLD,
+ S_OR(parkmohclass, NULL),
+ !ast_strlen_zero(parkmohclass) ? strlen(parkmohclass) + 1 : 0);
+ pu->moh_trys++;
+ }
+ goto std; /*! \todo XXX Ick: jumping into an else statement??? XXX */
+ }
+
+ } /* end for */
+ if (x >= AST_MAX_FDS) {
+std: for (x=0; x<AST_MAX_FDS; x++) { /* mark fds for next round */
+ if (chan->fds[x] > -1) {
+ FD_SET(chan->fds[x], &nrfds);
+ FD_SET(chan->fds[x], &nefds);
+ if (chan->fds[x] > max)
+ max = chan->fds[x];
+ }
+ }
+ /* Keep track of our shortest wait */
+ if (tms < ms || ms < 0)
+ ms = tms;
+ pl = pu;
+ pu = pu->next;
+ }
+ }
+ } /* end while */
+ ast_mutex_unlock(&parking_lock);
+ rfds = nrfds;
+ efds = nefds;
+ {
+ struct timeval tv = ast_samp2tv(ms, 1000);
+ /* Wait for something to happen */
+ ast_select(max + 1, &rfds, NULL, &efds, (ms > -1) ? &tv : NULL);
+ }
+ pthread_testcancel();
+ }
+ return NULL; /* Never reached */
+}
+
+/*! \brief Park a call */
+static int park_call_exec(struct ast_channel *chan, void *data)
+{
+ /* Cache the original channel name in case we get masqueraded in the middle
+ * of a park--it is still theoretically possible for a transfer to happen before
+ * we get here, but it is _really_ unlikely */
+ char orig_exten[AST_MAX_EXTENSION];
+ int orig_priority = chan->priority;
+
+ /* Data is unused at the moment but could contain a parking
+ lot context eventually */
+ int res = 0;
+ struct ast_module_user *u;
+
+ u = ast_module_user_add(chan);
+
+ ast_copy_string(orig_exten, chan->exten, sizeof(orig_exten));
+
+ /* Setup the exten/priority to be s/1 since we don't know
+ where this call should return */
+ strcpy(chan->exten, "s");
+ chan->priority = 1;
+ /* Answer if call is not up */
+ if (chan->_state != AST_STATE_UP)
+ res = ast_answer(chan);
+ /* Sleep to allow VoIP streams to settle down */
+ if (!res)
+ res = ast_safe_sleep(chan, 1000);
+ /* Park the call */
+ if (!res) {
+ res = masq_park_call_announce(chan, chan, 0, NULL);
+ /* Continue on in the dialplan */
+ if (res == 1) {
+ ast_copy_string(chan->exten, orig_exten, sizeof(chan->exten));
+ chan->priority = orig_priority;
+ res = 0;
+ } else if (!res) {
+ res = 1;
+ }
+ }
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+/*! \brief Pickup parked call */
+static int park_exec(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ struct ast_module_user *u;
+ struct ast_channel *peer=NULL;
+ struct parkeduser *pu, *pl=NULL;
+ struct ast_context *con;
+
+ int park;
+ struct ast_bridge_config config;
+
+ if (!data) {
+ ast_log(LOG_WARNING, "Parkedcall requires an argument (extension number)\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ park = atoi((char *)data);
+ ast_mutex_lock(&parking_lock);
+ pu = parkinglot;
+ while(pu) {
+ if (pu->parkingnum == park) {
+ if (pu->chan->pbx) { /* do not allow call to be picked up until the PBX thread is finished */
+ ast_mutex_unlock(&parking_lock);
+ ast_module_user_remove(u);
+ return -1;
+ }
+ if (pl)
+ pl->next = pu->next;
+ else
+ parkinglot = pu->next;
+ break;
+ }
+ pl = pu;
+ pu = pu->next;
+ }
+ ast_mutex_unlock(&parking_lock);
+ if (pu) {
+ peer = pu->chan;
+ con = ast_context_find(parking_con);
+ if (con) {
+ if (ast_context_remove_extension2(con, pu->parkingexten, 1, NULL))
+ ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n");
+ else
+ notify_metermaids(pu->parkingexten, parking_con);
+ } else
+ ast_log(LOG_WARNING, "Whoa, no parking context?\n");
+
+ manager_event(EVENT_FLAG_CALL, "UnParkedCall",
+ "Exten: %s\r\n"
+ "Channel: %s\r\n"
+ "From: %s\r\n"
+ "CallerID: %s\r\n"
+ "CallerIDName: %s\r\n",
+ pu->parkingexten, pu->chan->name, chan->name,
+ S_OR(pu->chan->cid.cid_num, "<unknown>"),
+ S_OR(pu->chan->cid.cid_name, "<unknown>")
+ );
+
+ free(pu);
+ }
+ /* JK02: it helps to answer the channel if not already up */
+ if (chan->_state != AST_STATE_UP)
+ ast_answer(chan);
+
+ if (peer) {
+ /* Play a courtesy to the source(s) configured to prefix the bridge connecting */
+
+ if (!ast_strlen_zero(courtesytone)) {
+ int error = 0;
+ ast_indicate(peer, AST_CONTROL_UNHOLD);
+ if (parkedplay == 0) {
+ error = ast_stream_and_wait(chan, courtesytone, chan->language, "");
+ } else if (parkedplay == 1) {
+ error = ast_stream_and_wait(peer, courtesytone, chan->language, "");
+ } else if (parkedplay == 2) {
+ if (!ast_streamfile(chan, courtesytone, chan->language) &&
+ !ast_streamfile(peer, courtesytone, chan->language)) {
+ /*! \todo XXX we would like to wait on both! */
+ res = ast_waitstream(chan, "");
+ if (res >= 0)
+ res = ast_waitstream(peer, "");
+ if (res < 0)
+ error = 1;
+ }
+ }
+ if (error) {
+ ast_log(LOG_WARNING, "Failed to play courtesy tone!\n");
+ ast_hangup(peer);
+ ast_module_user_remove(u);
+ return -1;
+ }
+ } else
+ ast_indicate(peer, AST_CONTROL_UNHOLD);
+
+ res = ast_channel_make_compatible(chan, peer);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Could not make channels %s and %s compatible for bridge\n", chan->name, peer->name);
+ ast_hangup(peer);
+ ast_module_user_remove(u);
+ return -1;
+ }
+ /* This runs sorta backwards, since we give the incoming channel control, as if it
+ were the person called. */
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Channel %s connected to parked call %d\n", chan->name, park);
+
+ pbx_builtin_setvar_helper(chan, "PARKEDCHANNEL", peer->name);
+ ast_cdr_setdestchan(chan->cdr, peer->name);
+ memset(&config, 0, sizeof(struct ast_bridge_config));
+
+ if ((parkedcalltransfers == AST_FEATURE_FLAG_BYCALLEE) || (parkedcalltransfers == AST_FEATURE_FLAG_BYBOTH)) {
+ ast_set_flag(&(config.features_callee), AST_FEATURE_REDIRECT);
+ }
+ if ((parkedcalltransfers == AST_FEATURE_FLAG_BYCALLER) || (parkedcalltransfers == AST_FEATURE_FLAG_BYBOTH)) {
+ ast_set_flag(&(config.features_caller), AST_FEATURE_REDIRECT);
+ }
+ res = ast_bridge_call(chan, peer, &config);
+
+ pbx_builtin_setvar_helper(chan, "PARKEDCHANNEL", peer->name);
+ ast_cdr_setdestchan(chan->cdr, peer->name);
+
+ /* Simulate the PBX hanging up */
+ ast_hangup(peer);
+ ast_module_user_remove(u);
+ return res;
+ } else {
+ /*! \todo XXX Play a message XXX */
+ if (ast_stream_and_wait(chan, "pbx-invalidpark", chan->language, ""))
+ ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", "pbx-invalidpark", chan->name);
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Channel %s tried to talk to nonexistent parked call %d\n", chan->name, park);
+ res = -1;
+ }
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int handle_showfeatures(int fd, int argc, char *argv[])
+{
+ int i;
+ struct ast_call_feature *feature;
+ char format[] = "%-25s %-7s %-7s\n";
+
+ ast_cli(fd, format, "Builtin Feature", "Default", "Current");
+ ast_cli(fd, format, "---------------", "-------", "-------");
+
+ ast_cli(fd, format, "Pickup", "*8", ast_pickup_ext()); /* default hardcoded above, so we'll hardcode it here */
+
+ ast_rwlock_rdlock(&features_lock);
+ for (i = 0; i < FEATURES_COUNT; i++)
+ ast_cli(fd, format, builtin_features[i].fname, builtin_features[i].default_exten, builtin_features[i].exten);
+ ast_rwlock_unlock(&features_lock);
+
+ ast_cli(fd, "\n");
+ ast_cli(fd, format, "Dynamic Feature", "Default", "Current");
+ ast_cli(fd, format, "---------------", "-------", "-------");
+ if (AST_RWLIST_EMPTY(&feature_list)) {
+ ast_cli(fd, "(none)\n");
+ } else {
+ AST_RWLIST_RDLOCK(&feature_list);
+ AST_RWLIST_TRAVERSE(&feature_list, feature, feature_entry) {
+ ast_cli(fd, format, feature->sname, "no def", feature->exten);
+ }
+ AST_RWLIST_UNLOCK(&feature_list);
+ }
+ ast_cli(fd, "\nCall parking\n");
+ ast_cli(fd, "------------\n");
+ ast_cli(fd,"%-20s: %s\n", "Parking extension", parking_ext);
+ ast_cli(fd,"%-20s: %s\n", "Parking context", parking_con);
+ ast_cli(fd,"%-20s: %d-%d\n", "Parked call extensions", parking_start, parking_stop);
+ ast_cli(fd,"\n");
+
+ return RESULT_SUCCESS;
+}
+
+static char showfeatures_help[] =
+"Usage: feature list\n"
+" Lists currently configured features.\n";
+
+static int handle_parkedcalls(int fd, int argc, char *argv[])
+{
+ struct parkeduser *cur;
+ int numparked = 0;
+
+ ast_cli(fd, "%4s %25s (%-15s %-12s %-4s) %-6s \n", "Num", "Channel"
+ , "Context", "Extension", "Pri", "Timeout");
+
+ ast_mutex_lock(&parking_lock);
+
+ for (cur = parkinglot; cur; cur = cur->next) {
+ ast_cli(fd, "%-10.10s %25s (%-15s %-12s %-4d) %6lds\n"
+ ,cur->parkingexten, cur->chan->name, cur->context, cur->exten
+ ,cur->priority, cur->start.tv_sec + (cur->parkingtime/1000) - time(NULL));
+
+ numparked++;
+ }
+ ast_mutex_unlock(&parking_lock);
+ ast_cli(fd, "%d parked call%s.\n", numparked, (numparked != 1) ? "s" : "");
+
+
+ return RESULT_SUCCESS;
+}
+
+static char showparked_help[] =
+"Usage: show parkedcalls\n"
+" Lists currently parked calls.\n";
+
+static struct ast_cli_entry cli_show_features_deprecated = {
+ { "show", "features", NULL },
+ handle_showfeatures, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_features[] = {
+ { { "feature", "show", NULL },
+ handle_showfeatures, "Lists configured features",
+ showfeatures_help, NULL, &cli_show_features_deprecated },
+
+ { { "show", "parkedcalls", NULL },
+ handle_parkedcalls, "Lists parked calls",
+ showparked_help },
+};
+
+/*! \brief Dump lot status */
+static int manager_parking_status( struct mansession *s, const struct message *m)
+{
+ struct parkeduser *cur;
+ const char *id = astman_get_header(m, "ActionID");
+ char idText[256] = "";
+
+ if (!ast_strlen_zero(id))
+ snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
+
+ astman_send_ack(s, m, "Parked calls will follow");
+
+ ast_mutex_lock(&parking_lock);
+
+ for (cur = parkinglot; cur; cur = cur->next) {
+ astman_append(s, "Event: ParkedCall\r\n"
+ "Exten: %d\r\n"
+ "Channel: %s\r\n"
+ "From: %s\r\n"
+ "Timeout: %ld\r\n"
+ "CallerID: %s\r\n"
+ "CallerIDName: %s\r\n"
+ "%s"
+ "\r\n",
+ cur->parkingnum, cur->chan->name, cur->peername,
+ (long) cur->start.tv_sec + (long) (cur->parkingtime / 1000) - (long) time(NULL),
+ S_OR(cur->chan->cid.cid_num, ""), /* XXX in other places it is <unknown> */
+ S_OR(cur->chan->cid.cid_name, ""),
+ idText);
+ }
+
+ astman_append(s,
+ "Event: ParkedCallsComplete\r\n"
+ "%s"
+ "\r\n",idText);
+
+ ast_mutex_unlock(&parking_lock);
+
+ return RESULT_SUCCESS;
+}
+
+static char mandescr_park[] =
+"Description: Park a channel.\n"
+"Variables: (Names marked with * are required)\n"
+" *Channel: Channel name to park\n"
+" *Channel2: Channel to announce park info to (and return to if timeout)\n"
+" Timeout: Number of milliseconds to wait before callback.\n";
+
+static int manager_park(struct mansession *s, const struct message *m)
+{
+ const char *channel = astman_get_header(m, "Channel");
+ const char *channel2 = astman_get_header(m, "Channel2");
+ const char *timeout = astman_get_header(m, "Timeout");
+ char buf[BUFSIZ];
+ int to = 0;
+ int res = 0;
+ int parkExt = 0;
+ struct ast_channel *ch1, *ch2;
+
+ if (ast_strlen_zero(channel)) {
+ astman_send_error(s, m, "Channel not specified");
+ return 0;
+ }
+
+ if (ast_strlen_zero(channel2)) {
+ astman_send_error(s, m, "Channel2 not specified");
+ return 0;
+ }
+
+ ch1 = ast_get_channel_by_name_locked(channel);
+ if (!ch1) {
+ snprintf(buf, sizeof(buf), "Channel does not exist: %s", channel);
+ astman_send_error(s, m, buf);
+ return 0;
+ }
+
+ ch2 = ast_get_channel_by_name_locked(channel2);
+ if (!ch2) {
+ snprintf(buf, sizeof(buf), "Channel does not exist: %s", channel2);
+ astman_send_error(s, m, buf);
+ ast_channel_unlock(ch1);
+ return 0;
+ }
+
+ if (!ast_strlen_zero(timeout)) {
+ sscanf(timeout, "%d", &to);
+ }
+
+ res = ast_masq_park_call(ch1, ch2, to, &parkExt);
+ if (!res) {
+ ast_softhangup(ch2, AST_SOFTHANGUP_EXPLICIT);
+ astman_send_ack(s, m, "Park successful");
+ } else {
+ astman_send_error(s, m, "Park failure");
+ }
+
+ ast_channel_unlock(ch1);
+ ast_channel_unlock(ch2);
+
+ return 0;
+}
+
+
+int ast_pickup_call(struct ast_channel *chan)
+{
+ struct ast_channel *cur = NULL;
+ int res = -1;
+
+ while ( (cur = ast_channel_walk_locked(cur)) != NULL) {
+ if (!cur->pbx &&
+ (cur != chan) &&
+ (chan->pickupgroup & cur->callgroup) &&
+ ((cur->_state == AST_STATE_RINGING) ||
+ (cur->_state == AST_STATE_RING))) {
+ break;
+ }
+ ast_channel_unlock(cur);
+ }
+ if (cur) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Call pickup on chan '%s' by '%s'\n",cur->name, chan->name);
+ res = ast_answer(chan);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to answer '%s'\n", chan->name);
+ res = ast_queue_control(chan, AST_CONTROL_ANSWER);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to queue answer on '%s'\n", chan->name);
+ res = ast_channel_masquerade(cur, chan);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to masquerade '%s' into '%s'\n", chan->name, cur->name); /* Done */
+ ast_channel_unlock(cur);
+ } else {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "No call pickup possible...\n");
+ }
+ return res;
+}
+
+/*! \brief Add parking hints for all defined parking lots */
+static void park_add_hints(char *context, int start, int stop)
+{
+ int numext;
+ char device[AST_MAX_EXTENSION];
+ char exten[10];
+
+ for (numext = start; numext <= stop; numext++) {
+ snprintf(exten, sizeof(exten), "%d", numext);
+ snprintf(device, sizeof(device), "park:%s@%s", exten, context);
+ ast_add_extension(context, 1, exten, PRIORITY_HINT, NULL, NULL, device, NULL, NULL, registrar);
+ }
+}
+
+
+static int load_config(void)
+{
+ int start = 0, end = 0;
+ int res;
+ struct ast_context *con = NULL;
+ struct ast_config *cfg = NULL;
+ struct ast_variable *var = NULL;
+ char old_parking_ext[AST_MAX_EXTENSION];
+ char old_parking_con[AST_MAX_EXTENSION] = "";
+
+ if (!ast_strlen_zero(parking_con)) {
+ strcpy(old_parking_ext, parking_ext);
+ strcpy(old_parking_con, parking_con);
+ }
+
+ /* Reset to defaults */
+ strcpy(parking_con, "parkedcalls");
+ strcpy(parking_con_dial, "park-dial");
+ strcpy(parking_ext, "700");
+ strcpy(pickup_ext, "*8");
+ strcpy(parkmohclass, "default");
+ courtesytone[0] = '\0';
+ strcpy(xfersound, "beep");
+ strcpy(xferfailsound, "pbx-invalid");
+ parking_start = 701;
+ parking_stop = 750;
+ parkfindnext = 0;
+ adsipark = 0;
+ parkaddhints = 0;
+ parkedcalltransfers = AST_FEATURE_FLAG_BYBOTH;
+
+ transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT;
+ featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT;
+ atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER;
+
+ cfg = ast_config_load("features.conf");
+ if (!cfg) {
+ ast_log(LOG_WARNING,"Could not load features.conf\n");
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ for (var = ast_variable_browse(cfg, "general"); var; var = var->next) {
+ if (!strcasecmp(var->name, "parkext")) {
+ ast_copy_string(parking_ext, var->value, sizeof(parking_ext));
+ } else if (!strcasecmp(var->name, "context")) {
+ ast_copy_string(parking_con, var->value, sizeof(parking_con));
+ } else if (!strcasecmp(var->name, "parkingtime")) {
+ if ((sscanf(var->value, "%d", &parkingtime) != 1) || (parkingtime < 1)) {
+ ast_log(LOG_WARNING, "%s is not a valid parkingtime\n", var->value);
+ parkingtime = DEFAULT_PARK_TIME;
+ } else
+ parkingtime = parkingtime * 1000;
+ } else if (!strcasecmp(var->name, "parkpos")) {
+ if (sscanf(var->value, "%d-%d", &start, &end) != 2) {
+ ast_log(LOG_WARNING, "Format for parking positions is a-b, where a and b are numbers at line %d of features.conf\n", var->lineno);
+ } else {
+ parking_start = start;
+ parking_stop = end;
+ }
+ } else if (!strcasecmp(var->name, "findslot")) {
+ parkfindnext = (!strcasecmp(var->value, "next"));
+ } else if (!strcasecmp(var->name, "parkinghints")) {
+ parkaddhints = ast_true(var->value);
+ } else if (!strcasecmp(var->name, "parkedcalltransfers")) {
+ if (!strcasecmp(var->value, "no"))
+ parkedcalltransfers = 0;
+ else if (!strcasecmp(var->value, "caller"))
+ parkedcalltransfers = AST_FEATURE_FLAG_BYCALLER;
+ else if (!strcasecmp(var->value, "callee"))
+ parkedcalltransfers = AST_FEATURE_FLAG_BYCALLEE;
+ else if (!strcasecmp(var->value, "both"))
+ parkedcalltransfers = AST_FEATURE_FLAG_BYBOTH;
+ } else if (!strcasecmp(var->name, "adsipark")) {
+ adsipark = ast_true(var->value);
+ } else if (!strcasecmp(var->name, "transferdigittimeout")) {
+ if ((sscanf(var->value, "%d", &transferdigittimeout) != 1) || (transferdigittimeout < 1)) {
+ ast_log(LOG_WARNING, "%s is not a valid transferdigittimeout\n", var->value);
+ transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT;
+ } else
+ transferdigittimeout = transferdigittimeout * 1000;
+ } else if (!strcasecmp(var->name, "featuredigittimeout")) {
+ if ((sscanf(var->value, "%d", &featuredigittimeout) != 1) || (featuredigittimeout < 1)) {
+ ast_log(LOG_WARNING, "%s is not a valid featuredigittimeout\n", var->value);
+ featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT;
+ }
+ } else if (!strcasecmp(var->name, "atxfernoanswertimeout")) {
+ if ((sscanf(var->value, "%d", &atxfernoanswertimeout) != 1) || (atxfernoanswertimeout < 1)) {
+ ast_log(LOG_WARNING, "%s is not a valid atxfernoanswertimeout\n", var->value);
+ atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER;
+ } else
+ atxfernoanswertimeout = atxfernoanswertimeout * 1000;
+ } else if (!strcasecmp(var->name, "courtesytone")) {
+ ast_copy_string(courtesytone, var->value, sizeof(courtesytone));
+ } else if (!strcasecmp(var->name, "parkedplay")) {
+ if (!strcasecmp(var->value, "both"))
+ parkedplay = 2;
+ else if (!strcasecmp(var->value, "parked"))
+ parkedplay = 1;
+ else
+ parkedplay = 0;
+ } else if (!strcasecmp(var->name, "xfersound")) {
+ ast_copy_string(xfersound, var->value, sizeof(xfersound));
+ } else if (!strcasecmp(var->name, "xferfailsound")) {
+ ast_copy_string(xferfailsound, var->value, sizeof(xferfailsound));
+ } else if (!strcasecmp(var->name, "pickupexten")) {
+ ast_copy_string(pickup_ext, var->value, sizeof(pickup_ext));
+ } else if (!strcasecmp(var->name, "parkedmusicclass")) {
+ ast_copy_string(parkmohclass, var->value, sizeof(parkmohclass));
+ }
+ }
+
+ unmap_features();
+ for (var = ast_variable_browse(cfg, "featuremap"); var; var = var->next) {
+ if (remap_feature(var->name, var->value))
+ ast_log(LOG_NOTICE, "Unknown feature '%s'\n", var->name);
+ }
+
+ /* Map a key combination to an application*/
+ ast_unregister_features();
+ for (var = ast_variable_browse(cfg, "applicationmap"); var; var = var->next) {
+ char *tmp_val = ast_strdupa(var->value);
+ char *exten, *activateon, *activatedby, *app, *app_args, *moh_class;
+ struct ast_call_feature *feature;
+
+ /* strsep() sets the argument to NULL if match not found, and it
+ * is safe to use it with a NULL argument, so we don't check
+ * between calls.
+ */
+ exten = strsep(&tmp_val,",");
+ activatedby = strsep(&tmp_val,",");
+ app = strsep(&tmp_val,",");
+ app_args = strsep(&tmp_val,",");
+ moh_class = strsep(&tmp_val,",");
+
+ activateon = strsep(&activatedby, "/");
+
+ /*! \todo XXX var_name or app_args ? */
+ if (ast_strlen_zero(app) || ast_strlen_zero(exten) || ast_strlen_zero(activateon) || ast_strlen_zero(var->name)) {
+ ast_log(LOG_NOTICE, "Please check the feature Mapping Syntax, either extension, name, or app aren't provided %s %s %s %s\n",
+ app, exten, activateon, var->name);
+ continue;
+ }
+
+ AST_RWLIST_RDLOCK(&feature_list);
+ if ((feature = find_dynamic_feature(var->name))) {
+ AST_RWLIST_UNLOCK(&feature_list);
+ ast_log(LOG_WARNING, "Dynamic Feature '%s' specified more than once!\n", var->name);
+ continue;
+ }
+ AST_RWLIST_UNLOCK(&feature_list);
+
+ if (!(feature = ast_calloc(1, sizeof(*feature))))
+ continue;
+
+ ast_copy_string(feature->sname, var->name, FEATURE_SNAME_LEN);
+ ast_copy_string(feature->app, app, FEATURE_APP_LEN);
+ ast_copy_string(feature->exten, exten, FEATURE_EXTEN_LEN);
+
+ if (app_args)
+ ast_copy_string(feature->app_args, app_args, FEATURE_APP_ARGS_LEN);
+
+ if (moh_class)
+ ast_copy_string(feature->moh_class, moh_class, FEATURE_MOH_LEN);
+
+ ast_copy_string(feature->exten, exten, sizeof(feature->exten));
+ feature->operation = feature_exec_app;
+ ast_set_flag(feature, AST_FEATURE_FLAG_NEEDSDTMF);
+
+ /* Allow caller and calle to be specified for backwards compatability */
+ if (!strcasecmp(activateon, "self") || !strcasecmp(activateon, "caller"))
+ ast_set_flag(feature, AST_FEATURE_FLAG_ONSELF);
+ else if (!strcasecmp(activateon, "peer") || !strcasecmp(activateon, "callee"))
+ ast_set_flag(feature, AST_FEATURE_FLAG_ONPEER);
+ else {
+ ast_log(LOG_NOTICE, "Invalid 'ActivateOn' specification for feature '%s',"
+ " must be 'self', or 'peer'\n", var->name);
+ continue;
+ }
+
+ if (ast_strlen_zero(activatedby))
+ ast_set_flag(feature, AST_FEATURE_FLAG_BYBOTH);
+ else if (!strcasecmp(activatedby, "caller"))
+ ast_set_flag(feature, AST_FEATURE_FLAG_BYCALLER);
+ else if (!strcasecmp(activatedby, "callee"))
+ ast_set_flag(feature, AST_FEATURE_FLAG_BYCALLEE);
+ else if (!strcasecmp(activatedby, "both"))
+ ast_set_flag(feature, AST_FEATURE_FLAG_BYBOTH);
+ else {
+ ast_log(LOG_NOTICE, "Invalid 'ActivatedBy' specification for feature '%s',"
+ " must be 'caller', or 'callee', or 'both'\n", var->name);
+ continue;
+ }
+
+ ast_register_feature(feature);
+
+ if (option_verbose >= 1)
+ ast_verbose(VERBOSE_PREFIX_2 "Mapping Feature '%s' to app '%s(%s)' with code '%s'\n", var->name, app, app_args, exten);
+ }
+ ast_config_destroy(cfg);
+
+ /* Remove the old parking extension */
+ if (!ast_strlen_zero(old_parking_con) && (con = ast_context_find(old_parking_con))) {
+ if(ast_context_remove_extension2(con, old_parking_ext, 1, registrar))
+ notify_metermaids(old_parking_ext, old_parking_con);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Removed old parking extension %s@%s\n", old_parking_ext, old_parking_con);
+ }
+
+ if (!(con = ast_context_find(parking_con)) && !(con = ast_context_create(NULL, parking_con, registrar))) {
+ ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n", parking_con);
+ return -1;
+ }
+ res = ast_add_extension2(con, 1, ast_parking_ext(), 1, NULL, NULL, parkcall, NULL, NULL, registrar);
+ if (parkaddhints)
+ park_add_hints(parking_con, parking_start, parking_stop);
+ if (!res)
+ notify_metermaids(ast_parking_ext(), parking_con);
+ return res;
+
+}
+
+static int reload(void)
+{
+ return load_config();
+}
+
+static int load_module(void)
+{
+ int res;
+
+ memset(parking_ext, 0, sizeof(parking_ext));
+ memset(parking_con, 0, sizeof(parking_con));
+
+ if ((res = load_config()))
+ return res;
+ ast_cli_register_multiple(cli_features, sizeof(cli_features) / sizeof(struct ast_cli_entry));
+ ast_pthread_create(&parking_thread, NULL, do_parking_thread, NULL);
+ res = ast_register_application(parkedcall, park_exec, synopsis, descrip);
+ if (!res)
+ res = ast_register_application(parkcall, park_call_exec, synopsis2, descrip2);
+ if (!res) {
+ ast_manager_register("ParkedCalls", 0, manager_parking_status, "List parked calls" );
+ ast_manager_register2("Park", EVENT_FLAG_CALL, manager_park,
+ "Park a channel", mandescr_park);
+ }
+
+ res |= ast_devstate_prov_add("Park", metermaidstate);
+
+ return res;
+}
+
+
+static int unload_module(void)
+{
+ ast_module_user_hangup_all();
+
+ ast_manager_unregister("ParkedCalls");
+ ast_manager_unregister("Park");
+ ast_cli_unregister_multiple(cli_features, sizeof(cli_features) / sizeof(struct ast_cli_entry));
+ ast_unregister_application(parkcall);
+ ast_devstate_prov_del("Park");
+ return ast_unregister_application(parkedcall);
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Call Features Resource",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
diff --git a/1.4.23-rc4/res/res_indications.c b/1.4.23-rc4/res/res_indications.c
new file mode 100644
index 000000000..9bff10b76
--- /dev/null
+++ b/1.4.23-rc4/res/res_indications.c
@@ -0,0 +1,406 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2002, Pauline Middelink
+ *
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file res_indications.c
+ *
+ * \brief Load the indications
+ *
+ * \author Pauline Middelink <middelink@polyware.nl>
+ *
+ * Load the country specific dialtones into the asterisk PBX.
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/cli.h"
+#include "asterisk/logger.h"
+#include "asterisk/config.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/indications.h"
+#include "asterisk/utils.h"
+
+/* Globals */
+static const char config[] = "indications.conf";
+
+/*
+ * Help for commands provided by this module ...
+ */
+static char help_add_indication[] =
+"Usage: indication add <country> <indication> \"<tonelist>\"\n"
+" Add the given indication to the country.\n";
+
+static char help_remove_indication[] =
+"Usage: indication remove <country> <indication>\n"
+" Remove the given indication from the country.\n";
+
+static char help_show_indications[] =
+"Usage: indication show [<country> ...]\n"
+" Display either a condensed for of all country/indications, or the\n"
+" indications for the specified countries.\n";
+
+char *playtones_desc=
+"PlayTones(arg): Plays a tone list. Execution will continue with the next step immediately,\n"
+"while the tones continue to play.\n"
+"Arg is either the tone name defined in the indications.conf configuration file, or a directly\n"
+"specified list of frequencies and durations.\n"
+"See the sample indications.conf for a description of the specification of a tonelist.\n\n"
+"Use the StopPlayTones application to stop the tones playing. \n";
+
+/*
+ * Implementation of functions provided by this module
+ */
+
+/*
+ * ADD INDICATION command stuff
+ */
+static int handle_add_indication(int fd, int argc, char *argv[])
+{
+ struct tone_zone *tz;
+ int created_country = 0;
+ if (argc != 5) return RESULT_SHOWUSAGE;
+
+ tz = ast_get_indication_zone(argv[2]);
+ if (!tz) {
+ /* country does not exist, create it */
+ ast_log(LOG_NOTICE, "Country '%s' does not exist, creating it.\n",argv[2]);
+
+ if (!(tz = ast_calloc(1, sizeof(*tz)))) {
+ return -1;
+ }
+ ast_copy_string(tz->country,argv[2],sizeof(tz->country));
+ if (ast_register_indication_country(tz)) {
+ ast_log(LOG_WARNING, "Unable to register new country\n");
+ free(tz);
+ return -1;
+ }
+ created_country = 1;
+ }
+ if (ast_register_indication(tz,argv[3],argv[4])) {
+ ast_log(LOG_WARNING, "Unable to register indication %s/%s\n",argv[2],argv[3]);
+ if (created_country)
+ ast_unregister_indication_country(argv[2]);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * REMOVE INDICATION command stuff
+ */
+static int handle_remove_indication(int fd, int argc, char *argv[])
+{
+ struct tone_zone *tz;
+ if (argc != 3 && argc != 4) return RESULT_SHOWUSAGE;
+
+ if (argc == 3) {
+ /* remove entiry country */
+ if (ast_unregister_indication_country(argv[2])) {
+ ast_log(LOG_WARNING, "Unable to unregister indication country %s\n",argv[2]);
+ return -1;
+ }
+ return 0;
+ }
+
+ tz = ast_get_indication_zone(argv[2]);
+ if (!tz) {
+ ast_log(LOG_WARNING, "Unable to unregister indication %s/%s, country does not exists\n",argv[2],argv[3]);
+ return -1;
+ }
+ if (ast_unregister_indication(tz,argv[3])) {
+ ast_log(LOG_WARNING, "Unable to unregister indication %s/%s\n",argv[2],argv[3]);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * SHOW INDICATIONS command stuff
+ */
+static int handle_show_indications(int fd, int argc, char *argv[])
+{
+ struct tone_zone *tz = NULL;
+ char buf[256];
+ int found_country = 0;
+
+ if (argc == 2) {
+ /* no arguments, show a list of countries */
+ ast_cli(fd,"Country Alias Description\n"
+ "===========================\n");
+ while ( (tz = ast_walk_indications(tz) ) )
+ ast_cli(fd,"%-7.7s %-7.7s %s\n", tz->country, tz->alias, tz->description);
+ return 0;
+ }
+ /* there was a request for specific country(ies), lets humor them */
+ while ( (tz = ast_walk_indications(tz) ) ) {
+ int i,j;
+ for (i=2; i<argc; i++) {
+ if (strcasecmp(tz->country,argv[i])==0 &&
+ !tz->alias[0]) {
+ struct tone_zone_sound* ts;
+ if (!found_country) {
+ found_country = 1;
+ ast_cli(fd,"Country Indication PlayList\n"
+ "=====================================\n");
+ }
+ j = snprintf(buf,sizeof(buf),"%-7.7s %-15.15s ",tz->country,"<ringcadence>");
+ for (i=0; i<tz->nrringcadence; i++) {
+ j += snprintf(buf+j,sizeof(buf)-j,"%d,",tz->ringcadence[i]);
+ }
+ if (tz->nrringcadence)
+ j--;
+ ast_copy_string(buf+j,"\n",sizeof(buf)-j);
+ ast_cli(fd, "%s", buf);
+ for (ts=tz->tones; ts; ts=ts->next)
+ ast_cli(fd,"%-7.7s %-15.15s %s\n",tz->country,ts->name,ts->data);
+ break;
+ }
+ }
+ }
+ if (!found_country)
+ ast_cli(fd,"No countries matched your criteria.\n");
+ return -1;
+}
+
+/*
+ * Playtones command stuff
+ */
+static int handle_playtones(struct ast_channel *chan, void *data)
+{
+ struct tone_zone_sound *ts;
+ int res;
+
+ if (!data || !((char*)data)[0]) {
+ ast_log(LOG_NOTICE,"Nothing to play\n");
+ return -1;
+ }
+ ts = ast_get_indication_tone(chan->zone, (const char*)data);
+ if (ts && ts->data[0])
+ res = ast_playtones_start(chan, 0, ts->data, 0);
+ else
+ res = ast_playtones_start(chan, 0, (const char*)data, 0);
+ if (res)
+ ast_log(LOG_NOTICE,"Unable to start playtones\n");
+ return res;
+}
+
+/*
+ * StopPlaylist command stuff
+ */
+static int handle_stopplaytones(struct ast_channel *chan, void *data)
+{
+ ast_playtones_stop(chan);
+ return 0;
+}
+
+/*
+ * Load module stuff
+ */
+static int ind_load_module(void)
+{
+ struct ast_config *cfg;
+ struct ast_variable *v;
+ char *cxt;
+ char *c;
+ struct tone_zone *tones;
+ const char *country = NULL;
+
+ /* that the following cast is needed, is yuk! */
+ /* yup, checked it out. It is NOT written to. */
+ cfg = ast_config_load((char *)config);
+ if (!cfg)
+ return -1;
+
+ /* Use existing config to populate the Indication table */
+ cxt = ast_category_browse(cfg, NULL);
+ while(cxt) {
+ /* All categories but "general" are considered countries */
+ if (!strcasecmp(cxt, "general")) {
+ cxt = ast_category_browse(cfg, cxt);
+ continue;
+ }
+ if (!(tones = ast_calloc(1, sizeof(*tones)))) {
+ ast_config_destroy(cfg);
+ return -1;
+ }
+ ast_copy_string(tones->country,cxt,sizeof(tones->country));
+
+ v = ast_variable_browse(cfg, cxt);
+ while(v) {
+ if (!strcasecmp(v->name, "description")) {
+ ast_copy_string(tones->description, v->value, sizeof(tones->description));
+ } else if ((!strcasecmp(v->name,"ringcadence"))||(!strcasecmp(v->name,"ringcadance"))) {
+ char *ring,*rings = ast_strdupa(v->value);
+ c = rings;
+ ring = strsep(&c,",");
+ while (ring) {
+ int *tmp, val;
+ if (!isdigit(ring[0]) || (val=atoi(ring))==-1) {
+ ast_log(LOG_WARNING,"Invalid ringcadence given '%s' at line %d.\n",ring,v->lineno);
+ ring = strsep(&c,",");
+ continue;
+ }
+ if (!(tmp = ast_realloc(tones->ringcadence, (tones->nrringcadence + 1) * sizeof(int)))) {
+ ast_config_destroy(cfg);
+ return -1;
+ }
+ tones->ringcadence = tmp;
+ tmp[tones->nrringcadence] = val;
+ tones->nrringcadence++;
+ /* next item */
+ ring = strsep(&c,",");
+ }
+ } else if (!strcasecmp(v->name,"alias")) {
+ char *countries = ast_strdupa(v->value);
+ c = countries;
+ country = strsep(&c,",");
+ while (country) {
+ struct tone_zone* azone;
+ if (!(azone = ast_calloc(1, sizeof(*azone)))) {
+ ast_config_destroy(cfg);
+ return -1;
+ }
+ ast_copy_string(azone->country, country, sizeof(azone->country));
+ ast_copy_string(azone->alias, cxt, sizeof(azone->alias));
+ if (ast_register_indication_country(azone)) {
+ ast_log(LOG_WARNING, "Unable to register indication alias at line %d.\n",v->lineno);
+ free(tones);
+ }
+ /* next item */
+ country = strsep(&c,",");
+ }
+ } else {
+ /* add tone to country */
+ struct tone_zone_sound *ps,*ts;
+ for (ps=NULL,ts=tones->tones; ts; ps=ts, ts=ts->next) {
+ if (strcasecmp(v->name,ts->name)==0) {
+ /* already there */
+ ast_log(LOG_NOTICE,"Duplicate entry '%s', skipped.\n",v->name);
+ goto out;
+ }
+ }
+ /* not there, add it to the back */
+ if (!(ts = ast_malloc(sizeof(*ts)))) {
+ ast_config_destroy(cfg);
+ return -1;
+ }
+ ts->next = NULL;
+ ts->name = strdup(v->name);
+ ts->data = strdup(v->value);
+ if (ps)
+ ps->next = ts;
+ else
+ tones->tones = ts;
+ }
+out: v = v->next;
+ }
+ if (tones->description[0] || tones->alias[0] || tones->tones) {
+ if (ast_register_indication_country(tones)) {
+ ast_log(LOG_WARNING, "Unable to register indication at line %d.\n",v->lineno);
+ free(tones);
+ }
+ } else free(tones);
+
+ cxt = ast_category_browse(cfg, cxt);
+ }
+
+ /* determine which country is the default */
+ country = ast_variable_retrieve(cfg,"general","country");
+ if (!country || !*country || ast_set_indication_country(country))
+ ast_log(LOG_WARNING,"Unable to set the default country (for indication tones)\n");
+
+ ast_config_destroy(cfg);
+ return 0;
+}
+
+/*
+ * CLI entries for commands provided by this module
+ */
+static struct ast_cli_entry cli_show_indications_deprecated = {
+ { "show", "indications", NULL },
+ handle_show_indications, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_indications[] = {
+ { { "indication", "add", NULL },
+ handle_add_indication, "Add the given indication to the country",
+ help_add_indication, NULL },
+
+ { { "indication", "remove", NULL },
+ handle_remove_indication, "Remove the given indication from the country",
+ help_remove_indication, NULL },
+
+ { { "indication", "show", NULL },
+ handle_show_indications, "Display a list of all countries/indications",
+ help_show_indications, NULL, &cli_show_indications_deprecated },
+};
+
+/*
+ * Standard module functions ...
+ */
+static int unload_module(void)
+{
+ /* remove the registed indications... */
+ ast_unregister_indication_country(NULL);
+
+ /* and the functions */
+ ast_cli_unregister_multiple(cli_indications, sizeof(cli_indications) / sizeof(struct ast_cli_entry));
+ ast_unregister_application("PlayTones");
+ ast_unregister_application("StopPlayTones");
+ return 0;
+}
+
+
+static int load_module(void)
+{
+ if (ind_load_module())
+ return AST_MODULE_LOAD_DECLINE;
+ ast_cli_register_multiple(cli_indications, sizeof(cli_indications) / sizeof(struct ast_cli_entry));
+ ast_register_application("PlayTones", handle_playtones, "Play a tone list", playtones_desc);
+ ast_register_application("StopPlayTones", handle_stopplaytones, "Stop playing a tone list","Stop playing a tone list");
+
+ return 0;
+}
+
+static int reload(void)
+{
+ /* remove the registed indications... */
+ ast_unregister_indication_country(NULL);
+
+ return ind_load_module();
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Indications Resource",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
diff --git a/1.4.23-rc4/res/res_jabber.c b/1.4.23-rc4/res/res_jabber.c
new file mode 100644
index 000000000..0079ee9ff
--- /dev/null
+++ b/1.4.23-rc4/res/res_jabber.c
@@ -0,0 +1,2518 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2006, Digium, Inc.
+ *
+ * Matt O'Gorman <mogorman@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ * \brief A resource for interfacing asterisk directly as a client
+ * or a component to a jabber compliant server.
+ *
+ * \todo If you unload this module, chan_gtalk/jingle will be dead. How do we handle that?
+ * \todo If you have TLS, you can't unload this module. See bug #9738. This needs to be fixed,
+ * but the bug is in the unmantained Iksemel library
+ *
+ */
+
+/*** MODULEINFO
+ <depend>iksemel</depend>
+ <use>gnutls</use>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <iksemel.h>
+
+#include "asterisk/channel.h"
+#include "asterisk/jabber.h"
+#include "asterisk/file.h"
+#include "asterisk/config.h"
+#include "asterisk/callerid.h"
+#include "asterisk/lock.h"
+#include "asterisk/logger.h"
+#include "asterisk/options.h"
+#include "asterisk/cli.h"
+#include "asterisk/app.h"
+#include "asterisk/pbx.h"
+#include "asterisk/md5.h"
+#include "asterisk/acl.h"
+#include "asterisk/utils.h"
+#include "asterisk/module.h"
+#include "asterisk/astobj.h"
+#include "asterisk/astdb.h"
+#include "asterisk/manager.h"
+
+#define JABBER_CONFIG "jabber.conf"
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+/*-- Forward declarations */
+static int aji_highest_bit(int number);
+static void aji_buddy_destroy(struct aji_buddy *obj);
+static void aji_client_destroy(struct aji_client *obj);
+static int aji_send_exec(struct ast_channel *chan, void *data);
+static int aji_status_exec(struct ast_channel *chan, void *data);
+static void aji_log_hook(void *data, const char *xmpp, size_t size, int is_incoming);
+static int aji_act_hook(void *data, int type, iks *node);
+static void aji_handle_iq(struct aji_client *client, iks *node);
+static void aji_handle_message(struct aji_client *client, ikspak *pak);
+static void aji_handle_presence(struct aji_client *client, ikspak *pak);
+static void aji_handle_subscribe(struct aji_client *client, ikspak *pak);
+static void *aji_recv_loop(void *data);
+static int aji_component_initialize(struct aji_client *client);
+static int aji_client_initialize(struct aji_client *client);
+static int aji_client_connect(void *data, ikspak *pak);
+static void aji_set_presence(struct aji_client *client, char *to, char *from, int level, char *desc);
+static int aji_do_debug(int fd, int argc, char *argv[]);
+static int aji_do_reload(int fd, int argc, char *argv[]);
+static int aji_no_debug(int fd, int argc, char *argv[]);
+static int aji_test(int fd, int argc, char *argv[]);
+static int aji_show_clients(int fd, int argc, char *argv[]);
+static int aji_create_client(char *label, struct ast_variable *var, int debug);
+static int aji_create_buddy(char *label, struct aji_client *client);
+static int aji_reload(void);
+static int aji_load_config(void);
+static void aji_pruneregister(struct aji_client *client);
+static int aji_filter_roster(void *data, ikspak *pak);
+static int aji_get_roster(struct aji_client *client);
+static int aji_client_info_handler(void *data, ikspak *pak);
+static int aji_dinfo_handler(void *data, ikspak *pak);
+static int aji_ditems_handler(void *data, ikspak *pak);
+static int aji_register_query_handler(void *data, ikspak *pak);
+static int aji_register_approve_handler(void *data, ikspak *pak);
+static int aji_reconnect(struct aji_client *client);
+static iks *jabber_make_auth(iksid * id, const char *pass, const char *sid);
+/* No transports in this version */
+/*
+static int aji_create_transport(char *label, struct aji_client *client);
+static int aji_register_transport(void *data, ikspak *pak);
+static int aji_register_transport2(void *data, ikspak *pak);
+*/
+
+static char debug_usage[] =
+"Usage: jabber debug\n"
+" Enables dumping of Jabber packets for debugging purposes.\n";
+
+static char no_debug_usage[] =
+"Usage: jabber debug off\n"
+" Disables dumping of Jabber packets for debugging purposes.\n";
+
+static char reload_usage[] =
+"Usage: jabber reload\n"
+" Enables reloading of Jabber module.\n";
+
+static char test_usage[] =
+"Usage: jabber test [client]\n"
+" Sends test message for debugging purposes. A specific client\n"
+" as configured in jabber.conf can be optionally specified.\n";
+
+static struct ast_cli_entry aji_cli[] = {
+ { { "jabber", "debug", NULL},
+ aji_do_debug, "Enable Jabber debugging",
+ debug_usage },
+
+ { { "jabber", "reload", NULL},
+ aji_do_reload, "Reload Jabber configuration",
+ reload_usage },
+
+ { { "jabber", "show", "connected", NULL},
+ aji_show_clients, "Show state of clients and components",
+ debug_usage },
+
+ { { "jabber", "debug", "off", NULL},
+ aji_no_debug, "Disable Jabber debug",
+ no_debug_usage },
+
+ { { "jabber", "test", NULL},
+ aji_test, "Shows roster, but is generally used for mog's debugging.",
+ test_usage },
+};
+
+static char *app_ajisend = "JabberSend";
+
+static char *ajisend_synopsis = "JabberSend(jabber,screenname,message)";
+
+static char *ajisend_descrip =
+"JabberSend(Jabber,ScreenName,Message)\n"
+" Jabber - Client or transport Asterisk uses to connect to Jabber\n"
+" ScreenName - User Name to message.\n"
+" Message - Message to be sent to the buddy\n";
+
+static char *app_ajistatus = "JabberStatus";
+
+static char *ajistatus_synopsis = "JabberStatus(Jabber,ScreenName,Variable)";
+
+static char *ajistatus_descrip =
+"JabberStatus(Jabber,ScreenName,Variable)\n"
+" Jabber - Client or transport Asterisk uses to connect to Jabber\n"
+" ScreenName - User Name to retrieve status from.\n"
+" Variable - Variable to store presence in will be 1-6.\n"
+" In order, Online, Chatty, Away, XAway, DND, Offline\n"
+" If not in roster variable will = 7\n";
+
+struct aji_client_container clients;
+struct aji_capabilities *capabilities = NULL;
+
+/*! \brief Global flags, initialized to default values */
+static struct ast_flags globalflags = { AJI_AUTOPRUNE | AJI_AUTOREGISTER };
+static int tls_initialized = FALSE;
+
+/*!
+ * \brief Deletes the aji_client data structure.
+ * \param obj is the structure we will delete.
+ * \return void.
+ */
+static void aji_client_destroy(struct aji_client *obj)
+{
+ struct aji_message *tmp;
+ ASTOBJ_CONTAINER_DESTROYALL(&obj->buddies, aji_buddy_destroy);
+ ASTOBJ_CONTAINER_DESTROY(&obj->buddies);
+ iks_filter_delete(obj->f);
+ iks_parser_delete(obj->p);
+ iks_stack_delete(obj->stack);
+ AST_LIST_LOCK(&obj->messages);
+ while ((tmp = AST_LIST_REMOVE_HEAD(&obj->messages, list))) {
+ if (tmp->from)
+ free(tmp->from);
+ if (tmp->message)
+ free(tmp->message);
+ }
+ AST_LIST_HEAD_DESTROY(&obj->messages);
+ free(obj);
+}
+
+/*!
+ * \brief Deletes the aji_buddy data structure.
+ * \param obj is the structure we will delete.
+ * \return void.
+ */
+static void aji_buddy_destroy(struct aji_buddy *obj)
+{
+ struct aji_resource *tmp;
+
+ while ((tmp = obj->resources)) {
+ obj->resources = obj->resources->next;
+ free(tmp->description);
+ free(tmp);
+ }
+
+ free(obj);
+}
+
+/*!
+ * \brief Find version in XML stream and populate our capabilities list
+ * \param node the node attribute in the caps element we'll look for or add to
+ * our list
+ * \param version the version attribute in the caps element we'll look for or
+ * add to our list
+ * \param pak the XML stanza we're processing
+ * \return a pointer to the added or found aji_version structure
+ */
+static struct aji_version *aji_find_version(char *node, char *version, ikspak *pak)
+{
+ struct aji_capabilities *list = NULL;
+ struct aji_version *res = NULL;
+
+ list = capabilities;
+
+ if(!node)
+ node = pak->from->full;
+ if(!version)
+ version = "none supplied.";
+ while(list) {
+ if(!strcasecmp(list->node, node)) {
+ res = list->versions;
+ while(res) {
+ if(!strcasecmp(res->version, version))
+ return res;
+ res = res->next;
+ }
+ /* Specified version not found. Let's add it to
+ this node in our capabilities list */
+ if(!res) {
+ res = (struct aji_version *)malloc(sizeof(struct aji_version));
+ if(!res) {
+ ast_log(LOG_ERROR, "Out of memory!\n");
+ return NULL;
+ }
+ res->jingle = 0;
+ res->parent = list;
+ ast_copy_string(res->version, version, sizeof(res->version));
+ res->next = list->versions;
+ list->versions = res;
+ return res;
+ }
+ }
+ list = list->next;
+ }
+ /* Specified node not found. Let's add it our capabilities list */
+ if(!list) {
+ list = (struct aji_capabilities *)malloc(sizeof(struct aji_capabilities));
+ if(!list) {
+ ast_log(LOG_ERROR, "Out of memory!\n");
+ return NULL;
+ }
+ res = (struct aji_version *)malloc(sizeof(struct aji_version));
+ if(!res) {
+ ast_log(LOG_ERROR, "Out of memory!\n");
+ ast_free(list);
+ return NULL;
+ }
+ ast_copy_string(list->node, node, sizeof(list->node));
+ ast_copy_string(res->version, version, sizeof(res->version));
+ res->jingle = 0;
+ res->parent = list;
+ res->next = NULL;
+ list->versions = res;
+ list->next = capabilities;
+ capabilities = list;
+ }
+ return res;
+}
+
+static struct aji_resource *aji_find_resource(struct aji_buddy *buddy, char *name)
+{
+ struct aji_resource *res = NULL;
+ if (!buddy || !name)
+ return res;
+ res = buddy->resources;
+ while (res) {
+ if (!strcasecmp(res->resource, name)) {
+ break;
+ }
+ res = res->next;
+ }
+ return res;
+}
+
+static int gtalk_yuck(iks *node)
+{
+ if (iks_find_with_attrib(node, "c", "node", "http://www.google.com/xmpp/client/caps"))
+ return 1;
+ return 0;
+}
+
+/*!
+ * \brief Detects the highest bit in a number.
+ * \param Number you want to have evaluated.
+ * \return the highest power of 2 that can go into the number.
+ */
+static int aji_highest_bit(int number)
+{
+ int x = sizeof(number) * 8 - 1;
+ if (!number)
+ return 0;
+ for (; x > 0; x--) {
+ if (number & (1 << x))
+ break;
+ }
+ return (1 << x);
+}
+
+static iks *jabber_make_auth(iksid * id, const char *pass, const char *sid)
+{
+ iks *x, *y;
+ x = iks_new("iq");
+ iks_insert_attrib(x, "type", "set");
+ y = iks_insert(x, "query");
+ iks_insert_attrib(y, "xmlns", IKS_NS_AUTH);
+ iks_insert_cdata(iks_insert(y, "username"), id->user, 0);
+ iks_insert_cdata(iks_insert(y, "resource"), id->resource, 0);
+ if (sid) {
+ char buf[41];
+ char sidpass[100];
+ snprintf(sidpass, sizeof(sidpass), "%s%s", sid, pass);
+ ast_sha1_hash(buf, sidpass);
+ iks_insert_cdata(iks_insert(y, "digest"), buf, 0);
+ } else {
+ iks_insert_cdata(iks_insert(y, "password"), pass, 0);
+ }
+ return x;
+}
+
+/*!
+ * \brief Dial plan function status(). puts the status of watched user
+ into a channel variable.
+ * \param channel, and username,watched user, status var
+ * \return 0.
+ */
+static int aji_status_exec(struct ast_channel *chan, void *data)
+{
+ struct aji_client *client = NULL;
+ struct aji_buddy *buddy = NULL;
+ struct aji_resource *r = NULL;
+ char *s = NULL, *sender = NULL, *jid = NULL, *screenname = NULL, *resource = NULL, *variable = NULL;
+ int stat = 7;
+ char status[2];
+
+ if (!data) {
+ ast_log(LOG_ERROR, "This application requires arguments.\n");
+ return 0;
+ }
+ s = ast_strdupa(data);
+ if (s) {
+ sender = strsep(&s, "|");
+ if (sender && (sender[0] != '\0')) {
+ jid = strsep(&s, "|");
+ if (jid && (jid[0] != '\0')) {
+ variable = s;
+ } else {
+ ast_log(LOG_ERROR, "Bad arguments\n");
+ return -1;
+ }
+ }
+ }
+
+ if(!strchr(jid, '/')) {
+ resource = NULL;
+ } else {
+ screenname = strsep(&jid, "/");
+ resource = jid;
+ }
+ client = ast_aji_get_client(sender);
+ if (!client) {
+ ast_log(LOG_WARNING, "Could not find sender connection: %s\n", sender);
+ return -1;
+ }
+ if(!&client->buddies) {
+ ast_log(LOG_WARNING, "No buddies for connection : %s\n", sender);
+ return -1;
+ }
+ buddy = ASTOBJ_CONTAINER_FIND(&client->buddies, resource ? screenname: jid);
+ if (!buddy) {
+ ast_log(LOG_WARNING, "Could not find buddy in list : %s\n", resource ? screenname : jid);
+ return -1;
+ }
+ r = aji_find_resource(buddy, resource);
+ if(!r && buddy->resources)
+ r = buddy->resources;
+ if(!r)
+ ast_log(LOG_NOTICE, "Resource %s of buddy %s not found \n", resource, screenname);
+ else
+ stat = r->status;
+ sprintf(status, "%d", stat);
+ pbx_builtin_setvar_helper(chan, variable, status);
+ return 0;
+}
+
+/*!
+ * \brief Dial plan function to send a message.
+ * \param channel, and data, data is sender, reciever, message.
+ * \return 0.
+ */
+static int aji_send_exec(struct ast_channel *chan, void *data)
+{
+ struct aji_client *client = NULL;
+
+ char *s = NULL, *sender = NULL, *recipient = NULL, *message = NULL;
+
+ if (!data) {
+ ast_log(LOG_ERROR, "This application requires arguments.\n");
+ return 0;
+ }
+ s = ast_strdupa(data);
+ if (s) {
+ sender = strsep(&s, "|");
+ if (sender && (sender[0] != '\0')) {
+ recipient = strsep(&s, "|");
+ if (recipient && (recipient[0] != '\0')) {
+ message = s;
+ } else {
+ ast_log(LOG_ERROR, "Bad arguments: %s\n", (char *) data);
+ return -1;
+ }
+ }
+ }
+ if (!(client = ast_aji_get_client(sender))) {
+ ast_log(LOG_WARNING, "Could not find sender connection: %s\n", sender);
+ return -1;
+ }
+ if (strchr(recipient, '@') && message)
+ ast_aji_send(client, recipient, message);
+ return 0;
+}
+
+/*!
+ * \brief the debug loop.
+ * \param aji_client structure, xml data as string, size of string, direction of packet, 1 for inbound 0 for outbound.
+ */
+static void aji_log_hook(void *data, const char *xmpp, size_t size, int is_incoming)
+{
+ struct aji_client *client = ASTOBJ_REF((struct aji_client *) data);
+ manager_event(EVENT_FLAG_USER, "JabberEvent", "Account: %s\r\nPacket: %s\r\n", client->name, xmpp);
+
+ if (client->debug) {
+ if (is_incoming)
+ ast_verbose("\nJABBER: %s INCOMING: %s\n", client->name, xmpp);
+ else {
+ if( strlen(xmpp) == 1) {
+ if(option_debug > 2 && xmpp[0] == ' ')
+ ast_verbose("\nJABBER: Keep alive packet\n");
+ } else
+ ast_verbose("\nJABBER: %s OUTGOING: %s\n", client->name, xmpp);
+ }
+
+ }
+ ASTOBJ_UNREF(client, aji_client_destroy);
+}
+
+/*!
+ * \brief The action hook parses the inbound packets, constantly running.
+ * \param data aji client structure
+ * \param type type of packet
+ * \param node the actual packet.
+ * \return IKS_OK or IKS_HOOK .
+ */
+static int aji_act_hook(void *data, int type, iks *node)
+{
+ struct aji_client *client = ASTOBJ_REF((struct aji_client *) data);
+ ikspak *pak = NULL;
+ iks *auth = NULL;
+
+ if(!node) {
+ ast_log(LOG_ERROR, "aji_act_hook was called with out a packet\n"); /* most likely cause type is IKS_NODE_ERROR lost connection */
+ ASTOBJ_UNREF(client, aji_client_destroy);
+ return IKS_HOOK;
+ }
+
+ if (client->state == AJI_DISCONNECTING) {
+ ASTOBJ_UNREF(client, aji_client_destroy);
+ return IKS_HOOK;
+ }
+
+ pak = iks_packet(node);
+
+ if (!client->component) { /*client */
+ switch (type) {
+ case IKS_NODE_START:
+ if (client->usetls && !iks_is_secure(client->p)) {
+ if (iks_has_tls()) {
+ iks_start_tls(client->p);
+ tls_initialized = TRUE;
+ } else
+ ast_log(LOG_ERROR, "gnuTLS not installed. You need to recompile the Iksemel library with gnuTLS support\n");
+ break;
+ }
+ if (!client->usesasl) {
+ iks_filter_add_rule(client->f, aji_client_connect, client, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_RESULT, IKS_RULE_ID, client->mid, IKS_RULE_DONE);
+ auth = jabber_make_auth(client->jid, client->password, iks_find_attrib(node, "id"));
+ if (auth) {
+ iks_insert_attrib(auth, "id", client->mid);
+ iks_insert_attrib(auth, "to", client->jid->server);
+ ast_aji_increment_mid(client->mid);
+ iks_send(client->p, auth);
+ iks_delete(auth);
+ } else
+ ast_log(LOG_ERROR, "Out of memory.\n");
+ }
+ break;
+
+ case IKS_NODE_NORMAL:
+ if (!strcmp("stream:features", iks_name(node))) {
+ int features = 0;
+ features = iks_stream_features(node);
+ if (client->usesasl) {
+ if (client->usetls && !iks_is_secure(client->p))
+ break;
+ if (client->authorized) {
+ if (features & IKS_STREAM_BIND) {
+ iks_filter_add_rule (client->f, aji_client_connect, client, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_RESULT, IKS_RULE_DONE);
+ auth = iks_make_resource_bind(client->jid);
+ if (auth) {
+ iks_insert_attrib(auth, "id", client->mid);
+ ast_aji_increment_mid(client->mid);
+ iks_send(client->p, auth);
+ iks_delete(auth);
+ } else {
+ ast_log(LOG_ERROR, "Out of memory.\n");
+ break;
+ }
+ }
+ if (features & IKS_STREAM_SESSION) {
+ iks_filter_add_rule (client->f, aji_client_connect, client, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_RESULT, IKS_RULE_ID, "auth", IKS_RULE_DONE);
+ auth = iks_make_session();
+ if (auth) {
+ iks_insert_attrib(auth, "id", "auth");
+ ast_aji_increment_mid(client->mid);
+ iks_send(client->p, auth);
+ iks_delete(auth);
+ } else {
+ ast_log(LOG_ERROR, "Out of memory.\n");
+ }
+ }
+ } else {
+ if (!client->jid->user) {
+ ast_log(LOG_ERROR, "Malformed Jabber ID : %s (domain missing?)\n", client->jid->full);
+ break;
+ }
+ features = aji_highest_bit(features);
+ if (features == IKS_STREAM_SASL_MD5)
+ iks_start_sasl(client->p, IKS_SASL_DIGEST_MD5, client->jid->user, client->password);
+ else {
+ if (features == IKS_STREAM_SASL_PLAIN) {
+ iks *x = NULL;
+ x = iks_new("auth");
+ if (x) {
+ int len = strlen(client->jid->user) + strlen(client->password) + 3;
+ /* XXX Check return values XXX */
+ char *s = ast_malloc(80 + len);
+ char *base64 = ast_malloc(80 + len * 2);
+ iks_insert_attrib(x, "xmlns", IKS_NS_XMPP_SASL);
+ iks_insert_attrib(x, "mechanism", "PLAIN");
+ sprintf(s, "%c%s%c%s", 0, client->jid->user, 0, client->password);
+
+ /* exclude the NULL training byte from the base64 encoding operation
+ as some XMPP servers will refuse it.
+ The format for authentication is [authzid]\0authcid\0password
+ not [authzid]\0authcid\0password\0 */
+ ast_base64encode(base64, (const unsigned char *) s, len - 1, len * 2);
+ iks_insert_cdata(x, base64, 0);
+ iks_send(client->p, x);
+ iks_delete(x);
+ if (base64)
+ free(base64);
+ if (s)
+ free(s);
+ } else {
+ ast_log(LOG_ERROR, "Out of memory.\n");
+ }
+ }
+ }
+ }
+ }
+ } else if (!strcmp("failure", iks_name(node))) {
+ ast_log(LOG_ERROR, "JABBER: encryption failure. possible bad password.\n");
+ } else if (!strcmp("success", iks_name(node))) {
+ client->authorized = 1;
+ iks_send_header(client->p, client->jid->server);
+ }
+ break;
+ case IKS_NODE_ERROR:
+ ast_log(LOG_ERROR, "JABBER: Node Error\n");
+ ASTOBJ_UNREF(client, aji_client_destroy);
+ return IKS_HOOK;
+ break;
+ case IKS_NODE_STOP:
+ ast_log(LOG_WARNING, "JABBER: Disconnected\n");
+ ASTOBJ_UNREF(client, aji_client_destroy);
+ return IKS_HOOK;
+ break;
+ }
+ } else if (client->state != AJI_CONNECTED && client->component) {
+ switch (type) {
+ case IKS_NODE_START:
+ if (client->state == AJI_DISCONNECTED) {
+ char secret[160], shasum[320], *handshake;
+
+ sprintf(secret, "%s%s", pak->id, client->password);
+ ast_sha1_hash(shasum, secret);
+ handshake = NULL;
+ if (asprintf(&handshake, "<handshake>%s</handshake>", shasum) > 0) {
+ iks_send_raw(client->p, handshake);
+ free(handshake);
+ handshake = NULL;
+ }
+ client->state = AJI_CONNECTING;
+ if(iks_recv(client->p,1) == 2) /*XXX proper result for iksemel library on iks_recv of <handshake/> XXX*/
+ client->state = AJI_CONNECTED;
+ else
+ ast_log(LOG_WARNING,"Jabber didn't seem to handshake, failed to authenicate.\n");
+ break;
+ }
+ break;
+
+ case IKS_NODE_NORMAL:
+ break;
+
+ case IKS_NODE_ERROR:
+ ast_log(LOG_ERROR, "JABBER: Node Error\n");
+ ASTOBJ_UNREF(client, aji_client_destroy);
+ return IKS_HOOK;
+
+ case IKS_NODE_STOP:
+ ast_log(LOG_WARNING, "JABBER: Disconnected\n");
+ ASTOBJ_UNREF(client, aji_client_destroy);
+ return IKS_HOOK;
+ }
+ }
+
+ switch (pak->type) {
+ case IKS_PAK_NONE:
+ if (option_debug)
+ ast_log(LOG_DEBUG, "JABBER: I Don't know what to do with you NONE\n");
+ break;
+ case IKS_PAK_MESSAGE:
+ aji_handle_message(client, pak);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "JABBER: I Don't know what to do with you MESSAGE\n");
+ break;
+ case IKS_PAK_PRESENCE:
+ aji_handle_presence(client, pak);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "JABBER: I Do know how to handle presence!!\n");
+ break;
+ case IKS_PAK_S10N:
+ aji_handle_subscribe(client, pak);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "JABBER: I Dont know S10N subscribe!!\n");
+ break;
+ case IKS_PAK_IQ:
+ if (option_debug)
+ ast_log(LOG_DEBUG, "JABBER: I Dont have an IQ!!!\n");
+ aji_handle_iq(client, node);
+ break;
+ default:
+ if (option_debug)
+ ast_log(LOG_DEBUG, "JABBER: I Dont know %i\n", pak->type);
+ break;
+ }
+
+ iks_filter_packet(client->f, pak);
+
+ if (node)
+ iks_delete(node);
+
+ ASTOBJ_UNREF(client, aji_client_destroy);
+ return IKS_OK;
+}
+
+static int aji_register_approve_handler(void *data, ikspak *pak)
+{
+ struct aji_client *client = ASTOBJ_REF((struct aji_client *) data);
+ iks *iq = NULL, *presence = NULL, *x = NULL;
+
+ iq = iks_new("iq");
+ presence = iks_new("presence");
+ x = iks_new("x");
+ if (client && iq && presence && x) {
+ if (!iks_find(pak->query, "remove")) {
+ iks_insert_attrib(iq, "from", client->jid->full);
+ iks_insert_attrib(iq, "to", pak->from->full);
+ iks_insert_attrib(iq, "id", pak->id);
+ iks_insert_attrib(iq, "type", "result");
+ iks_send(client->p, iq);
+
+ iks_insert_attrib(presence, "from", client->jid->full);
+ iks_insert_attrib(presence, "to", pak->from->partial);
+ iks_insert_attrib(presence, "id", client->mid);
+ ast_aji_increment_mid(client->mid);
+ iks_insert_attrib(presence, "type", "subscribe");
+ iks_insert_attrib(x, "xmlns", "vcard-temp:x:update");
+ iks_insert_node(presence, x);
+ iks_send(client->p, presence);
+ }
+ } else {
+ ast_log(LOG_ERROR, "Out of memory.\n");
+ }
+
+ if (iq)
+ iks_delete(iq);
+ if(presence)
+ iks_delete(presence);
+ if (x)
+ iks_delete(x);
+ ASTOBJ_UNREF(client, aji_client_destroy);
+ return IKS_FILTER_EAT;
+}
+
+static int aji_register_query_handler(void *data, ikspak *pak)
+{
+ struct aji_client *client = ASTOBJ_REF((struct aji_client *) data);
+ struct aji_buddy *buddy = NULL;
+ char *node = NULL;
+
+ client = (struct aji_client *) data;
+
+ buddy = ASTOBJ_CONTAINER_FIND(&client->buddies, pak->from->partial);
+ if (!buddy) {
+ iks *iq = NULL, *query = NULL, *error = NULL, *notacceptable = NULL;
+ ast_verbose("Someone.... %s tried to register but they aren't allowed\n", pak->from->partial);
+ iq = iks_new("iq");
+ query = iks_new("query");
+ error = iks_new("error");
+ notacceptable = iks_new("not-acceptable");
+ if(iq && query && error && notacceptable) {
+ iks_insert_attrib(iq, "type", "error");
+ iks_insert_attrib(iq, "from", client->user);
+ iks_insert_attrib(iq, "to", pak->from->full);
+ iks_insert_attrib(iq, "id", pak->id);
+ iks_insert_attrib(query, "xmlns", "jabber:iq:register");
+ iks_insert_attrib(error, "code" , "406");
+ iks_insert_attrib(error, "type", "modify");
+ iks_insert_attrib(notacceptable, "xmlns", "urn:ietf:params:xml:ns:xmpp-stanzas");
+ iks_insert_node(iq, query);
+ iks_insert_node(iq, error);
+ iks_insert_node(error, notacceptable);
+ iks_send(client->p, iq);
+ } else {
+ ast_log(LOG_ERROR, "Out of memory.\n");
+ }
+ if (iq)
+ iks_delete(iq);
+ if (query)
+ iks_delete(query);
+ if (error)
+ iks_delete(error);
+ if (notacceptable)
+ iks_delete(notacceptable);
+ } else if (!(node = iks_find_attrib(pak->query, "node"))) {
+ iks *iq = NULL, *query = NULL, *instructions = NULL;
+ char *explain = "Welcome to Asterisk - the Open Source PBX.\n";
+ iq = iks_new("iq");
+ query = iks_new("query");
+ instructions = iks_new("instructions");
+ if (iq && query && instructions && client) {
+ iks_insert_attrib(iq, "from", client->user);
+ iks_insert_attrib(iq, "to", pak->from->full);
+ iks_insert_attrib(iq, "id", pak->id);
+ iks_insert_attrib(iq, "type", "result");
+ iks_insert_attrib(query, "xmlns", "jabber:iq:register");
+ iks_insert_cdata(instructions, explain, 0);
+ iks_insert_node(iq, query);
+ iks_insert_node(query, instructions);
+ iks_send(client->p, iq);
+ } else {
+ ast_log(LOG_ERROR, "Out of memory.\n");
+ }
+ if (iq)
+ iks_delete(iq);
+ if (query)
+ iks_delete(query);
+ if (instructions)
+ iks_delete(instructions);
+ }
+ ASTOBJ_UNREF(client, aji_client_destroy);
+ return IKS_FILTER_EAT;
+}
+
+static int aji_ditems_handler(void *data, ikspak *pak)
+{
+ struct aji_client *client = ASTOBJ_REF((struct aji_client *) data);
+ char *node = NULL;
+
+ if (!(node = iks_find_attrib(pak->query, "node"))) {
+ iks *iq = NULL, *query = NULL, *item = NULL;
+ iq = iks_new("iq");
+ query = iks_new("query");
+ item = iks_new("item");
+
+ if (iq && query && item) {
+ iks_insert_attrib(iq, "from", client->user);
+ iks_insert_attrib(iq, "to", pak->from->full);
+ iks_insert_attrib(iq, "id", pak->id);
+ iks_insert_attrib(iq, "type", "result");
+ iks_insert_attrib(query, "xmlns", "http://jabber.org/protocol/disco#items");
+ iks_insert_attrib(item, "node", "http://jabber.org/protocol/commands");
+ iks_insert_attrib(item, "name", "Million Dollar Asterisk Commands");
+ iks_insert_attrib(item, "jid", client->user);
+
+ iks_insert_node(iq, query);
+ iks_insert_node(query, item);
+ iks_send(client->p, iq);
+ } else {
+ ast_log(LOG_ERROR, "Out of memory.\n");
+ }
+ if (iq)
+ iks_delete(iq);
+ if (query)
+ iks_delete(query);
+ if (item)
+ iks_delete(item);
+
+ } else if (!strcasecmp(node, "http://jabber.org/protocol/commands")) {
+ iks *iq, *query, *confirm;
+ iq = iks_new("iq");
+ query = iks_new("query");
+ confirm = iks_new("item");
+ if (iq && query && confirm && client) {
+ iks_insert_attrib(iq, "from", client->user);
+ iks_insert_attrib(iq, "to", pak->from->full);
+ iks_insert_attrib(iq, "id", pak->id);
+ iks_insert_attrib(iq, "type", "result");
+ iks_insert_attrib(query, "xmlns", "http://jabber.org/protocol/disco#items");
+ iks_insert_attrib(query, "node", "http://jabber.org/protocol/commands");
+ iks_insert_attrib(confirm, "node", "confirmaccount");
+ iks_insert_attrib(confirm, "name", "Confirm AIM account");
+ iks_insert_attrib(confirm, "jid", "blog.astjab.org");
+
+ iks_insert_node(iq, query);
+ iks_insert_node(query, confirm);
+ iks_send(client->p, iq);
+ } else {
+ ast_log(LOG_ERROR, "Out of memory.\n");
+ }
+ if (iq)
+ iks_delete(iq);
+ if (query)
+ iks_delete(query);
+ if (confirm)
+ iks_delete(confirm);
+
+ } else if (!strcasecmp(node, "confirmaccount")) {
+ iks *iq = NULL, *query = NULL, *feature = NULL;
+
+ iq = iks_new("iq");
+ query = iks_new("query");
+ feature = iks_new("feature");
+
+ if (iq && query && feature && client) {
+ iks_insert_attrib(iq, "from", client->user);
+ iks_insert_attrib(iq, "to", pak->from->full);
+ iks_insert_attrib(iq, "id", pak->id);
+ iks_insert_attrib(iq, "type", "result");
+ iks_insert_attrib(query, "xmlns", "http://jabber.org/protocol/disco#items");
+ iks_insert_attrib(feature, "var", "http://jabber.org/protocol/commands");
+ iks_insert_node(iq, query);
+ iks_insert_node(query, feature);
+ iks_send(client->p, iq);
+ } else {
+ ast_log(LOG_ERROR, "Out of memory.\n");
+ }
+ if (iq)
+ iks_delete(iq);
+ if (query)
+ iks_delete(query);
+ if (feature)
+ iks_delete(feature);
+ }
+
+ ASTOBJ_UNREF(client, aji_client_destroy);
+ return IKS_FILTER_EAT;
+
+}
+
+static int aji_client_info_handler(void *data, ikspak *pak)
+{
+ struct aji_client *client = ASTOBJ_REF((struct aji_client *) data);
+ struct aji_resource *resource = NULL;
+ struct aji_buddy *buddy = ASTOBJ_CONTAINER_FIND(&client->buddies, pak->from->partial);
+
+ resource = aji_find_resource(buddy, pak->from->resource);
+ if (pak->subtype == IKS_TYPE_RESULT) {
+ if (!resource) {
+ ast_log(LOG_NOTICE,"JABBER: Received client info from %s when not requested.\n", pak->from->full);
+ ASTOBJ_UNREF(client, aji_client_destroy);
+ return IKS_FILTER_EAT;
+ }
+ if (iks_find_with_attrib(pak->query, "feature", "var", "http://www.google.com/xmpp/protocol/voice/v1")) {
+ resource->cap->jingle = 1;
+ } else
+ resource->cap->jingle = 0;
+ } else if (pak->subtype == IKS_TYPE_GET) {
+ iks *iq, *disco, *ident, *google, *query;
+ iq = iks_new("iq");
+ query = iks_new("query");
+ ident = iks_new("identity");
+ disco = iks_new("feature");
+ google = iks_new("feature");
+ if (iq && ident && disco && google) {
+ iks_insert_attrib(iq, "from", client->jid->full);
+ iks_insert_attrib(iq, "to", pak->from->full);
+ iks_insert_attrib(iq, "type", "result");
+ iks_insert_attrib(iq, "id", pak->id);
+ iks_insert_attrib(query, "xmlns", "http://jabber.org/protocol/disco#info");
+ iks_insert_attrib(ident, "category", "client");
+ iks_insert_attrib(ident, "type", "pc");
+ iks_insert_attrib(ident, "name", "asterisk");
+ iks_insert_attrib(disco, "var", "http://jabber.org/protocol/disco#info");
+ iks_insert_attrib(google, "var", "http://www.google.com/xmpp/protocol/voice/v1");
+ iks_insert_node(iq, query);
+ iks_insert_node(query, ident);
+ iks_insert_node(query, google);
+ iks_insert_node(query, disco);
+ iks_send(client->p, iq);
+ } else
+ ast_log(LOG_ERROR, "Out of Memory.\n");
+ if (iq)
+ iks_delete(iq);
+ if (query)
+ iks_delete(query);
+ if (ident)
+ iks_delete(ident);
+ if (google)
+ iks_delete(google);
+ if (disco)
+ iks_delete(disco);
+ } else if (pak->subtype == IKS_TYPE_ERROR) {
+ ast_log(LOG_NOTICE, "User %s does not support discovery.\n", pak->from->full);
+ }
+ ASTOBJ_UNREF(client, aji_client_destroy);
+ return IKS_FILTER_EAT;
+}
+
+static int aji_dinfo_handler(void *data, ikspak *pak)
+{
+ struct aji_client *client = ASTOBJ_REF((struct aji_client *) data);
+ char *node = NULL;
+ struct aji_resource *resource = NULL;
+ struct aji_buddy *buddy = ASTOBJ_CONTAINER_FIND(&client->buddies, pak->from->partial);
+
+ resource = aji_find_resource(buddy, pak->from->resource);
+ if (pak->subtype == IKS_TYPE_ERROR) {
+ ast_log(LOG_WARNING, "Recieved error from a client, turn on jabber debug!\n");
+ return IKS_FILTER_EAT;
+ }
+ if (pak->subtype == IKS_TYPE_RESULT) {
+ if (!resource) {
+ ast_log(LOG_NOTICE,"JABBER: Received client info from %s when not requested.\n", pak->from->full);
+ ASTOBJ_UNREF(client, aji_client_destroy);
+ return IKS_FILTER_EAT;
+ }
+ if (iks_find_with_attrib(pak->query, "feature", "var", "http://www.google.com/xmpp/protocol/voice/v1")) {
+ resource->cap->jingle = 1;
+ } else
+ resource->cap->jingle = 0;
+ } else if (pak->subtype == IKS_TYPE_GET && !(node = iks_find_attrib(pak->query, "node"))) {
+ iks *iq, *query, *identity, *disco, *reg, *commands, *gateway, *version, *vcard, *search;
+
+ iq = iks_new("iq");
+ query = iks_new("query");
+ identity = iks_new("identity");
+ disco = iks_new("feature");
+ reg = iks_new("feature");
+ commands = iks_new("feature");
+ gateway = iks_new("feature");
+ version = iks_new("feature");
+ vcard = iks_new("feature");
+ search = iks_new("feature");
+
+ if (iq && query && identity && disco && reg && commands && gateway && version && vcard && search && client) {
+ iks_insert_attrib(iq, "from", client->user);
+ iks_insert_attrib(iq, "to", pak->from->full);
+ iks_insert_attrib(iq, "id", pak->id);
+ iks_insert_attrib(iq, "type", "result");
+ iks_insert_attrib(query, "xmlns", "http://jabber.org/protocol/disco#info");
+ iks_insert_attrib(identity, "category", "gateway");
+ iks_insert_attrib(identity, "type", "pstn");
+ iks_insert_attrib(identity, "name", "Asterisk The Open Source PBX");
+ iks_insert_attrib(disco, "var", "http://jabber.org/protocol/disco");
+ iks_insert_attrib(reg, "var", "jabber:iq:register");
+ iks_insert_attrib(commands, "var", "http://jabber.org/protocol/commands");
+ iks_insert_attrib(gateway, "var", "jabber:iq:gateway");
+ iks_insert_attrib(version, "var", "jabber:iq:version");
+ iks_insert_attrib(vcard, "var", "vcard-temp");
+ iks_insert_attrib(search, "var", "jabber:iq:search");
+
+ iks_insert_node(iq, query);
+ iks_insert_node(query, identity);
+ iks_insert_node(query, disco);
+ iks_insert_node(query, reg);
+ iks_insert_node(query, commands);
+ iks_insert_node(query, gateway);
+ iks_insert_node(query, version);
+ iks_insert_node(query, vcard);
+ iks_insert_node(query, search);
+ iks_send(client->p, iq);
+ } else {
+ ast_log(LOG_ERROR, "Out of memory.\n");
+ }
+
+ if (iq)
+ iks_delete(iq);
+ if (query)
+ iks_delete(query);
+ if (identity)
+ iks_delete(identity);
+ if (disco)
+ iks_delete(disco);
+ if (reg)
+ iks_delete(reg);
+ if (commands)
+ iks_delete(commands);
+ if (gateway)
+ iks_delete(gateway);
+ if (version)
+ iks_delete(version);
+ if (vcard)
+ iks_delete(vcard);
+ if (search)
+ iks_delete(search);
+
+ } else if (pak->subtype == IKS_TYPE_GET && !strcasecmp(node, "http://jabber.org/protocol/commands")) {
+ iks *iq, *query, *confirm;
+ iq = iks_new("iq");
+ query = iks_new("query");
+ confirm = iks_new("item");
+
+ if (iq && query && confirm && client) {
+ iks_insert_attrib(iq, "from", client->user);
+ iks_insert_attrib(iq, "to", pak->from->full);
+ iks_insert_attrib(iq, "id", pak->id);
+ iks_insert_attrib(iq, "type", "result");
+ iks_insert_attrib(query, "xmlns", "http://jabber.org/protocol/disco#items");
+ iks_insert_attrib(query, "node", "http://jabber.org/protocol/commands");
+ iks_insert_attrib(confirm, "node", "confirmaccount");
+ iks_insert_attrib(confirm, "name", "Confirm AIM account");
+ iks_insert_attrib(confirm, "jid", client->user);
+ iks_insert_node(iq, query);
+ iks_insert_node(query, confirm);
+ iks_send(client->p, iq);
+ } else {
+ ast_log(LOG_ERROR, "Out of memory.\n");
+ }
+ if (iq)
+ iks_delete(iq);
+ if (query)
+ iks_delete(query);
+ if (confirm)
+ iks_delete(confirm);
+
+ } else if (pak->subtype == IKS_TYPE_GET && !strcasecmp(node, "confirmaccount")) {
+ iks *iq, *query, *feature;
+
+ iq = iks_new("iq");
+ query = iks_new("query");
+ feature = iks_new("feature");
+
+ if (iq && query && feature && client) {
+ iks_insert_attrib(iq, "from", client->user);
+ iks_insert_attrib(iq, "to", pak->from->full);
+ iks_insert_attrib(iq, "id", pak->id);
+ iks_insert_attrib(iq, "type", "result");
+ iks_insert_attrib(query, "xmlns", "http://jabber.org/protocol/disco#info");
+ iks_insert_attrib(feature, "var", "http://jabber.org/protocol/commands");
+ iks_insert_node(iq, query);
+ iks_insert_node(query, feature);
+ iks_send(client->p, iq);
+ } else {
+ ast_log(LOG_ERROR, "Out of memory.\n");
+ }
+ if (iq)
+ iks_delete(iq);
+ if (query)
+ iks_delete(query);
+ if (feature)
+ iks_delete(feature);
+ }
+
+ ASTOBJ_UNREF(client, aji_client_destroy);
+ return IKS_FILTER_EAT;
+}
+
+/*!
+ * \brief Handles <iq> tags.
+ * \param client structure and the iq node.
+ * \return void.
+ */
+static void aji_handle_iq(struct aji_client *client, iks *node)
+{
+ /*Nothing to see here */
+}
+
+/*!
+ * \brief Handles presence packets.
+ * \param client structure and the node.
+ * \return void.
+ */
+static void aji_handle_message(struct aji_client *client, ikspak *pak)
+{
+ struct aji_message *insert, *tmp;
+ int flag = 0;
+
+ if (!(insert = ast_calloc(1, sizeof(struct aji_message))))
+ return;
+ time(&insert->arrived);
+ if (iks_find_cdata(pak->x, "body"))
+ insert->message = ast_strdup(iks_find_cdata(pak->x, "body"));
+ if(pak->id)
+ ast_copy_string(insert->id, pak->id, sizeof(insert->message));
+ if (pak->from)
+ insert->from = ast_strdup(pak->from->full);
+ AST_LIST_LOCK(&client->messages);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&client->messages, tmp, list) {
+ if (flag) {
+ AST_LIST_REMOVE_CURRENT(&client->messages, list);
+ if (tmp->from)
+ free(tmp->from);
+ if (tmp->message)
+ free(tmp->message);
+ } else if (difftime(time(NULL), tmp->arrived) >= client->message_timeout) {
+ flag = 1;
+ AST_LIST_REMOVE_CURRENT(&client->messages, list);
+ if (tmp->from)
+ free(tmp->from);
+ if (tmp->message)
+ free(tmp->message);
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+ AST_LIST_INSERT_HEAD(&client->messages, insert, list);
+ AST_LIST_UNLOCK(&client->messages);
+}
+
+static void aji_handle_presence(struct aji_client *client, ikspak *pak)
+{
+ int status, priority;
+ struct aji_buddy *buddy;
+ struct aji_resource *tmp = NULL, *last = NULL, *found = NULL;
+ char *ver, *node, *descrip, *type;
+
+ if(client->state != AJI_CONNECTED)
+ aji_create_buddy(pak->from->partial, client);
+
+ buddy = ASTOBJ_CONTAINER_FIND(&client->buddies, pak->from->partial);
+ if (!buddy && pak->from->partial) {
+ /* allow our jid to be used to log in with another resource */
+ if (!strcmp((const char *)pak->from->partial, (const char *)client->jid->partial))
+ aji_create_buddy(pak->from->partial, client);
+ else
+ ast_log(LOG_NOTICE, "Got presence packet from %s, someone not in our roster!!!!\n", pak->from->partial);
+ return;
+ }
+ type = iks_find_attrib(pak->x, "type");
+ if(client->component && type &&!strcasecmp("probe", type)) {
+ aji_set_presence(client, pak->from->full, iks_find_attrib(pak->x, "to"), 1, client->statusmessage);
+ ast_verbose("what i was looking for \n");
+ }
+ ASTOBJ_WRLOCK(buddy);
+ status = (pak->show) ? pak->show : 6;
+ priority = atoi((iks_find_cdata(pak->x, "priority")) ? iks_find_cdata(pak->x, "priority") : "0");
+ tmp = buddy->resources;
+ descrip = ast_strdup(iks_find_cdata(pak->x,"status"));
+
+ while (tmp && pak->from->resource) {
+ if (!strcasecmp(tmp->resource, pak->from->resource)) {
+ tmp->status = status;
+ if (tmp->description) free(tmp->description);
+ tmp->description = descrip;
+ found = tmp;
+ if (status == 6) { /* Sign off Destroy resource */
+ if (last && found->next) {
+ last->next = found->next;
+ } else if (!last) {
+ if (found->next)
+ buddy->resources = found->next;
+ else
+ buddy->resources = NULL;
+ } else if (!found->next) {
+ if (last)
+ last->next = NULL;
+ else
+ buddy->resources = NULL;
+ }
+ free(found);
+ found = NULL;
+ break;
+ }
+ /* resource list is sorted by descending priority */
+ if (tmp->priority != priority) {
+ found->priority = priority;
+ if (!last && !found->next)
+ /* resource was found to be unique,
+ leave loop */
+ break;
+ /* search for resource in our list
+ and take it out for the moment */
+ if (last)
+ last->next = found->next;
+ else
+ buddy->resources = found->next;
+
+ last = NULL;
+ tmp = buddy->resources;
+ if (!buddy->resources)
+ buddy->resources = found;
+ /* priority processing */
+ while (tmp) {
+ /* insert resource back according to
+ its priority value */
+ if (found->priority > tmp->priority) {
+ if (last)
+ /* insert within list */
+ last->next = found;
+ found->next = tmp;
+ if (!last)
+ /* insert on top */
+ buddy->resources = found;
+ break;
+ }
+ if (!tmp->next) {
+ /* insert at the end of the list */
+ tmp->next = found;
+ found->next = NULL;
+ break;
+ }
+ last = tmp;
+ tmp = tmp->next;
+ }
+ }
+ break;
+ }
+ last = tmp;
+ tmp = tmp->next;
+ }
+
+ /* resource not found in our list, create it */
+ if (!found && status != 6 && pak->from->resource) {
+ found = (struct aji_resource *) malloc(sizeof(struct aji_resource));
+ memset(found, 0, sizeof(struct aji_resource));
+
+ if (!found) {
+ ast_log(LOG_ERROR, "Out of memory!\n");
+ return;
+ }
+ ast_copy_string(found->resource, pak->from->resource, sizeof(found->resource));
+ found->status = status;
+ found->description = descrip;
+ found->priority = priority;
+ found->next = NULL;
+ last = NULL;
+ tmp = buddy->resources;
+ while (tmp) {
+ if (found->priority > tmp->priority) {
+ if (last)
+ last->next = found;
+ found->next = tmp;
+ if (!last)
+ buddy->resources = found;
+ break;
+ }
+ if (!tmp->next) {
+ tmp->next = found;
+ break;
+ }
+ last = tmp;
+ tmp = tmp->next;
+ }
+ if (!tmp)
+ buddy->resources = found;
+ }
+
+ ASTOBJ_UNLOCK(buddy);
+ ASTOBJ_UNREF(buddy, aji_buddy_destroy);
+
+ node = iks_find_attrib(iks_find(pak->x, "c"), "node");
+ ver = iks_find_attrib(iks_find(pak->x, "c"), "ver");
+
+ /* handle gmail client's special caps:c tag */
+ if (!node && !ver) {
+ node = iks_find_attrib(iks_find(pak->x, "caps:c"), "node");
+ ver = iks_find_attrib(iks_find(pak->x, "caps:c"), "ver");
+ }
+
+ /* retrieve capabilites of the new resource */
+ if(status !=6 && found && !found->cap) {
+ found->cap = aji_find_version(node, ver, pak);
+ if(gtalk_yuck(pak->x)) /* gtalk should do discover */
+ found->cap->jingle = 1;
+ if(found->cap->jingle && option_debug > 4)
+ ast_log(LOG_DEBUG,"Special case for google till they support discover.\n");
+ else {
+ iks *iq, *query;
+ iq = iks_new("iq");
+ query = iks_new("query");
+ if(query && iq) {
+ iks_insert_attrib(iq, "type", "get");
+ iks_insert_attrib(iq, "to", pak->from->full);
+ iks_insert_attrib(iq,"from", client->jid->full);
+ iks_insert_attrib(iq, "id", client->mid);
+ ast_aji_increment_mid(client->mid);
+ iks_insert_attrib(query, "xmlns", "http://jabber.org/protocol/disco#info");
+ iks_insert_node(iq, query);
+ iks_send(client->p, iq);
+
+ } else
+ ast_log(LOG_ERROR, "Out of memory.\n");
+ if(query)
+ iks_delete(query);
+ if(iq)
+ iks_delete(iq);
+ }
+ }
+ if (option_verbose > 4) {
+ switch (pak->subtype) {
+ case IKS_TYPE_AVAILABLE:
+ ast_verbose(VERBOSE_PREFIX_3 "JABBER: I am available ^_* %i\n", pak->subtype);
+ break;
+ case IKS_TYPE_UNAVAILABLE:
+ ast_verbose(VERBOSE_PREFIX_3 "JABBER: I am unavailable ^_* %i\n", pak->subtype);
+ break;
+ default:
+ ast_verbose(VERBOSE_PREFIX_3 "JABBER: Ohh sexy and the wrong type: %i\n", pak->subtype);
+ }
+ switch (pak->show) {
+ case IKS_SHOW_UNAVAILABLE:
+ ast_verbose(VERBOSE_PREFIX_3 "JABBER: type: %i subtype %i\n", pak->subtype, pak->show);
+ break;
+ case IKS_SHOW_AVAILABLE:
+ ast_verbose(VERBOSE_PREFIX_3 "JABBER: type is available\n");
+ break;
+ case IKS_SHOW_CHAT:
+ ast_verbose(VERBOSE_PREFIX_3 "JABBER: type: %i subtype %i\n", pak->subtype, pak->show);
+ break;
+ case IKS_SHOW_AWAY:
+ ast_verbose(VERBOSE_PREFIX_3 "JABBER: type is away\n");
+ break;
+ case IKS_SHOW_XA:
+ ast_verbose(VERBOSE_PREFIX_3 "JABBER: type: %i subtype %i\n", pak->subtype, pak->show);
+ break;
+ case IKS_SHOW_DND:
+ ast_verbose(VERBOSE_PREFIX_3 "JABBER: type: %i subtype %i\n", pak->subtype, pak->show);
+ break;
+ default:
+ ast_verbose(VERBOSE_PREFIX_3 "JABBER: Kinky! how did that happen %i\n", pak->show);
+ }
+ }
+}
+
+/*!
+ * \brief handles subscription requests.
+ * \param aji_client struct and xml packet.
+ * \return void.
+ */
+static void aji_handle_subscribe(struct aji_client *client, ikspak *pak)
+{
+ iks *presence = NULL, *status = NULL;
+ struct aji_buddy* buddy = NULL;
+
+ switch (pak->subtype) {
+ case IKS_TYPE_SUBSCRIBE:
+ presence = iks_new("presence");
+ status = iks_new("status");
+ if(presence && status) {
+ iks_insert_attrib(presence, "type", "subscribed");
+ iks_insert_attrib(presence, "to", pak->from->full);
+ iks_insert_attrib(presence, "from", client->jid->full);
+ if(pak->id)
+ iks_insert_attrib(presence, "id", pak->id);
+ iks_insert_cdata(status, "Asterisk has approved subscription", 0);
+ iks_insert_node(presence, status);
+ iks_send(client->p, presence);
+ } else
+ ast_log(LOG_ERROR, "Unable to allocate nodes\n");
+ if(presence)
+ iks_delete(presence);
+ if(status)
+ iks_delete(status);
+ if(client->component)
+ aji_set_presence(client, pak->from->full, iks_find_attrib(pak->x, "to"), 1, client->statusmessage);
+ case IKS_TYPE_SUBSCRIBED:
+ buddy = ASTOBJ_CONTAINER_FIND(&client->buddies, pak->from->partial);
+ if (!buddy && pak->from->partial) {
+ aji_create_buddy(pak->from->partial, client);
+ }
+ default:
+ if (option_verbose > 4) {
+ ast_verbose(VERBOSE_PREFIX_3 "JABBER: This is a subcription of type %i\n", pak->subtype);
+ }
+ }
+}
+
+/*!
+ * \brief sends messages.
+ * \param aji_client struct , reciever, message.
+ * \return 1.
+ */
+int ast_aji_send(struct aji_client *client, const char *address, const char *message)
+{
+ int res = 0;
+ iks *message_packet = NULL;
+ if (client->state == AJI_CONNECTED) {
+ message_packet = iks_make_msg(IKS_TYPE_CHAT, address, message);
+ if (message_packet) {
+ iks_insert_attrib(message_packet, "from", client->jid->full);
+ res = iks_send(client->p, message_packet);
+ } else {
+ ast_log(LOG_ERROR, "Out of memory.\n");
+ }
+ if (message_packet)
+ iks_delete(message_packet);
+ } else
+ ast_log(LOG_WARNING, "JABBER: Not connected can't send\n");
+ return 1;
+}
+
+/*!
+ * \brief create a chatroom.
+ * \param aji_client struct , room, server, topic for the room.
+ * \return 0.
+ */
+int ast_aji_create_chat(struct aji_client *client, char *room, char *server, char *topic)
+{
+ int res = 0;
+ iks *iq = NULL;
+ iq = iks_new("iq");
+
+ if (iq && client) {
+ iks_insert_attrib(iq, "type", "get");
+ iks_insert_attrib(iq, "to", server);
+ iks_insert_attrib(iq, "id", client->mid);
+ ast_aji_increment_mid(client->mid);
+ iks_send(client->p, iq);
+ } else
+ ast_log(LOG_ERROR, "Out of memory.\n");
+
+ iks_delete(iq);
+
+ return res;
+}
+
+/*!
+ * \brief join a chatroom.
+ * \param aji_client struct , room.
+ * \return res.
+ */
+int ast_aji_join_chat(struct aji_client *client, char *room)
+{
+ int res = 0;
+ iks *presence = NULL, *priority = NULL;
+ presence = iks_new("presence");
+ priority = iks_new("priority");
+ if (presence && priority && client) {
+ iks_insert_cdata(priority, "0", 1);
+ iks_insert_attrib(presence, "to", room);
+ iks_insert_node(presence, priority);
+ res = iks_send(client->p, presence);
+ iks_insert_cdata(priority, "5", 1);
+ iks_insert_attrib(presence, "to", room);
+ res = iks_send(client->p, presence);
+ } else
+ ast_log(LOG_ERROR, "Out of memory.\n");
+ if (presence)
+ iks_delete(presence);
+ if (priority)
+ iks_delete(priority);
+ return res;
+}
+
+/*!
+ * \brief invite to a chatroom.
+ * \param aji_client struct ,user, room, message.
+ * \return res.
+ */
+int ast_aji_invite_chat(struct aji_client *client, char *user, char *room, char *message)
+{
+ int res = 0;
+ iks *invite, *body, *namespace;
+
+ invite = iks_new("message");
+ body = iks_new("body");
+ namespace = iks_new("x");
+ if (client && invite && body && namespace) {
+ iks_insert_attrib(invite, "to", user);
+ iks_insert_attrib(invite, "id", client->mid);
+ ast_aji_increment_mid(client->mid);
+ iks_insert_cdata(body, message, 0);
+ iks_insert_attrib(namespace, "xmlns", "jabber:x:conference");
+ iks_insert_attrib(namespace, "jid", room);
+ iks_insert_node(invite, body);
+ iks_insert_node(invite, namespace);
+ res = iks_send(client->p, invite);
+ } else
+ ast_log(LOG_ERROR, "Out of memory.\n");
+ if (body)
+ iks_delete(body);
+ if (namespace)
+ iks_delete(namespace);
+ if (invite)
+ iks_delete(invite);
+ return res;
+}
+
+
+/*!
+ * \brief receive message loop.
+ * \param aji_client struct.
+ * \return void.
+ */
+static void *aji_recv_loop(void *data)
+{
+ struct aji_client *client = ASTOBJ_REF((struct aji_client *) data);
+ int res = IKS_HOOK;
+ do {
+ if (res != IKS_OK) {
+ while(res != IKS_OK) {
+ if(option_verbose > 3)
+ ast_verbose("JABBER: reconnecting.\n");
+ res = aji_reconnect(client);
+ sleep(4);
+ }
+ }
+
+ res = iks_recv(client->p, 1);
+
+ if (client->state == AJI_DISCONNECTING) {
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "Ending our Jabber client's thread due to a disconnect\n");
+ pthread_exit(NULL);
+ }
+ client->timeout--;
+ if (res == IKS_HOOK)
+ ast_log(LOG_WARNING, "JABBER: Got hook event.\n");
+ else if (res == IKS_NET_TLSFAIL)
+ ast_log(LOG_WARNING, "JABBER: Failure in TLS.\n");
+ else if (client->timeout == 0 && client->state == AJI_CONNECTED) {
+ res = client->keepalive ? iks_send_raw(client->p, " ") : IKS_OK;
+ if(res == IKS_OK)
+ client->timeout = 50;
+ else
+ ast_log(LOG_WARNING, "JABBER: Network Timeout\n");
+ } else if (res == IKS_NET_RWERR)
+ ast_log(LOG_WARNING, "JABBER: socket read error\n");
+ } while (client);
+ ASTOBJ_UNREF(client, aji_client_destroy);
+ return 0;
+}
+
+/*!
+ * \brief increments the mid field for messages and other events.
+ * \param message id.
+ * \return void.
+ */
+void ast_aji_increment_mid(char *mid)
+{
+ int i = 0;
+
+ for (i = strlen(mid) - 1; i >= 0; i--) {
+ if (mid[i] != 'z') {
+ mid[i] = mid[i] + 1;
+ i = 0;
+ } else
+ mid[i] = 'a';
+ }
+}
+
+
+/*!
+ * \brief attempts to register to a transport.
+ * \param aji_client struct, and xml packet.
+ * \return IKS_FILTER_EAT.
+ */
+/*allows for registering to transport , was too sketch and is out for now. */
+/*static int aji_register_transport(void *data, ikspak *pak)
+{
+ struct aji_client *client = ASTOBJ_REF((struct aji_client *) data);
+ int res = 0;
+ struct aji_buddy *buddy = NULL;
+ iks *send = iks_make_iq(IKS_TYPE_GET, "jabber:iq:register");
+
+ if (client && send) {
+ ASTOBJ_CONTAINER_TRAVERSE(&client->buddies, 1, {
+ ASTOBJ_RDLOCK(iterator);
+ if (iterator->btype == AJI_TRANS) {
+ buddy = iterator;
+ }
+ ASTOBJ_UNLOCK(iterator);
+ });
+ iks_filter_remove_hook(client->f, aji_register_transport);
+ iks_filter_add_rule(client->f, aji_register_transport2, client, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_RESULT, IKS_RULE_NS, IKS_NS_REGISTER, IKS_RULE_DONE);
+ iks_insert_attrib(send, "to", buddy->host);
+ iks_insert_attrib(send, "id", client->mid);
+ ast_aji_increment_mid(client->mid);
+ iks_insert_attrib(send, "from", client->user);
+ res = iks_send(client->p, send);
+ } else
+ ast_log(LOG_ERROR, "Out of memory.\n");
+
+ if (send)
+ iks_delete(send);
+ ASTOBJ_UNREF(client, aji_client_destroy);
+ return IKS_FILTER_EAT;
+
+}
+*/
+/*!
+ * \brief attempts to register to a transport step 2.
+ * \param aji_client struct, and xml packet.
+ * \return IKS_FILTER_EAT.
+ */
+/* more of the same blob of code, too wonky for now*/
+/* static int aji_register_transport2(void *data, ikspak *pak)
+{
+ struct aji_client *client = ASTOBJ_REF((struct aji_client *) data);
+ int res = 0;
+ struct aji_buddy *buddy = NULL;
+
+ iks *regiq = iks_new("iq");
+ iks *regquery = iks_new("query");
+ iks *reguser = iks_new("username");
+ iks *regpass = iks_new("password");
+
+ if (client && regquery && reguser && regpass && regiq) {
+ ASTOBJ_CONTAINER_TRAVERSE(&client->buddies, 1, {
+ ASTOBJ_RDLOCK(iterator);
+ if (iterator->btype == AJI_TRANS)
+ buddy = iterator; ASTOBJ_UNLOCK(iterator);
+ });
+ iks_filter_remove_hook(client->f, aji_register_transport2);
+ iks_insert_attrib(regiq, "to", buddy->host);
+ iks_insert_attrib(regiq, "type", "set");
+ iks_insert_attrib(regiq, "id", client->mid);
+ ast_aji_increment_mid(client->mid);
+ iks_insert_attrib(regiq, "from", client->user);
+ iks_insert_attrib(regquery, "xmlns", "jabber:iq:register");
+ iks_insert_cdata(reguser, buddy->user, 0);
+ iks_insert_cdata(regpass, buddy->pass, 0);
+ iks_insert_node(regiq, regquery);
+ iks_insert_node(regquery, reguser);
+ iks_insert_node(regquery, regpass);
+ res = iks_send(client->p, regiq);
+ } else
+ ast_log(LOG_ERROR, "Out of memory.\n");
+ if (regiq)
+ iks_delete(regiq);
+ if (regquery)
+ iks_delete(regquery);
+ if (reguser)
+ iks_delete(reguser);
+ if (regpass)
+ iks_delete(regpass);
+ ASTOBJ_UNREF(client, aji_client_destroy);
+ return IKS_FILTER_EAT;
+}
+*/
+/*!
+ * \brief goes through roster and prunes users not needed in list, or adds them accordingly.
+ * \param aji_client struct.
+ * \return void.
+ */
+static void aji_pruneregister(struct aji_client *client)
+{
+ int res = 0;
+ iks *removeiq = iks_new("iq");
+ iks *removequery = iks_new("query");
+ iks *removeitem = iks_new("item");
+ iks *send = iks_make_iq(IKS_TYPE_GET, "http://jabber.org/protocol/disco#items");
+
+ if (client && removeiq && removequery && removeitem && send) {
+ iks_insert_node(removeiq, removequery);
+ iks_insert_node(removequery, removeitem);
+ ASTOBJ_CONTAINER_TRAVERSE(&client->buddies, 1, {
+ ASTOBJ_RDLOCK(iterator);
+ /* For an aji_buddy, both AUTOPRUNE and AUTOREGISTER will never
+ * be called at the same time */
+ if (ast_test_flag(iterator, AJI_AUTOPRUNE)) {
+ res = iks_send(client->p, iks_make_s10n(IKS_TYPE_UNSUBSCRIBE, iterator->name,
+ "GoodBye your status is no longer needed by Asterisk the Open Source PBX"
+ " so I am no longer subscribing to your presence.\n"));
+ res = iks_send(client->p, iks_make_s10n(IKS_TYPE_UNSUBSCRIBED, iterator->name,
+ "GoodBye you are no longer in the asterisk config file so I am removing"
+ " your access to my presence.\n"));
+ iks_insert_attrib(removeiq, "from", client->jid->full);
+ iks_insert_attrib(removeiq, "type", "set");
+ iks_insert_attrib(removequery, "xmlns", "jabber:iq:roster");
+ iks_insert_attrib(removeitem, "jid", iterator->name);
+ iks_insert_attrib(removeitem, "subscription", "remove");
+ res = iks_send(client->p, removeiq);
+ } else if (ast_test_flag(iterator, AJI_AUTOREGISTER)) {
+ res = iks_send(client->p, iks_make_s10n(IKS_TYPE_SUBSCRIBE, iterator->name,
+ "Greetings I am the Asterisk Open Source PBX and I want to subscribe to your presence\n"));
+ ast_clear_flag(iterator, AJI_AUTOREGISTER);
+ }
+ ASTOBJ_UNLOCK(iterator);
+ });
+ } else
+ ast_log(LOG_ERROR, "Out of memory.\n");
+ if (removeiq)
+ iks_delete(removeiq);
+ if (removequery)
+ iks_delete(removequery);
+ if (removeitem)
+ iks_delete(removeitem);
+ if (send)
+ iks_delete(send);
+ ASTOBJ_CONTAINER_PRUNE_MARKED(&client->buddies, aji_buddy_destroy);
+}
+
+/*!
+ * \brief filters the roster packet we get back from server.
+ * \param aji_client struct, and xml packet.
+ * \return IKS_FILTER_EAT.
+ */
+static int aji_filter_roster(void *data, ikspak *pak)
+{
+ struct aji_client *client = ASTOBJ_REF((struct aji_client *) data);
+ int flag = 0;
+ iks *x = NULL;
+ struct aji_buddy *buddy;
+
+ client->state = AJI_CONNECTED;
+ ASTOBJ_CONTAINER_TRAVERSE(&client->buddies, 1, {
+ ASTOBJ_RDLOCK(iterator);
+ x = iks_child(pak->query);
+ flag = 0;
+ while (x) {
+ if (!iks_strcmp(iks_name(x), "item")) {
+ if (!strcasecmp(iterator->name, iks_find_attrib(x, "jid"))) {
+ flag = 1;
+ ast_clear_flag(iterator, AJI_AUTOPRUNE | AJI_AUTOREGISTER);
+ }
+ }
+ x = iks_next(x);
+ }
+ if (!flag)
+ ast_copy_flags(iterator, client, AJI_AUTOREGISTER);
+ if (x)
+ iks_delete(x);
+ ASTOBJ_UNLOCK(iterator);
+ });
+
+ x = iks_child(pak->query);
+ while (x) {
+ flag = 0;
+ if (iks_strcmp(iks_name(x), "item") == 0) {
+ ASTOBJ_CONTAINER_TRAVERSE(&client->buddies, 1, {
+ ASTOBJ_RDLOCK(iterator);
+ if (!strcasecmp(iterator->name, iks_find_attrib(x, "jid")))
+ flag = 1;
+ ASTOBJ_UNLOCK(iterator);
+ });
+
+ if (!flag) {
+ buddy = (struct aji_buddy *) malloc(sizeof(struct aji_buddy));
+ if (!buddy) {
+ ast_log(LOG_WARNING, "Out of memory\n");
+ return 0;
+ }
+ memset(buddy, 0, sizeof(struct aji_buddy));
+ ASTOBJ_INIT(buddy);
+ ASTOBJ_WRLOCK(buddy);
+ ast_copy_string(buddy->name, iks_find_attrib(x, "jid"), sizeof(buddy->name));
+ ast_clear_flag(buddy, AST_FLAGS_ALL);
+ if(ast_test_flag(client, AJI_AUTOPRUNE)) {
+ ast_set_flag(buddy, AJI_AUTOPRUNE);
+ buddy->objflags |= ASTOBJ_FLAG_MARKED;
+ } else
+ ast_set_flag(buddy, AJI_AUTOREGISTER);
+ ASTOBJ_UNLOCK(buddy);
+ if (buddy) {
+ ASTOBJ_CONTAINER_LINK(&client->buddies, buddy);
+ ASTOBJ_UNREF(buddy, aji_buddy_destroy);
+ }
+ }
+ }
+ x = iks_next(x);
+ }
+ if (x)
+ iks_delete(x);
+ aji_pruneregister(client);
+
+ ASTOBJ_UNREF(client, aji_client_destroy);
+ return IKS_FILTER_EAT;
+}
+
+static int aji_reconnect(struct aji_client *client)
+{
+ int res = 0;
+
+ if (client->state)
+ client->state = AJI_DISCONNECTED;
+ client->timeout=50;
+ if (client->p)
+ iks_parser_reset(client->p);
+ if (client->authorized)
+ client->authorized = 0;
+
+ if(client->component)
+ res = aji_component_initialize(client);
+ else
+ res = aji_client_initialize(client);
+
+ return res;
+}
+
+static int aji_get_roster(struct aji_client *client)
+{
+ iks *roster = NULL;
+ roster = iks_make_iq(IKS_TYPE_GET, IKS_NS_ROSTER);
+ if(roster) {
+ iks_insert_attrib(roster, "id", "roster");
+ aji_set_presence(client, NULL, client->jid->full, 1, client->statusmessage);
+ iks_send(client->p, roster);
+ }
+ if (roster)
+ iks_delete(roster);
+ return 1;
+}
+
+/*!
+ * \brief connects as a client to jabber server.
+ * \param aji_client struct, and xml packet.
+ * \return res.
+ */
+static int aji_client_connect(void *data, ikspak *pak)
+{
+ struct aji_client *client = ASTOBJ_REF((struct aji_client *) data);
+ int res = 0;
+
+ if (client) {
+ if (client->state == AJI_DISCONNECTED) {
+ iks_filter_add_rule(client->f, aji_filter_roster, client, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_RESULT, IKS_RULE_ID, "roster", IKS_RULE_DONE);
+ client->state = AJI_CONNECTING;
+ client->jid = (iks_find_cdata(pak->query, "jid")) ? iks_id_new(client->stack, iks_find_cdata(pak->query, "jid")) : client->jid;
+ iks_filter_remove_hook(client->f, aji_client_connect);
+ if(!client->component) /*client*/
+ aji_get_roster(client);
+ }
+ } else
+ ast_log(LOG_ERROR, "Out of memory.\n");
+
+ ASTOBJ_UNREF(client, aji_client_destroy);
+ return res;
+}
+
+/*!
+ * \brief prepares client for connect.
+ * \param aji_client struct.
+ * \return 1.
+ */
+static int aji_client_initialize(struct aji_client *client)
+{
+ int connected = 0;
+
+ connected = iks_connect_via(client->p, S_OR(client->serverhost, client->jid->server), client->port, client->jid->server);
+
+ if (connected == IKS_NET_NOCONN) {
+ ast_log(LOG_ERROR, "JABBER ERROR: No Connection\n");
+ return IKS_HOOK;
+ } else if (connected == IKS_NET_NODNS) {
+ ast_log(LOG_ERROR, "JABBER ERROR: No DNS %s for client to %s\n", client->name, S_OR(client->serverhost, client->jid->server));
+ return IKS_HOOK;
+ } else
+ iks_recv(client->p, 30);
+ return IKS_OK;
+}
+
+/*!
+ * \brief prepares component for connect.
+ * \param aji_client struct.
+ * \return 1.
+ */
+static int aji_component_initialize(struct aji_client *client)
+{
+ int connected = 1;
+
+ connected = iks_connect_via(client->p, S_OR(client->serverhost, client->jid->server), client->port, client->user);
+ if (connected == IKS_NET_NOCONN) {
+ ast_log(LOG_ERROR, "JABBER ERROR: No Connection\n");
+ return IKS_HOOK;
+ } else if (connected == IKS_NET_NODNS) {
+ ast_log(LOG_ERROR, "JABBER ERROR: No DNS %s for client to %s\n", client->name, S_OR(client->serverhost, client->jid->server));
+ return IKS_HOOK;
+ } else if (!connected)
+ iks_recv(client->p, 30);
+ return IKS_OK;
+}
+
+/*!
+ * \brief disconnect from jabber server.
+ * \param aji_client struct.
+ * \return 1.
+ */
+int ast_aji_disconnect(struct aji_client *client)
+{
+ if (client) {
+ if (option_verbose > 3)
+ ast_verbose(VERBOSE_PREFIX_3 "JABBER: Disconnecting\n");
+ iks_disconnect(client->p);
+ iks_parser_delete(client->p);
+ ASTOBJ_UNREF(client, aji_client_destroy);
+ }
+
+ return 1;
+}
+
+/*!
+ * \brief set presence of client.
+ * \param aji_client struct, user to send it to, and from, level, description.
+ * \return void.
+ */
+static void aji_set_presence(struct aji_client *client, char *to, char *from, int level, char *desc)
+{
+ int res = 0;
+ iks *presence = iks_make_pres(level, desc);
+ iks *cnode = iks_new("c");
+ iks *priority = iks_new("priority");
+
+ iks_insert_cdata(priority, "0", 1);
+ if (presence && cnode && client) {
+ if(to)
+ iks_insert_attrib(presence, "to", to);
+ if(from)
+ iks_insert_attrib(presence, "from", from);
+ iks_insert_attrib(cnode, "node", "http://www.asterisk.org/xmpp/client/caps");
+ iks_insert_attrib(cnode, "ver", "asterisk-xmpp");
+ iks_insert_attrib(cnode, "ext", "voice-v1");
+ iks_insert_attrib(cnode, "xmlns", "http://jabber.org/protocol/caps");
+ iks_insert_node(presence, cnode);
+ res = iks_send(client->p, presence);
+ } else
+ ast_log(LOG_ERROR, "Out of memory.\n");
+ if (cnode)
+ iks_delete(cnode);
+ if (presence)
+ iks_delete(presence);
+}
+
+/*!
+ * \brief turnon console debugging.
+ * \param fd, number of args, args.
+ * \return RESULT_SUCCESS.
+ */
+static int aji_do_debug(int fd, int argc, char *argv[])
+{
+ ASTOBJ_CONTAINER_TRAVERSE(&clients, 1, {
+ ASTOBJ_RDLOCK(iterator);
+ iterator->debug = 1;
+ ASTOBJ_UNLOCK(iterator);
+ });
+ ast_cli(fd, "Jabber Debugging Enabled.\n");
+ return RESULT_SUCCESS;
+}
+
+/*!
+ * \brief reload jabber module.
+ * \param fd, number of args, args.
+ * \return RESULT_SUCCESS.
+ */
+static int aji_do_reload(int fd, int argc, char *argv[])
+{
+ aji_reload();
+ ast_cli(fd, "Jabber Reloaded.\n");
+ return RESULT_SUCCESS;
+}
+
+/*!
+ * \brief turnoff console debugging.
+ * \param fd, number of args, args.
+ * \return RESULT_SUCCESS.
+ */
+static int aji_no_debug(int fd, int argc, char *argv[])
+{
+ ASTOBJ_CONTAINER_TRAVERSE(&clients, 1, {
+ ASTOBJ_RDLOCK(iterator);
+ iterator->debug = 0;
+ ASTOBJ_UNLOCK(iterator);
+ });
+ ast_cli(fd, "Jabber Debugging Disabled.\n");
+ return RESULT_SUCCESS;
+}
+
+/*!
+ * \brief show client status.
+ * \param fd, number of args, args.
+ * \return RESULT_SUCCESS.
+ */
+static int aji_show_clients(int fd, int argc, char *argv[])
+{
+ char *status;
+ int count = 0;
+ ast_cli(fd, "Jabber Users and their status:\n");
+ ASTOBJ_CONTAINER_TRAVERSE(&clients, 1, {
+ ASTOBJ_RDLOCK(iterator);
+ count++;
+ switch (iterator->state) {
+ case AJI_DISCONNECTED:
+ status = "Disconnected";
+ break;
+ case AJI_CONNECTING:
+ status = "Connecting";
+ break;
+ case AJI_CONNECTED:
+ status = "Connected";
+ break;
+ default:
+ status = "Unknown";
+ }
+ ast_cli(fd, " User: %s - %s\n", iterator->user, status);
+ ASTOBJ_UNLOCK(iterator);
+ });
+ ast_cli(fd, "----\n");
+ ast_cli(fd, " Number of users: %d\n", count);
+ return RESULT_SUCCESS;
+}
+
+/*!
+ * \brief send test message for debugging.
+ * \param fd, number of args, args.
+ * \return RESULT_SUCCESS.
+ */
+static int aji_test(int fd, int argc, char *argv[])
+{
+ struct aji_client *client;
+ struct aji_resource *resource;
+ const char *name = "asterisk";
+ struct aji_message *tmp;
+
+ if (argc > 3)
+ return RESULT_SHOWUSAGE;
+ else if (argc == 3)
+ name = argv[2];
+
+ if (!(client = ASTOBJ_CONTAINER_FIND(&clients, name))) {
+ ast_cli(fd, "Unable to find client '%s'!\n", name);
+ return RESULT_FAILURE;
+ }
+
+ /* XXX Does Matt really want everyone to use his personal address for tests? */ /* XXX yes he does */
+ ast_aji_send(client, "mogorman@astjab.org", "blahblah");
+ ASTOBJ_CONTAINER_TRAVERSE(&client->buddies, 1, {
+ ASTOBJ_RDLOCK(iterator);
+ ast_verbose("User: %s\n", iterator->name);
+ for (resource = iterator->resources; resource; resource = resource->next) {
+ ast_verbose("Resource: %s\n", resource->resource);
+ if(resource->cap) {
+ ast_verbose(" client: %s\n", resource->cap->parent->node);
+ ast_verbose(" version: %s\n", resource->cap->version);
+ ast_verbose(" Jingle Capable: %d\n", resource->cap->jingle);
+ }
+ ast_verbose(" Priority: %d\n", resource->priority);
+ ast_verbose(" Status: %d\n", resource->status);
+ ast_verbose(" Message: %s\n", S_OR(resource->description,""));
+ }
+ ASTOBJ_UNLOCK(iterator);
+ });
+ ast_verbose("\nOooh a working message stack!\n");
+ AST_LIST_LOCK(&client->messages);
+ AST_LIST_TRAVERSE(&client->messages, tmp, list) {
+ ast_verbose(" Message from: %s with id %s @ %s %s\n",tmp->from, S_OR(tmp->id,""), ctime(&tmp->arrived), S_OR(tmp->message, ""));
+ }
+ AST_LIST_UNLOCK(&client->messages);
+ ASTOBJ_UNREF(client, aji_client_destroy);
+
+ return RESULT_SUCCESS;
+}
+
+/*!
+ * \brief creates aji_client structure.
+ * \param label, ast_variable, debug, pruneregister, component/client, aji_client to dump into.
+ * \return 0.
+ */
+static int aji_create_client(char *label, struct ast_variable *var, int debug)
+{
+ char *resource;
+ struct aji_client *client = NULL;
+ int flag = 0;
+
+ client = ASTOBJ_CONTAINER_FIND(&clients,label);
+ if (!client) {
+ flag = 1;
+ client = (struct aji_client *) malloc(sizeof(struct aji_client));
+ if (!client) {
+ ast_log(LOG_ERROR, "Out of memory!\n");
+ return 0;
+ }
+ memset(client, 0, sizeof(struct aji_client));
+ ASTOBJ_INIT(client);
+ ASTOBJ_WRLOCK(client);
+ ASTOBJ_CONTAINER_INIT(&client->buddies);
+ } else {
+ ASTOBJ_WRLOCK(client);
+ ASTOBJ_UNMARK(client);
+ }
+ ASTOBJ_CONTAINER_MARKALL(&client->buddies);
+ ast_copy_string(client->name, label, sizeof(client->name));
+ ast_copy_string(client->mid, "aaaaa", sizeof(client->mid));
+
+ /* Set default values for the client object */
+ client->debug = debug;
+ ast_copy_flags(client, &globalflags, AST_FLAGS_ALL);
+ client->port = 5222;
+ client->usetls = 1;
+ client->usesasl = 1;
+ client->forcessl = 0;
+ client->keepalive = 1;
+ client->timeout = 50;
+ client->message_timeout = 100;
+ AST_LIST_HEAD_INIT(&client->messages);
+ client->component = 0;
+ ast_copy_string(client->statusmessage, "Online and Available", sizeof(client->statusmessage));
+
+ if (flag) {
+ client->authorized = 0;
+ client->state = AJI_DISCONNECTED;
+ }
+ while (var) {
+ if (!strcasecmp(var->name, "username"))
+ ast_copy_string(client->user, var->value, sizeof(client->user));
+ else if (!strcasecmp(var->name, "serverhost"))
+ ast_copy_string(client->serverhost, var->value, sizeof(client->serverhost));
+ else if (!strcasecmp(var->name, "secret"))
+ ast_copy_string(client->password, var->value, sizeof(client->password));
+ else if (!strcasecmp(var->name, "statusmessage"))
+ ast_copy_string(client->statusmessage, var->value, sizeof(client->statusmessage));
+ else if (!strcasecmp(var->name, "port"))
+ client->port = atoi(var->value);
+ else if (!strcasecmp(var->name, "timeout"))
+ client->message_timeout = atoi(var->value);
+ else if (!strcasecmp(var->name, "debug"))
+ client->debug = (ast_false(var->value)) ? 0 : 1;
+ else if (!strcasecmp(var->name, "type")) {
+ if (!strcasecmp(var->value, "component"))
+ client->component = 1;
+ } else if (!strcasecmp(var->name, "usetls")) {
+ client->usetls = (ast_false(var->value)) ? 0 : 1;
+ } else if (!strcasecmp(var->name, "usesasl")) {
+ client->usesasl = (ast_false(var->value)) ? 0 : 1;
+ } else if (!strcasecmp(var->name, "forceoldssl"))
+ client->forcessl = (ast_false(var->value)) ? 0 : 1;
+ else if (!strcasecmp(var->name, "keepalive"))
+ client->keepalive = (ast_false(var->value)) ? 0 : 1;
+ else if (!strcasecmp(var->name, "autoprune"))
+ ast_set2_flag(client, ast_true(var->value), AJI_AUTOPRUNE);
+ else if (!strcasecmp(var->name, "autoregister"))
+ ast_set2_flag(client, ast_true(var->value), AJI_AUTOREGISTER);
+ else if (!strcasecmp(var->name, "buddy"))
+ aji_create_buddy(var->value, client);
+ /* no transport support in this version */
+ /* else if (!strcasecmp(var->name, "transport"))
+ aji_create_transport(var->value, client);
+ */
+ var = var->next;
+ }
+ if (!flag) {
+ ASTOBJ_UNLOCK(client);
+ ASTOBJ_UNREF(client, aji_client_destroy);
+ return 1;
+ }
+ client->p = iks_stream_new(((client->component) ? "jabber:component:accept" : "jabber:client"), client, aji_act_hook);
+ if (!client->p) {
+ ast_log(LOG_ERROR, "Failed to create stream for client '%s'!\n", client->name);
+ return 0;
+ }
+ client->stack = iks_stack_new(8192, 8192);
+ if (!client->stack) {
+ ast_log(LOG_ERROR, "Failed to allocate stack for client '%s'\n", client->name);
+ return 0;
+ }
+ client->f = iks_filter_new();
+ if (!client->f) {
+ ast_log(LOG_ERROR, "Failed to create filter for client '%s'\n", client->name);
+ return 0;
+ }
+ if (!strchr(client->user, '/') && !client->component) { /*client */
+ resource = NULL;
+ if (asprintf(&resource, "%s/asterisk", client->user) > 0) {
+ client->jid = iks_id_new(client->stack, resource);
+ free(resource);
+ }
+ } else
+ client->jid = iks_id_new(client->stack, client->user);
+ if (client->component) {
+ iks_filter_add_rule(client->f, aji_dinfo_handler, client, IKS_RULE_NS, "http://jabber.org/protocol/disco#info", IKS_RULE_DONE);
+ iks_filter_add_rule(client->f, aji_ditems_handler, client, IKS_RULE_NS, "http://jabber.org/protocol/disco#items", IKS_RULE_DONE);
+ iks_filter_add_rule(client->f, aji_register_query_handler, client, IKS_RULE_SUBTYPE, IKS_TYPE_GET, IKS_RULE_NS, "jabber:iq:register", IKS_RULE_DONE);
+ iks_filter_add_rule(client->f, aji_register_approve_handler, client, IKS_RULE_SUBTYPE, IKS_TYPE_SET, IKS_RULE_NS, "jabber:iq:register", IKS_RULE_DONE);
+ } else {
+ iks_filter_add_rule(client->f, aji_client_info_handler, client, IKS_RULE_NS, "http://jabber.org/protocol/disco#info", IKS_RULE_DONE);
+ }
+ if (!strchr(client->user, '/') && !client->component) { /*client */
+ resource = NULL;
+ if (asprintf(&resource, "%s/asterisk", client->user) > 0) {
+ client->jid = iks_id_new(client->stack, resource);
+ free(resource);
+ }
+ } else
+ client->jid = iks_id_new(client->stack, client->user);
+ iks_set_log_hook(client->p, aji_log_hook);
+ ASTOBJ_UNLOCK(client);
+ ASTOBJ_CONTAINER_LINK(&clients,client);
+ return 1;
+}
+
+/*!
+ * \brief creates transport.
+ * \param label, buddy to dump it into.
+ * \return 0.
+ */
+/* no connecting to transports today */
+/*
+static int aji_create_transport(char *label, struct aji_client *client)
+{
+ char *server = NULL, *buddyname = NULL, *user = NULL, *pass = NULL;
+ struct aji_buddy *buddy = NULL;
+
+ buddy = ASTOBJ_CONTAINER_FIND(&client->buddies,label);
+ if (!buddy) {
+ buddy = malloc(sizeof(struct aji_buddy));
+ if(!buddy) {
+ ast_log(LOG_WARNING, "Out of memory\n");
+ return 0;
+ }
+ memset(buddy, 0, sizeof(struct aji_buddy));
+ ASTOBJ_INIT(buddy);
+ }
+ ASTOBJ_WRLOCK(buddy);
+ server = label;
+ if ((buddyname = strchr(label, ','))) {
+ *buddyname = '\0';
+ buddyname++;
+ if (buddyname && buddyname[0] != '\0') {
+ if ((user = strchr(buddyname, ','))) {
+ *user = '\0';
+ user++;
+ if (user && user[0] != '\0') {
+ if ((pass = strchr(user, ','))) {
+ *pass = '\0';
+ pass++;
+ ast_copy_string(buddy->pass, pass, sizeof(buddy->pass));
+ ast_copy_string(buddy->user, user, sizeof(buddy->user));
+ ast_copy_string(buddy->name, buddyname, sizeof(buddy->name));
+ ast_copy_string(buddy->server, server, sizeof(buddy->server));
+ return 1;
+ }
+ }
+ }
+ }
+ }
+ ASTOBJ_UNLOCK(buddy);
+ ASTOBJ_UNMARK(buddy);
+ ASTOBJ_CONTAINER_LINK(&client->buddies, buddy);
+ return 0;
+}
+*/
+
+/*!
+ * \brief creates buddy.
+ * \param label, buddy to dump it into.
+ * \return 0.
+ */
+static int aji_create_buddy(char *label, struct aji_client *client)
+{
+ struct aji_buddy *buddy = NULL;
+ int flag = 0;
+ buddy = ASTOBJ_CONTAINER_FIND(&client->buddies,label);
+ if (!buddy) {
+ flag = 1;
+ buddy = malloc(sizeof(struct aji_buddy));
+ if(!buddy) {
+ ast_log(LOG_WARNING, "Out of memory\n");
+ return 0;
+ }
+ memset(buddy, 0, sizeof(struct aji_buddy));
+ ASTOBJ_INIT(buddy);
+ }
+ ASTOBJ_WRLOCK(buddy);
+ ast_copy_string(buddy->name, label, sizeof(buddy->name));
+ ASTOBJ_UNLOCK(buddy);
+ if(flag)
+ ASTOBJ_CONTAINER_LINK(&client->buddies, buddy);
+ else {
+ ASTOBJ_UNMARK(buddy);
+ ASTOBJ_UNREF(buddy, aji_buddy_destroy);
+ }
+ return 1;
+}
+
+/*!
+ * \brief load config file.
+ * \param void.
+ * \return 1.
+ */
+static int aji_load_config(void)
+{
+ char *cat = NULL;
+ int debug = 1;
+ struct ast_config *cfg = NULL;
+ struct ast_variable *var = NULL;
+
+ cfg = ast_config_load(JABBER_CONFIG);
+ if (!cfg) {
+ ast_log(LOG_WARNING, "No such configuration file %s\n", JABBER_CONFIG);
+ return 0;
+ }
+
+ cat = ast_category_browse(cfg, NULL);
+ for (var = ast_variable_browse(cfg, "general"); var; var = var->next) {
+ if (!strcasecmp(var->name, "debug"))
+ debug = (ast_false(ast_variable_retrieve(cfg, "general", "debug"))) ? 0 : 1;
+ else if (!strcasecmp(var->name, "autoprune"))
+ ast_set2_flag(&globalflags, ast_true(var->value), AJI_AUTOPRUNE);
+ else if (!strcasecmp(var->name, "autoregister"))
+ ast_set2_flag(&globalflags, ast_true(var->value), AJI_AUTOREGISTER);
+ }
+
+ while (cat) {
+ if (strcasecmp(cat, "general")) {
+ var = ast_variable_browse(cfg, cat);
+ aji_create_client(cat, var, debug);
+ }
+ cat = ast_category_browse(cfg, cat);
+ }
+ ast_config_destroy(cfg); /* or leak memory */
+ return 1;
+}
+
+/*!
+ * \brief grab a aji_client structure by label name or JID
+ * (without the resource string)
+ * \param name label or JID
+ * \return aji_client.
+ */
+struct aji_client *ast_aji_get_client(const char *name)
+{
+ struct aji_client *client = NULL;
+ char *aux = NULL;
+
+ client = ASTOBJ_CONTAINER_FIND(&clients, name);
+ if (!client && strchr(name, '@')) {
+ ASTOBJ_CONTAINER_TRAVERSE(&clients, 1, {
+ aux = ast_strdupa(iterator->user);
+ if (strchr(aux, '/')) {
+ /* strip resource for comparison */
+ aux = strsep(&aux, "/");
+ }
+ if (!strncasecmp(aux, name, strlen(aux))) {
+ client = iterator;
+ }
+ });
+ }
+
+ return client;
+}
+
+struct aji_client_container *ast_aji_get_clients(void)
+{
+ return &clients;
+}
+
+static char mandescr_jabber_send[] =
+"Description: Sends a message to a Jabber Client.\n"
+"Variables: \n"
+" Jabber: Client or transport Asterisk uses to connect to JABBER.\n"
+" ScreenName: User Name to message.\n"
+" Message: Message to be sent to the buddy\n";
+
+/*! \brief Send a Jabber Message via call from the Manager */
+static int manager_jabber_send(struct mansession *s, const struct message *m)
+{
+ struct aji_client *client = NULL;
+ const char *id = astman_get_header(m,"ActionID");
+ const char *jabber = astman_get_header(m,"Jabber");
+ const char *screenname = astman_get_header(m,"ScreenName");
+ const char *message = astman_get_header(m,"Message");
+
+ if (ast_strlen_zero(jabber)) {
+ astman_send_error(s, m, "No transport specified");
+ return 0;
+ }
+ if (ast_strlen_zero(screenname)) {
+ astman_send_error(s, m, "No ScreenName specified");
+ return 0;
+ }
+ if (ast_strlen_zero(message)) {
+ astman_send_error(s, m, "No Message specified");
+ return 0;
+ }
+
+ astman_send_ack(s, m, "Attempting to send Jabber Message");
+ client = ast_aji_get_client(jabber);
+ if (!client) {
+ astman_send_error(s, m, "Could not find Sender");
+ return 0;
+ }
+ if (strchr(screenname, '@') && message){
+ ast_aji_send(client, screenname, message);
+ if (!ast_strlen_zero(id))
+ astman_append(s, "ActionID: %s\r\n",id);
+ astman_append(s, "Response: Success\r\n");
+ return 0;
+ }
+ if (!ast_strlen_zero(id))
+ astman_append(s, "ActionID: %s\r\n",id);
+ astman_append(s, "Response: Failure\r\n");
+ return 0;
+}
+
+
+static int aji_reload()
+{
+ ASTOBJ_CONTAINER_MARKALL(&clients);
+ if (!aji_load_config()) {
+ ast_log(LOG_ERROR, "JABBER: Failed to load config.\n");
+ return 0;
+ }
+ ASTOBJ_CONTAINER_PRUNE_MARKED(&clients, aji_client_destroy);
+ ASTOBJ_CONTAINER_TRAVERSE(&clients, 1, {
+ ASTOBJ_RDLOCK(iterator);
+ if(iterator->state == AJI_DISCONNECTED) {
+ if (!iterator->thread)
+ ast_pthread_create_background(&iterator->thread, NULL, aji_recv_loop, iterator);
+ } else if (iterator->state == AJI_CONNECTING)
+ aji_get_roster(iterator);
+ ASTOBJ_UNLOCK(iterator);
+ });
+
+ return 1;
+}
+
+static int unload_module(void)
+{
+
+ /* Check if TLS is initialized. If that's the case, we can't unload this
+ module due to a bug in the iksemel library that will cause a crash or
+ a deadlock. We're trying to find a way to handle this, but in the meantime
+ we will simply refuse to die...
+ */
+ if (tls_initialized) {
+ ast_log(LOG_ERROR, "Module can't be unloaded due to a bug in the Iksemel library when using TLS.\n");
+ return 1; /* You need a forced unload to get rid of this module */
+ }
+
+ ast_cli_unregister_multiple(aji_cli, sizeof(aji_cli) / sizeof(struct ast_cli_entry));
+ ast_unregister_application(app_ajisend);
+ ast_unregister_application(app_ajistatus);
+ ast_manager_unregister("JabberSend");
+
+ ASTOBJ_CONTAINER_TRAVERSE(&clients, 1, {
+ ASTOBJ_RDLOCK(iterator);
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "JABBER: Releasing and disconneing client: %s\n", iterator->name);
+ iterator->state = AJI_DISCONNECTING;
+ ast_aji_disconnect(iterator);
+ pthread_join(iterator->thread, NULL);
+ ASTOBJ_UNLOCK(iterator);
+ });
+
+ ASTOBJ_CONTAINER_DESTROYALL(&clients, aji_client_destroy);
+ ASTOBJ_CONTAINER_DESTROY(&clients);
+ return 0;
+}
+
+static int load_module(void)
+{
+ ASTOBJ_CONTAINER_INIT(&clients);
+ if(!aji_reload())
+ return AST_MODULE_LOAD_DECLINE;
+ ast_manager_register2("JabberSend", EVENT_FLAG_SYSTEM, manager_jabber_send,
+ "Sends a message to a Jabber Client", mandescr_jabber_send);
+ ast_register_application(app_ajisend, aji_send_exec, ajisend_synopsis, ajisend_descrip);
+ ast_register_application(app_ajistatus, aji_status_exec, ajistatus_synopsis, ajistatus_descrip);
+ ast_cli_register_multiple(aji_cli, sizeof(aji_cli) / sizeof(struct ast_cli_entry));
+
+ return 0;
+}
+
+static int reload(void)
+{
+ aji_reload();
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "AJI - Asterisk Jabber Interface",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
diff --git a/1.4.23-rc4/res/res_monitor.c b/1.4.23-rc4/res/res_monitor.c
new file mode 100644
index 000000000..4baa1580d
--- /dev/null
+++ b/1.4.23-rc4/res/res_monitor.c
@@ -0,0 +1,714 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief PBX channel monitoring
+ *
+ * \author Mark Spencer <markster@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <libgen.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/channel.h"
+#include "asterisk/logger.h"
+#include "asterisk/file.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/manager.h"
+#include "asterisk/cli.h"
+#include "asterisk/monitor.h"
+#include "asterisk/app.h"
+#include "asterisk/utils.h"
+#include "asterisk/config.h"
+#include "asterisk/options.h"
+
+AST_MUTEX_DEFINE_STATIC(monitorlock);
+
+#define LOCK_IF_NEEDED(lock, needed) do { \
+ if (needed) \
+ ast_channel_lock(lock); \
+ } while(0)
+
+#define UNLOCK_IF_NEEDED(lock, needed) do { \
+ if (needed) \
+ ast_channel_unlock(lock); \
+ } while (0)
+
+static unsigned long seq = 0;
+
+static char *monitor_synopsis = "Monitor a channel";
+
+static char *monitor_descrip = "Monitor([file_format[:urlbase]|[fname_base]|[options]]):\n"
+"Used to start monitoring a channel. The channel's input and output\n"
+"voice packets are logged to files until the channel hangs up or\n"
+"monitoring is stopped by the StopMonitor application.\n"
+" file_format optional, if not set, defaults to \"wav\"\n"
+" fname_base if set, changes the filename used to the one specified.\n"
+" options:\n"
+" m - when the recording ends mix the two leg files into one and\n"
+" delete the two leg files. If the variable MONITOR_EXEC is set, the\n"
+" application referenced in it will be executed instead of\n"
+#ifdef HAVE_SOXMIX
+" soxmix and the raw leg files will NOT be deleted automatically.\n"
+" soxmix or MONITOR_EXEC is handed 3 arguments, the two leg files\n"
+#else
+" sox and the raw leg files will NOT be deleted automatically.\n"
+" sox or MONITOR_EXEC is handed 3 arguments, the two leg files\n"
+#endif
+" and a target mixed file name which is the same as the leg file names\n"
+" only without the in/out designator.\n"
+" If MONITOR_EXEC_ARGS is set, the contents will be passed on as\n"
+" additional arguements to MONITOR_EXEC\n"
+" Both MONITOR_EXEC and the Mix flag can be set from the\n"
+" administrator interface\n"
+"\n"
+" b - Don't begin recording unless a call is bridged to another channel\n"
+"\nReturns -1 if monitor files can't be opened or if the channel is already\n"
+"monitored, otherwise 0.\n"
+;
+
+static char *stopmonitor_synopsis = "Stop monitoring a channel";
+
+static char *stopmonitor_descrip = "StopMonitor\n"
+ "Stops monitoring a channel. Has no effect if the channel is not monitored\n";
+
+static char *changemonitor_synopsis = "Change monitoring filename of a channel";
+
+static char *changemonitor_descrip = "ChangeMonitor(filename_base)\n"
+ "Changes monitoring filename of a channel. Has no effect if the channel is not monitored\n"
+ "The argument is the new filename base to use for monitoring this channel.\n";
+
+static char *pausemonitor_synopsis = "Pause monitoring of a channel";
+
+static char *pausemonitor_descrip = "PauseMonitor\n"
+ "Pauses monitoring of a channel until it is re-enabled by a call to UnpauseMonitor.\n";
+
+static char *unpausemonitor_synopsis = "Unpause monitoring of a channel";
+
+static char *unpausemonitor_descrip = "UnpauseMonitor\n"
+ "Unpauses monitoring of a channel on which monitoring had\n"
+ "previously been paused with PauseMonitor.\n";
+
+static int ast_monitor_set_state(struct ast_channel *chan, int state)
+{
+ LOCK_IF_NEEDED(chan, 1);
+ if (!chan->monitor) {
+ UNLOCK_IF_NEEDED(chan, 1);
+ return -1;
+ }
+ chan->monitor->state = state;
+ UNLOCK_IF_NEEDED(chan, 1);
+ return 0;
+}
+
+/* Start monitoring a channel */
+int ast_monitor_start( struct ast_channel *chan, const char *format_spec,
+ const char *fname_base, int need_lock)
+{
+ int res = 0;
+ char tmp[256];
+
+ LOCK_IF_NEEDED(chan, need_lock);
+
+ if (!(chan->monitor)) {
+ struct ast_channel_monitor *monitor;
+ char *channel_name, *p;
+
+ /* Create monitoring directory if needed */
+ if (mkdir(ast_config_AST_MONITOR_DIR, 0770) < 0) {
+ if (errno != EEXIST) {
+ ast_log(LOG_WARNING, "Unable to create audio monitor directory: %s\n",
+ strerror(errno));
+ }
+ }
+
+ if (!(monitor = ast_calloc(1, sizeof(*monitor)))) {
+ UNLOCK_IF_NEEDED(chan, need_lock);
+ return -1;
+ }
+
+ /* Determine file names */
+ if (!ast_strlen_zero(fname_base)) {
+ int directory = strchr(fname_base, '/') ? 1 : 0;
+ const char *absolute = *fname_base == '/' ? "" : "/";
+ /* try creating the directory just in case it doesn't exist */
+ if (directory) {
+ char *name = strdup(fname_base);
+ snprintf(tmp, sizeof(tmp), "mkdir -p \"%s\"",dirname(name));
+ free(name);
+ ast_safe_system(tmp);
+ }
+ snprintf(monitor->read_filename, FILENAME_MAX, "%s%s%s-in",
+ directory ? "" : ast_config_AST_MONITOR_DIR, absolute, fname_base);
+ snprintf(monitor->write_filename, FILENAME_MAX, "%s%s%s-out",
+ directory ? "" : ast_config_AST_MONITOR_DIR, absolute, fname_base);
+ ast_copy_string(monitor->filename_base, fname_base, sizeof(monitor->filename_base));
+ } else {
+ ast_mutex_lock(&monitorlock);
+ snprintf(monitor->read_filename, FILENAME_MAX, "%s/audio-in-%ld",
+ ast_config_AST_MONITOR_DIR, seq);
+ snprintf(monitor->write_filename, FILENAME_MAX, "%s/audio-out-%ld",
+ ast_config_AST_MONITOR_DIR, seq);
+ seq++;
+ ast_mutex_unlock(&monitorlock);
+
+ channel_name = ast_strdupa(chan->name);
+ while ((p = strchr(channel_name, '/'))) {
+ *p = '-';
+ }
+ snprintf(monitor->filename_base, FILENAME_MAX, "%s/%d-%s",
+ ast_config_AST_MONITOR_DIR, (int)time(NULL), channel_name);
+ monitor->filename_changed = 1;
+ }
+
+ monitor->stop = ast_monitor_stop;
+
+ /* Determine file format */
+ if (!ast_strlen_zero(format_spec)) {
+ monitor->format = strdup(format_spec);
+ } else {
+ monitor->format = strdup("wav");
+ }
+
+ /* open files */
+ if (ast_fileexists(monitor->read_filename, NULL, NULL) > 0) {
+ ast_filedelete(monitor->read_filename, NULL);
+ }
+ if (!(monitor->read_stream = ast_writefile(monitor->read_filename,
+ monitor->format, NULL,
+ O_CREAT|O_TRUNC|O_WRONLY, 0, 0644))) {
+ ast_log(LOG_WARNING, "Could not create file %s\n",
+ monitor->read_filename);
+ free(monitor);
+ UNLOCK_IF_NEEDED(chan, need_lock);
+ return -1;
+ }
+ if (ast_fileexists(monitor->write_filename, NULL, NULL) > 0) {
+ ast_filedelete(monitor->write_filename, NULL);
+ }
+ if (!(monitor->write_stream = ast_writefile(monitor->write_filename,
+ monitor->format, NULL,
+ O_CREAT|O_TRUNC|O_WRONLY, 0, 0644))) {
+ ast_log(LOG_WARNING, "Could not create file %s\n",
+ monitor->write_filename);
+ ast_closestream(monitor->read_stream);
+ free(monitor);
+ UNLOCK_IF_NEEDED(chan, need_lock);
+ return -1;
+ }
+ chan->monitor = monitor;
+ ast_monitor_set_state(chan, AST_MONITOR_RUNNING);
+ /* so we know this call has been monitored in case we need to bill for it or something */
+ pbx_builtin_setvar_helper(chan, "__MONITORED","true");
+ } else {
+ ast_log(LOG_DEBUG,"Cannot start monitoring %s, already monitored\n",
+ chan->name);
+ res = -1;
+ }
+
+ UNLOCK_IF_NEEDED(chan, need_lock);
+
+ return res;
+}
+
+/*
+ * The file format extensions that Asterisk uses are not all the same as that
+ * which soxmix expects. This function ensures that the format used as the
+ * extension on the filename is something soxmix will understand.
+ */
+static const char *get_soxmix_format(const char *format)
+{
+ const char *res = format;
+
+ if (!strcasecmp(format,"ulaw"))
+ res = "ul";
+ if (!strcasecmp(format,"alaw"))
+ res = "al";
+
+ return res;
+}
+
+/* Stop monitoring a channel */
+int ast_monitor_stop(struct ast_channel *chan, int need_lock)
+{
+ int delfiles = 0;
+
+ LOCK_IF_NEEDED(chan, need_lock);
+
+ if (chan->monitor) {
+ char filename[ FILENAME_MAX ];
+
+ if (chan->monitor->read_stream) {
+ ast_closestream(chan->monitor->read_stream);
+ }
+ if (chan->monitor->write_stream) {
+ ast_closestream(chan->monitor->write_stream);
+ }
+
+ if (chan->monitor->filename_changed && !ast_strlen_zero(chan->monitor->filename_base)) {
+ if (ast_fileexists(chan->monitor->read_filename,NULL,NULL) > 0) {
+ snprintf(filename, FILENAME_MAX, "%s-in", chan->monitor->filename_base);
+ if (ast_fileexists(filename, NULL, NULL) > 0) {
+ ast_filedelete(filename, NULL);
+ }
+ ast_filerename(chan->monitor->read_filename, filename, chan->monitor->format);
+ } else {
+ ast_log(LOG_WARNING, "File %s not found\n", chan->monitor->read_filename);
+ }
+
+ if (ast_fileexists(chan->monitor->write_filename,NULL,NULL) > 0) {
+ snprintf(filename, FILENAME_MAX, "%s-out", chan->monitor->filename_base);
+ if (ast_fileexists(filename, NULL, NULL) > 0) {
+ ast_filedelete(filename, NULL);
+ }
+ ast_filerename(chan->monitor->write_filename, filename, chan->monitor->format);
+ } else {
+ ast_log(LOG_WARNING, "File %s not found\n", chan->monitor->write_filename);
+ }
+ }
+
+ if (chan->monitor->joinfiles && !ast_strlen_zero(chan->monitor->filename_base)) {
+ char tmp[1024];
+ char tmp2[1024];
+ const char *format = !strcasecmp(chan->monitor->format,"wav49") ? "WAV" : chan->monitor->format;
+ char *name = chan->monitor->filename_base;
+ int directory = strchr(name, '/') ? 1 : 0;
+ char *dir = directory ? "" : ast_config_AST_MONITOR_DIR;
+ const char *execute, *execute_args;
+ const char *absolute = *name == '/' ? "" : "/";
+
+ /* Set the execute application */
+ execute = pbx_builtin_getvar_helper(chan, "MONITOR_EXEC");
+ if (ast_strlen_zero(execute)) {
+#ifdef HAVE_SOXMIX
+ execute = "nice -n 19 soxmix";
+#else
+ execute = "nice -n 19 sox -m";
+#endif
+ format = get_soxmix_format(format);
+ delfiles = 1;
+ }
+ execute_args = pbx_builtin_getvar_helper(chan, "MONITOR_EXEC_ARGS");
+ if (ast_strlen_zero(execute_args)) {
+ execute_args = "";
+ }
+
+ snprintf(tmp, sizeof(tmp), "%s \"%s%s%s-in.%s\" \"%s%s%s-out.%s\" \"%s%s%s.%s\" %s &", execute, dir, absolute, name, format, dir, absolute, name, format, dir, absolute, name, format,execute_args);
+ if (delfiles) {
+ snprintf(tmp2,sizeof(tmp2), "( %s& rm -f \"%s%s%s-\"* ) &",tmp, dir, absolute, name); /* remove legs when done mixing */
+ ast_copy_string(tmp, tmp2, sizeof(tmp));
+ }
+ ast_log(LOG_DEBUG,"monitor executing %s\n",tmp);
+ if (ast_safe_system(tmp) == -1)
+ ast_log(LOG_WARNING, "Execute of %s failed.\n",tmp);
+ }
+
+ free(chan->monitor->format);
+ free(chan->monitor);
+ chan->monitor = NULL;
+ }
+
+ UNLOCK_IF_NEEDED(chan, need_lock);
+
+ return 0;
+}
+
+
+/* Pause monitoring of a channel */
+int ast_monitor_pause(struct ast_channel *chan)
+{
+ return ast_monitor_set_state(chan, AST_MONITOR_PAUSED);
+}
+
+/* Unpause monitoring of a channel */
+int ast_monitor_unpause(struct ast_channel *chan)
+{
+ return ast_monitor_set_state(chan, AST_MONITOR_RUNNING);
+}
+
+static int pause_monitor_exec(struct ast_channel *chan, void *data)
+{
+ return ast_monitor_pause(chan);
+}
+
+static int unpause_monitor_exec(struct ast_channel *chan, void *data)
+{
+ return ast_monitor_unpause(chan);
+}
+
+/* Change monitoring filename of a channel */
+int ast_monitor_change_fname(struct ast_channel *chan, const char *fname_base, int need_lock)
+{
+ char tmp[256];
+ if (ast_strlen_zero(fname_base)) {
+ ast_log(LOG_WARNING, "Cannot change monitor filename of channel %s to null\n", chan->name);
+ return -1;
+ }
+
+ LOCK_IF_NEEDED(chan, need_lock);
+
+ if (chan->monitor) {
+ int directory = strchr(fname_base, '/') ? 1 : 0;
+ const char *absolute = *fname_base == '/' ? "" : "/";
+ char tmpstring[sizeof(chan->monitor->filename_base)] = "";
+
+ /* before continuing, see if we're trying to rename the file to itself... */
+ snprintf(tmpstring, sizeof(tmpstring), "%s%s%s", directory ? "" : ast_config_AST_MONITOR_DIR, absolute, fname_base);
+ if (!strcmp(tmpstring, chan->monitor->filename_base)) {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "No need to rename monitor filename to itself\n");
+ UNLOCK_IF_NEEDED(chan, need_lock);
+ return 0;
+ }
+
+ /* try creating the directory just in case it doesn't exist */
+ if (directory) {
+ char *name = strdup(fname_base);
+ snprintf(tmp, sizeof(tmp), "mkdir -p %s",dirname(name));
+ free(name);
+ ast_safe_system(tmp);
+ }
+
+ ast_copy_string(chan->monitor->filename_base, tmpstring, sizeof(chan->monitor->filename_base));
+ chan->monitor->filename_changed = 1;
+ } else {
+ ast_log(LOG_WARNING, "Cannot change monitor filename of channel %s to %s, monitoring not started\n", chan->name, fname_base);
+ }
+
+ UNLOCK_IF_NEEDED(chan, need_lock);
+
+ return 0;
+}
+
+static int start_monitor_exec(struct ast_channel *chan, void *data)
+{
+ char *arg = NULL;
+ char *format = NULL;
+ char *fname_base = NULL;
+ char *options = NULL;
+ char *delay = NULL;
+ char *urlprefix = NULL;
+ char tmp[256];
+ int joinfiles = 0;
+ int waitforbridge = 0;
+ int res = 0;
+
+ /* Parse arguments. */
+ if (!ast_strlen_zero((char*)data)) {
+ arg = ast_strdupa((char*)data);
+ format = arg;
+ fname_base = strchr(arg, '|');
+ if (fname_base) {
+ *fname_base = 0;
+ fname_base++;
+ if ((options = strchr(fname_base, '|'))) {
+ *options = 0;
+ options++;
+ if (strchr(options, 'm'))
+ joinfiles = 1;
+ if (strchr(options, 'b'))
+ waitforbridge = 1;
+ }
+ }
+ arg = strchr(format,':');
+ if (arg) {
+ *arg++ = 0;
+ urlprefix = arg;
+ }
+ }
+ if (urlprefix) {
+ snprintf(tmp,sizeof(tmp) - 1,"%s/%s.%s",urlprefix,fname_base,
+ ((strcmp(format,"gsm")) ? "wav" : "gsm"));
+ if (!chan->cdr && !(chan->cdr = ast_cdr_alloc()))
+ return -1;
+ ast_cdr_setuserfield(chan, tmp);
+ }
+ if (waitforbridge) {
+ /* We must remove the "b" option if listed. In principle none of
+ the following could give NULL results, but we check just to
+ be pedantic. Reconstructing with checks for 'm' option does not
+ work if we end up adding more options than 'm' in the future. */
+ delay = ast_strdupa(data);
+ options = strrchr(delay, '|');
+ if (options) {
+ arg = strchr(options, 'b');
+ if (arg) {
+ *arg = 'X';
+ pbx_builtin_setvar_helper(chan,"AUTO_MONITOR",delay);
+ }
+ }
+ return 0;
+ }
+
+ res = ast_monitor_start(chan, format, fname_base, 1);
+ if (res < 0)
+ res = ast_monitor_change_fname(chan, fname_base, 1);
+ ast_monitor_setjoinfiles(chan, joinfiles);
+
+ return res;
+}
+
+static int stop_monitor_exec(struct ast_channel *chan, void *data)
+{
+ return ast_monitor_stop(chan, 1);
+}
+
+static int change_monitor_exec(struct ast_channel *chan, void *data)
+{
+ return ast_monitor_change_fname(chan, (const char*)data, 1);
+}
+
+static char start_monitor_action_help[] =
+"Description: The 'Monitor' action may be used to record the audio on a\n"
+" specified channel. The following parameters may be used to control\n"
+" this:\n"
+" Channel - Required. Used to specify the channel to record.\n"
+" File - Optional. Is the name of the file created in the\n"
+" monitor spool directory. Defaults to the same name\n"
+" as the channel (with slashes replaced with dashes).\n"
+" Format - Optional. Is the audio recording format. Defaults\n"
+" to \"wav\".\n"
+" Mix - Optional. Boolean parameter as to whether to mix\n"
+" the input and output channels together after the\n"
+" recording is finished.\n";
+
+static int start_monitor_action(struct mansession *s, const struct message *m)
+{
+ struct ast_channel *c = NULL;
+ const char *name = astman_get_header(m, "Channel");
+ const char *fname = astman_get_header(m, "File");
+ const char *format = astman_get_header(m, "Format");
+ const char *mix = astman_get_header(m, "Mix");
+ char *d;
+
+ if (ast_strlen_zero(name)) {
+ astman_send_error(s, m, "No channel specified");
+ return 0;
+ }
+ c = ast_get_channel_by_name_locked(name);
+ if (!c) {
+ astman_send_error(s, m, "No such channel");
+ return 0;
+ }
+
+ if (ast_strlen_zero(fname)) {
+ /* No filename base specified, default to channel name as per CLI */
+ if (!(fname = ast_strdup(c->name))) {
+ astman_send_error(s, m, "Could not start monitoring channel");
+ ast_channel_unlock(c);
+ return 0;
+ }
+ /* Channels have the format technology/channel_name - have to replace that / */
+ if ((d = strchr(fname, '/')))
+ *d = '-';
+ }
+
+ if (ast_monitor_start(c, format, fname, 1)) {
+ if (ast_monitor_change_fname(c, fname, 1)) {
+ astman_send_error(s, m, "Could not start monitoring channel");
+ ast_channel_unlock(c);
+ return 0;
+ }
+ }
+
+ if (ast_true(mix)) {
+ ast_monitor_setjoinfiles(c, 1);
+ }
+
+ ast_channel_unlock(c);
+ astman_send_ack(s, m, "Started monitoring channel");
+ return 0;
+}
+
+static char stop_monitor_action_help[] =
+"Description: The 'StopMonitor' action may be used to end a previously\n"
+" started 'Monitor' action. The only parameter is 'Channel', the name\n"
+" of the channel monitored.\n";
+
+static int stop_monitor_action(struct mansession *s, const struct message *m)
+{
+ struct ast_channel *c = NULL;
+ const char *name = astman_get_header(m, "Channel");
+ int res;
+ if (ast_strlen_zero(name)) {
+ astman_send_error(s, m, "No channel specified");
+ return 0;
+ }
+ c = ast_get_channel_by_name_locked(name);
+ if (!c) {
+ astman_send_error(s, m, "No such channel");
+ return 0;
+ }
+ res = ast_monitor_stop(c, 1);
+ ast_channel_unlock(c);
+ if (res) {
+ astman_send_error(s, m, "Could not stop monitoring channel");
+ return 0;
+ }
+ astman_send_ack(s, m, "Stopped monitoring channel");
+ return 0;
+}
+
+static char change_monitor_action_help[] =
+"Description: The 'ChangeMonitor' action may be used to change the file\n"
+" started by a previous 'Monitor' action. The following parameters may\n"
+" be used to control this:\n"
+" Channel - Required. Used to specify the channel to record.\n"
+" File - Required. Is the new name of the file created in the\n"
+" monitor spool directory.\n";
+
+static int change_monitor_action(struct mansession *s, const struct message *m)
+{
+ struct ast_channel *c = NULL;
+ const char *name = astman_get_header(m, "Channel");
+ const char *fname = astman_get_header(m, "File");
+ if (ast_strlen_zero(name)) {
+ astman_send_error(s, m, "No channel specified");
+ return 0;
+ }
+ if (ast_strlen_zero(fname)) {
+ astman_send_error(s, m, "No filename specified");
+ return 0;
+ }
+ c = ast_get_channel_by_name_locked(name);
+ if (!c) {
+ astman_send_error(s, m, "No such channel");
+ return 0;
+ }
+ if (ast_monitor_change_fname(c, fname, 1)) {
+ astman_send_error(s, m, "Could not change monitored filename of channel");
+ ast_channel_unlock(c);
+ return 0;
+ }
+ ast_channel_unlock(c);
+ astman_send_ack(s, m, "Changed monitor filename");
+ return 0;
+}
+
+void ast_monitor_setjoinfiles(struct ast_channel *chan, int turnon)
+{
+ if (chan->monitor)
+ chan->monitor->joinfiles = turnon;
+}
+
+#define IS_NULL_STRING(string) ((!(string)) || (ast_strlen_zero((string))))
+
+enum MONITOR_PAUSING_ACTION
+{
+ MONITOR_ACTION_PAUSE,
+ MONITOR_ACTION_UNPAUSE
+};
+
+static int do_pause_or_unpause(struct mansession *s, const struct message *m, int action)
+{
+ struct ast_channel *c = NULL;
+ const char *name = astman_get_header(m, "Channel");
+
+ if (IS_NULL_STRING(name)) {
+ astman_send_error(s, m, "No channel specified");
+ return -1;
+ }
+
+ c = ast_get_channel_by_name_locked(name);
+ if (!c) {
+ astman_send_error(s, m, "No such channel");
+ return -1;
+ }
+
+ if (action == MONITOR_ACTION_PAUSE)
+ ast_monitor_pause(c);
+ else
+ ast_monitor_unpause(c);
+
+ ast_channel_unlock(c);
+ astman_send_ack(s, m, (action == MONITOR_ACTION_PAUSE ? "Paused monitoring of the channel" : "Unpaused monitoring of the channel"));
+ return 0;
+}
+
+static char pause_monitor_action_help[] =
+ "Description: The 'PauseMonitor' action may be used to temporarily stop the\n"
+ " recording of a channel. The following parameters may\n"
+ " be used to control this:\n"
+ " Channel - Required. Used to specify the channel to record.\n";
+
+static int pause_monitor_action(struct mansession *s, const struct message *m)
+{
+ return do_pause_or_unpause(s, m, MONITOR_ACTION_PAUSE);
+}
+
+static char unpause_monitor_action_help[] =
+ "Description: The 'UnpauseMonitor' action may be used to re-enable recording\n"
+ " of a channel after calling PauseMonitor. The following parameters may\n"
+ " be used to control this:\n"
+ " Channel - Required. Used to specify the channel to record.\n";
+
+static int unpause_monitor_action(struct mansession *s, const struct message *m)
+{
+ return do_pause_or_unpause(s, m, MONITOR_ACTION_UNPAUSE);
+}
+
+
+static int load_module(void)
+{
+ ast_register_application("Monitor", start_monitor_exec, monitor_synopsis, monitor_descrip);
+ ast_register_application("StopMonitor", stop_monitor_exec, stopmonitor_synopsis, stopmonitor_descrip);
+ ast_register_application("ChangeMonitor", change_monitor_exec, changemonitor_synopsis, changemonitor_descrip);
+ ast_register_application("PauseMonitor", pause_monitor_exec, pausemonitor_synopsis, pausemonitor_descrip);
+ ast_register_application("UnpauseMonitor", unpause_monitor_exec, unpausemonitor_synopsis, unpausemonitor_descrip);
+ ast_manager_register2("Monitor", EVENT_FLAG_CALL, start_monitor_action, monitor_synopsis, start_monitor_action_help);
+ ast_manager_register2("StopMonitor", EVENT_FLAG_CALL, stop_monitor_action, stopmonitor_synopsis, stop_monitor_action_help);
+ ast_manager_register2("ChangeMonitor", EVENT_FLAG_CALL, change_monitor_action, changemonitor_synopsis, change_monitor_action_help);
+ ast_manager_register2("PauseMonitor", EVENT_FLAG_CALL, pause_monitor_action, pausemonitor_synopsis, pause_monitor_action_help);
+ ast_manager_register2("UnpauseMonitor", EVENT_FLAG_CALL, unpause_monitor_action, unpausemonitor_synopsis, unpause_monitor_action_help);
+
+ return 0;
+}
+
+static int unload_module(void)
+{
+ ast_unregister_application("Monitor");
+ ast_unregister_application("StopMonitor");
+ ast_unregister_application("ChangeMonitor");
+ ast_unregister_application("PauseMonitor");
+ ast_unregister_application("UnpauseMonitor");
+ ast_manager_unregister("Monitor");
+ ast_manager_unregister("StopMonitor");
+ ast_manager_unregister("ChangeMonitor");
+ ast_manager_unregister("PauseMonitor");
+ ast_manager_unregister("UnpauseMonitor");
+
+ return 0;
+}
+
+/* usecount semantics need to be defined */
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Call Monitoring Resource",
+ .load = load_module,
+ .unload = unload_module,
+ );
diff --git a/1.4.23-rc4/res/res_musiconhold.c b/1.4.23-rc4/res/res_musiconhold.c
new file mode 100644
index 000000000..6bf2837dc
--- /dev/null
+++ b/1.4.23-rc4/res/res_musiconhold.c
@@ -0,0 +1,1480 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2006, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Routines implementing music on hold
+ *
+ * \arg See also \ref Config_moh
+ *
+ * \author Mark Spencer <markster@digium.com>
+ */
+
+/*** MODULEINFO
+ <conflict>win32</conflict>
+ <use>dahdi</use>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/signal.h>
+#include <netinet/in.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#ifdef SOLARIS
+#include <thread.h>
+#endif
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/options.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/say.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/config.h"
+#include "asterisk/utils.h"
+#include "asterisk/cli.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/astobj2.h"
+
+#include "asterisk/dahdi_compat.h"
+
+#define INITIAL_NUM_FILES 8
+
+static char *app0 = "MusicOnHold";
+static char *app1 = "WaitMusicOnHold";
+static char *app2 = "SetMusicOnHold";
+static char *app3 = "StartMusicOnHold";
+static char *app4 = "StopMusicOnHold";
+
+static char *synopsis0 = "Play Music On Hold indefinitely";
+static char *synopsis1 = "Wait, playing Music On Hold";
+static char *synopsis2 = "Set default Music On Hold class";
+static char *synopsis3 = "Play Music On Hold";
+static char *synopsis4 = "Stop Playing Music On Hold";
+
+static char *descrip0 = "MusicOnHold(class): "
+"Plays hold music specified by class. If omitted, the default\n"
+"music source for the channel will be used. Set the default \n"
+"class with the SetMusicOnHold() application.\n"
+"Returns -1 on hangup.\n"
+"Never returns otherwise.\n";
+
+static char *descrip1 = "WaitMusicOnHold(delay): "
+"Plays hold music specified number of seconds. Returns 0 when\n"
+"done, or -1 on hangup. If no hold music is available, the delay will\n"
+"still occur with no sound.\n";
+
+static char *descrip2 = "SetMusicOnHold(class): "
+"Sets the default class for music on hold for a given channel. When\n"
+"music on hold is activated, this class will be used to select which\n"
+"music is played.\n";
+
+static char *descrip3 = "StartMusicOnHold(class): "
+"Starts playing music on hold, uses default music class for channel.\n"
+"Starts playing music specified by class. If omitted, the default\n"
+"music source for the channel will be used. Always returns 0.\n";
+
+static char *descrip4 = "StopMusicOnHold: "
+"Stops playing music on hold.\n";
+
+static int respawn_time = 20;
+
+struct moh_files_state {
+ struct mohclass *class;
+ int origwfmt;
+ int samples;
+ int sample_queue;
+ int pos;
+ int save_pos;
+ char *save_pos_filename;
+};
+
+#define MOH_QUIET (1 << 0)
+#define MOH_SINGLE (1 << 1)
+#define MOH_CUSTOM (1 << 2)
+#define MOH_RANDOMIZE (1 << 3)
+
+struct mohclass {
+ char name[MAX_MUSICCLASS];
+ char dir[256];
+ char args[256];
+ char mode[80];
+ /*! A dynamically sized array to hold the list of filenames in "files" mode */
+ char **filearray;
+ /*! The current size of the filearray */
+ int allowed_files;
+ /*! The current number of files loaded into the filearray */
+ int total_files;
+ unsigned int flags;
+ /*! The format from the MOH source, not applicable to "files" mode */
+ int format;
+ /*! The pid of the external application delivering MOH */
+ int pid;
+ time_t start;
+ pthread_t thread;
+ /*! Source of audio */
+ int srcfd;
+ /*! FD for timing source */
+ int pseudofd;
+ unsigned int delete:1;
+ AST_LIST_HEAD_NOLOCK(, mohdata) members;
+ AST_LIST_ENTRY(mohclass) list;
+};
+
+struct mohdata {
+ int pipe[2];
+ int origwfmt;
+ struct mohclass *parent;
+ struct ast_frame f;
+ AST_LIST_ENTRY(mohdata) list;
+};
+
+static struct ao2_container *mohclasses;
+
+#define LOCAL_MPG_123 "/usr/local/bin/mpg123"
+#define MPG_123 "/usr/bin/mpg123"
+#define MAX_MP3S 256
+
+static int reload(void);
+
+#define mohclass_ref(class) (ao2_ref((class), +1), class)
+#define mohclass_unref(class) (ao2_ref((class), -1), (struct mohclass *) NULL)
+
+static void moh_files_release(struct ast_channel *chan, void *data)
+{
+ struct moh_files_state *state;
+
+ if (!chan || !chan->music_state) {
+ return;
+ }
+
+ state = chan->music_state;
+
+ if (chan->stream) {
+ ast_closestream(chan->stream);
+ chan->stream = NULL;
+ }
+
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_3 "Stopped music on hold on %s\n", chan->name);
+ }
+
+ if (state->origwfmt && ast_set_write_format(chan, state->origwfmt)) {
+ ast_log(LOG_WARNING, "Unable to restore channel '%s' to format '%d'\n", chan->name, state->origwfmt);
+ }
+
+ state->save_pos = state->pos;
+
+ state->class = mohclass_unref(state->class);
+}
+
+
+static int ast_moh_files_next(struct ast_channel *chan)
+{
+ struct moh_files_state *state = chan->music_state;
+ int tries;
+
+ /* Discontinue a stream if it is running already */
+ if (chan->stream) {
+ ast_closestream(chan->stream);
+ chan->stream = NULL;
+ }
+
+ if (!state->class->total_files) {
+ ast_log(LOG_WARNING, "No files available for class '%s'\n", state->class->name);
+ return -1;
+ }
+
+ /* If a specific file has been saved confirm it still exists and that it is still valid */
+ if (state->save_pos >= 0 && state->save_pos < state->class->total_files && state->class->filearray[state->save_pos] == state->save_pos_filename) {
+ state->pos = state->save_pos;
+ state->save_pos = -1;
+ } else if (ast_test_flag(state->class, MOH_RANDOMIZE)) {
+ /* Get a random file and ensure we can open it */
+ for (tries = 0; tries < 20; tries++) {
+ state->pos = ast_random() % state->class->total_files;
+ if (ast_fileexists(state->class->filearray[state->pos], NULL, NULL) > 0)
+ break;
+ }
+ state->save_pos = -1;
+ state->samples = 0;
+ } else {
+ /* This is easy, just increment our position and make sure we don't exceed the total file count */
+ state->pos++;
+ state->pos %= state->class->total_files;
+ state->save_pos = -1;
+ state->samples = 0;
+ }
+
+ if (!ast_openstream_full(chan, state->class->filearray[state->pos], chan->language, 1)) {
+ ast_log(LOG_WARNING, "Unable to open file '%s': %s\n", state->class->filearray[state->pos], strerror(errno));
+ state->pos++;
+ state->pos %= state->class->total_files;
+ return -1;
+ }
+
+ /* Record the pointer to the filename for position resuming later */
+ state->save_pos_filename = state->class->filearray[state->pos];
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "%s Opened file %d '%s'\n", chan->name, state->pos, state->class->filearray[state->pos]);
+
+ if (state->samples)
+ ast_seekstream(chan->stream, state->samples, SEEK_SET);
+
+ return 0;
+}
+
+
+static struct ast_frame *moh_files_readframe(struct ast_channel *chan)
+{
+ struct ast_frame *f = NULL;
+
+ if (!(chan->stream && (f = ast_readframe(chan->stream)))) {
+ if (!ast_moh_files_next(chan))
+ f = ast_readframe(chan->stream);
+ }
+
+ return f;
+}
+
+static int moh_files_generator(struct ast_channel *chan, void *data, int len, int samples)
+{
+ struct moh_files_state *state = chan->music_state;
+ struct ast_frame *f = NULL;
+ int res = 0;
+
+ state->sample_queue += samples;
+
+ while (state->sample_queue > 0) {
+ if ((f = moh_files_readframe(chan))) {
+ state->samples += f->samples;
+ state->sample_queue -= f->samples;
+ res = ast_write(chan, f);
+ ast_frfree(f);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
+ return -1;
+ }
+ } else
+ return -1;
+ }
+ return res;
+}
+
+
+static void *moh_files_alloc(struct ast_channel *chan, void *params)
+{
+ struct moh_files_state *state;
+ struct mohclass *class = params;
+
+ if (!chan->music_state && (state = ast_calloc(1, sizeof(*state)))) {
+ chan->music_state = state;
+ state->class = mohclass_ref(class);
+ state->save_pos = -1;
+ } else {
+ state = chan->music_state;
+ }
+
+ if (!state) {
+ return NULL;
+ }
+
+ if (state->class != class) {
+ /* (re-)initialize */
+ if (state->class) {
+ state->class = mohclass_unref(state->class);
+ }
+ memset(state, 0, sizeof(*state));
+ state->class = mohclass_ref(class);
+ if (ast_test_flag(state->class, MOH_RANDOMIZE) && class->total_files) {
+ state->pos = ast_random() % class->total_files;
+ }
+ }
+
+ state->origwfmt = chan->writeformat;
+
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on %s\n",
+ class->name, chan->name);
+ }
+
+ return chan->music_state;
+}
+
+static struct ast_generator moh_file_stream = {
+ .alloc = moh_files_alloc,
+ .release = moh_files_release,
+ .generate = moh_files_generator,
+};
+
+static int spawn_mp3(struct mohclass *class)
+{
+ int fds[2];
+ int files = 0;
+ char fns[MAX_MP3S][80];
+ char *argv[MAX_MP3S + 50];
+ char xargs[256];
+ char *argptr;
+ int argc = 0;
+ DIR *dir = NULL;
+ struct dirent *de;
+ sigset_t signal_set, old_set;
+
+
+ if (!strcasecmp(class->dir, "nodir")) {
+ files = 1;
+ } else {
+ dir = opendir(class->dir);
+ if (!dir && strncasecmp(class->dir, "http://", 7)) {
+ ast_log(LOG_WARNING, "%s is not a valid directory\n", class->dir);
+ return -1;
+ }
+ }
+
+ if (!ast_test_flag(class, MOH_CUSTOM)) {
+ argv[argc++] = "mpg123";
+ argv[argc++] = "-q";
+ argv[argc++] = "-s";
+ argv[argc++] = "--mono";
+ argv[argc++] = "-r";
+ argv[argc++] = "8000";
+
+ if (!ast_test_flag(class, MOH_SINGLE)) {
+ argv[argc++] = "-b";
+ argv[argc++] = "2048";
+ }
+
+ argv[argc++] = "-f";
+
+ if (ast_test_flag(class, MOH_QUIET))
+ argv[argc++] = "4096";
+ else
+ argv[argc++] = "8192";
+
+ /* Look for extra arguments and add them to the list */
+ ast_copy_string(xargs, class->args, sizeof(xargs));
+ argptr = xargs;
+ while (!ast_strlen_zero(argptr)) {
+ argv[argc++] = argptr;
+ strsep(&argptr, ",");
+ }
+ } else {
+ /* Format arguments for argv vector */
+ ast_copy_string(xargs, class->args, sizeof(xargs));
+ argptr = xargs;
+ while (!ast_strlen_zero(argptr)) {
+ argv[argc++] = argptr;
+ strsep(&argptr, " ");
+ }
+ }
+
+ if (!strncasecmp(class->dir, "http://", 7)) {
+ ast_copy_string(fns[files], class->dir, sizeof(fns[files]));
+ argv[argc++] = fns[files];
+ files++;
+ } else if (dir) {
+ while ((de = readdir(dir)) && (files < MAX_MP3S)) {
+ if ((strlen(de->d_name) > 3) &&
+ ((ast_test_flag(class, MOH_CUSTOM) &&
+ (!strcasecmp(de->d_name + strlen(de->d_name) - 4, ".raw") ||
+ !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".sln"))) ||
+ !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".mp3"))) {
+ ast_copy_string(fns[files], de->d_name, sizeof(fns[files]));
+ argv[argc++] = fns[files];
+ files++;
+ }
+ }
+ }
+ argv[argc] = NULL;
+ if (dir) {
+ closedir(dir);
+ }
+ if (pipe(fds)) {
+ ast_log(LOG_WARNING, "Pipe failed\n");
+ return -1;
+ }
+ if (!files) {
+ ast_log(LOG_WARNING, "Found no files in '%s'\n", class->dir);
+ close(fds[0]);
+ close(fds[1]);
+ return -1;
+ }
+ if (!strncasecmp(class->dir, "http://", 7) && time(NULL) - class->start < respawn_time) {
+ sleep(respawn_time - (time(NULL) - class->start));
+ }
+
+ /* Block signals during the fork() */
+ sigfillset(&signal_set);
+ pthread_sigmask(SIG_BLOCK, &signal_set, &old_set);
+
+ time(&class->start);
+ class->pid = fork();
+ if (class->pid < 0) {
+ close(fds[0]);
+ close(fds[1]);
+ ast_log(LOG_WARNING, "Fork failed: %s\n", strerror(errno));
+ return -1;
+ }
+ if (!class->pid) {
+ int x;
+
+ if (ast_opt_high_priority)
+ ast_set_priority(0);
+
+ /* Reset ignored signals back to default */
+ signal(SIGPIPE, SIG_DFL);
+ pthread_sigmask(SIG_UNBLOCK, &signal_set, NULL);
+
+ close(fds[0]);
+ /* Stdout goes to pipe */
+ dup2(fds[1], STDOUT_FILENO);
+ /* Close unused file descriptors */
+ for (x=3;x<8192;x++) {
+ if (-1 != fcntl(x, F_GETFL)) {
+ close(x);
+ }
+ }
+ /* Child */
+ if (strcasecmp(class->dir, "nodir") && chdir(class->dir) < 0) {
+ ast_log(LOG_WARNING, "chdir() failed: %s\n", strerror(errno));
+ _exit(1);
+ }
+ setpgid(0, getpid());
+ if (ast_test_flag(class, MOH_CUSTOM)) {
+ execv(argv[0], argv);
+ } else {
+ /* Default install is /usr/local/bin */
+ execv(LOCAL_MPG_123, argv);
+ /* Many places have it in /usr/bin */
+ execv(MPG_123, argv);
+ /* Check PATH as a last-ditch effort */
+ execvp("mpg123", argv);
+ }
+ ast_log(LOG_WARNING, "Exec failed: %s\n", strerror(errno));
+ close(fds[1]);
+ _exit(1);
+ } else {
+ /* Parent */
+ pthread_sigmask(SIG_SETMASK, &old_set, NULL);
+ close(fds[1]);
+ }
+ return fds[0];
+}
+
+static void *monmp3thread(void *data)
+{
+#define MOH_MS_INTERVAL 100
+
+ struct mohclass *class = data;
+ struct mohdata *moh;
+ char buf[8192];
+ short sbuf[8192];
+ int res, res2;
+ int len;
+ struct timeval tv, tv_tmp;
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ for(;/* ever */;) {
+ pthread_testcancel();
+ /* Spawn mp3 player if it's not there */
+ if (class->srcfd < 0) {
+ if ((class->srcfd = spawn_mp3(class)) < 0) {
+ ast_log(LOG_WARNING, "Unable to spawn mp3player\n");
+ /* Try again later */
+ sleep(500);
+ pthread_testcancel();
+ }
+ }
+ if (class->pseudofd > -1) {
+#ifdef SOLARIS
+ thr_yield();
+#endif
+ /* Pause some amount of time */
+ res = read(class->pseudofd, buf, sizeof(buf));
+ pthread_testcancel();
+ } else {
+ long delta;
+ /* Reliable sleep */
+ tv_tmp = ast_tvnow();
+ if (ast_tvzero(tv))
+ tv = tv_tmp;
+ delta = ast_tvdiff_ms(tv_tmp, tv);
+ if (delta < MOH_MS_INTERVAL) { /* too early */
+ tv = ast_tvadd(tv, ast_samp2tv(MOH_MS_INTERVAL, 1000)); /* next deadline */
+ usleep(1000 * (MOH_MS_INTERVAL - delta));
+ pthread_testcancel();
+ } else {
+ ast_log(LOG_NOTICE, "Request to schedule in the past?!?!\n");
+ tv = tv_tmp;
+ }
+ res = 8 * MOH_MS_INTERVAL; /* 8 samples per millisecond */
+ }
+ if (AST_LIST_EMPTY(&class->members))
+ continue;
+ /* Read mp3 audio */
+ len = ast_codec_get_len(class->format, res);
+
+ if ((res2 = read(class->srcfd, sbuf, len)) != len) {
+ if (!res2) {
+ close(class->srcfd);
+ class->srcfd = -1;
+ pthread_testcancel();
+ if (class->pid > 1) {
+ killpg(class->pid, SIGHUP);
+ usleep(100000);
+ killpg(class->pid, SIGTERM);
+ usleep(100000);
+ killpg(class->pid, SIGKILL);
+ class->pid = 0;
+ }
+ } else
+ ast_log(LOG_DEBUG, "Read %d bytes of audio while expecting %d\n", res2, len);
+ continue;
+ }
+
+ pthread_testcancel();
+
+ ao2_lock(class);
+ AST_LIST_TRAVERSE(&class->members, moh, list) {
+ /* Write data */
+ if ((res = write(moh->pipe[1], sbuf, res2)) != res2) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Only wrote %d of %d bytes to pipe\n", res, res2);
+ }
+ }
+ ao2_unlock(class);
+ }
+ return NULL;
+}
+
+static int moh0_exec(struct ast_channel *chan, void *data)
+{
+ if (ast_moh_start(chan, data, NULL)) {
+ ast_log(LOG_WARNING, "Unable to start music on hold (class '%s') on channel %s\n", (char *)data, chan->name);
+ return 0;
+ }
+ while (!ast_safe_sleep(chan, 10000));
+ ast_moh_stop(chan);
+ return -1;
+}
+
+static int moh1_exec(struct ast_channel *chan, void *data)
+{
+ int res;
+ if (!data || !atoi(data)) {
+ ast_log(LOG_WARNING, "WaitMusicOnHold requires an argument (number of seconds to wait)\n");
+ return -1;
+ }
+ if (ast_moh_start(chan, NULL, NULL)) {
+ ast_log(LOG_WARNING, "Unable to start music on hold for %d seconds on channel %s\n", atoi(data), chan->name);
+ return 0;
+ }
+ res = ast_safe_sleep(chan, atoi(data) * 1000);
+ ast_moh_stop(chan);
+ return res;
+}
+
+static int moh2_exec(struct ast_channel *chan, void *data)
+{
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "SetMusicOnHold requires an argument (class)\n");
+ return -1;
+ }
+ ast_string_field_set(chan, musicclass, data);
+ return 0;
+}
+
+static int moh3_exec(struct ast_channel *chan, void *data)
+{
+ char *class = NULL;
+ if (data && strlen(data))
+ class = data;
+ if (ast_moh_start(chan, class, NULL))
+ ast_log(LOG_NOTICE, "Unable to start music on hold class '%s' on channel %s\n", class ? class : "default", chan->name);
+
+ return 0;
+}
+
+static int moh4_exec(struct ast_channel *chan, void *data)
+{
+ ast_moh_stop(chan);
+
+ return 0;
+}
+
+static struct mohclass *get_mohbyname(const char *name, int warn)
+{
+ struct mohclass *moh = NULL;
+ struct mohclass tmp_class = {
+ .flags = 0,
+ };
+
+ ast_copy_string(tmp_class.name, name, sizeof(tmp_class.name));
+
+ moh = ao2_find(mohclasses, &tmp_class, 0);
+
+ if (!moh && warn) {
+ ast_log(LOG_WARNING, "Music on Hold class '%s' not found\n", name);
+ }
+
+ return moh;
+}
+
+static struct mohdata *mohalloc(struct mohclass *cl)
+{
+ struct mohdata *moh;
+ long flags;
+
+ if (!(moh = ast_calloc(1, sizeof(*moh))))
+ return NULL;
+
+ if (pipe(moh->pipe)) {
+ ast_log(LOG_WARNING, "Failed to create pipe: %s\n", strerror(errno));
+ free(moh);
+ return NULL;
+ }
+
+ /* Make entirely non-blocking */
+ flags = fcntl(moh->pipe[0], F_GETFL);
+ fcntl(moh->pipe[0], F_SETFL, flags | O_NONBLOCK);
+ flags = fcntl(moh->pipe[1], F_GETFL);
+ fcntl(moh->pipe[1], F_SETFL, flags | O_NONBLOCK);
+
+ moh->f.frametype = AST_FRAME_VOICE;
+ moh->f.subclass = cl->format;
+ moh->f.offset = AST_FRIENDLY_OFFSET;
+
+ moh->parent = mohclass_ref(cl);
+
+ ao2_lock(cl);
+ AST_LIST_INSERT_HEAD(&cl->members, moh, list);
+ ao2_unlock(cl);
+
+ return moh;
+}
+
+static void moh_release(struct ast_channel *chan, void *data)
+{
+ struct mohdata *moh = data;
+ struct mohclass *class = moh->parent;
+ int oldwfmt;
+
+ ao2_lock(class);
+ AST_LIST_REMOVE(&moh->parent->members, moh, list);
+ ao2_unlock(class);
+
+ close(moh->pipe[0]);
+ close(moh->pipe[1]);
+
+ oldwfmt = moh->origwfmt;
+
+ moh->parent = class = mohclass_unref(class);
+
+ free(moh);
+
+ if (chan) {
+ if (oldwfmt && ast_set_write_format(chan, oldwfmt)) {
+ ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n",
+ chan->name, ast_getformatname(oldwfmt));
+ }
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_3 "Stopped music on hold on %s\n", chan->name);
+ }
+ }
+}
+
+static void *moh_alloc(struct ast_channel *chan, void *params)
+{
+ struct mohdata *res;
+ struct mohclass *class = params;
+
+ if ((res = mohalloc(class))) {
+ res->origwfmt = chan->writeformat;
+ if (ast_set_write_format(chan, class->format)) {
+ ast_log(LOG_WARNING, "Unable to set channel '%s' to format '%s'\n", chan->name, ast_codec2str(class->format));
+ moh_release(NULL, res);
+ res = NULL;
+ }
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on channel '%s'\n", class->name, chan->name);
+ }
+ return res;
+}
+
+static int moh_generate(struct ast_channel *chan, void *data, int len, int samples)
+{
+ struct mohdata *moh = data;
+ short buf[1280 + AST_FRIENDLY_OFFSET / 2];
+ int res;
+
+ len = ast_codec_get_len(moh->parent->format, samples);
+
+ if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) {
+ ast_log(LOG_WARNING, "Only doing %d of %d requested bytes on %s\n", (int)sizeof(buf), len, chan->name);
+ len = sizeof(buf) - AST_FRIENDLY_OFFSET;
+ }
+ res = read(moh->pipe[0], buf + AST_FRIENDLY_OFFSET/2, len);
+ if (res <= 0)
+ return 0;
+
+ moh->f.datalen = res;
+ moh->f.data = buf + AST_FRIENDLY_OFFSET / 2;
+ moh->f.samples = ast_codec_get_samples(&moh->f);
+
+ if (ast_write(chan, &moh->f) < 0) {
+ ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static struct ast_generator mohgen = {
+ .alloc = moh_alloc,
+ .release = moh_release,
+ .generate = moh_generate,
+};
+
+static int moh_add_file(struct mohclass *class, const char *filepath)
+{
+ if (!class->allowed_files) {
+ if (!(class->filearray = ast_calloc(1, INITIAL_NUM_FILES * sizeof(*class->filearray))))
+ return -1;
+ class->allowed_files = INITIAL_NUM_FILES;
+ } else if (class->total_files == class->allowed_files) {
+ if (!(class->filearray = ast_realloc(class->filearray, class->allowed_files * sizeof(*class->filearray) * 2))) {
+ class->allowed_files = 0;
+ class->total_files = 0;
+ return -1;
+ }
+ class->allowed_files *= 2;
+ }
+
+ if (!(class->filearray[class->total_files] = ast_strdup(filepath)))
+ return -1;
+
+ class->total_files++;
+
+ return 0;
+}
+
+static int moh_scan_files(struct mohclass *class) {
+
+ DIR *files_DIR;
+ struct dirent *files_dirent;
+ char path[PATH_MAX];
+ char filepath[PATH_MAX];
+ char *ext;
+ struct stat statbuf;
+ int dirnamelen;
+ int i;
+
+ files_DIR = opendir(class->dir);
+ if (!files_DIR) {
+ ast_log(LOG_WARNING, "Cannot open dir %s or dir does not exist\n", class->dir);
+ return -1;
+ }
+
+ for (i = 0; i < class->total_files; i++)
+ free(class->filearray[i]);
+
+ class->total_files = 0;
+ dirnamelen = strlen(class->dir) + 2;
+ if (!getcwd(path, sizeof(path))) {
+ ast_log(LOG_WARNING, "getcwd() failed: %s\n", strerror(errno));
+ return -1;
+ }
+ if (chdir(class->dir) < 0) {
+ ast_log(LOG_WARNING, "chdir() failed: %s\n", strerror(errno));
+ return -1;
+ }
+ while ((files_dirent = readdir(files_DIR))) {
+ /* The file name must be at least long enough to have the file type extension */
+ if ((strlen(files_dirent->d_name) < 4))
+ continue;
+
+ /* Skip files that starts with a dot */
+ if (files_dirent->d_name[0] == '.')
+ continue;
+
+ /* Skip files without extensions... they are not audio */
+ if (!strchr(files_dirent->d_name, '.'))
+ continue;
+
+ snprintf(filepath, sizeof(filepath), "%s/%s", class->dir, files_dirent->d_name);
+
+ if (stat(filepath, &statbuf))
+ continue;
+
+ if (!S_ISREG(statbuf.st_mode))
+ continue;
+
+ if ((ext = strrchr(filepath, '.'))) {
+ *ext = '\0';
+ ext++;
+ }
+
+ /* if the file is present in multiple formats, ensure we only put it into the list once */
+ for (i = 0; i < class->total_files; i++)
+ if (!strcmp(filepath, class->filearray[i]))
+ break;
+
+ if (i == class->total_files) {
+ if (moh_add_file(class, filepath))
+ break;
+ }
+ }
+
+ closedir(files_DIR);
+ if (chdir(path) < 0) {
+ ast_log(LOG_WARNING, "chdir() failed: %s\n", strerror(errno));
+ return -1;
+ }
+ return class->total_files;
+}
+
+static int init_files_class(struct mohclass *class)
+{
+ int res;
+
+ res = moh_scan_files(class);
+
+ if (res < 0) {
+ return -1;
+ }
+
+ if (!res) {
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_3 "Files not found in %s for moh class:%s\n",
+ class->dir, class->name);
+ }
+ return -1;
+ }
+
+ if (strchr(class->args, 'r')) {
+ ast_set_flag(class, MOH_RANDOMIZE);
+ }
+
+ return 0;
+}
+
+static int init_app_class(struct mohclass *class)
+{
+#ifdef HAVE_DAHDI
+ int x;
+#endif
+
+ if (!strcasecmp(class->mode, "custom")) {
+ ast_set_flag(class, MOH_CUSTOM);
+ } else if (!strcasecmp(class->mode, "mp3nb")) {
+ ast_set_flag(class, MOH_SINGLE);
+ } else if (!strcasecmp(class->mode, "quietmp3nb")) {
+ ast_set_flag(class, MOH_SINGLE | MOH_QUIET);
+ } else if (!strcasecmp(class->mode, "quietmp3")) {
+ ast_set_flag(class, MOH_QUIET);
+ }
+
+ class->srcfd = -1;
+ class->pseudofd = -1;
+
+#ifdef HAVE_DAHDI
+ /* Open /dev/zap/pseudo for timing... Is
+ there a better, yet reliable way to do this? */
+ class->pseudofd = open(DAHDI_FILE_PSEUDO, O_RDONLY);
+ if (class->pseudofd < 0) {
+ ast_log(LOG_WARNING, "Unable to open pseudo channel for timing... Sound may be choppy.\n");
+ } else {
+ x = 320;
+ ioctl(class->pseudofd, DAHDI_SET_BLOCKSIZE, &x);
+ }
+#endif
+
+ if (ast_pthread_create_background(&class->thread, NULL, monmp3thread, class)) {
+ ast_log(LOG_WARNING, "Unable to create moh thread...\n");
+ if (class->pseudofd > -1) {
+ close(class->pseudofd);
+ class->pseudofd = -1;
+ }
+ return -1;
+ }
+
+ return 0;
+}
+
+/*!
+ * \note This function owns the reference it gets to moh
+ */
+static int moh_register(struct mohclass *moh, int reload)
+{
+ struct mohclass *mohclass = NULL;
+
+ if ((mohclass = get_mohbyname(moh->name, 0))) {
+ if (!mohclass->delete) {
+ ast_log(LOG_WARNING, "Music on Hold class '%s' already exists\n", moh->name);
+ mohclass = mohclass_unref(mohclass);
+ moh = mohclass_unref(moh);
+ return -1;
+ }
+ mohclass = mohclass_unref(mohclass);
+ }
+
+ time(&moh->start);
+ moh->start -= respawn_time;
+
+ if (!strcasecmp(moh->mode, "files")) {
+ if (init_files_class(moh)) {
+ moh = mohclass_unref(moh);
+ return -1;
+ }
+ } else if (!strcasecmp(moh->mode, "mp3") || !strcasecmp(moh->mode, "mp3nb") ||
+ !strcasecmp(moh->mode, "quietmp3") || !strcasecmp(moh->mode, "quietmp3nb") ||
+ !strcasecmp(moh->mode, "httpmp3") || !strcasecmp(moh->mode, "custom")) {
+ if (init_app_class(moh)) {
+ moh = mohclass_unref(moh);
+ return -1;
+ }
+ } else {
+ ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", moh->mode);
+ moh = mohclass_unref(moh);
+ return -1;
+ }
+
+ ao2_link(mohclasses, moh);
+
+ moh = mohclass_unref(moh);
+
+ return 0;
+}
+
+static void local_ast_moh_cleanup(struct ast_channel *chan)
+{
+ if (chan->music_state) {
+ free(chan->music_state);
+ chan->music_state = NULL;
+ }
+}
+
+static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, const char *interpclass)
+{
+ struct mohclass *mohclass = NULL;
+ int res;
+
+ /* The following is the order of preference for which class to use:
+ * 1) The channels explicitly set musicclass, which should *only* be
+ * set by a call to Set(CHANNEL(musicclass)=whatever) in the dialplan.
+ * 2) The mclass argument. If a channel is calling ast_moh_start() as the
+ * result of receiving a HOLD control frame, this should be the
+ * payload that came with the frame.
+ * 3) The interpclass argument. This would be from the mohinterpret
+ * option from channel drivers. This is the same as the old musicclass
+ * option.
+ * 4) The default class.
+ */
+ if (!ast_strlen_zero(chan->musicclass)) {
+ mohclass = get_mohbyname(chan->musicclass, 1);
+ }
+ if (!mohclass && !ast_strlen_zero(mclass)) {
+ mohclass = get_mohbyname(mclass, 1);
+ }
+ if (!mohclass && !ast_strlen_zero(interpclass)) {
+ mohclass = get_mohbyname(interpclass, 1);
+ }
+ if (!mohclass) {
+ mohclass = get_mohbyname("default", 1);
+ }
+
+ if (!mohclass) {
+ return -1;
+ }
+
+ ast_set_flag(chan, AST_FLAG_MOH);
+
+ if (mohclass->total_files) {
+ res = ast_activate_generator(chan, &moh_file_stream, mohclass);
+ } else {
+ res = ast_activate_generator(chan, &mohgen, mohclass);
+ }
+
+ mohclass = mohclass_unref(mohclass);
+
+ return res;
+}
+
+static void local_ast_moh_stop(struct ast_channel *chan)
+{
+ ast_clear_flag(chan, AST_FLAG_MOH);
+ ast_deactivate_generator(chan);
+
+ if (chan->music_state) {
+ if (chan->stream) {
+ ast_closestream(chan->stream);
+ chan->stream = NULL;
+ }
+ }
+}
+
+static void moh_class_destructor(void *obj)
+{
+ struct mohclass *class = obj;
+ struct mohdata *member;
+
+ if (option_debug) {
+ ast_log(LOG_DEBUG, "Destroying MOH class '%s'\n", class->name);
+ }
+
+ if (class->pid > 1) {
+ char buff[8192];
+ int bytes, tbytes = 0, stime = 0, pid = 0;
+
+ ast_log(LOG_DEBUG, "killing %d!\n", class->pid);
+
+ stime = time(NULL) + 2;
+ pid = class->pid;
+ class->pid = 0;
+
+ /* Back when this was just mpg123, SIGKILL was fine. Now we need
+ * to give the process a reason and time enough to kill off its
+ * children. */
+ killpg(pid, SIGHUP);
+ usleep(100000);
+ killpg(pid, SIGTERM);
+ usleep(100000);
+ killpg(pid, SIGKILL);
+
+ while ((ast_wait_for_input(class->srcfd, 100) > 0) &&
+ (bytes = read(class->srcfd, buff, 8192)) && time(NULL) < stime) {
+ tbytes = tbytes + bytes;
+ }
+
+ ast_log(LOG_DEBUG, "mpg123 pid %d and child died after %d bytes read\n", pid, tbytes);
+
+ close(class->srcfd);
+ }
+
+ while ((member = AST_LIST_REMOVE_HEAD(&class->members, list))) {
+ free(member);
+ }
+
+ if (class->thread) {
+ pthread_cancel(class->thread);
+ class->thread = AST_PTHREADT_NULL;
+ }
+
+ if (class->filearray) {
+ int i;
+ for (i = 0; i < class->total_files; i++) {
+ free(class->filearray[i]);
+ }
+ free(class->filearray);
+ class->filearray = NULL;
+ }
+}
+
+static struct mohclass *moh_class_malloc(void)
+{
+ struct mohclass *class;
+
+ if ((class = ao2_alloc(sizeof(*class), moh_class_destructor))) {
+ class->format = AST_FORMAT_SLINEAR;
+ }
+
+ return class;
+}
+
+static int moh_class_mark(void *obj, void *arg, int flags)
+{
+ struct mohclass *class = obj;
+
+ class->delete = 1;
+
+ return 0;
+}
+
+static int moh_classes_delete_marked(void *obj, void *arg, int flags)
+{
+ struct mohclass *class = obj;
+
+ return class->delete ? CMP_MATCH : 0;
+}
+
+static int load_moh_classes(int reload)
+{
+ struct ast_config *cfg;
+ struct ast_variable *var;
+ struct mohclass *class;
+ char *data;
+ char *args;
+ char *cat;
+ int numclasses = 0;
+ static int dep_warning = 0;
+
+ cfg = ast_config_load("musiconhold.conf");
+
+ if (!cfg) {
+ return 0;
+ }
+
+ if (reload) {
+ ao2_callback(mohclasses, OBJ_NODATA, moh_class_mark, NULL);
+ }
+
+ cat = ast_category_browse(cfg, NULL);
+ for (; cat; cat = ast_category_browse(cfg, cat)) {
+ if (!strcasecmp(cat, "classes") || !strcasecmp(cat, "moh_files")) {
+ continue;
+ }
+
+ if (!(class = moh_class_malloc())) {
+ break;
+ }
+
+ ast_copy_string(class->name, cat, sizeof(class->name));
+
+ for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
+ if (!strcasecmp(var->name, "mode")) {
+ ast_copy_string(class->mode, var->value, sizeof(class->mode));
+ } else if (!strcasecmp(var->name, "directory")) {
+ ast_copy_string(class->dir, var->value, sizeof(class->dir));
+ } else if (!strcasecmp(var->name, "application")) {
+ ast_copy_string(class->args, var->value, sizeof(class->args));
+ } else if (!strcasecmp(var->name, "random")) {
+ ast_set2_flag(class, ast_true(var->value), MOH_RANDOMIZE);
+ } else if (!strcasecmp(var->name, "format")) {
+ class->format = ast_getformatbyname(var->value);
+ if (!class->format) {
+ ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", var->value);
+ class->format = AST_FORMAT_SLINEAR;
+ }
+ }
+ }
+
+ if (ast_strlen_zero(class->dir)) {
+ if (!strcasecmp(class->mode, "custom")) {
+ ast_copy_string(class->dir, "nodir", sizeof(class->dir));
+ } else {
+ ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", class->name);
+ class = mohclass_unref(class);
+ continue;
+ }
+ }
+
+ if (ast_strlen_zero(class->mode)) {
+ ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", class->name);
+ class = mohclass_unref(class);
+ continue;
+ }
+
+ if (ast_strlen_zero(class->args) && !strcasecmp(class->mode, "custom")) {
+ ast_log(LOG_WARNING, "An application must be specified for class '%s'!\n", class->name);
+ class = mohclass_unref(class);
+ continue;
+ }
+
+ /* Don't leak a class when it's already registered */
+ moh_register(class, reload);
+
+ numclasses++;
+ }
+
+
+ /* Deprecated Old-School Configuration */
+ for (var = ast_variable_browse(cfg, "classes"); var; var = var->next) {
+ struct mohclass *tmp_class;
+
+ if (!dep_warning) {
+ ast_log(LOG_WARNING, "The old musiconhold.conf syntax has been deprecated! Please refer to the sample configuration for information on the new syntax.\n");
+ dep_warning = 1;
+ }
+
+ if (!(data = strchr(var->value, ':'))) {
+ continue;
+ }
+ *data++ = '\0';
+
+ if ((args = strchr(data, ','))) {
+ *args++ = '\0';
+ }
+
+ if ((tmp_class = get_mohbyname(var->name, 0))) {
+ tmp_class = mohclass_unref(tmp_class);
+ continue;
+ }
+
+ if (!(class = moh_class_malloc())) {
+ break;
+ }
+
+ ast_copy_string(class->name, var->name, sizeof(class->name));
+ ast_copy_string(class->dir, data, sizeof(class->dir));
+ ast_copy_string(class->mode, var->value, sizeof(class->mode));
+ if (args) {
+ ast_copy_string(class->args, args, sizeof(class->args));
+ }
+
+ moh_register(class, reload);
+ class = NULL;
+
+ numclasses++;
+ }
+
+ for (var = ast_variable_browse(cfg, "moh_files"); var; var = var->next) {
+ struct mohclass *tmp_class;
+
+ if (!dep_warning) {
+ ast_log(LOG_WARNING, "The old musiconhold.conf syntax has been deprecated! Please refer to the sample configuration for information on the new syntax.\n");
+ dep_warning = 1;
+ }
+
+ if ((tmp_class = get_mohbyname(var->name, 0))) {
+ tmp_class = mohclass_unref(tmp_class);
+ continue;
+ }
+
+ if ((args = strchr(var->value, ','))) {
+ *args++ = '\0';
+ }
+
+ if (!(class = moh_class_malloc())) {
+ break;
+ }
+
+ ast_copy_string(class->name, var->name, sizeof(class->name));
+ ast_copy_string(class->dir, var->value, sizeof(class->dir));
+ ast_copy_string(class->mode, "files", sizeof(class->mode));
+ if (args) {
+ ast_copy_string(class->args, args, sizeof(class->args));
+ }
+
+ moh_register(class, reload);
+ class = NULL;
+
+ numclasses++;
+ }
+
+ ast_config_destroy(cfg);
+
+ ao2_callback(mohclasses, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE,
+ moh_classes_delete_marked, NULL);
+
+ return numclasses;
+}
+
+static void ast_moh_destroy(void)
+{
+ if (option_verbose > 1) {
+ ast_verbose(VERBOSE_PREFIX_2 "Destroying musiconhold processes\n");
+ }
+
+ ao2_callback(mohclasses, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL);
+}
+
+static int moh_cli(int fd, int argc, char *argv[])
+{
+ reload();
+ return 0;
+}
+
+static int cli_files_show(int fd, int argc, char *argv[])
+{
+ struct mohclass *class;
+ struct ao2_iterator i;
+
+ i = ao2_iterator_init(mohclasses, 0);
+
+ for (; (class = ao2_iterator_next(&i)); mohclass_unref(class)) {
+ int x;
+
+ if (!class->total_files) {
+ continue;
+ }
+
+ ast_cli(fd, "Class: %s\n", class->name);
+
+ for (x = 0; x < class->total_files; x++) {
+ ast_cli(fd, "\tFile: %s\n", class->filearray[x]);
+ }
+ }
+
+ return 0;
+}
+
+static int moh_classes_show(int fd, int argc, char *argv[])
+{
+ struct mohclass *class;
+ struct ao2_iterator i;
+
+ i = ao2_iterator_init(mohclasses, 0);
+
+ for (; (class = ao2_iterator_next(&i)); mohclass_unref(class)) {
+ ast_cli(fd, "Class: %s\n", class->name);
+ ast_cli(fd, "\tMode: %s\n", S_OR(class->mode, "<none>"));
+ ast_cli(fd, "\tDirectory: %s\n", S_OR(class->dir, "<none>"));
+ if (ast_test_flag(class, MOH_CUSTOM)) {
+ ast_cli(fd, "\tApplication: %s\n", S_OR(class->args, "<none>"));
+ }
+ if (strcasecmp(class->mode, "files")) {
+ ast_cli(fd, "\tFormat: %s\n", ast_getformatname(class->format));
+ }
+ }
+
+ return 0;
+}
+
+static struct ast_cli_entry cli_moh_classes_show_deprecated = {
+ { "moh", "classes", "show"},
+ moh_classes_show, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_moh_files_show_deprecated = {
+ { "moh", "files", "show"},
+ cli_files_show, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_moh[] = {
+ { { "moh", "reload"},
+ moh_cli, "Music On Hold",
+ "Usage: moh reload\n Rereads configuration\n" },
+
+ { { "moh", "show", "classes"},
+ moh_classes_show, "List MOH classes",
+ "Usage: moh show classes\n Lists all MOH classes\n", NULL, &cli_moh_classes_show_deprecated },
+
+ { { "moh", "show", "files"},
+ cli_files_show, "List MOH file-based classes",
+ "Usage: moh show files\n Lists all loaded file-based MOH classes and their files\n", NULL, &cli_moh_files_show_deprecated },
+};
+
+static int moh_class_hash(const void *obj, const int flags)
+{
+ const struct mohclass *class = obj;
+
+ return ast_str_case_hash(class->name);
+}
+
+static int moh_class_cmp(void *obj, void *arg, int flags)
+{
+ struct mohclass *class = obj, *class2 = arg;
+
+ return strcasecmp(class->name, class2->name) ? 0 : CMP_MATCH | CMP_STOP;
+}
+
+static int load_module(void)
+{
+ int res;
+
+ if (!(mohclasses = ao2_container_alloc(53, moh_class_hash, moh_class_cmp))) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ if (!load_moh_classes(0)) { /* No music classes configured, so skip it */
+ ast_log(LOG_WARNING, "No music on hold classes configured, "
+ "disabling music on hold.\n");
+ } else {
+ ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop,
+ local_ast_moh_cleanup);
+ }
+
+ res = ast_register_application(app0, moh0_exec, synopsis0, descrip0);
+ ast_register_atexit(ast_moh_destroy);
+ ast_cli_register_multiple(cli_moh, ARRAY_LEN(cli_moh));
+ if (!res)
+ res = ast_register_application(app1, moh1_exec, synopsis1, descrip1);
+ if (!res)
+ res = ast_register_application(app2, moh2_exec, synopsis2, descrip2);
+ if (!res)
+ res = ast_register_application(app3, moh3_exec, synopsis3, descrip3);
+ if (!res)
+ res = ast_register_application(app4, moh4_exec, synopsis4, descrip4);
+
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int reload(void)
+{
+ if (load_moh_classes(1)) {
+ ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop,
+ local_ast_moh_cleanup);
+ }
+
+ return 0;
+}
+
+static int moh_class_inuse(void *obj, void *arg, int flags)
+{
+ struct mohclass *class = obj;
+
+ return AST_LIST_EMPTY(&class->members) ? 0 : CMP_MATCH | CMP_STOP;
+}
+
+static int unload_module(void)
+{
+ int res = 0;
+ struct mohclass *class = NULL;
+
+ /* XXX This check shouldn't be required if module ref counting was being used
+ * properly ... */
+ if ((class = ao2_callback(mohclasses, 0, moh_class_inuse, NULL))) {
+ class = mohclass_unref(class);
+ res = -1;
+ }
+
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to unload res_musiconhold due to active MOH channels\n");
+ return res;
+ }
+
+ ast_uninstall_music_functions();
+
+ ast_moh_destroy();
+
+ res = ast_unregister_application(app0);
+ res |= ast_unregister_application(app1);
+ res |= ast_unregister_application(app2);
+ res |= ast_unregister_application(app3);
+ res |= ast_unregister_application(app4);
+
+ ast_cli_unregister_multiple(cli_moh, ARRAY_LEN(cli_moh));
+
+ return res;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Music On Hold Resource",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+);
diff --git a/1.4.23-rc4/res/res_odbc.c b/1.4.23-rc4/res/res_odbc.c
new file mode 100644
index 000000000..b0fed02c9
--- /dev/null
+++ b/1.4.23-rc4/res/res_odbc.c
@@ -0,0 +1,741 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * res_odbc.c <ODBC resource manager>
+ * Copyright (C) 2004 - 2005 Anthony Minessale II <anthmct@yahoo.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief ODBC resource manager
+ *
+ * \author Mark Spencer <markster@digium.com>
+ * \author Anthony Minessale II <anthmct@yahoo.com>
+ *
+ * \arg See also: \ref cdr_odbc
+ */
+
+/*** MODULEINFO
+ <depend>unixodbc</depend>
+ <depend>ltdl</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/options.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/cli.h"
+#include "asterisk/lock.h"
+#include "asterisk/res_odbc.h"
+#include "asterisk/time.h"
+
+struct odbc_class
+{
+ AST_LIST_ENTRY(odbc_class) list;
+ char name[80];
+ char dsn[80];
+ char username[80];
+ char password[80];
+ SQLHENV env;
+ unsigned int haspool:1; /* Boolean - TDS databases need this */
+ unsigned int limit:10; /* Gives a limit of 1023 maximum */
+ unsigned int count:10; /* Running count of pooled connections */
+ unsigned int delme:1; /* Purge the class */
+ unsigned int backslash_is_escape:1; /* On this database, the backslash is a native escape sequence */
+ unsigned int idlecheck; /* Recheck the connection if it is idle for this long */
+ AST_LIST_HEAD(, odbc_obj) odbc_obj;
+};
+
+AST_LIST_HEAD_STATIC(odbc_list, odbc_class);
+
+static odbc_status odbc_obj_connect(struct odbc_obj *obj);
+static odbc_status odbc_obj_disconnect(struct odbc_obj *obj);
+static int odbc_register_class(struct odbc_class *class, int connect);
+
+
+SQLHSTMT ast_odbc_prepare_and_execute(struct odbc_obj *obj, SQLHSTMT (*prepare_cb)(struct odbc_obj *obj, void *data), void *data)
+{
+ int res = 0, i, attempt;
+ SQLINTEGER nativeerror=0, numfields=0;
+ SQLSMALLINT diagbytes=0;
+ unsigned char state[10], diagnostic[256];
+ SQLHSTMT stmt;
+
+ for (attempt = 0; attempt < 2; attempt++) {
+ /* This prepare callback may do more than just prepare -- it may also
+ * bind parameters, bind results, etc. The real key, here, is that
+ * when we disconnect, all handles become invalid for most databases.
+ * We must therefore redo everything when we establish a new
+ * connection. */
+ stmt = prepare_cb(obj, data);
+
+ if (stmt) {
+ res = SQLExecute(stmt);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO) && (res != SQL_NO_DATA)) {
+ if (res == SQL_ERROR) {
+ SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
+ for (i = 0; i < numfields; i++) {
+ SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
+ ast_log(LOG_WARNING, "SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes);
+ if (i > 10) {
+ ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
+ break;
+ }
+ }
+ }
+
+ ast_log(LOG_WARNING, "SQL Execute error %d! Attempting a reconnect...\n", res);
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ stmt = NULL;
+
+ obj->up = 0;
+ /*
+ * While this isn't the best way to try to correct an error, this won't automatically
+ * fail when the statement handle invalidates.
+ */
+ /* XXX Actually, it might, if we're using a non-pooled connection. Possible race here. XXX */
+ odbc_obj_disconnect(obj);
+ odbc_obj_connect(obj);
+ continue;
+ } else
+ obj->last_used = ast_tvnow();
+ break;
+ } else {
+ ast_log(LOG_WARNING, "SQL Prepare failed. Attempting a reconnect...\n");
+ odbc_obj_disconnect(obj);
+ odbc_obj_connect(obj);
+ }
+ }
+
+ return stmt;
+}
+
+int ast_odbc_smart_execute(struct odbc_obj *obj, SQLHSTMT stmt)
+{
+ int res = 0, i;
+ SQLINTEGER nativeerror=0, numfields=0;
+ SQLSMALLINT diagbytes=0;
+ unsigned char state[10], diagnostic[256];
+
+ res = SQLExecute(stmt);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO) && (res != SQL_NO_DATA)) {
+ if (res == SQL_ERROR) {
+ SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
+ for (i = 0; i < numfields; i++) {
+ SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
+ ast_log(LOG_WARNING, "SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes);
+ if (i > 10) {
+ ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields);
+ break;
+ }
+ }
+ }
+#if 0
+ /* This is a really bad method of trying to correct a dead connection. It
+ * only ever really worked with MySQL. It will not work with any other
+ * database, since most databases prepare their statements on the server,
+ * and if you disconnect, you invalidate the statement handle. Hence, if
+ * you disconnect, you're going to fail anyway, whether you try to execute
+ * a second time or not.
+ */
+ ast_log(LOG_WARNING, "SQL Execute error %d! Attempting a reconnect...\n", res);
+ ast_mutex_lock(&obj->lock);
+ obj->up = 0;
+ ast_mutex_unlock(&obj->lock);
+ odbc_obj_disconnect(obj);
+ odbc_obj_connect(obj);
+ res = SQLExecute(stmt);
+#endif
+ } else
+ obj->last_used = ast_tvnow();
+
+ return res;
+}
+
+
+int ast_odbc_sanity_check(struct odbc_obj *obj)
+{
+ char *test_sql = "select 1";
+ SQLHSTMT stmt;
+ int res = 0;
+
+ if (obj->up) {
+ res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ obj->up = 0;
+ } else {
+ res = SQLPrepare(stmt, (unsigned char *)test_sql, SQL_NTS);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ obj->up = 0;
+ } else {
+ res = SQLExecute(stmt);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ obj->up = 0;
+ }
+ }
+ }
+ SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+ }
+
+ if (!obj->up) { /* Try to reconnect! */
+ ast_log(LOG_WARNING, "Connection is down attempting to reconnect...\n");
+ odbc_obj_disconnect(obj);
+ odbc_obj_connect(obj);
+ }
+ return obj->up;
+}
+
+static int load_odbc_config(void)
+{
+ static char *cfg = "res_odbc.conf";
+ struct ast_config *config;
+ struct ast_variable *v;
+ char *cat, *dsn, *username, *password;
+ int enabled, pooling, limit, bse;
+ unsigned int idlecheck;
+ int connect = 0, res = 0;
+
+ struct odbc_class *new;
+
+ config = ast_config_load(cfg);
+ if (!config) {
+ ast_log(LOG_WARNING, "Unable to load config file res_odbc.conf\n");
+ return -1;
+ }
+ for (cat = ast_category_browse(config, NULL); cat; cat=ast_category_browse(config, cat)) {
+ if (!strcasecmp(cat, "ENV")) {
+ for (v = ast_variable_browse(config, cat); v; v = v->next) {
+ setenv(v->name, v->value, 1);
+ ast_log(LOG_NOTICE, "Adding ENV var: %s=%s\n", v->name, v->value);
+ }
+ } else {
+ /* Reset all to defaults for each class of odbc connections */
+ dsn = username = password = NULL;
+ enabled = 1;
+ connect = idlecheck = 0;
+ pooling = 0;
+ limit = 0;
+ bse = 1;
+ for (v = ast_variable_browse(config, cat); v; v = v->next) {
+ if (!strcasecmp(v->name, "pooling")) {
+ if (ast_true(v->value))
+ pooling = 1;
+ } else if (!strcasecmp(v->name, "limit")) {
+ sscanf(v->value, "%d", &limit);
+ if (ast_true(v->value) && !limit) {
+ ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'. Setting limit to 1023 for ODBC class '%s'.\n", v->value, cat);
+ limit = 1023;
+ } else if (ast_false(v->value)) {
+ ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'. Disabling ODBC class '%s'.\n", v->value, cat);
+ enabled = 0;
+ break;
+ }
+ } else if (!strcasecmp(v->name, "idlecheck")) {
+ sscanf(v->value, "%d", &idlecheck);
+ } else if (!strcasecmp(v->name, "enabled")) {
+ enabled = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "pre-connect")) {
+ connect = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "dsn")) {
+ dsn = v->value;
+ } else if (!strcasecmp(v->name, "username")) {
+ username = v->value;
+ } else if (!strcasecmp(v->name, "password")) {
+ password = v->value;
+ } else if (!strcasecmp(v->name, "backslash_is_escape")) {
+ bse = ast_true(v->value);
+ }
+ }
+
+ if (enabled && !ast_strlen_zero(dsn)) {
+ new = ast_calloc(1, sizeof(*new));
+
+ if (!new) {
+ res = -1;
+ break;
+ }
+
+ if (cat)
+ ast_copy_string(new->name, cat, sizeof(new->name));
+ if (dsn)
+ ast_copy_string(new->dsn, dsn, sizeof(new->dsn));
+ if (username)
+ ast_copy_string(new->username, username, sizeof(new->username));
+ if (password)
+ ast_copy_string(new->password, password, sizeof(new->password));
+
+ SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &new->env);
+ res = SQLSetEnvAttr(new->env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
+
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "res_odbc: Error SetEnv\n");
+ SQLFreeHandle(SQL_HANDLE_ENV, new->env);
+ return res;
+ }
+
+ if (pooling) {
+ new->haspool = pooling;
+ if (limit) {
+ new->limit = limit;
+ } else {
+ ast_log(LOG_WARNING, "Pooling without also setting a limit is pointless. Changing limit from 0 to 5.\n");
+ new->limit = 5;
+ }
+ }
+
+ new->backslash_is_escape = bse ? 1 : 0;
+ new->idlecheck = idlecheck;
+
+ odbc_register_class(new, connect);
+ ast_log(LOG_NOTICE, "Registered ODBC class '%s' dsn->[%s]\n", cat, dsn);
+ }
+ }
+ }
+ ast_config_destroy(config);
+ return res;
+}
+
+static int odbc_show_command(int fd, int argc, char **argv)
+{
+ struct odbc_class *class;
+ struct odbc_obj *current;
+
+ AST_LIST_LOCK(&odbc_list);
+ AST_LIST_TRAVERSE(&odbc_list, class, list) {
+ if ((argc == 2) || (argc == 3 && !strcmp(argv[2], "all")) || (!strcmp(argv[2], class->name))) {
+ int count = 0;
+ ast_cli(fd, "Name: %s\nDSN: %s\n", class->name, class->dsn);
+
+ if (class->haspool) {
+ ast_cli(fd, "Pooled: yes\nLimit: %d\nConnections in use: %d\n", class->limit, class->count);
+
+ AST_LIST_TRAVERSE(&(class->odbc_obj), current, list) {
+ ast_cli(fd, " Connection %d: %s\n", ++count, current->used ? "in use" : current->up && ast_odbc_sanity_check(current) ? "connected" : "disconnected");
+ }
+ } else {
+ /* Should only ever be one of these */
+ AST_LIST_TRAVERSE(&(class->odbc_obj), current, list) {
+ ast_cli(fd, "Pooled: no\nConnected: %s\n", current->up && ast_odbc_sanity_check(current) ? "yes" : "no");
+ }
+ }
+
+ ast_cli(fd, "\n");
+ }
+ }
+ AST_LIST_UNLOCK(&odbc_list);
+
+ return 0;
+}
+
+static char show_usage[] =
+"Usage: odbc show [<class>]\n"
+" List settings of a particular ODBC class.\n"
+" or, if not specified, all classes.\n";
+
+static struct ast_cli_entry cli_odbc[] = {
+ { { "odbc", "show", NULL },
+ odbc_show_command, "List ODBC DSN(s)",
+ show_usage },
+};
+
+static int odbc_register_class(struct odbc_class *class, int connect)
+{
+ struct odbc_obj *obj;
+ if (class) {
+ AST_LIST_LOCK(&odbc_list);
+ AST_LIST_INSERT_HEAD(&odbc_list, class, list);
+ AST_LIST_UNLOCK(&odbc_list);
+
+ if (connect) {
+ /* Request and release builds a connection */
+ obj = ast_odbc_request_obj(class->name, 0);
+ if (obj)
+ ast_odbc_release_obj(obj);
+ }
+
+ return 0;
+ } else {
+ ast_log(LOG_WARNING, "Attempted to register a NULL class?\n");
+ return -1;
+ }
+}
+
+void ast_odbc_release_obj(struct odbc_obj *obj)
+{
+ /* For pooled connections, this frees the connection to be
+ * reused. For non-pooled connections, it does nothing. */
+ obj->used = 0;
+}
+
+int ast_odbc_backslash_is_escape(struct odbc_obj *obj)
+{
+ return obj->parent->backslash_is_escape;
+}
+
+struct odbc_obj *ast_odbc_request_obj(const char *name, int check)
+{
+ struct odbc_obj *obj = NULL;
+ struct odbc_class *class;
+
+ AST_LIST_LOCK(&odbc_list);
+ AST_LIST_TRAVERSE(&odbc_list, class, list) {
+ if (!strcmp(class->name, name))
+ break;
+ }
+ AST_LIST_UNLOCK(&odbc_list);
+
+ if (!class)
+ return NULL;
+
+ AST_LIST_LOCK(&class->odbc_obj);
+ if (class->haspool) {
+ /* Recycle connections before building another */
+ AST_LIST_TRAVERSE(&class->odbc_obj, obj, list) {
+ if (! obj->used) {
+ obj->used = 1;
+ break;
+ }
+ }
+
+ if (!obj && (class->count < class->limit)) {
+ class->count++;
+ obj = ast_calloc(1, sizeof(*obj));
+ if (!obj) {
+ AST_LIST_UNLOCK(&class->odbc_obj);
+ return NULL;
+ }
+ ast_mutex_init(&obj->lock);
+ obj->parent = class;
+ if (odbc_obj_connect(obj) == ODBC_FAIL) {
+ ast_log(LOG_WARNING, "Failed to connect to %s\n", name);
+ ast_mutex_destroy(&obj->lock);
+ free(obj);
+ obj = NULL;
+ class->count--;
+ } else {
+ obj->used = 1;
+ AST_LIST_INSERT_TAIL(&class->odbc_obj, obj, list);
+ }
+ }
+ } else {
+ /* Non-pooled connection: multiple modules can use the same connection. */
+ AST_LIST_TRAVERSE(&class->odbc_obj, obj, list) {
+ /* Non-pooled connection: if there is an entry, return it */
+ break;
+ }
+
+ if (!obj) {
+ /* No entry: build one */
+ obj = ast_calloc(1, sizeof(*obj));
+ if (!obj) {
+ AST_LIST_UNLOCK(&class->odbc_obj);
+ return NULL;
+ }
+ ast_mutex_init(&obj->lock);
+ obj->parent = class;
+ if (odbc_obj_connect(obj) == ODBC_FAIL) {
+ ast_log(LOG_WARNING, "Failed to connect to %s\n", name);
+ ast_mutex_destroy(&obj->lock);
+ free(obj);
+ obj = NULL;
+ } else {
+ AST_LIST_INSERT_HEAD(&class->odbc_obj, obj, list);
+ }
+ }
+ }
+ AST_LIST_UNLOCK(&class->odbc_obj);
+
+ if (obj && check) {
+ ast_odbc_sanity_check(obj);
+ } else if (obj && obj->parent->idlecheck > 0 && ast_tvdiff_ms(ast_tvnow(), obj->last_used) / 1000 > obj->parent->idlecheck)
+ odbc_obj_connect(obj);
+
+ return obj;
+}
+
+static odbc_status odbc_obj_disconnect(struct odbc_obj *obj)
+{
+ int res;
+ SQLINTEGER err;
+ short int mlen;
+ unsigned char msg[200], stat[10];
+
+ /* Nothing to disconnect */
+ if (!obj->con) {
+ return ODBC_SUCCESS;
+ }
+
+ ast_mutex_lock(&obj->lock);
+
+ res = SQLDisconnect(obj->con);
+
+ if (res == SQL_SUCCESS || res == SQL_SUCCESS_WITH_INFO) {
+ ast_log(LOG_DEBUG, "Disconnected %d from %s [%s]\n", res, obj->parent->name, obj->parent->dsn);
+ } else {
+ ast_log(LOG_DEBUG, "res_odbc: %s [%s] already disconnected\n", obj->parent->name, obj->parent->dsn);
+ }
+
+ if ((res = SQLFreeHandle(SQL_HANDLE_DBC, obj->con) == SQL_SUCCESS)) {
+ obj->con = NULL;
+ ast_log(LOG_DEBUG, "Database handle deallocated\n");
+ } else {
+ SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, 1, stat, &err, msg, 100, &mlen);
+ ast_log(LOG_WARNING, "Unable to deallocate database handle? %d errno=%d %s\n", res, (int)err, msg);
+ }
+
+ obj->up = 0;
+ ast_mutex_unlock(&obj->lock);
+ return ODBC_SUCCESS;
+}
+
+static odbc_status odbc_obj_connect(struct odbc_obj *obj)
+{
+ int res;
+ SQLINTEGER err;
+ short int mlen;
+ unsigned char msg[200], stat[10];
+#ifdef NEEDTRACE
+ SQLINTEGER enable = 1;
+ char *tracefile = "/tmp/odbc.trace";
+#endif
+ ast_mutex_lock(&obj->lock);
+
+ if (obj->up) {
+ odbc_obj_disconnect(obj);
+ ast_log(LOG_NOTICE, "Re-connecting %s\n", obj->parent->name);
+ } else {
+ ast_log(LOG_NOTICE, "Connecting %s\n", obj->parent->name);
+ }
+
+ res = SQLAllocHandle(SQL_HANDLE_DBC, obj->parent->env, &obj->con);
+
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "res_odbc: Error AllocHDB %d\n", res);
+ ast_mutex_unlock(&obj->lock);
+ return ODBC_FAIL;
+ }
+ SQLSetConnectAttr(obj->con, SQL_LOGIN_TIMEOUT, (SQLPOINTER *) 10, 0);
+ SQLSetConnectAttr(obj->con, SQL_ATTR_CONNECTION_TIMEOUT, (SQLPOINTER *) 10, 0);
+#ifdef NEEDTRACE
+ SQLSetConnectAttr(obj->con, SQL_ATTR_TRACE, &enable, SQL_IS_INTEGER);
+ SQLSetConnectAttr(obj->con, SQL_ATTR_TRACEFILE, tracefile, strlen(tracefile));
+#endif
+
+ res = SQLConnect(obj->con,
+ (SQLCHAR *) obj->parent->dsn, SQL_NTS,
+ (SQLCHAR *) obj->parent->username, SQL_NTS,
+ (SQLCHAR *) obj->parent->password, SQL_NTS);
+
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, 1, stat, &err, msg, 100, &mlen);
+ ast_mutex_unlock(&obj->lock);
+ ast_log(LOG_WARNING, "res_odbc: Error SQLConnect=%d errno=%d %s\n", res, (int)err, msg);
+ return ODBC_FAIL;
+ } else {
+ ast_log(LOG_NOTICE, "res_odbc: Connected to %s [%s]\n", obj->parent->name, obj->parent->dsn);
+ obj->up = 1;
+ obj->last_used = ast_tvnow();
+ }
+
+ ast_mutex_unlock(&obj->lock);
+ return ODBC_SUCCESS;
+}
+
+static int reload(void)
+{
+ static char *cfg = "res_odbc.conf";
+ struct ast_config *config;
+ struct ast_variable *v;
+ char *cat, *dsn, *username, *password;
+ int enabled, pooling, limit, bse;
+ unsigned int idlecheck;
+ int connect = 0, res = 0;
+
+ struct odbc_class *new, *class;
+ struct odbc_obj *current;
+
+ /* First, mark all to be purged */
+ AST_LIST_LOCK(&odbc_list);
+ AST_LIST_TRAVERSE(&odbc_list, class, list) {
+ class->delme = 1;
+ }
+
+ config = ast_config_load(cfg);
+ if (config) {
+ for (cat = ast_category_browse(config, NULL); cat; cat=ast_category_browse(config, cat)) {
+ if (!strcasecmp(cat, "ENV")) {
+ for (v = ast_variable_browse(config, cat); v; v = v->next) {
+ setenv(v->name, v->value, 1);
+ ast_log(LOG_NOTICE, "Adding ENV var: %s=%s\n", v->name, v->value);
+ }
+ } else {
+ /* Reset all to defaults for each class of odbc connections */
+ dsn = username = password = NULL;
+ enabled = 1;
+ connect = idlecheck = 0;
+ pooling = 0;
+ limit = 0;
+ bse = 1;
+ for (v = ast_variable_browse(config, cat); v; v = v->next) {
+ if (!strcasecmp(v->name, "pooling")) {
+ pooling = 1;
+ } else if (!strcasecmp(v->name, "limit")) {
+ sscanf(v->value, "%d", &limit);
+ if (ast_true(v->value) && !limit) {
+ ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'. Setting limit to 1023 for ODBC class '%s'.\n", v->value, cat);
+ limit = 1023;
+ } else if (ast_false(v->value)) {
+ ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'. Disabling ODBC class '%s'.\n", v->value, cat);
+ enabled = 0;
+ break;
+ }
+ } else if (!strcasecmp(v->name, "idlecheck")) {
+ sscanf(v->value, "%ud", &idlecheck);
+ } else if (!strcasecmp(v->name, "enabled")) {
+ enabled = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "pre-connect")) {
+ connect = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "dsn")) {
+ dsn = v->value;
+ } else if (!strcasecmp(v->name, "username")) {
+ username = v->value;
+ } else if (!strcasecmp(v->name, "password")) {
+ password = v->value;
+ } else if (!strcasecmp(v->name, "backslash_is_escape")) {
+ bse = ast_true(v->value);
+ }
+ }
+
+ if (enabled && !ast_strlen_zero(dsn)) {
+ /* First, check the list to see if it already exists */
+ AST_LIST_TRAVERSE(&odbc_list, class, list) {
+ if (!strcmp(class->name, cat)) {
+ class->delme = 0;
+ break;
+ }
+ }
+
+ if (class) {
+ new = class;
+ } else {
+ new = ast_calloc(1, sizeof(*new));
+ }
+
+ if (!new) {
+ res = -1;
+ break;
+ }
+
+ if (cat)
+ ast_copy_string(new->name, cat, sizeof(new->name));
+ if (dsn)
+ ast_copy_string(new->dsn, dsn, sizeof(new->dsn));
+ if (username)
+ ast_copy_string(new->username, username, sizeof(new->username));
+ if (password)
+ ast_copy_string(new->password, password, sizeof(new->password));
+
+ if (!class) {
+ SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &new->env);
+ res = SQLSetEnvAttr(new->env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
+
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "res_odbc: Error SetEnv\n");
+ SQLFreeHandle(SQL_HANDLE_ENV, new->env);
+ AST_LIST_UNLOCK(&odbc_list);
+ return res;
+ }
+ }
+
+ if (pooling) {
+ new->haspool = pooling;
+ if (limit) {
+ new->limit = limit;
+ } else {
+ ast_log(LOG_WARNING, "Pooling without also setting a limit is pointless. Changing limit from 0 to 5.\n");
+ new->limit = 5;
+ }
+ }
+
+ new->backslash_is_escape = bse;
+ new->idlecheck = idlecheck;
+
+ if (class) {
+ ast_log(LOG_NOTICE, "Refreshing ODBC class '%s' dsn->[%s]\n", cat, dsn);
+ } else {
+ odbc_register_class(new, connect);
+ ast_log(LOG_NOTICE, "Registered ODBC class '%s' dsn->[%s]\n", cat, dsn);
+ }
+ }
+ }
+ }
+ ast_config_destroy(config);
+ }
+
+ /* Purge classes that we know can go away (pooled with 0, only) */
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&odbc_list, class, list) {
+ if (class->delme && class->haspool && class->count == 0) {
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&(class->odbc_obj), current, list) {
+ AST_LIST_REMOVE_CURRENT(&(class->odbc_obj), list);
+ odbc_obj_disconnect(current);
+ ast_mutex_destroy(&current->lock);
+ free(current);
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+
+ AST_LIST_REMOVE_CURRENT(&odbc_list, list);
+ free(class);
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+ AST_LIST_UNLOCK(&odbc_list);
+
+ return 0;
+}
+
+static int unload_module(void)
+{
+ /* Prohibit unloading */
+ return -1;
+}
+
+static int load_module(void)
+{
+ if(load_odbc_config() == -1)
+ return AST_MODULE_LOAD_DECLINE;
+ ast_cli_register_multiple(cli_odbc, sizeof(cli_odbc) / sizeof(struct ast_cli_entry));
+ ast_log(LOG_NOTICE, "res_odbc loaded.\n");
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "ODBC Resource",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
diff --git a/1.4.23-rc4/res/res_smdi.c b/1.4.23-rc4/res/res_smdi.c
new file mode 100644
index 000000000..406a740c9
--- /dev/null
+++ b/1.4.23-rc4/res/res_smdi.c
@@ -0,0 +1,1384 @@
+/*
+ * Asterisk -- A telephony toolkit for Linux.
+ *
+ * Copyright (C) 2005-2008, Digium, Inc.
+ *
+ * Matthew A. Nicholson <mnicholson@digium.com>
+ * Russell Bryant <russell@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief SMDI support for Asterisk.
+ * \author Matthew A. Nicholson <mnicholson@digium.com>
+ * \author Russell Bryant <russell@digium.com>
+ *
+ * Here is a useful mailing list post that describes SMDI protocol details:
+ * \ref http://lists.digium.com/pipermail/asterisk-dev/2003-June/000884.html
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <termios.h>
+#include <sys/time.h>
+#include <time.h>
+#include <ctype.h>
+
+#include "asterisk/module.h"
+#include "asterisk/lock.h"
+#include "asterisk/utils.h"
+#include "asterisk/smdi.h"
+#include "asterisk/config.h"
+#include "asterisk/astobj.h"
+#include "asterisk/io.h"
+#include "asterisk/logger.h"
+#include "asterisk/utils.h"
+#include "asterisk/options.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/app.h"
+#include "asterisk/pbx.h"
+
+/* Message expiry time in milliseconds */
+#define SMDI_MSG_EXPIRY_TIME 30000 /* 30 seconds */
+
+static const char config_file[] = "smdi.conf";
+
+/*! \brief SMDI message desk message queue. */
+struct ast_smdi_md_queue {
+ ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_md_message);
+};
+
+/*! \brief SMDI message waiting indicator message queue. */
+struct ast_smdi_mwi_queue {
+ ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_mwi_message);
+};
+
+struct ast_smdi_interface {
+ ASTOBJ_COMPONENTS_FULL(struct ast_smdi_interface, SMDI_MAX_FILENAME_LEN, 1);
+ struct ast_smdi_md_queue md_q;
+ ast_mutex_t md_q_lock;
+ ast_cond_t md_q_cond;
+ struct ast_smdi_mwi_queue mwi_q;
+ ast_mutex_t mwi_q_lock;
+ ast_cond_t mwi_q_cond;
+ FILE *file;
+ int fd;
+ pthread_t thread;
+ struct termios mode;
+ int msdstrip;
+ long msg_expiry;
+};
+
+/*! \brief SMDI interface container. */
+struct ast_smdi_interface_container {
+ ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_interface);
+} smdi_ifaces;
+
+/*! \brief A mapping between an SMDI mailbox ID and an Asterisk mailbox */
+struct mailbox_mapping {
+ /*! This is the current state of the mailbox. It is simply on or
+ * off to indicate if there are messages waiting or not. */
+ unsigned int cur_state:1;
+ /*! A Pointer to the appropriate SMDI interface */
+ struct ast_smdi_interface *iface;
+ AST_DECLARE_STRING_FIELDS(
+ /*! The Name of the mailbox for the SMDI link. */
+ AST_STRING_FIELD(smdi);
+ /*! The name of the mailbox on the Asterisk side */
+ AST_STRING_FIELD(mailbox);
+ /*! The name of the voicemail context in use */
+ AST_STRING_FIELD(context);
+ );
+ AST_LIST_ENTRY(mailbox_mapping) entry;
+};
+
+/*! 10 seconds */
+#define DEFAULT_POLLING_INTERVAL 10
+
+/*! \brief Data that gets used by the SMDI MWI monitoring thread */
+static struct {
+ /*! The thread ID */
+ pthread_t thread;
+ ast_mutex_t lock;
+ ast_cond_t cond;
+ /*! A list of mailboxes that need to be monitored */
+ AST_LIST_HEAD_NOLOCK(, mailbox_mapping) mailbox_mappings;
+ /*! Polling Interval for checking mailbox status */
+ unsigned int polling_interval;
+ /*! Set to 1 to tell the polling thread to stop */
+ unsigned int stop:1;
+ /*! The time that the last poll began */
+ struct timeval last_poll;
+} mwi_monitor = {
+ .thread = AST_PTHREADT_NULL,
+};
+
+static void ast_smdi_interface_destroy(struct ast_smdi_interface *iface)
+{
+ if (iface->thread != AST_PTHREADT_NULL && iface->thread != AST_PTHREADT_STOP) {
+ pthread_cancel(iface->thread);
+ pthread_join(iface->thread, NULL);
+ }
+
+ iface->thread = AST_PTHREADT_STOP;
+
+ if (iface->file)
+ fclose(iface->file);
+
+ ASTOBJ_CONTAINER_DESTROYALL(&iface->md_q, ast_smdi_md_message_destroy);
+ ASTOBJ_CONTAINER_DESTROYALL(&iface->mwi_q, ast_smdi_mwi_message_destroy);
+ ASTOBJ_CONTAINER_DESTROY(&iface->md_q);
+ ASTOBJ_CONTAINER_DESTROY(&iface->mwi_q);
+
+ ast_mutex_destroy(&iface->md_q_lock);
+ ast_cond_destroy(&iface->md_q_cond);
+
+ ast_mutex_destroy(&iface->mwi_q_lock);
+ ast_cond_destroy(&iface->mwi_q_cond);
+
+ free(iface);
+
+ ast_module_unref(ast_module_info->self);
+}
+
+void ast_smdi_interface_unref(struct ast_smdi_interface *iface)
+{
+ ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
+}
+
+/*!
+ * \internal
+ * \brief Push an SMDI message to the back of an interface's message queue.
+ * \param iface a pointer to the interface to use.
+ * \param md_msg a pointer to the message to use.
+ */
+static void ast_smdi_md_message_push(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
+{
+ ast_mutex_lock(&iface->md_q_lock);
+ ASTOBJ_CONTAINER_LINK_END(&iface->md_q, md_msg);
+ ast_cond_broadcast(&iface->md_q_cond);
+ ast_mutex_unlock(&iface->md_q_lock);
+}
+
+/*!
+ * \internal
+ * \brief Push an SMDI message to the back of an interface's message queue.
+ * \param iface a pointer to the interface to use.
+ * \param mwi_msg a pointer to the message to use.
+ */
+static void ast_smdi_mwi_message_push(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
+{
+ ast_mutex_lock(&iface->mwi_q_lock);
+ ASTOBJ_CONTAINER_LINK_END(&iface->mwi_q, mwi_msg);
+ ast_cond_broadcast(&iface->mwi_q_cond);
+ ast_mutex_unlock(&iface->mwi_q_lock);
+}
+
+static int smdi_toggle_mwi(struct ast_smdi_interface *iface, const char *mailbox, int on)
+{
+ FILE *file;
+ int i;
+
+ if (!(file = fopen(iface->name, "w"))) {
+ ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s) for writing\n", iface->name, strerror(errno));
+ return 1;
+ }
+
+ ASTOBJ_WRLOCK(iface);
+
+ fprintf(file, "%s:MWI ", on ? "OP" : "RMV");
+
+ for (i = 0; i < iface->msdstrip; i++)
+ fprintf(file, "0");
+
+ fprintf(file, "%s!\x04", mailbox);
+
+ fclose(file);
+
+ ASTOBJ_UNLOCK(iface);
+
+ ast_log(LOG_DEBUG, "Sent MWI %s message for %s on %s\n", on ? "set" : "unset",
+ mailbox, iface->name);
+
+ return 0;
+}
+
+int ast_smdi_mwi_set(struct ast_smdi_interface *iface, const char *mailbox)
+{
+ return smdi_toggle_mwi(iface, mailbox, 1);
+}
+
+int ast_smdi_mwi_unset(struct ast_smdi_interface *iface, const char *mailbox)
+{
+ return smdi_toggle_mwi(iface, mailbox, 0);
+}
+
+void ast_smdi_md_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
+{
+ ast_mutex_lock(&iface->md_q_lock);
+ ASTOBJ_CONTAINER_LINK_START(&iface->md_q, md_msg);
+ ast_cond_broadcast(&iface->md_q_cond);
+ ast_mutex_unlock(&iface->md_q_lock);
+}
+
+void ast_smdi_mwi_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
+{
+ ast_mutex_lock(&iface->mwi_q_lock);
+ ASTOBJ_CONTAINER_LINK_START(&iface->mwi_q, mwi_msg);
+ ast_cond_broadcast(&iface->mwi_q_cond);
+ ast_mutex_unlock(&iface->mwi_q_lock);
+}
+
+enum smdi_message_type {
+ SMDI_MWI,
+ SMDI_MD,
+};
+
+static inline int lock_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
+{
+ switch (type) {
+ case SMDI_MWI:
+ return ast_mutex_lock(&iface->mwi_q_lock);
+ case SMDI_MD:
+ return ast_mutex_lock(&iface->md_q_lock);
+ }
+
+ return -1;
+}
+
+static inline int unlock_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
+{
+ switch (type) {
+ case SMDI_MWI:
+ return ast_mutex_unlock(&iface->mwi_q_lock);
+ case SMDI_MD:
+ return ast_mutex_unlock(&iface->md_q_lock);
+ }
+
+ return -1;
+}
+
+static inline void *unlink_from_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
+{
+ switch (type) {
+ case SMDI_MWI:
+ return ASTOBJ_CONTAINER_UNLINK_START(&iface->mwi_q);
+ case SMDI_MD:
+ return ASTOBJ_CONTAINER_UNLINK_START(&iface->md_q);
+ }
+
+ return NULL;
+}
+
+static inline struct timeval msg_timestamp(void *msg, enum smdi_message_type type)
+{
+ struct ast_smdi_md_message *md_msg = msg;
+ struct ast_smdi_mwi_message *mwi_msg = msg;
+
+ switch (type) {
+ case SMDI_MWI:
+ return mwi_msg->timestamp;
+ case SMDI_MD:
+ return md_msg->timestamp;
+ }
+
+ return ast_tv(0, 0);
+}
+
+static inline void unref_msg(void *msg, enum smdi_message_type type)
+{
+ struct ast_smdi_md_message *md_msg = msg;
+ struct ast_smdi_mwi_message *mwi_msg = msg;
+
+ switch (type) {
+ case SMDI_MWI:
+ ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
+ case SMDI_MD:
+ ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
+ }
+}
+
+static void purge_old_messages(struct ast_smdi_interface *iface, enum smdi_message_type type)
+{
+ struct timeval now;
+ long elapsed = 0;
+ void *msg;
+
+ lock_msg_q(iface, type);
+ msg = unlink_from_msg_q(iface, type);
+ unlock_msg_q(iface, type);
+
+ /* purge old messages */
+ now = ast_tvnow();
+ while (msg) {
+ elapsed = ast_tvdiff_ms(now, msg_timestamp(msg, type));
+
+ if (elapsed > iface->msg_expiry) {
+ /* found an expired message */
+ unref_msg(msg, type);
+ ast_log(LOG_NOTICE, "Purged expired message from %s SMDI %s message queue. "
+ "Message was %ld milliseconds too old.\n",
+ iface->name, (type == SMDI_MD) ? "MD" : "MWI",
+ elapsed - iface->msg_expiry);
+
+ lock_msg_q(iface, type);
+ msg = unlink_from_msg_q(iface, type);
+ unlock_msg_q(iface, type);
+ } else {
+ /* good message, put it back and return */
+ switch (type) {
+ case SMDI_MD:
+ ast_smdi_md_message_push(iface, msg);
+ break;
+ case SMDI_MWI:
+ ast_smdi_mwi_message_push(iface, msg);
+ break;
+ }
+ unref_msg(msg, type);
+ break;
+ }
+ }
+}
+
+static void *smdi_msg_pop(struct ast_smdi_interface *iface, enum smdi_message_type type)
+{
+ void *msg;
+
+ purge_old_messages(iface, type);
+
+ lock_msg_q(iface, type);
+ msg = unlink_from_msg_q(iface, type);
+ unlock_msg_q(iface, type);
+
+ return msg;
+}
+
+enum {
+ OPT_SEARCH_TERMINAL = (1 << 0),
+ OPT_SEARCH_NUMBER = (1 << 1),
+};
+
+static void *smdi_msg_find(struct ast_smdi_interface *iface,
+ enum smdi_message_type type, const char *search_key, struct ast_flags options)
+{
+ void *msg = NULL;
+
+ purge_old_messages(iface, type);
+
+ switch (type) {
+ case SMDI_MD:
+ if (ast_test_flag(&options, OPT_SEARCH_TERMINAL)) {
+ struct ast_smdi_md_message *md_msg = NULL;
+
+ /* Searching by the message desk terminal */
+
+ ASTOBJ_CONTAINER_TRAVERSE(&iface->md_q, !md_msg, do {
+ if (!strcasecmp(iterator->mesg_desk_term, search_key))
+ md_msg = ASTOBJ_REF(iterator);
+ } while (0); );
+
+ msg = md_msg;
+ } else if (ast_test_flag(&options, OPT_SEARCH_NUMBER)) {
+ struct ast_smdi_md_message *md_msg = NULL;
+
+ /* Searching by the message desk number */
+
+ ASTOBJ_CONTAINER_TRAVERSE(&iface->md_q, !md_msg, do {
+ if (!strcasecmp(iterator->mesg_desk_num, search_key))
+ md_msg = ASTOBJ_REF(iterator);
+ } while (0); );
+
+ msg = md_msg;
+ } else {
+ /* Searching by the forwarding station */
+ msg = ASTOBJ_CONTAINER_FIND(&iface->md_q, search_key);
+ }
+ break;
+ case SMDI_MWI:
+ msg = ASTOBJ_CONTAINER_FIND(&iface->mwi_q, search_key);
+ break;
+ }
+
+ return msg;
+}
+
+static void *smdi_message_wait(struct ast_smdi_interface *iface, int timeout,
+ enum smdi_message_type type, const char *search_key, struct ast_flags options)
+{
+ struct timeval start;
+ long diff = 0;
+ void *msg;
+ ast_cond_t *cond = NULL;
+ ast_mutex_t *lock = NULL;
+
+ switch (type) {
+ case SMDI_MWI:
+ cond = &iface->mwi_q_cond;
+ lock = &iface->mwi_q_lock;
+ break;
+ case SMDI_MD:
+ cond = &iface->md_q_cond;
+ lock = &iface->md_q_lock;
+ break;
+ }
+
+ start = ast_tvnow();
+ while (diff < timeout) {
+ struct timespec ts = { 0, };
+ struct timeval tv;
+
+ lock_msg_q(iface, type);
+
+ if ((msg = smdi_msg_find(iface, type, search_key, options))) {
+ unlock_msg_q(iface, type);
+ return msg;
+ }
+
+ tv = ast_tvadd(start, ast_tv(0, timeout));
+ ts.tv_sec = tv.tv_sec;
+ ts.tv_nsec = tv.tv_usec * 1000;
+
+ /* If there were no messages in the queue, then go to sleep until one
+ * arrives. */
+
+ ast_cond_timedwait(cond, lock, &ts);
+
+ if ((msg = smdi_msg_find(iface, type, search_key, options))) {
+ unlock_msg_q(iface, type);
+ return msg;
+ }
+
+ unlock_msg_q(iface, type);
+
+ /* check timeout */
+ diff = ast_tvdiff_ms(ast_tvnow(), start);
+ }
+
+ return NULL;
+}
+
+struct ast_smdi_md_message *ast_smdi_md_message_pop(struct ast_smdi_interface *iface)
+{
+ return smdi_msg_pop(iface, SMDI_MD);
+}
+
+struct ast_smdi_md_message *ast_smdi_md_message_wait(struct ast_smdi_interface *iface, int timeout)
+{
+ struct ast_flags options = { 0 };
+ return smdi_message_wait(iface, timeout, SMDI_MD, NULL, options);
+}
+
+struct ast_smdi_mwi_message *ast_smdi_mwi_message_pop(struct ast_smdi_interface *iface)
+{
+ return smdi_msg_pop(iface, SMDI_MWI);
+}
+
+struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait(struct ast_smdi_interface *iface, int timeout)
+{
+ struct ast_flags options = { 0 };
+ return smdi_message_wait(iface, timeout, SMDI_MWI, NULL, options);
+}
+
+struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait_station(struct ast_smdi_interface *iface, int timeout,
+ const char *station)
+{
+ struct ast_flags options = { 0 };
+ return smdi_message_wait(iface, timeout, SMDI_MWI, station, options);
+}
+
+struct ast_smdi_interface *ast_smdi_interface_find(const char *iface_name)
+{
+ return (ASTOBJ_CONTAINER_FIND(&smdi_ifaces, iface_name));
+}
+
+/*!
+ * \internal
+ * \brief Read an SMDI message.
+ *
+ * \param iface_p the SMDI interface to read from.
+ *
+ * This function loops and reads from and SMDI interface. It must be stopped
+ * using pthread_cancel().
+ */
+static void *smdi_read(void *iface_p)
+{
+ struct ast_smdi_interface *iface = iface_p;
+ struct ast_smdi_md_message *md_msg;
+ struct ast_smdi_mwi_message *mwi_msg;
+ char c = '\0';
+ char *cp = NULL;
+ int i;
+ int start = 0;
+
+ /* read an smdi message */
+ while ((c = fgetc(iface->file))) {
+
+ /* check if this is the start of a message */
+ if (!start) {
+ if (c == 'M') {
+ ast_log(LOG_DEBUG, "Read an 'M' to start an SMDI message\n");
+ start = 1;
+ }
+ continue;
+ }
+
+ if (c == 'D') { /* MD message */
+ start = 0;
+
+ ast_log(LOG_DEBUG, "Read a 'D' ... it's an MD message.\n");
+
+ if (!(md_msg = ast_calloc(1, sizeof(*md_msg)))) {
+ ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
+ return NULL;
+ }
+
+ ASTOBJ_INIT(md_msg);
+
+ /* read the message desk number */
+ for (i = 0; i < sizeof(md_msg->mesg_desk_num) - 1; i++) {
+ md_msg->mesg_desk_num[i] = fgetc(iface->file);
+ ast_log(LOG_DEBUG, "Read a '%c'\n", md_msg->mesg_desk_num[i]);
+ }
+
+ md_msg->mesg_desk_num[sizeof(md_msg->mesg_desk_num) - 1] = '\0';
+
+ ast_log(LOG_DEBUG, "The message desk number is '%s'\n", md_msg->mesg_desk_num);
+
+ /* read the message desk terminal number */
+ for (i = 0; i < sizeof(md_msg->mesg_desk_term) - 1; i++) {
+ md_msg->mesg_desk_term[i] = fgetc(iface->file);
+ ast_log(LOG_DEBUG, "Read a '%c'\n", md_msg->mesg_desk_term[i]);
+ }
+
+ md_msg->mesg_desk_term[sizeof(md_msg->mesg_desk_term) - 1] = '\0';
+
+ ast_log(LOG_DEBUG, "The message desk terminal is '%s'\n", md_msg->mesg_desk_term);
+
+ /* read the message type */
+ md_msg->type = fgetc(iface->file);
+
+ ast_log(LOG_DEBUG, "Message type is '%c'\n", md_msg->type);
+
+ /* read the forwarding station number (may be blank) */
+ cp = &md_msg->fwd_st[0];
+ for (i = 0; i < sizeof(md_msg->fwd_st) - 1; i++) {
+ if ((c = fgetc(iface->file)) == ' ') {
+ *cp = '\0';
+ ast_log(LOG_DEBUG, "Read a space, done looking for the forwarding station\n");
+ break;
+ }
+
+ /* store c in md_msg->fwd_st */
+ if (i >= iface->msdstrip) {
+ ast_log(LOG_DEBUG, "Read a '%c' and stored it in the forwarding station buffer\n", c);
+ *cp++ = c;
+ } else {
+ ast_log(LOG_DEBUG, "Read a '%c', but didn't store it in the fwd station buffer, because of the msdstrip setting (%d < %d)\n", c, i, iface->msdstrip);
+ }
+ }
+
+ /* make sure the value is null terminated, even if this truncates it */
+ md_msg->fwd_st[sizeof(md_msg->fwd_st) - 1] = '\0';
+ cp = NULL;
+
+ ast_log(LOG_DEBUG, "The forwarding station is '%s'\n", md_msg->fwd_st);
+
+ /* Put the fwd_st in the name field so that we can use ASTOBJ_FIND to look
+ * up a message on this field */
+ ast_copy_string(md_msg->name, md_msg->fwd_st, sizeof(md_msg->name));
+
+ /* read the calling station number (may be blank) */
+ cp = &md_msg->calling_st[0];
+ for (i = 0; i < sizeof(md_msg->calling_st) - 1; i++) {
+ if (!isdigit((c = fgetc(iface->file)))) {
+ *cp = '\0';
+ ast_log(LOG_DEBUG, "Read a '%c', but didn't store it in the calling station buffer because it's not a digit\n", c);
+ if (c == ' ') {
+ /* Don't break on a space. We may read the space before the calling station
+ * here if the forwarding station buffer filled up. */
+ i--; /* We're still on the same character */
+ continue;
+ }
+ break;
+ }
+
+ /* store c in md_msg->calling_st */
+ if (i >= iface->msdstrip) {
+ ast_log(LOG_DEBUG, "Read a '%c' and stored it in the calling station buffer\n", c);
+ *cp++ = c;
+ } else {
+ ast_log(LOG_DEBUG, "Read a '%c', but didn't store it in the calling station buffer, because of the msdstrip setting (%d < %d)\n", c, i, iface->msdstrip);
+ }
+ }
+
+ /* make sure the value is null terminated, even if this truncates it */
+ md_msg->calling_st[sizeof(md_msg->calling_st) - 1] = '\0';
+ cp = NULL;
+
+ ast_log(LOG_DEBUG, "The calling station is '%s'\n", md_msg->calling_st);
+
+ /* add the message to the message queue */
+ md_msg->timestamp = ast_tvnow();
+ ast_smdi_md_message_push(iface, md_msg);
+ ast_log(LOG_DEBUG, "Recieved SMDI MD message on %s\n", iface->name);
+
+ ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
+
+ } else if (c == 'W') { /* MWI message */
+ start = 0;
+
+ ast_log(LOG_DEBUG, "Read a 'W', it's an MWI message. (No more debug coming for MWI messages)\n");
+
+ if (!(mwi_msg = ast_calloc(1, sizeof(*mwi_msg)))) {
+ ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
+ return NULL;
+ }
+
+ ASTOBJ_INIT(mwi_msg);
+
+ /* discard the 'I' (from 'MWI') */
+ fgetc(iface->file);
+
+ /* read the forwarding station number (may be blank) */
+ cp = &mwi_msg->fwd_st[0];
+ for (i = 0; i < sizeof(mwi_msg->fwd_st) - 1; i++) {
+ if ((c = fgetc(iface->file)) == ' ') {
+ *cp = '\0';
+ break;
+ }
+
+ /* store c in md_msg->fwd_st */
+ if (i >= iface->msdstrip)
+ *cp++ = c;
+ }
+
+ /* make sure the station number is null terminated, even if this will truncate it */
+ mwi_msg->fwd_st[sizeof(mwi_msg->fwd_st) - 1] = '\0';
+ cp = NULL;
+
+ /* Put the fwd_st in the name field so that we can use ASTOBJ_FIND to look
+ * up a message on this field */
+ ast_copy_string(mwi_msg->name, mwi_msg->fwd_st, sizeof(mwi_msg->name));
+
+ /* read the mwi failure cause */
+ for (i = 0; i < sizeof(mwi_msg->cause) - 1; i++)
+ mwi_msg->cause[i] = fgetc(iface->file);
+
+ mwi_msg->cause[sizeof(mwi_msg->cause) - 1] = '\0';
+
+ /* add the message to the message queue */
+ mwi_msg->timestamp = ast_tvnow();
+ ast_smdi_mwi_message_push(iface, mwi_msg);
+ ast_log(LOG_DEBUG, "Recieved SMDI MWI message on %s\n", iface->name);
+
+ ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
+ } else {
+ ast_log(LOG_ERROR, "Unknown SMDI message type recieved on %s (M%c).\n", iface->name, c);
+ start = 0;
+ }
+ }
+
+ ast_log(LOG_ERROR, "Error reading from SMDI interface %s, stopping listener thread\n", iface->name);
+ ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
+ return NULL;
+}
+
+void ast_smdi_md_message_destroy(struct ast_smdi_md_message *msg)
+{
+ free(msg);
+}
+
+void ast_smdi_mwi_message_destroy(struct ast_smdi_mwi_message *msg)
+{
+ free(msg);
+}
+
+static void destroy_mailbox_mapping(struct mailbox_mapping *mm)
+{
+ ast_string_field_free_memory(mm);
+ ASTOBJ_UNREF(mm->iface, ast_smdi_interface_destroy);
+ free(mm);
+}
+
+static void destroy_all_mailbox_mappings(void)
+{
+ struct mailbox_mapping *mm;
+
+ ast_mutex_lock(&mwi_monitor.lock);
+ while ((mm = AST_LIST_REMOVE_HEAD(&mwi_monitor.mailbox_mappings, entry)))
+ destroy_mailbox_mapping(mm);
+ ast_mutex_unlock(&mwi_monitor.lock);
+}
+
+static void append_mailbox_mapping(struct ast_variable *var, struct ast_smdi_interface *iface)
+{
+ struct mailbox_mapping *mm;
+ char *mailbox, *context;
+
+ if (!(mm = ast_calloc(1, sizeof(*mm))))
+ return;
+
+ if (ast_string_field_init(mm, 32)) {
+ free(mm);
+ return;
+ }
+
+ ast_string_field_set(mm, smdi, var->name);
+
+ context = ast_strdupa(var->value);
+ mailbox = strsep(&context, "@");
+ if (ast_strlen_zero(context))
+ context = "default";
+
+ ast_string_field_set(mm, mailbox, mailbox);
+ ast_string_field_set(mm, context, context);
+
+ mm->iface = ASTOBJ_REF(iface);
+
+ ast_mutex_lock(&mwi_monitor.lock);
+ AST_LIST_INSERT_TAIL(&mwi_monitor.mailbox_mappings, mm, entry);
+ ast_mutex_unlock(&mwi_monitor.lock);
+}
+
+/*!
+ * \note Called with the mwi_monitor.lock locked
+ */
+static void poll_mailbox(struct mailbox_mapping *mm)
+{
+ char buf[1024];
+ unsigned int state;
+
+ snprintf(buf, sizeof(buf), "%s@%s", mm->mailbox, mm->context);
+
+ state = !!ast_app_has_voicemail(mm->mailbox, NULL);
+
+ if (state != mm->cur_state) {
+ if (state)
+ ast_smdi_mwi_set(mm->iface, mm->smdi);
+ else
+ ast_smdi_mwi_unset(mm->iface, mm->smdi);
+
+ mm->cur_state = state;
+ }
+}
+
+static void *mwi_monitor_handler(void *data)
+{
+ while (!mwi_monitor.stop) {
+ struct timespec ts = { 0, };
+ struct timeval tv;
+ struct mailbox_mapping *mm;
+
+ ast_mutex_lock(&mwi_monitor.lock);
+
+ mwi_monitor.last_poll = ast_tvnow();
+
+ AST_LIST_TRAVERSE(&mwi_monitor.mailbox_mappings, mm, entry)
+ poll_mailbox(mm);
+
+ /* Sleep up to the configured polling interval. Allow unload_module()
+ * to signal us to wake up and exit. */
+ tv = ast_tvadd(mwi_monitor.last_poll, ast_tv(mwi_monitor.polling_interval, 0));
+ ts.tv_sec = tv.tv_sec;
+ ts.tv_nsec = tv.tv_usec * 1000;
+ ast_cond_timedwait(&mwi_monitor.cond, &mwi_monitor.lock, &ts);
+
+ ast_mutex_unlock(&mwi_monitor.lock);
+ }
+
+ return NULL;
+}
+
+static struct ast_smdi_interface *alloc_smdi_interface(void)
+{
+ struct ast_smdi_interface *iface;
+
+ if (!(iface = ast_calloc(1, sizeof(*iface))))
+ return NULL;
+
+ ASTOBJ_INIT(iface);
+ ASTOBJ_CONTAINER_INIT(&iface->md_q);
+ ASTOBJ_CONTAINER_INIT(&iface->mwi_q);
+
+ ast_mutex_init(&iface->md_q_lock);
+ ast_cond_init(&iface->md_q_cond, NULL);
+
+ ast_mutex_init(&iface->mwi_q_lock);
+ ast_cond_init(&iface->mwi_q_cond, NULL);
+
+ return iface;
+}
+
+/*!
+ * \internal
+ * \brief Load and reload SMDI configuration.
+ * \param reload this should be 1 if we are reloading and 0 if not.
+ *
+ * This function loads/reloads the SMDI configuration and starts and stops
+ * interfaces accordingly.
+ *
+ * \return zero on success, -1 on failure, and 1 if no smdi interfaces were started.
+ */
+static int smdi_load(int reload)
+{
+ struct ast_config *conf;
+ struct ast_variable *v;
+ struct ast_smdi_interface *iface = NULL;
+ int res = 0;
+
+ /* Config options */
+ speed_t baud_rate = B9600; /* 9600 baud rate */
+ tcflag_t paritybit = PARENB; /* even parity checking */
+ tcflag_t charsize = CS7; /* seven bit characters */
+ int stopbits = 0; /* One stop bit */
+
+ int msdstrip = 0; /* strip zero digits */
+ long msg_expiry = SMDI_MSG_EXPIRY_TIME;
+
+ conf = ast_config_load(config_file);
+
+ if (!conf) {
+ if (reload)
+ ast_log(LOG_NOTICE, "Unable to reload config %s: SMDI untouched\n", config_file);
+ else
+ ast_log(LOG_NOTICE, "Unable to load config %s: SMDI disabled\n", config_file);
+ return 1;
+ }
+
+ /* Mark all interfaces that we are listening on. We will unmark them
+ * as we find them in the config file, this way we know any interfaces
+ * still marked after we have finished parsing the config file should
+ * be stopped.
+ */
+ if (reload)
+ ASTOBJ_CONTAINER_MARKALL(&smdi_ifaces);
+
+ for (v = ast_variable_browse(conf, "interfaces"); v; v = v->next) {
+ if (!strcasecmp(v->name, "baudrate")) {
+ if (!strcasecmp(v->value, "9600"))
+ baud_rate = B9600;
+ else if (!strcasecmp(v->value, "4800"))
+ baud_rate = B4800;
+ else if (!strcasecmp(v->value, "2400"))
+ baud_rate = B2400;
+ else if (!strcasecmp(v->value, "1200"))
+ baud_rate = B1200;
+ else {
+ ast_log(LOG_NOTICE, "Invalid baud rate '%s' specified in %s (line %d), using default\n", v->value, config_file, v->lineno);
+ baud_rate = B9600;
+ }
+ } else if (!strcasecmp(v->name, "msdstrip")) {
+ if (!sscanf(v->value, "%d", &msdstrip)) {
+ ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
+ msdstrip = 0;
+ } else if (0 > msdstrip || msdstrip > 9) {
+ ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
+ msdstrip = 0;
+ }
+ } else if (!strcasecmp(v->name, "msgexpirytime")) {
+ if (!sscanf(v->value, "%ld", &msg_expiry)) {
+ ast_log(LOG_NOTICE, "Invalid msgexpirytime value in %s (line %d), using default\n", config_file, v->lineno);
+ msg_expiry = SMDI_MSG_EXPIRY_TIME;
+ }
+ } else if (!strcasecmp(v->name, "paritybit")) {
+ if (!strcasecmp(v->value, "even"))
+ paritybit = PARENB;
+ else if (!strcasecmp(v->value, "odd"))
+ paritybit = PARENB | PARODD;
+ else if (!strcasecmp(v->value, "none"))
+ paritybit = ~PARENB;
+ else {
+ ast_log(LOG_NOTICE, "Invalid parity bit setting in %s (line %d), using default\n", config_file, v->lineno);
+ paritybit = PARENB;
+ }
+ } else if (!strcasecmp(v->name, "charsize")) {
+ if (!strcasecmp(v->value, "7"))
+ charsize = CS7;
+ else if (!strcasecmp(v->value, "8"))
+ charsize = CS8;
+ else {
+ ast_log(LOG_NOTICE, "Invalid character size setting in %s (line %d), using default\n", config_file, v->lineno);
+ charsize = CS7;
+ }
+ } else if (!strcasecmp(v->name, "twostopbits")) {
+ stopbits = ast_true(v->name);
+ } else if (!strcasecmp(v->name, "smdiport")) {
+ if (reload) {
+ /* we are reloading, check if we are already
+ * monitoring this interface, if we are we do
+ * not want to start it again. This also has
+ * the side effect of not updating different
+ * setting for the serial port, but it should
+ * be trivial to rewrite this section so that
+ * options on the port are changed without
+ * restarting the interface. Or the interface
+ * could be restarted with out emptying the
+ * queue. */
+ if ((iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) {
+ ast_log(LOG_NOTICE, "SMDI interface %s already running, not restarting\n", iface->name);
+ ASTOBJ_UNMARK(iface);
+ ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
+ continue;
+ }
+ }
+
+ if (!(iface = alloc_smdi_interface()))
+ continue;
+
+ ast_copy_string(iface->name, v->value, sizeof(iface->name));
+
+ iface->thread = AST_PTHREADT_NULL;
+
+ if (!(iface->file = fopen(iface->name, "r"))) {
+ ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s)\n", iface->name, strerror(errno));
+ ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
+ continue;
+ }
+
+ iface->fd = fileno(iface->file);
+
+ /* Set the proper attributes for our serial port. */
+
+ /* get the current attributes from the port */
+ if (tcgetattr(iface->fd, &iface->mode)) {
+ ast_log(LOG_ERROR, "Error getting atributes of %s (%s)\n", iface->name, strerror(errno));
+ ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
+ continue;
+ }
+
+ /* set the desired speed */
+ if (cfsetispeed(&iface->mode, baud_rate) || cfsetospeed(&iface->mode, baud_rate)) {
+ ast_log(LOG_ERROR, "Error setting baud rate on %s (%s)\n", iface->name, strerror(errno));
+ ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
+ continue;
+ }
+
+ /* set the stop bits */
+ if (stopbits)
+ iface->mode.c_cflag = iface->mode.c_cflag | CSTOPB; /* set two stop bits */
+ else
+ iface->mode.c_cflag = iface->mode.c_cflag & ~CSTOPB; /* set one stop bit */
+
+ /* set the parity */
+ iface->mode.c_cflag = (iface->mode.c_cflag & ~PARENB & ~PARODD) | paritybit;
+
+ /* set the character size */
+ iface->mode.c_cflag = (iface->mode.c_cflag & ~CSIZE) | charsize;
+
+ /* commit the desired attributes */
+ if (tcsetattr(iface->fd, TCSAFLUSH, &iface->mode)) {
+ ast_log(LOG_ERROR, "Error setting attributes on %s (%s)\n", iface->name, strerror(errno));
+ ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
+ continue;
+ }
+
+ /* set the msdstrip */
+ iface->msdstrip = msdstrip;
+
+ /* set the message expiry time */
+ iface->msg_expiry = msg_expiry;
+
+ /* start the listener thread */
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Starting SMDI monitor thread for %s\n", iface->name);
+ if (ast_pthread_create_background(&iface->thread, NULL, smdi_read, iface)) {
+ ast_log(LOG_ERROR, "Error starting SMDI monitor thread for %s\n", iface->name);
+ ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
+ continue;
+ }
+
+ ASTOBJ_CONTAINER_LINK(&smdi_ifaces, iface);
+ ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
+ ast_module_ref(ast_module_info->self);
+ } else {
+ ast_log(LOG_NOTICE, "Ignoring unknown option %s in %s\n", v->name, config_file);
+ }
+ }
+
+ destroy_all_mailbox_mappings();
+ mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL;
+
+ iface = NULL;
+
+ for (v = ast_variable_browse(conf, "mailboxes"); v; v = v->next) {
+ if (!strcasecmp(v->name, "smdiport")) {
+ if (iface)
+ ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
+
+ if (!(iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) {
+ ast_log(LOG_NOTICE, "SMDI interface %s not found\n", iface->name);
+ continue;
+ }
+ } else if (!strcasecmp(v->name, "pollinginterval")) {
+ if (sscanf(v->value, "%u", &mwi_monitor.polling_interval) != 1) {
+ ast_log(LOG_ERROR, "Invalid value for pollinginterval: %s\n", v->value);
+ mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL;
+ }
+ } else {
+ if (!iface) {
+ ast_log(LOG_ERROR, "Mailbox mapping ignored, no valid SMDI interface specified in mailboxes section\n");
+ continue;
+ }
+ append_mailbox_mapping(v, iface);
+ }
+ }
+
+ if (iface)
+ ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
+
+ ast_config_destroy(conf);
+
+ if (!AST_LIST_EMPTY(&mwi_monitor.mailbox_mappings) && mwi_monitor.thread == AST_PTHREADT_NULL
+ && ast_pthread_create_background(&mwi_monitor.thread, NULL, mwi_monitor_handler, NULL)) {
+ ast_log(LOG_ERROR, "Failed to start MWI monitoring thread. This module will not operate.\n");
+ return AST_MODULE_LOAD_FAILURE;
+ }
+
+ /* Prune any interfaces we should no longer monitor. */
+ if (reload)
+ ASTOBJ_CONTAINER_PRUNE_MARKED(&smdi_ifaces, ast_smdi_interface_destroy);
+
+ ASTOBJ_CONTAINER_RDLOCK(&smdi_ifaces);
+ /* TODO: this is bad, we need an ASTOBJ method for this! */
+ if (!smdi_ifaces.head)
+ res = 1;
+ ASTOBJ_CONTAINER_UNLOCK(&smdi_ifaces);
+
+ return res;
+}
+
+struct smdi_msg_datastore {
+ unsigned int id;
+ struct ast_smdi_interface *iface;
+ struct ast_smdi_md_message *md_msg;
+};
+
+static void smdi_msg_datastore_destroy(void *data)
+{
+ struct smdi_msg_datastore *smd = data;
+
+ if (smd->iface)
+ ASTOBJ_UNREF(smd->iface, ast_smdi_interface_destroy);
+
+ if (smd->md_msg)
+ ASTOBJ_UNREF(smd->md_msg, ast_smdi_md_message_destroy);
+
+ free(smd);
+}
+
+static const struct ast_datastore_info smdi_msg_datastore_info = {
+ .type = "SMDIMSG",
+ .destroy = smdi_msg_datastore_destroy,
+};
+
+static int smdi_msg_id;
+
+/*! In milliseconds */
+#define SMDI_RETRIEVE_TIMEOUT_DEFAULT 3000
+
+AST_APP_OPTIONS(smdi_msg_ret_options, BEGIN_OPTIONS
+ AST_APP_OPTION('t', OPT_SEARCH_TERMINAL),
+ AST_APP_OPTION('n', OPT_SEARCH_NUMBER),
+END_OPTIONS );
+
+static int smdi_msg_retrieve_read(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len)
+{
+ struct ast_module_user *u;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(port);
+ AST_APP_ARG(search_key);
+ AST_APP_ARG(timeout);
+ AST_APP_ARG(options);
+ );
+ struct ast_flags options = { 0 };
+ unsigned int timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT;
+ int res = -1;
+ char *parse = NULL;
+ struct smdi_msg_datastore *smd = NULL;
+ struct ast_datastore *datastore = NULL;
+ struct ast_smdi_interface *iface = NULL;
+ struct ast_smdi_md_message *md_msg = NULL;
+
+ u = ast_module_user_add(chan);
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE requires an argument\n");
+ goto return_error;
+ }
+
+ if (!chan) {
+ ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE must be used with a channel\n");
+ goto return_error;
+ }
+
+ ast_autoservice_start(chan);
+
+ parse = ast_strdupa(data);
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ if (ast_strlen_zero(args.port) || ast_strlen_zero(args.search_key)) {
+ ast_log(LOG_ERROR, "Invalid arguments provided to SMDI_MSG_RETRIEVE\n");
+ goto return_error;
+ }
+
+ if (!(iface = ast_smdi_interface_find(args.port))) {
+ ast_log(LOG_ERROR, "SMDI port '%s' not found\n", args.port);
+ goto return_error;
+ }
+
+ if (!ast_strlen_zero(args.options)) {
+ ast_app_parse_options(smdi_msg_ret_options, &options, NULL, args.options);
+ }
+
+ if (!ast_strlen_zero(args.timeout)) {
+ if (sscanf(args.timeout, "%u", &timeout) != 1) {
+ ast_log(LOG_ERROR, "'%s' is not a valid timeout\n", args.timeout);
+ timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT;
+ }
+ }
+
+ if (!(md_msg = smdi_message_wait(iface, timeout, SMDI_MD, args.search_key, options))) {
+ ast_log(LOG_WARNING, "No SMDI message retrieved for search key '%s' after "
+ "waiting %u ms.\n", args.search_key, timeout);
+ goto return_error;
+ }
+
+ if (!(smd = ast_calloc(1, sizeof(*smd))))
+ goto return_error;
+
+ smd->iface = ASTOBJ_REF(iface);
+ smd->md_msg = ASTOBJ_REF(md_msg);
+ smd->id = ast_atomic_fetchadd_int((int *) &smdi_msg_id, 1);
+ snprintf(buf, len, "%u", smd->id);
+
+ if (!(datastore = ast_channel_datastore_alloc(&smdi_msg_datastore_info, buf)))
+ goto return_error;
+
+ datastore->data = smd;
+
+ ast_channel_lock(chan);
+ ast_channel_datastore_add(chan, datastore);
+ ast_channel_unlock(chan);
+
+ res = 0;
+
+return_error:
+ if (iface)
+ ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
+
+ if (md_msg)
+ ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
+
+ if (smd && !datastore)
+ smdi_msg_datastore_destroy(smd);
+
+ if (parse)
+ ast_autoservice_stop(chan);
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int smdi_msg_read(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len)
+{
+ struct ast_module_user *u;
+ int res = -1;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(id);
+ AST_APP_ARG(component);
+ );
+ char *parse;
+ struct ast_datastore *datastore = NULL;
+ struct smdi_msg_datastore *smd = NULL;
+
+ u = ast_module_user_add(chan);
+
+ if (!chan) {
+ ast_log(LOG_ERROR, "SMDI_MSG can not be called without a channel\n");
+ goto return_error;
+ }
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "SMDI_MSG requires an argument\n");
+ goto return_error;
+ }
+
+ parse = ast_strdupa(data);
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ if (ast_strlen_zero(args.id)) {
+ ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n");
+ goto return_error;
+ }
+
+ if (ast_strlen_zero(args.component)) {
+ ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n");
+ goto return_error;
+ }
+
+ ast_channel_lock(chan);
+ datastore = ast_channel_datastore_find(chan, &smdi_msg_datastore_info, args.id);
+ ast_channel_unlock(chan);
+
+ if (!datastore) {
+ ast_log(LOG_WARNING, "No SMDI message found for message ID '%s'\n", args.id);
+ goto return_error;
+ }
+
+ smd = datastore->data;
+
+ if (!strcasecmp(args.component, "number")) {
+ ast_copy_string(buf, smd->md_msg->mesg_desk_num, len);
+ } else if (!strcasecmp(args.component, "terminal")) {
+ ast_copy_string(buf, smd->md_msg->mesg_desk_term, len);
+ } else if (!strcasecmp(args.component, "station")) {
+ ast_copy_string(buf, smd->md_msg->fwd_st, len);
+ } else if (!strcasecmp(args.component, "callerid")) {
+ ast_copy_string(buf, smd->md_msg->calling_st, len);
+ } else if (!strcasecmp(args.component, "type")) {
+ snprintf(buf, len, "%c", smd->md_msg->type);
+ } else {
+ ast_log(LOG_ERROR, "'%s' is not a valid message component for SMDI_MSG\n",
+ args.component);
+ goto return_error;
+ }
+
+ res = 0;
+
+return_error:
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static struct ast_custom_function smdi_msg_retrieve_function = {
+ .name = "SMDI_MSG_RETRIEVE",
+ .synopsis = "Retrieve an SMDI message.",
+ .syntax = "SMDI_MSG_RETRIEVE(<smdi port>,<search key>[,timeout[,options]])",
+ .desc =
+ " This function is used to retrieve an incoming SMDI message. It returns\n"
+ "an ID which can be used with the SMDI_MSG() function to access details of\n"
+ "the message. Note that this is a destructive function in the sense that\n"
+ "once an SMDI message is retrieved using this function, it is no longer in\n"
+ "the global SMDI message queue, and can not be accessed by any other Asterisk\n"
+ "channels. The timeout for this function is optional, and the default is\n"
+ "3 seconds. When providing a timeout, it should be in milliseconds.\n"
+ " The default search is done on the forwarding station ID. However, if\n"
+ "you set one of the search key options in the options field, you can change\n"
+ "this behavior.\n"
+ " Options:\n"
+ " t - Instead of searching on the forwarding station, search on the message\n"
+ " desk terminal.\n"
+ " n - Instead of searching on the forwarding station, search on the message\n"
+ " desk number.\n"
+ "",
+ .read = smdi_msg_retrieve_read,
+};
+
+static struct ast_custom_function smdi_msg_function = {
+ .name = "SMDI_MSG",
+ .synopsis = "Retrieve details about an SMDI message.",
+ .syntax = "SMDI_MSG(<message_id>,<component>)",
+ .desc =
+ " This function is used to access details of an SMDI message that was\n"
+ "pulled from the incoming SMDI message queue using the SMDI_MSG_RETRIEVE()\n"
+ "function.\n"
+ " Valid message components are:\n"
+ " number - The message desk number\n"
+ " terminal - The message desk terminal\n"
+ " station - The forwarding station\n"
+ " callerid - The callerID of the calling party that was forwarded\n"
+ " type - The call type. The value here is the exact character\n"
+ " that came in on the SMDI link. Typically, example values\n"
+ " are: D - Direct Calls, A - Forward All Calls,\n"
+ " B - Forward Busy Calls, N - Forward No Answer Calls\n"
+ "",
+ .read = smdi_msg_read,
+};
+
+static int load_module(void)
+{
+ int res;
+
+ /* initialize our containers */
+ memset(&smdi_ifaces, 0, sizeof(smdi_ifaces));
+ ASTOBJ_CONTAINER_INIT(&smdi_ifaces);
+
+ ast_mutex_init(&mwi_monitor.lock);
+ ast_cond_init(&mwi_monitor.cond, NULL);
+
+ ast_custom_function_register(&smdi_msg_retrieve_function);
+ ast_custom_function_register(&smdi_msg_function);
+
+ /* load the config and start the listener threads*/
+ res = smdi_load(0);
+ if (res < 0) {
+ return res;
+ } else if (res == 1) {
+ ast_log(LOG_WARNING, "No SMDI interfaces are available to listen on, not starting SMDI listener.\n");
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ return 0;
+}
+
+static int unload_module(void)
+{
+ /* this destructor stops any running smdi_read threads */
+ ASTOBJ_CONTAINER_DESTROYALL(&smdi_ifaces, ast_smdi_interface_destroy);
+ ASTOBJ_CONTAINER_DESTROY(&smdi_ifaces);
+
+ destroy_all_mailbox_mappings();
+
+ ast_mutex_lock(&mwi_monitor.lock);
+ mwi_monitor.stop = 1;
+ ast_cond_signal(&mwi_monitor.cond);
+ ast_mutex_unlock(&mwi_monitor.lock);
+
+ if (mwi_monitor.thread != AST_PTHREADT_NULL) {
+ pthread_join(mwi_monitor.thread, NULL);
+ }
+
+ ast_custom_function_unregister(&smdi_msg_retrieve_function);
+ ast_custom_function_unregister(&smdi_msg_function);
+
+ return 0;
+}
+
+static int reload(void)
+{
+ int res;
+
+ res = smdi_load(1);
+
+ if (res < 0) {
+ return res;
+ } else if (res == 1) {
+ ast_log(LOG_WARNING, "No SMDI interfaces were specified to listen on, not starting SDMI listener.\n");
+ return 0;
+ } else
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Simplified Message Desk Interface (SMDI) Resource",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
diff --git a/1.4.23-rc4/res/res_snmp.c b/1.4.23-rc4/res/res_snmp.c
new file mode 100644
index 000000000..6bbf23171
--- /dev/null
+++ b/1.4.23-rc4/res/res_snmp.c
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2006 Voop as
+ * Thorsten Lockert <tholo@voop.as>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief SNMP Agent / SubAgent support for Asterisk
+ *
+ * \author Thorsten Lockert <tholo@voop.as>
+ */
+
+/*** MODULEINFO
+ <depend>netsnmp</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/channel.h"
+#include "asterisk/module.h"
+#include "asterisk/logger.h"
+#include "asterisk/options.h"
+
+#include "snmp/agent.h"
+
+#define MODULE_DESCRIPTION "SNMP [Sub]Agent for Asterisk"
+
+int res_snmp_agentx_subagent;
+int res_snmp_dont_stop;
+int res_snmp_enabled;
+
+static pthread_t thread = AST_PTHREADT_NULL;
+
+static int load_config(void)
+{
+ struct ast_variable *var;
+ struct ast_config *cfg;
+ char *cat;
+
+ res_snmp_enabled = 0;
+ res_snmp_agentx_subagent = 1;
+ cfg = ast_config_load("res_snmp.conf");
+ if (!cfg) {
+ ast_log(LOG_WARNING, "Could not load res_snmp.conf\n");
+ return 0;
+ }
+ cat = ast_category_browse(cfg, NULL);
+ while (cat) {
+ var = ast_variable_browse(cfg, cat);
+
+ if (strcasecmp(cat, "general") == 0) {
+ while (var) {
+ if (strcasecmp(var->name, "subagent") == 0) {
+ if (ast_true(var->value))
+ res_snmp_agentx_subagent = 1;
+ else if (ast_false(var->value))
+ res_snmp_agentx_subagent = 0;
+ else {
+ ast_log(LOG_ERROR, "Value '%s' does not evaluate to true or false.\n", var->value);
+ ast_config_destroy(cfg);
+ return 1;
+ }
+ } else if (strcasecmp(var->name, "enabled") == 0) {
+ res_snmp_enabled = ast_true(var->value);
+ } else {
+ ast_log(LOG_ERROR, "Unrecognized variable '%s' in category '%s'\n", var->name, cat);
+ ast_config_destroy(cfg);
+ return 1;
+ }
+ var = var->next;
+ }
+ } else {
+ ast_log(LOG_ERROR, "Unrecognized category '%s'\n", cat);
+ ast_config_destroy(cfg);
+ return 1;
+ }
+
+ cat = ast_category_browse(cfg, cat);
+ }
+ ast_config_destroy(cfg);
+ return 1;
+}
+
+static int load_module(void)
+{
+ if(!load_config())
+ return AST_MODULE_LOAD_DECLINE;
+
+ ast_verbose(VERBOSE_PREFIX_1 "Loading [Sub]Agent Module\n");
+
+ res_snmp_dont_stop = 1;
+ if (res_snmp_enabled)
+ return ast_pthread_create_background(&thread, NULL, agent_thread, NULL);
+ else
+ return 0;
+}
+
+static int unload_module(void)
+{
+ ast_verbose(VERBOSE_PREFIX_1 "Unloading [Sub]Agent Module\n");
+
+ res_snmp_dont_stop = 0;
+ return ((thread != AST_PTHREADT_NULL) ? pthread_join(thread, NULL) : 0);
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "SNMP [Sub]Agent for Asterisk",
+ .load = load_module,
+ .unload = unload_module,
+ );
diff --git a/1.4.23-rc4/res/res_speech.c b/1.4.23-rc4/res/res_speech.c
new file mode 100644
index 000000000..5baca9679
--- /dev/null
+++ b/1.4.23-rc4/res/res_speech.c
@@ -0,0 +1,404 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2006, Digium, Inc.
+ *
+ * Joshua Colp <jcolp@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Generic Speech Recognition API
+ *
+ * \author Joshua Colp <jcolp@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$");
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "asterisk/channel.h"
+#include "asterisk/module.h"
+#include "asterisk/lock.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/cli.h"
+#include "asterisk/term.h"
+#include "asterisk/options.h"
+#include "asterisk/speech.h"
+
+
+static AST_LIST_HEAD_STATIC(engines, ast_speech_engine);
+static struct ast_speech_engine *default_engine = NULL;
+
+/*! \brief Find a speech recognition engine of specified name, if NULL then use the default one */
+static struct ast_speech_engine *find_engine(char *engine_name)
+{
+ struct ast_speech_engine *engine = NULL;
+
+ /* If no name is specified -- use the default engine */
+ if (engine_name == NULL || strlen(engine_name) == 0) {
+ return default_engine;
+ }
+
+ AST_LIST_LOCK(&engines);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&engines, engine, list) {
+ if (!strcasecmp(engine->name, engine_name)) {
+ break;
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END
+ AST_LIST_UNLOCK(&engines);
+
+ return engine;
+}
+
+/*! \brief Activate a loaded (either local or global) grammar */
+int ast_speech_grammar_activate(struct ast_speech *speech, char *grammar_name)
+{
+ int res = 0;
+
+ if (speech->engine->activate != NULL) {
+ res = speech->engine->activate(speech, grammar_name);
+ }
+
+ return res;
+}
+
+/*! \brief Deactivate a loaded grammar on a speech structure */
+int ast_speech_grammar_deactivate(struct ast_speech *speech, char *grammar_name)
+{
+ int res = 0;
+
+ if (speech->engine->deactivate != NULL) {
+ res = speech->engine->deactivate(speech, grammar_name);
+ }
+
+ return res;
+}
+
+/*! \brief Load a local grammar on a speech structure */
+int ast_speech_grammar_load(struct ast_speech *speech, char *grammar_name, char *grammar)
+{
+ int res = 0;
+
+ if (speech->engine->load != NULL) {
+ res = speech->engine->load(speech, grammar_name, grammar);
+ }
+
+ return res;
+}
+
+/*! \brief Unload a local grammar from a speech structure */
+int ast_speech_grammar_unload(struct ast_speech *speech, char *grammar_name)
+{
+ int res = 0;
+
+ if (speech->engine->unload != NULL) {
+ res = speech->engine->unload(speech, grammar_name);
+ }
+
+ return res;
+}
+
+/*! \brief Return the results of a recognition from the speech structure */
+struct ast_speech_result *ast_speech_results_get(struct ast_speech *speech)
+{
+ struct ast_speech_result *result = NULL;
+
+ if (speech->engine->get != NULL) {
+ result = speech->engine->get(speech);
+ }
+
+ return result;
+}
+
+/*! \brief Free a list of results */
+int ast_speech_results_free(struct ast_speech_result *result)
+{
+ struct ast_speech_result *current_result = result, *prev_result = NULL;
+ int res = 0;
+
+ while (current_result != NULL) {
+ prev_result = current_result;
+ /* Deallocate what we can */
+ if (current_result->text != NULL) {
+ free(current_result->text);
+ current_result->text = NULL;
+ }
+ if (current_result->grammar != NULL) {
+ free(current_result->grammar);
+ current_result->grammar = NULL;
+ }
+ /* Move on and then free ourselves */
+ current_result = current_result->next;
+ free(prev_result);
+ prev_result = NULL;
+ }
+
+ return res;
+}
+
+/*! \brief Start speech recognition on a speech structure */
+void ast_speech_start(struct ast_speech *speech)
+{
+
+ /* Clear any flags that may affect things */
+ ast_clear_flag(speech, AST_SPEECH_SPOKE);
+ ast_clear_flag(speech, AST_SPEECH_QUIET);
+ ast_clear_flag(speech, AST_SPEECH_HAVE_RESULTS);
+
+ /* If results are on the structure, free them since we are starting again */
+ if (speech->results != NULL) {
+ ast_speech_results_free(speech->results);
+ speech->results = NULL;
+ }
+
+ /* If the engine needs to start stuff up, do it */
+ if (speech->engine->start != NULL) {
+ speech->engine->start(speech);
+ }
+
+ return;
+}
+
+/*! \brief Write in signed linear audio to be recognized */
+int ast_speech_write(struct ast_speech *speech, void *data, int len)
+{
+ int res = 0;
+
+ /* Make sure the speech engine is ready to accept audio */
+ if (speech->state != AST_SPEECH_STATE_READY) {
+ return -1;
+ }
+
+ if (speech->engine->write != NULL) {
+ speech->engine->write(speech, data, len);
+ }
+
+ return res;
+}
+
+/*! \brief Signal to the engine that DTMF was received */
+int ast_speech_dtmf(struct ast_speech *speech, const char *dtmf)
+{
+ int res = 0;
+
+ if (speech->state != AST_SPEECH_STATE_READY)
+ return -1;
+
+ if (speech->engine->dtmf != NULL) {
+ res = speech->engine->dtmf(speech, dtmf);
+ }
+
+ return res;
+}
+
+/*! \brief Change an engine specific attribute */
+int ast_speech_change(struct ast_speech *speech, char *name, const char *value)
+{
+ int res = 0;
+
+ if (speech->engine->change != NULL) {
+ res = speech->engine->change(speech, name, value);
+ }
+
+ return res;
+}
+
+/*! \brief Create a new speech structure using the engine specified */
+struct ast_speech *ast_speech_new(char *engine_name, int format)
+{
+ struct ast_speech_engine *engine = NULL;
+ struct ast_speech *new_speech = NULL;
+
+ /* Try to find the speech recognition engine that was requested */
+ engine = find_engine(engine_name);
+ if (engine == NULL) {
+ /* Invalid engine or no engine available */
+ return NULL;
+ }
+
+ /* Allocate our own speech structure, and try to allocate a structure from the engine too */
+ new_speech = ast_calloc(1, sizeof(*new_speech));
+ if (new_speech == NULL) {
+ /* Ran out of memory while trying to allocate some for a speech structure */
+ return NULL;
+ }
+
+ /* Initialize the lock */
+ ast_mutex_init(&new_speech->lock);
+
+ /* Make sure no results are present */
+ new_speech->results = NULL;
+
+ /* Copy over our engine pointer */
+ new_speech->engine = engine;
+
+ /* We are not ready to accept audio yet */
+ ast_speech_change_state(new_speech, AST_SPEECH_STATE_NOT_READY);
+
+ /* Pass ourselves to the engine so they can set us up some more and if they error out then do not create a structure */
+ if (engine->create(new_speech)) {
+ ast_mutex_destroy(&new_speech->lock);
+ free(new_speech);
+ new_speech = NULL;
+ }
+
+ return new_speech;
+}
+
+/*! \brief Destroy a speech structure */
+int ast_speech_destroy(struct ast_speech *speech)
+{
+ int res = 0;
+
+ /* Call our engine so we are destroyed properly */
+ speech->engine->destroy(speech);
+
+ /* Deinitialize the lock */
+ ast_mutex_destroy(&speech->lock);
+
+ /* If results exist on the speech structure, destroy them */
+ if (speech->results != NULL) {
+ ast_speech_results_free(speech->results);
+ speech->results = NULL;
+ }
+
+ /* If a processing sound is set - free the memory used by it */
+ if (speech->processing_sound != NULL) {
+ free(speech->processing_sound);
+ speech->processing_sound = NULL;
+ }
+
+ /* Aloha we are done */
+ free(speech);
+ speech = NULL;
+
+ return res;
+}
+
+/*! \brief Change state of a speech structure */
+int ast_speech_change_state(struct ast_speech *speech, int state)
+{
+ int res = 0;
+
+ switch (state) {
+ case AST_SPEECH_STATE_WAIT:
+ /* The engine heard audio, so they spoke */
+ ast_set_flag(speech, AST_SPEECH_SPOKE);
+ default:
+ speech->state = state;
+ break;
+ }
+
+ return res;
+}
+
+/*! \brief Change the type of results we want */
+int ast_speech_change_results_type(struct ast_speech *speech, enum ast_speech_results_type results_type)
+{
+ int res = 0;
+
+ speech->results_type = results_type;
+
+ if (speech->engine->change_results_type)
+ res = speech->engine->change_results_type(speech, results_type);
+
+ return res;
+}
+
+/*! \brief Register a speech recognition engine */
+int ast_speech_register(struct ast_speech_engine *engine)
+{
+ struct ast_speech_engine *existing_engine = NULL;
+ int res = 0;
+
+ existing_engine = find_engine(engine->name);
+ if (existing_engine != NULL) {
+ /* Engine already loaded */
+ return -1;
+ }
+
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "Registered speech recognition engine '%s'\n", engine->name);
+
+ /* Add to the engine linked list and make default if needed */
+ AST_LIST_LOCK(&engines);
+ AST_LIST_INSERT_HEAD(&engines, engine, list);
+ if (default_engine == NULL) {
+ default_engine = engine;
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "Made '%s' the default speech recognition engine\n", engine->name);
+ }
+ AST_LIST_UNLOCK(&engines);
+
+ return res;
+}
+
+/*! \brief Unregister a speech recognition engine */
+int ast_speech_unregister(char *engine_name)
+{
+ struct ast_speech_engine *engine = NULL;
+ int res = -1;
+
+ if (engine_name == NULL) {
+ return res;
+ }
+
+ AST_LIST_LOCK(&engines);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&engines, engine, list) {
+ if (!strcasecmp(engine->name, engine_name)) {
+ /* We have our engine... removed it */
+ AST_LIST_REMOVE_CURRENT(&engines, list);
+ /* If this was the default engine, we need to pick a new one */
+ if (default_engine == engine) {
+ default_engine = AST_LIST_FIRST(&engines);
+ }
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "Unregistered speech recognition engine '%s'\n", engine_name);
+ /* All went well */
+ res = 0;
+ break;
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END
+ AST_LIST_UNLOCK(&engines);
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ /* We can not be unloaded */
+ return -1;
+}
+
+static int load_module(void)
+{
+ int res = 0;
+
+ /* Initialize our list of engines */
+ AST_LIST_HEAD_INIT_NOLOCK(&engines);
+
+ return res;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Generic Speech Recognition API",
+ .load = load_module,
+ .unload = unload_module,
+ );
diff --git a/1.4.23-rc4/res/snmp/agent.c b/1.4.23-rc4/res/snmp/agent.c
new file mode 100644
index 000000000..96398e8a8
--- /dev/null
+++ b/1.4.23-rc4/res/snmp/agent.c
@@ -0,0 +1,823 @@
+/*
+ * Copyright (C) 2006 Voop as
+ * Thorsten Lockert <tholo@voop.as>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief SNMP Agent / SubAgent support for Asterisk
+ *
+ * \author Thorsten Lockert <tholo@voop.as>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <net-snmp/net-snmp-config.h>
+#include <net-snmp/net-snmp-includes.h>
+#include <net-snmp/agent/net-snmp-agent-includes.h>
+
+#include "asterisk/channel.h"
+#include "asterisk/logger.h"
+#include "asterisk/options.h"
+#include "asterisk/indications.h"
+#include "asterisk/version.h"
+#include "asterisk/pbx.h"
+
+/* Colission between Net-SNMP and Asterisk */
+#define unload_module ast_unload_module
+#include "asterisk/module.h"
+#undef unload_module
+
+#include "agent.h"
+
+/* Helper functions in Net-SNMP, header file not installed by default */
+int header_generic(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **);
+int header_simple_table(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **, int);
+int register_sysORTable(oid *, size_t, const char *);
+int unregister_sysORTable(oid *, size_t);
+
+/* Not defined in header files */
+extern char ast_config_AST_SOCKET[];
+
+/* Forward declaration */
+static void init_asterisk_mib(void);
+
+/*
+ * Anchor for all the Asterisk MIB values
+ */
+static oid asterisk_oid[] = { 1, 3, 6, 1, 4, 1, 22736, 1 };
+
+/*
+ * MIB values -- these correspond to values in the Asterisk MIB,
+ * and MUST be kept in sync with the MIB for things to work as
+ * expected.
+ */
+#define ASTVERSION 1
+#define ASTVERSTRING 1
+#define ASTVERTAG 2
+
+#define ASTCONFIGURATION 2
+#define ASTCONFUPTIME 1
+#define ASTCONFRELOADTIME 2
+#define ASTCONFPID 3
+#define ASTCONFSOCKET 4
+
+#define ASTMODULES 3
+#define ASTMODCOUNT 1
+
+#define ASTINDICATIONS 4
+#define ASTINDCOUNT 1
+#define ASTINDCURRENT 2
+
+#define ASTINDTABLE 3
+#define ASTINDINDEX 1
+#define ASTINDCOUNTRY 2
+#define ASTINDALIAS 3
+#define ASTINDDESCRIPTION 4
+
+#define ASTCHANNELS 5
+#define ASTCHANCOUNT 1
+
+#define ASTCHANTABLE 2
+#define ASTCHANINDEX 1
+#define ASTCHANNAME 2
+#define ASTCHANLANGUAGE 3
+#define ASTCHANTYPE 4
+#define ASTCHANMUSICCLASS 5
+#define ASTCHANBRIDGE 6
+#define ASTCHANMASQ 7
+#define ASTCHANMASQR 8
+#define ASTCHANWHENHANGUP 9
+#define ASTCHANAPP 10
+#define ASTCHANDATA 11
+#define ASTCHANCONTEXT 12
+#define ASTCHANMACROCONTEXT 13
+#define ASTCHANMACROEXTEN 14
+#define ASTCHANMACROPRI 15
+#define ASTCHANEXTEN 16
+#define ASTCHANPRI 17
+#define ASTCHANACCOUNTCODE 18
+#define ASTCHANFORWARDTO 19
+#define ASTCHANUNIQUEID 20
+#define ASTCHANCALLGROUP 21
+#define ASTCHANPICKUPGROUP 22
+#define ASTCHANSTATE 23
+#define ASTCHANMUTED 24
+#define ASTCHANRINGS 25
+#define ASTCHANCIDDNID 26
+#define ASTCHANCIDNUM 27
+#define ASTCHANCIDNAME 28
+#define ASTCHANCIDANI 29
+#define ASTCHANCIDRDNIS 30
+#define ASTCHANCIDPRES 31
+#define ASTCHANCIDANI2 32
+#define ASTCHANCIDTON 33
+#define ASTCHANCIDTNS 34
+#define ASTCHANAMAFLAGS 35
+#define ASTCHANADSI 36
+#define ASTCHANTONEZONE 37
+#define ASTCHANHANGUPCAUSE 38
+#define ASTCHANVARIABLES 39
+#define ASTCHANFLAGS 40
+#define ASTCHANTRANSFERCAP 41
+
+#define ASTCHANTYPECOUNT 3
+
+#define ASTCHANTYPETABLE 4
+#define ASTCHANTYPEINDEX 1
+#define ASTCHANTYPENAME 2
+#define ASTCHANTYPEDESC 3
+#define ASTCHANTYPEDEVSTATE 4
+#define ASTCHANTYPEINDICATIONS 5
+#define ASTCHANTYPETRANSFER 6
+#define ASTCHANTYPECHANNELS 7
+
+void *agent_thread(void *arg)
+{
+ ast_verbose(VERBOSE_PREFIX_2 "Starting %sAgent\n", res_snmp_agentx_subagent ? "Sub" : "");
+
+ snmp_enable_stderrlog();
+
+ if (res_snmp_agentx_subagent)
+ netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID,
+ NETSNMP_DS_AGENT_ROLE,
+ 1);
+
+ init_agent("asterisk");
+
+ init_asterisk_mib();
+
+ init_snmp("asterisk");
+
+ if (!res_snmp_agentx_subagent)
+ init_master_agent();
+
+ while (res_snmp_dont_stop)
+ agent_check_and_process(1);
+
+ snmp_shutdown("asterisk");
+
+ ast_verbose(VERBOSE_PREFIX_2 "Terminating %sAgent\n",
+ res_snmp_agentx_subagent ? "Sub" : "");
+
+ return NULL;
+}
+
+static u_char *
+ast_var_channels(struct variable *vp, oid *name, size_t *length,
+ int exact, size_t *var_len, WriteMethod **write_method)
+{
+ static unsigned long long_ret;
+
+ if (header_generic(vp, name, length, exact, var_len, write_method))
+ return NULL;
+
+ switch (vp->magic) {
+ case ASTCHANCOUNT:
+ long_ret = ast_active_channels();
+ return (u_char *)&long_ret;
+ default:
+ break;
+ }
+ return NULL;
+}
+
+static u_char *ast_var_channels_table(struct variable *vp, oid *name, size_t *length,
+ int exact, size_t *var_len, WriteMethod **write_method)
+{
+ static unsigned long long_ret;
+ static u_char bits_ret[2];
+ static char string_ret[256];
+ struct ast_channel *chan, *bridge;
+ struct timeval tval;
+ u_char *ret;
+ int i, bit;
+
+ if (header_simple_table(vp, name, length, exact, var_len, write_method, ast_active_channels()))
+ return NULL;
+
+ i = name[*length - 1] - 1;
+ for (chan = ast_channel_walk_locked(NULL);
+ chan && i;
+ chan = ast_channel_walk_locked(chan), i--)
+ ast_channel_unlock(chan);
+ if (chan == NULL)
+ return NULL;
+ *var_len = sizeof(long_ret);
+
+ switch (vp->magic) {
+ case ASTCHANINDEX:
+ long_ret = name[*length - 1];
+ ret = (u_char *)&long_ret;
+ break;
+ case ASTCHANNAME:
+ if (!ast_strlen_zero(chan->name)) {
+ strncpy(string_ret, chan->name, sizeof(string_ret));
+ string_ret[sizeof(string_ret) - 1] = '\0';
+ *var_len = strlen(string_ret);
+ ret = (u_char *)string_ret;
+ }
+ else
+ ret = NULL;
+ break;
+ case ASTCHANLANGUAGE:
+ if (!ast_strlen_zero(chan->language)) {
+ strncpy(string_ret, chan->language, sizeof(string_ret));
+ string_ret[sizeof(string_ret) - 1] = '\0';
+ *var_len = strlen(string_ret);
+ ret = (u_char *)string_ret;
+ }
+ else
+ ret = NULL;
+ break;
+ case ASTCHANTYPE:
+ strncpy(string_ret, chan->tech->type, sizeof(string_ret));
+ string_ret[sizeof(string_ret) - 1] = '\0';
+ *var_len = strlen(string_ret);
+ ret = (u_char *)string_ret;
+ break;
+ case ASTCHANMUSICCLASS:
+ if (!ast_strlen_zero(chan->musicclass)) {
+ strncpy(string_ret, chan->musicclass, sizeof(string_ret));
+ string_ret[sizeof(string_ret) - 1] = '\0';
+ *var_len = strlen(string_ret);
+ ret = (u_char *)string_ret;
+ }
+ else
+ ret = NULL;
+ break;
+ case ASTCHANBRIDGE:
+ if ((bridge = ast_bridged_channel(chan)) != NULL) {
+ strncpy(string_ret, bridge->name, sizeof(string_ret));
+ string_ret[sizeof(string_ret) - 1] = '\0';
+ *var_len = strlen(string_ret);
+ ret = (u_char *)string_ret;
+ }
+ else
+ ret = NULL;
+ break;
+ case ASTCHANMASQ:
+ if (chan->masq && !ast_strlen_zero(chan->masq->name)) {
+ strncpy(string_ret, chan->masq->name, sizeof(string_ret));
+ string_ret[sizeof(string_ret) - 1] = '\0';
+ *var_len = strlen(string_ret);
+ ret = (u_char *)string_ret;
+ }
+ else
+ ret = NULL;
+ break;
+ case ASTCHANMASQR:
+ if (chan->masqr && !ast_strlen_zero(chan->masqr->name)) {
+ strncpy(string_ret, chan->masqr->name, sizeof(string_ret));
+ string_ret[sizeof(string_ret) - 1] = '\0';
+ *var_len = strlen(string_ret);
+ ret = (u_char *)string_ret;
+ }
+ else
+ ret = NULL;
+ break;
+ case ASTCHANWHENHANGUP:
+ if (chan->whentohangup) {
+ gettimeofday(&tval, NULL);
+ long_ret = difftime(chan->whentohangup, tval.tv_sec) * 100 - tval.tv_usec / 10000;
+ ret= (u_char *)&long_ret;
+ }
+ else
+ ret = NULL;
+ break;
+ case ASTCHANAPP:
+ if (chan->appl) {
+ strncpy(string_ret, chan->appl, sizeof(string_ret));
+ string_ret[sizeof(string_ret) - 1] = '\0';
+ *var_len = strlen(string_ret);
+ ret = (u_char *)string_ret;
+ }
+ else
+ ret = NULL;
+ break;
+ case ASTCHANDATA:
+ if (chan->data) {
+ strncpy(string_ret, chan->data, sizeof(string_ret));
+ string_ret[sizeof(string_ret) - 1] = '\0';
+ *var_len = strlen(string_ret);
+ ret = (u_char *)string_ret;
+ }
+ else
+ ret = NULL;
+ break;
+ case ASTCHANCONTEXT:
+ strncpy(string_ret, chan->context, sizeof(string_ret));
+ string_ret[sizeof(string_ret) - 1] = '\0';
+ *var_len = strlen(string_ret);
+ ret = (u_char *)string_ret;
+ break;
+ case ASTCHANMACROCONTEXT:
+ strncpy(string_ret, chan->macrocontext, sizeof(string_ret));
+ string_ret[sizeof(string_ret) - 1] = '\0';
+ *var_len = strlen(string_ret);
+ ret = (u_char *)string_ret;
+ break;
+ case ASTCHANMACROEXTEN:
+ strncpy(string_ret, chan->macroexten, sizeof(string_ret));
+ string_ret[sizeof(string_ret) - 1] = '\0';
+ *var_len = strlen(string_ret);
+ ret = (u_char *)string_ret;
+ break;
+ case ASTCHANMACROPRI:
+ long_ret = chan->macropriority;
+ ret = (u_char *)&long_ret;
+ break;
+ case ASTCHANEXTEN:
+ strncpy(string_ret, chan->exten, sizeof(string_ret));
+ string_ret[sizeof(string_ret) - 1] = '\0';
+ *var_len = strlen(string_ret);
+ ret = (u_char *)string_ret;
+ break;
+ case ASTCHANPRI:
+ long_ret = chan->priority;
+ ret = (u_char *)&long_ret;
+ break;
+ case ASTCHANACCOUNTCODE:
+ if (!ast_strlen_zero(chan->accountcode)) {
+ strncpy(string_ret, chan->accountcode, sizeof(string_ret));
+ string_ret[sizeof(string_ret) - 1] = '\0';
+ *var_len = strlen(string_ret);
+ ret = (u_char *)string_ret;
+ }
+ else
+ ret = NULL;
+ break;
+ case ASTCHANFORWARDTO:
+ if (!ast_strlen_zero(chan->call_forward)) {
+ strncpy(string_ret, chan->call_forward, sizeof(string_ret));
+ string_ret[sizeof(string_ret) - 1] = '\0';
+ *var_len = strlen(string_ret);
+ ret = (u_char *)string_ret;
+ }
+ else
+ ret = NULL;
+ break;
+ case ASTCHANUNIQUEID:
+ strncpy(string_ret, chan->uniqueid, sizeof(string_ret));
+ string_ret[sizeof(string_ret) - 1] = '\0';
+ *var_len = strlen(string_ret);
+ ret = (u_char *)string_ret;
+ break;
+ case ASTCHANCALLGROUP:
+ long_ret = chan->callgroup;
+ ret = (u_char *)&long_ret;
+ break;
+ case ASTCHANPICKUPGROUP:
+ long_ret = chan->pickupgroup;
+ ret = (u_char *)&long_ret;
+ break;
+ case ASTCHANSTATE:
+ long_ret = chan->_state & 0xffff;
+ ret = (u_char *)&long_ret;
+ break;
+ case ASTCHANMUTED:
+ long_ret = chan->_state & AST_STATE_MUTE ? 1 : 2;
+ ret = (u_char *)&long_ret;
+ break;
+ case ASTCHANRINGS:
+ long_ret = chan->rings;
+ ret = (u_char *)&long_ret;
+ break;
+ case ASTCHANCIDDNID:
+ if (chan->cid.cid_dnid) {
+ strncpy(string_ret, chan->cid.cid_dnid, sizeof(string_ret));
+ string_ret[sizeof(string_ret) - 1] = '\0';
+ *var_len = strlen(string_ret);
+ ret = (u_char *)string_ret;
+ }
+ else
+ ret = NULL;
+ break;
+ case ASTCHANCIDNUM:
+ if (chan->cid.cid_num) {
+ strncpy(string_ret, chan->cid.cid_num, sizeof(string_ret));
+ string_ret[sizeof(string_ret) - 1] = '\0';
+ *var_len = strlen(string_ret);
+ ret = (u_char *)string_ret;
+ }
+ else
+ ret = NULL;
+ break;
+ case ASTCHANCIDNAME:
+ if (chan->cid.cid_name) {
+ strncpy(string_ret, chan->cid.cid_name, sizeof(string_ret));
+ string_ret[sizeof(string_ret) - 1] = '\0';
+ *var_len = strlen(string_ret);
+ ret = (u_char *)string_ret;
+ }
+ else
+ ret = NULL;
+ break;
+ case ASTCHANCIDANI:
+ if (chan->cid.cid_ani) {
+ strncpy(string_ret, chan->cid.cid_ani, sizeof(string_ret));
+ string_ret[sizeof(string_ret) - 1] = '\0';
+ *var_len = strlen(string_ret);
+ ret = (u_char *)string_ret;
+ }
+ else
+ ret = NULL;
+ break;
+ case ASTCHANCIDRDNIS:
+ if (chan->cid.cid_rdnis) {
+ strncpy(string_ret, chan->cid.cid_rdnis, sizeof(string_ret));
+ string_ret[sizeof(string_ret) - 1] = '\0';
+ *var_len = strlen(string_ret);
+ ret = (u_char *)string_ret;
+ }
+ else
+ ret = NULL;
+ break;
+ case ASTCHANCIDPRES:
+ long_ret = chan->cid.cid_pres;
+ ret = (u_char *)&long_ret;
+ break;
+ case ASTCHANCIDANI2:
+ long_ret = chan->cid.cid_ani2;
+ ret = (u_char *)&long_ret;
+ break;
+ case ASTCHANCIDTON:
+ long_ret = chan->cid.cid_ton;
+ ret = (u_char *)&long_ret;
+ break;
+ case ASTCHANCIDTNS:
+ long_ret = chan->cid.cid_tns;
+ ret = (u_char *)&long_ret;
+ break;
+ case ASTCHANAMAFLAGS:
+ long_ret = chan->amaflags;
+ ret = (u_char *)&long_ret;
+ break;
+ case ASTCHANADSI:
+ long_ret = chan->adsicpe;
+ ret = (u_char *)&long_ret;
+ break;
+ case ASTCHANTONEZONE:
+ if (chan->zone) {
+ strncpy(string_ret, chan->zone->country, sizeof(string_ret));
+ string_ret[sizeof(string_ret) - 1] = '\0';
+ *var_len = strlen(string_ret);
+ ret = (u_char *)string_ret;
+ }
+ else
+ ret = NULL;
+ break;
+ case ASTCHANHANGUPCAUSE:
+ long_ret = chan->hangupcause;
+ ret = (u_char *)&long_ret;
+ break;
+ case ASTCHANVARIABLES:
+ if (pbx_builtin_serialize_variables(chan, string_ret, sizeof(string_ret))) {
+ *var_len = strlen(string_ret);
+ ret = (u_char *)string_ret;
+ }
+ else
+ ret = NULL;
+ break;
+ case ASTCHANFLAGS:
+ bits_ret[0] = 0;
+ for (bit = 0; bit < 8; bit++)
+ bits_ret[0] |= ((chan->flags & (1 << bit)) >> bit) << (7 - bit);
+ bits_ret[1] = 0;
+ for (bit = 0; bit < 8; bit++)
+ bits_ret[1] |= (((chan->flags >> 8) & (1 << bit)) >> bit) << (7 - bit);
+ *var_len = 2;
+ ret = bits_ret;
+ break;
+ case ASTCHANTRANSFERCAP:
+ long_ret = chan->transfercapability;
+ ret = (u_char *)&long_ret;
+ break;
+ default:
+ ret = NULL;
+ break;
+ }
+ ast_channel_unlock(chan);
+ return ret;
+}
+
+static u_char *ast_var_channel_types(struct variable *vp, oid *name, size_t *length,
+ int exact, size_t *var_len, WriteMethod **write_method)
+{
+ static unsigned long long_ret;
+ struct ast_variable *channel_types, *next;
+
+ if (header_generic(vp, name, length, exact, var_len, write_method))
+ return NULL;
+
+ switch (vp->magic) {
+ case ASTCHANTYPECOUNT:
+ long_ret = 0;
+ for (channel_types = next = ast_channeltype_list(); next; next = next->next) {
+ long_ret++;
+ }
+ ast_variables_destroy(channel_types);
+ return (u_char *)&long_ret;
+ default:
+ break;
+ }
+ return NULL;
+}
+
+static u_char *ast_var_channel_types_table(struct variable *vp, oid *name, size_t *length,
+ int exact, size_t *var_len, WriteMethod **write_method)
+{
+ const struct ast_channel_tech *tech = NULL;
+ struct ast_variable *channel_types, *next;
+ static unsigned long long_ret;
+ struct ast_channel *chan;
+ u_long i;
+
+ if (header_simple_table(vp, name, length, exact, var_len, write_method, -1))
+ return NULL;
+
+ channel_types = ast_channeltype_list();
+ for (i = 1, next = channel_types; next && i != name[*length - 1]; next = next->next, i++)
+ ;
+ if (next != NULL)
+ tech = ast_get_channel_tech(next->name);
+ ast_variables_destroy(channel_types);
+ if (next == NULL || tech == NULL)
+ return NULL;
+
+ switch (vp->magic) {
+ case ASTCHANTYPEINDEX:
+ long_ret = name[*length - 1];
+ return (u_char *)&long_ret;
+ case ASTCHANTYPENAME:
+ *var_len = strlen(tech->type);
+ return (u_char *)tech->type;
+ case ASTCHANTYPEDESC:
+ *var_len = strlen(tech->description);
+ return (u_char *)tech->description;
+ case ASTCHANTYPEDEVSTATE:
+ long_ret = tech->devicestate ? 1 : 2;
+ return (u_char *)&long_ret;
+ case ASTCHANTYPEINDICATIONS:
+ long_ret = tech->indicate ? 1 : 2;
+ return (u_char *)&long_ret;
+ case ASTCHANTYPETRANSFER:
+ long_ret = tech->transfer ? 1 : 2;
+ return (u_char *)&long_ret;
+ case ASTCHANTYPECHANNELS:
+ long_ret = 0;
+ for (chan = ast_channel_walk_locked(NULL); chan; chan = ast_channel_walk_locked(chan)) {
+ ast_channel_unlock(chan);
+ if (chan->tech == tech)
+ long_ret++;
+ }
+ return (u_char *)&long_ret;
+ default:
+ break;
+ }
+ return NULL;
+}
+
+static u_char *ast_var_Config(struct variable *vp, oid *name, size_t *length,
+ int exact, size_t *var_len, WriteMethod **write_method)
+{
+ static unsigned long long_ret;
+ struct timeval tval;
+
+ if (header_generic(vp, name, length, exact, var_len, write_method))
+ return NULL;
+
+ switch (vp->magic) {
+ case ASTCONFUPTIME:
+ gettimeofday(&tval, NULL);
+ long_ret = difftime(tval.tv_sec, ast_startuptime) * 100 + tval.tv_usec / 10000;
+ return (u_char *)&long_ret;
+ case ASTCONFRELOADTIME:
+ gettimeofday(&tval, NULL);
+ if (ast_lastreloadtime)
+ long_ret = difftime(tval.tv_sec, ast_lastreloadtime) * 100 + tval.tv_usec / 10000;
+ else
+ long_ret = difftime(tval.tv_sec, ast_startuptime) * 100 + tval.tv_usec / 10000;
+ return (u_char *)&long_ret;
+ case ASTCONFPID:
+ long_ret = getpid();
+ return (u_char *)&long_ret;
+ case ASTCONFSOCKET:
+ *var_len = strlen(ast_config_AST_SOCKET);
+ return (u_char *)ast_config_AST_SOCKET;
+ default:
+ break;
+ }
+ return NULL;
+}
+
+static u_char *ast_var_indications(struct variable *vp, oid *name, size_t *length,
+ int exact, size_t *var_len, WriteMethod **write_method)
+{
+ static unsigned long long_ret;
+ struct tone_zone *tz = NULL;
+
+ if (header_generic(vp, name, length, exact, var_len, write_method))
+ return NULL;
+
+ switch (vp->magic) {
+ case ASTINDCOUNT:
+ long_ret = 0;
+ while ( (tz = ast_walk_indications(tz)) )
+ long_ret++;
+
+ return (u_char *)&long_ret;
+ case ASTINDCURRENT:
+ tz = ast_get_indication_zone(NULL);
+ if (tz) {
+ *var_len = strlen(tz->country);
+ return (u_char *)tz->country;
+ }
+ *var_len = 0;
+ return NULL;
+ default:
+ break;
+ }
+ return NULL;
+}
+
+static u_char *ast_var_indications_table(struct variable *vp, oid *name, size_t *length,
+ int exact, size_t *var_len, WriteMethod **write_method)
+{
+ static unsigned long long_ret;
+ struct tone_zone *tz = NULL;
+ int i;
+
+ if (header_simple_table(vp, name, length, exact, var_len, write_method, -1))
+ return NULL;
+
+ i = name[*length - 1] - 1;
+ while ( (tz = ast_walk_indications(tz)) && i )
+ i--;
+ if (tz == NULL)
+ return NULL;
+
+ switch (vp->magic) {
+ case ASTINDINDEX:
+ long_ret = name[*length - 1];
+ return (u_char *)&long_ret;
+ case ASTINDCOUNTRY:
+ *var_len = strlen(tz->country);
+ return (u_char *)tz->country;
+ case ASTINDALIAS:
+ if (tz->alias) {
+ *var_len = strlen(tz->alias);
+ return (u_char *)tz->alias;
+ }
+ return NULL;
+ case ASTINDDESCRIPTION:
+ *var_len = strlen(tz->description);
+ return (u_char *)tz->description;
+ default:
+ break;
+ }
+ return NULL;
+}
+
+static int countmodule(const char *mod, const char *desc, int use, const char *like)
+{
+ return 1;
+}
+
+static u_char *ast_var_Modules(struct variable *vp, oid *name, size_t *length,
+ int exact, size_t *var_len, WriteMethod **write_method)
+{
+ static unsigned long long_ret;
+
+ if (header_generic(vp, name, length, exact, var_len, write_method))
+ return NULL;
+
+ switch (vp->magic) {
+ case ASTMODCOUNT:
+ long_ret = ast_update_module_list(countmodule, NULL);
+ return (u_char *)&long_ret;
+ default:
+ break;
+ }
+ return NULL;
+}
+
+static u_char *ast_var_Version(struct variable *vp, oid *name, size_t *length,
+ int exact, size_t *var_len, WriteMethod **write_method)
+{
+ static unsigned long long_ret;
+
+ if (header_generic(vp, name, length, exact, var_len, write_method))
+ return NULL;
+
+ switch (vp->magic) {
+ case ASTVERSTRING:
+ *var_len = strlen(ASTERISK_VERSION);
+ return (u_char *)ASTERISK_VERSION;
+ case ASTVERTAG:
+ long_ret = ASTERISK_VERSION_NUM;
+ return (u_char *)&long_ret;
+ default:
+ break;
+ }
+ return NULL;
+}
+
+static int term_asterisk_mib(int majorID, int minorID, void *serverarg, void *clientarg)
+{
+ unregister_sysORTable(asterisk_oid, OID_LENGTH(asterisk_oid));
+ return 0;
+}
+
+static void init_asterisk_mib(void)
+{
+ static struct variable4 asterisk_vars[] = {
+ {ASTVERSTRING, ASN_OCTET_STR, RONLY, ast_var_Version, 2, {ASTVERSION, ASTVERSTRING}},
+ {ASTVERTAG, ASN_UNSIGNED, RONLY, ast_var_Version, 2, {ASTVERSION, ASTVERTAG}},
+ {ASTCONFUPTIME, ASN_TIMETICKS, RONLY, ast_var_Config, 2, {ASTCONFIGURATION, ASTCONFUPTIME}},
+ {ASTCONFRELOADTIME, ASN_TIMETICKS, RONLY, ast_var_Config, 2, {ASTCONFIGURATION, ASTCONFRELOADTIME}},
+ {ASTCONFPID, ASN_INTEGER, RONLY, ast_var_Config, 2, {ASTCONFIGURATION, ASTCONFPID}},
+ {ASTCONFSOCKET, ASN_OCTET_STR, RONLY, ast_var_Config, 2, {ASTCONFIGURATION, ASTCONFSOCKET}},
+ {ASTMODCOUNT, ASN_INTEGER, RONLY, ast_var_Modules , 2, {ASTMODULES, ASTMODCOUNT}},
+ {ASTINDCOUNT, ASN_INTEGER, RONLY, ast_var_indications, 2, {ASTINDICATIONS, ASTINDCOUNT}},
+ {ASTINDCURRENT, ASN_OCTET_STR, RONLY, ast_var_indications, 2, {ASTINDICATIONS, ASTINDCURRENT}},
+ {ASTINDINDEX, ASN_INTEGER, RONLY, ast_var_indications_table, 4, {ASTINDICATIONS, ASTINDTABLE, 1, ASTINDINDEX}},
+ {ASTINDCOUNTRY, ASN_OCTET_STR, RONLY, ast_var_indications_table, 4, {ASTINDICATIONS, ASTINDTABLE, 1, ASTINDCOUNTRY}},
+ {ASTINDALIAS, ASN_OCTET_STR, RONLY, ast_var_indications_table, 4, {ASTINDICATIONS, ASTINDTABLE, 1, ASTINDALIAS}},
+ {ASTINDDESCRIPTION, ASN_OCTET_STR, RONLY, ast_var_indications_table, 4, {ASTINDICATIONS, ASTINDTABLE, 1, ASTINDDESCRIPTION}},
+ {ASTCHANCOUNT, ASN_GAUGE, RONLY, ast_var_channels, 2, {ASTCHANNELS, ASTCHANCOUNT}},
+ {ASTCHANINDEX, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANINDEX}},
+ {ASTCHANNAME, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANNAME}},
+ {ASTCHANLANGUAGE, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANLANGUAGE}},
+ {ASTCHANTYPE, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANTYPE}},
+ {ASTCHANMUSICCLASS, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANMUSICCLASS}},
+ {ASTCHANBRIDGE, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANBRIDGE}},
+ {ASTCHANMASQ, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANMASQ}},
+ {ASTCHANMASQR, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANMASQR}},
+ {ASTCHANWHENHANGUP, ASN_TIMETICKS, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANWHENHANGUP}},
+ {ASTCHANAPP, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANAPP}},
+ {ASTCHANDATA, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANDATA}},
+ {ASTCHANCONTEXT, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCONTEXT}},
+ {ASTCHANMACROCONTEXT, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANMACROCONTEXT}},
+ {ASTCHANMACROEXTEN, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANMACROEXTEN}},
+ {ASTCHANMACROPRI, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANMACROPRI}},
+ {ASTCHANEXTEN, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANEXTEN}},
+ {ASTCHANPRI, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANPRI}},
+ {ASTCHANACCOUNTCODE, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANACCOUNTCODE}},
+ {ASTCHANFORWARDTO, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANFORWARDTO}},
+ {ASTCHANUNIQUEID, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANUNIQUEID}},
+ {ASTCHANCALLGROUP, ASN_UNSIGNED, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCALLGROUP}},
+ {ASTCHANPICKUPGROUP, ASN_UNSIGNED, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANPICKUPGROUP}},
+ {ASTCHANSTATE, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANSTATE}},
+ {ASTCHANMUTED, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANMUTED}},
+ {ASTCHANRINGS, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANRINGS}},
+ {ASTCHANCIDDNID, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDDNID}},
+ {ASTCHANCIDNUM, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDNUM}},
+ {ASTCHANCIDNAME, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDNAME}},
+ {ASTCHANCIDANI, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDANI}},
+ {ASTCHANCIDRDNIS, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDRDNIS}},
+ {ASTCHANCIDPRES, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDPRES}},
+ {ASTCHANCIDANI2, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDANI2}},
+ {ASTCHANCIDTON, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDTON}},
+ {ASTCHANCIDTNS, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDTNS}},
+ {ASTCHANAMAFLAGS, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANAMAFLAGS}},
+ {ASTCHANADSI, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANADSI}},
+ {ASTCHANTONEZONE, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANTONEZONE}},
+ {ASTCHANHANGUPCAUSE, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANHANGUPCAUSE}},
+ {ASTCHANVARIABLES, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANVARIABLES}},
+ {ASTCHANFLAGS, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANFLAGS}},
+ {ASTCHANTRANSFERCAP, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANTRANSFERCAP}},
+ {ASTCHANTYPECOUNT, ASN_INTEGER, RONLY, ast_var_channel_types, 2, {ASTCHANNELS, ASTCHANTYPECOUNT}},
+ {ASTCHANTYPEINDEX, ASN_INTEGER, RONLY, ast_var_channel_types_table, 4, {ASTCHANNELS, ASTCHANTYPETABLE, 1, ASTCHANTYPEINDEX}},
+ {ASTCHANTYPENAME, ASN_OCTET_STR, RONLY, ast_var_channel_types_table, 4, {ASTCHANNELS, ASTCHANTYPETABLE, 1, ASTCHANTYPENAME}},
+ {ASTCHANTYPEDESC, ASN_OCTET_STR, RONLY, ast_var_channel_types_table, 4, {ASTCHANNELS, ASTCHANTYPETABLE, 1, ASTCHANTYPEDESC}},
+ {ASTCHANTYPEDEVSTATE, ASN_INTEGER, RONLY, ast_var_channel_types_table, 4, {ASTCHANNELS, ASTCHANTYPETABLE, 1, ASTCHANTYPEDEVSTATE}},
+ {ASTCHANTYPEINDICATIONS, ASN_INTEGER, RONLY, ast_var_channel_types_table, 4, {ASTCHANNELS, ASTCHANTYPETABLE, 1, ASTCHANTYPEINDICATIONS}},
+ {ASTCHANTYPETRANSFER, ASN_INTEGER, RONLY, ast_var_channel_types_table, 4, {ASTCHANNELS, ASTCHANTYPETABLE, 1, ASTCHANTYPETRANSFER}},
+ {ASTCHANTYPECHANNELS, ASN_GAUGE, RONLY, ast_var_channel_types_table, 4, {ASTCHANNELS, ASTCHANTYPETABLE, 1, ASTCHANTYPECHANNELS}},
+ };
+
+ register_sysORTable(asterisk_oid, OID_LENGTH(asterisk_oid),
+ "ASTERISK-MIB implementation for Asterisk.");
+
+ REGISTER_MIB("res_snmp", asterisk_vars, variable4, asterisk_oid);
+
+ snmp_register_callback(SNMP_CALLBACK_LIBRARY,
+ SNMP_CALLBACK_SHUTDOWN,
+ term_asterisk_mib, NULL);
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * c-file-offsets: ((case-label . 0))
+ * tab-width: 4
+ * indent-tabs-mode: t
+ * End:
+ */
diff --git a/1.4.23-rc4/res/snmp/agent.h b/1.4.23-rc4/res/snmp/agent.h
new file mode 100644
index 000000000..21389d6c9
--- /dev/null
+++ b/1.4.23-rc4/res/snmp/agent.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2006 Voop as
+ * Thorsten Lockert <tholo@voop.as>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief SNMP Agent / SubAgent support for Asterisk
+ *
+ * \author Thorsten Lockert <tholo@voop.as>
+ */
+
+/*!
+ * \internal
+ * \brief Thread running the SNMP Agent or Subagent
+ * \param Not used -- required by pthread_create
+ * \return A pointer with return status -- not used
+ *
+ * This represent the main thread of the SNMP [sub]agent, and
+ * will initialize SNMP and loop, processing requests until
+ * termination is requested by resetting the flag in
+ * \ref res_snmp_dontStop.
+ */
+void *agent_thread(void *);
+
+/*!
+ * \internal
+ * Flag saying whether we run as a Subagent or full Agent
+ */
+extern int res_snmp_agentx_subagent;
+
+/*!
+ * \internal
+ * Flag stating the agent thread should not terminate
+ */
+extern int res_snmp_dont_stop;