#!/usr/bin/perl -w use strict; use IO::Socket; use IO::Select; ########################################## # CONFIGURATION # # parameters to connect to Asterisk Manager my $manager_host = "192.168.0.1"; my $manager_user = "dddd"; my $manager_secret = "ddddd"; # # parameters for the op_server my $web_hostname = "sip.house.com.ar"; # must be the same address you use to contact the web server my $listen_port = 4445; my $security_code = 'ddddd'; # secret code for performing hangups and transfers # # location of variables.txt needed by the flash applet # (must be the same directory as the web page and swf file) my $flash_dir = "/var/www/html/panel"; # # Debug level to stdot my $debug = 3; ########################################## # From now on do not modify! my $flash_file = $flash_dir . "/variables.txt"; my %datos = (); my $bloque_completo; my $bloque_final; my $todo; my @bloque; my @respuestas; my %botones; my %textos; $SIG{PIPE} = 'IGNORE'; $SIG{ALRM} = 'alarma_al_minuto'; $SIG{INT} = 'cierra_todo'; sub lee_config() { open( CONFIG, ") { my $campo1 = ""; my $campo3 = ""; my @campos = (); chop($_); $_ =~ s/^\s+(.*)/$1/g; next if ( $_ =~ /^#/ ); while ( $_ =~ m/"([^"\\]*(\\.[^"\\]*)*)",?|([^,]+),?|,/g ) { $campo1 = $1; $campo3 = $3; $campo1 =~ s/^\s+//g if ( defined($1) ); $campo3 =~ s/^\s+//g if ( defined($3) ); push( @campos, defined($campo1) ? $campo1 : $campo3 ); } push( @campos, undef ) if $_ =~ m/,$/; $botones{"$campos[0]"} = $campos[1]; $textos{"$campos[1]"} = $campos[2]; } close(CONFIG); } sub genera_config { 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"; while ( my ( $key, $val ) = each(%textos) ) { $val =~ s/\"(.*)\"/$1/g; print VARIABLES "&texto$key=$val"; } print VARIABLES "&CheckDone=1"; close(VARIABLES); } if (!(-r "op_server.cfg")) { die("Missing op_server.cfg file"); } lee_config(); genera_config(); my $p = new IO::Socket::INET->new( PeerAddr => $manager_host, PeerPort => 5038, Proto => "tcp", Type => SOCK_STREAM ) or die "\nCould not connect to Asterisk Manager Port\n"; syswrite( $p, "Action: Login\r\nUsername: $manager_user\r\nSecret: $manager_secret\r\n\r\n" ); my $m = new IO::Socket::INET( Listen => 1, LocalPort => $listen_port, ReuseAddr => 1 ) or die "\nCan't listen to port $listen_port\n"; my $O = new IO::Select(); $O->add($m); $O->add($p); $/ = "\0"; alarm(15); while (1) { while ( my @S = $O->can_read ) { foreach (@S) { if ( $_ == $m ) { # Se conecto un nuevo cliente, envio un pedido de Status log_debug( "Se conecto un nuevo cliente, manda un Status", 3 ); my $C = $m->accept; $O->add($C); syswrite( $p, "Action: Status\r\n\r\n" ); } else { # No es conexion nueva my $i; my $R = sysread( $_, $i, 2048 ); if ( defined($R) && $R == 0 ) { my $T = syswrite( $_, ' ', 2048 ); if ( defined($T) ) { } else { $O->remove($_); } } else { # pudo leer 2048 bytes en $i log_debug( "I es igual a:\n--------\n$i\n-------\nLargo de i: " . length($i) . "\n------", 1 ); $bloque_completo = "" if ( !defined($bloque_completo) ); if ( ( substr( $i, -4 ) eq "\r\n\r\n" ) || ( substr( $i, -4 ) eq " />\0" ) ) { log_debug( "Es un fin de linea!", 1 ); $bloque_final = $bloque_completo . $i; $bloque_completo = ""; } else { my $quehay = substr( $i, -4 ); log_debug( "No es un fin de linea --$quehay--!", 1 ); $bloque_completo .= $i; next; } foreach my $C ( $O->handles ) { if ( $C == $p ) { # Recibimos un evento de Asterisk # Leo la info y armo bloques de datos para # parsear en procesa_bloque if ( $bloque_final =~ /Event:/ ) { log_debug( "Hay Event en el bloque", 1 ); my @lineas = split( /\r\n/, $bloque_final ); @bloque = (); my $contador = 0; foreach $p (@lineas) { log_debug( "Parseo linea: $p", 4 ); if ( $p =~ /Event:/ ) { log_debug( "Detectado evento! $p", 2 ); $contador++; } my ( $atributo, $valor ) = split( /: /, $p ); if ( defined $atributo && $atributo ne "" ) { $bloque[$contador]{"$atributo"} = $valor; } } log_debug( "Hay $contador bloques para procesar", 4 ); @respuestas = (); log_debug( "Respuestas vaciadas", 4 ); @respuestas = digiere_el_bloque_y_devuelve_array_de_respuestas(@bloque); my $cuantas = @respuestas; } elsif ( $i =~ /--END/ ) { log_debug( "El bloque tiene END en el texto", 1 ); $todo .= $bloque_final; procesa_comando($todo); my $cuantos = @bloque; log_debug( "Hay $cuantos bloques para procesar", 1 ); @respuestas = digiere_el_bloque_y_devuelve_array_de_respuestas(@bloque); $todo = ""; } else { log_debug( "No tiene event ni END, vacio los datos", 1 ); # No tiene Event en el texto, vacio los datos @bloque = (); $todo .= $bloque_final; } } else { log_debug( "Bloque else para escribir a cliente flash", 2 ); my $cuantas = @respuestas; # Escribe mensajes a los clientes Flash # Recibio algo de un cilente? # $print "Recibi de un cliente $i\n"; foreach my $valor (@respuestas) { log_debug( "Escribo respuesta al cliente: $valor", 2 ); my $T = syswrite( $C, $valor, length($valor) ); } # cierra foreach respuestas } } # cierra el foreach handles if ( $i =~ /^ can read } # while can read } # endless loop sub digiere_el_bloque_y_devuelve_array_de_respuestas { log_debug( "---- Empieza Digiere bloques ----", 1 ); my $bloque = shift; my @respuestas = (); my $canal = ""; my $quehace = ""; my $dos = ""; my $uniqueid = ""; my $canalid = ""; my $quehay = ""; my $mensaje = ""; my $interno = ""; my $mensajefinal; my $cuantas; foreach my $blaque (@bloque) { log_debug( "Voy a procesar un bloque", 2 ); $mensaje = procesa_bloque($blaque); delete $datos{""}; ( $canal, $quehace, $dos, $uniqueid, $canalid ) = split( /\|/, $mensaje ); if ( !defined($canal) ) { $canal = ""; } if ( !defined($quehace) ) { $quehace = ""; } if ( !defined($dos) ) { $dos = ""; } log_debug("Canal $canal en digiere!",1); if ( $canal ne "" ) { $interno = $botones{$canal}; while ( my ($pepe,$papo) = each(%botones)) { print "$pepe $papo\n"; } log_debug("Interno $interno en digiere!",1); $interno = "" if ( !defined($interno) ); $mensajefinal = "\0"; if ( $quehace eq 'corto' ) { delete $datos{$uniqueid}; log_debug( "Corta, borra variable $uniqueid", 2 ); } for $quehay ( keys %datos ) { log_debug( "Activos: $quehay", 5 ); while ( my ( $key, $val ) = each( %{ $datos{$quehay} } ) ) { log_debug( " -> $key = $val", 7 ); } } if ( $quehace eq 'corto' ) { borra_todas_las_instancias_del_canal($canalid); } if ( chequea_interno_ocupado($canal) eq "si" && $quehace eq 'corto' ) { log_debug( "Corto pero sigue ocupado!", 2 ); # Como SIP puede transferir por si mismo, quedan zombies en Asterisk # Y no detecta bien el estado del cliente, forzamos un status log_debug( "Forzamos un status", 2 ); syswrite( $p, "Action: Status\r\n\r\n" ); } else { log_debug( "Mensaje final digerido: $mensajefinal", 1 ); push( @respuestas, $mensajefinal ); $cuantas = @respuestas; } } else { # endif canal distinto de nada log_debug( "No esta definida la respuesta", 2 ); } } # cierra foreach bloques foreach my $valor (@respuestas) { log_debug( "R: $valor", 4 ); } log_debug( "---- Termina Digiere bloques ----", 1 ); return @respuestas; } sub procesa_comando_cliente { log_debug( "Proceso el comando provieniente de un cliente flash!", 2 ); my $comando = shift; my $datos = ""; my $accion = ""; my $password = ""; my $valor = ""; my $canal_elegido = ""; my $canal_destino = ""; my $canal; my $nroboton; my $destino; $comando =~ s//$1/g; ( $datos, $accion, $password ) = split( /\|/, $comando ); chop($password); $datos =~ s/_level0\.casilla(\d+)/$1/g; undef $canal_elegido; while ( ( $canal, $nroboton ) = each(%botones) ) { if ( $nroboton eq $datos ) { $canal_elegido = $canal; log_debug( "Si!!", 4 ); } } if ( defined($canal_elegido) ) { if ( "$password" eq "$security_code" ) { log_debug( "El canal elegido es $canal_elegido y la clave coincide", 1 ); if ( $accion eq "cortar" ) { my @cuales_cortar = extraer_todas_las_sesiones_de_un_canal($canal_elegido); foreach $valor (@cuales_cortar) { $comando = "Action: Hangup\r\n"; $comando .= "Channel: $valor\r\n\r\n"; log_debug( "Comando recibido: $accion el $valor", 2 ); syswrite( $p, $comando ); } } elsif ( $accion =~ /^transf/ ) { $destino = $accion; $destino =~ s/transferir//g; while ( ( $canal, $nroboton ) = each(%botones) ) { if ( $nroboton eq $destino ) { $canal_destino = $canal; log_debug( "Si!!", 4 ); } } $canal_destino =~ s/.*\/(\d*)/$1/g; my @cuales_transferir = extraer_todas_las_sesiones_de_un_canal($canal_elegido); foreach $valor (@cuales_transferir) { log_debug( "Voy a transferir el $valor al $canal_destino!", 1 ); $comando = "Action: Redirect\r\n"; $comando .= "Channel: $valor\r\n"; $comando .= "Exten: $canal_destino\r\n"; $comando .= "Priority: 1\r\n\r\n"; syswrite( $p, $comando ); } } } else { log_debug( "La clave no coincide -$password-$security_code-!", 1 ); } } else { log_debug( "No hay canal elegido", 1 ); } } sub procesa_comando { log_debug( "--- Empieza procesa_comando -----\n", 1 ); my $texto = shift; @bloque = (); my @lineas = split( "\n", $texto ); my $contador = 0; my $interno = ""; my $estado = ""; my $nada = ""; foreach my $valor (@lineas) { log_debug( "Linea: $valor", 4 ); $valor =~ s/\s+/ /g; ( $interno, $nada, $nada, $nada, $nada, $estado ) = split( " ", $valor ); if ( defined($estado) && $estado ne "" ) { log_debug( "Estado: $estado", 5 ); $interno =~ s/(.*)\/(.*)/SIP\/$1/g; $bloque[$contador]{"Event"} = "Regstatus"; $bloque[$contador]{"Channel"} = $interno; $bloque[$contador]{"State"} = $estado; $contador++; } } log_debug( "--- Termina procesa_comando - hay $contador bloques ----", 1 ); } sub procesa_bloque { log_debug( "Comienzo subrutina procesa_bloque", 3 ); my $blaque = 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 $canalid = ""; my $key = ""; my $val = ""; my $return = ""; my $conquien = ""; my $enlazado = ""; my $viejo_nombre = ""; my $nuevo_nombre = ""; my $quehay = ""; my $elemento = ""; my $state = ""; undef $unico_id; log_debug( "\n\n--- Empieza Procesa_bloque ---", 2 ); while ( my ( $key, $val ) = each(%bloque) ) { if ( $key eq "Event" ) { $evento = ""; $hash_temporal{$key} = $val; if ( $val =~ /Newchannel/ ) { $evento = "newchannel"; } elsif ( $val =~ /Status/ ) { $evento = "status"; } elsif ( $val =~ /Newexten/ ) { $evento = "newexten"; } elsif ( $val =~ /Newstate/ ) { $evento = "newstate"; } elsif ( $val =~ /Hangup/ ) { $evento = "hangup"; } elsif ( $val =~ /Rename/ ) { $evento = "rename"; } elsif ( $val =~ /Regstatus/ ) { $evento = "regstatus"; } elsif ( $val =~ /Unlink/ ) { $evento = "unlink"; } else { log_debug( "No machea evento ($val)", 2 ); } } else { # Guarda todos los otros datos en un hash nuevo $hash_temporal{$key} = $val; } } $unico_id = ""; $unico_id = $hash_temporal{"Uniqueid"} if defined( $hash_temporal{"Uniqueid"} ); $enlazado = ""; $enlazado = $datos{$unico_id}{"Link"} if defined( $datos{$unico_id}{"Link"} ); $enlazado .= " - " . $datos{$unico_id}{"Context"} if defined( $datos{$unico_id}{"Context"} ); $enlazado .= ":" . $datos{$unico_id}{"Priority"} if defined( $datos{$unico_id}{"Priority"} ); if ( $evento eq "newexten" ) { # Si es una extension nueva sin state, por defecto lo pone en UP $datos{$unico_id}{'State'} = "Up"; } if ( $evento eq "rename" ) { log_debug( "Evento RENOMBRAR!!!!", 2 ); $evento = ""; while ( ( $key, $val ) = each(%hash_temporal) ) { if ( $key =~ /newname/i ) { my $nuevo_nombre = $val; } if ( $key =~ /oldname/i ) { my $viejo_nombre = $val; } } for $quehay ( keys %datos ) { while ( ( $key, $val ) = each( %{ $datos{$quehay} } ) ) { if ( ( $key eq "Channel" ) && ( $val eq $viejo_nombre ) ) { $datos{"$quehay"}{"$key"} = $nuevo_nombre; print "Renombre $viejo_nombre por $nuevo_nombre\n"; } } } } if ( $evento eq "unlink" ) { my $canal1 = $hash_temporal{"Channel1"}; my $canal2 = $hash_temporal{"Channel2"}; borra_todas_las_instancias_del_canal($canal1); borra_todas_las_instancias_del_canal($canal2); log_debug( "Desenlaza $canal1 de $canal2", 2 ); $evento = ""; } if ( $evento ne "" ) { # Ignora eventos link unlink log_debug( "Puso evento en switch $evento", 2 ); while ( my ( $key, $val ) = each(%hash_temporal) ) { $datos{$unico_id}{"$key"} = $val; } if ( $evento eq "hangup" ) { $datos{$unico_id}{'State'} = "Down"; # Acordarse de borrar el array una vez devuelto el mensaje!!!!! # !!!!!!!!!!!!!!!! } log_debug( "Evento " . $datos{$unico_id}{'Event'}, 2 ); # De acuerdo a los datos de la extension genera # la linea con info para el flash $elemento = $datos{$unico_id}{'Channel'} if defined( $datos{$unico_id}{'Channel'} ); $elemento =~ s/(.*)[-\/](.*)/$1\t$2/g; ($canal,$sesion) = split(/\t/,$elemento); $canal =~ tr/a-z/A-Z/ if defined($canal); if ( !defined($canal) ) { $canal = ""; } log_debug( "!!! Canal: $canal", 3 ) if ( defined($canal) ); $exten = $datos{$unico_id}{'Extension'} if ( defined( $datos{$unico_id}{'Extension'} ) ); $clid = $datos{$unico_id}{'Callerid'} if ( defined( $datos{$unico_id}{'Callerid'} ) ); $state = $datos{$unico_id}{'State'} if ( defined( $datos{$unico_id}{'State'} ) ); if ( $state eq "Ring" ) { $texto = "Making call " . $exten; $estado_final = "ocupado"; } if ( $state =~ /^UNK/ ) { $texto = "No registrado " . $exten; $estado_final = "noregistrado"; } if ( $state =~ /^UNR/ ) { $texto = "No alcanzable " . $exten; $estado_final = "unreachable"; } if ( $state =~ /^Unm/ ) { $texto = "Registrado " . $exten; $estado_final = "registrado"; } if ( $state =~ /^OK/ ) { $texto = "No registrado " . $exten; $estado_final = "registrado"; } if ( $state eq "Ringing" ) { $texto = "Incomming call from " . $clid . " " . $enlazado; $estado_final = "ringing"; } if ( $state eq "Down" ) { $canalid = $elemento; $estado_final = "corto"; } if ( $state eq "Up" ) { if ( $exten ne "" ) { $conquien = $exten; } else { $conquien = $clid; } $texto = "Talking to $conquien - $enlazado"; $estado_final = "ocupado"; } # Saca caracteres especiales del caller id $texto =~ s/\"/'/g; $texto =~ s//]/g; $return = "$canal|$estado_final|$texto|$unico_id|$canalid"; } else { log_debug( "No puso evento en switch ($evento)", 2 ); } log_debug( "--- Termina Procesa Bloque -----", 3 ); if ( $canal ne "" ) { return $return; } } sub borra_todas_las_instancias_del_canal { my $canalid = shift; my $quehay = ""; for $quehay ( keys %datos ) { while ( my ( $key, $val ) = each( %{ $datos{$quehay} } ) ) { if ( $val eq $canalid ) { log_debug( "Esta instancia es igual $canalid=$val ($quehay)!!", 2 ); delete $datos{$quehay}; } } } } sub extraer_todas_las_sesiones_de_un_canal { my $canal = shift; my $quehay = ""; my @result = (); for $quehay ( keys %datos ) { while ( my ( $key, $val ) = each( %{ $datos{$quehay} } ) ) { if ( $val =~ /^$canal/i ) { push( @result, $val ); } } } return @result; } sub chequea_interno_ocupado { my $interno = shift; my $return = "no"; my $quehay = ""; my $canal = ""; my $sesion = ""; my $comando = ""; for $quehay ( keys %datos ) { while ( my ( $key, $val ) = each( %{ $datos{$quehay} } ) ) { # print "interno_ocupado $key $val\n"; if ( $key eq "Channel" ) { if ( $val =~ /ZOMBIE/ ) { # Si hay un Zombie trata de matarlo $comando = "Action: Hangup\r\n"; $comando .= "Channel: $val\r\n\r\n"; syswrite( $p, $comando ); log_debug( "ZOMBIE!! Lo mato!! $val", 3 ); } 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( "El interno sigue ocupado $canal $interno!!!!!!", 2 ); } else { log_debug( "$canal $interno no son iguales", 2 ); } } } } } return $return; } sub log_debug { my $texto = shift; my $nivel = shift; print "$texto\n" if $debug >= $nivel; } sub alarma_al_minuto { my $comando = "Action: Command\r\n"; $comando .= "Command: sip show peers\r\n\r\n"; syswrite( $p, $comando ); alarm(120); } sub cierra_todo { log_debug( "Exiting...", 1 ); foreach my $hd ( $O->handles ) { $O->remove($hd); close($hd); } exit(0); }