Authentification Radius alternative
Created on 110329 by Yannick Vaucher
Switzernet.com
2 Authentification alternative
Pendant certaines heures du jour, notre serveur principal de comptabilité effectue des tâches gourmandes en ressources qui le rendent indisponible. Il en résulte que le server ne répond plus à la totalité des requêtes Radius qu'il reçoit. Ainsi, certaine tentative d'appel n'aboutisse pas durant ces périodes. Le but de ce projet était donc de garantir l'établissement d'appel durant cette période de non disponibilité de nos services.
L'installation d'un nouveau serveur en ayant pour objectif de résoudre ce manque de réponse Radius est en cours. Cependant, dans l'intervalle de temps qui sera nécessaire à cette opération, nous avons décidé d''améliorer la qualité de notre réseau, en changeant la manière dont les appels sont acceptés par nos serveurs Astrad.
C'est pourquoi nous avons opté pour une solution temporaire d'acceptation des appels par une voie alternative.
Voici le schéma standard d'un appel réussi dans un cas standard ou tout fonctionne correctement.
1. Un téléphone IP envoie une invitation SIP à son serveur d'enregistrement.
2. Le serveur SIP envoie une requête Radius au serveur de comptabilité.
3. Le serveur de comptabilité donne une réponse Radius que le client soit autorisé définissant s'il est en droit de faire un appel ou non, plus toutes les informations de routage pour l'établissement de la connexion avec la destination.
4. En fonction des données du serveur principal, le serveur SIP relaye l'invitation SIP à un autre serveur SIP.
5. L'invitation SIP est relayée jusqu'à sa destination et fera sonner le téléphone.
Nous voyons dans le schéma précédent que le serveur de comptabilité est un point critique qui, s'il n'est pas disponible et ne peut pas fournir des données de routage, empêche de faire des appels. Il est donc nécessaire de fournir un autre serveur qui pourra répondre aux demandes de connexion d'appel lors de l'indisponibilité du serveur principal.
Voici la solution mis en œuvre pour palier à ce problème.
Ci-dessous, le schéma d'utilisation d'un serveur de réplication (db2) en tant que backup du serveur principal.
1. Un téléphone IP envoie une invitation SIP à son serveur d'enregistrement.
2. Le serveur SIP envoie une requête Radius au serveur de comptabilité.
3. Le serveur de comptabilité n'est pas disponible et ne donne pas de réponse Radius.
4. Le serveur SIP après un timeout demande les données d'authentification et de routage à DB2.
5. DB2 donne la réponse souhaitée et le serveur SIP se base sur ces données pour définir son propre choix de routage.
6. En fonction de l'échange entre le server SIP et DB2, l'invitation SIP est relayée à un autre serveur SIP.
7. L'invitation SIP est relayée jusqu'à sa destination et fera sonner le téléphone.
Le script doit gérer 3 contextes différents, c'est-à-dire 3 types d'appel différent.
Ce premier cas de figure est pour le cas ou un appel depuis l'un des comptes enregistré sur le serveur fait un appel. Comme le compte fait alors son entrée dans le réseau par ce serveur SIP, les données du client doivent être vérifiées.
Ici, nous recevons un appel venant d'un autre nœud de notre réseau. Il s'agit donc d'un appel à destination d'un compte enregistré sur le serveur SIP en question.
Ce qui veut dire qu'il faut uniquement s'assurer que le nœud est bien connu. Nous acceptons l'appel provenant de ce nœud sans chercher à vérifier les données de compte car celles-ci ont du être vérifiées par le nœud source.
Ce cas est pour les appels entrant dans notre réseau. Quand un serveur SS7 reçoit un appel, celui-ci va le router sur l'un de nos serveurs SIP au hasard. Et c'est le serveur SIP choisis qui va se charger d'acheminer l'appel vers le bon serveur SIP.
Les appels n'ayant ayant été accepté par DB2 doivent cependant être comptabilisé sur le serveur principal. Cependant, ceci est l'affaire d'un autre script, ast-resend-lost.pl qui s'occupera lui-même de renvoyer les paquets qui n'ont pas pu être envoyés au serveur de comptabilité. De ce fait, nous ne nous intéressons pas dans ce script à la rediffusion des messages Radius. Nous nous contentons de les envoyer en sachant qu'ils seront récupéré par d'autre mécanismes.
Voici le script modifié qui se charge de l'authentification.
Celui-ci à pour rôle d'essayer d'entrer en contact avec le Master, et le cas échéant ou il n'y parvient pas, il contact ensuite le serveur DB2 en effectuant diverses requêtes MySQL.
En gris foncé, le code faisant partie du script d'origine
En gris clair les dernières modifications.
#!/usr/bin/perl
##########################################################################
# #
# Perl agi Script for Authenticate caller via RADIUS #
# #
# Modified on 110325 by yannick.vaucher.switzernet.com #
# #
# Switzernet(c)2011 #
# #
##########################################################################
Déclaration des modules utilisés
# my modules
use Crypt::CBC;
Module utilisé pour interagir avec asterisk
use Asterisk::AGI;
use Authen::Radius;
use Sys::Syslog;
use Switch;
use List::Util;
Gestion des fichiers de log
use Config::IniFiles;
Format de date
use Date::Format;
Format d'heure avec précision en millisecondes
use Time::HiRes qw/gettimeofday/;
Utilisation de base de données
use DBI;
use constant ROUTING => "SIP";
use constant DEBUG => 0;
Codes de retour H323
use constant {H323_CONTINUE => 0,
H323_INVALID_ACC => 1,
H323_ACC_EXPIRED => 5,
H323_CREDIT_LIMIT => 6,
H323_ACC_BLOCKED => 7,
H323_TECH_PROB => 8,
H323_DEST_BLOCKED => 9,
H323_FREE_CALL => 13
};
Positions dans le couple contact
use constant {CONTACT_PREFIX => 1,
CONTACT_IP => 0};
Dossier contenant le fichier de configuration
use constant CONFIG_DIR => "/etc/astrad/config";
Déclaration des variables globales
##########################################################################
# Globals
my $credit_time = -1;
my $hangup = 1;
my $return_code;
my @routes;
my @Expire;
my @Join_Expire;
my $numroutes=0;
my $skip_route=0;
my $iexpires=0;
my %expires;
my $auth_cli;
Définition du format d'affichage des dates dans les logs
my $date_format = "%Y-%m-%d %T";
Démarrage du chrono pour le temps
d'établissement de l'appel par authentification alternative
my ($setuptime1,$setupms1) = gettimeofday();
my ($setuptime2,$setupms2);
my ($setuptime3,$setupms3);
Données relative à DB2
##########################################################################
# Database
my $dbh;
my $LOCAL_HOST;
my $DB_NAME1;
my $DB_NAME2;
my $DB_HOST;
my $DB_USER;
my $DB_PASS;
##########################################################################
# FUNCTION #
##########################################################################
Chargement de la configuration de connexion pour DB2
# Load config
sub load_config {
my $conf=Config::IniFiles->new(-file => CONFIG_DIR . "/switzer.conf");
return 0 if !defined $conf ;
$LOCAL_HOST = $conf->val('GLOBAL','NAS_IP');
$DB_NAME1 = $conf->val('DB2','DB_NAME1');
$DB_NAME2 = $conf->val('DB2','DB_NAME2');
$DB_HOST = $conf->val('DB2','DB_HOST');
$DB_USER = $conf->val('DB2','DB_USER');
$DB_PASS = $conf->val('DB2','DB_PASS');
return 1;
}
sub unescape {
shift() if ref($_[0]);
my $todecode = shift;
return undef unless defined($todecode);
#$todecode =~ tr/+/ /; # pluses become spaces
$todecode =~ s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge;
return $todecode;
}
Fonction d'authentification alternative pour un compte (numéro ou IP) avec db2 retournant le code h323-return-code
##########################################################################
#
# Authenticate the client using db2 instead of Radius.
# Also return client data.
#
# params : - username of client
#
# return : - h323-return-code :
# o H323_TECH_PROB if database is unavailable
# o H323_INVALID_ACC if acc doesn't exist in db
# o H323_ACC_BLOCKED if acc or customer is blocked
# o H323_ACC_EXPIRED if acc is expired
# o H323_CONTINUE else
#
# - credit state :
# o 1 if client has credits
# o 0 else
#
sub alternative_acc_auth {
Récupération des valeurs passées en paramètre :
- Numéro de l'appelant
my $cli = @_[0];
Retourner une erreur si la base de données est inaccessible
if (not defined $dbh){
# Not available
syslog('err', "Ast-Aut: Could not connect to database $DB_NAME1 on $DB_HOST");
return (H323_TECH_PROB);
}
La partie ci-dessous s'occupe de récupérer les
informations
définissant si le client à le droit de faire un appel
###########################################################################
#
# Check the caller to know if he is allowed to call or if he is blocked
#
my $request="SELECT c.balance < c.credit_limit AS 'has_credits', "
." (c.blocked = 'Y' OR a.blocked = 'Y') AS 'is_blocked', "
." (a.expiration_date < CURDATE()) or false AS 'is_expired' "
." FROM Accounts a "
." INNER JOIN Customers c "
." ON (a.i_customer = c.i_customer) "
." WHERE a.id='$cli';";
my $sth = $dbh->prepare($request);
$sth->execute();
Si le compte du client n'est pas trouvé terminer avec le code de retour "Invalid Account"
return (H323_INVALID_ACC) unless($debug_data = $sth->fetchrow_hashref());
my $has_credits = $result1->{has_credits};
Cette partie permet d'afficher des informations plus détaillées sur les droits du client
Ceci seulement si la variable le script est en mode debug
my @debug_data;
# Additional log info
if (DEBUG) {
# if in debug mode request more details
my $dbg_req="SELECT c.balance AS 'balance', c.credit_limit AS 'credit_limit',"
." c.blocked AS 'cust_blocked', a.blocked AS 'acc_blocked',"
." a.expiration_date AS 'expires', CURDATE() AS 'cur_date'"
." FROM Accounts a"
." INNER JOIN Customers c"
." ON (a.i_customer = c.i_customer)"
." WHERE a.id='$cli';";
$sth = $dbh->prepare($dbg_req);
$sth->execute();
my $debug_data;
return (H323_INVALID_ACC) unless(@debug_data = $sth->fetchrow_hashref());
}
Si le compte ou le client est bloqué, terminer avec le code de retour "Account Blocked"
# Account or customer is blocked
return (H323_ACC_BLOCKED) if ($result1->{is_blocked});
Si le compte est expire, terminer avec le code de retour "Account Expired"
# Account is expired
return (H323_ACC_EXPIRED) if ($result1->{is_expired});
Sinon, il n'y a eu aucune erreur, on retourne "Continue call"
return (H323_CONTINUE,$has_credits);
}
Fonction d'authentification alternative pour un compte (numéro ou IP) avec db2 retournant le code h323-return-code
###############################################################################
#
# Authenticate a node using db2 instead of Radius
#
# params : - ip of node
#
# return : - h323-return-code :
# o H323_TECH_PROB if database is unavailable
# o H323_INVALID_ACC if acc doesn't exist in db
# o H323_ACC_BLOCKED if acc or customer is blocked
# o H323_ACC_EXPIRED if acc is expired
# o H323_CONTINUE else
#
# - credit state :
# o 1 if client has credits
# o 0 else
#
sub alternative_ip_auth {
my $node = @_[0];
if (not defined $dbh){
# Not available
syslog('err', "Ast-Aut: Could not connect to database $DB_NAME1 on $DB_HOST");
return (H323_TECH_PROB);
}
La partie ci-dessous s'occupe de récupérer les
informations
définissant si le nœud est dans la liste des nœuds autorisés à appeler
###########################################################################
#
# Check if the node is allowed to call
#
my $request="SELECT ip,name
FROM Nodes
WHERE ip = '$node'
AND i_env=1
AND i_node_type=11
AND name not like '%[Deleted]%'";
my $sth = $dbh->prepare($request);
$sth->execute();
Retourner une erreur si le nœud n'apparaît pas dans la liste des nœuds autorisés.
return H323_INVALID_ACC unless (my $result1 = $sth->fetchrow_hashref());
return H323_CONTINUE;
}
###############################################################################
#
# Define the route to destinations
#
# params : - destination
#
# return : - h323 return code
# - formated destination
# - a list of ip to contact for routing
#
sub define_routing_params {
# return values
my ($return_code, $dest, @contacts);
my $dest = @_[0];
Formatage du numéro de destination
Ici nous définissons par défaut que le pays d'origine d'appel est la Suisse
Un 0 seul est donc remplacé par 41
###########################################################################
# Do the regexp rules depending on number called
# It is important to match a format like +cc num
# If it begins by the national prefix (0) replace it by 41
# TODO use customer's defined rules
# remove 00 and +
$dest =~ s/^(00|\+)//;
$dest =~ s/^0/41/;
Ci-dessous, nous déterminons si la destination est un client à nous, c'est à dire un appel interne,
Autrement, cet appel doit être envoyé vers un vendeur
ATTENTION : les destinations externes gratuites ne sont pas considérées
###########################################################################
#
# Check the dest to know if it is a free call or not
#
my $request="SELECT id FROM Accounts WHERE id='$dest';";
$sth = $dbh->prepare($request);
$sth->execute();
my $acc_data = $sth->fetchrow_hashref();
Si le compte existe, il s'agit de l'un de nos clients
my $is_outgoing = not defined $acc_data->{id};
if ($is_outgoing) {
# set outgoing vendors by default
@contacts = (["212.249.15.9","+"], # Verizon 1 +user
["212.249.15.4","+"], # Verizon 2 +user
["212.249.15.5","00"], # Verizon SS7 00user
["217.168.45.4","00"] # Colt SS7 00user
);
$return_code = H323_CONTINUE;
} else {
# Find and define the right SIP server
my $request="SELECT domain FROM `$DB_NAME2`.location
WHERE username='$dest' ORDER BY last_modified";
my $sth = $dbh->prepare($request);
$sth->execute();
my $location = $sth->fetchrow_hashref();
# dest unchanged
@contacts = ([$location ->{domain},""]);
$return_code = H323_FREE_CALL;
}
return ($return_code, $dest, \@contacts);
}
Cette fonction définie les données de routage dans asterisk en utilisant le module AGI
sub set_routing_data {
my $cli = @_[0];
my $dest = @_[1];
my $contacts_ref = @_[2];
my @contacts = @$contacts_ref;
my $dial_info = '';
$num_contacts = @contacts;
$AGI->set_variable('NumRoutes',$num_contacts);
$AGI->set_variable('DNID',$cli);
my $join_expire = 1;
my $expire = 300;
my $XSUM = 0;
foreach my $c (@contacts){
$dial_info .= '&' if $dial_info ne '';
$dial_info .= ROUTING."/".$c->[CONTACT_PREFIX].$dest;
$dial_info .= ":$secret" if defined $route_info{'auth'} && defined $secret;
$dial_info .= ":$authuser" if defined $route_info{'auth'} && defined $authuser;
$dial_info .= '@'.$c->[CONTACT_IP];
my $route = ROUTING."/".$c->[CONTACT_PREFIX].$dest."@".$c->[CONTACT_IP];
$AGI->set_variable("XROUTE_".$join_expire."_".$XSUM, $route);
$AGI->set_variable("XEXP_".$join_expire."_".$XSUM, $expire);
$AGI->set_variable("XCLI_".$join_expire."_".$XSUM, $cli);
syslog('info', "Ast-Aut-Alt: XROUTE_".$join_expire."_".$XSUM."=".$route
." | XEXP_".$join_expire."_".$XSUM."=".$expire
." | XCLI_".$join_expire."_".$XSUM."=".$cli) ;
$XSUM++;
}
$AGI->set_variable("XSUM_$join_expire", $XSUM);
$AGI->set_variable("II", 1);
$AGI->set_variable("XTOT", $join_expire);
$AGI->set_variable('ActiveFolloMe', 1);
syslog('info', "Ast-Aut-Alt: XSUM = $XSUM");
syslog('info', "Ast-Aut-Alt: II = 1");
syslog('info', "Ast-Aut-Alt: XTOT = 1");
$AGI->set_variable('SIP_Username', $cli);
$AGI->set_variable('RADIUS_Status', 'OK');
$AGI->set_variable('Dial_Info', $dial_info) if $dial_info ne '';
$AGI->verbose(" -- AGI full dial info: $dial_info",4);
}
##########################################################################
# main() #
##########################################################################
$AGI = new Asterisk::AGI;
my %input = $AGI->ReadParse();
openlog('ast-auth', 'cons,pid', 'daemon');
syslog('notice', "Ast-Aut: Script Asterisk Authentication Started");
my %params;
my(@pairs) = split(/[&;]/,$ARGV[0]);
my($param,$value);
Copie des arguments fournis dans le fichier extensions.conf lors de l'appel du script.
Ces arguments sont copiés dans un hash "param"
foreach (@pairs) {
($param,$value) = split('=',$_,2);
$param = unescape($param);
$value = unescape($value);
$params{$param}=$value;
}
##########################################################################
# Sending auth. radius packet to portaone server
$hangup = 0 if defined $params{'IfFailed'} && $params{'IfFailed'} =~ /DoNotHangup/i;
Changement du format du H323-ID
my ($a,$b,$c,$d) = ($1,$2,$3,$4) if defined $params{'H323_ID'} && $params{'H323_ID'} =~ /([0-9]+)-([0-9]+)-([0-9]+)-([0-9]+)/;
my $h323_id = sprintf("%08X %08X %08X %08X",$a,$b,$c,$d);
$AGI->verbose("H323 hex: $h323_id");
my $max_duration = $AGI->get_variable('max_duration');
my $r = new Authen::Radius(Host => $AGI->get_variable('RADIUS_Server'), Secret => $AGI->get_variable('RADIUS_Secret'));
if( !defined $r ) {
$AGI->verbose('RADIUS server "'.$AGI->get_variable('RADIUS_Server').'" ERROR', 0);
$AGI->hangup() if $hangup;
syslog('err', "Ast-Aut: Error in Portaone Radius connection");
closelog;
exit;
}
Authen::Radius->load_dictionary;
$input{'callerid'} = $1 if defined $input{'callerid'} && $input{'callerid'} =~ /<(\d*)>/;
$input{'dnid'} = $1 if defined $input{'dnid'} && $input{'dnid'} =~ /<(\d*)>/;
Créer la requête Radius
$r->add_attributes (
{ Name => 'NAS-IP-Address', Value => $AGI->get_variable('NAS_IP_Address') },
{ Name => 'NAS-Port-Name', Value => $input{'channel'} },
{ Name => 'Calling-Station-Id', Value => $input{'callerid'} },
{ Name => 'Called-Station-Id', Value => $input{'dnid'} },
{ Name => 'Cisco-AVPair', Value => "call-id=$input{'uniqueid'}" },
#{ Name => 'Cisco-AVPair', Value => "call-id=$callid" },
{ Name => 'Cisco-AVPair', Value => "h323-conf-id=$h323_id" }
);
S'il s'agit d'un appel SIP sortant, donc un appel initié par un compte enregistré sur ce serveur SIP
if( defined $params{'AuthorizeBy'} && $params{'AuthorizeBy'} =~ /sip/i ) {
my $sip_auth_header = $AGI->get_variable('SIP_Authorization');
if( !defined $sip_auth_header ) {
$AGI->verbose("ERROR Authorization=SIP requested but no SIP Authorization header provided.", 0);
$AGI->set_variable('RADIUS_Status', 'ConfigurationError');
$AGI->hangup() if $hangup;
syslog('err', "Ast-Aut: ERROR Authorization=SIP requested but no SIP Authorization header provided.");
closelog;
exit;
}
$sip_auth_header =~ s/\s*Digest\s*//;
my %sip_auth;
my(@pairs) = split(/,\s?/,$sip_auth_header);
my($param,$value);
foreach (@pairs) {
($param,$value) = split('=',$_,2);
$param = unescape($param);
$value = unescape($value);
$value =~ s/^"//;
$value =~ s/"$//;
$sip_auth{$param}=$value;
}
Completer la requête Radius
$r->add_attributes ( { Name => 'User-Name', Value => $sip_auth{'username'} } );
$r->add_attributes ( { Name => 'Digest-Attributes', Value => 'User-Name = "'.$sip_auth{'username'}.'"' } );
$r->add_attributes ( { Name => 'Digest-Attributes', Value => 'Realm = "'.$sip_auth{'realm'}.'"' } );
$r->add_attributes ( { Name => 'Digest-Attributes', Value => 'Nonce = "'.$sip_auth{'nonce'}.'"' } );
$r->add_attributes ( { Name => 'Digest-Attributes', Value => 'URI = "'.$sip_auth{'uri'}.'"' } );
$r->add_attributes ( { Name => 'Digest-Attributes', Value => 'Method = "INVITE"' } );
$r->add_attributes ( { Name => 'Digest-Attributes', Value => 'Algorithm = "'.$sip_auth{'algorithm'}.'"' } );
$r->add_attributes ( { Name => 'Digest-Response', Value => $sip_auth{'response'} } );
$AGI->set_variable('SIP_Username', $sip_auth{'username'});
}
else {
#TODO looks wrong, check
$r->add_attributes ( { Name => 'User-Name', Value => $input{'accountcode'} } );
$r->add_attributes ( { Name => 'Password', Value => $params{'Password'} } ) if defined $params{'Password'};
}
Ajouter le paramètre H32 remote Address IP
$r->add_attributes ( { Name => 'h323-remote-address', Value => "$params{H323_RADDR}" } ) if defined $params{'H323_RADDR'};
$r->add_attributes ( { Name => 'h323-ivr-out', Value => "PortaBilling_Routing:$params{Routing}" } ) if defined $params{'Routing'};
Envoyer le paquet Radius
$r->send_packet (ACCESS_REQUEST) and $type = $r->recv_packet;
Ici nous vérifions si une réponse à été reçu
if( !defined $type) {
$error = $r->get_error;
Arrêter le chronomètre numéro 2
($setup2time,$setup2ms) = gettimeofday();
###########################################################################
#
# Master is unavailable. Use db2 to authentificate and route the call
#
syslog('info', "Ast-Aut: Master not available => Alternative Authentification for $h323_id");
closelog;
Ouverture du log en changeant le nom de programme pour laisser le filtrage faire son job
openlog('ast-auth-alt', 'cons,pid', 'daemon');
syslog('info', "Ast-Aut-Alt : Call $h323_id");
Connexion à la base de donnée contenant les données des clients
load_config();
$dbh = DBI->connect("DBI:mysql:$DB_NAME1;host=$DB_HOST", $DB_USER, $DB_PASS, {PrintError=>0});
my @debug_data;
Opérations dépendant du contexte, soit :
- Appel sortant nécessitant une authentification d'un compte SIP et un routage vers le bon serveur SIP
- Appel entrant nécessitant une confirmation que le node est de confiance
- Appel d'un compte IP à authentifier et a rerouter vers le bon serveur SIP
# Check in which context we are
switch ($params{'CustomerType'}) {
#####################################
#
# SIP Authentication
#
case("SipAuth") {
($return_code,my $cli_has_credits, @debug_data) = alternative_acc_auth($username);
($return_code,$dest,$contacts) = define_routing_params($input{'dnid'}) if ($return_code == H323_CONTINUE);
Vérification de la limite de crédit, uniquement pour les appels payant
Terminer à moins que l'appel soit gratuit ou que l'utilisateur possède encore du crédit
Le code de retour est "Credit Limit"
# check if customer has credits in case of non-free call
$return_code = H323_CREDIT_LIMIT if ($return_code == H323_CONTINUE and not $cli_has_credits);
}
#####################################
#
# Node Authentication
#
case("PeerAuth") {
$return_code = alternative_ip_auth($username);
if ($return_code == H323_CONTINUE) {
$dest = $input{'dnid'};
my @host = ([$LOCAL_HOST,'']);
$contacts = \@host;
}
}
#####################################
#
# IP Authentication
#
case("IpAuth"){
($return_code,my $void,@debug_data) = alternative_acc_auth($username);
($return_code,$dest,$contacts) = define_routing_params($input{'dnid'}) if ($return_code == H323_CONTINUE);
}
}
$dbh->disconnect();
Communiquer les données à asterisk uniquement si l'appel à été accepté
set_routing_data($input{'callerid'}, $dest, $contacts) if ($return_code == H323_CONTINUE or $return_code == H323_FREE_CALL);
$AGI->set_variable('h323-return-code', $return_code);
Arrêter le chronomètre numéro 3
($setuptime3,$setupms3) = gettimeofday();
Ajout des données de débogage
my $debug_str;
if (defined @debug_data) {
$debug_str = "";
foreach $d (@debug_data){
$debug_str.="," unless $debug_str eq "";
$debug_str.=$d;
}
} else {
# create empty fields
$debug_str = ",,,,,";
}
Insertion d'un log au format CSV des données de l'appel. Ces données seront ensuite filtrées dans le fichier ast-auth-alt.csv
# Log Call ID, CLI, CLD, setup time, connection time, error reason,
# return code
syslog('notice', ",$h323_id,$input{'callerid'},$username,$input{'dnid'},"
.time2str($date_format, $setuptime1).".$setupms1,"
.time2str($date_format, $setuptime2).".$setupms2,"
.time2str($date_format, $setuptime3).".$setupms3,"
."$error,$return_code,"
.$params{H323_RADDR}.",".$params{'CustomerType'}.","
. $debug_str);
Si le code est un code d'erreur on termine l'appel
$AGI->hangup() if $return_code != H323_CONTINUE && $return_code != H323_FREE_CALL && $hangup;
$AGI->set_autohangup($credit_time) if $credit_time != -1;
syslog('info', "Ast-Aut-Alt: return_code = $return_code");
closelog;
exit;
}
##########################################################################
# Treatment reply auth. radius packet from portaone server
my $dial_info = '';
my ($ActiveFolloMe,$TypeFollowMe);
my $Pred_expire=0;
Parcours de tous les attributs present dans la réponse Radius.
for $a ($r->get_attributes) {
$AGI->verbose("---- attr: name=$a->{'Name'} value=$a->{'Value'}", 4);
$AGI->set_variable($a->{'Name'}, $a->{'Value'});
$return_code = $a->{'Value'} if $a->{'Name'} eq 'h323-return-code';
$credit_time = List::Util::min($max_duration,$a->{'Value'}) if $a->{'Name'} eq 'h323-credit-time';
if( $a->{'Name'} eq 'Cisco-AVPair' && $a->{'Value'} =~ /^h323-ivr-in=PortaBilling_Routing:/ ) {
$AGI->verbose(" -- AGI full route: $a->{'Value'}", 4);
#TODO this regexp is very important, double-check
$a->{'Value'} =~ /^h323-ivr-in=PortaBilling_Routing:\s*([^@]*)@([^;]*);?(.*)$/;
my ($dest, $contact) = ($1, $2);
my %route_info;
my(@pairs) = split(/[&;]/,$3);
my($param,$value);
foreach (@pairs) {
($param,$value) = split('=',$_,2);
$param = unescape($param);
$value = unescape($value);
$route_info{$param}=$value;
}
my $cipher = Crypt::CBC->new({
'key' => $AGI->get_variable('RADIUS_Secret'),
'cipher' => 'DES',
'iv' => '$KJh#(}q',
'regenerate_key' => 1, # default true
'padding' => 'oneandzeroes',
'prepend_iv' => 0
});
my ($authuser, $secret) = split(/\0/, $cipher->decrypt(pack("H*",$route_info{'auth'})) );
$ActiveFolloMe=1 if ($route_info{'access-code'} eq 'FOLLOWME');
switch ($route_info{'g-hunt'}) {
case 'all' { $TypeFollowMe='Simultaneous';}
case 'seq' { $TypeFollowMe='Sequential' ;}
}
if ($route_info{'expires'}) {
$skip_route++;
$Pred_expire=$route_info{'expires'};
}
if($dest ne '' and $contact ne '' and $Pred_expire) {
@routes[$numroutes]=$params{'Routing'}."/".$dest."@".$contact;
@clis[$numroutes]=$route_info{'cli'} if defined $route_info{'cli'};
@Join_Expire[$numroutes]=$skip_route;
@Expire[$numroutes]=$Pred_expire;
$numroutes++;
$dial_info .= '&' if $dial_info ne '';
$dial_info .= "$params{Routing}/$dest";
$dial_info .= ":$secret" if defined $route_info{'auth'}
&& defined $secret;
$dial_info .= ":$authuser" if defined $route_info{'auth'}
&& defined $authuser;
$dial_info .= '@'.$contact;
}
else {
$AGI->verbose(" -- AGI route skipped",4);
}
}
if( $a->{'Name'} eq 'Cisco-AVPair'
&& $a->{'Value'} =~ /^h323-ivr-in=PortaBilling_CompleteNumber:/ ) {
$routing_info = $a->{'Value'};
$routing_info =~ s/^h323-ivr-in=PortaBilling_CompleteNumber://;
$AGI->set_variable('DNID',$routing_info);
}
if( $a->{'Name'} eq 'Cisco-AVPair'
&& $a->{'Value'} =~ /^h323-ivr-in=PortaBilling_CLI:/ ) {
$routing_info = $a->{'Value'};
$routing_info =~ s/^h323-ivr-in=PortaBilling_CLI://;
$auth_cli=$routing_info;
}
}
Définition du routage de l'appel à partir des données fournies par la réponse Radius
##########################################################################
# Routing call
$TypeFollowMe='List' if ($ActiveFolloMe && !$TypeFollowMe);
syslog('info', "Ast-Aut: ActiveFolloMe=$ActiveFolloMe -> $TypeFollowMe");
my @XSUM;
$AGI->set_variable('NumRoutes', $numroutes);
for ($i=0;$i<$numroutes;$i++) {
my $cli=$auth_cli;
$cli=@clis[$i] if defined @clis[$i];
$AGI->verbose(" -- AGI set route $i: @routes[$i] CLI: $cli",4);
if (@Join_Expire[$i]) {
@XSUM[@Join_Expire[$i]]=0 if (!defined @XSUM[@Join_Expire[$i]]);
if (@routes[$i] && @Expire[$i] && $cli) {
$AGI->set_variable(
"XROUTE_@Join_Expire[$i]_@XSUM[@Join_Expire[$i]]",
@routes[$i]);
$AGI->set_variable(
"XEXP_@Join_Expire[$i]_@XSUM[@Join_Expire[$i]]",
@Expire[$i]);
$AGI->set_variable(
"XCLI_@Join_Expire[$i]_@XSUM[@Join_Expire[$i]]",
$cli);
syslog('info', "Ast-Aut: XROUTE_@Join_Expire[$i]_@XSUM[@Join_Expire[$i]]=@routes[$i] | XEXP_@Join_Expire[$i]_@XSUM[@Join_Expire[$i]]=@Expire[$i] | XCLI_@Join_Expire[$i]_@XSUM[@Join_Expire[$i]]=$cli") ;
@XSUM[@Join_Expire[$i]]++;
}
}
}
my ($k,$II) = (0,0);
foreach my $vl (@XSUM) {
if ($vl) {
syslog('info', "Ast-Aut: XSUM_$k = $vl");
$AGI->set_variable("XSUM_$k", $vl);
if (!$II) {
$II=$k;
$AGI->set_variable("II", $II);
syslog('info', "Ast-Aut: II = $II");
}
}
$k++;
}
$k = $k - 1 ;
$AGI->set_variable("XTOT", $k);
syslog('info', "Ast-Aut: XTOT = $k");
$AGI->set_variable('ActiveFolloMe', $ActiveFolloMe);
$AGI->set_variable('RADIUS_Status', 'OK');
$AGI->set_variable('Dial_Info', $dial_info) if $dial_info ne '';
syslog('info', "AGI dial info : " . $dial_info);
$AGI->verbose(" -- AGI full dial info: $dial_info",4);
$AGI->hangup() if $return_code != 0 && $return_code != 13 && $hangup;
$AGI->set_autohangup($credit_time) if $credit_time != -1;
syslog('info', "Ast-Aut: return_code = $return_code");
closelog;
exit;
##########################################################################
# END #
##########################################################################
Le script précédemment décrit, est appelé depuis ce fichier dans 3 contextes différents.
Voici les appels de la fonction avec les paramètres qui lui sont donnés.
[auth-ip]
exten => _X.,1,NoOp(-- Inbound Authentication for cusromer IP --)
exten => _X.,n,Gosub(h323-id,${EXTEN},1)
exten => _X.,n,Set(CDR(accountcode)=${SIPCHANINFO(recvip)})
exten => _X.,n,Set(MYCALLID=${SIPCALLID})
exten => _X.,n,Set(CDR(CustomerType)=IpAuth)
exten => _X.,n,agi,agi-rad-auth.pl|Routing=SIP&AuthorizeBy=Account&Password=cisco&H323_ID=${H323_ID}&CustoerType=IpAuth&H323_RADDR=${SIPCHANINFO(recvip)}
exten => _X.,n,Set(CDR(disconnect-reason)=${h323-return-code})
exten => _X.,n,Goto(rad-auth,${EXTEN},1)
;exten => _X.,n,Goto(routing,${EXTEN},1)
[auth-inbound]
exten => _X.,1,NoOp(-- Inbound Authentication --)
exten => _X.,n,Gosub(h323-id,${EXTEN},1)
exten => _X.,n,Set(CDR(accountcode)=${SIPCHANINFO(recvip)})
exten => _X.,n,Set(MYCALLID=${SIPCALLID})
exten => _X.,n,Set(CDR(CustomerType)=PeerAuth)
exten => _X.,n,agi,agi-rad-auth.pl|Routing=SIP&AuthorizeBy=Account&Password=cisco&H323_ID=${H323_ID}&CustoerType=PeerAuth&H323_RADDR=${SIPCHANINFO(recvip)}
exten => _X.,n,Set(CDR(disconnect-reason)=${h323-return-code})
exten => _X.,n,Goto(rad-auth,${EXTEN},1)
;exten => _X.,n,Goto(routing,${EXTEN},1)
[sip-auth]
exten => _X.,1,NoOp(-- SIP Authentication --)
exten => _X.,n,Gosub(h323-id,${EXTEN},1)
exten => _X.,n,Set(SIP_Authorization=${SIP_HEADER(Proxy-Authorization)})
exten => _X.,n,Set(CDR(CustomerType)=SipAuth)
exten => _X.,n,agi,agi-rad-auth.pl|Routing=SIP&AuthorizeBy=SIP&IfFailed=DoNotHangup&H323_ID=${H323_ID}&CustomerType=SipAuth&H323_RADDR=${SIPCHANINFO(recvip)}
exten => _X.,n,Set(CDR(accountcode)=${SIP_Username})
exten => _X.,n,Set(CDR(disconnect-reason)=${h323-return-code})
exten => _X.,n,Goto(rad-auth,${EXTEN},1)
Dans le but de garder une trace des appels utilisant la solution d'authentification alternative, nous avons défini 3 nouveaux fichier de log.
/var/log/asterisk/ast-auth-alt.log
Ce fichier de log contient tous les messages écrits durant l'éxecution de l'authentification alternative.
/var/log/asterisk/ast-auth-alt.err
Ce second fichier de log rassemble uniquement les messages d'erreur tel que les avertissement de la base de donnée inaccessible.
/var/log/asterisk/ast-auth-alt.csv
Ce dernier fichier enregistre les message "notice" qui sont utilisés pour générer une ligne csv de rapport pour chaque appel effectué à travers l'authentification alternative.
Les données dans ce fichier sont les suivantes :
Les données indiquées avec un DEBUG sont des données qui ne sont enregistrée que si le mode de deboguage est activé.
/etc/rsyslog.d/asterisk.conf
:programname, contains, "ast-auth-alt" /var/log/asterisk/ast-auth-alt.log
if $programname contains 'ast-auth-alt' and $syslogseverity-text == 'err' then /var/log/asterisk/ast-auth-alt.err
if $programname contains 'ast-auth-alt' and $syslogseverity-text == 'notice' then /var/log/asterisk/ast-auth-alt.csv
Gestion des messages vocaux d'information dans Asterisk
http://switzernet.com/2/public/100719-asterisk-update/
Installation d'un serveur Asterisk
http://switzernet.com/public/100520-install-asterisk/
Quelques liens à propos de perl
http://www.tizag.com/perlT/perlvariables.php
http://sql-info.de/mysql/examples/Perl-DBI-examples.html
http://www.tizag.com/perlT/perlmysqlquery.php
http://www.regular-expressions.info/perl.html
* * *
Copyright © 2011 by Switzernet