Index: /trunk/amportal/SQL/cdr_mysql_table.sql =================================================================== --- /trunk/amportal/SQL/cdr_mysql_table.sql (revision 523) +++ /trunk/amportal/SQL/cdr_mysql_table.sql (revision 523) @@ -0,0 +1,18 @@ +CREATE TABLE cdr ( + calldate datetime NOT NULL default '0000-00-00 00:00:00', + clid varchar(80) NOT NULL default '', + src varchar(80) NOT NULL default '', + dst varchar(80) NOT NULL default '', + dcontext varchar(80) NOT NULL default '', + channel varchar(80) NOT NULL default '', + dstchannel varchar(80) NOT NULL default '', + lastapp varchar(80) NOT NULL default '', + lastdata varchar(80) NOT NULL default '', + duration int(11) NOT NULL default '0', + billsec int(11) NOT NULL default '0', + disposition varchar(45) NOT NULL default '', + amaflags int(11) NOT NULL default '0', + accountcode varchar(20) NOT NULL default '', + uniqueid varchar(32) NOT NULL default '', + userfield varchar(255) NOT NULL default '' +); Index: /trunk/amportal/SQL/newinstall.sql =================================================================== --- /trunk/amportal/SQL/newinstall.sql (revision 523) +++ /trunk/amportal/SQL/newinstall.sql (revision 523) @@ -0,0 +1,170 @@ +-- phpMyAdmin SQL Dump +-- version 2.6.0-alpha1 +-- http://www.phpmyadmin.net +-- +-- Host: localhost +-- Generation Time: May 20, 2004 at 04:00 PM +-- Server version: 3.23.58 +-- PHP Version: 4.3.2 +-- +-- Database : `asterisk` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `admin` +-- + +CREATE TABLE IF NOT EXISTS `admin` ( + `variable` varchar(20) NOT NULL default '', + `value` varchar(80) NOT NULL default '', + PRIMARY KEY (`variable`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `admin` +-- + +INSERT INTO `admin` VALUES ('need_reload', 'false'); +INSERT INTO `admin` VALUES ('version','1.10.007'); +-- -------------------------------------------------------- + +-- +-- Table structure for table `extensions` +-- + +CREATE TABLE IF NOT EXISTS `extensions` ( + `context` varchar(45) NOT NULL default 'default', + `extension` varchar(45) NOT NULL default '', + `priority` int(2) NOT NULL default '1', + `application` varchar(45) NOT NULL default '', + `args` varchar(255) default NULL, + `descr` text, + `flags` int(1) NOT NULL default '0', + PRIMARY KEY (`context`,`extension`,`priority`) +) TYPE=MyISAM; + + +-- +-- Create a default route (9 to get out) +-- + +INSERT INTO extensions (context, extension, priority, application, args) VALUES + ('outrt-001-9_outside','_9.','1','Macro','dialout-trunk,1,${EXTEN:1}'); + +INSERT INTO extensions (context, extension, priority, application, args, descr) VALUES + ('outrt-001-9_outside','_9.','2','Macro','outisbusy','No available circuits'); + +INSERT INTO extensions (context, extension, priority, application, args, descr, flags) VALUES + ('outbound-allroutes','include','1','outrt-001-9_outside','','','2'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `globals` +-- + +CREATE TABLE IF NOT EXISTS `globals` ( + `variable` char(20) NOT NULL default '', + `value` char(50) NOT NULL default '', + PRIMARY KEY (`variable`,`value`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `globals` +-- + +INSERT INTO `globals` VALUES ('CALLFILENAME', '""'); +INSERT INTO `globals` VALUES ('DIAL_OPTIONS', 'tr'); +INSERT INTO `globals` VALUES ('DIAL_OUT', '9'); +INSERT INTO `globals` VALUES ('FAX', ''); +INSERT INTO `globals` VALUES ('FAX_RX', 'system'); +INSERT INTO `globals` VALUES ('FAX_RX_EMAIL', 'fax@mydomain.com'); +INSERT INTO `globals` VALUES ('INCOMING', 'group-all'); +INSERT INTO `globals` VALUES ('NULL', '""'); +INSERT INTO `globals` VALUES ('OPERATOR', ''); +INSERT INTO `globals` VALUES ('PARKNOTIFY', 'SIP/200'); +INSERT INTO `globals` VALUES ('RECORDEXTEN', '""'); +INSERT INTO `globals` VALUES ('RINGTIMER', '15'); +INSERT INTO `globals` VALUES ('DIRECTORY', 'last'); +INSERT INTO `globals` VALUES ('AFTER_INCOMING', ''); +INSERT INTO `globals` VALUES ('IN_OVERRIDE', 'forcereghours'); +INSERT INTO `globals` VALUES ('REGTIME', '7:55-17:05'); +INSERT INTO `globals` VALUES ('REGDAYS', 'mon-fri'); +INSERT INTO `globals` VALUES ('DIRECTORY_OPTS', ''); +INSERT INTO `globals` VALUES ('DIALOUTIDS', '1'); +INSERT INTO `globals` VALUES ('OUT_1', 'ZAP/g0'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `sip` +-- + +CREATE TABLE IF NOT EXISTS `sip` ( + `id` bigint(11) NOT NULL default '-1', + `keyword` varchar(20) NOT NULL default '', + `data` varchar(150) NOT NULL default '', + `flags` int(1) NOT NULL default '0', + PRIMARY KEY (`id`,`keyword`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `sip` +-- + +-- ---------------------------------------------------------- + + +-- +-- Table structure for table `ampusers` +-- + +CREATE TABLE IF NOT EXISTS `ampusers` ( + `username` varchar(20) NOT NULL default '', + `password` varchar(20) NOT NULL default '', + `extension_low` varchar(20) NOT NULL default '', + `extension_high` varchar(20) NOT NULL default '', + `deptname` varchar(20) NOT NULL default '', + `sections` varchar(255) NOT NULL default '', + PRIMARY KEY (`username`) +) TYPE=MyISAM; + + +-- +-- Table structure for table `iax` +-- + +CREATE TABLE IF NOT EXISTS `iax` ( + `id` bigint(11) NOT NULL default '-1', + `keyword` varchar(20) NOT NULL default '', + `data` varchar(150) NOT NULL default '', + `flags` int(1) NOT NULL default '0', + PRIMARY KEY (`id`,`keyword`) + ) TYPE=MyISAM; + + +-- +-- Table structure for table `zap` +-- + +CREATE TABLE IF NOT EXISTS `zap` ( + `id` bigint(11) NOT NULL default '-1', + `keyword`varchar(20) NOT NULL default '', + `data`varchar(150) NOT NULL default '', + `flags` int(1) NOT NULL default '0', + PRIMARY KEY (`id`,`keyword`) + ) TYPE=MyISAM; + +-- +-- Table structure for table `queues` +-- + +CREATE TABLE IF NOT EXISTS `queues` ( + `id` bigint(11) NOT NULL default '-1', + `keyword` varchar(20) NOT NULL default '', + `data` varchar(150) NOT NULL default '', + `flags` int(1) NOT NULL default '0', + PRIMARY KEY (`id`,`keyword`,`data`) +) TYPE=MyISAM;; Index: /trunk/amportal/amp_conf/astetc/sip.conf =================================================================== --- /trunk/amportal/amp_conf/astetc/sip.conf (revision 523) +++ /trunk/amportal/amp_conf/astetc/sip.conf (revision 523) @@ -0,0 +1,17 @@ +; Note: If your SIP devices are behind a NAT and your Asterisk +; server isn't, try adding "nat=1" to each peer definition to +; solve translation problems. + +[general] + +port = 5060 ; Port to bind to (SIP is 5060) +bindaddr = 0.0.0.0 ; Address to bind to (all addresses on machine) +disallow=all +allow=ulaw +allow=alaw +context = from-sip-external ; Send unknown SIP callers to this context +callerid = Unknown + +#include sip_nat.conf +#include sip_custom.conf +#include sip_additional.conf Index: /trunk/amportal/amp_conf/astetc/cdr_mysql.conf =================================================================== --- /trunk/amportal/amp_conf/astetc/cdr_mysql.conf (revision 523) +++ /trunk/amportal/amp_conf/astetc/cdr_mysql.conf (revision 523) @@ -0,0 +1,19 @@ + ; + ; Note - if the database server is hosted on the same machine as the + ; asterisk server, you can achieve a local Unix socket connection by + ; setting hostname=localhost + ; + ; port and sock are both optional parameters. If hostname is specified + ; and is not "localhost", then cdr_mysql will attempt to connect to the + ; port specified or use the default port. If hostname is not specified + ; or if hostname is "localhost", then cdr_mysql will attempt to connect + ; to the socket file specified by sock or otherwise use the default socket + ; file. + ; + [global] + hostname=localhost + dbname=asteriskcdrdb + password=AMPDBPASS + user=AMPDBUSER + ;port=3306 + ;sock=/tmp/mysql.sock Index: /trunk/amportal/amp_conf/astetc/extensions.conf =================================================================== --- /trunk/amportal/amp_conf/astetc/extensions.conf (revision 523) +++ /trunk/amportal/amp_conf/astetc/extensions.conf (revision 523) @@ -0,0 +1,690 @@ +; Asterisk Management Portal (AMP) +; Copyright (C) 2004 Coalescent Systems Inc + +; dialparties.agi (http://www.sprackett.com/asterisk/) +; Asterisk::AGI (http://asterisk.gnuinter.net/) +; gsm (http://www.ibiblio.org/pub/Linux/utils/compress/!INDEX.short.html) +; loligo sounds (http://www.loligo.com/asterisk/sounds/) +; mpg123 (http://voip-info.org/wiki-Asterisk+config+musiconhold.conf) + + +; include extension contexts generated from AMP +#include extensions_additional.conf + +; Customizations to this dialplan should be made in extensions_custom.conf +; See extensions_custom.conf.sample for an example +#include extensions_custom.conf + +[from-trunk] ; just an alias since VoIP shouldn't be called PSTN +include => from-pstn + +[from-pstn] +include => from-pstn-custom ; create this context in extensions_custom.conf to include customizations +include => ext-did +include => from-pstn-timecheck ; this has to be included otherwise it overrides ext-did +exten => fax,1,Goto(ext-fax,in_fax,1) + +[from-pstn-timecheck] +exten => .,1,Goto(s,1) ; catch-all matching for calls that have DID info (if a DID route hasn't matched them) +exten => s,1,GotoIf($[${IN_OVERRIDE} = forcereghours]?from-pstn-reghours,s,1:) +exten => s,2,GotoIf($[${IN_OVERRIDE} = forceafthours]?from-pstn-afthours,s,1:) +exten => s,3,GotoIfTime(${REGTIME}|${REGDAYS}|*|*?from-pstn-reghours,s,1:) +exten => s,4,Goto(from-pstn-afthours,s,1) + +[from-pstn-reghours] +exten => s,1,GotoIf($[${FAX_RX} = disabled]?from-pstn-reghours-nofax,s,1:2) ; if fax detection is disabled, then jump to from-pstn-nofax - else continue +exten => s,2,Answer +exten => s,3,Wait(1) +exten => s,4,SetVar(intype=${INCOMING}) +exten => s,5,Cut(intype=intype,-,1) +exten => s,6,GotoIf($[${intype} = EXT]?7:9) ; If INCOMING starts with EXT, then assume its an extension +exten => s,7,Wait(3) ;wait 3 more second to make sure this isn't a fax before dialing someone +exten => s,8,Goto(ext-local,${INCOMING:4},1) +exten => s,9,GotoIf($[${intype} = GRP]?10:12) ; If INCOMING starts with GRP, then assume its a ring group +exten => s,10,Wait(3) +exten => s,11,Goto(ext-group,${INCOMING:4},1) +exten => s,12,GotoIf($[${intype} = QUE]?13:15) +exten => s,13,Wait(3) +exten => s,14,Goto(ext-queues,${INCOMING:4},1) +exten => s,15,Goto(${INCOMING},s,1) ; not EXT or GR1 - it's an auto attendant +exten => fax,1,Goto(ext-fax,in_fax,1) +exten => h,1,Hangup + +[from-pstn-reghours-nofax] +exten => s,1,SetVar(intype=${INCOMING}) +exten => s,2,Cut(intype=intype,-,1) +exten => s,3,GotoIf($[${intype} = EXT]?4:5) ; If INCOMING starts with EXT, then assume its an extension +exten => s,4,Goto(ext-local,${INCOMING:4},1) +exten => s,5,GotoIf($[${intype} = GRP]?6:7) ; If INCOMING starts with GRP, then assume its a ring group +exten => s,6,Goto(ext-group,${INCOMING:4},1) +exten => s,7,GotoIf($[${intype} = QUE]?8:11) ;queue +exten => s,8,Answer ; answer call before queue +exten => s,9,Wait(1) +exten => s,10,Goto(ext-queues,${INCOMING:4},1) +exten => s,11,Answer ; answer call before auto attendant +exten => s,12,Wait(1) +exten => s,13,Goto(${INCOMING},s,1) ; not EXT or GR1 - it's an auto attendant +exten => h,1,Hangup + +[from-pstn-afthours] +exten => s,1,GotoIf($[${FAX_RX} = disabled]?from-pstn-afthours-nofax,s,1:2) ; if fax detection is disabled, then jump to from-pstn-nofax - else continue +exten => s,2,Answer +exten => s,3,Wait(1) +exten => s,4,SetVar(intype=${AFTER_INCOMING}) +exten => s,5,Cut(intype=intype,-,1) +exten => s,6,GotoIf($[${intype} = EXT]?7:9) ; If INCOMING starts with EXT, then assume its an extension +exten => s,7,Wait(3) ;wait 3 more second to make sure this isn't a fax before dialing someone +exten => s,8,Goto(ext-local,${AFTER_INCOMING:4},1) +exten => s,9,GotoIf($[${intype} = GRP]?10:12) ; If INCOMING starts with GRP, then assume its a ring group +exten => s,10,Wait(3) +exten => s,11,Goto(ext-group,${AFTER_INCOMING:4},1) +exten => s,12,GotoIf($[${intype} = QUE]?13:15) +exten => s,13,Wait(3) +exten => s,14,Goto(ext-queues,${AFTER_INCOMING:4},1) +exten => s,15,Goto(${AFTER_INCOMING},s,1) ; not EXT or GR1 - it's an auto attendant +exten => fax,1,Goto(ext-fax,in_fax,1) +exten => h,1,Hangup + +[from-pstn-afthours-nofax] +exten => s,1,SetVar(intype=${AFTER_INCOMING}) +exten => s,2,Cut(intype=intype,-,1) +exten => s,3,GotoIf($[${intype} = EXT]?4:5) ; If INCOMING starts with EXT, then assume its an extension +exten => s,4,Goto(ext-local,${AFTER_INCOMING:4},1) +exten => s,5,GotoIf($[${intype} = GRP]?6:7) ; If INCOMING starts with GRP, then assume its a ring group +exten => s,6,Goto(ext-group,${AFTER_INCOMING:4},1) +exten => s,7,GotoIf($[${intype} = QUE]?8:11) ;queue +exten => s,8,Answer ; answer call before queue +exten => s,9,Wait(1) +exten => s,10,Goto(ext-queues,${AFTER_INCOMING:4},1) +exten => s,11,Answer ; answer call before auto attendant +exten => s,12,Wait(1) +exten => s,13,Goto(${AFTER_INCOMING},s,1) ; not EXT or GR1 - it's an auto attendant +exten => h,1,Hangup + +; ############################################################################ +; Macros [macro] +; ############################################################################ + +; Rings one or more extensions. Handles things like call forwarding and DND +; We don't call dial directly for anything internal anymore. +; ARGS: $TIMER, $OPTIONS, $EXT1, $EXT2, $EXT3, ... +; Use a Macro call such as the following: +; Macro(dial,$DIAL_TIMER,$DIAL_OPTIONS,$EXT1,$EXT2,$EXT3,...) +[macro-dial] +exten => s,1,GotoIf($[ "${MACRO_CONTEXT}" = "macro-rg-group" ]?4:2) ; if this is from rg-group, don't strip prefix +exten => s,2,GotoIf($[${CALLERIDNAME:0:${LEN(${RGPREFIX})}} != ${RGPREFIX}]?4:3) ; check for ring-group prefix +exten => s,3,SetCIDName(${CALLERIDNAME:${LEN(${RGPREFIX})}}) ; strip off prefix +exten => s,4,AGI,dialparties.agi +exten => s,5,NoOp(Returned from dialparties with no extensions to call) +exten => s,6,SetVar(DIALSTATUS=BUSY) +exten => s,10,Dial(${ds}) ; dialparties will set the priority to 10 if $ds is not null +exten => s,20,NoOp(Returned from dialparties with hunt groups to dial ) +exten => s,21,SetVar(HuntLoop=0) +exten => s,22,GotoIf($[$[${HuntMembers} >= 1]?30 ) ; if this is from rg-group, don't strip prefix +exten => s,23,NoOp(Returning there are no members left in the hunt group to ring) + +exten => s,30,SetVar(HuntMember=HuntMember${HuntLoop}) +exten => s,31,GotoIf($[$[${CALLTRACE_HUNT} != "" ] & $["${RingGroupMethod}" = "hunt" ]]?32:35 ) ; Set CAll Trace for Hunt member we are going to call + exten => s,32,Cut(CT_EXTEN=ARG3,,$[${HuntLoop} + 1]) + exten => s,33,DBput(CALLTRACE/${CT_EXTEN}=${CALLTRACE_HUNT}) + exten => s,34,Goto(s,42) + +exten => s,35,GotoIf($[$[${CALLTRACE_HUNT} != "" ] & $["${RingGroupMethod}" = "memoryhunt" ]]?36:50 ) ;Set Call Trace for each hunt member we are going to call "Memory groups have multiple members to set CALL TRACE For hence the loop + exten => s,36,SetVar(CTLoop=0) + exten => s,37,GotoIf($[${CTLoop} > ${HuntLoop}]?42 ) ; if this is from rg-group, don't strip prefix + exten => s,38,Cut(CT_EXTEN=ARG3,,$[${CTLoop} + 1]) + exten => s,39,DBput(CALLTRACE/${CT_EXTEN}=${CALLTRACE_HUNT}) + exten => s,40,SetVar(CTLoop=$[1 + ${CTLoop}]) + exten => s,41,Goto(s,37) +exten => s,42,Dial(${${HuntMember}}${ds} ) ; dialparties will set the priority to 20 if $ds is not null and its a hunt group +exten => s,43,SetVar(HuntLoop=$[1 + ${HuntLoop}]) +exten => s,44,SetVar(HuntMembers=$[${HuntMembers} - 1]) +exten => s,45,Goto(s,22) +exten => s,50,DBdel(CALLTRACE/${CT_EXTEN}) +exten => s,51,Goto(s,42) + + + +;exten => s,1,GotoIf($[ "${MACRO_CONTEXT}" = "macro-rg-group" ]?4:2) ; if this is from rg-group, don't strip prefix +;exten => s,2,GotoIf($[${CALLERIDNAME:0:${LEN(${RGPREFIX})}} != ${RGPREFIX}]?4:3) ; check for ring-group prefix +;exten => s,3,SetCIDName(${CALLERIDNAME:${LEN(${RGPREFIX})}}) ; strip off prefix +;exten => s,4,AGI,dialparties.agi +;exten => s,5,NoOp(Returned from dialparties with no extensions to call) +;exten => s,6,SetVar(DIALSTATUS=BUSY) +;exten => s,10,Dial(${ds}) ; dialparties will set the priority to 10 if $ds is not null + +; Ring an extension, if the extension is busy or there is no answer send it +; to voicemail +; ARGS: $VMBOX, $EXT +[macro-exten-vm] +exten => s,1,Setvar(FROMCONTEXT=exten-vm) +exten => s,2,Macro(record-enable,${ARG2},IN) +exten => s,3,Macro(dial,${RINGTIMER},${DIAL_OPTIONS},${ARG2}) +exten => s,4,GotoIf($[${CHANNEL:0:5} = Local]?s-${DIALSTATUS},1) ; if the channel is Local, then do not go to voicemail. This is primarily to avoid vm for call-forwarded extensions in ring groups +exten => s,5,GotoIf($[${ARG1} = novm]?s-${DIALSTATUS},1) ; no voicemail in use for this extension +exten => s,6,NoOp(Sending to Voicemail box ${ARG1}) +exten => s,7,Macro(vm,${ARG1},${DIALSTATUS}) +exten => s-BUSY,1,NoOp(Extension is reporting BUSY and has no Voicemail) +exten => s-BUSY,2,Busy() +exten => s-BUSY,3,Wait(60) +exten => s-BUSY,4,NoOp() +exten => _s-.,1,Congestion() + +[macro-vm] +exten => s,1,Goto(s-${ARG2},1) +exten => s-BUSY,1,Voicemail(b${ARG1}) ; Voicemail Busy message +exten => s-BUSY,2,Hangup() +exten => _s-.,1,Voicemail(u${ARG1}) ; Voicemail Unavailable message +exten => _s-.,2,Hangup() +exten => o,1,Background(one-moment-please) ; 0 during vm message will hangup +exten => o,2,GotoIf($["foo${FROM_DID}" = "foo"]?from-pstn,s,1:from-pstn,${FROM_DID},1) +exten => a,1,VoiceMailMain(${ARG1}) +exten => a,2,Hangup + +; For some reason, if I don't run setCIDname, CALLERIDNAME will be blank in my AGI +; ARGS: none +[macro-fixcid] +exten => s,1,SetCIDName(${CALLERIDNAME}) + +; Ring groups of phones +; ARGS: comma separated extension list +; 1 - Ring Group Strategy +; 2 - ringtimer +; 3 - prefix +; 4 - extension list +[macro-rg-group] +exten => s,1,GotoIf($[${CALLERIDNAME:0:${LEN(${RGPREFIX})}} != ${RGPREFIX}]?3:2) ; check for old prefix +exten => s,2,SetCIDName(${CALLERIDNAME:${LEN(${RGPREFIX})}}) ; strip off old prefix +exten => s,3,Setvar(RGPREFIX=${ARG3}) ; set new prefix +exten => s,4,SetCIDName(${RGPREFIX}${CALLERIDNAME}) ; add prefix to callerid name +exten => s,5,Setvar(RecordMethod=Group) ; set new prefix +exten => s,6,Macro(record-enable,${MACRO_EXTEN},${RecordMethod}) +exten => s,7,SetVar(RingGroupMethod=${ARG1}) ; +exten => s,8,Macro(dial,${ARG2},${DIAL_OPTIONS},${ARG4}) +exten => s,9,SetVar(RingGroupMethod='') ; + +;exten => s,1,GotoIf($[${CALLERIDNAME:0:${LEN(${RGPREFIX})}} != ${RGPREFIX}]?3:2) ; check for old prefix +;exten => s,2,SetCIDName(${CALLERIDNAME:${LEN(${RGPREFIX})}}) ; strip off old prefix +;exten => s,3,Setvar(RGPREFIX=${ARG2}) ; set new prefix +;exten => s,4,SetCIDName(${RGPREFIX}${CALLERIDNAME}) ; add prefix to callerid name +;exten => s,5,Setvar(RecordMethod=Group) ; set new prefix +;exten => s,6,Macro(record-enable,${MACRO_EXTEN},${RecordMethod}) +;exten => s,7,Macro(dial,${ARG1},${DIAL_OPTIONS},${ARG3}) + + + +; +; Outgoing channel(s) are busy ... inform the client +; +[macro-outisbusy] +exten => s,1,Playback(allison7/all-circuits-busy-now) +exten => s,2,Playback(allison7/pls-try-call-later) +exten => s,3,Macro(hangupcall) + +; What to do on hangup. +[macro-hangupcall] +exten => s,1,ResetCDR(w) +exten => s,2,NoCDR() +exten => s,3,Wait(5) +exten => s,4,Hangup + +[macro-faxreceive] +exten => s,1,SetVar(FAXFILE=/var/spool/asterisk/fax/${UNIQUEID}.tif) +exten => s,2,SetVar(EMAILADDR=${FAX_RX_EMAIL}) +exten => s,3,rxfax(${FAXFILE}) +exten => s,103,SetVar(EMAILADDR=${FAX_RX_EMAIL}) +exten => s,104,Goto(3) + +; dialout and strip the prefix +[macro-dialout] +exten => s,1,GotoIf($[foo${ECID${CALLERIDNUM}} = foo]?4) ;check for CID override for exten +exten => s,2,SetCallerID(${ECID${CALLERIDNUM}}) +exten => s,3,Goto(6) +exten => s,4,GotoIf($[foo${OUTCID_${ARG1}} = foo]?6) ;check for CID override for trunk +exten => s,5,SetCallerID(${OUTCID_${ARG1}}) +exten => s,6,SetVar(length=${LEN(${DIAL_OUT_${ARG1}})}) +exten => s,7,Dial(${OUT_${ARG1}}/${ARG2:${length}}) +exten => s,8,Congestion +exten => s,108,Macro(outisbusy) + + +; dialout using default OUT trunk - no prefix +[macro-dialout-default] +exten => s,1,Macro(record-enable,${CALLERIDNUM},OUT) +exten => s,2,GotoIf($[foo${ECID${CALLERIDNUM}} = foo]?5) ;check for CID override for exten +exten => s,3,SetCallerID(${ECID${CALLERIDNUM}}) +exten => s,4,Goto(7) +exten => s,5,GotoIf($[foo${OUTCID} = foo]?7) ;check for CID override for trunk +exten => s,6,SetCallerID(${OUTCID}) +exten => s,7,Dial(${OUT}/${ARG1}) +exten => s,8,Congestion +exten => s,108,Macro(outisbusy) + +; dialout using a trunk, using pattern matching (don't strip any prefix) +; arg1 = trunk number, arg2 = number, arg3 = route password +[macro-dialout-trunk] +exten => s,1,GotoIf($[foo${ARG3} = foo]?3:2)) ; arg3 is pattern password +exten => s,2,Authenticate(${ARG3}) +exten => s,3,Macro(record-enable,${CALLERIDNUM},OUT) +exten => s,4,GotoIf($[foo${ECID${CALLERIDNUM}} = foo]?7) ;check for CID override for exten +exten => s,5,SetCallerID(${ECID${CALLERIDNUM}}) +exten => s,6,Goto(9) +exten => s,7,GotoIf($[foo${OUTCID_${ARG1}} = foo]?9) ;check for CID override for trunk +exten => s,8,SetCallerID(${OUTCID_${ARG1}}) +exten => s,9,SetGroup(OUT_${ARG1}) +exten => s,10,CheckGroup(${OUTMAXCHANS_${ARG1}}) +; if we've used up the max channels, continue at 109 (n+101) +exten => s,11,SetVar(DIAL_NUMBER=${ARG2}) +exten => s,12,SetVar(DIAL_TRUNK=${ARG1}) +exten => s,13,AGI(fixlocalprefix) ; this sets DIAL_NUMBER to the proper dial string for this trunk +exten => s,14,SetVar(OUTNUM=${OUTPREFIX_${ARG1}}${DIAL_NUMBER}) ; OUTNUM is the final dial number +exten => s,15,Cut(custom=OUT_${ARG1},:,1) ; Custom trunks are prefixed with "AMP:" +exten => s,16,GotoIf($[${custom} = AMP]?19) +exten => s,17,Dial(${OUT_${ARG1}}/${OUTNUM}) ; Regular Trunk Dial +exten => s,18,Goto(s-${DIALSTATUS},1) + +; This is a custom trunk. Substitute $OUTNUM$ with the actual number and rebuild the dialstring +; example trunks: "AMP:CAPI/XXXXXXXX:b$OUTNUM$,30,r", "AMP:OH323/$OUTNUM$@XX.XX.XX.XX:XXXX" +exten => s,19,Cut(pre_num=OUT_${ARG1},$,1) +exten => s,20,Cut(the_num=OUT_${ARG1},$,2) ; this is where we expect to find string OUTNUM +exten => s,21,Cut(post_num=OUT_${ARG1},$,3) +exten => s,22,GotoIf($[${the_num} = OUTNUM]?23:24) ; if we didn't find "OUTNUM", then skip to Dial +exten => s,23,SetVar(the_num=${OUTNUM}) ; replace "OUTNUM" with the actual number to dial +exten => s,24,Dial(${pre_num:4}${the_num}${post_num}) +exten => s,25,Goto(s-${DIALSTATUS},1) + +exten => s,111,Noop(max channels used up) +exten => s-BUSY,1,NoOp(Trunk is reporting BUSY) +exten => s-BUSY,2,Busy() +exten => s-BUSY,3,Wait(60) +exten => s-BUSY,4,NoOp() + +exten => _s-.,1,NoOp(Dial failed due to ${DIALSTATUS}) + +; Adds a dynamic agent/member to a Queue +; Prompts for call-back number - in not entered, uses CIDNum +[macro-agent-add] +exten => s,1,Wait(1) +exten => s,2,NoOp +exten => s,3,Read(CALLBACKNUM,agent-user) ; get callback number from user +exten => s,4,GotoIf($[foo${CALLBACKNUM} = foo]?5:7)) ; if user just pressed # or timed out, use cidnum +exten => s,5,SetVar(CALLBACKNUM=${CALLERIDNUM}) +exten => s,6,GotoIf($[foo${CALLBACKNUM} = foo]?2)) ; if still no number, start over +exten => s,7,GotoIf($[foo${ARG2} = foo]?9:8)) ; arg2 is queue password +exten => s,8,Authenticate(${ARG2}) +exten => s,9,AddQueueMember(${ARG1}|Local/${CALLBACKNUM}@from-internal) ; using chan_local allows us to have agents over trunks +exten => s,10,Wait(1) +exten => s,11,Playback(agent-loginok) +exten => s,12,Hangup() + +; Removes a dynamic agent/member from a Queue +; Prompts for call-back number - in not entered, uses CIDNum +[macro-agent-del] +exten => s,1,Wait(1) +exten => s,2,NoOp +exten => s,3,Read(CALLBACKNUM,agent-user) ; get callback number from user +exten => s,4,GotoIf($[foo${CALLBACKNUM} = foo]?5:7)) ; if user just pressed # or timed out, use cidnum +exten => s,5,SetVar(CALLBACKNUM=${CALLERIDNUM}) +exten => s,6,GotoIf($[foo${CALLBACKNUM} = foo]?2)) ; if still no number, start over +exten => s,7,RemoveQueueMember(${ARG1}|Local/${CALLBACKNUM}@from-internal) +exten => s,8,Wait(1) +exten => s,9,Playback(agent-loggedoff) +exten => s,10,Hangup() + +; arg1 = trunk number, arg2 = number +[macro-dialout-enum] +exten => s,1,Macro(record-enable,${CALLERIDNUM},OUT) +exten => s,2,GotoIf($[foo${ECID${CALLERIDNUM}} = foo]?5) ;check for CID override for exten +exten => s,3,SetCallerID(${ECID${CALLERIDNUM}}) +exten => s,4,Goto(7) +exten => s,5,GotoIf($[foo${OUTCID_${ARG1}} = foo]?7) ;check for CID override for trunk +exten => s,6,SetCallerID(${OUTCID_${ARG1}}) +exten => s,7,SetGroup(OUT_${ARG1}) +exten => s,8,CheckGroup(${OUTMAXCHANS_${ARG1}}) ; if we've used up the max channels, continue at 108 (n+101) +exten => s,9,SetVar(DIAL_NUMBER=${ARG2}) +exten => s,10,SetVar(DIAL_TRUNK=${ARG1}) +exten => s,11,AGI(fixlocalprefix) ; this sets DIAL_NUMBER to the proper dial string for this trunk +exten => s,12,EnumLookup(${DIAL_NUMBER}) +exten => s,13,GotoIf($[$[${ENUM:0:3} = SIP] | $[${ENUM:0:3} = IAX]]?14:63) +exten => s,14,Dial(${ENUM}) +exten => s,15,Goto(s-${DIALSTATUS},1) +; if dial fails (ie, all channels are busy), continue at 116 (n+101) + +; exit points for macro +exten => s,63,NoOp(EnumLookup failed) +exten => s,109,NoOp(max channels used up) + +exten => s-BUSY,1,NoOp(Trunk is reporting BUSY) +exten => s-BUSY,2,Busy() +exten => s-BUSY,3,Wait(60) +exten => s-BUSY,4,NoOp() + +exten => _s-.,1,NoOp(Dial failed due to ${DIALSTATUS}) + +[macro-record-enable] +exten => s,1,GotoIf(${LEN(${BLINDTRANSFER})} > 0?2:4) +exten => s,2,ResetCDR(w) +exten => s,3,StopMonitor() +exten => s,4,GotoIf($["${ARG2}" = "OUT"]?5:8) +exten => s,5,DBGet(RecEnable=RECORD-OUT/${ARG1}) +exten => s,6,SetVar(CALLFILENAME=OUT${ARG1}-${TIMESTAMP}-${UNIQUEID}) +exten => s,7,Goto(s,14) +exten => s,8,GotoIf($["${ARG2}" = "Group"]?9:12) +exten => s,9,AGI(recordingcheck) +exten => s,10,SetVar(CALLFILENAME=g${ARG1}-${TIMESTAMP}-${UNIQUEID}) +exten => s,11,Goto(s,14) +exten => s,12,DBGet(RecEnable=RECORD-IN/${ARG1}) +exten => s,13,SetVar(CALLFILENAME=${TIMESTAMP}-${UNIQUEID}) +exten => s,14,GotoIf($["${RecEnable}" = "ENABLED"]?15:99) +exten => s,15,Monitor(wav49,${CALLFILENAME}, mb) +exten => s,99,Noop(NO RECORDING NEEDED) +;exten => s,3,BackGround(for-quality-purposes) +;exten => s,4,BackGround(this-call-may-be) +;exten => s,5,BackGround(recorded) + +; This macro is for dev purposes and just dumps channel/app variables. Useful when designing new contexts. +[macro-dumpvars] +exten => s,1,Noop(ACCOUNTCODE=${ACCOUNTCODE}) +exten => s,2,Noop(ANSWEREDTIME=${ANSWEREDTIME}) +exten => s,3,Noop(BLINDTRANSFER=${BLINDTRANSFER}) +exten => s,4,Noop(CALLERID=${CALLERID}) +exten => s,5,Noop(CALLERIDNAME=${CALLERIDNAME}) +exten => s,6,Noop(CALLERIDNUM=${CALLERIDNUM}) +exten => s,7,Noop(CALLINGPRES=${CALLINGPRES}) +exten => s,8,Noop(CHANNEL=${CHANNEL}) +exten => s,9,Noop(CONTEXT=${CONTEXT}) +exten => s,10,Noop(DATETIME=${DATETIME}) +exten => s,11,Noop(DIALEDPEERNAME=${DIALEDPEERNAME}) +exten => s,12,Noop(DIALEDPEERNUMBER=${DIALEDPEERNUMBER}) +exten => s,13,Noop(DIALEDTIME=${DIALEDTIME}) +exten => s,14,Noop(DIALSTATUS=${DIALSTATUS}) +exten => s,15,Noop(DNID=${DNID}) +exten => s,16,Noop(EPOCH=${EPOCH}) +exten => s,17,Noop(EXTEN=${EXTEN}) +exten => s,18,Noop(HANGUPCAUSE=${HANGUPCAUSE}) +exten => s,19,Noop(INVALID_EXTEN=${INVALID_EXTEN}) +exten => s,20,Noop(LANGUAGE=${LANGUAGE}) +exten => s,21,Noop(MEETMESECS=${MEETMESECS}) +exten => s,22,Noop(PRIORITY=${PRIORITY}) +exten => s,23,Noop(RDNIS=${RDNIS}) +exten => s,24,Noop(SIPDOMAIN=${SIPDOMAIN}) +exten => s,25,Noop(SIP_CODEC=${SIP_CODEC}) +exten => s,26,Noop(SIPCALLID=${SIPCALLID}) +exten => s,27,Noop(SIPUSERAGENT=${SIPUSERAGENT}) +exten => s,28,Noop(TIMESTAMP=${TIMESTAMP}) +exten => s,29,Noop(TXTCIDNAME=${TXTCIDNAME}) +exten => s,30,Noop(UNIQUEID=${UNIQUEID}) +exten => s,31,Noop(TOUCH_MONITOR=${TOUCH_MONITOR}) +exten => s,32,Noop(MACRO_CONTEXT=${MACRO_CONTEXT}) +exten => s,33,Noop(MACRO_EXTEN=${MACRO_EXTEN}) +exten => s,34,Noop(MACRO_PRIORITY=${MACRO_PRIORITY}) + + +; ############################################################################ +; Applications [app] +; ############################################################################ +; +[app-directory] +;DIR-CONTEXT set in Digital Receptionist +exten => #,1,Wait(1) +exten => #,2,AGI(directory,${DIR-CONTEXT},ext-local,${DIRECTORY:0:1}${DIRECTORY_OPTS}o) +exten => #,3,Playback(vm-goodbye) +exten => #,4,Hangup +; *411 will access the entire directory (not just a single context) +exten => *411,1,Answer +exten => *411,2,Wait(1) +exten => *411,3,AGI(directory,general,ext-local,${DIRECTORY:0:1}${DIRECTORY_OPTS}) +exten => *411,4,Playback(vm-goodbye) +exten => *411,5,Hangup +exten => h,1,Hangup +exten => o,1,GotoIf($["foo${FROM_DID}" = "foo"]?from-pstn,s,1:from-pstn,${FROM_DID},1) + +[app-dnd] +exten => *78,1,Answer +exten => *78,2,Wait(1) +exten => *78,3,DBput(DND/${CALLERIDNUM}=YES) +exten => *78,4,Playback(allison7/do-not-disturb) +exten => *78,5,Playback(activated) +exten => *78,6,Macro(hangupcall) +exten => *79,1,Answer +exten => *79,2,Wait(1) +exten => *79,3,DBdel(DND/${CALLERIDNUM}) +exten => *79,4,Playback(allison7/do-not-disturb) +exten => *79,5,Playback(de-activated) +exten => *79,6,Macro(hangupcall) + +[app-messagecenter] +exten => *98,1,Answer +exten => *98,2,Wait(1) +exten => *98,3,VoiceMailMain(default) +exten => *98,4,Macro(hangupcall) +exten => _*98X.,1,Answer ; can dial *98 to skip 'mailbox' prompt. Useful for speedial. +exten => _*98X.,2,Wait(1) +exten => _*98X.,3,VoiceMailMain(${EXTEN:3}@default) +exten => _*98X.,4,Macro(hangupcall) +exten => *97,1,Answer +exten => *97,2,Wait(1) +exten => *97,3,VoicemailMain(${CALLERIDNUM}@default) +exten => *97,4,Macro(hangupcall) + +[app-callwaiting] +exten => *70,1,Answer +exten => *70,2,Wait(1) +exten => *70,3,DBput(CW/${CALLERIDNUM}=ENABLED) +exten => *70,4,Playback(callwaiting) +exten => *70,5,Playback(activated) +exten => *70,6,Macro(hangupcall) +exten => *71,1,Answer +exten => *71,2,Wait(1) +exten => *71,3,DBdel(CW/${CALLERIDNUM}) +exten => *71,4,Playback(callwaiting) +exten => *71,5,Playback(de-activated) +exten => *71,6,Macro(hangupcall) + +[app-callforward] +; dialed call forward app - forwards calling extension +exten => _*72.,1,DBput(CF/${CALLERIDNUM}=${EXTEN:3}) +exten => _*72.,2,Answer +exten => _*72.,3,Wait(1) +exten => _*72.,4,Playback(loligo/call-fwd-unconditional) +exten => _*72.,5,Playback(loligo/for) +exten => _*72.,6,Playback(loligo/extension) +exten => _*72.,7,SayDigits(${CALLERIDNUM}) +exten => _*72.,8,Playback(loligo/is-set-to) +exten => _*72.,9,SayDigits(${EXTEN:3}) +exten => _*72.,10,Macro(hangupcall) +; prompting call forward app - forwards entered extension +exten => *72,1,Answer +exten => *72,2,Wait(1) +exten => *72,3,BackGround(allison7/please-enter-your) +exten => *72,4,Playback(extension) +exten => *72,5,Read(fromext,then-press-pound) +exten => *72,6,Wait(1) +exten => *72,7,BackGround(ent-target-attendant) +exten => *72,8,Read(toext,then-press-pound) +exten => *72,9,Wait(1) +exten => *72,10,DBput(CF/${fromext}=${toext}) +exten => *72,11,Playback(call-fwd-unconditional) +exten => *72,12,Playback(for) +exten => *72,13,Playback(extension) +exten => *72,14,SayDigits(${fromext}) +exten => *72,15,Playback(is-set-to) +exten => *72,16,SayDigits(${toext}) +exten => *72,17,Macro(hangupcall) +; cancels dialed extension call forward +exten => _*73.,1,DBdel(CF/${EXTEN:3}) +exten => _*73.,2,Answer +exten => _*73.,3,Wait(1) +exten => _*73.,4,SayDigits(${EXTEN:3}) +exten => _*73.,5,Playback(call-fwd-cancelled) +exten => _*73.,6,Macro(hangupcall) +; cancels call forward for calling extension +exten => *73,1,DBdel(CF/${CALLERIDNUM}) +exten => *73,2,Answer +exten => *73,3,Wait(1) +exten => *73,4,Playback(loligo/call-fwd-cancelled) +exten => *73,5,Macro(hangupcall) +; dialed call forward on busy app - forwards calling extension when busy +exten => _*90.,1,DBput(CFB/${CALLERIDNUM}=${EXTEN:3}) +exten => _*90.,2,Answer +exten => _*90.,3,Wait(1) +exten => _*90.,4,Playback(loligo/call-fwd-on-busy) +exten => _*90.,5,Playback(loligo/for) +exten => _*90.,6,Playback(loligo/extension) +exten => _*90.,7,SayDigits(${CALLERIDNUM}) +exten => _*90.,8,Playback(loligo/is-set-to) +exten => _*90.,9,SayDigits(${EXTEN:3}) +exten => _*90.,10,Macro(hangupcall) +; cancels call forward on busy for calling extension +exten => *91,1,DBdel(CFB/${CALLERIDNUM}) +exten => *91,2,Answer +exten => *91,3,Wait(1) +exten => *91,4,Playback(call-fwd-on-busy) +exten => *91,5,Playback(de-activated) +exten => *91,6,Macro(hangupcall) +exten => h,1,Hangup + +[app-calltrace] +; We can't have our timeouts or dial digits collide with other applications +; or extensions, so we build the app in pieces +exten => *69,1,Goto(app-calltrace-perform,s,1) + +[app-calltrace-perform] +exten => s,1,Answer +exten => s,2,Wait(1) +exten => s,3,Background(allison7/info-about-last-call) +exten => s,4,Background(allison7/telephone-number) +exten => s,5,Dbget(lastcaller=CALLTRACE/${CALLERIDNUM}) +exten => s,6,GotoIf($[${lastcaller}]?7:13) +exten => s,7,SayDigits(${lastcaller}) +exten => s,8,DigitTimeout(3) +exten => s,9,ResponseTimeout(7) +exten => s,10,Background(loligo/to-call-this-number) +exten => s,11,Background(allison7/press-1) +exten => s,12,Goto(15) +exten => s,13,Playback(loligo/from-unknown-caller) +exten => s,14,Macro(hangupcall) +exten => s,15,NoOp +exten => 1,1,Goto(from-internal,${lastcaller},1); +exten => i,1,Playback(vm-goodbye) +exten => i,2,Macro(hangupcall) +exten => t,1,Playback(vm-goodbye) +exten => t,2,Macro(hangupcall) + + +; ############################################################################ +; Inbound Contexts [from] +; ############################################################################ + +[from-sip-external] + +;give external sip users congestion and hangup +exten => _.,1,AbsoluteTimeout(15) +exten => _.,2,Congestion +exten => _.,3,Hangup + +[from-internal] +;allow phones to use applications +include => app-directory +include => app-dnd +include => app-callforward +include => app-callwaiting +include => app-messagecenter +include => app-calltrace +include => parkedcalls +include => from-internal-custom +;allow phones to dial other extensions +include => ext-fax +include => ext-local +include => ext-group +include => ext-queues +include => ext-zapbarge +include => ext-meetme +include => ext-record +include => ext-test +;allow phones to access trunks +include => outbound-allroutes +exten => s,1,Macro(hangupcall) +exten => h,1,Macro(hangupcall) + +; ############################################################################ +; Extension Contexts [ext] +; ############################################################################ + +[ext-zapbarge] +exten => 888,1,SetGroup(${CALLERIDNUM}) +exten => 888,2,Answer +exten => 888,3,Wait(1) +exten => 888,4,ZapBarge +exten => 888,5,Hangup + +[ext-meetme] +exten => _8X,1,Answer +exten => _8X,2,Wait(1) +exten => _8X,3,GotoIf($[${CALLERIDNUM} = ${EXTEN:1}]?5:4) +exten => _8X,4,MeetMe(${EXTEN}|sM) +exten => _8X,5,MeetMe(${EXTEN}|asM) + +exten => _8XX,1,Answer +exten => _8XX,2,Wait(1) +exten => _8XX,3,GotoIf($[${CALLERIDNUM} = ${EXTEN:1}]?5:4) +exten => _8XX,4,MeetMe(${EXTEN}|sM) +exten => _8XX,5,MeetMe(${EXTEN}|asM) + +exten => _8XXX,1,Answer +exten => _8XXX,2,Wait(1) +exten => _8XXX,3,GotoIf($[${CALLERIDNUM} = ${EXTEN:1}]?5:4) +exten => _8XXX,4,MeetMe(${EXTEN}|sM) +exten => _8XXX,5,MeetMe(${EXTEN}|asM) + +exten => _8XXXX,1,Answer +exten => _8XXXX,2,Wait(1) +exten => _8XXXX,3,GotoIf($[${CALLERIDNUM} = ${EXTEN:1}]?5:4) +exten => _8XXXX,4,MeetMe(${EXTEN}|sM) +exten => _8XXXX,5,MeetMe(${EXTEN}|asM) + + +[ext-fax] +exten => s,1,Answer +exten => s,2,Goto(in_fax,1) +exten => in_fax,1,GotoIf($[${FAX_RX} = system]?2:analog_fax,1) +exten => in_fax,2,Macro(faxreceive) +exten => in_fax,3,system(tiff2ps -2eaz -w 8.5 -h 11 ${FAXFILE} | ps2pdf - ${FAXFILE}.pdf) +exten => in_fax,4,system(mime-construct --to ${EMAILADDR} --subject "Fax from ${CALLERIDNUM} ${CALLERIDNAME}" --attachment ${CALLERIDNUM}.pdf --type application/pdf --file ${FAXFILE}.pdf) +exten => in_fax,5,system(rm ${FAXFILE} ${FAXFILE}.pdf) +exten => in_fax,6,Hangup +exten => analog_fax,1,GotoIf($[${FAX_RX} = disabled]?3:2) ;if fax is disabled, just hang up +exten => analog_fax,2,Dial(${FAX_RX},20,d) +exten => analog_fax,3,Hangup +;exten => out_fax,1,wait(7) +exten => out_fax,1,txfax(${TXFAX_NAME}|caller) +exten => out_fax,2,Hangup +exten => h,1,Hangup() + +[ext-record] +exten => *77,1,Wait(2) +exten => *77,2,Record(${CALLERIDNUM}ivrrecording:wav) +exten => *77,3,Wait(2) +exten => *77,4,Hangup +exten => *99,1,Playback(${CALLERIDNUM}ivrrecording) +exten => *99,2,Wait(2) +exten => *99,3,Hangup + +;this is where parked calls go if they time-out. Should probably re-ring +[default] +include => ext-local +exten => s,1,Playback(vm-goodbye) +exten => s,2,Macro(hangupcall) + +[ext-test] +exten => 7777,1,Goto(from-pstn,s,1) +exten => 666,1,Goto(ext-fax,in_fax,1) +exten => h,1,Macro(hangupcall) + +;echo test +exten => *43,1,Answer +exten => *43,2,Wait(2) +exten => *43,3,Playback(demo-echotest) +exten => *43,4,Echo +exten => *43,5,Playback(demo-echodone) +exten => *43,6,Hangup Index: /trunk/amportal/amp_conf/astetc/modem.conf =================================================================== --- /trunk/amportal/amp_conf/astetc/modem.conf (revision 523) +++ /trunk/amportal/amp_conf/astetc/modem.conf (revision 523) @@ -0,0 +1,74 @@ +; +; Internet Phone Jack +; +; Configuration file +; +[interfaces] +; +; By default, incoming calls should come in on the "remote" context +; +context=remote +; +; Modem Drivers to load +; +driver=aopen +; +; Default language +; +;language=en +; +; We can optionally override the auto detection. This is necessary +; particularly if asterisk does not know about our kind of modem. +; +;type=autodetect +;type=aopen +; +; We can strip a given number of digits on outgoing dialing, so, for example +; you can have it dial "8871042" when given "98871042". +; +stripmsd=1 +; +; Type of dialing +; +dialtype=tone +;dialtype=pulse +; +; Mode selection. "Immediate" means that as soon as you dial, you're connected +; and the line is considered up. "Ring" means we wait until the ring cadence +; occurs at least once. "Answer" means we wait until the other end picks up. +; +;mode=answer +;mode=ring +mode=immediate +; +; List all devices we can use. +; +;device => /dev/ttyS3 +; +; ISDN example +; +;msn=39907835 +;device => /dev/ttyI0 + +;=============== +; More complex ISDN example +; +; A single device which listens to 3 MSNs +; the wildcard '*' can be used when all MSN's should be accepted. +; (The incoming number can be used to go directly into the extension +; with the matching number. I.e. if MSN 33 is called, (context,33) +; will tried first, than (context,s) and finally (default,s). +; +;msn=50780020 +;incomingmsn=50780020,50780021,50780022 +;device => /dev/ttyI2 +; +; two other devices, which are in group '1' and are used when an +; outgoing dial used: exten => s,1,Dial,Modem/g1:1234|60|r +; (we do not need more outgoing devices, since ISDN2 has only 2 channels.) +; Lines can be in more than one group (1-31); comma seperated list. +; +group=1 ; group=1,2,3,9-12 +;msn=50780023 +;device => /dev/ttyI3 +;device => /dev/ttyI4 Index: /trunk/amportal/amp_conf/astetc/vm_general.inc =================================================================== --- /trunk/amportal/amp_conf/astetc/vm_general.inc (revision 523) +++ /trunk/amportal/amp_conf/astetc/vm_general.inc (revision 523) @@ -0,0 +1,14 @@ +; 1st listed format gets emailed +format=wav49|wav + +attach=yes +pbxskip=yes ; Don't put [PBX]: in the subject line +serveremail=vm@asterisk ; Who the e-mail notification should appear to come from +fromstring=Voicemail System ; Real name of email sender +maxmessage=180 ; max length of vm message +minmessage=3 ; Minimum length of a voicemail message in seconds +maxsilence=5 ; Wait for 5 silent seconds and end the voicemail +silencethreshold=128 ; What do we consider to be silence +skipms=3000 ; How many miliseconds to skip forward/back when rew/ff in message playback +review=yes ; Allow sender to review/rerecord their message before saving it +operator=yes ; Allow caller to press 0 Index: /trunk/amportal/amp_conf/astetc/indications.conf =================================================================== --- /trunk/amportal/amp_conf/astetc/indications.conf (revision 523) +++ /trunk/amportal/amp_conf/astetc/indications.conf (revision 523) @@ -0,0 +1,196 @@ +; +; Static indications configuration files, used by +; the pbx_indications module. +; +; The "general" category is for certain variables. All other categories +; are interpreted as indication countries +; +; Please note that there are NOT spaces allowed in lists! +; +[general] +country=us + +; [example] +; description = string +; The full name of your country, in English +; alias = iso[,iso]* +; List of other countries 2-letter iso codes, which have the same +; tone indications. +; ringcadance = num[,num]* +; List of durations the physical bell rings. +; dial = tonelist +; Set of tones to be played when one picks up the hook. +; busy = tonelist +; Set of tones played when the receiving end is busy. +; congestion = tonelist +; Set of tones played when the is some congestion (on the network?) +; callwaiting = tonelist +; Set of tones played when there is a callwaiting in the background. +; dialrecall = tonelist +; Set of tones played when there is somebody ??? +; record = tonelist +; Set of tones played whenver we feel like it ??? +; info = tonelist +; Set of tones played when information is to be tranfered? +; every other variable will be available as a shortcut for the "PlayList" command +; but will not automaticly be used by Asterisk. +; +; The tonelist itself is defined by a sequence of elements, seperated by ,'s. +; Each element consist of a frequency (f) with a possible frequency attached +; (f1+f2) to it. Behind the frequency there is an optional duration, in +; milliseconds. If the element starts with a !, that element is NOT repeat, +; so only if all elements start with !, the tonelist is time-limited, all +; others will repeat indefinitly. +; +; In tech-talk: +; tonelist = element[,element]* +; element = [!]freq[+freq2][/duration] + +[us] +description = United States / North America +ringcadance = 2000,4000 +dial = 350+440 +busy = 480+620/500,0/500 +ring = 440+480/2000,0/4000 +congestion = 480+620/250,0/250 +callwaiting = 440/300,0/10000 +dialrecall = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440 +record = 1400/500,0/15000 +info = !950/330,!1400/330,!1800/330,0 + +[au] +description = Australia +ringcadance = 400,200,400,2000 +dial = 425*25 +busy = 400/375,0/375 +ring = 425*25/400,0/200,425*25/400,0/2000 +; XXX Congestion: Should reduce by 10 db every other cadence XXX +congestion = 400/375,0/375 +callwaiting = 425/100,0/100,525/100,0/4700 +dialrecall = !425*25/100!0/100,!425*25/100,!0/100,!425*25/100,!0/100,425*25 +record = 1400/425,0/14525 +info = 400/2500,0/500 + +[fr] +description = France +ringcadance = 1500,3500 +; Dialtone can also be 440+330 +dial = 440 +busy = 440/500,0/500 +ring = 440/1500,0/3500 +; XXX I'm making up the congestion tone XXX +congestion = 440/250,0/250 +; XXX I'm making up the call wait tone too XXX +callwait = 440/300,0/10000 +; XXX I'm making up dial recall XXX +dialrecall = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440 +; XXX I'm making up the record tone XXX +record = 1400/500,0/15000 +info = !950/330,!1400/330,!1800/330 + +[nl] +alias = de +description = Netherlands +ringcadance = 1000,4000 +; Most of these 425's can also be 450's +dial = 425 +busy = 425/500,0/500 +ring = 425/1000,0/4000 +congestion = 425/250,0/250 +; XXX I'm making up the call wait tone XXX +callwaiting = 440/300,0/10000 +; XXX Assuming this is "Special Dial Tone" XXX +dialrecall = 425/500,0/50 +; XXX I'm making up the record tone XXX +record = 1400/500,0/15000 +info = 950/330,1400/330,1800/330,0/1000 + +[uk] +description = United Kingdom +ringcadance = 400,200,400,2000 +dial = 350+440 +busy = 400/375,0/375 +ring = 400+450/400,0/200,400+450/400,0/2000 +congestion = 400/400,0/350,400/225,0/525 +callwaiting = 440/100,0/4000 +dialrecall = 350+440 +; XXX Not sure about the RECORDTONE +record = 1400/500,0/10000 +info = 950/330,1400/330,1800/330 + +[fi] +description = Finland +ringcadance = 1000,4000 +dial = 425 +busy = 425/300,0/300 +ring = 425/1000,0/4000 +congestion = 425/200,0/200 +callwaiting = 425/150,0/150,425/150,0/8000 +dialrecall = 425/650,0/25 +record = 1400/500,0/15000 +info = 950/650,0/325,950/325,0/30,1400/1300,0/2600 + +[no] +description = Norway +ringcadence = 1000,4000 +dial = 425 +busy = 425/500,0/500 +ring = 425/1000,0/4000 +congestion = 425/200,0/200 +callwaiting = 425/200,0/600,425/200,0/10000 +dialrecall = 470/400,425/400 +record = 1400/400,0/15000 +info = !950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,0 + +[br] +description = Brazil +ringcadance = 1000,4000 +dial = 425 +busy = 425/250,0/250 +ring = 425/1000,0/4000 +congestion = 425/250,0/250,425/750,0/250 +callwaiting = 425/50,0/1000 +; Dialrecall not used in Brazil standard (using UK standard) +dialrecall = 350+440 +; Record tone is not used in Brazil, use busy tone +record = 425/250,0/250 +; Info not used in Brazil standard (using UK standard) +info = 950/330,1400/330,1800/330 + +; Steve Davies +; Tone definition source for za was +; http://www.cisco.com/univercd/cc/td/doc/product/tel_pswt/vco_prod/safr_sup/saf02.htm +; (definitions for other countries can also be found there) +; Note, though, that South Africa uses two switch types in their network - Alcatel +; switches - mainly in the Western Cape, and Siemens elsewhere. +; The former use 383+417 in dial, ringback etc. The latter use 400*33 +; I've provided both, uncomment the ones you prefer +[za] +description = South Africa +ringcadance = 400,200,400,2000 +; dial/ring/callwaiting for the Siemens switches: +dial = 400*33 +ring = 400*33/400,0/200,400*33/400,0/2000 +callwaiting = 400*33/250,0/250,400*33/250,0/250,400*33/250,0/250,400*33/250,0/250 +; dial/ring/callwaiting for the Alcatel switches: +; dial = 383+417 +; ring = 383+417/400,0/200,383+417/400,0/2000 +; callwaiting = 383+417/250,0/250,383+417/250,0/250,383+417/250,0/250,383+417/250,0/250 +congestion = 400/250,0/250 +busy = 400/500,0/500 +dialrecall = 350+440 +; XXX Not sure about the RECORDTONE +record = 1400/500,0/10000 +info = 950/330,1400/330,1800/330,0/330 + +[it] +description = Italy +ringcadence = 1000,4000 +dial = 425/600,0/1000,425/200,0/200 +busy = 425/500,0/500 +ring = 425/1000,0/4000 +congestion = 425/200,0/200 +callwaiting = 425/200,0/600,425/200,0/10000 +dialrecall = 470/400,425/400 +record = 1400/400,0/15000 +info = !950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,0 Index: /trunk/amportal/amp_conf/astetc/enum.conf =================================================================== --- /trunk/amportal/amp_conf/astetc/enum.conf (revision 523) +++ /trunk/amportal/amp_conf/astetc/enum.conf (revision 523) @@ -0,0 +1,4 @@ +[general] +search => e164.arpa +search => e164.org + Index: /trunk/amportal/amp_conf/astetc/rtp.conf =================================================================== --- /trunk/amportal/amp_conf/astetc/rtp.conf (revision 523) +++ /trunk/amportal/amp_conf/astetc/rtp.conf (revision 523) @@ -0,0 +1,9 @@ +; +; RTP Configuration +; +[general] +; +; RTP start and RTP end configure start and end addresses +; +rtpstart=10000 +rtpend=20000 Index: /trunk/amportal/amp_conf/astetc/queues.conf =================================================================== --- /trunk/amportal/amp_conf/astetc/queues.conf (revision 523) +++ /trunk/amportal/amp_conf/astetc/queues.conf (revision 523) @@ -0,0 +1,17 @@ +[general] +; +; Global settings for call queues +; (none exist currently) +; +; Note that a timeout to fail out of a queue may be passed as part of application call +; from extensions.conf: +; Queue(queuename|[options]|[optionalurl]|[announceoverride]|[timeout]) +; example: Queue(dave|t|||45) + +[default] +; +; Default settings for queues (currently unused) +; + +#include queues_custom.conf +#include queues_additional.conf Index: /trunk/amportal/amp_conf/astetc/phpagi.conf =================================================================== --- /trunk/amportal/amp_conf/astetc/phpagi.conf (revision 523) +++ /trunk/amportal/amp_conf/astetc/phpagi.conf (revision 523) @@ -0,0 +1,3 @@ +[festival] +text2wave=/usr/src/festival/bin/text2wave +tempdir=/var/lib/asterisk/sounds/tmp/ Index: /trunk/amportal/amp_conf/astetc/modules.conf =================================================================== --- /trunk/amportal/amp_conf/astetc/modules.conf (revision 523) +++ /trunk/amportal/amp_conf/astetc/modules.conf (revision 523) @@ -0,0 +1,39 @@ +; +; Asterisk configuration file +; +; Module Loader configuration file +; + +[modules] +autoload=yes +; +; If you want, load the GTK console right away. +; Don't load the KDE console since +; it's not as sophisticated right now. +; +noload => pbx_gtkconsole.so +;load => pbx_gtkconsole.so +noload => pbx_kdeconsole.so +; +; Intercom application is obsoleted by +; chan_oss. Don't load it. +; +noload => app_intercom.so +; +; Explicitly load the chan_modem.so early on to be sure +; it loads before any of the chan_modem_* 's afte rit +; +load => chan_modem.so +load => res_musiconhold.so +; +; Load either OSS or ALSA, not both +; By default, load OSS only (automatically) and do not load ALSA +; +noload => chan_alsa.so +noload => chan_oss.so +; +; Module names listed in "global" section will have symbols globally +; exported to modules loaded after them. +; +[global] +chan_modem.so=yes Index: /trunk/amportal/amp_conf/astetc/phone.conf =================================================================== --- /trunk/amportal/amp_conf/astetc/phone.conf (revision 523) +++ /trunk/amportal/amp_conf/astetc/phone.conf (revision 523) @@ -0,0 +1,47 @@ +; +; Linux Telephony Interface +; +; Configuration file +; +[interfaces] +; +; Select a mode, either the phone jack provides dialtone, reads digits, +; then starts PBX with the given extension (dialtone mode), or +; immediately provides the PBX without reading any digits or providing +; any dialtone (this is the immediate mode, the default). Also, you +; can set the mode to "fxo" if you have a linejack to make it operate +; properly. +; +mode=immediate +;mode=dialtone +;mode=fxo +; +; You can decide which format to use by default, "g723.1" or "slinear". +; XXX Be careful, sometimes the card causes kernel panics when running +; in signed linear mode for some reason... XXX +; +;format=slinear +format=g723.1 +; +; And set the echo cancellation to "off", "low", "medium", and "high". +; This is not supported on all phones. +; +echocancel=medium +; +; You can optionally use VAD/CNG silence supression +; +;silencesupression=yes +; +; List all devices we can use. Contexts may also be specified +; +;context=local +; +; You can set txgain and rxgain for each device in the same way as context. +; If you want to change default gain value (1.0 =~ 100%) for device, simple +; add txgain or rxgain line before device line. But rememeber, if you change +; volume all cards listed below will be affected by these values. You can +; use float values (1.0, 0.5, 2.0) or percentage values (100%, 150%, 50%). +; +;txgain=100% +;rxgain=1.0 +;device => /dev/phone0 Index: /trunk/amportal/amp_conf/astetc/extensions_custom.conf.sample =================================================================== --- /trunk/amportal/amp_conf/astetc/extensions_custom.conf.sample (revision 523) +++ /trunk/amportal/amp_conf/astetc/extensions_custom.conf.sample (revision 523) @@ -0,0 +1,30 @@ +; This file contains example extensions_custom.conf entries. +; extensions_custom.conf should be used to include customizations +; to AMP's Asterisk dialplan. + +; All custom context should contain the string 'custom' in it's name + +; Extensions in AMP have access to the 'from-internal' context. +; The context 'from-internal-custom' is included in 'from-internal' by default + +[from-internal-custom] +exten => 1234,1,Playback(demo-congrats) ; extensions can dial 1234 +exten => 1234,2,Hangup() +exten => h,1,Hangup() +include => custom-recordme ; extensions can also dial 5678 + +; custom-count2four,s,1 can be used as a custom target for +; a Digital Receptionist menu or a Ring Group +[custom-count2four] +exten => s,1,SayDigits(1234) +exten => s,2,Hangup + +; custom-recordme,5678,1 can be used as a custom target for +; a Digital Receptionist menu or a Ring Group +[custom-recordme] +exten => 5678,1,Wait(2) +exten => 5678,2,Record(/tmp/asterisk-recording:gsm) +exten => 5678,3,Wait(2) +exten => 5678,4,Playback(/tmp/asterisk-recording) +exten => 5678,5,Wait(2) +exten => 5678,6,Hangup Index: /trunk/amportal/amp_conf/astetc/manager.conf =================================================================== --- /trunk/amportal/amp_conf/astetc/manager.conf (revision 523) +++ /trunk/amportal/amp_conf/astetc/manager.conf (revision 523) @@ -0,0 +1,16 @@ +; +; Asterisk Call Management support +; +[general] +enabled = yes +port = 5038 +bindaddr = 0.0.0.0 + +[AMPMGRUSER] +secret = AMPMGRPASS +deny=0.0.0.0/0.0.0.0 +permit=127.0.0.1/255.255.255.0 +read = system,call,log,verbose,command,agent,user +write = system,call,log,verbose,command,agent,user + +#include manager_custom.conf Index: /trunk/amportal/amp_conf/astetc/meetme.conf =================================================================== --- /trunk/amportal/amp_conf/astetc/meetme.conf (revision 523) +++ /trunk/amportal/amp_conf/astetc/meetme.conf (revision 523) @@ -0,0 +1,2 @@ +[rooms] +#include meetme_additional.conf Index: /trunk/amportal/amp_conf/astetc/privacy.conf =================================================================== --- /trunk/amportal/amp_conf/astetc/privacy.conf (revision 523) +++ /trunk/amportal/amp_conf/astetc/privacy.conf (revision 523) @@ -0,0 +1,3 @@ +[general] + +maxretries = 2 ;How many chances the caller has to enter their number Index: /trunk/amportal/amp_conf/astetc/vm_email.inc =================================================================== --- /trunk/amportal/amp_conf/astetc/vm_email.inc (revision 523) +++ /trunk/amportal/amp_conf/astetc/vm_email.inc (revision 523) @@ -0,0 +1,5 @@ +; Change the email body, variables: VM_NAME, VM_DUR, VM_MSGNUM, VM_MAILBOX, VM_CALLERID, VM_DATE + +emailbody=${VM_NAME},\n\nThere is a new voicemail in mailbox ${VM_MAILBOX}:\n\n\tFrom:\t${VM_CALLERID}\n\tLength:\t${VM_DUR} seconds\n\tDate:\t${VM_DATE}\n\nDial *98 to access your voicemail by phone.\nVisit http://AMPWEBADDRESS/cgi-bin/vmail.cgi?action=login&mailbox=${VM_MAILBOX} to check your voicemail with a web browser.\n + + Index: /trunk/amportal/amp_conf/astetc/features.conf =================================================================== --- /trunk/amportal/amp_conf/astetc/features.conf (revision 523) +++ /trunk/amportal/amp_conf/astetc/features.conf (revision 523) @@ -0,0 +1,9 @@ +; +; Sample Parking configuration +; + +[general] +parkext => 70 ; What ext. to dial to park +parkpos => 71-79 ; What extensions to park calls on +context => parkedcalls ; Which context parked calls are in +#parkingtime => 60 ; Number of seconds a call can be parked for (default is 45 seconds) Index: /trunk/amportal/amp_conf/astetc/logger.conf =================================================================== --- /trunk/amportal/amp_conf/astetc/logger.conf (revision 523) +++ /trunk/amportal/amp_conf/astetc/logger.conf (revision 523) @@ -0,0 +1,36 @@ +; +; Logging Configuration +; +; In this file, you configure logging to files or to +; the syslog system. +; +; For each file, specify what to log. +; +; For console logging, you set options at start of +; Asterisk with -v for verbose and -d for debug +; See 'asterisk -h' for more information. +; +; Directory for log files is configures in asterisk.conf +; option astlogdir +; +[logfiles] +; +; Format is "filename" and then "levels" of debugging to be included: +; debug +; notice +; warning +; error +; verbose +; +; Special filename "console" represents the system console +; +;debug => debug +;console => notice,warning,error +;console => notice,warning,error,debug +;messages => notice,warning,error +full => notice,warning,error,debug,verbose + +;syslog keyword : This special keyword logs to syslog facility +; +;syslog.local0 => notice,warning,error +; Index: /trunk/amportal/amp_conf/astetc/iax.conf =================================================================== --- /trunk/amportal/amp_conf/astetc/iax.conf (revision 523) +++ /trunk/amportal/amp_conf/astetc/iax.conf (revision 523) @@ -0,0 +1,12 @@ +[general] +bindport = 4569 ; Port to bind to (IAX is 4569) +bindaddr = 0.0.0.0 ; Address to bind to (all addresses on machine) +disallow=all +allow=ulaw +allow=alaw +allow=gsm +mailboxdetail=yes + +#include iax_additional.conf +#include iax_custom.conf + Index: /trunk/amportal/amp_conf/astetc/voicemail.conf.template =================================================================== --- /trunk/amportal/amp_conf/astetc/voicemail.conf.template (revision 523) +++ /trunk/amportal/amp_conf/astetc/voicemail.conf.template (revision 523) @@ -0,0 +1,5 @@ +[general] +#include vm_general.inc +#include vm_email.inc +[default] + Index: /trunk/amportal/amp_conf/astetc/musiconhold.conf =================================================================== --- /trunk/amportal/amp_conf/astetc/musiconhold.conf (revision 523) +++ /trunk/amportal/amp_conf/astetc/musiconhold.conf (revision 523) @@ -0,0 +1,8 @@ +; +; Music on hold class definitions +; +[classes] +default => quietmp3:/var/lib/asterisk/mohmp3 +;loud => mp3:/var/lib/asterisk/mohmp3 +;random => quietmp3:/var/lib/asterisk/mohmp3,-z +#include musiconhold_additional.conf Index: /trunk/amportal/amp_conf/astetc/asterisk.conf =================================================================== --- /trunk/amportal/amp_conf/astetc/asterisk.conf (revision 523) +++ /trunk/amportal/amp_conf/astetc/asterisk.conf (revision 523) @@ -0,0 +1,8 @@ +[directories] +astetcdir => /etc/asterisk +astmoddir => /usr/lib/asterisk/modules +astvarlibdir => /var/lib/asterisk +astagidir => /var/lib/asterisk/agi-bin +astspooldir => /var/spool/asterisk +astrundir => /var/run/asterisk +astlogdir => /var/log/asterisk Index: /trunk/amportal/amp_conf/astetc/zapata.conf.template =================================================================== --- /trunk/amportal/amp_conf/astetc/zapata.conf.template (revision 523) +++ /trunk/amportal/amp_conf/astetc/zapata.conf.template (revision 523) @@ -0,0 +1,34 @@ +;# Flash Operator Panel will parse this file for zap trunk buttons +;# AMPLABEL will be used for the display labels on the buttons + +;# %c Zap Channel number +;# %n Line number +;# %N Line number, but restart counter +;# Example: +;# ;AMPLABEL:Channel %c - Button %n + +;# For Zap/* buttons use the following +;# (where x=number of buttons to dislpay) +;# ;AMPWILDCARDLABEL(x):MyLabel + + +[channels] +language=en + +; include zap extensions defined in AMP +#include zapata_additional.conf + +; XTDM20B Port #1,2 plugged into PSTN +;AMPLABEL:Channel %c - Button %n +context=from-pstn +signalling=fxs_ks +faxdetect=incoming +usecallerid=yes +echocancel=yes +echocancelwhenbridged=no +echotraining=800 +group=0 +channel=1-2 + + + Index: /trunk/amportal/amp_conf/astspool/asterisk/fax/fax.call =================================================================== --- /trunk/amportal/amp_conf/astspool/asterisk/fax/fax.call (revision 523) +++ /trunk/amportal/amp_conf/astspool/asterisk/fax/fax.call (revision 523) @@ -0,0 +1,7 @@ +Channel: Zap/g1/2442790 +MaxRetries: 0 +WaitTime: 20 +Context: ext-fax +Extension: out_fax +Priority: 1 +SetVar: TXFAX_NAME=/var/spool/asterisk/fax/test.tif Index: /trunk/amportal/amp_conf/htdocs/panel/LICENSE =================================================================== --- /trunk/amportal/amp_conf/htdocs/panel/LICENSE (revision 523) +++ /trunk/amportal/amp_conf/htdocs/panel/LICENSE (revision 523) @@ -0,0 +1,341 @@ + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. Index: /trunk/amportal/amp_conf/htdocs/panel/UPGRADE =================================================================== --- /trunk/amportal/amp_conf/htdocs/panel/UPGRADE (revision 523) +++ /trunk/amportal/amp_conf/htdocs/panel/UPGRADE (revision 523) @@ -0,0 +1,375 @@ +Notes on upgrading from an older release +======================================== + +o Upgrading from version .20 to .21: + New configuration file: op_astdb.cfg used to monitor asterisk + db values and change states for a button based on them. + + New option: voicemail_extension parameter in op_server.cfg. + If set, FOP will originate a call to that extension when + double clicking on the MWI icon. + + The PARK buttons are now specified as PARK/XXX + instead of PARKXXX to make it more consistent with + channel naming conventions. The old name will still + work. + + To enable agent_status (displays agent idle + time and refresh queue status after each + call). In op_server.cfg: + + agent_status=1 + + + If there is a background.jpg image in the same + directory as the swf file, it will be used as + the background for the panel. Resolution: 996x600 + + The syntax for wildcard buttons has changed, if + you have [SIP/*] change it to [_SIP/.*] + The new matching routine allows full regexps to + be used. To use regexps buttons start them with + an underscore followed by your expression. + + To disable the security code, leave it blank. In + op_server.cfg: + security_code = "" + + To enable timeout on transfers, you have to set the + transfer_timeout paramenter to op_server.cfg + + You can set the reload button to perform an asterisk + restart if you set enable_restart to 1 in op_server.cfg + +o Upgrading from version .19 to .20: + + You can define the server number of a button in op_buttons.cfg + using the 'server' directive. If you leave it empty, it will + default to Server=1 + + To monitor more than one asterisk box, just repeat the sequence of + connection parameters in op_server.cfg + + ; Server 1 + manager_host=1.2.3.4 + manager_user=john + manager_secret=doe + + ; Server 2 + manager_host=1.2.3.5 + manager_user=mary + manager_secret=poppins + + To add text legends, the format is: + + [LEGEND] + x=535 + y=50 + text=Conferences + font_size=32 + font_family=Arial + use_embed_fonts=1 + + To add park slot buttons, the format is: + + [PARK701] + Position=17 + Icon=3 + Extension=700 + Label="Park 701" + + You can specify the mailbox for a button with the mailbox parameter: + + [SIP/100] + Position=1 + Mailbox=100@default + ... + + The 'old way' still works, by using the combination of Extension and + Voicemail_Context. I recommend you to use the new format as it is more + flexible (you can monitor a mailbox independant from the extension of + that button). + + + New directives in op_server.cfg + + rename_label_wildcard + barge_muted + clid_privacy + show_ip + +o Upgrading from version .18 to .19: + + In op_style.cfg there is a new paramter to confgure the + highlighting color for the buttons: + + btn_highlight_color=ff0000 + +o Upgrading from version .17 to .18: + + The default .swf client is now the ming port. The new features + are available only in the ming port. The flash .swf is included + but it was not tested. + + In op_style.cfg there are new parameters (to configure led colors): + + ledcolor_ready + ledcolor_busy + ledcolor_agent + + The parameter led_color is not used anymore. (It is used by the + flash client only, not the default ming client) + + In op_server.cfg there are new options too (all commented in + op_server.cfg itsef): + + rename_label_agentlogin + rename_label_callbacklogin + rename_to_agent_name + rename_queue_member + change_led_agent + clicktodial_insecure + + It is possible now to draw rectangles, see op_buttons.cfg for + an example. + + If you use click-to-dial, the button used to originate the call + must be specified when invoking the .swf file. The parameter used + is 'dial'. See index-clicktodial.html for an example. + +o Upgrading from version .16 to .17: + + Just replace op_server.pl and your favorite .swf client. Its + mostly a bug fix release. The new features do not require + configuration changes. + +o Upgrading from version .15 to .16: + + The help window is now a browser windows that loads help.html + (instead of being a flash window that loads the help.txt file) + So you need to put a help.html page in the same location as the + .swf file. + + There are a couple of new parameters. In op_server.cfg + + poll_voicemail + + Will check for voicemail status every poll_interval seconds. + Remeber that poll_interval will also check for sip peers. + + rename_label_agentlogin + rename_label_callbacklogin + + Both parameters acomplish the same goal: to rename a button + label when an Agent logs in. One of them works with the regular + AgentLogin application. The other one with AgentCallbackLogin. + For the later, you need to have a button with the same extension + and context for the callback for it to work. + + There is also a new parameter in op_style.cfg + + enable_crypto (1 for enable, 0 for disable) + + If you want to encrypt server to client messages, turn this on. + +o Upgrading from version .14 to .15: + + The format of the configuration file is different. You have to + run ./convert_config_pre_14.pl in order to convert your old + configuration files to the new format. The conversion utility + must reside in the same directory as the old configuration files: + + op_server.cfg + op_style.cfg + op_buttons.cfg + + When you run the conversion routine, it will backup your old + configuration files and do its thing. Just in case, backup the + files yourself. The conversion program does not have extensive + error checking. + + The barge-in functionality has changed also. You no longer need + an auto_conference_extension in your dialplan. The panel will + keep track of the conferences itself. You *do* need to add the + conference room numbers that must be used for barge-in. The + conversion routine adds the parameter, you have to modify it to + suit your needs. The new parameter (in op_server.cfg) is: + + barge_rooms + + It must have at least two rooms defined, with the format: + "minor-major". Those rooms must be defined in your extensions.conf + under the context defined by conference_context. The extension + number must match the meetme room number. See op_server.cfg for + an example. + + To authenticate using MD5 to Asterisk Manager you can add the parameter + auth_md5=1 in op_server.cfg. It is enabled by default. + + There are new parameter in op_style.cfg: + + enable_animation: it will animate the phone icons when ringing. + + use_embed_font: lets you choose between embed fonts or system fonts. + + (1 for enable, 0 for disable) + + +o Upgrading from version .13 to .14: + + In Asterisk RC1, the IAX naming convention has changed. There are + no more brackets in IAX2 channel names. If you run RC1 (try it!) + you will have to name your IAX2 channels like: + + IAX2/user + + You will have to rename your op_buttons.cfg if you use IAX2 channels. + + The code for handling the previous naming convention is still there + but I have not tested it. It might or might not work. + + There are new elements in op_style that affects the button style: + + arrow_scale + arrow_margin_top + arrow_margin_left + + The above parameters indicate the size and position of a little + arrow that shows the 'direction' of the call. + + detail_title=Last call details + detail_from=From: + detail_to=To: + detail_duration=Duration: + + These four parameters are for setting the text displayed in the + detail box when you double click on the little arrow after a call + is made. + + led_color + + The color scheme for the available/busy led. + 0 for leds green/red. (default) + 1 for leds grey/green + + label_shadow + + You can add a shadow to the text label + 0 for disabling the shadow (default) + 1 for enabling the shadow + + + There is a new parameter in op_server.cfg: + + clid_format + + This mask will apply to the callerid field to format the + number as you see fit. Every 'x' will be replaced by a number + from right to left. Any other char will be preserved. Ex: + (xxx) xxx-xxxx + +o Upgrading from version .12 to .13: + + For the timer to work, you need to add four new parameters to + op_style.cfg + + timer_font_size + timer_font_family + timer_margin_top + timer_margin_left + +o Upgrading from version .11 to .12: + + There are 4 new parameters in op_style.cfg for the caller id + display on each button. + + clid_font_size + clid_font_family + clid_margin_top + clid_margin_left + +o Upgrading from version .10 to .11: + + There are no new parameters in the configuration files. Your .10 + configuration should work fine with version .11 + + The debug level bitmap is now different: + + 1 Show Manager Events Received + 2 Show Commands set to Manager + 4 Show Flash events Received + 8 Show events sent to Flash Clients + 16 1st level Debug on op_server.pl + 32 2nd level Debug on op_server.pl + 64 3rd level Debug on op_server.pl + +o Upgrading from version .09 to .10: + + There are 2 new parameters in op_style.cfg for a new element + in the toolbar (a reload button) + + btn_reload_label + show_btn_reload + + +o Upgrading from version .08 to .09: + + The debug level (op_server.cfg) is now a bitmap. Now you have more + control of the output produced by debug. The possible values are: + + 1 Show Manager Events Received + 2 Show Commands set to Manager + 4 1st level debug on op_server.pl + 8 2nd level debug on op_server.pl + 16 3rd level debug on op_server.pl + + If you want full debug, set the value to 31 in op_server.cfg + If you want to see just the events received and sent, set it to 3 (1+2) + + The fade matrix for the buttons is slightly different. Its the first + step towards a visual button layout configurator. + +o Upgrading from version .07 to .08: + + The offset and size of the icons have changed. You will need to adjust + your op_style.cfg. Now all the icons are aproximatly the same size and + have the same center offset, so the margins and scale parameters will + match from icon to icon. New layout configurations should be simpler. + + There are several new parameters in op_style.cfg, related to the + configurability of the toolbar. You have to add them in your current + configuration, if you don't do it you will not see any toolbar. The + parameters to add are: + + clid_label=Extra Info: + security_label=Security Code: + btn_help_label=Help + btn_log_label=Debug + show_security_code=1 + show_clid_info=2 + show_status=3 + show_btn_help=4 + show_btn_debug=5 + + The number in show_xxx represents the order in which it is rendered. If + you want to hide an element of the toolbar, set it to 0. + +o Upgrading from version .06 to .07: + + There are two new parameters in op_server.cfg: + + auto_conference_extension = 900 + conference_context = conferences + + Add them to your existing configuration file. In op_server.cfg there are + also examples of asterisk configuration files to use the 3way auto + conferences. + +o Upgrading from previous versions: + + There are three new parameters in op_sytle.cfg and a new one in + op_buttons.cfg. See CHANGES. Just add those parameters to your current + config files, and replace op_server.pl with the new one. The fixed 24 + buttons flash movie does not support voicemail notifications. There is a + new index.html that scales the applet to the size of the browser window. + Try it and use the one you like more. Index: /trunk/amportal/amp_conf/htdocs/panel/op_astdb.cfg =================================================================== --- /trunk/amportal/amp_conf/htdocs/panel/op_astdb.cfg (revision 523) +++ /trunk/amportal/amp_conf/htdocs/panel/op_astdb.cfg (revision 523) @@ -0,0 +1,30 @@ +; FOP will ask for the value of the asterisk database families +; specified between brackets. If the value is non empty, it will +; send the commands to the flash client. Note that the family +; is case sensitive! + +[dnd] +settext=DND: ${value} +setalpha=70 +flip=1 +fopledcolor=0x001020 + +[cfb] +settext=CFB: ${value} +status=busy ; free, busy, ringing +fopledcolor=0x102030 + +# How it works: when the panel is first started, it will check +# for every family defined in op_astdb.cfg between brakets. The +# key is the channel name, as defined between brackets in op_buttons.cfg +# If a value is found and non empty it will perform the actions +# specified in op_astdb.cfg, those actions modify the button for +# that channel in FOP, they are: +# +# settext = sets the text where the callerid is displayed +# setlabel = sets the label for the button +# setalpha = sets the alpha blending for the whole button (0-100) +# flip = flips the button (1) +# state = set the state of the led to: free, busy or ringing +# fopledcolor = sets the led color using hex values like 0x2030a0 + Index: /trunk/amportal/amp_conf/htdocs/panel/convert_config_pre_14.pl =================================================================== --- /trunk/amportal/amp_conf/htdocs/panel/convert_config_pre_14.pl (revision 523) +++ /trunk/amportal/amp_conf/htdocs/panel/convert_config_pre_14.pl (revision 523) @@ -0,0 +1,147 @@ +#!/usr/bin/perl + +sub copy +{ + $oldfile = shift; + $newfile = shift; + + open(IN, "< $oldfile") or die "can't open $oldfile: $!"; + open(OUT, "> $newfile") or die "can't open $newfile: $!"; + + $blksize = (stat IN)[11] || 16384; # preferred block size? + while ($len = sysread IN, $buf, $blksize) + { + if (!defined $len) + { + next if $! =~ /^Interrupted/; # ^Z and fg + die "System read error: $!\n"; + } + $offset = 0; + while ($len) + { # Handle partial writes. + defined($written = syswrite OUT, $buf, $len, $offset) + or die "System write error: $!\n"; + $len -= $written; + $offset += $written; + } + } + + close(IN); + close(OUT); +} + +copy("op_buttons.cfg", "op_buttons.cfg.bak"); +copy("op_style.cfg", "op_style.cfg.bak"); +copy("op_server.cfg", "op_server.cfg.bak"); + +open(NEWCONF, ">op_buttons.cfg.new"); + +open(CONFIG, "< op_buttons.cfg") + or die("Could not open op_buttons.cfg. Aborting..."); + +while () +{ + chop; + $_ =~ s/^\s+//g; + if (/^#/ || /^;/ || /^$/) { next; } # Ignores comments and empty lines + + ($channel, $position, $label, $extension, $icon, $vmail_context) = + split(/,/, $_); + $channel =~ s/\s+//g; + $position =~ s/\s+//g; + $position =~ s/;/,/g; + $extension =~ s/\s+//g; + $vmail_context =~ s/\s+//g; + $icon =~ s/\s+//g; + $label =~ s/^\s+//g; + + if ($position =~ /\@/) + { + ($parte1, $parte2) = split(/\@/, $position); + $position = $parte1; + $panel_context = $parte2; + } + else + { + $panel_context = ""; + } + + if ($extension =~ /\@/) + { + ($parte1, $parte2) = split(/\@/, $extension); + $extension = $parte1; + $contexto = $parte2; + } + else + { + $contexto = ""; + } + + print NEWCONF "[$channel]\n"; + print NEWCONF "Position=$position\n"; + print NEWCONF "Label=$label\n"; + print NEWCONF "Extension=$extension\n"; + print NEWCONF "Context=$contexto\n" if ($contexto ne ""); + print NEWCONF "Icon=$icon\n"; + print NEWCONF "Panel_Context=$panel_context\n" if ($panel_context ne ""); + print NEWCONF "Voicemail_Context=$vmail_context\n" + if ($vmail_context ne ""); + print NEWCONF "\n"; +} +close(CONFIG); +close(NEWCONF); + +open(NEWCONF, ">op_server.cfg.new"); +print NEWCONF "[general]\n"; +open(CONFIG, ") +{ + $_ =~ s/\s+//g; + $_ =~ s/(.*)#.*/$1/g; + if (!/^$/) + { + my ($variable_name, $value) = split(/=/, $_); + $variable_name =~ tr/A-Z/a-z/; + $value =~ s/\"//g; + print NEWCONF "$variable_name=$value\n" + unless ($variable_name =~ /auto_conference_extension/); + } +} +print NEWCONF "auth_md5=1\n"; +print NEWCONF "barge_rooms=900-910\n"; +close(CONFIG); +close(NEWCONF); + +open(NEWCONF, ">op_style.cfg.new"); +print NEWCONF "[general]\n"; +print NEWCONF "enable_animation=1\n"; +open(CONFIG, ") +{ + $_ =~ s/(.*)#.*/$1/g; + if (!/^$/) + { + my ($variable_name, $value) = split(/=/, $_); + $variable_name =~ s/\s+//g; + $variable_name =~ tr/A-Z/a-z/; + $value =~ s/\"//g; + chomp($value); + print NEWCONF "$variable_name=$value\n" + unless ( $variable_name =~ /btn_help_label/ + || $variable_name =~ /btn_reload_label/ + || $variable_name =~ /btn_help_label/ + || $variable_name =~ /security_label/); + } +} +close(CONFIG); +close(NEWCONF); + +copy("op_buttons.cfg.new", "op_buttons.cfg"); +copy("op_style.cfg.new", "op_style.cfg"); +copy("op_server.cfg.new", "op_server.cfg"); + +unlink("op_buttons.cfg.new"); +unlink("op_style.cfg.new"); +unlink("op_server.cfg.new"); Index: /trunk/amportal/amp_conf/htdocs/panel/op_buttons.cfg =================================================================== --- /trunk/amportal/amp_conf/htdocs/panel/op_buttons.cfg (revision 523) +++ /trunk/amportal/amp_conf/htdocs/panel/op_buttons.cfg (revision 523) @@ -0,0 +1,67 @@ +include => op_buttons_additional.cfg + +; use this for your customizations to FOP +include => op_buttons_custom.cfg + +[rectangle] +x=492 +y=32 +width=491 +height=278 +line_width=0 +line_color=ff1010 +fade_color1=ff1010 +fade_color2=a01000 +rnd_border=2 +alpha=20 +layer=bottom + +[rectangle] +x=492 +y=312 +width=491 +height=283 +line_width=0 +line_color=10ff10 +fade_color1=10ff10 +fade_color2=10ff00 +rnd_border=2 +alpha=20 +layer=bottom + +[rectangle] +x=-1 +y=32 +width=491 +height=564 +line_width=0 +line_color=1010ff +fade_color1=1010ff +fade_color2=1010ff +rnd_border=2 +alpha=10 +layer=bottom + +[LEGEND] +x=500 +y=315 +text=Trunks +font_size=18 +font_family=Times New Roman +use_embed_fonts=1 + +[LEGEND] +x=500 +y=32 +text=Queues +font_size=18 +font_family=Arial +use_embed_fonts=1 + +[LEGEND] +x=10 +y=32 +text=Extensions +font_size=18 +font_family=Arial +use_embed_fonts=1 Index: /trunk/amportal/amp_conf/htdocs/panel/index_amp.php =================================================================== --- /trunk/amportal/amp_conf/htdocs/panel/index_amp.php (revision 523) +++ /trunk/amportal/amp_conf/htdocs/panel/index_amp.php (revision 523) @@ -0,0 +1,48 @@ + + + + +Flash Operator Panel + + + + + + + + + + + + + Index: /trunk/amportal/amp_conf/htdocs/panel/RECIPES =================================================================== --- /trunk/amportal/amp_conf/htdocs/panel/RECIPES (revision 523) +++ /trunk/amportal/amp_conf/htdocs/panel/RECIPES (revision 523) @@ -0,0 +1,101 @@ +RECIPE 1 +-------- +Set DND (Do not disturb) from your dialplan and +reflect the status on FOP: + +# Example on setting DND state from the dialplan +# *78 Sets DND ON +# *79 Sets DND OFF +# +# This example only sets the dnd db value and +# signals FOP to display the status on the button +# you might have to add a check in your stdexten +# macro to honour the DND status + +in extensions.conf: + +exten => *78,1,UserEvent(ASTDB|Family: dnd^State: On) +exten => *78,2,SetVar(temp=${CHANNEL}) +exten => *78,3,Cut(temp=temp,,1) +exten => *78,4,DBPut(dnd/${temp}=On) +exten => *78,5,Hangup + +exten => *79,1,UserEvent(ASTDB|Family: dnd^State: ^) +exten => *79,2,SetVar(temp=${CHANNEL}) +exten => *79,3,Cut(temp=temp,,1) +exten => *79,4,DBDel(dnd/${temp}) +exten => *79,5,Hangup + +in op_astdb.cfg: +[dnd] +settext=DND: ${value} +setalpha=70 + +RECIPE 2 +-------- +How to monitor rxfax. This is just a barebones sample, you can +customize it for your needs. You have to Goto to the fax context +from your dialplan: + +in extensions.conf: + +exten => fax,1,Goto(rxfax,s,1) + +[rxfax] +exten => s,1,SetVar(FAXFILE=/var/spool/fax/fax-${TIMESTAMP}.tif) +exten => s,2,SetVar(LOCALSTATIONID=My Company) +exten => s,3,UserEvent(Newexten|Channel: FAX/FAX-${UNIQUEID}^State: Up^Uniqueid: 1234) +exten => s,4,rxfax(${FAXFILE}) +exten => s,5,Hangup() +exten => t,1,Hangup() +exten => h,1,UserEvent(Hangup|Channel: FAX/FAX-${UNIQUEID}^State: Down^Uniqueid: 1234) + +in op_buttons.cfg: +[FAX/FAX] +Position=1 +Label="Fax" + + + +RECIPE 3 +-------- +How to monitor voicemailmain (users entering the voicemail application) +You have to define a regular extensions that performs a 'Goto' to the +vmail context (similar to the fax case above) + +in extensions.conf: + +[vmail] +exten => s,1,SetLanguage(es) +exten => s,2,UserEvent(Newexten|Channel: VMAIL/VMAIL-${UNIQUEID}^State: Up^Uniqueid: 4321) +exten => s,3,VoicemailMain(${CALLERIDNUM}@internos) +exten => t,1,Hangup +exten => h,1,NoOp(Hangup en voicemail) +exten => h,2,UserEvent(Hangup|Channel: VMAIL/VMAIL-${UNIQUEID}^State: Down^Uniqueid: 4321) +exten => h,3,Hangup + +in op_buttons.cfg: + +[VMAIL/VMAIL] +Position=1 +Label="Voicemail" + +RECIPE 4 +-------- +Show day/night mode based on an asterisk db value: + +in extensions.conf: + +exten => 80,1,DBPut(daymode/DAYMODE=Day); +exten => 80,2,UserEvent(ASTDB|Family: daymode^Channel: daymode^Value: Day) +exten => 80,3,Hangup +; +exten => 81,1,DBPut(daymode/DAYMODE=Night); +exten => 81,2,UserEvent(ASTDB|Family: daymode^Channel: daymode^Value: Night) +exten => 81,3,Hangup + +in op_astdb.cfg: + +[daymode] +setlabel=${value} + Index: /trunk/amportal/amp_conf/htdocs/panel/op_style.cfg =================================================================== --- /trunk/amportal/amp_conf/htdocs/panel/op_style.cfg (revision 523) +++ /trunk/amportal/amp_conf/htdocs/panel/op_style.cfg (revision 523) @@ -0,0 +1,73 @@ +[general] +enable_crypto=1 +enable_animation=1 +use_embed_fonts=1 +label_font_size=11 +label_font_family=_sans +label_margin_top=0 +label_margin_left=35 +clid_font_size=11 +clid_font_family=_sans +clid_margin_top=11 +clid_margin_left=35 +timer_font_size=11 +timer_font_family=_serif +timer_margin_top=11 +timer_margin_left=160 +btn_width=244 +btn_height=26 +btn_padding=2 +btn_line_width=1 +btn_line_color=000000 +btn_fadecolor_1=ccccff +btn_fadecolor_2=ffffff +btn_round_border=0 +led_color=0 +led_scale=60 +led_margin_top=10 +led_margin_left=20 +arrow_scale=60 +arrow_margin_top=5 +arrow_margin_left=5 +icon1_margin_top=10 +icon1_margin_left=-15 +icon1_scale=6 +icon2_margin_top=10 +icon2_margin_left=-15 +icon2_scale=6 +icon3_margin_top=10 +icon3_margin_left=-15 +icon3_scale=6 +icon4_margin_top=10 +icon4_margin_left=-15 +icon4_scale=6 +icon5_margin_top=10 +icon5_margin_left=-15 +icon5_scale=6 +icon6_margin_top=10 +icon6_margin_left=-15 +icon6_scale=6 +mail_margin_left=-20 +mail_margin_top=13 +mail_scale=4 +clid_label=Extra Info: +security_label=Security Code: +btn_help_label=Help +detail_title=Last call details +detail_from=From: +detail_to=To: +detail_duration=Duration: +btn_log_label=Debug +show_security_code=2 +show_clid_info=0 +show_btn_help=0 +show_btn_debug=0 +show_btn_reload=1 +show_status=3 +;[sip] +; You can have different styles per panel context +; You need to copy all the variables, they will not be +; inherited. If you fail to include an option the flash +; client migth hang. So, copy the complete [general] +; section an change the header to the panel context name. +; Then adjust the parameters to your liking. Index: /trunk/amportal/amp_conf/htdocs/panel/index.php =================================================================== --- /trunk/amportal/amp_conf/htdocs/panel/index.php (revision 523) +++ /trunk/amportal/amp_conf/htdocs/panel/index.php (revision 523) @@ -0,0 +1,48 @@ + + + + +Flash Operator Panel + + + + + + + + + + + + + Index: /trunk/amportal/amp_conf/htdocs/panel/README =================================================================== --- /trunk/amportal/amp_conf/htdocs/panel/README (revision 523) +++ /trunk/amportal/amp_conf/htdocs/panel/README (revision 523) @@ -0,0 +1,189 @@ +Asterisk Flash Operator Panel +Copyright (c) 2004 Nicolás Gudiño. All rights reserved. +http://www.asternic.org + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +For complete and up to date documentation, please visit the web page +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +What is Flash Operator Panel? +----------------------------- + +The Flash Operator panel is a 'switchboard' application for the Asterisk PBX +system. It displays information about your Asterisk PBX activity in real +time via a standard web browser with Flash plugin. The display and button +layout is configurable, you can have more than a 100 buttons on the screen +at once. + +You can see at a glance: + + * What extensions are busy, ringing or available + * Who is talking and to whom (clid, context, priority) + * SIP registration status and reachability + * Meetme room status (number of users) in the room + * Queue status (number of users waiting) + * Parked extensions + * Call duration Timers + * Logged in Agents + +You can perform these actions: + + * Hang-up a channel (double click the colored dot on the button) + * Transfer a call leg via drag&drop (drag the phone icon on a button to + another button) + * Originate calls via drag&drop + * Drag an available extension to an ongoing conversation and conference + the three together. + * Change the callerid text to something meaningfull when transferring or + originating a call + * Mute/Unmute meetme participants + +Requirements +------------ + +* You need PERL and basic knowledge on how to use Asterisk. + +* You have to add a user to asterisk's manager.conf and reload asterisk for + the changes to take effect. + +* For the conference buttons, message waiting indication and automatic three + way conferences, you need asterisk CVS-HEAD as of 5/25/2004 + +* You need flash player versions 7 and up + +* You also need to define in your dialplan the conferences in a proper way + and in their own context, as explained in op_server.cfg comments. + +* If you plan to use the "Info" box to set the callerid text when + transferring or originating a call, you need to modify your dialplan. See + extensions.conf.sample + +* You also need to be wary, as English is not my first language. + + +Install +------- + +1) Copy the files in the html subdirectory to a suitable place on your web + server. If your web root is /var/www/html, you can create a subdirectory + 'panel' and copy the files there. + + There are two index and two applets, try them and use the one you like + more. You can modify the file help.txt, just be sure to left the "text=" + as the first part of the file. You can use some basic html tags also. + +2) Edit op_server.cfg and change the appropriate parameters for your setup. + + "flash_dir" parameter must be the exact location of the directory where + the html and swf files are placed. + + "web_hostname" must be the same hostname you use to access the web + server. Eg: if you access the web server using 'http://www.myserver.com' + then web_hostname must be 'www.myserver.com'. If you use an IP address + instead of a name, you should write that IP. + + The rest of the parameters are well commented in the cfg file + +3) Edit op_buttons.cfg to suit your needs. The file is commented and its + self explanatory. + +4) Edit op_style.cfg to suit your needs. You can change the button size and + colors, icon placement and size, etc. DO NOT modify the variable names, + just the value after the equal sign and DO NOT use spaces. With proper + adjusting, you can have more than a 100 buttons on the screen. + + You can change the toolbar layout by changing the number after the + variable show_???. Each one represents a possible element in the toolbar. + A value of 0 disables that element. A number represents the order in the + toolbar it will be displayed, number one being the leftmost part of the + toolbar. In the example configuration, all the toolbar elements are + displayed in correlative order. Eg: if you do not want to display a DEBUG + button, set the 'show_btn_debug' to 0. You can translate the text of the + toolbar in the corresponding variables. + + --!! Please note !!--------------------------------------------------- + If you want to transfer an available channel to an already connected + call, you have to configure your dialplan correctly and have the + context properly defined, if you don't do that you will experience + hanged channels and asterisk lockups. Thats because when you redirect + a call within the asterisk manager with an incorrect contexts, + asterisk does not handle the error gracefully. + --!!----------------------------------------------------------------- + + +International Characters +------------------------ + +If you want to display foreign characters in button labels, you have to save +the configuration file with UTF-8 encoding. To convert the file to UTF-8 +utilizing vi and the command line just perform: + +vi -c ":wq! ++enc=utf8" op_buttons.cfg + + +Running +------- + +The op_server.pl must run on the same computer as the web server + +When started, it writes the file 'variables.txt' to the http directory where +the flash applet is installed with configuration data. It must have +permissions to write to that directory. + +You can run it daemonized using -d as its command line argument. There are +some example init scripts in the directory inits + +If you want to start the server when the machine starts, you can add a line +similar to the following to your rc.local file (you have to replace the +values between '*' with the ones for your system, if you do not want to run +the op_server.pl as root, just su to that user: + +(cd */path/*; su *operator-user* -c */path/*op_server.pl & ) + +Or better yet, use one of the init scripts provided. + + +Security +-------- + +Its not meant to be secure. You should take provisions yourself, like +limiting who can connect by means of .htaccess files, firewall rules, etc. +There is basic encryption for messages sent from the server to the client, +and the security code is sent with MD5. It will hide sensitive information +from the casual observer, but its not strong enough to send credit card +information. + + +How to use it +------------- + +Click the HELP button when running the Flash Operator Panel. Experiment. +Drag icons, move your mouse around. Click and double click when the arrow +turns into a hand. + + +Support +------- + +For support or submitting bug reports, features requests, etc, please +subscribe to the mailing list by sending an empty email to +operator_panel-subscribe@lists.house.com.ar + +Donations +--------- + +If you like the program, or have feature requests, you can contribute to the +cause by donating via paypal. Click the donate button on the webpage. You can +also contact me for custom works, or asterisk consultancy. + +Thanks! + + +Credits +------- + +MD5 Algorithm. Copyright: +(C) 1991-2, RSA Data Security, Inc. Created 1991. All rights reserved. + +TEA Encryption algorithm: +Copyright (c) 2000, Peter J Billam c/o P J B Computing, www.pjb.com.au Index: /trunk/amportal/amp_conf/htdocs/panel/op_server.cfg =================================================================== --- /trunk/amportal/amp_conf/htdocs/panel/op_server.cfg (revision 523) +++ /trunk/amportal/amp_conf/htdocs/panel/op_server.cfg (revision 523) @@ -0,0 +1,177 @@ +[general] +; host or ip address of asterisk +manager_host=127.0.0.1 + +; user and secret for connecting to * manager +manager_user=admin +manager_secret=amp111 + +; You can specify many asterisk servers to +; monitor. Just repeat the manager_host, manager_user +; and manager_secret parameters in order. The first +; one will be server number 1, and so on. +; +; manager_host=1.2.3.4 +; manager_user=john +; manager_secret=doe + +; Enable MD5 auth to Asterisk manager +auth_md5=1 + +; port to listen for inbound flash connections +listen_port=4445 + +; hostname or ip address used to connect to the webserver where +; the flash movie resides (just the hostname, without directories) +web_hostname=AMPWEBADDRESS + +; location of the .swf file in your disk (must reside somewhere +; inside your web root) +flash_dir=AMPWEBROOT/panel + +; secret code for performing hangups and transfers +security_code=FOPPASSWORD + +; Frequency in second to poll for sip and iax status +poll_interval=120 + +; Poll for voicemail status (only necesary when you access the +; voicemail directories outside asterisk itself - eg. web access) +poll_voicemail=0 + +; 1 Enable automatic hangup of zombies +; 0 Disable +kill_zombies=0 + +; Debug level to stdout (bitmap) +; 1 Manager Events Received +; 2 Manager Commands Sent +; 4 Show Flash events Received +; 8 Show events sent to Flash Clients +; 16 Server 1st Debug Level +; 32 Server 2nd Debug Level +; 64 Server 3rd Debug Level +; +; Eg: to display manager events and +; commands sent set it to 3 (1+2) +; +; Maximum debug level 255 +debug=0 + +; Context in your diaplan where you have the conferences for barge in +; Example: +; +; meetme.conf +; [rooms] +; conf => 900 +; conf => 901 +; conf => 902 +; +; extensions.conf +; [conferences] +; exten => 900,1,MeetMe(900) +; exten => 901,1,MeetMe(901) +; exten => 902,1,MeetMe(902) +conference_context=conferences + +; Meetme room numbers to use for barge in. The room number must match +; the extension number (see above). +barge_rooms=900-902 + +; When doing barge ins, you can make the 3rd party to start +; the meetme muted, so the other parties wont notice they are +; now being monitored +barge_muted=1 + +; Formatting of the callerid field +; where 'x' is a number +clid_format=(xxx)xxx-xxxx + +; If you want not to show the callerid on the buttons, set this +; to one +clid_privacy=0 + +; To display the ip address of sip or iax peer inside the button +; set this to 1 +show_ip=0 + +; Will change the button label on AgentLogin +rename_label_agentlogin=0 + +; Will change the button label on Agentcallbacklogin +rename_label_callbacklogin=0 + +; Will rename the label for a wildcard button +rename_label_wildcard=0 + +; Will rename to the name specified in agents.conf +; If disabled the renaming will be Agent/XXXX +rename_to_agent_name=1 + +; Will display IDLE time for agents, as well as +; update the queue status after an agent hangs up +; the call, so you don't need to reload to get +; queue statistics +agent_status=0 + +; Will rename labels for queuemembers +; If you use addqueuemember in your dialplan, you +; can fake an AgengLogin event by sending it with +; the UserEvent application. Eg: +; +; exten => 25,1,AddQueueMember(sales|SIP/${CALLERIDNUM} +; exten => 25,2,UserEvent(Agentlogin|Agent: ${CALLERIDNUM}); +; exten => 25,3,Answer +; exten => 25,4,Playback(added-to-sales-queue) +; exten => 25,5,Hangup +; +; exten => 26,1,RemoveQueueMember(sales|SIP/${CALLERIDNUM}) +; exten => 26,2,UserEvent(RefreshQueue); +; exten => 26,3,Answer +; exten => 26,4,Playback(removed-from-sales-queue) +; exten => 26,5,Hangup +rename_queue_member=0 + +; Will change the led color when the agent logs in +; The color is configurable in op_style.cfg +change_led_agent=1 + +; If set to 1, you will transfer the linked channel instead +; of the current channel when you drag the icon on a button +reverse_transfer=0 + +; If enabled, it will not ask forthe security code +; when performing a click to dial +clicktodial_insecure=1 + +; Enable select box with absolutetimeout for the call after +; a transfer is performed within the panel +transfer_timeout= "0,No timeout|300,5 minutes|600,10 minutes|1200,20 minutes|2400,40 minutes|3000,50 minutes" + +; If set to 1, when hitting the reload button on the flash +; client it will instead restart the 1st asterisk box +; (For asterisk to restart you have to start it with +; safe_asterisk, if you dont do that, asterisk will just +; shut down) +enable_restart = 0 + +; If you set this parameter to your voicemailmain +; extension@context, it will originate a call to +; voicemailmain when double clicking on the MWI icon +; for any button. +voicemail_extension = 3000@features + +; You can have panel contexts with their own +; button layout and configuration. The following entry +; will create a context called sip with a different +; security code. In the online documentation you will +; find how to use contexts +; +;[sip] +;security_code=djdjdi43 +;web_hostname=www.virtualwebserver.com +;flash_dir=/var/www/virtualwebserver/html/panel +;barge_rooms=800-802 +;conference_context=otherconferences +;transfer_timeout="0,No timeout|60,1 minute" +;voicemail_extension=1000@nine Index: /trunk/amportal/amp_conf/htdocs/panel/extensions.conf.sample =================================================================== --- /trunk/amportal/amp_conf/htdocs/panel/extensions.conf.sample (revision 523) +++ /trunk/amportal/amp_conf/htdocs/panel/extensions.conf.sample (revision 523) @@ -0,0 +1,46 @@ +# +# Example entry for setting a callerid text comming +# from the flash operator panel +# You have to modify line 1 and 3 to match the channel +# name for that extension: temp=clid should remain untouched. +# +# If the variable does not exist in the database +# (if was not set by the operator panel) the line +# 1 jumps to priority n+101, to perform a normal +# dial without setting the CallerIDName + +exten => 11,1,DBget(temp=clid/SIP/11) +exten => 11,2,SetCIDName(${temp}) +exten => 11,3,DBdel(clid/SIP/11) +exten => 11,4,Dial(SIP/11,30,TrH) +exten => 11,5,Hangup + +; gets here if there was not 'info' provided +exten => 11,102,Dial(SIP/11,30,TrH) + +; busy from the dial +exten => 11,105,Busy +exten => 11,203,Busy + + +# Example on setting DND state from the dialplan +# *78 Sets DND ON +# *79 Sets DND OFF +# +# This example only sets the dnd db value and +# signals FOP to display the status on the button +# you might have to add a check in your stdext +# macro to honour the DND status + +exten => *78,1,UserEvent(ASTDB|Family: dnd^State: On) +exten => *78,2,SetVar(temp=${CHANNEL}) +exten => *78,3,Cut(temp=temp,,1) +exten => *78,4,DBPut(dnd/${temp}=On) +exten => *78,5,Hangup + +exten => *79,1,UserEvent(ASTDB|Family: dnd^State: ^) +exten => *79,2,SetVar(temp=${CHANNEL}) +exten => *79,3,Cut(temp=temp,,1) +exten => *79,4,DBDel(dnd/${temp}) +exten => *79,5,Hangup + Index: /trunk/amportal/amp_conf/htdocs/panel/safe_opserver =================================================================== --- /trunk/amportal/amp_conf/htdocs/panel/safe_opserver (revision 523) +++ /trunk/amportal/amp_conf/htdocs/panel/safe_opserver (revision 523) @@ -0,0 +1,5 @@ +#!/bin/bash +while true; do +./op_server.pl +sleep 4 +done Index: /trunk/amportal/amp_conf/htdocs/panel/TODO =================================================================== --- /trunk/amportal/amp_conf/htdocs/panel/TODO (revision 523) +++ /trunk/amportal/amp_conf/htdocs/panel/TODO (revision 523) @@ -0,0 +1,8 @@ +* Clean bugs +* A way to set astdb values from the .swf +* A way to perform any cli or manager action from the .swf +* Go to Astricon Europe with the help of donnors +* Go to Astricon USA with more help +* Change user authentication and restrictions methods +* Get some sleep +* Make a client in JAVA Index: /trunk/amportal/amp_conf/htdocs/panel/help.txt =================================================================== --- /trunk/amportal/amp_conf/htdocs/panel/help.txt (revision 523) +++ /trunk/amportal/amp_conf/htdocs/panel/help.txt (revision 523) @@ -0,0 +1,15 @@ +text=Before performing actions, you need to enter the security code in the box. + +After that you can: + +
    +
  • Transfer calls: by dragging the phone icon to the destination you want + +Hangup calls: by double clikcing on the red button + +Originate calls: by dragging an available extension to an available destination + +Conference calls: You can add a third person to an existing conversation by dragging an available extension to a leg of an already connected call. +
+You can also write additional information in the Info box before transferring or originating a call. If text is entered in the Info box, it will be used as the Caller ID text for any originated or transferred calls.e.g. "555-1212 Bob Jones/IBM" + Index: /trunk/amportal/amp_conf/htdocs/panel/FAQ =================================================================== --- /trunk/amportal/amp_conf/htdocs/panel/FAQ (revision 523) +++ /trunk/amportal/amp_conf/htdocs/panel/FAQ (revision 523) @@ -0,0 +1,65 @@ +Flash Operator Panel frequently asked questions: +------------------------------------------------ + +Q0: Do I have to run a webserver? + +A0: Yes, its a Flash applet, it works from a web browser. You can use apache + or any other webserver you like. It was tested with Windows, Linux, and + Mac browsers. Its truly multiplatform! + + +Q1: Do I need to install additional Perl modules to run op_server.pl? + +A1: No, it works without extra modules. + + +Q2: International characters are not displayed in button labels! Is it + possible to use foreign characters? + +A2: Yes, its possible. The flash movie will display international + characters if you encode the text with UTF-8. You can encode the files + from the command line using vi: + + vi -c ":wq! ++enc=utf8" op_buttons.cfg + + There is a downside to this. If you originate calls from the panel, the + caller id will be set with the utf8 label (ilegible characters in the + clid) + +Q3: I do not want to mess with the caller id text and the asterisk database. + Can I disable that feature? + +A3: Sure, just modify op_style.cfg and set the show_clid_info variable to 0 + (zero). + + +Q4: When I try to open the webpage, the browser hangs. Whats wrong? + +A4: The flash movie tries to read a file named 'variables.txt' that is + generated by op_server.pl in the same directory where the .swf file + lives. If this file is corrupted or incomplete, the flash movie might + loop forever. Make sure you have the file in place. If its not there, + you might have permissions problems or you forgot to run op_server.pl. + + +Q5: I changed the style op_style.cfg, but when I reload the page I don't + see the changes. What's up? + +A5: The flash movie requests the file variables.txt when it starts. If your + browser caches that file, you won't see the changes you made unless you + clear your browser cache, or maybe just requesting the variables.txt + file and hitting reload a couple of times. + +Q6: I do not like editing a text file to change a visual layout! + +A6: Me neither, I dream of a visual layout configuration. But I do not have + the time to make it happen. Maybe in the future. + +Q7: Where is the .fla file? + +A7: The server is GPL, the client is not. I do not like the .fla + format. I have ported the client to MING, under ming-source you will + find the perl source for generating a working .swf client. I will give + the .fla source to the first person who donates me a loaded powerbook + with OSX 10.3.5 and Macromedia Tools installed . + Index: /trunk/amportal/amp_conf/htdocs/panel/CHANGES =================================================================== --- /trunk/amportal/amp_conf/htdocs/panel/CHANGES (revision 523) +++ /trunk/amportal/amp_conf/htdocs/panel/CHANGES (revision 523) @@ -0,0 +1,518 @@ +CHANGES to Flash Operator Panel + +.21 + May 29 2005: + + - You can use 'transparent' as a fade_color for a button in + op_style.cfg. That button will only display its borders, with + no background color. You can use the background.jpg in creative + ways now. + + - The restrict input parameter can be set to a channel name + instead of just the button possition. The restricted button + will be hightlithed. + + - Added margintop and marginleft to the input parameters taken by + operator_panel.swf, to set the global margins for the swf object. + You can experiment by loading the .swf file directly like: + operator_panel.swf?margintop=20&marginleft=40 + + - Added voicemail_extension parameter to op_server.cfg. If set, + FOP will originate a call to that extension when double clicking + on the MWI icon. + + - Added version checking between client & server + + - You can disable the icon for a button by using icon=0 + + - Added support for Zap DND state. You have to define the 'dnd' + family in op_astdb.cfg + + - Added astdb checks. See op_astdb.cfg and extensions.conf.sample + for details. You can check for day/time mode or anything that its + stored on asterisk db. + + - Changed the PARKXXX buttons to PARK/XXX for consistency. The old + syntax will still work fine. + + - Added monitoring for callerid instead of channel name. Use + [CLID/XXXXXX] as the button name in op_buttons.cfg. Its experimental + Commands like originates and others might not work. This kind + of button will work with CVS-HEAD and without using the dial 'o' + flag. + + - Added persistent security code. It remembers your last input so + you don't have to complete it the next time you open FOP. + + - Added Panel_Context=* to op_buttons.cfg files, with it, that entry + will be added to every panel context defined. + + - Fixed callerid on CVS-HEAD, now it works without using + the dial 'o' flag + + - Added agent status. It displays Idle status an its + timers, and refresh queue statistics after each agent + call. Set agent_status to 1 in op_server.cfg + + - Added the option to change the led color from asterisk's + dialplan: + + exten => 1,1,UserEvent(FOP_ledcolor|Color: 0x0000FF^State: 0) + + The color can be any hex value. The State is: + + 0 for available status (channel not in use) + 1 for busy status (channel in use) + 2 for agent status (channel not in use and logged in agent) + + - Added the option to fire screen pops from asterisk's + dialplan: + + exten => 1,1,UserEvent(FOP_Popup|URL: page.php?e=${EXTEN}^Target: top) + + - Removed some DOWN status events that were redundant + + - Added callerid name in screen popups, look at the + mypage.php sample in the html directory + + - Initial Status is fetched from op_server.pl memory + instead of querying asterisk every time. Saves lots + of asterisk resources and speeds things up + + - Fixed infobox bug when a button has multiple matches + + - Background image. Just place a background.jpg file + in the same directory as the .swf file and it will + be displayed as the background. The canvas size is + 996x600 + + - REGEXP buttons. Wildcard buttons are discarded in + favor of the more powerful REGEXP buttons. If you + use wildcard buttons, replace them with a REGEXP + (Ex: if you have [SIP/*] change it to [_SIP/.*]) + + - The security code is now optional (leave it blank + in op_server.cfg for no security code when performing + actions) + + - Added absolute timeout for transferred calls + + - Added the option to restart asterisk instead of reload + the panel when hitting the reload button (enable_restart + in op_server.cfg) + + - Reworked some button matching routines, now it should + support Modem[i4l], oh323/* and mISDN + +.20 + Feb 22 2005: + + - Added support for monitoring multiple asterisk servers + + - Added wildcard buttons (IAX2/*) + + - Added Park Slot buttons (PARK701) + + - Barge Muted (barge_muted in op_server.cfg) + Will start the 3rd leg muted when barging in + + - CallerID Privacy (clid_privacy in op_server.cfg) + Will hide the callerid number in the buttons + + - Show IP address of peers (show_ip in op_server.cfg) + Will show the ip address of peers in their buttons + + - Text legends (LEGEND primitive in op_buttons.cfg) + + - Highlight of linked buttons + + - Added Mailbox parameter to the button definition + + - You can specify a channel name in the dial parameter + when using click-to-dial features, thus making it easier + to implement + +.19 + Nov 04 2004: + + - Improved call details, now there is a queue/agent information + window and last call details window. + + - Bugfixes and visual layout tweaks. You can set the highlight color, etc. + + - You can define a distinct style per panel context. See op_style.cfg + + - You can include files in op_buttons.cfg with the keyword 'include =>' + + - The swf client is compressed and much smaller. + + - When op_server.pl recconects, it close flash clients connections to + force a reconnect and update on their status. + +.18 + Oct 29 2004: + + - Ming client is now the default, the new features work with + that client only. + + - Led color configurable via op_style.cfg + + - More label renaming options + (rename to agent name, rename queuememebers, etc) + + - Option to change led color for logged in agents + + - New click to dial feature, accesible via javascript + (examples in the html subdir) + + - Ability to draw rectangles (see op_buttons.cfg for examples) + + - Added polling for IAX presence + +.17 + Oct 21 2004: + + - Mostly a bug fix release. Fixed the reload button in the ming client. + + - Added polling of agents status on connect + + - Initial take on showing detail info on each agent + +.16 + Oct 20 2004: + + - Bug fixes: parked channel feature works again, ringing state too. + + - Encryption is now optional, you can enable or disable it by changing + enable_crypto in op_style.cfg (while is this parameter in op_style + you may ask? well, the client has the ability to request encryption or + not, op_style.cfg sets not only visual parameters, but any client + parameter. I might change the configuration file name to op_client.cfg + in the future. Without encryption the client uses less CPU. + + - Include the option for polling voicemail status together with sip + peers (poll_voicemail in op_server.cfg) + + - New feature: ability to rename button labels when agents log in ( + rename_label_agentlogin and rename_label_callbacklogin inside + op_server.cfg) + + - New Ming client included. Ming is a library for generating .swf files + with wrappers in several languages. I used the perl wrapper to produce + a complete client. The source is included in the ming-source directory + The precompiled .swf file is in the html directory together with the + native flash client. Further development might be done exclusively in + Ming. + + - The Ming client treats fonts a little diferent. If you enable + use_embed_fonts in op_style.cfg, then all the font_family values will + be overriden by the only embedded font in the .swf. The + embedded font looks uniform compared to system (or browser) fonts. You + *can* use any font_family available in your platform: just disable the + use of embed fonts and select the family for each legend in a button.. + but you might end up with chopped text depending of the type of font + you use. + + +.15 + Oct 1st 2004: + + - New configuration file format, there is an utility to convert + your old configuration to the new format. See UPGRADE, and don't + forget to backup first. Be sure to run the utility only with + old configuration files, use it once and then remove it just in + case. + + - Fixed MessageWaiting when channel was in another context + + - MD5 Authentication to Asterisk Manager (md5_auth in op_server.cfg) + + - Improved context handling in general. Now you can have a security + code for each context, as well as meetme rooms to use for bargein. + + - There is no more auto_conf_exten for finding an empty meetme for + barge-ins. You now have to specify the rooms available for that + feature with the parameter barge_rooms in op_server.cfg + + - Timers are now polled from Asterisk on initial connect. (If you open + the panel when a conversation was going, you will see the real duration + of the call) + + - TEA Encryption for messages sent from server to client. MD5 used for + hashing the password. + + - Many cosmetical changes to the flash movie, as well as optimizations + and rewrites. There are new icons, animations, etc. + + - More information available when a call is disconnected, including + queue status information (completed calls, average holdtime, etc) + To get the info double click on the arrow when a call is finished. + + - Added ability to mute/unmute meetme participants by clicking on the + arrow. + + - Supports for register/unregister/unreachable/lagged realtime events + for SIP and IAX peers + + - You can restrict the drag and drop commands to one button only, see + index-restrict.html in the html subdirectory + +.14 + Jul 28th 2004: + + - You can run the op_server.pl dettached from the console by starting it + with the parameter '-d'. There are sample init scripts in the init + directory for redhat and debian. + + - You can now drag a parked channel and transfer it to an available + extension just like any other transfer. + + - Changed to work well with Asterisk RC1 (IAX2 channel names have changed + from "IAX2[ext@context]" to IAX2/ext@context. Your IAX2 channels in + op_buttons.cfg must be renamed if you use RC1. If you do not user RC1, + it might work as before, but I have not tested it. + + - The flash side has a lot of improvements and changes. The fonts _sans + and _serif are now embedded, so they will look consistent in different + client computers (the tradeoff is a bigger swf file). When a channel is + offline, the label text will also be grayed out. When dragging an icon, + there is now visual feedback for the destination button. Added transparent + mask to the icons to make drag easier. If the client looses connection to + the op_server.pl, it will try to reconnect by itself (it does not work + on linux, its a flash bug). + + - Also in the flash side, there are new elements. A little arrow showing + the direction of the call. And if you double click that little arrow + after a call is made, you can see the last call status. The new parameters + for op_style.cfg are described in UPGRADE + + - There is a new parameter in op_server.cfg: clid_format + You can choose the format for the caller id to be presented in the client + The letter 'x' will be replaced with a number, any other text will be + preserved. + +.13 + Jul 12th 2004: + + - Fixed an annoying bug that prevented the initial status to be displayed + (the context for wich the panel request events was sent after the + status events, so they were ignored by the flash client) + + - Fixed another annoying bug, the clid text was of an incorrect height, + and the drag and drop was erratic because of this + + - Improved the parking display on the channels. Now you have a flashing + led and a text in the button itself + + - Added a timer with the duration of a call to each button + + - Still more bugs to clean on the trunk feature, please report feedback + on the mailing list.. + +.12 + Jul 5th 2004: + + - Added a caller id display on the button itself + + - The status of meetme and queues is queried on initial connect + + - The event handling was rewriten. Now the flash client receives the + events for its own context (in previous versions, the events were + broadcasted to all clients) + + - Cleaned the debug output a bit. + + - You can send a USR1 signal to the server and look at some state + variables. + + +.11 + Jun 29th 2004: + + - Added 'trunk' buttons. You can define many buttons for just one user. + It is not well tested, as I do not have the means to test it. It might + introduce bugs! + + - Added CRM software integration. You can monitor a button, and when it + rings, a web page is requested in the url and target you specify, with + the clid sent as a GET variable. + + - The debug option in op_server.cfg has changed. See the UPGRADE file or + the online documentation, or the comments on op_server.cfg + +.10 + Jun 22th 2004: + + - Added Contexts for the panel. You can have one server and several + different panels. + + - Added 'Parked calls' as a status for a defined channel/button + + - Fixed important bugs (MWI with contexts not working, transfers not + working in particular situations) + + - Totally new webpage, for up to date documentation, go there + + - You can send a HUP signal to op_server.pl and it will rewrite the + configuration files for the flash applet. + + - New RELOAD button in flash, it will reread the server configuration + and refresh the display. + +.09 + Jun 8th 2004: + + - Added Message Waiting count. When passing the mouse over the envelope + icon the status shows the New and Old messages in that mailbox + + - The server sets the CallerID when originating a call. + + - Changed the debug parameter in op_server.cfg to a bitmap for greater + control off debug output. + + - Changed the way it handles extension numbers (column number 4) in + op_buttons.cfg. Now you can specify the context where the extension + resides by using the syntax: + + extension@context + + If the extension is not reachable from the default context, you must + specify its context there. + + - Changed the way it handles IAX2 channels. To specify an IAX channel you + have to specify its name only, without '@context', eg: + + IAX2[john] + + +.08 + Jun 2nd 2004: + + - Added "Extra Info" Input box + + You can write any text you want in that box. When transferring or + originating a call, that text will be used as the callerid text for + that call. This way you can pass usefull information to the person + you are transferring the call. In order for this feature to work you + need to modify your dialplan ("extensions.conf"). There is an example + extensions.conf provided. + + - Configurable layout of the toolbar + + The bar at the top of the flash applet is now configurable. There are + new parameters in op_style.cfg to adjust the layout: + + clid_label=Extra Info: + security_label=Security Code: + btn_help_label=Help + btn_log_label=Debug + show_security_code=1 + show_clid_info=2 + show_status=3 + show_btn_help=4 + show_btn_debug=5 + + The numbers in the show_xxx varialbes indicates the position in the + toolbar. If you do not want to display an element, set it to 0. + + - Added HELP button and text to the flash movie + + You can add a help.txt file in the same directory as the flash movie + on your webserver. This file will be displayed inside the help window + of the flash movie. You can use basic html tags. You *must* start the + file with the words "text=" + +.07 + May 19th 2004: + + - Added conference buttons + + The channel column (first one) in op_buttons.cfg must be named with + the number of the meetme conference. Eg: you have a meetme conference + number 901, the name of the channel must be '901'. Look at the example + config. + + - Added tranfer of an empty channel to an already connected call and + automatically conference the three parties together + + You have two new parameters in op_server.cfg: + + auto_conference_extension + conference_context + + Set them up and look at op_server.cfg for an example configuration for + asterisk in extensions.conf and meetme.conf + + - Added origination of calls + + You can drag an available button to another available button. This + will originate a call from the first channel to the extension defined + in the desintation button. + + - More readable output log for op_server.pl + +.06 + May 12th 2004 + + - Added voicemail notification: + + There is a new parameter in op_buttons.cfg. The last column has the + voicemail context of the extension. You can leave it blank and it will + not check/show the voicemail status for that button (if its a queue + button or an extension without voicemail on). + + You also have three new parameters in op_style.cfg: + + mail_margin_left + mail_margin_top + mail_scale + + to set the placement and size of the voicemail icon in the button. + +.05 + Apr 17th 2004 + + - Fixed a typo that prevented the applet from working. + +.04 + Apr 16th 2004 + + - Changed configuration files + + The configuration files are different: in version .03 the + op_server.cfg had the button configuration and layout. In version + .04 that information is stored in op_buttons.cfg. There are 2 new + parameters in op_buttons.cfg, extension and icon. + + op_server.cfg is now used for the parameters of the op_server + itself, like port to listent to, security code, debug level. You + don't have to modify op_server.pl anymore. All configuration is done + in the cfg files. + + There is a new configuration file: op_style.cfg with the style and + size of the buttons. + + - Added Queues buttons + + You can display call queues on a button. Just put the name + of the queue as the channel name. + + - Reconnection to Asterisk Manager port + + The op_server now tries to reconnect to Asterisk Manager + port in case of disconnection. So if you restart Asterisk + you don't need to restart the op_server.pl, it will reconnect + by itself. + + - Totally redone flash movie: operator_panel3.swf + + The flash applet in version .04 is totally rewriten. IÏt + has no bitmaps. All graphics are vector based, so you can + change the style and sizes without loosing detail. There + is no timer or status on each button in order to save space. + You can see the status of a channel by passing the mouse + over the red oval. + + The new op_server.pl is compatible with the flash applet + of previous versions (operator_panel.swf), with fixed 24 + buttons display, timers, and scrolling info on each channel. Index: /trunk/amportal/amp_conf/htdocs/panel/op_server.pl =================================================================== --- /trunk/amportal/amp_conf/htdocs/panel/op_server.pl (revision 523) +++ /trunk/amportal/amp_conf/htdocs/panel/op_server.pl (revision 523) @@ -0,0 +1,8127 @@ +#!/usr/bin/perl -w + +# Flash Operator Panel. http://www.asternic.org +# +# Copyright (c) 2004 Nicolás Gudiño. All rights reserved. +# +# Nicolás Gudiño +# +# This program is free software, distributed under the terms of +# the GNU General Public License. +# +# THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +# EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use strict; +use integer; + +use IO::Socket; +use IO::Select; +use Fcntl; +use POSIX qw(setsid EWOULDBLOCK); + +my $FOP_VERSION = "021.001"; +my %datos = (); +my %sesbot = (); +my %linkbot = (); +my %cache_hit = (); +my %estadoboton = (); +my %statusboton = (); +my %botonled = (); +my %botonalpha = (); +my %botonregistrado = (); +my %botontext = (); +my %boton_ip = (); +my %botonlabel = (); +my %botontimer = (); +my %botonpark = (); +my %botonmeetme = (); +my %botonclid = (); +my %botonqueue = (); +my %botonqueuemember = (); +my %botonvoicemail = (); +my %botonvoicemailcount = (); +my %botonlinked = (); +my %parked = (); +my %laststatus = (); +my %autenticado = (); +my %auto_conference = (); +my %buttons = (); +my %button_server = (); +my %buttons_reverse = (); +my %textos = (); +my %iconos = (); +my %remote_callerid = (); +my %remote_callerid_name = (); +my %extension_transfer = (); +my %extension_transfer_reverse = (); +my %flash_contexto = (); +my %saved_clidnum = (); +my %saved_clidname = (); +my %keys_socket = (); +my %manager_socket = (); +my %start_muted = (); +my %timeouts = (); +my %no_rectangle = (); +my %astdbcommands = (); +my %client_queue = (); +my %manager_queue = (); +my %client_queue_nocrypt = (); +my %ip_addy = (); +my $config = {}; +my $global_verbose = 1; +my $counter_servers = -1; +my %bloque_completo; +my $bloque_final; +my $todo; +my @bloque; +my @respuestas; +my @all_flash_files; +my @masrespuestas; +my @fake_bloque; +my @flash_clients; +my @status_active; +my %mailbox; +my %instancias; +my %agents; +my %channel_to_agent; +my %reverse_agents; +my %agents_name; +my @p; +my $m; +my $O; +my @S; +my @key; +my @manager_host = (); +my @manager_user = (); +my @manager_secret = (); +my @manager_conectado = (); +my $web_hostname; +my $listen_port; +my $security_code; +my $flash_dir; +my $restrict_channel = ""; +my $socketpolicy; +my $poll_interval; +my $poll_voicemail; +my $kill_zombies; +my $ren_agentlogin; +my $ren_cbacklogin; +my $ren_agentname; +my $agent_status; +my $ren_queuemember; +my $ren_wildcard; +my $clid_privacy; +my $show_ip; +my $enable_restart; +my $change_led; +my $cdial_nosecure; +my $barge_muted; +my $debug = -1; +my $debug_cache = ""; +my $flash_file; +my %barge_rooms; +my %barge_context; +my $first_room; +my $last_room; +my $meetme_context; +my $clid_format; +my $directorio = $0; +my $papa; +my $auth_md5 = 1; +my $md5challenge; +my $reverse_transfer; +my %shapes; +my %legends; +my %no_encryption = (); +my %total_shapes; +my %total_legends; +my %lastposition; +my @btninclude = (); +my $command = ""; +my $daemonized = 0; + +my $PADDING = join( + '', + map(chr, + ( + 0x80, 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, 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 + )) + ); +my %a2b = ( + A => 000, + B => 001, + C => 002, + D => 003, + E => 004, + F => 005, + G => 006, + H => 007, + I => 010, + J => 011, + K => 012, + L => 013, + M => 014, + N => 015, + O => 016, + P => 017, + Q => 020, + R => 021, + S => 022, + T => 023, + U => 024, + V => 025, + W => 026, + X => 027, + Y => 030, + Z => 031, + a => 032, + b => 033, + c => 034, + d => 035, + e => 036, + f => 037, + g => 040, + h => 041, + i => 042, + j => 043, + k => 044, + l => 045, + m => 046, + n => 047, + o => 050, + p => 051, + q => 052, + r => 053, + s => 054, + t => 055, + u => 056, + v => 057, + w => 060, + x => 061, + y => 062, + z => 063, + '0' => 064, + '1' => 065, + '2' => 066, + '3' => 067, + '4' => 070, + '5' => 071, + '6' => 072, + '7' => 073, + '8' => 074, + '9' => 075, + '+' => 076, + '_' => 077, + ); +my %b2a = reverse %a2b; +my $rand_byte_already_called = 0; + +$SIG{PIPE} = 'IGNORE'; +$SIG{ALRM} = 'alarma_al_minuto'; +$SIG{INT} = 'close_all'; +$SIG{HUP} = 'generate_configs_onhup'; +$SIG{USR1} = 'dump_internal_hashes_to_stdout'; + +if (defined($ARGV[0])) +{ + if ($ARGV[0] eq "-d") + { + defined(my $pid = fork) or die "Can't Fork: $!"; + exit if $pid; + setsid or die "Can't start a new session: $!"; + open MYPIDFILE, ">/var/run/op_panel.pid"; + print MYPIDFILE $$; + close MYPIDFILE; + $daemonized = 1; + } +} + +sub read_server_config() +{ + my $context = ""; + $counter_servers = -1; + + $/ = "\n"; + + open(CONFIG, "<$directorio/op_server.cfg") + or die("Could not open op_server.cfg. Aborting..."); + + while () + { + chop; + $_ =~ s/^\s+//g; + $_ =~ s/([^;]*)[;](.*)/$1/g; + $_ =~ s/\s+$//g; + + if (/^#/ || /^;/ || /^$/) + { + next; + } # Ignores comments and empty lines + + if (/^\Q[\E/) + { + s/\[(.*)\]/$1/g; + tr/a-z/A-Z/; + $context = $_; + } + else + { + if ($context ne "") + { + my ($variable_name, $value) = split(/=/, $_); + $variable_name =~ tr/A-Z/a-z/; + $variable_name =~ s/\s+//g; + $value =~ s/^\s+//g; + $value =~ s/\s+$//g; + $value =~ s/\"//g; + $config->{$context}{$variable_name} = $value; + + if ($variable_name eq "manager_host") + { + $counter_servers++; + $manager_host[$counter_servers] = $value; + } + + if ($variable_name eq "manager_user") + { + $manager_user[$counter_servers] = $value; + } + + if ($variable_name eq "manager_secret") + { + $manager_secret[$counter_servers] = $value; + } + + } + } + } + close(CONFIG); + + $web_hostname = $config->{"GENERAL"}{"web_hostname"}; + $listen_port = $config->{"GENERAL"}{"listen_port"}; + $security_code = $config->{"GENERAL"}{"security_code"}; + $flash_dir = $config->{"GENERAL"}{"flash_dir"}; + $poll_interval = $config->{"GENERAL"}{"poll_interval"}; + $poll_voicemail = $config->{"GENERAL"}{"poll_voicemail"}; + $kill_zombies = $config->{"GENERAL"}{"kill_zombies"}; + $reverse_transfer = $config->{"GENERAL"}{"reverse_transfer"}; + $debug = $config->{"GENERAL"}{"debug"}; + $auth_md5 = $config->{"GENERAL"}{"auth_md5"}; + $ren_agentlogin = $config->{"GENERAL"}{"rename_label_agentlogin"}; + $ren_cbacklogin = $config->{"GENERAL"}{"rename_label_callbacklogin"}; + $ren_wildcard = $config->{"GENERAL"}{"rename_label_wildcard"}; + $ren_agentname = $config->{"GENERAL"}{"rename_to_agent_name"}; + $agent_status = $config->{"GENERAL"}{"agent_status"}; + $ren_queuemember = $config->{"GENERAL"}{"rename_queue_member"}; + $change_led = $config->{"GENERAL"}{"change_led_agent"}; + $cdial_nosecure = $config->{"GENERAL"}{"clicktodial_insecure"}; + $barge_muted = $config->{"GENERAL"}{"barge_muted"}; + $clid_privacy = $config->{"GENERAL"}{"clid_privacy"}; + $show_ip = $config->{"GENERAL"}{"show_ip"}; + $enable_restart = $config->{"GENERAL"}{"enable_restart"}; + + my @todos_los_rooms; + foreach my $val ($config) + { + while (my ($aa, $bb) = each(%{$val})) + { + while (my ($cc, $dd) = each(%{$bb})) + { + if ($cc eq "barge_rooms") + { + ($first_room, $last_room) = split(/-/, $dd); + if (!defined($last_room)) + { + $last_room = $first_room; + } + my @arrayroom = $first_room .. $last_room; + foreach (@arrayroom) + { + $barge_context{"$_"} = $aa; + } + push(@todos_los_rooms, @arrayroom); + } + } + } + } + %barge_rooms = map { $todos_los_rooms[$_], 0 } 0 .. $#todos_los_rooms; + + $meetme_context = $config->{"GENERAL"}{"conference_context"}; + $clid_format = $config->{"GENERAL"}{"clid_format"}; + + $flash_file = $flash_dir . "/variables.txt"; + push @all_flash_files, $flash_file; + + if (!defined $web_hostname) + { + die("Missing web_hostname in op_server.cfg!"); + } + if (!defined $listen_port) + { + die("Missing listen_port in op_server.cfg!"); + } + if (!defined $security_code) + { + die("Missing security_code in op_server.cfg!"); + } + if (!defined $flash_dir) { die("Missing flash_dir in op_server.cfg!"); } + if (!defined $poll_interval) + { + die("Missing poll_interval in op_server.cfg!"); + } + if (!defined $ren_agentlogin) + { + $ren_agentlogin = 0; + } + if (!defined $clid_privacy) + { + $clid_privacy = 0; + } + if (!defined $show_ip) + { + $show_ip = 0; + } + if (!defined $ren_wildcard) + { + $ren_wildcard = 1; + } + if (!defined $reverse_transfer) + { + $reverse_transfer = 0; + } + if (!defined $barge_muted) + { + $barge_muted = 0; + } + if (!defined $enable_restart) + { + $enable_restart = 0; + } + if (!defined $cdial_nosecure) + { + $cdial_nosecure = 0; + } + if (!defined $agent_status) + { + $agent_status = 1; + } + if (!defined $ren_agentname) + { + $ren_agentname = 0; + } + if (!defined $ren_cbacklogin) + { + $ren_cbacklogin = 0; + } + if (!defined $ren_queuemember) + { + $ren_queuemember = 0; + } + if (!defined $change_led) + { + $change_led = 0; + } + if (!defined $kill_zombies) + { + $kill_zombies = 0; + } + if (!defined $poll_voicemail) + { + $poll_voicemail = 0; + } + if (!defined $clid_format) + { + $clid_format = "(xxx) xxx-xxxx"; + } + if (!defined $debug) + { + $debug = 0; + } + else + { + if ($daemonized == 1) + { + $debug = 0; + } + } + $/ = "\0"; +} + +sub collect_includes +{ + my $filename = shift; + my $archivo = $directorio . "/" . $filename; + + if (-r $archivo) + { + + if (!inArray($filename, @btninclude)) + { + push(@btninclude, $filename); + } + else + { + log_debug("** $filename already included", 16); + return; + } + + push(@btninclude, $filename); + + open(CONFIG, "< $archivo") + or die("Could not open $filename. Aborting...\n\n"); + + my @lineas = ; + my $cuantos = @lineas; + foreach my $linea (@lineas) + { + $linea =~ s/^\s+//g; + $linea =~ s/([^;]*)[;](.*)/$1/g; + $linea =~ s/\s+$//g; + if ($linea =~ /^include/) + { + + # store include lines in an array so we can + # process them later excluding duplicates + $linea =~ s/^include//g; + $linea =~ s/^\s+//g; + $linea =~ s/^=>//g; + $linea =~ s/^\s+//g; + $linea =~ s/\s+$//g; + collect_includes($linea); + } + } + close CONFIG; + } + else + { + log_debug("** $archivo not readable... skipping", 16); + } +} + +sub read_astdb_config() +{ + $/ = "\n"; + if (-e "$directorio/op_astdb.cfg") + { + open(ASTDB, "<$directorio/op_astdb.cfg") + or die("Could not open op_astdb.cfg. Aborting..."); + my $contador = 0; + my $key = ""; + while () + { + chomp; + $_ =~ s/^\s+//g; + $_ =~ s/([^;]*)[;](.*)/$1/g; + $_ =~ s/\s+$//g; + + if (/^#/ || /^;/ || /^$/) + { + next; + } # Ignores comments and empty lines + + if (/^\Q[\E/) + { + s/\[(.*)\]/$1/g; + $key = $_; + } + else + { + push @{$astdbcommands{$key}}, $_; + } + } + } + + $/ = "\0"; +} + +sub read_buttons_config() +{ + my @btn_cfg = (); + my $contador = -1; + my @uniq; + my $no_counter = 0; + my @contextos = (); + + $/ = "\n"; + + my %seen = (); + foreach my $item (@btninclude) + { + push(@uniq, $item) unless $seen{$item}++; + } + + foreach my $archivo (@uniq) + { + open(CONFIG, "< $directorio/$archivo") + or die("Could not open $directorio/$archivo. Aborting..."); + + # Read op_buttons.cfg loading it into a hash for easier processing + + while () + { + chop; + $_ =~ s/^\s+//g; + $_ =~ s/([^;]*)[;](.*)/$1/g; + $_ =~ s/\s+$//g; + if (/^#/ || /^;/ || /^$/) + { + next; + } # Ignores comments and empty lines + + if (/^\Q[\E/) + { + $contador++; + s/\[(.*)\]/$1/g; + if (!/^Local/i && !/^_/) + { + tr/a-z/A-Z/; + } + $btn_cfg[$contador]{'channel'} = $_; + } + else + { + next unless ($contador >= 0); + my ($key, $val) = split(/=/, $_); + $key =~ tr/A-Z/a-z/; + $key =~ s/^\s+//g; + $key =~ s/(.*)\s+/$1/g; + if ( $key ne "label" + && $key ne "font_family" + && $key ne "text" + && $key ne "mailbox" + && $key ne "voicemail_context") + { + $val =~ s/^\s+//g; + $val =~ s/(.*)\s+/$1/g; + } + $btn_cfg[$contador]{"$key"} = $val; + if ($key eq "panel_context") + { + push @contextos, $val; + } + } + } + + close(CONFIG); + } + + my %seen2 = (); + my @uniq2 = grep { !$seen2{$_}++ } @contextos; + @contextos = @uniq2; + @uniq2 = grep { !/\*/ } @contextos; + @contextos = @uniq2; + push @contextos, "DEFAULT"; + + # Pass to replicate panel_context=* configuration + my @copy_cfg = (); + @copy_cfg = @btn_cfg; + foreach (@copy_cfg) + { + my %tmphash = %$_; + if (defined($tmphash{"panel_context"})) + { + if ($tmphash{"panel_context"} eq "*") + { + foreach my $contextoahora (@contextos) + { + $contador++; + while (my ($key, $val) = each(%tmphash)) + { + if ($key eq "panel_context") + { + $val = $contextoahora; + } + $btn_cfg[$contador]{"$key"} = $val; + } + } + } + } + } + + # We finished reading the file, now we populate our + # structures with the relevant data + my %rectangles_counter; + my %legends_counter; + + foreach (@btn_cfg) + { + my @positions = (); + my %tmphash = %$_; + + if (defined($tmphash{"panel_context"})) + { + if ($tmphash{"panel_context"} eq "*") + { + + # We skip the * panel_context because we already + # expand them to every context possible before + next; + } + } + + if ($tmphash{"channel"} eq "LEGEND") + { + if (defined($tmphash{"panel_context"})) + { + $tmphash{"panel_context"} =~ tr/a-z/A-Z/; + $tmphash{"panel_context"} =~ s/^DEFAULT$//; + } + else + { + $tmphash{"panel_context"} = ""; + } + my $conttemp = $tmphash{"panel_context"}; + + if (!defined($tmphash{"text"})) + { + $tmphash{"text"} = "LEGEND"; + } + if (!defined($tmphash{"x"})) + { + $tmphash{"x"} = 1; + } + if (!defined($tmphash{"y"})) + { + $tmphash{"y"} = 1; + } + if (!defined($tmphash{"font_size"})) + { + $tmphash{"font_size"} = 16; + } + if (!defined($tmphash{"use_embed_fonts"})) + { + $tmphash{"use_embed_fonts"} = 1; + } + if (!defined($tmphash{"font_family"})) + { + $tmphash{"font_family"} = "Arial"; + } + $tmphash{"text"} = encode_base64($tmphash{"text"}); + $legends_counter{$conttemp}++; + if ($legends_counter{$conttemp} > 1) + { + $legends{$conttemp} .= "&"; + } + $total_legends{$conttemp}++; + $legends{$conttemp} .= "legend_$legends_counter{$conttemp}=" . $tmphash{"x"} . ","; + $legends{$conttemp} .= $tmphash{"y"} . ","; + $legends{$conttemp} .= $tmphash{"text"} . ","; + $legends{$conttemp} .= $tmphash{"font_size"} . ","; + $legends{$conttemp} .= $tmphash{"font_family"} . ","; + $legends{$conttemp} .= $tmphash{"use_embed_fonts"}; + next; + } + + if ($tmphash{"channel"} eq "RECTANGLE") + { + if (defined($tmphash{"panel_context"})) + { + $tmphash{"panel_context"} =~ tr/a-z/A-Z/; + $tmphash{"panel_context"} =~ s/^DEFAULT$//; + } + else + { + $tmphash{"panel_context"} = ""; + } + my $conttemp = $tmphash{"panel_context"}; + + if (!defined($tmphash{"x"})) + { + $tmphash{"x"} = 1; + } + if (!defined($tmphash{"y"})) + { + $tmphash{"y"} = 1; + } + if (!defined($tmphash{"width"})) + { + $tmphash{"width"} = 1; + } + if (!defined($tmphash{"height"})) + { + $tmphash{"height"} = 1; + } + if (!defined($tmphash{"line_width"})) + { + $tmphash{"line_width"} = 1; + } + if (!defined($tmphash{"line_color"})) + { + $tmphash{"line_color"} = "0x000000"; + } + if (!defined($tmphash{"fade_color1"})) + { + $tmphash{"fade_color1"} = "0xd0d0d0"; + } + if (!defined($tmphash{"fade_color2"})) + { + $tmphash{"fade_color2"} = "0xd0d000"; + } + if (!defined($tmphash{"rnd_border"})) + { + $tmphash{"rnd_border"} = 3; + } + if (!defined($tmphash{"alpha"})) + { + $tmphash{"alpha"} = 100; + } + if (!defined($tmphash{"layer"})) + { + $tmphash{"layer"} = "bottom"; + } + + $rectangles_counter{$conttemp}++; + if ($rectangles_counter{$conttemp} > 1) + { + $shapes{$conttemp} .= "&"; + } + $total_shapes{$conttemp}++; + $shapes{$conttemp} .= "rect_$rectangles_counter{$conttemp}=" . $tmphash{"x"} . ","; + $shapes{$conttemp} .= $tmphash{"y"} . ","; + $shapes{$conttemp} .= $tmphash{"width"} . ","; + $shapes{$conttemp} .= $tmphash{"height"} . ","; + $shapes{$conttemp} .= $tmphash{"line_width"} . ","; + $shapes{$conttemp} .= $tmphash{"line_color"} . ","; + $shapes{$conttemp} .= $tmphash{"fade_color1"} . ","; + $shapes{$conttemp} .= $tmphash{"fade_color2"} . ","; + $shapes{$conttemp} .= $tmphash{"rnd_border"} . ","; + $shapes{$conttemp} .= $tmphash{"alpha"} . ","; + $shapes{$conttemp} .= $tmphash{"layer"}; + next; + } + + if (!defined($tmphash{"position"})) + { + log_debug("** Ignored button $tmphash{'channel'}, position?", 16); + next; + } + + if (!defined($tmphash{"server"})) + { + $tmphash{"server"} = 0; + } + else + { + $tmphash{"server"} = $tmphash{"server"} - 1; + } + + if (!defined($tmphash{"label"})) + { + $tmphash{"label"} = $tmphash{"channel"}; + } + + if (!defined($tmphash{"icon"})) + { + $tmphash{"icon"} = "0"; + } + + my $canal_key = $tmphash{"channel"}; + + if ($canal_key =~ m/^PARK\d/) + { + + # Change the PARKXXX tu PARK/XXX + $canal_key =~ s/PARK(.*)/PARK\/$1/g; + } + + if (defined($tmphash{"panel_context"})) + { + $tmphash{"panel_context"} =~ tr/a-z/A-Z/; + $tmphash{"panel_context"} =~ s/^DEFAULT$//; + } + else + { + $tmphash{"panel_context"} = ""; + } + + if ($tmphash{"panel_context"} ne "") + { + $canal_key .= "&" . $tmphash{"panel_context"}; + } + + if ( ($tmphash{"position"} !~ /,/) + && ($tmphash{"position"} !~ /-/) + && (($canal_key =~ /\*/) || ($canal_key =~ /^_/))) + { + + # If it's a wildcard button with just one position + # we fake the same position number to populate + # the array and make the button work anyways. + my $pos = $tmphash{"position"}; + $pos =~ s/(\d+),(\d+)/$1/g; + $tmphash{"position"} = "$pos,$pos"; + $no_counter = 1; + } + + if ($tmphash{"position"} =~ /[,-]/) + { + + my $canalidx = $tmphash{'server'} . "^" . $tmphash{'channel'}; + if (defined($tmphash{"panel_context"}) + && $tmphash{"panel_context"} ne "") + { + $canalidx .= "&" . $tmphash{"panel_context"}; + } + + $instancias{"$canalidx"}{""} = 0; + + my @ranges = split(/,/, $tmphash{"position"}); + foreach my $valu (@ranges) + { + if ($valu !~ m/-/) + { + push @positions, $valu; + } + else + { + my @range2 = split(/-/, $valu); + my $menor = $range2[0] < $range2[1] ? $range2[0] : $range2[1]; + my $mayor = $range2[0] > $range2[1] ? $range2[0] : $range2[1]; + my @newrange = $menor .. $mayor; + foreach my $valevale (@newrange) + { + push @positions, $valevale; + } + } + } + + #@positions = split(/,/, $tmphash{"position"}); + my $count = 0; + foreach my $pos (@positions) + { + $count++; + my $indice_contexto = $pos; + my $chan_trunk = $tmphash{"channel"} . "=" . $count; + if ($tmphash{"panel_context"} ne "") + { + $chan_trunk .= "&" . $tmphash{"panel_context"}; + $indice_contexto .= "@" . $tmphash{"panel_context"}; + $pos .= "@" . $tmphash{"panel_context"}; + } + + $buttons{"$tmphash{'server'}^$chan_trunk"} = $pos; + $textos{"$indice_contexto"} = $tmphash{"label"}; + if ($no_counter == 0) + { + $textos{"$indice_contexto"} .= " " . $count; + } + $iconos{"$indice_contexto"} = $tmphash{"icon"}; + $button_server{"$pos"} = $tmphash{"server"}; + + # Saves last position for the button@context + $lastposition{$tmphash{"panel_context"}} = $pos; + log_debug("** " . $tmphash{"server"} . "^$chan_trunk in position " . $pos, 16); + } + } + else + { + my $lastpos = 0; + $lastpos = $lastposition{$tmphash{"panel_context"}} + if defined($lastposition{$tmphash{"panel_context"}}); + if ($tmphash{"position"} eq "n") + { + if (is_number($lastpos)) + { + $lastpos++; + $lastposition{$tmphash{"panel_context"}} = $lastpos; + } + } + else + { + $lastpos = $tmphash{"position"}; + $lastposition{$tmphash{"panel_context"}} = $lastpos; + } + + log_debug("** " . $tmphash{"channel"} . " in position " . $lastpos, 16); + + if ($tmphash{"panel_context"} ne "") + { + + $buttons{"$tmphash{'server'}^$canal_key"} = $lastpos . "\@" . $tmphash{'panel_context'}; + + $textos{"$lastpos\@$tmphash{'panel_context'}"} = $tmphash{"label"}; + $iconos{"$lastpos\@$tmphash{'panel_context'}"} = $tmphash{"icon"}; + $button_server{$buttons{"$tmphash{'server'}^$canal_key"}} = $tmphash{"server"}; + } + else + { + if ($canal_key =~ /(.*)\*$/) + { + $canal_key .= "=1"; + } + + $buttons{"$tmphash{'server'}^$canal_key"} = $lastpos; + $textos{$lastpos} = $tmphash{"label"}; + $iconos{$lastpos} = $tmphash{"icon"}; + $button_server{$buttons{"$tmphash{'server'}^$canal_key"}} = $tmphash{"server"}; + } + } + + if (defined($tmphash{"no_rectangle"})) + { + my $count = @positions; + if ($count == 0) + { + push @positions, $lastposition{$tmphash{'panel_context'}}; + } + + if ($tmphash{"no_rectangle"} eq "true") + { + my $pcont = $tmphash{"panel_context"}; + if ($pcont eq "") { $pcont = "GENERAL"; } + foreach my $pos (@positions) + { + $pos =~ s/\@$pcont//g; + $no_rectangle{$pcont}{$pos} = 1; + } + } + } + + if (defined($tmphash{"extension"})) + { + if (defined($tmphash{"context"})) + { + $extension_transfer{"$tmphash{'server'}^$canal_key"} = + $tmphash{"extension"} . "@" . $tmphash{"context"}; + } + else + { + $extension_transfer{"$tmphash{'server'}^$canal_key"} = $tmphash{"extension"}; + } + if (defined($tmphash{"voicemail_context"})) + { + $mailbox{"$tmphash{'server'}^$canal_key"} = $tmphash{"extension"} . "@" . $tmphash{"voicemail_context"}; + } + } + if (defined($tmphash{"mailbox"})) + { + $mailbox{"$tmphash{'server'}^$canal_key"} = $tmphash{"mailbox"}; + } + $/ = "\0"; + } + %extension_transfer_reverse = reverse %extension_transfer; + %buttons_reverse = reverse %buttons; +} + +sub genera_config +{ + + # This sub generates the file variables.txt that is read by the + # swf movie on load, with info about buttons, layout, etc. + + $/ = "\n"; + my %style_variables; + my @contextos = (); + my @uniq = (); + my $contextoactual = ""; + + open(STYLE, ") + { + chop($_); + $_ =~ s/^\s+//g; + $_ =~ s/([^;]*)[;](.*)/$1/g; + $_ =~ s/\s+$//g; + next unless $_ ne ""; + + if (/^\Q[\E/) + { + s/\[(.*)\]/$1/g; + $contextoactual = $_; + $contextoactual =~ tr/A-Z/a-z/; + next; + } + $style_variables{$contextoactual} .= $_ . "&"; + } + close(STYLE); + + for (keys %textos) + { + if ($_ =~ /\@/) + { + my @partes = split(/\@/); + if ($partes[1] ne "*") + { + push(@contextos, $partes[1]); + } + } + } + + # Writes default context variables.txt + open(VARIABLES, ">$flash_file") + or die("Could not write configuration data $flash_file.\nCheck your file permissions\n"); + print VARIABLES "server=$web_hostname&port=$listen_port&restart=$enable_restart"; + + if ($config->{"GENERAL"}{"security_code"} eq "") + { + print VARIABLES "&nosecurity=1"; + } + + if (defined($config->{"GENERAL"}{"transfer_timeout"})) + { + my @partes = split(/\|/, $config->{"GENERAL"}{"transfer_timeout"}); + my $cuantos = @partes; + print VARIABLES "&totaltimes=$cuantos"; + my $contador = 1; + foreach (@partes) + { + print VARIABLES "&timeout_$contador=$_"; + $contador++; + } + } + else + { + print VARIABLES "&totaltimes=0"; + } + + if ($no_rectangle{"GENERAL"}) + { + my $pos_no_dibujar = ""; + while (my ($key, $val) = each(%{$no_rectangle{'GENERAL'}})) + { + $pos_no_dibujar .= "$key,"; + } + $pos_no_dibujar = substr($pos_no_dibujar, 0, -1); + print VARIABLES "&nodraw=$pos_no_dibujar"; + } + + while (my ($key, $val) = each(%shapes)) + { + if ($key eq "") # DEFAULT PANEL CONTEXT + { + print VARIABLES "&$val"; + } + } + while (my ($key, $val) = each(%legends)) + { + if ($key eq "") # DEFAULT PANEL CONTEXT + { + print VARIABLES "&$val"; + } + } + while (my ($key, $val) = each(%textos)) + { + $val =~ s/\"(.*)\"/$1/g; + if ($key !~ /\@/) + { + print VARIABLES "&texto$key=$val"; + } + } + while (my ($key, $val) = each(%iconos)) + { + $val =~ s/\"(.*)\"/$1/g; + if ($key !~ /\@/) + { + print VARIABLES "&icono$key=$val"; + } + } + print VARIABLES "&" . $style_variables{"general"}; + if (!defined($total_shapes{""})) + { + $total_shapes{""} = 0; + } + print VARIABLES "total_rectangles=" . $total_shapes{""}; + if (!defined($total_legends{""})) + { + $total_legends{""} = 0; + } + print VARIABLES "&total_legends=" . $total_legends{""}; + close(VARIABLES); + + my %seen = (); + foreach my $item (@contextos) + { + push(@uniq, $item) unless $seen{$item}++; + } + + # Writes variables.txt for each context defined + foreach (@uniq) + { + my $directorio = ""; + my $host_web = ""; + my $contextlower = $_; + $contextlower =~ tr/A-Z/a-z/; + + if (defined($config->{$_}{"flash_dir"})) + { + $directorio = $config->{$_}{"flash_dir"}; + } + else + { + $directorio = $config->{"GENERAL"}{"flash_dir"}; + } + + if (defined($config->{$_}{"web_hostname"})) + { + $host_web = $config->{$_}{"web_hostname"}; + } + else + { + $host_web = $config->{"GENERAL"}{"web_hostname"}; + } + + my $flash_context_file = $directorio . "/variables" . $_ . ".txt"; + push @all_flash_files, $flash_context_file; + open(VARIABLES, ">$flash_context_file") + or die("Could not write configuration data $flash_context_file.\nCheck your file permissions\n"); + print VARIABLES "server=$host_web&port=$listen_port&restart=$enable_restart"; + + if (defined($config->{$_}{"security_code"})) + { + if ($config->{"$_"}{"security_code"} eq "") + { + print VARIABLES "&nosecurity=1"; + } + } + + if (defined($config->{"$_"}{"transfer_timeout"})) + { + my @partes = split(/\|/, $config->{"$_"}{"transfer_timeout"}); + my $cuantos = @partes; + print VARIABLES "&totaltimes=$cuantos"; + my $contador = 1; + foreach (@partes) + { + print VARIABLES "&timeout_$contador=$_"; + $contador++; + } + } + else + { + print VARIABLES "&totaltimes=0"; + } + + if ($no_rectangle{$_}) + { + my $pos_no_dibujar = ""; + while (my ($key, $val) = each(%{$no_rectangle{$_}})) + { + $pos_no_dibujar .= "$key,"; + } + $pos_no_dibujar = substr($pos_no_dibujar, 0, -1); + print VARIABLES "&nodraw=$pos_no_dibujar"; + } + + while (my ($key, $val) = each(%shapes)) + { + if ($key eq $_) # OTHER CONTEXT + { + print VARIABLES "&$val"; + } + } + while (my ($key, $val) = each(%legends)) + { + if ($key eq $_) # OTHER CONTEXT + { + print VARIABLES "&$val"; + } + } + while (my ($key, $val) = each(%textos)) + { + $val =~ s/\"(.*)\"/$1/g; + my $contextoboton = $key; + $contextoboton =~ s/(.*)\@(.*)/$2/g; + $contextoboton =~ tr/a-z/A-Z/; + if ($contextoboton eq $_) + { + $key =~ s/(\d+)\@.+/$1/g; + print VARIABLES "&texto$key=$val"; + } + } + while (my ($key, $val) = each(%iconos)) + { + $val =~ s/\"(.*)\"/$1/g; + my $contextoboton = $key; + $contextoboton =~ s/(.*)\@(.*)/$2/g; + $contextoboton =~ tr/a-z/A-Z/; + if ($contextoboton eq $_) + { + $key =~ s/(\d+)\@.+/$1/g; + print VARIABLES "&icono$key=$val"; + } + } + if (!defined($style_variables{$contextlower})) + { + $style_variables{$contextlower} = $style_variables{"general"}; + } + print VARIABLES "&" . $style_variables{$contextlower}; + if (!defined($total_shapes{$_})) + { + $total_shapes{$_} = 0; + } + print VARIABLES "total_rectangles=" . $total_shapes{$_}; + if (!defined($total_legends{$_})) + { + $total_legends{$_} = 0; + } + print VARIABLES "&total_legends=" . $total_legends{$_}; + close(VARIABLES); + } + $/ = "\0"; +} + +sub dump_internal_hashes_to_stdout +{ + + &print_botones(1); + + &print_instancias(1); + + if (keys(%datos)) + { + &print_datos(1); + } + else + { + print "No data blocks in memory\n"; + } + + if (keys(%sesbot)) + { + &print_sesbot(1); + } + else + { + print "No data sesiones botones\n"; + } + + if (keys(%linkbot)) + { + &print_linkbot(); + } + + &print_cachehit(); + + print "\n"; + while (my ($key, $val) = each(%timeouts)) + { + print "Timer($key)=$val\n"; + } + print "\n"; + + &print_status(); + + &print_clients(); + + &print_cola_write(); +} + +sub generate_configs_onhup +{ + %buttons = (); + %sesbot = (); + %linkbot = (); + %instancias = (); + %textos = (); + %iconos = (); + %extension_transfer = (); + %shapes = (); + %legends = (); + %total_shapes = (); + %total_legends = (); + &read_buttons_config(); + &read_server_config(); + &read_astdb_config(); + &genera_config(); + &send_initial_status(); +} + +sub get_next_trunk_button +{ + my $canalid = shift; + my $contexto = shift; + my $server = shift; + my $canalsesion = shift; + my $canal_tipo_fop = ""; + my $canal; + my $sesion; + my $return = ""; + my @uniq; + my $trunk_pos; + my $debugh = "** GET_NEXT_TRUNK"; + + # This routine mantains and returns the position of each channel inside + # a trunk button. + + log_debug("$debugh START SUB $canalid $contexto $server $canalsesion", 16); + + if ($canalid !~ /\^/) + { + $canal_tipo_fop = $server . "^" . $canalid; + } + else + { + $canal_tipo_fop = $canalid; + $canalid =~ s/(.*)\^(.*)/$2/g; + } + + if ($canal_tipo_fop =~ /\QCAPI[\E/) + { + $canal_tipo_fop =~ tr/a-z/A-Z/; + $canalid =~ tr/a-z/A-Z/; + } + $canal_tipo_fop =~ s/(.*)<(.*)>/$1/g; + $canal_tipo_fop =~ s/\s+//g; + $canal_tipo_fop =~ s/(.*)[-\/](.*)/$1/g; + $sesion = $2; + $sesion =~ s/(.*)\&(.*)/$1/g; # removes context if it has any + $canal_tipo_fop =~ s/(\d+\^IAX2\/)(.*)@?(.*)?/$1\U$2\E/g; + $canal_tipo_fop =~ s/(\d+\^IAX2)\[(.*)@?(.*)?\]?/$1\[\U$2\E\]/g; + log_debug("$debugh canal_tipo_fop $canal_tipo_fop", 64); + + if ($canalid =~ /^_.*/) + { + $canal_tipo_fop = $canalid; + $canal_tipo_fop =~ /([^=].*)(=\d+)(.*)/; + $canal_tipo_fop = $1; + if (defined($3)) + { + $contexto = $3; + } + log_debug("$debugh contexto $contexto", 32); + my ($nada, $ses) = separate_session_from_channel($canalsesion); + $sesion = $ses; + } + $canal_tipo_fop =~ tr/a-z/A-Z/; + log_debug("$debugh canal_tipo_fop $canal_tipo_fop", 64); + + my $canalconcontexto = ""; + if ($contexto ne "") + { + $canalconcontexto = "$canal_tipo_fop$contexto"; + } + else + { + $canalconcontexto = $canal_tipo_fop; + $contexto = ""; + } + + if ($sesion eq "XXXX") + { + + # Si la sesion es XXXX devuelve siempre el 1er boton + log_debug("$debugh return $canal_tipo_fop=1$contexto (1st one)", 64); + return "$canal_tipo_fop=1$contexto"; + } + + my $canalconcontextosinserver = $canalconcontexto; + $canalconcontextosinserver =~ s/(\d+)\^(.*)/$2/g; + if ($canalconcontexto !~ /\^/) + { + $canalconcontexto = $server . "^" . $canalconcontexto; + } + + if (exists($instancias{"$canalconcontexto"})) + { + if (exists($instancias{"$canalconcontexto"}{"$server^$canalsesion"})) + { + log_debug( + "$debugh Found instancias($canalconcontexto)($server^$canalsesion)=$instancias{\"$canalconcontexto\"}{\"$server^$canalsesion\"}", + 64 + ); + $trunk_pos = $instancias{"$canalconcontexto"}{"$server^$canalsesion"}; + } + else + { + log_debug("$debugh Not Found instancias($canalconcontexto)($server^$canalsesion)", 64); + my %busy_slots = (); + foreach my $key1 (sort (keys(%instancias))) + { + if ($key1 eq $canalconcontexto) + { + foreach my $key2 (sort (keys(%{$instancias{$key1}}))) + { + my $indice = $instancias{$key1}{$key2}; + $busy_slots{$indice} = 1; + } + } + } + for ($trunk_pos = 1 ; ; $trunk_pos++) + { + last if (!exists($busy_slots{$trunk_pos})); + } + $instancias{"$canalconcontexto"}{"$server^$canalsesion"} = $trunk_pos; + } + $return = "$canal_tipo_fop=${trunk_pos}$contexto"; + } + return $return; +} + +sub separate_session_from_channel +{ + my $elemento = shift; + my $debugh = "** SEPARATE_SESSION_FROM_CHAN"; + log_debug("$debugh elemento1 $elemento", 32); + if ($elemento !~ /.*[-\/].*[-\/].+$/) + { + if ($elemento =~ /^OH323/ || $elemento =~ /^\QMODEM[I4l]\E/i || $elemento =~ /^mISDN/i) + { + $elemento =~ s/(.*)\/(.*)/\U$1\E\/${2}-${2}/g; + } + else + { + $elemento .= "-XXXX"; + } + } + if ($elemento =~ /^mISDN/i) + { + $elemento =~ s/(.*)\/(.*)/\U$1\E\/${2}-${2}/g; + } + log_debug("$debugh elemento1 $elemento", 32); + $elemento =~ s/(.*)[-\/](.*)/$1\t$2/g; + log_debug("$debugh elemento2 $elemento", 32); + my $canal = $1; + my $sesion = $2; + log_debug("$debugh canal $canal sesion $sesion", 32); + + if (defined($canal) && defined($sesion)) + { + $canal =~ tr/a-z/A-Z/; + $elemento = $canal . "\t" . $sesion; + } + $elemento =~ s/IAX2\[(.*)@(.*)\]\t(.*)/IAX2\[$1\]\t$3/; + $elemento =~ s/IAX2\/(.*)@(.*)\t(.*)/IAX2\/$1\t$3/; + + my @partes = split(/\t/, $elemento); + return @partes; +} + +sub peerinfo +{ + my $sock = shift; + my $short = shift; + if ($sock eq "") + { + return ""; + } + if (defined($sock->peeraddr)) + { + if (defined($short)) + { + return $sock->peerhost; + } + else + { + return sprintf("%s:%s", $sock->peerhost, $sock->peerport); + } + + } + else + { + return ""; + } +} + +sub erase_instances_for_trunk_buttons +{ + my $canalid = shift; + my $canal = shift; + my $server = shift; + my $canalidsinserver = ""; + my $canalglobal; + my $valor; + my @new = (); + my $debugh = "** ERASE_INSTANCE_TRUNK"; + + $canalidsinserver = $canalid; + $canalid = "$server^$canalid"; + $canalid =~ s/(.*)<(.*)>/$1/g; #discards ZOMBIE or MASQ + + log_debug("$debugh canalid $canalid canal $canal", 1); + + $canalglobal = $canalid; + $canalglobal =~ s/(.*)[-\/](.*)/$1/g; + $canalglobal =~ s/IAX2\/(.*)@(.*)/IAX2\/$1/g; + $canalglobal =~ s/IAX2\[(.*)@(.*)\]/IAX2\[$1\]/g; + + my ($nada, $contexto) = split(/\&/, $canal); + if (!defined($contexto)) { $contexto = ""; } + + my $canalconcontexto = ""; + if ($contexto ne "") + { + $canalconcontexto = "$canalglobal&$contexto"; + $contexto = "&$contexto"; + } + else + { + $canalconcontexto = $canalglobal; + $contexto = ""; + } + + my $sesiontemp = $canalid; + if ($canalid =~ /^Zap/i && $canal =~ /\*/) + { + + # Si es un Zap y ademas wildcard, cambio el canalid para + # que tenga la sesion modificada + # $sesiontemp =~ s/Zap/ZAP/g; + $sesiontemp =~ s/(.*)\/(.*)-(.*)/\U$1\/$2-${2}\E${3}/g; + } + if ($canalid =~ /^MGCP/i && $canal =~ /\*/) + { + + # Si es un MGCP y ademas wildcard, cambio el canalid para + # que tenga la sesion modificada + my $sesiontemp2 = $sesiontemp; + $sesiontemp2 =~ s/(.*)\@(.*)-(.*)/$2/g; + $sesiontemp2 = substr($sesiontemp2, -3); + $sesiontemp =~ s/(.*)\/(.*)-(.*)/\U$1\E\/${2}-${sesiontemp2}${3}/g; + } + + log_debug("$debugh looking for $canalid on instancias to erase it", 128); + + foreach my $key1 (sort (keys(%instancias))) + { + foreach my $key2 (sort (keys(%{$instancias{$key1}}))) + { + if ($key2 eq $canalid) + { + delete $instancias{$key1}{$key2}; + log_debug("$debugh Erasing $canalid from instanacias!", 128); + } + } + } +} + +sub generate_linked_buttons_list +{ + my $nroboton = shift; + my $server = shift; + my @botonas = (); + my $listabotones = ""; + my $debugh = "** GEN_LINK_LIST "; + + log_debug("$debugh canal $nroboton server $server", 16); + + if ($nroboton !~ /\^/) + { + $nroboton = "$server^$nroboton"; + } + + my ($nada1, $contexto1) = split(/\&/, $nroboton); + if (!defined($contexto1)) { $contexto1 = ""; } + + if (defined(@{$linkbot{"$nroboton"}})) + { + log_debug("$debugh Esta definido linkbot {$nroboton}", 32); + foreach (@{$linkbot{"$nroboton"}}) + { + log_debug("$debugh y contiene $_", 32); + my ($canal1, $sesion1) = separate_session_from_channel($_); + log_debug("$debugh luego de separate canal1 = $canal1 y sesion1 = $sesion1", 128); + my $canalsesion = $_; + if (!defined($sesion1)) + { + $canalsesion = $canal1 . "-XXXX"; + } + log_debug("$debugh canal1 = $canal1 y sesion1 = $sesion1 canalsesion=$canalsesion", 128); + my @linkbotones = find_panel_buttons($canal1, $canalsesion, $server); + foreach my $cual (@linkbotones) + { + my ($nada2, $contexto2) = split(/\&/, $cual); + if (!defined($contexto2)) { $contexto2 = ""; } + if ($contexto1 eq $contexto2) + { + my $botinro = $buttons{"$server^$cual"}; + push @botonas, $botinro; + log_debug("$debugh Agrego $botinro", 64); + } + } + } + + my %seen2 = (); + my @uniq2 = grep { !$seen2{$_}++ } @botonas; + @botonas = \@uniq2; + + foreach my $val (@uniq2) + { + if (defined($val)) + { + $listabotones .= "$val,"; + log_debug("$debugh devuelve $val", 128); + } + } + $listabotones = substr($listabotones, 0, -1); + } + else + { + log_debug("$debugh NO ESTA DEFINIDO linkbot {$nroboton}", 32); + } + return $listabotones; +} + +sub erase_all_sessions_from_channel +{ + my $canalid = shift; + my $canal = shift; + my $server = shift; + my $canalsesion = $canalid; + my @final; + my @return; + my $debugh = "** ERASE_ALL_SESS_FROM"; + log_debug("$debugh canal $canal canalid $canalid", 16); + + my $indice_cache = $canalid . "-" . $canal . "-" . $server; + log_debug("$debugh borro cache_hit($indice_cache)", 128); + delete $cache_hit{$indice_cache}; + if (keys(%cache_hit)) + { + for (keys %cache_hit) + { + if (defined(@{$cache_hit{$_}})) + { + foreach my $val (@{$cache_hit{$_}}) + { + if ($val eq $canal) + { + log_debug("$debugh borro cache $_", 128); + delete $cache_hit{$_}; + } + } + } + } + } + + if ($canal =~ /=/) + { + + # If its a trunk button, erase instances + erase_instances_for_trunk_buttons($canalsesion, $canal, $server); + } + $canalsesion =~ s/\t/-/g; + $canalid =~ s/(.*)<(.*)>/$1/g; # Removes + + for my $mnroboton (keys %sesbot) + { + @final = (); + foreach my $msesion (@{$sesbot{$mnroboton}}) + { + log_debug("$debugh $msesion ne $canalsesion?", 64); + if ($msesion ne $canalsesion) + { + log_debug("$debugh sesbot es distinto dejo $msesion a \@final", 64); + push @final, $msesion; + } + } + $sesbot{$mnroboton} = [@final]; + } + + if (keys(%linkbot)) + { + for (keys %linkbot) + { + if (defined(@{$linkbot{$_}})) + { + my @final = (); + foreach my $val (@{$linkbot{$_}}) + { + log_debug("$debugh linkbot($_) ne $val ?", 64); + if ($val ne $canalsesion) + { + push @final, $val; + log_debug("$debugh No es igual lo dejo $_", 64); + } + else + { + push @return, $_; + log_debug("$debugh Es igual lo AGREGO RETURN $_", 64); + } + } + + log_debug("$debugh delete linkbot($_)", 64); + delete $linkbot{$_}; + $linkbot{$_} = [@final]; + } + } + } + + my $quehay = ""; + for $quehay (keys %datos) + { + while (my ($key, $val) = each(%{$datos{$quehay}})) + { + if ($key eq "Channel") + { + $val =~ s/(.*)[-\/](.*)/$1\t$2/g; + $val =~ tr/a-z/A-Z/; + if ($canalid eq $val) + { + log_debug("** Found a match $canalid=$val ($quehay) - Cleared!", 16); + delete $datos{$quehay}; + } + } + } + } + for my $valores (@return) + { + log_debug("$debugh devuleve $valores", 64); + } + return @return; +} + +sub extraer_todas_las_sesiones_de_un_canal +{ + my $canal = shift; + my $canalbase = ""; + my $sesion_numero = ""; + my $sesion = ""; + my $key = ""; + my $val = ""; + my $quehay = ""; + my @result = (); + my $debugh = "** EXTRAER_TODAS "; + log_debug("$debugh from the channel $canal", 16); + + # Removes the context if its set + + my @pedazos = split(/&/, $canal); + $canal = $pedazos[0]; + + # Checks if the channel name has an equal sign + # (its a trunk button channel) + + if ($canal =~ /(.*)=(\d+)/) + { + ($canalbase, $sesion_numero) = split(/\=/, $canal); + log_debug("** Its a trunk $canalbase button number $sesion_numero!", 16); + + foreach my $key1 (sort (keys(%instancias))) + { + foreach my $key2 (sort (keys(%{$instancias{$key1}}))) + { + if ($key2 eq $canalbase) + { + push @result, $key2; + log_debug("$debugh encontro sesion $canalbase", 16); + } + } + } + } + + my $cuantos = @result; + if ($cuantos == 0) + { + + # If there is no results for a trunk button, look into the %datos + # hash. + + for $quehay (keys %datos) + { + while (($key, $val) = each(%{$datos{$quehay}})) + { + if (defined($val)) + { + my $vel = $val; + if ($vel =~ /^IAX2/) + { + $vel =~ s/IAX2\/(.*)@(.*)\/(.*)/IAX2\/$1\/$3/g; + $vel =~ s/IAX2\[(.*)@(.*)\](.*)/IAX2\[$1\]$3/g; + } + if ($vel =~ /^\Q$canal\E[-\/]/i && $key eq "Channel") + { + push(@result, $val); + log_debug("** Sesion: $val", 16); + } + } + } + } + } + return @result; +} + +sub extracts_exten_from_active_channel +{ + my $canal = shift; + my $quehay = ""; + my @result = (); + + my @pedazos = split(/&/, $canal); + $canal = $pedazos[0]; + + for $quehay (keys %datos) + { + my $canalaqui = 0; + my $linkeado = ""; + while (my ($key, $val) = each(%{$datos{$quehay}})) + { + + if ($val =~ /^$canal-/i && ($key =~ /^Chan/i || $key =~ /^Link/i)) + { + $canalaqui = 1; + } + if ($key =~ /^Exten/i) + { + $linkeado = $val; + } + } + if ($canalaqui == 1 && $linkeado ne "") + { + push(@result, $linkeado); + } + } + return @result; +} + +sub extraer_todos_los_enlaces_de_un_canal +{ + my $canal = shift; + my $quehay = ""; + my $server = "0"; + my @result = (); + my $debugh = "** EXTRACT_LINKS_CHAN"; + + my @pedazos = split(/&/, $canal); + $canal = $pedazos[0]; + + if ($canal =~ /\^/) + { + @pedazos = split(/\^/, $canal); + $server = $pedazos[0]; + $canal = $pedazos[1]; + } + + # If the channel is not an agent, it will have a session + # appended to it separated with a hypen + if ($canal !~ m/^Agent/i) + { + $canal = $canal . "-"; + } + + log_debug("$debugh canal $canal server $server", 32); + + for $quehay (keys %datos) + { + my $canalaqui = 0; + my $serveraqui = 0; + my $linkeado = ""; + while (my ($key, $val) = each(%{$datos{$quehay}})) + { + log_debug("$debugh buscando $canal en $key=$val", 128); + if ($val =~ /^$canal/i && $key =~ /^Chan/i) + { + $canalaqui = 1; + } + if ($key =~ /^Server/i && $val eq $server) + { + $serveraqui = 1; + } + if ($key =~ /^Link/i) + { + $linkeado = $val; + } + } + if ($canalaqui == 1 && $linkeado ne "" && $serveraqui == 1) + { + push(@result, $linkeado); + log_debug("$debugh Agrego $linkeado a la lista", 32); + } + } + return @result; +} + +sub find_panel_buttons +{ + + # ***************************************************************** + # Based on a CHANNEL name returned by Asterisk, we try to match + # one or more of our buttons to show status. Returns array with list + # of channel names as set in op_buttons.cfg + + my $canal = shift; + my $canalsesion = shift; + my $server = shift; + my $pos = 0; + my $sesion = ""; + my @canales = (); + my $quehay = ""; + my $canalfinal = ""; + my $contextoindex = ""; + my $server_boton = 0; + my $debugh = "** FIND_PANEL_BUT"; + my $calleridnum = "noexiste"; + log_debug("$debugh canal $canal canalsesion $canalsesion server $server", 32); + + my $uniqueid = find_uniqueid($canalsesion, $server); + if ($uniqueid ne "") + { + if (defined($datos{$uniqueid}{"CallerID"})) + { + $calleridnum = $datos{$uniqueid}{"CallerID"}; + } + } + + # XXXXX We have to try hard to find a match for the channel + # There are several posibilities: + # + # Exact match: SIP/jo (no panel context, not trunk, no wildcard) + # Panel Ctxt match: SIP/jo&SIP (exact name, not trunk, no wildcard, panel context) + # Trunk match: SIP/jo=1 (exact name, trunk, no wildcard, no panel context) + # Ctxt&Trunk match: SIP/jo=1&SIP (exact name, trunk, no wildcard, panel context) + # Wildcard SIP/*=1 (wildcard name, trunk) + # + # The key to match syntax is server^[chan_name|wildcard](=trunk_position)(&panel_context) + # + # Here I first will try to match any $buttons that might match the given channel name + + if ($canalsesion =~ / 0) + { + $cache_hit{$indice_cache} = [@canales]; + } + foreach (@canales) + { + log_debug("$debugh cache button $_", 128); + } + return @canales; +} + +sub procesa_bloque +{ + my $blaque = shift; + my $socket = shift; + my %bloque = %$blaque if defined(%$blaque); + + my %hash_temporal = (); + my $evento = ""; + my $canal = ""; + my $sesion = ""; + my $texto = ""; + my $estado_final = ""; + my $unico_id = ""; + my $exten = ""; + my $clid = ""; + my $clidnum = ""; + my $clidname = ""; + my $canalid = ""; + my $key = ""; + my $val = ""; + my @return = (); + my $conquien = ""; + my $enlazado = ""; + my $viejo_nombre = ""; + my $nuevo_nombre = ""; + my $quehay = ""; + my $elemento = ""; + my $state = ""; + my $exists = 0; + my $fakecounter = 1; + my $fill_datos = 0; + my $server = 0; + my $timeout = 0; + my $debugh = "** PROCESA_BLOQUE"; + + log_debug("$debugh START SUB", 16); + + $hash_temporal{"Event"} = ""; + + while (my ($key, $val) = each(%bloque)) + { + if (defined($val)) + { + $val =~ s/(.*)\s+$/$1/g; + } + else + { + $val = ""; + } + + # my ($lcan,$lses) = separate_session_from_channel($val); + # if(exists($channel_to_agent{$lcan})) + # { + # print "Cambio $val por ".$channel_to_agent{$lcan}."\n"; + # my $ori_val = $val; + # $val =~ s/(.*)-(.*)/$channel_to_agent{$lcan}-$2/g; + # ($lcan,$lses) = separate_session_from_channel($val); + # $channel_to_agent{$lcan}=$lcan; + # } + + $hash_temporal{$key} = $val; + + log_debug("$debugh HASH_TEMPORAL($key) = $val", 128); + } + + if ($hash_temporal{"Event"} =~ /^UserEvent/) + { + + # This blocks checks if we have an UserEvent + # and splits every key value pair if it haves + # a caret as a delimiter + while (my ($key, $val) = each(%hash_temporal)) + { + if ($val =~ /\^/) + { + my @partes = split(/\^/, $val, 2); + $hash_temporal{$key} = $partes[0]; + my $resto_de_parametros = $partes[1]; + @partes = split(/\^/, $resto_de_parametros); + foreach my $value (@partes) + { + my @partes2 = split(/: /, $value); + if (!defined($partes2[0])) { next; } + $hash_temporal{$partes2[0]} = $partes2[1]; + } + } + } + } + + $canalid = ""; + $canalid = $hash_temporal{"Channel"} + if defined($hash_temporal{"Channel"}); + + $server = 0; + $server = $hash_temporal{"Server"} + if defined($hash_temporal{"Server"}); + + if (defined($hash_temporal{"Uniqueid"})) + { + $unico_id = $hash_temporal{"Uniqueid"}; + $fill_datos = 1; + } + else + { + $unico_id = "YYYY"; + } + + $enlazado = ""; + if (exists($datos{$unico_id})) + { + + if (exists($datos{$unico_id}{"Link"})) + { + $enlazado = $datos{$unico_id}{"Link"}; + } + + if (exists($datos{$unico_id}{"Application"})) + { + $enlazado .= " - " . $datos{$unico_id}{"Application"}; + } + + if (exists($datos{$unico_id}{"AppData"})) + { + $enlazado .= ":" . $datos{$unico_id}{"AppData"}; + } + + } + + if ($unico_id !~ /-\d+$/) + { + + # Add the server at the end of the uniqueid + # if its not already there + $unico_id .= "-" . $server; + } + + $evento = ""; + if (defined($hash_temporal{"Event"})) + { + $evento = $hash_temporal{"Event"}; + } + + if (defined($hash_temporal{"ActionID"})) + { + if ($hash_temporal{"ActionID"} =~ /^timeout/i) + { + my @partes = split(/\|/, $hash_temporal{"ActionID"}); + $canalid = $partes[1]; + $timeout = $partes[2]; + $evento = "Timeout"; + $unico_id = "YYYY-$server"; + } + } + + log_debug("$debugh canalid $canalid unico_id $unico_id evento $evento enlazado $enlazado", 128); + + # Populates a global hash to keep track of + # 'active' channels, the ones that are not + # state down. + if (defined($unico_id)) + { + if ($unico_id !~ /^YYYY/) + { + + if ($fill_datos) + { # Ignores blocks without Uniqueid + log_debug("$debugh LLENANDO el global datos $unico_id", 64); + delete $datos{$unico_id}{"State"}; + while (my ($key, $val) = each(%hash_temporal)) + { + if ($key eq "Uniqueid") + { + if ($val !~ /-/) + { + $val .= "-" . $server; + } + } + if (!defined($val)) + { + $val = ""; + } + $datos{$unico_id}{"$key"} = $val; + log_debug("$debugh POPULATES datos($unico_id){ $key } = $val", 128); + } + } + } + else + { + log_debug("$debugh NO LLENO el global datos $unico_id", 64); + } + } + + $evento =~ s/UserEvent//g; + if ($evento =~ /Newchannel/) { $evento = "newchannel"; } + elsif ($evento =~ /Newcallerid/) { $evento = "newcallerid"; } + elsif ($evento =~ /^Status$/) { $evento = "status"; } + elsif ($evento =~ /^StatusComplete/) { $evento = "statuscomplete"; } + elsif ($evento =~ /Newexten/) { $evento = "newexten"; } + elsif ($evento =~ /^ParkedCall$/) { $evento = "parkedcall"; } + elsif ($evento =~ /Newstate/) { $evento = "newstate"; } + elsif ($evento =~ /Hangup/) { $evento = "hangup"; } + elsif ($evento =~ /Rename/) { $evento = "rename"; } + elsif ($evento =~ /MessageWaiting/) { $evento = "voicemail"; } + elsif ($evento =~ /Regstatus/) { $evento = "regstatus"; } + elsif ($evento =~ /^Unlink/) { $evento = "unlink"; } + elsif ($evento =~ /QueueParams/) { $evento = "queueparams"; } + elsif ($evento =~ /QueueMember/) { $evento = "queuemember"; } + elsif ($evento =~ /QueueStatus/) { $evento = "queuestatus"; } + elsif ($evento =~ /^Link/) { $evento = "link"; } + elsif ($evento =~ /^Join/) { $evento = "join"; } + elsif ($evento =~ /^MeetmeJoin/) { $evento = "meetmejoin"; } + elsif ($evento =~ /^MeetmeLeave/) { $evento = "meetmeleave"; } + elsif ($evento =~ /^meetmemute/) { $evento = "meetmemute"; } + elsif ($evento =~ /^meetmeunmute/) { $evento = "meetmeunmute"; } + elsif ($evento =~ /^Agentlogin/) { $evento = "agentlogin"; } + elsif ($evento =~ /^RefreshQueue/) { $evento = "refreshqueue"; } + elsif ($evento =~ /^Timeout/) { $evento = "timeout"; } + elsif ($evento =~ /^AgentCalled/) { $evento = "agentcalled"; } + elsif ($evento =~ /^AgentConnect/) { $evento = "agentconnect"; } + elsif ($evento =~ /^AgentCompleted/) { $evento = "agentcompleted"; } + elsif ($evento =~ /^Agentcallbacklogin/) { $evento = "agentcblogin"; } + elsif ($evento =~ /^Agentcallbacklogoff/) { $evento = "agentlogoff"; } + elsif ($evento =~ /^Agentlogoff/) { $evento = "agentlogoff"; } + elsif ($evento =~ /^IsMeetmeMember/) { $evento = "fakeismeetmemember"; } + elsif ($evento =~ /^PeerStatus/) { $evento = "peerstatus"; } + elsif ($evento =~ /^Leave/) { $evento = "leave"; } + elsif ($evento =~ /^FOP_Popup/i) { $evento = "foppopup"; } + elsif ($evento =~ /^FOP_LedColor/i) { $evento = "fopledcolor"; } + elsif ($evento =~ /^Dial/) { $evento = "dial"; } + elsif ($evento =~ /^ASTDB/) { $evento = "astdb"; } + elsif ($evento =~ /^DNDState/) { $evento = "zapdndstate"; } + elsif ($evento =~ /^ZapShowChannels$/) { $evento = "zapdndstate"; } + else { log_debug("$debugh No event match ($evento)", 16); } + + if (defined($hash_temporal{"Link"})) + { + if (defined($hash_temporal{"Seconds"})) + { + my $unid = find_uniqueid($hash_temporal{"Link"}, $server); + $fake_bloque[$fakecounter]{"Event"} = "Newexten"; + $fake_bloque[$fakecounter]{"Channel"} = $hash_temporal{"Link"}; + $fake_bloque[$fakecounter]{"State"} = "Up"; + $fake_bloque[$fakecounter]{"Seconds"} = $hash_temporal{"Seconds"}; + $fake_bloque[$fakecounter]{"CallerID"} = $hash_temporal{"CallerID"}; + $fake_bloque[$fakecounter]{"Uniqueid"} = $unid; + $fakecounter++; + log_debug("$debugh Fake bloque canal $hash_temporal{'Link'} con seconds $hash_temporal{'Seconds'}", 128); + } + } + + if ($evento eq "agentcalled") + { + + # We use this event to send the ringing state for an Agent + $estado_final = "ringing"; + $canal = $hash_temporal{"AgentCalled"}; + $canal =~ tr/a-z/A-Z/; + $canalid = $canal . "-XXXX"; + $clidnum = $hash_temporal{"CallerID"}; + $clidname = $hash_temporal{"CallerIDName"}; + $texto = "Incoming call from [" . format_clid($clidnum, $clid_format) . "]"; + my $base64_clidnum = encode_base64($clidnum . " "); + my $base64_clidname = encode_base64($clidname . " "); + push @return, "$canal|clidnum|$base64_clidnum|$canalid-$server|$canalid"; + push @return, "$canal|clidname|$base64_clidname|$canalid-$server|$canalid"; + push @return, "$canal|$estado_final|$texto|$canalid-$server|$canalid"; + $evento = ""; + } + + if ($evento eq "agentconnect") + { + + # We use this event to fake the ringing state + $estado_final = "ocupado"; + $texto = "Taking call from $hash_temporal{\"Queue\"}"; + $canal = $hash_temporal{"Channel"}; + $canal =~ tr/a-z/A-Z/; + $canalid = $canal . "-XXXX"; + push @return, "$canal|$estado_final|$texto|$canalid-$server|$canalid"; #NEW + $evento = ""; + } + + if ($evento eq "dial") + { + + # We use this hashes to store the remote callerid for CVS-HEAD + my $key = "$server^$hash_temporal{'Destination'}"; + my $dorigen = ""; + my $ddestino = ""; + my $dnada = ""; + $remote_callerid{$key} = $hash_temporal{"CallerID"}; + $remote_callerid_name{$key} = $hash_temporal{"CallerIDName"}; + + # We also look for Dial from Local/XX@context to TECH/XX for + # matching agentcallbacklogins exten@context to real channels + # so we can map outgoing calls to Agent buttons + # It will only work after the agent receives at least one call + ($dorigen, $dnada) = separate_session_from_channel($hash_temporal{'Source'}); + ($ddestino, $dnada) = separate_session_from_channel($hash_temporal{'Destination'}); + if (exists($channel_to_agent{$dorigen})) + { + my $agente = $channel_to_agent{$dorigen}; + + # delete $channel_to_agent{$dorigen}; + $channel_to_agent{$ddestino} = $agente; + } + } + + if ($evento eq "zapdndstate") + { + $canal = $hash_temporal{"Channel"}; + my $zstatus = ""; + if ($canal !~ m/Zap/i) + { + $canal = "Zap/$canal"; + } + if (defined($hash_temporal{"Status"})) + { + $zstatus = $hash_temporal{"Status"}; + } + if (defined($hash_temporal{"DND"})) + { + $zstatus = $hash_temporal{"DND"}; + } + if ($zstatus =~ /disabled/i) + { + $zstatus = ""; + } + + # If we receive a zap dnd state, we fake the ASTDB + # event with family 'dnd'. So it will execute the + # actions listed on op_astdb.cfg inside [dnd] + $fake_bloque[$fakecounter]{"Event"} = "ASTDB"; + $fake_bloque[$fakecounter]{"Channel"} = $canal; + $fake_bloque[$fakecounter]{"Family"} = "dnd"; + $fake_bloque[$fakecounter]{"Value"} = $zstatus; + $fakecounter++; + + $evento = ""; + } + + if ($evento eq "astdb") + { + my $valor = ""; + $estado_final = "astdb"; + ($canal, my $nada) = separate_session_from_channel($hash_temporal{"Channel"}); + $canalid = $hash_temporal{"Channel"} . "-XXXX"; + my $clave = $hash_temporal{"Family"}; + if (!defined($hash_temporal{"Value"})) + { + $valor = ""; + } + else + { + $valor = $hash_temporal{"Value"}; + } + + foreach my $item (@{$astdbcommands{$clave}}) + { + my $item_temp = $item; + $item_temp =~ s/\${value}/$valor/g; + my ($comando, $datos) = split(/=/, $item_temp); + if ($valor ne "") + { + push @return, "$canal|$comando|$datos|$canalid-$server|$canalid"; + } + else + { + push @return, "$canal|$comando||$canalid-$server|$canalid"; + } + } + $evento = ""; + } + + if ($evento eq "timeout") + { + $estado_final = "timeout"; + $texto = $timeout; + my $ahora = time(); + my $unique = find_uniqueid($canalid, $server); + $datos{$unique}{"Timeout"} = $ahora + $timeout; + $timeouts{$canalid} = $ahora + $timeout; + push @return, "$canal|$estado_final|$texto|$unique|$canalid"; #NEW + $evento = ""; + } + + if ($evento eq "regstatus") + { + + # Sends the IP address of the peer to the flash client + # XXXX It will have to store this value internally in future version + # to avoid polling asterisk every time + ($canal, my $nada) = separate_session_from_channel($hash_temporal{"Channel"}); + $texto = $hash_temporal{"IP"}; + my $serv = $hash_temporal{"Server"}; + if ($show_ip) + { + + # $estado_final = "ip"; + $estado_final = "settext"; + $boton_ip{$canalid} = $texto; + push @return, "$canal|$estado_final|$texto|$unico_id|$canalid"; + + # $evento = ""; + } + } + + if ($evento eq "fopledcolor") + { + my $color = ""; + my $state = ""; + ($canal, my $nada) = separate_session_from_channel($hash_temporal{"Channel"}); + $color = $hash_temporal{"Color"}; + $state = $hash_temporal{"State"}; + $estado_final = "fopledcolor"; + push @return, "$canal|$estado_final|$color^$state|$unico_id|$canalid"; + $evento = ""; + } + + if ($evento eq "foppopup") + { + ($canal, my $nada) = separate_session_from_channel($hash_temporal{"Channel"}); + my $url = $hash_temporal{"URL"}; + my $target = $hash_temporal{"Target"}; + my $data = "$url^$target"; + $estado_final = "foppopup"; + push @return, "$canal|$estado_final|$data|$unico_id|$canalid"; + $evento = ""; + } + + if ($evento eq "refreshqueue") + { + ($canal, my $nada) = separate_session_from_channel($hash_temporal{"Channel"}); + + # Turns off led of the agent that generated the refresh + if ($change_led == 1) + { + $estado_final = "changelabel" . $change_led; + push @return, "$canal|$estado_final|original|$unico_id|$canalid"; + } + request_queue_status($socket, $hash_temporal{"Channel"}); + $evento = ""; + } + + if ($evento eq "agentcblogin") + { + my $canalreal = ""; + my $labeltext = "."; + my $texto = $hash_temporal{"Agent"}; + + if (defined($datos{$unico_id}{"Channel"})) + { + ($canalreal, my $nada) = separate_session_from_channel($datos{$unico_id}{"Channel"}); + $canalreal =~ tr/a-z/A-Z/; + $channel_to_agent{"$canalreal"} = "AGENT/$texto"; + $channel_to_agent{"$canalreal"} =~ tr/a-z/A-Z/; + } + $canalreal = "Local/$hash_temporal{'Loginchan'}"; + $canalreal =~ tr/a-z/A-Z/; + $channel_to_agent{"$canalreal"} = "AGENT/$texto"; + $channel_to_agent{"$canalreal"} =~ tr/a-z/A-Z/; + + if ($canalid eq "") + { + $canalid = "$texto-XXXX"; + } + + if (defined($agents{"$server^$texto"})) + { + + # If it was already logged in, fake a logout event + + if ( $ren_agentlogin == 1 + || $ren_cbacklogin == 1 + || $change_led == 1) + { + $canal = $agents{"$server^$texto"}; + + $estado_final = "changelabel" . $change_led; + if ($canal =~ /^AGENT/i) + { + push @return, "AGENT/$texto|setalpha|100|$unico_id|$canalid"; + } + else + { + push @return, "$canal|$estado_final|original|$unico_id|$canalid"; + } + delete $agents{"$server^$texto"}; + } + } + + if (defined($extension_transfer_reverse{$hash_temporal{"Loginchan"}})) + { + $canal = $extension_transfer_reverse{$hash_temporal{"Loginchan"}}; + $canal =~ s/(.*)&(.*)/$1/g; + if ($canal =~ /\^/) + { + my @pedacete = split(/\^/, $canal); + $canal = $pedacete[1]; + } + $estado_final = "changelabel" . $change_led; + if ($ren_cbacklogin == 1) + { + $labeltext = "AGENT/$texto"; + if ($ren_agentname == 1) + { + if (defined($agents_name{"$server^$texto"})) + { + $labeltext = $agents_name{"$server^$texto"}; + } + } + } + if ($canal !~ /^agent/i) + { + push @return, "$canal|$estado_final|$labeltext|$unico_id|$canalid"; + } + else + { + push @return, "AGENT/$texto|setalpha|100|$unico_id|$canalid"; + } + if ($agent_status == 1) + { + push @return, "$canal|isagent|0|$unico_id|$canalid"; + push @return, "AGENT/$texto|isagent|0|$unico_id|$canalid"; + } + $agents{"$server^$texto"} = $canal; + $reverse_agents{$canal} = $texto; + } + $evento = ""; + } + + if ($evento eq "agentlogin") + { + + while (($key, $val) = each(%hash_temporal)) + { + + # print "$key = $val\n"; + } + + my $labeltext = "."; + + $texto = $hash_temporal{"Agent"}; + ($canal, my $nada) = separate_session_from_channel($hash_temporal{"Channel"}); + $estado_final = "changelabel" . $change_led; + if ($canalid eq "") + { + $canalid = "AGENT/$texto-XXXX"; + } + + if ($ren_agentlogin == 1 && !defined($hash_temporal{'Fake'})) + { + $labeltext = "Agent/$texto"; + if ($ren_agentname == 1) + { + if (defined($agents_name{"$server^$texto"})) + { + $labeltext = $agents_name{"$server^$texto"}; + } + } + } + if ($ren_queuemember == 1) + { + $labeltext = "Agent/$texto"; + if ($ren_agentname == 1) + { + if (defined($agents_name{"$server^$texto"})) + { + $labeltext = $agents_name{"$server^$texto"}; + } + } + } + + if ($canal =~ m/^AGENT/i) + { + push @return, "$canal|setalpha|100|$unico_id|$canalid"; + } + else + { + push @return, "$canal|$estado_final|$labeltext|$unico_id|$canalid"; + } + + if ($agent_status == 1) + { + push @return, "$canal|isagent|0|$unico_id|$canalid"; + } + + $agents{"$server^$texto"} = $canal; + $evento = ""; + } + + if ($evento eq "agentlogoff") + { + my $agente = "AGENT/" . $hash_temporal{'Agent'}; + my %temp_channel_to_agent = %channel_to_agent; + while (($key, $val) = each(%temp_channel_to_agent)) + { + if ($val eq $agente) + { + delete $channel_to_agent{$key}; + } + } + undef %temp_channel_to_agent; + + if ($ren_agentlogin == 1 || $ren_cbacklogin == 1 || $change_led == 1) + { + $texto = $hash_temporal{"Agent"}; + $estado_final = "changelabel" . $change_led; + + if ($canalid eq "") + { + $canalid = "AGENT/$texto-XXXX"; + } + + push @return, "AGENT/$texto|setalpha|50|$unico_id|$canalid"; + + if ($agent_status == 1) + { + push @return, "$canal|isagent|-1|$unico_id|$canalid"; + push @return, "AGENT/$texto|isagent|-1|$unico_id|$canalid"; + } + + if (defined($agents{"$server^$texto"})) + { + $canal = $agents{"$server^$texto"}; + push @return, "$canal|$estado_final|original|$unico_id|$canalid"; + delete $agents{"$server^$canal"}; + delete $agents{"$server^$texto"}; + delete $reverse_agents{$texto}; + delete $reverse_agents{$canal}; + } + + $evento = ""; + } + } + + if ($evento eq "queuemember") + { + my $canalag = $hash_temporal{"Location"}; + $canalag =~ tr/a-z/A-Z/; + my $canalagid = $canalag . "-XXXX"; + my $unicoag_id = "$canalag-$server"; + $canal = $hash_temporal{"Location"}; + + if ($canal =~ /^AGENT/i) + { + my $temp = $canal; + $temp =~ s/^AGENT\///gi; + if (defined($agents{"$server^$temp"})) + { + $canal = $agents{"$server^$temp"}; + } + } + $canal =~ tr/a-z/A-Z/; + $estado_final = "info"; + $texto = ""; + while (($key, $val) = each(%hash_temporal)) + { + if (!defined($val)) { $val = " "; } + $texto .= "$key = $val\n"; + if ($key eq "Queue") + { + $estado_final .= $val; + } + } + $unico_id = "$canal-$server"; + $texto .= " "; + $texto = encode_base64($texto); + $canalid = $canal . "-XXXX"; + push @return, "$canal|$estado_final|$texto|$unico_id|$canalid"; + push @return, "$canalag|$estado_final|$texto|$unicoag_id|$canalagid"; + $evento = ""; + + if ($canal !~ /^AGENT/i) + { + + # Generates Fake Agent Login to change led color and label renaming + $fake_bloque[$fakecounter]{"Event"} = "Agentlogin"; + $fake_bloque[$fakecounter]{"Channel"} = $canal . "-XXXX"; + $fake_bloque[$fakecounter]{"Agent"} = $canal; + $fake_bloque[$fakecounter]{"Fake"} = "1"; + $fakecounter++; + } + + } + + if ($evento eq "queuestatus") + { + $canal = $hash_temporal{"Queue"}; + $canal =~ tr/a-z/A-Z/; + $estado_final = "infoqstat"; + $texto = ""; + while (($key, $val) = each(%hash_temporal)) + { + $texto .= "$key = $val\n"; + } + $unico_id = "$canal-$server"; + $texto .= " "; + $texto = encode_base64($texto); + push @return, "$canal|$estado_final|$texto|$unico_id|$canalid"; + $evento = ""; + } + + if ($evento eq "meetmemute" || $evento eq "meetmeunmute") + { + my ($canal, $nada) = separate_session_from_channel($canalid); + $estado_final = $evento; + push @return, "$canal|$evento||$unico_id|$canalid"; + $evento = ""; + } + + if ($evento eq "queueparams") + { + $canal = $hash_temporal{"Queue"}; + $canal =~ tr/a-z/A-Z/; + $estado_final = "ocupado"; + my $plural = ""; + if ($hash_temporal{"Calls"} > 0) + { + if ($hash_temporal{"Calls"} > 1) { $plural = "s"; } + $texto = $hash_temporal{"Calls"} . " member$plural waiting on queue..."; + $unico_id = "$canal-$server"; + push @return, "$canal|$estado_final|$texto|$unico_id|$canalid"; + $evento = ""; + } + + # Generates a Fake Block/Event for sending info status to queues + while (($key, $val) = each(%hash_temporal)) + { + $fake_bloque[$fakecounter]{$key} = $val; + } + $fake_bloque[$fakecounter]{"Event"} = "QueueStatus"; + $fakecounter++; + } + + if ($evento eq "join") + { + $canal = $hash_temporal{"Queue"}; + $canal =~ tr/a-z/A-Z/; + $estado_final = "ocupado"; + my $plural = ""; + if ($hash_temporal{"Count"} > 1) { $plural = "s"; } + $texto = "[" . $hash_temporal{"Count"} . " user$plural waiting.]"; + $unico_id = "$canal-$server"; + push @return, "$canal|$estado_final|$texto|$unico_id|$canalid"; + $evento = ""; + } + + if ($evento eq "meetmejoin") + { + my $originate = "no"; + my $nada = ""; + my $contexto = ""; + + $canal = $hash_temporal{"Meetme"}; + my $uni_id = $hash_temporal{"Uniqueid"} . "-" . $server; + log_debug("$debugh MEETMEJOIN uni_id = $uni_id y canal = $canal", 128); + $datos{$uni_id}{"Extension"} = $canal; + log_debug("$debugh 2 BORRO datos $uni_id { link }", 128); + delete $datos{$uni_id}{"Link"}; + + $canal =~ tr/a-z/A-Z/; + + for $quehay (keys %auto_conference) + { + + if ($quehay eq $hash_temporal{"Channel"}) + { + $originate = $auto_conference{"$quehay"}; + $contexto = $barge_context{$canal}; + } + } + + if ($originate ne "no") + { + log_debug("$debugh origino a meetme en el contexto $contexto!", 128); + my $comando = "Action: Originate\r\n"; + $comando .= "Channel: $originate\r\n"; + $comando .= "Exten: $canal\r\n"; + $comando .= "Context: " . $config->{$contexto}{'conference_context'} . "\r\n"; + $comando .= "Priority: 1\r\n"; + $comando .= "\r\n"; + send_command_to_manager($comando, $socket); + + if ($barge_muted) + { + $start_muted{"$server^$originate"} = 1; + } + } + + $estado_final = "ocupado9"; # 9 for conference + my $plural = ""; + + if (!defined($hash_temporal{"Fake"})) + { + if (!defined($datos{"$canal-$server"}{"Count"})) + { + $datos{"$canal-$server"}{"Count"} = 0; + log_debug("$debugh POPULATES datos($canal-$server){ count } = 0", 64); + } + $datos{"$canal-$server"}{"Count"}++; + log_debug("$debugh pongo DATOS ($canal-$server) {count} en $datos{\"$canal-$server\"}{'Count'}", 16); + } + else + { + } + + # Its a fake meetmejoin generated from the meetme status at startup + my ($canalsinses, $pedses) = separate_session_from_channel($hash_temporal{'Channel'}); + push @return, + "$hash_temporal{'Meetme'}|setlink|$hash_temporal{'Channel'}|YYYY-$server|$hash_temporal{'Channel'}"; + push @return, + "$canalsinses|setlink|$hash_temporal{'Meetme'}|$hash_temporal{'Meetme'}-$server|$hash_temporal{'Channel'}"; + push @return, + "$canalsinses|meetmeuser|$hash_temporal{'Usernum'},$hash_temporal{'Meetme'}|YYYY-$server|$hash_temporal{'Channel'}"; + + if (defined($hash_temporal{"Total"})) + { + $datos{"$canal-$server"}{"Count"} = $hash_temporal{"Total"}; + log_debug("$debugh pongo DATOS de ($canal-$server) {count} en $hash_temporal{'Total'}", 64); + } + + $barge_rooms{"$canal"} = $datos{"$canal-$server"}{"Count"}; + + if (defined($datos{"$canal-$server"}{"Count"})) + { + if ($datos{"$canal-$server"}{"Count"} > 1) { $plural = "s"; } + $texto = + $datos{"$canal-$server"}{"Count"} + . " member$plural on conference [" + . $datos{"$canal-$server"}{"Count"} + . " Member$plural]."; + } + + if (exists($start_muted{"$server^$canalsinses"})) + { + my $boton_con_contexto = $buttons{"$server^$canalsinses"}; + my $comando = "Action: Command\r\n"; + $comando .= "ActionID: meetmemute$boton_con_contexto\r\n"; + $comando .= "Command: meetme mute $hash_temporal{'Meetme'} $hash_temporal{'Usernum'}\r\n\r\n"; + send_command_to_manager($comando, $socket); + delete $start_muted{"$server^$canalsinses"}; + } + + $unico_id = $canal . "-" . $server; + push @return, "$canal|$estado_final|$texto|$unico_id|$canalid"; + $evento = ""; + + } + + if ($evento eq "meetmeleave") + { + $canal = $hash_temporal{"Meetme"}; + $canal =~ tr/a-z/A-Z/; + $estado_final = "ocupado9"; # 9 for meetme + my $plural = ""; + $datos{"$canal-$server"}{"Count"}--; + log_debug("$debugh pongo DATOS ( $canal-$server) (count) en $datos{\"$canal-$server\"}{'Count'} leave", 64); + $barge_rooms{"$canal"} = $datos{"$canal-$server"}{"Count"}; + if ($datos{"$canal-$server"}{"Count"} > 1) { $plural = "s"; } + if ($datos{"$canal-$server"}{"Count"} <= 0) { $estado_final = "corto"; } + $texto = + $datos{"$canal-$server"}{"Count"} + . " member$plural on conference [" + . $datos{"$canal-$server"}{"Count"} + . " member$plural]."; + $unico_id = $canal . "-" . $server; + push @return, "$canal|$estado_final|$texto|$unico_id|$canalid"; + my $canaleja = $hash_temporal{"Channel"}; + delete $auto_conference{$canaleja}; + log_debug("$debugh Erasing auto_conference $canaleja", 16); + + for $quehay (keys %auto_conference) + { + log_debug("$debugh Remaining conferences: $quehay", 16); + } + + my ($canal1, $nada1) = separate_session_from_channel($canaleja); + push @return, "$canal1|unsetlink|$canal|$unico_id|$canalid"; + $evento = ""; + } + + if ($evento eq "leave") + { + $canal = $hash_temporal{"Queue"}; + $canal =~ tr/a-z/A-Z/; + $estado_final = "ocupado"; + my $plural = ""; + if ($hash_temporal{"Count"} > 1) { $plural = "s"; } + if ($hash_temporal{"Count"} == 0) { $estado_final = "corto"; } + $texto = "[" . $hash_temporal{"Count"} . " member$plural on queue.]"; + $unico_id = "$canal-$server"; + push @return, "$canal|$estado_final|$texto|$unico_id|$canalid"; + $evento = ""; + } + + if ($evento eq "voicemail") + { + my @canalesvoicemail = (); + + while (my ($ecanal, $eextension) = each(%mailbox)) + { + if ($eextension eq $hash_temporal{"Mailbox"}) + { + $canal = $ecanal; + $canal =~ s/(.*)\&(.*)/$1/g; # Remove &context + $canal =~ s/(.*)\^(.*)/$2/g; # Remove Server + push @canalesvoicemail, $canal; + } + } + foreach my $canal (@canalesvoicemail) + { + $unico_id = $canal; + $canalid = $canal . "-XXXX"; + if (defined($hash_temporal{"Waiting"})) + { + $estado_final = "voicemail"; + $texto = $hash_temporal{"Waiting"}; + if ($texto eq "1") + { + + # If it has new voicemail, ask for mailboxcount + send_command_to_manager("Action: MailboxCount\r\nMailbox: $hash_temporal{'Mailbox'}\r\n\r\n", + $socket); + } + } + else + { + $estado_final = "voicemailcount"; + my $nuevos = $hash_temporal{"NewMessages"}; + my $viejos = $hash_temporal{"OldMessages"}; + $texto = "New $nuevos, Old $viejos"; + $canalid = $canal . "-XXXX"; + } + push @return, "$canal|$estado_final|$texto|$unico_id-$server|$canalid"; + } + $evento = ""; + } + + if ($evento eq "link") + { + my $uniqueid1 = $hash_temporal{"Uniqueid1"}; + my $uniqueid2 = $hash_temporal{"Uniqueid2"}; + if ($uniqueid1 !~ /-\d+$/) + { + $uniqueid1 .= "-" . $server; + } + if ($uniqueid2 !~ /-\d+$/) + { + $uniqueid2 .= "-" . $server; + } + my $channel1 = $hash_temporal{"Channel1"}; + my $channel2 = $hash_temporal{"Channel2"}; + log_debug("$debugh DATOS de $uniqueid1 { link } en $channel2", 64); + log_debug("$debugh DATOS de $uniqueid2 { link } en $channel1", 64); + $datos{$uniqueid1}{"Link"} = $channel2; + $datos{$uniqueid2}{"Link"} = $channel1; + my ($canal1, $sesion1) = separate_session_from_channel($channel1); + my ($canal2, $sesion2) = separate_session_from_channel($channel2); + + # push @return, "$canal1|link|$canal2|$uniqueid2|${canal1}-XXXX"; + # delete $datos{$unico_id}; + # print "3 BORRO datos $unico_id \n"; + $evento = ""; + $canal = $canal1; + $estado_final = "ocupado7"; # 7 for linked channel, start billing + + if (exists($parked{"$server^$channel1"})) + { + log_debug("$debugh EXISTE parked{$server^$channel1} ", 128); + my $parkexten = $parked{"$server^$channel1"}; + delete $parked{"$server^$channel1"}; + push @return, "PARK/$parkexten|corto||YYYY-$server|$channel1"; + push @return, "$canal1|ocupado5||$uniqueid1|$channel1"; + } + else + { + log_debug("$debugh NO EXISTE parked{$server^$channel1}", 128); + } + + if (exists($parked{"$server^$channel2"})) + { + log_debug("$debugh EXISTE parked{$server^$channel2} ", 128); + log_debug("$debugh SI EXISTE!", 128); + my $parkexten = $parked{"$server^$channel2"}; + delete $parked{"$server^$channel2"}; + push @return, "PARK/$parkexten|corto||YYYY-$server|$channel2"; + push @return, "$canal2|ocupado5||$uniqueid2|$channel2"; + } + else + { + log_debug("$debugh NO EXISTE parked{$server^$channel2}", 128); + } + my $clid1 = ""; + my $clid2 = ""; + + if (defined($datos{$uniqueid2}{"Callerid"})) + { + $clid1 = $datos{$uniqueid2}{"Callerid"}; + } + if (defined($datos{$uniqueid2}{"CallerID"})) + { + $clid1 = $datos{$uniqueid2}{"CallerID"}; + } + if (defined($datos{$uniqueid1}{"Callerid"})) + { + $clid2 = $datos{$uniqueid1}{"Callerid"}; + } + if (defined($datos{$uniqueid1}{"CallerID"})) + { + $clid2 = $datos{$uniqueid1}{"CallerID"}; + } + + if ($clid1 ne "" && $channel2 ne "") + { + push @return, "$canal1|settext|$clid1|$uniqueid1|$channel1"; + } + if ($clid2 ne "" && $channel1 ne "") + { + push @return, "$canal2|settext|$clid2|$uniqueid2|$channel2"; + } + push @return, "$canal1|setlink|$channel2|$uniqueid1|$channel1"; + push @return, "$canal2|setlink|$channel1|$uniqueid2|$channel2"; + $evento = ""; #NEW + } + + if ($evento eq "unlink") + { + my $uniqueid1 = $hash_temporal{"Uniqueid1"}; + my $uniqueid2 = $hash_temporal{"Uniqueid2"}; + my $channel1 = $hash_temporal{"Channel1"}; + my $channel2 = $hash_temporal{"Channel2"}; + my ($canal1, $sesion1) = separate_session_from_channel($channel1); + my ($canal2, $sesion2) = separate_session_from_channel($channel2); + + log_debug("$debugh Unlink $canal1 and $canal2", 16); + $evento = ""; + + $estado_final = "unsetlink"; + $canal = $canal1; + + my $boton1 = 0; + my $boton2 = 0; + + for my $mnroboton (keys %sesbot) + { + foreach my $msesion (@{$sesbot{$mnroboton}}) + { + if ($msesion eq $channel1) + { + $boton1 = $mnroboton; + } + if ($msesion eq $channel2) + { + $boton2 = $mnroboton; + } + } + } + + #push @return, "$boton1|unsetlink|$boton2|$uniqueid1|$channel2"; + #push @return, "$boton2|unsetlink|$boton1|$uniqueid2|$channel1"; + push @return, "$canal1|unsetlink|$channel2|$uniqueid1-$server|$channel1"; + push @return, "$canal2|unsetlink|$channel1|$uniqueid2-$server|$channel2"; + $evento = ""; #NEW + } + + if ($evento eq "rename") + { + my $nuevo_nombre = ""; + my $viejo_nombre = ""; + log_debug("$debugh RENAME Event", 16); + $evento = ""; + while (($key, $val) = each(%hash_temporal)) + { + if ($key =~ /newname/i) + { + $nuevo_nombre = $val; + } + if ($key =~ /oldname/i) + { + $viejo_nombre = $val; + } + } + log_debug("$debugh RENAME $viejo_nombre por $nuevo_nombre (id $unico_id)", 16); + + # Directamente borra la sesion que se debe renombrar + if (($nuevo_nombre !~ ///g; + push @return, "$canal|ocupado3|$texto|$unidchan|$canalid"; + push @return, + "PARK/$hash_temporal{'Exten'}|park|[$textid]$timeout|$hash_temporal{'Timeout'}-$server|$hash_temporal{'Channel'}"; + + log_debug("$debugh pongo parked($server^$hash_temporal{'Channel'}) en $hash_temporal{'Exten'}", 64); + $parked{"$server^$hash_temporal{'Channel'}"} = $hash_temporal{'Exten'}; + $evento = ""; #NEW + } + + if ($evento eq "newcallerid") + { + $estado_final = "ocupado"; + $state = "Newcallerid"; + my $save_clidnum = ""; + my $save_clidname = ""; + if (defined($hash_temporal{'CallerIDName'})) + { + $save_clidnum = $hash_temporal{'CallerID'}; + $save_clidname = $hash_temporal{'CallerIDName'}; + } + elsif (defined($hash_temporal{'CalleridName'})) + { + $save_clidnum = $hash_temporal{'Callerid'}; + $save_clidname = $hash_temporal{'CalleridName'}; + } + else + { + ($save_clidnum, $save_clidname) = split_callerid($hash_temporal{'CallerID'}); + } + $saved_clidnum{"$server^$hash_temporal{'Channel'}"} = $save_clidnum; + $saved_clidname{"$server^$hash_temporal{'Channel'}"} = $save_clidname; + } + + # From now on, we only look for the State of the datos block + # Dont check for $evento bellow this line! + + if ($evento ne "") + { + log_debug("$debugh Event $evento, canal '$canal'", 16); + + # De acuerdo a los datos de la extension genera + # la linea con info para el flash + + $elemento = $canalid; + + if (exists($datos{$unico_id})) + { + if (exists($datos{$unico_id}{'Channel'})) + { + $elemento = $datos{$unico_id}{'Channel'}; + + # Old IAX naming convention + if ($elemento =~ /^IAX2\[/ && $elemento =~ /\@/) + { + + # The channel is IAX2 and has the @context + # I will remove the @context/host because it varies + $elemento =~ s/IAX2\[(.*)@(.*)\](.*)/IAX2\[$1\]$3/g; + } + + if ($elemento =~ /^IAX2\// && $elemento =~ /\@/) + { + $elemento =~ s/IAX2\/(.*)@(.*)\/(.*)/IAX2\/$1\/$3/g; + } + } + } + ($canal, $sesion) = separate_session_from_channel($elemento); + + if (defined($canal)) + { + $canal =~ tr/a-z/A-Z/; + } + else + { + log_debug("$debugh canal not defined!! END $elemento", 16); + + while (my ($key, $val) = each(%hash_temporal)) + { + log_debug("$debugh hash_temporal $key = $val", 128); + } + return; + } + + if (defined($sesion)) + { + log_debug("$debugh canal $canal sesion $sesion", 128); + } + + if (exists($datos{$unico_id})) + { + + log_debug("$debugh EXISTE datos($unico_id) ", 32); + + if (exists($datos{$unico_id}{'Extension'})) + { + $exten = $datos{$unico_id}{'Extension'}; + } + + if (exists($datos{$unico_id}{'State'})) + { + log_debug("$debugh EXISTE datos($unico_id){state}", 32); + $state = $datos{$unico_id}{'State'}; + } + + if (exists($datos{$unico_id}{'Callerid'})) + { + $clid = $datos{$unico_id}{'Callerid'}; + } + + if (exists($datos{$unico_id}{'CallerID'})) + { + $clid = $datos{$unico_id}{'CallerID'}; + } + if ($clid ne "") + { + ($clidnum, $clidname) = split_callerid($clid); + } + if (exists($datos{$unico_id}{'CallerIDName'})) + { + $clidname = $datos{$unico_id}{'CallerIDName'}; + } + + # The ones below are for catching the callerid from the + # Dial event on CVS-HEAD + if (exists($remote_callerid{"$server^$canalid"})) + { + $clidnum = $remote_callerid{"$server^$canalid"}; + } + if (exists($remote_callerid_name{"$server^$canalid"})) + { + $clidname = $remote_callerid_name{"$server^$canalid"}; + } + } + else + { + log_debug("$debugh NO EXISTE datos($unico_id)", 32); + } + + if ($state eq "") + { + if (defined($hash_temporal{'State'})) + { + $state = $hash_temporal{'State'}; + } + else + { + $state = ""; + } + } + + my $clid_with_format = format_clid($clidnum, $clid_format); + + if ($state eq "Ringing") + { + if ($clidnum ne "") + { + my $base64_clidnum = encode_base64($clidnum . " "); + my $base64_clidname = encode_base64($clidname . " "); + my $ret = "$canal|clidnum|$base64_clidnum|$unico_id|$hash_temporal{'Channel'}"; + push @return, $ret; + $ret = "$canal|clidname|$base64_clidname|$unico_id|$hash_temporal{'Channel'}"; + push @return, $ret; + } + } + + if ($state eq "Rsrvd") + { + $state = "Ring"; + } + + if ($state eq "Ring") + { + $texto = "Originating call "; + $estado_final = "ring"; + $datos{$unico_id}{'Origin'} = "true"; + log_debug("$debugh POPULATES datos($unico_id){ Origin } = true", 128); + } + + if ($state eq "Dialing") + { + $texto = "Dialing"; + $estado_final = "ring"; + if ($canal =~ /^OH323/) + { + + # OH323 use a random channel name when Dialing + # that later changes its name. So we just discard + # this event/name to avoid getting Dialing channels + # foerever (because we never receive a Down status) + $estado_final = ""; + } + } + + if ($state =~ /^UNK/) + { + $texto = "No registrado"; + $estado_final = "noregistrado"; + $unico_id = "YYYY-$server"; + if ($canalid !~ /(.*)-XXXX$/) + { + $canalid = $canalid .= "-XXXX"; + } + } + + if ($state =~ /^UNR/) + { + $texto = "No alcanzable"; + $estado_final = "unreachable"; + $unico_id = "YYYY-$server"; + if ($canalid !~ /(.*)-XXXX$/) + { + $canalid = $canalid .= "-XXXX"; + } + } + + if ($state =~ /^Unm/) + { + $texto = "Registrado"; + $estado_final = "registrado"; + $unico_id = "YYYY-$server"; + if ($canalid !~ /(.*)-XXXX$/) + { + $canalid = $canalid .= "-XXXX"; + } + } + + if ($state =~ /^OK/) + { + $texto = "Registrado"; + $estado_final = "registrado"; + $unico_id = "YYYY-$server"; + if ($canalid !~ /(.*)-XXXX$/) + { + $canalid = $canalid .= "-XXXX"; + } + } + + if ($state eq "Newcallerid") + { + $texto = "Incoming call from [" . $clid_with_format . "] " . $enlazado; + } + + if ($state eq "Ringing") + { + $texto = "Incoming call from [" . $clid_with_format . "] " . $enlazado; + $estado_final = "ringing"; + } + + if ($state eq "Down") + { + + if ($evento eq "hangup") + { + + # Solo nos interesa mandar a flash los down por hangup + # para evitar enviar eventos redundantes + $estado_final = "corto"; + if ($agent_status == 1) + { + if (exists($agents{"$server^$canal"}) || exists($reverse_agents{$canal})) + { + print + "Existe agents{$server^$canal) $agents{\"$server^$canal\"}, o reverse_agents($canal) $reverse_agents{$canal}\n"; + push @return, "$canal|isagent|0|$unico_id|$canalid"; + } + } + } + delete $timeouts{$canalid}; + delete $remote_callerid{"$server^$canalid"}; + delete $remote_callerid_name{"$server^$canalid"}; + delete $saved_clidname{"$server^$canalid"}; + delete $saved_clidnum{"$server^$canalid"}; + + #erase_instances_for_trunk_buttons($canalsesion); + } + + my $exclude = 0; + my $exten_clid = ""; + if ($state eq "Up") + { + if ($exten ne "") + { + if (is_number($exten)) + { + $exten_clid = $exten; + if (defined($saved_clidnum{"$server^$canalid"})) + { + $exten_clid = format_clid($saved_clidnum{"$server^$canalid"}, $clid_format); + } + if ($clid_privacy) + { + $exten_clid = "n/a"; + } + $conquien = "[" . $exten_clid . "]"; + } + else + { + if (length($exten) == 1) + { + $conquien = $exten; + $exclude = 1; # We ignore events that have letter extensions 's', 'h', etc + log_debug("$debugh CLID is not a number! $exten", 16); + } + else + { + $conquien = $exten; + } + } + } + else + { + $conquien = $clid_with_format; + } + + if (defined($hash_temporal{'Seconds'})) + { + + # $conquien .= " (" . $hash_temporal{'Seconds'} . ")"; + push @return, "$canal|settimer|$hash_temporal{'Seconds'}|$unico_id|$canalid"; + push @return, "$canal|state|busy|$unico_id|$canalid"; + + } + + if (defined($datos{$unico_id}{'Origin'})) + { + if ($datos{$unico_id}{'Origin'} eq "true") + { + if ($exclude == 1) + { + $texto = "skip"; + } + else + { + + # $texto = "Calling $conquien - $enlazado"; + $texto = "Calling [$exten_clid]"; + } + $estado_final = "ocupado2"; # 2 for origin button + } + } + else + { + + # $texto = "Incoming call from $conquien - $enlazado"; + $texto = "Incoming call from [$conquien]"; + $estado_final = "ocupado1"; # 1 for destination button + } + } + + # Remove special character from Caller ID string + $texto =~ s/\"/'/g; + $texto =~ s//]/g; + $texto =~ s/\|/ /g; + + push @return, "$canal|$estado_final|$texto|$unico_id|$canalid"; + + } + + my %seen = (); + my @uniq = + grep { !$seen{$_}++ } @return; + @return = @uniq; + + foreach (@return) + { + log_debug("$debugh returns $_", 16); + } + return @return; + +} + +sub digest_event_block +{ + my $bleque = shift; + my $tipo = shift; + my $socket = shift; + my @blique = @$bleque; + my @respuestas = (); + my $canal = ""; + my $quehace = ""; + my $dos = ""; + my $uniqueid = ""; + my $canalid = ""; + my $quehay = ""; + my @mensajes = (); + my $interno = ""; + my $todo = ""; + my @mensajefinal; + my $cuantas; + my $server = 0; + my %cambiaron; + my $debugh = "** DIGEST_EVENT:"; + + log_debug("$debugh START SUB ($tipo)", 32); + + @fake_bloque = (); + delete $datos{""}; + foreach my $blaque (@blique) + { + @mensajes = procesa_bloque($blaque, $socket); + foreach my $mensaje (@mensajes) + { + if (defined($mensaje) && $mensaje ne "") + { + log_debug("$debugh GOT $mensaje", 32); + delete $datos{""}; # Erase the hash with no uniqueid + ($canal, $quehace, $dos, $uniqueid, $canalid) = + split(/\|/, $mensaje); + if ($dos eq "skip") + { + log_debug("$debugh SALTEO $mensaje", 32); + next; + } + log_debug("$debugh canal $canal quehace $quehace dos $dos", 16); + log_debug("$debugh Uniqueid $uniqueid Canalid $canalid", 16); + + if (!defined($canal)) { $canal = ""; } + if (!defined($quehace)) { $quehace = ""; } + if (!defined($dos)) { $dos = ""; } + $canalid =~ s/\s+//g; # Removes whitespace from CHANNEL-ID + $canalid =~ s/(.*)<(.*)>/$1/g; # discards ZOMBIE or MASQ + + if ($canal =~ /^vpb\//i) + { + + # For vpb channels, we fake a session number + $canal = $canalid; + $canal =~ tr/a-z/A-Z/; + $canalid = $canalid .= "-VPB1"; + } + + $server = $uniqueid; + $server =~ s/(.*)-(.*)/$2/g; + + log_debug("$debugh Quehace $quehace", 64); + + my $buttontext = $dos; + + if ($buttontext =~ /\Q[\E/) + { + $buttontext =~ s/.*\Q[\E(.*)\Q]\E.*/$1/g; + } + else + { + $buttontext = ""; + } + + my @canaleja = find_panel_buttons($canal, $canalid, $server); + my $cuantos = @canaleja; + + if ($quehace eq "corto" || $quehace eq "info") + { + + # We collect the last state of the channel on hangup + while (my ($key, $val) = each(%{$datos{$uniqueid}})) + { + $todo .= "$key = $val\n" + if ($key ne "E") && (defined($val)); + log_debug("$debugh \tAgrego $key = $val", 128); + } + $todo .= " "; + $todo = encode_base64($todo); + + delete $datos{$uniqueid}; + log_debug("$debugh erasing datos{$uniqueid}", 128); + } + + foreach $canal (@canaleja) + { + log_debug("$debugh LOOP por canaleja es el turno de $canal", 16); + + if (!defined($buttons{"$server^$canal"})) + { + log_debug("$debugh \tNo tengo botones para $server^$canal, END FUNCTION", 128); + for (keys %buttons) + { + log_debug("$debugh \t\tKey $_", 128); + } + next; + } + + # If its a REGEXP button, we have to ignore all events + # except ocupado*, corto, setlink and unsetlink + if ($canal =~ /^_/) + { + log_debug("$debugh canal $canal es un WILD y quehace vale $quehace", 32); + + if ( $quehace =~ /registr/ + || $quehace =~ /reacha/ + || $quehace =~ /^inf/) + { + log_debug("$debugh IGNORO $quehace porque es un wildcard", 16); + next; + } + + if ( $quehace !~ /^ocupado/ + && $quehace !~ /^corto/ + && $quehace !~ /^state/ + && $quehace !~ /^settext/ + && $quehace !~ /^setlabel/ + && $quehace !~ /^setlink/ + && $quehace !~ /^meetme/ + && $quehace !~ /^ring/ + && $quehace !~ /^settimer/ + && $quehace !~ /^unsetlink/) + { + my ($nada, $elcontexto) = split(/\&/, $canal); + if (!defined($elcontexto)) { $elcontexto = ""; } + if ($elcontexto ne "") + { + $elcontexto = "&" . $elcontexto; + } + my ($canalsolo, $nrotrunk) = split(/=/, $canal); + $canal = $canalsolo . "=1" . $elcontexto; + log_debug("$debugh quehace=$quehace, elijo el 1ero del trunk $canal", 16); + + #next; + } + + # If we have a wildcard button with changelabel + # and change led_color (the 1 after changelabel) + # change it so to not change the led color. + if ($quehace =~ /changelabel1/) + { + log_debug("$debugh el wildcard tiene changelabel1, lo cambio por changelabel0!", 16); + $quehace = "changelabel0"; + } + } + + if ($canal ne "") + { + if ($quehace eq 'corto' || $quehace eq 'info') + { + + my @linked = erase_all_sessions_from_channel($canalid, $canal, $server); + push @linked, $canal; + my $btnorinum = ""; + foreach my $canaleje (@linked) + { + if ($canaleje =~ /\^/) + { + $btnorinum = $buttons{$canaleje}; + } + else + { + $btnorinum = $buttons{"$server^$canaleje"}; + } + log_debug("$debugh call GEN_LINKED 1", 16); + my $listabotones = generate_linked_buttons_list($canaleje, $server); + push @respuestas, "$btnorinum|linked|$listabotones"; + } + + delete $datos{$uniqueid}; + log_debug("$debugh REMOVING datos { $uniqueid }", 16); + } + + if ($quehace eq "setlink") + { + log_debug("$debugh IF quehace = SETLINK", 16); + my ($nada1, $contexto1) = split(/\&/, $canal); + if (!defined($contexto1)) { $contexto1 = ""; } + my $listabotones = ""; + + if (!defined(@{$linkbot{"$server^$canal"}})) + { + push @{$linkbot{"$server^$canal"}}, ""; + pop @{$linkbot{"$server^$canal"}}; + log_debug("$debugh DEFINIENDO linkbot ($server^$canal)", 16); + } + + my ($canal1, $sesion1) = separate_session_from_channel($dos); + my @linkbotones = find_panel_buttons($canal1, $dos, $server); + foreach (@linkbotones) + { + my ($nada2, $contexto2) = split(/\&/, $_); + if (!defined($contexto2)) { $contexto2 = ""; } + if ($contexto1 eq $contexto2) + { + push @{$linkbot{"$server^$canal"}}, $dos; + log_debug("$debugh AGREGO a linkbot{ $server^$canal} el valor $dos", 16); + } + } + + my %seen = (); + my @uniq = + grep { !$seen{$_}++ } @{$linkbot{"$server^$canal"}}; + $linkbot{"$server^$canal"} = \@uniq; + foreach my $valorad (@uniq) + { + log_debug("$debugh linkbot ($server^$canal) = $valorad", 16); + } + my $btnorinum = $buttons{"$server^$canal"}; + log_debug("$debugh llamo a GENERATE_LINKED", 16); + $listabotones = generate_linked_buttons_list($canal, $server); + push @respuestas, "$btnorinum|linked|$listabotones"; + $botonlinked{$btnorinum} = $listabotones; + log_debug("$debugh linkeado con $listabotones", 16); + log_debug("$debugh ENDIF quehace = SETLINK", 16); + } + + if ($quehace eq "unsetlink") + { + log_debug("$debugh IF quehace = UNSETLINK", 16); + my @final = (); + foreach my $msesion (@{$linkbot{"$server^$canal"}}) + { + if ($msesion ne $dos) + { + push @final, $msesion; + } + } + $linkbot{"$server^$canal"} = [@final]; + log_debug("$debugh ENDIF quehace = UNSETLINK", 16); + } + + $interno = $buttons{"$server^$canal"}; + $interno = "" if (!defined($interno)); + + if ($interno eq "") + { + log_debug("$debugh NO HAY INTERNO buttons($server^$canal), ABORTO", 16); + next; + } + log_debug("$debugh EL INTERNO es $interno", 16); + + if (!defined($laststatus{$interno})) + { + $laststatus{$interno} = ""; + } + if (!defined($estadoboton{$interno})) + { + $estadoboton{$interno} = ""; + $statusboton{$interno} = ""; + } + + # Mantains hash of arrays with sessions for each button number + # %sesbot{key}=value where: + # + # key is the button number (anything after '@' is the panel context) + # value is an array containing the sessions Eg: SIP/mary-43xZ + # + # The rename manager event also modifies this hash + # + # There are other hashes to maintain a 'view' of the status: + # + # %estadoboton{key} = shows busy, free or ringing + # %statusboton{key} = text status + # + if ( + $canalid ne "" + && ( $canalid !~ /zombie/i + && $canalid !~ /(.*)-XXXX$/) + ) + { + + if ($quehace eq "corto") + { + + log_debug("$debugh CORTO interno $interno canal $canal", 16); + + delete $botonpark{$interno}; + delete $botonmeetme{$interno}; + delete $botonclid{$interno}; + delete $botonlinked{$interno}; + delete $botontimer{$interno}; + + my $canalbotonreverse = $buttons_reverse{$interno}; + + if ($canal =~ /^_/ && $ren_wildcard == 1) + { + push @respuestas, "$interno|changelabel0|labeloriginal"; + } + + delete $linkbot{$interno}; + delete $linkbot{$canalbotonreverse}; + + if (!defined($sesbot{$interno})) + { + push @{$sesbot{$interno}}, ""; + pop @{$sesbot{$interno}}; + } + my $cuantos = @{$sesbot{$interno}}; + if ($cuantos == 0) + { + log_debug( + "$debugh CORTO y SE DESOCUPO estadoboton($interno) = free, sesbot($interno) esta vacio", + 16 + ); + $cambiaron{$interno} = 1; + $estadoboton{$interno} = "free"; + $statusboton{$interno} = $dos; + } + else + { + log_debug( + "$debugh CORTO y SIGUE OCUPADO estadoboton($interno) = busy, sesbot($interno) tiene algo", + 16 + ); + &print_sesbot(3); + if ($laststatus{$interno} ne "busy|${buttontext}") + { + $cambiaron{$interno} = 1; + log_debug( + "$debugh Y es distinto al ultimo estado $laststatus{$interno} ne $estadoboton{$interno}", + 16 + ); + } + $estadoboton{$interno} = "busy|${buttontext}"; + } + + } + else + { + + # quehace no es "corto" + # + + if (!defined(@{$sesbot{$interno}})) + { + push @{$sesbot{$interno}}, ""; + pop @{$sesbot{$interno}}; + } + + push @{$sesbot{$interno}}, "$canalid"; + log_debug("$debugh AGREGO a sesbot($interno) el valor $canalid", 16); + foreach my $vavi (@{$sesbot{$interno}}) + { + log_debug("$debugh sesbot($interno) tiene $vavi", 16); + } + + if ($canal =~ /^_/ && $quehace =~ /^ring/) + { + log_debug("$debugh TENGO UN WILDCARD ORIGINANDO LLAMADO! $canal $quehace $canalid", + 16); + if ($quehace eq "ring") + { + + # $quehace = "ocupado1"; + } + if ($ren_wildcard == 1) + { + push @respuestas, "$interno|changelabel0|$canalid"; + $botonlabel{$interno} = $canalid; + } + } + + my %seen = (); + my @uniq = + grep { !$seen{$_}++ } @{$sesbot{$interno}}; + $sesbot{$interno} = [@uniq]; + + if ($quehace eq "ringing") + { + if ($laststatus{$interno} ne "ringing|${buttontext}") + { + $cambiaron{$interno} = 1; + } + $estadoboton{$interno} = "ringing|${buttontext}"; + if ($dos =~ m/(.*)?\[(.*)\].*?/) + { + my $clidtext = $2; + $botonclid{$interno} = $clidtext; + } + + } + elsif ($quehace =~ /^ocupado/) + { + if ($laststatus{$interno} ne "busy|${buttontext}") + { + $cambiaron{$interno} = 1; + } + $estadoboton{$interno} = "busy|${buttontext}"; + $statusboton{$interno} = $dos; + } + } + } + else + { + log_debug("$debugh ATENCION canalid es $canalid, NO PROCESAR?", 16); + } + + if ($quehace =~ /changelabel/) + { + + # Mantains state of label and led + my $cambia_el_led = $quehace; + $cambia_el_led =~ s/changelabel//g; + $botonled{$interno} = $cambia_el_led; + $botonlabel{$interno} = $dos; + } + if ($quehace eq "park") + { + $dos =~ m/(.*)\((.*)\)/; + my $texto = $1; + my $timeout = $2; + $timeout = time() + $timeout; + $botonpark{$interno} = "$texto|$timeout"; + } + if ($quehace eq "meetmeuser") + { + $botonmeetme{$interno} = $dos; + } + + if ($quehace =~ /infoqstat/) + { + $botonqueue{$interno} = $dos; + } + elsif ($quehace =~ /info/) + { + my $cola = $quehace; + $cola =~ s/^info//g; + push @{$botonqueuemember{$interno}}, "$cola|$dos"; + } + + if ($quehace eq "settext") + { + $botontext{$interno} = $dos; + push @respuestas, "$interno|settext|$dos"; + } + if ($quehace eq "setalpha") + { + $botonalpha{$interno} = $dos; + push @respuestas, "$interno|setalpha|$dos"; + } + if ($quehace eq "flip") + { + push @respuestas, "$interno|flip|$dos"; + } + if ($quehace eq "setlabel") + { + if ( $dos ne "." + && $dos ne "original" + && $dos ne "labeloriginal") + { + $botonlabel{$interno} = $dos; + push @respuestas, "$interno|setlabel|$dos"; + } + } + if ($quehace eq "voicemail") + { + $botonvoicemail{$interno} = $dos; + } + if ($quehace eq "voicemailcount") + { + $botonvoicemailcount{$interno} = $dos; + } + + # linkbot{key} hash mantains the list of linked channels + # for a button. key is the button number, the value is the + # channel-session, like SIP/jose-AxiD + + if ( ($quehace !~ /^corto/) + && ($quehace !~ /^ocupado/) + && ($quehace !~ /link/)) + { + $cambiaron{$interno} = 1; + } + + if (!defined($sesbot{$interno})) + { + push @{$sesbot{$interno}}, ""; + pop @{$sesbot{$interno}}; + } + + if (@{$sesbot{$interno}} > 0 && $quehace eq 'corto') + { + log_debug("$debugh Still busy...sesbot($interno) is not empty, ignoring hangup", 16); + } + else + { + + # This block is for the voicemail client + if ($quehace =~ "^voicemail") + { + my $canalsincontexto = $canal; + $canalsincontexto =~ s/(.*)&(.*)/$1/g; + push @mensajefinal, "$canalsincontexto\@$canalsincontexto|$quehace|$dos"; + } + + # This block is for the voicemail client, popups + if ($quehace =~ "^ringing") + { + my $canalsincontexto = $canal; + $canalsincontexto =~ s/(.*)&(.*)/$1/g; + my $calleridpop = $dos; + $calleridpop =~ s/(.*)\Q[\E(.*)/$2/g; + $calleridpop =~ s/\]//g; + $calleridpop =~ s/\s+//g; + push @mensajefinal, "$canalsincontexto\@$canalsincontexto|$quehace|$calleridpop"; + } + + my $quehace2 = $quehace; + + if ($quehace2 eq "ring") + { + + #$quehace2 = "ocupado"; + } + + next unless ($quehace2 ne "setlink"); + next unless ($quehace2 ne "unsetlink"); + + if ($quehace2 !~ /isagent/) + { + + # Discard events that we dont want to send + # to flash clients + # "isagent" + push @mensajefinal, "$interno|$quehace2|$dos"; + } + + if ($quehace2 =~ /ocupado/) + { + if ($dos =~ m/(.*)?\[(.*)\].*?/) + { + my $clidtext = $2; + $botonclid{$interno} = $clidtext; + } + + #push @mensajefinal, "$interno|state|busy"; + push @mensajefinal, "$interno|settimer|0\@UP"; + } + if ($quehace2 eq "settimer") + { + $botontimer{$interno} = time() - $dos; + } + if ($quehace eq "ocupado1" || $quehace eq "ocupado9") + { + push @mensajefinal, "$interno|settimer|0\@UP"; + if (!defined($botontimer{$interno})) + { + $botontimer{$interno} = time(); + } + } + if ($quehace2 =~ /^ring$/) + { + push @mensajefinal, "$interno|state|busy"; + + #push @mensajefinal, "$interno|settext|$dos"; + } + if ($quehace2 =~ /corto/) + { + + #push @mensajefinal, "$interno|state|free"; + #push @mensajefinal, "$interno|settext|"; + if ((exists($agents{"$server^$canal"}) || exists($reverse_agents{$canal})) + && $agent_status == 1) + { + push @mensajefinal, "$interno|settimer|0\@IDLE"; + push @mensajefinal, "$interno|settext|Idle"; + if ($laststatus{$interno} =~ /busy/) + { + request_queue_status($socket, $canalid); + } + } + else + { + push @mensajefinal, "$interno|settimer|0\@STOP"; + if (defined($boton_ip{"$canal-XXXX"})) + { + my $valip = $boton_ip{"$canal-XXXX"}; + push @mensajefinal, "$interno|settext|$valip"; + $botontext{$interno} = $valip; + } + else + { + $botontext{$interno} = ""; + } + + if (defined($botonalpha{$interno})) + { + if ($botonalpha{$interno} ne "") + { + push @mensajefinal, "$interno|setalpha|$botonalpha{$interno}"; + } + } + } + } + + if ($quehace eq "registrado") + { + if (defined($botonalpha{$interno})) + { + if ($botonalpha{$interno} ne "") + { + push @mensajefinal, "$interno|setalpha|50"; + } + else + { + push @mensajefinal, "$interno|setalpha|100"; + } + } + } + + if ($quehace eq "registrado" || $quehace eq "noregistrado" || $quehace eq "unreachable") + { + $botonregistrado{$interno} = "$quehace|$dos"; + } + + log_debug("$debugh Agrego mensaje final $interno|$quehace2|$dos", 16); + + #if (defined($mensajefinal) && $interno ne "") + $cuantas = @mensajefinal; + if ($cuantas > 0 && $interno ne "") + { + if (exists $cambiaron{$interno}) + { + + log_debug("$debugh Existe cambiaron($interno) = $cambiaron{$interno}", 16); + + #push(@respuestas, $mensajefinal); + foreach (@mensajefinal) + { + push @respuestas, $_; + } + } + else + { + + log_debug("$debugh No existe cambiaron($interno)", 16); + foreach (@mensajefinal) + { + + # If the last status was not modified, avoid sending info + # push @respuestas, $_; + } + } + if ($todo ne "") + { + my $otromensajefinal = "$interno|info|$todo"; + push(@respuestas, $otromensajefinal); + } + } + } + + $laststatus{$interno} = $estadoboton{$interno}; + } + else + { # endif canal distinto de nada + log_debug("$debugh There is no command defined", 16); + } + } + } + } + } + my %seen = (); + my @uniq = grep { !$seen{$_}++ } @respuestas; + @respuestas = @uniq; + $cuantas = @respuestas; + log_debug("$debugh There are $cuantas commands to send to flash clients", 16); + foreach my $valor (@respuestas) + { + log_debug("$debugh RET: $valor", 32); + } + return @respuestas; +} + +sub nonblock +{ + my $socket = shift; + my $flags; + + $flags = fcntl($socket, F_GETFL, 0) + or die "Can't get flags for socket: $!\n"; + fcntl($socket, F_SETFL, $flags | O_NONBLOCK) + or die "Can't make socket nonblocking: $!\n"; +} + +sub manager_connection +{ + my $host = ""; + my $user = ""; + my $pass = ""; + my $debugh = "** MANAGER CONNECTION"; + my $temphandle = ""; + my $contador = 0; + + foreach my $mhost (@manager_host) + { + if (defined($mhost)) + { + $host = $mhost; + $user = $manager_user[$contador]; + $pass = $manager_secret[$contador]; + + if (defined($manager_conectado[$contador])) + { + if ($manager_conectado[$contador] == 1) + { + $contador++; + next; + } + } + + log_debug("$debugh Connecting to $mhost $contador", 1); + + $p[$contador] = + new IO::Socket::INET->new( + PeerAddr => $manager_host[$contador], + PeerPort => 5038, + Proto => "tcp", + Type => SOCK_STREAM + ); + + if (!$p[$contador]) + { + log_debug("$debugh Couldn't connect to $mhost (Server $contador). Retry in $poll_interval seconds", 1); + $p[$contador] = ""; + $manager_conectado[$contador] = 0; + $contador++; + next; + } + else + { + log_debug("$debugh Connected to $mhost (Server $contador)", 1); + $manager_conectado[$contador] = 1; + $p[$contador]->autoflush(1); + $ip_addy{$p[$contador]} = peerinfo($p[$contador], 1); + } + + $manager_socket{$p[$contador]} = + $manager_host[$contador] . "|" . $manager_user[$contador] . "|" . $manager_secret[$contador]; + my $command = ""; + + if ($auth_md5 == 1) + { + $command = "Action: Challenge\r\n"; + $command .= "AuthType: MD5\r\n\r\n"; + } + else + { + $command = "Action: Login\r\n"; + $command .= "Username: $user\r\n"; + $command .= "Secret: $pass\r\n\r\n"; + } + send_command_to_manager($command, $p[$contador],, 1); + } + $contador++; + } + + # Adds AMI handles into IO::Select + foreach (@p) + { + if (defined($_)) + { + $O->add($_); + } + } + +} + +sub clean_socket +{ + my $socket = shift; + my $debugh = "** CLEAN SOCKET "; + log_debug("$debugh connection lost removing socket $socket", 1); + + $O->remove($socket); + $socket->close; + delete $ip_addy{$socket}; + if (exists($manager_socket{$socket})) + { + delete $manager_queue{$socket}; + + # The closed connections belong to an asterisk manager port + my @partes = split(/\|/, $manager_socket{$socket}); + my @pp = (); + my $counter = 0; + foreach my $cual (@p) + { + if (defined($cual)) + { + if ($cual eq $_) + { + log_debug("$debugh Connection lost to server $counter", 1); + $manager_conectado[$counter] = 0; + delete $autenticado{$_}; + push @pp, ""; + } + else + { + log_debug("$debugh still connected to $ip_addy{$cual}", 16); + push @pp, $cual; + } + } + $counter++; + } + @p = @pp; + } + else + { + + # The closed socket was from a client + log_debug("$debugh flash client connection lost", 1); + delete $client_queue{$socket}; + delete $client_queue_nocrypt{$socket}; + my $cualborrar = $socket; + my @temp = grep(!/\Q$cualborrar\E/, @flash_clients); + @flash_clients = @temp; + delete($keys_socket{$socket}); + &print_clients(); + } +} + +# Checks file_name to find out the directory where the configuration +# files should reside + +$directorio =~ s/(.*)\/(.*)/$1/g; +chdir($directorio); +$directorio = `pwd`; +chop($directorio); + +collect_includes("op_buttons.cfg"); +read_buttons_config(); +read_server_config(); +read_astdb_config(); +genera_config(); + +# Tries to open the listening socket +$m = new IO::Socket::INET(Listen => 1, LocalPort => $listen_port, ReuseAddr => 1, Blocking => 0) + or die "\nCan't listen to port $listen_port\n"; +$O = new IO::Select(); +$O->add($m); + +# Connects to the asterisk boxes +manager_connection(); + +$/ = "\0"; + +# Endless loop +while (1) +{ + my $debugh = "** MAIN"; + + while (@S = $O->can_read(0.001)) + { + foreach (@S) + { + my $handle = $_; + + if ($_ == $m) + { + + # New client connection + my $C = $m->accept; + $ip_addy{$C} = peerinfo($C, 1); + log_debug("$debugh New client connection $ip_addy{$C}", 1); + push(@flash_clients, $C); + $O->add($C); + nonblock($C); + &print_clients(); + } + else + { + + # Its not a new client connection + my %i; + my $R; + $R = sysread($_, $i{$handle}, 1); + if (defined($R) && $R == 0) + { + + # Could not read.. close the socket + log_debug("$debugh closing $ip_addy{$_}", 1); + clean_socket($_); + } + else + { + $bloque_completo{$handle} = "" + if (!defined($bloque_completo{$handle})); + $bloque_completo{$handle} .= $i{$handle}; + + next + if ( $bloque_completo{$_} !~ /\r\n\r\n/ + && $bloque_completo{$_} !~ /\0/); + + # From here we have a complete Event block + # to process + + if (exists($manager_socket{$_})) + { + my @part = split(/\|/, $manager_socket{$_}); + log_debug("$debugh End of block from $part[0]", 16); + } + + $bloque_final = $bloque_completo{$handle}; + $bloque_final =~ s/([^\r])\n/$1\r\n/g; # Reemplaza \n solo por \r\n + $bloque_final =~ s/\r\n\r\n/\r\n/g; + $bloque_completo{$handle} = ""; + + # Add the asterisk server number as a part of the event block + my $que_manager = 0; + foreach my $handle_manager_connected (@p) + { + if ($handle_manager_connected eq $handle) + { + + $bloque_final = $bloque_final . "Server: $que_manager"; + } + $que_manager++; + } + + #################################################### + # This block is just for logging in the event + # to stdout + if ($debug & 1) + { + my @lineas = split("\r\n", $bloque_final); + foreach my $linea (@lineas) + { + if (exists($manager_socket{$handle})) + { + my $linea_formato = sprintf("%-15s <- %s", $ip_addy{$handle}, $linea); + log_debug($linea_formato, 1); + } + } + $global_verbose = 'separator'; + } + ################################################## + + foreach my $C ($O->handles) + { + if ($C == $handle) + { + log_debug("$debugh AST event received...", 16); + + # Asterisk event received + # Read the info and arrange it into blocks + # for processing in 'procesa_bloque' + if ( $bloque_final =~ /Event:/ + || $bloque_final =~ /Message: Mailbox/ + || $bloque_final =~ /Message: Timeout/) + { + log_debug("$debugh There's an 'Event' in the event block", 32); + my @lineas = split(/\r\n/, $bloque_final); + @bloque = (); + my $contador = -1; + foreach my $p (@lineas) + { + my $my_event = ""; + if ($p =~ /Event:/) + { + $contador++; + log_debug("$debugh Event detected contador = $contador", 128); + } + elsif ($p =~ /Message: Mailbox/) + { + $my_event = "MessageWaiting"; # Fake event + $contador++; + log_debug("$debugh Event mailbox detected contador = $contador", 128); + } + my ($atributo, $valor) = split(/: /, $p, 2); + if (defined $atributo && $atributo ne "") + { + if ($my_event ne "") + { + $atributo = "Event"; + $valor = $my_event; + log_debug("$debugh Fake event generated $atributo=$valor", 128); + } + if (length($atributo) >= 1) + { + if ($contador < 0) + { + $contador = 0; + } + $bloque[$contador]{"$atributo"} = $valor; + } + } + } + log_debug("$debugh There are $contador blocks for processing", 128); + @respuestas = (); + log_debug("$debugh Answer block cleared", 32); + @respuestas = digest_event_block(\@bloque, "real", $C); + @masrespuestas = (); + while (@fake_bloque) + { + my @respi = digest_event_block(\@fake_bloque, "fake", $C); + foreach (@respi) + { + push @masrespuestas, $_; + } + } + } + elsif ($bloque_final =~ /--END COMMAND--/) + { + log_debug("$debugh There's an 'END' in the event block", 32); + $todo .= $bloque_final; + process_cli_command($todo); + my $cuantos = @bloque; + log_debug("$debugh There are $cuantos blocks for processing", 128); + @respuestas = digest_event_block(\@bloque, "real", $C); + @masrespuestas = (); + while (@fake_bloque) + { + my @respi = digest_event_block(\@fake_bloque, "fake", $C); + foreach (@respi) + { + push @masrespuestas, $_; + } + } + $todo = ""; + } + elsif ($bloque_final =~ / can read + } # while can read + + foreach my $sacket ($O->can_write(0.001)) + { + if (defined($client_queue{$sacket})) + { + + # Loop through command buffer to send to clients + while (my $comd = shift @{$client_queue{$sacket}}) + { + my $tolog = shift @{$client_queue_nocrypt{$sacket}}; + my $ret = actual_syswrite($sacket, $comd, "isclient", $tolog); + if ($ret == -1) + { + log_debug("Partial syswrite, buffering for $ip_addy{$sacket}", 1); + last; + } + + } + } + if (defined($manager_queue{$sacket})) + { + + # Loop through command buffer to send to managers + while (my $comd = shift @{$manager_queue{$sacket}}) + { + my $cuantos = @{$manager_queue{$sacket}}; + + my $ret = actual_syswrite($sacket, $comd, "ismanager", $comd); + if ($ret == -1) + { + log_debug("Partial syswrite, buffering for $ip_addy{$sacket}", 1); + last; + } + + } + } + } +} # endless loop + +sub actual_syswrite +{ + my ($socket, $encriptadofinal, $whom, $log) = @_; + my $largo = length($encriptadofinal); + my $res = syswrite($socket, $encriptadofinal, $largo); + + if (defined $res && $res > 0) + { + if ($res != $largo) + { + + # Could not write the whole command, buffer + # the rest for later + my $offset = $largo - $res; + $offset = $offset * -1; + my $buf = substr($encriptadofinal, $offset); + unshift(@{$client_queue{$socket}}, $buf); + unshift(@{$client_queue_nocrypt{$socket}}, $log); + log_debug("Partial syswrite, len $res but $largo written", 1); + + my $cuantos = @{$client_queue{$socket}}; + if ($cuantos > 200) + { + &clean_socket($socket); + } + return -1; + } + else + { + + # Write succesfull, log to stdout + $log = substr($log, 0, -1); + if ($debug > 0) + { + if ($whom eq "isclient") + { + my $linea_formato = sprintf("%-15s => %s", $ip_addy{$socket}, $log); + log_debug("$linea_formato", 8); + $global_verbose = "separador"; + } + else + { + $log =~ s/\r//g; + $log =~ s/\n//g; + if ($log ne "") + { + my $linea_formato = sprintf("%-15s -> %s", $ip_addy{$socket}, $log); + log_debug("$linea_formato", 2); + } + else + { + $global_verbose = "separador"; + } + } + } + } + } + elsif ($! == EWOULDBLOCK) + { + + # would block: not an error + # handle blocking, by trying again later + log_debug("Write wouldblock, buffering for $ip_addy{$socket}", 1); + push(@{$client_queue{$socket}}, $encriptadofinal); + push(@{$client_queue_nocrypt{$socket}}, $log); + my $cuantos = @{$client_queue{$socket}}; + if ($cuantos > 200) + { + &clean_socket($socket); + } + } + else + { + log_debug("Write error on $ip_addy{$socket}", 1); + &clean_socket($socket); + } + return 0; +} + +sub process_flash_command +{ + + # This function process a command received from a Flash client + # Including request of transfers, hangups, etc + my $comando = shift; + my $socket = shift; + my $datosflash = ""; + my $accion = ""; + my $password = ""; + my $valor = ""; + my $origin_channel = ""; + my $origin_server = ""; + my $canal_destino = ""; + my $destin_server = ""; + my $contexto = ""; + my $btn_destino; + my $extension_destino; + my $origin_context = ""; + my $canal; + my $nroboton; + my $destino; + my $sesion; + my @partes; + my $ultimo; + my $clid; + my $myclave; + my $md5clave; + my @pedazos; + my $panelcontext; + my $auto_conf_exten; + my $conference_context; + my $bargerooms; + my $found_room; + my $servidor_dial = ""; + my $debugh = "-- PROCESS_FLASH_COMMAND"; + my $calltimeout = 0; + + my $linea_formato = sprintf("%-15s <= %s", $ip_addy{$socket}, $comando); + log_debug("$linea_formato", 4); + + log_debug("$debugh START SUB", 16); + + $comando =~ s//$1/g; # Removes XML markup + ($datosflash, $accion, $password) = split(/\|/, $comando); + chop($password); + + log_debug("$debugh datosflash $datosflash accion $accion password $password", 16); + + if ($accion =~ /\+/) + { + + # The command has a timeout for the call + $accion =~ s/(.*)\+(.*)\+(.*)/$1$3/g; + $calltimeout = $2; + } + + if ($datosflash =~ /_level0\.casilla/) + { + $datosflash =~ s/_level0\.casilla(\d+)/$1/g; + } + if ($datosflash =~ /_level0\.rectangulo/) + { + $datosflash =~ s/_level0\.rectangulo(\d+).*/$1/g; + } + + log_debug("$debugh datosflash before context $datosflash", 128); + + # Appends context if defined because my crappy regexp only extracts digits + # FIXME make a regexp that extract digits and digits@context + if (defined($flash_contexto{$socket})) + { + if ($flash_contexto{$socket} ne "") + { + if ($datosflash =~ /\@/) + { + + # No need to append context + } + else + { + $datosflash .= "\@" . $flash_contexto{$socket}; + } + } + } + log_debug("$debugh datosflash after context $datosflash", 128); + + undef $origin_channel; + + # Flash clients send a "contexto" command on connect indicating + # the panel context they want to receive. We populate a hash with + # sockets/contexts in order to send only the events they want + # And because this is an initial connection, it triggers a status + # request to Asterisk + + if ($accion =~ /^contexto\d+/) + { + + my ($nada, $contextoenviado) = split(/\@/, $datosflash); + + if (defined($contextoenviado)) + { + $flash_contexto{$socket} = $contextoenviado; + } + else + { + $flash_contexto{$socket} = ""; + } + if ($datosflash =~ /^1/) + { + $no_encryption{"$socket"} = 1; + } + else + { + $no_encryption{"$socket"} = 0; + } + + sends_key($socket); + sends_version($socket); + + # send_initial_status(); + first_client_status($socket); + return; + } + if (defined($flash_contexto{$socket})) + { + $panelcontext = $flash_contexto{$socket}; + } + else + { + $panelcontext = ""; + } + if ($panelcontext eq "") { $panelcontext = "GENERAL"; } + + if (defined($config->{$panelcontext}{"conference_context"})) + { + $conference_context = $config->{$panelcontext}{"conference_context"}; + } + else + { + if (defined($config->{"GENERAL"}{"conference_context"})) + { + $conference_context = $config->{"GENERAL"}{"conference_context"}; + } + else + { + $conference_context = ""; + } + } + + if (defined($config->{$panelcontext}{"barge_rooms"})) + { + $bargerooms = $config->{$panelcontext}{"barge_rooms"}; + ($first_room, $last_room) = split(/-/, $bargerooms); + } + else + { + if (defined($config->{"GENERAL"}{"barge_rooms"})) + { + $bargerooms = $config->{"GENERAL"}{"barge_rooms"}; + ($first_room, $last_room) = split(/-/, $bargerooms); + } + else + { + $bargerooms = ""; + } + } + + # We have the origin button number from the drag&drop in the 'datos' + # variable. We need to traverse the %buttons hash in order to extract + # the channel name and the panel context, used to find the destination + # button of the command if any + if ( $accion =~ /^meetmemute/ + || $accion =~ /^meetmeunmute/ + || $accion =~ /^bogus/ + || $accion =~ /^restart/) + { + $origin_channel = "bogus"; + } + else + { + my $datosflash_sincontexto = $datosflash; + $datosflash_sincontexto =~ s/(.*)\@(.*)/$1/g; + + if (is_number($datosflash_sincontexto)) + { + + # If the originator is a number, assume button position + # on fop, search for the channel in the buttons hash + while (($canal, $nroboton) = each(%buttons)) + { + if ($nroboton eq $datosflash) + { + + # A button key with an & is for a context channel + # A button key with an = is for a trunk channel + # This bit of code just cleans the channel name and context + @pedazos = split(/&/, $canal); + $origin_context = $pedazos[1]; + my @pedazos2 = split(/\^/, $pedazos[0]); + $origin_server = $pedazos2[0]; + $origin_channel = $pedazos2[1]; + + $origin_channel =~ s/(.*)[=](.*)/$1/g; + } + + } + } + else + { + + # The origin has letters, assume its a channel name + # with a possible extensions.conf context after '@' + # (We have already removed the '@fop_context') + + if ($datosflash_sincontexto =~ /\@/) + { + $contexto = $datosflash_sincontexto; + $datosflash_sincontexto =~ s/(.*)\@(.*)/$1/g; + $contexto =~ s/(.*)\@(.*)/$2/g; + } + + $origin_channel = $datosflash_sincontexto; + + # If we receive a channel name for the dial command + # we default to server number 1 to send the command + $servidor_dial = "default"; + + } + } + + if ($accion =~ /^restrict/ && defined($origin_channel)) + { + my $contextoaagregar = ""; + if ($panelcontext ne "GENERAL") + { + $contextoaagregar = "&$panelcontext"; + } + $restrict_channel = $origin_channel; + log_debug("$debugh RESTRICT commands to channel $restrict_channel", 32); + my $indice = "0^$restrict_channel$contextoaagregar"; + log_debug("$debugh RESTRICT indice $indice", 32); + my $btn_num = "0"; + if (defined($buttons{"$indice"})) + { + $btn_num = $buttons{"$indice"}; + $btn_num =~ s/(.*)\@(.*)/$1/g; + } + if ($btn_num ne "0") + { + log_debug("$debugh RESTRICT btn_num $btn_num", 32); + my $manda = "$btn_num|restrict|0"; + send_status_to_flash($socket, $manda, 0); + } + else + { + log_debug("$debugh RESTRICT channel not found $indice!", 32); + } + return; + } + + if (defined($origin_channel)) + { + log_debug("$debugh origin_channel = $origin_channel", 64); + + if (defined($config->{$panelcontext}{"security_code"})) + { + $myclave = $config->{$panelcontext}{"security_code"} . $keys_socket{$socket}; + log_debug("$debugh usando key " . $keys_socket{$socket}, 16); + } + else + { + $myclave = ""; + $myclave = $config->{"GENERAL"}{"security_code"} . $keys_socket{$socket}; + log_debug("$debugh usando key " . $keys_socket{$socket}, 16); + } + + if ($myclave ne "") + { + $md5clave = MD5HexDigest($myclave); + } + + if ( ("$password" eq "$md5clave") + || ($accion =~ /^dial/ && $cdial_nosecure == 1)) + { + sends_correct($socket); + log_debug("** The channel selected is $origin_channel and the security code matches", 16); + sends_key($socket); + if ($accion =~ /^restart/) + { + + $comando = "Action: Command\r\n"; + $comando .= "Command: restart when convenient\r\n\r\n"; + log_debug("!! Command received: restart when convenient", 32); + send_command_to_manager($comando, $p[0]); + alarm(10); + return; + } + + if ($accion =~ /-/) + { + + #if action has an "-" the command has clid text to pass + @partes = split(/-/, $accion); + $ultimo = @partes; + $ultimo--; + $btn_destino = $partes[$ultimo]; + $ultimo--; + $clid = $partes[$ultimo]; + if (defined($origin_context)) + { + + if (length($origin_context) > 0) + { + $btn_destino = $btn_destino . "@" . $origin_context; + } + } + } + else + { + + #strips the destination button (number at the end) + $btn_destino = $accion; + $btn_destino =~ s/[A-Za-z- ]//g; + + if (defined($origin_context)) + { + if (length($origin_context) > 0) + { + $btn_destino = $btn_destino . "@" . $origin_context; + } + } + } + if ($btn_destino eq "0") + { + log_debug("$debugh btn_destino es igual a cero!", 32); + } + else + { + + log_debug("$debugh btn_destino = $btn_destino", 32); + + # Now assigns the channel name to destino variable + # traversing the %buttons hash to find the key/channel + while (($canal, $nroboton) = each(%buttons)) + { + log_debug("$debugh compara nroboton $nroboton con btn_destino $btn_destino", 32); + + if ($nroboton eq $btn_destino) + { + $canal =~ s/(.*)=(.*)/$1/g; + $destino = $canal; + last; + } + } + } + + if (defined($destino)) + { + if ($destino ne "0") + { + log_debug("$debugh destino es igual a $destino", 32); + my @pedazos2 = split(/\^/, $destino); + $destin_server = $pedazos2[0]; + $destino = $pedazos2[1]; + log_debug("$debugh El boton de destino es $destino en el server $destin_server", 64); + } + } + + if ($accion =~ /^voicemail/) + { + my $vext = ""; + my $vcontext = ""; + + if (defined($config->{$panelcontext}{"voicemail_extension"})) + { + my $voicemailext = $config->{$panelcontext}{"voicemail_extension"}; + ($vext, $vcontext) = split(/\@/, $voicemailext); + } + else + { + log_debug("There is no voicemail_extension defined in op_server.cfg!", 32); + return; + } + + my $keyext = "$origin_server^$origin_channel"; + if ($contexto ne "") { $keyext .= "\&$contexto"; } + my $vclid = $extension_transfer{$keyext}; + $vclid =~ s/(.*)\@(.*)/$1/g; + + $comando = "Action: Originate\r\n"; + $comando .= "Channel: $origin_channel\r\n"; + $comando .= "Callerid: $vclid <$vclid>\r\n"; + $comando .= "Async: True\r\n"; + $comando .= "Exten: $vext\r\n"; + if (defined($vcontext)) + { + $comando .= "Context: $vcontext\r\n"; + } + $comando .= "Priority: 1\r\n"; + $comando .= "\r\n"; + send_command_to_manager($comando, $p[$button_server{$datosflash}]); + return; + } + + if (is_number($destino)) + { + + # If the selected channel name is only digits, its a + # conference. So treat a conference command as a regular + # transfer or redirect. (We do not want to send into a + # meetme conference another ongoing meetme conference) + my @sesiones_del_canal = extraer_todas_las_sesiones_de_un_canal($origin_channel); + my $cuantos = @sesiones_del_canal; + + if ($accion =~ /^conference/) + { + if ($cuantos == 0) + { + $accion =~ s/conference/originate/g; + } + elsif ($cuantos > 0) + { + $accion =~ s/conference/transferir/g; + } + } + } + + if ($accion eq "cortar") + { + log_debug("$debugh Will try to hangup channel", 16); + my $buton_number = $datosflash; + + foreach (@{$sesbot{$buton_number}}) + { + $comando = "Action: Hangup\r\n"; + $comando .= "Channel: $_\r\n\r\n"; + log_debug("-- Command received: $accion chan $_", 32); + send_command_to_manager($comando, $p[$button_server{$datosflash}]); + + # $comando = "Action: Command\r\n"; + # $comando .= "Command: soft hangup $_\r\n\r\n"; + # send_command_to_manager($comando, $p[$button_server{$datosflash}]); + } + } + elsif ($accion =~ /^meetmemute/) + { + my $conference = $btn_destino; + my $meetmemember = $datosflash; + $conference =~ s/(.*)\@(.*)/$1/g; + $meetmemember =~ s/(.*)\@(.*)/$1/g; + my $boton_con_contexto = $clid; + $boton_con_contexto =~ s/^meetmemute//g; + $comando = "Action: Command\r\n"; + $comando .= "ActionID: meetmemute$boton_con_contexto\r\n"; + $comando .= "Command: meetme mute $conference $meetmemember\r\n\r\n"; + send_command_to_manager($comando, $p[$button_server{$boton_con_contexto}]); + } + elsif ($accion =~ /^meetmeunmute/) + { + my $conference = $btn_destino; + my $meetmemember = $datosflash; + $conference =~ s/(.*)\@(.*)/$1/g; + $meetmemember =~ s/(.*)\@(.*)/$1/g; + my $boton_con_contexto = $clid; + $boton_con_contexto =~ s/^meetmeunmute//g; + $comando = "Action: Command\r\n"; + $comando .= "ActionID: meetmeunmute$boton_con_contexto\r\n"; + $comando .= "Command: meetme unmute $conference $meetmemember\r\n\r\n"; + send_command_to_manager($comando, $p[$button_server{$boton_con_contexto}]); + } + elsif ($accion =~ /^conference/) + { + log_debug("$debugh CONFERENCE extension_transfer($origin_channel)", 32); + my $indice = $origin_server . "^" . $origin_channel; + my $originate = $extension_transfer{$indice}; + + foreach (keys(%buttons)) + { + log_debug("$debugh comparo $buttons{$_} con btn_destino $btn_destino", 64); + if ($buttons{$_} eq $btn_destino) + { + if ($canal =~ /\*/) + { + my @canalarray = @{$sesbot{$btn_destino}}; + my $canalses = $canalarray[0]; + my ($newcanal, $newses) = separate_session_from_channel($canalses); + $canal = $newcanal; + } + $canal =~ s/(.*)=(.*)/$1/g; + log_debug("$debugh coincidencia para btn_destino $btn_destino el canal es $canal", 64); + my @links = extraer_todos_los_enlaces_de_un_canal($canal); + + my @canal_transferir = @{$sesbot{$btn_destino}}; + + my $cuantos = @links; + if ($cuantos <= 0) + { + my @extensiondialed = extracts_exten_from_active_channel($canal); + $comando = "Action: Originate\r\n"; + $comando .= "Channel: $origin_channel\r\n"; + $comando .= "Exten: $extensiondialed[0]\r\n"; + $comando .= "Priority: 1\r\n\r\n"; + } + else + { + + log_debug( + "** $canal_transferir[0] $links[0] will be conferenced together with $origin_channel ($originate)", + 16 + ); + + # Try to find an empty conference + my $empty_room = $first_room; + for (my $at = $first_room ; $at <= $last_room ; $at++) + { + log_debug("room $at = " . $barge_rooms{"$at"}, 128); + if ($barge_rooms{"$at"} == 0) + { + $found_room = 1; + $empty_room = $at; + last; + } + } + + if ($found_room == 1) + { + $comando = "Action: Redirect\r\n"; + $comando .= "Channel: $canal_transferir[0]\r\n"; + $comando .= "ExtraChannel: $links[0]\r\n"; + $comando .= "Exten: $empty_room\r\n"; + $comando .= "ActionID: 1234\r\n"; + $comando .= "Context: $conference_context\r\n"; + $comando .= "Priority: 1\r\n\r\n"; + $auto_conference{$canal_transferir[0]} = $origin_channel; + } + else + { + log_debug("$debugh No hay meetme vacio!", 64); + $comando = ""; + } + } + send_command_to_manager($comando, $p[$button_server{$datosflash}]); + last; + } + } + } + elsif ($accion =~ /^ctransferir/) + { + $comando = "Action: Command\r\n"; + $comando .= "Command: database put clid $destino "; + $comando .= "\"$clid\"\r\n\r\n"; + send_command_to_manager($comando, $p[$button_server{$datosflash}]); + + $canal_destino = retrieve_extension($btn_destino); + + if ($origin_channel =~ /\*/) + { + my @canalarray = @{$sesbot{$datosflash}}; + my $canalses = $canalarray[0]; + my ($newcanal, $newses) = separate_session_from_channel($canalses); + $origin_channel = $newcanal; + } + + if ($canal_destino ne "-1") + { + if ($canal_destino =~ /\@/) + { + @pedazos = split(/\@/, $canal_destino); + $canal_destino = $pedazos[0]; + $contexto = $pedazos[1]; + } + my @cuales_transferir = (); + if ($reverse_transfer == 1) + { + + # Transfer the session from the *other* button + @cuales_transferir = extraer_todos_los_enlaces_de_un_canal($origin_channel); + } + else + { + + # Transfer the session from the same button + # @cuales_transferir = + # extraer_todas_las_sesiones_de_un_canal( + # $origin_channel); + + @cuales_transferir = @{$sesbot{$datosflash}}; + + } + foreach my $valor (@cuales_transferir) + { + log_debug("** Will try to transfer $valor to extension number $canal_destino!", 16); + $comando = "Action: Redirect\r\n"; + $comando .= "Channel: $valor\r\n"; + $comando .= "Exten: $canal_destino\r\n"; + if ($contexto ne "") + { + $comando .= "Context: $contexto\r\n"; + } + $comando .= "Priority: 1\r\n\r\n"; + send_command_to_manager($comando, $p[$button_server{$datosflash}]); + + if ($calltimeout > 0) + { + $comando = "Action: AbsoluteTimeout\r\n"; + $comando .= "Channel: $valor\r\n"; + $comando .= "Timeout: $calltimeout\r\n"; + $comando .= "ActionID: timeout|$valor|$calltimeout\r\n"; + $comando .= "\r\n"; + send_command_to_manager($comando, $p[$button_server{$datosflash}]); + } + + } + } + else + { + log_debug("** Untransferable destination!", 16); + } + } + elsif ($accion =~ /^transferir/) + { + + $canal_destino = retrieve_extension($btn_destino); + + if ($origin_channel =~ /\*/) + { + my @canalarray = @{$sesbot{$datosflash}}; + my $canalses = $canalarray[0]; + my ($newcanal, $newses) = separate_session_from_channel($canalses); + $origin_channel = $newcanal; + } + + if ($canal_destino ne "-1") + { + if ($canal_destino =~ /\@/) + { + @pedazos = split(/\@/, $canal_destino); + $canal_destino = $pedazos[0]; + $contexto = $pedazos[1]; + } + + my @cuales_transferir = (); + if ($reverse_transfer == 1) + { + log_debug("$debugh REVERSE TRANSFER", 16); + + # Transfer the session from the *other* button + @cuales_transferir = extraer_todos_los_enlaces_de_un_canal($origin_channel); + } + else + { + log_debug("$debugh NORMAL TRANSFER", 16); + + # Transfer the session from the same button + #@cuales_transferir = + # extraer_todas_las_sesiones_de_un_canal( + # $origin_channel); + @cuales_transferir = @{$sesbot{$datosflash}}; + + } + + foreach my $valor (@cuales_transferir) + { + log_debug("$debugh Will try to transfer $valor to extension number $canal_destino!", 16); + $comando = "Action: Redirect\r\n"; + $comando .= "Channel: $valor\r\n"; + $comando .= "Exten: $canal_destino\r\n"; + if ($contexto ne "") + { + $comando .= "Context: $contexto\r\n"; + } + $comando .= "Priority: 1\r\n\r\n"; + send_command_to_manager($comando, $p[$button_server{$datosflash}]); + + if ($calltimeout > 0) + { + $comando = "Action: AbsoluteTimeout\r\n"; + $comando .= "Channel: $valor\r\n"; + $comando .= "Timeout: $calltimeout\r\n"; + $comando .= "ActionID: timeout|$valor|$calltimeout\r\n"; + $comando .= "\r\n"; + send_command_to_manager($comando, $p[$button_server{$datosflash}]); + } + + } + } + else + { + log_debug("** Untransferable destination! ($origin_channel)", 16); + } + } + elsif ($accion =~ /^coriginate/) + { + if ($origin_channel =~ /\*/) + { + log_debug("Cannot originate from wildcard buttons ($origin_channel)", 16); + return; + } + $comando = "Action: Command\r\n"; + $comando .= "Command: database put clid $destino "; + $comando .= "\"$clid\"\r\n\r\n"; + send_command_to_manager($comando, $p[$button_server{$datosflash}]); + + $extension_destino = retrieve_extension($btn_destino); + if ($extension_destino =~ /\@/) + { + @pedazos = split(/\@/, $extension_destino); + $extension_destino = $pedazos[0]; + $contexto = $pedazos[1]; + } + + log_debug("$debugh Originate from $origin_channel to extension $extension_destino!", 16); + + if ($origin_channel =~ /^IAX2\[/) + { + $origin_channel =~ s/^IAX2\[(.*)\]/IAX2\/$1/g; + } + $comando = "Action: Originate\r\n"; + $comando .= "Async: True\r\n"; + $comando .= "Channel: $origin_channel\r\n"; + $comando .= "Exten: $extension_destino\r\n"; + + if ($contexto ne "") + { + $comando .= "Context: $contexto\r\n"; + } + $comando .= "Priority: 1\r\n"; + $comando .= "\r\n"; + send_command_to_manager($comando, $p[$button_server{$datosflash}]); + } + elsif ($accion =~ /^originate/) + { + if ($origin_channel =~ /\*/) + { + log_debug("** Cannot originate from wildcard buttons ($origin_channel)", 16); + return; + } + $extension_destino = retrieve_extension($btn_destino); + if ($extension_destino =~ /\@/) + { + @pedazos = split(/\@/, $extension_destino); + $extension_destino = $pedazos[0]; + $contexto = $pedazos[1]; + } + + log_debug("$debugh Originate from $origin_channel to extension $extension_destino!", 16); + my $keyext = "$origin_server^$origin_channel"; + + if ($panelcontext ne "") { $keyext .= "\&$panelcontext"; } + $clid = $textos{"$datosflash"} . " <" . $extension_transfer{$keyext} . ">"; + + if ($origin_channel =~ /^IAX2\[/) + { + $origin_channel =~ s/^IAX2\[(.*)\]/IAX2\/$1/g; + } + $comando = "Action: Originate\r\n"; + $comando .= "Channel: $origin_channel\r\n"; + $comando .= "Async: True\r\n"; + $comando .= "Callerid: $clid\r\n"; + $comando .= "Exten: $extension_destino\r\n"; + + if ($contexto ne "") + { + $comando .= "Context: $contexto\r\n"; + } + $comando .= "Priority: 1\r\n"; + $comando .= "\r\n"; + send_command_to_manager($comando, $p[$button_server{$datosflash}]); + } + elsif ($accion =~ /^dial/) + { + if ($servidor_dial eq "default") + { + $servidor_dial = $p[0]; + } + else + { + $servidor_dial = $p[$button_server{$datosflash}]; + } + my $numero_a_discar = $accion; + $numero_a_discar =~ s/^dial//g; + $comando = "Action: Originate\r\n"; + $comando .= "Channel: $origin_channel\r\n"; + $comando .= "Async: True\r\n"; + $comando .= "Exten: $numero_a_discar\r\n"; + if ($contexto ne "") + { + $comando .= "Context: $contexto\r\n"; + } + $comando .= "Priority: 1\r\n"; + $comando .= "\r\n"; + send_command_to_manager($comando, $servidor_dial); + } + } + else + { + log_debug("$debugh Password mismatch -$password-$md5clave-!", 1); + sends_key($socket); + sends_incorrect($socket); + } + } + else + { + log_debug("$debugh There is no channel selected ?", 16); + print("$debugh There is no channel selected ?\n"); + } +} + +sub retrieve_extension +{ + my $param = shift; + my $canal = ""; + my $canal_destino = ""; + my $debugh = "** RETRIEVE_EXTEN"; + + my $param_sin_contexto = $param; + $param_sin_contexto =~ s/(.*)(\@.*)/$1/g; + + log_debug("$debugh param $param param_sin_con $param_sin_contexto", 32); + + if (is_number($param_sin_contexto)) + { + log_debug("$debugh I guess its a button number", 32); + + # If the parameter is a number, assume button number + foreach (keys(%buttons)) + { + my $linealog = sprintf("%-20s %-10s", $_, $buttons{$_}); + + # log_debug("$debugh $linealog",64); + + if ($buttons{$_} eq $param) + { + log_debug("$debugh coincide con $param", 64); + $canal = $_; + $canal =~ s/(.*)=(.*)/$1/g; + log_debug("$debugh canal $canal", 64); + $canal_destino = $extension_transfer{"$canal"}; + last; + } + } + } + else + { + log_debug("$debugh I guess its a channel name", 32); + + # If its not a number, asume channel name (technology/name) + foreach (keys(%buttons)) + { + my $linealog = sprintf("%-20s %-10s", $_, $buttons{$_}); + + # log_debug("$debugh $linealog",64); + if ($_ eq $param) + { + log_debug("$debugh coincide con $param", 64); + $canal = $_; + $canal =~ s/(.*)=(.*)/$1/g; + $canal_destino = $extension_transfer{"$canal"}; + last; + } + } + } + log_debug("$debugh La extension para $param es $canal_destino", 32); + return $canal_destino; +} + +sub request_astdb_status +{ + + for my $key (keys %astdbcommands) + { + my $nro_servidor = 0; + foreach my $socket (@p) + { + if (defined($socket) && $socket ne "") + { + for (keys %buttons) + { + my $canal = $_; + $canal =~ m/(\d+)\^([^&]*)(.*)/g; + my $servidor = $1; + my $canalito = $2; + if ($canalito !~ m/^_/ && $nro_servidor == $servidor && $canalito !~ m/=/) + { + my $comando = "Action: Command\r\n"; + $comando .= "ActionID: astdb-$key-$canalito\r\n"; + $comando .= "Command: database get $key $canalito\r\n\r\n"; + if (defined($autenticado{$socket})) + { + if ($autenticado{$socket} == 1) + { + send_command_to_manager($comando, $socket); + } + } + } + } + } + $nro_servidor++; + } + } +} + +sub request_queue_status +{ + my $socket = shift; + my $canalid = shift; + my $member = ""; + my $nada = ""; + + if (defined($canalid)) + { + ($member, $nada) = separate_session_from_channel($canalid); + } + + my @todos = (); + + if ($socket eq "all") + { + @todos = @p; + } + else + { + push @todos, $socket; + } + + foreach my $socket2 (@todos) + { + if (defined($socket2) && $socket2 ne "") + { + send_command_to_manager("Action: Command\r\nActionId: agents\r\nCommand: show agents\r\n\r\n", $socket2); + if (defined($member)) + { + my @agentes = (); + push @agentes, $member; + if (exists($reverse_agents{$member})) + { + push @agentes, "Agent/" . $reverse_agents{$member}; + } + foreach my $cual (@agentes) + { + send_command_to_manager("Action: QueueStatus\r\nMember: $cual\r\n\r\n", $socket2); + + } + } + else + { + send_command_to_manager("Action: QueueStatus\r\n\r\n", $socket2); + } + } + } +} + +sub first_client_status +{ + + # This functions traverses all FOP internal hashes and send the proper + # commands to the flash client to reflect the status of each button. + + my $socket = shift; + my $interno = ""; + if (keys(%estadoboton)) + { + for $interno (keys %botonled) + { + if ($botonled{$interno} == 1) + { + send_status_to_flash($socket, "$interno|changelabel1|$botonlabel{$interno}", 0); + } + } + for $interno (keys %botonvoicemail) + { + if ($botonvoicemail{$interno} == 1) + { + send_status_to_flash($socket, "$interno|voicemail|1", 0); + } + } + for $interno (keys %botonvoicemailcount) + { + send_status_to_flash($socket, "$interno|voicemailcount|$botonvoicemailcount{$interno}", 0); + } + for $interno (keys %botonalpha) + { + if ($botonalpha{$interno} ne "") + { + send_status_to_flash($socket, "$interno|setalpha|$botonalpha{$interno}", 0); + } + } + for $interno (keys %botontext) + { + if ($botontext{$interno} ne "") + { + send_status_to_flash($socket, "$interno|settext|$botontext{$interno}", 0); + } + } + for $interno (keys %botonqueue) + { + send_status_to_flash($socket, "$interno|infoqstat|$botonqueue{$interno}", 0); + } + if (keys(%botonqueuemember)) + { + for $interno (keys %botonqueuemember) + { + if (defined(@{$botonqueuemember{$interno}})) + { + my %temphash = (); + foreach my $val (@{$botonqueuemember{$interno}}) + { + my @datos = split(/\|/, $val); + $temphash{$datos[0]} = $datos[1]; + + # send_status_to_flash($socket, "$interno|info$datos[0]|$datos[1]", 0); + } + while (my ($key, $val) = each(%temphash)) + { + send_status_to_flash($socket, "$interno|info$key|$val", 0); + } + } + } + } + if (keys(%botonpark)) + { + for $interno (keys %botonpark) + { + $botonpark{$interno} =~ m/(.*)\|(.*)/; + my $texto = $1; + my $timeout = $2; + my $diftime = $timeout - time(); + if ($diftime > 0) + { + send_status_to_flash($socket, "$interno|park|$texto($diftime)", 0); + } + } + } + for $interno (keys %estadoboton) + { + if ($estadoboton{$interno} !~ /^free/) + { + if ($estadoboton{$interno} =~ /^busy/) + { + send_status_to_flash($socket, "$interno|state|busy", 0); + if (defined($botonlabel{$interno})) + { + send_status_to_flash($socket, "$interno|changelabel0|$botonlabel{$interno}", 0); + } + } + elsif ($estadoboton{$interno} =~ /ringi/) + { + send_status_to_flash($socket, "$interno|state|ringing", 0); + } + if (defined($botonclid{$interno})) + { + if ($botonclid{$interno} ne "") + { + send_status_to_flash($socket, "$interno|settext|$botonclid{$interno}", 0); + } + } + } + } + if (keys(%botonlinked)) + { + for $interno (keys %botonlinked) + { + if ($botonlinked{$interno} ne "") + { + send_status_to_flash($socket, "$interno|linked|$botonlinked{$interno}", 0); + } + } + } + if (keys(%botonmeetme)) + { + for $interno (keys %botonmeetme) + { + if ($botonmeetme{$interno} ne "") + { + send_status_to_flash($socket, "$interno|meetmeuser|$botonmeetme{$interno}", 0); + } + } + } + if (keys(%botontimer)) + { + for $interno (keys %botontimer) + { + if ($botontimer{$interno} ne "") + { + my $diftime = time() - $botontimer{$interno}; + send_status_to_flash($socket, "$interno|settimer|$diftime\@UP", 0); + send_status_to_flash($socket, "$interno|state|busy", 0); + } + } + } + if (keys(%botonlabel)) + { + for $interno (keys %botonlabel) + { + if ( $botonlabel{$interno} ne "" + && $botonlabel{$interno} ne "." + && $botonlabel{$interno} ne "original" + && $botonlabel{$interno} ne "labeloriginal") + { + send_status_to_flash($socket, "$interno|setlabel|$botonlabel{$interno}", 0); + } + } + } + if (keys(%botonregistrado)) + { + for $interno (keys %botonregistrado) + { + if ($botonregistrado{$interno} ne "") + { + my ($quehace, $dos) = split(/\|/, $botonregistrado{$interno}); + send_status_to_flash($socket, "$interno|$quehace|$dos", 0); + } + } + } + } +} + +sub send_initial_status +{ + %datos = (); + my $nro_servidor = 0; + my $debugh = "** SEND INITIAL STATUS"; + my $cual = shift; + my @socket_manager; + + if (defined($cual)) + { + push @socket_manager, $cual; + } + else + { + @socket_manager = @p; + } + + log_debug("$debugh START SUB", 16); + + request_astdb_status(); + + foreach my $socket (@socket_manager) + { + + if (defined($socket) && $socket ne "") + { + my @pedazos = split(/\|/, $manager_socket{$socket}); + if ($pedazos[0] eq $ip_addy{$socket}) + { + my $contador = 0; + foreach my $valor (@manager_host) + { + if ($valor eq $pedazos[0]) + { + $nro_servidor = $contador; + } + $contador++; + } + } + + send_command_to_manager("Action: Status\r\n\r\n", $socket); + + send_command_to_manager("Action: ZapShowChannels\r\n\r\n", $socket); + + send_command_to_manager("Action: Command\r\nActionID: parkedcalls\r\nCommand: show parkedcalls\r\n\r\n", + $socket); + + # Send commands to check the mailbox status for each mailbox defined + while (my ($key, $val) = each(%mailbox)) + { + my @pedacitos = split(/\^/, $key); + my $servidormbox = $pedacitos[0]; + if ("$servidormbox" eq "$nro_servidor") + { + log_debug("$debugh mailbox $ip_addy{$socket} $key $val", 32); + send_command_to_manager("Action: MailboxStatus\r\nMailbox: $val\r\n\r\n", $socket); + } + } + my @all_meetme_rooms = (); + + # generates an array with all meetme rooms to check on init + for my $valor (keys %barge_rooms) + { + push(@all_meetme_rooms, $valor); + } + + for my $key (keys %buttons) + { + if ($key =~ /^\d+\^\d+$/) + { + push(@all_meetme_rooms, $key); + } + } + + my %count = (); + my @unique_meetme_rooms = + grep { ++$count{$_} < 2 } @all_meetme_rooms; + + foreach my $valor (@unique_meetme_rooms) + { + my $servidormeetme = 0; + my $meetmeroom = ""; + + if ($valor =~ /\^/) + { + my @pedacitos = split(/\^/, $valor); + $servidormeetme = $pedacitos[0]; + $meetmeroom = $pedacitos[1]; + } + else + { + + # If there is no server defined (its a barge_room) + # we will query all servers - quick hack FIX IT or + # try to figure out a way to have barge-rooms separated + # in panel_contexts (as it is now) and also asterisk + # servers. + $servidormeetme = $nro_servidor; + $meetmeroom = $valor; + } + + if ("$servidormeetme" eq "$nro_servidor") + { + send_command_to_manager( + "Action: Command\r\nActionID: meetme_$meetmeroom\r\nCommand: meetme list $meetmeroom\r\n\r\n", + $socket); + } + } + request_queue_status($socket, ""); + } + } + alarm(2); +} + +sub process_cli_command +{ + + # This subroutine process the output for a manager "Command" + # sent, as 'sip show peers' + + my $texto = shift; + @bloque = (); + my @lineas = split("\r\n", $texto); + my $contador = 0; + my $interno = ""; + my $estado = ""; + my $nada = ""; + my $conference = 0; + my $usernum = 0; + my $canal = ""; + my $sesion = ""; + my $debugh = "** PROCESS_CLI"; + my $server = 0; + + log_debug("$debugh START SUB", 16); + + foreach my $valor (@lineas) + { + if ($valor =~ /^Server/) + { + $server = $valor; + $server =~ s/Server: (.*)/$1/g; + } + } + + if ($texto =~ /ActionID: meetme_/) + { + + # Its a meetme status report + foreach my $valor (@lineas) + { + $valor =~ s/\s+/ /g; + my ($key, $value) = split(/: /, $valor, 2); + + if (defined($key)) + { + + if ($key eq "ActionID") + { + $value =~ s/meetme_(\d+)$/$1/g; + $conference = $value; + } + if ($key eq "User #") + { + my @partes = split(/Channel:/, $value); + $usernum = $partes[0]; + $usernum =~ s/\s+//g; + $canal = $partes[1]; + $canal =~ s/^\s+//g; + $canal =~ s/(.*)\((.*)/$1/g; + $bloque[$contador]{"Event"} = "MeetmeJoin"; + $bloque[$contador]{"Meetme"} = $conference; + $bloque[$contador]{"Count"} = $contador; + $bloque[$contador]{"Channel"} = $canal; + $bloque[$contador]{"Usernum"} = $usernum; + $bloque[$contador]{"Fake"} = "hola"; + $bloque[$contador]{"Server"} = "$server"; + $bloque[$contador]{"Uniqueid"} = "YYYY"; + $contador++; + } + } + } + my $cuentamenos = $contador - 1; + if ($cuentamenos >= 0) + { + $bloque[$cuentamenos]{"Total"} = $contador; + } + } + elsif ($texto =~ "ActionID: agents") + { + my $agent_number; + my $agent_state; + my $agent_name; + + # Show Agents CLI command, generates fake events + + foreach (@lineas) + { + $_ =~ s/\s+/ /g; + /(\d+) \((.*)\) (.*) (\(.*\))/; + if (defined($1)) + { + $agent_number = $1; + $agent_name = $2; + $agent_state = $3; + $agents_name{"$server^$agent_number"} = $agent_name; + } + + if (defined($3)) + { + if ($agent_state =~ /available at/) + { + + # Agent callback login + $agent_state =~ s/.*'(.*)'.*/$1/g; + $bloque[$contador]{"Event"} = "Agentcallbacklogin"; + $bloque[$contador]{"Loginchan"} = $agent_state; + $bloque[$contador]{"Agent"} = $agent_number; + $bloque[$contador]{"Server"} = "$server"; + $contador++; + } + + if ($agent_state =~ /logged in on/) + { + + # Agent login + $agent_state =~ s/\s+/ /g; + $agent_state =~ s/logged in on //g; + $agent_state =~ s/([^ ]*).*/$1/g; + + $bloque[$contador]{"Event"} = "Agentlogin"; + $bloque[$contador]{"Channel"} = $agent_state; + $bloque[$contador]{"Agent"} = $agent_number; + $bloque[$contador]{"Server"} = "$server"; + $contador++; + } + if ($agent_state =~ /not logged in/) + { + $bloque[$contador]{"Event"} = "Agentlogoff"; + $bloque[$contador]{"Agent"} = "$agent_number"; + $bloque[$contador]{"Server"} = "$server"; + $contador++; + } + } + } + } + elsif ($texto =~ /ActionID: astdb-/) + { + my $astdbk = ""; + my $canalk = ""; + my $valork = ""; + foreach (@lineas) + { + if (/^ActionID/) + { + $_ =~ m/ActionID: astdb-([^-]*)-(.*)/; + $astdbk = $1; + $canalk = $2; + } + if (/^Value:/) + { + $valork = $_; + $valork =~ s/Value: //g; + } + } + if ($valork ne "") + { + $bloque[$contador]{"Event"} = "ASTDB"; + $bloque[$contador]{"Channel"} = $canalk; + $bloque[$contador]{"Value"} = $valork; + $bloque[$contador]{"Family"} = $astdbk; + $contador++; + } + + } + elsif ($texto =~ /ActionID: meetmeun/ || $texto =~ /ActionID: meetmemute/) + { + my $quecomando = ""; + my $quecanal = ""; + foreach my $valor (@lineas) + { + if ($valor =~ /^ActionID:/) + { + $quecomando = $valor; + $quecomando =~ s/^ActionID: //g; + if ($quecomando =~ /meetmemute/) + { + $quecanal = $quecomando; + $quecanal =~ s/meetmemute//g; + $quecomando = "meetmemute"; + } + else + { + $quecanal = $quecomando; + $quecanal =~ s/meetmeunmute//g; + $quecomando = "meetmeunmute"; + } + } + } + my $canal_a_mutear = $buttons_reverse{$quecanal}; + my @pedazos = split /\^/, $canal_a_mutear; + $canal_a_mutear = $pedazos[1]; + $canal_a_mutear =~ s/(.*)\&(.*)/$1/g; + $bloque[$contador]{"Event"} = $quecomando; + $bloque[$contador]{"Channel"} = $canal_a_mutear . "-XXXX"; + $bloque[$contador]{"Server"} = "$server"; + $contador++; + } + elsif ($texto =~ "ActionID: iaxpeers") + { + my $info = 0; + my $statPos = 74; + foreach my $valor (@lineas) + { + log_debug("$debugh Line iaxpeers: $valor", 32); + if ($valor =~ /^Name\/User/i) + { + $statPos = index($valor, "Status"); + $info = 1; + next; + } + last if $valor =~ /^--End/i; + next unless $info; + next unless (length($valor) > $statPos); + my $estado = substr($valor, $statPos); + $valor =~ s/\s+/ /g; + my @parametros = split(" ", $valor); + my $interno = $parametros[0]; + + if ($interno =~ /\//) + { + my @partecitas = split(/\//, $interno); + $interno = $partecitas[0]; + } + my $dirip = $parametros[1]; + + if (defined($estado) && $estado ne "") + { + $interno = "IAX2/" . $interno . "-XXXX"; + log_debug("$debugh State: $estado Extension: $interno", 16); + $bloque[$contador]{"Event"} = "Regstatus"; + $bloque[$contador]{"Channel"} = $interno; + $bloque[$contador]{"State"} = $estado; + $bloque[$contador]{"IP"} = $dirip; + $bloque[$contador]{"Server"} = "$server"; + $contador++; + } + } + } + elsif ($texto =~ "ActionID: parkedcalls") + { + my $info = 0; + foreach my $valor (@lineas) + { + log_debug("$debugh Line parkedcalls: $valor", 32); + if ($valor =~ /Timeout/) + { + $info = 1; + next; + } + last if $valor =~ /^--End/i; + last if $valor =~ /^\d+ parked/i; + next unless $info; + $valor =~ s/\s+/ /g; + my @parametros = split(" ", $valor); + my $timeout = $parametros[6]; + $timeout =~ s/(\d+)s/$1/; + + $bloque[$contador]{"Event"} = "ParkedCall"; + $bloque[$contador]{"Channel"} = $parametros[1]; + $bloque[$contador]{"Exten"} = $parametros[0]; + $bloque[$contador]{"Timeout"} = $timeout; + $bloque[$contador]{"Server"} = "$server"; + $contador++; + + } + } + else + { + my $info = 0; + my $statPos = 74; + + # Its a sip show peers report + foreach my $valor (@lineas) + { + if ($valor =~ /^Name\/User/i) + { + $statPos = index($valor, "Status"); + $info = 1; + next; + } + last if $valor =~ /^--End/i; + next unless $info; + next unless (length($valor) > $statPos); + log_debug("$debugh Line: $valor", 32); + + if (length($valor) < $statPos) + { + log_debug("$debugh SIP PEER line $valor does not match $statPos!", 16); + next; + } + + my $estado = substr($valor, $statPos); + $valor =~ s/\s+/ /g; + if ($valor eq "") { next; } + my @parametros = split(" ", $valor); + my $interno = $parametros[0]; + my $dirip = $parametros[1]; + $interno =~ s/(.*)\/(.*)/$1/g; + + if (defined($interno)) + { + + if ($interno =~ /(.*)\/(.*)/) + { + if ($1 eq $2) + { + $interno = $1 . "-XXXX"; + } + else + { + $interno .= "-XXXX"; + } + } + } + if (defined($estado) + && $estado ne "") # If set, is the status of 'sip show peers' + { + $interno = "SIP/" . $interno; + log_debug("$debugh State: $estado Extension: $interno", 16); + $bloque[$contador]{"Event"} = "Regstatus"; + $bloque[$contador]{"Channel"} = $interno . "-XXXX"; + $bloque[$contador]{"State"} = $estado; + $bloque[$contador]{"IP"} = $dirip; + $bloque[$contador]{"Server"} = "$server"; + $contador++; + } + } + } +} + +sub find_uniqueid +{ + + # returns the uniqueid of a given channel + my $canal = shift; + my $server = shift; + my $uniqid = ""; + my $match = 0; + + if (keys(%datos)) + { + for (keys %datos) + { + $match = 0; + while (my ($key, $val) = each(%{$datos{$_}})) + { + if ($key eq "Channel" && $val eq $canal) + { + $match++; + } + if ($key eq "Server" && $val eq $server) + { + $match++; + } + } + if ($match > 1) + { + $uniqid = $_; + last; + } + } + } + + return $uniqid; +} + +sub check_if_extension_is_busy +{ + my $interno = shift; + my $return = "no"; + my $quehay = ""; + my $canal = ""; + my $sesion = ""; + my $comando = ""; + my $debugh = "** CHECK_EXT_BUSY"; + + for $quehay (keys %datos) + { + while (my ($key, $val) = each(%{$datos{$quehay}})) + { + if ($key eq "Channel") + { + if ($val =~ /ZOMBIE/) + { + if ($kill_zombies == 1) + { + + # If it finds a Zombie, try to hang it up + $comando = "Action: Hangup\r\n"; + $comando .= "Channel: $val\r\n\r\n"; + send_command_to_manager($comando); + log_debug("$debugh ZOMBIE!! I will try to kill it!! $val", 16); + } + } + else + { + $val =~ s/(.*)[-\/](.*)/$1\t$2/g; + ($canal, $sesion) = split(/\t/, $val); + $canal =~ tr/a-z/A-Z/; + if ($canal eq $interno) + { + $return = "si"; + log_debug("$debugh Extension still busy $canal $interno", 16); + } + else + { + log_debug("$debugh $canal <> $interno", 32); + } + } + } + } + } + return $return; +} + +sub log_debug +{ + my $texto = shift; + my $nivel = shift; + my $verbose = "0"; + + if (!defined($nivel)) { $nivel = 1; } + + if ($debug & $nivel) + { + $texto =~ s/\0//g; + if ($texto !~ m/^\d+\.\d+\.\d+\.\d+/) + { + $verbose = $texto; + $verbose =~ s/^\*\* ([^\s]*).*/$1/g; + } + else + { + my $parte = $texto; + $parte =~ s/(\d+\.\d+\.\d+\.\d+)\s+(.*)/$1/g; + $verbose = $parte; + } + if ($debug == -1) + { + + # Debug log Cache + $debug_cache .= "$texto\n"; + } + else + { + if ($debug_cache ne "") + { + print $debug_cache. "\n"; + $debug_cache = ""; + } + if ($verbose ne $global_verbose) + { + print "\n"; + } + $global_verbose = $verbose; + print "$texto\n"; + } + } +} + +sub alarma_al_minuto +{ + my $nro_servidor = 0; + my $debugh = "** ALARM "; + manager_connection(); + + # %cache_hit = (); # Clears button cache + foreach (@p) + { + if (defined($_) && $_ ne "") + { + log_debug("Enviando status a " . $ip_addy{$_}, 16); + my @pedazos = split(/\|/, $manager_socket{$_}); + + if ($pedazos[0] eq $ip_addy{$_}) + { + my $contador = 0; + foreach my $valor (@manager_host) + { + if ($valor eq $pedazos[0]) + { + $nro_servidor = $contador; + } + $contador++; + } + } + + my $comando = "Action: Command\r\n"; + $comando .= "Command: sip show peers\r\n\r\n"; + send_command_to_manager($comando, $_); + + $comando = "Action: Command\r\n"; + $comando .= "ActionID: iaxpeers\r\n"; + $comando .= "Command: iax2 show peers\r\n\r\n"; + send_command_to_manager($comando, $_); + + if ($poll_voicemail == 1) + { + + # Send commands to check the mailbox status for each mailbox defined + while (my ($key, $val) = each(%mailbox)) + { + my @pedacitos = split(/\^/, $key); + my $servidormbox = $pedacitos[0]; + if ("$servidormbox" eq "$nro_servidor") + { + log_debug("$debugh mailbox $ip_addy{$_} $key $val", 32); + send_command_to_manager("Action: MailboxStatus\r\nMailbox: $val\r\n\r\n", $_); + } + } + } + } + } + alarm($poll_interval); +} + +sub send_status_to_flash +{ + my $socket = shift; + my $status = shift; + my $nocrypt = shift; + my $encriptado = $status; + my $but_no = 0; + my $debugh = "** SEND_STATUS_TO_FLASH "; + my $contexto = ""; + my $cmd = ""; + my $cmd_crypt = ""; + my $data = ""; + my $data_crypt = ""; + my $noencriptado = ""; + + if (!defined($socket)) + { + log_debug("$debugh socket $socket not open!!!", 64); + } + + if ($encriptado =~ /key\|0/) + { + $but_no = '0'; + $encriptado =~ m/(.*)\|(.*)\|(.*)/; + $cmd = $2; + $data = $1; + + } + else + { + $but_no = $status; + $but_no =~ s/(\d+)(.*)\|(.*)/$1/g; + $contexto = $status; + $contexto =~ s/([^\|]*).*/$1/g; + $contexto =~ m/(.*)\@(.*)/; + + if (defined($2)) + { + $contexto = $2; + $but_no .= "\@$contexto"; + } + else + { + $contexto = ""; + } + $status =~ m/(.*)\|(.*)\|(.*)/; + $cmd = $2; + $data = $3; + } + + if ($flash_contexto{$socket} ne $contexto && $but_no ne "0") + { + + # If the context does not match, exit without queueing anything + return; + } + + if (!defined($no_encryption{"$socket"})) + { + $no_encryption{"$socket"} = 0; + } + + $noencriptado = "\0"; + + if (!defined($keys_socket{"$socket"}) || $nocrypt == 1 || $no_encryption{"$socket"} == 1) + { + $encriptado = "\0"; + if ($cmd eq "key") + { + $keys_socket{$socket} = $data; + print "pongo keys_socket en $data\n"; + } + } + else + { + $cmd_crypt = &TEAencrypt($cmd, $keys_socket{"$socket"}); + $data_crypt = &TEAencrypt($data, $keys_socket{"$socket"}); + $encriptado = "\0"; + if ($cmd eq "key") + { + $keys_socket{$socket} = $data; + } + } + if (!defined($ip_addy{$socket})) + { + log_debug("Skip actual_syswrite to $socket cause it does not exists!", 128); + } + else + { + actual_syswrite($socket, $encriptado, "isclient", $noencriptado); + } + + #push @{$client_queue_nocrypt{$socket}}, $noencriptado; + #push @{$client_queue{$socket}}, $encriptado; +} + +sub manager_login_md5 +{ + my $challenge = shift; + my $handle = shift; + my @partes = split(/\|/, $manager_socket{$handle}); + + my $md5clave = MD5HexDigest($challenge . $partes[2]); + + $command = "Action: Login\r\n"; + $command .= "Username: $partes[1]\r\n"; + $command .= "AuthType: MD5\r\n"; + $command .= "Key: $md5clave\r\n\r\n"; + send_command_to_manager($command, $handle, 1); +} + +sub send_command_to_manager +{ + my $comando = shift; + my $socket = shift; + my $noneedtoauth = shift; + my @todos_sockets = (); + + if (!defined($socket)) + { + return; + } + + if (!defined($autenticado{$socket}) && !defined($noneedtoauth)) + { + log_debug("Cannot send command $comando, manager connection to " . $ip_addy{$socket} . " failed", 1); + return; + } + + my @partes = split(/\|/, $manager_socket{$socket}); + + # $comando = ""; + + if ($comando eq "") + { + return; + } + + if (!defined($socket)) + { + @todos_sockets = @p; + } + else + { + push @todos_sockets, $socket; + } + + foreach (@todos_sockets) + { + my $sockwrite = $_; + if (!defined($sockwrite) || $sockwrite eq "") { next; } + my @lineas = split("\r\n", $comando); + foreach my $linea (@lineas) + { + + # syswrite($sockwrite, "$linea\r\n"); + push @{$manager_queue{$sockwrite}}, "$linea\r\n"; + + # my $linea_formato = sprintf("%-15s -> %s", $partes[0], $linea); + + # log_debug("$partes[0] -> $linea", 2); + #log_debug("$linea_formato", 2); + } + $global_verbose = "separator"; + push @{$manager_queue{$sockwrite}}, "\r\n"; + + # syswrite($sockwrite, "\r\n"); + } +} + +sub split_callerid +{ + my $clid = shift; + my @return = (); + my $calleridname = ""; + my $calleridnum = ""; + + if ($clid =~ //; + $calleridname = $1; + $calleridnum = $2; + } + else + { + $calleridnum = $clid; + $calleridname = "unknown"; + } + push @return, $calleridnum; + push @return, $calleridname; + + return @return; +} + +sub is_number +{ + my $num = shift; + if (!defined($num)) { return 1; } + if ($num =~ /[^0-9]/) + { + return 0; + } + else + { + return 1; + } +} + +sub close_all +{ + foreach my $file (@all_flash_files) + { + log_debug("Removing $file...", 1); + unlink($file); + } + foreach my $hd ($O->handles) + { + my $peer_ip = $ip_addy{$hd}; + if (defined($peer_ip)) + { + log_debug("Closing " . $peer_ip, 1); + } + + $O->remove($hd); + close($hd); + } + + log_debug("Exiting...", 1); + exit(0); +} + +sub inArray +{ + my $val = shift; + for my $elem (@_) + { + if ($val eq $elem) + { + return 1; + } + } + return 0; +} + +sub encode_base64 +{ + my $res = ""; + my $eol = "\n"; + pos($_[0]) = 0; + while ($_[0] =~ /(.{1,45})/gs) + { + $res .= substr(pack("u", $1), 1); + chop($res); + } + $res =~ tr|` -_|AA-Za-z0-9+/|; # ` + my $padding = (3 - length($_[0]) % 3) % 3; + $res =~ s/.{$padding}$/"=" x $padding/e if $padding; + + return $res; +} + +sub format_clid +{ + + # Subroutine to format the caller id number + # The format string is in the form "(xxx) xxx-xxxx" + # Every x is counted as a digit, any other text is + # displayed as is. The digits are replaced from right + # to left. If there are digits left, they are discarded + + my $numero = shift; + my $format = shift; + my @chars_number = (); + my @chars_format = (); + my @result = (); + my $devuelve = ""; + + if (!is_number($numero)) + { + return $numero; + } + + if ($clid_privacy) + { + return "n/a"; + } + + @chars_number = split(//, $numero); + @chars_format = split(//, $format); + + @chars_format = reverse @chars_format; + + my $parate = 0; + foreach (@chars_format) + { + if (@chars_number) + { + if ($_ eq "x" or $_ eq "X") + { + push(@result, pop @chars_number); + } + else + { + push(@result, $_); + } + } + else + { + if ($parate) { last; } + + if ($_ eq "x" or $_ eq "X") + { + $parate = 1; + next; + } + else + { + push(@result, $_); + last; + } + } + } + + @result = reverse @result; + $devuelve = join("", @result); + return $devuelve; +} + +sub generate_random_password +{ + my $passwordsize = shift; + my @alphanumeric = ('a' .. 'z', 'A' .. 'Z', 0 .. 9); + my $randpassword = join '', map $alphanumeric[rand @alphanumeric], 0 .. $passwordsize; + + return $randpassword; +} + +sub sends_incorrect +{ + my $socket = shift; + my $manda = "0|incorrect|0"; + my $T = send_status_to_flash($socket, $manda, 0); + print " Manda icorrect!\n"; +} + +sub sends_correct +{ + my $socket = shift; + my $manda = "0|correct|0"; + my $T = send_status_to_flash($socket, $manda, 0); +} + +sub sends_version +{ + my $socket = shift; + my $nocrypt = 0; + my $contexto = $flash_contexto{$socket}; + my $boton = "0"; + if ($contexto ne "") + { + $boton .= "\@$contexto"; + } + my $version_string = "$boton|version|$FOP_VERSION"; + if (!$keys_socket{"$socket"}) + { + $nocrypt = 1; + } + send_status_to_flash($socket, $version_string, $nocrypt); +} + +sub sends_key +{ + + # Generate random key por padding the password + # and write it to the client + my $socket = shift; + my $keylen = int(rand(22)); + my $nocrypt = 0; + $keylen += 15; + my $randomkey = generate_random_password($keylen); + my $mandakey = "$randomkey|key|0"; + if (!$keys_socket{"$socket"}) + { + $nocrypt = 1; + } + if (!defined($keys_socket{$socket})) + { + $keys_socket{$socket} = $randomkey; + } + send_status_to_flash($socket, $mandakey, $nocrypt); +} + +sub MD5Digest +{ + my $context = &MD5Init(); + + # security feature: uncomment and put your own "magic string" + # note: MD5test.pl will not work with your magic string, of course + # my $magicString = '!@#$%^'; + # &MD5Update($context, $magicString, length($magicString)); + + # this should be done always + &MD5Update($context, $_[0], length($_[0])); + + return &MD5Final($context); +} + +# +# same as Digest but returns digest in a printable (hex) form +# + +sub MD5HexDigest +{ + return unpack("H*", &MD5Digest(@_)); +} + +# +# MD5 implementation is below +# + +# derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm + +# Original context structure +# typedef struct { +# +# UINT4 state[4]; /* state (ABCD) */ +# UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ +# unsigned char buffer[64]; /* input buffer */ +# +# } MD5_CTX; + +# Constants for MD5Transform routine. + +sub S11 { 7 } +sub S12 { 12 } +sub S13 { 17 } +sub S14 { 22 } +sub S21 { 5 } +sub S22 { 9 } +sub S23 { 14 } +sub S24 { 20 } +sub S31 { 4 } +sub S32 { 11 } +sub S33 { 16 } +sub S34 { 23 } +sub S41 { 6 } +sub S42 { 10 } +sub S43 { 15 } +sub S44 { 21 } + +# F, G, H and I are basic MD5 functions. + +sub F { my ($x, $y, $z) = @_; ((($x) & ($y)) | ((~$x) & ($z))); } +sub G { my ($x, $y, $z) = @_; ((($x) & ($z)) | (($y) & (~$z))); } +sub H { my ($x, $y, $z) = @_; (($x) ^ ($y) ^ ($z)); } +sub I { my ($x, $y, $z) = @_; (($y) ^ (($x) | (~$z))); } + +# ROTATE_LEFT rotates x left n bits. +# Note: "& ~(-1 << $n)" is not in C version +# +sub ROTATE_LEFT +{ + my ($x, $n) = @_; + ($x << $n) | (($x >> (32 - $n) & ~(-1 << $n))); +} + +# FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. +# Rotation is separate from addition to prevent recomputation. + +sub FF +{ + my ($a, $b, $c, $d, $x, $s, $ac) = @_; + + $a += &F($b, $c, $d) + $x + $ac; + $a = &ROTATE_LEFT($a, $s); + $a += $b; + + return $a; +} + +sub GG +{ + my ($a, $b, $c, $d, $x, $s, $ac) = @_; + + $a += &G($b, $c, $d) + $x + $ac; + $a = &ROTATE_LEFT($a, $s); + $a += $b; + + return $a; +} + +sub HH +{ + my ($a, $b, $c, $d, $x, $s, $ac) = @_; + $a += &H($b, $c, $d) + $x + $ac; + $a = &ROTATE_LEFT($a, $s); + $a += $b; + + return $a; +} + +sub II +{ + my ($a, $b, $c, $d, $x, $s, $ac) = @_; + + $a += &I($b, $c, $d) + $x + $ac; + $a = &ROTATE_LEFT($a, $s); + $a += $b; + + return $a; +} + +# MD5 initialization. Begins an MD5 operation, writing a new context. + +sub MD5Init +{ + my $context = {}; + + @{$context->{count}} = 2; + $context->{count}[0] = $context->{count}[1] = 0; + $context->{buffer} = ''; + + # Load magic initialization constants. + + @{$context->{state}} = 4; + $context->{state}[0] = 0x67452301; + $context->{state}[1] = 0xefcdab89; + $context->{state}[2] = 0x98badcfe; + $context->{state}[3] = 0x10325476; + + return $context; +} + +# MD5 block update operation. Continues an MD5 message-digest +# operation, processing another message block, and updating the context. + +sub MD5Update +{ + my ($context, $input, $inputLen) = @_; + + # Compute number of bytes mod 64 + my $index = (($context->{count}[0] >> 3) & 0x3F); + + # Update number of bits + if (($context->{count}[0] += ($inputLen << 3)) < ($inputLen << 3)) + { + $context->{count}[1]++; + $context->{count}[1] += ($inputLen >> 29); + } + + my $partLen = 64 - $index; + + # Transform as many times as possible. + + my $i; + if ($inputLen >= $partLen) + { + + substr($context->{buffer}, $index, $partLen) = substr($input, 0, $partLen); + + &MD5Transform(\@{$context->{state}}, $context->{buffer}); + + for ($i = $partLen ; $i + 63 < $inputLen ; $i += 64) + { + &MD5Transform($context->{state}, substr($input, $i)); + } + + $index = 0; + } + else + { + $i = 0; + } + + # Buffer remaining input + substr($context->{buffer}, $index, $inputLen - $i) = substr($input, $i, $inputLen - $i); +} + +# MD5 finalization. Ends an MD5 message-digest operation, writing the +# the message digest and zeroizing the context. + +sub MD5Final +{ + my $context = shift; + + # Save number of bits + my $bits = &Encode(\@{$context->{count}}, 8); + + # Pad out to 56 mod 64. + my ($index, $padLen); + $index = ($context->{count}[0] >> 3) & 0x3f; + $padLen = ($index < 56) ? (56 - $index) : (120 - $index); + + &MD5Update($context, $PADDING, $padLen); + + # Append length (before padding) + MD5Update($context, $bits, 8); + + # Store state in digest + my $digest = &Encode(\@{$context->{state}}, 16); + + # &MD5_memset ($context, 0); + + return $digest; +} + +# MD5 basic transformation. Transforms state based on block. + +sub MD5Transform +{ + my ($state, $block) = @_; + + my ($a, $b, $c, $d) = @{$state}; + my @x = 16; + + &Decode(\@x, $block, 64); + + # Round 1 + $a = &FF($a, $b, $c, $d, $x[0], S11, 0xd76aa478); # 1 + $d = &FF($d, $a, $b, $c, $x[1], S12, 0xe8c7b756); # 2 + $c = &FF($c, $d, $a, $b, $x[2], S13, 0x242070db); # 3 + $b = &FF($b, $c, $d, $a, $x[3], S14, 0xc1bdceee); # 4 + $a = &FF($a, $b, $c, $d, $x[4], S11, 0xf57c0faf); # 5 + $d = &FF($d, $a, $b, $c, $x[5], S12, 0x4787c62a); # 6 + $c = &FF($c, $d, $a, $b, $x[6], S13, 0xa8304613); # 7 + $b = &FF($b, $c, $d, $a, $x[7], S14, 0xfd469501); # 8 + $a = &FF($a, $b, $c, $d, $x[8], S11, 0x698098d8); # 9 + $d = &FF($d, $a, $b, $c, $x[9], S12, 0x8b44f7af); # 10 + $c = &FF($c, $d, $a, $b, $x[10], S13, 0xffff5bb1); # 11 + $b = &FF($b, $c, $d, $a, $x[11], S14, 0x895cd7be); # 12 + $a = &FF($a, $b, $c, $d, $x[12], S11, 0x6b901122); # 13 + $d = &FF($d, $a, $b, $c, $x[13], S12, 0xfd987193); # 14 + $c = &FF($c, $d, $a, $b, $x[14], S13, 0xa679438e); # 15 + $b = &FF($b, $c, $d, $a, $x[15], S14, 0x49b40821); # 16 + + # Round 2 + $a = &GG($a, $b, $c, $d, $x[1], S21, 0xf61e2562); # 17 + $d = &GG($d, $a, $b, $c, $x[6], S22, 0xc040b340); # 18 + $c = &GG($c, $d, $a, $b, $x[11], S23, 0x265e5a51); # 19 + $b = &GG($b, $c, $d, $a, $x[0], S24, 0xe9b6c7aa); # 20 + $a = &GG($a, $b, $c, $d, $x[5], S21, 0xd62f105d); # 21 + $d = &GG($d, $a, $b, $c, $x[10], S22, 0x2441453); # 22 + $c = &GG($c, $d, $a, $b, $x[15], S23, 0xd8a1e681); # 23 + $b = &GG($b, $c, $d, $a, $x[4], S24, 0xe7d3fbc8); # 24 + $a = &GG($a, $b, $c, $d, $x[9], S21, 0x21e1cde6); # 25 + $d = &GG($d, $a, $b, $c, $x[14], S22, 0xc33707d6); # 26 + $c = &GG($c, $d, $a, $b, $x[3], S23, 0xf4d50d87); # 27 + $b = &GG($b, $c, $d, $a, $x[8], S24, 0x455a14ed); # 28 + $a = &GG($a, $b, $c, $d, $x[13], S21, 0xa9e3e905); # 29 + $d = &GG($d, $a, $b, $c, $x[2], S22, 0xfcefa3f8); # 30 + $c = &GG($c, $d, $a, $b, $x[7], S23, 0x676f02d9); # 31 + $b = &GG($b, $c, $d, $a, $x[12], S24, 0x8d2a4c8a); # 32 + + # Round 3 + $a = &HH($a, $b, $c, $d, $x[5], S31, 0xfffa3942); # 33 + $d = &HH($d, $a, $b, $c, $x[8], S32, 0x8771f681); # 34 + $c = &HH($c, $d, $a, $b, $x[11], S33, 0x6d9d6122); # 35 + $b = &HH($b, $c, $d, $a, $x[14], S34, 0xfde5380c); # 36 + $a = &HH($a, $b, $c, $d, $x[1], S31, 0xa4beea44); # 37 + $d = &HH($d, $a, $b, $c, $x[4], S32, 0x4bdecfa9); # 38 + $c = &HH($c, $d, $a, $b, $x[7], S33, 0xf6bb4b60); # 39 + $b = &HH($b, $c, $d, $a, $x[10], S34, 0xbebfbc70); # 40 + $a = &HH($a, $b, $c, $d, $x[13], S31, 0x289b7ec6); # 41 + $d = &HH($d, $a, $b, $c, $x[0], S32, 0xeaa127fa); # 42 + $c = &HH($c, $d, $a, $b, $x[3], S33, 0xd4ef3085); # 43 + $b = &HH($b, $c, $d, $a, $x[6], S34, 0x4881d05); # 44 + $a = &HH($a, $b, $c, $d, $x[9], S31, 0xd9d4d039); # 45 + $d = &HH($d, $a, $b, $c, $x[12], S32, 0xe6db99e5); # 46 + $c = &HH($c, $d, $a, $b, $x[15], S33, 0x1fa27cf8); # 47 + $b = &HH($b, $c, $d, $a, $x[2], S34, 0xc4ac5665); # 48 + + # Round 4 + $a = &II($a, $b, $c, $d, $x[0], S41, 0xf4292244); # 49 + $d = &II($d, $a, $b, $c, $x[7], S42, 0x432aff97); # 50 + $c = &II($c, $d, $a, $b, $x[14], S43, 0xab9423a7); # 51 + $b = &II($b, $c, $d, $a, $x[5], S44, 0xfc93a039); # 52 + $a = &II($a, $b, $c, $d, $x[12], S41, 0x655b59c3); # 53 + $d = &II($d, $a, $b, $c, $x[3], S42, 0x8f0ccc92); # 54 + $c = &II($c, $d, $a, $b, $x[10], S43, 0xffeff47d); # 55 + $b = &II($b, $c, $d, $a, $x[1], S44, 0x85845dd1); # 56 + $a = &II($a, $b, $c, $d, $x[8], S41, 0x6fa87e4f); # 57 + $d = &II($d, $a, $b, $c, $x[15], S42, 0xfe2ce6e0); # 58 + $c = &II($c, $d, $a, $b, $x[6], S43, 0xa3014314); # 59 + $b = &II($b, $c, $d, $a, $x[13], S44, 0x4e0811a1); # 60 + $a = &II($a, $b, $c, $d, $x[4], S41, 0xf7537e82); # 61 + $d = &II($d, $a, $b, $c, $x[11], S42, 0xbd3af235); # 62 + $c = &II($c, $d, $a, $b, $x[2], S43, 0x2ad7d2bb); # 63 + $b = &II($b, $c, $d, $a, $x[9], S44, 0xeb86d391); # 64 + + $state->[0] += $a; + $state->[1] += $b; + $state->[2] += $c; + $state->[3] += $d; + + # Zeroize sensitive information. + # MD5_memset ((POINTER)x, 0, sizeof (x)); +} + +# Encodes input (UINT4) into output (unsigned char). Assumes len is +# a multiple of 4. + +sub Encode +{ + my ($input, $len) = @_; + + my $output = ''; + my ($i, $j); + for ($i = 0, $j = 0 ; $j < $len ; $i++, $j += 4) + { + substr($output, $j + 0, 1) = chr($input->[$i] & 0xff); + substr($output, $j + 1, 1) = chr(($input->[$i] >> 8) & 0xff); + substr($output, $j + 2, 1) = chr(($input->[$i] >> 16) & 0xff); + substr($output, $j + 3, 1) = chr(($input->[$i] >> 24) & 0xff); + } + + return $output; +} + +# Decodes input (unsigned char) into output (UINT4). Assumes len is +# a multiple of 4. + +sub Decode +{ + my ($output, $input, $len) = @_; + + my ($i, $j); + + for ($i = 0, $j = 0 ; $j < $len ; $i++, $j += 4) + { + $output->[$i] = + (ord(substr($input, $j + 0, 1))) | (ord(substr($input, $j + 1, 1)) << 8) | + (ord(substr($input, $j + 2, 1)) << 16) | (ord(substr($input, $j + 3, 1)) << 24); + } +} +######################################################################### +# TEA Encryption algorithm +# +######################################################################### +# This Perl module is Copyright (c) 2000, Peter J Billam # +# c/o P J B Computing, www.pjb.com.au # +######################################################################### + +sub binary2ascii +{ + return &str2ascii(&binary2str(@_)); +} + +sub ascii2binary +{ + return &str2binary(&ascii2str($_[$[])); +} + +sub str2binary +{ + my @str = split //, $_[$[]; + my @intarray = (); + my $ii = $[; + while (1) + { + last unless @str; + $intarray[$ii] = (0xFF & ord shift @str) << 24; + last unless @str; + $intarray[$ii] |= (0xFF & ord shift @str) << 16; + last unless @str; + $intarray[$ii] |= (0xFF & ord shift @str) << 8; + last unless @str; + $intarray[$ii] |= 0xFF & ord shift @str; + $ii++; + } + return @intarray; +} + +sub binary2str +{ + my @str = (); + foreach my $i (@_) + { + push @str, chr(0xFF & ($i >> 24)), chr(0xFF & ($i >> 16)), chr(0xFF & ($i >> 8)), chr(0xFF & $i); + } + return join '', @str; +} + +sub ascii2str +{ + my $a = $_[$[]; # converts pseudo-base64 to string of bytes + $a =~ tr#A-Za-z0-9+_##cd; + my $ia = $[ - 1; + my $la = length $a; # BUG not length, final! + my $ib = $[; + my @b = (); + my $carry; + while (1) + { # reads 4 ascii chars and produces 3 bytes + $ia++; + last if ($ia >= $la); + $b[$ib] = $a2b{substr $a, $ia + $[, 1} << 2; + $ia++; + last if ($ia >= $la); + $carry = $a2b{substr $a, $ia + $[, 1}; + $b[$ib] |= ($carry >> 4); + $ib++; + + # if low 4 bits of $carry are 0 and its the last char, then break + $carry = 0xF & $carry; + last if ($carry == 0 && $ia == ($la - 1)); + $b[$ib] = $carry << 4; + $ia++; + last if ($ia >= $la); + $carry = $a2b{substr $a, $ia + $[, 1}; + $b[$ib] |= ($carry >> 2); + $ib++; + + # if low 2 bits of $carry are 0 and its the last char, then break + $carry = 03 & $carry; + last if ($carry == 0 && $ia == ($la - 1)); + $b[$ib] = $carry << 6; + $ia++; + last if ($ia >= $la); + $b[$ib] |= $a2b{substr $a, $ia + $[, 1}; + $ib++; + } + return pack 'c*', @b; +} + +sub str2ascii +{ + my $b = $_[$[]; # converts string of bytes to pseudo-base64 + my $ib = $[; + my $lb = length $b; + my @s = (); + my $b1; + my $b2; + my $b3; + my $carry; + + while (1) + { # reads 3 bytes and produces 4 ascii chars + if ($ib >= $lb) { last; } + $b1 = ord substr $b, $ib + $[, 1; + $ib++; + push @s, $b2a{$b1 >> 2}; + $carry = 03 & $b1; + if ($ib >= $lb) { push @s, $b2a{$carry << 4}; last; } + $b2 = ord substr $b, $ib + $[, 1; + $ib++; + push @s, $b2a{($b2 >> 4) | ($carry << 4)}; + $carry = 0xF & $b2; + if ($ib >= $lb) { push @s, $b2a{$carry << 2}; last; } + $b3 = ord substr $b, $ib + $[, 1; + $ib++; + push @s, $b2a{($b3 >> 6) | ($carry << 2)}, $b2a{077 & $b3}; + if (!$ENV{REMOTE_ADDR} && (($ib % 36) == 0)) { push @s, "\n"; } + } + return join('', @s); +} + +sub asciidigest +{ # returns 22-char ascii signature + return &binary2ascii(&binarydigest($_[$[])); +} + +sub binarydigest +{ + my $str = $_[$[]; # returns 4 32-bit-int binary signature + # warning: mode of use invented by Peter Billam 1998, needs checking ! + return '' unless $str; + + # add 1 char ('0'..'15') at front to specify no of pad chars at end ... + my $npads = 15 - ((length $str) % 16); + $str = chr($npads) . $str; + if ($npads) { $str .= "\0" x $npads; } + my @str = &str2binary($str); + my @key = (0x61626364, 0x62636465, 0x63646566, 0x64656667); + + my ($cswap, $v0, $v1, $v2, $v3); + my $c0 = 0x61626364; + my $c1 = 0x62636465; # CBC Initial Value. Retain ! + my $c2 = 0x61626364; + my $c3 = 0x62636465; # likewise (abcdbcde). + while (@str) + { + + # shift 2 blocks off front of str ... + $v0 = shift @str; + $v1 = shift @str; + $v2 = shift @str; + $v3 = shift @str; + + # cipher them XOR'd with previous stage ... + ($c0, $c1) = &tea_code($v0 ^ $c0, $v1 ^ $c1, @key); + ($c2, $c3) = &tea_code($v2 ^ $c2, $v3 ^ $c3, @key); + + # mix up the two cipher blocks with a 4-byte left rotation ... + $cswap = $c0; + $c0 = $c1; + $c1 = $c2; + $c2 = $c3; + $c3 = $cswap; + } + return ($c0, $c1, $c2, $c3); +} + +sub TEAencrypt +{ + my ($str, $key) = @_; # encodes with CBC (Cipher Block Chaining) + use integer; + return '' unless $str; + return '' unless $key; + @key = &binarydigest($key); + + # add 1 char ('0'..'7') at front to specify no of pad chars at end ... + my $npads = 7 - ((length $str) % 8); + $str = chr($npads | (0xF8 & &rand_byte)) . $str; + if ($npads) + { + my $padding = pack 'CCCCCCC', &rand_byte, &rand_byte, &rand_byte, &rand_byte, &rand_byte, &rand_byte, + &rand_byte; + $str = $str . substr($padding, $[, $npads); + } + my @pblocks = &str2binary($str); + my $v0; + my $v1; + my $c0 = 0x61626364; + my $c1 = 0x62636465; # CBC Initial Value. Retain ! + my @cblocks; + while (1) + { + last unless @pblocks; + $v0 = shift @pblocks; + $v1 = shift @pblocks; + ($c0, $c1) = &tea_code($v0 ^ $c0, $v1 ^ $c1, @key); + push @cblocks, $c0, $c1; + } + my $btmp = &binary2str(@cblocks); + return &str2ascii(&binary2str(@cblocks)); +} + +sub TEAdecrypt +{ + my ($acstr, $key) = @_; # decodes with CBC + use integer; + return '' unless $acstr; + return '' unless $key; + @key = &binarydigest($key); + my $v0; + my $v1; + my $c0; + my $c1; + my @pblocks = (); + my $de0; + my $de1; + my $lastc0 = 0x61626364; + my $lastc1 = 0x62636465; # CBC Init Val. Retain! + my @cblocks = &str2binary(&ascii2str($acstr)); + + while (1) + { + last unless @cblocks; + $c0 = shift @cblocks; + $c1 = shift @cblocks; + ($de0, $de1) = &tea_decode($c0, $c1, @key); + $v0 = $lastc0 ^ $de0; + $v1 = $lastc1 ^ $de1; + push @pblocks, $v0, $v1; + $lastc0 = $c0; + $lastc1 = $c1; + } + my $str = &binary2str(@pblocks); + + # remove no of pad chars at end specified by 1 char ('0'..'7') at front + my $npads = 0x7 & ord $str; + substr($str, $[, 1) = ''; + if ($npads) { substr($str, 0 - $npads) = ''; } + return $str; +} + +sub triple_encrypt +{ + my ($plaintext, $long_key) = @_; # not yet ... +} + +sub triple_decrypt +{ + my ($cyphertext, $long_key) = @_; # not yet ... +} + +sub tea_code +{ + my ($v0, $v1, $k0, $k1, $k2, $k3) = @_; + + # TEA. 64-bit cleartext block in $v0,$v1. 128-bit key in $k0..$k3. + # &prn("tea_code: v0=$v0 v1=$v1"); + use integer; + my $sum = 0; + my $n = 32; + while ($n-- > 0) + { + $sum += 0x9e3779b9; # TEA magic number delta + $v0 += (($v1 << 4) + $k0) ^ ($v1 + $sum) ^ ((0x07FFFFFF & ($v1 >> 5)) + $k1); + $v1 += (($v0 << 4) + $k2) ^ ($v0 + $sum) ^ ((0x07FFFFFF & ($v0 >> 5)) + $k3); + } + return ($v0, $v1); +} + +sub tea_decode +{ + my ($v0, $v1, $k0, $k1, $k2, $k3) = @_; + + # TEA. 64-bit cyphertext block in $v0,$v1. 128-bit key in $k0..$k3. + use integer; + my $sum = 0; + my $n = 32; + $sum = 0x9e3779b9 << 5; # TEA magic number delta + while ($n-- > 0) + { + $v1 -= (($v0 << 4) + $k2) ^ ($v0 + $sum) ^ ((0x07FFFFFF & ($v0 >> 5)) + $k3); + $v0 -= (($v1 << 4) + $k0) ^ ($v1 + $sum) ^ ((0x07FFFFFF & ($v1 >> 5)) + $k1); + $sum -= 0x9e3779b9; + } + return ($v0, $v1); +} + +sub rand_byte +{ + if (!$rand_byte_already_called) + { + srand(time() ^ ($$ + ($$ << 15))); # could do better, but its only padding + $rand_byte_already_called = 1; + } + int(rand 256); +} + +# +# End TEA + +sub print_datos +{ + if ($debug & 1) + { + my $num = shift; + + if (keys(%datos)) + { + print "---------------------------------------------------\n"; + print "DATOS $num\n"; + print "---------------------------------------------------\n"; + for (keys %datos) + { + print $_. "\n"; + while (my ($key, $val) = each(%{$datos{$_}})) + { + if (defined($val)) + { + print "\t$key = $val\n"; + } + } + print "---------------------------------------------------\n"; + } + } + else + { + print "NO DATOS TO DISPLAY\n"; + } + } +} + +sub print_cachehit +{ + if ($debug & 1) + { + print "---------------------------------------------------\n"; + print "CACHE HIT\n"; + print "---------------------------------------------------\n"; + if (keys(%cache_hit)) + { + for (keys %cache_hit) + { + print "key $_\n"; + + if (defined(@{$cache_hit{$_}})) + { + my @final = (); + foreach my $val (@{$cache_hit{$_}}) + { + print "\tcache_hit($_) = $val\n"; + } + } + } + } + else + { + print "NO CACHE HITS TO DISPLAY\n"; + } + print "---------------------------------------------------\n"; + } +} + +sub print_linkbot +{ + if ($debug & 1) + { + print "---------------------------------------------------\n"; + print "LINKS BOTONES\n"; + print "---------------------------------------------------\n"; + if (keys(%linkbot)) + { + for (keys %linkbot) + { + if (defined(@{$linkbot{$_}})) + { + my @final = (); + foreach my $val (@{$linkbot{$_}}) + { + print "\tlinkbot($_) = $val\n"; + } + } + } + } + else + { + print "NO DATOS TO DISPLAY\n"; + } + print "---------------------------------------------------\n"; + } +} + +sub print_sesbot +{ + my $quien = shift; + if ($debug & 1) + { + print "---------------------------------------------------\n"; + print "SESIONES BOTONES $quien\n"; + print "---------------------------------------------------\n"; + if (keys(%sesbot)) + { + for (keys %sesbot) + { + if (defined(@{$sesbot{$_}})) + { + my @final = (); + foreach my $val (@{$sesbot{$_}}) + { + print "\tsesbot($_) = $val\n"; + } + } + } + } + else + { + print "NO DATOS TO DISPLAY\n"; + } + print "---------------------------------------------------\n"; + } +} + +sub print_instancias +{ + if ($debug & 1) + { + my $num = shift; + print "---------------------------------------------------\n"; + print "Instancias 2 $num\n"; + print "---------------------------------------------------\n"; + foreach my $caca (sort (keys(%instancias))) + { + print $caca. "\n"; + foreach my $pipu (sort (keys(%{$instancias{$caca}}))) + { + print "\t$pipu = $instancias{$caca}{$pipu}\n"; + } + } + print "---------------------------------------------------\n"; + } +} + +sub print_botones +{ + if ($debug & 1) + { + my $num = shift; + print "---------------------------------------------------\n"; + print "Botones $num\n"; + print "---------------------------------------------------\n"; + foreach (sort (keys(%buttons))) + { + printf("%-20s %-10s %-10s\n", $_, $buttons{$_}, $button_server{$buttons{$_}}); + } + } +} + +sub print_cola_write +{ + my $socket = shift; + if (!defined($socket)) + { + for (keys %client_queue) + { + my $contame = 0; + foreach my $val (@{$client_queue{$_}}) + { + $contame++; + print "cola $contame $_ comando $val\n"; + } + } + } + else + { + my $contame = 0; + foreach my $val (@{$client_queue{$socket}}) + { + $contame++; + print "cola $contame $socket comando $val\n"; + } + } +} + +sub print_clients +{ + if ($debug & 1) + { + my $number_of_flash_clients_connected = @flash_clients; + + if ($number_of_flash_clients_connected > 0) + { + print "\nFlash clients connected: $number_of_flash_clients_connected\n"; + print "---------------------------------------------------\n"; + + foreach my $C (@flash_clients) + { + print peerinfo($C) . " $C\n"; + } + print "---------------------------------------------------\n"; + } + else + { + print "No flash clients connected\n"; + } + } +} + +sub print_status +{ + if (keys(%estadoboton)) + { + print "---------------------------------------------------\n"; + print "ESTADO BOTONES\n"; + print "---------------------------------------------------\n"; + for (keys %estadoboton) + { + my $separador = 0; + my $nroboton = $_; + print "$nroboton\t $estadoboton{$nroboton}\t $statusboton{$nroboton}\n"; + } + print "---------------------------------------------------\n"; + } + else + { + print "No estadoboton populated\n"; + } + + if (keys(%botonled)) + { + print "----- LEDS --------\n"; + for (keys %botonled) + { + print "$_ = $botonled{$_} $botonlabel{$_}\n"; + } + } + if (keys(%botonvoicemail)) + { + print "----- VOICEMAIL --------\n"; + for (keys %botonvoicemail) + { + print "$_ = $botonvoicemail{$_}\n"; + } + } +} Index: /trunk/amportal/amp_conf/htdocs/_asterisk/vmail.css =================================================================== --- /trunk/amportal/amp_conf/htdocs/_asterisk/vmail.css (revision 523) +++ /trunk/amportal/amp_conf/htdocs/_asterisk/vmail.css (revision 523) @@ -0,0 +1,34 @@ +body { + background-color: white; + font: 12px Geneva, Arial, Helvetica, sans-serif; +} + +.headline { + font-size: 18px; +} + +.notice { + font-size: 16px; + color: #AD3145; +} + +.subheadline { + font-size: 16px; +} + +td.name { + font-weight: bold; +} + +td.value { + +} + +td { + margin: 4px; + font-size: 12px; +} + +.colTitle { + padding-top: 10px; +} Index: /trunk/amportal/amp_conf/astvarlib/agi-bin/fixlocalprefix =================================================================== --- /trunk/amportal/amp_conf/astvarlib/agi-bin/fixlocalprefix (revision 523) +++ /trunk/amportal/amp_conf/astvarlib/agi-bin/fixlocalprefix (revision 523) @@ -0,0 +1,209 @@ +#!/usr/bin/php -q +verbose("Could not parse ".LOCALPREFIX_FILE); + exit(1); + } +} else { + $agi->verbose("Could not open ".LOCALPREFIX_FILE); + exit(1); +} + +$r = $agi->get_variable("DIAL_NUMBER"); +if ($r["result"] == 0) { + $agi->verbose("DIAL_NUMBER not set -- nothing to do"); + exit(1); +} +$number = $r["data"]; + + +$r = $agi->get_variable("DIAL_TRUNK"); +if ($r["result"] == 0) { + $agi->verbose("DIAL_TRUNK not set -- nothing to do"); + exit(1); +} +$trunk = $r["data"]; + + +if (isset($conf["trunk-".$trunk])) { + foreach ($conf["trunk-".$trunk] as $key=>$rule) { + // extract all ruleXX keys + //$agi->conlog($key." = ".$rule); + if (preg_match("/^rule\d+$/",$key)) { + // $rule is a dial rule + + $regex = $rule; + if (false !== ($pos = strpos($rule,"|"))) { + // we're removing digits + $type = "remove"; + } else if (false !== ($pos = strpos($rule,"+"))) { + $type = "add"; + + $regex = substr($regex, $pos); + } else { + $pos = 0; + $type = "asis"; + } + + // remove all non-pattern digits from rule + $regex = preg_replace("/[^0-9XNZ#*\.]/", "", $regex); + + // convert asterisk pattern matching into perl regular expression + $regex = str_replace( + array( + "X", + "Z", + "N", + ".", + ), + array( + "[0-9]", + "[1-9]", + "[2-9]", + "[0-9#*]+", + ), + $regex); + + //$agi->conlog("trying ".$regex." (".$rule.")"); + if (preg_match("/^".$regex."$/",$number)) { + // it matched + switch ($type) { + case "remove": + $number = substr($number, $pos); + $agi->verbose("Removed prefix. New number: ".$number); + $agi->set_variable("DIAL_NUMBER", $number); + break; + case "add": + // we're adding digits + $number = substr($rule,0,$pos).$number; + $agi->verbose("Added prefix. New number: ".$number); + $agi->set_variable("DIAL_NUMBER", $number); + break; + } + + // note, because this matched, we exit even if we didn't change anything + exit(0); + } // else, it didn't match this rule + } // else, this isn't a rule + } +} // else, no config for this section + +// we just exit with no changes to the variable. +exit(0); + +?> Index: /trunk/amportal/amp_conf/astvarlib/agi-bin/dialparties.agi =================================================================== --- /trunk/amportal/amp_conf/astvarlib/agi-bin/dialparties.agi (revision 523) +++ /trunk/amportal/amp_conf/astvarlib/agi-bin/dialparties.agi (revision 523) @@ -0,0 +1,302 @@ +#!/usr/bin/perl -w +# +# Copyright (C) 2003 Zac Sprackett +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# Amended by Coalescent Systems Inc. Sept, 2004 +# to include support for DND, Call Waiting, and CF to external trunk +# info@coalescentsystems.ca + +use Asterisk::AGI; +use Net::Telnet (); + +my $debug = 2; + +my %ext; # Hash that will contain our list of extensions to call +my %ext_hunt;# Hash that will contain our list of extensions to call used by huntgroup +my $cidnum; # Caller ID Number for this call +my $cidname; # Caller ID Name for this call +my $timer; # Call timer for Dial command +my $dialopts;# options for dialing +my $rc; # Catch return code +my $priority;# Next priority +my $rgmethod;# If Ring Group what ringing method was chosen + +my $AGI = new Asterisk::AGI; +my %input = $AGI->ReadParse(); +$AGI->setcallback(\&mycallback); + +if ($debug >= 2) { + foreach $key (keys %input) { + debug("$key = " . $input{$key},3); + } +} +$priority = $input{'priority'} + 1; + +if ($input{'callerid'} =~ /^\"(.*)\"\s+\<(\d+)-?(\d*)\>\s*$/) { + $cidname = $1; + $cidnum = $2.$3; + debug("Caller ID name is '$cidname' number is '$cidnum'", 1); +} elsif ($input{'callerid'} =~ /^(\d+)*$/) { + $cidname = $1; + $cidnum = $1; + debug("Caller ID name and number are '$cidnum'", 1); +} else { + $cidname = undef; + $cidnum = undef; + debug("Caller ID is not set", 1); +} + +$timer = $AGI->get_variable('ARG1') || 0; +$dialopts = $AGI->get_variable('ARG2') || ''; +$rgmethod = $AGI->get_variable("RingGroupMethod") || 'none'; +debug("Methodology of ring is '$rgmethod'", 1); + +# Start with Arg Count set to 3 as two args are used +my $arg_cnt = 3; +while(my $arg = $AGI->get_variable('ARG' . $arg_cnt)) { + if ($arg == 'noresponse') { #not sure why, dialparties will get stuck in a loop if noresponse + debug("get_variable got a \"noresponse\"! Exiting"); + exit($arg_cnt); + } + @extarray=split(/-/,$arg); + foreach my $k (@extarray) { + $ext{$k} = $k; + debug("Added extension $k to extension map", 3); + } + + $arg_cnt++; +} + +# Check for call forwarding first +# If call forward is enabled, we use chan_local +foreach my $k (keys %ext) { + my $cf = $AGI->database_get('CF',$k); + if ($cf) { + $ext{$k} = $cf.'#'; # append a hash sign so we can send out on chan_local below. + debug("Extension $k has call forward set to $cf", 1); + } else { + debug("Extension $k cf is disabled", 3); + } +} + +# Now check for DND +foreach my $k (keys %ext) { + if (($ext{$k} =~ /\#/)!=1) { #no point in doing if cf is enabled + my $dnd = $AGI->database_get('DND',$ext{$k}); + if ($dnd) { + debug("Extension $ext{$k} has do not disturb enabled", 1); + delete $ext{$k}; + } else { + debug("Extension $ext{$k} do not disturb is disabled", 3); + } + } +} + +# Main calling loop + +my $ds = ''; + +foreach my $k (keys %ext) { + my $extnum = $ext{$k}; + my $exthascw = ($AGI->database_get('CW', $extnum)) ? 1 : 0; + my $extcfb = $AGI->database_get('CFB', $extnum); + my $exthascfb = (length($extcfb) > 0) ? 1 : 0; + + # Dump details in level 4 + debug("extnum: $extnum",4); + debug("exthascw: $exthascw",4); + debug("exthascfb: $exthascfb",4); + debug("extcfb: $extcfb",4); + + # if CF is not in use; AND + # CW is not in use or CFB is in use on this extension, then we need to check! + if (($ext{$k} =~ /\#/)!=1 && (($exthascw == 0) || ($exthascfb == 1))) { + debug("Checking CW and CFB status for extension $extnum",3); + my $extstate = is_ext_avail($extnum); + debug("extstate: $extstate",4); + + if ($extstate > 0) { # extension in use + debug("Extension $extnum is not available to be called",1); + + if ($exthascfb == 1) { # CFB is in use + debug("Extension $extnum has call forward on busy set to $extcfb",1); + + $extnum = $extcfb . '#'; # same method as the normal cf, i.e. send to Local + } elsif ($exthascw == 0) { # CW not in use + debug("Extension $extnum has call waiting disabled",1); + $extnum = ''; + } else { + # no reason why this will ever happen! but kept in for clarity + debug("Extension $extnum has call waiting enabled",1); + } + } elsif ($extstate < 0) { # -1 means couldn't read status or chan unavailable + debug("ExtensionState for $extnum could not be read...assuming ok"); + } else { + debug("Extension $extnum is available...skipping checks",1); + } + } elsif ($exthascw == 1) { # just log the fact that CW enabled + debug("Extension $extnum has call waiting enabled",1); + } + +if ($extnum != '') +{ # Still got an extension to be called? + my $extds = get_dial_string($extnum); + $ds .= $extds . '&'; + + # Update Caller ID for calltrace application + if (($ext{$k} =~ /#/)!=1 && ($rgmethod ne "hunt") && ($rgmethod ne "memoryhunt")) { + if ($cidnum) { + $rc = $AGI->database_put('CALLTRACE', $ext{$k}, $cidnum); + if ($rc == 1) { + debug("DbSet CALLTRACE/$ext{$k} to $cidnum", 3); + } else { + debug("Failed to DbSet CALLTRACE/$ext{$k} to $cidnum ($rc)", 1); + } + } else { + # We don't care about retval, this key may not exist + $AGI->database_del('CALLTRACE', $ext{$k}); + debug("DbDel CALLTRACE/$ext{$k} - Caller ID is not defined", 3); + } + } else{ + $ext_hunt{$k}=$extds; # Need to have the extension HASH set with technology for hunt group ring + } +} +} +my $dshunt =''; +my $loops=0; +my $myhuntmember=""; +if (($rgmethod eq "hunt") || ($rgmethod eq "memoryhunt")) { + $AGI->set_variable(CALLTRACE_HUNT,$cidnum) if ($cidnum); + foreach my $k (@extarray) { # we loop through the original array to get the extensions in order of importance + if ($ext_hunt{$k}) {#If the original array is included in the extension hash then set variables + $myhuntmember="HuntMember"."$loops"; + if ($rgmethod eq "hunt") { + $AGI->set_variable($myhuntmember,$ext_hunt{$k}); + } elsif ($rgmethod eq "memoryhunt") { + if ($loops==0) { + $dshunt =$ext_hunt{$k}; + } else { + $dshunt .='&'.$ext_hunt{$k}; + } + $AGI->set_variable($myhuntmember,$dshunt); + } + $loops+=1; + } + } +} + +chop $ds if length($ds); + +if (!length($ds)) { + $AGI->exec('NoOp'); +} else { + if (($rgmethod eq "hunt") || ($rgmethod eq "memoryhunt")){ + $ds = '|'; + $ds .= $timer if ($timer); + $ds .= '|' . $dialopts; # pound to transfer, provide ringing + $AGI->set_variable('ds',$ds); + $AGI->set_variable("HuntMembers",$loops); + $AGI->set_priority(20); #dial command is at priority 20 where dialplan handles calling a ringgroup with strategy of "hunt" or "MemoryHunt" + } else{ + $ds .= '|'; + $ds .= $timer if ($timer); + $ds .= '|' . $dialopts; # pound to transfer, provide ringing + $AGI->set_variable('ds',$ds); + $AGI->set_priority(10); #dial command is at priority 10 + } +} + +exit 0; + +sub get_dial_string +{ + my $extnum = shift; + + my $dialstring = ''; + my $tech = ''; + my $channel = ''; + + if ($extnum =~ s/#//) { # "#" used to identify external numbers in forwards and callgourps + $dialstring = 'Local/'.$extnum.'@from-internal'; + } else { + $tech = $AGI->get_variable('E'.$extnum) || 'SIP'; # grab the global var that defines extension technology. Assume SIP if empty. + if ($tech eq "ZAP") { + $channel = $AGI->get_variable('ZAPCHAN_'.$extnum); + $dialstring = $tech . '/' . $channel; + } else { + $dialstring = $tech . '/' . $extnum; + } + } +} + +sub debug +{ + my $string = shift; + my $level = shift || 3; + + if ($debug) { + $AGI->verbose($string, $level); + } + return(0); +} + +sub mycallback +{ + my $rc = shift; + debug("User hung up. (rc=" . $rc . ")", 1); + exit ($rc) +} + +sub is_ext_avail{ #uses manager api to get ExtensionState info + #asterisk server manager interface information + $mgrUSERNAME='AMPMGRUSER'; + $mgrSECRET='AMPMGRPASS'; + + $server_ip='127.0.0.1'; + + my $extnum = shift; + + $tn = new Net::Telnet (Port => 5038, + Prompt => '/.*[\$%#>] $/', + Output_record_separator => '', + Errmode => 'return' + ); + + #connect to manager and login + $tn->open("$server_ip"); + $tn->waitfor('/0\n$/'); + $tn->print("Action: Login\nUsername: $mgrUSERNAME\nSecret: $mgrSECRET\n\n"); + $tn->waitfor('/Authentication accepted\n\n/'); + + #issue command + $tn->print("Action: ExtensionState\nExten: $extnum\nContext: ext-local\nActionId: 8355\n\n"); + $tn->waitfor('/Response: Success\n/'); + $tn->waitfor('/ActionID: 8355\n/'); + + #wait for status + my $ok = 0; # 0 means ok to call + my $extstatus = 0; + ($ok, $extstatus) = $tn->waitfor('/Status: .*\n/') or die "Could not get ExtensionState"; + + #logoff + $tn->print("Action: Logoff\n\n"); + + if ($ok && $extstatus =~ /Status: (.*)/) { + $extstatus = $1; + } else { + $extstatus = -1; # Make -1 if couldn't read correctly + } + + return $extstatus; +} + Index: /trunk/amportal/amp_conf/astvarlib/agi-bin/directory =================================================================== --- /trunk/amportal/amp_conf/astvarlib/agi-bin/directory (revision 523) +++ /trunk/amportal/amp_conf/astvarlib/agi-bin/directory (revision 523) @@ -0,0 +1,495 @@ +#!/usr/bin/php -q +