aboutsummaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
Diffstat (limited to 'apps')
-rw-r--r--apps/Makefile42
-rw-r--r--apps/app_adsiprog.c1592
-rw-r--r--apps/app_alarmreceiver.c841
-rw-r--r--apps/app_amd.c430
-rw-r--r--apps/app_authenticate.c254
-rw-r--r--apps/app_cdr.c78
-rw-r--r--apps/app_chanisavail.c173
-rw-r--r--apps/app_channelredirect.c140
-rw-r--r--apps/app_chanspy.c878
-rw-r--r--apps/app_controlplayback.c168
-rw-r--r--apps/app_dahdibarge.c358
-rw-r--r--apps/app_dahdiras.c288
-rw-r--r--apps/app_dahdiscan.c389
-rw-r--r--apps/app_db.c167
-rw-r--r--apps/app_dial.c2015
-rw-r--r--apps/app_dictate.c349
-rw-r--r--apps/app_directed_pickup.c181
-rw-r--r--apps/app_directory.c707
-rw-r--r--apps/app_disa.c399
-rw-r--r--apps/app_dumpchan.c176
-rw-r--r--apps/app_echo.c104
-rw-r--r--apps/app_exec.c221
-rw-r--r--apps/app_externalivr.c585
-rw-r--r--apps/app_festival.c566
-rw-r--r--apps/app_flash.c136
-rw-r--r--apps/app_followme.c1138
-rw-r--r--apps/app_forkcdr.c267
-rw-r--r--apps/app_getcpeid.c148
-rw-r--r--apps/app_hasnewvoicemail.c225
-rw-r--r--apps/app_ices.c233
-rw-r--r--apps/app_image.c125
-rw-r--r--apps/app_ivrdemo.c132
-rw-r--r--apps/app_lookupblacklist.c160
-rw-r--r--apps/app_lookupcidname.c103
-rw-r--r--apps/app_macro.c592
-rw-r--r--apps/app_meetme.c5037
-rw-r--r--apps/app_milliwatt.c194
-rw-r--r--apps/app_mixmonitor.c446
-rw-r--r--apps/app_morsecode.c179
-rw-r--r--apps/app_mp3.c255
-rw-r--r--apps/app_nbscat.c237
-rw-r--r--apps/app_osplookup.c1677
-rw-r--r--apps/app_page.c217
-rw-r--r--apps/app_parkandannounce.c260
-rw-r--r--apps/app_playback.c494
-rw-r--r--apps/app_privacy.c232
-rw-r--r--apps/app_queue.c5140
-rw-r--r--apps/app_random.c108
-rw-r--r--apps/app_read.c234
-rw-r--r--apps/app_readfile.c120
-rw-r--r--apps/app_realtime.c263
-rw-r--r--apps/app_record.c386
-rw-r--r--apps/app_rpt.c11930
-rw-r--r--apps/app_sayunixtime.c126
-rw-r--r--apps/app_senddtmf.c143
-rw-r--r--apps/app_sendtext.c131
-rw-r--r--apps/app_setcallerid.c162
-rw-r--r--apps/app_setcdruserfield.c175
-rw-r--r--apps/app_settransfercapability.c136
-rw-r--r--apps/app_skel.c133
-rw-r--r--apps/app_sms.c1538
-rw-r--r--apps/app_softhangup.c121
-rw-r--r--apps/app_speech_utils.c875
-rw-r--r--apps/app_stack.c174
-rw-r--r--apps/app_system.c162
-rw-r--r--apps/app_talkdetect.c227
-rw-r--r--apps/app_test.c512
-rw-r--r--apps/app_transfer.c156
-rw-r--r--apps/app_url.c173
-rw-r--r--apps/app_userevent.c109
-rw-r--r--apps/app_verbose.c168
-rw-r--r--apps/app_voicemail.c9268
-rw-r--r--apps/app_waitforring.c136
-rw-r--r--apps/app_waitforsilence.c207
-rw-r--r--apps/app_while.c339
-rw-r--r--apps/app_zapateller.c120
-rw-r--r--apps/enter.h287
-rw-r--r--apps/leave.h207
-rw-r--r--apps/rpt_flow.pdfbin0 -> 51935 bytes
79 files changed, 57554 insertions, 0 deletions
diff --git a/apps/Makefile b/apps/Makefile
new file mode 100644
index 000000000..c57926a6a
--- /dev/null
+++ b/apps/Makefile
@@ -0,0 +1,42 @@
+#
+# Asterisk -- A telephony toolkit for Linux.
+#
+# Makefile for PBX applications
+#
+# 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=APPS
+MENUSELECT_DESCRIPTION=Applications
+
+ALL_C_MODS:=$(patsubst %.c,%,$(wildcard app_*.c))
+ALL_CC_MODS:=$(patsubst %.cc,%,$(wildcard app_*.cc))
+
+C_MODS:=$(filter-out $(MENUSELECT_APPS),$(ALL_C_MODS))
+CC_MODS:=$(filter-out $(MENUSELECT_APPS),$(ALL_CC_MODS))
+
+LOADABLE_MODS:=$(C_MODS) $(CC_MODS)
+
+ifneq ($(findstring apps,$(MENUSELECT_EMBED)),)
+ EMBEDDED_MODS:=$(LOADABLE_MODS)
+ LOADABLE_MODS:=
+endif
+
+MENUSELECT_OPTS_app_directory:=$(MENUSELECT_OPTS_app_voicemail)
+ifneq ($(findstring ODBC_STORAGE,$(MENUSELECT_OPTS_app_voicemail)),)
+MENUSELECT_DEPENDS_app_voicemail+=$(MENUSELECT_DEPENDS_ODBC_STORAGE)
+MENUSELECT_DEPENDS_app_directory+=$(MENUSELECT_DEPENDS_ODBC_STORAGE)
+endif
+ifneq ($(findstring IMAP_STORAGE,$(MENUSELECT_OPTS_app_voicemail)),)
+MENUSELECT_DEPENDS_app_voicemail+=$(MENUSELECT_DEPENDS_IMAP_STORAGE)
+MENUSELECT_DEPENDS_app_directory+=$(MENUSELECT_DEPENDS_IMAP_STORAGE)
+endif
+
+all: _all
+
+include $(ASTTOPDIR)/Makefile.moddir_rules
diff --git a/apps/app_adsiprog.c b/apps/app_adsiprog.c
new file mode 100644
index 000000000..750cc6fc7
--- /dev/null
+++ b/apps/app_adsiprog.c
@@ -0,0 +1,1592 @@
+/*
+ * 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 Program Asterisk ADSI Scripts into phone
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+ <depend>res_adsi</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/adsi.h"
+#include "asterisk/options.h"
+#include "asterisk/utils.h"
+#include "asterisk/lock.h"
+
+static char *app = "ADSIProg";
+
+static char *synopsis = "Load Asterisk ADSI Scripts into phone";
+
+/* #define DUMP_MESSAGES */
+
+static char *descrip =
+" ADSIProg(script): This application programs an ADSI Phone with the given\n"
+"script. If nothing is specified, the default script (asterisk.adsi) is used.\n";
+
+struct adsi_event {
+ int id;
+ char *name;
+};
+
+static struct adsi_event events[] = {
+ { 1, "CALLERID" },
+ { 2, "VMWI" },
+ { 3, "NEARANSWER" },
+ { 4, "FARANSWER" },
+ { 5, "ENDOFRING" },
+ { 6, "IDLE" },
+ { 7, "OFFHOOK" },
+ { 8, "CIDCW" },
+ { 9, "BUSY" },
+ { 10, "FARRING" },
+ { 11, "DIALTONE" },
+ { 12, "RECALL" },
+ { 13, "MESSAGE" },
+ { 14, "REORDER" },
+ { 15, "DISTINCTIVERING" },
+ { 16, "RING" },
+ { 17, "REMINDERRING" },
+ { 18, "SPECIALRING" },
+ { 19, "CODEDRING" },
+ { 20, "TIMER" },
+ { 21, "INUSE" },
+ { 22, "EVENT22" },
+ { 23, "EVENT23" },
+ { 24, "CPEID" },
+};
+
+static struct adsi_event justify[] = {
+ { 0, "CENTER" },
+ { 1, "RIGHT" },
+ { 2, "LEFT" },
+ { 3, "INDENT" },
+};
+
+#define STATE_NORMAL 0
+#define STATE_INKEY 1
+#define STATE_INSUB 2
+#define STATE_INIF 3
+
+#define MAX_RET_CODE 20
+#define MAX_SUB_LEN 255
+#define MAX_MAIN_LEN 1600
+
+#define ARG_STRING (1 << 0)
+#define ARG_NUMBER (1 << 1)
+
+struct adsi_soft_key {
+ char vname[40]; /* Which "variable" is associated with it */
+ int retstrlen; /* Length of return string */
+ int initlen; /* initial length */
+ int id;
+ int defined;
+ char retstr[80]; /* Return string data */
+};
+
+struct adsi_subscript {
+ char vname[40];
+ int id;
+ int defined;
+ int datalen;
+ int inscount;
+ int ifinscount;
+ char *ifdata;
+ char data[2048];
+};
+
+struct adsi_state {
+ char vname[40];
+ int id;
+};
+
+struct adsi_flag {
+ char vname[40];
+ int id;
+};
+
+struct adsi_display {
+ char vname[40];
+ int id;
+ char data[70];
+ int datalen;
+};
+
+struct adsi_script {
+ int state;
+ int numkeys;
+ int numsubs;
+ int numstates;
+ int numdisplays;
+ int numflags;
+ struct adsi_soft_key *key;
+ struct adsi_subscript *sub;
+ /* Pre-defined displays */
+ struct adsi_display displays[63];
+ /* ADSI States 1 (initial) - 254 */
+ struct adsi_state states[256];
+ /* Keys 2-63 */
+ struct adsi_soft_key keys[62];
+ /* Subscripts 0 (main) to 127 */
+ struct adsi_subscript subs[128];
+ /* Flags 1-7 */
+ struct adsi_flag flags[7];
+
+ /* Stuff from adsi script */
+ unsigned char sec[5];
+ char desc[19];
+ unsigned char fdn[5];
+ int ver;
+};
+
+
+static int process_token(void *out, char *src, int maxlen, int argtype)
+{
+ if ((strlen(src) > 1) && src[0] == '\"') {
+ /* This is a quoted string */
+ if (!(argtype & ARG_STRING))
+ return -1;
+ src++;
+ /* Don't take more than what's there */
+ if (maxlen > strlen(src) - 1)
+ maxlen = strlen(src) - 1;
+ memcpy(out, src, maxlen);
+ ((char *)out)[maxlen] = '\0';
+ } else if (!ast_strlen_zero(src) && (src[0] == '\\')) {
+ if (!(argtype & ARG_NUMBER))
+ return -1;
+ /* Octal value */
+ if (sscanf(src, "%o", (int *)out) != 1)
+ return -1;
+ if (argtype & ARG_STRING) {
+ /* Convert */
+ *((unsigned int *)out) = htonl(*((unsigned int *)out));
+ }
+ } else if ((strlen(src) > 2) && (src[0] == '0') && (tolower(src[1]) == 'x')) {
+ if (!(argtype & ARG_NUMBER))
+ return -1;
+ /* Hex value */
+ if (sscanf(src + 2, "%x", (unsigned int *)out) != 1)
+ return -1;
+ if (argtype & ARG_STRING) {
+ /* Convert */
+ *((unsigned int *)out) = htonl(*((unsigned int *)out));
+ }
+ } else if ((!ast_strlen_zero(src) && isdigit(src[0]))) {
+ if (!(argtype & ARG_NUMBER))
+ return -1;
+ /* Hex value */
+ if (sscanf(src, "%d", (int *)out) != 1)
+ return -1;
+ if (argtype & ARG_STRING) {
+ /* Convert */
+ *((unsigned int *)out) = htonl(*((unsigned int *)out));
+ }
+ } else
+ return -1;
+ return 0;
+}
+
+static char *get_token(char **buf, char *script, int lineno)
+{
+ char *tmp = *buf;
+ char *keyword;
+ int quoted = 0;
+ /* Advance past any white space */
+ while(*tmp && (*tmp < 33))
+ tmp++;
+ if (!*tmp)
+ return NULL;
+ keyword = tmp;
+ while(*tmp && ((*tmp > 32) || quoted)) {
+ if (*tmp == '\"') {
+ quoted = !quoted;
+ }
+ tmp++;
+ }
+ if (quoted) {
+ ast_log(LOG_WARNING, "Mismatched quotes at line %d of %s\n", lineno, script);
+ return NULL;
+ }
+ *tmp = '\0';
+ tmp++;
+ while(*tmp && (*tmp < 33))
+ tmp++;
+ /* Note where we left off */
+ *buf = tmp;
+ return keyword;
+}
+
+static char *validdtmf = "123456789*0#ABCD";
+
+static int send_dtmf(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno)
+{
+ char dtmfstr[80];
+ char *a;
+ int bytes=0;
+ a = get_token(&args, script, lineno);
+ if (!a) {
+ ast_log(LOG_WARNING, "Expecting something to send for SENDDTMF at line %d of %s\n", lineno, script);
+ return 0;
+ }
+ if (process_token(dtmfstr, a, sizeof(dtmfstr) - 1, ARG_STRING)) {
+ ast_log(LOG_WARNING, "Invalid token for SENDDTMF at line %d of %s\n", lineno, script);
+ return 0;
+ }
+ a = dtmfstr;
+ while(*a) {
+ if (strchr(validdtmf, *a)) {
+ *buf = *a;
+ buf++;
+ bytes++;
+ } else
+ ast_log(LOG_WARNING, "'%c' is not a valid DTMF tone at line %d of %s\n", *a, lineno, script);
+ a++;
+ }
+ return bytes;
+}
+
+static int goto_line(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno)
+{
+ char *page;
+ char *gline;
+ int line;
+ unsigned char cmd;
+ page = get_token(&args, script, lineno);
+ gline = get_token(&args, script, lineno);
+ if (!page || !gline) {
+ ast_log(LOG_WARNING, "Expecting page and line number for GOTOLINE at line %d of %s\n", lineno, script);
+ return 0;
+ }
+ if (!strcasecmp(page, "INFO")) {
+ cmd = 0;
+ } else if (!strcasecmp(page, "COMM")) {
+ cmd = 0x80;
+ } else {
+ ast_log(LOG_WARNING, "Expecting either 'INFO' or 'COMM' page, got got '%s' at line %d of %s\n", page, lineno, script);
+ return 0;
+ }
+ if (process_token(&line, gline, sizeof(line), ARG_NUMBER)) {
+ ast_log(LOG_WARNING, "Invalid line number '%s' at line %d of %s\n", gline, lineno, script);
+ return 0;
+ }
+ cmd |= line;
+ buf[0] = 0x8b;
+ buf[1] = cmd;
+ return 2;
+}
+
+static int goto_line_rel(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno)
+{
+ char *dir;
+ char *gline;
+ int line;
+ unsigned char cmd;
+ dir = get_token(&args, script, lineno);
+ gline = get_token(&args, script, lineno);
+ if (!dir || !gline) {
+ ast_log(LOG_WARNING, "Expecting direction and number of lines for GOTOLINEREL at line %d of %s\n", lineno, script);
+ return 0;
+ }
+ if (!strcasecmp(dir, "UP")) {
+ cmd = 0;
+ } else if (!strcasecmp(dir, "DOWN")) {
+ cmd = 0x20;
+ } else {
+ ast_log(LOG_WARNING, "Expecting either 'UP' or 'DOWN' direction, got '%s' at line %d of %s\n", dir, lineno, script);
+ return 0;
+ }
+ if (process_token(&line, gline, sizeof(line), ARG_NUMBER)) {
+ ast_log(LOG_WARNING, "Invalid line number '%s' at line %d of %s\n", gline, lineno, script);
+ return 0;
+ }
+ cmd |= line;
+ buf[0] = 0x8c;
+ buf[1] = cmd;
+ return 2;
+}
+
+static int send_delay(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno)
+{
+ char *gtime;
+ int ms;
+ gtime = get_token(&args, script, lineno);
+ if (!gtime) {
+ ast_log(LOG_WARNING, "Expecting number of milliseconds to wait at line %d of %s\n", lineno, script);
+ return 0;
+ }
+ if (process_token(&ms, gtime, sizeof(ms), ARG_NUMBER)) {
+ ast_log(LOG_WARNING, "Invalid delay milliseconds '%s' at line %d of %s\n", gtime, lineno, script);
+ return 0;
+ }
+ buf[0] = 0x90;
+ if (id == 11)
+ buf[1] = ms / 100;
+ else
+ buf[1] = ms / 10;
+ return 2;
+}
+
+static int set_state(char *buf, char *name, int id, char *args, struct adsi_script *istate, char *script, int lineno)
+{
+ char *gstate;
+ int state;
+ gstate = get_token(&args, script, lineno);
+ if (!gstate) {
+ ast_log(LOG_WARNING, "Expecting state number at line %d of %s\n", lineno, script);
+ return 0;
+ }
+ if (process_token(&state, gstate, sizeof(state), ARG_NUMBER)) {
+ ast_log(LOG_WARNING, "Invalid state number '%s' at line %d of %s\n", gstate, lineno, script);
+ return 0;
+ }
+ buf[0] = id;
+ buf[1] = state;
+ return 2;
+}
+
+static int cleartimer(char *buf, char *name, int id, char *args, struct adsi_script *istate, char *script, int lineno)
+{
+ char *tok;
+ tok = get_token(&args, script, lineno);
+ if (tok)
+ ast_log(LOG_WARNING, "Clearing timer requires no arguments ('%s') at line %d of %s\n", tok, lineno, script);
+
+ buf[0] = id;
+ /* For some reason the clear code is different slightly */
+ if (id == 7)
+ buf[1] = 0x10;
+ else
+ buf[1] = 0x00;
+ return 2;
+}
+
+static struct adsi_flag *getflagbyname(struct adsi_script *state, char *name, char *script, int lineno, int create)
+{
+ int x;
+ for (x=0;x<state->numflags;x++)
+ if (!strcasecmp(state->flags[x].vname, name))
+ return &state->flags[x];
+ /* Return now if we're not allowed to create */
+ if (!create)
+ return NULL;
+ if (state->numflags > 6) {
+ ast_log(LOG_WARNING, "No more flag space at line %d of %s\n", lineno, script);
+ return NULL;
+ }
+ ast_copy_string(state->flags[state->numflags].vname, name, sizeof(state->flags[state->numflags].vname));
+ state->flags[state->numflags].id = state->numflags + 1;
+ state->numflags++;
+ return &state->flags[state->numflags-1];
+}
+
+static int setflag(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno)
+{
+ char *tok;
+ char sname[80];
+ struct adsi_flag *flag;
+ tok = get_token(&args, script, lineno);
+ if (!tok) {
+ ast_log(LOG_WARNING, "Setting flag requires a flag number at line %d of %s\n", lineno, script);
+ return 0;
+ }
+ if (process_token(sname, tok, sizeof(sname) - 1, ARG_STRING)) {
+ ast_log(LOG_WARNING, "Invalid flag '%s' at line %d of %s\n", tok, lineno, script);
+ return 0;
+ }
+ flag = getflagbyname(state, sname, script, lineno, 0);
+ if (!flag) {
+ ast_log(LOG_WARNING, "Flag '%s' is undeclared at line %d of %s\n", sname, lineno, script);
+ return 0;
+ }
+ buf[0] = id;
+ buf[1] = ((flag->id & 0x7) << 4) | 1;
+ return 2;
+}
+
+static int clearflag(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno)
+{
+ char *tok;
+ struct adsi_flag *flag;
+ char sname[80];
+ tok = get_token(&args, script, lineno);
+ if (!tok) {
+ ast_log(LOG_WARNING, "Clearing flag requires a flag number at line %d of %s\n", lineno, script);
+ return 0;
+ }
+ if (process_token(sname, tok, sizeof(sname) - 1, ARG_STRING)) {
+ ast_log(LOG_WARNING, "Invalid flag '%s' at line %d of %s\n", tok, lineno, script);
+ return 0;
+ }
+ flag = getflagbyname(state, sname, script, lineno, 0);
+ if (!flag) {
+ ast_log(LOG_WARNING, "Flag '%s' is undeclared at line %d of %s\n", sname, lineno, script);
+ return 0;
+ }
+ buf[0] = id;
+ buf[1] = ((flag->id & 0x7) << 4);
+ return 2;
+}
+
+static int starttimer(char *buf, char *name, int id, char *args, struct adsi_script *istate, char *script, int lineno)
+{
+ char *tok;
+ int secs;
+ tok = get_token(&args, script, lineno);
+ if (!tok) {
+ ast_log(LOG_WARNING, "Missing number of seconds at line %d of %s\n", lineno, script);
+ return 0;
+ }
+ if (process_token(&secs, tok, sizeof(secs), ARG_NUMBER)) {
+ ast_log(LOG_WARNING, "Invalid number of seconds '%s' at line %d of %s\n", tok, lineno, script);
+ return 0;
+ }
+ buf[0] = id;
+ buf[1] = 0x1;
+ buf[2] = secs;
+ return 3;
+}
+
+static int geteventbyname(char *name)
+{
+ int x;
+ for (x=0;x<sizeof(events) / sizeof(events[0]); x++) {
+ if (!strcasecmp(events[x].name, name))
+ return events[x].id;
+ }
+ return 0;
+}
+
+static int getjustifybyname(char *name)
+{
+ int x;
+ for (x=0;x<sizeof(justify) / sizeof(justify[0]); x++) {
+ if (!strcasecmp(justify[x].name, name))
+ return justify[x].id;
+ }
+ return -1;
+}
+
+static struct adsi_soft_key *getkeybyname(struct adsi_script *state, char *name, char *script, int lineno)
+{
+ int x;
+ for (x=0;x<state->numkeys;x++)
+ if (!strcasecmp(state->keys[x].vname, name))
+ return &state->keys[x];
+ if (state->numkeys > 61) {
+ ast_log(LOG_WARNING, "No more key space at line %d of %s\n", lineno, script);
+ return NULL;
+ }
+ ast_copy_string(state->keys[state->numkeys].vname, name, sizeof(state->keys[state->numkeys].vname));
+ state->keys[state->numkeys].id = state->numkeys + 2;
+ state->numkeys++;
+ return &state->keys[state->numkeys-1];
+}
+
+static struct adsi_subscript *getsubbyname(struct adsi_script *state, char *name, char *script, int lineno)
+{
+ int x;
+ for (x=0;x<state->numsubs;x++)
+ if (!strcasecmp(state->subs[x].vname, name))
+ return &state->subs[x];
+ if (state->numsubs > 127) {
+ ast_log(LOG_WARNING, "No more subscript space at line %d of %s\n", lineno, script);
+ return NULL;
+ }
+ ast_copy_string(state->subs[state->numsubs].vname, name, sizeof(state->subs[state->numsubs].vname));
+ state->subs[state->numsubs].id = state->numsubs;
+ state->numsubs++;
+ return &state->subs[state->numsubs-1];
+}
+
+static struct adsi_state *getstatebyname(struct adsi_script *state, char *name, char *script, int lineno, int create)
+{
+ int x;
+ for (x=0;x<state->numstates;x++)
+ if (!strcasecmp(state->states[x].vname, name))
+ return &state->states[x];
+ /* Return now if we're not allowed to create */
+ if (!create)
+ return NULL;
+ if (state->numstates > 253) {
+ ast_log(LOG_WARNING, "No more state space at line %d of %s\n", lineno, script);
+ return NULL;
+ }
+ ast_copy_string(state->states[state->numstates].vname, name, sizeof(state->states[state->numstates].vname));
+ state->states[state->numstates].id = state->numstates + 1;
+ state->numstates++;
+ return &state->states[state->numstates-1];
+}
+
+static struct adsi_display *getdisplaybyname(struct adsi_script *state, char *name, char *script, int lineno, int create)
+{
+ int x;
+ for (x=0;x<state->numdisplays;x++)
+ if (!strcasecmp(state->displays[x].vname, name))
+ return &state->displays[x];
+ /* Return now if we're not allowed to create */
+ if (!create)
+ return NULL;
+ if (state->numdisplays > 61) {
+ ast_log(LOG_WARNING, "No more display space at line %d of %s\n", lineno, script);
+ return NULL;
+ }
+ ast_copy_string(state->displays[state->numdisplays].vname, name, sizeof(state->displays[state->numdisplays].vname));
+ state->displays[state->numdisplays].id = state->numdisplays + 1;
+ state->numdisplays++;
+ return &state->displays[state->numdisplays-1];
+}
+
+static int showkeys(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno)
+{
+ char *tok;
+ char newkey[80];
+ int bytes;
+ unsigned char keyid[6];
+ int x;
+ int flagid=0;
+ struct adsi_soft_key *key;
+ struct adsi_flag *flag;
+
+ for (x=0;x<7;x++) {
+ /* Up to 6 key arguments */
+ tok = get_token(&args, script, lineno);
+ if (!tok)
+ break;
+ if (!strcasecmp(tok, "UNLESS")) {
+ /* Check for trailing UNLESS flag */
+ tok = get_token(&args, script, lineno);
+ if (!tok) {
+ ast_log(LOG_WARNING, "Missing argument for UNLESS clause at line %d of %s\n", lineno, script);
+ } else if (process_token(newkey, tok, sizeof(newkey) - 1, ARG_STRING)) {
+ ast_log(LOG_WARNING, "Invalid flag name '%s' at line %d of %s\n", tok, lineno, script);
+ } else if (!(flag = getflagbyname(state, newkey, script, lineno, 0))) {
+ ast_log(LOG_WARNING, "Flag '%s' is undeclared at line %d of %s\n", newkey, lineno, script);
+ } else
+ flagid = flag->id;
+ if ((tok = get_token(&args, script, lineno)))
+ ast_log(LOG_WARNING, "Extra arguments after UNLESS clause: '%s' at line %d of %s\n", tok, lineno, script);
+ break;
+ }
+ if (x > 5) {
+ ast_log(LOG_WARNING, "Only 6 keys can be defined, ignoring '%s' at line %d of %s\n", tok, lineno, script);
+ break;
+ }
+ if (process_token(newkey, tok, sizeof(newkey) - 1, ARG_STRING)) {
+ ast_log(LOG_WARNING, "Invalid token for key name: %s\n", tok);
+ continue;
+ }
+
+ key = getkeybyname(state, newkey, script, lineno);
+ if (!key)
+ break;
+ keyid[x] = key->id;
+ }
+ buf[0] = id;
+ buf[1] = (flagid & 0x7) << 3 | (x & 0x7);
+ for (bytes=0;bytes<x;bytes++) {
+ buf[bytes + 2] = keyid[bytes];
+ }
+ return 2 + x;
+}
+
+static int showdisplay(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno)
+{
+ char *tok;
+ char dispname[80];
+ int line=0;
+ int flag=0;
+ int cmd = 3;
+ struct adsi_display *disp;
+
+ /* Get display */
+ tok = get_token(&args, script, lineno);
+ if (!tok || process_token(dispname, tok, sizeof(dispname) - 1, ARG_STRING)) {
+ ast_log(LOG_WARNING, "Invalid display name: %s at line %d of %s\n", tok ? tok : "<nothing>", lineno, script);
+ return 0;
+ }
+ disp = getdisplaybyname(state, dispname, script, lineno, 0);
+ if (!disp) {
+ ast_log(LOG_WARNING, "Display '%s' is undefined at line %d of %s\n", dispname, lineno, script);
+ return 0;
+ }
+
+ tok = get_token(&args, script, lineno);
+ if (!tok || strcasecmp(tok, "AT")) {
+ ast_log(LOG_WARNING, "Missing token 'AT' at line %d of %s\n", lineno, script);
+ return 0;
+ }
+ /* Get line number */
+ tok = get_token(&args, script, lineno);
+ if (!tok || process_token(&line, tok, sizeof(line), ARG_NUMBER)) {
+ ast_log(LOG_WARNING, "Invalid line: '%s' at line %d of %s\n", tok ? tok : "<nothing>", lineno, script);
+ return 0;
+ }
+ tok = get_token(&args, script, lineno);
+ if (tok && !strcasecmp(tok, "NOUPDATE")) {
+ cmd = 1;
+ tok = get_token(&args, script, lineno);
+ }
+ if (tok && !strcasecmp(tok, "UNLESS")) {
+ /* Check for trailing UNLESS flag */
+ tok = get_token(&args, script, lineno);
+ if (!tok) {
+ ast_log(LOG_WARNING, "Missing argument for UNLESS clause at line %d of %s\n", lineno, script);
+ } else if (process_token(&flag, tok, sizeof(flag), ARG_NUMBER)) {
+ ast_log(LOG_WARNING, "Invalid flag number '%s' at line %d of %s\n", tok, lineno, script);
+ }
+ if ((tok = get_token(&args, script, lineno)))
+ ast_log(LOG_WARNING, "Extra arguments after UNLESS clause: '%s' at line %d of %s\n", tok, lineno, script);
+ }
+
+ buf[0] = id;
+ buf[1] = (cmd << 6) | (disp->id & 0x3f);
+ buf[2] = ((line & 0x1f) << 3) | (flag & 0x7);
+ return 3;
+}
+
+static int cleardisplay(char *buf, char *name, int id, char *args, struct adsi_script *istate, char *script, int lineno)
+{
+ char *tok;
+ tok = get_token(&args, script, lineno);
+ if (tok)
+ ast_log(LOG_WARNING, "Clearing display requires no arguments ('%s') at line %d of %s\n", tok, lineno, script);
+
+ buf[0] = id;
+ buf[1] = 0x00;
+ return 2;
+}
+
+static int digitdirect(char *buf, char *name, int id, char *args, struct adsi_script *istate, char *script, int lineno)
+{
+ char *tok;
+ tok = get_token(&args, script, lineno);
+ if (tok)
+ ast_log(LOG_WARNING, "Digitdirect requires no arguments ('%s') at line %d of %s\n", tok, lineno, script);
+
+ buf[0] = id;
+ buf[1] = 0x7;
+ return 2;
+}
+
+static int clearcbone(char *buf, char *name, int id, char *args, struct adsi_script *istate, char *script, int lineno)
+{
+ char *tok;
+ tok = get_token(&args, script, lineno);
+ if (tok)
+ ast_log(LOG_WARNING, "CLEARCB1 requires no arguments ('%s') at line %d of %s\n", tok, lineno, script);
+
+ buf[0] = id;
+ buf[1] = 0;
+ return 2;
+}
+
+static int digitcollect(char *buf, char *name, int id, char *args, struct adsi_script *istate, char *script, int lineno)
+{
+ char *tok;
+ tok = get_token(&args, script, lineno);
+ if (tok)
+ ast_log(LOG_WARNING, "Digitcollect requires no arguments ('%s') at line %d of %s\n", tok, lineno, script);
+
+ buf[0] = id;
+ buf[1] = 0xf;
+ return 2;
+}
+
+static int subscript(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno)
+{
+ char *tok;
+ char subscript[80];
+ struct adsi_subscript *sub;
+ tok = get_token(&args, script, lineno);
+ if (!tok) {
+ ast_log(LOG_WARNING, "Missing subscript to call at line %d of %s\n", lineno, script);
+ return 0;
+ }
+ if (process_token(subscript, tok, sizeof(subscript) - 1, ARG_STRING)) {
+ ast_log(LOG_WARNING, "Invalid number of seconds '%s' at line %d of %s\n", tok, lineno, script);
+ return 0;
+ }
+ sub = getsubbyname(state, subscript, script, lineno);
+ if (!sub)
+ return 0;
+ buf[0] = 0x9d;
+ buf[1] = sub->id;
+ return 2;
+}
+
+static int onevent(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno)
+{
+ char *tok;
+ char subscript[80];
+ char sname[80];
+ int sawin=0;
+ int event;
+ int snums[8];
+ int scnt = 0;
+ int x;
+ struct adsi_subscript *sub;
+ tok = get_token(&args, script, lineno);
+ if (!tok) {
+ ast_log(LOG_WARNING, "Missing event for 'ONEVENT' at line %d of %s\n", lineno, script);
+ return 0;
+ }
+ event = geteventbyname(tok);
+ if (event < 1) {
+ ast_log(LOG_WARNING, "'%s' is not a valid event name, at line %d of %s\n", args, lineno, script);
+ return 0;
+ }
+ tok = get_token(&args, script, lineno);
+ while ((!sawin && !strcasecmp(tok, "IN")) ||
+ (sawin && !strcasecmp(tok, "OR"))) {
+ sawin = 1;
+ if (scnt > 7) {
+ ast_log(LOG_WARNING, "No more than 8 states may be specified for inclusion at line %d of %s\n", lineno, script);
+ return 0;
+ }
+ /* Process 'in' things */
+ tok = get_token(&args, script, lineno);
+ if (process_token(sname, tok, sizeof(sname), ARG_STRING)) {
+ ast_log(LOG_WARNING, "'%s' is not a valid state name at line %d of %s\n", tok, lineno, script);
+ return 0;
+ }
+ if ((snums[scnt] = getstatebyname(state, sname, script, lineno, 0) < 0)) {
+ ast_log(LOG_WARNING, "State '%s' not declared at line %d of %s\n", sname, lineno, script);
+ return 0;
+ }
+ scnt++;
+ tok = get_token(&args, script, lineno);
+ if (!tok)
+ break;
+ }
+ if (!tok || strcasecmp(tok, "GOTO")) {
+ if (!tok)
+ tok = "<nothing>";
+ if (sawin)
+ ast_log(LOG_WARNING, "Got '%s' while looking for 'GOTO' or 'OR' at line %d of %s\n", tok, lineno, script);
+ else
+ ast_log(LOG_WARNING, "Got '%s' while looking for 'GOTO' or 'IN' at line %d of %s\n", tok, lineno, script);
+ }
+ tok = get_token(&args, script, lineno);
+ if (!tok) {
+ ast_log(LOG_WARNING, "Missing subscript to call at line %d of %s\n", lineno, script);
+ return 0;
+ }
+ if (process_token(subscript, tok, sizeof(subscript) - 1, ARG_STRING)) {
+ ast_log(LOG_WARNING, "Invalid subscript '%s' at line %d of %s\n", tok, lineno, script);
+ return 0;
+ }
+ sub = getsubbyname(state, subscript, script, lineno);
+ if (!sub)
+ return 0;
+ buf[0] = 8;
+ buf[1] = event;
+ buf[2] = sub->id | 0x80;
+ for (x=0;x<scnt;x++)
+ buf[3 + x] = snums[x];
+ return 3 + scnt;
+}
+
+struct adsi_key_cmd {
+ char *name;
+ int id;
+ int (*add_args)(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno);
+};
+
+static struct adsi_key_cmd kcmds[] = {
+ { "SENDDTMF", 0, send_dtmf },
+ /* Encoded DTMF would go here */
+ { "ONHOOK", 0x81 },
+ { "OFFHOOK", 0x82 },
+ { "FLASH", 0x83 },
+ { "WAITDIALTONE", 0x84 },
+ /* Send line number */
+ { "BLANK", 0x86 },
+ { "SENDCHARS", 0x87 },
+ { "CLEARCHARS", 0x88 },
+ { "BACKSPACE", 0x89 },
+ /* Tab column */
+ { "GOTOLINE", 0x8b, goto_line },
+ { "GOTOLINEREL", 0x8c, goto_line_rel },
+ { "PAGEUP", 0x8d },
+ { "PAGEDOWN", 0x8e },
+ /* Extended DTMF */
+ { "DELAY", 0x90, send_delay },
+ { "DIALPULSEONE", 0x91 },
+ { "DATAMODE", 0x92 },
+ { "VOICEMODE", 0x93 },
+ /* Display call buffer 'n' */
+ /* Clear call buffer 'n' */
+ { "CLEARCB1", 0x95, clearcbone },
+ { "DIGITCOLLECT", 0x96, digitcollect },
+ { "DIGITDIRECT", 0x96, digitdirect },
+ { "CLEAR", 0x97 },
+ { "SHOWDISPLAY", 0x98, showdisplay },
+ { "CLEARDISPLAY", 0x98, cleardisplay },
+ { "SHOWKEYS", 0x99, showkeys },
+ { "SETSTATE", 0x9a, set_state },
+ { "TIMERSTART", 0x9b, starttimer },
+ { "TIMERCLEAR", 0x9b, cleartimer },
+ { "SETFLAG", 0x9c, setflag },
+ { "CLEARFLAG", 0x9c, clearflag },
+ { "GOTO", 0x9d, subscript },
+ { "EVENT22", 0x9e },
+ { "EVENT23", 0x9f },
+ { "EXIT", 0xa0 },
+};
+
+static struct adsi_key_cmd opcmds[] = {
+
+ /* 1 - Branch on event -- handled specially */
+ { "SHOWKEYS", 2, showkeys },
+ /* Display Control */
+ { "SHOWDISPLAY", 3, showdisplay },
+ { "CLEARDISPLAY", 3, cleardisplay },
+ { "CLEAR", 5 },
+ { "SETSTATE", 6, set_state },
+ { "TIMERSTART", 7, starttimer },
+ { "TIMERCLEAR", 7, cleartimer },
+ { "ONEVENT", 8, onevent },
+ /* 9 - Subroutine label, treated specially */
+ { "SETFLAG", 10, setflag },
+ { "CLEARFLAG", 10, clearflag },
+ { "DELAY", 11, send_delay },
+ { "EXIT", 12 },
+};
+
+
+static int process_returncode(struct adsi_soft_key *key, char *code, char *args, struct adsi_script *state, char *script, int lineno)
+{
+ int x;
+ char *unused;
+ int res;
+ for (x=0;x<sizeof(kcmds) / sizeof(kcmds[0]);x++) {
+ if ((kcmds[x].id > -1) && !strcasecmp(kcmds[x].name, code)) {
+ if (kcmds[x].add_args) {
+ res = kcmds[x].add_args(key->retstr + key->retstrlen,
+ code, kcmds[x].id, args, state, script, lineno);
+ if ((key->retstrlen + res - key->initlen) <= MAX_RET_CODE)
+ key->retstrlen += res;
+ else
+ ast_log(LOG_WARNING, "No space for '%s' code in key '%s' at line %d of %s\n", kcmds[x].name, key->vname, lineno, script);
+ } else {
+ if ((unused = get_token(&args, script, lineno)))
+ ast_log(LOG_WARNING, "'%s' takes no arguments at line %d of %s (token is '%s')\n", kcmds[x].name, lineno, script, unused);
+ if ((key->retstrlen + 1 - key->initlen) <= MAX_RET_CODE) {
+ key->retstr[key->retstrlen] = kcmds[x].id;
+ key->retstrlen++;
+ } else
+ ast_log(LOG_WARNING, "No space for '%s' code in key '%s' at line %d of %s\n", kcmds[x].name, key->vname, lineno, script);
+ }
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static int process_opcode(struct adsi_subscript *sub, char *code, char *args, struct adsi_script *state, char *script, int lineno)
+{
+ int x;
+ char *unused;
+ int res;
+ int max = sub->id ? MAX_SUB_LEN : MAX_MAIN_LEN;
+ for (x=0;x<sizeof(opcmds) / sizeof(opcmds[0]);x++) {
+ if ((opcmds[x].id > -1) && !strcasecmp(opcmds[x].name, code)) {
+ if (opcmds[x].add_args) {
+ res = opcmds[x].add_args(sub->data + sub->datalen,
+ code, opcmds[x].id, args, state, script, lineno);
+ if ((sub->datalen + res + 1) <= max)
+ sub->datalen += res;
+ else {
+ ast_log(LOG_WARNING, "No space for '%s' code in subscript '%s' at line %d of %s\n", opcmds[x].name, sub->vname, lineno, script);
+ return -1;
+ }
+ } else {
+ if ((unused = get_token(&args, script, lineno)))
+ ast_log(LOG_WARNING, "'%s' takes no arguments at line %d of %s (token is '%s')\n", opcmds[x].name, lineno, script, unused);
+ if ((sub->datalen + 2) <= max) {
+ sub->data[sub->datalen] = opcmds[x].id;
+ sub->datalen++;
+ } else {
+ ast_log(LOG_WARNING, "No space for '%s' code in key '%s' at line %d of %s\n", opcmds[x].name, sub->vname, lineno, script);
+ return -1;
+ }
+ }
+ /* Separate commands with 0xff */
+ sub->data[sub->datalen] = 0xff;
+ sub->datalen++;
+ sub->inscount++;
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static int adsi_process(struct adsi_script *state, char *buf, char *script, int lineno)
+{
+ char *keyword;
+ char *args;
+ char vname[256];
+ char tmp[80];
+ char tmp2[80];
+ int lrci;
+ int wi;
+ int event;
+ struct adsi_display *disp;
+ struct adsi_subscript *newsub;
+ /* Find the first keyword */
+ keyword = get_token(&buf, script, lineno);
+ if (!keyword)
+ return 0;
+ switch(state->state) {
+ case STATE_NORMAL:
+ if (!strcasecmp(keyword, "DESCRIPTION")) {
+ args = get_token(&buf, script, lineno);
+ if (args) {
+ if (process_token(state->desc, args, sizeof(state->desc) - 1, ARG_STRING))
+ ast_log(LOG_WARNING, "'%s' is not a valid token for DESCRIPTION at line %d of %s\n", args, lineno, script);
+ } else
+ ast_log(LOG_WARNING, "Missing argument for DESCRIPTION at line %d of %s\n", lineno, script);
+ } else if (!strcasecmp(keyword, "VERSION")) {
+ args = get_token(&buf, script, lineno);
+ if (args) {
+ if (process_token(&state->ver, args, sizeof(state->ver) - 1, ARG_NUMBER))
+ ast_log(LOG_WARNING, "'%s' is not a valid token for VERSION at line %d of %s\n", args, lineno, script);
+ } else
+ ast_log(LOG_WARNING, "Missing argument for VERSION at line %d of %s\n", lineno, script);
+ } else if (!strcasecmp(keyword, "SECURITY")) {
+ args = get_token(&buf, script, lineno);
+ if (args) {
+ if (process_token(state->sec, args, sizeof(state->sec) - 1, ARG_STRING | ARG_NUMBER))
+ ast_log(LOG_WARNING, "'%s' is not a valid token for SECURITY at line %d of %s\n", args, lineno, script);
+ } else
+ ast_log(LOG_WARNING, "Missing argument for SECURITY at line %d of %s\n", lineno, script);
+ } else if (!strcasecmp(keyword, "FDN")) {
+ args = get_token(&buf, script, lineno);
+ if (args) {
+ if (process_token(state->fdn, args, sizeof(state->fdn) - 1, ARG_STRING | ARG_NUMBER))
+ ast_log(LOG_WARNING, "'%s' is not a valid token for FDN at line %d of %s\n", args, lineno, script);
+ } else
+ ast_log(LOG_WARNING, "Missing argument for FDN at line %d of %s\n", lineno, script);
+ } else if (!strcasecmp(keyword, "KEY")) {
+ args = get_token(&buf, script, lineno);
+ if (!args) {
+ ast_log(LOG_WARNING, "KEY definition missing name at line %d of %s\n", lineno, script);
+ break;
+ }
+ if (process_token(vname, args, sizeof(vname) - 1, ARG_STRING)) {
+ ast_log(LOG_WARNING, "'%s' is not a valid token for a KEY name at line %d of %s\n", args, lineno, script);
+ break;
+ }
+ state->key = getkeybyname(state, vname, script, lineno);
+ if (!state->key) {
+ ast_log(LOG_WARNING, "Out of key space at line %d of %s\n", lineno, script);
+ break;
+ }
+ if (state->key->defined) {
+ ast_log(LOG_WARNING, "Cannot redefine key '%s' at line %d of %s\n", vname, lineno, script);
+ break;
+ }
+ args = get_token(&buf, script, lineno);
+ if (!args || strcasecmp(args, "IS")) {
+ ast_log(LOG_WARNING, "Expecting 'IS', but got '%s' at line %d of %s\n", args ? args : "<nothing>", lineno, script);
+ break;
+ }
+ args = get_token(&buf, script, lineno);
+ if (!args) {
+ ast_log(LOG_WARNING, "KEY definition missing short name at line %d of %s\n", lineno, script);
+ break;
+ }
+ if (process_token(tmp, args, sizeof(tmp) - 1, ARG_STRING)) {
+ ast_log(LOG_WARNING, "'%s' is not a valid token for a KEY short name at line %d of %s\n", args, lineno, script);
+ break;
+ }
+ args = get_token(&buf, script, lineno);
+ if (args) {
+ if (strcasecmp(args, "OR")) {
+ ast_log(LOG_WARNING, "Expecting 'OR' but got '%s' instead at line %d of %s\n", args, lineno, script);
+ break;
+ }
+ args = get_token(&buf, script, lineno);
+ if (!args) {
+ ast_log(LOG_WARNING, "KEY definition missing optional long name at line %d of %s\n", lineno, script);
+ break;
+ }
+ if (process_token(tmp2, args, sizeof(tmp2) - 1, ARG_STRING)) {
+ ast_log(LOG_WARNING, "'%s' is not a valid token for a KEY long name at line %d of %s\n", args, lineno, script);
+ break;
+ }
+ } else {
+ ast_copy_string(tmp2, tmp, sizeof(tmp2));
+ }
+ if (strlen(tmp2) > 18) {
+ ast_log(LOG_WARNING, "Truncating full name to 18 characters at line %d of %s\n", lineno, script);
+ tmp2[18] = '\0';
+ }
+ if (strlen(tmp) > 7) {
+ ast_log(LOG_WARNING, "Truncating short name to 7 bytes at line %d of %s\n", lineno, script);
+ tmp[7] = '\0';
+ }
+ /* Setup initial stuff */
+ state->key->retstr[0] = 128;
+ /* 1 has the length */
+ state->key->retstr[2] = state->key->id;
+ /* Put the Full name in */
+ memcpy(state->key->retstr + 3, tmp2, strlen(tmp2));
+ /* Update length */
+ state->key->retstrlen = strlen(tmp2) + 3;
+ /* Put trailing 0xff */
+ state->key->retstr[state->key->retstrlen++] = 0xff;
+ /* Put the short name */
+ memcpy(state->key->retstr + state->key->retstrlen, tmp, strlen(tmp));
+ /* Update length */
+ state->key->retstrlen += strlen(tmp);
+ /* Put trailing 0xff */
+ state->key->retstr[state->key->retstrlen++] = 0xff;
+ /* Record initial length */
+ state->key->initlen = state->key->retstrlen;
+ state->state = STATE_INKEY;
+ } else if (!strcasecmp(keyword, "SUB")) {
+ args = get_token(&buf, script, lineno);
+ if (!args) {
+ ast_log(LOG_WARNING, "SUB definition missing name at line %d of %s\n", lineno, script);
+ break;
+ }
+ if (process_token(vname, args, sizeof(vname) - 1, ARG_STRING)) {
+ ast_log(LOG_WARNING, "'%s' is not a valid token for a KEY name at line %d of %s\n", args, lineno, script);
+ break;
+ }
+ state->sub = getsubbyname(state, vname, script, lineno);
+ if (!state->sub) {
+ ast_log(LOG_WARNING, "Out of subroutine space at line %d of %s\n", lineno, script);
+ break;
+ }
+ if (state->sub->defined) {
+ ast_log(LOG_WARNING, "Cannot redefine subroutine '%s' at line %d of %s\n", vname, lineno, script);
+ break;
+ }
+ /* Setup sub */
+ state->sub->data[0] = 130;
+ /* 1 is the length */
+ state->sub->data[2] = 0x0; /* Clear extensibility bit */
+ state->sub->datalen = 3;
+ if (state->sub->id) {
+ /* If this isn't the main subroutine, make a subroutine label for it */
+ state->sub->data[3] = 9;
+ state->sub->data[4] = state->sub->id;
+ /* 5 is length */
+ state->sub->data[6] = 0xff;
+ state->sub->datalen = 7;
+ }
+ args = get_token(&buf, script, lineno);
+ if (!args || strcasecmp(args, "IS")) {
+ ast_log(LOG_WARNING, "Expecting 'IS', but got '%s' at line %d of %s\n", args ? args : "<nothing>", lineno, script);
+ break;
+ }
+ state->state = STATE_INSUB;
+ } else if (!strcasecmp(keyword, "STATE")) {
+ args = get_token(&buf, script, lineno);
+ if (!args) {
+ ast_log(LOG_WARNING, "STATE definition missing name at line %d of %s\n", lineno, script);
+ break;
+ }
+ if (process_token(vname, args, sizeof(vname) - 1, ARG_STRING)) {
+ ast_log(LOG_WARNING, "'%s' is not a valid token for a STATE name at line %d of %s\n", args, lineno, script);
+ break;
+ }
+ if (getstatebyname(state, vname, script, lineno, 0)) {
+ ast_log(LOG_WARNING, "State '%s' is already defined at line %d of %s\n", vname, lineno, script);
+ break;
+ }
+ getstatebyname(state, vname, script, lineno, 1);
+ } else if (!strcasecmp(keyword, "FLAG")) {
+ args = get_token(&buf, script, lineno);
+ if (!args) {
+ ast_log(LOG_WARNING, "FLAG definition missing name at line %d of %s\n", lineno, script);
+ break;
+ }
+ if (process_token(vname, args, sizeof(vname) - 1, ARG_STRING)) {
+ ast_log(LOG_WARNING, "'%s' is not a valid token for a FLAG name at line %d of %s\n", args, lineno, script);
+ break;
+ }
+ if (getflagbyname(state, vname, script, lineno, 0)) {
+ ast_log(LOG_WARNING, "Flag '%s' is already defined\n", vname);
+ break;
+ }
+ getflagbyname(state, vname, script, lineno, 1);
+ } else if (!strcasecmp(keyword, "DISPLAY")) {
+ lrci = 0;
+ wi = 0;
+ args = get_token(&buf, script, lineno);
+ if (!args) {
+ ast_log(LOG_WARNING, "SUB definition missing name at line %d of %s\n", lineno, script);
+ break;
+ }
+ if (process_token(vname, args, sizeof(vname) - 1, ARG_STRING)) {
+ ast_log(LOG_WARNING, "'%s' is not a valid token for a KEY name at line %d of %s\n", args, lineno, script);
+ break;
+ }
+ if (getdisplaybyname(state, vname, script, lineno, 0)) {
+ ast_log(LOG_WARNING, "State '%s' is already defined\n", vname);
+ break;
+ }
+ disp = getdisplaybyname(state, vname, script, lineno, 1);
+ if (!disp)
+ break;
+ args = get_token(&buf, script, lineno);
+ if (!args || strcasecmp(args, "IS")) {
+ ast_log(LOG_WARNING, "Missing 'IS' at line %d of %s\n", lineno, script);
+ break;
+ }
+ args = get_token(&buf, script, lineno);
+ if (!args) {
+ ast_log(LOG_WARNING, "Missing Column 1 text at line %d of %s\n", lineno, script);
+ break;
+ }
+ if (process_token(tmp, args, sizeof(tmp) - 1, ARG_STRING)) {
+ ast_log(LOG_WARNING, "Token '%s' is not valid column 1 text at line %d of %s\n", args, lineno, script);
+ break;
+ }
+ if (strlen(tmp) > 20) {
+ ast_log(LOG_WARNING, "Truncating column one to 20 characters at line %d of %s\n", lineno, script);
+ tmp[20] = '\0';
+ }
+ memcpy(disp->data + 5, tmp, strlen(tmp));
+ disp->datalen = strlen(tmp) + 5;
+ disp->data[disp->datalen++] = 0xff;
+
+ args = get_token(&buf, script, lineno);
+ if (args && !process_token(tmp, args, sizeof(tmp) - 1, ARG_STRING)) {
+ /* Got a column two */
+ if (strlen(tmp) > 20) {
+ ast_log(LOG_WARNING, "Truncating column two to 20 characters at line %d of %s\n", lineno, script);
+ tmp[20] = '\0';
+ }
+ memcpy(disp->data + disp->datalen, tmp, strlen(tmp));
+ disp->datalen += strlen(tmp);
+ args = get_token(&buf, script, lineno);
+ }
+ while(args) {
+ if (!strcasecmp(args, "JUSTIFY")) {
+ args = get_token(&buf, script, lineno);
+ if (!args) {
+ ast_log(LOG_WARNING, "Qualifier 'JUSTIFY' requires an argument at line %d of %s\n", lineno, script);
+ break;
+ }
+ lrci = getjustifybyname(args);
+ if (lrci < 0) {
+ ast_log(LOG_WARNING, "'%s' is not a valid justification at line %d of %s\n", args, lineno, script);
+ break;
+ }
+ } else if (!strcasecmp(args, "WRAP")) {
+ wi = 0x80;
+ } else {
+ ast_log(LOG_WARNING, "'%s' is not a known qualifier at line %d of %s\n", args, lineno, script);
+ break;
+ }
+ args = get_token(&buf, script, lineno);
+ }
+ if (args) {
+ /* Something bad happened */
+ break;
+ }
+ disp->data[0] = 129;
+ disp->data[1] = disp->datalen - 2;
+ disp->data[2] = ((lrci & 0x3) << 6) | disp->id;
+ disp->data[3] = wi;
+ disp->data[4] = 0xff;
+ } else {
+ ast_log(LOG_WARNING, "Invalid or Unknown keyword '%s' in PROGRAM\n", keyword);
+ }
+ break;
+ case STATE_INKEY:
+ if (process_returncode(state->key, keyword, buf, state, script, lineno)) {
+ if (!strcasecmp(keyword, "ENDKEY")) {
+ /* Return to normal operation and increment current key */
+ state->state = STATE_NORMAL;
+ state->key->defined = 1;
+ state->key->retstr[1] = state->key->retstrlen - 2;
+ state->key = NULL;
+ } else {
+ ast_log(LOG_WARNING, "Invalid or Unknown keyword '%s' in SOFTKEY definition at line %d of %s\n", keyword, lineno, script);
+ }
+ }
+ break;
+ case STATE_INIF:
+ if (process_opcode(state->sub, keyword, buf, state, script, lineno)) {
+ if (!strcasecmp(keyword, "ENDIF")) {
+ /* Return to normal SUB operation and increment current key */
+ state->state = STATE_INSUB;
+ state->sub->defined = 1;
+ /* Store the proper number of instructions */
+ state->sub->ifdata[2] = state->sub->ifinscount;
+ } else if (!strcasecmp(keyword, "GOTO")) {
+ args = get_token(&buf, script, lineno);
+ if (!args) {
+ ast_log(LOG_WARNING, "GOTO clause missing Subscript name at line %d of %s\n", lineno, script);
+ break;
+ }
+ if (process_token(tmp, args, sizeof(tmp) - 1, ARG_STRING)) {
+ ast_log(LOG_WARNING, "'%s' is not a valid subscript name token at line %d of %s\n", args, lineno, script);
+ break;
+ }
+ newsub = getsubbyname(state, tmp, script, lineno);
+ if (!newsub)
+ break;
+ /* Somehow you use GOTO to go to another place */
+ state->sub->data[state->sub->datalen++] = 0x8;
+ state->sub->data[state->sub->datalen++] = state->sub->ifdata[1];
+ state->sub->data[state->sub->datalen++] = newsub->id;
+ /* Terminate */
+ state->sub->data[state->sub->datalen++] = 0xff;
+ /* Increment counters */
+ state->sub->inscount++;
+ state->sub->ifinscount++;
+ } else {
+ ast_log(LOG_WARNING, "Invalid or Unknown keyword '%s' in IF clause at line %d of %s\n", keyword, lineno, script);
+ }
+ } else
+ state->sub->ifinscount++;
+ break;
+ case STATE_INSUB:
+ if (process_opcode(state->sub, keyword, buf, state, script, lineno)) {
+ if (!strcasecmp(keyword, "ENDSUB")) {
+ /* Return to normal operation and increment current key */
+ state->state = STATE_NORMAL;
+ state->sub->defined = 1;
+ /* Store the proper length */
+ state->sub->data[1] = state->sub->datalen - 2;
+ if (state->sub->id) {
+ /* if this isn't main, store number of instructions, too */
+ state->sub->data[5] = state->sub->inscount;
+ }
+ state->sub = NULL;
+ } else if (!strcasecmp(keyword, "IFEVENT")) {
+ args = get_token(&buf, script, lineno);
+ if (!args) {
+ ast_log(LOG_WARNING, "IFEVENT clause missing Event name at line %d of %s\n", lineno, script);
+ break;
+ }
+ event = geteventbyname(args);
+ if (event < 1) {
+ ast_log(LOG_WARNING, "'%s' is not a valid event\n", args);
+ break;
+ }
+ args = get_token(&buf, script, lineno);
+ if (!args || strcasecmp(args, "THEN")) {
+ ast_log(LOG_WARNING, "IFEVENT clause missing 'THEN' at line %d of %s\n", lineno, script);
+ break;
+ }
+ state->sub->ifinscount = 0;
+ state->sub->ifdata = state->sub->data +
+ state->sub->datalen;
+ /* Reserve header and insert op codes */
+ state->sub->ifdata[0] = 0x1;
+ state->sub->ifdata[1] = event;
+ /* 2 is for the number of instructions */
+ state->sub->ifdata[3] = 0xff;
+ state->sub->datalen += 4;
+ /* Update Subscript instruction count */
+ state->sub->inscount++;
+ state->state = STATE_INIF;
+ } else {
+ ast_log(LOG_WARNING, "Invalid or Unknown keyword '%s' in SUB definition at line %d of %s\n", keyword, lineno, script);
+ }
+ }
+ break;
+ default:
+ ast_log(LOG_WARNING, "Can't process keyword '%s' in weird state %d\n", keyword, state->state);
+ }
+ return 0;
+}
+
+static struct adsi_script *compile_script(char *script)
+{
+ FILE *f;
+ char fn[256];
+ char buf[256];
+ char *c;
+ int lineno=0;
+ int x, err;
+ struct adsi_script *scr;
+ if (script[0] == '/')
+ ast_copy_string(fn, script, sizeof(fn));
+ else
+ snprintf(fn, sizeof(fn), "%s/%s", (char *)ast_config_AST_CONFIG_DIR, script);
+ f = fopen(fn, "r");
+ if (!f) {
+ ast_log(LOG_WARNING, "Can't open file '%s'\n", fn);
+ return NULL;
+ }
+ if (!(scr = ast_calloc(1, sizeof(*scr)))) {
+ fclose(f);
+ return NULL;
+ }
+ /* Create "main" as first subroutine */
+ getsubbyname(scr, "main", NULL, 0);
+ while(!feof(f)) {
+ if (!fgets(buf, sizeof(buf), f)) {
+ continue;
+ }
+ if (!feof(f)) {
+ lineno++;
+ /* Trim off trailing return */
+ buf[strlen(buf) - 1] = '\0';
+ c = strchr(buf, ';');
+ /* Strip comments */
+ if (c)
+ *c = '\0';
+ if (!ast_strlen_zero(buf))
+ adsi_process(scr, buf, script, lineno);
+ }
+ }
+ fclose(f);
+ /* Make sure we're in the main routine again */
+ switch(scr->state) {
+ case STATE_NORMAL:
+ break;
+ case STATE_INSUB:
+ ast_log(LOG_WARNING, "Missing ENDSUB at end of file %s\n", script);
+ free(scr);
+ return NULL;
+ case STATE_INKEY:
+ ast_log(LOG_WARNING, "Missing ENDKEY at end of file %s\n", script);
+ free(scr);
+ return NULL;
+ }
+ err = 0;
+
+ /* Resolve all keys and record their lengths */
+ for (x=0;x<scr->numkeys;x++) {
+ if (!scr->keys[x].defined) {
+ ast_log(LOG_WARNING, "Key '%s' referenced but never defined in file %s\n", scr->keys[x].vname, fn);
+ err++;
+ }
+ }
+
+ /* Resolve all subs */
+ for (x=0;x<scr->numsubs;x++) {
+ if (!scr->subs[x].defined) {
+ ast_log(LOG_WARNING, "Subscript '%s' referenced but never defined in file %s\n", scr->subs[x].vname, fn);
+ err++;
+ }
+ if (x == (scr->numsubs - 1)) {
+ /* Clear out extension bit on last message */
+ scr->subs[x].data[2] = 0x80;
+ }
+ }
+
+ if (err) {
+ free(scr);
+ return NULL;
+ }
+ return scr;
+}
+
+#ifdef DUMP_MESSAGES
+static void dump_message(char *type, char *vname, unsigned char *buf, int buflen)
+{
+ int x;
+ printf("%s %s: [ ", type, vname);
+ for (x=0;x<buflen;x++)
+ printf("%02x ", buf[x]);
+ printf("]\n");
+}
+#endif
+
+static int adsi_prog(struct ast_channel *chan, char *script)
+{
+ struct adsi_script *scr;
+ int x;
+ unsigned char buf[1024];
+ int bytes;
+ scr = compile_script(script);
+ if (!scr)
+ return -1;
+
+ /* Start an empty ADSI Session */
+ if (ast_adsi_load_session(chan, NULL, 0, 1) < 1)
+ return -1;
+
+ /* Now begin the download attempt */
+ if (ast_adsi_begin_download(chan, scr->desc, scr->fdn, scr->sec, scr->ver)) {
+ /* User rejected us for some reason */
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "User rejected download attempt\n");
+ ast_log(LOG_NOTICE, "User rejected download on channel %s\n", chan->name);
+ free(scr);
+ return -1;
+ }
+
+ bytes = 0;
+ /* Start with key definitions */
+ for (x=0;x<scr->numkeys;x++) {
+ if (bytes + scr->keys[x].retstrlen > 253) {
+ /* Send what we've collected so far */
+ if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD)) {
+ ast_log(LOG_WARNING, "Unable to send chunk ending at %d\n", x);
+ return -1;
+ }
+ bytes =0;
+ }
+ memcpy(buf + bytes, scr->keys[x].retstr, scr->keys[x].retstrlen);
+ bytes += scr->keys[x].retstrlen;
+#ifdef DUMP_MESSAGES
+ dump_message("Key", scr->keys[x].vname, scr->keys[x].retstr, scr->keys[x].retstrlen);
+#endif
+ }
+ if (bytes) {
+ if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD)) {
+ ast_log(LOG_WARNING, "Unable to send chunk ending at %d\n", x);
+ return -1;
+ }
+ }
+
+ bytes = 0;
+ /* Continue with the display messages */
+ for (x=0;x<scr->numdisplays;x++) {
+ if (bytes + scr->displays[x].datalen > 253) {
+ /* Send what we've collected so far */
+ if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD)) {
+ ast_log(LOG_WARNING, "Unable to send chunk ending at %d\n", x);
+ return -1;
+ }
+ bytes =0;
+ }
+ memcpy(buf + bytes, scr->displays[x].data, scr->displays[x].datalen);
+ bytes += scr->displays[x].datalen;
+#ifdef DUMP_MESSAGES
+ dump_message("Display", scr->displays[x].vname, scr->displays[x].data, scr->displays[x].datalen);
+#endif
+ }
+ if (bytes) {
+ if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD)) {
+ ast_log(LOG_WARNING, "Unable to send chunk ending at %d\n", x);
+ return -1;
+ }
+ }
+
+ bytes = 0;
+ /* Send subroutines */
+ for (x=0;x<scr->numsubs;x++) {
+ if (bytes + scr->subs[x].datalen > 253) {
+ /* Send what we've collected so far */
+ if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD)) {
+ ast_log(LOG_WARNING, "Unable to send chunk ending at %d\n", x);
+ return -1;
+ }
+ bytes =0;
+ }
+ memcpy(buf + bytes, scr->subs[x].data, scr->subs[x].datalen);
+ bytes += scr->subs[x].datalen;
+#ifdef DUMP_MESSAGES
+ dump_message("Sub", scr->subs[x].vname, scr->subs[x].data, scr->subs[x].datalen);
+#endif
+ }
+ if (bytes) {
+ if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD)) {
+ ast_log(LOG_WARNING, "Unable to send chunk ending at %d\n", x);
+ return -1;
+ }
+ }
+
+
+ bytes = 0;
+ bytes += ast_adsi_display(buf, ADSI_INFO_PAGE, 1, ADSI_JUST_LEFT, 0, "Download complete.", "");
+ bytes += ast_adsi_set_line(buf, ADSI_INFO_PAGE, 1);
+ if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY) < 0)
+ return -1;
+ if (ast_adsi_end_download(chan)) {
+ /* Download failed for some reason */
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Download attempt failed\n");
+ ast_log(LOG_NOTICE, "Download failed on %s\n", chan->name);
+ free(scr);
+ return -1;
+ }
+ free(scr);
+ ast_adsi_unload_session(chan);
+ return 0;
+}
+
+static int adsi_exec(struct ast_channel *chan, void *data)
+{
+ int res=0;
+ struct ast_module_user *u;
+
+ u = ast_module_user_add(chan);
+
+ if (ast_strlen_zero(data))
+ data = "asterisk.adsi";
+
+ if (!ast_adsi_available(chan)) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "ADSI Unavailable on CPE. Not bothering to try.\n");
+ } else {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "ADSI Available on CPE. Attempting Upload.\n");
+ res = adsi_prog(chan, data);
+ }
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ ast_module_user_hangup_all();
+
+ res = ast_unregister_application(app);
+
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, adsi_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Asterisk ADSI Programming Application");
diff --git a/apps/app_alarmreceiver.c b/apps/app_alarmreceiver.c
new file mode 100644
index 000000000..8afce25d5
--- /dev/null
+++ b/apps/app_alarmreceiver.c
@@ -0,0 +1,841 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2004 - 2005 Steve Rodgers
+ *
+ * Steve Rodgers <hwstar@rodgers.sdcoxmail.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 Central Station Alarm receiver for Ademco Contact ID
+ * \author Steve Rodgers <hwstar@rodgers.sdcoxmail.com>
+ *
+ * *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING ***
+ *
+ * Use at your own risk. Please consult the GNU GPL license document included with Asterisk. *
+ *
+ * *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING ***
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <sys/time.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/ulaw.h"
+#include "asterisk/options.h"
+#include "asterisk/app.h"
+#include "asterisk/dsp.h"
+#include "asterisk/config.h"
+#include "asterisk/localtime.h"
+#include "asterisk/callerid.h"
+#include "asterisk/astdb.h"
+#include "asterisk/utils.h"
+
+#define ALMRCV_CONFIG "alarmreceiver.conf"
+#define ADEMCO_CONTACT_ID "ADEMCO_CONTACT_ID"
+
+struct event_node{
+ char data[17];
+ struct event_node *next;
+};
+
+typedef struct event_node event_node_t;
+
+static char *app = "AlarmReceiver";
+
+static char *synopsis = "Provide support for receiving alarm reports from a burglar or fire alarm panel";
+static char *descrip =
+" AlarmReceiver(): Only 1 signalling format is supported at this time: Ademco\n"
+"Contact ID. This application should be called whenever there is an alarm\n"
+"panel calling in to dump its events. The application will handshake with the\n"
+"alarm panel, and receive events, validate them, handshake them, and store them\n"
+"until the panel hangs up. Once the panel hangs up, the application will run the\n"
+"system command specified by the eventcmd setting in alarmreceiver.conf and pipe\n"
+"the events to the standard input of the application. The configuration file also\n"
+"contains settings for DTMF timing, and for the loudness of the acknowledgement\n"
+"tones.\n";
+
+/* Config Variables */
+
+static int fdtimeout = 2000;
+static int sdtimeout = 200;
+static int toneloudness = 4096;
+static int log_individual_events = 0;
+static char event_spool_dir[128] = {'\0'};
+static char event_app[128] = {'\0'};
+static char db_family[128] = {'\0'};
+static char time_stamp_format[128] = {"%a %b %d, %Y @ %H:%M:%S %Z"};
+
+/* Misc variables */
+
+static char event_file[14] = "/event-XXXXXX";
+
+/*
+* Attempt to access a database variable and increment it,
+* provided that the user defined db-family in alarmreceiver.conf
+* The alarmreceiver app will write statistics to a few variables
+* in this family if it is defined. If the new key doesn't exist in the
+* family, then create it and set its value to 1.
+*/
+
+static void database_increment( char *key )
+{
+ int res = 0;
+ unsigned v;
+ char value[16];
+
+
+ if (ast_strlen_zero(db_family))
+ return; /* If not defined, don't do anything */
+
+ res = ast_db_get(db_family, key, value, sizeof(value) - 1);
+
+ if(res){
+ if(option_verbose >= 4)
+ ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Creating database entry %s and setting to 1\n", key);
+ /* Guess we have to create it */
+ res = ast_db_put(db_family, key, "1");
+ return;
+ }
+
+ sscanf(value, "%u", &v);
+ v++;
+
+ if(option_verbose >= 4)
+ ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: New value for %s: %u\n", key, v);
+
+ snprintf(value, sizeof(value), "%u", v);
+
+ res = ast_db_put(db_family, key, value);
+
+ if((res)&&(option_verbose >= 4))
+ ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: database_increment write error\n");
+
+ return;
+}
+
+
+/*
+* Build a MuLaw data block for a single frequency tone
+*/
+
+static void make_tone_burst(unsigned char *data, float freq, float loudness, int len, int *x)
+{
+ int i;
+ float val;
+
+ for(i = 0; i < len; i++){
+ val = loudness * sin((freq * 2.0 * M_PI * (*x)++)/8000.0);
+ data[i] = AST_LIN2MU((int)val);
+ }
+
+ /* wrap back around from 8000 */
+
+ if (*x >= 8000) *x = 0;
+ return;
+}
+
+/*
+* Send a single tone burst for a specifed duration and frequency.
+* Returns 0 if successful
+*/
+
+static int send_tone_burst(struct ast_channel *chan, float freq, int duration, int tldn)
+{
+ int res = 0;
+ int i = 0;
+ int x = 0;
+ struct ast_frame *f, wf;
+
+ struct {
+ unsigned char offset[AST_FRIENDLY_OFFSET];
+ unsigned char buf[640];
+ } tone_block;
+
+ for(;;)
+ {
+
+ if (ast_waitfor(chan, -1) < 0){
+ res = -1;
+ break;
+ }
+
+ f = ast_read(chan);
+ if (!f){
+ res = -1;
+ break;
+ }
+
+ if (f->frametype == AST_FRAME_VOICE) {
+ wf.frametype = AST_FRAME_VOICE;
+ wf.subclass = AST_FORMAT_ULAW;
+ wf.offset = AST_FRIENDLY_OFFSET;
+ wf.mallocd = 0;
+ wf.data = tone_block.buf;
+ wf.datalen = f->datalen;
+ wf.samples = wf.datalen;
+
+ make_tone_burst(tone_block.buf, freq, (float) tldn, wf.datalen, &x);
+
+ i += wf.datalen / 8;
+ if (i > duration) {
+ ast_frfree(f);
+ break;
+ }
+ if (ast_write(chan, &wf)){
+ if(option_verbose >= 4)
+ ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Failed to write frame on %s\n", chan->name);
+ ast_log(LOG_WARNING, "AlarmReceiver Failed to write frame on %s\n",chan->name);
+ res = -1;
+ ast_frfree(f);
+ break;
+ }
+ }
+
+ ast_frfree(f);
+ }
+ return res;
+}
+
+/*
+* Receive a string of DTMF digits where the length of the digit string is known in advance. Do not give preferential
+* treatment to any digit value, and allow separate time out values to be specified for the first digit and all subsequent
+* digits.
+*
+* Returns 0 if all digits successfully received.
+* Returns 1 if a digit time out occurred
+* Returns -1 if the caller hung up or there was a channel error.
+*
+*/
+
+static int receive_dtmf_digits(struct ast_channel *chan, char *digit_string, int length, int fdto, int sdto)
+{
+ int res = 0;
+ int i = 0;
+ int r;
+ struct ast_frame *f;
+ struct timeval lastdigittime;
+
+ lastdigittime = ast_tvnow();
+ for(;;){
+ /* if outa time, leave */
+ if (ast_tvdiff_ms(ast_tvnow(), lastdigittime) >
+ ((i > 0) ? sdto : fdto)){
+ if(option_verbose >= 4)
+ ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: DTMF Digit Timeout on %s\n", chan->name);
+
+ ast_log(LOG_DEBUG,"AlarmReceiver: DTMF timeout on chan %s\n",chan->name);
+
+ res = 1;
+ break;
+ }
+
+ if ((r = ast_waitfor(chan, -1) < 0)) {
+ ast_log(LOG_DEBUG, "Waitfor returned %d\n", r);
+ continue;
+ }
+
+ f = ast_read(chan);
+
+ if (f == NULL){
+ res = -1;
+ break;
+ }
+
+ /* If they hung up, leave */
+ if ((f->frametype == AST_FRAME_CONTROL) &&
+ (f->subclass == AST_CONTROL_HANGUP)){
+ ast_frfree(f);
+ res = -1;
+ break;
+ }
+
+ /* if not DTMF, just do it again */
+ if (f->frametype != AST_FRAME_DTMF){
+ ast_frfree(f);
+ continue;
+ }
+
+ digit_string[i++] = f->subclass; /* save digit */
+
+ ast_frfree(f);
+
+ /* If we have all the digits we expect, leave */
+ if(i >= length)
+ break;
+
+ lastdigittime = ast_tvnow();
+ }
+
+ digit_string[i] = '\0'; /* Nul terminate the end of the digit string */
+ return res;
+
+}
+
+/*
+* Write the metadata to the log file
+*/
+
+static int write_metadata( FILE *logfile, char *signalling_type, struct ast_channel *chan)
+{
+ int res = 0;
+ time_t t;
+ struct tm now;
+ char *cl,*cn;
+ char workstring[80];
+ char timestamp[80];
+
+ /* Extract the caller ID location */
+ if (chan->cid.cid_num)
+ ast_copy_string(workstring, chan->cid.cid_num, sizeof(workstring));
+ workstring[sizeof(workstring) - 1] = '\0';
+
+ ast_callerid_parse(workstring, &cn, &cl);
+ if (cl)
+ ast_shrink_phone_number(cl);
+
+
+ /* Get the current time */
+
+ time(&t);
+ ast_localtime(&t, &now, NULL);
+
+ /* Format the time */
+
+ strftime(timestamp, sizeof(timestamp), time_stamp_format, &now);
+
+
+ res = fprintf(logfile, "\n\n[metadata]\n\n");
+
+ if(res >= 0)
+ res = fprintf(logfile, "PROTOCOL=%s\n", signalling_type);
+
+ if(res >= 0)
+ res = fprintf(logfile, "CALLINGFROM=%s\n", (!cl) ? "<unknown>" : cl);
+
+ if(res >- 0)
+ res = fprintf(logfile, "CALLERNAME=%s\n", (!cn) ? "<unknown>" : cn);
+
+ if(res >= 0)
+ res = fprintf(logfile, "TIMESTAMP=%s\n\n", timestamp);
+
+ if(res >= 0)
+ res = fprintf(logfile, "[events]\n\n");
+
+ if(res < 0){
+ ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: can't write metadata\n");
+
+ ast_log(LOG_DEBUG,"AlarmReceiver: can't write metadata\n");
+ }
+ else
+ res = 0;
+
+ return res;
+}
+
+/*
+* Write a single event to the log file
+*/
+
+static int write_event( FILE *logfile, event_node_t *event)
+{
+ int res = 0;
+
+ if( fprintf(logfile, "%s\n", event->data) < 0)
+ res = -1;
+
+ return res;
+}
+
+
+/*
+* If we are configured to log events, do so here.
+*
+*/
+
+static int log_events(struct ast_channel *chan, char *signalling_type, event_node_t *event)
+{
+
+ int res = 0;
+ char workstring[sizeof(event_spool_dir)+sizeof(event_file)] = "";
+ int fd;
+ FILE *logfile;
+ event_node_t *elp = event;
+
+ if (!ast_strlen_zero(event_spool_dir)) {
+
+ /* Make a template */
+
+ ast_copy_string(workstring, event_spool_dir, sizeof(workstring));
+ strncat(workstring, event_file, sizeof(workstring) - strlen(workstring) - 1);
+
+ /* Make the temporary file */
+
+ fd = mkstemp(workstring);
+
+ if(fd == -1){
+ ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: can't make temporary file\n");
+ ast_log(LOG_DEBUG,"AlarmReceiver: can't make temporary file\n");
+ res = -1;
+ }
+
+ if(!res){
+ logfile = fdopen(fd, "w");
+ if(logfile){
+ /* Write the file */
+ res = write_metadata(logfile, signalling_type, chan);
+ if(!res)
+ while((!res) && (elp != NULL)){
+ res = write_event(logfile, elp);
+ elp = elp->next;
+ }
+ if(!res){
+ if(fflush(logfile) == EOF)
+ res = -1;
+ if(!res){
+ if(fclose(logfile) == EOF)
+ res = -1;
+ }
+ }
+ }
+ else
+ res = -1;
+ }
+ }
+
+ return res;
+}
+
+/*
+* This function implements the logic to receive the Ademco contact ID format.
+*
+* The function will return 0 when the caller hangs up, else a -1 if there was a problem.
+*/
+
+static int receive_ademco_contact_id( struct ast_channel *chan, void *data, int fdto, int sdto, int tldn, event_node_t **ehead)
+{
+ int i,j;
+ int res = 0;
+ int checksum;
+ char event[17];
+ event_node_t *enew, *elp;
+ int got_some_digits = 0;
+ int events_received = 0;
+ int ack_retries = 0;
+
+ static char digit_map[15] = "0123456789*#ABC";
+ static unsigned char digit_weights[15] = {10,1,2,3,4,5,6,7,8,9,11,12,13,14,15};
+
+ database_increment("calls-received");
+
+ /* Wait for first event */
+
+ if(option_verbose >= 4)
+ ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Waiting for first event from panel\n");
+
+ while(res >= 0){
+
+ if(got_some_digits == 0){
+
+ /* Send ACK tone sequence */
+
+
+ if(option_verbose >= 4)
+ ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Sending 1400Hz 100ms burst (ACK)\n");
+
+
+ res = send_tone_burst(chan, 1400.0, 100, tldn);
+
+ if(!res)
+ res = ast_safe_sleep(chan, 100);
+
+ if(!res){
+ if(option_verbose >= 4)
+ ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Sending 2300Hz 100ms burst (ACK)\n");
+
+ res = send_tone_burst(chan, 2300.0, 100, tldn);
+ }
+
+ }
+
+ if( res >= 0)
+ res = receive_dtmf_digits(chan, event, sizeof(event) - 1, fdto, sdto);
+
+ if (res < 0){
+
+ if(events_received == 0)
+ /* Hangup with no events received should be logged in the DB */
+ database_increment("no-events-received");
+ else{
+ if(ack_retries){
+ if(option_verbose >= 4)
+ ast_verbose(VERBOSE_PREFIX_2 "AlarmReceiver: ACK retries during this call: %d\n", ack_retries);
+
+ database_increment("ack-retries");
+ }
+ }
+ if(option_verbose >= 4)
+ ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: App exiting...\n");
+ res = -1;
+ break;
+ }
+
+ if(res != 0){
+ /* Didn't get all of the digits */
+ if(option_verbose >= 2)
+ ast_verbose(VERBOSE_PREFIX_2 "AlarmReceiver: Incomplete string: %s, trying again...\n", event);
+
+ if(!got_some_digits){
+ got_some_digits = (!ast_strlen_zero(event)) ? 1 : 0;
+ ack_retries++;
+ }
+ continue;
+ }
+
+ got_some_digits = 1;
+
+ if(option_verbose >= 2)
+ ast_verbose(VERBOSE_PREFIX_2 "AlarmReceiver: Received Event %s\n", event);
+ ast_log(LOG_DEBUG, "AlarmReceiver: Received event: %s\n", event);
+
+ /* Calculate checksum */
+
+ for(j = 0, checksum = 0; j < 16; j++){
+ for(i = 0 ; i < sizeof(digit_map) ; i++){
+ if(digit_map[i] == event[j])
+ break;
+ }
+
+ if(i == 16)
+ break;
+
+ checksum += digit_weights[i];
+ }
+
+ if(i == 16){
+ if(option_verbose >= 2)
+ ast_verbose(VERBOSE_PREFIX_2 "AlarmReceiver: Bad DTMF character %c, trying again\n", event[j]);
+ continue; /* Bad character */
+ }
+
+ /* Checksum is mod(15) of the total */
+
+ checksum = checksum % 15;
+
+ if (checksum) {
+ database_increment("checksum-errors");
+ if (option_verbose >= 2)
+ ast_verbose(VERBOSE_PREFIX_2 "AlarmReceiver: Nonzero checksum\n");
+ ast_log(LOG_DEBUG, "AlarmReceiver: Nonzero checksum\n");
+ continue;
+ }
+
+ /* Check the message type for correctness */
+
+ if(strncmp(event + 4, "18", 2)){
+ if(strncmp(event + 4, "98", 2)){
+ database_increment("format-errors");
+ if(option_verbose >= 2)
+ ast_verbose(VERBOSE_PREFIX_2 "AlarmReceiver: Wrong message type\n");
+ ast_log(LOG_DEBUG, "AlarmReceiver: Wrong message type\n");
+ continue;
+ }
+ }
+
+ events_received++;
+
+ /* Queue the Event */
+ if (!(enew = ast_calloc(1, sizeof(*enew)))) {
+ res = -1;
+ break;
+ }
+
+ enew->next = NULL;
+ ast_copy_string(enew->data, event, sizeof(enew->data));
+
+ /*
+ * Insert event onto end of list
+ */
+
+ if(*ehead == NULL){
+ *ehead = enew;
+ }
+ else{
+ for(elp = *ehead; elp->next != NULL; elp = elp->next)
+ ;
+
+ elp->next = enew;
+ }
+
+ if(res > 0)
+ res = 0;
+
+ /* Let the user have the option of logging the single event before sending the kissoff tone */
+
+ if((res == 0) && (log_individual_events))
+ res = log_events(chan, ADEMCO_CONTACT_ID, enew);
+
+ /* Wait 200 msec before sending kissoff */
+
+ if(res == 0)
+ res = ast_safe_sleep(chan, 200);
+
+ /* Send the kissoff tone */
+
+ if(res == 0)
+ res = send_tone_burst(chan, 1400.0, 900, tldn);
+ }
+
+
+ return res;
+}
+
+
+/*
+* This is the main function called by Asterisk Core whenever the App is invoked in the extension logic.
+* This function will always return 0.
+*/
+
+static int alarmreceiver_exec(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ struct ast_module_user *u;
+ event_node_t *elp, *efree;
+ char signalling_type[64] = "";
+
+ event_node_t *event_head = NULL;
+
+ u = ast_module_user_add(chan);
+
+ /* Set write and read formats to ULAW */
+
+ if(option_verbose >= 4)
+ ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Setting read and write formats to ULAW\n");
+
+ if (ast_set_write_format(chan,AST_FORMAT_ULAW)){
+ ast_log(LOG_WARNING, "AlarmReceiver: Unable to set write format to Mu-law on %s\n",chan->name);
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ if (ast_set_read_format(chan,AST_FORMAT_ULAW)){
+ ast_log(LOG_WARNING, "AlarmReceiver: Unable to set read format to Mu-law on %s\n",chan->name);
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ /* Set default values for this invokation of the application */
+
+ ast_copy_string(signalling_type, ADEMCO_CONTACT_ID, sizeof(signalling_type));
+
+
+ /* Answer the channel if it is not already */
+
+ if(option_verbose >= 4)
+ ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Answering channel\n");
+
+ if (chan->_state != AST_STATE_UP) {
+
+ res = ast_answer(chan);
+
+ if (res) {
+ ast_module_user_remove(u);
+ return -1;
+ }
+ }
+
+ /* Wait for the connection to settle post-answer */
+
+ if(option_verbose >= 4)
+ ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Waiting for connection to stabilize\n");
+
+ res = ast_safe_sleep(chan, 1250);
+
+ /* Attempt to receive the events */
+
+ if(!res){
+
+ /* Determine the protocol to receive in advance */
+ /* Note: Ademco contact is the only one supported at this time */
+ /* Others may be added later */
+
+ if(!strcmp(signalling_type, ADEMCO_CONTACT_ID))
+ receive_ademco_contact_id(chan, data, fdtimeout, sdtimeout, toneloudness, &event_head);
+ else
+ res = -1;
+ }
+
+
+
+ /* Events queued by receiver, write them all out here if so configured */
+
+ if((!res) && (log_individual_events == 0)){
+ res = log_events(chan, signalling_type, event_head);
+
+ }
+
+ /*
+ * Do we exec a command line at the end?
+ */
+
+ if((!res) && (!ast_strlen_zero(event_app)) && (event_head)){
+ ast_log(LOG_DEBUG,"Alarmreceiver: executing: %s\n", event_app);
+ ast_safe_system(event_app);
+ }
+
+ /*
+ * Free up the data allocated in our linked list
+ */
+
+ for(elp = event_head; (elp != NULL);){
+ efree = elp;
+ elp = elp->next;
+ free(efree);
+ }
+
+
+ ast_module_user_remove(u);
+
+ return 0;
+}
+
+/*
+* Load the configuration from the configuration file
+*/
+
+static int load_config(void)
+{
+ struct ast_config *cfg;
+ const char *p;
+
+ /* Read in the config file */
+
+ cfg = ast_config_load(ALMRCV_CONFIG);
+
+ if(!cfg){
+
+ if(option_verbose >= 4)
+ ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: No config file\n");
+ return 0;
+ }
+ else{
+
+
+ p = ast_variable_retrieve(cfg, "general", "eventcmd");
+
+ if(p){
+ ast_copy_string(event_app, p, sizeof(event_app));
+ event_app[sizeof(event_app) - 1] = '\0';
+ }
+
+ p = ast_variable_retrieve(cfg, "general", "loudness");
+ if(p){
+ toneloudness = atoi(p);
+ if(toneloudness < 100)
+ toneloudness = 100;
+ if(toneloudness > 8192)
+ toneloudness = 8192;
+ }
+ p = ast_variable_retrieve(cfg, "general", "fdtimeout");
+ if(p){
+ fdtimeout = atoi(p);
+ if(fdtimeout < 1000)
+ fdtimeout = 1000;
+ if(fdtimeout > 10000)
+ fdtimeout = 10000;
+ }
+
+ p = ast_variable_retrieve(cfg, "general", "sdtimeout");
+ if(p){
+ sdtimeout = atoi(p);
+ if(sdtimeout < 110)
+ sdtimeout = 110;
+ if(sdtimeout > 4000)
+ sdtimeout = 4000;
+
+ }
+
+ p = ast_variable_retrieve(cfg, "general", "logindividualevents");
+ if(p){
+ log_individual_events = ast_true(p);
+
+ }
+
+ p = ast_variable_retrieve(cfg, "general", "eventspooldir");
+
+ if(p){
+ ast_copy_string(event_spool_dir, p, sizeof(event_spool_dir));
+ event_spool_dir[sizeof(event_spool_dir) - 1] = '\0';
+ }
+
+ p = ast_variable_retrieve(cfg, "general", "timestampformat");
+
+ if(p){
+ ast_copy_string(time_stamp_format, p, sizeof(time_stamp_format));
+ time_stamp_format[sizeof(time_stamp_format) - 1] = '\0';
+ }
+
+ p = ast_variable_retrieve(cfg, "general", "db-family");
+
+ if(p){
+ ast_copy_string(db_family, p, sizeof(db_family));
+ db_family[sizeof(db_family) - 1] = '\0';
+ }
+ ast_config_destroy(cfg);
+ }
+ return 1;
+
+}
+
+/*
+* These functions are required to implement an Asterisk App.
+*/
+
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ if(load_config())
+ return ast_register_application(app, alarmreceiver_exec, synopsis, descrip);
+ else
+ return AST_MODULE_LOAD_DECLINE;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Alarm Receiver for Asterisk");
diff --git a/apps/app_amd.c b/apps/app_amd.c
new file mode 100644
index 000000000..52f474048
--- /dev/null
+++ b/apps/app_amd.c
@@ -0,0 +1,430 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2003 - 2006, Aheeva Technology.
+ *
+ * Claude Klimos (claude.klimos@aheeva.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.
+ *
+ * A license has been granted to Digium (via disclaimer) for the use of
+ * this code.
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "asterisk/module.h"
+#include "asterisk/lock.h"
+#include "asterisk/options.h"
+#include "asterisk/channel.h"
+#include "asterisk/dsp.h"
+#include "asterisk/pbx.h"
+#include "asterisk/config.h"
+#include "asterisk/app.h"
+
+
+static char *app = "AMD";
+static char *synopsis = "Attempts to detect answering machines";
+static char *descrip =
+" AMD([initialSilence][|greeting][|afterGreetingSilence][|totalAnalysisTime]\n"
+" [|minimumWordLength][|betweenWordsSilence][|maximumNumberOfWords]\n"
+" [|silenceThreshold])\n"
+" This application attempts to detect answering machines at the beginning\n"
+" of outbound calls. Simply call this application after the call\n"
+" has been answered (outbound only, of course).\n"
+" When loaded, AMD reads amd.conf and uses the parameters specified as\n"
+" default values. Those default values get overwritten when calling AMD\n"
+" with parameters.\n"
+"- 'initialSilence' is the maximum silence duration before the greeting. If\n"
+" exceeded then MACHINE.\n"
+"- 'greeting' is the maximum length of a greeting. If exceeded then MACHINE.\n"
+"- 'afterGreetingSilence' is the silence after detecting a greeting.\n"
+" If exceeded then HUMAN.\n"
+"- 'totalAnalysisTime' is the maximum time allowed for the algorithm to decide\n"
+" on a HUMAN or MACHINE.\n"
+"- 'minimumWordLength'is the minimum duration of Voice to considered as a word.\n"
+"- 'betweenWordsSilence' is the minimum duration of silence after a word to \n"
+" consider the audio that follows as a new word.\n"
+"- 'maximumNumberOfWords'is the maximum number of words in the greeting. \n"
+" If exceeded then MACHINE.\n"
+"- 'silenceThreshold' is the silence threshold.\n"
+"This application sets the following channel variable upon completion:\n"
+" AMDSTATUS - This is the status of the answering machine detection.\n"
+" Possible values are:\n"
+" MACHINE | HUMAN | NOTSURE | HANGUP\n"
+" AMDCAUSE - Indicates the cause that led to the conclusion.\n"
+" Possible values are:\n"
+" TOOLONG-<%d total_time>\n"
+" INITIALSILENCE-<%d silenceDuration>-<%d initialSilence>\n"
+" HUMAN-<%d silenceDuration>-<%d afterGreetingSilence>\n"
+" MAXWORDS-<%d wordsCount>-<%d maximumNumberOfWords>\n"
+" LONGGREETING-<%d voiceDuration>-<%d greeting>\n";
+
+#define STATE_IN_WORD 1
+#define STATE_IN_SILENCE 2
+
+/* Some default values for the algorithm parameters. These defaults will be overwritten from amd.conf */
+static int dfltInitialSilence = 2500;
+static int dfltGreeting = 1500;
+static int dfltAfterGreetingSilence = 800;
+static int dfltTotalAnalysisTime = 5000;
+static int dfltMinimumWordLength = 100;
+static int dfltBetweenWordsSilence = 50;
+static int dfltMaximumNumberOfWords = 3;
+static int dfltSilenceThreshold = 256;
+
+/* Set to the lowest ms value provided in amd.conf or application parameters */
+static int dfltMaxWaitTimeForFrame = 50;
+
+static void isAnsweringMachine(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ struct ast_frame *f = NULL;
+ struct ast_dsp *silenceDetector = NULL;
+ int dspsilence = 0, readFormat, framelength = 0;
+ int inInitialSilence = 1;
+ int inGreeting = 0;
+ int voiceDuration = 0;
+ int silenceDuration = 0;
+ int iTotalTime = 0;
+ int iWordsCount = 0;
+ int currentState = STATE_IN_WORD;
+ int previousState = STATE_IN_SILENCE;
+ int consecutiveVoiceDuration = 0;
+ char amdCause[256] = "", amdStatus[256] = "";
+ char *parse = ast_strdupa(data);
+
+ /* Lets set the initial values of the variables that will control the algorithm.
+ The initial values are the default ones. If they are passed as arguments
+ when invoking the application, then the default values will be overwritten
+ by the ones passed as parameters. */
+ int initialSilence = dfltInitialSilence;
+ int greeting = dfltGreeting;
+ int afterGreetingSilence = dfltAfterGreetingSilence;
+ int totalAnalysisTime = dfltTotalAnalysisTime;
+ int minimumWordLength = dfltMinimumWordLength;
+ int betweenWordsSilence = dfltBetweenWordsSilence;
+ int maximumNumberOfWords = dfltMaximumNumberOfWords;
+ int silenceThreshold = dfltSilenceThreshold;
+ int maxWaitTimeForFrame = dfltMaxWaitTimeForFrame;
+
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(argInitialSilence);
+ AST_APP_ARG(argGreeting);
+ AST_APP_ARG(argAfterGreetingSilence);
+ AST_APP_ARG(argTotalAnalysisTime);
+ AST_APP_ARG(argMinimumWordLength);
+ AST_APP_ARG(argBetweenWordsSilence);
+ AST_APP_ARG(argMaximumNumberOfWords);
+ AST_APP_ARG(argSilenceThreshold);
+ );
+
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "AMD: %s %s %s (Fmt: %d)\n", chan->name ,chan->cid.cid_ani, chan->cid.cid_rdnis, chan->readformat);
+
+ /* Lets parse the arguments. */
+ if (!ast_strlen_zero(parse)) {
+ /* Some arguments have been passed. Lets parse them and overwrite the defaults. */
+ AST_STANDARD_APP_ARGS(args, parse);
+ if (!ast_strlen_zero(args.argInitialSilence))
+ initialSilence = atoi(args.argInitialSilence);
+ if (!ast_strlen_zero(args.argGreeting))
+ greeting = atoi(args.argGreeting);
+ if (!ast_strlen_zero(args.argAfterGreetingSilence))
+ afterGreetingSilence = atoi(args.argAfterGreetingSilence);
+ if (!ast_strlen_zero(args.argTotalAnalysisTime))
+ totalAnalysisTime = atoi(args.argTotalAnalysisTime);
+ if (!ast_strlen_zero(args.argMinimumWordLength))
+ minimumWordLength = atoi(args.argMinimumWordLength);
+ if (!ast_strlen_zero(args.argBetweenWordsSilence))
+ betweenWordsSilence = atoi(args.argBetweenWordsSilence);
+ if (!ast_strlen_zero(args.argMaximumNumberOfWords))
+ maximumNumberOfWords = atoi(args.argMaximumNumberOfWords);
+ if (!ast_strlen_zero(args.argSilenceThreshold))
+ silenceThreshold = atoi(args.argSilenceThreshold);
+ } else if (option_debug)
+ ast_log(LOG_DEBUG, "AMD using the default parameters.\n");
+
+ /* Find lowest ms value, that will be max wait time for a frame */
+ if (maxWaitTimeForFrame > initialSilence)
+ maxWaitTimeForFrame = initialSilence;
+ if (maxWaitTimeForFrame > greeting)
+ maxWaitTimeForFrame = greeting;
+ if (maxWaitTimeForFrame > afterGreetingSilence)
+ maxWaitTimeForFrame = afterGreetingSilence;
+ if (maxWaitTimeForFrame > totalAnalysisTime)
+ maxWaitTimeForFrame = totalAnalysisTime;
+ if (maxWaitTimeForFrame > minimumWordLength)
+ maxWaitTimeForFrame = minimumWordLength;
+ if (maxWaitTimeForFrame > betweenWordsSilence)
+ maxWaitTimeForFrame = betweenWordsSilence;
+
+ /* Now we're ready to roll! */
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
+ "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n",
+ initialSilence, greeting, afterGreetingSilence, totalAnalysisTime,
+ minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold );
+
+ /* Set read format to signed linear so we get signed linear frames in */
+ readFormat = chan->readformat;
+ if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0 ) {
+ ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", chan->name );
+ pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
+ pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
+ return;
+ }
+
+ /* Create a new DSP that will detect the silence */
+ if (!(silenceDetector = ast_dsp_new())) {
+ ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", chan->name );
+ pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
+ pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
+ return;
+ }
+
+ /* Set silence threshold to specified value */
+ ast_dsp_set_threshold(silenceDetector, silenceThreshold);
+
+ /* Now we go into a loop waiting for frames from the channel */
+ while ((res = ast_waitfor(chan, 2 * maxWaitTimeForFrame)) > -1) {
+
+ /* If we fail to read in a frame, that means they hung up */
+ if (!(f = ast_read(chan))) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "AMD: HANGUP\n");
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Got hangup\n");
+ strcpy(amdStatus, "HANGUP");
+ break;
+ }
+
+ if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_NULL || f->frametype == AST_FRAME_CNG) {
+ /* If the total time exceeds the analysis time then give up as we are not too sure */
+ if (f->frametype == AST_FRAME_VOICE)
+ framelength = (ast_codec_get_samples(f) / DEFAULT_SAMPLES_PER_MS);
+ else
+ framelength += 2 * maxWaitTimeForFrame;
+
+ iTotalTime += framelength;
+ if (iTotalTime >= totalAnalysisTime) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name );
+ ast_frfree(f);
+ strcpy(amdStatus , "NOTSURE");
+ sprintf(amdCause , "TOOLONG-%d", iTotalTime);
+ break;
+ }
+
+ /* Feed the frame of audio into the silence detector and see if we get a result */
+ if (f->frametype != AST_FRAME_VOICE)
+ dspsilence += 2 * maxWaitTimeForFrame;
+ else {
+ dspsilence = 0;
+ ast_dsp_silence(silenceDetector, f, &dspsilence);
+ }
+
+ if (dspsilence > 0) {
+ silenceDuration = dspsilence;
+
+ if (silenceDuration >= betweenWordsSilence) {
+ if (currentState != STATE_IN_SILENCE ) {
+ previousState = currentState;
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "AMD: Changed state to STATE_IN_SILENCE\n");
+ }
+ currentState = STATE_IN_SILENCE;
+ consecutiveVoiceDuration = 0;
+ }
+
+ if (inInitialSilence == 1 && silenceDuration >= initialSilence) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n",
+ silenceDuration, initialSilence);
+ ast_frfree(f);
+ strcpy(amdStatus , "MACHINE");
+ sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence);
+ res = 1;
+ break;
+ }
+
+ if (silenceDuration >= afterGreetingSilence && inGreeting == 1) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "AMD: HUMAN: silenceDuration:%d afterGreetingSilence:%d\n",
+ silenceDuration, afterGreetingSilence);
+ ast_frfree(f);
+ strcpy(amdStatus , "HUMAN");
+ sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence);
+ res = 1;
+ break;
+ }
+
+ } else {
+ consecutiveVoiceDuration += framelength;
+ voiceDuration += framelength;
+
+ /* If I have enough consecutive voice to say that I am in a Word, I can only increment the
+ number of words if my previous state was Silence, which means that I moved into a word. */
+ if (consecutiveVoiceDuration >= minimumWordLength && currentState == STATE_IN_SILENCE) {
+ iWordsCount++;
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "AMD: Word detected. iWordsCount:%d\n", iWordsCount);
+ previousState = currentState;
+ currentState = STATE_IN_WORD;
+ }
+
+ if (iWordsCount >= maximumNumberOfWords) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: iWordsCount:%d\n", iWordsCount);
+ ast_frfree(f);
+ strcpy(amdStatus , "MACHINE");
+ sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords);
+ res = 1;
+ break;
+ }
+
+ if (inGreeting == 1 && voiceDuration >= greeting) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", voiceDuration, greeting);
+ ast_frfree(f);
+ strcpy(amdStatus , "MACHINE");
+ sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting);
+ res = 1;
+ break;
+ }
+
+ if (voiceDuration >= minimumWordLength ) {
+ silenceDuration = 0;
+ inInitialSilence = 0;
+ inGreeting = 1;
+ }
+
+ }
+ }
+ ast_frfree(f);
+ }
+
+ if (!res) {
+ /* It took too long to get a frame back. Giving up. */
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name);
+ strcpy(amdStatus , "NOTSURE");
+ sprintf(amdCause , "TOOLONG-%d", iTotalTime);
+ }
+
+ /* Set the status and cause on the channel */
+ pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus);
+ pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause);
+
+ /* Restore channel read format */
+ if (readFormat && ast_set_read_format(chan, readFormat))
+ ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name);
+
+ /* Free the DSP used to detect silence */
+ ast_dsp_free(silenceDetector);
+
+ return;
+}
+
+
+static int amd_exec(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *u = NULL;
+
+ u = ast_module_user_add(chan);
+ isAnsweringMachine(chan, data);
+ ast_module_user_remove(u);
+
+ return 0;
+}
+
+static void load_config(void)
+{
+ struct ast_config *cfg = NULL;
+ char *cat = NULL;
+ struct ast_variable *var = NULL;
+
+ if (!(cfg = ast_config_load("amd.conf"))) {
+ ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n");
+ return;
+ }
+
+ cat = ast_category_browse(cfg, NULL);
+
+ while (cat) {
+ if (!strcasecmp(cat, "general") ) {
+ var = ast_variable_browse(cfg, cat);
+ while (var) {
+ if (!strcasecmp(var->name, "initial_silence")) {
+ dfltInitialSilence = atoi(var->value);
+ } else if (!strcasecmp(var->name, "greeting")) {
+ dfltGreeting = atoi(var->value);
+ } else if (!strcasecmp(var->name, "after_greeting_silence")) {
+ dfltAfterGreetingSilence = atoi(var->value);
+ } else if (!strcasecmp(var->name, "silence_threshold")) {
+ dfltSilenceThreshold = atoi(var->value);
+ } else if (!strcasecmp(var->name, "total_analysis_time")) {
+ dfltTotalAnalysisTime = atoi(var->value);
+ } else if (!strcasecmp(var->name, "min_word_length")) {
+ dfltMinimumWordLength = atoi(var->value);
+ } else if (!strcasecmp(var->name, "between_words_silence")) {
+ dfltBetweenWordsSilence = atoi(var->value);
+ } else if (!strcasecmp(var->name, "maximum_number_of_words")) {
+ dfltMaximumNumberOfWords = atoi(var->value);
+ } else {
+ ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n",
+ app, cat, var->name, var->lineno);
+ }
+ var = var->next;
+ }
+ }
+ cat = ast_category_browse(cfg, cat);
+ }
+
+ ast_config_destroy(cfg);
+
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
+ "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n",
+ dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime,
+ dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold );
+
+ return;
+}
+
+static int unload_module(void)
+{
+ ast_module_user_hangup_all();
+ return ast_unregister_application(app);
+}
+
+static int load_module(void)
+{
+ load_config();
+ return ast_register_application(app, amd_exec, synopsis, descrip);
+}
+
+static int reload(void)
+{
+ load_config();
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Answering Machine Detection Application",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
diff --git a/apps/app_authenticate.c b/apps/app_authenticate.c
new file mode 100644
index 000000000..e182deee2
--- /dev/null
+++ b/apps/app_authenticate.c
@@ -0,0 +1,254 @@
+/*
+ * 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 Execute arbitrary authenticate commands
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/app.h"
+#include "asterisk/astdb.h"
+#include "asterisk/utils.h"
+#include "asterisk/options.h"
+
+enum {
+ OPT_ACCOUNT = (1 << 0),
+ OPT_DATABASE = (1 << 1),
+ OPT_JUMP = (1 << 2),
+ OPT_MULTIPLE = (1 << 3),
+ OPT_REMOVE = (1 << 4),
+} auth_option_flags;
+
+AST_APP_OPTIONS(auth_app_options, {
+ AST_APP_OPTION('a', OPT_ACCOUNT),
+ AST_APP_OPTION('d', OPT_DATABASE),
+ AST_APP_OPTION('j', OPT_JUMP),
+ AST_APP_OPTION('m', OPT_MULTIPLE),
+ AST_APP_OPTION('r', OPT_REMOVE),
+});
+
+
+static char *app = "Authenticate";
+
+static char *synopsis = "Authenticate a user";
+
+static char *descrip =
+" Authenticate(password[|options[|maxdigits]]): This application asks the caller\n"
+"to enter a given password in order to continue dialplan execution. If the password\n"
+"begins with the '/' character, it is interpreted as a file which contains a list of\n"
+"valid passwords, listed 1 password per line in the file.\n"
+" When using a database key, the value associated with the key can be anything.\n"
+"Users have three attempts to authenticate before the channel is hung up. If the\n"
+"passsword is invalid, the 'j' option is specified, and priority n+101 exists,\n"
+"dialplan execution will continnue at this location.\n"
+" Options:\n"
+" a - Set the channels' account code to the password that is entered\n"
+" d - Interpret the given path as database key, not a literal file\n"
+" j - Support jumping to n+101 if authentication fails\n"
+" m - Interpret the given path as a file which contains a list of account\n"
+" codes and password hashes delimited with ':', listed one per line in\n"
+" the file. When one of the passwords is matched, the channel will have\n"
+" its account code set to the corresponding account code in the file.\n"
+" r - Remove the database key upon successful entry (valid with 'd' only)\n"
+" maxdigits - maximum acceptable number of digits. Stops reading after\n"
+" maxdigits have been entered (without requiring the user to\n"
+" press the '#' key).\n"
+" Defaults to 0 - no limit - wait for the user press the '#' key.\n"
+;
+
+static int auth_exec(struct ast_channel *chan, void *data)
+{
+ int res=0;
+ int retries;
+ struct ast_module_user *u;
+ char passwd[256];
+ char *prompt;
+ int maxdigits;
+ char *argcopy =NULL;
+ struct ast_flags flags = {0};
+
+ AST_DECLARE_APP_ARGS(arglist,
+ AST_APP_ARG(password);
+ AST_APP_ARG(options);
+ AST_APP_ARG(maxdigits);
+ );
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "Authenticate requires an argument(password)\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ if (chan->_state != AST_STATE_UP) {
+ res = ast_answer(chan);
+ if (res) {
+ ast_module_user_remove(u);
+ return -1;
+ }
+ }
+
+ argcopy = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(arglist,argcopy);
+
+ if (!ast_strlen_zero(arglist.options)) {
+ ast_app_parse_options(auth_app_options, &flags, NULL, arglist.options);
+ }
+
+ if (!ast_strlen_zero(arglist.maxdigits)) {
+ maxdigits = atoi(arglist.maxdigits);
+ if ((maxdigits<1) || (maxdigits>sizeof(passwd)-2))
+ maxdigits = sizeof(passwd) - 2;
+ } else {
+ maxdigits = sizeof(passwd) - 2;
+ }
+
+ /* Start asking for password */
+ prompt = "agent-pass";
+ for (retries = 0; retries < 3; retries++) {
+ res = ast_app_getdata(chan, prompt, passwd, maxdigits, 0);
+ if (res < 0)
+ break;
+ res = 0;
+ if (arglist.password[0] == '/') {
+ if (ast_test_flag(&flags,OPT_DATABASE)) {
+ char tmp[256];
+ /* Compare against a database key */
+ if (!ast_db_get(arglist.password + 1, passwd, tmp, sizeof(tmp))) {
+ /* It's a good password */
+ if (ast_test_flag(&flags,OPT_REMOVE)) {
+ ast_db_del(arglist.password + 1, passwd);
+ }
+ break;
+ }
+ } else {
+ /* Compare against a file */
+ FILE *f;
+ f = fopen(arglist.password, "r");
+ if (f) {
+ char buf[256] = "";
+ char md5passwd[33] = "";
+ char *md5secret = NULL;
+
+ while (!feof(f)) {
+ if (!fgets(buf, sizeof(buf), f)) {
+ continue;
+ }
+ if (!ast_strlen_zero(buf)) {
+ size_t len = strlen(buf);
+ if (buf[len - 1] == '\n')
+ buf[len - 1] = '\0';
+ if (ast_test_flag(&flags,OPT_MULTIPLE)) {
+ md5secret = strchr(buf, ':');
+ if (md5secret == NULL)
+ continue;
+ *md5secret = '\0';
+ md5secret++;
+ ast_md5_hash(md5passwd, passwd);
+ if (!strcmp(md5passwd, md5secret)) {
+ if (ast_test_flag(&flags,OPT_ACCOUNT))
+ ast_cdr_setaccount(chan, buf);
+ break;
+ }
+ } else {
+ if (!strcmp(passwd, buf)) {
+ if (ast_test_flag(&flags,OPT_ACCOUNT))
+ ast_cdr_setaccount(chan, buf);
+ break;
+ }
+ }
+ }
+ }
+ fclose(f);
+ if (!ast_strlen_zero(buf)) {
+ if (ast_test_flag(&flags,OPT_MULTIPLE)) {
+ if (md5secret && !strcmp(md5passwd, md5secret))
+ break;
+ } else {
+ if (!strcmp(passwd, buf))
+ break;
+ }
+ }
+ } else
+ ast_log(LOG_WARNING, "Unable to open file '%s' for authentication: %s\n", arglist.password, strerror(errno));
+ }
+ } else {
+ /* Compare against a fixed password */
+ if (!strcmp(passwd, arglist.password))
+ break;
+ }
+ prompt="auth-incorrect";
+ }
+ if ((retries < 3) && !res) {
+ if (ast_test_flag(&flags,OPT_ACCOUNT) && !ast_test_flag(&flags,OPT_MULTIPLE))
+ ast_cdr_setaccount(chan, passwd);
+ res = ast_streamfile(chan, "auth-thankyou", chan->language);
+ if (!res)
+ res = ast_waitstream(chan, "");
+ } else {
+ if (ast_test_flag(&flags,OPT_JUMP) && ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101) == 0) {
+ res = 0;
+ } else {
+ if (!ast_streamfile(chan, "vm-goodbye", chan->language))
+ res = ast_waitstream(chan, "");
+ res = -1;
+ }
+ }
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ ast_module_user_hangup_all();
+
+ res = ast_unregister_application(app);
+
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, auth_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Authentication Application");
diff --git a/apps/app_cdr.c b/apps/app_cdr.c
new file mode 100644
index 000000000..a70d9d2f5
--- /dev/null
+++ b/apps/app_cdr.c
@@ -0,0 +1,78 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * Martin Pycko <martinp@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 Applications connected with CDR engine
+ *
+ * Martin Pycko <martinp@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sys/types.h>
+#include <stdlib.h>
+
+#include "asterisk/channel.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+
+static char *nocdr_descrip =
+" NoCDR(): This application will tell Asterisk not to maintain a CDR for the\n"
+"current call.\n";
+
+static char *nocdr_app = "NoCDR";
+static char *nocdr_synopsis = "Tell Asterisk to not maintain a CDR for the current call";
+
+
+static int nocdr_exec(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *u;
+
+ u = ast_module_user_add(chan);
+
+ if (chan->cdr) {
+ ast_set_flag(chan->cdr, AST_CDR_FLAG_POST_DISABLED);
+ }
+
+ ast_module_user_remove(u);
+
+ return 0;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(nocdr_app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(nocdr_app, nocdr_exec, nocdr_synopsis, nocdr_descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Tell Asterisk to not maintain a CDR for the current call");
diff --git a/apps/app_chanisavail.c b/apps/app_chanisavail.c
new file mode 100644
index 000000000..c6931d8db
--- /dev/null
+++ b/apps/app_chanisavail.c
@@ -0,0 +1,173 @@
+/*
+* Asterisk -- An open source telephony toolkit.
+*
+* Copyright (C) 1999 - 2005, Digium, Inc.
+*
+* Mark Spencer <markster@digium.com>
+* James Golovich <james@gnuinter.net>
+*
+* 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 Check if Channel is Available
+ *
+ * \author Mark Spencer <markster@digium.com>
+ * \author James Golovich <james@gnuinter.net>
+
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/app.h"
+#include "asterisk/devicestate.h"
+#include "asterisk/options.h"
+
+static char *app = "ChanIsAvail";
+
+static char *synopsis = "Check channel availability";
+
+static char *descrip =
+" ChanIsAvail(Technology/resource[&Technology2/resource2...][|options]): \n"
+"This application will check to see if any of the specified channels are\n"
+"available. The following variables will be set by this application:\n"
+" ${AVAILCHAN} - the name of the available channel, if one exists\n"
+" ${AVAILORIGCHAN} - the canonical channel name that was used to create the channel\n"
+" ${AVAILSTATUS} - the status code for the available channel\n"
+" Options:\n"
+" s - Consider the channel unavailable if the channel is in use at all\n"
+" j - Support jumping to priority n+101 if no channel is available\n";
+
+
+static int chanavail_exec(struct ast_channel *chan, void *data)
+{
+ int res=-1, inuse=-1, option_state=0, priority_jump=0;
+ int status;
+ struct ast_module_user *u;
+ char *info, tmp[512], trychan[512], *peers, *tech, *number, *rest, *cur;
+ struct ast_channel *tempchan;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(reqchans);
+ AST_APP_ARG(options);
+ );
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "ChanIsAvail requires an argument (Zap/1&Zap/2)\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ info = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(args, info);
+
+ if (args.options) {
+ if (strchr(args.options, 's'))
+ option_state = 1;
+ if (strchr(args.options, 'j'))
+ priority_jump = 1;
+ }
+ peers = args.reqchans;
+ if (peers) {
+ cur = peers;
+ do {
+ /* remember where to start next time */
+ rest = strchr(cur, '&');
+ if (rest) {
+ *rest = 0;
+ rest++;
+ }
+ tech = cur;
+ number = strchr(tech, '/');
+ if (!number) {
+ ast_log(LOG_WARNING, "ChanIsAvail argument takes format ([technology]/[device])\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+ *number = '\0';
+ number++;
+
+ if (option_state) {
+ /* If the pbx says in use then don't bother trying further.
+ This is to permit testing if someone's on a call, even if the
+ channel can permit more calls (ie callwaiting, sip calls, etc). */
+
+ snprintf(trychan, sizeof(trychan), "%s/%s",cur,number);
+ status = inuse = ast_device_state(trychan);
+ }
+ if ((inuse <= 1) && (tempchan = ast_request(tech, chan->nativeformats, number, &status))) {
+ pbx_builtin_setvar_helper(chan, "AVAILCHAN", tempchan->name);
+ /* Store the originally used channel too */
+ snprintf(tmp, sizeof(tmp), "%s/%s", tech, number);
+ pbx_builtin_setvar_helper(chan, "AVAILORIGCHAN", tmp);
+ snprintf(tmp, sizeof(tmp), "%d", status);
+ pbx_builtin_setvar_helper(chan, "AVAILSTATUS", tmp);
+ ast_hangup(tempchan);
+ tempchan = NULL;
+ res = 1;
+ break;
+ } else {
+ snprintf(tmp, sizeof(tmp), "%d", status);
+ pbx_builtin_setvar_helper(chan, "AVAILSTATUS", tmp);
+ }
+ cur = rest;
+ } while (cur);
+ }
+ if (res < 1) {
+ pbx_builtin_setvar_helper(chan, "AVAILCHAN", "");
+ pbx_builtin_setvar_helper(chan, "AVAILORIGCHAN", "");
+ if (priority_jump || ast_opt_priority_jumping) {
+ if (ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101)) {
+ ast_module_user_remove(u);
+ return -1;
+ }
+ }
+ }
+
+ ast_module_user_remove(u);
+ return 0;
+}
+
+static int unload_module(void)
+{
+ int res = 0;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, chanavail_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Check channel availability");
diff --git a/apps/app_channelredirect.c b/apps/app_channelredirect.c
new file mode 100644
index 000000000..a8eedf9b4
--- /dev/null
+++ b/apps/app_channelredirect.c
@@ -0,0 +1,140 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2006, Sergey Basmanov
+ *
+ * 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 ChannelRedirect application
+ *
+ * \author Sergey Basmanov <sergey_basmanov@mail.ru>
+ *
+ * \ingroup applications
+ */
+
+#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/module.h"
+#include "asterisk/lock.h"
+#include "asterisk/app.h"
+#include "asterisk/features.h"
+#include "asterisk/options.h"
+
+static char *app = "ChannelRedirect";
+static char *synopsis = "Redirects given channel to a dialplan target.";
+static char *descrip =
+"ChannelRedirect(channel|[[context|]extension|]priority):\n"
+" Sends the specified channel to the specified extension priority\n";
+
+
+static int asyncgoto_exec(struct ast_channel *chan, void *data)
+{
+ int res = -1;
+ struct ast_module_user *u;
+ char *info, *context, *exten, *priority;
+ int prio = 1;
+ struct ast_channel *chan2 = NULL;
+
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(channel);
+ AST_APP_ARG(label);
+ );
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "%s requires an argument (channel|[[context|]exten|]priority)\n", app);
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ info = ast_strdupa(data);
+ AST_STANDARD_APP_ARGS(args, info);
+
+ if (ast_strlen_zero(args.channel) || ast_strlen_zero(args.label)) {
+ ast_log(LOG_WARNING, "%s requires an argument (channel|[[context|]exten|]priority)\n", app);
+ goto quit;
+ }
+
+ chan2 = ast_get_channel_by_name_locked(args.channel);
+ if (!chan2) {
+ ast_log(LOG_WARNING, "No such channel: %s\n", args.channel);
+ goto quit;
+ }
+
+ /* Parsed right to left, so standard parsing won't work */
+ context = strsep(&args.label, "|");
+ exten = strsep(&args.label, "|");
+ if (exten) {
+ priority = strsep(&args.label, "|");
+ if (!priority) {
+ priority = exten;
+ exten = context;
+ context = NULL;
+ }
+ } else {
+ priority = context;
+ context = NULL;
+ }
+
+ /* ast_findlabel_extension does not convert numeric priorities; it only does a lookup */
+ if (!(prio = atoi(priority)) && !(prio = ast_findlabel_extension(chan2, S_OR(context, chan2->context),
+ S_OR(exten, chan2->exten), priority, chan2->cid.cid_num))) {
+ ast_log(LOG_WARNING, "'%s' is not a known priority or label\n", priority);
+ goto chanquit;
+ }
+
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "Attempting async goto (%s) to %s|%s|%d\n", args.channel, S_OR(context, chan2->context), S_OR(exten, chan2->exten), prio);
+
+ if (ast_async_goto_if_exists(chan2, S_OR(context, chan2->context), S_OR(exten, chan2->exten), prio))
+ ast_log(LOG_WARNING, "%s failed for %s\n", app, args.channel);
+ else
+ res = 0;
+
+ chanquit:
+ ast_mutex_unlock(&chan2->lock);
+ quit:
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, asyncgoto_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Channel Redirect");
diff --git a/apps/app_chanspy.c b/apps/app_chanspy.c
new file mode 100644
index 000000000..18e4972a5
--- /dev/null
+++ b/apps/app_chanspy.c
@@ -0,0 +1,878 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2005 Anthony Minessale II (anthmct@yahoo.com)
+ * Copyright (C) 2005 - 2008, Digium, Inc.
+ *
+ * A license has been granted to Digium (via disclaimer) for the use of
+ * this code.
+ *
+ * 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 ChanSpy: Listen in on any channel.
+ *
+ * \author Anthony Minessale II <anthmct@yahoo.com>
+ * \author Joshua Colp <jcolp@digium.com>
+ * \author Russell Bryant <russell@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/audiohook.h"
+#include "asterisk/features.h"
+#include "asterisk/options.h"
+#include "asterisk/app.h"
+#include "asterisk/utils.h"
+#include "asterisk/say.h"
+#include "asterisk/pbx.h"
+#include "asterisk/translate.h"
+#include "asterisk/module.h"
+#include "asterisk/lock.h"
+
+#define AST_NAME_STRLEN 256
+
+/* "Zap/pseudo" is ten characters.
+ * "DAHDI/pseudo" is twelve characters.
+ */
+
+static const char *tdesc = "Listen to a channel, and optionally whisper into it";
+static const char *app_chan = "ChanSpy";
+static const char *desc_chan =
+" ChanSpy([chanprefix][|options]): This application is used to listen to the\n"
+"audio from an Asterisk channel. This includes the audio coming in and\n"
+"out of the channel being spied on. If the 'chanprefix' parameter is specified,\n"
+"only channels beginning with this string will be spied upon.\n"
+" While spying, the following actions may be performed:\n"
+" - Dialing # cycles the volume level.\n"
+" - Dialing * will stop spying and look for another channel to spy on.\n"
+" - Dialing a series of digits followed by # builds a channel name to append\n"
+" to 'chanprefix'. For example, executing ChanSpy(Agent) and then dialing\n"
+" the digits '1234#' while spying will begin spying on the channel\n"
+" 'Agent/1234'.\n"
+" Options:\n"
+" b - Only spy on channels involved in a bridged call.\n"
+" g(grp) - Match only channels where their ${SPYGROUP} variable is set to\n"
+" contain 'grp' in an optional : delimited list.\n"
+" q - Don't play a beep when beginning to spy on a channel, or speak the\n"
+" selected channel name.\n"
+" r[(basename)] - Record the session to the monitor spool directory. An\n"
+" optional base for the filename may be specified. The\n"
+" default is 'chanspy'.\n"
+" v([value]) - Adjust the initial volume in the range from -4 to 4. A\n"
+" negative value refers to a quieter setting.\n"
+" w - Enable 'whisper' mode, so the spying channel can talk to\n"
+" the spied-on channel.\n"
+" W - Enable 'private whisper' mode, so the spying channel can\n"
+" talk to the spied-on channel but cannot listen to that\n"
+" channel.\n"
+;
+
+static const char *app_ext = "ExtenSpy";
+static const char *desc_ext =
+" ExtenSpy(exten[@context][|options]): This application is used to listen to the\n"
+"audio from an Asterisk channel. This includes the audio coming in and\n"
+"out of the channel being spied on. Only channels created by outgoing calls for the\n"
+"specified extension will be selected for spying. If the optional context is not\n"
+"supplied, the current channel's context will be used.\n"
+" While spying, the following actions may be performed:\n"
+" - Dialing # cycles the volume level.\n"
+" - Dialing * will stop spying and look for another channel to spy on.\n"
+" Options:\n"
+" b - Only spy on channels involved in a bridged call.\n"
+" g(grp) - Match only channels where their ${SPYGROUP} variable is set to\n"
+" contain 'grp' in an optional : delimited list.\n"
+" q - Don't play a beep when beginning to spy on a channel, or speak the\n"
+" selected channel name.\n"
+" r[(basename)] - Record the session to the monitor spool directory. An\n"
+" optional base for the filename may be specified. The\n"
+" default is 'chanspy'.\n"
+" v([value]) - Adjust the initial volume in the range from -4 to 4. A\n"
+" negative value refers to a quieter setting.\n"
+" w - Enable 'whisper' mode, so the spying channel can talk to\n"
+" the spied-on channel.\n"
+" W - Enable 'private whisper' mode, so the spying channel can\n"
+" talk to the spied-on channel but cannot listen to that\n"
+" channel.\n"
+;
+
+enum {
+ OPTION_QUIET = (1 << 0), /* Quiet, no announcement */
+ OPTION_BRIDGED = (1 << 1), /* Only look at bridged calls */
+ OPTION_VOLUME = (1 << 2), /* Specify initial volume */
+ OPTION_GROUP = (1 << 3), /* Only look at channels in group */
+ OPTION_RECORD = (1 << 4),
+ OPTION_WHISPER = (1 << 5),
+ OPTION_PRIVATE = (1 << 6), /* Private Whisper mode */
+} chanspy_opt_flags;
+
+enum {
+ OPT_ARG_VOLUME = 0,
+ OPT_ARG_GROUP,
+ OPT_ARG_RECORD,
+ OPT_ARG_ARRAY_SIZE,
+} chanspy_opt_args;
+
+AST_APP_OPTIONS(spy_opts, {
+ AST_APP_OPTION('q', OPTION_QUIET),
+ AST_APP_OPTION('b', OPTION_BRIDGED),
+ AST_APP_OPTION('w', OPTION_WHISPER),
+ AST_APP_OPTION('W', OPTION_PRIVATE),
+ AST_APP_OPTION_ARG('v', OPTION_VOLUME, OPT_ARG_VOLUME),
+ AST_APP_OPTION_ARG('g', OPTION_GROUP, OPT_ARG_GROUP),
+ AST_APP_OPTION_ARG('r', OPTION_RECORD, OPT_ARG_RECORD),
+});
+
+static int next_unique_id_to_use = 0;
+
+struct chanspy_translation_helper {
+ /* spy data */
+ struct ast_audiohook spy_audiohook;
+ struct ast_audiohook whisper_audiohook;
+ int fd;
+ int volfactor;
+};
+
+static void *spy_alloc(struct ast_channel *chan, void *data)
+{
+ /* just store the data pointer in the channel structure */
+ return data;
+}
+
+static void spy_release(struct ast_channel *chan, void *data)
+{
+ /* nothing to do */
+}
+
+static int spy_generate(struct ast_channel *chan, void *data, int len, int samples)
+{
+ struct chanspy_translation_helper *csth = data;
+ struct ast_frame *f;
+
+ ast_audiohook_lock(&csth->spy_audiohook);
+ if (csth->spy_audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING) {
+ ast_audiohook_unlock(&csth->spy_audiohook);
+ return -1;
+ }
+
+ f = ast_audiohook_read_frame(&csth->spy_audiohook, samples, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR);
+
+ ast_audiohook_unlock(&csth->spy_audiohook);
+
+ if (!f)
+ return 0;
+
+ if (ast_write(chan, f)) {
+ ast_frfree(f);
+ return -1;
+ }
+
+ if (csth->fd) {
+ if (write(csth->fd, f->data, f->datalen) < 0) {
+ ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
+ }
+ }
+
+ ast_frfree(f);
+
+ return 0;
+}
+
+static struct ast_generator spygen = {
+ .alloc = spy_alloc,
+ .release = spy_release,
+ .generate = spy_generate,
+};
+
+static int start_spying(struct ast_channel *chan, const char *spychan_name, struct ast_audiohook *audiohook)
+{
+ int res;
+ struct ast_channel *peer;
+
+ ast_log(LOG_NOTICE, "Attaching %s to %s\n", spychan_name, chan->name);
+
+ res = ast_audiohook_attach(chan, audiohook);
+
+ if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan))) {
+ ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);
+ }
+ return res;
+}
+
+struct chanspy_ds {
+ struct ast_channel *chan;
+ char unique_id[20];
+ ast_mutex_t lock;
+};
+
+static int channel_spy(struct ast_channel *chan, struct chanspy_ds *spyee_chanspy_ds,
+ int *volfactor, int fd, const struct ast_flags *flags)
+{
+ struct chanspy_translation_helper csth;
+ int running = 0, res, x = 0;
+ char inp[24] = {0};
+ char *name;
+ struct ast_frame *f;
+ struct ast_silence_generator *silgen = NULL;
+ struct ast_channel *spyee = NULL;
+ const char *spyer_name;
+
+ ast_channel_lock(chan);
+ spyer_name = ast_strdupa(chan->name);
+ ast_channel_unlock(chan);
+
+ ast_mutex_lock(&spyee_chanspy_ds->lock);
+ if (spyee_chanspy_ds->chan) {
+ spyee = spyee_chanspy_ds->chan;
+ ast_channel_lock(spyee);
+ }
+ ast_mutex_unlock(&spyee_chanspy_ds->lock);
+
+ if (!spyee)
+ return 0;
+
+ /* We now hold the channel lock on spyee */
+
+ if (ast_check_hangup(chan) || ast_check_hangup(spyee)) {
+ ast_channel_unlock(spyee);
+ return 0;
+ }
+
+ name = ast_strdupa(spyee->name);
+ if (option_verbose >= 2)
+ ast_verbose(VERBOSE_PREFIX_2 "Spying on channel %s\n", name);
+
+ memset(&csth, 0, sizeof(csth));
+
+ ast_audiohook_init(&csth.spy_audiohook, AST_AUDIOHOOK_TYPE_SPY, "ChanSpy");
+
+ if (start_spying(spyee, spyer_name, &csth.spy_audiohook)) {
+ ast_audiohook_destroy(&csth.spy_audiohook);
+ ast_channel_unlock(spyee);
+ return 0;
+ }
+
+ if (ast_test_flag(flags, OPTION_WHISPER)) {
+ ast_audiohook_init(&csth.whisper_audiohook, AST_AUDIOHOOK_TYPE_WHISPER, "ChanSpy");
+ start_spying(spyee, spyer_name, &csth.whisper_audiohook);
+ }
+
+ ast_channel_unlock(spyee);
+ spyee = NULL;
+
+ csth.volfactor = *volfactor;
+
+ if (csth.volfactor) {
+ csth.spy_audiohook.options.read_volume = csth.volfactor;
+ csth.spy_audiohook.options.write_volume = csth.volfactor;
+ }
+
+ csth.fd = fd;
+
+ if (ast_test_flag(flags, OPTION_PRIVATE))
+ silgen = ast_channel_start_silence_generator(chan);
+ else
+ ast_activate_generator(chan, &spygen, &csth);
+
+ /* We can no longer rely on 'spyee' being an actual channel;
+ it can be hung up and freed out from under us. However, the
+ channel destructor will put NULL into our csth.spy.chan
+ field when that happens, so that is our signal that the spyee
+ channel has gone away.
+ */
+
+ /* Note: it is very important that the ast_waitfor() be the first
+ condition in this expression, so that if we wait for some period
+ of time before receiving a frame from our spying channel, we check
+ for hangup on the spied-on channel _after_ knowing that a frame
+ has arrived, since the spied-on channel could have gone away while
+ we were waiting
+ */
+ while ((res = ast_waitfor(chan, -1) > -1) && csth.spy_audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING) {
+ if (!(f = ast_read(chan)) || ast_check_hangup(chan)) {
+ running = -1;
+ break;
+ }
+
+ if (ast_test_flag(flags, OPTION_WHISPER) && (f->frametype == AST_FRAME_VOICE)) {
+ ast_audiohook_lock(&csth.whisper_audiohook);
+ ast_audiohook_write_frame(&csth.whisper_audiohook, AST_AUDIOHOOK_DIRECTION_WRITE, f);
+ ast_audiohook_unlock(&csth.whisper_audiohook);
+ ast_frfree(f);
+ continue;
+ }
+
+ res = (f->frametype == AST_FRAME_DTMF) ? f->subclass : 0;
+ ast_frfree(f);
+ if (!res)
+ continue;
+
+ if (x == sizeof(inp))
+ x = 0;
+
+ if (res < 0) {
+ running = -1;
+ break;
+ }
+
+ if (res == '*') {
+ running = 0;
+ break;
+ } else if (res == '#') {
+ if (!ast_strlen_zero(inp)) {
+ running = atoi(inp);
+ break;
+ }
+
+ (*volfactor)++;
+ if (*volfactor > 4)
+ *volfactor = -4;
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Setting spy volume on %s to %d\n", chan->name, *volfactor);
+ csth.volfactor = *volfactor;
+ csth.spy_audiohook.options.read_volume = csth.volfactor;
+ csth.spy_audiohook.options.write_volume = csth.volfactor;
+ } else if (res >= '0' && res <= '9') {
+ inp[x++] = res;
+ }
+ }
+
+ if (ast_test_flag(flags, OPTION_PRIVATE))
+ ast_channel_stop_silence_generator(chan, silgen);
+ else
+ ast_deactivate_generator(chan);
+
+ if (ast_test_flag(flags, OPTION_WHISPER)) {
+ ast_audiohook_lock(&csth.whisper_audiohook);
+ ast_audiohook_detach(&csth.whisper_audiohook);
+ ast_audiohook_unlock(&csth.whisper_audiohook);
+ ast_audiohook_destroy(&csth.whisper_audiohook);
+ }
+
+ ast_audiohook_lock(&csth.spy_audiohook);
+ ast_audiohook_detach(&csth.spy_audiohook);
+ ast_audiohook_unlock(&csth.spy_audiohook);
+ ast_audiohook_destroy(&csth.spy_audiohook);
+
+ if (option_verbose >= 2)
+ ast_verbose(VERBOSE_PREFIX_2 "Done Spying on channel %s\n", name);
+
+ return running;
+}
+
+/*!
+ * \note This relies on the embedded lock to be recursive, as it may be called
+ * due to a call to chanspy_ds_free with the lock held there.
+ */
+static void chanspy_ds_destroy(void *data)
+{
+ struct chanspy_ds *chanspy_ds = data;
+
+ /* Setting chan to be NULL is an atomic operation, but we don't want this
+ * value to change while this lock is held. The lock is held elsewhere
+ * while it performs non-atomic operations with this channel pointer */
+
+ ast_mutex_lock(&chanspy_ds->lock);
+ chanspy_ds->chan = NULL;
+ ast_mutex_unlock(&chanspy_ds->lock);
+}
+
+static void chanspy_ds_chan_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
+{
+ struct chanspy_ds *chanspy_ds = data;
+
+ ast_mutex_lock(&chanspy_ds->lock);
+ chanspy_ds->chan = new_chan;
+ ast_mutex_unlock(&chanspy_ds->lock);
+}
+
+static const struct ast_datastore_info chanspy_ds_info = {
+ .type = "chanspy",
+ .destroy = chanspy_ds_destroy,
+ .chan_fixup = chanspy_ds_chan_fixup,
+};
+
+static struct chanspy_ds *chanspy_ds_free(struct chanspy_ds *chanspy_ds)
+{
+ if (!chanspy_ds)
+ return NULL;
+
+ ast_mutex_lock(&chanspy_ds->lock);
+ if (chanspy_ds->chan) {
+ struct ast_datastore *datastore;
+ struct ast_channel *chan;
+
+ chan = chanspy_ds->chan;
+
+ ast_channel_lock(chan);
+ if ((datastore = ast_channel_datastore_find(chan, &chanspy_ds_info, chanspy_ds->unique_id))) {
+ ast_channel_datastore_remove(chan, datastore);
+ /* chanspy_ds->chan is NULL after this call */
+ chanspy_ds_destroy(datastore->data);
+ datastore->data = NULL;
+ ast_channel_datastore_free(datastore);
+ }
+ ast_channel_unlock(chan);
+ }
+ ast_mutex_unlock(&chanspy_ds->lock);
+
+ return NULL;
+}
+
+/*! \note Returns the channel in the chanspy_ds locked as well as the chanspy_ds locked */
+static struct chanspy_ds *setup_chanspy_ds(struct ast_channel *chan, struct chanspy_ds *chanspy_ds)
+{
+ struct ast_datastore *datastore = NULL;
+
+ ast_mutex_lock(&chanspy_ds->lock);
+
+ if (!(datastore = ast_channel_datastore_alloc(&chanspy_ds_info, chanspy_ds->unique_id))) {
+ ast_mutex_unlock(&chanspy_ds->lock);
+ chanspy_ds = chanspy_ds_free(chanspy_ds);
+ ast_channel_unlock(chan);
+ return NULL;
+ }
+
+ chanspy_ds->chan = chan;
+ datastore->data = chanspy_ds;
+ ast_channel_datastore_add(chan, datastore);
+
+ return chanspy_ds;
+}
+
+static struct chanspy_ds *next_channel(struct ast_channel *chan,
+ const struct ast_channel *last, const char *spec,
+ const char *exten, const char *context, struct chanspy_ds *chanspy_ds)
+{
+ struct ast_channel *this;
+ char channel_name[AST_CHANNEL_NAME];
+ static size_t PSEUDO_CHAN_LEN = 0;
+
+ if (!PSEUDO_CHAN_LEN) {
+ PSEUDO_CHAN_LEN = *dahdi_chan_name_len + strlen("/pseudo");
+ }
+
+redo:
+ if (spec)
+ this = ast_walk_channel_by_name_prefix_locked(last, spec, strlen(spec));
+ else if (exten)
+ this = ast_walk_channel_by_exten_locked(last, exten, context);
+ else
+ this = ast_channel_walk_locked(last);
+
+ if (!this)
+ return NULL;
+
+ snprintf(channel_name, AST_CHANNEL_NAME, "%s/pseudo", dahdi_chan_name);
+ if (!strncmp(this->name, channel_name, PSEUDO_CHAN_LEN)) {
+ last = this;
+ ast_channel_unlock(this);
+ goto redo;
+ } else if (this == chan) {
+ last = this;
+ ast_channel_unlock(this);
+ goto redo;
+ }
+
+ return setup_chanspy_ds(this, chanspy_ds);
+}
+
+static int common_exec(struct ast_channel *chan, const struct ast_flags *flags,
+ int volfactor, const int fd, const char *mygroup, const char *spec,
+ const char *exten, const char *context)
+{
+ char nameprefix[AST_NAME_STRLEN];
+ char peer_name[AST_NAME_STRLEN + 5];
+ signed char zero_volume = 0;
+ int waitms;
+ int res;
+ char *ptr;
+ int num;
+ int num_spyed_upon = 1;
+ struct chanspy_ds chanspy_ds = { 0, };
+
+ ast_mutex_init(&chanspy_ds.lock);
+
+ snprintf(chanspy_ds.unique_id, sizeof(chanspy_ds.unique_id), "%d", ast_atomic_fetchadd_int(&next_unique_id_to_use, +1));
+
+ if (chan->_state != AST_STATE_UP)
+ ast_answer(chan);
+
+ ast_set_flag(chan, AST_FLAG_SPYING); /* so nobody can spy on us while we are spying */
+
+ waitms = 100;
+
+ for (;;) {
+ struct chanspy_ds *peer_chanspy_ds = NULL, *next_chanspy_ds = NULL;
+ struct ast_channel *prev = NULL, *peer = NULL;
+
+ if (!ast_test_flag(flags, OPTION_QUIET) && num_spyed_upon) {
+ res = ast_streamfile(chan, "beep", chan->language);
+ if (!res)
+ res = ast_waitstream(chan, "");
+ else if (res < 0) {
+ ast_clear_flag(chan, AST_FLAG_SPYING);
+ break;
+ }
+ }
+
+ res = ast_waitfordigit(chan, waitms);
+ if (res < 0) {
+ ast_clear_flag(chan, AST_FLAG_SPYING);
+ break;
+ }
+
+ /* reset for the next loop around, unless overridden later */
+ waitms = 100;
+ num_spyed_upon = 0;
+
+ for (peer_chanspy_ds = next_channel(chan, prev, spec, exten, context, &chanspy_ds);
+ peer_chanspy_ds;
+ chanspy_ds_free(peer_chanspy_ds), prev = peer,
+ peer_chanspy_ds = next_chanspy_ds ? next_chanspy_ds :
+ next_channel(chan, prev, spec, exten, context, &chanspy_ds), next_chanspy_ds = NULL) {
+ const char *group;
+ int igrp = !mygroup;
+ char *groups[25];
+ int num_groups = 0;
+ char dup_group[512];
+ int x;
+ char *s;
+
+ peer = peer_chanspy_ds->chan;
+
+ ast_mutex_unlock(&peer_chanspy_ds->lock);
+
+ if (peer == prev) {
+ ast_channel_unlock(peer);
+ chanspy_ds_free(peer_chanspy_ds);
+ break;
+ }
+
+ if (ast_check_hangup(chan)) {
+ ast_channel_unlock(peer);
+ chanspy_ds_free(peer_chanspy_ds);
+ break;
+ }
+
+ if (ast_test_flag(flags, OPTION_BRIDGED) && !ast_bridged_channel(peer)) {
+ ast_channel_unlock(peer);
+ continue;
+ }
+
+ if (ast_check_hangup(peer) || ast_test_flag(peer, AST_FLAG_SPYING)) {
+ ast_channel_unlock(peer);
+ continue;
+ }
+
+ if (mygroup) {
+ if ((group = pbx_builtin_getvar_helper(peer, "SPYGROUP"))) {
+ ast_copy_string(dup_group, group, sizeof(dup_group));
+ num_groups = ast_app_separate_args(dup_group, ':', groups,
+ sizeof(groups) / sizeof(groups[0]));
+ }
+
+ for (x = 0; x < num_groups; x++) {
+ if (!strcmp(mygroup, groups[x])) {
+ igrp = 1;
+ break;
+ }
+ }
+ }
+
+ if (!igrp) {
+ ast_channel_unlock(peer);
+ continue;
+ }
+
+ strcpy(peer_name, "spy-");
+ strncat(peer_name, peer->name, AST_NAME_STRLEN - 4 - 1);
+ ptr = strchr(peer_name, '/');
+ *ptr++ = '\0';
+
+ for (s = peer_name; s < ptr; s++)
+ *s = tolower(*s);
+
+ /* We have to unlock the peer channel here to avoid a deadlock.
+ * So, when we need to dereference it again, we have to lock the
+ * datastore and get the pointer from there to see if the channel
+ * is still valid. */
+ ast_channel_unlock(peer);
+
+ if (!ast_test_flag(flags, OPTION_QUIET)) {
+ if (ast_fileexists(peer_name, NULL, NULL) != -1) {
+ res = ast_streamfile(chan, peer_name, chan->language);
+ if (!res)
+ res = ast_waitstream(chan, "");
+ if (res) {
+ chanspy_ds_free(peer_chanspy_ds);
+ break;
+ }
+ } else
+ res = ast_say_character_str(chan, peer_name, "", chan->language);
+ if ((num = atoi(ptr)))
+ ast_say_digits(chan, atoi(ptr), "", chan->language);
+ }
+
+ res = channel_spy(chan, peer_chanspy_ds, &volfactor, fd, flags);
+ num_spyed_upon++;
+
+ if (res == -1) {
+ chanspy_ds_free(peer_chanspy_ds);
+ break;
+ } else if (res > 1 && spec) {
+ struct ast_channel *next;
+
+ snprintf(nameprefix, AST_NAME_STRLEN, "%s/%d", spec, res);
+
+ if ((next = ast_get_channel_by_name_prefix_locked(nameprefix, strlen(nameprefix)))) {
+ peer_chanspy_ds = chanspy_ds_free(peer_chanspy_ds);
+ next_chanspy_ds = setup_chanspy_ds(next, &chanspy_ds);
+ } else {
+ /* stay on this channel, if it is still valid */
+
+ ast_mutex_lock(&peer_chanspy_ds->lock);
+ if (peer_chanspy_ds->chan) {
+ ast_channel_lock(peer_chanspy_ds->chan);
+ next_chanspy_ds = peer_chanspy_ds;
+ peer_chanspy_ds = NULL;
+ } else {
+ /* the channel is gone */
+ ast_mutex_unlock(&peer_chanspy_ds->lock);
+ next_chanspy_ds = NULL;
+ }
+ }
+
+ peer = NULL;
+ }
+ }
+ if (res == -1 || ast_check_hangup(chan))
+ break;
+ }
+
+ ast_clear_flag(chan, AST_FLAG_SPYING);
+
+ ast_channel_setoption(chan, AST_OPTION_TXGAIN, &zero_volume, sizeof(zero_volume), 0);
+
+ ast_mutex_lock(&chanspy_ds.lock);
+ ast_mutex_unlock(&chanspy_ds.lock);
+ ast_mutex_destroy(&chanspy_ds.lock);
+
+ return res;
+}
+
+static int chanspy_exec(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *u;
+ char *options = NULL;
+ char *spec = NULL;
+ char *argv[2];
+ char *mygroup = NULL;
+ char *recbase = NULL;
+ int fd = 0;
+ struct ast_flags flags;
+ int oldwf = 0;
+ int argc = 0;
+ int volfactor = 0;
+ int res;
+
+ data = ast_strdupa(data);
+
+ u = ast_module_user_add(chan);
+
+ if ((argc = ast_app_separate_args(data, '|', argv, sizeof(argv) / sizeof(argv[0])))) {
+ spec = argv[0];
+ if (argc > 1)
+ options = argv[1];
+
+ if (ast_strlen_zero(spec) || !strcmp(spec, "all"))
+ spec = NULL;
+ }
+
+ if (options) {
+ char *opts[OPT_ARG_ARRAY_SIZE];
+
+ ast_app_parse_options(spy_opts, &flags, opts, options);
+ if (ast_test_flag(&flags, OPTION_GROUP))
+ mygroup = opts[OPT_ARG_GROUP];
+
+ if (ast_test_flag(&flags, OPTION_RECORD) &&
+ !(recbase = opts[OPT_ARG_RECORD]))
+ recbase = "chanspy";
+
+ if (ast_test_flag(&flags, OPTION_VOLUME) && opts[OPT_ARG_VOLUME]) {
+ int vol;
+
+ if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &vol) != 1) || (vol > 4) || (vol < -4))
+ ast_log(LOG_NOTICE, "Volume factor must be a number between -4 and 4\n");
+ else
+ volfactor = vol;
+ }
+
+ if (ast_test_flag(&flags, OPTION_PRIVATE))
+ ast_set_flag(&flags, OPTION_WHISPER);
+ } else
+ ast_clear_flag(&flags, AST_FLAGS_ALL);
+
+ oldwf = chan->writeformat;
+ if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) {
+ ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ if (recbase) {
+ char filename[PATH_MAX];
+
+ snprintf(filename, sizeof(filename), "%s/%s.%d.raw", ast_config_AST_MONITOR_DIR, recbase, (int) time(NULL));
+ if ((fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644)) <= 0) {
+ ast_log(LOG_WARNING, "Cannot open '%s' for recording\n", filename);
+ fd = 0;
+ }
+ }
+
+ res = common_exec(chan, &flags, volfactor, fd, mygroup, spec, NULL, NULL);
+
+ if (fd)
+ close(fd);
+
+ if (oldwf && ast_set_write_format(chan, oldwf) < 0)
+ ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int extenspy_exec(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *u;
+ char *options = NULL;
+ char *exten = NULL;
+ char *context = NULL;
+ char *argv[2];
+ char *mygroup = NULL;
+ char *recbase = NULL;
+ int fd = 0;
+ struct ast_flags flags;
+ int oldwf = 0;
+ int argc = 0;
+ int volfactor = 0;
+ int res;
+
+ data = ast_strdupa(data);
+
+ u = ast_module_user_add(chan);
+
+ if ((argc = ast_app_separate_args(data, '|', argv, sizeof(argv) / sizeof(argv[0])))) {
+ context = argv[0];
+ if (!ast_strlen_zero(argv[0]))
+ exten = strsep(&context, "@");
+ if (ast_strlen_zero(context))
+ context = ast_strdupa(chan->context);
+ if (argc > 1)
+ options = argv[1];
+ }
+
+ if (options) {
+ char *opts[OPT_ARG_ARRAY_SIZE];
+
+ ast_app_parse_options(spy_opts, &flags, opts, options);
+ if (ast_test_flag(&flags, OPTION_GROUP))
+ mygroup = opts[OPT_ARG_GROUP];
+
+ if (ast_test_flag(&flags, OPTION_RECORD) &&
+ !(recbase = opts[OPT_ARG_RECORD]))
+ recbase = "chanspy";
+
+ if (ast_test_flag(&flags, OPTION_VOLUME) && opts[OPT_ARG_VOLUME]) {
+ int vol;
+
+ if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &vol) != 1) || (vol > 4) || (vol < -4))
+ ast_log(LOG_NOTICE, "Volume factor must be a number between -4 and 4\n");
+ else
+ volfactor = vol;
+ }
+
+ if (ast_test_flag(&flags, OPTION_PRIVATE))
+ ast_set_flag(&flags, OPTION_WHISPER);
+ } else
+ ast_clear_flag(&flags, AST_FLAGS_ALL);
+
+ oldwf = chan->writeformat;
+ if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) {
+ ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ if (recbase) {
+ char filename[PATH_MAX];
+
+ snprintf(filename, sizeof(filename), "%s/%s.%d.raw", ast_config_AST_MONITOR_DIR, recbase, (int) time(NULL));
+ if ((fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644)) <= 0) {
+ ast_log(LOG_WARNING, "Cannot open '%s' for recording\n", filename);
+ fd = 0;
+ }
+ }
+
+ res = common_exec(chan, &flags, volfactor, fd, mygroup, NULL, exten, context);
+
+ if (fd)
+ close(fd);
+
+ if (oldwf && ast_set_write_format(chan, oldwf) < 0)
+ ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res = 0;
+
+ res |= ast_unregister_application(app_chan);
+ res |= ast_unregister_application(app_ext);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ int res = 0;
+
+ res |= ast_register_application(app_chan, chanspy_exec, tdesc, desc_chan);
+ res |= ast_register_application(app_ext, extenspy_exec, tdesc, desc_ext);
+
+ return res;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Listen to the audio of an active channel");
diff --git a/apps/app_controlplayback.c b/apps/app_controlplayback.c
new file mode 100644
index 000000000..6f2c03315
--- /dev/null
+++ b/apps/app_controlplayback.c
@@ -0,0 +1,168 @@
+/*
+ * 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 Trivial application to control playback of a sound file
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/app.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/utils.h"
+#include "asterisk/options.h"
+
+static const char *app = "ControlPlayback";
+
+static const char *synopsis = "Play a file with fast forward and rewind";
+
+static const char *descrip =
+" ControlPlayback(file[|skipms[|ff[|rew[|stop[|pause[|restart|options]]]]]]]):\n"
+"This application will play back the given filename. By default, the '*' key\n"
+"can be used to rewind, and the '#' key can be used to fast-forward.\n"
+"Parameters:\n"
+" skipms - This is number of milliseconds to skip when rewinding or\n"
+" fast-forwarding.\n"
+" ff - Fast-forward when this DTMF digit is received.\n"
+" rew - Rewind when this DTMF digit is received.\n"
+" stop - Stop playback when this DTMF digit is received.\n"
+" pause - Pause playback when this DTMF digit is received.\n"
+" restart - Restart playback when this DTMF digit is received.\n"
+"Options:\n"
+" j - Jump to priority n+101 if the requested file is not found.\n"
+"This application sets the following channel variable upon completion:\n"
+" CPLAYBACKSTATUS - This variable contains the status of the attempt as a text\n"
+" string, one of: SUCCESS | USERSTOPPED | ERROR\n";
+
+
+static int is_on_phonepad(char key)
+{
+ return key == 35 || key == 42 || (key >= 48 && key <= 57);
+}
+
+static int controlplayback_exec(struct ast_channel *chan, void *data)
+{
+ int res = 0, priority_jump = 0;
+ int skipms = 0;
+ struct ast_module_user *u;
+ char *tmp;
+ int argc;
+ char *argv[8];
+ enum arg_ids {
+ arg_file = 0,
+ arg_skip = 1,
+ arg_fwd = 2,
+ arg_rev = 3,
+ arg_stop = 4,
+ arg_pause = 5,
+ arg_restart = 6,
+ options = 7,
+ };
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "ControlPlayback requires an argument (filename)\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ tmp = ast_strdupa(data);
+ memset(argv, 0, sizeof(argv));
+
+ argc = ast_app_separate_args(tmp, '|', argv, sizeof(argv) / sizeof(argv[0]));
+
+ if (argc < 1) {
+ ast_log(LOG_WARNING, "ControlPlayback requires an argument (filename)\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ skipms = argv[arg_skip] ? atoi(argv[arg_skip]) : 3000;
+ if (!skipms)
+ skipms = 3000;
+
+ if (!argv[arg_fwd] || !is_on_phonepad(*argv[arg_fwd]))
+ argv[arg_fwd] = "#";
+ if (!argv[arg_rev] || !is_on_phonepad(*argv[arg_rev]))
+ argv[arg_rev] = "*";
+ if (argv[arg_stop] && !is_on_phonepad(*argv[arg_stop]))
+ argv[arg_stop] = NULL;
+ if (argv[arg_pause] && !is_on_phonepad(*argv[arg_pause]))
+ argv[arg_pause] = NULL;
+ if (argv[arg_restart] && !is_on_phonepad(*argv[arg_restart]))
+ argv[arg_restart] = NULL;
+
+ if (argv[options]) {
+ if (strchr(argv[options], 'j'))
+ priority_jump = 1;
+ }
+
+ res = ast_control_streamfile(chan, argv[arg_file], argv[arg_fwd], argv[arg_rev], argv[arg_stop], argv[arg_pause], argv[arg_restart], skipms);
+
+ /* If we stopped on one of our stop keys, return 0 */
+ if (res > 0 && argv[arg_stop] && strchr(argv[arg_stop], res)) {
+ res = 0;
+ pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "USERSTOPPED");
+ } else {
+ if (res < 0) {
+ if (priority_jump || ast_opt_priority_jumping) {
+ if (ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101)) {
+ ast_log(LOG_WARNING, "ControlPlayback tried to jump to priority n+101 as requested, but priority didn't exist\n");
+ }
+ }
+ res = 0;
+ pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "ERROR");
+ } else
+ pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "SUCCESS");
+ }
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+ res = ast_unregister_application(app);
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, controlplayback_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Control Playback Application");
diff --git a/apps/app_dahdibarge.c b/apps/app_dahdibarge.c
new file mode 100644
index 000000000..cba85a9b6
--- /dev/null
+++ b/apps/app_dahdibarge.c
@@ -0,0 +1,358 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * Special thanks to comphealth.com for sponsoring this
+ * GPL application.
+ *
+ * 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 Zap Barge support
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \note Special thanks to comphealth.com for sponsoring this
+ * GPL application.
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+ <depend>dahdi</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/config.h"
+#include "asterisk/app.h"
+#include "asterisk/options.h"
+#include "asterisk/cli.h"
+#include "asterisk/say.h"
+#include "asterisk/utils.h"
+
+#include "asterisk/dahdi_compat.h"
+
+static char *dahdi_app = "DAHDIBarge";
+static char *zap_app = "ZapBarge";
+
+static char *dahdi_synopsis = "Barge in (monitor) DAHDI channel";
+static char *zap_synopsis = "Barge in (monitor) Zap channel";
+
+static char *dahdi_descrip =
+" DAHDIBarge([channel]): Barges in on a specified DAHDI\n"
+"channel or prompts if one is not specified. Returns\n"
+"-1 when caller user hangs up and is independent of the\n"
+"state of the channel being monitored.";
+
+static char *zap_descrip =
+" ZapBarge([channel]): Barges in on a specified Zaptel\n"
+"channel or prompts if one is not specified. Returns\n"
+"-1 when caller user hangs up and is independent of the\n"
+"state of the channel being monitored.";
+
+#define CONF_SIZE 160
+
+static int careful_write(int fd, unsigned char *data, int len)
+{
+ int res;
+ while(len) {
+ res = write(fd, data, len);
+ if (res < 1) {
+ if (errno != EAGAIN) {
+ ast_log(LOG_WARNING, "Failed to write audio data to conference: %s\n", strerror(errno));
+ return -1;
+ } else
+ return 0;
+ }
+ len -= res;
+ data += res;
+ }
+ return 0;
+}
+
+static int conf_run(struct ast_channel *chan, int confno, int confflags)
+{
+ int fd;
+ struct dahdi_confinfo ztc;
+ struct ast_frame *f;
+ struct ast_channel *c;
+ struct ast_frame fr;
+ int outfd;
+ int ms;
+ int nfds;
+ int res;
+ int flags;
+ int retryzap;
+ int origfd;
+ int ret = -1;
+ struct dahdi_bufferinfo bi;
+ char __buf[CONF_SIZE + AST_FRIENDLY_OFFSET];
+ char *buf = __buf + AST_FRIENDLY_OFFSET;
+
+ /* Set it into U-law mode (write) */
+ if (ast_set_write_format(chan, AST_FORMAT_ULAW) < 0) {
+ ast_log(LOG_WARNING, "Unable to set '%s' to write ulaw mode\n", chan->name);
+ goto outrun;
+ }
+
+ /* Set it into U-law mode (read) */
+ if (ast_set_read_format(chan, AST_FORMAT_ULAW) < 0) {
+ ast_log(LOG_WARNING, "Unable to set '%s' to read ulaw mode\n", chan->name);
+ goto outrun;
+ }
+ ast_indicate(chan, -1);
+ retryzap = strcasecmp(chan->tech->type, dahdi_chan_name);
+zapretry:
+ origfd = chan->fds[0];
+ if (retryzap) {
+ fd = open(DAHDI_FILE_PSEUDO, O_RDWR);
+ if (fd < 0) {
+ ast_log(LOG_WARNING, "Unable to open pseudo channel: %s\n", strerror(errno));
+ goto outrun;
+ }
+ /* Make non-blocking */
+ flags = fcntl(fd, F_GETFL);
+ if (flags < 0) {
+ ast_log(LOG_WARNING, "Unable to get flags: %s\n", strerror(errno));
+ close(fd);
+ goto outrun;
+ }
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) {
+ ast_log(LOG_WARNING, "Unable to set flags: %s\n", strerror(errno));
+ close(fd);
+ goto outrun;
+ }
+ /* Setup buffering information */
+ memset(&bi, 0, sizeof(bi));
+ bi.bufsize = CONF_SIZE;
+ bi.txbufpolicy = DAHDI_POLICY_IMMEDIATE;
+ bi.rxbufpolicy = DAHDI_POLICY_IMMEDIATE;
+ bi.numbufs = 4;
+ if (ioctl(fd, DAHDI_SET_BUFINFO, &bi)) {
+ ast_log(LOG_WARNING, "Unable to set buffering information: %s\n", strerror(errno));
+ close(fd);
+ goto outrun;
+ }
+ nfds = 1;
+ } else {
+ /* XXX Make sure we're not running on a pseudo channel XXX */
+ fd = chan->fds[0];
+ nfds = 0;
+ }
+ memset(&ztc, 0, sizeof(ztc));
+ /* Check to see if we're in a conference... */
+ ztc.chan = 0;
+ if (ioctl(fd, DAHDI_GETCONF, &ztc)) {
+ ast_log(LOG_WARNING, "Error getting conference\n");
+ close(fd);
+ goto outrun;
+ }
+ if (ztc.confmode) {
+ /* Whoa, already in a conference... Retry... */
+ if (!retryzap) {
+ ast_log(LOG_DEBUG, "Channel is in a conference already, retrying with pseudo\n");
+ retryzap = 1;
+ goto zapretry;
+ }
+ }
+ memset(&ztc, 0, sizeof(ztc));
+ /* Add us to the conference */
+ ztc.chan = 0;
+ ztc.confno = confno;
+ ztc.confmode = DAHDI_CONF_MONITORBOTH;
+
+ if (ioctl(fd, DAHDI_SETCONF, &ztc)) {
+ ast_log(LOG_WARNING, "Error setting conference\n");
+ close(fd);
+ goto outrun;
+ }
+ ast_log(LOG_DEBUG, "Placed channel %s in channel %d monitor\n", chan->name, confno);
+
+ for(;;) {
+ outfd = -1;
+ ms = -1;
+ c = ast_waitfor_nandfds(&chan, 1, &fd, nfds, NULL, &outfd, &ms);
+ if (c) {
+ if (c->fds[0] != origfd) {
+ if (retryzap) {
+ /* Kill old pseudo */
+ close(fd);
+ }
+ ast_log(LOG_DEBUG, "Ooh, something swapped out under us, starting over\n");
+ retryzap = 0;
+ goto zapretry;
+ }
+ f = ast_read(c);
+ if (!f)
+ break;
+ if ((f->frametype == AST_FRAME_DTMF) && (f->subclass == '#')) {
+ ret = 0;
+ ast_frfree(f);
+ break;
+ } else if (fd != chan->fds[0]) {
+ if (f->frametype == AST_FRAME_VOICE) {
+ if (f->subclass == AST_FORMAT_ULAW) {
+ /* Carefully write */
+ careful_write(fd, f->data, f->datalen);
+ } else
+ ast_log(LOG_WARNING, "Huh? Got a non-ulaw (%d) frame in the conference\n", f->subclass);
+ }
+ }
+ ast_frfree(f);
+ } else if (outfd > -1) {
+ res = read(outfd, buf, CONF_SIZE);
+ if (res > 0) {
+ memset(&fr, 0, sizeof(fr));
+ fr.frametype = AST_FRAME_VOICE;
+ fr.subclass = AST_FORMAT_ULAW;
+ fr.datalen = res;
+ fr.samples = res;
+ fr.data = buf;
+ fr.offset = AST_FRIENDLY_OFFSET;
+ if (ast_write(chan, &fr) < 0) {
+ ast_log(LOG_WARNING, "Unable to write frame to channel: %s\n", strerror(errno));
+ /* break; */
+ }
+ } else
+ ast_log(LOG_WARNING, "Failed to read frame: %s\n", strerror(errno));
+ }
+ }
+ if (fd != chan->fds[0])
+ close(fd);
+ else {
+ /* Take out of conference */
+ /* Add us to the conference */
+ ztc.chan = 0;
+ ztc.confno = 0;
+ ztc.confmode = 0;
+ if (ioctl(fd, DAHDI_SETCONF, &ztc)) {
+ ast_log(LOG_WARNING, "Error setting conference\n");
+ }
+ }
+
+outrun:
+
+ return ret;
+}
+
+static int exec(struct ast_channel *chan, void *data, int dahdimode)
+{
+ int res=-1;
+ struct ast_module_user *u;
+ int retrycnt = 0;
+ int confflags = 0;
+ int confno = 0;
+ char confstr[80] = "";
+
+ u = ast_module_user_add(chan);
+
+ if (!ast_strlen_zero(data)) {
+ if (dahdimode) {
+ if ((sscanf(data, "DAHDI/%d", &confno) != 1) &&
+ (sscanf(data, "%d", &confno) != 1)) {
+ ast_log(LOG_WARNING, "Argument (if specified) must be a channel number, not '%s'\n", (char *) data);
+ ast_module_user_remove(u);
+ return 0;
+ }
+ } else {
+ if ((sscanf(data, "Zap/%d", &confno) != 1) &&
+ (sscanf(data, "%d", &confno) != 1)) {
+ ast_log(LOG_WARNING, "Argument (if specified) must be a channel number, not '%s'\n", (char *) data);
+ ast_module_user_remove(u);
+ return 0;
+ }
+ }
+ }
+
+ if (chan->_state != AST_STATE_UP)
+ ast_answer(chan);
+
+ while(!confno && (++retrycnt < 4)) {
+ /* Prompt user for conference number */
+ confstr[0] = '\0';
+ res = ast_app_getdata(chan, "conf-getchannel",confstr, sizeof(confstr) - 1, 0);
+ if (res <0) goto out;
+ if (sscanf(confstr, "%d", &confno) != 1)
+ confno = 0;
+ }
+ if (confno) {
+ /* XXX Should prompt user for pin if pin is required XXX */
+ /* Run the conference */
+ res = conf_run(chan, confno, confflags);
+ }
+out:
+ /* Do the conference */
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int exec_zap(struct ast_channel *chan, void *data)
+{
+ ast_log(LOG_WARNING, "Use of the command %s is deprecated, please use %s instead.\n", zap_app, dahdi_app);
+
+ return exec(chan, data, 0);
+}
+
+static int exec_dahdi(struct ast_channel *chan, void *data)
+{
+ return exec(chan, data, 1);
+}
+
+static int unload_module(void)
+{
+ int res = 0;
+
+ if (*dahdi_chan_mode == CHAN_DAHDI_PLUS_ZAP_MODE) {
+ res |= ast_unregister_application(dahdi_app);
+ }
+
+ res |= ast_unregister_application(zap_app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ int res = 0;
+
+ if (*dahdi_chan_mode == CHAN_DAHDI_PLUS_ZAP_MODE) {
+ res |= ast_register_application(dahdi_app, exec_dahdi, dahdi_synopsis, dahdi_descrip);
+ }
+
+ res |= ast_register_application(zap_app, exec_zap, zap_synopsis, zap_descrip);
+
+ return res;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Barge in on channel application");
diff --git a/apps/app_dahdiras.c b/apps/app_dahdiras.c
new file mode 100644
index 000000000..4ac5daa3d
--- /dev/null
+++ b/apps/app_dahdiras.c
@@ -0,0 +1,288 @@
+/*
+ * 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 Execute an ISDN RAS
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+ <depend>dahdi</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#ifdef __linux__
+#include <sys/signal.h>
+#else
+#include <signal.h>
+#endif /* __linux__ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdio.h>
+#include <fcntl.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/options.h"
+
+#include "asterisk/dahdi_compat.h"
+
+static char *dahdi_app = "DAHDIRAS";
+static char *zap_app = "ZapRAS";
+
+static char *dahdi_synopsis = "Executes DAHDI ISDN RAS application";
+static char *zap_synopsis = "Executes Zaptel ISDN RAS application";
+
+static char *dahdi_descrip =
+" DAHDIRAS(args): Executes a RAS server using pppd on the given channel.\n"
+"The channel must be a clear channel (i.e. PRI source) and a DAHDI\n"
+"channel to be able to use this function (no modem emulation is included).\n"
+"Your pppd must have the DAHDI plugin available. Arguments should be\n"
+"separated by | characters.\n";
+
+static char *zap_descrip =
+" ZapRAS(args): Executes a RAS server using pppd on the given channel.\n"
+"The channel must be a clear channel (i.e. PRI source) and a Zaptel\n"
+"channel to be able to use this function (no modem emulation is included).\n"
+"Your pppd must have the Zaptel plugin available. Arguments should be\n"
+"separated by | characters.\n";
+
+#define PPP_MAX_ARGS 32
+#define PPP_EXEC "/usr/sbin/pppd"
+
+static pid_t spawn_ras(struct ast_channel *chan, char *args)
+{
+ pid_t pid;
+ int x;
+ char *c;
+
+ char *argv[PPP_MAX_ARGS];
+ int argc = 0;
+ char *stringp=NULL;
+ sigset_t fullset, oldset;
+
+ sigfillset(&fullset);
+ pthread_sigmask(SIG_BLOCK, &fullset, &oldset);
+
+ /* Start by forking */
+ pid = fork();
+ if (pid) {
+ pthread_sigmask(SIG_SETMASK, &oldset, NULL);
+ return pid;
+ }
+
+ /* Restore original signal handlers */
+ for (x=0;x<NSIG;x++)
+ signal(x, SIG_DFL);
+
+ pthread_sigmask(SIG_UNBLOCK, &fullset, NULL);
+
+ /* Execute RAS on File handles */
+ dup2(chan->fds[0], STDIN_FILENO);
+
+ /* Drop high priority */
+ if (ast_opt_high_priority)
+ ast_set_priority(0);
+
+ /* Close other file descriptors */
+ for (x=STDERR_FILENO + 1;x<1024;x++)
+ close(x);
+
+ /* Reset all arguments */
+ memset(argv, 0, sizeof(argv));
+
+ /* First argument is executable, followed by standard
+ arguments for DAHDI PPP */
+ argv[argc++] = PPP_EXEC;
+ argv[argc++] = "nodetach";
+
+ /* And all the other arguments */
+ stringp=args;
+ c = strsep(&stringp, "|");
+ while(c && strlen(c) && (argc < (PPP_MAX_ARGS - 4))) {
+ argv[argc++] = c;
+ c = strsep(&stringp, "|");
+ }
+
+ argv[argc++] = "plugin";
+#ifdef HAVE_ZAPTEL
+ argv[argc++] = "zaptel.so";
+#else
+ argv[argc++] = "dahdi.so";
+#endif
+ argv[argc++] = "stdin";
+
+ /* Finally launch PPP */
+ execv(PPP_EXEC, argv);
+ fprintf(stderr, "Failed to exec PPPD!\n");
+ exit(1);
+}
+
+static void run_ras(struct ast_channel *chan, char *args)
+{
+ pid_t pid;
+ int status;
+ int res;
+ int signalled = 0;
+ struct dahdi_bufferinfo savebi;
+ int x;
+
+ res = ioctl(chan->fds[0], DAHDI_GET_BUFINFO, &savebi);
+ if(res) {
+ ast_log(LOG_WARNING, "Unable to check buffer policy on channel %s\n", chan->name);
+ return;
+ }
+
+ pid = spawn_ras(chan, args);
+ if (pid < 0) {
+ ast_log(LOG_WARNING, "Failed to spawn RAS\n");
+ } else {
+ for (;;) {
+ res = wait4(pid, &status, WNOHANG, NULL);
+ if (!res) {
+ /* Check for hangup */
+ if (chan->_softhangup && !signalled) {
+ ast_log(LOG_DEBUG, "Channel '%s' hungup. Signalling RAS at %d to die...\n", chan->name, pid);
+ kill(pid, SIGTERM);
+ signalled=1;
+ }
+ /* Try again */
+ sleep(1);
+ continue;
+ }
+ if (res < 0) {
+ ast_log(LOG_WARNING, "wait4 returned %d: %s\n", res, strerror(errno));
+ }
+ if (option_verbose > 2) {
+ if (WIFEXITED(status)) {
+ ast_verbose(VERBOSE_PREFIX_3 "RAS on %s terminated with status %d\n", chan->name, WEXITSTATUS(status));
+ } else if (WIFSIGNALED(status)) {
+ ast_verbose(VERBOSE_PREFIX_3 "RAS on %s terminated with signal %d\n",
+ chan->name, WTERMSIG(status));
+ } else {
+ ast_verbose(VERBOSE_PREFIX_3 "RAS on %s terminated weirdly.\n", chan->name);
+ }
+ }
+ /* Throw back into audio mode */
+ x = 1;
+ ioctl(chan->fds[0], DAHDI_AUDIOMODE, &x);
+
+ /* Restore saved values */
+ res = ioctl(chan->fds[0], DAHDI_SET_BUFINFO, &savebi);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to set buffer policy on channel %s\n", chan->name);
+ }
+ break;
+ }
+ }
+}
+
+static int exec(struct ast_channel *chan, void *data)
+{
+ int res=-1;
+ char *args;
+ struct ast_module_user *u;
+ struct dahdi_params ztp;
+
+ if (!data)
+ data = "";
+
+ u = ast_module_user_add(chan);
+
+ args = ast_strdupa(data);
+
+ /* Answer the channel if it's not up */
+ if (chan->_state != AST_STATE_UP)
+ ast_answer(chan);
+ if (strcasecmp(chan->tech->type, dahdi_chan_name)) {
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "Channel %s is not a %s channel\n", chan->name, dahdi_chan_name);
+ sleep(2);
+ } else {
+ memset(&ztp, 0, sizeof(ztp));
+ if (ioctl(chan->fds[0], DAHDI_GET_PARAMS, &ztp)) {
+ ast_log(LOG_WARNING, "Unable to get parameters\n");
+ } else if (ztp.sigtype != DAHDI_SIG_CLEAR) {
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "Channel %s is not a clear channel\n", chan->name);
+ } else {
+ /* Everything should be okay. Run PPP. */
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Starting RAS on %s\n", chan->name);
+ /* Execute RAS */
+ run_ras(chan, args);
+ }
+ }
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int exec_warn(struct ast_channel *chan, void *data)
+{
+ ast_log(LOG_WARNING, "Use of the command %s is deprecated, please use %s instead.\n", zap_app, dahdi_app);
+
+ return exec(chan, data);
+}
+
+static int unload_module(void)
+{
+ int res = 0;
+
+ if (*dahdi_chan_mode == CHAN_DAHDI_PLUS_ZAP_MODE) {
+ res |= ast_unregister_application(dahdi_app);
+ }
+
+ res |= ast_unregister_application(zap_app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ int res = 0;
+
+ if (*dahdi_chan_mode == CHAN_DAHDI_PLUS_ZAP_MODE) {
+ res |= ast_register_application(dahdi_app, exec, dahdi_synopsis, dahdi_descrip);
+ }
+
+ res |= ast_register_application(zap_app, exec_warn, zap_synopsis, zap_descrip);
+
+ return res;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "DAHDI RAS Application");
+
diff --git a/apps/app_dahdiscan.c b/apps/app_dahdiscan.c
new file mode 100644
index 000000000..6a600756e
--- /dev/null
+++ b/apps/app_dahdiscan.c
@@ -0,0 +1,389 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * Modified from app_zapbarge by David Troy <dave@toad.net>
+ *
+ * Special thanks to comphealth.com for sponsoring this
+ * GPL application.
+ *
+ * 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 Zap Scanner
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+ <depend>dahdi</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/config.h"
+#include "asterisk/app.h"
+#include "asterisk/options.h"
+#include "asterisk/utils.h"
+#include "asterisk/cli.h"
+#include "asterisk/say.h"
+
+#include "asterisk/dahdi_compat.h"
+
+static char *app = "DAHDIScan";
+static char *deprecated_app = "ZapScan";
+
+static char *synopsis = "Scan Zap channels to monitor calls";
+
+static char *descrip =
+" ZapScan([group]) allows a call center manager to monitor Zap channels in\n"
+"a convenient way. Use '#' to select the next channel and use '*' to exit\n"
+"Limit scanning to a channel GROUP by setting the option group argument.\n";
+
+
+#define CONF_SIZE 160
+
+static struct ast_channel *get_zap_channel_locked(int num) {
+ char name[80];
+
+ snprintf(name,sizeof(name),"%s/%d-1", dahdi_chan_name, num);
+ return ast_get_channel_by_name_locked(name);
+}
+
+static int careful_write(int fd, unsigned char *data, int len)
+{
+ int res;
+ while(len) {
+ res = write(fd, data, len);
+ if (res < 1) {
+ if (errno != EAGAIN) {
+ ast_log(LOG_WARNING, "Failed to write audio data to conference: %s\n", strerror(errno));
+ return -1;
+ } else
+ return 0;
+ }
+ len -= res;
+ data += res;
+ }
+ return 0;
+}
+
+static int conf_run(struct ast_channel *chan, int confno, int confflags)
+{
+ int fd;
+ struct dahdi_confinfo ztc;
+ struct ast_frame *f;
+ struct ast_channel *c;
+ struct ast_frame fr;
+ int outfd;
+ int ms;
+ int nfds;
+ int res;
+ int flags;
+ int retryzap;
+ int origfd;
+ int ret = -1;
+ char input[4];
+ int ic=0;
+ struct dahdi_bufferinfo bi;
+ char __buf[CONF_SIZE + AST_FRIENDLY_OFFSET];
+ char *buf = __buf + AST_FRIENDLY_OFFSET;
+
+ /* Set it into U-law mode (write) */
+ if (ast_set_write_format(chan, AST_FORMAT_ULAW) < 0) {
+ ast_log(LOG_WARNING, "Unable to set '%s' to write ulaw mode\n", chan->name);
+ goto outrun;
+ }
+
+ /* Set it into U-law mode (read) */
+ if (ast_set_read_format(chan, AST_FORMAT_ULAW) < 0) {
+ ast_log(LOG_WARNING, "Unable to set '%s' to read ulaw mode\n", chan->name);
+ goto outrun;
+ }
+ ast_indicate(chan, -1);
+ retryzap = strcasecmp(chan->tech->type, "Zap");
+ zapretry:
+ origfd = chan->fds[0];
+ if (retryzap) {
+ fd = open(DAHDI_FILE_PSEUDO, O_RDWR);
+ if (fd < 0) {
+ ast_log(LOG_WARNING, "Unable to open pseudo channel: %s\n", strerror(errno));
+ goto outrun;
+ }
+ /* Make non-blocking */
+ flags = fcntl(fd, F_GETFL);
+ if (flags < 0) {
+ ast_log(LOG_WARNING, "Unable to get flags: %s\n", strerror(errno));
+ close(fd);
+ goto outrun;
+ }
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) {
+ ast_log(LOG_WARNING, "Unable to set flags: %s\n", strerror(errno));
+ close(fd);
+ goto outrun;
+ }
+ /* Setup buffering information */
+ memset(&bi, 0, sizeof(bi));
+ bi.bufsize = CONF_SIZE;
+ bi.txbufpolicy = DAHDI_POLICY_IMMEDIATE;
+ bi.rxbufpolicy = DAHDI_POLICY_IMMEDIATE;
+ bi.numbufs = 4;
+ if (ioctl(fd, DAHDI_SET_BUFINFO, &bi)) {
+ ast_log(LOG_WARNING, "Unable to set buffering information: %s\n", strerror(errno));
+ close(fd);
+ goto outrun;
+ }
+ nfds = 1;
+ } else {
+ /* XXX Make sure we're not running on a pseudo channel XXX */
+ fd = chan->fds[0];
+ nfds = 0;
+ }
+ memset(&ztc, 0, sizeof(ztc));
+ /* Check to see if we're in a conference... */
+ ztc.chan = 0;
+ if (ioctl(fd, DAHDI_GETCONF, &ztc)) {
+ ast_log(LOG_WARNING, "Error getting conference\n");
+ close(fd);
+ goto outrun;
+ }
+ if (ztc.confmode) {
+ /* Whoa, already in a conference... Retry... */
+ if (!retryzap) {
+ ast_log(LOG_DEBUG, "Zap channel is in a conference already, retrying with pseudo\n");
+ retryzap = 1;
+ goto zapretry;
+ }
+ }
+ memset(&ztc, 0, sizeof(ztc));
+ /* Add us to the conference */
+ ztc.chan = 0;
+ ztc.confno = confno;
+ ztc.confmode = DAHDI_CONF_MONITORBOTH;
+
+ if (ioctl(fd, DAHDI_SETCONF, &ztc)) {
+ ast_log(LOG_WARNING, "Error setting conference\n");
+ close(fd);
+ goto outrun;
+ }
+ ast_log(LOG_DEBUG, "Placed channel %s in ZAP channel %d monitor\n", chan->name, confno);
+
+ for(;;) {
+ outfd = -1;
+ ms = -1;
+ c = ast_waitfor_nandfds(&chan, 1, &fd, nfds, NULL, &outfd, &ms);
+ if (c) {
+ if (c->fds[0] != origfd) {
+ if (retryzap) {
+ /* Kill old pseudo */
+ close(fd);
+ }
+ ast_log(LOG_DEBUG, "Ooh, something swapped out under us, starting over\n");
+ retryzap = 0;
+ goto zapretry;
+ }
+ f = ast_read(c);
+ if (!f)
+ break;
+ if(f->frametype == AST_FRAME_DTMF) {
+ if(f->subclass == '#') {
+ ret = 0;
+ break;
+ }
+ else if (f->subclass == '*') {
+ ret = -1;
+ break;
+
+ }
+ else {
+ input[ic++] = f->subclass;
+ }
+ if(ic == 3) {
+ input[ic++] = '\0';
+ ic=0;
+ ret = atoi(input);
+ ast_verbose(VERBOSE_PREFIX_3 "Zapscan: change channel to %d\n",ret);
+ break;
+ }
+ }
+
+ if (fd != chan->fds[0]) {
+ if (f->frametype == AST_FRAME_VOICE) {
+ if (f->subclass == AST_FORMAT_ULAW) {
+ /* Carefully write */
+ careful_write(fd, f->data, f->datalen);
+ } else
+ ast_log(LOG_WARNING, "Huh? Got a non-ulaw (%d) frame in the conference\n", f->subclass);
+ }
+ }
+ ast_frfree(f);
+ } else if (outfd > -1) {
+ res = read(outfd, buf, CONF_SIZE);
+ if (res > 0) {
+ memset(&fr, 0, sizeof(fr));
+ fr.frametype = AST_FRAME_VOICE;
+ fr.subclass = AST_FORMAT_ULAW;
+ fr.datalen = res;
+ fr.samples = res;
+ fr.data = buf;
+ fr.offset = AST_FRIENDLY_OFFSET;
+ if (ast_write(chan, &fr) < 0) {
+ ast_log(LOG_WARNING, "Unable to write frame to channel: %s\n", strerror(errno));
+ /* break; */
+ }
+ } else
+ ast_log(LOG_WARNING, "Failed to read frame: %s\n", strerror(errno));
+ }
+ }
+ if (f)
+ ast_frfree(f);
+ if (fd != chan->fds[0])
+ close(fd);
+ else {
+ /* Take out of conference */
+ /* Add us to the conference */
+ ztc.chan = 0;
+ ztc.confno = 0;
+ ztc.confmode = 0;
+ if (ioctl(fd, DAHDI_SETCONF, &ztc)) {
+ ast_log(LOG_WARNING, "Error setting conference\n");
+ }
+ }
+
+ outrun:
+
+ return ret;
+}
+
+static int conf_exec(struct ast_channel *chan, void *data)
+{
+ int res=-1;
+ struct ast_module_user *u;
+ int confflags = 0;
+ int confno = 0;
+ char confstr[80] = "", *tmp = NULL;
+ struct ast_channel *tempchan = NULL, *lastchan = NULL,*ichan = NULL;
+ struct ast_frame *f;
+ char *desired_group;
+ int input=0,search_group=0;
+
+ u = ast_module_user_add(chan);
+
+ if (chan->_state != AST_STATE_UP)
+ ast_answer(chan);
+
+ desired_group = ast_strdupa(data);
+ if(!ast_strlen_zero(desired_group)) {
+ ast_verbose(VERBOSE_PREFIX_3 "Scanning for group %s\n", desired_group);
+ search_group = 1;
+ }
+
+ for (;;) {
+ if (ast_waitfor(chan, 100) < 0)
+ break;
+
+ f = ast_read(chan);
+ if (!f)
+ break;
+ if ((f->frametype == AST_FRAME_DTMF) && (f->subclass == '*')) {
+ ast_frfree(f);
+ break;
+ }
+ ast_frfree(f);
+ ichan = NULL;
+ if(input) {
+ ichan = get_zap_channel_locked(input);
+ input = 0;
+ }
+
+ tempchan = ichan ? ichan : ast_channel_walk_locked(tempchan);
+
+ if ( !tempchan && !lastchan )
+ break;
+
+ if (tempchan && search_group) {
+ const char *mygroup;
+ if((mygroup = pbx_builtin_getvar_helper(tempchan, "GROUP")) && (!strcmp(mygroup, desired_group))) {
+ ast_verbose(VERBOSE_PREFIX_3 "Found Matching Channel %s in group %s\n", tempchan->name, desired_group);
+ } else {
+ ast_mutex_unlock(&tempchan->lock);
+ lastchan = tempchan;
+ continue;
+ }
+ }
+ if (tempchan && (!strcmp(tempchan->tech->type, "Zap")) && (tempchan != chan) ) {
+ ast_verbose(VERBOSE_PREFIX_3 "Zap channel %s is in-use, monitoring...\n", tempchan->name);
+ ast_copy_string(confstr, tempchan->name, sizeof(confstr));
+ ast_mutex_unlock(&tempchan->lock);
+ if ((tmp = strchr(confstr,'-'))) {
+ *tmp = '\0';
+ }
+ confno = atoi(strchr(confstr,'/') + 1);
+ ast_stopstream(chan);
+ ast_say_number(chan, confno, AST_DIGIT_ANY, chan->language, (char *) NULL);
+ res = conf_run(chan, confno, confflags);
+ if (res<0) break;
+ input = res;
+ } else if (tempchan)
+ ast_mutex_unlock(&tempchan->lock);
+ lastchan = tempchan;
+ }
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int conf_exec_warn(struct ast_channel *chan, void *data)
+{
+ ast_log(LOG_WARNING, "Use of the command %s is deprecated, please use %s instead.\n", deprecated_app, app);
+ return conf_exec(chan, data);
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ ast_register_application(deprecated_app, conf_exec_warn, synopsis, descrip);
+ return ast_register_application(app, conf_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Scan Zap channels application");
+
diff --git a/apps/app_db.c b/apps/app_db.c
new file mode 100644
index 000000000..0c8d0585b
--- /dev/null
+++ b/apps/app_db.c
@@ -0,0 +1,167 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ * Copyright (C) 2003, Jefferson Noxon
+ *
+ * Mark Spencer <markster@digium.com>
+ * Jefferson Noxon <jeff@debian.org>
+ *
+ * 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 Database access functions
+ *
+ * \author Mark Spencer <markster@digium.com>
+ * \author Jefferson Noxon <jeff@debian.org>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "asterisk/options.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/lock.h"
+#include "asterisk/options.h"
+
+/*! \todo XXX Remove this application after 1.4 is relased */
+static char *d_descrip =
+" DBdel(family/key): This application will delete a key from the Asterisk\n"
+"database.\n"
+" This application has been DEPRECATED in favor of the DB_DELETE function.\n";
+
+static char *dt_descrip =
+" DBdeltree(family[/keytree]): This application will delete a family or keytree\n"
+"from the Asterisk database\n";
+
+static char *d_app = "DBdel";
+static char *dt_app = "DBdeltree";
+
+static char *d_synopsis = "Delete a key from the database";
+static char *dt_synopsis = "Delete a family or keytree from the database";
+
+
+static int deltree_exec(struct ast_channel *chan, void *data)
+{
+ char *argv, *family, *keytree;
+ struct ast_module_user *u;
+
+ u = ast_module_user_add(chan);
+
+ argv = ast_strdupa(data);
+
+ if (strchr(argv, '/')) {
+ family = strsep(&argv, "/");
+ keytree = strsep(&argv, "\0");
+ if (!family || !keytree) {
+ ast_log(LOG_DEBUG, "Ignoring; Syntax error in argument\n");
+ ast_module_user_remove(u);
+ return 0;
+ }
+ if (ast_strlen_zero(keytree))
+ keytree = 0;
+ } else {
+ family = argv;
+ keytree = 0;
+ }
+
+ if (option_verbose > 2) {
+ if (keytree)
+ ast_verbose(VERBOSE_PREFIX_3 "DBdeltree: family=%s, keytree=%s\n", family, keytree);
+ else
+ ast_verbose(VERBOSE_PREFIX_3 "DBdeltree: family=%s\n", family);
+ }
+
+ if (ast_db_deltree(family, keytree)) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "DBdeltree: Error deleting key from database.\n");
+ }
+
+ ast_module_user_remove(u);
+
+ return 0;
+}
+
+static int del_exec(struct ast_channel *chan, void *data)
+{
+ char *argv, *family, *key;
+ struct ast_module_user *u;
+ static int deprecation_warning = 0;
+
+ u = ast_module_user_add(chan);
+
+ if (!deprecation_warning) {
+ deprecation_warning = 1;
+ ast_log(LOG_WARNING, "The DBdel application has been deprecated in favor of the DB_DELETE dialplan function!\n");
+ }
+
+ argv = ast_strdupa(data);
+
+ if (strchr(argv, '/')) {
+ family = strsep(&argv, "/");
+ key = strsep(&argv, "\0");
+ if (!family || !key) {
+ ast_log(LOG_DEBUG, "Ignoring; Syntax error in argument\n");
+ ast_module_user_remove(u);
+ return 0;
+ }
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "DBdel: family=%s, key=%s\n", family, key);
+ if (ast_db_del(family, key)) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "DBdel: Error deleting key from database.\n");
+ }
+ } else {
+ ast_log(LOG_DEBUG, "Ignoring, no parameters\n");
+ }
+
+ ast_module_user_remove(u);
+
+ return 0;
+}
+
+static int unload_module(void)
+{
+ int retval;
+
+ retval = ast_unregister_application(dt_app);
+ retval |= ast_unregister_application(d_app);
+
+ return retval;
+}
+
+static int load_module(void)
+{
+ int retval;
+
+ retval = ast_register_application(d_app, del_exec, d_synopsis, d_descrip);
+ retval |= ast_register_application(dt_app, deltree_exec, dt_synopsis, dt_descrip);
+
+ return retval;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Database Access Functions");
diff --git a/apps/app_dial.c b/apps/app_dial.c
new file mode 100644
index 000000000..303b36121
--- /dev/null
+++ b/apps/app_dial.c
@@ -0,0 +1,2015 @@
+/*
+ * 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 dial() & retrydial() - Trivial application to dial a channel and send an URL on answer
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+ <depend>chan_local</depend>
+ ***/
+
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#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 <sys/stat.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/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/say.h"
+#include "asterisk/config.h"
+#include "asterisk/features.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/callerid.h"
+#include "asterisk/utils.h"
+#include "asterisk/app.h"
+#include "asterisk/causes.h"
+#include "asterisk/rtp.h"
+#include "asterisk/cdr.h"
+#include "asterisk/manager.h"
+#include "asterisk/privacy.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/global_datastores.h"
+
+static char *app = "Dial";
+
+static char *synopsis = "Place a call and connect to the current channel";
+
+static char *descrip =
+" Dial(Technology/resource[&Tech2/resource2...][|timeout][|options][|URL]):\n"
+"This application will place calls to one or more specified channels. As soon\n"
+"as one of the requested channels answers, the originating channel will be\n"
+"answered, if it has not already been answered. These two channels will then\n"
+"be active in a bridged call. All other channels that were requested will then\n"
+"be hung up.\n"
+" Unless there is a timeout specified, the Dial application will wait\n"
+"indefinitely until one of the called channels answers, the user hangs up, or\n"
+"if all of the called channels are busy or unavailable. Dialplan executing will\n"
+"continue if no requested channels can be called, or if the timeout expires.\n\n"
+" This application sets the following channel variables upon completion:\n"
+" DIALEDTIME - This is the time from dialing a channel until when it\n"
+" is disconnected.\n"
+" ANSWEREDTIME - This is the amount of time for actual call.\n"
+" DIALSTATUS - This is the status of the call:\n"
+" CHANUNAVAIL | CONGESTION | NOANSWER | BUSY | ANSWER | CANCEL\n"
+" DONTCALL | TORTURE | INVALIDARGS\n"
+" For the Privacy and Screening Modes, the DIALSTATUS variable will be set to\n"
+"DONTCALL if the called party chooses to send the calling party to the 'Go Away'\n"
+"script. The DIALSTATUS variable will be set to TORTURE if the called party\n"
+"wants to send the caller to the 'torture' script.\n"
+" This application will report normal termination if the originating channel\n"
+"hangs up, or if the call is bridged and either of the parties in the bridge\n"
+"ends the call.\n"
+" The optional URL will be sent to the called party if the channel supports it.\n"
+" If the OUTBOUND_GROUP variable is set, all peer channels created by this\n"
+"application will be put into that group (as in Set(GROUP()=...).\n"
+" If the OUTBOUND_GROUP_ONCE variable is set, all peer channels created by this\n"
+"application will be put into that group (as in Set(GROUP()=...). Unlike OUTBOUND_GROUP,\n"
+"however, the variable will be unset after use.\n\n"
+" Options:\n"
+" A(x) - Play an announcement to the called party, using 'x' as the file.\n"
+" C - Reset the CDR for this call.\n"
+" d - Allow the calling user to dial a 1 digit extension while waiting for\n"
+" a call to be answered. Exit to that extension if it exists in the\n"
+" current context, or the context defined in the EXITCONTEXT variable,\n"
+" if it exists.\n"
+" D([called][:calling]) - Send the specified DTMF strings *after* the called\n"
+" party has answered, but before the call gets bridged. The 'called'\n"
+" DTMF string is sent to the called party, and the 'calling' DTMF\n"
+" string is sent to the calling party. Both parameters can be used\n"
+" alone.\n"
+" f - Force the callerid of the *calling* channel to be set as the\n"
+" extension associated with the channel using a dialplan 'hint'.\n"
+" For example, some PSTNs do not allow CallerID to be set to anything\n"
+" other than the number assigned to the caller.\n"
+" g - Proceed with dialplan execution at the current extension if the\n"
+" destination channel hangs up.\n"
+" G(context^exten^pri) - If the call is answered, transfer the calling party to\n"
+" the specified priority and the called party to the specified priority+1.\n"
+" Optionally, an extension, or extension and context may be specified. \n"
+" Otherwise, the current extension is used. You cannot use any additional\n"
+" action post answer options in conjunction with this option.\n"
+" h - Allow the called party to hang up by sending the '*' DTMF digit, or\n"
+" whatever sequence was defined in the featuremap section for\n"
+" 'disconnect' in features.conf\n"
+" H - Allow the calling party to hang up by hitting the '*' DTMF digit, or\n"
+" whatever sequence was defined in the featuremap section for\n"
+" 'disconnect' in features.conf\n"
+" i - Asterisk will ignore any forwarding requests it may receive on this\n"
+" dial attempt.\n"
+" j - Jump to priority n+101 if all of the requested channels were busy.\n"
+" k - Allow the called party to enable parking of the call by sending\n"
+" the DTMF sequence defined for call parking in the featuremap section of features.conf.\n"
+" K - Allow the calling party to enable parking of the call by sending\n"
+" the DTMF sequence defined for call parking in the featuremap section of features.conf.\n"
+" L(x[:y][:z]) - Limit the call to 'x' ms. Play a warning when 'y' ms are\n"
+" left. Repeat the warning every 'z' ms. The following special\n"
+" variables can be used with this option:\n"
+" * LIMIT_PLAYAUDIO_CALLER yes|no (default yes)\n"
+" Play sounds to the caller.\n"
+" * LIMIT_PLAYAUDIO_CALLEE yes|no\n"
+" Play sounds to the callee.\n"
+" * LIMIT_TIMEOUT_FILE File to play when time is up.\n"
+" * LIMIT_CONNECT_FILE File to play when call begins.\n"
+" * LIMIT_WARNING_FILE File to play as warning if 'y' is defined.\n"
+" The default is to say the time remaining.\n"
+" m([class]) - Provide hold music to the calling party until a requested\n"
+" channel answers. A specific MusicOnHold class can be\n"
+" specified.\n"
+" M(x[^arg]) - Execute the Macro for the *called* channel before connecting\n"
+" to the calling channel. Arguments can be specified to the Macro\n"
+" using '^' as a delimeter. The Macro can set the variable\n"
+" MACRO_RESULT to specify the following actions after the Macro is\n"
+" finished executing.\n"
+" * ABORT Hangup both legs of the call.\n"
+" * CONGESTION Behave as if line congestion was encountered.\n"
+" * BUSY Behave as if a busy signal was encountered. This will also\n"
+" have the application jump to priority n+101 if the\n"
+" 'j' option is set.\n"
+" * CONTINUE Hangup the called party and allow the calling party\n"
+" to continue dialplan execution at the next priority.\n"
+" * GOTO:<context>^<exten>^<priority> - Transfer the call to the\n"
+" specified priority. Optionally, an extension, or\n"
+" extension and priority can be specified.\n"
+" You cannot use any additional action post answer options in conjunction\n"
+" with this option. Also, pbx services are not run on the peer (called) channel,\n"
+" so you will not be able to set timeouts via the TIMEOUT() function in this macro.\n"
+" n - This option is a modifier for the screen/privacy mode. It specifies\n"
+" that no introductions are to be saved in the priv-callerintros\n"
+" directory.\n"
+" N - This option is a modifier for the screen/privacy mode. It specifies\n"
+" that if callerID is present, do not screen the call.\n"
+" o - Specify that the CallerID that was present on the *calling* channel\n"
+" be set as the CallerID on the *called* channel. This was the\n"
+" behavior of Asterisk 1.0 and earlier.\n"
+" O([x]) - \"Operator Services\" mode (Zaptel channel to Zaptel channel\n"
+" only, if specified on non-Zaptel interface, it will be ignored).\n"
+" When the destination answers (presumably an operator services\n"
+" station), the originator no longer has control of their line.\n"
+" They may hang up, but the switch will not release their line\n"
+" until the destination party hangs up (the operator). Specified\n"
+" without an arg, or with 1 as an arg, the originator hanging up\n"
+" will cause the phone to ring back immediately. With a 2 specified,\n"
+" when the \"operator\" flashes the trunk, it will ring their phone\n"
+" back.\n"
+" p - This option enables screening mode. This is basically Privacy mode\n"
+" without memory.\n"
+" P([x]) - Enable privacy mode. Use 'x' as the family/key in the database if\n"
+" it is provided. The current extension is used if a database\n"
+" family/key is not specified.\n"
+" r - Indicate ringing to the calling party. Pass no audio to the calling\n"
+" party until the called channel has answered.\n"
+" S(x) - Hang up the call after 'x' seconds *after* the called party has\n"
+" answered the call.\n"
+" t - Allow the called party to transfer the calling party by sending the\n"
+" DTMF sequence defined in the blindxfer setting in the featuremap section\n"
+" of features.conf.\n"
+" T - Allow the calling party to transfer the called party by sending the\n"
+" DTMF sequence defined in the blindxfer setting in the featuremap section\n"
+" of features.conf.\n"
+" w - Allow the called party to enable recording of the call by sending\n"
+" the DTMF sequence defined in the automon setting in the featuremap section\n"
+" of features.conf.\n"
+" W - Allow the calling party to enable recording of the call by sending\n"
+" the DTMF sequence defined in the automon setting in the featuremap section\n"
+" of features.conf.\n";
+
+/* RetryDial App by Anthony Minessale II <anthmct@yahoo.com> Jan/2005 */
+static char *rapp = "RetryDial";
+static char *rsynopsis = "Place a call, retrying on failure allowing optional exit extension.";
+static char *rdescrip =
+" RetryDial(announce|sleep|retries|dialargs): This application will attempt to\n"
+"place a call using the normal Dial application. If no channel can be reached,\n"
+"the 'announce' file will be played. Then, it will wait 'sleep' number of\n"
+"seconds before retrying the call. After 'retries' number of attempts, the\n"
+"calling channel will continue at the next priority in the dialplan. If the\n"
+"'retries' setting is set to 0, this application will retry endlessly.\n"
+" While waiting to retry a call, a 1 digit extension may be dialed. If that\n"
+"extension exists in either the context defined in ${EXITCONTEXT} or the current\n"
+"one, The call will jump to that extension immediately.\n"
+" The 'dialargs' are specified in the same format that arguments are provided\n"
+"to the Dial application.\n";
+
+enum {
+ OPT_ANNOUNCE = (1 << 0),
+ OPT_RESETCDR = (1 << 1),
+ OPT_DTMF_EXIT = (1 << 2),
+ OPT_SENDDTMF = (1 << 3),
+ OPT_FORCECLID = (1 << 4),
+ OPT_GO_ON = (1 << 5),
+ OPT_CALLEE_HANGUP = (1 << 6),
+ OPT_CALLER_HANGUP = (1 << 7),
+ OPT_PRIORITY_JUMP = (1 << 8),
+ OPT_DURATION_LIMIT = (1 << 9),
+ OPT_MUSICBACK = (1 << 10),
+ OPT_CALLEE_MACRO = (1 << 11),
+ OPT_SCREEN_NOINTRO = (1 << 12),
+ OPT_SCREEN_NOCLID = (1 << 13),
+ OPT_ORIGINAL_CLID = (1 << 14),
+ OPT_SCREENING = (1 << 15),
+ OPT_PRIVACY = (1 << 16),
+ OPT_RINGBACK = (1 << 17),
+ OPT_DURATION_STOP = (1 << 18),
+ OPT_CALLEE_TRANSFER = (1 << 19),
+ OPT_CALLER_TRANSFER = (1 << 20),
+ OPT_CALLEE_MONITOR = (1 << 21),
+ OPT_CALLER_MONITOR = (1 << 22),
+ OPT_GOTO = (1 << 23),
+ OPT_OPERMODE = (1 << 24),
+ OPT_CALLEE_PARK = (1 << 25),
+ OPT_CALLER_PARK = (1 << 26),
+ OPT_IGNORE_FORWARDING = (1 << 27),
+} dial_exec_option_flags;
+
+#define DIAL_STILLGOING (1 << 30)
+#define DIAL_NOFORWARDHTML (1 << 31)
+
+enum {
+ OPT_ARG_ANNOUNCE = 0,
+ OPT_ARG_SENDDTMF,
+ OPT_ARG_GOTO,
+ OPT_ARG_DURATION_LIMIT,
+ OPT_ARG_MUSICBACK,
+ OPT_ARG_CALLEE_MACRO,
+ OPT_ARG_PRIVACY,
+ OPT_ARG_DURATION_STOP,
+ OPT_ARG_OPERMODE,
+ /* note: this entry _MUST_ be the last one in the enum */
+ OPT_ARG_ARRAY_SIZE,
+} dial_exec_option_args;
+
+AST_APP_OPTIONS(dial_exec_options, {
+ AST_APP_OPTION_ARG('A', OPT_ANNOUNCE, OPT_ARG_ANNOUNCE),
+ AST_APP_OPTION('C', OPT_RESETCDR),
+ AST_APP_OPTION('d', OPT_DTMF_EXIT),
+ AST_APP_OPTION_ARG('D', OPT_SENDDTMF, OPT_ARG_SENDDTMF),
+ AST_APP_OPTION('f', OPT_FORCECLID),
+ AST_APP_OPTION('g', OPT_GO_ON),
+ AST_APP_OPTION_ARG('G', OPT_GOTO, OPT_ARG_GOTO),
+ AST_APP_OPTION('h', OPT_CALLEE_HANGUP),
+ AST_APP_OPTION('H', OPT_CALLER_HANGUP),
+ AST_APP_OPTION('i', OPT_IGNORE_FORWARDING),
+ AST_APP_OPTION('j', OPT_PRIORITY_JUMP),
+ AST_APP_OPTION('k', OPT_CALLEE_PARK),
+ AST_APP_OPTION('K', OPT_CALLER_PARK),
+ AST_APP_OPTION_ARG('L', OPT_DURATION_LIMIT, OPT_ARG_DURATION_LIMIT),
+ AST_APP_OPTION_ARG('m', OPT_MUSICBACK, OPT_ARG_MUSICBACK),
+ AST_APP_OPTION_ARG('M', OPT_CALLEE_MACRO, OPT_ARG_CALLEE_MACRO),
+ AST_APP_OPTION('n', OPT_SCREEN_NOINTRO),
+ AST_APP_OPTION('N', OPT_SCREEN_NOCLID),
+ AST_APP_OPTION('o', OPT_ORIGINAL_CLID),
+ AST_APP_OPTION_ARG('O', OPT_OPERMODE,OPT_ARG_OPERMODE),
+ AST_APP_OPTION('p', OPT_SCREENING),
+ AST_APP_OPTION_ARG('P', OPT_PRIVACY, OPT_ARG_PRIVACY),
+ AST_APP_OPTION('r', OPT_RINGBACK),
+ AST_APP_OPTION_ARG('S', OPT_DURATION_STOP, OPT_ARG_DURATION_STOP),
+ AST_APP_OPTION('t', OPT_CALLEE_TRANSFER),
+ AST_APP_OPTION('T', OPT_CALLER_TRANSFER),
+ AST_APP_OPTION('w', OPT_CALLEE_MONITOR),
+ AST_APP_OPTION('W', OPT_CALLER_MONITOR),
+});
+
+#define CAN_EARLY_BRIDGE(flags,chan,peer) (!ast_test_flag(flags, OPT_CALLEE_HANGUP | \
+ OPT_CALLER_HANGUP | OPT_CALLEE_TRANSFER | OPT_CALLER_TRANSFER | \
+ OPT_CALLEE_MONITOR | OPT_CALLER_MONITOR | OPT_CALLEE_PARK | OPT_CALLER_PARK) && \
+ !chan->audiohooks && !peer->audiohooks)
+
+/* We define a custom "local user" structure because we
+ use it not only for keeping track of what is in use but
+ also for keeping track of who we're dialing. */
+
+struct dial_localuser {
+ struct ast_channel *chan;
+ unsigned int flags;
+ struct dial_localuser *next;
+};
+
+
+static void hanguptree(struct dial_localuser *outgoing, struct ast_channel *exception)
+{
+ /* Hang up a tree of stuff */
+ struct dial_localuser *oo;
+ while (outgoing) {
+ /* Hangup any existing lines we have open */
+ if (outgoing->chan && (outgoing->chan != exception))
+ ast_hangup(outgoing->chan);
+ oo = outgoing;
+ outgoing=outgoing->next;
+ free(oo);
+ }
+}
+
+#define AST_MAX_WATCHERS 256
+
+#define HANDLE_CAUSE(cause, chan) do { \
+ switch(cause) { \
+ case AST_CAUSE_BUSY: \
+ if (chan->cdr) \
+ ast_cdr_busy(chan->cdr); \
+ numbusy++; \
+ break; \
+ case AST_CAUSE_CONGESTION: \
+ if (chan->cdr) \
+ ast_cdr_failed(chan->cdr); \
+ numcongestion++; \
+ break; \
+ case AST_CAUSE_NO_ROUTE_DESTINATION: \
+ case AST_CAUSE_UNREGISTERED: \
+ if (chan->cdr) \
+ ast_cdr_failed(chan->cdr); \
+ numnochan++; \
+ break; \
+ case AST_CAUSE_NORMAL_CLEARING: \
+ break; \
+ default: \
+ numnochan++; \
+ break; \
+ } \
+} while (0)
+
+
+static int onedigit_goto(struct ast_channel *chan, const char *context, char exten, int pri)
+{
+ char rexten[2] = { exten, '\0' };
+
+ if (context) {
+ if (!ast_goto_if_exists(chan, context, rexten, pri))
+ return 1;
+ } else {
+ if (!ast_goto_if_exists(chan, chan->context, rexten, pri))
+ return 1;
+ else if (!ast_strlen_zero(chan->macrocontext)) {
+ if (!ast_goto_if_exists(chan, chan->macrocontext, rexten, pri))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+static const char *get_cid_name(char *name, int namelen, struct ast_channel *chan)
+{
+ const char *context = S_OR(chan->macrocontext, chan->context);
+ const char *exten = S_OR(chan->macroexten, chan->exten);
+
+ return ast_get_hint(NULL, 0, name, namelen, chan, context, exten) ? name : "";
+}
+
+static void senddialevent(struct ast_channel *src, struct ast_channel *dst)
+{
+ /* XXX do we need also CallerIDnum ? */
+ manager_event(EVENT_FLAG_CALL, "Dial",
+ "Source: %s\r\n"
+ "Destination: %s\r\n"
+ "CallerID: %s\r\n"
+ "CallerIDName: %s\r\n"
+ "SrcUniqueID: %s\r\n"
+ "DestUniqueID: %s\r\n",
+ src->name, dst->name, S_OR(src->cid.cid_num, "<unknown>"),
+ S_OR(src->cid.cid_name, "<unknown>"), src->uniqueid,
+ dst->uniqueid);
+}
+
+static struct ast_channel *wait_for_answer(struct ast_channel *in, struct dial_localuser *outgoing, int *to, struct ast_flags *peerflags, int *sentringing, char *status, size_t statussize, int busystart, int nochanstart, int congestionstart, int priority_jump, int *result)
+{
+ int numbusy = busystart;
+ int numcongestion = congestionstart;
+ int numnochan = nochanstart;
+ int prestart = busystart + congestionstart + nochanstart;
+ int orig = *to;
+ struct ast_channel *peer = NULL;
+ /* single is set if only one destination is enabled */
+ int single = outgoing && !outgoing->next && !ast_test_flag(outgoing, OPT_MUSICBACK | OPT_RINGBACK);
+
+ if (single) {
+ /* Turn off hold music, etc */
+ ast_deactivate_generator(in);
+ /* If we are calling a single channel, make them compatible for in-band tone purpose */
+ ast_channel_make_compatible(outgoing->chan, in);
+ }
+
+
+ while (*to && !peer) {
+ struct dial_localuser *o;
+ int pos = 0; /* how many channels do we handle */
+ int numlines = prestart;
+ struct ast_channel *winner;
+ struct ast_channel *watchers[AST_MAX_WATCHERS];
+
+ watchers[pos++] = in;
+ for (o = outgoing; o; o = o->next) {
+ /* Keep track of important channels */
+ if (ast_test_flag(o, DIAL_STILLGOING) && o->chan)
+ watchers[pos++] = o->chan;
+ numlines++;
+ }
+ if (pos == 1) { /* only the input channel is available */
+ if (numlines == (numbusy + numcongestion + numnochan)) {
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_2 "Everyone is busy/congested at this time (%d:%d/%d/%d)\n", numlines, numbusy, numcongestion, numnochan);
+ if (numbusy)
+ strcpy(status, "BUSY");
+ else if (numcongestion)
+ strcpy(status, "CONGESTION");
+ else if (numnochan)
+ strcpy(status, "CHANUNAVAIL");
+ if (ast_opt_priority_jumping || priority_jump)
+ ast_goto_if_exists(in, in->context, in->exten, in->priority + 101);
+ } else {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "No one is available to answer at this time (%d:%d/%d/%d)\n", numlines, numbusy, numcongestion, numnochan);
+ }
+ *to = 0;
+ return NULL;
+ }
+ winner = ast_waitfor_n(watchers, pos, to);
+ for (o = outgoing; o; o = o->next) {
+ struct ast_frame *f;
+ struct ast_channel *c = o->chan;
+
+ if (c == NULL)
+ continue;
+ if (ast_test_flag(o, DIAL_STILLGOING) && c->_state == AST_STATE_UP) {
+ if (!peer) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "%s answered %s\n", c->name, in->name);
+ peer = c;
+ ast_copy_flags(peerflags, o,
+ OPT_CALLEE_TRANSFER | OPT_CALLER_TRANSFER |
+ OPT_CALLEE_HANGUP | OPT_CALLER_HANGUP |
+ OPT_CALLEE_MONITOR | OPT_CALLER_MONITOR |
+ OPT_CALLEE_PARK | OPT_CALLER_PARK |
+ DIAL_NOFORWARDHTML);
+ ast_copy_string(c->dialcontext, "", sizeof(c->dialcontext));
+ ast_copy_string(c->exten, "", sizeof(c->exten));
+ }
+ continue;
+ }
+ if (c != winner)
+ continue;
+ if (!ast_strlen_zero(c->call_forward)) {
+ char tmpchan[256];
+ char *stuff;
+ char *tech;
+ int cause;
+
+ ast_copy_string(tmpchan, c->call_forward, sizeof(tmpchan));
+ if ((stuff = strchr(tmpchan, '/'))) {
+ *stuff++ = '\0';
+ tech = tmpchan;
+ } else {
+ const char *forward_context = pbx_builtin_getvar_helper(c, "FORWARD_CONTEXT");
+ snprintf(tmpchan, sizeof(tmpchan), "%s@%s", c->call_forward, forward_context ? forward_context : c->context);
+ stuff = tmpchan;
+ tech = "Local";
+ }
+ /* Before processing channel, go ahead and check for forwarding */
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Now forwarding %s to '%s/%s' (thanks to %s)\n", in->name, tech, stuff, c->name);
+ /* If we have been told to ignore forwards, just set this channel to null and continue processing extensions normally */
+ if (ast_test_flag(peerflags, OPT_IGNORE_FORWARDING)) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Forwarding %s to '%s/%s' prevented.\n", in->name, tech, stuff);
+ c = o->chan = NULL;
+ cause = AST_CAUSE_BUSY;
+ } else {
+ /* Setup parameters */
+ if ((c = o->chan = ast_request(tech, in->nativeformats, stuff, &cause))) {
+ if (single)
+ ast_channel_make_compatible(o->chan, in);
+ ast_channel_inherit_variables(in, o->chan);
+ ast_channel_datastore_inherit(in, o->chan);
+ } else
+ ast_log(LOG_NOTICE, "Unable to create local channel for call forward to '%s/%s' (cause = %d)\n", tech, stuff, cause);
+ }
+ if (!c) {
+ ast_clear_flag(o, DIAL_STILLGOING);
+ HANDLE_CAUSE(cause, in);
+ } else {
+ ast_rtp_make_compatible(c, in, single);
+ if (c->cid.cid_num)
+ free(c->cid.cid_num);
+ c->cid.cid_num = NULL;
+ if (c->cid.cid_name)
+ free(c->cid.cid_name);
+ c->cid.cid_name = NULL;
+
+ if (ast_test_flag(o, OPT_FORCECLID)) {
+ c->cid.cid_num = ast_strdup(S_OR(in->macroexten, in->exten));
+ ast_string_field_set(c, accountcode, winner->accountcode);
+ c->cdrflags = winner->cdrflags;
+ } else {
+ c->cid.cid_num = ast_strdup(in->cid.cid_num);
+ c->cid.cid_name = ast_strdup(in->cid.cid_name);
+ ast_string_field_set(c, accountcode, in->accountcode);
+ c->cdrflags = in->cdrflags;
+ }
+
+ if (in->cid.cid_ani) {
+ if (c->cid.cid_ani)
+ free(c->cid.cid_ani);
+ c->cid.cid_ani = ast_strdup(in->cid.cid_ani);
+ }
+ if (c->cid.cid_rdnis)
+ free(c->cid.cid_rdnis);
+ c->cid.cid_rdnis = ast_strdup(S_OR(in->macroexten, in->exten));
+ if (ast_call(c, tmpchan, 0)) {
+ ast_log(LOG_NOTICE, "Failed to dial on local channel for call forward to '%s'\n", tmpchan);
+ ast_clear_flag(o, DIAL_STILLGOING);
+ ast_hangup(c);
+ c = o->chan = NULL;
+ numnochan++;
+ } else {
+ senddialevent(in, c);
+ /* After calling, set callerid to extension */
+ if (!ast_test_flag(peerflags, OPT_ORIGINAL_CLID)) {
+ char cidname[AST_MAX_EXTENSION] = "";
+ ast_set_callerid(c, S_OR(in->macroexten, in->exten), get_cid_name(cidname, sizeof(cidname), in), NULL);
+ }
+ }
+ }
+ /* Hangup the original channel now, in case we needed it */
+ ast_hangup(winner);
+ continue;
+ }
+ f = ast_read(winner);
+ if (!f) {
+ in->hangupcause = c->hangupcause;
+ ast_hangup(c);
+ c = o->chan = NULL;
+ ast_clear_flag(o, DIAL_STILLGOING);
+ HANDLE_CAUSE(in->hangupcause, in);
+ continue;
+ }
+ if (f->frametype == AST_FRAME_CONTROL) {
+ switch(f->subclass) {
+ case AST_CONTROL_ANSWER:
+ /* This is our guy if someone answered. */
+ if (!peer) {
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", c->name, in->name);
+ peer = c;
+ if (peer->cdr) {
+ peer->cdr->answer = ast_tvnow();
+ peer->cdr->disposition = AST_CDR_ANSWERED;
+ }
+ ast_copy_flags(peerflags, o,
+ OPT_CALLEE_TRANSFER | OPT_CALLER_TRANSFER |
+ OPT_CALLEE_HANGUP | OPT_CALLER_HANGUP |
+ OPT_CALLEE_MONITOR | OPT_CALLER_MONITOR |
+ OPT_CALLEE_PARK | OPT_CALLER_PARK |
+ DIAL_NOFORWARDHTML);
+ ast_copy_string(c->dialcontext, "", sizeof(c->dialcontext));
+ ast_copy_string(c->exten, "", sizeof(c->exten));
+ /* Setup RTP early bridge if appropriate */
+ if (CAN_EARLY_BRIDGE(peerflags, in, peer))
+ ast_rtp_early_bridge(in, peer);
+ }
+ /* If call has been answered, then the eventual hangup is likely to be normal hangup */
+ in->hangupcause = AST_CAUSE_NORMAL_CLEARING;
+ c->hangupcause = AST_CAUSE_NORMAL_CLEARING;
+ break;
+ case AST_CONTROL_BUSY:
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "%s is busy\n", c->name);
+ in->hangupcause = c->hangupcause;
+ ast_hangup(c);
+ c = o->chan = NULL;
+ ast_clear_flag(o, DIAL_STILLGOING);
+ HANDLE_CAUSE(AST_CAUSE_BUSY, in);
+ break;
+ case AST_CONTROL_CONGESTION:
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "%s is circuit-busy\n", c->name);
+ in->hangupcause = c->hangupcause;
+ ast_hangup(c);
+ c = o->chan = NULL;
+ ast_clear_flag(o, DIAL_STILLGOING);
+ HANDLE_CAUSE(AST_CAUSE_CONGESTION, in);
+ break;
+ case AST_CONTROL_RINGING:
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "%s is ringing\n", c->name);
+ /* Setup early media if appropriate */
+ if (single && CAN_EARLY_BRIDGE(peerflags, in, c))
+ ast_rtp_early_bridge(in, c);
+ if (!(*sentringing) && !ast_test_flag(outgoing, OPT_MUSICBACK)) {
+ ast_indicate(in, AST_CONTROL_RINGING);
+ (*sentringing)++;
+ }
+ break;
+ case AST_CONTROL_PROGRESS:
+ if (option_verbose > 2)
+ ast_verbose (VERBOSE_PREFIX_3 "%s is making progress passing it to %s\n", c->name, in->name);
+ /* Setup early media if appropriate */
+ if (single && CAN_EARLY_BRIDGE(peerflags, in, c))
+ ast_rtp_early_bridge(in, c);
+ if (!ast_test_flag(outgoing, OPT_RINGBACK))
+ ast_indicate(in, AST_CONTROL_PROGRESS);
+ break;
+ case AST_CONTROL_VIDUPDATE:
+ if (option_verbose > 2)
+ ast_verbose (VERBOSE_PREFIX_3 "%s requested a video update, passing it to %s\n", c->name, in->name);
+ ast_indicate(in, AST_CONTROL_VIDUPDATE);
+ break;
+ case AST_CONTROL_SRCUPDATE:
+ if (option_verbose > 2)
+ ast_verbose (VERBOSE_PREFIX_3 "%s requested a source update, passing it to %s\n", c->name, in->name);
+ ast_indicate(in, AST_CONTROL_SRCUPDATE);
+ break;
+ case AST_CONTROL_PROCEEDING:
+ if (option_verbose > 2)
+ ast_verbose (VERBOSE_PREFIX_3 "%s is proceeding passing it to %s\n", c->name, in->name);
+ if (single && CAN_EARLY_BRIDGE(peerflags, in, c))
+ ast_rtp_early_bridge(in, c);
+ if (!ast_test_flag(outgoing, OPT_RINGBACK))
+ ast_indicate(in, AST_CONTROL_PROCEEDING);
+ break;
+ case AST_CONTROL_HOLD:
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Call on %s placed on hold\n", c->name);
+ ast_indicate(in, AST_CONTROL_HOLD);
+ break;
+ case AST_CONTROL_UNHOLD:
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Call on %s left from hold\n", c->name);
+ ast_indicate(in, AST_CONTROL_UNHOLD);
+ break;
+ case AST_CONTROL_OFFHOOK:
+ case AST_CONTROL_FLASH:
+ /* Ignore going off hook and flash */
+ break;
+ case -1:
+ if (!ast_test_flag(outgoing, OPT_RINGBACK | OPT_MUSICBACK)) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "%s stopped sounds\n", c->name);
+ ast_indicate(in, -1);
+ (*sentringing) = 0;
+ }
+ break;
+ default:
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Dunno what to do with control type %d\n", f->subclass);
+ }
+ } else if (single) {
+ /* XXX are we sure the logic is correct ? or we should just switch on f->frametype ? */
+ if (f->frametype == AST_FRAME_VOICE && !ast_test_flag(outgoing, OPT_RINGBACK|OPT_MUSICBACK)) {
+ if (ast_write(in, f))
+ ast_log(LOG_WARNING, "Unable to forward voice frame\n");
+ } else if (f->frametype == AST_FRAME_IMAGE && !ast_test_flag(outgoing, OPT_RINGBACK|OPT_MUSICBACK)) {
+ if (ast_write(in, f))
+ ast_log(LOG_WARNING, "Unable to forward image\n");
+ } else if (f->frametype == AST_FRAME_TEXT && !ast_test_flag(outgoing, OPT_RINGBACK|OPT_MUSICBACK)) {
+ if (ast_write(in, f))
+ ast_log(LOG_WARNING, "Unable to send text\n");
+ } else if (f->frametype == AST_FRAME_HTML && !ast_test_flag(outgoing, DIAL_NOFORWARDHTML)) {
+ if (ast_channel_sendhtml(in, f->subclass, f->data, f->datalen) == -1)
+ ast_log(LOG_WARNING, "Unable to send URL\n");
+ }
+ }
+ ast_frfree(f);
+ } /* end for */
+ if (winner == in) {
+ struct ast_frame *f = ast_read(in);
+#if 0
+ if (f && (f->frametype != AST_FRAME_VOICE))
+ printf("Frame type: %d, %d\n", f->frametype, f->subclass);
+ else if (!f || (f->frametype != AST_FRAME_VOICE))
+ printf("Hangup received on %s\n", in->name);
+#endif
+ if (!f || ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP))) {
+ /* Got hung up */
+ *to = -1;
+ ast_cdr_noanswer(in->cdr);
+ strcpy(status, "CANCEL");
+ if (f)
+ ast_frfree(f);
+ return NULL;
+ }
+
+ if (f && (f->frametype == AST_FRAME_DTMF)) {
+ if (ast_test_flag(peerflags, OPT_DTMF_EXIT)) {
+ const char *context = pbx_builtin_getvar_helper(in, "EXITCONTEXT");
+ if (onedigit_goto(in, context, (char) f->subclass, 1)) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "User hit %c to disconnect call.\n", f->subclass);
+ *to=0;
+ ast_cdr_noanswer(in->cdr);
+ *result = f->subclass;
+ strcpy(status, "CANCEL");
+ ast_frfree(f);
+ return NULL;
+ }
+ }
+
+ if (ast_test_flag(peerflags, OPT_CALLER_HANGUP) &&
+ (f->subclass == '*')) { /* hmm it it not guaranteed to be '*' anymore. */
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "User hit %c to disconnect call.\n", f->subclass);
+ *to=0;
+ ast_cdr_noanswer(in->cdr);
+ strcpy(status, "CANCEL");
+ ast_frfree(f);
+ return NULL;
+ }
+ }
+
+ /* Forward HTML stuff */
+ if (single && f && (f->frametype == AST_FRAME_HTML) && !ast_test_flag(outgoing, DIAL_NOFORWARDHTML))
+ if(ast_channel_sendhtml(outgoing->chan, f->subclass, f->data, f->datalen) == -1)
+ ast_log(LOG_WARNING, "Unable to send URL\n");
+
+
+ if (single && ((f->frametype == AST_FRAME_VOICE) || (f->frametype == AST_FRAME_DTMF_BEGIN) || (f->frametype == AST_FRAME_DTMF_END))) {
+ if (ast_write(outgoing->chan, f))
+ ast_log(LOG_WARNING, "Unable to forward voice or dtmf\n");
+ }
+ if (single && (f->frametype == AST_FRAME_CONTROL) &&
+ ((f->subclass == AST_CONTROL_HOLD) ||
+ (f->subclass == AST_CONTROL_UNHOLD) ||
+ (f->subclass == AST_CONTROL_VIDUPDATE) ||
+ (f->subclass == AST_CONTROL_SRCUPDATE))) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "%s requested special control %d, passing it to %s\n", in->name, f->subclass, outgoing->chan->name);
+ ast_indicate_data(outgoing->chan, f->subclass, f->data, f->datalen);
+ }
+ ast_frfree(f);
+ }
+ if (!*to && (option_verbose > 2))
+ ast_verbose(VERBOSE_PREFIX_3 "Nobody picked up in %d ms\n", orig);
+ if (!*to || ast_check_hangup(in)) {
+ ast_cdr_noanswer(in->cdr);
+ }
+
+ }
+
+ return peer;
+}
+
+static void replace_macro_delimiter(char *s)
+{
+ for (; *s; s++)
+ if (*s == '^')
+ *s = '|';
+}
+
+
+/* returns true if there is a valid privacy reply */
+static int valid_priv_reply(struct ast_flags *opts, int res)
+{
+ if (res < '1')
+ return 0;
+ if (ast_test_flag(opts, OPT_PRIVACY) && res <= '5')
+ return 1;
+ if (ast_test_flag(opts, OPT_SCREENING) && res <= '4')
+ return 1;
+ return 0;
+}
+
+static void set_dial_features(struct ast_flags *opts, struct ast_dial_features *features)
+{
+ struct ast_flags perm_opts = {.flags = 0};
+
+ ast_copy_flags(&perm_opts, opts,
+ OPT_CALLER_TRANSFER | OPT_CALLER_PARK | OPT_CALLER_MONITOR | OPT_CALLER_HANGUP |
+ OPT_CALLEE_TRANSFER | OPT_CALLEE_PARK | OPT_CALLEE_MONITOR | OPT_CALLEE_HANGUP);
+
+ memset(features->options, 0, sizeof(features->options));
+
+ ast_app_options2str(dial_exec_options, &perm_opts, features->options, sizeof(features->options));
+ if (ast_test_flag(&perm_opts, OPT_CALLEE_TRANSFER))
+ ast_set_flag(&(features->features_callee), AST_FEATURE_REDIRECT);
+ if (ast_test_flag(&perm_opts, OPT_CALLER_TRANSFER))
+ ast_set_flag(&(features->features_caller), AST_FEATURE_REDIRECT);
+ if (ast_test_flag(&perm_opts, OPT_CALLEE_HANGUP))
+ ast_set_flag(&(features->features_callee), AST_FEATURE_DISCONNECT);
+ if (ast_test_flag(&perm_opts, OPT_CALLER_HANGUP))
+ ast_set_flag(&(features->features_caller), AST_FEATURE_DISCONNECT);
+ if (ast_test_flag(&perm_opts, OPT_CALLEE_MONITOR))
+ ast_set_flag(&(features->features_callee), AST_FEATURE_AUTOMON);
+ if (ast_test_flag(&perm_opts, OPT_CALLER_MONITOR))
+ ast_set_flag(&(features->features_caller), AST_FEATURE_AUTOMON);
+ if (ast_test_flag(&perm_opts, OPT_CALLEE_PARK))
+ ast_set_flag(&(features->features_callee), AST_FEATURE_PARKCALL);
+ if (ast_test_flag(&perm_opts, OPT_CALLER_PARK))
+ ast_set_flag(&(features->features_caller), AST_FEATURE_PARKCALL);
+}
+
+static void end_bridge_callback (void *data)
+{
+ char buf[80];
+ time_t end;
+ struct ast_channel *chan = data;
+
+ if (!chan->cdr) {
+ return;
+ }
+
+ time(&end);
+
+ ast_channel_lock(chan);
+ if (chan->cdr->answer.tv_sec) {
+ snprintf(buf, sizeof(buf), "%ld", end - chan->cdr->answer.tv_sec);
+ pbx_builtin_setvar_helper(chan, "ANSWEREDTIME", buf);
+ }
+
+ if (chan->cdr->start.tv_sec) {
+ snprintf(buf, sizeof(buf), "%ld", end - chan->cdr->start.tv_sec);
+ pbx_builtin_setvar_helper(chan, "DIALEDTIME", buf);
+ }
+ ast_channel_unlock(chan);
+}
+
+static void end_bridge_callback_data_fixup(struct ast_bridge_config *bconfig, struct ast_channel *originator, struct ast_channel *terminator) {
+ bconfig->end_bridge_callback_data = originator;
+}
+
+static int dial_exec_full(struct ast_channel *chan, void *data, struct ast_flags *peerflags, int *continue_exec)
+{
+ int res = -1;
+ struct ast_module_user *u;
+ char *rest, *cur;
+ struct dial_localuser *outgoing = NULL;
+ struct ast_channel *peer;
+ int to;
+ int numbusy = 0;
+ int numcongestion = 0;
+ int numnochan = 0;
+ int cause;
+ char numsubst[256];
+ char cidname[AST_MAX_EXTENSION] = "";
+ int privdb_val = 0;
+ int calldurationlimit = -1;
+ long timelimit = 0;
+ long play_warning = 0;
+ long warning_freq = 0;
+ const char *warning_sound = NULL;
+ const char *end_sound = NULL;
+ const char *start_sound = NULL;
+ char *dtmfcalled = NULL, *dtmfcalling = NULL;
+ char status[256] = "INVALIDARGS";
+ int play_to_caller = 0, play_to_callee = 0;
+ int sentringing = 0, moh = 0;
+ const char *outbound_group = NULL;
+ int result = 0;
+ time_t start_time;
+ char privintro[1024];
+ char privcid[256];
+ char *parse;
+ int opermode = 0;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(peers);
+ AST_APP_ARG(timeout);
+ AST_APP_ARG(options);
+ AST_APP_ARG(url);
+ );
+ struct ast_flags opts = { 0, };
+ char *opt_args[OPT_ARG_ARRAY_SIZE];
+ struct ast_datastore *datastore = NULL;
+ struct ast_datastore *ds_caller_features = NULL;
+ struct ast_datastore *ds_callee_features = NULL;
+ struct ast_dial_features *caller_features;
+ int fulldial = 0, num_dialed = 0;
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "Dial requires an argument (technology/number)\n");
+ pbx_builtin_setvar_helper(chan, "DIALSTATUS", status);
+ return -1;
+ }
+
+ /* Reset all DIAL variables back to blank, to prevent confusion (in case we don't reset all of them). */
+ pbx_builtin_setvar_helper(chan, "DIALSTATUS", "");
+ pbx_builtin_setvar_helper(chan, "DIALEDPEERNUMBER", "");
+ pbx_builtin_setvar_helper(chan, "DIALEDPEERNAME", "");
+ pbx_builtin_setvar_helper(chan, "ANSWEREDTIME", "");
+ pbx_builtin_setvar_helper(chan, "DIALEDTIME", "");
+
+ u = ast_module_user_add(chan);
+
+ parse = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ if (!ast_strlen_zero(args.options) &&
+ ast_app_parse_options(dial_exec_options, &opts, opt_args, args.options)) {
+ pbx_builtin_setvar_helper(chan, "DIALSTATUS", status);
+ goto done;
+ }
+
+ if (ast_strlen_zero(args.peers)) {
+ ast_log(LOG_WARNING, "Dial requires an argument (technology/number)\n");
+ pbx_builtin_setvar_helper(chan, "DIALSTATUS", status);
+ goto done;
+ }
+
+ if (ast_test_flag(&opts, OPT_OPERMODE)) {
+ if (ast_strlen_zero(opt_args[OPT_ARG_OPERMODE]))
+ opermode = 1;
+ else opermode = atoi(opt_args[OPT_ARG_OPERMODE]);
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Setting operator services mode to %d.\n", opermode);
+ }
+
+ if (ast_test_flag(&opts, OPT_DURATION_STOP) && !ast_strlen_zero(opt_args[OPT_ARG_DURATION_STOP])) {
+ calldurationlimit = atoi(opt_args[OPT_ARG_DURATION_STOP]);
+ if (!calldurationlimit) {
+ ast_log(LOG_WARNING, "Dial does not accept S(%s), hanging up.\n", opt_args[OPT_ARG_DURATION_STOP]);
+ pbx_builtin_setvar_helper(chan, "DIALSTATUS", status);
+ goto done;
+ }
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Setting call duration limit to %d seconds.\n", calldurationlimit);
+ }
+
+ if (ast_test_flag(&opts, OPT_SENDDTMF) && !ast_strlen_zero(opt_args[OPT_ARG_SENDDTMF])) {
+ dtmfcalling = opt_args[OPT_ARG_SENDDTMF];
+ dtmfcalled = strsep(&dtmfcalling, ":");
+ }
+
+ if (ast_test_flag(&opts, OPT_DURATION_LIMIT) && !ast_strlen_zero(opt_args[OPT_ARG_DURATION_LIMIT])) {
+ char *limit_str, *warning_str, *warnfreq_str;
+ const char *var;
+
+ warnfreq_str = opt_args[OPT_ARG_DURATION_LIMIT];
+ limit_str = strsep(&warnfreq_str, ":");
+ warning_str = strsep(&warnfreq_str, ":");
+
+ timelimit = atol(limit_str);
+ if (warning_str)
+ play_warning = atol(warning_str);
+ if (warnfreq_str)
+ warning_freq = atol(warnfreq_str);
+
+ if (!timelimit) {
+ ast_log(LOG_WARNING, "Dial does not accept L(%s), hanging up.\n", limit_str);
+ goto done;
+ } else if (play_warning > timelimit) {
+ /* If the first warning is requested _after_ the entire call would end,
+ and no warning frequency is requested, then turn off the warning. If
+ a warning frequency is requested, reduce the 'first warning' time by
+ that frequency until it falls within the call's total time limit.
+ */
+
+ if (!warning_freq) {
+ play_warning = 0;
+ } else {
+ /* XXX fix this!! */
+ while (play_warning > timelimit)
+ play_warning -= warning_freq;
+ if (play_warning < 1)
+ play_warning = warning_freq = 0;
+ }
+ }
+
+ var = pbx_builtin_getvar_helper(chan,"LIMIT_PLAYAUDIO_CALLER");
+ play_to_caller = var ? ast_true(var) : 1;
+
+ var = pbx_builtin_getvar_helper(chan,"LIMIT_PLAYAUDIO_CALLEE");
+ play_to_callee = var ? ast_true(var) : 0;
+
+ if (!play_to_caller && !play_to_callee)
+ play_to_caller = 1;
+
+ var = pbx_builtin_getvar_helper(chan,"LIMIT_WARNING_FILE");
+ warning_sound = S_OR(var, "timeleft");
+
+ var = pbx_builtin_getvar_helper(chan,"LIMIT_TIMEOUT_FILE");
+ end_sound = S_OR(var, NULL); /* XXX not much of a point in doing this! */
+
+ var = pbx_builtin_getvar_helper(chan,"LIMIT_CONNECT_FILE");
+ start_sound = S_OR(var, NULL); /* XXX not much of a point in doing this! */
+
+ /* undo effect of S(x) in case they are both used */
+ calldurationlimit = -1;
+ /* more efficient to do it like S(x) does since no advanced opts */
+ if (!play_warning && !start_sound && !end_sound && timelimit) {
+ calldurationlimit = timelimit / 1000;
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Setting call duration limit to %d seconds.\n", calldurationlimit);
+ timelimit = play_to_caller = play_to_callee = play_warning = warning_freq = 0;
+ } else if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_3 "Limit Data for this call:\n");
+ ast_verbose(VERBOSE_PREFIX_4 "timelimit = %ld\n", timelimit);
+ ast_verbose(VERBOSE_PREFIX_4 "play_warning = %ld\n", play_warning);
+ ast_verbose(VERBOSE_PREFIX_4 "play_to_caller = %s\n", play_to_caller ? "yes" : "no");
+ ast_verbose(VERBOSE_PREFIX_4 "play_to_callee = %s\n", play_to_callee ? "yes" : "no");
+ ast_verbose(VERBOSE_PREFIX_4 "warning_freq = %ld\n", warning_freq);
+ ast_verbose(VERBOSE_PREFIX_4 "start_sound = %s\n", start_sound);
+ ast_verbose(VERBOSE_PREFIX_4 "warning_sound = %s\n", warning_sound);
+ ast_verbose(VERBOSE_PREFIX_4 "end_sound = %s\n", end_sound);
+ }
+ }
+
+ if (ast_test_flag(&opts, OPT_RESETCDR) && chan->cdr)
+ ast_cdr_reset(chan->cdr, NULL);
+ if (ast_test_flag(&opts, OPT_PRIVACY) && ast_strlen_zero(opt_args[OPT_ARG_PRIVACY]))
+ opt_args[OPT_ARG_PRIVACY] = ast_strdupa(chan->exten);
+ if (ast_test_flag(&opts, OPT_PRIVACY) || ast_test_flag(&opts, OPT_SCREENING)) {
+ char callerid[60];
+ char *l = chan->cid.cid_num; /* XXX watch out, we are overwriting it */
+ if (!ast_strlen_zero(l)) {
+ ast_shrink_phone_number(l);
+ if( ast_test_flag(&opts, OPT_PRIVACY) ) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Privacy DB is '%s', clid is '%s'\n",
+ opt_args[OPT_ARG_PRIVACY], l);
+ privdb_val = ast_privacy_check(opt_args[OPT_ARG_PRIVACY], l);
+ }
+ else {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Privacy Screening, clid is '%s'\n", l);
+ privdb_val = AST_PRIVACY_UNKNOWN;
+ }
+ } else {
+ char *tnam, *tn2;
+
+ tnam = ast_strdupa(chan->name);
+ /* clean the channel name so slashes don't try to end up in disk file name */
+ for(tn2 = tnam; *tn2; tn2++) {
+ if( *tn2=='/')
+ *tn2 = '='; /* any other chars to be afraid of? */
+ }
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Privacy-- callerid is empty\n");
+
+ snprintf(callerid, sizeof(callerid), "NOCALLERID_%s%s", chan->exten, tnam);
+ l = callerid;
+ privdb_val = AST_PRIVACY_UNKNOWN;
+ }
+
+ ast_copy_string(privcid,l,sizeof(privcid));
+
+ if( strncmp(privcid,"NOCALLERID",10) != 0 && ast_test_flag(&opts, OPT_SCREEN_NOCLID) ) { /* if callerid is set, and ast_test_flag(&opts, OPT_SCREEN_NOCLID) is set also */
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "CallerID set (%s); N option set; Screening should be off\n", privcid);
+ privdb_val = AST_PRIVACY_ALLOW;
+ }
+ else if(ast_test_flag(&opts, OPT_SCREEN_NOCLID) && strncmp(privcid,"NOCALLERID",10) == 0 ) {
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "CallerID blank; N option set; Screening should happen; dbval is %d\n", privdb_val);
+ }
+
+ if(privdb_val == AST_PRIVACY_DENY ) {
+ ast_copy_string(status, "NOANSWER", sizeof(status));
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "Privacy DB reports PRIVACY_DENY for this callerid. Dial reports unavailable\n");
+ res=0;
+ goto out;
+ }
+ else if(privdb_val == AST_PRIVACY_KILL ) {
+ ast_copy_string(status, "DONTCALL", sizeof(status));
+ if (ast_opt_priority_jumping || ast_test_flag(&opts, OPT_PRIORITY_JUMP)) {
+ ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 201);
+ }
+ res = 0;
+ goto out; /* Is this right? */
+ }
+ else if(privdb_val == AST_PRIVACY_TORTURE ) {
+ ast_copy_string(status, "TORTURE", sizeof(status));
+ if (ast_opt_priority_jumping || ast_test_flag(&opts, OPT_PRIORITY_JUMP)) {
+ ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 301);
+ }
+ res = 0;
+ goto out; /* is this right??? */
+ }
+ else if(privdb_val == AST_PRIVACY_UNKNOWN ) {
+ /* Get the user's intro, store it in priv-callerintros/$CID,
+ unless it is already there-- this should be done before the
+ call is actually dialed */
+
+ /* make sure the priv-callerintros dir actually exists */
+ snprintf(privintro, sizeof(privintro), "%s/sounds/priv-callerintros", ast_config_AST_DATA_DIR);
+ if (mkdir(privintro, 0755) && errno != EEXIST) {
+ ast_log(LOG_WARNING, "privacy: can't create directory priv-callerintros: %s\n", strerror(errno));
+ res = -1;
+ goto out;
+ }
+
+ snprintf(privintro,sizeof(privintro), "priv-callerintros/%s", privcid);
+ if( ast_fileexists(privintro,NULL,NULL ) > 0 && strncmp(privcid,"NOCALLERID",10) != 0) {
+ /* the DELUX version of this code would allow this caller the
+ option to hear and retape their previously recorded intro.
+ */
+ }
+ else {
+ int duration; /* for feedback from play_and_wait */
+ /* the file doesn't exist yet. Let the caller submit his
+ vocal intro for posterity */
+ /* priv-recordintro script:
+
+ "At the tone, please say your name:"
+
+ */
+ ast_answer(chan);
+ res = ast_play_and_record(chan, "priv-recordintro", privintro, 4, "gsm", &duration, 128, 2000, 0); /* NOTE: I've reduced the total time to 4 sec */
+ /* don't think we'll need a lock removed, we took care of
+ conflicts by naming the privintro file */
+ if (res == -1) {
+ /* Delete the file regardless since they hung up during recording */
+ ast_filedelete(privintro, NULL);
+ if( ast_fileexists(privintro,NULL,NULL ) > 0 )
+ ast_log(LOG_NOTICE,"privacy: ast_filedelete didn't do its job on %s\n", privintro);
+ else if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "Successfully deleted %s intro file\n", privintro);
+ goto out;
+ }
+ if( !ast_streamfile(chan, "vm-dialout", chan->language) )
+ ast_waitstream(chan, "");
+ }
+ }
+ }
+
+ if (continue_exec)
+ *continue_exec = 0;
+
+ /* If a channel group has been specified, get it for use when we create peer channels */
+ if ((outbound_group = pbx_builtin_getvar_helper(chan, "OUTBOUND_GROUP_ONCE"))) {
+ outbound_group = ast_strdupa(outbound_group);
+ pbx_builtin_setvar_helper(chan, "OUTBOUND_GROUP_ONCE", NULL);
+ } else {
+ outbound_group = pbx_builtin_getvar_helper(chan, "OUTBOUND_GROUP");
+ }
+
+ ast_copy_flags(peerflags, &opts, OPT_DTMF_EXIT | OPT_GO_ON | OPT_ORIGINAL_CLID | OPT_CALLER_HANGUP | OPT_IGNORE_FORWARDING);
+
+ /* Create datastore for channel dial features for caller */
+ if (!(ds_caller_features = ast_channel_datastore_alloc(&dial_features_info, NULL))) {
+ ast_log(LOG_WARNING, "Unable to create channel datastore for dial features. Aborting!\n");
+ goto out;
+ }
+
+ if (!(caller_features = ast_calloc(1, sizeof(*caller_features)))) {
+ ast_log(LOG_WARNING, "Unable to allocate memory for feature flags. Aborting!\n");
+ goto out;
+ }
+
+ ast_channel_lock(chan);
+ caller_features->is_caller = 1;
+ set_dial_features(&opts, caller_features);
+ ds_caller_features->inheritance = -1;
+ ds_caller_features->data = caller_features;
+ ast_channel_datastore_add(chan, ds_caller_features);
+ ast_channel_unlock(chan);
+
+ /* loop through the list of dial destinations */
+ rest = args.peers;
+ while ((cur = strsep(&rest, "&")) ) {
+ struct dial_localuser *tmp;
+ /* Get a technology/[device:]number pair */
+ char *number = cur;
+ char *interface = ast_strdupa(number);
+ char *tech = strsep(&number, "/");
+ /* find if we already dialed this interface */
+ struct ast_dialed_interface *di;
+ struct ast_dial_features *callee_features;
+ AST_LIST_HEAD(, ast_dialed_interface) *dialed_interfaces;
+ num_dialed++;
+ if (!number) {
+ ast_log(LOG_WARNING, "Dial argument takes format (technology/[device:]number1)\n");
+ goto out;
+ }
+ if (!(tmp = ast_calloc(1, sizeof(*tmp))))
+ goto out;
+ if (opts.flags) {
+ ast_copy_flags(tmp, &opts,
+ OPT_CALLEE_TRANSFER | OPT_CALLER_TRANSFER |
+ OPT_CALLEE_HANGUP | OPT_CALLER_HANGUP |
+ OPT_CALLEE_MONITOR | OPT_CALLER_MONITOR |
+ OPT_CALLEE_PARK | OPT_CALLER_PARK |
+ OPT_RINGBACK | OPT_MUSICBACK | OPT_FORCECLID);
+ ast_set2_flag(tmp, args.url, DIAL_NOFORWARDHTML);
+ }
+ ast_copy_string(numsubst, number, sizeof(numsubst));
+ /* Request the peer */
+
+ ast_channel_lock(chan);
+ datastore = ast_channel_datastore_find(chan, &dialed_interface_info, NULL);
+ ast_channel_unlock(chan);
+
+ if (datastore)
+ dialed_interfaces = datastore->data;
+ else {
+ if (!(datastore = ast_channel_datastore_alloc(&dialed_interface_info, NULL))) {
+ ast_log(LOG_WARNING, "Unable to create channel datastore for dialed interfaces. Aborting!\n");
+ free(tmp);
+ goto out;
+ }
+
+ datastore->inheritance = DATASTORE_INHERIT_FOREVER;
+
+ if (!(dialed_interfaces = ast_calloc(1, sizeof(*dialed_interfaces)))) {
+ free(tmp);
+ goto out;
+ }
+
+ datastore->data = dialed_interfaces;
+ AST_LIST_HEAD_INIT(dialed_interfaces);
+
+ ast_channel_lock(chan);
+ ast_channel_datastore_add(chan, datastore);
+ ast_channel_unlock(chan);
+ }
+
+ AST_LIST_LOCK(dialed_interfaces);
+ AST_LIST_TRAVERSE(dialed_interfaces, di, list) {
+ if (!strcasecmp(di->interface, interface)) {
+ ast_log(LOG_WARNING, "Skipping dialing interface '%s' again since it has already been dialed\n",
+ di->interface);
+ break;
+ }
+ }
+ AST_LIST_UNLOCK(dialed_interfaces);
+
+ if (di) {
+ fulldial++;
+ free(tmp);
+ continue;
+ }
+
+ /* It is always ok to dial a Local interface. We only keep track of
+ * which "real" interfaces have been dialed. The Local channel will
+ * inherit this list so that if it ends up dialing a real interface,
+ * it won't call one that has already been called. */
+ if (strcasecmp(tech, "Local")) {
+ if (!(di = ast_calloc(1, sizeof(*di) + strlen(interface)))) {
+ AST_LIST_UNLOCK(dialed_interfaces);
+ free(tmp);
+ goto out;
+ }
+ strcpy(di->interface, interface);
+
+ AST_LIST_LOCK(dialed_interfaces);
+ AST_LIST_INSERT_TAIL(dialed_interfaces, di, list);
+ AST_LIST_UNLOCK(dialed_interfaces);
+ }
+
+ tmp->chan = ast_request(tech, chan->nativeformats, numsubst, &cause);
+ if (!tmp->chan) {
+ /* If we can't, just go on to the next call */
+ ast_log(LOG_WARNING, "Unable to create channel of type '%s' (cause %d - %s)\n", tech, cause, ast_cause2str(cause));
+ HANDLE_CAUSE(cause, chan);
+ if (!rest) /* we are on the last destination */
+ chan->hangupcause = cause;
+ free(tmp);
+ continue;
+ }
+
+ pbx_builtin_setvar_helper(tmp->chan, "DIALEDPEERNUMBER", numsubst);
+
+ /* Setup outgoing SDP to match incoming one */
+ ast_rtp_make_compatible(tmp->chan, chan, !outgoing && !rest);
+
+ /* Inherit specially named variables from parent channel */
+ ast_channel_inherit_variables(chan, tmp->chan);
+
+ tmp->chan->appl = "AppDial";
+ tmp->chan->data = "(Outgoing Line)";
+ tmp->chan->whentohangup = 0;
+
+ if (tmp->chan->cid.cid_num)
+ free(tmp->chan->cid.cid_num);
+ tmp->chan->cid.cid_num = ast_strdup(chan->cid.cid_num);
+
+ if (tmp->chan->cid.cid_name)
+ free(tmp->chan->cid.cid_name);
+ tmp->chan->cid.cid_name = ast_strdup(chan->cid.cid_name);
+
+ if (tmp->chan->cid.cid_ani)
+ free(tmp->chan->cid.cid_ani);
+ tmp->chan->cid.cid_ani = ast_strdup(chan->cid.cid_ani);
+
+ /* Copy language from incoming to outgoing */
+ ast_string_field_set(tmp->chan, language, chan->language);
+ ast_string_field_set(tmp->chan, accountcode, chan->accountcode);
+ tmp->chan->cdrflags = chan->cdrflags;
+ if (ast_strlen_zero(tmp->chan->musicclass))
+ ast_string_field_set(tmp->chan, musicclass, chan->musicclass);
+ /* XXX don't we free previous values ? */
+ tmp->chan->cid.cid_rdnis = ast_strdup(chan->cid.cid_rdnis);
+ /* Pass callingpres setting */
+ tmp->chan->cid.cid_pres = chan->cid.cid_pres;
+ /* Pass type of number */
+ tmp->chan->cid.cid_ton = chan->cid.cid_ton;
+ /* Pass type of tns */
+ tmp->chan->cid.cid_tns = chan->cid.cid_tns;
+ /* Presense of ADSI CPE on outgoing channel follows ours */
+ tmp->chan->adsicpe = chan->adsicpe;
+ /* Pass the transfer capability */
+ tmp->chan->transfercapability = chan->transfercapability;
+
+ /* If we have an outbound group, set this peer channel to it */
+ if (outbound_group)
+ ast_app_group_set_channel(tmp->chan, outbound_group);
+
+ /* Inherit context and extension */
+ if (!ast_strlen_zero(chan->macrocontext))
+ ast_copy_string(tmp->chan->dialcontext, chan->macrocontext, sizeof(tmp->chan->dialcontext));
+ else
+ ast_copy_string(tmp->chan->dialcontext, chan->context, sizeof(tmp->chan->dialcontext));
+ if (!ast_strlen_zero(chan->macroexten))
+ ast_copy_string(tmp->chan->exten, chan->macroexten, sizeof(tmp->chan->exten));
+ else
+ ast_copy_string(tmp->chan->exten, chan->exten, sizeof(tmp->chan->exten));
+
+ /* Save callee features */
+ if (!(ds_callee_features = ast_channel_datastore_alloc(&dial_features_info, NULL))) {
+ ast_log(LOG_WARNING, "Unable to create channel datastore for dial features. Aborting!\n");
+ ast_free(tmp);
+ goto out;
+ }
+
+ if (!(callee_features = ast_calloc(1, sizeof(*callee_features)))) {
+ ast_log(LOG_WARNING, "Unable to allocate memory for feature flags. Aborting!\n");
+ ast_free(tmp);
+ goto out;
+ }
+
+ ast_channel_lock(tmp->chan);
+ callee_features->is_caller = 0;
+ set_dial_features(&opts, callee_features);
+ ds_callee_features->inheritance = -1;
+ ds_callee_features->data = callee_features;
+ ast_channel_datastore_add(tmp->chan, ds_callee_features);
+ ast_channel_unlock(tmp->chan);
+
+ /* Place the call, but don't wait on the answer */
+ res = ast_call(tmp->chan, numsubst, 0);
+
+ /* Save the info in cdr's that we called them */
+ if (chan->cdr)
+ ast_cdr_setdestchan(chan->cdr, tmp->chan->name);
+
+ /* check the results of ast_call */
+ if (res) {
+ /* Again, keep going even if there's an error */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "ast call on peer returned %d\n", res);
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Couldn't call %s\n", numsubst);
+ if (tmp->chan->hangupcause) {
+ chan->hangupcause = tmp->chan->hangupcause;
+ }
+ ast_hangup(tmp->chan);
+ tmp->chan = NULL;
+ free(tmp);
+ continue;
+ } else {
+ senddialevent(chan, tmp->chan);
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Called %s\n", numsubst);
+ if (!ast_test_flag(peerflags, OPT_ORIGINAL_CLID))
+ ast_set_callerid(tmp->chan, S_OR(chan->macroexten, chan->exten), get_cid_name(cidname, sizeof(cidname), chan), NULL);
+ }
+ /* Put them in the list of outgoing thingies... We're ready now.
+ XXX If we're forcibly removed, these outgoing calls won't get
+ hung up XXX */
+ ast_set_flag(tmp, DIAL_STILLGOING);
+ tmp->next = outgoing;
+ outgoing = tmp;
+ /* If this line is up, don't try anybody else */
+ if (outgoing->chan->_state == AST_STATE_UP)
+ break;
+ }
+
+ if (ast_strlen_zero(args.timeout)) {
+ to = -1;
+ } else {
+ to = atoi(args.timeout);
+ if (to > 0)
+ to *= 1000;
+ else
+ ast_log(LOG_WARNING, "Invalid timeout specified: '%s'\n", args.timeout);
+ }
+
+ if (!outgoing) {
+ strcpy(status, "CHANUNAVAIL");
+ if(fulldial == num_dialed) {
+ res = -1;
+ goto out;
+ }
+ } else {
+ /* Our status will at least be NOANSWER */
+ strcpy(status, "NOANSWER");
+ if (ast_test_flag(outgoing, OPT_MUSICBACK)) {
+ moh = 1;
+ if (!ast_strlen_zero(opt_args[OPT_ARG_MUSICBACK])) {
+ char *original_moh = ast_strdupa(chan->musicclass);
+ ast_string_field_set(chan, musicclass, opt_args[OPT_ARG_MUSICBACK]);
+ ast_moh_start(chan, opt_args[OPT_ARG_MUSICBACK], NULL);
+ ast_string_field_set(chan, musicclass, original_moh);
+ } else {
+ ast_moh_start(chan, NULL, NULL);
+ }
+ ast_indicate(chan, AST_CONTROL_PROGRESS);
+ } else if (ast_test_flag(outgoing, OPT_RINGBACK)) {
+ ast_indicate(chan, AST_CONTROL_RINGING);
+ sentringing++;
+ }
+ }
+
+ time(&start_time);
+ peer = wait_for_answer(chan, outgoing, &to, peerflags, &sentringing, status, sizeof(status), numbusy, numnochan, numcongestion, ast_test_flag(&opts, OPT_PRIORITY_JUMP), &result);
+
+ /* The ast_channel_datastore_remove() function could fail here if the
+ * datastore was moved to another channel during a masquerade. If this is
+ * the case, don't free the datastore here because later, when the channel
+ * to which the datastore was moved hangs up, it will attempt to free this
+ * datastore again, causing a crash
+ */
+ if (!ast_channel_datastore_remove(chan, datastore))
+ ast_channel_datastore_free(datastore);
+ if (!peer) {
+ if (result) {
+ res = result;
+ } else if (to) { /* Musta gotten hung up */
+ res = -1;
+ } else { /* Nobody answered, next please? */
+ res = 0;
+ }
+ /* almost done, although the 'else' block is 400 lines */
+ } else {
+ const char *number;
+
+ strcpy(status, "ANSWER");
+ pbx_builtin_setvar_helper(chan, "DIALSTATUS", status);
+ /* Ah ha! Someone answered within the desired timeframe. Of course after this
+ we will always return with -1 so that it is hung up properly after the
+ conversation. */
+ hanguptree(outgoing, peer);
+ outgoing = NULL;
+ /* If appropriate, log that we have a destination channel */
+ if (chan->cdr)
+ ast_cdr_setdestchan(chan->cdr, peer->name);
+ if (peer->name)
+ pbx_builtin_setvar_helper(chan, "DIALEDPEERNAME", peer->name);
+
+ number = pbx_builtin_getvar_helper(peer, "DIALEDPEERNUMBER");
+ if (!number)
+ number = numsubst;
+ pbx_builtin_setvar_helper(chan, "DIALEDPEERNUMBER", number);
+ if (!ast_strlen_zero(args.url) && ast_channel_supports_html(peer) ) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "app_dial: sendurl=%s.\n", args.url);
+ ast_channel_sendurl( peer, args.url );
+ }
+ if ( (ast_test_flag(&opts, OPT_PRIVACY) || ast_test_flag(&opts, OPT_SCREENING)) && privdb_val == AST_PRIVACY_UNKNOWN) {
+ int res2;
+ int loopcount = 0;
+
+ /* Get the user's intro, store it in priv-callerintros/$CID,
+ unless it is already there-- this should be done before the
+ call is actually dialed */
+
+ /* all ring indications and moh for the caller has been halted as soon as the
+ target extension was picked up. We are going to have to kill some
+ time and make the caller believe the peer hasn't picked up yet */
+
+ if (ast_test_flag(&opts, OPT_MUSICBACK) && !ast_strlen_zero(opt_args[OPT_ARG_MUSICBACK])) {
+ char *original_moh = ast_strdupa(chan->musicclass);
+ ast_indicate(chan, -1);
+ ast_string_field_set(chan, musicclass, opt_args[OPT_ARG_MUSICBACK]);
+ ast_moh_start(chan, opt_args[OPT_ARG_MUSICBACK], NULL);
+ ast_string_field_set(chan, musicclass, original_moh);
+ } else if (ast_test_flag(&opts, OPT_RINGBACK)) {
+ ast_indicate(chan, AST_CONTROL_RINGING);
+ sentringing++;
+ }
+
+ /* Start autoservice on the other chan ?? */
+ res2 = ast_autoservice_start(chan);
+ /* Now Stream the File */
+ for (loopcount = 0; loopcount < 3; loopcount++) {
+ if (res2 && loopcount == 0) /* error in ast_autoservice_start() */
+ break;
+ if (!res2) /* on timeout, play the message again */
+ res2 = ast_play_and_wait(peer,"priv-callpending");
+ if (!valid_priv_reply(&opts, res2))
+ res2 = 0;
+ /* priv-callpending script:
+ "I have a caller waiting, who introduces themselves as:"
+ */
+ if (!res2)
+ res2 = ast_play_and_wait(peer,privintro);
+ if (!valid_priv_reply(&opts, res2))
+ res2 = 0;
+ /* now get input from the called party, as to their choice */
+ if( !res2 ) {
+ /* XXX can we have both, or they are mutually exclusive ? */
+ if( ast_test_flag(&opts, OPT_PRIVACY) )
+ res2 = ast_play_and_wait(peer,"priv-callee-options");
+ if( ast_test_flag(&opts, OPT_SCREENING) )
+ res2 = ast_play_and_wait(peer,"screen-callee-options");
+ }
+ /*! \page DialPrivacy Dial Privacy scripts
+ \par priv-callee-options script:
+ "Dial 1 if you wish this caller to reach you directly in the future,
+ and immediately connect to their incoming call
+ Dial 2 if you wish to send this caller to voicemail now and
+ forevermore.
+ Dial 3 to send this caller to the torture menus, now and forevermore.
+ Dial 4 to send this caller to a simple "go away" menu, now and forevermore.
+ Dial 5 to allow this caller to come straight thru to you in the future,
+ but right now, just this once, send them to voicemail."
+ \par screen-callee-options script:
+ "Dial 1 if you wish to immediately connect to the incoming call
+ Dial 2 if you wish to send this caller to voicemail.
+ Dial 3 to send this caller to the torture menus.
+ Dial 4 to send this caller to a simple "go away" menu.
+ */
+ if (valid_priv_reply(&opts, res2))
+ break;
+ /* invalid option */
+ res2 = ast_play_and_wait(peer, "vm-sorry");
+ }
+
+ if (ast_test_flag(&opts, OPT_MUSICBACK)) {
+ ast_moh_stop(chan);
+ } else if (ast_test_flag(&opts, OPT_RINGBACK)) {
+ ast_indicate(chan, -1);
+ sentringing=0;
+ }
+ ast_autoservice_stop(chan);
+
+ switch (res2) {
+ case '1':
+ if( ast_test_flag(&opts, OPT_PRIVACY) ) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "--Set privacy database entry %s/%s to ALLOW\n",
+ opt_args[OPT_ARG_PRIVACY], privcid);
+ ast_privacy_set(opt_args[OPT_ARG_PRIVACY], privcid, AST_PRIVACY_ALLOW);
+ }
+ break;
+ case '2':
+ if( ast_test_flag(&opts, OPT_PRIVACY) ) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "--Set privacy database entry %s/%s to DENY\n",
+ opt_args[OPT_ARG_PRIVACY], privcid);
+ ast_privacy_set(opt_args[OPT_ARG_PRIVACY], privcid, AST_PRIVACY_DENY);
+ }
+ ast_copy_string(status, "NOANSWER", sizeof(status));
+ ast_hangup(peer); /* hang up on the callee -- he didn't want to talk anyway! */
+ res=0;
+ goto out;
+ case '3':
+ if( ast_test_flag(&opts, OPT_PRIVACY) ) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "--Set privacy database entry %s/%s to TORTURE\n",
+ opt_args[OPT_ARG_PRIVACY], privcid);
+ ast_privacy_set(opt_args[OPT_ARG_PRIVACY], privcid, AST_PRIVACY_TORTURE);
+ }
+ ast_copy_string(status, "TORTURE", sizeof(status));
+
+ res = 0;
+ ast_hangup(peer); /* hang up on the caller -- he didn't want to talk anyway! */
+ goto out; /* Is this right? */
+ case '4':
+ if( ast_test_flag(&opts, OPT_PRIVACY) ) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "--Set privacy database entry %s/%s to KILL\n",
+ opt_args[OPT_ARG_PRIVACY], privcid);
+ ast_privacy_set(opt_args[OPT_ARG_PRIVACY], privcid, AST_PRIVACY_KILL);
+ }
+
+ ast_copy_string(status, "DONTCALL", sizeof(status));
+ res = 0;
+ ast_hangup(peer); /* hang up on the caller -- he didn't want to talk anyway! */
+ goto out; /* Is this right? */
+ case '5':
+ if( ast_test_flag(&opts, OPT_PRIVACY) ) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "--Set privacy database entry %s/%s to ALLOW\n",
+ opt_args[OPT_ARG_PRIVACY], privcid);
+ ast_privacy_set(opt_args[OPT_ARG_PRIVACY], privcid, AST_PRIVACY_ALLOW);
+ ast_hangup(peer); /* hang up on the caller -- he didn't want to talk anyway! */
+ res=0;
+ goto out;
+ } /* if not privacy, then 5 is the same as "default" case */
+ default: /* bad input or -1 if failure to start autoservice */
+ /* well, if the user messes up, ... he had his chance... What Is The Best Thing To Do? */
+ /* well, there seems basically two choices. Just patch the caller thru immediately,
+ or,... put 'em thru to voicemail. */
+ /* since the callee may have hung up, let's do the voicemail thing, no database decision */
+ ast_log(LOG_NOTICE, "privacy: no valid response from the callee. Sending the caller to voicemail, the callee isn't responding\n");
+ ast_hangup(peer); /* hang up on the callee -- he didn't want to talk anyway! */
+ res=0;
+ goto out;
+ }
+
+ /* XXX once again, this path is only taken in the case '1', so it could be
+ * moved there, although i am not really sure that this is correct - maybe
+ * the check applies to other cases as well.
+ */
+ /* if the intro is NOCALLERID, then there's no reason to leave it on disk, it'll
+ just clog things up, and it's not useful information, not being tied to a CID */
+ if( strncmp(privcid,"NOCALLERID",10) == 0 || ast_test_flag(&opts, OPT_SCREEN_NOINTRO) ) {
+ ast_filedelete(privintro, NULL);
+ if( ast_fileexists(privintro, NULL, NULL ) > 0 )
+ ast_log(LOG_NOTICE, "privacy: ast_filedelete didn't do its job on %s\n", privintro);
+ else if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Successfully deleted %s intro file\n", privintro);
+ }
+ }
+ if (!ast_test_flag(&opts, OPT_ANNOUNCE) || ast_strlen_zero(opt_args[OPT_ARG_ANNOUNCE])) {
+ res = 0;
+ } else {
+ int digit = 0;
+ /* Start autoservice on the other chan */
+ res = ast_autoservice_start(chan);
+ /* Now Stream the File */
+ if (!res)
+ res = ast_streamfile(peer, opt_args[OPT_ARG_ANNOUNCE], peer->language);
+ if (!res) {
+ digit = ast_waitstream(peer, AST_DIGIT_ANY);
+ }
+ /* Ok, done. stop autoservice */
+ res = ast_autoservice_stop(chan);
+ if (digit > 0 && !res)
+ res = ast_senddigit(chan, digit);
+ else
+ res = digit;
+
+ }
+
+ if (chan && peer && ast_test_flag(&opts, OPT_GOTO) && !ast_strlen_zero(opt_args[OPT_ARG_GOTO])) {
+ replace_macro_delimiter(opt_args[OPT_ARG_GOTO]);
+ ast_parseable_goto(chan, opt_args[OPT_ARG_GOTO]);
+ /* peer goes to the same context and extension as chan, so just copy info from chan*/
+ ast_copy_string(peer->context, chan->context, sizeof(peer->context));
+ ast_copy_string(peer->exten, chan->exten, sizeof(peer->exten));
+ peer->priority = chan->priority + 2;
+ ast_pbx_start(peer);
+ hanguptree(outgoing, NULL);
+ if (continue_exec)
+ *continue_exec = 1;
+ res = 0;
+ goto done;
+ }
+
+ if (ast_test_flag(&opts, OPT_CALLEE_MACRO) && !ast_strlen_zero(opt_args[OPT_ARG_CALLEE_MACRO])) {
+ struct ast_app *theapp;
+ const char *macro_result;
+
+ res = ast_autoservice_start(chan);
+ if (res) {
+ ast_log(LOG_ERROR, "Unable to start autoservice on calling channel\n");
+ res = -1;
+ }
+
+ theapp = pbx_findapp("Macro");
+
+ if (theapp && !res) { /* XXX why check res here ? */
+ replace_macro_delimiter(opt_args[OPT_ARG_CALLEE_MACRO]);
+ res = pbx_exec(peer, theapp, opt_args[OPT_ARG_CALLEE_MACRO]);
+ ast_log(LOG_DEBUG, "Macro exited with status %d\n", res);
+ res = 0;
+ } else {
+ ast_log(LOG_ERROR, "Could not find application Macro\n");
+ res = -1;
+ }
+
+ if (ast_autoservice_stop(chan) < 0) {
+ ast_log(LOG_ERROR, "Could not stop autoservice on calling channel\n");
+ res = -1;
+ }
+
+ if (!res && (macro_result = pbx_builtin_getvar_helper(peer, "MACRO_RESULT"))) {
+ char *macro_transfer_dest;
+
+ if (!strcasecmp(macro_result, "BUSY")) {
+ ast_copy_string(status, macro_result, sizeof(status));
+ if (ast_opt_priority_jumping || ast_test_flag(&opts, OPT_PRIORITY_JUMP)) {
+ if (!ast_goto_if_exists(chan, NULL, NULL, chan->priority + 101)) {
+ ast_set_flag(peerflags, OPT_GO_ON);
+ }
+ } else
+ ast_set_flag(peerflags, OPT_GO_ON);
+ res = -1;
+ } else if (!strcasecmp(macro_result, "CONGESTION") || !strcasecmp(macro_result, "CHANUNAVAIL")) {
+ ast_copy_string(status, macro_result, sizeof(status));
+ ast_set_flag(peerflags, OPT_GO_ON);
+ res = -1;
+ } else if (!strcasecmp(macro_result, "CONTINUE")) {
+ /* hangup peer and keep chan alive assuming the macro has changed
+ the context / exten / priority or perhaps
+ the next priority in the current exten is desired.
+ */
+ ast_set_flag(peerflags, OPT_GO_ON);
+ res = -1;
+ } else if (!strcasecmp(macro_result, "ABORT")) {
+ /* Hangup both ends unless the caller has the g flag */
+ res = -1;
+ } else if (!strncasecmp(macro_result, "GOTO:", 5) && (macro_transfer_dest = ast_strdupa(macro_result + 5))) {
+ res = -1;
+ /* perform a transfer to a new extension */
+ if (strchr(macro_transfer_dest, '^')) { /* context^exten^priority*/
+ replace_macro_delimiter(macro_transfer_dest);
+ if (!ast_parseable_goto(chan, macro_transfer_dest))
+ ast_set_flag(peerflags, OPT_GO_ON);
+
+ }
+ }
+ }
+ }
+
+ if (!res) {
+ if (calldurationlimit > 0) {
+ peer->whentohangup = time(NULL) + calldurationlimit;
+ } else if (calldurationlimit != -1 && timelimit > 0) {
+ /* Not enough granularity to make it less, but we can't use the special value 0 */
+ peer->whentohangup = time(NULL) + 1;
+ }
+ if (!ast_strlen_zero(dtmfcalled)) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Sending DTMF '%s' to the called party.\n", dtmfcalled);
+ res = ast_dtmf_stream(peer,chan,dtmfcalled,250);
+ }
+ if (!ast_strlen_zero(dtmfcalling)) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Sending DTMF '%s' to the calling party.\n", dtmfcalling);
+ res = ast_dtmf_stream(chan,peer,dtmfcalling,250);
+ }
+ }
+
+ if (!res) {
+ struct ast_bridge_config config;
+
+ memset(&config,0,sizeof(struct ast_bridge_config));
+ if (play_to_caller)
+ ast_set_flag(&(config.features_caller), AST_FEATURE_PLAY_WARNING);
+ if (play_to_callee)
+ ast_set_flag(&(config.features_callee), AST_FEATURE_PLAY_WARNING);
+ if (ast_test_flag(peerflags, OPT_CALLEE_TRANSFER))
+ ast_set_flag(&(config.features_callee), AST_FEATURE_REDIRECT);
+ if (ast_test_flag(peerflags, OPT_CALLER_TRANSFER))
+ ast_set_flag(&(config.features_caller), AST_FEATURE_REDIRECT);
+ if (ast_test_flag(peerflags, OPT_CALLEE_HANGUP))
+ ast_set_flag(&(config.features_callee), AST_FEATURE_DISCONNECT);
+ if (ast_test_flag(peerflags, OPT_CALLER_HANGUP))
+ ast_set_flag(&(config.features_caller), AST_FEATURE_DISCONNECT);
+ if (ast_test_flag(peerflags, OPT_CALLEE_MONITOR))
+ ast_set_flag(&(config.features_callee), AST_FEATURE_AUTOMON);
+ if (ast_test_flag(peerflags, OPT_CALLER_MONITOR))
+ ast_set_flag(&(config.features_caller), AST_FEATURE_AUTOMON);
+ if (ast_test_flag(peerflags, OPT_CALLEE_PARK))
+ ast_set_flag(&(config.features_callee), AST_FEATURE_PARKCALL);
+ if (ast_test_flag(peerflags, OPT_CALLER_PARK))
+ ast_set_flag(&(config.features_caller), AST_FEATURE_PARKCALL);
+ if (ast_test_flag(peerflags, OPT_GO_ON))
+ ast_set_flag(&(config.features_caller), AST_FEATURE_NO_H_EXTEN);
+
+ config.timelimit = timelimit;
+ config.play_warning = play_warning;
+ config.warning_freq = warning_freq;
+ config.warning_sound = warning_sound;
+ config.end_sound = end_sound;
+ config.start_sound = start_sound;
+ config.end_bridge_callback = end_bridge_callback;
+ config.end_bridge_callback_data = chan;
+ config.end_bridge_callback_data_fixup = end_bridge_callback_data_fixup;
+ if (moh) {
+ moh = 0;
+ ast_moh_stop(chan);
+ } else if (sentringing) {
+ sentringing = 0;
+ ast_indicate(chan, -1);
+ }
+ /* Be sure no generators are left on it */
+ ast_deactivate_generator(chan);
+ /* Make sure channels are compatible */
+ res = ast_channel_make_compatible(chan, peer);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", chan->name, peer->name);
+ ast_hangup(peer);
+ res = -1;
+ goto done;
+ }
+ if (opermode &&
+ (((!strncasecmp(chan->name,"Zap",3)) && (!strncasecmp(peer->name,"Zap",3))) ||
+ ((!strncasecmp(chan->name,"Dahdi",5)) && (!strncasecmp(peer->name,"Dahdi",5)))))
+ {
+ struct oprmode oprmode;
+
+ oprmode.peer = peer;
+ oprmode.mode = opermode;
+
+ ast_channel_setoption(chan,
+ AST_OPTION_OPRMODE,&oprmode,sizeof(struct oprmode),0);
+ }
+ res = ast_bridge_call(chan,peer,&config);
+ } else {
+ res = -1;
+ }
+
+ if (!chan->_softhangup)
+ chan->hangupcause = peer->hangupcause;
+ ast_hangup(peer);
+ }
+out:
+ if (moh) {
+ moh = 0;
+ ast_moh_stop(chan);
+ } else if (sentringing) {
+ sentringing = 0;
+ ast_indicate(chan, -1);
+ }
+ ast_rtp_early_bridge(chan, NULL);
+ hanguptree(outgoing, NULL);
+ pbx_builtin_setvar_helper(chan, "DIALSTATUS", status);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Exiting with DIALSTATUS=%s.\n", status);
+
+ if (ast_test_flag(peerflags, OPT_GO_ON) && !chan->_softhangup) {
+ if (calldurationlimit)
+ chan->whentohangup = 0;
+ res = 0;
+ }
+done:
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int dial_exec(struct ast_channel *chan, void *data)
+{
+ struct ast_flags peerflags;
+
+ memset(&peerflags, 0, sizeof(peerflags));
+
+ return dial_exec_full(chan, data, &peerflags, NULL);
+}
+
+static int retrydial_exec(struct ast_channel *chan, void *data)
+{
+ char *announce = NULL, *dialdata = NULL;
+ const char *context = NULL;
+ int sleep = 0, loops = 0, res = -1;
+ struct ast_module_user *u;
+ struct ast_flags peerflags;
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "RetryDial requires an argument!\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ announce = ast_strdupa(data);
+
+ memset(&peerflags, 0, sizeof(peerflags));
+
+ if ((dialdata = strchr(announce, '|'))) {
+ *dialdata++ = '\0';
+ if (sscanf(dialdata, "%d", &sleep) == 1) {
+ sleep *= 1000;
+ } else {
+ ast_log(LOG_ERROR, "%s requires the numerical argument <sleep>\n",rapp);
+ goto done;
+ }
+ if ((dialdata = strchr(dialdata, '|'))) {
+ *dialdata++ = '\0';
+ if (sscanf(dialdata, "%d", &loops) != 1) {
+ ast_log(LOG_ERROR, "%s requires the numerical argument <loops>\n",rapp);
+ goto done;
+ }
+ }
+ }
+
+ if ((dialdata = strchr(dialdata, '|'))) {
+ *dialdata++ = '\0';
+ } else {
+ ast_log(LOG_ERROR, "%s requires more arguments\n",rapp);
+ goto done;
+ }
+
+ if (sleep < 1000)
+ sleep = 10000;
+
+ if (!loops)
+ loops = -1; /* run forever */
+
+ context = pbx_builtin_getvar_helper(chan, "EXITCONTEXT");
+
+ res = 0;
+ while (loops) {
+ int continue_exec;
+
+ chan->data = "Retrying";
+ if (ast_test_flag(chan, AST_FLAG_MOH))
+ ast_moh_stop(chan);
+
+ res = dial_exec_full(chan, dialdata, &peerflags, &continue_exec);
+ if (continue_exec)
+ break;
+
+ if (res == 0) {
+ if (ast_test_flag(&peerflags, OPT_DTMF_EXIT)) {
+ if (!ast_strlen_zero(announce)) {
+ if (ast_fileexists(announce, NULL, chan->language) > 0) {
+ if(!(res = ast_streamfile(chan, announce, chan->language)))
+ ast_waitstream(chan, AST_DIGIT_ANY);
+ } else
+ ast_log(LOG_WARNING, "Announce file \"%s\" specified in Retrydial does not exist\n", announce);
+ }
+ if (!res && sleep) {
+ if (!ast_test_flag(chan, AST_FLAG_MOH))
+ ast_moh_start(chan, NULL, NULL);
+ res = ast_waitfordigit(chan, sleep);
+ }
+ } else {
+ if (!ast_strlen_zero(announce)) {
+ if (ast_fileexists(announce, NULL, chan->language) > 0) {
+ if (!(res = ast_streamfile(chan, announce, chan->language)))
+ res = ast_waitstream(chan, "");
+ } else
+ ast_log(LOG_WARNING, "Announce file \"%s\" specified in Retrydial does not exist\n", announce);
+ }
+ if (sleep) {
+ if (!ast_test_flag(chan, AST_FLAG_MOH))
+ ast_moh_start(chan, NULL, NULL);
+ if (!res)
+ res = ast_waitfordigit(chan, sleep);
+ }
+ }
+ }
+
+ if (res < 0)
+ break;
+ else if (res > 0) { /* Trying to send the call elsewhere (1 digit ext) */
+ if (onedigit_goto(chan, context, (char) res, 1)) {
+ res = 0;
+ break;
+ }
+ }
+ loops--;
+ }
+ if (loops == 0)
+ res = 0;
+ else if (res == 1)
+ res = 0;
+
+ if (ast_test_flag(chan, AST_FLAG_MOH))
+ ast_moh_stop(chan);
+ done:
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+ res |= ast_unregister_application(rapp);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ int res;
+
+ res = ast_register_application(app, dial_exec, synopsis, descrip);
+ res |= ast_register_application(rapp, retrydial_exec, rsynopsis, rdescrip);
+
+ return res;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Dialing Application");
diff --git a/apps/app_dictate.c b/apps/app_dictate.c
new file mode 100644
index 000000000..7db747e12
--- /dev/null
+++ b/apps/app_dictate.c
@@ -0,0 +1,349 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2005, Anthony Minessale II
+ *
+ * Anthony Minessale II <anthmct@yahoo.com>
+ *
+ * Donated by Sangoma Technologies <http://www.samgoma.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 Virtual Dictation Machine Application For Asterisk
+ *
+ * \author Anthony Minessale II <anthmct@yahoo.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/say.h"
+#include "asterisk/lock.h"
+#include "asterisk/app.h"
+
+static char *app = "Dictate";
+static char *synopsis = "Virtual Dictation Machine";
+static char *desc = " Dictate([<base_dir>[|<filename>]])\n"
+"Start dictation machine using optional base dir for files.\n";
+
+
+typedef enum {
+ DFLAG_RECORD = (1 << 0),
+ DFLAG_PLAY = (1 << 1),
+ DFLAG_TRUNC = (1 << 2),
+ DFLAG_PAUSE = (1 << 3),
+} dflags;
+
+typedef enum {
+ DMODE_INIT,
+ DMODE_RECORD,
+ DMODE_PLAY
+} dmodes;
+
+#define ast_toggle_flag(it,flag) if(ast_test_flag(it, flag)) ast_clear_flag(it, flag); else ast_set_flag(it, flag)
+
+static int play_and_wait(struct ast_channel *chan, char *file, char *digits)
+{
+ int res = -1;
+ if (!ast_streamfile(chan, file, chan->language)) {
+ res = ast_waitstream(chan, digits);
+ }
+ return res;
+}
+
+static int dictate_exec(struct ast_channel *chan, void *data)
+{
+ char *path = NULL, filein[256], *filename = "";
+ char *parse;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(base);
+ AST_APP_ARG(filename);
+ );
+ char dftbase[256];
+ char *base;
+ struct ast_flags flags = {0};
+ struct ast_filestream *fs;
+ struct ast_frame *f = NULL;
+ struct ast_module_user *u;
+ int ffactor = 320 * 80,
+ res = 0,
+ done = 0,
+ oldr = 0,
+ lastop = 0,
+ samples = 0,
+ speed = 1,
+ digit = 0,
+ len = 0,
+ maxlen = 0,
+ mode = 0;
+
+ u = ast_module_user_add(chan);
+
+ snprintf(dftbase, sizeof(dftbase), "%s/dictate", ast_config_AST_SPOOL_DIR);
+ if (!ast_strlen_zero(data)) {
+ parse = ast_strdupa(data);
+ AST_STANDARD_APP_ARGS(args, parse);
+ } else
+ args.argc = 0;
+
+ if (args.argc && !ast_strlen_zero(args.base)) {
+ base = args.base;
+ } else {
+ base = dftbase;
+ }
+ if (args.argc > 1 && args.filename) {
+ filename = args.filename;
+ }
+ oldr = chan->readformat;
+ if ((res = ast_set_read_format(chan, AST_FORMAT_SLINEAR)) < 0) {
+ ast_log(LOG_WARNING, "Unable to set to linear mode.\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ ast_answer(chan);
+ ast_safe_sleep(chan, 200);
+ for (res = 0; !res;) {
+ if (ast_strlen_zero(filename)) {
+ if (ast_app_getdata(chan, "dictate/enter_filename", filein, sizeof(filein), 0) ||
+ ast_strlen_zero(filein)) {
+ res = -1;
+ break;
+ }
+ } else {
+ ast_copy_string(filein, filename, sizeof(filein));
+ filename = "";
+ }
+ mkdir(base, 0755);
+ len = strlen(base) + strlen(filein) + 2;
+ if (!path || len > maxlen) {
+ path = alloca(len);
+ memset(path, 0, len);
+ maxlen = len;
+ } else {
+ memset(path, 0, maxlen);
+ }
+
+ snprintf(path, len, "%s/%s", base, filein);
+ fs = ast_writefile(path, "raw", NULL, O_CREAT|O_APPEND, 0, 0700);
+ mode = DMODE_PLAY;
+ memset(&flags, 0, sizeof(flags));
+ ast_set_flag(&flags, DFLAG_PAUSE);
+ digit = play_and_wait(chan, "dictate/forhelp", AST_DIGIT_ANY);
+ done = 0;
+ speed = 1;
+ res = 0;
+ lastop = 0;
+ samples = 0;
+ while (!done && ((res = ast_waitfor(chan, -1)) > -1) && fs && (f = ast_read(chan))) {
+ if (digit) {
+ struct ast_frame fr = {AST_FRAME_DTMF, digit};
+ ast_queue_frame(chan, &fr);
+ digit = 0;
+ }
+ if ((f->frametype == AST_FRAME_DTMF)) {
+ int got = 1;
+ switch(mode) {
+ case DMODE_PLAY:
+ switch(f->subclass) {
+ case '1':
+ ast_set_flag(&flags, DFLAG_PAUSE);
+ mode = DMODE_RECORD;
+ break;
+ case '2':
+ speed++;
+ if (speed > 4) {
+ speed = 1;
+ }
+ res = ast_say_number(chan, speed, AST_DIGIT_ANY, chan->language, (char *) NULL);
+ break;
+ case '7':
+ samples -= ffactor;
+ if(samples < 0) {
+ samples = 0;
+ }
+ ast_seekstream(fs, samples, SEEK_SET);
+ break;
+ case '8':
+ samples += ffactor;
+ ast_seekstream(fs, samples, SEEK_SET);
+ break;
+
+ default:
+ got = 0;
+ }
+ break;
+ case DMODE_RECORD:
+ switch(f->subclass) {
+ case '1':
+ ast_set_flag(&flags, DFLAG_PAUSE);
+ mode = DMODE_PLAY;
+ break;
+ case '8':
+ ast_toggle_flag(&flags, DFLAG_TRUNC);
+ lastop = 0;
+ break;
+ default:
+ got = 0;
+ }
+ break;
+ default:
+ got = 0;
+ }
+ if (!got) {
+ switch(f->subclass) {
+ case '#':
+ done = 1;
+ continue;
+ break;
+ case '*':
+ ast_toggle_flag(&flags, DFLAG_PAUSE);
+ if (ast_test_flag(&flags, DFLAG_PAUSE)) {
+ digit = play_and_wait(chan, "dictate/pause", AST_DIGIT_ANY);
+ } else {
+ digit = play_and_wait(chan, mode == DMODE_PLAY ? "dictate/playback" : "dictate/record", AST_DIGIT_ANY);
+ }
+ break;
+ case '0':
+ ast_set_flag(&flags, DFLAG_PAUSE);
+ digit = play_and_wait(chan, "dictate/paused", AST_DIGIT_ANY);
+ switch(mode) {
+ case DMODE_PLAY:
+ digit = play_and_wait(chan, "dictate/play_help", AST_DIGIT_ANY);
+ break;
+ case DMODE_RECORD:
+ digit = play_and_wait(chan, "dictate/record_help", AST_DIGIT_ANY);
+ break;
+ }
+ if (digit == 0) {
+ digit = play_and_wait(chan, "dictate/both_help", AST_DIGIT_ANY);
+ } else if (digit < 0) {
+ done = 1;
+ break;
+ }
+ break;
+ }
+ }
+
+ } else if (f->frametype == AST_FRAME_VOICE) {
+ switch(mode) {
+ struct ast_frame *fr;
+ int x;
+ case DMODE_PLAY:
+ if (lastop != DMODE_PLAY) {
+ if (ast_test_flag(&flags, DFLAG_PAUSE)) {
+ digit = play_and_wait(chan, "dictate/playback_mode", AST_DIGIT_ANY);
+ if (digit == 0) {
+ digit = play_and_wait(chan, "dictate/paused", AST_DIGIT_ANY);
+ } else if (digit < 0) {
+ break;
+ }
+ }
+ if (lastop != DFLAG_PLAY) {
+ lastop = DFLAG_PLAY;
+ ast_closestream(fs);
+ if (!(fs = ast_openstream(chan, path, chan->language)))
+ break;
+ ast_seekstream(fs, samples, SEEK_SET);
+ chan->stream = NULL;
+ }
+ lastop = DMODE_PLAY;
+ }
+
+ if (!ast_test_flag(&flags, DFLAG_PAUSE)) {
+ for (x = 0; x < speed; x++) {
+ if ((fr = ast_readframe(fs))) {
+ ast_write(chan, fr);
+ samples += fr->samples;
+ ast_frfree(fr);
+ fr = NULL;
+ } else {
+ samples = 0;
+ ast_seekstream(fs, 0, SEEK_SET);
+ }
+ }
+ }
+ break;
+ case DMODE_RECORD:
+ if (lastop != DMODE_RECORD) {
+ int oflags = O_CREAT | O_WRONLY;
+ if (ast_test_flag(&flags, DFLAG_PAUSE)) {
+ digit = play_and_wait(chan, "dictate/record_mode", AST_DIGIT_ANY);
+ if (digit == 0) {
+ digit = play_and_wait(chan, "dictate/paused", AST_DIGIT_ANY);
+ } else if (digit < 0) {
+ break;
+ }
+ }
+ lastop = DMODE_RECORD;
+ ast_closestream(fs);
+ if ( ast_test_flag(&flags, DFLAG_TRUNC)) {
+ oflags |= O_TRUNC;
+ digit = play_and_wait(chan, "dictate/truncating_audio", AST_DIGIT_ANY);
+ } else {
+ oflags |= O_APPEND;
+ }
+ fs = ast_writefile(path, "raw", NULL, oflags, 0, 0700);
+ if (ast_test_flag(&flags, DFLAG_TRUNC)) {
+ ast_seekstream(fs, 0, SEEK_SET);
+ ast_clear_flag(&flags, DFLAG_TRUNC);
+ } else {
+ ast_seekstream(fs, 0, SEEK_END);
+ }
+ }
+ if (!ast_test_flag(&flags, DFLAG_PAUSE)) {
+ res = ast_writestream(fs, f);
+ }
+ break;
+ }
+
+ }
+
+ ast_frfree(f);
+ }
+ }
+ if (oldr) {
+ ast_set_read_format(chan, oldr);
+ }
+ ast_module_user_remove(u);
+ return 0;
+}
+
+static int unload_module(void)
+{
+ int res;
+ res = ast_unregister_application(app);
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, dictate_exec, synopsis, desc);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Virtual Dictation Machine");
diff --git a/apps/app_directed_pickup.c b/apps/app_directed_pickup.c
new file mode 100644
index 000000000..68d965244
--- /dev/null
+++ b/apps/app_directed_pickup.c
@@ -0,0 +1,181 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2005, Joshua Colp
+ *
+ * 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 Directed Call Pickup Support
+ *
+ * \author Joshua Colp <jcolp@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/lock.h"
+#include "asterisk/app.h"
+#include "asterisk/options.h"
+
+#define PICKUPMARK "PICKUPMARK"
+
+static const char *app = "Pickup";
+static const char *synopsis = "Directed Call Pickup";
+static const char *descrip =
+" Pickup(extension[@context][&extension2@context...]): This application can pickup any ringing channel\n"
+"that is calling the specified extension. If no context is specified, the current\n"
+"context will be used. If you use the special string \"PICKUPMARK\" for the context parameter, for example\n"
+"10@PICKUPMARK, this application tries to find a channel which has defined a channel variable with the same content\n"
+"as \"extension\".";
+
+/* Perform actual pickup between two channels */
+static int pickup_do(struct ast_channel *chan, struct ast_channel *target)
+{
+ int res = 0;
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Call pickup on '%s' by '%s'\n", target->name, chan->name);
+
+ if ((res = ast_answer(chan))) {
+ ast_log(LOG_WARNING, "Unable to answer '%s'\n", chan->name);
+ return -1;
+ }
+
+ if ((res = ast_queue_control(chan, AST_CONTROL_ANSWER))) {
+ ast_log(LOG_WARNING, "Unable to queue answer on '%s'\n", chan->name);
+ return -1;
+ }
+
+ if ((res = ast_channel_masquerade(target, chan))) {
+ ast_log(LOG_WARNING, "Unable to masquerade '%s' into '%s'\n", chan->name, target->name);
+ return -1;
+ }
+
+ return res;
+}
+
+/* Helper function that determines whether a channel is capable of being picked up */
+static int can_pickup(struct ast_channel *chan)
+{
+ if (!chan->pbx && (chan->_state == AST_STATE_RINGING || chan->_state == AST_STATE_RING || chan->_state == AST_STATE_DOWN))
+ return 1;
+ else
+ return 0;
+}
+
+/* Attempt to pick up specified extension with context */
+static int pickup_by_exten(struct ast_channel *chan, char *exten, char *context)
+{
+ int res = -1;
+ struct ast_channel *target = NULL;
+
+ while ((target = ast_channel_walk_locked(target))) {
+ if ((!strcasecmp(target->macroexten, exten) || !strcasecmp(target->exten, exten)) &&
+ !strcasecmp(target->dialcontext, context) &&
+ can_pickup(target)) {
+ res = pickup_do(chan, target);
+ ast_channel_unlock(target);
+ break;
+ }
+ ast_channel_unlock(target);
+ }
+
+ return res;
+}
+
+/* Attempt to pick up specified mark */
+static int pickup_by_mark(struct ast_channel *chan, char *mark)
+{
+ int res = -1;
+ const char *tmp = NULL;
+ struct ast_channel *target = NULL;
+
+ while ((target = ast_channel_walk_locked(target))) {
+ if ((tmp = pbx_builtin_getvar_helper(target, PICKUPMARK)) &&
+ !strcasecmp(tmp, mark) &&
+ can_pickup(target)) {
+ res = pickup_do(chan, target);
+ ast_channel_unlock(target);
+ break;
+ }
+ ast_channel_unlock(target);
+ }
+
+ return res;
+}
+
+/* Main application entry point */
+static int pickup_exec(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ struct ast_module_user *u = NULL;
+ char *tmp = ast_strdupa(data);
+ char *exten = NULL, *context = NULL;
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "Pickup requires an argument (extension)!\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ /* Parse extension (and context if there) */
+ while (!ast_strlen_zero(tmp) && (exten = strsep(&tmp, "&"))) {
+ if ((context = strchr(exten, '@')))
+ *context++ = '\0';
+ if (context && !strcasecmp(context, PICKUPMARK)) {
+ if (!pickup_by_mark(chan, exten))
+ break;
+ } else {
+ if (!pickup_by_exten(chan, exten, context ? context : chan->context))
+ break;
+ }
+ ast_log(LOG_NOTICE, "No target channel found for %s.\n", exten);
+ }
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, pickup_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Directed Call Pickup Application");
diff --git a/apps/app_directory.c b/apps/app_directory.c
new file mode 100644
index 000000000..23d2b4d62
--- /dev/null
+++ b/apps/app_directory.c
@@ -0,0 +1,707 @@
+/*
+ * 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 Provide a directory of extensions
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/config.h"
+#include "asterisk/say.h"
+#include "asterisk/utils.h"
+#include "asterisk/app.h"
+
+#ifdef ODBC_STORAGE
+#include <errno.h>
+#include <sys/mman.h>
+#include "asterisk/res_odbc.h"
+
+static char odbc_database[80] = "asterisk";
+static char odbc_table[80] = "voicemessages";
+static char vmfmts[80] = "wav";
+#endif
+
+static char *app = "Directory";
+
+static char *synopsis = "Provide directory of voicemail extensions";
+static char *descrip =
+" Directory(vm-context[|dial-context[|options]]): This application will present\n"
+"the calling channel with a directory of extensions from which they can search\n"
+"by name. The list of names and corresponding extensions is retrieved from the\n"
+"voicemail configuration file, voicemail.conf.\n"
+" This application will immediately exit if one of the following DTMF digits are\n"
+"received and the extension to jump to exists:\n"
+" 0 - Jump to the 'o' extension, if it exists.\n"
+" * - Jump to the 'a' extension, if it exists.\n\n"
+" Parameters:\n"
+" vm-context - This is the context within voicemail.conf to use for the\n"
+" Directory.\n"
+" dial-context - This is the dialplan context to use when looking for an\n"
+" extension that the user has selected, or when jumping to the\n"
+" 'o' or 'a' extension.\n\n"
+" Options:\n"
+" e - In addition to the name, also read the extension number to the\n"
+" caller before presenting dialing options.\n"
+" f - Allow the caller to enter the first name of a user in the directory\n"
+" instead of using the last name.\n";
+
+/* For simplicity, I'm keeping the format compatible with the voicemail config,
+ but i'm open to suggestions for isolating it */
+
+#define VOICEMAIL_CONFIG "voicemail.conf"
+
+/* How many digits to read in */
+#define NUMDIGITS 3
+
+
+#ifdef ODBC_STORAGE
+struct generic_prepare_struct {
+ const char *sql;
+ const char *param;
+};
+
+static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
+{
+ struct generic_prepare_struct *gps = data;
+ SQLHSTMT stmt;
+ int res;
+
+ 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 *)gps->sql, SQL_NTS);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", (char *)gps->sql);
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ return NULL;
+ }
+
+ if (!ast_strlen_zero(gps->param))
+ SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->param), 0, (void *)gps->param, 0, NULL);
+
+ return stmt;
+}
+
+static void retrieve_file(char *dir)
+{
+ int x = 0;
+ int res;
+ int fd=-1;
+ size_t fdlen = 0;
+ void *fdm = MAP_FAILED;
+ SQLHSTMT stmt;
+ char sql[256];
+ char fmt[80]="", empty[10] = "";
+ char *c;
+ SQLLEN colsize;
+ char full_fn[256];
+ struct odbc_obj *obj;
+ struct generic_prepare_struct gps = { .sql = sql, .param = dir };
+
+ obj = ast_odbc_request_obj(odbc_database, 1);
+ if (obj) {
+ do {
+ ast_copy_string(fmt, vmfmts, sizeof(fmt));
+ c = strchr(fmt, '|');
+ if (c)
+ *c = '\0';
+ if (!strcasecmp(fmt, "wav49"))
+ strcpy(fmt, "WAV");
+ snprintf(full_fn, sizeof(full_fn), "%s.%s", dir, fmt);
+ snprintf(sql, sizeof(sql), "SELECT recording FROM %s WHERE dir=? AND msgnum=-1", odbc_table);
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+
+ if (!stmt) {
+ ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ break;
+ }
+ res = SQLFetch(stmt);
+ if (res == SQL_NO_DATA) {
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ break;
+ } else 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);
+ break;
+ }
+ fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, 0770);
+ if (fd < 0) {
+ ast_log(LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ break;
+ }
+
+ res = SQLGetData(stmt, 1, SQL_BINARY, empty, 0, &colsize);
+ fdlen = colsize;
+ if (fd > -1) {
+ char tmp[1]="";
+ lseek(fd, fdlen - 1, SEEK_SET);
+ if (write(fd, tmp, 1) != 1) {
+ close(fd);
+ fd = -1;
+ break;
+ }
+ if (fd > -1)
+ fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ }
+ if (fdm != MAP_FAILED) {
+ memset(fdm, 0, fdlen);
+ res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, fdlen, &colsize);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ break;
+ }
+ }
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ } while (0);
+ ast_odbc_release_obj(obj);
+ } else
+ ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
+ if (fdm != MAP_FAILED)
+ munmap(fdm, fdlen);
+ if (fd > -1)
+ close(fd);
+ return;
+}
+#endif
+
+static char *convert(const char *lastname)
+{
+ char *tmp;
+ int lcount = 0;
+ tmp = ast_malloc(NUMDIGITS + 1);
+ if (tmp) {
+ while((*lastname > 32) && lcount < NUMDIGITS) {
+ switch(toupper(*lastname)) {
+ case '1':
+ tmp[lcount++] = '1';
+ break;
+ case '2':
+ case 'A':
+ case 'B':
+ case 'C':
+ tmp[lcount++] = '2';
+ break;
+ case '3':
+ case 'D':
+ case 'E':
+ case 'F':
+ tmp[lcount++] = '3';
+ break;
+ case '4':
+ case 'G':
+ case 'H':
+ case 'I':
+ tmp[lcount++] = '4';
+ break;
+ case '5':
+ case 'J':
+ case 'K':
+ case 'L':
+ tmp[lcount++] = '5';
+ break;
+ case '6':
+ case 'M':
+ case 'N':
+ case 'O':
+ tmp[lcount++] = '6';
+ break;
+ case '7':
+ case 'P':
+ case 'Q':
+ case 'R':
+ case 'S':
+ tmp[lcount++] = '7';
+ break;
+ case '8':
+ case 'T':
+ case 'U':
+ case 'V':
+ tmp[lcount++] = '8';
+ break;
+ case '9':
+ case 'W':
+ case 'X':
+ case 'Y':
+ case 'Z':
+ tmp[lcount++] = '9';
+ break;
+ }
+ lastname++;
+ }
+ tmp[lcount] = '\0';
+ }
+ return tmp;
+}
+
+/* play name of mailbox owner.
+ * returns: -1 for bad or missing extension
+ * '1' for selected entry from directory
+ * '*' for skipped entry from directory
+ */
+static int play_mailbox_owner(struct ast_channel *chan, char *context,
+ char *dialcontext, char *ext, char *name, int readext,
+ int fromappvm)
+{
+ int res = 0;
+ int loop;
+ char fn[256];
+
+ /* Check for the VoiceMail2 greeting first */
+ snprintf(fn, sizeof(fn), "%s/voicemail/%s/%s/greet",
+ ast_config_AST_SPOOL_DIR, context, ext);
+#ifdef ODBC_STORAGE
+ retrieve_file(fn);
+#endif
+
+ if (ast_fileexists(fn, NULL, chan->language) <= 0) {
+ /* no file, check for an old-style Voicemail greeting */
+ snprintf(fn, sizeof(fn), "%s/vm/%s/greet",
+ ast_config_AST_SPOOL_DIR, ext);
+ }
+#ifdef ODBC_STORAGE
+ retrieve_file(fn);
+#endif
+
+ if (ast_fileexists(fn, NULL, chan->language) > 0) {
+ res = ast_stream_and_wait(chan, fn, chan->language, AST_DIGIT_ANY);
+ ast_stopstream(chan);
+ /* If Option 'e' was specified, also read the extension number with the name */
+ if (readext) {
+ ast_stream_and_wait(chan, "vm-extension", chan->language, AST_DIGIT_ANY);
+ res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language);
+ }
+ } else {
+ res = ast_say_character_str(chan, S_OR(name, ext), AST_DIGIT_ANY, chan->language);
+ if (!ast_strlen_zero(name) && readext) {
+ ast_stream_and_wait(chan, "vm-extension", chan->language, AST_DIGIT_ANY);
+ res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language);
+ }
+ }
+#ifdef ODBC_STORAGE
+ ast_filedelete(fn, NULL);
+#endif
+
+ for (loop = 3 ; loop > 0; loop--) {
+ if (!res)
+ res = ast_stream_and_wait(chan, "dir-instr", chan->language, AST_DIGIT_ANY);
+ if (!res)
+ res = ast_waitfordigit(chan, 3000);
+ ast_stopstream(chan);
+
+ if (res < 0) /* User hungup, so jump out now */
+ break;
+ if (res == '1') { /* Name selected */
+ if (fromappvm) {
+ /* We still want to set the exten though */
+ ast_copy_string(chan->exten, ext, sizeof(chan->exten));
+ } else {
+ if (ast_goto_if_exists(chan, dialcontext, ext, 1)) {
+ ast_log(LOG_WARNING,
+ "Can't find extension '%s' in context '%s'. "
+ "Did you pass the wrong context to Directory?\n",
+ ext, dialcontext);
+ res = -1;
+ }
+ }
+ break;
+ }
+ if (res == '*') /* Skip to next match in list */
+ break;
+
+ /* Not '1', or '*', so decrement number of tries */
+ res = 0;
+ }
+
+ return(res);
+}
+
+static struct ast_config *realtime_directory(char *context)
+{
+ struct ast_config *cfg;
+ struct ast_config *rtdata;
+ struct ast_category *cat;
+ struct ast_variable *var;
+ char *mailbox;
+ const char *fullname;
+ const char *hidefromdir;
+ char tmp[100];
+
+ /* Load flat file config. */
+ cfg = ast_config_load(VOICEMAIL_CONFIG);
+
+ if (!cfg) {
+ /* Loading config failed. */
+ ast_log(LOG_WARNING, "Loading config failed.\n");
+ return NULL;
+ }
+
+ /* Get realtime entries, categorized by their mailbox number
+ and present in the requested context */
+ rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", "context", context, NULL);
+
+ /* if there are no results, just return the entries from the config file */
+ if (!rtdata)
+ return cfg;
+
+ /* Does the context exist within the config file? If not, make one */
+ cat = ast_category_get(cfg, context);
+ if (!cat) {
+ cat = ast_category_new(context);
+ if (!cat) {
+ ast_log(LOG_WARNING, "Out of memory\n");
+ ast_config_destroy(cfg);
+ if (rtdata) {
+ ast_config_destroy(rtdata);
+ }
+ return NULL;
+ }
+ ast_category_append(cfg, cat);
+ }
+
+ mailbox = NULL;
+ while ( (mailbox = ast_category_browse(rtdata, mailbox)) ) {
+ fullname = ast_variable_retrieve(rtdata, mailbox, "fullname");
+ hidefromdir = ast_variable_retrieve(rtdata, mailbox, "hidefromdir");
+ snprintf(tmp, sizeof(tmp), "no-password,%s,hidefromdir=%s",
+ fullname ? fullname : "",
+ hidefromdir ? hidefromdir : "no");
+ var = ast_variable_new(mailbox, tmp);
+ if (var)
+ ast_variable_append(cat, var);
+ else
+ ast_log(LOG_WARNING, "Out of memory adding mailbox '%s'\n", mailbox);
+ }
+ ast_config_destroy(rtdata);
+
+ return cfg;
+}
+
+static int do_directory(struct ast_channel *chan, struct ast_config *cfg, struct ast_config *ucfg, char *context, char *dialcontext, char digit, int last, int readext, int fromappvm)
+{
+ /* Read in the first three digits.. "digit" is the first digit, already read */
+ char ext[NUMDIGITS + 1], *cat;
+ char name[80] = "";
+ struct ast_variable *v;
+ int res;
+ int found=0;
+ int lastuserchoice = 0;
+ char *start, *conv, *stringp = NULL;
+ const char *pos;
+ int breakout = 0;
+
+ if (ast_strlen_zero(context)) {
+ ast_log(LOG_WARNING,
+ "Directory must be called with an argument "
+ "(context in which to interpret extensions)\n");
+ return -1;
+ }
+ if (digit == '0') {
+ if (!ast_goto_if_exists(chan, dialcontext, "o", 1) ||
+ (!ast_strlen_zero(chan->macrocontext) &&
+ !ast_goto_if_exists(chan, chan->macrocontext, "o", 1))) {
+ return 0;
+ } else {
+ ast_log(LOG_WARNING, "Can't find extension 'o' in current context. "
+ "Not Exiting the Directory!\n");
+ res = 0;
+ }
+ }
+ if (digit == '*') {
+ if (!ast_goto_if_exists(chan, dialcontext, "a", 1) ||
+ (!ast_strlen_zero(chan->macrocontext) &&
+ !ast_goto_if_exists(chan, chan->macrocontext, "a", 1))) {
+ return 0;
+ } else {
+ ast_log(LOG_WARNING, "Can't find extension 'a' in current context. "
+ "Not Exiting the Directory!\n");
+ res = 0;
+ }
+ }
+ memset(ext, 0, sizeof(ext));
+ ext[0] = digit;
+ res = 0;
+ if (ast_readstring(chan, ext + 1, NUMDIGITS - 1, 3000, 3000, "#") < 0) res = -1;
+ if (!res) {
+ /* Search for all names which start with those digits */
+ v = ast_variable_browse(cfg, context);
+ while(v && !res) {
+ /* Find all candidate extensions */
+ while(v) {
+ /* Find a candidate extension */
+ start = strdup(v->value);
+ if (start && !strcasestr(start, "hidefromdir=yes")) {
+ stringp=start;
+ strsep(&stringp, ",");
+ pos = strsep(&stringp, ",");
+ if (pos) {
+ ast_copy_string(name, pos, sizeof(name));
+ /* Grab the last name */
+ if (last && strrchr(pos,' '))
+ pos = strrchr(pos, ' ') + 1;
+ conv = convert(pos);
+ if (conv) {
+ if (!strncmp(conv, ext, strlen(ext))) {
+ /* Match! */
+ found++;
+ free(conv);
+ free(start);
+ break;
+ }
+ free(conv);
+ }
+ }
+ free(start);
+ }
+ v = v->next;
+ }
+
+ if (v) {
+ /* We have a match -- play a greeting if they have it */
+ res = play_mailbox_owner(chan, context, dialcontext, v->name, name, readext, fromappvm);
+ switch (res) {
+ case -1:
+ /* user pressed '1' but extension does not exist, or
+ * user hungup
+ */
+ lastuserchoice = 0;
+ break;
+ case '1':
+ /* user pressed '1' and extensions exists;
+ play_mailbox_owner will already have done
+ a goto() on the channel
+ */
+ lastuserchoice = res;
+ break;
+ case '*':
+ /* user pressed '*' to skip something found */
+ lastuserchoice = res;
+ res = 0;
+ break;
+ default:
+ break;
+ }
+ v = v->next;
+ }
+ }
+
+ if (!res && ucfg) {
+ /* Search users.conf for all names which start with those digits */
+ for (cat = ast_category_browse(ucfg, NULL); cat && !res ; cat = ast_category_browse(ucfg, cat)) {
+ if (!strcasecmp(cat, "general"))
+ continue;
+ if (!ast_true(ast_config_option(ucfg, cat, "hasdirectory")))
+ continue;
+
+ /* Find all candidate extensions */
+ if ((pos = ast_variable_retrieve(ucfg, cat, "fullname"))) {
+ ast_copy_string(name, pos, sizeof(name));
+ /* Grab the last name */
+ if (last && strrchr(pos,' '))
+ pos = strrchr(pos, ' ') + 1;
+ conv = convert(pos);
+ if (conv) {
+ if (!strcmp(conv, ext)) {
+ /* Match! */
+ found++;
+ /* We have a match -- play a greeting if they have it */
+ res = play_mailbox_owner(chan, context, dialcontext, cat, name, readext, fromappvm);
+ switch (res) {
+ case -1:
+ /* user pressed '1' but extension does not exist, or
+ * user hungup
+ */
+ lastuserchoice = 0;
+ breakout = 1;
+ break;
+ case '1':
+ /* user pressed '1' and extensions exists;
+ play_mailbox_owner will already have done
+ a goto() on the channel
+ */
+ lastuserchoice = res;
+ breakout = 1;
+ break;
+ case '*':
+ /* user pressed '*' to skip something found */
+ lastuserchoice = res;
+ breakout = 0;
+ res = 0;
+ break;
+ default:
+ breakout = 1;
+ break;
+ }
+ free(conv);
+ if (breakout)
+ break;
+ }
+ else
+ free(conv);
+ }
+ }
+ }
+ }
+
+ if (lastuserchoice != '1') {
+ res = ast_streamfile(chan, found ? "dir-nomore" : "dir-nomatch", chan->language);
+ if (!res)
+ res = 1;
+ return res;
+ }
+ return 0;
+ }
+ return res;
+}
+
+static int directory_exec(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ struct ast_module_user *u;
+ struct ast_config *cfg, *ucfg;
+ int last = 1;
+ int readext = 0;
+ int fromappvm = 0;
+ const char *dirintro;
+ char *parse;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(vmcontext);
+ AST_APP_ARG(dialcontext);
+ AST_APP_ARG(options);
+ );
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "Directory requires an argument (context[,dialcontext])\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ parse = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ if (args.options) {
+ if (strchr(args.options, 'f'))
+ last = 0;
+ if (strchr(args.options, 'e'))
+ readext = 1;
+ if (strchr(args.options, 'v'))
+ fromappvm = 1;
+ }
+
+ if (ast_strlen_zero(args.dialcontext))
+ args.dialcontext = args.vmcontext;
+
+ cfg = realtime_directory(args.vmcontext);
+ if (!cfg) {
+ ast_log(LOG_ERROR, "Unable to read the configuration data!\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ ucfg = ast_config_load("users.conf");
+
+ dirintro = ast_variable_retrieve(cfg, args.vmcontext, "directoryintro");
+ if (ast_strlen_zero(dirintro))
+ dirintro = ast_variable_retrieve(cfg, "general", "directoryintro");
+ if (ast_strlen_zero(dirintro))
+ dirintro = last ? "dir-intro" : "dir-intro-fn";
+
+ if (chan->_state != AST_STATE_UP)
+ res = ast_answer(chan);
+
+ for (;;) {
+ if (!res)
+ res = ast_stream_and_wait(chan, dirintro, chan->language, AST_DIGIT_ANY);
+ ast_stopstream(chan);
+ if (!res)
+ res = ast_waitfordigit(chan, 5000);
+ if (res > 0) {
+ res = do_directory(chan, cfg, ucfg, args.vmcontext, args.dialcontext, res, last, readext, fromappvm);
+ if (res > 0) {
+ res = ast_waitstream(chan, AST_DIGIT_ANY);
+ ast_stopstream(chan);
+ if (res >= 0)
+ continue;
+ }
+ }
+ break;
+ }
+ if (ucfg)
+ ast_config_destroy(ucfg);
+ ast_config_destroy(cfg);
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+ res = ast_unregister_application(app);
+ return res;
+}
+
+static int load_module(void)
+{
+#ifdef ODBC_STORAGE
+ struct ast_config *cfg = ast_config_load(VOICEMAIL_CONFIG);
+ const char *tmp;
+
+ if (cfg) {
+ if ((tmp = ast_variable_retrieve(cfg, "general", "odbcstorage"))) {
+ ast_copy_string(odbc_database, tmp, sizeof(odbc_database));
+ }
+ if ((tmp = ast_variable_retrieve(cfg, "general", "odbctable"))) {
+ ast_copy_string(odbc_table, tmp, sizeof(odbc_table));
+ }
+ if ((tmp = ast_variable_retrieve(cfg, "general", "format"))) {
+ ast_copy_string(vmfmts, tmp, sizeof(vmfmts));
+ }
+ ast_config_destroy(cfg);
+ } else
+ ast_log(LOG_WARNING, "Unable to load " VOICEMAIL_CONFIG " - ODBC defaults will be used\n");
+#endif
+
+ return ast_register_application(app, directory_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Directory");
diff --git a/apps/app_disa.c b/apps/app_disa.c
new file mode 100644
index 000000000..f49d19704
--- /dev/null
+++ b/apps/app_disa.c
@@ -0,0 +1,399 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ *
+ * Made only slightly more sane by 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 DISA -- Direct Inward System Access Application
+ *
+ * \author Jim Dixon <jim@lambdatel.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <sys/time.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/app.h"
+#include "asterisk/indications.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/ulaw.h"
+#include "asterisk/callerid.h"
+#include "asterisk/stringfields.h"
+
+static char *app = "DISA";
+
+static char *synopsis = "DISA (Direct Inward System Access)";
+
+static char *descrip =
+ "DISA(<numeric passcode>[|<context>]) or DISA(<filename>)\n"
+ "The DISA, Direct Inward System Access, application allows someone from \n"
+ "outside the telephone switch (PBX) to obtain an \"internal\" system \n"
+ "dialtone and to place calls from it as if they were placing a call from \n"
+ "within the switch.\n"
+ "DISA plays a dialtone. The user enters their numeric passcode, followed by\n"
+ "the pound sign (#). If the passcode is correct, the user is then given\n"
+ "system dialtone on which a call may be placed. Obviously, this type\n"
+ "of access has SERIOUS security implications, and GREAT care must be\n"
+ "taken NOT to compromise your security.\n\n"
+ "There is a possibility of accessing DISA without password. Simply\n"
+ "exchange your password with \"no-password\".\n\n"
+ " Example: exten => s,1,DISA(no-password|local)\n\n"
+ "Be aware that using this compromises the security of your PBX.\n\n"
+ "The arguments to this application (in extensions.conf) allow either\n"
+ "specification of a single global passcode (that everyone uses), or\n"
+ "individual passcodes contained in a file. It also allows specification\n"
+ "of the context on which the user will be dialing. If no context is\n"
+ "specified, the DISA application defaults the context to \"disa\".\n"
+ "Presumably a normal system will have a special context set up\n"
+ "for DISA use with some or a lot of restrictions. \n\n"
+ "The file that contains the passcodes (if used) allows specification\n"
+ "of either just a passcode (defaulting to the \"disa\" context, or\n"
+ "passcode|context on each line of the file. The file may contain blank\n"
+ "lines, or comments starting with \"#\" or \";\". In addition, the\n"
+ "above arguments may have |new-callerid-string appended to them, to\n"
+ "specify a new (different) callerid to be used for this call, for\n"
+ "example: numeric-passcode|context|\"My Phone\" <(234) 123-4567> or \n"
+ "full-pathname-of-passcode-file|\"My Phone\" <(234) 123-4567>. Last\n"
+ "but not least, |mailbox[@context] may be appended, which will cause\n"
+ "a stutter-dialtone (indication \"dialrecall\") to be used, if the\n"
+ "specified mailbox contains any new messages, for example:\n"
+ "numeric-passcode|context||1234 (w/a changing callerid). Note that\n"
+ "in the case of specifying the numeric-passcode, the context must be\n"
+ "specified if the callerid is specified also.\n\n"
+ "If login is successful, the application looks up the dialed number in\n"
+ "the specified (or default) context, and executes it if found.\n"
+ "If the user enters an invalid extension and extension \"i\" (invalid) \n"
+ "exists in the context, it will be used. Also, if you set the 5th argument\n"
+ "to 'NOANSWER', the DISA application will not answer initially.\n";
+
+
+static void play_dialtone(struct ast_channel *chan, char *mailbox)
+{
+ const struct tone_zone_sound *ts = NULL;
+ if(ast_app_has_voicemail(mailbox, NULL))
+ ts = ast_get_indication_tone(chan->zone, "dialrecall");
+ else
+ ts = ast_get_indication_tone(chan->zone, "dial");
+ if (ts)
+ ast_playtones_start(chan, 0, ts->data, 0);
+ else
+ ast_tonepair_start(chan, 350, 440, 0, 0);
+}
+
+static int disa_exec(struct ast_channel *chan, void *data)
+{
+ int i,j,k,x,did_ignore,special_noanswer;
+ int firstdigittimeout = 20000;
+ int digittimeout = 10000;
+ struct ast_module_user *u;
+ char *tmp, exten[AST_MAX_EXTENSION],acctcode[20]="";
+ char pwline[256];
+ char ourcidname[256],ourcidnum[256];
+ struct ast_frame *f;
+ struct timeval lastdigittime;
+ int res;
+ time_t rstart;
+ FILE *fp;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(passcode);
+ AST_APP_ARG(context);
+ AST_APP_ARG(cid);
+ AST_APP_ARG(mailbox);
+ AST_APP_ARG(noanswer);
+ );
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "DISA requires an argument (passcode/passcode file)\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ if (chan->pbx) {
+ firstdigittimeout = chan->pbx->rtimeout*1000;
+ digittimeout = chan->pbx->dtimeout*1000;
+ }
+
+ if (ast_set_write_format(chan,AST_FORMAT_ULAW)) {
+ ast_log(LOG_WARNING, "Unable to set write format to Mu-law on %s\n", chan->name);
+ ast_module_user_remove(u);
+ return -1;
+ }
+ if (ast_set_read_format(chan,AST_FORMAT_ULAW)) {
+ ast_log(LOG_WARNING, "Unable to set read format to Mu-law on %s\n", chan->name);
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ ast_log(LOG_DEBUG, "Digittimeout: %d\n", digittimeout);
+ ast_log(LOG_DEBUG, "Responsetimeout: %d\n", firstdigittimeout);
+
+ tmp = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(args, tmp);
+
+ if (ast_strlen_zero(args.context))
+ args.context = "disa";
+ if (ast_strlen_zero(args.mailbox))
+ args.mailbox = "";
+
+ ast_log(LOG_DEBUG, "Mailbox: %s\n",args.mailbox);
+
+
+ special_noanswer = 0;
+ if ((!args.noanswer) || strcmp(args.noanswer,"NOANSWER"))
+ {
+ if (chan->_state != AST_STATE_UP) {
+ /* answer */
+ ast_answer(chan);
+ }
+ } else special_noanswer = 1;
+ i = k = x = 0; /* k is 0 for pswd entry, 1 for ext entry */
+ did_ignore = 0;
+ exten[0] = 0;
+ acctcode[0] = 0;
+ /* can we access DISA without password? */
+
+ ast_log(LOG_DEBUG, "Context: %s\n",args.context);
+
+ if (!strcasecmp(args.passcode, "no-password")) {
+ k |= 1; /* We have the password */
+ ast_log(LOG_DEBUG, "DISA no-password login success\n");
+ }
+ lastdigittime = ast_tvnow();
+
+ play_dialtone(chan, args.mailbox);
+
+ for (;;) {
+ /* if outa time, give em reorder */
+ if (ast_tvdiff_ms(ast_tvnow(), lastdigittime) >
+ ((k&2) ? digittimeout : firstdigittimeout)) {
+ ast_log(LOG_DEBUG,"DISA %s entry timeout on chan %s\n",
+ ((k&1) ? "extension" : "password"),chan->name);
+ break;
+ }
+ if ((res = ast_waitfor(chan, -1) < 0)) {
+ ast_log(LOG_DEBUG, "Waitfor returned %d\n", res);
+ continue;
+ }
+
+ f = ast_read(chan);
+ if (f == NULL) {
+ ast_module_user_remove(u);
+ return -1;
+ }
+ if ((f->frametype == AST_FRAME_CONTROL) &&
+ (f->subclass == AST_CONTROL_HANGUP)) {
+ ast_frfree(f);
+ ast_module_user_remove(u);
+ return -1;
+ }
+ if (f->frametype == AST_FRAME_VOICE) {
+ ast_frfree(f);
+ continue;
+ }
+
+ /* if not DTMF, just do it again */
+ if (f->frametype != AST_FRAME_DTMF) {
+ ast_frfree(f);
+ continue;
+ }
+
+ j = f->subclass; /* save digit */
+ ast_frfree(f);
+ if (i == 0) {
+ k|=2; /* We have the first digit */
+ ast_playtones_stop(chan);
+ }
+ lastdigittime = ast_tvnow();
+ /* got a DTMF tone */
+ if (i < AST_MAX_EXTENSION) { /* if still valid number of digits */
+ if (!(k&1)) { /* if in password state */
+ if (j == '#') { /* end of password */
+ /* see if this is an integer */
+ if (sscanf(args.passcode,"%d",&j) < 1) { /* nope, it must be a filename */
+ fp = fopen(args.passcode,"r");
+ if (!fp) {
+ ast_log(LOG_WARNING,"DISA password file %s not found on chan %s\n",args.passcode,chan->name);
+ ast_module_user_remove(u);
+ return -1;
+ }
+ pwline[0] = 0;
+ while(fgets(pwline,sizeof(pwline) - 1,fp)) {
+ if (!pwline[0])
+ continue;
+ if (pwline[strlen(pwline) - 1] == '\n')
+ pwline[strlen(pwline) - 1] = 0;
+ if (!pwline[0])
+ continue;
+ /* skip comments */
+ if (pwline[0] == '#')
+ continue;
+ if (pwline[0] == ';')
+ continue;
+
+ AST_STANDARD_APP_ARGS(args, pwline);
+
+ ast_log(LOG_DEBUG, "Mailbox: %s\n",args.mailbox);
+
+ /* password must be in valid format (numeric) */
+ if (sscanf(args.passcode,"%d", &j) < 1)
+ continue;
+ /* if we got it */
+ if (!strcmp(exten,args.passcode)) {
+ if (ast_strlen_zero(args.context))
+ args.context = "disa";
+ if (ast_strlen_zero(args.mailbox))
+ args.mailbox = "";
+ break;
+ }
+ }
+ fclose(fp);
+ }
+ /* compare the two */
+ if (strcmp(exten,args.passcode)) {
+ ast_log(LOG_WARNING,"DISA on chan %s got bad password %s\n",chan->name,exten);
+ goto reorder;
+
+ }
+ /* password good, set to dial state */
+ ast_log(LOG_DEBUG,"DISA on chan %s password is good\n",chan->name);
+ play_dialtone(chan, args.mailbox);
+
+ k|=1; /* In number mode */
+ i = 0; /* re-set buffer pointer */
+ exten[sizeof(acctcode)] = 0;
+ ast_copy_string(acctcode, exten, sizeof(acctcode));
+ exten[0] = 0;
+ ast_log(LOG_DEBUG,"Successful DISA log-in on chan %s\n", chan->name);
+ continue;
+ }
+ } else {
+ if (j == '#') { /* end of extension .. maybe */
+ if (i == 0 &&
+ (ast_matchmore_extension(chan, args.context, "#", 1, chan->cid.cid_num) ||
+ ast_exists_extension(chan, args.context, "#", 1, chan->cid.cid_num)) ) {
+ /* Let the # be the part of, or the entire extension */
+ } else {
+ break;
+ }
+ }
+ }
+
+ exten[i++] = j; /* save digit */
+ exten[i] = 0;
+ if (!(k&1))
+ continue; /* if getting password, continue doing it */
+ /* if this exists */
+
+ if (ast_ignore_pattern(args.context, exten)) {
+ play_dialtone(chan, "");
+ did_ignore = 1;
+ } else
+ if (did_ignore) {
+ ast_playtones_stop(chan);
+ did_ignore = 0;
+ }
+
+ /* if can do some more, do it */
+ if (!ast_matchmore_extension(chan,args.context,exten,1, chan->cid.cid_num)) {
+ break;
+ }
+ }
+ }
+
+ if (k == 3) {
+ int recheck = 0;
+ struct ast_flags flags = { AST_CDR_FLAG_POSTED };
+
+ if (!ast_exists_extension(chan, args.context, exten, 1, chan->cid.cid_num)) {
+ pbx_builtin_setvar_helper(chan, "INVALID_EXTEN", exten);
+ exten[0] = 'i';
+ exten[1] = '\0';
+ recheck = 1;
+ }
+ if (!recheck || ast_exists_extension(chan, args.context, exten, 1, chan->cid.cid_num)) {
+ ast_playtones_stop(chan);
+ /* We're authenticated and have a target extension */
+ if (!ast_strlen_zero(args.cid)) {
+ ast_callerid_split(args.cid, ourcidname, sizeof(ourcidname), ourcidnum, sizeof(ourcidnum));
+ ast_set_callerid(chan, ourcidnum, ourcidname, ourcidnum);
+ }
+
+ if (!ast_strlen_zero(acctcode))
+ ast_string_field_set(chan, accountcode, acctcode);
+
+ if (special_noanswer) flags.flags = 0;
+ ast_cdr_reset(chan->cdr, &flags);
+ ast_explicit_goto(chan, args.context, exten, 1);
+ ast_module_user_remove(u);
+ return 0;
+ }
+ }
+
+ /* Received invalid, but no "i" extension exists in the given context */
+
+reorder:
+
+ ast_indicate(chan,AST_CONTROL_CONGESTION);
+ /* something is invalid, give em reorder for several seconds */
+ time(&rstart);
+ while(time(NULL) < rstart + 10) {
+ if (ast_waitfor(chan, -1) < 0)
+ break;
+ f = ast_read(chan);
+ if (!f)
+ break;
+ ast_frfree(f);
+ }
+ ast_playtones_stop(chan);
+ ast_module_user_remove(u);
+ return -1;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, disa_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "DISA (Direct Inward System Access) Application");
diff --git a/apps/app_dumpchan.c b/apps/app_dumpchan.c
new file mode 100644
index 000000000..426ba4eab
--- /dev/null
+++ b/apps/app_dumpchan.c
@@ -0,0 +1,176 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2004 - 2005, Anthony Minessale II.
+ *
+ * Anthony Minessale <anthmct@yahoo.com>
+ *
+ * A license has been granted to Digium (via disclaimer) for the use of
+ * this code.
+ *
+ * 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 Application to dump channel variables
+ *
+ * \author Anthony Minessale <anthmct@yahoo.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/options.h"
+#include "asterisk/utils.h"
+#include "asterisk/lock.h"
+#include "asterisk/utils.h"
+
+static char *app = "DumpChan";
+static char *synopsis = "Dump Info About The Calling Channel";
+static char *desc =
+ " DumpChan([<min_verbose_level>])\n"
+ "Displays information on channel and listing of all channel\n"
+ "variables. If min_verbose_level is specified, output is only\n"
+ "displayed when the verbose level is currently set to that number\n"
+ "or greater. \n";
+
+
+static int serialize_showchan(struct ast_channel *c, char *buf, size_t size)
+{
+ struct timeval now;
+ long elapsed_seconds = 0;
+ int hour = 0, min = 0, sec = 0;
+ char cgrp[BUFSIZ/2];
+ char pgrp[BUFSIZ/2];
+ char formatbuf[BUFSIZ/2];
+
+ now = ast_tvnow();
+ memset(buf, 0, size);
+ if (!c)
+ return 0;
+
+ if (c->cdr) {
+ elapsed_seconds = now.tv_sec - c->cdr->start.tv_sec;
+ hour = elapsed_seconds / 3600;
+ min = (elapsed_seconds % 3600) / 60;
+ sec = elapsed_seconds % 60;
+ }
+
+ snprintf(buf,size,
+ "Name= %s\n"
+ "Type= %s\n"
+ "UniqueID= %s\n"
+ "CallerID= %s\n"
+ "CallerIDName= %s\n"
+ "DNIDDigits= %s\n"
+ "RDNIS= %s\n"
+ "State= %s (%d)\n"
+ "Rings= %d\n"
+ "NativeFormat= %s\n"
+ "WriteFormat= %s\n"
+ "ReadFormat= %s\n"
+ "1stFileDescriptor= %d\n"
+ "Framesin= %d %s\n"
+ "Framesout= %d %s\n"
+ "TimetoHangup= %ld\n"
+ "ElapsedTime= %dh%dm%ds\n"
+ "Context= %s\n"
+ "Extension= %s\n"
+ "Priority= %d\n"
+ "CallGroup= %s\n"
+ "PickupGroup= %s\n"
+ "Application= %s\n"
+ "Data= %s\n"
+ "Blocking_in= %s\n",
+ c->name,
+ c->tech->type,
+ c->uniqueid,
+ S_OR(c->cid.cid_num, "(N/A)"),
+ S_OR(c->cid.cid_name, "(N/A)"),
+ S_OR(c->cid.cid_dnid, "(N/A)"),
+ S_OR(c->cid.cid_rdnis, "(N/A)"),
+ ast_state2str(c->_state),
+ c->_state,
+ c->rings,
+ ast_getformatname_multiple(formatbuf, sizeof(formatbuf), c->nativeformats),
+ ast_getformatname_multiple(formatbuf, sizeof(formatbuf), c->writeformat),
+ ast_getformatname_multiple(formatbuf, sizeof(formatbuf), c->readformat),
+ c->fds[0], c->fin & ~DEBUGCHAN_FLAG, (c->fin & DEBUGCHAN_FLAG) ? " (DEBUGGED)" : "",
+ c->fout & ~DEBUGCHAN_FLAG, (c->fout & DEBUGCHAN_FLAG) ? " (DEBUGGED)" : "", (long)c->whentohangup,
+ hour,
+ min,
+ sec,
+ c->context,
+ c->exten,
+ c->priority,
+ ast_print_group(cgrp, sizeof(cgrp), c->callgroup),
+ ast_print_group(pgrp, sizeof(pgrp), c->pickupgroup),
+ ( c->appl ? c->appl : "(N/A)" ),
+ ( c-> data ? S_OR(c->data, "(Empty)") : "(None)"),
+ (ast_test_flag(c, AST_FLAG_BLOCKING) ? c->blockproc : "(Not Blocking)"));
+
+ return 0;
+}
+
+static int dumpchan_exec(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *u;
+ char vars[BUFSIZ * 4];
+ char info[1024];
+ int level = 0;
+ static char *line = "================================================================================";
+
+ u = ast_module_user_add(chan);
+
+ if (!ast_strlen_zero(data))
+ level = atoi(data);
+
+ pbx_builtin_serialize_variables(chan, vars, sizeof(vars));
+ serialize_showchan(chan, info, sizeof(info));
+ if (option_verbose >= level)
+ ast_verbose("\nDumping Info For Channel: %s:\n%s\nInfo:\n%s\nVariables:\n%s%s\n", chan->name, line, info, vars, line);
+
+ ast_module_user_remove(u);
+
+ return 0;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, dumpchan_exec, synopsis, desc);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Dump Info About The Calling Channel");
diff --git a/apps/app_echo.c b/apps/app_echo.c
new file mode 100644
index 000000000..14f7c6d65
--- /dev/null
+++ b/apps/app_echo.c
@@ -0,0 +1,104 @@
+/*
+ * 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 Echo application -- play back what you hear to evaluate latency
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+
+static char *app = "Echo";
+
+static char *synopsis = "Echo audio, video, or DTMF back to the calling party";
+
+static char *descrip =
+" Echo(): This application will echo any audio, video, or DTMF frames read from\n"
+"the calling channel back to itself. If the DTMF digit '#' is received, the\n"
+"application will exit.\n";
+
+
+static int echo_exec(struct ast_channel *chan, void *data)
+{
+ int res = -1;
+ int format;
+ struct ast_module_user *u;
+
+ u = ast_module_user_add(chan);
+
+ format = ast_best_codec(chan->nativeformats);
+ ast_set_write_format(chan, format);
+ ast_set_read_format(chan, format);
+
+ while (ast_waitfor(chan, -1) > -1) {
+ struct ast_frame *f = ast_read(chan);
+ if (!f)
+ break;
+ f->delivery.tv_sec = 0;
+ f->delivery.tv_usec = 0;
+ if (ast_write(chan, f)) {
+ ast_frfree(f);
+ goto end;
+ }
+ if ((f->frametype == AST_FRAME_DTMF) && (f->subclass == '#')) {
+ res = 0;
+ ast_frfree(f);
+ goto end;
+ }
+ ast_frfree(f);
+ }
+end:
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, echo_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Simple Echo Application");
diff --git a/apps/app_exec.c b/apps/app_exec.c
new file mode 100644
index 000000000..2ab96f593
--- /dev/null
+++ b/apps/app_exec.c
@@ -0,0 +1,221 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (c) 2004 - 2005, Tilghman Lesher. All rights reserved.
+ * Portions copyright (c) 2006, Philipp Dunkel.
+ *
+ * Tilghman Lesher <app_exec__v002@the-tilghman.com>
+ *
+ * This code is released by the author with no restrictions on usage.
+ *
+ * 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.
+ *
+ */
+
+/*! \file
+ *
+ * \brief Exec application
+ *
+ * \author Tilghman Lesher <app_exec__v002@the-tilghman.com>
+ * \author Philipp Dunkel <philipp.dunkel@ebox.at>
+ *
+ * \ingroup applications
+ */
+
+#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/options.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+
+/* Maximum length of any variable */
+#define MAXRESULT 1024
+
+/*! Note
+ *
+ * The key difference between these two apps is exit status. In a
+ * nutshell, Exec tries to be transparent as possible, behaving
+ * in exactly the same way as if the application it calls was
+ * directly invoked from the dialplan.
+ *
+ * TryExec, on the other hand, provides a way to execute applications
+ * and catch any possible fatal error without actually fatally
+ * affecting the dialplan.
+ */
+
+static char *app_exec = "Exec";
+static char *exec_synopsis = "Executes dialplan application";
+static char *exec_descrip =
+"Usage: Exec(appname(arguments))\n"
+" Allows an arbitrary application to be invoked even when not\n"
+"hardcoded into the dialplan. If the underlying application\n"
+"terminates the dialplan, or if the application cannot be found,\n"
+"Exec will terminate the dialplan.\n"
+" To invoke external applications, see the application System.\n"
+" If you would like to catch any error instead, see TryExec.\n";
+
+static char *app_tryexec = "TryExec";
+static char *tryexec_synopsis = "Executes dialplan application, always returning";
+static char *tryexec_descrip =
+"Usage: TryExec(appname(arguments))\n"
+" Allows an arbitrary application to be invoked even when not\n"
+"hardcoded into the dialplan. To invoke external applications\n"
+"see the application System. Always returns to the dialplan.\n"
+"The channel variable TRYSTATUS will be set to:\n"
+" SUCCESS if the application returned zero\n"
+" FAILED if the application returned non-zero\n"
+" NOAPP if the application was not found or was not specified\n";
+
+static char *app_execif = "ExecIf";
+static char *execif_synopsis = "Executes dialplan application, conditionally";
+static char *execif_descrip =
+"Usage: ExecIF (<expr>|<app>|<data>)\n"
+"If <expr> is true, execute and return the result of <app>(<data>).\n"
+"If <expr> is true, but <app> is not found, then the application\n"
+"will return a non-zero value.\n";
+
+static int exec_exec(struct ast_channel *chan, void *data)
+{
+ int res=0;
+ struct ast_module_user *u;
+ char *s, *appname, *endargs, args[MAXRESULT] = "";
+ struct ast_app *app;
+
+ u = ast_module_user_add(chan);
+
+ /* Check and parse arguments */
+ if (data) {
+ s = ast_strdupa(data);
+ appname = strsep(&s, "(");
+ if (s) {
+ endargs = strrchr(s, ')');
+ if (endargs)
+ *endargs = '\0';
+ pbx_substitute_variables_helper(chan, s, args, MAXRESULT - 1);
+ }
+ if (appname) {
+ app = pbx_findapp(appname);
+ if (app) {
+ res = pbx_exec(chan, app, args);
+ } else {
+ ast_log(LOG_WARNING, "Could not find application (%s)\n", appname);
+ res = -1;
+ }
+ }
+ }
+
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int tryexec_exec(struct ast_channel *chan, void *data)
+{
+ int res=0;
+ struct ast_module_user *u;
+ char *s, *appname, *endargs, args[MAXRESULT] = "";
+ struct ast_app *app;
+
+ u = ast_module_user_add(chan);
+
+ /* Check and parse arguments */
+ if (data) {
+ s = ast_strdupa(data);
+ appname = strsep(&s, "(");
+ if (s) {
+ endargs = strrchr(s, ')');
+ if (endargs)
+ *endargs = '\0';
+ pbx_substitute_variables_helper(chan, s, args, MAXRESULT - 1);
+ }
+ if (appname) {
+ app = pbx_findapp(appname);
+ if (app) {
+ res = pbx_exec(chan, app, args);
+ pbx_builtin_setvar_helper(chan, "TRYSTATUS", res ? "FAILED" : "SUCCESS");
+ } else {
+ ast_log(LOG_WARNING, "Could not find application (%s)\n", appname);
+ pbx_builtin_setvar_helper(chan, "TRYSTATUS", "NOAPP");
+ }
+ }
+ }
+
+ ast_module_user_remove(u);
+ return 0;
+}
+
+static int execif_exec(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ struct ast_module_user *u;
+ char *myapp = NULL;
+ char *mydata = NULL;
+ char *expr = NULL;
+ struct ast_app *app = NULL;
+
+ u = ast_module_user_add(chan);
+
+ expr = ast_strdupa(data);
+
+ if ((myapp = strchr(expr,'|'))) {
+ *myapp = '\0';
+ myapp++;
+ if ((mydata = strchr(myapp,'|'))) {
+ *mydata = '\0';
+ mydata++;
+ } else
+ mydata = "";
+
+ if (pbx_checkcondition(expr)) {
+ if ((app = pbx_findapp(myapp))) {
+ res = pbx_exec(chan, app, mydata);
+ } else {
+ ast_log(LOG_WARNING, "Could not find application! (%s)\n", myapp);
+ res = -1;
+ }
+ }
+ } else {
+ ast_log(LOG_ERROR,"Invalid Syntax.\n");
+ res = -1;
+ }
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app_exec);
+ res |= ast_unregister_application(app_tryexec);
+ res |= ast_unregister_application(app_execif);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ int res = ast_register_application(app_exec, exec_exec, exec_synopsis, exec_descrip);
+ res |= ast_register_application(app_tryexec, tryexec_exec, tryexec_synopsis, tryexec_descrip);
+ res |= ast_register_application(app_execif, execif_exec, execif_synopsis, execif_descrip);
+ return res;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Executes dialplan applications");
diff --git a/apps/app_externalivr.c b/apps/app_externalivr.c
new file mode 100644
index 000000000..8004fecd1
--- /dev/null
+++ b/apps/app_externalivr.c
@@ -0,0 +1,585 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * Kevin P. Fleming <kpfleming@digium.com>
+ *
+ * Portions taken from the file-based music-on-hold work
+ * created by Anthony Minessale II in res_musiconhold.c
+ *
+ * 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 External IVR application interface
+ *
+ * \author Kevin P. Fleming <kpfleming@digium.com>
+ *
+ * \note Portions taken from the file-based music-on-hold work
+ * created by Anthony Minessale II in res_musiconhold.c
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/app.h"
+#include "asterisk/utils.h"
+#include "asterisk/options.h"
+
+static const char *app = "ExternalIVR";
+
+static const char *synopsis = "Interfaces with an external IVR application";
+
+static const char *descrip =
+" ExternalIVR(command[|arg[|arg...]]): Forks a process to run the supplied command,\n"
+"and starts a generator on the channel. The generator's play list is\n"
+"controlled by the external application, which can add and clear entries\n"
+"via simple commands issued over its stdout. The external application\n"
+"will receive all DTMF events received on the channel, and notification\n"
+"if the channel is hung up. The application will not be forcibly terminated\n"
+"when the channel is hung up.\n"
+"See doc/externalivr.txt for a protocol specification.\n";
+
+/* XXX the parser in gcc 2.95 gets confused if you don't put a space between 'name' and the comma */
+#define ast_chan_log(level, channel, format, ...) ast_log(level, "%s: " format, channel->name , ## __VA_ARGS__)
+
+struct playlist_entry {
+ AST_LIST_ENTRY(playlist_entry) list;
+ char filename[1];
+};
+
+struct ivr_localuser {
+ struct ast_channel *chan;
+ AST_LIST_HEAD(playlist, playlist_entry) playlist;
+ AST_LIST_HEAD(finishlist, playlist_entry) finishlist;
+ int abort_current_sound;
+ int playing_silence;
+ int option_autoclear;
+};
+
+
+struct gen_state {
+ struct ivr_localuser *u;
+ struct ast_filestream *stream;
+ struct playlist_entry *current;
+ int sample_queue;
+};
+
+static void send_child_event(FILE *handle, const char event, const char *data,
+ const struct ast_channel *chan)
+{
+ char tmp[256];
+
+ if (!data) {
+ snprintf(tmp, sizeof(tmp), "%c,%10d", event, (int)time(NULL));
+ } else {
+ snprintf(tmp, sizeof(tmp), "%c,%10d,%s", event, (int)time(NULL), data);
+ }
+
+ fprintf(handle, "%s\n", tmp);
+ ast_chan_log(LOG_DEBUG, chan, "sent '%s'\n", tmp);
+}
+
+static void *gen_alloc(struct ast_channel *chan, void *params)
+{
+ struct ivr_localuser *u = params;
+ struct gen_state *state;
+
+ if (!(state = ast_calloc(1, sizeof(*state))))
+ return NULL;
+
+ state->u = u;
+
+ return state;
+}
+
+static void gen_closestream(struct gen_state *state)
+{
+ if (!state->stream)
+ return;
+
+ ast_closestream(state->stream);
+ state->u->chan->stream = NULL;
+ state->stream = NULL;
+}
+
+static void gen_release(struct ast_channel *chan, void *data)
+{
+ struct gen_state *state = data;
+
+ gen_closestream(state);
+ free(data);
+}
+
+/* caller has the playlist locked */
+static int gen_nextfile(struct gen_state *state)
+{
+ struct ivr_localuser *u = state->u;
+ char *file_to_stream;
+
+ u->abort_current_sound = 0;
+ u->playing_silence = 0;
+ gen_closestream(state);
+
+ while (!state->stream) {
+ state->current = AST_LIST_REMOVE_HEAD(&u->playlist, list);
+ if (state->current) {
+ file_to_stream = state->current->filename;
+ } else {
+ file_to_stream = "silence/10";
+ u->playing_silence = 1;
+ }
+
+ if (!(state->stream = ast_openstream_full(u->chan, file_to_stream, u->chan->language, 1))) {
+ ast_chan_log(LOG_WARNING, u->chan, "File '%s' could not be opened: %s\n", file_to_stream, strerror(errno));
+ if (!u->playing_silence) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ }
+
+ return (!state->stream);
+}
+
+static struct ast_frame *gen_readframe(struct gen_state *state)
+{
+ struct ast_frame *f = NULL;
+ struct ivr_localuser *u = state->u;
+
+ if (u->abort_current_sound ||
+ (u->playing_silence && AST_LIST_FIRST(&u->playlist))) {
+ gen_closestream(state);
+ AST_LIST_LOCK(&u->playlist);
+ gen_nextfile(state);
+ AST_LIST_UNLOCK(&u->playlist);
+ }
+
+ if (!(state->stream && (f = ast_readframe(state->stream)))) {
+ if (state->current) {
+ AST_LIST_LOCK(&u->finishlist);
+ AST_LIST_INSERT_TAIL(&u->finishlist, state->current, list);
+ AST_LIST_UNLOCK(&u->finishlist);
+ state->current = NULL;
+ }
+ if (!gen_nextfile(state))
+ f = ast_readframe(state->stream);
+ }
+
+ return f;
+}
+
+static int gen_generate(struct ast_channel *chan, void *data, int len, int samples)
+{
+ struct gen_state *state = data;
+ struct ast_frame *f = NULL;
+ int res = 0;
+
+ state->sample_queue += samples;
+
+ while (state->sample_queue > 0) {
+ if (!(f = gen_readframe(state)))
+ return -1;
+
+ res = ast_write(chan, f);
+ ast_frfree(f);
+ if (res < 0) {
+ ast_chan_log(LOG_WARNING, chan, "Failed to write frame: %s\n", strerror(errno));
+ return -1;
+ }
+ state->sample_queue -= f->samples;
+ }
+
+ return res;
+}
+
+static struct ast_generator gen =
+{
+ alloc: gen_alloc,
+ release: gen_release,
+ generate: gen_generate,
+};
+
+static struct playlist_entry *make_entry(const char *filename)
+{
+ struct playlist_entry *entry;
+
+ if (!(entry = ast_calloc(1, sizeof(*entry) + strlen(filename) + 10))) /* XXX why 10 ? */
+ return NULL;
+
+ strcpy(entry->filename, filename);
+
+ return entry;
+}
+
+static int app_exec(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *lu;
+ struct playlist_entry *entry;
+ const char *args = data;
+ int child_stdin[2] = { 0,0 };
+ int child_stdout[2] = { 0,0 };
+ int child_stderr[2] = { 0,0 };
+ int res = -1;
+ int test_available_fd = -1;
+ int gen_active = 0;
+ int pid;
+ char *argv[32];
+ int argc = 1;
+ char *buf, *command;
+ FILE *child_commands = NULL;
+ FILE *child_errors = NULL;
+ FILE *child_events = NULL;
+ struct ivr_localuser foo = {
+ .playlist = AST_LIST_HEAD_INIT_VALUE,
+ .finishlist = AST_LIST_HEAD_INIT_VALUE,
+ };
+ struct ivr_localuser *u = &foo;
+ sigset_t fullset, oldset;
+
+ lu = ast_module_user_add(chan);
+
+ sigfillset(&fullset);
+ pthread_sigmask(SIG_BLOCK, &fullset, &oldset);
+
+ u->abort_current_sound = 0;
+ u->chan = chan;
+
+ if (ast_strlen_zero(args)) {
+ ast_log(LOG_WARNING, "ExternalIVR requires a command to execute\n");
+ ast_module_user_remove(lu);
+ return -1;
+ }
+
+ buf = ast_strdupa(data);
+
+ argc = ast_app_separate_args(buf, '|', argv, sizeof(argv) / sizeof(argv[0]));
+
+ if (pipe(child_stdin)) {
+ ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child input: %s\n", strerror(errno));
+ goto exit;
+ }
+
+ if (pipe(child_stdout)) {
+ ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child output: %s\n", strerror(errno));
+ goto exit;
+ }
+
+ if (pipe(child_stderr)) {
+ ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child errors: %s\n", strerror(errno));
+ goto exit;
+ }
+
+ if (chan->_state != AST_STATE_UP) {
+ ast_answer(chan);
+ }
+
+ if (ast_activate_generator(chan, &gen, u) < 0) {
+ ast_chan_log(LOG_WARNING, chan, "Failed to activate generator\n");
+ goto exit;
+ } else
+ gen_active = 1;
+
+ pid = fork();
+ if (pid < 0) {
+ ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno));
+ goto exit;
+ }
+
+ if (!pid) {
+ /* child process */
+ int i;
+
+ signal(SIGPIPE, SIG_DFL);
+ pthread_sigmask(SIG_UNBLOCK, &fullset, NULL);
+
+ if (ast_opt_high_priority)
+ ast_set_priority(0);
+
+ dup2(child_stdin[0], STDIN_FILENO);
+ dup2(child_stdout[1], STDOUT_FILENO);
+ dup2(child_stderr[1], STDERR_FILENO);
+ for (i = STDERR_FILENO + 1; i < 1024; i++)
+ close(i);
+ execv(argv[0], argv);
+ fprintf(stderr, "Failed to execute '%s': %s\n", argv[0], strerror(errno));
+ _exit(1);
+ } else {
+ /* parent process */
+ int child_events_fd = child_stdin[1];
+ int child_commands_fd = child_stdout[0];
+ int child_errors_fd = child_stderr[0];
+ struct ast_frame *f;
+ int ms;
+ int exception;
+ int ready_fd;
+ int waitfds[2] = { child_errors_fd, child_commands_fd };
+ struct ast_channel *rchan;
+
+ pthread_sigmask(SIG_SETMASK, &oldset, NULL);
+
+ close(child_stdin[0]);
+ child_stdin[0] = 0;
+ close(child_stdout[1]);
+ child_stdout[1] = 0;
+ close(child_stderr[1]);
+ child_stderr[1] = 0;
+
+ if (!(child_events = fdopen(child_events_fd, "w"))) {
+ ast_chan_log(LOG_WARNING, chan, "Could not open stream for child events\n");
+ goto exit;
+ }
+
+ if (!(child_commands = fdopen(child_commands_fd, "r"))) {
+ ast_chan_log(LOG_WARNING, chan, "Could not open stream for child commands\n");
+ goto exit;
+ }
+
+ if (!(child_errors = fdopen(child_errors_fd, "r"))) {
+ ast_chan_log(LOG_WARNING, chan, "Could not open stream for child errors\n");
+ goto exit;
+ }
+
+ test_available_fd = open("/dev/null", O_RDONLY);
+
+ setvbuf(child_events, NULL, _IONBF, 0);
+ setvbuf(child_commands, NULL, _IONBF, 0);
+ setvbuf(child_errors, NULL, _IONBF, 0);
+
+ res = 0;
+
+ while (1) {
+ if (ast_test_flag(chan, AST_FLAG_ZOMBIE)) {
+ ast_chan_log(LOG_NOTICE, chan, "Is a zombie\n");
+ res = -1;
+ break;
+ }
+
+ if (ast_check_hangup(chan)) {
+ ast_chan_log(LOG_NOTICE, chan, "Got check_hangup\n");
+ send_child_event(child_events, 'H', NULL, chan);
+ res = -1;
+ break;
+ }
+
+ ready_fd = 0;
+ ms = 100;
+ errno = 0;
+ exception = 0;
+
+ rchan = ast_waitfor_nandfds(&chan, 1, waitfds, 2, &exception, &ready_fd, &ms);
+
+ if (!AST_LIST_EMPTY(&u->finishlist)) {
+ AST_LIST_LOCK(&u->finishlist);
+ while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) {
+ send_child_event(child_events, 'F', entry->filename, chan);
+ free(entry);
+ }
+ AST_LIST_UNLOCK(&u->finishlist);
+ }
+
+ if (rchan) {
+ /* the channel has something */
+ f = ast_read(chan);
+ if (!f) {
+ ast_chan_log(LOG_NOTICE, chan, "Returned no frame\n");
+ send_child_event(child_events, 'H', NULL, chan);
+ res = -1;
+ break;
+ }
+
+ if (f->frametype == AST_FRAME_DTMF) {
+ send_child_event(child_events, f->subclass, NULL, chan);
+ if (u->option_autoclear) {
+ if (!u->abort_current_sound && !u->playing_silence)
+ send_child_event(child_events, 'T', NULL, chan);
+ AST_LIST_LOCK(&u->playlist);
+ while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
+ send_child_event(child_events, 'D', entry->filename, chan);
+ free(entry);
+ }
+ if (!u->playing_silence)
+ u->abort_current_sound = 1;
+ AST_LIST_UNLOCK(&u->playlist);
+ }
+ } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
+ ast_chan_log(LOG_NOTICE, chan, "Got AST_CONTROL_HANGUP\n");
+ send_child_event(child_events, 'H', NULL, chan);
+ ast_frfree(f);
+ res = -1;
+ break;
+ }
+ ast_frfree(f);
+ } else if (ready_fd == child_commands_fd) {
+ char input[1024];
+
+ if (exception || feof(child_commands)) {
+ ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
+ res = -1;
+ break;
+ }
+
+ if (!fgets(input, sizeof(input), child_commands))
+ continue;
+
+ command = ast_strip(input);
+
+ ast_chan_log(LOG_DEBUG, chan, "got command '%s'\n", input);
+
+ if (strlen(input) < 4)
+ continue;
+
+ if (input[0] == 'S') {
+ if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
+ ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
+ send_child_event(child_events, 'Z', NULL, chan);
+ strcpy(&input[2], "exception");
+ }
+ if (!u->abort_current_sound && !u->playing_silence)
+ send_child_event(child_events, 'T', NULL, chan);
+ AST_LIST_LOCK(&u->playlist);
+ while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
+ send_child_event(child_events, 'D', entry->filename, chan);
+ free(entry);
+ }
+ if (!u->playing_silence)
+ u->abort_current_sound = 1;
+ entry = make_entry(&input[2]);
+ if (entry)
+ AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
+ AST_LIST_UNLOCK(&u->playlist);
+ } else if (input[0] == 'A') {
+ if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
+ ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
+ send_child_event(child_events, 'Z', NULL, chan);
+ strcpy(&input[2], "exception");
+ }
+ entry = make_entry(&input[2]);
+ if (entry) {
+ AST_LIST_LOCK(&u->playlist);
+ AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
+ AST_LIST_UNLOCK(&u->playlist);
+ }
+ } else if (input[0] == 'H') {
+ ast_chan_log(LOG_NOTICE, chan, "Hanging up: %s\n", &input[2]);
+ send_child_event(child_events, 'H', NULL, chan);
+ break;
+ } else if (input[0] == 'O') {
+ if (!strcasecmp(&input[2], "autoclear"))
+ u->option_autoclear = 1;
+ else if (!strcasecmp(&input[2], "noautoclear"))
+ u->option_autoclear = 0;
+ else
+ ast_chan_log(LOG_WARNING, chan, "Unknown option requested '%s'\n", &input[2]);
+ }
+ } else if (ready_fd == child_errors_fd) {
+ char input[1024];
+
+ if (exception || (dup2(child_commands_fd, test_available_fd) == -1) || feof(child_errors)) {
+ ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
+ res = -1;
+ break;
+ }
+
+ if (fgets(input, sizeof(input), child_errors)) {
+ command = ast_strip(input);
+ ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", command);
+ }
+ } else if ((ready_fd < 0) && ms) {
+ if (errno == 0 || errno == EINTR)
+ continue;
+
+ ast_chan_log(LOG_WARNING, chan, "Wait failed (%s)\n", strerror(errno));
+ break;
+ }
+ }
+ }
+
+ exit:
+ if (gen_active)
+ ast_deactivate_generator(chan);
+
+ if (child_events)
+ fclose(child_events);
+
+ if (child_commands)
+ fclose(child_commands);
+
+ if (child_errors)
+ fclose(child_errors);
+
+ if (test_available_fd > -1) {
+ close(test_available_fd);
+ }
+
+ if (child_stdin[0])
+ close(child_stdin[0]);
+
+ if (child_stdin[1])
+ close(child_stdin[1]);
+
+ if (child_stdout[0])
+ close(child_stdout[0]);
+
+ if (child_stdout[1])
+ close(child_stdout[1]);
+
+ if (child_stderr[0])
+ close(child_stderr[0]);
+
+ if (child_stderr[1])
+ close(child_stderr[1]);
+
+ while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list)))
+ free(entry);
+
+ ast_module_user_remove(lu);
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, app_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "External IVR Interface Application");
diff --git a/apps/app_festival.c b/apps/app_festival.c
new file mode 100644
index 000000000..ab05824f5
--- /dev/null
+++ b/apps/app_festival.c
@@ -0,0 +1,566 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2002, Christos Ricudis
+ *
+ * Christos Ricudis <ricudis@itc.auth.gr>
+ *
+ * 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 Connect to festival
+ *
+ * \author Christos Ricudis <ricudis@itc.auth.gr>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/md5.h"
+#include "asterisk/config.h"
+#include "asterisk/utils.h"
+#include "asterisk/lock.h"
+#include "asterisk/options.h"
+
+#define FESTIVAL_CONFIG "festival.conf"
+
+static char *app = "Festival";
+
+static char *synopsis = "Say text to the user";
+
+static char *descrip =
+" Festival(text[|intkeys]): Connect to Festival, send the argument, get back the waveform,"
+"play it to the user, allowing any given interrupt keys to immediately terminate and return\n"
+"the value, or 'any' to allow any number back (useful in dialplan)\n";
+
+
+static char *socket_receive_file_to_buff(int fd,int *size)
+{
+ /* Receive file (probably a waveform file) from socket using */
+ /* Festival key stuff technique, but long winded I know, sorry */
+ /* but will receive any file without closeing the stream or */
+ /* using OOB data */
+ static char *file_stuff_key = "ft_StUfF_key"; /* must == Festival's key */
+ char *buff;
+ int bufflen;
+ int n,k,i;
+ char c;
+
+ bufflen = 1024;
+ if (!(buff = ast_malloc(bufflen)))
+ {
+ /* TODO: Handle memory allocation failure */
+ }
+ *size=0;
+
+ for (k=0; file_stuff_key[k] != '\0';)
+ {
+ n = read(fd,&c,1);
+ if (n==0) break; /* hit stream eof before end of file */
+ if ((*size)+k+1 >= bufflen)
+ { /* +1 so you can add a NULL if you want */
+ bufflen += bufflen/4;
+ if (!(buff = ast_realloc(buff, bufflen)))
+ {
+ /* TODO: Handle memory allocation failure */
+ }
+ }
+ if (file_stuff_key[k] == c)
+ k++;
+ else if ((c == 'X') && (file_stuff_key[k+1] == '\0'))
+ { /* It looked like the key but wasn't */
+ for (i=0; i < k; i++,(*size)++)
+ buff[*size] = file_stuff_key[i];
+ k=0;
+ /* omit the stuffed 'X' */
+ }
+ else
+ {
+ for (i=0; i < k; i++,(*size)++)
+ buff[*size] = file_stuff_key[i];
+ k=0;
+ buff[*size] = c;
+ (*size)++;
+ }
+
+ }
+
+ return buff;
+}
+
+static int send_waveform_to_fd(char *waveform, int length, int fd) {
+
+ int res;
+ int x;
+#ifdef __PPC__
+ char c;
+#endif
+ sigset_t fullset, oldset;
+
+ sigfillset(&fullset);
+ pthread_sigmask(SIG_BLOCK, &fullset, &oldset);
+
+ res = fork();
+ if (res < 0)
+ ast_log(LOG_WARNING, "Fork failed\n");
+ if (res) {
+ pthread_sigmask(SIG_SETMASK, &oldset, NULL);
+ return res;
+ }
+ for (x=0;x<256;x++) {
+ if (x != fd)
+ close(x);
+ }
+ if (ast_opt_high_priority)
+ ast_set_priority(0);
+ signal(SIGPIPE, SIG_DFL);
+ pthread_sigmask(SIG_UNBLOCK, &fullset, NULL);
+/*IAS */
+#ifdef __PPC__
+ for( x=0; x<length; x+=2)
+ {
+ c = *(waveform+x+1);
+ *(waveform+x+1)=*(waveform+x);
+ *(waveform+x)=c;
+ }
+#endif
+
+ if (write(fd,waveform,length) < 0) {
+ ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
+ }
+ close(fd);
+ exit(0);
+}
+
+
+static int send_waveform_to_channel(struct ast_channel *chan, char *waveform, int length, char *intkeys) {
+ int res=0;
+ int fds[2];
+ int ms = -1;
+ int pid = -1;
+ int needed = 0;
+ int owriteformat;
+ struct ast_frame *f;
+ struct myframe {
+ struct ast_frame f;
+ char offset[AST_FRIENDLY_OFFSET];
+ char frdata[2048];
+ } myf = {
+ .f = { 0, },
+ };
+
+ if (pipe(fds)) {
+ ast_log(LOG_WARNING, "Unable to create pipe\n");
+ return -1;
+ }
+
+ /* Answer if it's not already going */
+ if (chan->_state != AST_STATE_UP)
+ ast_answer(chan);
+ ast_stopstream(chan);
+ ast_indicate(chan, -1);
+
+ owriteformat = chan->writeformat;
+ res = ast_set_write_format(chan, AST_FORMAT_SLINEAR);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
+ return -1;
+ }
+
+ res=send_waveform_to_fd(waveform,length,fds[1]);
+ if (res >= 0) {
+ pid = res;
+ /* Order is important -- there's almost always going to be mp3... we want to prioritize the
+ user */
+ for (;;) {
+ ms = 1000;
+ res = ast_waitfor(chan, ms);
+ if (res < 1) {
+ res = -1;
+ break;
+ }
+ f = ast_read(chan);
+ if (!f) {
+ ast_log(LOG_WARNING, "Null frame == hangup() detected\n");
+ res = -1;
+ break;
+ }
+ if (f->frametype == AST_FRAME_DTMF) {
+ ast_log(LOG_DEBUG, "User pressed a key\n");
+ if (intkeys && strchr(intkeys, f->subclass)) {
+ res = f->subclass;
+ ast_frfree(f);
+ break;
+ }
+ }
+ if (f->frametype == AST_FRAME_VOICE) {
+ /* Treat as a generator */
+ needed = f->samples * 2;
+ if (needed > sizeof(myf.frdata)) {
+ ast_log(LOG_WARNING, "Only able to deliver %d of %d requested samples\n",
+ (int)sizeof(myf.frdata) / 2, needed/2);
+ needed = sizeof(myf.frdata);
+ }
+ res = read(fds[0], myf.frdata, needed);
+ if (res > 0) {
+ myf.f.frametype = AST_FRAME_VOICE;
+ myf.f.subclass = AST_FORMAT_SLINEAR;
+ myf.f.datalen = res;
+ myf.f.samples = res / 2;
+ myf.f.offset = AST_FRIENDLY_OFFSET;
+ myf.f.src = __PRETTY_FUNCTION__;
+ myf.f.data = myf.frdata;
+ if (ast_write(chan, &myf.f) < 0) {
+ res = -1;
+ ast_frfree(f);
+ break;
+ }
+ if (res < needed) { /* last frame */
+ ast_log(LOG_DEBUG, "Last frame\n");
+ res=0;
+ ast_frfree(f);
+ break;
+ }
+ } else {
+ ast_log(LOG_DEBUG, "No more waveform\n");
+ res = 0;
+ }
+ }
+ ast_frfree(f);
+ }
+ }
+ close(fds[0]);
+ close(fds[1]);
+
+/* if (pid > -1) */
+/* kill(pid, SIGKILL); */
+ if (!res && owriteformat)
+ ast_set_write_format(chan, owriteformat);
+ return res;
+}
+
+#define MAXLEN 180
+#define MAXFESTLEN 2048
+
+
+
+
+static int festival_exec(struct ast_channel *chan, void *vdata)
+{
+ int usecache;
+ int res=0;
+ struct ast_module_user *u;
+ struct sockaddr_in serv_addr;
+ struct hostent *serverhost;
+ struct ast_hostent ahp;
+ int fd;
+ FILE *fs;
+ const char *host;
+ const char *cachedir;
+ const char *temp;
+ const char *festivalcommand;
+ int port=1314;
+ int n;
+ char ack[4];
+ char *waveform;
+ int filesize;
+ int wave;
+ char bigstring[MAXFESTLEN];
+ int i;
+ struct MD5Context md5ctx;
+ unsigned char MD5Res[16];
+ char MD5Hex[33] = "";
+ char koko[4] = "";
+ char cachefile[MAXFESTLEN]="";
+ int readcache=0;
+ int writecache=0;
+ int strln;
+ int fdesc = -1;
+ char buffer[16384];
+ int seekpos = 0;
+ char *data;
+ char *intstr;
+ struct ast_config *cfg;
+ char *newfestivalcommand;
+
+ if (ast_strlen_zero(vdata)) {
+ ast_log(LOG_WARNING, "festival requires an argument (text)\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ cfg = ast_config_load(FESTIVAL_CONFIG);
+ if (!cfg) {
+ ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
+ ast_module_user_remove(u);
+ return -1;
+ }
+ if (!(host = ast_variable_retrieve(cfg, "general", "host"))) {
+ host = "localhost";
+ }
+ if (!(temp = ast_variable_retrieve(cfg, "general", "port"))) {
+ port = 1314;
+ } else {
+ port = atoi(temp);
+ }
+ if (!(temp = ast_variable_retrieve(cfg, "general", "usecache"))) {
+ usecache=0;
+ } else {
+ usecache = ast_true(temp);
+ }
+ if (!(cachedir = ast_variable_retrieve(cfg, "general", "cachedir"))) {
+ cachedir = "/tmp/";
+ }
+ if (!(festivalcommand = ast_variable_retrieve(cfg, "general", "festivalcommand"))) {
+ festivalcommand = "(tts_textasterisk \"%s\" 'file)(quit)\n";
+ } else { /* This else parses the festivalcommand that we're sent from the config file for \n's, etc */
+ int i, j;
+ newfestivalcommand = alloca(strlen(festivalcommand) + 1);
+
+ for (i = 0, j = 0; i < strlen(festivalcommand); i++) {
+ if (festivalcommand[i] == '\\' && festivalcommand[i + 1] == 'n') {
+ newfestivalcommand[j++] = '\n';
+ i++;
+ } else if (festivalcommand[i] == '\\') {
+ newfestivalcommand[j++] = festivalcommand[i + 1];
+ i++;
+ } else
+ newfestivalcommand[j++] = festivalcommand[i];
+ }
+ newfestivalcommand[j] = '\0';
+ festivalcommand = newfestivalcommand;
+ }
+
+ data = ast_strdupa(vdata);
+
+ intstr = strchr(data, '|');
+ if (intstr) {
+ *intstr = '\0';
+ intstr++;
+ if (!strcasecmp(intstr, "any"))
+ intstr = AST_DIGIT_ANY;
+ }
+
+ ast_log(LOG_DEBUG, "Text passed to festival server : %s\n",(char *)data);
+ /* Connect to local festival server */
+
+ fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+
+ if (fd < 0) {
+ ast_log(LOG_WARNING,"festival_client: can't get socket\n");
+ ast_config_destroy(cfg);
+ ast_module_user_remove(u);
+ return -1;
+ }
+ memset(&serv_addr, 0, sizeof(serv_addr));
+ if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) {
+ /* its a name rather than an ipnum */
+ serverhost = ast_gethostbyname(host, &ahp);
+ if (serverhost == (struct hostent *)0) {
+ ast_log(LOG_WARNING,"festival_client: gethostbyname failed\n");
+ ast_config_destroy(cfg);
+ ast_module_user_remove(u);
+ return -1;
+ }
+ memmove(&serv_addr.sin_addr,serverhost->h_addr, serverhost->h_length);
+ }
+ serv_addr.sin_family = AF_INET;
+ serv_addr.sin_port = htons(port);
+
+ if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) {
+ ast_log(LOG_WARNING,"festival_client: connect to server failed\n");
+ ast_config_destroy(cfg);
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ /* Compute MD5 sum of string */
+ MD5Init(&md5ctx);
+ MD5Update(&md5ctx,(unsigned char const *)data,strlen(data));
+ MD5Final(MD5Res,&md5ctx);
+ MD5Hex[0] = '\0';
+
+ /* Convert to HEX and look if there is any matching file in the cache
+ directory */
+ for (i=0;i<16;i++) {
+ snprintf(koko, sizeof(koko), "%X",MD5Res[i]);
+ strncat(MD5Hex, koko, sizeof(MD5Hex) - strlen(MD5Hex) - 1);
+ }
+ readcache=0;
+ writecache=0;
+ if (strlen(cachedir)+strlen(MD5Hex)+1<=MAXFESTLEN && (usecache==-1)) {
+ snprintf(cachefile, sizeof(cachefile), "%s/%s", cachedir, MD5Hex);
+ fdesc=open(cachefile,O_RDWR);
+ if (fdesc==-1) {
+ fdesc=open(cachefile,O_CREAT|O_RDWR,0777);
+ if (fdesc!=-1) {
+ writecache=1;
+ strln=strlen((char *)data);
+ ast_log(LOG_DEBUG,"line length : %d\n",strln);
+ if (write(fdesc,&strln,sizeof(int)) < 0) {
+ ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
+ }
+ if (write(fdesc,data,strln) < 0) {
+ ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
+ }
+ seekpos=lseek(fdesc,0,SEEK_CUR);
+ ast_log(LOG_DEBUG,"Seek position : %d\n",seekpos);
+ }
+ } else {
+ if (read(fdesc,&strln,sizeof(int)) != sizeof(int)) {
+ ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
+ }
+ ast_log(LOG_DEBUG,"Cache file exists, strln=%d, strlen=%d\n",strln,(int)strlen((char *)data));
+ if (strlen((char *)data)==strln) {
+ ast_log(LOG_DEBUG,"Size OK\n");
+ if (read(fdesc,&bigstring,strln) != strln) {
+ ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
+ }
+ bigstring[strln] = 0;
+ if (strcmp(bigstring,data)==0) {
+ readcache=1;
+ } else {
+ ast_log(LOG_WARNING,"Strings do not match\n");
+ }
+ } else {
+ ast_log(LOG_WARNING,"Size mismatch\n");
+ }
+ }
+ }
+
+ if (readcache==1) {
+ close(fd);
+ fd=fdesc;
+ ast_log(LOG_DEBUG,"Reading from cache...\n");
+ } else {
+ ast_log(LOG_DEBUG,"Passing text to festival...\n");
+ fs=fdopen(dup(fd),"wb");
+ fprintf(fs,festivalcommand,(char *)data);
+ fflush(fs);
+ fclose(fs);
+ }
+
+ /* Write to cache and then pass it down */
+ if (writecache==1) {
+ ast_log(LOG_DEBUG,"Writing result to cache...\n");
+ while ((strln=read(fd,buffer,16384))!=0) {
+ if (write(fdesc,buffer,strln) < 0) {
+ ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
+ }
+ }
+ close(fd);
+ close(fdesc);
+ fd=open(cachefile,O_RDWR);
+ lseek(fd,seekpos,SEEK_SET);
+ }
+
+ ast_log(LOG_DEBUG,"Passing data to channel...\n");
+
+ /* Read back info from server */
+ /* This assumes only one waveform will come back, also LP is unlikely */
+ wave = 0;
+ do {
+ int read_data;
+ for (n=0; n < 3; )
+ {
+ read_data = read(fd,ack+n,3-n);
+ /* this avoids falling in infinite loop
+ * in case that festival server goes down
+ * */
+ if ( read_data == -1 )
+ {
+ ast_log(LOG_WARNING,"Unable to read from cache/festival fd\n");
+ close(fd);
+ ast_config_destroy(cfg);
+ ast_module_user_remove(u);
+ return -1;
+ }
+ n += read_data;
+ }
+ ack[3] = '\0';
+ if (strcmp(ack,"WV\n") == 0) { /* receive a waveform */
+ ast_log(LOG_DEBUG,"Festival WV command\n");
+ waveform = socket_receive_file_to_buff(fd,&filesize);
+ res = send_waveform_to_channel(chan,waveform,filesize, intstr);
+ free(waveform);
+ break;
+ }
+ else if (strcmp(ack,"LP\n") == 0) { /* receive an s-expr */
+ ast_log(LOG_DEBUG,"Festival LP command\n");
+ waveform = socket_receive_file_to_buff(fd,&filesize);
+ waveform[filesize]='\0';
+ ast_log(LOG_WARNING,"Festival returned LP : %s\n",waveform);
+ free(waveform);
+ } else if (strcmp(ack,"ER\n") == 0) { /* server got an error */
+ ast_log(LOG_WARNING,"Festival returned ER\n");
+ res=-1;
+ break;
+ }
+ } while (strcmp(ack,"OK\n") != 0);
+ close(fd);
+ ast_config_destroy(cfg);
+ ast_module_user_remove(u);
+ return res;
+
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ struct ast_config *cfg = ast_config_load(FESTIVAL_CONFIG);
+ if (!cfg) {
+ ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ ast_config_destroy(cfg);
+ return ast_register_application(app, festival_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Simple Festival Interface");
diff --git a/apps/app_flash.c b/apps/app_flash.c
new file mode 100644
index 000000000..813294583
--- /dev/null
+++ b/apps/app_flash.c
@@ -0,0 +1,136 @@
+/*
+ * 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 App to flash a DAHDI trunk
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+ <depend>dahdi</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/image.h"
+#include "asterisk/options.h"
+
+#include "asterisk/dahdi_compat.h"
+
+static char *app = "Flash";
+
+static char *dahdi_synopsis = "Flashes a DAHDI trunk";
+
+static char *dahdi_descrip =
+"Performs a flash on a DAHDI trunk. This can be used\n"
+"to access features provided on an incoming analogue circuit\n"
+"such as conference and call waiting. Use with SendDTMF() to\n"
+"perform external transfers\n";
+
+static char *zap_synopsis = "Flashes a Zap trunk";
+
+static char *zap_descrip =
+"Performs a flash on a Zap trunk. This can be used\n"
+"to access features provided on an incoming analogue circuit\n"
+"such as conference and call waiting. Use with SendDTMF() to\n"
+"perform external transfers\n";
+
+static inline int zt_wait_event(int fd)
+{
+ /* Avoid the silly zt_waitevent which ignores a bunch of events */
+ int i,j=0;
+ i = DAHDI_IOMUX_SIGEVENT;
+ if (ioctl(fd, DAHDI_IOMUX, &i) == -1) return -1;
+ if (ioctl(fd, DAHDI_GETEVENT, &j) == -1) return -1;
+ return j;
+}
+
+static int flash_exec(struct ast_channel *chan, void *data)
+{
+ int res = -1;
+ int x;
+ struct ast_module_user *u;
+ struct dahdi_params ztp;
+ u = ast_module_user_add(chan);
+ if (!strcasecmp(chan->tech->type, dahdi_chan_name)) {
+ memset(&ztp, 0, sizeof(ztp));
+ res = ioctl(chan->fds[0], DAHDI_GET_PARAMS, &ztp);
+ if (!res) {
+ if (ztp.sigtype & __DAHDI_SIG_FXS) {
+ x = DAHDI_FLASH;
+ res = ioctl(chan->fds[0], DAHDI_HOOK, &x);
+ if (!res || (errno == EINPROGRESS)) {
+ if (res) {
+ /* Wait for the event to finish */
+ zt_wait_event(chan->fds[0]);
+ }
+ res = ast_safe_sleep(chan, 1000);
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Flashed channel %s\n", chan->name);
+ } else
+ ast_log(LOG_WARNING, "Unable to flash channel %s: %s\n", chan->name, strerror(errno));
+ } else
+ ast_log(LOG_WARNING, "%s is not an FXO Channel\n", chan->name);
+ } else
+ ast_log(LOG_WARNING, "Unable to get parameters of %s: %s\n", chan->name, strerror(errno));
+ } else
+ ast_log(LOG_WARNING, "%s is not a DAHDI channel\n", chan->name);
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ if (dahdi_chan_mode == CHAN_ZAP_MODE) {
+ return ast_register_application(app, flash_exec, zap_synopsis, zap_descrip);
+ } else {
+ return ast_register_application(app, flash_exec, dahdi_synopsis, dahdi_descrip);
+ }
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Flash channel application");
diff --git a/apps/app_followme.c b/apps/app_followme.c
new file mode 100644
index 000000000..b5bb5588c
--- /dev/null
+++ b/apps/app_followme.c
@@ -0,0 +1,1138 @@
+/*
+ * Asterisk -- A telephony toolkit for Linux.
+ *
+ * A full-featured Find-Me/Follow-Me Application
+ *
+ * Copyright (C) 2005-2006, BJ Weschke All Rights Reserved.
+ *
+ * BJ Weschke <bweschke@btwtech.com>
+ *
+ * This code is released by the author with no restrictions on usage.
+ *
+ * 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.
+ *
+ */
+
+/*! \file
+ *
+ * \brief Find-Me Follow-Me application
+ *
+ * \author BJ Weschke <bweschke@btwtech.com>
+ *
+ * \arg See \ref Config_followme
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+ <depend>chan_local</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.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/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/say.h"
+#include "asterisk/features.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/cli.h"
+#include "asterisk/manager.h"
+#include "asterisk/config.h"
+#include "asterisk/monitor.h"
+#include "asterisk/utils.h"
+#include "asterisk/causes.h"
+#include "asterisk/astdb.h"
+#include "asterisk/app.h"
+
+static char *app = "FollowMe";
+static char *synopsis = "Find-Me/Follow-Me application";
+static char *descrip =
+" FollowMe(followmeid|options):\n"
+"This application performs Find-Me/Follow-Me functionality for the caller\n"
+"as defined in the profile matching the <followmeid> parameter in\n"
+"followme.conf. If the specified <followmeid> profile doesn't exist in\n"
+"followme.conf, execution will be returned to the dialplan and call\n"
+"execution will continue at the next priority.\n\n"
+" Options:\n"
+" s - Playback the incoming status message prior to starting the follow-me step(s)\n"
+" a - Record the caller's name so it can be announced to the callee on each step\n"
+" n - Playback the unreachable status message if we've run out of steps to reach the\n"
+" or the callee has elected not to be reachable.\n"
+"Returns -1 on hangup\n";
+
+/*! \brief Number structure */
+struct number {
+ char number[512]; /*!< Phone Number(s) and/or Extension(s) */
+ long timeout; /*!< Dial Timeout, if used. */
+ char language[MAX_LANGUAGE]; /*!< The language to be used on this dial, if used. */
+ int order; /*!< The order to dial in */
+ AST_LIST_ENTRY(number) entry; /*!< Next Number record */
+};
+
+/*! \brief Data structure for followme scripts */
+struct call_followme {
+ ast_mutex_t lock;
+ char name[AST_MAX_EXTENSION]; /*!< Name - FollowMeID */
+ char moh[AST_MAX_CONTEXT]; /*!< Music On Hold Class to be used */
+ char context[AST_MAX_CONTEXT]; /*!< Context to dial from */
+ unsigned int active; /*!< Profile is active (1), or disabled (0). */
+ char takecall[20]; /*!< Digit mapping to take a call */
+ char nextindp[20]; /*!< Digit mapping to decline a call */
+ char callfromprompt[PATH_MAX]; /*!< Sound prompt name and path */
+ char norecordingprompt[PATH_MAX]; /*!< Sound prompt name and path */
+ char optionsprompt[PATH_MAX]; /*!< Sound prompt name and path */
+ char plsholdprompt[PATH_MAX]; /*!< Sound prompt name and path */
+ char statusprompt[PATH_MAX]; /*!< Sound prompt name and path */
+ char sorryprompt[PATH_MAX]; /*!< Sound prompt name and path */
+
+ AST_LIST_HEAD_NOLOCK(numbers, number) numbers; /*!< Head of the list of follow-me numbers */
+ AST_LIST_HEAD_NOLOCK(blnumbers, number) blnumbers; /*!< Head of the list of black-listed numbers */
+ AST_LIST_HEAD_NOLOCK(wlnumbers, number) wlnumbers; /*!< Head of the list of white-listed numbers */
+ AST_LIST_ENTRY(call_followme) entry; /*!< Next Follow-Me record */
+};
+
+struct fm_args {
+ struct ast_channel *chan;
+ char *mohclass;
+ AST_LIST_HEAD_NOLOCK(cnumbers, number) cnumbers;
+ int status;
+ char context[AST_MAX_CONTEXT];
+ char namerecloc[AST_MAX_CONTEXT];
+ struct ast_channel *outbound;
+ char takecall[20]; /*!< Digit mapping to take a call */
+ char nextindp[20]; /*!< Digit mapping to decline a call */
+ char callfromprompt[PATH_MAX]; /*!< Sound prompt name and path */
+ char norecordingprompt[PATH_MAX]; /*!< Sound prompt name and path */
+ char optionsprompt[PATH_MAX]; /*!< Sound prompt name and path */
+ char plsholdprompt[PATH_MAX]; /*!< Sound prompt name and path */
+ char statusprompt[PATH_MAX]; /*!< Sound prompt name and path */
+ char sorryprompt[PATH_MAX]; /*!< Sound prompt name and path */
+ struct ast_flags followmeflags;
+};
+
+struct findme_user {
+ struct ast_channel *ochan;
+ int state;
+ char dialarg[256];
+ char yn[10];
+ int ynidx;
+ long digts;
+ int cleared;
+ AST_LIST_ENTRY(findme_user) entry;
+};
+
+enum {
+ FOLLOWMEFLAG_STATUSMSG = (1 << 0),
+ FOLLOWMEFLAG_RECORDNAME = (1 << 1),
+ FOLLOWMEFLAG_UNREACHABLEMSG = (1 << 2)
+};
+
+AST_APP_OPTIONS(followme_opts, {
+ AST_APP_OPTION('s', FOLLOWMEFLAG_STATUSMSG ),
+ AST_APP_OPTION('a', FOLLOWMEFLAG_RECORDNAME ),
+ AST_APP_OPTION('n', FOLLOWMEFLAG_UNREACHABLEMSG ),
+});
+
+static int ynlongest = 0;
+
+static const char *featuredigittostr;
+static int featuredigittimeout = 5000; /*!< Feature Digit Timeout */
+static const char *defaultmoh = "default"; /*!< Default Music-On-Hold Class */
+
+static char takecall[20] = "1", nextindp[20] = "2";
+static char callfromprompt[PATH_MAX] = "followme/call-from";
+static char norecordingprompt[PATH_MAX] = "followme/no-recording";
+static char optionsprompt[PATH_MAX] = "followme/options";
+static char plsholdprompt[PATH_MAX] = "followme/pls-hold-while-try";
+static char statusprompt[PATH_MAX] = "followme/status";
+static char sorryprompt[PATH_MAX] = "followme/sorry";
+
+
+static AST_LIST_HEAD_STATIC(followmes, call_followme);
+AST_LIST_HEAD_NOLOCK(findme_user_listptr, findme_user);
+
+static void free_numbers(struct call_followme *f)
+{
+ /* Free numbers attached to the profile */
+ struct number *prev;
+
+ while ((prev = AST_LIST_REMOVE_HEAD(&f->numbers, entry)))
+ /* Free the number */
+ free(prev);
+ AST_LIST_HEAD_INIT_NOLOCK(&f->numbers);
+
+ while ((prev = AST_LIST_REMOVE_HEAD(&f->blnumbers, entry)))
+ /* Free the blacklisted number */
+ free(prev);
+ AST_LIST_HEAD_INIT_NOLOCK(&f->blnumbers);
+
+ while ((prev = AST_LIST_REMOVE_HEAD(&f->wlnumbers, entry)))
+ /* Free the whitelisted number */
+ free(prev);
+ AST_LIST_HEAD_INIT_NOLOCK(&f->wlnumbers);
+
+}
+
+
+/*! \brief Allocate and initialize followme profile */
+static struct call_followme *alloc_profile(const char *fmname)
+{
+ struct call_followme *f;
+
+ if (!(f = ast_calloc(1, sizeof(*f))))
+ return NULL;
+
+ ast_mutex_init(&f->lock);
+ ast_copy_string(f->name, fmname, sizeof(f->name));
+ f->moh[0] = '\0';
+ f->context[0] = '\0';
+ ast_copy_string(f->takecall, takecall, sizeof(f->takecall));
+ ast_copy_string(f->nextindp, nextindp, sizeof(f->nextindp));
+ ast_copy_string(f->callfromprompt, callfromprompt, sizeof(f->callfromprompt));
+ ast_copy_string(f->norecordingprompt, norecordingprompt, sizeof(f->norecordingprompt));
+ ast_copy_string(f->optionsprompt, optionsprompt, sizeof(f->optionsprompt));
+ ast_copy_string(f->plsholdprompt, plsholdprompt, sizeof(f->plsholdprompt));
+ ast_copy_string(f->statusprompt, statusprompt, sizeof(f->statusprompt));
+ ast_copy_string(f->sorryprompt, sorryprompt, sizeof(f->sorryprompt));
+ AST_LIST_HEAD_INIT_NOLOCK(&f->numbers);
+ AST_LIST_HEAD_INIT_NOLOCK(&f->blnumbers);
+ AST_LIST_HEAD_INIT_NOLOCK(&f->wlnumbers);
+ return f;
+}
+
+static void init_profile(struct call_followme *f)
+{
+ f->active = 1;
+ ast_copy_string(f->moh, defaultmoh, sizeof(f->moh));
+}
+
+
+
+/*! \brief Set parameter in profile from configuration file */
+static void profile_set_param(struct call_followme *f, const char *param, const char *val, int linenum, int failunknown)
+{
+
+ if (!strcasecmp(param, "musicclass") || !strcasecmp(param, "musiconhold") || !strcasecmp(param, "music"))
+ ast_copy_string(f->moh, val, sizeof(f->moh));
+ else if (!strcasecmp(param, "context"))
+ ast_copy_string(f->context, val, sizeof(f->context));
+ else if (!strcasecmp(param, "takecall"))
+ ast_copy_string(f->takecall, val, sizeof(f->takecall));
+ else if (!strcasecmp(param, "declinecall"))
+ ast_copy_string(f->nextindp, val, sizeof(f->nextindp));
+ else if (!strcasecmp(param, "call-from-prompt"))
+ ast_copy_string(f->callfromprompt, val, sizeof(f->callfromprompt));
+ else if (!strcasecmp(param, "followme-norecording-prompt"))
+ ast_copy_string(f->norecordingprompt, val, sizeof(f->norecordingprompt));
+ else if (!strcasecmp(param, "followme-options-prompt"))
+ ast_copy_string(f->optionsprompt, val, sizeof(f->optionsprompt));
+ else if (!strcasecmp(param, "followme-pls-hold-prompt"))
+ ast_copy_string(f->plsholdprompt, val, sizeof(f->plsholdprompt));
+ else if (!strcasecmp(param, "followme-status-prompt"))
+ ast_copy_string(f->statusprompt, val, sizeof(f->statusprompt));
+ else if (!strcasecmp(param, "followme-sorry-prompt"))
+ ast_copy_string(f->sorryprompt, val, sizeof(f->sorryprompt));
+ else if (failunknown) {
+ if (linenum >= 0)
+ ast_log(LOG_WARNING, "Unknown keyword in profile '%s': %s at line %d of followme.conf\n", f->name, param, linenum);
+ else
+ ast_log(LOG_WARNING, "Unknown keyword in profile '%s': %s\n", f->name, param);
+ }
+}
+
+/*! \brief Add a new number */
+static struct number *create_followme_number(char *number, char *language, int timeout, int numorder)
+{
+ struct number *cur;
+ char *tmp;
+
+
+ if (!(cur = ast_calloc(1, sizeof(*cur))))
+ return NULL;
+
+ cur->timeout = timeout;
+ if ((tmp = strchr(number, ',')))
+ *tmp = '\0';
+ ast_copy_string(cur->number, number, sizeof(cur->number));
+ ast_copy_string(cur->language, language, sizeof(cur->language));
+ cur->order = numorder;
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Created a number, %s, order of , %d, with a timeout of %ld.\n", cur->number, cur->order, cur->timeout);
+
+ return cur;
+}
+
+/*! \brief Reload followme application module */
+static int reload_followme(void)
+{
+ struct call_followme *f;
+ struct ast_config *cfg;
+ char *cat = NULL, *tmp;
+ struct ast_variable *var;
+ struct number *cur, *nm;
+ int new, idx;
+ char numberstr[90];
+ int timeout;
+ char *timeoutstr;
+ int numorder;
+ const char *takecallstr;
+ const char *declinecallstr;
+ const char *tmpstr;
+
+ cfg = ast_config_load("followme.conf");
+ if (!cfg) {
+ ast_log(LOG_WARNING, "No follow me config file (followme.conf), so no follow me\n");
+ return 0;
+ }
+
+ AST_LIST_LOCK(&followmes);
+
+ /* Reset Global Var Values */
+ featuredigittimeout = 5000;
+
+ /* Mark all profiles as inactive for the moment */
+ AST_LIST_TRAVERSE(&followmes, f, entry) {
+ f->active = 0;
+ }
+ featuredigittostr = ast_variable_retrieve(cfg, "general", "featuredigittimeout");
+
+ if (!ast_strlen_zero(featuredigittostr)) {
+ if (!sscanf(featuredigittostr, "%d", &featuredigittimeout))
+ featuredigittimeout = 5000;
+ }
+
+ takecallstr = ast_variable_retrieve(cfg, "general", "takecall");
+ if (!ast_strlen_zero(takecallstr))
+ ast_copy_string(takecall, takecallstr, sizeof(takecall));
+
+ declinecallstr = ast_variable_retrieve(cfg, "general", "declinecall");
+ if (!ast_strlen_zero(declinecallstr))
+ ast_copy_string(nextindp, declinecallstr, sizeof(nextindp));
+
+ tmpstr = ast_variable_retrieve(cfg, "general", "call-from-prompt");
+ if (!ast_strlen_zero(tmpstr))
+ ast_copy_string(callfromprompt, tmpstr, sizeof(callfromprompt));
+
+ tmpstr = ast_variable_retrieve(cfg, "general", "norecording-prompt");
+ if (!ast_strlen_zero(tmpstr))
+ ast_copy_string(norecordingprompt, tmpstr, sizeof(norecordingprompt));
+
+ tmpstr = ast_variable_retrieve(cfg, "general", "options-prompt");
+ if (!ast_strlen_zero(tmpstr))
+ ast_copy_string(optionsprompt, tmpstr, sizeof(optionsprompt));
+
+ tmpstr = ast_variable_retrieve(cfg, "general", "pls-hold-prompt");
+ if (!ast_strlen_zero(tmpstr))
+ ast_copy_string(plsholdprompt, tmpstr, sizeof(plsholdprompt));
+
+ tmpstr = ast_variable_retrieve(cfg, "general", "status-prompt");
+ if (!ast_strlen_zero(tmpstr))
+ ast_copy_string(statusprompt, tmpstr, sizeof(statusprompt));
+
+ tmpstr = ast_variable_retrieve(cfg, "general", "sorry-prompt");
+ if (!ast_strlen_zero(tmpstr))
+ ast_copy_string(sorryprompt, tmpstr, sizeof(sorryprompt));
+
+ /* Chug through config file */
+ while ((cat = ast_category_browse(cfg, cat))) {
+ if (!strcasecmp(cat, "general"))
+ continue;
+ /* Define a new profile */
+ /* Look for an existing one */
+ AST_LIST_TRAVERSE(&followmes, f, entry) {
+ if (!strcasecmp(f->name, cat))
+ break;
+ }
+ if (option_debug)
+ ast_log(LOG_DEBUG, "New profile %s.\n", cat);
+ if (!f) {
+ /* Make one then */
+ f = alloc_profile(cat);
+ new = 1;
+ } else
+ new = 0;
+
+ if (f) {
+ if (!new)
+ ast_mutex_lock(&f->lock);
+ /* Re-initialize the profile */
+ init_profile(f);
+ free_numbers(f);
+ var = ast_variable_browse(cfg, cat);
+ while(var) {
+ if (!strcasecmp(var->name, "number")) {
+ /* Add a new number */
+ ast_copy_string(numberstr, var->value, sizeof(numberstr));
+ if ((tmp = strchr(numberstr, ','))) {
+ *tmp = '\0';
+ tmp++;
+ timeoutstr = ast_strdupa(tmp);
+ if ((tmp = strchr(timeoutstr, ','))) {
+ *tmp = '\0';
+ tmp++;
+ numorder = atoi(tmp);
+ if (numorder < 0)
+ numorder = 0;
+ } else
+ numorder = 0;
+ timeout = atoi(timeoutstr);
+ if (timeout < 0)
+ timeout = 25;
+ } else {
+ timeout = 25;
+ numorder = 0;
+ }
+
+ if (!numorder) {
+ idx = 1;
+ AST_LIST_TRAVERSE(&f->numbers, nm, entry)
+ idx++;
+ numorder = idx;
+ }
+ cur = create_followme_number(numberstr, "", timeout, numorder);
+ AST_LIST_INSERT_TAIL(&f->numbers, cur, entry);
+ } else {
+ profile_set_param(f, var->name, var->value, var->lineno, 1);
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "Logging parameter %s with value %s from lineno %d\n", var->name, var->value, var->lineno);
+ }
+ var = var->next;
+ } /* End while(var) loop */
+
+ if (!new)
+ ast_mutex_unlock(&f->lock);
+ else
+ AST_LIST_INSERT_HEAD(&followmes, f, entry);
+ }
+ }
+ ast_config_destroy(cfg);
+
+ AST_LIST_UNLOCK(&followmes);
+
+ return 1;
+}
+
+static void clear_caller(struct findme_user *tmpuser)
+{
+ struct ast_channel *outbound;
+
+ if (tmpuser && tmpuser->ochan && tmpuser->state >= 0) {
+ outbound = tmpuser->ochan;
+ if (!outbound->cdr) {
+ outbound->cdr = ast_cdr_alloc();
+ if (outbound->cdr)
+ ast_cdr_init(outbound->cdr, outbound);
+ }
+ if (outbound->cdr) {
+ char tmp[256];
+
+ snprintf(tmp, sizeof(tmp), "%s/%s", "Local", tmpuser->dialarg);
+ ast_cdr_setapp(outbound->cdr, "FollowMe", tmp);
+ ast_cdr_update(outbound);
+ ast_cdr_start(outbound->cdr);
+ ast_cdr_end(outbound->cdr);
+ /* If the cause wasn't handled properly */
+ if (ast_cdr_disposition(outbound->cdr, outbound->hangupcause))
+ ast_cdr_failed(outbound->cdr);
+ } else
+ ast_log(LOG_WARNING, "Unable to create Call Detail Record\n");
+ ast_hangup(tmpuser->ochan);
+ }
+
+}
+
+static void clear_calling_tree(struct findme_user_listptr *findme_user_list)
+{
+ struct findme_user *tmpuser;
+
+ AST_LIST_TRAVERSE(findme_user_list, tmpuser, entry) {
+ clear_caller(tmpuser);
+ tmpuser->cleared = 1;
+ }
+
+}
+
+
+
+static struct ast_channel *wait_for_winner(struct findme_user_listptr *findme_user_list, struct number *nm, struct ast_channel *caller, char *namerecloc, int *status, struct fm_args *tpargs)
+{
+ struct ast_channel *watchers[256];
+ int pos;
+ struct ast_channel *winner;
+ struct ast_frame *f;
+ int ctstatus;
+ int dg;
+ struct findme_user *tmpuser;
+ int to = 0;
+ int livechannels = 0;
+ int tmpto;
+ long totalwait = 0, wtd, towas = 0;
+ char *callfromname;
+ char *pressbuttonname;
+
+ /* ------------ wait_for_winner_channel start --------------- */
+
+ callfromname = ast_strdupa(tpargs->callfromprompt);
+ pressbuttonname = ast_strdupa(tpargs->optionsprompt);
+
+ if (!AST_LIST_EMPTY(findme_user_list)) {
+ if (!caller) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Original caller hungup. Cleanup.\n");
+ clear_calling_tree(findme_user_list);
+ return NULL;
+ }
+ ctstatus = 0;
+ totalwait = nm->timeout * 1000;
+ wtd = 0;
+ while (!ctstatus) {
+ to = 1000;
+ pos = 1;
+ livechannels = 0;
+ watchers[0] = caller;
+
+ dg = 0;
+ winner = NULL;
+ AST_LIST_TRAVERSE(findme_user_list, tmpuser, entry) {
+ if (tmpuser->state >= 0 && tmpuser->ochan) {
+ if (tmpuser->state == 3)
+ tmpuser->digts += (towas - wtd);
+ if (tmpuser->digts && (tmpuser->digts > featuredigittimeout)) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "We've been waiting for digits longer than we should have.\n");
+ if (!ast_strlen_zero(namerecloc)) {
+ tmpuser->state = 1;
+ tmpuser->digts = 0;
+ if (!ast_streamfile(tmpuser->ochan, callfromname, tmpuser->ochan->language)) {
+ ast_sched_runq(tmpuser->ochan->sched);
+ } else {
+ ast_log(LOG_WARNING, "Unable to playback %s.\n", callfromname);
+ return NULL;
+ }
+ } else {
+ tmpuser->state = 2;
+ tmpuser->digts = 0;
+ if (!ast_streamfile(tmpuser->ochan, tpargs->norecordingprompt, tmpuser->ochan->language))
+ ast_sched_runq(tmpuser->ochan->sched);
+ else {
+ ast_log(LOG_WARNING, "Unable to playback %s.\n", tpargs->norecordingprompt);
+ return NULL;
+ }
+ }
+ }
+ if (tmpuser->ochan->stream) {
+ ast_sched_runq(tmpuser->ochan->sched);
+ tmpto = ast_sched_wait(tmpuser->ochan->sched);
+ if (tmpto > 0 && tmpto < to)
+ to = tmpto;
+ else if (tmpto < 0 && !tmpuser->ochan->timingfunc) {
+ ast_stopstream(tmpuser->ochan);
+ if (tmpuser->state == 1) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Playback of the call-from file appears to be done.\n");
+ if (!ast_streamfile(tmpuser->ochan, namerecloc, tmpuser->ochan->language)) {
+ tmpuser->state = 2;
+ } else {
+ ast_log(LOG_NOTICE, "Unable to playback %s. Maybe the caller didn't record their name?\n", namerecloc);
+ memset(tmpuser->yn, 0, sizeof(tmpuser->yn));
+ tmpuser->ynidx = 0;
+ if (!ast_streamfile(tmpuser->ochan, pressbuttonname, tmpuser->ochan->language))
+ tmpuser->state = 3;
+ else {
+ ast_log(LOG_WARNING, "Unable to playback %s.\n", pressbuttonname);
+ return NULL;
+ }
+ }
+ } else if (tmpuser->state == 2) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Playback of name file appears to be done.\n");
+ memset(tmpuser->yn, 0, sizeof(tmpuser->yn));
+ tmpuser->ynidx = 0;
+ if (!ast_streamfile(tmpuser->ochan, pressbuttonname, tmpuser->ochan->language)) {
+ tmpuser->state = 3;
+
+ } else {
+ return NULL;
+ }
+ } else if (tmpuser->state == 3) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Playback of the next step file appears to be done.\n");
+ tmpuser->digts = 0;
+ }
+ }
+ }
+ watchers[pos++] = tmpuser->ochan;
+ livechannels++;
+ }
+ }
+
+ tmpto = to;
+ if (to < 0) {
+ to = 1000;
+ tmpto = 1000;
+ }
+ towas = to;
+ winner = ast_waitfor_n(watchers, pos, &to);
+ tmpto -= to;
+ totalwait -= tmpto;
+ wtd = to;
+ if (totalwait <= 0) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "We've hit our timeout for this step. Drop everyone and move on to the next one. %ld\n", totalwait);
+ clear_calling_tree(findme_user_list);
+ return NULL;
+ }
+ if (winner) {
+ /* Need to find out which channel this is */
+ dg = 0;
+ while ((winner != watchers[dg]) && (dg < 256))
+ dg++;
+ AST_LIST_TRAVERSE(findme_user_list, tmpuser, entry)
+ if (tmpuser->ochan == winner)
+ break;
+ f = ast_read(winner);
+ if (f) {
+ if (f->frametype == AST_FRAME_CONTROL) {
+ switch(f->subclass) {
+ case AST_CONTROL_HANGUP:
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "%s received a hangup frame.\n", winner->name);
+ if (dg == 0) {
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "The calling channel hungup. Need to drop everyone else.\n");
+ clear_calling_tree(findme_user_list);
+ ctstatus = -1;
+ }
+ break;
+ case AST_CONTROL_ANSWER:
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", winner->name, caller->name);
+ /* If call has been answered, then the eventual hangup is likely to be normal hangup */
+ winner->hangupcause = AST_CAUSE_NORMAL_CLEARING;
+ caller->hangupcause = AST_CAUSE_NORMAL_CLEARING;
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "Starting playback of %s\n", callfromname);
+ if (dg > 0) {
+ if (!ast_strlen_zero(namerecloc)) {
+ if (!ast_streamfile(winner, callfromname, winner->language)) {
+ ast_sched_runq(winner->sched);
+ tmpuser->state = 1;
+ } else {
+ ast_log(LOG_WARNING, "Unable to playback %s.\n", callfromname);
+ ast_frfree(f);
+ return NULL;
+ }
+ } else {
+ tmpuser->state = 2;
+ if (!ast_streamfile(tmpuser->ochan, tpargs->norecordingprompt, tmpuser->ochan->language))
+ ast_sched_runq(tmpuser->ochan->sched);
+ else {
+ ast_log(LOG_WARNING, "Unable to playback %s.\n", tpargs->norecordingprompt);
+ ast_frfree(f);
+ return NULL;
+ }
+ }
+ }
+ break;
+ case AST_CONTROL_BUSY:
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "%s is busy\n", winner->name);
+ break;
+ case AST_CONTROL_CONGESTION:
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "%s is circuit-busy\n", winner->name);
+ break;
+ case AST_CONTROL_RINGING:
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "%s is ringing\n", winner->name);
+ break;
+ case AST_CONTROL_PROGRESS:
+ if (option_verbose > 2)
+ ast_verbose ( VERBOSE_PREFIX_3 "%s is making progress passing it to %s\n", winner->name, caller->name);
+ break;
+ case AST_CONTROL_VIDUPDATE:
+ if (option_verbose > 2)
+ ast_verbose ( VERBOSE_PREFIX_3 "%s requested a video update, passing it to %s\n", winner->name, caller->name);
+ break;
+ case AST_CONTROL_SRCUPDATE:
+ if (option_verbose > 2)
+ ast_verbose ( VERBOSE_PREFIX_3 "%s requested a source update, passing it to %s\n", winner->name, caller->name);
+ break;
+ case AST_CONTROL_PROCEEDING:
+ if (option_verbose > 2)
+ ast_verbose ( VERBOSE_PREFIX_3 "%s is proceeding passing it to %s\n", winner->name,caller->name);
+ break;
+ case AST_CONTROL_HOLD:
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Call on %s placed on hold\n", winner->name);
+ break;
+ case AST_CONTROL_UNHOLD:
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Call on %s left from hold\n", winner->name);
+ break;
+ case AST_CONTROL_OFFHOOK:
+ case AST_CONTROL_FLASH:
+ /* Ignore going off hook and flash */
+ break;
+ case -1:
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "%s stopped sounds\n", winner->name);
+ break;
+ default:
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Dunno what to do with control type %d\n", f->subclass);
+ break;
+ }
+ }
+ if (tmpuser && tmpuser->state == 3 && f->frametype == AST_FRAME_DTMF) {
+ if (winner->stream)
+ ast_stopstream(winner);
+ tmpuser->digts = 0;
+ if (option_debug)
+ ast_log(LOG_DEBUG, "DTMF received: %c\n",(char) f->subclass);
+ tmpuser->yn[tmpuser->ynidx] = (char) f->subclass;
+ tmpuser->ynidx++;
+ if (option_debug)
+ ast_log(LOG_DEBUG, "DTMF string: %s\n", tmpuser->yn);
+ if (tmpuser->ynidx >= ynlongest) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "reached longest possible match - doing evals\n");
+ if (!strcmp(tmpuser->yn, tpargs->takecall)) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Match to take the call!\n");
+ ast_frfree(f);
+ return tmpuser->ochan;
+ }
+ if (!strcmp(tmpuser->yn, tpargs->nextindp)) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Next in dial plan step requested.\n");
+ *status = 1;
+ ast_frfree(f);
+ return NULL;
+ }
+
+ }
+ }
+
+ ast_frfree(f);
+ } else {
+ if (winner) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "we didn't get a frame. hanging up. dg is %d\n",dg);
+ if (!dg) {
+ clear_calling_tree(findme_user_list);
+ return NULL;
+ } else {
+ tmpuser->state = -1;
+ ast_hangup(winner);
+ livechannels--;
+ if (option_debug)
+ ast_log(LOG_DEBUG, "live channels left %d\n", livechannels);
+ if (!livechannels) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "no live channels left. exiting.\n");
+ return NULL;
+ }
+ }
+ }
+ }
+
+ } else
+ if (option_debug)
+ ast_log(LOG_DEBUG, "timed out waiting for action\n");
+ }
+
+ } else {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "couldn't reach at this number.\n");
+ }
+
+ /* --- WAIT FOR WINNER NUMBER END! -----------*/
+ return NULL;
+}
+
+static void findmeexec(struct fm_args *tpargs)
+{
+ struct number *nm;
+ struct ast_channel *outbound;
+ struct ast_channel *caller;
+ struct ast_channel *winner = NULL;
+ char dialarg[512];
+ int dg, idx;
+ char *rest, *number;
+ struct findme_user *tmpuser;
+ struct findme_user *fmuser;
+ struct findme_user *headuser;
+ struct findme_user_listptr *findme_user_list;
+ int status;
+
+ findme_user_list = ast_calloc(1, sizeof(*findme_user_list));
+ AST_LIST_HEAD_INIT_NOLOCK(findme_user_list);
+
+ /* We're going to figure out what the longest possible string of digits to collect is */
+ ynlongest = 0;
+ if (strlen(tpargs->takecall) > ynlongest)
+ ynlongest = strlen(tpargs->takecall);
+ if (strlen(tpargs->nextindp) > ynlongest)
+ ynlongest = strlen(tpargs->nextindp);
+
+ idx = 1;
+ caller = tpargs->chan;
+ AST_LIST_TRAVERSE(&tpargs->cnumbers, nm, entry)
+ if (nm->order == idx)
+ break;
+
+ while (nm) {
+
+ if (option_debug > 1)
+ ast_log(LOG_DEBUG, "Number %s timeout %ld\n", nm->number,nm->timeout);
+
+ number = ast_strdupa(nm->number);
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "examining %s\n", number);
+ do {
+ rest = strchr(number, '&');
+ if (rest) {
+ *rest = 0;
+ rest++;
+ }
+
+ if (!strcmp(tpargs->context, ""))
+ snprintf(dialarg, sizeof(dialarg), "%s", number);
+ else
+ snprintf(dialarg, sizeof(dialarg), "%s@%s", number, tpargs->context);
+
+ tmpuser = ast_calloc(1, sizeof(*tmpuser));
+ if (!tmpuser) {
+ ast_log(LOG_WARNING, "Out of memory!\n");
+ free(findme_user_list);
+ return;
+ }
+
+ outbound = ast_request("Local", ast_best_codec(caller->nativeformats), dialarg, &dg);
+ if (outbound) {
+ ast_set_callerid(outbound, caller->cid.cid_num, caller->cid.cid_name, caller->cid.cid_num);
+ ast_channel_inherit_variables(tpargs->chan, outbound);
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "calling %s\n", dialarg);
+ if (!ast_call(outbound,dialarg,0)) {
+ tmpuser->ochan = outbound;
+ tmpuser->state = 0;
+ tmpuser->cleared = 0;
+ ast_copy_string(tmpuser->dialarg, dialarg, sizeof(dialarg));
+ AST_LIST_INSERT_TAIL(findme_user_list, tmpuser, entry);
+ } else {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "couldn't reach at this number.\n");
+ if (outbound) {
+ if (!outbound->cdr)
+ outbound->cdr = ast_cdr_alloc();
+ if (outbound->cdr) {
+ char tmp[256];
+
+ ast_cdr_init(outbound->cdr, outbound);
+ snprintf(tmp, sizeof(tmp), "%s/%s", "Local", dialarg);
+ ast_cdr_setapp(outbound->cdr, "FollowMe", tmp);
+ ast_cdr_update(outbound);
+ ast_cdr_start(outbound->cdr);
+ ast_cdr_end(outbound->cdr);
+ /* If the cause wasn't handled properly */
+ if (ast_cdr_disposition(outbound->cdr,outbound->hangupcause))
+ ast_cdr_failed(outbound->cdr);
+ } else {
+ ast_log(LOG_ERROR, "Unable to create Call Detail Record\n");
+ ast_hangup(outbound);
+ outbound = NULL;
+ }
+ }
+
+ }
+ } else
+ ast_log(LOG_WARNING, "Unable to allocate a channel for Local/%s cause: %s\n", dialarg, ast_cause2str(dg));
+
+ number = rest;
+ } while (number);
+
+ status = 0;
+ if (!AST_LIST_EMPTY(findme_user_list))
+ winner = wait_for_winner(findme_user_list, nm, caller, tpargs->namerecloc, &status, tpargs);
+
+
+ AST_LIST_TRAVERSE_SAFE_BEGIN(findme_user_list, fmuser, entry) {
+ if (!fmuser->cleared && fmuser->ochan != winner)
+ clear_caller(fmuser);
+ AST_LIST_REMOVE_CURRENT(findme_user_list, entry);
+ free(fmuser);
+ }
+ AST_LIST_TRAVERSE_SAFE_END
+ fmuser = NULL;
+ tmpuser = NULL;
+ headuser = NULL;
+ if (winner)
+ break;
+
+ if (!caller) {
+ tpargs->status = 1;
+ free(findme_user_list);
+ return;
+ }
+
+ idx++;
+ AST_LIST_TRAVERSE(&tpargs->cnumbers, nm, entry)
+ if (nm->order == idx)
+ break;
+
+ }
+ free(findme_user_list);
+ if (!winner)
+ tpargs->status = 1;
+ else {
+ tpargs->status = 100;
+ tpargs->outbound = winner;
+ }
+
+
+ return;
+
+}
+
+static void end_bridge_callback (void *data)
+{
+ char buf[80];
+ time_t end;
+ struct ast_channel *chan = data;
+
+ time(&end);
+
+ ast_channel_lock(chan);
+ if (chan->cdr->answer.tv_sec) {
+ snprintf(buf, sizeof(buf), "%ld", end - chan->cdr->answer.tv_sec);
+ pbx_builtin_setvar_helper(chan, "ANSWEREDTIME", buf);
+ }
+
+ if (chan->cdr->start.tv_sec) {
+ snprintf(buf, sizeof(buf), "%ld", end - chan->cdr->start.tv_sec);
+ pbx_builtin_setvar_helper(chan, "DIALEDTIME", buf);
+ }
+ ast_channel_unlock(chan);
+}
+
+static void end_bridge_callback_data_fixup(struct ast_bridge_config *bconfig, struct ast_channel *originator, struct ast_channel *terminator)
+{
+ bconfig->end_bridge_callback_data = originator;
+}
+
+static int app_exec(struct ast_channel *chan, void *data)
+{
+ struct fm_args targs;
+ struct ast_bridge_config config;
+ struct call_followme *f;
+ struct number *nm, *newnm;
+ int res = 0;
+ struct ast_module_user *u;
+ char *argstr;
+ char namerecloc[255];
+ char *fname = NULL;
+ int duration = 0;
+ struct ast_channel *caller;
+ struct ast_channel *outbound;
+
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(followmeid);
+ AST_APP_ARG(options);
+ );
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "%s requires an argument (followmeid)\n",app);
+ return -1;
+ }
+
+ if (!(argstr = ast_strdupa((char *)data))) {
+ ast_log(LOG_ERROR, "Out of memory!\n");
+ return -1;
+ }
+
+
+ AST_STANDARD_APP_ARGS(args, argstr);
+ if (ast_strlen_zero(args.followmeid)) {
+ ast_log(LOG_WARNING, "%s requires an argument (followmeid)\n", app);
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ AST_LIST_LOCK(&followmes);
+ AST_LIST_TRAVERSE(&followmes, f, entry) {
+ if (!strcasecmp(f->name, args.followmeid) && (f->active))
+ break;
+ }
+ AST_LIST_UNLOCK(&followmes);
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "New profile %s.\n", args.followmeid);
+ if (!f) {
+ ast_log(LOG_WARNING, "Profile requested, %s, not found in the configuration.\n", args.followmeid);
+ res = 0;
+ } else {
+ /* XXX TODO: Reinsert the db check value to see whether or not follow-me is on or off */
+
+
+ if (args.options)
+ ast_app_parse_options(followme_opts, &targs.followmeflags, NULL, args.options);
+
+ /* Lock the profile lock and copy out everything we need to run with before unlocking it again */
+ ast_mutex_lock(&f->lock);
+ targs.mohclass = ast_strdupa(f->moh);
+ ast_copy_string(targs.context, f->context, sizeof(targs.context));
+ ast_copy_string(targs.takecall, f->takecall, sizeof(targs.takecall));
+ ast_copy_string(targs.nextindp, f->nextindp, sizeof(targs.nextindp));
+ ast_copy_string(targs.callfromprompt, f->callfromprompt, sizeof(targs.callfromprompt));
+ ast_copy_string(targs.norecordingprompt, f->norecordingprompt, sizeof(targs.norecordingprompt));
+ ast_copy_string(targs.optionsprompt, f->optionsprompt, sizeof(targs.optionsprompt));
+ ast_copy_string(targs.plsholdprompt, f->plsholdprompt, sizeof(targs.plsholdprompt));
+ ast_copy_string(targs.statusprompt, f->statusprompt, sizeof(targs.statusprompt));
+ ast_copy_string(targs.sorryprompt, f->sorryprompt, sizeof(targs.sorryprompt));
+ /* Copy the numbers we're going to use into another list in case the master list should get modified
+ (and locked) while we're trying to do a follow-me */
+ AST_LIST_HEAD_INIT_NOLOCK(&targs.cnumbers);
+ AST_LIST_TRAVERSE(&f->numbers, nm, entry) {
+ newnm = create_followme_number(nm->number, "", nm->timeout, nm->order);
+ AST_LIST_INSERT_TAIL(&targs.cnumbers, newnm, entry);
+ }
+ ast_mutex_unlock(&f->lock);
+
+ if (ast_test_flag(&targs.followmeflags, FOLLOWMEFLAG_STATUSMSG))
+ ast_stream_and_wait(chan, targs.statusprompt, chan->language, "");
+
+ snprintf(namerecloc,sizeof(namerecloc),"%s/followme.%s",ast_config_AST_SPOOL_DIR,chan->uniqueid);
+ duration = 5;
+
+ if (ast_test_flag(&targs.followmeflags, FOLLOWMEFLAG_RECORDNAME))
+ if (ast_play_and_record(chan, "vm-rec-name", namerecloc, 5, "sln", &duration, 128, 0, NULL) < 0)
+ goto outrun;
+
+ if (!ast_fileexists(namerecloc, NULL, chan->language))
+ ast_copy_string(namerecloc, "", sizeof(namerecloc));
+
+ if (ast_streamfile(chan, targs.plsholdprompt, chan->language))
+ goto outrun;
+ if (ast_waitstream(chan, "") < 0)
+ goto outrun;
+ ast_moh_start(chan, S_OR(targs.mohclass, NULL), NULL);
+
+ targs.status = 0;
+ targs.chan = chan;
+ ast_copy_string(targs.namerecloc, namerecloc, sizeof(targs.namerecloc));
+
+ findmeexec(&targs);
+
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&targs.cnumbers, nm, entry) {
+ AST_LIST_REMOVE_CURRENT(&targs.cnumbers, entry);
+ free(nm);
+ }
+ AST_LIST_TRAVERSE_SAFE_END
+ if (targs.status != 100) {
+ ast_moh_stop(chan);
+ if (ast_test_flag(&targs.followmeflags, FOLLOWMEFLAG_UNREACHABLEMSG))
+ ast_stream_and_wait(chan, targs.sorryprompt, chan->language, "");
+ res = 0;
+ } else {
+ caller = chan;
+ outbound = targs.outbound;
+ /* Bridge the two channels. */
+
+ memset(&config,0,sizeof(struct ast_bridge_config));
+ ast_set_flag(&(config.features_callee), AST_FEATURE_REDIRECT);
+ ast_set_flag(&(config.features_callee), AST_FEATURE_AUTOMON);
+ ast_set_flag(&(config.features_caller), AST_FEATURE_AUTOMON);
+
+ config.end_bridge_callback = end_bridge_callback;
+ config.end_bridge_callback_data = chan;
+ config.end_bridge_callback_data_fixup = end_bridge_callback_data_fixup;
+
+ ast_moh_stop(caller);
+ /* Be sure no generators are left on it */
+ ast_deactivate_generator(caller);
+ /* Make sure channels are compatible */
+ res = ast_channel_make_compatible(caller, outbound);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", caller->name, outbound->name);
+ ast_hangup(outbound);
+ goto outrun;
+ }
+ res = ast_bridge_call(caller,outbound,&config);
+ if (outbound)
+ ast_hangup(outbound);
+ }
+ }
+ outrun:
+
+ if (!ast_strlen_zero(namerecloc)){
+ fname = alloca(strlen(namerecloc) + 5);
+ sprintf(fname, "%s.sln", namerecloc);
+ unlink(fname);
+ }
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ struct call_followme *f;
+
+ ast_module_user_hangup_all();
+
+ ast_unregister_application(app);
+
+ /* Free Memory. Yeah! I'm free! */
+ AST_LIST_LOCK(&followmes);
+ while ((f = AST_LIST_REMOVE_HEAD(&followmes, entry))) {
+ free_numbers(f);
+ free(f);
+ }
+
+ AST_LIST_UNLOCK(&followmes);
+
+ return 0;
+}
+
+static int load_module(void)
+{
+ if(!reload_followme())
+ return AST_MODULE_LOAD_DECLINE;
+
+ return ast_register_application(app, app_exec, synopsis, descrip);
+}
+
+static int reload(void)
+{
+ reload_followme();
+
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Find-Me/Follow-Me Application",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
diff --git a/apps/app_forkcdr.c b/apps/app_forkcdr.c
new file mode 100644
index 000000000..dc53ef78b
--- /dev/null
+++ b/apps/app_forkcdr.c
@@ -0,0 +1,267 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Anthony Minessale anthmct@yahoo.com
+ * Development of this app Sponsered/Funded by TAAN Softworks Corp
+ *
+ * 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 Fork CDR application
+ *
+ * \author Anthony Minessale anthmct@yahoo.com
+ *
+ * \note Development of this app Sponsored/Funded by TAAN Softworks Corp
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/cdr.h"
+#include "asterisk/app.h"
+#include "asterisk/module.h"
+
+static char *app = "ForkCDR";
+static char *synopsis =
+"Forks the Call Data Record";
+static char *descrip =
+" ForkCDR([options]): Causes the Call Data Record to fork an additional\n"
+"cdr record starting from the time of the fork call. This new cdr record will\n"
+"be linked to end of the list of cdr records attached to the channel. The original CDR is\n"
+"has a LOCKED flag set, which forces most cdr operations to skip it, except\n"
+"for the functions that set the answer and end times, which ignore the LOCKED\n"
+"flag. This allows all the cdr records in the channel to be 'ended' together\n"
+"when the channel is closed.\n"
+"The CDR() func (when setting CDR values) normally ignores the LOCKED flag also,\n"
+"but has options to vary its behavior. The 'T' option (described below), can\n"
+"override this behavior, but beware the risks.\n"
+"\n"
+"Detailed Behavior Description:\n"
+"First, this app finds the last cdr record in the list, and makes\n"
+"a copy of it. This new copy will be the newly forked cdr record.\n"
+"Next, this new record is linked to the end of the cdr record list.\n"
+"Next, The new cdr record is RESET (unless you use an option to prevent this)\n"
+"This means that:\n"
+" 1. All flags are unset on the cdr record\n"
+" 2. the start, end, and answer times are all set to zero.\n"
+" 3. the billsec and duration fields are set to zero.\n"
+" 4. the start time is set to the current time.\n"
+" 5. the disposition is set to NULL.\n"
+"Next, unless you specified the 'v' option, all variables will be\n"
+"removed from the original cdr record. Thus, the 'v' option allows\n"
+"any CDR variables to be replicated to all new forked cdr records.\n"
+"Without the 'v' option, the variables on the original are effectively\n"
+"moved to the new forked cdr record.\n"
+"Next, if the 's' option is set, the provided variable and value\n"
+"are set on the original cdr record.\n"
+"Next, if the 'a' option is given, and the original cdr record has an\n"
+"answer time set, then the new forked cdr record will have its answer\n"
+"time set to its start time. If the old answer time were carried forward,\n"
+"the answer time would be earlier than the start time, giving strange\n"
+"duration and billsec times.\n"
+"Next, if the 'd' option was specified, the disposition is copied from\n"
+"the original cdr record to the new forked cdr.\n"
+"Next, if the 'D' option was specified, the destination channel field\n"
+"in the new forked CDR is erased.\n"
+"Next, if the 'e' option was specified, the 'end' time for the original\n"
+"cdr record is set to the current time. Future hang-up or ending events\n"
+"will not override this time stamp.\n"
+"Next, If the 'A' option is specified, the original cdr record will have\n"
+"it ANS_LOCKED flag set, which prevent future answer events\n"
+"from updating the original cdr record's disposition. Normally, an\n"
+"'ANSWERED' event would mark all cdr records in the chain as 'ANSWERED'.\n"
+"Next, if the 'T' option is specified, the original cdr record will have\n"
+"its 'DONT_TOUCH' flag set, which will force the cdr_answer, cdr_end, and\n"
+"cdr_setvar functions to leave that cdr record alone.\n"
+"And, last but not least, the original cdr record has its LOCKED flag\n"
+"set. Almost all internal CDR functions (except for the funcs that set\n"
+"the end, and answer times, and set a variable) will honor this flag\n"
+"and leave a LOCKED cdr record alone.\n"
+"This means that the newly created forked cdr record will affected\n"
+"by events transpiring within Asterisk, with the previously noted\n"
+"exceptions.\n"
+" Options:\n"
+" a - update the answer time on the NEW CDR just after it's been inited..\n"
+" The new CDR may have been answered already, the reset that forkcdr.\n"
+" does will erase the answer time. This will bring it back, but\n"
+" the answer time will be a copy of the fork/start time. It will.\n"
+" only do this if the initial cdr was indeed already answered..\n"
+" A - Lock the original CDR against the answer time being updated.\n"
+" This will allow the disposition on the original CDR to remain the same.\n"
+" d - Copy the disposition forward from the old cdr, after the .\n"
+" init..\n"
+" D - Clear the dstchannel on the new CDR after reset..\n"
+" e - end the original CDR. Do this after all the necc. data.\n"
+" is copied from the original CDR to the new forked CDR..\n"
+" R - do NOT reset the new cdr..\n"
+" s(name=val) - Set the CDR var 'name' in the original CDR, with value.\n"
+" 'val'.\n"
+" T - Mark the original CDR with a DONT_TOUCH flag. setvar, answer, and end\n"
+" cdr funcs will obey this flag; normally they don't honor the LOCKED\n"
+" flag set on the original CDR record.\n"
+" Beware-- using this flag may cause CDR's not to have their end times\n"
+" updated! It is suggested that if you specify this flag, you might\n"
+" wish to use the 'e' flag as well!\n"
+" v - When the new CDR is forked, it gets a copy of the vars attached\n"
+" to the current CDR. The vars attached to the original CDR are removed\n"
+" unless this option is specified.\n";
+
+
+enum {
+ OPT_SETANS = (1 << 0),
+ OPT_SETDISP = (1 << 1),
+ OPT_RESETDEST = (1 << 2),
+ OPT_ENDCDR = (1 << 3),
+ OPT_NORESET = (1 << 4),
+ OPT_KEEPVARS = (1 << 5),
+ OPT_VARSET = (1 << 6),
+ OPT_ANSLOCK = (1 << 7),
+ OPT_DONTOUCH = (1 << 8),
+};
+
+enum {
+ OPT_ARG_VARSET = 0,
+ /* note: this entry _MUST_ be the last one in the enum */
+ OPT_ARG_ARRAY_SIZE,
+};
+
+AST_APP_OPTIONS(forkcdr_exec_options, {
+ AST_APP_OPTION('a', OPT_SETANS),
+ AST_APP_OPTION('A', OPT_ANSLOCK),
+ AST_APP_OPTION('d', OPT_SETDISP),
+ AST_APP_OPTION('D', OPT_RESETDEST),
+ AST_APP_OPTION('e', OPT_ENDCDR),
+ AST_APP_OPTION('R', OPT_NORESET),
+ AST_APP_OPTION_ARG('s', OPT_VARSET, OPT_ARG_VARSET),
+ AST_APP_OPTION('T', OPT_DONTOUCH),
+ AST_APP_OPTION('v', OPT_KEEPVARS),
+});
+
+static void ast_cdr_fork(struct ast_channel *chan, struct ast_flags optflags, char *set)
+{
+ struct ast_cdr *cdr;
+ struct ast_cdr *newcdr;
+ struct ast_flags flags = { AST_CDR_FLAG_KEEP_VARS };
+
+ cdr = chan->cdr;
+
+ while (cdr->next)
+ cdr = cdr->next;
+
+ if (!(newcdr = ast_cdr_dup(cdr)))
+ return;
+
+ ast_cdr_append(cdr, newcdr);
+
+ if (!ast_test_flag(&optflags, OPT_NORESET))
+ ast_cdr_reset(newcdr, &flags);
+
+ if (!ast_test_flag(cdr, AST_CDR_FLAG_KEEP_VARS))
+ ast_cdr_free_vars(cdr, 0);
+
+ if (!ast_strlen_zero(set)) {
+ char *varname = ast_strdupa(set), *varval;
+ varval = strchr(varname,'=');
+ if (varval) {
+ *varval = 0;
+ varval++;
+ ast_cdr_setvar(cdr, varname, varval, 0);
+ }
+ }
+
+ if (ast_test_flag(&optflags, OPT_SETANS) && !ast_tvzero(cdr->answer))
+ newcdr->answer = newcdr->start;
+
+ if (ast_test_flag(&optflags, OPT_SETDISP))
+ newcdr->disposition = cdr->disposition;
+
+ if (ast_test_flag(&optflags, OPT_RESETDEST))
+ newcdr->dstchannel[0] = 0;
+
+ if (ast_test_flag(&optflags, OPT_ENDCDR))
+ ast_cdr_end(cdr);
+
+ if (ast_test_flag(&optflags, OPT_ANSLOCK))
+ ast_set_flag(cdr, AST_CDR_FLAG_ANSLOCKED);
+
+ if (ast_test_flag(&optflags, OPT_DONTOUCH))
+ ast_set_flag(cdr, AST_CDR_FLAG_DONT_TOUCH);
+
+ ast_set_flag(cdr, AST_CDR_FLAG_CHILD | AST_CDR_FLAG_LOCKED);
+}
+
+static int forkcdr_exec(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ struct ast_module_user *u;
+ char *argcopy = NULL;
+ struct ast_flags flags = {0};
+ char *opts[OPT_ARG_ARRAY_SIZE];
+ AST_DECLARE_APP_ARGS(arglist,
+ AST_APP_ARG(options);
+ );
+
+ if (!chan->cdr) {
+ ast_log(LOG_WARNING, "Channel does not have a CDR\n");
+ return 0;
+ }
+
+ u = ast_module_user_add(chan);
+
+ argcopy = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(arglist, argcopy);
+
+ opts[OPT_ARG_VARSET] = 0;
+
+ if (!ast_strlen_zero(arglist.options))
+ ast_app_parse_options(forkcdr_exec_options, &flags, opts, arglist.options);
+
+ if (!ast_strlen_zero(data))
+ ast_set2_flag(chan->cdr, ast_test_flag(&flags, OPT_KEEPVARS), AST_CDR_FLAG_KEEP_VARS);
+
+ ast_cdr_fork(chan, flags, opts[OPT_ARG_VARSET]);
+
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, forkcdr_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Fork The CDR into 2 separate entities");
diff --git a/apps/app_getcpeid.c b/apps/app_getcpeid.c
new file mode 100644
index 000000000..bebc60e9b
--- /dev/null
+++ b/apps/app_getcpeid.c
@@ -0,0 +1,148 @@
+/*
+ * 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 Get ADSI CPE ID
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/adsi.h"
+#include "asterisk/options.h"
+
+static char *app = "GetCPEID";
+
+static char *synopsis = "Get ADSI CPE ID";
+
+static char *descrip =
+" GetCPEID: Obtains and displays ADSI CPE ID and other information in order\n"
+"to properly setup chan_dahdi.conf for on-hook operations.\n";
+
+
+static int cpeid_setstatus(struct ast_channel *chan, char *stuff[], int voice)
+{
+ int justify[5] = { ADSI_JUST_CENT, ADSI_JUST_LEFT, ADSI_JUST_LEFT, ADSI_JUST_LEFT };
+ char *tmp[5];
+ int x;
+ for (x=0;x<4;x++)
+ tmp[x] = stuff[x];
+ tmp[4] = NULL;
+ return ast_adsi_print(chan, tmp, justify, voice);
+}
+
+static int cpeid_exec(struct ast_channel *chan, void *idata)
+{
+ int res=0;
+ struct ast_module_user *u;
+ unsigned char cpeid[4];
+ int gotgeometry = 0;
+ int gotcpeid = 0;
+ int width, height, buttons;
+ char *data[4];
+ unsigned int x;
+
+ u = ast_module_user_add(chan);
+
+ for (x = 0; x < 4; x++)
+ data[x] = alloca(80);
+
+ strcpy(data[0], "** CPE Info **");
+ strcpy(data[1], "Identifying CPE...");
+ strcpy(data[2], "Please wait...");
+ res = ast_adsi_load_session(chan, NULL, 0, 1);
+ if (res > 0) {
+ cpeid_setstatus(chan, data, 0);
+ res = ast_adsi_get_cpeid(chan, cpeid, 0);
+ if (res > 0) {
+ gotcpeid = 1;
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Got CPEID of '%02x:%02x:%02x:%02x' on '%s'\n", cpeid[0], cpeid[1], cpeid[2], cpeid[3], chan->name);
+ }
+ if (res > -1) {
+ strcpy(data[1], "Measuring CPE...");
+ strcpy(data[2], "Please wait...");
+ cpeid_setstatus(chan, data, 0);
+ res = ast_adsi_get_cpeinfo(chan, &width, &height, &buttons, 0);
+ if (res > -1) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "CPE has %d lines, %d columns, and %d buttons on '%s'\n", height, width, buttons, chan->name);
+ gotgeometry = 1;
+ }
+ }
+ if (res > -1) {
+ if (gotcpeid)
+ snprintf(data[1], 80, "CPEID: %02x:%02x:%02x:%02x", cpeid[0], cpeid[1], cpeid[2], cpeid[3]);
+ else
+ strcpy(data[1], "CPEID Unknown");
+ if (gotgeometry)
+ snprintf(data[2], 80, "Geom: %dx%d, %d buttons", width, height, buttons);
+ else
+ strcpy(data[2], "Geometry unknown");
+ strcpy(data[3], "Press # to exit");
+ cpeid_setstatus(chan, data, 1);
+ for(;;) {
+ res = ast_waitfordigit(chan, 1000);
+ if (res < 0)
+ break;
+ if (res == '#') {
+ res = 0;
+ break;
+ }
+ }
+ ast_adsi_unload_session(chan);
+ }
+ }
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, cpeid_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Get ADSI CPE ID");
diff --git a/apps/app_hasnewvoicemail.c b/apps/app_hasnewvoicemail.c
new file mode 100644
index 000000000..8f3d33504
--- /dev/null
+++ b/apps/app_hasnewvoicemail.c
@@ -0,0 +1,225 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Changes Copyright (c) 2004 - 2006 Todd Freeman <freeman@andrews.edu>
+ *
+ * 95% based on HasNewVoicemail by:
+ *
+ * Copyright (c) 2003 Tilghman Lesher. All rights reserved.
+ *
+ * Tilghman Lesher <asterisk-hasnewvoicemail-app@the-tilghman.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 HasVoicemail application
+ *
+ * \author Todd Freeman <freeman@andrews.edu>
+ *
+ * \note 95% based on HasNewVoicemail by
+ * Tilghman Lesher <asterisk-hasnewvoicemail-app@the-tilghman.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/types.h>
+
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/lock.h"
+#include "asterisk/utils.h"
+#include "asterisk/app.h"
+#include "asterisk/options.h"
+
+static char *app_hasvoicemail = "HasVoicemail";
+static char *hasvoicemail_synopsis = "Conditionally branches to priority + 101 with the right options set";
+static char *hasvoicemail_descrip =
+"HasVoicemail(vmbox[/folder][@context][|varname[|options]])\n"
+" Optionally sets <varname> to the number of messages in that folder."
+" Assumes folder of INBOX if not specified.\n"
+" The option string may contain zero or the following character:\n"
+" 'j' -- jump to priority n+101, if there is voicemail in the folder indicated.\n"
+" This application sets the following channel variable upon completion:\n"
+" HASVMSTATUS The result of the voicemail check returned as a text string as follows\n"
+" <# of messages in the folder, 0 for NONE>\n"
+"\nThis application has been deprecated in favor of the VMCOUNT() function\n";
+
+static char *app_hasnewvoicemail = "HasNewVoicemail";
+static char *hasnewvoicemail_synopsis = "Conditionally branches to priority + 101 with the right options set";
+static char *hasnewvoicemail_descrip =
+"HasNewVoicemail(vmbox[/folder][@context][|varname[|options]])\n"
+"Assumes folder 'INBOX' if folder is not specified. Optionally sets <varname> to the number of messages\n"
+"in that folder.\n"
+" The option string may contain zero of the following character:\n"
+" 'j' -- jump to priority n+101, if there is new voicemail in folder 'folder' or INBOX\n"
+" This application sets the following channel variable upon completion:\n"
+" HASVMSTATUS The result of the new voicemail check returned as a text string as follows\n"
+" <# of messages in the folder, 0 for NONE>\n"
+"\nThis application has been deprecated in favor of the VMCOUNT() function\n";
+
+
+static int hasvoicemail_exec(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *u;
+ char *input, *varname = NULL, *vmbox, *context = "default";
+ char *vmfolder;
+ int vmcount = 0;
+ static int dep_warning = 0;
+ int priority_jump = 0;
+ char tmp[12];
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(vmbox);
+ AST_APP_ARG(varname);
+ AST_APP_ARG(options);
+ );
+
+ if (!dep_warning) {
+ ast_log(LOG_WARNING, "The applications HasVoicemail and HasNewVoicemail have been deprecated. Please use the VMCOUNT() function instead.\n");
+ dep_warning = 1;
+ }
+
+ if (!data) {
+ ast_log(LOG_WARNING, "HasVoicemail requires an argument (vm-box[/folder][@context][|varname[|options]])\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ input = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(args, input);
+
+ vmbox = strsep(&args.vmbox, "@");
+
+ if (!ast_strlen_zero(args.vmbox))
+ context = args.vmbox;
+
+ vmfolder = strchr(vmbox, '/');
+ if (vmfolder) {
+ *vmfolder = '\0';
+ vmfolder++;
+ } else {
+ vmfolder = "INBOX";
+ }
+
+ if (args.options) {
+ if (strchr(args.options, 'j'))
+ priority_jump = 1;
+ }
+
+ vmcount = ast_app_messagecount(context, vmbox, vmfolder);
+ /* Set the count in the channel variable */
+ if (varname) {
+ snprintf(tmp, sizeof(tmp), "%d", vmcount);
+ pbx_builtin_setvar_helper(chan, varname, tmp);
+ }
+
+ if (vmcount > 0) {
+ /* Branch to the next extension */
+ if (priority_jump || ast_opt_priority_jumping) {
+ if (ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101))
+ ast_log(LOG_WARNING, "VM box %s@%s has new voicemail, but extension %s, priority %d doesn't exist\n", vmbox, context, chan->exten, chan->priority + 101);
+ }
+ }
+
+ snprintf(tmp, sizeof(tmp), "%d", vmcount);
+ pbx_builtin_setvar_helper(chan, "HASVMSTATUS", tmp);
+
+ ast_module_user_remove(u);
+
+ return 0;
+}
+
+static int acf_vmcount_exec(struct ast_channel *chan, char *cmd, char *argsstr, char *buf, size_t len)
+{
+ struct ast_module_user *u;
+ char *context;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(vmbox);
+ AST_APP_ARG(folder);
+ );
+
+ if (ast_strlen_zero(argsstr))
+ return -1;
+
+ u = ast_module_user_add(chan);
+
+ buf[0] = '\0';
+
+ AST_STANDARD_APP_ARGS(args, argsstr);
+
+ if (strchr(args.vmbox, '@')) {
+ context = args.vmbox;
+ args.vmbox = strsep(&context, "@");
+ } else {
+ context = "default";
+ }
+
+ if (ast_strlen_zero(args.folder)) {
+ args.folder = "INBOX";
+ }
+
+ snprintf(buf, len, "%d", ast_app_messagecount(context, args.vmbox, args.folder));
+
+ ast_module_user_remove(u);
+
+ return 0;
+}
+
+struct ast_custom_function acf_vmcount = {
+ .name = "VMCOUNT",
+ .synopsis = "Counts the voicemail in a specified mailbox",
+ .syntax = "VMCOUNT(vmbox[@context][|folder])",
+ .desc =
+ " context - defaults to \"default\"\n"
+ " folder - defaults to \"INBOX\"\n",
+ .read = acf_vmcount_exec,
+};
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_custom_function_unregister(&acf_vmcount);
+ res |= ast_unregister_application(app_hasvoicemail);
+ res |= ast_unregister_application(app_hasnewvoicemail);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ int res;
+
+ res = ast_custom_function_register(&acf_vmcount);
+ res |= ast_register_application(app_hasvoicemail, hasvoicemail_exec, hasvoicemail_synopsis, hasvoicemail_descrip);
+ res |= ast_register_application(app_hasnewvoicemail, hasvoicemail_exec, hasnewvoicemail_synopsis, hasnewvoicemail_descrip);
+
+ return res;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Indicator for whether a voice mailbox has messages in a given folder.");
diff --git a/apps/app_ices.c b/apps/app_ices.c
new file mode 100644
index 000000000..eb5980f93
--- /dev/null
+++ b/apps/app_ices.c
@@ -0,0 +1,233 @@
+/*
+ * 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 Stream to an icecast server via ICES (see contrib/asterisk-ices.xml)
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <string.h>
+#include <stdio.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <errno.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/frame.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/options.h"
+
+#define path_BIN "/usr/bin/"
+#define path_LOCAL "/usr/local/bin/"
+
+static char *app = "ICES";
+
+static char *synopsis = "Encode and stream using 'ices'";
+
+static char *descrip =
+" ICES(config.xml) Streams to an icecast server using ices\n"
+"(available separately). A configuration file must be supplied\n"
+"for ices (see contrib/asterisk-ices.xml). \n";
+
+static int icesencode(char *filename, int fd)
+{
+ int res;
+ int x;
+ sigset_t fullset, oldset;
+
+ sigfillset(&fullset);
+ pthread_sigmask(SIG_BLOCK, &fullset, &oldset);
+
+ res = fork();
+ if (res < 0)
+ ast_log(LOG_WARNING, "Fork failed\n");
+ if (res) {
+ pthread_sigmask(SIG_SETMASK, &oldset, NULL);
+ return res;
+ }
+
+ /* Stop ignoring PIPE */
+ signal(SIGPIPE, SIG_DFL);
+ pthread_sigmask(SIG_UNBLOCK, &fullset, NULL);
+
+ if (ast_opt_high_priority)
+ ast_set_priority(0);
+ dup2(fd, STDIN_FILENO);
+ for (x=STDERR_FILENO + 1;x<1024;x++) {
+ if ((x != STDIN_FILENO) && (x != STDOUT_FILENO))
+ close(x);
+ }
+
+ /* Most commonly installed in /usr/local/bin
+ * But many places has it in /usr/bin
+ * As a last-ditch effort, try to use PATH
+ */
+ execl(path_LOCAL "ices2", "ices", filename, (char *)NULL);
+ execl(path_BIN "ices2", "ices", filename, (char *)NULL);
+ execlp("ices2", "ices", filename, (char *)NULL);
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Couldn't find ices version 2, attempting to use ices version 1.");
+
+ execl(path_LOCAL "ices", "ices", filename, (char *)NULL);
+ execl(path_BIN "ices", "ices", filename, (char *)NULL);
+ execlp("ices", "ices", filename, (char *)NULL);
+
+ ast_log(LOG_WARNING, "Execute of ices failed, could not be found.\n");
+ close(fd);
+ _exit(0);
+}
+
+static int ices_exec(struct ast_channel *chan, void *data)
+{
+ int res=0;
+ struct ast_module_user *u;
+ int fds[2];
+ int ms = -1;
+ int pid = -1;
+ int flags;
+ int oreadformat;
+ struct timeval last;
+ struct ast_frame *f;
+ char filename[256]="";
+ char *c;
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "ICES requires an argument (configfile.xml)\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ last = ast_tv(0, 0);
+
+ if (pipe(fds)) {
+ ast_log(LOG_WARNING, "Unable to create pipe\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+ flags = fcntl(fds[1], F_GETFL);
+ fcntl(fds[1], F_SETFL, flags | O_NONBLOCK);
+
+ ast_stopstream(chan);
+
+ if (chan->_state != AST_STATE_UP)
+ res = ast_answer(chan);
+
+ if (res) {
+ close(fds[0]);
+ close(fds[1]);
+ ast_log(LOG_WARNING, "Answer failed!\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ oreadformat = chan->readformat;
+ res = ast_set_read_format(chan, AST_FORMAT_SLINEAR);
+ if (res < 0) {
+ close(fds[0]);
+ close(fds[1]);
+ ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+ if (((char *)data)[0] == '/')
+ ast_copy_string(filename, (char *) data, sizeof(filename));
+ else
+ snprintf(filename, sizeof(filename), "%s/%s", (char *)ast_config_AST_CONFIG_DIR, (char *)data);
+ /* Placeholder for options */
+ c = strchr(filename, '|');
+ if (c)
+ *c = '\0';
+ res = icesencode(filename, fds[0]);
+ if (res >= 0) {
+ pid = res;
+ for (;;) {
+ /* Wait for audio, and stream */
+ ms = ast_waitfor(chan, -1);
+ if (ms < 0) {
+ ast_log(LOG_DEBUG, "Hangup detected\n");
+ res = -1;
+ break;
+ }
+ f = ast_read(chan);
+ if (!f) {
+ ast_log(LOG_DEBUG, "Null frame == hangup() detected\n");
+ res = -1;
+ break;
+ }
+ if (f->frametype == AST_FRAME_VOICE) {
+ res = write(fds[1], f->data, f->datalen);
+ if (res < 0) {
+ if (errno != EAGAIN) {
+ ast_log(LOG_WARNING, "Write failed to pipe: %s\n", strerror(errno));
+ res = -1;
+ ast_frfree(f);
+ break;
+ }
+ }
+ }
+ ast_frfree(f);
+ }
+ }
+ close(fds[0]);
+ close(fds[1]);
+
+ if (pid > -1)
+ kill(pid, SIGKILL);
+ if (!res && oreadformat)
+ ast_set_read_format(chan, oreadformat);
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, ices_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Encode and Stream via icecast and ices");
diff --git a/apps/app_image.c b/apps/app_image.c
new file mode 100644
index 000000000..f5f327197
--- /dev/null
+++ b/apps/app_image.c
@@ -0,0 +1,125 @@
+/*
+ * 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 App to transmit an image
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/image.h"
+#include "asterisk/app.h"
+#include "asterisk/options.h"
+
+static char *app = "SendImage";
+
+static char *synopsis = "Send an image file";
+
+static char *descrip =
+" SendImage(filename): Sends an image on a channel. \n"
+"If the channel supports image transport but the image send\n"
+"fails, the channel will be hung up. Otherwise, the dialplan\n"
+"continues execution.\n"
+"The option string may contain the following character:\n"
+" 'j' -- jump to priority n+101 if the channel doesn't support image transport\n"
+"This application sets the following channel variable upon completion:\n"
+" SENDIMAGESTATUS The status is the result of the attempt as a text string, one of\n"
+" OK | NOSUPPORT \n";
+
+
+static int sendimage_exec(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ struct ast_module_user *u;
+ char *parse;
+ int priority_jump = 0;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(filename);
+ AST_APP_ARG(options);
+ );
+
+ u = ast_module_user_add(chan);
+
+ parse = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ if (ast_strlen_zero(args.filename)) {
+ ast_log(LOG_WARNING, "SendImage requires an argument (filename[|options])\n");
+ return -1;
+ }
+
+ if (args.options) {
+ if (strchr(args.options, 'j'))
+ priority_jump = 1;
+ }
+
+ if (!ast_supports_images(chan)) {
+ /* Does not support transport */
+ if (priority_jump || ast_opt_priority_jumping)
+ ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
+ pbx_builtin_setvar_helper(chan, "SENDIMAGESTATUS", "NOSUPPORT");
+ ast_module_user_remove(u);
+ return 0;
+ }
+
+ res = ast_send_image(chan, args.filename);
+
+ if (!res)
+ pbx_builtin_setvar_helper(chan, "SENDIMAGESTATUS", "OK");
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, sendimage_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Image Transmission Application");
diff --git a/apps/app_ivrdemo.c b/apps/app_ivrdemo.c
new file mode 100644
index 000000000..933ae9040
--- /dev/null
+++ b/apps/app_ivrdemo.c
@@ -0,0 +1,132 @@
+/*
+ * 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 IVR Demo application
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+ <defaultenabled>no</defaultenabled>
+ ***/
+
+#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/module.h"
+#include "asterisk/lock.h"
+#include "asterisk/app.h"
+
+static char *tdesc = "IVR Demo Application";
+static char *app = "IVRDemo";
+static char *synopsis =
+" This is a skeleton application that shows you the basic structure to create your\n"
+"own asterisk applications and demonstrates the IVR demo.\n";
+
+static int ivr_demo_func(struct ast_channel *chan, void *data)
+{
+ ast_verbose("IVR Demo, data is %s!\n", (char *)data);
+ return 0;
+}
+
+AST_IVR_DECLARE_MENU(ivr_submenu, "IVR Demo Sub Menu", 0,
+{
+ { "s", AST_ACTION_BACKGROUND, "demo-abouttotry" },
+ { "s", AST_ACTION_WAITOPTION },
+ { "1", AST_ACTION_PLAYBACK, "digits/1" },
+ { "1", AST_ACTION_PLAYBACK, "digits/1" },
+ { "1", AST_ACTION_RESTART },
+ { "2", AST_ACTION_PLAYLIST, "digits/2;digits/3" },
+ { "3", AST_ACTION_CALLBACK, ivr_demo_func },
+ { "4", AST_ACTION_TRANSFER, "demo|s|1" },
+ { "*", AST_ACTION_REPEAT },
+ { "#", AST_ACTION_UPONE },
+ { NULL }
+});
+
+AST_IVR_DECLARE_MENU(ivr_demo, "IVR Demo Main Menu", 0,
+{
+ { "s", AST_ACTION_BACKGROUND, "demo-congrats" },
+ { "g", AST_ACTION_BACKGROUND, "demo-instruct" },
+ { "g", AST_ACTION_WAITOPTION },
+ { "1", AST_ACTION_PLAYBACK, "digits/1" },
+ { "1", AST_ACTION_RESTART },
+ { "2", AST_ACTION_MENU, &ivr_submenu },
+ { "2", AST_ACTION_RESTART },
+ { "i", AST_ACTION_PLAYBACK, "invalid" },
+ { "i", AST_ACTION_REPEAT, (void *)(unsigned long)2 },
+ { "#", AST_ACTION_EXIT },
+ { NULL },
+});
+
+
+static int skel_exec(struct ast_channel *chan, void *data)
+{
+ int res=0;
+ struct ast_module_user *u;
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "skel requires an argument (filename)\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ /* Do our thing here */
+
+ if (chan->_state != AST_STATE_UP)
+ res = ast_answer(chan);
+ if (!res)
+ res = ast_ivr_menu_run(chan, &ivr_demo, data);
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, skel_exec, tdesc, synopsis);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "IVR Demo Application");
diff --git a/apps/app_lookupblacklist.c b/apps/app_lookupblacklist.c
new file mode 100644
index 000000000..88ed52d86
--- /dev/null
+++ b/apps/app_lookupblacklist.c
@@ -0,0 +1,160 @@
+/*
+ * 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 App to lookup the callerid number, and see if it is blacklisted
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ *
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/options.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/image.h"
+#include "asterisk/callerid.h"
+#include "asterisk/astdb.h"
+#include "asterisk/options.h"
+
+static char *app = "LookupBlacklist";
+
+static char *synopsis = "Look up Caller*ID name/number from blacklist database";
+
+static char *descrip =
+ " LookupBlacklist(options): Looks up the Caller*ID number on the active\n"
+ "channel in the Asterisk database (family 'blacklist'). \n"
+ "The option string may contain the following character:\n"
+ " 'j' -- jump to n+101 priority if the number/name is found in the blacklist\n"
+ "This application sets the following channel variable upon completion:\n"
+ " LOOKUPBLSTATUS The status of the Blacklist lookup as a text string, one of\n"
+ " FOUND | NOTFOUND\n"
+ "Example: exten => 1234,1,LookupBlacklist()\n\n"
+ "This application is deprecated and may be removed from a future release.\n"
+ "Please use the dialplan function BLACKLIST() instead.\n";
+
+
+static int blacklist_read(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len)
+{
+ char blacklist[1];
+ int bl = 0;
+
+ if (chan->cid.cid_num) {
+ if (!ast_db_get("blacklist", chan->cid.cid_num, blacklist, sizeof (blacklist)))
+ bl = 1;
+ }
+ if (chan->cid.cid_name) {
+ if (!ast_db_get("blacklist", chan->cid.cid_name, blacklist, sizeof (blacklist)))
+ bl = 1;
+ }
+
+ snprintf(buf, len, "%d", bl);
+ return 0;
+}
+
+static struct ast_custom_function blacklist_function = {
+ .name = "BLACKLIST",
+ .synopsis = "Check if the callerid is on the blacklist",
+ .desc = "Uses astdb to check if the Caller*ID is in family 'blacklist'. Returns 1 or 0.\n",
+ .syntax = "BLACKLIST()",
+ .read = blacklist_read,
+};
+
+static int
+lookupblacklist_exec (struct ast_channel *chan, void *data)
+{
+ char blacklist[1];
+ struct ast_module_user *u;
+ int bl = 0;
+ int priority_jump = 0;
+ static int dep_warning = 0;
+
+ u = ast_module_user_add(chan);
+
+ if (!dep_warning) {
+ dep_warning = 1;
+ ast_log(LOG_WARNING, "LookupBlacklist is deprecated. Please use ${BLACKLIST()} instead.\n");
+ }
+
+ if (!ast_strlen_zero(data)) {
+ if (strchr(data, 'j'))
+ priority_jump = 1;
+ }
+
+ if (chan->cid.cid_num) {
+ if (!ast_db_get("blacklist", chan->cid.cid_num, blacklist, sizeof (blacklist))) {
+ if (option_verbose > 2)
+ ast_log(LOG_NOTICE, "Blacklisted number %s found\n",chan->cid.cid_num);
+ bl = 1;
+ }
+ }
+ if (chan->cid.cid_name) {
+ if (!ast_db_get("blacklist", chan->cid.cid_name, blacklist, sizeof (blacklist))) {
+ if (option_verbose > 2)
+ ast_log (LOG_NOTICE,"Blacklisted name \"%s\" found\n",chan->cid.cid_name);
+ bl = 1;
+ }
+ }
+
+ if (bl) {
+ if (priority_jump || ast_opt_priority_jumping)
+ ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
+ pbx_builtin_setvar_helper(chan, "LOOKUPBLSTATUS", "FOUND");
+ } else
+ pbx_builtin_setvar_helper(chan, "LOOKUPBLSTATUS", "NOTFOUND");
+
+ ast_module_user_remove(u);
+
+ return 0;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+ res |= ast_custom_function_unregister(&blacklist_function);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ int res = ast_custom_function_register(&blacklist_function);
+ res |= ast_register_application (app, lookupblacklist_exec, synopsis,descrip);
+ return res;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Look up Caller*ID name/number from blacklist database");
diff --git a/apps/app_lookupcidname.c b/apps/app_lookupcidname.c
new file mode 100644
index 000000000..8b8d5207a
--- /dev/null
+++ b/apps/app_lookupcidname.c
@@ -0,0 +1,103 @@
+/*
+ * 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 App to set callerid name from database, based on directory number
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/options.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/image.h"
+#include "asterisk/callerid.h"
+#include "asterisk/astdb.h"
+
+static char *app = "LookupCIDName";
+
+static char *synopsis = "Look up CallerID Name from local database";
+
+static char *descrip =
+ " LookupCIDName: Looks up the Caller*ID number on the active\n"
+ "channel in the Asterisk database (family 'cidname') and sets the\n"
+ "Caller*ID name. Does nothing if no Caller*ID was received on the\n"
+ "channel. This is useful if you do not subscribe to Caller*ID\n"
+ "name delivery, or if you want to change the names on some incoming\n"
+ "calls.\n\n"
+ "LookupCIDName is deprecated. Please use ${DB(cidname/${CALLERID(num)})}\n"
+ "instead.\n";
+
+
+static int lookupcidname_exec (struct ast_channel *chan, void *data)
+{
+ char dbname[64];
+ struct ast_module_user *u;
+ static int dep_warning = 0;
+
+ u = ast_module_user_add(chan);
+ if (!dep_warning) {
+ dep_warning = 1;
+ ast_log(LOG_WARNING, "LookupCIDName is deprecated. Please use ${DB(cidname/${CALLERID(num)})} instead.\n");
+ }
+ if (chan->cid.cid_num) {
+ if (!ast_db_get ("cidname", chan->cid.cid_num, dbname, sizeof (dbname))) {
+ ast_set_callerid (chan, NULL, dbname, NULL);
+ if (option_verbose > 2)
+ ast_verbose (VERBOSE_PREFIX_3 "Changed Caller*ID name to %s\n",
+ dbname);
+ }
+ }
+ ast_module_user_remove(u);
+
+ return 0;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application (app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application (app, lookupcidname_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Look up CallerID Name from local database");
diff --git a/apps/app_macro.c b/apps/app_macro.c
new file mode 100644
index 000000000..5296e8723
--- /dev/null
+++ b/apps/app_macro.c
@@ -0,0 +1,592 @@
+/*
+ * 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 Dial plan macro Implementation
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/options.h"
+#include "asterisk/config.h"
+#include "asterisk/utils.h"
+#include "asterisk/lock.h"
+
+#define MAX_ARGS 80
+
+/* special result value used to force macro exit */
+#define MACRO_EXIT_RESULT 1024
+
+static char *descrip =
+" Macro(macroname|arg1|arg2...): Executes a macro using the context\n"
+"'macro-<macroname>', jumping to the 's' extension of that context and\n"
+"executing each step, then returning when the steps end. \n"
+"The calling extension, context, and priority are stored in ${MACRO_EXTEN}, \n"
+"${MACRO_CONTEXT} and ${MACRO_PRIORITY} respectively. Arguments become\n"
+"${ARG1}, ${ARG2}, etc in the macro context.\n"
+"If you Goto out of the Macro context, the Macro will terminate and control\n"
+"will be returned at the location of the Goto.\n"
+"If ${MACRO_OFFSET} is set at termination, Macro will attempt to continue\n"
+"at priority MACRO_OFFSET + N + 1 if such a step exists, and N + 1 otherwise.\n"
+"Extensions: While a macro is being executed, it becomes the current context.\n"
+" This means that if a hangup occurs, for instance, that the macro\n"
+" will be searched for an 'h' extension, NOT the context from which\n"
+" the macro was called. So, make sure to define all appropriate\n"
+" extensions in your macro! (you can use 'catch' in AEL) \n"
+"WARNING: Because of the way Macro is implemented (it executes the priorities\n"
+" contained within it via sub-engine), and a fixed per-thread\n"
+" memory stack allowance, macros are limited to 7 levels\n"
+" of nesting (macro calling macro calling macro, etc.); It\n"
+" may be possible that stack-intensive applications in deeply nested macros\n"
+" could cause asterisk to crash earlier than this limit.\n";
+
+static char *if_descrip =
+" MacroIf(<expr>?macroname_a[|arg1][:macroname_b[|arg1]])\n"
+"Executes macro defined in <macroname_a> if <expr> is true\n"
+"(otherwise <macroname_b> if provided)\n"
+"Arguments and return values as in application macro()\n";
+
+static char *exclusive_descrip =
+" MacroExclusive(macroname|arg1|arg2...):\n"
+"Executes macro defined in the context 'macro-macroname'\n"
+"Only one call at a time may run the macro.\n"
+"(we'll wait if another call is busy executing in the Macro)\n"
+"Arguments and return values as in application Macro()\n";
+
+static char *exit_descrip =
+" MacroExit():\n"
+"Causes the currently running macro to exit as if it had\n"
+"ended normally by running out of priorities to execute.\n"
+"If used outside a macro, will likely cause unexpected\n"
+"behavior.\n";
+
+static char *app = "Macro";
+static char *if_app = "MacroIf";
+static char *exclusive_app = "MacroExclusive";
+static char *exit_app = "MacroExit";
+
+static char *synopsis = "Macro Implementation";
+static char *if_synopsis = "Conditional Macro Implementation";
+static char *exclusive_synopsis = "Exclusive Macro Implementation";
+static char *exit_synopsis = "Exit From Macro";
+
+static void macro_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan);
+
+struct ast_datastore_info macro_ds_info = {
+ .type = "MACRO",
+ .chan_fixup = macro_fixup,
+};
+
+static void macro_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
+{
+ int i;
+ char varname[10];
+ pbx_builtin_setvar_helper(new_chan, "MACRO_DEPTH", "0");
+ pbx_builtin_setvar_helper(new_chan, "MACRO_CONTEXT", NULL);
+ pbx_builtin_setvar_helper(new_chan, "MACRO_EXTEN", NULL);
+ pbx_builtin_setvar_helper(new_chan, "MACRO_PRIORITY", NULL);
+ pbx_builtin_setvar_helper(new_chan, "MACRO_OFFSET", NULL);
+ for (i = 1; i < 100; i++) {
+ snprintf(varname, sizeof(varname), "ARG%d", i);
+ while (pbx_builtin_getvar_helper(new_chan, varname)) {
+ /* Kill all levels of arguments */
+ pbx_builtin_setvar_helper(new_chan, varname, NULL);
+ }
+ }
+}
+
+static struct ast_exten *find_matching_priority(struct ast_context *c, const char *exten, int priority, const char *callerid)
+{
+ struct ast_exten *e;
+ struct ast_include *i;
+ struct ast_context *c2;
+
+ for (e=ast_walk_context_extensions(c, NULL); e; e=ast_walk_context_extensions(c, e)) {
+ if (ast_extension_match(ast_get_extension_name(e), exten)) {
+ int needmatch = ast_get_extension_matchcid(e);
+ if ((needmatch && ast_extension_match(ast_get_extension_cidmatch(e), callerid)) ||
+ (!needmatch)) {
+ /* This is the matching extension we want */
+ struct ast_exten *p;
+ for (p=ast_walk_extension_priorities(e, NULL); p; p=ast_walk_extension_priorities(e, p)) {
+ if (priority != ast_get_extension_priority(p))
+ continue;
+ return p;
+ }
+ }
+ }
+ }
+
+ /* No match; run through includes */
+ for (i=ast_walk_context_includes(c, NULL); i; i=ast_walk_context_includes(c, i)) {
+ for (c2=ast_walk_contexts(NULL); c2; c2=ast_walk_contexts(c2)) {
+ if (!strcmp(ast_get_context_name(c2), ast_get_include_name(i))) {
+ e = find_matching_priority(c2, exten, priority, callerid);
+ if (e)
+ return e;
+ }
+ }
+ }
+ return NULL;
+}
+
+static int _macro_exec(struct ast_channel *chan, void *data, int exclusive)
+{
+ const char *s;
+ char *tmp;
+ char *cur, *rest;
+ char *macro;
+ char fullmacro[80];
+ char varname[80];
+ char runningapp[80], runningdata[1024];
+ char *oldargs[MAX_ARGS + 1] = { NULL, };
+ int argc, x;
+ int res=0;
+ char oldexten[256]="";
+ int oldpriority, gosub_level = 0;
+ char pc[80], depthc[12];
+ char oldcontext[AST_MAX_CONTEXT] = "";
+ const char *inhangupc;
+ int offset, depth = 0, maxdepth = 7;
+ int setmacrocontext=0;
+ int autoloopflag, inhangup = 0;
+
+ char *save_macro_exten;
+ char *save_macro_context;
+ char *save_macro_priority;
+ char *save_macro_offset;
+ struct ast_module_user *u;
+ struct ast_datastore *macro_store = ast_channel_datastore_find(chan, &macro_ds_info, NULL);
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "Macro() requires arguments. See \"show application macro\" for help.\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ do {
+ if (macro_store) {
+ break;
+ }
+ if (!(macro_store = ast_channel_datastore_alloc(&macro_ds_info, NULL))) {
+ ast_log(LOG_WARNING, "Unable to allocate new datastore.\n");
+ break;
+ }
+ /* Just the existence of this datastore is enough. */
+ macro_store->inheritance = DATASTORE_INHERIT_FOREVER;
+ ast_channel_datastore_add(chan, macro_store);
+ } while (0);
+
+ /* does the user want a deeper rabbit hole? */
+ s = pbx_builtin_getvar_helper(chan, "MACRO_RECURSION");
+ if (s)
+ sscanf(s, "%d", &maxdepth);
+
+ /* Count how many levels deep the rabbit hole goes */
+ s = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH");
+ if (s)
+ sscanf(s, "%d", &depth);
+ /* Used for detecting whether to return when a Macro is called from another Macro after hangup */
+ if (strcmp(chan->exten, "h") == 0)
+ pbx_builtin_setvar_helper(chan, "MACRO_IN_HANGUP", "1");
+ inhangupc = pbx_builtin_getvar_helper(chan, "MACRO_IN_HANGUP");
+ if (!ast_strlen_zero(inhangupc))
+ sscanf(inhangupc, "%d", &inhangup);
+
+ if (depth >= maxdepth) {
+ ast_log(LOG_ERROR, "Macro(): possible infinite loop detected. Returning early.\n");
+ ast_module_user_remove(u);
+ return 0;
+ }
+ snprintf(depthc, sizeof(depthc), "%d", depth + 1);
+ pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
+
+ tmp = ast_strdupa(data);
+ rest = tmp;
+ macro = strsep(&rest, "|");
+ if (ast_strlen_zero(macro)) {
+ ast_log(LOG_WARNING, "Invalid macro name specified\n");
+ ast_module_user_remove(u);
+ return 0;
+ }
+
+ snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
+ if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
+ if (!ast_context_find(fullmacro))
+ ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
+ else
+ ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
+ ast_module_user_remove(u);
+ return 0;
+ }
+
+ /* If we are to run the macro exclusively, take the mutex */
+ if (exclusive) {
+ ast_log(LOG_DEBUG, "Locking macrolock for '%s'\n", fullmacro);
+ ast_autoservice_start(chan);
+ if (ast_context_lockmacro(fullmacro)) {
+ ast_log(LOG_WARNING, "Failed to lock macro '%s' as in-use\n", fullmacro);
+ ast_autoservice_stop(chan);
+ ast_module_user_remove(u);
+
+ return 0;
+ }
+ ast_autoservice_stop(chan);
+ }
+
+ /* Save old info */
+ oldpriority = chan->priority;
+ ast_copy_string(oldexten, chan->exten, sizeof(oldexten));
+ ast_copy_string(oldcontext, chan->context, sizeof(oldcontext));
+ if (ast_strlen_zero(chan->macrocontext)) {
+ ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext));
+ ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten));
+ chan->macropriority = chan->priority;
+ setmacrocontext=1;
+ }
+ argc = 1;
+ /* Save old macro variables */
+ save_macro_exten = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_EXTEN"));
+ pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
+
+ save_macro_context = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"));
+ pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
+
+ save_macro_priority = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY"));
+ snprintf(pc, sizeof(pc), "%d", oldpriority);
+ pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
+
+ save_macro_offset = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"));
+ pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
+
+ /* Setup environment for new run */
+ chan->exten[0] = 's';
+ chan->exten[1] = '\0';
+ ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
+ chan->priority = 1;
+
+ while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) {
+ const char *s;
+ /* Save copy of old arguments if we're overwriting some, otherwise
+ let them pass through to the other macro */
+ snprintf(varname, sizeof(varname), "ARG%d", argc);
+ s = pbx_builtin_getvar_helper(chan, varname);
+ if (s)
+ oldargs[argc] = ast_strdup(s);
+ pbx_builtin_setvar_helper(chan, varname, cur);
+ argc++;
+ }
+ autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
+ ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
+ while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
+ struct ast_context *c;
+ struct ast_exten *e;
+ runningapp[0] = '\0';
+ runningdata[0] = '\0';
+
+ /* What application will execute? */
+ if (ast_rdlock_contexts()) {
+ ast_log(LOG_WARNING, "Failed to lock contexts list\n");
+ } else {
+ for (c = ast_walk_contexts(NULL), e = NULL; c; c = ast_walk_contexts(c)) {
+ if (!strcmp(ast_get_context_name(c), chan->context)) {
+ if (ast_lock_context(c)) {
+ ast_log(LOG_WARNING, "Unable to lock context?\n");
+ } else {
+ e = find_matching_priority(c, chan->exten, chan->priority, chan->cid.cid_num);
+ if (e) { /* This will only be undefined for pbx_realtime, which is majorly broken. */
+ ast_copy_string(runningapp, ast_get_extension_app(e), sizeof(runningapp));
+ ast_copy_string(runningdata, ast_get_extension_app_data(e), sizeof(runningdata));
+ }
+ ast_unlock_context(c);
+ }
+ break;
+ }
+ }
+ }
+ ast_unlock_contexts();
+
+ /* Reset the macro depth, if it was changed in the last iteration */
+ pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
+
+ if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num))) {
+ /* Something bad happened, or a hangup has been requested. */
+ if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
+ (res == '*') || (res == '#')) {
+ /* Just return result as to the previous application as if it had been dialed */
+ ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
+ break;
+ }
+ switch(res) {
+ case MACRO_EXIT_RESULT:
+ res = 0;
+ goto out;
+ case AST_PBX_KEEPALIVE:
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Spawn extension (%s,%s,%d) exited KEEPALIVE in macro %s on '%s'\n", chan->context, chan->exten, chan->priority, macro, chan->name);
+ else if (option_verbose > 1)
+ ast_verbose( VERBOSE_PREFIX_2 "Spawn extension (%s, %s, %d) exited KEEPALIVE in macro '%s' on '%s'\n", chan->context, chan->exten, chan->priority, macro, chan->name);
+ goto out;
+ default:
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Spawn extension (%s,%s,%d) exited non-zero on '%s' in macro '%s'\n", chan->context, chan->exten, chan->priority, chan->name, macro);
+ else if (option_verbose > 1)
+ ast_verbose( VERBOSE_PREFIX_2 "Spawn extension (%s, %s, %d) exited non-zero on '%s' in macro '%s'\n", chan->context, chan->exten, chan->priority, chan->name, macro);
+ goto out;
+ }
+ }
+
+ ast_log(LOG_DEBUG, "Executed application: %s\n", runningapp);
+
+ if (!strcasecmp(runningapp, "GOSUB")) {
+ gosub_level++;
+ ast_log(LOG_DEBUG, "Incrementing gosub_level\n");
+ } else if (!strcasecmp(runningapp, "GOSUBIF")) {
+ char tmp2[1024] = "", *cond, *app, *app2 = tmp2;
+ pbx_substitute_variables_helper(chan, runningdata, tmp2, sizeof(tmp2) - 1);
+ cond = strsep(&app2, "?");
+ app = strsep(&app2, ":");
+ if (pbx_checkcondition(cond)) {
+ if (!ast_strlen_zero(app)) {
+ gosub_level++;
+ ast_log(LOG_DEBUG, "Incrementing gosub_level\n");
+ }
+ } else {
+ if (!ast_strlen_zero(app2)) {
+ gosub_level++;
+ ast_log(LOG_DEBUG, "Incrementing gosub_level\n");
+ }
+ }
+ } else if (!strcasecmp(runningapp, "RETURN")) {
+ gosub_level--;
+ ast_log(LOG_DEBUG, "Decrementing gosub_level\n");
+ } else if (!strcasecmp(runningapp, "STACKPOP")) {
+ gosub_level--;
+ ast_log(LOG_DEBUG, "Decrementing gosub_level\n");
+ } else if (!strncasecmp(runningapp, "EXEC", 4)) {
+ /* Must evaluate args to find actual app */
+ char tmp2[1024] = "", *tmp3 = NULL;
+ pbx_substitute_variables_helper(chan, runningdata, tmp2, sizeof(tmp2) - 1);
+ if (!strcasecmp(runningapp, "EXECIF")) {
+ tmp3 = strchr(tmp2, '|');
+ if (tmp3)
+ *tmp3++ = '\0';
+ if (!pbx_checkcondition(tmp2))
+ tmp3 = NULL;
+ } else
+ tmp3 = tmp2;
+
+ if (tmp3)
+ ast_log(LOG_DEBUG, "Last app: %s\n", tmp3);
+
+ if (tmp3 && !strncasecmp(tmp3, "GOSUB", 5)) {
+ gosub_level++;
+ ast_log(LOG_DEBUG, "Incrementing gosub_level\n");
+ } else if (tmp3 && !strncasecmp(tmp3, "RETURN", 6)) {
+ gosub_level--;
+ ast_log(LOG_DEBUG, "Decrementing gosub_level\n");
+ } else if (tmp3 && !strncasecmp(tmp3, "STACKPOP", 8)) {
+ gosub_level--;
+ ast_log(LOG_DEBUG, "Decrementing gosub_level\n");
+ }
+ }
+
+ if (gosub_level == 0 && strcasecmp(chan->context, fullmacro)) {
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
+ break;
+ }
+
+ /* don't stop executing extensions when we're in "h" */
+ if (chan->_softhangup && !inhangup) {
+ ast_log(LOG_DEBUG, "Extension %s, macroexten %s, priority %d returned normally even though call was hung up\n",
+ chan->exten, chan->macroexten, chan->priority);
+ goto out;
+ }
+ chan->priority++;
+ }
+ out:
+
+ /* Don't let the channel change now. */
+ ast_channel_lock(chan);
+
+ /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
+ snprintf(depthc, sizeof(depthc), "%d", depth);
+ pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
+ ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
+
+ for (x = 1; x < argc; x++) {
+ /* Restore old arguments and delete ours */
+ snprintf(varname, sizeof(varname), "ARG%d", x);
+ if (oldargs[x]) {
+ pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
+ free(oldargs[x]);
+ } else {
+ pbx_builtin_setvar_helper(chan, varname, NULL);
+ }
+ }
+
+ /* Restore macro variables */
+ pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
+ pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
+ pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
+ if (save_macro_exten)
+ free(save_macro_exten);
+ if (save_macro_context)
+ free(save_macro_context);
+ if (save_macro_priority)
+ free(save_macro_priority);
+
+ if (setmacrocontext) {
+ chan->macrocontext[0] = '\0';
+ chan->macroexten[0] = '\0';
+ chan->macropriority = 0;
+ }
+
+ if (!strcasecmp(chan->context, fullmacro)) {
+ /* If we're leaving the macro normally, restore original information */
+ chan->priority = oldpriority;
+ ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
+ if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) {
+ /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
+ const char *offsets;
+ ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
+ if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
+ /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
+ normally if there is any problem */
+ if (sscanf(offsets, "%d", &offset) == 1) {
+ if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
+ chan->priority += offset;
+ }
+ }
+ }
+ }
+ }
+
+ pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
+ if (save_macro_offset)
+ free(save_macro_offset);
+
+ /* Unlock the macro */
+ if (exclusive) {
+ ast_log(LOG_DEBUG, "Unlocking macrolock for '%s'\n", fullmacro);
+ if (ast_context_unlockmacro(fullmacro)) {
+ ast_log(LOG_ERROR, "Failed to unlock macro '%s' - that isn't good\n", fullmacro);
+ res = 0;
+ }
+ }
+ ast_channel_unlock(chan);
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int macro_exec(struct ast_channel *chan, void *data)
+{
+ return _macro_exec(chan, data, 0);
+}
+
+static int macroexclusive_exec(struct ast_channel *chan, void *data)
+{
+ return _macro_exec(chan, data, 1);
+}
+
+static int macroif_exec(struct ast_channel *chan, void *data)
+{
+ char *expr = NULL, *label_a = NULL, *label_b = NULL;
+ int res = 0;
+ struct ast_module_user *u;
+
+ u = ast_module_user_add(chan);
+
+ if (!(expr = ast_strdupa(data))) {
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ if ((label_a = strchr(expr, '?'))) {
+ *label_a = '\0';
+ label_a++;
+ if ((label_b = strchr(label_a, ':'))) {
+ *label_b = '\0';
+ label_b++;
+ }
+ if (pbx_checkcondition(expr))
+ res = macro_exec(chan, label_a);
+ else if (label_b)
+ res = macro_exec(chan, label_b);
+ } else
+ ast_log(LOG_WARNING, "Invalid Syntax.\n");
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int macro_exit_exec(struct ast_channel *chan, void *data)
+{
+ return MACRO_EXIT_RESULT;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(if_app);
+ res |= ast_unregister_application(exit_app);
+ res |= ast_unregister_application(app);
+ res |= ast_unregister_application(exclusive_app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ int res;
+
+ res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
+ res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
+ res |= ast_register_application(exclusive_app, macroexclusive_exec, exclusive_synopsis, exclusive_descrip);
+ res |= ast_register_application(app, macro_exec, synopsis, descrip);
+
+ return res;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Macros");
diff --git a/apps/app_meetme.c b/apps/app_meetme.c
new file mode 100644
index 000000000..2503b0f38
--- /dev/null
+++ b/apps/app_meetme.c
@@ -0,0 +1,5037 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2007, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * SLA Implementation by:
+ * 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 Meet me conference bridge and Shared Line Appearances
+ *
+ * \author Mark Spencer <markster@digium.com>
+ * \author (SLA) Russell Bryant <russell@digium.com>
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+ <depend>dahdi</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/config.h"
+#include "asterisk/app.h"
+#include "asterisk/dsp.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/manager.h"
+#include "asterisk/options.h"
+#include "asterisk/cli.h"
+#include "asterisk/say.h"
+#include "asterisk/utils.h"
+#include "asterisk/translate.h"
+#include "asterisk/ulaw.h"
+#include "asterisk/astobj.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/devicestate.h"
+#include "asterisk/dial.h"
+#include "asterisk/causes.h"
+
+#include "asterisk/dahdi_compat.h"
+
+#include "enter.h"
+#include "leave.h"
+
+#define CONFIG_FILE_NAME "meetme.conf"
+#define SLA_CONFIG_FILE "sla.conf"
+
+/*! each buffer is 20ms, so this is 640ms total */
+#define DEFAULT_AUDIO_BUFFERS 32
+
+enum {
+ ADMINFLAG_MUTED = (1 << 1), /*!< User is muted */
+ ADMINFLAG_SELFMUTED = (1 << 2), /*!< User muted self */
+ ADMINFLAG_KICKME = (1 << 3) /*!< User has been kicked */
+};
+
+#define MEETME_DELAYDETECTTALK 300
+#define MEETME_DELAYDETECTENDTALK 1000
+
+#define AST_FRAME_BITS 32
+
+enum volume_action {
+ VOL_UP,
+ VOL_DOWN
+};
+
+enum entrance_sound {
+ ENTER,
+ LEAVE
+};
+
+enum recording_state {
+ MEETME_RECORD_OFF,
+ MEETME_RECORD_STARTED,
+ MEETME_RECORD_ACTIVE,
+ MEETME_RECORD_TERMINATE
+};
+
+#define CONF_SIZE 320
+
+enum {
+ /*! user has admin access on the conference */
+ CONFFLAG_ADMIN = (1 << 0),
+ /*! If set the user can only receive audio from the conference */
+ CONFFLAG_MONITOR = (1 << 1),
+ /*! If set asterisk will exit conference when '#' is pressed */
+ CONFFLAG_POUNDEXIT = (1 << 2),
+ /*! If set asterisk will provide a menu to the user when '*' is pressed */
+ CONFFLAG_STARMENU = (1 << 3),
+ /*! If set the use can only send audio to the conference */
+ CONFFLAG_TALKER = (1 << 4),
+ /*! If set there will be no enter or leave sounds */
+ CONFFLAG_QUIET = (1 << 5),
+ /*! If set, when user joins the conference, they will be told the number
+ * of users that are already in */
+ CONFFLAG_ANNOUNCEUSERCOUNT = (1 << 6),
+ /*! Set to run AGI Script in Background */
+ CONFFLAG_AGI = (1 << 7),
+ /*! Set to have music on hold when user is alone in conference */
+ CONFFLAG_MOH = (1 << 8),
+ /*! If set the MeetMe will return if all marked with this flag left */
+ CONFFLAG_MARKEDEXIT = (1 << 9),
+ /*! If set, the MeetMe will wait until a marked user enters */
+ CONFFLAG_WAITMARKED = (1 << 10),
+ /*! If set, the MeetMe will exit to the specified context */
+ CONFFLAG_EXIT_CONTEXT = (1 << 11),
+ /*! If set, the user will be marked */
+ CONFFLAG_MARKEDUSER = (1 << 12),
+ /*! If set, user will be ask record name on entry of conference */
+ CONFFLAG_INTROUSER = (1 << 13),
+ /*! If set, the MeetMe will be recorded */
+ CONFFLAG_RECORDCONF = (1<< 14),
+ /*! If set, the user will be monitored if the user is talking or not */
+ CONFFLAG_MONITORTALKER = (1 << 15),
+ CONFFLAG_DYNAMIC = (1 << 16),
+ CONFFLAG_DYNAMICPIN = (1 << 17),
+ CONFFLAG_EMPTY = (1 << 18),
+ CONFFLAG_EMPTYNOPIN = (1 << 19),
+ CONFFLAG_ALWAYSPROMPT = (1 << 20),
+ /*! If set, treats talking users as muted users */
+ CONFFLAG_OPTIMIZETALKER = (1 << 21),
+ /*! If set, won't speak the extra prompt when the first person
+ * enters the conference */
+ CONFFLAG_NOONLYPERSON = (1 << 22),
+ /*! If set, user will be asked to record name on entry of conference
+ * without review */
+ CONFFLAG_INTROUSERNOREVIEW = (1 << 23),
+ /*! If set, the user will be initially self-muted */
+ CONFFLAG_STARTMUTED = (1 << 24),
+ /*! Pass DTMF through the conference */
+ CONFFLAG_PASS_DTMF = (1 << 25),
+ /*! This is a SLA station. (Only for use by the SLA applications.) */
+ CONFFLAG_SLA_STATION = (1 << 26),
+ /*! This is a SLA trunk. (Only for use by the SLA applications.) */
+ CONFFLAG_SLA_TRUNK = (1 << 27),
+ /*! Do not write any audio to this channel until the state is up. */
+ CONFFLAG_NO_AUDIO_UNTIL_UP = (1 << 28),
+};
+
+enum {
+ OPT_ARG_WAITMARKED = 0,
+ OPT_ARG_ARRAY_SIZE = 1,
+};
+
+AST_APP_OPTIONS(meetme_opts, BEGIN_OPTIONS
+ AST_APP_OPTION('A', CONFFLAG_MARKEDUSER ),
+ AST_APP_OPTION('a', CONFFLAG_ADMIN ),
+ AST_APP_OPTION('b', CONFFLAG_AGI ),
+ AST_APP_OPTION('c', CONFFLAG_ANNOUNCEUSERCOUNT ),
+ AST_APP_OPTION('D', CONFFLAG_DYNAMICPIN ),
+ AST_APP_OPTION('d', CONFFLAG_DYNAMIC ),
+ AST_APP_OPTION('E', CONFFLAG_EMPTYNOPIN ),
+ AST_APP_OPTION('e', CONFFLAG_EMPTY ),
+ AST_APP_OPTION('F', CONFFLAG_PASS_DTMF ),
+ AST_APP_OPTION('i', CONFFLAG_INTROUSER ),
+ AST_APP_OPTION('I', CONFFLAG_INTROUSERNOREVIEW ),
+ AST_APP_OPTION('M', CONFFLAG_MOH ),
+ AST_APP_OPTION('m', CONFFLAG_STARTMUTED ),
+ AST_APP_OPTION('o', CONFFLAG_OPTIMIZETALKER ),
+ AST_APP_OPTION('P', CONFFLAG_ALWAYSPROMPT ),
+ AST_APP_OPTION('p', CONFFLAG_POUNDEXIT ),
+ AST_APP_OPTION('q', CONFFLAG_QUIET ),
+ AST_APP_OPTION('r', CONFFLAG_RECORDCONF ),
+ AST_APP_OPTION('s', CONFFLAG_STARMENU ),
+ AST_APP_OPTION('T', CONFFLAG_MONITORTALKER ),
+ AST_APP_OPTION('l', CONFFLAG_MONITOR ),
+ AST_APP_OPTION('t', CONFFLAG_TALKER ),
+ AST_APP_OPTION_ARG('w', CONFFLAG_WAITMARKED, OPT_ARG_WAITMARKED ),
+ AST_APP_OPTION('X', CONFFLAG_EXIT_CONTEXT ),
+ AST_APP_OPTION('x', CONFFLAG_MARKEDEXIT ),
+ AST_APP_OPTION('1', CONFFLAG_NOONLYPERSON ),
+END_OPTIONS );
+
+static const char *app = "MeetMe";
+static const char *app2 = "MeetMeCount";
+static const char *app3 = "MeetMeAdmin";
+static const char *slastation_app = "SLAStation";
+static const char *slatrunk_app = "SLATrunk";
+
+static const char *synopsis = "MeetMe conference bridge";
+static const char *synopsis2 = "MeetMe participant count";
+static const char *synopsis3 = "MeetMe conference Administration";
+static const char *slastation_synopsis = "Shared Line Appearance Station";
+static const char *slatrunk_synopsis = "Shared Line Appearance Trunk";
+
+static const char *descrip =
+" MeetMe([confno][,[options][,pin]]): Enters the user into a specified MeetMe\n"
+"conference. If the conference number is omitted, the user will be prompted\n"
+"to enter one. User can exit the conference by hangup, or if the 'p' option\n"
+"is specified, by pressing '#'.\n"
+"Please note: The DAHDI kernel modules and at least one hardware driver (or dahdi_dummy)\n"
+" must be present for conferencing to operate properly. In addition, the chan_dahdi\n"
+" channel driver must be loaded for the 'i' and 'r' options to operate at all.\n\n"
+"The option string may contain zero or more of the following characters:\n"
+" 'a' -- set admin mode\n"
+" 'A' -- set marked mode\n"
+" 'b' -- run AGI script specified in ${MEETME_AGI_BACKGROUND}\n"
+" Default: conf-background.agi (Note: This does not work with\n"
+" non-DAHDI channels in the same conference)\n"
+" 'c' -- announce user(s) count on joining a conference\n"
+" 'd' -- dynamically add conference\n"
+" 'D' -- dynamically add conference, prompting for a PIN\n"
+" 'e' -- select an empty conference\n"
+" 'E' -- select an empty pinless conference\n"
+" 'F' -- Pass DTMF through the conference.\n"
+" 'i' -- announce user join/leave with review\n"
+" 'I' -- announce user join/leave without review\n"
+" 'l' -- set listen only mode (Listen only, no talking)\n"
+" 'm' -- set initially muted\n"
+" 'M' -- enable music on hold when the conference has a single caller\n"
+" 'o' -- set talker optimization - treats talkers who aren't speaking as\n"
+" being muted, meaning (a) No encode is done on transmission and\n"
+" (b) Received audio that is not registered as talking is omitted\n"
+" causing no buildup in background noise. Note that this option\n"
+" will be removed in 1.6 and enabled by default.\n"
+" 'p' -- allow user to exit the conference by pressing '#'\n"
+" 'P' -- always prompt for the pin even if it is specified\n"
+" 'q' -- quiet mode (don't play enter/leave sounds)\n"
+" 'r' -- Record conference (records as ${MEETME_RECORDINGFILE}\n"
+" using format ${MEETME_RECORDINGFORMAT}). Default filename is\n"
+" meetme-conf-rec-${CONFNO}-${UNIQUEID} and the default format is\n"
+" wav.\n"
+" 's' -- Present menu (user or admin) when '*' is received ('send' to menu)\n"
+" 't' -- set talk only mode. (Talk only, no listening)\n"
+" 'T' -- set talker detection (sent to manager interface and meetme list)\n"
+" 'w[(<secs>)]'\n"
+" -- wait until the marked user enters the conference\n"
+" 'x' -- close the conference when last marked user exits\n"
+" 'X' -- allow user to exit the conference by entering a valid single\n"
+" digit extension ${MEETME_EXIT_CONTEXT} or the current context\n"
+" if that variable is not defined.\n"
+" '1' -- do not play message when first person enters\n";
+
+static const char *descrip2 =
+" MeetMeCount(confno[|var]): Plays back the number of users in the specified\n"
+"MeetMe conference. If var is specified, playback will be skipped and the value\n"
+"will be returned in the variable. Upon app completion, MeetMeCount will hangup\n"
+"the channel, unless priority n+1 exists, in which case priority progress will\n"
+"continue.\n";
+
+static const char *descrip3 =
+" MeetMeAdmin(confno,command[,user]): Run admin command for conference\n"
+" 'e' -- Eject last user that joined\n"
+" 'k' -- Kick one user out of conference\n"
+" 'K' -- Kick all users out of conference\n"
+" 'l' -- Unlock conference\n"
+" 'L' -- Lock conference\n"
+" 'm' -- Unmute one user\n"
+" 'M' -- Mute one user\n"
+" 'n' -- Unmute all users in the conference\n"
+" 'N' -- Mute all non-admin users in the conference\n"
+" 'r' -- Reset one user's volume settings\n"
+" 'R' -- Reset all users volume settings\n"
+" 's' -- Lower entire conference speaking volume\n"
+" 'S' -- Raise entire conference speaking volume\n"
+" 't' -- Lower one user's talk volume\n"
+" 'T' -- Raise one user's talk volume\n"
+" 'u' -- Lower one user's listen volume\n"
+" 'U' -- Raise one user's listen volume\n"
+" 'v' -- Lower entire conference listening volume\n"
+" 'V' -- Raise entire conference listening volume\n"
+"";
+
+static const char *slastation_desc =
+" SLAStation(station):\n"
+"This application should be executed by an SLA station. The argument depends\n"
+"on how the call was initiated. If the phone was just taken off hook, then\n"
+"the argument \"station\" should be just the station name. If the call was\n"
+"initiated by pressing a line key, then the station name should be preceded\n"
+"by an underscore and the trunk name associated with that line button.\n"
+"For example: \"station1_line1\"."
+" On exit, this application will set the variable SLASTATION_STATUS to\n"
+"one of the following values:\n"
+" FAILURE | CONGESTION | SUCCESS\n"
+"";
+
+static const char *slatrunk_desc =
+" SLATrunk(trunk):\n"
+"This application should be executed by an SLA trunk on an inbound call.\n"
+"The channel calling this application should correspond to the SLA trunk\n"
+"with the name \"trunk\" that is being passed as an argument.\n"
+" On exit, this application will set the variable SLATRUNK_STATUS to\n"
+"one of the following values:\n"
+" FAILURE | SUCCESS | UNANSWERED | RINGTIMEOUT\n"
+"";
+
+#define MAX_CONFNUM 80
+#define MAX_PIN 80
+
+enum announcetypes {
+ CONF_HASJOIN,
+ CONF_HASLEFT
+};
+
+struct announce_listitem {
+ AST_LIST_ENTRY(announce_listitem) entry;
+ char namerecloc[PATH_MAX]; /*!< Name Recorded file Location */
+ char language[MAX_LANGUAGE];
+ struct ast_channel *confchan;
+ int confusers;
+ enum announcetypes announcetype;
+};
+
+/*! \brief The MeetMe Conference object */
+struct ast_conference {
+ ast_mutex_t playlock; /*!< Conference specific lock (players) */
+ ast_mutex_t listenlock; /*!< Conference specific lock (listeners) */
+ char confno[MAX_CONFNUM]; /*!< Conference */
+ struct ast_channel *chan; /*!< Announcements channel */
+ struct ast_channel *lchan; /*!< Listen/Record channel */
+ int fd; /*!< Announcements fd */
+ int zapconf; /*!< Zaptel Conf # */
+ int users; /*!< Number of active users */
+ int markedusers; /*!< Number of marked users */
+ time_t start; /*!< Start time (s) */
+ int refcount; /*!< reference count of usage */
+ enum recording_state recording:2; /*!< recording status */
+ unsigned int isdynamic:1; /*!< Created on the fly? */
+ unsigned int locked:1; /*!< Is the conference locked? */
+ pthread_t recordthread; /*!< thread for recording */
+ ast_mutex_t recordthreadlock; /*!< control threads trying to start recordthread */
+ pthread_attr_t attr; /*!< thread attribute */
+ const char *recordingfilename; /*!< Filename to record the Conference into */
+ const char *recordingformat; /*!< Format to record the Conference in */
+ char pin[MAX_PIN]; /*!< If protected by a PIN */
+ char pinadmin[MAX_PIN]; /*!< If protected by a admin PIN */
+ struct ast_frame *transframe[32];
+ struct ast_frame *origframe;
+ struct ast_trans_pvt *transpath[32];
+ AST_LIST_HEAD_NOLOCK(, ast_conf_user) userlist;
+ AST_LIST_ENTRY(ast_conference) list;
+ /* announce_thread related data */
+ pthread_t announcethread;
+ ast_mutex_t announcethreadlock;
+ unsigned int announcethread_stop:1;
+ ast_cond_t announcelist_addition;
+ AST_LIST_HEAD_NOLOCK(, announce_listitem) announcelist;
+ ast_mutex_t announcelistlock;
+};
+
+static AST_LIST_HEAD_STATIC(confs, ast_conference);
+
+static unsigned int conf_map[1024] = {0, };
+
+struct volume {
+ int desired; /*!< Desired volume adjustment */
+ int actual; /*!< Actual volume adjustment (for channels that can't adjust) */
+};
+
+struct ast_conf_user {
+ int user_no; /*!< User Number */
+ int userflags; /*!< Flags as set in the conference */
+ int adminflags; /*!< Flags set by the Admin */
+ struct ast_channel *chan; /*!< Connected channel */
+ int talking; /*!< Is user talking */
+ int zapchannel; /*!< Is a Zaptel channel */
+ char usrvalue[50]; /*!< Custom User Value */
+ char namerecloc[PATH_MAX]; /*!< Name Recorded file Location */
+ time_t jointime; /*!< Time the user joined the conference */
+ struct volume talk;
+ struct volume listen;
+ AST_LIST_ENTRY(ast_conf_user) list;
+};
+
+enum sla_which_trunk_refs {
+ ALL_TRUNK_REFS,
+ INACTIVE_TRUNK_REFS,
+};
+
+enum sla_trunk_state {
+ SLA_TRUNK_STATE_IDLE,
+ SLA_TRUNK_STATE_RINGING,
+ SLA_TRUNK_STATE_UP,
+ SLA_TRUNK_STATE_ONHOLD,
+ SLA_TRUNK_STATE_ONHOLD_BYME,
+};
+
+enum sla_hold_access {
+ /*! This means that any station can put it on hold, and any station
+ * can retrieve the call from hold. */
+ SLA_HOLD_OPEN,
+ /*! This means that only the station that put the call on hold may
+ * retrieve it from hold. */
+ SLA_HOLD_PRIVATE,
+};
+
+struct sla_trunk_ref;
+
+struct sla_station {
+ AST_RWLIST_ENTRY(sla_station) entry;
+ AST_DECLARE_STRING_FIELDS(
+ AST_STRING_FIELD(name);
+ AST_STRING_FIELD(device);
+ AST_STRING_FIELD(autocontext);
+ );
+ AST_LIST_HEAD_NOLOCK(, sla_trunk_ref) trunks;
+ struct ast_dial *dial;
+ /*! Ring timeout for this station, for any trunk. If a ring timeout
+ * is set for a specific trunk on this station, that will take
+ * priority over this value. */
+ unsigned int ring_timeout;
+ /*! Ring delay for this station, for any trunk. If a ring delay
+ * is set for a specific trunk on this station, that will take
+ * priority over this value. */
+ unsigned int ring_delay;
+ /*! This option uses the values in the sla_hold_access enum and sets the
+ * access control type for hold on this station. */
+ unsigned int hold_access:1;
+};
+
+struct sla_station_ref {
+ AST_LIST_ENTRY(sla_station_ref) entry;
+ struct sla_station *station;
+};
+
+struct sla_trunk {
+ AST_RWLIST_ENTRY(sla_trunk) entry;
+ AST_DECLARE_STRING_FIELDS(
+ AST_STRING_FIELD(name);
+ AST_STRING_FIELD(device);
+ AST_STRING_FIELD(autocontext);
+ );
+ AST_LIST_HEAD_NOLOCK(, sla_station_ref) stations;
+ /*! Number of stations that use this trunk */
+ unsigned int num_stations;
+ /*! Number of stations currently on a call with this trunk */
+ unsigned int active_stations;
+ /*! Number of stations that have this trunk on hold. */
+ unsigned int hold_stations;
+ struct ast_channel *chan;
+ unsigned int ring_timeout;
+ /*! If set to 1, no station will be able to join an active call with
+ * this trunk. */
+ unsigned int barge_disabled:1;
+ /*! This option uses the values in the sla_hold_access enum and sets the
+ * access control type for hold on this trunk. */
+ unsigned int hold_access:1;
+ /*! Whether this trunk is currently on hold, meaning that once a station
+ * connects to it, the trunk channel needs to have UNHOLD indicated to it. */
+ unsigned int on_hold:1;
+};
+
+struct sla_trunk_ref {
+ AST_LIST_ENTRY(sla_trunk_ref) entry;
+ struct sla_trunk *trunk;
+ enum sla_trunk_state state;
+ struct ast_channel *chan;
+ /*! Ring timeout to use when this trunk is ringing on this specific
+ * station. This takes higher priority than a ring timeout set at
+ * the station level. */
+ unsigned int ring_timeout;
+ /*! Ring delay to use when this trunk is ringing on this specific
+ * station. This takes higher priority than a ring delay set at
+ * the station level. */
+ unsigned int ring_delay;
+};
+
+static AST_RWLIST_HEAD_STATIC(sla_stations, sla_station);
+static AST_RWLIST_HEAD_STATIC(sla_trunks, sla_trunk);
+
+static const char sla_registrar[] = "SLA";
+
+/*! \brief Event types that can be queued up for the SLA thread */
+enum sla_event_type {
+ /*! A station has put the call on hold */
+ SLA_EVENT_HOLD,
+ /*! The state of a dial has changed */
+ SLA_EVENT_DIAL_STATE,
+ /*! The state of a ringing trunk has changed */
+ SLA_EVENT_RINGING_TRUNK,
+};
+
+struct sla_event {
+ enum sla_event_type type;
+ struct sla_station *station;
+ struct sla_trunk_ref *trunk_ref;
+ AST_LIST_ENTRY(sla_event) entry;
+};
+
+/*! \brief A station that failed to be dialed
+ * \note Only used by the SLA thread. */
+struct sla_failed_station {
+ struct sla_station *station;
+ struct timeval last_try;
+ AST_LIST_ENTRY(sla_failed_station) entry;
+};
+
+/*! \brief A trunk that is ringing */
+struct sla_ringing_trunk {
+ struct sla_trunk *trunk;
+ /*! The time that this trunk started ringing */
+ struct timeval ring_begin;
+ AST_LIST_HEAD_NOLOCK(, sla_station_ref) timed_out_stations;
+ AST_LIST_ENTRY(sla_ringing_trunk) entry;
+};
+
+enum sla_station_hangup {
+ SLA_STATION_HANGUP_NORMAL,
+ SLA_STATION_HANGUP_TIMEOUT,
+};
+
+/*! \brief A station that is ringing */
+struct sla_ringing_station {
+ struct sla_station *station;
+ /*! The time that this station started ringing */
+ struct timeval ring_begin;
+ AST_LIST_ENTRY(sla_ringing_station) entry;
+};
+
+/*!
+ * \brief A structure for data used by the sla thread
+ */
+static struct {
+ /*! The SLA thread ID */
+ pthread_t thread;
+ ast_cond_t cond;
+ ast_mutex_t lock;
+ AST_LIST_HEAD_NOLOCK(, sla_ringing_trunk) ringing_trunks;
+ AST_LIST_HEAD_NOLOCK(, sla_ringing_station) ringing_stations;
+ AST_LIST_HEAD_NOLOCK(, sla_failed_station) failed_stations;
+ AST_LIST_HEAD_NOLOCK(, sla_event) event_q;
+ unsigned int stop:1;
+ /*! Attempt to handle CallerID, even though it is known not to work
+ * properly in some situations. */
+ unsigned int attempt_callerid:1;
+} sla = {
+ .thread = AST_PTHREADT_NULL,
+};
+
+/*! The number of audio buffers to be allocated on pseudo channels
+ * when in a conference */
+static int audio_buffers;
+
+/*! Map 'volume' levels from -5 through +5 into
+ * decibel (dB) settings for channel drivers
+ * Note: these are not a straight linear-to-dB
+ * conversion... the numbers have been modified
+ * to give the user a better level of adjustability
+ */
+static char const gain_map[] = {
+ -15,
+ -13,
+ -10,
+ -6,
+ 0,
+ 0,
+ 0,
+ 6,
+ 10,
+ 13,
+ 15,
+};
+
+
+static int admin_exec(struct ast_channel *chan, void *data);
+static void *recordthread(void *args);
+
+static char *istalking(int x)
+{
+ if (x > 0)
+ return "(talking)";
+ else if (x < 0)
+ return "(unmonitored)";
+ else
+ return "(not talking)";
+}
+
+static int careful_write(int fd, unsigned char *data, int len, int block)
+{
+ int res;
+ int x;
+
+ while (len) {
+ if (block) {
+ x = DAHDI_IOMUX_WRITE | DAHDI_IOMUX_SIGEVENT;
+ res = ioctl(fd, DAHDI_IOMUX, &x);
+ } else
+ res = 0;
+ if (res >= 0)
+ res = write(fd, data, len);
+ if (res < 1) {
+ if (errno != EAGAIN) {
+ ast_log(LOG_WARNING, "Failed to write audio data to conference: %s\n", strerror(errno));
+ return -1;
+ } else
+ return 0;
+ }
+ len -= res;
+ data += res;
+ }
+
+ return 0;
+}
+
+static int set_talk_volume(struct ast_conf_user *user, int volume)
+{
+ char gain_adjust;
+
+ /* attempt to make the adjustment in the channel driver;
+ if successful, don't adjust in the frame reading routine
+ */
+ gain_adjust = gain_map[volume + 5];
+
+ return ast_channel_setoption(user->chan, AST_OPTION_RXGAIN, &gain_adjust, sizeof(gain_adjust), 0);
+}
+
+static int set_listen_volume(struct ast_conf_user *user, int volume)
+{
+ char gain_adjust;
+
+ /* attempt to make the adjustment in the channel driver;
+ if successful, don't adjust in the frame reading routine
+ */
+ gain_adjust = gain_map[volume + 5];
+
+ return ast_channel_setoption(user->chan, AST_OPTION_TXGAIN, &gain_adjust, sizeof(gain_adjust), 0);
+}
+
+static void tweak_volume(struct volume *vol, enum volume_action action)
+{
+ switch (action) {
+ case VOL_UP:
+ switch (vol->desired) {
+ case 5:
+ break;
+ case 0:
+ vol->desired = 2;
+ break;
+ case -2:
+ vol->desired = 0;
+ break;
+ default:
+ vol->desired++;
+ break;
+ }
+ break;
+ case VOL_DOWN:
+ switch (vol->desired) {
+ case -5:
+ break;
+ case 2:
+ vol->desired = 0;
+ break;
+ case 0:
+ vol->desired = -2;
+ break;
+ default:
+ vol->desired--;
+ break;
+ }
+ }
+}
+
+static void tweak_talk_volume(struct ast_conf_user *user, enum volume_action action)
+{
+ tweak_volume(&user->talk, action);
+ /* attempt to make the adjustment in the channel driver;
+ if successful, don't adjust in the frame reading routine
+ */
+ if (!set_talk_volume(user, user->talk.desired))
+ user->talk.actual = 0;
+ else
+ user->talk.actual = user->talk.desired;
+}
+
+static void tweak_listen_volume(struct ast_conf_user *user, enum volume_action action)
+{
+ tweak_volume(&user->listen, action);
+ /* attempt to make the adjustment in the channel driver;
+ if successful, don't adjust in the frame reading routine
+ */
+ if (!set_listen_volume(user, user->listen.desired))
+ user->listen.actual = 0;
+ else
+ user->listen.actual = user->listen.desired;
+}
+
+static void reset_volumes(struct ast_conf_user *user)
+{
+ signed char zero_volume = 0;
+
+ ast_channel_setoption(user->chan, AST_OPTION_TXGAIN, &zero_volume, sizeof(zero_volume), 0);
+ ast_channel_setoption(user->chan, AST_OPTION_RXGAIN, &zero_volume, sizeof(zero_volume), 0);
+}
+
+static void conf_play(struct ast_channel *chan, struct ast_conference *conf, enum entrance_sound sound)
+{
+ unsigned char *data;
+ int len;
+ int res = -1;
+
+ if (!chan->_softhangup)
+ res = ast_autoservice_start(chan);
+
+ AST_LIST_LOCK(&confs);
+
+ switch(sound) {
+ case ENTER:
+ data = enter;
+ len = sizeof(enter);
+ break;
+ case LEAVE:
+ data = leave;
+ len = sizeof(leave);
+ break;
+ default:
+ data = NULL;
+ len = 0;
+ }
+ if (data) {
+ careful_write(conf->fd, data, len, 1);
+ }
+
+ AST_LIST_UNLOCK(&confs);
+
+ if (!res)
+ ast_autoservice_stop(chan);
+}
+
+/*!
+ * \brief Find or create a conference
+ *
+ * \param confno The conference name/number
+ * \param pin The regular user pin
+ * \param pinadmin The admin pin
+ * \param make Make the conf if it doesn't exist
+ * \param dynamic Mark the newly created conference as dynamic
+ * \param refcount How many references to mark on the conference
+ *
+ * \return A pointer to the conference struct, or NULL if it wasn't found and
+ * make or dynamic were not set.
+ */
+static struct ast_conference *build_conf(char *confno, char *pin, char *pinadmin, int make, int dynamic, int refcount)
+{
+ struct ast_conference *cnf;
+ struct dahdi_confinfo ztc = { 0, };
+ int confno_int = 0;
+
+ AST_LIST_LOCK(&confs);
+
+ AST_LIST_TRAVERSE(&confs, cnf, list) {
+ if (!strcmp(confno, cnf->confno))
+ break;
+ }
+
+ if (cnf || (!make && !dynamic))
+ goto cnfout;
+
+ /* Make a new one */
+ if (!(cnf = ast_calloc(1, sizeof(*cnf))))
+ goto cnfout;
+
+ ast_mutex_init(&cnf->playlock);
+ ast_mutex_init(&cnf->listenlock);
+ cnf->recordthread = AST_PTHREADT_NULL;
+ ast_mutex_init(&cnf->recordthreadlock);
+ cnf->announcethread = AST_PTHREADT_NULL;
+ ast_mutex_init(&cnf->announcethreadlock);
+ ast_copy_string(cnf->confno, confno, sizeof(cnf->confno));
+ ast_copy_string(cnf->pin, pin, sizeof(cnf->pin));
+ ast_copy_string(cnf->pinadmin, pinadmin, sizeof(cnf->pinadmin));
+
+ /* Setup a new zap conference */
+ ztc.confno = -1;
+ ztc.confmode = DAHDI_CONF_CONFANN | DAHDI_CONF_CONFANNMON;
+ cnf->fd = open(DAHDI_FILE_PSEUDO, O_RDWR);
+ if (cnf->fd < 0 || ioctl(cnf->fd, DAHDI_SETCONF, &ztc)) {
+ ast_log(LOG_WARNING, "Unable to open pseudo device\n");
+ if (cnf->fd >= 0)
+ close(cnf->fd);
+ free(cnf);
+ cnf = NULL;
+ goto cnfout;
+ }
+
+ cnf->zapconf = ztc.confno;
+
+ /* Setup a new channel for playback of audio files */
+ cnf->chan = ast_request(dahdi_chan_name, AST_FORMAT_SLINEAR, "pseudo", NULL);
+ if (cnf->chan) {
+ ast_set_read_format(cnf->chan, AST_FORMAT_SLINEAR);
+ ast_set_write_format(cnf->chan, AST_FORMAT_SLINEAR);
+ ztc.chan = 0;
+ ztc.confno = cnf->zapconf;
+ ztc.confmode = DAHDI_CONF_CONFANN | DAHDI_CONF_CONFANNMON;
+ if (ioctl(cnf->chan->fds[0], DAHDI_SETCONF, &ztc)) {
+ ast_log(LOG_WARNING, "Error setting conference\n");
+ if (cnf->chan)
+ ast_hangup(cnf->chan);
+ else
+ close(cnf->fd);
+ free(cnf);
+ cnf = NULL;
+ goto cnfout;
+ }
+ }
+
+ /* Fill the conference struct */
+ cnf->start = time(NULL);
+ cnf->isdynamic = dynamic ? 1 : 0;
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Created MeetMe conference %d for conference '%s'\n", cnf->zapconf, cnf->confno);
+ AST_LIST_INSERT_HEAD(&confs, cnf, list);
+
+ /* Reserve conference number in map */
+ if ((sscanf(cnf->confno, "%d", &confno_int) == 1) && (confno_int >= 0 && confno_int < 1024))
+ conf_map[confno_int] = 1;
+
+cnfout:
+ if (cnf)
+ ast_atomic_fetchadd_int(&cnf->refcount, refcount);
+
+ AST_LIST_UNLOCK(&confs);
+
+ return cnf;
+}
+
+static int meetme_cmd(int fd, int argc, char **argv)
+{
+ /* Process the command */
+ struct ast_conference *cnf;
+ struct ast_conf_user *user;
+ int hr, min, sec;
+ int i = 0, total = 0;
+ time_t now;
+ char *header_format = "%-14s %-14s %-10s %-8s %-8s\n";
+ char *data_format = "%-12.12s %4.4d %4.4s %02d:%02d:%02d %-8s\n";
+ char cmdline[1024] = "";
+
+ if (argc > 8)
+ ast_cli(fd, "Invalid Arguments.\n");
+ /* Check for length so no buffer will overflow... */
+ for (i = 0; i < argc; i++) {
+ if (strlen(argv[i]) > 100)
+ ast_cli(fd, "Invalid Arguments.\n");
+ }
+ if (argc == 1) {
+ /* 'MeetMe': List all the conferences */
+ now = time(NULL);
+ AST_LIST_LOCK(&confs);
+ if (AST_LIST_EMPTY(&confs)) {
+ ast_cli(fd, "No active MeetMe conferences.\n");
+ AST_LIST_UNLOCK(&confs);
+ return RESULT_SUCCESS;
+ }
+ ast_cli(fd, header_format, "Conf Num", "Parties", "Marked", "Activity", "Creation");
+ AST_LIST_TRAVERSE(&confs, cnf, list) {
+ if (cnf->markedusers == 0)
+ strcpy(cmdline, "N/A ");
+ else
+ snprintf(cmdline, sizeof(cmdline), "%4.4d", cnf->markedusers);
+ hr = (now - cnf->start) / 3600;
+ min = ((now - cnf->start) % 3600) / 60;
+ sec = (now - cnf->start) % 60;
+
+ ast_cli(fd, data_format, cnf->confno, cnf->users, cmdline, hr, min, sec, cnf->isdynamic ? "Dynamic" : "Static");
+
+ total += cnf->users;
+ }
+ AST_LIST_UNLOCK(&confs);
+ ast_cli(fd, "* Total number of MeetMe users: %d\n", total);
+ return RESULT_SUCCESS;
+ }
+ if (argc < 3)
+ return RESULT_SHOWUSAGE;
+ ast_copy_string(cmdline, argv[2], sizeof(cmdline)); /* Argv 2: conference number */
+ if (strstr(argv[1], "lock")) {
+ if (strcmp(argv[1], "lock") == 0) {
+ /* Lock */
+ strncat(cmdline, "|L", sizeof(cmdline) - strlen(cmdline) - 1);
+ } else {
+ /* Unlock */
+ strncat(cmdline, "|l", sizeof(cmdline) - strlen(cmdline) - 1);
+ }
+ } else if (strstr(argv[1], "mute")) {
+ if (argc < 4)
+ return RESULT_SHOWUSAGE;
+ if (strcmp(argv[1], "mute") == 0) {
+ /* Mute */
+ if (strcmp(argv[3], "all") == 0) {
+ strncat(cmdline, "|N", sizeof(cmdline) - strlen(cmdline) - 1);
+ } else {
+ strncat(cmdline, "|M|", sizeof(cmdline) - strlen(cmdline) - 1);
+ strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1);
+ }
+ } else {
+ /* Unmute */
+ if (strcmp(argv[3], "all") == 0) {
+ strncat(cmdline, "|n", sizeof(cmdline) - strlen(cmdline) - 1);
+ } else {
+ strncat(cmdline, "|m|", sizeof(cmdline) - strlen(cmdline) - 1);
+ strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1);
+ }
+ }
+ } else if (strcmp(argv[1], "kick") == 0) {
+ if (argc < 4)
+ return RESULT_SHOWUSAGE;
+ if (strcmp(argv[3], "all") == 0) {
+ /* Kick all */
+ strncat(cmdline, "|K", sizeof(cmdline) - strlen(cmdline) - 1);
+ } else {
+ /* Kick a single user */
+ strncat(cmdline, "|k|", sizeof(cmdline) - strlen(cmdline) - 1);
+ strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1);
+ }
+ } else if(strcmp(argv[1], "list") == 0) {
+ int concise = ( 4 == argc && ( !strcasecmp(argv[3], "concise") ) );
+ /* List all the users in a conference */
+ if (AST_LIST_EMPTY(&confs)) {
+ if ( !concise )
+ ast_cli(fd, "No active conferences.\n");
+ return RESULT_SUCCESS;
+ }
+ /* Find the right conference */
+ AST_LIST_LOCK(&confs);
+ AST_LIST_TRAVERSE(&confs, cnf, list) {
+ if (strcmp(cnf->confno, argv[2]) == 0)
+ break;
+ }
+ if (!cnf) {
+ if ( !concise )
+ ast_cli(fd, "No such conference: %s.\n",argv[2]);
+ AST_LIST_UNLOCK(&confs);
+ return RESULT_SUCCESS;
+ }
+ /* Show all the users */
+ time(&now);
+ AST_LIST_TRAVERSE(&cnf->userlist, user, list) {
+ hr = (now - user->jointime) / 3600;
+ min = ((now - user->jointime) % 3600) / 60;
+ sec = (now - user->jointime) % 60;
+ if ( !concise )
+ ast_cli(fd, "User #: %-2.2d %12.12s %-20.20s Channel: %s %s %s %s %s %02d:%02d:%02d\n",
+ user->user_no,
+ S_OR(user->chan->cid.cid_num, "<unknown>"),
+ S_OR(user->chan->cid.cid_name, "<no name>"),
+ user->chan->name,
+ user->userflags & CONFFLAG_ADMIN ? "(Admin)" : "",
+ user->userflags & CONFFLAG_MONITOR ? "(Listen only)" : "",
+ user->adminflags & ADMINFLAG_MUTED ? "(Admin Muted)" : user->adminflags & ADMINFLAG_SELFMUTED ? "(Muted)" : "",
+ istalking(user->talking), hr, min, sec);
+ else
+ ast_cli(fd, "%d!%s!%s!%s!%s!%s!%s!%d!%02d:%02d:%02d\n",
+ user->user_no,
+ S_OR(user->chan->cid.cid_num, ""),
+ S_OR(user->chan->cid.cid_name, ""),
+ user->chan->name,
+ user->userflags & CONFFLAG_ADMIN ? "1" : "",
+ user->userflags & CONFFLAG_MONITOR ? "1" : "",
+ user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED) ? "1" : "",
+ user->talking, hr, min, sec);
+
+ }
+ if ( !concise )
+ ast_cli(fd,"%d users in that conference.\n",cnf->users);
+ AST_LIST_UNLOCK(&confs);
+ return RESULT_SUCCESS;
+ } else
+ return RESULT_SHOWUSAGE;
+ ast_log(LOG_DEBUG, "Cmdline: %s\n", cmdline);
+ admin_exec(NULL, cmdline);
+
+ return 0;
+}
+
+static char *complete_meetmecmd(const char *line, const char *word, int pos, int state)
+{
+ static char *cmds[] = {"lock", "unlock", "mute", "unmute", "kick", "list", NULL};
+
+ int len = strlen(word);
+ int which = 0;
+ struct ast_conference *cnf = NULL;
+ struct ast_conf_user *usr = NULL;
+ char *confno = NULL;
+ char usrno[50] = "";
+ char *myline, *ret = NULL;
+
+ if (pos == 1) { /* Command */
+ return ast_cli_complete(word, cmds, state);
+ } else if (pos == 2) { /* Conference Number */
+ AST_LIST_LOCK(&confs);
+ AST_LIST_TRAVERSE(&confs, cnf, list) {
+ if (!strncasecmp(word, cnf->confno, len) && ++which > state) {
+ ret = cnf->confno;
+ break;
+ }
+ }
+ ret = ast_strdup(ret); /* dup before releasing the lock */
+ AST_LIST_UNLOCK(&confs);
+ return ret;
+ } else if (pos == 3) {
+ /* User Number || Conf Command option*/
+ if (strstr(line, "mute") || strstr(line, "kick")) {
+ if (state == 0 && (strstr(line, "kick") || strstr(line,"mute")) && !strncasecmp(word, "all", len))
+ return strdup("all");
+ which++;
+ AST_LIST_LOCK(&confs);
+
+ /* TODO: Find the conf number from the cmdline (ignore spaces) <- test this and make it fail-safe! */
+ myline = ast_strdupa(line);
+ if (strsep(&myline, " ") && strsep(&myline, " ") && !confno) {
+ while((confno = strsep(&myline, " ")) && (strcmp(confno, " ") == 0))
+ ;
+ }
+
+ AST_LIST_TRAVERSE(&confs, cnf, list) {
+ if (!strcmp(confno, cnf->confno))
+ break;
+ }
+
+ if (cnf) {
+ /* Search for the user */
+ AST_LIST_TRAVERSE(&cnf->userlist, usr, list) {
+ snprintf(usrno, sizeof(usrno), "%d", usr->user_no);
+ if (!strncasecmp(word, usrno, len) && ++which > state)
+ break;
+ }
+ }
+ AST_LIST_UNLOCK(&confs);
+ return usr ? strdup(usrno) : NULL;
+ } else if ( strstr(line, "list") && ( 0 == state ) )
+ return strdup("concise");
+ }
+
+ return NULL;
+}
+
+static char meetme_usage[] =
+"Usage: meetme (un)lock|(un)mute|kick|list [concise] <confno> <usernumber>\n"
+" Executes a command for the conference or on a conferee\n";
+
+static const char *sla_hold_str(unsigned int hold_access)
+{
+ const char *hold = "Unknown";
+
+ switch (hold_access) {
+ case SLA_HOLD_OPEN:
+ hold = "Open";
+ break;
+ case SLA_HOLD_PRIVATE:
+ hold = "Private";
+ default:
+ break;
+ }
+
+ return hold;
+}
+
+static int sla_show_trunks(int fd, int argc, char **argv)
+{
+ const struct sla_trunk *trunk;
+
+ ast_cli(fd, "\n"
+ "=============================================================\n"
+ "=== Configured SLA Trunks ===================================\n"
+ "=============================================================\n"
+ "===\n");
+ AST_RWLIST_RDLOCK(&sla_trunks);
+ AST_RWLIST_TRAVERSE(&sla_trunks, trunk, entry) {
+ struct sla_station_ref *station_ref;
+ char ring_timeout[16] = "(none)";
+ if (trunk->ring_timeout)
+ snprintf(ring_timeout, sizeof(ring_timeout), "%u Seconds", trunk->ring_timeout);
+ ast_cli(fd, "=== ---------------------------------------------------------\n"
+ "=== Trunk Name: %s\n"
+ "=== ==> Device: %s\n"
+ "=== ==> AutoContext: %s\n"
+ "=== ==> RingTimeout: %s\n"
+ "=== ==> BargeAllowed: %s\n"
+ "=== ==> HoldAccess: %s\n"
+ "=== ==> Stations ...\n",
+ trunk->name, trunk->device,
+ S_OR(trunk->autocontext, "(none)"),
+ ring_timeout,
+ trunk->barge_disabled ? "No" : "Yes",
+ sla_hold_str(trunk->hold_access));
+ AST_RWLIST_RDLOCK(&sla_stations);
+ AST_LIST_TRAVERSE(&trunk->stations, station_ref, entry)
+ ast_cli(fd, "=== ==> Station name: %s\n", station_ref->station->name);
+ AST_RWLIST_UNLOCK(&sla_stations);
+ ast_cli(fd, "=== ---------------------------------------------------------\n"
+ "===\n");
+ }
+ AST_RWLIST_UNLOCK(&sla_trunks);
+ ast_cli(fd, "=============================================================\n"
+ "\n");
+
+ return RESULT_SUCCESS;
+}
+
+static const char *trunkstate2str(enum sla_trunk_state state)
+{
+#define S(e) case e: return # e;
+ switch (state) {
+ S(SLA_TRUNK_STATE_IDLE)
+ S(SLA_TRUNK_STATE_RINGING)
+ S(SLA_TRUNK_STATE_UP)
+ S(SLA_TRUNK_STATE_ONHOLD)
+ S(SLA_TRUNK_STATE_ONHOLD_BYME)
+ }
+ return "Uknown State";
+#undef S
+}
+
+static const char sla_show_trunks_usage[] =
+"Usage: sla show trunks\n"
+" This will list all trunks defined in sla.conf\n";
+
+static int sla_show_stations(int fd, int argc, char **argv)
+{
+ const struct sla_station *station;
+
+ ast_cli(fd, "\n"
+ "=============================================================\n"
+ "=== Configured SLA Stations =================================\n"
+ "=============================================================\n"
+ "===\n");
+ AST_RWLIST_RDLOCK(&sla_stations);
+ AST_RWLIST_TRAVERSE(&sla_stations, station, entry) {
+ struct sla_trunk_ref *trunk_ref;
+ char ring_timeout[16] = "(none)";
+ char ring_delay[16] = "(none)";
+ if (station->ring_timeout) {
+ snprintf(ring_timeout, sizeof(ring_timeout),
+ "%u", station->ring_timeout);
+ }
+ if (station->ring_delay) {
+ snprintf(ring_delay, sizeof(ring_delay),
+ "%u", station->ring_delay);
+ }
+ ast_cli(fd, "=== ---------------------------------------------------------\n"
+ "=== Station Name: %s\n"
+ "=== ==> Device: %s\n"
+ "=== ==> AutoContext: %s\n"
+ "=== ==> RingTimeout: %s\n"
+ "=== ==> RingDelay: %s\n"
+ "=== ==> HoldAccess: %s\n"
+ "=== ==> Trunks ...\n",
+ station->name, station->device,
+ S_OR(station->autocontext, "(none)"),
+ ring_timeout, ring_delay,
+ sla_hold_str(station->hold_access));
+ AST_RWLIST_RDLOCK(&sla_trunks);
+ AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) {
+ if (trunk_ref->ring_timeout) {
+ snprintf(ring_timeout, sizeof(ring_timeout),
+ "%u", trunk_ref->ring_timeout);
+ } else
+ strcpy(ring_timeout, "(none)");
+ if (trunk_ref->ring_delay) {
+ snprintf(ring_delay, sizeof(ring_delay),
+ "%u", trunk_ref->ring_delay);
+ } else
+ strcpy(ring_delay, "(none)");
+ ast_cli(fd, "=== ==> Trunk Name: %s\n"
+ "=== ==> State: %s\n"
+ "=== ==> RingTimeout: %s\n"
+ "=== ==> RingDelay: %s\n",
+ trunk_ref->trunk->name,
+ trunkstate2str(trunk_ref->state),
+ ring_timeout, ring_delay);
+ }
+ AST_RWLIST_UNLOCK(&sla_trunks);
+ ast_cli(fd, "=== ---------------------------------------------------------\n"
+ "===\n");
+ }
+ AST_RWLIST_UNLOCK(&sla_stations);
+ ast_cli(fd, "============================================================\n"
+ "\n");
+
+ return RESULT_SUCCESS;
+}
+
+static const char sla_show_stations_usage[] =
+"Usage: sla show stations\n"
+" This will list all stations defined in sla.conf\n";
+
+static struct ast_cli_entry cli_meetme[] = {
+ { { "meetme", NULL, NULL },
+ meetme_cmd, "Execute a command on a conference or conferee",
+ meetme_usage, complete_meetmecmd },
+
+ { { "sla", "show", "trunks", NULL },
+ sla_show_trunks, "Show SLA Trunks",
+ sla_show_trunks_usage, NULL },
+
+ { { "sla", "show", "stations", NULL },
+ sla_show_stations, "Show SLA Stations",
+ sla_show_stations_usage, NULL },
+};
+
+static void conf_flush(int fd, struct ast_channel *chan)
+{
+ int x;
+
+ /* read any frames that may be waiting on the channel
+ and throw them away
+ */
+ if (chan) {
+ struct ast_frame *f;
+
+ /* when no frames are available, this will wait
+ for 1 millisecond maximum
+ */
+ while (ast_waitfor(chan, 1)) {
+ f = ast_read(chan);
+ if (f)
+ ast_frfree(f);
+ else /* channel was hung up or something else happened */
+ break;
+ }
+ }
+
+ /* flush any data sitting in the pseudo channel */
+ x = DAHDI_FLUSH_ALL;
+ if (ioctl(fd, DAHDI_FLUSH, &x))
+ ast_log(LOG_WARNING, "Error flushing channel\n");
+
+}
+
+/* Remove the conference from the list and free it.
+ We assume that this was called while holding conflock. */
+static int conf_free(struct ast_conference *conf)
+{
+ int x;
+ struct announce_listitem *item;
+
+ AST_LIST_REMOVE(&confs, conf, list);
+
+ if (conf->recording == MEETME_RECORD_ACTIVE) {
+ conf->recording = MEETME_RECORD_TERMINATE;
+ AST_LIST_UNLOCK(&confs);
+ while (1) {
+ usleep(1);
+ AST_LIST_LOCK(&confs);
+ if (conf->recording == MEETME_RECORD_OFF)
+ break;
+ AST_LIST_UNLOCK(&confs);
+ }
+ }
+
+ for (x=0;x<AST_FRAME_BITS;x++) {
+ if (conf->transframe[x])
+ ast_frfree(conf->transframe[x]);
+ if (conf->transpath[x])
+ ast_translator_free_path(conf->transpath[x]);
+ }
+ if (conf->announcethread != AST_PTHREADT_NULL) {
+ ast_mutex_lock(&conf->announcelistlock);
+ conf->announcethread_stop = 1;
+ ast_softhangup(conf->chan, AST_SOFTHANGUP_EXPLICIT);
+ ast_cond_signal(&conf->announcelist_addition);
+ ast_mutex_unlock(&conf->announcelistlock);
+ pthread_join(conf->announcethread, NULL);
+
+ while ((item = AST_LIST_REMOVE_HEAD(&conf->announcelist, entry))) {
+ ast_filedelete(item->namerecloc, NULL);
+ ao2_ref(item, -1);
+ }
+ ast_mutex_destroy(&conf->announcelistlock);
+ }
+ if (conf->origframe)
+ ast_frfree(conf->origframe);
+ if (conf->lchan)
+ ast_hangup(conf->lchan);
+ if (conf->chan)
+ ast_hangup(conf->chan);
+ if (conf->fd >= 0)
+ close(conf->fd);
+
+ ast_mutex_destroy(&conf->playlock);
+ ast_mutex_destroy(&conf->listenlock);
+ ast_mutex_destroy(&conf->recordthreadlock);
+ ast_mutex_destroy(&conf->announcethreadlock);
+
+ free(conf);
+
+ return 0;
+}
+
+static void conf_queue_dtmf(const struct ast_conference *conf,
+ const struct ast_conf_user *sender, struct ast_frame *f)
+{
+ struct ast_conf_user *user;
+
+ AST_LIST_TRAVERSE(&conf->userlist, user, list) {
+ if (user == sender)
+ continue;
+ if (ast_write(user->chan, f) < 0)
+ ast_log(LOG_WARNING, "Error writing frame to channel %s\n", user->chan->name);
+ }
+}
+
+static void sla_queue_event_full(enum sla_event_type type,
+ struct sla_trunk_ref *trunk_ref, struct sla_station *station, int lock)
+{
+ struct sla_event *event;
+
+ if (sla.thread == AST_PTHREADT_NULL) {
+ return;
+ }
+
+ if (!(event = ast_calloc(1, sizeof(*event))))
+ return;
+
+ event->type = type;
+ event->trunk_ref = trunk_ref;
+ event->station = station;
+
+ if (!lock) {
+ AST_LIST_INSERT_TAIL(&sla.event_q, event, entry);
+ return;
+ }
+
+ ast_mutex_lock(&sla.lock);
+ AST_LIST_INSERT_TAIL(&sla.event_q, event, entry);
+ ast_cond_signal(&sla.cond);
+ ast_mutex_unlock(&sla.lock);
+}
+
+static void sla_queue_event_nolock(enum sla_event_type type)
+{
+ sla_queue_event_full(type, NULL, NULL, 0);
+}
+
+static void sla_queue_event(enum sla_event_type type)
+{
+ sla_queue_event_full(type, NULL, NULL, 1);
+}
+
+/*! \brief Queue a SLA event from the conference */
+static void sla_queue_event_conf(enum sla_event_type type, struct ast_channel *chan,
+ struct ast_conference *conf)
+{
+ struct sla_station *station;
+ struct sla_trunk_ref *trunk_ref = NULL;
+ char *trunk_name;
+
+ trunk_name = ast_strdupa(conf->confno);
+ strsep(&trunk_name, "_");
+ if (ast_strlen_zero(trunk_name)) {
+ ast_log(LOG_ERROR, "Invalid conference name for SLA - '%s'!\n", conf->confno);
+ return;
+ }
+
+ AST_RWLIST_RDLOCK(&sla_stations);
+ AST_RWLIST_TRAVERSE(&sla_stations, station, entry) {
+ AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) {
+ if (trunk_ref->chan == chan && !strcmp(trunk_ref->trunk->name, trunk_name))
+ break;
+ }
+ if (trunk_ref)
+ break;
+ }
+ AST_RWLIST_UNLOCK(&sla_stations);
+
+ if (!trunk_ref) {
+ ast_log(LOG_DEBUG, "Trunk not found for event!\n");
+ return;
+ }
+
+ sla_queue_event_full(type, trunk_ref, station, 1);
+}
+
+/* Decrement reference counts, as incremented by find_conf() */
+static int dispose_conf(struct ast_conference *conf)
+{
+ int res = 0;
+ int confno_int = 0;
+
+ AST_LIST_LOCK(&confs);
+ if (ast_atomic_dec_and_test(&conf->refcount)) {
+ /* Take the conference room number out of an inuse state */
+ if ((sscanf(conf->confno, "%d", &confno_int) == 1) && (confno_int >= 0 && confno_int < 1024))
+ conf_map[confno_int] = 0;
+ conf_free(conf);
+ res = 1;
+ }
+ AST_LIST_UNLOCK(&confs);
+
+ return res;
+}
+
+static const char *get_announce_filename(enum announcetypes type)
+{
+ switch (type) {
+ case CONF_HASLEFT:
+ return "conf-hasleft";
+ break;
+ case CONF_HASJOIN:
+ return "conf-hasjoin";
+ break;
+ default:
+ return "";
+ }
+}
+
+static void *announce_thread(void *data)
+{
+ struct announce_listitem *current;
+ struct ast_conference *conf = data;
+ int res;
+ char filename[PATH_MAX] = "";
+ AST_LIST_HEAD_NOLOCK(, announce_listitem) local_list;
+ AST_LIST_HEAD_INIT_NOLOCK(&local_list);
+
+ while (!conf->announcethread_stop) {
+ ast_mutex_lock(&conf->announcelistlock);
+ if (conf->announcethread_stop) {
+ ast_mutex_unlock(&conf->announcelistlock);
+ break;
+ }
+ if (AST_LIST_EMPTY(&conf->announcelist))
+ ast_cond_wait(&conf->announcelist_addition, &conf->announcelistlock);
+
+ AST_LIST_APPEND_LIST(&local_list, &conf->announcelist, entry);
+ AST_LIST_HEAD_INIT_NOLOCK(&conf->announcelist);
+
+ ast_mutex_unlock(&conf->announcelistlock);
+ if (conf->announcethread_stop) {
+ break;
+ }
+
+ for (res = 1; !conf->announcethread_stop && (current = AST_LIST_REMOVE_HEAD(&local_list, entry)); ao2_ref(current, -1)) {
+ ast_log(LOG_DEBUG, "About to play %s\n", current->namerecloc);
+ if (!ast_fileexists(current->namerecloc, NULL, NULL))
+ continue;
+ if ((current->confchan) && (current->confusers > 1) && !ast_check_hangup(current->confchan)) {
+ if (!ast_streamfile(current->confchan, current->namerecloc, current->language))
+ res = ast_waitstream(current->confchan, "");
+ if (!res) {
+ ast_copy_string(filename, get_announce_filename(current->announcetype), sizeof(filename));
+ if (!ast_streamfile(current->confchan, filename, current->language))
+ ast_waitstream(current->confchan, "");
+ }
+ }
+ if (current->announcetype == CONF_HASLEFT) {
+ ast_filedelete(current->namerecloc, NULL);
+ }
+ }
+ }
+
+ /* thread marked to stop, clean up */
+ while ((current = AST_LIST_REMOVE_HEAD(&local_list, entry))) {
+ ast_filedelete(current->namerecloc, NULL);
+ ao2_ref(current, -1);
+ }
+ return NULL;
+}
+
+static int can_write(struct ast_channel *chan, int confflags)
+{
+ if (!(confflags & CONFFLAG_NO_AUDIO_UNTIL_UP)) {
+ return 1;
+ }
+
+ return (chan->_state == AST_STATE_UP);
+}
+
+static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int confflags, char *optargs[])
+{
+ struct ast_conf_user *user = NULL;
+ struct ast_conf_user *usr = NULL;
+ int fd;
+ struct dahdi_confinfo ztc, ztc_empty;
+ struct ast_frame *f;
+ struct ast_channel *c;
+ struct ast_frame fr;
+ int outfd;
+ int ms;
+ int nfds;
+ int res;
+ int flags;
+ int retryzap;
+ int origfd;
+ int musiconhold = 0;
+ int firstpass = 0;
+ int lastmarked = 0;
+ int currentmarked = 0;
+ int ret = -1;
+ int x;
+ int menu_active = 0;
+ int using_pseudo = 0;
+ int duration=20;
+ int hr, min, sec;
+ int sent_event = 0;
+ time_t now;
+ struct ast_dsp *dsp=NULL;
+ struct ast_app *app;
+ const char *agifile;
+ const char *agifiledefault = "conf-background.agi";
+ char meetmesecs[30] = "";
+ char exitcontext[AST_MAX_CONTEXT] = "";
+ char recordingtmp[AST_MAX_EXTENSION] = "";
+ char members[10] = "";
+ int dtmf, opt_waitmarked_timeout = 0;
+ time_t timeout = 0;
+ struct dahdi_bufferinfo bi;
+ char __buf[CONF_SIZE + AST_FRIENDLY_OFFSET];
+ char *buf = __buf + AST_FRIENDLY_OFFSET;
+ int setusercount = 0;
+
+ if (!(user = ast_calloc(1, sizeof(*user))))
+ return ret;
+
+ /* Possible timeout waiting for marked user */
+ if ((confflags & CONFFLAG_WAITMARKED) &&
+ !ast_strlen_zero(optargs[OPT_ARG_WAITMARKED]) &&
+ (sscanf(optargs[OPT_ARG_WAITMARKED], "%d", &opt_waitmarked_timeout) == 1) &&
+ (opt_waitmarked_timeout > 0)) {
+ timeout = time(NULL) + opt_waitmarked_timeout;
+ }
+
+ if (confflags & CONFFLAG_RECORDCONF) {
+ if (!conf->recordingfilename) {
+ conf->recordingfilename = pbx_builtin_getvar_helper(chan, "MEETME_RECORDINGFILE");
+ if (!conf->recordingfilename) {
+ snprintf(recordingtmp, sizeof(recordingtmp), "meetme-conf-rec-%s-%s", conf->confno, chan->uniqueid);
+ conf->recordingfilename = ast_strdupa(recordingtmp);
+ }
+ conf->recordingformat = pbx_builtin_getvar_helper(chan, "MEETME_RECORDINGFORMAT");
+ if (!conf->recordingformat) {
+ snprintf(recordingtmp, sizeof(recordingtmp), "wav");
+ conf->recordingformat = ast_strdupa(recordingtmp);
+ }
+ ast_verbose(VERBOSE_PREFIX_4 "Starting recording of MeetMe Conference %s into file %s.%s.\n",
+ conf->confno, conf->recordingfilename, conf->recordingformat);
+ }
+ }
+
+ ast_mutex_lock(&conf->recordthreadlock);
+ if ((conf->recordthread == AST_PTHREADT_NULL) && (confflags & CONFFLAG_RECORDCONF) && ((conf->lchan = ast_request(dahdi_chan_name, AST_FORMAT_SLINEAR, "pseudo", NULL)))) {
+ ast_set_read_format(conf->lchan, AST_FORMAT_SLINEAR);
+ ast_set_write_format(conf->lchan, AST_FORMAT_SLINEAR);
+ ztc.chan = 0;
+ ztc.confno = conf->zapconf;
+ ztc.confmode = DAHDI_CONF_CONFANN | DAHDI_CONF_CONFANNMON;
+ if (ioctl(conf->lchan->fds[0], DAHDI_SETCONF, &ztc)) {
+ ast_log(LOG_WARNING, "Error starting listen channel\n");
+ ast_hangup(conf->lchan);
+ conf->lchan = NULL;
+ } else {
+ pthread_attr_init(&conf->attr);
+ pthread_attr_setdetachstate(&conf->attr, PTHREAD_CREATE_DETACHED);
+ ast_pthread_create_background(&conf->recordthread, &conf->attr, recordthread, conf);
+ pthread_attr_destroy(&conf->attr);
+ }
+ }
+ ast_mutex_unlock(&conf->recordthreadlock);
+
+ ast_mutex_lock(&conf->announcethreadlock);
+ if ((conf->announcethread == AST_PTHREADT_NULL) && !(confflags & CONFFLAG_QUIET) && ((confflags & CONFFLAG_INTROUSER) || (confflags & CONFFLAG_INTROUSERNOREVIEW))) {
+ ast_mutex_init(&conf->announcelistlock);
+ AST_LIST_HEAD_INIT_NOLOCK(&conf->announcelist);
+ ast_pthread_create_background(&conf->announcethread, NULL, announce_thread, conf);
+ }
+ ast_mutex_unlock(&conf->announcethreadlock);
+
+ time(&user->jointime);
+
+ if (conf->locked && (!(confflags & CONFFLAG_ADMIN))) {
+ /* Sorry, but this confernce is locked! */
+ if (!ast_streamfile(chan, "conf-locked", chan->language))
+ ast_waitstream(chan, "");
+ goto outrun;
+ }
+
+ ast_mutex_lock(&conf->playlock);
+
+ if (AST_LIST_EMPTY(&conf->userlist))
+ user->user_no = 1;
+ else
+ user->user_no = AST_LIST_LAST(&conf->userlist)->user_no + 1;
+
+ AST_LIST_INSERT_TAIL(&conf->userlist, user, list);
+
+ user->chan = chan;
+ user->userflags = confflags;
+ user->adminflags = (confflags & CONFFLAG_STARTMUTED) ? ADMINFLAG_SELFMUTED : 0;
+ user->talking = -1;
+
+ ast_mutex_unlock(&conf->playlock);
+
+ if (!(confflags & CONFFLAG_QUIET) && ((confflags & CONFFLAG_INTROUSER) || (confflags & CONFFLAG_INTROUSERNOREVIEW))) {
+ char destdir[PATH_MAX];
+
+ snprintf(destdir, sizeof(destdir), "%s/meetme", ast_config_AST_SPOOL_DIR);
+
+ if (mkdir(destdir, 0777) && errno != EEXIST) {
+ ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", destdir, strerror(errno));
+ goto outrun;
+ }
+
+ snprintf(user->namerecloc, sizeof(user->namerecloc),
+ "%s/meetme-username-%s-%d", destdir,
+ conf->confno, user->user_no);
+ if (confflags & CONFFLAG_INTROUSERNOREVIEW)
+ res = ast_play_and_record(chan, "vm-rec-name", user->namerecloc, 10, "sln", &duration, 128, 0, NULL);
+ else
+ res = ast_record_review(chan, "vm-rec-name", user->namerecloc, 10, "sln", &duration, NULL);
+ if (res == -1)
+ goto outrun;
+ }
+
+ ast_mutex_lock(&conf->playlock);
+
+ if (confflags & CONFFLAG_MARKEDUSER)
+ conf->markedusers++;
+ conf->users++;
+ /* Update table */
+ snprintf(members, sizeof(members), "%d", conf->users);
+ ast_update_realtime("meetme", "confno", conf->confno, "members", members , NULL);
+ setusercount = 1;
+
+ /* This device changed state now - if this is the first user */
+ if (conf->users == 1)
+ ast_device_state_changed("meetme:%s", conf->confno);
+
+ ast_mutex_unlock(&conf->playlock);
+
+ if (confflags & CONFFLAG_EXIT_CONTEXT) {
+ if ((agifile = pbx_builtin_getvar_helper(chan, "MEETME_EXIT_CONTEXT")))
+ ast_copy_string(exitcontext, agifile, sizeof(exitcontext));
+ else if (!ast_strlen_zero(chan->macrocontext))
+ ast_copy_string(exitcontext, chan->macrocontext, sizeof(exitcontext));
+ else
+ ast_copy_string(exitcontext, chan->context, sizeof(exitcontext));
+ }
+
+ if ( !(confflags & (CONFFLAG_QUIET | CONFFLAG_NOONLYPERSON)) ) {
+ if (conf->users == 1 && !(confflags & CONFFLAG_WAITMARKED))
+ if (!ast_streamfile(chan, "conf-onlyperson", chan->language))
+ ast_waitstream(chan, "");
+ if ((confflags & CONFFLAG_WAITMARKED) && conf->markedusers == 0)
+ if (!ast_streamfile(chan, "conf-waitforleader", chan->language))
+ ast_waitstream(chan, "");
+ }
+
+ if (!(confflags & CONFFLAG_QUIET) && (confflags & CONFFLAG_ANNOUNCEUSERCOUNT) && conf->users > 1) {
+ int keepplaying = 1;
+
+ if (conf->users == 2) {
+ if (!ast_streamfile(chan,"conf-onlyone",chan->language)) {
+ res = ast_waitstream(chan, AST_DIGIT_ANY);
+ ast_stopstream(chan);
+ if (res > 0)
+ keepplaying=0;
+ else if (res == -1)
+ goto outrun;
+ }
+ } else {
+ if (!ast_streamfile(chan, "conf-thereare", chan->language)) {
+ res = ast_waitstream(chan, AST_DIGIT_ANY);
+ ast_stopstream(chan);
+ if (res > 0)
+ keepplaying=0;
+ else if (res == -1)
+ goto outrun;
+ }
+ if (keepplaying) {
+ res = ast_say_number(chan, conf->users - 1, AST_DIGIT_ANY, chan->language, (char *) NULL);
+ if (res > 0)
+ keepplaying=0;
+ else if (res == -1)
+ goto outrun;
+ }
+ if (keepplaying && !ast_streamfile(chan, "conf-otherinparty", chan->language)) {
+ res = ast_waitstream(chan, AST_DIGIT_ANY);
+ ast_stopstream(chan);
+ if (res > 0)
+ keepplaying=0;
+ else if (res == -1)
+ goto outrun;
+ }
+ }
+ }
+
+ if (!(confflags & CONFFLAG_NO_AUDIO_UNTIL_UP)) {
+ /* We're leaving this alone until the state gets changed to up */
+ ast_indicate(chan, -1);
+ }
+
+ if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) {
+ ast_log(LOG_WARNING, "Unable to set '%s' to write linear mode\n", chan->name);
+ goto outrun;
+ }
+
+ if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0) {
+ ast_log(LOG_WARNING, "Unable to set '%s' to read linear mode\n", chan->name);
+ goto outrun;
+ }
+
+ retryzap = (strcasecmp(chan->tech->type, dahdi_chan_name) || (chan->audiohooks || chan->monitor) ? 1 : 0);
+ user->zapchannel = !retryzap;
+
+ zapretry:
+ origfd = chan->fds[0];
+ if (retryzap) {
+ fd = open(DAHDI_FILE_PSEUDO, O_RDWR);
+ if (fd < 0) {
+ ast_log(LOG_WARNING, "Unable to open pseudo channel: %s\n", strerror(errno));
+ goto outrun;
+ }
+ using_pseudo = 1;
+ /* Make non-blocking */
+ flags = fcntl(fd, F_GETFL);
+ if (flags < 0) {
+ ast_log(LOG_WARNING, "Unable to get flags: %s\n", strerror(errno));
+ close(fd);
+ goto outrun;
+ }
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) {
+ ast_log(LOG_WARNING, "Unable to set flags: %s\n", strerror(errno));
+ close(fd);
+ goto outrun;
+ }
+ /* Setup buffering information */
+ memset(&bi, 0, sizeof(bi));
+ bi.bufsize = CONF_SIZE/2;
+ bi.txbufpolicy = DAHDI_POLICY_IMMEDIATE;
+ bi.rxbufpolicy = DAHDI_POLICY_IMMEDIATE;
+ bi.numbufs = audio_buffers;
+ if (ioctl(fd, DAHDI_SET_BUFINFO, &bi)) {
+ ast_log(LOG_WARNING, "Unable to set buffering information: %s\n", strerror(errno));
+ close(fd);
+ goto outrun;
+ }
+ x = 1;
+ if (ioctl(fd, DAHDI_SETLINEAR, &x)) {
+ ast_log(LOG_WARNING, "Unable to set linear mode: %s\n", strerror(errno));
+ close(fd);
+ goto outrun;
+ }
+ nfds = 1;
+ } else {
+ /* XXX Make sure we're not running on a pseudo channel XXX */
+ fd = chan->fds[0];
+ nfds = 0;
+ }
+ memset(&ztc, 0, sizeof(ztc));
+ memset(&ztc_empty, 0, sizeof(ztc_empty));
+ /* Check to see if we're in a conference... */
+ ztc.chan = 0;
+ if (ioctl(fd, DAHDI_GETCONF, &ztc)) {
+ ast_log(LOG_WARNING, "Error getting conference\n");
+ close(fd);
+ goto outrun;
+ }
+ if (ztc.confmode) {
+ /* Whoa, already in a conference... Retry... */
+ if (!retryzap) {
+ ast_log(LOG_DEBUG, "%s channel is in a conference already, retrying with pseudo\n", dahdi_chan_name);
+ retryzap = 1;
+ goto zapretry;
+ }
+ }
+ memset(&ztc, 0, sizeof(ztc));
+ /* Add us to the conference */
+ ztc.chan = 0;
+ ztc.confno = conf->zapconf;
+
+ if (!(confflags & CONFFLAG_QUIET) && ((confflags & CONFFLAG_INTROUSER) || (confflags & CONFFLAG_INTROUSERNOREVIEW)) && conf->users > 1) {
+ struct announce_listitem *item;
+ if (!(item = ao2_alloc(sizeof(*item), NULL)))
+ return -1;
+ ast_copy_string(item->namerecloc, user->namerecloc, sizeof(item->namerecloc));
+ ast_copy_string(item->language, chan->language, sizeof(item->language));
+ item->confchan = conf->chan;
+ item->confusers = conf->users;
+ item->announcetype = CONF_HASJOIN;
+ ast_mutex_lock(&conf->announcelistlock);
+ ao2_ref(item, +1); /* add one more so we can determine when announce_thread is done playing it */
+ AST_LIST_INSERT_TAIL(&conf->announcelist, item, entry);
+ ast_cond_signal(&conf->announcelist_addition);
+ ast_mutex_unlock(&conf->announcelistlock);
+
+ while (!ast_check_hangup(conf->chan) && ao2_ref(item, 0) == 2 && !ast_safe_sleep(chan, 1000)) {
+ ;
+ }
+ ao2_ref(item, -1);
+ }
+
+ if (confflags & CONFFLAG_WAITMARKED && !conf->markedusers)
+ ztc.confmode = DAHDI_CONF_CONF;
+ else if (confflags & CONFFLAG_MONITOR)
+ ztc.confmode = DAHDI_CONF_CONFMON | DAHDI_CONF_LISTENER;
+ else if (confflags & CONFFLAG_TALKER)
+ ztc.confmode = DAHDI_CONF_CONF | DAHDI_CONF_TALKER;
+ else
+ ztc.confmode = DAHDI_CONF_CONF | DAHDI_CONF_TALKER | DAHDI_CONF_LISTENER;
+
+ if (ioctl(fd, DAHDI_SETCONF, &ztc)) {
+ ast_log(LOG_WARNING, "Error setting conference\n");
+ close(fd);
+ goto outrun;
+ }
+ ast_log(LOG_DEBUG, "Placed channel %s in %s conf %d\n", chan->name, dahdi_chan_name, conf->zapconf);
+
+ if (!sent_event) {
+ manager_event(EVENT_FLAG_CALL, "MeetmeJoin",
+ "Channel: %s\r\n"
+ "Uniqueid: %s\r\n"
+ "Meetme: %s\r\n"
+ "Usernum: %d\r\n",
+ chan->name, chan->uniqueid, conf->confno, user->user_no);
+ sent_event = 1;
+ }
+
+ if (!firstpass && !(confflags & CONFFLAG_MONITOR) && !(confflags & CONFFLAG_ADMIN)) {
+ firstpass = 1;
+ if (!(confflags & CONFFLAG_QUIET))
+ if (!(confflags & CONFFLAG_WAITMARKED) || ((confflags & CONFFLAG_MARKEDUSER) && (conf->markedusers >= 1)))
+ conf_play(chan, conf, ENTER);
+ }
+
+ conf_flush(fd, chan);
+
+ if (confflags & CONFFLAG_AGI) {
+ /* Get name of AGI file to run from $(MEETME_AGI_BACKGROUND)
+ or use default filename of conf-background.agi */
+
+ agifile = pbx_builtin_getvar_helper(chan, "MEETME_AGI_BACKGROUND");
+ if (!agifile)
+ agifile = agifiledefault;
+
+ if (user->zapchannel) {
+ /* Set CONFMUTE mode on Zap channel to mute DTMF tones */
+ x = 1;
+ ast_channel_setoption(chan, AST_OPTION_TONE_VERIFY, &x, sizeof(char), 0);
+ }
+ /* Find a pointer to the agi app and execute the script */
+ app = pbx_findapp("agi");
+ if (app) {
+ char *s = ast_strdupa(agifile);
+ ret = pbx_exec(chan, app, s);
+ } else {
+ ast_log(LOG_WARNING, "Could not find application (agi)\n");
+ ret = -2;
+ }
+ if (user->zapchannel) {
+ /* Remove CONFMUTE mode on Zap channel */
+ x = 0;
+ ast_channel_setoption(chan, AST_OPTION_TONE_VERIFY, &x, sizeof(char), 0);
+ }
+ } else {
+ if (user->zapchannel && (confflags & CONFFLAG_STARMENU)) {
+ /* Set CONFMUTE mode on Zap channel to mute DTMF tones when the menu is enabled */
+ x = 1;
+ ast_channel_setoption(chan, AST_OPTION_TONE_VERIFY, &x, sizeof(char), 0);
+ }
+ if (confflags & (CONFFLAG_MONITORTALKER | CONFFLAG_OPTIMIZETALKER) && !(dsp = ast_dsp_new())) {
+ ast_log(LOG_WARNING, "Unable to allocate DSP!\n");
+ res = -1;
+ }
+ for(;;) {
+ int menu_was_active = 0;
+
+ outfd = -1;
+ ms = -1;
+
+ if (timeout && time(NULL) >= timeout)
+ break;
+
+ /* if we have just exited from the menu, and the user had a channel-driver
+ volume adjustment, restore it
+ */
+ if (!menu_active && menu_was_active && user->listen.desired && !user->listen.actual)
+ set_talk_volume(user, user->listen.desired);
+
+ menu_was_active = menu_active;
+
+ currentmarked = conf->markedusers;
+ if (!(confflags & CONFFLAG_QUIET) &&
+ (confflags & CONFFLAG_MARKEDUSER) &&
+ (confflags & CONFFLAG_WAITMARKED) &&
+ lastmarked == 0) {
+ if (currentmarked == 1 && conf->users > 1) {
+ ast_say_number(chan, conf->users - 1, AST_DIGIT_ANY, chan->language, (char *) NULL);
+ if (conf->users - 1 == 1) {
+ if (!ast_streamfile(chan, "conf-userwilljoin", chan->language))
+ ast_waitstream(chan, "");
+ } else {
+ if (!ast_streamfile(chan, "conf-userswilljoin", chan->language))
+ ast_waitstream(chan, "");
+ }
+ }
+ if (conf->users == 1 && ! (confflags & CONFFLAG_MARKEDUSER))
+ if (!ast_streamfile(chan, "conf-onlyperson", chan->language))
+ ast_waitstream(chan, "");
+ }
+
+ c = ast_waitfor_nandfds(&chan, 1, &fd, nfds, NULL, &outfd, &ms);
+
+
+ /* Update the struct with the actual confflags */
+ user->userflags = confflags;
+
+ if (confflags & CONFFLAG_WAITMARKED) {
+ if(currentmarked == 0) {
+ if (lastmarked != 0) {
+ if (!(confflags & CONFFLAG_QUIET))
+ if (!ast_streamfile(chan, "conf-leaderhasleft", chan->language))
+ ast_waitstream(chan, "");
+ if(confflags & CONFFLAG_MARKEDEXIT)
+ break;
+ else {
+ ztc.confmode = DAHDI_CONF_CONF;
+ if (ioctl(fd, DAHDI_SETCONF, &ztc)) {
+ ast_log(LOG_WARNING, "Error setting conference\n");
+ close(fd);
+ goto outrun;
+ }
+ }
+ }
+ if (musiconhold == 0 && (confflags & CONFFLAG_MOH)) {
+ ast_moh_start(chan, NULL, NULL);
+ musiconhold = 1;
+ }
+ } else if(currentmarked >= 1 && lastmarked == 0) {
+ /* Marked user entered, so cancel timeout */
+ timeout = 0;
+ if (confflags & CONFFLAG_MONITOR)
+ ztc.confmode = DAHDI_CONF_CONFMON | DAHDI_CONF_LISTENER;
+ else if (confflags & CONFFLAG_TALKER)
+ ztc.confmode = DAHDI_CONF_CONF | DAHDI_CONF_TALKER;
+ else
+ ztc.confmode = DAHDI_CONF_CONF | DAHDI_CONF_TALKER | DAHDI_CONF_LISTENER;
+ if (ioctl(fd, DAHDI_SETCONF, &ztc)) {
+ ast_log(LOG_WARNING, "Error setting conference\n");
+ close(fd);
+ goto outrun;
+ }
+ if (musiconhold && (confflags & CONFFLAG_MOH)) {
+ ast_moh_stop(chan);
+ musiconhold = 0;
+ }
+ if ( !(confflags & CONFFLAG_QUIET) && !(confflags & CONFFLAG_MARKEDUSER)) {
+ if (!ast_streamfile(chan, "conf-placeintoconf", chan->language))
+ ast_waitstream(chan, "");
+ conf_play(chan, conf, ENTER);
+ }
+ }
+ }
+
+ /* trying to add moh for single person conf */
+ if ((confflags & CONFFLAG_MOH) && !(confflags & CONFFLAG_WAITMARKED)) {
+ if (conf->users == 1) {
+ if (musiconhold == 0) {
+ ast_moh_start(chan, NULL, NULL);
+ musiconhold = 1;
+ }
+ } else {
+ if (musiconhold) {
+ ast_moh_stop(chan);
+ musiconhold = 0;
+ }
+ }
+ }
+
+ /* Leave if the last marked user left */
+ if (currentmarked == 0 && lastmarked != 0 && (confflags & CONFFLAG_MARKEDEXIT)) {
+ ret = -1;
+ break;
+ }
+
+ /* Check if my modes have changed */
+
+ /* If I should be muted but am still talker, mute me */
+ if ((user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED)) && (ztc.confmode & DAHDI_CONF_TALKER)) {
+ ztc.confmode ^= DAHDI_CONF_TALKER;
+ if (ioctl(fd, DAHDI_SETCONF, &ztc)) {
+ ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
+ ret = -1;
+ break;
+ }
+
+ manager_event(EVENT_FLAG_CALL, "MeetmeMute",
+ "Channel: %s\r\n"
+ "Uniqueid: %s\r\n"
+ "Meetme: %s\r\n"
+ "Usernum: %i\r\n"
+ "Status: on\r\n",
+ chan->name, chan->uniqueid, conf->confno, user->user_no);
+ }
+
+ /* If I should be un-muted but am not talker, un-mute me */
+ if (!(user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED)) && !(confflags & CONFFLAG_MONITOR) && !(ztc.confmode & DAHDI_CONF_TALKER)) {
+ ztc.confmode |= DAHDI_CONF_TALKER;
+ if (ioctl(fd, DAHDI_SETCONF, &ztc)) {
+ ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
+ ret = -1;
+ break;
+ }
+
+ manager_event(EVENT_FLAG_CALL, "MeetmeMute",
+ "Channel: %s\r\n"
+ "Uniqueid: %s\r\n"
+ "Meetme: %s\r\n"
+ "Usernum: %i\r\n"
+ "Status: off\r\n",
+ chan->name, chan->uniqueid, conf->confno, user->user_no);
+ }
+
+ /* If I have been kicked, exit the conference */
+ if (user->adminflags & ADMINFLAG_KICKME) {
+ //You have been kicked.
+ if (!(confflags & CONFFLAG_QUIET) &&
+ !ast_streamfile(chan, "conf-kicked", chan->language)) {
+ ast_waitstream(chan, "");
+ }
+ ret = 0;
+ break;
+ }
+
+ /* Perform an extra hangup check just in case */
+ if (ast_check_hangup(chan))
+ break;
+
+ if (c) {
+ char dtmfstr[2] = "";
+
+ if (c->fds[0] != origfd || (user->zapchannel && (c->audiohooks || c->monitor))) {
+ if (using_pseudo) {
+ /* Kill old pseudo */
+ close(fd);
+ using_pseudo = 0;
+ }
+ ast_log(LOG_DEBUG, "Ooh, something swapped out under us, starting over\n");
+ retryzap = (strcasecmp(c->tech->type, dahdi_chan_name) || (c->audiohooks || c->monitor) ? 1 : 0);
+ user->zapchannel = !retryzap;
+ goto zapretry;
+ }
+ if ((confflags & CONFFLAG_MONITOR) || (user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED)))
+ f = ast_read_noaudio(c);
+ else
+ f = ast_read(c);
+ if (!f)
+ break;
+ if (f->frametype == AST_FRAME_DTMF) {
+ dtmfstr[0] = f->subclass;
+ dtmfstr[1] = '\0';
+ }
+
+ if ((f->frametype == AST_FRAME_VOICE) && (f->subclass == AST_FORMAT_SLINEAR)) {
+ if (user->talk.actual)
+ ast_frame_adjust_volume(f, user->talk.actual);
+
+ if (confflags & (CONFFLAG_MONITORTALKER | CONFFLAG_OPTIMIZETALKER)) {
+ int totalsilence;
+
+ if (user->talking == -1)
+ user->talking = 0;
+
+ res = ast_dsp_silence(dsp, f, &totalsilence);
+ if (!user->talking && totalsilence < MEETME_DELAYDETECTTALK) {
+ user->talking = 1;
+ if (confflags & CONFFLAG_MONITORTALKER)
+ manager_event(EVENT_FLAG_CALL, "MeetmeTalking",
+ "Channel: %s\r\n"
+ "Uniqueid: %s\r\n"
+ "Meetme: %s\r\n"
+ "Usernum: %d\r\n"
+ "Status: on\r\n",
+ chan->name, chan->uniqueid, conf->confno, user->user_no);
+ }
+ if (user->talking && totalsilence > MEETME_DELAYDETECTENDTALK) {
+ user->talking = 0;
+ if (confflags & CONFFLAG_MONITORTALKER)
+ manager_event(EVENT_FLAG_CALL, "MeetmeTalking",
+ "Channel: %s\r\n"
+ "Uniqueid: %s\r\n"
+ "Meetme: %s\r\n"
+ "Usernum: %d\r\n"
+ "Status: off\r\n",
+ chan->name, chan->uniqueid, conf->confno, user->user_no);
+ }
+ }
+ if (using_pseudo) {
+ /* Absolutely do _not_ use careful_write here...
+ it is important that we read data from the channel
+ as fast as it arrives, and feed it into the conference.
+ The buffering in the pseudo channel will take care of any
+ timing differences, unless they are so drastic as to lose
+ audio frames (in which case carefully writing would only
+ have delayed the audio even further).
+ */
+ /* As it turns out, we do want to use careful write. We just
+ don't want to block, but we do want to at least *try*
+ to write out all the samples.
+ */
+ if (user->talking || !(confflags & CONFFLAG_OPTIMIZETALKER))
+ careful_write(fd, f->data, f->datalen, 0);
+ }
+ } else if ((f->frametype == AST_FRAME_DTMF) && (f->subclass == '#') && (confflags & CONFFLAG_POUNDEXIT)) {
+ if (confflags & CONFFLAG_PASS_DTMF)
+ conf_queue_dtmf(conf, user, f);
+ ret = 0;
+ ast_frfree(f);
+ break;
+ } else if (((f->frametype == AST_FRAME_DTMF) && (f->subclass == '*') && (confflags & CONFFLAG_STARMENU)) || ((f->frametype == AST_FRAME_DTMF) && menu_active)) {
+ if (confflags & CONFFLAG_PASS_DTMF)
+ conf_queue_dtmf(conf, user, f);
+ if (ioctl(fd, DAHDI_SETCONF, &ztc_empty)) {
+ ast_log(LOG_WARNING, "Error setting conference\n");
+ close(fd);
+ ast_frfree(f);
+ goto outrun;
+ }
+
+ /* if we are entering the menu, and the user has a channel-driver
+ volume adjustment, clear it
+ */
+ if (!menu_active && user->talk.desired && !user->talk.actual)
+ set_talk_volume(user, 0);
+
+ if (musiconhold) {
+ ast_moh_stop(chan);
+ }
+ if ((confflags & CONFFLAG_ADMIN)) {
+ /* Admin menu */
+ if (!menu_active) {
+ menu_active = 1;
+ /* Record this sound! */
+ if (!ast_streamfile(chan, "conf-adminmenu", chan->language)) {
+ dtmf = ast_waitstream(chan, AST_DIGIT_ANY);
+ ast_stopstream(chan);
+ } else
+ dtmf = 0;
+ } else
+ dtmf = f->subclass;
+ if (dtmf) {
+ switch(dtmf) {
+ case '1': /* Un/Mute */
+ menu_active = 0;
+
+ /* for admin, change both admin and use flags */
+ if (user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED))
+ user->adminflags &= ~(ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED);
+ else
+ user->adminflags |= (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED);
+
+ if ((confflags & CONFFLAG_MONITOR) || (user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED))) {
+ if (!ast_streamfile(chan, "conf-muted", chan->language))
+ ast_waitstream(chan, "");
+ } else {
+ if (!ast_streamfile(chan, "conf-unmuted", chan->language))
+ ast_waitstream(chan, "");
+ }
+ break;
+ case '2': /* Un/Lock the Conference */
+ menu_active = 0;
+ if (conf->locked) {
+ conf->locked = 0;
+ if (!ast_streamfile(chan, "conf-unlockednow", chan->language))
+ ast_waitstream(chan, "");
+ } else {
+ conf->locked = 1;
+ if (!ast_streamfile(chan, "conf-lockednow", chan->language))
+ ast_waitstream(chan, "");
+ }
+ break;
+ case '3': /* Eject last user */
+ menu_active = 0;
+ usr = AST_LIST_LAST(&conf->userlist);
+ if ((usr->chan->name == chan->name)||(usr->userflags & CONFFLAG_ADMIN)) {
+ if(!ast_streamfile(chan, "conf-errormenu", chan->language))
+ ast_waitstream(chan, "");
+ } else
+ usr->adminflags |= ADMINFLAG_KICKME;
+ ast_stopstream(chan);
+ break;
+ case '4':
+ tweak_listen_volume(user, VOL_DOWN);
+ break;
+ case '6':
+ tweak_listen_volume(user, VOL_UP);
+ break;
+ case '7':
+ tweak_talk_volume(user, VOL_DOWN);
+ break;
+ case '8':
+ menu_active = 0;
+ break;
+ case '9':
+ tweak_talk_volume(user, VOL_UP);
+ break;
+ default:
+ menu_active = 0;
+ /* Play an error message! */
+ if (!ast_streamfile(chan, "conf-errormenu", chan->language))
+ ast_waitstream(chan, "");
+ break;
+ }
+ }
+ } else {
+ /* User menu */
+ if (!menu_active) {
+ menu_active = 1;
+ if (!ast_streamfile(chan, "conf-usermenu", chan->language)) {
+ dtmf = ast_waitstream(chan, AST_DIGIT_ANY);
+ ast_stopstream(chan);
+ } else
+ dtmf = 0;
+ } else
+ dtmf = f->subclass;
+ if (dtmf) {
+ switch(dtmf) {
+ case '1': /* Un/Mute */
+ menu_active = 0;
+
+ /* user can only toggle the self-muted state */
+ user->adminflags ^= ADMINFLAG_SELFMUTED;
+
+ /* they can't override the admin mute state */
+ if ((confflags & CONFFLAG_MONITOR) || (user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED))) {
+ if (!ast_streamfile(chan, "conf-muted", chan->language))
+ ast_waitstream(chan, "");
+ } else {
+ if (!ast_streamfile(chan, "conf-unmuted", chan->language))
+ ast_waitstream(chan, "");
+ }
+ break;
+ case '4':
+ tweak_listen_volume(user, VOL_DOWN);
+ break;
+ case '6':
+ tweak_listen_volume(user, VOL_UP);
+ break;
+ case '7':
+ tweak_talk_volume(user, VOL_DOWN);
+ break;
+ case '8':
+ menu_active = 0;
+ break;
+ case '9':
+ tweak_talk_volume(user, VOL_UP);
+ break;
+ default:
+ menu_active = 0;
+ if (!ast_streamfile(chan, "conf-errormenu", chan->language))
+ ast_waitstream(chan, "");
+ break;
+ }
+ }
+ }
+ if (musiconhold)
+ ast_moh_start(chan, NULL, NULL);
+
+ if (ioctl(fd, DAHDI_SETCONF, &ztc)) {
+ ast_log(LOG_WARNING, "Error setting conference\n");
+ close(fd);
+ ast_frfree(f);
+ goto outrun;
+ }
+
+ conf_flush(fd, chan);
+ /* Since this option could absorb dtmf for the previous, we have to check this one last */
+ } else if ((f->frametype == AST_FRAME_DTMF) && (confflags & CONFFLAG_EXIT_CONTEXT) && ast_exists_extension(chan, exitcontext, dtmfstr, 1, "")) {
+ if (confflags & CONFFLAG_PASS_DTMF)
+ conf_queue_dtmf(conf, user, f);
+
+ if (!ast_goto_if_exists(chan, exitcontext, dtmfstr, 1)) {
+ ast_log(LOG_DEBUG, "Got DTMF %c, goto context %s\n", dtmfstr[0], exitcontext);
+ ret = 0;
+ ast_frfree(f);
+ break;
+ } else if (option_debug > 1)
+ ast_log(LOG_DEBUG, "Exit by single digit did not work in meetme. Extension '%s' does not exist in context '%s'\n", dtmfstr, exitcontext);
+ } else if ((f->frametype == AST_FRAME_DTMF_BEGIN || f->frametype == AST_FRAME_DTMF_END)
+ && confflags & CONFFLAG_PASS_DTMF) {
+ conf_queue_dtmf(conf, user, f);
+ } else if ((confflags & CONFFLAG_SLA_STATION) && f->frametype == AST_FRAME_CONTROL) {
+ switch (f->subclass) {
+ case AST_CONTROL_HOLD:
+ sla_queue_event_conf(SLA_EVENT_HOLD, chan, conf);
+ break;
+ default:
+ break;
+ }
+ } else if (f->frametype == AST_FRAME_NULL) {
+ /* Ignore NULL frames. It is perfectly normal to get these if the person is muted. */
+ } else if (option_debug) {
+ ast_log(LOG_DEBUG,
+ "Got unrecognized frame on channel %s, f->frametype=%d,f->subclass=%d\n",
+ chan->name, f->frametype, f->subclass);
+ }
+ ast_frfree(f);
+ } else if (outfd > -1) {
+ res = read(outfd, buf, CONF_SIZE);
+ if (res > 0) {
+ memset(&fr, 0, sizeof(fr));
+ fr.frametype = AST_FRAME_VOICE;
+ fr.subclass = AST_FORMAT_SLINEAR;
+ fr.datalen = res;
+ fr.samples = res/2;
+ fr.data = buf;
+ fr.offset = AST_FRIENDLY_OFFSET;
+ if (!user->listen.actual &&
+ ((confflags & CONFFLAG_MONITOR) ||
+ (user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED)) ||
+ (!user->talking && (confflags & CONFFLAG_OPTIMIZETALKER))
+ )) {
+ int index;
+ for (index=0;index<AST_FRAME_BITS;index++)
+ if (chan->rawwriteformat & (1 << index))
+ break;
+ if (index >= AST_FRAME_BITS)
+ goto bailoutandtrynormal;
+ ast_mutex_lock(&conf->listenlock);
+ if (!conf->transframe[index]) {
+ if (conf->origframe) {
+ if (!conf->transpath[index])
+ conf->transpath[index] = ast_translator_build_path((1 << index), AST_FORMAT_SLINEAR);
+ if (conf->transpath[index]) {
+ conf->transframe[index] = ast_translate(conf->transpath[index], conf->origframe, 0);
+ if (!conf->transframe[index])
+ conf->transframe[index] = &ast_null_frame;
+ }
+ }
+ }
+ if (conf->transframe[index]) {
+ if (conf->transframe[index]->frametype != AST_FRAME_NULL) {
+ if (can_write(chan, confflags) && ast_write(chan, conf->transframe[index]))
+ ast_log(LOG_WARNING, "Unable to write frame to channel %s\n", chan->name);
+ }
+ } else {
+ ast_mutex_unlock(&conf->listenlock);
+ goto bailoutandtrynormal;
+ }
+ ast_mutex_unlock(&conf->listenlock);
+ } else {
+bailoutandtrynormal:
+ if (user->listen.actual)
+ ast_frame_adjust_volume(&fr, user->listen.actual);
+ if (can_write(chan, confflags) && ast_write(chan, &fr) < 0) {
+ ast_log(LOG_WARNING, "Unable to write frame to channel %s\n", chan->name);
+ }
+ }
+ } else
+ ast_log(LOG_WARNING, "Failed to read frame: %s\n", strerror(errno));
+ }
+ lastmarked = currentmarked;
+ }
+ }
+
+ if (musiconhold)
+ ast_moh_stop(chan);
+
+ if (using_pseudo)
+ close(fd);
+ else {
+ /* Take out of conference */
+ ztc.chan = 0;
+ ztc.confno = 0;
+ ztc.confmode = 0;
+ if (ioctl(fd, DAHDI_SETCONF, &ztc)) {
+ ast_log(LOG_WARNING, "Error setting conference\n");
+ }
+ }
+
+ reset_volumes(user);
+
+ if (!(confflags & CONFFLAG_QUIET) && !(confflags & CONFFLAG_MONITOR) && !(confflags & CONFFLAG_ADMIN))
+ conf_play(chan, conf, LEAVE);
+
+ if (!(confflags & CONFFLAG_QUIET) && ((confflags & CONFFLAG_INTROUSER) || (confflags & CONFFLAG_INTROUSERNOREVIEW)) && conf->users > 1) {
+ struct announce_listitem *item;
+ if (!(item = ao2_alloc(sizeof(*item), NULL)))
+ return -1;
+ ast_copy_string(item->namerecloc, user->namerecloc, sizeof(item->namerecloc));
+ ast_copy_string(item->language, chan->language, sizeof(item->language));
+ item->confchan = conf->chan;
+ item->confusers = conf->users;
+ item->announcetype = CONF_HASLEFT;
+ ast_mutex_lock(&conf->announcelistlock);
+ AST_LIST_INSERT_TAIL(&conf->announcelist, item, entry);
+ ast_cond_signal(&conf->announcelist_addition);
+ ast_mutex_unlock(&conf->announcelistlock);
+ } else if (!(confflags & CONFFLAG_QUIET) && ((confflags & CONFFLAG_INTROUSER) || (confflags & CONFFLAG_INTROUSERNOREVIEW)) && conf->users == 1) {
+ /* Last person is leaving, so no reason to try and announce, but should delete the name recording */
+ ast_filedelete(user->namerecloc, NULL);
+ }
+
+ outrun:
+ AST_LIST_LOCK(&confs);
+
+ if (dsp)
+ ast_dsp_free(dsp);
+
+ if (user->user_no) { /* Only cleanup users who really joined! */
+ now = time(NULL);
+ hr = (now - user->jointime) / 3600;
+ min = ((now - user->jointime) % 3600) / 60;
+ sec = (now - user->jointime) % 60;
+
+ if (sent_event) {
+ manager_event(EVENT_FLAG_CALL, "MeetmeLeave",
+ "Channel: %s\r\n"
+ "Uniqueid: %s\r\n"
+ "Meetme: %s\r\n"
+ "Usernum: %d\r\n"
+ "CallerIDnum: %s\r\n"
+ "CallerIDname: %s\r\n"
+ "Duration: %ld\r\n",
+ chan->name, chan->uniqueid, conf->confno,
+ user->user_no,
+ S_OR(user->chan->cid.cid_num, "<unknown>"),
+ S_OR(user->chan->cid.cid_name, "<unknown>"),
+ (long)(now - user->jointime));
+ }
+
+ if (setusercount) {
+ conf->users--;
+ /* Update table */
+ snprintf(members, sizeof(members), "%d", conf->users);
+ ast_update_realtime("meetme", "confno", conf->confno, "members", members, NULL);
+ if (confflags & CONFFLAG_MARKEDUSER)
+ conf->markedusers--;
+ }
+ /* Remove ourselves from the list */
+ AST_LIST_REMOVE(&conf->userlist, user, list);
+
+ /* Change any states */
+ if (!conf->users)
+ ast_device_state_changed("meetme:%s", conf->confno);
+
+ /* Return the number of seconds the user was in the conf */
+ snprintf(meetmesecs, sizeof(meetmesecs), "%d", (int) (time(NULL) - user->jointime));
+ pbx_builtin_setvar_helper(chan, "MEETMESECS", meetmesecs);
+ }
+ free(user);
+ AST_LIST_UNLOCK(&confs);
+
+ return ret;
+}
+
+static struct ast_conference *find_conf_realtime(struct ast_channel *chan, char *confno, int make, int dynamic,
+ char *dynamic_pin, size_t pin_buf_len, int refcount, struct ast_flags *confflags)
+{
+ struct ast_variable *var, *save;
+ struct ast_conference *cnf;
+
+ /* Check first in the conference list */
+ AST_LIST_LOCK(&confs);
+ AST_LIST_TRAVERSE(&confs, cnf, list) {
+ if (!strcmp(confno, cnf->confno))
+ break;
+ }
+ if (cnf) {
+ cnf->refcount += refcount;
+ }
+ AST_LIST_UNLOCK(&confs);
+
+ if (!cnf) {
+ char *pin = NULL, *pinadmin = NULL; /* For temp use */
+
+ var = ast_load_realtime("meetme", "confno", confno, NULL);
+
+ if (!var)
+ return NULL;
+
+ save = var;
+ while (var) {
+ if (!strcasecmp(var->name, "pin")) {
+ pin = ast_strdupa(var->value);
+ } else if (!strcasecmp(var->name, "adminpin")) {
+ pinadmin = ast_strdupa(var->value);
+ }
+ var = var->next;
+ }
+ ast_variables_destroy(save);
+
+ cnf = build_conf(confno, pin ? pin : "", pinadmin ? pinadmin : "", make, dynamic, refcount);
+ }
+
+ if (cnf) {
+ if (confflags && !cnf->chan &&
+ !ast_test_flag(confflags, CONFFLAG_QUIET) &&
+ ast_test_flag(confflags, CONFFLAG_INTROUSER)) {
+ ast_log(LOG_WARNING, "No %s channel available for conference, user introduction disabled\n", dahdi_chan_name);
+ ast_clear_flag(confflags, CONFFLAG_INTROUSER);
+ }
+
+ if (confflags && !cnf->chan &&
+ ast_test_flag(confflags, CONFFLAG_RECORDCONF)) {
+ ast_log(LOG_WARNING, "No %s channel available for conference, conference recording disabled\n", dahdi_chan_name);
+ ast_clear_flag(confflags, CONFFLAG_RECORDCONF);
+ }
+ }
+
+ return cnf;
+}
+
+
+static struct ast_conference *find_conf(struct ast_channel *chan, char *confno, int make, int dynamic,
+ char *dynamic_pin, size_t pin_buf_len, int refcount, struct ast_flags *confflags)
+{
+ struct ast_config *cfg;
+ struct ast_variable *var;
+ struct ast_conference *cnf;
+ char *parse;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(confno);
+ AST_APP_ARG(pin);
+ AST_APP_ARG(pinadmin);
+ );
+
+ /* Check first in the conference list */
+ AST_LIST_LOCK(&confs);
+ AST_LIST_TRAVERSE(&confs, cnf, list) {
+ if (!strcmp(confno, cnf->confno))
+ break;
+ }
+ if (cnf){
+ cnf->refcount += refcount;
+ }
+ AST_LIST_UNLOCK(&confs);
+
+ if (!cnf) {
+ if (dynamic) {
+ /* No need to parse meetme.conf */
+ ast_log(LOG_DEBUG, "Building dynamic conference '%s'\n", confno);
+ if (dynamic_pin) {
+ if (dynamic_pin[0] == 'q') {
+ /* Query the user to enter a PIN */
+ if (ast_app_getdata(chan, "conf-getpin", dynamic_pin, pin_buf_len - 1, 0) < 0)
+ return NULL;
+ }
+ cnf = build_conf(confno, dynamic_pin, "", make, dynamic, refcount);
+ } else {
+ cnf = build_conf(confno, "", "", make, dynamic, refcount);
+ }
+ } else {
+ /* Check the config */
+ cfg = ast_config_load(CONFIG_FILE_NAME);
+ if (!cfg) {
+ ast_log(LOG_WARNING, "No %s file :(\n", CONFIG_FILE_NAME);
+ return NULL;
+ }
+ for (var = ast_variable_browse(cfg, "rooms"); var; var = var->next) {
+ if (strcasecmp(var->name, "conf"))
+ continue;
+
+ if (!(parse = ast_strdupa(var->value)))
+ return NULL;
+
+ AST_NONSTANDARD_APP_ARGS(args, parse, ',');
+ if (!strcasecmp(args.confno, confno)) {
+ /* Bingo it's a valid conference */
+ cnf = build_conf(args.confno,
+ S_OR(args.pin, ""),
+ S_OR(args.pinadmin, ""),
+ make, dynamic, refcount);
+ break;
+ }
+ }
+ if (!var) {
+ ast_log(LOG_DEBUG, "%s isn't a valid conference\n", confno);
+ }
+ ast_config_destroy(cfg);
+ }
+ } else if (dynamic_pin) {
+ /* Correct for the user selecting 'D' instead of 'd' to have
+ someone join into a conference that has already been created
+ with a pin. */
+ if (dynamic_pin[0] == 'q')
+ dynamic_pin[0] = '\0';
+ }
+
+ if (cnf) {
+ if (confflags && !cnf->chan &&
+ !ast_test_flag(confflags, CONFFLAG_QUIET) &&
+ ast_test_flag(confflags, CONFFLAG_INTROUSER)) {
+ ast_log(LOG_WARNING, "No %s channel available for conference, user introduction disabled\n", dahdi_chan_name);
+ ast_clear_flag(confflags, CONFFLAG_INTROUSER);
+ }
+
+ if (confflags && !cnf->chan &&
+ ast_test_flag(confflags, CONFFLAG_RECORDCONF)) {
+ ast_log(LOG_WARNING, "No %s channel available for conference, conference recording disabled\n", dahdi_chan_name);
+ ast_clear_flag(confflags, CONFFLAG_RECORDCONF);
+ }
+ }
+
+ return cnf;
+}
+
+/*! \brief The MeetmeCount application */
+static int count_exec(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *u;
+ int res = 0;
+ struct ast_conference *conf;
+ int count;
+ char *localdata;
+ char val[80] = "0";
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(confno);
+ AST_APP_ARG(varname);
+ );
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "MeetMeCount requires an argument (conference number)\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ if (!(localdata = ast_strdupa(data))) {
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ AST_STANDARD_APP_ARGS(args, localdata);
+
+ conf = find_conf(chan, args.confno, 0, 0, NULL, 0, 1, NULL);
+
+ if (conf) {
+ count = conf->users;
+ dispose_conf(conf);
+ conf = NULL;
+ } else
+ count = 0;
+
+ if (!ast_strlen_zero(args.varname)){
+ /* have var so load it and exit */
+ snprintf(val, sizeof(val), "%d",count);
+ pbx_builtin_setvar_helper(chan, args.varname, val);
+ } else {
+ if (chan->_state != AST_STATE_UP)
+ ast_answer(chan);
+ res = ast_say_number(chan, count, "", chan->language, (char *) NULL); /* Needs gender */
+ }
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+/*! \brief The meetme() application */
+static int conf_exec(struct ast_channel *chan, void *data)
+{
+ int res=-1;
+ struct ast_module_user *u;
+ char confno[MAX_CONFNUM] = "";
+ int allowretry = 0;
+ int retrycnt = 0;
+ struct ast_conference *cnf = NULL;
+ struct ast_flags confflags = {0};
+ int dynamic = 0;
+ int empty = 0, empty_no_pin = 0;
+ int always_prompt = 0;
+ char *notdata, *info, the_pin[MAX_PIN] = "";
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(confno);
+ AST_APP_ARG(options);
+ AST_APP_ARG(pin);
+ );
+ char *optargs[OPT_ARG_ARRAY_SIZE] = { NULL, };
+
+ u = ast_module_user_add(chan);
+
+ if (ast_strlen_zero(data)) {
+ allowretry = 1;
+ notdata = "";
+ } else {
+ notdata = data;
+ }
+
+ if (chan->_state != AST_STATE_UP)
+ ast_answer(chan);
+
+ info = ast_strdupa(notdata);
+
+ AST_STANDARD_APP_ARGS(args, info);
+
+ if (args.confno) {
+ ast_copy_string(confno, args.confno, sizeof(confno));
+ if (ast_strlen_zero(confno)) {
+ allowretry = 1;
+ }
+ }
+
+ if (args.pin)
+ ast_copy_string(the_pin, args.pin, sizeof(the_pin));
+
+ if (args.options) {
+ ast_app_parse_options(meetme_opts, &confflags, optargs, args.options);
+ dynamic = ast_test_flag(&confflags, CONFFLAG_DYNAMIC | CONFFLAG_DYNAMICPIN);
+ if (ast_test_flag(&confflags, CONFFLAG_DYNAMICPIN) && ast_strlen_zero(args.pin))
+ strcpy(the_pin, "q");
+
+ empty = ast_test_flag(&confflags, CONFFLAG_EMPTY | CONFFLAG_EMPTYNOPIN);
+ empty_no_pin = ast_test_flag(&confflags, CONFFLAG_EMPTYNOPIN);
+ always_prompt = ast_test_flag(&confflags, CONFFLAG_ALWAYSPROMPT);
+ }
+
+ do {
+ if (retrycnt > 3)
+ allowretry = 0;
+ if (empty) {
+ int i;
+ struct ast_config *cfg;
+ struct ast_variable *var;
+ int confno_int;
+
+ /* We only need to load the config file for static and empty_no_pin (otherwise we don't care) */
+ if ((empty_no_pin) || (!dynamic)) {
+ cfg = ast_config_load(CONFIG_FILE_NAME);
+ if (cfg) {
+ var = ast_variable_browse(cfg, "rooms");
+ while (var) {
+ if (!strcasecmp(var->name, "conf")) {
+ char *stringp = ast_strdupa(var->value);
+ if (stringp) {
+ char *confno_tmp = strsep(&stringp, "|,");
+ int found = 0;
+ if (!dynamic) {
+ /* For static: run through the list and see if this conference is empty */
+ AST_LIST_LOCK(&confs);
+ AST_LIST_TRAVERSE(&confs, cnf, list) {
+ if (!strcmp(confno_tmp, cnf->confno)) {
+ /* The conference exists, therefore it's not empty */
+ found = 1;
+ break;
+ }
+ }
+ AST_LIST_UNLOCK(&confs);
+ if (!found) {
+ /* At this point, we have a confno_tmp (static conference) that is empty */
+ if ((empty_no_pin && ast_strlen_zero(stringp)) || (!empty_no_pin)) {
+ /* Case 1: empty_no_pin and pin is nonexistent (NULL)
+ * Case 2: empty_no_pin and pin is blank (but not NULL)
+ * Case 3: not empty_no_pin
+ */
+ ast_copy_string(confno, confno_tmp, sizeof(confno));
+ break;
+ /* XXX the map is not complete (but we do have a confno) */
+ }
+ }
+ }
+ }
+ }
+ var = var->next;
+ }
+ ast_config_destroy(cfg);
+ }
+ }
+
+ /* Select first conference number not in use */
+ if (ast_strlen_zero(confno) && dynamic) {
+ AST_LIST_LOCK(&confs);
+ for (i = 0; i < sizeof(conf_map) / sizeof(conf_map[0]); i++) {
+ if (!conf_map[i]) {
+ snprintf(confno, sizeof(confno), "%d", i);
+ conf_map[i] = 1;
+ break;
+ }
+ }
+ AST_LIST_UNLOCK(&confs);
+ }
+
+ /* Not found? */
+ if (ast_strlen_zero(confno)) {
+ res = ast_streamfile(chan, "conf-noempty", chan->language);
+ if (!res)
+ ast_waitstream(chan, "");
+ } else {
+ if (sscanf(confno, "%d", &confno_int) == 1) {
+ res = ast_streamfile(chan, "conf-enteringno", chan->language);
+ if (!res) {
+ ast_waitstream(chan, "");
+ res = ast_say_digits(chan, confno_int, "", chan->language);
+ }
+ } else {
+ ast_log(LOG_ERROR, "Could not scan confno '%s'\n", confno);
+ }
+ }
+ }
+
+ while (allowretry && (ast_strlen_zero(confno)) && (++retrycnt < 4)) {
+ /* Prompt user for conference number */
+ res = ast_app_getdata(chan, "conf-getconfno", confno, sizeof(confno) - 1, 0);
+ if (res < 0) {
+ /* Don't try to validate when we catch an error */
+ confno[0] = '\0';
+ allowretry = 0;
+ break;
+ }
+ }
+ if (!ast_strlen_zero(confno)) {
+ /* Check the validity of the conference */
+ cnf = find_conf(chan, confno, 1, dynamic, the_pin,
+ sizeof(the_pin), 1, &confflags);
+ if (!cnf) {
+ cnf = find_conf_realtime(chan, confno, 1, dynamic,
+ the_pin, sizeof(the_pin), 1, &confflags);
+ }
+
+ if (!cnf) {
+ res = ast_streamfile(chan, "conf-invalid", chan->language);
+ if (!res)
+ ast_waitstream(chan, "");
+ res = -1;
+ if (allowretry)
+ confno[0] = '\0';
+ } else {
+ if ((!ast_strlen_zero(cnf->pin) &&
+ !ast_test_flag(&confflags, CONFFLAG_ADMIN)) ||
+ (!ast_strlen_zero(cnf->pinadmin) &&
+ ast_test_flag(&confflags, CONFFLAG_ADMIN))) {
+ char pin[MAX_PIN] = "";
+ int j;
+
+ /* Allow the pin to be retried up to 3 times */
+ for (j = 0; j < 3; j++) {
+ if (*the_pin && (always_prompt == 0)) {
+ ast_copy_string(pin, the_pin, sizeof(pin));
+ res = 0;
+ } else {
+ /* Prompt user for pin if pin is required */
+ res = ast_app_getdata(chan, "conf-getpin", pin + strlen(pin), sizeof(pin) - 1 - strlen(pin), 0);
+ }
+ if (res >= 0) {
+ if (!strcasecmp(pin, cnf->pin) ||
+ (!ast_strlen_zero(cnf->pinadmin) &&
+ !strcasecmp(pin, cnf->pinadmin))) {
+ /* Pin correct */
+ allowretry = 0;
+ if (!ast_strlen_zero(cnf->pinadmin) && !strcasecmp(pin, cnf->pinadmin))
+ ast_set_flag(&confflags, CONFFLAG_ADMIN);
+ /* Run the conference */
+ res = conf_run(chan, cnf, confflags.flags, optargs);
+ break;
+ } else {
+ /* Pin invalid */
+ if (!ast_streamfile(chan, "conf-invalidpin", chan->language)) {
+ res = ast_waitstream(chan, AST_DIGIT_ANY);
+ ast_stopstream(chan);
+ }
+ else {
+ ast_log(LOG_WARNING, "Couldn't play invalid pin msg!\n");
+ break;
+ }
+ if (res < 0)
+ break;
+ pin[0] = res;
+ pin[1] = '\0';
+ res = -1;
+ if (allowretry)
+ confno[0] = '\0';
+ }
+ } else {
+ /* failed when getting the pin */
+ res = -1;
+ allowretry = 0;
+ /* see if we need to get rid of the conference */
+ break;
+ }
+
+ /* Don't retry pin with a static pin */
+ if (*the_pin && (always_prompt==0)) {
+ break;
+ }
+ }
+ } else {
+ /* No pin required */
+ allowretry = 0;
+
+ /* Run the conference */
+ res = conf_run(chan, cnf, confflags.flags, optargs);
+ }
+ dispose_conf(cnf);
+ cnf = NULL;
+ }
+ }
+ } while (allowretry);
+
+ if (cnf)
+ dispose_conf(cnf);
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static struct ast_conf_user *find_user(struct ast_conference *conf, char *callerident)
+{
+ struct ast_conf_user *user = NULL;
+ int cid;
+
+ sscanf(callerident, "%i", &cid);
+ if (conf && callerident) {
+ AST_LIST_TRAVERSE(&conf->userlist, user, list) {
+ if (cid == user->user_no)
+ return user;
+ }
+ }
+ return NULL;
+}
+
+/*! \brief The MeetMeadmin application */
+/* MeetMeAdmin(confno, command, caller) */
+static int admin_exec(struct ast_channel *chan, void *data) {
+ char *params;
+ struct ast_conference *cnf;
+ struct ast_conf_user *user = NULL;
+ struct ast_module_user *u;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(confno);
+ AST_APP_ARG(command);
+ AST_APP_ARG(user);
+ );
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "MeetMeAdmin requires an argument!\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ AST_LIST_LOCK(&confs);
+
+ params = ast_strdupa(data);
+ AST_STANDARD_APP_ARGS(args, params);
+
+ if (!args.command) {
+ ast_log(LOG_WARNING, "MeetmeAdmin requires a command!\n");
+ AST_LIST_UNLOCK(&confs);
+ ast_module_user_remove(u);
+ return -1;
+ }
+ AST_LIST_TRAVERSE(&confs, cnf, list) {
+ if (!strcmp(cnf->confno, args.confno))
+ break;
+ }
+
+ if (!cnf) {
+ ast_log(LOG_WARNING, "Conference number '%s' not found!\n", args.confno);
+ AST_LIST_UNLOCK(&confs);
+ ast_module_user_remove(u);
+ return 0;
+ }
+
+ ast_atomic_fetchadd_int(&cnf->refcount, 1);
+
+ if (args.user)
+ user = find_user(cnf, args.user);
+
+ switch (*args.command) {
+ case 76: /* L: Lock */
+ cnf->locked = 1;
+ break;
+ case 108: /* l: Unlock */
+ cnf->locked = 0;
+ break;
+ case 75: /* K: kick all users */
+ AST_LIST_TRAVERSE(&cnf->userlist, user, list)
+ user->adminflags |= ADMINFLAG_KICKME;
+ break;
+ case 101: /* e: Eject last user*/
+ user = AST_LIST_LAST(&cnf->userlist);
+ if (!(user->userflags & CONFFLAG_ADMIN))
+ user->adminflags |= ADMINFLAG_KICKME;
+ else
+ ast_log(LOG_NOTICE, "Not kicking last user, is an Admin!\n");
+ break;
+ case 77: /* M: Mute */
+ if (user) {
+ user->adminflags |= ADMINFLAG_MUTED;
+ } else
+ ast_log(LOG_NOTICE, "Specified User not found!\n");
+ break;
+ case 78: /* N: Mute all (non-admin) users */
+ AST_LIST_TRAVERSE(&cnf->userlist, user, list) {
+ if (!(user->userflags & CONFFLAG_ADMIN))
+ user->adminflags |= ADMINFLAG_MUTED;
+ }
+ break;
+ case 109: /* m: Unmute */
+ if (user) {
+ user->adminflags &= ~(ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED);
+ } else
+ ast_log(LOG_NOTICE, "Specified User not found!\n");
+ break;
+ case 110: /* n: Unmute all users */
+ AST_LIST_TRAVERSE(&cnf->userlist, user, list)
+ user->adminflags &= ~(ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED);
+ break;
+ case 107: /* k: Kick user */
+ if (user)
+ user->adminflags |= ADMINFLAG_KICKME;
+ else
+ ast_log(LOG_NOTICE, "Specified User not found!\n");
+ break;
+ case 118: /* v: Lower all users listen volume */
+ AST_LIST_TRAVERSE(&cnf->userlist, user, list)
+ tweak_listen_volume(user, VOL_DOWN);
+ break;
+ case 86: /* V: Raise all users listen volume */
+ AST_LIST_TRAVERSE(&cnf->userlist, user, list)
+ tweak_listen_volume(user, VOL_UP);
+ break;
+ case 115: /* s: Lower all users speaking volume */
+ AST_LIST_TRAVERSE(&cnf->userlist, user, list)
+ tweak_talk_volume(user, VOL_DOWN);
+ break;
+ case 83: /* S: Raise all users speaking volume */
+ AST_LIST_TRAVERSE(&cnf->userlist, user, list)
+ tweak_talk_volume(user, VOL_UP);
+ break;
+ case 82: /* R: Reset all volume levels */
+ AST_LIST_TRAVERSE(&cnf->userlist, user, list)
+ reset_volumes(user);
+ break;
+ case 114: /* r: Reset user's volume level */
+ if (user)
+ reset_volumes(user);
+ else
+ ast_log(LOG_NOTICE, "Specified User not found!\n");
+ break;
+ case 85: /* U: Raise user's listen volume */
+ if (user)
+ tweak_listen_volume(user, VOL_UP);
+ else
+ ast_log(LOG_NOTICE, "Specified User not found!\n");
+ break;
+ case 117: /* u: Lower user's listen volume */
+ if (user)
+ tweak_listen_volume(user, VOL_DOWN);
+ else
+ ast_log(LOG_NOTICE, "Specified User not found!\n");
+ break;
+ case 84: /* T: Raise user's talk volume */
+ if (user)
+ tweak_talk_volume(user, VOL_UP);
+ else
+ ast_log(LOG_NOTICE, "Specified User not found!\n");
+ break;
+ case 116: /* t: Lower user's talk volume */
+ if (user)
+ tweak_talk_volume(user, VOL_DOWN);
+ else
+ ast_log(LOG_NOTICE, "Specified User not found!\n");
+ break;
+ }
+
+ AST_LIST_UNLOCK(&confs);
+
+ dispose_conf(cnf);
+
+ ast_module_user_remove(u);
+
+ return 0;
+}
+
+static int meetmemute(struct mansession *s, const struct message *m, int mute)
+{
+ struct ast_conference *conf;
+ struct ast_conf_user *user;
+ const char *confid = astman_get_header(m, "Meetme");
+ char *userid = ast_strdupa(astman_get_header(m, "Usernum"));
+ int userno;
+
+ if (ast_strlen_zero(confid)) {
+ astman_send_error(s, m, "Meetme conference not specified");
+ return 0;
+ }
+
+ if (ast_strlen_zero(userid)) {
+ astman_send_error(s, m, "Meetme user number not specified");
+ return 0;
+ }
+
+ userno = strtoul(userid, &userid, 10);
+
+ if (*userid) {
+ astman_send_error(s, m, "Invalid user number");
+ return 0;
+ }
+
+ /* Look in the conference list */
+ AST_LIST_LOCK(&confs);
+ AST_LIST_TRAVERSE(&confs, conf, list) {
+ if (!strcmp(confid, conf->confno))
+ break;
+ }
+
+ if (!conf) {
+ AST_LIST_UNLOCK(&confs);
+ astman_send_error(s, m, "Meetme conference does not exist");
+ return 0;
+ }
+
+ AST_LIST_TRAVERSE(&conf->userlist, user, list)
+ if (user->user_no == userno)
+ break;
+
+ if (!user) {
+ AST_LIST_UNLOCK(&confs);
+ astman_send_error(s, m, "User number not found");
+ return 0;
+ }
+
+ if (mute)
+ user->adminflags |= ADMINFLAG_MUTED; /* request user muting */
+ else
+ user->adminflags &= ~(ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED); /* request user unmuting */
+
+ AST_LIST_UNLOCK(&confs);
+
+ ast_log(LOG_NOTICE, "Requested to %smute conf %s user %d userchan %s uniqueid %s\n", mute ? "" : "un", conf->confno, user->user_no, user->chan->name, user->chan->uniqueid);
+
+ astman_send_ack(s, m, mute ? "User muted" : "User unmuted");
+ return 0;
+}
+
+static int action_meetmemute(struct mansession *s, const struct message *m)
+{
+ return meetmemute(s, m, 1);
+}
+
+static int action_meetmeunmute(struct mansession *s, const struct message *m)
+{
+ return meetmemute(s, m, 0);
+}
+
+static void *recordthread(void *args)
+{
+ struct ast_conference *cnf = args;
+ struct ast_frame *f=NULL;
+ int flags;
+ struct ast_filestream *s=NULL;
+ int res=0;
+ int x;
+ const char *oldrecordingfilename = NULL;
+
+ if (!cnf || !cnf->lchan) {
+ pthread_exit(0);
+ }
+
+ ast_stopstream(cnf->lchan);
+ flags = O_CREAT|O_TRUNC|O_WRONLY;
+
+
+ cnf->recording = MEETME_RECORD_ACTIVE;
+ while (ast_waitfor(cnf->lchan, -1) > -1) {
+ if (cnf->recording == MEETME_RECORD_TERMINATE) {
+ AST_LIST_LOCK(&confs);
+ AST_LIST_UNLOCK(&confs);
+ break;
+ }
+ if (!s && cnf->recordingfilename && (cnf->recordingfilename != oldrecordingfilename)) {
+ s = ast_writefile(cnf->recordingfilename, cnf->recordingformat, NULL, flags, 0, 0644);
+ oldrecordingfilename = cnf->recordingfilename;
+ }
+
+ f = ast_read(cnf->lchan);
+ if (!f) {
+ res = -1;
+ break;
+ }
+ if (f->frametype == AST_FRAME_VOICE) {
+ ast_mutex_lock(&cnf->listenlock);
+ for (x=0;x<AST_FRAME_BITS;x++) {
+ /* Free any translations that have occured */
+ if (cnf->transframe[x]) {
+ ast_frfree(cnf->transframe[x]);
+ cnf->transframe[x] = NULL;
+ }
+ }
+ if (cnf->origframe)
+ ast_frfree(cnf->origframe);
+ cnf->origframe = ast_frdup(f);
+ ast_mutex_unlock(&cnf->listenlock);
+ if (s)
+ res = ast_writestream(s, f);
+ if (res) {
+ ast_frfree(f);
+ break;
+ }
+ }
+ ast_frfree(f);
+ }
+ cnf->recording = MEETME_RECORD_OFF;
+ if (s)
+ ast_closestream(s);
+
+ pthread_exit(0);
+}
+
+/*! \brief Callback for devicestate providers */
+static int meetmestate(const char *data)
+{
+ struct ast_conference *conf;
+
+ /* Find conference */
+ AST_LIST_LOCK(&confs);
+ AST_LIST_TRAVERSE(&confs, conf, list) {
+ if (!strcmp(data, conf->confno))
+ break;
+ }
+ AST_LIST_UNLOCK(&confs);
+ if (!conf)
+ return AST_DEVICE_INVALID;
+
+
+ /* SKREP to fill */
+ if (!conf->users)
+ return AST_DEVICE_NOT_INUSE;
+
+ return AST_DEVICE_INUSE;
+}
+
+static void load_config_meetme(void)
+{
+ struct ast_config *cfg;
+ const char *val;
+
+ audio_buffers = DEFAULT_AUDIO_BUFFERS;
+
+ if (!(cfg = ast_config_load(CONFIG_FILE_NAME)))
+ return;
+
+ if ((val = ast_variable_retrieve(cfg, "general", "audiobuffers"))) {
+ if ((sscanf(val, "%d", &audio_buffers) != 1)) {
+ ast_log(LOG_WARNING, "audiobuffers setting must be a number, not '%s'\n", val);
+ audio_buffers = DEFAULT_AUDIO_BUFFERS;
+ } else if ((audio_buffers < DAHDI_DEFAULT_NUM_BUFS) || (audio_buffers > DAHDI_MAX_NUM_BUFS)) {
+ ast_log(LOG_WARNING, "audiobuffers setting must be between %d and %d\n",
+ DAHDI_DEFAULT_NUM_BUFS, DAHDI_MAX_NUM_BUFS);
+ audio_buffers = DEFAULT_AUDIO_BUFFERS;
+ }
+ if (audio_buffers != DEFAULT_AUDIO_BUFFERS)
+ ast_log(LOG_NOTICE, "Audio buffers per channel set to %d\n", audio_buffers);
+ }
+
+ ast_config_destroy(cfg);
+}
+
+/*! \brief Find an SLA trunk by name
+ * \note This must be called with the sla_trunks container locked
+ */
+static struct sla_trunk *sla_find_trunk(const char *name)
+{
+ struct sla_trunk *trunk = NULL;
+
+ AST_RWLIST_TRAVERSE(&sla_trunks, trunk, entry) {
+ if (!strcasecmp(trunk->name, name))
+ break;
+ }
+
+ return trunk;
+}
+
+/*! \brief Find an SLA station by name
+ * \note This must be called with the sla_stations container locked
+ */
+static struct sla_station *sla_find_station(const char *name)
+{
+ struct sla_station *station = NULL;
+
+ AST_RWLIST_TRAVERSE(&sla_stations, station, entry) {
+ if (!strcasecmp(station->name, name))
+ break;
+ }
+
+ return station;
+}
+
+static int sla_check_station_hold_access(const struct sla_trunk *trunk,
+ const struct sla_station *station)
+{
+ struct sla_station_ref *station_ref;
+ struct sla_trunk_ref *trunk_ref;
+
+ /* For each station that has this call on hold, check for private hold. */
+ AST_LIST_TRAVERSE(&trunk->stations, station_ref, entry) {
+ AST_LIST_TRAVERSE(&station_ref->station->trunks, trunk_ref, entry) {
+ if (trunk_ref->trunk != trunk || station_ref->station == station)
+ continue;
+ if (trunk_ref->state == SLA_TRUNK_STATE_ONHOLD_BYME &&
+ station_ref->station->hold_access == SLA_HOLD_PRIVATE)
+ return 1;
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+/*! \brief Find a trunk reference on a station by name
+ * \param station the station
+ * \param name the trunk's name
+ * \return a pointer to the station's trunk reference. If the trunk
+ * is not found, it is not idle and barge is disabled, or if
+ * it is on hold and private hold is set, then NULL will be returned.
+ */
+static struct sla_trunk_ref *sla_find_trunk_ref_byname(const struct sla_station *station,
+ const char *name)
+{
+ struct sla_trunk_ref *trunk_ref = NULL;
+
+ AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) {
+ if (strcasecmp(trunk_ref->trunk->name, name))
+ continue;
+
+ if ( (trunk_ref->trunk->barge_disabled
+ && trunk_ref->state == SLA_TRUNK_STATE_UP) ||
+ (trunk_ref->trunk->hold_stations
+ && trunk_ref->trunk->hold_access == SLA_HOLD_PRIVATE
+ && trunk_ref->state != SLA_TRUNK_STATE_ONHOLD_BYME) ||
+ sla_check_station_hold_access(trunk_ref->trunk, station) )
+ {
+ trunk_ref = NULL;
+ }
+
+ break;
+ }
+
+ return trunk_ref;
+}
+
+static struct sla_station_ref *sla_create_station_ref(struct sla_station *station)
+{
+ struct sla_station_ref *station_ref;
+
+ if (!(station_ref = ast_calloc(1, sizeof(*station_ref))))
+ return NULL;
+
+ station_ref->station = station;
+
+ return station_ref;
+}
+
+static struct sla_ringing_station *sla_create_ringing_station(struct sla_station *station)
+{
+ struct sla_ringing_station *ringing_station;
+
+ if (!(ringing_station = ast_calloc(1, sizeof(*ringing_station))))
+ return NULL;
+
+ ringing_station->station = station;
+ ringing_station->ring_begin = ast_tvnow();
+
+ return ringing_station;
+}
+
+static void sla_change_trunk_state(const struct sla_trunk *trunk, enum sla_trunk_state state,
+ enum sla_which_trunk_refs inactive_only, const struct sla_trunk_ref *exclude)
+{
+ struct sla_station *station;
+ struct sla_trunk_ref *trunk_ref;
+
+ AST_LIST_TRAVERSE(&sla_stations, station, entry) {
+ AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) {
+ if (trunk_ref->trunk != trunk || (inactive_only ? trunk_ref->chan : 0)
+ || trunk_ref == exclude)
+ continue;
+ trunk_ref->state = state;
+ ast_device_state_changed("SLA:%s_%s", station->name, trunk->name);
+ break;
+ }
+ }
+}
+
+struct run_station_args {
+ struct sla_station *station;
+ struct sla_trunk_ref *trunk_ref;
+ ast_mutex_t *cond_lock;
+ ast_cond_t *cond;
+};
+
+static void answer_trunk_chan(struct ast_channel *chan)
+{
+ ast_answer(chan);
+ ast_indicate(chan, -1);
+}
+
+static void *run_station(void *data)
+{
+ struct sla_station *station;
+ struct sla_trunk_ref *trunk_ref;
+ char conf_name[MAX_CONFNUM];
+ struct ast_flags conf_flags = { 0 };
+ struct ast_conference *conf;
+
+ {
+ struct run_station_args *args = data;
+ station = args->station;
+ trunk_ref = args->trunk_ref;
+ ast_mutex_lock(args->cond_lock);
+ ast_cond_signal(args->cond);
+ ast_mutex_unlock(args->cond_lock);
+ /* args is no longer valid here. */
+ }
+
+ ast_atomic_fetchadd_int((int *) &trunk_ref->trunk->active_stations, 1);
+ snprintf(conf_name, sizeof(conf_name), "SLA_%s", trunk_ref->trunk->name);
+ ast_set_flag(&conf_flags,
+ CONFFLAG_QUIET | CONFFLAG_MARKEDEXIT | CONFFLAG_PASS_DTMF | CONFFLAG_SLA_STATION);
+ answer_trunk_chan(trunk_ref->chan);
+ conf = build_conf(conf_name, "", "", 0, 0, 1);
+ if (conf) {
+ conf_run(trunk_ref->chan, conf, conf_flags.flags, NULL);
+ dispose_conf(conf);
+ conf = NULL;
+ }
+ trunk_ref->chan = NULL;
+ if (ast_atomic_dec_and_test((int *) &trunk_ref->trunk->active_stations) &&
+ trunk_ref->state != SLA_TRUNK_STATE_ONHOLD_BYME) {
+ strncat(conf_name, "|K", sizeof(conf_name) - strlen(conf_name) - 1);
+ admin_exec(NULL, conf_name);
+ trunk_ref->trunk->hold_stations = 0;
+ sla_change_trunk_state(trunk_ref->trunk, SLA_TRUNK_STATE_IDLE, ALL_TRUNK_REFS, NULL);
+ }
+
+ ast_dial_join(station->dial);
+ ast_dial_destroy(station->dial);
+ station->dial = NULL;
+
+ return NULL;
+}
+
+static void sla_stop_ringing_trunk(struct sla_ringing_trunk *ringing_trunk)
+{
+ char buf[80];
+ struct sla_station_ref *station_ref;
+
+ snprintf(buf, sizeof(buf), "SLA_%s|K", ringing_trunk->trunk->name);
+ admin_exec(NULL, buf);
+ sla_change_trunk_state(ringing_trunk->trunk, SLA_TRUNK_STATE_IDLE, ALL_TRUNK_REFS, NULL);
+
+ while ((station_ref = AST_LIST_REMOVE_HEAD(&ringing_trunk->timed_out_stations, entry)))
+ free(station_ref);
+
+ free(ringing_trunk);
+}
+
+static void sla_stop_ringing_station(struct sla_ringing_station *ringing_station,
+ enum sla_station_hangup hangup)
+{
+ struct sla_ringing_trunk *ringing_trunk;
+ struct sla_trunk_ref *trunk_ref;
+ struct sla_station_ref *station_ref;
+
+ ast_dial_join(ringing_station->station->dial);
+ ast_dial_destroy(ringing_station->station->dial);
+ ringing_station->station->dial = NULL;
+
+ if (hangup == SLA_STATION_HANGUP_NORMAL)
+ goto done;
+
+ /* If the station is being hung up because of a timeout, then add it to the
+ * list of timed out stations on each of the ringing trunks. This is so
+ * that when doing further processing to figure out which stations should be
+ * ringing, which trunk to answer, determining timeouts, etc., we know which
+ * ringing trunks we should ignore. */
+ AST_LIST_TRAVERSE(&sla.ringing_trunks, ringing_trunk, entry) {
+ AST_LIST_TRAVERSE(&ringing_station->station->trunks, trunk_ref, entry) {
+ if (ringing_trunk->trunk == trunk_ref->trunk)
+ break;
+ }
+ if (!trunk_ref)
+ continue;
+ if (!(station_ref = sla_create_station_ref(ringing_station->station)))
+ continue;
+ AST_LIST_INSERT_TAIL(&ringing_trunk->timed_out_stations, station_ref, entry);
+ }
+
+done:
+ free(ringing_station);
+}
+
+static void sla_dial_state_callback(struct ast_dial *dial)
+{
+ sla_queue_event(SLA_EVENT_DIAL_STATE);
+}
+
+/*! \brief Check to see if dialing this station already timed out for this ringing trunk
+ * \note Assumes sla.lock is locked
+ */
+static int sla_check_timed_out_station(const struct sla_ringing_trunk *ringing_trunk,
+ const struct sla_station *station)
+{
+ struct sla_station_ref *timed_out_station;
+
+ AST_LIST_TRAVERSE(&ringing_trunk->timed_out_stations, timed_out_station, entry) {
+ if (station == timed_out_station->station)
+ return 1;
+ }
+
+ return 0;
+}
+
+/*! \brief Choose the highest priority ringing trunk for a station
+ * \param station the station
+ * \param remove remove the ringing trunk once selected
+ * \param trunk_ref a place to store the pointer to this stations reference to
+ * the selected trunk
+ * \return a pointer to the selected ringing trunk, or NULL if none found
+ * \note Assumes that sla.lock is locked
+ */
+static struct sla_ringing_trunk *sla_choose_ringing_trunk(struct sla_station *station,
+ struct sla_trunk_ref **trunk_ref, int remove)
+{
+ struct sla_trunk_ref *s_trunk_ref;
+ struct sla_ringing_trunk *ringing_trunk = NULL;
+
+ AST_LIST_TRAVERSE(&station->trunks, s_trunk_ref, entry) {
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&sla.ringing_trunks, ringing_trunk, entry) {
+ /* Make sure this is the trunk we're looking for */
+ if (s_trunk_ref->trunk != ringing_trunk->trunk)
+ continue;
+
+ /* This trunk on the station is ringing. But, make sure this station
+ * didn't already time out while this trunk was ringing. */
+ if (sla_check_timed_out_station(ringing_trunk, station))
+ continue;
+
+ if (remove)
+ AST_LIST_REMOVE_CURRENT(&sla.ringing_trunks, entry);
+
+ if (trunk_ref)
+ *trunk_ref = s_trunk_ref;
+
+ break;
+ }
+ AST_LIST_TRAVERSE_SAFE_END
+
+ if (ringing_trunk)
+ break;
+ }
+
+ return ringing_trunk;
+}
+
+static void sla_handle_dial_state_event(void)
+{
+ struct sla_ringing_station *ringing_station;
+
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&sla.ringing_stations, ringing_station, entry) {
+ struct sla_trunk_ref *s_trunk_ref = NULL;
+ struct sla_ringing_trunk *ringing_trunk = NULL;
+ struct run_station_args args;
+ enum ast_dial_result dial_res;
+ pthread_attr_t attr;
+ pthread_t dont_care;
+ ast_mutex_t cond_lock;
+ ast_cond_t cond;
+
+ switch ((dial_res = ast_dial_state(ringing_station->station->dial))) {
+ case AST_DIAL_RESULT_HANGUP:
+ case AST_DIAL_RESULT_INVALID:
+ case AST_DIAL_RESULT_FAILED:
+ case AST_DIAL_RESULT_TIMEOUT:
+ case AST_DIAL_RESULT_UNANSWERED:
+ AST_LIST_REMOVE_CURRENT(&sla.ringing_stations, entry);
+ sla_stop_ringing_station(ringing_station, SLA_STATION_HANGUP_NORMAL);
+ break;
+ case AST_DIAL_RESULT_ANSWERED:
+ AST_LIST_REMOVE_CURRENT(&sla.ringing_stations, entry);
+ /* Find the appropriate trunk to answer. */
+ ast_mutex_lock(&sla.lock);
+ ringing_trunk = sla_choose_ringing_trunk(ringing_station->station, &s_trunk_ref, 1);
+ ast_mutex_unlock(&sla.lock);
+ if (!ringing_trunk) {
+ ast_log(LOG_DEBUG, "Found no ringing trunk for station '%s' to answer!\n",
+ ringing_station->station->name);
+ break;
+ }
+ /* Track the channel that answered this trunk */
+ s_trunk_ref->chan = ast_dial_answered(ringing_station->station->dial);
+ /* Actually answer the trunk */
+ answer_trunk_chan(ringing_trunk->trunk->chan);
+ sla_change_trunk_state(ringing_trunk->trunk, SLA_TRUNK_STATE_UP, ALL_TRUNK_REFS, NULL);
+ /* Now, start a thread that will connect this station to the trunk. The rest of
+ * the code here sets up the thread and ensures that it is able to save the arguments
+ * before they are no longer valid since they are allocated on the stack. */
+ args.trunk_ref = s_trunk_ref;
+ args.station = ringing_station->station;
+ args.cond = &cond;
+ args.cond_lock = &cond_lock;
+ free(ringing_trunk);
+ free(ringing_station);
+ ast_mutex_init(&cond_lock);
+ ast_cond_init(&cond, NULL);
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ ast_mutex_lock(&cond_lock);
+ ast_pthread_create_background(&dont_care, &attr, run_station, &args);
+ ast_cond_wait(&cond, &cond_lock);
+ ast_mutex_unlock(&cond_lock);
+ ast_mutex_destroy(&cond_lock);
+ ast_cond_destroy(&cond);
+ pthread_attr_destroy(&attr);
+ break;
+ case AST_DIAL_RESULT_TRYING:
+ case AST_DIAL_RESULT_RINGING:
+ case AST_DIAL_RESULT_PROGRESS:
+ case AST_DIAL_RESULT_PROCEEDING:
+ break;
+ }
+ if (dial_res == AST_DIAL_RESULT_ANSWERED) {
+ /* Queue up reprocessing ringing trunks, and then ringing stations again */
+ sla_queue_event(SLA_EVENT_RINGING_TRUNK);
+ sla_queue_event(SLA_EVENT_DIAL_STATE);
+ break;
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END
+}
+
+/*! \brief Check to see if this station is already ringing
+ * \note Assumes sla.lock is locked
+ */
+static int sla_check_ringing_station(const struct sla_station *station)
+{
+ struct sla_ringing_station *ringing_station;
+
+ AST_LIST_TRAVERSE(&sla.ringing_stations, ringing_station, entry) {
+ if (station == ringing_station->station)
+ return 1;
+ }
+
+ return 0;
+}
+
+/*! \brief Check to see if this station has failed to be dialed in the past minute
+ * \note assumes sla.lock is locked
+ */
+static int sla_check_failed_station(const struct sla_station *station)
+{
+ struct sla_failed_station *failed_station;
+ int res = 0;
+
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&sla.failed_stations, failed_station, entry) {
+ if (station != failed_station->station)
+ continue;
+ if (ast_tvdiff_ms(ast_tvnow(), failed_station->last_try) > 1000) {
+ AST_LIST_REMOVE_CURRENT(&sla.failed_stations, entry);
+ free(failed_station);
+ break;
+ }
+ res = 1;
+ }
+ AST_LIST_TRAVERSE_SAFE_END
+
+ return res;
+}
+
+/*! \brief Ring a station
+ * \note Assumes sla.lock is locked
+ */
+static int sla_ring_station(struct sla_ringing_trunk *ringing_trunk, struct sla_station *station)
+{
+ char *tech, *tech_data;
+ struct ast_dial *dial;
+ struct sla_ringing_station *ringing_station;
+ const char *cid_name = NULL, *cid_num = NULL;
+ enum ast_dial_result res;
+
+ if (!(dial = ast_dial_create()))
+ return -1;
+
+ ast_dial_set_state_callback(dial, sla_dial_state_callback);
+ tech_data = ast_strdupa(station->device);
+ tech = strsep(&tech_data, "/");
+
+ if (ast_dial_append(dial, tech, tech_data) == -1) {
+ ast_dial_destroy(dial);
+ return -1;
+ }
+
+ if (!sla.attempt_callerid && !ast_strlen_zero(ringing_trunk->trunk->chan->cid.cid_name)) {
+ cid_name = ast_strdupa(ringing_trunk->trunk->chan->cid.cid_name);
+ free(ringing_trunk->trunk->chan->cid.cid_name);
+ ringing_trunk->trunk->chan->cid.cid_name = NULL;
+ }
+ if (!sla.attempt_callerid && !ast_strlen_zero(ringing_trunk->trunk->chan->cid.cid_num)) {
+ cid_num = ast_strdupa(ringing_trunk->trunk->chan->cid.cid_num);
+ free(ringing_trunk->trunk->chan->cid.cid_num);
+ ringing_trunk->trunk->chan->cid.cid_num = NULL;
+ }
+
+ res = ast_dial_run(dial, ringing_trunk->trunk->chan, 1);
+
+ if (cid_name)
+ ringing_trunk->trunk->chan->cid.cid_name = ast_strdup(cid_name);
+ if (cid_num)
+ ringing_trunk->trunk->chan->cid.cid_num = ast_strdup(cid_num);
+
+ if (res != AST_DIAL_RESULT_TRYING) {
+ struct sla_failed_station *failed_station;
+ ast_dial_destroy(dial);
+ if (!(failed_station = ast_calloc(1, sizeof(*failed_station))))
+ return -1;
+ failed_station->station = station;
+ failed_station->last_try = ast_tvnow();
+ AST_LIST_INSERT_HEAD(&sla.failed_stations, failed_station, entry);
+ return -1;
+ }
+ if (!(ringing_station = sla_create_ringing_station(station))) {
+ ast_dial_join(dial);
+ ast_dial_destroy(dial);
+ return -1;
+ }
+
+ station->dial = dial;
+
+ AST_LIST_INSERT_HEAD(&sla.ringing_stations, ringing_station, entry);
+
+ return 0;
+}
+
+/*! \brief Check to see if a station is in use
+ */
+static int sla_check_inuse_station(const struct sla_station *station)
+{
+ struct sla_trunk_ref *trunk_ref;
+
+ AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) {
+ if (trunk_ref->chan)
+ return 1;
+ }
+
+ return 0;
+}
+
+static struct sla_trunk_ref *sla_find_trunk_ref(const struct sla_station *station,
+ const struct sla_trunk *trunk)
+{
+ struct sla_trunk_ref *trunk_ref = NULL;
+
+ AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) {
+ if (trunk_ref->trunk == trunk)
+ break;
+ }
+
+ return trunk_ref;
+}
+
+/*! \brief Calculate the ring delay for a given ringing trunk on a station
+ * \param station the station
+ * \param trunk the trunk. If NULL, the highest priority ringing trunk will be used
+ * \return the number of ms left before the delay is complete, or INT_MAX if there is no delay
+ */
+static int sla_check_station_delay(struct sla_station *station,
+ struct sla_ringing_trunk *ringing_trunk)
+{
+ struct sla_trunk_ref *trunk_ref;
+ unsigned int delay = UINT_MAX;
+ int time_left, time_elapsed;
+
+ if (!ringing_trunk)
+ ringing_trunk = sla_choose_ringing_trunk(station, &trunk_ref, 0);
+ else
+ trunk_ref = sla_find_trunk_ref(station, ringing_trunk->trunk);
+
+ if (!ringing_trunk || !trunk_ref)
+ return delay;
+
+ /* If this station has a ring delay specific to the highest priority
+ * ringing trunk, use that. Otherwise, use the ring delay specified
+ * globally for the station. */
+ delay = trunk_ref->ring_delay;
+ if (!delay)
+ delay = station->ring_delay;
+ if (!delay)
+ return INT_MAX;
+
+ time_elapsed = ast_tvdiff_ms(ast_tvnow(), ringing_trunk->ring_begin);
+ time_left = (delay * 1000) - time_elapsed;
+
+ return time_left;
+}
+
+/*! \brief Ring stations based on current set of ringing trunks
+ * \note Assumes that sla.lock is locked
+ */
+static void sla_ring_stations(void)
+{
+ struct sla_station_ref *station_ref;
+ struct sla_ringing_trunk *ringing_trunk;
+
+ /* Make sure that every station that uses at least one of the ringing
+ * trunks, is ringing. */
+ AST_LIST_TRAVERSE(&sla.ringing_trunks, ringing_trunk, entry) {
+ AST_LIST_TRAVERSE(&ringing_trunk->trunk->stations, station_ref, entry) {
+ int time_left;
+
+ /* Is this station already ringing? */
+ if (sla_check_ringing_station(station_ref->station))
+ continue;
+
+ /* Is this station already in a call? */
+ if (sla_check_inuse_station(station_ref->station))
+ continue;
+
+ /* Did we fail to dial this station earlier? If so, has it been
+ * a minute since we tried? */
+ if (sla_check_failed_station(station_ref->station))
+ continue;
+
+ /* If this station already timed out while this trunk was ringing,
+ * do not dial it again for this ringing trunk. */
+ if (sla_check_timed_out_station(ringing_trunk, station_ref->station))
+ continue;
+
+ /* Check for a ring delay in progress */
+ time_left = sla_check_station_delay(station_ref->station, ringing_trunk);
+ if (time_left != INT_MAX && time_left > 0)
+ continue;
+
+ /* It is time to make this station begin to ring. Do it! */
+ sla_ring_station(ringing_trunk, station_ref->station);
+ }
+ }
+ /* Now, all of the stations that should be ringing, are ringing. */
+}
+
+static void sla_hangup_stations(void)
+{
+ struct sla_trunk_ref *trunk_ref;
+ struct sla_ringing_station *ringing_station;
+
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&sla.ringing_stations, ringing_station, entry) {
+ AST_LIST_TRAVERSE(&ringing_station->station->trunks, trunk_ref, entry) {
+ struct sla_ringing_trunk *ringing_trunk;
+ ast_mutex_lock(&sla.lock);
+ AST_LIST_TRAVERSE(&sla.ringing_trunks, ringing_trunk, entry) {
+ if (trunk_ref->trunk == ringing_trunk->trunk)
+ break;
+ }
+ ast_mutex_unlock(&sla.lock);
+ if (ringing_trunk)
+ break;
+ }
+ if (!trunk_ref) {
+ AST_LIST_REMOVE_CURRENT(&sla.ringing_stations, entry);
+ ast_dial_join(ringing_station->station->dial);
+ ast_dial_destroy(ringing_station->station->dial);
+ ringing_station->station->dial = NULL;
+ free(ringing_station);
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END
+}
+
+static void sla_handle_ringing_trunk_event(void)
+{
+ ast_mutex_lock(&sla.lock);
+ sla_ring_stations();
+ ast_mutex_unlock(&sla.lock);
+
+ /* Find stations that shouldn't be ringing anymore. */
+ sla_hangup_stations();
+}
+
+static void sla_handle_hold_event(struct sla_event *event)
+{
+ ast_atomic_fetchadd_int((int *) &event->trunk_ref->trunk->hold_stations, 1);
+ event->trunk_ref->state = SLA_TRUNK_STATE_ONHOLD_BYME;
+ ast_device_state_changed("SLA:%s_%s",
+ event->station->name, event->trunk_ref->trunk->name);
+ sla_change_trunk_state(event->trunk_ref->trunk, SLA_TRUNK_STATE_ONHOLD,
+ INACTIVE_TRUNK_REFS, event->trunk_ref);
+
+ if (event->trunk_ref->trunk->active_stations == 1) {
+ /* The station putting it on hold is the only one on the call, so start
+ * Music on hold to the trunk. */
+ event->trunk_ref->trunk->on_hold = 1;
+ ast_indicate(event->trunk_ref->trunk->chan, AST_CONTROL_HOLD);
+ }
+
+ ast_softhangup(event->trunk_ref->chan, AST_CAUSE_NORMAL);
+ event->trunk_ref->chan = NULL;
+}
+
+/*! \brief Process trunk ring timeouts
+ * \note Called with sla.lock locked
+ * \return non-zero if a change to the ringing trunks was made
+ */
+static int sla_calc_trunk_timeouts(unsigned int *timeout)
+{
+ struct sla_ringing_trunk *ringing_trunk;
+ int res = 0;
+
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&sla.ringing_trunks, ringing_trunk, entry) {
+ int time_left, time_elapsed;
+ if (!ringing_trunk->trunk->ring_timeout)
+ continue;
+ time_elapsed = ast_tvdiff_ms(ast_tvnow(), ringing_trunk->ring_begin);
+ time_left = (ringing_trunk->trunk->ring_timeout * 1000) - time_elapsed;
+ if (time_left <= 0) {
+ pbx_builtin_setvar_helper(ringing_trunk->trunk->chan, "SLATRUNK_STATUS", "RINGTIMEOUT");
+ AST_LIST_REMOVE_CURRENT(&sla.ringing_trunks, entry);
+ sla_stop_ringing_trunk(ringing_trunk);
+ res = 1;
+ continue;
+ }
+ if (time_left < *timeout)
+ *timeout = time_left;
+ }
+ AST_LIST_TRAVERSE_SAFE_END
+
+ return res;
+}
+
+/*! \brief Process station ring timeouts
+ * \note Called with sla.lock locked
+ * \return non-zero if a change to the ringing stations was made
+ */
+static int sla_calc_station_timeouts(unsigned int *timeout)
+{
+ struct sla_ringing_trunk *ringing_trunk;
+ struct sla_ringing_station *ringing_station;
+ int res = 0;
+
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&sla.ringing_stations, ringing_station, entry) {
+ unsigned int ring_timeout = 0;
+ int time_elapsed, time_left = INT_MAX, final_trunk_time_left = INT_MIN;
+ struct sla_trunk_ref *trunk_ref;
+
+ /* If there are any ring timeouts specified for a specific trunk
+ * on the station, then use the highest per-trunk ring timeout.
+ * Otherwise, use the ring timeout set for the entire station. */
+ AST_LIST_TRAVERSE(&ringing_station->station->trunks, trunk_ref, entry) {
+ struct sla_station_ref *station_ref;
+ int trunk_time_elapsed, trunk_time_left;
+
+ AST_LIST_TRAVERSE(&sla.ringing_trunks, ringing_trunk, entry) {
+ if (ringing_trunk->trunk == trunk_ref->trunk)
+ break;
+ }
+ if (!ringing_trunk)
+ continue;
+
+ /* If there is a trunk that is ringing without a timeout, then the
+ * only timeout that could matter is a global station ring timeout. */
+ if (!trunk_ref->ring_timeout)
+ break;
+
+ /* This trunk on this station is ringing and has a timeout.
+ * However, make sure this trunk isn't still ringing from a
+ * previous timeout. If so, don't consider it. */
+ AST_LIST_TRAVERSE(&ringing_trunk->timed_out_stations, station_ref, entry) {
+ if (station_ref->station == ringing_station->station)
+ break;
+ }
+ if (station_ref)
+ continue;
+
+ trunk_time_elapsed = ast_tvdiff_ms(ast_tvnow(), ringing_trunk->ring_begin);
+ trunk_time_left = (trunk_ref->ring_timeout * 1000) - trunk_time_elapsed;
+ if (trunk_time_left > final_trunk_time_left)
+ final_trunk_time_left = trunk_time_left;
+ }
+
+ /* No timeout was found for ringing trunks, and no timeout for the entire station */
+ if (final_trunk_time_left == INT_MIN && !ringing_station->station->ring_timeout)
+ continue;
+
+ /* Compute how much time is left for a global station timeout */
+ if (ringing_station->station->ring_timeout) {
+ ring_timeout = ringing_station->station->ring_timeout;
+ time_elapsed = ast_tvdiff_ms(ast_tvnow(), ringing_station->ring_begin);
+ time_left = (ring_timeout * 1000) - time_elapsed;
+ }
+
+ /* If the time left based on the per-trunk timeouts is smaller than the
+ * global station ring timeout, use that. */
+ if (final_trunk_time_left > INT_MIN && final_trunk_time_left < time_left)
+ time_left = final_trunk_time_left;
+
+ /* If there is no time left, the station needs to stop ringing */
+ if (time_left <= 0) {
+ AST_LIST_REMOVE_CURRENT(&sla.ringing_stations, entry);
+ sla_stop_ringing_station(ringing_station, SLA_STATION_HANGUP_TIMEOUT);
+ res = 1;
+ continue;
+ }
+
+ /* There is still some time left for this station to ring, so save that
+ * timeout if it is the first event scheduled to occur */
+ if (time_left < *timeout)
+ *timeout = time_left;
+ }
+ AST_LIST_TRAVERSE_SAFE_END
+
+ return res;
+}
+
+/*! \brief Calculate the ring delay for a station
+ * \note Assumes sla.lock is locked
+ */
+static int sla_calc_station_delays(unsigned int *timeout)
+{
+ struct sla_station *station;
+ int res = 0;
+
+ AST_LIST_TRAVERSE(&sla_stations, station, entry) {
+ struct sla_ringing_trunk *ringing_trunk;
+ int time_left;
+
+ /* Ignore stations already ringing */
+ if (sla_check_ringing_station(station))
+ continue;
+
+ /* Ignore stations already on a call */
+ if (sla_check_inuse_station(station))
+ continue;
+
+ /* Ignore stations that don't have one of their trunks ringing */
+ if (!(ringing_trunk = sla_choose_ringing_trunk(station, NULL, 0)))
+ continue;
+
+ if ((time_left = sla_check_station_delay(station, ringing_trunk)) == INT_MAX)
+ continue;
+
+ /* If there is no time left, then the station needs to start ringing.
+ * Return non-zero so that an event will be queued up an event to
+ * make that happen. */
+ if (time_left <= 0) {
+ res = 1;
+ continue;
+ }
+
+ if (time_left < *timeout)
+ *timeout = time_left;
+ }
+
+ return res;
+}
+
+/*! \brief Calculate the time until the next known event
+ * \note Called with sla.lock locked */
+static int sla_process_timers(struct timespec *ts)
+{
+ unsigned int timeout = UINT_MAX;
+ struct timeval tv;
+ unsigned int change_made = 0;
+
+ /* Check for ring timeouts on ringing trunks */
+ if (sla_calc_trunk_timeouts(&timeout))
+ change_made = 1;
+
+ /* Check for ring timeouts on ringing stations */
+ if (sla_calc_station_timeouts(&timeout))
+ change_made = 1;
+
+ /* Check for station ring delays */
+ if (sla_calc_station_delays(&timeout))
+ change_made = 1;
+
+ /* queue reprocessing of ringing trunks */
+ if (change_made)
+ sla_queue_event_nolock(SLA_EVENT_RINGING_TRUNK);
+
+ /* No timeout */
+ if (timeout == UINT_MAX)
+ return 0;
+
+ if (ts) {
+ tv = ast_tvadd(ast_tvnow(), ast_samp2tv(timeout, 1000));
+ ts->tv_sec = tv.tv_sec;
+ ts->tv_nsec = tv.tv_usec * 1000;
+ }
+
+ return 1;
+}
+
+static void *sla_thread(void *data)
+{
+ struct sla_failed_station *failed_station;
+ struct sla_ringing_station *ringing_station;
+
+ ast_mutex_lock(&sla.lock);
+
+ while (!sla.stop) {
+ struct sla_event *event;
+ struct timespec ts = { 0, };
+ unsigned int have_timeout = 0;
+
+ if (AST_LIST_EMPTY(&sla.event_q)) {
+ if ((have_timeout = sla_process_timers(&ts)))
+ ast_cond_timedwait(&sla.cond, &sla.lock, &ts);
+ else
+ ast_cond_wait(&sla.cond, &sla.lock);
+ if (sla.stop)
+ break;
+ }
+
+ if (have_timeout)
+ sla_process_timers(NULL);
+
+ while ((event = AST_LIST_REMOVE_HEAD(&sla.event_q, entry))) {
+ ast_mutex_unlock(&sla.lock);
+ switch (event->type) {
+ case SLA_EVENT_HOLD:
+ sla_handle_hold_event(event);
+ break;
+ case SLA_EVENT_DIAL_STATE:
+ sla_handle_dial_state_event();
+ break;
+ case SLA_EVENT_RINGING_TRUNK:
+ sla_handle_ringing_trunk_event();
+ break;
+ }
+ free(event);
+ ast_mutex_lock(&sla.lock);
+ }
+ }
+
+ ast_mutex_unlock(&sla.lock);
+
+ while ((ringing_station = AST_LIST_REMOVE_HEAD(&sla.ringing_stations, entry)))
+ free(ringing_station);
+
+ while ((failed_station = AST_LIST_REMOVE_HEAD(&sla.failed_stations, entry)))
+ free(failed_station);
+
+ return NULL;
+}
+
+struct dial_trunk_args {
+ struct sla_trunk_ref *trunk_ref;
+ struct sla_station *station;
+ ast_mutex_t *cond_lock;
+ ast_cond_t *cond;
+};
+
+static void *dial_trunk(void *data)
+{
+ struct dial_trunk_args *args = data;
+ struct ast_dial *dial;
+ char *tech, *tech_data;
+ enum ast_dial_result dial_res;
+ char conf_name[MAX_CONFNUM];
+ struct ast_conference *conf;
+ struct ast_flags conf_flags = { 0 };
+ struct sla_trunk_ref *trunk_ref = args->trunk_ref;
+ const char *cid_name = NULL, *cid_num = NULL;
+
+ if (!(dial = ast_dial_create())) {
+ ast_mutex_lock(args->cond_lock);
+ ast_cond_signal(args->cond);
+ ast_mutex_unlock(args->cond_lock);
+ return NULL;
+ }
+
+ tech_data = ast_strdupa(trunk_ref->trunk->device);
+ tech = strsep(&tech_data, "/");
+ if (ast_dial_append(dial, tech, tech_data) == -1) {
+ ast_mutex_lock(args->cond_lock);
+ ast_cond_signal(args->cond);
+ ast_mutex_unlock(args->cond_lock);
+ ast_dial_destroy(dial);
+ return NULL;
+ }
+
+ if (!sla.attempt_callerid && !ast_strlen_zero(trunk_ref->chan->cid.cid_name)) {
+ cid_name = ast_strdupa(trunk_ref->chan->cid.cid_name);
+ free(trunk_ref->chan->cid.cid_name);
+ trunk_ref->chan->cid.cid_name = NULL;
+ }
+ if (!sla.attempt_callerid && !ast_strlen_zero(trunk_ref->chan->cid.cid_num)) {
+ cid_num = ast_strdupa(trunk_ref->chan->cid.cid_num);
+ free(trunk_ref->chan->cid.cid_num);
+ trunk_ref->chan->cid.cid_num = NULL;
+ }
+
+ dial_res = ast_dial_run(dial, trunk_ref->chan, 1);
+
+ if (cid_name)
+ trunk_ref->chan->cid.cid_name = ast_strdup(cid_name);
+ if (cid_num)
+ trunk_ref->chan->cid.cid_num = ast_strdup(cid_num);
+
+ if (dial_res != AST_DIAL_RESULT_TRYING) {
+ ast_mutex_lock(args->cond_lock);
+ ast_cond_signal(args->cond);
+ ast_mutex_unlock(args->cond_lock);
+ ast_dial_destroy(dial);
+ return NULL;
+ }
+
+ for (;;) {
+ unsigned int done = 0;
+ switch ((dial_res = ast_dial_state(dial))) {
+ case AST_DIAL_RESULT_ANSWERED:
+ trunk_ref->trunk->chan = ast_dial_answered(dial);
+ case AST_DIAL_RESULT_HANGUP:
+ case AST_DIAL_RESULT_INVALID:
+ case AST_DIAL_RESULT_FAILED:
+ case AST_DIAL_RESULT_TIMEOUT:
+ case AST_DIAL_RESULT_UNANSWERED:
+ done = 1;
+ case AST_DIAL_RESULT_TRYING:
+ case AST_DIAL_RESULT_RINGING:
+ case AST_DIAL_RESULT_PROGRESS:
+ case AST_DIAL_RESULT_PROCEEDING:
+ break;
+ }
+ if (done)
+ break;
+ }
+
+ if (!trunk_ref->trunk->chan) {
+ ast_mutex_lock(args->cond_lock);
+ ast_cond_signal(args->cond);
+ ast_mutex_unlock(args->cond_lock);
+ ast_dial_join(dial);
+ ast_dial_destroy(dial);
+ return NULL;
+ }
+
+ snprintf(conf_name, sizeof(conf_name), "SLA_%s", trunk_ref->trunk->name);
+ ast_set_flag(&conf_flags,
+ CONFFLAG_QUIET | CONFFLAG_MARKEDEXIT | CONFFLAG_MARKEDUSER |
+ CONFFLAG_PASS_DTMF | CONFFLAG_SLA_TRUNK);
+ conf = build_conf(conf_name, "", "", 1, 1, 1);
+
+ ast_mutex_lock(args->cond_lock);
+ ast_cond_signal(args->cond);
+ ast_mutex_unlock(args->cond_lock);
+
+ if (conf) {
+ conf_run(trunk_ref->trunk->chan, conf, conf_flags.flags, NULL);
+ dispose_conf(conf);
+ conf = NULL;
+ }
+
+ /* If the trunk is going away, it is definitely now IDLE. */
+ sla_change_trunk_state(trunk_ref->trunk, SLA_TRUNK_STATE_IDLE, ALL_TRUNK_REFS, NULL);
+
+ trunk_ref->trunk->chan = NULL;
+ trunk_ref->trunk->on_hold = 0;
+
+ ast_dial_join(dial);
+ ast_dial_destroy(dial);
+
+ return NULL;
+}
+
+/*! \brief For a given station, choose the highest priority idle trunk
+ */
+static struct sla_trunk_ref *sla_choose_idle_trunk(const struct sla_station *station)
+{
+ struct sla_trunk_ref *trunk_ref = NULL;
+
+ AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) {
+ if (trunk_ref->state == SLA_TRUNK_STATE_IDLE)
+ break;
+ }
+
+ return trunk_ref;
+}
+
+static int sla_station_exec(struct ast_channel *chan, void *data)
+{
+ char *station_name, *trunk_name;
+ struct sla_station *station;
+ struct sla_trunk_ref *trunk_ref = NULL;
+ char conf_name[MAX_CONFNUM];
+ struct ast_flags conf_flags = { 0 };
+ struct ast_conference *conf;
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "Invalid Arguments to SLAStation!\n");
+ pbx_builtin_setvar_helper(chan, "SLASTATION_STATUS", "FAILURE");
+ return 0;
+ }
+
+ trunk_name = ast_strdupa(data);
+ station_name = strsep(&trunk_name, "_");
+
+ if (ast_strlen_zero(station_name)) {
+ ast_log(LOG_WARNING, "Invalid Arguments to SLAStation!\n");
+ pbx_builtin_setvar_helper(chan, "SLASTATION_STATUS", "FAILURE");
+ return 0;
+ }
+
+ AST_RWLIST_RDLOCK(&sla_stations);
+ station = sla_find_station(station_name);
+ AST_RWLIST_UNLOCK(&sla_stations);
+
+ if (!station) {
+ ast_log(LOG_WARNING, "Station '%s' not found!\n", station_name);
+ pbx_builtin_setvar_helper(chan, "SLASTATION_STATUS", "FAILURE");
+ return 0;
+ }
+
+ AST_RWLIST_RDLOCK(&sla_trunks);
+ if (!ast_strlen_zero(trunk_name)) {
+ trunk_ref = sla_find_trunk_ref_byname(station, trunk_name);
+ } else
+ trunk_ref = sla_choose_idle_trunk(station);
+ AST_RWLIST_UNLOCK(&sla_trunks);
+
+ if (!trunk_ref) {
+ if (ast_strlen_zero(trunk_name))
+ ast_log(LOG_NOTICE, "No trunks available for call.\n");
+ else {
+ ast_log(LOG_NOTICE, "Can't join existing call on trunk "
+ "'%s' due to access controls.\n", trunk_name);
+ }
+ pbx_builtin_setvar_helper(chan, "SLASTATION_STATUS", "CONGESTION");
+ return 0;
+ }
+
+ if (trunk_ref->state == SLA_TRUNK_STATE_ONHOLD_BYME) {
+ if (ast_atomic_dec_and_test((int *) &trunk_ref->trunk->hold_stations) == 1)
+ sla_change_trunk_state(trunk_ref->trunk, SLA_TRUNK_STATE_UP, ALL_TRUNK_REFS, NULL);
+ else {
+ trunk_ref->state = SLA_TRUNK_STATE_UP;
+ ast_device_state_changed("SLA:%s_%s", station->name, trunk_ref->trunk->name);
+ }
+ } else if (trunk_ref->state == SLA_TRUNK_STATE_RINGING) {
+ struct sla_ringing_trunk *ringing_trunk;
+
+ ast_mutex_lock(&sla.lock);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&sla.ringing_trunks, ringing_trunk, entry) {
+ if (ringing_trunk->trunk == trunk_ref->trunk) {
+ AST_LIST_REMOVE_CURRENT(&sla.ringing_trunks, entry);
+ break;
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END
+ ast_mutex_unlock(&sla.lock);
+
+ if (ringing_trunk) {
+ answer_trunk_chan(ringing_trunk->trunk->chan);
+ sla_change_trunk_state(ringing_trunk->trunk, SLA_TRUNK_STATE_UP, ALL_TRUNK_REFS, NULL);
+
+ free(ringing_trunk);
+
+ /* Queue up reprocessing ringing trunks, and then ringing stations again */
+ sla_queue_event(SLA_EVENT_RINGING_TRUNK);
+ sla_queue_event(SLA_EVENT_DIAL_STATE);
+ }
+ }
+
+ trunk_ref->chan = chan;
+
+ if (!trunk_ref->trunk->chan) {
+ ast_mutex_t cond_lock;
+ ast_cond_t cond;
+ pthread_t dont_care;
+ pthread_attr_t attr;
+ struct dial_trunk_args args = {
+ .trunk_ref = trunk_ref,
+ .station = station,
+ .cond_lock = &cond_lock,
+ .cond = &cond,
+ };
+ sla_change_trunk_state(trunk_ref->trunk, SLA_TRUNK_STATE_UP, ALL_TRUNK_REFS, NULL);
+ /* Create a thread to dial the trunk and dump it into the conference.
+ * However, we want to wait until the trunk has been dialed and the
+ * conference is created before continuing on here. */
+ ast_autoservice_start(chan);
+ ast_mutex_init(&cond_lock);
+ ast_cond_init(&cond, NULL);
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ ast_mutex_lock(&cond_lock);
+ ast_pthread_create_background(&dont_care, &attr, dial_trunk, &args);
+ ast_cond_wait(&cond, &cond_lock);
+ ast_mutex_unlock(&cond_lock);
+ ast_mutex_destroy(&cond_lock);
+ ast_cond_destroy(&cond);
+ pthread_attr_destroy(&attr);
+ ast_autoservice_stop(chan);
+ if (!trunk_ref->trunk->chan) {
+ ast_log(LOG_DEBUG, "Trunk didn't get created. chan: %lx\n", (long) trunk_ref->trunk->chan);
+ pbx_builtin_setvar_helper(chan, "SLASTATION_STATUS", "CONGESTION");
+ sla_change_trunk_state(trunk_ref->trunk, SLA_TRUNK_STATE_IDLE, ALL_TRUNK_REFS, NULL);
+ trunk_ref->chan = NULL;
+ return 0;
+ }
+ }
+
+ if (ast_atomic_fetchadd_int((int *) &trunk_ref->trunk->active_stations, 1) == 0 &&
+ trunk_ref->trunk->on_hold) {
+ trunk_ref->trunk->on_hold = 0;
+ ast_indicate(trunk_ref->trunk->chan, AST_CONTROL_UNHOLD);
+ sla_change_trunk_state(trunk_ref->trunk, SLA_TRUNK_STATE_UP, ALL_TRUNK_REFS, NULL);
+ }
+
+ snprintf(conf_name, sizeof(conf_name), "SLA_%s", trunk_ref->trunk->name);
+ ast_set_flag(&conf_flags,
+ CONFFLAG_QUIET | CONFFLAG_MARKEDEXIT | CONFFLAG_PASS_DTMF | CONFFLAG_SLA_STATION);
+ ast_answer(chan);
+ conf = build_conf(conf_name, "", "", 0, 0, 1);
+ if (conf) {
+ conf_run(chan, conf, conf_flags.flags, NULL);
+ dispose_conf(conf);
+ conf = NULL;
+ }
+ trunk_ref->chan = NULL;
+ if (ast_atomic_dec_and_test((int *) &trunk_ref->trunk->active_stations) &&
+ trunk_ref->state != SLA_TRUNK_STATE_ONHOLD_BYME) {
+ strncat(conf_name, "|K", sizeof(conf_name) - strlen(conf_name) - 1);
+ admin_exec(NULL, conf_name);
+ trunk_ref->trunk->hold_stations = 0;
+ sla_change_trunk_state(trunk_ref->trunk, SLA_TRUNK_STATE_IDLE, ALL_TRUNK_REFS, NULL);
+ }
+
+ pbx_builtin_setvar_helper(chan, "SLASTATION_STATUS", "SUCCESS");
+
+ return 0;
+}
+
+static struct sla_trunk_ref *create_trunk_ref(struct sla_trunk *trunk)
+{
+ struct sla_trunk_ref *trunk_ref;
+
+ if (!(trunk_ref = ast_calloc(1, sizeof(*trunk_ref))))
+ return NULL;
+
+ trunk_ref->trunk = trunk;
+
+ return trunk_ref;
+}
+
+static struct sla_ringing_trunk *queue_ringing_trunk(struct sla_trunk *trunk)
+{
+ struct sla_ringing_trunk *ringing_trunk;
+
+ if (!(ringing_trunk = ast_calloc(1, sizeof(*ringing_trunk))))
+ return NULL;
+
+ ringing_trunk->trunk = trunk;
+ ringing_trunk->ring_begin = ast_tvnow();
+
+ sla_change_trunk_state(trunk, SLA_TRUNK_STATE_RINGING, ALL_TRUNK_REFS, NULL);
+
+ ast_mutex_lock(&sla.lock);
+ AST_LIST_INSERT_HEAD(&sla.ringing_trunks, ringing_trunk, entry);
+ ast_mutex_unlock(&sla.lock);
+
+ sla_queue_event(SLA_EVENT_RINGING_TRUNK);
+
+ return ringing_trunk;
+}
+
+static int sla_trunk_exec(struct ast_channel *chan, void *data)
+{
+ const char *trunk_name = data;
+ char conf_name[MAX_CONFNUM];
+ struct ast_conference *conf;
+ struct ast_flags conf_flags = { 0 };
+ struct sla_trunk *trunk;
+ struct sla_ringing_trunk *ringing_trunk;
+
+ AST_RWLIST_RDLOCK(&sla_trunks);
+ trunk = sla_find_trunk(trunk_name);
+ AST_RWLIST_UNLOCK(&sla_trunks);
+ if (!trunk) {
+ ast_log(LOG_ERROR, "SLA Trunk '%s' not found!\n", trunk_name);
+ pbx_builtin_setvar_helper(chan, "SLATRUNK_STATUS", "FAILURE");
+ return 0;
+ }
+ if (trunk->chan) {
+ ast_log(LOG_ERROR, "Call came in on %s, but the trunk is already in use!\n",
+ trunk_name);
+ pbx_builtin_setvar_helper(chan, "SLATRUNK_STATUS", "FAILURE");
+ return 0;
+ }
+ trunk->chan = chan;
+
+ if (!(ringing_trunk = queue_ringing_trunk(trunk))) {
+ pbx_builtin_setvar_helper(chan, "SLATRUNK_STATUS", "FAILURE");
+ return 0;
+ }
+
+ snprintf(conf_name, sizeof(conf_name), "SLA_%s", trunk_name);
+ conf = build_conf(conf_name, "", "", 1, 1, 1);
+ if (!conf) {
+ pbx_builtin_setvar_helper(chan, "SLATRUNK_STATUS", "FAILURE");
+ return 0;
+ }
+ ast_set_flag(&conf_flags,
+ CONFFLAG_QUIET | CONFFLAG_MARKEDEXIT | CONFFLAG_MARKEDUSER | CONFFLAG_PASS_DTMF | CONFFLAG_NO_AUDIO_UNTIL_UP);
+ ast_indicate(chan, AST_CONTROL_RINGING);
+ conf_run(chan, conf, conf_flags.flags, NULL);
+ dispose_conf(conf);
+ conf = NULL;
+ trunk->chan = NULL;
+ trunk->on_hold = 0;
+
+ sla_change_trunk_state(trunk, SLA_TRUNK_STATE_IDLE, ALL_TRUNK_REFS, NULL);
+
+ if (!pbx_builtin_getvar_helper(chan, "SLATRUNK_STATUS"))
+ pbx_builtin_setvar_helper(chan, "SLATRUNK_STATUS", "SUCCESS");
+
+ /* Remove the entry from the list of ringing trunks if it is still there. */
+ ast_mutex_lock(&sla.lock);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&sla.ringing_trunks, ringing_trunk, entry) {
+ if (ringing_trunk->trunk == trunk) {
+ AST_LIST_REMOVE_CURRENT(&sla.ringing_trunks, entry);
+ break;
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END
+ ast_mutex_unlock(&sla.lock);
+ if (ringing_trunk) {
+ free(ringing_trunk);
+ pbx_builtin_setvar_helper(chan, "SLATRUNK_STATUS", "UNANSWERED");
+ /* Queue reprocessing of ringing trunks to make stations stop ringing
+ * that shouldn't be ringing after this trunk stopped. */
+ sla_queue_event(SLA_EVENT_RINGING_TRUNK);
+ }
+
+ return 0;
+}
+
+static int sla_state(const char *data)
+{
+ char *buf, *station_name, *trunk_name;
+ struct sla_station *station;
+ struct sla_trunk_ref *trunk_ref;
+ int res = AST_DEVICE_INVALID;
+
+ trunk_name = buf = ast_strdupa(data);
+ station_name = strsep(&trunk_name, "_");
+
+ AST_RWLIST_RDLOCK(&sla_stations);
+ AST_LIST_TRAVERSE(&sla_stations, station, entry) {
+ if (strcasecmp(station_name, station->name))
+ continue;
+ AST_RWLIST_RDLOCK(&sla_trunks);
+ AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) {
+ if (!strcasecmp(trunk_name, trunk_ref->trunk->name))
+ break;
+ }
+ if (!trunk_ref) {
+ AST_RWLIST_UNLOCK(&sla_trunks);
+ break;
+ }
+ switch (trunk_ref->state) {
+ case SLA_TRUNK_STATE_IDLE:
+ res = AST_DEVICE_NOT_INUSE;
+ break;
+ case SLA_TRUNK_STATE_RINGING:
+ res = AST_DEVICE_RINGING;
+ break;
+ case SLA_TRUNK_STATE_UP:
+ res = AST_DEVICE_INUSE;
+ break;
+ case SLA_TRUNK_STATE_ONHOLD:
+ case SLA_TRUNK_STATE_ONHOLD_BYME:
+ res = AST_DEVICE_ONHOLD;
+ break;
+ }
+ AST_RWLIST_UNLOCK(&sla_trunks);
+ }
+ AST_RWLIST_UNLOCK(&sla_stations);
+
+ if (res == AST_DEVICE_INVALID) {
+ ast_log(LOG_ERROR, "Could not determine state for trunk %s on station %s!\n",
+ trunk_name, station_name);
+ }
+
+ return res;
+}
+
+static void destroy_trunk(struct sla_trunk *trunk)
+{
+ struct sla_station_ref *station_ref;
+
+ if (!ast_strlen_zero(trunk->autocontext))
+ ast_context_remove_extension(trunk->autocontext, "s", 1, sla_registrar);
+
+ while ((station_ref = AST_LIST_REMOVE_HEAD(&trunk->stations, entry)))
+ free(station_ref);
+
+ ast_string_field_free_memory(trunk);
+ free(trunk);
+}
+
+static void destroy_station(struct sla_station *station)
+{
+ struct sla_trunk_ref *trunk_ref;
+
+ if (!ast_strlen_zero(station->autocontext)) {
+ AST_RWLIST_RDLOCK(&sla_trunks);
+ AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) {
+ char exten[AST_MAX_EXTENSION];
+ char hint[AST_MAX_APP];
+ snprintf(exten, sizeof(exten), "%s_%s", station->name, trunk_ref->trunk->name);
+ snprintf(hint, sizeof(hint), "SLA:%s", exten);
+ ast_context_remove_extension(station->autocontext, exten,
+ 1, sla_registrar);
+ ast_context_remove_extension(station->autocontext, hint,
+ PRIORITY_HINT, sla_registrar);
+ }
+ AST_RWLIST_UNLOCK(&sla_trunks);
+ }
+
+ while ((trunk_ref = AST_LIST_REMOVE_HEAD(&station->trunks, entry)))
+ free(trunk_ref);
+
+ ast_string_field_free_memory(station);
+ free(station);
+}
+
+static void sla_destroy(void)
+{
+ struct sla_trunk *trunk;
+ struct sla_station *station;
+
+ AST_RWLIST_WRLOCK(&sla_trunks);
+ while ((trunk = AST_RWLIST_REMOVE_HEAD(&sla_trunks, entry)))
+ destroy_trunk(trunk);
+ AST_RWLIST_UNLOCK(&sla_trunks);
+
+ AST_RWLIST_WRLOCK(&sla_stations);
+ while ((station = AST_RWLIST_REMOVE_HEAD(&sla_stations, entry)))
+ destroy_station(station);
+ AST_RWLIST_UNLOCK(&sla_stations);
+
+ if (sla.thread != AST_PTHREADT_NULL) {
+ ast_mutex_lock(&sla.lock);
+ sla.stop = 1;
+ ast_cond_signal(&sla.cond);
+ ast_mutex_unlock(&sla.lock);
+ pthread_join(sla.thread, NULL);
+ }
+
+ /* Drop any created contexts from the dialplan */
+ ast_context_destroy(NULL, sla_registrar);
+
+ ast_mutex_destroy(&sla.lock);
+ ast_cond_destroy(&sla.cond);
+}
+
+static int sla_check_device(const char *device)
+{
+ char *tech, *tech_data;
+
+ tech_data = ast_strdupa(device);
+ tech = strsep(&tech_data, "/");
+
+ if (ast_strlen_zero(tech) || ast_strlen_zero(tech_data))
+ return -1;
+
+ return 0;
+}
+
+static int sla_build_trunk(struct ast_config *cfg, const char *cat)
+{
+ struct sla_trunk *trunk;
+ struct ast_variable *var;
+ const char *dev;
+
+ if (!(dev = ast_variable_retrieve(cfg, cat, "device"))) {
+ ast_log(LOG_ERROR, "SLA Trunk '%s' defined with no device!\n", cat);
+ return -1;
+ }
+
+ if (sla_check_device(dev)) {
+ ast_log(LOG_ERROR, "SLA Trunk '%s' define with invalid device '%s'!\n",
+ cat, dev);
+ return -1;
+ }
+
+ if (!(trunk = ast_calloc(1, sizeof(*trunk))))
+ return -1;
+ if (ast_string_field_init(trunk, 32)) {
+ free(trunk);
+ return -1;
+ }
+
+ ast_string_field_set(trunk, name, cat);
+ ast_string_field_set(trunk, device, dev);
+
+ for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
+ if (!strcasecmp(var->name, "autocontext"))
+ ast_string_field_set(trunk, autocontext, var->value);
+ else if (!strcasecmp(var->name, "ringtimeout")) {
+ if (sscanf(var->value, "%u", &trunk->ring_timeout) != 1) {
+ ast_log(LOG_WARNING, "Invalid ringtimeout '%s' specified for trunk '%s'\n",
+ var->value, trunk->name);
+ trunk->ring_timeout = 0;
+ }
+ } else if (!strcasecmp(var->name, "barge"))
+ trunk->barge_disabled = ast_false(var->value);
+ else if (!strcasecmp(var->name, "hold")) {
+ if (!strcasecmp(var->value, "private"))
+ trunk->hold_access = SLA_HOLD_PRIVATE;
+ else if (!strcasecmp(var->value, "open"))
+ trunk->hold_access = SLA_HOLD_OPEN;
+ else {
+ ast_log(LOG_WARNING, "Invalid value '%s' for hold on trunk %s\n",
+ var->value, trunk->name);
+ }
+ } else if (strcasecmp(var->name, "type") && strcasecmp(var->name, "device")) {
+ ast_log(LOG_ERROR, "Invalid option '%s' specified at line %d of %s!\n",
+ var->name, var->lineno, SLA_CONFIG_FILE);
+ }
+ }
+
+ if (!ast_strlen_zero(trunk->autocontext)) {
+ struct ast_context *context;
+ context = ast_context_find_or_create(NULL, trunk->autocontext, sla_registrar);
+ if (!context) {
+ ast_log(LOG_ERROR, "Failed to automatically find or create "
+ "context '%s' for SLA!\n", trunk->autocontext);
+ destroy_trunk(trunk);
+ return -1;
+ }
+ if (ast_add_extension2(context, 0 /* don't replace */, "s", 1,
+ NULL, NULL, slatrunk_app, ast_strdup(trunk->name), ast_free, sla_registrar)) {
+ ast_log(LOG_ERROR, "Failed to automatically create extension "
+ "for trunk '%s'!\n", trunk->name);
+ destroy_trunk(trunk);
+ return -1;
+ }
+ }
+
+ AST_RWLIST_WRLOCK(&sla_trunks);
+ AST_RWLIST_INSERT_TAIL(&sla_trunks, trunk, entry);
+ AST_RWLIST_UNLOCK(&sla_trunks);
+
+ return 0;
+}
+
+static void sla_add_trunk_to_station(struct sla_station *station, struct ast_variable *var)
+{
+ struct sla_trunk *trunk;
+ struct sla_trunk_ref *trunk_ref;
+ struct sla_station_ref *station_ref;
+ char *trunk_name, *options, *cur;
+
+ options = ast_strdupa(var->value);
+ trunk_name = strsep(&options, ",");
+
+ AST_RWLIST_RDLOCK(&sla_trunks);
+ AST_RWLIST_TRAVERSE(&sla_trunks, trunk, entry) {
+ if (!strcasecmp(trunk->name, trunk_name))
+ break;
+ }
+
+ AST_RWLIST_UNLOCK(&sla_trunks);
+ if (!trunk) {
+ ast_log(LOG_ERROR, "Trunk '%s' not found!\n", var->value);
+ return;
+ }
+ if (!(trunk_ref = create_trunk_ref(trunk)))
+ return;
+ trunk_ref->state = SLA_TRUNK_STATE_IDLE;
+
+ while ((cur = strsep(&options, ","))) {
+ char *name, *value = cur;
+ name = strsep(&value, "=");
+ if (!strcasecmp(name, "ringtimeout")) {
+ if (sscanf(value, "%u", &trunk_ref->ring_timeout) != 1) {
+ ast_log(LOG_WARNING, "Invalid ringtimeout value '%s' for "
+ "trunk '%s' on station '%s'\n", value, trunk->name, station->name);
+ trunk_ref->ring_timeout = 0;
+ }
+ } else if (!strcasecmp(name, "ringdelay")) {
+ if (sscanf(value, "%u", &trunk_ref->ring_delay) != 1) {
+ ast_log(LOG_WARNING, "Invalid ringdelay value '%s' for "
+ "trunk '%s' on station '%s'\n", value, trunk->name, station->name);
+ trunk_ref->ring_delay = 0;
+ }
+ } else {
+ ast_log(LOG_WARNING, "Invalid option '%s' for "
+ "trunk '%s' on station '%s'\n", name, trunk->name, station->name);
+ }
+ }
+
+ if (!(station_ref = sla_create_station_ref(station))) {
+ free(trunk_ref);
+ return;
+ }
+ ast_atomic_fetchadd_int((int *) &trunk->num_stations, 1);
+ AST_RWLIST_WRLOCK(&sla_trunks);
+ AST_LIST_INSERT_TAIL(&trunk->stations, station_ref, entry);
+ AST_RWLIST_UNLOCK(&sla_trunks);
+ AST_LIST_INSERT_TAIL(&station->trunks, trunk_ref, entry);
+}
+
+static int sla_build_station(struct ast_config *cfg, const char *cat)
+{
+ struct sla_station *station;
+ struct ast_variable *var;
+ const char *dev;
+
+ if (!(dev = ast_variable_retrieve(cfg, cat, "device"))) {
+ ast_log(LOG_ERROR, "SLA Station '%s' defined with no device!\n", cat);
+ return -1;
+ }
+
+ if (!(station = ast_calloc(1, sizeof(*station))))
+ return -1;
+ if (ast_string_field_init(station, 32)) {
+ free(station);
+ return -1;
+ }
+
+ ast_string_field_set(station, name, cat);
+ ast_string_field_set(station, device, dev);
+
+ for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
+ if (!strcasecmp(var->name, "trunk"))
+ sla_add_trunk_to_station(station, var);
+ else if (!strcasecmp(var->name, "autocontext"))
+ ast_string_field_set(station, autocontext, var->value);
+ else if (!strcasecmp(var->name, "ringtimeout")) {
+ if (sscanf(var->value, "%u", &station->ring_timeout) != 1) {
+ ast_log(LOG_WARNING, "Invalid ringtimeout '%s' specified for station '%s'\n",
+ var->value, station->name);
+ station->ring_timeout = 0;
+ }
+ } else if (!strcasecmp(var->name, "ringdelay")) {
+ if (sscanf(var->value, "%u", &station->ring_delay) != 1) {
+ ast_log(LOG_WARNING, "Invalid ringdelay '%s' specified for station '%s'\n",
+ var->value, station->name);
+ station->ring_delay = 0;
+ }
+ } else if (!strcasecmp(var->name, "hold")) {
+ if (!strcasecmp(var->value, "private"))
+ station->hold_access = SLA_HOLD_PRIVATE;
+ else if (!strcasecmp(var->value, "open"))
+ station->hold_access = SLA_HOLD_OPEN;
+ else {
+ ast_log(LOG_WARNING, "Invalid value '%s' for hold on station %s\n",
+ var->value, station->name);
+ }
+
+ } else if (strcasecmp(var->name, "type") && strcasecmp(var->name, "device")) {
+ ast_log(LOG_ERROR, "Invalid option '%s' specified at line %d of %s!\n",
+ var->name, var->lineno, SLA_CONFIG_FILE);
+ }
+ }
+
+ if (!ast_strlen_zero(station->autocontext)) {
+ struct ast_context *context;
+ struct sla_trunk_ref *trunk_ref;
+ context = ast_context_find_or_create(NULL, station->autocontext, sla_registrar);
+ if (!context) {
+ ast_log(LOG_ERROR, "Failed to automatically find or create "
+ "context '%s' for SLA!\n", station->autocontext);
+ destroy_station(station);
+ return -1;
+ }
+ /* The extension for when the handset goes off-hook.
+ * exten => station1,1,SLAStation(station1) */
+ if (ast_add_extension2(context, 0 /* don't replace */, station->name, 1,
+ NULL, NULL, slastation_app, ast_strdup(station->name), ast_free, sla_registrar)) {
+ ast_log(LOG_ERROR, "Failed to automatically create extension "
+ "for trunk '%s'!\n", station->name);
+ destroy_station(station);
+ return -1;
+ }
+ AST_RWLIST_RDLOCK(&sla_trunks);
+ AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) {
+ char exten[AST_MAX_EXTENSION];
+ char hint[AST_MAX_APP];
+ snprintf(exten, sizeof(exten), "%s_%s", station->name, trunk_ref->trunk->name);
+ snprintf(hint, sizeof(hint), "SLA:%s", exten);
+ /* Extension for this line button
+ * exten => station1_line1,1,SLAStation(station1_line1) */
+ if (ast_add_extension2(context, 0 /* don't replace */, exten, 1,
+ NULL, NULL, slastation_app, ast_strdup(exten), ast_free, sla_registrar)) {
+ ast_log(LOG_ERROR, "Failed to automatically create extension "
+ "for trunk '%s'!\n", station->name);
+ destroy_station(station);
+ return -1;
+ }
+ /* Hint for this line button
+ * exten => station1_line1,hint,SLA:station1_line1 */
+ if (ast_add_extension2(context, 0 /* don't replace */, exten, PRIORITY_HINT,
+ NULL, NULL, hint, NULL, NULL, sla_registrar)) {
+ ast_log(LOG_ERROR, "Failed to automatically create hint "
+ "for trunk '%s'!\n", station->name);
+ destroy_station(station);
+ return -1;
+ }
+ }
+ AST_RWLIST_UNLOCK(&sla_trunks);
+ }
+
+ AST_RWLIST_WRLOCK(&sla_stations);
+ AST_RWLIST_INSERT_TAIL(&sla_stations, station, entry);
+ AST_RWLIST_UNLOCK(&sla_stations);
+
+ return 0;
+}
+
+static int sla_load_config(void)
+{
+ struct ast_config *cfg;
+ const char *cat = NULL;
+ int res = 0;
+ const char *val;
+
+ ast_mutex_init(&sla.lock);
+ ast_cond_init(&sla.cond, NULL);
+
+ if (!(cfg = ast_config_load(SLA_CONFIG_FILE)))
+ return 0; /* Treat no config as normal */
+
+ if ((val = ast_variable_retrieve(cfg, "general", "attemptcallerid")))
+ sla.attempt_callerid = ast_true(val);
+
+ while ((cat = ast_category_browse(cfg, cat)) && !res) {
+ const char *type;
+ if (!strcasecmp(cat, "general"))
+ continue;
+ if (!(type = ast_variable_retrieve(cfg, cat, "type"))) {
+ ast_log(LOG_WARNING, "Invalid entry in %s defined with no type!\n",
+ SLA_CONFIG_FILE);
+ continue;
+ }
+ if (!strcasecmp(type, "trunk"))
+ res = sla_build_trunk(cfg, cat);
+ else if (!strcasecmp(type, "station"))
+ res = sla_build_station(cfg, cat);
+ else {
+ ast_log(LOG_WARNING, "Entry in %s defined with invalid type '%s'!\n",
+ SLA_CONFIG_FILE, type);
+ }
+ }
+
+ ast_config_destroy(cfg);
+
+ if (!AST_LIST_EMPTY(&sla_stations) || !AST_LIST_EMPTY(&sla_stations))
+ ast_pthread_create(&sla.thread, NULL, sla_thread, NULL);
+
+ return res;
+}
+
+static int load_config(int reload)
+{
+ int res = 0;
+
+ load_config_meetme();
+ if (!reload)
+ res = sla_load_config();
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res = 0;
+
+ ast_cli_unregister_multiple(cli_meetme, ARRAY_LEN(cli_meetme));
+ res = ast_manager_unregister("MeetmeMute");
+ res |= ast_manager_unregister("MeetmeUnmute");
+ res |= ast_unregister_application(app3);
+ res |= ast_unregister_application(app2);
+ res |= ast_unregister_application(app);
+ res |= ast_unregister_application(slastation_app);
+ res |= ast_unregister_application(slatrunk_app);
+
+ ast_devstate_prov_del("Meetme");
+ ast_devstate_prov_del("SLA");
+
+ ast_module_user_hangup_all();
+
+ sla_destroy();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ int res = 0;
+
+ res |= load_config(0);
+
+ ast_cli_register_multiple(cli_meetme, ARRAY_LEN(cli_meetme));
+ res |= ast_manager_register("MeetmeMute", EVENT_FLAG_CALL,
+ action_meetmemute, "Mute a Meetme user");
+ res |= ast_manager_register("MeetmeUnmute", EVENT_FLAG_CALL,
+ action_meetmeunmute, "Unmute a Meetme user");
+ res |= ast_register_application(app3, admin_exec, synopsis3, descrip3);
+ res |= ast_register_application(app2, count_exec, synopsis2, descrip2);
+ res |= ast_register_application(app, conf_exec, synopsis, descrip);
+ res |= ast_register_application(slastation_app, sla_station_exec,
+ slastation_synopsis, slastation_desc);
+ res |= ast_register_application(slatrunk_app, sla_trunk_exec,
+ slatrunk_synopsis, slatrunk_desc);
+
+ res |= ast_devstate_prov_add("Meetme", meetmestate);
+ res |= ast_devstate_prov_add("SLA", sla_state);
+
+ return res;
+}
+
+static int reload(void)
+{
+ return load_config(1);
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "MeetMe conference bridge",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
+
diff --git a/apps/app_milliwatt.c b/apps/app_milliwatt.c
new file mode 100644
index 000000000..b63771c38
--- /dev/null
+++ b/apps/app_milliwatt.c
@@ -0,0 +1,194 @@
+/*
+ * 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 Digital Milliwatt Test
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+ <depend>res_indications</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/utils.h"
+
+static char *app = "Milliwatt";
+
+static char *synopsis = "Generate a Constant 1004Hz tone at 0dbm (mu-law)";
+
+static char *descrip =
+" Milliwatt([options]): Generate a Constant 1004Hz tone at 0dbm.\n"
+"Previous versions of this application generated the tone at 1000Hz. If for\n"
+"some reason you would prefer that behavior, supply the 'o' option to get the\n"
+"old behavior.\n"
+"";
+
+
+static char digital_milliwatt[] = {0x1e,0x0b,0x0b,0x1e,0x9e,0x8b,0x8b,0x9e} ;
+
+static void *milliwatt_alloc(struct ast_channel *chan, void *params)
+{
+ return ast_calloc(1, sizeof(int));
+}
+
+static void milliwatt_release(struct ast_channel *chan, void *data)
+{
+ free(data);
+ return;
+}
+
+static int milliwatt_generate(struct ast_channel *chan, void *data, int len, int samples)
+{
+ unsigned char buf[AST_FRIENDLY_OFFSET + 640];
+ const int maxsamples = sizeof (buf) / sizeof (buf[0]);
+ int i, *indexp = (int *) data;
+ struct ast_frame wf = {
+ .frametype = AST_FRAME_VOICE,
+ .subclass = AST_FORMAT_ULAW,
+ .offset = AST_FRIENDLY_OFFSET,
+ .data = buf + AST_FRIENDLY_OFFSET,
+ .src = __FUNCTION__,
+ };
+
+ /* Instead of len, use samples, because channel.c generator_force
+ * generate(chan, tmp, 0, 160) ignores len. In any case, len is
+ * a multiple of samples, given by number of samples times bytes per
+ * sample. In the case of ulaw, len = samples. for signed linear
+ * len = 2 * samples */
+
+ if (samples > maxsamples) {
+ ast_log(LOG_WARNING, "Only doing %d samples (%d requested)\n", maxsamples, samples);
+ samples = maxsamples;
+ }
+
+ len = samples * sizeof (buf[0]);
+ wf.datalen = len;
+ wf.samples = samples;
+
+ /* create a buffer containing the digital milliwatt pattern */
+ for (i = 0; i < len; i++) {
+ buf[AST_FRIENDLY_OFFSET + i] = digital_milliwatt[(*indexp)++];
+ *indexp &= 7;
+ }
+
+ if (ast_write(chan,&wf) < 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 milliwattgen = {
+ alloc: milliwatt_alloc,
+ release: milliwatt_release,
+ generate: milliwatt_generate,
+};
+
+static int old_milliwatt_exec(struct ast_channel *chan)
+{
+ ast_set_write_format(chan, AST_FORMAT_ULAW);
+ ast_set_read_format(chan, AST_FORMAT_ULAW);
+
+ if (chan->_state != AST_STATE_UP) {
+ ast_answer(chan);
+ }
+
+ if (ast_activate_generator(chan,&milliwattgen,"milliwatt") < 0) {
+ ast_log(LOG_WARNING,"Failed to activate generator on '%s'\n",chan->name);
+ return -1;
+ }
+
+ while (!ast_safe_sleep(chan, 10000))
+ ;
+
+ ast_deactivate_generator(chan);
+
+ return -1;
+}
+
+static int milliwatt_exec(struct ast_channel *chan, void *data)
+{
+ const char *options = data;
+ struct ast_app *playtones_app;
+ struct ast_module_user *u;
+ int res = -1;
+
+ u = ast_module_user_add(chan);
+
+ if (!ast_strlen_zero(options) && strchr(options, 'o')) {
+ res = old_milliwatt_exec(chan);
+ goto exit_app;
+ }
+
+ if (!(playtones_app = pbx_findapp("Playtones"))) {
+ ast_log(LOG_ERROR, "The Playtones application is required to run Milliwatt()\n");
+ goto exit_app;
+ }
+
+ res = pbx_exec(chan, playtones_app, "1004/1000");
+
+ while (!res) {
+ res = ast_safe_sleep(chan, 10000);
+ }
+
+ res = 0;
+
+exit_app:
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, milliwatt_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Digital Milliwatt (mu-law) Test Application");
diff --git a/apps/app_mixmonitor.c b/apps/app_mixmonitor.c
new file mode 100644
index 000000000..3cf79b1fc
--- /dev/null
+++ b/apps/app_mixmonitor.c
@@ -0,0 +1,446 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2005, Anthony Minessale II
+ * Copyright (C) 2005 - 2006, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ * Kevin P. Fleming <kpfleming@digium.com>
+ *
+ * Based on app_muxmon.c provided by
+ * 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 MixMonitor() - Record a call and mix the audio during the recording
+ * \ingroup applications
+ *
+ * \author Mark Spencer <markster@digium.com>
+ * \author Kevin P. Fleming <kpfleming@digium.com>
+ *
+ * \note Based on app_muxmon.c provided by
+ * Anthony Minessale II <anthmct@yahoo.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/audiohook.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/lock.h"
+#include "asterisk/cli.h"
+#include "asterisk/options.h"
+#include "asterisk/app.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/utils.h"
+
+#define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0
+
+static const char *app = "MixMonitor";
+static const char *synopsis = "Record a call and mix the audio during the recording";
+static const char *desc = ""
+" MixMonitor(<file>.<ext>[|<options>[|<command>]])\n\n"
+"Records the audio on the current channel to the specified file.\n"
+"If the filename is an absolute path, uses that path, otherwise\n"
+"creates the file in the configured monitoring directory from\n"
+"asterisk.conf.\n\n"
+"Valid options:\n"
+" a - Append to the file instead of overwriting it.\n"
+" b - Only save audio to the file while the channel is bridged.\n"
+" Note: Does not include conferences or sounds played to each bridged\n"
+" party.\n"
+" v(<x>) - Adjust the heard volume by a factor of <x> (range -4 to 4)\n"
+" V(<x>) - Adjust the spoken volume by a factor of <x> (range -4 to 4)\n"
+" W(<x>) - Adjust the both heard and spoken volumes by a factor of <x>\n"
+" (range -4 to 4)\n\n"
+"<command> will be executed when the recording is over\n"
+"Any strings matching ^{X} will be unescaped to ${X}.\n"
+"All variables will be evaluated at the time MixMonitor is called.\n"
+"The variable MIXMONITOR_FILENAME will contain the filename used to record.\n"
+"";
+
+static const char *stop_app = "StopMixMonitor";
+static const char *stop_synopsis = "Stop recording a call through MixMonitor";
+static const char *stop_desc = ""
+" StopMixMonitor()\n\n"
+"Stops the audio recording that was started with a call to MixMonitor()\n"
+"on the current channel.\n"
+"";
+
+struct module_symbols *me;
+
+static const char *mixmonitor_spy_type = "MixMonitor";
+
+struct mixmonitor {
+ struct ast_audiohook audiohook;
+ char *filename;
+ char *post_process;
+ char *name;
+ unsigned int flags;
+ struct ast_channel *chan;
+};
+
+enum {
+ MUXFLAG_APPEND = (1 << 1),
+ MUXFLAG_BRIDGED = (1 << 2),
+ MUXFLAG_VOLUME = (1 << 3),
+ MUXFLAG_READVOLUME = (1 << 4),
+ MUXFLAG_WRITEVOLUME = (1 << 5),
+} mixmonitor_flags;
+
+enum {
+ OPT_ARG_READVOLUME = 0,
+ OPT_ARG_WRITEVOLUME,
+ OPT_ARG_VOLUME,
+ OPT_ARG_ARRAY_SIZE,
+} mixmonitor_args;
+
+AST_APP_OPTIONS(mixmonitor_opts, {
+ AST_APP_OPTION('a', MUXFLAG_APPEND),
+ AST_APP_OPTION('b', MUXFLAG_BRIDGED),
+ AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME, OPT_ARG_READVOLUME),
+ AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME, OPT_ARG_WRITEVOLUME),
+ AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME),
+});
+
+static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook)
+{
+ struct ast_channel *peer;
+ int res;
+
+ if (!chan)
+ return -1;
+
+ res = ast_audiohook_attach(chan, audiohook);
+
+ if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan)))
+ ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);
+
+ return res;
+}
+
+#define SAMPLES_PER_FRAME 160
+
+static void *mixmonitor_thread(void *obj)
+{
+ struct mixmonitor *mixmonitor = obj;
+ struct ast_filestream *fs = NULL;
+ unsigned int oflags;
+ char *ext;
+ int errflag = 0;
+
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "Begin MixMonitor Recording %s\n", mixmonitor->name);
+
+ ast_audiohook_lock(&mixmonitor->audiohook);
+
+ while (mixmonitor->audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING) {
+ struct ast_frame *fr = NULL;
+
+ ast_audiohook_trigger_wait(&mixmonitor->audiohook);
+
+ if (mixmonitor->audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING)
+ break;
+
+ if (!(fr = ast_audiohook_read_frame(&mixmonitor->audiohook, SAMPLES_PER_FRAME, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR)))
+ continue;
+
+ if (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) || ast_bridged_channel(mixmonitor->chan)) {
+ /* Initialize the file if not already done so */
+ if (!fs && !errflag) {
+ oflags = O_CREAT | O_WRONLY;
+ oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
+
+ if ((ext = strrchr(mixmonitor->filename, '.')))
+ *(ext++) = '\0';
+ else
+ ext = "raw";
+
+ if (!(fs = ast_writefile(mixmonitor->filename, ext, NULL, oflags, 0, 0644))) {
+ ast_log(LOG_ERROR, "Cannot open %s.%s\n", mixmonitor->filename, ext);
+ errflag = 1;
+ }
+ }
+
+ /* Write out the frame */
+ if (fs)
+ ast_writestream(fs, fr);
+ }
+
+ /* All done! free it. */
+ ast_frame_free(fr, 0);
+ }
+
+ ast_audiohook_detach(&mixmonitor->audiohook);
+ ast_audiohook_unlock(&mixmonitor->audiohook);
+ ast_audiohook_destroy(&mixmonitor->audiohook);
+
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "End MixMonitor Recording %s\n", mixmonitor->name);
+
+ if (fs)
+ ast_closestream(fs);
+
+ if (mixmonitor->post_process) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_2 "Executing [%s]\n", mixmonitor->post_process);
+ ast_safe_system(mixmonitor->post_process);
+ }
+
+ free(mixmonitor);
+
+
+ return NULL;
+}
+
+static void launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags,
+ int readvol, int writevol, const char *post_process)
+{
+ pthread_attr_t attr;
+ pthread_t thread;
+ struct mixmonitor *mixmonitor;
+ char postprocess2[1024] = "";
+ size_t len;
+
+ len = sizeof(*mixmonitor) + strlen(chan->name) + strlen(filename) + 2;
+
+ /* If a post process system command is given attach it to the structure */
+ if (!ast_strlen_zero(post_process)) {
+ char *p1, *p2;
+
+ p1 = ast_strdupa(post_process);
+ for (p2 = p1; *p2 ; p2++) {
+ if (*p2 == '^' && *(p2+1) == '{') {
+ *p2 = '$';
+ }
+ }
+
+ pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
+ if (!ast_strlen_zero(postprocess2))
+ len += strlen(postprocess2) + 1;
+ }
+
+ /* Pre-allocate mixmonitor structure and spy */
+ if (!(mixmonitor = calloc(1, len))) {
+ return;
+ }
+
+ /* Copy over flags and channel name */
+ mixmonitor->flags = flags;
+ mixmonitor->chan = chan;
+ mixmonitor->name = (char *) mixmonitor + sizeof(*mixmonitor);
+ strcpy(mixmonitor->name, chan->name);
+ if (!ast_strlen_zero(postprocess2)) {
+ mixmonitor->post_process = mixmonitor->name + strlen(mixmonitor->name) + strlen(filename) + 2;
+ strcpy(mixmonitor->post_process, postprocess2);
+ }
+
+ mixmonitor->filename = (char *) mixmonitor + sizeof(*mixmonitor) + strlen(chan->name) + 1;
+ strcpy(mixmonitor->filename, filename);
+
+ /* Setup the actual spy before creating our thread */
+ if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type)) {
+ free(mixmonitor);
+ return;
+ }
+
+ ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
+
+ if (readvol)
+ mixmonitor->audiohook.options.read_volume = readvol;
+ if (writevol)
+ mixmonitor->audiohook.options.write_volume = writevol;
+
+ if (startmon(chan, &mixmonitor->audiohook)) {
+ ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
+ mixmonitor_spy_type, chan->name);
+ /* Since we couldn't add ourselves - bail out! */
+ ast_audiohook_destroy(&mixmonitor->audiohook);
+ free(mixmonitor);
+ return;
+ }
+
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ ast_pthread_create_background(&thread, &attr, mixmonitor_thread, mixmonitor);
+ pthread_attr_destroy(&attr);
+
+}
+
+static int mixmonitor_exec(struct ast_channel *chan, void *data)
+{
+ int x, readvol = 0, writevol = 0;
+ struct ast_module_user *u;
+ struct ast_flags flags = {0};
+ char *parse;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(filename);
+ AST_APP_ARG(options);
+ AST_APP_ARG(post_process);
+ );
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ parse = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ if (ast_strlen_zero(args.filename)) {
+ ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ if (args.options) {
+ char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
+
+ ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
+
+ if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
+ if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
+ ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
+ } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
+ ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
+ } else {
+ readvol = get_volfactor(x);
+ }
+ }
+
+ if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
+ if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
+ ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
+ } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
+ ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
+ } else {
+ writevol = get_volfactor(x);
+ }
+ }
+
+ if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
+ if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
+ ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
+ } else if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
+ ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
+ } else {
+ readvol = writevol = get_volfactor(x);
+ }
+ }
+ }
+
+ /* if not provided an absolute path, use the system-configured monitoring directory */
+ if (args.filename[0] != '/') {
+ char *build;
+
+ build = alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(args.filename) + 3);
+ sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, args.filename);
+ args.filename = build;
+ }
+
+ pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
+ launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process);
+
+ ast_module_user_remove(u);
+
+ return 0;
+}
+
+static int stop_mixmonitor_exec(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *u;
+
+ u = ast_module_user_add(chan);
+
+ ast_audiohook_detach_source(chan, mixmonitor_spy_type);
+
+ ast_module_user_remove(u);
+
+ return 0;
+}
+
+static int mixmonitor_cli(int fd, int argc, char **argv)
+{
+ struct ast_channel *chan;
+
+ if (argc < 3)
+ return RESULT_SHOWUSAGE;
+
+ if (!(chan = ast_get_channel_by_name_prefix_locked(argv[2], strlen(argv[2])))) {
+ ast_cli(fd, "No channel matching '%s' found.\n", argv[2]);
+ return RESULT_SUCCESS;
+ }
+
+ if (!strcasecmp(argv[1], "start"))
+ mixmonitor_exec(chan, argv[3]);
+ else if (!strcasecmp(argv[1], "stop"))
+ ast_audiohook_detach_source(chan, mixmonitor_spy_type);
+
+ ast_channel_unlock(chan);
+
+ return RESULT_SUCCESS;
+}
+
+static char *complete_mixmonitor_cli(const char *line, const char *word, int pos, int state)
+{
+ return ast_complete_channels(line, word, pos, state, 2);
+}
+
+static struct ast_cli_entry cli_mixmonitor[] = {
+ { { "mixmonitor", NULL, NULL },
+ mixmonitor_cli, "Execute a MixMonitor command.",
+ "mixmonitor <start|stop> <chan_name> [args]\n\n"
+ "The optional arguments are passed to the\n"
+ "MixMonitor application when the 'start' command is used.\n",
+ complete_mixmonitor_cli },
+};
+
+static int unload_module(void)
+{
+ int res;
+
+ ast_cli_unregister_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
+ res = ast_unregister_application(stop_app);
+ res |= ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ int res;
+
+ ast_cli_register_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
+ res = ast_register_application(app, mixmonitor_exec, synopsis, desc);
+ res |= ast_register_application(stop_app, stop_mixmonitor_exec, stop_synopsis, stop_desc);
+
+ return res;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");
diff --git a/apps/app_morsecode.c b/apps/app_morsecode.c
new file mode 100644
index 000000000..aec946a09
--- /dev/null
+++ b/apps/app_morsecode.c
@@ -0,0 +1,179 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (c) 2006, Tilghman Lesher. All rights reserved.
+ *
+ * Tilghman Lesher <app_morsecode__v001@the-tilghman.com>
+ *
+ * This code is released by the author with no restrictions on usage.
+ *
+ * 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.
+ *
+ */
+
+/*! \file
+ *
+ * \brief Morsecode application
+ *
+ * \author Tilghman Lesher <app_morsecode__v001@the-tilghman.com>
+ *
+ * \ingroup applications
+ */
+
+#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/options.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/indications.h"
+
+static char *app_morsecode = "Morsecode";
+
+static char *morsecode_synopsis = "Plays morse code";
+
+static char *morsecode_descrip =
+"Usage: Morsecode(<string>)\n"
+"Plays the Morse code equivalent of the passed string. If the variable\n"
+"MORSEDITLEN is set, it will use that value for the length (in ms) of the dit\n"
+"(defaults to 80). Additionally, if MORSETONE is set, it will use that tone\n"
+"(in Hz). The tone default is 800.\n";
+
+
+static char *morsecode[] = {
+ "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", /* 0-15 */
+ "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", /* 16-31 */
+ " ", /* 32 - <space> */
+ ".-.-.-", /* 33 - ! */
+ ".-..-.", /* 34 - " */
+ "", /* 35 - # */
+ "", /* 36 - $ */
+ "", /* 37 - % */
+ "", /* 38 - & */
+ ".----.", /* 39 - ' */
+ "-.--.-", /* 40 - ( */
+ "-.--.-", /* 41 - ) */
+ "", /* 42 - * */
+ "", /* 43 - + */
+ "--..--", /* 44 - , */
+ "-....-", /* 45 - - */
+ ".-.-.-", /* 46 - . */
+ "-..-.", /* 47 - / */
+ "-----", ".----", "..---", "...--", "....-", ".....", "-....", "--...", "---..", "----.", /* 48-57 - 0-9 */
+ "---...", /* 58 - : */
+ "-.-.-.", /* 59 - ; */
+ "", /* 60 - < */
+ "-...-", /* 61 - = */
+ "", /* 62 - > */
+ "..--..", /* 63 - ? */
+ ".--.-.", /* 64 - @ */
+ ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--",
+ "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..",
+ "-.--.-", /* 91 - [ (really '(') */
+ "-..-.", /* 92 - \ (really '/') */
+ "-.--.-", /* 93 - ] (really ')') */
+ "", /* 94 - ^ */
+ "..--.-", /* 95 - _ */
+ ".----.", /* 96 - ` */
+ ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--",
+ "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..",
+ "-.--.-", /* 123 - { (really '(') */
+ "", /* 124 - | */
+ "-.--.-", /* 125 - } (really ')') */
+ "-..-.", /* 126 - ~ (really bar) */
+ ". . .", /* 127 - <del> (error) */
+};
+
+static void playtone(struct ast_channel *chan, int tone, int len)
+{
+ char dtmf[20];
+ snprintf(dtmf, sizeof(dtmf), "%d/%d", tone, len);
+ ast_playtones_start(chan, 0, dtmf, 0);
+ ast_safe_sleep(chan, len);
+ ast_playtones_stop(chan);
+}
+
+static int morsecode_exec(struct ast_channel *chan, void *data)
+{
+ int res=0, ditlen, tone;
+ char *digit;
+ const char *ditlenc, *tonec;
+ struct ast_module_user *u;
+
+ u = ast_module_user_add(chan);
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "Syntax: Morsecode(<string>) - no argument found\n");
+ ast_module_user_remove(u);
+ return 0;
+ }
+
+ /* Use variable MORESEDITLEN, if set (else 80) */
+ ditlenc = pbx_builtin_getvar_helper(chan, "MORSEDITLEN");
+ if (ast_strlen_zero(ditlenc) || (sscanf(ditlenc, "%d", &ditlen) != 1)) {
+ ditlen = 80;
+ }
+
+ /* Use variable MORSETONE, if set (else 800) */
+ tonec = pbx_builtin_getvar_helper(chan, "MORSETONE");
+ if (ast_strlen_zero(tonec) || (sscanf(tonec, "%d", &tone) != 1)) {
+ tone = 800;
+ }
+
+ for (digit = data; *digit; digit++) {
+ int digit2 = *digit;
+ char *dahdit;
+ if (digit2 < 0) {
+ continue;
+ }
+ for (dahdit = morsecode[digit2]; *dahdit; dahdit++) {
+ if (*dahdit == '-') {
+ playtone(chan, tone, 3 * ditlen);
+ } else if (*dahdit == '.') {
+ playtone(chan, tone, 1 * ditlen);
+ } else {
+ /* Account for ditlen of silence immediately following */
+ playtone(chan, 0, 2 * ditlen);
+ }
+
+ /* Pause slightly between each dit and dah */
+ playtone(chan, 0, 1 * ditlen);
+ }
+ /* Pause between characters */
+ playtone(chan, 0, 2 * ditlen);
+ }
+
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app_morsecode);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app_morsecode, morsecode_exec, morsecode_synopsis, morsecode_descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Morse code");
diff --git a/apps/app_mp3.c b/apps/app_mp3.c
new file mode 100644
index 000000000..55d50f011
--- /dev/null
+++ b/apps/app_mp3.c
@@ -0,0 +1,255 @@
+/*
+ * 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 Silly application to play an MP3 file -- uses mpg123
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <string.h>
+#include <stdio.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/time.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/frame.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/options.h"
+
+#define LOCAL_MPG_123 "/usr/local/bin/mpg123"
+#define MPG_123 "/usr/bin/mpg123"
+
+static char *app = "MP3Player";
+
+static char *synopsis = "Play an MP3 file or stream";
+
+static char *descrip =
+" MP3Player(location) Executes mpg123 to play the given location,\n"
+"which typically would be a filename or a URL. User can exit by pressing\n"
+"any key on the dialpad, or by hanging up.";
+
+
+static int mp3play(char *filename, int fd)
+{
+ int res;
+ int x;
+ sigset_t fullset, oldset;
+
+ sigfillset(&fullset);
+ pthread_sigmask(SIG_BLOCK, &fullset, &oldset);
+
+ res = fork();
+ if (res < 0)
+ ast_log(LOG_WARNING, "Fork failed\n");
+ if (res) {
+ pthread_sigmask(SIG_SETMASK, &oldset, NULL);
+ return res;
+ }
+ if (ast_opt_high_priority)
+ ast_set_priority(0);
+ signal(SIGPIPE, SIG_DFL);
+ pthread_sigmask(SIG_UNBLOCK, &fullset, NULL);
+
+ dup2(fd, STDOUT_FILENO);
+ for (x=STDERR_FILENO + 1;x<256;x++) {
+ if (x != STDOUT_FILENO)
+ close(x);
+ }
+ /* Execute mpg123, but buffer if it's a net connection */
+ if (!strncasecmp(filename, "http://", 7)) {
+ /* Most commonly installed in /usr/local/bin */
+ execl(LOCAL_MPG_123, "mpg123", "-q", "-s", "-b", "1024", "-f", "8192", "--mono", "-r", "8000", filename, (char *)NULL);
+ /* But many places has it in /usr/bin */
+ execl(MPG_123, "mpg123", "-q", "-s", "-b", "1024","-f", "8192", "--mono", "-r", "8000", filename, (char *)NULL);
+ /* As a last-ditch effort, try to use PATH */
+ execlp("mpg123", "mpg123", "-q", "-s", "-b", "1024", "-f", "8192", "--mono", "-r", "8000", filename, (char *)NULL);
+ }
+ else {
+ /* Most commonly installed in /usr/local/bin */
+ execl(MPG_123, "mpg123", "-q", "-s", "-f", "8192", "--mono", "-r", "8000", filename, (char *)NULL);
+ /* But many places has it in /usr/bin */
+ execl(LOCAL_MPG_123, "mpg123", "-q", "-s", "-f", "8192", "--mono", "-r", "8000", filename, (char *)NULL);
+ /* As a last-ditch effort, try to use PATH */
+ execlp("mpg123", "mpg123", "-q", "-s", "-f", "8192", "--mono", "-r", "8000", filename, (char *)NULL);
+ }
+ ast_log(LOG_WARNING, "Execute of mpg123 failed\n");
+ _exit(0);
+}
+
+static int timed_read(int fd, void *data, int datalen, int timeout)
+{
+ int res;
+ struct pollfd fds[1];
+ fds[0].fd = fd;
+ fds[0].events = POLLIN;
+ res = poll(fds, 1, timeout);
+ if (res < 1) {
+ ast_log(LOG_NOTICE, "Poll timed out/errored out with %d\n", res);
+ return -1;
+ }
+ return read(fd, data, datalen);
+
+}
+
+static int mp3_exec(struct ast_channel *chan, void *data)
+{
+ int res=0;
+ struct ast_module_user *u;
+ int fds[2];
+ int ms = -1;
+ int pid = -1;
+ int owriteformat;
+ int timeout = 2000;
+ struct timeval next;
+ struct ast_frame *f;
+ struct myframe {
+ struct ast_frame f;
+ char offset[AST_FRIENDLY_OFFSET];
+ short frdata[160];
+ } myf;
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "MP3 Playback requires an argument (filename)\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ if (pipe(fds)) {
+ ast_log(LOG_WARNING, "Unable to create pipe\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ ast_stopstream(chan);
+
+ owriteformat = chan->writeformat;
+ res = ast_set_write_format(chan, AST_FORMAT_SLINEAR);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ res = mp3play((char *)data, fds[1]);
+ if (!strncasecmp((char *)data, "http://", 7)) {
+ timeout = 10000;
+ }
+ /* Wait 1000 ms first */
+ next = ast_tvnow();
+ next.tv_sec += 1;
+ if (res >= 0) {
+ pid = res;
+ /* Order is important -- there's almost always going to be mp3... we want to prioritize the
+ user */
+ for (;;) {
+ ms = ast_tvdiff_ms(next, ast_tvnow());
+ if (ms <= 0) {
+ res = timed_read(fds[0], myf.frdata, sizeof(myf.frdata), timeout);
+ if (res > 0) {
+ myf.f.frametype = AST_FRAME_VOICE;
+ myf.f.subclass = AST_FORMAT_SLINEAR;
+ myf.f.datalen = res;
+ myf.f.samples = res / 2;
+ myf.f.mallocd = 0;
+ myf.f.offset = AST_FRIENDLY_OFFSET;
+ myf.f.src = __PRETTY_FUNCTION__;
+ myf.f.delivery.tv_sec = 0;
+ myf.f.delivery.tv_usec = 0;
+ myf.f.data = myf.frdata;
+ if (ast_write(chan, &myf.f) < 0) {
+ res = -1;
+ break;
+ }
+ } else {
+ ast_log(LOG_DEBUG, "No more mp3\n");
+ res = 0;
+ break;
+ }
+ next = ast_tvadd(next, ast_samp2tv(myf.f.samples, 8000));
+ } else {
+ ms = ast_waitfor(chan, ms);
+ if (ms < 0) {
+ ast_log(LOG_DEBUG, "Hangup detected\n");
+ res = -1;
+ break;
+ }
+ if (ms) {
+ f = ast_read(chan);
+ if (!f) {
+ ast_log(LOG_DEBUG, "Null frame == hangup() detected\n");
+ res = -1;
+ break;
+ }
+ if (f->frametype == AST_FRAME_DTMF) {
+ ast_log(LOG_DEBUG, "User pressed a key\n");
+ ast_frfree(f);
+ res = 0;
+ break;
+ }
+ ast_frfree(f);
+ }
+ }
+ }
+ }
+ close(fds[0]);
+ close(fds[1]);
+
+ if (pid > -1)
+ kill(pid, SIGKILL);
+ if (!res && owriteformat)
+ ast_set_write_format(chan, owriteformat);
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, mp3_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Silly MP3 Application");
diff --git a/apps/app_nbscat.c b/apps/app_nbscat.c
new file mode 100644
index 000000000..5f3000404
--- /dev/null
+++ b/apps/app_nbscat.c
@@ -0,0 +1,237 @@
+/*
+ * 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 Silly application to play an NBScat file -- uses nbscat8k
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <string.h>
+#include <stdio.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/frame.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/options.h"
+
+#define LOCAL_NBSCAT "/usr/local/bin/nbscat8k"
+#define NBSCAT "/usr/bin/nbscat8k"
+
+#ifndef AF_LOCAL
+#define AF_LOCAL AF_UNIX
+#endif
+
+static char *app = "NBScat";
+
+static char *synopsis = "Play an NBS local stream";
+
+static char *descrip =
+" NBScat: Executes nbscat to listen to the local NBS stream.\n"
+"User can exit by pressing any key\n.";
+
+
+static int NBScatplay(int fd)
+{
+ int res;
+ int x;
+ sigset_t fullset, oldset;
+
+ sigfillset(&fullset);
+ pthread_sigmask(SIG_BLOCK, &fullset, &oldset);
+
+ res = fork();
+ if (res < 0)
+ ast_log(LOG_WARNING, "Fork failed\n");
+ if (res) {
+ pthread_sigmask(SIG_SETMASK, &oldset, NULL);
+ return res;
+ }
+ signal(SIGPIPE, SIG_DFL);
+ pthread_sigmask(SIG_UNBLOCK, &fullset, NULL);
+
+ if (ast_opt_high_priority)
+ ast_set_priority(0);
+
+ dup2(fd, STDOUT_FILENO);
+ for (x = STDERR_FILENO + 1; x < 1024; x++) {
+ if (x != STDOUT_FILENO)
+ close(x);
+ }
+ /* Most commonly installed in /usr/local/bin */
+ execl(NBSCAT, "nbscat8k", "-d", (char *)NULL);
+ execl(LOCAL_NBSCAT, "nbscat8k", "-d", (char *)NULL);
+ ast_log(LOG_WARNING, "Execute of nbscat8k failed\n");
+ _exit(0);
+}
+
+static int timed_read(int fd, void *data, int datalen)
+{
+ int res;
+ struct pollfd fds[1];
+ fds[0].fd = fd;
+ fds[0].events = POLLIN;
+ res = poll(fds, 1, 2000);
+ if (res < 1) {
+ ast_log(LOG_NOTICE, "Selected timed out/errored out with %d\n", res);
+ return -1;
+ }
+ return read(fd, data, datalen);
+
+}
+
+static int NBScat_exec(struct ast_channel *chan, void *data)
+{
+ int res=0;
+ struct ast_module_user *u;
+ int fds[2];
+ int ms = -1;
+ int pid = -1;
+ int owriteformat;
+ struct timeval next;
+ struct ast_frame *f;
+ struct myframe {
+ struct ast_frame f;
+ char offset[AST_FRIENDLY_OFFSET];
+ short frdata[160];
+ } myf;
+
+ u = ast_module_user_add(chan);
+
+ if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fds)) {
+ ast_log(LOG_WARNING, "Unable to create socketpair\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ ast_stopstream(chan);
+
+ owriteformat = chan->writeformat;
+ res = ast_set_write_format(chan, AST_FORMAT_SLINEAR);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ res = NBScatplay(fds[1]);
+ /* Wait 1000 ms first */
+ next = ast_tvnow();
+ next.tv_sec += 1;
+ if (res >= 0) {
+ pid = res;
+ /* Order is important -- there's almost always going to be mp3... we want to prioritize the
+ user */
+ for (;;) {
+ ms = ast_tvdiff_ms(next, ast_tvnow());
+ if (ms <= 0) {
+ res = timed_read(fds[0], myf.frdata, sizeof(myf.frdata));
+ if (res > 0) {
+ myf.f.frametype = AST_FRAME_VOICE;
+ myf.f.subclass = AST_FORMAT_SLINEAR;
+ myf.f.datalen = res;
+ myf.f.samples = res / 2;
+ myf.f.mallocd = 0;
+ myf.f.offset = AST_FRIENDLY_OFFSET;
+ myf.f.src = __PRETTY_FUNCTION__;
+ myf.f.delivery.tv_sec = 0;
+ myf.f.delivery.tv_usec = 0;
+ myf.f.data = myf.frdata;
+ if (ast_write(chan, &myf.f) < 0) {
+ res = -1;
+ break;
+ }
+ } else {
+ ast_log(LOG_DEBUG, "No more mp3\n");
+ res = 0;
+ break;
+ }
+ next = ast_tvadd(next, ast_samp2tv(myf.f.samples, 8000));
+ } else {
+ ms = ast_waitfor(chan, ms);
+ if (ms < 0) {
+ ast_log(LOG_DEBUG, "Hangup detected\n");
+ res = -1;
+ break;
+ }
+ if (ms) {
+ f = ast_read(chan);
+ if (!f) {
+ ast_log(LOG_DEBUG, "Null frame == hangup() detected\n");
+ res = -1;
+ break;
+ }
+ if (f->frametype == AST_FRAME_DTMF) {
+ ast_log(LOG_DEBUG, "User pressed a key\n");
+ ast_frfree(f);
+ res = 0;
+ break;
+ }
+ ast_frfree(f);
+ }
+ }
+ }
+ }
+ close(fds[0]);
+ close(fds[1]);
+
+ if (pid > -1)
+ kill(pid, SIGKILL);
+ if (!res && owriteformat)
+ ast_set_write_format(chan, owriteformat);
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, NBScat_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Silly NBS Stream Application");
diff --git a/apps/app_osplookup.c b/apps/app_osplookup.c
new file mode 100644
index 000000000..ad2ce5065
--- /dev/null
+++ b/apps/app_osplookup.c
@@ -0,0 +1,1677 @@
+/*
+ * 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 Open Settlement Protocol (OSP) Applications
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+ <depend>osptk</depend>
+ <depend>ssl</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <osp/osp.h>
+#include <osp/osputils.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/config.h"
+#include "asterisk/utils.h"
+#include "asterisk/causes.h"
+#include "asterisk/channel.h"
+#include "asterisk/app.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/options.h"
+#include "asterisk/cli.h"
+#include "asterisk/logger.h"
+#include "asterisk/astosp.h"
+
+/* OSP Buffer Sizes */
+#define OSP_INTSTR_SIZE ((unsigned int)16) /* OSP signed/unsigned int string buffer size */
+#define OSP_NORSTR_SIZE ((unsigned int)256) /* OSP normal string buffer size */
+#define OSP_TOKSTR_SIZE ((unsigned int)4096) /* OSP token string buffer size */
+
+/* OSP Constants */
+#define OSP_INVALID_HANDLE ((int)-1) /* Invalid OSP handle, provider, transaction etc. */
+#define OSP_CONFIG_FILE ((const char*)"osp.conf") /* OSP configuration file name */
+#define OSP_GENERAL_CAT ((const char*)"general") /* OSP global configuration context name */
+#define OSP_DEF_PROVIDER ((const char*)"default") /* OSP default provider context name */
+#define OSP_MAX_CERTS ((unsigned int)10) /* OSP max number of cacerts */
+#define OSP_MAX_SRVS ((unsigned int)10) /* OSP max number of service points */
+#define OSP_DEF_MAXCONNECTIONS ((unsigned int)20) /* OSP default max_connections */
+#define OSP_MIN_MAXCONNECTIONS ((unsigned int)1) /* OSP min max_connections */
+#define OSP_MAX_MAXCONNECTIONS ((unsigned int)1000) /* OSP max max_connections */
+#define OSP_DEF_RETRYDELAY ((unsigned int)0) /* OSP default retry delay */
+#define OSP_MIN_RETRYDELAY ((unsigned int)0) /* OSP min retry delay */
+#define OSP_MAX_RETRYDELAY ((unsigned int)10) /* OSP max retry delay */
+#define OSP_DEF_RETRYLIMIT ((unsigned int)2) /* OSP default retry times */
+#define OSP_MIN_RETRYLIMIT ((unsigned int)0) /* OSP min retry times */
+#define OSP_MAX_RETRYLIMIT ((unsigned int)100) /* OSP max retry times */
+#define OSP_DEF_TIMEOUT ((unsigned int)500) /* OSP default timeout in ms */
+#define OSP_MIN_TIMEOUT ((unsigned int)200) /* OSP min timeout in ms */
+#define OSP_MAX_TIMEOUT ((unsigned int)10000) /* OSP max timeout in ms */
+#define OSP_DEF_AUTHPOLICY ((enum osp_authpolicy)OSP_AUTH_YES)
+#define OSP_AUDIT_URL ((const char*)"localhost") /* OSP default Audit URL */
+#define OSP_LOCAL_VALIDATION ((int)1) /* Validate OSP token locally */
+#define OSP_SSL_LIFETIME ((unsigned int)300) /* SSL life time, in seconds */
+#define OSP_HTTP_PERSISTENCE ((int)1) /* In seconds */
+#define OSP_CUSTOMER_ID ((const char*)"") /* OSP customer ID */
+#define OSP_DEVICE_ID ((const char*)"") /* OSP device ID */
+#define OSP_DEF_DESTINATIONS ((unsigned int)5) /* OSP default max number of destinations */
+#define OSP_DEF_TIMELIMIT ((unsigned int)0) /* OSP default duration limit, no limit */
+
+/* OSP Authentication Policy */
+enum osp_authpolicy {
+ OSP_AUTH_NO, /* Accept any call */
+ OSP_AUTH_YES, /* Accept call with valid OSP token or without OSP token */
+ OSP_AUTH_EXCLUSIVE /* Only accept call with valid OSP token */
+};
+
+/* OSP Provider */
+struct osp_provider {
+ char name[OSP_NORSTR_SIZE]; /* OSP provider context name */
+ char privatekey[OSP_NORSTR_SIZE]; /* OSP private key file name */
+ char localcert[OSP_NORSTR_SIZE]; /* OSP local cert file name */
+ unsigned int cacount; /* Number of cacerts */
+ char cacerts[OSP_MAX_CERTS][OSP_NORSTR_SIZE]; /* Cacert file names */
+ unsigned int spcount; /* Number of service points */
+ char srvpoints[OSP_MAX_SRVS][OSP_NORSTR_SIZE]; /* Service point URLs */
+ int maxconnections; /* Max number of connections */
+ int retrydelay; /* Retry delay */
+ int retrylimit; /* Retry limit */
+ int timeout; /* Timeout in ms */
+ char source[OSP_NORSTR_SIZE]; /* IP of self */
+ enum osp_authpolicy authpolicy; /* OSP authentication policy */
+ OSPTPROVHANDLE handle; /* OSP provider handle */
+ struct osp_provider* next; /* Pointer to next OSP provider */
+};
+
+/* OSP Application In/Output Results */
+struct osp_result {
+ int inhandle; /* Inbound transaction handle */
+ int outhandle; /* Outbound transaction handle */
+ unsigned int intimelimit; /* Inbound duration limit */
+ unsigned int outtimelimit; /* Outbound duration limit */
+ char tech[20]; /* Asterisk TECH string */
+ char dest[OSP_NORSTR_SIZE]; /* Destination in called@IP format */
+ char calling[OSP_NORSTR_SIZE]; /* Calling number, may be translated */
+ char token[OSP_TOKSTR_SIZE]; /* Outbound OSP token */
+ unsigned int numresults; /* Number of remain destinations */
+};
+
+/* OSP Module Global Variables */
+AST_MUTEX_DEFINE_STATIC(osplock); /* Lock of OSP provider list */
+static int osp_initialized = 0; /* Init flag */
+static int osp_hardware = 0; /* Hardware accelleration flag */
+static struct osp_provider* ospproviders = NULL; /* OSP provider list */
+static unsigned int osp_tokenformat = TOKEN_ALGO_SIGNED; /* Token format supported */
+
+/* OSP Client Wrapper APIs */
+
+/*!
+ * \brief Create OSP provider handle according to configuration
+ * \param cfg OSP configuration
+ * \param provider OSP provider context name
+ * \return 1 Success, 0 Failed, -1 Error
+ */
+static int osp_create_provider(struct ast_config* cfg, const char* provider)
+{
+ int res;
+ unsigned int t, i, j;
+ struct osp_provider* p;
+ struct ast_variable* v;
+ OSPTPRIVATEKEY privatekey;
+ OSPTCERT localcert;
+ const char* psrvpoints[OSP_MAX_SRVS];
+ OSPTCERT cacerts[OSP_MAX_CERTS];
+ const OSPTCERT* pcacerts[OSP_MAX_CERTS];
+ int error = OSPC_ERR_NO_ERROR;
+
+ if (!(p = ast_calloc(1, sizeof(*p)))) {
+ ast_log(LOG_ERROR, "Out of memory\n");
+ return -1;
+ }
+
+ ast_copy_string(p->name, provider, sizeof(p->name));
+ snprintf(p->privatekey, sizeof(p->privatekey), "%s/%s-privatekey.pem", ast_config_AST_KEY_DIR, provider);
+ snprintf(p->localcert, sizeof(p->localcert), "%s/%s-localcert.pem", ast_config_AST_KEY_DIR, provider);
+ p->maxconnections = OSP_DEF_MAXCONNECTIONS;
+ p->retrydelay = OSP_DEF_RETRYDELAY;
+ p->retrylimit = OSP_DEF_RETRYLIMIT;
+ p->timeout = OSP_DEF_TIMEOUT;
+ p->authpolicy = OSP_DEF_AUTHPOLICY;
+ p->handle = OSP_INVALID_HANDLE;
+
+ v = ast_variable_browse(cfg, provider);
+ while(v) {
+ if (!strcasecmp(v->name, "privatekey")) {
+ if (v->value[0] == '/') {
+ ast_copy_string(p->privatekey, v->value, sizeof(p->privatekey));
+ } else {
+ snprintf(p->privatekey, sizeof(p->privatekey), "%s/%s", ast_config_AST_KEY_DIR, v->value);
+ }
+ ast_log(LOG_DEBUG, "OSP: privatekey '%s'\n", p->privatekey);
+ } else if (!strcasecmp(v->name, "localcert")) {
+ if (v->value[0] == '/') {
+ ast_copy_string(p->localcert, v->value, sizeof(p->localcert));
+ } else {
+ snprintf(p->localcert, sizeof(p->localcert), "%s/%s", ast_config_AST_KEY_DIR, v->value);
+ }
+ ast_log(LOG_DEBUG, "OSP: localcert '%s'\n", p->localcert);
+ } else if (!strcasecmp(v->name, "cacert")) {
+ if (p->cacount < OSP_MAX_CERTS) {
+ if (v->value[0] == '/') {
+ ast_copy_string(p->cacerts[p->cacount], v->value, sizeof(p->cacerts[0]));
+ } else {
+ snprintf(p->cacerts[p->cacount], sizeof(p->cacerts[0]), "%s/%s", ast_config_AST_KEY_DIR, v->value);
+ }
+ ast_log(LOG_DEBUG, "OSP: cacert[%d]: '%s'\n", p->cacount, p->cacerts[p->cacount]);
+ p->cacount++;
+ } else {
+ ast_log(LOG_WARNING, "OSP: Too many CA Certificates at line %d\n", v->lineno);
+ }
+ } else if (!strcasecmp(v->name, "servicepoint")) {
+ if (p->spcount < OSP_MAX_SRVS) {
+ ast_copy_string(p->srvpoints[p->spcount], v->value, sizeof(p->srvpoints[0]));
+ ast_log(LOG_DEBUG, "OSP: servicepoint[%d]: '%s'\n", p->spcount, p->srvpoints[p->spcount]);
+ p->spcount++;
+ } else {
+ ast_log(LOG_WARNING, "OSP: Too many Service Points at line %d\n", v->lineno);
+ }
+ } else if (!strcasecmp(v->name, "maxconnections")) {
+ if ((sscanf(v->value, "%d", &t) == 1) && (t >= OSP_MIN_MAXCONNECTIONS) && (t <= OSP_MAX_MAXCONNECTIONS)) {
+ p->maxconnections = t;
+ ast_log(LOG_DEBUG, "OSP: maxconnections '%d'\n", t);
+ } else {
+ ast_log(LOG_WARNING, "OSP: maxconnections should be an integer from %d to %d, not '%s' at line %d\n",
+ OSP_MIN_MAXCONNECTIONS, OSP_MAX_MAXCONNECTIONS, v->value, v->lineno);
+ }
+ } else if (!strcasecmp(v->name, "retrydelay")) {
+ if ((sscanf(v->value, "%d", &t) == 1) && (t >= OSP_MIN_RETRYDELAY) && (t <= OSP_MAX_RETRYDELAY)) {
+ p->retrydelay = t;
+ ast_log(LOG_DEBUG, "OSP: retrydelay '%d'\n", t);
+ } else {
+ ast_log(LOG_WARNING, "OSP: retrydelay should be an integer from %d to %d, not '%s' at line %d\n",
+ OSP_MIN_RETRYDELAY, OSP_MAX_RETRYDELAY, v->value, v->lineno);
+ }
+ } else if (!strcasecmp(v->name, "retrylimit")) {
+ if ((sscanf(v->value, "%d", &t) == 1) && (t >= OSP_MIN_RETRYLIMIT) && (t <= OSP_MAX_RETRYLIMIT)) {
+ p->retrylimit = t;
+ ast_log(LOG_DEBUG, "OSP: retrylimit '%d'\n", t);
+ } else {
+ ast_log(LOG_WARNING, "OSP: retrylimit should be an integer from %d to %d, not '%s' at line %d\n",
+ OSP_MIN_RETRYLIMIT, OSP_MAX_RETRYLIMIT, v->value, v->lineno);
+ }
+ } else if (!strcasecmp(v->name, "timeout")) {
+ if ((sscanf(v->value, "%d", &t) == 1) && (t >= OSP_MIN_TIMEOUT) && (t <= OSP_MAX_TIMEOUT)) {
+ p->timeout = t;
+ ast_log(LOG_DEBUG, "OSP: timeout '%d'\n", t);
+ } else {
+ ast_log(LOG_WARNING, "OSP: timeout should be an integer from %d to %d, not '%s' at line %d\n",
+ OSP_MIN_TIMEOUT, OSP_MAX_TIMEOUT, v->value, v->lineno);
+ }
+ } else if (!strcasecmp(v->name, "source")) {
+ ast_copy_string(p->source, v->value, sizeof(p->source));
+ ast_log(LOG_DEBUG, "OSP: source '%s'\n", p->source);
+ } else if (!strcasecmp(v->name, "authpolicy")) {
+ if ((sscanf(v->value, "%d", &t) == 1) && ((t == OSP_AUTH_NO) || (t == OSP_AUTH_YES) || (t == OSP_AUTH_EXCLUSIVE))) {
+ p->authpolicy = t;
+ ast_log(LOG_DEBUG, "OSP: authpolicy '%d'\n", t);
+ } else {
+ ast_log(LOG_WARNING, "OSP: authpolicy should be %d, %d or %d, not '%s' at line %d\n",
+ OSP_AUTH_NO, OSP_AUTH_YES, OSP_AUTH_EXCLUSIVE, v->value, v->lineno);
+ }
+ }
+ v = v->next;
+ }
+
+ error = OSPPUtilLoadPEMPrivateKey((unsigned char *) p->privatekey, &privatekey);
+ if (error != OSPC_ERR_NO_ERROR) {
+ ast_log(LOG_WARNING, "OSP: Unable to load privatekey '%s', error '%d'\n", p->privatekey, error);
+ free(p);
+ return 0;
+ }
+
+ error = OSPPUtilLoadPEMCert((unsigned char *) p->localcert, &localcert);
+ if (error != OSPC_ERR_NO_ERROR) {
+ ast_log(LOG_WARNING, "OSP: Unable to load localcert '%s', error '%d'\n", p->localcert, error);
+ if (privatekey.PrivateKeyData) {
+ free(privatekey.PrivateKeyData);
+ }
+ free(p);
+ return 0;
+ }
+
+ if (p->cacount < 1) {
+ snprintf(p->cacerts[p->cacount], sizeof(p->cacerts[0]), "%s/%s-cacert.pem", ast_config_AST_KEY_DIR, provider);
+ ast_log(LOG_DEBUG, "OSP: cacert[%d]: '%s'\n", p->cacount, p->cacerts[p->cacount]);
+ p->cacount++;
+ }
+ for (i = 0; i < p->cacount; i++) {
+ error = OSPPUtilLoadPEMCert((unsigned char *) p->cacerts[i], &cacerts[i]);
+ if (error != OSPC_ERR_NO_ERROR) {
+ ast_log(LOG_WARNING, "OSP: Unable to load cacert '%s', error '%d'\n", p->cacerts[i], error);
+ for (j = 0; j < i; j++) {
+ if (cacerts[j].CertData) {
+ free(cacerts[j].CertData);
+ }
+ }
+ if (localcert.CertData) {
+ free(localcert.CertData);
+ }
+ if (privatekey.PrivateKeyData) {
+ free(privatekey.PrivateKeyData);
+ }
+ free(p);
+ return 0;
+ }
+ pcacerts[i] = &cacerts[i];
+ }
+
+ for (i = 0; i < p->spcount; i++) {
+ psrvpoints[i] = p->srvpoints[i];
+ }
+
+ error = OSPPProviderNew(p->spcount, psrvpoints, NULL, OSP_AUDIT_URL, &privatekey, &localcert, p->cacount, pcacerts, OSP_LOCAL_VALIDATION,
+ OSP_SSL_LIFETIME, p->maxconnections, OSP_HTTP_PERSISTENCE, p->retrydelay, p->retrylimit,p->timeout, OSP_CUSTOMER_ID,
+ OSP_DEVICE_ID, &p->handle);
+ if (error != OSPC_ERR_NO_ERROR) {
+ ast_log(LOG_WARNING, "OSP: Unable to create provider '%s', error '%d'\n", provider, error);
+ free(p);
+ res = -1;
+ } else {
+ ast_log(LOG_DEBUG, "OSP: provider '%s'\n", provider);
+ ast_mutex_lock(&osplock);
+ p->next = ospproviders;
+ ospproviders = p;
+ ast_mutex_unlock(&osplock);
+ res = 1;
+ }
+
+ for (i = 0; i < p->cacount; i++) {
+ if (cacerts[i].CertData) {
+ free(cacerts[i].CertData);
+ }
+ }
+ if (localcert.CertData) {
+ free(localcert.CertData);
+ }
+ if (privatekey.PrivateKeyData) {
+ free(privatekey.PrivateKeyData);
+ }
+
+ return res;
+}
+
+/*!
+ * \brief Get OSP authenticiation policy of provider
+ * \param provider OSP provider context name
+ * \param policy OSP authentication policy, output
+ * \return 1 Success, 0 Failed, -1 Error
+ */
+static int osp_get_policy(const char* provider, int* policy)
+{
+ int res = 0;
+ struct osp_provider* p;
+
+ ast_mutex_lock(&osplock);
+ p = ospproviders;
+ while(p) {
+ if (!strcasecmp(p->name, provider)) {
+ *policy = p->authpolicy;
+ ast_log(LOG_DEBUG, "OSP: authpolicy '%d'\n", *policy);
+ res = 1;
+ break;
+ }
+ p = p->next;
+ }
+ ast_mutex_unlock(&osplock);
+
+ return res;
+}
+
+/*!
+ * \brief Create OSP transaction handle
+ * \param provider OSP provider context name
+ * \param transaction OSP transaction handle, output
+ * \param sourcesize Size of source buffer, in/output
+ * \param source Source of provider, output
+ * \return 1 Success, 0 Failed, -1 Error
+ */
+static int osp_create_transaction(const char* provider, int* transaction, unsigned int sourcesize, char* source)
+{
+ int res = 0;
+ struct osp_provider* p;
+ int error;
+
+ ast_mutex_lock(&osplock);
+ p = ospproviders;
+ while(p) {
+ if (!strcasecmp(p->name, provider)) {
+ error = OSPPTransactionNew(p->handle, transaction);
+ if (error == OSPC_ERR_NO_ERROR) {
+ ast_log(LOG_DEBUG, "OSP: transaction '%d'\n", *transaction);
+ ast_copy_string(source, p->source, sourcesize);
+ ast_log(LOG_DEBUG, "OSP: source '%s'\n", source);
+ res = 1;
+ } else {
+ *transaction = OSP_INVALID_HANDLE;
+ ast_log(LOG_DEBUG, "OSP: Unable to create transaction handle, error '%d'\n", error);
+ res = -1;
+ }
+ break;
+ }
+ p = p->next;
+ }
+ ast_mutex_unlock(&osplock);
+
+ return res;
+}
+
+/*!
+ * \brief Convert address to "[x.x.x.x]" or "host.domain" format
+ * \param src Source address string
+ * \param dst Destination address string
+ * \param buffersize Size of dst buffer
+ */
+static void osp_convert_address(
+ const char* src,
+ char* dst,
+ int buffersize)
+{
+ struct in_addr inp;
+
+ if (inet_aton(src, &inp) != 0) {
+ snprintf(dst, buffersize, "[%s]", src);
+ } else {
+ snprintf(dst, buffersize, "%s", src);
+ }
+}
+
+/*!
+ * \brief Validate OSP token of inbound call
+ * \param transaction OSP transaction handle
+ * \param source Source of inbound call
+ * \param dest Destination of inbound call
+ * \param calling Calling number
+ * \param called Called number
+ * \param token OSP token, may be empty
+ * \param timelimit Call duration limit, output
+ * \return 1 Success, 0 Failed, -1 Error
+ */
+static int osp_validate_token(int transaction, const char* source, const char* dest, const char* calling, const char* called, const char* token, unsigned int* timelimit)
+{
+ int res;
+ int tokenlen;
+ unsigned char tokenstr[OSP_TOKSTR_SIZE];
+ char src[OSP_NORSTR_SIZE];
+ char dst[OSP_NORSTR_SIZE];
+ unsigned int authorised;
+ unsigned int dummy = 0;
+ int error;
+
+ tokenlen = ast_base64decode(tokenstr, token, strlen(token));
+ osp_convert_address(source, src, sizeof(src));
+ osp_convert_address(dest, dst, sizeof(dst));
+ error = OSPPTransactionValidateAuthorisation(
+ transaction,
+ src, dst, NULL, NULL,
+ calling ? calling : "", OSPC_E164,
+ called, OSPC_E164,
+ 0, NULL,
+ tokenlen, (char *) tokenstr,
+ &authorised,
+ timelimit,
+ &dummy, NULL,
+ osp_tokenformat);
+ if (error != OSPC_ERR_NO_ERROR) {
+ ast_log(LOG_DEBUG, "OSP: Unable to validate inbound token\n");
+ res = -1;
+ } else if (authorised) {
+ ast_log(LOG_DEBUG, "OSP: Authorised\n");
+ res = 1;
+ } else {
+ ast_log(LOG_DEBUG, "OSP: Unauthorised\n");
+ res = 0;
+ }
+
+ return res;
+}
+
+/*!
+ * \brief Choose min duration limit
+ * \param in Inbound duration limit
+ * \param out Outbound duration limit
+ * \return min duration limit
+ */
+static unsigned int osp_choose_timelimit(unsigned int in, unsigned int out)
+{
+ if (in == OSP_DEF_TIMELIMIT) {
+ return out;
+ } else if (out == OSP_DEF_TIMELIMIT) {
+ return in;
+ } else {
+ return in < out ? in : out;
+ }
+}
+
+/*!
+ * \brief Choose min duration limit
+ * \param called Called number
+ * \param calling Calling number
+ * \param destination Destination IP in '[x.x.x.x]' format
+ * \param tokenlen OSP token length
+ * \param token OSP token
+ * \param reason Failure reason, output
+ * \param result OSP lookup results, in/output
+ * \return 1 Success, 0 Failed, -1 Error
+ */
+static int osp_check_destination(const char* called, const char* calling, char* destination, unsigned int tokenlen, const char* token, enum OSPEFAILREASON* reason, struct osp_result* result)
+{
+ int res;
+ OSPE_DEST_OSP_ENABLED enabled;
+ OSPE_DEST_PROT protocol;
+ int error;
+
+ if (strlen(destination) <= 2) {
+ ast_log(LOG_DEBUG, "OSP: Wrong destination format '%s'\n", destination);
+ *reason = OSPC_FAIL_NORMAL_UNSPECIFIED;
+ return -1;
+ }
+
+ if ((error = OSPPTransactionIsDestOSPEnabled(result->outhandle, &enabled)) != OSPC_ERR_NO_ERROR) {
+ ast_log(LOG_DEBUG, "OSP: Unable to get destination OSP version, error '%d'\n", error);
+ *reason = OSPC_FAIL_NORMAL_UNSPECIFIED;
+ return -1;
+ }
+
+ if (enabled == OSPE_OSP_FALSE) {
+ result->token[0] = '\0';
+ } else {
+ ast_base64encode(result->token, (const unsigned char *) token, tokenlen, sizeof(result->token) - 1);
+ }
+
+ if ((error = OSPPTransactionGetDestProtocol(result->outhandle, &protocol)) != OSPC_ERR_NO_ERROR) {
+ ast_log(LOG_DEBUG, "OSP: Unable to get destination protocol, error '%d'\n", error);
+ *reason = OSPC_FAIL_NORMAL_UNSPECIFIED;
+ result->token[0] = '\0';
+ return -1;
+ }
+
+ res = 1;
+ /* Strip leading and trailing brackets */
+ destination[strlen(destination) - 1] = '\0';
+ switch(protocol) {
+ case OSPE_DEST_PROT_H323_SETUP:
+ ast_log(LOG_DEBUG, "OSP: protocol '%d'\n", protocol);
+ ast_copy_string(result->tech, "H323", sizeof(result->tech));
+ snprintf(result->dest, sizeof(result->dest), "%s@%s", called, destination + 1);
+ ast_copy_string(result->calling, calling, sizeof(result->calling));
+ break;
+ case OSPE_DEST_PROT_SIP:
+ ast_log(LOG_DEBUG, "OSP: protocol '%d'\n", protocol);
+ ast_copy_string(result->tech, "SIP", sizeof(result->tech));
+ snprintf(result->dest, sizeof(result->dest), "%s@%s", called, destination + 1);
+ ast_copy_string(result->calling, calling, sizeof(result->calling));
+ break;
+ case OSPE_DEST_PROT_IAX:
+ ast_log(LOG_DEBUG, "OSP: protocol '%d'\n", protocol);
+ ast_copy_string(result->tech, "IAX", sizeof(result->tech));
+ snprintf(result->dest, sizeof(result->dest), "%s@%s", called, destination + 1);
+ ast_copy_string(result->calling, calling, sizeof(result->calling));
+ break;
+ default:
+ ast_log(LOG_DEBUG, "OSP: Unknown protocol '%d'\n", protocol);
+ *reason = OSPC_FAIL_PROTOCOL_ERROR;
+ result->token[0] = '\0';
+ res = 0;
+ }
+
+ return res;
+}
+
+/*!
+ * \brief Convert Asterisk status to TC code
+ * \param cause Asterisk hangup cause
+ * \return OSP TC code
+ */
+static enum OSPEFAILREASON asterisk2osp(int cause)
+{
+ return (enum OSPEFAILREASON)cause;
+}
+
+/*!
+ * \brief OSP Authentication function
+ * \param provider OSP provider context name
+ * \param transaction OSP transaction handle, output
+ * \param source Source of inbound call
+ * \param calling Calling number
+ * \param called Called number
+ * \param token OSP token, may be empty
+ * \param timelimit Call duration limit, output
+ * \return 1 Authenricated, 0 Unauthenticated, -1 Error
+ */
+static int osp_auth(const char* provider, int* transaction, const char* source, const char* calling, const char* called, const char* token, unsigned int* timelimit)
+{
+ int res;
+ int policy = OSP_AUTH_YES;
+ char dest[OSP_NORSTR_SIZE];
+
+ *transaction = OSP_INVALID_HANDLE;
+ *timelimit = OSP_DEF_TIMELIMIT;
+ res = osp_get_policy(provider, &policy);
+ if (!res) {
+ ast_log(LOG_DEBUG, "OSP: Unabe to find OSP authentication policy\n");
+ return res;
+ }
+
+ switch (policy) {
+ case OSP_AUTH_NO:
+ res = 1;
+ break;
+ case OSP_AUTH_EXCLUSIVE:
+ if (ast_strlen_zero(token)) {
+ res = 0;
+ } else if ((res = osp_create_transaction(provider, transaction, sizeof(dest), dest)) <= 0) {
+ ast_log(LOG_DEBUG, "OSP: Unable to generate transaction handle\n");
+ *transaction = OSP_INVALID_HANDLE;
+ res = 0;
+ } else if((res = osp_validate_token(*transaction, source, dest, calling, called, token, timelimit)) <= 0) {
+ OSPPTransactionRecordFailure(*transaction, OSPC_FAIL_CALL_REJECTED);
+ }
+ break;
+ case OSP_AUTH_YES:
+ default:
+ if (ast_strlen_zero(token)) {
+ res = 1;
+ } else if ((res = osp_create_transaction(provider, transaction, sizeof(dest), dest)) <= 0) {
+ ast_log(LOG_DEBUG, "OSP: Unable to generate transaction handle\n");
+ *transaction = OSP_INVALID_HANDLE;
+ res = 0;
+ } else if((res = osp_validate_token(*transaction, source, dest, calling, called, token, timelimit)) <= 0) {
+ OSPPTransactionRecordFailure(*transaction, OSPC_FAIL_CALL_REJECTED);
+ }
+ break;
+ }
+
+ return res;
+}
+
+/*!
+ * \brief OSP Lookup function
+ * \param provider OSP provider context name
+ * \param srcdev Source device of outbound call
+ * \param calling Calling number
+ * \param called Called number
+ * \param result Lookup results
+ * \return 1 Found , 0 No route, -1 Error
+ */
+static int osp_lookup(const char* provider, const char* srcdev, const char* calling, const char* called, struct osp_result* result)
+{
+ int res;
+ char source[OSP_NORSTR_SIZE];
+ unsigned int callidlen;
+ char callid[OSPC_CALLID_MAXSIZE];
+ char callingnum[OSP_NORSTR_SIZE];
+ char callednum[OSP_NORSTR_SIZE];
+ char destination[OSP_NORSTR_SIZE];
+ unsigned int tokenlen;
+ char token[OSP_TOKSTR_SIZE];
+ char src[OSP_NORSTR_SIZE];
+ char dev[OSP_NORSTR_SIZE];
+ unsigned int dummy = 0;
+ enum OSPEFAILREASON reason;
+ int error;
+
+ result->outhandle = OSP_INVALID_HANDLE;
+ result->tech[0] = '\0';
+ result->dest[0] = '\0';
+ result->calling[0] = '\0';
+ result->token[0] = '\0';
+ result->numresults = 0;
+ result->outtimelimit = OSP_DEF_TIMELIMIT;
+
+ if ((res = osp_create_transaction(provider, &result->outhandle, sizeof(source), source)) <= 0) {
+ ast_log(LOG_DEBUG, "OSP: Unable to generate transaction handle\n");
+ result->outhandle = OSP_INVALID_HANDLE;
+ if (result->inhandle != OSP_INVALID_HANDLE) {
+ OSPPTransactionRecordFailure(result->inhandle, OSPC_FAIL_NORMAL_UNSPECIFIED);
+ }
+ return -1;
+ }
+
+ osp_convert_address(source, src, sizeof(src));
+ osp_convert_address(srcdev, dev, sizeof(dev));
+ result->numresults = OSP_DEF_DESTINATIONS;
+ error = OSPPTransactionRequestAuthorisation(result->outhandle, src, dev, calling ? calling : "",
+ OSPC_E164, called, OSPC_E164, NULL, 0, NULL, NULL, &result->numresults, &dummy, NULL);
+ if (error != OSPC_ERR_NO_ERROR) {
+ ast_log(LOG_DEBUG, "OSP: Unable to request authorization\n");
+ result->numresults = 0;
+ if (result->inhandle != OSP_INVALID_HANDLE) {
+ OSPPTransactionRecordFailure(result->inhandle, OSPC_FAIL_NORMAL_UNSPECIFIED);
+ }
+ return -1;
+ }
+
+ if (!result->numresults) {
+ ast_log(LOG_DEBUG, "OSP: No more destination\n");
+ if (result->inhandle != OSP_INVALID_HANDLE) {
+ OSPPTransactionRecordFailure(result->inhandle, OSPC_FAIL_NO_ROUTE_TO_DEST);
+ }
+ return 0;
+ }
+
+ callidlen = sizeof(callid);
+ tokenlen = sizeof(token);
+ error = OSPPTransactionGetFirstDestination(result->outhandle, 0, NULL, NULL, &result->outtimelimit, &callidlen, callid,
+ sizeof(callednum), callednum, sizeof(callingnum), callingnum, sizeof(destination), destination, 0, NULL, &tokenlen, token);
+ if (error != OSPC_ERR_NO_ERROR) {
+ ast_log(LOG_DEBUG, "OSP: Unable to get first route\n");
+ result->numresults = 0;
+ result->outtimelimit = OSP_DEF_TIMELIMIT;
+ if (result->inhandle != OSP_INVALID_HANDLE) {
+ OSPPTransactionRecordFailure(result->inhandle, OSPC_FAIL_NO_ROUTE_TO_DEST);
+ }
+ return -1;
+ }
+
+ result->numresults--;
+ result->outtimelimit = osp_choose_timelimit(result->intimelimit, result->outtimelimit);
+ ast_log(LOG_DEBUG, "OSP: outtimelimit '%d'\n", result->outtimelimit);
+ ast_log(LOG_DEBUG, "OSP: called '%s'\n", callednum);
+ ast_log(LOG_DEBUG, "OSP: calling '%s'\n", callingnum);
+ ast_log(LOG_DEBUG, "OSP: destination '%s'\n", destination);
+ ast_log(LOG_DEBUG, "OSP: token size '%d'\n", tokenlen);
+
+ if ((res = osp_check_destination(callednum, callingnum, destination, tokenlen, token, &reason, result)) > 0) {
+ return 1;
+ }
+
+ if (!result->numresults) {
+ ast_log(LOG_DEBUG, "OSP: No more destination\n");
+ result->outtimelimit = OSP_DEF_TIMELIMIT;
+ OSPPTransactionRecordFailure(result->outhandle, reason);
+ if (result->inhandle != OSP_INVALID_HANDLE) {
+ OSPPTransactionRecordFailure(result->inhandle, OSPC_FAIL_NO_ROUTE_TO_DEST);
+ }
+ return 0;
+ }
+
+ while(result->numresults) {
+ callidlen = sizeof(callid);
+ tokenlen = sizeof(token);
+ error = OSPPTransactionGetNextDestination(result->outhandle, reason, 0, NULL, NULL, &result->outtimelimit, &callidlen, callid,
+ sizeof(callednum), callednum, sizeof(callingnum), callingnum, sizeof(destination), destination, 0, NULL, &tokenlen, token);
+ if (error == OSPC_ERR_NO_ERROR) {
+ result->numresults--;
+ result->outtimelimit = osp_choose_timelimit(result->intimelimit, result->outtimelimit);
+ ast_log(LOG_DEBUG, "OSP: outtimelimit '%d'\n", result->outtimelimit);
+ ast_log(LOG_DEBUG, "OSP: called '%s'\n", callednum);
+ ast_log(LOG_DEBUG, "OSP: calling '%s'\n", callingnum);
+ ast_log(LOG_DEBUG, "OSP: destination '%s'\n", destination);
+ ast_log(LOG_DEBUG, "OSP: token size '%d'\n", tokenlen);
+ if ((res = osp_check_destination(callednum, callingnum, destination, tokenlen, token, &reason, result)) > 0) {
+ break;
+ } else if (!result->numresults) {
+ ast_log(LOG_DEBUG, "OSP: No more destination\n");
+ OSPPTransactionRecordFailure(result->outhandle, reason);
+ if (result->inhandle != OSP_INVALID_HANDLE) {
+ OSPPTransactionRecordFailure(result->inhandle, OSPC_FAIL_NO_ROUTE_TO_DEST);
+ }
+ res = 0;
+ break;
+ }
+ } else {
+ ast_log(LOG_DEBUG, "OSP: Unable to get route, error '%d'\n", error);
+ result->numresults = 0;
+ result->outtimelimit = OSP_DEF_TIMELIMIT;
+ if (result->inhandle != OSP_INVALID_HANDLE) {
+ OSPPTransactionRecordFailure(result->inhandle, OSPC_FAIL_NORMAL_UNSPECIFIED);
+ }
+ res = -1;
+ break;
+ }
+ }
+ return res;
+}
+
+/*!
+ * \brief OSP Lookup Next function
+ * \param cause Asterisk hangup cuase
+ * \param result Lookup results, in/output
+ * \return 1 Found , 0 No route, -1 Error
+ */
+static int osp_next(int cause, struct osp_result* result)
+{
+ int res;
+ unsigned int callidlen;
+ char callid[OSPC_CALLID_MAXSIZE];
+ char callingnum[OSP_NORSTR_SIZE];
+ char callednum[OSP_NORSTR_SIZE];
+ char destination[OSP_NORSTR_SIZE];
+ unsigned int tokenlen;
+ char token[OSP_TOKSTR_SIZE];
+ enum OSPEFAILREASON reason;
+ int error;
+
+ result->tech[0] = '\0';
+ result->dest[0] = '\0';
+ result->calling[0] = '\0';
+ result->token[0] = '\0';
+ result->outtimelimit = OSP_DEF_TIMELIMIT;
+
+ if (result->outhandle == OSP_INVALID_HANDLE) {
+ ast_log(LOG_DEBUG, "OSP: Transaction handle undefined\n");
+ result->numresults = 0;
+ if (result->inhandle != OSP_INVALID_HANDLE) {
+ OSPPTransactionRecordFailure(result->inhandle, OSPC_FAIL_NORMAL_UNSPECIFIED);
+ }
+ return -1;
+ }
+
+ reason = asterisk2osp(cause);
+
+ if (!result->numresults) {
+ ast_log(LOG_DEBUG, "OSP: No more destination\n");
+ OSPPTransactionRecordFailure(result->outhandle, reason);
+ if (result->inhandle != OSP_INVALID_HANDLE) {
+ OSPPTransactionRecordFailure(result->inhandle, OSPC_FAIL_NO_ROUTE_TO_DEST);
+ }
+ return 0;
+ }
+
+ while(result->numresults) {
+ callidlen = sizeof(callid);
+ tokenlen = sizeof(token);
+ error = OSPPTransactionGetNextDestination(result->outhandle, reason, 0, NULL, NULL, &result->outtimelimit, &callidlen,
+ callid, sizeof(callednum), callednum, sizeof(callingnum), callingnum, sizeof(destination), destination, 0, NULL, &tokenlen, token);
+ if (error == OSPC_ERR_NO_ERROR) {
+ result->numresults--;
+ result->outtimelimit = osp_choose_timelimit(result->intimelimit, result->outtimelimit);
+ ast_log(LOG_DEBUG, "OSP: outtimelimit '%d'\n", result->outtimelimit);
+ ast_log(LOG_DEBUG, "OSP: called '%s'\n", callednum);
+ ast_log(LOG_DEBUG, "OSP: calling '%s'\n", callingnum);
+ ast_log(LOG_DEBUG, "OSP: destination '%s'\n", destination);
+ ast_log(LOG_DEBUG, "OSP: token size '%d'\n", tokenlen);
+ if ((res = osp_check_destination(callednum, callingnum, destination, tokenlen, token, &reason, result)) > 0) {
+ res = 1;
+ break;
+ } else if (!result->numresults) {
+ ast_log(LOG_DEBUG, "OSP: No more destination\n");
+ OSPPTransactionRecordFailure(result->outhandle, reason);
+ if (result->inhandle != OSP_INVALID_HANDLE) {
+ OSPPTransactionRecordFailure(result->inhandle, OSPC_FAIL_NO_ROUTE_TO_DEST);
+ }
+ res = 0;
+ break;
+ }
+ } else {
+ ast_log(LOG_DEBUG, "OSP: Unable to get route, error '%d'\n", error);
+ result->token[0] = '\0';
+ result->numresults = 0;
+ result->outtimelimit = OSP_DEF_TIMELIMIT;
+ if (result->inhandle != OSP_INVALID_HANDLE) {
+ OSPPTransactionRecordFailure(result->inhandle, OSPC_FAIL_NORMAL_UNSPECIFIED);
+ }
+ res = -1;
+ break;
+ }
+ }
+
+ return res;
+}
+
+/*!
+ * \brief OSP Finish function
+ * \param handle OSP in/outbound transaction handle
+ * \param recorded If failure reason has been recorded
+ * \param cause Asterisk hangup cause
+ * \param start Call start time
+ * \param connect Call connect time
+ * \param end Call end time
+ * \param release Who release first, 0 source, 1 destination
+ * \return 1 Success, 0 Failed, -1 Error
+ */
+static int osp_finish(int handle, int recorded, int cause, time_t start, time_t connect, time_t end, unsigned int release)
+{
+ int res;
+ enum OSPEFAILREASON reason;
+ time_t alert = 0;
+ unsigned isPddInfoPresent = 0;
+ unsigned pdd = 0;
+ unsigned int dummy = 0;
+ int error;
+
+ if (handle == OSP_INVALID_HANDLE) {
+ return 0;
+ }
+
+ if (!recorded) {
+ reason = asterisk2osp(cause);
+ OSPPTransactionRecordFailure(handle, reason);
+ }
+
+ error = OSPPTransactionReportUsage(handle, difftime(end, connect), start, end, alert, connect, isPddInfoPresent, pdd,
+ release, (unsigned char *) "", 0, 0, 0, 0, &dummy, NULL);
+ if (error == OSPC_ERR_NO_ERROR) {
+ ast_log(LOG_DEBUG, "OSP: Usage reported\n");
+ res = 1;
+ } else {
+ ast_log(LOG_DEBUG, "OSP: Unable to report usage, error '%d'\n", error);
+ res = -1;
+ }
+ OSPPTransactionDelete(handle);
+
+ return res;
+}
+
+/* OSP Application APIs */
+
+/*!
+ * \brief OSP Application OSPAuth
+ * \param chan Channel
+ * \param data Parameter
+ * \return 0 Success, -1 Failed
+ */
+static int ospauth_exec(struct ast_channel* chan, void* data)
+{
+ int res;
+ struct ast_module_user *u;
+ const char* provider = OSP_DEF_PROVIDER;
+ int priority_jump = 0;
+ struct varshead *headp;
+ struct ast_var_t *current;
+ const char *source = "";
+ const char *token = "";
+ int handle;
+ unsigned int timelimit;
+ char buffer[OSP_INTSTR_SIZE];
+ const char *status;
+ char *tmp;
+
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(provider);
+ AST_APP_ARG(options);
+ );
+
+ u = ast_module_user_add(chan);
+
+ if (!(tmp = ast_strdupa(data))) {
+ ast_log(LOG_ERROR, "Out of memory\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ AST_STANDARD_APP_ARGS(args, tmp);
+
+ if (!ast_strlen_zero(args.provider)) {
+ provider = args.provider;
+ }
+ ast_log(LOG_DEBUG, "OSPAuth: provider '%s'\n", provider);
+
+ if ((args.options) && (strchr(args.options, 'j'))) {
+ priority_jump = 1;
+ }
+ ast_log(LOG_DEBUG, "OSPAuth: priority jump '%d'\n", priority_jump);
+
+ headp = &chan->varshead;
+ AST_LIST_TRAVERSE(headp, current, entries) {
+ if (!strcasecmp(ast_var_name(current), "OSPPEERIP")) {
+ source = ast_var_value(current);
+ } else if (!strcasecmp(ast_var_name(current), "OSPINTOKEN")) {
+ token = ast_var_value(current);
+ }
+ }
+ ast_log(LOG_DEBUG, "OSPAuth: source '%s'\n", source);
+ ast_log(LOG_DEBUG, "OSPAuth: token size '%zd'\n", strlen(token));
+
+
+ if ((res = osp_auth(provider, &handle, source, chan->cid.cid_num, chan->exten, token, &timelimit)) > 0) {
+ status = AST_OSP_SUCCESS;
+ } else {
+ timelimit = OSP_DEF_TIMELIMIT;
+ if (!res) {
+ status = AST_OSP_FAILED;
+ } else {
+ status = AST_OSP_ERROR;
+ }
+ }
+
+ snprintf(buffer, sizeof(buffer), "%d", handle);
+ pbx_builtin_setvar_helper(chan, "OSPINHANDLE", buffer);
+ ast_log(LOG_DEBUG, "OSPAuth: OSPINHANDLE '%s'\n", buffer);
+ snprintf(buffer, sizeof(buffer), "%d", timelimit);
+ pbx_builtin_setvar_helper(chan, "OSPINTIMELIMIT", buffer);
+ ast_log(LOG_DEBUG, "OSPAuth: OSPINTIMELIMIT '%s'\n", buffer);
+ pbx_builtin_setvar_helper(chan, "OSPAUTHSTATUS", status);
+ ast_log(LOG_DEBUG, "OSPAuth: %s\n", status);
+
+ if(res <= 0) {
+ if (priority_jump || ast_opt_priority_jumping) {
+ ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
+ res = 0;
+ } else {
+ res = -1;
+ }
+ } else {
+ res = 0;
+ }
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+/*!
+ * \brief OSP Application OSPLookup
+ * \param chan Channel
+ * \param data Parameter
+ * \return 0 Success, -1 Failed
+ */
+static int osplookup_exec(struct ast_channel* chan, void* data)
+{
+ int res, cres;
+ struct ast_module_user *u;
+ const char *provider = OSP_DEF_PROVIDER;
+ int priority_jump = 0;
+ struct varshead *headp;
+ struct ast_var_t* current;
+ const char *srcdev = "";
+ char buffer[OSP_TOKSTR_SIZE];
+ struct osp_result result;
+ const char *status;
+ char *tmp;
+
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(exten);
+ AST_APP_ARG(provider);
+ AST_APP_ARG(options);
+ );
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "OSPLookup: Arg required, OSPLookup(exten[|provider[|options]])\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ if (!(tmp = ast_strdupa(data))) {
+ ast_log(LOG_ERROR, "Out of memory\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ AST_STANDARD_APP_ARGS(args, tmp);
+
+ ast_log(LOG_DEBUG, "OSPLookup: exten '%s'\n", args.exten);
+
+ if (!ast_strlen_zero(args.provider)) {
+ provider = args.provider;
+ }
+ ast_log(LOG_DEBUG, "OSPlookup: provider '%s'\n", provider);
+
+ if ((args.options) && (strchr(args.options, 'j'))) {
+ priority_jump = 1;
+ }
+ ast_log(LOG_DEBUG, "OSPLookup: priority jump '%d'\n", priority_jump);
+
+ result.inhandle = OSP_INVALID_HANDLE;
+ result.intimelimit = OSP_DEF_TIMELIMIT;
+
+ headp = &chan->varshead;
+ AST_LIST_TRAVERSE(headp, current, entries) {
+ if (!strcasecmp(ast_var_name(current), "OSPINHANDLE")) {
+ if (sscanf(ast_var_value(current), "%d", &result.inhandle) != 1) {
+ result.inhandle = OSP_INVALID_HANDLE;
+ }
+ } else if (!strcasecmp(ast_var_name(current), "OSPINTIMELIMIT")) {
+ if (sscanf(ast_var_value(current), "%d", &result.intimelimit) != 1) {
+ result.intimelimit = OSP_DEF_TIMELIMIT;
+ }
+ } else if (!strcasecmp(ast_var_name(current), "OSPPEERIP")) {
+ srcdev = ast_var_value(current);
+ }
+ }
+ ast_log(LOG_DEBUG, "OSPLookup: OSPINHANDLE '%d'\n", result.inhandle);
+ ast_log(LOG_DEBUG, "OSPLookup: OSPINTIMELIMIT '%d'\n", result.intimelimit);
+ ast_log(LOG_DEBUG, "OSPLookup: source device '%s'\n", srcdev);
+
+ if ((cres = ast_autoservice_start(chan)) < 0) {
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ if ((res = osp_lookup(provider, srcdev, chan->cid.cid_num, args.exten, &result)) > 0) {
+ status = AST_OSP_SUCCESS;
+ } else {
+ result.tech[0] = '\0';
+ result.dest[0] = '\0';
+ result.calling[0] = '\0';
+ result.token[0] = '\0';
+ result.numresults = 0;
+ result.outtimelimit = OSP_DEF_TIMELIMIT;
+ if (!res) {
+ status = AST_OSP_FAILED;
+ } else {
+ status = AST_OSP_ERROR;
+ }
+ }
+
+ snprintf(buffer, sizeof(buffer), "%d", result.outhandle);
+ pbx_builtin_setvar_helper(chan, "OSPOUTHANDLE", buffer);
+ ast_log(LOG_DEBUG, "OSPLookup: OSPOUTHANDLE '%s'\n", buffer);
+ pbx_builtin_setvar_helper(chan, "OSPTECH", result.tech);
+ ast_log(LOG_DEBUG, "OSPLookup: OSPTECH '%s'\n", result.tech);
+ pbx_builtin_setvar_helper(chan, "OSPDEST", result.dest);
+ ast_log(LOG_DEBUG, "OSPLookup: OSPDEST '%s'\n", result.dest);
+ pbx_builtin_setvar_helper(chan, "OSPCALLING", result.calling);
+ ast_log(LOG_DEBUG, "OSPLookup: OSPCALLING '%s'\n", result.calling);
+ pbx_builtin_setvar_helper(chan, "OSPOUTTOKEN", result.token);
+ ast_log(LOG_DEBUG, "OSPLookup: OSPOUTTOKEN size '%zd'\n", strlen(result.token));
+ snprintf(buffer, sizeof(buffer), "%d", result.numresults);
+ pbx_builtin_setvar_helper(chan, "OSPRESULTS", buffer);
+ ast_log(LOG_DEBUG, "OSPLookup: OSPRESULTS '%s'\n", buffer);
+ snprintf(buffer, sizeof(buffer), "%d", result.outtimelimit);
+ pbx_builtin_setvar_helper(chan, "OSPOUTTIMELIMIT", buffer);
+ ast_log(LOG_DEBUG, "OSPLookup: OSPOUTTIMELIMIT '%s'\n", buffer);
+ pbx_builtin_setvar_helper(chan, "OSPLOOKUPSTATUS", status);
+ ast_log(LOG_DEBUG, "OSPLookup: %s\n", status);
+
+ if (!strcasecmp(result.tech, "SIP")) {
+ if (!ast_strlen_zero(result.token)) {
+ snprintf(buffer, sizeof(buffer), "P-OSP-Auth-Token: %s", result.token);
+ pbx_builtin_setvar_helper(chan, "_SIPADDHEADER", buffer);
+ ast_log(LOG_DEBUG, "OSPLookup: SIPADDHEADER size '%zd'\n", strlen(buffer));
+ }
+ } else if (!strcasecmp(result.tech, "H323")) {
+ } else if (!strcasecmp(result.tech, "IAX")) {
+ }
+
+ if ((cres = ast_autoservice_stop(chan)) < 0) {
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ if(res <= 0) {
+ if (priority_jump || ast_opt_priority_jumping) {
+ ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
+ res = 0;
+ } else {
+ res = -1;
+ }
+ } else {
+ res = 0;
+ }
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+/*!
+ * \brief OSP Application OSPNext
+ * \param chan Channel
+ * \param data Parameter
+ * \return 0 Success, -1 Failed
+ */
+static int ospnext_exec(struct ast_channel* chan, void* data)
+{
+ int res;
+ struct ast_module_user *u;
+ int priority_jump = 0;
+ int cause = 0;
+ struct varshead* headp;
+ struct ast_var_t* current;
+ struct osp_result result;
+ char buffer[OSP_TOKSTR_SIZE];
+ const char* status;
+ char* tmp;
+
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(cause);
+ AST_APP_ARG(options);
+ );
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "OSPNext: Arg required, OSPNext(cause[|options])\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ if (!(tmp = ast_strdupa(data))) {
+ ast_log(LOG_ERROR, "Out of memory\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ AST_STANDARD_APP_ARGS(args, tmp);
+
+ if (!ast_strlen_zero(args.cause) && sscanf(args.cause, "%d", &cause) != 1) {
+ cause = 0;
+ }
+ ast_log(LOG_DEBUG, "OSPNext: cause '%d'\n", cause);
+
+ if ((args.options) && (strchr(args.options, 'j'))) {
+ priority_jump = 1;
+ }
+ ast_log(LOG_DEBUG, "OSPNext: priority jump '%d'\n", priority_jump);
+
+ result.inhandle = OSP_INVALID_HANDLE;
+ result.outhandle = OSP_INVALID_HANDLE;
+ result.intimelimit = OSP_DEF_TIMELIMIT;
+ result.numresults = 0;
+
+ headp = &chan->varshead;
+ AST_LIST_TRAVERSE(headp, current, entries) {
+ if (!strcasecmp(ast_var_name(current), "OSPINHANDLE")) {
+ if (sscanf(ast_var_value(current), "%d", &result.inhandle) != 1) {
+ result.inhandle = OSP_INVALID_HANDLE;
+ }
+ } else if (!strcasecmp(ast_var_name(current), "OSPOUTHANDLE")) {
+ if (sscanf(ast_var_value(current), "%d", &result.outhandle) != 1) {
+ result.outhandle = OSP_INVALID_HANDLE;
+ }
+ } else if (!strcasecmp(ast_var_name(current), "OSPINTIMELIMIT")) {
+ if (sscanf(ast_var_value(current), "%d", &result.intimelimit) != 1) {
+ result.intimelimit = OSP_DEF_TIMELIMIT;
+ }
+ } else if (!strcasecmp(ast_var_name(current), "OSPRESULTS")) {
+ if (sscanf(ast_var_value(current), "%d", &result.numresults) != 1) {
+ result.numresults = 0;
+ }
+ }
+ }
+ ast_log(LOG_DEBUG, "OSPNext: OSPINHANDLE '%d'\n", result.inhandle);
+ ast_log(LOG_DEBUG, "OSPNext: OSPOUTHANDLE '%d'\n", result.outhandle);
+ ast_log(LOG_DEBUG, "OSPNext: OSPINTIMELIMIT '%d'\n", result.intimelimit);
+ ast_log(LOG_DEBUG, "OSPNext: OSPRESULTS '%d'\n", result.numresults);
+
+ if ((res = osp_next(cause, &result)) > 0) {
+ status = AST_OSP_SUCCESS;
+ } else {
+ result.tech[0] = '\0';
+ result.dest[0] = '\0';
+ result.calling[0] = '\0';
+ result.token[0] = '\0';
+ result.numresults = 0;
+ result.outtimelimit = OSP_DEF_TIMELIMIT;
+ if (!res) {
+ status = AST_OSP_FAILED;
+ } else {
+ status = AST_OSP_ERROR;
+ }
+ }
+
+ pbx_builtin_setvar_helper(chan, "OSPTECH", result.tech);
+ ast_log(LOG_DEBUG, "OSPNext: OSPTECH '%s'\n", result.tech);
+ pbx_builtin_setvar_helper(chan, "OSPDEST", result.dest);
+ ast_log(LOG_DEBUG, "OSPNext: OSPDEST '%s'\n", result.dest);
+ pbx_builtin_setvar_helper(chan, "OSPCALLING", result.calling);
+ ast_log(LOG_DEBUG, "OSPNext: OSPCALLING '%s'\n", result.calling);
+ pbx_builtin_setvar_helper(chan, "OSPOUTTOKEN", result.token);
+ ast_log(LOG_DEBUG, "OSPNext: OSPOUTTOKEN size '%zd'\n", strlen(result.token));
+ snprintf(buffer, sizeof(buffer), "%d", result.numresults);
+ pbx_builtin_setvar_helper(chan, "OSPRESULTS", buffer);
+ ast_log(LOG_DEBUG, "OSPNext: OSPRESULTS '%s'\n", buffer);
+ snprintf(buffer, sizeof(buffer), "%d", result.outtimelimit);
+ pbx_builtin_setvar_helper(chan, "OSPOUTTIMELIMIT", buffer);
+ ast_log(LOG_DEBUG, "OSPNext: OSPOUTTIMELIMIT '%s'\n", buffer);
+ pbx_builtin_setvar_helper(chan, "OSPNEXTSTATUS", status);
+ ast_log(LOG_DEBUG, "OSPNext: %s\n", status);
+
+ if (!strcasecmp(result.tech, "SIP")) {
+ if (!ast_strlen_zero(result.token)) {
+ snprintf(buffer, sizeof(buffer), "P-OSP-Auth-Token: %s", result.token);
+ pbx_builtin_setvar_helper(chan, "_SIPADDHEADER", buffer);
+ ast_log(LOG_DEBUG, "OSPLookup: SIPADDHEADER size '%zd'\n", strlen(buffer));
+ }
+ } else if (!strcasecmp(result.tech, "H323")) {
+ } else if (!strcasecmp(result.tech, "IAX")) {
+ }
+
+ if(res <= 0) {
+ if (priority_jump || ast_opt_priority_jumping) {
+ ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
+ res = 0;
+ } else {
+ res = -1;
+ }
+ } else {
+ res = 0;
+ }
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+/*!
+ * \brief OSP Application OSPFinish
+ * \param chan Channel
+ * \param data Parameter
+ * \return 0 Success, -1 Failed
+ */
+static int ospfinished_exec(struct ast_channel* chan, void* data)
+{
+ int res = 1;
+ struct ast_module_user *u;
+ int priority_jump = 0;
+ int cause = 0;
+ struct varshead *headp;
+ struct ast_var_t *current;
+ int inhandle = OSP_INVALID_HANDLE;
+ int outhandle = OSP_INVALID_HANDLE;
+ int recorded = 0;
+ time_t start, connect, end;
+ unsigned int release;
+ char buffer[OSP_INTSTR_SIZE];
+ const char *status;
+ char *tmp;
+
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(cause);
+ AST_APP_ARG(options);
+ );
+
+ u = ast_module_user_add(chan);
+
+ if (!(tmp = ast_strdupa(data))) {
+ ast_log(LOG_ERROR, "Out of memory\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ AST_STANDARD_APP_ARGS(args, tmp);
+
+ if ((args.options) && (strchr(args.options, 'j'))) {
+ priority_jump = 1;
+ }
+ ast_log(LOG_DEBUG, "OSPFinish: priority jump '%d'\n", priority_jump);
+
+ headp = &chan->varshead;
+ AST_LIST_TRAVERSE(headp, current, entries) {
+ if (!strcasecmp(ast_var_name(current), "OSPINHANDLE")) {
+ if (sscanf(ast_var_value(current), "%d", &inhandle) != 1) {
+ inhandle = OSP_INVALID_HANDLE;
+ }
+ } else if (!strcasecmp(ast_var_name(current), "OSPOUTHANDLE")) {
+ if (sscanf(ast_var_value(current), "%d", &outhandle) != 1) {
+ outhandle = OSP_INVALID_HANDLE;
+ }
+ } else if (!recorded &&
+ (!strcasecmp(ast_var_name(current), "OSPAUTHSTATUS") ||
+ !strcasecmp(ast_var_name(current), "OSPLOOKUPSTATUS") ||
+ !strcasecmp(ast_var_name(current), "OSPNEXTSTATUS")))
+ {
+ if (strcasecmp(ast_var_value(current), AST_OSP_SUCCESS)) {
+ recorded = 1;
+ }
+ }
+ }
+ ast_log(LOG_DEBUG, "OSPFinish: OSPINHANDLE '%d'\n", inhandle);
+ ast_log(LOG_DEBUG, "OSPFinish: OSPOUTHANDLE '%d'\n", outhandle);
+ ast_log(LOG_DEBUG, "OSPFinish: recorded '%d'\n", recorded);
+
+ if (!ast_strlen_zero(args.cause) && sscanf(args.cause, "%d", &cause) != 1) {
+ cause = 0;
+ }
+ ast_log(LOG_DEBUG, "OSPFinish: cause '%d'\n", cause);
+
+ if (chan->cdr) {
+ start = chan->cdr->start.tv_sec;
+ connect = chan->cdr->answer.tv_sec;
+ if (connect) {
+ end = time(NULL);
+ } else {
+ end = connect;
+ }
+ } else {
+ start = 0;
+ connect = 0;
+ end = 0;
+ }
+ ast_log(LOG_DEBUG, "OSPFinish: start '%ld'\n", start);
+ ast_log(LOG_DEBUG, "OSPFinish: connect '%ld'\n", connect);
+ ast_log(LOG_DEBUG, "OSPFinish: end '%ld'\n", end);
+
+ release = chan->_softhangup ? 0 : 1;
+
+ if (osp_finish(outhandle, recorded, cause, start, connect, end, release) <= 0) {
+ ast_log(LOG_DEBUG, "OSPFinish: Unable to report usage for outbound call\n");
+ }
+ switch (cause) {
+ case AST_CAUSE_NORMAL_CLEARING:
+ break;
+ default:
+ cause = AST_CAUSE_NO_ROUTE_DESTINATION;
+ break;
+ }
+ if (osp_finish(inhandle, recorded, cause, start, connect, end, release) <= 0) {
+ ast_log(LOG_DEBUG, "OSPFinish: Unable to report usage for inbound call\n");
+ }
+ snprintf(buffer, sizeof(buffer), "%d", OSP_INVALID_HANDLE);
+ pbx_builtin_setvar_helper(chan, "OSPOUTHANDLE", buffer);
+ pbx_builtin_setvar_helper(chan, "OSPINHANDLE", buffer);
+
+ if (res > 0) {
+ status = AST_OSP_SUCCESS;
+ } else if (!res) {
+ status = AST_OSP_FAILED;
+ } else {
+ status = AST_OSP_ERROR;
+ }
+ pbx_builtin_setvar_helper(chan, "OSPFINISHSTATUS", status);
+
+ if(!res) {
+ if (priority_jump || ast_opt_priority_jumping) {
+ ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
+ res = 0;
+ } else {
+ res = -1;
+ }
+ } else {
+ res = 0;
+ }
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+/* OSP Module APIs */
+
+static int osp_load(void)
+{
+ const char* t;
+ unsigned int v;
+ struct ast_config* cfg;
+ int error = OSPC_ERR_NO_ERROR;
+
+ cfg = ast_config_load(OSP_CONFIG_FILE);
+ if (cfg) {
+ t = ast_variable_retrieve(cfg, OSP_GENERAL_CAT, "accelerate");
+ if (t && ast_true(t)) {
+ if ((error = OSPPInit(1)) != OSPC_ERR_NO_ERROR) {
+ ast_log(LOG_WARNING, "OSP: Unable to enable hardware accelleration\n");
+ OSPPInit(0);
+ } else {
+ osp_hardware = 1;
+ }
+ } else {
+ OSPPInit(0);
+ }
+ ast_log(LOG_DEBUG, "OSP: osp_hardware '%d'\n", osp_hardware);
+
+ t = ast_variable_retrieve(cfg, OSP_GENERAL_CAT, "tokenformat");
+ if (t) {
+ if ((sscanf(t, "%d", &v) == 1) &&
+ ((v == TOKEN_ALGO_SIGNED) || (v == TOKEN_ALGO_UNSIGNED) || (v == TOKEN_ALGO_BOTH)))
+ {
+ osp_tokenformat = v;
+ } else {
+ ast_log(LOG_WARNING, "tokenformat should be an integer from %d, %d or %d, not '%s'\n",
+ TOKEN_ALGO_SIGNED, TOKEN_ALGO_UNSIGNED, TOKEN_ALGO_BOTH, t);
+ }
+ }
+ ast_log(LOG_DEBUG, "OSP: osp_tokenformat '%d'\n", osp_tokenformat);
+
+ t = ast_category_browse(cfg, NULL);
+ while(t) {
+ if (strcasecmp(t, OSP_GENERAL_CAT)) {
+ osp_create_provider(cfg, t);
+ }
+ t = ast_category_browse(cfg, t);
+ }
+
+ osp_initialized = 1;
+
+ ast_config_destroy(cfg);
+ } else {
+ ast_log(LOG_WARNING, "OSP: Unable to find configuration. OSP support disabled\n");
+ return 0;
+ }
+ ast_log(LOG_DEBUG, "OSP: osp_initialized '%d'\n", osp_initialized);
+
+ return 1;
+}
+
+static int osp_unload(void)
+{
+ struct osp_provider* p;
+ struct osp_provider* next;
+
+ if (osp_initialized) {
+ ast_mutex_lock(&osplock);
+ p = ospproviders;
+ while(p) {
+ next = p->next;
+ OSPPProviderDelete(p->handle, 0);
+ free(p);
+ p = next;
+ }
+ ospproviders = NULL;
+ ast_mutex_unlock(&osplock);
+
+ OSPPCleanup();
+
+ osp_tokenformat = TOKEN_ALGO_SIGNED;
+ osp_hardware = 0;
+ osp_initialized = 0;
+ }
+ return 0;
+}
+
+static int osp_show(int fd, int argc, char* argv[])
+{
+ int i;
+ int found = 0;
+ struct osp_provider* p;
+ const char* provider = NULL;
+ const char* tokenalgo;
+
+ if ((argc < 2) || (argc > 3)) {
+ return RESULT_SHOWUSAGE;
+ }
+ if (argc > 2) {
+ provider = argv[2];
+ }
+ if (!provider) {
+ switch (osp_tokenformat) {
+ case TOKEN_ALGO_BOTH:
+ tokenalgo = "Both";
+ break;
+ case TOKEN_ALGO_UNSIGNED:
+ tokenalgo = "Unsigned";
+ break;
+ case TOKEN_ALGO_SIGNED:
+ default:
+ tokenalgo = "Signed";
+ break;
+ }
+ ast_cli(fd, "OSP: %s %s %s\n",
+ osp_initialized ? "Initialized" : "Uninitialized", osp_hardware ? "Accelerated" : "Normal", tokenalgo);
+ }
+
+ ast_mutex_lock(&osplock);
+ p = ospproviders;
+ while(p) {
+ if (!provider || !strcasecmp(p->name, provider)) {
+ if (found) {
+ ast_cli(fd, "\n");
+ }
+ ast_cli(fd, " == OSP Provider '%s' == \n", p->name);
+ ast_cli(fd, "Local Private Key: %s\n", p->privatekey);
+ ast_cli(fd, "Local Certificate: %s\n", p->localcert);
+ for (i = 0; i < p->cacount; i++) {
+ ast_cli(fd, "CA Certificate %d: %s\n", i + 1, p->cacerts[i]);
+ }
+ for (i = 0; i < p->spcount; i++) {
+ ast_cli(fd, "Service Point %d: %s\n", i + 1, p->srvpoints[i]);
+ }
+ ast_cli(fd, "Max Connections: %d\n", p->maxconnections);
+ ast_cli(fd, "Retry Delay: %d seconds\n", p->retrydelay);
+ ast_cli(fd, "Retry Limit: %d\n", p->retrylimit);
+ ast_cli(fd, "Timeout: %d milliseconds\n", p->timeout);
+ ast_cli(fd, "Source: %s\n", strlen(p->source) ? p->source : "<unspecified>");
+ ast_cli(fd, "Auth Policy %d\n", p->authpolicy);
+ ast_cli(fd, "OSP Handle: %d\n", p->handle);
+ found++;
+ }
+ p = p->next;
+ }
+ ast_mutex_unlock(&osplock);
+
+ if (!found) {
+ if (provider) {
+ ast_cli(fd, "Unable to find OSP provider '%s'\n", provider);
+ } else {
+ ast_cli(fd, "No OSP providers configured\n");
+ }
+ }
+ return RESULT_SUCCESS;
+}
+
+static const char* app1= "OSPAuth";
+static const char* synopsis1 = "OSP authentication";
+static const char* descrip1 =
+" OSPAuth([provider[|options]]): Authenticate a SIP INVITE by OSP and sets\n"
+"the variables:\n"
+" ${OSPINHANDLE}: The inbound call transaction handle\n"
+" ${OSPINTIMELIMIT}: The inbound call duration limit in seconds\n"
+"\n"
+"The option string may contain the following character:\n"
+" 'j' -- jump to n+101 priority if the authentication was NOT successful\n"
+"This application sets the following channel variable upon completion:\n"
+" OSPAUTHSTATUS The status of the OSP Auth attempt as a text string, one of\n"
+" SUCCESS | FAILED | ERROR\n";
+
+static const char* app2= "OSPLookup";
+static const char* synopsis2 = "Lookup destination by OSP";
+static const char* descrip2 =
+" OSPLookup(exten[|provider[|options]]): Looks up an extension via OSP and sets\n"
+"the variables, where 'n' is the number of the result beginning with 1:\n"
+" ${OSPOUTHANDLE}: The OSP Handle for anything remaining\n"
+" ${OSPTECH}: The technology to use for the call\n"
+" ${OSPDEST}: The destination to use for the call\n"
+" ${OSPCALLING}: The calling number to use for the call\n"
+" ${OSPOUTTOKEN}: The actual OSP token as a string\n"
+" ${OSPOUTTIMELIMIT}: The outbound call duration limit in seconds\n"
+" ${OSPRESULTS}: The number of OSP results total remaining\n"
+"\n"
+"The option string may contain the following character:\n"
+" 'j' -- jump to n+101 priority if the lookup was NOT successful\n"
+"This application sets the following channel variable upon completion:\n"
+" OSPLOOKUPSTATUS The status of the OSP Lookup attempt as a text string, one of\n"
+" SUCCESS | FAILED | ERROR\n";
+
+static const char* app3 = "OSPNext";
+static const char* synopsis3 = "Lookup next destination by OSP";
+static const char* descrip3 =
+" OSPNext(cause[|options]): Looks up the next OSP Destination for ${OSPOUTHANDLE}\n"
+"See OSPLookup for more information\n"
+"\n"
+"The option string may contain the following character:\n"
+" 'j' -- jump to n+101 priority if the lookup was NOT successful\n"
+"This application sets the following channel variable upon completion:\n"
+" OSPNEXTSTATUS The status of the OSP Next attempt as a text string, one of\n"
+" SUCCESS | FAILED |ERROR\n";
+
+static const char* app4 = "OSPFinish";
+static const char* synopsis4 = "Record OSP entry";
+static const char* descrip4 =
+" OSPFinish([status[|options]]): Records call state for ${OSPINHANDLE}, according to\n"
+"status, which should be one of BUSY, CONGESTION, ANSWER, NOANSWER, or CHANUNAVAIL\n"
+"or coincidentally, just what the Dial application stores in its ${DIALSTATUS}.\n"
+"\n"
+"The option string may contain the following character:\n"
+" 'j' -- jump to n+101 priority if the finish attempt was NOT successful\n"
+"This application sets the following channel variable upon completion:\n"
+" OSPFINISHSTATUS The status of the OSP Finish attempt as a text string, one of\n"
+" SUCCESS | FAILED |ERROR \n";
+
+static const char osp_usage[] =
+"Usage: osp show\n"
+" Displays information on Open Settlement Protocol support\n";
+
+static struct ast_cli_entry cli_osp[] = {
+ { { "osp", "show", NULL},
+ osp_show, "Displays OSP information",
+ osp_usage },
+};
+
+static int load_module(void)
+{
+ int res;
+
+ if(!osp_load())
+ return AST_MODULE_LOAD_DECLINE;
+
+ ast_cli_register_multiple(cli_osp, sizeof(cli_osp) / sizeof(struct ast_cli_entry));
+ res = ast_register_application(app1, ospauth_exec, synopsis1, descrip1);
+ res |= ast_register_application(app2, osplookup_exec, synopsis2, descrip2);
+ res |= ast_register_application(app3, ospnext_exec, synopsis3, descrip3);
+ res |= ast_register_application(app4, ospfinished_exec, synopsis4, descrip4);
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app4);
+ res |= ast_unregister_application(app3);
+ res |= ast_unregister_application(app2);
+ res |= ast_unregister_application(app1);
+ ast_cli_unregister_multiple(cli_osp, sizeof(cli_osp) / sizeof(struct ast_cli_entry));
+ osp_unload();
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int reload(void)
+{
+ osp_unload();
+ osp_load();
+
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Open Settlement Protocol Applications",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
diff --git a/apps/app_page.c b/apps/app_page.c
new file mode 100644
index 000000000..c94e1b11a
--- /dev/null
+++ b/apps/app_page.c
@@ -0,0 +1,217 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (c) 2004 - 2006 Digium, Inc. All rights reserved.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * This code is released under the GNU General Public License
+ * version 2.0. See LICENSE for more information.
+ *
+ * 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.
+ *
+ */
+
+/*! \file
+ *
+ * \brief page() - Paging application
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+ <depend>dahdi</depend>
+ <depend>app_meetme</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include "asterisk/options.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/file.h"
+#include "asterisk/app.h"
+#include "asterisk/chanvars.h"
+#include "asterisk/utils.h"
+#include "asterisk/dial.h"
+#include "asterisk/devicestate.h"
+
+static const char *app_page= "Page";
+
+static const char *page_synopsis = "Pages phones";
+
+static const char *page_descrip =
+"Page(Technology/Resource&Technology2/Resource2[|options])\n"
+" Places outbound calls to the given technology / resource and dumps\n"
+"them into a conference bridge as muted participants. The original\n"
+"caller is dumped into the conference as a speaker and the room is\n"
+"destroyed when the original caller leaves. Valid options are:\n"
+" d - full duplex audio\n"
+" q - quiet, do not play beep to caller\n"
+" r - record the page into a file (see 'r' for app_meetme)\n";
+
+enum {
+ PAGE_DUPLEX = (1 << 0),
+ PAGE_QUIET = (1 << 1),
+ PAGE_RECORD = (1 << 2),
+} page_opt_flags;
+
+AST_APP_OPTIONS(page_opts, {
+ AST_APP_OPTION('d', PAGE_DUPLEX),
+ AST_APP_OPTION('q', PAGE_QUIET),
+ AST_APP_OPTION('r', PAGE_RECORD),
+});
+
+
+static int page_exec(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *u;
+ char *options, *tech, *resource, *tmp, *tmp2;
+ char meetmeopts[88], originator[AST_CHANNEL_NAME];
+ struct ast_flags flags = { 0 };
+ unsigned int confid = ast_random();
+ struct ast_app *app;
+ int res = 0, pos = 0, i = 0;
+ struct ast_dial **dial_list;
+ unsigned int num_dials;
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "This application requires at least one argument (destination(s) to page)\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ if (!(app = pbx_findapp("MeetMe"))) {
+ ast_log(LOG_WARNING, "There is no MeetMe application available!\n");
+ ast_module_user_remove(u);
+ return -1;
+ };
+
+ options = ast_strdupa(data);
+
+ ast_copy_string(originator, chan->name, sizeof(originator));
+ if ((tmp = strchr(originator, '-')))
+ *tmp = '\0';
+
+ tmp = strsep(&options, "|");
+ if (options)
+ ast_app_parse_options(page_opts, &flags, NULL, options);
+
+ snprintf(meetmeopts, sizeof(meetmeopts), "MeetMe|%ud|%s%sqxdw(5)", confid, (ast_test_flag(&flags, PAGE_DUPLEX) ? "" : "m"),
+ (ast_test_flag(&flags, PAGE_RECORD) ? "r" : "") );
+
+ /* Count number of extensions in list by number of ampersands + 1 */
+ num_dials = 1;
+ tmp2 = tmp;
+ while (*tmp2 && *tmp2++ == '&') {
+ num_dials++;
+ }
+
+ if (!(dial_list = ast_calloc(num_dials, sizeof(void *)))) {
+ ast_log(LOG_ERROR, "Can't allocate %ld bytes for dial list\n", (long)(sizeof(void *) * num_dials));
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ /* Go through parsing/calling each device */
+ while ((tech = strsep(&tmp, "&"))) {
+ struct ast_dial *dial = NULL;
+
+ /* don't call the originating device */
+ if (!strcasecmp(tech, originator))
+ continue;
+
+ /* If no resource is available, continue on */
+ if (!(resource = strchr(tech, '/'))) {
+ ast_log(LOG_WARNING, "Incomplete destination '%s' supplied.\n", tech);
+ continue;
+ }
+
+ *resource++ = '\0';
+
+ /* Create a dialing structure */
+ if (!(dial = ast_dial_create())) {
+ ast_log(LOG_WARNING, "Failed to create dialing structure.\n");
+ continue;
+ }
+
+ /* Append technology and resource */
+ ast_dial_append(dial, tech, resource);
+
+ /* Set ANSWER_EXEC as global option */
+ ast_dial_option_global_enable(dial, AST_DIAL_OPTION_ANSWER_EXEC, meetmeopts);
+
+ /* Run this dial in async mode */
+ ast_dial_run(dial, chan, 1);
+
+ /* Put in our dialing array */
+ dial_list[pos++] = dial;
+ }
+
+ if (!ast_test_flag(&flags, PAGE_QUIET)) {
+ res = ast_streamfile(chan, "beep", chan->language);
+ if (!res)
+ res = ast_waitstream(chan, "");
+ }
+
+ if (!res) {
+ snprintf(meetmeopts, sizeof(meetmeopts), "%ud|A%s%sqxd", confid, (ast_test_flag(&flags, PAGE_DUPLEX) ? "" : "t"),
+ (ast_test_flag(&flags, PAGE_RECORD) ? "r" : "") );
+ pbx_exec(chan, app, meetmeopts);
+ }
+
+ /* Go through each dial attempt cancelling, joining, and destroying */
+ for (i = 0; i < pos; i++) {
+ struct ast_dial *dial = dial_list[i];
+
+ /* We have to wait for the async thread to exit as it's possible Meetme won't throw them out immediately */
+ ast_dial_join(dial);
+
+ /* Hangup all channels */
+ ast_dial_hangup(dial);
+
+ /* Destroy dialing structure */
+ ast_dial_destroy(dial);
+ }
+
+ ast_free(dial_list);
+ ast_module_user_remove(u);
+
+ return -1;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app_page);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app_page, page_exec, page_synopsis, page_descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Page Multiple Phones");
+
diff --git a/apps/app_parkandannounce.c b/apps/app_parkandannounce.c
new file mode 100644
index 000000000..9e9f1604d
--- /dev/null
+++ b/apps/app_parkandannounce.c
@@ -0,0 +1,260 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2006, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * Author: Ben Miller <bgmiller@dccinc.com>
+ * With TONS of help from Mark!
+ *
+ * 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 ParkAndAnnounce application for Asterisk
+ *
+ * \author Ben Miller <bgmiller@dccinc.com>
+ * \arg With TONS of help from Mark!
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/features.h"
+#include "asterisk/options.h"
+#include "asterisk/logger.h"
+#include "asterisk/say.h"
+#include "asterisk/lock.h"
+#include "asterisk/utils.h"
+
+static char *app = "ParkAndAnnounce";
+
+static char *synopsis = "Park and Announce";
+
+static char *descrip =
+" ParkAndAnnounce(announce:template|timeout|dial|[return_context]):\n"
+"Park a call into the parkinglot and announce the call to another channel.\n"
+"\n"
+"announce template: Colon-separated list of files to announce. The word PARKED\n"
+" will be replaced by a say_digits of the extension in which\n"
+" the call is parked.\n"
+"timeout: Time in seconds before the call returns into the return\n"
+" context.\n"
+"dial: The app_dial style resource to call to make the\n"
+" announcement. Console/dsp calls the console.\n"
+"return_context: The goto-style label to jump the call back into after\n"
+" timeout. Default <priority+1>.\n"
+"\n"
+"The variable ${PARKEDAT} will contain the parking extension into which the\n"
+"call was placed. Use with the Local channel to allow the dialplan to make\n"
+"use of this information.\n";
+
+
+static int parkandannounce_exec(struct ast_channel *chan, void *data)
+{
+ char *return_context;
+ int lot, timeout = 0, dres;
+ char *working, *context, *exten, *priority, *dial, *dialtech, *dialstr;
+ char *template, *tpl_working, *tpl_current;
+ char *tmp[100];
+ char buf[13];
+ int looptemp = 0,i = 0, res = 0;
+ char *s;
+
+ struct ast_channel *dchan;
+ struct outgoing_helper oh;
+ int outstate;
+
+ struct ast_module_user *u;
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "ParkAndAnnounce requires arguments: (announce:template|timeout|dial|[return_context])\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ s = ast_strdupa(data);
+
+ template = strsep(&s,"|");
+ if(! template) {
+ ast_log(LOG_WARNING, "PARK: An announce template must be defined\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ if(s) {
+ timeout = atoi(strsep(&s, "|"));
+ timeout *= 1000;
+ }
+ dial = strsep(&s, "|");
+ if(!dial) {
+ ast_log(LOG_WARNING, "PARK: A dial resource must be specified i.e: Console/dsp or Zap/g1/5551212\n");
+ ast_module_user_remove(u);
+ return -1;
+ } else {
+ dialtech = strsep(&dial, "/");
+ dialstr = dial;
+ ast_verbose( VERBOSE_PREFIX_3 "Dial Tech,String: (%s,%s)\n", dialtech,dialstr);
+ }
+
+ return_context = s;
+
+ if(return_context != NULL) {
+ /* set the return context. Code borrowed from the Goto builtin */
+
+ working = return_context;
+ context = strsep(&working, "|");
+ exten = strsep(&working, "|");
+ if(!exten) {
+ /* Only a priority in this one */
+ priority = context;
+ exten = NULL;
+ context = NULL;
+ } else {
+ priority = strsep(&working, "|");
+ if(!priority) {
+ /* Only an extension and priority in this one */
+ priority = exten;
+ exten = context;
+ context = NULL;
+ }
+ }
+ if(atoi(priority) < 0) {
+ ast_log(LOG_WARNING, "Priority '%s' must be a number > 0\n", priority);
+ ast_module_user_remove(u);
+ return -1;
+ }
+ /* At this point we have a priority and maybe an extension and a context */
+ chan->priority = atoi(priority);
+ if (exten)
+ ast_copy_string(chan->exten, exten, sizeof(chan->exten));
+ if (context)
+ ast_copy_string(chan->context, context, sizeof(chan->context));
+ } else { /* increment the priority by default*/
+ chan->priority++;
+ }
+
+ if(option_verbose > 2) {
+ ast_verbose( VERBOSE_PREFIX_3 "Return Context: (%s,%s,%d) ID: %s\n", chan->context,chan->exten, chan->priority, chan->cid.cid_num);
+ if(!ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
+ ast_verbose( VERBOSE_PREFIX_3 "Warning: Return Context Invalid, call will return to default|s\n");
+ }
+ }
+
+ /* we are using masq_park here to protect * from touching the channel once we park it. If the channel comes out of timeout
+ before we are done announcing and the channel is messed with, Kablooeee. So we use Masq to prevent this. */
+
+ res = ast_masq_park_call(chan, NULL, timeout, &lot);
+ if (res == -1) {
+ goto finish;
+ }
+
+ ast_verbose( VERBOSE_PREFIX_3 "Call Parking Called, lot: %d, timeout: %d, context: %s\n", lot, timeout, return_context);
+
+ /* Now place the call to the extention */
+
+ snprintf(buf, sizeof(buf), "%d", lot);
+ memset(&oh, 0, sizeof(oh));
+ oh.parent_channel = chan;
+ oh.vars = ast_variable_new("_PARKEDAT", buf);
+ dchan = __ast_request_and_dial(dialtech, AST_FORMAT_SLINEAR, dialstr,30000, &outstate, chan->cid.cid_num, chan->cid.cid_name, &oh);
+
+ if(dchan) {
+ if(dchan->_state == AST_STATE_UP) {
+ if(option_verbose > 3)
+ ast_verbose(VERBOSE_PREFIX_4 "Channel %s was answered.\n", dchan->name);
+ } else {
+ if(option_verbose > 3)
+ ast_verbose(VERBOSE_PREFIX_4 "Channel %s was never answered.\n", dchan->name);
+ ast_log(LOG_WARNING, "PARK: Channel %s was never answered for the announce.\n", dchan->name);
+ ast_hangup(dchan);
+ ast_module_user_remove(u);
+ return -1;
+ }
+ } else {
+ ast_log(LOG_WARNING, "PARK: Unable to allocate announce channel.\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ ast_stopstream(dchan);
+
+ /* now we have the call placed and are ready to play stuff to it */
+
+ ast_verbose(VERBOSE_PREFIX_4 "Announce Template:%s\n", template);
+
+ tpl_working = template;
+ tpl_current = strsep(&tpl_working, ":");
+
+ while(tpl_current && looptemp < ARRAY_LEN(tmp)) {
+ tmp[looptemp]=tpl_current;
+ looptemp++;
+ tpl_current = strsep(&tpl_working,":");
+ }
+
+ for(i = 0; i < looptemp; i++) {
+ ast_verbose(VERBOSE_PREFIX_4 "Announce:%s\n", tmp[i]);
+ if(!strcmp(tmp[i], "PARKED")) {
+ ast_say_digits(dchan, lot, "", dchan->language);
+ } else {
+ dres = ast_streamfile(dchan, tmp[i], dchan->language);
+ if(!dres) {
+ dres = ast_waitstream(dchan, "");
+ } else {
+ ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", tmp[i], dchan->name);
+ dres = 0;
+ }
+ }
+ }
+
+ ast_stopstream(dchan);
+ ast_hangup(dchan);
+
+finish:
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ /* return ast_register_application(app, park_exec); */
+ return ast_register_application(app, parkandannounce_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Call Parking and Announce Application");
diff --git a/apps/app_playback.c b/apps/app_playback.c
new file mode 100644
index 000000000..8f78bef78
--- /dev/null
+++ b/apps/app_playback.c
@@ -0,0 +1,494 @@
+/*
+ * 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 Trivial application to playback a sound file
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/utils.h"
+#include "asterisk/options.h"
+#include "asterisk/app.h"
+#include "asterisk/cli.h"
+#include "asterisk/localtime.h"
+#include "asterisk/say.h"
+
+static char *app = "Playback";
+
+static char *synopsis = "Play a file";
+
+static char *descrip =
+" Playback(filename[&filename2...][|option]): Plays back given filenames (do not put\n"
+"extension). Options may also be included following a pipe symbol. The 'skip'\n"
+"option causes the playback of the message to be skipped if the channel\n"
+"is not in the 'up' state (i.e. it hasn't been answered yet). If 'skip' is \n"
+"specified, the application will return immediately should the channel not be\n"
+"off hook. Otherwise, unless 'noanswer' is specified, the channel will\n"
+"be answered before the sound is played. Not all channels support playing\n"
+"messages while still on hook. If 'j' is specified, the application\n"
+"will jump to priority n+101 if present when a file specified to be played\n"
+"does not exist.\n"
+"This application sets the following channel variable upon completion:\n"
+" PLAYBACKSTATUS The status of the playback attempt as a text string, one of\n"
+" SUCCESS | FAILED\n"
+"See Also: Background (application) -- for playing soundfiles that are interruptible\n"
+" WaitExten (application) -- wait for digits from caller, optionally play music on hold\n"
+;
+
+
+static struct ast_config *say_cfg = NULL;
+/* save the say' api calls.
+ * The first entry is NULL if we have the standard source,
+ * otherwise we are sourcing from here.
+ * 'say load [new|old]' will enable the new or old method, or report status
+ */
+static const void * say_api_buf[40];
+static const char *say_old = "old";
+static const char *say_new = "new";
+
+static void save_say_mode(const void *arg)
+{
+ int i = 0;
+ say_api_buf[i++] = arg;
+
+ say_api_buf[i++] = ast_say_number_full;
+ say_api_buf[i++] = ast_say_enumeration_full;
+ say_api_buf[i++] = ast_say_digit_str_full;
+ say_api_buf[i++] = ast_say_character_str_full;
+ say_api_buf[i++] = ast_say_phonetic_str_full;
+ say_api_buf[i++] = ast_say_datetime;
+ say_api_buf[i++] = ast_say_time;
+ say_api_buf[i++] = ast_say_date;
+ say_api_buf[i++] = ast_say_datetime_from_now;
+ say_api_buf[i++] = ast_say_date_with_format;
+}
+
+static void restore_say_mode(void *arg)
+{
+ int i = 0;
+ say_api_buf[i++] = arg;
+
+ ast_say_number_full = say_api_buf[i++];
+ ast_say_enumeration_full = say_api_buf[i++];
+ ast_say_digit_str_full = say_api_buf[i++];
+ ast_say_character_str_full = say_api_buf[i++];
+ ast_say_phonetic_str_full = say_api_buf[i++];
+ ast_say_datetime = say_api_buf[i++];
+ ast_say_time = say_api_buf[i++];
+ ast_say_date = say_api_buf[i++];
+ ast_say_datetime_from_now = say_api_buf[i++];
+ ast_say_date_with_format = say_api_buf[i++];
+}
+
+/*
+ * Typical 'say' arguments in addition to the date or number or string
+ * to say. We do not include 'options' because they may be different
+ * in recursive calls, and so they are better left as an external
+ * parameter.
+ */
+typedef struct {
+ struct ast_channel *chan;
+ const char *ints;
+ const char *language;
+ int audiofd;
+ int ctrlfd;
+} say_args_t;
+
+static int s_streamwait3(const say_args_t *a, const char *fn)
+{
+ int res = ast_streamfile(a->chan, fn, a->language);
+ if (res) {
+ ast_log(LOG_WARNING, "Unable to play message %s\n", fn);
+ return res;
+ }
+ res = (a->audiofd > -1 && a->ctrlfd > -1) ?
+ ast_waitstream_full(a->chan, a->ints, a->audiofd, a->ctrlfd) :
+ ast_waitstream(a->chan, a->ints);
+ ast_stopstream(a->chan);
+ return res;
+}
+
+/*
+ * the string is 'prefix:data' or prefix:fmt:data'
+ * with ':' being invalid in strings.
+ */
+static int do_say(say_args_t *a, const char *s, const char *options, int depth)
+{
+ struct ast_variable *v;
+ char *lang, *x, *rule = NULL;
+ int ret = 0;
+ struct varshead head = { .first = NULL, .last = NULL };
+ struct ast_var_t *n;
+
+ if (depth++ > 10) {
+ ast_log(LOG_WARNING, "recursion too deep, exiting\n");
+ return -1;
+ } else if (!say_cfg) {
+ ast_log(LOG_WARNING, "no say.conf, cannot spell '%s'\n", s);
+ return -1;
+ }
+
+ /* scan languages same as in file.c */
+ if (a->language == NULL)
+ a->language = "en"; /* default */
+ lang = ast_strdupa(a->language);
+ for (;;) {
+ for (v = ast_variable_browse(say_cfg, lang); v ; v = v->next) {
+ if (ast_extension_match(v->name, s)) {
+ rule = ast_strdupa(v->value);
+ break;
+ }
+ }
+ if (rule)
+ break;
+ if ( (x = strchr(lang, '_')) )
+ *x = '\0'; /* try without suffix */
+ else if (strcmp(lang, "en"))
+ lang = "en"; /* last resort, try 'en' if not done yet */
+ else
+ break;
+ }
+ if (!rule)
+ return 0;
+
+ /* skip up to two prefixes to get the value */
+ if ( (x = strchr(s, ':')) )
+ s = x + 1;
+ if ( (x = strchr(s, ':')) )
+ s = x + 1;
+ n = ast_var_assign("SAY", s);
+ AST_LIST_INSERT_HEAD(&head, n, entries);
+
+ /* scan the body, one piece at a time */
+ while ( !ret && (x = strsep(&rule, ",")) ) { /* exit on key */
+ char fn[128];
+ const char *p, *fmt, *data; /* format and data pointers */
+
+ /* prepare a decent file name */
+ x = ast_skip_blanks(x);
+ ast_trim_blanks(x);
+
+ /* replace variables */
+ memset(fn, 0, sizeof(fn)); /* XXX why isn't done in pbx_substitute_variables_helper! */
+ pbx_substitute_variables_varshead(&head, x, fn, sizeof(fn));
+
+ /* locate prefix and data, if any */
+ fmt = index(fn, ':');
+ if (!fmt || fmt == fn) { /* regular filename */
+ ret = s_streamwait3(a, fn);
+ continue;
+ }
+ fmt++;
+ data = index(fmt, ':'); /* colon before data */
+ if (!data || data == fmt) { /* simple prefix-fmt */
+ ret = do_say(a, fn, options, depth);
+ continue;
+ }
+ /* prefix:fmt:data */
+ for (p = fmt; p < data && ret <= 0; p++) {
+ char fn2[sizeof(fn)];
+ if (*p == ' ' || *p == '\t') /* skip blanks */
+ continue;
+ if (*p == '\'') {/* file name - we trim them */
+ char *y;
+ strcpy(fn2, ast_skip_blanks(p+1)); /* make a full copy */
+ y = index(fn2, '\'');
+ if (!y) {
+ p = data; /* invalid. prepare to end */
+ break;
+ }
+ *y = '\0';
+ ast_trim_blanks(fn2);
+ p = index(p+1, '\'');
+ ret = s_streamwait3(a, fn2);
+ } else {
+ int l = fmt-fn;
+ strcpy(fn2, fn); /* copy everything */
+ /* after prefix, append the format */
+ fn2[l++] = *p;
+ strcpy(fn2 + l, data);
+ ret = do_say(a, fn2, options, depth);
+ }
+
+ if (ret) {
+ break;
+ }
+ }
+ }
+ ast_var_delete(n);
+ return ret;
+}
+
+static int say_full(struct ast_channel *chan, const char *string,
+ const char *ints, const char *lang, const char *options,
+ int audiofd, int ctrlfd)
+{
+ say_args_t a = { chan, ints, lang, audiofd, ctrlfd };
+ return do_say(&a, string, options, 0);
+}
+
+static int say_number_full(struct ast_channel *chan, int num,
+ const char *ints, const char *lang, const char *options,
+ int audiofd, int ctrlfd)
+{
+ char buf[64];
+ say_args_t a = { chan, ints, lang, audiofd, ctrlfd };
+ snprintf(buf, sizeof(buf), "num:%d", num);
+ return do_say(&a, buf, options, 0);
+}
+
+static int say_enumeration_full(struct ast_channel *chan, int num,
+ const char *ints, const char *lang, const char *options,
+ int audiofd, int ctrlfd)
+{
+ char buf[64];
+ say_args_t a = { chan, ints, lang, audiofd, ctrlfd };
+ snprintf(buf, sizeof(buf), "enum:%d", num);
+ return do_say(&a, buf, options, 0);
+}
+
+static int say_date_generic(struct ast_channel *chan, time_t t,
+ const char *ints, const char *lang, const char *format, const char *timezone, const char *prefix)
+{
+ char buf[128];
+ struct tm tm;
+ say_args_t a = { chan, ints, lang, -1, -1 };
+ if (format == NULL)
+ format = "";
+
+ ast_localtime(&t, &tm, NULL);
+ snprintf(buf, sizeof(buf), "%s:%s:%04d%02d%02d%02d%02d.%02d-%d-%3d",
+ prefix,
+ format,
+ tm.tm_year+1900,
+ tm.tm_mon+1,
+ tm.tm_mday,
+ tm.tm_hour,
+ tm.tm_min,
+ tm.tm_sec,
+ tm.tm_wday,
+ tm.tm_yday);
+ return do_say(&a, buf, NULL, 0);
+}
+
+static int say_date_with_format(struct ast_channel *chan, time_t t,
+ const char *ints, const char *lang, const char *format, const char *timezone)
+{
+ return say_date_generic(chan, t, ints, lang, format, timezone, "datetime");
+}
+
+static int say_date(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
+{
+ return say_date_generic(chan, t, ints, lang, "", NULL, "date");
+}
+
+static int say_time(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
+{
+ return say_date_generic(chan, t, ints, lang, "", NULL, "time");
+}
+
+static int say_datetime(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
+{
+ return say_date_generic(chan, t, ints, lang, "", NULL, "datetime");
+}
+
+/*
+ * remap the 'say' functions to use those in this file
+ */
+static int __say_init(int fd, int argc, char *argv[])
+{
+ const char *old_mode = say_api_buf[0] ? say_new : say_old;
+ char *mode;
+
+ if (argc == 2) {
+ ast_cli(fd, "say mode is [%s]\n", old_mode);
+ return RESULT_SUCCESS;
+ } else if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ mode = argv[2];
+
+ ast_log(LOG_WARNING, "init say.c from %s to %s\n", old_mode, mode);
+
+ if (!strcmp(mode, old_mode)) {
+ ast_log(LOG_WARNING, "say mode is %s already\n", mode);
+ } else if (!strcmp(mode, say_new)) {
+ if (say_cfg == NULL)
+ say_cfg = ast_config_load("say.conf");
+ save_say_mode(say_new);
+ ast_say_number_full = say_number_full;
+
+ ast_say_enumeration_full = say_enumeration_full;
+#if 0
+ ast_say_digits_full = say_digits_full;
+ ast_say_digit_str_full = say_digit_str_full;
+ ast_say_character_str_full = say_character_str_full;
+ ast_say_phonetic_str_full = say_phonetic_str_full;
+ ast_say_datetime_from_now = say_datetime_from_now;
+#endif
+ ast_say_datetime = say_datetime;
+ ast_say_time = say_time;
+ ast_say_date = say_date;
+ ast_say_date_with_format = say_date_with_format;
+ } else if (!strcmp(mode, say_old) && say_api_buf[0] == say_new) {
+ restore_say_mode(NULL);
+ } else {
+ ast_log(LOG_WARNING, "unrecognized mode %s\n", mode);
+ }
+ return RESULT_SUCCESS;
+}
+
+static struct ast_cli_entry cli_playback[] = {
+ { { "say", "load", NULL },
+ __say_init, "set/show the say mode",
+ "Usage: say load [new|old]\n Set/show the say mode\n" },
+};
+
+static int playback_exec(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ int mres = 0;
+ struct ast_module_user *u;
+ char *tmp;
+ int option_skip=0;
+ int option_say=0;
+ int option_noanswer = 0;
+ int priority_jump = 0;
+
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(filenames);
+ AST_APP_ARG(options);
+ );
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "Playback requires an argument (filename)\n");
+ return -1;
+ }
+
+ tmp = ast_strdupa(data);
+
+ u = ast_module_user_add(chan);
+ AST_STANDARD_APP_ARGS(args, tmp);
+
+ if (args.options) {
+ if (strcasestr(args.options, "skip"))
+ option_skip = 1;
+ if (strcasestr(args.options, "say"))
+ option_say = 1;
+ if (strcasestr(args.options, "noanswer"))
+ option_noanswer = 1;
+ if (strchr(args.options, 'j'))
+ priority_jump = 1;
+ }
+
+ if (chan->_state != AST_STATE_UP) {
+ if (option_skip) {
+ /* At the user's option, skip if the line is not up */
+ goto done;
+ } else if (!option_noanswer)
+ /* Otherwise answer unless we're supposed to send this while on-hook */
+ res = ast_answer(chan);
+ }
+ if (!res) {
+ char *back = args.filenames;
+ char *front;
+
+ ast_stopstream(chan);
+ while (!res && (front = strsep(&back, "&"))) {
+ if (option_say)
+ res = say_full(chan, front, "", chan->language, NULL, -1, -1);
+ else
+ res = ast_streamfile(chan, front, chan->language);
+ if (!res) {
+ res = ast_waitstream(chan, "");
+ ast_stopstream(chan);
+ } else {
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s for %s\n", chan->name, (char *)data);
+ if (priority_jump || ast_opt_priority_jumping)
+ ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
+ res = 0;
+ mres = 1;
+ }
+ }
+ }
+done:
+ pbx_builtin_setvar_helper(chan, "PLAYBACKSTATUS", mres ? "FAILED" : "SUCCESS");
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int reload(void)
+{
+ if (say_cfg) {
+ ast_config_destroy(say_cfg);
+ ast_log(LOG_NOTICE, "Reloading say.conf\n");
+ }
+ say_cfg = ast_config_load("say.conf");
+ /*
+ * XXX here we should sort rules according to the same order
+ * we have in pbx.c so we have the same matching behaviour.
+ */
+ return 0;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_cli_unregister_multiple(cli_playback, sizeof(cli_playback) / sizeof(struct ast_cli_entry));
+
+ ast_module_user_hangup_all();
+
+ if (say_cfg)
+ ast_config_destroy(say_cfg);
+
+ return res;
+}
+
+static int load_module(void)
+{
+ say_cfg = ast_config_load("say.conf");
+ ast_cli_register_multiple(cli_playback, sizeof(cli_playback) / sizeof(struct ast_cli_entry));
+ return ast_register_application(app, playback_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Sound File Playback Application",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
diff --git a/apps/app_privacy.c b/apps/app_privacy.c
new file mode 100644
index 000000000..5da93eb40
--- /dev/null
+++ b/apps/app_privacy.c
@@ -0,0 +1,232 @@
+/*
+ * 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 Block all calls without Caller*ID, require phone # to be entered
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/utils.h"
+#include "asterisk/logger.h"
+#include "asterisk/options.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/image.h"
+#include "asterisk/callerid.h"
+#include "asterisk/app.h"
+#include "asterisk/config.h"
+
+#define PRIV_CONFIG "privacy.conf"
+
+static char *app = "PrivacyManager";
+
+static char *synopsis = "Require phone number to be entered, if no CallerID sent";
+
+static char *descrip =
+ " PrivacyManager([maxretries[|minlength[|options]]]): If no Caller*ID \n"
+ "is sent, PrivacyManager answers the channel and asks the caller to\n"
+ "enter their phone number. The caller is given 3 attempts to do so.\n"
+ "The application does nothing if Caller*ID was received on the channel.\n"
+ " Configuration file privacy.conf contains two variables:\n"
+ " maxretries default 3 -maximum number of attempts the caller is allowed \n"
+ " to input a callerid.\n"
+ " minlength default 10 -minimum allowable digits in the input callerid number.\n"
+ "If you don't want to use the config file and have an i/o operation with\n"
+ "every call, you can also specify maxretries and minlength as application\n"
+ "parameters. Doing so supercedes any values set in privacy.conf.\n"
+ "The option string may contain the following character: \n"
+ " 'j' -- jump to n+101 priority after <maxretries> failed attempts to collect\n"
+ " the minlength number of digits.\n"
+ "The application sets the following channel variable upon completion: \n"
+ "PRIVACYMGRSTATUS The status of the privacy manager's attempt to collect \n"
+ " a phone number from the user. A text string that is either:\n"
+ " SUCCESS | FAILED \n"
+;
+
+
+static int privacy_exec (struct ast_channel *chan, void *data)
+{
+ int res=0;
+ int retries;
+ int maxretries = 3;
+ int minlength = 10;
+ int x = 0;
+ const char *s;
+ char phone[30];
+ struct ast_module_user *u;
+ struct ast_config *cfg = NULL;
+ char *parse = NULL;
+ int priority_jump = 0;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(maxretries);
+ AST_APP_ARG(minlength);
+ AST_APP_ARG(options);
+ );
+
+ u = ast_module_user_add(chan);
+
+ if (!ast_strlen_zero(chan->cid.cid_num)) {
+ if (option_verbose > 2)
+ ast_verbose (VERBOSE_PREFIX_3 "CallerID Present: Skipping\n");
+ } else {
+ /*Answer the channel if it is not already*/
+ if (chan->_state != AST_STATE_UP) {
+ res = ast_answer(chan);
+ if (res) {
+ ast_module_user_remove(u);
+ return -1;
+ }
+ }
+
+ if (!ast_strlen_zero(data)) {
+ parse = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ if (args.maxretries) {
+ if (sscanf(args.maxretries, "%d", &x) == 1)
+ maxretries = x;
+ else
+ ast_log(LOG_WARNING, "Invalid max retries argument\n");
+ }
+ if (args.minlength) {
+ if (sscanf(args.minlength, "%d", &x) == 1)
+ minlength = x;
+ else
+ ast_log(LOG_WARNING, "Invalid min length argument\n");
+ }
+ if (args.options)
+ if (strchr(args.options, 'j'))
+ priority_jump = 1;
+
+ }
+
+ if (!x)
+ {
+ /*Read in the config file*/
+ cfg = ast_config_load(PRIV_CONFIG);
+
+ if (cfg && (s = ast_variable_retrieve(cfg, "general", "maxretries"))) {
+ if (sscanf(s, "%d", &x) == 1)
+ maxretries = x;
+ else
+ ast_log(LOG_WARNING, "Invalid max retries argument\n");
+ }
+
+ if (cfg && (s = ast_variable_retrieve(cfg, "general", "minlength"))) {
+ if (sscanf(s, "%d", &x) == 1)
+ minlength = x;
+ else
+ ast_log(LOG_WARNING, "Invalid min length argument\n");
+ }
+ }
+
+ /*Play unidentified call*/
+ res = ast_safe_sleep(chan, 1000);
+ if (!res)
+ res = ast_streamfile(chan, "privacy-unident", chan->language);
+ if (!res)
+ res = ast_waitstream(chan, "");
+
+ /*Ask for 10 digit number, give 3 attempts*/
+ for (retries = 0; retries < maxretries; retries++) {
+ if (!res)
+ res = ast_streamfile(chan, "privacy-prompt", chan->language);
+ if (!res)
+ res = ast_waitstream(chan, "");
+
+ if (!res )
+ res = ast_readstring(chan, phone, sizeof(phone) - 1, /* digit timeout ms */ 3200, /* first digit timeout */ 5000, "#");
+
+ if (res < 0)
+ break;
+
+ /*Make sure we get at least digits*/
+ if (strlen(phone) >= minlength )
+ break;
+ else {
+ res = ast_streamfile(chan, "privacy-incorrect", chan->language);
+ if (!res)
+ res = ast_waitstream(chan, "");
+ }
+ }
+
+ /*Got a number, play sounds and send them on their way*/
+ if ((retries < maxretries) && res >= 0 ) {
+ res = ast_streamfile(chan, "privacy-thankyou", chan->language);
+ if (!res)
+ res = ast_waitstream(chan, "");
+
+ ast_set_callerid (chan, phone, "Privacy Manager", NULL);
+
+ /* Clear the unavailable presence bit so if it came in on PRI
+ * the caller id will now be passed out to other channels
+ */
+ chan->cid.cid_pres &= (AST_PRES_UNAVAILABLE ^ 0xFF);
+
+ if (option_verbose > 2) {
+ ast_verbose (VERBOSE_PREFIX_3 "Changed Caller*ID to %s, callerpres to %d\n",phone,chan->cid.cid_pres);
+ }
+ pbx_builtin_setvar_helper(chan, "PRIVACYMGRSTATUS", "SUCCESS");
+ } else {
+ if (priority_jump || ast_opt_priority_jumping)
+ ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
+ pbx_builtin_setvar_helper(chan, "PRIVACYMGRSTATUS", "FAILED");
+ }
+ if (cfg)
+ ast_config_destroy(cfg);
+ }
+
+ ast_module_user_remove(u);
+
+ return 0;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application (app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application (app, privacy_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Require phone number to be entered, if no CallerID sent");
diff --git a/apps/app_queue.c b/apps/app_queue.c
new file mode 100644
index 000000000..474ba9d88
--- /dev/null
+++ b/apps/app_queue.c
@@ -0,0 +1,5140 @@
+/*
+ * 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 True call queues with optional send URL on answer
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \arg Config in \ref Config_qu queues.conf
+ *
+ * \par Development notes
+ * \note 2004-11-25: Persistent Dynamic Members added by:
+ * NetNation Communications (www.netnation.com)
+ * Kevin Lindsay <kevinl@netnation.com>
+ *
+ * Each dynamic agent in each queue is now stored in the astdb.
+ * When asterisk is restarted, each agent will be automatically
+ * readded into their recorded queues. This feature can be
+ * configured with the 'persistent_members=<1|0>' setting in the
+ * '[general]' category in queues.conf. The default is on.
+ *
+ * \note 2004-06-04: Priorities in queues added by inAccess Networks (work funded by Hellas On Line (HOL) www.hol.gr).
+ *
+ * \note These features added by David C. Troy <dave@toad.net>:
+ * - Per-queue holdtime calculation
+ * - Estimated holdtime announcement
+ * - Position announcement
+ * - Abandoned/completed call counters
+ * - Failout timer passed as optional app parameter
+ * - Optional monitoring of calls, started when call is answered
+ *
+ * Patch Version 1.07 2003-12-24 01
+ *
+ * Added servicelevel statistic by Michiel Betel <michiel@betel.nl>
+ * Added Priority jumping code for adding and removing queue members by Jonathan Stanton <asterisk@doilooklikeicare.com>
+ *
+ * Fixed to work with CVS as of 2004-02-25 and released as 1.07a
+ * by Matthew Enger <m.enger@xi.com.au>
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+ <depend>res_monitor</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#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/app.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/say.h"
+#include "asterisk/features.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/cli.h"
+#include "asterisk/manager.h"
+#include "asterisk/config.h"
+#include "asterisk/monitor.h"
+#include "asterisk/utils.h"
+#include "asterisk/causes.h"
+#include "asterisk/astdb.h"
+#include "asterisk/devicestate.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/global_datastores.h"
+
+/* Please read before modifying this file.
+ * There are three locks which are regularly used
+ * throughout this file, the queue list lock, the lock
+ * for each individual queue, and the interface list lock.
+ * Please be extra careful to always lock in the following order
+ * 1) queue list lock
+ * 2) individual queue lock
+ * 3) interface list lock
+ * This order has sort of "evolved" over the lifetime of this
+ * application, but it is now in place this way, so please adhere
+ * to this order!
+ */
+
+
+enum {
+ QUEUE_STRATEGY_RINGALL = 0,
+ QUEUE_STRATEGY_ROUNDROBIN,
+ QUEUE_STRATEGY_LEASTRECENT,
+ QUEUE_STRATEGY_FEWESTCALLS,
+ QUEUE_STRATEGY_RANDOM,
+ QUEUE_STRATEGY_RRMEMORY
+};
+
+static struct strategy {
+ int strategy;
+ char *name;
+} strategies[] = {
+ { QUEUE_STRATEGY_RINGALL, "ringall" },
+ { QUEUE_STRATEGY_ROUNDROBIN, "roundrobin" },
+ { QUEUE_STRATEGY_LEASTRECENT, "leastrecent" },
+ { QUEUE_STRATEGY_FEWESTCALLS, "fewestcalls" },
+ { QUEUE_STRATEGY_RANDOM, "random" },
+ { QUEUE_STRATEGY_RRMEMORY, "rrmemory" },
+};
+
+#define DEFAULT_RETRY 5
+#define DEFAULT_TIMEOUT 15
+#define RECHECK 1 /* Recheck every second to see we we're at the top yet */
+#define MAX_PERIODIC_ANNOUNCEMENTS 10 /* The maximum periodic announcements we can have */
+
+#define RES_OKAY 0 /* Action completed */
+#define RES_EXISTS (-1) /* Entry already exists */
+#define RES_OUTOFMEMORY (-2) /* Out of memory */
+#define RES_NOSUCHQUEUE (-3) /* No such queue */
+#define RES_NOT_DYNAMIC (-4) /* Member is not dynamic */
+
+static char *app = "Queue";
+
+static char *synopsis = "Queue a call for a call queue";
+
+static char *descrip =
+" Queue(queuename[|options[|URL][|announceoverride][|timeout][|AGI]):\n"
+"Queues an incoming call in a particular call queue as defined in queues.conf.\n"
+"This application will return to the dialplan if the queue does not exist, or\n"
+"any of the join options cause the caller to not enter the queue.\n"
+"The option string may contain zero or more of the following characters:\n"
+" 'd' -- data-quality (modem) call (minimum delay).\n"
+" 'h' -- allow callee to hang up by hitting '*', or whatver disconnect sequence\n"
+" defined in the featuremap section in features.conf.\n"
+" 'H' -- allow caller to hang up by hitting '*', or whatever disconnect sequence\n"
+" defined in the featuremap section in features.conf.\n"
+" 'n' -- no retries on the timeout; will exit this application and \n"
+" go to the next step.\n"
+" 'i' -- ignore call forward requests from queue members and do nothing\n"
+" when they are requested.\n"
+" 'r' -- ring instead of playing MOH\n"
+" 't' -- allow the called user transfer the calling user by pressing '#' or\n"
+" whatever blindxfer sequence defined in the featuremap section in\n"
+" features.conf\n"
+" 'T' -- to allow the calling user to transfer the call by pressing '#' or\n"
+" whatever blindxfer sequence defined in the featuremap section in\n"
+" features.conf\n"
+" 'w' -- allow the called user to write the conversation to disk via Monitor\n"
+" by pressing the automon sequence defined in the featuremap section in\n"
+" features.conf\n"
+" 'W' -- allow the calling user to write the conversation to disk via Monitor\n"
+" by pressing the automon sequence defined in the featuremap section in\n"
+" features.conf\n"
+" In addition to transferring the call, a call may be parked and then picked\n"
+"up by another user, by transferring to the parking lot extension. See features.conf.\n"
+" The optional URL will be sent to the called party if the channel supports\n"
+"it.\n"
+" The optional AGI parameter will setup an AGI script to be executed on the \n"
+"calling party's channel once they are connected to a queue member.\n"
+" The timeout will cause the queue to fail out after a specified number of\n"
+"seconds, checked between each queues.conf 'timeout' and 'retry' cycle.\n"
+" This application sets the following channel variable upon completion:\n"
+" QUEUESTATUS The status of the call as a text string, one of\n"
+" TIMEOUT | FULL | JOINEMPTY | LEAVEEMPTY | JOINUNAVAIL | LEAVEUNAVAIL\n";
+
+static char *app_aqm = "AddQueueMember" ;
+static char *app_aqm_synopsis = "Dynamically adds queue members" ;
+static char *app_aqm_descrip =
+" AddQueueMember(queuename[|interface[|penalty[|options[|membername]]]]):\n"
+"Dynamically adds interface to an existing queue.\n"
+"If the interface is already in the queue and there exists an n+101 priority\n"
+"then it will then jump to this priority. Otherwise it will return an error\n"
+"The option string may contain zero or more of the following characters:\n"
+" 'j' -- jump to +101 priority when appropriate.\n"
+" This application sets the following channel variable upon completion:\n"
+" AQMSTATUS The status of the attempt to add a queue member as a \n"
+" text string, one of\n"
+" ADDED | MEMBERALREADY | NOSUCHQUEUE \n"
+"Example: AddQueueMember(techsupport|SIP/3000)\n"
+"";
+
+static char *app_rqm = "RemoveQueueMember" ;
+static char *app_rqm_synopsis = "Dynamically removes queue members" ;
+static char *app_rqm_descrip =
+" RemoveQueueMember(queuename[|interface[|options]]):\n"
+"Dynamically removes interface to an existing queue\n"
+"If the interface is NOT in the queue and there exists an n+101 priority\n"
+"then it will then jump to this priority. Otherwise it will return an error\n"
+"The option string may contain zero or more of the following characters:\n"
+" 'j' -- jump to +101 priority when appropriate.\n"
+" This application sets the following channel variable upon completion:\n"
+" RQMSTATUS The status of the attempt to remove a queue member as a\n"
+" text string, one of\n"
+" REMOVED | NOTINQUEUE | NOSUCHQUEUE \n"
+"Example: RemoveQueueMember(techsupport|SIP/3000)\n"
+"";
+
+static char *app_pqm = "PauseQueueMember" ;
+static char *app_pqm_synopsis = "Pauses a queue member" ;
+static char *app_pqm_descrip =
+" PauseQueueMember([queuename]|interface[|options]):\n"
+"Pauses (blocks calls for) a queue member.\n"
+"The given interface will be paused in the given queue. This prevents\n"
+"any calls from being sent from the queue to the interface until it is\n"
+"unpaused with UnpauseQueueMember or the manager interface. If no\n"
+"queuename is given, the interface is paused in every queue it is a\n"
+"member of. If the interface is not in the named queue, or if no queue\n"
+"is given and the interface is not in any queue, it will jump to\n"
+"priority n+101, if it exists and the appropriate options are set.\n"
+"The application will fail if the interface is not found and no extension\n"
+"to jump to exists.\n"
+"The option string may contain zero or more of the following characters:\n"
+" 'j' -- jump to +101 priority when appropriate.\n"
+" This application sets the following channel variable upon completion:\n"
+" PQMSTATUS The status of the attempt to pause a queue member as a\n"
+" text string, one of\n"
+" PAUSED | NOTFOUND\n"
+"Example: PauseQueueMember(|SIP/3000)\n";
+
+static char *app_upqm = "UnpauseQueueMember" ;
+static char *app_upqm_synopsis = "Unpauses a queue member" ;
+static char *app_upqm_descrip =
+" UnpauseQueueMember([queuename]|interface[|options]):\n"
+"Unpauses (resumes calls to) a queue member.\n"
+"This is the counterpart to PauseQueueMember and operates exactly the\n"
+"same way, except it unpauses instead of pausing the given interface.\n"
+"The option string may contain zero or more of the following characters:\n"
+" 'j' -- jump to +101 priority when appropriate.\n"
+" This application sets the following channel variable upon completion:\n"
+" UPQMSTATUS The status of the attempt to unpause a queue \n"
+" member as a text string, one of\n"
+" UNPAUSED | NOTFOUND\n"
+"Example: UnpauseQueueMember(|SIP/3000)\n";
+
+static char *app_ql = "QueueLog" ;
+static char *app_ql_synopsis = "Writes to the queue_log" ;
+static char *app_ql_descrip =
+" QueueLog(queuename|uniqueid|agent|event[|additionalinfo]):\n"
+"Allows you to write your own events into the queue log\n"
+"Example: QueueLog(101|${UNIQUEID}|${AGENT}|WENTONBREAK|600)\n";
+
+/*! \brief Persistent Members astdb family */
+static const char *pm_family = "Queue/PersistentMembers";
+/* The maximum length of each persistent member queue database entry */
+#define PM_MAX_LEN 8192
+
+/*! \brief queues.conf [general] option */
+static int queue_persistent_members = 0;
+
+/*! \brief queues.conf per-queue weight option */
+static int use_weight = 0;
+
+/*! \brief queues.conf [general] option */
+static int autofill_default = 0;
+
+/*! \brief queues.conf [general] option */
+static int montype_default = 0;
+
+enum queue_result {
+ QUEUE_UNKNOWN = 0,
+ QUEUE_TIMEOUT = 1,
+ QUEUE_JOINEMPTY = 2,
+ QUEUE_LEAVEEMPTY = 3,
+ QUEUE_JOINUNAVAIL = 4,
+ QUEUE_LEAVEUNAVAIL = 5,
+ QUEUE_FULL = 6,
+};
+
+const struct {
+ enum queue_result id;
+ char *text;
+} queue_results[] = {
+ { QUEUE_UNKNOWN, "UNKNOWN" },
+ { QUEUE_TIMEOUT, "TIMEOUT" },
+ { QUEUE_JOINEMPTY,"JOINEMPTY" },
+ { QUEUE_LEAVEEMPTY, "LEAVEEMPTY" },
+ { QUEUE_JOINUNAVAIL, "JOINUNAVAIL" },
+ { QUEUE_LEAVEUNAVAIL, "LEAVEUNAVAIL" },
+ { QUEUE_FULL, "FULL" },
+};
+
+/*! \brief We define a custom "local user" structure because we
+ use it not only for keeping track of what is in use but
+ also for keeping track of who we're dialing.
+
+ There are two "links" defined in this structure, q_next and call_next.
+ q_next links ALL defined callattempt structures into a linked list. call_next is
+ a link which allows for a subset of the callattempts to be traversed. This subset
+ is used in wait_for_answer so that irrelevant callattempts are not traversed. This
+ also is helpful so that queue logs are always accurate in the case where a call to
+ a member times out, especially if using the ringall strategy. */
+
+struct callattempt {
+ struct callattempt *q_next;
+ struct callattempt *call_next;
+ struct ast_channel *chan;
+ char interface[256];
+ int stillgoing;
+ int metric;
+ int oldstatus;
+ time_t lastcall;
+ struct member *member;
+};
+
+
+struct queue_ent {
+ struct call_queue *parent; /*!< What queue is our parent */
+ char moh[80]; /*!< Name of musiconhold to be used */
+ char announce[80]; /*!< Announcement to play for member when call is answered */
+ char context[AST_MAX_CONTEXT]; /*!< Context when user exits queue */
+ char digits[AST_MAX_EXTENSION]; /*!< Digits entered while in queue */
+ int valid_digits; /*!< Digits entered correspond to valid extension. Exited */
+ int pos; /*!< Where we are in the queue */
+ int prio; /*!< Our priority */
+ int last_pos_said; /*!< Last position we told the user */
+ time_t last_periodic_announce_time; /*!< The last time we played a periodic announcement */
+ int last_periodic_announce_sound; /*!< The last periodic announcement we made */
+ time_t last_pos; /*!< Last time we told the user their position */
+ int opos; /*!< Where we started in the queue */
+ int handled; /*!< Whether our call was handled */
+ int pending; /*!< Non-zero if we are attempting to call a member */
+ int max_penalty; /*!< Limit the members that can take this call to this penalty or lower */
+ time_t start; /*!< When we started holding */
+ time_t expire; /*!< When this entry should expire (time out of queue) */
+ struct ast_channel *chan; /*!< Our channel */
+ struct queue_ent *next; /*!< The next queue entry */
+};
+
+struct member {
+ char interface[80]; /*!< Technology/Location */
+ char membername[80]; /*!< Member name to use in queue logs */
+ int penalty; /*!< Are we a last resort? */
+ int calls; /*!< Number of calls serviced by this member */
+ int dynamic; /*!< Are we dynamically added? */
+ int realtime; /*!< Is this member realtime? */
+ int status; /*!< Status of queue member */
+ int paused; /*!< Are we paused (not accepting calls)? */
+ time_t lastcall; /*!< When last successful call was hungup */
+ unsigned int dead:1; /*!< Used to detect members deleted in realtime */
+ unsigned int delme:1; /*!< Flag to delete entry on reload */
+};
+
+struct member_interface {
+ char interface[80];
+ AST_LIST_ENTRY(member_interface) list; /*!< Next call queue */
+};
+
+static AST_LIST_HEAD_STATIC(interfaces, member_interface);
+
+/* values used in multi-bit flags in call_queue */
+#define QUEUE_EMPTY_NORMAL 1
+#define QUEUE_EMPTY_STRICT 2
+#define ANNOUNCEHOLDTIME_ALWAYS 1
+#define ANNOUNCEHOLDTIME_ONCE 2
+#define QUEUE_EVENT_VARIABLES 3
+
+struct call_queue {
+ ast_mutex_t lock;
+ char name[80]; /*!< Name */
+ char moh[80]; /*!< Music On Hold class to be used */
+ char announce[80]; /*!< Announcement to play when call is answered */
+ char context[AST_MAX_CONTEXT]; /*!< Exit context */
+ unsigned int monjoin:1;
+ unsigned int dead:1;
+ unsigned int joinempty:2;
+ unsigned int eventwhencalled:2;
+ unsigned int leavewhenempty:2;
+ unsigned int ringinuse:1;
+ unsigned int setinterfacevar:1;
+ unsigned int reportholdtime:1;
+ unsigned int wrapped:1;
+ unsigned int timeoutrestart:1;
+ unsigned int announceholdtime:2;
+ int strategy:4;
+ unsigned int maskmemberstatus:1;
+ unsigned int realtime:1;
+ unsigned int found:1;
+ int announcefrequency; /*!< How often to announce their position */
+ int periodicannouncefrequency; /*!< How often to play periodic announcement */
+ int roundingseconds; /*!< How many seconds do we round to? */
+ int holdtime; /*!< Current avg holdtime, based on an exponential average */
+ int callscompleted; /*!< Number of queue calls completed */
+ int callsabandoned; /*!< Number of queue calls abandoned */
+ int servicelevel; /*!< seconds setting for servicelevel*/
+ int callscompletedinsl; /*!< Number of calls answered with servicelevel*/
+ char monfmt[8]; /*!< Format to use when recording calls */
+ int montype; /*!< Monitor type Monitor vs. MixMonitor */
+ char sound_next[80]; /*!< Sound file: "Your call is now first in line" (def. queue-youarenext) */
+ char sound_thereare[80]; /*!< Sound file: "There are currently" (def. queue-thereare) */
+ char sound_calls[80]; /*!< Sound file: "calls waiting to speak to a representative." (def. queue-callswaiting)*/
+ char sound_holdtime[80]; /*!< Sound file: "The current estimated total holdtime is" (def. queue-holdtime) */
+ char sound_minutes[80]; /*!< Sound file: "minutes." (def. queue-minutes) */
+ char sound_lessthan[80]; /*!< Sound file: "less-than" (def. queue-lessthan) */
+ char sound_seconds[80]; /*!< Sound file: "seconds." (def. queue-seconds) */
+ char sound_thanks[80]; /*!< Sound file: "Thank you for your patience." (def. queue-thankyou) */
+ char sound_reporthold[80]; /*!< Sound file: "Hold time" (def. queue-reporthold) */
+ char sound_periodicannounce[MAX_PERIODIC_ANNOUNCEMENTS][80];/*!< Sound files: Custom announce, no default */
+
+ int count; /*!< How many entries */
+ int maxlen; /*!< Max number of entries */
+ int wrapuptime; /*!< Wrapup Time */
+
+ int retry; /*!< Retry calling everyone after this amount of time */
+ int timeout; /*!< How long to wait for an answer */
+ int weight; /*!< Respective weight */
+ int autopause; /*!< Auto pause queue members if they fail to answer */
+
+ /* Queue strategy things */
+ int rrpos; /*!< Round Robin - position */
+ int memberdelay; /*!< Seconds to delay connecting member to caller */
+ int autofill; /*!< Ignore the head call status and ring an available agent */
+
+ struct ao2_container *members; /*!< Head of the list of members */
+ /*!
+ * \brief Number of members _logged in_
+ * \note There will be members in the members container that are not logged
+ * in, so this can not simply be replaced with ao2_container_count().
+ */
+ int membercount;
+ struct queue_ent *head; /*!< Head of the list of callers */
+ AST_LIST_ENTRY(call_queue) list; /*!< Next call queue */
+};
+
+static AST_LIST_HEAD_STATIC(queues, call_queue);
+
+static int set_member_paused(const char *queuename, const char *interface, int paused);
+
+static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan);
+
+static void rr_dep_warning(void)
+{
+ static unsigned int warned = 0;
+
+ if (!warned) {
+ ast_log(LOG_NOTICE, "The 'roundrobin' queue strategy is deprecated. Please use the 'rrmemory' strategy instead.\n");
+ warned = 1;
+ }
+}
+
+static void monjoin_dep_warning(void)
+{
+ static unsigned int warned = 0;
+ if (!warned) {
+ ast_log(LOG_NOTICE, "The 'monitor-join' queue option is deprecated. Please use monitor-type=mixmonitor instead.\n");
+ warned = 1;
+ }
+}
+/*! \brief sets the QUEUESTATUS channel variable */
+static void set_queue_result(struct ast_channel *chan, enum queue_result res)
+{
+ int i;
+
+ for (i = 0; i < sizeof(queue_results) / sizeof(queue_results[0]); i++) {
+ if (queue_results[i].id == res) {
+ pbx_builtin_setvar_helper(chan, "QUEUESTATUS", queue_results[i].text);
+ return;
+ }
+ }
+}
+
+static char *int2strat(int strategy)
+{
+ int x;
+
+ for (x = 0; x < sizeof(strategies) / sizeof(strategies[0]); x++) {
+ if (strategy == strategies[x].strategy)
+ return strategies[x].name;
+ }
+
+ return "<unknown>";
+}
+
+static int strat2int(const char *strategy)
+{
+ int x;
+
+ for (x = 0; x < sizeof(strategies) / sizeof(strategies[0]); x++) {
+ if (!strcasecmp(strategy, strategies[x].name))
+ return strategies[x].strategy;
+ }
+
+ return -1;
+}
+
+/*! \brief Insert the 'new' entry after the 'prev' entry of queue 'q' */
+static inline void insert_entry(struct call_queue *q, struct queue_ent *prev, struct queue_ent *new, int *pos)
+{
+ struct queue_ent *cur;
+
+ if (!q || !new)
+ return;
+ if (prev) {
+ cur = prev->next;
+ prev->next = new;
+ } else {
+ cur = q->head;
+ q->head = new;
+ }
+ new->next = cur;
+ new->parent = q;
+ new->pos = ++(*pos);
+ new->opos = *pos;
+}
+
+enum queue_member_status {
+ QUEUE_NO_MEMBERS,
+ QUEUE_NO_REACHABLE_MEMBERS,
+ QUEUE_NORMAL
+};
+
+/*! \brief Check if members are available
+ *
+ * This function checks to see if members are available to be called. If any member
+ * is available, the function immediately returns QUEUE_NORMAL. If no members are available,
+ * the appropriate reason why is returned
+ */
+static enum queue_member_status get_member_status(struct call_queue *q, int max_penalty)
+{
+ struct member *member;
+ struct ao2_iterator mem_iter;
+ enum queue_member_status result = QUEUE_NO_MEMBERS;
+
+ ast_mutex_lock(&q->lock);
+ mem_iter = ao2_iterator_init(q->members, 0);
+ while ((member = ao2_iterator_next(&mem_iter))) {
+ if (max_penalty && (member->penalty > max_penalty)) {
+ ao2_ref(member, -1);
+ continue;
+ }
+
+ if (member->paused) {
+ ao2_ref(member, -1);
+ continue;
+ }
+
+ switch (member->status) {
+ case AST_DEVICE_INVALID:
+ /* nothing to do */
+ ao2_ref(member, -1);
+ break;
+ case AST_DEVICE_UNAVAILABLE:
+ result = QUEUE_NO_REACHABLE_MEMBERS;
+ ao2_ref(member, -1);
+ break;
+ default:
+ ast_mutex_unlock(&q->lock);
+ ao2_ref(member, -1);
+ return QUEUE_NORMAL;
+ }
+ }
+
+ ast_mutex_unlock(&q->lock);
+ return result;
+}
+
+struct statechange {
+ AST_LIST_ENTRY(statechange) entry;
+ int state;
+ char dev[0];
+};
+
+static int update_status(const char *interface, const int status)
+{
+ struct member *cur;
+ struct ao2_iterator mem_iter;
+ struct call_queue *q;
+
+ AST_LIST_LOCK(&queues);
+ AST_LIST_TRAVERSE(&queues, q, list) {
+ ast_mutex_lock(&q->lock);
+ mem_iter = ao2_iterator_init(q->members, 0);
+ while ((cur = ao2_iterator_next(&mem_iter))) {
+ char *tmp_interface;
+ char *slash_pos;
+ tmp_interface = ast_strdupa(cur->interface);
+ if ((slash_pos = strchr(tmp_interface, '/')))
+ if ((slash_pos = strchr(slash_pos + 1, '/')))
+ *slash_pos = '\0';
+
+ if (strcasecmp(interface, tmp_interface)) {
+ ao2_ref(cur, -1);
+ continue;
+ }
+
+ if (cur->status != status) {
+ cur->status = status;
+ if (q->maskmemberstatus) {
+ ao2_ref(cur, -1);
+ continue;
+ }
+
+ manager_event(EVENT_FLAG_AGENT, "QueueMemberStatus",
+ "Queue: %s\r\n"
+ "Location: %s\r\n"
+ "MemberName: %s\r\n"
+ "Membership: %s\r\n"
+ "Penalty: %d\r\n"
+ "CallsTaken: %d\r\n"
+ "LastCall: %d\r\n"
+ "Status: %d\r\n"
+ "Paused: %d\r\n",
+ q->name, cur->interface, cur->membername, cur->dynamic ? "dynamic" : cur->realtime ? "realtime" : "static",
+ cur->penalty, cur->calls, (int)cur->lastcall, cur->status, cur->paused);
+ }
+ ao2_ref(cur, -1);
+ }
+ ast_mutex_unlock(&q->lock);
+ }
+ AST_LIST_UNLOCK(&queues);
+
+ return 0;
+}
+
+/*! \brief set a member's status based on device state of that member's interface*/
+static void *handle_statechange(struct statechange *sc)
+{
+ struct member_interface *curint;
+ char *loc;
+ char *technology;
+
+ technology = ast_strdupa(sc->dev);
+ loc = strchr(technology, '/');
+ if (loc) {
+ *loc++ = '\0';
+ } else {
+ return NULL;
+ }
+
+ AST_LIST_LOCK(&interfaces);
+ AST_LIST_TRAVERSE(&interfaces, curint, list) {
+ char *interface;
+ char *slash_pos;
+ interface = ast_strdupa(curint->interface);
+ if ((slash_pos = strchr(interface, '/')))
+ if ((slash_pos = strchr(slash_pos + 1, '/')))
+ *slash_pos = '\0';
+
+ if (!strcasecmp(interface, sc->dev))
+ break;
+ }
+ AST_LIST_UNLOCK(&interfaces);
+
+ if (!curint) {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Device '%s/%s' changed to state '%d' (%s) but we don't care because they're not a member of any queue.\n", technology, loc, sc->state, devstate2str(sc->state));
+ return NULL;
+ }
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Device '%s/%s' changed to state '%d' (%s)\n", technology, loc, sc->state, devstate2str(sc->state));
+
+ update_status(sc->dev, sc->state);
+
+ return NULL;
+}
+
+/*!
+ * \brief Data used by the device state thread
+ */
+static struct {
+ /*! Set to 1 to stop the thread */
+ unsigned int stop:1;
+ /*! The device state monitoring thread */
+ pthread_t thread;
+ /*! Lock for the state change queue */
+ ast_mutex_t lock;
+ /*! Condition for the state change queue */
+ ast_cond_t cond;
+ /*! Queue of state changes */
+ AST_LIST_HEAD_NOLOCK(, statechange) state_change_q;
+} device_state = {
+ .thread = AST_PTHREADT_NULL,
+};
+
+/*! \brief Consumer of the statechange queue */
+static void *device_state_thread(void *data)
+{
+ struct statechange *sc = NULL;
+
+ while (!device_state.stop) {
+ ast_mutex_lock(&device_state.lock);
+ if (!(sc = AST_LIST_REMOVE_HEAD(&device_state.state_change_q, entry))) {
+ ast_cond_wait(&device_state.cond, &device_state.lock);
+ sc = AST_LIST_REMOVE_HEAD(&device_state.state_change_q, entry);
+ }
+ ast_mutex_unlock(&device_state.lock);
+
+ /* Check to see if we were woken up to see the request to stop */
+ if (device_state.stop)
+ break;
+
+ if (!sc)
+ continue;
+
+ handle_statechange(sc);
+
+ free(sc);
+ sc = NULL;
+ }
+
+ if (sc)
+ free(sc);
+
+ while ((sc = AST_LIST_REMOVE_HEAD(&device_state.state_change_q, entry)))
+ free(sc);
+
+ return NULL;
+}
+/*! \brief Producer of the statechange queue */
+static int statechange_queue(const char *dev, int state, void *ign)
+{
+ struct statechange *sc;
+
+ if (!(sc = ast_calloc(1, sizeof(*sc) + strlen(dev) + 1)))
+ return 0;
+
+ sc->state = state;
+ strcpy(sc->dev, dev);
+
+ ast_mutex_lock(&device_state.lock);
+ AST_LIST_INSERT_TAIL(&device_state.state_change_q, sc, entry);
+ ast_cond_signal(&device_state.cond);
+ ast_mutex_unlock(&device_state.lock);
+
+ return 0;
+}
+/*! \brief allocate space for new queue member and set fields based on parameters passed */
+static struct member *create_queue_member(const char *interface, const char *membername, int penalty, int paused)
+{
+ struct member *cur;
+
+ if ((cur = ao2_alloc(sizeof(*cur), NULL))) {
+ cur->penalty = penalty;
+ cur->paused = paused;
+ ast_copy_string(cur->interface, interface, sizeof(cur->interface));
+ if (!ast_strlen_zero(membername))
+ ast_copy_string(cur->membername, membername, sizeof(cur->membername));
+ else
+ ast_copy_string(cur->membername, interface, sizeof(cur->membername));
+ if (!strchr(cur->interface, '/'))
+ ast_log(LOG_WARNING, "No location at interface '%s'\n", interface);
+ cur->status = ast_device_state(interface);
+ }
+
+ return cur;
+}
+
+static struct call_queue *alloc_queue(const char *queuename)
+{
+ struct call_queue *q;
+
+ if ((q = ast_calloc(1, sizeof(*q)))) {
+ ast_mutex_init(&q->lock);
+ ast_copy_string(q->name, queuename, sizeof(q->name));
+ }
+ return q;
+}
+
+static int compress_char(const char c)
+{
+ if (c < 32)
+ return 0;
+ else if (c > 96)
+ return c - 64;
+ else
+ return c - 32;
+}
+
+static int member_hash_fn(const void *obj, const int flags)
+{
+ const struct member *mem = obj;
+ const char *chname = strchr(mem->interface, '/');
+ int ret = 0, i;
+ if (!chname)
+ chname = mem->interface;
+ for (i = 0; i < 5 && chname[i]; i++)
+ ret += compress_char(chname[i]) << (i * 6);
+ return ret;
+}
+
+static int member_cmp_fn(void *obj1, void *obj2, int flags)
+{
+ struct member *mem1 = obj1, *mem2 = obj2;
+ return strcmp(mem1->interface, mem2->interface) ? 0 : CMP_MATCH | CMP_STOP;
+}
+
+static void init_queue(struct call_queue *q)
+{
+ int i;
+
+ q->dead = 0;
+ q->retry = DEFAULT_RETRY;
+ q->timeout = -1;
+ q->maxlen = 0;
+ q->announcefrequency = 0;
+ q->announceholdtime = 0;
+ q->roundingseconds = 0; /* Default - don't announce seconds */
+ q->servicelevel = 0;
+ q->ringinuse = 1;
+ q->setinterfacevar = 0;
+ q->autofill = autofill_default;
+ q->montype = montype_default;
+ q->moh[0] = '\0';
+ q->announce[0] = '\0';
+ q->context[0] = '\0';
+ q->monfmt[0] = '\0';
+ q->periodicannouncefrequency = 0;
+ q->reportholdtime = 0;
+ q->monjoin = 0;
+ q->wrapuptime = 0;
+ q->joinempty = 0;
+ q->leavewhenempty = 0;
+ q->memberdelay = 0;
+ q->maskmemberstatus = 0;
+ q->eventwhencalled = 0;
+ q->weight = 0;
+ q->timeoutrestart = 0;
+ if (!q->members)
+ q->members = ao2_container_alloc(37, member_hash_fn, member_cmp_fn);
+ q->membercount = 0;
+ q->found = 1;
+ ast_copy_string(q->sound_next, "queue-youarenext", sizeof(q->sound_next));
+ ast_copy_string(q->sound_thereare, "queue-thereare", sizeof(q->sound_thereare));
+ ast_copy_string(q->sound_calls, "queue-callswaiting", sizeof(q->sound_calls));
+ ast_copy_string(q->sound_holdtime, "queue-holdtime", sizeof(q->sound_holdtime));
+ ast_copy_string(q->sound_minutes, "queue-minutes", sizeof(q->sound_minutes));
+ ast_copy_string(q->sound_seconds, "queue-seconds", sizeof(q->sound_seconds));
+ ast_copy_string(q->sound_thanks, "queue-thankyou", sizeof(q->sound_thanks));
+ ast_copy_string(q->sound_lessthan, "queue-less-than", sizeof(q->sound_lessthan));
+ ast_copy_string(q->sound_reporthold, "queue-reporthold", sizeof(q->sound_reporthold));
+ ast_copy_string(q->sound_periodicannounce[0], "queue-periodic-announce", sizeof(q->sound_periodicannounce[0]));
+ for (i = 1; i < MAX_PERIODIC_ANNOUNCEMENTS; i++) {
+ q->sound_periodicannounce[i][0]='\0';
+ }
+}
+
+static void clear_queue(struct call_queue *q)
+{
+ q->holdtime = 0;
+ q->callscompleted = 0;
+ q->callsabandoned = 0;
+ q->callscompletedinsl = 0;
+ q->wrapuptime = 0;
+}
+
+static int add_to_interfaces(const char *interface)
+{
+ struct member_interface *curint;
+
+ AST_LIST_LOCK(&interfaces);
+ AST_LIST_TRAVERSE(&interfaces, curint, list) {
+ if (!strcasecmp(curint->interface, interface))
+ break;
+ }
+
+ if (curint) {
+ AST_LIST_UNLOCK(&interfaces);
+ return 0;
+ }
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Adding %s to the list of interfaces that make up all of our queue members.\n", interface);
+
+ if ((curint = ast_calloc(1, sizeof(*curint)))) {
+ ast_copy_string(curint->interface, interface, sizeof(curint->interface));
+ AST_LIST_INSERT_HEAD(&interfaces, curint, list);
+ }
+ AST_LIST_UNLOCK(&interfaces);
+
+ return 0;
+}
+
+static int interface_exists_global(const char *interface)
+{
+ struct call_queue *q;
+ struct member *mem, tmpmem;
+ int ret = 0;
+
+ ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface));
+
+ AST_LIST_LOCK(&queues);
+ AST_LIST_TRAVERSE(&queues, q, list) {
+ ast_mutex_lock(&q->lock);
+ if ((mem = ao2_find(q->members, &tmpmem, OBJ_POINTER))) {
+ ao2_ref(mem, -1);
+ ret = 1;
+ }
+ ast_mutex_unlock(&q->lock);
+ if (ret)
+ break;
+ }
+ AST_LIST_UNLOCK(&queues);
+
+ return ret;
+}
+
+static int remove_from_interfaces(const char *interface)
+{
+ struct member_interface *curint;
+
+ if (interface_exists_global(interface))
+ return 0;
+
+ AST_LIST_LOCK(&interfaces);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&interfaces, curint, list) {
+ if (!strcasecmp(curint->interface, interface)) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Removing %s from the list of interfaces that make up all of our queue members.\n", interface);
+ AST_LIST_REMOVE_CURRENT(&interfaces, list);
+ free(curint);
+ break;
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+ AST_LIST_UNLOCK(&interfaces);
+
+ return 0;
+}
+
+static void clear_and_free_interfaces(void)
+{
+ struct member_interface *curint;
+
+ AST_LIST_LOCK(&interfaces);
+ while ((curint = AST_LIST_REMOVE_HEAD(&interfaces, list)))
+ free(curint);
+ AST_LIST_UNLOCK(&interfaces);
+}
+
+/*! \brief Configure a queue parameter.
+\par
+ For error reporting, line number is passed for .conf static configuration.
+ For Realtime queues, linenum is -1.
+ The failunknown flag is set for config files (and static realtime) to show
+ errors for unknown parameters. It is cleared for dynamic realtime to allow
+ extra fields in the tables. */
+static void queue_set_param(struct call_queue *q, const char *param, const char *val, int linenum, int failunknown)
+{
+ if (!strcasecmp(param, "musicclass") ||
+ !strcasecmp(param, "music") || !strcasecmp(param, "musiconhold")) {
+ ast_copy_string(q->moh, val, sizeof(q->moh));
+ } else if (!strcasecmp(param, "announce")) {
+ ast_copy_string(q->announce, val, sizeof(q->announce));
+ } else if (!strcasecmp(param, "context")) {
+ ast_copy_string(q->context, val, sizeof(q->context));
+ } else if (!strcasecmp(param, "timeout")) {
+ q->timeout = atoi(val);
+ if (q->timeout < 0)
+ q->timeout = DEFAULT_TIMEOUT;
+ } else if (!strcasecmp(param, "ringinuse")) {
+ q->ringinuse = ast_true(val);
+ } else if (!strcasecmp(param, "setinterfacevar")) {
+ q->setinterfacevar = ast_true(val);
+ } else if (!strcasecmp(param, "monitor-join")) {
+ monjoin_dep_warning();
+ q->monjoin = ast_true(val);
+ } else if (!strcasecmp(param, "monitor-format")) {
+ ast_copy_string(q->monfmt, val, sizeof(q->monfmt));
+ } else if (!strcasecmp(param, "queue-youarenext")) {
+ ast_copy_string(q->sound_next, val, sizeof(q->sound_next));
+ } else if (!strcasecmp(param, "queue-thereare")) {
+ ast_copy_string(q->sound_thereare, val, sizeof(q->sound_thereare));
+ } else if (!strcasecmp(param, "queue-callswaiting")) {
+ ast_copy_string(q->sound_calls, val, sizeof(q->sound_calls));
+ } else if (!strcasecmp(param, "queue-holdtime")) {
+ ast_copy_string(q->sound_holdtime, val, sizeof(q->sound_holdtime));
+ } else if (!strcasecmp(param, "queue-minutes")) {
+ ast_copy_string(q->sound_minutes, val, sizeof(q->sound_minutes));
+ } else if (!strcasecmp(param, "queue-seconds")) {
+ ast_copy_string(q->sound_seconds, val, sizeof(q->sound_seconds));
+ } else if (!strcasecmp(param, "queue-lessthan")) {
+ ast_copy_string(q->sound_lessthan, val, sizeof(q->sound_lessthan));
+ } else if (!strcasecmp(param, "queue-thankyou")) {
+ ast_copy_string(q->sound_thanks, val, sizeof(q->sound_thanks));
+ } else if (!strcasecmp(param, "queue-reporthold")) {
+ ast_copy_string(q->sound_reporthold, val, sizeof(q->sound_reporthold));
+ } else if (!strcasecmp(param, "announce-frequency")) {
+ q->announcefrequency = atoi(val);
+ } else if (!strcasecmp(param, "announce-round-seconds")) {
+ q->roundingseconds = atoi(val);
+ if (q->roundingseconds>60 || q->roundingseconds<0) {
+ if (linenum >= 0) {
+ ast_log(LOG_WARNING, "'%s' isn't a valid value for %s "
+ "using 0 instead for queue '%s' at line %d of queues.conf\n",
+ val, param, q->name, linenum);
+ } else {
+ ast_log(LOG_WARNING, "'%s' isn't a valid value for %s "
+ "using 0 instead for queue '%s'\n", val, param, q->name);
+ }
+ q->roundingseconds=0;
+ }
+ } else if (!strcasecmp(param, "announce-holdtime")) {
+ if (!strcasecmp(val, "once"))
+ q->announceholdtime = ANNOUNCEHOLDTIME_ONCE;
+ else if (ast_true(val))
+ q->announceholdtime = ANNOUNCEHOLDTIME_ALWAYS;
+ else
+ q->announceholdtime = 0;
+ } else if (!strcasecmp(param, "periodic-announce")) {
+ if (strchr(val, '|')) {
+ char *s, *buf = ast_strdupa(val);
+ unsigned int i = 0;
+
+ while ((s = strsep(&buf, "|"))) {
+ ast_copy_string(q->sound_periodicannounce[i], s, sizeof(q->sound_periodicannounce[i]));
+ i++;
+ if (i == MAX_PERIODIC_ANNOUNCEMENTS)
+ break;
+ }
+ } else {
+ ast_copy_string(q->sound_periodicannounce[0], val, sizeof(q->sound_periodicannounce[0]));
+ }
+ } else if (!strcasecmp(param, "periodic-announce-frequency")) {
+ q->periodicannouncefrequency = atoi(val);
+ } else if (!strcasecmp(param, "retry")) {
+ q->retry = atoi(val);
+ if (q->retry <= 0)
+ q->retry = DEFAULT_RETRY;
+ } else if (!strcasecmp(param, "wrapuptime")) {
+ q->wrapuptime = atoi(val);
+ } else if (!strcasecmp(param, "autofill")) {
+ q->autofill = ast_true(val);
+ } else if (!strcasecmp(param, "monitor-type")) {
+ if (!strcasecmp(val, "mixmonitor"))
+ q->montype = 1;
+ } else if (!strcasecmp(param, "autopause")) {
+ q->autopause = ast_true(val);
+ } else if (!strcasecmp(param, "maxlen")) {
+ q->maxlen = atoi(val);
+ if (q->maxlen < 0)
+ q->maxlen = 0;
+ } else if (!strcasecmp(param, "servicelevel")) {
+ q->servicelevel= atoi(val);
+ } else if (!strcasecmp(param, "strategy")) {
+ q->strategy = strat2int(val);
+ if (q->strategy < 0) {
+ ast_log(LOG_WARNING, "'%s' isn't a valid strategy for queue '%s', using ringall instead\n",
+ val, q->name);
+ q->strategy = QUEUE_STRATEGY_RINGALL;
+ }
+ } else if (!strcasecmp(param, "joinempty")) {
+ if (!strcasecmp(val, "strict"))
+ q->joinempty = QUEUE_EMPTY_STRICT;
+ else if (ast_true(val))
+ q->joinempty = QUEUE_EMPTY_NORMAL;
+ else
+ q->joinempty = 0;
+ } else if (!strcasecmp(param, "leavewhenempty")) {
+ if (!strcasecmp(val, "strict"))
+ q->leavewhenempty = QUEUE_EMPTY_STRICT;
+ else if (ast_true(val))
+ q->leavewhenempty = QUEUE_EMPTY_NORMAL;
+ else
+ q->leavewhenempty = 0;
+ } else if (!strcasecmp(param, "eventmemberstatus")) {
+ q->maskmemberstatus = !ast_true(val);
+ } else if (!strcasecmp(param, "eventwhencalled")) {
+ if (!strcasecmp(val, "vars")) {
+ q->eventwhencalled = QUEUE_EVENT_VARIABLES;
+ } else {
+ q->eventwhencalled = ast_true(val) ? 1 : 0;
+ }
+ } else if (!strcasecmp(param, "reportholdtime")) {
+ q->reportholdtime = ast_true(val);
+ } else if (!strcasecmp(param, "memberdelay")) {
+ q->memberdelay = atoi(val);
+ } else if (!strcasecmp(param, "weight")) {
+ q->weight = atoi(val);
+ if (q->weight)
+ use_weight++;
+ /* With Realtime queues, if the last queue using weights is deleted in realtime,
+ we will not see any effect on use_weight until next reload. */
+ } else if (!strcasecmp(param, "timeoutrestart")) {
+ q->timeoutrestart = ast_true(val);
+ } else if (failunknown) {
+ if (linenum >= 0) {
+ ast_log(LOG_WARNING, "Unknown keyword in queue '%s': %s at line %d of queues.conf\n",
+ q->name, param, linenum);
+ } else {
+ ast_log(LOG_WARNING, "Unknown keyword in queue '%s': %s\n", q->name, param);
+ }
+ }
+}
+
+static void rt_handle_member_record(struct call_queue *q, char *interface, const char *membername, const char *penalty_str, const char *paused_str)
+{
+ struct member *m, tmpmem;
+ int penalty = 0;
+ int paused = 0;
+
+ if (penalty_str) {
+ penalty = atoi(penalty_str);
+ if (penalty < 0)
+ penalty = 0;
+ }
+
+ if (paused_str) {
+ paused = atoi(paused_str);
+ if (paused < 0)
+ paused = 0;
+ }
+
+ /* Find the member, or the place to put a new one. */
+ ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface));
+ m = ao2_find(q->members, &tmpmem, OBJ_POINTER);
+
+ /* Create a new one if not found, else update penalty */
+ if (!m) {
+ if ((m = create_queue_member(interface, membername, penalty, paused))) {
+ m->dead = 0;
+ m->realtime = 1;
+ add_to_interfaces(interface);
+ ao2_link(q->members, m);
+ ao2_ref(m, -1);
+ m = NULL;
+ q->membercount++;
+ }
+ } else {
+ m->dead = 0; /* Do not delete this one. */
+ if (paused_str)
+ m->paused = paused;
+ m->penalty = penalty;
+ ao2_ref(m, -1);
+ }
+}
+
+static void free_members(struct call_queue *q, int all)
+{
+ /* Free non-dynamic members */
+ struct member *cur;
+ struct ao2_iterator mem_iter = ao2_iterator_init(q->members, 0);
+
+ while ((cur = ao2_iterator_next(&mem_iter))) {
+ if (all || !cur->dynamic) {
+ ao2_unlink(q->members, cur);
+ remove_from_interfaces(cur->interface);
+ q->membercount--;
+ }
+ ao2_ref(cur, -1);
+ }
+}
+
+static void destroy_queue(struct call_queue *q)
+{
+ free_members(q, 1);
+ ast_mutex_destroy(&q->lock);
+ ao2_ref(q->members, -1);
+ free(q);
+}
+
+/*!\brief Reload a single queue via realtime.
+ \return Return the queue, or NULL if it doesn't exist.
+ \note Should be called with the global qlock locked. */
+static struct call_queue *find_queue_by_name_rt(const char *queuename, struct ast_variable *queue_vars, struct ast_config *member_config)
+{
+ struct ast_variable *v;
+ struct call_queue *q;
+ struct member *m;
+ struct ao2_iterator mem_iter;
+ char *interface = NULL;
+ char *tmp, *tmp_name;
+ char tmpbuf[64]; /* Must be longer than the longest queue param name. */
+
+ /* Find the queue in the in-core list (we will create a new one if not found). */
+ AST_LIST_TRAVERSE(&queues, q, list) {
+ if (!strcasecmp(q->name, queuename))
+ break;
+ }
+
+ /* Static queues override realtime. */
+ if (q) {
+ ast_mutex_lock(&q->lock);
+ if (!q->realtime) {
+ if (q->dead) {
+ ast_mutex_unlock(&q->lock);
+ return NULL;
+ } else {
+ ast_log(LOG_WARNING, "Static queue '%s' already exists. Not loading from realtime\n", q->name);
+ ast_mutex_unlock(&q->lock);
+ return q;
+ }
+ }
+ } else if (!member_config)
+ /* Not found in the list, and it's not realtime ... */
+ return NULL;
+
+ /* Check if queue is defined in realtime. */
+ if (!queue_vars) {
+ /* Delete queue from in-core list if it has been deleted in realtime. */
+ if (q) {
+ /*! \note Hmm, can't seem to distinguish a DB failure from a not
+ found condition... So we might delete an in-core queue
+ in case of DB failure. */
+ ast_log(LOG_DEBUG, "Queue %s not found in realtime.\n", queuename);
+
+ q->dead = 1;
+ /* Delete if unused (else will be deleted when last caller leaves). */
+ if (!q->count) {
+ /* Delete. */
+ AST_LIST_REMOVE(&queues, q, list);
+ ast_mutex_unlock(&q->lock);
+ destroy_queue(q);
+ } else
+ ast_mutex_unlock(&q->lock);
+ }
+ return NULL;
+ }
+
+ /* Create a new queue if an in-core entry does not exist yet. */
+ if (!q) {
+ if (!(q = alloc_queue(queuename)))
+ return NULL;
+ ast_mutex_lock(&q->lock);
+ clear_queue(q);
+ q->realtime = 1;
+ AST_LIST_INSERT_HEAD(&queues, q, list);
+ }
+ init_queue(q); /* Ensure defaults for all parameters not set explicitly. */
+
+ memset(tmpbuf, 0, sizeof(tmpbuf));
+ for (v = queue_vars; v; v = v->next) {
+ /* Convert to dashes `-' from underscores `_' as the latter are more SQL friendly. */
+ if ((tmp = strchr(v->name, '_'))) {
+ ast_copy_string(tmpbuf, v->name, sizeof(tmpbuf));
+ tmp_name = tmpbuf;
+ tmp = tmp_name;
+ while ((tmp = strchr(tmp, '_')))
+ *tmp++ = '-';
+ } else
+ tmp_name = v->name;
+
+ if (!ast_strlen_zero(v->value)) {
+ /* Don't want to try to set the option if the value is empty */
+ queue_set_param(q, tmp_name, v->value, -1, 0);
+ }
+ }
+
+ if (q->strategy == QUEUE_STRATEGY_ROUNDROBIN)
+ rr_dep_warning();
+
+ /* Temporarily set realtime members dead so we can detect deleted ones.
+ * Also set the membercount correctly for realtime*/
+ mem_iter = ao2_iterator_init(q->members, 0);
+ while ((m = ao2_iterator_next(&mem_iter))) {
+ q->membercount++;
+ if (m->realtime)
+ m->dead = 1;
+ ao2_ref(m, -1);
+ }
+
+ while ((interface = ast_category_browse(member_config, interface))) {
+ rt_handle_member_record(q, interface,
+ ast_variable_retrieve(member_config, interface, "membername"),
+ ast_variable_retrieve(member_config, interface, "penalty"),
+ ast_variable_retrieve(member_config, interface, "paused"));
+ }
+
+ /* Delete all realtime members that have been deleted in DB. */
+ mem_iter = ao2_iterator_init(q->members, 0);
+ while ((m = ao2_iterator_next(&mem_iter))) {
+ if (m->dead) {
+ ao2_unlink(q->members, m);
+ ast_mutex_unlock(&q->lock);
+ remove_from_interfaces(m->interface);
+ ast_mutex_lock(&q->lock);
+ q->membercount--;
+ }
+ ao2_ref(m, -1);
+ }
+
+ ast_mutex_unlock(&q->lock);
+
+ return q;
+}
+
+static int update_realtime_member_field(struct member *mem, const char *queue_name, const char *field, const char *value)
+{
+ struct ast_variable *var, *save;
+ int ret = -1;
+
+ if (!(var = ast_load_realtime("queue_members", "interface", mem->interface, "queue_name", queue_name, NULL)))
+ return ret;
+ save = var;
+ while (var) {
+ if (!strcmp(var->name, "uniqueid"))
+ break;
+ var = var->next;
+ }
+ if (var && !ast_strlen_zero(var->value)) {
+ if ((ast_update_realtime("queue_members", "uniqueid", var->value, field, value, NULL)) > -1)
+ ret = 0;
+ }
+ ast_variables_destroy(save);
+ return ret;
+}
+
+static void update_realtime_members(struct call_queue *q)
+{
+ struct ast_config *member_config = NULL;
+ struct member *m;
+ char *interface = NULL;
+ struct ao2_iterator mem_iter;
+
+ if (!(member_config = ast_load_realtime_multientry("queue_members", "interface LIKE", "%", "queue_name", q->name , NULL))) {
+ /*This queue doesn't have realtime members*/
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Queue %s has no realtime members defined. No need for update\n", q->name);
+ return;
+ }
+
+ ast_mutex_lock(&q->lock);
+
+ /* Temporarily set realtime members dead so we can detect deleted ones.*/
+ mem_iter = ao2_iterator_init(q->members, 0);
+ while ((m = ao2_iterator_next(&mem_iter))) {
+ if (m->realtime)
+ m->dead = 1;
+ ao2_ref(m, -1);
+ }
+
+ while ((interface = ast_category_browse(member_config, interface))) {
+ rt_handle_member_record(q, interface,
+ S_OR(ast_variable_retrieve(member_config, interface, "membername"), interface),
+ ast_variable_retrieve(member_config, interface, "penalty"),
+ ast_variable_retrieve(member_config, interface, "paused"));
+ }
+
+ /* Delete all realtime members that have been deleted in DB. */
+ mem_iter = ao2_iterator_init(q->members, 0);
+ while ((m = ao2_iterator_next(&mem_iter))) {
+ if (m->dead) {
+ ao2_unlink(q->members, m);
+ ast_mutex_unlock(&q->lock);
+ remove_from_interfaces(m->interface);
+ ast_mutex_lock(&q->lock);
+ q->membercount--;
+ }
+ ao2_ref(m, -1);
+ }
+ ast_mutex_unlock(&q->lock);
+ ast_config_destroy(member_config);
+}
+
+static struct call_queue *load_realtime_queue(const char *queuename)
+{
+ struct ast_variable *queue_vars;
+ struct ast_config *member_config = NULL;
+ struct call_queue *q;
+
+ /* Find the queue in the in-core list first. */
+ AST_LIST_LOCK(&queues);
+ AST_LIST_TRAVERSE(&queues, q, list) {
+ if (!strcasecmp(q->name, queuename)) {
+ break;
+ }
+ }
+ AST_LIST_UNLOCK(&queues);
+
+ if (!q || q->realtime) {
+ /*! \note Load from realtime before taking the global qlock, to avoid blocking all
+ queue operations while waiting for the DB.
+
+ This will be two separate database transactions, so we might
+ see queue parameters as they were before another process
+ changed the queue and member list as it was after the change.
+ Thus we might see an empty member list when a queue is
+ deleted. In practise, this is unlikely to cause a problem. */
+
+ queue_vars = ast_load_realtime("queues", "name", queuename, NULL);
+ if (queue_vars) {
+ member_config = ast_load_realtime_multientry("queue_members", "interface LIKE", "%", "queue_name", queuename, NULL);
+ if (!member_config) {
+ ast_log(LOG_ERROR, "no queue_members defined in your config (extconfig.conf).\n");
+ ast_variables_destroy(queue_vars);
+ return NULL;
+ }
+ }
+
+ AST_LIST_LOCK(&queues);
+
+ q = find_queue_by_name_rt(queuename, queue_vars, member_config);
+ if (member_config)
+ ast_config_destroy(member_config);
+ if (queue_vars)
+ ast_variables_destroy(queue_vars);
+
+ AST_LIST_UNLOCK(&queues);
+ } else {
+ update_realtime_members(q);
+ }
+ return q;
+}
+
+static int join_queue(char *queuename, struct queue_ent *qe, enum queue_result *reason)
+{
+ struct call_queue *q;
+ struct queue_ent *cur, *prev = NULL;
+ int res = -1;
+ int pos = 0;
+ int inserted = 0;
+ enum queue_member_status stat;
+
+ if (!(q = load_realtime_queue(queuename)))
+ return res;
+
+ AST_LIST_LOCK(&queues);
+ ast_mutex_lock(&q->lock);
+
+ /* This is our one */
+ stat = get_member_status(q, qe->max_penalty);
+ if (!q->joinempty && (stat == QUEUE_NO_MEMBERS))
+ *reason = QUEUE_JOINEMPTY;
+ else if ((q->joinempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS || stat == QUEUE_NO_MEMBERS))
+ *reason = QUEUE_JOINUNAVAIL;
+ else if (q->maxlen && (q->count >= q->maxlen))
+ *reason = QUEUE_FULL;
+ else {
+ /* There's space for us, put us at the right position inside
+ * the queue.
+ * Take into account the priority of the calling user */
+ inserted = 0;
+ prev = NULL;
+ cur = q->head;
+ while (cur) {
+ /* We have higher priority than the current user, enter
+ * before him, after all the other users with priority
+ * higher or equal to our priority. */
+ if ((!inserted) && (qe->prio > cur->prio)) {
+ insert_entry(q, prev, qe, &pos);
+ inserted = 1;
+ }
+ cur->pos = ++pos;
+ prev = cur;
+ cur = cur->next;
+ }
+ /* No luck, join at the end of the queue */
+ if (!inserted)
+ insert_entry(q, prev, qe, &pos);
+ ast_copy_string(qe->moh, q->moh, sizeof(qe->moh));
+ ast_copy_string(qe->announce, q->announce, sizeof(qe->announce));
+ ast_copy_string(qe->context, q->context, sizeof(qe->context));
+ q->count++;
+ res = 0;
+ manager_event(EVENT_FLAG_CALL, "Join",
+ "Channel: %s\r\nCallerID: %s\r\nCallerIDName: %s\r\nQueue: %s\r\nPosition: %d\r\nCount: %d\r\nUniqueid: %s\r\n",
+ qe->chan->name,
+ S_OR(qe->chan->cid.cid_num, "unknown"), /* XXX somewhere else it is <unknown> */
+ S_OR(qe->chan->cid.cid_name, "unknown"),
+ q->name, qe->pos, q->count, qe->chan->uniqueid );
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Queue '%s' Join, Channel '%s', Position '%d'\n", q->name, qe->chan->name, qe->pos );
+ }
+ ast_mutex_unlock(&q->lock);
+ AST_LIST_UNLOCK(&queues);
+
+ return res;
+}
+
+static int play_file(struct ast_channel *chan, char *filename)
+{
+ int res;
+
+ ast_stopstream(chan);
+
+ res = ast_streamfile(chan, filename, chan->language);
+ if (!res)
+ res = ast_waitstream(chan, AST_DIGIT_ANY);
+
+ ast_stopstream(chan);
+
+ return res;
+}
+
+static int valid_exit(struct queue_ent *qe, char digit)
+{
+ int digitlen = strlen(qe->digits);
+
+ /* Prevent possible buffer overflow */
+ if (digitlen < sizeof(qe->digits) - 2) {
+ qe->digits[digitlen] = digit;
+ qe->digits[digitlen + 1] = '\0';
+ } else {
+ qe->digits[0] = '\0';
+ return 0;
+ }
+
+ /* If there's no context to goto, short-circuit */
+ if (ast_strlen_zero(qe->context))
+ return 0;
+
+ /* If the extension is bad, then reset the digits to blank */
+ if (!ast_canmatch_extension(qe->chan, qe->context, qe->digits, 1, qe->chan->cid.cid_num)) {
+ qe->digits[0] = '\0';
+ return 0;
+ }
+
+ /* We have an exact match */
+ if (!ast_goto_if_exists(qe->chan, qe->context, qe->digits, 1)) {
+ qe->valid_digits = 1;
+ /* Return 1 on a successful goto */
+ return 1;
+ }
+
+ return 0;
+}
+
+static int say_position(struct queue_ent *qe)
+{
+ int res = 0, avgholdmins, avgholdsecs;
+ time_t now;
+
+ /* Check to see if this is ludicrous -- if we just announced position, don't do it again*/
+ time(&now);
+ if ((now - qe->last_pos) < 15)
+ return 0;
+
+ /* If either our position has changed, or we are over the freq timer, say position */
+ if ((qe->last_pos_said == qe->pos) && ((now - qe->last_pos) < qe->parent->announcefrequency))
+ return 0;
+
+ ast_moh_stop(qe->chan);
+ /* Say we're next, if we are */
+ if (qe->pos == 1) {
+ res = play_file(qe->chan, qe->parent->sound_next);
+ if (res)
+ goto playout;
+ else
+ goto posout;
+ } else {
+ res = play_file(qe->chan, qe->parent->sound_thereare);
+ if (res)
+ goto playout;
+ res = ast_say_number(qe->chan, qe->pos, AST_DIGIT_ANY, qe->chan->language, (char *) NULL); /* Needs gender */
+ if (res)
+ goto playout;
+ res = play_file(qe->chan, qe->parent->sound_calls);
+ if (res)
+ goto playout;
+ }
+ /* Round hold time to nearest minute */
+ avgholdmins = abs(((qe->parent->holdtime + 30) - (now - qe->start)) / 60);
+
+ /* If they have specified a rounding then round the seconds as well */
+ if (qe->parent->roundingseconds) {
+ avgholdsecs = (abs(((qe->parent->holdtime + 30) - (now - qe->start))) - 60 * avgholdmins) / qe->parent->roundingseconds;
+ avgholdsecs *= qe->parent->roundingseconds;
+ } else {
+ avgholdsecs = 0;
+ }
+
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Hold time for %s is %d minutes %d seconds\n", qe->parent->name, avgholdmins, avgholdsecs);
+
+ /* If the hold time is >1 min, if it's enabled, and if it's not
+ supposed to be only once and we have already said it, say it */
+ if ((avgholdmins+avgholdsecs) > 0 && qe->parent->announceholdtime &&
+ ((qe->parent->announceholdtime == ANNOUNCEHOLDTIME_ONCE && !qe->last_pos) ||
+ !(qe->parent->announceholdtime == ANNOUNCEHOLDTIME_ONCE))) {
+ res = play_file(qe->chan, qe->parent->sound_holdtime);
+ if (res)
+ goto playout;
+
+ if (avgholdmins > 0) {
+ if (avgholdmins < 2) {
+ res = play_file(qe->chan, qe->parent->sound_lessthan);
+ if (res)
+ goto playout;
+
+ res = ast_say_number(qe->chan, 2, AST_DIGIT_ANY, qe->chan->language, NULL);
+ if (res)
+ goto playout;
+ } else {
+ res = ast_say_number(qe->chan, avgholdmins, AST_DIGIT_ANY, qe->chan->language, NULL);
+ if (res)
+ goto playout;
+ }
+
+ res = play_file(qe->chan, qe->parent->sound_minutes);
+ if (res)
+ goto playout;
+ }
+ if (avgholdsecs>0) {
+ res = ast_say_number(qe->chan, avgholdsecs, AST_DIGIT_ANY, qe->chan->language, NULL);
+ if (res)
+ goto playout;
+
+ res = play_file(qe->chan, qe->parent->sound_seconds);
+ if (res)
+ goto playout;
+ }
+
+ }
+
+posout:
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Told %s in %s their queue position (which was %d)\n",
+ qe->chan->name, qe->parent->name, qe->pos);
+ res = play_file(qe->chan, qe->parent->sound_thanks);
+
+playout:
+ if ((res > 0 && !valid_exit(qe, res)) || res < 0)
+ res = 0;
+
+ /* Set our last_pos indicators */
+ qe->last_pos = now;
+ qe->last_pos_said = qe->pos;
+
+ /* Don't restart music on hold if we're about to exit the caller from the queue */
+ if (!res)
+ ast_moh_start(qe->chan, qe->moh, NULL);
+
+ return res;
+}
+
+static void recalc_holdtime(struct queue_ent *qe, int newholdtime)
+{
+ int oldvalue;
+
+ /* Calculate holdtime using an exponential average */
+ /* Thanks to SRT for this contribution */
+ /* 2^2 (4) is the filter coefficient; a higher exponent would give old entries more weight */
+
+ ast_mutex_lock(&qe->parent->lock);
+ oldvalue = qe->parent->holdtime;
+ qe->parent->holdtime = (((oldvalue << 2) - oldvalue) + newholdtime) >> 2;
+ ast_mutex_unlock(&qe->parent->lock);
+}
+
+
+static void leave_queue(struct queue_ent *qe)
+{
+ struct call_queue *q;
+ struct queue_ent *cur, *prev = NULL;
+ int pos = 0;
+
+ if (!(q = qe->parent))
+ return;
+ ast_mutex_lock(&q->lock);
+
+ prev = NULL;
+ for (cur = q->head; cur; cur = cur->next) {
+ if (cur == qe) {
+ q->count--;
+
+ /* Take us out of the queue */
+ manager_event(EVENT_FLAG_CALL, "Leave",
+ "Channel: %s\r\nQueue: %s\r\nCount: %d\r\nUniqueid: %s\r\n",
+ qe->chan->name, q->name, q->count, qe->chan->uniqueid);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Queue '%s' Leave, Channel '%s'\n", q->name, qe->chan->name );
+ /* Take us out of the queue */
+ if (prev)
+ prev->next = cur->next;
+ else
+ q->head = cur->next;
+ } else {
+ /* Renumber the people after us in the queue based on a new count */
+ cur->pos = ++pos;
+ prev = cur;
+ }
+ }
+ ast_mutex_unlock(&q->lock);
+
+ if (q->dead && !q->count) {
+ /* It's dead and nobody is in it, so kill it */
+ AST_LIST_LOCK(&queues);
+ AST_LIST_REMOVE(&queues, q, list);
+ AST_LIST_UNLOCK(&queues);
+ destroy_queue(q);
+ }
+}
+
+/* Hang up a list of outgoing calls */
+static void hangupcalls(struct callattempt *outgoing, struct ast_channel *exception)
+{
+ struct callattempt *oo;
+
+ while (outgoing) {
+ /* Hangup any existing lines we have open */
+ if (outgoing->chan && (outgoing->chan != exception))
+ ast_hangup(outgoing->chan);
+ oo = outgoing;
+ outgoing = outgoing->q_next;
+ if (oo->member)
+ ao2_ref(oo->member, -1);
+ free(oo);
+ }
+}
+
+
+/* traverse all defined queues which have calls waiting and contain this member
+ return 0 if no other queue has precedence (higher weight) or 1 if found */
+static int compare_weight(struct call_queue *rq, struct member *member)
+{
+ struct call_queue *q;
+ struct member *mem;
+ int found = 0;
+
+ /* &qlock and &rq->lock already set by try_calling()
+ * to solve deadlock */
+ AST_LIST_TRAVERSE(&queues, q, list) {
+ if (q == rq) /* don't check myself, could deadlock */
+ continue;
+ ast_mutex_lock(&q->lock);
+ if (q->count && q->members) {
+ if ((mem = ao2_find(q->members, member, OBJ_POINTER))) {
+ ast_log(LOG_DEBUG, "Found matching member %s in queue '%s'\n", mem->interface, q->name);
+ if (q->weight > rq->weight) {
+ ast_log(LOG_DEBUG, "Queue '%s' (weight %d, calls %d) is preferred over '%s' (weight %d, calls %d)\n", q->name, q->weight, q->count, rq->name, rq->weight, rq->count);
+ found = 1;
+ }
+ ao2_ref(mem, -1);
+ }
+ }
+ ast_mutex_unlock(&q->lock);
+ if (found)
+ break;
+ }
+ return found;
+}
+
+/*! \brief common hangup actions */
+static void do_hang(struct callattempt *o)
+{
+ o->stillgoing = 0;
+ ast_hangup(o->chan);
+ o->chan = NULL;
+}
+
+static char *vars2manager(struct ast_channel *chan, char *vars, size_t len)
+{
+ char *tmp = alloca(len);
+
+ if (pbx_builtin_serialize_variables(chan, tmp, len)) {
+ int i, j;
+
+ /* convert "\n" to "\nVariable: " */
+ strcpy(vars, "Variable: ");
+
+ for (i = 0, j = 10; (i < len - 1) && (j < len - 1); i++, j++) {
+ vars[j] = tmp[i];
+
+ if (tmp[i + 1] == '\0')
+ break;
+ if (tmp[i] == '\n') {
+ vars[j++] = '\r';
+ vars[j++] = '\n';
+
+ ast_copy_string(&(vars[j]), "Variable: ", len - j);
+ j += 9;
+ }
+ }
+ if (j > len - 3)
+ j = len - 3;
+ vars[j++] = '\r';
+ vars[j++] = '\n';
+ vars[j] = '\0';
+ } else {
+ /* there are no channel variables; leave it blank */
+ *vars = '\0';
+ }
+ return vars;
+}
+
+/*! \brief Part 2 of ring_one
+ *
+ * Does error checking before attempting to request a channel and call a member. This
+ * function is only called from ring_one
+ */
+static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies)
+{
+ int res;
+ int status;
+ char tech[256];
+ char *location;
+ const char *macrocontext, *macroexten;
+
+ /* on entry here, we know that tmp->chan == NULL */
+ if (qe->parent->wrapuptime && (time(NULL) - tmp->lastcall < qe->parent->wrapuptime)) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Wrapuptime not yet expired for %s\n", tmp->interface);
+ if (qe->chan->cdr)
+ ast_cdr_busy(qe->chan->cdr);
+ tmp->stillgoing = 0;
+ (*busies)++;
+ return 0;
+ }
+
+ if (!qe->parent->ringinuse && (tmp->member->status != AST_DEVICE_NOT_INUSE) && (tmp->member->status != AST_DEVICE_UNKNOWN)) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "%s in use, can't receive call\n", tmp->interface);
+ if (qe->chan->cdr)
+ ast_cdr_busy(qe->chan->cdr);
+ tmp->stillgoing = 0;
+ return 0;
+ }
+
+ if (tmp->member->paused) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "%s paused, can't receive call\n", tmp->interface);
+ if (qe->chan->cdr)
+ ast_cdr_busy(qe->chan->cdr);
+ tmp->stillgoing = 0;
+ return 0;
+ }
+ if (use_weight && compare_weight(qe->parent,tmp->member)) {
+ ast_log(LOG_DEBUG, "Priority queue delaying call to %s:%s\n", qe->parent->name, tmp->interface);
+ if (qe->chan->cdr)
+ ast_cdr_busy(qe->chan->cdr);
+ tmp->stillgoing = 0;
+ (*busies)++;
+ return 0;
+ }
+
+ ast_copy_string(tech, tmp->interface, sizeof(tech));
+ if ((location = strchr(tech, '/')))
+ *location++ = '\0';
+ else
+ location = "";
+
+ /* Request the peer */
+ tmp->chan = ast_request(tech, qe->chan->nativeformats, location, &status);
+ if (!tmp->chan) { /* If we can't, just go on to the next call */
+ if (qe->chan->cdr)
+ ast_cdr_busy(qe->chan->cdr);
+ tmp->stillgoing = 0;
+
+ update_status(tmp->member->interface, ast_device_state(tmp->member->interface));
+
+ ast_mutex_lock(&qe->parent->lock);
+ qe->parent->rrpos++;
+ ast_mutex_unlock(&qe->parent->lock);
+
+ (*busies)++;
+ return 0;
+ }
+
+ tmp->chan->appl = "AppQueue";
+ tmp->chan->data = "(Outgoing Line)";
+ tmp->chan->whentohangup = 0;
+ if (tmp->chan->cid.cid_num)
+ free(tmp->chan->cid.cid_num);
+ tmp->chan->cid.cid_num = ast_strdup(qe->chan->cid.cid_num);
+ if (tmp->chan->cid.cid_name)
+ free(tmp->chan->cid.cid_name);
+ tmp->chan->cid.cid_name = ast_strdup(qe->chan->cid.cid_name);
+ if (tmp->chan->cid.cid_ani)
+ free(tmp->chan->cid.cid_ani);
+ tmp->chan->cid.cid_ani = ast_strdup(qe->chan->cid.cid_ani);
+
+ /* Inherit specially named variables from parent channel */
+ ast_channel_inherit_variables(qe->chan, tmp->chan);
+
+ /* Presense of ADSI CPE on outgoing channel follows ours */
+ tmp->chan->adsicpe = qe->chan->adsicpe;
+
+ /* Inherit context and extension */
+ ast_channel_lock(qe->chan);
+ macrocontext = pbx_builtin_getvar_helper(qe->chan, "MACRO_CONTEXT");
+ if (!ast_strlen_zero(macrocontext))
+ ast_copy_string(tmp->chan->dialcontext, macrocontext, sizeof(tmp->chan->dialcontext));
+ else
+ ast_copy_string(tmp->chan->dialcontext, qe->chan->context, sizeof(tmp->chan->dialcontext));
+ macroexten = pbx_builtin_getvar_helper(qe->chan, "MACRO_EXTEN");
+ if (!ast_strlen_zero(macroexten))
+ ast_copy_string(tmp->chan->exten, macroexten, sizeof(tmp->chan->exten));
+ else
+ ast_copy_string(tmp->chan->exten, qe->chan->exten, sizeof(tmp->chan->exten));
+ ast_channel_unlock(qe->chan);
+
+ /* Place the call, but don't wait on the answer */
+ if ((res = ast_call(tmp->chan, location, 0))) {
+ /* Again, keep going even if there's an error */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "ast call on peer returned %d\n", res);
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Couldn't call %s\n", tmp->interface);
+ do_hang(tmp);
+ (*busies)++;
+ update_status(tmp->member->interface, ast_device_state(tmp->member->interface));
+ return 0;
+ } else if (qe->parent->eventwhencalled) {
+ char vars[2048];
+
+ manager_event(EVENT_FLAG_AGENT, "AgentCalled",
+ "AgentCalled: %s\r\n"
+ "AgentName: %s\r\n"
+ "ChannelCalling: %s\r\n"
+ "CallerID: %s\r\n"
+ "CallerIDName: %s\r\n"
+ "Context: %s\r\n"
+ "Extension: %s\r\n"
+ "Priority: %d\r\n"
+ "%s",
+ tmp->interface, tmp->member->membername, qe->chan->name,
+ tmp->chan->cid.cid_num ? tmp->chan->cid.cid_num : "unknown",
+ tmp->chan->cid.cid_name ? tmp->chan->cid.cid_name : "unknown",
+ qe->chan->context, qe->chan->exten, qe->chan->priority,
+ qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : "");
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Called %s\n", tmp->interface);
+ }
+
+ update_status(tmp->member->interface, ast_device_state(tmp->member->interface));
+ return 1;
+}
+
+/*! \brief find the entry with the best metric, or NULL */
+static struct callattempt *find_best(struct callattempt *outgoing)
+{
+ struct callattempt *best = NULL, *cur;
+
+ for (cur = outgoing; cur; cur = cur->q_next) {
+ if (cur->stillgoing && /* Not already done */
+ !cur->chan && /* Isn't already going */
+ (!best || cur->metric < best->metric)) { /* We haven't found one yet, or it's better */
+ best = cur;
+ }
+ }
+
+ return best;
+}
+
+/*! \brief Place a call to a queue member
+ *
+ * Once metrics have been calculated for each member, this function is used
+ * to place a call to the appropriate member (or members). The low-level
+ * channel-handling and error detection is handled in ring_entry
+ *
+ * Returns 1 if a member was called successfully, 0 otherwise
+ */
+static int ring_one(struct queue_ent *qe, struct callattempt *outgoing, int *busies)
+{
+ int ret = 0;
+
+ while (ret == 0) {
+ struct callattempt *best = find_best(outgoing);
+ if (!best) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Nobody left to try ringing in queue\n");
+ break;
+ }
+ if (qe->parent->strategy == QUEUE_STRATEGY_RINGALL) {
+ struct callattempt *cur;
+ /* Ring everyone who shares this best metric (for ringall) */
+ for (cur = outgoing; cur; cur = cur->q_next) {
+ if (cur->stillgoing && !cur->chan && cur->metric <= best->metric) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "(Parallel) Trying '%s' with metric %d\n", cur->interface, cur->metric);
+ ret |= ring_entry(qe, cur, busies);
+ }
+ }
+ } else {
+ /* Ring just the best channel */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Trying '%s' with metric %d\n", best->interface, best->metric);
+ ret = ring_entry(qe, best, busies);
+ }
+ }
+
+ return ret;
+}
+
+static int store_next(struct queue_ent *qe, struct callattempt *outgoing)
+{
+ struct callattempt *best = find_best(outgoing);
+
+ if (best) {
+ /* Ring just the best channel */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Next is '%s' with metric %d\n", best->interface, best->metric);
+ qe->parent->rrpos = best->metric % 1000;
+ } else {
+ /* Just increment rrpos */
+ if (qe->parent->wrapped) {
+ /* No more channels, start over */
+ qe->parent->rrpos = 0;
+ } else {
+ /* Prioritize next entry */
+ qe->parent->rrpos++;
+ }
+ }
+ qe->parent->wrapped = 0;
+
+ return 0;
+}
+
+static int say_periodic_announcement(struct queue_ent *qe)
+{
+ int res = 0;
+ time_t now;
+
+ /* Get the current time */
+ time(&now);
+
+ /* Check to see if it is time to announce */
+ if ((now - qe->last_periodic_announce_time) < qe->parent->periodicannouncefrequency)
+ return 0;
+
+ /* Stop the music on hold so we can play our own file */
+ ast_moh_stop(qe->chan);
+
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Playing periodic announcement\n");
+
+ /* Check to make sure we have a sound file. If not, reset to the first sound file */
+ if (qe->last_periodic_announce_sound >= MAX_PERIODIC_ANNOUNCEMENTS || !strlen(qe->parent->sound_periodicannounce[qe->last_periodic_announce_sound])) {
+ qe->last_periodic_announce_sound = 0;
+ }
+
+ /* play the announcement */
+ res = play_file(qe->chan, qe->parent->sound_periodicannounce[qe->last_periodic_announce_sound]);
+
+ if ((res > 0 && !valid_exit(qe, res)) || res < 0)
+ res = 0;
+
+ /* Resume Music on Hold if the caller is going to stay in the queue */
+ if (!res)
+ ast_moh_start(qe->chan, qe->moh, NULL);
+
+ /* update last_periodic_announce_time */
+ qe->last_periodic_announce_time = now;
+
+ /* Update the current periodic announcement to the next announcement */
+ qe->last_periodic_announce_sound++;
+
+ return res;
+}
+
+static void record_abandoned(struct queue_ent *qe)
+{
+ ast_mutex_lock(&qe->parent->lock);
+ manager_event(EVENT_FLAG_AGENT, "QueueCallerAbandon",
+ "Queue: %s\r\n"
+ "Uniqueid: %s\r\n"
+ "Position: %d\r\n"
+ "OriginalPosition: %d\r\n"
+ "HoldTime: %d\r\n",
+ qe->parent->name, qe->chan->uniqueid, qe->pos, qe->opos, (int)(time(NULL) - qe->start));
+
+ qe->parent->callsabandoned++;
+ ast_mutex_unlock(&qe->parent->lock);
+}
+
+/*! \brief RNA == Ring No Answer. Common code that is executed when we try a queue member and they don't answer. */
+static void rna(int rnatime, struct queue_ent *qe, char *interface, char *membername)
+{
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "Nobody picked up in %d ms\n", rnatime);
+ ast_queue_log(qe->parent->name, qe->chan->uniqueid, membername, "RINGNOANSWER", "%d", rnatime);
+ if (qe->parent->autopause) {
+ if (!set_member_paused(qe->parent->name, interface, 1)) {
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "Auto-Pausing Queue Member %s in queue %s since they failed to answer.\n", interface, qe->parent->name);
+ } else {
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "Failed to pause Queue Member %s in queue %s!\n", interface, qe->parent->name);
+ }
+ }
+ return;
+}
+
+#define AST_MAX_WATCHERS 256
+/*! \brief Wait for a member to answer the call
+ *
+ * \param[in] qe the queue_ent corresponding to the caller in the queue
+ * \param[in] outgoing the list of callattempts. Relevant ones will have their chan and stillgoing parameters non-zero
+ * \param[in] to the amount of time (in milliseconds) to wait for a response
+ * \param[out] digit if a user presses a digit to exit the queue, this is the digit the caller pressed
+ * \param[in] prebusies number of busy members calculated prior to calling wait_for_answer
+ * \param[in] caller_disconnect if the 'H' option is used when calling Queue(), this is used to detect if the caller pressed * to disconnect the call
+ * \param[in] forwardsallowed used to detect if we should allow call forwarding, based on the 'i' option to Queue()
+ */
+static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callattempt *outgoing, int *to, char *digit, int prebusies, int caller_disconnect, int forwardsallowed)
+{
+ char *queue = qe->parent->name;
+ struct callattempt *o, *start = NULL, *prev = NULL;
+ int status;
+ int numbusies = prebusies;
+ int numnochan = 0;
+ int stillgoing = 0;
+ int orig = *to;
+ struct ast_frame *f;
+ struct callattempt *peer = NULL;
+ struct ast_channel *winner;
+ struct ast_channel *in = qe->chan;
+ char on[80] = "";
+ char membername[80] = "";
+ long starttime = 0;
+ long endtime = 0;
+
+ starttime = (long) time(NULL);
+
+ while (*to && !peer) {
+ int numlines, retry, pos = 1;
+ struct ast_channel *watchers[AST_MAX_WATCHERS];
+ watchers[0] = in;
+ start = NULL;
+
+ for (retry = 0; retry < 2; retry++) {
+ numlines = 0;
+ for (o = outgoing; o; o = o->q_next) { /* Keep track of important channels */
+ if (o->stillgoing) { /* Keep track of important channels */
+ stillgoing = 1;
+ if (o->chan) {
+ watchers[pos++] = o->chan;
+ if (!start)
+ start = o;
+ else
+ prev->call_next = o;
+ prev = o;
+ }
+ }
+ numlines++;
+ }
+ if (pos > 1 /* found */ || !stillgoing /* nobody listening */ ||
+ (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) /* ring would not be delivered */)
+ break;
+ /* On "ringall" strategy we only move to the next penalty level
+ when *all* ringing phones are done in the current penalty level */
+ ring_one(qe, outgoing, &numbusies);
+ /* and retry... */
+ }
+ if (pos == 1 /* not found */) {
+ if (numlines == (numbusies + numnochan)) {
+ ast_log(LOG_DEBUG, "Everyone is busy at this time\n");
+ } else {
+ ast_log(LOG_NOTICE, "No one is answering queue '%s' (%d/%d/%d)\n", queue, numlines, numbusies, numnochan);
+ }
+ *to = 0;
+ return NULL;
+ }
+ winner = ast_waitfor_n(watchers, pos, to);
+ for (o = start; o; o = o->call_next) {
+ if (o->stillgoing && (o->chan) && (o->chan->_state == AST_STATE_UP)) {
+ if (!peer) {
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", o->chan->name, in->name);
+ peer = o;
+ }
+ } else if (o->chan && (o->chan == winner)) {
+
+ ast_copy_string(on, o->member->interface, sizeof(on));
+ ast_copy_string(membername, o->member->membername, sizeof(membername));
+
+ if (!ast_strlen_zero(o->chan->call_forward) && !forwardsallowed) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Forwarding %s to '%s' prevented.\n", in->name, o->chan->call_forward);
+ numnochan++;
+ do_hang(o);
+ winner = NULL;
+ continue;
+ } else if (!ast_strlen_zero(o->chan->call_forward)) {
+ char tmpchan[256];
+ char *stuff;
+ char *tech;
+
+ ast_copy_string(tmpchan, o->chan->call_forward, sizeof(tmpchan));
+ if ((stuff = strchr(tmpchan, '/'))) {
+ *stuff++ = '\0';
+ tech = tmpchan;
+ } else {
+ snprintf(tmpchan, sizeof(tmpchan), "%s@%s", o->chan->call_forward, o->chan->context);
+ stuff = tmpchan;
+ tech = "Local";
+ }
+ /* Before processing channel, go ahead and check for forwarding */
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Now forwarding %s to '%s/%s' (thanks to %s)\n", in->name, tech, stuff, o->chan->name);
+ /* Setup parameters */
+ o->chan = ast_request(tech, in->nativeformats, stuff, &status);
+ if (!o->chan) {
+ ast_log(LOG_NOTICE, "Unable to create local channel for call forward to '%s/%s'\n", tech, stuff);
+ o->stillgoing = 0;
+ numnochan++;
+ } else {
+ ast_channel_inherit_variables(in, o->chan);
+ ast_channel_datastore_inherit(in, o->chan);
+ if (o->chan->cid.cid_num)
+ free(o->chan->cid.cid_num);
+ o->chan->cid.cid_num = ast_strdup(in->cid.cid_num);
+
+ if (o->chan->cid.cid_name)
+ free(o->chan->cid.cid_name);
+ o->chan->cid.cid_name = ast_strdup(in->cid.cid_name);
+
+ ast_string_field_set(o->chan, accountcode, in->accountcode);
+ o->chan->cdrflags = in->cdrflags;
+
+ if (in->cid.cid_ani) {
+ if (o->chan->cid.cid_ani)
+ free(o->chan->cid.cid_ani);
+ o->chan->cid.cid_ani = ast_strdup(in->cid.cid_ani);
+ }
+ if (o->chan->cid.cid_rdnis)
+ free(o->chan->cid.cid_rdnis);
+ o->chan->cid.cid_rdnis = ast_strdup(S_OR(in->macroexten, in->exten));
+ if (ast_call(o->chan, tmpchan, 0)) {
+ ast_log(LOG_NOTICE, "Failed to dial on local channel for call forward to '%s'\n", tmpchan);
+ do_hang(o);
+ numnochan++;
+ }
+ }
+ /* Hangup the original channel now, in case we needed it */
+ ast_hangup(winner);
+ continue;
+ }
+ f = ast_read(winner);
+ if (f) {
+ if (f->frametype == AST_FRAME_CONTROL) {
+ switch (f->subclass) {
+ case AST_CONTROL_ANSWER:
+ /* This is our guy if someone answered. */
+ if (!peer) {
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", o->chan->name, in->name);
+ peer = o;
+ }
+ break;
+ case AST_CONTROL_BUSY:
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "%s is busy\n", o->chan->name);
+ if (in->cdr)
+ ast_cdr_busy(in->cdr);
+ do_hang(o);
+ endtime = (long)time(NULL);
+ endtime -= starttime;
+ rna(endtime*1000, qe, on, membername);
+ if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) {
+ if (qe->parent->timeoutrestart)
+ *to = orig;
+ ring_one(qe, outgoing, &numbusies);
+ }
+ numbusies++;
+ break;
+ case AST_CONTROL_CONGESTION:
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "%s is circuit-busy\n", o->chan->name);
+ if (in->cdr)
+ ast_cdr_busy(in->cdr);
+ endtime = (long)time(NULL);
+ endtime -= starttime;
+ rna(endtime*1000, qe, on, membername);
+ do_hang(o);
+ if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) {
+ if (qe->parent->timeoutrestart)
+ *to = orig;
+ ring_one(qe, outgoing, &numbusies);
+ }
+ numbusies++;
+ break;
+ case AST_CONTROL_RINGING:
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "%s is ringing\n", o->chan->name);
+ break;
+ case AST_CONTROL_OFFHOOK:
+ /* Ignore going off hook */
+ break;
+ default:
+ ast_log(LOG_DEBUG, "Dunno what to do with control type %d\n", f->subclass);
+ }
+ }
+ ast_frfree(f);
+ } else {
+ endtime = (long) time(NULL) - starttime;
+ rna(endtime * 1000, qe, on, membername);
+ do_hang(o);
+ if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) {
+ if (qe->parent->timeoutrestart)
+ *to = orig;
+ ring_one(qe, outgoing, &numbusies);
+ }
+ }
+ }
+ }
+ if (winner == in) {
+ f = ast_read(in);
+ if (!f || ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP))) {
+ /* Got hung up */
+ *to = -1;
+ if (f)
+ ast_frfree(f);
+ return NULL;
+ }
+ if ((f->frametype == AST_FRAME_DTMF) && caller_disconnect && (f->subclass == '*')) {
+ if (option_verbose > 3)
+ ast_verbose(VERBOSE_PREFIX_3 "User hit %c to disconnect call.\n", f->subclass);
+ *to = 0;
+ ast_frfree(f);
+ return NULL;
+ }
+ if ((f->frametype == AST_FRAME_DTMF) && valid_exit(qe, f->subclass)) {
+ if (option_verbose > 3)
+ ast_verbose(VERBOSE_PREFIX_3 "User pressed digit: %c\n", f->subclass);
+ *to = 0;
+ *digit = f->subclass;
+ ast_frfree(f);
+ return NULL;
+ }
+ ast_frfree(f);
+ }
+ if (!*to) {
+ for (o = start; o; o = o->call_next)
+ rna(orig, qe, o->interface, o->member->membername);
+ }
+ }
+
+ return peer;
+}
+/*! \brief Check if we should start attempting to call queue members
+ *
+ * The behavior of this function is dependent first on whether autofill is enabled
+ * and second on whether the ring strategy is ringall. If autofill is not enabled,
+ * then return true if we're the head of the queue. If autofill is enabled, then
+ * we count the available members and see if the number of available members is enough
+ * that given our position in the queue, we would theoretically be able to connect to
+ * one of those available members
+ */
+static int is_our_turn(struct queue_ent *qe)
+{
+ struct queue_ent *ch;
+ struct member *cur;
+ int avl = 0;
+ int idx = 0;
+ int res;
+
+ if (!qe->parent->autofill) {
+ /* Atomically read the parent head -- does not need a lock */
+ ch = qe->parent->head;
+ /* If we are now at the top of the head, break out */
+ if (ch == qe) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "It's our turn (%s).\n", qe->chan->name);
+ res = 1;
+ } else {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "It's not our turn (%s).\n", qe->chan->name);
+ res = 0;
+ }
+
+ } else {
+ /* This needs a lock. How many members are available to be served? */
+ ast_mutex_lock(&qe->parent->lock);
+
+ ch = qe->parent->head;
+
+ if (qe->parent->strategy == QUEUE_STRATEGY_RINGALL) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Even though there may be multiple members available, the strategy is ringall so only the head call is allowed in\n");
+ avl = 1;
+ } else {
+ struct ao2_iterator mem_iter = ao2_iterator_init(qe->parent->members, 0);
+ while ((cur = ao2_iterator_next(&mem_iter))) {
+ switch (cur->status) {
+ case AST_DEVICE_INUSE:
+ if (!qe->parent->ringinuse)
+ break;
+ /* else fall through */
+ case AST_DEVICE_NOT_INUSE:
+ case AST_DEVICE_UNKNOWN:
+ if (!cur->paused)
+ avl++;
+ break;
+ }
+ ao2_ref(cur, -1);
+ }
+ }
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "There are %d available members.\n", avl);
+
+ while ((idx < avl) && (ch) && (ch != qe)) {
+ if (!ch->pending)
+ idx++;
+ ch = ch->next;
+ }
+
+ /* If the queue entry is within avl [the number of available members] calls from the top ... */
+ if (ch && idx < avl) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "It's our turn (%s).\n", qe->chan->name);
+ res = 1;
+ } else {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "It's not our turn (%s).\n", qe->chan->name);
+ res = 0;
+ }
+
+ ast_mutex_unlock(&qe->parent->lock);
+ }
+
+ return res;
+}
+/*! \brief The waiting areas for callers who are not actively calling members
+ *
+ * This function is one large loop. This function will return if a caller
+ * either exits the queue or it becomes that caller's turn to attempt calling
+ * queue members. Inside the loop, we service the caller with periodic announcements,
+ * holdtime announcements, etc. as configured in queues.conf
+ *
+ * \retval 0 if the caller's turn has arrived
+ * \retval -1 if the caller should exit the queue.
+ */
+static int wait_our_turn(struct queue_ent *qe, int ringing, enum queue_result *reason)
+{
+ int res = 0;
+
+ /* This is the holding pen for callers 2 through maxlen */
+ for (;;) {
+ enum queue_member_status stat;
+
+ if (is_our_turn(qe))
+ break;
+
+ /* If we have timed out, break out */
+ if (qe->expire && (time(NULL) >= qe->expire)) {
+ *reason = QUEUE_TIMEOUT;
+ break;
+ }
+
+ stat = get_member_status(qe->parent, qe->max_penalty);
+
+ /* leave the queue if no agents, if enabled */
+ if (qe->parent->leavewhenempty && (stat == QUEUE_NO_MEMBERS)) {
+ *reason = QUEUE_LEAVEEMPTY;
+ ast_queue_log(qe->parent->name, qe->chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe->pos, qe->opos, (long)time(NULL) - qe->start);
+ leave_queue(qe);
+ break;
+ }
+
+ /* leave the queue if no reachable agents, if enabled */
+ if ((qe->parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS)) {
+ *reason = QUEUE_LEAVEUNAVAIL;
+ ast_queue_log(qe->parent->name, qe->chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe->pos, qe->opos, (long)time(NULL) - qe->start);
+ leave_queue(qe);
+ break;
+ }
+
+ /* Make a position announcement, if enabled */
+ if (qe->parent->announcefrequency && !ringing &&
+ (res = say_position(qe)))
+ break;
+
+ /* If we have timed out, break out */
+ if (qe->expire && (time(NULL) >= qe->expire)) {
+ *reason = QUEUE_TIMEOUT;
+ break;
+ }
+
+ /* Make a periodic announcement, if enabled */
+ if (qe->parent->periodicannouncefrequency && !ringing &&
+ (res = say_periodic_announcement(qe)))
+ break;
+
+ /* If we have timed out, break out */
+ if (qe->expire && (time(NULL) >= qe->expire)) {
+ *reason = QUEUE_TIMEOUT;
+ break;
+ }
+
+ /* Wait a second before checking again */
+ if ((res = ast_waitfordigit(qe->chan, RECHECK * 1000))) {
+ if (res > 0 && !valid_exit(qe, res))
+ res = 0;
+ else
+ break;
+ }
+
+ /* If we have timed out, break out */
+ if (qe->expire && (time(NULL) >= qe->expire)) {
+ *reason = QUEUE_TIMEOUT;
+ break;
+ }
+ }
+
+ return res;
+}
+
+static int update_queue(struct call_queue *q, struct member *member, int callcompletedinsl)
+{
+ ast_mutex_lock(&q->lock);
+ time(&member->lastcall);
+ member->calls++;
+ q->callscompleted++;
+ if (callcompletedinsl)
+ q->callscompletedinsl++;
+ ast_mutex_unlock(&q->lock);
+ return 0;
+}
+
+/*! \brief Calculate the metric of each member in the outgoing callattempts
+ *
+ * A numeric metric is given to each member depending on the ring strategy used
+ * by the queue. Members with lower metrics will be called before members with
+ * higher metrics
+ */
+static int calc_metric(struct call_queue *q, struct member *mem, int pos, struct queue_ent *qe, struct callattempt *tmp)
+{
+ if (qe->max_penalty && (mem->penalty > qe->max_penalty))
+ return -1;
+
+ switch (q->strategy) {
+ case QUEUE_STRATEGY_RINGALL:
+ /* Everyone equal, except for penalty */
+ tmp->metric = mem->penalty * 1000000;
+ break;
+ case QUEUE_STRATEGY_ROUNDROBIN:
+ if (!pos) {
+ if (!q->wrapped) {
+ /* No more channels, start over */
+ q->rrpos = 0;
+ } else {
+ /* Prioritize next entry */
+ q->rrpos++;
+ }
+ q->wrapped = 0;
+ }
+ /* Fall through */
+ case QUEUE_STRATEGY_RRMEMORY:
+ if (pos < q->rrpos) {
+ tmp->metric = 1000 + pos;
+ } else {
+ if (pos > q->rrpos)
+ /* Indicate there is another priority */
+ q->wrapped = 1;
+ tmp->metric = pos;
+ }
+ tmp->metric += mem->penalty * 1000000;
+ break;
+ case QUEUE_STRATEGY_RANDOM:
+ tmp->metric = ast_random() % 1000;
+ tmp->metric += mem->penalty * 1000000;
+ break;
+ case QUEUE_STRATEGY_FEWESTCALLS:
+ tmp->metric = mem->calls;
+ tmp->metric += mem->penalty * 1000000;
+ break;
+ case QUEUE_STRATEGY_LEASTRECENT:
+ if (!mem->lastcall)
+ tmp->metric = 0;
+ else
+ tmp->metric = 1000000 - (time(NULL) - mem->lastcall);
+ tmp->metric += mem->penalty * 1000000;
+ break;
+ default:
+ ast_log(LOG_WARNING, "Can't calculate metric for unknown strategy %d\n", q->strategy);
+ break;
+ }
+ return 0;
+}
+
+struct queue_transfer_ds {
+ struct queue_ent *qe;
+ struct member *member;
+ time_t starttime;
+ int callcompletedinsl;
+};
+
+static void queue_transfer_destroy(void *data)
+{
+ struct queue_transfer_ds *qtds = data;
+ ast_free(qtds);
+}
+
+/*! \brief a datastore used to help correctly log attended transfers of queue callers
+ */
+static const struct ast_datastore_info queue_transfer_info = {
+ .type = "queue_transfer",
+ .chan_fixup = queue_transfer_fixup,
+ .destroy = queue_transfer_destroy,
+};
+
+/*! \brief Log an attended transfer when a queue caller channel is masqueraded
+ *
+ * When a caller is masqueraded, we want to log a transfer. Fixup time is the closest we can come to when
+ * the actual transfer occurs. This happens during the masquerade after datastores are moved from old_chan
+ * to new_chan. This is why new_chan is referenced for exten, context, and datastore information.
+ *
+ * At the end of this, we want to remove the datastore so that this fixup function is not called on any
+ * future masquerades of the caller during the current call.
+ */
+static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
+{
+ struct queue_transfer_ds *qtds = data;
+ struct queue_ent *qe = qtds->qe;
+ struct member *member = qtds->member;
+ time_t callstart = qtds->starttime;
+ int callcompletedinsl = qtds->callcompletedinsl;
+ struct ast_datastore *datastore;
+
+ ast_queue_log(qe->parent->name, qe->chan->uniqueid, member->membername, "TRANSFER", "%s|%s|%ld|%ld",
+ new_chan->exten, new_chan->context, (long) (callstart - qe->start),
+ (long) (time(NULL) - callstart));
+
+ update_queue(qe->parent, member, callcompletedinsl);
+
+ /* No need to lock the channels because they are already locked in ast_do_masquerade */
+ if ((datastore = ast_channel_datastore_find(old_chan, &queue_transfer_info, NULL))) {
+ ast_channel_datastore_remove(old_chan, datastore);
+ } else {
+ ast_log(LOG_WARNING, "Can't find the queue_transfer datastore.\n");
+ }
+}
+
+/*! \brief mechanism to tell if a queue caller was atxferred by a queue member.
+ *
+ * When a caller is atxferred, then the queue_transfer_info datastore
+ * is removed from the channel. If it's still there after the bridge is
+ * broken, then the caller was not atxferred.
+ *
+ * \note Only call this with chan locked
+ */
+static int attended_transfer_occurred(struct ast_channel *chan)
+{
+ return ast_channel_datastore_find(chan, &queue_transfer_info, NULL) ? 0 : 1;
+}
+
+/*! \brief create a datastore for storing relevant info to log attended transfers in the queue_log
+ */
+static struct ast_datastore *setup_transfer_datastore(struct queue_ent *qe, struct member *member, time_t starttime, int callcompletedinsl)
+{
+ struct ast_datastore *ds;
+ struct queue_transfer_ds *qtds = ast_calloc(1, sizeof(*qtds));
+
+ if (!qtds) {
+ ast_log(LOG_WARNING, "Memory allocation error!\n");
+ return NULL;
+ }
+
+ ast_channel_lock(qe->chan);
+ if (!(ds = ast_channel_datastore_alloc(&queue_transfer_info, NULL))) {
+ ast_channel_unlock(qe->chan);
+ ast_log(LOG_WARNING, "Unable to create transfer datastore. queue_log will not show attended transfer\n");
+ return NULL;
+ }
+
+ qtds->qe = qe;
+ /* This member is refcounted in try_calling, so no need to add it here, too */
+ qtds->member = member;
+ qtds->starttime = starttime;
+ qtds->callcompletedinsl = callcompletedinsl;
+ ds->data = qtds;
+ ast_channel_datastore_add(qe->chan, ds);
+ ast_channel_unlock(qe->chan);
+ return ds;
+}
+
+
+/*! \brief A large function which calls members, updates statistics, and bridges the caller and a member
+ *
+ * Here is the process of this function
+ * 1. Process any options passed to the Queue() application. Options here mean the third argument to Queue()
+ * 2. Iterate trough the members of the queue, creating a callattempt corresponding to each member. During this
+ * iteration, we also check the dialed_interfaces datastore to see if we have already attempted calling this
+ * member. If we have, we do not create a callattempt. This is in place to prevent call forwarding loops. Also
+ * during each iteration, we call calc_metric to determine which members should be rung when.
+ * 3. Call ring_one to place a call to the appropriate member(s)
+ * 4. Call wait_for_answer to wait for an answer. If no one answers, return.
+ * 5. Take care of any holdtime announcements, member delays, or other options which occur after a call has been answered.
+ * 6. Start the monitor or mixmonitor if the option is set
+ * 7. Remove the caller from the queue to allow other callers to advance
+ * 8. Bridge the call.
+ * 9. Do any post processing after the call has disconnected.
+ *
+ * \param[in] qe the queue_ent structure which corresponds to the caller attempting to reach members
+ * \param[in] options the options passed as the third parameter to the Queue() application
+ * \param[in] url the url passed as the fourth parameter to the Queue() application
+ * \param[in,out] tries the number of times we have tried calling queue members
+ * \param[out] noption set if the call to Queue() has the 'n' option set.
+ * \param[in] agi the agi passed as the fifth parameter to the Queue() application
+ */
+
+static int try_calling(struct queue_ent *qe, const char *options, char *announceoverride, const char *url, int *tries, int *noption, const char *agi)
+{
+ struct member *cur;
+ struct callattempt *outgoing = NULL; /* the list of calls we are building */
+ int to;
+ char oldexten[AST_MAX_EXTENSION]="";
+ char oldcontext[AST_MAX_CONTEXT]="";
+ char queuename[256]="";
+ struct ast_channel *peer;
+ struct ast_channel *which;
+ struct callattempt *lpeer;
+ struct member *member;
+ struct ast_app *app;
+ int res = 0, bridge = 0;
+ int numbusies = 0;
+ int x=0;
+ char *announce = NULL;
+ char digit = 0;
+ time_t callstart;
+ time_t now = time(NULL);
+ struct ast_bridge_config bridge_config;
+ char nondataquality = 1;
+ char *agiexec = NULL;
+ int ret = 0;
+ const char *monitorfilename;
+ const char *monitor_exec;
+ const char *monitor_options;
+ char tmpid[256], tmpid2[256];
+ char meid[1024], meid2[1024];
+ char mixmonargs[1512];
+ struct ast_app *mixmonapp = NULL;
+ char *p;
+ char vars[2048];
+ int forwardsallowed = 1;
+ int callcompletedinsl;
+ struct ao2_iterator memi;
+ struct ast_datastore *datastore, *transfer_ds;
+
+ ast_channel_lock(qe->chan);
+ datastore = ast_channel_datastore_find(qe->chan, &dialed_interface_info, NULL);
+ ast_channel_unlock(qe->chan);
+
+ memset(&bridge_config, 0, sizeof(bridge_config));
+ time(&now);
+
+ /* If we've already exceeded our timeout, then just stop
+ * This should be extremely rare. queue_exec will take care
+ * of removing the caller and reporting the timeout as the reason.
+ */
+ if (qe->expire && now >= qe->expire) {
+ res = 0;
+ goto out;
+ }
+
+ for (; options && *options; options++)
+ switch (*options) {
+ case 't':
+ ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_REDIRECT);
+ break;
+ case 'T':
+ ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_REDIRECT);
+ break;
+ case 'w':
+ ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_AUTOMON);
+ break;
+ case 'W':
+ ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_AUTOMON);
+ break;
+ case 'd':
+ nondataquality = 0;
+ break;
+ case 'h':
+ ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_DISCONNECT);
+ break;
+ case 'H':
+ ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT);
+ break;
+ case 'n':
+ if (qe->parent->strategy == QUEUE_STRATEGY_ROUNDROBIN || qe->parent->strategy == QUEUE_STRATEGY_RRMEMORY)
+ (*tries)++;
+ else
+ *tries = qe->parent->membercount;
+ *noption = 1;
+ break;
+ case 'i':
+ forwardsallowed = 0;
+ break;
+ }
+
+ /* Hold the lock while we setup the outgoing calls */
+ if (use_weight)
+ AST_LIST_LOCK(&queues);
+ ast_mutex_lock(&qe->parent->lock);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "%s is trying to call a queue member.\n",
+ qe->chan->name);
+ ast_copy_string(queuename, qe->parent->name, sizeof(queuename));
+ if (!ast_strlen_zero(qe->announce))
+ announce = qe->announce;
+ if (!ast_strlen_zero(announceoverride))
+ announce = announceoverride;
+
+ memi = ao2_iterator_init(qe->parent->members, 0);
+ while ((cur = ao2_iterator_next(&memi))) {
+ struct callattempt *tmp = ast_calloc(1, sizeof(*tmp));
+ struct ast_dialed_interface *di;
+ AST_LIST_HEAD(, ast_dialed_interface) *dialed_interfaces;
+ if (!tmp) {
+ ao2_ref(cur, -1);
+ ast_mutex_unlock(&qe->parent->lock);
+ if (use_weight)
+ AST_LIST_UNLOCK(&queues);
+ goto out;
+ }
+ if (!datastore) {
+ if (!(datastore = ast_channel_datastore_alloc(&dialed_interface_info, NULL))) {
+ ao2_ref(cur, -1);
+ ast_mutex_unlock(&qe->parent->lock);
+ if (use_weight)
+ AST_LIST_UNLOCK(&queues);
+ free(tmp);
+ goto out;
+ }
+ datastore->inheritance = DATASTORE_INHERIT_FOREVER;
+ if (!(dialed_interfaces = ast_calloc(1, sizeof(*dialed_interfaces)))) {
+ ao2_ref(cur, -1);
+ ast_mutex_unlock(&qe->parent->lock);
+ if (use_weight)
+ AST_LIST_UNLOCK(&queues);
+ free(tmp);
+ goto out;
+ }
+ datastore->data = dialed_interfaces;
+ AST_LIST_HEAD_INIT(dialed_interfaces);
+
+ ast_channel_lock(qe->chan);
+ ast_channel_datastore_add(qe->chan, datastore);
+ ast_channel_unlock(qe->chan);
+ } else
+ dialed_interfaces = datastore->data;
+
+ AST_LIST_LOCK(dialed_interfaces);
+ AST_LIST_TRAVERSE(dialed_interfaces, di, list) {
+ if (!strcasecmp(cur->interface, di->interface)) {
+ ast_log(LOG_DEBUG, "Skipping dialing interface '%s' since it has already been dialed\n",
+ di->interface);
+ break;
+ }
+ }
+ AST_LIST_UNLOCK(dialed_interfaces);
+
+ if (di) {
+ free(tmp);
+ continue;
+ }
+
+ /* It is always ok to dial a Local interface. We only keep track of
+ * which "real" interfaces have been dialed. The Local channel will
+ * inherit this list so that if it ends up dialing a real interface,
+ * it won't call one that has already been called. */
+ if (strncasecmp(cur->interface, "Local/", 6)) {
+ if (!(di = ast_calloc(1, sizeof(*di) + strlen(cur->interface)))) {
+ ao2_ref(cur, -1);
+ ast_mutex_unlock(&qe->parent->lock);
+ if (use_weight)
+ AST_LIST_UNLOCK(&queues);
+ free(tmp);
+ goto out;
+ }
+ strcpy(di->interface, cur->interface);
+
+ AST_LIST_LOCK(dialed_interfaces);
+ AST_LIST_INSERT_TAIL(dialed_interfaces, di, list);
+ AST_LIST_UNLOCK(dialed_interfaces);
+ }
+
+ tmp->stillgoing = -1;
+ tmp->member = cur;
+ tmp->oldstatus = cur->status;
+ tmp->lastcall = cur->lastcall;
+ ast_copy_string(tmp->interface, cur->interface, sizeof(tmp->interface));
+ /* Special case: If we ring everyone, go ahead and ring them, otherwise
+ just calculate their metric for the appropriate strategy */
+ if (!calc_metric(qe->parent, cur, x++, qe, tmp)) {
+ /* Put them in the list of outgoing thingies... We're ready now.
+ XXX If we're forcibly removed, these outgoing calls won't get
+ hung up XXX */
+ tmp->q_next = outgoing;
+ outgoing = tmp;
+ /* If this line is up, don't try anybody else */
+ if (outgoing->chan && (outgoing->chan->_state == AST_STATE_UP))
+ break;
+ } else {
+ ao2_ref(cur, -1);
+ free(tmp);
+ }
+ }
+ if (qe->expire && (!qe->parent->timeout || (qe->expire - now) <= qe->parent->timeout))
+ to = (qe->expire - now) * 1000;
+ else
+ to = (qe->parent->timeout) ? qe->parent->timeout * 1000 : -1;
+ ++qe->pending;
+ ast_mutex_unlock(&qe->parent->lock);
+ ring_one(qe, outgoing, &numbusies);
+ if (use_weight)
+ AST_LIST_UNLOCK(&queues);
+ lpeer = wait_for_answer(qe, outgoing, &to, &digit, numbusies, ast_test_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT), forwardsallowed);
+ /* The ast_channel_datastore_remove() function could fail here if the
+ * datastore was moved to another channel during a masquerade. If this is
+ * the case, don't free the datastore here because later, when the channel
+ * to which the datastore was moved hangs up, it will attempt to free this
+ * datastore again, causing a crash
+ */
+ ast_channel_lock(qe->chan);
+ if (datastore && !ast_channel_datastore_remove(qe->chan, datastore)) {
+ ast_channel_datastore_free(datastore);
+ }
+ ast_channel_unlock(qe->chan);
+ ast_mutex_lock(&qe->parent->lock);
+ if (qe->parent->strategy == QUEUE_STRATEGY_RRMEMORY) {
+ store_next(qe, outgoing);
+ }
+ ast_mutex_unlock(&qe->parent->lock);
+ peer = lpeer ? lpeer->chan : NULL;
+ if (!peer) {
+ qe->pending = 0;
+ if (to) {
+ /* Must gotten hung up */
+ res = -1;
+ } else {
+ /* User exited by pressing a digit */
+ res = digit;
+ }
+ if (option_debug && res == -1)
+ ast_log(LOG_DEBUG, "%s: Nobody answered.\n", qe->chan->name);
+ } else { /* peer is valid */
+ /* Ah ha! Someone answered within the desired timeframe. Of course after this
+ we will always return with -1 so that it is hung up properly after the
+ conversation. */
+ if (!strcmp(qe->chan->tech->type, "Zap"))
+ ast_channel_setoption(qe->chan, AST_OPTION_TONE_VERIFY, &nondataquality, sizeof(nondataquality), 0);
+ if (!strcmp(peer->tech->type, "Zap"))
+ ast_channel_setoption(peer, AST_OPTION_TONE_VERIFY, &nondataquality, sizeof(nondataquality), 0);
+ /* Update parameters for the queue */
+ time(&now);
+ recalc_holdtime(qe, (now - qe->start));
+ ast_mutex_lock(&qe->parent->lock);
+ callcompletedinsl = ((now - qe->start) <= qe->parent->servicelevel);
+ ast_mutex_unlock(&qe->parent->lock);
+ member = lpeer->member;
+ /* Increment the refcount for this member, since we're going to be using it for awhile in here. */
+ ao2_ref(member, 1);
+ hangupcalls(outgoing, peer);
+ outgoing = NULL;
+ if (announce || qe->parent->reportholdtime || qe->parent->memberdelay) {
+ int res2;
+
+ res2 = ast_autoservice_start(qe->chan);
+ if (!res2) {
+ if (qe->parent->memberdelay) {
+ ast_log(LOG_NOTICE, "Delaying member connect for %d seconds\n", qe->parent->memberdelay);
+ res2 |= ast_safe_sleep(peer, qe->parent->memberdelay * 1000);
+ }
+ if (!res2 && announce) {
+ play_file(peer, announce);
+ }
+ if (!res2 && qe->parent->reportholdtime) {
+ if (!play_file(peer, qe->parent->sound_reporthold)) {
+ int holdtime;
+
+ time(&now);
+ holdtime = abs((now - qe->start) / 60);
+ if (holdtime < 2) {
+ play_file(peer, qe->parent->sound_lessthan);
+ ast_say_number(peer, 2, AST_DIGIT_ANY, peer->language, NULL);
+ } else
+ ast_say_number(peer, holdtime, AST_DIGIT_ANY, peer->language, NULL);
+ play_file(peer, qe->parent->sound_minutes);
+ }
+ }
+ }
+ res2 |= ast_autoservice_stop(qe->chan);
+ if (peer->_softhangup) {
+ /* Agent must have hung up */
+ ast_log(LOG_WARNING, "Agent on %s hungup on the customer.\n", peer->name);
+ ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "AGENTDUMP", "%s", "");
+ if (qe->parent->eventwhencalled)
+ manager_event(EVENT_FLAG_AGENT, "AgentDump",
+ "Queue: %s\r\n"
+ "Uniqueid: %s\r\n"
+ "Channel: %s\r\n"
+ "Member: %s\r\n"
+ "MemberName: %s\r\n"
+ "%s",
+ queuename, qe->chan->uniqueid, peer->name, member->interface, member->membername,
+ qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : "");
+ ast_hangup(peer);
+ ao2_ref(member, -1);
+ goto out;
+ } else if (res2) {
+ /* Caller must have hung up just before being connected*/
+ ast_log(LOG_NOTICE, "Caller was about to talk to agent on %s but the caller hungup.\n", peer->name);
+ ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "ABANDON", "%d|%d|%ld", qe->pos, qe->opos, (long)time(NULL) - qe->start);
+ record_abandoned(qe);
+ ast_hangup(peer);
+ ao2_ref(member, -1);
+ return -1;
+ }
+ }
+ /* Stop music on hold */
+ ast_moh_stop(qe->chan);
+ /* If appropriate, log that we have a destination channel */
+ if (qe->chan->cdr)
+ ast_cdr_setdestchan(qe->chan->cdr, peer->name);
+ /* Make sure channels are compatible */
+ res = ast_channel_make_compatible(qe->chan, peer);
+ if (res < 0) {
+ ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "SYSCOMPAT", "%s", "");
+ ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", qe->chan->name, peer->name);
+ record_abandoned(qe);
+ ast_hangup(peer);
+ ao2_ref(member, -1);
+ return -1;
+ }
+
+ if (qe->parent->setinterfacevar)
+ pbx_builtin_setvar_helper(qe->chan, "MEMBERINTERFACE", member->interface);
+
+ /* Begin Monitoring */
+ if (qe->parent->monfmt && *qe->parent->monfmt) {
+ if (!qe->parent->montype) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Starting Monitor as requested.\n");
+ monitorfilename = pbx_builtin_getvar_helper(qe->chan, "MONITOR_FILENAME");
+ if (pbx_builtin_getvar_helper(qe->chan, "MONITOR_EXEC") || pbx_builtin_getvar_helper(qe->chan, "MONITOR_EXEC_ARGS"))
+ which = qe->chan;
+ else
+ which = peer;
+ if (monitorfilename)
+ ast_monitor_start(which, qe->parent->monfmt, monitorfilename, 1 );
+ else if (qe->chan->cdr)
+ ast_monitor_start(which, qe->parent->monfmt, qe->chan->cdr->uniqueid, 1 );
+ else {
+ /* Last ditch effort -- no CDR, make up something */
+ snprintf(tmpid, sizeof(tmpid), "chan-%lx", ast_random());
+ ast_monitor_start(which, qe->parent->monfmt, tmpid, 1 );
+ }
+ if (qe->parent->monjoin)
+ ast_monitor_setjoinfiles(which, 1);
+ } else {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Starting MixMonitor as requested.\n");
+ monitorfilename = pbx_builtin_getvar_helper(qe->chan, "MONITOR_FILENAME");
+ if (!monitorfilename) {
+ if (qe->chan->cdr)
+ ast_copy_string(tmpid, qe->chan->cdr->uniqueid, sizeof(tmpid)-1);
+ else
+ snprintf(tmpid, sizeof(tmpid), "chan-%lx", ast_random());
+ } else {
+ ast_copy_string(tmpid2, monitorfilename, sizeof(tmpid2)-1);
+ for (p = tmpid2; *p ; p++) {
+ if (*p == '^' && *(p+1) == '{') {
+ *p = '$';
+ }
+ }
+
+ memset(tmpid, 0, sizeof(tmpid));
+ pbx_substitute_variables_helper(qe->chan, tmpid2, tmpid, sizeof(tmpid) - 1);
+ }
+
+ monitor_exec = pbx_builtin_getvar_helper(qe->chan, "MONITOR_EXEC");
+ monitor_options = pbx_builtin_getvar_helper(qe->chan, "MONITOR_OPTIONS");
+
+ if (monitor_exec) {
+ ast_copy_string(meid2, monitor_exec, sizeof(meid2)-1);
+ for (p = meid2; *p ; p++) {
+ if (*p == '^' && *(p+1) == '{') {
+ *p = '$';
+ }
+ }
+
+ memset(meid, 0, sizeof(meid));
+ pbx_substitute_variables_helper(qe->chan, meid2, meid, sizeof(meid) - 1);
+ }
+
+ snprintf(tmpid2, sizeof(tmpid2)-1, "%s.%s", tmpid, qe->parent->monfmt);
+
+ mixmonapp = pbx_findapp("MixMonitor");
+
+ if (strchr(tmpid2, '|')) {
+ ast_log(LOG_WARNING, "monitor-format (in queues.conf) and MONITOR_FILENAME cannot contain a '|'! Not recording.\n");
+ mixmonapp = NULL;
+ }
+
+ if (!monitor_options)
+ monitor_options = "";
+
+ if (strchr(monitor_options, '|')) {
+ ast_log(LOG_WARNING, "MONITOR_OPTIONS cannot contain a '|'! Not recording.\n");
+ mixmonapp = NULL;
+ }
+
+ if (mixmonapp) {
+ if (!ast_strlen_zero(monitor_exec))
+ snprintf(mixmonargs, sizeof(mixmonargs)-1, "%s|b%s|%s", tmpid2, monitor_options, monitor_exec);
+ else
+ snprintf(mixmonargs, sizeof(mixmonargs)-1, "%s|b%s", tmpid2, monitor_options);
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Arguments being passed to MixMonitor: %s\n", mixmonargs);
+ /* We purposely lock the CDR so that pbx_exec does not update the application data */
+ if (qe->chan->cdr)
+ ast_set_flag(qe->chan->cdr, AST_CDR_FLAG_LOCKED);
+ ret = pbx_exec(qe->chan, mixmonapp, mixmonargs);
+ if (qe->chan->cdr)
+ ast_clear_flag(qe->chan->cdr, AST_CDR_FLAG_LOCKED);
+
+ } else
+ ast_log(LOG_WARNING, "Asked to run MixMonitor on this call, but cannot find the MixMonitor app!\n");
+
+ }
+ }
+ /* Drop out of the queue at this point, to prepare for next caller */
+ leave_queue(qe);
+ if (!ast_strlen_zero(url) && ast_channel_supports_html(peer)) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "app_queue: sendurl=%s.\n", url);
+ ast_channel_sendurl(peer, url);
+ }
+ if (!ast_strlen_zero(agi)) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "app_queue: agi=%s.\n", agi);
+ app = pbx_findapp("agi");
+ if (app) {
+ agiexec = ast_strdupa(agi);
+ ret = pbx_exec(qe->chan, app, agiexec);
+ } else
+ ast_log(LOG_WARNING, "Asked to execute an AGI on this channel, but could not find application (agi)!\n");
+ }
+ qe->handled++;
+ ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "CONNECT", "%ld|%s", (long)time(NULL) - qe->start, peer->uniqueid);
+ if (qe->parent->eventwhencalled)
+ manager_event(EVENT_FLAG_AGENT, "AgentConnect",
+ "Queue: %s\r\n"
+ "Uniqueid: %s\r\n"
+ "Channel: %s\r\n"
+ "Member: %s\r\n"
+ "MemberName: %s\r\n"
+ "Holdtime: %ld\r\n"
+ "BridgedChannel: %s\r\n"
+ "%s",
+ queuename, qe->chan->uniqueid, peer->name, member->interface, member->membername,
+ (long)time(NULL) - qe->start, peer->uniqueid,
+ qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : "");
+ ast_copy_string(oldcontext, qe->chan->context, sizeof(oldcontext));
+ ast_copy_string(oldexten, qe->chan->exten, sizeof(oldexten));
+ time(&callstart);
+
+ if (member->status == AST_DEVICE_NOT_INUSE)
+ ast_log(LOG_WARNING, "The device state of this queue member, %s, is still 'Not in Use' when it probably should not be! Please check UPGRADE.txt for correct configuration settings.\n", member->membername);
+
+ transfer_ds = setup_transfer_datastore(qe, member, callstart, callcompletedinsl);
+ bridge = ast_bridge_call(qe->chan,peer, &bridge_config);
+
+ ast_channel_lock(qe->chan);
+ if (!attended_transfer_occurred(qe->chan)) {
+ struct ast_datastore *tds;
+ if (strcasecmp(oldcontext, qe->chan->context) || strcasecmp(oldexten, qe->chan->exten)) {
+ ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "TRANSFER", "%s|%s|%ld|%ld",
+ qe->chan->exten, qe->chan->context, (long) (callstart - qe->start),
+ (long) (time(NULL) - callstart));
+ } else if (qe->chan->_softhangup) {
+ ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "COMPLETECALLER", "%ld|%ld|%d",
+ (long) (callstart - qe->start), (long) (time(NULL) - callstart), qe->opos);
+ if (qe->parent->eventwhencalled)
+ manager_event(EVENT_FLAG_AGENT, "AgentComplete",
+ "Queue: %s\r\n"
+ "Uniqueid: %s\r\n"
+ "Channel: %s\r\n"
+ "Member: %s\r\n"
+ "MemberName: %s\r\n"
+ "HoldTime: %ld\r\n"
+ "TalkTime: %ld\r\n"
+ "Reason: caller\r\n"
+ "%s",
+ queuename, qe->chan->uniqueid, peer->name, member->interface, member->membername,
+ (long)(callstart - qe->start), (long)(time(NULL) - callstart),
+ qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : "");
+ } else {
+ ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "COMPLETEAGENT", "%ld|%ld|%d",
+ (long) (callstart - qe->start), (long) (time(NULL) - callstart), qe->opos);
+ if (qe->parent->eventwhencalled)
+ manager_event(EVENT_FLAG_AGENT, "AgentComplete",
+ "Queue: %s\r\n"
+ "Uniqueid: %s\r\n"
+ "Channel: %s\r\n"
+ "MemberName: %s\r\n"
+ "HoldTime: %ld\r\n"
+ "TalkTime: %ld\r\n"
+ "Reason: agent\r\n"
+ "%s",
+ queuename, qe->chan->uniqueid, peer->name, member->membername, (long)(callstart - qe->start),
+ (long)(time(NULL) - callstart),
+ qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : "");
+ }
+ if ((tds = ast_channel_datastore_find(qe->chan, &queue_transfer_info, NULL))) {
+ ast_channel_datastore_remove(qe->chan, tds);
+ }
+ update_queue(qe->parent, member, callcompletedinsl);
+ }
+
+ if (transfer_ds) {
+ ast_channel_datastore_free(transfer_ds);
+ }
+ ast_channel_unlock(qe->chan);
+ ast_hangup(peer);
+ res = bridge ? bridge : 1;
+ ao2_ref(member, -1);
+ }
+out:
+ hangupcalls(outgoing, NULL);
+
+ return res;
+}
+
+static int wait_a_bit(struct queue_ent *qe)
+{
+ /* Don't need to hold the lock while we setup the outgoing calls */
+ int retrywait = qe->parent->retry * 1000;
+
+ int res = ast_waitfordigit(qe->chan, retrywait);
+ if (res > 0 && !valid_exit(qe, res))
+ res = 0;
+
+ return res;
+}
+
+static struct member *interface_exists(struct call_queue *q, const char *interface)
+{
+ struct member *mem;
+ struct ao2_iterator mem_iter;
+
+ if (!q)
+ return NULL;
+
+ mem_iter = ao2_iterator_init(q->members, 0);
+ while ((mem = ao2_iterator_next(&mem_iter))) {
+ if (!strcasecmp(interface, mem->interface))
+ return mem;
+ ao2_ref(mem, -1);
+ }
+
+ return NULL;
+}
+
+
+/* Dump all members in a specific queue to the database
+ *
+ * <pm_family>/<queuename> = <interface>;<penalty>;<paused>[|...]
+ *
+ */
+static void dump_queue_members(struct call_queue *pm_queue)
+{
+ struct member *cur_member;
+ char value[PM_MAX_LEN];
+ int value_len = 0;
+ int res;
+ struct ao2_iterator mem_iter;
+
+ memset(value, 0, sizeof(value));
+
+ if (!pm_queue)
+ return;
+
+ mem_iter = ao2_iterator_init(pm_queue->members, 0);
+ while ((cur_member = ao2_iterator_next(&mem_iter))) {
+ if (!cur_member->dynamic) {
+ ao2_ref(cur_member, -1);
+ continue;
+ }
+
+ res = snprintf(value + value_len, sizeof(value) - value_len, "%s%s;%d;%d;%s",
+ value_len ? "|" : "", cur_member->interface, cur_member->penalty, cur_member->paused, cur_member->membername);
+
+ ao2_ref(cur_member, -1);
+
+ if (res != strlen(value + value_len)) {
+ ast_log(LOG_WARNING, "Could not create persistent member string, out of space\n");
+ break;
+ }
+ value_len += res;
+ }
+
+ if (value_len && !cur_member) {
+ if (ast_db_put(pm_family, pm_queue->name, value))
+ ast_log(LOG_WARNING, "failed to create persistent dynamic entry!\n");
+ } else
+ /* Delete the entry if the queue is empty or there is an error */
+ ast_db_del(pm_family, pm_queue->name);
+}
+
+static int remove_from_queue(const char *queuename, const char *interface)
+{
+ struct call_queue *q;
+ struct member *mem, tmpmem;
+ int res = RES_NOSUCHQUEUE;
+
+ ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface));
+
+ AST_LIST_LOCK(&queues);
+ AST_LIST_TRAVERSE(&queues, q, list) {
+ ast_mutex_lock(&q->lock);
+ if (strcmp(q->name, queuename)) {
+ ast_mutex_unlock(&q->lock);
+ continue;
+ }
+
+ if ((mem = ao2_find(q->members, &tmpmem, OBJ_POINTER))) {
+ /* XXX future changes should beware of this assumption!! */
+ if (!mem->dynamic) {
+ res = RES_NOT_DYNAMIC;
+ ao2_ref(mem, -1);
+ ast_mutex_unlock(&q->lock);
+ break;
+ }
+ q->membercount--;
+ manager_event(EVENT_FLAG_AGENT, "QueueMemberRemoved",
+ "Queue: %s\r\n"
+ "Location: %s\r\n"
+ "MemberName: %s\r\n",
+ q->name, mem->interface, mem->membername);
+ ao2_unlink(q->members, mem);
+ ao2_ref(mem, -1);
+
+ if (queue_persistent_members)
+ dump_queue_members(q);
+
+ res = RES_OKAY;
+ } else {
+ res = RES_EXISTS;
+ }
+ ast_mutex_unlock(&q->lock);
+ break;
+ }
+
+ if (res == RES_OKAY)
+ remove_from_interfaces(interface);
+
+ AST_LIST_UNLOCK(&queues);
+
+ return res;
+}
+
+
+static int add_to_queue(const char *queuename, const char *interface, const char *membername, int penalty, int paused, int dump)
+{
+ struct call_queue *q;
+ struct member *new_member, *old_member;
+ int res = RES_NOSUCHQUEUE;
+
+ /* \note Ensure the appropriate realtime queue is loaded. Note that this
+ * short-circuits if the queue is already in memory. */
+ if (!(q = load_realtime_queue(queuename)))
+ return res;
+
+ AST_LIST_LOCK(&queues);
+
+ ast_mutex_lock(&q->lock);
+ if ((old_member = interface_exists(q, interface)) == NULL) {
+ add_to_interfaces(interface);
+ if ((new_member = create_queue_member(interface, membername, penalty, paused))) {
+ new_member->dynamic = 1;
+ ao2_link(q->members, new_member);
+ q->membercount++;
+ manager_event(EVENT_FLAG_AGENT, "QueueMemberAdded",
+ "Queue: %s\r\n"
+ "Location: %s\r\n"
+ "MemberName: %s\r\n"
+ "Membership: %s\r\n"
+ "Penalty: %d\r\n"
+ "CallsTaken: %d\r\n"
+ "LastCall: %d\r\n"
+ "Status: %d\r\n"
+ "Paused: %d\r\n",
+ q->name, new_member->interface, new_member->membername,
+ "dynamic",
+ new_member->penalty, new_member->calls, (int) new_member->lastcall,
+ new_member->status, new_member->paused);
+
+ ao2_ref(new_member, -1);
+ new_member = NULL;
+
+ if (dump)
+ dump_queue_members(q);
+
+ res = RES_OKAY;
+ } else {
+ res = RES_OUTOFMEMORY;
+ }
+ } else {
+ ao2_ref(old_member, -1);
+ res = RES_EXISTS;
+ }
+ ast_mutex_unlock(&q->lock);
+ AST_LIST_UNLOCK(&queues);
+
+ return res;
+}
+
+static int set_member_paused(const char *queuename, const char *interface, int paused)
+{
+ int found = 0;
+ struct call_queue *q;
+ struct member *mem;
+
+ /* Special event for when all queues are paused - individual events still generated */
+ /* XXX In all other cases, we use the membername, but since this affects all queues, we cannot */
+ if (ast_strlen_zero(queuename))
+ ast_queue_log("NONE", "NONE", interface, (paused ? "PAUSEALL" : "UNPAUSEALL"), "%s", "");
+
+ AST_LIST_LOCK(&queues);
+ AST_LIST_TRAVERSE(&queues, q, list) {
+ ast_mutex_lock(&q->lock);
+ if (ast_strlen_zero(queuename) || !strcasecmp(q->name, queuename)) {
+ if ((mem = interface_exists(q, interface))) {
+ found++;
+ if (mem->paused == paused)
+ ast_log(LOG_DEBUG, "%spausing already-%spaused queue member %s:%s\n", (paused ? "" : "un"), (paused ? "" : "un"), q->name, interface);
+ mem->paused = paused;
+
+ if (queue_persistent_members)
+ dump_queue_members(q);
+
+ if (mem->realtime)
+ update_realtime_member_field(mem, q->name, "paused", paused ? "1" : "0");
+
+ ast_queue_log(q->name, "NONE", mem->membername, (paused ? "PAUSE" : "UNPAUSE"), "%s", "");
+
+ manager_event(EVENT_FLAG_AGENT, "QueueMemberPaused",
+ "Queue: %s\r\n"
+ "Location: %s\r\n"
+ "MemberName: %s\r\n"
+ "Paused: %d\r\n",
+ q->name, mem->interface, mem->membername, paused);
+ ao2_ref(mem, -1);
+ }
+ }
+ ast_mutex_unlock(&q->lock);
+ }
+ AST_LIST_UNLOCK(&queues);
+
+ return found ? RESULT_SUCCESS : RESULT_FAILURE;
+}
+
+/* Reload dynamic queue members persisted into the astdb */
+static void reload_queue_members(void)
+{
+ char *cur_ptr;
+ char *queue_name;
+ char *member;
+ char *interface;
+ char *membername = NULL;
+ char *penalty_tok;
+ int penalty = 0;
+ char *paused_tok;
+ int paused = 0;
+ struct ast_db_entry *db_tree;
+ struct ast_db_entry *entry;
+ struct call_queue *cur_queue;
+ char queue_data[PM_MAX_LEN];
+
+ AST_LIST_LOCK(&queues);
+
+ /* Each key in 'pm_family' is the name of a queue */
+ db_tree = ast_db_gettree(pm_family, NULL);
+ for (entry = db_tree; entry; entry = entry->next) {
+
+ queue_name = entry->key + strlen(pm_family) + 2;
+
+ AST_LIST_TRAVERSE(&queues, cur_queue, list) {
+ ast_mutex_lock(&cur_queue->lock);
+ if (!strcmp(queue_name, cur_queue->name))
+ break;
+ ast_mutex_unlock(&cur_queue->lock);
+ }
+
+ if (!cur_queue)
+ cur_queue = load_realtime_queue(queue_name);
+
+ if (!cur_queue) {
+ /* If the queue no longer exists, remove it from the
+ * database */
+ ast_log(LOG_WARNING, "Error loading persistent queue: '%s': it does not exist\n", queue_name);
+ ast_db_del(pm_family, queue_name);
+ continue;
+ } else
+ ast_mutex_unlock(&cur_queue->lock);
+
+ if (ast_db_get(pm_family, queue_name, queue_data, PM_MAX_LEN))
+ continue;
+
+ cur_ptr = queue_data;
+ while ((member = strsep(&cur_ptr, "|"))) {
+ if (ast_strlen_zero(member))
+ continue;
+
+ interface = strsep(&member, ";");
+ penalty_tok = strsep(&member, ";");
+ paused_tok = strsep(&member, ";");
+ membername = strsep(&member, ";");
+
+ if (!penalty_tok) {
+ ast_log(LOG_WARNING, "Error parsing persistent member string for '%s' (penalty)\n", queue_name);
+ break;
+ }
+ penalty = strtol(penalty_tok, NULL, 10);
+ if (errno == ERANGE) {
+ ast_log(LOG_WARNING, "Error converting penalty: %s: Out of range.\n", penalty_tok);
+ break;
+ }
+
+ if (!paused_tok) {
+ ast_log(LOG_WARNING, "Error parsing persistent member string for '%s' (paused)\n", queue_name);
+ break;
+ }
+ paused = strtol(paused_tok, NULL, 10);
+ if ((errno == ERANGE) || paused < 0 || paused > 1) {
+ ast_log(LOG_WARNING, "Error converting paused: %s: Expected 0 or 1.\n", paused_tok);
+ break;
+ }
+ if (ast_strlen_zero(membername))
+ membername = interface;
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Reload Members: Queue: %s Member: %s Name: %s Penalty: %d Paused: %d\n", queue_name, interface, membername, penalty, paused);
+
+ if (add_to_queue(queue_name, interface, membername, penalty, paused, 0) == RES_OUTOFMEMORY) {
+ ast_log(LOG_ERROR, "Out of Memory when reloading persistent queue member\n");
+ break;
+ }
+ }
+ }
+
+ AST_LIST_UNLOCK(&queues);
+ if (db_tree) {
+ ast_log(LOG_NOTICE, "Queue members successfully reloaded from database.\n");
+ ast_db_freetree(db_tree);
+ }
+}
+
+static int pqm_exec(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *lu;
+ char *parse;
+ int priority_jump = 0;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(queuename);
+ AST_APP_ARG(interface);
+ AST_APP_ARG(options);
+ );
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "PauseQueueMember requires an argument ([queuename]|interface[|options])\n");
+ return -1;
+ }
+
+ parse = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ lu = ast_module_user_add(chan);
+
+ if (args.options) {
+ if (strchr(args.options, 'j'))
+ priority_jump = 1;
+ }
+
+ if (ast_strlen_zero(args.interface)) {
+ ast_log(LOG_WARNING, "Missing interface argument to PauseQueueMember ([queuename]|interface[|options])\n");
+ ast_module_user_remove(lu);
+ return -1;
+ }
+
+ if (set_member_paused(args.queuename, args.interface, 1)) {
+ ast_log(LOG_WARNING, "Attempt to pause interface %s, not found\n", args.interface);
+ if (priority_jump || ast_opt_priority_jumping) {
+ if (ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101)) {
+ pbx_builtin_setvar_helper(chan, "PQMSTATUS", "NOTFOUND");
+ ast_module_user_remove(lu);
+ return 0;
+ }
+ }
+ ast_module_user_remove(lu);
+ pbx_builtin_setvar_helper(chan, "PQMSTATUS", "NOTFOUND");
+ return 0;
+ }
+
+ ast_module_user_remove(lu);
+ pbx_builtin_setvar_helper(chan, "PQMSTATUS", "PAUSED");
+
+ return 0;
+}
+
+static int upqm_exec(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *lu;
+ char *parse;
+ int priority_jump = 0;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(queuename);
+ AST_APP_ARG(interface);
+ AST_APP_ARG(options);
+ );
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "UnpauseQueueMember requires an argument ([queuename]|interface[|options])\n");
+ return -1;
+ }
+
+ parse = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ lu = ast_module_user_add(chan);
+
+ if (args.options) {
+ if (strchr(args.options, 'j'))
+ priority_jump = 1;
+ }
+
+ if (ast_strlen_zero(args.interface)) {
+ ast_log(LOG_WARNING, "Missing interface argument to PauseQueueMember ([queuename]|interface[|options])\n");
+ ast_module_user_remove(lu);
+ return -1;
+ }
+
+ if (set_member_paused(args.queuename, args.interface, 0)) {
+ ast_log(LOG_WARNING, "Attempt to unpause interface %s, not found\n", args.interface);
+ if (priority_jump || ast_opt_priority_jumping) {
+ if (ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101)) {
+ pbx_builtin_setvar_helper(chan, "UPQMSTATUS", "NOTFOUND");
+ ast_module_user_remove(lu);
+ return 0;
+ }
+ }
+ ast_module_user_remove(lu);
+ pbx_builtin_setvar_helper(chan, "UPQMSTATUS", "NOTFOUND");
+ return 0;
+ }
+
+ ast_module_user_remove(lu);
+ pbx_builtin_setvar_helper(chan, "UPQMSTATUS", "UNPAUSED");
+
+ return 0;
+}
+
+static int rqm_exec(struct ast_channel *chan, void *data)
+{
+ int res=-1;
+ struct ast_module_user *lu;
+ char *parse, *temppos = NULL;
+ int priority_jump = 0;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(queuename);
+ AST_APP_ARG(interface);
+ AST_APP_ARG(options);
+ );
+
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "RemoveQueueMember requires an argument (queuename[|interface[|options]])\n");
+ return -1;
+ }
+
+ parse = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ lu = ast_module_user_add(chan);
+
+ if (ast_strlen_zero(args.interface)) {
+ args.interface = ast_strdupa(chan->name);
+ temppos = strrchr(args.interface, '-');
+ if (temppos)
+ *temppos = '\0';
+ }
+
+ if (args.options) {
+ if (strchr(args.options, 'j'))
+ priority_jump = 1;
+ }
+
+ switch (remove_from_queue(args.queuename, args.interface)) {
+ case RES_OKAY:
+ ast_queue_log(args.queuename, chan->uniqueid, args.interface, "REMOVEMEMBER", "%s", "");
+ ast_log(LOG_NOTICE, "Removed interface '%s' from queue '%s'\n", args.interface, args.queuename);
+ pbx_builtin_setvar_helper(chan, "RQMSTATUS", "REMOVED");
+ res = 0;
+ break;
+ case RES_EXISTS:
+ ast_log(LOG_DEBUG, "Unable to remove interface '%s' from queue '%s': Not there\n", args.interface, args.queuename);
+ if (priority_jump || ast_opt_priority_jumping)
+ ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
+ pbx_builtin_setvar_helper(chan, "RQMSTATUS", "NOTINQUEUE");
+ res = 0;
+ break;
+ case RES_NOSUCHQUEUE:
+ ast_log(LOG_WARNING, "Unable to remove interface from queue '%s': No such queue\n", args.queuename);
+ pbx_builtin_setvar_helper(chan, "RQMSTATUS", "NOSUCHQUEUE");
+ res = 0;
+ break;
+ case RES_NOT_DYNAMIC:
+ ast_log(LOG_WARNING, "Unable to remove interface from queue '%s': '%s' is not a dynamic member\n", args.queuename, args.interface);
+ pbx_builtin_setvar_helper(chan, "RQMSTATUS", "NOTDYNAMIC");
+ res = 0;
+ break;
+ }
+
+ ast_module_user_remove(lu);
+
+ return res;
+}
+
+static int aqm_exec(struct ast_channel *chan, void *data)
+{
+ int res=-1;
+ struct ast_module_user *lu;
+ char *parse, *temppos = NULL;
+ int priority_jump = 0;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(queuename);
+ AST_APP_ARG(interface);
+ AST_APP_ARG(penalty);
+ AST_APP_ARG(options);
+ AST_APP_ARG(membername);
+ );
+ int penalty = 0;
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "AddQueueMember requires an argument (queuename[|[interface]|[penalty][|options][|membername]])\n");
+ return -1;
+ }
+
+ parse = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ lu = ast_module_user_add(chan);
+
+ if (ast_strlen_zero(args.interface)) {
+ args.interface = ast_strdupa(chan->name);
+ temppos = strrchr(args.interface, '-');
+ if (temppos)
+ *temppos = '\0';
+ }
+
+ if (!ast_strlen_zero(args.penalty)) {
+ if ((sscanf(args.penalty, "%d", &penalty) != 1) || penalty < 0) {
+ ast_log(LOG_WARNING, "Penalty '%s' is invalid, must be an integer >= 0\n", args.penalty);
+ penalty = 0;
+ }
+ }
+
+ if (args.options) {
+ if (strchr(args.options, 'j'))
+ priority_jump = 1;
+ }
+
+ switch (add_to_queue(args.queuename, args.interface, args.membername, penalty, 0, queue_persistent_members)) {
+ case RES_OKAY:
+ ast_queue_log(args.queuename, chan->uniqueid, args.interface, "ADDMEMBER", "%s", "");
+ ast_log(LOG_NOTICE, "Added interface '%s' to queue '%s'\n", args.interface, args.queuename);
+ pbx_builtin_setvar_helper(chan, "AQMSTATUS", "ADDED");
+ res = 0;
+ break;
+ case RES_EXISTS:
+ ast_log(LOG_WARNING, "Unable to add interface '%s' to queue '%s': Already there\n", args.interface, args.queuename);
+ if (priority_jump || ast_opt_priority_jumping)
+ ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
+ pbx_builtin_setvar_helper(chan, "AQMSTATUS", "MEMBERALREADY");
+ res = 0;
+ break;
+ case RES_NOSUCHQUEUE:
+ ast_log(LOG_WARNING, "Unable to add interface to queue '%s': No such queue\n", args.queuename);
+ pbx_builtin_setvar_helper(chan, "AQMSTATUS", "NOSUCHQUEUE");
+ res = 0;
+ break;
+ case RES_OUTOFMEMORY:
+ ast_log(LOG_ERROR, "Out of memory adding member %s to queue %s\n", args.interface, args.queuename);
+ break;
+ }
+
+ ast_module_user_remove(lu);
+
+ return res;
+}
+
+static int ql_exec(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *u;
+ char *parse;
+
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(queuename);
+ AST_APP_ARG(uniqueid);
+ AST_APP_ARG(membername);
+ AST_APP_ARG(event);
+ AST_APP_ARG(params);
+ );
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "QueueLog requires arguments (queuename|uniqueid|membername|event[|additionalinfo]\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ parse = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ if (ast_strlen_zero(args.queuename) || ast_strlen_zero(args.uniqueid)
+ || ast_strlen_zero(args.membername) || ast_strlen_zero(args.event)) {
+ ast_log(LOG_WARNING, "QueueLog requires arguments (queuename|uniqueid|membername|event[|additionalinfo])\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ ast_queue_log(args.queuename, args.uniqueid, args.membername, args.event,
+ "%s", args.params ? args.params : "");
+
+ ast_module_user_remove(u);
+
+ return 0;
+}
+
+/*!\brief The starting point for all queue calls
+ *
+ * The process involved here is to
+ * 1. Parse the options specified in the call to Queue()
+ * 2. Join the queue
+ * 3. Wait in a loop until it is our turn to try calling a queue member
+ * 4. Attempt to call a queue member
+ * 5. If 4. did not result in a bridged call, then check for between
+ * call options such as periodic announcements etc.
+ * 6. Try 4 again uless some condition (such as an expiration time) causes us to
+ * exit the queue.
+ */
+static int queue_exec(struct ast_channel *chan, void *data)
+{
+ int res=-1;
+ int ringing=0;
+ struct ast_module_user *lu;
+ const char *user_priority;
+ const char *max_penalty_str;
+ int prio;
+ int max_penalty;
+ enum queue_result reason = QUEUE_UNKNOWN;
+ /* whether to exit Queue application after the timeout hits */
+ int tries = 0;
+ int noption = 0;
+ char *parse;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(queuename);
+ AST_APP_ARG(options);
+ AST_APP_ARG(url);
+ AST_APP_ARG(announceoverride);
+ AST_APP_ARG(queuetimeoutstr);
+ AST_APP_ARG(agi);
+ );
+ /* Our queue entry */
+ struct queue_ent qe;
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "Queue requires an argument: queuename[|options[|URL[|announceoverride[|timeout[|agi]]]]]\n");
+ return -1;
+ }
+
+ parse = ast_strdupa(data);
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ lu = ast_module_user_add(chan);
+
+ /* Setup our queue entry */
+ memset(&qe, 0, sizeof(qe));
+ qe.start = time(NULL);
+
+ /* set the expire time based on the supplied timeout; */
+ if (!ast_strlen_zero(args.queuetimeoutstr))
+ qe.expire = qe.start + atoi(args.queuetimeoutstr);
+ else
+ qe.expire = 0;
+
+ /* Get the priority from the variable ${QUEUE_PRIO} */
+ user_priority = pbx_builtin_getvar_helper(chan, "QUEUE_PRIO");
+ if (user_priority) {
+ if (sscanf(user_priority, "%d", &prio) == 1) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "%s: Got priority %d from ${QUEUE_PRIO}.\n",
+ chan->name, prio);
+ } else {
+ ast_log(LOG_WARNING, "${QUEUE_PRIO}: Invalid value (%s), channel %s.\n",
+ user_priority, chan->name);
+ prio = 0;
+ }
+ } else {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "NO QUEUE_PRIO variable found. Using default.\n");
+ prio = 0;
+ }
+
+ /* Get the maximum penalty from the variable ${QUEUE_MAX_PENALTY} */
+ if ((max_penalty_str = pbx_builtin_getvar_helper(chan, "QUEUE_MAX_PENALTY"))) {
+ if (sscanf(max_penalty_str, "%d", &max_penalty) == 1) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "%s: Got max penalty %d from ${QUEUE_MAX_PENALTY}.\n",
+ chan->name, max_penalty);
+ } else {
+ ast_log(LOG_WARNING, "${QUEUE_MAX_PENALTY}: Invalid value (%s), channel %s.\n",
+ max_penalty_str, chan->name);
+ max_penalty = 0;
+ }
+ } else {
+ max_penalty = 0;
+ }
+
+ if (args.options && (strchr(args.options, 'r')))
+ ringing = 1;
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "queue: %s, options: %s, url: %s, announce: %s, expires: %ld, priority: %d\n",
+ args.queuename, args.options, args.url, args.announceoverride, (long)qe.expire, prio);
+
+ qe.chan = chan;
+ qe.prio = prio;
+ qe.max_penalty = max_penalty;
+ qe.last_pos_said = 0;
+ qe.last_pos = 0;
+ qe.last_periodic_announce_time = time(NULL);
+ qe.last_periodic_announce_sound = 0;
+ qe.valid_digits = 0;
+ if (!join_queue(args.queuename, &qe, &reason)) {
+ int makeannouncement = 0;
+
+ ast_queue_log(args.queuename, chan->uniqueid, "NONE", "ENTERQUEUE", "%s|%s", S_OR(args.url, ""),
+ S_OR(chan->cid.cid_num, ""));
+check_turns:
+ if (ringing) {
+ ast_indicate(chan, AST_CONTROL_RINGING);
+ } else {
+ ast_moh_start(chan, qe.moh, NULL);
+ }
+
+ /* This is the wait loop for callers 2 through maxlen */
+ res = wait_our_turn(&qe, ringing, &reason);
+ if (res)
+ goto stop;
+
+ for (;;) {
+ /* This is the wait loop for the head caller*/
+ /* To exit, they may get their call answered; */
+ /* they may dial a digit from the queue context; */
+ /* or, they may timeout. */
+
+ enum queue_member_status stat;
+
+ /* Leave if we have exceeded our queuetimeout */
+ if (qe.expire && (time(NULL) >= qe.expire)) {
+ record_abandoned(&qe);
+ reason = QUEUE_TIMEOUT;
+ res = 0;
+ ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos);
+ break;
+ }
+
+ if (makeannouncement) {
+ /* Make a position announcement, if enabled */
+ if (qe.parent->announcefrequency && !ringing)
+ if ((res = say_position(&qe)))
+ goto stop;
+
+ }
+ makeannouncement = 1;
+
+ /* Leave if we have exceeded our queuetimeout */
+ if (qe.expire && (time(NULL) >= qe.expire)) {
+ record_abandoned(&qe);
+ reason = QUEUE_TIMEOUT;
+ res = 0;
+ ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos);
+ break;
+ }
+ /* Make a periodic announcement, if enabled */
+ if (qe.parent->periodicannouncefrequency && !ringing)
+ if ((res = say_periodic_announcement(&qe)))
+ goto stop;
+
+ /* Leave if we have exceeded our queuetimeout */
+ if (qe.expire && (time(NULL) >= qe.expire)) {
+ record_abandoned(&qe);
+ reason = QUEUE_TIMEOUT;
+ res = 0;
+ ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos);
+ break;
+ }
+ /* Try calling all queue members for 'timeout' seconds */
+ res = try_calling(&qe, args.options, args.announceoverride, args.url, &tries, &noption, args.agi);
+ if (res)
+ goto stop;
+
+ stat = get_member_status(qe.parent, qe.max_penalty);
+
+ /* exit after 'timeout' cycle if 'n' option enabled */
+ if (noption && tries >= qe.parent->membercount) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Exiting on time-out cycle\n");
+ ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos);
+ record_abandoned(&qe);
+ reason = QUEUE_TIMEOUT;
+ res = 0;
+ break;
+ }
+
+ /* leave the queue if no agents, if enabled */
+ if (qe.parent->leavewhenempty && (stat == QUEUE_NO_MEMBERS)) {
+ record_abandoned(&qe);
+ reason = QUEUE_LEAVEEMPTY;
+ ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe.pos, qe.opos, (long)(time(NULL) - qe.start));
+ res = 0;
+ break;
+ }
+
+ /* leave the queue if no reachable agents, if enabled */
+ if ((qe.parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS)) {
+ record_abandoned(&qe);
+ reason = QUEUE_LEAVEUNAVAIL;
+ ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe.pos, qe.opos, (long)(time(NULL) - qe.start));
+ res = 0;
+ break;
+ }
+
+ /* Leave if we have exceeded our queuetimeout */
+ if (qe.expire && (time(NULL) >= qe.expire)) {
+ record_abandoned(&qe);
+ reason = QUEUE_TIMEOUT;
+ res = 0;
+ ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos);
+ break;
+ }
+
+ /* If using dynamic realtime members, we should regenerate the member list for this queue */
+ update_realtime_members(qe.parent);
+
+ /* OK, we didn't get anybody; wait for 'retry' seconds; may get a digit to exit with */
+ res = wait_a_bit(&qe);
+ if (res)
+ goto stop;
+
+ /* Since this is a priority queue and
+ * it is not sure that we are still at the head
+ * of the queue, go and check for our turn again.
+ */
+ if (!is_our_turn(&qe)) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Darn priorities, going back in queue (%s)!\n",
+ qe.chan->name);
+ goto check_turns;
+ }
+ }
+
+stop:
+ if (res) {
+ if (res < 0) {
+ if (!qe.handled) {
+ record_abandoned(&qe);
+ ast_queue_log(args.queuename, chan->uniqueid, "NONE", "ABANDON",
+ "%d|%d|%ld", qe.pos, qe.opos,
+ (long) time(NULL) - qe.start);
+ }
+ res = -1;
+ } else if (qe.valid_digits) {
+ ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHKEY",
+ "%s|%d", qe.digits, qe.pos);
+ }
+ }
+
+ /* Don't allow return code > 0 */
+ if (res >= 0) {
+ res = 0;
+ if (ringing) {
+ ast_indicate(chan, -1);
+ } else {
+ ast_moh_stop(chan);
+ }
+ ast_stopstream(chan);
+ }
+ leave_queue(&qe);
+ if (reason != QUEUE_UNKNOWN)
+ set_queue_result(chan, reason);
+ } else {
+ ast_log(LOG_WARNING, "Unable to join queue '%s'\n", args.queuename);
+ set_queue_result(chan, reason);
+ res = 0;
+ }
+ ast_module_user_remove(lu);
+
+ return res;
+}
+
+static int queue_function_qac(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len)
+{
+ int count = 0;
+ struct call_queue *q;
+ struct ast_module_user *lu;
+ struct member *m;
+ struct ao2_iterator mem_iter;
+
+ buf[0] = '\0';
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_ERROR, "%s requires an argument: queuename\n", cmd);
+ return -1;
+ }
+
+ lu = ast_module_user_add(chan);
+
+ if ((q = load_realtime_queue(data))) {
+ ast_mutex_lock(&q->lock);
+ mem_iter = ao2_iterator_init(q->members, 0);
+ while ((m = ao2_iterator_next(&mem_iter))) {
+ /* Count the agents who are logged in and presently answering calls */
+ if ((m->status != AST_DEVICE_UNAVAILABLE) && (m->status != AST_DEVICE_INVALID)) {
+ count++;
+ }
+ ao2_ref(m, -1);
+ }
+ ast_mutex_unlock(&q->lock);
+ } else
+ ast_log(LOG_WARNING, "queue %s was not found\n", data);
+
+ snprintf(buf, len, "%d", count);
+ ast_module_user_remove(lu);
+
+ return 0;
+}
+
+static int queue_function_queuewaitingcount(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len)
+{
+ int count = 0;
+ struct call_queue *q;
+ struct ast_module_user *lu;
+ struct ast_variable *var = NULL;
+
+ buf[0] = '\0';
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_ERROR, "%s requires an argument: queuename\n", cmd);
+ return -1;
+ }
+
+ lu = ast_module_user_add(chan);
+
+ AST_LIST_LOCK(&queues);
+ AST_LIST_TRAVERSE(&queues, q, list) {
+ if (!strcasecmp(q->name, data)) {
+ ast_mutex_lock(&q->lock);
+ break;
+ }
+ }
+ AST_LIST_UNLOCK(&queues);
+
+ if (q) {
+ count = q->count;
+ ast_mutex_unlock(&q->lock);
+ } else if ((var = ast_load_realtime("queues", "name", data, NULL))) {
+ /* if the queue is realtime but was not found in memory, this
+ * means that the queue had been deleted from memory since it was
+ * "dead." This means it has a 0 waiting count
+ */
+ count = 0;
+ ast_variables_destroy(var);
+ } else
+ ast_log(LOG_WARNING, "queue %s was not found\n", data);
+
+ snprintf(buf, len, "%d", count);
+ ast_module_user_remove(lu);
+ return 0;
+}
+
+static int queue_function_queuememberlist(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len)
+{
+ struct ast_module_user *u;
+ struct call_queue *q;
+ struct member *m;
+
+ /* Ensure an otherwise empty list doesn't return garbage */
+ buf[0] = '\0';
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_ERROR, "QUEUE_MEMBER_LIST requires an argument: queuename\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ AST_LIST_LOCK(&queues);
+ AST_LIST_TRAVERSE(&queues, q, list) {
+ if (!strcasecmp(q->name, data)) {
+ ast_mutex_lock(&q->lock);
+ break;
+ }
+ }
+ AST_LIST_UNLOCK(&queues);
+
+ if (q) {
+ int buflen = 0, count = 0;
+ struct ao2_iterator mem_iter = ao2_iterator_init(q->members, 0);
+
+ while ((m = ao2_iterator_next(&mem_iter))) {
+ /* strcat() is always faster than printf() */
+ if (count++) {
+ strncat(buf + buflen, ",", len - buflen - 1);
+ buflen++;
+ }
+ strncat(buf + buflen, m->membername, len - buflen - 1);
+ buflen += strlen(m->membername);
+ /* Safeguard against overflow (negative length) */
+ if (buflen >= len - 2) {
+ ao2_ref(m, -1);
+ ast_log(LOG_WARNING, "Truncating list\n");
+ break;
+ }
+ ao2_ref(m, -1);
+ }
+ ast_mutex_unlock(&q->lock);
+ } else
+ ast_log(LOG_WARNING, "queue %s was not found\n", data);
+
+ /* We should already be terminated, but let's make sure. */
+ buf[len - 1] = '\0';
+ ast_module_user_remove(u);
+
+ return 0;
+}
+
+static struct ast_custom_function queueagentcount_function = {
+ .name = "QUEUEAGENTCOUNT",
+ .synopsis = "Count number of agents answering a queue",
+ .syntax = "QUEUEAGENTCOUNT(<queuename>)",
+ .desc =
+"Returns the number of members currently associated with the specified queue.\n"
+"This function is deprecated. You should use QUEUE_MEMBER_COUNT() instead.\n",
+ .read = queue_function_qac,
+};
+
+static struct ast_custom_function queuemembercount_function = {
+ .name = "QUEUE_MEMBER_COUNT",
+ .synopsis = "Count number of members answering a queue",
+ .syntax = "QUEUE_MEMBER_COUNT(<queuename>)",
+ .desc =
+"Returns the number of members currently associated with the specified queue.\n",
+ .read = queue_function_qac,
+};
+
+static struct ast_custom_function queuewaitingcount_function = {
+ .name = "QUEUE_WAITING_COUNT",
+ .synopsis = "Count number of calls currently waiting in a queue",
+ .syntax = "QUEUE_WAITING_COUNT(<queuename>)",
+ .desc =
+"Returns the number of callers currently waiting in the specified queue.\n",
+ .read = queue_function_queuewaitingcount,
+};
+
+static struct ast_custom_function queuememberlist_function = {
+ .name = "QUEUE_MEMBER_LIST",
+ .synopsis = "Returns a list of interfaces on a queue",
+ .syntax = "QUEUE_MEMBER_LIST(<queuename>)",
+ .desc =
+"Returns a comma-separated list of members associated with the specified queue.\n",
+ .read = queue_function_queuememberlist,
+};
+
+static int reload_queues(void)
+{
+ struct call_queue *q;
+ struct ast_config *cfg;
+ char *cat, *tmp;
+ struct ast_variable *var;
+ struct member *cur, *newm;
+ struct ao2_iterator mem_iter;
+ int new;
+ const char *general_val = NULL;
+ char parse[80];
+ char *interface;
+ char *membername = NULL;
+ int penalty;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(interface);
+ AST_APP_ARG(penalty);
+ AST_APP_ARG(membername);
+ );
+
+ if (!(cfg = ast_config_load("queues.conf"))) {
+ ast_log(LOG_NOTICE, "No call queueing config file (queues.conf), so no call queues\n");
+ return 0;
+ }
+ AST_LIST_LOCK(&queues);
+ use_weight=0;
+ /* Mark all non-realtime queues as dead for the moment */
+ AST_LIST_TRAVERSE(&queues, q, list) {
+ if (!q->realtime) {
+ q->dead = 1;
+ q->found = 0;
+ }
+ }
+
+ /* Chug through config file */
+ cat = NULL;
+ while ((cat = ast_category_browse(cfg, cat)) ) {
+ if (!strcasecmp(cat, "general")) {
+ /* Initialize global settings */
+ queue_persistent_members = 0;
+ if ((general_val = ast_variable_retrieve(cfg, "general", "persistentmembers")))
+ queue_persistent_members = ast_true(general_val);
+ autofill_default = 0;
+ if ((general_val = ast_variable_retrieve(cfg, "general", "autofill")))
+ autofill_default = ast_true(general_val);
+ montype_default = 0;
+ if ((general_val = ast_variable_retrieve(cfg, "general", "monitor-type")))
+ if (!strcasecmp(general_val, "mixmonitor"))
+ montype_default = 1;
+ } else { /* Define queue */
+ /* Look for an existing one */
+ AST_LIST_TRAVERSE(&queues, q, list) {
+ if (!strcmp(q->name, cat))
+ break;
+ }
+ if (!q) {
+ /* Make one then */
+ if (!(q = alloc_queue(cat))) {
+ /* TODO: Handle memory allocation failure */
+ }
+ new = 1;
+ } else
+ new = 0;
+ if (q) {
+ if (!new)
+ ast_mutex_lock(&q->lock);
+ /* Check if a queue with this name already exists */
+ if (q->found) {
+ ast_log(LOG_WARNING, "Queue '%s' already defined! Skipping!\n", cat);
+ if (!new)
+ ast_mutex_unlock(&q->lock);
+ continue;
+ }
+ /* Re-initialize the queue, and clear statistics */
+ init_queue(q);
+ clear_queue(q);
+ mem_iter = ao2_iterator_init(q->members, 0);
+ while ((cur = ao2_iterator_next(&mem_iter))) {
+ if (!cur->dynamic) {
+ cur->delme = 1;
+ }
+ ao2_ref(cur, -1);
+ }
+ for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
+ if (!strcasecmp(var->name, "member")) {
+ struct member tmpmem;
+ membername = NULL;
+
+ /* Add a new member */
+ ast_copy_string(parse, var->value, sizeof(parse));
+
+ AST_NONSTANDARD_APP_ARGS(args, parse, ',');
+
+ interface = args.interface;
+ if (!ast_strlen_zero(args.penalty)) {
+ tmp = args.penalty;
+ while (*tmp && *tmp < 33) tmp++;
+ penalty = atoi(tmp);
+ if (penalty < 0) {
+ penalty = 0;
+ }
+ } else
+ penalty = 0;
+
+ if (!ast_strlen_zero(args.membername)) {
+ membername = args.membername;
+ while (*membername && *membername < 33) membername++;
+ }
+
+ /* Find the old position in the list */
+ ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface));
+ cur = ao2_find(q->members, &tmpmem, OBJ_POINTER | OBJ_UNLINK);
+
+ newm = create_queue_member(interface, membername, penalty, cur ? cur->paused : 0);
+ ao2_link(q->members, newm);
+ ao2_ref(newm, -1);
+ newm = NULL;
+
+ if (cur)
+ ao2_ref(cur, -1);
+ else {
+ /* Add them to the master int list if necessary */
+ add_to_interfaces(interface);
+ q->membercount++;
+ }
+ } else {
+ queue_set_param(q, var->name, var->value, var->lineno, 1);
+ }
+ }
+
+ /* Free remaining members marked as delme */
+ mem_iter = ao2_iterator_init(q->members, 0);
+ while ((cur = ao2_iterator_next(&mem_iter))) {
+ if (! cur->delme) {
+ ao2_ref(cur, -1);
+ continue;
+ }
+
+ q->membercount--;
+ ao2_unlink(q->members, cur);
+ remove_from_interfaces(cur->interface);
+ ao2_ref(cur, -1);
+ }
+
+ if (q->strategy == QUEUE_STRATEGY_ROUNDROBIN)
+ rr_dep_warning();
+
+ if (new) {
+ AST_LIST_INSERT_HEAD(&queues, q, list);
+ } else
+ ast_mutex_unlock(&q->lock);
+ }
+ }
+ }
+ ast_config_destroy(cfg);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&queues, q, list) {
+ if (q->dead) {
+ AST_LIST_REMOVE_CURRENT(&queues, list);
+ if (!q->count)
+ destroy_queue(q);
+ else
+ ast_log(LOG_DEBUG, "XXX Leaking a little memory :( XXX\n");
+ } else {
+ ast_mutex_lock(&q->lock);
+ mem_iter = ao2_iterator_init(q->members, 0);
+ while ((cur = ao2_iterator_next(&mem_iter))) {
+ if (cur->dynamic)
+ q->membercount++;
+ cur->status = ast_device_state(cur->interface);
+ ao2_ref(cur, -1);
+ }
+ ast_mutex_unlock(&q->lock);
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+ AST_LIST_UNLOCK(&queues);
+ return 1;
+}
+
+static int __queues_show(struct mansession *s, int manager, int fd, int argc, char **argv)
+{
+ struct call_queue *q;
+ struct queue_ent *qe;
+ struct member *mem;
+ int pos, queue_show;
+ time_t now;
+ char max_buf[150];
+ char *max;
+ size_t max_left;
+ float sl = 0;
+ char *term = manager ? "\r\n" : "\n";
+ struct ao2_iterator mem_iter;
+
+ time(&now);
+ if (argc == 2)
+ queue_show = 0;
+ else if (argc == 3)
+ queue_show = 1;
+ else
+ return RESULT_SHOWUSAGE;
+
+ /* We only want to load realtime queues when a specific queue is asked for. */
+ if (queue_show) {
+ load_realtime_queue(argv[2]);
+ } else if (ast_check_realtime("queues")) {
+ struct ast_config *cfg = ast_load_realtime_multientry("queues", "name LIKE", "%", (char *) NULL);
+ char *queuename;
+ if (cfg) {
+ for (queuename = ast_category_browse(cfg, NULL); !ast_strlen_zero(queuename); queuename = ast_category_browse(cfg, queuename)) {
+ load_realtime_queue(queuename);
+ }
+ ast_config_destroy(cfg);
+ }
+ }
+
+ AST_LIST_LOCK(&queues);
+ if (AST_LIST_EMPTY(&queues)) {
+ AST_LIST_UNLOCK(&queues);
+ if (queue_show) {
+ if (s)
+ astman_append(s, "No such queue: %s.%s",argv[2], term);
+ else
+ ast_cli(fd, "No such queue: %s.%s",argv[2], term);
+ } else {
+ if (s)
+ astman_append(s, "No queues.%s", term);
+ else
+ ast_cli(fd, "No queues.%s", term);
+ }
+ return RESULT_SUCCESS;
+ }
+ AST_LIST_TRAVERSE(&queues, q, list) {
+ ast_mutex_lock(&q->lock);
+ if (queue_show) {
+ if (strcasecmp(q->name, argv[2]) != 0) {
+ ast_mutex_unlock(&q->lock);
+ if (!AST_LIST_NEXT(q, list)) {
+ ast_cli(fd, "No such queue: %s.%s",argv[2], term);
+ break;
+ }
+ continue;
+ }
+ }
+ max_buf[0] = '\0';
+ max = max_buf;
+ max_left = sizeof(max_buf);
+ if (q->maxlen)
+ ast_build_string(&max, &max_left, "%d", q->maxlen);
+ else
+ ast_build_string(&max, &max_left, "unlimited");
+ sl = 0;
+ if (q->callscompleted > 0)
+ sl = 100 * ((float) q->callscompletedinsl / (float) q->callscompleted);
+ if (s)
+ astman_append(s, "%-12.12s has %d calls (max %s) in '%s' strategy (%ds holdtime), W:%d, C:%d, A:%d, SL:%2.1f%% within %ds%s",
+ q->name, q->count, max_buf, int2strat(q->strategy), q->holdtime, q->weight,
+ q->callscompleted, q->callsabandoned,sl,q->servicelevel, term);
+ else
+ ast_cli(fd, "%-12.12s has %d calls (max %s) in '%s' strategy (%ds holdtime), W:%d, C:%d, A:%d, SL:%2.1f%% within %ds%s",
+ q->name, q->count, max_buf, int2strat(q->strategy), q->holdtime, q->weight, q->callscompleted, q->callsabandoned,sl,q->servicelevel, term);
+ if (ao2_container_count(q->members)) {
+ if (s)
+ astman_append(s, " Members: %s", term);
+ else
+ ast_cli(fd, " Members: %s", term);
+ mem_iter = ao2_iterator_init(q->members, 0);
+ while ((mem = ao2_iterator_next(&mem_iter))) {
+ max_buf[0] = '\0';
+ max = max_buf;
+ max_left = sizeof(max_buf);
+ if (strcasecmp(mem->membername, mem->interface)) {
+ ast_build_string(&max, &max_left, " (%s)", mem->interface);
+ }
+ if (mem->penalty)
+ ast_build_string(&max, &max_left, " with penalty %d", mem->penalty);
+ if (mem->dynamic)
+ ast_build_string(&max, &max_left, " (dynamic)");
+ if (mem->realtime)
+ ast_build_string(&max, &max_left, " (realtime)");
+ if (mem->paused)
+ ast_build_string(&max, &max_left, " (paused)");
+ ast_build_string(&max, &max_left, " (%s)", devstate2str(mem->status));
+ if (mem->calls) {
+ ast_build_string(&max, &max_left, " has taken %d calls (last was %ld secs ago)",
+ mem->calls, (long) (time(NULL) - mem->lastcall));
+ } else
+ ast_build_string(&max, &max_left, " has taken no calls yet");
+ if (s)
+ astman_append(s, " %s%s%s", mem->membername, max_buf, term);
+ else
+ ast_cli(fd, " %s%s%s", mem->membername, max_buf, term);
+ ao2_ref(mem, -1);
+ }
+ } else if (s)
+ astman_append(s, " No Members%s", term);
+ else
+ ast_cli(fd, " No Members%s", term);
+ if (q->head) {
+ pos = 1;
+ if (s)
+ astman_append(s, " Callers: %s", term);
+ else
+ ast_cli(fd, " Callers: %s", term);
+ for (qe = q->head; qe; qe = qe->next) {
+ if (s)
+ astman_append(s, " %d. %s (wait: %ld:%2.2ld, prio: %d)%s",
+ pos++, qe->chan->name, (long) (now - qe->start) / 60,
+ (long) (now - qe->start) % 60, qe->prio, term);
+ else
+ ast_cli(fd, " %d. %s (wait: %ld:%2.2ld, prio: %d)%s", pos++,
+ qe->chan->name, (long) (now - qe->start) / 60,
+ (long) (now - qe->start) % 60, qe->prio, term);
+ }
+ } else if (s)
+ astman_append(s, " No Callers%s", term);
+ else
+ ast_cli(fd, " No Callers%s", term);
+ if (s)
+ astman_append(s, "%s", term);
+ else
+ ast_cli(fd, "%s", term);
+ ast_mutex_unlock(&q->lock);
+ if (queue_show)
+ break;
+ }
+ AST_LIST_UNLOCK(&queues);
+ return RESULT_SUCCESS;
+}
+
+static int queue_show(int fd, int argc, char **argv)
+{
+ return __queues_show(NULL, 0, fd, argc, argv);
+}
+
+static char *complete_queue(const char *line, const char *word, int pos, int state)
+{
+ struct call_queue *q;
+ char *ret = NULL;
+ int which = 0;
+ int wordlen = strlen(word);
+
+ AST_LIST_LOCK(&queues);
+ AST_LIST_TRAVERSE(&queues, q, list) {
+ if (!strncasecmp(word, q->name, wordlen) && ++which > state) {
+ ret = ast_strdup(q->name);
+ break;
+ }
+ }
+ AST_LIST_UNLOCK(&queues);
+
+ return ret;
+}
+
+static char *complete_queue_show(const char *line, const char *word, int pos, int state)
+{
+ if (pos == 2)
+ return complete_queue(line, word, pos, state);
+ return NULL;
+}
+
+/*!\brief callback to display queues status in manager
+ \addtogroup Group_AMI
+ */
+static int manager_queues_show(struct mansession *s, const struct message *m)
+{
+ char *a[] = { "queue", "show" };
+
+ __queues_show(s, 1, -1, 2, a);
+ astman_append(s, "\r\n\r\n"); /* Properly terminate Manager output */
+
+ return RESULT_SUCCESS;
+}
+
+/* Dump queue status */
+static int manager_queues_status(struct mansession *s, const struct message *m)
+{
+ time_t now;
+ int pos;
+ const char *id = astman_get_header(m,"ActionID");
+ const char *queuefilter = astman_get_header(m,"Queue");
+ const char *memberfilter = astman_get_header(m,"Member");
+ char idText[256] = "";
+ struct call_queue *q;
+ struct queue_ent *qe;
+ float sl = 0;
+ struct member *mem;
+ struct ao2_iterator mem_iter;
+
+ astman_send_ack(s, m, "Queue status will follow");
+ time(&now);
+ AST_LIST_LOCK(&queues);
+ if (!ast_strlen_zero(id))
+ snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
+
+ AST_LIST_TRAVERSE(&queues, q, list) {
+ ast_mutex_lock(&q->lock);
+
+ /* List queue properties */
+ if (ast_strlen_zero(queuefilter) || !strcmp(q->name, queuefilter)) {
+ sl = ((q->callscompleted > 0) ? 100 * ((float)q->callscompletedinsl / (float)q->callscompleted) : 0);
+ astman_append(s, "Event: QueueParams\r\n"
+ "Queue: %s\r\n"
+ "Max: %d\r\n"
+ "Calls: %d\r\n"
+ "Holdtime: %d\r\n"
+ "Completed: %d\r\n"
+ "Abandoned: %d\r\n"
+ "ServiceLevel: %d\r\n"
+ "ServicelevelPerf: %2.1f\r\n"
+ "Weight: %d\r\n"
+ "%s"
+ "\r\n",
+ q->name, q->maxlen, q->count, q->holdtime, q->callscompleted,
+ q->callsabandoned, q->servicelevel, sl, q->weight, idText);
+ /* List Queue Members */
+ mem_iter = ao2_iterator_init(q->members, 0);
+ while ((mem = ao2_iterator_next(&mem_iter))) {
+ if (ast_strlen_zero(memberfilter) || !strcmp(mem->interface, memberfilter)) {
+ astman_append(s, "Event: QueueMember\r\n"
+ "Queue: %s\r\n"
+ "Name: %s\r\n"
+ "Location: %s\r\n"
+ "Membership: %s\r\n"
+ "Penalty: %d\r\n"
+ "CallsTaken: %d\r\n"
+ "LastCall: %d\r\n"
+ "Status: %d\r\n"
+ "Paused: %d\r\n"
+ "%s"
+ "\r\n",
+ q->name, mem->membername, mem->interface, mem->dynamic ? "dynamic" : "static",
+ mem->penalty, mem->calls, (int)mem->lastcall, mem->status, mem->paused, idText);
+ }
+ ao2_ref(mem, -1);
+ }
+ /* List Queue Entries */
+ pos = 1;
+ for (qe = q->head; qe; qe = qe->next) {
+ astman_append(s, "Event: QueueEntry\r\n"
+ "Queue: %s\r\n"
+ "Position: %d\r\n"
+ "Channel: %s\r\n"
+ "CallerID: %s\r\n"
+ "CallerIDName: %s\r\n"
+ "Wait: %ld\r\n"
+ "%s"
+ "\r\n",
+ q->name, pos++, qe->chan->name,
+ S_OR(qe->chan->cid.cid_num, "unknown"),
+ S_OR(qe->chan->cid.cid_name, "unknown"),
+ (long) (now - qe->start), idText);
+ }
+ }
+ ast_mutex_unlock(&q->lock);
+ }
+
+ astman_append(s,
+ "Event: QueueStatusComplete\r\n"
+ "%s"
+ "\r\n",idText);
+
+ AST_LIST_UNLOCK(&queues);
+
+
+ return RESULT_SUCCESS;
+}
+
+static int manager_add_queue_member(struct mansession *s, const struct message *m)
+{
+ const char *queuename, *interface, *penalty_s, *paused_s, *membername;
+ int paused, penalty = 0;
+
+ queuename = astman_get_header(m, "Queue");
+ interface = astman_get_header(m, "Interface");
+ penalty_s = astman_get_header(m, "Penalty");
+ paused_s = astman_get_header(m, "Paused");
+ membername = astman_get_header(m, "MemberName");
+
+ if (ast_strlen_zero(queuename)) {
+ astman_send_error(s, m, "'Queue' not specified.");
+ return 0;
+ }
+
+ if (ast_strlen_zero(interface)) {
+ astman_send_error(s, m, "'Interface' not specified.");
+ return 0;
+ }
+
+ if (ast_strlen_zero(penalty_s))
+ penalty = 0;
+ else if (sscanf(penalty_s, "%d", &penalty) != 1 || penalty < 0)
+ penalty = 0;
+
+ if (ast_strlen_zero(paused_s))
+ paused = 0;
+ else
+ paused = abs(ast_true(paused_s));
+
+ switch (add_to_queue(queuename, interface, membername, penalty, paused, queue_persistent_members)) {
+ case RES_OKAY:
+ ast_queue_log(queuename, "MANAGER", interface, "ADDMEMBER", "%s", "");
+ astman_send_ack(s, m, "Added interface to queue");
+ break;
+ case RES_EXISTS:
+ astman_send_error(s, m, "Unable to add interface: Already there");
+ break;
+ case RES_NOSUCHQUEUE:
+ astman_send_error(s, m, "Unable to add interface to queue: No such queue");
+ break;
+ case RES_OUTOFMEMORY:
+ astman_send_error(s, m, "Out of memory");
+ break;
+ }
+
+ return 0;
+}
+
+static int manager_remove_queue_member(struct mansession *s, const struct message *m)
+{
+ const char *queuename, *interface;
+
+ queuename = astman_get_header(m, "Queue");
+ interface = astman_get_header(m, "Interface");
+
+ if (ast_strlen_zero(queuename) || ast_strlen_zero(interface)) {
+ astman_send_error(s, m, "Need 'Queue' and 'Interface' parameters.");
+ return 0;
+ }
+
+ switch (remove_from_queue(queuename, interface)) {
+ case RES_OKAY:
+ ast_queue_log(queuename, "MANAGER", interface, "REMOVEMEMBER", "%s", "");
+ astman_send_ack(s, m, "Removed interface from queue");
+ break;
+ case RES_EXISTS:
+ astman_send_error(s, m, "Unable to remove interface: Not there");
+ break;
+ case RES_NOSUCHQUEUE:
+ astman_send_error(s, m, "Unable to remove interface from queue: No such queue");
+ break;
+ case RES_OUTOFMEMORY:
+ astman_send_error(s, m, "Out of memory");
+ break;
+ case RES_NOT_DYNAMIC:
+ astman_send_error(s, m, "Member not dynamic");
+ break;
+ }
+
+ return 0;
+}
+
+static int manager_pause_queue_member(struct mansession *s, const struct message *m)
+{
+ const char *queuename, *interface, *paused_s;
+ int paused;
+
+ interface = astman_get_header(m, "Interface");
+ paused_s = astman_get_header(m, "Paused");
+ queuename = astman_get_header(m, "Queue"); /* Optional - if not supplied, pause the given Interface in all queues */
+
+ if (ast_strlen_zero(interface) || ast_strlen_zero(paused_s)) {
+ astman_send_error(s, m, "Need 'Interface' and 'Paused' parameters.");
+ return 0;
+ }
+
+ paused = abs(ast_true(paused_s));
+
+ if (set_member_paused(queuename, interface, paused))
+ astman_send_error(s, m, "Interface not found");
+ else
+ astman_send_ack(s, m, paused ? "Interface paused successfully" : "Interface unpaused successfully");
+ return 0;
+}
+
+static int handle_queue_add_member(int fd, int argc, char *argv[])
+{
+ char *queuename, *interface, *membername = NULL;
+ int penalty;
+
+ if ((argc != 6) && (argc != 8) && (argc != 10)) {
+ return RESULT_SHOWUSAGE;
+ } else if (strcmp(argv[4], "to")) {
+ return RESULT_SHOWUSAGE;
+ } else if ((argc == 8) && strcmp(argv[6], "penalty")) {
+ return RESULT_SHOWUSAGE;
+ } else if ((argc == 10) && strcmp(argv[8], "as")) {
+ return RESULT_SHOWUSAGE;
+ }
+
+ queuename = argv[5];
+ interface = argv[3];
+ if (argc >= 8) {
+ if (sscanf(argv[7], "%d", &penalty) == 1) {
+ if (penalty < 0) {
+ ast_cli(fd, "Penalty must be >= 0\n");
+ penalty = 0;
+ }
+ } else {
+ ast_cli(fd, "Penalty must be an integer >= 0\n");
+ penalty = 0;
+ }
+ } else {
+ penalty = 0;
+ }
+
+ if (argc >= 10) {
+ membername = argv[9];
+ }
+
+ switch (add_to_queue(queuename, interface, membername, penalty, 0, queue_persistent_members)) {
+ case RES_OKAY:
+ ast_queue_log(queuename, "CLI", interface, "ADDMEMBER", "%s", "");
+ ast_cli(fd, "Added interface '%s' to queue '%s'\n", interface, queuename);
+ return RESULT_SUCCESS;
+ case RES_EXISTS:
+ ast_cli(fd, "Unable to add interface '%s' to queue '%s': Already there\n", interface, queuename);
+ return RESULT_FAILURE;
+ case RES_NOSUCHQUEUE:
+ ast_cli(fd, "Unable to add interface to queue '%s': No such queue\n", queuename);
+ return RESULT_FAILURE;
+ case RES_OUTOFMEMORY:
+ ast_cli(fd, "Out of memory\n");
+ return RESULT_FAILURE;
+ default:
+ return RESULT_FAILURE;
+ }
+}
+
+static char *complete_queue_add_member(const char *line, const char *word, int pos, int state)
+{
+ /* 0 - queue; 1 - add; 2 - member; 3 - <interface>; 4 - to; 5 - <queue>; 6 - penalty; 7 - <penalty>; 8 - as; 9 - <membername> */
+ switch (pos) {
+ case 3: /* Don't attempt to complete name of interface (infinite possibilities) */
+ return NULL;
+ case 4: /* only one possible match, "to" */
+ return state == 0 ? ast_strdup("to") : NULL;
+ case 5: /* <queue> */
+ return complete_queue(line, word, pos, state);
+ case 6: /* only one possible match, "penalty" */
+ return state == 0 ? ast_strdup("penalty") : NULL;
+ case 7:
+ if (state < 100) { /* 0-99 */
+ char *num;
+ if ((num = ast_malloc(3))) {
+ sprintf(num, "%d", state);
+ }
+ return num;
+ } else {
+ return NULL;
+ }
+ case 8: /* only one possible match, "as" */
+ return state == 0 ? ast_strdup("as") : NULL;
+ case 9: /* Don't attempt to complete name of member (infinite possibilities) */
+ return NULL;
+ default:
+ return NULL;
+ }
+}
+
+static int handle_queue_remove_member(int fd, int argc, char *argv[])
+{
+ char *queuename, *interface;
+
+ if (argc != 6) {
+ return RESULT_SHOWUSAGE;
+ } else if (strcmp(argv[4], "from")) {
+ return RESULT_SHOWUSAGE;
+ }
+
+ queuename = argv[5];
+ interface = argv[3];
+
+ switch (remove_from_queue(queuename, interface)) {
+ case RES_OKAY:
+ ast_queue_log(queuename, "CLI", interface, "REMOVEMEMBER", "%s", "");
+ ast_cli(fd, "Removed interface '%s' from queue '%s'\n", interface, queuename);
+ return RESULT_SUCCESS;
+ case RES_EXISTS:
+ ast_cli(fd, "Unable to remove interface '%s' from queue '%s': Not there\n", interface, queuename);
+ return RESULT_FAILURE;
+ case RES_NOSUCHQUEUE:
+ ast_cli(fd, "Unable to remove interface from queue '%s': No such queue\n", queuename);
+ return RESULT_FAILURE;
+ case RES_OUTOFMEMORY:
+ ast_cli(fd, "Out of memory\n");
+ return RESULT_FAILURE;
+ case RES_NOT_DYNAMIC:
+ ast_cli(fd, "Member not dynamic\n");
+ return RESULT_FAILURE;
+ default:
+ return RESULT_FAILURE;
+ }
+}
+
+static char *complete_queue_remove_member(const char *line, const char *word, int pos, int state)
+{
+ int which = 0;
+ struct call_queue *q;
+ struct member *m;
+ struct ao2_iterator mem_iter;
+
+ /* 0 - queue; 1 - remove; 2 - member; 3 - <member>; 4 - from; 5 - <queue> */
+ if (pos > 5 || pos < 3)
+ return NULL;
+ if (pos == 4) /* only one possible match, 'from' */
+ return state == 0 ? ast_strdup("from") : NULL;
+
+ if (pos == 5) /* No need to duplicate code */
+ return complete_queue(line, word, pos, state);
+
+ /* here is the case for 3, <member> */
+ if (!AST_LIST_EMPTY(&queues)) { /* XXX unnecessary ? the traverse does that for us */
+ AST_LIST_TRAVERSE(&queues, q, list) {
+ ast_mutex_lock(&q->lock);
+ mem_iter = ao2_iterator_init(q->members, 0);
+ while ((m = ao2_iterator_next(&mem_iter))) {
+ if (++which > state) {
+ char *tmp;
+ ast_mutex_unlock(&q->lock);
+ tmp = ast_strdup(m->interface);
+ ao2_ref(m, -1);
+ return tmp;
+ }
+ ao2_ref(m, -1);
+ }
+ ast_mutex_unlock(&q->lock);
+ }
+ }
+
+ return NULL;
+}
+
+static char queue_show_usage[] =
+"Usage: queue show\n"
+" Provides summary information on a specified queue.\n";
+
+static char qam_cmd_usage[] =
+"Usage: queue add member <channel> to <queue> [penalty <penalty>]\n";
+
+static char qrm_cmd_usage[] =
+"Usage: queue remove member <channel> from <queue>\n";
+
+static struct ast_cli_entry cli_show_queue_deprecated = {
+ { "show", "queue", NULL },
+ queue_show, NULL,
+ NULL, complete_queue_show };
+
+static struct ast_cli_entry cli_add_queue_member_deprecated = {
+ { "add", "queue", "member", NULL },
+ handle_queue_add_member, NULL,
+ NULL, complete_queue_add_member };
+
+static struct ast_cli_entry cli_remove_queue_member_deprecated = {
+ { "remove", "queue", "member", NULL },
+ handle_queue_remove_member, NULL,
+ NULL, complete_queue_remove_member };
+
+static struct ast_cli_entry cli_queue[] = {
+ /* Deprecated */
+ { { "show", "queues", NULL },
+ queue_show, NULL,
+ NULL, NULL },
+
+ { { "queue", "show", NULL },
+ queue_show, "Show status of a specified queue",
+ queue_show_usage, complete_queue_show, &cli_show_queue_deprecated },
+
+ { { "queue", "add", "member", NULL },
+ handle_queue_add_member, "Add a channel to a specified queue",
+ qam_cmd_usage, complete_queue_add_member, &cli_add_queue_member_deprecated },
+
+ { { "queue", "remove", "member", NULL },
+ handle_queue_remove_member, "Removes a channel from a specified queue",
+ qrm_cmd_usage, complete_queue_remove_member, &cli_remove_queue_member_deprecated },
+};
+
+static int unload_module(void)
+{
+ int res;
+
+ if (device_state.thread != AST_PTHREADT_NULL) {
+ device_state.stop = 1;
+ ast_mutex_lock(&device_state.lock);
+ ast_cond_signal(&device_state.cond);
+ ast_mutex_unlock(&device_state.lock);
+ pthread_join(device_state.thread, NULL);
+ }
+
+ ast_cli_unregister_multiple(cli_queue, sizeof(cli_queue) / sizeof(struct ast_cli_entry));
+ res = ast_manager_unregister("QueueStatus");
+ res |= ast_manager_unregister("Queues");
+ res |= ast_manager_unregister("QueueAdd");
+ res |= ast_manager_unregister("QueueRemove");
+ res |= ast_manager_unregister("QueuePause");
+ res |= ast_unregister_application(app_aqm);
+ res |= ast_unregister_application(app_rqm);
+ res |= ast_unregister_application(app_pqm);
+ res |= ast_unregister_application(app_upqm);
+ res |= ast_unregister_application(app_ql);
+ res |= ast_unregister_application(app);
+ res |= ast_custom_function_unregister(&queueagentcount_function);
+ res |= ast_custom_function_unregister(&queuemembercount_function);
+ res |= ast_custom_function_unregister(&queuememberlist_function);
+ res |= ast_custom_function_unregister(&queuewaitingcount_function);
+ ast_devstate_del(statechange_queue, NULL);
+
+ ast_module_user_hangup_all();
+
+ clear_and_free_interfaces();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ int res;
+
+ if (!reload_queues())
+ return AST_MODULE_LOAD_DECLINE;
+
+ if (queue_persistent_members)
+ reload_queue_members();
+
+ ast_mutex_init(&device_state.lock);
+ ast_cond_init(&device_state.cond, NULL);
+ ast_pthread_create(&device_state.thread, NULL, device_state_thread, NULL);
+
+ ast_cli_register_multiple(cli_queue, sizeof(cli_queue) / sizeof(struct ast_cli_entry));
+ res = ast_register_application(app, queue_exec, synopsis, descrip);
+ res |= ast_register_application(app_aqm, aqm_exec, app_aqm_synopsis, app_aqm_descrip);
+ res |= ast_register_application(app_rqm, rqm_exec, app_rqm_synopsis, app_rqm_descrip);
+ res |= ast_register_application(app_pqm, pqm_exec, app_pqm_synopsis, app_pqm_descrip);
+ res |= ast_register_application(app_upqm, upqm_exec, app_upqm_synopsis, app_upqm_descrip);
+ res |= ast_register_application(app_ql, ql_exec, app_ql_synopsis, app_ql_descrip);
+ res |= ast_manager_register("Queues", 0, manager_queues_show, "Queues");
+ res |= ast_manager_register("QueueStatus", 0, manager_queues_status, "Queue Status");
+ res |= ast_manager_register("QueueAdd", EVENT_FLAG_AGENT, manager_add_queue_member, "Add interface to queue.");
+ res |= ast_manager_register("QueueRemove", EVENT_FLAG_AGENT, manager_remove_queue_member, "Remove interface from queue.");
+ res |= ast_manager_register("QueuePause", EVENT_FLAG_AGENT, manager_pause_queue_member, "Makes a queue member temporarily unavailable");
+ res |= ast_custom_function_register(&queueagentcount_function);
+ res |= ast_custom_function_register(&queuemembercount_function);
+ res |= ast_custom_function_register(&queuememberlist_function);
+ res |= ast_custom_function_register(&queuewaitingcount_function);
+ res |= ast_devstate_add(statechange_queue, NULL);
+
+ return res;
+}
+
+static int reload(void)
+{
+ reload_queues();
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "True Call Queueing",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
+
diff --git a/apps/app_random.c b/apps/app_random.c
new file mode 100644
index 000000000..8484f656d
--- /dev/null
+++ b/apps/app_random.c
@@ -0,0 +1,108 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (c) 2003 - 2005 Tilghman Lesher. All rights reserved.
+ *
+ * Tilghman Lesher <asterisk__app_random__200508@the-tilghman.com>
+ *
+ * This code is released by the author with no restrictions on usage or distribution.
+ *
+ * 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.
+ *
+ */
+
+/*! \file
+ *
+ * \brief Random application
+ *
+ * \author Tilghman Lesher <asterisk__app_random__200508@the-tilghman.com>
+ * \ingroup applications
+ */
+
+#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/options.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+
+/*! \todo The Random() app should be removed from trunk following the release of 1.4 */
+
+static char *app_random = "Random";
+
+static char *random_synopsis = "Conditionally branches, based upon a probability";
+
+static char *random_descrip =
+"Random([probability]:[[context|]extension|]priority)\n"
+" probability := INTEGER in the range 1 to 100\n"
+"DEPRECATED: Use GotoIf($[${RAND(1,100)} > <number>]?<label>)\n";
+
+
+static int random_exec(struct ast_channel *chan, void *data)
+{
+ int res=0;
+ struct ast_module_user *u;
+
+ char *s;
+ char *prob;
+ int probint;
+ static int deprecated = 0;
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "Random requires an argument ([probability]:[[context|]extension|]priority)\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ s = ast_strdupa(data);
+
+ prob = strsep(&s,":");
+ if ((!prob) || (sscanf(prob, "%d", &probint) != 1))
+ probint = 0;
+
+ if (!deprecated) {
+ deprecated = 1;
+ ast_log(LOG_WARNING, "Random is deprecated in Asterisk 1.4. Replace with GotoIf($[${RAND(0,99)} + %d >= 100]?%s)\n", probint, s);
+ }
+
+ if ((ast_random() % 100) + probint >= 100) {
+ res = ast_parseable_goto(chan, s);
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "Random branches to (%s,%s,%d)\n",
+ chan->context,chan->exten, chan->priority+1);
+ }
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app_random);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app_random, random_exec, random_synopsis, random_descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Random goto");
diff --git a/apps/app_read.c b/apps/app_read.c
new file mode 100644
index 000000000..bb3dd669b
--- /dev/null
+++ b/apps/app_read.c
@@ -0,0 +1,234 @@
+/*
+ * 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 Trivial application to read a variable
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/app.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/options.h"
+#include "asterisk/utils.h"
+#include "asterisk/indications.h"
+
+enum {
+ OPT_SKIP = (1 << 0),
+ OPT_INDICATION = (1 << 1),
+ OPT_NOANSWER = (1 << 2),
+} read_option_flags;
+
+AST_APP_OPTIONS(read_app_options, {
+ AST_APP_OPTION('s', OPT_SKIP),
+ AST_APP_OPTION('i', OPT_INDICATION),
+ AST_APP_OPTION('n', OPT_NOANSWER),
+});
+
+static char *app = "Read";
+
+static char *synopsis = "Read a variable";
+
+static char *descrip =
+" Read(variable[|filename][|maxdigits][|option][|attempts][|timeout])\n\n"
+"Reads a #-terminated string of digits a certain number of times from the\n"
+"user in to the given variable.\n"
+" filename -- file to play before reading digits or tone with option i\n"
+" maxdigits -- maximum acceptable number of digits. Stops reading after\n"
+" maxdigits have been entered (without requiring the user to\n"
+" press the '#' key).\n"
+" Defaults to 0 - no limit - wait for the user press the '#' key.\n"
+" Any value below 0 means the same. Max accepted value is 255.\n"
+" option -- options are 's' , 'i', 'n'\n"
+" 's' to return immediately if the line is not up,\n"
+" 'i' to play filename as an indication tone from your indications.conf\n"
+" 'n' to read digits even if the line is not up.\n"
+" attempts -- if greater than 1, that many attempts will be made in the \n"
+" event no data is entered.\n"
+" timeout -- An integer number of seconds to wait for a digit response. If greater\n"
+" than 0, that value will override the default timeout.\n\n"
+"Read should disconnect if the function fails or errors out.\n";
+
+
+#define ast_next_data(instr,ptr,delim) if((ptr=strchr(instr,delim))) { *(ptr) = '\0' ; ptr++;}
+
+static int read_exec(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ struct ast_module_user *u;
+ char tmp[256] = "";
+ int maxdigits = 255;
+ int tries = 1, to = 0, x = 0;
+ char *argcopy = NULL;
+ struct tone_zone_sound *ts;
+ struct ast_flags flags = {0};
+
+ AST_DECLARE_APP_ARGS(arglist,
+ AST_APP_ARG(variable);
+ AST_APP_ARG(filename);
+ AST_APP_ARG(maxdigits);
+ AST_APP_ARG(options);
+ AST_APP_ARG(attempts);
+ AST_APP_ARG(timeout);
+ );
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "Read requires an argument (variable)\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ argcopy = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(arglist, argcopy);
+
+ if (!ast_strlen_zero(arglist.options)) {
+ ast_app_parse_options(read_app_options, &flags, NULL, arglist.options);
+ }
+
+ if (!ast_strlen_zero(arglist.attempts)) {
+ tries = atoi(arglist.attempts);
+ if (tries <= 0)
+ tries = 1;
+ }
+
+ if (!ast_strlen_zero(arglist.timeout)) {
+ to = atoi(arglist.timeout);
+ if (to <= 0)
+ to = 0;
+ else
+ to *= 1000;
+ }
+
+ if (ast_strlen_zero(arglist.filename)) {
+ arglist.filename = NULL;
+ }
+ if (!ast_strlen_zero(arglist.maxdigits)) {
+ maxdigits = atoi(arglist.maxdigits);
+ if ((maxdigits<1) || (maxdigits>255)) {
+ maxdigits = 255;
+ } else if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Accepting a maximum of %d digits.\n", maxdigits);
+ }
+ if (ast_strlen_zero(arglist.variable)) {
+ ast_log(LOG_WARNING, "Invalid! Usage: Read(variable[|filename][|maxdigits][|option][|attempts][|timeout])\n\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+ ts=NULL;
+ if (ast_test_flag(&flags,OPT_INDICATION)) {
+ if (!ast_strlen_zero(arglist.filename)) {
+ ts = ast_get_indication_tone(chan->zone,arglist.filename);
+ }
+ }
+ if (chan->_state != AST_STATE_UP) {
+ if (ast_test_flag(&flags,OPT_SKIP)) {
+ /* At the user's option, skip if the line is not up */
+ pbx_builtin_setvar_helper(chan, arglist.variable, "\0");
+ ast_module_user_remove(u);
+ return 0;
+ } else if (!ast_test_flag(&flags,OPT_NOANSWER)) {
+ /* Otherwise answer unless we're supposed to read while on-hook */
+ res = ast_answer(chan);
+ }
+ }
+ if (!res) {
+ while (tries && !res) {
+ ast_stopstream(chan);
+ if (ts && ts->data[0]) {
+ if (!to)
+ to = chan->pbx ? chan->pbx->rtimeout * 1000 : 6000;
+ res = ast_playtones_start(chan, 0, ts->data, 0);
+ for (x = 0; x < maxdigits; ) {
+ res = ast_waitfordigit(chan, to);
+ ast_playtones_stop(chan);
+ if (res < 1) {
+ tmp[x]='\0';
+ break;
+ }
+ tmp[x++] = res;
+ if (tmp[x-1] == '#') {
+ tmp[x-1] = '\0';
+ break;
+ }
+ }
+ } else {
+ res = ast_app_getdata(chan, arglist.filename, tmp, maxdigits, to);
+ }
+ if (res > -1) {
+ pbx_builtin_setvar_helper(chan, arglist.variable, tmp);
+ if (!ast_strlen_zero(tmp)) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "User entered '%s'\n", tmp);
+ tries = 0;
+ } else {
+ tries--;
+ if (option_verbose > 2) {
+ if (tries)
+ ast_verbose(VERBOSE_PREFIX_3 "User entered nothing, %d chance%s left\n", tries, (tries != 1) ? "s" : "");
+ else
+ ast_verbose(VERBOSE_PREFIX_3 "User entered nothing.\n");
+ }
+ }
+ res = 0;
+ } else {
+ pbx_builtin_setvar_helper(chan, arglist.variable, tmp);
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "User disconnected\n");
+ }
+ }
+ }
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, read_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Read Variable Application");
diff --git a/apps/app_readfile.c b/apps/app_readfile.c
new file mode 100644
index 000000000..7e43a3806
--- /dev/null
+++ b/apps/app_readfile.c
@@ -0,0 +1,120 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, 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 ReadFile application -- Reads in a File for you.
+ *
+ * \author Matt O'Gorman <mogorman@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#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/options.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/app.h"
+#include "asterisk/module.h"
+
+static char *app_readfile = "ReadFile";
+
+static char *readfile_synopsis = "ReadFile(varname=file,length)";
+
+static char *readfile_descrip =
+"ReadFile(varname=file,length)\n"
+" Varname - Result stored here.\n"
+" File - The name of the file to read.\n"
+" Length - Maximum number of characters to capture.\n";
+
+
+static int readfile_exec(struct ast_channel *chan, void *data)
+{
+ int res=0;
+ struct ast_module_user *u;
+ char *s, *varname=NULL, *file=NULL, *length=NULL, *returnvar=NULL;
+ int len=0;
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "ReadFile require an argument!\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ s = ast_strdupa(data);
+
+ varname = strsep(&s, "=");
+ file = strsep(&s, "|");
+ length = s;
+
+ if (!varname || !file) {
+ ast_log(LOG_ERROR, "No file or variable specified!\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ if (length) {
+ if ((sscanf(length, "%d", &len) != 1) || (len < 0)) {
+ ast_log(LOG_WARNING, "%s is not a positive number, defaulting length to max\n", length);
+ len = 0;
+ }
+ }
+
+ if ((returnvar = ast_read_textfile(file))) {
+ if (len > 0) {
+ if (len < strlen(returnvar))
+ returnvar[len]='\0';
+ else
+ ast_log(LOG_WARNING, "%s is longer than %d, and %d \n", file, len, (int)strlen(returnvar));
+ }
+ pbx_builtin_setvar_helper(chan, varname, returnvar);
+ free(returnvar);
+ }
+ ast_module_user_remove(u);
+ return res;
+}
+
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app_readfile);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app_readfile, readfile_exec, readfile_synopsis, readfile_descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Stores output of file into a variable");
diff --git a/apps/app_realtime.c b/apps/app_realtime.c
new file mode 100644
index 000000000..9bc6e315b
--- /dev/null
+++ b/apps/app_realtime.c
@@ -0,0 +1,263 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * Anthony Minessale <anthmct@yahoo.com>
+ * 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 RealTime App
+ *
+ * \author Anthony Minessale <anthmct@yahoo.com>
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/options.h"
+#include "asterisk/pbx.h"
+#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/lock.h"
+#include "asterisk/cli.h"
+
+#define next_one(var) var = var->next
+#define crop_data(str) { *(str) = '\0' ; (str)++; }
+
+static char *app = "RealTime";
+static char *uapp = "RealTimeUpdate";
+static char *synopsis = "Realtime Data Lookup";
+static char *usynopsis = "Realtime Data Rewrite";
+static char *USAGE = "RealTime(<family>|<colmatch>|<value>[|<prefix>])";
+static char *UUSAGE = "RealTimeUpdate(<family>|<colmatch>|<value>|<newcol>|<newval>)";
+static char *desc =
+"Use the RealTime config handler system to read data into channel variables.\n"
+"RealTime(<family>|<colmatch>|<value>[|<prefix>])\n\n"
+"All unique column names will be set as channel variables with optional prefix\n"
+"to the name. For example, a prefix of 'var_' would make the column 'name'\n"
+"become the variable ${var_name}. REALTIMECOUNT will be set with the number\n"
+"of values read.\n";
+static char *udesc = "Use the RealTime config handler system to update a value\n"
+"RealTimeUpdate(<family>|<colmatch>|<value>|<newcol>|<newval>)\n\n"
+"The column <newcol> in 'family' matching column <colmatch>=<value> will be\n"
+"updated to <newval>. REALTIMECOUNT will be set with the number of rows\n"
+"updated or -1 if an error occurs.\n";
+
+
+static int cli_realtime_load(int fd, int argc, char **argv)
+{
+ char *header_format = "%30s %-30s\n";
+ struct ast_variable *var = NULL, *save = NULL;
+
+ if (argc < 5) {
+ ast_cli(fd, "You must supply a family name, a column to match on, and a value to match to.\n");
+ return RESULT_FAILURE;
+ }
+
+ var = ast_load_realtime(argv[2], argv[3], argv[4], NULL);
+
+ if (var) {
+ save = var;
+ ast_cli(fd, header_format, "Column Name", "Column Value");
+ ast_cli(fd, header_format, "--------------------", "--------------------");
+ while (var) {
+ ast_cli(fd, header_format, var->name, var->value);
+ var = var->next;
+ }
+ ast_variables_destroy(save);
+ } else {
+ ast_cli(fd, "No rows found matching search criteria.\n");
+ }
+ return RESULT_SUCCESS;
+}
+
+static int cli_realtime_update(int fd, int argc, char **argv) {
+ int res = 0;
+
+ if(argc<7) {
+ ast_cli(fd, "You must supply a family name, a column to update on, a new value, column to match, and value to to match.\n");
+ ast_cli(fd, "Ex: realtime update sipfriends name bobsphone port 4343\n will execute SQL as UPDATE sipfriends SET port = 4343 WHERE name = bobsphone\n");
+ return RESULT_FAILURE;
+ }
+
+ res = ast_update_realtime(argv[2], argv[3], argv[4], argv[5], argv[6], NULL);
+
+ if(res < 0) {
+ ast_cli(fd, "Failed to update. Check the debug log for possible SQL related entries.\n");
+ return RESULT_SUCCESS;
+ }
+
+ ast_cli(fd, "Updated %d RealTime record%s.\n", res, (res != 1) ? "s" : "");
+
+ return RESULT_SUCCESS;
+}
+
+static char cli_realtime_load_usage[] =
+"Usage: realtime load <family> <colmatch> <value>\n"
+" Prints out a list of variables using the RealTime driver.\n";
+
+static char cli_realtime_update_usage[] =
+"Usage: realtime update <family> <colmatch> <value>\n"
+" Update a single variable using the RealTime driver.\n";
+
+static struct ast_cli_entry cli_realtime[] = {
+ { { "realtime", "load", NULL, NULL },
+ cli_realtime_load, "Used to print out RealTime variables.",
+ cli_realtime_load_usage, NULL },
+
+ { { "realtime", "update", NULL, NULL },
+ cli_realtime_update, "Used to update RealTime variables.",
+ cli_realtime_update_usage, NULL },
+};
+
+static int realtime_update_exec(struct ast_channel *chan, void *data)
+{
+ char *family=NULL, *colmatch=NULL, *value=NULL, *newcol=NULL, *newval=NULL;
+ struct ast_module_user *u;
+ int res = 0, count = 0;
+ char countc[13];
+
+ ast_log(LOG_WARNING, "The RealTimeUpdate application has been deprecated in favor of the REALTIME dialplan function.\n");
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_ERROR,"Invalid input: usage %s\n",UUSAGE);
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ family = ast_strdupa(data);
+ if ((colmatch = strchr(family,'|'))) {
+ crop_data(colmatch);
+ if ((value = strchr(colmatch,'|'))) {
+ crop_data(value);
+ if ((newcol = strchr(value,'|'))) {
+ crop_data(newcol);
+ if ((newval = strchr(newcol,'|')))
+ crop_data(newval);
+ }
+ }
+ }
+ if (! (family && value && colmatch && newcol && newval) ) {
+ ast_log(LOG_ERROR,"Invalid input: usage %s\n",UUSAGE);
+ res = -1;
+ } else {
+ count = ast_update_realtime(family,colmatch,value,newcol,newval,NULL);
+ }
+
+ snprintf(countc, sizeof(countc), "%d", count);
+ pbx_builtin_setvar_helper(chan, "REALTIMECOUNT", countc);
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+
+static int realtime_exec(struct ast_channel *chan, void *data)
+{
+ int res=0, count=0;
+ struct ast_module_user *u;
+ struct ast_variable *var, *itt;
+ char *family=NULL, *colmatch=NULL, *value=NULL, *prefix=NULL, *vname=NULL;
+ char countc[13];
+ size_t len;
+
+ ast_log(LOG_WARNING, "The RealTime application has been deprecated in favor of the REALTIME dialplan function.\n");
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_ERROR,"Invalid input: usage %s\n",USAGE);
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ family = ast_strdupa(data);
+ if ((colmatch = strchr(family,'|'))) {
+ crop_data(colmatch);
+ if ((value = strchr(colmatch,'|'))) {
+ crop_data(value);
+ if ((prefix = strchr(value,'|')))
+ crop_data(prefix);
+ }
+ }
+ if (! (family && value && colmatch) ) {
+ ast_log(LOG_ERROR,"Invalid input: usage %s\n",USAGE);
+ res = -1;
+ } else {
+ if (option_verbose > 3)
+ ast_verbose(VERBOSE_PREFIX_4"Realtime Lookup: family:'%s' colmatch:'%s' value:'%s'\n",family,colmatch,value);
+ if ((var = ast_load_realtime(family, colmatch, value, NULL))) {
+ for (itt = var; itt; itt = itt->next) {
+ if(prefix) {
+ len = strlen(prefix) + strlen(itt->name) + 2;
+ vname = alloca(len);
+ snprintf(vname,len,"%s%s",prefix,itt->name);
+
+ } else
+ vname = itt->name;
+
+ pbx_builtin_setvar_helper(chan, vname, itt->value);
+ count++;
+ }
+ ast_variables_destroy(var);
+ } else if (option_verbose > 3)
+ ast_verbose(VERBOSE_PREFIX_4"No Realtime Matches Found.\n");
+ }
+ snprintf(countc, sizeof(countc), "%d", count);
+ pbx_builtin_setvar_helper(chan, "REALTIMECOUNT", countc);
+
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ ast_cli_unregister_multiple(cli_realtime, sizeof(cli_realtime) / sizeof(struct ast_cli_entry));
+ res = ast_unregister_application(uapp);
+ res |= ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ int res;
+
+ ast_cli_register_multiple(cli_realtime, sizeof(cli_realtime) / sizeof(struct ast_cli_entry));
+ res = ast_register_application(uapp, realtime_update_exec, usynopsis, udesc);
+ res |= ast_register_application(app, realtime_exec, synopsis, desc);
+
+ return res;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Realtime Data Lookup/Rewrite");
diff --git a/apps/app_record.c b/apps/app_record.c
new file mode 100644
index 000000000..23e1a9a85
--- /dev/null
+++ b/apps/app_record.c
@@ -0,0 +1,386 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * Matthew Fredrickson <creslin@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 Trivial application to record a sound file
+ *
+ * \author Matthew Fredrickson <creslin@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/dsp.h"
+#include "asterisk/utils.h"
+#include "asterisk/options.h"
+#include "asterisk/app.h"
+
+
+static char *app = "Record";
+
+static char *synopsis = "Record to a file";
+
+static char *descrip =
+" Record(filename.format|silence[|maxduration][|options])\n\n"
+"Records from the channel into a given filename. If the file exists it will\n"
+"be overwritten.\n"
+"- 'format' is the format of the file type to be recorded (wav, gsm, etc).\n"
+"- 'silence' is the number of seconds of silence to allow before returning.\n"
+"- 'maxduration' is the maximum recording duration in seconds. If missing\n"
+"or 0 there is no maximum.\n"
+"- 'options' may contain any of the following letters:\n"
+" 'a' : append to existing recording rather than replacing\n"
+" 'n' : do not answer, but record anyway if line not yet answered\n"
+" 'q' : quiet (do not play a beep tone)\n"
+" 's' : skip recording if the line is not yet answered\n"
+" 't' : use alternate '*' terminator key (DTMF) instead of default '#'\n"
+" 'x' : ignore all terminator keys (DTMF) and keep recording until hangup\n"
+"\n"
+"If filename contains '%d', these characters will be replaced with a number\n"
+"incremented by one each time the file is recorded. A channel variable\n"
+"named RECORDED_FILE will also be set, which contains the final filemname.\n\n"
+"Use 'core show file formats' to see the available formats on your system\n\n"
+"User can press '#' to terminate the recording and continue to the next priority.\n\n"
+"If the user should hangup during a recording, all data will be lost and the\n"
+"application will teminate. \n";
+
+
+static int record_exec(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ int count = 0;
+ int percentflag = 0;
+ char *filename, *ext = NULL, *silstr, *maxstr, *options;
+ char *vdata, *p;
+ int i = 0;
+ char tmp[256];
+
+ struct ast_filestream *s = '\0';
+ struct ast_module_user *u;
+ struct ast_frame *f = NULL;
+
+ 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? */
+ int maxduration = 0; /* max duration of recording in milliseconds */
+ int gottimeout = 0; /* did we timeout for maxduration exceeded? */
+ int option_skip = 0;
+ int option_noanswer = 0;
+ int option_append = 0;
+ int terminator = '#';
+ int option_quiet = 0;
+ int rfmt = 0;
+ int flags;
+ int waitres;
+ struct ast_silence_generator *silgen = NULL;
+
+ /* The next few lines of code parse out the filename and header from the input string */
+ if (ast_strlen_zero(data)) { /* no data implies no filename or anything is present */
+ ast_log(LOG_WARNING, "Record requires an argument (filename)\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ /* Yay for strsep being easy */
+ vdata = ast_strdupa(data);
+
+ p = vdata;
+ filename = strsep(&p, "|");
+ silstr = strsep(&p, "|");
+ maxstr = strsep(&p, "|");
+ options = strsep(&p, "|");
+
+ if (filename) {
+ if (strstr(filename, "%d"))
+ percentflag = 1;
+ ext = strrchr(filename, '.'); /* to support filename with a . in the filename, not format */
+ if (!ext)
+ ext = strchr(filename, ':');
+ if (ext) {
+ *ext = '\0';
+ ext++;
+ }
+ }
+ if (!ext) {
+ ast_log(LOG_WARNING, "No extension specified to filename!\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+ if (silstr) {
+ if ((sscanf(silstr, "%d", &i) == 1) && (i > -1)) {
+ silence = i * 1000;
+ } else if (!ast_strlen_zero(silstr)) {
+ ast_log(LOG_WARNING, "'%s' is not a valid silence duration\n", silstr);
+ }
+ }
+
+ if (maxstr) {
+ if ((sscanf(maxstr, "%d", &i) == 1) && (i > -1))
+ /* Convert duration to milliseconds */
+ maxduration = i * 1000;
+ else if (!ast_strlen_zero(maxstr))
+ ast_log(LOG_WARNING, "'%s' is not a valid maximum duration\n", maxstr);
+ }
+ if (options) {
+ /* Retain backwards compatibility with old style options */
+ if (!strcasecmp(options, "skip"))
+ option_skip = 1;
+ else if (!strcasecmp(options, "noanswer"))
+ option_noanswer = 1;
+ else {
+ if (strchr(options, 's'))
+ option_skip = 1;
+ if (strchr(options, 'n'))
+ option_noanswer = 1;
+ if (strchr(options, 'a'))
+ option_append = 1;
+ if (strchr(options, 't'))
+ terminator = '*';
+ if (strchr(options, 'x'))
+ terminator = 0;
+ if (strchr(options, 'q'))
+ option_quiet = 1;
+ }
+ }
+
+ /* done parsing */
+
+ /* these are to allow the use of the %d in the config file for a wild card of sort to
+ create a new file with the inputed name scheme */
+ if (percentflag) {
+ AST_DECLARE_APP_ARGS(fname,
+ AST_APP_ARG(piece)[100];
+ );
+ char *tmp2 = ast_strdupa(filename);
+ char countstring[15];
+ int i;
+
+ /* Separate each piece out by the format specifier */
+ AST_NONSTANDARD_APP_ARGS(fname, tmp2, '%');
+ do {
+ int tmplen;
+ /* First piece has no leading percent, so it's copied verbatim */
+ ast_copy_string(tmp, fname.piece[0], sizeof(tmp));
+ tmplen = strlen(tmp);
+ for (i = 1; i < fname.argc; i++) {
+ if (fname.piece[i][0] == 'd') {
+ /* Substitute the count */
+ snprintf(countstring, sizeof(countstring), "%d", count);
+ ast_copy_string(tmp + tmplen, countstring, sizeof(tmp) - tmplen);
+ tmplen += strlen(countstring);
+ } else if (tmplen + 2 < sizeof(tmp)) {
+ /* Unknown format specifier - just copy it verbatim */
+ tmp[tmplen++] = '%';
+ tmp[tmplen++] = fname.piece[i][0];
+ }
+ /* Copy the remaining portion of the piece */
+ ast_copy_string(tmp + tmplen, &(fname.piece[i][1]), sizeof(tmp) - tmplen);
+ }
+ count++;
+ } while (ast_fileexists(tmp, ext, chan->language) > 0);
+ pbx_builtin_setvar_helper(chan, "RECORDED_FILE", tmp);
+ } else
+ ast_copy_string(tmp, filename, sizeof(tmp));
+ /* end of routine mentioned */
+
+
+
+ if (chan->_state != AST_STATE_UP) {
+ if (option_skip) {
+ /* At the user's option, skip if the line is not up */
+ ast_module_user_remove(u);
+ return 0;
+ } else if (!option_noanswer) {
+ /* Otherwise answer unless we're supposed to record while on-hook */
+ res = ast_answer(chan);
+ }
+ }
+
+ if (res) {
+ ast_log(LOG_WARNING, "Could not answer channel '%s'\n", chan->name);
+ goto out;
+ }
+
+ if (!option_quiet) {
+ /* Some code to play a nice little beep to signify the start of the record operation */
+ res = ast_streamfile(chan, "beep", chan->language);
+ if (!res) {
+ res = ast_waitstream(chan, "");
+ } else {
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", chan->name);
+ }
+ ast_stopstream(chan);
+ }
+
+ /* The end of beep code. Now the recording starts */
+
+ 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");
+ ast_module_user_remove(u);
+ return -1;
+ }
+ sildet = ast_dsp_new();
+ if (!sildet) {
+ ast_log(LOG_WARNING, "Unable to create silence detector :(\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+ ast_dsp_set_threshold(sildet, 256);
+ }
+
+
+ flags = option_append ? O_CREAT|O_APPEND|O_WRONLY : O_CREAT|O_TRUNC|O_WRONLY;
+ s = ast_writefile( tmp, ext, NULL, flags , 0, 0644);
+
+ if (!s) {
+ ast_log(LOG_WARNING, "Could not create file %s\n", filename);
+ goto out;
+ }
+
+ if (ast_opt_transmit_silence)
+ silgen = ast_channel_start_silence_generator(chan);
+
+ /* Request a video update */
+ ast_indicate(chan, AST_CONTROL_VIDUPDATE);
+
+ if (maxduration <= 0)
+ maxduration = -1;
+
+ while ((waitres = ast_waitfor(chan, maxduration)) > -1) {
+ if (maxduration > 0) {
+ if (waitres == 0) {
+ gottimeout = 1;
+ break;
+ }
+ maxduration = waitres;
+ }
+
+ f = ast_read(chan);
+ if (!f) {
+ res = -1;
+ break;
+ }
+ if (f->frametype == AST_FRAME_VOICE) {
+ res = ast_writestream(s, f);
+
+ if (res) {
+ ast_log(LOG_WARNING, "Problem writing frame\n");
+ ast_frfree(f);
+ break;
+ }
+
+ 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 */
+ ast_frfree(f);
+ gotsilence = 1;
+ break;
+ }
+ }
+ } else if (f->frametype == AST_FRAME_VIDEO) {
+ res = ast_writestream(s, f);
+
+ if (res) {
+ ast_log(LOG_WARNING, "Problem writing frame\n");
+ ast_frfree(f);
+ break;
+ }
+ } else if ((f->frametype == AST_FRAME_DTMF) &&
+ (f->subclass == terminator)) {
+ ast_frfree(f);
+ break;
+ }
+ ast_frfree(f);
+ }
+ if (!f) {
+ ast_log(LOG_DEBUG, "Got hangup\n");
+ res = -1;
+ }
+
+ if (gotsilence) {
+ ast_stream_rewind(s, silence-1000);
+ ast_truncstream(s);
+ } else if (!gottimeout) {
+ /* Strip off the last 1/4 second of it */
+ ast_stream_rewind(s, 250);
+ ast_truncstream(s);
+ }
+ ast_closestream(s);
+
+ if (silgen)
+ ast_channel_stop_silence_generator(chan, silgen);
+
+ out:
+ if ((silence > 0) && rfmt) {
+ res = ast_set_read_format(chan, rfmt);
+ if (res)
+ ast_log(LOG_WARNING, "Unable to restore read format on '%s'\n", chan->name);
+ if (sildet)
+ ast_dsp_free(sildet);
+ }
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, record_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Trivial Record Application");
diff --git a/apps/app_rpt.c b/apps/app_rpt.c
new file mode 100644
index 000000000..b7dc13fdc
--- /dev/null
+++ b/apps/app_rpt.c
@@ -0,0 +1,11930 @@
+/* #define OLD_ASTERISK */
+#define OLDKEY
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2002-2007, Jim Dixon, WB6NIL
+ *
+ * Jim Dixon, WB6NIL <jim@lambdatel.com>
+ * Serious contributions by Steve RoDgers, WA6ZFT <hwstar@rodgers.sdcoxmail.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 Radio Repeater / Remote Base program
+ * version 0.73 09/04/07
+ *
+ * \author Jim Dixon, WB6NIL <jim@lambdatel.com>
+ *
+ * \note Serious contributions by Steve RoDgers, WA6ZFT <hwstar@rodgers.sdcoxmail.com>
+ *
+ * See http://www.zapatatelephony.org/app_rpt.html
+ *
+ *
+ * Repeater / Remote Functions:
+ * "Simple" Mode: * - autopatch access, # - autopatch hangup
+ * Normal mode:
+ * See the function list in rpt.conf (autopatchup, autopatchdn)
+ * autopatchup can optionally take comma delimited setting=value pairs:
+ *
+ *
+ * context=string : Override default context with "string"
+ * dialtime=ms : Specify the max number of milliseconds between phone number digits (1000 milliseconds = 1 second)
+ * farenddisconnect=1 : Automatically disconnect when called party hangs up
+ * noct=1 : Don't send repeater courtesy tone during autopatch calls
+ * quiet=1 : Don't send dial tone, or connect messages. Do not send patch down message when called party hangs up
+ *
+ *
+ * Example: 123=autopatchup,dialtime=20000,noct=1,farenddisconnect=1
+ *
+ * To send an asterisk (*) while dialing or talking on phone,
+ * use the autopatch acess code.
+ *
+ *
+ * status cmds:
+ *
+ * 1 - Force ID
+ * 2 - Give Time of Day
+ * 3 - Give software Version
+ *
+ * cop (control operator) cmds:
+ *
+ * 1 - System warm boot
+ * 2 - System enable
+ * 3 - System disable
+ * 4 - Test Tone On/Off
+ * 5 - Dump System Variables on Console (debug)
+ * 6 - PTT (phone mode only)
+ * 7 - Time out timer enable
+ * 8 - Time out timer disable
+ * 9 - Autopatch enable
+ * 10 - Autopatch disable
+ * 11 - Link enable
+ * 12 - Link disable
+ * 13 - Query System State
+ * 14 - Change System State
+ * 15 - Scheduler Enable
+ * 16 - Scheduler Disable
+ * 17 - User functions (time, id, etc) enable
+ * 18 - User functions (time, id, etc) disable
+ * 19 - Select alternate hang timer
+ * 20 - Select standard hang timer
+ *
+ * ilink cmds:
+ *
+ * 1 - Disconnect specified link
+ * 2 - Connect specified link -- monitor only
+ * 3 - Connect specified link -- tranceive
+ * 4 - Enter command mode on specified link
+ * 5 - System status
+ * 6 - Disconnect all links
+ * 11 - Disconnect a previously permanently connected link
+ * 12 - Permanently connect specified link -- monitor only
+ * 13 - Permanently connect specified link -- tranceive
+ * 15 - Full system status (all nodes)
+ * 16 - Reconnect links disconnected with "disconnect all links"
+ * 200 thru 215 - (Send DTMF 0-9,*,#,A-D) (200=0, 201=1, 210=*, etc)
+ *
+ * remote cmds:
+ *
+ * 1 - Recall Memory MM (*000-*099) (Gets memory from rpt.conf)
+ * 2 - Set VFO MMMMM*KKK*O (Mhz digits, Khz digits, Offset)
+ * 3 - Set Rx PL Tone HHH*D*
+ * 4 - Set Tx PL Tone HHH*D* (Not currently implemented with DHE RBI-1)
+ * 5 - Link Status (long)
+ * 6 - Set operating mode M (FM, USB, LSB, AM, etc)
+ * 100 - RX PL off (Default)
+ * 101 - RX PL On
+ * 102 - TX PL Off (Default)
+ * 103 - TX PL On
+ * 104 - Low Power
+ * 105 - Med Power
+ * 106 - Hi Power
+ * 107 - Bump Down 20 Hz
+ * 108 - Bump Down 100 Hz
+ * 109 - Bump Down 500 Hz
+ * 110 - Bump Up 20 Hz
+ * 111 - Bump Up 100 Hz
+ * 112 - Bump Up 500 Hz
+ * 113 - Scan Down Slow
+ * 114 - Scan Down Medium
+ * 115 - Scan Down Fast
+ * 116 - Scan Up Slow
+ * 117 - Scan Up Medium
+ * 118 - Scan Up Fast
+ * 119 - Transmit allowing auto-tune
+ * 140 - Link Status (brief)
+ * 200 thru 215 - (Send DTMF 0-9,*,#,A-D) (200=0, 201=1, 210=*, etc)
+ *
+ *
+ * 'duplex' modes: (defaults to duplex=2)
+ *
+ * 0 - Only remote links key Tx and no main repeat audio.
+ * 1 - Everything other then main Rx keys Tx, no main repeat audio.
+ * 2 - Normal mode
+ * 3 - Normal except no main repeat audio.
+ * 4 - Normal except no main repeat audio during autopatch only
+ *
+*/
+
+/*** MODULEINFO
+ <depend>dahdi</depend>
+ <depend>tonezone</depend>
+ <defaultenabled>no</defaultenabled>
+ ***/
+
+/* Un-comment the following to include support for MDC-1200 digital tone
+ signalling protocol (using KA6SQG's GPL'ed implementation) */
+/* #include "mdc_decode.c" */
+
+/* Un-comment the following to include support for notch filters in the
+ rx audio stream (using Tony Fisher's mknotch (mkfilter) implementation) */
+/* #include "rpt_notch.c" */
+
+/* maximum digits in DTMF buffer, and seconds after * for DTMF command timeout */
+
+#define MAXDTMF 32
+#define MAXMACRO 2048
+#define MAXLINKLIST 512
+#define LINKLISTTIME 10000
+#define LINKLISTSHORTTIME 200
+#define MACROTIME 100
+#define MACROPTIME 500
+#define DTMF_TIMEOUT 3
+#define KENWOOD_RETRIES 5
+
+#define AUTHTELLTIME 7000
+#define AUTHTXTIME 1000
+#define AUTHLOGOUTTIME 25000
+
+#ifdef __RPT_NOTCH
+#define MAXFILTERS 10
+#endif
+
+#define DISC_TIME 10000 /* report disc after 10 seconds of no connect */
+#define MAX_RETRIES 5
+#define MAX_RETRIES_PERM 1000000000
+
+#define REDUNDANT_TX_TIME 2000
+
+#define RETRY_TIMER_MS 5000
+
+#define START_DELAY 2
+
+#define MAXPEERSTR 31
+#define MAXREMSTR 15
+
+#define DELIMCHR ','
+#define QUOTECHR 34
+
+#define MONITOR_DISK_BLOCKS_PER_MINUTE 38
+
+#define DEFAULT_MONITOR_MIN_DISK_BLOCKS 10000
+#define DEFAULT_REMOTE_INACT_TIMEOUT (15 * 60)
+#define DEFAULT_REMOTE_TIMEOUT (60 * 60)
+#define DEFAULT_REMOTE_TIMEOUT_WARNING (3 * 60)
+#define DEFAULT_REMOTE_TIMEOUT_WARNING_FREQ 30
+
+#define NODES "nodes"
+#define EXTNODES "extnodes"
+#define MEMORY "memory"
+#define MACRO "macro"
+#define FUNCTIONS "functions"
+#define TELEMETRY "telemetry"
+#define MORSE "morse"
+#define FUNCCHAR '*'
+#define ENDCHAR '#'
+#define EXTNODEFILE "/var/lib/asterisk/rpt_extnodes"
+
+#define DEFAULT_IOBASE 0x378
+
+#define DEFAULT_CIV_ADDR 0x58
+
+#define MAXCONNECTTIME 5000
+
+#define MAXNODESTR 300
+
+#define MAXPATCHCONTEXT 100
+
+#define ACTIONSIZE 32
+
+#define TELEPARAMSIZE 256
+
+#define REM_SCANTIME 100
+
+#define DTMF_LOCAL_TIME 250
+#define DTMF_LOCAL_STARTTIME 500
+
+#define IC706_PL_MEMORY_OFFSET 50
+
+#define ALLOW_LOCAL_CHANNELS
+
+enum {REM_OFF,REM_MONITOR,REM_TX};
+
+enum{ID,PROC,TERM,COMPLETE,UNKEY,REMDISC,REMALREADY,REMNOTFOUND,REMGO,
+ CONNECTED,CONNFAIL,STATUS,TIMEOUT,ID1, STATS_TIME,
+ STATS_VERSION, IDTALKOVER, ARB_ALPHA, TEST_TONE, REV_PATCH,
+ TAILMSG, MACRO_NOTFOUND, MACRO_BUSY, LASTNODEKEY, FULLSTATUS,
+ MEMNOTFOUND, INVFREQ, REMMODE, REMLOGIN, REMXXX, REMSHORTSTATUS,
+ REMLONGSTATUS, LOGINREQ, SCAN, SCANSTAT, TUNE, SETREMOTE,
+ TIMEOUT_WARNING, ACT_TIMEOUT_WARNING, LINKUNKEY, UNAUTHTX};
+
+
+enum {REM_SIMPLEX,REM_MINUS,REM_PLUS};
+
+enum {REM_LOWPWR,REM_MEDPWR,REM_HIPWR};
+
+enum {DC_INDETERMINATE, DC_REQ_FLUSH, DC_ERROR, DC_COMPLETE, DC_COMPLETEQUIET, DC_DOKEY};
+
+enum {SOURCE_RPT, SOURCE_LNK, SOURCE_RMT, SOURCE_PHONE, SOURCE_DPHONE};
+
+enum {DLY_TELEM, DLY_ID, DLY_UNKEY, DLY_CALLTERM, DLY_COMP, DLY_LINKUNKEY};
+
+enum {REM_MODE_FM,REM_MODE_USB,REM_MODE_LSB,REM_MODE_AM};
+
+enum {HF_SCAN_OFF,HF_SCAN_DOWN_SLOW,HF_SCAN_DOWN_QUICK,
+ HF_SCAN_DOWN_FAST,HF_SCAN_UP_SLOW,HF_SCAN_UP_QUICK,HF_SCAN_UP_FAST};
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <signal.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <search.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <dirent.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/file.h>
+#include <sys/ioctl.h>
+#ifdef HAVE_SYS_IO_H
+#include <sys/io.h>
+#endif
+#include <sys/vfs.h>
+#include <math.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <termios.h>
+
+#include "asterisk/utils.h"
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/callerid.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/features.h"
+#include "asterisk/options.h"
+#include "asterisk/cli.h"
+#include "asterisk/config.h"
+#include "asterisk/say.h"
+#include "asterisk/localtime.h"
+#include "asterisk/cdr.h"
+#include "asterisk/options.h"
+
+#include "asterisk/dahdi_compat.h"
+#include "asterisk/tonezone_compat.h"
+
+/* Start a tone-list going */
+int ast_playtones_start(struct ast_channel *chan, int vol, const char* tonelist, int interruptible);
+/*! Stop the tones from playing */
+void ast_playtones_stop(struct ast_channel *chan);
+
+static char *tdesc = "Radio Repeater / Remote Base version 0.73 09/04/2007";
+
+static char *app = "Rpt";
+
+static char *synopsis = "Radio Repeater/Remote Base Control System";
+
+static char *descrip =
+" Rpt(nodename[|options]): Radio Remote Link or Remote Base Link Endpoint Process.\n"
+"\n"
+" Not specifying an option puts it in normal endpoint mode (where source\n"
+" IP and nodename are verified).\n"
+"\n"
+" Options are as follows:\n"
+"\n"
+" X - Normal endpoint mode WITHOUT security check. Only specify\n"
+" this if you have checked security already (like with an IAX2\n"
+" user/password or something).\n"
+"\n"
+" Rannounce-string[|timeout[|timeout-destination]] - Amateur Radio\n"
+" Reverse Autopatch. Caller is put on hold, and announcement (as\n"
+" specified by the 'announce-string') is played on radio system.\n"
+" Users of radio system can access autopatch, dial specified\n"
+" code, and pick up call. Announce-string is list of names of\n"
+" recordings, or \"PARKED\" to substitute code for un-parking,\n"
+" or \"NODE\" to substitute node number.\n"
+"\n"
+" P - Phone Control mode. This allows a regular phone user to have\n"
+" full control and audio access to the radio system. For the\n"
+" user to have DTMF control, the 'phone_functions' parameter\n"
+" must be specified for the node in 'rpt.conf'. An additional\n"
+" function (cop,6) must be listed so that PTT control is available.\n"
+"\n"
+" D - Dumb Phone Control mode. This allows a regular phone user to\n"
+" have full control and audio access to the radio system. In this\n"
+" mode, the PTT is activated for the entire length of the call.\n"
+" For the user to have DTMF control (not generally recomended in\n"
+" this mode), the 'dphone_functions' parameter must be specified\n"
+" for the node in 'rpt.conf'. Otherwise no DTMF control will be\n"
+" available to the phone user.\n"
+"\n";
+
+static int debug = 0; /* Set this >0 for extra debug output */
+static int nrpts = 0;
+
+static char remdtmfstr[] = "0123456789*#ABCD";
+
+enum {TOP_TOP,TOP_WON,WON_BEFREAD,BEFREAD_AFTERREAD};
+
+int max_chan_stat [] = {22000,1000,22000,100,22000,2000,22000};
+
+#define NRPTSTAT 7
+
+struct rpt_chan_stat
+{
+ struct timeval last;
+ long long total;
+ unsigned long count;
+ unsigned long largest;
+ struct timeval largest_time;
+};
+
+char *discstr = "!!DISCONNECT!!";
+static char *remote_rig_ft897="ft897";
+static char *remote_rig_rbi="rbi";
+static char *remote_rig_kenwood="kenwood";
+static char *remote_rig_ic706="ic706";
+
+#ifdef OLD_ASTERISK
+STANDARD_LOCAL_USER;
+LOCAL_USER_DECL;
+#endif
+
+#define MSWAIT 200
+#define HANGTIME 5000
+#define TOTIME 180000
+#define IDTIME 300000
+#define MAXRPTS 20
+#define MAX_STAT_LINKS 32
+#define POLITEID 30000
+#define FUNCTDELAY 1500
+
+#define MAXXLAT 20
+#define MAXXLATTIME 3
+
+#define MAX_SYSSTATES 10
+
+struct rpt_xlat
+{
+char funccharseq[MAXXLAT];
+char endcharseq[MAXXLAT];
+char passchars[MAXXLAT];
+int funcindex;
+int endindex;
+time_t lastone;
+} ;
+
+static time_t starttime = 0;
+
+static pthread_t rpt_master_thread;
+
+struct rpt;
+
+struct rpt_link
+{
+ struct rpt_link *next;
+ struct rpt_link *prev;
+ char mode; /* 1 if in tx mode */
+ char isremote;
+ char phonemode;
+ char name[MAXNODESTR]; /* identifier (routing) string */
+ char lasttx;
+ char lastrx;
+ char lastrx1;
+ char connected;
+ char hasconnected;
+ char perma;
+ char thisconnected;
+ char outbound;
+ char disced;
+ char killme;
+ long elaptime;
+ long disctime;
+ long retrytimer;
+ long retxtimer;
+ long rerxtimer;
+ int retries;
+ int max_retries;
+ int reconnects;
+ long long connecttime;
+ struct ast_channel *chan;
+ struct ast_channel *pchan;
+ char linklist[MAXLINKLIST];
+ time_t linklistreceived;
+ long linklisttimer;
+ int dtmfed;
+ int linkunkeytocttimer;
+ struct ast_frame *lastf1,*lastf2;
+ struct rpt_chan_stat chan_stat[NRPTSTAT];
+} ;
+
+struct rpt_lstat
+{
+ struct rpt_lstat *next;
+ struct rpt_lstat *prev;
+ char peer[MAXPEERSTR];
+ char name[MAXNODESTR];
+ char mode;
+ char outbound;
+ char reconnects;
+ char thisconnected;
+ long long connecttime;
+ struct rpt_chan_stat chan_stat[NRPTSTAT];
+} ;
+
+struct rpt_tele
+{
+ struct rpt_tele *next;
+ struct rpt_tele *prev;
+ struct rpt *rpt;
+ struct ast_channel *chan;
+ int mode;
+ struct rpt_link mylink;
+ char param[TELEPARAMSIZE];
+ int submode;
+ pthread_t threadid;
+} ;
+
+struct function_table_tag
+{
+ char action[ACTIONSIZE];
+ int (*function)(struct rpt *myrpt, char *param, char *digitbuf,
+ int command_source, struct rpt_link *mylink);
+} ;
+
+/* Used to store the morse code patterns */
+
+struct morse_bits
+{
+ int len;
+ int ddcomb;
+} ;
+
+struct telem_defaults
+{
+ char name[20];
+ char value[80];
+} ;
+
+
+struct sysstate
+{
+ char txdisable;
+ char totdisable;
+ char linkfundisable;
+ char autopatchdisable;
+ char schedulerdisable;
+ char userfundisable;
+ char alternatetail;
+};
+
+static struct rpt
+{
+ ast_mutex_t lock;
+ ast_mutex_t remlock;
+ struct ast_config *cfg;
+ char reload;
+
+ char *name;
+ char *rxchanname;
+ char *txchanname;
+ char *remote;
+ struct rpt_chan_stat chan_stat[NRPTSTAT];
+ unsigned int scram;
+
+ struct {
+ char *ourcontext;
+ char *ourcallerid;
+ char *acctcode;
+ char *ident;
+ char *tonezone;
+ char simple;
+ char *functions;
+ char *link_functions;
+ char *phone_functions;
+ char *dphone_functions;
+ char *nodes;
+ char *extnodes;
+ char *extnodefile;
+ int hangtime;
+ int althangtime;
+ int totime;
+ int idtime;
+ int tailmessagetime;
+ int tailsquashedtime;
+ int duplex;
+ int politeid;
+ char *tailmessages[500];
+ int tailmessagemax;
+ char *memory;
+ char *macro;
+ char *startupmacro;
+ int iobase;
+ char *ioport;
+ char funcchar;
+ char endchar;
+ char nobusyout;
+ char notelemtx;
+ char propagate_dtmf;
+ char propagate_phonedtmf;
+ char linktolink;
+ unsigned char civaddr;
+ struct rpt_xlat inxlat;
+ struct rpt_xlat outxlat;
+ char *archivedir;
+ int authlevel;
+ char *csstanzaname;
+ char *skedstanzaname;
+ char *txlimitsstanzaname;
+ long monminblocks;
+ int remoteinacttimeout;
+ int remotetimeout;
+ int remotetimeoutwarning;
+ int remotetimeoutwarningfreq;
+ int sysstate_cur;
+ struct sysstate s[MAX_SYSSTATES];
+ } p;
+ struct rpt_link links;
+ int unkeytocttimer;
+ char keyed;
+ char exttx;
+ char localtx;
+ char remoterx;
+ char remotetx;
+ char remoteon;
+ char remtxfreqok;
+ char tounkeyed;
+ char tonotify;
+ char dtmfbuf[MAXDTMF];
+ char macrobuf[MAXMACRO];
+ char rem_dtmfbuf[MAXDTMF];
+ char lastdtmfcommand[MAXDTMF];
+ char cmdnode[50];
+ struct ast_channel *rxchannel,*txchannel, *monchannel;
+ struct ast_channel *pchannel,*txpchannel, *zaprxchannel, *zaptxchannel;
+ struct ast_frame *lastf1,*lastf2;
+ struct rpt_tele tele;
+ struct timeval lasttv,curtv;
+ pthread_t rpt_call_thread,rpt_thread;
+ time_t dtmf_time,rem_dtmf_time,dtmf_time_rem;
+ int tailtimer,totimer,idtimer,txconf,conf,callmode,cidx,scantimer,tmsgtimer,skedtimer;
+ int mustid,tailid;
+ int tailevent;
+ int telemrefcount;
+ int dtmfidx,rem_dtmfidx;
+ int dailytxtime,dailykerchunks,totalkerchunks,dailykeyups,totalkeyups,timeouts;
+ int totalexecdcommands, dailyexecdcommands;
+ long retxtimer;
+ long rerxtimer;
+ long long totaltxtime;
+ char mydtmf;
+ char exten[AST_MAX_EXTENSION];
+ char freq[MAXREMSTR],rxpl[MAXREMSTR],txpl[MAXREMSTR];
+ char offset;
+ char powerlevel;
+ char txplon;
+ char rxplon;
+ char remmode;
+ char tunerequest;
+ char hfscanmode;
+ int hfscanstatus;
+ char hfscanstop;
+ char lastlinknode[MAXNODESTR];
+ char savednodes[MAXNODESTR];
+ int stopgen;
+ char patchfarenddisconnect;
+ char patchnoct;
+ char patchquiet;
+ char patchcontext[MAXPATCHCONTEXT];
+ int patchdialtime;
+ int macro_longest;
+ int phone_longestfunc;
+ int dphone_longestfunc;
+ int link_longestfunc;
+ int longestfunc;
+ int longestnode;
+ int threadrestarts;
+ int tailmessagen;
+ time_t disgorgetime;
+ time_t lastthreadrestarttime;
+ long macrotimer;
+ char lastnodewhichkeyedusup[MAXNODESTR];
+ int dtmf_local_timer;
+ char dtmf_local_str[100];
+ struct ast_filestream *monstream;
+ char loginuser[50];
+ char loginlevel[10];
+ long authtelltimer;
+ long authtimer;
+ int iofd;
+ time_t start_time,last_activity_time;
+#ifdef __RPT_NOTCH
+ struct rptfilter
+ {
+ char desc[100];
+ float x0;
+ float x1;
+ float x2;
+ float y0;
+ float y1;
+ float y2;
+ float gain;
+ float const0;
+ float const1;
+ float const2;
+ } filters[MAXFILTERS];
+#endif
+#ifdef _MDC_DECODE_H_
+ mdc_decoder_t *mdc;
+ unsigned short lastunit;
+#endif
+} rpt_vars[MAXRPTS];
+
+struct nodelog {
+struct nodelog *next;
+struct nodelog *prev;
+time_t timestamp;
+char archivedir[MAXNODESTR];
+char str[MAXNODESTR * 2];
+} nodelog;
+
+static int service_scan(struct rpt *myrpt);
+static int set_mode_ft897(struct rpt *myrpt, char newmode);
+static int set_mode_ic706(struct rpt *myrpt, char newmode);
+static int simple_command_ft897(struct rpt *myrpt, char command);
+static int setrem(struct rpt *myrpt);
+
+AST_MUTEX_DEFINE_STATIC(nodeloglock);
+
+AST_MUTEX_DEFINE_STATIC(nodelookuplock);
+
+#ifdef APP_RPT_LOCK_DEBUG
+
+#warning COMPILING WITH LOCK-DEBUGGING ENABLED!!
+
+#define MAXLOCKTHREAD 100
+
+#define rpt_mutex_lock(x) _rpt_mutex_lock(x,myrpt,__LINE__)
+#define rpt_mutex_unlock(x) _rpt_mutex_unlock(x,myrpt,__LINE__)
+
+struct lockthread
+{
+ pthread_t id;
+ int lockcount;
+ int lastlock;
+ int lastunlock;
+} lockthreads[MAXLOCKTHREAD];
+
+
+struct by_lightning
+{
+ int line;
+ struct timeval tv;
+ struct rpt *rpt;
+ struct lockthread lockthread;
+} lock_ring[32];
+
+int lock_ring_index = 0;
+
+AST_MUTEX_DEFINE_STATIC(locklock);
+
+static struct lockthread *get_lockthread(pthread_t id)
+{
+int i;
+
+ for(i = 0; i < MAXLOCKTHREAD; i++)
+ {
+ if (lockthreads[i].id == id) return(&lockthreads[i]);
+ }
+ return(NULL);
+}
+
+static struct lockthread *put_lockthread(pthread_t id)
+{
+int i;
+
+ for(i = 0; i < MAXLOCKTHREAD; i++)
+ {
+ if (lockthreads[i].id == id)
+ return(&lockthreads[i]);
+ }
+ for(i = 0; i < MAXLOCKTHREAD; i++)
+ {
+ if (!lockthreads[i].id)
+ {
+ lockthreads[i].lockcount = 0;
+ lockthreads[i].lastlock = 0;
+ lockthreads[i].lastunlock = 0;
+ lockthreads[i].id = id;
+ return(&lockthreads[i]);
+ }
+ }
+ return(NULL);
+}
+
+
+static void rpt_mutex_spew(void)
+{
+ struct by_lightning lock_ring_copy[32];
+ int lock_ring_index_copy;
+ int i,j;
+ long long diff;
+ char a[100];
+ struct timeval lasttv;
+
+ ast_mutex_lock(&locklock);
+ memcpy(&lock_ring_copy, &lock_ring, sizeof(lock_ring_copy));
+ lock_ring_index_copy = lock_ring_index;
+ ast_mutex_unlock(&locklock);
+
+ lasttv.tv_sec = lasttv.tv_usec = 0;
+ for(i = 0 ; i < 32 ; i++)
+ {
+ j = (i + lock_ring_index_copy) % 32;
+ strftime(a,sizeof(a) - 1,"%m/%d/%Y %H:%M:%S",
+ localtime(&lock_ring_copy[j].tv.tv_sec));
+ diff = 0;
+ if(lasttv.tv_sec)
+ {
+ diff = (lock_ring_copy[j].tv.tv_sec - lasttv.tv_sec)
+ * 1000000;
+ diff += (lock_ring_copy[j].tv.tv_usec - lasttv.tv_usec);
+ }
+ lasttv.tv_sec = lock_ring_copy[j].tv.tv_sec;
+ lasttv.tv_usec = lock_ring_copy[j].tv.tv_usec;
+ if (!lock_ring_copy[j].tv.tv_sec) continue;
+ if (lock_ring_copy[j].line < 0)
+ {
+ ast_log(LOG_NOTICE,"LOCKDEBUG [#%d] UNLOCK app_rpt.c:%d node %s pid %x diff %lld us at %s.%06d\n",
+ i - 31,-lock_ring_copy[j].line,lock_ring_copy[j].rpt->name,(int) lock_ring_copy[j].lockthread.id,diff,a,(int)lock_ring_copy[j].tv.tv_usec);
+ }
+ else
+ {
+ ast_log(LOG_NOTICE,"LOCKDEBUG [#%d] LOCK app_rpt.c:%d node %s pid %x diff %lld us at %s.%06d\n",
+ i - 31,lock_ring_copy[j].line,lock_ring_copy[j].rpt->name,(int) lock_ring_copy[j].lockthread.id,diff,a,(int)lock_ring_copy[j].tv.tv_usec);
+ }
+ }
+}
+
+
+static void _rpt_mutex_lock(ast_mutex_t *lockp, struct rpt *myrpt, int line)
+{
+struct lockthread *t;
+pthread_t id;
+
+ id = pthread_self();
+ ast_mutex_lock(&locklock);
+ t = put_lockthread(id);
+ if (!t)
+ {
+ ast_mutex_unlock(&locklock);
+ return;
+ }
+ if (t->lockcount)
+ {
+ int lastline = t->lastlock;
+ ast_mutex_unlock(&locklock);
+ ast_log(LOG_NOTICE,"rpt_mutex_lock: Double lock request line %d node %s pid %x, last lock was line %d\n",line,myrpt->name,(int) t->id,lastline);
+ rpt_mutex_spew();
+ return;
+ }
+ t->lastlock = line;
+ t->lockcount = 1;
+ gettimeofday(&lock_ring[lock_ring_index].tv, NULL);
+ lock_ring[lock_ring_index].rpt = myrpt;
+ memcpy(&lock_ring[lock_ring_index].lockthread,t,sizeof(struct lockthread));
+ lock_ring[lock_ring_index++].line = line;
+ if(lock_ring_index == 32)
+ lock_ring_index = 0;
+ ast_mutex_unlock(&locklock);
+ ast_mutex_lock(lockp);
+}
+
+
+static void _rpt_mutex_unlock(ast_mutex_t *lockp, struct rpt *myrpt, int line)
+{
+struct lockthread *t;
+pthread_t id;
+
+ id = pthread_self();
+ ast_mutex_lock(&locklock);
+ t = put_lockthread(id);
+ if (!t)
+ {
+ ast_mutex_unlock(&locklock);
+ return;
+ }
+ if (!t->lockcount)
+ {
+ int lastline = t->lastunlock;
+ ast_mutex_unlock(&locklock);
+ ast_log(LOG_NOTICE,"rpt_mutex_lock: Double un-lock request line %d node %s pid %x, last un-lock was line %d\n",line,myrpt->name,(int) t->id,lastline);
+ rpt_mutex_spew();
+ return;
+ }
+ t->lastunlock = line;
+ t->lockcount = 0;
+ gettimeofday(&lock_ring[lock_ring_index].tv, NULL);
+ lock_ring[lock_ring_index].rpt = myrpt;
+ memcpy(&lock_ring[lock_ring_index].lockthread,t,sizeof(struct lockthread));
+ lock_ring[lock_ring_index++].line = -line;
+ if(lock_ring_index == 32)
+ lock_ring_index = 0;
+ ast_mutex_unlock(&locklock);
+ ast_mutex_unlock(lockp);
+}
+
+#else /* APP_RPT_LOCK_DEBUG */
+
+#define rpt_mutex_lock(x) ast_mutex_lock(x)
+#define rpt_mutex_unlock(x) ast_mutex_unlock(x)
+
+#endif /* APP_RPT_LOCK_DEBUG */
+
+/*
+* Return 1 if rig is multimode capable
+*/
+
+static int multimode_capable(struct rpt *myrpt)
+{
+ if(!strcmp(myrpt->remote, remote_rig_ft897))
+ return 1;
+ if(!strcmp(myrpt->remote, remote_rig_ic706))
+ return 1;
+ return 0;
+}
+
+/*
+* CLI extensions
+*/
+
+/* Debug mode */
+static int rpt_do_debug(int fd, int argc, char *argv[]);
+static int rpt_do_dump(int fd, int argc, char *argv[]);
+static int rpt_do_stats(int fd, int argc, char *argv[]);
+static int rpt_do_lstats(int fd, int argc, char *argv[]);
+static int rpt_do_nodes(int fd, int argc, char *argv[]);
+static int rpt_do_reload(int fd, int argc, char *argv[]);
+static int rpt_do_restart(int fd, int argc, char *argv[]);
+static int rpt_do_fun(int fd, int argc, char *argv[]);
+
+static char debug_usage[] =
+"Usage: rpt debug level {0-7}\n"
+" Enables debug messages in app_rpt\n";
+
+static char dump_usage[] =
+"Usage: rpt dump <nodename>\n"
+" Dumps struct debug info to log\n";
+
+static char dump_stats[] =
+"Usage: rpt stats <nodename>\n"
+" Dumps node statistics to console\n";
+
+static char dump_lstats[] =
+"Usage: rpt lstats <nodename>\n"
+" Dumps link statistics to console\n";
+
+static char dump_nodes[] =
+"Usage: rpt nodes <nodename>\n"
+" Dumps a list of directly and indirectly connected nodes to the console\n";
+
+static char reload_usage[] =
+"Usage: rpt reload\n"
+" Reloads app_rpt running config parameters\n";
+
+static char restart_usage[] =
+"Usage: rpt restart\n"
+" Restarts app_rpt\n";
+
+static char fun_usage[] =
+"Usage: rpt fun <nodename> <command>\n"
+" Send a DTMF function to a node\n";
+
+
+static struct ast_cli_entry cli_debug =
+ { { "rpt", "debug", "level" }, rpt_do_debug,
+ "Enable app_rpt debugging", debug_usage };
+
+static struct ast_cli_entry cli_dump =
+ { { "rpt", "dump" }, rpt_do_dump,
+ "Dump app_rpt structs for debugging", dump_usage };
+
+static struct ast_cli_entry cli_stats =
+ { { "rpt", "stats" }, rpt_do_stats,
+ "Dump node statistics", dump_stats };
+
+static struct ast_cli_entry cli_nodes =
+ { { "rpt", "nodes" }, rpt_do_nodes,
+ "Dump node list", dump_nodes };
+
+static struct ast_cli_entry cli_lstats =
+ { { "rpt", "lstats" }, rpt_do_lstats,
+ "Dump link statistics", dump_lstats };
+
+static struct ast_cli_entry cli_reload =
+ { { "rpt", "reload" }, rpt_do_reload,
+ "Reload app_rpt config", reload_usage };
+
+static struct ast_cli_entry cli_restart =
+ { { "rpt", "restart" }, rpt_do_restart,
+ "Restart app_rpt", restart_usage };
+
+static struct ast_cli_entry cli_fun =
+ { { "rpt", "fun" }, rpt_do_fun,
+ "Execute a DTMF function", fun_usage };
+
+/*
+* Telemetry defaults
+*/
+
+
+static struct telem_defaults tele_defs[] = {
+ {"ct1","|t(350,0,100,3072)(500,0,100,3072)(660,0,100,3072)"},
+ {"ct2","|t(660,880,150,3072)"},
+ {"ct3","|t(440,0,150,3072)"},
+ {"ct4","|t(550,0,150,3072)"},
+ {"ct5","|t(660,0,150,3072)"},
+ {"ct6","|t(880,0,150,3072)"},
+ {"ct7","|t(660,440,150,3072)"},
+ {"ct8","|t(700,1100,150,3072)"},
+ {"remotemon","|t(1600,0,75,2048)"},
+ {"remotetx","|t(2000,0,75,2048)(0,0,75,0)(1600,0,75,2048)"},
+ {"cmdmode","|t(900,904,200,2048)"},
+ {"functcomplete","|t(1000,0,100,2048)(0,0,100,0)(1000,0,100,2048)"}
+} ;
+
+/*
+* Forward decl's - these suppress compiler warnings when funcs coded further down the file than thier invokation
+*/
+
+static int setrbi(struct rpt *myrpt);
+static int set_ft897(struct rpt *myrpt);
+static int set_ic706(struct rpt *myrpt);
+static int setkenwood(struct rpt *myrpt);
+static int setrbi_check(struct rpt *myrpt);
+
+
+
+/*
+* Define function protos for function table here
+*/
+
+static int function_ilink(struct rpt *myrpt, char *param, char *digitbuf, int command_source, struct rpt_link *mylink);
+static int function_autopatchup(struct rpt *myrpt, char *param, char *digitbuf, int command_source, struct rpt_link *mylink);
+static int function_autopatchdn(struct rpt *myrpt, char *param, char *digitbuf, int command_source, struct rpt_link *mylink);
+static int function_status(struct rpt *myrpt, char *param, char *digitbuf, int command_source, struct rpt_link *mylink);
+static int function_cop(struct rpt *myrpt, char *param, char *digitbuf, int command_source, struct rpt_link *mylink);
+static int function_remote(struct rpt *myrpt, char *param, char *digitbuf, int command_source, struct rpt_link *mylink);
+static int function_macro(struct rpt *myrpt, char *param, char *digitbuf, int command_source, struct rpt_link *mylink);
+/*
+* Function table
+*/
+
+static struct function_table_tag function_table[] = {
+ {"cop", function_cop},
+ {"autopatchup", function_autopatchup},
+ {"autopatchdn", function_autopatchdn},
+ {"ilink", function_ilink},
+ {"status", function_status},
+ {"remote", function_remote},
+ {"macro", function_macro}
+} ;
+
+static long diskavail(struct rpt *myrpt)
+{
+struct statfs statfsbuf;
+
+ if (!myrpt->p.archivedir) return(0);
+ if (statfs(myrpt->p.archivedir,&statfsbuf) == -1)
+ {
+ ast_log(LOG_WARNING,"Cannot get filesystem size for %s node %s\n",
+ myrpt->p.archivedir,myrpt->name);
+ return(-1);
+ }
+ return(statfsbuf.f_bavail);
+}
+
+static void do_dtmf_phone(struct rpt *myrpt, struct rpt_link *mylink, char c)
+{
+struct rpt_link *l;
+
+ l = myrpt->links.next;
+ /* go thru all the links */
+ while(l != &myrpt->links)
+ {
+ if (!l->phonemode)
+ {
+ l = l->next;
+ continue;
+ }
+ /* dont send to self */
+ if (mylink && (l == mylink))
+ {
+ l = l->next;
+ continue;
+ }
+ if (l->chan) ast_senddigit(l->chan,c);
+ l = l->next;
+ }
+ return;
+}
+
+/* node logging function */
+static void donodelog(struct rpt *myrpt,char *str)
+{
+struct nodelog *nodep;
+char datestr[100];
+
+ if (!myrpt->p.archivedir) return;
+ nodep = (struct nodelog *)malloc(sizeof(struct nodelog));
+ if (nodep == NULL)
+ {
+ ast_log(LOG_ERROR,"Cannot get memory for node log");
+ return;
+ }
+ time(&nodep->timestamp);
+ strncpy(nodep->archivedir,myrpt->p.archivedir,
+ sizeof(nodep->archivedir) - 1);
+ strftime(datestr,sizeof(datestr) - 1,"%Y%m%d%H%M%S",
+ localtime(&nodep->timestamp));
+ snprintf(nodep->str,sizeof(nodep->str) - 1,"%s %s,%s\n",
+ myrpt->name,datestr,str);
+ ast_mutex_lock(&nodeloglock);
+ insque((struct qelem *) nodep, (struct qelem *) nodelog.prev);
+ ast_mutex_unlock(&nodeloglock);
+}
+
+/* must be called locked */
+static void do_dtmf_local(struct rpt *myrpt, char c)
+{
+int i;
+char digit;
+static const char* dtmf_tones[] = {
+ "!941+1336/200,!0/200", /* 0 */
+ "!697+1209/200,!0/200", /* 1 */
+ "!697+1336/200,!0/200", /* 2 */
+ "!697+1477/200,!0/200", /* 3 */
+ "!770+1209/200,!0/200", /* 4 */
+ "!770+1336/200,!0/200", /* 5 */
+ "!770+1477/200,!0/200", /* 6 */
+ "!852+1209/200,!0/200", /* 7 */
+ "!852+1336/200,!0/200", /* 8 */
+ "!852+1477/200,!0/200", /* 9 */
+ "!697+1633/200,!0/200", /* A */
+ "!770+1633/200,!0/200", /* B */
+ "!852+1633/200,!0/200", /* C */
+ "!941+1633/200,!0/200", /* D */
+ "!941+1209/200,!0/200", /* * */
+ "!941+1477/200,!0/200" }; /* # */
+
+
+ if (c)
+ {
+ snprintf(myrpt->dtmf_local_str + strlen(myrpt->dtmf_local_str),sizeof(myrpt->dtmf_local_str) - 1,"%c",c);
+ if (!myrpt->dtmf_local_timer)
+ myrpt->dtmf_local_timer = DTMF_LOCAL_STARTTIME;
+ }
+ /* if at timeout */
+ if (myrpt->dtmf_local_timer == 1)
+ {
+ /* if anything in the string */
+ if (myrpt->dtmf_local_str[0])
+ {
+ digit = myrpt->dtmf_local_str[0];
+ myrpt->dtmf_local_str[0] = 0;
+ for(i = 1; myrpt->dtmf_local_str[i]; i++)
+ {
+ myrpt->dtmf_local_str[i - 1] =
+ myrpt->dtmf_local_str[i];
+ }
+ myrpt->dtmf_local_str[i - 1] = 0;
+ myrpt->dtmf_local_timer = DTMF_LOCAL_TIME;
+ rpt_mutex_unlock(&myrpt->lock);
+ if (digit >= '0' && digit <='9')
+ ast_playtones_start(myrpt->txchannel, 0, dtmf_tones[digit-'0'], 0);
+ else if (digit >= 'A' && digit <= 'D')
+ ast_playtones_start(myrpt->txchannel, 0, dtmf_tones[digit-'A'+10], 0);
+ else if (digit == '*')
+ ast_playtones_start(myrpt->txchannel, 0, dtmf_tones[14], 0);
+ else if (digit == '#')
+ ast_playtones_start(myrpt->txchannel, 0, dtmf_tones[15], 0);
+ else {
+ /* not handled */
+ ast_log(LOG_DEBUG, "Unable to generate DTMF tone '%c' for '%s'\n", digit, myrpt->txchannel->name);
+ }
+ rpt_mutex_lock(&myrpt->lock);
+ }
+ else
+ {
+ myrpt->dtmf_local_timer = 0;
+ }
+ }
+}
+
+static int openserial(char *fname)
+{
+ struct termios mode;
+ int fd;
+
+ fd = open(fname,O_RDWR);
+ if (fd == -1)
+ {
+ ast_log(LOG_WARNING,"Cannot open serial port %s\n",fname);
+ return -1;
+ }
+ memset(&mode, 0, sizeof(mode));
+ if (tcgetattr(fd, &mode)) {
+ ast_log(LOG_WARNING, "Unable to get serial parameters on %s: %s\n", fname, strerror(errno));
+ return -1;
+ }
+#ifndef SOLARIS
+ cfmakeraw(&mode);
+#else
+ mode.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP
+ |INLCR|IGNCR|ICRNL|IXON);
+ mode.c_oflag &= ~OPOST;
+ mode.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
+ mode.c_cflag &= ~(CSIZE|PARENB|CRTSCTS);
+ mode.c_cflag |= CS8;
+ mode.c_cc[TIME] = 3;
+ mode.c_cc[MAX] = 1;
+#endif
+
+ cfsetispeed(&mode, B9600);
+ cfsetospeed(&mode, B9600);
+ if (tcsetattr(fd, TCSANOW, &mode))
+ ast_log(LOG_WARNING, "Unable to set serial parameters on %s: %s\n", fname, strerror(errno));
+ return(fd);
+}
+
+static void mdc1200_notify(struct rpt *myrpt,char *fromnode, unsigned int unit)
+{
+ if (!fromnode)
+ {
+ ast_verbose("Got MDC-1200 ID %04X from local system (%s)\n",
+ unit,myrpt->name);
+ }
+ else
+ {
+ ast_verbose("Got MDC-1200 ID %04X from node %s (%s)\n",
+ unit,fromnode,myrpt->name);
+ }
+}
+
+#ifdef _MDC_DECODE_H_
+
+static void mdc1200_send(struct rpt *myrpt, unsigned int unit)
+{
+struct rpt_link *l;
+struct ast_frame wf;
+char str[200];
+
+
+ sprintf(str,"I %s %04X",myrpt->name,unit);
+
+ wf.frametype = AST_FRAME_TEXT;
+ wf.subclass = 0;
+ wf.offset = 0;
+ wf.mallocd = 0;
+ wf.datalen = strlen(str) + 1;
+ wf.samples = 0;
+
+
+ l = myrpt->links.next;
+ /* otherwise, send it to all of em */
+ while(l != &myrpt->links)
+ {
+ if (l->name[0] == '0')
+ {
+ l = l->next;
+ continue;
+ }
+ wf.data = str;
+ if (l->chan) ast_write(l->chan,&wf);
+ l = l->next;
+ }
+ return;
+}
+
+#endif
+
+static char func_xlat(struct rpt *myrpt,char c,struct rpt_xlat *xlat)
+{
+time_t now;
+int gotone;
+
+ time(&now);
+ gotone = 0;
+ /* if too much time, reset the skate machine */
+ if ((now - xlat->lastone) > MAXXLATTIME)
+ {
+ xlat->funcindex = xlat->endindex = 0;
+ }
+ if (xlat->funccharseq[0] && (c == xlat->funccharseq[xlat->funcindex++]))
+ {
+ time(&xlat->lastone);
+ gotone = 1;
+ if (!xlat->funccharseq[xlat->funcindex])
+ {
+ xlat->funcindex = xlat->endindex = 0;
+ return(myrpt->p.funcchar);
+ }
+ } else xlat->funcindex = 0;
+ if (xlat->endcharseq[0] && (c == xlat->endcharseq[xlat->endindex++]))
+ {
+ time(&xlat->lastone);
+ gotone = 1;
+ if (!xlat->endcharseq[xlat->endindex])
+ {
+ xlat->funcindex = xlat->endindex = 0;
+ return(myrpt->p.endchar);
+ }
+ } else xlat->endindex = 0;
+ /* if in middle of decode seq, send nothing back */
+ if (gotone) return(0);
+ /* if no pass chars specified, return em all */
+ if (!xlat->passchars[0]) return(c);
+ /* if a "pass char", pass it */
+ if (strchr(xlat->passchars,c)) return(c);
+ return(0);
+}
+
+/*
+ * Return a pointer to the first non-whitespace character
+ */
+
+static char *eatwhite(char *s)
+{
+ while((*s == ' ') || (*s == 0x09)){ /* get rid of any leading white space */
+ if(!*s)
+ break;
+ s++;
+ }
+ return s;
+}
+
+/*
+* Break up a delimited string into a table of substrings
+*
+* str - delimited string ( will be modified )
+* strp- list of pointers to substrings (this is built by this function), NULL will be placed at end of list
+* limit- maximum number of substrings to process
+*/
+
+
+
+static int finddelim(char *str, char *strp[], int limit)
+{
+int i,l,inquo;
+
+ inquo = 0;
+ i = 0;
+ strp[i++] = str;
+ if (!*str)
+ {
+ strp[0] = 0;
+ return(0);
+ }
+ for(l = 0; *str && (l < limit) ; str++)
+ {
+ if (*str == QUOTECHR)
+ {
+ if (inquo)
+ {
+ *str = 0;
+ inquo = 0;
+ }
+ else
+ {
+ strp[i - 1] = str + 1;
+ inquo = 1;
+ }
+ }
+ if ((*str == DELIMCHR) && (!inquo))
+ {
+ *str = 0;
+ l++;
+ strp[i++] = str + 1;
+ }
+ }
+ strp[i] = 0;
+ return(i);
+
+}
+
+/* must be called locked */
+static void __mklinklist(struct rpt *myrpt, struct rpt_link *mylink, char *buf)
+{
+struct rpt_link *l;
+char mode;
+int i,spos;
+
+ buf[0] = 0; /* clear output buffer */
+ /* go thru all links */
+ for(l = myrpt->links.next; l != &myrpt->links; l = l->next)
+ {
+ /* if is not a real link, ignore it */
+ if (l->name[0] == '0') continue;
+ /* dont count our stuff */
+ if (l == mylink) continue;
+ if (mylink && (!strcmp(l->name,mylink->name))) continue;
+ /* figure out mode to report */
+ mode = 'T'; /* use Tranceive by default */
+ if (!l->mode) mode = 'R'; /* indicate RX for our mode */
+ if (!l->thisconnected) mode = 'C'; /* indicate connecting */
+ spos = strlen(buf); /* current buf size (b4 we add our stuff) */
+ if (spos)
+ {
+ strcat(buf,",");
+ spos++;
+ }
+ /* add nodes into buffer */
+ if (l->linklist[0])
+ {
+ snprintf(buf + spos,MAXLINKLIST - spos,
+ "%c%s,%s",mode,l->name,l->linklist);
+ }
+ else /* if no nodes, add this node into buffer */
+ {
+ snprintf(buf + spos,MAXLINKLIST - spos,
+ "%c%s",mode,l->name);
+ }
+ /* if we are in tranceive mode, let all modes stand */
+ if (mode == 'T') continue;
+ /* downgrade everyone on this node if appropriate */
+ for(i = spos; buf[i]; i++)
+ {
+ if (buf[i] == 'T') buf[i] = mode;
+ if ((buf[i] == 'R') && (mode == 'C')) buf[i] = mode;
+ }
+ }
+ return;
+}
+
+/* must be called locked */
+static void __kickshort(struct rpt *myrpt)
+{
+struct rpt_link *l;
+
+ for(l = myrpt->links.next; l != &myrpt->links; l = l->next)
+ {
+ /* if is not a real link, ignore it */
+ if (l->name[0] == '0') continue;
+ l->linklisttimer = LINKLISTSHORTTIME;
+ }
+ return;
+}
+
+static char *node_lookup(struct rpt *myrpt,char *digitbuf)
+{
+
+char *val;
+int longestnode,j;
+struct stat mystat;
+static time_t last = 0;
+static struct ast_config *ourcfg = NULL;
+struct ast_variable *vp;
+
+ /* try to look it up locally first */
+ val = (char *) ast_variable_retrieve(myrpt->cfg, myrpt->p.nodes, digitbuf);
+ if (val) return(val);
+ ast_mutex_lock(&nodelookuplock);
+ /* if file does not exist */
+ if (stat(myrpt->p.extnodefile,&mystat) == -1)
+ {
+ if (ourcfg) ast_config_destroy(ourcfg);
+ ourcfg = NULL;
+ ast_mutex_unlock(&nodelookuplock);
+ return(NULL);
+ }
+ /* if we need to reload */
+ if (mystat.st_mtime > last)
+ {
+ if (ourcfg) ast_config_destroy(ourcfg);
+ ourcfg = ast_config_load(myrpt->p.extnodefile);
+ /* if file not there, just bail */
+ if (!ourcfg)
+ {
+ ast_mutex_unlock(&nodelookuplock);
+ return(NULL);
+ }
+ /* reset "last" time */
+ last = mystat.st_mtime;
+
+ /* determine longest node length again */
+ longestnode = 0;
+ vp = ast_variable_browse(myrpt->cfg, myrpt->p.nodes);
+ while(vp){
+ j = strlen(vp->name);
+ if (j > longestnode)
+ longestnode = j;
+ vp = vp->next;
+ }
+
+ vp = ast_variable_browse(ourcfg, myrpt->p.extnodes);
+ while(vp){
+ j = strlen(vp->name);
+ if (j > longestnode)
+ longestnode = j;
+ vp = vp->next;
+ }
+
+ myrpt->longestnode = longestnode;
+ }
+ val = NULL;
+ if (ourcfg)
+ val = (char *) ast_variable_retrieve(ourcfg, myrpt->p.extnodes, digitbuf);
+ ast_mutex_unlock(&nodelookuplock);
+ return(val);
+}
+
+/*
+* Match a keyword in a list, and return index of string plus 1 if there was a match,* else return 0.
+* If param is passed in non-null, then it will be set to the first character past the match
+*/
+
+static int matchkeyword(char *string, char **param, char *keywords[])
+{
+int i,ls;
+ for( i = 0 ; keywords[i] ; i++){
+ ls = strlen(keywords[i]);
+ if(!ls){
+ *param = NULL;
+ return 0;
+ }
+ if(!strncmp(string, keywords[i], ls)){
+ if(param)
+ *param = string + ls;
+ return i + 1;
+ }
+ }
+ param = NULL;
+ return 0;
+}
+
+/*
+* Skip characters in string which are in charlist, and return a pointer to the
+* first non-matching character
+*/
+
+static char *skipchars(char *string, char *charlist)
+{
+int i;
+ while(*string){
+ for(i = 0; charlist[i] ; i++){
+ if(*string == charlist[i]){
+ string++;
+ break;
+ }
+ }
+ if(!charlist[i])
+ return string;
+ }
+ return string;
+}
+
+
+
+static int myatoi(char *str)
+{
+int ret;
+
+ if (str == NULL) return -1;
+ /* leave this %i alone, non-base-10 input is useful here */
+ if (sscanf(str,"%i",&ret) != 1) return -1;
+ return ret;
+}
+
+static int mycompar(const void *a, const void *b)
+{
+char **x = (char **) a;
+char **y = (char **) b;
+int xoff,yoff;
+
+ if ((**x < '0') || (**x > '9')) xoff = 1; else xoff = 0;
+ if ((**y < '0') || (**y > '9')) yoff = 1; else yoff = 0;
+ return(strcmp((*x) + xoff,(*y) + yoff));
+}
+
+#ifdef __RPT_NOTCH
+
+/* rpt filter routine */
+static void rpt_filter(struct rpt *myrpt, volatile short *buf, int len)
+{
+int i,j;
+struct rptfilter *f;
+
+ for(i = 0; i < len; i++)
+ {
+ for(j = 0; j < MAXFILTERS; j++)
+ {
+ f = &myrpt->filters[j];
+ if (!*f->desc) continue;
+ f->x0 = f->x1; f->x1 = f->x2;
+ f->x2 = ((float)buf[i]) / f->gain;
+ f->y0 = f->y1; f->y1 = f->y2;
+ f->y2 = (f->x0 + f->x2) + f->const0 * f->x1
+ + (f->const1 * f->y0) + (f->const2 * f->y1);
+ buf[i] = (short)f->y2;
+ }
+ }
+}
+
+#endif
+
+
+/*
+ Get the time for the machine's time zone
+ Note: Asterisk requires a copy of localtime
+ in the /etc directory for this to work properly.
+ If /etc/localtime is not present, you will get
+ GMT time! This is especially important on systems
+ running embedded linux distributions as they don't usually
+ have support for locales.
+
+ If OLD_ASTERISK is defined, then the older localtime_r
+ function will be used. The /etc/localtime file is not
+ required in this case. This provides backward compatibility
+ with Asterisk 1.2 systems.
+
+*/
+
+static void rpt_localtime( time_t * t, struct tm *lt)
+{
+#ifdef OLD_ASTERISK
+ localtime_r(t, lt);
+#else
+ ast_localtime(t, lt, NULL);
+#endif
+}
+
+/* Retrieve an int from a config file */
+
+static int retrieve_astcfgint(struct rpt *myrpt,char *category, char *name, int min, int max, int defl)
+{
+ char *var;
+ int ret;
+ char include_zero = 0;
+
+ if(min < 0){ /* If min is negative, this means include 0 as a valid entry */
+ min = -min;
+ include_zero = 1;
+ }
+
+ var = (char *) ast_variable_retrieve(myrpt->cfg, category, name);
+ if(var){
+ ret = myatoi(var);
+ if(include_zero && !ret)
+ return 0;
+ if(ret < min)
+ ret = min;
+ if(ret > max)
+ ret = max;
+ }
+ else
+ ret = defl;
+ return ret;
+}
+
+
+static void load_rpt_vars(int n,int init)
+{
+char *this,*val;
+int i,j,longestnode;
+struct ast_variable *vp;
+struct ast_config *cfg;
+char *strs[100];
+char s1[256];
+static char *cs_keywords[] = {"rptena","rptdis","apena","apdis","lnkena","lnkdis","totena","totdis","skena","skdis",
+ "ufena","ufdis","atena","atdis",NULL};
+
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "%s config for repeater %s\n",
+ (init) ? "Loading initial" : "Re-Loading",rpt_vars[n].name);
+ ast_mutex_lock(&rpt_vars[n].lock);
+ if (rpt_vars[n].cfg) ast_config_destroy(rpt_vars[n].cfg);
+ cfg = ast_config_load("rpt.conf");
+ if (!cfg) {
+ ast_mutex_unlock(&rpt_vars[n].lock);
+ ast_log(LOG_NOTICE, "Unable to open radio repeater configuration rpt.conf. Radio Repeater disabled.\n");
+ pthread_exit(NULL);
+ }
+ rpt_vars[n].cfg = cfg;
+ this = rpt_vars[n].name;
+ memset(&rpt_vars[n].p,0,sizeof(rpt_vars[n].p));
+ if (init)
+ {
+ /* clear all the fields in the structure after 'p' */
+ memset(&rpt_vars[n].p + sizeof(rpt_vars[0].p), 0, sizeof(rpt_vars[0]) - sizeof(rpt_vars[0].p) - offsetof(typeof(rpt_vars[0]), p));
+ rpt_vars[n].tele.next = &rpt_vars[n].tele;
+ rpt_vars[n].tele.prev = &rpt_vars[n].tele;
+ rpt_vars[n].rpt_thread = AST_PTHREADT_NULL;
+ rpt_vars[n].tailmessagen = 0;
+ }
+#ifdef __RPT_NOTCH
+ /* zot out filters stuff */
+ memset(&rpt_vars[n].filters,0,sizeof(rpt_vars[n].filters));
+#endif
+ val = (char *) ast_variable_retrieve(cfg,this,"context");
+ if (val) rpt_vars[n].p.ourcontext = val;
+ else rpt_vars[n].p.ourcontext = this;
+ val = (char *) ast_variable_retrieve(cfg,this,"callerid");
+ if (val) rpt_vars[n].p.ourcallerid = val;
+ val = (char *) ast_variable_retrieve(cfg,this,"accountcode");
+ if (val) rpt_vars[n].p.acctcode = val;
+ val = (char *) ast_variable_retrieve(cfg,this,"idrecording");
+ if (val) rpt_vars[n].p.ident = val;
+ val = (char *) ast_variable_retrieve(cfg,this,"hangtime");
+ if (val) rpt_vars[n].p.hangtime = atoi(val);
+ else rpt_vars[n].p.hangtime = HANGTIME;
+ val = (char *) ast_variable_retrieve(cfg,this,"althangtime");
+ if (val) rpt_vars[n].p.althangtime = atoi(val);
+ else rpt_vars[n].p.althangtime = HANGTIME;
+ val = (char *) ast_variable_retrieve(cfg,this,"totime");
+ if (val) rpt_vars[n].p.totime = atoi(val);
+ else rpt_vars[n].p.totime = TOTIME;
+ rpt_vars[n].p.tailmessagetime = retrieve_astcfgint(&rpt_vars[n],this, "tailmessagetime", 0, 2400000, 0);
+ rpt_vars[n].p.tailsquashedtime = retrieve_astcfgint(&rpt_vars[n],this, "tailsquashedtime", 0, 2400000, 0);
+ rpt_vars[n].p.duplex = retrieve_astcfgint(&rpt_vars[n],this,"duplex",0,4,2);
+ rpt_vars[n].p.idtime = retrieve_astcfgint(&rpt_vars[n],this, "idtime", -60000, 2400000, IDTIME); /* Enforce a min max including zero */
+ rpt_vars[n].p.politeid = retrieve_astcfgint(&rpt_vars[n],this, "politeid", 30000, 300000, POLITEID); /* Enforce a min max */
+ val = (char *) ast_variable_retrieve(cfg,this,"tonezone");
+ if (val) rpt_vars[n].p.tonezone = val;
+ rpt_vars[n].p.tailmessages[0] = 0;
+ rpt_vars[n].p.tailmessagemax = 0;
+ val = (char *) ast_variable_retrieve(cfg,this,"tailmessagelist");
+ if (val) rpt_vars[n].p.tailmessagemax = finddelim(val, rpt_vars[n].p.tailmessages, 500);
+ val = (char *) ast_variable_retrieve(cfg,this,"memory");
+ if (!val) val = MEMORY;
+ rpt_vars[n].p.memory = val;
+ val = (char *) ast_variable_retrieve(cfg,this,"macro");
+ if (!val) val = MACRO;
+ rpt_vars[n].p.macro = val;
+ val = (char *) ast_variable_retrieve(cfg,this,"startup_macro");
+ if (val) rpt_vars[n].p.startupmacro = val;
+ val = (char *) ast_variable_retrieve(cfg,this,"iobase");
+ /* do not use atoi() here, we need to be able to have
+ the input specified in hex or decimal so we use
+ sscanf with a %i */
+ if ((!val) || (sscanf(val,"%i",&rpt_vars[n].p.iobase) != 1))
+ rpt_vars[n].p.iobase = DEFAULT_IOBASE;
+ val = (char *) ast_variable_retrieve(cfg,this,"ioport");
+ rpt_vars[n].p.ioport = val;
+ val = (char *) ast_variable_retrieve(cfg,this,"functions");
+ if (!val)
+ {
+ val = FUNCTIONS;
+ rpt_vars[n].p.simple = 1;
+ }
+ rpt_vars[n].p.functions = val;
+ val = (char *) ast_variable_retrieve(cfg,this,"link_functions");
+ if (val) rpt_vars[n].p.link_functions = val;
+ else
+ rpt_vars[n].p.link_functions = rpt_vars[n].p.functions;
+ val = (char *) ast_variable_retrieve(cfg,this,"phone_functions");
+ if (val) rpt_vars[n].p.phone_functions = val;
+ val = (char *) ast_variable_retrieve(cfg,this,"dphone_functions");
+ if (val) rpt_vars[n].p.dphone_functions = val;
+ val = (char *) ast_variable_retrieve(cfg,this,"funcchar");
+ if (!val) rpt_vars[n].p.funcchar = FUNCCHAR; else
+ rpt_vars[n].p.funcchar = *val;
+ val = (char *) ast_variable_retrieve(cfg,this,"endchar");
+ if (!val) rpt_vars[n].p.endchar = ENDCHAR; else
+ rpt_vars[n].p.endchar = *val;
+ val = (char *) ast_variable_retrieve(cfg,this,"nobusyout");
+ if (val) rpt_vars[n].p.nobusyout = ast_true(val);
+ val = (char *) ast_variable_retrieve(cfg,this,"notelemtx");
+ if (val) rpt_vars[n].p.notelemtx = ast_true(val);
+ val = (char *) ast_variable_retrieve(cfg,this,"propagate_dtmf");
+ if (val) rpt_vars[n].p.propagate_dtmf = ast_true(val);
+ val = (char *) ast_variable_retrieve(cfg,this,"propagate_phonedtmf");
+ if (val) rpt_vars[n].p.propagate_phonedtmf = ast_true(val);
+ val = (char *) ast_variable_retrieve(cfg,this,"linktolink");
+ if (val) rpt_vars[n].p.linktolink = ast_true(val);
+ val = (char *) ast_variable_retrieve(cfg,this,"nodes");
+ if (!val) val = NODES;
+ rpt_vars[n].p.nodes = val;
+ val = (char *) ast_variable_retrieve(cfg,this,"extnodes");
+ if (!val) val = EXTNODES;
+ rpt_vars[n].p.extnodes = val;
+ val = (char *) ast_variable_retrieve(cfg,this,"extnodefile");
+ if (!val) val = EXTNODEFILE;
+ rpt_vars[n].p.extnodefile = val;
+ val = (char *) ast_variable_retrieve(cfg,this,"archivedir");
+ if (val) rpt_vars[n].p.archivedir = val;
+ val = (char *) ast_variable_retrieve(cfg,this,"authlevel");
+ if (val) rpt_vars[n].p.authlevel = atoi(val);
+ else rpt_vars[n].p.authlevel = 0;
+ val = (char *) ast_variable_retrieve(cfg,this,"monminblocks");
+ if (val) rpt_vars[n].p.monminblocks = atol(val);
+ else rpt_vars[n].p.monminblocks = DEFAULT_MONITOR_MIN_DISK_BLOCKS;
+ val = (char *) ast_variable_retrieve(cfg,this,"remote_inact_timeout");
+ if (val) rpt_vars[n].p.remoteinacttimeout = atoi(val);
+ else rpt_vars[n].p.remoteinacttimeout = DEFAULT_REMOTE_INACT_TIMEOUT;
+ val = (char *) ast_variable_retrieve(cfg,this,"civaddr");
+ if (val) rpt_vars[n].p.civaddr = atoi(val);
+ else rpt_vars[n].p.civaddr = DEFAULT_CIV_ADDR;
+ val = (char *) ast_variable_retrieve(cfg,this,"remote_timeout");
+ if (val) rpt_vars[n].p.remotetimeout = atoi(val);
+ else rpt_vars[n].p.remotetimeout = DEFAULT_REMOTE_TIMEOUT;
+ val = (char *) ast_variable_retrieve(cfg,this,"remote_timeout_warning");
+ if (val) rpt_vars[n].p.remotetimeoutwarning = atoi(val);
+ else rpt_vars[n].p.remotetimeoutwarning = DEFAULT_REMOTE_TIMEOUT_WARNING;
+ val = (char *) ast_variable_retrieve(cfg,this,"remote_timeout_warning_freq");
+ if (val) rpt_vars[n].p.remotetimeoutwarningfreq = atoi(val);
+ else rpt_vars[n].p.remotetimeoutwarningfreq = DEFAULT_REMOTE_TIMEOUT_WARNING_FREQ;
+#ifdef __RPT_NOTCH
+ val = (char *) ast_variable_retrieve(cfg,this,"rxnotch");
+ if (val) {
+ i = finddelim(val,strs,MAXFILTERS * 2);
+ i &= ~1; /* force an even number, rounded down */
+ if (i >= 2) for(j = 0; j < i; j += 2)
+ {
+ rpt_mknotch(atof(strs[j]),atof(strs[j + 1]),
+ &rpt_vars[n].filters[j >> 1].gain,
+ &rpt_vars[n].filters[j >> 1].const0,
+ &rpt_vars[n].filters[j >> 1].const1,
+ &rpt_vars[n].filters[j >> 1].const2);
+ sprintf(rpt_vars[n].filters[j >> 1].desc,"%s Hz, BW = %s",
+ strs[j],strs[j + 1]);
+ }
+
+ }
+#endif
+ val = (char *) ast_variable_retrieve(cfg,this,"inxlat");
+ if (val) {
+ memset(&rpt_vars[n].p.inxlat,0,sizeof(struct rpt_xlat));
+ i = finddelim(val,strs,3);
+ if (i) strncpy(rpt_vars[n].p.inxlat.funccharseq,strs[0],MAXXLAT - 1);
+ if (i > 1) strncpy(rpt_vars[n].p.inxlat.endcharseq,strs[1],MAXXLAT - 1);
+ if (i > 2) strncpy(rpt_vars[n].p.inxlat.passchars,strs[2],MAXXLAT - 1);
+ }
+ val = (char *) ast_variable_retrieve(cfg,this,"outxlat");
+ if (val) {
+ memset(&rpt_vars[n].p.outxlat,0,sizeof(struct rpt_xlat));
+ i = finddelim(val,strs,3);
+ if (i) strncpy(rpt_vars[n].p.outxlat.funccharseq,strs[0],MAXXLAT - 1);
+ if (i > 1) strncpy(rpt_vars[n].p.outxlat.endcharseq,strs[1],MAXXLAT - 1);
+ if (i > 2) strncpy(rpt_vars[n].p.outxlat.passchars,strs[2],MAXXLAT - 1);
+ }
+ /* retreive the stanza name for the control states if there is one */
+ val = (char *) ast_variable_retrieve(cfg,this,"controlstates");
+ rpt_vars[n].p.csstanzaname = val;
+
+ /* retreive the stanza name for the scheduler if there is one */
+ val = (char *) ast_variable_retrieve(cfg,this,"scheduler");
+ rpt_vars[n].p.skedstanzaname = val;
+
+ /* retreive the stanza name for the txlimits */
+ val = (char *) ast_variable_retrieve(cfg,this,"txlimits");
+ rpt_vars[n].p.txlimitsstanzaname = val;
+
+ longestnode = 0;
+
+ vp = ast_variable_browse(cfg, rpt_vars[n].p.nodes);
+
+ while(vp){
+ j = strlen(vp->name);
+ if (j > longestnode)
+ longestnode = j;
+ vp = vp->next;
+ }
+
+ rpt_vars[n].longestnode = longestnode;
+
+ /*
+ * For this repeater, Determine the length of the longest function
+ */
+ rpt_vars[n].longestfunc = 0;
+ vp = ast_variable_browse(cfg, rpt_vars[n].p.functions);
+ while(vp){
+ j = strlen(vp->name);
+ if (j > rpt_vars[n].longestfunc)
+ rpt_vars[n].longestfunc = j;
+ vp = vp->next;
+ }
+ /*
+ * For this repeater, Determine the length of the longest function
+ */
+ rpt_vars[n].link_longestfunc = 0;
+ vp = ast_variable_browse(cfg, rpt_vars[n].p.link_functions);
+ while(vp){
+ j = strlen(vp->name);
+ if (j > rpt_vars[n].link_longestfunc)
+ rpt_vars[n].link_longestfunc = j;
+ vp = vp->next;
+ }
+ rpt_vars[n].phone_longestfunc = 0;
+ if (rpt_vars[n].p.phone_functions)
+ {
+ vp = ast_variable_browse(cfg, rpt_vars[n].p.phone_functions);
+ while(vp){
+ j = strlen(vp->name);
+ if (j > rpt_vars[n].phone_longestfunc)
+ rpt_vars[n].phone_longestfunc = j;
+ vp = vp->next;
+ }
+ }
+ rpt_vars[n].dphone_longestfunc = 0;
+ if (rpt_vars[n].p.dphone_functions)
+ {
+ vp = ast_variable_browse(cfg, rpt_vars[n].p.dphone_functions);
+ while(vp){
+ j = strlen(vp->name);
+ if (j > rpt_vars[n].dphone_longestfunc)
+ rpt_vars[n].dphone_longestfunc = j;
+ vp = vp->next;
+ }
+ }
+ rpt_vars[n].macro_longest = 1;
+ vp = ast_variable_browse(cfg, rpt_vars[n].p.macro);
+ while(vp){
+ j = strlen(vp->name);
+ if (j > rpt_vars[n].macro_longest)
+ rpt_vars[n].macro_longest = j;
+ vp = vp->next;
+ }
+
+ /* Browse for control states */
+ if(rpt_vars[n].p.csstanzaname)
+ vp = ast_variable_browse(cfg, rpt_vars[n].p.csstanzaname);
+ else
+ vp = NULL;
+ for( i = 0 ; vp && (i < MAX_SYSSTATES) ; i++){ /* Iterate over the number of control state lines in the stanza */
+ int k,nukw,statenum;
+ statenum=atoi(vp->name);
+ strncpy(s1, vp->value, 255);
+ s1[255] = 0;
+ nukw = finddelim(s1,strs,32);
+
+ for (k = 0 ; k < nukw ; k++){ /* for each user specified keyword */
+ for(j = 0 ; cs_keywords[j] != NULL ; j++){ /* try to match to one in our internal table */
+ if(!strcmp(strs[k],cs_keywords[j])){
+ switch(j){
+ case 0: /* rptena */
+ rpt_vars[n].p.s[statenum].txdisable = 0;
+ break;
+ case 1: /* rptdis */
+ rpt_vars[n].p.s[statenum].txdisable = 1;
+ break;
+
+ case 2: /* apena */
+ rpt_vars[n].p.s[statenum].autopatchdisable = 0;
+ break;
+
+ case 3: /* apdis */
+ rpt_vars[n].p.s[statenum].autopatchdisable = 1;
+ break;
+
+ case 4: /* lnkena */
+ rpt_vars[n].p.s[statenum].linkfundisable = 0;
+ break;
+
+ case 5: /* lnkdis */
+ rpt_vars[n].p.s[statenum].linkfundisable = 1;
+ break;
+
+ case 6: /* totena */
+ rpt_vars[n].p.s[statenum].totdisable = 0;
+ break;
+
+ case 7: /* totdis */
+ rpt_vars[n].p.s[statenum].totdisable = 1;
+ break;
+
+ case 8: /* skena */
+ rpt_vars[n].p.s[statenum].schedulerdisable = 0;
+ break;
+
+ case 9: /* skdis */
+ rpt_vars[n].p.s[statenum].schedulerdisable = 1;
+ break;
+
+ case 10: /* ufena */
+ rpt_vars[n].p.s[statenum].userfundisable = 0;
+ break;
+
+ case 11: /* ufdis */
+ rpt_vars[n].p.s[statenum].userfundisable = 1;
+ break;
+
+ case 12: /* atena */
+ rpt_vars[n].p.s[statenum].alternatetail = 1;
+ break;
+
+ case 13: /* atdis */
+ rpt_vars[n].p.s[statenum].alternatetail = 0;
+ break;
+
+ default:
+ ast_log(LOG_WARNING,
+ "Unhandled control state keyword %s", cs_keywords[i]);
+ break;
+ }
+ }
+ }
+ }
+ vp = vp->next;
+ }
+ ast_mutex_unlock(&rpt_vars[n].lock);
+}
+
+/*
+* Enable or disable debug output at a given level at the console
+*/
+
+static int rpt_do_debug(int fd, int argc, char *argv[])
+{
+ int newlevel;
+
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+ newlevel = myatoi(argv[3]);
+ if((newlevel < 0) || (newlevel > 7))
+ return RESULT_SHOWUSAGE;
+ if(newlevel)
+ ast_cli(fd, "app_rpt Debugging enabled, previous level: %d, new level: %d\n", debug, newlevel);
+ else
+ ast_cli(fd, "app_rpt Debugging disabled\n");
+
+ debug = newlevel;
+ return RESULT_SUCCESS;
+}
+
+/*
+* Dump rpt struct debugging onto console
+*/
+
+static int rpt_do_dump(int fd, int argc, char *argv[])
+{
+ int i;
+
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+
+ for(i = 0; i < nrpts; i++)
+ {
+ if (!strcmp(argv[2],rpt_vars[i].name))
+ {
+ rpt_vars[i].disgorgetime = time(NULL) + 10; /* Do it 10 seconds later */
+ ast_cli(fd, "app_rpt struct dump requested for node %s\n",argv[2]);
+ return RESULT_SUCCESS;
+ }
+ }
+ return RESULT_FAILURE;
+}
+
+/*
+* Dump statistics onto console
+*/
+
+static int rpt_do_stats(int fd, int argc, char *argv[])
+{
+ int i,j;
+ int dailytxtime, dailykerchunks;
+ int totalkerchunks, dailykeyups, totalkeyups, timeouts;
+ int totalexecdcommands, dailyexecdcommands, hours, minutes, seconds;
+ long long totaltxtime;
+ struct rpt_link *l;
+ char *listoflinks[MAX_STAT_LINKS];
+ char *lastnodewhichkeyedusup, *lastdtmfcommand;
+ char *tot_state, *ider_state, *patch_state;
+ char *reverse_patch_state, *sys_ena, *tot_ena, *link_ena, *patch_ena;
+ char *sch_ena, *input_signal, *called_number, *user_funs, *tail_type;
+ struct rpt *myrpt;
+
+ static char *not_applicable = "N/A";
+
+ if(argc != 3)
+ return RESULT_SHOWUSAGE;
+
+ for(i = 0 ; i < MAX_STAT_LINKS; i++)
+ listoflinks[i] = NULL;
+
+ tot_state = ider_state =
+ patch_state = reverse_patch_state =
+ input_signal = called_number =
+ lastdtmfcommand = not_applicable;
+
+ for(i = 0; i < nrpts; i++)
+ {
+ if (!strcmp(argv[2],rpt_vars[i].name)){
+ /* Make a copy of all stat variables while locked */
+ myrpt = &rpt_vars[i];
+ rpt_mutex_lock(&myrpt->lock); /* LOCK */
+
+ dailytxtime = myrpt->dailytxtime;
+ totaltxtime = myrpt->totaltxtime;
+ dailykeyups = myrpt->dailykeyups;
+ totalkeyups = myrpt->totalkeyups;
+ dailykerchunks = myrpt->dailykerchunks;
+ totalkerchunks = myrpt->totalkerchunks;
+ dailyexecdcommands = myrpt->dailyexecdcommands;
+ totalexecdcommands = myrpt->totalexecdcommands;
+ timeouts = myrpt->timeouts;
+
+ /* Traverse the list of connected nodes */
+ reverse_patch_state = "DOWN";
+ j = 0;
+ l = myrpt->links.next;
+ while(l && (l != &myrpt->links)){
+ if (l->name[0] == '0'){ /* Skip '0' nodes */
+ reverse_patch_state = "UP";
+ l = l->next;
+ continue;
+ }
+ listoflinks[j] = ast_strdupa(l->name);
+ if(listoflinks[j])
+ j++;
+ l = l->next;
+ }
+
+ lastnodewhichkeyedusup = ast_strdupa(myrpt->lastnodewhichkeyedusup);
+ if((!lastnodewhichkeyedusup) || (!strlen(lastnodewhichkeyedusup)))
+ lastnodewhichkeyedusup = not_applicable;
+
+ if(myrpt->keyed)
+ input_signal = "YES";
+ else
+ input_signal = "NO";
+
+ if(myrpt->p.s[myrpt->p.sysstate_cur].txdisable)
+ sys_ena = "DISABLED";
+ else
+ sys_ena = "ENABLED";
+
+ if(myrpt->p.s[myrpt->p.sysstate_cur].totdisable)
+ tot_ena = "DISABLED";
+ else
+ tot_ena = "ENABLED";
+
+ if(myrpt->p.s[myrpt->p.sysstate_cur].linkfundisable)
+ link_ena = "DISABLED";
+ else
+ link_ena = "ENABLED";
+
+ if(myrpt->p.s[myrpt->p.sysstate_cur].autopatchdisable)
+ patch_ena = "DISABLED";
+ else
+ patch_ena = "ENABLED";
+
+ if(myrpt->p.s[myrpt->p.sysstate_cur].schedulerdisable)
+ sch_ena = "DISABLED";
+ else
+ sch_ena = "ENABLED";
+
+ if(myrpt->p.s[myrpt->p.sysstate_cur].userfundisable)
+ user_funs = "DISABLED";
+ else
+ user_funs = "ENABLED";
+
+ if(myrpt->p.s[myrpt->p.sysstate_cur].alternatetail)
+ tail_type = "ALTERNATE";
+ else
+ tail_type = "STANDARD";
+
+ if(!myrpt->totimer)
+ tot_state = "TIMED OUT!";
+ else if(myrpt->totimer != myrpt->p.totime)
+ tot_state = "ARMED";
+ else
+ tot_state = "RESET";
+
+ if(myrpt->tailid)
+ ider_state = "QUEUED IN TAIL";
+ else if(myrpt->mustid)
+ ider_state = "QUEUED FOR CLEANUP";
+ else
+ ider_state = "CLEAN";
+
+ switch(myrpt->callmode){
+ case 1:
+ patch_state = "DIALING";
+ break;
+ case 2:
+ patch_state = "CONNECTING";
+ break;
+ case 3:
+ patch_state = "UP";
+ break;
+
+ case 4:
+ patch_state = "CALL FAILED";
+ break;
+
+ default:
+ patch_state = "DOWN";
+ }
+
+ if(strlen(myrpt->exten)){
+ called_number = ast_strdupa(myrpt->exten);
+ if(!called_number)
+ called_number = not_applicable;
+ }
+
+ if(strlen(myrpt->lastdtmfcommand)){
+ lastdtmfcommand = ast_strdupa(myrpt->lastdtmfcommand);
+ if(!lastdtmfcommand)
+ lastdtmfcommand = not_applicable;
+ }
+
+ rpt_mutex_unlock(&myrpt->lock); /* UNLOCK */
+
+ ast_cli(fd, "************************ NODE %s STATISTICS *************************\n\n", myrpt->name);
+ ast_cli(fd, "Selected system state............................: %d\n", myrpt->p.sysstate_cur);
+ ast_cli(fd, "Signal on input..................................: %s\n", input_signal);
+ ast_cli(fd, "System...........................................: %s\n", sys_ena);
+ ast_cli(fd, "Scheduler........................................: %s\n", sch_ena);
+ ast_cli(fd, "Tail Time........................................: %s\n", tail_type);
+ ast_cli(fd, "Time out timer...................................: %s\n", tot_ena);
+ ast_cli(fd, "Time out timer state.............................: %s\n", tot_state);
+ ast_cli(fd, "Time outs since system initialization............: %d\n", timeouts);
+ ast_cli(fd, "Identifier state.................................: %s\n", ider_state);
+ ast_cli(fd, "Kerchunks today..................................: %d\n", dailykerchunks);
+ ast_cli(fd, "Kerchunks since system initialization............: %d\n", totalkerchunks);
+ ast_cli(fd, "Keyups today.....................................: %d\n", dailykeyups);
+ ast_cli(fd, "Keyups since system initialization...............: %d\n", totalkeyups);
+ ast_cli(fd, "DTMF commands today..............................: %d\n", dailyexecdcommands);
+ ast_cli(fd, "DTMF commands since system initialization........: %d\n", totalexecdcommands);
+ ast_cli(fd, "Last DTMF command executed.......................: %s\n", lastdtmfcommand);
+ hours = dailytxtime/3600000;
+ dailytxtime %= 3600000;
+ minutes = dailytxtime/60000;
+ dailytxtime %= 60000;
+ seconds = dailytxtime/1000;
+ dailytxtime %= 1000;
+
+ ast_cli(fd, "TX time today ...................................: %02d:%02d:%02d.%d\n",
+ hours, minutes, seconds, dailytxtime);
+
+ hours = (int) totaltxtime/3600000;
+ totaltxtime %= 3600000;
+ minutes = (int) totaltxtime/60000;
+ totaltxtime %= 60000;
+ seconds = (int) totaltxtime/1000;
+ totaltxtime %= 1000;
+
+ ast_cli(fd, "TX time since system initialization..............: %02d:%02d:%02d.%d\n",
+ hours, minutes, seconds, (int) totaltxtime);
+ ast_cli(fd, "Nodes currently connected to us..................: ");
+ for(j = 0 ;; j++){
+ if(!listoflinks[j]){
+ if(!j){
+ ast_cli(fd,"<NONE>");
+ }
+ break;
+ }
+ ast_cli(fd, "%s", listoflinks[j]);
+ if(j % 4 == 3){
+ ast_cli(fd, "\n");
+ ast_cli(fd, " : ");
+ }
+ else{
+ if(listoflinks[j + 1])
+ ast_cli(fd, ", ");
+ }
+ }
+ ast_cli(fd,"\n");
+
+ ast_cli(fd, "Last node which transmitted to us................: %s\n", lastnodewhichkeyedusup);
+ ast_cli(fd, "Autopatch........................................: %s\n", patch_ena);
+ ast_cli(fd, "Autopatch state..................................: %s\n", patch_state);
+ ast_cli(fd, "Autopatch called number..........................: %s\n", called_number);
+ ast_cli(fd, "Reverse patch/IAXRPT connected...................: %s\n", reverse_patch_state);
+ ast_cli(fd, "User linking commands............................: %s\n", link_ena);
+ ast_cli(fd, "User functions...................................: %s\n\n", user_funs);
+ return RESULT_SUCCESS;
+ }
+ }
+ return RESULT_FAILURE;
+}
+
+/*
+* Link stats function
+*/
+
+static int rpt_do_lstats(int fd, int argc, char *argv[])
+{
+ int i,j;
+ char *connstate;
+ struct rpt *myrpt;
+ struct rpt_link *l;
+ struct rpt_lstat *s,*t;
+ struct rpt_lstat s_head;
+ if(argc != 3)
+ return RESULT_SHOWUSAGE;
+
+ s = NULL;
+ s_head.next = &s_head;
+ s_head.prev = &s_head;
+
+ for(i = 0; i < nrpts; i++)
+ {
+ if (!strcmp(argv[2],rpt_vars[i].name)){
+ /* Make a copy of all stat variables while locked */
+ myrpt = &rpt_vars[i];
+ rpt_mutex_lock(&myrpt->lock); /* LOCK */
+ /* Traverse the list of connected nodes */
+ j = 0;
+ l = myrpt->links.next;
+ while(l && (l != &myrpt->links)){
+ if (l->name[0] == '0'){ /* Skip '0' nodes */
+ l = l->next;
+ continue;
+ }
+ if((s = (struct rpt_lstat *) malloc(sizeof(struct rpt_lstat))) == NULL){
+ ast_log(LOG_ERROR, "Malloc failed in rpt_do_lstats\n");
+ rpt_mutex_unlock(&myrpt->lock); /* UNLOCK */
+ return RESULT_FAILURE;
+ }
+ memset(s, 0, sizeof(struct rpt_lstat));
+ strncpy(s->name, l->name, MAXREMSTR - 1);
+ if (l->chan) pbx_substitute_variables_helper(l->chan, "${IAXPEER(CURRENTCHANNEL)}", s->peer, MAXPEERSTR - 1);
+ else strcpy(s->peer,"(none)");
+ s->mode = l->mode;
+ s->outbound = l->outbound;
+ s->reconnects = l->reconnects;
+ s->connecttime = l->connecttime;
+ s->thisconnected = l->thisconnected;
+ memcpy(s->chan_stat,l->chan_stat,NRPTSTAT * sizeof(struct rpt_chan_stat));
+ insque((struct qelem *) s, (struct qelem *) s_head.next);
+ memset(l->chan_stat,0,NRPTSTAT * sizeof(struct rpt_chan_stat));
+ l = l->next;
+ }
+ rpt_mutex_unlock(&myrpt->lock); /* UNLOCK */
+ ast_cli(fd, "NODE PEER RECONNECTS DIRECTION CONNECT TIME CONNECT STATE\n");
+ ast_cli(fd, "---- ---- ---------- --------- ------------ -------------\n");
+
+ for(s = s_head.next; s != &s_head; s = s->next){
+ int hours, minutes, seconds;
+ long long connecttime = s->connecttime;
+ char conntime[21];
+ hours = (int) connecttime/3600000;
+ connecttime %= 3600000;
+ minutes = (int) connecttime/60000;
+ connecttime %= 60000;
+ seconds = (int) connecttime/1000;
+ connecttime %= 1000;
+ snprintf(conntime, 20, "%02d:%02d:%02d.%d",
+ hours, minutes, seconds, (int) connecttime);
+ conntime[20] = 0;
+ if(s->thisconnected)
+ connstate = "ESTABLISHED";
+ else
+ connstate = "CONNECTING";
+ ast_cli(fd, "%-10s%-20s%-12d%-11s%-20s%-20s\n",
+ s->name, s->peer, s->reconnects, (s->outbound)? "OUT":"IN", conntime, connstate);
+ }
+ /* destroy our local link queue */
+ s = s_head.next;
+ while(s != &s_head){
+ t = s;
+ s = s->next;
+ remque((struct qelem *)t);
+ free(t);
+ }
+ return RESULT_SUCCESS;
+ }
+ }
+ return RESULT_FAILURE;
+}
+
+/*
+* List all nodes connected, directly or indirectly
+*/
+
+static int rpt_do_nodes(int fd, int argc, char *argv[])
+{
+ int i,j;
+ char ns;
+ char lbuf[MAXLINKLIST],*strs[MAXLINKLIST];
+ struct rpt *myrpt;
+ if(argc != 3)
+ return RESULT_SHOWUSAGE;
+
+ for(i = 0; i < nrpts; i++)
+ {
+ if (!strcmp(argv[2],rpt_vars[i].name)){
+ /* Make a copy of all stat variables while locked */
+ myrpt = &rpt_vars[i];
+ rpt_mutex_lock(&myrpt->lock); /* LOCK */
+ __mklinklist(myrpt,NULL,lbuf);
+ rpt_mutex_unlock(&myrpt->lock); /* UNLOCK */
+ /* parse em */
+ ns = finddelim(lbuf,strs,MAXLINKLIST);
+ /* sort em */
+ if (ns) qsort((void *)strs,ns,sizeof(char *),mycompar);
+ ast_cli(fd,"\n");
+ ast_cli(fd, "************************* CONNECTED NODES *************************\n\n");
+ for(j = 0 ;; j++){
+ if(!strs[j]){
+ if(!j){
+ ast_cli(fd,"<NONE>");
+ }
+ break;
+ }
+ ast_cli(fd, "%s", strs[j]);
+ if(j % 8 == 7){
+ ast_cli(fd, "\n");
+ }
+ else{
+ if(strs[j + 1])
+ ast_cli(fd, ", ");
+ }
+ }
+ ast_cli(fd,"\n\n");
+ return RESULT_SUCCESS;
+ }
+ }
+ return RESULT_FAILURE;
+}
+
+/*
+* reload vars
+*/
+
+static int rpt_do_reload(int fd, int argc, char *argv[])
+{
+int n;
+
+ if (argc > 2) return RESULT_SHOWUSAGE;
+
+ for(n = 0; n < nrpts; n++) rpt_vars[n].reload = 1;
+
+ return RESULT_FAILURE;
+}
+
+/*
+* restart app_rpt
+*/
+
+static int rpt_do_restart(int fd, int argc, char *argv[])
+{
+int i;
+
+ if (argc > 2) return RESULT_SHOWUSAGE;
+ for(i = 0; i < nrpts; i++)
+ {
+ if (rpt_vars[i].rxchannel) ast_softhangup(rpt_vars[i].rxchannel,AST_SOFTHANGUP_DEV);
+ }
+ return RESULT_FAILURE;
+}
+
+
+/*
+* send an app_rpt DTMF function from the CLI
+*/
+
+static int rpt_do_fun(int fd, int argc, char *argv[])
+{
+ int i,busy=0;
+
+ if (argc != 4) return RESULT_SHOWUSAGE;
+
+ for(i = 0; i < nrpts; i++){
+ if(!strcmp(argv[2], rpt_vars[i].name)){
+ struct rpt *myrpt = &rpt_vars[i];
+ rpt_mutex_lock(&myrpt->lock);
+ if ((MAXMACRO - strlen(myrpt->macrobuf)) < strlen(argv[3])){
+ rpt_mutex_unlock(&myrpt->lock);
+ busy=1;
+ }
+ if(!busy){
+ myrpt->macrotimer = MACROTIME;
+ strncat(myrpt->macrobuf, argv[3], MAXMACRO - strlen(myrpt->macrobuf) - 1);
+ }
+ rpt_mutex_unlock(&myrpt->lock);
+ }
+ }
+ if(busy){
+ ast_cli(fd, "Function decoder busy");
+ }
+ return RESULT_FAILURE;
+}
+
+
+
+static int play_tone_pair(struct ast_channel *chan, int f1, int f2, int duration, int amplitude)
+{
+ int res;
+
+ if ((res = ast_tonepair_start(chan, f1, f2, duration, amplitude)))
+ return res;
+
+ while(chan->generatordata) {
+ if (ast_safe_sleep(chan,1)) return -1;
+ }
+
+ return 0;
+}
+
+static int play_tone(struct ast_channel *chan, int freq, int duration, int amplitude)
+{
+ return play_tone_pair(chan, freq, 0, duration, amplitude);
+}
+
+static int play_silence(struct ast_channel *chan, int duration)
+{
+ return play_tone_pair(chan, 0, 0, duration, 0);
+}
+
+
+static int send_morse(struct ast_channel *chan, char *string, int speed, int freq, int amplitude)
+{
+
+static struct morse_bits mbits[] = {
+ {0, 0}, /* SPACE */
+ {0, 0},
+ {6, 18},/* " */
+ {0, 0},
+ {7, 72},/* $ */
+ {0, 0},
+ {0, 0},
+ {6, 30},/* ' */
+ {5, 13},/* ( */
+ {6, 29},/* ) */
+ {0, 0},
+ {5, 10},/* + */
+ {6, 51},/* , */
+ {6, 33},/* - */
+ {6, 42},/* . */
+ {5, 9}, /* / */
+ {5, 31},/* 0 */
+ {5, 30},/* 1 */
+ {5, 28},/* 2 */
+ {5, 24},/* 3 */
+ {5, 16},/* 4 */
+ {5, 0}, /* 5 */
+ {5, 1}, /* 6 */
+ {5, 3}, /* 7 */
+ {5, 7}, /* 8 */
+ {5, 15},/* 9 */
+ {6, 7}, /* : */
+ {6, 21},/* ; */
+ {0, 0},
+ {5, 33},/* = */
+ {0, 0},
+ {6, 12},/* ? */
+ {0, 0},
+ {2, 2}, /* A */
+ {4, 1}, /* B */
+ {4, 5}, /* C */
+ {3, 1}, /* D */
+ {1, 0}, /* E */
+ {4, 4}, /* F */
+ {3, 3}, /* G */
+ {4, 0}, /* H */
+ {2, 0}, /* I */
+ {4, 14},/* J */
+ {3, 5}, /* K */
+ {4, 2}, /* L */
+ {2, 3}, /* M */
+ {2, 1}, /* N */
+ {3, 7}, /* O */
+ {4, 6}, /* P */
+ {4, 11},/* Q */
+ {3, 2}, /* R */
+ {3, 0}, /* S */
+ {1, 1}, /* T */
+ {3, 4}, /* U */
+ {4, 8}, /* V */
+ {3, 6}, /* W */
+ {4, 9}, /* X */
+ {4, 13},/* Y */
+ {4, 3} /* Z */
+ };
+
+
+ int dottime;
+ int dashtime;
+ int intralettertime;
+ int interlettertime;
+ int interwordtime;
+ int len, ddcomb;
+ int res;
+ int c;
+ int i;
+ int flags;
+
+ res = 0;
+
+ /* Approximate the dot time from the speed arg. */
+
+ dottime = 900/speed;
+
+ /* Establish timing releationships */
+
+ dashtime = 3 * dottime;
+ intralettertime = dottime;
+ interlettertime = dottime * 4 ;
+ interwordtime = dottime * 7;
+
+ for(;(*string) && (!res); string++){
+
+ c = *string;
+
+ /* Convert lower case to upper case */
+
+ if((c >= 'a') && (c <= 'z'))
+ c -= 0x20;
+
+ /* Can't deal with any char code greater than Z, skip it */
+
+ if(c > 'Z')
+ continue;
+
+ /* If space char, wait the inter word time */
+
+ if(c == ' '){
+ if(!res)
+ res = play_silence(chan, interwordtime);
+ continue;
+ }
+
+ /* Subtract out control char offset to match our table */
+
+ c -= 0x20;
+
+ /* Get the character data */
+
+ len = mbits[c].len;
+ ddcomb = mbits[c].ddcomb;
+
+ /* Send the character */
+
+ for(; len ; len--){
+ if(!res)
+ res = play_tone(chan, freq, (ddcomb & 1) ? dashtime : dottime, amplitude);
+ if(!res)
+ res = play_silence(chan, intralettertime);
+ ddcomb >>= 1;
+ }
+
+ /* Wait the interletter time */
+
+ if(!res)
+ res = play_silence(chan, interlettertime - intralettertime);
+ }
+
+ /* Wait for all the frames to be sent */
+
+ if (!res)
+ res = ast_waitstream(chan, "");
+ ast_stopstream(chan);
+
+ /*
+ * Wait for the zaptel driver to physically write the tone blocks to the hardware
+ */
+
+ for(i = 0; i < 20 ; i++){
+ flags = DAHDI_IOMUX_WRITEEMPTY | DAHDI_IOMUX_NOWAIT;
+ res = ioctl(chan->fds[0], DAHDI_IOMUX, &flags);
+ if(flags & DAHDI_IOMUX_WRITEEMPTY)
+ break;
+ if( ast_safe_sleep(chan, 50)){
+ res = -1;
+ break;
+ }
+ }
+
+
+ return res;
+}
+
+static int send_tone_telemetry(struct ast_channel *chan, char *tonestring)
+{
+ char *stringp;
+ char *tonesubset;
+ int f1,f2;
+ int duration;
+ int amplitude;
+ int res;
+ int i;
+ int flags;
+
+ res = 0;
+
+ stringp = ast_strdupa(tonestring);
+
+ for(;tonestring;){
+ tonesubset = strsep(&stringp,")");
+ if(!tonesubset)
+ break;
+ if(sscanf(tonesubset,"(%d,%d,%d,%d", &f1, &f2, &duration, &amplitude) != 4)
+ break;
+ res = play_tone_pair(chan, f1, f2, duration, amplitude);
+ if(res)
+ break;
+ }
+ if(!res)
+ res = play_tone_pair(chan, 0, 0, 100, 0); /* This is needed to ensure the last tone segment is timed correctly */
+
+ if (!res)
+ res = ast_waitstream(chan, "");
+ ast_stopstream(chan);
+
+ /*
+ * Wait for the zaptel driver to physically write the tone blocks to the hardware
+ */
+
+ for(i = 0; i < 20 ; i++){
+ flags = DAHDI_IOMUX_WRITEEMPTY | DAHDI_IOMUX_NOWAIT;
+ res = ioctl(chan->fds[0], DAHDI_IOMUX, &flags);
+ if(flags & DAHDI_IOMUX_WRITEEMPTY)
+ break;
+ if( ast_safe_sleep(chan, 50)){
+ res = -1;
+ break;
+ }
+ }
+
+ return res;
+
+}
+
+static int sayfile(struct ast_channel *mychannel,char *fname)
+{
+int res;
+
+ res = ast_streamfile(mychannel, fname, mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ return res;
+}
+
+static int saycharstr(struct ast_channel *mychannel,char *str)
+{
+int res;
+
+ res = ast_say_character_str(mychannel,str,NULL,mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ return res;
+}
+
+static int saynum(struct ast_channel *mychannel, int num)
+{
+ int res;
+ res = ast_say_number(mychannel, num, NULL, mychannel->language, NULL);
+ if(!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ return res;
+}
+
+
+static int telem_any(struct rpt *myrpt,struct ast_channel *chan, char *entry)
+{
+ int res;
+ char c;
+
+ static int morsespeed;
+ static int morsefreq;
+ static int morseampl;
+ static int morseidfreq = 0;
+ static int morseidampl;
+ static char mcat[] = MORSE;
+
+ res = 0;
+
+ if(!morseidfreq){ /* Get the morse parameters if not already loaded */
+ morsespeed = retrieve_astcfgint(myrpt, mcat, "speed", 5, 20, 20);
+ morsefreq = retrieve_astcfgint(myrpt, mcat, "frequency", 300, 3000, 800);
+ morseampl = retrieve_astcfgint(myrpt, mcat, "amplitude", 200, 8192, 4096);
+ morseidampl = retrieve_astcfgint(myrpt, mcat, "idamplitude", 200, 8192, 2048);
+ morseidfreq = retrieve_astcfgint(myrpt, mcat, "idfrequency", 300, 3000, 330);
+ }
+
+ /* Is it a file, or a tone sequence? */
+
+ if(entry[0] == '|'){
+ c = entry[1];
+ if((c >= 'a')&&(c <= 'z'))
+ c -= 0x20;
+
+ switch(c){
+ case 'I': /* Morse ID */
+ res = send_morse(chan, entry + 2, morsespeed, morseidfreq, morseidampl);
+ break;
+
+ case 'M': /* Morse Message */
+ res = send_morse(chan, entry + 2, morsespeed, morsefreq, morseampl);
+ break;
+
+ case 'T': /* Tone sequence */
+ res = send_tone_telemetry(chan, entry + 2);
+ break;
+ default:
+ res = -1;
+ }
+ }
+ else
+ res = sayfile(chan, entry); /* File */
+ return res;
+}
+
+/*
+* This function looks up a telemetry name in the config file, and does a telemetry response as configured.
+*
+* 4 types of telemtry are handled: Morse ID, Morse Message, Tone Sequence, and a File containing a recording.
+*/
+
+static int telem_lookup(struct rpt *myrpt,struct ast_channel *chan, char *node, char *name)
+{
+
+ int res;
+ int i;
+ char *entry;
+ char *telemetry;
+ char *telemetry_save;
+
+ res = 0;
+ telemetry_save = NULL;
+ entry = NULL;
+
+ /* Retrieve the section name for telemetry from the node section */
+ telemetry = (char *) ast_variable_retrieve(myrpt->cfg, node, TELEMETRY);
+ if(telemetry ){
+ telemetry_save = ast_strdupa(telemetry);
+ if(!telemetry_save){
+ ast_log(LOG_WARNING,"ast_strdupa() failed in telem_lookup()\n");
+ return res;
+ }
+ entry = (char *) ast_variable_retrieve(myrpt->cfg, telemetry_save, name);
+ }
+
+ /* Try to look up the telemetry name */
+
+ if(!entry){
+ /* Telemetry name wasn't found in the config file, use the default */
+ for(i = 0; i < sizeof(tele_defs)/sizeof(struct telem_defaults) ; i++){
+ if(!strcasecmp(tele_defs[i].name, name))
+ entry = tele_defs[i].value;
+ }
+ }
+ if(entry){
+ if(strlen(entry))
+ telem_any(myrpt,chan, entry);
+ }
+ else{
+ res = -1;
+ }
+ return res;
+}
+
+/*
+* Retrieve a wait interval
+*/
+
+static int get_wait_interval(struct rpt *myrpt, int type)
+{
+ int interval;
+ char *wait_times;
+ char *wait_times_save;
+
+ wait_times_save = NULL;
+ wait_times = (char *) ast_variable_retrieve(myrpt->cfg, myrpt->name, "wait_times");
+
+ if(wait_times){
+ wait_times_save = ast_strdupa(wait_times);
+ if(!wait_times_save){
+ ast_log(LOG_WARNING, "Out of memory in wait_interval()\n");
+ wait_times = NULL;
+ }
+ }
+
+ switch(type){
+ case DLY_TELEM:
+ if(wait_times)
+ interval = retrieve_astcfgint(myrpt,wait_times_save, "telemwait", 500, 5000, 1000);
+ else
+ interval = 1000;
+ break;
+
+ case DLY_ID:
+ if(wait_times)
+ interval = retrieve_astcfgint(myrpt,wait_times_save, "idwait",250,5000,500);
+ else
+ interval = 500;
+ break;
+
+ case DLY_UNKEY:
+ if(wait_times)
+ interval = retrieve_astcfgint(myrpt,wait_times_save, "unkeywait",500,5000,1000);
+ else
+ interval = 1000;
+ break;
+
+ case DLY_LINKUNKEY:
+ if(wait_times)
+ interval = retrieve_astcfgint(myrpt,wait_times_save, "linkunkeywait",500,5000,1000);
+ else
+ interval = 1000;
+ break;
+
+ case DLY_CALLTERM:
+ if(wait_times)
+ interval = retrieve_astcfgint(myrpt,wait_times_save, "calltermwait",500,5000,1500);
+ else
+ interval = 1500;
+ break;
+
+ case DLY_COMP:
+ if(wait_times)
+ interval = retrieve_astcfgint(myrpt,wait_times_save, "compwait",500,5000,200);
+ else
+ interval = 200;
+ break;
+
+ default:
+ return 0;
+ }
+ return interval;
+}
+
+
+/*
+* Wait a configurable interval of time
+*/
+
+
+static void wait_interval(struct rpt *myrpt, int type, struct ast_channel *chan)
+{
+ int interval;
+ interval = get_wait_interval(myrpt, type);
+ if(debug)
+ ast_log(LOG_NOTICE," Delay interval = %d\n", interval);
+ if(interval)
+ ast_safe_sleep(chan,interval);
+ if(debug)
+ ast_log(LOG_NOTICE,"Delay complete\n");
+ return;
+}
+
+static int split_freq(char *mhz, char *decimals, char *freq);
+
+static void *rpt_tele_thread(void *this)
+{
+struct dahdi_confinfo ci; /* conference info */
+int res = 0,haslink,hastx,hasremote,imdone = 0, unkeys_queued, x;
+struct rpt_tele *mytele = (struct rpt_tele *)this;
+struct rpt_tele *tlist;
+struct rpt *myrpt;
+struct rpt_link *l,*l1,linkbase;
+struct ast_channel *mychannel;
+int vmajor, vminor, m;
+char *p,*ct,*ct_copy,*ident, *nodename,*cp;
+time_t t;
+struct tm localtm;
+char lbuf[MAXLINKLIST],*strs[MAXLINKLIST];
+int i,ns,rbimode;
+char mhz[MAXREMSTR];
+char decimals[MAXREMSTR];
+struct dahdi_params par;
+
+
+ /* get a pointer to myrpt */
+ myrpt = mytele->rpt;
+
+ /* Snag copies of a few key myrpt variables */
+ rpt_mutex_lock(&myrpt->lock);
+ nodename = ast_strdupa(myrpt->name);
+ if (myrpt->p.ident) ident = ast_strdupa(myrpt->p.ident);
+ else ident = "";
+ rpt_mutex_unlock(&myrpt->lock);
+
+ /* allocate a pseudo-channel thru asterisk */
+ mychannel = ast_request("zap",AST_FORMAT_SLINEAR,"pseudo",NULL);
+ if (!mychannel)
+ {
+ fprintf(stderr,"rpt:Sorry unable to obtain pseudo channel\n");
+ rpt_mutex_lock(&myrpt->lock);
+ remque((struct qelem *)mytele);
+ ast_log(LOG_NOTICE,"Telemetry thread aborted at line %d, mode: %d\n",__LINE__, mytele->mode); /*@@@@@@@@@@@*/
+ rpt_mutex_unlock(&myrpt->lock);
+ free(mytele);
+ pthread_exit(NULL);
+ }
+#ifdef AST_CDR_FLAG_POST_DISABLED
+ ast_set_flag(mychannel->cdr,AST_CDR_FLAG_POST_DISABLED);
+#endif
+ rpt_mutex_lock(&myrpt->lock);
+ mytele->chan = mychannel;
+ rpt_mutex_unlock(&myrpt->lock);
+ /* make a conference for the tx */
+ ci.chan = 0;
+ /* If there's an ID queued, or tail message queued, */
+ /* only connect the ID audio to the local tx conference so */
+ /* linked systems can't hear it */
+ ci.confno = (((mytele->mode == ID) || (mytele->mode == IDTALKOVER) || (mytele->mode == UNKEY) ||
+ (mytele->mode == TAILMSG) || (mytele->mode == LINKUNKEY)) || (mytele->mode == TIMEOUT) ?
+ myrpt->txconf : myrpt->conf);
+ ci.confmode = DAHDI_CONF_CONFANN;
+ /* first put the channel on the conference in announce mode */
+ if (ioctl(mychannel->fds[0],DAHDI_SETCONF,&ci) == -1)
+ {
+ ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n");
+ rpt_mutex_lock(&myrpt->lock);
+ remque((struct qelem *)mytele);
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_log(LOG_NOTICE,"Telemetry thread aborted at line %d, mode: %d\n",__LINE__, mytele->mode); /*@@@@@@@@@@@*/
+ free(mytele);
+ ast_hangup(mychannel);
+ pthread_exit(NULL);
+ }
+ ast_stopstream(mychannel);
+ switch(mytele->mode)
+ {
+ case ID:
+ case ID1:
+ /* wait a bit */
+ wait_interval(myrpt, (mytele->mode == ID) ? DLY_ID : DLY_TELEM,mychannel);
+ res = telem_any(myrpt,mychannel, ident);
+ imdone=1;
+ break;
+
+ case TAILMSG:
+ res = ast_streamfile(mychannel, myrpt->p.tailmessages[myrpt->tailmessagen], mychannel->language);
+ break;
+
+ case IDTALKOVER:
+ p = (char *) ast_variable_retrieve(myrpt->cfg, nodename, "idtalkover");
+ if(p)
+ res = telem_any(myrpt,mychannel, p);
+ imdone=1;
+ break;
+
+ case PROC:
+ /* wait a little bit longer */
+ wait_interval(myrpt, DLY_TELEM, mychannel);
+ res = telem_lookup(myrpt, mychannel, myrpt->name, "patchup");
+ if(res < 0){ /* Then default message */
+ res = ast_streamfile(mychannel, "rpt/callproceeding", mychannel->language);
+ }
+ break;
+ case TERM:
+ /* wait a little bit longer */
+ wait_interval(myrpt, DLY_CALLTERM, mychannel);
+ res = telem_lookup(myrpt, mychannel, myrpt->name, "patchdown");
+ if(res < 0){ /* Then default message */
+ res = ast_streamfile(mychannel, "rpt/callterminated", mychannel->language);
+ }
+ break;
+ case COMPLETE:
+ /* wait a little bit */
+ wait_interval(myrpt, DLY_TELEM, mychannel);
+ res = telem_lookup(myrpt,mychannel, myrpt->name, "functcomplete");
+ break;
+ case MACRO_NOTFOUND:
+ /* wait a little bit */
+ wait_interval(myrpt, DLY_TELEM, mychannel);
+ res = ast_streamfile(mychannel, "rpt/macro_notfound", mychannel->language);
+ break;
+ case MACRO_BUSY:
+ /* wait a little bit */
+ wait_interval(myrpt, DLY_TELEM, mychannel);
+ res = ast_streamfile(mychannel, "rpt/macro_busy", mychannel->language);
+ break;
+ case UNKEY:
+ if(myrpt->patchnoct && myrpt->callmode){ /* If no CT during patch configured, then don't send one */
+ imdone = 1;
+ break;
+ }
+
+ /*
+ * Reset the Unkey to CT timer
+ */
+
+ x = get_wait_interval(myrpt, DLY_UNKEY);
+ rpt_mutex_lock(&myrpt->lock);
+ myrpt->unkeytocttimer = x; /* Must be protected as it is changed below */
+ rpt_mutex_unlock(&myrpt->lock);
+
+ /*
+ * If there's one already queued, don't do another
+ */
+
+ tlist = myrpt->tele.next;
+ unkeys_queued = 0;
+ if (tlist != &myrpt->tele)
+ {
+ rpt_mutex_lock(&myrpt->lock);
+ while(tlist != &myrpt->tele){
+ if (tlist->mode == UNKEY) unkeys_queued++;
+ tlist = tlist->next;
+ }
+ rpt_mutex_unlock(&myrpt->lock);
+ }
+ if( unkeys_queued > 1){
+ imdone = 1;
+ break;
+ }
+
+ /* Wait for the telemetry timer to expire */
+ /* Periodically check the timer since it can be re-initialized above */
+ while(myrpt->unkeytocttimer)
+ {
+ int ctint;
+ if(myrpt->unkeytocttimer > 100)
+ ctint = 100;
+ else
+ ctint = myrpt->unkeytocttimer;
+ ast_safe_sleep(mychannel, ctint);
+ rpt_mutex_lock(&myrpt->lock);
+ if(myrpt->unkeytocttimer < ctint)
+ myrpt->unkeytocttimer = 0;
+ else
+ myrpt->unkeytocttimer -= ctint;
+ rpt_mutex_unlock(&myrpt->lock);
+ }
+
+ /*
+ * Now, the carrier on the rptr rx should be gone.
+ * If it re-appeared, then forget about sending the CT
+ */
+ if(myrpt->keyed){
+ imdone = 1;
+ break;
+ }
+
+ rpt_mutex_lock(&myrpt->lock); /* Update the kerchunk counters */
+ myrpt->dailykerchunks++;
+ myrpt->totalkerchunks++;
+ rpt_mutex_unlock(&myrpt->lock);
+
+ haslink = 0;
+ hastx = 0;
+ hasremote = 0;
+ l = myrpt->links.next;
+ if (l != &myrpt->links)
+ {
+ rpt_mutex_lock(&myrpt->lock);
+ while(l != &myrpt->links)
+ {
+ if (l->name[0] == '0')
+ {
+ l = l->next;
+ continue;
+ }
+ haslink = 1;
+ if (l->mode) {
+ hastx++;
+ if (l->isremote) hasremote++;
+ }
+ l = l->next;
+ }
+ rpt_mutex_unlock(&myrpt->lock);
+ }
+ if (haslink)
+ {
+
+ res = telem_lookup(myrpt,mychannel, myrpt->name, (!hastx) ? "remotemon" : "remotetx");
+ if(res)
+ ast_log(LOG_WARNING, "telem_lookup:remotexx failed on %s\n", mychannel->name);
+
+
+ /* if in remote cmd mode, indicate it */
+ if (myrpt->cmdnode[0])
+ {
+ ast_safe_sleep(mychannel,200);
+ res = telem_lookup(myrpt,mychannel, myrpt->name, "cmdmode");
+ if(res)
+ ast_log(LOG_WARNING, "telem_lookup:cmdmode failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ }
+ }
+ else if((ct = (char *) ast_variable_retrieve(myrpt->cfg, nodename, "unlinkedct"))){ /* Unlinked Courtesy Tone */
+ ct_copy = ast_strdupa(ct);
+ res = telem_lookup(myrpt,mychannel, myrpt->name, ct_copy);
+ if(res)
+ ast_log(LOG_WARNING, "telem_lookup:ctx failed on %s\n", mychannel->name);
+ }
+ if (hasremote && (!myrpt->cmdnode[0]))
+ {
+ /* set for all to hear */
+ ci.chan = 0;
+ ci.confno = myrpt->conf;
+ ci.confmode = DAHDI_CONF_CONFANN;
+ /* first put the channel on the conference in announce mode */
+ if (ioctl(mychannel->fds[0],DAHDI_SETCONF,&ci) == -1)
+ {
+ ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n");
+ rpt_mutex_lock(&myrpt->lock);
+ remque((struct qelem *)mytele);
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_log(LOG_NOTICE,"Telemetry thread aborted at line %d, mode: %d\n",__LINE__, mytele->mode); /*@@@@@@@@@@@*/
+ free(mytele);
+ ast_hangup(mychannel);
+ pthread_exit(NULL);
+ }
+ if((ct = (char *) ast_variable_retrieve(myrpt->cfg, nodename, "remotect"))){ /* Unlinked Courtesy Tone */
+ ast_safe_sleep(mychannel,200);
+ ct_copy = ast_strdupa(ct);
+ res = telem_lookup(myrpt,mychannel, myrpt->name, ct_copy);
+ if(res)
+ ast_log(LOG_WARNING, "telem_lookup:ctx failed on %s\n", mychannel->name);
+ }
+ }
+#if defined(_MDC_DECODE_H_) && defined(MDC_SAY_WHEN_DOING_CT)
+ if (myrpt->lastunit)
+ {
+ char mystr[10];
+
+ ast_safe_sleep(mychannel,200);
+ /* set for all to hear */
+ ci.chan = 0;
+ ci.confno = myrpt->txconf;
+ ci.confmode = DAHDI_CONF_CONFANN;
+ /* first put the channel on the conference in announce mode */
+ if (ioctl(mychannel->fds[0],DAHDI_SETCONF,&ci) == -1)
+ {
+ ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n");
+ rpt_mutex_lock(&myrpt->lock);
+ remque((struct qelem *)mytele);
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_log(LOG_NOTICE,"Telemetry thread aborted at line %d, mode: %d\n",__LINE__, mytele->mode); /*@@@@@@@@@@@*/
+ free(mytele);
+ ast_hangup(mychannel);
+ pthread_exit(NULL);
+ }
+ sprintf(mystr,"%04x",myrpt->lastunit);
+ myrpt->lastunit = 0;
+ ast_say_character_str(mychannel,mystr,NULL,mychannel->language);
+ break;
+ }
+#endif
+ imdone = 1;
+ break;
+ case LINKUNKEY:
+ if(myrpt->patchnoct && myrpt->callmode){ /* If no CT during patch configured, then don't send one */
+ imdone = 1;
+ break;
+ }
+
+ /*
+ * Reset the Unkey to CT timer
+ */
+
+ x = get_wait_interval(myrpt, DLY_LINKUNKEY);
+ mytele->mylink.linkunkeytocttimer = x; /* Must be protected as it is changed below */
+
+ /*
+ * If there's one already queued, don't do another
+ */
+
+ tlist = myrpt->tele.next;
+ unkeys_queued = 0;
+ if (tlist != &myrpt->tele)
+ {
+ rpt_mutex_lock(&myrpt->lock);
+ while(tlist != &myrpt->tele){
+ if (tlist->mode == LINKUNKEY) unkeys_queued++;
+ tlist = tlist->next;
+ }
+ rpt_mutex_unlock(&myrpt->lock);
+ }
+ if( unkeys_queued > 1){
+ imdone = 1;
+ break;
+ }
+
+ /* Wait for the telemetry timer to expire */
+ /* Periodically check the timer since it can be re-initialized above */
+ while(mytele->mylink.linkunkeytocttimer)
+ {
+ int ctint;
+ if(mytele->mylink.linkunkeytocttimer > 100)
+ ctint = 100;
+ else
+ ctint = mytele->mylink.linkunkeytocttimer;
+ ast_safe_sleep(mychannel, ctint);
+ rpt_mutex_lock(&myrpt->lock);
+ if(mytele->mylink.linkunkeytocttimer < ctint)
+ mytele->mylink.linkunkeytocttimer = 0;
+ else
+ mytele->mylink.linkunkeytocttimer -= ctint;
+ rpt_mutex_unlock(&myrpt->lock);
+ }
+
+ if((ct = (char *) ast_variable_retrieve(myrpt->cfg, nodename, "linkunkeyct"))){ /* Unlinked Courtesy Tone */
+ ct_copy = ast_strdupa(ct);
+ res = telem_lookup(myrpt,mychannel, myrpt->name, ct_copy);
+ if(res)
+ ast_log(LOG_WARNING, "telem_lookup:ctx failed on %s\n", mychannel->name);
+ }
+ imdone = 1;
+ break;
+ case REMDISC:
+ /* wait a little bit */
+ wait_interval(myrpt, DLY_TELEM, mychannel);
+ l = myrpt->links.next;
+ haslink = 0;
+ /* dont report if a link for this one still on system */
+ if (l != &myrpt->links)
+ {
+ rpt_mutex_lock(&myrpt->lock);
+ while(l != &myrpt->links)
+ {
+ if (l->name[0] == '0')
+ {
+ l = l->next;
+ continue;
+ }
+ if (!strcmp(l->name,mytele->mylink.name))
+ {
+ haslink = 1;
+ break;
+ }
+ l = l->next;
+ }
+ rpt_mutex_unlock(&myrpt->lock);
+ }
+ if (haslink)
+ {
+ imdone = 1;
+ break;
+ }
+ res = ast_streamfile(mychannel, "rpt/node", mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ ast_say_character_str(mychannel,mytele->mylink.name,NULL,mychannel->language);
+ res = ast_streamfile(mychannel, ((mytele->mylink.hasconnected) ?
+ "rpt/remote_disc" : "rpt/remote_busy"), mychannel->language);
+ break;
+ case REMALREADY:
+ /* wait a little bit */
+ wait_interval(myrpt, DLY_TELEM, mychannel);
+ res = ast_streamfile(mychannel, "rpt/remote_already", mychannel->language);
+ break;
+ case REMNOTFOUND:
+ /* wait a little bit */
+ wait_interval(myrpt, DLY_TELEM, mychannel);
+ res = ast_streamfile(mychannel, "rpt/remote_notfound", mychannel->language);
+ break;
+ case REMGO:
+ /* wait a little bit */
+ wait_interval(myrpt, DLY_TELEM, mychannel);
+ res = ast_streamfile(mychannel, "rpt/remote_go", mychannel->language);
+ break;
+ case CONNECTED:
+ /* wait a little bit */
+ wait_interval(myrpt, DLY_TELEM, mychannel);
+ res = ast_streamfile(mychannel, "rpt/node", mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ ast_say_character_str(mychannel,mytele->mylink.name,NULL,mychannel->language);
+ res = ast_streamfile(mychannel, "rpt/connected", mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ res = ast_streamfile(mychannel, "digits/2", mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ res = ast_streamfile(mychannel, "rpt/node", mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ ast_say_character_str(mychannel,myrpt->name,NULL,mychannel->language);
+ imdone = 1;
+ break;
+ case CONNFAIL:
+ res = ast_streamfile(mychannel, "rpt/node", mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ ast_say_character_str(mychannel,mytele->mylink.name,NULL,mychannel->language);
+ res = ast_streamfile(mychannel, "rpt/connection_failed", mychannel->language);
+ break;
+ case MEMNOTFOUND:
+ /* wait a little bit */
+ wait_interval(myrpt, DLY_TELEM, mychannel);
+ res = ast_streamfile(mychannel, "rpt/memory_notfound", mychannel->language);
+ break;
+ case SETREMOTE:
+ ast_mutex_lock(&myrpt->remlock);
+ res = 0;
+ if(!strcmp(myrpt->remote, remote_rig_ft897))
+ {
+ res = set_ft897(myrpt);
+ }
+ if(!strcmp(myrpt->remote, remote_rig_ic706))
+ {
+ res = set_ic706(myrpt);
+ }
+#ifdef HAVE_IOPERM
+ else if(!strcmp(myrpt->remote, remote_rig_rbi))
+ {
+ if (ioperm(myrpt->p.iobase,1,1) == -1)
+ {
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_log(LOG_WARNING, "Cant get io permission on IO port %x hex\n",myrpt->p.iobase);
+ res = -1;
+ }
+ else res = setrbi(myrpt);
+ }
+#endif
+ else if(!strcmp(myrpt->remote, remote_rig_kenwood))
+ {
+ res = setkenwood(myrpt);
+ if (ast_safe_sleep(mychannel,200) == -1)
+ {
+ ast_mutex_unlock(&myrpt->remlock);
+ res = -1;
+ break;
+ }
+ i = DAHDI_FLUSH_EVENT;
+ if (ioctl(myrpt->zaptxchannel->fds[0],DAHDI_FLUSH,&i) == -1)
+ {
+ ast_mutex_unlock(&myrpt->remlock);
+ ast_log(LOG_ERROR,"Cant flush events");
+ res = -1;
+ break;
+ }
+ if (ioctl(myrpt->zaprxchannel->fds[0],DAHDI_GET_PARAMS,&par) == -1)
+ {
+ ast_mutex_unlock(&myrpt->remlock);
+ ast_log(LOG_ERROR,"Cant get params");
+ res = -1;
+ break;
+ }
+ myrpt->remoterx =
+ (par.rxisoffhook || (myrpt->tele.next != &myrpt->tele));
+ }
+ ast_mutex_unlock(&myrpt->remlock);
+ if (!res)
+ {
+ imdone = 1;
+ break;
+ }
+ /* fall thru to invalid freq */
+ case INVFREQ:
+ /* wait a little bit */
+ wait_interval(myrpt, DLY_TELEM, mychannel);
+ res = ast_streamfile(mychannel, "rpt/invalid-freq", mychannel->language);
+ break;
+ case REMMODE:
+ cp = 0;
+ wait_interval(myrpt, DLY_TELEM, mychannel);
+ switch(myrpt->remmode)
+ {
+ case REM_MODE_FM:
+ saycharstr(mychannel,"FM");
+ break;
+ case REM_MODE_USB:
+ saycharstr(mychannel,"USB");
+ break;
+ case REM_MODE_LSB:
+ saycharstr(mychannel,"LSB");
+ break;
+ case REM_MODE_AM:
+ saycharstr(mychannel,"AM");
+ break;
+ }
+ wait_interval(myrpt, DLY_COMP, mychannel);
+ if (!res) res = telem_lookup(myrpt,mychannel, myrpt->name, "functcomplete");
+ break;
+ case LOGINREQ:
+ wait_interval(myrpt, DLY_TELEM, mychannel);
+ sayfile(mychannel,"rpt/login");
+ saycharstr(mychannel,myrpt->name);
+ break;
+ case REMLOGIN:
+ wait_interval(myrpt, DLY_TELEM, mychannel);
+ saycharstr(mychannel,myrpt->loginuser);
+ sayfile(mychannel,"rpt/node");
+ saycharstr(mychannel,myrpt->name);
+ wait_interval(myrpt, DLY_COMP, mychannel);
+ if (!res) res = telem_lookup(myrpt,mychannel, myrpt->name, "functcomplete");
+ break;
+ case REMXXX:
+ wait_interval(myrpt, DLY_TELEM, mychannel);
+ res = 0;
+ switch(mytele->submode)
+ {
+ case 100: /* RX PL Off */
+ sayfile(mychannel, "rpt/rxpl");
+ sayfile(mychannel, "rpt/off");
+ break;
+ case 101: /* RX PL On */
+ sayfile(mychannel, "rpt/rxpl");
+ sayfile(mychannel, "rpt/on");
+ break;
+ case 102: /* TX PL Off */
+ sayfile(mychannel, "rpt/txpl");
+ sayfile(mychannel, "rpt/off");
+ break;
+ case 103: /* TX PL On */
+ sayfile(mychannel, "rpt/txpl");
+ sayfile(mychannel, "rpt/on");
+ break;
+ case 104: /* Low Power */
+ sayfile(mychannel, "rpt/lopwr");
+ break;
+ case 105: /* Medium Power */
+ sayfile(mychannel, "rpt/medpwr");
+ break;
+ case 106: /* Hi Power */
+ sayfile(mychannel, "rpt/hipwr");
+ break;
+ case 113: /* Scan down slow */
+ sayfile(mychannel,"rpt/down");
+ sayfile(mychannel, "rpt/slow");
+ break;
+ case 114: /* Scan down quick */
+ sayfile(mychannel,"rpt/down");
+ sayfile(mychannel, "rpt/quick");
+ break;
+ case 115: /* Scan down fast */
+ sayfile(mychannel,"rpt/down");
+ sayfile(mychannel, "rpt/fast");
+ break;
+ case 116: /* Scan up slow */
+ sayfile(mychannel,"rpt/up");
+ sayfile(mychannel, "rpt/slow");
+ break;
+ case 117: /* Scan up quick */
+ sayfile(mychannel,"rpt/up");
+ sayfile(mychannel, "rpt/quick");
+ break;
+ case 118: /* Scan up fast */
+ sayfile(mychannel,"rpt/up");
+ sayfile(mychannel, "rpt/fast");
+ break;
+ default:
+ res = -1;
+ }
+ wait_interval(myrpt, DLY_COMP, mychannel);
+ if (!res) res = telem_lookup(myrpt,mychannel, myrpt->name, "functcomplete");
+ break;
+ case SCAN:
+ ast_mutex_lock(&myrpt->remlock);
+ if (myrpt->hfscanstop)
+ {
+ myrpt->hfscanstatus = 0;
+ myrpt->hfscanmode = 0;
+ myrpt->hfscanstop = 0;
+ mytele->mode = SCANSTAT;
+ ast_mutex_unlock(&myrpt->remlock);
+ if (ast_safe_sleep(mychannel,1000) == -1) break;
+ sayfile(mychannel, "rpt/stop");
+ imdone = 1;
+ break;
+ }
+ if (myrpt->hfscanstatus > -2) service_scan(myrpt);
+ i = myrpt->hfscanstatus;
+ myrpt->hfscanstatus = 0;
+ if (i) mytele->mode = SCANSTAT;
+ ast_mutex_unlock(&myrpt->remlock);
+ if (i < 0) sayfile(mychannel, "rpt/stop");
+ else if (i > 0) saynum(mychannel,i);
+ imdone = 1;
+ break;
+ case TUNE:
+ ast_mutex_lock(&myrpt->remlock);
+ if (!strcmp(myrpt->remote,remote_rig_ic706))
+ {
+ set_mode_ic706(myrpt, REM_MODE_AM);
+ if(play_tone(mychannel, 800, 6000, 8192) == -1) break;
+ ast_safe_sleep(mychannel,500);
+ set_mode_ic706(myrpt, myrpt->remmode);
+ myrpt->tunerequest = 0;
+ ast_mutex_unlock(&myrpt->remlock);
+ imdone = 1;
+ break;
+ }
+ set_mode_ft897(myrpt, REM_MODE_AM);
+ simple_command_ft897(myrpt, 8);
+ if(play_tone(mychannel, 800, 6000, 8192) == -1) break;
+ simple_command_ft897(myrpt, 0x88);
+ ast_safe_sleep(mychannel,500);
+ set_mode_ft897(myrpt, myrpt->remmode);
+ myrpt->tunerequest = 0;
+ ast_mutex_unlock(&myrpt->remlock);
+ imdone = 1;
+ break;
+ case REMSHORTSTATUS:
+ case REMLONGSTATUS:
+ wait_interval(myrpt, DLY_TELEM, mychannel);
+ res = sayfile(mychannel,"rpt/node");
+ if(!res)
+ res = saycharstr(mychannel, myrpt->name);
+ if(!res)
+ res = sayfile(mychannel,"rpt/frequency");
+ if(!res)
+ res = split_freq(mhz, decimals, myrpt->freq);
+ if (!multimode_capable(myrpt)) decimals[3] = 0;
+ if(!res){
+ m = atoi(mhz);
+ if(m < 100)
+ res = saynum(mychannel, m);
+ else
+ res = saycharstr(mychannel, mhz);
+ }
+ if(!res)
+ res = sayfile(mychannel, "letters/dot");
+ if(!res)
+ res = saycharstr(mychannel, decimals);
+
+ if(res) break;
+ if(myrpt->remmode == REM_MODE_FM){ /* Mode FM? */
+ switch(myrpt->offset){
+
+ case REM_MINUS:
+ res = sayfile(mychannel,"rpt/minus");
+ break;
+
+ case REM_SIMPLEX:
+ res = sayfile(mychannel,"rpt/simplex");
+ break;
+
+ case REM_PLUS:
+ res = sayfile(mychannel,"rpt/plus");
+ break;
+
+ default:
+ break;
+ }
+ }
+ else{ /* Must be USB, LSB, or AM */
+ switch(myrpt->remmode){
+
+ case REM_MODE_USB:
+ res = saycharstr(mychannel, "USB");
+ break;
+
+ case REM_MODE_LSB:
+ res = saycharstr(mychannel, "LSB");
+ break;
+
+ case REM_MODE_AM:
+ res = saycharstr(mychannel, "AM");
+ break;
+
+
+ default:
+ break;
+ }
+ }
+
+ if (res == -1) break;
+
+ if(mytele->mode == REMSHORTSTATUS){ /* Short status? */
+ wait_interval(myrpt, DLY_COMP, mychannel);
+ if (!res) res = telem_lookup(myrpt,mychannel, myrpt->name, "functcomplete");
+ break;
+ }
+
+ if (strcmp(myrpt->remote,remote_rig_ic706))
+ {
+ switch(myrpt->powerlevel){
+
+ case REM_LOWPWR:
+ res = sayfile(mychannel,"rpt/lopwr") ;
+ break;
+ case REM_MEDPWR:
+ res = sayfile(mychannel,"rpt/medpwr");
+ break;
+ case REM_HIPWR:
+ res = sayfile(mychannel,"rpt/hipwr");
+ break;
+ }
+ }
+
+ rbimode = ((!strncmp(myrpt->remote,remote_rig_rbi,3))
+ || (!strncmp(myrpt->remote,remote_rig_ic706,3)));
+ if (res || (sayfile(mychannel,"rpt/rxpl") == -1)) break;
+ if (rbimode && (sayfile(mychannel,"rpt/txpl") == -1)) break;
+ if ((sayfile(mychannel,"rpt/frequency") == -1) ||
+ (saycharstr(mychannel,myrpt->rxpl) == -1)) break;
+ if ((!rbimode) && ((sayfile(mychannel,"rpt/txpl") == -1) ||
+ (sayfile(mychannel,"rpt/frequency") == -1) ||
+ (saycharstr(mychannel,myrpt->txpl) == -1))) break;
+ if(myrpt->remmode == REM_MODE_FM){ /* Mode FM? */
+ if ((sayfile(mychannel,"rpt/rxpl") == -1) ||
+ (sayfile(mychannel,((myrpt->rxplon) ? "rpt/on" : "rpt/off")) == -1) ||
+ (sayfile(mychannel,"rpt/txpl") == -1) ||
+ (sayfile(mychannel,((myrpt->txplon) ? "rpt/on" : "rpt/off")) == -1))
+ {
+ break;
+ }
+ }
+ wait_interval(myrpt, DLY_COMP, mychannel);
+ if (!res) res = telem_lookup(myrpt,mychannel, myrpt->name, "functcomplete");
+ break;
+ case STATUS:
+ /* wait a little bit */
+ wait_interval(myrpt, DLY_TELEM, mychannel);
+ hastx = 0;
+ linkbase.next = &linkbase;
+ linkbase.prev = &linkbase;
+ rpt_mutex_lock(&myrpt->lock);
+ /* make our own list of links */
+ l = myrpt->links.next;
+ while(l != &myrpt->links)
+ {
+ if (l->name[0] == '0')
+ {
+ l = l->next;
+ continue;
+ }
+ l1 = malloc(sizeof(struct rpt_link));
+ if (!l1)
+ {
+ ast_log(LOG_WARNING, "Cannot alloc memory on %s\n", mychannel->name);
+ remque((struct qelem *)mytele);
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_log(LOG_NOTICE,"Telemetry thread aborted at line %d, mode: %d\n",__LINE__, mytele->mode); /*@@@@@@@@@@@*/
+ free(mytele);
+ ast_hangup(mychannel);
+ pthread_exit(NULL);
+ }
+ memcpy(l1,l,sizeof(struct rpt_link));
+ l1->next = l1->prev = NULL;
+ insque((struct qelem *)l1,(struct qelem *)linkbase.next);
+ l = l->next;
+ }
+ rpt_mutex_unlock(&myrpt->lock);
+ res = ast_streamfile(mychannel, "rpt/node", mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ ast_say_character_str(mychannel,myrpt->name,NULL,mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ if (myrpt->callmode)
+ {
+ hastx = 1;
+ res = ast_streamfile(mychannel, "rpt/autopatch_on", mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ }
+ l = linkbase.next;
+ while(l != &linkbase)
+ {
+ char *s;
+
+ hastx = 1;
+ res = ast_streamfile(mychannel, "rpt/node", mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ ast_say_character_str(mychannel,l->name,NULL,mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ s = "rpt/tranceive";
+ if (!l->mode) s = "rpt/monitor";
+ if (!l->thisconnected) s = "rpt/connecting";
+ res = ast_streamfile(mychannel, s, mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ l = l->next;
+ }
+ if (!hastx)
+ {
+ res = ast_streamfile(mychannel, "rpt/repeat_only", mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ }
+ /* destroy our local link queue */
+ l = linkbase.next;
+ while(l != &linkbase)
+ {
+ l1 = l;
+ l = l->next;
+ remque((struct qelem *)l1);
+ free(l1);
+ }
+ imdone = 1;
+ break;
+ case FULLSTATUS:
+ rpt_mutex_lock(&myrpt->lock);
+ /* get all the nodes */
+ __mklinklist(myrpt,NULL,lbuf);
+ rpt_mutex_unlock(&myrpt->lock);
+ /* parse em */
+ ns = finddelim(lbuf,strs,MAXLINKLIST);
+ /* sort em */
+ if (ns) qsort((void *)strs,ns,sizeof(char *),mycompar);
+ /* wait a little bit */
+ wait_interval(myrpt, DLY_TELEM, mychannel);
+ hastx = 0;
+ res = ast_streamfile(mychannel, "rpt/node", mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ ast_say_character_str(mychannel,myrpt->name,NULL,mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ if (myrpt->callmode)
+ {
+ hastx = 1;
+ res = ast_streamfile(mychannel, "rpt/autopatch_on", mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ }
+ /* go thru all the nodes in list */
+ for(i = 0; i < ns; i++)
+ {
+ char *s,mode = 'T';
+
+ /* if a mode spec at first, handle it */
+ if ((*strs[i] < '0') || (*strs[i] > '9'))
+ {
+ mode = *strs[i];
+ strs[i]++;
+ }
+
+ hastx = 1;
+ res = ast_streamfile(mychannel, "rpt/node", mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ ast_say_character_str(mychannel,strs[i],NULL,mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ s = "rpt/tranceive";
+ if (mode == 'R') s = "rpt/monitor";
+ if (mode == 'C') s = "rpt/connecting";
+ res = ast_streamfile(mychannel, s, mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ }
+ if (!hastx)
+ {
+ res = ast_streamfile(mychannel, "rpt/repeat_only", mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ }
+ imdone = 1;
+ break;
+
+ case LASTNODEKEY: /* Identify last node which keyed us up */
+ rpt_mutex_lock(&myrpt->lock);
+ if(myrpt->lastnodewhichkeyedusup)
+ p = ast_strdupa(myrpt->lastnodewhichkeyedusup); /* Make a local copy of the node name */
+ else
+ p = NULL;
+ rpt_mutex_unlock(&myrpt->lock);
+ if(!p){
+ imdone = 1; /* no node previously keyed us up, or the node which did has been disconnected */
+ break;
+ }
+ wait_interval(myrpt, DLY_TELEM, mychannel);
+ res = ast_streamfile(mychannel, "rpt/node", mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ ast_say_character_str(mychannel, p, NULL, mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ imdone = 1;
+ break;
+
+ case UNAUTHTX: /* Say unauthorized transmit frequency */
+ wait_interval(myrpt, DLY_TELEM, mychannel);
+ res = ast_streamfile(mychannel, "rpt/unauthtx", mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ imdone = 1;
+ break;
+
+
+ case TIMEOUT:
+ res = ast_streamfile(mychannel, "rpt/node", mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ ast_say_character_str(mychannel,myrpt->name,NULL,mychannel->language);
+ res = ast_streamfile(mychannel, "rpt/timeout", mychannel->language);
+ break;
+
+ case TIMEOUT_WARNING:
+ time(&t);
+ res = ast_streamfile(mychannel, "rpt/node", mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ ast_say_character_str(mychannel,myrpt->name,NULL,mychannel->language);
+ res = ast_streamfile(mychannel, "rpt/timeout-warning", mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ if(!res) /* Say number of seconds */
+ ast_say_number(mychannel, myrpt->p.remotetimeout -
+ (t - myrpt->last_activity_time),
+ "", mychannel->language, (char *) NULL);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ ast_stopstream(mychannel);
+ res = ast_streamfile(mychannel, "queue-seconds", mychannel->language);
+ break;
+
+ case ACT_TIMEOUT_WARNING:
+ time(&t);
+ res = ast_streamfile(mychannel, "rpt/node", mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ ast_say_character_str(mychannel,myrpt->name,NULL,mychannel->language);
+ res = ast_streamfile(mychannel, "rpt/act-timeout-warning", mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ ast_stopstream(mychannel);
+ if(!res) /* Say number of seconds */
+ ast_say_number(mychannel, myrpt->p.remoteinacttimeout -
+ (t - myrpt->last_activity_time),
+ "", mychannel->language, (char *) NULL);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ ast_stopstream(mychannel);
+ res = ast_streamfile(mychannel, "queue-seconds", mychannel->language);
+ break;
+
+ case STATS_TIME:
+ wait_interval(myrpt, DLY_TELEM, mychannel); /* Wait a little bit */
+ t = time(NULL);
+ rpt_localtime(&t, &localtm);
+ /* Say the phase of the day is before the time */
+ if((localtm.tm_hour >= 0) && (localtm.tm_hour < 12))
+ p = "rpt/goodmorning";
+ else if((localtm.tm_hour >= 12) && (localtm.tm_hour < 18))
+ p = "rpt/goodafternoon";
+ else
+ p = "rpt/goodevening";
+ if (sayfile(mychannel,p) == -1)
+ {
+ imdone = 1;
+ break;
+ }
+ /* Say the time is ... */
+ if (sayfile(mychannel,"rpt/thetimeis") == -1)
+ {
+ imdone = 1;
+ break;
+ }
+ /* Say the time */
+ res = ast_say_time(mychannel, t, "", mychannel->language);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ ast_stopstream(mychannel);
+ imdone = 1;
+ break;
+ case STATS_VERSION:
+ p = strstr(tdesc, "version");
+ if(!p)
+ break;
+ if(sscanf(p, "version %d.%d", &vmajor, &vminor) != 2)
+ break;
+ wait_interval(myrpt, DLY_TELEM, mychannel); /* Wait a little bit */
+ /* Say "version" */
+ if (sayfile(mychannel,"rpt/version") == -1)
+ {
+ imdone = 1;
+ break;
+ }
+ if(!res) /* Say "X" */
+ ast_say_number(mychannel, vmajor, "", mychannel->language, (char *) NULL);
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ ast_stopstream(mychannel);
+ if (saycharstr(mychannel,".") == -1)
+ {
+ imdone = 1;
+ break;
+ }
+ if(!res) /* Say "Y" */
+ ast_say_number(mychannel, vminor, "", mychannel->language, (char *) NULL);
+ if (!res){
+ res = ast_waitstream(mychannel, "");
+ ast_stopstream(mychannel);
+ }
+ else
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ imdone = 1;
+ break;
+ case ARB_ALPHA:
+ wait_interval(myrpt, DLY_TELEM, mychannel); /* Wait a little bit */
+ if(mytele->param)
+ saycharstr(mychannel, mytele->param);
+ imdone = 1;
+ break;
+ case REV_PATCH:
+ wait_interval(myrpt, DLY_TELEM, mychannel); /* Wait a little bit */
+ if(mytele->param) {
+
+ /* Parts of this section taken from app_parkandannounce */
+ char *tpl_working, *tpl_current;
+ char *tmp[100], *myparm;
+ int looptemp=0,i=0, dres = 0;
+
+
+ tpl_working = strdupa(mytele->param);
+ myparm = strsep(&tpl_working,",");
+ tpl_current=strsep(&tpl_working, ":");
+
+ while(tpl_current && looptemp < sizeof(tmp)) {
+ tmp[looptemp]=tpl_current;
+ looptemp++;
+ tpl_current=strsep(&tpl_working,":");
+ }
+
+ for(i=0; i<looptemp; i++) {
+ if(!strcmp(tmp[i], "PARKED")) {
+ ast_say_digits(mychannel, atoi(myparm), "", mychannel->language);
+ } else if(!strcmp(tmp[i], "NODE")) {
+ ast_say_digits(mychannel, atoi(myrpt->name), "", mychannel->language);
+ } else {
+ dres = ast_streamfile(mychannel, tmp[i], mychannel->language);
+ if(!dres) {
+ dres = ast_waitstream(mychannel, "");
+ } else {
+ ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", tmp[i], mychannel->name);
+ dres = 0;
+ }
+ }
+ }
+ }
+ imdone = 1;
+ break;
+ case TEST_TONE:
+ imdone = 1;
+ if (myrpt->stopgen) break;
+ myrpt->stopgen = -1;
+ if ((res = ast_tonepair_start(mychannel, 1004.0, 0, 99999999, 7200.0)))
+ {
+ myrpt->stopgen = 0;
+ break;
+ }
+ while(mychannel->generatordata && (myrpt->stopgen <= 0)) {
+ if (ast_safe_sleep(mychannel,1)) break;
+ imdone = 1;
+ }
+ myrpt->stopgen = 0;
+ break;
+ default:
+ break;
+ }
+ if (!imdone)
+ {
+ if (!res)
+ res = ast_waitstream(mychannel, "");
+ else {
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", mychannel->name);
+ res = 0;
+ }
+ }
+ ast_stopstream(mychannel);
+ rpt_mutex_lock(&myrpt->lock);
+ if (mytele->mode == TAILMSG)
+ {
+ if (!res)
+ {
+ myrpt->tailmessagen++;
+ if(myrpt->tailmessagen >= myrpt->p.tailmessagemax) myrpt->tailmessagen = 0;
+ }
+ else
+ {
+ myrpt->tmsgtimer = myrpt->p.tailsquashedtime;
+ }
+ }
+ remque((struct qelem *)mytele);
+ rpt_mutex_unlock(&myrpt->lock);
+ free(mytele);
+ ast_hangup(mychannel);
+#ifdef APP_RPT_LOCK_DEBUG
+ {
+ struct lockthread *t;
+
+ sleep(5);
+ ast_mutex_lock(&locklock);
+ t = get_lockthread(pthread_self());
+ if (t) memset(t,0,sizeof(struct lockthread));
+ ast_mutex_unlock(&locklock);
+ }
+#endif
+ pthread_exit(NULL);
+}
+
+static void rpt_telemetry(struct rpt *myrpt,int mode, void *data)
+{
+struct rpt_tele *tele;
+struct rpt_link *mylink = (struct rpt_link *) data;
+int res;
+pthread_attr_t attr;
+
+ tele = malloc(sizeof(struct rpt_tele));
+ if (!tele)
+ {
+ ast_log(LOG_WARNING, "Unable to allocate memory\n");
+ pthread_exit(NULL);
+ return;
+ }
+ /* zero it out */
+ memset((char *)tele,0,sizeof(struct rpt_tele));
+ tele->rpt = myrpt;
+ tele->mode = mode;
+ rpt_mutex_lock(&myrpt->lock);
+ if((mode == CONNFAIL) || (mode == REMDISC) || (mode == CONNECTED) ||
+ (mode == LINKUNKEY)){
+ memset(&tele->mylink,0,sizeof(struct rpt_link));
+ if (mylink){
+ memcpy(&tele->mylink,mylink,sizeof(struct rpt_link));
+ }
+ }
+ else if ((mode == ARB_ALPHA) || (mode == REV_PATCH)) {
+ strncpy(tele->param, (char *) data, TELEPARAMSIZE - 1);
+ tele->param[TELEPARAMSIZE - 1] = 0;
+ }
+ if (mode == REMXXX) tele->submode = (int) data;
+ insque((struct qelem *)tele, (struct qelem *)myrpt->tele.next);
+ rpt_mutex_unlock(&myrpt->lock);
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ res = ast_pthread_create(&tele->threadid,&attr,rpt_tele_thread,(void *) tele);
+ if(res < 0){
+ rpt_mutex_lock(&myrpt->lock);
+ remque((struct qlem *) tele); /* We don't like stuck transmitters, remove it from the queue */
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_log(LOG_WARNING, "Could not create telemetry thread: %s",strerror(res));
+ }
+ return;
+}
+
+static void *rpt_call(void *this)
+{
+struct dahdi_confinfo ci; /* conference info */
+struct rpt *myrpt = (struct rpt *)this;
+int res;
+int stopped,congstarted,dialtimer,lastcidx,aborted;
+struct ast_channel *mychannel,*genchannel;
+
+
+ myrpt->mydtmf = 0;
+ /* allocate a pseudo-channel thru asterisk */
+ mychannel = ast_request("zap",AST_FORMAT_SLINEAR,"pseudo",NULL);
+ if (!mychannel)
+ {
+ fprintf(stderr,"rpt:Sorry unable to obtain pseudo channel\n");
+ pthread_exit(NULL);
+ }
+#ifdef AST_CDR_FLAG_POST_DISABLED
+ ast_set_flag(mychannel->cdr,AST_CDR_FLAG_POST_DISABLED);
+#endif
+ ci.chan = 0;
+ ci.confno = myrpt->conf; /* use the pseudo conference */
+ ci.confmode = DAHDI_CONF_REALANDPSEUDO | DAHDI_CONF_TALKER | DAHDI_CONF_LISTENER
+ | DAHDI_CONF_PSEUDO_TALKER | DAHDI_CONF_PSEUDO_LISTENER;
+ /* first put the channel on the conference */
+ if (ioctl(mychannel->fds[0],DAHDI_SETCONF,&ci) == -1)
+ {
+ ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n");
+ ast_hangup(mychannel);
+ myrpt->callmode = 0;
+ pthread_exit(NULL);
+ }
+ /* allocate a pseudo-channel thru asterisk */
+ genchannel = ast_request("zap",AST_FORMAT_SLINEAR,"pseudo",NULL);
+ if (!genchannel)
+ {
+ fprintf(stderr,"rpt:Sorry unable to obtain pseudo channel\n");
+ ast_hangup(mychannel);
+ pthread_exit(NULL);
+ }
+#ifdef AST_CDR_FLAG_POST_DISABLED
+ ast_set_flag(genchannel->cdr,AST_CDR_FLAG_POST_DISABLED);
+#endif
+ ci.chan = 0;
+ ci.confno = myrpt->conf;
+ ci.confmode = DAHDI_CONF_REALANDPSEUDO | DAHDI_CONF_TALKER | DAHDI_CONF_LISTENER
+ | DAHDI_CONF_PSEUDO_TALKER | DAHDI_CONF_PSEUDO_LISTENER;
+ /* first put the channel on the conference */
+ if (ioctl(genchannel->fds[0],DAHDI_SETCONF,&ci) == -1)
+ {
+ ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n");
+ ast_hangup(mychannel);
+ ast_hangup(genchannel);
+ myrpt->callmode = 0;
+ pthread_exit(NULL);
+ }
+ if (myrpt->p.tonezone && (tone_zone_set_zone(mychannel->fds[0],myrpt->p.tonezone) == -1))
+ {
+ ast_log(LOG_WARNING, "Unable to set tone zone %s\n",myrpt->p.tonezone);
+ ast_hangup(mychannel);
+ ast_hangup(genchannel);
+ myrpt->callmode = 0;
+ pthread_exit(NULL);
+ }
+ if (myrpt->p.tonezone && (tone_zone_set_zone(genchannel->fds[0],myrpt->p.tonezone) == -1))
+ {
+ ast_log(LOG_WARNING, "Unable to set tone zone %s\n",myrpt->p.tonezone);
+ ast_hangup(mychannel);
+ ast_hangup(genchannel);
+ myrpt->callmode = 0;
+ pthread_exit(NULL);
+ }
+ /* start dialtone if patchquiet is 0. Special patch modes don't send dial tone */
+ if ((!myrpt->patchquiet) && (tone_zone_play_tone(mychannel->fds[0],DAHDI_TONE_DIALTONE) < 0))
+ {
+ ast_log(LOG_WARNING, "Cannot start dialtone\n");
+ ast_hangup(mychannel);
+ ast_hangup(genchannel);
+ myrpt->callmode = 0;
+ pthread_exit(NULL);
+ }
+ stopped = 0;
+ congstarted = 0;
+ dialtimer = 0;
+ lastcidx = 0;
+ aborted = 0;
+
+
+ while ((myrpt->callmode == 1) || (myrpt->callmode == 4))
+ {
+
+ if((myrpt->patchdialtime)&&(myrpt->callmode == 1)&&(myrpt->cidx != lastcidx)){
+ dialtimer = 0;
+ lastcidx = myrpt->cidx;
+ }
+
+ if((myrpt->patchdialtime)&&(dialtimer >= myrpt->patchdialtime)){
+ rpt_mutex_lock(&myrpt->lock);
+ aborted = 1;
+ myrpt->callmode = 0;
+ rpt_mutex_unlock(&myrpt->lock);
+ break;
+ }
+
+ if ((!myrpt->patchquiet) && (!stopped) && (myrpt->callmode == 1) && (myrpt->cidx > 0))
+ {
+ stopped = 1;
+ /* stop dial tone */
+ tone_zone_play_tone(mychannel->fds[0],-1);
+ }
+ if (myrpt->callmode == 4)
+ {
+ if(!congstarted){
+ congstarted = 1;
+ /* start congestion tone */
+ tone_zone_play_tone(mychannel->fds[0],DAHDI_TONE_CONGESTION);
+ }
+ }
+ res = ast_safe_sleep(mychannel, MSWAIT);
+ if (res < 0)
+ {
+ ast_hangup(mychannel);
+ ast_hangup(genchannel);
+ rpt_mutex_lock(&myrpt->lock);
+ myrpt->callmode = 0;
+ rpt_mutex_unlock(&myrpt->lock);
+ pthread_exit(NULL);
+ }
+ dialtimer += MSWAIT;
+ }
+ /* stop any tone generation */
+ tone_zone_play_tone(mychannel->fds[0],-1);
+ /* end if done */
+ if (!myrpt->callmode)
+ {
+ ast_hangup(mychannel);
+ ast_hangup(genchannel);
+ rpt_mutex_lock(&myrpt->lock);
+ myrpt->callmode = 0;
+ rpt_mutex_unlock(&myrpt->lock);
+ if((!myrpt->patchquiet) && aborted)
+ rpt_telemetry(myrpt, TERM, NULL);
+ pthread_exit(NULL);
+ }
+
+ if (myrpt->p.ourcallerid && *myrpt->p.ourcallerid){
+ char *name, *loc, *instr;
+ instr = strdup(myrpt->p.ourcallerid);
+ if(instr){
+ ast_callerid_parse(instr, &name, &loc);
+ if(loc){
+ if(mychannel->cid.cid_num)
+ free(mychannel->cid.cid_num);
+ mychannel->cid.cid_num = strdup(loc);
+ }
+ if(name){
+ if(mychannel->cid.cid_name)
+ free(mychannel->cid.cid_name);
+ mychannel->cid.cid_name = strdup(name);
+ }
+ free(instr);
+ }
+ }
+
+ ast_copy_string(mychannel->exten, myrpt->exten, sizeof(mychannel->exten) - 1);
+ ast_copy_string(mychannel->context, myrpt->patchcontext, sizeof(mychannel->context) - 1);
+
+ if (myrpt->p.acctcode)
+ ast_cdr_setaccount(mychannel,myrpt->p.acctcode);
+ mychannel->priority = 1;
+ ast_channel_undefer_dtmf(mychannel);
+ if (ast_pbx_start(mychannel) < 0)
+ {
+ ast_log(LOG_WARNING, "Unable to start PBX!!\n");
+ ast_hangup(mychannel);
+ ast_hangup(genchannel);
+ rpt_mutex_lock(&myrpt->lock);
+ myrpt->callmode = 0;
+ rpt_mutex_unlock(&myrpt->lock);
+ pthread_exit(NULL);
+ }
+ usleep(10000);
+ rpt_mutex_lock(&myrpt->lock);
+ myrpt->callmode = 3;
+ /* set appropriate conference for the pseudo */
+ ci.chan = 0;
+ ci.confno = myrpt->conf;
+ ci.confmode = (myrpt->p.duplex == 2) ? DAHDI_CONF_CONFANNMON :
+ (DAHDI_CONF_CONF | DAHDI_CONF_LISTENER | DAHDI_CONF_TALKER);
+ /* first put the channel on the conference in announce mode */
+ if (ioctl(myrpt->pchannel->fds[0],DAHDI_SETCONF,&ci) == -1)
+ {
+ ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n");
+ ast_hangup(mychannel);
+ ast_hangup(genchannel);
+ myrpt->callmode = 0;
+ pthread_exit(NULL);
+ }
+ while(myrpt->callmode)
+ {
+ if ((!mychannel->pbx) && (myrpt->callmode != 4))
+ {
+ if(myrpt->patchfarenddisconnect){ /* If patch is setup for far end disconnect */
+ myrpt->callmode = 0;
+ if(!myrpt->patchquiet){
+ rpt_mutex_unlock(&myrpt->lock);
+ rpt_telemetry(myrpt, TERM, NULL);
+ rpt_mutex_lock(&myrpt->lock);
+ }
+ }
+ else{ /* Send congestion until patch is downed by command */
+ myrpt->callmode = 4;
+ rpt_mutex_unlock(&myrpt->lock);
+ /* start congestion tone */
+ tone_zone_play_tone(genchannel->fds[0],DAHDI_TONE_CONGESTION);
+ rpt_mutex_lock(&myrpt->lock);
+ }
+ }
+ if (myrpt->mydtmf)
+ {
+ struct ast_frame wf = {AST_FRAME_DTMF, } ;
+ wf.subclass = myrpt->mydtmf;
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_queue_frame(mychannel,&wf);
+ ast_senddigit(genchannel,myrpt->mydtmf);
+ rpt_mutex_lock(&myrpt->lock);
+ myrpt->mydtmf = 0;
+ }
+ rpt_mutex_unlock(&myrpt->lock);
+ usleep(MSWAIT * 1000);
+ rpt_mutex_lock(&myrpt->lock);
+ }
+ rpt_mutex_unlock(&myrpt->lock);
+ tone_zone_play_tone(genchannel->fds[0],-1);
+ if (mychannel->pbx) ast_softhangup(mychannel,AST_SOFTHANGUP_DEV);
+ ast_hangup(genchannel);
+ rpt_mutex_lock(&myrpt->lock);
+ myrpt->callmode = 0;
+ rpt_mutex_unlock(&myrpt->lock);
+ /* set appropriate conference for the pseudo */
+ ci.chan = 0;
+ ci.confno = myrpt->conf;
+ ci.confmode = ((myrpt->p.duplex == 2) || (myrpt->p.duplex == 4)) ? DAHDI_CONF_CONFANNMON :
+ (DAHDI_CONF_CONF | DAHDI_CONF_LISTENER | DAHDI_CONF_TALKER);
+ /* first put the channel on the conference in announce mode */
+ if (ioctl(myrpt->pchannel->fds[0],DAHDI_SETCONF,&ci) == -1)
+ {
+ ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n");
+ }
+ pthread_exit(NULL);
+}
+
+static void send_link_dtmf(struct rpt *myrpt,char c)
+{
+char str[300];
+struct ast_frame wf;
+struct rpt_link *l;
+
+ snprintf(str, sizeof(str), "D %s %s %d %c", myrpt->cmdnode, myrpt->name, ++(myrpt->dtmfidx), c);
+ wf.frametype = AST_FRAME_TEXT;
+ wf.subclass = 0;
+ wf.offset = 0;
+ wf.mallocd = 0;
+ wf.datalen = strlen(str) + 1;
+ wf.samples = 0;
+ l = myrpt->links.next;
+ /* first, see if our dude is there */
+ while(l != &myrpt->links)
+ {
+ if (l->name[0] == '0')
+ {
+ l = l->next;
+ continue;
+ }
+ /* if we found it, write it and were done */
+ if (!strcmp(l->name,myrpt->cmdnode))
+ {
+ wf.data = str;
+ if (l->chan) ast_write(l->chan,&wf);
+ return;
+ }
+ l = l->next;
+ }
+ l = myrpt->links.next;
+ /* if not, give it to everyone */
+ while(l != &myrpt->links)
+ {
+ wf.data = str;
+ if (l->chan) ast_write(l->chan,&wf);
+ l = l->next;
+ }
+ return;
+}
+
+/*
+ * Connect a link
+ *
+ * Return values:
+ * -1: Error
+ * 0: Success
+ * 1: No match yet
+ * 2: Already connected to this node
+ */
+
+static int connect_link(struct rpt *myrpt, char* node, int mode, int perma)
+{
+ char *val, *s, *s1, *s2, *tele;
+ char lstr[MAXLINKLIST],*strs[MAXLINKLIST];
+ char tmp[300], deststr[300] = "",modechange = 0;
+ struct rpt_link *l;
+ int reconnects = 0;
+ int i,n;
+ struct dahdi_confinfo ci; /* conference info */
+
+ val = node_lookup(myrpt,node);
+ if (!val){
+ if(strlen(node) >= myrpt->longestnode)
+ return -1; /* No such node */
+ return 1; /* No match yet */
+ }
+ if(debug > 3){
+ ast_log(LOG_NOTICE,"Connect attempt to node %s\n", node);
+ ast_log(LOG_NOTICE,"Mode: %s\n",(mode)?"Transceive":"Monitor");
+ ast_log(LOG_NOTICE,"Connection type: %s\n",(perma)?"Permalink":"Normal");
+ }
+
+ strncpy(tmp,val,sizeof(tmp) - 1);
+ s = tmp;
+ s1 = strsep(&s,",");
+ s2 = strsep(&s,",");
+ rpt_mutex_lock(&myrpt->lock);
+ l = myrpt->links.next;
+ /* try to find this one in queue */
+ while(l != &myrpt->links){
+ if (l->name[0] == '0')
+ {
+ l = l->next;
+ continue;
+ }
+ /* if found matching string */
+ if (!strcmp(l->name, node))
+ break;
+ l = l->next;
+ }
+ /* if found */
+ if (l != &myrpt->links){
+ /* if already in this mode, just ignore */
+ if ((l->mode) || (!l->chan)) {
+ rpt_mutex_unlock(&myrpt->lock);
+ return 2; /* Already linked */
+ }
+ reconnects = l->reconnects;
+ rpt_mutex_unlock(&myrpt->lock);
+ if (l->chan) ast_softhangup(l->chan, AST_SOFTHANGUP_DEV);
+ l->retries = l->max_retries + 1;
+ l->disced = 2;
+ modechange = 1;
+ } else
+ {
+ __mklinklist(myrpt,NULL,lstr);
+ rpt_mutex_unlock(&myrpt->lock);
+ n = finddelim(lstr,strs,MAXLINKLIST);
+ for(i = 0; i < n; i++)
+ {
+ if ((*strs[i] < '0') ||
+ (*strs[i] > '9')) strs[i]++;
+ if (!strcmp(strs[i],node))
+ {
+ return 2; /* Already linked */
+ }
+ }
+ }
+ strncpy(myrpt->lastlinknode,node,MAXNODESTR - 1);
+ /* establish call */
+ l = malloc(sizeof(struct rpt_link));
+ if (!l)
+ {
+ ast_log(LOG_WARNING, "Unable to malloc\n");
+ return -1;
+ }
+ /* zero the silly thing */
+ memset((char *)l,0,sizeof(struct rpt_link));
+ l->mode = mode;
+ l->outbound = 1;
+ l->thisconnected = 0;
+ strncpy(l->name, node, MAXNODESTR - 1);
+ l->isremote = (s && ast_true(s));
+ if (modechange) l->connected = 1;
+ l->hasconnected = l->perma = perma;
+#ifdef ALLOW_LOCAL_CHANNELS
+ if ((strncasecmp(s1,"iax2/", 5) == 0) || (strncasecmp(s1, "local/", 6) == 0))
+ strncpy(deststr, s1, sizeof(deststr));
+ else
+ snprintf(deststr, sizeof(deststr), "IAX2/%s", s1);
+#else
+ snprintf(deststr, sizeof(deststr), "IAX2/%s", s1);
+#endif
+ tele = strchr(deststr, '/');
+ if (!tele){
+ ast_log(LOG_WARNING,"link3:Dial number (%s) must be in format tech/number\n",deststr);
+ free(l);
+ return -1;
+ }
+ *tele++ = 0;
+ l->chan = ast_request(deststr, AST_FORMAT_SLINEAR, tele,NULL);
+ if (l->chan){
+ ast_set_read_format(l->chan, AST_FORMAT_SLINEAR);
+ ast_set_write_format(l->chan, AST_FORMAT_SLINEAR);
+#ifdef AST_CDR_FLAG_POST_DISABLED
+ ast_set_flag(l->chan->cdr,AST_CDR_FLAG_POST_DISABLED);
+#endif
+ l->chan->whentohangup = 0;
+ l->chan->appl = "Apprpt";
+ l->chan->data = "(Remote Rx)";
+ if (debug > 3)
+ ast_log(LOG_NOTICE, "rpt (remote) initiating call to %s/%s on %s\n",
+ deststr, tele, l->chan->name);
+ if(l->chan->cid.cid_num)
+ free(l->chan->cid.cid_num);
+ l->chan->cid.cid_num = strdup(myrpt->name);
+ ast_call(l->chan,tele,999);
+ }
+ else {
+ if(debug > 3)
+ ast_log(LOG_NOTICE, "Unable to place call to %s/%s on %s\n",
+ deststr,tele,l->chan->name);
+ if (myrpt->p.archivedir)
+ {
+ char str[100];
+ sprintf(str,"LINKFAIL,%s",l->name);
+ donodelog(myrpt,str);
+ }
+ free(l);
+ return -1;
+ }
+ /* allocate a pseudo-channel thru asterisk */
+ l->pchan = ast_request("zap",AST_FORMAT_SLINEAR,"pseudo",NULL);
+ if (!l->pchan){
+ ast_log(LOG_WARNING,"rpt connect: Sorry unable to obtain pseudo channel\n");
+ ast_hangup(l->chan);
+ free(l);
+ return -1;
+ }
+ ast_set_read_format(l->pchan, AST_FORMAT_SLINEAR);
+ ast_set_write_format(l->pchan, AST_FORMAT_SLINEAR);
+#ifdef AST_CDR_FLAG_POST_DISABLED
+ ast_set_flag(l->pchan->cdr,AST_CDR_FLAG_POST_DISABLED);
+#endif
+ /* make a conference for the tx */
+ ci.chan = 0;
+ ci.confno = myrpt->conf;
+ ci.confmode = DAHDI_CONF_CONF | DAHDI_CONF_LISTENER | DAHDI_CONF_TALKER;
+ /* first put the channel on the conference in proper mode */
+ if (ioctl(l->pchan->fds[0], DAHDI_SETCONF, &ci) == -1)
+ {
+ ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n");
+ ast_hangup(l->chan);
+ ast_hangup(l->pchan);
+ free(l);
+ return -1;
+ }
+ rpt_mutex_lock(&myrpt->lock);
+ l->reconnects = reconnects;
+ /* insert at end of queue */
+ l->max_retries = MAX_RETRIES;
+ if (perma)
+ l->max_retries = MAX_RETRIES_PERM;
+ if (l->isremote) l->retries = l->max_retries + 1;
+ insque((struct qelem *)l,(struct qelem *)myrpt->links.next);
+ __kickshort(myrpt);
+ rpt_mutex_unlock(&myrpt->lock);
+ return 0;
+}
+
+
+
+/*
+* Internet linking function
+*/
+
+static int function_ilink(struct rpt *myrpt, char *param, char *digits, int command_source, struct rpt_link *mylink)
+{
+
+ char *val, *s, *s1, *s2;
+ char tmp[300];
+ char digitbuf[MAXNODESTR],*strs[MAXLINKLIST];
+ char mode,perma;
+ struct rpt_link *l;
+ int i,r;
+
+ if(!param)
+ return DC_ERROR;
+
+
+ if (myrpt->p.s[myrpt->p.sysstate_cur].txdisable || myrpt->p.s[myrpt->p.sysstate_cur].linkfundisable )
+ return DC_ERROR;
+
+ strncpy(digitbuf,digits,MAXNODESTR - 1);
+
+ if(debug > 6)
+ printf("@@@@ ilink param = %s, digitbuf = %s\n", (param)? param : "(null)", digitbuf);
+
+ switch(myatoi(param)){
+ case 11: /* Perm Link off */
+ case 1: /* Link off */
+ if ((digitbuf[0] == '0') && (myrpt->lastlinknode[0]))
+ strcpy(digitbuf,myrpt->lastlinknode);
+ val = node_lookup(myrpt,digitbuf);
+ if (!val){
+ if(strlen(digitbuf) >= myrpt->longestnode)
+ return DC_ERROR;
+ break;
+ }
+ strncpy(tmp,val,sizeof(tmp) - 1);
+ s = tmp;
+ s1 = strsep(&s,",");
+ s2 = strsep(&s,",");
+ rpt_mutex_lock(&myrpt->lock);
+ l = myrpt->links.next;
+ /* try to find this one in queue */
+ while(l != &myrpt->links){
+ if (l->name[0] == '0')
+ {
+ l = l->next;
+ continue;
+ }
+ /* if found matching string */
+ if (!strcmp(l->name, digitbuf))
+ break;
+ l = l->next;
+ }
+ if (l != &myrpt->links){ /* if found */
+ struct ast_frame wf;
+
+ /* must use perm command on perm link */
+ if ((myatoi(param) < 10) &&
+ (l->max_retries > MAX_RETRIES))
+ {
+ rpt_mutex_unlock(&myrpt->lock);
+ return DC_COMPLETE;
+ }
+ strncpy(myrpt->lastlinknode,digitbuf,MAXNODESTR - 1);
+ l->retries = l->max_retries + 1;
+ l->disced = 1;
+ rpt_mutex_unlock(&myrpt->lock);
+ wf.frametype = AST_FRAME_TEXT;
+ wf.subclass = 0;
+ wf.offset = 0;
+ wf.mallocd = 0;
+ wf.datalen = strlen(discstr) + 1;
+ wf.samples = 0;
+ wf.data = discstr;
+ if (l->chan)
+ {
+ ast_write(l->chan,&wf);
+ if (ast_safe_sleep(l->chan,250) == -1) return DC_ERROR;
+ ast_softhangup(l->chan,AST_SOFTHANGUP_DEV);
+ }
+ rpt_telemetry(myrpt, COMPLETE, NULL);
+ return DC_COMPLETE;
+ }
+ rpt_mutex_unlock(&myrpt->lock);
+ return DC_COMPLETE;
+ case 2: /* Link Monitor */
+ case 3: /* Link transceive */
+ case 12: /* Link Monitor permanent */
+ case 13: /* Link transceive permanent */
+ if ((digitbuf[0] == '0') && (myrpt->lastlinknode[0]))
+ strcpy(digitbuf,myrpt->lastlinknode);
+ /* Attempt connection */
+ perma = (atoi(param) > 10) ? 1 : 0;
+ mode = (atoi(param) & 1) ? 1 : 0;
+ r = connect_link(myrpt, digitbuf, mode, perma);
+ switch(r){
+ case 0:
+ rpt_telemetry(myrpt, COMPLETE, NULL);
+ return DC_COMPLETE;
+
+ case 1:
+ break;
+
+ case 2:
+ rpt_telemetry(myrpt, REMALREADY, NULL);
+ return DC_COMPLETE;
+
+ default:
+ rpt_telemetry(myrpt, CONNFAIL, NULL);
+ return DC_COMPLETE;
+ }
+ break;
+
+ case 4: /* Enter Command Mode */
+
+ /* if doesnt allow link cmd, or no links active, return */
+ if (((command_source != SOURCE_RPT) &&
+ (command_source != SOURCE_PHONE) &&
+ (command_source != SOURCE_DPHONE)) ||
+ (myrpt->links.next == &myrpt->links))
+ return DC_COMPLETE;
+
+ /* if already in cmd mode, or selected self, fughetabahtit */
+ if ((myrpt->cmdnode[0]) || (!strcmp(myrpt->name, digitbuf))){
+
+ rpt_telemetry(myrpt, REMALREADY, NULL);
+ return DC_COMPLETE;
+ }
+ if ((digitbuf[0] == '0') && (myrpt->lastlinknode[0]))
+ strcpy(digitbuf,myrpt->lastlinknode);
+ /* node must at least exist in list */
+ val = node_lookup(myrpt,digitbuf);
+ if (!val){
+ if(strlen(digitbuf) >= myrpt->longestnode)
+ return DC_ERROR;
+ break;
+
+ }
+ rpt_mutex_lock(&myrpt->lock);
+ strcpy(myrpt->lastlinknode,digitbuf);
+ strncpy(myrpt->cmdnode, digitbuf, sizeof(myrpt->cmdnode) - 1);
+ rpt_mutex_unlock(&myrpt->lock);
+ rpt_telemetry(myrpt, REMGO, NULL);
+ return DC_COMPLETE;
+
+ case 5: /* Status */
+ rpt_telemetry(myrpt, STATUS, NULL);
+ return DC_COMPLETE;
+
+ case 15: /* Full Status */
+ rpt_telemetry(myrpt, FULLSTATUS, NULL);
+ return DC_COMPLETE;
+
+
+ case 6: /* All Links Off, including permalinks */
+ rpt_mutex_lock(&myrpt->lock);
+ myrpt->savednodes[0] = 0;
+ l = myrpt->links.next;
+ /* loop through all links */
+ while(l != &myrpt->links){
+ struct ast_frame wf;
+ if (l->name[0] == '0') /* Skip any IAXRPT monitoring */
+ {
+ l = l->next;
+ continue;
+ }
+ /* Make a string of disconnected nodes for possible restoration */
+ sprintf(tmp,"%c%c%s",(l->mode) ? 'X' : 'M',(l->perma) ? 'P':'T',l->name);
+ if(strlen(tmp) + strlen(myrpt->savednodes) + 1 < MAXNODESTR){
+ if(myrpt->savednodes[0])
+ strcat(myrpt->savednodes, ",");
+ strcat(myrpt->savednodes, tmp);
+ }
+ l->retries = l->max_retries + 1;
+ l->disced = 2; /* Silently disconnect */
+ rpt_mutex_unlock(&myrpt->lock);
+ /* ast_log(LOG_NOTICE,"dumping link %s\n",l->name); */
+
+ wf.frametype = AST_FRAME_TEXT;
+ wf.subclass = 0;
+ wf.offset = 0;
+ wf.mallocd = 0;
+ wf.datalen = strlen(discstr) + 1;
+ wf.samples = 0;
+ wf.data = discstr;
+ if (l->chan)
+ {
+ ast_write(l->chan,&wf);
+ ast_safe_sleep(l->chan,250); /* It's dead already, why check the return value? */
+ ast_softhangup(l->chan,AST_SOFTHANGUP_DEV);
+ }
+ rpt_mutex_lock(&myrpt->lock);
+ l = l->next;
+ }
+ rpt_mutex_unlock(&myrpt->lock);
+ if(debug > 3)
+ ast_log(LOG_NOTICE,"Nodes disconnected: %s\n",myrpt->savednodes);
+ rpt_telemetry(myrpt, COMPLETE, NULL);
+ return DC_COMPLETE;
+
+ case 7: /* Identify last node which keyed us up */
+ rpt_telemetry(myrpt, LASTNODEKEY, NULL);
+ break;
+
+
+#ifdef _MDC_DECODE_H_
+ case 8:
+ myrpt->lastunit = 0xd00d;
+ mdc1200_notify(myrpt,NULL,myrpt->lastunit);
+ mdc1200_send(myrpt,myrpt->lastunit);
+ break;
+#endif
+
+ case 16: /* Restore links disconnected with "disconnect all links" command */
+ strcpy(tmp, myrpt->savednodes); /* Make a copy */
+ finddelim(tmp, strs, MAXLINKLIST); /* convert into substrings */
+ for(i = 0; tmp[0] && strs[i] != NULL && i < MAXLINKLIST; i++){
+ s1 = strs[i];
+ mode = (s1[0] == 'X') ? 1 : 0;
+ perma = (s1[1] == 'P') ? 1 : 0;
+ connect_link(myrpt, s1 + 2, mode, perma); /* Try to reconnect */
+ }
+ rpt_telemetry(myrpt, COMPLETE, NULL);
+ break;
+
+ case 200:
+ case 201:
+ case 202:
+ case 203:
+ case 204:
+ case 205:
+ case 206:
+ case 207:
+ case 208:
+ case 209:
+ case 210:
+ case 211:
+ case 212:
+ case 213:
+ case 214:
+ case 215:
+ if (((myrpt->p.propagate_dtmf) &&
+ (command_source == SOURCE_LNK)) ||
+ ((myrpt->p.propagate_phonedtmf) &&
+ ((command_source == SOURCE_PHONE) ||
+ (command_source == SOURCE_DPHONE))))
+ do_dtmf_local(myrpt,
+ remdtmfstr[myatoi(param) - 200]);
+ default:
+ return DC_ERROR;
+
+ }
+
+ return DC_INDETERMINATE;
+}
+
+/*
+* Autopatch up
+*/
+
+static int function_autopatchup(struct rpt *myrpt, char *param, char *digitbuf, int command_source, struct rpt_link *mylink)
+{
+ pthread_attr_t attr;
+ int i, index, paramlength;
+ char *lparam;
+ char *value = NULL;
+ char *paramlist[20];
+
+ static char *keywords[] = {
+ "context",
+ "dialtime",
+ "farenddisconnect",
+ "noct",
+ "quiet",
+ NULL
+ };
+
+ if (myrpt->p.s[myrpt->p.sysstate_cur].txdisable || myrpt->p.s[myrpt->p.sysstate_cur].autopatchdisable)
+ return DC_ERROR;
+
+ if(debug)
+ printf("@@@@ Autopatch up\n");
+
+ if(!myrpt->callmode){
+ /* Set defaults */
+ myrpt->patchnoct = 0;
+ myrpt->patchdialtime = 0;
+ myrpt->patchfarenddisconnect = 0;
+ myrpt->patchquiet = 0;
+ strncpy(myrpt->patchcontext, myrpt->p.ourcontext, MAXPATCHCONTEXT);
+
+ if(param){
+ /* Process parameter list */
+ lparam = ast_strdupa(param);
+ if(!lparam){
+ ast_log(LOG_ERROR,"App_rpt out of memory on line %d\n",__LINE__);
+ return DC_ERROR;
+ }
+ paramlength = finddelim(lparam, paramlist, 20);
+ for(i = 0; i < paramlength; i++){
+ index = matchkeyword(paramlist[i], &value, keywords);
+ if(value)
+ value = skipchars(value, "= ");
+ switch(index){
+
+ case 1: /* context */
+ strncpy(myrpt->patchcontext, value, MAXPATCHCONTEXT - 1) ;
+ break;
+
+ case 2: /* dialtime */
+ myrpt->patchdialtime = atoi(value);
+ break;
+
+ case 3: /* farenddisconnect */
+ myrpt->patchfarenddisconnect = atoi(value);
+ break;
+
+ case 4: /* noct */
+ myrpt->patchnoct = atoi(value);
+ break;
+
+ case 5: /* quiet */
+ myrpt->patchquiet = atoi(value);
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ rpt_mutex_lock(&myrpt->lock);
+
+ /* if on call, force * into current audio stream */
+
+ if ((myrpt->callmode == 2) || (myrpt->callmode == 3)){
+ myrpt->mydtmf = myrpt->p.endchar;
+ }
+ if (myrpt->callmode){
+ rpt_mutex_unlock(&myrpt->lock);
+ return DC_COMPLETE;
+ }
+ myrpt->callmode = 1;
+ myrpt->cidx = 0;
+ myrpt->exten[myrpt->cidx] = 0;
+ rpt_mutex_unlock(&myrpt->lock);
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ ast_pthread_create(&myrpt->rpt_call_thread,&attr,rpt_call,(void *) myrpt);
+ return DC_COMPLETE;
+}
+
+/*
+* Autopatch down
+*/
+
+static int function_autopatchdn(struct rpt *myrpt, char *param, char *digitbuf, int command_source, struct rpt_link *mylink)
+{
+ if (myrpt->p.s[myrpt->p.sysstate_cur].txdisable || myrpt->p.s[myrpt->p.sysstate_cur].autopatchdisable)
+ return DC_ERROR;
+
+ if(debug)
+ printf("@@@@ Autopatch down\n");
+
+ rpt_mutex_lock(&myrpt->lock);
+
+ if (!myrpt->callmode){
+ rpt_mutex_unlock(&myrpt->lock);
+ return DC_COMPLETE;
+ }
+
+ myrpt->callmode = 0;
+ rpt_mutex_unlock(&myrpt->lock);
+ rpt_telemetry(myrpt, TERM, NULL);
+ return DC_COMPLETE;
+}
+
+/*
+* Status
+*/
+
+static int function_status(struct rpt *myrpt, char *param, char *digitbuf, int command_source, struct rpt_link *mylink)
+{
+
+ if (!param)
+ return DC_ERROR;
+
+ if ((myrpt->p.s[myrpt->p.sysstate_cur].txdisable) || (myrpt->p.s[myrpt->p.sysstate_cur].userfundisable))
+ return DC_ERROR;
+
+ if(debug)
+ printf("@@@@ status param = %s, digitbuf = %s\n", (param)? param : "(null)", digitbuf);
+
+ switch(myatoi(param)){
+ case 1: /* System ID */
+ rpt_telemetry(myrpt, ID1, NULL);
+ return DC_COMPLETE;
+ case 2: /* System Time */
+ rpt_telemetry(myrpt, STATS_TIME, NULL);
+ return DC_COMPLETE;
+ case 3: /* app_rpt.c version */
+ rpt_telemetry(myrpt, STATS_VERSION, NULL);
+ default:
+ return DC_ERROR;
+ }
+ return DC_INDETERMINATE;
+}
+
+/*
+* Macro-oni (without Salami)
+*/
+
+static int function_macro(struct rpt *myrpt, char *param, char *digitbuf, int command_source, struct rpt_link *mylink)
+{
+
+char *val;
+int i;
+ if (myrpt->remote)
+ return DC_ERROR;
+
+ if(debug)
+ printf("@@@@ macro-oni param = %s, digitbuf = %s\n", (param)? param : "(null)", digitbuf);
+
+ if(strlen(digitbuf) < 1) /* needs 1 digit */
+ return DC_INDETERMINATE;
+
+ for(i = 0 ; i < digitbuf[i] ; i++) {
+ if((digitbuf[i] < '0') || (digitbuf[i] > '9'))
+ return DC_ERROR;
+ }
+
+ if (*digitbuf == '0') val = myrpt->p.startupmacro;
+ else val = (char *) ast_variable_retrieve(myrpt->cfg, myrpt->p.macro, digitbuf);
+ /* param was 1 for local buf */
+ if (!val){
+ if (strlen(digitbuf) < myrpt->macro_longest)
+ return DC_INDETERMINATE;
+ rpt_telemetry(myrpt, MACRO_NOTFOUND, NULL);
+ return DC_COMPLETE;
+ }
+ rpt_mutex_lock(&myrpt->lock);
+ if ((MAXMACRO - strlen(myrpt->macrobuf)) < strlen(val))
+ {
+ rpt_mutex_unlock(&myrpt->lock);
+ rpt_telemetry(myrpt, MACRO_BUSY, NULL);
+ return DC_ERROR;
+ }
+ myrpt->macrotimer = MACROTIME;
+ strncat(myrpt->macrobuf, val, MAXMACRO - strlen(myrpt->macrobuf) - 1);
+ rpt_mutex_unlock(&myrpt->lock);
+ return DC_COMPLETE;
+}
+
+/*
+* COP - Control operator
+*/
+
+static int function_cop(struct rpt *myrpt, char *param, char *digitbuf, int command_source, struct rpt_link *mylink)
+{
+ char string[16];
+
+ if(!param)
+ return DC_ERROR;
+
+ switch(myatoi(param)){
+ case 1: /* System reset */
+ system("killall -9 asterisk");
+ return DC_COMPLETE;
+
+ case 2:
+ myrpt->p.s[myrpt->p.sysstate_cur].txdisable = 0;
+ rpt_telemetry(myrpt, ARB_ALPHA, (void *) "RPTENA");
+ return DC_COMPLETE;
+
+ case 3:
+ myrpt->p.s[myrpt->p.sysstate_cur].txdisable = 1;
+ return DC_COMPLETE;
+
+ case 4: /* test tone on */
+ if (myrpt->stopgen < 0)
+ {
+ myrpt->stopgen = 1;
+ }
+ else
+ {
+ myrpt->stopgen = 0;
+ rpt_telemetry(myrpt, TEST_TONE, NULL);
+ }
+ return DC_COMPLETE;
+
+ case 5: /* Disgorge variables to log for debug purposes */
+ myrpt->disgorgetime = time(NULL) + 10; /* Do it 10 seconds later */
+ return DC_COMPLETE;
+
+ case 6: /* Simulate COR being activated (phone only) */
+ if (command_source != SOURCE_PHONE) return DC_INDETERMINATE;
+ return DC_DOKEY;
+
+
+ case 7: /* Time out timer enable */
+ myrpt->p.s[myrpt->p.sysstate_cur].totdisable = 0;
+ rpt_telemetry(myrpt, ARB_ALPHA, (void *) "TOTENA");
+ return DC_COMPLETE;
+
+ case 8: /* Time out timer disable */
+ myrpt->p.s[myrpt->p.sysstate_cur].totdisable = 1;
+ rpt_telemetry(myrpt, ARB_ALPHA, (void *) "TOTDIS");
+ return DC_COMPLETE;
+
+ case 9: /* Autopatch enable */
+ myrpt->p.s[myrpt->p.sysstate_cur].autopatchdisable = 0;
+ rpt_telemetry(myrpt, ARB_ALPHA, (void *) "APENA");
+ return DC_COMPLETE;
+
+ case 10: /* Autopatch disable */
+ myrpt->p.s[myrpt->p.sysstate_cur].autopatchdisable = 1;
+ rpt_telemetry(myrpt, ARB_ALPHA, (void *) "APDIS");
+ return DC_COMPLETE;
+
+ case 11: /* Link Enable */
+ myrpt->p.s[myrpt->p.sysstate_cur].linkfundisable = 0;
+ rpt_telemetry(myrpt, ARB_ALPHA, (void *) "LNKENA");
+ return DC_COMPLETE;
+
+ case 12: /* Link Disable */
+ myrpt->p.s[myrpt->p.sysstate_cur].linkfundisable = 1;
+ rpt_telemetry(myrpt, ARB_ALPHA, (void *) "LNKDIS");
+ return DC_COMPLETE;
+
+ case 13: /* Query System State */
+ string[0] = string[1] = 'S';
+ string[2] = myrpt->p.sysstate_cur + '0';
+ string[3] = '\0';
+ rpt_telemetry(myrpt, ARB_ALPHA, (void *) string);
+ return DC_COMPLETE;
+
+ case 14: /* Change System State */
+ if(strlen(digitbuf) == 0)
+ break;
+ if((digitbuf[0] < '0') || (digitbuf[0] > '9'))
+ return DC_ERROR;
+ myrpt->p.sysstate_cur = digitbuf[0] - '0';
+ string[0] = string[1] = 'S';
+ string[2] = myrpt->p.sysstate_cur + '0';
+ string[3] = '\0';
+ rpt_telemetry(myrpt, ARB_ALPHA, (void *) string);
+ return DC_COMPLETE;
+
+ case 15: /* Scheduler Enable */
+ myrpt->p.s[myrpt->p.sysstate_cur].schedulerdisable = 0;
+ rpt_telemetry(myrpt, ARB_ALPHA, (void *) "SKENA");
+ return DC_COMPLETE;
+
+ case 16: /* Scheduler Disable */
+ myrpt->p.s[myrpt->p.sysstate_cur].schedulerdisable = 1;
+ rpt_telemetry(myrpt, ARB_ALPHA, (void *) "SKDIS");
+ return DC_COMPLETE;
+
+ case 17: /* User functions Enable */
+ myrpt->p.s[myrpt->p.sysstate_cur].userfundisable = 0;
+ rpt_telemetry(myrpt, ARB_ALPHA, (void *) "UFENA");
+ return DC_COMPLETE;
+
+ case 18: /* User Functions Disable */
+ myrpt->p.s[myrpt->p.sysstate_cur].userfundisable = 1;
+ rpt_telemetry(myrpt, ARB_ALPHA, (void *) "UFDIS");
+ return DC_COMPLETE;
+
+ case 19: /* Alternate Tail Enable */
+ myrpt->p.s[myrpt->p.sysstate_cur].alternatetail = 1;
+ rpt_telemetry(myrpt, ARB_ALPHA, (void *) "ATENA");
+ return DC_COMPLETE;
+
+ case 20: /* Alternate Tail Disable */
+ myrpt->p.s[myrpt->p.sysstate_cur].alternatetail = 0;
+ rpt_telemetry(myrpt, ARB_ALPHA, (void *) "ATDIS");
+ return DC_COMPLETE;
+ }
+ return DC_INDETERMINATE;
+}
+
+/*
+* Collect digits one by one until something matches
+*/
+
+static int collect_function_digits(struct rpt *myrpt, char *digits,
+ int command_source, struct rpt_link *mylink)
+{
+ int i;
+ char *stringp,*action,*param,*functiondigits;
+ char function_table_name[30] = "";
+ char workstring[200];
+
+ struct ast_variable *vp;
+
+ if(debug)
+ printf("@@@@ Digits collected: %s, source: %d\n", digits, command_source);
+
+ if (command_source == SOURCE_DPHONE) {
+ if (!myrpt->p.dphone_functions) return DC_INDETERMINATE;
+ strncpy(function_table_name, myrpt->p.dphone_functions, sizeof(function_table_name) - 1);
+ }
+ else if (command_source == SOURCE_PHONE) {
+ if (!myrpt->p.phone_functions) return DC_INDETERMINATE;
+ strncpy(function_table_name, myrpt->p.phone_functions, sizeof(function_table_name) - 1);
+ }
+ else if (command_source == SOURCE_LNK)
+ strncpy(function_table_name, myrpt->p.link_functions, sizeof(function_table_name) - 1);
+ else
+ strncpy(function_table_name, myrpt->p.functions, sizeof(function_table_name) - 1);
+ vp = ast_variable_browse(myrpt->cfg, function_table_name);
+ while(vp) {
+ if(!strncasecmp(vp->name, digits, strlen(vp->name)))
+ break;
+ vp = vp->next;
+ }
+ if(!vp) {
+ int n;
+
+ n = myrpt->longestfunc;
+ if (command_source == SOURCE_LNK) n = myrpt->link_longestfunc;
+ else
+ if (command_source == SOURCE_PHONE) n = myrpt->phone_longestfunc;
+ else
+ if (command_source == SOURCE_DPHONE) n = myrpt->dphone_longestfunc;
+
+ if(strlen(digits) >= n)
+ return DC_ERROR;
+ else
+ return DC_INDETERMINATE;
+ }
+ /* Found a match, retrieve value part and parse */
+ strncpy(workstring, vp->value, sizeof(workstring) - 1 );
+ stringp = workstring;
+ action = strsep(&stringp, ",");
+ param = stringp;
+ if(debug)
+ printf("@@@@ action: %s, param = %s\n",action, (param) ? param : "(null)");
+ /* Look up the action */
+ for(i = 0 ; i < (sizeof(function_table)/sizeof(struct function_table_tag)); i++){
+ if(!strncasecmp(action, function_table[i].action, strlen(action)))
+ break;
+ }
+ if(debug)
+ printf("@@@@ table index i = %d\n",i);
+ if(i == (sizeof(function_table)/sizeof(struct function_table_tag))){
+ /* Error, action not in table */
+ return DC_ERROR;
+ }
+ if(function_table[i].function == NULL){
+ /* Error, function undefined */
+ if(debug)
+ printf("@@@@ NULL for action: %s\n",action);
+ return DC_ERROR;
+ }
+ functiondigits = digits + strlen(vp->name);
+ return (*function_table[i].function)(myrpt, param, functiondigits, command_source, mylink);
+}
+
+
+static void handle_link_data(struct rpt *myrpt, struct rpt_link *mylink,
+ char *str)
+{
+char tmp[512],cmd[300] = "",dest[300],src[300],c;
+int seq, res;
+struct rpt_link *l;
+struct ast_frame wf;
+
+ wf.frametype = AST_FRAME_TEXT;
+ wf.subclass = 0;
+ wf.offset = 0;
+ wf.mallocd = 0;
+ wf.datalen = strlen(str) + 1;
+ wf.samples = 0;
+ /* put string in our buffer */
+ strncpy(tmp,str,sizeof(tmp) - 1);
+
+ if (!strcmp(tmp,discstr))
+ {
+ mylink->disced = 1;
+ mylink->retries = mylink->max_retries + 1;
+ ast_softhangup(mylink->chan,AST_SOFTHANGUP_DEV);
+ return;
+ }
+ if (tmp[0] == 'L')
+ {
+ rpt_mutex_lock(&myrpt->lock);
+ strcpy(mylink->linklist,tmp + 2);
+ time(&mylink->linklistreceived);
+ rpt_mutex_unlock(&myrpt->lock);
+ if (debug > 6) ast_log(LOG_NOTICE,"@@@@ node %s recieved node list %s from node %s\n",
+ myrpt->name,tmp,mylink->name);
+ return;
+ }
+ if (tmp[0] == 'I')
+ {
+ if (sscanf(tmp,"%s %s %x",cmd,src,&seq) != 3)
+ {
+ ast_log(LOG_WARNING, "Unable to parse ident string %s\n",str);
+ return;
+ }
+ mdc1200_notify(myrpt,src,seq);
+ strcpy(dest,"*");
+ }
+ else
+ {
+ if (sscanf(tmp,"%s %s %s %d %c",cmd,dest,src,&seq,&c) != 5)
+ {
+ ast_log(LOG_WARNING, "Unable to parse link string %s\n",str);
+ return;
+ }
+ if (strcmp(cmd,"D"))
+ {
+ ast_log(LOG_WARNING, "Unable to parse link string %s\n",str);
+ return;
+ }
+ }
+ if (dest[0] == '0')
+ {
+ strcpy(dest,myrpt->name);
+ }
+
+ /* if not for me, redistribute to all links */
+ if (strcmp(dest,myrpt->name))
+ {
+ l = myrpt->links.next;
+ /* see if this is one in list */
+ while(l != &myrpt->links)
+ {
+ if (l->name[0] == '0')
+ {
+ l = l->next;
+ continue;
+ }
+ /* dont send back from where it came */
+ if ((l == mylink) || (!strcmp(l->name,mylink->name)))
+ {
+ l = l->next;
+ continue;
+ }
+ /* if it is, send it and we're done */
+ if (!strcmp(l->name,dest))
+ {
+ /* send, but not to src */
+ if (strcmp(l->name,src)) {
+ wf.data = str;
+ if (l->chan) ast_write(l->chan,&wf);
+ }
+ return;
+ }
+ l = l->next;
+ }
+ l = myrpt->links.next;
+ /* otherwise, send it to all of em */
+ while(l != &myrpt->links)
+ {
+ if (l->name[0] == '0')
+ {
+ l = l->next;
+ continue;
+ }
+ /* dont send back from where it came */
+ if ((l == mylink) || (!strcmp(l->name,mylink->name)))
+ {
+ l = l->next;
+ continue;
+ }
+ /* send, but not to src */
+ if (strcmp(l->name,src)) {
+ wf.data = str;
+ if (l->chan) ast_write(l->chan,&wf);
+ }
+ l = l->next;
+ }
+ return;
+ }
+ if (myrpt->p.archivedir)
+ {
+ char str[100];
+
+ sprintf(str,"DTMF,%s,%c",mylink->name,c);
+ donodelog(myrpt,str);
+ }
+ c = func_xlat(myrpt,c,&myrpt->p.outxlat);
+ if (!c) return;
+ rpt_mutex_lock(&myrpt->lock);
+ if (c == myrpt->p.endchar) myrpt->stopgen = 1;
+ if (myrpt->callmode == 1)
+ {
+ myrpt->exten[myrpt->cidx++] = c;
+ myrpt->exten[myrpt->cidx] = 0;
+ /* if this exists */
+ if (ast_exists_extension(myrpt->pchannel,myrpt->patchcontext,myrpt->exten,1,NULL))
+ {
+ myrpt->callmode = 2;
+ if(!myrpt->patchquiet){
+ rpt_mutex_unlock(&myrpt->lock);
+ rpt_telemetry(myrpt,PROC,NULL);
+ rpt_mutex_lock(&myrpt->lock);
+ }
+ }
+ /* if can continue, do so */
+ if (!ast_canmatch_extension(myrpt->pchannel,myrpt->patchcontext,myrpt->exten,1,NULL))
+ {
+ /* call has failed, inform user */
+ myrpt->callmode = 4;
+ }
+ }
+ if (c == myrpt->p.funcchar)
+ {
+ myrpt->rem_dtmfidx = 0;
+ myrpt->rem_dtmfbuf[myrpt->rem_dtmfidx] = 0;
+ time(&myrpt->rem_dtmf_time);
+ rpt_mutex_unlock(&myrpt->lock);
+ return;
+ }
+ else if (myrpt->rem_dtmfidx < 0)
+ {
+ if ((myrpt->callmode == 2) || (myrpt->callmode == 3))
+ {
+ myrpt->mydtmf = c;
+ }
+ if (myrpt->p.propagate_dtmf) do_dtmf_local(myrpt,c);
+ if (myrpt->p.propagate_phonedtmf) do_dtmf_phone(myrpt,mylink,c);
+ rpt_mutex_unlock(&myrpt->lock);
+ return;
+ }
+ else if ((c != myrpt->p.endchar) && (myrpt->rem_dtmfidx >= 0))
+ {
+ time(&myrpt->rem_dtmf_time);
+ if (myrpt->rem_dtmfidx < MAXDTMF)
+ {
+ myrpt->rem_dtmfbuf[myrpt->rem_dtmfidx++] = c;
+ myrpt->rem_dtmfbuf[myrpt->rem_dtmfidx] = 0;
+
+ rpt_mutex_unlock(&myrpt->lock);
+ strncpy(cmd, myrpt->rem_dtmfbuf, sizeof(cmd) - 1);
+ res = collect_function_digits(myrpt, cmd, SOURCE_LNK, mylink);
+ rpt_mutex_lock(&myrpt->lock);
+
+ switch(res){
+
+ case DC_INDETERMINATE:
+ break;
+
+ case DC_REQ_FLUSH:
+ myrpt->rem_dtmfidx = 0;
+ myrpt->rem_dtmfbuf[0] = 0;
+ break;
+
+
+ case DC_COMPLETE:
+ case DC_COMPLETEQUIET:
+ myrpt->totalexecdcommands++;
+ myrpt->dailyexecdcommands++;
+ strncpy(myrpt->lastdtmfcommand, cmd, MAXDTMF-1);
+ myrpt->lastdtmfcommand[MAXDTMF-1] = '\0';
+ myrpt->rem_dtmfbuf[0] = 0;
+ myrpt->rem_dtmfidx = -1;
+ myrpt->rem_dtmf_time = 0;
+ break;
+
+ case DC_ERROR:
+ default:
+ myrpt->rem_dtmfbuf[0] = 0;
+ myrpt->rem_dtmfidx = -1;
+ myrpt->rem_dtmf_time = 0;
+ break;
+ }
+ }
+
+ }
+ rpt_mutex_unlock(&myrpt->lock);
+ return;
+}
+
+static void handle_link_phone_dtmf(struct rpt *myrpt, struct rpt_link *mylink,
+ char c)
+{
+
+char cmd[300];
+int res;
+
+ if (myrpt->p.archivedir)
+ {
+ char str[100];
+
+ sprintf(str,"DTMF(P),%s,%c",mylink->name,c);
+ donodelog(myrpt,str);
+ }
+ rpt_mutex_lock(&myrpt->lock);
+ if (c == myrpt->p.endchar)
+ {
+ if (mylink->lastrx)
+ {
+ mylink->lastrx = 0;
+ rpt_mutex_unlock(&myrpt->lock);
+ return;
+ }
+ myrpt->stopgen = 1;
+ if (myrpt->cmdnode[0])
+ {
+ myrpt->cmdnode[0] = 0;
+ myrpt->dtmfidx = -1;
+ myrpt->dtmfbuf[0] = 0;
+ rpt_mutex_unlock(&myrpt->lock);
+ rpt_telemetry(myrpt,COMPLETE,NULL);
+ return;
+ }
+ }
+ if (myrpt->cmdnode[0])
+ {
+ rpt_mutex_unlock(&myrpt->lock);
+ send_link_dtmf(myrpt,c);
+ return;
+ }
+ if (myrpt->callmode == 1)
+ {
+ myrpt->exten[myrpt->cidx++] = c;
+ myrpt->exten[myrpt->cidx] = 0;
+ /* if this exists */
+ if (ast_exists_extension(myrpt->pchannel,myrpt->patchcontext,myrpt->exten,1,NULL))
+ {
+ myrpt->callmode = 2;
+ if(!myrpt->patchquiet){
+ rpt_mutex_unlock(&myrpt->lock);
+ rpt_telemetry(myrpt,PROC,NULL);
+ rpt_mutex_lock(&myrpt->lock);
+ }
+ }
+ /* if can continue, do so */
+ if (!ast_canmatch_extension(myrpt->pchannel,myrpt->patchcontext,myrpt->exten,1,NULL))
+ {
+ /* call has failed, inform user */
+ myrpt->callmode = 4;
+ }
+ }
+ if ((myrpt->callmode == 2) || (myrpt->callmode == 3))
+ {
+ myrpt->mydtmf = c;
+ }
+ if (c == myrpt->p.funcchar)
+ {
+ myrpt->rem_dtmfidx = 0;
+ myrpt->rem_dtmfbuf[myrpt->rem_dtmfidx] = 0;
+ time(&myrpt->rem_dtmf_time);
+ rpt_mutex_unlock(&myrpt->lock);
+ return;
+ }
+ else if ((c != myrpt->p.endchar) && (myrpt->rem_dtmfidx >= 0))
+ {
+ time(&myrpt->rem_dtmf_time);
+ if (myrpt->rem_dtmfidx < MAXDTMF)
+ {
+ myrpt->rem_dtmfbuf[myrpt->rem_dtmfidx++] = c;
+ myrpt->rem_dtmfbuf[myrpt->rem_dtmfidx] = 0;
+
+ rpt_mutex_unlock(&myrpt->lock);
+ strncpy(cmd, myrpt->rem_dtmfbuf, sizeof(cmd) - 1);
+ switch(mylink->phonemode)
+ {
+ case 1:
+ res = collect_function_digits(myrpt, cmd,
+ SOURCE_PHONE, mylink);
+ break;
+ case 2:
+ res = collect_function_digits(myrpt, cmd,
+ SOURCE_DPHONE,mylink);
+ break;
+ default:
+ res = collect_function_digits(myrpt, cmd,
+ SOURCE_LNK, mylink);
+ break;
+ }
+
+ rpt_mutex_lock(&myrpt->lock);
+
+ switch(res){
+
+ case DC_INDETERMINATE:
+ break;
+
+ case DC_DOKEY:
+ mylink->lastrx = 1;
+ break;
+
+ case DC_REQ_FLUSH:
+ myrpt->rem_dtmfidx = 0;
+ myrpt->rem_dtmfbuf[0] = 0;
+ break;
+
+
+ case DC_COMPLETE:
+ case DC_COMPLETEQUIET:
+ myrpt->totalexecdcommands++;
+ myrpt->dailyexecdcommands++;
+ strncpy(myrpt->lastdtmfcommand, cmd, MAXDTMF-1);
+ myrpt->lastdtmfcommand[MAXDTMF-1] = '\0';
+ myrpt->rem_dtmfbuf[0] = 0;
+ myrpt->rem_dtmfidx = -1;
+ myrpt->rem_dtmf_time = 0;
+ break;
+
+ case DC_ERROR:
+ default:
+ myrpt->rem_dtmfbuf[0] = 0;
+ myrpt->rem_dtmfidx = -1;
+ myrpt->rem_dtmf_time = 0;
+ break;
+ }
+ }
+
+ }
+ rpt_mutex_unlock(&myrpt->lock);
+ return;
+}
+
+/* Doug Hall RBI-1 serial data definitions:
+ *
+ * Byte 0: Expansion external outputs
+ * Byte 1:
+ * Bits 0-3 are BAND as follows:
+ * Bits 4-5 are POWER bits as follows:
+ * 00 - Low Power
+ * 01 - Hi Power
+ * 02 - Med Power
+ * Bits 6-7 are always set
+ * Byte 2:
+ * Bits 0-3 MHZ in BCD format
+ * Bits 4-5 are offset as follows:
+ * 00 - minus
+ * 01 - plus
+ * 02 - simplex
+ * 03 - minus minus (whatever that is)
+ * Bit 6 is the 0/5 KHZ bit
+ * Bit 7 is always set
+ * Byte 3:
+ * Bits 0-3 are 10 KHZ in BCD format
+ * Bits 4-7 are 100 KHZ in BCD format
+ * Byte 4: PL Tone code and encode/decode enable bits
+ * Bits 0-5 are PL tone code (comspec binary codes)
+ * Bit 6 is encode enable/disable
+ * Bit 7 is decode enable/disable
+ */
+
+/* take the frequency from the 10 mhz digits (and up) and convert it
+ to a band number */
+
+static int rbi_mhztoband(char *str)
+{
+int i;
+
+ i = atoi(str) / 10; /* get the 10's of mhz */
+ switch(i)
+ {
+ case 2:
+ return 10;
+ case 5:
+ return 11;
+ case 14:
+ return 2;
+ case 22:
+ return 3;
+ case 44:
+ return 4;
+ case 124:
+ return 0;
+ case 125:
+ return 1;
+ case 126:
+ return 8;
+ case 127:
+ return 5;
+ case 128:
+ return 6;
+ case 129:
+ return 7;
+ default:
+ break;
+ }
+ return -1;
+}
+
+/* take a PL frequency and turn it into a code */
+static int rbi_pltocode(char *str)
+{
+int i;
+char *s;
+
+ s = strchr(str,'.');
+ i = 0;
+ if (s) i = atoi(s + 1);
+ i += atoi(str) * 10;
+ switch(i)
+ {
+ case 670:
+ return 0;
+ case 719:
+ return 1;
+ case 744:
+ return 2;
+ case 770:
+ return 3;
+ case 797:
+ return 4;
+ case 825:
+ return 5;
+ case 854:
+ return 6;
+ case 885:
+ return 7;
+ case 915:
+ return 8;
+ case 948:
+ return 9;
+ case 974:
+ return 10;
+ case 1000:
+ return 11;
+ case 1035:
+ return 12;
+ case 1072:
+ return 13;
+ case 1109:
+ return 14;
+ case 1148:
+ return 15;
+ case 1188:
+ return 16;
+ case 1230:
+ return 17;
+ case 1273:
+ return 18;
+ case 1318:
+ return 19;
+ case 1365:
+ return 20;
+ case 1413:
+ return 21;
+ case 1462:
+ return 22;
+ case 1514:
+ return 23;
+ case 1567:
+ return 24;
+ case 1622:
+ return 25;
+ case 1679:
+ return 26;
+ case 1738:
+ return 27;
+ case 1799:
+ return 28;
+ case 1862:
+ return 29;
+ case 1928:
+ return 30;
+ case 2035:
+ return 31;
+ case 2107:
+ return 32;
+ case 2181:
+ return 33;
+ case 2257:
+ return 34;
+ case 2336:
+ return 35;
+ case 2418:
+ return 36;
+ case 2503:
+ return 37;
+ }
+ return -1;
+}
+
+/*
+* Shift out a formatted serial bit stream
+*/
+
+static void rbi_out_parallel(struct rpt *myrpt,unsigned char *data)
+ {
+#ifdef __i386__
+ int i,j;
+ unsigned char od,d;
+ static volatile long long delayvar;
+
+ for(i = 0 ; i < 5 ; i++){
+ od = *data++;
+ for(j = 0 ; j < 8 ; j++){
+ d = od & 1;
+ outb(d,myrpt->p.iobase);
+ /* >= 15 us */
+ for(delayvar = 1; delayvar < 15000; delayvar++);
+ od >>= 1;
+ outb(d | 2,myrpt->p.iobase);
+ /* >= 30 us */
+ for(delayvar = 1; delayvar < 30000; delayvar++);
+ outb(d,myrpt->p.iobase);
+ /* >= 10 us */
+ for(delayvar = 1; delayvar < 10000; delayvar++);
+ }
+ }
+ /* >= 50 us */
+ for(delayvar = 1; delayvar < 50000; delayvar++);
+#endif
+ }
+
+static void rbi_out(struct rpt *myrpt,unsigned char *data)
+{
+struct dahdi_radio_param r;
+
+ memset(&r,0,sizeof(struct dahdi_radio_param));
+ r.radpar = DAHDI_RADPAR_REMMODE;
+ r.data = DAHDI_RADPAR_REM_RBI1;
+ /* if setparam ioctl fails, its probably not a pciradio card */
+ if (ioctl(myrpt->zaprxchannel->fds[0],DAHDI_RADIO_SETPARAM,&r) == -1)
+ {
+ rbi_out_parallel(myrpt,data);
+ return;
+ }
+ r.radpar = DAHDI_RADPAR_REMCOMMAND;
+ memcpy(&r.data,data,5);
+ if (ioctl(myrpt->zaprxchannel->fds[0],DAHDI_RADIO_SETPARAM,&r) == -1)
+ {
+ ast_log(LOG_WARNING,"Cannot send RBI command for channel %s\n",myrpt->zaprxchannel->name);
+ return;
+ }
+}
+
+static int serial_remote_io(struct rpt *myrpt, unsigned char *txbuf, int txbytes,
+ unsigned char *rxbuf, int rxmaxbytes, int asciiflag)
+{
+ int i,j,index,oldmode,olddata;
+ struct dahdi_radio_param prm;
+ char c;
+
+ if(debug){
+ printf("String output was: ");
+ for(i = 0; i < txbytes; i++)
+ printf("%02X ", (unsigned char ) txbuf[i]);
+ printf("\n");
+ }
+ if (myrpt->iofd > 0) /* if to do out a serial port */
+ {
+ if (rxmaxbytes && rxbuf) tcflush(myrpt->iofd,TCIFLUSH);
+ if (write(myrpt->iofd,txbuf,txbytes) != txbytes) return -1;
+ if ((!rxmaxbytes) || (rxbuf == NULL)) return(0);
+ memset(rxbuf,0,rxmaxbytes);
+ for(i = 0; i < rxmaxbytes; i++)
+ {
+ j = read(myrpt->iofd,&c,1);
+ if (j < 1) return(i);
+ rxbuf[i] = c;
+ if (asciiflag & 1)
+ {
+ rxbuf[i + 1] = 0;
+ if (c == '\r') break;
+ }
+ }
+ if(debug){
+ printf("String returned was: ");
+ for(j = 0; j < i; j++)
+ printf("%02X ", (unsigned char ) rxbuf[j]);
+ printf("\n");
+ }
+ return(i);
+ }
+
+ /* if not a zap channel, cant use pciradio stuff */
+ if (myrpt->rxchannel != myrpt->zaprxchannel) return -1;
+
+ prm.radpar = DAHDI_RADPAR_UIOMODE;
+ if (ioctl(myrpt->zaprxchannel->fds[0],DAHDI_RADIO_GETPARAM,&prm) == -1) return -1;
+ oldmode = prm.data;
+ prm.radpar = DAHDI_RADPAR_UIODATA;
+ if (ioctl(myrpt->zaprxchannel->fds[0],DAHDI_RADIO_GETPARAM,&prm) == -1) return -1;
+ olddata = prm.data;
+ prm.radpar = DAHDI_RADPAR_REMMODE;
+ if (asciiflag & 1) prm.data = DAHDI_RADPAR_REM_SERIAL_ASCII;
+ else prm.data = DAHDI_RADPAR_REM_SERIAL;
+ if (ioctl(myrpt->zaprxchannel->fds[0],DAHDI_RADIO_SETPARAM,&prm) == -1) return -1;
+ if (asciiflag & 2)
+ {
+ i = DAHDI_ONHOOK;
+ if (ioctl(myrpt->zaprxchannel->fds[0],DAHDI_HOOK,&i) == -1) return -1;
+ usleep(100000);
+ }
+ prm.radpar = DAHDI_RADPAR_REMCOMMAND;
+ prm.data = rxmaxbytes;
+ memcpy(prm.buf,txbuf,txbytes);
+ prm.index = txbytes;
+ if (ioctl(myrpt->zaprxchannel->fds[0],DAHDI_RADIO_SETPARAM,&prm) == -1) return -1;
+ if (rxbuf)
+ {
+ *rxbuf = 0;
+ memcpy(rxbuf,prm.buf,prm.index);
+ }
+ index = prm.index;
+ prm.radpar = DAHDI_RADPAR_REMMODE;
+ prm.data = DAHDI_RADPAR_REM_NONE;
+ if (ioctl(myrpt->zaprxchannel->fds[0],DAHDI_RADIO_SETPARAM,&prm) == -1) return -1;
+ if (asciiflag & 2)
+ {
+ i = DAHDI_OFFHOOK;
+ if (ioctl(myrpt->zaprxchannel->fds[0],DAHDI_HOOK,&i) == -1) return -1;
+ }
+ prm.radpar = DAHDI_RADPAR_UIOMODE;
+ prm.data = oldmode;
+ if (ioctl(myrpt->zaprxchannel->fds[0],DAHDI_RADIO_SETPARAM,&prm) == -1) return -1;
+ prm.radpar = DAHDI_RADPAR_UIODATA;
+ prm.data = olddata;
+ if (ioctl(myrpt->zaprxchannel->fds[0],DAHDI_RADIO_SETPARAM,&prm) == -1) return -1;
+ return(index);
+}
+
+static int civ_cmd(struct rpt *myrpt,unsigned char *cmd, int cmdlen)
+{
+unsigned char rxbuf[100];
+int i,rv ;
+
+ rv = serial_remote_io(myrpt,cmd,cmdlen,rxbuf,cmdlen + 6,0);
+ if (rv == -1) return(-1);
+ if (rv != (cmdlen + 6)) return(1);
+ for(i = 0; i < 6; i++)
+ if (rxbuf[i] != cmd[i]) return(1);
+ if (rxbuf[cmdlen] != 0xfe) return(1);
+ if (rxbuf[cmdlen + 1] != 0xfe) return(1);
+ if (rxbuf[cmdlen + 4] != 0xfb) return(1);
+ if (rxbuf[cmdlen + 5] != 0xfd) return(1);
+ return(0);
+}
+
+static int sendkenwood(struct rpt *myrpt,char *txstr, char *rxstr)
+{
+int i;
+
+ if (debug) printf("Send to kenwood: %s\n",txstr);
+ i = serial_remote_io(myrpt, (unsigned char *)txstr, strlen(txstr),
+ (unsigned char *)rxstr,RAD_SERIAL_BUFLEN - 1,3);
+ if (i < 0) return -1;
+ if ((i > 0) && (rxstr[i - 1] == '\r'))
+ rxstr[i-- - 1] = 0;
+ if (debug) printf("Got from kenwood: %s\n",rxstr);
+ return(i);
+}
+
+/* take a PL frequency and turn it into a code */
+static int kenwood_pltocode(char *str)
+{
+int i;
+char *s;
+
+ s = strchr(str,'.');
+ i = 0;
+ if (s) i = atoi(s + 1);
+ i += atoi(str) * 10;
+ switch(i)
+ {
+ case 670:
+ return 1;
+ case 719:
+ return 3;
+ case 744:
+ return 4;
+ case 770:
+ return 5;
+ case 797:
+ return 6;
+ case 825:
+ return 7;
+ case 854:
+ return 8;
+ case 885:
+ return 9;
+ case 915:
+ return 10;
+ case 948:
+ return 11;
+ case 974:
+ return 12;
+ case 1000:
+ return 13;
+ case 1035:
+ return 14;
+ case 1072:
+ return 15;
+ case 1109:
+ return 16;
+ case 1148:
+ return 17;
+ case 1188:
+ return 18;
+ case 1230:
+ return 19;
+ case 1273:
+ return 20;
+ case 1318:
+ return 21;
+ case 1365:
+ return 22;
+ case 1413:
+ return 23;
+ case 1462:
+ return 24;
+ case 1514:
+ return 25;
+ case 1567:
+ return 26;
+ case 1622:
+ return 27;
+ case 1679:
+ return 28;
+ case 1738:
+ return 29;
+ case 1799:
+ return 30;
+ case 1862:
+ return 31;
+ case 1928:
+ return 32;
+ case 2035:
+ return 33;
+ case 2107:
+ return 34;
+ case 2181:
+ return 35;
+ case 2257:
+ return 36;
+ case 2336:
+ return 37;
+ case 2418:
+ return 38;
+ case 2503:
+ return 39;
+ }
+ return -1;
+}
+
+static int sendrxkenwood(struct rpt *myrpt, char *txstr, char *rxstr,
+ char *cmpstr)
+{
+int i,j;
+
+ for(i = 0;i < KENWOOD_RETRIES;i++)
+ {
+ j = sendkenwood(myrpt,txstr,rxstr);
+ if (j < 0) return(j);
+ if (j == 0) continue;
+ if (!strncmp(rxstr,cmpstr,strlen(cmpstr))) return(0);
+ }
+ return(-1);
+}
+
+static int setkenwood(struct rpt *myrpt)
+{
+char rxstr[RAD_SERIAL_BUFLEN],txstr[RAD_SERIAL_BUFLEN],freq[20];
+char mhz[MAXREMSTR],offset[20],band,decimals[MAXREMSTR],band1,band2;
+
+int offsets[] = {0,2,1};
+int powers[] = {2,1,0};
+
+ if (sendrxkenwood(myrpt,"VMC 0,0\r",rxstr,"VMC") < 0) return -1;
+ split_freq(mhz, decimals, myrpt->freq);
+ if (atoi(mhz) > 400)
+ {
+ band = '6';
+ band1 = '1';
+ band2 = '5';
+ strcpy(offset,"005000000");
+ }
+ else
+ {
+ band = '2';
+ band1 = '0';
+ band2 = '2';
+ strcpy(offset,"000600000");
+ }
+ strcpy(freq,"000000");
+ strncpy(freq,decimals,strlen(decimals));
+ sprintf(txstr,"VW %c,%05d%s,0,%d,0,%d,%d,,%02d,,%02d,%s\r",
+ band,atoi(mhz),freq,offsets[(int)myrpt->offset],
+ (myrpt->txplon != 0),(myrpt->rxplon != 0),
+ kenwood_pltocode(myrpt->txpl),kenwood_pltocode(myrpt->rxpl),
+ offset);
+ if (sendrxkenwood(myrpt,txstr,rxstr,"VW") < 0) return -1;
+ sprintf(txstr,"RBN %c\r",band2);
+ if (sendrxkenwood(myrpt,txstr,rxstr,"RBN") < 0) return -1;
+ sprintf(txstr,"PC %c,%d\r",band1,powers[(int)myrpt->powerlevel]);
+ if (sendrxkenwood(myrpt,txstr,rxstr,"PC") < 0) return -1;
+ return 0;
+}
+
+static int setrbi(struct rpt *myrpt)
+{
+char tmp[MAXREMSTR] = "",*s;
+unsigned char rbicmd[5];
+int band,txoffset = 0,txpower = 0,rxpl;
+
+ /* must be a remote system */
+ if (!myrpt->remote) return(0);
+ /* must have rbi hardware */
+ if (strncmp(myrpt->remote,remote_rig_rbi,3)) return(0);
+ if (setrbi_check(myrpt) == -1) return(-1);
+ strncpy(tmp, myrpt->freq, sizeof(tmp) - 1);
+ s = strchr(tmp,'.');
+ /* if no decimal, is invalid */
+
+ if (s == NULL){
+ if(debug)
+ printf("@@@@ Frequency needs a decimal\n");
+ return -1;
+ }
+
+ *s++ = 0;
+ if (strlen(tmp) < 2){
+ if(debug)
+ printf("@@@@ Bad MHz digits: %s\n", tmp);
+ return -1;
+ }
+
+ if (strlen(s) < 3){
+ if(debug)
+ printf("@@@@ Bad KHz digits: %s\n", s);
+ return -1;
+ }
+
+ if ((s[2] != '0') && (s[2] != '5')){
+ if(debug)
+ printf("@@@@ KHz must end in 0 or 5: %c\n", s[2]);
+ return -1;
+ }
+
+ band = rbi_mhztoband(tmp);
+ if (band == -1){
+ if(debug)
+ printf("@@@@ Bad Band: %s\n", tmp);
+ return -1;
+ }
+
+ rxpl = rbi_pltocode(myrpt->rxpl);
+
+ if (rxpl == -1){
+ if(debug)
+ printf("@@@@ Bad TX PL: %s\n", myrpt->rxpl);
+ return -1;
+ }
+
+
+ switch(myrpt->offset)
+ {
+ case REM_MINUS:
+ txoffset = 0;
+ break;
+ case REM_PLUS:
+ txoffset = 0x10;
+ break;
+ case REM_SIMPLEX:
+ txoffset = 0x20;
+ break;
+ }
+ switch(myrpt->powerlevel)
+ {
+ case REM_LOWPWR:
+ txpower = 0;
+ break;
+ case REM_MEDPWR:
+ txpower = 0x20;
+ break;
+ case REM_HIPWR:
+ txpower = 0x10;
+ break;
+ }
+ rbicmd[0] = 0;
+ rbicmd[1] = band | txpower | 0xc0;
+ rbicmd[2] = (*(s - 2) - '0') | txoffset | 0x80;
+ if (s[2] == '5') rbicmd[2] |= 0x40;
+ rbicmd[3] = ((*s - '0') << 4) + (s[1] - '0');
+ rbicmd[4] = rxpl;
+ if (myrpt->txplon) rbicmd[4] |= 0x40;
+ if (myrpt->rxplon) rbicmd[4] |= 0x80;
+ rbi_out(myrpt,rbicmd);
+ return 0;
+}
+
+static int setrbi_check(struct rpt *myrpt)
+{
+char tmp[MAXREMSTR] = "",*s;
+int band,txpl;
+
+ /* must be a remote system */
+ if (!myrpt->remote) return(0);
+ /* must have rbi hardware */
+ if (strncmp(myrpt->remote,remote_rig_rbi,3)) return(0);
+ strncpy(tmp, myrpt->freq, sizeof(tmp) - 1);
+ s = strchr(tmp,'.');
+ /* if no decimal, is invalid */
+
+ if (s == NULL){
+ if(debug)
+ printf("@@@@ Frequency needs a decimal\n");
+ return -1;
+ }
+
+ *s++ = 0;
+ if (strlen(tmp) < 2){
+ if(debug)
+ printf("@@@@ Bad MHz digits: %s\n", tmp);
+ return -1;
+ }
+
+ if (strlen(s) < 3){
+ if(debug)
+ printf("@@@@ Bad KHz digits: %s\n", s);
+ return -1;
+ }
+
+ if ((s[2] != '0') && (s[2] != '5')){
+ if(debug)
+ printf("@@@@ KHz must end in 0 or 5: %c\n", s[2]);
+ return -1;
+ }
+
+ band = rbi_mhztoband(tmp);
+ if (band == -1){
+ if(debug)
+ printf("@@@@ Bad Band: %s\n", tmp);
+ return -1;
+ }
+
+ txpl = rbi_pltocode(myrpt->txpl);
+
+ if (txpl == -1){
+ if(debug)
+ printf("@@@@ Bad TX PL: %s\n", myrpt->txpl);
+ return -1;
+ }
+ return 0;
+}
+
+static int check_freq_kenwood(int m, int d, int *defmode)
+{
+ int dflmd = REM_MODE_FM;
+
+ if (m == 144){ /* 2 meters */
+ if(d < 10100)
+ return -1;
+ }
+ else if((m >= 145) && (m < 148)){
+ ;
+ }
+ else if((m >= 430) && (m < 450)){ /* 70 centimeters */
+ ;
+ }
+ else
+ return -1;
+
+ if(defmode)
+ *defmode = dflmd;
+
+
+ return 0;
+}
+
+
+/* Check for valid rbi frequency */
+/* Hard coded limits now, configurable later, maybe? */
+
+static int check_freq_rbi(int m, int d, int *defmode)
+{
+ int dflmd = REM_MODE_FM;
+
+ if(m == 50){ /* 6 meters */
+ if(d < 10100)
+ return -1;
+ }
+ else if((m >= 51) && ( m < 54)){
+ ;
+ }
+ else if(m == 144){ /* 2 meters */
+ if(d < 10100)
+ return -1;
+ }
+ else if((m >= 145) && (m < 148)){
+ ;
+ }
+ else if((m >= 222) && (m < 225)){ /* 1.25 meters */
+ ;
+ }
+ else if((m >= 430) && (m < 450)){ /* 70 centimeters */
+ ;
+ }
+ else if((m >= 1240) && (m < 1300)){ /* 23 centimeters */
+ ;
+ }
+ else
+ return -1;
+
+ if(defmode)
+ *defmode = dflmd;
+
+
+ return 0;
+}
+
+/*
+ * Convert decimals of frequency to int
+ */
+
+static int decimals2int(char *fraction)
+{
+ int i;
+ char len = strlen(fraction);
+ int multiplier = 100000;
+ int res = 0;
+
+ if(!len)
+ return 0;
+ for( i = 0 ; i < len ; i++, multiplier /= 10)
+ res += (fraction[i] - '0') * multiplier;
+ return res;
+}
+
+
+/*
+* Split frequency into mhz and decimals
+*/
+
+static int split_freq(char *mhz, char *decimals, char *freq)
+{
+ char freq_copy[MAXREMSTR];
+ char *decp;
+
+ decp = strchr(strncpy(freq_copy, freq, MAXREMSTR),'.');
+ if(decp){
+ *decp++ = 0;
+ strncpy(mhz, freq_copy, MAXREMSTR);
+ strcpy(decimals, "00000");
+ strncpy(decimals, decp, strlen(decp));
+ decimals[5] = 0;
+ return 0;
+ }
+ else
+ return -1;
+
+}
+
+/*
+* Split ctcss frequency into hertz and decimal
+*/
+
+static int split_ctcss_freq(char *hertz, char *decimal, char *freq)
+{
+ char freq_copy[MAXREMSTR];
+ char *decp;
+
+ decp = strchr(strncpy(freq_copy, freq, MAXREMSTR),'.');
+ if(decp){
+ *decp++ = 0;
+ strncpy(hertz, freq_copy, MAXREMSTR);
+ strncpy(decimal, decp, strlen(decp));
+ decimal[strlen(decp)] = '\0';
+ return 0;
+ }
+ else
+ return -1;
+}
+
+
+
+/*
+* FT-897 I/O handlers
+*/
+
+/* Check to see that the frequency is valid */
+/* Hard coded limits now, configurable later, maybe? */
+
+
+static int check_freq_ft897(int m, int d, int *defmode)
+{
+ int dflmd = REM_MODE_FM;
+
+ if(m == 1){ /* 160 meters */
+ dflmd = REM_MODE_LSB;
+ if(d < 80000)
+ return -1;
+ }
+ else if(m == 3){ /* 80 meters */
+ dflmd = REM_MODE_LSB;
+ if(d < 50000)
+ return -1;
+ }
+ else if(m == 7){ /* 40 meters */
+ dflmd = REM_MODE_LSB;
+ if(d > 30000)
+ return -1;
+ }
+ else if(m == 14){ /* 20 meters */
+ dflmd = REM_MODE_USB;
+ if(d > 35000)
+ return -1;
+ }
+ else if(m == 18){ /* 17 meters */
+ dflmd = REM_MODE_USB;
+ if((d < 6800) || (d > 16800))
+ return -1;
+ }
+ else if(m == 21){ /* 15 meters */
+ dflmd = REM_MODE_USB;
+ if((d < 20000) || (d > 45000))
+ return -1;
+ }
+ else if(m == 24){ /* 12 meters */
+ dflmd = REM_MODE_USB;
+ if((d < 89000) || (d > 99000))
+ return -1;
+ }
+ else if(m == 28){ /* 10 meters */
+ dflmd = REM_MODE_USB;
+ }
+ else if(m == 29){
+ if(d >= 51000)
+ dflmd = REM_MODE_FM;
+ else
+ dflmd = REM_MODE_USB;
+ if(d > 70000)
+ return -1;
+ }
+ else if(m == 50){ /* 6 meters */
+ if(d >= 30000)
+ dflmd = REM_MODE_FM;
+ else
+ dflmd = REM_MODE_USB;
+
+ }
+ else if((m >= 51) && ( m < 54)){
+ dflmd = REM_MODE_FM;
+ }
+ else if(m == 144){ /* 2 meters */
+ if(d >= 30000)
+ dflmd = REM_MODE_FM;
+ else
+ dflmd = REM_MODE_USB;
+ }
+ else if((m >= 145) && (m < 148)){
+ dflmd = REM_MODE_FM;
+ }
+ else if((m >= 430) && (m < 450)){ /* 70 centimeters */
+ if(m < 438)
+ dflmd = REM_MODE_USB;
+ else
+ dflmd = REM_MODE_FM;
+ ;
+ }
+ else
+ return -1;
+
+ if(defmode)
+ *defmode = dflmd;
+
+ return 0;
+}
+
+/*
+* Set a new frequency for the FT897
+*/
+
+static int set_freq_ft897(struct rpt *myrpt, char *newfreq)
+{
+ unsigned char cmdstr[5];
+ int fd,m,d;
+ char mhz[MAXREMSTR];
+ char decimals[MAXREMSTR];
+
+ fd = 0;
+ if(debug)
+ printf("New frequency: %s\n",newfreq);
+
+ if(split_freq(mhz, decimals, newfreq))
+ return -1;
+
+ m = atoi(mhz);
+ d = atoi(decimals);
+
+ /* The FT-897 likes packed BCD frequencies */
+
+ cmdstr[0] = ((m / 100) << 4) + ((m % 100)/10); /* 100MHz 10Mhz */
+ cmdstr[1] = ((m % 10) << 4) + (d / 10000); /* 1MHz 100KHz */
+ cmdstr[2] = (((d % 10000)/1000) << 4) + ((d % 1000)/ 100); /* 10KHz 1KHz */
+ cmdstr[3] = (((d % 100)/10) << 4) + (d % 10); /* 100Hz 10Hz */
+ cmdstr[4] = 0x01; /* command */
+
+ return serial_remote_io(myrpt, cmdstr, 5, NULL, 0, 0);
+
+}
+
+/* ft-897 simple commands */
+
+static int simple_command_ft897(struct rpt *myrpt, char command)
+{
+ unsigned char cmdstr[5];
+
+ memset(cmdstr, 0, 5);
+
+ cmdstr[4] = command;
+
+ return serial_remote_io(myrpt, cmdstr, 5, NULL, 0, 0);
+
+}
+
+/* ft-897 offset */
+
+static int set_offset_ft897(struct rpt *myrpt, char offset)
+{
+ unsigned char cmdstr[5];
+
+ memset(cmdstr, 0, 5);
+
+ switch(offset){
+ case REM_SIMPLEX:
+ cmdstr[0] = 0x89;
+ break;
+
+ case REM_MINUS:
+ cmdstr[0] = 0x09;
+ break;
+
+ case REM_PLUS:
+ cmdstr[0] = 0x49;
+ break;
+
+ default:
+ return -1;
+ }
+
+ cmdstr[4] = 0x09;
+
+ return serial_remote_io(myrpt, cmdstr, 5, NULL, 0, 0);
+}
+
+/* ft-897 mode */
+
+static int set_mode_ft897(struct rpt *myrpt, char newmode)
+{
+ unsigned char cmdstr[5];
+
+ memset(cmdstr, 0, 5);
+
+ switch(newmode){
+ case REM_MODE_FM:
+ cmdstr[0] = 0x08;
+ break;
+
+ case REM_MODE_USB:
+ cmdstr[0] = 0x01;
+ break;
+
+ case REM_MODE_LSB:
+ cmdstr[0] = 0x00;
+ break;
+
+ case REM_MODE_AM:
+ cmdstr[0] = 0x04;
+ break;
+
+ default:
+ return -1;
+ }
+ cmdstr[4] = 0x07;
+
+ return serial_remote_io(myrpt, cmdstr, 5, NULL, 0, 0);
+}
+
+/* Set tone encode and decode modes */
+
+static int set_ctcss_mode_ft897(struct rpt *myrpt, char txplon, char rxplon)
+{
+ unsigned char cmdstr[5];
+
+ memset(cmdstr, 0, 5);
+
+ if(rxplon && txplon)
+ cmdstr[0] = 0x2A; /* Encode and Decode */
+ else if (!rxplon && txplon)
+ cmdstr[0] = 0x4A; /* Encode only */
+ else if (rxplon && !txplon)
+ cmdstr[0] = 0x3A; /* Encode only */
+ else
+ cmdstr[0] = 0x8A; /* OFF */
+
+ cmdstr[4] = 0x0A;
+
+ return serial_remote_io(myrpt, cmdstr, 5, NULL, 0, 0);
+}
+
+
+/* Set transmit and receive ctcss tone frequencies */
+
+static int set_ctcss_freq_ft897(struct rpt *myrpt, char *txtone, char *rxtone)
+{
+ unsigned char cmdstr[5];
+ char hertz[MAXREMSTR],decimal[MAXREMSTR];
+ int h,d;
+
+ memset(cmdstr, 0, 5);
+
+ if(split_ctcss_freq(hertz, decimal, txtone))
+ return -1;
+
+ h = atoi(hertz);
+ d = atoi(decimal);
+
+ cmdstr[0] = ((h / 100) << 4) + (h % 100)/ 10;
+ cmdstr[1] = ((h % 10) << 4) + (d % 10);
+
+ if(rxtone){
+
+ if(split_ctcss_freq(hertz, decimal, rxtone))
+ return -1;
+
+ h = atoi(hertz);
+ d = atoi(decimal);
+
+ cmdstr[2] = ((h / 100) << 4) + (h % 100)/ 10;
+ cmdstr[3] = ((h % 10) << 4) + (d % 10);
+ }
+ cmdstr[4] = 0x0B;
+
+ return serial_remote_io(myrpt, cmdstr, 5, NULL, 0, 0);
+}
+
+
+
+static int set_ft897(struct rpt *myrpt)
+{
+ int res;
+
+ if(debug)
+ printf("@@@@ lock on\n");
+
+ res = simple_command_ft897(myrpt, 0x00); /* LOCK on */
+
+ if(debug)
+ printf("@@@@ ptt off\n");
+
+ if(!res)
+ res = simple_command_ft897(myrpt, 0x88); /* PTT off */
+
+ if(debug)
+ printf("Modulation mode\n");
+
+ if(!res)
+ res = set_mode_ft897(myrpt, myrpt->remmode); /* Modulation mode */
+
+ if(debug)
+ printf("Split off\n");
+
+ if(!res)
+ simple_command_ft897(myrpt, 0x82); /* Split off */
+
+ if(debug)
+ printf("Frequency\n");
+
+ if(!res)
+ res = set_freq_ft897(myrpt, myrpt->freq); /* Frequency */
+ if((myrpt->remmode == REM_MODE_FM)){
+ if(debug)
+ printf("Offset\n");
+ if(!res)
+ res = set_offset_ft897(myrpt, myrpt->offset); /* Offset if FM */
+ if((!res)&&(myrpt->rxplon || myrpt->txplon)){
+ if(debug)
+ printf("CTCSS tone freqs.\n");
+ res = set_ctcss_freq_ft897(myrpt, myrpt->txpl, myrpt->rxpl); /* CTCSS freqs if CTCSS is enabled */
+ }
+ if(!res){
+ if(debug)
+ printf("CTCSS mode\n");
+ res = set_ctcss_mode_ft897(myrpt, myrpt->txplon, myrpt->rxplon); /* CTCSS mode */
+ }
+ }
+ if((myrpt->remmode == REM_MODE_USB)||(myrpt->remmode == REM_MODE_LSB)){
+ if(debug)
+ printf("Clarifier off\n");
+ simple_command_ft897(myrpt, 0x85); /* Clarifier off if LSB or USB */
+ }
+ return res;
+}
+
+static int closerem_ft897(struct rpt *myrpt)
+{
+ simple_command_ft897(myrpt, 0x88); /* PTT off */
+ return 0;
+}
+
+/*
+* Bump frequency up or down by a small amount
+* Return 0 if the new frequnecy is valid, or -1 if invalid
+* Interval is in Hz, resolution is 10Hz
+*/
+
+static int multimode_bump_freq_ft897(struct rpt *myrpt, int interval)
+{
+ int m,d;
+ char mhz[MAXREMSTR], decimals[MAXREMSTR];
+
+ if(debug)
+ printf("Before bump: %s\n", myrpt->freq);
+
+ if(split_freq(mhz, decimals, myrpt->freq))
+ return -1;
+
+ m = atoi(mhz);
+ d = atoi(decimals);
+
+ d += (interval / 10); /* 10Hz resolution */
+ if(d < 0){
+ m--;
+ d += 100000;
+ }
+ else if(d >= 100000){
+ m++;
+ d -= 100000;
+ }
+
+ if(check_freq_ft897(m, d, NULL)){
+ if(debug)
+ printf("Bump freq invalid\n");
+ return -1;
+ }
+
+ snprintf(myrpt->freq, MAXREMSTR, "%d.%05d", m, d);
+
+ if(debug)
+ printf("After bump: %s\n", myrpt->freq);
+
+ return set_freq_ft897(myrpt, myrpt->freq);
+}
+
+
+
+/*
+* IC-706 I/O handlers
+*/
+
+/* Check to see that the frequency is valid */
+/* Hard coded limits now, configurable later, maybe? */
+
+
+static int check_freq_ic706(int m, int d, int *defmode)
+{
+ int dflmd = REM_MODE_FM;
+
+ if(m == 1){ /* 160 meters */
+ dflmd = REM_MODE_LSB;
+ if(d < 80000)
+ return -1;
+ }
+ else if(m == 3){ /* 80 meters */
+ dflmd = REM_MODE_LSB;
+ if(d < 50000)
+ return -1;
+ }
+ else if(m == 7){ /* 40 meters */
+ dflmd = REM_MODE_LSB;
+ if(d > 30000)
+ return -1;
+ }
+ else if(m == 14){ /* 20 meters */
+ dflmd = REM_MODE_USB;
+ if(d > 35000)
+ return -1;
+ }
+ else if(m == 18){ /* 17 meters */
+ dflmd = REM_MODE_USB;
+ if((d < 6800) || (d > 16800))
+ return -1;
+ }
+ else if(m == 21){ /* 15 meters */
+ dflmd = REM_MODE_USB;
+ if((d < 20000) || (d > 45000))
+ return -1;
+ }
+ else if(m == 24){ /* 12 meters */
+ dflmd = REM_MODE_USB;
+ if((d < 89000) || (d > 99000))
+ return -1;
+ }
+ else if(m == 28){ /* 10 meters */
+ dflmd = REM_MODE_USB;
+ }
+ else if(m == 29){
+ if(d >= 51000)
+ dflmd = REM_MODE_FM;
+ else
+ dflmd = REM_MODE_USB;
+ if(d > 70000)
+ return -1;
+ }
+ else if(m == 50){ /* 6 meters */
+ if(d >= 30000)
+ dflmd = REM_MODE_FM;
+ else
+ dflmd = REM_MODE_USB;
+
+ }
+ else if((m >= 51) && ( m < 54)){
+ dflmd = REM_MODE_FM;
+ }
+ else if(m == 144){ /* 2 meters */
+ if(d >= 30000)
+ dflmd = REM_MODE_FM;
+ else
+ dflmd = REM_MODE_USB;
+ }
+ else if((m >= 145) && (m < 148)){
+ dflmd = REM_MODE_FM;
+ }
+ else if((m >= 430) && (m < 450)){ /* 70 centimeters */
+ if(m < 438)
+ dflmd = REM_MODE_USB;
+ else
+ dflmd = REM_MODE_FM;
+ ;
+ }
+ else
+ return -1;
+
+ if(defmode)
+ *defmode = dflmd;
+
+ return 0;
+}
+
+/* take a PL frequency and turn it into a code */
+static int ic706_pltocode(char *str)
+{
+int i;
+char *s;
+
+ s = strchr(str,'.');
+ i = 0;
+ if (s) i = atoi(s + 1);
+ i += atoi(str) * 10;
+ switch(i)
+ {
+ case 670:
+ return 0;
+ case 693:
+ return 1;
+ case 719:
+ return 2;
+ case 744:
+ return 3;
+ case 770:
+ return 4;
+ case 797:
+ return 5;
+ case 825:
+ return 6;
+ case 854:
+ return 7;
+ case 885:
+ return 8;
+ case 915:
+ return 9;
+ case 948:
+ return 10;
+ case 974:
+ return 11;
+ case 1000:
+ return 12;
+ case 1035:
+ return 13;
+ case 1072:
+ return 14;
+ case 1109:
+ return 15;
+ case 1148:
+ return 16;
+ case 1188:
+ return 17;
+ case 1230:
+ return 18;
+ case 1273:
+ return 19;
+ case 1318:
+ return 20;
+ case 1365:
+ return 21;
+ case 1413:
+ return 22;
+ case 1462:
+ return 23;
+ case 1514:
+ return 24;
+ case 1567:
+ return 25;
+ case 1598:
+ return 26;
+ case 1622:
+ return 27;
+ case 1655:
+ return 28;
+ case 1679:
+ return 29;
+ case 1713:
+ return 30;
+ case 1738:
+ return 31;
+ case 1773:
+ return 32;
+ case 1799:
+ return 33;
+ case 1835:
+ return 34;
+ case 1862:
+ return 35;
+ case 1899:
+ return 36;
+ case 1928:
+ return 37;
+ case 1966:
+ return 38;
+ case 1995:
+ return 39;
+ case 2035:
+ return 40;
+ case 2065:
+ return 41;
+ case 2107:
+ return 42;
+ case 2181:
+ return 43;
+ case 2257:
+ return 44;
+ case 2291:
+ return 45;
+ case 2336:
+ return 46;
+ case 2418:
+ return 47;
+ case 2503:
+ return 48;
+ case 2541:
+ return 49;
+ }
+ return -1;
+}
+
+/* ic-706 simple commands */
+
+static int simple_command_ic706(struct rpt *myrpt, char command, char subcommand)
+{
+ unsigned char cmdstr[10];
+
+ cmdstr[0] = cmdstr[1] = 0xfe;
+ cmdstr[2] = myrpt->p.civaddr;
+ cmdstr[3] = 0xe0;
+ cmdstr[4] = command;
+ cmdstr[5] = subcommand;
+ cmdstr[6] = 0xfd;
+
+ return(civ_cmd(myrpt,cmdstr,7));
+}
+
+/*
+* Set a new frequency for the ic706
+*/
+
+static int set_freq_ic706(struct rpt *myrpt, char *newfreq)
+{
+ unsigned char cmdstr[20];
+ char mhz[MAXREMSTR], decimals[MAXREMSTR];
+ int fd,m,d;
+
+ fd = 0;
+ if(debug)
+ printf("New frequency: %s\n",newfreq);
+
+ if(split_freq(mhz, decimals, newfreq))
+ return -1;
+
+ m = atoi(mhz);
+ d = atoi(decimals);
+
+ /* The ic-706 likes packed BCD frequencies */
+
+ cmdstr[0] = cmdstr[1] = 0xfe;
+ cmdstr[2] = myrpt->p.civaddr;
+ cmdstr[3] = 0xe0;
+ cmdstr[4] = 5;
+ cmdstr[5] = ((d % 10) << 4);
+ cmdstr[6] = (((d % 1000)/ 100) << 4) + ((d % 100)/10);
+ cmdstr[7] = ((d / 10000) << 4) + ((d % 10000)/1000);
+ cmdstr[8] = (((m % 100)/10) << 4) + (m % 10);
+ cmdstr[9] = (m / 100);
+ cmdstr[10] = 0xfd;
+
+ return(civ_cmd(myrpt,cmdstr,11));
+}
+
+/* ic-706 offset */
+
+static int set_offset_ic706(struct rpt *myrpt, char offset)
+{
+ unsigned char c;
+
+ switch(offset){
+ case REM_SIMPLEX:
+ c = 0x10;
+ break;
+
+ case REM_MINUS:
+ c = 0x11;
+ break;
+
+ case REM_PLUS:
+ c = 0x12;
+ break;
+
+ default:
+ return -1;
+ }
+
+ return simple_command_ic706(myrpt,0x0f,c);
+
+}
+
+/* ic-706 mode */
+
+static int set_mode_ic706(struct rpt *myrpt, char newmode)
+{
+ unsigned char c;
+
+ switch(newmode){
+ case REM_MODE_FM:
+ c = 5;
+ break;
+
+ case REM_MODE_USB:
+ c = 1;
+ break;
+
+ case REM_MODE_LSB:
+ c = 0;
+ break;
+
+ case REM_MODE_AM:
+ c = 2;
+ break;
+
+ default:
+ return -1;
+ }
+ return simple_command_ic706(myrpt,6,c);
+}
+
+/* Set tone encode and decode modes */
+
+static int set_ctcss_mode_ic706(struct rpt *myrpt, char txplon, char rxplon)
+{
+ unsigned char cmdstr[10];
+ int rv;
+
+ cmdstr[0] = cmdstr[1] = 0xfe;
+ cmdstr[2] = myrpt->p.civaddr;
+ cmdstr[3] = 0xe0;
+ cmdstr[4] = 0x16;
+ cmdstr[5] = 0x42;
+ cmdstr[6] = (txplon != 0);
+ cmdstr[7] = 0xfd;
+
+ rv = civ_cmd(myrpt,cmdstr,8);
+ if (rv) return(-1);
+
+ cmdstr[0] = cmdstr[1] = 0xfe;
+ cmdstr[2] = myrpt->p.civaddr;
+ cmdstr[3] = 0xe0;
+ cmdstr[4] = 0x16;
+ cmdstr[5] = 0x43;
+ cmdstr[6] = (rxplon != 0);
+ cmdstr[7] = 0xfd;
+
+ return(civ_cmd(myrpt,cmdstr,8));
+}
+
+#if 0
+/* Set transmit and receive ctcss tone frequencies */
+
+static int set_ctcss_freq_ic706(struct rpt *myrpt, char *txtone, char *rxtone)
+{
+ unsigned char cmdstr[10];
+ char hertz[MAXREMSTR],decimal[MAXREMSTR];
+ int h,d,rv;
+
+ memset(cmdstr, 0, 5);
+
+ if(split_ctcss_freq(hertz, decimal, txtone))
+ return -1;
+
+ h = atoi(hertz);
+ d = atoi(decimal);
+
+ cmdstr[0] = cmdstr[1] = 0xfe;
+ cmdstr[2] = myrpt->p.civaddr;
+ cmdstr[3] = 0xe0;
+ cmdstr[4] = 0x1b;
+ cmdstr[5] = 0;
+ cmdstr[6] = ((h / 100) << 4) + (h % 100)/ 10;
+ cmdstr[7] = ((h % 10) << 4) + (d % 10);
+ cmdstr[8] = 0xfd;
+
+ rv = civ_cmd(myrpt,cmdstr,9);
+ if (rv) return(-1);
+
+ if (!rxtone) return(0);
+
+ if(split_ctcss_freq(hertz, decimal, rxtone))
+ return -1;
+
+ h = atoi(hertz);
+ d = atoi(decimal);
+
+ cmdstr[0] = cmdstr[1] = 0xfe;
+ cmdstr[2] = myrpt->p.civaddr;
+ cmdstr[3] = 0xe0;
+ cmdstr[4] = 0x1b;
+ cmdstr[5] = 1;
+ cmdstr[6] = ((h / 100) << 4) + (h % 100)/ 10;
+ cmdstr[7] = ((h % 10) << 4) + (d % 10);
+ cmdstr[8] = 0xfd;
+ return(civ_cmd(myrpt,cmdstr,9));
+}
+#endif
+
+static int vfo_ic706(struct rpt *myrpt)
+{
+ unsigned char cmdstr[10];
+
+ cmdstr[0] = cmdstr[1] = 0xfe;
+ cmdstr[2] = myrpt->p.civaddr;
+ cmdstr[3] = 0xe0;
+ cmdstr[4] = 7;
+ cmdstr[5] = 0xfd;
+
+ return(civ_cmd(myrpt,cmdstr,6));
+}
+
+static int mem2vfo_ic706(struct rpt *myrpt)
+{
+ unsigned char cmdstr[10];
+
+ cmdstr[0] = cmdstr[1] = 0xfe;
+ cmdstr[2] = myrpt->p.civaddr;
+ cmdstr[3] = 0xe0;
+ cmdstr[4] = 0x0a;
+ cmdstr[5] = 0xfd;
+
+ return(civ_cmd(myrpt,cmdstr,6));
+}
+
+static int select_mem_ic706(struct rpt *myrpt, int slot)
+{
+ unsigned char cmdstr[10];
+
+ cmdstr[0] = cmdstr[1] = 0xfe;
+ cmdstr[2] = myrpt->p.civaddr;
+ cmdstr[3] = 0xe0;
+ cmdstr[4] = 8;
+ cmdstr[5] = 0;
+ cmdstr[6] = ((slot / 10) << 4) + (slot % 10);
+ cmdstr[7] = 0xfd;
+
+ return(civ_cmd(myrpt,cmdstr,8));
+}
+
+static int set_ic706(struct rpt *myrpt)
+{
+ int res = 0,i;
+
+ if(debug)
+ printf("Set to VFO A\n");
+
+ if (!res)
+ res = simple_command_ic706(myrpt,7,0);
+
+
+ if((myrpt->remmode == REM_MODE_FM))
+ {
+ i = ic706_pltocode(myrpt->rxpl);
+ if (i == -1) return -1;
+ if(debug)
+ printf("Select memory number\n");
+ if (!res)
+ res = select_mem_ic706(myrpt,i + IC706_PL_MEMORY_OFFSET);
+ if(debug)
+ printf("Transfer memory to VFO\n");
+ if (!res)
+ res = mem2vfo_ic706(myrpt);
+ }
+
+ if(debug)
+ printf("Set to VFO\n");
+
+ if (!res)
+ res = vfo_ic706(myrpt);
+
+ if(debug)
+ printf("Modulation mode\n");
+
+ if (!res)
+ res = set_mode_ic706(myrpt, myrpt->remmode); /* Modulation mode */
+
+ if(debug)
+ printf("Split off\n");
+
+ if(!res)
+ simple_command_ic706(myrpt, 0x82,0); /* Split off */
+
+ if(debug)
+ printf("Frequency\n");
+
+ if(!res)
+ res = set_freq_ic706(myrpt, myrpt->freq); /* Frequency */
+ if((myrpt->remmode == REM_MODE_FM)){
+ if(debug)
+ printf("Offset\n");
+ if(!res)
+ res = set_offset_ic706(myrpt, myrpt->offset); /* Offset if FM */
+ if(!res){
+ if(debug)
+ printf("CTCSS mode\n");
+ res = set_ctcss_mode_ic706(myrpt, myrpt->txplon, myrpt->rxplon); /* CTCSS mode */
+ }
+ }
+ return res;
+}
+
+/*
+* Bump frequency up or down by a small amount
+* Return 0 if the new frequnecy is valid, or -1 if invalid
+* Interval is in Hz, resolution is 10Hz
+*/
+
+static int multimode_bump_freq_ic706(struct rpt *myrpt, int interval)
+{
+ int m,d;
+ char mhz[MAXREMSTR], decimals[MAXREMSTR];
+ unsigned char cmdstr[20];
+
+ if(debug)
+ printf("Before bump: %s\n", myrpt->freq);
+
+ if(split_freq(mhz, decimals, myrpt->freq))
+ return -1;
+
+ m = atoi(mhz);
+ d = atoi(decimals);
+
+ d += (interval / 10); /* 10Hz resolution */
+ if(d < 0){
+ m--;
+ d += 100000;
+ }
+ else if(d >= 100000){
+ m++;
+ d -= 100000;
+ }
+
+ if(check_freq_ic706(m, d, NULL)){
+ if(debug)
+ printf("Bump freq invalid\n");
+ return -1;
+ }
+
+ snprintf(myrpt->freq, MAXREMSTR, "%d.%05d", m, d);
+
+ if(debug)
+ printf("After bump: %s\n", myrpt->freq);
+
+ /* The ic-706 likes packed BCD frequencies */
+
+ cmdstr[0] = cmdstr[1] = 0xfe;
+ cmdstr[2] = myrpt->p.civaddr;
+ cmdstr[3] = 0xe0;
+ cmdstr[4] = 0;
+ cmdstr[5] = ((d % 10) << 4);
+ cmdstr[6] = (((d % 1000)/ 100) << 4) + ((d % 100)/10);
+ cmdstr[7] = ((d / 10000) << 4) + ((d % 10000)/1000);
+ cmdstr[8] = (((m % 100)/10) << 4) + (m % 10);
+ cmdstr[9] = (m / 100);
+ cmdstr[10] = 0xfd;
+
+ return(serial_remote_io(myrpt,cmdstr,11,NULL,0,0));
+}
+
+
+
+/*
+* Dispatch to correct I/O handler
+*/
+
+static int setrem(struct rpt *myrpt)
+{
+char str[300];
+char *offsets[] = {"MINUS","SIMPLEX","PLUS"};
+char *powerlevels[] = {"LOW","MEDIUM","HIGH"};
+char *modes[] = {"FM","USB","LSB","AM"};
+int res = -1;
+
+ if (myrpt->p.archivedir)
+ {
+ sprintf(str,"FREQ,%s,%s,%s,%s,%s,%s,%d,%d",myrpt->freq,
+ modes[(int)myrpt->remmode],
+ myrpt->txpl,myrpt->rxpl,offsets[(int)myrpt->offset],
+ powerlevels[(int)myrpt->powerlevel],myrpt->txplon,
+ myrpt->rxplon);
+ donodelog(myrpt,str);
+ }
+ if(!strcmp(myrpt->remote, remote_rig_ft897))
+ {
+ rpt_telemetry(myrpt,SETREMOTE,NULL);
+ res = 0;
+ }
+ if(!strcmp(myrpt->remote, remote_rig_ic706))
+ {
+ rpt_telemetry(myrpt,SETREMOTE,NULL);
+ res = 0;
+ }
+ else if(!strcmp(myrpt->remote, remote_rig_rbi))
+ {
+ res = setrbi_check(myrpt);
+ if (!res)
+ {
+ rpt_telemetry(myrpt,SETREMOTE,NULL);
+ res = 0;
+ }
+ }
+ else if(!strcmp(myrpt->remote, remote_rig_kenwood)) {
+ rpt_telemetry(myrpt,SETREMOTE,NULL);
+ res = 0;
+ }
+ else
+ res = 0;
+
+ if (res < 0) ast_log(LOG_ERROR,"Unable to send remote command on node %s\n",myrpt->name);
+
+ return res;
+}
+
+static int closerem(struct rpt *myrpt)
+{
+ if(!strcmp(myrpt->remote, remote_rig_ft897))
+ return closerem_ft897(myrpt);
+ else
+ return 0;
+}
+
+/*
+* Dispatch to correct RX frequency checker
+*/
+
+static int check_freq(struct rpt *myrpt, int m, int d, int *defmode)
+{
+ if(!strcmp(myrpt->remote, remote_rig_ft897))
+ return check_freq_ft897(m, d, defmode);
+ else if(!strcmp(myrpt->remote, remote_rig_ic706))
+ return check_freq_ic706(m, d, defmode);
+ else if(!strcmp(myrpt->remote, remote_rig_rbi))
+ return check_freq_rbi(m, d, defmode);
+ else if(!strcmp(myrpt->remote, remote_rig_kenwood))
+ return check_freq_kenwood(m, d, defmode);
+ else
+ return -1;
+}
+
+/*
+ * Check TX frequency before transmitting
+ */
+
+static char check_tx_freq(struct rpt *myrpt)
+{
+ int i;
+ int radio_mhz, radio_decimals, ulimit_mhz, ulimit_decimals, llimit_mhz, llimit_decimals;
+ char radio_mhz_char[MAXREMSTR];
+ char radio_decimals_char[MAXREMSTR];
+ char limit_mhz_char[MAXREMSTR];
+ char limit_decimals_char[MAXREMSTR];
+ char limits[256];
+ char *limit_ranges[40];
+ struct ast_variable *limitlist;
+
+
+ /* Must have user logged in and tx_limits defined */
+
+ if(!myrpt->p.txlimitsstanzaname || !myrpt->loginuser[0] || !myrpt->loginlevel[0]){
+ if(debug > 3){
+ ast_log(LOG_NOTICE, "No tx band table defined, or no user logged in\n");
+ }
+ return 1; /* Assume it's ok otherwise */
+ }
+
+ /* Retrieve the band table for the loginlevel */
+ limitlist = ast_variable_browse(myrpt->cfg, myrpt->p.txlimitsstanzaname);
+
+ if(!limitlist){
+ ast_log(LOG_WARNING, "No entries in %s band table stanza\n", myrpt->p.txlimitsstanzaname);
+ return 0;
+ }
+
+ split_freq(radio_mhz_char, radio_decimals_char, myrpt->freq);
+ radio_mhz = atoi(radio_mhz_char);
+ radio_decimals = decimals2int(radio_decimals_char);
+
+
+ if(debug > 3){
+ ast_log(LOG_NOTICE, "Login User = %s, login level = %s\n", myrpt->loginuser, myrpt->loginlevel);
+ }
+
+ /* Find our entry */
+
+ for(;limitlist; limitlist=limitlist->next){
+ if(!strcmp(limitlist->name, myrpt->loginlevel))
+ break;
+ }
+
+ if(!limitlist){
+ ast_log(LOG_WARNING, "Can't find %s entry in band table stanza %s\n", myrpt->loginlevel, myrpt->p.txlimitsstanzaname);
+ return 0;
+ }
+
+ if(debug > 3){
+ ast_log(LOG_NOTICE, "Auth %s = %s\n", limitlist->name, limitlist->value);
+ }
+
+ /* Parse the limits */
+
+ strncpy(limits, limitlist->value, 256);
+ limits[255] = 0;
+ finddelim(limits, limit_ranges, 40);
+ for(i = 0; i < 40 && limit_ranges[i] ; i++){
+ char range[40];
+ char *r,*s;
+ strncpy(range, limit_ranges[i], 40);
+ range[39] = 0;
+ if(debug > 3){
+ ast_log(LOG_NOTICE, "Checking to see if %s is within limits of %s\n", myrpt->freq, range);
+ }
+
+ r = strchr(range, '-');
+ if(!r){
+ ast_log(LOG_WARNING, "Malformed range in %s tx band table entry\n", limitlist->name);
+ return 0;
+ }
+ *r++ = 0;
+ s = eatwhite(range);
+ r = eatwhite(r);
+ split_freq(limit_mhz_char, limit_decimals_char, s);
+ llimit_mhz = atoi(limit_mhz_char);
+ llimit_decimals = decimals2int(limit_decimals_char);
+ split_freq(limit_mhz_char, limit_decimals_char, r);
+ ulimit_mhz = atoi(limit_mhz_char);
+ ulimit_decimals = decimals2int(limit_decimals_char);
+
+ if((radio_mhz >= llimit_mhz) && (radio_mhz <= ulimit_mhz)){
+ if(radio_mhz == llimit_mhz){ /* CASE 1: TX freq is in llimit mhz portion of band */
+ if(radio_decimals >= llimit_decimals){ /* Cannot be below llimit decimals */
+ if(llimit_mhz == ulimit_mhz){ /* If bandwidth < 1Mhz, check ulimit decimals */
+ if(radio_decimals <= ulimit_decimals){
+ return 1;
+ }
+ else{
+ if(debug > 3)
+ ast_log(LOG_NOTICE, "Invalid TX frequency, debug msg 1\n");
+ return 0;
+ }
+ }
+ else{
+ return 1;
+ }
+ }
+ else{ /* Is below llimit decimals */
+ if(debug > 3)
+ ast_log(LOG_NOTICE, "Invalid TX frequency, debug msg 2\n");
+ return 0;
+ }
+ }
+ else if(radio_mhz == ulimit_mhz){ /* CASE 2: TX freq not in llimit mhz portion of band */
+ if(radio_decimals <= ulimit_decimals){
+ return 1;
+ }
+ else{ /* Is above ulimit decimals */
+ if(debug > 3)
+ ast_log(LOG_NOTICE, "Invalid TX frequency, debug msg 3\n");
+ return 0;
+ }
+ }
+ else /* CASE 3: TX freq within a multi-Mhz band and ok */
+ return 1;
+ }
+ }
+ if(debug > 3) /* No match found in TX band table */
+ ast_log(LOG_NOTICE, "Invalid TX frequency, debug msg 4\n");
+ return 0;
+}
+
+
+/*
+* Dispatch to correct frequency bumping function
+*/
+
+static int multimode_bump_freq(struct rpt *myrpt, int interval)
+{
+ if(!strcmp(myrpt->remote, remote_rig_ft897))
+ return multimode_bump_freq_ft897(myrpt, interval);
+ else if(!strcmp(myrpt->remote, remote_rig_ic706))
+ return multimode_bump_freq_ic706(myrpt, interval);
+ else
+ return -1;
+}
+
+
+/*
+* Queue announcment that scan has been stopped
+*/
+
+static void stop_scan(struct rpt *myrpt)
+{
+ myrpt->hfscanstop = 1;
+ rpt_telemetry(myrpt,SCAN,0);
+}
+
+/*
+* This is called periodically when in scan mode
+*/
+
+
+static int service_scan(struct rpt *myrpt)
+{
+ int res, interval;
+ char mhz[MAXREMSTR], decimals[MAXREMSTR], k10=0i, k100=0;
+
+ switch(myrpt->hfscanmode){
+
+ case HF_SCAN_DOWN_SLOW:
+ interval = -10; /* 100Hz /sec */
+ break;
+
+ case HF_SCAN_DOWN_QUICK:
+ interval = -50; /* 500Hz /sec */
+ break;
+
+ case HF_SCAN_DOWN_FAST:
+ interval = -200; /* 2KHz /sec */
+ break;
+
+ case HF_SCAN_UP_SLOW:
+ interval = 10; /* 100Hz /sec */
+ break;
+
+ case HF_SCAN_UP_QUICK:
+ interval = 50; /* 500 Hz/sec */
+ break;
+
+ case HF_SCAN_UP_FAST:
+ interval = 200; /* 2KHz /sec */
+ break;
+
+ default:
+ myrpt->hfscanmode = 0; /* Huh? */
+ return -1;
+ }
+
+ res = split_freq(mhz, decimals, myrpt->freq);
+
+ if(!res){
+ k100 =decimals[0];
+ k10 = decimals[1];
+ res = multimode_bump_freq(myrpt, interval);
+ }
+
+ if(!res)
+ res = split_freq(mhz, decimals, myrpt->freq);
+
+
+ if(res){
+ myrpt->hfscanmode = 0;
+ myrpt->hfscanstatus = -2;
+ return -1;
+ }
+
+ /* Announce 10KHz boundaries */
+ if(k10 != decimals[1]){
+ int myhund = (interval < 0) ? k100 : decimals[0];
+ int myten = (interval < 0) ? k10 : decimals[1];
+ myrpt->hfscanstatus = (myten == '0') ? (myhund - '0') * 100 : (myten - '0') * 10;
+ } else myrpt->hfscanstatus = 0;
+ return res;
+
+}
+
+/*
+ * Retrieve a memory channel
+ * Return 0 if sucessful,
+ * -1 if channel not found,
+ * 1 if parse error
+ */
+
+static int retreive_memory(struct rpt *myrpt, char *memory)
+{
+ char tmp[30], *s, *s1, *val;
+
+ val = (char *) ast_variable_retrieve(myrpt->cfg, myrpt->p.memory, memory);
+ if (!val){
+ return -1;
+ }
+ strncpy(tmp,val,sizeof(tmp) - 1);
+ tmp[sizeof(tmp)-1] = 0;
+
+ s = strchr(tmp,',');
+ if (!s)
+ return 1;
+ *s++ = 0;
+ s1 = strchr(s,',');
+ if (!s1)
+ return 1;
+ *s1++ = 0;
+ strncpy(myrpt->freq, tmp, sizeof(myrpt->freq) - 1);
+ strncpy(myrpt->rxpl, s, sizeof(myrpt->rxpl) - 1);
+ strncpy(myrpt->txpl, s, sizeof(myrpt->rxpl) - 1);
+ myrpt->remmode = REM_MODE_FM;
+ myrpt->offset = REM_SIMPLEX;
+ myrpt->powerlevel = REM_MEDPWR;
+ myrpt->txplon = myrpt->rxplon = 0;
+ while(*s1){
+ switch(*s1++){
+ case 'A':
+ case 'a':
+ strcpy(myrpt->rxpl, "100.0");
+ strcpy(myrpt->txpl, "100.0");
+ myrpt->remmode = REM_MODE_AM;
+ break;
+ case 'B':
+ case 'b':
+ strcpy(myrpt->rxpl, "100.0");
+ strcpy(myrpt->txpl, "100.0");
+ myrpt->remmode = REM_MODE_LSB;
+ break;
+ case 'F':
+ myrpt->remmode = REM_MODE_FM;
+ break;
+ case 'L':
+ case 'l':
+ myrpt->powerlevel = REM_LOWPWR;
+ break;
+ case 'H':
+ case 'h':
+ myrpt->powerlevel = REM_HIPWR;
+ break;
+
+ case 'M':
+ case 'm':
+ myrpt->powerlevel = REM_MEDPWR;
+ break;
+
+ case '-':
+ myrpt->offset = REM_MINUS;
+ break;
+
+ case '+':
+ myrpt->offset = REM_PLUS;
+ break;
+
+ case 'S':
+ case 's':
+ myrpt->offset = REM_SIMPLEX;
+ break;
+
+ case 'T':
+ case 't':
+ myrpt->txplon = 1;
+ break;
+
+ case 'R':
+ case 'r':
+ myrpt->rxplon = 1;
+ break;
+
+ case 'U':
+ case 'u':
+ strcpy(myrpt->rxpl, "100.0");
+ strcpy(myrpt->txpl, "100.0");
+ myrpt->remmode = REM_MODE_USB;
+ break;
+ default:
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+
+/*
+* Remote base function
+*/
+
+static int function_remote(struct rpt *myrpt, char *param, char *digitbuf, int command_source, struct rpt_link *mylink)
+{
+ char *s,*s1,*s2;
+ int i,j,p,r,ht,k,l,ls2,m,d,offset,offsave, modesave, defmode;
+ char multimode = 0;
+ char oc,*cp,*cp1,*cp2;
+ char tmp[20], freq[20] = "", savestr[20] = "";
+ char mhz[MAXREMSTR], decimals[MAXREMSTR];
+
+ if((!param) || (command_source == SOURCE_RPT) || (command_source == SOURCE_LNK))
+ return DC_ERROR;
+
+ p = myatoi(param);
+
+ if ((p != 99) && (p != 5) && (p != 140) && myrpt->p.authlevel &&
+ (!myrpt->loginlevel[0])) return DC_ERROR;
+ multimode = multimode_capable(myrpt);
+
+ switch(p){
+
+ case 1: /* retrieve memory */
+ if(strlen(digitbuf) < 2) /* needs 2 digits */
+ break;
+
+ for(i = 0 ; i < 2 ; i++){
+ if((digitbuf[i] < '0') || (digitbuf[i] > '9'))
+ return DC_ERROR;
+ }
+
+ r = retreive_memory(myrpt, digitbuf);
+ if (r < 0){
+ rpt_telemetry(myrpt,MEMNOTFOUND,NULL);
+ return DC_COMPLETE;
+ }
+ if (r > 0){
+ return DC_ERROR;
+ }
+ if (setrem(myrpt) == -1) return DC_ERROR;
+ return DC_COMPLETE;
+
+ case 2: /* set freq and offset */
+
+
+ for(i = 0, j = 0, k = 0, l = 0 ; digitbuf[i] ; i++){ /* look for M+*K+*O or M+*H+* depending on mode */
+ if(digitbuf[i] == '*'){
+ j++;
+ continue;
+ }
+ if((digitbuf[i] < '0') || (digitbuf[i] > '9'))
+ goto invalid_freq;
+ else{
+ if(j == 0)
+ l++; /* # of digits before first * */
+ if(j == 1)
+ k++; /* # of digits after first * */
+ }
+ }
+
+ i = strlen(digitbuf) - 1;
+ if(multimode){
+ if((j > 2) || (l > 3) || (k > 6))
+ goto invalid_freq; /* &^@#! */
+ }
+ else{
+ if((j > 2) || (l > 4) || (k > 3))
+ goto invalid_freq; /* &^@#! */
+ }
+
+ /* Wait for M+*K+* */
+
+ if(j < 2)
+ break; /* Not yet */
+
+ /* We have a frequency */
+
+ strncpy(tmp, digitbuf ,sizeof(tmp) - 1);
+
+ s = tmp;
+ s1 = strsep(&s, "*"); /* Pick off MHz */
+ s2 = strsep(&s,"*"); /* Pick off KHz and Hz */
+ ls2 = strlen(s2);
+
+ switch(ls2){ /* Allow partial entry of khz and hz digits for laziness support */
+ case 1:
+ ht = 0;
+ k = 100 * atoi(s2);
+ break;
+
+ case 2:
+ ht = 0;
+ k = 10 * atoi(s2);
+ break;
+
+ case 3:
+ if(!multimode){
+ if((s2[2] != '0')&&(s2[2] != '5'))
+ goto invalid_freq;
+ }
+ ht = 0;
+ k = atoi(s2);
+ break;
+ case 4:
+ k = atoi(s2)/10;
+ ht = 10 * (atoi(s2+(ls2-1)));
+ break;
+
+ case 5:
+ k = atoi(s2)/100;
+ ht = (atoi(s2+(ls2-2)));
+ break;
+
+ default:
+ goto invalid_freq;
+ }
+
+ /* Check frequency for validity and establish a default mode */
+
+ snprintf(freq, sizeof(freq), "%s.%03d%02d",s1, k, ht);
+
+ if(debug)
+ printf("New frequency: %s\n", freq);
+
+ split_freq(mhz, decimals, freq);
+ m = atoi(mhz);
+ d = atoi(decimals);
+
+ if(check_freq(myrpt, m, d, &defmode)) /* Check to see if frequency entered is legit */
+ goto invalid_freq;
+
+
+ if((defmode == REM_MODE_FM) && (digitbuf[i] == '*')) /* If FM, user must enter and additional offset digit */
+ break; /* Not yet */
+
+
+ offset = REM_SIMPLEX; /* Assume simplex */
+
+ if(defmode == REM_MODE_FM){
+ oc = *s; /* Pick off offset */
+
+ if (oc){
+ switch(oc){
+ case '1':
+ offset = REM_MINUS;
+ break;
+
+ case '2':
+ offset = REM_SIMPLEX;
+ break;
+
+ case '3':
+ offset = REM_PLUS;
+ break;
+
+ default:
+ goto invalid_freq;
+ }
+ }
+ }
+ offsave = myrpt->offset;
+ modesave = myrpt->remmode;
+ strncpy(savestr, myrpt->freq, sizeof(savestr) - 1);
+ strncpy(myrpt->freq, freq, sizeof(myrpt->freq) - 1);
+ myrpt->offset = offset;
+ myrpt->remmode = defmode;
+
+ if (setrem(myrpt) == -1){
+ myrpt->offset = offsave;
+ myrpt->remmode = modesave;
+ strncpy(myrpt->freq, savestr, sizeof(myrpt->freq) - 1);
+ goto invalid_freq;
+ }
+
+ return DC_COMPLETE;
+
+invalid_freq:
+ rpt_telemetry(myrpt,INVFREQ,NULL);
+ return DC_ERROR;
+
+ case 3: /* set rx PL tone */
+ for(i = 0, j = 0, k = 0, l = 0 ; digitbuf[i] ; i++){ /* look for N+*N */
+ if(digitbuf[i] == '*'){
+ j++;
+ continue;
+ }
+ if((digitbuf[i] < '0') || (digitbuf[i] > '9'))
+ return DC_ERROR;
+ else{
+ if(j)
+ l++;
+ else
+ k++;
+ }
+ }
+ if((j > 1) || (k > 3) || (l > 1))
+ return DC_ERROR; /* &$@^! */
+ i = strlen(digitbuf) - 1;
+ if((j != 1) || (k < 2)|| (l != 1))
+ break; /* Not yet */
+ if(debug)
+ printf("PL digits entered %s\n", digitbuf);
+
+ strncpy(tmp, digitbuf, sizeof(tmp) - 1);
+ /* see if we have at least 1 */
+ s = strchr(tmp,'*');
+ if(s)
+ *s = '.';
+ strncpy(savestr, myrpt->rxpl, sizeof(savestr) - 1);
+ strncpy(myrpt->rxpl, tmp, sizeof(myrpt->rxpl) - 1);
+ if(!strcmp(myrpt->remote, remote_rig_rbi))
+ {
+ strncpy(myrpt->txpl, tmp, sizeof(myrpt->txpl) - 1);
+ }
+ if (setrem(myrpt) == -1){
+ strncpy(myrpt->rxpl, savestr, sizeof(myrpt->rxpl) - 1);
+ return DC_ERROR;
+ }
+
+
+ return DC_COMPLETE;
+
+ case 4: /* set tx PL tone */
+ /* cant set tx tone on RBI (rx tone does both) */
+ if(!strcmp(myrpt->remote, remote_rig_rbi))
+ return DC_ERROR;
+ if(!strcmp(myrpt->remote, remote_rig_ic706))
+ return DC_ERROR;
+ for(i = 0, j = 0, k = 0, l = 0 ; digitbuf[i] ; i++){ /* look for N+*N */
+ if(digitbuf[i] == '*'){
+ j++;
+ continue;
+ }
+ if((digitbuf[i] < '0') || (digitbuf[i] > '9'))
+ return DC_ERROR;
+ else{
+ if(j)
+ l++;
+ else
+ k++;
+ }
+ }
+ if((j > 1) || (k > 3) || (l > 1))
+ return DC_ERROR; /* &$@^! */
+ i = strlen(digitbuf) - 1;
+ if((j != 1) || (k < 2)|| (l != 1))
+ break; /* Not yet */
+ if(debug)
+ printf("PL digits entered %s\n", digitbuf);
+
+ strncpy(tmp, digitbuf, sizeof(tmp) - 1);
+ /* see if we have at least 1 */
+ s = strchr(tmp,'*');
+ if(s)
+ *s = '.';
+ strncpy(savestr, myrpt->txpl, sizeof(savestr) - 1);
+ strncpy(myrpt->txpl, tmp, sizeof(myrpt->txpl) - 1);
+
+ if (setrem(myrpt) == -1){
+ strncpy(myrpt->txpl, savestr, sizeof(myrpt->txpl) - 1);
+ return DC_ERROR;
+ }
+
+
+ return DC_COMPLETE;
+
+
+ case 6: /* MODE (FM,USB,LSB,AM) */
+ if(strlen(digitbuf) < 1)
+ break;
+
+ if(!multimode)
+ return DC_ERROR; /* Multimode radios only */
+
+ switch(*digitbuf){
+ case '1':
+ split_freq(mhz, decimals, myrpt->freq);
+ m=atoi(mhz);
+ if(m < 29) /* No FM allowed below 29MHz! */
+ return DC_ERROR;
+ myrpt->remmode = REM_MODE_FM;
+
+ rpt_telemetry(myrpt,REMMODE,NULL);
+ break;
+
+ case '2':
+ myrpt->remmode = REM_MODE_USB;
+ rpt_telemetry(myrpt,REMMODE,NULL);
+ break;
+
+ case '3':
+ myrpt->remmode = REM_MODE_LSB;
+ rpt_telemetry(myrpt,REMMODE,NULL);
+ break;
+
+ case '4':
+ myrpt->remmode = REM_MODE_AM;
+ rpt_telemetry(myrpt,REMMODE,NULL);
+ break;
+
+ default:
+ return DC_ERROR;
+ }
+
+ if(setrem(myrpt))
+ return DC_ERROR;
+ return DC_COMPLETEQUIET;
+ case 99:
+ /* cant log in when logged in */
+ if (myrpt->loginlevel[0])
+ return DC_ERROR;
+ *myrpt->loginuser = 0;
+ myrpt->loginlevel[0] = 0;
+ cp = strdup(param);
+ cp1 = strchr(cp,',');
+ ast_mutex_lock(&myrpt->lock);
+ if (cp1)
+ {
+ *cp1 = 0;
+ cp2 = strchr(cp1 + 1,',');
+ if (cp2)
+ {
+ *cp2 = 0;
+ strncpy(myrpt->loginlevel,cp2 + 1,
+ sizeof(myrpt->loginlevel) - 1);
+ }
+ strncpy(myrpt->loginuser,cp1 + 1,sizeof(myrpt->loginuser));
+ ast_mutex_unlock(&myrpt->lock);
+ if (myrpt->p.archivedir)
+ {
+ char str[100];
+
+ sprintf(str,"LOGIN,%s,%s",
+ myrpt->loginuser,myrpt->loginlevel);
+ donodelog(myrpt,str);
+ }
+ if (debug)
+ printf("loginuser %s level %s\n",myrpt->loginuser,myrpt->loginlevel);
+ rpt_telemetry(myrpt,REMLOGIN,NULL);
+ }
+ free(cp);
+ return DC_COMPLETEQUIET;
+ case 100: /* RX PL Off */
+ myrpt->rxplon = 0;
+ setrem(myrpt);
+ rpt_telemetry(myrpt,REMXXX,(void *)p);
+ return DC_COMPLETEQUIET;
+ case 101: /* RX PL On */
+ myrpt->rxplon = 1;
+ setrem(myrpt);
+ rpt_telemetry(myrpt,REMXXX,(void *)p);
+ return DC_COMPLETEQUIET;
+ case 102: /* TX PL Off */
+ myrpt->txplon = 0;
+ setrem(myrpt);
+ rpt_telemetry(myrpt,REMXXX,(void *)p);
+ return DC_COMPLETEQUIET;
+ case 103: /* TX PL On */
+ myrpt->txplon = 1;
+ setrem(myrpt);
+ rpt_telemetry(myrpt,REMXXX,(void *)p);
+ return DC_COMPLETEQUIET;
+ case 104: /* Low Power */
+ if(!strcmp(myrpt->remote, remote_rig_ic706))
+ return DC_ERROR;
+ myrpt->powerlevel = REM_LOWPWR;
+ setrem(myrpt);
+ rpt_telemetry(myrpt,REMXXX,(void *)p);
+ return DC_COMPLETEQUIET;
+ case 105: /* Medium Power */
+ if(!strcmp(myrpt->remote, remote_rig_ic706))
+ return DC_ERROR;
+ myrpt->powerlevel = REM_MEDPWR;
+ setrem(myrpt);
+ rpt_telemetry(myrpt,REMXXX,(void *)p);
+ return DC_COMPLETEQUIET;
+ case 106: /* Hi Power */
+ if(!strcmp(myrpt->remote, remote_rig_ic706))
+ return DC_ERROR;
+ myrpt->powerlevel = REM_HIPWR;
+ setrem(myrpt);
+ rpt_telemetry(myrpt,REMXXX,(void *)p);
+ return DC_COMPLETEQUIET;
+ case 107: /* Bump down 20Hz */
+ multimode_bump_freq(myrpt, -20);
+ return DC_COMPLETE;
+ case 108: /* Bump down 100Hz */
+ multimode_bump_freq(myrpt, -100);
+ return DC_COMPLETE;
+ case 109: /* Bump down 500Hz */
+ multimode_bump_freq(myrpt, -500);
+ return DC_COMPLETE;
+ case 110: /* Bump up 20Hz */
+ multimode_bump_freq(myrpt, 20);
+ return DC_COMPLETE;
+ case 111: /* Bump up 100Hz */
+ multimode_bump_freq(myrpt, 100);
+ return DC_COMPLETE;
+ case 112: /* Bump up 500Hz */
+ multimode_bump_freq(myrpt, 500);
+ return DC_COMPLETE;
+ case 113: /* Scan down slow */
+ myrpt->scantimer = REM_SCANTIME;
+ myrpt->hfscanmode = HF_SCAN_DOWN_SLOW;
+ rpt_telemetry(myrpt,REMXXX,(void *)p);
+ return DC_COMPLETEQUIET;
+ case 114: /* Scan down quick */
+ myrpt->scantimer = REM_SCANTIME;
+ myrpt->hfscanmode = HF_SCAN_DOWN_QUICK;
+ rpt_telemetry(myrpt,REMXXX,(void *)p);
+ return DC_COMPLETEQUIET;
+ case 115: /* Scan down fast */
+ myrpt->scantimer = REM_SCANTIME;
+ myrpt->hfscanmode = HF_SCAN_DOWN_FAST;
+ rpt_telemetry(myrpt,REMXXX,(void *)p);
+ return DC_COMPLETEQUIET;
+ case 116: /* Scan up slow */
+ myrpt->scantimer = REM_SCANTIME;
+ myrpt->hfscanmode = HF_SCAN_UP_SLOW;
+ rpt_telemetry(myrpt,REMXXX,(void *)p);
+ return DC_COMPLETEQUIET;
+ case 117: /* Scan up quick */
+ myrpt->scantimer = REM_SCANTIME;
+ myrpt->hfscanmode = HF_SCAN_UP_QUICK;
+ rpt_telemetry(myrpt,REMXXX,(void *)p);
+ return DC_COMPLETEQUIET;
+ case 118: /* Scan up fast */
+ myrpt->scantimer = REM_SCANTIME;
+ myrpt->hfscanmode = HF_SCAN_UP_FAST;
+ rpt_telemetry(myrpt,REMXXX,(void *)p);
+ return DC_COMPLETEQUIET;
+ case 119: /* Tune Request */
+ /* if not currently going, and valid to do */
+ if((!myrpt->tunerequest) &&
+ ((!strcmp(myrpt->remote, remote_rig_ft897) ||
+ !strcmp(myrpt->remote, remote_rig_ic706)) )) {
+ myrpt->remotetx = 0;
+ ast_indicate(myrpt->txchannel,AST_CONTROL_RADIO_UNKEY);
+ myrpt->tunerequest = 1;
+ rpt_telemetry(myrpt,TUNE,NULL);
+ return DC_COMPLETEQUIET;
+ }
+ return DC_ERROR;
+ case 5: /* Long Status */
+ rpt_telemetry(myrpt,REMLONGSTATUS,NULL);
+ return DC_COMPLETEQUIET;
+ case 140: /* Short Status */
+ rpt_telemetry(myrpt,REMSHORTSTATUS,NULL);
+ return DC_COMPLETEQUIET;
+ case 200:
+ case 201:
+ case 202:
+ case 203:
+ case 204:
+ case 205:
+ case 206:
+ case 207:
+ case 208:
+ case 209:
+ case 210:
+ case 211:
+ case 212:
+ case 213:
+ case 214:
+ case 215:
+ do_dtmf_local(myrpt,remdtmfstr[p - 200]);
+ return DC_COMPLETEQUIET;
+ default:
+ break;
+ }
+ return DC_INDETERMINATE;
+}
+
+
+static int handle_remote_dtmf_digit(struct rpt *myrpt,char c, char *keyed, int phonemode)
+{
+time_t now;
+int ret,res = 0,src;
+
+ time(&myrpt->last_activity_time);
+ /* Stop scan mode if in scan mode */
+ if(myrpt->hfscanmode){
+ stop_scan(myrpt);
+ return 0;
+ }
+
+ time(&now);
+ /* if timed-out */
+ if ((myrpt->dtmf_time_rem + DTMF_TIMEOUT) < now)
+ {
+ myrpt->dtmfidx = -1;
+ myrpt->dtmfbuf[0] = 0;
+ myrpt->dtmf_time_rem = 0;
+ }
+ /* if decode not active */
+ if (myrpt->dtmfidx == -1)
+ {
+ /* if not lead-in digit, dont worry */
+ if (c != myrpt->p.funcchar)
+ {
+ if (!myrpt->p.propagate_dtmf)
+ {
+ rpt_mutex_lock(&myrpt->lock);
+ do_dtmf_local(myrpt,c);
+ rpt_mutex_unlock(&myrpt->lock);
+ }
+ return 0;
+ }
+ myrpt->dtmfidx = 0;
+ myrpt->dtmfbuf[0] = 0;
+ myrpt->dtmf_time_rem = now;
+ return 0;
+ }
+ /* if too many in buffer, start over */
+ if (myrpt->dtmfidx >= MAXDTMF)
+ {
+ myrpt->dtmfidx = 0;
+ myrpt->dtmfbuf[0] = 0;
+ myrpt->dtmf_time_rem = now;
+ }
+ if (c == myrpt->p.funcchar)
+ {
+ /* if star at beginning, or 2 together, erase buffer */
+ if ((myrpt->dtmfidx < 1) ||
+ (myrpt->dtmfbuf[myrpt->dtmfidx - 1] == myrpt->p.funcchar))
+ {
+ myrpt->dtmfidx = 0;
+ myrpt->dtmfbuf[0] = 0;
+ myrpt->dtmf_time_rem = now;
+ return 0;
+ }
+ }
+ myrpt->dtmfbuf[myrpt->dtmfidx++] = c;
+ myrpt->dtmfbuf[myrpt->dtmfidx] = 0;
+ myrpt->dtmf_time_rem = now;
+
+
+ src = SOURCE_RMT;
+ if (phonemode > 1) src = SOURCE_DPHONE;
+ else if (phonemode) src = SOURCE_PHONE;
+ ret = collect_function_digits(myrpt, myrpt->dtmfbuf, src, NULL);
+
+ switch(ret){
+
+ case DC_INDETERMINATE:
+ res = 0;
+ break;
+
+ case DC_DOKEY:
+ if (keyed) *keyed = 1;
+ res = 0;
+ break;
+
+ case DC_REQ_FLUSH:
+ myrpt->dtmfidx = 0;
+ myrpt->dtmfbuf[0] = 0;
+ res = 0;
+ break;
+
+
+ case DC_COMPLETE:
+ res = 1;
+ case DC_COMPLETEQUIET:
+ myrpt->totalexecdcommands++;
+ myrpt->dailyexecdcommands++;
+ strncpy(myrpt->lastdtmfcommand, myrpt->dtmfbuf, MAXDTMF-1);
+ myrpt->lastdtmfcommand[MAXDTMF-1] = '\0';
+ myrpt->dtmfbuf[0] = 0;
+ myrpt->dtmfidx = -1;
+ myrpt->dtmf_time_rem = 0;
+ break;
+
+ case DC_ERROR:
+ default:
+ myrpt->dtmfbuf[0] = 0;
+ myrpt->dtmfidx = -1;
+ myrpt->dtmf_time_rem = 0;
+ res = 0;
+ break;
+ }
+
+ return res;
+}
+
+static int handle_remote_data(struct rpt *myrpt, char *str)
+{
+char tmp[300],cmd[300],dest[300],src[300],c;
+int seq,res;
+
+ /* put string in our buffer */
+ strncpy(tmp,str,sizeof(tmp) - 1);
+ if (!strcmp(tmp,discstr)) return 0;
+
+#ifndef DO_NOT_NOTIFY_MDC1200_ON_REMOTE_BASES
+ if (tmp[0] == 'I')
+ {
+ if (sscanf(tmp,"%s %s %x",cmd,src,&seq) != 3)
+ {
+ ast_log(LOG_WARNING, "Unable to parse ident string %s\n",str);
+ return 0;
+ }
+ mdc1200_notify(myrpt,src,seq);
+ return 0;
+ }
+#endif
+ if (sscanf(tmp,"%s %s %s %d %c",cmd,dest,src,&seq,&c) != 5)
+ {
+ ast_log(LOG_WARNING, "Unable to parse link string %s\n",str);
+ return 0;
+ }
+ if (strcmp(cmd,"D"))
+ {
+ ast_log(LOG_WARNING, "Unable to parse link string %s\n",str);
+ return 0;
+ }
+ /* if not for me, ignore */
+ if (strcmp(dest,myrpt->name)) return 0;
+ if (myrpt->p.archivedir)
+ {
+ char str[100];
+
+ sprintf(str,"DTMF,%c",c);
+ donodelog(myrpt,str);
+ }
+ c = func_xlat(myrpt,c,&myrpt->p.outxlat);
+ if (!c) return(0);
+ res = handle_remote_dtmf_digit(myrpt,c, NULL, 0);
+ if (res != 1)
+ return res;
+ rpt_telemetry(myrpt,COMPLETE,NULL);
+ return 0;
+}
+
+static int handle_remote_phone_dtmf(struct rpt *myrpt, char c, char *keyed, int phonemode)
+{
+int res;
+
+
+ if (keyed && *keyed && (c == myrpt->p.endchar))
+ {
+ *keyed = 0;
+ return DC_INDETERMINATE;
+ }
+
+ if (myrpt->p.archivedir)
+ {
+ char str[100];
+
+ sprintf(str,"DTMF(P),%c",c);
+ donodelog(myrpt,str);
+ }
+ res = handle_remote_dtmf_digit(myrpt,c,keyed, phonemode);
+ if (res != 1)
+ return res;
+ rpt_telemetry(myrpt,COMPLETE,NULL);
+ return 0;
+}
+
+static int attempt_reconnect(struct rpt *myrpt, struct rpt_link *l)
+{
+ char *val, *s, *s1, *s2, *tele;
+ char tmp[300], deststr[300] = "";
+
+ val = node_lookup(myrpt,l->name);
+ if (!val)
+ {
+ fprintf(stderr,"attempt_reconnect: cannot find node %s\n",l->name);
+ return -1;
+ }
+
+ rpt_mutex_lock(&myrpt->lock);
+ /* remove from queue */
+ remque((struct qelem *) l);
+ rpt_mutex_unlock(&myrpt->lock);
+ strncpy(tmp,val,sizeof(tmp) - 1);
+ s = tmp;
+ s1 = strsep(&s,",");
+ s2 = strsep(&s,",");
+ snprintf(deststr, sizeof(deststr), "IAX2/%s", s1);
+ tele = strchr(deststr, '/');
+ if (!tele) {
+ fprintf(stderr,"attempt_reconnect:Dial number (%s) must be in format tech/number\n",deststr);
+ return -1;
+ }
+ *tele++ = 0;
+ l->elaptime = 0;
+ l->connecttime = 0;
+ l->thisconnected = 0;
+ l->chan = ast_request(deststr, AST_FORMAT_SLINEAR, tele,NULL);
+ if (l->chan){
+ ast_set_read_format(l->chan, AST_FORMAT_SLINEAR);
+ ast_set_write_format(l->chan, AST_FORMAT_SLINEAR);
+ l->chan->whentohangup = 0;
+ l->chan->appl = "Apprpt";
+ l->chan->data = "(Remote Rx)";
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "rpt (attempt_reconnect) initiating call to %s/%s on %s\n",
+ deststr, tele, l->chan->name);
+ if(l->chan->cid.cid_num)
+ free(l->chan->cid.cid_num);
+ l->chan->cid.cid_num = strdup(myrpt->name);
+ ast_call(l->chan,tele,999);
+
+ }
+ else
+ {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Unable to place call to %s/%s on %s\n",
+ deststr,tele,l->chan->name);
+ return -1;
+ }
+ rpt_mutex_lock(&myrpt->lock);
+ /* put back in queue */
+ insque((struct qelem *)l,(struct qelem *)myrpt->links.next);
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_log(LOG_NOTICE,"Reconnect Attempt to %s in process\n",l->name);
+ return 0;
+}
+
+/* 0 return=continue, 1 return = break, -1 return = error */
+static void local_dtmf_helper(struct rpt *myrpt,char c)
+{
+int res;
+pthread_attr_t attr;
+char cmd[MAXDTMF+1] = "";
+
+ if (myrpt->p.archivedir)
+ {
+ char str[100];
+
+ sprintf(str,"DTMF,MAIN,%c",c);
+ donodelog(myrpt,str);
+ }
+ if (c == myrpt->p.endchar)
+ {
+ /* if in simple mode, kill autopatch */
+ if (myrpt->p.simple && myrpt->callmode)
+ {
+ rpt_mutex_lock(&myrpt->lock);
+ myrpt->callmode = 0;
+ rpt_mutex_unlock(&myrpt->lock);
+ rpt_telemetry(myrpt,TERM,NULL);
+ return;
+ }
+ rpt_mutex_lock(&myrpt->lock);
+ myrpt->stopgen = 1;
+ if (myrpt->cmdnode[0])
+ {
+ myrpt->cmdnode[0] = 0;
+ myrpt->dtmfidx = -1;
+ myrpt->dtmfbuf[0] = 0;
+ rpt_mutex_unlock(&myrpt->lock);
+ rpt_telemetry(myrpt,COMPLETE,NULL);
+ }
+ else
+ {
+ rpt_mutex_unlock(&myrpt->lock);
+ if (myrpt->p.propagate_phonedtmf)
+ do_dtmf_phone(myrpt,NULL,c);
+ }
+ return;
+ }
+ rpt_mutex_lock(&myrpt->lock);
+ if (myrpt->cmdnode[0])
+ {
+ rpt_mutex_unlock(&myrpt->lock);
+ send_link_dtmf(myrpt,c);
+ return;
+ }
+ if (!myrpt->p.simple)
+ {
+ if (c == myrpt->p.funcchar)
+ {
+ myrpt->dtmfidx = 0;
+ myrpt->dtmfbuf[myrpt->dtmfidx] = 0;
+ rpt_mutex_unlock(&myrpt->lock);
+ time(&myrpt->dtmf_time);
+ return;
+ }
+ else if ((c != myrpt->p.endchar) && (myrpt->dtmfidx >= 0))
+ {
+ time(&myrpt->dtmf_time);
+
+ if (myrpt->dtmfidx < MAXDTMF)
+ {
+ myrpt->dtmfbuf[myrpt->dtmfidx++] = c;
+ myrpt->dtmfbuf[myrpt->dtmfidx] = 0;
+
+ strncpy(cmd, myrpt->dtmfbuf, sizeof(cmd) - 1);
+
+ rpt_mutex_unlock(&myrpt->lock);
+ res = collect_function_digits(myrpt, cmd, SOURCE_RPT, NULL);
+ rpt_mutex_lock(&myrpt->lock);
+ switch(res){
+ case DC_INDETERMINATE:
+ break;
+ case DC_REQ_FLUSH:
+ myrpt->dtmfidx = 0;
+ myrpt->dtmfbuf[0] = 0;
+ break;
+ case DC_COMPLETE:
+ case DC_COMPLETEQUIET:
+ myrpt->totalexecdcommands++;
+ myrpt->dailyexecdcommands++;
+ strncpy(myrpt->lastdtmfcommand, cmd, MAXDTMF-1);
+ myrpt->lastdtmfcommand[MAXDTMF-1] = '\0';
+ myrpt->dtmfbuf[0] = 0;
+ myrpt->dtmfidx = -1;
+ myrpt->dtmf_time = 0;
+ break;
+
+ case DC_ERROR:
+ default:
+ myrpt->dtmfbuf[0] = 0;
+ myrpt->dtmfidx = -1;
+ myrpt->dtmf_time = 0;
+ break;
+ }
+ if(res != DC_INDETERMINATE) {
+ rpt_mutex_unlock(&myrpt->lock);
+ return;
+ }
+ }
+ }
+ }
+ else /* if simple */
+ {
+ if ((!myrpt->callmode) && (c == myrpt->p.funcchar))
+ {
+ myrpt->callmode = 1;
+ myrpt->patchnoct = 0;
+ myrpt->patchquiet = 0;
+ myrpt->patchfarenddisconnect = 0;
+ myrpt->patchdialtime = 0;
+ strncpy(myrpt->patchcontext, myrpt->p.ourcontext, MAXPATCHCONTEXT);
+ myrpt->cidx = 0;
+ myrpt->exten[myrpt->cidx] = 0;
+ rpt_mutex_unlock(&myrpt->lock);
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ ast_pthread_create(&myrpt->rpt_call_thread,&attr,rpt_call,(void *)myrpt);
+ return;
+ }
+ }
+ if (myrpt->callmode == 1)
+ {
+ myrpt->exten[myrpt->cidx++] = c;
+ myrpt->exten[myrpt->cidx] = 0;
+ /* if this exists */
+ if (ast_exists_extension(myrpt->pchannel,myrpt->patchcontext,myrpt->exten,1,NULL))
+ {
+ myrpt->callmode = 2;
+ rpt_mutex_unlock(&myrpt->lock);
+ if(!myrpt->patchquiet)
+ rpt_telemetry(myrpt,PROC,NULL);
+ return;
+ }
+ /* if can continue, do so */
+ if (!ast_canmatch_extension(myrpt->pchannel,myrpt->patchcontext,myrpt->exten,1,NULL))
+ {
+ /* call has failed, inform user */
+ myrpt->callmode = 4;
+ }
+ rpt_mutex_unlock(&myrpt->lock);
+ return;
+ }
+ if ((myrpt->callmode == 2) || (myrpt->callmode == 3))
+ {
+ myrpt->mydtmf = c;
+ }
+ rpt_mutex_unlock(&myrpt->lock);
+ if ((myrpt->dtmfidx < 0) && myrpt->p.propagate_phonedtmf)
+ do_dtmf_phone(myrpt,NULL,c);
+ return;
+}
+
+
+/* place an ID event in the telemetry queue */
+
+static void queue_id(struct rpt *myrpt)
+{
+ if(myrpt->p.idtime){ /* ID time must be non-zero */
+ myrpt->mustid = myrpt->tailid = 0;
+ myrpt->idtimer = myrpt->p.idtime; /* Reset our ID timer */
+ rpt_mutex_unlock(&myrpt->lock);
+ rpt_telemetry(myrpt,ID,NULL);
+ rpt_mutex_lock(&myrpt->lock);
+ }
+}
+
+/* Scheduler */
+/* must be called locked */
+
+static void do_scheduler(struct rpt *myrpt)
+{
+ int i,res;
+ struct tm tmnow;
+ struct ast_variable *skedlist;
+ char *strs[5],*vp,*val,value[100];
+
+ memcpy(&myrpt->lasttv, &myrpt->curtv, sizeof(struct timeval));
+
+ if( (res = gettimeofday(&myrpt->curtv, NULL)) < 0)
+ ast_log(LOG_NOTICE, "Scheduler gettime of day returned: %s\n", strerror(res));
+
+ /* Try to get close to a 1 second resolution */
+
+ if(myrpt->lasttv.tv_sec == myrpt->curtv.tv_sec)
+ return;
+
+ rpt_localtime(&myrpt->curtv.tv_sec, &tmnow);
+
+ /* If midnight, then reset all daily statistics */
+
+ if((tmnow.tm_hour == 0)&&(tmnow.tm_min == 0)&&(tmnow.tm_sec == 0)){
+ myrpt->dailykeyups = 0;
+ myrpt->dailytxtime = 0;
+ myrpt->dailykerchunks = 0;
+ myrpt->dailyexecdcommands = 0;
+ }
+
+ if(tmnow.tm_sec != 0)
+ return;
+
+ /* Code below only executes once per minute */
+
+
+ /* Don't schedule if remote */
+
+ if (myrpt->remote)
+ return;
+
+ /* Don't schedule if disabled */
+
+ if(myrpt->p.s[myrpt->p.sysstate_cur].schedulerdisable){
+ if(debug > 6)
+ ast_log(LOG_NOTICE, "Scheduler disabled\n");
+ return;
+ }
+
+ if(!myrpt->p.skedstanzaname){ /* No stanza means we do nothing */
+ if(debug > 6)
+ ast_log(LOG_NOTICE,"No stanza for scheduler in rpt.conf\n");
+ return;
+ }
+
+ /* get pointer to linked list of scheduler entries */
+ skedlist = ast_variable_browse(myrpt->cfg, myrpt->p.skedstanzaname);
+
+ if(debug > 6){
+ ast_log(LOG_NOTICE, "Time now: %02d:%02d %02d %02d %02d\n",
+ tmnow.tm_hour,tmnow.tm_min,tmnow.tm_mday,tmnow.tm_mon + 1, tmnow.tm_wday);
+ }
+ /* walk the list */
+ for(; skedlist; skedlist = skedlist->next){
+ if(debug > 6)
+ ast_log(LOG_NOTICE, "Scheduler entry %s = %s being considered\n",skedlist->name, skedlist->value);
+ strncpy(value,skedlist->value,99);
+ value[99] = 0;
+ /* point to the substrings for minute, hour, dom, month, and dow */
+ for( i = 0, vp = value ; i < 5; i++){
+ if(!*vp)
+ break;
+ while((*vp == ' ') || (*vp == 0x09)) /* get rid of any leading white space */
+ vp++;
+ strs[i] = vp; /* save pointer to beginning of substring */
+ while((*vp != ' ') && (*vp != 0x09) && (*vp != 0)) /* skip over substring */
+ vp++;
+ if(*vp)
+ *vp++ = 0; /* mark end of substring */
+ }
+ if(debug > 6)
+ ast_log(LOG_NOTICE, "i = %d, min = %s, hour = %s, mday=%s, mon=%s, wday=%s\n",i,
+ strs[0], strs[1], strs[2], strs[3], strs[4]);
+ if(i == 5){
+ if((*strs[0] != '*')&&(atoi(strs[0]) != tmnow.tm_min))
+ continue;
+ if((*strs[1] != '*')&&(atoi(strs[1]) != tmnow.tm_hour))
+ continue;
+ if((*strs[2] != '*')&&(atoi(strs[2]) != tmnow.tm_mday))
+ continue;
+ if((*strs[3] != '*')&&(atoi(strs[3]) != tmnow.tm_mon + 1))
+ continue;
+ if(atoi(strs[4]) == 7)
+ strs[4] = "0";
+ if((*strs[4] != '*')&&(atoi(strs[4]) != tmnow.tm_wday))
+ continue;
+ if(debug)
+ ast_log(LOG_NOTICE, "Executing scheduler entry %s = %s\n", skedlist->name, skedlist->value);
+ if(atoi(skedlist->name) == 0)
+ return; /* Zero is reserved for the startup macro */
+ val = (char *) ast_variable_retrieve(myrpt->cfg, myrpt->p.macro, skedlist->name);
+ if (!val){
+ ast_log(LOG_WARNING,"Scheduler could not find macro %s\n",skedlist->name);
+ return; /* Macro not found */
+ }
+ if ((MAXMACRO - strlen(myrpt->macrobuf)) < strlen(val)){
+ ast_log(LOG_WARNING, "Scheduler could not execute macro %s: Macro buffer full\n",
+ skedlist->name);
+ return; /* Macro buffer full */
+ }
+ myrpt->macrotimer = MACROTIME;
+ strncat(myrpt->macrobuf,val,MAXMACRO - strlen(myrpt->macrobuf) - 1);
+ }
+ else{
+ ast_log(LOG_WARNING,"Malformed scheduler entry in rpt.conf: %s = %s\n",
+ skedlist->name, skedlist->value);
+ }
+ }
+
+}
+
+/* single thread with one file (request) to dial */
+static void *rpt(void *this)
+{
+struct rpt *myrpt = (struct rpt *)this;
+char *tele,*idtalkover,c;
+int ms = MSWAIT,i,lasttx=0,val,remrx=0,identqueued,othertelemqueued;
+int tailmessagequeued,ctqueued,dtmfed;
+struct ast_channel *who;
+struct dahdi_confinfo ci; /* conference info */
+time_t t;
+struct rpt_link *l,*m;
+struct rpt_tele *telem;
+char tmpstr[300],lstr[MAXLINKLIST];
+
+
+ if (myrpt->p.archivedir) mkdir(myrpt->p.archivedir,0600);
+ sprintf(tmpstr,"%s/%s",myrpt->p.archivedir,myrpt->name);
+ mkdir(tmpstr,0600);
+ rpt_mutex_lock(&myrpt->lock);
+
+ telem = myrpt->tele.next;
+ while(telem != &myrpt->tele)
+ {
+ ast_softhangup(telem->chan,AST_SOFTHANGUP_DEV);
+ telem = telem->next;
+ }
+ rpt_mutex_unlock(&myrpt->lock);
+ /* find our index, and load the vars initially */
+ for(i = 0; i < nrpts; i++)
+ {
+ if (&rpt_vars[i] == myrpt)
+ {
+ load_rpt_vars(i,0);
+ break;
+ }
+ }
+ rpt_mutex_lock(&myrpt->lock);
+ strncpy(tmpstr,myrpt->rxchanname,sizeof(tmpstr) - 1);
+ tele = strchr(tmpstr,'/');
+ if (!tele)
+ {
+ fprintf(stderr,"rpt:Rxchannel Dial number (%s) must be in format tech/number\n",myrpt->rxchanname);
+ rpt_mutex_unlock(&myrpt->lock);
+ myrpt->rpt_thread = AST_PTHREADT_STOP;
+ pthread_exit(NULL);
+ }
+ *tele++ = 0;
+ myrpt->rxchannel = ast_request(tmpstr,AST_FORMAT_SLINEAR,tele,NULL);
+ myrpt->zaprxchannel = NULL;
+ if (!strcasecmp(tmpstr,"Zap"))
+ myrpt->zaprxchannel = myrpt->rxchannel;
+ if (myrpt->rxchannel)
+ {
+ if (myrpt->rxchannel->_state == AST_STATE_BUSY)
+ {
+ fprintf(stderr,"rpt:Sorry unable to obtain Rx channel\n");
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_hangup(myrpt->rxchannel);
+ myrpt->rpt_thread = AST_PTHREADT_STOP;
+ pthread_exit(NULL);
+ }
+ ast_set_read_format(myrpt->rxchannel,AST_FORMAT_SLINEAR);
+ ast_set_write_format(myrpt->rxchannel,AST_FORMAT_SLINEAR);
+#ifdef AST_CDR_FLAG_POST_DISABLED
+ ast_set_flag(myrpt->rxchannel->cdr,AST_CDR_FLAG_POST_DISABLED);
+#endif
+ myrpt->rxchannel->whentohangup = 0;
+ myrpt->rxchannel->appl = "Apprpt";
+ myrpt->rxchannel->data = "(Repeater Rx)";
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "rpt (Rx) initiating call to %s/%s on %s\n",
+ tmpstr,tele,myrpt->rxchannel->name);
+ ast_call(myrpt->rxchannel,tele,999);
+ if (myrpt->rxchannel->_state != AST_STATE_UP)
+ {
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_hangup(myrpt->rxchannel);
+ myrpt->rpt_thread = AST_PTHREADT_STOP;
+ pthread_exit(NULL);
+ }
+ }
+ else
+ {
+ fprintf(stderr,"rpt:Sorry unable to obtain Rx channel\n");
+ rpt_mutex_unlock(&myrpt->lock);
+ myrpt->rpt_thread = AST_PTHREADT_STOP;
+ pthread_exit(NULL);
+ }
+ myrpt->zaptxchannel = NULL;
+ if (myrpt->txchanname)
+ {
+ strncpy(tmpstr,myrpt->txchanname,sizeof(tmpstr) - 1);
+ tele = strchr(tmpstr,'/');
+ if (!tele)
+ {
+ fprintf(stderr,"rpt:Txchannel Dial number (%s) must be in format tech/number\n",myrpt->txchanname);
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_hangup(myrpt->rxchannel);
+ myrpt->rpt_thread = AST_PTHREADT_STOP;
+ pthread_exit(NULL);
+ }
+ *tele++ = 0;
+ myrpt->txchannel = ast_request(tmpstr,AST_FORMAT_SLINEAR,tele,NULL);
+ if (!strcasecmp(tmpstr,"Zap"))
+ myrpt->zaptxchannel = myrpt->txchannel;
+ if (myrpt->txchannel)
+ {
+ if (myrpt->txchannel->_state == AST_STATE_BUSY)
+ {
+ fprintf(stderr,"rpt:Sorry unable to obtain Tx channel\n");
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_hangup(myrpt->txchannel);
+ ast_hangup(myrpt->rxchannel);
+ myrpt->rpt_thread = AST_PTHREADT_STOP;
+ pthread_exit(NULL);
+ }
+ ast_set_read_format(myrpt->txchannel,AST_FORMAT_SLINEAR);
+ ast_set_write_format(myrpt->txchannel,AST_FORMAT_SLINEAR);
+#ifdef AST_CDR_FLAG_POST_DISABLED
+ ast_set_flag(myrpt->txchannel->cdr,AST_CDR_FLAG_POST_DISABLED);
+#endif
+ myrpt->txchannel->whentohangup = 0;
+ myrpt->txchannel->appl = "Apprpt";
+ myrpt->txchannel->data = "(Repeater Tx)";
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "rpt (Tx) initiating call to %s/%s on %s\n",
+ tmpstr,tele,myrpt->txchannel->name);
+ ast_call(myrpt->txchannel,tele,999);
+ if (myrpt->rxchannel->_state != AST_STATE_UP)
+ {
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_hangup(myrpt->rxchannel);
+ ast_hangup(myrpt->txchannel);
+ myrpt->rpt_thread = AST_PTHREADT_STOP;
+ pthread_exit(NULL);
+ }
+ }
+ else
+ {
+ fprintf(stderr,"rpt:Sorry unable to obtain Tx channel\n");
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_hangup(myrpt->rxchannel);
+ myrpt->rpt_thread = AST_PTHREADT_STOP;
+ pthread_exit(NULL);
+ }
+ }
+ else
+ {
+ myrpt->txchannel = myrpt->rxchannel;
+ }
+ ast_indicate(myrpt->txchannel,AST_CONTROL_RADIO_KEY);
+ ast_indicate(myrpt->txchannel,AST_CONTROL_RADIO_UNKEY);
+ /* allocate a pseudo-channel thru asterisk */
+ myrpt->pchannel = ast_request("zap",AST_FORMAT_SLINEAR,"pseudo",NULL);
+ if (!myrpt->pchannel)
+ {
+ fprintf(stderr,"rpt:Sorry unable to obtain pseudo channel\n");
+ rpt_mutex_unlock(&myrpt->lock);
+ if (myrpt->txchannel != myrpt->rxchannel)
+ ast_hangup(myrpt->txchannel);
+ ast_hangup(myrpt->rxchannel);
+ myrpt->rpt_thread = AST_PTHREADT_STOP;
+ pthread_exit(NULL);
+ }
+#ifdef AST_CDR_FLAG_POST_DISABLED
+ ast_set_flag(myrpt->pchannel->cdr,AST_CDR_FLAG_POST_DISABLED);
+#endif
+ if (!myrpt->zaprxchannel) myrpt->zaprxchannel = myrpt->pchannel;
+ if (!myrpt->zaptxchannel)
+ {
+ /* allocate a pseudo-channel thru asterisk */
+ myrpt->zaptxchannel = ast_request("zap",AST_FORMAT_SLINEAR,"pseudo",NULL);
+ if (!myrpt->zaptxchannel)
+ {
+ fprintf(stderr,"rpt:Sorry unable to obtain pseudo channel\n");
+ rpt_mutex_unlock(&myrpt->lock);
+ if (myrpt->txchannel != myrpt->rxchannel)
+ ast_hangup(myrpt->txchannel);
+ ast_hangup(myrpt->rxchannel);
+ myrpt->rpt_thread = AST_PTHREADT_STOP;
+ pthread_exit(NULL);
+ }
+ ast_set_read_format(myrpt->zaptxchannel,AST_FORMAT_SLINEAR);
+ ast_set_write_format(myrpt->zaptxchannel,AST_FORMAT_SLINEAR);
+#ifdef AST_CDR_FLAG_POST_DISABLED
+ ast_set_flag(myrpt->zaptxchannel->cdr,AST_CDR_FLAG_POST_DISABLED);
+#endif
+ }
+ /* allocate a pseudo-channel thru asterisk */
+ myrpt->monchannel = ast_request("zap",AST_FORMAT_SLINEAR,"pseudo",NULL);
+ if (!myrpt->monchannel)
+ {
+ fprintf(stderr,"rpt:Sorry unable to obtain pseudo channel\n");
+ rpt_mutex_unlock(&myrpt->lock);
+ if (myrpt->txchannel != myrpt->rxchannel)
+ ast_hangup(myrpt->txchannel);
+ ast_hangup(myrpt->rxchannel);
+ myrpt->rpt_thread = AST_PTHREADT_STOP;
+ pthread_exit(NULL);
+ }
+ ast_set_read_format(myrpt->monchannel,AST_FORMAT_SLINEAR);
+ ast_set_write_format(myrpt->monchannel,AST_FORMAT_SLINEAR);
+#ifdef AST_CDR_FLAG_POST_DISABLED
+ ast_set_flag(myrpt->monchannel->cdr,AST_CDR_FLAG_POST_DISABLED);
+#endif
+ /* make a conference for the tx */
+ ci.chan = 0;
+ ci.confno = -1; /* make a new conf */
+ ci.confmode = DAHDI_CONF_CONF | DAHDI_CONF_LISTENER;
+ /* first put the channel on the conference in proper mode */
+ if (ioctl(myrpt->zaptxchannel->fds[0],DAHDI_SETCONF,&ci) == -1)
+ {
+ ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n");
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_hangup(myrpt->pchannel);
+ ast_hangup(myrpt->monchannel);
+ if (myrpt->txchannel != myrpt->rxchannel)
+ ast_hangup(myrpt->txchannel);
+ ast_hangup(myrpt->rxchannel);
+ myrpt->rpt_thread = AST_PTHREADT_STOP;
+ pthread_exit(NULL);
+ }
+ /* save tx conference number */
+ myrpt->txconf = ci.confno;
+ /* make a conference for the pseudo */
+ ci.chan = 0;
+ ci.confno = -1; /* make a new conf */
+ ci.confmode = ((myrpt->p.duplex == 2) || (myrpt->p.duplex == 4)) ? DAHDI_CONF_CONFANNMON :
+ (DAHDI_CONF_CONF | DAHDI_CONF_LISTENER | DAHDI_CONF_TALKER);
+ /* first put the channel on the conference in announce mode */
+ if (ioctl(myrpt->pchannel->fds[0],DAHDI_SETCONF,&ci) == -1)
+ {
+ ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n");
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_hangup(myrpt->pchannel);
+ ast_hangup(myrpt->monchannel);
+ if (myrpt->txchannel != myrpt->rxchannel)
+ ast_hangup(myrpt->txchannel);
+ ast_hangup(myrpt->rxchannel);
+ myrpt->rpt_thread = AST_PTHREADT_STOP;
+ pthread_exit(NULL);
+ }
+ /* save pseudo channel conference number */
+ myrpt->conf = ci.confno;
+ /* make a conference for the pseudo */
+ ci.chan = 0;
+ if ((strstr(myrpt->txchannel->name,"pseudo") == NULL) &&
+ (myrpt->zaptxchannel == myrpt->txchannel))
+ {
+ /* get tx channel's port number */
+ if (ioctl(myrpt->txchannel->fds[0],DAHDI_CHANNO,&ci.confno) == -1)
+ {
+ ast_log(LOG_WARNING, "Unable to set tx channel's chan number\n");
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_hangup(myrpt->pchannel);
+ ast_hangup(myrpt->monchannel);
+ if (myrpt->txchannel != myrpt->rxchannel)
+ ast_hangup(myrpt->txchannel);
+ ast_hangup(myrpt->rxchannel);
+ myrpt->rpt_thread = AST_PTHREADT_STOP;
+ pthread_exit(NULL);
+ }
+ ci.confmode = DAHDI_CONF_MONITORTX;
+ }
+ else
+ {
+ ci.confno = myrpt->txconf;
+ ci.confmode = DAHDI_CONF_CONFANNMON;
+ }
+ /* first put the channel on the conference in announce mode */
+ if (ioctl(myrpt->monchannel->fds[0],DAHDI_SETCONF,&ci) == -1)
+ {
+ ast_log(LOG_WARNING, "Unable to set conference mode for monitor\n");
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_hangup(myrpt->pchannel);
+ ast_hangup(myrpt->monchannel);
+ if (myrpt->txchannel != myrpt->rxchannel)
+ ast_hangup(myrpt->txchannel);
+ ast_hangup(myrpt->rxchannel);
+ myrpt->rpt_thread = AST_PTHREADT_STOP;
+ pthread_exit(NULL);
+ }
+ /* allocate a pseudo-channel thru asterisk */
+ myrpt->txpchannel = ast_request("zap",AST_FORMAT_SLINEAR,"pseudo",NULL);
+ if (!myrpt->txpchannel)
+ {
+ fprintf(stderr,"rpt:Sorry unable to obtain pseudo channel\n");
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_hangup(myrpt->pchannel);
+ ast_hangup(myrpt->monchannel);
+ if (myrpt->txchannel != myrpt->rxchannel)
+ ast_hangup(myrpt->txchannel);
+ ast_hangup(myrpt->rxchannel);
+ myrpt->rpt_thread = AST_PTHREADT_STOP;
+ pthread_exit(NULL);
+ }
+#ifdef AST_CDR_FLAG_POST_DISABLED
+ ast_set_flag(myrpt->txpchannel->cdr,AST_CDR_FLAG_POST_DISABLED);
+#endif
+ /* make a conference for the tx */
+ ci.chan = 0;
+ ci.confno = myrpt->txconf;
+ ci.confmode = DAHDI_CONF_CONF | DAHDI_CONF_TALKER ;
+ /* first put the channel on the conference in proper mode */
+ if (ioctl(myrpt->txpchannel->fds[0],DAHDI_SETCONF,&ci) == -1)
+ {
+ ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n");
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_hangup(myrpt->txpchannel);
+ ast_hangup(myrpt->monchannel);
+ if (myrpt->txchannel != myrpt->rxchannel)
+ ast_hangup(myrpt->txchannel);
+ ast_hangup(myrpt->rxchannel);
+ myrpt->rpt_thread = AST_PTHREADT_STOP;
+ pthread_exit(NULL);
+ }
+ /* Now, the idea here is to copy from the physical rx channel buffer
+ into the pseudo tx buffer, and from the pseudo rx buffer into the
+ tx channel buffer */
+ myrpt->links.next = &myrpt->links;
+ myrpt->links.prev = &myrpt->links;
+ myrpt->tailtimer = 0;
+ myrpt->totimer = 0;
+ myrpt->tmsgtimer = myrpt->p.tailmessagetime;
+ myrpt->idtimer = myrpt->p.politeid;
+ myrpt->mustid = myrpt->tailid = 0;
+ myrpt->callmode = 0;
+ myrpt->tounkeyed = 0;
+ myrpt->tonotify = 0;
+ myrpt->retxtimer = 0;
+ myrpt->rerxtimer = 0;
+ myrpt->skedtimer = 0;
+ myrpt->tailevent = 0;
+ lasttx = 0;
+ myrpt->keyed = 0;
+ idtalkover = (char *) ast_variable_retrieve(myrpt->cfg, myrpt->name, "idtalkover");
+ myrpt->dtmfidx = -1;
+ myrpt->dtmfbuf[0] = 0;
+ myrpt->rem_dtmfidx = -1;
+ myrpt->rem_dtmfbuf[0] = 0;
+ myrpt->dtmf_time = 0;
+ myrpt->rem_dtmf_time = 0;
+ myrpt->disgorgetime = 0;
+ myrpt->lastnodewhichkeyedusup[0] = '\0';
+ myrpt->dailytxtime = 0;
+ myrpt->totaltxtime = 0;
+ myrpt->dailykeyups = 0;
+ myrpt->totalkeyups = 0;
+ myrpt->dailykerchunks = 0;
+ myrpt->totalkerchunks = 0;
+ myrpt->dailyexecdcommands = 0;
+ myrpt->totalexecdcommands = 0;
+ myrpt->timeouts = 0;
+ myrpt->exten[0] = '\0';
+ myrpt->lastdtmfcommand[0] = '\0';
+ if (myrpt->p.startupmacro)
+ {
+ snprintf(myrpt->macrobuf,MAXMACRO - 1,"PPPP%s",myrpt->p.startupmacro);
+ }
+ rpt_mutex_unlock(&myrpt->lock);
+ val = 1;
+ ast_channel_setoption(myrpt->rxchannel,AST_OPTION_RELAXDTMF,&val,sizeof(char),0);
+ val = 1;
+ ast_channel_setoption(myrpt->rxchannel,AST_OPTION_TONE_VERIFY,&val,sizeof(char),0);
+ if (myrpt->p.archivedir) donodelog(myrpt,"STARTUP");
+ dtmfed = 0;
+ while (ms >= 0)
+ {
+ struct ast_frame *f,*f1,*f2;
+ struct ast_channel *cs[300],*cs1[300];
+ int totx=0,elap=0,n,x,toexit=0;
+
+ /* DEBUG Dump */
+ if((myrpt->disgorgetime) && (time(NULL) >= myrpt->disgorgetime)){
+ struct rpt_link *zl;
+ struct rpt_tele *zt;
+
+ myrpt->disgorgetime = 0;
+ ast_log(LOG_NOTICE,"********** Variable Dump Start (app_rpt) **********\n");
+ ast_log(LOG_NOTICE,"totx = %d\n",totx);
+ ast_log(LOG_NOTICE,"remrx = %d\n",remrx);
+ ast_log(LOG_NOTICE,"lasttx = %d\n",lasttx);
+ ast_log(LOG_NOTICE,"elap = %d\n",elap);
+ ast_log(LOG_NOTICE,"toexit = %d\n",toexit);
+
+ ast_log(LOG_NOTICE,"myrpt->keyed = %d\n",myrpt->keyed);
+ ast_log(LOG_NOTICE,"myrpt->localtx = %d\n",myrpt->localtx);
+ ast_log(LOG_NOTICE,"myrpt->callmode = %d\n",myrpt->callmode);
+ ast_log(LOG_NOTICE,"myrpt->mustid = %d\n",myrpt->mustid);
+ ast_log(LOG_NOTICE,"myrpt->tounkeyed = %d\n",myrpt->tounkeyed);
+ ast_log(LOG_NOTICE,"myrpt->tonotify = %d\n",myrpt->tonotify);
+ ast_log(LOG_NOTICE,"myrpt->retxtimer = %ld\n",myrpt->retxtimer);
+ ast_log(LOG_NOTICE,"myrpt->totimer = %d\n",myrpt->totimer);
+ ast_log(LOG_NOTICE,"myrpt->tailtimer = %d\n",myrpt->tailtimer);
+ ast_log(LOG_NOTICE,"myrpt->tailevent = %d\n",myrpt->tailevent);
+
+ zl = myrpt->links.next;
+ while(zl != &myrpt->links){
+ ast_log(LOG_NOTICE,"*** Link Name: %s ***\n",zl->name);
+ ast_log(LOG_NOTICE," link->lasttx %d\n",zl->lasttx);
+ ast_log(LOG_NOTICE," link->lastrx %d\n",zl->lastrx);
+ ast_log(LOG_NOTICE," link->connected %d\n",zl->connected);
+ ast_log(LOG_NOTICE," link->hasconnected %d\n",zl->hasconnected);
+ ast_log(LOG_NOTICE," link->outbound %d\n",zl->outbound);
+ ast_log(LOG_NOTICE," link->disced %d\n",zl->disced);
+ ast_log(LOG_NOTICE," link->killme %d\n",zl->killme);
+ ast_log(LOG_NOTICE," link->disctime %ld\n",zl->disctime);
+ ast_log(LOG_NOTICE," link->retrytimer %ld\n",zl->retrytimer);
+ ast_log(LOG_NOTICE," link->retries = %d\n",zl->retries);
+ ast_log(LOG_NOTICE," link->reconnects = %d\n",zl->reconnects);
+ zl = zl->next;
+ }
+
+ zt = myrpt->tele.next;
+ if(zt != &myrpt->tele)
+ ast_log(LOG_NOTICE,"*** Telemetry Queue ***\n");
+ while(zt != &myrpt->tele){
+ ast_log(LOG_NOTICE," Telemetry mode: %d\n",zt->mode);
+ zt = zt->next;
+ }
+ ast_log(LOG_NOTICE,"******* Variable Dump End (app_rpt) *******\n");
+
+ }
+
+
+ if (myrpt->reload)
+ {
+ struct rpt_tele *telem;
+
+ rpt_mutex_lock(&myrpt->lock);
+ telem = myrpt->tele.next;
+ while(telem != &myrpt->tele)
+ {
+ ast_softhangup(telem->chan,AST_SOFTHANGUP_DEV);
+ telem = telem->next;
+ }
+ myrpt->reload = 0;
+ rpt_mutex_unlock(&myrpt->lock);
+ usleep(10000);
+ /* find our index, and load the vars */
+ for(i = 0; i < nrpts; i++)
+ {
+ if (&rpt_vars[i] == myrpt)
+ {
+ load_rpt_vars(i,0);
+ break;
+ }
+ }
+ }
+
+ rpt_mutex_lock(&myrpt->lock);
+ if (ast_check_hangup(myrpt->rxchannel)) break;
+ if (ast_check_hangup(myrpt->txchannel)) break;
+ if (ast_check_hangup(myrpt->pchannel)) break;
+ if (ast_check_hangup(myrpt->monchannel)) break;
+ if (ast_check_hangup(myrpt->txpchannel)) break;
+ if (myrpt->zaptxchannel && ast_check_hangup(myrpt->zaptxchannel)) break;
+
+ /* Set local tx with keyed */
+ myrpt->localtx = myrpt->keyed;
+ /* If someone's connected, and they're transmitting from their end to us, set remrx true */
+ l = myrpt->links.next;
+ remrx = 0;
+ while(l != &myrpt->links)
+ {
+ if (l->lastrx){
+ remrx = 1;
+ if(l->name[0] != '0') /* Ignore '0' nodes */
+ strcpy(myrpt->lastnodewhichkeyedusup, l->name); /* Note the node which is doing the key up */
+ }
+ l = l->next;
+ }
+ /* Create a "must_id" flag for the cleanup ID */
+ if(myrpt->p.idtime) /* ID time must be non-zero */
+ myrpt->mustid |= (myrpt->idtimer) && (myrpt->keyed || remrx) ;
+ /* Build a fresh totx from myrpt->keyed and autopatch activated */
+ totx = myrpt->callmode;
+ /* If full duplex, add local tx to totx */
+ if (myrpt->p.duplex > 1)
+ {
+ totx = totx || myrpt->localtx;
+ }
+ /* Traverse the telemetry list to see what's queued */
+ identqueued = 0;
+ othertelemqueued = 0;
+ tailmessagequeued = 0;
+ ctqueued = 0;
+ telem = myrpt->tele.next;
+ while(telem != &myrpt->tele)
+ {
+ if((telem->mode == ID) || (telem->mode == IDTALKOVER)){
+ identqueued = 1; /* Identification telemetry */
+ }
+ else if(telem->mode == TAILMSG)
+ {
+ tailmessagequeued = 1; /* Tail message telemetry */
+ }
+ else
+ {
+ if ((telem->mode != UNKEY) && (telem->mode != LINKUNKEY))
+ othertelemqueued = 1; /* Other telemetry */
+ else
+ ctqueued = 1; /* Courtesy tone telemetry */
+ }
+ telem = telem->next;
+ }
+
+ /* Add in any "other" telemetry, unless specified otherwise */
+ if (!myrpt->p.notelemtx) totx = totx || othertelemqueued;
+ /* Update external (to links) transmitter PTT state with everything but ID, CT, and tailmessage telemetry */
+ myrpt->exttx = totx;
+ totx = totx || myrpt->dtmf_local_timer;
+ /* If half or 3/4 duplex, add localtx to external link tx */
+ if (myrpt->p.duplex < 2) myrpt->exttx = myrpt->exttx || myrpt->localtx;
+ /* Add in ID telemetry to local transmitter */
+ totx = totx || remrx;
+ /* If 3/4 or full duplex, add in ident and CT telemetry */
+ if (myrpt->p.duplex > 0)
+ totx = totx || identqueued || ctqueued;
+ /* If full duplex, add local dtmf stuff active */
+ if (myrpt->p.duplex > 1)
+ {
+ totx = totx || (myrpt->dtmfidx > -1) ||
+ myrpt->cmdnode[0];
+ }
+ /* Reset time out timer variables if there is no activity */
+ if (!totx)
+ {
+ myrpt->totimer = myrpt->p.totime;
+ myrpt->tounkeyed = 0;
+ myrpt->tonotify = 0;
+ }
+ else{
+ myrpt->tailtimer = myrpt->p.s[myrpt->p.sysstate_cur].alternatetail ?
+ myrpt->p.althangtime : /* Initialize tail timer */
+ myrpt->p.hangtime;
+ }
+ /* Disable the local transmitter if we are timed out */
+ totx = totx && myrpt->totimer;
+ /* if timed-out and not said already, say it */
+ if ((!myrpt->totimer) && (!myrpt->tonotify))
+ {
+ myrpt->tonotify = 1;
+ myrpt->timeouts++;
+ rpt_mutex_unlock(&myrpt->lock);
+ rpt_telemetry(myrpt,TIMEOUT,NULL);
+ rpt_mutex_lock(&myrpt->lock);
+ }
+
+ /* If unkey and re-key, reset time out timer */
+ if ((!totx) && (!myrpt->totimer) && (!myrpt->tounkeyed) && (!myrpt->keyed))
+ {
+ myrpt->tounkeyed = 1;
+ }
+ if ((!totx) && (!myrpt->totimer) && myrpt->tounkeyed && myrpt->keyed)
+ {
+ myrpt->totimer = myrpt->p.totime;
+ myrpt->tounkeyed = 0;
+ myrpt->tonotify = 0;
+ rpt_mutex_unlock(&myrpt->lock);
+ continue;
+ }
+ /* if timed-out and in circuit busy after call */
+ if ((!totx) && (!myrpt->totimer) && (myrpt->callmode == 4))
+ {
+ myrpt->callmode = 0;
+ }
+ /* get rid of tail if timed out */
+ if (!myrpt->totimer) myrpt->tailtimer = 0;
+ /* if not timed-out, add in tail */
+ if (myrpt->totimer) totx = totx || myrpt->tailtimer;
+ /* If user or links key up or are keyed up over standard ID, switch to talkover ID, if one is defined */
+ /* If tail message, kill the message if someone keys up over it */
+ if ((myrpt->keyed || remrx) && ((identqueued && idtalkover) || (tailmessagequeued))) {
+ int hasid = 0,hastalkover = 0;
+
+ telem = myrpt->tele.next;
+ while(telem != &myrpt->tele){
+ if(telem->mode == ID){
+ if (telem->chan) ast_softhangup(telem->chan, AST_SOFTHANGUP_DEV); /* Whoosh! */
+ hasid = 1;
+ }
+ if(telem->mode == TAILMSG){
+ if (telem->chan) ast_softhangup(telem->chan, AST_SOFTHANGUP_DEV); /* Whoosh! */
+ }
+ if (telem->mode == IDTALKOVER) hastalkover = 1;
+ telem = telem->next;
+ }
+ rpt_mutex_unlock(&myrpt->lock);
+ if (hasid && (!hastalkover)) rpt_telemetry(myrpt, IDTALKOVER, NULL); /* Start Talkover ID */
+ rpt_mutex_lock(&myrpt->lock);
+ }
+ /* Try to be polite */
+ /* If the repeater has been inactive for longer than the ID time, do an initial ID in the tail*/
+ /* If within 30 seconds of the time to ID, try do it in the tail */
+ /* else if at ID time limit, do it right over the top of them */
+ /* Lastly, if the repeater has been keyed, and the ID timer is expired, do a clean up ID */
+ if(myrpt->mustid && (!myrpt->idtimer))
+ queue_id(myrpt);
+
+ if ((myrpt->p.idtime && totx && (!myrpt->exttx) &&
+ (myrpt->idtimer <= myrpt->p.politeid) && myrpt->tailtimer)) /* ID time must be non-zero */
+ {
+ myrpt->tailid = 1;
+ }
+
+ /* If tail timer expires, then check for tail messages */
+
+ if(myrpt->tailevent){
+ myrpt->tailevent = 0;
+ if(myrpt->tailid){
+ totx = 1;
+ queue_id(myrpt);
+ }
+ else if ((myrpt->p.tailmessages[0]) &&
+ (myrpt->p.tailmessagetime) && (myrpt->tmsgtimer == 0)){
+ totx = 1;
+ myrpt->tmsgtimer = myrpt->p.tailmessagetime;
+ rpt_mutex_unlock(&myrpt->lock);
+ rpt_telemetry(myrpt, TAILMSG, NULL);
+ rpt_mutex_lock(&myrpt->lock);
+ }
+ }
+
+ /* Main TX control */
+
+ /* let telemetry transmit anyway (regardless of timeout) */
+ if (myrpt->p.duplex > 0) totx = totx || (myrpt->tele.next != &myrpt->tele);
+ if (totx && (!lasttx))
+ {
+ char mydate[100],myfname[100];
+ time_t myt;
+
+ if (myrpt->monstream) ast_closestream(myrpt->monstream);
+ if (myrpt->p.archivedir)
+ {
+ long blocksleft;
+
+ time(&myt);
+ strftime(mydate,sizeof(mydate) - 1,"%Y%m%d%H%M%S",
+ localtime(&myt));
+ sprintf(myfname,"%s/%s/%s",myrpt->p.archivedir,
+ myrpt->name,mydate);
+ myrpt->monstream = ast_writefile(myfname,"wav49",
+ "app_rpt Air Archive",O_CREAT | O_APPEND,0,0600);
+ if (myrpt->p.monminblocks)
+ {
+ blocksleft = diskavail(myrpt);
+ if (blocksleft >= myrpt->p.monminblocks)
+ donodelog(myrpt,"TXKEY,MAIN");
+ } else donodelog(myrpt,"TXKEY,MAIN");
+ }
+ lasttx = 1;
+ myrpt->dailykeyups++;
+ myrpt->totalkeyups++;
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_indicate(myrpt->txchannel,AST_CONTROL_RADIO_KEY);
+ rpt_mutex_lock(&myrpt->lock);
+ }
+ totx = totx && !myrpt->p.s[myrpt->p.sysstate_cur].txdisable;
+ if ((!totx) && lasttx)
+ {
+ if (myrpt->monstream) ast_closestream(myrpt->monstream);
+ myrpt->monstream = NULL;
+
+ lasttx = 0;
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_indicate(myrpt->txchannel,AST_CONTROL_RADIO_UNKEY);
+ rpt_mutex_lock(&myrpt->lock);
+ donodelog(myrpt,"TXUNKEY,MAIN");
+ }
+ time(&t);
+ /* if DTMF timeout */
+ if ((!myrpt->cmdnode[0]) && (myrpt->dtmfidx >= 0) && ((myrpt->dtmf_time + DTMF_TIMEOUT) < t))
+ {
+ myrpt->dtmfidx = -1;
+ myrpt->dtmfbuf[0] = 0;
+ }
+ /* if remote DTMF timeout */
+ if ((myrpt->rem_dtmfidx >= 0) && ((myrpt->rem_dtmf_time + DTMF_TIMEOUT) < t))
+ {
+ myrpt->rem_dtmfidx = -1;
+ myrpt->rem_dtmfbuf[0] = 0;
+ }
+
+ /* Reconnect */
+
+ l = myrpt->links.next;
+ while(l != &myrpt->links)
+ {
+ if (l->killme)
+ {
+ /* remove from queue */
+ remque((struct qelem *) l);
+ if (!strcmp(myrpt->cmdnode,l->name))
+ myrpt->cmdnode[0] = 0;
+ rpt_mutex_unlock(&myrpt->lock);
+ /* hang-up on call to device */
+ if (l->chan) ast_hangup(l->chan);
+ ast_hangup(l->pchan);
+ free(l);
+ rpt_mutex_lock(&myrpt->lock);
+ /* re-start link traversal */
+ l = myrpt->links.next;
+ continue;
+ }
+ l = l->next;
+ }
+ n = 0;
+ cs[n++] = myrpt->rxchannel;
+ cs[n++] = myrpt->pchannel;
+ cs[n++] = myrpt->monchannel;
+ cs[n++] = myrpt->txpchannel;
+ if (myrpt->txchannel != myrpt->rxchannel) cs[n++] = myrpt->txchannel;
+ if (myrpt->zaptxchannel != myrpt->txchannel)
+ cs[n++] = myrpt->zaptxchannel;
+ l = myrpt->links.next;
+ while(l != &myrpt->links)
+ {
+ if ((!l->killme) && (!l->disctime) && l->chan)
+ {
+ cs[n++] = l->chan;
+ cs[n++] = l->pchan;
+ }
+ l = l->next;
+ }
+ rpt_mutex_unlock(&myrpt->lock);
+ ms = MSWAIT;
+ for(x = 0; x < n; x++)
+ {
+ int s = -(-x - myrpt->scram - 1) % n;
+ cs1[x] = cs[s];
+ }
+ myrpt->scram++;
+ who = ast_waitfor_n(cs1,n,&ms);
+ if (who == NULL) ms = 0;
+ elap = MSWAIT - ms;
+ rpt_mutex_lock(&myrpt->lock);
+ l = myrpt->links.next;
+ while(l != &myrpt->links)
+ {
+ if (l->linklisttimer)
+ {
+ l->linklisttimer -= elap;
+ if (l->linklisttimer < 0) l->linklisttimer = 0;
+ }
+ if ((!l->linklisttimer) && (l->name[0] != '0') && (!l->isremote))
+ {
+ struct ast_frame lf;
+
+ memset(&lf,0,sizeof(lf));
+ lf.frametype = AST_FRAME_TEXT;
+ lf.subclass = 0;
+ lf.offset = 0;
+ lf.mallocd = 0;
+ lf.samples = 0;
+ l->linklisttimer = LINKLISTTIME;
+ strcpy(lstr,"L ");
+ __mklinklist(myrpt,l,lstr + 2);
+ if (l->chan)
+ {
+ lf.datalen = strlen(lstr) + 1;
+ lf.data = lstr;
+ ast_write(l->chan,&lf);
+ if (debug > 6) ast_log(LOG_NOTICE,
+ "@@@@ node %s sent node string %s to node %s\n",
+ myrpt->name,lstr,l->name);
+ }
+ }
+#ifndef OLDKEY
+ if ((l->retxtimer += elap) >= REDUNDANT_TX_TIME)
+ {
+ l->retxtimer = 0;
+ if (l->chan && l->phonemode == 0)
+ {
+ if (l->lasttx)
+ ast_indicate(l->chan,AST_CONTROL_RADIO_KEY);
+ else
+ ast_indicate(l->chan,AST_CONTROL_RADIO_UNKEY);
+ }
+ }
+ if ((l->rerxtimer += elap) >= (REDUNDANT_TX_TIME * 5))
+ {
+ if (debug == 7) printf("@@@@ rx un-key\n");
+ l->lastrx = 0;
+ l->rerxtimer = 0;
+ if(myrpt->p.duplex)
+ rpt_telemetry(myrpt,LINKUNKEY,l);
+ if (myrpt->p.archivedir)
+ {
+ char str[100];
+
+ l->lastrx1 = 0;
+ sprintf(str,"RXUNKEY(T),%s",l->name);
+ donodelog(myrpt,str);
+ }
+ }
+#endif
+ if (l->disctime) /* Disconnect timer active on a channel ? */
+ {
+ l->disctime -= elap;
+ if (l->disctime <= 0) /* Disconnect timer expired on inbound channel ? */
+ l->disctime = 0; /* Yep */
+ }
+
+ if (l->retrytimer)
+ {
+ l->retrytimer -= elap;
+ if (l->retrytimer < 0) l->retrytimer = 0;
+ }
+
+ /* Tally connect time */
+ l->connecttime += elap;
+
+ /* ignore non-timing channels */
+ if (l->elaptime < 0)
+ {
+ l = l->next;
+ continue;
+ }
+ l->elaptime += elap;
+ /* if connection has taken too long */
+ if ((l->elaptime > MAXCONNECTTIME) &&
+ ((!l->chan) || (l->chan->_state != AST_STATE_UP)))
+ {
+ l->elaptime = 0;
+ rpt_mutex_unlock(&myrpt->lock);
+ if (l->chan) ast_softhangup(l->chan,AST_SOFTHANGUP_DEV);
+ rpt_mutex_lock(&myrpt->lock);
+ break;
+ }
+ if ((!l->chan) && (!l->retrytimer) && l->outbound &&
+ (l->retries++ < l->max_retries) && (l->hasconnected))
+ {
+ if (l->chan) ast_hangup(l->chan);
+ l->chan = 0;
+ rpt_mutex_unlock(&myrpt->lock);
+ if ((l->name[0] != '0') && (!l->isremote))
+ {
+ if (attempt_reconnect(myrpt,l) == -1)
+ {
+ l->retrytimer = RETRY_TIMER_MS;
+ }
+ }
+ else
+ {
+ l->retrytimer = l->max_retries + 1;
+ }
+
+ rpt_mutex_lock(&myrpt->lock);
+ break;
+ }
+ if ((!l->chan) && (!l->retrytimer) && l->outbound &&
+ (l->retries >= l->max_retries))
+ {
+ /* remove from queue */
+ remque((struct qelem *) l);
+ if (!strcmp(myrpt->cmdnode,l->name))
+ myrpt->cmdnode[0] = 0;
+ rpt_mutex_unlock(&myrpt->lock);
+ if (l->name[0] != '0')
+ {
+ if (!l->hasconnected)
+ rpt_telemetry(myrpt,CONNFAIL,l);
+ else rpt_telemetry(myrpt,REMDISC,l);
+ }
+ if (myrpt->p.archivedir)
+ {
+ char str[100];
+
+ if (!l->hasconnected)
+ sprintf(str,"LINKFAIL,%s",l->name);
+ else
+ sprintf(str,"LINKDISC,%s",l->name);
+ donodelog(myrpt,str);
+ }
+ /* hang-up on call to device */
+ ast_hangup(l->pchan);
+ free(l);
+ rpt_mutex_lock(&myrpt->lock);
+ break;
+ }
+ if ((!l->chan) && (!l->disctime) && (!l->outbound))
+ {
+ /* remove from queue */
+ remque((struct qelem *) l);
+ if (!strcmp(myrpt->cmdnode,l->name))
+ myrpt->cmdnode[0] = 0;
+ rpt_mutex_unlock(&myrpt->lock);
+ if (l->name[0] != '0')
+ {
+ rpt_telemetry(myrpt,REMDISC,l);
+ }
+ if (myrpt->p.archivedir)
+ {
+ char str[100];
+
+ sprintf(str,"LINKDISC,%s",l->name);
+ donodelog(myrpt,str);
+ }
+ /* hang-up on call to device */
+ ast_hangup(l->pchan);
+ free(l);
+ rpt_mutex_lock(&myrpt->lock);
+ break;
+ }
+ l = l->next;
+ }
+ if(totx){
+ myrpt->dailytxtime += elap;
+ myrpt->totaltxtime += elap;
+ }
+ i = myrpt->tailtimer;
+ if (myrpt->tailtimer) myrpt->tailtimer -= elap;
+ if (myrpt->tailtimer < 0) myrpt->tailtimer = 0;
+ if((i) && (myrpt->tailtimer == 0))
+ myrpt->tailevent = 1;
+ if ((!myrpt->p.s[myrpt->p.sysstate_cur].totdisable) && myrpt->totimer) myrpt->totimer -= elap;
+ if (myrpt->totimer < 0) myrpt->totimer = 0;
+ if (myrpt->idtimer) myrpt->idtimer -= elap;
+ if (myrpt->idtimer < 0) myrpt->idtimer = 0;
+ if (myrpt->tmsgtimer) myrpt->tmsgtimer -= elap;
+ if (myrpt->tmsgtimer < 0) myrpt->tmsgtimer = 0;
+ /* do macro timers */
+ if (myrpt->macrotimer) myrpt->macrotimer -= elap;
+ if (myrpt->macrotimer < 0) myrpt->macrotimer = 0;
+ /* do local dtmf timer */
+ if (myrpt->dtmf_local_timer)
+ {
+ if (myrpt->dtmf_local_timer > 1) myrpt->dtmf_local_timer -= elap;
+ if (myrpt->dtmf_local_timer < 1) myrpt->dtmf_local_timer = 1;
+ }
+ do_dtmf_local(myrpt,0);
+ /* Execute scheduler appx. every 2 tenths of a second */
+ if (myrpt->skedtimer <= 0){
+ myrpt->skedtimer = 200;
+ do_scheduler(myrpt);
+ }
+ else
+ myrpt->skedtimer -=elap;
+ if (!ms)
+ {
+ rpt_mutex_unlock(&myrpt->lock);
+ continue;
+ }
+ c = myrpt->macrobuf[0];
+ time(&t);
+ if (c && (!myrpt->macrotimer) &&
+ starttime && (t > (starttime + START_DELAY)))
+ {
+ myrpt->macrotimer = MACROTIME;
+ memmove(myrpt->macrobuf,myrpt->macrobuf + 1,MAXMACRO - 1);
+ if ((c == 'p') || (c == 'P'))
+ myrpt->macrotimer = MACROPTIME;
+ rpt_mutex_unlock(&myrpt->lock);
+ if (myrpt->p.archivedir)
+ {
+ char str[100];
+
+ sprintf(str,"DTMF(M),MAIN,%c",c);
+ donodelog(myrpt,str);
+ }
+ local_dtmf_helper(myrpt,c);
+ } else rpt_mutex_unlock(&myrpt->lock);
+ if (who == myrpt->rxchannel) /* if it was a read from rx */
+ {
+ int ismuted;
+
+ f = ast_read(myrpt->rxchannel);
+ if (!f)
+ {
+ if (debug) printf("@@@@ rpt:Hung Up\n");
+ break;
+ }
+ if (f->frametype == AST_FRAME_VOICE)
+ {
+#ifdef _MDC_DECODE_H_
+ unsigned char ubuf[2560];
+ short *sp;
+ int n;
+#endif
+
+ if ((!myrpt->localtx) && (!myrpt->p.linktolink)) {
+ memset(f->data,0,f->datalen);
+ }
+
+#ifdef _MDC_DECODE_H_
+ sp = (short *) f->data;
+ /* convert block to unsigned char */
+ for(n = 0; n < f->datalen / 2; n++)
+ {
+ ubuf[n] = (*sp++ >> 8) + 128;
+ }
+ n = mdc_decoder_process_samples(myrpt->mdc,ubuf,f->datalen / 2);
+ if (n == 1)
+ {
+ unsigned char op,arg;
+ unsigned short unitID;
+
+ mdc_decoder_get_packet(myrpt->mdc,&op,&arg,&unitID);
+ if (debug > 2)
+ {
+ ast_log(LOG_NOTICE,"Got (single-length) packet:\n");
+ ast_log(LOG_NOTICE,"op: %02x, arg: %02x, UnitID: %04x\n",
+ op & 255,arg & 255,unitID);
+ }
+ if ((op == 1) && (arg == 0))
+ {
+ myrpt->lastunit = unitID;
+ mdc1200_notify(myrpt,NULL,myrpt->lastunit);
+ mdc1200_send(myrpt,myrpt->lastunit);
+ }
+ }
+ if ((debug > 2) && (i == 2))
+ {
+ unsigned char op,arg,ex1,ex2,ex3,ex4;
+ unsigned short unitID;
+
+ mdc_decoder_get_double_packet(myrpt->mdc,&op,&arg,&unitID,
+ &ex1,&ex2,&ex3,&ex4);
+ ast_log(LOG_NOTICE,"Got (double-length) packet:\n");
+ ast_log(LOG_NOTICE,"op: %02x, arg: %02x, UnitID: %04x\n",
+ op & 255,arg & 255,unitID);
+ ast_log(LOG_NOTICE,"ex1: %02x, ex2: %02x, ex3: %02x, ex4: %02x\n",
+ ex1 & 255, ex2 & 255, ex3 & 255, ex4 & 255);
+ }
+#endif
+#ifdef __RPT_NOTCH
+ /* apply inbound filters, if any */
+ rpt_filter(myrpt,f->data,f->datalen / 2);
+#endif
+ if (ioctl(myrpt->zaprxchannel->fds[0], DAHDI_GETCONFMUTE, &ismuted) == -1)
+ {
+ ismuted = 0;
+ }
+ if (dtmfed) ismuted = 1;
+ dtmfed = 0;
+ if (ismuted)
+ {
+ memset(f->data,0,f->datalen);
+ if (myrpt->lastf1)
+ memset(myrpt->lastf1->data,0,myrpt->lastf1->datalen);
+ if (myrpt->lastf2)
+ memset(myrpt->lastf2->data,0,myrpt->lastf2->datalen);
+ }
+ if (f) f2 = ast_frdup(f);
+ else f2 = NULL;
+ f1 = myrpt->lastf2;
+ myrpt->lastf2 = myrpt->lastf1;
+ myrpt->lastf1 = f2;
+ if (ismuted)
+ {
+ if (myrpt->lastf1)
+ memset(myrpt->lastf1->data,0,myrpt->lastf1->datalen);
+ if (myrpt->lastf2)
+ memset(myrpt->lastf2->data,0,myrpt->lastf2->datalen);
+ }
+ if (f1)
+ {
+ ast_write(myrpt->pchannel,f1);
+ ast_frfree(f1);
+ }
+ }
+#ifndef OLD_ASTERISK
+ else if (f->frametype == AST_FRAME_DTMF_BEGIN)
+ {
+ if (myrpt->lastf1)
+ memset(myrpt->lastf1->data,0,myrpt->lastf1->datalen);
+ if (myrpt->lastf2)
+ memset(myrpt->lastf2->data,0,myrpt->lastf2->datalen);
+ dtmfed = 1;
+ }
+#endif
+ else if (f->frametype == AST_FRAME_DTMF)
+ {
+ c = (char) f->subclass; /* get DTMF char */
+ ast_frfree(f);
+ if (myrpt->lastf1)
+ memset(myrpt->lastf1->data,0,myrpt->lastf1->datalen);
+ if (myrpt->lastf2)
+ memset(myrpt->lastf2->data,0,myrpt->lastf2->datalen);
+ dtmfed = 1;
+ if (!myrpt->keyed) continue;
+ c = func_xlat(myrpt,c,&myrpt->p.inxlat);
+ if (c) local_dtmf_helper(myrpt,c);
+ continue;
+ }
+ else if (f->frametype == AST_FRAME_CONTROL)
+ {
+ if (f->subclass == AST_CONTROL_HANGUP)
+ {
+ if (debug) printf("@@@@ rpt:Hung Up\n");
+ ast_frfree(f);
+ break;
+ }
+ /* if RX key */
+ if (f->subclass == AST_CONTROL_RADIO_KEY)
+ {
+ if ((!lasttx) || (myrpt->p.duplex > 1) || (myrpt->p.linktolink))
+ {
+ if (debug == 7) printf("@@@@ rx key\n");
+ myrpt->keyed = 1;
+ }
+ if (myrpt->p.archivedir)
+ {
+ donodelog(myrpt,"RXKEY,MAIN");
+ }
+ }
+ /* if RX un-key */
+ if (f->subclass == AST_CONTROL_RADIO_UNKEY)
+ {
+ if ((!lasttx) || (myrpt->p.duplex > 1) || (myrpt->p.linktolink))
+ {
+ if (debug == 7) printf("@@@@ rx un-key\n");
+ if(myrpt->p.duplex && myrpt->keyed) {
+ rpt_telemetry(myrpt,UNKEY,NULL);
+ }
+ }
+ myrpt->keyed = 0;
+ if (myrpt->p.archivedir)
+ {
+ donodelog(myrpt,"RXUNKEY,MAIN");
+ }
+ }
+ }
+ ast_frfree(f);
+ continue;
+ }
+ if (who == myrpt->pchannel) /* if it was a read from pseudo */
+ {
+ f = ast_read(myrpt->pchannel);
+ if (!f)
+ {
+ if (debug) printf("@@@@ rpt:Hung Up\n");
+ break;
+ }
+ if (f->frametype == AST_FRAME_VOICE)
+ {
+ ast_write(myrpt->txpchannel,f);
+ }
+ if (f->frametype == AST_FRAME_CONTROL)
+ {
+ if (f->subclass == AST_CONTROL_HANGUP)
+ {
+ if (debug) printf("@@@@ rpt:Hung Up\n");
+ ast_frfree(f);
+ break;
+ }
+ }
+ ast_frfree(f);
+ continue;
+ }
+ if (who == myrpt->txchannel) /* if it was a read from tx */
+ {
+ f = ast_read(myrpt->txchannel);
+ if (!f)
+ {
+ if (debug) printf("@@@@ rpt:Hung Up\n");
+ break;
+ }
+ if (f->frametype == AST_FRAME_CONTROL)
+ {
+ if (f->subclass == AST_CONTROL_HANGUP)
+ {
+ if (debug) printf("@@@@ rpt:Hung Up\n");
+ ast_frfree(f);
+ break;
+ }
+ }
+ ast_frfree(f);
+ continue;
+ }
+ if (who == myrpt->zaptxchannel) /* if it was a read from pseudo-tx */
+ {
+ f = ast_read(myrpt->zaptxchannel);
+ if (!f)
+ {
+ if (debug) printf("@@@@ rpt:Hung Up\n");
+ break;
+ }
+ if (f->frametype == AST_FRAME_VOICE)
+ {
+ ast_write(myrpt->txchannel,f);
+ }
+ if (f->frametype == AST_FRAME_CONTROL)
+ {
+ if (f->subclass == AST_CONTROL_HANGUP)
+ {
+ if (debug) printf("@@@@ rpt:Hung Up\n");
+ ast_frfree(f);
+ break;
+ }
+ }
+ ast_frfree(f);
+ continue;
+ }
+ toexit = 0;
+ rpt_mutex_lock(&myrpt->lock);
+ l = myrpt->links.next;
+ while(l != &myrpt->links)
+ {
+ if (l->disctime)
+ {
+ l = l->next;
+ continue;
+ }
+ if (who == l->chan) /* if it was a read from rx */
+ {
+ int remnomute;
+
+ remrx = 0;
+ /* see if any other links are receiving */
+ m = myrpt->links.next;
+ while(m != &myrpt->links)
+ {
+ /* if not us, count it */
+ if ((m != l) && (m->lastrx)) remrx = 1;
+ m = m->next;
+ }
+ rpt_mutex_unlock(&myrpt->lock);
+ remnomute = myrpt->localtx &&
+ (!(myrpt->cmdnode[0] ||
+ (myrpt->dtmfidx > -1)));
+ totx = (((l->isremote) ? (remnomute) :
+ myrpt->exttx) || remrx) && l->mode;
+ if (l->phonemode == 0 && l->chan && (l->lasttx != totx))
+ {
+ if (totx)
+ {
+ ast_indicate(l->chan,AST_CONTROL_RADIO_KEY);
+ }
+ else
+ {
+ ast_indicate(l->chan,AST_CONTROL_RADIO_UNKEY);
+ }
+ if (myrpt->p.archivedir)
+ {
+ char str[100];
+
+ if (totx)
+ sprintf(str,"TXKEY,%s",l->name);
+ else
+ sprintf(str,"TXUNKEY,%s",l->name);
+ donodelog(myrpt,str);
+ }
+ }
+ l->lasttx = totx;
+ f = ast_read(l->chan);
+ if (!f)
+ {
+ rpt_mutex_lock(&myrpt->lock);
+ __kickshort(myrpt);
+ rpt_mutex_unlock(&myrpt->lock);
+ if ((!l->disced) && (!l->outbound))
+ {
+ if ((l->name[0] == '0') || l->isremote)
+ l->disctime = 1;
+ else
+ l->disctime = DISC_TIME;
+ rpt_mutex_lock(&myrpt->lock);
+ ast_hangup(l->chan);
+ l->chan = 0;
+ break;
+ }
+
+ if (l->retrytimer)
+ {
+ ast_hangup(l->chan);
+ l->chan = 0;
+ rpt_mutex_lock(&myrpt->lock);
+ break;
+ }
+ if (l->outbound && (l->retries++ < l->max_retries) && (l->hasconnected))
+ {
+ rpt_mutex_lock(&myrpt->lock);
+ if (l->chan) ast_hangup(l->chan);
+ l->chan = 0;
+ l->hasconnected = 1;
+ l->retrytimer = RETRY_TIMER_MS;
+ l->elaptime = 0;
+ l->connecttime = 0;
+ l->thisconnected = 0;
+ break;
+ }
+ rpt_mutex_lock(&myrpt->lock);
+ /* remove from queue */
+ remque((struct qelem *) l);
+ if (!strcmp(myrpt->cmdnode,l->name))
+ myrpt->cmdnode[0] = 0;
+ __kickshort(myrpt);
+ rpt_mutex_unlock(&myrpt->lock);
+ if (!l->hasconnected)
+ rpt_telemetry(myrpt,CONNFAIL,l);
+ else if (l->disced != 2) rpt_telemetry(myrpt,REMDISC,l);
+ if (myrpt->p.archivedir)
+ {
+ char str[100];
+
+ if (!l->hasconnected)
+ sprintf(str,"LINKFAIL,%s",l->name);
+ else
+ sprintf(str,"LINKDISC,%s",l->name);
+ donodelog(myrpt,str);
+ }
+ if (l->lastf1) ast_frfree(l->lastf1);
+ l->lastf1 = NULL;
+ if (l->lastf2) ast_frfree(l->lastf2);
+ l->lastf2 = NULL;
+ /* hang-up on call to device */
+ ast_hangup(l->chan);
+ ast_hangup(l->pchan);
+ free(l);
+ rpt_mutex_lock(&myrpt->lock);
+ break;
+ }
+ if (f->frametype == AST_FRAME_VOICE)
+ {
+ int ismuted;
+
+ if (l->phonemode)
+ {
+ if (ioctl(l->chan->fds[0], DAHDI_GETCONFMUTE, &ismuted) == -1)
+ {
+ ismuted = 0;
+ }
+ /* if not receiving, zero-out audio */
+ ismuted |= (!l->lastrx);
+ if (l->dtmfed && l->phonemode) ismuted = 1;
+ l->dtmfed = 0;
+ if (ismuted)
+ {
+ memset(f->data,0,f->datalen);
+ if (l->lastf1)
+ memset(l->lastf1->data,0,l->lastf1->datalen);
+ if (l->lastf2)
+ memset(l->lastf2->data,0,l->lastf2->datalen);
+ }
+ if (f) f2 = ast_frdup(f);
+ else f2 = NULL;
+ f1 = l->lastf2;
+ l->lastf2 = l->lastf1;
+ l->lastf1 = f2;
+ if (ismuted)
+ {
+ if (l->lastf1)
+ memset(l->lastf1->data,0,l->lastf1->datalen);
+ if (l->lastf2)
+ memset(l->lastf2->data,0,l->lastf2->datalen);
+ }
+ if (f1)
+ {
+ ast_write(l->pchan,f1);
+ ast_frfree(f1);
+ }
+ }
+ else
+ {
+ if (!l->lastrx)
+ memset(f->data,0,f->datalen);
+ ast_write(l->pchan,f);
+ }
+ }
+#ifndef OLD_ASTERISK
+ else if (f->frametype == AST_FRAME_DTMF_BEGIN)
+ {
+ if (l->lastf1)
+ memset(l->lastf1->data,0,l->lastf1->datalen);
+ if (l->lastf2)
+ memset(l->lastf2->data,0,l->lastf2->datalen);
+ l->dtmfed = 1;
+ }
+#endif
+
+ if (f->frametype == AST_FRAME_TEXT)
+ {
+ handle_link_data(myrpt,l,f->data);
+ }
+ if (f->frametype == AST_FRAME_DTMF)
+ {
+ if (l->lastf1)
+ memset(l->lastf1->data,0,l->lastf1->datalen);
+ if (l->lastf2)
+ memset(l->lastf2->data,0,l->lastf2->datalen);
+ l->dtmfed = 1;
+ handle_link_phone_dtmf(myrpt,l,f->subclass);
+ }
+ if (f->frametype == AST_FRAME_CONTROL)
+ {
+ if (f->subclass == AST_CONTROL_ANSWER)
+ {
+ char lconnected = l->connected;
+
+ __kickshort(myrpt);
+ l->connected = 1;
+ l->hasconnected = 1;
+ l->thisconnected = 1;
+ l->elaptime = -1;
+ if (!l->isremote) l->retries = 0;
+ if (!lconnected)
+ {
+ rpt_telemetry(myrpt,CONNECTED,l);
+ if (myrpt->p.archivedir)
+ {
+ char str[100];
+
+ if (l->mode)
+ sprintf(str,"LINKTRX,%s",l->name);
+ else
+ sprintf(str,"LINKMONITOR,%s",l->name);
+ donodelog(myrpt,str);
+ }
+ }
+ else
+ l->reconnects++;
+ }
+ /* if RX key */
+ if (f->subclass == AST_CONTROL_RADIO_KEY)
+ {
+ if (debug == 7 ) printf("@@@@ rx key\n");
+ l->lastrx = 1;
+ l->rerxtimer = 0;
+ if (myrpt->p.archivedir && (!l->lastrx1))
+ {
+ char str[100];
+
+ l->lastrx1 = 1;
+ sprintf(str,"RXKEY,%s",l->name);
+ donodelog(myrpt,str);
+ }
+ }
+ /* if RX un-key */
+ if (f->subclass == AST_CONTROL_RADIO_UNKEY)
+ {
+ if (debug == 7) printf("@@@@ rx un-key\n");
+ l->lastrx = 0;
+ l->rerxtimer = 0;
+ if(myrpt->p.duplex)
+ rpt_telemetry(myrpt,LINKUNKEY,l);
+ if (myrpt->p.archivedir && (l->lastrx1))
+ {
+ char str[100];
+
+ l->lastrx1 = 0;
+ sprintf(str,"RXUNKEY,%s",l->name);
+ donodelog(myrpt,str);
+ }
+ }
+ if (f->subclass == AST_CONTROL_HANGUP)
+ {
+ ast_frfree(f);
+ rpt_mutex_lock(&myrpt->lock);
+ __kickshort(myrpt);
+ rpt_mutex_unlock(&myrpt->lock);
+ if ((!l->outbound) && (!l->disced))
+ {
+ if ((l->name[0] == '0') || l->isremote)
+ l->disctime = 1;
+ else
+ l->disctime = DISC_TIME;
+ rpt_mutex_lock(&myrpt->lock);
+ ast_hangup(l->chan);
+ l->chan = 0;
+ break;
+ }
+ if (l->retrytimer)
+ {
+ if (l->chan) ast_hangup(l->chan);
+ l->chan = 0;
+ rpt_mutex_lock(&myrpt->lock);
+ break;
+ }
+ if (l->outbound && (l->retries++ < l->max_retries) && (l->hasconnected))
+ {
+ rpt_mutex_lock(&myrpt->lock);
+ if (l->chan) ast_hangup(l->chan);
+ l->chan = 0;
+ l->hasconnected = 1;
+ l->elaptime = 0;
+ l->retrytimer = RETRY_TIMER_MS;
+ l->connecttime = 0;
+ l->thisconnected = 0;
+ break;
+ }
+ rpt_mutex_lock(&myrpt->lock);
+ /* remove from queue */
+ remque((struct qelem *) l);
+ if (!strcmp(myrpt->cmdnode,l->name))
+ myrpt->cmdnode[0] = 0;
+ __kickshort(myrpt);
+ rpt_mutex_unlock(&myrpt->lock);
+ if (!l->hasconnected)
+ rpt_telemetry(myrpt,CONNFAIL,l);
+ else if (l->disced != 2) rpt_telemetry(myrpt,REMDISC,l);
+ if (myrpt->p.archivedir)
+ {
+ char str[100];
+
+ if (!l->hasconnected)
+ sprintf(str,"LINKFAIL,%s",l->name);
+ else
+ sprintf(str,"LINKDISC,%s",l->name);
+ donodelog(myrpt,str);
+ }
+ if (l->lastf1) ast_frfree(l->lastf1);
+ l->lastf1 = NULL;
+ if (l->lastf2) ast_frfree(l->lastf2);
+ l->lastf2 = NULL;
+ /* hang-up on call to device */
+ ast_hangup(l->chan);
+ ast_hangup(l->pchan);
+ free(l);
+ rpt_mutex_lock(&myrpt->lock);
+ break;
+ }
+ }
+ ast_frfree(f);
+ rpt_mutex_lock(&myrpt->lock);
+ break;
+ }
+ if (who == l->pchan)
+ {
+ rpt_mutex_unlock(&myrpt->lock);
+ f = ast_read(l->pchan);
+ if (!f)
+ {
+ if (debug) printf("@@@@ rpt:Hung Up\n");
+ toexit = 1;
+ rpt_mutex_lock(&myrpt->lock);
+ break;
+ }
+ if (f->frametype == AST_FRAME_VOICE)
+ {
+ if (l->chan) ast_write(l->chan,f);
+ }
+ if (f->frametype == AST_FRAME_CONTROL)
+ {
+ if (f->subclass == AST_CONTROL_HANGUP)
+ {
+ if (debug) printf("@@@@ rpt:Hung Up\n");
+ ast_frfree(f);
+ toexit = 1;
+ rpt_mutex_lock(&myrpt->lock);
+ break;
+ }
+ }
+ ast_frfree(f);
+ rpt_mutex_lock(&myrpt->lock);
+ break;
+ }
+ l = l->next;
+ }
+ rpt_mutex_unlock(&myrpt->lock);
+ if (toexit) break;
+ if (who == myrpt->monchannel)
+ {
+ f = ast_read(myrpt->monchannel);
+ if (!f)
+ {
+ if (debug) printf("@@@@ rpt:Hung Up\n");
+ break;
+ }
+ if (f->frametype == AST_FRAME_VOICE)
+ {
+ if (myrpt->monstream)
+ ast_writestream(myrpt->monstream,f);
+ }
+ if (f->frametype == AST_FRAME_CONTROL)
+ {
+ if (f->subclass == AST_CONTROL_HANGUP)
+ {
+ if (debug) printf("@@@@ rpt:Hung Up\n");
+ ast_frfree(f);
+ break;
+ }
+ }
+ ast_frfree(f);
+ continue;
+ }
+ if (who == myrpt->txpchannel) /* if it was a read from remote tx */
+ {
+ f = ast_read(myrpt->txpchannel);
+ if (!f)
+ {
+ if (debug) printf("@@@@ rpt:Hung Up\n");
+ break;
+ }
+ if (f->frametype == AST_FRAME_CONTROL)
+ {
+ if (f->subclass == AST_CONTROL_HANGUP)
+ {
+ if (debug) printf("@@@@ rpt:Hung Up\n");
+ ast_frfree(f);
+ break;
+ }
+ }
+ ast_frfree(f);
+ continue;
+ }
+ }
+ usleep(100000);
+ ast_hangup(myrpt->pchannel);
+ ast_hangup(myrpt->monchannel);
+ ast_hangup(myrpt->txpchannel);
+ if (myrpt->txchannel != myrpt->rxchannel) ast_hangup(myrpt->txchannel);
+ if (myrpt->zaptxchannel != myrpt->txchannel) ast_hangup(myrpt->zaptxchannel);
+ if (myrpt->lastf1) ast_frfree(myrpt->lastf1);
+ myrpt->lastf1 = NULL;
+ if (myrpt->lastf2) ast_frfree(myrpt->lastf2);
+ myrpt->lastf2 = NULL;
+ ast_hangup(myrpt->rxchannel);
+ rpt_mutex_lock(&myrpt->lock);
+ l = myrpt->links.next;
+ while(l != &myrpt->links)
+ {
+ struct rpt_link *ll = l;
+ /* remove from queue */
+ remque((struct qelem *) l);
+ /* hang-up on call to device */
+ if (l->chan) ast_hangup(l->chan);
+ ast_hangup(l->pchan);
+ l = l->next;
+ free(ll);
+ }
+ rpt_mutex_unlock(&myrpt->lock);
+ if (debug) printf("@@@@ rpt:Hung up channel\n");
+ myrpt->rpt_thread = AST_PTHREADT_STOP;
+ pthread_exit(NULL);
+ return NULL;
+}
+
+
+static void *rpt_master(void *ignore)
+{
+int i,n;
+pthread_attr_t attr;
+struct ast_config *cfg;
+char *this,*val;
+
+ /* init nodelog queue */
+ nodelog.next = nodelog.prev = &nodelog;
+ /* go thru all the specified repeaters */
+ this = NULL;
+ n = 0;
+ /* wait until asterisk starts */
+ while(!ast_test_flag(&ast_options,AST_OPT_FLAG_FULLY_BOOTED))
+ usleep(250000);
+ rpt_vars[n].cfg = ast_config_load("rpt.conf");
+ cfg = rpt_vars[n].cfg;
+ if (!cfg) {
+ ast_log(LOG_NOTICE, "Unable to open radio repeater configuration rpt.conf. Radio Repeater disabled.\n");
+ pthread_exit(NULL);
+ }
+ while((this = ast_category_browse(cfg,this)) != NULL)
+ {
+ for(i = 0 ; i < strlen(this) ; i++){
+ if((this[i] < '0') || (this[i] > '9'))
+ break;
+ }
+ if(i != strlen(this)) continue; /* Not a node defn */
+ memset(&rpt_vars[n],0,sizeof(rpt_vars[n]));
+ rpt_vars[n].name = strdup(this);
+ val = (char *) ast_variable_retrieve(cfg,this,"rxchannel");
+ if (val) rpt_vars[n].rxchanname = strdup(val);
+ val = (char *) ast_variable_retrieve(cfg,this,"txchannel");
+ if (val) rpt_vars[n].txchanname = strdup(val);
+ val = (char *) ast_variable_retrieve(cfg,this,"remote");
+ if (val) rpt_vars[n].remote = strdup(val);
+ ast_mutex_init(&rpt_vars[n].lock);
+ ast_mutex_init(&rpt_vars[n].remlock);
+ rpt_vars[n].tele.next = &rpt_vars[n].tele;
+ rpt_vars[n].tele.prev = &rpt_vars[n].tele;
+ rpt_vars[n].rpt_thread = AST_PTHREADT_NULL;
+ rpt_vars[n].tailmessagen = 0;
+#ifdef _MDC_DECODE_H_
+ rpt_vars[n].mdc = mdc_decoder_new(8000);
+#endif
+ n++;
+ }
+ nrpts = n;
+ ast_config_destroy(cfg);
+
+ /* start em all */
+ for(i = 0; i < n; i++)
+ {
+ load_rpt_vars(i,1);
+
+ /* if is a remote, dont start one for it */
+ if (rpt_vars[i].remote)
+ {
+ if(retreive_memory(&rpt_vars[i],"init")){ /* Try to retreive initial memory channel */
+ strncpy(rpt_vars[i].freq, "146.580", sizeof(rpt_vars[i].freq) - 1);
+ strncpy(rpt_vars[i].rxpl, "100.0", sizeof(rpt_vars[i].rxpl) - 1);
+
+ strncpy(rpt_vars[i].txpl, "100.0", sizeof(rpt_vars[i].txpl) - 1);
+ rpt_vars[i].remmode = REM_MODE_FM;
+ rpt_vars[i].offset = REM_SIMPLEX;
+ rpt_vars[i].powerlevel = REM_MEDPWR;
+ }
+ continue;
+ }
+ if (!rpt_vars[i].p.ident)
+ {
+ ast_log(LOG_WARNING,"Did not specify ident for node %s\n",rpt_vars[i].name);
+ ast_config_destroy(cfg);
+ pthread_exit(NULL);
+ }
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ ast_pthread_create(&rpt_vars[i].rpt_thread,&attr,rpt,(void *) &rpt_vars[i]);
+ }
+ usleep(500000);
+ time(&starttime);
+ for(;;)
+ {
+ /* Now monitor each thread, and restart it if necessary */
+ for(i = 0; i < n; i++)
+ {
+ int rv;
+ if (rpt_vars[i].remote) continue;
+ if (rpt_vars[i].rpt_thread == AST_PTHREADT_STOP)
+ rv = -1;
+ else
+ rv = pthread_kill(rpt_vars[i].rpt_thread,0);
+ if (rv)
+ {
+ if(time(NULL) - rpt_vars[i].lastthreadrestarttime <= 15)
+ {
+ if(rpt_vars[i].threadrestarts >= 5)
+ {
+ ast_log(LOG_ERROR,"Continual RPT thread restarts, killing Asterisk\n");
+ exit(1); /* Stuck in a restart loop, kill Asterisk and start over */
+ }
+ else
+ {
+ ast_log(LOG_NOTICE,"RPT thread restarted on %s\n",rpt_vars[i].name);
+ rpt_vars[i].threadrestarts++;
+ }
+ }
+ else
+ rpt_vars[i].threadrestarts = 0;
+
+ rpt_vars[i].lastthreadrestarttime = time(NULL);
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ ast_pthread_create(&rpt_vars[i].rpt_thread,&attr,rpt,(void *) &rpt_vars[i]);
+ ast_log(LOG_WARNING, "rpt_thread restarted on node %s\n", rpt_vars[i].name);
+ }
+
+ }
+ for(;;)
+ {
+ struct nodelog *nodep;
+ char *space,datestr[100],fname[300];
+ int fd;
+
+ ast_mutex_lock(&nodeloglock);
+ nodep = nodelog.next;
+ if(nodep == &nodelog) /* if nothing in queue */
+ {
+ ast_mutex_unlock(&nodeloglock);
+ break;
+ }
+ remque((struct qelem *)nodep);
+ ast_mutex_unlock(&nodeloglock);
+ space = strchr(nodep->str,' ');
+ if (!space)
+ {
+ free(nodep);
+ continue;
+ }
+ *space = 0;
+ strftime(datestr,sizeof(datestr) - 1,"%Y%m%d",
+ localtime(&nodep->timestamp));
+ sprintf(fname,"%s/%s/%s.txt",nodep->archivedir,
+ nodep->str,datestr);
+ fd = open(fname,O_WRONLY | O_CREAT | O_APPEND,0600);
+ if (fd == -1)
+ {
+ ast_log(LOG_ERROR,"Cannot open node log file %s for write",space + 1);
+ free(nodep);
+ continue;
+ }
+ if (write(fd,space + 1,strlen(space + 1)) !=
+ strlen(space + 1))
+ {
+ ast_log(LOG_ERROR,"Cannot write node log file %s for write",space + 1);
+ free(nodep);
+ continue;
+ }
+ close(fd);
+ free(nodep);
+ }
+ sleep(2);
+ }
+ ast_config_destroy(cfg);
+ pthread_exit(NULL);
+}
+
+static int rpt_exec(struct ast_channel *chan, void *data)
+{
+ int res=-1,i,rem_totx,rem_rx,remkeyed,n,phone_mode = 0;
+ int iskenwood_pci4,authtold,authreq,setting,notremming,reming;
+ int ismuted,dtmfed;
+#ifdef OLD_ASTERISK
+ struct localuser *u;
+#endif
+ char tmp[256], keyed = 0,keyed1 = 0;
+ char *options,*stringp,*tele,c;
+ struct rpt *myrpt;
+ struct ast_frame *f,*f1,*f2;
+ struct ast_channel *who;
+ struct ast_channel *cs[20];
+ struct rpt_link *l;
+ struct dahdi_confinfo ci; /* conference info */
+ struct dahdi_params par;
+ int ms,elap,nullfd;
+ time_t t,last_timeout_warning;
+ struct dahdi_radio_param z;
+ struct rpt_tele *telem;
+
+ nullfd = open("/dev/null",O_RDWR);
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "Rpt requires an argument (system node)\n");
+ return -1;
+ }
+
+ strncpy(tmp, (char *)data, sizeof(tmp)-1);
+ time(&t);
+ /* if time has externally shifted negative, screw it */
+ if (t < starttime) t = starttime + START_DELAY;
+ if ((!starttime) || (t < (starttime + START_DELAY)))
+ {
+ ast_log(LOG_NOTICE,"Node %s rejecting call: too soon!\n",tmp);
+ ast_safe_sleep(chan,3000);
+ return -1;
+ }
+ stringp=tmp;
+ strsep(&stringp, "|");
+ options = stringp;
+ myrpt = NULL;
+ /* see if we can find our specified one */
+ for(i = 0; i < nrpts; i++)
+ {
+ /* if name matches, assign it and exit loop */
+ if (!strcmp(tmp,rpt_vars[i].name))
+ {
+ myrpt = &rpt_vars[i];
+ break;
+ }
+ }
+ if (myrpt == NULL)
+ {
+ ast_log(LOG_WARNING, "Cannot find specified system node %s\n",tmp);
+ return -1;
+ }
+
+ if(myrpt->p.s[myrpt->p.sysstate_cur].txdisable){ /* Do not allow incoming connections if disabled */
+ ast_log(LOG_NOTICE, "Connect attempt to node %s with tx disabled", myrpt->name);
+ return -1;
+ }
+
+ /* if not phone access, must be an IAX connection */
+ if (options && ((*options == 'P') || (*options == 'D') || (*options == 'R')))
+ {
+ int val;
+
+ phone_mode = 1;
+ if (*options == 'D') phone_mode = 2;
+ ast_set_callerid(chan,"0","app_rpt user","0");
+ val = 1;
+ ast_channel_setoption(chan,AST_OPTION_TONE_VERIFY,&val,sizeof(char),0);
+ }
+ else
+ {
+#ifdef ALLOW_LOCAL_CHANNELS
+ /* Check to insure the connection is IAX2 or Local*/
+ if ( (strncmp(chan->name,"IAX2",4)) && (strncmp(chan->name,"Local",5)) ) {
+ ast_log(LOG_WARNING, "We only accept links via IAX2 or Local!!\n");
+ return -1;
+ }
+#else
+ if (strncmp(chan->name,"IAX2",4))
+ {
+ ast_log(LOG_WARNING, "We only accept links via IAX2!!\n");
+ return -1;
+ }
+#endif
+ }
+ if (options && (*options == 'R'))
+ {
+
+ /* Parts of this section taken from app_parkandannounce */
+ char *return_context;
+ int l, m, lot, timeout = 0;
+ char tmp[256],*template;
+ char *working, *context, *exten, *priority;
+ char *s,*orig_s;
+
+
+ rpt_mutex_lock(&myrpt->lock);
+ m = myrpt->callmode;
+ rpt_mutex_unlock(&myrpt->lock);
+
+ if ((!myrpt->p.nobusyout) && m)
+ {
+ if (chan->_state != AST_STATE_UP)
+ {
+ ast_indicate(chan,AST_CONTROL_BUSY);
+ }
+ while(ast_safe_sleep(chan,10000) != -1);
+ return -1;
+ }
+
+ if (chan->_state != AST_STATE_UP)
+ {
+ ast_answer(chan);
+ }
+
+ l=strlen(options)+2;
+ orig_s=malloc(l);
+ if(!orig_s) {
+ ast_log(LOG_WARNING, "Out of memory\n");
+ return -1;
+ }
+ s=orig_s;
+ strncpy(s,options,l);
+
+ template=strsep(&s,"|");
+ if(!template) {
+ ast_log(LOG_WARNING, "An announce template must be defined\n");
+ free(orig_s);
+ return -1;
+ }
+
+ if(s) {
+ timeout = atoi(strsep(&s, "|"));
+ timeout *= 1000;
+ }
+
+ return_context = s;
+
+ if(return_context != NULL) {
+ /* set the return context. Code borrowed from the Goto builtin */
+
+ working = return_context;
+ context = strsep(&working, "|");
+ exten = strsep(&working, "|");
+ if(!exten) {
+ /* Only a priority in this one */
+ priority = context;
+ exten = NULL;
+ context = NULL;
+ } else {
+ priority = strsep(&working, "|");
+ if(!priority) {
+ /* Only an extension and priority in this one */
+ priority = exten;
+ exten = context;
+ context = NULL;
+ }
+ }
+ if(atoi(priority) < 0) {
+ ast_log(LOG_WARNING, "Priority '%s' must be a number > 0\n", priority);
+ free(orig_s);
+ return -1;
+ }
+ /* At this point we have a priority and maybe an extension and a context */
+ chan->priority = atoi(priority);
+#ifdef OLD_ASTERISK
+ if(exten && strcasecmp(exten, "BYEXTENSION"))
+#else
+ if(exten)
+#endif
+ strncpy(chan->exten, exten, sizeof(chan->exten)-1);
+ if(context)
+ strncpy(chan->context, context, sizeof(chan->context)-1);
+ } else { /* increment the priority by default*/
+ chan->priority++;
+ }
+
+ if(option_verbose > 2) {
+ ast_verbose( VERBOSE_PREFIX_3 "Return Context: (%s,%s,%d) ID: %s\n", chan->context,chan->exten, chan->priority, chan->cid.cid_num);
+ if(!ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
+ ast_verbose( VERBOSE_PREFIX_3 "Warning: Return Context Invalid, call will return to default|s\n");
+ }
+ }
+
+ /* we are using masq_park here to protect * from touching the channel once we park it. If the channel comes out of timeout
+ before we are done announcing and the channel is messed with, Kablooeee. So we use Masq to prevent this. */
+
+ ast_masq_park_call(chan, NULL, timeout, &lot);
+
+ if (option_verbose > 2) ast_verbose( VERBOSE_PREFIX_3 "Call Parking Called, lot: %d, timeout: %d, context: %s\n", lot, timeout, return_context);
+
+ snprintf(tmp,sizeof(tmp) - 1,"%d,%s",lot,template + 1);
+
+ rpt_telemetry(myrpt,REV_PATCH,tmp);
+
+ free(orig_s);
+
+ return 0;
+
+ }
+
+ if (!options)
+ {
+ struct ast_hostent ahp;
+ struct hostent *hp;
+ struct in_addr ia;
+ char hisip[100],nodeip[100],*val, *s, *s1, *s2, *b,*b1;
+
+ /* look at callerid to see what node this comes from */
+ if (!chan->cid.cid_num) /* if doesn't have caller id */
+ {
+ ast_log(LOG_WARNING, "Doesnt have callerid on %s\n",tmp);
+ return -1;
+ }
+
+ /* get his IP from IAX2 module */
+ memset(hisip,0,sizeof(hisip));
+#ifdef ALLOW_LOCAL_CHANNELS
+ /* set IP address if this is a local connection*/
+ if (strncmp(chan->name,"Local",5)==0) {
+ strcpy(hisip,"127.0.0.1");
+ } else {
+ pbx_substitute_variables_helper(chan,"${IAXPEER(CURRENTCHANNEL)}",hisip,sizeof(hisip) - 1);
+ }
+#else
+ pbx_substitute_variables_helper(chan,"${IAXPEER(CURRENTCHANNEL)}",hisip,sizeof(hisip) - 1);
+#endif
+
+ if (!hisip[0])
+ {
+ ast_log(LOG_WARNING, "Link IP address cannot be determined!!\n");
+ return -1;
+ }
+
+ ast_callerid_parse(chan->cid.cid_num,&b,&b1);
+ ast_shrink_phone_number(b1);
+ if (!strcmp(myrpt->name,b1))
+ {
+ ast_log(LOG_WARNING, "Trying to link to self!!\n");
+ return -1;
+ }
+
+ if (*b1 < '1')
+ {
+ ast_log(LOG_WARNING, "Node %s Invalid for connection here!!\n",b1);
+ return -1;
+ }
+
+
+ /* look for his reported node string */
+ val = node_lookup(myrpt,b1);
+ if (!val)
+ {
+ ast_log(LOG_WARNING, "Reported node %s cannot be found!!\n",b1);
+ return -1;
+ }
+ strncpy(tmp,val,sizeof(tmp) - 1);
+ s = tmp;
+ s1 = strsep(&s,",");
+ s2 = strsep(&s,",");
+ if (!s2)
+ {
+ ast_log(LOG_WARNING, "Reported node %s not in correct format!!\n",b1);
+ return -1;
+ }
+ if (strcmp(s2,"NONE")) {
+ hp = ast_gethostbyname(s2, &ahp);
+ if (!hp)
+ {
+ ast_log(LOG_WARNING, "Reported node %s, name %s cannot be found!!\n",b1,s2);
+ return -1;
+ }
+ memcpy(&ia,hp->h_addr,sizeof(in_addr_t));
+#ifdef OLD_ASTERISK
+ ast_inet_ntoa(nodeip,sizeof(nodeip) - 1,ia);
+#else
+ strncpy(nodeip,ast_inet_ntoa(ia),sizeof(nodeip) - 1);
+#endif
+ if (strcmp(hisip,nodeip))
+ {
+ char *s3 = strchr(s1,'@');
+ if (s3) s1 = s3 + 1;
+ s3 = strchr(s1,'/');
+ if (s3) *s3 = 0;
+ hp = ast_gethostbyname(s1, &ahp);
+ if (!hp)
+ {
+ ast_log(LOG_WARNING, "Reported node %s, name %s cannot be found!!\n",b1,s1);
+ return -1;
+ }
+ memcpy(&ia,hp->h_addr,sizeof(in_addr_t));
+#ifdef OLD_ASTERISK
+ ast_inet_ntoa(nodeip,sizeof(nodeip) - 1,ia);
+#else
+ strncpy(nodeip,ast_inet_ntoa(ia),sizeof(nodeip) - 1);
+#endif
+ if (strcmp(hisip,nodeip))
+ {
+ ast_log(LOG_WARNING, "Node %s IP %s does not match link IP %s!!\n",b1,nodeip,hisip);
+ return -1;
+ }
+ }
+ }
+ }
+
+ /* if is not a remote */
+ if (!myrpt->remote)
+ {
+
+ char *b,*b1;
+ int reconnects = 0;
+
+ /* look at callerid to see what node this comes from */
+ if (!chan->cid.cid_num) /* if doesn't have caller id */
+ {
+ ast_log(LOG_WARNING, "Doesnt have callerid on %s\n",tmp);
+ return -1;
+ }
+
+ ast_callerid_parse(chan->cid.cid_num,&b,&b1);
+ ast_shrink_phone_number(b1);
+ if (!strcmp(myrpt->name,b1))
+ {
+ ast_log(LOG_WARNING, "Trying to link to self!!\n");
+ return -1;
+ }
+ rpt_mutex_lock(&myrpt->lock);
+ l = myrpt->links.next;
+ /* try to find this one in queue */
+ while(l != &myrpt->links)
+ {
+ if (l->name[0] == '0')
+ {
+ l = l->next;
+ continue;
+ }
+ /* if found matching string */
+ if (!strcmp(l->name,b1)) break;
+ l = l->next;
+ }
+ /* if found */
+ if (l != &myrpt->links)
+ {
+ l->killme = 1;
+ l->retries = l->max_retries + 1;
+ l->disced = 2;
+ reconnects = l->reconnects;
+ reconnects++;
+ rpt_mutex_unlock(&myrpt->lock);
+ usleep(500000);
+ } else
+ rpt_mutex_unlock(&myrpt->lock);
+ /* establish call in tranceive mode */
+ l = malloc(sizeof(struct rpt_link));
+ if (!l)
+ {
+ ast_log(LOG_WARNING, "Unable to malloc\n");
+ pthread_exit(NULL);
+ }
+ /* zero the silly thing */
+ memset((char *)l,0,sizeof(struct rpt_link));
+ l->mode = 1;
+ strncpy(l->name,b1,MAXNODESTR - 1);
+ l->isremote = 0;
+ l->chan = chan;
+ l->connected = 1;
+ l->thisconnected = 1;
+ l->hasconnected = 1;
+ l->reconnects = reconnects;
+ l->phonemode = phone_mode;
+ l->lastf1 = NULL;
+ l->lastf2 = NULL;
+ l->dtmfed = 0;
+ ast_set_read_format(l->chan,AST_FORMAT_SLINEAR);
+ ast_set_write_format(l->chan,AST_FORMAT_SLINEAR);
+ /* allocate a pseudo-channel thru asterisk */
+ l->pchan = ast_request("zap",AST_FORMAT_SLINEAR,"pseudo",NULL);
+ if (!l->pchan)
+ {
+ fprintf(stderr,"rpt:Sorry unable to obtain pseudo channel\n");
+ pthread_exit(NULL);
+ }
+ ast_set_read_format(l->pchan,AST_FORMAT_SLINEAR);
+ ast_set_write_format(l->pchan,AST_FORMAT_SLINEAR);
+#ifdef AST_CDR_FLAG_POST_DISABLED
+ ast_set_flag(l->pchan->cdr,AST_CDR_FLAG_POST_DISABLED);
+#endif
+ /* make a conference for the tx */
+ ci.chan = 0;
+ ci.confno = myrpt->conf;
+ ci.confmode = DAHDI_CONF_CONF | DAHDI_CONF_LISTENER | DAHDI_CONF_TALKER;
+ /* first put the channel on the conference in proper mode */
+ if (ioctl(l->pchan->fds[0],DAHDI_SETCONF,&ci) == -1)
+ {
+ ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n");
+ pthread_exit(NULL);
+ }
+ rpt_mutex_lock(&myrpt->lock);
+ if (phone_mode > 1) l->lastrx = 1;
+ l->max_retries = MAX_RETRIES;
+ /* insert at end of queue */
+ insque((struct qelem *)l,(struct qelem *)myrpt->links.next);
+ __kickshort(myrpt);
+ rpt_mutex_unlock(&myrpt->lock);
+ if (chan->_state != AST_STATE_UP) {
+ ast_answer(chan);
+ }
+ if (myrpt->p.archivedir)
+ {
+ char str[100];
+
+ if (l->phonemode)
+ sprintf(str,"LINK(P),%s",l->name);
+ else
+ sprintf(str,"LINK,%s",l->name);
+ donodelog(myrpt,str);
+ }
+ return AST_PBX_KEEPALIVE;
+ }
+ /* well, then it is a remote */
+ rpt_mutex_lock(&myrpt->lock);
+ /* if remote, error if anyone else already linked */
+ if (myrpt->remoteon)
+ {
+ rpt_mutex_unlock(&myrpt->lock);
+ usleep(500000);
+ if (myrpt->remoteon)
+ {
+ ast_log(LOG_WARNING, "Trying to use busy link on %s\n",tmp);
+ return -1;
+ }
+ rpt_mutex_lock(&myrpt->lock);
+ }
+#ifdef HAVE_IOPERM
+ if ((!strcmp(myrpt->remote, remote_rig_rbi)) &&
+ (ioperm(myrpt->p.iobase,1,1) == -1))
+ {
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_log(LOG_WARNING, "Cant get io permission on IO port %x hex\n",myrpt->p.iobase);
+ return -1;
+ }
+#endif
+ myrpt->remoteon = 1;
+#ifdef OLD_ASTERISK
+ LOCAL_USER_ADD(u);
+#endif
+ rpt_mutex_unlock(&myrpt->lock);
+ /* find our index, and load the vars initially */
+ for(i = 0; i < nrpts; i++)
+ {
+ if (&rpt_vars[i] == myrpt)
+ {
+ load_rpt_vars(i,0);
+ break;
+ }
+ }
+ rpt_mutex_lock(&myrpt->lock);
+ tele = strchr(myrpt->rxchanname,'/');
+ if (!tele)
+ {
+ fprintf(stderr,"rpt:Dial number must be in format tech/number\n");
+ rpt_mutex_unlock(&myrpt->lock);
+ pthread_exit(NULL);
+ }
+ *tele++ = 0;
+ myrpt->rxchannel = ast_request(myrpt->rxchanname,AST_FORMAT_SLINEAR,tele,NULL);
+ myrpt->zaprxchannel = NULL;
+ if (!strcasecmp(myrpt->rxchanname,"Zap"))
+ myrpt->zaprxchannel = myrpt->rxchannel;
+ if (myrpt->rxchannel)
+ {
+ ast_set_read_format(myrpt->rxchannel,AST_FORMAT_SLINEAR);
+ ast_set_write_format(myrpt->rxchannel,AST_FORMAT_SLINEAR);
+#ifdef AST_CDR_FLAG_POST_DISABLED
+ ast_set_flag(myrpt->rxchannel->cdr,AST_CDR_FLAG_POST_DISABLED);
+#endif
+ myrpt->rxchannel->whentohangup = 0;
+ myrpt->rxchannel->appl = "Apprpt";
+ myrpt->rxchannel->data = "(Link Rx)";
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "rpt (Rx) initiating call to %s/%s on %s\n",
+ myrpt->rxchanname,tele,myrpt->rxchannel->name);
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_call(myrpt->rxchannel,tele,999);
+ rpt_mutex_lock(&myrpt->lock);
+ }
+ else
+ {
+ fprintf(stderr,"rpt:Sorry unable to obtain Rx channel\n");
+ rpt_mutex_unlock(&myrpt->lock);
+ pthread_exit(NULL);
+ }
+ *--tele = '/';
+ myrpt->zaptxchannel = NULL;
+ if (myrpt->txchanname)
+ {
+ tele = strchr(myrpt->txchanname,'/');
+ if (!tele)
+ {
+ fprintf(stderr,"rpt:Dial number must be in format tech/number\n");
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_hangup(myrpt->rxchannel);
+ pthread_exit(NULL);
+ }
+ *tele++ = 0;
+ myrpt->txchannel = ast_request(myrpt->txchanname,AST_FORMAT_SLINEAR,tele,NULL);
+ if (!strcasecmp(myrpt->txchanname,"Zap"))
+ myrpt->zaptxchannel = myrpt->txchannel;
+ if (myrpt->txchannel)
+ {
+ ast_set_read_format(myrpt->txchannel,AST_FORMAT_SLINEAR);
+ ast_set_write_format(myrpt->txchannel,AST_FORMAT_SLINEAR);
+#ifdef AST_CDR_FLAG_POST_DISABLED
+ ast_set_flag(myrpt->txchannel->cdr,AST_CDR_FLAG_POST_DISABLED);
+#endif
+ myrpt->txchannel->whentohangup = 0;
+ myrpt->txchannel->appl = "Apprpt";
+ myrpt->txchannel->data = "(Link Tx)";
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "rpt (Tx) initiating call to %s/%s on %s\n",
+ myrpt->txchanname,tele,myrpt->txchannel->name);
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_call(myrpt->txchannel,tele,999);
+ rpt_mutex_lock(&myrpt->lock);
+ }
+ else
+ {
+ fprintf(stderr,"rpt:Sorry unable to obtain Tx channel\n");
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_hangup(myrpt->rxchannel);
+ pthread_exit(NULL);
+ }
+ *--tele = '/';
+ }
+ else
+ {
+ myrpt->txchannel = myrpt->rxchannel;
+ }
+ /* allocate a pseudo-channel thru asterisk */
+ myrpt->pchannel = ast_request("zap",AST_FORMAT_SLINEAR,"pseudo",NULL);
+ if (!myrpt->pchannel)
+ {
+ fprintf(stderr,"rpt:Sorry unable to obtain pseudo channel\n");
+ rpt_mutex_unlock(&myrpt->lock);
+ if (myrpt->txchannel != myrpt->rxchannel)
+ ast_hangup(myrpt->txchannel);
+ ast_hangup(myrpt->rxchannel);
+ pthread_exit(NULL);
+ }
+ ast_set_read_format(myrpt->pchannel,AST_FORMAT_SLINEAR);
+ ast_set_write_format(myrpt->pchannel,AST_FORMAT_SLINEAR);
+#ifdef AST_CDR_FLAG_POST_DISABLED
+ ast_set_flag(myrpt->pchannel->cdr,AST_CDR_FLAG_POST_DISABLED);
+#endif
+ if (!myrpt->zaprxchannel) myrpt->zaprxchannel = myrpt->pchannel;
+ if (!myrpt->zaptxchannel) myrpt->zaptxchannel = myrpt->pchannel;
+ /* make a conference for the pseudo */
+ ci.chan = 0;
+ ci.confno = -1; /* make a new conf */
+ ci.confmode = DAHDI_CONF_CONFANNMON ;
+ /* first put the channel on the conference in announce/monitor mode */
+ if (ioctl(myrpt->pchannel->fds[0],DAHDI_SETCONF,&ci) == -1)
+ {
+ ast_log(LOG_WARNING, "Unable to set conference mode to Announce\n");
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_hangup(myrpt->pchannel);
+ if (myrpt->txchannel != myrpt->rxchannel)
+ ast_hangup(myrpt->txchannel);
+ ast_hangup(myrpt->rxchannel);
+ pthread_exit(NULL);
+ }
+ /* save pseudo channel conference number */
+ myrpt->conf = myrpt->txconf = ci.confno;
+ /* if serial io port, open it */
+ myrpt->iofd = -1;
+ if (myrpt->p.ioport && ((myrpt->iofd = openserial(myrpt->p.ioport)) == -1))
+ {
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_hangup(myrpt->pchannel);
+ if (myrpt->txchannel != myrpt->rxchannel)
+ ast_hangup(myrpt->txchannel);
+ ast_hangup(myrpt->rxchannel);
+ pthread_exit(NULL);
+ }
+ iskenwood_pci4 = 0;
+ memset(&z,0,sizeof(z));
+ if ((myrpt->iofd < 1) && (myrpt->txchannel == myrpt->zaptxchannel))
+ {
+ z.radpar = DAHDI_RADPAR_REMMODE;
+ z.data = DAHDI_RADPAR_REM_NONE;
+ res = ioctl(myrpt->zaptxchannel->fds[0],DAHDI_RADIO_SETPARAM,&z);
+ /* if PCIRADIO and kenwood selected */
+ if ((!res) && (!strcmp(myrpt->remote,remote_rig_kenwood)))
+ {
+ z.radpar = DAHDI_RADPAR_UIOMODE;
+ z.data = 1;
+ if (ioctl(myrpt->zaptxchannel->fds[0],DAHDI_RADIO_SETPARAM,&z) == -1)
+ {
+ ast_log(LOG_ERROR,"Cannot set UIOMODE\n");
+ return -1;
+ }
+ z.radpar = DAHDI_RADPAR_UIODATA;
+ z.data = 3;
+ if (ioctl(myrpt->zaptxchannel->fds[0],DAHDI_RADIO_SETPARAM,&z) == -1)
+ {
+ ast_log(LOG_ERROR,"Cannot set UIODATA\n");
+ return -1;
+ }
+ i = DAHDI_OFFHOOK;
+ if (ioctl(myrpt->zaptxchannel->fds[0],DAHDI_HOOK,&i) == -1)
+ {
+ ast_log(LOG_ERROR,"Cannot set hook\n");
+ return -1;
+ }
+ iskenwood_pci4 = 1;
+ }
+ }
+ if (myrpt->txchannel == myrpt->zaptxchannel)
+ {
+ i = DAHDI_ONHOOK;
+ ioctl(myrpt->zaptxchannel->fds[0],DAHDI_HOOK,&i);
+ /* if PCIRADIO and Yaesu ft897/ICOM IC-706 selected */
+ if ((myrpt->iofd < 1) && (!res) &&
+ (!strcmp(myrpt->remote,remote_rig_ft897) ||
+ (!strcmp(myrpt->remote,remote_rig_ic706))))
+ {
+ z.radpar = DAHDI_RADPAR_UIOMODE;
+ z.data = 1;
+ if (ioctl(myrpt->zaptxchannel->fds[0],DAHDI_RADIO_SETPARAM,&z) == -1)
+ {
+ ast_log(LOG_ERROR,"Cannot set UIOMODE\n");
+ return -1;
+ }
+ z.radpar = DAHDI_RADPAR_UIODATA;
+ z.data = 3;
+ if (ioctl(myrpt->zaptxchannel->fds[0],DAHDI_RADIO_SETPARAM,&z) == -1)
+ {
+ ast_log(LOG_ERROR,"Cannot set UIODATA\n");
+ return -1;
+ }
+ }
+ }
+ myrpt->remoterx = 0;
+ myrpt->remotetx = 0;
+ myrpt->retxtimer = 0;
+ myrpt->rerxtimer = 0;
+ myrpt->remoteon = 1;
+ myrpt->dtmfidx = -1;
+ myrpt->dtmfbuf[0] = 0;
+ myrpt->dtmf_time_rem = 0;
+ myrpt->hfscanmode = 0;
+ myrpt->hfscanstatus = 0;
+ if (myrpt->p.startupmacro)
+ {
+ snprintf(myrpt->macrobuf,MAXMACRO - 1,"PPPP%s",myrpt->p.startupmacro);
+ }
+ time(&myrpt->start_time);
+ myrpt->last_activity_time = myrpt->start_time;
+ last_timeout_warning = 0;
+ myrpt->reload = 0;
+ myrpt->tele.next = &myrpt->tele;
+ myrpt->tele.prev = &myrpt->tele;
+ rpt_mutex_unlock(&myrpt->lock);
+ ast_set_write_format(chan, AST_FORMAT_SLINEAR);
+ ast_set_read_format(chan, AST_FORMAT_SLINEAR);
+ rem_rx = 0;
+ remkeyed = 0;
+ /* if we are on 2w loop and are a remote, turn EC on */
+ if (myrpt->remote && (myrpt->rxchannel == myrpt->txchannel))
+ {
+ i = 128;
+ ioctl(myrpt->zaprxchannel->fds[0],DAHDI_ECHOCANCEL,&i);
+ }
+ if (chan->_state != AST_STATE_UP) {
+ ast_answer(chan);
+ }
+
+ if (myrpt->rxchannel == myrpt->zaprxchannel)
+ {
+ if (ioctl(myrpt->zaprxchannel->fds[0],DAHDI_GET_PARAMS,&par) != -1)
+ {
+ if (par.rxisoffhook)
+ {
+ ast_indicate(chan,AST_CONTROL_RADIO_KEY);
+ myrpt->remoterx = 1;
+ remkeyed = 1;
+ }
+ }
+ }
+ if (myrpt->p.archivedir)
+ {
+ char mycmd[100],mydate[100],*b,*b1;
+ time_t myt;
+ long blocksleft;
+
+
+ mkdir(myrpt->p.archivedir,0600);
+ sprintf(mycmd,"%s/%s",myrpt->p.archivedir,myrpt->name);
+ mkdir(mycmd,0600);
+ time(&myt);
+ strftime(mydate,sizeof(mydate) - 1,"%Y%m%d%H%M%S",
+ localtime(&myt));
+ sprintf(mycmd,"mixmonitor start %s %s/%s/%s.wav49 a",chan->name,
+ myrpt->p.archivedir,myrpt->name,mydate);
+ if (myrpt->p.monminblocks)
+ {
+ blocksleft = diskavail(myrpt);
+ if (myrpt->p.remotetimeout)
+ {
+ blocksleft -= (myrpt->p.remotetimeout *
+ MONITOR_DISK_BLOCKS_PER_MINUTE) / 60;
+ }
+ if (blocksleft >= myrpt->p.monminblocks)
+ ast_cli_command(nullfd,mycmd);
+ } else ast_cli_command(nullfd,mycmd);
+ /* look at callerid to see what node this comes from */
+ if (!chan->cid.cid_num) /* if doesn't have caller id */
+ {
+ b1 = "0";
+ } else {
+ ast_callerid_parse(chan->cid.cid_num,&b,&b1);
+ ast_shrink_phone_number(b1);
+ }
+ sprintf(mycmd,"CONNECT,%s",b1);
+ donodelog(myrpt,mycmd);
+ }
+ myrpt->loginuser[0] = 0;
+ myrpt->loginlevel[0] = 0;
+ myrpt->authtelltimer = 0;
+ myrpt->authtimer = 0;
+ authtold = 0;
+ authreq = 0;
+ if (myrpt->p.authlevel > 1) authreq = 1;
+ setrem(myrpt);
+ n = 0;
+ dtmfed = 0;
+ cs[n++] = chan;
+ cs[n++] = myrpt->rxchannel;
+ cs[n++] = myrpt->pchannel;
+ if (myrpt->rxchannel != myrpt->txchannel)
+ cs[n++] = myrpt->txchannel;
+ /* start un-locked */
+ for(;;)
+ {
+ if (ast_check_hangup(chan)) break;
+ if (ast_check_hangup(myrpt->rxchannel)) break;
+ notremming = 0;
+ setting = 0;
+ reming = 0;
+ telem = myrpt->tele.next;
+ while(telem != &myrpt->tele)
+ {
+ if (telem->mode == SETREMOTE) setting = 1;
+ if ((telem->mode == SETREMOTE) ||
+ (telem->mode == SCAN) ||
+ (telem->mode == TUNE)) reming = 1;
+ else notremming = 1;
+ telem = telem->next;
+ }
+ if (myrpt->reload)
+ {
+ myrpt->reload = 0;
+ /* find our index, and load the vars */
+ for(i = 0; i < nrpts; i++)
+ {
+ if (&rpt_vars[i] == myrpt)
+ {
+ load_rpt_vars(i,0);
+ break;
+ }
+ }
+ }
+ time(&t);
+ if (myrpt->p.remotetimeout)
+ {
+ time_t r;
+
+ r = (t - myrpt->start_time);
+ if (r >= myrpt->p.remotetimeout)
+ {
+ sayfile(chan,"rpt/node");
+ ast_say_character_str(chan,myrpt->name,NULL,chan->language);
+ sayfile(chan,"rpt/timeout");
+ ast_safe_sleep(chan,1000);
+ break;
+ }
+ if ((myrpt->p.remotetimeoutwarning) &&
+ (r >= (myrpt->p.remotetimeout -
+ myrpt->p.remotetimeoutwarning)) &&
+ (r <= (myrpt->p.remotetimeout -
+ myrpt->p.remotetimeoutwarningfreq)))
+ {
+ if (myrpt->p.remotetimeoutwarningfreq)
+ {
+ if ((t - last_timeout_warning) >=
+ myrpt->p.remotetimeoutwarningfreq)
+ {
+ time(&last_timeout_warning);
+ rpt_telemetry(myrpt,TIMEOUT_WARNING,0);
+ }
+ }
+ else
+ {
+ if (!last_timeout_warning)
+ {
+ time(&last_timeout_warning);
+ rpt_telemetry(myrpt,TIMEOUT_WARNING,0);
+ }
+ }
+ }
+ }
+ if (myrpt->p.remoteinacttimeout && myrpt->last_activity_time)
+ {
+ time_t r;
+
+ r = (t - myrpt->last_activity_time);
+ if (r >= myrpt->p.remoteinacttimeout)
+ {
+ sayfile(chan,"rpt/node");
+ ast_say_character_str(chan,myrpt->name,NULL,chan->language);
+ sayfile(chan,"rpt/timeout");
+ ast_safe_sleep(chan,1000);
+ break;
+ }
+ if ((myrpt->p.remotetimeoutwarning) &&
+ (r >= (myrpt->p.remoteinacttimeout -
+ myrpt->p.remotetimeoutwarning)) &&
+ (r <= (myrpt->p.remoteinacttimeout -
+ myrpt->p.remotetimeoutwarningfreq)))
+ {
+ if (myrpt->p.remotetimeoutwarningfreq)
+ {
+ if ((t - last_timeout_warning) >=
+ myrpt->p.remotetimeoutwarningfreq)
+ {
+ time(&last_timeout_warning);
+ rpt_telemetry(myrpt,ACT_TIMEOUT_WARNING,0);
+ }
+ }
+ else
+ {
+ if (!last_timeout_warning)
+ {
+ time(&last_timeout_warning);
+ rpt_telemetry(myrpt,ACT_TIMEOUT_WARNING,0);
+ }
+ }
+ }
+ }
+ ms = MSWAIT;
+ who = ast_waitfor_n(cs,n,&ms);
+ if (who == NULL) ms = 0;
+ elap = MSWAIT - ms;
+ if (myrpt->macrotimer) myrpt->macrotimer -= elap;
+ if (myrpt->macrotimer < 0) myrpt->macrotimer = 0;
+ if (!ms) continue;
+ /* do local dtmf timer */
+ if (myrpt->dtmf_local_timer)
+ {
+ if (myrpt->dtmf_local_timer > 1) myrpt->dtmf_local_timer -= elap;
+ if (myrpt->dtmf_local_timer < 1) myrpt->dtmf_local_timer = 1;
+ }
+ rpt_mutex_lock(&myrpt->lock);
+ do_dtmf_local(myrpt,0);
+ rpt_mutex_unlock(&myrpt->lock);
+ rem_totx = myrpt->dtmf_local_timer && (!phone_mode);
+ rem_totx |= keyed && (!myrpt->tunerequest);
+ rem_rx = (remkeyed && (!setting)) || (myrpt->tele.next != &myrpt->tele);
+ if(!strcmp(myrpt->remote, remote_rig_ic706))
+ rem_totx |= myrpt->tunerequest;
+ if (keyed && (!keyed1))
+ {
+ keyed1 = 1;
+ }
+
+ if (!keyed && (keyed1))
+ {
+ time_t myt;
+
+ keyed1 = 0;
+ time(&myt);
+ /* if login necessary, and not too soon */
+ if ((myrpt->p.authlevel) &&
+ (!myrpt->loginlevel[0]) &&
+ (myt > (t + 3)))
+ {
+ authreq = 1;
+ authtold = 0;
+ myrpt->authtelltimer = AUTHTELLTIME - AUTHTXTIME;
+ }
+ }
+
+
+ if (rem_rx && (!myrpt->remoterx))
+ {
+ myrpt->remoterx = 1;
+ ast_indicate(chan,AST_CONTROL_RADIO_KEY);
+ }
+ if ((!rem_rx) && (myrpt->remoterx))
+ {
+ myrpt->remoterx = 0;
+ ast_indicate(chan,AST_CONTROL_RADIO_UNKEY);
+ }
+ /* if auth requested, and not authed yet */
+ if (authreq && (!myrpt->loginlevel[0]))
+ {
+ if ((!authtold) && ((myrpt->authtelltimer += elap)
+ >= AUTHTELLTIME))
+ {
+ authtold = 1;
+ rpt_telemetry(myrpt,LOGINREQ,NULL);
+ }
+ if ((myrpt->authtimer += elap) >= AUTHLOGOUTTIME)
+ {
+ break; /* if not logged in, hang up after a time */
+ }
+ }
+#ifndef OLDKEY
+ if ((myrpt->retxtimer += elap) >= REDUNDANT_TX_TIME)
+ {
+ myrpt->retxtimer = 0;
+ if ((myrpt->remoterx) && (!myrpt->remotetx))
+ ast_indicate(chan,AST_CONTROL_RADIO_KEY);
+ else
+ ast_indicate(chan,AST_CONTROL_RADIO_UNKEY);
+ }
+
+ if ((myrpt->rerxtimer += elap) >= (REDUNDANT_TX_TIME * 2))
+ {
+ keyed = 0;
+ myrpt->rerxtimer = 0;
+ }
+#endif
+ if (rem_totx && (!myrpt->remotetx))
+ {
+ /* if not authed, and needed, dont transmit */
+ if ((!myrpt->p.authlevel) || myrpt->loginlevel[0])
+ {
+ myrpt->remotetx = 1;
+ if((myrpt->remtxfreqok = check_tx_freq(myrpt)))
+ {
+ time(&myrpt->last_activity_time);
+ if ((iskenwood_pci4) && (myrpt->txchannel == myrpt->zaptxchannel))
+ {
+ z.radpar = DAHDI_RADPAR_UIODATA;
+ z.data = 1;
+ if (ioctl(myrpt->zaptxchannel->fds[0],DAHDI_RADIO_SETPARAM,&z) == -1)
+ {
+ ast_log(LOG_ERROR,"Cannot set UIODATA\n");
+ return -1;
+ }
+ }
+ else
+ {
+ ast_indicate(myrpt->txchannel,AST_CONTROL_RADIO_KEY);
+ }
+ if (myrpt->p.archivedir) donodelog(myrpt,"TXKEY");
+ }
+ }
+ }
+ if ((!rem_totx) && myrpt->remotetx) /* Remote base radio TX unkey */
+ {
+ myrpt->remotetx = 0;
+ if(!myrpt->remtxfreqok){
+ rpt_telemetry(myrpt,UNAUTHTX,NULL);
+ }
+ if ((iskenwood_pci4) && (myrpt->txchannel == myrpt->zaptxchannel))
+ {
+ z.radpar = DAHDI_RADPAR_UIODATA;
+ z.data = 3;
+ if (ioctl(myrpt->zaptxchannel->fds[0],DAHDI_RADIO_SETPARAM,&z) == -1)
+ {
+ ast_log(LOG_ERROR,"Cannot set UIODATA\n");
+ return -1;
+ }
+ }
+ else
+ {
+ ast_indicate(myrpt->txchannel,AST_CONTROL_RADIO_UNKEY);
+ }
+ if (myrpt->p.archivedir) donodelog(myrpt,"TXUNKEY");
+ }
+ if (myrpt->hfscanmode){
+ myrpt->scantimer -= elap;
+ if(myrpt->scantimer <= 0){
+ if (!reming)
+ {
+ myrpt->scantimer = REM_SCANTIME;
+ rpt_telemetry(myrpt,SCAN,0);
+ } else myrpt->scantimer = 1;
+ }
+ }
+ rpt_mutex_lock(&myrpt->lock);
+ c = myrpt->macrobuf[0];
+ if (c && (!myrpt->macrotimer))
+ {
+ myrpt->macrotimer = MACROTIME;
+ memmove(myrpt->macrobuf,myrpt->macrobuf + 1,MAXMACRO - 1);
+ if ((c == 'p') || (c == 'P'))
+ myrpt->macrotimer = MACROPTIME;
+ rpt_mutex_unlock(&myrpt->lock);
+ if (myrpt->p.archivedir)
+ {
+ char str[100];
+ sprintf(str,"DTMF(M),%c",c);
+ donodelog(myrpt,str);
+ }
+ if (handle_remote_dtmf_digit(myrpt,c,&keyed,0) == -1) break;
+ continue;
+ } else rpt_mutex_unlock(&myrpt->lock);
+ if (who == chan) /* if it was a read from incomming */
+ {
+ f = ast_read(chan);
+ if (!f)
+ {
+ if (debug) printf("@@@@ link:Hung Up\n");
+ break;
+ }
+ if (f->frametype == AST_FRAME_VOICE)
+ {
+ if (ioctl(chan->fds[0], DAHDI_GETCONFMUTE, &ismuted) == -1)
+ {
+ ismuted = 0;
+ }
+ /* if not transmitting, zero-out audio */
+ ismuted |= (!myrpt->remotetx);
+ if (dtmfed && phone_mode) ismuted = 1;
+ dtmfed = 0;
+ if (ismuted)
+ {
+ memset(f->data,0,f->datalen);
+ if (myrpt->lastf1)
+ memset(myrpt->lastf1->data,0,myrpt->lastf1->datalen);
+ if (myrpt->lastf2)
+ memset(myrpt->lastf2->data,0,myrpt->lastf2->datalen);
+ }
+ if (f) f2 = ast_frdup(f);
+ else f2 = NULL;
+ f1 = myrpt->lastf2;
+ myrpt->lastf2 = myrpt->lastf1;
+ myrpt->lastf1 = f2;
+ if (ismuted)
+ {
+ if (myrpt->lastf1)
+ memset(myrpt->lastf1->data,0,myrpt->lastf1->datalen);
+ if (myrpt->lastf2)
+ memset(myrpt->lastf2->data,0,myrpt->lastf2->datalen);
+ }
+ if (f1)
+ {
+ if (phone_mode)
+ ast_write(myrpt->txchannel,f1);
+ else
+ ast_write(myrpt->txchannel,f);
+ ast_frfree(f1);
+ }
+ }
+#ifndef OLD_ASTERISK
+ else if (f->frametype == AST_FRAME_DTMF_BEGIN)
+ {
+ if (myrpt->lastf1)
+ memset(myrpt->lastf1->data,0,myrpt->lastf1->datalen);
+ if (myrpt->lastf2)
+ memset(myrpt->lastf2->data,0,myrpt->lastf2->datalen);
+ dtmfed = 1;
+ }
+#endif
+ if (f->frametype == AST_FRAME_DTMF)
+ {
+ if (myrpt->lastf1)
+ memset(myrpt->lastf1->data,0,myrpt->lastf1->datalen);
+ if (myrpt->lastf2)
+ memset(myrpt->lastf2->data,0,myrpt->lastf2->datalen);
+ dtmfed = 1;
+ if (handle_remote_phone_dtmf(myrpt,f->subclass,&keyed,phone_mode) == -1)
+ {
+ if (debug) printf("@@@@ rpt:Hung Up\n");
+ ast_frfree(f);
+ break;
+ }
+ }
+ if (f->frametype == AST_FRAME_TEXT)
+ {
+ if (handle_remote_data(myrpt,f->data) == -1)
+ {
+ if (debug) printf("@@@@ rpt:Hung Up\n");
+ ast_frfree(f);
+ break;
+ }
+ }
+ if (f->frametype == AST_FRAME_CONTROL)
+ {
+ if (f->subclass == AST_CONTROL_HANGUP)
+ {
+ if (debug) printf("@@@@ rpt:Hung Up\n");
+ ast_frfree(f);
+ break;
+ }
+ /* if RX key */
+ if (f->subclass == AST_CONTROL_RADIO_KEY)
+ {
+ if (debug == 7) printf("@@@@ rx key\n");
+ keyed = 1;
+ myrpt->rerxtimer = 0;
+ }
+ /* if RX un-key */
+ if (f->subclass == AST_CONTROL_RADIO_UNKEY)
+ {
+ myrpt->rerxtimer = 0;
+ if (debug == 7) printf("@@@@ rx un-key\n");
+ keyed = 0;
+ }
+ }
+ ast_frfree(f);
+ continue;
+ }
+ if (who == myrpt->rxchannel) /* if it was a read from radio */
+ {
+ f = ast_read(myrpt->rxchannel);
+ if (!f)
+ {
+ if (debug) printf("@@@@ link:Hung Up\n");
+ break;
+ }
+ if (f->frametype == AST_FRAME_VOICE)
+ {
+ int myreming = 0;
+
+ if(!strcmp(myrpt->remote, remote_rig_kenwood))
+ myreming = reming;
+
+ if (myreming || (!remkeyed) ||
+ ((myrpt->remote) && (myrpt->remotetx)) ||
+ ((myrpt->remmode != REM_MODE_FM) &&
+ notremming))
+ memset(f->data,0,f->datalen);
+ ast_write(myrpt->pchannel,f);
+ }
+ else if (f->frametype == AST_FRAME_CONTROL)
+ {
+ if (f->subclass == AST_CONTROL_HANGUP)
+ {
+ if (debug) printf("@@@@ rpt:Hung Up\n");
+ ast_frfree(f);
+ break;
+ }
+ /* if RX key */
+ if (f->subclass == AST_CONTROL_RADIO_KEY)
+ {
+ if (debug == 7) printf("@@@@ remote rx key\n");
+ if (!myrpt->remotetx)
+ {
+ remkeyed = 1;
+ }
+ }
+ /* if RX un-key */
+ if (f->subclass == AST_CONTROL_RADIO_UNKEY)
+ {
+ if (debug == 7) printf("@@@@ remote rx un-key\n");
+ if (!myrpt->remotetx)
+ {
+ remkeyed = 0;
+ }
+ }
+ }
+ ast_frfree(f);
+ continue;
+ }
+ if (who == myrpt->pchannel) /* if is remote mix output */
+ {
+ f = ast_read(myrpt->pchannel);
+ if (!f)
+ {
+ if (debug) printf("@@@@ link:Hung Up\n");
+ break;
+ }
+ if (f->frametype == AST_FRAME_VOICE)
+ {
+ ast_write(chan,f);
+ }
+ if (f->frametype == AST_FRAME_CONTROL)
+ {
+ if (f->subclass == AST_CONTROL_HANGUP)
+ {
+ if (debug) printf("@@@@ rpt:Hung Up\n");
+ ast_frfree(f);
+ break;
+ }
+ }
+ ast_frfree(f);
+ continue;
+ }
+ if ((myrpt->rxchannel != myrpt->txchannel) &&
+ (who == myrpt->txchannel)) /* do this cuz you have to */
+ {
+ f = ast_read(myrpt->txchannel);
+ if (!f)
+ {
+ if (debug) printf("@@@@ link:Hung Up\n");
+ break;
+ }
+ if (f->frametype == AST_FRAME_CONTROL)
+ {
+ if (f->subclass == AST_CONTROL_HANGUP)
+ {
+ if (debug) printf("@@@@ rpt:Hung Up\n");
+ ast_frfree(f);
+ break;
+ }
+ }
+ ast_frfree(f);
+ continue;
+ }
+ }
+ if (myrpt->p.archivedir)
+ {
+ char mycmd[100],*b,*b1;
+
+ /* look at callerid to see what node this comes from */
+ if (!chan->cid.cid_num) /* if doesn't have caller id */
+ {
+ b1 = "0";
+ } else {
+ ast_callerid_parse(chan->cid.cid_num,&b,&b1);
+ ast_shrink_phone_number(b1);
+ }
+ sprintf(mycmd,"DISCONNECT,%s",b1);
+ donodelog(myrpt,mycmd);
+ }
+ /* wait for telem to be done */
+ while(myrpt->tele.next != &myrpt->tele) usleep(100000);
+ sprintf(tmp,"mixmonitor stop %s",chan->name);
+ ast_cli_command(nullfd,tmp);
+ close(nullfd);
+ rpt_mutex_lock(&myrpt->lock);
+ myrpt->hfscanmode = 0;
+ myrpt->hfscanstatus = 0;
+ myrpt->remoteon = 0;
+ rpt_mutex_unlock(&myrpt->lock);
+ if (myrpt->lastf1) ast_frfree(myrpt->lastf1);
+ myrpt->lastf1 = NULL;
+ if (myrpt->lastf2) ast_frfree(myrpt->lastf2);
+ myrpt->lastf2 = NULL;
+ if ((iskenwood_pci4) && (myrpt->txchannel == myrpt->zaptxchannel))
+ {
+ z.radpar = DAHDI_RADPAR_UIOMODE;
+ z.data = 3;
+ if (ioctl(myrpt->zaptxchannel->fds[0],DAHDI_RADIO_SETPARAM,&z) == -1)
+ {
+ ast_log(LOG_ERROR,"Cannot set UIOMODE\n");
+ return -1;
+ }
+ z.radpar = DAHDI_RADPAR_UIODATA;
+ z.data = 3;
+ if (ioctl(myrpt->zaptxchannel->fds[0],DAHDI_RADIO_SETPARAM,&z) == -1)
+ {
+ ast_log(LOG_ERROR,"Cannot set UIODATA\n");
+ return -1;
+ }
+ i = DAHDI_OFFHOOK;
+ if (ioctl(myrpt->zaptxchannel->fds[0],DAHDI_HOOK,&i) == -1)
+ {
+ ast_log(LOG_ERROR,"Cannot set hook\n");
+ return -1;
+ }
+ }
+ if (myrpt->iofd) close(myrpt->iofd);
+ myrpt->iofd = -1;
+ ast_hangup(myrpt->pchannel);
+ if (myrpt->rxchannel != myrpt->txchannel) ast_hangup(myrpt->txchannel);
+ ast_hangup(myrpt->rxchannel);
+ closerem(myrpt);
+#ifdef OLD_ASTERISK
+ LOCAL_USER_REMOVE(u);
+#endif
+ return res;
+}
+
+#ifdef OLD_ASTERISK
+int unload_module()
+#else
+static int unload_module(void)
+#endif
+{
+ int i;
+
+#ifdef OLD_ASTERISK
+ STANDARD_HANGUP_LOCALUSERS;
+#endif
+ for(i = 0; i < nrpts; i++) {
+ if (!strcmp(rpt_vars[i].name,rpt_vars[i].p.nodes)) continue;
+ ast_mutex_destroy(&rpt_vars[i].lock);
+ ast_mutex_destroy(&rpt_vars[i].remlock);
+ }
+ i = ast_unregister_application(app);
+
+ /* Unregister cli extensions */
+ ast_cli_unregister(&cli_debug);
+ ast_cli_unregister(&cli_dump);
+ ast_cli_unregister(&cli_stats);
+ ast_cli_unregister(&cli_lstats);
+ ast_cli_unregister(&cli_nodes);
+ ast_cli_unregister(&cli_reload);
+ ast_cli_unregister(&cli_restart);
+ ast_cli_unregister(&cli_fun);
+
+ return i;
+}
+
+#ifdef OLD_ASTERISK
+int load_module()
+#else
+static int load_module(void)
+#endif
+{
+ ast_pthread_create(&rpt_master_thread,NULL,rpt_master,NULL);
+
+ /* Register cli extensions */
+ ast_cli_register(&cli_debug);
+ ast_cli_register(&cli_dump);
+ ast_cli_register(&cli_stats);
+ ast_cli_register(&cli_lstats);
+ ast_cli_register(&cli_nodes);
+ ast_cli_register(&cli_reload);
+ ast_cli_register(&cli_restart);
+ ast_cli_register(&cli_fun);
+
+ return ast_register_application(app, rpt_exec, synopsis, descrip);
+}
+
+#ifdef OLD_ASTERISK
+char *description()
+{
+ return tdesc;
+}
+int usecount(void)
+{
+ int res;
+ STANDARD_USECOUNT(res);
+ return res;
+}
+
+char *key()
+{
+ return ASTERISK_GPL_KEY;
+}
+#endif
+
+#ifdef OLD_ASTERISK
+int reload()
+#else
+static int reload(void)
+#endif
+{
+int n;
+
+ for(n = 0; n < nrpts; n++) rpt_vars[n].reload = 1;
+ return(0);
+}
+
+#ifndef OLD_ASTERISK
+/* STD_MOD(MOD_1, reload, NULL, NULL); */
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Radio Repeater/Remote Base Application",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
+
+#endif
+
diff --git a/apps/app_sayunixtime.c b/apps/app_sayunixtime.c
new file mode 100644
index 000000000..2e9c9b323
--- /dev/null
+++ b/apps/app_sayunixtime.c
@@ -0,0 +1,126 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (c) 2003, 2006 Tilghman Lesher. All rights reserved.
+ * Copyright (c) 2006 Digium, Inc.
+ *
+ * Tilghman Lesher <app_sayunixtime__200309@the-tilghman.com>
+ *
+ * This code is released by the author with no restrictions on usage.
+ *
+ * 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.
+ *
+ */
+
+/*! \file
+ *
+ * \brief SayUnixTime application
+ *
+ * \author Tilghman Lesher <app_sayunixtime__200309@the-tilghman.com>
+ *
+ * \ingroup applications
+ */
+
+#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/options.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/say.h"
+#include "asterisk/app.h"
+
+static char *app_sayunixtime = "SayUnixTime";
+static char *app_datetime = "DateTime";
+
+static char *sayunixtime_synopsis = "Says a specified time in a custom format";
+
+static char *sayunixtime_descrip =
+"SayUnixTime([unixtime][|[timezone][|format]])\n"
+" unixtime: time, in seconds since Jan 1, 1970. May be negative.\n"
+" defaults to now.\n"
+" timezone: timezone, see /usr/share/zoneinfo for a list.\n"
+" defaults to machine default.\n"
+" format: a format the time is to be said in. See voicemail.conf.\n"
+" defaults to \"ABdY 'digits/at' IMp\"\n";
+static char *datetime_descrip =
+"DateTime([unixtime][|[timezone][|format]])\n"
+" unixtime: time, in seconds since Jan 1, 1970. May be negative.\n"
+" defaults to now.\n"
+" timezone: timezone, see /usr/share/zoneinfo for a list.\n"
+" defaults to machine default.\n"
+" format: a format the time is to be said in. See voicemail.conf.\n"
+" defaults to \"ABdY 'digits/at' IMp\"\n";
+
+
+static int sayunixtime_exec(struct ast_channel *chan, void *data)
+{
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(timeval);
+ AST_APP_ARG(timezone);
+ AST_APP_ARG(format);
+ );
+ char *parse;
+ int res = 0;
+ struct ast_module_user *u;
+ time_t unixtime;
+
+ if (!data)
+ return 0;
+
+ parse = ast_strdupa(data);
+
+ u = ast_module_user_add(chan);
+
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ ast_get_time_t(args.timeval, &unixtime, time(NULL), NULL);
+
+ if (chan->_state != AST_STATE_UP)
+ res = ast_answer(chan);
+
+ if (!res)
+ res = ast_say_date_with_format(chan, unixtime, AST_DIGIT_ANY,
+ chan->language, args.format, args.timezone);
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app_sayunixtime);
+ res |= ast_unregister_application(app_datetime);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ int res;
+
+ res = ast_register_application(app_sayunixtime, sayunixtime_exec, sayunixtime_synopsis, sayunixtime_descrip);
+ res |= ast_register_application(app_datetime, sayunixtime_exec, sayunixtime_synopsis, datetime_descrip);
+
+ return res;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Say time");
diff --git a/apps/app_senddtmf.c b/apps/app_senddtmf.c
new file mode 100644
index 000000000..e48ba4fe0
--- /dev/null
+++ b/apps/app_senddtmf.c
@@ -0,0 +1,143 @@
+/*
+ * 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 App to send DTMF digits
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/options.h"
+#include "asterisk/utils.h"
+#include "asterisk/app.h"
+#include "asterisk/manager.h"
+
+static char *app = "SendDTMF";
+
+static char *synopsis = "Sends arbitrary DTMF digits";
+
+static char *descrip =
+" SendDTMF(digits[|timeout_ms]): Sends DTMF digits on a channel. \n"
+" Accepted digits: 0-9, *#abcd, w (.5s pause)\n"
+" The application will either pass the assigned digits or terminate if it\n"
+" encounters an error.\n";
+
+
+static int senddtmf_exec(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ struct ast_module_user *u;
+ char *digits = NULL, *to = NULL;
+ int timeout = 250;
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "SendDTMF requires an argument (digits or *#aAbBcCdD)\n");
+ return 0;
+ }
+
+ u = ast_module_user_add(chan);
+
+ digits = ast_strdupa(data);
+
+ if ((to = strchr(digits,'|'))) {
+ *to = '\0';
+ to++;
+ timeout = atoi(to);
+ }
+
+ if (timeout <= 0)
+ timeout = 250;
+
+ res = ast_dtmf_stream(chan,NULL,digits,timeout);
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static char mandescr_playdtmf[] =
+"Description: Plays a dtmf digit on the specified channel.\n"
+"Variables: (all are required)\n"
+" Channel: Channel name to send digit to\n"
+" Digit: The dtmf digit to play\n";
+
+static int manager_play_dtmf(struct mansession *s, const struct message *m)
+{
+ const char *channel = astman_get_header(m, "Channel");
+ const char *digit = astman_get_header(m, "Digit");
+ struct ast_channel *chan = ast_get_channel_by_name_locked(channel);
+
+ if (!chan) {
+ astman_send_error(s, m, "Channel not specified");
+ return 0;
+ }
+ if (!digit) {
+ astman_send_error(s, m, "No digit specified");
+ ast_mutex_unlock(&chan->lock);
+ return 0;
+ }
+
+ ast_senddigit(chan, *digit);
+
+ ast_mutex_unlock(&chan->lock);
+ astman_send_ack(s, m, "DTMF successfully queued");
+
+ return 0;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+ res |= ast_manager_unregister("PlayDTMF");
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ int res;
+
+ res = ast_manager_register2( "PlayDTMF", EVENT_FLAG_CALL, manager_play_dtmf, "Play DTMF signal on a specific channel.", mandescr_playdtmf );
+ res |= ast_register_application(app, senddtmf_exec, synopsis, descrip);
+
+ return res;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Send DTMF digits Application");
diff --git a/apps/app_sendtext.c b/apps/app_sendtext.c
new file mode 100644
index 000000000..af4fcade5
--- /dev/null
+++ b/apps/app_sendtext.c
@@ -0,0 +1,131 @@
+/*
+ * 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 App to transmit a text message
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \note Requires support of sending text messages from channel driver
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/image.h"
+#include "asterisk/options.h"
+#include "asterisk/app.h"
+
+static const char *app = "SendText";
+
+static const char *synopsis = "Send a Text Message";
+
+static const char *descrip =
+" SendText(text[|options]): Sends text to current channel (callee).\n"
+"Result of transmission will be stored in the SENDTEXTSTATUS\n"
+"channel variable:\n"
+" SUCCESS Transmission succeeded\n"
+" FAILURE Transmission failed\n"
+" UNSUPPORTED Text transmission not supported by channel\n"
+"\n"
+"At this moment, text is supposed to be 7 bit ASCII in most channels.\n"
+"The option string many contain the following character:\n"
+"'j' -- jump to n+101 priority if the channel doesn't support\n"
+" text transport\n";
+
+
+static int sendtext_exec(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ struct ast_module_user *u;
+ char *status = "UNSUPPORTED";
+ char *parse = NULL;
+ int priority_jump = 0;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(text);
+ AST_APP_ARG(options);
+ );
+
+ u = ast_module_user_add(chan);
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "SendText requires an argument (text[|options])\n");
+ ast_module_user_remove(u);
+ return -1;
+ } else
+ parse = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ if (args.options) {
+ if (strchr(args.options, 'j'))
+ priority_jump = 1;
+ }
+
+ ast_channel_lock(chan);
+ if (!chan->tech->send_text) {
+ ast_channel_unlock(chan);
+ pbx_builtin_setvar_helper(chan, "SENDTEXTSTATUS", status);
+ /* Does not support transport */
+ if (priority_jump || ast_opt_priority_jumping)
+ ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
+ ast_module_user_remove(u);
+ return 0;
+ }
+ status = "FAILURE";
+ ast_channel_unlock(chan);
+ res = ast_sendtext(chan, args.text);
+ if (!res)
+ status = "SUCCESS";
+ pbx_builtin_setvar_helper(chan, "SENDTEXTSTATUS", status);
+ ast_module_user_remove(u);
+ return 0;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, sendtext_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Send Text Applications");
diff --git a/apps/app_setcallerid.c b/apps/app_setcallerid.c
new file mode 100644
index 000000000..fb060f11b
--- /dev/null
+++ b/apps/app_setcallerid.c
@@ -0,0 +1,162 @@
+/*
+ * 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 App to set callerid
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/image.h"
+#include "asterisk/callerid.h"
+
+static char *app2 = "SetCallerPres";
+
+static char *synopsis2 = "Set CallerID Presentation";
+
+
+static char *descrip2 =
+" SetCallerPres(presentation): Set Caller*ID presentation on a call.\n"
+" Valid presentations are:\n"
+"\n"
+" allowed_not_screened : Presentation Allowed, Not Screened\n"
+" allowed_passed_screen : Presentation Allowed, Passed Screen\n"
+" allowed_failed_screen : Presentation Allowed, Failed Screen\n"
+" allowed : Presentation Allowed, Network Number\n"
+" prohib_not_screened : Presentation Prohibited, Not Screened\n"
+" prohib_passed_screen : Presentation Prohibited, Passed Screen\n"
+" prohib_failed_screen : Presentation Prohibited, Failed Screen\n"
+" prohib : Presentation Prohibited, Network Number\n"
+" unavailable : Number Unavailable\n"
+"\n"
+;
+
+static int setcallerid_pres_exec(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *u;
+ int pres = -1;
+
+ u = ast_module_user_add(chan);
+
+ /* For interface consistency, permit the argument to be specified as a number */
+ if (sscanf(data, "%d", &pres) != 1 || pres < 0 || pres > 255 || (pres & 0x9c)) {
+ pres = ast_parse_caller_presentation(data);
+ }
+
+ if (pres < 0) {
+ ast_log(LOG_WARNING, "'%s' is not a valid presentation (see 'show application SetCallerPres')\n",
+ (char *) data);
+ ast_module_user_remove(u);
+ return 0;
+ }
+
+ chan->cid.cid_pres = pres;
+ ast_module_user_remove(u);
+ return 0;
+}
+
+static char *app = "SetCallerID";
+
+static char *synopsis = "Set CallerID";
+
+static char *descrip =
+" SetCallerID(clid[|a]): Set Caller*ID on a call to a new\n"
+"value. Sets ANI as well if a flag is used. \n";
+
+static int setcallerid_exec(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ char *tmp = NULL;
+ char name[256];
+ char num[256];
+ struct ast_module_user *u;
+ char *opt;
+ int anitoo = 0;
+ static int dep_warning = 0;
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "SetCallerID requires an argument!\n");
+ return 0;
+ }
+
+ u = ast_module_user_add(chan);
+
+ if (!dep_warning) {
+ dep_warning = 1;
+ ast_log(LOG_WARNING, "SetCallerID is deprecated. Please use Set(CALLERID(all)=...) or Set(CALLERID(ani)=...) instead.\n");
+ }
+
+ tmp = ast_strdupa(data);
+
+ opt = strchr(tmp, '|');
+ if (opt) {
+ *opt = '\0';
+ opt++;
+ if (*opt == 'a')
+ anitoo = 1;
+ }
+
+ ast_callerid_split(tmp, name, sizeof(name), num, sizeof(num));
+ ast_set_callerid(chan, num, name, anitoo ? num : NULL);
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app2);
+ res |= ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ int res;
+
+ res = ast_register_application(app2, setcallerid_pres_exec, synopsis2, descrip2);
+ res |= ast_register_application(app, setcallerid_exec, synopsis, descrip);
+
+ return res;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Set CallerID Application");
diff --git a/apps/app_setcdruserfield.c b/apps/app_setcdruserfield.c
new file mode 100644
index 000000000..ccfbcedd2
--- /dev/null
+++ b/apps/app_setcdruserfield.c
@@ -0,0 +1,175 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * Justin Huff <jjhuff@mspin.net>
+ *
+ * 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 Applictions connected with CDR engine
+ *
+ * \author Justin Huff <jjhuff@mspin.net>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "asterisk/channel.h"
+#include "asterisk/cdr.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/logger.h"
+#include "asterisk/config.h"
+#include "asterisk/manager.h"
+#include "asterisk/utils.h"
+
+
+static char *setcdruserfield_descrip =
+ "[Synopsis]\n"
+ "SetCDRUserField(value)\n\n"
+ "[Description]\n"
+ "SetCDRUserField(value): Set the CDR 'user field' to value\n"
+ " The Call Data Record (CDR) user field is an extra field you\n"
+ " can use for data not stored anywhere else in the record.\n"
+ " CDR records can be used for billing or storing other arbitrary data\n"
+ " (I.E. telephone survey responses)\n"
+ " Also see AppendCDRUserField().\n"
+ "\nThis application is deprecated in favor of Set(CDR(userfield)=...)\n";
+
+
+static char *setcdruserfield_app = "SetCDRUserField";
+static char *setcdruserfield_synopsis = "Set the CDR user field";
+
+static char *appendcdruserfield_descrip =
+ "[Synopsis]\n"
+ "AppendCDRUserField(value)\n\n"
+ "[Description]\n"
+ "AppendCDRUserField(value): Append value to the CDR user field\n"
+ " The Call Data Record (CDR) user field is an extra field you\n"
+ " can use for data not stored anywhere else in the record.\n"
+ " CDR records can be used for billing or storing other arbitrary data\n"
+ " (I.E. telephone survey responses)\n"
+ " Also see SetCDRUserField().\n"
+ "\nThis application is deprecated in favor of Set(CDR(userfield)=...)\n";
+
+static char *appendcdruserfield_app = "AppendCDRUserField";
+static char *appendcdruserfield_synopsis = "Append to the CDR user field";
+
+
+static int action_setcdruserfield(struct mansession *s, const struct message *m)
+{
+ struct ast_channel *c = NULL;
+ const char *userfield = astman_get_header(m, "UserField");
+ const char *channel = astman_get_header(m, "Channel");
+ const char *append = astman_get_header(m, "Append");
+
+ if (ast_strlen_zero(channel)) {
+ astman_send_error(s, m, "No Channel specified");
+ return 0;
+ }
+ if (ast_strlen_zero(userfield)) {
+ astman_send_error(s, m, "No UserField specified");
+ return 0;
+ }
+ c = ast_get_channel_by_name_locked(channel);
+ if (!c) {
+ astman_send_error(s, m, "No such channel");
+ return 0;
+ }
+ if (ast_true(append))
+ ast_cdr_appenduserfield(c, userfield);
+ else
+ ast_cdr_setuserfield(c, userfield);
+ ast_mutex_unlock(&c->lock);
+ astman_send_ack(s, m, "CDR Userfield Set");
+ return 0;
+}
+
+static int setcdruserfield_exec(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *u;
+ int res = 0;
+ static int dep_warning = 0;
+
+ u = ast_module_user_add(chan);
+
+ if (chan->cdr && data) {
+ ast_cdr_setuserfield(chan, (char*)data);
+ }
+
+ if (!dep_warning) {
+ dep_warning = 1;
+ ast_log(LOG_WARNING, "SetCDRUserField is deprecated. Please use CDR(userfield) instead.\n");
+ }
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int appendcdruserfield_exec(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *u;
+ int res = 0;
+ static int dep_warning = 0;
+
+ u = ast_module_user_add(chan);
+
+ if (chan->cdr && data) {
+ ast_cdr_appenduserfield(chan, (char*)data);
+ }
+
+ if (!dep_warning) {
+ dep_warning = 1;
+ ast_log(LOG_WARNING, "AppendCDRUserField is deprecated. Please use CDR(userfield) instead.\n");
+ }
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(setcdruserfield_app);
+ res |= ast_unregister_application(appendcdruserfield_app);
+ res |= ast_manager_unregister("SetCDRUserField");
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ int res;
+
+ res = ast_register_application(setcdruserfield_app, setcdruserfield_exec, setcdruserfield_synopsis, setcdruserfield_descrip);
+ res |= ast_register_application(appendcdruserfield_app, appendcdruserfield_exec, appendcdruserfield_synopsis, appendcdruserfield_descrip);
+ res |= ast_manager_register("SetCDRUserField", EVENT_FLAG_CALL, action_setcdruserfield, "Set the CDR UserField");
+
+ return res;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "CDR user field apps");
diff --git a/apps/app_settransfercapability.c b/apps/app_settransfercapability.c
new file mode 100644
index 000000000..e29b44e22
--- /dev/null
+++ b/apps/app_settransfercapability.c
@@ -0,0 +1,136 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2005, Frank Sautter, levigo holding gmbh, www.levigo.de
+ *
+ * Frank Sautter - asterisk+at+sautter+dot+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 App to set the ISDN Transfer Capability
+ *
+ * \author Frank Sautter - asterisk+at+sautter+dot+com
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/options.h"
+#include "asterisk/transcap.h"
+
+
+static char *app = "SetTransferCapability";
+
+static char *synopsis = "Set ISDN Transfer Capability";
+
+
+static struct { int val; char *name; } transcaps[] = {
+ { AST_TRANS_CAP_SPEECH, "SPEECH" },
+ { AST_TRANS_CAP_DIGITAL, "DIGITAL" },
+ { AST_TRANS_CAP_RESTRICTED_DIGITAL, "RESTRICTED_DIGITAL" },
+ { AST_TRANS_CAP_3_1K_AUDIO, "3K1AUDIO" },
+ { AST_TRANS_CAP_DIGITAL_W_TONES, "DIGITAL_W_TONES" },
+ { AST_TRANS_CAP_VIDEO, "VIDEO" },
+};
+
+static char *descrip =
+" SetTransferCapability(transfercapability): Set the ISDN Transfer \n"
+"Capability of a call to a new value.\n"
+"Valid Transfer Capabilities are:\n"
+"\n"
+" SPEECH : 0x00 - Speech (default, voice calls)\n"
+" DIGITAL : 0x08 - Unrestricted digital information (data calls)\n"
+" RESTRICTED_DIGITAL : 0x09 - Restricted digital information\n"
+" 3K1AUDIO : 0x10 - 3.1kHz Audio (fax calls)\n"
+" DIGITAL_W_TONES : 0x11 - Unrestricted digital information with tones/announcements\n"
+" VIDEO : 0x18 - Video\n"
+"\n"
+"This application is deprecated in favor of Set(CHANNEL(transfercapability)=...)\n"
+;
+
+static int settransfercapability_exec(struct ast_channel *chan, void *data)
+{
+ char *tmp = NULL;
+ struct ast_module_user *u;
+ int x;
+ char *opts;
+ int transfercapability = -1;
+ static int dep_warning = 0;
+
+ u = ast_module_user_add(chan);
+
+ if (!dep_warning) {
+ dep_warning = 1;
+ ast_log(LOG_WARNING, "SetTransferCapability is deprecated. Please use CHANNEL(transfercapability) instead.\n");
+ }
+
+ if (data)
+ tmp = ast_strdupa(data);
+ else
+ tmp = "";
+
+ opts = strchr(tmp, '|');
+ if (opts)
+ *opts = '\0';
+
+ for (x = 0; x < (sizeof(transcaps) / sizeof(transcaps[0])); x++) {
+ if (!strcasecmp(transcaps[x].name, tmp)) {
+ transfercapability = transcaps[x].val;
+ break;
+ }
+ }
+ if (transfercapability < 0) {
+ ast_log(LOG_WARNING, "'%s' is not a valid transfer capability (see 'show application SetTransferCapability')\n", tmp);
+ ast_module_user_remove(u);
+ return 0;
+ }
+
+ chan->transfercapability = (unsigned short)transfercapability;
+
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Setting transfer capability to: 0x%.2x - %s.\n", transfercapability, tmp);
+
+ ast_module_user_remove(u);
+
+ return 0;
+}
+
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, settransfercapability_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Set ISDN Transfer Capability");
diff --git a/apps/app_skel.c b/apps/app_skel.c
new file mode 100644
index 000000000..55830ebee
--- /dev/null
+++ b/apps/app_skel.c
@@ -0,0 +1,133 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) <Year>, <Your Name Here>
+ *
+ * <Your Name Here> <<Your Email Here>>
+ *
+ * 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 Skeleton application
+ *
+ * \author <Your Name Here> <<Your Email Here>>
+ *
+ * This is a skeleton for development of an Asterisk application
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+ <defaultenabled>no</defaultenabled>
+ ***/
+
+#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/module.h"
+#include "asterisk/lock.h"
+#include "asterisk/app.h"
+
+static char *app = "Skel";
+static char *synopsis =
+"Skeleton application.";
+static char *descrip = "This application is a template to build other applications from.\n"
+ " It shows you the basic structure to create your own Asterisk applications.\n";
+
+enum {
+ OPTION_A = (1 << 0),
+ OPTION_B = (1 << 1),
+ OPTION_C = (1 << 2),
+} option_flags;
+
+enum {
+ OPTION_ARG_B = 0,
+ OPTION_ARG_C = 1,
+ /* This *must* be the last value in this enum! */
+ OPTION_ARG_ARRAY_SIZE = 2,
+} option_args;
+
+AST_APP_OPTIONS(app_opts,{
+ AST_APP_OPTION('a', OPTION_A),
+ AST_APP_OPTION_ARG('b', OPTION_B, OPTION_ARG_B),
+ AST_APP_OPTION_ARG('c', OPTION_C, OPTION_ARG_C),
+});
+
+
+static int app_exec(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ struct ast_flags flags;
+ struct ast_module_user *u;
+ char *parse, *opts[OPTION_ARG_ARRAY_SIZE];
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(dummy);
+ AST_APP_ARG(options);
+ );
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "%s requires an argument (dummy|[options])\n", app);
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ /* Do our thing here */
+
+ /* We need to make a copy of the input string if we are going to modify it! */
+ parse = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ if (args.argc == 2)
+ ast_app_parse_options(app_opts, &flags, opts, args.options);
+
+ if (!ast_strlen_zero(args.dummy))
+ ast_log(LOG_NOTICE, "Dummy value is : %s\n", args.dummy);
+
+ if (ast_test_flag(&flags, OPTION_A))
+ ast_log(LOG_NOTICE, "Option A is set\n");
+
+ if (ast_test_flag(&flags, OPTION_B))
+ ast_log(LOG_NOTICE, "Option B is set with : %s\n", opts[OPTION_ARG_B] ? opts[OPTION_ARG_B] : "<unspecified>");
+
+ if (ast_test_flag(&flags, OPTION_C))
+ ast_log(LOG_NOTICE, "Option C is set with : %s\n", opts[OPTION_ARG_C] ? opts[OPTION_ARG_C] : "<unspecified>");
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+ res = ast_unregister_application(app);
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, app_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Skeleton (sample) Application");
diff --git a/apps/app_sms.c b/apps/app_sms.c
new file mode 100644
index 000000000..cd445456e
--- /dev/null
+++ b/apps/app_sms.c
@@ -0,0 +1,1538 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2004 - 2005, Adrian Kennard, rights assigned to Digium
+ *
+ * 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 SMS application - ETSI ES 201 912 protocol 1 implimentation
+ * \ingroup applications
+ *
+ * \author Adrian Kennard
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <dirent.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/options.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/alaw.h"
+#include "asterisk/callerid.h"
+
+/* output using Alaw rather than linear */
+/* #define OUTALAW */
+
+/* ToDo */
+/* Add full VP support */
+/* Handle status report messages (generation and reception) */
+/* Time zones on time stamps */
+/* user ref field */
+
+static volatile unsigned char message_ref; /* arbitary message ref */
+static volatile unsigned int seq; /* arbitrary message sequence number for unqiue files */
+
+static char log_file[255];
+static char spool_dir[255];
+
+static char *app = "SMS";
+
+static char *synopsis = "Communicates with SMS service centres and SMS capable analogue phones";
+
+static char *descrip =
+ " SMS(name|[a][s]): SMS handles exchange of SMS data with a call to/from SMS capabale\n"
+ "phone or SMS PSTN service center. Can send and/or receive SMS messages.\n"
+ "Works to ETSI ES 201 912 compatible with BT SMS PSTN service in UK\n"
+ "Typical usage is to use to handle called from the SMS service centre CLI,\n"
+ "or to set up a call using 'outgoing' or manager interface to connect\n"
+ "service centre to SMS()\n"
+ "name is the name of the queue used in /var/spool/asterisk/sms\n"
+ "Arguments:\n"
+ " a: answer, i.e. send initial FSK packet.\n"
+ " s: act as service centre talking to a phone.\n"
+ "Messages are processed as per text file message queues.\n"
+ "smsq (a separate software) is a command to generate message\n"
+ "queues and send messages.\n";
+
+static signed short wave[] = {
+ 0, 392, 782, 1167, 1545, 1913, 2270, 2612, 2939, 3247, 3536, 3802, 4045, 4263, 4455, 4619, 4755, 4862, 4938, 4985,
+ 5000, 4985, 4938, 4862, 4755, 4619, 4455, 4263, 4045, 3802, 3536, 3247, 2939, 2612, 2270, 1913, 1545, 1167, 782, 392,
+ 0, -392, -782, -1167,
+ -1545, -1913, -2270, -2612, -2939, -3247, -3536, -3802, -4045, -4263, -4455, -4619, -4755, -4862, -4938, -4985, -5000,
+ -4985, -4938, -4862,
+ -4755, -4619, -4455, -4263, -4045, -3802, -3536, -3247, -2939, -2612, -2270, -1913, -1545, -1167, -782, -392
+};
+
+#ifdef OUTALAW
+static unsigned char wavea[80];
+#endif
+
+
+/* SMS 7 bit character mapping to UCS-2 */
+static const unsigned short defaultalphabet[] = {
+ 0x0040, 0x00A3, 0x0024, 0x00A5, 0x00E8, 0x00E9, 0x00F9, 0x00EC,
+ 0x00F2, 0x00E7, 0x000A, 0x00D8, 0x00F8, 0x000D, 0x00C5, 0x00E5,
+ 0x0394, 0x005F, 0x03A6, 0x0393, 0x039B, 0x03A9, 0x03A0, 0x03A8,
+ 0x03A3, 0x0398, 0x039E, 0x00A0, 0x00C6, 0x00E6, 0x00DF, 0x00C9,
+ ' ', '!', '"', '#', 164, '%', '&', 39, '(', ')', '*', '+', ',', '-', '.', '/',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?',
+ 161, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
+ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 196, 214, 209, 220, 167,
+ 191, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 228, 246, 241, 252, 224,
+};
+
+static const unsigned short escapes[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x000C, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0x005E, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0x007B, 0x007D, 0, 0, 0, 0, 0, 0x005C,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x005B, 0x007E, 0x005D, 0,
+ 0x007C, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0x20AC, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+#define SMSLEN 160 /* max SMS length */
+
+typedef struct sms_s
+{
+ unsigned char hangup; /* we are done... */
+ unsigned char err; /* set for any errors */
+ unsigned char smsc:1; /* we are SMSC */
+ unsigned char rx:1; /* this is a received message */
+ char queue[30]; /* queue name */
+ char oa[20]; /* originating address */
+ char da[20]; /* destination address */
+ time_t scts; /* time stamp, UTC */
+ unsigned char pid; /* protocol ID */
+ unsigned char dcs; /* data coding scheme */
+ short mr; /* message reference - actually a byte, but usde -1 for not set */
+ int udl; /* user data length */
+ int udhl; /* user data header length */
+ unsigned char srr:1; /* Status Report request */
+ unsigned char udhi:1; /* User Data Header required, even if length 0 */
+ unsigned char rp:1; /* Reply Path */
+ unsigned int vp; /* validity period in minutes, 0 for not set */
+ unsigned short ud[SMSLEN]; /* user data (message), UCS-2 coded */
+ unsigned char udh[SMSLEN]; /* user data header */
+ char cli[20]; /* caller ID */
+ unsigned char ophase; /* phase (0-79) for 0 and 1 frequencies (1300Hz and 2100Hz) */
+ unsigned char ophasep; /* phase (0-79) for 1200 bps */
+ unsigned char obyte; /* byte being sent */
+ unsigned int opause; /* silent pause before sending (in sample periods) */
+ unsigned char obitp; /* bit in byte */
+ unsigned char osync; /* sync bits to send */
+ unsigned char obytep; /* byte in data */
+ unsigned char obyten; /* bytes in data */
+ unsigned char omsg[256]; /* data buffer (out) */
+ unsigned char imsg[200]; /* data buffer (in) */
+ signed long long ims0,
+ imc0,
+ ims1,
+ imc1; /* magnitude averages sin/cos 0/1 */
+ unsigned int idle;
+ unsigned short imag; /* signal level */
+ unsigned char ips0,
+ ips1,
+ ipc0,
+ ipc1; /* phase sin/cos 0/1 */
+ unsigned char ibitl; /* last bit */
+ unsigned char ibitc; /* bit run length count */
+ unsigned char iphasep; /* bit phase (0-79) for 1200 bps */
+ unsigned char ibitn; /* bit number in byte being received */
+ unsigned char ibytev; /* byte value being received */
+ unsigned char ibytep; /* byte pointer in messafe */
+ unsigned char ibytec; /* byte checksum for message */
+ unsigned char ierr; /* error flag */
+ unsigned char ibith; /* history of last bits */
+ unsigned char ibitt; /* total of 1's in last 3 bites */
+ /* more to go here */
+} sms_t;
+
+/* different types of encoding */
+#define is7bit(dcs) (((dcs)&0xC0)?(!((dcs)&4)):(!((dcs)&12)))
+#define is8bit(dcs) (((dcs)&0xC0)?(((dcs)&4)):(((dcs)&12)==4))
+#define is16bit(dcs) (((dcs)&0xC0)?0:(((dcs)&12)==8))
+
+static void *sms_alloc (struct ast_channel *chan, void *params)
+{
+ return params;
+}
+
+static void sms_release (struct ast_channel *chan, void *data)
+{
+ return;
+}
+
+static void sms_messagetx (sms_t * h);
+
+/*! \brief copy number, skipping non digits apart from leading + */
+static void numcpy (char *d, char *s)
+{
+ if (*s == '+')
+ *d++ = *s++;
+ while (*s) {
+ if (isdigit (*s))
+ *d++ = *s;
+ s++;
+ }
+ *d = 0;
+}
+
+/*! \brief static, return a date/time in ISO format */
+static char * isodate (time_t t)
+{
+ static char date[20];
+ strftime (date, sizeof (date), "%Y-%m-%dT%H:%M:%S", localtime (&t));
+ return date;
+}
+
+/*! \brief reads next UCS character from null terminated UTF-8 string and advanced pointer */
+/* for non valid UTF-8 sequences, returns character as is */
+/* Does not advance pointer for null termination */
+static long utf8decode (unsigned char **pp)
+{
+ unsigned char *p = *pp;
+ if (!*p)
+ return 0; /* null termination of string */
+ (*pp)++;
+ if (*p < 0xC0)
+ return *p; /* ascii or continuation character */
+ if (*p < 0xE0) {
+ if (*p < 0xC2 || (p[1] & 0xC0) != 0x80)
+ return *p; /* not valid UTF-8 */
+ (*pp)++;
+ return ((*p & 0x1F) << 6) + (p[1] & 0x3F);
+ }
+ if (*p < 0xF0) {
+ if ((*p == 0xE0 && p[1] < 0xA0) || (p[1] & 0xC0) != 0x80 || (p[2] & 0xC0) != 0x80)
+ return *p; /* not valid UTF-8 */
+ (*pp) += 2;
+ return ((*p & 0x0F) << 12) + ((p[1] & 0x3F) << 6) + (p[2] & 0x3F);
+ }
+ if (*p < 0xF8) {
+ if ((*p == 0xF0 && p[1] < 0x90) || (p[1] & 0xC0) != 0x80 || (p[2] & 0xC0) != 0x80 || (p[3] & 0xC0) != 0x80)
+ return *p; /* not valid UTF-8 */
+ (*pp) += 3;
+ return ((*p & 0x07) << 18) + ((p[1] & 0x3F) << 12) + ((p[2] & 0x3F) << 6) + (p[3] & 0x3F);
+ }
+ if (*p < 0xFC) {
+ if ((*p == 0xF8 && p[1] < 0x88) || (p[1] & 0xC0) != 0x80 || (p[2] & 0xC0) != 0x80 || (p[3] & 0xC0) != 0x80
+ || (p[4] & 0xC0) != 0x80)
+ return *p; /* not valid UTF-8 */
+ (*pp) += 4;
+ return ((*p & 0x03) << 24) + ((p[1] & 0x3F) << 18) + ((p[2] & 0x3F) << 12) + ((p[3] & 0x3F) << 6) + (p[4] & 0x3F);
+ }
+ if (*p < 0xFE) {
+ if ((*p == 0xFC && p[1] < 0x84) || (p[1] & 0xC0) != 0x80 || (p[2] & 0xC0) != 0x80 || (p[3] & 0xC0) != 0x80
+ || (p[4] & 0xC0) != 0x80 || (p[5] & 0xC0) != 0x80)
+ return *p; /* not valid UTF-8 */
+ (*pp) += 5;
+ return ((*p & 0x01) << 30) + ((p[1] & 0x3F) << 24) + ((p[2] & 0x3F) << 18) + ((p[3] & 0x3F) << 12) + ((p[4] & 0x3F) << 6) + (p[5] & 0x3F);
+ }
+ return *p; /* not sensible */
+}
+
+/*! \brief takes a binary header (udhl bytes at udh) and UCS-2 message (udl characters at ud) and packs in to o using SMS 7 bit character codes */
+/* The return value is the number of septets packed in to o, which is internally limited to SMSLEN */
+/* o can be null, in which case this is used to validate or count only */
+/* if the input contains invalid characters then the return value is -1 */
+static int packsms7 (unsigned char *o, int udhl, unsigned char *udh, int udl, unsigned short *ud)
+{
+ unsigned char p = 0, b = 0, n = 0;
+
+ if (udhl) { /* header */
+ if (o)
+ o[p++] = udhl;
+ b = 1;
+ n = 1;
+ while (udhl--) {
+ if (o)
+ o[p++] = *udh++;
+ b += 8;
+ while (b >= 7) {
+ b -= 7;
+ n++;
+ }
+ if (n >= SMSLEN)
+ return n;
+ }
+ if (b) {
+ b = 7 - b;
+ if (++n >= SMSLEN)
+ return n;
+ }; /* filling to septet boundary */
+ }
+ if (o)
+ o[p] = 0;
+ /* message */
+ while (udl--) {
+ long u;
+ unsigned char v;
+ u = *ud++;
+ for (v = 0; v < 128 && defaultalphabet[v] != u; v++);
+ if (v == 128 && u && n + 1 < SMSLEN) {
+ for (v = 0; v < 128 && escapes[v] != u; v++);
+ if (v < 128) { /* escaped sequence */
+ if (o)
+ o[p] |= (27 << b);
+ b += 7;
+ if (b >= 8) {
+ b -= 8;
+ p++;
+ if (o)
+ o[p] = (27 >> (7 - b));
+ }
+ n++;
+ }
+ }
+ if (v == 128)
+ return -1; /* invalid character */
+ if (o)
+ o[p] |= (v << b);
+ b += 7;
+ if (b >= 8) {
+ b -= 8;
+ p++;
+ if (o)
+ o[p] = (v >> (7 - b));
+ }
+ if (++n >= SMSLEN)
+ return n;
+ }
+ return n;
+}
+
+/*! \brief takes a binary header (udhl bytes at udh) and UCS-2 message (udl characters at ud) and packs in to o using 8 bit character codes */
+/* The return value is the number of bytes packed in to o, which is internally limited to 140 */
+/* o can be null, in which case this is used to validate or count only */
+/* if the input contains invalid characters then the return value is -1 */
+static int packsms8 (unsigned char *o, int udhl, unsigned char *udh, int udl, unsigned short *ud)
+{
+ unsigned char p = 0;
+
+ /* header - no encoding */
+ if (udhl) {
+ if (o)
+ o[p++] = udhl;
+ while (udhl--) {
+ if (o)
+ o[p++] = *udh++;
+ if (p >= 140)
+ return p;
+ }
+ }
+ while (udl--) {
+ long u;
+ u = *ud++;
+ if (u < 0 || u > 0xFF)
+ return -1; /* not valid */
+ if (o)
+ o[p++] = u;
+ if (p >= 140)
+ return p;
+ }
+ return p;
+}
+
+/*! \brief takes a binary header (udhl bytes at udh) and UCS-2
+ message (udl characters at ud) and packs in to o using 16 bit
+ UCS-2 character codes
+ The return value is the number of bytes packed in to o, which is
+ internally limited to 140
+ o can be null, in which case this is used to validate or count
+ only if the input contains invalid characters then
+ the return value is -1 */
+static int packsms16 (unsigned char *o, int udhl, unsigned char *udh, int udl, unsigned short *ud)
+{
+ unsigned char p = 0;
+ /* header - no encoding */
+ if (udhl) {
+ if (o)
+ o[p++] = udhl;
+ while (udhl--) {
+ if (o)
+ o[p++] = *udh++;
+ if (p >= 140)
+ return p;
+ }
+ }
+ while (udl--) {
+ long u;
+ u = *ud++;
+ if (o)
+ o[p++] = (u >> 8);
+ if (p >= 140)
+ return p - 1; /* could not fit last character */
+ if (o)
+ o[p++] = u;
+ if (p >= 140)
+ return p;
+ }
+ return p;
+}
+
+/*! \brief general pack, with length and data,
+ returns number of bytes of target used */
+static int packsms (unsigned char dcs, unsigned char *base, unsigned int udhl, unsigned char *udh, int udl, unsigned short *ud)
+{
+ unsigned char *p = base;
+ if (udl) {
+ int l = 0;
+ if (is7bit (dcs)) { /* 7 bit */
+ l = packsms7 (p + 1, udhl, udh, udl, ud);
+ if (l < 0)
+ l = 0;
+ *p++ = l;
+ p += (l * 7 + 7) / 8;
+ } else if (is8bit (dcs)) { /* 8 bit */
+ l = packsms8 (p + 1, udhl, udh, udl, ud);
+ if (l < 0)
+ l = 0;
+ *p++ = l;
+ p += l;
+ } else { /* UCS-2 */
+ l = packsms16 (p + 1, udhl, udh, udl, ud);
+ if (l < 0)
+ l = 0;
+ *p++ = l;
+ p += l;
+ }
+ } else
+ *p++ = 0; /* no user data */
+ return p - base;
+}
+
+
+/*! \brief pack a date and return */
+static void packdate (unsigned char *o, time_t w)
+{
+ struct tm *t = localtime (&w);
+#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined( __NetBSD__ ) || defined(__APPLE__)
+ int z = -t->tm_gmtoff / 60 / 15;
+#else
+ int z = timezone / 60 / 15;
+#endif
+ *o++ = ((t->tm_year % 10) << 4) + (t->tm_year % 100) / 10;
+ *o++ = (((t->tm_mon + 1) % 10) << 4) + (t->tm_mon + 1) / 10;
+ *o++ = ((t->tm_mday % 10) << 4) + t->tm_mday / 10;
+ *o++ = ((t->tm_hour % 10) << 4) + t->tm_hour / 10;
+ *o++ = ((t->tm_min % 10) << 4) + t->tm_min / 10;
+ *o++ = ((t->tm_sec % 10) << 4) + t->tm_sec / 10;
+ if (z < 0)
+ *o++ = (((-z) % 10) << 4) + (-z) / 10 + 0x08;
+ else
+ *o++ = ((z % 10) << 4) + z / 10;
+}
+
+/*! \brief unpack a date and return */
+static time_t unpackdate (unsigned char *i)
+{
+ struct tm t;
+ t.tm_year = 100 + (i[0] & 0xF) * 10 + (i[0] >> 4);
+ t.tm_mon = (i[1] & 0xF) * 10 + (i[1] >> 4) - 1;
+ t.tm_mday = (i[2] & 0xF) * 10 + (i[2] >> 4);
+ t.tm_hour = (i[3] & 0xF) * 10 + (i[3] >> 4);
+ t.tm_min = (i[4] & 0xF) * 10 + (i[4] >> 4);
+ t.tm_sec = (i[5] & 0xF) * 10 + (i[5] >> 4);
+ t.tm_isdst = 0;
+ if (i[6] & 0x08)
+ t.tm_min += 15 * ((i[6] & 0x7) * 10 + (i[6] >> 4));
+ else
+ t.tm_min -= 15 * ((i[6] & 0x7) * 10 + (i[6] >> 4));
+ return ast_mktime(&t, NULL);
+}
+
+/*! \brief unpacks bytes (7 bit encoding) at i, len l septets,
+ and places in udh and ud setting udhl and udl. udh not used
+ if udhi not set */
+static void unpacksms7 (unsigned char *i, unsigned char l, unsigned char *udh, int *udhl, unsigned short *ud, int *udl, char udhi)
+{
+ unsigned char b = 0, p = 0;
+ unsigned short *o = ud;
+ *udhl = 0;
+ if (udhi && l) { /* header */
+ int h = i[p];
+ *udhl = h;
+ if (h) {
+ b = 1;
+ p++;
+ l--;
+ while (h-- && l) {
+ *udh++ = i[p++];
+ b += 8;
+ while (b >= 7) {
+ b -= 7;
+ l--;
+ if (!l)
+ break;
+ }
+ }
+ /* adjust for fill, septets */
+ if (b) {
+ b = 7 - b;
+ l--;
+ }
+ }
+ }
+ while (l--) {
+ unsigned char v;
+ if (b < 2)
+ v = ((i[p] >> b) & 0x7F);
+ else
+ v = ((((i[p] >> b) + (i[p + 1] << (8 - b)))) & 0x7F);
+ b += 7;
+ if (b >= 8) {
+ b -= 8;
+ p++;
+ }
+ if (o > ud && o[-1] == 0x00A0 && escapes[v])
+ o[-1] = escapes[v];
+ else
+ *o++ = defaultalphabet[v];
+ }
+ *udl = (o - ud);
+}
+
+/*! \brief unpacks bytes (8 bit encoding) at i, len l septets,
+ and places in udh and ud setting udhl and udl. udh not used
+ if udhi not set */
+static void unpacksms8 (unsigned char *i, unsigned char l, unsigned char *udh, int *udhl, unsigned short *ud, int *udl, char udhi)
+{
+ unsigned short *o = ud;
+ *udhl = 0;
+ if (udhi) {
+ int n = *i;
+ *udhl = n;
+ if (n) {
+ i++;
+ l--;
+ while (l && n) {
+ l--;
+ n--;
+ *udh++ = *i++;
+ }
+ }
+ }
+ while (l--)
+ *o++ = *i++; /* not to UTF-8 as explicitely 8 bit coding in DCS */
+ *udl = (o - ud);
+}
+
+/*! \brief unpacks bytes (16 bit encoding) at i, len l septets,
+ and places in udh and ud setting udhl and udl.
+ udh not used if udhi not set */
+static void unpacksms16 (unsigned char *i, unsigned char l, unsigned char *udh, int *udhl, unsigned short *ud, int *udl, char udhi)
+{
+ unsigned short *o = ud;
+ *udhl = 0;
+ if (udhi) {
+ int n = *i;
+ *udhl = n;
+ if (n) {
+ i++;
+ l--;
+ while (l && n) {
+ l--;
+ n--;
+ *udh++ = *i++;
+ }
+ }
+ }
+ while (l--) {
+ int v = *i++;
+ if (l--)
+ v = (v << 8) + *i++;
+ *o++ = v;
+ }
+ *udl = (o - ud);
+}
+
+/*! \brief general unpack - starts with length byte (octet or septet) and returns number of bytes used, inc length */
+static int unpacksms (unsigned char dcs, unsigned char *i, unsigned char *udh, int *udhl, unsigned short *ud, int *udl, char udhi)
+{
+ int l = *i++;
+ if (is7bit (dcs)) {
+ unpacksms7 (i, l, udh, udhl, ud, udl, udhi);
+ l = (l * 7 + 7) / 8; /* adjust length to return */
+ } else if (is8bit (dcs))
+ unpacksms8 (i, l, udh, udhl, ud, udl, udhi);
+ else
+ unpacksms16 (i, l, udh, udhl, ud, udl, udhi);
+ return l + 1;
+}
+
+/*! \brief unpack an address from i, return byte length, unpack to o */
+static unsigned char unpackaddress (char *o, unsigned char *i)
+{
+ unsigned char l = i[0],
+ p;
+ if (i[1] == 0x91)
+ *o++ = '+';
+ for (p = 0; p < l; p++) {
+ if (p & 1)
+ *o++ = (i[2 + p / 2] >> 4) + '0';
+ else
+ *o++ = (i[2 + p / 2] & 0xF) + '0';
+ }
+ *o = 0;
+ return (l + 5) / 2;
+}
+
+/*! \brief store an address at o, and return number of bytes used */
+static unsigned char packaddress (unsigned char *o, char *i)
+{
+ unsigned char p = 2;
+ o[0] = 0;
+ if (*i == '+') {
+ i++;
+ o[1] = 0x91;
+ } else
+ o[1] = 0x81;
+ while (*i)
+ if (isdigit (*i)) {
+ if (o[0] & 1)
+ o[p++] |= ((*i & 0xF) << 4);
+ else
+ o[p] = (*i & 0xF);
+ o[0]++;
+ i++;
+ } else
+ i++;
+ if (o[0] & 1)
+ o[p++] |= 0xF0; /* pad */
+ return p;
+}
+
+/*! \brief Log the output, and remove file */
+static void sms_log (sms_t * h, char status)
+{
+ if (*h->oa || *h->da) {
+ int o = open (log_file, O_CREAT | O_APPEND | O_WRONLY, 0666);
+ if (o >= 0) {
+ char line[1000], mrs[3] = "", *p;
+ unsigned char n;
+
+ if (h->mr >= 0)
+ snprintf (mrs, sizeof (mrs), "%02X", h->mr);
+ snprintf (line, sizeof (line), "%s %c%c%c%s %s %s %s ",
+ isodate (time (0)), status, h->rx ? 'I' : 'O', h->smsc ? 'S' : 'M', mrs, h->queue, *h->oa ? h->oa : "-",
+ *h->da ? h->da : "-");
+ p = line + strlen (line);
+ for (n = 0; n < h->udl; n++)
+ if (h->ud[n] == '\\') {
+ *p++ = '\\';
+ *p++ = '\\';
+ } else if (h->ud[n] == '\n') {
+ *p++ = '\\';
+ *p++ = 'n';
+ } else if (h->ud[n] == '\r') {
+ *p++ = '\\';
+ *p++ = 'r';
+ } else if (h->ud[n] < 32 || h->ud[n] == 127)
+ *p++ = 191;
+ else
+ *p++ = h->ud[n];
+ *p++ = '\n';
+ *p = 0;
+ if (write (o, line, strlen (line)) < 0) {
+ ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
+ }
+ close (o);
+ }
+ *h->oa = *h->da = h->udl = 0;
+ }
+}
+
+/*! \brief parse and delete a file */
+static void sms_readfile (sms_t * h, char *fn)
+{
+ char line[1000];
+ FILE *s;
+ char dcsset = 0; /* if DSC set */
+ ast_log (LOG_EVENT, "Sending %s\n", fn);
+ h->rx = h->udl = *h->oa = *h->da = h->pid = h->srr = h->udhi = h->rp = h->vp = h->udhl = 0;
+ h->mr = -1;
+ h->dcs = 0xF1; /* normal messages class 1 */
+ h->scts = time (0);
+ s = fopen (fn, "r");
+ if (s)
+ {
+ if (unlink (fn))
+ { /* concurrent access, we lost */
+ fclose (s);
+ return;
+ }
+ while (fgets (line, sizeof (line), s))
+ { /* process line in file */
+ char *p;
+ void *pp = &p;
+ for (p = line; *p && *p != '\n' && *p != '\r'; p++);
+ *p = 0; /* strip eoln */
+ p = line;
+ if (!*p || *p == ';')
+ continue; /* blank line or comment, ignore */
+ while (isalnum (*p))
+ {
+ *p = tolower (*p);
+ p++;
+ }
+ while (isspace (*p))
+ *p++ = 0;
+ if (*p == '=')
+ {
+ *p++ = 0;
+ if (!strcmp (line, "ud"))
+ { /* parse message (UTF-8) */
+ unsigned char o = 0;
+ while (*p && o < SMSLEN)
+ h->ud[o++] = utf8decode(pp);
+ h->udl = o;
+ if (*p)
+ ast_log (LOG_WARNING, "UD too long in %s\n", fn);
+ } else
+ {
+ while (isspace (*p))
+ p++;
+ if (!strcmp (line, "oa") && strlen (p) < sizeof (h->oa))
+ numcpy (h->oa, p);
+ else if (!strcmp (line, "da") && strlen (p) < sizeof (h->oa))
+ numcpy (h->da, p);
+ else if (!strcmp (line, "pid"))
+ h->pid = atoi (p);
+ else if (!strcmp (line, "dcs"))
+ {
+ h->dcs = atoi (p);
+ dcsset = 1;
+ } else if (!strcmp (line, "mr"))
+ h->mr = atoi (p);
+ else if (!strcmp (line, "srr"))
+ h->srr = (atoi (p) ? 1 : 0);
+ else if (!strcmp (line, "vp"))
+ h->vp = atoi (p);
+ else if (!strcmp (line, "rp"))
+ h->rp = (atoi (p) ? 1 : 0);
+ else if (!strcmp (line, "scts"))
+ { /* get date/time */
+ int Y,
+ m,
+ d,
+ H,
+ M,
+ S;
+ if (sscanf (p, "%d-%d-%dT%d:%d:%d", &Y, &m, &d, &H, &M, &S) == 6)
+ {
+ struct tm t;
+ t.tm_year = Y - 1900;
+ t.tm_mon = m - 1;
+ t.tm_mday = d;
+ t.tm_hour = H;
+ t.tm_min = M;
+ t.tm_sec = S;
+ t.tm_isdst = -1;
+ h->scts = ast_mktime(&t, NULL);
+ if (h->scts == (time_t) - 1)
+ ast_log (LOG_WARNING, "Bad date/timein %s: %s", fn, p);
+ }
+ } else
+ ast_log (LOG_WARNING, "Cannot parse in %s: %s=%si\n", fn, line, p);
+ }
+ } else if (*p == '#')
+ { /* raw hex format */
+ *p++ = 0;
+ if (*p == '#')
+ {
+ p++;
+ if (!strcmp (line, "ud"))
+ { /* user data */
+ int o = 0;
+ while (*p && o < SMSLEN)
+ {
+ if (isxdigit (*p) && isxdigit (p[1]) && isxdigit (p[2]) && isxdigit (p[3]))
+ {
+ h->ud[o++] =
+ (((isalpha (*p) ? 9 : 0) + (*p & 0xF)) << 12) +
+ (((isalpha (p[1]) ? 9 : 0) + (p[1] & 0xF)) << 8) +
+ (((isalpha (p[2]) ? 9 : 0) + (p[2] & 0xF)) << 4) + ((isalpha (p[3]) ? 9 : 0) + (p[3] & 0xF));
+ p += 4;
+ } else
+ break;
+ }
+ h->udl = o;
+ if (*p)
+ ast_log (LOG_WARNING, "UD too long / invalid UCS-2 hex in %s\n", fn);
+ } else
+ ast_log (LOG_WARNING, "Only ud can use ## format, %s\n", fn);
+ } else if (!strcmp (line, "ud"))
+ { /* user data */
+ int o = 0;
+ while (*p && o < SMSLEN)
+ {
+ if (isxdigit (*p) && isxdigit (p[1]))
+ {
+ h->ud[o++] = (((isalpha (*p) ? 9 : 0) + (*p & 0xF)) << 4) + ((isalpha (p[1]) ? 9 : 0) + (p[1] & 0xF));
+ p += 2;
+ } else
+ break;
+ }
+ h->udl = o;
+ if (*p)
+ ast_log (LOG_WARNING, "UD too long / invalid UCS-1 hex in %s\n", fn);
+ } else if (!strcmp (line, "udh"))
+ { /* user data header */
+ unsigned char o = 0;
+ h->udhi = 1;
+ while (*p && o < SMSLEN)
+ {
+ if (isxdigit (*p) && isxdigit (p[1]))
+ {
+ h->udh[o] = (((isalpha (*p) ? 9 : 0) + (*p & 0xF)) << 4) + ((isalpha (p[1]) ? 9 : 0) + (p[1] & 0xF));
+ o++;
+ p += 2;
+ } else
+ break;
+ }
+ h->udhl = o;
+ if (*p)
+ ast_log (LOG_WARNING, "UDH too long / invalid hex in %s\n", fn);
+ } else
+ ast_log (LOG_WARNING, "Only ud and udh can use # format, %s\n", fn);
+ } else
+ ast_log (LOG_WARNING, "Cannot parse in %s: %s\n", fn, line);
+ }
+ fclose (s);
+ if (!dcsset && packsms7 (0, h->udhl, h->udh, h->udl, h->ud) < 0)
+ {
+ if (packsms8 (0, h->udhl, h->udh, h->udl, h->ud) < 0)
+ {
+ if (packsms16 (0, h->udhl, h->udh, h->udl, h->ud) < 0)
+ ast_log (LOG_WARNING, "Invalid UTF-8 message even for UCS-2 (%s)\n", fn);
+ else
+ {
+ h->dcs = 0x08; /* default to 16 bit */
+ ast_log (LOG_WARNING, "Sending in 16 bit format (%s)\n", fn);
+ }
+ } else
+ {
+ h->dcs = 0xF5; /* default to 8 bit */
+ ast_log (LOG_WARNING, "Sending in 8 bit format (%s)\n", fn);
+ }
+ }
+ if (is7bit (h->dcs) && packsms7 (0, h->udhl, h->udh, h->udl, h->ud) < 0)
+ ast_log (LOG_WARNING, "Invalid 7 bit GSM data %s\n", fn);
+ if (is8bit (h->dcs) && packsms8 (0, h->udhl, h->udh, h->udl, h->ud) < 0)
+ ast_log (LOG_WARNING, "Invalid 8 bit data %s\n", fn);
+ if (is16bit (h->dcs) && packsms16 (0, h->udhl, h->udh, h->udl, h->ud) < 0)
+ ast_log (LOG_WARNING, "Invalid 16 bit data %s\n", fn);
+ }
+}
+
+/*! \brief white a received text message to a file */
+static void sms_writefile (sms_t * h)
+{
+ char fn[200] = "", fn2[200] = "";
+ FILE *o;
+ ast_copy_string (fn, spool_dir, sizeof (fn));
+ mkdir (fn, 0777); /* ensure it exists */
+ snprintf (fn + strlen (fn), sizeof (fn) - strlen (fn), "/%s", h->smsc ? h->rx ? "morx" : "mttx" : h->rx ? "mtrx" : "motx");
+ mkdir (fn, 0777); /* ensure it exists */
+ ast_copy_string (fn2, fn, sizeof (fn2));
+ snprintf (fn2 + strlen (fn2), sizeof (fn2) - strlen (fn2), "/%s.%s-%d", h->queue, isodate (h->scts), seq++);
+ snprintf (fn + strlen (fn), sizeof (fn) - strlen (fn), "/.%s", fn2 + strlen (fn) + 1);
+ o = fopen (fn, "w");
+ if (o) {
+ if (*h->oa)
+ fprintf (o, "oa=%s\n", h->oa);
+ if (*h->da)
+ fprintf (o, "da=%s\n", h->da);
+ if (h->udhi) {
+ unsigned int p;
+ fprintf (o, "udh#");
+ for (p = 0; p < h->udhl; p++)
+ fprintf (o, "%02X", h->udh[p]);
+ fprintf (o, "\n");
+ }
+ if (h->udl) {
+ unsigned int p;
+ for (p = 0; p < h->udl && h->ud[p] >= ' '; p++);
+ if (p < h->udl)
+ fputc (';', o); /* cannot use ud=, but include as a comment for human readable */
+ fprintf (o, "ud=");
+ for (p = 0; p < h->udl; p++) {
+ unsigned short v = h->ud[p];
+ if (v < 32)
+ fputc (191, o);
+ else if (v < 0x80)
+ fputc (v, o);
+ else if (v < 0x800)
+ {
+ fputc (0xC0 + (v >> 6), o);
+ fputc (0x80 + (v & 0x3F), o);
+ } else
+ {
+ fputc (0xE0 + (v >> 12), o);
+ fputc (0x80 + ((v >> 6) & 0x3F), o);
+ fputc (0x80 + (v & 0x3F), o);
+ }
+ }
+ fprintf (o, "\n");
+ for (p = 0; p < h->udl && h->ud[p] >= ' '; p++);
+ if (p < h->udl) {
+ for (p = 0; p < h->udl && h->ud[p] < 0x100; p++);
+ if (p == h->udl) { /* can write in ucs-1 hex */
+ fprintf (o, "ud#");
+ for (p = 0; p < h->udl; p++)
+ fprintf (o, "%02X", h->ud[p]);
+ fprintf (o, "\n");
+ } else { /* write in UCS-2 */
+ fprintf (o, "ud##");
+ for (p = 0; p < h->udl; p++)
+ fprintf (o, "%04X", h->ud[p]);
+ fprintf (o, "\n");
+ }
+ }
+ }
+ if (h->scts)
+ fprintf (o, "scts=%s\n", isodate (h->scts));
+ if (h->pid)
+ fprintf (o, "pid=%d\n", h->pid);
+ if (h->dcs != 0xF1)
+ fprintf (o, "dcs=%d\n", h->dcs);
+ if (h->vp)
+ fprintf (o, "vp=%d\n", h->vp);
+ if (h->srr)
+ fprintf (o, "srr=1\n");
+ if (h->mr >= 0)
+ fprintf (o, "mr=%d\n", h->mr);
+ if (h->rp)
+ fprintf (o, "rp=1\n");
+ fclose (o);
+ if (rename (fn, fn2))
+ unlink (fn);
+ else
+ ast_log (LOG_EVENT, "Received to %s\n", fn2);
+ }
+}
+
+/*! \brief read dir skipping dot files... */
+static struct dirent *readdirqueue (DIR * d, char *queue)
+{
+ struct dirent *f;
+ do {
+ f = readdir (d);
+ } while (f && (*f->d_name == '.' || strncmp (f->d_name, queue, strlen (queue)) || f->d_name[strlen (queue)] != '.'));
+ return f;
+}
+
+/*! \brief handle the incoming message */
+static unsigned char sms_handleincoming (sms_t * h)
+{
+ unsigned char p = 3;
+ if (h->smsc) { /* SMSC */
+ if ((h->imsg[2] & 3) == 1) { /* SMS-SUBMIT */
+ h->udhl = h->udl = 0;
+ h->vp = 0;
+ h->srr = ((h->imsg[2] & 0x20) ? 1 : 0);
+ h->udhi = ((h->imsg[2] & 0x40) ? 1 : 0);
+ h->rp = ((h->imsg[2] & 0x80) ? 1 : 0);
+ ast_copy_string (h->oa, h->cli, sizeof (h->oa));
+ h->scts = time (0);
+ h->mr = h->imsg[p++];
+ p += unpackaddress (h->da, h->imsg + p);
+ h->pid = h->imsg[p++];
+ h->dcs = h->imsg[p++];
+ if ((h->imsg[2] & 0x18) == 0x10) { /* relative VP */
+ if (h->imsg[p] < 144)
+ h->vp = (h->imsg[p] + 1) * 5;
+ else if (h->imsg[p] < 168)
+ h->vp = 720 + (h->imsg[p] - 143) * 30;
+ else if (h->imsg[p] < 197)
+ h->vp = (h->imsg[p] - 166) * 1440;
+ else
+ h->vp = (h->imsg[p] - 192) * 10080;
+ p++;
+ } else if (h->imsg[2] & 0x18)
+ p += 7; /* ignore enhanced / absolute VP */
+ p += unpacksms (h->dcs, h->imsg + p, h->udh, &h->udhl, h->ud, &h->udl, h->udhi);
+ h->rx = 1; /* received message */
+ sms_writefile (h); /* write the file */
+ if (p != h->imsg[1] + 2) {
+ ast_log (LOG_WARNING, "Mismatch receive unpacking %d/%d\n", p, h->imsg[1] + 2);
+ return 0xFF; /* duh! */
+ }
+ } else {
+ ast_log (LOG_WARNING, "Unknown message type %02X\n", h->imsg[2]);
+ return 0xFF;
+ }
+ } else { /* client */
+ if (!(h->imsg[2] & 3)) { /* SMS-DELIVER */
+ *h->da = h->srr = h->rp = h->vp = h->udhi = h->udhl = h->udl = 0;
+ h->srr = ((h->imsg[2] & 0x20) ? 1 : 0);
+ h->udhi = ((h->imsg[2] & 0x40) ? 1 : 0);
+ h->rp = ((h->imsg[2] & 0x80) ? 1 : 0);
+ h->mr = -1;
+ p += unpackaddress (h->oa, h->imsg + p);
+ h->pid = h->imsg[p++];
+ h->dcs = h->imsg[p++];
+ h->scts = unpackdate (h->imsg + p);
+ p += 7;
+ p += unpacksms (h->dcs, h->imsg + p, h->udh, &h->udhl, h->ud, &h->udl, h->udhi);
+ h->rx = 1; /* received message */
+ sms_writefile (h); /* write the file */
+ if (p != h->imsg[1] + 2) {
+ ast_log (LOG_WARNING, "Mismatch receive unpacking %d/%d\n", p, h->imsg[1] + 2);
+ return 0xFF; /* duh! */
+ }
+ } else {
+ ast_log (LOG_WARNING, "Unknown message type %02X\n", h->imsg[2]);
+ return 0xFF;
+ }
+ }
+ return 0; /* no error */
+}
+
+#ifdef SOLARIS
+#define NAME_MAX 1024
+#endif
+
+/*! \brief find and fill in next message, or send a REL if none waiting */
+static void sms_nextoutgoing (sms_t * h)
+{
+ char fn[100 + NAME_MAX] = "";
+ DIR *d;
+ char more = 0;
+ ast_copy_string (fn, spool_dir, sizeof (fn));
+ mkdir (fn, 0777); /* ensure it exists */
+ h->rx = 0; /* outgoing message */
+ snprintf (fn + strlen (fn), sizeof (fn) - strlen (fn), "/%s", h->smsc ? "mttx" : "motx");
+ mkdir (fn, 0777); /* ensure it exists */
+ d = opendir (fn);
+ if (d) {
+ struct dirent *f = readdirqueue (d, h->queue);
+ if (f) {
+ snprintf (fn + strlen (fn), sizeof (fn) - strlen (fn), "/%s", f->d_name);
+ sms_readfile (h, fn);
+ if (readdirqueue (d, h->queue))
+ more = 1; /* more to send */
+ }
+ closedir (d);
+ }
+ if (*h->da || *h->oa) { /* message to send */
+ unsigned char p = 2;
+ h->omsg[0] = 0x91; /* SMS_DATA */
+ if (h->smsc) { /* deliver */
+ h->omsg[p++] = (more ? 4 : 0) + ((h->udhl > 0) ? 0x40 : 0);
+ p += packaddress (h->omsg + p, h->oa);
+ h->omsg[p++] = h->pid;
+ h->omsg[p++] = h->dcs;
+ packdate (h->omsg + p, h->scts);
+ p += 7;
+ p += packsms (h->dcs, h->omsg + p, h->udhl, h->udh, h->udl, h->ud);
+ } else { /* submit */
+ h->omsg[p++] =
+ 0x01 + (more ? 4 : 0) + (h->srr ? 0x20 : 0) + (h->rp ? 0x80 : 0) + (h->vp ? 0x10 : 0) + (h->udhi ? 0x40 : 0);
+ if (h->mr < 0)
+ h->mr = message_ref++;
+ h->omsg[p++] = h->mr;
+ p += packaddress (h->omsg + p, h->da);
+ h->omsg[p++] = h->pid;
+ h->omsg[p++] = h->dcs;
+ if (h->vp) { /* relative VP */
+ if (h->vp < 720)
+ h->omsg[p++] = (h->vp + 4) / 5 - 1;
+ else if (h->vp < 1440)
+ h->omsg[p++] = (h->vp - 720 + 29) / 30 + 143;
+ else if (h->vp < 43200)
+ h->omsg[p++] = (h->vp + 1439) / 1440 + 166;
+ else if (h->vp < 635040)
+ h->omsg[p++] = (h->vp + 10079) / 10080 + 192;
+ else
+ h->omsg[p++] = 255; /* max */
+ }
+ p += packsms (h->dcs, h->omsg + p, h->udhl, h->udh, h->udl, h->ud);
+ }
+ h->omsg[1] = p - 2;
+ sms_messagetx (h);
+ } else { /* no message */
+ h->omsg[0] = 0x94; /* SMS_REL */
+ h->omsg[1] = 0;
+ sms_messagetx (h);
+ }
+}
+
+static void sms_debug (char *dir, unsigned char *msg)
+{
+ char txt[259 * 3 + 1],
+ *p = txt; /* always long enough */
+ int n = msg[1] + 3,
+ q = 0;
+ while (q < n && q < 30) {
+ sprintf (p, " %02X", msg[q++]);
+ p += 3;
+ }
+ if (q < n)
+ sprintf (p, "...");
+ if (option_verbose > 2)
+ ast_verbose (VERBOSE_PREFIX_3 "SMS %s%s\n", dir, txt);
+}
+
+static void sms_messagerx(sms_t * h)
+{
+ sms_debug ("RX", h->imsg);
+ /* testing */
+ switch (h->imsg[0]) {
+ case 0x91: /* SMS_DATA */
+ {
+ unsigned char cause = sms_handleincoming (h);
+ if (!cause) {
+ sms_log (h, 'Y');
+ h->omsg[0] = 0x95; /* SMS_ACK */
+ h->omsg[1] = 0x02;
+ h->omsg[2] = 0x00; /* deliver report */
+ h->omsg[3] = 0x00; /* no parameters */
+ } else { /* NACK */
+ sms_log (h, 'N');
+ h->omsg[0] = 0x96; /* SMS_NACK */
+ h->omsg[1] = 3;
+ h->omsg[2] = 0; /* delivery report */
+ h->omsg[3] = cause; /* cause */
+ h->omsg[4] = 0; /* no parameters */
+ }
+ sms_messagetx (h);
+ }
+ break;
+ case 0x92: /* SMS_ERROR */
+ h->err = 1;
+ sms_messagetx (h); /* send whatever we sent again */
+ break;
+ case 0x93: /* SMS_EST */
+ sms_nextoutgoing (h);
+ break;
+ case 0x94: /* SMS_REL */
+ h->hangup = 1; /* hangup */
+ break;
+ case 0x95: /* SMS_ACK */
+ sms_log (h, 'Y');
+ sms_nextoutgoing (h);
+ break;
+ case 0x96: /* SMS_NACK */
+ h->err = 1;
+ sms_log (h, 'N');
+ sms_nextoutgoing (h);
+ break;
+ default: /* Unknown */
+ h->omsg[0] = 0x92; /* SMS_ERROR */
+ h->omsg[1] = 1;
+ h->omsg[2] = 3; /* unknown message type; */
+ sms_messagetx (h);
+ break;
+ }
+}
+
+static void sms_messagetx(sms_t * h)
+{
+ unsigned char c = 0, p;
+ for (p = 0; p < h->omsg[1] + 2; p++)
+ c += h->omsg[p];
+ h->omsg[h->omsg[1] + 2] = 0 - c;
+ sms_debug ("TX", h->omsg);
+ h->obyte = 1;
+ h->opause = 200;
+ if (h->omsg[0] == 0x93)
+ h->opause = 2400; /* initial message delay 300ms (for BT) */
+ h->obytep = 0;
+ h->obitp = 0;
+ h->osync = 80;
+ h->obyten = h->omsg[1] + 3;
+}
+
+static int sms_generate (struct ast_channel *chan, void *data, int len, int samples)
+{
+ struct ast_frame f = { 0 };
+#define MAXSAMPLES (800)
+#ifdef OUTALAW
+ unsigned char *buf;
+#else
+ short *buf;
+#endif
+#define SAMPLE2LEN sizeof(*buf)
+ sms_t *h = data;
+ int i;
+
+ if (samples > MAXSAMPLES) {
+ ast_log (LOG_WARNING, "Only doing %d samples (%d requested)\n",
+ MAXSAMPLES, samples);
+ samples = MAXSAMPLES;
+ }
+ len = samples * SAMPLE2LEN + AST_FRIENDLY_OFFSET;
+ buf = alloca(len);
+
+ f.frametype = AST_FRAME_VOICE;
+#ifdef OUTALAW
+ f.subclass = AST_FORMAT_ALAW;
+#else
+ f.subclass = AST_FORMAT_SLINEAR;
+#endif
+ f.datalen = samples * SAMPLE2LEN;
+ f.offset = AST_FRIENDLY_OFFSET;
+ f.mallocd = 0;
+ f.data = buf;
+ f.samples = samples;
+ f.src = "app_sms";
+ /* create a buffer containing the digital sms pattern */
+ for (i = 0; i < samples; i++) {
+#ifdef OUTALAW
+ buf[i] = wavea[0];
+#else
+ buf[i] = wave[0];
+#endif
+ if (h->opause)
+ h->opause--;
+ else if (h->obyten || h->osync) { /* sending data */
+#ifdef OUTALAW
+ buf[i] = wavea[h->ophase];
+#else
+ buf[i] = wave[h->ophase];
+#endif
+ if ((h->ophase += ((h->obyte & 1) ? 13 : 21)) >= 80)
+ h->ophase -= 80;
+ if ((h->ophasep += 12) >= 80) { /* next bit */
+ h->ophasep -= 80;
+ if (h->osync)
+ h->osync--; /* sending sync bits */
+ else {
+ h->obyte >>= 1;
+ h->obitp++;
+ if (h->obitp == 1)
+ h->obyte = 0; /* start bit; */
+ else if (h->obitp == 2)
+ h->obyte = h->omsg[h->obytep];
+ else if (h->obitp == 10) {
+ h->obyte = 1; /* stop bit */
+ h->obitp = 0;
+ h->obytep++;
+ if (h->obytep == h->obyten) {
+ h->obytep = h->obyten = 0; /* sent */
+ h->osync = 10; /* trailing marks */
+ }
+ }
+ }
+ }
+ }
+ }
+ if (ast_write (chan, &f) < 0) {
+ ast_log (LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror (errno));
+ return -1;
+ }
+ return 0;
+#undef SAMPLE2LEN
+#undef MAXSAMPLES
+}
+
+static void sms_process (sms_t * h, int samples, signed short *data)
+{
+ if (h->obyten || h->osync)
+ return; /* sending */
+ while (samples--) {
+ unsigned long long m0, m1;
+ if (abs (*data) > h->imag)
+ h->imag = abs (*data);
+ else
+ h->imag = h->imag * 7 / 8;
+ if (h->imag > 500) {
+ h->idle = 0;
+ h->ims0 = (h->ims0 * 6 + *data * wave[h->ips0]) / 7;
+ h->imc0 = (h->imc0 * 6 + *data * wave[h->ipc0]) / 7;
+ h->ims1 = (h->ims1 * 6 + *data * wave[h->ips1]) / 7;
+ h->imc1 = (h->imc1 * 6 + *data * wave[h->ipc1]) / 7;
+ m0 = h->ims0 * h->ims0 + h->imc0 * h->imc0;
+ m1 = h->ims1 * h->ims1 + h->imc1 * h->imc1;
+ if ((h->ips0 += 21) >= 80)
+ h->ips0 -= 80;
+ if ((h->ipc0 += 21) >= 80)
+ h->ipc0 -= 80;
+ if ((h->ips1 += 13) >= 80)
+ h->ips1 -= 80;
+ if ((h->ipc1 += 13) >= 80)
+ h->ipc1 -= 80;
+ {
+ char bit;
+ h->ibith <<= 1;
+ if (m1 > m0)
+ h->ibith |= 1;
+ if (h->ibith & 8)
+ h->ibitt--;
+ if (h->ibith & 1)
+ h->ibitt++;
+ bit = ((h->ibitt > 1) ? 1 : 0);
+ if (bit != h->ibitl)
+ h->ibitc = 1;
+ else
+ h->ibitc++;
+ h->ibitl = bit;
+ if (!h->ibitn && h->ibitc == 4 && !bit) {
+ h->ibitn = 1;
+ h->iphasep = 0;
+ }
+ if (bit && h->ibitc == 200) { /* sync, restart message */
+ h->ierr = h->ibitn = h->ibytep = h->ibytec = 0;
+ }
+ if (h->ibitn) {
+ h->iphasep += 12;
+ if (h->iphasep >= 80) { /* next bit */
+ h->iphasep -= 80;
+ if (h->ibitn++ == 9) { /* end of byte */
+ if (!bit) /* bad stop bit */
+ h->ierr = 0xFF; /* unknown error */
+ else {
+ if (h->ibytep < sizeof (h->imsg)) {
+ h->imsg[h->ibytep] = h->ibytev;
+ h->ibytec += h->ibytev;
+ h->ibytep++;
+ } else if (h->ibytep == sizeof (h->imsg))
+ h->ierr = 2; /* bad message length */
+ if (h->ibytep > 1 && h->ibytep == 3 + h->imsg[1] && !h->ierr) {
+ if (!h->ibytec)
+ sms_messagerx (h);
+ else
+ h->ierr = 1; /* bad checksum */
+ }
+ }
+ h->ibitn = 0;
+ }
+ h->ibytev = (h->ibytev >> 1) + (bit ? 0x80 : 0);
+ }
+ }
+ }
+ } else { /* lost carrier */
+ if (h->idle++ == 80000) { /* nothing happening */
+ ast_log (LOG_EVENT, "No data, hanging up\n");
+ h->hangup = 1;
+ h->err = 1;
+ }
+ if (h->ierr) { /* error */
+ h->err = 1;
+ h->omsg[0] = 0x92; /* error */
+ h->omsg[1] = 1;
+ h->omsg[2] = h->ierr;
+ sms_messagetx (h); /* send error */
+ }
+ h->ierr = h->ibitn = h->ibytep = h->ibytec = 0;
+ }
+ data++;
+ }
+}
+
+static struct ast_generator smsgen = {
+ alloc:sms_alloc,
+ release:sms_release,
+ generate:sms_generate,
+};
+
+static int sms_exec (struct ast_channel *chan, void *data)
+{
+ int res = -1;
+ struct ast_module_user *u;
+ struct ast_frame *f;
+ sms_t h = { 0 };
+
+ u = ast_module_user_add(chan);
+
+ h.ipc0 = h.ipc1 = 20; /* phase for cosine */
+ h.dcs = 0xF1; /* default */
+ if (!data) {
+ ast_log (LOG_ERROR, "Requires queue name at least\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ if (chan->cid.cid_num)
+ ast_copy_string (h.cli, chan->cid.cid_num, sizeof (h.cli));
+
+ {
+ unsigned char *p;
+ unsigned char *d = data,
+ answer = 0;
+ if (!*d || *d == '|') {
+ ast_log (LOG_ERROR, "Requires queue name\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+ for (p = d; *p && *p != '|'; p++);
+ if (p - d >= sizeof (h.queue)) {
+ ast_log (LOG_ERROR, "Queue name too long\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+ strncpy(h.queue, (char *)d, p - d);
+ if (*p == '|')
+ p++;
+ d = p;
+ for (p = (unsigned char *)h.queue; *p; p++)
+ if (!isalnum (*p))
+ *p = '-'; /* make very safe for filenames */
+ while (*d && *d != '|') {
+ switch (*d) {
+ case 'a': /* we have to send the initial FSK sequence */
+ answer = 1;
+ break;
+ case 's': /* we are acting as a service centre talking to a phone */
+ h.smsc = 1;
+ break;
+ /* the following apply if there is an arg3/4 and apply to the created message file */
+ case 'r':
+ h.srr = 1;
+ break;
+ case 'o':
+ h.dcs |= 4; /* octets */
+ break;
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7': /* set the pid for saved local message */
+ h.pid = 0x40 + (*d & 0xF);
+ break;
+ }
+ d++;
+ }
+ if (*d == '|') {
+ /* submitting a message, not taking call. */
+ /* deprecated, use smsq instead */
+ d++;
+ h.scts = time (0);
+ for (p = d; *p && *p != '|'; p++);
+ if (*p)
+ *p++ = 0;
+ if (strlen ((char *)d) >= sizeof (h.oa)) {
+ ast_log (LOG_ERROR, "Address too long %s\n", d);
+ return 0;
+ }
+ if (h.smsc) {
+ ast_copy_string (h.oa, (char *)d, sizeof (h.oa));
+ } else {
+ ast_copy_string (h.da, (char *)d, sizeof (h.da));
+ }
+ if (!h.smsc)
+ ast_copy_string (h.oa, h.cli, sizeof (h.oa));
+ d = p;
+ h.udl = 0;
+ while (*p && h.udl < SMSLEN)
+ h.ud[h.udl++] = utf8decode(&p);
+ if (is7bit (h.dcs) && packsms7 (0, h.udhl, h.udh, h.udl, h.ud) < 0)
+ ast_log (LOG_WARNING, "Invalid 7 bit GSM data\n");
+ if (is8bit (h.dcs) && packsms8 (0, h.udhl, h.udh, h.udl, h.ud) < 0)
+ ast_log (LOG_WARNING, "Invalid 8 bit data\n");
+ if (is16bit (h.dcs) && packsms16 (0, h.udhl, h.udh, h.udl, h.ud) < 0)
+ ast_log (LOG_WARNING, "Invalid 16 bit data\n");
+ h.rx = 0; /* sent message */
+ h.mr = -1;
+ sms_writefile (&h);
+ ast_module_user_remove(u);
+ return 0;
+ }
+
+ if (answer) {
+ /* set up SMS_EST initial message */
+ h.omsg[0] = 0x93;
+ h.omsg[1] = 0;
+ sms_messagetx (&h);
+ }
+ }
+
+ if (chan->_state != AST_STATE_UP)
+ ast_answer (chan);
+
+#ifdef OUTALAW
+ res = ast_set_write_format (chan, AST_FORMAT_ALAW);
+#else
+ res = ast_set_write_format (chan, AST_FORMAT_SLINEAR);
+#endif
+ if (res >= 0)
+ res = ast_set_read_format (chan, AST_FORMAT_SLINEAR);
+ if (res < 0) {
+ ast_log (LOG_ERROR, "Unable to set to linear mode, giving up\n");
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ if (ast_activate_generator (chan, &smsgen, &h) < 0) {
+ ast_log (LOG_ERROR, "Failed to activate generator on '%s'\n", chan->name);
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ /* Do our thing here */
+ while (ast_waitfor (chan, -1) > -1 && !h.hangup)
+ {
+ f = ast_read (chan);
+ if (!f)
+ break;
+ if (f->frametype == AST_FRAME_VOICE) {
+ sms_process (&h, f->samples, f->data);
+ }
+
+ ast_frfree (f);
+ }
+
+ sms_log (&h, '?'); /* log incomplete message */
+
+ ast_module_user_remove(u);
+ return (h.err);
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application (app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+#ifdef OUTALAW
+ {
+ int p;
+ for (p = 0; p < 80; p++)
+ wavea[p] = AST_LIN2A (wave[p]);
+ }
+#endif
+ snprintf (log_file, sizeof (log_file), "%s/sms", ast_config_AST_LOG_DIR);
+ snprintf (spool_dir, sizeof (spool_dir), "%s/sms", ast_config_AST_SPOOL_DIR);
+ return ast_register_application (app, sms_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "SMS/PSTN handler");
diff --git a/apps/app_softhangup.c b/apps/app_softhangup.c
new file mode 100644
index 000000000..018edc07d
--- /dev/null
+++ b/apps/app_softhangup.c
@@ -0,0 +1,121 @@
+/*
+ * 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 SoftHangup application
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/lock.h"
+
+static char *synopsis = "Soft Hangup Application";
+
+static char *desc = " SoftHangup(Technology/resource|options)\n"
+"Hangs up the requested channel. If there are no channels to hangup,\n"
+"the application will report it.\n"
+"- 'options' may contain the following letter:\n"
+" 'a' : hang up all channels on a specified device instead of a single resource\n";
+
+static char *app = "SoftHangup";
+
+
+static int softhangup_exec(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *u;
+ struct ast_channel *c=NULL;
+ char *options, *cut, *cdata, *match;
+ char name[AST_CHANNEL_NAME] = "";
+ int all = 0;
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "SoftHangup requires an argument (Technology/resource)\n");
+ return 0;
+ }
+
+ u = ast_module_user_add(chan);
+
+ cdata = ast_strdupa(data);
+ match = strsep(&cdata, "|");
+ options = strsep(&cdata, "|");
+ all = options && strchr(options,'a');
+ c = ast_channel_walk_locked(NULL);
+ while (c) {
+ ast_copy_string(name, c->name, sizeof(name));
+ ast_mutex_unlock(&c->lock);
+ /* XXX watch out, i think it is wrong to access c-> after unlocking! */
+ if (all) {
+ /* CAPI is set up like CAPI[foo/bar]/clcnt */
+ if (!strcmp(c->tech->type, "CAPI"))
+ cut = strrchr(name,'/');
+ /* Basically everything else is Foo/Bar-Z */
+ else
+ cut = strchr(name,'-');
+ /* Get rid of what we've cut */
+ if (cut)
+ *cut = 0;
+ }
+ if (!strcasecmp(name, match)) {
+ ast_log(LOG_WARNING, "Soft hanging %s up.\n",c->name);
+ ast_softhangup(c, AST_SOFTHANGUP_EXPLICIT);
+ if(!all)
+ break;
+ }
+ c = ast_channel_walk_locked(c);
+ }
+
+ ast_module_user_remove(u);
+
+ return 0;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, softhangup_exec, synopsis, desc);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Hangs up the requested channel");
diff --git a/apps/app_speech_utils.c b/apps/app_speech_utils.c
new file mode 100644
index 000000000..702e9348f
--- /dev/null
+++ b/apps/app_speech_utils.c
@@ -0,0 +1,875 @@
+/*
+ * 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 Speech Recognition Utility Applications
+ *
+ * \author Joshua Colp <jcolp@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#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/module.h"
+#include "asterisk/lock.h"
+#include "asterisk/app.h"
+#include "asterisk/speech.h"
+
+/* Descriptions for each application */
+static char *speechcreate_descrip =
+"SpeechCreate(engine name)\n"
+"This application creates information to be used by all the other applications. It must be called before doing any speech recognition activities such as activating a grammar.\n"
+"It takes the engine name to use as the argument, if not specified the default engine will be used.\n";
+
+static char *speechactivategrammar_descrip =
+"SpeechActivateGrammar(Grammar Name)\n"
+"This activates the specified grammar to be recognized by the engine. A grammar tells the speech recognition engine what to recognize, \n"
+ "and how to portray it back to you in the dialplan. The grammar name is the only argument to this application.\n";
+
+static char *speechstart_descrip =
+"SpeechStart()\n"
+ "Tell the speech recognition engine that it should start trying to get results from audio being fed to it. This has no arguments.\n";
+
+static char *speechbackground_descrip =
+"SpeechBackground(Sound File|Timeout)\n"
+"This application plays a sound file and waits for the person to speak. Once they start speaking playback of the file stops, and silence is heard.\n"
+"Once they stop talking the processing sound is played to indicate the speech recognition engine is working.\n"
+"Once results are available the application returns and results (score and text) are available using dialplan functions.\n"
+"The first text and score are ${SPEECH_TEXT(0)} AND ${SPEECH_SCORE(0)} while the second are ${SPEECH_TEXT(1)} and ${SPEECH_SCORE(1)}.\n"
+"The first argument is the sound file and the second is the timeout. Note the timeout will only start once the sound file has stopped playing.\n";
+
+static char *speechdeactivategrammar_descrip =
+"SpeechDeactivateGrammar(Grammar Name)\n"
+ "This deactivates the specified grammar so that it is no longer recognized. The only argument is the grammar name to deactivate.\n";
+
+static char *speechprocessingsound_descrip =
+"SpeechProcessingSound(Sound File)\n"
+"This changes the processing sound that SpeechBackground plays back when the speech recognition engine is processing and working to get results.\n"
+ "It takes the sound file as the only argument.\n";
+
+static char *speechdestroy_descrip =
+"SpeechDestroy()\n"
+"This destroys the information used by all the other speech recognition applications.\n"
+"If you call this application but end up wanting to recognize more speech, you must call SpeechCreate\n"
+ "again before calling any other application. It takes no arguments.\n";
+
+static char *speechload_descrip =
+"SpeechLoadGrammar(Grammar Name|Path)\n"
+"Load a grammar only on the channel, not globally.\n"
+"It takes the grammar name as first argument and path as second.\n";
+
+static char *speechunload_descrip =
+"SpeechUnloadGrammar(Grammar Name)\n"
+"Unload a grammar. It takes the grammar name as the only argument.\n";
+
+/*! \brief Helper function used by datastores to destroy the speech structure upon hangup */
+static void destroy_callback(void *data)
+{
+ struct ast_speech *speech = (struct ast_speech*)data;
+
+ if (speech == NULL) {
+ return;
+ }
+
+ /* Deallocate now */
+ ast_speech_destroy(speech);
+
+ return;
+}
+
+/*! \brief Static structure for datastore information */
+static const struct ast_datastore_info speech_datastore = {
+ .type = "speech",
+ .destroy = destroy_callback
+};
+
+/*! \brief Helper function used to find the speech structure attached to a channel */
+static struct ast_speech *find_speech(struct ast_channel *chan)
+{
+ struct ast_speech *speech = NULL;
+ struct ast_datastore *datastore = NULL;
+
+ datastore = ast_channel_datastore_find(chan, &speech_datastore, NULL);
+ if (datastore == NULL) {
+ return NULL;
+ }
+ speech = datastore->data;
+
+ return speech;
+}
+
+/* Helper function to find a specific speech recognition result by number and nbest alternative */
+static struct ast_speech_result *find_result(struct ast_speech_result *results, char *result_num)
+{
+ struct ast_speech_result *result = results;
+ char *tmp = NULL;
+ int nbest_num = 0, wanted_num = 0, i = 0;
+
+ if (!result)
+ return NULL;
+
+ if ((tmp = strchr(result_num, '/'))) {
+ *tmp++ = '\0';
+ nbest_num = atoi(result_num);
+ wanted_num = atoi(tmp);
+ } else {
+ wanted_num = atoi(result_num);
+ }
+
+ do {
+ if (result->nbest_num != nbest_num)
+ continue;
+ if (i == wanted_num)
+ break;
+ i++;
+ } while ((result = result->next));
+
+ return result;
+}
+
+/*! \brief SPEECH_SCORE() Dialplan Function */
+static int speech_score(struct ast_channel *chan, char *cmd, char *data,
+ char *buf, size_t len)
+{
+ struct ast_speech_result *result = NULL;
+ struct ast_speech *speech = find_speech(chan);
+ char tmp[128] = "";
+
+ if (data == NULL || speech == NULL || !(result = find_result(speech->results, data)))
+ return -1;
+
+ snprintf(tmp, sizeof(tmp), "%d", result->score);
+
+ ast_copy_string(buf, tmp, len);
+
+ return 0;
+}
+
+static struct ast_custom_function speech_score_function = {
+ .name = "SPEECH_SCORE",
+ .synopsis = "Gets the confidence score of a result.",
+ .syntax = "SPEECH_SCORE([nbest number/]result number)",
+ .desc =
+ "Gets the confidence score of a result.\n",
+ .read = speech_score,
+ .write = NULL,
+};
+
+/*! \brief SPEECH_TEXT() Dialplan Function */
+static int speech_text(struct ast_channel *chan, char *cmd, char *data,
+ char *buf, size_t len)
+{
+ struct ast_speech_result *result = NULL;
+ struct ast_speech *speech = find_speech(chan);
+
+ if (data == NULL || speech == NULL || !(result = find_result(speech->results, data)))
+ return -1;
+
+ if (result->text != NULL) {
+ ast_copy_string(buf, result->text, len);
+ } else {
+ buf[0] = '\0';
+ }
+
+ return 0;
+}
+
+static struct ast_custom_function speech_text_function = {
+ .name = "SPEECH_TEXT",
+ .synopsis = "Gets the recognized text of a result.",
+ .syntax = "SPEECH_TEXT([nbest number/]result number)",
+ .desc =
+ "Gets the recognized text of a result.\n",
+ .read = speech_text,
+ .write = NULL,
+};
+
+/*! \brief SPEECH_GRAMMAR() Dialplan Function */
+static int speech_grammar(struct ast_channel *chan, char *cmd, char *data,
+ char *buf, size_t len)
+{
+ struct ast_speech_result *result = NULL;
+ struct ast_speech *speech = find_speech(chan);
+
+ if (data == NULL || speech == NULL || !(result = find_result(speech->results, data)))
+ return -1;
+
+ if (result->grammar != NULL) {
+ ast_copy_string(buf, result->grammar, len);
+ } else {
+ buf[0] = '\0';
+ }
+
+ return 0;
+}
+
+static struct ast_custom_function speech_grammar_function = {
+ .name = "SPEECH_GRAMMAR",
+ .synopsis = "Gets the matched grammar of a result if available.",
+ .syntax = "SPEECH_GRAMMAR([nbest number/]result number)",
+ .desc =
+ "Gets the matched grammar of a result if available.\n",
+ .read = speech_grammar,
+ .write = NULL,
+};
+
+/*! \brief SPEECH_ENGINE() Dialplan Function */
+static int speech_engine_write(struct ast_channel *chan, char *cmd, char *data, const char *value)
+{
+ struct ast_speech *speech = find_speech(chan);
+
+ if (data == NULL || speech == NULL)
+ return -1;
+
+ ast_speech_change(speech, data, value);
+
+ return 0;
+}
+
+static struct ast_custom_function speech_engine_function = {
+ .name = "SPEECH_ENGINE",
+ .synopsis = "Change a speech engine specific attribute.",
+ .syntax = "SPEECH_ENGINE(name)=value",
+ .desc =
+ "Changes a speech engine specific attribute.\n",
+ .read = NULL,
+ .write = speech_engine_write,
+};
+
+/*! \brief SPEECH_RESULTS_TYPE() Dialplan Function */
+static int speech_results_type_write(struct ast_channel *chan, char *cmd, char *data, const char *value)
+{
+ struct ast_speech *speech = find_speech(chan);
+
+ if (data == NULL || speech == NULL)
+ return -1;
+
+ if (!strcasecmp(value, "normal"))
+ ast_speech_change_results_type(speech, AST_SPEECH_RESULTS_TYPE_NORMAL);
+ else if (!strcasecmp(value, "nbest"))
+ ast_speech_change_results_type(speech, AST_SPEECH_RESULTS_TYPE_NBEST);
+
+ return 0;
+}
+
+static struct ast_custom_function speech_results_type_function = {
+ .name = "SPEECH_RESULTS_TYPE",
+ .synopsis = "Sets the type of results that will be returned.",
+ .syntax = "SPEECH_RESULTS_TYPE()=results type",
+ .desc =
+ "Sets the type of results that will be returned. Valid options are normal or nbest.",
+ .read = NULL,
+ .write = speech_results_type_write,
+};
+
+/*! \brief SPEECH() Dialplan Function */
+static int speech_read(struct ast_channel *chan, char *cmd, char *data,
+ char *buf, size_t len)
+{
+ int results = 0;
+ struct ast_speech_result *result = NULL;
+ struct ast_speech *speech = find_speech(chan);
+ char tmp[128] = "";
+
+ /* Now go for the various options */
+ if (!strcasecmp(data, "status")) {
+ if (speech != NULL)
+ ast_copy_string(buf, "1", len);
+ else
+ ast_copy_string(buf, "0", len);
+ return 0;
+ }
+
+ /* Make sure we have a speech structure for everything else */
+ if (speech == NULL) {
+ return -1;
+ }
+
+ /* Check to see if they are checking for silence */
+ if (!strcasecmp(data, "spoke")) {
+ if (ast_test_flag(speech, AST_SPEECH_SPOKE))
+ ast_copy_string(buf, "1", len);
+ else
+ ast_copy_string(buf, "0", len);
+ } else if (!strcasecmp(data, "results")) {
+ /* Count number of results */
+ result = speech->results;
+ while (result) {
+ results++;
+ result = result->next;
+ }
+ snprintf(tmp, sizeof(tmp), "%d", results);
+ ast_copy_string(buf, tmp, len);
+ } else {
+ buf[0] = '\0';
+ }
+
+ return 0;
+}
+
+static struct ast_custom_function speech_function = {
+ .name = "SPEECH",
+ .synopsis = "Gets information about speech recognition results.",
+ .syntax = "SPEECH(argument)",
+ .desc =
+ "Gets information about speech recognition results.\n"
+ "status: Returns 1 upon speech object existing, or 0 if not\n"
+ "spoke: Returns 1 if spoker spoke, or 0 if not\n"
+ "results: Returns number of results that were recognized\n",
+ .read = speech_read,
+ .write = NULL,
+};
+
+
+
+/*! \brief SpeechCreate() Dialplan Application */
+static int speech_create(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *u = NULL;
+ struct ast_speech *speech = NULL;
+ struct ast_datastore *datastore = NULL;
+
+ u = ast_module_user_add(chan);
+
+ /* Request a speech object */
+ speech = ast_speech_new(data, AST_FORMAT_SLINEAR);
+ if (speech == NULL) {
+ /* Not available */
+ pbx_builtin_setvar_helper(chan, "ERROR", "1");
+ ast_module_user_remove(u);
+ return 0;
+ }
+
+ datastore = ast_channel_datastore_alloc(&speech_datastore, NULL);
+ if (datastore == NULL) {
+ ast_speech_destroy(speech);
+ pbx_builtin_setvar_helper(chan, "ERROR", "1");
+ ast_module_user_remove(u);
+ return 0;
+ }
+ datastore->data = speech;
+ ast_channel_datastore_add(chan, datastore);
+
+ ast_module_user_remove(u);
+
+ return 0;
+}
+
+/*! \brief SpeechLoadGrammar(Grammar Name|Path) Dialplan Application */
+static int speech_load(struct ast_channel *chan, void *data)
+{
+ int res = 0, argc = 0;
+ struct ast_module_user *u = NULL;
+ struct ast_speech *speech = find_speech(chan);
+ char *argv[2], *args = NULL, *name = NULL, *path = NULL;
+
+ args = ast_strdupa(data);
+
+ u = ast_module_user_add(chan);
+
+ if (speech == NULL) {
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ /* Parse out arguments */
+ argc = ast_app_separate_args(args, '|', argv, sizeof(argv) / sizeof(argv[0]));
+ if (argc != 2) {
+ ast_module_user_remove(u);
+ return -1;
+ }
+ name = argv[0];
+ path = argv[1];
+
+ /* Load the grammar locally on the object */
+ res = ast_speech_grammar_load(speech, name, path);
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+/*! \brief SpeechUnloadGrammar(Grammar Name) Dialplan Application */
+static int speech_unload(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ struct ast_module_user *u = NULL;
+ struct ast_speech *speech = find_speech(chan);
+
+ u = ast_module_user_add(chan);
+
+ if (speech == NULL) {
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ /* Unload the grammar */
+ res = ast_speech_grammar_unload(speech, data);
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+/*! \brief SpeechDeactivateGrammar(Grammar Name) Dialplan Application */
+static int speech_deactivate(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ struct ast_module_user *u = NULL;
+ struct ast_speech *speech = find_speech(chan);
+
+ u = ast_module_user_add(chan);
+
+ if (speech == NULL) {
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ /* Deactivate the grammar on the speech object */
+ res = ast_speech_grammar_deactivate(speech, data);
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+/*! \brief SpeechActivateGrammar(Grammar Name) Dialplan Application */
+static int speech_activate(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ struct ast_module_user *u = NULL;
+ struct ast_speech *speech = find_speech(chan);
+
+ u = ast_module_user_add(chan);
+
+ if (speech == NULL) {
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ /* Activate the grammar on the speech object */
+ res = ast_speech_grammar_activate(speech, data);
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+/*! \brief SpeechStart() Dialplan Application */
+static int speech_start(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ struct ast_module_user *u = NULL;
+ struct ast_speech *speech = find_speech(chan);
+
+ u = ast_module_user_add(chan);
+
+ if (speech == NULL) {
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ ast_speech_start(speech);
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+/*! \brief SpeechProcessingSound(Sound File) Dialplan Application */
+static int speech_processing_sound(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ struct ast_module_user *u = NULL;
+ struct ast_speech *speech = find_speech(chan);
+
+ u = ast_module_user_add(chan);
+
+ if (speech == NULL) {
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ if (speech->processing_sound != NULL) {
+ free(speech->processing_sound);
+ speech->processing_sound = NULL;
+ }
+
+ speech->processing_sound = strdup(data);
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+/*! \brief Helper function used by speech_background to playback a soundfile */
+static int speech_streamfile(struct ast_channel *chan, const char *filename, const char *preflang)
+{
+ struct ast_filestream *fs = NULL;
+
+ if (!(fs = ast_openstream(chan, filename, preflang)))
+ return -1;
+
+ if (ast_applystream(chan, fs))
+ return -1;
+
+ ast_playstream(fs);
+
+ return 0;
+}
+
+/*! \brief SpeechBackground(Sound File|Timeout) Dialplan Application */
+static int speech_background(struct ast_channel *chan, void *data)
+{
+ unsigned int timeout = 0;
+ int res = 0, done = 0, argc = 0, started = 0, quieted = 0, max_dtmf_len = 0;
+ struct ast_module_user *u = NULL;
+ struct ast_speech *speech = find_speech(chan);
+ struct ast_frame *f = NULL;
+ int oldreadformat = AST_FORMAT_SLINEAR;
+ char dtmf[AST_MAX_EXTENSION] = "";
+ time_t start, current;
+ struct ast_datastore *datastore = NULL;
+ char *argv[2], *args = NULL, *filename_tmp = NULL, *filename = NULL, tmp[2] = "", dtmf_terminator = '#';
+ const char *tmp2 = NULL;
+
+ args = ast_strdupa(data);
+
+ u = ast_module_user_add(chan);
+
+ if (speech == NULL) {
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ /* If channel is not already answered, then answer it */
+ if (chan->_state != AST_STATE_UP && ast_answer(chan)) {
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ /* Record old read format */
+ oldreadformat = chan->readformat;
+
+ /* Change read format to be signed linear */
+ if (ast_set_read_format(chan, AST_FORMAT_SLINEAR)) {
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ /* Parse out options */
+ argc = ast_app_separate_args(args, '|', argv, sizeof(argv) / sizeof(argv[0]));
+ if (argc > 0) {
+ /* Yay sound file */
+ filename_tmp = ast_strdupa(argv[0]);
+ if (!ast_strlen_zero(argv[1])) {
+ if ((timeout = atoi(argv[1])) == 0)
+ timeout = -1;
+ } else
+ timeout = 0;
+ }
+
+ /* See if the maximum DTMF length variable is set... we use a variable in case they want to carry it through their entire dialplan */
+ if ((tmp2 = pbx_builtin_getvar_helper(chan, "SPEECH_DTMF_MAXLEN")) && !ast_strlen_zero(tmp2))
+ max_dtmf_len = atoi(tmp2);
+
+ /* See if a terminator is specified */
+ if ((tmp2 = pbx_builtin_getvar_helper(chan, "SPEECH_DTMF_TERMINATOR"))) {
+ if (ast_strlen_zero(tmp2))
+ dtmf_terminator = '\0';
+ else
+ dtmf_terminator = tmp2[0];
+ }
+
+ /* Before we go into waiting for stuff... make sure the structure is ready, if not - start it again */
+ if (speech->state == AST_SPEECH_STATE_NOT_READY || speech->state == AST_SPEECH_STATE_DONE) {
+ ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY);
+ ast_speech_start(speech);
+ }
+
+ /* Ensure no streams are currently running */
+ ast_stopstream(chan);
+
+ /* Okay it's streaming so go into a loop grabbing frames! */
+ while (done == 0) {
+ /* If the filename is null and stream is not running, start up a new sound file */
+ if (!quieted && (chan->streamid == -1 && chan->timingfunc == NULL) && (filename = strsep(&filename_tmp, "&"))) {
+ /* Discard old stream information */
+ ast_stopstream(chan);
+ /* Start new stream */
+ speech_streamfile(chan, filename, chan->language);
+ }
+
+ /* Run scheduled stuff */
+ ast_sched_runq(chan->sched);
+
+ /* Yay scheduling */
+ res = ast_sched_wait(chan->sched);
+ if (res < 0) {
+ res = 1000;
+ }
+
+ /* If there is a frame waiting, get it - if not - oh well */
+ if (ast_waitfor(chan, res) > 0) {
+ f = ast_read(chan);
+ if (f == NULL) {
+ /* The channel has hung up most likely */
+ done = 3;
+ break;
+ }
+ }
+
+ /* Do timeout check (shared between audio/dtmf) */
+ if ((!quieted || strlen(dtmf)) && started == 1) {
+ time(&current);
+ if ((current-start) >= timeout) {
+ done = 1;
+ if (f)
+ ast_frfree(f);
+ break;
+ }
+ }
+
+ /* Do checks on speech structure to see if it's changed */
+ ast_mutex_lock(&speech->lock);
+ if (ast_test_flag(speech, AST_SPEECH_QUIET)) {
+ if (chan->stream)
+ ast_stopstream(chan);
+ ast_clear_flag(speech, AST_SPEECH_QUIET);
+ quieted = 1;
+ }
+ /* Check state so we can see what to do */
+ switch (speech->state) {
+ case AST_SPEECH_STATE_READY:
+ /* If audio playback has stopped do a check for timeout purposes */
+ if (chan->streamid == -1 && chan->timingfunc == NULL)
+ ast_stopstream(chan);
+ if (!quieted && chan->stream == NULL && timeout && started == 0 && !filename_tmp) {
+ if (timeout == -1) {
+ done = 1;
+ if (f)
+ ast_frfree(f);
+ break;
+ }
+ time(&start);
+ started = 1;
+ }
+ /* Write audio frame out to speech engine if no DTMF has been received */
+ if (!strlen(dtmf) && f != NULL && f->frametype == AST_FRAME_VOICE) {
+ ast_speech_write(speech, f->data, f->datalen);
+ }
+ break;
+ case AST_SPEECH_STATE_WAIT:
+ /* Cue up waiting sound if not already playing */
+ if (!strlen(dtmf)) {
+ if (chan->stream == NULL) {
+ if (speech->processing_sound != NULL) {
+ if (strlen(speech->processing_sound) > 0 && strcasecmp(speech->processing_sound,"none")) {
+ speech_streamfile(chan, speech->processing_sound, chan->language);
+ }
+ }
+ } else if (chan->streamid == -1 && chan->timingfunc == NULL) {
+ ast_stopstream(chan);
+ if (speech->processing_sound != NULL) {
+ if (strlen(speech->processing_sound) > 0 && strcasecmp(speech->processing_sound,"none")) {
+ speech_streamfile(chan, speech->processing_sound, chan->language);
+ }
+ }
+ }
+ }
+ break;
+ case AST_SPEECH_STATE_DONE:
+ /* Now that we are done... let's switch back to not ready state */
+ ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY);
+ if (!strlen(dtmf)) {
+ /* Copy to speech structure the results, if available */
+ speech->results = ast_speech_results_get(speech);
+ /* Break out of our background too */
+ done = 1;
+ /* Stop audio playback */
+ if (chan->stream != NULL) {
+ ast_stopstream(chan);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ ast_mutex_unlock(&speech->lock);
+
+ /* Deal with other frame types */
+ if (f != NULL) {
+ /* Free the frame we received */
+ switch (f->frametype) {
+ case AST_FRAME_DTMF:
+ if (dtmf_terminator != '\0' && f->subclass == dtmf_terminator) {
+ done = 1;
+ } else {
+ if (chan->stream != NULL) {
+ ast_stopstream(chan);
+ }
+ if (!started) {
+ /* Change timeout to be 5 seconds for DTMF input */
+ timeout = (chan->pbx && chan->pbx->dtimeout) ? chan->pbx->dtimeout : 5;
+ started = 1;
+ }
+ time(&start);
+ snprintf(tmp, sizeof(tmp), "%c", f->subclass);
+ strncat(dtmf, tmp, sizeof(dtmf) - strlen(dtmf) - 1);
+ /* If the maximum length of the DTMF has been reached, stop now */
+ if (max_dtmf_len && strlen(dtmf) == max_dtmf_len)
+ done = 1;
+ }
+ break;
+ case AST_FRAME_CONTROL:
+ switch (f->subclass) {
+ case AST_CONTROL_HANGUP:
+ /* Since they hung up we should destroy the speech structure */
+ done = 3;
+ default:
+ break;
+ }
+ default:
+ break;
+ }
+ ast_frfree(f);
+ f = NULL;
+ }
+ }
+
+ if (strlen(dtmf)) {
+ /* We sort of make a results entry */
+ speech->results = ast_calloc(1, sizeof(*speech->results));
+ if (speech->results != NULL) {
+ ast_speech_dtmf(speech, dtmf);
+ speech->results->score = 1000;
+ speech->results->text = strdup(dtmf);
+ speech->results->grammar = strdup("dtmf");
+ }
+ ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY);
+ }
+
+ /* See if it was because they hung up */
+ if (done == 3) {
+ /* Destroy speech structure */
+ ast_speech_destroy(speech);
+ datastore = ast_channel_datastore_find(chan, &speech_datastore, NULL);
+ if (datastore != NULL) {
+ ast_channel_datastore_remove(chan, datastore);
+ }
+ } else {
+ /* Channel is okay so restore read format */
+ ast_set_read_format(chan, oldreadformat);
+ }
+
+ ast_module_user_remove(u);
+
+ return 0;
+}
+
+
+/*! \brief SpeechDestroy() Dialplan Application */
+static int speech_destroy(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ struct ast_module_user *u = NULL;
+ struct ast_speech *speech = find_speech(chan);
+ struct ast_datastore *datastore = NULL;
+
+ u = ast_module_user_add(chan);
+
+ if (speech == NULL) {
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ /* Destroy speech structure */
+ ast_speech_destroy(speech);
+
+ datastore = ast_channel_datastore_find(chan, &speech_datastore, NULL);
+ if (datastore != NULL) {
+ ast_channel_datastore_remove(chan, datastore);
+ }
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res = 0;
+
+ res = ast_unregister_application("SpeechCreate");
+ res |= ast_unregister_application("SpeechLoadGrammar");
+ res |= ast_unregister_application("SpeechUnloadGrammar");
+ res |= ast_unregister_application("SpeechActivateGrammar");
+ res |= ast_unregister_application("SpeechDeactivateGrammar");
+ res |= ast_unregister_application("SpeechStart");
+ res |= ast_unregister_application("SpeechBackground");
+ res |= ast_unregister_application("SpeechDestroy");
+ res |= ast_unregister_application("SpeechProcessingSound");
+ res |= ast_custom_function_unregister(&speech_function);
+ res |= ast_custom_function_unregister(&speech_score_function);
+ res |= ast_custom_function_unregister(&speech_text_function);
+ res |= ast_custom_function_unregister(&speech_grammar_function);
+ res |= ast_custom_function_unregister(&speech_engine_function);
+ res |= ast_custom_function_unregister(&speech_results_type_function);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ int res = 0;
+
+ res = ast_register_application("SpeechCreate", speech_create, "Create a Speech Structure", speechcreate_descrip);
+ res |= ast_register_application("SpeechLoadGrammar", speech_load, "Load a Grammar", speechload_descrip);
+ res |= ast_register_application("SpeechUnloadGrammar", speech_unload, "Unload a Grammar", speechunload_descrip);
+ res |= ast_register_application("SpeechActivateGrammar", speech_activate, "Activate a Grammar", speechactivategrammar_descrip);
+ res |= ast_register_application("SpeechDeactivateGrammar", speech_deactivate, "Deactivate a Grammar", speechdeactivategrammar_descrip);
+ res |= ast_register_application("SpeechStart", speech_start, "Start recognizing voice in the audio stream", speechstart_descrip);
+ res |= ast_register_application("SpeechBackground", speech_background, "Play a sound file and wait for speech to be recognized", speechbackground_descrip);
+ res |= ast_register_application("SpeechDestroy", speech_destroy, "End speech recognition", speechdestroy_descrip);
+ res |= ast_register_application("SpeechProcessingSound", speech_processing_sound, "Change background processing sound", speechprocessingsound_descrip);
+ res |= ast_custom_function_register(&speech_function);
+ res |= ast_custom_function_register(&speech_score_function);
+ res |= ast_custom_function_register(&speech_text_function);
+ res |= ast_custom_function_register(&speech_grammar_function);
+ res |= ast_custom_function_register(&speech_engine_function);
+ res |= ast_custom_function_register(&speech_results_type_function);
+
+ return res;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Dialplan Speech Applications");
diff --git a/apps/app_stack.c b/apps/app_stack.c
new file mode 100644
index 000000000..ace440a63
--- /dev/null
+++ b/apps/app_stack.c
@@ -0,0 +1,174 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (c) 2004-2006 Tilghman Lesher <app_stack_v002@the-tilghman.com>.
+ *
+ * This code is released by the author with no restrictions on usage.
+ *
+ * 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 Stack applications Gosub, Return, etc.
+ *
+ * \author Tilghman Lesher <app_stack_v002@the-tilghman.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "asterisk/options.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/chanvars.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/config.h"
+
+#define STACKVAR "~GOSUB~STACK~"
+
+
+static const char *app_gosub = "Gosub";
+static const char *app_gosubif = "GosubIf";
+static const char *app_return = "Return";
+static const char *app_pop = "StackPop";
+
+static const char *gosub_synopsis = "Jump to label, saving return address";
+static const char *gosubif_synopsis = "Conditionally jump to label, saving return address";
+static const char *return_synopsis = "Return from gosub routine";
+static const char *pop_synopsis = "Remove one address from gosub stack";
+
+static const char *gosub_descrip =
+"Gosub([[context|]exten|]priority)\n"
+" Jumps to the label specified, saving the return address.\n";
+static const char *gosubif_descrip =
+"GosubIf(condition?labeliftrue[:labeliffalse])\n"
+" If the condition is true, then jump to labeliftrue. If false, jumps to\n"
+"labeliffalse, if specified. In either case, a jump saves the return point\n"
+"in the dialplan, to be returned to with a Return.\n";
+static const char *return_descrip =
+"Return()\n"
+" Jumps to the last label on the stack, removing it.\n";
+static const char *pop_descrip =
+"StackPop()\n"
+" Removes last label on the stack, discarding it.\n";
+
+
+static int pop_exec(struct ast_channel *chan, void *data)
+{
+ pbx_builtin_setvar_helper(chan, STACKVAR, NULL);
+
+ return 0;
+}
+
+static int return_exec(struct ast_channel *chan, void *data)
+{
+ const char *label = pbx_builtin_getvar_helper(chan, STACKVAR);
+
+ if (ast_strlen_zero(label)) {
+ ast_log(LOG_ERROR, "Return without Gosub: stack is empty\n");
+ return -1;
+ } else if (ast_parseable_goto(chan, label)) {
+ ast_log(LOG_WARNING, "No next statement after Gosub?\n");
+ return -1;
+ }
+
+ pbx_builtin_setvar_helper(chan, STACKVAR, NULL);
+ return 0;
+}
+
+static int gosub_exec(struct ast_channel *chan, void *data)
+{
+ char newlabel[AST_MAX_EXTENSION * 2 + 3 + 11];
+ struct ast_module_user *u;
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_ERROR, "%s requires an argument: %s([[context|]exten|]priority)\n", app_gosub, app_gosub);
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+ snprintf(newlabel, sizeof(newlabel), "%s|%s|%d", chan->context, chan->exten, chan->priority + 1);
+
+ if (ast_parseable_goto(chan, data)) {
+ ast_module_user_remove(u);
+ return -1;
+ }
+
+ pbx_builtin_pushvar_helper(chan, STACKVAR, newlabel);
+ ast_module_user_remove(u);
+
+ return 0;
+}
+
+static int gosubif_exec(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *u;
+ char *condition="", *label1, *label2, *args;
+ int res=0;
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "GosubIf requires an argument\n");
+ return 0;
+ }
+
+ args = ast_strdupa(data);
+
+ u = ast_module_user_add(chan);
+
+ condition = strsep(&args, "?");
+ label1 = strsep(&args, ":");
+ label2 = args;
+
+ if (pbx_checkcondition(condition)) {
+ if (!ast_strlen_zero(label1)) {
+ res = gosub_exec(chan, label1);
+ }
+ } else if (!ast_strlen_zero(label2)) {
+ res = gosub_exec(chan, label2);
+ }
+
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int unload_module(void)
+{
+ ast_unregister_application(app_return);
+ ast_unregister_application(app_pop);
+ ast_unregister_application(app_gosubif);
+ ast_unregister_application(app_gosub);
+
+ ast_module_user_hangup_all();
+
+ return 0;
+}
+
+static int load_module(void)
+{
+ ast_register_application(app_pop, pop_exec, pop_synopsis, pop_descrip);
+ ast_register_application(app_return, return_exec, return_synopsis, return_descrip);
+ ast_register_application(app_gosubif, gosubif_exec, gosubif_synopsis, gosubif_descrip);
+ ast_register_application(app_gosub, gosub_exec, gosub_synopsis, gosub_descrip);
+
+ return 0;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Stack Routines");
diff --git a/apps/app_system.c b/apps/app_system.c
new file mode 100644
index 000000000..b60e11c5a
--- /dev/null
+++ b/apps/app_system.c
@@ -0,0 +1,162 @@
+/*
+ * 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 Execute arbitrary system commands
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/app.h"
+#include "asterisk/options.h"
+
+static char *app = "System";
+
+static char *app2 = "TrySystem";
+
+static char *synopsis = "Execute a system command";
+
+static char *synopsis2 = "Try executing a system command";
+
+static char *chanvar = "SYSTEMSTATUS";
+
+static char *descrip =
+" System(command): Executes a command by using system(). If the command\n"
+"fails, the console should report a fallthrough. \n"
+"Result of execution is returned in the SYSTEMSTATUS channel variable:\n"
+" FAILURE Could not execute the specified command\n"
+" SUCCESS Specified command successfully executed\n"
+"\n"
+"Old behaviour:\n"
+"If the command itself executes but is in error, and if there exists\n"
+"a priority n + 101, where 'n' is the priority of the current instance,\n"
+"then the channel will be setup to continue at that priority level.\n"
+"Note that this jump functionality has been deprecated and will only occur\n"
+"if the global priority jumping option is enabled in extensions.conf.\n";
+
+static char *descrip2 =
+" TrySystem(command): Executes a command by using system().\n"
+"on any situation.\n"
+"Result of execution is returned in the SYSTEMSTATUS channel variable:\n"
+" FAILURE Could not execute the specified command\n"
+" SUCCESS Specified command successfully executed\n"
+" APPERROR Specified command successfully executed, but returned error code\n"
+"\n"
+"Old behaviour:\nIf the command itself executes but is in error, and if\n"
+"there exists a priority n + 101, where 'n' is the priority of the current\n"
+"instance, then the channel will be setup to continue at that\n"
+"priority level. Otherwise, System will terminate.\n";
+
+
+static int system_exec_helper(struct ast_channel *chan, void *data, int failmode)
+{
+ int res=0;
+ struct ast_module_user *u;
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "System requires an argument(command)\n");
+ pbx_builtin_setvar_helper(chan, chanvar, "FAILURE");
+ return failmode;
+ }
+
+ u = ast_module_user_add(chan);
+
+ ast_autoservice_start(chan);
+
+ /* Do our thing here */
+ res = ast_safe_system((char *)data);
+ if ((res < 0) && (errno != ECHILD)) {
+ ast_log(LOG_WARNING, "Unable to execute '%s'\n", (char *)data);
+ pbx_builtin_setvar_helper(chan, chanvar, "FAILURE");
+ res = failmode;
+ } else if (res == 127) {
+ ast_log(LOG_WARNING, "Unable to execute '%s'\n", (char *)data);
+ pbx_builtin_setvar_helper(chan, chanvar, "FAILURE");
+ res = failmode;
+ } else {
+ if (res < 0)
+ res = 0;
+ if (ast_opt_priority_jumping && res)
+ ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
+
+ if (res != 0)
+ pbx_builtin_setvar_helper(chan, chanvar, "APPERROR");
+ else
+ pbx_builtin_setvar_helper(chan, chanvar, "SUCCESS");
+ res = 0;
+ }
+
+ ast_autoservice_stop(chan);
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int system_exec(struct ast_channel *chan, void *data)
+{
+ return system_exec_helper(chan, data, -1);
+}
+
+static int trysystem_exec(struct ast_channel *chan, void *data)
+{
+ return system_exec_helper(chan, data, 0);
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+ res |= ast_unregister_application(app2);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ int res;
+
+ res = ast_register_application(app2, trysystem_exec, synopsis2, descrip2);
+ res |= ast_register_application(app, system_exec, synopsis, descrip);
+
+ return res;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Generic System() application");
diff --git a/apps/app_talkdetect.c b/apps/app_talkdetect.c
new file mode 100644
index 000000000..79cbbd5d0
--- /dev/null
+++ b/apps/app_talkdetect.c
@@ -0,0 +1,227 @@
+/*
+ * 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 Playback a file with audio detect
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/utils.h"
+#include "asterisk/dsp.h"
+
+static char *app = "BackgroundDetect";
+
+static char *synopsis = "Background a file with talk detect";
+
+static char *descrip =
+" BackgroundDetect(filename[|sil[|min|[max]]]): Plays back a given\n"
+"filename, waiting for interruption from a given digit (the digit must\n"
+"start the beginning of a valid extension, or it will be ignored).\n"
+"During the playback of the file, audio is monitored in the receive\n"
+"direction, and if a period of non-silence which is greater than 'min' ms\n"
+"yet less than 'max' ms is followed by silence for at least 'sil' ms then\n"
+"the audio playback is aborted and processing jumps to the 'talk' extension\n"
+"if available. If unspecified, sil, min, and max default to 1000, 100, and\n"
+"infinity respectively.\n";
+
+
+static int background_detect_exec(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ struct ast_module_user *u;
+ char *tmp;
+ char *options;
+ char *stringp;
+ struct ast_frame *fr;
+ int notsilent=0;
+ struct timeval start = { 0, 0};
+ int sil = 1000;
+ int min = 100;
+ int max = -1;
+ int x;
+ int origrformat=0;
+ struct ast_dsp *dsp;
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "BackgroundDetect requires an argument (filename)\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ tmp = ast_strdupa(data);
+
+ stringp=tmp;
+ strsep(&stringp, "|");
+ options = strsep(&stringp, "|");
+ if (options) {
+ if ((sscanf(options, "%d", &x) == 1) && (x > 0))
+ sil = x;
+ options = strsep(&stringp, "|");
+ if (options) {
+ if ((sscanf(options, "%d", &x) == 1) && (x > 0))
+ min = x;
+ options = strsep(&stringp, "|");
+ if (options) {
+ if ((sscanf(options, "%d", &x) == 1) && (x > 0))
+ max = x;
+ }
+ }
+ }
+ ast_log(LOG_DEBUG, "Preparing detect of '%s', sil=%d,min=%d,max=%d\n",
+ tmp, sil, min, max);
+ if (chan->_state != AST_STATE_UP) {
+ /* Otherwise answer unless we're supposed to send this while on-hook */
+ res = ast_answer(chan);
+ }
+ if (!res) {
+ origrformat = chan->readformat;
+ if ((res = ast_set_read_format(chan, AST_FORMAT_SLINEAR)))
+ ast_log(LOG_WARNING, "Unable to set read format to linear!\n");
+ }
+ if (!(dsp = ast_dsp_new())) {
+ ast_log(LOG_WARNING, "Unable to allocate DSP!\n");
+ res = -1;
+ }
+ if (!res) {
+ ast_stopstream(chan);
+ res = ast_streamfile(chan, tmp, chan->language);
+ if (!res) {
+ while(chan->stream) {
+ res = ast_sched_wait(chan->sched);
+ if ((res < 0) && !chan->timingfunc) {
+ res = 0;
+ break;
+ }
+ if (res < 0)
+ res = 1000;
+ res = ast_waitfor(chan, res);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Waitfor failed on %s\n", chan->name);
+ break;
+ } else if (res > 0) {
+ fr = ast_read(chan);
+ if (!fr) {
+ res = -1;
+ break;
+ } else if (fr->frametype == AST_FRAME_DTMF) {
+ char t[2];
+ t[0] = fr->subclass;
+ t[1] = '\0';
+ if (ast_canmatch_extension(chan, chan->context, t, 1, chan->cid.cid_num)) {
+ /* They entered a valid extension, or might be anyhow */
+ res = fr->subclass;
+ ast_frfree(fr);
+ break;
+ }
+ } else if ((fr->frametype == AST_FRAME_VOICE) && (fr->subclass == AST_FORMAT_SLINEAR)) {
+ int totalsilence;
+ int ms;
+ res = ast_dsp_silence(dsp, fr, &totalsilence);
+ if (res && (totalsilence > sil)) {
+ /* We've been quiet a little while */
+ if (notsilent) {
+ /* We had heard some talking */
+ ms = ast_tvdiff_ms(ast_tvnow(), start);
+ ms -= sil;
+ if (ms < 0)
+ ms = 0;
+ if ((ms > min) && ((max < 0) || (ms < max))) {
+ char ms_str[10];
+ ast_log(LOG_DEBUG, "Found qualified token of %d ms\n", ms);
+
+ /* Save detected talk time (in milliseconds) */
+ sprintf(ms_str, "%d", ms );
+ pbx_builtin_setvar_helper(chan, "TALK_DETECTED", ms_str);
+
+ ast_goto_if_exists(chan, chan->context, "talk", 1);
+ res = 0;
+ ast_frfree(fr);
+ break;
+ } else
+ ast_log(LOG_DEBUG, "Found unqualified token of %d ms\n", ms);
+ notsilent = 0;
+ }
+ } else {
+ if (!notsilent) {
+ /* Heard some audio, mark the begining of the token */
+ start = ast_tvnow();
+ ast_log(LOG_DEBUG, "Start of voice token!\n");
+ notsilent = 1;
+ }
+ }
+
+ }
+ ast_frfree(fr);
+ }
+ ast_sched_runq(chan->sched);
+ }
+ ast_stopstream(chan);
+ } else {
+ ast_log(LOG_WARNING, "ast_streamfile failed on %s for %s\n", chan->name, (char *)data);
+ res = 0;
+ }
+ }
+ if (res > -1) {
+ if (origrformat && ast_set_read_format(chan, origrformat)) {
+ ast_log(LOG_WARNING, "Failed to restore read format for %s to %s\n",
+ chan->name, ast_getformatname(origrformat));
+ }
+ }
+ if (dsp)
+ ast_dsp_free(dsp);
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, background_detect_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Playback with Talk Detection");
diff --git a/apps/app_test.c b/apps/app_test.c
new file mode 100644
index 000000000..b38fe4ca7
--- /dev/null
+++ b/apps/app_test.c
@@ -0,0 +1,512 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ * Russell Bryant <russelb@clemson.edu>
+ *
+ * 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 Applications to test connection and produce report in text file
+ *
+ * \author Mark Spencer <markster@digium.com>
+ * \author Russell Bryant <russelb@clemson.edu>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "asterisk/channel.h"
+#include "asterisk/options.h"
+#include "asterisk/module.h"
+#include "asterisk/logger.h"
+#include "asterisk/lock.h"
+#include "asterisk/app.h"
+#include "asterisk/pbx.h"
+#include "asterisk/utils.h"
+
+static char *tests_descrip =
+ "TestServer(): Perform test server function and write call report.\n"
+ "Results stored in /var/log/asterisk/testreports/<testid>-server.txt";
+static char *tests_app = "TestServer";
+static char *tests_synopsis = "Execute Interface Test Server";
+
+static char *testc_descrip =
+ "TestClient(testid): Executes test client with given testid.\n"
+ "Results stored in /var/log/asterisk/testreports/<testid>-client.txt";
+
+static char *testc_app = "TestClient";
+static char *testc_synopsis = "Execute Interface Test Client";
+
+static int measurenoise(struct ast_channel *chan, int ms, char *who)
+{
+ int res=0;
+ int mssofar;
+ int noise=0;
+ int samples=0;
+ int x;
+ short *foo;
+ struct timeval start;
+ struct ast_frame *f;
+ int rformat;
+ rformat = chan->readformat;
+ if (ast_set_read_format(chan, AST_FORMAT_SLINEAR)) {
+ ast_log(LOG_NOTICE, "Unable to set to linear mode!\n");
+ return -1;
+ }
+ start = ast_tvnow();
+ for(;;) {
+ mssofar = ast_tvdiff_ms(ast_tvnow(), start);
+ if (mssofar > ms)
+ break;
+ res = ast_waitfor(chan, ms - mssofar);
+ if (res < 1)
+ break;
+ f = ast_read(chan);
+ if (!f) {
+ res = -1;
+ break;
+ }
+ if ((f->frametype == AST_FRAME_VOICE) && (f->subclass == AST_FORMAT_SLINEAR)) {
+ foo = (short *)f->data;
+ for (x=0;x<f->samples;x++) {
+ noise += abs(foo[x]);
+ samples++;
+ }
+ }
+ ast_frfree(f);
+ }
+
+ if (rformat) {
+ if (ast_set_read_format(chan, rformat)) {
+ ast_log(LOG_NOTICE, "Unable to restore original format!\n");
+ return -1;
+ }
+ }
+ if (res < 0)
+ return res;
+ if (!samples) {
+ ast_log(LOG_NOTICE, "No samples were received from the other side!\n");
+ return -1;
+ }
+ ast_log(LOG_DEBUG, "%s: Noise: %d, samples: %d, avg: %d\n", who, noise, samples, noise / samples);
+ return (noise / samples);
+}
+
+static int sendnoise(struct ast_channel *chan, int ms)
+{
+ int res;
+ res = ast_tonepair_start(chan, 1537, 2195, ms, 8192);
+ if (!res) {
+ res = ast_waitfordigit(chan, ms);
+ ast_tonepair_stop(chan);
+ }
+ return res;
+}
+
+static int testclient_exec(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *u;
+ int res = 0;
+ char *testid=data;
+ char fn[80];
+ char serverver[80];
+ FILE *f;
+
+ /* Check for test id */
+ if (ast_strlen_zero(testid)) {
+ ast_log(LOG_WARNING, "TestClient requires an argument - the test id\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ if (chan->_state != AST_STATE_UP)
+ res = ast_answer(chan);
+
+ /* Wait a few just to be sure things get started */
+ res = ast_safe_sleep(chan, 3000);
+ /* Transmit client version */
+ if (!res)
+ res = ast_dtmf_stream(chan, NULL, "8378*1#", 0);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Transmit client version\n");
+
+ /* Read server version */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Read server version\n");
+ if (!res)
+ res = ast_app_getdata(chan, NULL, serverver, sizeof(serverver) - 1, 0);
+ if (res > 0)
+ res = 0;
+ if (option_debug)
+ ast_log(LOG_DEBUG, "server version: %s\n", serverver);
+
+ if (res > 0)
+ res = 0;
+
+ if (!res)
+ res = ast_safe_sleep(chan, 1000);
+ /* Send test id */
+ if (!res)
+ res = ast_dtmf_stream(chan, NULL, testid, 0);
+ if (!res)
+ res = ast_dtmf_stream(chan, NULL, "#", 0);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "send test identifier: %s\n", testid);
+
+ if ((res >=0) && (!ast_strlen_zero(testid))) {
+ /* Make the directory to hold the test results in case it's not there */
+ snprintf(fn, sizeof(fn), "%s/testresults", ast_config_AST_LOG_DIR);
+ mkdir(fn, 0777);
+ snprintf(fn, sizeof(fn), "%s/testresults/%s-client.txt", ast_config_AST_LOG_DIR, testid);
+ if ((f = fopen(fn, "w+"))) {
+ setlinebuf(f);
+ fprintf(f, "CLIENTCHAN: %s\n", chan->name);
+ fprintf(f, "CLIENTTEST ID: %s\n", testid);
+ fprintf(f, "ANSWER: PASS\n");
+ res = 0;
+
+ if (!res) {
+ /* Step 1: Wait for "1" */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "TestClient: 2. Wait DTMF 1\n");
+ res = ast_waitfordigit(chan, 3000);
+ fprintf(f, "WAIT DTMF 1: %s\n", (res != '1') ? "FAIL" : "PASS");
+ if (res == '1')
+ res = 0;
+ else
+ res = -1;
+ }
+ if (!res)
+ res = ast_safe_sleep(chan, 1000);
+ if (!res) {
+ /* Step 2: Send "2" */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "TestClient: 2. Send DTMF 2\n");
+ res = ast_dtmf_stream(chan, NULL, "2", 0);
+ fprintf(f, "SEND DTMF 2: %s\n", (res < 0) ? "FAIL" : "PASS");
+ if (res > 0)
+ res = 0;
+ }
+ if (!res) {
+ /* Step 3: Wait one second */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "TestClient: 3. Wait one second\n");
+ res = ast_safe_sleep(chan, 1000);
+ fprintf(f, "WAIT 1 SEC: %s\n", (res < 0) ? "FAIL" : "PASS");
+ if (res > 0)
+ res = 0;
+ }
+ if (!res) {
+ /* Step 4: Measure noise */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "TestClient: 4. Measure noise\n");
+ res = measurenoise(chan, 5000, "TestClient");
+ fprintf(f, "MEASURENOISE: %s (%d)\n", (res < 0) ? "FAIL" : "PASS", res);
+ if (res > 0)
+ res = 0;
+ }
+ if (!res) {
+ /* Step 5: Wait for "4" */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "TestClient: 5. Wait DTMF 4\n");
+ res = ast_waitfordigit(chan, 3000);
+ fprintf(f, "WAIT DTMF 4: %s\n", (res != '4') ? "FAIL" : "PASS");
+ if (res == '4')
+ res = 0;
+ else
+ res = -1;
+ }
+ if (!res) {
+ /* Step 6: Transmit tone noise */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "TestClient: 6. Transmit tone\n");
+ res = sendnoise(chan, 6000);
+ fprintf(f, "SENDTONE: %s\n", (res < 0) ? "FAIL" : "PASS");
+ }
+ if (!res || (res == '5')) {
+ /* Step 7: Wait for "5" */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "TestClient: 7. Wait DTMF 5\n");
+ if (!res)
+ res = ast_waitfordigit(chan, 3000);
+ fprintf(f, "WAIT DTMF 5: %s\n", (res != '5') ? "FAIL" : "PASS");
+ if (res == '5')
+ res = 0;
+ else
+ res = -1;
+ }
+ if (!res) {
+ /* Step 8: Wait one second */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "TestClient: 8. Wait one second\n");
+ res = ast_safe_sleep(chan, 1000);
+ fprintf(f, "WAIT 1 SEC: %s\n", (res < 0) ? "FAIL" : "PASS");
+ if (res > 0)
+ res = 0;
+ }
+ if (!res) {
+ /* Step 9: Measure noise */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "TestClient: 6. Measure tone\n");
+ res = measurenoise(chan, 4000, "TestClient");
+ fprintf(f, "MEASURETONE: %s (%d)\n", (res < 0) ? "FAIL" : "PASS", res);
+ if (res > 0)
+ res = 0;
+ }
+ if (!res) {
+ /* Step 10: Send "7" */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "TestClient: 7. Send DTMF 7\n");
+ res = ast_dtmf_stream(chan, NULL, "7", 0);
+ fprintf(f, "SEND DTMF 7: %s\n", (res < 0) ? "FAIL" : "PASS");
+ if (res > 0)
+ res =0;
+ }
+ if (!res) {
+ /* Step 11: Wait for "8" */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "TestClient: 11. Wait DTMF 8\n");
+ res = ast_waitfordigit(chan, 3000);
+ fprintf(f, "WAIT DTMF 8: %s\n", (res != '8') ? "FAIL" : "PASS");
+ if (res == '8')
+ res = 0;
+ else
+ res = -1;
+ }
+ if (option_debug && !res ) {
+ /* Step 12: Hangup! */
+ ast_log(LOG_DEBUG, "TestClient: 12. Hangup\n");
+ }
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "-- TEST COMPLETE--\n");
+ fprintf(f, "-- END TEST--\n");
+ fclose(f);
+ res = -1;
+ } else
+ res = -1;
+ } else {
+ ast_log(LOG_NOTICE, "Did not read a test ID on '%s'\n", chan->name);
+ res = -1;
+ }
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int testserver_exec(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *u;
+ int res = 0;
+ char testid[80]="";
+ char fn[80];
+ FILE *f;
+ u = ast_module_user_add(chan);
+ if (chan->_state != AST_STATE_UP)
+ res = ast_answer(chan);
+ /* Read version */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Read client version\n");
+ if (!res)
+ res = ast_app_getdata(chan, NULL, testid, sizeof(testid) - 1, 0);
+ if (res > 0)
+ res = 0;
+ if (option_debug) {
+ ast_log(LOG_DEBUG, "client version: %s\n", testid);
+ ast_log(LOG_DEBUG, "Transmit server version\n");
+ }
+ res = ast_safe_sleep(chan, 1000);
+ if (!res)
+ res = ast_dtmf_stream(chan, NULL, "8378*1#", 0);
+ if (res > 0)
+ res = 0;
+
+ if (!res)
+ res = ast_app_getdata(chan, NULL, testid, sizeof(testid) - 1, 0);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "read test identifier: %s\n", testid);
+ /* Check for sneakyness */
+ if (strchr(testid, '/'))
+ res = -1;
+ if ((res >=0) && (!ast_strlen_zero(testid))) {
+ /* Got a Test ID! Whoo hoo! */
+ /* Make the directory to hold the test results in case it's not there */
+ snprintf(fn, sizeof(fn), "%s/testresults", ast_config_AST_LOG_DIR);
+ mkdir(fn, 0777);
+ snprintf(fn, sizeof(fn), "%s/testresults/%s-server.txt", ast_config_AST_LOG_DIR, testid);
+ if ((f = fopen(fn, "w+"))) {
+ setlinebuf(f);
+ fprintf(f, "SERVERCHAN: %s\n", chan->name);
+ fprintf(f, "SERVERTEST ID: %s\n", testid);
+ fprintf(f, "ANSWER: PASS\n");
+ ast_log(LOG_DEBUG, "Processing Test ID '%s'\n", testid);
+ res = ast_safe_sleep(chan, 1000);
+ if (!res) {
+ /* Step 1: Send "1" */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "TestServer: 1. Send DTMF 1\n");
+ res = ast_dtmf_stream(chan, NULL, "1", 0);
+ fprintf(f, "SEND DTMF 1: %s\n", (res < 0) ? "FAIL" : "PASS");
+ if (res > 0)
+ res = 0;
+ }
+ if (!res) {
+ /* Step 2: Wait for "2" */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "TestServer: 2. Wait DTMF 2\n");
+ res = ast_waitfordigit(chan, 3000);
+ fprintf(f, "WAIT DTMF 2: %s\n", (res != '2') ? "FAIL" : "PASS");
+ if (res == '2')
+ res = 0;
+ else
+ res = -1;
+ }
+ if (!res) {
+ /* Step 3: Measure noise */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "TestServer: 3. Measure noise\n");
+ res = measurenoise(chan, 6000, "TestServer");
+ fprintf(f, "MEASURENOISE: %s (%d)\n", (res < 0) ? "FAIL" : "PASS", res);
+ if (res > 0)
+ res = 0;
+ }
+ if (!res) {
+ /* Step 4: Send "4" */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "TestServer: 4. Send DTMF 4\n");
+ res = ast_dtmf_stream(chan, NULL, "4", 0);
+ fprintf(f, "SEND DTMF 4: %s\n", (res < 0) ? "FAIL" : "PASS");
+ if (res > 0)
+ res = 0;
+ }
+
+ if (!res) {
+ /* Step 5: Wait one second */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "TestServer: 5. Wait one second\n");
+ res = ast_safe_sleep(chan, 1000);
+ fprintf(f, "WAIT 1 SEC: %s\n", (res < 0) ? "FAIL" : "PASS");
+ if (res > 0)
+ res = 0;
+ }
+
+ if (!res) {
+ /* Step 6: Measure noise */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "TestServer: 6. Measure tone\n");
+ res = measurenoise(chan, 4000, "TestServer");
+ fprintf(f, "MEASURETONE: %s (%d)\n", (res < 0) ? "FAIL" : "PASS", res);
+ if (res > 0)
+ res = 0;
+ }
+
+ if (!res) {
+ /* Step 7: Send "5" */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "TestServer: 7. Send DTMF 5\n");
+ res = ast_dtmf_stream(chan, NULL, "5", 0);
+ fprintf(f, "SEND DTMF 5: %s\n", (res < 0) ? "FAIL" : "PASS");
+ if (res > 0)
+ res = 0;
+ }
+
+ if (!res) {
+ /* Step 8: Transmit tone noise */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "TestServer: 8. Transmit tone\n");
+ res = sendnoise(chan, 6000);
+ fprintf(f, "SENDTONE: %s\n", (res < 0) ? "FAIL" : "PASS");
+ }
+
+ if (!res || (res == '7')) {
+ /* Step 9: Wait for "7" */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "TestServer: 9. Wait DTMF 7\n");
+ if (!res)
+ res = ast_waitfordigit(chan, 3000);
+ fprintf(f, "WAIT DTMF 7: %s\n", (res != '7') ? "FAIL" : "PASS");
+ if (res == '7')
+ res = 0;
+ else
+ res = -1;
+ }
+ if (!res)
+ res = ast_safe_sleep(chan, 1000);
+ if (!res) {
+ /* Step 10: Send "8" */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "TestServer: 10. Send DTMF 8\n");
+ res = ast_dtmf_stream(chan, NULL, "8", 0);
+ fprintf(f, "SEND DTMF 8: %s\n", (res < 0) ? "FAIL" : "PASS");
+ if (res > 0)
+ res = 0;
+ }
+ if (!res) {
+ /* Step 11: Wait for hangup to arrive! */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "TestServer: 11. Waiting for hangup\n");
+ res = ast_safe_sleep(chan, 10000);
+ fprintf(f, "WAIT HANGUP: %s\n", (res < 0) ? "PASS" : "FAIL");
+ }
+
+ ast_log(LOG_NOTICE, "-- TEST COMPLETE--\n");
+ fprintf(f, "-- END TEST--\n");
+ fclose(f);
+ res = -1;
+ } else
+ res = -1;
+ } else {
+ ast_log(LOG_NOTICE, "Did not read a test ID on '%s'\n", chan->name);
+ res = -1;
+ }
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(testc_app);
+ res |= ast_unregister_application(tests_app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ int res;
+
+ res = ast_register_application(testc_app, testclient_exec, testc_synopsis, testc_descrip);
+ res |= ast_register_application(tests_app, testserver_exec, tests_synopsis, tests_descrip);
+
+ return res;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Interface Test Application");
diff --git a/apps/app_transfer.c b/apps/app_transfer.c
new file mode 100644
index 000000000..cda2914c7
--- /dev/null
+++ b/apps/app_transfer.c
@@ -0,0 +1,156 @@
+/*
+ * 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 Transfer a caller
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * Requires transfer support from channel driver
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/options.h"
+#include "asterisk/app.h"
+
+
+static const char *app = "Transfer";
+
+static const char *synopsis = "Transfer caller to remote extension";
+
+static const char *descrip =
+" Transfer([Tech/]dest[|options]): Requests the remote caller be transferred\n"
+"to a given destination. If TECH (SIP, IAX2, LOCAL etc) is used, only\n"
+"an incoming call with the same channel technology will be transfered.\n"
+"Note that for SIP, if you transfer before call is setup, a 302 redirect\n"
+"SIP message will be returned to the caller.\n"
+"\nThe result of the application will be reported in the TRANSFERSTATUS\n"
+"channel variable:\n"
+" SUCCESS Transfer succeeded\n"
+" FAILURE Transfer failed\n"
+" UNSUPPORTED Transfer unsupported by channel driver\n"
+"The option string many contain the following character:\n"
+"'j' -- jump to n+101 priority if the channel transfer attempt\n"
+" fails\n";
+
+static int transfer_exec(struct ast_channel *chan, void *data)
+{
+ int res;
+ int len;
+ struct ast_module_user *u;
+ char *slash;
+ char *tech = NULL;
+ char *dest = NULL;
+ char *status;
+ char *parse;
+ int priority_jump = 0;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(dest);
+ AST_APP_ARG(options);
+ );
+
+ u = ast_module_user_add(chan);
+
+ if (ast_strlen_zero((char *)data)) {
+ ast_log(LOG_WARNING, "Transfer requires an argument ([Tech/]destination[|options])\n");
+ ast_module_user_remove(u);
+ pbx_builtin_setvar_helper(chan, "TRANSFERSTATUS", "FAILURE");
+ return 0;
+ } else
+ parse = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ if (args.options) {
+ if (strchr(args.options, 'j'))
+ priority_jump = 1;
+ }
+
+ dest = args.dest;
+
+ if ((slash = strchr(dest, '/')) && (len = (slash - dest))) {
+ tech = dest;
+ dest = slash + 1;
+ /* Allow execution only if the Tech/destination agrees with the type of the channel */
+ if (strncasecmp(chan->tech->type, tech, len)) {
+ pbx_builtin_setvar_helper(chan, "TRANSFERSTATUS", "FAILURE");
+ ast_module_user_remove(u);
+ return 0;
+ }
+ }
+
+ /* Check if the channel supports transfer before we try it */
+ if (!chan->tech->transfer) {
+ pbx_builtin_setvar_helper(chan, "TRANSFERSTATUS", "UNSUPPORTED");
+ ast_module_user_remove(u);
+ return 0;
+ }
+
+ res = ast_transfer(chan, dest);
+
+ if (res < 0) {
+ status = "FAILURE";
+ if (priority_jump || ast_opt_priority_jumping)
+ ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
+ res = 0;
+ } else {
+ status = "SUCCESS";
+ res = 0;
+ }
+
+ pbx_builtin_setvar_helper(chan, "TRANSFERSTATUS", status);
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, transfer_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Transfer");
diff --git a/apps/app_url.c b/apps/app_url.c
new file mode 100644
index 000000000..a1f295a32
--- /dev/null
+++ b/apps/app_url.c
@@ -0,0 +1,173 @@
+/*
+ * 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 App to transmit a URL
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/image.h"
+#include "asterisk/options.h"
+
+static char *app = "SendURL";
+
+static char *synopsis = "Send a URL";
+
+static char *descrip =
+" SendURL(URL[|option]): Requests client go to URL (IAX2) or sends the \n"
+"URL to the client (other channels).\n"
+"Result is returned in the SENDURLSTATUS channel variable:\n"
+" SUCCESS URL successfully sent to client\n"
+" FAILURE Failed to send URL\n"
+" NOLOAD Client failed to load URL (wait enabled)\n"
+" UNSUPPORTED Channel does not support URL transport\n"
+"\n"
+"If the option 'wait' is specified, execution will wait for an\n"
+"acknowledgement that the URL has been loaded before continuing\n"
+"\n"
+"If jumping is specified as an option (the 'j' flag), the client does not\n"
+"support Asterisk \"html\" transport, and there exists a step with priority\n"
+"n + 101, then execution will continue at that step.\n"
+"\n"
+"SendURL continues normally if the URL was sent correctly or if the channel\n"
+"does not support HTML transport. Otherwise, the channel is hung up.\n";
+
+
+static int sendurl_exec(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ struct ast_module_user *u;
+ char *tmp;
+ char *options;
+ int local_option_wait=0;
+ int local_option_jump = 0;
+ struct ast_frame *f;
+ char *stringp=NULL;
+ char *status = "FAILURE";
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "SendURL requires an argument (URL)\n");
+ pbx_builtin_setvar_helper(chan, "SENDURLSTATUS", status);
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ tmp = ast_strdupa(data);
+
+ stringp=tmp;
+ strsep(&stringp, "|");
+ options = strsep(&stringp, "|");
+ if (options && !strcasecmp(options, "wait"))
+ local_option_wait = 1;
+ if (options && !strcasecmp(options, "j"))
+ local_option_jump = 1;
+
+ if (!ast_channel_supports_html(chan)) {
+ /* Does not support transport */
+ if (local_option_jump || ast_opt_priority_jumping)
+ ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
+ pbx_builtin_setvar_helper(chan, "SENDURLSTATUS", "UNSUPPORTED");
+ ast_module_user_remove(u);
+ return 0;
+ }
+ res = ast_channel_sendurl(chan, tmp);
+ if (res == -1) {
+ pbx_builtin_setvar_helper(chan, "SENDURLSTATUS", "FAILURE");
+ ast_module_user_remove(u);
+ return res;
+ }
+ status = "SUCCESS";
+ if (local_option_wait) {
+ for(;;) {
+ /* Wait for an event */
+ res = ast_waitfor(chan, -1);
+ if (res < 0)
+ break;
+ f = ast_read(chan);
+ if (!f) {
+ res = -1;
+ status = "FAILURE";
+ break;
+ }
+ if (f->frametype == AST_FRAME_HTML) {
+ switch(f->subclass) {
+ case AST_HTML_LDCOMPLETE:
+ res = 0;
+ ast_frfree(f);
+ status = "NOLOAD";
+ goto out;
+ break;
+ case AST_HTML_NOSUPPORT:
+ /* Does not support transport */
+ status ="UNSUPPORTED";
+ if (local_option_jump || ast_opt_priority_jumping)
+ ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
+ res = 0;
+ ast_frfree(f);
+ goto out;
+ break;
+ default:
+ ast_log(LOG_WARNING, "Don't know what to do with HTML subclass %d\n", f->subclass);
+ };
+ }
+ ast_frfree(f);
+ }
+ }
+out:
+ pbx_builtin_setvar_helper(chan, "SENDURLSTATUS", status);
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, sendurl_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Send URL Applications");
diff --git a/apps/app_userevent.c b/apps/app_userevent.c
new file mode 100644
index 000000000..df7bc58a7
--- /dev/null
+++ b/apps/app_userevent.c
@@ -0,0 +1,109 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * 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 UserEvent application -- send manager event
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/manager.h"
+#include "asterisk/app.h"
+
+static char *app = "UserEvent";
+
+static char *synopsis = "Send an arbitrary event to the manager interface";
+
+static char *descrip =
+" UserEvent(eventname[|body]): Sends an arbitrary event to the manager\n"
+"interface, with an optional body representing additional arguments. The\n"
+"body may be specified as a | delimeted list of headers. Each additional\n"
+"argument will be placed on a new line in the event. The format of the\n"
+"event will be:\n"
+" Event: UserEvent\n"
+" UserEvent: <specified event name>\n"
+" [body]\n"
+"If no body is specified, only Event and UserEvent headers will be present.\n";
+
+
+static int userevent_exec(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *u;
+ char *parse, buf[2048] = "";
+ int x, buflen = 0;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(eventname);
+ AST_APP_ARG(extra)[100];
+ );
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "UserEvent requires an argument (eventname|optional event body)\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ parse = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ for (x = 0; x < args.argc - 1; x++) {
+ ast_copy_string(buf + buflen, args.extra[x], sizeof(buf) - buflen - 2);
+ buflen += strlen(args.extra[x]);
+ ast_copy_string(buf + buflen, "\r\n", 3);
+ buflen += 2;
+ }
+
+ manager_event(EVENT_FLAG_USER, "UserEvent", "UserEvent: %s\r\n%s", args.eventname, buf);
+
+ ast_module_user_remove(u);
+
+ return 0;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, userevent_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Custom User Event Application");
diff --git a/apps/app_verbose.c b/apps/app_verbose.c
new file mode 100644
index 000000000..f9bcfd116
--- /dev/null
+++ b/apps/app_verbose.c
@@ -0,0 +1,168 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (c) 2004 - 2005 Tilghman Lesher. All rights reserved.
+ *
+ * Tilghman Lesher <app_verbose_v001@the-tilghman.com>
+ *
+ * This code is released by the author with no restrictions on usage.
+ *
+ * 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.
+ *
+ */
+
+/*! \file
+ *
+ * \brief Verbose logging application
+ *
+ * \author Tilghman Lesher <app_verbose_v001@the-tilghman.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "asterisk/options.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+
+static char *app_verbose = "Verbose";
+static char *verbose_synopsis = "Send arbitrary text to verbose output";
+static char *verbose_descrip =
+"Verbose([<level>|]<message>)\n"
+" level must be an integer value. If not specified, defaults to 0.\n";
+
+static char *app_log = "Log";
+static char *log_synopsis = "Send arbitrary text to a selected log level";
+static char *log_descrip =
+"Log(<level>|<message>)\n"
+" level must be one of ERROR, WARNING, NOTICE, DEBUG, VERBOSE, DTMF\n";
+
+
+static int verbose_exec(struct ast_channel *chan, void *data)
+{
+ char *vtext;
+ int vsize;
+ struct ast_module_user *u;
+
+ u = ast_module_user_add(chan);
+
+ if (data) {
+ char *tmp;
+ vtext = ast_strdupa(data);
+ tmp = strsep(&vtext, "|");
+ if (vtext) {
+ if (sscanf(tmp, "%d", &vsize) != 1) {
+ vsize = 0;
+ ast_log(LOG_WARNING, "'%s' is not a verboser number\n", vtext);
+ }
+ } else {
+ vtext = tmp;
+ vsize = 0;
+ }
+ if (option_verbose >= vsize) {
+ switch (vsize) {
+ case 0:
+ ast_verbose("%s\n", vtext);
+ break;
+ case 1:
+ ast_verbose(VERBOSE_PREFIX_1 "%s\n", vtext);
+ break;
+ case 2:
+ ast_verbose(VERBOSE_PREFIX_2 "%s\n", vtext);
+ break;
+ case 3:
+ ast_verbose(VERBOSE_PREFIX_3 "%s\n", vtext);
+ break;
+ default:
+ ast_verbose(VERBOSE_PREFIX_4 "%s\n", vtext);
+ }
+ }
+ }
+
+ ast_module_user_remove(u);
+
+ return 0;
+}
+
+static int log_exec(struct ast_channel *chan, void *data)
+{
+ char *level, *ltext;
+ struct ast_module_user *u;
+ int lnum = -1;
+ char extension[AST_MAX_EXTENSION + 5], context[AST_MAX_EXTENSION + 2];
+
+ u = ast_module_user_add(chan);
+ if (ast_strlen_zero(data)) {
+ ast_module_user_remove(u);
+ return 0;
+ }
+
+ ltext = ast_strdupa(data);
+
+ level = strsep(&ltext, "|");
+
+ if (!strcasecmp(level, "ERROR")) {
+ lnum = __LOG_ERROR;
+ } else if (!strcasecmp(level, "WARNING")) {
+ lnum = __LOG_WARNING;
+ } else if (!strcasecmp(level, "NOTICE")) {
+ lnum = __LOG_NOTICE;
+ } else if (!strcasecmp(level, "DEBUG")) {
+ lnum = __LOG_DEBUG;
+ } else if (!strcasecmp(level, "VERBOSE")) {
+ lnum = __LOG_VERBOSE;
+ } else if (!strcasecmp(level, "DTMF")) {
+ lnum = __LOG_DTMF;
+ } else if (!strcasecmp(level, "EVENT")) {
+ lnum = __LOG_EVENT;
+ } else {
+ ast_log(LOG_ERROR, "Unknown log level: '%s'\n", level);
+ }
+
+ if (lnum > -1) {
+ snprintf(context, sizeof(context), "@ %s", chan->context);
+ snprintf(extension, sizeof(extension), "Ext. %s", chan->exten);
+
+ ast_log(lnum, extension, chan->priority, context, "%s\n", ltext);
+ }
+ ast_module_user_remove(u);
+ return 0;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app_verbose);
+ res |= ast_unregister_application(app_log);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ int res;
+
+ res = ast_register_application(app_log, log_exec, log_synopsis, log_descrip);
+ res |= ast_register_application(app_verbose, verbose_exec, verbose_synopsis, verbose_descrip);
+
+ return res;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Send verbose output");
diff --git a/apps/app_voicemail.c b/apps/app_voicemail.c
new file mode 100644
index 000000000..b0530f78c
--- /dev/null
+++ b/apps/app_voicemail.c
@@ -0,0 +1,9268 @@
+/*
+ * 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 Comedian Mail - Voicemail System
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \par See also
+ * \arg \ref Config_vm
+ * \ingroup applications
+ * \note This module requires res_adsi to load.
+ */
+
+/*** MODULEINFO
+ <depend>res_adsi</depend>
+ <depend>res_smdi</depend>
+ ***/
+
+/*** MAKEOPTS
+<category name="MENUSELECT_OPTS_app_voicemail" displayname="Voicemail Build Options" positive_output="yes" remove_on_change="apps/app_voicemail.o apps/app_directory.o">
+ <member name="ODBC_STORAGE" displayname="Storage of Voicemail using ODBC">
+ <depend>unixodbc</depend>
+ <depend>ltdl</depend>
+ <conflict>IMAP_STORAGE</conflict>
+ <defaultenabled>no</defaultenabled>
+ </member>
+ <member name="IMAP_STORAGE" displayname="Storage of Voicemail using IMAP4">
+ <depend>imap_tk</depend>
+ <conflict>ODBC_STORAGE</conflict>
+ <use>ssl</use>
+ <defaultenabled>no</defaultenabled>
+ </member>
+</category>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <time.h>
+#include <dirent.h>
+#ifdef IMAP_STORAGE
+#include <ctype.h>
+#include <signal.h>
+#include <pwd.h>
+#ifdef USE_SYSTEM_IMAP
+#include <imap/c-client.h>
+#include <imap/imap4r1.h>
+#include <imap/linkage.h>
+#elif defined (USE_SYSTEM_CCLIENT)
+#include <c-client/c-client.h>
+#include <c-client/imap4r1.h>
+#include <c-client/linkage.h>
+#else
+#include "c-client.h"
+#include "imap4r1.h"
+#include "linkage.h"
+#endif
+#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/config.h"
+#include "asterisk/say.h"
+#include "asterisk/module.h"
+#include "asterisk/adsi.h"
+#include "asterisk/app.h"
+#include "asterisk/manager.h"
+#include "asterisk/dsp.h"
+#include "asterisk/localtime.h"
+#include "asterisk/cli.h"
+#include "asterisk/utils.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/smdi.h"
+#ifdef ODBC_STORAGE
+#include "asterisk/res_odbc.h"
+#endif
+
+#ifdef IMAP_STORAGE
+AST_MUTEX_DEFINE_STATIC(imaptemp_lock);
+static char imaptemp[1024];
+static char imapserver[48];
+static char imapport[8];
+static char imapflags[128];
+static char imapfolder[64];
+static char authuser[32];
+static char authpassword[42];
+
+static int expungeonhangup = 1;
+static char delimiter = '\0';
+static const long DEFAULT_IMAP_TCP_TIMEOUT = 60L;
+
+struct vm_state;
+struct ast_vm_user;
+
+static int init_mailstream (struct vm_state *vms, int box);
+static void write_file (char *filename, char *buffer, unsigned long len);
+/*static void status (MAILSTREAM *stream); */ /* No need for this. */
+static char *get_header_by_tag(char *header, char *tag);
+static void vm_imap_delete(int msgnum, struct ast_vm_user *vmu);
+static char *get_user_by_mailbox(char *mailbox);
+static struct vm_state *get_vm_state_by_imapuser(char *user, int interactive);
+static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive);
+static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu);
+static void vmstate_insert(struct vm_state *vms);
+static void vmstate_delete(struct vm_state *vms);
+static void set_update(MAILSTREAM * stream);
+static void init_vm_state(struct vm_state *vms);
+static void copy_msgArray(struct vm_state *dst, struct vm_state *src);
+static int save_body(BODY *body, struct vm_state *vms, char *section, char *format);
+static void get_mailbox_delimiter(MAILSTREAM *stream);
+static void mm_parsequota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota);
+static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int target);
+static int imap_store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms);
+static void check_quota(struct vm_state *vms, char *mailbox);
+static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu,int box);
+struct vmstate {
+ struct vm_state *vms;
+ struct vmstate *next;
+};
+AST_MUTEX_DEFINE_STATIC(vmstate_lock);
+static struct vmstate *vmstates = NULL;
+#endif
+
+#define SMDI_MWI_WAIT_TIMEOUT 1000 /* 1 second */
+
+#define COMMAND_TIMEOUT 5000
+/* Don't modify these here; set your umask at runtime instead */
+#define VOICEMAIL_DIR_MODE 0777
+#define VOICEMAIL_FILE_MODE 0666
+#define CHUNKSIZE 65536
+
+#define VOICEMAIL_CONFIG "voicemail.conf"
+#define ASTERISK_USERNAME "asterisk"
+
+/* Default mail command to mail voicemail. Change it with the
+ mailcmd= command in voicemail.conf */
+#define SENDMAIL "/usr/sbin/sendmail -t"
+
+#define INTRO "vm-intro"
+
+#define MAXMSG 100
+#ifndef IMAP_STORAGE
+#define MAXMSGLIMIT 9999
+#else
+#define MAXMSGLIMIT 255
+#endif
+
+#define BASEMAXINLINE 256
+#define BASELINELEN 72
+#define BASEMAXINLINE 256
+#define eol "\r\n"
+
+#define MAX_DATETIME_FORMAT 512
+#define MAX_NUM_CID_CONTEXTS 10
+
+#define VM_REVIEW (1 << 0)
+#define VM_OPERATOR (1 << 1)
+#define VM_SAYCID (1 << 2)
+#define VM_SVMAIL (1 << 3)
+#define VM_ENVELOPE (1 << 4)
+#define VM_SAYDURATION (1 << 5)
+#define VM_SKIPAFTERCMD (1 << 6)
+#define VM_FORCENAME (1 << 7) /*!< Have new users record their name */
+#define VM_FORCEGREET (1 << 8) /*!< Have new users record their greetings */
+#define VM_PBXSKIP (1 << 9)
+#define VM_DIRECFORWARD (1 << 10) /*!< directory_forward */
+#define VM_ATTACH (1 << 11)
+#define VM_DELETE (1 << 12)
+#define VM_ALLOCED (1 << 13)
+#define VM_SEARCH (1 << 14)
+#define VM_TEMPGREETWARN (1 << 15) /*!< Remind user tempgreeting is set */
+#define ERROR_LOCK_PATH -100
+#define ERROR_MAILBOX_FULL -200
+
+
+enum {
+ OPT_SILENT = (1 << 0),
+ OPT_BUSY_GREETING = (1 << 1),
+ OPT_UNAVAIL_GREETING = (1 << 2),
+ OPT_RECORDGAIN = (1 << 3),
+ OPT_PREPEND_MAILBOX = (1 << 4),
+ OPT_PRIORITY_JUMP = (1 << 5),
+ OPT_AUTOPLAY = (1 << 6),
+} vm_option_flags;
+
+enum {
+ OPT_ARG_RECORDGAIN = 0,
+ OPT_ARG_PLAYFOLDER = 1,
+ /* This *must* be the last value in this enum! */
+ OPT_ARG_ARRAY_SIZE = 2,
+} vm_option_args;
+
+AST_APP_OPTIONS(vm_app_options, {
+ AST_APP_OPTION('s', OPT_SILENT),
+ AST_APP_OPTION('b', OPT_BUSY_GREETING),
+ AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
+ AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
+ AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
+ AST_APP_OPTION('j', OPT_PRIORITY_JUMP),
+ AST_APP_OPTION_ARG('a', OPT_AUTOPLAY, OPT_ARG_PLAYFOLDER),
+});
+
+static int load_config(void);
+
+/*! \page vmlang Voicemail Language Syntaxes Supported
+
+ \par Syntaxes supported, not really language codes.
+ \arg \b en - English
+ \arg \b de - German
+ \arg \b es - Spanish
+ \arg \b fr - French
+ \arg \b it = Italian
+ \arg \b nl - Dutch
+ \arg \b pt - Polish
+ \arg \b pt - Portuguese
+ \arg \b pt_BR - Portuguese (Brazil)
+ \arg \b gr - Greek
+ \arg \b no - Norwegian
+ \arg \b se - Swedish
+ \arg \b ua - Ukrainian
+ \arg \b he - Hebrew
+
+German requires the following additional soundfile:
+\arg \b 1F einE (feminine)
+
+Spanish requires the following additional soundfile:
+\arg \b 1M un (masculine)
+
+Dutch, Portuguese & Spanish require the following additional soundfiles:
+\arg \b vm-INBOXs singular of 'new'
+\arg \b vm-Olds singular of 'old/heard/read'
+
+NB these are plural:
+\arg \b vm-INBOX nieuwe (nl)
+\arg \b vm-Old oude (nl)
+
+Polish uses:
+\arg \b vm-new-a 'new', feminine singular accusative
+\arg \b vm-new-e 'new', feminine plural accusative
+\arg \b vm-new-ych 'new', feminine plural genitive
+\arg \b vm-old-a 'old', feminine singular accusative
+\arg \b vm-old-e 'old', feminine plural accusative
+\arg \b vm-old-ych 'old', feminine plural genitive
+\arg \b digits/1-a 'one', not always same as 'digits/1'
+\arg \b digits/2-ie 'two', not always same as 'digits/2'
+
+Swedish uses:
+\arg \b vm-nytt singular of 'new'
+\arg \b vm-nya plural of 'new'
+\arg \b vm-gammalt singular of 'old'
+\arg \b vm-gamla plural of 'old'
+\arg \b digits/ett 'one', not always same as 'digits/1'
+
+Norwegian uses:
+\arg \b vm-ny singular of 'new'
+\arg \b vm-nye plural of 'new'
+\arg \b vm-gammel singular of 'old'
+\arg \b vm-gamle plural of 'old'
+
+Dutch also uses:
+\arg \b nl-om 'at'?
+
+Spanish also uses:
+\arg \b vm-youhaveno
+
+Ukrainian requires the following additional soundfile:
+\arg \b vm-nove 'nove'
+\arg \b vm-stare 'stare'
+\arg \b digits/ua/1e 'odne'
+
+Italian requires the following additional soundfile:
+
+For vm_intro_it:
+\arg \b vm-nuovo new
+\arg \b vm-nuovi new plural
+\arg \b vm-vecchio old
+\arg \b vm-vecchi old plural
+
+Hebrew also uses:
+\arg \b vm-INBOX1 '1 new message'
+\arg \b vm-OLD1 '1 old message'
+\arg \b vm-shtei 'shtei'
+\arg \b vm-nomessages 'you have no new messages'
+
+\note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
+spelled among others when you have to change folder. For the above reasons, vm-INBOX
+and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
+
+*/
+
+struct baseio {
+ int iocp;
+ int iolen;
+ int linelength;
+ int ateof;
+ unsigned char iobuf[BASEMAXINLINE];
+};
+
+/*! Structure for linked list of users */
+struct ast_vm_user {
+ char context[AST_MAX_CONTEXT]; /*!< Voicemail context */
+ char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox id, unique within vm context */
+ char password[80]; /*!< Secret pin code, numbers only */
+ char fullname[80]; /*!< Full name, for directory app */
+ char email[80]; /*!< E-mail address */
+ char pager[80]; /*!< E-mail address to pager (no attachment) */
+ char serveremail[80]; /*!< From: Mail address */
+ char mailcmd[160]; /*!< Configurable mail command */
+ char language[MAX_LANGUAGE]; /*!< Config: Language setting */
+ char zonetag[80]; /*!< Time zone */
+ char callback[80];
+ char dialout[80];
+ char uniqueid[80]; /*!< Unique integer identifier */
+ char exit[80];
+ char attachfmt[20]; /*!< Attachment format */
+ unsigned int flags; /*!< VM_ flags */
+ int saydurationm;
+ int maxmsg; /*!< Maximum number of msgs per folder for this mailbox */
+#ifdef IMAP_STORAGE
+ char imapuser[80]; /* IMAP server login */
+ char imappassword[80]; /* IMAP server password if authpassword not defined */
+#endif
+ double volgain; /*!< Volume gain for voicemails sent via email */
+ AST_LIST_ENTRY(ast_vm_user) list;
+};
+
+struct vm_zone {
+ AST_LIST_ENTRY(vm_zone) list;
+ char name[80];
+ char timezone[80];
+ char msg_format[512];
+};
+
+struct vm_state {
+ char curbox[80];
+ char username[80];
+ char context[80];
+ char curdir[PATH_MAX];
+ char vmbox[PATH_MAX];
+ char fn[PATH_MAX];
+ char fn2[PATH_MAX];
+ int *deleted;
+ int *heard;
+ int curmsg;
+ int lastmsg;
+ int newmessages;
+ int oldmessages;
+ int starting;
+ int repeats;
+#ifdef IMAP_STORAGE
+ ast_mutex_t lock;
+ int updated; /* decremented on each mail check until 1 -allows delay */
+ long msgArray[256];
+ MAILSTREAM *mailstream;
+ int vmArrayIndex;
+ char imapuser[80]; /* IMAP server login */
+ int interactive;
+ unsigned int quota_limit;
+ unsigned int quota_usage;
+ struct vm_state *persist_vms;
+#endif
+};
+static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msg, int option, signed char record_gain);
+static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
+static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
+ char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir,
+ signed char record_gain, struct vm_state *vms);
+static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
+static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
+static int notify_new_message(struct ast_channel *chan, struct ast_vm_user *vmu, int msgnum, long duration, char *fmt, char *cidnum, char *cidname);
+static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, char *cidnum, char *cidname, char *attach, char *format, int duration, int attach_user_voicemail, struct ast_channel *chan, const char *category, int imap);
+#if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
+static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
+#endif
+static void apply_options(struct ast_vm_user *vmu, const char *options);
+
+#ifdef ODBC_STORAGE
+static char odbc_database[80];
+static char odbc_table[80];
+#define RETRIEVE(a,b,c) retrieve_file(a,b)
+#define DISPOSE(a,b) remove_file(a,b)
+#define STORE(a,b,c,d,e,f,g,h,i) store_file(a,b,c,d)
+#define EXISTS(a,b,c,d) (message_exists(a,b))
+#define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
+#define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
+#define DELETE(a,b,c,d) (delete_file(a,b))
+#else
+#ifdef IMAP_STORAGE
+#define RETRIEVE(a,b,c) imap_retrieve_file(a,b,c)
+#define DISPOSE(a,b) remove_file(a,b)
+#define STORE(a,b,c,d,e,f,g,h,i) (imap_store_file(a,b,c,d,e,f,g,h,i))
+#define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
+#define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
+#define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
+#define DELETE(a,b,c,d) (vm_imap_delete(b,d))
+#else
+#define RETRIEVE(a,b,c)
+#define DISPOSE(a,b)
+#define STORE(a,b,c,d,e,f,g,h,i)
+#define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
+#define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
+#define COPY(a,b,c,d,e,f,g,h) (copy_plain_file(g,h));
+#define DELETE(a,b,c,d) (vm_delete(c))
+#endif
+#endif
+
+static char VM_SPOOL_DIR[PATH_MAX];
+
+static char ext_pass_cmd[128];
+
+int my_umask;
+
+#if ODBC_STORAGE
+#define tdesc "Comedian Mail (Voicemail System) with ODBC Storage"
+#elif IMAP_STORAGE
+#define tdesc "Comedian Mail (Voicemail System) with IMAP Storage"
+#else
+#define tdesc "Comedian Mail (Voicemail System)"
+#endif
+
+static char userscontext[AST_MAX_EXTENSION] = "default";
+
+static char *addesc = "Comedian Mail";
+
+static char *synopsis_vm =
+"Leave a Voicemail message";
+
+static char *descrip_vm =
+" VoiceMail(mailbox[@context][&mailbox[@context]][...][|options]): This\n"
+"application allows the calling party to leave a message for the specified\n"
+"list of mailboxes. When multiple mailboxes are specified, the greeting will\n"
+"be taken from the first mailbox specified. Dialplan execution will stop if the\n"
+"specified mailbox does not exist.\n"
+" The Voicemail application will exit if any of the following DTMF digits are\n"
+"received:\n"
+" 0 - Jump to the 'o' extension in the current dialplan context.\n"
+" * - Jump to the 'a' extension in the current dialplan context.\n"
+" This application will set the following channel variable upon completion:\n"
+" VMSTATUS - This indicates the status of the execution of the VoiceMail\n"
+" application. The possible values are:\n"
+" SUCCESS | USEREXIT | FAILED\n\n"
+" Options:\n"
+" b - Play the 'busy' greeting to the calling party.\n"
+" g(#) - Use the specified amount of gain when recording the voicemail\n"
+" message. The units are whole-number decibels (dB).\n"
+" Only works on supported technologies, which is Zap only.\n"
+" s - Skip the playback of instructions for leaving a message to the\n"
+" calling party.\n"
+" u - Play the 'unavailable' greeting.\n"
+" j - Jump to priority n+101 if the mailbox is not found or some other\n"
+" error occurs.\n";
+
+static char *synopsis_vmain =
+"Check Voicemail messages";
+
+static char *descrip_vmain =
+" VoiceMailMain([mailbox][@context][|options]): This application allows the\n"
+"calling party to check voicemail messages. A specific mailbox, and optional\n"
+"corresponding context, may be specified. If a mailbox is not provided, the\n"
+"calling party will be prompted to enter one. If a context is not specified,\n"
+"the 'default' context will be used.\n\n"
+" Options:\n"
+" p - Consider the mailbox parameter as a prefix to the mailbox that\n"
+" is entered by the caller.\n"
+" g(#) - Use the specified amount of gain when recording a voicemail\n"
+" message. The units are whole-number decibels (dB).\n"
+" s - Skip checking the passcode for the mailbox.\n"
+" a(#) - Skip folder prompt and go directly to folder specified.\n"
+" Defaults to INBOX\n";
+
+static char *synopsis_vm_box_exists =
+"Check to see if Voicemail mailbox exists";
+
+static char *descrip_vm_box_exists =
+" MailboxExists(mailbox[@context][|options]): Check to see if the specified\n"
+"mailbox exists. If no voicemail context is specified, the 'default' context\n"
+"will be used.\n"
+" This application will set the following channel variable upon completion:\n"
+" VMBOXEXISTSSTATUS - This will contain the status of the execution of the\n"
+" MailboxExists application. Possible values include:\n"
+" SUCCESS | FAILED\n\n"
+" Options:\n"
+" j - Jump to priority n+101 if the mailbox is found.\n";
+
+static char *synopsis_vmauthenticate =
+"Authenticate with Voicemail passwords";
+
+static char *descrip_vmauthenticate =
+" VMAuthenticate([mailbox][@context][|options]): This application behaves the\n"
+"same way as the Authenticate application, but the passwords are taken from\n"
+"voicemail.conf.\n"
+" If the mailbox is specified, only that mailbox's password will be considered\n"
+"valid. If the mailbox is not specified, the channel variable AUTH_MAILBOX will\n"
+"be set with the authenticated mailbox.\n\n"
+" Options:\n"
+" s - Skip playing the initial prompts.\n";
+
+/* Leave a message */
+static char *app = "VoiceMail";
+
+/* Check mail, control, etc */
+static char *app2 = "VoiceMailMain";
+
+static char *app3 = "MailboxExists";
+static char *app4 = "VMAuthenticate";
+
+static AST_LIST_HEAD_STATIC(users, ast_vm_user);
+static AST_LIST_HEAD_STATIC(zones, vm_zone);
+static char zonetag[80];
+static int maxsilence;
+static int maxmsg;
+static int silencethreshold = 128;
+static char serveremail[80];
+static char mailcmd[160]; /* Configurable mail cmd */
+static char externnotify[160];
+static struct ast_smdi_interface *smdi_iface = NULL;
+static char vmfmts[80];
+static double volgain;
+static int vmminmessage;
+static int vmmaxmessage;
+static int maxgreet;
+static int skipms;
+static int maxlogins;
+
+static struct ast_flags globalflags = {0};
+
+static int saydurationminfo;
+
+static char dialcontext[AST_MAX_CONTEXT];
+static char callcontext[AST_MAX_CONTEXT];
+static char exitcontext[AST_MAX_CONTEXT];
+
+static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
+
+
+static char *emailbody = NULL;
+static char *emailsubject = NULL;
+static char *pagerbody = NULL;
+static char *pagersubject = NULL;
+static char fromstring[100];
+static char pagerfromstring[100];
+static char emailtitle[100];
+static char charset[32] = "ISO-8859-1";
+
+static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
+static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
+static int adsiver = 1;
+static char emaildateformat[32] = "%A, %B %d, %Y at %r";
+
+
+static char *strip_control(const char *input, char *buf, size_t buflen)
+{
+ char *bufptr = buf;
+ for (; *input; input++) {
+ if (*input < 32) {
+ continue;
+ }
+ *bufptr++ = *input;
+ if (bufptr == buf + buflen - 1) {
+ break;
+ }
+ }
+ *bufptr = '\0';
+ return buf;
+}
+
+static void populate_defaults(struct ast_vm_user *vmu)
+{
+ ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
+ if (saydurationminfo)
+ vmu->saydurationm = saydurationminfo;
+ ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
+ ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
+ ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
+ ast_copy_string(vmu->zonetag, zonetag, sizeof(vmu->zonetag));
+ if (maxmsg)
+ vmu->maxmsg = maxmsg;
+ vmu->volgain = volgain;
+}
+
+static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
+{
+ int x;
+ if (!strcasecmp(var, "attach")) {
+ ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
+ } else if (!strcasecmp(var, "attachfmt")) {
+ ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
+ } else if (!strcasecmp(var, "serveremail")) {
+ ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
+ } else if (!strcasecmp(var, "language")) {
+ ast_copy_string(vmu->language, value, sizeof(vmu->language));
+ } else if (!strcasecmp(var, "tz")) {
+ ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
+#ifdef IMAP_STORAGE
+ } else if (!strcasecmp(var, "imapuser")) {
+ ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
+ } else if (!strcasecmp(var, "imappassword") || !strcasecmp(var, "imapsecret")) {
+ ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
+#endif
+ } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
+ ast_set2_flag(vmu, ast_true(value), VM_DELETE);
+ } else if (!strcasecmp(var, "saycid")){
+ ast_set2_flag(vmu, ast_true(value), VM_SAYCID);
+ } else if (!strcasecmp(var,"sendvoicemail")){
+ ast_set2_flag(vmu, ast_true(value), VM_SVMAIL);
+ } else if (!strcasecmp(var, "review")){
+ ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
+ } else if (!strcasecmp(var, "tempgreetwarn")){
+ ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);
+ } else if (!strcasecmp(var, "operator")){
+ ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);
+ } else if (!strcasecmp(var, "envelope")){
+ ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);
+ } else if (!strcasecmp(var, "sayduration")){
+ ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);
+ } else if (!strcasecmp(var, "saydurationm")){
+ if (sscanf(value, "%d", &x) == 1) {
+ vmu->saydurationm = x;
+ } else {
+ ast_log(LOG_WARNING, "Invalid min duration for say duration\n");
+ }
+ } else if (!strcasecmp(var, "forcename")){
+ ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);
+ } else if (!strcasecmp(var, "forcegreetings")){
+ ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);
+ } else if (!strcasecmp(var, "callback")) {
+ ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
+ } else if (!strcasecmp(var, "dialout")) {
+ ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
+ } else if (!strcasecmp(var, "exitcontext")) {
+ ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
+ } else if (!strcasecmp(var, "maxmsg")) {
+ vmu->maxmsg = atoi(value);
+ if (vmu->maxmsg <= 0) {
+ ast_log(LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %i\n", value, MAXMSG);
+ vmu->maxmsg = MAXMSG;
+ } else if (vmu->maxmsg > MAXMSGLIMIT) {
+ ast_log(LOG_WARNING, "Maximum number of messages per folder is %i. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
+ vmu->maxmsg = MAXMSGLIMIT;
+ }
+ } else if (!strcasecmp(var, "volgain")) {
+ sscanf(value, "%lf", &vmu->volgain);
+ } else if (!strcasecmp(var, "options")) {
+ apply_options(vmu, value);
+ }
+}
+
+static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
+{
+ int res;
+ if (!ast_strlen_zero(vmu->uniqueid)) {
+ res = ast_update_realtime("voicemail", "uniqueid", vmu->uniqueid, "password", password, NULL);
+ if (res > 0) {
+ ast_copy_string(vmu->password, password, sizeof(vmu->password));
+ res = 0;
+ } else if (!res) {
+ res = -1;
+ }
+ return res;
+ }
+ return -1;
+}
+
+static void apply_options(struct ast_vm_user *vmu, const char *options)
+{ /* Destructively Parse options and apply */
+ char *stringp;
+ char *s;
+ char *var, *value;
+ stringp = ast_strdupa(options);
+ while ((s = strsep(&stringp, "|"))) {
+ value = s;
+ if ((var = strsep(&value, "=")) && value) {
+ apply_option(vmu, var, value);
+ }
+ }
+}
+
+static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
+{
+ struct ast_variable *tmp;
+ tmp = var;
+ while (tmp) {
+ if (!strcasecmp(tmp->name, "vmsecret")) {
+ ast_copy_string(retval->password, tmp->value, sizeof(retval->password));
+ } else if (!strcasecmp(tmp->name, "secret") || !strcasecmp(tmp->name, "password")) { /* don't overwrite vmsecret if it exists */
+ if (ast_strlen_zero(retval->password))
+ ast_copy_string(retval->password, tmp->value, sizeof(retval->password));
+ } else if (!strcasecmp(tmp->name, "uniqueid")) {
+ ast_copy_string(retval->uniqueid, tmp->value, sizeof(retval->uniqueid));
+ } else if (!strcasecmp(tmp->name, "pager")) {
+ ast_copy_string(retval->pager, tmp->value, sizeof(retval->pager));
+ } else if (!strcasecmp(tmp->name, "email")) {
+ ast_copy_string(retval->email, tmp->value, sizeof(retval->email));
+ } else if (!strcasecmp(tmp->name, "fullname")) {
+ ast_copy_string(retval->fullname, tmp->value, sizeof(retval->fullname));
+ } else if (!strcasecmp(tmp->name, "context")) {
+ ast_copy_string(retval->context, tmp->value, sizeof(retval->context));
+#ifdef IMAP_STORAGE
+ } else if (!strcasecmp(tmp->name, "imapuser")) {
+ ast_copy_string(retval->imapuser, tmp->value, sizeof(retval->imapuser));
+ } else if (!strcasecmp(tmp->name, "imappassword") || !strcasecmp(tmp->name, "imapsecret")) {
+ ast_copy_string(retval->imappassword, tmp->value, sizeof(retval->imappassword));
+#endif
+ } else
+ apply_option(retval, tmp->name, tmp->value);
+ tmp = tmp->next;
+ }
+}
+
+static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
+{
+ struct ast_variable *var;
+ struct ast_vm_user *retval;
+
+ if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
+ if (!ivm)
+ ast_set_flag(retval, VM_ALLOCED);
+ else
+ memset(retval, 0, sizeof(*retval));
+ if (mailbox)
+ ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
+ populate_defaults(retval);
+ if (!context && ast_test_flag((&globalflags), VM_SEARCH))
+ var = ast_load_realtime("voicemail", "mailbox", mailbox, NULL);
+ else
+ var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, NULL);
+ if (var) {
+ apply_options_full(retval, var);
+ ast_variables_destroy(var);
+ } else {
+ if (!ivm)
+ free(retval);
+ retval = NULL;
+ }
+ }
+ return retval;
+}
+
+static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
+{
+ /* This function could be made to generate one from a database, too */
+ struct ast_vm_user *vmu=NULL, *cur;
+ AST_LIST_LOCK(&users);
+
+ if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
+ context = "default";
+
+ AST_LIST_TRAVERSE(&users, cur, list) {
+ if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
+ break;
+ if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
+ break;
+ }
+ if (cur) {
+ /* Make a copy, so that on a reload, we have no race */
+ if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
+ memcpy(vmu, cur, sizeof(*vmu));
+ ast_set2_flag(vmu, !ivm, VM_ALLOCED);
+ AST_LIST_NEXT(vmu, list) = NULL;
+ }
+ } else
+ vmu = find_user_realtime(ivm, context, mailbox);
+ AST_LIST_UNLOCK(&users);
+ return vmu;
+}
+
+static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
+{
+ /* This function could be made to generate one from a database, too */
+ struct ast_vm_user *cur;
+ int res = -1;
+ AST_LIST_LOCK(&users);
+ AST_LIST_TRAVERSE(&users, cur, list) {
+ if ((!context || !strcasecmp(context, cur->context)) &&
+ (!strcasecmp(mailbox, cur->mailbox)))
+ break;
+ }
+ if (cur) {
+ ast_copy_string(cur->password, newpass, sizeof(cur->password));
+ res = 0;
+ }
+ AST_LIST_UNLOCK(&users);
+ return res;
+}
+
+static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
+{
+ struct ast_config *cfg=NULL;
+ struct ast_variable *var=NULL;
+ struct ast_category *cat=NULL;
+ char *category=NULL, *value=NULL, *new=NULL;
+ const char *tmp=NULL;
+
+ if (!change_password_realtime(vmu, newpassword))
+ return;
+
+ /* check voicemail.conf */
+ if ((cfg = ast_config_load_with_comments(VOICEMAIL_CONFIG))) {
+ while ((category = ast_category_browse(cfg, category))) {
+ if (!strcasecmp(category, vmu->context)) {
+ tmp = ast_variable_retrieve(cfg, category, vmu->mailbox);
+ if (!tmp) {
+ ast_log(LOG_WARNING, "We could not find the mailbox.\n");
+ break;
+ }
+ value = strstr(tmp,",");
+ if (!value) {
+ ast_log(LOG_WARNING, "variable has bad format.\n");
+ break;
+ }
+ new = alloca((strlen(value)+strlen(newpassword)+1));
+ sprintf(new,"%s%s", newpassword, value);
+ if (!(cat = ast_category_get(cfg, category))) {
+ ast_log(LOG_WARNING, "Failed to get category structure.\n");
+ break;
+ }
+ ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
+ }
+ }
+ /* save the results */
+ reset_user_pw(vmu->context, vmu->mailbox, newpassword);
+ ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
+ config_text_file_save(VOICEMAIL_CONFIG, cfg, "AppVoicemail");
+ }
+ category = NULL;
+ var = NULL;
+ /* check users.conf and update the password stored for the mailbox*/
+ /* if no vmsecret entry exists create one. */
+ if ((cfg = ast_config_load_with_comments("users.conf"))) {
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "we are looking for %s\n", vmu->mailbox);
+ while ((category = ast_category_browse(cfg, category))) {
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "users.conf: %s\n", category);
+ if (!strcasecmp(category, vmu->mailbox)) {
+ if (!(tmp = ast_variable_retrieve(cfg, category, "vmsecret"))) {
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "looks like we need to make vmsecret!\n");
+ var = ast_variable_new("vmsecret", newpassword);
+ }
+ new = alloca(strlen(newpassword)+1);
+ sprintf(new, "%s", newpassword);
+ if (!(cat = ast_category_get(cfg, category))) {
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "failed to get category!\n");
+ break;
+ }
+ if (!var)
+ ast_variable_update(cat, "vmsecret", new, NULL, 0);
+ else
+ ast_variable_append(cat, var);
+ }
+ }
+ /* save the results and clean things up */
+ reset_user_pw(vmu->context, vmu->mailbox, newpassword);
+ ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
+ config_text_file_save("users.conf", cfg, "AppVoicemail");
+ }
+}
+
+static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
+{
+ char buf[255];
+ snprintf(buf,255,"%s %s %s %s",ext_pass_cmd,vmu->context,vmu->mailbox,newpassword);
+ if (!ast_safe_system(buf)) {
+ ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
+ /* Reset the password in memory, too */
+ reset_user_pw(vmu->context, vmu->mailbox, newpassword);
+ }
+}
+
+static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
+{
+ return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
+}
+
+static int make_file(char *dest, const int len, const char *dir, const int num)
+{
+ return snprintf(dest, len, "%s/msg%04d", dir, num);
+}
+
+/* same as mkstemp, but return a FILE * */
+static FILE *vm_mkftemp(char *template)
+{
+ FILE *p = NULL;
+ int pfd = mkstemp(template);
+ chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
+ if (pfd > -1) {
+ p = fdopen(pfd, "w+");
+ if (!p) {
+ close(pfd);
+ pfd = -1;
+ }
+ }
+ return p;
+}
+
+/*! \brief basically mkdir -p $dest/$context/$ext/$folder
+ * \param dest String. base directory.
+ * \param len Length of dest.
+ * \param context String. Ignored if is null or empty string.
+ * \param ext String. Ignored if is null or empty string.
+ * \param folder String. Ignored if is null or empty string.
+ * \return -1 on failure, 0 on success.
+ */
+static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
+{
+ mode_t mode = VOICEMAIL_DIR_MODE;
+
+ if (!ast_strlen_zero(context)) {
+ make_dir(dest, len, context, "", "");
+ if (mkdir(dest, mode) && errno != EEXIST) {
+ ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", dest, strerror(errno));
+ return -1;
+ }
+ }
+ if (!ast_strlen_zero(ext)) {
+ make_dir(dest, len, context, ext, "");
+ if (mkdir(dest, mode) && errno != EEXIST) {
+ ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", dest, strerror(errno));
+ return -1;
+ }
+ }
+ if (!ast_strlen_zero(folder)) {
+ make_dir(dest, len, context, ext, folder);
+ if (mkdir(dest, mode) && errno != EEXIST) {
+ ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", dest, strerror(errno));
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static char *mbox(int id)
+{
+ static char *msgs[] = {
+ "INBOX",
+ "Old",
+ "Work",
+ "Family",
+ "Friends",
+ "Cust1",
+ "Cust2",
+ "Cust3",
+ "Cust4",
+ "Cust5",
+ };
+ return (id >= 0 && id < (sizeof(msgs)/sizeof(msgs[0]))) ? msgs[id] : "tmp";
+}
+
+static void free_user(struct ast_vm_user *vmu)
+{
+ if (ast_test_flag(vmu, VM_ALLOCED))
+ free(vmu);
+}
+
+/* All IMAP-specific functions should go in this block. This
+ * keeps them from being spread out all over the code */
+#ifdef IMAP_STORAGE
+static void vm_imap_delete(int msgnum, struct ast_vm_user *vmu)
+{
+ char arg[10];
+ struct vm_state *vms;
+ unsigned long messageNum;
+
+ /* Greetings aren't stored in IMAP, so we can't delete them there */
+ if (msgnum < 0) {
+ return;
+ }
+
+ if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
+ ast_log(LOG_WARNING, "Couldn't find a vm_state for mailbox %s. Unable to set \\DELETED flag for message %d\n", vmu->mailbox, msgnum);
+ return;
+ }
+
+ /* find real message number based on msgnum */
+ /* this may be an index into vms->msgArray based on the msgnum. */
+ messageNum = vms->msgArray[msgnum];
+ if (messageNum == 0) {
+ ast_log(LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n",msgnum,messageNum);
+ return;
+ }
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "deleting msgnum %d, which is mailbox message %lu\n",msgnum,messageNum);
+ /* delete message */
+ snprintf (arg, sizeof(arg), "%lu",messageNum);
+ ast_mutex_lock(&vms->lock);
+ mail_setflag (vms->mailstream,arg,"\\DELETED");
+ ast_mutex_unlock(&vms->lock);
+}
+
+static int imap_retrieve_file(const char *dir, const int msgnum, const struct ast_vm_user *vmu)
+{
+ BODY *body;
+ char *header_content;
+ char *attachedfilefmt;
+ const char *cid_num;
+ const char *cid_name;
+ const char *duration;
+ const char *context;
+ const char *category;
+ const char *origtime;
+ struct vm_state *vms;
+ char text_file[PATH_MAX];
+ FILE *text_file_ptr;
+
+ /* Greetings are not stored on the IMAP server, so we should not
+ * attempt to retrieve them.
+ */
+ if (msgnum < 0) {
+ return 0;
+ }
+
+ /* Before anything can happen, we need a vm_state so that we can
+ * actually access the imap server through the vms->mailstream
+ */
+ if(!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
+ /* This should not happen. If it does, then I guess we'd
+ * need to create the vm_state, extract which mailbox to
+ * open, and then set up the msgArray so that the correct
+ * IMAP message could be accessed. If I have seen correctly
+ * though, the vms should be obtainable from the vmstates list
+ * and should have its msgArray properly set up.
+ */
+ ast_log(LOG_ERROR, "Couldn't find a vm_state for mailbox %s!!! Oh no!\n", vmu->mailbox);
+ }
+
+ make_file(vms->fn, sizeof(vms->fn), dir, msgnum);
+
+ /* Don't try to retrieve a message from IMAP if it already is on the file system */
+ if (ast_fileexists(vms->fn, NULL, NULL) > 0) {
+ return 0;
+ }
+
+ if (option_debug > 2)
+ ast_log (LOG_DEBUG,"Before mail_fetchheaders, curmsg is: %d, imap messages is %lu\n", msgnum, vms->msgArray[msgnum]);
+ if (vms->msgArray[msgnum] == 0) {
+ ast_log (LOG_WARNING,"Trying to access unknown message\n");
+ return -1;
+ }
+
+ /* This will only work for new messages... */
+ ast_mutex_lock(&vms->lock);
+ header_content = mail_fetchheader (vms->mailstream, vms->msgArray[msgnum]);
+ ast_mutex_unlock(&vms->lock);
+ /* empty string means no valid header */
+ if (ast_strlen_zero(header_content)) {
+ ast_log (LOG_ERROR,"Could not fetch header for message number %ld\n",vms->msgArray[msgnum]);
+ return -1;
+ }
+
+ ast_mutex_lock(&vms->lock);
+ mail_fetchstructure (vms->mailstream,vms->msgArray[msgnum],&body);
+ ast_mutex_unlock(&vms->lock);
+
+ /* We have the body, now we extract the file name of the first attachment. */
+ if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
+ attachedfilefmt = ast_strdupa(body->nested.part->next->body.parameter->value);
+ } else {
+ ast_log(LOG_ERROR, "There is no file attached to this IMAP message.\n");
+ return -1;
+ }
+
+ /* Find the format of the attached file */
+
+ strsep(&attachedfilefmt, ".");
+ if (!attachedfilefmt) {
+ ast_log(LOG_ERROR, "File format could not be obtained from IMAP message attachment\n");
+ return -1;
+ }
+
+ save_body(body, vms, "2", attachedfilefmt);
+
+ /* Get info from headers!! */
+ snprintf(text_file, sizeof(text_file), "%s.%s", vms->fn, "txt");
+
+ if (!(text_file_ptr = fopen(text_file, "w"))) {
+ ast_log(LOG_WARNING, "Unable to open/create file %s: %s\n", text_file, strerror(errno));
+ }
+
+ fprintf(text_file_ptr, "%s\n", "[message]");
+
+ cid_num = get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Num:");
+ cid_name = get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Name:");
+ fprintf(text_file_ptr, "callerid=\"%s\" <%s>\n", S_OR(cid_name, ""), S_OR(cid_num, ""));
+ context = get_header_by_tag(header_content, "X-Asterisk-VM-Context:");
+ fprintf(text_file_ptr, "context=%s\n", S_OR(context, ""));
+ origtime = get_header_by_tag(header_content, "X-Asterisk-VM-Orig-time:");
+ fprintf(text_file_ptr, "origtime=%s\n", S_OR(origtime, ""));
+ duration = get_header_by_tag(header_content, "X-Asterisk-VM-Duration:");
+ fprintf(text_file_ptr, "duration=%s\n", S_OR(origtime, ""));
+ category = get_header_by_tag(header_content, "X-Asterisk-VM-Category:");
+ fprintf(text_file_ptr, "category=%s\n", S_OR(category, ""));
+
+ fclose(text_file_ptr);
+ return 0;
+}
+
+static int folder_int(const char *folder)
+{
+ /*assume a NULL folder means INBOX*/
+ if (!folder)
+ return 0;
+ if (!strcasecmp(folder, "INBOX"))
+ return 0;
+ else if (!strcasecmp(folder, "Old"))
+ return 1;
+ else if (!strcasecmp(folder, "Work"))
+ return 2;
+ else if (!strcasecmp(folder, "Family"))
+ return 3;
+ else if (!strcasecmp(folder, "Friends"))
+ return 4;
+ else if (!strcasecmp(folder, "Cust1"))
+ return 5;
+ else if (!strcasecmp(folder, "Cust2"))
+ return 6;
+ else if (!strcasecmp(folder, "Cust3"))
+ return 7;
+ else if (!strcasecmp(folder, "Cust4"))
+ return 8;
+ else if (!strcasecmp(folder, "Cust5"))
+ return 9;
+ else /*assume they meant INBOX if folder is not found otherwise*/
+ return 0;
+}
+
+static int messagecount(const char *context, const char *mailbox, const char *folder)
+{
+ SEARCHPGM *pgm;
+ SEARCHHEADER *hdr;
+
+ struct ast_vm_user *vmu, vmus;
+ struct vm_state *vms_p;
+ int ret = 0;
+ int fold = folder_int(folder);
+
+ if (ast_strlen_zero(mailbox))
+ return 0;
+
+ /* We have to get the user before we can open the stream! */
+ /* ast_log (LOG_DEBUG,"Before find_user, context is %s and mailbox is %s\n",context,mailbox); */
+ vmu = find_user(&vmus, context, mailbox);
+ if (!vmu) {
+ ast_log (LOG_ERROR,"Couldn't find mailbox %s in context %s\n",mailbox,context);
+ return -1;
+ } else {
+ /* No IMAP account available */
+ if (vmu->imapuser[0] == '\0') {
+ ast_log (LOG_WARNING,"IMAP user not set for mailbox %s\n",vmu->mailbox);
+ return -1;
+ }
+ }
+
+ /* check if someone is accessing this box right now... */
+ vms_p = get_vm_state_by_imapuser(vmu->imapuser,1);
+ if (!vms_p) {
+ vms_p = get_vm_state_by_mailbox(mailbox, context, 1);
+ }
+ if (vms_p) {
+ if (option_debug > 2)
+ ast_log (LOG_DEBUG,"Returning before search - user is logged in\n");
+ if (fold == 0) {/*INBOX*/
+ return vms_p->newmessages;
+ }
+ if (fold == 1) {/*Old messages*/
+ return vms_p->oldmessages;
+ }
+ }
+
+ /* add one if not there... */
+ vms_p = get_vm_state_by_imapuser(vmu->imapuser,0);
+ if (!vms_p) {
+ vms_p = get_vm_state_by_mailbox(mailbox, context, 0);
+ }
+
+ if (!vms_p) {
+ if (!(vms_p = create_vm_state_from_user(vmu))) {
+ ast_log(LOG_WARNING, "Unable to allocate space for new vm_state!\n");
+ return -1;
+ }
+ }
+ ret = init_mailstream(vms_p, fold);
+ if (!vms_p->mailstream) {
+ ast_log (LOG_ERROR,"IMAP mailstream is NULL\n");
+ return -1;
+ }
+ if (ret == 0) {
+ ast_mutex_lock(&vms_p->lock);
+ pgm = mail_newsearchpgm ();
+ hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)mailbox);
+ pgm->header = hdr;
+ if (fold != 1) {
+ pgm->unseen = 1;
+ pgm->seen = 0;
+ }
+ /* In the special case where fold is 1 (old messages) we have to do things a bit
+ * differently. Old messages are stored in the INBOX but are marked as "seen"
+ */
+ else {
+ pgm->unseen = 0;
+ pgm->seen = 1;
+ }
+ pgm->undeleted = 1;
+ pgm->deleted = 0;
+
+ vms_p->vmArrayIndex = 0;
+ mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
+ if (fold == 0)
+ vms_p->newmessages = vms_p->vmArrayIndex;
+ if (fold == 1)
+ vms_p->oldmessages = vms_p->vmArrayIndex;
+ /*Freeing the searchpgm also frees the searchhdr*/
+ mail_free_searchpgm(&pgm);
+ ast_mutex_unlock(&vms_p->lock);
+ vms_p->updated = 0;
+ return vms_p->vmArrayIndex;
+ } else {
+ ast_mutex_lock(&vms_p->lock);
+ mail_ping(vms_p->mailstream);
+ ast_mutex_unlock(&vms_p->lock);
+ }
+ return 0;
+}
+
+static int imap_store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms)
+{
+ char *myserveremail = serveremail;
+ char fn[PATH_MAX];
+ char mailbox[256];
+ char *stringp;
+ FILE *p=NULL;
+ char tmp[80] = "/tmp/astmail-XXXXXX";
+ long len;
+ void *buf;
+ int tempcopy = 0;
+ STRING str;
+
+ /*Greetings are not retrieved from IMAP, so there is no reason to attempt storing them there either*/
+ if (msgnum < 0)
+ return 0;
+
+ /* Attach only the first format */
+ fmt = ast_strdupa(fmt);
+ stringp = fmt;
+ strsep(&stringp, "|");
+
+ if (!ast_strlen_zero(vmu->serveremail))
+ myserveremail = vmu->serveremail;
+
+ make_file(fn, sizeof(fn), dir, msgnum);
+
+ if (ast_strlen_zero(vmu->email)) {
+ /*we need the vmu->email to be set when we call make_email_file, but if we keep it set,
+ * a duplicate e-mail will be created. So at the end of this function, we will revert back to an empty
+ * string if tempcopy is 1
+ */
+ ast_copy_string(vmu->email, vmu->imapuser, sizeof(vmu->email));
+ tempcopy = 1;
+ }
+
+ if (!strcmp(fmt, "wav49"))
+ fmt = "WAV";
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Storing file '%s', format '%s'\n", fn, fmt);
+ /* Make a temporary file instead of piping directly to sendmail, in case the mail
+ command hangs */
+ if ((p = vm_mkftemp(tmp)) == NULL) {
+ ast_log(LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
+ if (tempcopy)
+ *(vmu->email) = '\0';
+ return -1;
+ } else {
+ make_email_file(p, myserveremail, vmu, msgnum, vmu->context, vmu->mailbox, S_OR(chan->cid.cid_num, NULL), S_OR(chan->cid.cid_name, NULL), fn, fmt, duration, 1, chan, NULL, 1);
+ /* read mail file to memory */
+ len = ftell(p);
+ rewind(p);
+ if ((buf = ast_malloc(len+1)) == NIL) {
+ ast_log(LOG_ERROR, "Can't allocate %ld bytes to read message\n", len+1);
+ fclose(p);
+ return -1;
+ }
+ if (fread(buf, len, 1, p) != 1) {
+ ast_log(LOG_WARNING, "Short read: %s\n", strerror(errno));
+ }
+ ((char *)buf)[len] = '\0';
+ INIT(&str, mail_string, buf, len);
+ init_mailstream(vms, 0);
+ imap_mailbox_name(mailbox, sizeof(mailbox), vms, 0, 1);
+ ast_mutex_lock(&vms->lock);
+ if (!mail_append(vms->mailstream, mailbox, &str))
+ ast_log(LOG_ERROR, "Error while sending the message to %s\n", mailbox);
+ ast_mutex_unlock(&vms->lock);
+ fclose(p);
+ unlink(tmp);
+ ast_free(buf);
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "%s stored\n", fn);
+ /* Using messagecount to populate the last place in the msgArray
+ * is less than optimal, but it's the only way given the current setup
+ */
+ messagecount(vmu->context, vmu->mailbox, "INBOX");
+ }
+ if (tempcopy)
+ *(vmu->email) = '\0';
+ return 0;
+
+}
+
+static int inboxcount(const char *mailbox_context, int *newmsgs, int *oldmsgs)
+{
+ char tmp[PATH_MAX] = "";
+ char *mailboxnc;
+ char *context;
+ char *mb;
+ char *cur;
+ if (newmsgs)
+ *newmsgs = 0;
+ if (oldmsgs)
+ *oldmsgs = 0;
+
+ if (option_debug > 2)
+ ast_log (LOG_DEBUG,"Mailbox is set to %s\n",mailbox_context);
+ /* If no mailbox, return immediately */
+ if (ast_strlen_zero(mailbox_context))
+ return 0;
+
+ ast_copy_string(tmp, mailbox_context, sizeof(tmp));
+ context = strchr(tmp, '@');
+ if (strchr(mailbox_context, ',')) {
+ int tmpnew, tmpold;
+ ast_copy_string(tmp, mailbox_context, sizeof(tmp));
+ mb = tmp;
+ while ((cur = strsep(&mb, ", "))) {
+ if (!ast_strlen_zero(cur)) {
+ if (inboxcount(cur, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
+ return -1;
+ else {
+ if (newmsgs)
+ *newmsgs += tmpnew;
+ if (oldmsgs)
+ *oldmsgs += tmpold;
+ }
+ }
+ }
+ return 0;
+ }
+ if (context) {
+ *context = '\0';
+ mailboxnc = tmp;
+ context++;
+ } else {
+ context = "default";
+ mailboxnc = (char *)mailbox_context;
+ }
+ if (newmsgs) {
+ if ((*newmsgs = messagecount(context, mailboxnc, "INBOX")) < 0)
+ return -1;
+ }
+ if (oldmsgs) {
+ if ((*oldmsgs = messagecount(context, mailboxnc, "Old")) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+
+static int has_voicemail(const char *mailbox, const char *folder)
+{
+ char tmp[256], *tmp2, *mbox, *context;
+ ast_copy_string(tmp, mailbox, sizeof(tmp));
+ tmp2 = tmp;
+ if (strchr(tmp2, ',')) {
+ while ((mbox = strsep(&tmp2, ","))) {
+ if (!ast_strlen_zero(mbox)) {
+ if (has_voicemail(mbox, folder))
+ return 1;
+ }
+ }
+ }
+ if ((context= strchr(tmp, '@')))
+ *context++ = '\0';
+ else
+ context = "default";
+ return messagecount(context, tmp, folder) ? 1 : 0;
+}
+
+static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir)
+{
+ struct vm_state *sendvms = NULL, *destvms = NULL;
+ char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/
+ if (msgnum >= recip->maxmsg) {
+ ast_log(LOG_WARNING, "Unable to copy mail, mailbox %s is full\n", recip->mailbox);
+ return -1;
+ }
+ if (!(sendvms = get_vm_state_by_imapuser(vmu->imapuser, 0))) {
+ ast_log(LOG_ERROR, "Couldn't get vm_state for originator's mailbox!!\n");
+ return -1;
+ }
+ if (!(destvms = get_vm_state_by_imapuser(recip->imapuser, 0))) {
+ ast_log(LOG_ERROR, "Couldn't get vm_state for destination mailbox!\n");
+ return -1;
+ }
+ snprintf(messagestring, sizeof(messagestring), "%ld", sendvms->msgArray[msgnum]);
+ ast_mutex_lock(&sendvms->lock);
+ if ((mail_copy(sendvms->mailstream, messagestring, (char *) mbox(imbox)) == T)) {
+ ast_mutex_unlock(&sendvms->lock);
+ return 0;
+ }
+ ast_mutex_unlock(&sendvms->lock);
+ ast_log(LOG_WARNING, "Unable to copy message from mailbox %s to mailbox %s\n", vmu->mailbox, recip->mailbox);
+ return -1;
+}
+
+static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int use_folder)
+{
+ char tmp[256], *t = tmp;
+ size_t left = sizeof(tmp);
+
+ if (box == 1) {
+ ast_copy_string(vms->curbox, mbox(0), sizeof(vms->curbox));
+ snprintf(vms->vmbox, sizeof(vms->vmbox), "vm-%s", mbox(1));
+ } else {
+ ast_copy_string(vms->curbox, mbox(box), sizeof(vms->curbox));
+ snprintf(vms->vmbox, sizeof(vms->vmbox), "vm-%s", vms->curbox);
+ }
+
+ /* Build up server information */
+ ast_build_string(&t, &left, "{%s:%s/imap", imapserver, imapport);
+
+ /* Add authentication user if present */
+ if (!ast_strlen_zero(authuser))
+ ast_build_string(&t, &left, "/authuser=%s", authuser);
+
+ /* Add flags if present */
+ if (!ast_strlen_zero(imapflags))
+ ast_build_string(&t, &left, "/%s", imapflags);
+
+ /* End with username */
+ ast_build_string(&t, &left, "/user=%s}", vms->imapuser);
+
+ if (box == 0 || box == 1)
+ snprintf(spec, len, "%s%s", tmp, use_folder? imapfolder: "INBOX");
+ else
+ snprintf(spec, len, "%s%s%c%s", tmp, imapfolder, delimiter, mbox(box));
+}
+
+static int init_mailstream(struct vm_state *vms, int box)
+{
+ MAILSTREAM *stream = NIL;
+ long debug;
+ char tmp[256];
+
+ if (!vms) {
+ ast_log (LOG_ERROR,"vm_state is NULL!\n");
+ return -1;
+ }
+ if (option_debug > 2)
+ ast_log (LOG_DEBUG,"vm_state user is:%s\n",vms->imapuser);
+ if (vms->mailstream == NIL || !vms->mailstream) {
+ if (option_debug)
+ ast_log (LOG_DEBUG,"mailstream not set.\n");
+ } else {
+ stream = vms->mailstream;
+ }
+ /* debug = T; user wants protocol telemetry? */
+ debug = NIL; /* NO protocol telemetry? */
+
+ if (delimiter == '\0') { /* did not probe the server yet */
+ char *cp;
+#ifdef USE_SYSTEM_IMAP
+#include <imap/linkage.c>
+#elif defined(USE_SYSTEM_CCLIENT)
+#include <c-client/linkage.c>
+#else
+#include "linkage.c"
+#endif
+ /* Connect to INBOX first to get folders delimiter */
+ imap_mailbox_name(tmp, sizeof(tmp), vms, 0, 1);
+ ast_mutex_lock(&vms->lock);
+ stream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
+ ast_mutex_unlock(&vms->lock);
+ if (stream == NIL) {
+ ast_log (LOG_ERROR, "Can't connect to imap server %s\n", tmp);
+ return -1;
+ }
+ get_mailbox_delimiter(stream);
+ /* update delimiter in imapfolder */
+ for (cp = imapfolder; *cp; cp++)
+ if (*cp == '/')
+ *cp = delimiter;
+ }
+ /* Now connect to the target folder */
+ imap_mailbox_name(tmp, sizeof(tmp), vms, box, 1);
+ if (option_debug > 2)
+ ast_log (LOG_DEBUG,"Before mail_open, server: %s, box:%d\n", tmp, box);
+ ast_mutex_lock(&vms->lock);
+ vms->mailstream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
+ ast_mutex_unlock(&vms->lock);
+ if (vms->mailstream == NIL) {
+ return -1;
+ } else {
+ return 0;
+ }
+}
+
+static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box)
+{
+ SEARCHPGM *pgm;
+ SEARCHHEADER *hdr;
+ int ret;
+
+ ast_copy_string(vms->imapuser,vmu->imapuser, sizeof(vms->imapuser));
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG,"Before init_mailstream, user is %s\n",vmu->imapuser);
+ ret = init_mailstream(vms, box);
+ if (ret != 0 || !vms->mailstream) {
+ ast_log (LOG_ERROR,"Could not initialize mailstream\n");
+ return -1;
+ }
+
+ create_dirpath(vms->curdir, sizeof(vms->curdir), vmu->context, vms->username, vms->curbox);
+
+ /* Check Quota */
+ if (box == 0) {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Mailbox name set to: %s, about to check quotas\n", mbox(box));
+ check_quota(vms,(char *)mbox(box));
+ }
+
+ ast_mutex_lock(&vms->lock);
+ pgm = mail_newsearchpgm();
+
+ /* Check IMAP folder for Asterisk messages only... */
+ hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", vmu->mailbox);
+ pgm->header = hdr;
+ pgm->deleted = 0;
+ pgm->undeleted = 1;
+
+ /* if box = 0, check for new, if box = 1, check for read */
+ if (box == 0) {
+ pgm->unseen = 1;
+ pgm->seen = 0;
+ } else if (box == 1) {
+ pgm->seen = 1;
+ pgm->unseen = 0;
+ }
+
+ vms->vmArrayIndex = 0;
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG,"Before mail_search_full, user is %s\n",vmu->imapuser);
+ mail_search_full (vms->mailstream, NULL, pgm, NIL);
+
+ vms->lastmsg = vms->vmArrayIndex - 1;
+
+ mail_free_searchpgm(&pgm);
+ ast_mutex_unlock(&vms->lock);
+ return 0;
+}
+
+static void write_file(char *filename, char *buffer, unsigned long len)
+{
+ FILE *output;
+
+ output = fopen (filename, "w");
+ if (fwrite (buffer, len, 1, output) != 1) {
+ ast_log(LOG_WARNING, "Short write: %s\n", strerror(errno));
+ }
+ fclose (output);
+}
+
+void mm_searched(MAILSTREAM *stream, unsigned long number)
+{
+ struct vm_state *vms;
+ char *mailbox;
+ char *user;
+ mailbox = stream->mailbox;
+ user = get_user_by_mailbox(mailbox);
+ vms = get_vm_state_by_imapuser(user,2);
+ if (vms) {
+ if (option_debug > 2)
+ ast_log (LOG_DEBUG, "saving mailbox message number %lu as message %d. Interactive set to %d\n",number,vms->vmArrayIndex,vms->interactive);
+ vms->msgArray[vms->vmArrayIndex++] = number;
+ } else {
+ ast_log (LOG_ERROR, "No state found.\n");
+ }
+}
+
+static struct ast_vm_user *find_user_realtime_imapuser(const char *imapuser)
+{
+ struct ast_variable *var;
+ struct ast_vm_user *vmu;
+
+ vmu = ast_calloc(1, sizeof *vmu);
+ if (!vmu)
+ return NULL;
+ ast_set_flag(vmu, VM_ALLOCED);
+ populate_defaults(vmu);
+
+ var = ast_load_realtime("voicemail", "imapuser", imapuser, NULL);
+ if (var) {
+ apply_options_full(vmu, var);
+ ast_variables_destroy(var);
+ return vmu;
+ } else {
+ free(vmu);
+ return NULL;
+ }
+}
+
+/* Interfaces to C-client */
+
+void mm_exists(MAILSTREAM * stream, unsigned long number)
+{
+ /* mail_ping will callback here if new mail! */
+ if (option_debug > 3)
+ ast_log (LOG_DEBUG, "Entering EXISTS callback for message %ld\n", number);
+ if (number == 0) return;
+ set_update(stream);
+}
+
+
+void mm_expunged(MAILSTREAM * stream, unsigned long number)
+{
+ /* mail_ping will callback here if expunged mail! */
+ if (option_debug > 3)
+ ast_log (LOG_DEBUG, "Entering EXPUNGE callback for message %ld\n", number);
+ if (number == 0) return;
+ set_update(stream);
+}
+
+
+void mm_flags(MAILSTREAM * stream, unsigned long number)
+{
+ /* mail_ping will callback here if read mail! */
+ if (option_debug > 3)
+ ast_log (LOG_DEBUG, "Entering FLAGS callback for message %ld\n", number);
+ if (number == 0) return;
+ set_update(stream);
+}
+
+
+void mm_notify(MAILSTREAM * stream, char *string, long errflg)
+{
+ mm_log (string, errflg);
+}
+
+
+void mm_list(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
+{
+ if (delimiter == '\0') {
+ delimiter = delim;
+ }
+ if (option_debug > 4) {
+ ast_log(LOG_DEBUG, "Delimiter set to %c and mailbox %s\n",delim, mailbox);
+ if (attributes & LATT_NOINFERIORS)
+ ast_log(LOG_DEBUG, "no inferiors\n");
+ if (attributes & LATT_NOSELECT)
+ ast_log(LOG_DEBUG, "no select\n");
+ if (attributes & LATT_MARKED)
+ ast_log(LOG_DEBUG, "marked\n");
+ if (attributes & LATT_UNMARKED)
+ ast_log(LOG_DEBUG, "unmarked\n");
+ }
+}
+
+
+void mm_lsub(MAILSTREAM * stream, int delimiter, char *mailbox, long attributes)
+{
+ if (option_debug > 4) {
+ ast_log(LOG_DEBUG, "Delimiter set to %c and mailbox %s\n",delimiter, mailbox);
+ if (attributes & LATT_NOINFERIORS)
+ ast_log(LOG_DEBUG, "no inferiors\n");
+ if (attributes & LATT_NOSELECT)
+ ast_log(LOG_DEBUG, "no select\n");
+ if (attributes & LATT_MARKED)
+ ast_log(LOG_DEBUG, "marked\n");
+ if (attributes & LATT_UNMARKED)
+ ast_log(LOG_DEBUG, "unmarked\n");
+ }
+}
+
+
+void mm_status(MAILSTREAM * stream, char *mailbox, MAILSTATUS * status)
+{
+ ast_log (LOG_NOTICE," Mailbox %s", mailbox);
+ if (status->flags & SA_MESSAGES)
+ ast_log (LOG_NOTICE,", %lu messages", status->messages);
+ if (status->flags & SA_RECENT)
+ ast_log (LOG_NOTICE,", %lu recent", status->recent);
+ if (status->flags & SA_UNSEEN)
+ ast_log (LOG_NOTICE,", %lu unseen", status->unseen);
+ if (status->flags & SA_UIDVALIDITY)
+ ast_log (LOG_NOTICE,", %lu UID validity", status->uidvalidity);
+ if (status->flags & SA_UIDNEXT)
+ ast_log (LOG_NOTICE,", %lu next UID", status->uidnext);
+ ast_log (LOG_NOTICE,"\n");
+}
+
+
+void mm_log(char *string, long errflg)
+{
+ switch ((short) errflg) {
+ case NIL:
+ if (option_debug)
+ ast_log(LOG_DEBUG,"IMAP Info: %s\n", string);
+ break;
+ case PARSE:
+ case WARN:
+ ast_log (LOG_WARNING,"IMAP Warning: %s\n", string);
+ break;
+ case ERROR:
+ ast_log (LOG_ERROR,"IMAP Error: %s\n", string);
+ break;
+ }
+}
+
+
+void mm_dlog(char *string)
+{
+ ast_log (LOG_NOTICE, "%s\n", string);
+}
+
+
+void mm_login(NETMBX * mb, char *user, char *pwd, long trial)
+{
+ struct ast_vm_user *vmu;
+
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "Entering callback mm_login\n");
+
+ ast_copy_string(user, mb->user, MAILTMPLEN);
+
+ /* We should only do this when necessary */
+ if (!ast_strlen_zero(authpassword)) {
+ ast_copy_string(pwd, authpassword, MAILTMPLEN);
+ } else {
+ AST_LIST_TRAVERSE(&users, vmu, list) {
+ if (!strcasecmp(mb->user, vmu->imapuser)) {
+ ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
+ break;
+ }
+ }
+ if (!vmu) {
+ if ((vmu = find_user_realtime_imapuser(mb->user))) {
+ ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
+ free_user(vmu);
+ }
+ }
+ }
+}
+
+
+void mm_critical(MAILSTREAM * stream)
+{
+}
+
+
+void mm_nocritical(MAILSTREAM * stream)
+{
+}
+
+
+long mm_diskerror(MAILSTREAM * stream, long errcode, long serious)
+{
+ kill (getpid (), SIGSTOP);
+ return NIL;
+}
+
+
+void mm_fatal(char *string)
+{
+ ast_log(LOG_ERROR,"IMAP access FATAL error: %s\n", string);
+}
+
+/* C-client callback to handle quota */
+static void mm_parsequota(MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
+{
+ struct vm_state *vms;
+ char *mailbox;
+ char *user;
+ unsigned long usage = 0;
+ unsigned long limit = 0;
+
+ while (pquota) {
+ usage = pquota->usage;
+ limit = pquota->limit;
+ pquota = pquota->next;
+ }
+
+ mailbox = stream->mailbox;
+ user = get_user_by_mailbox(mailbox);
+ vms = get_vm_state_by_imapuser(user,2);
+ if (vms) {
+ if (option_debug > 2)
+ ast_log (LOG_DEBUG, "User %s usage is %lu, limit is %lu\n",user,usage,limit);
+ vms->quota_usage = usage;
+ vms->quota_limit = limit;
+ } else {
+ ast_log (LOG_ERROR, "No state found.\n");
+ }
+}
+
+static char *get_header_by_tag(char *header, char *tag)
+{
+ char *start;
+ int taglen;
+ char *eol_pnt;
+
+ if (!header || !tag)
+ return NULL;
+
+ taglen = strlen(tag) + 1;
+ if (taglen < 1)
+ return NULL;
+
+ start = strstr(header, tag);
+ if (!start)
+ return NULL;
+
+ ast_mutex_lock(&imaptemp_lock);
+ ast_copy_string(imaptemp, start+taglen, sizeof(imaptemp));
+ ast_mutex_unlock(&imaptemp_lock);
+ if ((eol_pnt = strchr(imaptemp,'\r')) || (eol_pnt = strchr(imaptemp,'\n')))
+ *eol_pnt = '\0';
+ return imaptemp;
+}
+
+static char *get_user_by_mailbox(char *mailbox)
+{
+ char *start, *quote;
+ char *eol_pnt;
+
+ if (!mailbox)
+ return NULL;
+
+ start = strstr(mailbox,"/user=");
+ if (!start)
+ return NULL;
+
+ ast_mutex_lock(&imaptemp_lock);
+ ast_copy_string(imaptemp, start+6, sizeof(imaptemp));
+ ast_mutex_unlock(&imaptemp_lock);
+
+ quote = strchr(imaptemp,'\"');
+ if (!quote) { /* if username is not in quotes */
+ eol_pnt = strchr(imaptemp,'/');
+ if (!eol_pnt) {
+ eol_pnt = strchr(imaptemp,'}');
+ }
+ *eol_pnt = '\0';
+ return imaptemp;
+ } else {
+ eol_pnt = strchr(imaptemp+1,'\"');
+ *eol_pnt = '\0';
+ return imaptemp+1;
+ }
+}
+
+static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu)
+{
+ struct vm_state *vms_p;
+
+ if (option_debug > 4)
+ ast_log(LOG_DEBUG,"Adding new vmstate for %s\n",vmu->imapuser);
+ if (!(vms_p = ast_calloc(1, sizeof(*vms_p))))
+ return NULL;
+ ast_copy_string(vms_p->imapuser,vmu->imapuser, sizeof(vms_p->imapuser));
+ ast_copy_string(vms_p->username, vmu->mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
+ ast_copy_string(vms_p->context, vmu->context, sizeof(vms_p->context));
+ vms_p->mailstream = NIL; /* save for access from interactive entry point */
+ if (option_debug > 4)
+ ast_log(LOG_DEBUG,"Copied %s to %s\n",vmu->imapuser,vms_p->imapuser);
+ vms_p->updated = 1;
+ /* set mailbox to INBOX! */
+ ast_copy_string(vms_p->curbox, mbox(0), sizeof(vms_p->curbox));
+ init_vm_state(vms_p);
+ vmstate_insert(vms_p);
+ return vms_p;
+}
+
+static struct vm_state *get_vm_state_by_imapuser(char *user, int interactive)
+{
+ struct vmstate *vlist = NULL;
+
+ ast_mutex_lock(&vmstate_lock);
+ vlist = vmstates;
+ while (vlist) {
+ if (vlist->vms) {
+ if (vlist->vms->imapuser) {
+ if (!strcmp(vlist->vms->imapuser,user)) {
+ if (interactive == 2) {
+ ast_mutex_unlock(&vmstate_lock);
+ return vlist->vms;
+ } else if (vlist->vms->interactive == interactive) {
+ ast_mutex_unlock(&vmstate_lock);
+ return vlist->vms;
+ }
+ }
+ } else {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, " error: imapuser is NULL for %s\n",user);
+ }
+ } else {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, " error: vms is NULL for %s\n",user);
+ }
+ vlist = vlist->next;
+ }
+ ast_mutex_unlock(&vmstate_lock);
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "%s not found in vmstates\n",user);
+ return NULL;
+}
+
+static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive)
+{
+ struct vmstate *vlist = NULL;
+ const char *local_context = S_OR(context, "default");
+
+ ast_mutex_lock(&vmstate_lock);
+ vlist = vmstates;
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Mailbox set to %s\n",mailbox);
+ while (vlist) {
+ if (vlist->vms) {
+ if (vlist->vms->username && vlist->vms->context) {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, " comparing mailbox %s (i=%d) to vmstate mailbox %s (i=%d)\n",mailbox,interactive,vlist->vms->username,vlist->vms->interactive);
+ if (!strcmp(vlist->vms->username,mailbox) && !(strcmp(vlist->vms->context, local_context)) && vlist->vms->interactive == interactive) {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, " Found it!\n");
+ ast_mutex_unlock(&vmstate_lock);
+ return vlist->vms;
+ }
+ } else {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, " error: username or context is NULL for %s\n",mailbox);
+ }
+ } else {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, " error: vms is NULL for %s\n",mailbox);
+ }
+ vlist = vlist->next;
+ }
+ ast_mutex_unlock(&vmstate_lock);
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "%s not found in vmstates\n",mailbox);
+ return NULL;
+}
+
+static void vmstate_insert(struct vm_state *vms)
+{
+ struct vmstate *v;
+ struct vm_state *altvms;
+
+ /* If interactive, it probably already exists, and we should
+ use the one we already have since it is more up to date.
+ We can compare the username to find the duplicate */
+ if (vms->interactive == 1) {
+ altvms = get_vm_state_by_mailbox(vms->username, vms->context, 0);
+ if (altvms) {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Duplicate mailbox %s, copying message info...\n",vms->username);
+ vms->newmessages = altvms->newmessages;
+ vms->oldmessages = altvms->oldmessages;
+ /* memcpy(vms->msgArray, altvms->msgArray, sizeof(long)*256); */
+ copy_msgArray(vms, altvms);
+ vms->vmArrayIndex = altvms->vmArrayIndex;
+ vms->lastmsg = altvms->lastmsg;
+ vms->curmsg = altvms->curmsg;
+ /* get a pointer to the persistent store */
+ vms->persist_vms = altvms;
+ /* Reuse the mailstream? */
+ vms->mailstream = altvms->mailstream;
+ /* vms->mailstream = NIL; */
+ }
+ }
+
+ v = (struct vmstate *)malloc(sizeof(struct vmstate));
+ if (!v) {
+ ast_log(LOG_ERROR, "Out of memory\n");
+ }
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Inserting vm_state for user:%s, mailbox %s\n",vms->imapuser,vms->username);
+ ast_mutex_lock(&vmstate_lock);
+ v->vms = vms;
+ v->next = vmstates;
+ vmstates = v;
+ ast_mutex_unlock(&vmstate_lock);
+}
+
+static void vmstate_delete(struct vm_state *vms)
+{
+ struct vmstate *vc, *vf = NULL, *vl = NULL;
+ struct vm_state *altvms;
+
+ /* If interactive, we should copy pertainent info
+ back to the persistent state (to make update immediate) */
+ if (vms->interactive == 1) {
+ altvms = vms->persist_vms;
+ if (altvms) {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Duplicate mailbox %s, copying message info...\n",vms->username);
+ altvms->newmessages = vms->newmessages;
+ altvms->oldmessages = vms->oldmessages;
+ altvms->updated = 1;
+ }
+ }
+
+ ast_mutex_lock(&vmstate_lock);
+ vc = vmstates;
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Removing vm_state for user:%s, mailbox %s\n",vms->imapuser,vms->username);
+ while (vc) {
+ if (vc->vms == vms) {
+ vf = vc;
+ if (vl)
+ vl->next = vc->next;
+ else
+ vmstates = vc->next;
+ break;
+ }
+ vl = vc;
+ vc = vc->next;
+ }
+ if (!vf) {
+ ast_log(LOG_ERROR, "No vmstate found for user:%s, mailbox %s\n",vms->imapuser,vms->username);
+ } else {
+ ast_mutex_destroy(&vf->vms->lock);
+ free(vf);
+ }
+ ast_mutex_unlock(&vmstate_lock);
+}
+
+static void set_update(MAILSTREAM * stream)
+{
+ struct vm_state *vms;
+ char *mailbox;
+ char *user;
+
+ mailbox = stream->mailbox;
+ user = get_user_by_mailbox(mailbox);
+ vms = get_vm_state_by_imapuser(user, 0);
+ if (vms) {
+ if (option_debug > 2)
+ ast_log (LOG_DEBUG, "User %s mailbox set for update.\n",user);
+ vms->updated = 1; /* set updated flag since mailbox changed */
+ } else {
+ if (option_debug > 2)
+ ast_log (LOG_WARNING, "User %s mailbox not found for update.\n",user);
+ }
+}
+
+static void init_vm_state(struct vm_state *vms)
+{
+ int x;
+ vms->vmArrayIndex = 0;
+ for (x = 0; x < 256; x++) {
+ vms->msgArray[x] = 0;
+ }
+ ast_mutex_init(&vms->lock);
+}
+
+static void copy_msgArray(struct vm_state *dst, struct vm_state *src)
+{
+ int x;
+ for (x = 0; x<256; x++) {
+ dst->msgArray[x] = src->msgArray[x];
+ }
+}
+
+static int save_body(BODY *body, struct vm_state *vms, char *section, char *format)
+{
+ char *body_content;
+ char *body_decoded;
+ unsigned long len;
+ unsigned long newlen;
+ char filename[256];
+
+ if (!body || body == NIL)
+ return -1;
+ ast_mutex_lock(&vms->lock);
+ body_content = mail_fetchbody (vms->mailstream, vms->msgArray[vms->curmsg], section, &len);
+ ast_mutex_unlock(&vms->lock);
+ if (body_content != NIL) {
+ snprintf(filename, sizeof(filename), "%s.%s", vms->fn, format);
+ /* ast_log (LOG_DEBUG,body_content); */
+ body_decoded = rfc822_base64 ((unsigned char *)body_content, len, &newlen);
+ write_file (filename, (char *) body_decoded, newlen);
+ }
+ return 0;
+}
+
+/* get delimiter via mm_list callback */
+/* MUTEX should already be held */
+static void get_mailbox_delimiter(MAILSTREAM *stream) {
+ char tmp[50];
+ snprintf(tmp, sizeof(tmp), "{%s}", imapserver);
+ mail_list(stream, tmp, "*");
+}
+
+/* Check Quota for user */
+static void check_quota(struct vm_state *vms, char *mailbox) {
+ ast_mutex_lock(&vms->lock);
+ mail_parameters(NULL, SET_QUOTA, (void *) mm_parsequota);
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Mailbox name set to: %s, about to check quotas\n", mailbox);
+ if (vms && vms->mailstream != NULL) {
+ imap_getquotaroot(vms->mailstream, mailbox);
+ } else {
+ ast_log(LOG_WARNING,"Mailstream not available for mailbox: %s\n",mailbox);
+ }
+ ast_mutex_unlock(&vms->lock);
+}
+#endif
+
+/* only return failure if ast_lock_path returns 'timeout',
+ not if the path does not exist or any other reason
+*/
+static int vm_lock_path(const char *path)
+{
+ switch (ast_lock_path(path)) {
+ case AST_LOCK_TIMEOUT:
+ return -1;
+ default:
+ return 0;
+ }
+}
+
+
+#ifdef ODBC_STORAGE
+struct generic_prepare_struct {
+ char *sql;
+ int argc;
+ char **argv;
+};
+
+static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
+{
+ struct generic_prepare_struct *gps = data;
+ int res, i;
+ SQLHSTMT stmt;
+
+ 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 *)gps->sql, SQL_NTS);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", gps->sql);
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ return NULL;
+ }
+ for (i = 0; i < gps->argc; i++)
+ SQLBindParameter(stmt, i + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->argv[i]), 0, gps->argv[i], 0, NULL);
+
+ return stmt;
+}
+
+static int retrieve_file(char *dir, int msgnum)
+{
+ int x = 0;
+ int res;
+ int fd=-1;
+ size_t fdlen = 0;
+ void *fdm = MAP_FAILED;
+ SQLSMALLINT colcount=0;
+ SQLHSTMT stmt;
+ char sql[PATH_MAX];
+ char fmt[80]="";
+ char *c;
+ char coltitle[256];
+ SQLSMALLINT collen;
+ SQLSMALLINT datatype;
+ SQLSMALLINT decimaldigits;
+ SQLSMALLINT nullable;
+ SQLULEN colsize;
+ SQLLEN colsize2;
+ FILE *f=NULL;
+ char rowdata[80];
+ char fn[PATH_MAX];
+ char full_fn[PATH_MAX];
+ char msgnums[80];
+ char *argv[] = { dir, msgnums };
+ struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
+
+ struct odbc_obj *obj;
+ obj = ast_odbc_request_obj(odbc_database, 0);
+ if (obj) {
+ ast_copy_string(fmt, vmfmts, sizeof(fmt));
+ c = strchr(fmt, '|');
+ if (c)
+ *c = '\0';
+ if (!strcasecmp(fmt, "wav49"))
+ strcpy(fmt, "WAV");
+ snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
+ if (msgnum > -1)
+ make_file(fn, sizeof(fn), dir, msgnum);
+ else
+ ast_copy_string(fn, dir, sizeof(fn));
+ snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
+
+ if (!(f = fopen(full_fn, "w+"))) {
+ ast_log(LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
+ goto yuck;
+ }
+
+ snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
+ snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?",odbc_table);
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt) {
+ ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ ast_odbc_release_obj(obj);
+ goto yuck;
+ }
+ res = SQLFetch(stmt);
+ if (res == SQL_NO_DATA) {
+ SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ goto yuck;
+ }
+ else 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);
+ goto yuck;
+ }
+ fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, 0770);
+ if (fd < 0) {
+ ast_log(LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
+ SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ goto yuck;
+ }
+ 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);
+ goto yuck;
+ }
+ if (f)
+ fprintf(f, "[message]\n");
+ 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);
+ SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ goto yuck;
+ }
+ if (!strcasecmp(coltitle, "recording")) {
+ off_t offset;
+ res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
+ fdlen = colsize2;
+ if (fd > -1) {
+ char tmp[1]="";
+ lseek(fd, fdlen - 1, SEEK_SET);
+ if (write(fd, tmp, 1) != 1) {
+ close(fd);
+ fd = -1;
+ continue;
+ }
+ /* Read out in small chunks */
+ for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
+ if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
+ ast_log(LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ goto yuck;
+ } else {
+ res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
+ munmap(fdm, CHUNKSIZE);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
+ unlink(full_fn);
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ goto yuck;
+ }
+ }
+ }
+ if (truncate(full_fn, fdlen) < 0) {
+ ast_log(LOG_WARNING, "Unable to truncate '%s': %s\n", full_fn, strerror(errno));
+ }
+ }
+ } else {
+ SQLLEN ind;
+ res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), &ind);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ SQLINTEGER nativeerror = 0;
+ SQLSMALLINT diagbytes = 0;
+ unsigned char state[10], diagnostic[256];
+ SQLGetDiagRec(SQL_HANDLE_STMT, stmt, 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
+ ast_log(LOG_WARNING, "SQL Get Data error: %s: %s!\n[%s]\n\n", state, diagnostic, sql);
+ SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ goto yuck;
+ }
+ if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
+ fprintf(f, "%s=%s\n", coltitle, rowdata);
+ }
+ }
+ SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ } else
+ ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
+yuck:
+ if (f)
+ fclose(f);
+ if (fd > -1)
+ close(fd);
+ return x - 1;
+}
+
+static int last_message_index(struct ast_vm_user *vmu, char *dir)
+{
+ int x = 0;
+ int res;
+ SQLHSTMT stmt;
+ char sql[PATH_MAX];
+ char rowdata[20];
+ char *argv[] = { dir };
+ struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
+
+ struct odbc_obj *obj;
+ obj = ast_odbc_request_obj(odbc_database, 0);
+ if (obj) {
+ snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?",odbc_table);
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt) {
+ ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ ast_odbc_release_obj(obj);
+ goto yuck;
+ }
+ res = SQLFetch(stmt);
+ 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);
+ goto yuck;
+ }
+ res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
+ SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ goto yuck;
+ }
+ if (sscanf(rowdata, "%d", &x) != 1)
+ ast_log(LOG_WARNING, "Failed to read message count!\n");
+ SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ } else
+ ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
+yuck:
+ return x - 1;
+}
+
+static int message_exists(char *dir, int msgnum)
+{
+ int x = 0;
+ int res;
+ SQLHSTMT stmt;
+ char sql[PATH_MAX];
+ char rowdata[20];
+ char msgnums[20];
+ char *argv[] = { dir, msgnums };
+ struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
+
+ struct odbc_obj *obj;
+ obj = ast_odbc_request_obj(odbc_database, 0);
+ if (obj) {
+ snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
+ snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?",odbc_table);
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt) {
+ ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ ast_odbc_release_obj(obj);
+ goto yuck;
+ }
+ res = SQLFetch(stmt);
+ 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);
+ goto yuck;
+ }
+ res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
+ SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ goto yuck;
+ }
+ if (sscanf(rowdata, "%d", &x) != 1)
+ ast_log(LOG_WARNING, "Failed to read message count!\n");
+ SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ } else
+ ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
+yuck:
+ return x;
+}
+
+static int count_messages(struct ast_vm_user *vmu, char *dir)
+{
+ return last_message_index(vmu, dir) + 1;
+}
+
+static void delete_file(char *sdir, int smsg)
+{
+ SQLHSTMT stmt;
+ char sql[PATH_MAX];
+ char msgnums[20];
+ char *argv[] = { sdir, msgnums };
+ struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
+
+ struct odbc_obj *obj;
+ obj = ast_odbc_request_obj(odbc_database, 0);
+ if (obj) {
+ snprintf(msgnums, sizeof(msgnums), "%d", smsg);
+ snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?",odbc_table);
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt)
+ ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ else
+ SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ } else
+ ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
+ return;
+}
+
+static void copy_file(char *sdir, int smsg, char *ddir, int dmsg, char *dmailboxuser, char *dmailboxcontext)
+{
+ SQLHSTMT stmt;
+ char sql[512];
+ char msgnums[20];
+ char msgnumd[20];
+ struct odbc_obj *obj;
+ char *argv[] = { ddir, msgnumd, dmailboxuser, dmailboxcontext, sdir, msgnums };
+ struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
+
+ delete_file(ddir, dmsg);
+ obj = ast_odbc_request_obj(odbc_database, 0);
+ if (obj) {
+ snprintf(msgnums, sizeof(msgnums), "%d", smsg);
+ snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
+ snprintf(sql, sizeof(sql), "INSERT INTO %s (dir, msgnum, context, macrocontext, callerid, origtime, duration, recording, mailboxuser, mailboxcontext) SELECT ?,?,context,macrocontext,callerid,origtime,duration,recording,?,? FROM %s WHERE dir=? AND msgnum=?",odbc_table,odbc_table);
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt)
+ ast_log(LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
+ else
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ } else
+ ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
+ return;
+}
+
+struct insert_cb_struct {
+ char *dir;
+ char *msgnum;
+ void *recording;
+ size_t recordinglen;
+ SQLLEN indlen;
+ const char *context;
+ const char *macrocontext;
+ const char *callerid;
+ const char *origtime;
+ const char *duration;
+ char *mailboxuser;
+ char *mailboxcontext;
+ const char *category;
+ char *sql;
+};
+
+static SQLHSTMT insert_cb(struct odbc_obj *obj, void *vd)
+{
+ struct insert_cb_struct *d = vd;
+ int res;
+ SQLHSTMT stmt;
+
+ 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 *)d->sql, SQL_NTS);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", d->sql);
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ return NULL;
+ }
+
+ SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(d->dir), 0, (void *)d->dir, 0, NULL);
+ SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(d->msgnum), 0, (void *)d->msgnum, 0, NULL);
+ SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, d->recordinglen, 0, (void *)d->recording, 0, &d->indlen);
+ SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(d->context), 0, (void *)d->context, 0, NULL);
+ SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(d->macrocontext), 0, (void *)d->macrocontext, 0, NULL);
+ SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(d->callerid), 0, (void *)d->callerid, 0, NULL);
+ SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(d->origtime), 0, (void *)d->origtime, 0, NULL);
+ SQLBindParameter(stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(d->duration), 0, (void *)d->duration, 0, NULL);
+ SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(d->mailboxuser), 0, (void *)d->mailboxuser, 0, NULL);
+ SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(d->mailboxcontext), 0, (void *)d->mailboxcontext, 0, NULL);
+ if (!ast_strlen_zero(d->category)) {
+ SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(d->category), 0, (void *)d->category, 0, NULL);
+ }
+
+ return stmt;
+}
+
+static int store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum)
+{
+ int x = 0;
+ int fd = -1;
+ void *fdm = MAP_FAILED;
+ size_t fdlen = -1;
+ SQLHSTMT stmt;
+ char sql[PATH_MAX];
+ char msgnums[20];
+ char fn[PATH_MAX];
+ char full_fn[PATH_MAX];
+ char fmt[80]="";
+ char *c;
+ struct insert_cb_struct d = {
+ .dir = dir,
+ .msgnum = msgnums,
+ .context = "",
+ .macrocontext = "",
+ .callerid = "",
+ .origtime = "",
+ .duration = "",
+ .mailboxuser = mailboxuser,
+ .mailboxcontext = mailboxcontext,
+ .category = "",
+ .sql = sql
+ };
+ struct ast_config *cfg=NULL;
+ struct odbc_obj *obj;
+
+ delete_file(dir, msgnum);
+ obj = ast_odbc_request_obj(odbc_database, 0);
+ if (obj) {
+ ast_copy_string(fmt, vmfmts, sizeof(fmt));
+ c = strchr(fmt, '|');
+ if (c)
+ *c = '\0';
+ if (!strcasecmp(fmt, "wav49"))
+ strcpy(fmt, "WAV");
+ snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
+ if (msgnum > -1)
+ make_file(fn, sizeof(fn), dir, msgnum);
+ else
+ ast_copy_string(fn, dir, sizeof(fn));
+ snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
+ cfg = ast_config_load(full_fn);
+ snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
+ fd = open(full_fn, O_RDWR);
+ if (fd < 0) {
+ ast_log(LOG_WARNING, "Open of sound file '%s' failed: %s\n", full_fn, strerror(errno));
+ ast_odbc_release_obj(obj);
+ goto yuck;
+ }
+ if (cfg) {
+ d.context = ast_variable_retrieve(cfg, "message", "context");
+ if (!d.context) d.context = "";
+ d.macrocontext = ast_variable_retrieve(cfg, "message", "macrocontext");
+ if (!d.macrocontext) d.macrocontext = "";
+ d.callerid = ast_variable_retrieve(cfg, "message", "callerid");
+ if (!d.callerid) d.callerid = "";
+ d.origtime = ast_variable_retrieve(cfg, "message", "origtime");
+ if (!d.origtime) d.origtime = "";
+ d.duration = ast_variable_retrieve(cfg, "message", "duration");
+ if (!d.duration) d.duration = "";
+ d.category = ast_variable_retrieve(cfg, "message", "category");
+ if (!d.category) d.category = "";
+ }
+ fdlen = lseek(fd, 0, SEEK_END);
+ lseek(fd, 0, SEEK_SET);
+ printf("Length is %zd\n", fdlen);
+ fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (fdm == MAP_FAILED) {
+ ast_log(LOG_WARNING, "Memory map failed!\n");
+ ast_odbc_release_obj(obj);
+ goto yuck;
+ }
+ d.recording = fdm;
+ d.recordinglen = d.indlen = fdlen; /* SQL_LEN_DATA_AT_EXEC(fdlen); */
+ if (!ast_strlen_zero(d.category))
+ snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,category) VALUES (?,?,?,?,?,?,?,?,?,?,?)",odbc_table);
+ else
+ snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext) VALUES (?,?, ? , ?,?,?,?,?,?,?)",odbc_table);
+ stmt = ast_odbc_prepare_and_execute(obj, insert_cb, &d);
+ if (stmt) {
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ }
+ ast_odbc_release_obj(obj);
+ } else
+ ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
+yuck:
+ if (cfg)
+ ast_config_destroy(cfg);
+ if (fdm != MAP_FAILED)
+ munmap(fdm, fdlen);
+ if (fd > -1)
+ close(fd);
+ return x;
+}
+
+static void rename_file(char *sdir, int smsg, char *mailboxuser, char *mailboxcontext, char *ddir, int dmsg)
+{
+ SQLHSTMT stmt;
+ char sql[PATH_MAX];
+ char msgnums[20];
+ char msgnumd[20];
+ struct odbc_obj *obj;
+ char *argv[] = { ddir, msgnumd, mailboxuser, mailboxcontext, sdir, msgnums };
+ struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
+
+ delete_file(ddir, dmsg);
+ obj = ast_odbc_request_obj(odbc_database, 0);
+ if (obj) {
+ snprintf(msgnums, sizeof(msgnums), "%d", smsg);
+ snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
+ snprintf(sql, sizeof(sql), "UPDATE %s SET dir=?, msgnum=?, mailboxuser=?, mailboxcontext=? WHERE dir=? AND msgnum=?",odbc_table);
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt)
+ ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ else
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ } else
+ ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
+ return;
+}
+
+#else
+#ifndef IMAP_STORAGE
+static int count_messages(struct ast_vm_user *vmu, char *dir)
+{
+ /* Find all .txt files - even if they are not in sequence from 0000 */
+
+ int vmcount = 0;
+ DIR *vmdir = NULL;
+ struct dirent *vment = NULL;
+
+ if (vm_lock_path(dir))
+ return ERROR_LOCK_PATH;
+
+ if ((vmdir = opendir(dir))) {
+ while ((vment = readdir(vmdir))) {
+ if (strlen(vment->d_name) > 7 && !strncmp(vment->d_name + 7, ".txt", 4))
+ vmcount++;
+ }
+ closedir(vmdir);
+ }
+ ast_unlock_path(dir);
+
+ return vmcount;
+}
+
+static void rename_file(char *sfn, char *dfn)
+{
+ char stxt[PATH_MAX];
+ char dtxt[PATH_MAX];
+ ast_filerename(sfn,dfn,NULL);
+ snprintf(stxt, sizeof(stxt), "%s.txt", sfn);
+ snprintf(dtxt, sizeof(dtxt), "%s.txt", dfn);
+ rename(stxt, dtxt);
+}
+#endif
+
+/*
+ * A negative return value indicates an error.
+ */
+#if (!defined(IMAP_STORAGE) && !defined(ODBC_STORAGE))
+static int last_message_index(struct ast_vm_user *vmu, char *dir)
+{
+ int x;
+ char fn[PATH_MAX];
+
+ if (vm_lock_path(dir))
+ return ERROR_LOCK_PATH;
+
+ for (x = 0; x < vmu->maxmsg; x++) {
+ make_file(fn, sizeof(fn), dir, x);
+ if (ast_fileexists(fn, NULL, NULL) < 1)
+ break;
+ }
+ ast_unlock_path(dir);
+
+ return x - 1;
+}
+#endif
+#endif
+#if (defined(IMAP_STORAGE) || defined(ODBC_STORAGE))
+static int remove_file(char *dir, int msgnum)
+{
+ char fn[PATH_MAX];
+ char full_fn[PATH_MAX];
+ char msgnums[80];
+
+ if (msgnum > -1) {
+ snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
+ make_file(fn, sizeof(fn), dir, msgnum);
+ } else {
+#ifndef IMAP_STORAGE
+ ast_copy_string(fn, dir, sizeof(fn));
+#else
+ /*IMAP stores greetings locally so it should not
+ * try to dispose of them
+ */
+ return 0;
+#endif
+ }
+ ast_filedelete(fn, NULL);
+ snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
+ unlink(full_fn);
+ return 0;
+}
+#endif
+
+static int copy(char *infile, char *outfile)
+{
+ int ifd;
+ int ofd;
+ int res;
+ int len;
+ char buf[4096];
+
+#ifdef HARDLINK_WHEN_POSSIBLE
+ /* Hard link if possible; saves disk space & is faster */
+ if (link(infile, outfile)) {
+#endif
+ if ((ifd = open(infile, O_RDONLY)) < 0) {
+ ast_log(LOG_WARNING, "Unable to open %s in read-only mode: %s\n", infile, strerror(errno));
+ return -1;
+ }
+ if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, VOICEMAIL_FILE_MODE)) < 0) {
+ ast_log(LOG_WARNING, "Unable to open %s in write-only mode: %s\n", outfile, strerror(errno));
+ close(ifd);
+ return -1;
+ }
+ do {
+ len = read(ifd, buf, sizeof(buf));
+ if (len < 0) {
+ ast_log(LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno));
+ close(ifd);
+ close(ofd);
+ unlink(outfile);
+ }
+ if (len) {
+ res = write(ofd, buf, len);
+ if (errno == ENOMEM || errno == ENOSPC || res != len) {
+ ast_log(LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno));
+ close(ifd);
+ close(ofd);
+ unlink(outfile);
+ }
+ }
+ } while (len);
+ close(ifd);
+ close(ofd);
+ return 0;
+#ifdef HARDLINK_WHEN_POSSIBLE
+ } else {
+ /* Hard link succeeded */
+ return 0;
+ }
+#endif
+}
+
+static void copy_plain_file(char *frompath, char *topath)
+{
+ char frompath2[PATH_MAX], topath2[PATH_MAX];
+ ast_filecopy(frompath, topath, NULL);
+ snprintf(frompath2, sizeof(frompath2), "%s.txt", frompath);
+ snprintf(topath2, sizeof(topath2), "%s.txt", topath);
+ copy(frompath2, topath2);
+}
+
+static int vm_delete(char *file)
+{
+ char *txt;
+ int txtsize = 0;
+
+ txtsize = (strlen(file) + 5)*sizeof(char);
+ txt = alloca(txtsize);
+ /* Sprintf here would safe because we alloca'd exactly the right length,
+ * but trying to eliminate all sprintf's anyhow
+ */
+ snprintf(txt, txtsize, "%s.txt", file);
+ unlink(txt);
+ return ast_filedelete(file, NULL);
+}
+
+static int inbuf(struct baseio *bio, FILE *fi)
+{
+ int l;
+
+ if (bio->ateof)
+ return 0;
+
+ if ((l = fread(bio->iobuf,1,BASEMAXINLINE,fi)) <= 0) {
+ if (ferror(fi))
+ return -1;
+
+ bio->ateof = 1;
+ return 0;
+ }
+
+ bio->iolen= l;
+ bio->iocp= 0;
+
+ return 1;
+}
+
+static int inchar(struct baseio *bio, FILE *fi)
+{
+ if (bio->iocp>=bio->iolen) {
+ if (!inbuf(bio, fi))
+ return EOF;
+ }
+
+ return bio->iobuf[bio->iocp++];
+}
+
+static int ochar(struct baseio *bio, int c, FILE *so)
+{
+ if (bio->linelength>=BASELINELEN) {
+ if (fputs(eol,so)==EOF)
+ return -1;
+
+ bio->linelength= 0;
+ }
+
+ if (putc(((unsigned char)c),so)==EOF)
+ return -1;
+
+ bio->linelength++;
+
+ return 1;
+}
+
+static int base_encode(char *filename, FILE *so)
+{
+ unsigned char dtable[BASEMAXINLINE];
+ int i,hiteof= 0;
+ FILE *fi;
+ struct baseio bio;
+
+ memset(&bio, 0, sizeof(bio));
+ bio.iocp = BASEMAXINLINE;
+
+ if (!(fi = fopen(filename, "rb"))) {
+ ast_log(LOG_WARNING, "Failed to open file: %s: %s\n", filename, strerror(errno));
+ return -1;
+ }
+
+ for (i= 0;i<9;i++) {
+ dtable[i]= 'A'+i;
+ dtable[i+9]= 'J'+i;
+ dtable[26+i]= 'a'+i;
+ dtable[26+i+9]= 'j'+i;
+ }
+ for (i= 0;i<8;i++) {
+ dtable[i+18]= 'S'+i;
+ dtable[26+i+18]= 's'+i;
+ }
+ for (i= 0;i<10;i++) {
+ dtable[52+i]= '0'+i;
+ }
+ dtable[62]= '+';
+ dtable[63]= '/';
+
+ while (!hiteof){
+ unsigned char igroup[3],ogroup[4];
+ int c,n;
+
+ igroup[0]= igroup[1]= igroup[2]= 0;
+
+ for (n= 0;n<3;n++) {
+ if ((c = inchar(&bio, fi)) == EOF) {
+ hiteof= 1;
+ break;
+ }
+
+ igroup[n]= (unsigned char)c;
+ }
+
+ if (n> 0) {
+ ogroup[0]= dtable[igroup[0]>>2];
+ ogroup[1]= dtable[((igroup[0]&3)<<4)|(igroup[1]>>4)];
+ ogroup[2]= dtable[((igroup[1]&0xF)<<2)|(igroup[2]>>6)];
+ ogroup[3]= dtable[igroup[2]&0x3F];
+
+ if (n<3) {
+ ogroup[3]= '=';
+
+ if (n<2)
+ ogroup[2]= '=';
+ }
+
+ for (i= 0;i<4;i++)
+ ochar(&bio, ogroup[i], so);
+ }
+ }
+
+ fclose(fi);
+
+ if (fputs(eol,so)==EOF)
+ return 0;
+
+ return 1;
+}
+
+static void prep_email_sub_vars(struct ast_channel *ast, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, char *cidnum, char *cidname, char *dur, char *date, char *passdata, size_t passdatasize, const char *category)
+{
+ char callerid[256];
+ /* Prepare variables for substition in email body and subject */
+ pbx_builtin_setvar_helper(ast, "VM_NAME", vmu->fullname);
+ pbx_builtin_setvar_helper(ast, "VM_DUR", dur);
+ snprintf(passdata, passdatasize, "%d", msgnum);
+ pbx_builtin_setvar_helper(ast, "VM_MSGNUM", passdata);
+ pbx_builtin_setvar_helper(ast, "VM_CONTEXT", context);
+ pbx_builtin_setvar_helper(ast, "VM_MAILBOX", mailbox);
+ pbx_builtin_setvar_helper(ast, "VM_CALLERID", (!ast_strlen_zero(cidname) || !ast_strlen_zero(cidnum)) ?
+ ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, NULL) : "an unknown caller");
+ pbx_builtin_setvar_helper(ast, "VM_CIDNAME", (!ast_strlen_zero(cidname) ? cidname : "an unknown caller"));
+ pbx_builtin_setvar_helper(ast, "VM_CIDNUM", (!ast_strlen_zero(cidnum) ? cidnum : "an unknown caller"));
+ pbx_builtin_setvar_helper(ast, "VM_DATE", date);
+ pbx_builtin_setvar_helper(ast, "VM_CATEGORY", category ? ast_strdupa(category) : "no category");
+}
+
+static char *quote(const char *from, char *to, size_t len)
+{
+ char *ptr = to;
+ *ptr++ = '"';
+ for (; ptr < to + len - 1; from++) {
+ if (*from == '"')
+ *ptr++ = '\\';
+ else if (*from == '\0')
+ break;
+ *ptr++ = *from;
+ }
+ if (ptr < to + len - 1)
+ *ptr++ = '"';
+ *ptr = '\0';
+ return to;
+}
+/*
+ * fill in *tm for current time according to the proper timezone, if any.
+ * Return tm so it can be used as a function argument.
+ */
+static const struct tm *vmu_tm(const struct ast_vm_user *vmu, struct tm *tm)
+{
+ const struct vm_zone *z = NULL;
+ time_t t = time(NULL);
+
+ /* Does this user have a timezone specified? */
+ if (!ast_strlen_zero(vmu->zonetag)) {
+ /* Find the zone in the list */
+ AST_LIST_LOCK(&zones);
+ AST_LIST_TRAVERSE(&zones, z, list) {
+ if (!strcmp(z->name, vmu->zonetag))
+ break;
+ }
+ AST_LIST_UNLOCK(&zones);
+ }
+ ast_localtime(&t, tm, z ? z->timezone : NULL);
+ return tm;
+}
+
+/*!\brief Check if the string would need encoding within the MIME standard, to
+ * avoid confusing certain mail software that expects messages to be 7-bit
+ * clean.
+ */
+static int check_mime(const char *str)
+{
+ for (; *str; str++) {
+ if (*str > 126 || *str < 32 || strchr("()<>@,:;/\"[]?.=", *str)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*!\brief Encode a string according to the MIME rules for encoding strings
+ * that are not 7-bit clean or contain control characters.
+ *
+ * Additionally, if the encoded string would exceed the MIME limit of 76
+ * characters per line, then the encoding will be broken up into multiple
+ * sections, separated by a space character, in order to facilitate
+ * breaking up the associated header across multiple lines.
+ *
+ * \param start A string to be encoded
+ * \param end An expandable buffer for holding the result
+ * \param preamble The length of the first line already used for this string,
+ * to ensure that each line maintains a maximum length of 76 chars.
+ * \param postamble the length of any additional characters appended to the
+ * line, used to ensure proper field wrapping.
+ * \retval The encoded string.
+ */
+static char *encode_mime_str(const char *start, char *end, size_t endsize, size_t preamble, size_t postamble)
+{
+ char tmp[80];
+ int first_section = 1;
+ size_t endlen = 0, tmplen = 0;
+ *end = '\0';
+
+ tmplen = snprintf(tmp, sizeof(tmp), "=?%s?Q?", charset);
+ for (; *start; start++) {
+ int need_encoding = 0;
+ if (*start < 33 || *start > 126 || strchr("()<>@,:;/\"[]?.=_", *start)) {
+ need_encoding = 1;
+ }
+ if ((first_section && need_encoding && preamble + tmplen > 70) ||
+ (first_section && !need_encoding && preamble + tmplen > 72) ||
+ (!first_section && need_encoding && tmplen > 70) ||
+ (!first_section && !need_encoding && tmplen > 72)) {
+ /* Start new line */
+ endlen += snprintf(end + endlen, endsize - endlen, "%s%s?=", first_section ? "" : " ", tmp);
+ tmplen = snprintf(tmp, sizeof(tmp), "=?%s?Q?", charset);
+ first_section = 0;
+ }
+ if (need_encoding && *start == ' ') {
+ tmplen += snprintf(tmp + tmplen, sizeof(tmp) - tmplen, "_");
+ } else if (need_encoding) {
+ tmplen += snprintf(tmp + tmplen, sizeof(tmp) - tmplen, "=%hhX", *start);
+ } else {
+ tmplen += snprintf(tmp + tmplen, sizeof(tmp) - tmplen, "%c", *start);
+ }
+ }
+ snprintf(end + endlen, endsize - endlen, "%s%s?=%s", first_section ? "" : " ", tmp, endlen + postamble > 74 ? " " : "");
+ return end;
+}
+
+static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, char *cidnum, char *cidname, char *attach, char *format, int duration, int attach_user_voicemail, struct ast_channel *chan, const char *category, int imap)
+{
+ char date[256];
+ char host[MAXHOSTNAMELEN] = "";
+ char who[256];
+ char bound[256];
+ char fname[256];
+ char dur[256];
+ char tmpcmd[256];
+ char enc_cidnum[256] = "", enc_cidname[256] = "";
+ struct tm tm;
+ char *passdata = NULL, *passdata2;
+ size_t len_passdata = 0, len_passdata2, tmplen;
+#ifdef IMAP_STORAGE
+#define ENDL "\r\n"
+#else
+#define ENDL "\n"
+#endif
+
+ /* One alloca for multiple fields */
+ len_passdata2 = strlen(vmu->fullname);
+ if (emailsubject && (tmplen = strlen(emailsubject)) > len_passdata2) {
+ len_passdata2 = tmplen;
+ }
+ if ((tmplen = strlen(emailtitle)) > len_passdata2) {
+ len_passdata2 = tmplen;
+ }
+ if ((tmplen = strlen(fromstring)) > len_passdata2) {
+ len_passdata2 = tmplen;
+ }
+ len_passdata2 = len_passdata2 * 3 + 200;
+ passdata2 = alloca(len_passdata2);
+
+ if (cidnum) {
+ strip_control(cidnum, enc_cidnum, sizeof(enc_cidnum));
+ }
+ if (cidname) {
+ strip_control(cidname, enc_cidname, sizeof(enc_cidname));
+ }
+ gethostname(host, sizeof(host) - 1);
+ if (strchr(srcemail, '@'))
+ ast_copy_string(who, srcemail, sizeof(who));
+ else {
+ snprintf(who, sizeof(who), "%s@%s", srcemail, host);
+ }
+ snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
+ strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
+ fprintf(p, "Date: %s" ENDL, date);
+
+ /* Set date format for voicemail mail */
+ strftime(date, sizeof(date), emaildateformat, &tm);
+
+ if (*fromstring) {
+ struct ast_channel *ast;
+ if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "Substitution/voicemail"))) {
+ char *ptr;
+ memset(passdata2, 0, len_passdata2);
+ prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, enc_cidnum, enc_cidname, dur, date, passdata2, len_passdata2, category);
+ pbx_substitute_variables_helper(ast, fromstring, passdata2, len_passdata2);
+ len_passdata = strlen(passdata2) * 3 + 300;
+ passdata = alloca(len_passdata);
+ if (check_mime(passdata2)) {
+ int first_line = 1;
+ encode_mime_str(passdata2, passdata, len_passdata, strlen("From: "), strlen(who) + 3);
+ while ((ptr = strchr(passdata, ' '))) {
+ *ptr = '\0';
+ fprintf(p, "%s %s" ENDL, first_line ? "From:" : "", passdata);
+ first_line = 0;
+ passdata = ptr + 1;
+ }
+ fprintf(p, "%s %s <%s>" ENDL, first_line ? "From:" : "", passdata, who);
+ } else {
+ fprintf(p, "From: %s <%s>" ENDL, quote(passdata2, passdata, len_passdata), who);
+ }
+ ast_channel_free(ast);
+ } else
+ ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
+ } else
+ fprintf(p, "From: Asterisk PBX <%s>" ENDL, who);
+
+ if (check_mime(vmu->fullname)) {
+ int first_line = 1;
+ char *ptr;
+ encode_mime_str(vmu->fullname, passdata2, len_passdata2, strlen("To: "), strlen(vmu->email) + 3);
+ while ((ptr = strchr(passdata2, ' '))) {
+ *ptr = '\0';
+ fprintf(p, "%s %s" ENDL, first_line ? "To:" : "", passdata2);
+ first_line = 0;
+ passdata2 = ptr + 1;
+ }
+ fprintf(p, "%s %s <%s>" ENDL, first_line ? "To:" : "", passdata2, vmu->email);
+ } else {
+ fprintf(p, "To: %s <%s>" ENDL, quote(vmu->fullname, passdata2, len_passdata2), vmu->email);
+ }
+ if (emailsubject) {
+ struct ast_channel *ast;
+ if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "Substitution/voicemail"))) {
+ int vmlen = strlen(emailsubject) * 3 + 200;
+ /* Only allocate more space if the previous was not large enough */
+ if (vmlen > len_passdata) {
+ passdata = alloca(vmlen);
+ len_passdata = vmlen;
+ }
+
+ memset(passdata, 0, len_passdata);
+ prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, len_passdata, category);
+ pbx_substitute_variables_helper(ast, emailsubject, passdata, len_passdata);
+ if (check_mime(passdata)) {
+ int first_line = 1;
+ char *ptr;
+ encode_mime_str(passdata, passdata2, len_passdata2, strlen("Subject: "), 0);
+ while ((ptr = strchr(passdata2, ' '))) {
+ *ptr = '\0';
+ fprintf(p, "%s %s" ENDL, first_line ? "Subject:" : "", passdata2);
+ first_line = 0;
+ passdata2 = ptr + 1;
+ }
+ fprintf(p, "%s %s" ENDL, first_line ? "Subject:" : "", passdata2);
+ } else {
+ fprintf(p, "Subject: %s" ENDL, passdata);
+ }
+ ast_channel_free(ast);
+ } else {
+ ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
+ }
+ } else if (*emailtitle) {
+ fprintf(p, emailtitle, msgnum + 1, mailbox) ;
+ fprintf(p, ENDL) ;
+ } else if (ast_test_flag((&globalflags), VM_PBXSKIP)) {
+ fprintf(p, "Subject: New message %d in mailbox %s" ENDL, msgnum + 1, mailbox);
+ } else {
+ fprintf(p, "Subject: [PBX]: New message %d in mailbox %s" ENDL, msgnum + 1, mailbox);
+ }
+ fprintf(p, "Message-ID: <Asterisk-%d-%d-%s-%d@%s>" ENDL, msgnum + 1, (unsigned int)ast_random(), mailbox, (int)getpid(), host);
+ if (imap) {
+ /* additional information needed for IMAP searching */
+ fprintf(p, "X-Asterisk-VM-Message-Num: %d" ENDL, msgnum + 1);
+ /* fprintf(p, "X-Asterisk-VM-Orig-Mailbox: %s" ENDL, ext); */
+ fprintf(p, "X-Asterisk-VM-Server-Name: %s" ENDL, fromstring);
+ fprintf(p, "X-Asterisk-VM-Context: %s" ENDL, context);
+ fprintf(p, "X-Asterisk-VM-Extension: %s" ENDL, mailbox);
+ fprintf(p, "X-Asterisk-VM-Priority: %d" ENDL, chan->priority);
+ fprintf(p, "X-Asterisk-VM-Caller-channel: %s" ENDL, chan->name);
+ fprintf(p, "X-Asterisk-VM-Caller-ID-Num: %s" ENDL, enc_cidnum);
+ fprintf(p, "X-Asterisk-VM-Caller-ID-Name: %s" ENDL, enc_cidname);
+ fprintf(p, "X-Asterisk-VM-Duration: %d" ENDL, duration);
+ if (!ast_strlen_zero(category)) {
+ fprintf(p, "X-Asterisk-VM-Category: %s" ENDL, category);
+ }
+ fprintf(p, "X-Asterisk-VM-Orig-date: %s" ENDL, date);
+ fprintf(p, "X-Asterisk-VM-Orig-time: %ld" ENDL, (long)time(NULL));
+ }
+ if (!ast_strlen_zero(cidnum)) {
+ fprintf(p, "X-Asterisk-CallerID: %s" ENDL, enc_cidnum);
+ }
+ if (!ast_strlen_zero(cidname)) {
+ fprintf(p, "X-Asterisk-CallerIDName: %s" ENDL, enc_cidname);
+ }
+ fprintf(p, "MIME-Version: 1.0" ENDL);
+ if (attach_user_voicemail) {
+ /* Something unique. */
+ snprintf(bound, sizeof(bound), "----voicemail_%d%s%d%d", msgnum + 1, mailbox, (int)getpid(), (unsigned int)ast_random());
+
+ fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"" ENDL, bound);
+ fprintf(p, ENDL ENDL "This is a multi-part message in MIME format." ENDL ENDL);
+ fprintf(p, "--%s" ENDL, bound);
+ }
+ fprintf(p, "Content-Type: text/plain; charset=%s" ENDL "Content-Transfer-Encoding: 8bit" ENDL ENDL, charset);
+ if (emailbody) {
+ struct ast_channel *ast;
+ if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "Substitution/voicemail"))) {
+ char *passdata;
+ int vmlen = strlen(emailbody)*3 + 200;
+ if ((passdata = alloca(vmlen))) {
+ memset(passdata, 0, vmlen);
+ prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
+ pbx_substitute_variables_helper(ast, emailbody, passdata, vmlen);
+ fprintf(p, "%s" ENDL, passdata);
+ } else
+ ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
+ ast_channel_free(ast);
+ } else
+ ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
+ } else {
+ fprintf(p, "Dear %s:" ENDL ENDL "\tJust wanted to let you know you were just left a %s long message (number %d)" ENDL
+
+ "in mailbox %s from %s, on %s so you might" ENDL
+ "want to check it when you get a chance. Thanks!" ENDL ENDL "\t\t\t\t--Asterisk" ENDL ENDL, vmu->fullname,
+ dur, msgnum + 1, mailbox, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
+ }
+ if (attach_user_voicemail) {
+ /* Eww. We want formats to tell us their own MIME type */
+ char *ctype = (!strcasecmp(format, "ogg")) ? "application/" : "audio/x-";
+ char tmpdir[256], newtmp[256];
+ int tmpfd = -1;
+
+ if (vmu->volgain < -.001 || vmu->volgain > .001) {
+ create_dirpath(tmpdir, sizeof(tmpdir), vmu->context, vmu->mailbox, "tmp");
+ snprintf(newtmp, sizeof(newtmp), "%s/XXXXXX", tmpdir);
+ tmpfd = mkstemp(newtmp);
+ chmod(newtmp, VOICEMAIL_FILE_MODE & ~my_umask);
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "newtmp: %s\n", newtmp);
+ if (tmpfd > -1) {
+ int soxstatus;
+ snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, attach, format, newtmp, format);
+ if ((soxstatus = ast_safe_system(tmpcmd)) == 0) {
+ attach = newtmp;
+ if (option_debug > 2) {
+ ast_log(LOG_DEBUG, "VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", attach, format, vmu->volgain, mailbox);
+ }
+ } else {
+ ast_log(LOG_WARNING, "Sox failed to reencode %s.%s: %s (have you installed support for all sox file formats?)\n", attach, format,
+ soxstatus == 1 ? "Problem with command line options" : "An error occurred during file processing");
+ ast_log(LOG_WARNING, "Voicemail attachment will have no volume gain.\n");
+ }
+ }
+ }
+ fprintf(p, "--%s" ENDL, bound);
+ fprintf(p, "Content-Type: %s%s; name=\"msg%04d.%s\"" ENDL, ctype, format, msgnum + 1, format);
+ fprintf(p, "Content-Transfer-Encoding: base64" ENDL);
+ fprintf(p, "Content-Description: Voicemail sound attachment." ENDL);
+ fprintf(p, "Content-Disposition: attachment; filename=\"msg%04d.%s\"" ENDL ENDL, msgnum + 1, format);
+ snprintf(fname, sizeof(fname), "%s.%s", attach, format);
+ base_encode(fname, p);
+ fprintf(p, ENDL "--%s--" ENDL "." ENDL, bound);
+ if (tmpfd > -1) {
+ unlink(fname);
+ close(tmpfd);
+ unlink(newtmp);
+ }
+ }
+#undef ENDL
+}
+static int sendmail(char *srcemail, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, char *cidnum, char *cidname, char *attach, char *format, int duration, int attach_user_voicemail, struct ast_channel *chan, const char *category)
+{
+ FILE *p=NULL;
+ char tmp[80] = "/tmp/astmail-XXXXXX";
+ char tmp2[256];
+
+ if (vmu && ast_strlen_zero(vmu->email)) {
+ ast_log(LOG_WARNING, "E-mail address missing for mailbox [%s]. E-mail will not be sent.\n", vmu->mailbox);
+ return(0);
+ }
+ if (!strcmp(format, "wav49"))
+ format = "WAV";
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Attaching file '%s', format '%s', uservm is '%d', global is %d\n", attach, format, attach_user_voicemail, ast_test_flag((&globalflags), VM_ATTACH));
+ /* Make a temporary file instead of piping directly to sendmail, in case the mail
+ command hangs */
+ if ((p = vm_mkftemp(tmp)) == NULL) {
+ ast_log(LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
+ return -1;
+ } else {
+ make_email_file(p, srcemail, vmu, msgnum, context, mailbox, cidnum, cidname, attach, format, duration, attach_user_voicemail, chan, category, 0);
+ fclose(p);
+ snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
+ ast_safe_system(tmp2);
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Sent mail to %s with command '%s'\n", vmu->email, mailcmd);
+ }
+ return 0;
+}
+
+static int sendpage(char *srcemail, char *pager, int msgnum, char *context, char *mailbox, char *cidnum, char *cidname, int duration, struct ast_vm_user *vmu, const char *category)
+{
+ char date[256];
+ char host[MAXHOSTNAMELEN] = "";
+ char who[256];
+ char dur[PATH_MAX];
+ char tmp[80] = "/tmp/astmail-XXXXXX";
+ char tmp2[PATH_MAX];
+ struct tm tm;
+ FILE *p;
+
+ if ((p = vm_mkftemp(tmp)) == NULL) {
+ ast_log(LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
+ return -1;
+ } else {
+ gethostname(host, sizeof(host)-1);
+ if (strchr(srcemail, '@'))
+ ast_copy_string(who, srcemail, sizeof(who));
+ else {
+ snprintf(who, sizeof(who), "%s@%s", srcemail, host);
+ }
+ snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
+ strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
+ fprintf(p, "Date: %s\n", date);
+
+ if (*pagerfromstring) {
+ struct ast_channel *ast;
+ if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "Substitution/voicemail"))) {
+ char *passdata;
+ int vmlen = strlen(fromstring)*3 + 200;
+ if ((passdata = alloca(vmlen))) {
+ memset(passdata, 0, vmlen);
+ prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
+ pbx_substitute_variables_helper(ast, pagerfromstring, passdata, vmlen);
+ fprintf(p, "From: %s <%s>\n", passdata, who);
+ } else
+ ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
+ ast_channel_free(ast);
+ } else ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
+ } else
+ fprintf(p, "From: Asterisk PBX <%s>\n", who);
+ fprintf(p, "To: %s\n", pager);
+ if (pagersubject) {
+ struct ast_channel *ast;
+ if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "Substitution/voicemail"))) {
+ char *passdata;
+ int vmlen = strlen(pagersubject) * 3 + 200;
+ if ((passdata = alloca(vmlen))) {
+ memset(passdata, 0, vmlen);
+ prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
+ pbx_substitute_variables_helper(ast, pagersubject, passdata, vmlen);
+ fprintf(p, "Subject: %s\n\n", passdata);
+ } else ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
+ ast_channel_free(ast);
+ } else ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
+ } else
+ fprintf(p, "Subject: New VM\n\n");
+ strftime(date, sizeof(date), "%A, %B %d, %Y at %r", &tm);
+ if (pagerbody) {
+ struct ast_channel *ast;
+ if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "Substitution/voicemail"))) {
+ char *passdata;
+ int vmlen = strlen(pagerbody)*3 + 200;
+ if ((passdata = alloca(vmlen))) {
+ memset(passdata, 0, vmlen);
+ prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
+ pbx_substitute_variables_helper(ast, pagerbody, passdata, vmlen);
+ fprintf(p, "%s\n", passdata);
+ } else ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
+ ast_channel_free(ast);
+ } else ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
+ } else {
+ fprintf(p, "New %s long msg in box %s\n"
+ "from %s, on %s", dur, mailbox, (cidname ? cidname : (cidnum ? cidnum : "unknown")), date);
+ }
+ fclose(p);
+ snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
+ ast_safe_system(tmp2);
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Sent page to %s with command '%s'\n", pager, mailcmd);
+ }
+ return 0;
+}
+
+static int get_date(char *s, int len)
+{
+ struct tm tm;
+ time_t t;
+
+ time(&t);
+
+ ast_localtime(&t, &tm, NULL);
+
+ return strftime(s, len, "%a %b %e %r %Z %Y", &tm);
+}
+
+static int play_greeting(struct ast_channel *chan, struct ast_vm_user *vmu, char *filename, char *ecodes)
+{
+ int res = -2;
+
+#ifdef ODBC_STORAGE
+ int success =
+#endif
+ RETRIEVE(filename, -1, vmu);
+ if (ast_fileexists(filename, NULL, NULL) > 0) {
+ res = ast_streamfile(chan, filename, chan->language);
+ if (res > -1)
+ res = ast_waitstream(chan, ecodes);
+#ifdef ODBC_STORAGE
+ if (success == -1) {
+ /* We couldn't retrieve the file from the database, but we found it on the file system. Let's put it in the database. */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Greeting not retrieved from database, but found in file storage. Inserting into database\n");
+ store_file(filename, vmu->mailbox, vmu->context, -1);
+ }
+#endif
+ }
+ DISPOSE(filename, -1);
+
+ return res;
+}
+
+static int invent_message(struct ast_channel *chan, struct ast_vm_user *vmu, char *ext, int busy, char *ecodes)
+{
+ int res;
+ char fn[PATH_MAX];
+ char dest[PATH_MAX];
+
+ snprintf(fn, sizeof(fn), "%s%s/%s/greet", VM_SPOOL_DIR, vmu->context, ext);
+
+ if ((res = create_dirpath(dest, sizeof(dest), vmu->context, ext, "greet"))) {
+ ast_log(LOG_WARNING, "Failed to make directory(%s)\n", fn);
+ return -1;
+ }
+
+ res = play_greeting(chan, vmu, fn, ecodes);
+ if (res == -2) {
+ /* File did not exist */
+ res = ast_stream_and_wait(chan, "vm-theperson", chan->language, ecodes);
+ if (res)
+ return res;
+ res = ast_say_digit_str(chan, ext, ecodes, chan->language);
+ }
+
+ if (res)
+ return res;
+
+ res = ast_stream_and_wait(chan, busy ? "vm-isonphone" : "vm-isunavail", chan->language, ecodes);
+ return res;
+}
+
+static void free_zone(struct vm_zone *z)
+{
+ free(z);
+}
+
+#ifdef ODBC_STORAGE
+/*! XXX \todo Fix this function to support multiple mailboxes in the intput string */
+static int inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
+{
+ int x = -1;
+ int res;
+ SQLHSTMT stmt;
+ char sql[PATH_MAX];
+ char rowdata[20];
+ char tmp[PATH_MAX] = "";
+ struct odbc_obj *obj;
+ char *context;
+ struct generic_prepare_struct gps = { .sql = sql, .argc = 0 };
+
+ if (newmsgs)
+ *newmsgs = 0;
+ if (oldmsgs)
+ *oldmsgs = 0;
+
+ /* If no mailbox, return immediately */
+ if (ast_strlen_zero(mailbox))
+ return 0;
+
+ ast_copy_string(tmp, mailbox, sizeof(tmp));
+
+ context = strchr(tmp, '@');
+ if (context) {
+ *context = '\0';
+ context++;
+ } else
+ context = "default";
+
+ obj = ast_odbc_request_obj(odbc_database, 0);
+ if (obj) {
+ snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "INBOX");
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt) {
+ ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ ast_odbc_release_obj(obj);
+ goto yuck;
+ }
+ res = SQLFetch(stmt);
+ 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);
+ goto yuck;
+ }
+ res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
+ SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ goto yuck;
+ }
+ *newmsgs = atoi(rowdata);
+ SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+
+ snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "Old");
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt) {
+ ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ ast_odbc_release_obj(obj);
+ goto yuck;
+ }
+ res = SQLFetch(stmt);
+ 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);
+ goto yuck;
+ }
+ res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
+ SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ goto yuck;
+ }
+ SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ *oldmsgs = atoi(rowdata);
+ x = 0;
+ } else
+ ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
+
+yuck:
+ return x;
+}
+
+static int messagecount(const char *context, const char *mailbox, const char *folder)
+{
+ struct odbc_obj *obj = NULL;
+ int nummsgs = 0;
+ int res;
+ SQLHSTMT stmt = NULL;
+ char sql[PATH_MAX];
+ char rowdata[20];
+ struct generic_prepare_struct gps = { .sql = sql, .argc = 0 };
+ if (!folder)
+ folder = "INBOX";
+ /* If no mailbox, return immediately */
+ if (ast_strlen_zero(mailbox))
+ return 0;
+
+ obj = ast_odbc_request_obj(odbc_database, 0);
+ if (obj) {
+ snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, mailbox, folder);
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt) {
+ ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ goto yuck;
+ }
+ res = SQLFetch(stmt);
+ 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);
+ goto yuck;
+ }
+ res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
+ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
+ SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+ goto yuck;
+ }
+ nummsgs = atoi(rowdata);
+ SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+ } else
+ ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
+
+yuck:
+ if (obj)
+ ast_odbc_release_obj(obj);
+ return nummsgs;
+}
+
+static int has_voicemail(const char *mailbox, const char *folder)
+{
+ char tmp[256], *tmp2 = tmp, *mbox, *context;
+ ast_copy_string(tmp, mailbox, sizeof(tmp));
+ while ((context = mbox = strsep(&tmp2, ","))) {
+ strsep(&context, "@");
+ if (ast_strlen_zero(context))
+ context = "default";
+ if (messagecount(context, mbox, folder))
+ return 1;
+ }
+ return 0;
+}
+#endif
+#ifndef IMAP_STORAGE
+/* copy message only used by file storage */
+static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir)
+{
+ char fromdir[PATH_MAX], todir[PATH_MAX], frompath[PATH_MAX], topath[PATH_MAX];
+ const char *frombox = mbox(imbox);
+ int recipmsgnum;
+
+ ast_log(LOG_NOTICE, "Copying message from %s@%s to %s@%s\n", vmu->mailbox, vmu->context, recip->mailbox, recip->context);
+
+ create_dirpath(todir, sizeof(todir), recip->context, recip->mailbox, "INBOX");
+
+ if (!dir)
+ make_dir(fromdir, sizeof(fromdir), vmu->context, vmu->mailbox, frombox);
+ else
+ ast_copy_string(fromdir, dir, sizeof(fromdir));
+
+ make_file(frompath, sizeof(frompath), fromdir, msgnum);
+
+ if (vm_lock_path(todir))
+ return ERROR_LOCK_PATH;
+
+ recipmsgnum = 0;
+ do {
+ make_file(topath, sizeof(topath), todir, recipmsgnum);
+ if (!EXISTS(todir, recipmsgnum, topath, chan->language))
+ break;
+ recipmsgnum++;
+ } while (recipmsgnum < recip->maxmsg);
+ if (recipmsgnum < recip->maxmsg) {
+ if (EXISTS(fromdir, msgnum, frompath, chan->language)) {
+ COPY(fromdir, msgnum, todir, recipmsgnum, recip->mailbox, recip->context, frompath, topath);
+ } else {
+ /* For ODBC storage, if the file we want to copy isn't yet in the database, then the SQL
+ * copy will fail. Instead, we need to create a local copy, store it, and delete the local
+ * copy. We don't have to #ifdef this because if file storage reaches this point, there's a
+ * much worse problem happening and IMAP storage doesn't call this function
+ */
+ copy_plain_file(frompath, topath);
+ STORE(todir, recip->mailbox, recip->context, recipmsgnum, chan, recip, fmt, duration, NULL);
+ vm_delete(topath);
+ }
+ } else {
+ ast_log(LOG_ERROR, "Recipient mailbox %s@%s is full\n", recip->mailbox, recip->context);
+ }
+ ast_unlock_path(todir);
+ notify_new_message(chan, recip, recipmsgnum, duration, fmt, S_OR(chan->cid.cid_num, NULL), S_OR(chan->cid.cid_name, NULL));
+
+ return 0;
+}
+#endif
+#if !(defined(IMAP_STORAGE) || defined(ODBC_STORAGE))
+static int messagecount(const char *context, const char *mailbox, const char *folder)
+{
+ return __has_voicemail(context, mailbox, folder, 0);
+}
+
+
+static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit)
+{
+ DIR *dir;
+ struct dirent *de;
+ char fn[256];
+ int ret = 0;
+ if (!folder)
+ folder = "INBOX";
+ /* If no mailbox, return immediately */
+ if (ast_strlen_zero(mailbox))
+ return 0;
+ if (!context)
+ context = "default";
+ snprintf(fn, sizeof(fn), "%s%s/%s/%s", VM_SPOOL_DIR, context, mailbox, folder);
+ dir = opendir(fn);
+ if (!dir)
+ return 0;
+ while ((de = readdir(dir))) {
+ if (!strncasecmp(de->d_name, "msg", 3)) {
+ if (shortcircuit) {
+ ret = 1;
+ break;
+ } else if (!strncasecmp(de->d_name + 8, "txt", 3))
+ ret++;
+ }
+ }
+ closedir(dir);
+ return ret;
+}
+
+
+static int has_voicemail(const char *mailbox, const char *folder)
+{
+ char tmp[256], *tmp2 = tmp, *mbox, *context;
+ ast_copy_string(tmp, mailbox, sizeof(tmp));
+ while ((mbox = strsep(&tmp2, ","))) {
+ if ((context = strchr(mbox, '@')))
+ *context++ = '\0';
+ else
+ context = "default";
+ if (__has_voicemail(context, mbox, folder, 1))
+ return 1;
+ }
+ return 0;
+}
+
+
+static int inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
+{
+ char tmp[256];
+ char *context;
+
+ if (newmsgs)
+ *newmsgs = 0;
+ if (oldmsgs)
+ *oldmsgs = 0;
+ /* If no mailbox, return immediately */
+ if (ast_strlen_zero(mailbox))
+ return 0;
+ if (strchr(mailbox, ',')) {
+ int tmpnew, tmpold;
+ char *mb, *cur;
+
+ ast_copy_string(tmp, mailbox, sizeof(tmp));
+ mb = tmp;
+ while ((cur = strsep(&mb, ", "))) {
+ if (!ast_strlen_zero(cur)) {
+ if (inboxcount(cur, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
+ return -1;
+ else {
+ if (newmsgs)
+ *newmsgs += tmpnew;
+ if (oldmsgs)
+ *oldmsgs += tmpold;
+ }
+ }
+ }
+ return 0;
+ }
+ ast_copy_string(tmp, mailbox, sizeof(tmp));
+ context = strchr(tmp, '@');
+ if (context) {
+ *context = '\0';
+ context++;
+ } else
+ context = "default";
+ if (newmsgs)
+ *newmsgs = __has_voicemail(context, tmp, "INBOX", 0);
+ if (oldmsgs)
+ *oldmsgs = __has_voicemail(context, tmp, "Old", 0);
+ return 0;
+}
+
+#endif
+
+static void run_externnotify(char *context, char *extension)
+{
+ char arguments[255];
+ char ext_context[256] = "";
+ int newvoicemails = 0, oldvoicemails = 0;
+ struct ast_smdi_mwi_message *mwi_msg;
+
+ if (!ast_strlen_zero(context))
+ snprintf(ext_context, sizeof(ext_context), "%s@%s", extension, context);
+ else
+ ast_copy_string(ext_context, extension, sizeof(ext_context));
+
+ if (!strcasecmp(externnotify, "smdi")) {
+ if (ast_app_has_voicemail(ext_context, NULL))
+ ast_smdi_mwi_set(smdi_iface, extension);
+ else
+ ast_smdi_mwi_unset(smdi_iface, extension);
+
+ if ((mwi_msg = ast_smdi_mwi_message_wait_station(smdi_iface, SMDI_MWI_WAIT_TIMEOUT, extension))) {
+ ast_log(LOG_ERROR, "Error executing SMDI MWI change for %s\n", extension);
+ if (!strncmp(mwi_msg->cause, "INV", 3))
+ ast_log(LOG_ERROR, "Invalid MWI extension: %s\n", mwi_msg->fwd_st);
+ else if (!strncmp(mwi_msg->cause, "BLK", 3))
+ ast_log(LOG_WARNING, "MWI light was already on or off for %s\n", mwi_msg->fwd_st);
+ ast_log(LOG_WARNING, "The switch reported '%s'\n", mwi_msg->cause);
+ ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
+ } else {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Successfully executed SMDI MWI change for %s\n", extension);
+ }
+ } else if (!ast_strlen_zero(externnotify)) {
+ if (inboxcount(ext_context, &newvoicemails, &oldvoicemails)) {
+ ast_log(LOG_ERROR, "Problem in calculating number of voicemail messages available for extension %s\n", extension);
+ } else {
+ snprintf(arguments, sizeof(arguments), "%s %s %s %d&", externnotify, context, extension, newvoicemails);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Executing %s\n", arguments);
+ ast_safe_system(arguments);
+ }
+ }
+}
+
+struct leave_vm_options {
+ unsigned int flags;
+ signed char record_gain;
+};
+
+static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_options *options)
+{
+#ifdef IMAP_STORAGE
+ int newmsgs, oldmsgs;
+#endif
+ struct vm_state *vms = NULL;
+ char txtfile[PATH_MAX], tmptxtfile[PATH_MAX];
+ char callerid[256];
+ FILE *txt;
+ char date[256];
+ int txtdes;
+ int res = 0;
+ int msgnum;
+ int duration = 0;
+ int ausemacro = 0;
+ int ousemacro = 0;
+ int ouseexten = 0;
+ char dir[PATH_MAX], tmpdir[PATH_MAX];
+ char dest[PATH_MAX];
+ char fn[PATH_MAX];
+ char prefile[PATH_MAX] = "";
+ char tempfile[PATH_MAX] = "";
+ char ext_context[256] = "";
+ char fmt[80];
+ char *context;
+ char ecodes[16] = "#";
+ char tmp[1024] = "", *tmpptr;
+ struct ast_vm_user *vmu;
+ struct ast_vm_user svm;
+ const char *category = NULL;
+
+ ast_copy_string(tmp, ext, sizeof(tmp));
+ ext = tmp;
+ context = strchr(tmp, '@');
+ if (context) {
+ *context++ = '\0';
+ tmpptr = strchr(context, '&');
+ } else {
+ tmpptr = strchr(ext, '&');
+ }
+
+ if (tmpptr)
+ *tmpptr++ = '\0';
+
+ category = pbx_builtin_getvar_helper(chan, "VM_CATEGORY");
+
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Before find_user\n");
+ if (!(vmu = find_user(&svm, context, ext))) {
+ ast_log(LOG_WARNING, "No entry in voicemail config file for '%s'\n", ext);
+ if (ast_test_flag(options, OPT_PRIORITY_JUMP) || ast_opt_priority_jumping)
+ ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
+ pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
+ return res;
+ }
+ /* Setup pre-file if appropriate */
+ if (strcmp(vmu->context, "default"))
+ snprintf(ext_context, sizeof(ext_context), "%s@%s", ext, vmu->context);
+ else
+ ast_copy_string(ext_context, vmu->mailbox, sizeof(ext_context));
+ if (ast_test_flag(options, OPT_BUSY_GREETING)) {
+ res = create_dirpath(dest, sizeof(dest), vmu->context, ext, "busy");
+ snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, ext);
+ } else if (ast_test_flag(options, OPT_UNAVAIL_GREETING)) {
+ res = create_dirpath(dest, sizeof(dest), vmu->context, ext, "unavail");
+ snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, ext);
+ }
+ snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", VM_SPOOL_DIR, vmu->context, ext);
+ if ((res = create_dirpath(dest, sizeof(dest), vmu->context, ext, "temp"))) {
+ ast_log(LOG_WARNING, "Failed to make directory (%s)\n", tempfile);
+ return -1;
+ }
+ RETRIEVE(tempfile, -1, vmu);
+ if (ast_fileexists(tempfile, NULL, NULL) > 0)
+ ast_copy_string(prefile, tempfile, sizeof(prefile));
+ DISPOSE(tempfile, -1);
+ /* It's easier just to try to make it than to check for its existence */
+ create_dirpath(dir, sizeof(dir), vmu->context, ext, "INBOX");
+ create_dirpath(tmpdir, sizeof(tmpdir), vmu->context, ext, "tmp");
+
+ /* Check current or macro-calling context for special extensions */
+ if (ast_test_flag(vmu, VM_OPERATOR)) {
+ if (!ast_strlen_zero(vmu->exit)) {
+ if (ast_exists_extension(chan, vmu->exit, "o", 1, chan->cid.cid_num)) {
+ strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
+ ouseexten = 1;
+ }
+ } else if (ast_exists_extension(chan, chan->context, "o", 1, chan->cid.cid_num)) {
+ strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
+ ouseexten = 1;
+ }
+ else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "o", 1, chan->cid.cid_num)) {
+ strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
+ ousemacro = 1;
+ }
+ }
+
+ if (!ast_strlen_zero(vmu->exit)) {
+ if (ast_exists_extension(chan, vmu->exit, "a", 1, chan->cid.cid_num))
+ strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
+ } else if (ast_exists_extension(chan, chan->context, "a", 1, chan->cid.cid_num))
+ strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
+ else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "a", 1, chan->cid.cid_num)) {
+ strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
+ ausemacro = 1;
+ }
+
+ /* Play the beginning intro if desired */
+ if (!ast_strlen_zero(prefile)) {
+ res = play_greeting(chan, vmu, prefile, ecodes);
+ if (res == -2) {
+ /* The file did not exist */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "%s doesn't exist, doing what we can\n", prefile);
+ res = invent_message(chan, vmu, ext, ast_test_flag(options, OPT_BUSY_GREETING), ecodes);
+ }
+ if (res < 0) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Hang up during prefile playback\n");
+ free_user(vmu);
+ pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
+ return -1;
+ }
+ }
+ if (res == '#') {
+ /* On a '#' we skip the instructions */
+ ast_set_flag(options, OPT_SILENT);
+ res = 0;
+ }
+ if (!res && !ast_test_flag(options, OPT_SILENT)) {
+ res = ast_stream_and_wait(chan, INTRO, chan->language, ecodes);
+ if (res == '#') {
+ ast_set_flag(options, OPT_SILENT);
+ res = 0;
+ }
+ }
+ if (res > 0)
+ ast_stopstream(chan);
+ /* Check for a '*' here in case the caller wants to escape from voicemail to something
+ other than the operator -- an automated attendant or mailbox login for example */
+ if (res == '*') {
+ chan->exten[0] = 'a';
+ chan->exten[1] = '\0';
+ if (!ast_strlen_zero(vmu->exit)) {
+ ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
+ } else if (ausemacro && !ast_strlen_zero(chan->macrocontext)) {
+ ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
+ }
+ chan->priority = 0;
+ free_user(vmu);
+ pbx_builtin_setvar_helper(chan, "VMSTATUS", "USEREXIT");
+ return 0;
+ }
+
+ /* Check for a '0' here */
+ if (res == '0') {
+ transfer:
+ if (ouseexten || ousemacro) {
+ chan->exten[0] = 'o';
+ chan->exten[1] = '\0';
+ if (!ast_strlen_zero(vmu->exit)) {
+ ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
+ } else if (ousemacro && !ast_strlen_zero(chan->macrocontext)) {
+ ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
+ }
+ ast_play_and_wait(chan, "transfer");
+ chan->priority = 0;
+ free_user(vmu);
+ pbx_builtin_setvar_helper(chan, "VMSTATUS", "USEREXIT");
+ }
+ return 0;
+ }
+ if (res < 0) {
+ free_user(vmu);
+ pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
+ return -1;
+ }
+ /* The meat of recording the message... All the announcements and beeps have been played*/
+ ast_copy_string(fmt, vmfmts, sizeof(fmt));
+ if (!ast_strlen_zero(fmt)) {
+ msgnum = 0;
+
+#ifdef IMAP_STORAGE
+ /* Is ext a mailbox? */
+ /* must open stream for this user to get info! */
+ res = inboxcount(ext_context, &newmsgs, &oldmsgs);
+ if (res < 0) {
+ ast_log(LOG_NOTICE,"Can not leave voicemail, unable to count messages\n");
+ return -1;
+ }
+ if (!(vms = get_vm_state_by_mailbox(ext, context, 0))) {
+ /*It is possible under certain circumstances that inboxcount did not create a vm_state when it was needed. This is a catchall which will
+ * rarely be used*/
+ if (!(vms = create_vm_state_from_user(vmu))) {
+ ast_log(LOG_ERROR, "Couldn't allocate necessary space\n");
+ return -1;
+ }
+ }
+ vms->newmessages++;
+ /* here is a big difference! We add one to it later */
+ msgnum = newmsgs + oldmsgs;
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Messagecount set to %d\n",msgnum);
+ snprintf(fn, sizeof(fn), "%s/imap/msg%s%04d", VM_SPOOL_DIR, vmu->mailbox, msgnum);
+ /* set variable for compatability */
+ pbx_builtin_setvar_helper(chan, "VM_MESSAGEFILE", "IMAP_STORAGE");
+
+ /* Check if mailbox is full */
+ check_quota(vms, imapfolder);
+ if (vms->quota_limit && vms->quota_usage >= vms->quota_limit) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "*** QUOTA EXCEEDED!! %u >= %u\n", vms->quota_usage, vms->quota_limit);
+ ast_play_and_wait(chan, "vm-mailboxfull");
+ return -1;
+ }
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Checking message number quota - mailbox has %d messages, maximum is set to %d\n",msgnum,vmu->maxmsg);
+ if (msgnum >= vmu->maxmsg) {
+ res = ast_streamfile(chan, "vm-mailboxfull", chan->language);
+ if (!res)
+ res = ast_waitstream(chan, "");
+ ast_log(LOG_WARNING, "No more messages possible\n");
+ pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
+ goto leave_vm_out;
+ }
+
+ /* Check if we have exceeded maxmsg */
+ if (msgnum >= vmu->maxmsg) {
+ ast_log(LOG_WARNING, "Unable to leave message since we will exceed the maximum number of messages allowed (%u > %u)\n", msgnum, vmu->maxmsg);
+ ast_play_and_wait(chan, "vm-mailboxfull");
+ return -1;
+ }
+#else
+ if (count_messages(vmu, dir) >= vmu->maxmsg) {
+ res = ast_streamfile(chan, "vm-mailboxfull", chan->language);
+ if (!res)
+ res = ast_waitstream(chan, "");
+ ast_log(LOG_WARNING, "No more messages possible\n");
+ pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
+ goto leave_vm_out;
+ }
+
+#endif
+ snprintf(tmptxtfile, sizeof(tmptxtfile), "%s/XXXXXX", tmpdir);
+ txtdes = mkstemp(tmptxtfile);
+ chmod(tmptxtfile, VOICEMAIL_FILE_MODE & ~my_umask);
+ if (txtdes < 0) {
+ res = ast_streamfile(chan, "vm-mailboxfull", chan->language);
+ if (!res)
+ res = ast_waitstream(chan, "");
+ ast_log(LOG_ERROR, "Unable to create message file: %s\n", strerror(errno));
+ pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
+ goto leave_vm_out;
+ }
+
+ /* Now play the beep once we have the message number for our next message. */
+ if (res >= 0) {
+ /* Unless we're *really* silent, try to send the beep */
+ res = ast_stream_and_wait(chan, "beep", chan->language, "");
+ }
+
+ /* Store information */
+ txt = fdopen(txtdes, "w+");
+ if (txt) {
+ get_date(date, sizeof(date));
+ fprintf(txt,
+ ";\n"
+ "; Message Information file\n"
+ ";\n"
+ "[message]\n"
+ "origmailbox=%s\n"
+ "context=%s\n"
+ "macrocontext=%s\n"
+ "exten=%s\n"
+ "priority=%d\n"
+ "callerchan=%s\n"
+ "callerid=%s\n"
+ "origdate=%s\n"
+ "origtime=%ld\n"
+ "category=%s\n",
+ ext,
+ chan->context,
+ chan->macrocontext,
+ chan->exten,
+ chan->priority,
+ chan->name,
+ ast_callerid_merge(callerid, sizeof(callerid), S_OR(chan->cid.cid_name, NULL), S_OR(chan->cid.cid_num, NULL), "Unknown"),
+ date, (long)time(NULL),
+ category ? category : "");
+ } else
+ ast_log(LOG_WARNING, "Error opening text file for output\n");
+ res = play_record_review(chan, NULL, tmptxtfile, vmmaxmessage, fmt, 1, vmu, &duration, NULL, options->record_gain, vms);
+
+ if (txt) {
+ if (duration < vmminmessage) {
+ fclose(txt);
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "Recording was %d seconds long but needs to be at least %d - abandoning\n", duration, vmminmessage);
+ ast_filedelete(tmptxtfile, NULL);
+ unlink(tmptxtfile);
+ } else {
+ fprintf(txt, "duration=%d\n", duration);
+ fclose(txt);
+ if (vm_lock_path(dir)) {
+ ast_log(LOG_ERROR, "Couldn't lock directory %s. Voicemail will be lost.\n", dir);
+ /* Delete files */
+ ast_filedelete(tmptxtfile, NULL);
+ unlink(tmptxtfile);
+ } else if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "The recorded media file is gone, so we should remove the .txt file too!\n");
+ unlink(tmptxtfile);
+ ast_unlock_path(dir);
+ } else {
+ for (;;) {
+ make_file(fn, sizeof(fn), dir, msgnum);
+ if (!EXISTS(dir, msgnum, fn, NULL))
+ break;
+ msgnum++;
+ }
+
+ /* assign a variable with the name of the voicemail file */
+#ifndef IMAP_STORAGE
+ pbx_builtin_setvar_helper(chan, "VM_MESSAGEFILE", fn);
+#else
+ pbx_builtin_setvar_helper(chan, "VM_MESSAGEFILE", "IMAP_STORAGE");
+#endif
+
+ snprintf(txtfile, sizeof(txtfile), "%s.txt", fn);
+ ast_filerename(tmptxtfile, fn, NULL);
+ rename(tmptxtfile, txtfile);
+
+ ast_unlock_path(dir);
+ /* We must store the file first, before copying the message, because
+ * ODBC storage does the entire copy with SQL.
+ */
+ if (ast_fileexists(fn, NULL, NULL) > 0) {
+ STORE(dir, vmu->mailbox, vmu->context, msgnum, chan, vmu, fmt, duration, vms);
+ }
+
+ /* Are there to be more recipients of this message? */
+ while (tmpptr) {
+ struct ast_vm_user recipu, *recip;
+ char *exten, *context;
+
+ exten = strsep(&tmpptr, "&");
+ context = strchr(exten, '@');
+ if (context) {
+ *context = '\0';
+ context++;
+ }
+ if ((recip = find_user(&recipu, context, exten))) {
+ copy_message(chan, vmu, 0, msgnum, duration, recip, fmt, dir);
+ free_user(recip);
+ }
+ }
+ /* Notification and disposal needs to happen after the copy, though. */
+ if (ast_fileexists(fn, NULL, NULL)) {
+ notify_new_message(chan, vmu, msgnum, duration, fmt, S_OR(chan->cid.cid_num, NULL), S_OR(chan->cid.cid_name, NULL));
+ DISPOSE(dir, msgnum);
+ }
+ }
+ }
+ }
+ if (res == '0') {
+ goto transfer;
+ } else if (res > 0)
+ res = 0;
+
+ if (duration < vmminmessage)
+ /* XXX We should really give a prompt too short/option start again, with leave_vm_out called only after a timeout XXX */
+ pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
+ else
+ pbx_builtin_setvar_helper(chan, "VMSTATUS", "SUCCESS");
+ } else
+ ast_log(LOG_WARNING, "No format for saving voicemail?\n");
+leave_vm_out:
+ free_user(vmu);
+
+ return res;
+}
+
+#ifndef IMAP_STORAGE
+static int resequence_mailbox(struct ast_vm_user *vmu, char *dir)
+{
+ /* we know max messages, so stop process when number is hit */
+
+ int x,dest;
+ char sfn[PATH_MAX];
+ char dfn[PATH_MAX];
+
+ if (vm_lock_path(dir))
+ return ERROR_LOCK_PATH;
+
+ for (x = 0, dest = 0; x < vmu->maxmsg; x++) {
+ make_file(sfn, sizeof(sfn), dir, x);
+ if (EXISTS(dir, x, sfn, NULL)) {
+
+ if (x != dest) {
+ make_file(dfn, sizeof(dfn), dir, dest);
+ RENAME(dir, x, vmu->mailbox, vmu->context, dir, dest, sfn, dfn);
+ }
+
+ dest++;
+ }
+ }
+ ast_unlock_path(dir);
+
+ return 0;
+}
+#endif
+
+static int say_and_wait(struct ast_channel *chan, int num, const char *language)
+{
+ int d;
+ d = ast_say_number(chan, num, AST_DIGIT_ANY, language, (char *) NULL);
+ return d;
+}
+
+static int save_to_folder(struct ast_vm_user *vmu, struct vm_state *vms, int msg, int box)
+{
+#ifdef IMAP_STORAGE
+ /* we must use mbox(x) folder names, and copy the message there */
+ /* simple. huh? */
+ char sequence[10];
+ /* get the real IMAP message number for this message */
+ snprintf(sequence, sizeof(sequence), "%ld", vms->msgArray[msg]);
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Copying sequence %s to mailbox %s\n",sequence,(char *) mbox(box));
+ ast_mutex_lock(&vms->lock);
+ if (box == 1) {
+ mail_setflag(vms->mailstream, sequence, "\\Seen");
+ } else if (box == 0) {
+ mail_clearflag(vms->mailstream, sequence, "\\Seen");
+ }
+ if (!strcasecmp(mbox(0), vms->curbox) && (box == 0 || box == 1)) {
+ ast_mutex_unlock(&vms->lock);
+ return 0;
+ } else {
+ int res = !mail_copy(vms->mailstream,sequence,(char *) mbox(box));
+ ast_mutex_unlock(&vms->lock);
+ return res;
+ }
+#else
+ char *dir = vms->curdir;
+ char *username = vms->username;
+ char *context = vmu->context;
+ char sfn[PATH_MAX];
+ char dfn[PATH_MAX];
+ char ddir[PATH_MAX];
+ const char *dbox = mbox(box);
+ int x;
+ make_file(sfn, sizeof(sfn), dir, msg);
+ create_dirpath(ddir, sizeof(ddir), context, username, dbox);
+
+ if (vm_lock_path(ddir))
+ return ERROR_LOCK_PATH;
+
+ for (x = 0; x < vmu->maxmsg; x++) {
+ make_file(dfn, sizeof(dfn), ddir, x);
+ if (!EXISTS(ddir, x, dfn, NULL))
+ break;
+ }
+ if (x >= vmu->maxmsg) {
+ ast_unlock_path(ddir);
+ return ERROR_MAILBOX_FULL;
+ }
+ if (strcmp(sfn, dfn)) {
+ COPY(dir, msg, ddir, x, username, context, sfn, dfn);
+ }
+ ast_unlock_path(ddir);
+#endif
+ return 0;
+}
+
+static int adsi_logo(unsigned char *buf)
+{
+ int bytes = 0;
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 1, ADSI_JUST_CENT, 0, "Comedian Mail", "");
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 2, ADSI_JUST_CENT, 0, "(C)2002-2006 Digium, Inc.", "");
+ return bytes;
+}
+
+static int adsi_load_vmail(struct ast_channel *chan, int *useadsi)
+{
+ unsigned char buf[256];
+ int bytes=0;
+ int x;
+ char num[5];
+
+ *useadsi = 0;
+ bytes += ast_adsi_data_mode(buf + bytes);
+ ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
+
+ bytes = 0;
+ bytes += adsi_logo(buf);
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_CENT, 0, "Downloading Scripts", "");
+#ifdef DISPLAY
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_LEFT, 0, " .", "");
+#endif
+ bytes += ast_adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
+ bytes += ast_adsi_data_mode(buf + bytes);
+ ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
+
+ if (ast_adsi_begin_download(chan, addesc, adsifdn, adsisec, adsiver)) {
+ bytes = 0;
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_CENT, 0, "Load Cancelled.", "");
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_CENT, 0, "ADSI Unavailable", "");
+ bytes += ast_adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
+ bytes += ast_adsi_voice_mode(buf + bytes, 0);
+ ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
+ return 0;
+ }
+
+#ifdef DISPLAY
+ /* Add a dot */
+ bytes = 0;
+ bytes += ast_adsi_logo(buf);
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_CENT, 0, "Downloading Scripts", "");
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_LEFT, 0, " ..", "");
+ bytes += ast_adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
+ ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
+#endif
+ bytes = 0;
+ bytes += ast_adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 0, "Listen", "Listen", "1", 1);
+ bytes += ast_adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 1, "Folder", "Folder", "2", 1);
+ bytes += ast_adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 2, "Advanced", "Advnced", "3", 1);
+ bytes += ast_adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 3, "Options", "Options", "0", 1);
+ bytes += ast_adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 4, "Help", "Help", "*", 1);
+ bytes += ast_adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 5, "Exit", "Exit", "#", 1);
+ ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD);
+
+#ifdef DISPLAY
+ /* Add another dot */
+ bytes = 0;
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_LEFT, 0, " ...", "");
+ bytes += ast_adsi_voice_mode(buf + bytes, 0);
+
+ bytes += ast_adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
+ ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
+#endif
+
+ bytes = 0;
+ /* These buttons we load but don't use yet */
+ bytes += ast_adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 6, "Previous", "Prev", "4", 1);
+ bytes += ast_adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 8, "Repeat", "Repeat", "5", 1);
+ bytes += ast_adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 7, "Delete", "Delete", "7", 1);
+ bytes += ast_adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 9, "Next", "Next", "6", 1);
+ bytes += ast_adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 10, "Save", "Save", "9", 1);
+ bytes += ast_adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 11, "Undelete", "Restore", "7", 1);
+ ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD);
+
+#ifdef DISPLAY
+ /* Add another dot */
+ bytes = 0;
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_LEFT, 0, " ....", "");
+ bytes += ast_adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
+ ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
+#endif
+
+ bytes = 0;
+ for (x=0;x<5;x++) {
+ snprintf(num, sizeof(num), "%d", x);
+ bytes += ast_adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 12 + x, mbox(x), mbox(x), num, 1);
+ }
+ bytes += ast_adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 12 + 5, "Cancel", "Cancel", "#", 1);
+ ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD);
+
+#ifdef DISPLAY
+ /* Add another dot */
+ bytes = 0;
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_LEFT, 0, " .....", "");
+ bytes += ast_adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
+ ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
+#endif
+
+ if (ast_adsi_end_download(chan)) {
+ bytes = 0;
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_CENT, 0, "Download Unsuccessful.", "");
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_CENT, 0, "ADSI Unavailable", "");
+ bytes += ast_adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
+ bytes += ast_adsi_voice_mode(buf + bytes, 0);
+ ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
+ return 0;
+ }
+ bytes = 0;
+ bytes += ast_adsi_download_disconnect(buf + bytes);
+ bytes += ast_adsi_voice_mode(buf + bytes, 0);
+ ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD);
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Done downloading scripts...\n");
+
+#ifdef DISPLAY
+ /* Add last dot */
+ bytes = 0;
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_CENT, 0, " ......", "");
+ bytes += ast_adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
+#endif
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Restarting session...\n");
+
+ bytes = 0;
+ /* Load the session now */
+ if (ast_adsi_load_session(chan, adsifdn, adsiver, 1) == 1) {
+ *useadsi = 1;
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_CENT, 0, "Scripts Loaded!", "");
+ } else
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_CENT, 0, "Load Failed!", "");
+
+ ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
+ return 0;
+}
+
+static void adsi_begin(struct ast_channel *chan, int *useadsi)
+{
+ int x;
+ if (!ast_adsi_available(chan))
+ return;
+ x = ast_adsi_load_session(chan, adsifdn, adsiver, 1);
+ if (x < 0)
+ return;
+ if (!x) {
+ if (adsi_load_vmail(chan, useadsi)) {
+ ast_log(LOG_WARNING, "Unable to upload voicemail scripts\n");
+ return;
+ }
+ } else
+ *useadsi = 1;
+}
+
+static void adsi_login(struct ast_channel *chan)
+{
+ unsigned char buf[256];
+ int bytes=0;
+ unsigned char keys[8];
+ int x;
+ if (!ast_adsi_available(chan))
+ return;
+
+ for (x=0;x<8;x++)
+ keys[x] = 0;
+ /* Set one key for next */
+ keys[3] = ADSI_KEY_APPS + 3;
+
+ bytes += adsi_logo(buf + bytes);
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_CENT, 0, " ", "");
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_CENT, 0, " ", "");
+ bytes += ast_adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
+ bytes += ast_adsi_input_format(buf + bytes, 1, ADSI_DIR_FROM_LEFT, 0, "Mailbox: ******", "");
+ bytes += ast_adsi_input_control(buf + bytes, ADSI_COMM_PAGE, 4, 1, 1, ADSI_JUST_LEFT);
+ bytes += ast_adsi_load_soft_key(buf + bytes, ADSI_KEY_APPS + 3, "Enter", "Enter", "#", 1);
+ bytes += ast_adsi_set_keys(buf + bytes, keys);
+ bytes += ast_adsi_voice_mode(buf + bytes, 0);
+ ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
+}
+
+static void adsi_password(struct ast_channel *chan)
+{
+ unsigned char buf[256];
+ int bytes=0;
+ unsigned char keys[8];
+ int x;
+ if (!ast_adsi_available(chan))
+ return;
+
+ for (x=0;x<8;x++)
+ keys[x] = 0;
+ /* Set one key for next */
+ keys[3] = ADSI_KEY_APPS + 3;
+
+ bytes += ast_adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
+ bytes += ast_adsi_input_format(buf + bytes, 1, ADSI_DIR_FROM_LEFT, 0, "Password: ******", "");
+ bytes += ast_adsi_input_control(buf + bytes, ADSI_COMM_PAGE, 4, 0, 1, ADSI_JUST_LEFT);
+ bytes += ast_adsi_set_keys(buf + bytes, keys);
+ bytes += ast_adsi_voice_mode(buf + bytes, 0);
+ ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
+}
+
+static void adsi_folders(struct ast_channel *chan, int start, char *label)
+{
+ unsigned char buf[256];
+ int bytes=0;
+ unsigned char keys[8];
+ int x,y;
+
+ if (!ast_adsi_available(chan))
+ return;
+
+ for (x=0;x<5;x++) {
+ y = ADSI_KEY_APPS + 12 + start + x;
+ if (y > ADSI_KEY_APPS + 12 + 4)
+ y = 0;
+ keys[x] = ADSI_KEY_SKT | y;
+ }
+ keys[5] = ADSI_KEY_SKT | (ADSI_KEY_APPS + 17);
+ keys[6] = 0;
+ keys[7] = 0;
+
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 1, ADSI_JUST_CENT, 0, label, "");
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 2, ADSI_JUST_CENT, 0, " ", "");
+ bytes += ast_adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
+ bytes += ast_adsi_set_keys(buf + bytes, keys);
+ bytes += ast_adsi_voice_mode(buf + bytes, 0);
+
+ ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
+}
+
+static void adsi_message(struct ast_channel *chan, struct vm_state *vms)
+{
+ int bytes=0;
+ unsigned char buf[256];
+ char buf1[256], buf2[256];
+ char fn2[PATH_MAX];
+
+ char cid[256]="";
+ char *val;
+ char *name, *num;
+ char datetime[21]="";
+ FILE *f;
+
+ unsigned char keys[8];
+
+ int x;
+
+ if (!ast_adsi_available(chan))
+ return;
+
+ /* Retrieve important info */
+ snprintf(fn2, sizeof(fn2), "%s.txt", vms->fn);
+ f = fopen(fn2, "r");
+ if (f) {
+ while (!feof(f)) {
+ if (!fgets((char *)buf, sizeof(buf), f)) {
+ continue;
+ }
+ if (!feof(f)) {
+ char *stringp=NULL;
+ stringp = (char *)buf;
+ strsep(&stringp, "=");
+ val = strsep(&stringp, "=");
+ if (!ast_strlen_zero(val)) {
+ if (!strcmp((char *)buf, "callerid"))
+ ast_copy_string(cid, val, sizeof(cid));
+ if (!strcmp((char *)buf, "origdate"))
+ ast_copy_string(datetime, val, sizeof(datetime));
+ }
+ }
+ }
+ fclose(f);
+ }
+ /* New meaning for keys */
+ for (x=0;x<5;x++)
+ keys[x] = ADSI_KEY_SKT | (ADSI_KEY_APPS + 6 + x);
+ keys[6] = 0x0;
+ keys[7] = 0x0;
+
+ if (!vms->curmsg) {
+ /* No prev key, provide "Folder" instead */
+ keys[0] = ADSI_KEY_SKT | (ADSI_KEY_APPS + 1);
+ }
+ if (vms->curmsg >= vms->lastmsg) {
+ /* If last message ... */
+ if (vms->curmsg) {
+ /* but not only message, provide "Folder" instead */
+ keys[3] = ADSI_KEY_SKT | (ADSI_KEY_APPS + 1);
+ bytes += ast_adsi_voice_mode(buf + bytes, 0);
+
+ } else {
+ /* Otherwise if only message, leave blank */
+ keys[3] = 1;
+ }
+ }
+
+ if (!ast_strlen_zero(cid)) {
+ ast_callerid_parse(cid, &name, &num);
+ if (!name)
+ name = num;
+ } else
+ name = "Unknown Caller";
+
+ /* If deleted, show "undeleted" */
+
+ if (vms->deleted[vms->curmsg])
+ keys[1] = ADSI_KEY_SKT | (ADSI_KEY_APPS + 11);
+
+ /* Except "Exit" */
+ keys[5] = ADSI_KEY_SKT | (ADSI_KEY_APPS + 5);
+ snprintf(buf1, sizeof(buf1), "%s%s", vms->curbox,
+ strcasecmp(vms->curbox, "INBOX") ? " Messages" : "");
+ snprintf(buf2, sizeof(buf2), "Message %d of %d", vms->curmsg + 1, vms->lastmsg + 1);
+
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 1, ADSI_JUST_LEFT, 0, buf1, "");
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 2, ADSI_JUST_LEFT, 0, buf2, "");
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_LEFT, 0, name, "");
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_LEFT, 0, datetime, "");
+ bytes += ast_adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
+ bytes += ast_adsi_set_keys(buf + bytes, keys);
+ bytes += ast_adsi_voice_mode(buf + bytes, 0);
+
+ ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
+}
+
+static void adsi_delete(struct ast_channel *chan, struct vm_state *vms)
+{
+ int bytes=0;
+ unsigned char buf[256];
+ unsigned char keys[8];
+
+ int x;
+
+ if (!ast_adsi_available(chan))
+ return;
+
+ /* New meaning for keys */
+ for (x=0;x<5;x++)
+ keys[x] = ADSI_KEY_SKT | (ADSI_KEY_APPS + 6 + x);
+
+ keys[6] = 0x0;
+ keys[7] = 0x0;
+
+ if (!vms->curmsg) {
+ /* No prev key, provide "Folder" instead */
+ keys[0] = ADSI_KEY_SKT | (ADSI_KEY_APPS + 1);
+ }
+ if (vms->curmsg >= vms->lastmsg) {
+ /* If last message ... */
+ if (vms->curmsg) {
+ /* but not only message, provide "Folder" instead */
+ keys[3] = ADSI_KEY_SKT | (ADSI_KEY_APPS + 1);
+ } else {
+ /* Otherwise if only message, leave blank */
+ keys[3] = 1;
+ }
+ }
+
+ /* If deleted, show "undeleted" */
+ if (vms->deleted[vms->curmsg])
+ keys[1] = ADSI_KEY_SKT | (ADSI_KEY_APPS + 11);
+
+ /* Except "Exit" */
+ keys[5] = ADSI_KEY_SKT | (ADSI_KEY_APPS + 5);
+ bytes += ast_adsi_set_keys(buf + bytes, keys);
+ bytes += ast_adsi_voice_mode(buf + bytes, 0);
+
+ ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
+}
+
+static void adsi_status(struct ast_channel *chan, struct vm_state *vms)
+{
+ unsigned char buf[256] = "";
+ char buf1[256] = "", buf2[256] = "";
+ int bytes=0;
+ unsigned char keys[8];
+ int x;
+
+ char *newm = (vms->newmessages == 1) ? "message" : "messages";
+ char *oldm = (vms->oldmessages == 1) ? "message" : "messages";
+ if (!ast_adsi_available(chan))
+ return;
+ if (vms->newmessages) {
+ snprintf(buf1, sizeof(buf1), "You have %d new", vms->newmessages);
+ if (vms->oldmessages) {
+ strncat(buf1, " and", sizeof(buf1) - strlen(buf1) - 1);
+ snprintf(buf2, sizeof(buf2), "%d old %s.", vms->oldmessages, oldm);
+ } else {
+ snprintf(buf2, sizeof(buf2), "%s.", newm);
+ }
+ } else if (vms->oldmessages) {
+ snprintf(buf1, sizeof(buf1), "You have %d old", vms->oldmessages);
+ snprintf(buf2, sizeof(buf2), "%s.", oldm);
+ } else {
+ strcpy(buf1, "You have no messages.");
+ buf2[0] = ' ';
+ buf2[1] = '\0';
+ }
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 1, ADSI_JUST_LEFT, 0, buf1, "");
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 2, ADSI_JUST_LEFT, 0, buf2, "");
+ bytes += ast_adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
+
+ for (x=0;x<6;x++)
+ keys[x] = ADSI_KEY_SKT | (ADSI_KEY_APPS + x);
+ keys[6] = 0;
+ keys[7] = 0;
+
+ /* Don't let them listen if there are none */
+ if (vms->lastmsg < 0)
+ keys[0] = 1;
+ bytes += ast_adsi_set_keys(buf + bytes, keys);
+
+ bytes += ast_adsi_voice_mode(buf + bytes, 0);
+
+ ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
+}
+
+static void adsi_status2(struct ast_channel *chan, struct vm_state *vms)
+{
+ unsigned char buf[256] = "";
+ char buf1[256] = "", buf2[256] = "";
+ int bytes=0;
+ unsigned char keys[8];
+ int x;
+
+ char *mess = (vms->lastmsg == 0) ? "message" : "messages";
+
+ if (!ast_adsi_available(chan))
+ return;
+
+ /* Original command keys */
+ for (x=0;x<6;x++)
+ keys[x] = ADSI_KEY_SKT | (ADSI_KEY_APPS + x);
+
+ keys[6] = 0;
+ keys[7] = 0;
+
+ if ((vms->lastmsg + 1) < 1)
+ keys[0] = 0;
+
+ snprintf(buf1, sizeof(buf1), "%s%s has", vms->curbox,
+ strcasecmp(vms->curbox, "INBOX") ? " folder" : "");
+
+ if (vms->lastmsg + 1)
+ snprintf(buf2, sizeof(buf2), "%d %s.", vms->lastmsg + 1, mess);
+ else
+ strcpy(buf2, "no messages.");
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 1, ADSI_JUST_LEFT, 0, buf1, "");
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 2, ADSI_JUST_LEFT, 0, buf2, "");
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_LEFT, 0, "", "");
+ bytes += ast_adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
+ bytes += ast_adsi_set_keys(buf + bytes, keys);
+
+ bytes += ast_adsi_voice_mode(buf + bytes, 0);
+
+ ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
+
+}
+
+/*
+static void adsi_clear(struct ast_channel *chan)
+{
+ char buf[256];
+ int bytes=0;
+ if (!ast_adsi_available(chan))
+ return;
+ bytes += ast_adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
+ bytes += ast_adsi_voice_mode(buf + bytes, 0);
+
+ ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
+}
+*/
+
+static void adsi_goodbye(struct ast_channel *chan)
+{
+ unsigned char buf[256];
+ int bytes=0;
+
+ if (!ast_adsi_available(chan))
+ return;
+ bytes += adsi_logo(buf + bytes);
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_LEFT, 0, " ", "");
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_CENT, 0, "Goodbye", "");
+ bytes += ast_adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
+ bytes += ast_adsi_voice_mode(buf + bytes, 0);
+
+ ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
+}
+
+/*--- get_folder: Folder menu ---*/
+/* Plays "press 1 for INBOX messages" etc
+ Should possibly be internationalized
+ */
+static int get_folder(struct ast_channel *chan, int start)
+{
+ int x;
+ int d;
+ char fn[PATH_MAX];
+ d = ast_play_and_wait(chan, "vm-press"); /* "Press" */
+ if (d)
+ return d;
+ for (x = start; x< 5; x++) { /* For all folders */
+ if ((d = ast_say_number(chan, x, AST_DIGIT_ANY, chan->language, (char *) NULL)))
+ return d;
+ d = ast_play_and_wait(chan, "vm-for"); /* "for" */
+ if (d)
+ return d;
+ snprintf(fn, sizeof(fn), "vm-%s", mbox(x)); /* Folder name */
+ d = vm_play_folder_name(chan, fn);
+ if (d)
+ return d;
+ d = ast_waitfordigit(chan, 500);
+ if (d)
+ return d;
+ }
+ d = ast_play_and_wait(chan, "vm-tocancel"); /* "or pound to cancel" */
+ if (d)
+ return d;
+ d = ast_waitfordigit(chan, 4000);
+ return d;
+}
+
+static int get_folder2(struct ast_channel *chan, char *fn, int start)
+{
+ int res = 0;
+ res = ast_play_and_wait(chan, fn); /* Folder name */
+ while (((res < '0') || (res > '9')) &&
+ (res != '#') && (res >= 0)) {
+ res = get_folder(chan, 0);
+ }
+ return res;
+}
+
+static int vm_forwardoptions(struct ast_channel *chan, struct ast_vm_user *vmu, char *curdir, int curmsg, char *vmfmts,
+ char *context, signed char record_gain, long *duration, struct vm_state *vms)
+{
+ int cmd = 0;
+ int retries = 0, prepend_duration = 0, already_recorded = 0;
+ signed char zero_gain = 0;
+ struct ast_config *msg_cfg;
+ const char *duration_str;
+ char msgfile[PATH_MAX], backup[PATH_MAX];
+ char textfile[PATH_MAX];
+
+ /* Must always populate duration correctly */
+ make_file(msgfile, sizeof(msgfile), curdir, curmsg);
+ strcpy(textfile, msgfile);
+ strcpy(backup, msgfile);
+ strncat(textfile, ".txt", sizeof(textfile) - strlen(textfile) - 1);
+ strncat(backup, "-bak", sizeof(backup) - strlen(backup) - 1);
+
+ if (!(msg_cfg = ast_config_load(textfile))) {
+ return -1;
+ }
+
+ *duration = 0;
+ if ((duration_str = ast_variable_retrieve(msg_cfg, "message", "duration")))
+ *duration = atoi(duration_str);
+
+ while ((cmd >= 0) && (cmd != 't') && (cmd != '*')) {
+ if (cmd)
+ retries = 0;
+ switch (cmd) {
+ case '1':
+ /* prepend a message to the current message, update the metadata and return */
+ {
+ prepend_duration = 0;
+
+ /* if we can't read the message metadata, stop now */
+ if (!msg_cfg) {
+ cmd = 0;
+ break;
+ }
+
+ /* Back up the original file, so we can retry the prepend */
+ if (already_recorded)
+ ast_filecopy(backup, msgfile, NULL);
+ else
+ ast_filecopy(msgfile, backup, NULL);
+ already_recorded = 1;
+
+ if (record_gain)
+ ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0);
+
+ cmd = ast_play_and_prepend(chan, NULL, msgfile, 0, vmfmts, &prepend_duration, 1, silencethreshold, maxsilence);
+ if (record_gain)
+ ast_channel_setoption(chan, AST_OPTION_RXGAIN, &zero_gain, sizeof(zero_gain), 0);
+
+ if (prepend_duration) {
+ struct ast_category *msg_cat;
+ /* need enough space for a maximum-length message duration */
+ char duration_str[12];
+
+ prepend_duration += *duration;
+ msg_cat = ast_category_get(msg_cfg, "message");
+ snprintf(duration_str, 11, "%d", prepend_duration);
+ if (!ast_variable_update(msg_cat, "duration", duration_str, NULL, 0)) {
+ config_text_file_save(textfile, msg_cfg, "app_voicemail");
+ }
+ }
+
+ break;
+ }
+ case '2':
+ cmd = 't';
+ break;
+ case '*':
+ cmd = '*';
+ break;
+ default:
+ cmd = ast_play_and_wait(chan,"vm-forwardoptions");
+ /* "Press 1 to prepend a message or 2 to forward the message without prepending" */
+ if (!cmd)
+ cmd = ast_play_and_wait(chan,"vm-starmain");
+ /* "press star to return to the main menu" */
+ if (!cmd)
+ cmd = ast_waitfordigit(chan,6000);
+ if (!cmd)
+ retries++;
+ if (retries > 3)
+ cmd = 't';
+ }
+ }
+
+ ast_config_destroy(msg_cfg);
+ if (already_recorded)
+ ast_filedelete(backup, NULL);
+ if (prepend_duration)
+ *duration = prepend_duration;
+
+ if (cmd == 't' || cmd == 'S')
+ cmd = 0;
+ return cmd;
+}
+
+static int notify_new_message(struct ast_channel *chan, struct ast_vm_user *vmu, int msgnum, long duration, char *fmt, char *cidnum, char *cidname)
+{
+ char todir[PATH_MAX], fn[PATH_MAX], ext_context[PATH_MAX], *stringp;
+ int newmsgs = 0, oldmsgs = 0;
+ const char *category = pbx_builtin_getvar_helper(chan, "VM_CATEGORY");
+
+ make_dir(todir, sizeof(todir), vmu->context, vmu->mailbox, "INBOX");
+ make_file(fn, sizeof(fn), todir, msgnum);
+ snprintf(ext_context, sizeof(ext_context), "%s@%s", vmu->mailbox, vmu->context);
+
+ if (!ast_strlen_zero(vmu->attachfmt)) {
+ if (strstr(fmt, vmu->attachfmt)) {
+ fmt = vmu->attachfmt;
+ } else {
+ ast_log(LOG_WARNING, "Attachment format '%s' is not one of the recorded formats '%s'. Falling back to default format for '%s@%s'.\n", vmu->attachfmt, fmt, vmu->mailbox, vmu->context);
+ }
+ }
+
+ /* Attach only the first format */
+ fmt = ast_strdupa(fmt);
+ stringp = fmt;
+ strsep(&stringp, "|");
+
+ if (!ast_strlen_zero(vmu->email)) {
+ int attach_user_voicemail = ast_test_flag((&globalflags), VM_ATTACH);
+ char *myserveremail = serveremail;
+ attach_user_voicemail = ast_test_flag(vmu, VM_ATTACH);
+ if (!ast_strlen_zero(vmu->serveremail))
+ myserveremail = vmu->serveremail;
+
+ if (attach_user_voicemail)
+ RETRIEVE(todir, msgnum, vmu);
+
+ /*XXX possible imap issue, should category be NULL XXX*/
+ sendmail(myserveremail, vmu, msgnum, vmu->context, vmu->mailbox, cidnum, cidname, fn, fmt, duration, attach_user_voicemail, chan, category);
+
+ if (attach_user_voicemail)
+ DISPOSE(todir, msgnum);
+ }
+
+ if (!ast_strlen_zero(vmu->pager)) {
+ char *myserveremail = serveremail;
+ if (!ast_strlen_zero(vmu->serveremail))
+ myserveremail = vmu->serveremail;
+ sendpage(myserveremail, vmu->pager, msgnum, vmu->context, vmu->mailbox, cidnum, cidname, duration, vmu, category);
+ }
+
+ if (ast_test_flag(vmu, VM_DELETE)) {
+ DELETE(todir, msgnum, fn, vmu);
+ }
+
+ /* Leave voicemail for someone */
+ if (ast_app_has_voicemail(ext_context, NULL)) {
+ ast_app_inboxcount(ext_context, &newmsgs, &oldmsgs);
+ }
+ manager_event(EVENT_FLAG_CALL, "MessageWaiting", "Mailbox: %s@%s\r\nWaiting: %d\r\nNew: %d\r\nOld: %d\r\n", vmu->mailbox, vmu->context, ast_app_has_voicemail(ext_context, NULL), newmsgs, oldmsgs);
+ run_externnotify(vmu->context, vmu->mailbox);
+ return 0;
+}
+
+static int forward_message(struct ast_channel *chan, char *context, struct vm_state *vms, struct ast_vm_user *sender, char *fmt, int flag, signed char record_gain)
+{
+#ifdef IMAP_STORAGE
+ int todircount=0;
+ struct vm_state *dstvms;
+#endif
+ char username[70]="";
+ int res = 0, cmd = 0;
+ struct ast_vm_user *receiver = NULL, *vmtmp;
+ AST_LIST_HEAD_NOLOCK_STATIC(extensions, ast_vm_user);
+ char *stringp;
+ const char *s;
+ int saved_messages = 0, found = 0;
+ int valid_extensions = 0;
+ char *dir;
+ int curmsg;
+
+ if (vms == NULL) return -1;
+ dir = vms->curdir;
+ curmsg = vms->curmsg;
+
+ while (!res && !valid_extensions) {
+ int use_directory = 0;
+ if (ast_test_flag((&globalflags), VM_DIRECFORWARD)) {
+ int done = 0;
+ int retries = 0;
+ cmd=0;
+ while ((cmd >= 0) && !done ){
+ if (cmd)
+ retries = 0;
+ switch (cmd) {
+ case '1':
+ use_directory = 0;
+ done = 1;
+ break;
+ case '2':
+ use_directory = 1;
+ done=1;
+ break;
+ case '*':
+ cmd = 't';
+ done = 1;
+ break;
+ default:
+ /* Press 1 to enter an extension press 2 to use the directory */
+ cmd = ast_play_and_wait(chan,"vm-forward");
+ if (!cmd)
+ cmd = ast_waitfordigit(chan,3000);
+ if (!cmd)
+ retries++;
+ if (retries > 3)
+ {
+ cmd = 't';
+ done = 1;
+ }
+
+ }
+ }
+ if (cmd < 0 || cmd == 't')
+ break;
+ }
+
+ if (use_directory) {
+ /* use app_directory */
+
+ char old_context[sizeof(chan->context)];
+ char old_exten[sizeof(chan->exten)];
+ int old_priority;
+ struct ast_app* app;
+
+
+ app = pbx_findapp("Directory");
+ if (app) {
+ char vmcontext[256];
+ /* make backup copies */
+ memcpy(old_context, chan->context, sizeof(chan->context));
+ memcpy(old_exten, chan->exten, sizeof(chan->exten));
+ old_priority = chan->priority;
+
+ /* call the the Directory, changes the channel */
+ snprintf(vmcontext, sizeof(vmcontext), "%s||v", context ? context : "default");
+ res = pbx_exec(chan, app, vmcontext);
+
+ ast_copy_string(username, chan->exten, sizeof(username));
+
+ /* restore the old context, exten, and priority */
+ memcpy(chan->context, old_context, sizeof(chan->context));
+ memcpy(chan->exten, old_exten, sizeof(chan->exten));
+ chan->priority = old_priority;
+
+ } else {
+ ast_log(LOG_WARNING, "Could not find the Directory application, disabling directory_forward\n");
+ ast_clear_flag((&globalflags), VM_DIRECFORWARD);
+ }
+ } else {
+ /* Ask for an extension */
+ res = ast_streamfile(chan, "vm-extension", chan->language); /* "extension" */
+ if (res)
+ break;
+ if ((res = ast_readstring(chan, username, sizeof(username) - 1, 2000, 10000, "#") < 0))
+ break;
+ }
+
+ /* start all over if no username */
+ if (ast_strlen_zero(username))
+ continue;
+ stringp = username;
+ s = strsep(&stringp, "*");
+ /* start optimistic */
+ valid_extensions = 1;
+ while (s) {
+ /* Don't forward to ourselves but allow leaving a message for ourselves (flag == 1). find_user is going to malloc since we have a NULL as first argument */
+ if ((flag == 1 || strcmp(s,sender->mailbox)) && (receiver = find_user(NULL, context, s))) {
+ AST_LIST_INSERT_HEAD(&extensions, receiver, list);
+ found++;
+ } else {
+ valid_extensions = 0;
+ break;
+ }
+ s = strsep(&stringp, "*");
+ }
+ /* break from the loop of reading the extensions */
+ if (valid_extensions)
+ break;
+ /* "I am sorry, that's not a valid extension. Please try again." */
+ res = ast_play_and_wait(chan, "pbx-invalid");
+ }
+ /* check if we're clear to proceed */
+ if (AST_LIST_EMPTY(&extensions) || !valid_extensions)
+ return res;
+ if (flag==1) {
+ struct leave_vm_options leave_options;
+ char mailbox[AST_MAX_EXTENSION * 2 + 2];
+ /* Make sure that context doesn't get set as a literal "(null)" (or else find_user won't find it) */
+ if (context)
+ snprintf(mailbox, sizeof(mailbox), "%s@%s", username, context);
+ else
+ ast_copy_string(mailbox, username, sizeof(mailbox));
+
+ /* Send VoiceMail */
+ memset(&leave_options, 0, sizeof(leave_options));
+ leave_options.record_gain = record_gain;
+ cmd = leave_voicemail(chan, mailbox, &leave_options);
+ } else {
+ /* Forward VoiceMail */
+ long duration = 0;
+ char origmsgfile[PATH_MAX], msgfile[PATH_MAX];
+ struct vm_state vmstmp;
+
+ memcpy(&vmstmp, vms, sizeof(vmstmp));
+
+ make_file(origmsgfile, sizeof(origmsgfile), dir, curmsg);
+ create_dirpath(vmstmp.curdir, sizeof(vmstmp.curdir), sender->context, vmstmp.username, "tmp");
+ make_file(msgfile, sizeof(msgfile), vmstmp.curdir, curmsg);
+
+ RETRIEVE(dir, curmsg, sender);
+
+ /* Alter a surrogate file, only */
+ copy_plain_file(origmsgfile, msgfile);
+
+ cmd = vm_forwardoptions(chan, sender, vmstmp.curdir, curmsg, vmfmts, S_OR(context, "default"), record_gain, &duration, &vmstmp);
+ if (!cmd) {
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&extensions, vmtmp, list) {
+#ifdef IMAP_STORAGE
+ char *myserveremail;
+ int attach_user_voicemail;
+ /* get destination mailbox */
+ dstvms = get_vm_state_by_mailbox(vmtmp->mailbox, vmtmp->context, 0);
+ if (!dstvms) {
+ dstvms = create_vm_state_from_user(vmtmp);
+ }
+ if (dstvms) {
+ init_mailstream(dstvms, 0);
+ if (!dstvms->mailstream) {
+ ast_log (LOG_ERROR,"IMAP mailstream for %s is NULL\n",vmtmp->mailbox);
+ } else {
+ STORE(vmstmp.curdir, vmtmp->mailbox, vmtmp->context, dstvms->curmsg, chan, vmtmp, fmt, duration, dstvms);
+ run_externnotify(vmtmp->context, vmtmp->mailbox);
+ }
+ } else {
+ ast_log (LOG_ERROR,"Could not find state information for mailbox %s\n",vmtmp->mailbox);
+ }
+ myserveremail = serveremail;
+ if (!ast_strlen_zero(vmtmp->serveremail))
+ myserveremail = vmtmp->serveremail;
+ attach_user_voicemail = ast_test_flag(vmtmp, VM_ATTACH);
+ /* NULL category for IMAP storage */
+ sendmail(myserveremail, vmtmp, todircount, vmtmp->context, vmtmp->mailbox, S_OR(chan->cid.cid_num, NULL), S_OR(chan->cid.cid_name, NULL), vms->fn, fmt, duration, attach_user_voicemail, chan, NULL);
+#else
+ copy_message(chan, sender, -1, curmsg, duration, vmtmp, fmt, vmstmp.curdir);
+#endif
+ saved_messages++;
+ AST_LIST_REMOVE_CURRENT(&extensions, list);
+ free_user(vmtmp);
+ if (res)
+ break;
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+ if (saved_messages > 0) {
+ /* give confirmation that the message was saved */
+ /* commented out since we can't forward batches yet
+ if (saved_messages == 1)
+ res = ast_play_and_wait(chan, "vm-message");
+ else
+ res = ast_play_and_wait(chan, "vm-messages");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-saved"); */
+ res = ast_play_and_wait(chan, "vm-msgsaved");
+ }
+ }
+
+ /* Remove surrogate file */
+ vm_delete(msgfile);
+ DISPOSE(dir, curmsg);
+ }
+
+ /* If anything failed above, we still have this list to free */
+ while ((vmtmp = AST_LIST_REMOVE_HEAD(&extensions, list)))
+ free_user(vmtmp);
+ return res ? res : cmd;
+}
+
+static int wait_file2(struct ast_channel *chan, struct vm_state *vms, char *file)
+{
+ int res;
+ if ((res = ast_stream_and_wait(chan, file, chan->language, AST_DIGIT_ANY)) < 0)
+ ast_log(LOG_WARNING, "Unable to play message %s\n", file);
+ return res;
+}
+
+static int wait_file(struct ast_channel *chan, struct vm_state *vms, char *file)
+{
+ return ast_control_streamfile(chan, file, "#", "*", "1456789", "0", "2", skipms);
+}
+
+static int play_message_category(struct ast_channel *chan, const char *category)
+{
+ int res = 0;
+
+ if (!ast_strlen_zero(category))
+ res = ast_play_and_wait(chan, category);
+
+ if (res) {
+ ast_log(LOG_WARNING, "No sound file for category '%s' was found.\n", category);
+ res = 0;
+ }
+
+ return res;
+}
+
+static int play_message_datetime(struct ast_channel *chan, struct ast_vm_user *vmu, const char *origtime, const char *filename)
+{
+ int res = 0;
+ struct vm_zone *the_zone = NULL;
+ time_t t;
+
+ if (ast_get_time_t(origtime, &t, 0, NULL)) {
+ ast_log(LOG_WARNING, "Couldn't find origtime in %s\n", filename);
+ return 0;
+ }
+
+ /* Does this user have a timezone specified? */
+ if (!ast_strlen_zero(vmu->zonetag)) {
+ /* Find the zone in the list */
+ struct vm_zone *z;
+ AST_LIST_LOCK(&zones);
+ AST_LIST_TRAVERSE(&zones, z, list) {
+ if (!strcmp(z->name, vmu->zonetag)) {
+ the_zone = z;
+ break;
+ }
+ }
+ AST_LIST_UNLOCK(&zones);
+ }
+
+/* No internal variable parsing for now, so we'll comment it out for the time being */
+#if 0
+ /* Set the DIFF_* variables */
+ ast_localtime(&t, &time_now, NULL);
+ tv_now = ast_tvnow();
+ tnow = tv_now.tv_sec;
+ ast_localtime(&tnow, &time_then, NULL);
+
+ /* Day difference */
+ if (time_now.tm_year == time_then.tm_year)
+ snprintf(temp,sizeof(temp),"%d",time_now.tm_yday);
+ else
+ snprintf(temp,sizeof(temp),"%d",(time_now.tm_year - time_then.tm_year) * 365 + (time_now.tm_yday - time_then.tm_yday));
+ pbx_builtin_setvar_helper(chan, "DIFF_DAY", temp);
+
+ /* Can't think of how other diffs might be helpful, but I'm sure somebody will think of something. */
+#endif
+ if (the_zone)
+ res = ast_say_date_with_format(chan, t, AST_DIGIT_ANY, chan->language, the_zone->msg_format, the_zone->timezone);
+ else if (!strcasecmp(chan->language,"pl")) /* POLISH syntax */
+ res = ast_say_date_with_format(chan, t, AST_DIGIT_ANY, chan->language, "'vm-received' Q HM", NULL);
+ else if (!strcasecmp(chan->language,"se")) /* SWEDISH syntax */
+ res = ast_say_date_with_format(chan, t, AST_DIGIT_ANY, chan->language, "'vm-received' dB 'digits/at' k 'and' M", NULL);
+ else if (!strcasecmp(chan->language,"no")) /* NORWEGIAN syntax */
+ res = ast_say_date_with_format(chan, t, AST_DIGIT_ANY, chan->language, "'vm-received' Q 'digits/at' HM", NULL);
+ else if (!strcasecmp(chan->language,"de")) /* GERMAN syntax */
+ res = ast_say_date_with_format(chan, t, AST_DIGIT_ANY, chan->language, "'vm-received' Q 'digits/at' HM", NULL);
+ else if (!strcasecmp(chan->language,"nl")) /* DUTCH syntax */
+ res = ast_say_date_with_format(chan, t, AST_DIGIT_ANY, chan->language, "'vm-received' q 'digits/nl-om' HM", NULL);
+ else if (!strcasecmp(chan->language,"it")) /* ITALIAN syntax */
+ res = ast_say_date_with_format(chan, t, AST_DIGIT_ANY, chan->language, "'vm-received' q 'digits/at' 'digits/hours' k 'digits/e' M 'digits/minutes'", NULL);
+ else if (!strcasecmp(chan->language,"gr"))
+ res = ast_say_date_with_format(chan, t, AST_DIGIT_ANY, chan->language, "'vm-received' q H 'digits/kai' M ", NULL);
+ else if (!strcasecmp(chan->language,"pt_BR"))
+ res = ast_say_date_with_format(chan, t, AST_DIGIT_ANY, chan->language, "'vm-received' Ad 'digits/pt-de' B 'digits/pt-de' Y 'digits/pt-as' HM ", NULL);
+ else if (!strcasecmp(chan->language,"he"))
+ res = ast_say_date_with_format(chan, t, AST_DIGIT_ANY, chan->language, "'vm-received' Ad 'at2' kM", NULL);
+ else
+ res = ast_say_date_with_format(chan, t, AST_DIGIT_ANY, chan->language, "'vm-received' q 'digits/at' IMp", NULL);
+#if 0
+ pbx_builtin_setvar_helper(chan, "DIFF_DAY", NULL);
+#endif
+ return res;
+}
+
+
+
+static int play_message_callerid(struct ast_channel *chan, struct vm_state *vms, char *cid, const char *context, int callback)
+{
+ int res = 0;
+ int i;
+ char *callerid, *name;
+ char prefile[PATH_MAX] = "";
+
+
+ /* If voicemail cid is not enabled, or we didn't get cid or context from the attribute file, leave now. */
+ /* BB: Still need to change this so that if this function is called by the message envelope (and someone is explicitly requesting to hear the CID), it does not check to see if CID is enabled in the config file */
+ if ((cid == NULL)||(context == NULL))
+ return res;
+
+ /* Strip off caller ID number from name */
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "VM-CID: composite caller ID received: %s, context: %s\n", cid, context);
+ ast_callerid_parse(cid, &name, &callerid);
+ if ((!ast_strlen_zero(callerid)) && strcmp(callerid, "Unknown")) {
+ /* Check for internal contexts and only */
+ /* say extension when the call didn't come from an internal context in the list */
+ for (i = 0 ; i < MAX_NUM_CID_CONTEXTS ; i++){
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "VM-CID: comparing internalcontext: %s\n", cidinternalcontexts[i]);
+ if ((strcmp(cidinternalcontexts[i], context) == 0))
+ break;
+ }
+ if (i != MAX_NUM_CID_CONTEXTS){ /* internal context? */
+ if (!res) {
+ snprintf(prefile, sizeof(prefile), "%s%s/%s/greet", VM_SPOOL_DIR, context, callerid);
+ if (!ast_strlen_zero(prefile)) {
+ /* See if we can find a recorded name for this person instead of their extension number */
+ if (ast_fileexists(prefile, NULL, NULL) > 0) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Playing envelope info: CID number '%s' matches mailbox number, playing recorded name\n", callerid);
+ if (!callback)
+ res = wait_file2(chan, vms, "vm-from");
+ res = ast_stream_and_wait(chan, prefile, chan->language, "");
+ } else {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Playing envelope info: message from '%s'\n", callerid);
+ /* BB: Say "from extension" as one saying to sound smoother */
+ if (!callback)
+ res = wait_file2(chan, vms, "vm-from-extension");
+ res = ast_say_digit_str(chan, callerid, "", chan->language);
+ }
+ }
+ }
+ }
+
+ else if (!res){
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "VM-CID: Numeric caller id: (%s)\n",callerid);
+ /* BB: Since this is all nicely figured out, why not say "from phone number" in this case" */
+ if (!callback)
+ res = wait_file2(chan, vms, "vm-from-phonenumber");
+ res = ast_say_digit_str(chan, callerid, AST_DIGIT_ANY, chan->language);
+ }
+ } else {
+ /* Number unknown */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "VM-CID: From an unknown number\n");
+ /* Say "from an unknown caller" as one phrase - it is already recorded by "the voice" anyhow */
+ res = wait_file2(chan, vms, "vm-unknown-caller");
+ }
+ return res;
+}
+
+static int play_message_duration(struct ast_channel *chan, struct vm_state *vms, const char *duration, int minduration)
+{
+ int res = 0;
+ int durationm;
+ int durations;
+ /* Verify that we have a duration for the message */
+ if (duration == NULL)
+ return res;
+
+ /* Convert from seconds to minutes */
+ durations=atoi(duration);
+ durationm=(durations / 60);
+
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "VM-Duration: duration is: %d seconds converted to: %d minutes\n", durations, durationm);
+
+ if ((!res) && (durationm >= minduration)) {
+ res = wait_file2(chan, vms, "vm-duration");
+
+ /* POLISH syntax */
+ if (!strcasecmp(chan->language, "pl")) {
+ div_t num = div(durationm, 10);
+
+ if (durationm == 1) {
+ res = ast_play_and_wait(chan, "digits/1z");
+ res = res ? res : ast_play_and_wait(chan, "vm-minute-ta");
+ } else if (num.rem > 1 && num.rem < 5 && num.quot != 1) {
+ if (num.rem == 2) {
+ if (!num.quot) {
+ res = ast_play_and_wait(chan, "digits/2-ie");
+ } else {
+ res = say_and_wait(chan, durationm - 2 , chan->language);
+ res = res ? res : ast_play_and_wait(chan, "digits/2-ie");
+ }
+ } else {
+ res = say_and_wait(chan, durationm, chan->language);
+ }
+ res = res ? res : ast_play_and_wait(chan, "vm-minute-ty");
+ } else {
+ res = say_and_wait(chan, durationm, chan->language);
+ res = res ? res : ast_play_and_wait(chan, "vm-minute-t");
+ }
+ /* DEFAULT syntax */
+ } else {
+ res = ast_say_number(chan, durationm, AST_DIGIT_ANY, chan->language, NULL);
+ res = wait_file2(chan, vms, "vm-minutes");
+ }
+ }
+ return res;
+}
+
+static int play_message(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms)
+{
+ int res = 0;
+ char filename[256], *cid;
+ const char *origtime, *context, *category, *duration;
+ struct ast_config *msg_cfg;
+
+ vms->starting = 0;
+ make_file(vms->fn, sizeof(vms->fn), vms->curdir, vms->curmsg);
+ adsi_message(chan, vms);
+ if (!strcasecmp(chan->language, "he")) { /* HEBREW FORMAT */
+ /*
+ * The syntax in hebrew for counting the number of message is up side down
+ * in comparison to english.
+ */
+ if (!vms->curmsg) {
+ res = wait_file2(chan, vms, "vm-message");
+ res = wait_file2(chan, vms, "vm-first"); /* "First" */
+ } else if (vms->curmsg == vms->lastmsg) {
+ res = wait_file2(chan, vms, "vm-message");
+ res = wait_file2(chan, vms, "vm-last"); /* "last" */
+ } else {
+ res = wait_file2(chan, vms, "vm-message"); /* "message" */
+ if (vms->curmsg && (vms->curmsg != vms->lastmsg)) {
+ ast_log(LOG_DEBUG, "curmsg: %d\n", vms->curmsg);
+ ast_log(LOG_DEBUG, "lagmsg: %d\n", vms->lastmsg);
+ if (!res) {
+ res = ast_say_number(chan, vms->curmsg + 1, AST_DIGIT_ANY, chan->language, "f");
+ }
+ }
+ }
+ } else {
+ if (!vms->curmsg)
+ res = wait_file2(chan, vms, "vm-first"); /* "First" */
+ else if (vms->curmsg == vms->lastmsg)
+ res = wait_file2(chan, vms, "vm-last"); /* "last" */
+ }
+ if (!res) {
+ /* POLISH syntax */
+ if (!strcasecmp(chan->language, "pl")) {
+ if (vms->curmsg && (vms->curmsg != vms->lastmsg)) {
+ int ten, one;
+ char nextmsg[256];
+ ten = (vms->curmsg + 1) / 10;
+ one = (vms->curmsg + 1) % 10;
+
+ if (vms->curmsg < 20) {
+ snprintf(nextmsg, sizeof(nextmsg), "digits/n-%d", vms->curmsg + 1);
+ res = wait_file2(chan, vms, nextmsg);
+ } else {
+ snprintf(nextmsg, sizeof(nextmsg), "digits/n-%d", ten * 10);
+ res = wait_file2(chan, vms, nextmsg);
+ if (one > 0) {
+ if (!res) {
+ snprintf(nextmsg, sizeof(nextmsg), "digits/n-%d", one);
+ res = wait_file2(chan, vms, nextmsg);
+ }
+ }
+ }
+ }
+ if (!res)
+ res = wait_file2(chan, vms, "vm-message");
+ } else {
+ if (!strcasecmp(chan->language, "se")) /* SWEDISH syntax */
+ res = wait_file2(chan, vms, "vm-meddelandet"); /* "message" */
+ else /* DEFAULT syntax */
+ res = wait_file2(chan, vms, "vm-message");
+ if (vms->curmsg && (vms->curmsg != vms->lastmsg)) {
+ if (!res)
+ res = ast_say_number(chan, vms->curmsg + 1, AST_DIGIT_ANY, chan->language, NULL);
+ }
+ }
+ }
+
+ /* Retrieve info from VM attribute file */
+ make_file(vms->fn2, sizeof(vms->fn2), vms->curdir, vms->curmsg);
+ snprintf(filename, sizeof(filename), "%s.txt", vms->fn2);
+ RETRIEVE(vms->curdir, vms->curmsg, vmu);
+ msg_cfg = ast_config_load(filename);
+ if (!msg_cfg) {
+ ast_log(LOG_WARNING, "No message attribute file?!! (%s)\n", filename);
+ return 0;
+ }
+
+ if (!(origtime = ast_variable_retrieve(msg_cfg, "message", "origtime"))) {
+ ast_log(LOG_WARNING, "No origtime?!\n");
+ DISPOSE(vms->curdir, vms->curmsg);
+ ast_config_destroy(msg_cfg);
+ return 0;
+ }
+
+ cid = ast_strdupa(ast_variable_retrieve(msg_cfg, "message", "callerid"));
+ duration = ast_variable_retrieve(msg_cfg, "message", "duration");
+ category = ast_variable_retrieve(msg_cfg, "message", "category");
+
+ context = ast_variable_retrieve(msg_cfg, "message", "context");
+ if (!strncasecmp("macro",context,5)) /* Macro names in contexts are useless for our needs */
+ context = ast_variable_retrieve(msg_cfg, "message","macrocontext");
+ if (!res)
+ res = play_message_category(chan, category);
+ if ((!res) && (ast_test_flag(vmu, VM_ENVELOPE)))
+ res = play_message_datetime(chan, vmu, origtime, filename);
+ if ((!res) && (ast_test_flag(vmu, VM_SAYCID)))
+ res = play_message_callerid(chan, vms, cid, context, 0);
+ if ((!res) && (ast_test_flag(vmu, VM_SAYDURATION)))
+ res = play_message_duration(chan, vms, duration, vmu->saydurationm);
+ /* Allow pressing '1' to skip envelope / callerid */
+ if (res == '1')
+ res = 0;
+ ast_config_destroy(msg_cfg);
+
+ if (!res) {
+ make_file(vms->fn, sizeof(vms->fn), vms->curdir, vms->curmsg);
+ vms->heard[vms->curmsg] = 1;
+ if ((res = wait_file(chan, vms, vms->fn)) < 0) {
+ ast_log(LOG_WARNING, "Playback of message %s failed\n", vms->fn);
+ res = 0;
+ }
+ }
+ DISPOSE(vms->curdir, vms->curmsg);
+ return res;
+}
+
+#ifndef IMAP_STORAGE
+static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu,int box)
+{
+ int res = 0;
+ int count_msg, last_msg;
+
+ ast_copy_string(vms->curbox, mbox(box), sizeof(vms->curbox));
+
+ /* Rename the member vmbox HERE so that we don't try to return before
+ * we know what's going on.
+ */
+ snprintf(vms->vmbox, sizeof(vms->vmbox), "vm-%s", vms->curbox);
+
+ /* Faster to make the directory than to check if it exists. */
+ create_dirpath(vms->curdir, sizeof(vms->curdir), vmu->context, vms->username, vms->curbox);
+
+ count_msg = count_messages(vmu, vms->curdir);
+ if (count_msg < 0)
+ return count_msg;
+ else
+ vms->lastmsg = count_msg - 1;
+
+ /*
+ The following test is needed in case sequencing gets messed up.
+ There appears to be more than one way to mess up sequence, so
+ we will not try to find all of the root causes--just fix it when
+ detected.
+ */
+
+ last_msg = last_message_index(vmu, vms->curdir);
+ if (last_msg < 0)
+ return last_msg;
+ else if (vms->lastmsg != last_msg)
+ {
+ ast_log(LOG_NOTICE, "Resequencing Mailbox: %s\n", vms->curdir);
+ res = resequence_mailbox(vmu, vms->curdir);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+#endif
+
+static int close_mailbox(struct vm_state *vms, struct ast_vm_user *vmu)
+{
+ int x = 0;
+#ifndef IMAP_STORAGE
+ int res = 0, nummsg;
+#endif
+
+ if (vms->lastmsg <= -1)
+ goto done;
+
+ vms->curmsg = -1;
+#ifndef IMAP_STORAGE
+ /* Get the deleted messages fixed */
+ if (vm_lock_path(vms->curdir))
+ return ERROR_LOCK_PATH;
+
+ for (x = 0; x < vmu->maxmsg; x++) {
+ if (!vms->deleted[x] && (strcasecmp(vms->curbox, "INBOX") || !vms->heard[x])) {
+ /* Save this message. It's not in INBOX or hasn't been heard */
+ make_file(vms->fn, sizeof(vms->fn), vms->curdir, x);
+ if (!EXISTS(vms->curdir, x, vms->fn, NULL))
+ break;
+ vms->curmsg++;
+ make_file(vms->fn2, sizeof(vms->fn2), vms->curdir, vms->curmsg);
+ if (strcmp(vms->fn, vms->fn2)) {
+ RENAME(vms->curdir, x, vmu->mailbox,vmu->context, vms->curdir, vms->curmsg, vms->fn, vms->fn2);
+ }
+ } else if (!strcasecmp(vms->curbox, "INBOX") && vms->heard[x] && !vms->deleted[x]) {
+ /* Move to old folder before deleting */
+ res = save_to_folder(vmu, vms, x, 1);
+ if (res == ERROR_LOCK_PATH || res == ERROR_MAILBOX_FULL) {
+ /* If save failed do not delete the message */
+ ast_log(LOG_WARNING, "Save failed. Not moving message: %s.\n", res == ERROR_LOCK_PATH ? "unable to lock path" : "destination folder full");
+ vms->deleted[x] = 0;
+ vms->heard[x] = 0;
+ --x;
+ }
+ }
+ }
+
+ /* Delete ALL remaining messages */
+ nummsg = x - 1;
+ for (x = vms->curmsg + 1; x <= nummsg; x++) {
+ make_file(vms->fn, sizeof(vms->fn), vms->curdir, x);
+ if (EXISTS(vms->curdir, x, vms->fn, NULL))
+ DELETE(vms->curdir, x, vms->fn, vmu);
+ }
+ ast_unlock_path(vms->curdir);
+#else
+ if (vms->deleted) {
+ for (x=0;x < vmu->maxmsg;x++) {
+ if (vms->deleted[x]) {
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG,"IMAP delete of %d\n",x);
+ DELETE(vms->curdir, x, vms->fn, vmu);
+ }
+ }
+ }
+#endif
+
+done:
+ if (vms->deleted)
+ memset(vms->deleted, 0, vmu->maxmsg * sizeof(int));
+ if (vms->heard)
+ memset(vms->heard, 0, vmu->maxmsg * sizeof(int));
+
+ return 0;
+}
+
+/* In Greek even though we CAN use a syntax like "friends messages"
+ * ("filika mynhmata") it is not elegant. This also goes for "work/family messages"
+ * ("ergasiaka/oikogeniaka mynhmata"). Therefore it is better to use a reversed
+ * syntax for the above three categories which is more elegant.
+ */
+
+static int vm_play_folder_name_gr(struct ast_channel *chan, char *mbox)
+{
+ int cmd;
+ char *buf;
+
+ buf = alloca(strlen(mbox)+2);
+ strcpy(buf, mbox);
+ strcat(buf,"s");
+
+ if (!strcasecmp(mbox, "vm-INBOX") || !strcasecmp(mbox, "vm-Old")){
+ cmd = ast_play_and_wait(chan, buf); /* "NEA / PALIA" */
+ return cmd ? cmd : ast_play_and_wait(chan, "vm-messages"); /* "messages" -> "MYNHMATA" */
+ } else {
+ cmd = ast_play_and_wait(chan, "vm-messages"); /* "messages" -> "MYNHMATA" */
+ return cmd ? cmd : ast_play_and_wait(chan, mbox); /* friends/family/work... -> "FILWN"/"OIKOGENIAS"/"DOULEIAS"*/
+ }
+}
+
+static int vm_play_folder_name_pl(struct ast_channel *chan, char *mbox)
+{
+ int cmd;
+
+ if (!strcasecmp(mbox, "vm-INBOX") || !strcasecmp(mbox, "vm-Old")) {
+ if (!strcasecmp(mbox, "vm-INBOX"))
+ cmd = ast_play_and_wait(chan, "vm-new-e");
+ else
+ cmd = ast_play_and_wait(chan, "vm-old-e");
+ return cmd ? cmd : ast_play_and_wait(chan, "vm-messages");
+ } else {
+ cmd = ast_play_and_wait(chan, "vm-messages");
+ return cmd ? cmd : ast_play_and_wait(chan, mbox);
+ }
+}
+
+static int vm_play_folder_name_ua(struct ast_channel *chan, char *mbox)
+{
+ int cmd;
+
+ if (!strcasecmp(mbox, "vm-Family") || !strcasecmp(mbox, "vm-Friends") || !strcasecmp(mbox, "vm-Work")){
+ cmd = ast_play_and_wait(chan, "vm-messages");
+ return cmd ? cmd : ast_play_and_wait(chan, mbox);
+ } else {
+ cmd = ast_play_and_wait(chan, mbox);
+ return cmd ? cmd : ast_play_and_wait(chan, "vm-messages");
+ }
+}
+
+static int vm_play_folder_name(struct ast_channel *chan, char *mbox)
+{
+ int cmd;
+
+ if (!strcasecmp(chan->language, "it") || !strcasecmp(chan->language, "es") || !strcasecmp(chan->language, "pt") || !strcasecmp(chan->language, "pt_BR")) { /* Italian, Spanish, French or Portuguese syntax */
+ cmd = ast_play_and_wait(chan, "vm-messages"); /* "messages */
+ return cmd ? cmd : ast_play_and_wait(chan, mbox);
+ } else if (!strcasecmp(chan->language, "gr")){
+ return vm_play_folder_name_gr(chan, mbox);
+ } else if (!strcasecmp(chan->language, "pl")){
+ return vm_play_folder_name_pl(chan, mbox);
+ } else if (!strcasecmp(chan->language, "ua")){ /* Ukrainian syntax */
+ return vm_play_folder_name_ua(chan, mbox);
+ } else if (!strcasecmp(chan->language, "he")){ /* Hebrew syntax */
+ cmd = ast_play_and_wait(chan, mbox);
+ return cmd;
+ } else { /* Default English */
+ cmd = ast_play_and_wait(chan, mbox);
+ return cmd ? cmd : ast_play_and_wait(chan, "vm-messages"); /* "messages */
+ }
+}
+
+/* GREEK SYNTAX
+ In greek the plural for old/new is
+ different so we need the following files
+ We also need vm-denExeteMynhmata because
+ this syntax is different.
+
+ -> vm-Olds.wav : "Palia"
+ -> vm-INBOXs.wav : "Nea"
+ -> vm-denExeteMynhmata : "den exete mynhmata"
+*/
+
+
+static int vm_intro_gr(struct ast_channel *chan, struct vm_state *vms)
+{
+ int res = 0;
+
+ if (vms->newmessages) {
+ res = ast_play_and_wait(chan, "vm-youhave");
+ if (!res)
+ res = ast_say_number(chan, vms->newmessages, AST_DIGIT_ANY, chan->language, NULL);
+ if (!res) {
+ if ((vms->newmessages == 1)) {
+ res = ast_play_and_wait(chan, "vm-INBOX");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-message");
+ } else {
+ res = ast_play_and_wait(chan, "vm-INBOXs");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-messages");
+ }
+ }
+ } else if (vms->oldmessages){
+ res = ast_play_and_wait(chan, "vm-youhave");
+ if (!res)
+ res = ast_say_number(chan, vms->oldmessages, AST_DIGIT_ANY, chan->language, NULL);
+ if ((vms->oldmessages == 1)){
+ res = ast_play_and_wait(chan, "vm-Old");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-message");
+ } else {
+ res = ast_play_and_wait(chan, "vm-Olds");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-messages");
+ }
+ } else if (!vms->oldmessages && !vms->newmessages)
+ res = ast_play_and_wait(chan, "vm-denExeteMynhmata");
+ return res;
+}
+
+/* Default English syntax */
+static int vm_intro_en(struct ast_channel *chan, struct vm_state *vms)
+{
+ int res;
+
+ /* Introduce messages they have */
+ res = ast_play_and_wait(chan, "vm-youhave");
+ if (!res) {
+ if (vms->newmessages) {
+ res = say_and_wait(chan, vms->newmessages, chan->language);
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-INBOX");
+ if (vms->oldmessages && !res)
+ res = ast_play_and_wait(chan, "vm-and");
+ else if (!res) {
+ if ((vms->newmessages == 1))
+ res = ast_play_and_wait(chan, "vm-message");
+ else
+ res = ast_play_and_wait(chan, "vm-messages");
+ }
+
+ }
+ if (!res && vms->oldmessages) {
+ res = say_and_wait(chan, vms->oldmessages, chan->language);
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-Old");
+ if (!res) {
+ if (vms->oldmessages == 1)
+ res = ast_play_and_wait(chan, "vm-message");
+ else
+ res = ast_play_and_wait(chan, "vm-messages");
+ }
+ }
+ if (!res) {
+ if (!vms->oldmessages && !vms->newmessages) {
+ res = ast_play_and_wait(chan, "vm-no");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-messages");
+ }
+ }
+ }
+ return res;
+}
+
+/* Default Hebrew syntax */
+static int vm_intro_he(struct ast_channel *chan, struct vm_state *vms)
+{
+ int res=0;
+
+ /* Introduce messages they have */
+ if (!res) {
+ if ((vms->newmessages) || (vms->oldmessages)) {
+ res = ast_play_and_wait(chan, "vm-youhave");
+ }
+ /*
+ * The word "shtei" refers to the number 2 in hebrew when performing a count
+ * of elements. In Hebrew, there are 6 forms of enumerating the number 2 for
+ * an element, this is one of them.
+ */
+ if (vms->newmessages) {
+ if (!res) {
+ if (vms->newmessages == 1) {
+ res = ast_play_and_wait(chan, "vm-INBOX1");
+ } else {
+ if (vms->newmessages == 2) {
+ res = ast_play_and_wait(chan, "vm-shtei");
+ } else {
+ res = ast_say_number(chan, vms->newmessages, AST_DIGIT_ANY, chan->language, "f");
+ }
+ res = ast_play_and_wait(chan, "vm-INBOX");
+ }
+ }
+ if (vms->oldmessages && !res) {
+ res = ast_play_and_wait(chan, "vm-and");
+ if (vms->oldmessages == 1) {
+ res = ast_play_and_wait(chan, "vm-Old1");
+ } else {
+ if (vms->oldmessages == 2) {
+ res = ast_play_and_wait(chan, "vm-shtei");
+ } else {
+ res = ast_say_number(chan, vms->oldmessages, AST_DIGIT_ANY, chan->language, "f");
+ }
+ res = ast_play_and_wait(chan, "vm-Old");
+ }
+ }
+ }
+ if (!res && vms->oldmessages && !vms->newmessages) {
+ if (!res) {
+ if (vms->oldmessages == 1) {
+ res = ast_play_and_wait(chan, "vm-Old1");
+ } else {
+ if (vms->oldmessages == 2) {
+ res = ast_play_and_wait(chan, "vm-shtei");
+ } else {
+ res = ast_say_number(chan, vms->oldmessages, AST_DIGIT_ANY, chan->language, "f");
+ }
+ res = ast_play_and_wait(chan, "vm-Old");
+ }
+ }
+ }
+ if (!res) {
+ if (!vms->oldmessages && !vms->newmessages) {
+ if (!res) {
+ res = ast_play_and_wait(chan, "vm-nomessages");
+ }
+ }
+ }
+ }
+ return res;
+}
+
+
+/* ITALIAN syntax */
+static int vm_intro_it(struct ast_channel *chan, struct vm_state *vms)
+{
+ /* Introduce messages they have */
+ int res;
+ if (!vms->oldmessages && !vms->newmessages)
+ res = ast_play_and_wait(chan, "vm-no") ||
+ ast_play_and_wait(chan, "vm-message");
+ else
+ res = ast_play_and_wait(chan, "vm-youhave");
+ if (!res && vms->newmessages) {
+ res = (vms->newmessages == 1) ?
+ ast_play_and_wait(chan, "digits/un") ||
+ ast_play_and_wait(chan, "vm-nuovo") ||
+ ast_play_and_wait(chan, "vm-message") :
+ /* 2 or more new messages */
+ say_and_wait(chan, vms->newmessages, chan->language) ||
+ ast_play_and_wait(chan, "vm-nuovi") ||
+ ast_play_and_wait(chan, "vm-messages");
+ if (!res && vms->oldmessages)
+ res = ast_play_and_wait(chan, "vm-and");
+ }
+ if (!res && vms->oldmessages) {
+ res = (vms->oldmessages == 1) ?
+ ast_play_and_wait(chan, "digits/un") ||
+ ast_play_and_wait(chan, "vm-vecchio") ||
+ ast_play_and_wait(chan, "vm-message") :
+ /* 2 or more old messages */
+ say_and_wait(chan, vms->oldmessages, chan->language) ||
+ ast_play_and_wait(chan, "vm-vecchi") ||
+ ast_play_and_wait(chan, "vm-messages");
+ }
+ return res;
+}
+
+/* POLISH syntax */
+static int vm_intro_pl(struct ast_channel *chan, struct vm_state *vms)
+{
+ /* Introduce messages they have */
+ int res;
+ div_t num;
+
+ if (!vms->oldmessages && !vms->newmessages) {
+ res = ast_play_and_wait(chan, "vm-no");
+ res = res ? res : ast_play_and_wait(chan, "vm-messages");
+ return res;
+ } else {
+ res = ast_play_and_wait(chan, "vm-youhave");
+ }
+
+ if (vms->newmessages) {
+ num = div(vms->newmessages, 10);
+ if (vms->newmessages == 1) {
+ res = ast_play_and_wait(chan, "digits/1-a");
+ res = res ? res : ast_play_and_wait(chan, "vm-new-a");
+ res = res ? res : ast_play_and_wait(chan, "vm-message");
+ } else if (num.rem > 1 && num.rem < 5 && num.quot != 1) {
+ if (num.rem == 2) {
+ if (!num.quot) {
+ res = ast_play_and_wait(chan, "digits/2-ie");
+ } else {
+ res = say_and_wait(chan, vms->newmessages - 2 , chan->language);
+ res = res ? res : ast_play_and_wait(chan, "digits/2-ie");
+ }
+ } else {
+ res = say_and_wait(chan, vms->newmessages, chan->language);
+ }
+ res = res ? res : ast_play_and_wait(chan, "vm-new-e");
+ res = res ? res : ast_play_and_wait(chan, "vm-messages");
+ } else {
+ res = say_and_wait(chan, vms->newmessages, chan->language);
+ res = res ? res : ast_play_and_wait(chan, "vm-new-ych");
+ res = res ? res : ast_play_and_wait(chan, "vm-messages");
+ }
+ if (!res && vms->oldmessages)
+ res = ast_play_and_wait(chan, "vm-and");
+ }
+ if (!res && vms->oldmessages) {
+ num = div(vms->oldmessages, 10);
+ if (vms->oldmessages == 1) {
+ res = ast_play_and_wait(chan, "digits/1-a");
+ res = res ? res : ast_play_and_wait(chan, "vm-old-a");
+ res = res ? res : ast_play_and_wait(chan, "vm-message");
+ } else if (num.rem > 1 && num.rem < 5 && num.quot != 1) {
+ if (num.rem == 2) {
+ if (!num.quot) {
+ res = ast_play_and_wait(chan, "digits/2-ie");
+ } else {
+ res = say_and_wait(chan, vms->oldmessages - 2 , chan->language);
+ res = res ? res : ast_play_and_wait(chan, "digits/2-ie");
+ }
+ } else {
+ res = say_and_wait(chan, vms->oldmessages, chan->language);
+ }
+ res = res ? res : ast_play_and_wait(chan, "vm-old-e");
+ res = res ? res : ast_play_and_wait(chan, "vm-messages");
+ } else {
+ res = say_and_wait(chan, vms->oldmessages, chan->language);
+ res = res ? res : ast_play_and_wait(chan, "vm-old-ych");
+ res = res ? res : ast_play_and_wait(chan, "vm-messages");
+ }
+ }
+
+ return res;
+}
+
+/* SWEDISH syntax */
+static int vm_intro_se(struct ast_channel *chan, struct vm_state *vms)
+{
+ /* Introduce messages they have */
+ int res;
+
+ res = ast_play_and_wait(chan, "vm-youhave");
+ if (res)
+ return res;
+
+ if (!vms->oldmessages && !vms->newmessages) {
+ res = ast_play_and_wait(chan, "vm-no");
+ res = res ? res : ast_play_and_wait(chan, "vm-messages");
+ return res;
+ }
+
+ if (vms->newmessages) {
+ if ((vms->newmessages == 1)) {
+ res = ast_play_and_wait(chan, "digits/ett");
+ res = res ? res : ast_play_and_wait(chan, "vm-nytt");
+ res = res ? res : ast_play_and_wait(chan, "vm-message");
+ } else {
+ res = say_and_wait(chan, vms->newmessages, chan->language);
+ res = res ? res : ast_play_and_wait(chan, "vm-nya");
+ res = res ? res : ast_play_and_wait(chan, "vm-messages");
+ }
+ if (!res && vms->oldmessages)
+ res = ast_play_and_wait(chan, "vm-and");
+ }
+ if (!res && vms->oldmessages) {
+ if (vms->oldmessages == 1) {
+ res = ast_play_and_wait(chan, "digits/ett");
+ res = res ? res : ast_play_and_wait(chan, "vm-gammalt");
+ res = res ? res : ast_play_and_wait(chan, "vm-message");
+ } else {
+ res = say_and_wait(chan, vms->oldmessages, chan->language);
+ res = res ? res : ast_play_and_wait(chan, "vm-gamla");
+ res = res ? res : ast_play_and_wait(chan, "vm-messages");
+ }
+ }
+
+ return res;
+}
+
+/* NORWEGIAN syntax */
+static int vm_intro_no(struct ast_channel *chan,struct vm_state *vms)
+{
+ /* Introduce messages they have */
+ int res;
+
+ res = ast_play_and_wait(chan, "vm-youhave");
+ if (res)
+ return res;
+
+ if (!vms->oldmessages && !vms->newmessages) {
+ res = ast_play_and_wait(chan, "vm-no");
+ res = res ? res : ast_play_and_wait(chan, "vm-messages");
+ return res;
+ }
+
+ if (vms->newmessages) {
+ if ((vms->newmessages == 1)) {
+ res = ast_play_and_wait(chan, "digits/1");
+ res = res ? res : ast_play_and_wait(chan, "vm-ny");
+ res = res ? res : ast_play_and_wait(chan, "vm-message");
+ } else {
+ res = say_and_wait(chan, vms->newmessages, chan->language);
+ res = res ? res : ast_play_and_wait(chan, "vm-nye");
+ res = res ? res : ast_play_and_wait(chan, "vm-messages");
+ }
+ if (!res && vms->oldmessages)
+ res = ast_play_and_wait(chan, "vm-and");
+ }
+ if (!res && vms->oldmessages) {
+ if (vms->oldmessages == 1) {
+ res = ast_play_and_wait(chan, "digits/1");
+ res = res ? res : ast_play_and_wait(chan, "vm-gamel");
+ res = res ? res : ast_play_and_wait(chan, "vm-message");
+ } else {
+ res = say_and_wait(chan, vms->oldmessages, chan->language);
+ res = res ? res : ast_play_and_wait(chan, "vm-gamle");
+ res = res ? res : ast_play_and_wait(chan, "vm-messages");
+ }
+ }
+
+ return res;
+}
+
+/* GERMAN syntax */
+static int vm_intro_de(struct ast_channel *chan,struct vm_state *vms)
+{
+ /* Introduce messages they have */
+ int res;
+ res = ast_play_and_wait(chan, "vm-youhave");
+ if (!res) {
+ if (vms->newmessages) {
+ if ((vms->newmessages == 1))
+ res = ast_play_and_wait(chan, "digits/1F");
+ else
+ res = say_and_wait(chan, vms->newmessages, chan->language);
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-INBOX");
+ if (vms->oldmessages && !res)
+ res = ast_play_and_wait(chan, "vm-and");
+ else if (!res) {
+ if ((vms->newmessages == 1))
+ res = ast_play_and_wait(chan, "vm-message");
+ else
+ res = ast_play_and_wait(chan, "vm-messages");
+ }
+
+ }
+ if (!res && vms->oldmessages) {
+ if (vms->oldmessages == 1)
+ res = ast_play_and_wait(chan, "digits/1F");
+ else
+ res = say_and_wait(chan, vms->oldmessages, chan->language);
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-Old");
+ if (!res) {
+ if (vms->oldmessages == 1)
+ res = ast_play_and_wait(chan, "vm-message");
+ else
+ res = ast_play_and_wait(chan, "vm-messages");
+ }
+ }
+ if (!res) {
+ if (!vms->oldmessages && !vms->newmessages) {
+ res = ast_play_and_wait(chan, "vm-no");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-messages");
+ }
+ }
+ }
+ return res;
+}
+
+/* SPANISH syntax */
+static int vm_intro_es(struct ast_channel *chan,struct vm_state *vms)
+{
+ /* Introduce messages they have */
+ int res;
+ if (!vms->oldmessages && !vms->newmessages) {
+ res = ast_play_and_wait(chan, "vm-youhaveno");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-messages");
+ } else {
+ res = ast_play_and_wait(chan, "vm-youhave");
+ }
+ if (!res) {
+ if (vms->newmessages) {
+ if (!res) {
+ if ((vms->newmessages == 1)) {
+ res = ast_play_and_wait(chan, "digits/1M");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-message");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-INBOXs");
+ } else {
+ res = say_and_wait(chan, vms->newmessages, chan->language);
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-messages");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-INBOX");
+ }
+ }
+ if (vms->oldmessages && !res)
+ res = ast_play_and_wait(chan, "vm-and");
+ }
+ if (vms->oldmessages) {
+ if (!res) {
+ if (vms->oldmessages == 1) {
+ res = ast_play_and_wait(chan, "digits/1M");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-message");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-Olds");
+ } else {
+ res = say_and_wait(chan, vms->oldmessages, chan->language);
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-messages");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-Old");
+ }
+ }
+ }
+ }
+return res;
+}
+
+/* BRAZILIAN PORTUGUESE syntax */
+static int vm_intro_pt_BR(struct ast_channel *chan,struct vm_state *vms) {
+ /* Introduce messages they have */
+ int res;
+ if (!vms->oldmessages && !vms->newmessages) {
+ res = ast_play_and_wait(chan, "vm-nomessages");
+ return res;
+ }
+ else {
+ res = ast_play_and_wait(chan, "vm-youhave");
+ }
+ if (vms->newmessages) {
+ if (!res)
+ res = ast_say_number(chan, vms->newmessages, AST_DIGIT_ANY, chan->language, "f");
+ if ((vms->newmessages == 1)) {
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-message");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-INBOXs");
+ }
+ else {
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-messages");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-INBOX");
+ }
+ if (vms->oldmessages && !res)
+ res = ast_play_and_wait(chan, "vm-and");
+ }
+ if (vms->oldmessages) {
+ if (!res)
+ res = ast_say_number(chan, vms->oldmessages, AST_DIGIT_ANY, chan->language, "f");
+ if (vms->oldmessages == 1) {
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-message");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-Olds");
+ }
+ else {
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-messages");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-Old");
+ }
+ }
+ return res;
+}
+
+/* FRENCH syntax */
+static int vm_intro_fr(struct ast_channel *chan,struct vm_state *vms)
+{
+ /* Introduce messages they have */
+ int res;
+ res = ast_play_and_wait(chan, "vm-youhave");
+ if (!res) {
+ if (vms->newmessages) {
+ res = say_and_wait(chan, vms->newmessages, chan->language);
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-INBOX");
+ if (vms->oldmessages && !res)
+ res = ast_play_and_wait(chan, "vm-and");
+ else if (!res) {
+ if ((vms->newmessages == 1))
+ res = ast_play_and_wait(chan, "vm-message");
+ else
+ res = ast_play_and_wait(chan, "vm-messages");
+ }
+
+ }
+ if (!res && vms->oldmessages) {
+ res = say_and_wait(chan, vms->oldmessages, chan->language);
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-Old");
+ if (!res) {
+ if (vms->oldmessages == 1)
+ res = ast_play_and_wait(chan, "vm-message");
+ else
+ res = ast_play_and_wait(chan, "vm-messages");
+ }
+ }
+ if (!res) {
+ if (!vms->oldmessages && !vms->newmessages) {
+ res = ast_play_and_wait(chan, "vm-no");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-messages");
+ }
+ }
+ }
+ return res;
+}
+
+/* DUTCH syntax */
+static int vm_intro_nl(struct ast_channel *chan,struct vm_state *vms)
+{
+ /* Introduce messages they have */
+ int res;
+ res = ast_play_and_wait(chan, "vm-youhave");
+ if (!res) {
+ if (vms->newmessages) {
+ res = say_and_wait(chan, vms->newmessages, chan->language);
+ if (!res) {
+ if (vms->newmessages == 1)
+ res = ast_play_and_wait(chan, "vm-INBOXs");
+ else
+ res = ast_play_and_wait(chan, "vm-INBOX");
+ }
+ if (vms->oldmessages && !res)
+ res = ast_play_and_wait(chan, "vm-and");
+ else if (!res) {
+ if ((vms->newmessages == 1))
+ res = ast_play_and_wait(chan, "vm-message");
+ else
+ res = ast_play_and_wait(chan, "vm-messages");
+ }
+
+ }
+ if (!res && vms->oldmessages) {
+ res = say_and_wait(chan, vms->oldmessages, chan->language);
+ if (!res) {
+ if (vms->oldmessages == 1)
+ res = ast_play_and_wait(chan, "vm-Olds");
+ else
+ res = ast_play_and_wait(chan, "vm-Old");
+ }
+ if (!res) {
+ if (vms->oldmessages == 1)
+ res = ast_play_and_wait(chan, "vm-message");
+ else
+ res = ast_play_and_wait(chan, "vm-messages");
+ }
+ }
+ if (!res) {
+ if (!vms->oldmessages && !vms->newmessages) {
+ res = ast_play_and_wait(chan, "vm-no");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-messages");
+ }
+ }
+ }
+ return res;
+}
+
+/* PORTUGUESE syntax */
+static int vm_intro_pt(struct ast_channel *chan,struct vm_state *vms)
+{
+ /* Introduce messages they have */
+ int res;
+ res = ast_play_and_wait(chan, "vm-youhave");
+ if (!res) {
+ if (vms->newmessages) {
+ res = ast_say_number(chan, vms->newmessages, AST_DIGIT_ANY, chan->language, "f");
+ if (!res) {
+ if ((vms->newmessages == 1)) {
+ res = ast_play_and_wait(chan, "vm-message");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-INBOXs");
+ } else {
+ res = ast_play_and_wait(chan, "vm-messages");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-INBOX");
+ }
+ }
+ if (vms->oldmessages && !res)
+ res = ast_play_and_wait(chan, "vm-and");
+ }
+ if (!res && vms->oldmessages) {
+ res = ast_say_number(chan, vms->oldmessages, AST_DIGIT_ANY, chan->language, "f");
+ if (!res) {
+ if (vms->oldmessages == 1) {
+ res = ast_play_and_wait(chan, "vm-message");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-Olds");
+ } else {
+ res = ast_play_and_wait(chan, "vm-messages");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-Old");
+ }
+ }
+ }
+ if (!res) {
+ if (!vms->oldmessages && !vms->newmessages) {
+ res = ast_play_and_wait(chan, "vm-no");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-messages");
+ }
+ }
+ }
+ return res;
+}
+
+
+/* CZECH syntax */
+/* in czech there must be declension of word new and message
+ * czech : english : czech : english
+ * --------------------------------------------------------
+ * vm-youhave : you have
+ * vm-novou : one new : vm-zpravu : message
+ * vm-nove : 2-4 new : vm-zpravy : messages
+ * vm-novych : 5-infinite new : vm-zprav : messages
+ * vm-starou : one old
+ * vm-stare : 2-4 old
+ * vm-starych : 5-infinite old
+ * jednu : one - falling 4.
+ * vm-no : no ( no messages )
+ */
+
+static int vm_intro_cz(struct ast_channel *chan,struct vm_state *vms)
+{
+ int res;
+ res = ast_play_and_wait(chan, "vm-youhave");
+ if (!res) {
+ if (vms->newmessages) {
+ if (vms->newmessages == 1) {
+ res = ast_play_and_wait(chan, "digits/jednu");
+ } else {
+ res = say_and_wait(chan, vms->newmessages, chan->language);
+ }
+ if (!res) {
+ if ((vms->newmessages == 1))
+ res = ast_play_and_wait(chan, "vm-novou");
+ if ((vms->newmessages) > 1 && (vms->newmessages < 5))
+ res = ast_play_and_wait(chan, "vm-nove");
+ if (vms->newmessages > 4)
+ res = ast_play_and_wait(chan, "vm-novych");
+ }
+ if (vms->oldmessages && !res)
+ res = ast_play_and_wait(chan, "vm-and");
+ else if (!res) {
+ if ((vms->newmessages == 1))
+ res = ast_play_and_wait(chan, "vm-zpravu");
+ if ((vms->newmessages) > 1 && (vms->newmessages < 5))
+ res = ast_play_and_wait(chan, "vm-zpravy");
+ if (vms->newmessages > 4)
+ res = ast_play_and_wait(chan, "vm-zprav");
+ }
+ }
+ if (!res && vms->oldmessages) {
+ res = say_and_wait(chan, vms->oldmessages, chan->language);
+ if (!res) {
+ if ((vms->oldmessages == 1))
+ res = ast_play_and_wait(chan, "vm-starou");
+ if ((vms->oldmessages) > 1 && (vms->oldmessages < 5))
+ res = ast_play_and_wait(chan, "vm-stare");
+ if (vms->oldmessages > 4)
+ res = ast_play_and_wait(chan, "vm-starych");
+ }
+ if (!res) {
+ if ((vms->oldmessages == 1))
+ res = ast_play_and_wait(chan, "vm-zpravu");
+ if ((vms->oldmessages) > 1 && (vms->oldmessages < 5))
+ res = ast_play_and_wait(chan, "vm-zpravy");
+ if (vms->oldmessages > 4)
+ res = ast_play_and_wait(chan, "vm-zprav");
+ }
+ }
+ if (!res) {
+ if (!vms->oldmessages && !vms->newmessages) {
+ res = ast_play_and_wait(chan, "vm-no");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-zpravy");
+ }
+ }
+ }
+ return res;
+}
+
+static int get_lastdigits(int num)
+{
+ num %= 100;
+ return (num < 20) ? num : num % 10;
+}
+
+static int vm_intro_ru(struct ast_channel *chan,struct vm_state *vms)
+{
+ int res;
+ int lastnum = 0;
+ int dcnum;
+
+ res = ast_play_and_wait(chan, "vm-youhave");
+ if (!res && vms->newmessages) {
+ lastnum = get_lastdigits(vms->newmessages);
+ dcnum = vms->newmessages - lastnum;
+ if (dcnum)
+ res = say_and_wait(chan, dcnum, chan->language);
+ if (!res && lastnum) {
+ if (lastnum == 1)
+ res = ast_play_and_wait(chan, "digits/odno");
+ else
+ res = say_and_wait(chan, lastnum, chan->language);
+ }
+
+ if (!res)
+ res = ast_play_and_wait(chan, (lastnum == 1) ? "vm-novoe" : "vm-novyh");
+
+ if (!res && vms->oldmessages)
+ res = ast_play_and_wait(chan, "vm-and");
+ }
+
+ if (!res && vms->oldmessages) {
+ lastnum = get_lastdigits(vms->oldmessages);
+ dcnum = vms->oldmessages - lastnum;
+ if (dcnum)
+ res = say_and_wait(chan, dcnum, chan->language);
+ if (!res && lastnum) {
+ if (lastnum == 1)
+ res = ast_play_and_wait(chan, "digits/odno");
+ else
+ res = say_and_wait(chan, lastnum, chan->language);
+ }
+
+ if (!res)
+ res = ast_play_and_wait(chan, (lastnum == 1) ? "vm-staroe" : "vm-staryh");
+ }
+
+ if (!res && !vms->newmessages && !vms->oldmessages) {
+ lastnum = 0;
+ res = ast_play_and_wait(chan, "vm-no");
+ }
+
+ if (!res) {
+ switch (lastnum) {
+ case 1:
+ res = ast_play_and_wait(chan, "vm-soobshenie");
+ break;
+ case 2:
+ case 3:
+ case 4:
+ res = ast_play_and_wait(chan, "vm-soobsheniya");
+ break;
+ default:
+ res = ast_play_and_wait(chan, "vm-soobsheniy");
+ break;
+ }
+ }
+
+ return res;
+}
+
+/* UKRAINIAN syntax */
+/* in ukrainian the syntax is different so we need the following files
+ * --------------------------------------------------------
+ * /digits/ua/1e 'odne'
+ * vm-nove 'nove'
+ * vm-stare 'stare'
+ */
+
+static int vm_intro_ua(struct ast_channel *chan,struct vm_state *vms)
+{
+ int res;
+ int lastnum = 0;
+ int dcnum;
+
+ res = ast_play_and_wait(chan, "vm-youhave");
+ if (!res && vms->newmessages) {
+ lastnum = get_lastdigits(vms->newmessages);
+ dcnum = vms->newmessages - lastnum;
+ if (dcnum)
+ res = say_and_wait(chan, dcnum, chan->language);
+ if (!res && lastnum) {
+ if (lastnum == 1)
+ res = ast_play_and_wait(chan, "digits/ua/1e");
+ else
+ res = say_and_wait(chan, lastnum, chan->language);
+ }
+
+ if (!res)
+ res = ast_play_and_wait(chan, (lastnum == 1) ? "vm-nove" : "vm-INBOX");
+
+ if (!res && vms->oldmessages)
+ res = ast_play_and_wait(chan, "vm-and");
+ }
+
+ if (!res && vms->oldmessages) {
+ lastnum = get_lastdigits(vms->oldmessages);
+ dcnum = vms->oldmessages - lastnum;
+ if (dcnum)
+ res = say_and_wait(chan, dcnum, chan->language);
+ if (!res && lastnum) {
+ if (lastnum == 1)
+ res = ast_play_and_wait(chan, "digits/ua/1e");
+ else
+ res = say_and_wait(chan, lastnum, chan->language);
+ }
+
+ if (!res)
+ res = ast_play_and_wait(chan, (lastnum == 1) ? "vm-stare" : "vm-Old");
+ }
+
+ if (!res && !vms->newmessages && !vms->oldmessages) {
+ lastnum = 0;
+ res = ast_play_and_wait(chan, "vm-no");
+ }
+
+ if (!res) {
+ switch (lastnum) {
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ res = ast_play_and_wait(chan, "vm-message");
+ break;
+ default:
+ res = ast_play_and_wait(chan, "vm-messages");
+ break;
+ }
+ }
+
+ return res;
+}
+
+
+static int vm_intro(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms)
+{
+ char prefile[256];
+
+ /* Notify the user that the temp greeting is set and give them the option to remove it */
+ snprintf(prefile, sizeof(prefile), "%s%s/%s/temp", VM_SPOOL_DIR, vmu->context, vms->username);
+ if (ast_test_flag(vmu, VM_TEMPGREETWARN)) {
+ RETRIEVE(prefile, -1, vmu);
+ if (ast_fileexists(prefile, NULL, NULL) > 0)
+ ast_play_and_wait(chan, "vm-tempgreetactive");
+ DISPOSE(prefile, -1);
+ }
+
+ /* Play voicemail intro - syntax is different for different languages */
+ if (!strcasecmp(chan->language, "de")) { /* GERMAN syntax */
+ return vm_intro_de(chan, vms);
+ } else if (!strcasecmp(chan->language, "es")) { /* SPANISH syntax */
+ return vm_intro_es(chan, vms);
+ } else if (!strcasecmp(chan->language, "it")) { /* ITALIAN syntax */
+ return vm_intro_it(chan, vms);
+ } else if (!strcasecmp(chan->language, "fr")) { /* FRENCH syntax */
+ return vm_intro_fr(chan, vms);
+ } else if (!strcasecmp(chan->language, "nl")) { /* DUTCH syntax */
+ return vm_intro_nl(chan, vms);
+ } else if (!strcasecmp(chan->language, "pt")) { /* PORTUGUESE syntax */
+ return vm_intro_pt(chan, vms);
+ } else if (!strcasecmp(chan->language, "pt_BR")) { /* BRAZILIAN PORTUGUESE syntax */
+ return vm_intro_pt_BR(chan, vms);
+ } else if (!strcasecmp(chan->language, "cz")) { /* CZECH syntax */
+ return vm_intro_cz(chan, vms);
+ } else if (!strcasecmp(chan->language, "gr")) { /* GREEK syntax */
+ return vm_intro_gr(chan, vms);
+ } else if (!strcasecmp(chan->language, "pl")) { /* POLISH syntax */
+ return vm_intro_pl(chan, vms);
+ } else if (!strcasecmp(chan->language, "se")) { /* SWEDISH syntax */
+ return vm_intro_se(chan, vms);
+ } else if (!strcasecmp(chan->language, "no")) { /* NORWEGIAN syntax */
+ return vm_intro_no(chan, vms);
+ } else if (!strcasecmp(chan->language, "ru")) { /* RUSSIAN syntax */
+ return vm_intro_ru(chan, vms);
+ } else if (!strcasecmp(chan->language, "ua")) { /* UKRAINIAN syntax */
+ return vm_intro_ua(chan, vms);
+ } else if (!strcasecmp(chan->language, "he")) { /* HEBREW syntax */
+ return vm_intro_he(chan, vms);
+ } else { /* Default to ENGLISH */
+ return vm_intro_en(chan, vms);
+ }
+}
+
+static int vm_instructions(struct ast_channel *chan, struct vm_state *vms, int skipadvanced)
+{
+ int res = 0;
+ /* Play instructions and wait for new command */
+ while (!res) {
+ if (vms->starting) {
+ if (vms->lastmsg > -1) {
+ res = ast_play_and_wait(chan, "vm-onefor");
+ if (!strcasecmp(chan->language, "he"))
+ res = ast_play_and_wait(chan, "vm-for");
+ if (!res)
+ res = vm_play_folder_name(chan, vms->vmbox);
+ }
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-opts");
+ } else {
+ if (vms->curmsg)
+ res = ast_play_and_wait(chan, "vm-prev");
+ if (!res && !skipadvanced)
+ res = ast_play_and_wait(chan, "vm-advopts");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-repeat");
+ if (!res && (vms->curmsg != vms->lastmsg))
+ res = ast_play_and_wait(chan, "vm-next");
+ if (!res) {
+ if (!vms->deleted[vms->curmsg])
+ res = ast_play_and_wait(chan, "vm-delete");
+ else
+ res = ast_play_and_wait(chan, "vm-undelete");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-toforward");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-savemessage");
+ }
+ }
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-helpexit");
+ if (!res)
+ res = ast_waitfordigit(chan, 6000);
+ if (!res) {
+ vms->repeats++;
+ if (vms->repeats > 2) {
+ res = 't';
+ }
+ }
+ }
+ return res;
+}
+
+static int vm_newuser(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain)
+{
+ int cmd = 0;
+ int duration = 0;
+ int tries = 0;
+ char newpassword[80] = "";
+ char newpassword2[80] = "";
+ char prefile[PATH_MAX] = "";
+ unsigned char buf[256];
+ int bytes=0;
+
+ if (ast_adsi_available(chan)) {
+ bytes += adsi_logo(buf + bytes);
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_CENT, 0, "New User Setup", "");
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_CENT, 0, "Not Done", "");
+ bytes += ast_adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
+ bytes += ast_adsi_voice_mode(buf + bytes, 0);
+ ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
+ }
+
+ /* First, have the user change their password
+ so they won't get here again */
+ for (;;) {
+ newpassword[1] = '\0';
+ newpassword[0] = cmd = ast_play_and_wait(chan,"vm-newpassword");
+ if (cmd == '#')
+ newpassword[0] = '\0';
+ if (cmd < 0 || cmd == 't' || cmd == '#')
+ return cmd;
+ cmd = ast_readstring(chan,newpassword + strlen(newpassword),sizeof(newpassword)-1,2000,10000,"#");
+ if (cmd < 0 || cmd == 't' || cmd == '#')
+ return cmd;
+ newpassword2[1] = '\0';
+ newpassword2[0] = cmd = ast_play_and_wait(chan,"vm-reenterpassword");
+ if (cmd == '#')
+ newpassword2[0] = '\0';
+ if (cmd < 0 || cmd == 't' || cmd == '#')
+ return cmd;
+ cmd = ast_readstring(chan,newpassword2 + strlen(newpassword2),sizeof(newpassword2)-1,2000,10000,"#");
+ if (cmd < 0 || cmd == 't' || cmd == '#')
+ return cmd;
+ if (!strcmp(newpassword, newpassword2))
+ break;
+ ast_log(LOG_NOTICE,"Password mismatch for user %s (%s != %s)\n", vms->username, newpassword, newpassword2);
+ cmd = ast_play_and_wait(chan, "vm-mismatch");
+ if (++tries == 3)
+ return -1;
+ }
+ if (ast_strlen_zero(ext_pass_cmd))
+ vm_change_password(vmu,newpassword);
+ else
+ vm_change_password_shell(vmu,newpassword);
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG,"User %s set password to %s of length %d\n",vms->username,newpassword,(int)strlen(newpassword));
+ cmd = ast_play_and_wait(chan,"vm-passchanged");
+
+ /* If forcename is set, have the user record their name */
+ if (ast_test_flag(vmu, VM_FORCENAME)) {
+ snprintf(prefile,sizeof(prefile), "%s%s/%s/greet", VM_SPOOL_DIR, vmu->context, vms->username);
+ if (ast_fileexists(prefile, NULL, NULL) < 1) {
+ cmd = play_record_review(chan, "vm-rec-name", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, record_gain, vms);
+ if (cmd < 0 || cmd == 't' || cmd == '#')
+ return cmd;
+ }
+ }
+
+ /* If forcegreetings is set, have the user record their greetings */
+ if (ast_test_flag(vmu, VM_FORCEGREET)) {
+ snprintf(prefile,sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, vms->username);
+ if (ast_fileexists(prefile, NULL, NULL) < 1) {
+ cmd = play_record_review(chan, "vm-rec-unv", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, record_gain, vms);
+ if (cmd < 0 || cmd == 't' || cmd == '#')
+ return cmd;
+ }
+
+ snprintf(prefile,sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, vms->username);
+ if (ast_fileexists(prefile, NULL, NULL) < 1) {
+ cmd = play_record_review(chan, "vm-rec-busy", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, record_gain, vms);
+ if (cmd < 0 || cmd == 't' || cmd == '#')
+ return cmd;
+ }
+ }
+
+ return cmd;
+}
+
+static int vm_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain)
+{
+ int cmd = 0;
+ int retries = 0;
+ int duration = 0;
+ char newpassword[80] = "";
+ char newpassword2[80] = "";
+ char prefile[PATH_MAX] = "";
+ unsigned char buf[256];
+ int bytes=0;
+
+ if (ast_adsi_available(chan))
+ {
+ bytes += adsi_logo(buf + bytes);
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_CENT, 0, "Options Menu", "");
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_CENT, 0, "Not Done", "");
+ bytes += ast_adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
+ bytes += ast_adsi_voice_mode(buf + bytes, 0);
+ ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
+ }
+ while ((cmd >= 0) && (cmd != 't')) {
+ if (cmd)
+ retries = 0;
+ switch (cmd) {
+ case '1':
+ snprintf(prefile,sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, vms->username);
+ cmd = play_record_review(chan,"vm-rec-unv",prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, record_gain, vms);
+ break;
+ case '2':
+ snprintf(prefile,sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, vms->username);
+ cmd = play_record_review(chan,"vm-rec-busy",prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, record_gain, vms);
+ break;
+ case '3':
+ snprintf(prefile,sizeof(prefile), "%s%s/%s/greet", VM_SPOOL_DIR, vmu->context, vms->username);
+ cmd = play_record_review(chan,"vm-rec-name",prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, record_gain, vms);
+ break;
+ case '4':
+ cmd = vm_tempgreeting(chan, vmu, vms, fmtc, record_gain);
+ break;
+ case '5':
+ if (vmu->password[0] == '-') {
+ cmd = ast_play_and_wait(chan, "vm-no");
+ break;
+ }
+ newpassword[1] = '\0';
+ newpassword[0] = cmd = ast_play_and_wait(chan,"vm-newpassword");
+ if (cmd == '#')
+ newpassword[0] = '\0';
+ else {
+ if (cmd < 0)
+ break;
+ if ((cmd = ast_readstring(chan,newpassword + strlen(newpassword),sizeof(newpassword)-1,2000,10000,"#")) < 0) {
+ break;
+ }
+ }
+ newpassword2[1] = '\0';
+ newpassword2[0] = cmd = ast_play_and_wait(chan,"vm-reenterpassword");
+ if (cmd == '#')
+ newpassword2[0] = '\0';
+ else {
+ if (cmd < 0)
+ break;
+
+ if ((cmd = ast_readstring(chan,newpassword2 + strlen(newpassword2),sizeof(newpassword2)-1,2000,10000,"#")) < 0) {
+ break;
+ }
+ }
+ if (strcmp(newpassword, newpassword2)) {
+ ast_log(LOG_NOTICE,"Password mismatch for user %s (%s != %s)\n", vms->username, newpassword, newpassword2);
+ cmd = ast_play_and_wait(chan, "vm-mismatch");
+ break;
+ }
+ if (ast_strlen_zero(ext_pass_cmd))
+ vm_change_password(vmu,newpassword);
+ else
+ vm_change_password_shell(vmu,newpassword);
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG,"User %s set password to %s of length %d\n",vms->username,newpassword,(int)strlen(newpassword));
+ cmd = ast_play_and_wait(chan,"vm-passchanged");
+ break;
+ case '*':
+ cmd = 't';
+ break;
+ default:
+ cmd = 0;
+ snprintf(prefile, sizeof(prefile), "%s%s/%s/temp", VM_SPOOL_DIR, vmu->context, vms->username);
+ RETRIEVE(prefile, -1, vmu);
+ if (ast_fileexists(prefile, NULL, NULL))
+ cmd = ast_play_and_wait(chan, "vm-tmpexists");
+ DISPOSE(prefile, -1);
+ if (!cmd)
+ cmd = ast_play_and_wait(chan, "vm-options");
+ if (!cmd)
+ cmd = ast_waitfordigit(chan,6000);
+ if (!cmd)
+ retries++;
+ if (retries > 3)
+ cmd = 't';
+ }
+ }
+ if (cmd == 't')
+ cmd = 0;
+ return cmd;
+}
+
+static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain)
+{
+ int res;
+ int cmd = 0;
+ int retries = 0;
+ int duration = 0;
+ char prefile[PATH_MAX] = "";
+ unsigned char buf[256];
+ char dest[PATH_MAX];
+ int bytes = 0;
+
+ if (ast_adsi_available(chan)) {
+ bytes += adsi_logo(buf + bytes);
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 3, ADSI_JUST_CENT, 0, "Temp Greeting Menu", "");
+ bytes += ast_adsi_display(buf + bytes, ADSI_COMM_PAGE, 4, ADSI_JUST_CENT, 0, "Not Done", "");
+ bytes += ast_adsi_set_line(buf + bytes, ADSI_COMM_PAGE, 1);
+ bytes += ast_adsi_voice_mode(buf + bytes, 0);
+ ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY);
+ }
+
+ snprintf(prefile, sizeof(prefile), "%s%s/%s/temp", VM_SPOOL_DIR, vmu->context, vms->username);
+ if ((res = create_dirpath(dest, sizeof(dest), vmu->context, vms->username, "temp"))) {
+ ast_log(LOG_WARNING, "Failed to create directory (%s).\n", prefile);
+ return -1;
+ }
+ while ((cmd >= 0) && (cmd != 't')) {
+ if (cmd)
+ retries = 0;
+ RETRIEVE(prefile, -1, vmu);
+ if (ast_fileexists(prefile, NULL, NULL) <= 0) {
+ play_record_review(chan, "vm-rec-temp", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, record_gain, vms);
+ cmd = 't';
+ } else {
+ switch (cmd) {
+ case '1':
+ cmd = play_record_review(chan, "vm-rec-temp", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, record_gain, vms);
+ break;
+ case '2':
+ DELETE(prefile, -1, prefile, vmu);
+ ast_play_and_wait(chan, "vm-tempremoved");
+ cmd = 't';
+ break;
+ case '*':
+ cmd = 't';
+ break;
+ default:
+ cmd = ast_play_and_wait(chan,
+ ast_fileexists(prefile, NULL, NULL) > 0 ? /* XXX always true ? */
+ "vm-tempgreeting2" : "vm-tempgreeting");
+ if (!cmd)
+ cmd = ast_waitfordigit(chan,6000);
+ if (!cmd)
+ retries++;
+ if (retries > 3)
+ cmd = 't';
+ }
+ }
+ DISPOSE(prefile, -1);
+ }
+ if (cmd == 't')
+ cmd = 0;
+ return cmd;
+}
+
+/* GREEK SYNTAX */
+
+static int vm_browse_messages_gr(struct ast_channel *chan, struct vm_state *vms, struct ast_vm_user *vmu)
+{
+ int cmd=0;
+
+ if (vms->lastmsg > -1) {
+ cmd = play_message(chan, vmu, vms);
+ } else {
+ cmd = ast_play_and_wait(chan, "vm-youhaveno");
+ if (!strcasecmp(vms->vmbox, "vm-INBOX") ||!strcasecmp(vms->vmbox, "vm-Old")){
+ if (!cmd) {
+ snprintf(vms->fn, sizeof(vms->fn), "vm-%ss", vms->curbox);
+ cmd = ast_play_and_wait(chan, vms->fn);
+ }
+ if (!cmd)
+ cmd = ast_play_and_wait(chan, "vm-messages");
+ } else {
+ if (!cmd)
+ cmd = ast_play_and_wait(chan, "vm-messages");
+ if (!cmd) {
+ snprintf(vms->fn, sizeof(vms->fn), "vm-%s", vms->curbox);
+ cmd = ast_play_and_wait(chan, vms->fn);
+ }
+ }
+ }
+ return cmd;
+}
+
+/* Default English syntax */
+static int vm_browse_messages_en(struct ast_channel *chan, struct vm_state *vms, struct ast_vm_user *vmu)
+{
+ int cmd=0;
+
+ if (vms->lastmsg > -1) {
+ cmd = play_message(chan, vmu, vms);
+ } else {
+ cmd = ast_play_and_wait(chan, "vm-youhave");
+ if (!cmd)
+ cmd = ast_play_and_wait(chan, "vm-no");
+ if (!cmd) {
+ snprintf(vms->fn, sizeof(vms->fn), "vm-%s", vms->curbox);
+ cmd = ast_play_and_wait(chan, vms->fn);
+ }
+ if (!cmd)
+ cmd = ast_play_and_wait(chan, "vm-messages");
+ }
+ return cmd;
+}
+
+/* Hebrew Syntax */
+static int vm_browse_messages_he(struct ast_channel *chan, struct vm_state *vms, struct ast_vm_user *vmu)
+{
+ int cmd = 0;
+
+ if (vms->lastmsg > -1) {
+ cmd = play_message(chan, vmu, vms);
+ } else {
+ if (!strcasecmp(vms->fn, "INBOX")) {
+ cmd = ast_play_and_wait(chan, "vm-nonewmessages");
+ } else {
+ cmd = ast_play_and_wait(chan, "vm-nomessages");
+ }
+ }
+ return cmd;
+}
+
+
+/* ITALIAN syntax */
+static int vm_browse_messages_it(struct ast_channel *chan, struct vm_state *vms, struct ast_vm_user *vmu)
+{
+ int cmd=0;
+
+ if (vms->lastmsg > -1) {
+ cmd = play_message(chan, vmu, vms);
+ } else {
+ cmd = ast_play_and_wait(chan, "vm-no");
+ if (!cmd)
+ cmd = ast_play_and_wait(chan, "vm-message");
+ if (!cmd) {
+ snprintf(vms->fn, sizeof(vms->fn), "vm-%s", vms->curbox);
+ cmd = ast_play_and_wait(chan, vms->fn);
+ }
+ }
+ return cmd;
+}
+
+/* SPANISH syntax */
+static int vm_browse_messages_es(struct ast_channel *chan, struct vm_state *vms, struct ast_vm_user *vmu)
+{
+ int cmd=0;
+
+ if (vms->lastmsg > -1) {
+ cmd = play_message(chan, vmu, vms);
+ } else {
+ cmd = ast_play_and_wait(chan, "vm-youhaveno");
+ if (!cmd)
+ cmd = ast_play_and_wait(chan, "vm-messages");
+ if (!cmd) {
+ snprintf(vms->fn, sizeof(vms->fn), "vm-%s", vms->curbox);
+ cmd = ast_play_and_wait(chan, vms->fn);
+ }
+ }
+ return cmd;
+}
+
+/* PORTUGUESE syntax */
+static int vm_browse_messages_pt(struct ast_channel *chan, struct vm_state *vms, struct ast_vm_user *vmu)
+{
+ int cmd=0;
+
+ if (vms->lastmsg > -1) {
+ cmd = play_message(chan, vmu, vms);
+ } else {
+ cmd = ast_play_and_wait(chan, "vm-no");
+ if (!cmd) {
+ snprintf(vms->fn, sizeof(vms->fn), "vm-%s", vms->curbox);
+ cmd = ast_play_and_wait(chan, vms->fn);
+ }
+ if (!cmd)
+ cmd = ast_play_and_wait(chan, "vm-messages");
+ }
+ return cmd;
+}
+
+static int vm_browse_messages(struct ast_channel *chan, struct vm_state *vms, struct ast_vm_user *vmu)
+{
+ if (!strcasecmp(chan->language, "es")) { /* SPANISH */
+ return vm_browse_messages_es(chan, vms, vmu);
+ } else if (!strcasecmp(chan->language, "it")) { /* ITALIAN */
+ return vm_browse_messages_it(chan, vms, vmu);
+ } else if (!strcasecmp(chan->language, "pt") || !strcasecmp(chan->language, "pt_BR")) { /* PORTUGUESE */
+ return vm_browse_messages_pt(chan, vms, vmu);
+ } else if (!strcasecmp(chan->language, "gr")){
+ return vm_browse_messages_gr(chan, vms, vmu); /* GREEK */
+ } else if (!strcasecmp(chan->language, "he")) {
+ return vm_browse_messages_he(chan, vms, vmu); /* HEBREW */
+ } else { /* Default to English syntax */
+ return vm_browse_messages_en(chan, vms, vmu);
+ }
+}
+
+static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_size,
+ struct ast_vm_user *res_vmu, const char *context, const char *prefix,
+ int skipuser, int maxlogins, int silent)
+{
+ int useadsi=0, valid=0, logretries=0;
+ char password[AST_MAX_EXTENSION]="", *passptr;
+ struct ast_vm_user vmus, *vmu = NULL;
+
+ /* If ADSI is supported, setup login screen */
+ adsi_begin(chan, &useadsi);
+ if (!skipuser && useadsi)
+ adsi_login(chan);
+ if (!silent && !skipuser && ast_streamfile(chan, "vm-login", chan->language)) {
+ ast_log(LOG_WARNING, "Couldn't stream login file\n");
+ return -1;
+ }
+
+ /* Authenticate them and get their mailbox/password */
+
+ while (!valid && (logretries < maxlogins)) {
+ /* Prompt for, and read in the username */
+ if (!skipuser && ast_readstring(chan, mailbox, mailbox_size - 1, 2000, 10000, "#") < 0) {
+ ast_log(LOG_WARNING, "Couldn't read username\n");
+ return -1;
+ }
+ if (ast_strlen_zero(mailbox)) {
+ if (chan->cid.cid_num) {
+ ast_copy_string(mailbox, chan->cid.cid_num, mailbox_size);
+ } else {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Username not entered\n");
+ return -1;
+ }
+ }
+ if (useadsi)
+ adsi_password(chan);
+
+ if (!ast_strlen_zero(prefix)) {
+ char fullusername[80] = "";
+ ast_copy_string(fullusername, prefix, sizeof(fullusername));
+ strncat(fullusername, mailbox, sizeof(fullusername) - 1 - strlen(fullusername));
+ ast_copy_string(mailbox, fullusername, mailbox_size);
+ }
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Before find user for mailbox %s\n",mailbox);
+ vmu = find_user(&vmus, context, mailbox);
+ if (vmu && (vmu->password[0] == '\0' || (vmu->password[0] == '-' && vmu->password[1] == '\0'))) {
+ /* saved password is blank, so don't bother asking */
+ password[0] = '\0';
+ } else {
+ if (ast_streamfile(chan, "vm-password", chan->language)) {
+ ast_log(LOG_WARNING, "Unable to stream password file\n");
+ return -1;
+ }
+ if (ast_readstring(chan, password, sizeof(password) - 1, 2000, 10000, "#") < 0) {
+ ast_log(LOG_WARNING, "Unable to read password\n");
+ return -1;
+ }
+ }
+
+ if (vmu) {
+ passptr = vmu->password;
+ if (passptr[0] == '-') passptr++;
+ }
+ if (vmu && !strcmp(passptr, password))
+ valid++;
+ else {
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "Incorrect password '%s' for user '%s' (context = %s)\n", password, mailbox, context ? context : "default");
+ if (!ast_strlen_zero(prefix))
+ mailbox[0] = '\0';
+ }
+ logretries++;
+ if (!valid) {
+ if (skipuser || logretries >= maxlogins) {
+ if (ast_streamfile(chan, "vm-incorrect", chan->language)) {
+ ast_log(LOG_WARNING, "Unable to stream incorrect message\n");
+ return -1;
+ }
+ } else {
+ if (useadsi)
+ adsi_login(chan);
+ if (ast_streamfile(chan, "vm-incorrect-mailbox", chan->language)) {
+ ast_log(LOG_WARNING, "Unable to stream incorrect mailbox message\n");
+ return -1;
+ }
+ }
+ if (ast_waitstream(chan, "")) /* Channel is hung up */
+ return -1;
+ }
+ }
+ if (!valid && (logretries >= maxlogins)) {
+ ast_stopstream(chan);
+ ast_play_and_wait(chan, "vm-goodbye");
+ return -1;
+ }
+ if (vmu && !skipuser) {
+ memcpy(res_vmu, vmu, sizeof(struct ast_vm_user));
+ }
+ return 0;
+}
+
+static int vm_execmain(struct ast_channel *chan, void *data)
+{
+ /* XXX This is, admittedly, some pretty horrendus code. For some
+ reason it just seemed a lot easier to do with GOTO's. I feel
+ like I'm back in my GWBASIC days. XXX */
+ int res=-1;
+ int cmd=0;
+ int valid = 0;
+ struct ast_module_user *u;
+ char prefixstr[80] ="";
+ char ext_context[256]="";
+ int box;
+ int useadsi = 0;
+ int skipuser = 0;
+ struct vm_state vms;
+ struct ast_vm_user *vmu = NULL, vmus;
+ char *context=NULL;
+ int silentexit = 0;
+ struct ast_flags flags = { 0 };
+ signed char record_gain = 0;
+ int play_auto = 0;
+ int play_folder = 0;
+#ifdef IMAP_STORAGE
+ int deleted = 0;
+#endif
+ u = ast_module_user_add(chan);
+
+ /* Add the vm_state to the active list and keep it active */
+ memset(&vms, 0, sizeof(vms));
+ vms.lastmsg = -1;
+
+ memset(&vmus, 0, sizeof(vmus));
+
+ if (chan->_state != AST_STATE_UP) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Before ast_answer\n");
+ ast_answer(chan);
+ }
+
+ if (!ast_strlen_zero(data)) {
+ char *opts[OPT_ARG_ARRAY_SIZE];
+ char *parse;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(argv0);
+ AST_APP_ARG(argv1);
+ );
+
+ parse = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ if (args.argc == 2) {
+ if (ast_app_parse_options(vm_app_options, &flags, opts, args.argv1)) {
+ ast_module_user_remove(u);
+ return -1;
+ }
+ if (ast_test_flag(&flags, OPT_RECORDGAIN)) {
+ int gain;
+ if (!ast_strlen_zero(opts[OPT_ARG_RECORDGAIN])) {
+ if (sscanf(opts[OPT_ARG_RECORDGAIN], "%d", &gain) != 1) {
+ ast_log(LOG_WARNING, "Invalid value '%s' provided for record gain option\n", opts[OPT_ARG_RECORDGAIN]);
+ ast_module_user_remove(u);
+ return -1;
+ } else {
+ record_gain = (signed char) gain;
+ }
+ } else {
+ ast_log(LOG_WARNING, "Invalid Gain level set with option g\n");
+ }
+ }
+ if (ast_test_flag(&flags, OPT_AUTOPLAY) ) {
+ play_auto = 1;
+ if (opts[OPT_ARG_PLAYFOLDER]) {
+ if (sscanf(opts[OPT_ARG_PLAYFOLDER], "%d", &play_folder) != 1) {
+ ast_log(LOG_WARNING, "Invalid value '%s' provided for folder autoplay option\n", opts[OPT_ARG_PLAYFOLDER]);
+ }
+ } else {
+ ast_log(LOG_WARNING, "Invalid folder set with option a\n");
+ }
+ if ( play_folder > 9 || play_folder < 0) {
+ ast_log(LOG_WARNING, "Invalid value '%d' provided for folder autoplay option\n", play_folder);
+ play_folder = 0;
+ }
+ }
+ } else {
+ /* old style options parsing */
+ while (*(args.argv0)) {
+ if (*(args.argv0) == 's')
+ ast_set_flag(&flags, OPT_SILENT);
+ else if (*(args.argv0) == 'p')
+ ast_set_flag(&flags, OPT_PREPEND_MAILBOX);
+ else
+ break;
+ (args.argv0)++;
+ }
+
+ }
+
+ valid = ast_test_flag(&flags, OPT_SILENT);
+
+ if ((context = strchr(args.argv0, '@')))
+ *context++ = '\0';
+
+ if (ast_test_flag(&flags, OPT_PREPEND_MAILBOX))
+ ast_copy_string(prefixstr, args.argv0, sizeof(prefixstr));
+ else
+ ast_copy_string(vms.username, args.argv0, sizeof(vms.username));
+
+ if (!ast_strlen_zero(vms.username) && (vmu = find_user(&vmus, context ,vms.username)))
+ skipuser++;
+ else
+ valid = 0;
+ }
+
+ if (!valid)
+ res = vm_authenticate(chan, vms.username, sizeof(vms.username), &vmus, context, prefixstr, skipuser, maxlogins, 0);
+
+ if (option_debug)
+ ast_log(LOG_DEBUG, "After vm_authenticate\n");
+ if (!res) {
+ valid = 1;
+ if (!skipuser)
+ vmu = &vmus;
+ } else {
+ res = 0;
+ }
+
+ /* If ADSI is supported, setup login screen */
+ adsi_begin(chan, &useadsi);
+
+#ifdef IMAP_STORAGE
+ vms.interactive = 1;
+ vms.updated = 1;
+ ast_copy_string(vms.context, vmu->context, sizeof(vms.context));
+ vmstate_insert(&vms);
+ init_vm_state(&vms);
+#endif
+ if (!valid)
+ goto out;
+
+ if (!(vms.deleted = ast_calloc(vmu->maxmsg, sizeof(int)))) {
+ /* TODO: Handle memory allocation failure */
+ }
+ if (!(vms.heard = ast_calloc(vmu->maxmsg, sizeof(int)))) {
+ /* TODO: Handle memory allocation failure */
+ }
+
+ /* Set language from config to override channel language */
+ if (!ast_strlen_zero(vmu->language))
+ ast_string_field_set(chan, language, vmu->language);
+ create_dirpath(vms.curdir, sizeof(vms.curdir), vmu->context, vms.username, "");
+ /* Retrieve old and new message counts */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Before open_mailbox\n");
+ res = open_mailbox(&vms, vmu, 1);
+ if (res == ERROR_LOCK_PATH)
+ goto out;
+ vms.oldmessages = vms.lastmsg + 1;
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Number of old messages: %d\n",vms.oldmessages);
+ /* Start in INBOX */
+ res = open_mailbox(&vms, vmu, 0);
+ if (res == ERROR_LOCK_PATH)
+ goto out;
+ vms.newmessages = vms.lastmsg + 1;
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Number of new messages: %d\n",vms.newmessages);
+
+ /* Select proper mailbox FIRST!! */
+ if (play_auto) {
+ res = open_mailbox(&vms, vmu, play_folder);
+ if (res == ERROR_LOCK_PATH)
+ goto out;
+
+ /* If there are no new messages, inform the user and hangup */
+ if (vms.lastmsg == -1) {
+ cmd = vm_browse_messages(chan, &vms, vmu);
+ res = 0;
+ goto out;
+ }
+ } else {
+ if (!vms.newmessages && vms.oldmessages) {
+ /* If we only have old messages start here */
+ res = open_mailbox(&vms, vmu, 1);
+ play_folder = 1;
+ if (res == ERROR_LOCK_PATH)
+ goto out;
+ }
+ }
+
+ if (useadsi)
+ adsi_status(chan, &vms);
+ res = 0;
+
+ /* Check to see if this is a new user */
+ if (!strcasecmp(vmu->mailbox, vmu->password) &&
+ (ast_test_flag(vmu, VM_FORCENAME | VM_FORCEGREET))) {
+ if (ast_play_and_wait(chan, "vm-newuser") == -1)
+ ast_log(LOG_WARNING, "Couldn't stream new user file\n");
+ cmd = vm_newuser(chan, vmu, &vms, vmfmts, record_gain);
+ if ((cmd == 't') || (cmd == '#')) {
+ /* Timeout */
+ res = 0;
+ goto out;
+ } else if (cmd < 0) {
+ /* Hangup */
+ res = -1;
+ goto out;
+ }
+ }
+#ifdef IMAP_STORAGE
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Checking quotas: comparing %u to %u\n",vms.quota_usage,vms.quota_limit);
+ if (vms.quota_limit && vms.quota_usage >= vms.quota_limit) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "*** QUOTA EXCEEDED!!\n");
+ cmd = ast_play_and_wait(chan, "vm-mailboxfull");
+ }
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Checking quotas: User has %d messages and limit is %d.\n",(vms.newmessages + vms.oldmessages),vmu->maxmsg);
+ if ((vms.newmessages + vms.oldmessages) >= vmu->maxmsg) {
+ ast_log(LOG_WARNING, "No more messages possible. User has %d messages and limit is %d.\n",(vms.newmessages + vms.oldmessages),vmu->maxmsg);
+ cmd = ast_play_and_wait(chan, "vm-mailboxfull");
+ }
+#endif
+ if (play_auto) {
+ cmd = '1';
+ } else {
+ cmd = vm_intro(chan, vmu, &vms);
+ }
+
+ vms.repeats = 0;
+ vms.starting = 1;
+ while ((cmd > -1) && (cmd != 't') && (cmd != '#')) {
+ /* Run main menu */
+ switch (cmd) {
+ case '1':
+ vms.curmsg = 0;
+ /* Fall through */
+ case '5':
+ cmd = vm_browse_messages(chan, &vms, vmu);
+ break;
+ case '2': /* Change folders */
+ if (useadsi)
+ adsi_folders(chan, 0, "Change to folder...");
+ cmd = get_folder2(chan, "vm-changeto", 0);
+ if (cmd == '#') {
+ cmd = 0;
+ } else if (cmd > 0) {
+ cmd = cmd - '0';
+ res = close_mailbox(&vms, vmu);
+ if (res == ERROR_LOCK_PATH)
+ goto out;
+ res = open_mailbox(&vms, vmu, cmd);
+ if (res == ERROR_LOCK_PATH)
+ goto out;
+ play_folder = cmd;
+ cmd = 0;
+ }
+ if (useadsi)
+ adsi_status2(chan, &vms);
+
+ if (!cmd)
+ cmd = vm_play_folder_name(chan, vms.vmbox);
+
+ vms.starting = 1;
+ break;
+ case '3': /* Advanced options */
+ cmd = 0;
+ vms.repeats = 0;
+ while ((cmd > -1) && (cmd != 't') && (cmd != '#')) {
+ switch (cmd) {
+ case '1': /* Reply */
+ if (vms.lastmsg > -1 && !vms.starting) {
+ cmd = advanced_options(chan, vmu, &vms, vms.curmsg, 1, record_gain);
+ if (cmd == ERROR_LOCK_PATH) {
+ res = cmd;
+ goto out;
+ }
+ } else
+ cmd = ast_play_and_wait(chan, "vm-sorry");
+ cmd = 't';
+ break;
+ case '2': /* Callback */
+ if (option_verbose > 2 && !vms.starting)
+ ast_verbose( VERBOSE_PREFIX_3 "Callback Requested\n");
+ if (!ast_strlen_zero(vmu->callback) && vms.lastmsg > -1 && !vms.starting) {
+ cmd = advanced_options(chan, vmu, &vms, vms.curmsg, 2, record_gain);
+ if (cmd == 9) {
+ silentexit = 1;
+ goto out;
+ } else if (cmd == ERROR_LOCK_PATH) {
+ res = cmd;
+ goto out;
+ }
+ }
+ else
+ cmd = ast_play_and_wait(chan, "vm-sorry");
+ cmd = 't';
+ break;
+ case '3': /* Envelope */
+ if (vms.lastmsg > -1 && !vms.starting) {
+ cmd = advanced_options(chan, vmu, &vms, vms.curmsg, 3, record_gain);
+ if (cmd == ERROR_LOCK_PATH) {
+ res = cmd;
+ goto out;
+ }
+ } else
+ cmd = ast_play_and_wait(chan, "vm-sorry");
+ cmd = 't';
+ break;
+ case '4': /* Dialout */
+ if (!ast_strlen_zero(vmu->dialout)) {
+ cmd = dialout(chan, vmu, NULL, vmu->dialout);
+ if (cmd == 9) {
+ silentexit = 1;
+ goto out;
+ }
+ }
+ else
+ cmd = ast_play_and_wait(chan, "vm-sorry");
+ cmd = 't';
+ break;
+
+ case '5': /* Leave VoiceMail */
+ if (ast_test_flag(vmu, VM_SVMAIL)) {
+ cmd = forward_message(chan, context, &vms, vmu, vmfmts, 1, record_gain);
+ if (cmd == ERROR_LOCK_PATH) {
+ res = cmd;
+ ast_log(LOG_WARNING, "forward_message failed to lock path.\n");
+ goto out;
+ }
+ } else
+ cmd = ast_play_and_wait(chan,"vm-sorry");
+ cmd='t';
+ break;
+
+ case '*': /* Return to main menu */
+ cmd = 't';
+ break;
+
+ default:
+ cmd = 0;
+ if (!vms.starting) {
+ cmd = ast_play_and_wait(chan, "vm-toreply");
+ }
+ if (!ast_strlen_zero(vmu->callback) && !vms.starting && !cmd) {
+ cmd = ast_play_and_wait(chan, "vm-tocallback");
+ }
+ if (!cmd && !vms.starting) {
+ cmd = ast_play_and_wait(chan, "vm-tohearenv");
+ }
+ if (!ast_strlen_zero(vmu->dialout) && !cmd) {
+ cmd = ast_play_and_wait(chan, "vm-tomakecall");
+ }
+ if (ast_test_flag(vmu, VM_SVMAIL) && !cmd)
+ cmd=ast_play_and_wait(chan, "vm-leavemsg");
+ if (!cmd)
+ cmd = ast_play_and_wait(chan, "vm-starmain");
+ if (!cmd)
+ cmd = ast_waitfordigit(chan,6000);
+ if (!cmd)
+ vms.repeats++;
+ if (vms.repeats > 3)
+ cmd = 't';
+ }
+ }
+ if (cmd == 't') {
+ cmd = 0;
+ vms.repeats = 0;
+ }
+ break;
+ case '4':
+ if (vms.curmsg > 0) {
+ vms.curmsg--;
+ cmd = play_message(chan, vmu, &vms);
+ } else {
+ cmd = ast_play_and_wait(chan, "vm-nomore");
+ }
+ break;
+ case '6':
+ if (vms.curmsg < vms.lastmsg) {
+ vms.curmsg++;
+ cmd = play_message(chan, vmu, &vms);
+ } else {
+ cmd = ast_play_and_wait(chan, "vm-nomore");
+ }
+ break;
+ case '7':
+ if (vms.curmsg >= 0 && vms.curmsg <= vms.lastmsg) {
+ vms.deleted[vms.curmsg] = !vms.deleted[vms.curmsg];
+ if (useadsi)
+ adsi_delete(chan, &vms);
+ if (vms.deleted[vms.curmsg]) {
+ if (play_folder == 0)
+ vms.newmessages--;
+ else if (play_folder == 1)
+ vms.oldmessages--;
+ cmd = ast_play_and_wait(chan, "vm-deleted");
+ }
+ else {
+ if (play_folder == 0)
+ vms.newmessages++;
+ else if (play_folder == 1)
+ vms.oldmessages++;
+ cmd = ast_play_and_wait(chan, "vm-undeleted");
+ }
+ if (ast_test_flag((&globalflags), VM_SKIPAFTERCMD)) {
+ if (vms.curmsg < vms.lastmsg) {
+ vms.curmsg++;
+ cmd = play_message(chan, vmu, &vms);
+ } else {
+ cmd = ast_play_and_wait(chan, "vm-nomore");
+ }
+ }
+ } else /* Delete not valid if we haven't selected a message */
+ cmd = 0;
+#ifdef IMAP_STORAGE
+ deleted = 1;
+#endif
+ break;
+
+ case '8':
+ if (vms.lastmsg > -1) {
+ cmd = forward_message(chan, context, &vms, vmu, vmfmts, 0, record_gain);
+ if (cmd == ERROR_LOCK_PATH) {
+ res = cmd;
+ goto out;
+ }
+ } else
+ cmd = ast_play_and_wait(chan, "vm-nomore");
+ break;
+ case '9':
+ if (vms.curmsg < 0 || vms.curmsg > vms.lastmsg) {
+ /* No message selected */
+ cmd = 0;
+ break;
+ }
+ if (useadsi)
+ adsi_folders(chan, 1, "Save to folder...");
+ cmd = get_folder2(chan, "vm-savefolder", 1);
+ box = 0; /* Shut up compiler */
+ if (cmd == '#') {
+ cmd = 0;
+ break;
+ } else if (cmd > 0) {
+ box = cmd = cmd - '0';
+ cmd = save_to_folder(vmu, &vms, vms.curmsg, cmd);
+ if (cmd == ERROR_LOCK_PATH) {
+ res = cmd;
+ goto out;
+#ifndef IMAP_STORAGE
+ } else if (!cmd) {
+ vms.deleted[vms.curmsg] = 1;
+#endif
+ } else {
+ vms.deleted[vms.curmsg] = 0;
+ vms.heard[vms.curmsg] = 0;
+ }
+ }
+ make_file(vms.fn, sizeof(vms.fn), vms.curdir, vms.curmsg);
+ if (useadsi)
+ adsi_message(chan, &vms);
+ snprintf(vms.fn, sizeof(vms.fn), "vm-%s", mbox(box));
+ if (!cmd) {
+ cmd = ast_play_and_wait(chan, "vm-message");
+ if (!cmd)
+ cmd = say_and_wait(chan, vms.curmsg + 1, chan->language);
+ if (!cmd)
+ cmd = ast_play_and_wait(chan, "vm-savedto");
+ if (!cmd)
+ cmd = vm_play_folder_name(chan, vms.fn);
+ } else {
+ cmd = ast_play_and_wait(chan, "vm-mailboxfull");
+ }
+ if (ast_test_flag((&globalflags), VM_SKIPAFTERCMD)) {
+ if (vms.curmsg < vms.lastmsg) {
+ vms.curmsg++;
+ cmd = play_message(chan, vmu, &vms);
+ } else {
+ cmd = ast_play_and_wait(chan, "vm-nomore");
+ }
+ }
+ break;
+ case '*':
+ if (!vms.starting) {
+ cmd = ast_play_and_wait(chan, "vm-onefor");
+ if (!strcasecmp(chan->language, "he"))
+ cmd = ast_play_and_wait(chan, "vm-for");
+ if (!cmd)
+ cmd = vm_play_folder_name(chan, vms.vmbox);
+ if (!cmd)
+ cmd = ast_play_and_wait(chan, "vm-opts");
+ if (!cmd)
+ cmd = vm_instructions(chan, &vms, 1);
+ } else
+ cmd = 0;
+ break;
+ case '0':
+ cmd = vm_options(chan, vmu, &vms, vmfmts, record_gain);
+ if (useadsi)
+ adsi_status(chan, &vms);
+ break;
+ default: /* Nothing */
+ cmd = vm_instructions(chan, &vms, 0);
+ break;
+ }
+ }
+ if ((cmd == 't') || (cmd == '#')) {
+ /* Timeout */
+ res = 0;
+ } else {
+ /* Hangup */
+ res = -1;
+ }
+
+out:
+ if (res > -1) {
+ ast_stopstream(chan);
+ adsi_goodbye(chan);
+ if (valid) {
+ if (silentexit)
+ res = ast_play_and_wait(chan, "vm-dialout");
+ else
+ res = ast_play_and_wait(chan, "vm-goodbye");
+ if (res > 0)
+ res = 0;
+ }
+ if (useadsi)
+ ast_adsi_unload_session(chan);
+ }
+ if (vmu)
+ close_mailbox(&vms, vmu);
+ if (valid) {
+ snprintf(ext_context, sizeof(ext_context), "%s@%s", vms.username, vmu->context);
+ manager_event(EVENT_FLAG_CALL, "MessageWaiting", "Mailbox: %s\r\nWaiting: %d\r\n", ext_context, has_voicemail(ext_context, NULL));
+ run_externnotify(vmu->context, vmu->mailbox);
+ }
+#ifdef IMAP_STORAGE
+ /* expunge message - use UID Expunge if supported on IMAP server*/
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "*** Checking if we can expunge, deleted set to %d, expungeonhangup set to %d\n",deleted,expungeonhangup);
+ if (vmu && deleted == 1 && expungeonhangup == 1 && vms.mailstream != NULL) {
+ ast_mutex_lock(&vms.lock);
+#ifdef HAVE_IMAP_TK2006
+ if (LEVELUIDPLUS (vms.mailstream)) {
+ mail_expunge_full(vms.mailstream,NIL,EX_UID);
+ } else
+#endif
+ mail_expunge(vms.mailstream);
+ ast_mutex_unlock(&vms.lock);
+ }
+ /* before we delete the state, we should copy pertinent info
+ * back to the persistent model */
+ vmstate_delete(&vms);
+#endif
+ if (vmu)
+ free_user(vmu);
+ if (vms.deleted)
+ free(vms.deleted);
+ if (vms.heard)
+ free(vms.heard);
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int vm_exec(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ struct ast_module_user *u;
+ char *tmp;
+ struct leave_vm_options leave_options;
+ struct ast_flags flags = { 0 };
+ static int deprecate_warning = 0;
+ char *opts[OPT_ARG_ARRAY_SIZE];
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(argv0);
+ AST_APP_ARG(argv1);
+ );
+
+ u = ast_module_user_add(chan);
+
+ memset(&leave_options, 0, sizeof(leave_options));
+
+ if (chan->_state != AST_STATE_UP)
+ ast_answer(chan);
+
+ if (!ast_strlen_zero(data)) {
+ tmp = ast_strdupa(data);
+ AST_STANDARD_APP_ARGS(args, tmp);
+ if (args.argc == 2) {
+ if (ast_app_parse_options(vm_app_options, &flags, opts, args.argv1)) {
+ ast_module_user_remove(u);
+ return -1;
+ }
+ ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING | OPT_PRIORITY_JUMP);
+ if (ast_test_flag(&flags, OPT_RECORDGAIN)) {
+ int gain;
+
+ if (sscanf(opts[OPT_ARG_RECORDGAIN], "%d", &gain) != 1) {
+ ast_log(LOG_WARNING, "Invalid value '%s' provided for record gain option\n", opts[OPT_ARG_RECORDGAIN]);
+ ast_module_user_remove(u);
+ return -1;
+ } else {
+ leave_options.record_gain = (signed char) gain;
+ }
+ }
+ } else {
+ /* old style options parsing */
+ int old = 0;
+ char *orig_argv0 = args.argv0;
+ while (*(args.argv0)) {
+ if (*(args.argv0) == 's') {
+ old = 1;
+ ast_set_flag(&leave_options, OPT_SILENT);
+ } else if (*(args.argv0) == 'b') {
+ old = 1;
+ ast_set_flag(&leave_options, OPT_BUSY_GREETING);
+ } else if (*(args.argv0) == 'u') {
+ old = 1;
+ ast_set_flag(&leave_options, OPT_UNAVAIL_GREETING);
+ } else if (*(args.argv0) == 'j') {
+ old = 1;
+ ast_set_flag(&leave_options, OPT_PRIORITY_JUMP);
+ } else
+ break;
+ (args.argv0)++;
+ }
+ if (!deprecate_warning && old) {
+ deprecate_warning = 1;
+ ast_log(LOG_WARNING, "Prefixing the mailbox with an option is deprecated ('%s').\n", orig_argv0);
+ ast_log(LOG_WARNING, "Please move all leading options to the second argument.\n");
+ }
+ }
+ } else {
+ char tmp[256];
+ res = ast_app_getdata(chan, "vm-whichbox", tmp, sizeof(tmp) - 1, 0);
+ if (res < 0) {
+ ast_module_user_remove(u);
+ return res;
+ }
+ if (ast_strlen_zero(tmp)) {
+ ast_module_user_remove(u);
+ return 0;
+ }
+ args.argv0 = ast_strdupa(tmp);
+ }
+
+ res = leave_voicemail(chan, args.argv0, &leave_options);
+
+ if (res == ERROR_LOCK_PATH) {
+ ast_log(LOG_ERROR, "Could not leave voicemail. The path is already locked.\n");
+ /*Send the call to n+101 priority, where n is the current priority*/
+ if (ast_test_flag(&leave_options, OPT_PRIORITY_JUMP) || ast_opt_priority_jumping)
+ if (ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101))
+ ast_log(LOG_WARNING, "Extension %s, priority %d doesn't exist.\n", chan->exten, chan->priority + 101);
+ pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
+ res = 0;
+ }
+
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static struct ast_vm_user *find_or_create(char *context, char *mbox)
+{
+ struct ast_vm_user *vmu;
+ AST_LIST_TRAVERSE(&users, vmu, list) {
+ if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mbox, vmu->mailbox))
+ break;
+ if (context && (!strcasecmp(context, vmu->context)) && (!strcasecmp(mbox, vmu->mailbox)))
+ break;
+ }
+
+ if (!vmu) {
+ if ((vmu = ast_calloc(1, sizeof(*vmu)))) {
+ ast_copy_string(vmu->context, context, sizeof(vmu->context));
+ ast_copy_string(vmu->mailbox, mbox, sizeof(vmu->mailbox));
+ AST_LIST_INSERT_TAIL(&users, vmu, list);
+ }
+ }
+ return vmu;
+}
+
+static int append_mailbox(char *context, char *mbox, char *data)
+{
+ /* Assumes lock is already held */
+ char *tmp;
+ char *stringp;
+ char *s;
+ struct ast_vm_user *vmu;
+
+ tmp = ast_strdupa(data);
+
+ if ((vmu = find_or_create(context, mbox))) {
+ populate_defaults(vmu);
+
+ stringp = tmp;
+ if ((s = strsep(&stringp, ",")))
+ ast_copy_string(vmu->password, s, sizeof(vmu->password));
+ if (stringp && (s = strsep(&stringp, ",")))
+ ast_copy_string(vmu->fullname, s, sizeof(vmu->fullname));
+ if (stringp && (s = strsep(&stringp, ",")))
+ ast_copy_string(vmu->email, s, sizeof(vmu->email));
+ if (stringp && (s = strsep(&stringp, ",")))
+ ast_copy_string(vmu->pager, s, sizeof(vmu->pager));
+ if (stringp && (s = strsep(&stringp, ",")))
+ apply_options(vmu, s);
+ }
+ return 0;
+}
+
+static int vm_box_exists(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *u;
+ struct ast_vm_user svm;
+ char *context, *box;
+ int priority_jump = 0;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(mbox);
+ AST_APP_ARG(options);
+ );
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_ERROR, "MailboxExists requires an argument: (vmbox[@context][|options])\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ box = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(args, box);
+
+ if (args.options) {
+ if (strchr(args.options, 'j'))
+ priority_jump = 1;
+ }
+
+ if ((context = strchr(args.mbox, '@'))) {
+ *context = '\0';
+ context++;
+ }
+
+ if (find_user(&svm, context, args.mbox)) {
+ pbx_builtin_setvar_helper(chan, "VMBOXEXISTSSTATUS", "SUCCESS");
+ if (priority_jump || ast_opt_priority_jumping)
+ if (ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101))
+ ast_log(LOG_WARNING, "VM box %s@%s exists, but extension %s, priority %d doesn't exist\n", box, context, chan->exten, chan->priority + 101);
+ } else
+ pbx_builtin_setvar_helper(chan, "VMBOXEXISTSSTATUS", "FAILED");
+ ast_module_user_remove(u);
+ return 0;
+}
+
+static int vmauthenticate(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *u;
+ char *s = data, *user=NULL, *context=NULL, mailbox[AST_MAX_EXTENSION] = "";
+ struct ast_vm_user vmus;
+ char *options = NULL;
+ int silent = 0, skipuser = 0;
+ int res = -1;
+
+ u = ast_module_user_add(chan);
+
+ if (s) {
+ s = ast_strdupa(s);
+ user = strsep(&s, "|");
+ options = strsep(&s, "|");
+ if (user) {
+ s = user;
+ user = strsep(&s, "@");
+ context = strsep(&s, "");
+ if (!ast_strlen_zero(user))
+ skipuser++;
+ ast_copy_string(mailbox, user, sizeof(mailbox));
+ }
+ }
+
+ if (options) {
+ silent = (strchr(options, 's')) != NULL;
+ }
+
+ if (!vm_authenticate(chan, mailbox, sizeof(mailbox), &vmus, context, NULL, skipuser, 3, silent)) {
+ pbx_builtin_setvar_helper(chan, "AUTH_MAILBOX", mailbox);
+ pbx_builtin_setvar_helper(chan, "AUTH_CONTEXT", vmus.context);
+ ast_play_and_wait(chan, "auth-thankyou");
+ res = 0;
+ }
+
+ ast_module_user_remove(u);
+ return res;
+}
+
+static char voicemail_show_users_help[] =
+"Usage: voicemail show users [for <context>]\n"
+" Lists all mailboxes currently set up\n";
+
+static char voicemail_show_zones_help[] =
+"Usage: voicemail show zones\n"
+" Lists zone message formats\n";
+
+static int handle_voicemail_show_users(int fd, int argc, char *argv[])
+{
+ struct ast_vm_user *vmu;
+ char *output_format = "%-10s %-5s %-25s %-10s %6s\n";
+
+ if ((argc < 3) || (argc > 5) || (argc == 4)) return RESULT_SHOWUSAGE;
+ else if ((argc == 5) && strcmp(argv[3],"for")) return RESULT_SHOWUSAGE;
+
+ AST_LIST_LOCK(&users);
+ if (!AST_LIST_EMPTY(&users)) {
+ if (argc == 3)
+ ast_cli(fd, output_format, "Context", "Mbox", "User", "Zone", "NewMsg");
+ else {
+ int count = 0;
+ AST_LIST_TRAVERSE(&users, vmu, list) {
+ if (!strcmp(argv[4],vmu->context))
+ count++;
+ }
+ if (count) {
+ ast_cli(fd, output_format, "Context", "Mbox", "User", "Zone", "NewMsg");
+ } else {
+ ast_cli(fd, "No such voicemail context \"%s\"\n", argv[4]);
+ AST_LIST_UNLOCK(&users);
+ return RESULT_FAILURE;
+ }
+ }
+ AST_LIST_TRAVERSE(&users, vmu, list) {
+ int newmsgs = 0, oldmsgs = 0;
+ char count[12], tmp[256] = "";
+
+ if ((argc == 3) || ((argc == 5) && !strcmp(argv[4],vmu->context))) {
+ snprintf(tmp, sizeof(tmp), "%s@%s", vmu->mailbox, ast_strlen_zero(vmu->context) ? "default" : vmu->context);
+ inboxcount(tmp, &newmsgs, &oldmsgs);
+ snprintf(count,sizeof(count),"%d",newmsgs);
+ ast_cli(fd, output_format, vmu->context, vmu->mailbox, vmu->fullname, vmu->zonetag, count);
+ }
+ }
+ } else {
+ ast_cli(fd, "There are no voicemail users currently defined\n");
+ AST_LIST_UNLOCK(&users);
+ return RESULT_FAILURE;
+ }
+ AST_LIST_UNLOCK(&users);
+ return RESULT_SUCCESS;
+}
+
+static int handle_voicemail_show_zones(int fd, int argc, char *argv[])
+{
+ struct vm_zone *zone;
+ char *output_format = "%-15s %-20s %-45s\n";
+ int res = RESULT_SUCCESS;
+
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+
+ AST_LIST_LOCK(&zones);
+ if (!AST_LIST_EMPTY(&zones)) {
+ ast_cli(fd, output_format, "Zone", "Timezone", "Message Format");
+ AST_LIST_TRAVERSE(&zones, zone, list) {
+ ast_cli(fd, output_format, zone->name, zone->timezone, zone->msg_format);
+ }
+ } else {
+ ast_cli(fd, "There are no voicemail zones currently defined\n");
+ res = RESULT_FAILURE;
+ }
+ AST_LIST_UNLOCK(&zones);
+
+ return res;
+}
+
+static char *complete_voicemail_show_users(const char *line, const char *word, int pos, int state)
+{
+ int which = 0;
+ int wordlen;
+ struct ast_vm_user *vmu;
+ const char *context = "";
+
+ /* 0 - show; 1 - voicemail; 2 - users; 3 - for; 4 - <context> */
+ if (pos > 4)
+ return NULL;
+ if (pos == 3)
+ return (state == 0) ? ast_strdup("for") : NULL;
+ wordlen = strlen(word);
+ AST_LIST_TRAVERSE(&users, vmu, list) {
+ if (!strncasecmp(word, vmu->context, wordlen)) {
+ if (context && strcmp(context, vmu->context) && ++which > state)
+ return ast_strdup(vmu->context);
+ /* ignore repeated contexts ? */
+ context = vmu->context;
+ }
+ }
+ return NULL;
+}
+
+static struct ast_cli_entry cli_show_voicemail_users_deprecated = {
+ { "show", "voicemail", "users", NULL },
+ handle_voicemail_show_users, NULL,
+ NULL, complete_voicemail_show_users };
+
+static struct ast_cli_entry cli_show_voicemail_zones_deprecated = {
+ { "show", "voicemail", "zones", NULL },
+ handle_voicemail_show_zones, NULL,
+ NULL, NULL };
+
+static struct ast_cli_entry cli_voicemail[] = {
+ { { "voicemail", "show", "users", NULL },
+ handle_voicemail_show_users, "List defined voicemail boxes",
+ voicemail_show_users_help, complete_voicemail_show_users, &cli_show_voicemail_users_deprecated },
+
+ { { "voicemail", "show", "zones", NULL },
+ handle_voicemail_show_zones, "List zone message formats",
+ voicemail_show_zones_help, NULL, &cli_show_voicemail_zones_deprecated },
+};
+
+static void free_vm_users(void)
+{
+ struct ast_vm_user *cur;
+ struct vm_zone *zcur;
+
+ AST_LIST_LOCK(&users);
+ while ((cur = AST_LIST_REMOVE_HEAD(&users, list))) {
+ ast_set_flag(cur, VM_ALLOCED);
+ free_user(cur);
+ }
+ AST_LIST_UNLOCK(&users);
+
+ AST_LIST_LOCK(&zones);
+ while ((zcur = AST_LIST_REMOVE_HEAD(&zones, list))) {
+ free_zone(zcur);
+ }
+ AST_LIST_UNLOCK(&zones);
+}
+
+static int load_config(void)
+{
+ struct ast_vm_user *cur;
+ struct ast_config *cfg, *ucfg;
+ char *cat;
+ struct ast_variable *var;
+ const char *notifystr = NULL;
+ const char *smdistr = NULL;
+ const char *astattach;
+ const char *astsearch;
+ const char *astsaycid;
+ const char *send_voicemail;
+#ifdef IMAP_STORAGE
+ const char *imap_server;
+ const char *imap_port;
+ const char *imap_flags;
+ const char *imap_folder;
+ const char *auth_user;
+ const char *auth_password;
+ const char *expunge_on_hangup;
+ const char *imap_timeout;
+#endif
+ const char *astcallop;
+ const char *astreview;
+ const char *asttempgreetwarn;
+ const char *astskipcmd;
+ const char *asthearenv;
+ const char *astsaydurationinfo;
+ const char *astsaydurationminfo;
+ const char *silencestr;
+ const char *maxmsgstr;
+ const char *astdirfwd;
+ const char *thresholdstr;
+ const char *fmt;
+ const char *astemail;
+ const char *ucontext;
+ const char *astmailcmd = SENDMAIL;
+ const char *astforcename;
+ const char *astforcegreet;
+ const char *s;
+ char *q,*stringp;
+ const char *dialoutcxt = NULL;
+ const char *callbackcxt = NULL;
+ const char *exitcxt = NULL;
+ const char *extpc;
+ const char *emaildateformatstr;
+ const char *volgainstr;
+ int x;
+ int tmpadsi[4];
+
+ cfg = ast_config_load(VOICEMAIL_CONFIG);
+
+ free_vm_users();
+
+ AST_LIST_LOCK(&users);
+
+ memset(ext_pass_cmd, 0, sizeof(ext_pass_cmd));
+
+ if (cfg) {
+ /* General settings */
+
+ if (!(ucontext = ast_variable_retrieve(cfg, "general", "userscontext")))
+ ucontext = "default";
+ ast_copy_string(userscontext, ucontext, sizeof(userscontext));
+ /* Attach voice message to mail message ? */
+ if (!(astattach = ast_variable_retrieve(cfg, "general", "attach")))
+ astattach = "yes";
+ ast_set2_flag((&globalflags), ast_true(astattach), VM_ATTACH);
+
+ if (!(astsearch = ast_variable_retrieve(cfg, "general", "searchcontexts")))
+ astsearch = "no";
+ ast_set2_flag((&globalflags), ast_true(astsearch), VM_SEARCH);
+
+ volgain = 0.0;
+ if ((volgainstr = ast_variable_retrieve(cfg, "general", "volgain")))
+ sscanf(volgainstr, "%lf", &volgain);
+
+#ifdef ODBC_STORAGE
+ strcpy(odbc_database, "asterisk");
+ if ((thresholdstr = ast_variable_retrieve(cfg, "general", "odbcstorage"))) {
+ ast_copy_string(odbc_database, thresholdstr, sizeof(odbc_database));
+ }
+ strcpy(odbc_table, "voicemessages");
+ if ((thresholdstr = ast_variable_retrieve(cfg, "general", "odbctable"))) {
+ ast_copy_string(odbc_table, thresholdstr, sizeof(odbc_table));
+ }
+#endif
+ /* Mail command */
+ strcpy(mailcmd, SENDMAIL);
+ if ((astmailcmd = ast_variable_retrieve(cfg, "general", "mailcmd")))
+ ast_copy_string(mailcmd, astmailcmd, sizeof(mailcmd)); /* User setting */
+
+ maxsilence = 0;
+ if ((silencestr = ast_variable_retrieve(cfg, "general", "maxsilence"))) {
+ maxsilence = atoi(silencestr);
+ if (maxsilence > 0)
+ maxsilence *= 1000;
+ }
+
+ if (!(maxmsgstr = ast_variable_retrieve(cfg, "general", "maxmsg"))) {
+ maxmsg = MAXMSG;
+ } else {
+ maxmsg = atoi(maxmsgstr);
+ if (maxmsg <= 0) {
+ ast_log(LOG_WARNING, "Invalid number of messages per folder '%s'. Using default value %i\n", maxmsgstr, MAXMSG);
+ maxmsg = MAXMSG;
+ } else if (maxmsg > MAXMSGLIMIT) {
+ ast_log(LOG_WARNING, "Maximum number of messages per folder is %i. Cannot accept value '%s'\n", MAXMSGLIMIT, maxmsgstr);
+ maxmsg = MAXMSGLIMIT;
+ }
+ }
+
+ /* Load date format config for voicemail mail */
+ if ((emaildateformatstr = ast_variable_retrieve(cfg, "general", "emaildateformat"))) {
+ ast_copy_string(emaildateformat, emaildateformatstr, sizeof(emaildateformat));
+ }
+
+ /* External password changing command */
+ if ((extpc = ast_variable_retrieve(cfg, "general", "externpass"))) {
+ ast_copy_string(ext_pass_cmd,extpc,sizeof(ext_pass_cmd));
+ }
+#ifdef IMAP_STORAGE
+ /* IMAP server address */
+ if ((imap_server = ast_variable_retrieve(cfg, "general", "imapserver"))) {
+ ast_copy_string(imapserver, imap_server, sizeof(imapserver));
+ } else {
+ ast_copy_string(imapserver,"localhost", sizeof(imapserver));
+ }
+ /* IMAP server port */
+ if ((imap_port = ast_variable_retrieve(cfg, "general", "imapport"))) {
+ ast_copy_string(imapport, imap_port, sizeof(imapport));
+ } else {
+ ast_copy_string(imapport,"143", sizeof(imapport));
+ }
+ /* IMAP server flags */
+ if ((imap_flags = ast_variable_retrieve(cfg, "general", "imapflags"))) {
+ ast_copy_string(imapflags, imap_flags, sizeof(imapflags));
+ }
+ /* IMAP server master username */
+ if ((auth_user = ast_variable_retrieve(cfg, "general", "authuser"))) {
+ ast_copy_string(authuser, auth_user, sizeof(authuser));
+ }
+ /* IMAP server master password */
+ if ((auth_password = ast_variable_retrieve(cfg, "general", "authpassword"))) {
+ ast_copy_string(authpassword, auth_password, sizeof(authpassword));
+ }
+ /* Expunge on exit */
+ if ((expunge_on_hangup = ast_variable_retrieve(cfg, "general", "expungeonhangup"))) {
+ if (ast_false(expunge_on_hangup))
+ expungeonhangup = 0;
+ else
+ expungeonhangup = 1;
+ } else {
+ expungeonhangup = 1;
+ }
+ /* IMAP voicemail folder */
+ if ((imap_folder = ast_variable_retrieve(cfg, "general", "imapfolder"))) {
+ ast_copy_string(imapfolder, imap_folder, sizeof(imapfolder));
+ } else {
+ ast_copy_string(imapfolder,"INBOX", sizeof(imapfolder));
+ }
+
+ /* There is some very unorthodox casting done here. This is due
+ * to the way c-client handles the argument passed in. It expects a
+ * void pointer and casts the pointer directly to a long without
+ * first dereferencing it. */
+
+ if ((imap_timeout = ast_variable_retrieve(cfg, "general", "imapreadtimeout"))) {
+ mail_parameters(NIL, SET_READTIMEOUT, (void *) (atol(imap_timeout)));
+ } else {
+ mail_parameters(NIL, SET_READTIMEOUT, (void *) DEFAULT_IMAP_TCP_TIMEOUT);
+ }
+
+ if ((imap_timeout = ast_variable_retrieve(cfg, "general", "imapwritetimeout"))) {
+ mail_parameters(NIL, SET_WRITETIMEOUT, (void *) (atol(imap_timeout)));
+ } else {
+ mail_parameters(NIL, SET_WRITETIMEOUT, (void *) DEFAULT_IMAP_TCP_TIMEOUT);
+ }
+
+ if ((imap_timeout = ast_variable_retrieve(cfg, "general", "imapopentimeout"))) {
+ mail_parameters(NIL, SET_OPENTIMEOUT, (void *) (atol(imap_timeout)));
+ } else {
+ mail_parameters(NIL, SET_OPENTIMEOUT, (void *) DEFAULT_IMAP_TCP_TIMEOUT);
+ }
+
+ if ((imap_timeout = ast_variable_retrieve(cfg, "general", "imapclosetimeout"))) {
+ mail_parameters(NIL, SET_CLOSETIMEOUT, (void *) (atol(imap_timeout)));
+ } else {
+ mail_parameters(NIL, SET_CLOSETIMEOUT, (void *) DEFAULT_IMAP_TCP_TIMEOUT);
+ }
+
+#endif
+ /* External voicemail notify application */
+
+ if ((notifystr = ast_variable_retrieve(cfg, "general", "externnotify"))) {
+ ast_copy_string(externnotify, notifystr, sizeof(externnotify));
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "found externnotify: %s\n", externnotify);
+ if (!strcasecmp(externnotify, "smdi")) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Using SMDI for external voicemail notification\n");
+ if ((smdistr = ast_variable_retrieve(cfg, "general", "smdiport"))) {
+ smdi_iface = ast_smdi_interface_find(smdistr);
+ } else {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "No SMDI interface set, trying default (/dev/ttyS0)\n");
+ smdi_iface = ast_smdi_interface_find("/dev/ttyS0");
+ }
+
+ if (!smdi_iface) {
+ ast_log(LOG_ERROR, "No valid SMDI interface specfied, disabling external voicemail notification\n");
+ externnotify[0] = '\0';
+ }
+ }
+ } else {
+ externnotify[0] = '\0';
+ }
+
+ /* Silence treshold */
+ silencethreshold = 256;
+ if ((thresholdstr = ast_variable_retrieve(cfg, "general", "silencethreshold")))
+ silencethreshold = atoi(thresholdstr);
+
+ if (!(astemail = ast_variable_retrieve(cfg, "general", "serveremail")))
+ astemail = ASTERISK_USERNAME;
+ ast_copy_string(serveremail, astemail, sizeof(serveremail));
+
+ vmmaxmessage = 0;
+ if ((s = ast_variable_retrieve(cfg, "general", "maxmessage"))) {
+ if (sscanf(s, "%d", &x) == 1) {
+ vmmaxmessage = x;
+ } else {
+ ast_log(LOG_WARNING, "Invalid max message time length\n");
+ }
+ }
+
+ vmminmessage = 0;
+ if ((s = ast_variable_retrieve(cfg, "general", "minmessage"))) {
+ if (sscanf(s, "%d", &x) == 1) {
+ vmminmessage = x;
+ if (maxsilence <= vmminmessage)
+ ast_log(LOG_WARNING, "maxsilence should be less than minmessage or you may get empty messages\n");
+ } else {
+ ast_log(LOG_WARNING, "Invalid min message time length\n");
+ }
+ }
+ fmt = ast_variable_retrieve(cfg, "general", "format");
+ if (!fmt)
+ fmt = "wav";
+ ast_copy_string(vmfmts, fmt, sizeof(vmfmts));
+
+ skipms = 3000;
+ if ((s = ast_variable_retrieve(cfg, "general", "maxgreet"))) {
+ if (sscanf(s, "%d", &x) == 1) {
+ maxgreet = x;
+ } else {
+ ast_log(LOG_WARNING, "Invalid max message greeting length\n");
+ }
+ }
+
+ if ((s = ast_variable_retrieve(cfg, "general", "skipms"))) {
+ if (sscanf(s, "%d", &x) == 1) {
+ skipms = x;
+ } else {
+ ast_log(LOG_WARNING, "Invalid skipms value\n");
+ }
+ }
+
+ maxlogins = 3;
+ if ((s = ast_variable_retrieve(cfg, "general", "maxlogins"))) {
+ if (sscanf(s, "%d", &x) == 1) {
+ maxlogins = x;
+ } else {
+ ast_log(LOG_WARNING, "Invalid max failed login attempts\n");
+ }
+ }
+
+ /* Force new user to record name ? */
+ if (!(astforcename = ast_variable_retrieve(cfg, "general", "forcename")))
+ astforcename = "no";
+ ast_set2_flag((&globalflags), ast_true(astforcename), VM_FORCENAME);
+
+ /* Force new user to record greetings ? */
+ if (!(astforcegreet = ast_variable_retrieve(cfg, "general", "forcegreetings")))
+ astforcegreet = "no";
+ ast_set2_flag((&globalflags), ast_true(astforcegreet), VM_FORCEGREET);
+
+ if ((s = ast_variable_retrieve(cfg, "general", "cidinternalcontexts"))){
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG,"VM_CID Internal context string: %s\n",s);
+ stringp = ast_strdupa(s);
+ for (x = 0 ; x < MAX_NUM_CID_CONTEXTS ; x++){
+ if (!ast_strlen_zero(stringp)) {
+ q = strsep(&stringp,",");
+ while ((*q == ' ')||(*q == '\t')) /* Eat white space between contexts */
+ q++;
+ ast_copy_string(cidinternalcontexts[x], q, sizeof(cidinternalcontexts[x]));
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG,"VM_CID Internal context %d: %s\n", x, cidinternalcontexts[x]);
+ } else {
+ cidinternalcontexts[x][0] = '\0';
+ }
+ }
+ }
+ if (!(astreview = ast_variable_retrieve(cfg, "general", "review"))){
+ if (option_debug)
+ ast_log(LOG_DEBUG,"VM Review Option disabled globally\n");
+ astreview = "no";
+ }
+ ast_set2_flag((&globalflags), ast_true(astreview), VM_REVIEW);
+
+ /*Temperary greeting reminder */
+ if (!(asttempgreetwarn = ast_variable_retrieve(cfg, "general", "tempgreetwarn"))) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "VM Temperary Greeting Reminder Option disabled globally\n");
+ asttempgreetwarn = "no";
+ } else {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "VM Temperary Greeting Reminder Option enabled globally\n");
+ }
+ ast_set2_flag((&globalflags), ast_true(asttempgreetwarn), VM_TEMPGREETWARN);
+
+ if (!(astcallop = ast_variable_retrieve(cfg, "general", "operator"))){
+ if (option_debug)
+ ast_log(LOG_DEBUG,"VM Operator break disabled globally\n");
+ astcallop = "no";
+ }
+ ast_set2_flag((&globalflags), ast_true(astcallop), VM_OPERATOR);
+
+ if (!(astsaycid = ast_variable_retrieve(cfg, "general", "saycid"))) {
+ if (option_debug)
+ ast_log(LOG_DEBUG,"VM CID Info before msg disabled globally\n");
+ astsaycid = "no";
+ }
+ ast_set2_flag((&globalflags), ast_true(astsaycid), VM_SAYCID);
+
+ if (!(send_voicemail = ast_variable_retrieve(cfg,"general", "sendvoicemail"))){
+ if (option_debug)
+ ast_log(LOG_DEBUG,"Send Voicemail msg disabled globally\n");
+ send_voicemail = "no";
+ }
+ ast_set2_flag((&globalflags), ast_true(send_voicemail), VM_SVMAIL);
+
+ if (!(asthearenv = ast_variable_retrieve(cfg, "general", "envelope"))) {
+ if (option_debug)
+ ast_log(LOG_DEBUG,"ENVELOPE before msg enabled globally\n");
+ asthearenv = "yes";
+ }
+ ast_set2_flag((&globalflags), ast_true(asthearenv), VM_ENVELOPE);
+
+ if (!(astsaydurationinfo = ast_variable_retrieve(cfg, "general", "sayduration"))) {
+ if (option_debug)
+ ast_log(LOG_DEBUG,"Duration info before msg enabled globally\n");
+ astsaydurationinfo = "yes";
+ }
+ ast_set2_flag((&globalflags), ast_true(astsaydurationinfo), VM_SAYDURATION);
+
+ saydurationminfo = 2;
+ if ((astsaydurationminfo = ast_variable_retrieve(cfg, "general", "saydurationm"))) {
+ if (sscanf(astsaydurationminfo, "%d", &x) == 1) {
+ saydurationminfo = x;
+ } else {
+ ast_log(LOG_WARNING, "Invalid min duration for say duration\n");
+ }
+ }
+
+ if (!(astskipcmd = ast_variable_retrieve(cfg, "general", "nextaftercmd"))) {
+ if (option_debug)
+ ast_log(LOG_DEBUG,"We are not going to skip to the next msg after save/delete\n");
+ astskipcmd = "no";
+ }
+ ast_set2_flag((&globalflags), ast_true(astskipcmd), VM_SKIPAFTERCMD);
+
+ if ((dialoutcxt = ast_variable_retrieve(cfg, "general", "dialout"))) {
+ ast_copy_string(dialcontext, dialoutcxt, sizeof(dialcontext));
+ if (option_debug)
+ ast_log(LOG_DEBUG, "found dialout context: %s\n", dialcontext);
+ } else {
+ dialcontext[0] = '\0';
+ }
+
+ if ((callbackcxt = ast_variable_retrieve(cfg, "general", "callback"))) {
+ ast_copy_string(callcontext, callbackcxt, sizeof(callcontext));
+ if (option_debug)
+ ast_log(LOG_DEBUG, "found callback context: %s\n", callcontext);
+ } else {
+ callcontext[0] = '\0';
+ }
+
+ if ((exitcxt = ast_variable_retrieve(cfg, "general", "exitcontext"))) {
+ ast_copy_string(exitcontext, exitcxt, sizeof(exitcontext));
+ if (option_debug)
+ ast_log(LOG_DEBUG, "found operator context: %s\n", exitcontext);
+ } else {
+ exitcontext[0] = '\0';
+ }
+
+ if (!(astdirfwd = ast_variable_retrieve(cfg, "general", "usedirectory")))
+ astdirfwd = "no";
+ ast_set2_flag((&globalflags), ast_true(astdirfwd), VM_DIRECFORWARD);
+ if ((ucfg = ast_config_load("users.conf"))) {
+ for (cat = ast_category_browse(ucfg, NULL); cat ; cat = ast_category_browse(ucfg, cat)) {
+ if (!ast_true(ast_config_option(ucfg, cat, "hasvoicemail")))
+ continue;
+ if ((cur = find_or_create(userscontext, cat))) {
+ populate_defaults(cur);
+ apply_options_full(cur, ast_variable_browse(ucfg, cat));
+ ast_copy_string(cur->context, userscontext, sizeof(cur->context));
+ }
+ }
+ ast_config_destroy(ucfg);
+ }
+ cat = ast_category_browse(cfg, NULL);
+ while (cat) {
+ if (strcasecmp(cat, "general")) {
+ var = ast_variable_browse(cfg, cat);
+ if (strcasecmp(cat, "zonemessages")) {
+ /* Process mailboxes in this context */
+ while (var) {
+ append_mailbox(cat, var->name, var->value);
+ var = var->next;
+ }
+ } else {
+ /* Timezones in this context */
+ while (var) {
+ struct vm_zone *z;
+ if ((z = ast_malloc(sizeof(*z)))) {
+ char *msg_format, *timezone;
+ msg_format = ast_strdupa(var->value);
+ timezone = strsep(&msg_format, "|");
+ if (msg_format) {
+ ast_copy_string(z->name, var->name, sizeof(z->name));
+ ast_copy_string(z->timezone, timezone, sizeof(z->timezone));
+ ast_copy_string(z->msg_format, msg_format, sizeof(z->msg_format));
+ AST_LIST_LOCK(&zones);
+ AST_LIST_INSERT_HEAD(&zones, z, list);
+ AST_LIST_UNLOCK(&zones);
+ } else {
+ ast_log(LOG_WARNING, "Invalid timezone definition at line %d\n", var->lineno);
+ free(z);
+ }
+ } else {
+ free(z);
+ AST_LIST_UNLOCK(&users);
+ ast_config_destroy(cfg);
+ return -1;
+ }
+ var = var->next;
+ }
+ }
+ }
+ cat = ast_category_browse(cfg, cat);
+ }
+ memset(fromstring,0,sizeof(fromstring));
+ memset(pagerfromstring,0,sizeof(pagerfromstring));
+ memset(emailtitle,0,sizeof(emailtitle));
+ strcpy(charset, "ISO-8859-1");
+ if (emailbody) {
+ free(emailbody);
+ emailbody = NULL;
+ }
+ if (emailsubject) {
+ free(emailsubject);
+ emailsubject = NULL;
+ }
+ if (pagerbody) {
+ free(pagerbody);
+ pagerbody = NULL;
+ }
+ if (pagersubject) {
+ free(pagersubject);
+ pagersubject = NULL;
+ }
+ if ((s = ast_variable_retrieve(cfg, "general", "pbxskip")))
+ ast_set2_flag((&globalflags), ast_true(s), VM_PBXSKIP);
+ if ((s = ast_variable_retrieve(cfg, "general", "fromstring")))
+ ast_copy_string(fromstring,s,sizeof(fromstring));
+ if ((s = ast_variable_retrieve(cfg, "general", "pagerfromstring")))
+ ast_copy_string(pagerfromstring,s,sizeof(pagerfromstring));
+ if ((s = ast_variable_retrieve(cfg, "general", "charset")))
+ ast_copy_string(charset,s,sizeof(charset));
+ if ((s = ast_variable_retrieve(cfg, "general", "adsifdn"))) {
+ sscanf(s, "%2x%2x%2x%2x", &tmpadsi[0], &tmpadsi[1], &tmpadsi[2], &tmpadsi[3]);
+ for (x = 0; x < 4; x++) {
+ memcpy(&adsifdn[x], &tmpadsi[x], 1);
+ }
+ }
+ if ((s = ast_variable_retrieve(cfg, "general", "adsisec"))) {
+ sscanf(s, "%2x%2x%2x%2x", &tmpadsi[0], &tmpadsi[1], &tmpadsi[2], &tmpadsi[3]);
+ for (x = 0; x < 4; x++) {
+ memcpy(&adsisec[x], &tmpadsi[x], 1);
+ }
+ }
+ if ((s = ast_variable_retrieve(cfg, "general", "adsiver")))
+ if (atoi(s)) {
+ adsiver = atoi(s);
+ }
+ if ((s = ast_variable_retrieve(cfg, "general", "emailtitle"))) {
+ ast_log(LOG_NOTICE, "Keyword 'emailtitle' is DEPRECATED, please use 'emailsubject' instead.\n");
+ ast_copy_string(emailtitle,s,sizeof(emailtitle));
+ }
+ if ((s = ast_variable_retrieve(cfg, "general", "emailsubject")))
+ emailsubject = ast_strdup(s);
+ if ((s = ast_variable_retrieve(cfg, "general", "emailbody"))) {
+ char *tmpread, *tmpwrite;
+ emailbody = ast_strdup(s);
+
+ /* substitute strings \t and \n into the appropriate characters */
+ tmpread = tmpwrite = emailbody;
+ while ((tmpwrite = strchr(tmpread,'\\'))) {
+ switch (tmpwrite[1]) {
+ case 'r':
+ memmove(tmpwrite + 1, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
+ *tmpwrite = '\r';
+ break;
+ case 'n':
+ memmove(tmpwrite + 1, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
+ *tmpwrite = '\n';
+ break;
+ case 't':
+ memmove(tmpwrite + 1, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
+ *tmpwrite = '\t';
+ break;
+ default:
+ ast_log(LOG_NOTICE, "Substitution routine does not support this character: %c\n", tmpwrite[1]);
+ }
+ tmpread = tmpwrite + 1;
+ }
+ }
+ if ((s = ast_variable_retrieve(cfg, "general", "tz"))) {
+ ast_copy_string(zonetag, s, sizeof(zonetag));
+ }
+ if ((s = ast_variable_retrieve(cfg, "general", "pagersubject")))
+ pagersubject = ast_strdup(s);
+ if ((s = ast_variable_retrieve(cfg, "general", "pagerbody"))) {
+ char *tmpread, *tmpwrite;
+ pagerbody = ast_strdup(s);
+
+ /* substitute strings \t and \n into the appropriate characters */
+ tmpread = tmpwrite = pagerbody;
+ while ((tmpwrite = strchr(tmpread, '\\'))) {
+ switch (tmpwrite[1]) {
+ case 'r':
+ memmove(tmpwrite + 1, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
+ *tmpwrite = '\r';
+ break;
+ case 'n':
+ memmove(tmpwrite + 1, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
+ *tmpwrite = '\n';
+ break;
+ case 't':
+ memmove(tmpwrite + 1, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
+ *tmpwrite = '\t';
+ break;
+ default:
+ ast_log(LOG_NOTICE, "Substitution routine does not support this character: %c\n", tmpwrite[1]);
+ }
+ tmpread = tmpwrite + 1;
+ }
+ }
+ AST_LIST_UNLOCK(&users);
+ ast_config_destroy(cfg);
+ return 0;
+ } else {
+ AST_LIST_UNLOCK(&users);
+ ast_log(LOG_WARNING, "Failed to load configuration file.\n");
+ return 0;
+ }
+}
+
+static int reload(void)
+{
+ return(load_config());
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+ res |= ast_unregister_application(app2);
+ res |= ast_unregister_application(app3);
+ res |= ast_unregister_application(app4);
+ ast_cli_unregister_multiple(cli_voicemail, sizeof(cli_voicemail) / sizeof(struct ast_cli_entry));
+ ast_uninstall_vm_functions();
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ int res;
+ char *adsi_loaded = ast_module_helper("", "res_adsi.so", 0, 0, 0, 0);
+ free(adsi_loaded);
+ if (!adsi_loaded) {
+ /* If embedded, res_adsi may be known as "res_adsi" not "res_adsi.so" */
+ adsi_loaded = ast_module_helper("", "res_adsi", 0, 0, 0, 0);
+ ast_free(adsi_loaded);
+ if (!adsi_loaded) {
+ ast_log(LOG_ERROR, "app_voicemail.so depends upon res_adsi.so\n");
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ }
+
+ my_umask = umask(0);
+ umask(my_umask);
+ res = ast_register_application(app, vm_exec, synopsis_vm, descrip_vm);
+ res |= ast_register_application(app2, vm_execmain, synopsis_vmain, descrip_vmain);
+ res |= ast_register_application(app3, vm_box_exists, synopsis_vm_box_exists, descrip_vm_box_exists);
+ res |= ast_register_application(app4, vmauthenticate, synopsis_vmauthenticate, descrip_vmauthenticate);
+ if (res)
+ return(res);
+
+ if ((res=load_config())) {
+ return(res);
+ }
+
+ ast_cli_register_multiple(cli_voicemail, sizeof(cli_voicemail) / sizeof(struct ast_cli_entry));
+
+ /* compute the location of the voicemail spool directory */
+ snprintf(VM_SPOOL_DIR, sizeof(VM_SPOOL_DIR), "%s/voicemail/", ast_config_AST_SPOOL_DIR);
+
+ ast_install_vm_functions(has_voicemail, inboxcount, messagecount);
+
+ return res;
+}
+
+static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context)
+{
+ int cmd = 0;
+ char destination[80] = "";
+ int retries = 0;
+
+ if (!num) {
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "Destination number will be entered manually\n");
+ while (retries < 3 && cmd != 't') {
+ destination[1] = '\0';
+ destination[0] = cmd = ast_play_and_wait(chan,"vm-enter-num-to-call");
+ if (!cmd)
+ destination[0] = cmd = ast_play_and_wait(chan, "vm-then-pound");
+ if (!cmd)
+ destination[0] = cmd = ast_play_and_wait(chan, "vm-star-cancel");
+ if (!cmd) {
+ cmd = ast_waitfordigit(chan, 6000);
+ if (cmd)
+ destination[0] = cmd;
+ }
+ if (!cmd) {
+ retries++;
+ } else {
+
+ if (cmd < 0)
+ return 0;
+ if (cmd == '*') {
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "User hit '*' to cancel outgoing call\n");
+ return 0;
+ }
+ if ((cmd = ast_readstring(chan,destination + strlen(destination),sizeof(destination)-1,6000,10000,"#")) < 0)
+ retries++;
+ else
+ cmd = 't';
+ }
+ }
+ if (retries >= 3) {
+ return 0;
+ }
+
+ } else {
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "Destination number is CID number '%s'\n", num);
+ ast_copy_string(destination, num, sizeof(destination));
+ }
+
+ if (!ast_strlen_zero(destination)) {
+ if (destination[strlen(destination) -1 ] == '*')
+ return 0;
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "Placing outgoing call to extension '%s' in context '%s' from context '%s'\n", destination, outgoing_context, chan->context);
+ ast_copy_string(chan->exten, destination, sizeof(chan->exten));
+ ast_copy_string(chan->context, outgoing_context, sizeof(chan->context));
+ chan->priority = 0;
+ return 9;
+ }
+ return 0;
+}
+
+static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msg, int option, signed char record_gain)
+{
+ int res = 0;
+ char filename[PATH_MAX];
+ struct ast_config *msg_cfg = NULL;
+ const char *origtime, *context;
+ char *cid, *name, *num;
+ int retries = 0;
+
+ vms->starting = 0;
+ make_file(vms->fn, sizeof(vms->fn), vms->curdir, msg);
+
+ /* Retrieve info from VM attribute file */
+ make_file(vms->fn2, sizeof(vms->fn2), vms->curdir, vms->curmsg);
+ snprintf(filename,sizeof(filename), "%s.txt", vms->fn2);
+ RETRIEVE(vms->curdir, vms->curmsg, vmu);
+ msg_cfg = ast_config_load(filename);
+ DISPOSE(vms->curdir, vms->curmsg);
+ if (!msg_cfg) {
+ ast_log(LOG_WARNING, "No message attribute file?!! (%s)\n", filename);
+ return 0;
+ }
+
+ if (!(origtime = ast_variable_retrieve(msg_cfg, "message", "origtime"))) {
+ ast_config_destroy(msg_cfg);
+ return 0;
+ }
+
+ cid = ast_strdupa(ast_variable_retrieve(msg_cfg, "message", "callerid"));
+
+ context = ast_variable_retrieve(msg_cfg, "message", "context");
+ if (!strncasecmp("macro",context,5)) /* Macro names in contexts are useless for our needs */
+ context = ast_variable_retrieve(msg_cfg, "message","macrocontext");
+ switch (option) {
+ case 3:
+ if (!res)
+ res = play_message_datetime(chan, vmu, origtime, filename);
+ if (!res)
+ res = play_message_callerid(chan, vms, cid, context, 0);
+
+ res = 't';
+ break;
+
+ case 2: /* Call back */
+
+ if (ast_strlen_zero(cid))
+ break;
+
+ ast_callerid_parse(cid, &name, &num);
+ while ((res > -1) && (res != 't')) {
+ switch (res) {
+ case '1':
+ if (num) {
+ /* Dial the CID number */
+ res = dialout(chan, vmu, num, vmu->callback);
+ if (res) {
+ ast_config_destroy(msg_cfg);
+ return 9;
+ }
+ } else {
+ res = '2';
+ }
+ break;
+
+ case '2':
+ /* Want to enter a different number, can only do this if there's a dialout context for this user */
+ if (!ast_strlen_zero(vmu->dialout)) {
+ res = dialout(chan, vmu, NULL, vmu->dialout);
+ if (res) {
+ ast_config_destroy(msg_cfg);
+ return 9;
+ }
+ } else {
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "Caller can not specify callback number - no dialout context available\n");
+ res = ast_play_and_wait(chan, "vm-sorry");
+ }
+ ast_config_destroy(msg_cfg);
+ return res;
+ case '*':
+ res = 't';
+ break;
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '0':
+
+ res = ast_play_and_wait(chan, "vm-sorry");
+ retries++;
+ break;
+ default:
+ if (num) {
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "Confirm CID number '%s' is number to use for callback\n", num);
+ res = ast_play_and_wait(chan, "vm-num-i-have");
+ if (!res)
+ res = play_message_callerid(chan, vms, num, vmu->context, 1);
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-tocallnum");
+ /* Only prompt for a caller-specified number if there is a dialout context specified */
+ if (!ast_strlen_zero(vmu->dialout)) {
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-calldiffnum");
+ }
+ } else {
+ res = ast_play_and_wait(chan, "vm-nonumber");
+ if (!ast_strlen_zero(vmu->dialout)) {
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-toenternumber");
+ }
+ }
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-star-cancel");
+ if (!res)
+ res = ast_waitfordigit(chan, 6000);
+ if (!res) {
+ retries++;
+ if (retries > 3)
+ res = 't';
+ }
+ break;
+
+ }
+ if (res == 't')
+ res = 0;
+ else if (res == '*')
+ res = -1;
+ }
+ break;
+
+ case 1: /* Reply */
+ /* Send reply directly to sender */
+ if (ast_strlen_zero(cid))
+ break;
+
+ ast_callerid_parse(cid, &name, &num);
+ if (!num) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "No CID number available, no reply sent\n");
+ if (!res)
+ res = ast_play_and_wait(chan, "vm-nonumber");
+ ast_config_destroy(msg_cfg);
+ return res;
+ } else {
+ struct ast_vm_user vmu2;
+ if (find_user(&vmu2, vmu->context, num)) {
+ struct leave_vm_options leave_options;
+ char mailbox[AST_MAX_EXTENSION * 2 + 2];
+ snprintf(mailbox, sizeof(mailbox), "%s@%s", num, vmu->context);
+
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Leaving voicemail for '%s' in context '%s'\n", num, vmu->context);
+
+ memset(&leave_options, 0, sizeof(leave_options));
+ leave_options.record_gain = record_gain;
+ res = leave_voicemail(chan, mailbox, &leave_options);
+ if (!res)
+ res = 't';
+ ast_config_destroy(msg_cfg);
+ return res;
+ } else {
+ /* Sender has no mailbox, can't reply */
+ if (option_verbose > 2)
+ ast_verbose( VERBOSE_PREFIX_3 "No mailbox number '%s' in context '%s', no reply sent\n", num, vmu->context);
+ ast_play_and_wait(chan, "vm-nobox");
+ res = 't';
+ ast_config_destroy(msg_cfg);
+ return res;
+ }
+ }
+ res = 0;
+
+ break;
+ }
+
+#ifndef IMAP_STORAGE
+ ast_config_destroy(msg_cfg);
+
+ if (!res) {
+ make_file(vms->fn, sizeof(vms->fn), vms->curdir, msg);
+ vms->heard[msg] = 1;
+ res = wait_file(chan, vms, vms->fn);
+ }
+#endif
+ return res;
+}
+
+static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt,
+ int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir,
+ signed char record_gain, struct vm_state *vms)
+{
+ /* Record message & let caller review or re-record it, or set options if applicable */
+ int res = 0;
+ int cmd = 0;
+ int max_attempts = 3;
+ int attempts = 0;
+ int recorded = 0;
+ int message_exists = 0;
+ signed char zero_gain = 0;
+ char tempfile[PATH_MAX];
+ char *acceptdtmf = "#";
+ char *canceldtmf = "";
+
+ /* Note that urgent and private are for flagging messages as such in the future */
+
+ /* barf if no pointer passed to store duration in */
+ if (duration == NULL) {
+ ast_log(LOG_WARNING, "Error play_record_review called without duration pointer\n");
+ return -1;
+ }
+
+ if (!outsidecaller)
+ snprintf(tempfile, sizeof(tempfile), "%s.tmp", recordfile);
+ else
+ ast_copy_string(tempfile, recordfile, sizeof(tempfile));
+
+ cmd = '3'; /* Want to start by recording */
+
+ while ((cmd >= 0) && (cmd != 't')) {
+ switch (cmd) {
+ case '1':
+ if (!message_exists) {
+ /* In this case, 1 is to record a message */
+ cmd = '3';
+ break;
+ } else {
+ /* Otherwise 1 is to save the existing message */
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Saving message as is\n");
+ if (!outsidecaller)
+ ast_filerename(tempfile, recordfile, NULL);
+ ast_stream_and_wait(chan, "vm-msgsaved", chan->language, "");
+ if (!outsidecaller) {
+ STORE(recordfile, vmu->mailbox, vmu->context, -1, chan, vmu, fmt, *duration, vms);
+ DISPOSE(recordfile, -1);
+ }
+ cmd = 't';
+ return res;
+ }
+ case '2':
+ /* Review */
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Reviewing the message\n");
+ cmd = ast_stream_and_wait(chan, tempfile, chan->language, AST_DIGIT_ANY);
+ break;
+ case '3':
+ message_exists = 0;
+ /* Record */
+ if (recorded == 1) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Re-recording the message\n");
+ } else {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Recording the message\n");
+ }
+ if (recorded && outsidecaller) {
+ cmd = ast_play_and_wait(chan, INTRO);
+ cmd = ast_play_and_wait(chan, "beep");
+ }
+ recorded = 1;
+ /* After an attempt has been made to record message, we have to take care of INTRO and beep for incoming messages, but not for greetings */
+ if (record_gain)
+ ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0);
+ if (ast_test_flag(vmu, VM_OPERATOR))
+ canceldtmf = "0";
+ cmd = ast_play_and_record_full(chan, playfile, tempfile, maxtime, fmt, duration, silencethreshold, maxsilence, unlockdir, acceptdtmf, canceldtmf);
+ if (record_gain)
+ ast_channel_setoption(chan, AST_OPTION_RXGAIN, &zero_gain, sizeof(zero_gain), 0);
+ if (cmd == -1) {
+ /* User has hung up, no options to give */
+ if (!outsidecaller) {
+ /* user was recording a greeting and they hung up, so let's delete the recording. */
+ ast_filedelete(tempfile, NULL);
+ }
+ return cmd;
+ }
+ if (cmd == '0') {
+ break;
+ } else if (cmd == '*') {
+ break;
+ }
+#if 0
+ else if (vmu->review && (*duration < 5)) {
+ /* Message is too short */
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Message too short\n");
+ cmd = ast_play_and_wait(chan, "vm-tooshort");
+ cmd = ast_filedelete(tempfile, NULL);
+ break;
+ }
+ else if (vmu->review && (cmd == 2 && *duration < (maxsilence + 3))) {
+ /* Message is all silence */
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Nothing recorded\n");
+ cmd = ast_filedelete(tempfile, NULL);
+ cmd = ast_play_and_wait(chan, "vm-nothingrecorded");
+ if (!cmd)
+ cmd = ast_play_and_wait(chan, "vm-speakup");
+ break;
+ }
+#endif
+ else {
+ /* If all is well, a message exists */
+ message_exists = 1;
+ cmd = 0;
+ }
+ break;
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '*':
+ case '#':
+ cmd = ast_play_and_wait(chan, "vm-sorry");
+ break;
+#if 0
+/* XXX Commented out for the moment because of the dangers of deleting
+ a message while recording (can put the message numbers out of sync) */
+ case '*':
+ /* Cancel recording, delete message, offer to take another message*/
+ cmd = ast_play_and_wait(chan, "vm-deleted");
+ cmd = ast_filedelete(tempfile, NULL);
+ if (outsidecaller) {
+ res = vm_exec(chan, NULL);
+ return res;
+ }
+ else
+ return 1;
+#endif
+ case '0':
+ if (!ast_test_flag(vmu, VM_OPERATOR)) {
+ cmd = ast_play_and_wait(chan, "vm-sorry");
+ break;
+ }
+ if (message_exists || recorded) {
+ cmd = ast_play_and_wait(chan, "vm-saveoper");
+ if (!cmd)
+ cmd = ast_waitfordigit(chan, 3000);
+ if (cmd == '1') {
+ ast_play_and_wait(chan, "vm-msgsaved");
+ cmd = '0';
+ } else {
+ ast_play_and_wait(chan, "vm-deleted");
+ DELETE(recordfile, -1, recordfile, vmu);
+ cmd = '0';
+ }
+ }
+ return cmd;
+ default:
+ /* If the caller is an ouside caller, and the review option is enabled,
+ allow them to review the message, but let the owner of the box review
+ their OGM's */
+ if (outsidecaller && !ast_test_flag(vmu, VM_REVIEW))
+ return cmd;
+ if (message_exists) {
+ cmd = ast_play_and_wait(chan, "vm-review");
+ }
+ else {
+ cmd = ast_play_and_wait(chan, "vm-torerecord");
+ if (!cmd)
+ cmd = ast_waitfordigit(chan, 600);
+ }
+
+ if (!cmd && outsidecaller && ast_test_flag(vmu, VM_OPERATOR)) {
+ cmd = ast_play_and_wait(chan, "vm-reachoper");
+ if (!cmd)
+ cmd = ast_waitfordigit(chan, 600);
+ }
+#if 0
+ if (!cmd)
+ cmd = ast_play_and_wait(chan, "vm-tocancelmsg");
+#endif
+ if (!cmd)
+ cmd = ast_waitfordigit(chan, 6000);
+ if (!cmd) {
+ attempts++;
+ }
+ if (attempts > max_attempts) {
+ cmd = 't';
+ }
+ }
+ }
+ if (outsidecaller)
+ ast_play_and_wait(chan, "vm-goodbye");
+ if (cmd == 't')
+ cmd = 0;
+ return cmd;
+}
+
+/* This is a workaround so that menuselect displays a proper description
+ * AST_MODULE_INFO(, , "Comedian Mail (Voicemail System)"
+ */
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, tdesc,
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ );
diff --git a/apps/app_waitforring.c b/apps/app_waitforring.c
new file mode 100644
index 000000000..a4f69ae77
--- /dev/null
+++ b/apps/app_waitforring.c
@@ -0,0 +1,136 @@
+/*
+ * 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 Wait for Ring Application
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/options.h"
+#include "asterisk/lock.h"
+
+static char *synopsis = "Wait for Ring Application";
+
+static char *desc = " WaitForRing(timeout)\n"
+"Returns 0 after waiting at least timeout seconds. and\n"
+"only after the next ring has completed. Returns 0 on\n"
+"success or -1 on hangup\n";
+
+static char *app = "WaitForRing";
+
+
+static int waitforring_exec(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *u;
+ struct ast_frame *f;
+ int res = 0;
+ int ms;
+
+ if (!data || (sscanf(data, "%d", &ms) != 1)) {
+ ast_log(LOG_WARNING, "WaitForRing requires an argument (minimum seconds)\n");
+ return 0;
+ }
+
+ u = ast_module_user_add(chan);
+
+ ms *= 1000;
+ while(ms > 0) {
+ ms = ast_waitfor(chan, ms);
+ if (ms < 0) {
+ res = ms;
+ break;
+ }
+ if (ms > 0) {
+ f = ast_read(chan);
+ if (!f) {
+ res = -1;
+ break;
+ }
+ if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_RING)) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Got a ring but still waiting for timeout\n");
+ }
+ ast_frfree(f);
+ }
+ }
+ /* Now we're really ready for the ring */
+ if (!res) {
+ ms = 99999999;
+ while(ms > 0) {
+ ms = ast_waitfor(chan, ms);
+ if (ms < 0) {
+ res = ms;
+ break;
+ }
+ if (ms > 0) {
+ f = ast_read(chan);
+ if (!f) {
+ res = -1;
+ break;
+ }
+ if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_RING)) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Got a ring after the timeout\n");
+ ast_frfree(f);
+ break;
+ }
+ ast_frfree(f);
+ }
+ }
+ }
+ ast_module_user_remove(u);
+
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, waitforring_exec, synopsis, desc);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Waits until first ring after time");
diff --git a/apps/app_waitforsilence.c b/apps/app_waitforsilence.c
new file mode 100644
index 000000000..40435fb5c
--- /dev/null
+++ b/apps/app_waitforsilence.c
@@ -0,0 +1,207 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * WaitForSilence Application by David C. Troy <dave@popvox.com>
+ * Version 1.11 2006-06-29
+ *
+ * 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 Wait for Silence
+ * - Waits for up to 'x' milliseconds of silence, 'y' times \n
+ * - WaitForSilence(500,2) will wait for 1/2 second of silence, twice \n
+ * - WaitForSilence(1000,1) will wait for 1 second of silence, once \n
+ * - WaitForSilence(300,3,10) will wait for 300ms of silence, 3 times, and return after 10sec \n
+ *
+ * \author David C. Troy <dave@popvox.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/dsp.h"
+#include "asterisk/module.h"
+#include "asterisk/options.h"
+
+static char *app = "WaitForSilence";
+static char *synopsis = "Waits for a specified amount of silence";
+static char *descrip =
+" WaitForSilence(silencerequired[|iterations][|timeout]) \n"
+"Wait for Silence: Waits for up to 'silencerequired' \n"
+"milliseconds of silence, 'iterations' times or once if omitted.\n"
+"An optional timeout specified the number of seconds to return\n"
+"after, even if we do not receive the specified amount of silence.\n"
+"Use 'timeout' with caution, as it may defeat the purpose of this\n"
+"application, which is to wait indefinitely until silence is detected\n"
+"on the line. This is particularly useful for reverse-911-type\n"
+"call broadcast applications where you need to wait for an answering\n"
+"machine to complete its spiel before playing a message.\n"
+"The timeout parameter is specified only to avoid an infinite loop in\n"
+"cases where silence is never achieved. Typically you will want to\n"
+"include two or more calls to WaitForSilence when dealing with an answering\n"
+"machine; first waiting for the spiel to finish, then waiting for the beep, etc.\n\n"
+ "Examples:\n"
+" - WaitForSilence(500|2) will wait for 1/2 second of silence, twice\n"
+" - WaitForSilence(1000) will wait for 1 second of silence, once\n"
+" - WaitForSilence(300|3|10) will wait for 300ms silence, 3 times,\n"
+" and returns after 10 sec, even if silence is not detected\n\n"
+"Sets the channel variable WAITSTATUS with to one of these values:\n"
+"SILENCE - if exited with silence detected\n"
+"TIMEOUT - if exited without silence detected after timeout\n";
+
+static int do_waiting(struct ast_channel *chan, int silencereqd, time_t waitstart, int timeout) {
+ struct ast_frame *f;
+ int dspsilence = 0;
+ static int silencethreshold = 128;
+ int rfmt = 0;
+ int res = 0;
+ struct ast_dsp *sildet; /* silence detector dsp */
+ time_t now;
+
+ rfmt = chan->readformat; /* Set to linear mode */
+ res = ast_set_read_format(chan, AST_FORMAT_SLINEAR);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to set channel to linear mode, giving up\n");
+ return -1;
+ }
+
+ sildet = ast_dsp_new(); /* Create the silence detector */
+ if (!sildet) {
+ ast_log(LOG_WARNING, "Unable to create silence detector :(\n");
+ return -1;
+ }
+ ast_dsp_set_threshold(sildet, silencethreshold);
+
+ /* Await silence... */
+ f = NULL;
+ for(;;) {
+ /* Start with no silence received */
+ dspsilence = 0;
+
+ res = ast_waitfor(chan, silencereqd);
+
+ /* Must have gotten a hangup; let's exit */
+ if (res < 0) {
+ f = NULL;
+ break;
+ }
+
+ /* We waited and got no frame; sounds like digital silence or a muted digital channel */
+ if (res == 0) {
+ dspsilence = silencereqd;
+ } else {
+ /* Looks like we did get a frame, so let's check it out */
+ f = ast_read(chan);
+ if (!f)
+ break;
+ if (f && f->frametype == AST_FRAME_VOICE) {
+ ast_dsp_silence(sildet, f, &dspsilence);
+ }
+ if (f) {
+ ast_frfree(f);
+ }
+ }
+
+ if (option_verbose > 6)
+ ast_verbose(VERBOSE_PREFIX_3 "Got %dms silence< %dms required\n", dspsilence, silencereqd);
+
+ if (dspsilence >= silencereqd) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Exiting with %dms silence >= %dms required\n", dspsilence, silencereqd);
+ /* Ended happily with silence */
+ res = 1;
+ pbx_builtin_setvar_helper(chan, "WAITSTATUS", "SILENCE");
+ ast_log(LOG_DEBUG, "WAITSTATUS was set to SILENCE\n");
+ break;
+ }
+
+ if ( timeout && (difftime(time(&now),waitstart) >= timeout) ) {
+ pbx_builtin_setvar_helper(chan, "WAITSTATUS", "TIMEOUT");
+ ast_log(LOG_DEBUG, "WAITSTATUS was set to TIMEOUT\n");
+ res = 0;
+ break;
+ }
+ }
+
+
+ if (rfmt && ast_set_read_format(chan, rfmt)) {
+ ast_log(LOG_WARNING, "Unable to restore format %s to channel '%s'\n", ast_getformatname(rfmt), chan->name);
+ }
+ ast_dsp_free(sildet);
+ return res;
+}
+
+static int waitforsilence_exec(struct ast_channel *chan, void *data)
+{
+ int res = 1;
+ int silencereqd = 1000;
+ int timeout = 0;
+ int iterations = 1, i;
+ time_t waitstart;
+
+ res = ast_answer(chan); /* Answer the channel */
+
+ if (!data || ( (sscanf(data, "%d|%d|%d", &silencereqd, &iterations, &timeout) != 3) &&
+ (sscanf(data, "%d|%d", &silencereqd, &iterations) != 2) &&
+ (sscanf(data, "%d", &silencereqd) != 1) ) ) {
+ ast_log(LOG_WARNING, "Using default value of 1000ms, 1 iteration, no timeout\n");
+ }
+
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Waiting %d time(s) for %d ms silence with %d timeout\n", iterations, silencereqd, timeout);
+
+ time(&waitstart);
+ res = 1;
+ for (i=0; (i<iterations) && (res == 1); i++) {
+ res = do_waiting(chan, silencereqd, waitstart, timeout);
+ }
+ if (res > 0)
+ res = 0;
+ return res;
+}
+
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, waitforsilence_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Wait For Silence");
+
diff --git a/apps/app_while.c b/apps/app_while.c
new file mode 100644
index 000000000..8e8fabd5b
--- /dev/null
+++ b/apps/app_while.c
@@ -0,0 +1,339 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright 2004 - 2005, Anthony Minessale <anthmct@yahoo.com>
+ *
+ * Anthony Minessale <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 While Loop Implementation
+ *
+ * \author Anthony Minessale <anthmct@yahoo.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/utils.h"
+#include "asterisk/config.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/lock.h"
+#include "asterisk/options.h"
+
+#define ALL_DONE(u,ret) {ast_module_user_remove(u); return ret;}
+
+
+static char *start_app = "While";
+static char *start_desc =
+"Usage: While(<expr>)\n"
+"Start a While Loop. Execution will return to this point when\n"
+"EndWhile is called until expr is no longer true.\n";
+
+static char *start_synopsis = "Start a while loop";
+
+
+static char *stop_app = "EndWhile";
+static char *stop_desc =
+"Usage: EndWhile()\n"
+"Return to the previous called While\n";
+
+static char *stop_synopsis = "End a while loop";
+
+static char *exit_app = "ExitWhile";
+static char *exit_desc =
+"Usage: ExitWhile()\n"
+"Exits a While loop, whether or not the conditional has been satisfied.\n";
+static char *exit_synopsis = "End a While loop";
+
+static char *continue_app = "ContinueWhile";
+static char *continue_desc =
+"Usage: ContinueWhile()\n"
+"Returns to the top of the while loop and re-evaluates the conditional.\n";
+static char *continue_synopsis = "Restart a While loop";
+
+#define VAR_SIZE 64
+
+
+static const char *get_index(struct ast_channel *chan, const char *prefix, int index) {
+ char varname[VAR_SIZE];
+
+ snprintf(varname, VAR_SIZE, "%s_%d", prefix, index);
+ return pbx_builtin_getvar_helper(chan, varname);
+}
+
+static struct ast_exten *find_matching_priority(struct ast_context *c, const char *exten, int priority, const char *callerid)
+{
+ struct ast_exten *e;
+ struct ast_include *i;
+ struct ast_context *c2;
+
+ for (e=ast_walk_context_extensions(c, NULL); e; e=ast_walk_context_extensions(c, e)) {
+ if (ast_extension_match(ast_get_extension_name(e), exten)) {
+ int needmatch = ast_get_extension_matchcid(e);
+ if ((needmatch && ast_extension_match(ast_get_extension_cidmatch(e), callerid)) ||
+ (!needmatch)) {
+ /* This is the matching extension we want */
+ struct ast_exten *p;
+ for (p=ast_walk_extension_priorities(e, NULL); p; p=ast_walk_extension_priorities(e, p)) {
+ if (priority != ast_get_extension_priority(p))
+ continue;
+ return p;
+ }
+ }
+ }
+ }
+
+ /* No match; run through includes */
+ for (i=ast_walk_context_includes(c, NULL); i; i=ast_walk_context_includes(c, i)) {
+ for (c2=ast_walk_contexts(NULL); c2; c2=ast_walk_contexts(c2)) {
+ if (!strcmp(ast_get_context_name(c2), ast_get_include_name(i))) {
+ e = find_matching_priority(c2, exten, priority, callerid);
+ if (e)
+ return e;
+ }
+ }
+ }
+ return NULL;
+}
+
+static int find_matching_endwhile(struct ast_channel *chan)
+{
+ struct ast_context *c;
+ int res=-1;
+
+ if (ast_lock_contexts()) {
+ ast_log(LOG_ERROR, "Failed to lock contexts list\n");
+ return -1;
+ }
+
+ for (c=ast_walk_contexts(NULL); c; c=ast_walk_contexts(c)) {
+ struct ast_exten *e;
+
+ if (!ast_lock_context(c)) {
+ if (!strcmp(ast_get_context_name(c), chan->context)) {
+ /* This is the matching context we want */
+ int cur_priority = chan->priority + 1, level=1;
+
+ for (e = find_matching_priority(c, chan->exten, cur_priority, chan->cid.cid_num); e; e = find_matching_priority(c, chan->exten, ++cur_priority, chan->cid.cid_num)) {
+ if (!strcasecmp(ast_get_extension_app(e), "WHILE")) {
+ level++;
+ } else if (!strcasecmp(ast_get_extension_app(e), "ENDWHILE")) {
+ level--;
+ }
+
+ if (level == 0) {
+ res = cur_priority;
+ break;
+ }
+ }
+ }
+ ast_unlock_context(c);
+ if (res > 0) {
+ break;
+ }
+ }
+ }
+ ast_unlock_contexts();
+ return res;
+}
+
+static int _while_exec(struct ast_channel *chan, void *data, int end)
+{
+ int res=0;
+ struct ast_module_user *u;
+ const char *while_pri = NULL;
+ char *my_name = NULL;
+ const char *condition = NULL, *label = NULL;
+ char varname[VAR_SIZE], end_varname[VAR_SIZE];
+ const char *prefix = "WHILE";
+ size_t size=0;
+ int used_index_i = -1, x=0;
+ char used_index[VAR_SIZE] = "0", new_index[VAR_SIZE] = "0";
+
+ if (!chan) {
+ /* huh ? */
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+#if 0
+ /* dont want run away loops if the chan isn't even up
+ this is up for debate since it slows things down a tad ......
+
+ Debate is over... this prevents While/EndWhile from working
+ within the "h" extension. Not good.
+ */
+ if (ast_waitfordigit(chan,1) < 0)
+ ALL_DONE(u,-1);
+#endif
+
+ for (x=0;;x++) {
+ if (get_index(chan, prefix, x)) {
+ used_index_i = x;
+ } else
+ break;
+ }
+
+ snprintf(used_index, VAR_SIZE, "%d", used_index_i);
+ snprintf(new_index, VAR_SIZE, "%d", used_index_i + 1);
+
+ if (!end)
+ condition = ast_strdupa(data);
+
+ size = strlen(chan->context) + strlen(chan->exten) + 32;
+ my_name = alloca(size);
+ memset(my_name, 0, size);
+ snprintf(my_name, size, "%s_%s_%d", chan->context, chan->exten, chan->priority);
+
+ if (ast_strlen_zero(label)) {
+ if (end)
+ label = used_index;
+ else if (!(label = pbx_builtin_getvar_helper(chan, my_name))) {
+ label = new_index;
+ pbx_builtin_setvar_helper(chan, my_name, label);
+ }
+
+ }
+
+ snprintf(varname, VAR_SIZE, "%s_%s", prefix, label);
+ while_pri = pbx_builtin_getvar_helper(chan, varname);
+
+ if ((while_pri = pbx_builtin_getvar_helper(chan, varname)) && !end) {
+ snprintf(end_varname,VAR_SIZE,"END_%s",varname);
+ }
+
+
+ if ((!end && !pbx_checkcondition(condition)) || (end == 2)) {
+ /* Condition Met (clean up helper vars) */
+ const char *goto_str;
+ pbx_builtin_setvar_helper(chan, varname, NULL);
+ pbx_builtin_setvar_helper(chan, my_name, NULL);
+ snprintf(end_varname,VAR_SIZE,"END_%s",varname);
+ if ((goto_str=pbx_builtin_getvar_helper(chan, end_varname))) {
+ ast_parseable_goto(chan, goto_str);
+ pbx_builtin_setvar_helper(chan, end_varname, NULL);
+ } else {
+ int pri = find_matching_endwhile(chan);
+ if (pri > 0) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Jumping to priority %d\n", pri);
+ chan->priority = pri;
+ } else {
+ ast_log(LOG_WARNING, "Couldn't find matching EndWhile? (While at %s@%s priority %d)\n", chan->context, chan->exten, chan->priority);
+ }
+ }
+ ALL_DONE(u,res);
+ }
+
+ if (!end && !while_pri) {
+ char *goto_str;
+ size = strlen(chan->context) + strlen(chan->exten) + 32;
+ goto_str = alloca(size);
+ memset(goto_str, 0, size);
+ snprintf(goto_str, size, "%s|%s|%d", chan->context, chan->exten, chan->priority);
+ pbx_builtin_setvar_helper(chan, varname, goto_str);
+ }
+
+ else if (end && while_pri) {
+ /* END of loop */
+ snprintf(end_varname, VAR_SIZE, "END_%s", varname);
+ if (! pbx_builtin_getvar_helper(chan, end_varname)) {
+ char *goto_str;
+ size = strlen(chan->context) + strlen(chan->exten) + 32;
+ goto_str = alloca(size);
+ memset(goto_str, 0, size);
+ snprintf(goto_str, size, "%s|%s|%d", chan->context, chan->exten, chan->priority+1);
+ pbx_builtin_setvar_helper(chan, end_varname, goto_str);
+ }
+ ast_parseable_goto(chan, while_pri);
+ }
+
+
+
+
+ ALL_DONE(u, res);
+}
+
+static int while_start_exec(struct ast_channel *chan, void *data) {
+ return _while_exec(chan, data, 0);
+}
+
+static int while_end_exec(struct ast_channel *chan, void *data) {
+ return _while_exec(chan, data, 1);
+}
+
+static int while_exit_exec(struct ast_channel *chan, void *data) {
+ return _while_exec(chan, data, 2);
+}
+
+static int while_continue_exec(struct ast_channel *chan, void *data)
+{
+ int x;
+ const char *prefix = "WHILE", *while_pri=NULL;
+
+ for (x = 0; ; x++) {
+ const char *tmp = get_index(chan, prefix, x);
+ if (tmp)
+ while_pri = tmp;
+ else
+ break;
+ }
+
+ if (while_pri)
+ ast_parseable_goto(chan, while_pri);
+
+ return 0;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(start_app);
+ res |= ast_unregister_application(stop_app);
+ res |= ast_unregister_application(exit_app);
+ res |= ast_unregister_application(continue_app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ int res;
+
+ res = ast_register_application(start_app, while_start_exec, start_synopsis, start_desc);
+ res |= ast_register_application(stop_app, while_end_exec, stop_synopsis, stop_desc);
+ res |= ast_register_application(exit_app, while_exit_exec, exit_synopsis, exit_desc);
+ res |= ast_register_application(continue_app, while_continue_exec, continue_synopsis, continue_desc);
+
+ return res;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "While Loops and Conditional Execution");
diff --git a/apps/app_zapateller.c b/apps/app_zapateller.c
new file mode 100644
index 000000000..5a209e315
--- /dev/null
+++ b/apps/app_zapateller.c
@@ -0,0 +1,120 @@
+/*
+ * 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 Playback the special information tone to get rid of telemarketers
+ *
+ * \author Mark Spencer <markster@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+
+static char *app = "Zapateller";
+
+static char *synopsis = "Block telemarketers with SIT";
+
+static char *descrip =
+" Zapateller(options): Generates special information tone to block\n"
+"telemarketers from calling you. Options is a pipe-delimited list of\n"
+"options. The following options are available:\n"
+"'answer' causes the line to be answered before playing the tone,\n"
+"'nocallerid' causes Zapateller to only play the tone if there\n"
+"is no callerid information available. Options should be separated by |\n"
+"characters\n";
+
+
+static int zapateller_exec(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ struct ast_module_user *u;
+ int answer = 0, nocallerid = 0;
+ char *c;
+ char *stringp=NULL;
+
+ u = ast_module_user_add(chan);
+
+ stringp=data;
+ c = strsep(&stringp, "|");
+ while(!ast_strlen_zero(c)) {
+ if (!strcasecmp(c, "answer"))
+ answer = 1;
+ else if (!strcasecmp(c, "nocallerid"))
+ nocallerid = 1;
+
+ c = strsep(&stringp, "|");
+ }
+
+ ast_stopstream(chan);
+ if (chan->_state != AST_STATE_UP) {
+
+ if (answer)
+ res = ast_answer(chan);
+ if (!res) {
+ res = ast_safe_sleep(chan, 500);
+ }
+ }
+ if (!ast_strlen_zero(chan->cid.cid_num) && nocallerid) {
+ ast_module_user_remove(u);
+ return res;
+ }
+ if (!res)
+ res = ast_tonepair(chan, 950, 0, 330, 0);
+ if (!res)
+ res = ast_tonepair(chan, 1400, 0, 330, 0);
+ if (!res)
+ res = ast_tonepair(chan, 1800, 0, 330, 0);
+ if (!res)
+ res = ast_tonepair(chan, 0, 0, 1000, 0);
+ ast_module_user_remove(u);
+ return res;
+}
+
+static int unload_module(void)
+{
+ int res;
+
+ res = ast_unregister_application(app);
+
+ ast_module_user_hangup_all();
+
+ return res;
+}
+
+static int load_module(void)
+{
+ return ast_register_application(app, zapateller_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Block Telemarketers with Special Information Tone");
diff --git a/apps/enter.h b/apps/enter.h
new file mode 100644
index 000000000..ac765984a
--- /dev/null
+++ b/apps/enter.h
@@ -0,0 +1,287 @@
+/*
+ * U-law 8-bit audio data
+ *
+ * Source: enter.raw
+ *
+ * Copyright (C) 1999, Mark Spencer and Linux Support Services
+ *
+ * Distributed under the terms of the GNU General Public License
+ *
+ */
+
+static unsigned char enter[] = {
+0xba, 0xba, 0xb0, 0xa6, 0xa9, 0xb8, 0xfe, 0x46, 0x42, 0x46,
+0x4a, 0xfe, 0xac, 0xa2, 0x9f, 0x9f, 0xa8, 0xb8, 0x3b, 0x29,
+0x35, 0x4a, 0xfe, 0xc1, 0xad, 0xa2, 0xad, 0xc5, 0x4e, 0x68,
+0x68, 0xe7, 0xb8, 0xb0, 0xb2, 0xc1, 0xc1, 0xb0, 0xae, 0xcd,
+0xfe, 0xfe, 0xcd, 0xcd, 0xfe, 0x68, 0xd3, 0xb2, 0xae, 0xab,
+0xb2, 0xfe, 0x35, 0x31, 0xdb, 0xac, 0xab, 0xaf, 0xab, 0xaa,
+0xb4, 0x68, 0x3b, 0x39, 0x3f, 0x68, 0xb4, 0xa8, 0xa8, 0xb0,
+0xbc, 0xbc, 0xc5, 0x3f, 0x31, 0x37, 0xfe, 0xc1, 0xbc, 0xb0,
+0xa5, 0xa2, 0xa8, 0xaf, 0xbe, 0x3b, 0x28, 0x26, 0x3d, 0xbc,
+0xb0, 0xae, 0xa2, 0x9f, 0xa2, 0xfe, 0x29, 0x24, 0x29, 0x4a,
+0xc5, 0xaa, 0xa8, 0xa9, 0xa8, 0xa5, 0xa7, 0xdb, 0x2c, 0x27,
+0x2d, 0x4a, 0xfe, 0xdb, 0xb2, 0xa2, 0x9f, 0x9f, 0xae, 0xe7,
+0x2c, 0x22, 0x2b, 0xfe, 0xba, 0xb0, 0xaa, 0x9f, 0xa3, 0xb0,
+0x5c, 0x33, 0x33, 0x39, 0x5c, 0xdb, 0xc1, 0xb4, 0xb0, 0xaa,
+0xad, 0xba, 0x54, 0x46, 0xfe, 0xe7, 0xfe, 0x54, 0xe7, 0xaf,
+0xa6, 0xa7, 0xb0, 0xfe, 0x46, 0x39, 0x5c, 0xe7, 0xdb, 0xfe,
+0xba, 0xac, 0xa8, 0xc5, 0x46, 0x33, 0x54, 0xc5, 0xae, 0xad,
+0xb2, 0xc1, 0xcd, 0xc1, 0xbc, 0xfe, 0x3f, 0x37, 0xfe, 0xb4,
+0xb6, 0xcd, 0xdb, 0xc1, 0xb0, 0xb6, 0xcd, 0x4e, 0x39, 0x37,
+0xfe, 0xb0, 0xab, 0xa9, 0xa9, 0xa9, 0xb0, 0x5c, 0x29, 0x25,
+0x31, 0xfe, 0xc1, 0xb4, 0xae, 0xab, 0xab, 0xb2, 0xcd, 0x3b,
+0x2a, 0x2c, 0x54, 0xb4, 0xb4, 0xba, 0xb2, 0xa3, 0x9f, 0xa8,
+0xfe, 0x33, 0x27, 0x2a, 0x39, 0xfe, 0xc1, 0xbe, 0xb0, 0xa2,
+0x9f, 0xb0, 0x33, 0x22, 0x25, 0x46, 0xc1, 0xb8, 0xb0, 0xab,
+0xa8, 0xa8, 0xb0, 0xbe, 0x42, 0x2c, 0x2e, 0x4a, 0xfe, 0x5c,
+0xfe, 0xb4, 0xa8, 0xa8, 0xba, 0xfe, 0x4a, 0x39, 0x39, 0x46,
+0xfe, 0xbc, 0xaf, 0xa5, 0xa5, 0xae, 0x68, 0x37, 0x4a, 0xfe,
+0xfe, 0x4a, 0x4a, 0xd3, 0xb0, 0xb0, 0xc1, 0x5c, 0x46, 0x46,
+0xd3, 0xb6, 0xbe, 0x54, 0x54, 0xc9, 0xab, 0xae, 0xc5, 0x46,
+0x4a, 0xfe, 0xcd, 0xc9, 0xcd, 0xe7, 0xe7, 0xc9, 0xb4, 0xc5,
+0x4a, 0x2c, 0x37, 0xc1, 0xb0, 0xb2, 0xb4, 0xb2, 0xb6, 0xdb,
+0xfe, 0x4a, 0x46, 0x3f, 0x68, 0xba, 0xb2, 0xba, 0xc5, 0xb6,
+0xb2, 0xcd, 0x33, 0x2e, 0x39, 0x68, 0xfe, 0xe7, 0xba, 0xaf,
+0xa7, 0xa7, 0xad, 0xe7, 0x2d, 0x25, 0x2f, 0xd3, 0xbe, 0xcd,
+0xc5, 0xac, 0xa6, 0xac, 0xfe, 0x3b, 0x2c, 0x2d, 0x3d, 0xc1,
+0xb4, 0xbe, 0xcd, 0xaf, 0xa5, 0xa8, 0xe7, 0x31, 0x2f, 0x39,
+0x46, 0x5c, 0xdb, 0xbc, 0xba, 0xaf, 0xa9, 0xad, 0xfe, 0x2f,
+0x2d, 0xba, 0xad, 0xba, 0xfe, 0x3d, 0x42, 0x5c, 0xc9, 0xc1,
+0xcd, 0xfe, 0xc1, 0xae, 0xa6, 0xcd, 0x33, 0x25, 0x3b, 0xdb,
+0xb0, 0xb6, 0xb8, 0xb6, 0xb4, 0xb8, 0xba, 0xfe, 0x3d, 0x37,
+0xfe, 0xba, 0xc1, 0x54, 0x54, 0xd3, 0xb0, 0xb4, 0xe7, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xd3, 0xb6, 0xa9, 0xa7, 0xba,
+0x3d, 0x35, 0xfe, 0xc1, 0xcd, 0x4a, 0x54, 0xbe, 0xb2, 0xb8,
+0xfe, 0x46, 0x3b, 0xfe, 0xba, 0xab, 0xc5, 0x46, 0x3b, 0xbc,
+0xaa, 0xab, 0xd3, 0x68, 0xfe, 0xd3, 0xcd, 0xdb, 0x54, 0x3d,
+0x4a, 0xbc, 0xac, 0xb4, 0x3f, 0x2e, 0x3d, 0xba, 0xb0, 0xb8,
+0xba, 0xb6, 0xba, 0xcd, 0xfe, 0xfe, 0x5c, 0x54, 0xc9, 0xb4,
+0xbe, 0x54, 0x54, 0xcd, 0xb6, 0xc9, 0x46, 0x54, 0xcd, 0xc5,
+0xdb, 0xfe, 0xfe, 0xc1, 0xae, 0xa9, 0xac, 0xfe, 0x35, 0x2e,
+0xfe, 0xba, 0xc1, 0x5c, 0xfe, 0xb6, 0xaa, 0xb0, 0xe7, 0x35,
+0x2e, 0x39, 0xc1, 0xac, 0xb0, 0xfe, 0xfe, 0xbc, 0xa6, 0xac,
+0xc1, 0x42, 0x46, 0x54, 0xfe, 0xfe, 0xfe, 0xfe, 0xc9, 0xae,
+0xa9, 0xb0, 0x54, 0x35, 0x37, 0xfe, 0xd3, 0xd3, 0xb8, 0xae,
+0xab, 0xb6, 0xe7, 0xfe, 0xfe, 0x68, 0xfe, 0xfe, 0xfe, 0x4e,
+0xfe, 0xb0, 0xac, 0xb8, 0xfe, 0xfe, 0xc1, 0xb6, 0xc5, 0x46,
+0x3d, 0xe7, 0xb4, 0xa7, 0xab, 0xbc, 0x3f, 0x37, 0x54, 0xba,
+0xcd, 0x54, 0x42, 0xc5, 0xae, 0xac, 0xc9, 0x46, 0x3d, 0x54,
+0xba, 0xb0, 0xb0, 0xfe, 0x5c, 0xcd, 0xb0, 0xb0, 0xc9, 0x54,
+0x54, 0xfe, 0xfe, 0xfe, 0xfe, 0xe7, 0xcd, 0xc1, 0xba, 0xc5,
+0xfe, 0x42, 0x46, 0xfe, 0xc5, 0xba, 0xb2, 0xa7, 0xa7, 0xb0,
+0xfe, 0x3d, 0x4a, 0x5c, 0xfe, 0xfe, 0xfe, 0xe7, 0xbc, 0xb0,
+0xae, 0xc5, 0x4e, 0x39, 0xfe, 0xc5, 0xbe, 0xfe, 0x54, 0xc9,
+0xa9, 0xa2, 0xa5, 0xbc, 0x3b, 0x2f, 0x35, 0xfe, 0xc9, 0xfe,
+0xfe, 0xc5, 0xa9, 0xa6, 0xb0, 0x54, 0x31, 0x31, 0x3f, 0xd3,
+0xbc, 0xc1, 0xcd, 0xb8, 0xae, 0xa8, 0xb4, 0xd3, 0x54, 0x4e,
+0x5c, 0x54, 0xfe, 0xdb, 0xba, 0xb4, 0xb4, 0xba, 0xcd, 0x5c,
+0x3d, 0x3f, 0x54, 0xfe, 0xcd, 0xaf, 0xa8, 0xac, 0xc5, 0xfe,
+0xfe, 0xe7, 0xdb, 0xfe, 0xfe, 0xfe, 0xe7, 0xb8, 0xaf, 0xb0,
+0xe7, 0x42, 0x4a, 0xcd, 0xbc, 0xdb, 0x46, 0x68, 0xcd, 0xb0,
+0xab, 0xbc, 0xfe, 0x3d, 0x46, 0xfe, 0xb8, 0xbc, 0xd3, 0xd3,
+0xb6, 0xb0, 0xb6, 0x5c, 0x3b, 0x35, 0x54, 0xdb, 0xba, 0xb4,
+0xc1, 0xc9, 0xc1, 0xba, 0xc9, 0x5c, 0x3d, 0x46, 0xfe, 0xcd,
+0xc5, 0xb8, 0xae, 0xaf, 0xb4, 0xd3, 0x54, 0x3d, 0x35, 0x46,
+0xfe, 0xdb, 0xbc, 0xb2, 0xa9, 0xab, 0xba, 0x3f, 0x31, 0x39,
+0xfe, 0xe7, 0xdb, 0xcd, 0xb8, 0xae, 0xab, 0xac, 0xe7, 0x3d,
+0x2d, 0x3f, 0xfe, 0xdb, 0xfe, 0xfe, 0xbc, 0xaa, 0xa8, 0xb0,
+0xfe, 0x31, 0x2d, 0x3d, 0xdb, 0xc5, 0xcd, 0xc9, 0xb4, 0xa8,
+0xad, 0xc5, 0x46, 0x39, 0x3f, 0x5c, 0xfe, 0xd3, 0xc5, 0xc1,
+0xb6, 0xb0, 0xbc, 0x68, 0x46, 0x4e, 0xe7, 0xfe, 0x5c, 0xfe,
+0xc1, 0xaf, 0xb0, 0xb8, 0xe7, 0x5c, 0x5c, 0xfe, 0xe7, 0xfe,
+0xfe, 0xe7, 0xb0, 0xab, 0xb2, 0x4a, 0x37, 0x3f, 0xcd, 0xbe,
+0xc1, 0xe7, 0xe7, 0xd3, 0xb6, 0xb4, 0xc9, 0x3b, 0x33, 0x4a,
+0xba, 0xb4, 0xc5, 0xfe, 0xc9, 0xb6, 0xb4, 0xcd, 0xfe, 0x3b,
+0x3b, 0xfe, 0xc1, 0xb6, 0xc5, 0xc5, 0xb8, 0xb0, 0xba, 0x4a,
+0x31, 0x35, 0x68, 0xcd, 0xc5, 0xba, 0xb4, 0xb0, 0xb0, 0xba,
+0x5c, 0x35, 0x2f, 0x4e, 0xd3, 0xc1, 0xdb, 0xd3, 0xb4, 0xa9,
+0xab, 0xcd, 0x3b, 0x2f, 0x35, 0xfe, 0xd3, 0xd3, 0xdb, 0xbc,
+0xad, 0xa4, 0xb0, 0xfe, 0x2d, 0x2f, 0x3f, 0xe7, 0xe7, 0xe7,
+0xcd, 0xb4, 0xaf, 0xad, 0xc5, 0x3d, 0x31, 0x3d, 0xe7, 0xd3,
+0xe7, 0xe7, 0xc1, 0xaf, 0xad, 0xb6, 0xfe, 0x4a, 0x42, 0x54,
+0xfe, 0x68, 0xfe, 0xd3, 0xb2, 0xae, 0xb4, 0xfe, 0x42, 0x4e,
+0xcd, 0xc5, 0xcd, 0xdb, 0xc9, 0xb4, 0xb0, 0xb6, 0xfe, 0x3b,
+0x42, 0xe7, 0xb0, 0xb8, 0xcd, 0xfe, 0xc9, 0xb6, 0xb8, 0xfe,
+0x42, 0x3d, 0xfe, 0xc1, 0xb0, 0xba, 0xd3, 0xfe, 0xc1, 0xb0,
+0xb6, 0xfe, 0x3b, 0x3f, 0xe7, 0xba, 0xb8, 0xbc, 0xc5, 0xc1,
+0xc1, 0xcd, 0xfe, 0x3b, 0x37, 0xfe, 0xc1, 0xb4, 0xb6, 0xb8,
+0xb6, 0xb8, 0xc5, 0x5c, 0x3f, 0x46, 0xfe, 0xcd, 0xc5, 0xcd,
+0xcd, 0xc1, 0xb2, 0xb2, 0xfe, 0x3f, 0x35, 0x54, 0xdb, 0xc1,
+0xcd, 0xcd, 0xbc, 0xaf, 0xac, 0xb6, 0x54, 0x35, 0x31, 0x68,
+0xba, 0xb8, 0xcd, 0xdb, 0xc9, 0xb2, 0xb4, 0xc9, 0x46, 0x39,
+0x42, 0xdb, 0xbc, 0xbc, 0xcd, 0xcd, 0xbe, 0xb2, 0xb8, 0xe7,
+0x54, 0x46, 0xfe, 0xfe, 0xdb, 0xc9, 0xc5, 0xbe, 0xbe, 0xc9,
+0xfe, 0x5c, 0x5c, 0xfe, 0xd3, 0xcd, 0xcd, 0xc5, 0xb6, 0xb2,
+0xc5, 0x68, 0x4e, 0xfe, 0xc5, 0xc1, 0xcd, 0x68, 0x5c, 0xe7,
+0xb8, 0xb6, 0xd3, 0x4a, 0x46, 0xfe, 0xbc, 0xb8, 0xc1, 0xe7,
+0xe7, 0xc1, 0xb4, 0xbe, 0xfe, 0x3f, 0x3f, 0xfe, 0xba, 0xb2,
+0xba, 0xe7, 0xfe, 0xcd, 0xcd, 0xfe, 0x4e, 0x46, 0xfe, 0xc5,
+0xb8, 0xb2, 0xba, 0xc1, 0xcd, 0xd3, 0xe7, 0xfe, 0x5c, 0x5c,
+0xfe, 0xe7, 0xc5, 0xbe, 0xb6, 0xba, 0xc5, 0xfe, 0x3f, 0x3f,
+0x54, 0xfe, 0xd3, 0xc1, 0xbc, 0xb6, 0xb0, 0xb0, 0xd3, 0x54,
+0x39, 0x46, 0xfe, 0xc1, 0xcd, 0xe7, 0xe7, 0xc5, 0xb8, 0xb4,
+0xd3, 0x54, 0x37, 0x42, 0xdb, 0xbe, 0xc1, 0xd3, 0xcd, 0xb8,
+0xb0, 0xb0, 0xcd, 0x4a, 0x3b, 0x42, 0xe7, 0xc5, 0xbe, 0xcd,
+0xe7, 0xd3, 0xc5, 0xcd, 0xfe, 0x54, 0x54, 0x68, 0xe7, 0xc5,
+0xc1, 0xc1, 0xcd, 0xcd, 0xc9, 0xc9, 0xcd, 0xe7, 0xfe, 0xfe,
+0xfe, 0xe7, 0xc5, 0xbe, 0xc1, 0xfe, 0x5c, 0x5c, 0xfe, 0xcd,
+0xcd, 0xcd, 0xdb, 0xd3, 0xc1, 0xbc, 0xbe, 0xfe, 0x4e, 0x54,
+0xcd, 0xb6, 0xb8, 0xd3, 0x5c, 0x5c, 0xfe, 0xc5, 0xc9, 0xfe,
+0x46, 0x4a, 0xe7, 0xb4, 0xb6, 0xc5, 0xfe, 0xe7, 0xcd, 0xc9,
+0xdb, 0xfe, 0x4e, 0x68, 0xd3, 0xb6, 0xb2, 0xbc, 0xfe, 0x68,
+0xfe, 0xfe, 0x68, 0x54, 0x68, 0xe7, 0xc5, 0xbc, 0xb8, 0xbe,
+0xcd, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xd3, 0xd3, 0xcd,
+0xc1, 0xb8, 0xbc, 0xdb, 0x4e, 0x42, 0x4a, 0xfe, 0xc9, 0xc1,
+0xcd, 0xd3, 0xcd, 0xba, 0xb8, 0xcd, 0x46, 0x3b, 0xfe, 0xc9,
+0xba, 0xcd, 0xe7, 0xfe, 0xd3, 0xc1, 0xba, 0xdb, 0x54, 0x3d,
+0x68, 0xd3, 0xbc, 0xcd, 0xfe, 0xfe, 0xc5, 0xbe, 0xc1, 0xe7,
+0x54, 0x4a, 0xfe, 0xc9, 0xc1, 0xcd, 0xfe, 0xfe, 0xd3, 0xd3,
+0xd3, 0xfe, 0xe7, 0xe7, 0xe7, 0xdb, 0xd3, 0xe7, 0xe7, 0xe7,
+0xfe, 0xfe, 0xfe, 0xfe, 0xcd, 0xc9, 0xdb, 0xfe, 0xfe, 0xdb,
+0xbe, 0xc9, 0xfe, 0x5c, 0xfe, 0xc9, 0xbc, 0xbe, 0xdb, 0x68,
+0x5c, 0xdb, 0xc5, 0xd3, 0x54, 0x46, 0xfe, 0xbc, 0xb2, 0xb8,
+0xdb, 0x68, 0x68, 0xe7, 0xcd, 0xdb, 0x5c, 0x54, 0xfe, 0xc1,
+0xb8, 0xc1, 0xe7, 0xfe, 0xfe, 0xe7, 0xe7, 0xfe, 0xfe, 0xfe,
+0xd3, 0xc5, 0xc1, 0xc5, 0xcd, 0xd3, 0xe7, 0xfe, 0x54, 0x4e,
+0xfe, 0xd3, 0xcd, 0xd3, 0xd3, 0xc5, 0xc1, 0xc1, 0xe7, 0x5c,
+0x4e, 0x5c, 0xd3, 0xc1, 0xcd, 0xfe, 0xfe, 0xcd, 0xba, 0xba,
+0xe7, 0x4a, 0x4a, 0x68, 0xcd, 0xc5, 0xcd, 0xfe, 0xfe, 0xcd,
+0xb8, 0xc1, 0xe7, 0x4e, 0x5c, 0xe7, 0xc1, 0xc9, 0xdb, 0xfe,
+0xe7, 0xc9, 0xc5, 0xd3, 0xfe, 0x68, 0xfe, 0xdb, 0xd3, 0xe7,
+0xfe, 0xfe, 0xcd, 0xc9, 0xcd, 0xd3, 0xd3, 0xd3, 0xcd, 0xe7,
+0xfe, 0xfe, 0xe7, 0xc5, 0xc5, 0xe7, 0x68, 0x68, 0xe7, 0xc1,
+0xc5, 0xfe, 0x5c, 0xfe, 0xd3, 0xc1, 0xd3, 0xfe, 0x68, 0xe7,
+0xc5, 0xb6, 0xc5, 0xe7, 0x68, 0xfe, 0xcd, 0xc5, 0xe7, 0xfe,
+0x54, 0xfe, 0xc9, 0xc5, 0xdb, 0xfe, 0xfe, 0xfe, 0xd3, 0xd3,
+0xfe, 0xfe, 0xfe, 0xcd, 0xc1, 0xc1, 0xc9, 0xd3, 0xd3, 0xe7,
+0xfe, 0xfe, 0xfe, 0xfe, 0xe7, 0xd3, 0xdb, 0xe7, 0xe7, 0xd3,
+0xcd, 0xd3, 0xfe, 0xfe, 0xfe, 0xcd, 0xc5, 0xd3, 0xe7, 0xe7,
+0xc9, 0xbc, 0xbe, 0xe7, 0x68, 0x4a, 0xfe, 0xdb, 0xcd, 0xfe,
+0xfe, 0xfe, 0xcd, 0xc1, 0xc9, 0xfe, 0x54, 0x5c, 0xe7, 0xc9,
+0xc5, 0xe7, 0xfe, 0xfe, 0xcd, 0xc5, 0xc5, 0xe7, 0xfe, 0xfe,
+0xfe, 0xe7, 0xe7, 0xfe, 0xfe, 0xdb, 0xd3, 0xd3, 0xdb, 0xe7,
+0xfe, 0xfe, 0xe7, 0xe7, 0xdb, 0xd3, 0xc9, 0xd3, 0xe7, 0xfe,
+0xfe, 0xd3, 0xd3, 0xdb, 0xfe, 0xfe, 0xfe, 0xd3, 0xcd, 0xcd,
+0xfe, 0xfe, 0xe7, 0xc9, 0xc5, 0xd3, 0xfe, 0xfe, 0xfe, 0xcd,
+0xc9, 0xd3, 0xfe, 0xfe, 0xfe, 0xdb, 0xc9, 0xcd, 0xe7, 0xfe,
+0xe7, 0xcd, 0xcd, 0xe7, 0xfe, 0xfe, 0xe7, 0xd3, 0xc5, 0xcd,
+0xe7, 0xfe, 0xfe, 0xfe, 0xdb, 0xe7, 0xfe, 0xfe, 0xfe, 0xfe,
+0xe7, 0xcd, 0xcd, 0xd3, 0xe7, 0xe7, 0xe7, 0xe7, 0xfe, 0xfe,
+0xe7, 0xe7, 0xdb, 0xc9, 0xc1, 0xc5, 0xfe, 0x5c, 0x68, 0xfe,
+0xd3, 0xdb, 0xe7, 0xe7, 0xe7, 0xd3, 0xc5, 0xcd, 0xe7, 0x68,
+0xfe, 0xe7, 0xcd, 0xd3, 0xe7, 0xfe, 0xe7, 0xcd, 0xc1, 0xc1,
+0xdb, 0xfe, 0x54, 0xfe, 0xe7, 0xcd, 0xe7, 0xfe, 0xe7, 0xd3,
+0xcd, 0xd3, 0xe7, 0xfe, 0xfe, 0xfe, 0xcd, 0xc5, 0xcd, 0xfe,
+0xfe, 0xe7, 0xcd, 0xd3, 0xdb, 0xe7, 0xfe, 0xfe, 0xfe, 0xe7,
+0xd3, 0xd3, 0xe7, 0xfe, 0xe7, 0xe7, 0xe7, 0xfe, 0xfe, 0xfe,
+0xfe, 0xdb, 0xc5, 0xc1, 0xd3, 0xfe, 0xfe, 0xfe, 0xd3, 0xc9,
+0xcd, 0xe7, 0xfe, 0xfe, 0xd3, 0xcd, 0xdb, 0xfe, 0x5c, 0xfe,
+0xcd, 0xc9, 0xd3, 0xfe, 0xfe, 0xfe, 0xd3, 0xc9, 0xcd, 0xfe,
+0x68, 0xfe, 0xd3, 0xc1, 0xc1, 0xdb, 0xfe, 0xfe, 0xe7, 0xe7,
+0xfe, 0xfe, 0x68, 0xfe, 0xe7, 0xc5, 0xc9, 0xdb, 0xfe, 0xfe,
+0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xdb, 0xc5, 0xc5,
+0xd3, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xe7, 0xe7, 0xfe, 0xfe,
+0xc9, 0xc1, 0xc5, 0xfe, 0x54, 0x5c, 0xfe, 0xcd, 0xc5, 0xcd,
+0xfe, 0xfe, 0xdb, 0xc5, 0xc9, 0xfe, 0x5c, 0x68, 0xfe, 0xcd,
+0xcd, 0xfe, 0xfe, 0xfe, 0xe7, 0xc5, 0xc1, 0xd3, 0xfe, 0xfe,
+0xdb, 0xc9, 0xc5, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xfe,
+0xfe, 0xfe, 0xe7, 0xcd, 0xcd, 0xdb, 0xfe, 0xfe, 0xfe, 0xfe,
+0xe7, 0xd3, 0xcd, 0xd3, 0xfe, 0xfe, 0xdb, 0xcd, 0xd3, 0xe7,
+0xfe, 0xfe, 0xfe, 0xdb, 0xcd, 0xd3, 0xe7, 0xfe, 0xd3, 0xc5,
+0xc9, 0xfe, 0x5c, 0x54, 0xfe, 0xcd, 0xc1, 0xcd, 0xe7, 0xfe,
+0xfe, 0xd3, 0xcd, 0xfe, 0x54, 0x5c, 0xe7, 0xc1, 0xc1, 0xd3,
+0xfe, 0xfe, 0xe7, 0xd3, 0xd3, 0xe7, 0xfe, 0xfe, 0xfe, 0xcd,
+0xc5, 0xcd, 0xd3, 0xe7, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xe7, 0xd3, 0xcd, 0xc9, 0xcd, 0xe7, 0xfe, 0xfe, 0xfe, 0xdb,
+0xc9, 0xcd, 0xe7, 0xfe, 0xe7, 0xc9, 0xc5, 0xdb, 0xfe, 0x5c,
+0xfe, 0xe7, 0xcd, 0xcd, 0xe7, 0xfe, 0xe7, 0xc5, 0xc1, 0xd3,
+0xfe, 0x5c, 0xfe, 0xcd, 0xc5, 0xcd, 0xe7, 0xfe, 0xfe, 0xe7,
+0xd3, 0xe7, 0xfe, 0xfe, 0xe7, 0xcd, 0xcd, 0xdb, 0xfe, 0xfe,
+0xfe, 0xe7, 0xe7, 0xe7, 0xe7, 0xfe, 0xe7, 0xdb, 0xcd, 0xd3,
+0xd3, 0xdb, 0xfe, 0xfe, 0xfe, 0xfe, 0xdb, 0xd3, 0xdb, 0xe7,
+0xe7, 0xdb, 0xd3, 0xe7, 0xfe, 0xfe, 0xfe, 0xe7, 0xc9, 0xc5,
+0xcd, 0xe7, 0xfe, 0xdb, 0xd3, 0xe7, 0xfe, 0x68, 0xfe, 0xe7,
+0xcd, 0xcd, 0xd3, 0xfe, 0xfe, 0xe7, 0xdb, 0xe7, 0xfe, 0x68,
+0xfe, 0xdb, 0xfe, 0x68, 0xbe, 0xb2, 0xae, 0xab, 0xb2, 0xfe,
+0x2f, 0x31, 0xdb, 0xac, 0xad, 0xaf, 0xab, 0xab, 0xb4, 0x68,
+0x37, 0x39, 0x3f, 0xe7, 0xb4, 0xa8, 0xaa, 0xb0, 0xbc, 0xbc,
+0xc5, 0x3f, 0x31, 0x3d, 0xfe, 0xc1, 0xb8, 0xb0, 0xa5, 0xa2,
+0xa8, 0xaf, 0xdb, 0x3b, 0x28, 0x2a, 0x3d, 0xbc, 0xb0, 0xaa,
+0xa2, 0x9f, 0xab, 0xfe, 0x29, 0x24, 0x29, 0x4a, 0xb4, 0xaa,
+0xa8, 0xa9, 0xa8, 0xa5, 0xac, 0xdb, 0x2c, 0x27, 0x35, 0x4a,
+0xfe, 0xcd, 0xb2, 0xa2, 0x9f, 0x9f, 0xae, 0x4e, 0x2c, 0x22,
+0x33, 0xfe, 0xba, 0xb0, 0xa6, 0x9f, 0xa3, 0xbc, 0x5c, 0x33,
+0x31, 0x39, 0x5c, 0xcd, 0xc1, 0xb4, 0xad, 0xaa, 0xad, 0xcd,
+0x54, 0x46, 0xfe, 0xe7, 0xfe, 0x54, 0xc5, 0xaf, 0xa6, 0xa9,
+0xb0, 0xfe, 0x3d, 0x39, 0x5c, 0xdb, 0xdb, 0xfe, 0xba, 0xac,
+0xa8, 0xc5, 0x39, 0x33, 0x54, 0xb8, 0xae, 0xad, 0xb8, 0xc1,
+0xcd, 0xbe, 0xbc, 0xfe, 0x39, 0x37, 0xfe, 0xb4, 0xba, 0xcd,
+0xdb, 0xb8, 0xb0, 0xb6, 0xfe, 0x4e, 0x39, 0x3d, 0xfe, 0xb0,
+0xaa, 0xa9, 0xa9, 0xaa, 0xb0, 0x5c, 0x29, 0x28, 0x31, 0xfe,
+0xba, 0xb4, 0xae, 0xab, 0xab, 0xb2, 0xfe, 0x3b, 0x2a, 0x2f,
+0x54, 0xb4, 0xb4, 0xba, 0xb2, 0xa3, 0x9f, 0xa8, 0xfe, 0x2c,
+0x27, 0x2a, 0x46, 0xfe, 0xc1, 0xbc, 0xb0, 0xa2, 0xa2, 0xb0,
+0x33, 0x22, 0x2b, 0x46, 0xc1, 0xb4, 0xb0, 0xab, 0xa8, 0xa8,
+0xb0, 0xdb, 0x42, 0x2c, 0x33, 0x4a, 0xfe, 0x5c, 0xdb, 0xb4,
+0xa8, 0xad, 0xba, 0xfe, 0x46, 0x39, 0x39, 0x4a, 0xfe, 0xbc,
+0xab, 0xa5, 0xa5, 0xb8, 0x68, 0x37, 0x4a, 0xe7, 0xfe, 0x4a,
+0x5c, 0xd3, 0xb0, 0xb2, 0xc1, 0x5c, 0x42, 0x46, 0xd3, 0xb4,
+0xbe, 0x54, 0x54, 0xb6, 0xab, 0xae, 0xe7, 0x46, 0x4a, 0xfe,
+0xcd, 0xc9, 0xd3, 0xe7, 0xe7, 0xbe, 0xb4, 0xc5, 0x37, 0x2c,
+0x37, 0xc1, 0xb0, 0xb2, 0xb4, 0xb2, 0xb6, 0xdb, 0x54, 0x4a,
+0x46, 0x42, 0x68, 0xba, 0xb2, 0xba, 0xc5, 0xb6, 0xb6, 0xcd,
+0x33, 0x2f, 0x39, 0x68, 0xfe, 0xe7, 0xba, 0xac, 0xa7, 0xa7,
+0xb2, 0xe7, 0x2d, 0x25, 0x2f, 0xd3, 0xbe, 0xd3, 0xc5, 0xac,
+0xa6, 0xac, 0xfe, 0x33, 0x2c, 0x2d, 0x54, 0xc1, 0xb4, 0xcd,
+0xcd, 0xaf, 0xa4, 0xa8, 0xe7, 0x31, 0x31, 0x39, 0x46, 0xfe,
+0xdb, 0xbc, 0xb6, 0xaf, 0xa9, 0xb2, 0xfe, 0x2f, 0xfe, 0xba,
+0xad, 0xba, 0x4e, 0x3d, 0x42, 0xfe, 0xc9, 0xc1, 0xe7, 0xfe,
+0xc1, 0xa9, 0xa6, 0xcd, 0x2a, 0x25, 0x3b, 0xbc, 0xb0, 0xb6,
+0xb8, 0xb4, 0xb4, 0xb8, 0xc1, 0xfe, 0x3d, 0x3d, 0xfe, 0xba,
+0xd3, 0x54, 0x54, 0xbe, 0xb0, 0xb4, 0xe7, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xc5, 0xb6, 0xa9, 0xaa, 0xba, 0x3d, 0x39,
+0xfe, 0xc1, 0xfe, 0x4a, 0x54, 0xbe, 0xb2, 0xb8, 0xfe, 0x3d,
+0x3b, 0xfe, 0xb0, 0xab, 0xc5, 0x39, 0x3b, 0xbc, 0xa7, 0xab,
+0xd3, 0x68, 0xfe, 0xd3, 0xcd, 0xfe, 0x54, 0x3d, 0xfe, 0xbc,
+0xac, 0xc9, 0x3f, 0x2e, 0xfe, 0xba, 0xb0, 0xba, 0xba, 0xb6,
+0xba, 0xd3, 0xfe, 0xfe, 0x5c, 0x54, 0xc9, 0xb4, 0xbe, 0x54,
+0x68, 0xcd, 0xb6, 0xfe, 0x46, 0x54, 0xcd, 0xc5, 0xdb, 0xfe,
+0xe7, 0xc1, 0xae, 0xa8, 0xac, 0xfe, 0x2e, 0x2e, 0xfe, 0xb6,
+0xc1, 0x5c, 0xe7, 0xb6, 0xaa, 0xb0, 0x54, 0x35, 0x2e, 0x4a,
+0xc1, 0xac, 0xbc, 0xfe, 0xfe, 0xaf, 0xa6, 0xac, 0xfe, 0x42,
+0x46, 0x5c, 0xfe, 0xfe, 0xfe, 0xe7, 0xc9, 0xae, 0xa9, 0xb0,
+0x54, 0x31, 0x37, 0xfe, 0xd3, 0xd3, 0xb8, 0xac, 0xab, 0xb6,
+0xe7, 0xfe, 0xfe, 0x68, 0xfe, 0xfe, 0xfe, 0x54, 0xfe, 0xb0,
+0xae, 0xb8, 0xfe, 0xe7, 0xc1, 0xb6, 0xe7, 0x46, 0x3d, 0xe7,
+0xae, 0xa7, 0xab, 0xdb, 0x3f, 0x37, 0xfe, 0xba, 0xcd, 0x3f,
+0x42, 0xc5, 0xab, 0xac, 0xc9, 0x46, 0x3d, 0x54, 0xba, 0xad,
+0xb0, 0xfe, 0x68, 0xcd, 0xb0, 0xb0, 0xc9, 0x54, 0x54, 0xfe,
+0xfe, 0xfe, 0xfe, 0xe7, 0xcd, 0xbe, 0xba, 0xc5, 0x68, 0x42,
+0x46, 0xe7, 0xc5, 0xba, 0xaf, 0xa7, 0xa7, 0xbc, 0xfe, 0x3d,
+0x4a, 0x68, 0xfe, 0xfe, 0xfe, 0xe7, 0xbc, 0xaf, 0xae, 0xc5,
+0x3d, 0x39, 0xfe, 0xbc, 0xbe, 0xfe, 0x68, 0xc9, 0xa9, 0xa2,
+0xaa, 0xbc, 0x3b, 0x2d, 0x35, 0xfe, 0xcd, 0xfe, 0xfe, 0xb4,
+0xa9, 0xa6, 0xbc, 0x54, 0x31, 0x31, 0x54, 0xd3, 0xbc, 0xc5,
+0xcd, 0xb8, 0xab, 0xa8, 0xb4, 0xfe, 0x54, 0x4e, 0x68, 0x54,
+0xfe, 0xc9, 0xba, 0xb4, 0xb4, 0xba, 0xcd, 0x5c, 0x3b, 0x3f,
+0x54, 0xfe, 0xcd, 0xaf, 0xa8, 0xac, 0xc5, 0x68, 0xfe, 0xe7,
+0xdb, 0xfe, 0xfe, 0xfe, 0xcd, 0xb8, 0xaf, 0xb6, 0xe7, 0x42,
+0x5c, 0xcd, 0xbc, 0xfe, 0x46, 0x68, 0xba, 0xb0, 0xab, 0xbc,
+0x54, 0x3d, 0x46, 0xc9, 0xb8, 0xbc, 0xdb, 0xd3, 0xb6, 0xb0,
+0xb6, 0x5c, 0x37, 0x35, 0x54, 0xc9, 0xba, 0xb4, 0xc1, 0xc9,
+0xc1, 0xba, 0xe7, 0x5c, 0x3d, 0x54, 0xfe, 0xcd, 0xc5, 0xb8,
+0xae, 0xaf, 0xb4, 0xd3, 0x54, 0x3b, 0x35, 0x46, 0xfe, 0xdb,
+0xbc, 0xaf, 0xa9, 0xab, 0xd3, 0x3f, 0x31, 0x3f, 0xfe, 0xe7,
+0xdb, 0xcd, 0xb8, 0xae, 0xaa, 0xac, 0xe7, 0x33, 0x2d, 0x3f,
+0xd3, 0xdb, 0xfe, 0xfe, 0xbc, 0xaa, 0xa9, 0xb0, 0xfe, 0x31,
+0x2f, 0x3d, 0xdb, 0xc5, 0xcd, 0xc9, 0xae, 0xa8, 0xad, 0xfe,
+0x46, 0x39, 0x46, 0x5c, 0xfe, 0xcd, 0xc5, 0xc1, 0xb6, 0xb0,
+0xbc, 0x68, 0x42, 0x4e, 0xe7, 0xfe, 0x5c, 0xfe, 0xb6, 0xaf,
+0xb0, 0xc5, 0xe7, 0x5c, 0x5c, 0xfe, 0xe7, 0xfe, 0x68, 0xe7,
+0xb0, 0xac, 0xb2, 0x4a, 0x35, 0x3f, 0xcd, 0xbc, 0xc1, 0xe7,
+0xe7, 0xd3, 0xb6, 0xb4, 0xfe, 0x3b, 0x33, 0xfe, 0xba, 0xb4,
+0xd3, 0xfe, 0xc9, 0xb4, 0xb4, 0xcd, 0x4a, 0x3b, 0x3b, 0xfe,
+0xb8, 0xb6, 0xc5, 0xc5, 0xb8, 0xb0, 0xcd, 0x4a, 0x31, 0x3b,
+0x68, 0xcd, 0xc1, 0xba, 0xb4, 0xb0, 0xb0, 0xba, 0x5c, 0x2f,
+0x2f, 0x4e, 0xc9, 0xc1, 0xdb, 0xc9, 0xb4 };
diff --git a/apps/leave.h b/apps/leave.h
new file mode 100644
index 000000000..238976f20
--- /dev/null
+++ b/apps/leave.h
@@ -0,0 +1,207 @@
+/*
+ * U-law 8-bit audio data
+ *
+ * Source: leave.raw
+ *
+ * Copyright (C) 1999, Mark Spencer and Linux Support Services
+ *
+ * Distributed under the terms of the GNU General Public License
+ *
+ */
+
+static unsigned char leave[] = {
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xc1, 0x3d,
+0x42, 0x46, 0x3f, 0x3f, 0x46, 0x3f, 0x4e, 0xba, 0xbe, 0xbe,
+0xbc, 0xba, 0xbe, 0xc5, 0xb6, 0x2e, 0x2c, 0x33, 0x2f, 0x2e,
+0x2f, 0x33, 0x2b, 0x54, 0xac, 0xb0, 0xb0, 0xad, 0xaf, 0xb0,
+0xae, 0xcd, 0x3b, 0x2f, 0x31, 0x2e, 0x2f, 0x31, 0x2e, 0x46,
+0xc5, 0xaf, 0xb0, 0xaf, 0xae, 0xaf, 0xaf, 0xb0, 0xfe, 0x2d,
+0x31, 0x31, 0x2e, 0x31, 0x2f, 0x31, 0xfe, 0xae, 0xaf, 0xaf,
+0xae, 0xb0, 0xae, 0xaf, 0xfe, 0xdb, 0x2e, 0x2e, 0x31, 0x31,
+0x2d, 0x2e, 0xdb, 0x68, 0xaf, 0xad, 0xb0, 0xb0, 0xae, 0xaf,
+0x5c, 0xe7, 0x39, 0x2d, 0x31, 0x31, 0x31, 0x2d, 0xfe, 0xfe,
+0x68, 0xad, 0xaf, 0xb0, 0xaf, 0xac, 0xbc, 0xfe, 0xd3, 0x2f,
+0x2e, 0x33, 0x31, 0x2d, 0x4e, 0xdb, 0xfe, 0xfe, 0xac, 0xaf,
+0xb0, 0xac, 0xb6, 0x68, 0xe7, 0xdb, 0x2e, 0x2f, 0x35, 0x2f,
+0x31, 0xe7, 0xe7, 0x68, 0xad, 0xac, 0xb0, 0xae, 0xac, 0xfe,
+0xfe, 0xdb, 0xfe, 0x2d, 0x33, 0x31, 0x2e, 0xfe, 0xfe, 0xfe,
+0xfe, 0xbc, 0xaf, 0xb0, 0xad, 0xfe, 0xfe, 0xfe, 0xe7, 0x5c,
+0x2e, 0x33, 0x2e, 0x35, 0xe7, 0xfe, 0xfe, 0xfe, 0xad, 0xb0,
+0xaf, 0xc1, 0xfe, 0xe7, 0xfe, 0xe7, 0x3d, 0x31, 0x2f, 0x37,
+0xe7, 0xfe, 0xfe, 0xe7, 0xfe, 0xaf, 0xad, 0xbe, 0xfe, 0xdb,
+0xfe, 0xfe, 0xdb, 0x35, 0x2d, 0x39, 0xdb, 0xfe, 0xfe, 0xdb,
+0xfe, 0xfe, 0xad, 0xaf, 0xfe, 0xfe, 0xe7, 0x68, 0xfe, 0xd3,
+0x2e, 0x2c, 0xdb, 0xdb, 0x2c, 0x35, 0xd3, 0x68, 0xaf, 0xad,
+0xb0, 0xb0, 0xad, 0xba, 0x68, 0xe7, 0xe7, 0x2e, 0x2f, 0x33,
+0x31, 0x2d, 0xdb, 0xd3, 0x5c, 0xae, 0xaa, 0xe7, 0x68, 0xaa,
+0xe7, 0xfe, 0xdb, 0xe7, 0xfe, 0xe7, 0xd3, 0x2d, 0xfe, 0xdb,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xc5, 0xfe, 0xe7, 0xe7,
+0xfe, 0xfe, 0xe7, 0xe7, 0x3b, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xe7, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xc5, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xe7, 0xfe, 0x3b,
+0xdb, 0xfe, 0xfe, 0xfe, 0xe7, 0xfe, 0xfe, 0xb0, 0xfe, 0xfe,
+0xe7, 0xfe, 0xfe, 0xfe, 0xdb, 0x2e, 0x5c, 0xdb, 0xfe, 0xfe,
+0xe7, 0xe7, 0x68, 0xb0, 0xbe, 0x68, 0xe7, 0xe7, 0xfe, 0xfe,
+0xdb, 0x39, 0x2f, 0xdb, 0xfe, 0xfe, 0xe7, 0xe7, 0xfe, 0xbe,
+0xaf, 0xe7, 0x68, 0xe7, 0xfe, 0xfe, 0xfe, 0xfe, 0x33, 0x33,
+0xdb, 0xfe, 0xfe, 0xdb, 0xe7, 0xfe, 0xb0, 0xb0, 0xfe, 0xfe,
+0xe7, 0xfe, 0xfe, 0xfe, 0x35, 0x33, 0xe7, 0xe7, 0xfe, 0xe7,
+0xe7, 0xfe, 0xb0, 0xb2, 0xb0, 0xfe, 0xfe, 0xe7, 0xfe, 0xe7,
+0x46, 0x35, 0x35, 0x3f, 0xe7, 0xfe, 0xe7, 0xfe, 0xb2, 0xb0,
+0xb2, 0xb0, 0xfe, 0xfe, 0xfe, 0xfe, 0x42, 0x35, 0x37, 0x33,
+0xe7, 0xfe, 0xfe, 0xfe, 0xb8, 0xb0, 0xb6, 0xb0, 0xba, 0xfe,
+0xfe, 0xe7, 0xe7, 0x33, 0x39, 0x39, 0x33, 0xe7, 0xdb, 0xfe,
+0xe7, 0xb0, 0xb4, 0xb6, 0xb0, 0xcd, 0xfe, 0xe7, 0xe7, 0x33,
+0x39, 0x3b, 0x33, 0x46, 0xd3, 0xfe, 0xfe, 0xb0, 0xb2, 0xb6,
+0xb4, 0xb0, 0xfe, 0xfe, 0xdb, 0x35, 0x37, 0x39, 0x39, 0x35,
+0x37, 0xdb, 0x68, 0xcd, 0xb2, 0xb6, 0xb6, 0xb4, 0xb4, 0x68,
+0xe7, 0x42, 0x37, 0x3b, 0x3b, 0x39, 0x37, 0xdb, 0xfe, 0xcd,
+0xb2, 0xb6, 0xb6, 0xb6, 0xb2, 0xb4, 0xfe, 0x54, 0x37, 0x3b,
+0x39, 0x3b, 0x3b, 0x39, 0xe7, 0xfe, 0xb6, 0xb6, 0xb6, 0xb4,
+0xb6, 0xb6, 0xbc, 0xfe, 0x3f, 0x3b, 0x3b, 0x39, 0x3b, 0x3b,
+0x39, 0xe7, 0xb6, 0xb8, 0xb8, 0xb6, 0xb8, 0xb8, 0xb4, 0xfe,
+0x3b, 0x3d, 0x3d, 0x3b, 0x39, 0x3d, 0x3b, 0x39, 0xbe, 0xb8,
+0xba, 0xb8, 0xb6, 0xb8, 0xba, 0xb4, 0xfe, 0x39, 0x3f, 0x3d,
+0x3b, 0x3d, 0x3f, 0x39, 0xdb, 0xb4, 0xba, 0xb8, 0xb6, 0xb8,
+0xbc, 0xb4, 0xba, 0x39, 0x42, 0x3f, 0x3d, 0x3d, 0x3f, 0x3f,
+0x3b, 0xb8, 0xb6, 0xbc, 0xb8, 0xb8, 0xba, 0xbc, 0xb8, 0xe7,
+0x3d, 0x42, 0x3f, 0x3d, 0x3f, 0x42, 0x3d, 0xfe, 0xb8, 0xbc,
+0xbc, 0xba, 0xba, 0xbc, 0xba, 0xe7, 0x3d, 0x3f, 0x42, 0x3f,
+0x3f, 0x42, 0x42, 0xfe, 0xfe, 0xbc, 0xbc, 0xbe, 0xbc, 0xbe,
+0xbc, 0xc5, 0xe7, 0x68, 0x42, 0x46, 0x42, 0x46, 0x42, 0x46,
+0xfe, 0xfe, 0xbc, 0xbe, 0xbe, 0xbe, 0xbc, 0xc5, 0xfe, 0xdb,
+0x46, 0x46, 0x4a, 0x4a, 0x46, 0x46, 0xe7, 0xfe, 0xd3, 0xbe,
+0xc9, 0xc9, 0xc5, 0xc5, 0xe7, 0xdb, 0xd3, 0x4a, 0x4e, 0x54,
+0x4e, 0x4e, 0xfe, 0x5c, 0x54, 0xd3, 0xcd, 0xd3, 0xd3, 0xcd,
+0xd3, 0xd3, 0xcd, 0xfe, 0x5c, 0x68, 0x5c, 0x5c, 0x5c, 0x68,
+0x5c, 0x5c, 0xcd, 0xcd, 0xd3, 0xcd, 0xdb, 0xe7, 0xe7, 0xdb,
+0xe7, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xe7,
+0xfe, 0x5c, 0x5c, 0xfe, 0xfe, 0xfe, 0xfe, 0x46, 0x35, 0x35,
+0x37, 0x39, 0x3b, 0x39, 0x35, 0x33, 0x35, 0x5c, 0xd3, 0xcd,
+0xdb, 0xfe, 0xfe, 0xd3, 0xb0, 0xb0, 0xb0, 0xb4, 0xb4, 0xb6,
+0xb2, 0xb0, 0xb0, 0xb6, 0xcd, 0x5c, 0x68, 0xfe, 0xfe, 0xfe,
+0x3b, 0x33, 0x35, 0x37, 0x39, 0x3b, 0x39, 0x37, 0x35, 0x35,
+0x3f, 0xdb, 0xcd, 0xcd, 0xdb, 0xfe, 0xe7, 0xc5, 0xb0, 0xb0,
+0xb2, 0xb6, 0xb6, 0xb6, 0xb2, 0xb0, 0xb0, 0xcd, 0x5c, 0x5c,
+0xfe, 0xe7, 0xe7, 0xfe, 0x35, 0x35, 0x35, 0x39, 0x3b, 0x3b,
+0x39, 0x35, 0x33, 0x39, 0xdb, 0xcd, 0xd3, 0xe7, 0xfe, 0xfe,
+0xba, 0xb0, 0xb0, 0xb2, 0xb4, 0xb6, 0xb6, 0xb4, 0xb2, 0xb0,
+0xb4, 0xc9, 0x5c, 0x68, 0xfe, 0xfe, 0x5c, 0x3b, 0x35, 0x37,
+0x39, 0x3b, 0x3b, 0x3b, 0x39, 0x37, 0x37, 0x3d, 0xe7, 0xcd,
+0xdb, 0xfe, 0xe7, 0xbe, 0xb2, 0xb2, 0xb4, 0xb4, 0xb6, 0xb6,
+0xb6, 0xb4, 0xb0, 0xb0, 0xc5, 0x5c, 0x5c, 0xfe, 0xe7, 0xe7,
+0x4e, 0x35, 0x35, 0x37, 0x3b, 0x3b, 0x3b, 0x39, 0x37, 0x37,
+0x3b, 0xe7, 0xc9, 0xcd, 0xe7, 0xfe, 0xd3, 0xb4, 0xb2, 0xb2,
+0xb4, 0xb6, 0xb6, 0xb6, 0xb6, 0xb4, 0xb2, 0xb4, 0xc1, 0x68,
+0x68, 0xfe, 0xfe, 0x42, 0x39, 0x37, 0x39, 0x3b, 0x3b, 0x3b,
+0x3b, 0x3b, 0x39, 0x37, 0x3b, 0xfe, 0xd3, 0xdb, 0xfe, 0xcd,
+0xb4, 0xb2, 0xb4, 0xb4, 0xb6, 0xb6, 0xb6, 0xb6, 0xb4, 0xb2,
+0xb2, 0xc1, 0x5c, 0x5c, 0xfe, 0xfe, 0xfe, 0x3d, 0x37, 0x37,
+0x39, 0x3b, 0x3b, 0x3b, 0x3b, 0x37, 0x37, 0x39, 0xfe, 0xcd,
+0xd3, 0xfe, 0xfe, 0xc1, 0xb2, 0xb2, 0xb4, 0xb6, 0xb6, 0xb6,
+0xb6, 0xb6, 0xb6, 0xb4, 0xb4, 0xbc, 0x68, 0xfe, 0xfe, 0xfe,
+0x3b, 0x39, 0x39, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x39,
+0x39, 0x3b, 0xfe, 0xdb, 0xe7, 0xfe, 0xbc, 0xb6, 0xb6, 0xb6,
+0xb8, 0xb6, 0xb6, 0xb6, 0xb8, 0xb6, 0xb4, 0xb4, 0xbc, 0xfe,
+0x68, 0xfe, 0xe7, 0x5c, 0x3b, 0x39, 0x39, 0x3b, 0x3b, 0x3b,
+0x3d, 0x3d, 0x3b, 0x39, 0x3b, 0x68, 0xdb, 0xdb, 0xfe, 0xe7,
+0xb8, 0xb6, 0xb6, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb6,
+0xb4, 0xb6, 0xdb, 0x68, 0xfe, 0xfe, 0x46, 0x3b, 0x3b, 0x3b,
+0x3d, 0x3d, 0x3b, 0x3d, 0x3d, 0x3d, 0x3d, 0x3b, 0x3b, 0x5c,
+0xdb, 0xdb, 0xc9, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8,
+0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xbc, 0xcd, 0xfe, 0xfe, 0x3d,
+0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3b, 0x3d, 0x3d, 0x3d, 0x3d,
+0x3b, 0x3d, 0x46, 0xfe, 0xe7, 0xe7, 0xc5, 0xb8, 0xb8, 0xb8,
+0xba, 0xba, 0xb8, 0xb8, 0xba, 0xba, 0xb8, 0xb8, 0xb8, 0xcd,
+0xfe, 0xfe, 0x68, 0x3f, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d,
+0x3d, 0x3d, 0x3f, 0x3f, 0x3d, 0x3b, 0x4a, 0xfe, 0xdb, 0xbc,
+0xb8, 0xba, 0xba, 0xba, 0xba, 0xb8, 0xb8, 0xb8, 0xba, 0xba,
+0xba, 0xba, 0xba, 0xc5, 0xfe, 0x54, 0x3f, 0x3f, 0x3f, 0x3f,
+0x3f, 0x3f, 0x3d, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x42,
+0xfe, 0xe7, 0xdb, 0xbc, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba,
+0xba, 0xba, 0xbc, 0xba, 0xba, 0xba, 0xc5, 0xfe, 0xfe, 0x4e,
+0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x42,
+0x42, 0x42, 0x3f, 0x46, 0xfe, 0xcd, 0xb8, 0xba, 0xbc, 0xbc,
+0xbc, 0xba, 0xba, 0xba, 0xba, 0xbc, 0xbc, 0xbc, 0xba, 0xb8,
+0xbe, 0xfe, 0x42, 0x3d, 0x3f, 0x42, 0x42, 0x42, 0x3f, 0x3f,
+0x3f, 0x42, 0x42, 0x42, 0x42, 0x3f, 0x3f, 0x68, 0xdb, 0xc5,
+0xba, 0xbc, 0xbc, 0xbc, 0xbc, 0xba, 0xba, 0xba, 0xbc, 0xbc,
+0xbc, 0xbc, 0xba, 0xc1, 0xfe, 0xfe, 0x3f, 0x42, 0x46, 0x46,
+0x46, 0x42, 0x42, 0x42, 0x42, 0x42, 0x46, 0x46, 0x42, 0x3f,
+0x42, 0x68, 0xbe, 0xba, 0xbc, 0xbe, 0xbe, 0xbe, 0xbc, 0xbc,
+0xbc, 0xbc, 0xbe, 0xc1, 0xbe, 0xbc, 0xba, 0xbe, 0x68, 0x3f,
+0x42, 0x46, 0x4a, 0x4a, 0x46, 0x42, 0x42, 0x42, 0x46, 0x46,
+0x46, 0x46, 0x42, 0x42, 0x68, 0xd3, 0xbc, 0xbc, 0xbe, 0xc1,
+0xc1, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xc1, 0xc1, 0xbe, 0xbe,
+0xc1, 0xfe, 0x4e, 0x42, 0x46, 0x4a, 0x4a, 0x4a, 0x46, 0x46,
+0x46, 0x46, 0x4a, 0x4a, 0x4a, 0x46, 0x46, 0x68, 0xdb, 0xbe,
+0xbe, 0xc1, 0xc5, 0xc1, 0xc1, 0xbe, 0xbe, 0xbe, 0xbe, 0xc1,
+0xc5, 0xc5, 0xbe, 0xbc, 0xc1, 0x4e, 0x46, 0x46, 0x4a, 0x4e,
+0x4e, 0x4a, 0x46, 0x46, 0x46, 0x4a, 0x4a, 0x4e, 0x4a, 0x46,
+0x46, 0xfe, 0xbe, 0xbe, 0xc1, 0xc9, 0xc5, 0xc5, 0xc1, 0xc1,
+0xc1, 0xc1, 0xc5, 0xc5, 0xc5, 0xc5, 0xbe, 0xc1, 0xfe, 0x4a,
+0x4a, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4a, 0x4a, 0x4a, 0x4e,
+0x54, 0x4e, 0x4a, 0x4a, 0x4e, 0xcd, 0xc1, 0xc5, 0xc5, 0xc9,
+0xc5, 0xc5, 0xc5, 0xc5, 0xc9, 0xcd, 0xcd, 0xcd, 0xcd, 0xc9,
+0xc9, 0xd3, 0x68, 0x54, 0x5c, 0x68, 0x68, 0x68, 0x5c, 0x5c,
+0x5c, 0x5c, 0x5c, 0x68, 0x68, 0x5c, 0x54, 0x5c, 0xdb, 0xcd,
+0xcd, 0xdb, 0xdb, 0xdb, 0xdb, 0xd3, 0xd3, 0xe7, 0xe7, 0xe7,
+0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xe7, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+0xfe, 0xfe, 0xfe };
diff --git a/apps/rpt_flow.pdf b/apps/rpt_flow.pdf
new file mode 100644
index 000000000..2085af0f2
--- /dev/null
+++ b/apps/rpt_flow.pdf
Binary files differ