diff options
Diffstat (limited to '1.4.23-rc4/res')
-rw-r--r-- | 1.4.23-rc4/res/Makefile | 39 | ||||
-rw-r--r-- | 1.4.23-rc4/res/res_adsi.c | 1132 | ||||
-rw-r--r-- | 1.4.23-rc4/res/res_agi.c | 2226 | ||||
-rw-r--r-- | 1.4.23-rc4/res/res_clioriginate.c | 175 | ||||
-rw-r--r-- | 1.4.23-rc4/res/res_config_odbc.c | 564 | ||||
-rw-r--r-- | 1.4.23-rc4/res/res_config_pgsql.c | 842 | ||||
-rw-r--r-- | 1.4.23-rc4/res/res_convert.c | 224 | ||||
-rw-r--r-- | 1.4.23-rc4/res/res_crypto.c | 631 | ||||
-rw-r--r-- | 1.4.23-rc4/res/res_features.c | 2755 | ||||
-rw-r--r-- | 1.4.23-rc4/res/res_indications.c | 406 | ||||
-rw-r--r-- | 1.4.23-rc4/res/res_jabber.c | 2518 | ||||
-rw-r--r-- | 1.4.23-rc4/res/res_monitor.c | 714 | ||||
-rw-r--r-- | 1.4.23-rc4/res/res_musiconhold.c | 1480 | ||||
-rw-r--r-- | 1.4.23-rc4/res/res_odbc.c | 741 | ||||
-rw-r--r-- | 1.4.23-rc4/res/res_smdi.c | 1384 | ||||
-rw-r--r-- | 1.4.23-rc4/res/res_snmp.c | 115 | ||||
-rw-r--r-- | 1.4.23-rc4/res/res_speech.c | 404 | ||||
-rw-r--r-- | 1.4.23-rc4/res/snmp/agent.c | 823 | ||||
-rw-r--r-- | 1.4.23-rc4/res/snmp/agent.h | 40 |
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, ¬e); + } + 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(¤t->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; |