Outbound call restriction

makko's picture

Hi,

If it's could be usefull for someone, here is a little AGI script I wrote in order to grant extension's outbound dialing, without aut.
I used the "account" field, so it would be improved as billing capabilities are disabled.
Be careful as this is not a standard @ pattern matching. We only watch the begenning of the dialed number.
All explanations and user guide inside the code.

[code:1]
//
// RESTRICTION D'ACCES SUR ROUTES SORTANTES PROGRAMMABLE PAR EXTENSION
//
// Written By: Bruno MOLTO (makko at wanadoo.fr)
//
//
//
//
// Contrôle d'accès aux routes sortantes pour chaque extension.
// Permet de contrôler poste per poste l'accès aux routes sortantes, sans avoir à saisir le mot de passe systématiquement.
// Si l'accès est interdit, le fonctionnement standard est conservé, et un mot de passe est demandé à l'utilisateur.
// Ceci permet à un administrateur de pouvoir tout de même utiliser un poste restreint.
// Utilise le champ account code :
// - Le billing est donc inutilisable.
//
//
//
// Version 1.0 - le 05/05/2006
// todo:
// Implémenter correctement dans un champ spécifique de la base asterisk
// Implémenter les couples route/pw route quand on aura de la place...
// Gérer les connexions d'agent afin de bloquer/débloquer provisoirement le poste
//
// Arguments d'appel :
// 1er argument : mot de passe affecté à la route
// 2e argument : le numéro que l'utilisateur a composé
// 3e argument : le numéro de priorité où on jumpe si appel OK
// 4e argument : le numéro de priorité où on jumpe si appel interdit
//
//
//
// Fonctionnement :
//
// On utilise le mot de passe paramétrable sur les routes sortantes.
// et le code account code paramétrable sur chaque extension, et normalement utilisé par le billing.
//
// Pour protéger l'accès d'une route, il faut commencer par lui affecter un mot de passe.
//
// Pour le réglage de l'accès de chaque poste (extension), paramétrer le account code comme suit:
//
// p=xxxx,y,ab,cd,ef,p=tttt,y,gh,ij
// xxxx et tttt sont les passwords correspondants aux routes traitées
// y est la stratégie appliquée :
// - Si y=a, (stratégie "accept") on accepte par défaut touts les numéros, sauf ceux qui commencent par les exceptions qui suivent
// - Si y=d, (stratégie "deny") on rejette par défaut touts les numéros, sauf ceux qui commencent par les exceptions qui suivent
// ab,cd et ef décrivent la liste d'exceptions pour le premier mot de passe (donc la première route)
// gh et ij décrivent la liste d'exceptions pour le second mot de passe (donc la deuxième route)
//
// Il n'y a pas de limitation concernant le nbre de routes ou de préfixes d'exception traités.
// La seule limitation est la longueur totale de la chaine qui ne peut excéder 150 caractères (taille du champ "data" ds MySQL)
//
// Exemple :
// p=123,a,06,08,p=5678,d,0466,0467,0468
// La route qui a le pw 123 va accepter tous les numéros, sauf ceux qui commencent par 06 ou 08
// La route qui a le pw 5678 va refuser tous les numéros, sauf ceux qui commencent par 0466,0467 ou 0468.
//
// Note : la recheche d'exception est effectuée de gauche à droite dans la liste.
// La première exception qui correspond est validée.
//
// Attention : afin de simplifier la chaine d'accès, on ne tient compte que du mot de passe de la route,
// pas du couple (route/mot de passe). Ceci signifie qu'il faut impérativement attribuer un mot de passe différent pour
// chaque route pour que la gestion soit correcte.
//
//
// Mise en service :
// Il faut simplement modifier la macro [macro-dialout-trunk] de extensions.conf comme suit :
// (insersion appel agi et décalage des lignes suivantes (et des destinations de saut bien sûr)
//
//

/********************************MODIFICATION MACRO DIALOUT-TRUNK (Freepbx 2.0.1)**************************

;BEFORE
; 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($["${ARG3}" = ""]?3:2) ; arg3 is pattern password
;exten => s,2,Authenticate(${ARG3})
;exten => s,3,Macro(user-callerid)
;exten => s,4,Macro(record-enable,${CALLERID(number)},OUT)
;exten => s,5,Macro(outbound-callerid,${ARG1})
;exten => s,6,Set(GROUP()=OUT_${ARG1})
;exten => s,7,GotoIf($[ ${GROUP_COUNT()} > ${OUTMAXCHANS_${ARG1}} ]?108)
; if we've used up the max channels, continue at (n+101)
;exten => s,8,Set(DIAL_NUMBER=${ARG2})
;exten => s,9,Set(DIAL_TRUNK=${ARG1})
;exten => s,10,AGI(fixlocalprefix) ; this sets DIAL_NUMBER to the proper dial string for this trunk
;exten => s,11,Set(OUTNUM=${OUTPREFIX_${ARG1}}${DIAL_NUMBER}) ; OUTNUM is the final dial number
;exten => s,12,Set(custom=${CUT(OUT_${ARG1},:,1)}) ; Custom trunks are prefixed with "AMP:"
;exten => s,13,GotoIf($["${custom}" = "AMP"]?16)
;exten => s,14,Dial(${OUT_${ARG1}}/${OUTNUM}|120|WT) ; Regular Trunk Dial, allow recording.Modifié BM ajouté T
;exten => s,15,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,16,Set(pre_num=${CUT(OUT_${ARG1},$,1)})
;exten => s,17,Set(the_num=${CUT(OUT_${ARG1},$,2)}) ; this is where we expect to find string OUTNUM
;exten => s,18,Set(post_num=${CUT(OUT_${ARG1},$,3)})
;exten => s,19,GotoIf($["${the_num}" = "OUTNUM"]?20:21) ; if we didn't find "OUTNUM", then skip to Dial
;exten => s,20,Set(the_num=${OUTNUM}) ; replace "OUTNUM" with the actual number to dial
;exten => s,21,Dial(${pre_num:4}${the_num}${post_num})
;exten => s,22,Goto(s-${DIALSTATUS},1)

;exten => s,108,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})

;AFTER
; 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($["${ARG3}" = ""]?4:2) ; arg3 is pattern password
exten => s,2,AGI(checktrunkrights,${ARG3}|${ARG2}|4|3);Verification autorisation accès du poste sur la route
exten => s,3,Authenticate(${ARG3})
exten => s,4,Macro(user-callerid)
exten => s,5,Macro(record-enable,${CALLERID(number)},OUT)
exten => s,6,Macro(outbound-callerid,${ARG1})
exten => s,7,Set(GROUP()=OUT_${ARG1})
exten => s,8,GotoIf($[ ${GROUP_COUNT()} > ${OUTMAXCHANS_${ARG1}} ]?109)
; if we've used up the max channels, continue at (n+101)
exten => s,9,Set(DIAL_NUMBER=${ARG2})
exten => s,10,Set(DIAL_TRUNK=${ARG1})
exten => s,11,AGI(fixlocalprefix) ; this sets DIAL_NUMBER to the proper dial string for this trunk
exten => s,12,Set(OUTNUM=${OUTPREFIX_${ARG1}}${DIAL_NUMBER}) ; OUTNUM is the final dial number
exten => s,13,Set(custom=${CUT(OUT_${ARG1},:,1)}) ; Custom trunks are prefixed with "AMP:"
exten => s,14,GotoIf($["${custom}" = "AMP"]?17)
exten => s,15,Dial(${OUT_${ARG1}}/${OUTNUM}|120|WT) ; Regular Trunk Dial, allow recording.Modifié BM ajouté T
exten => s,16,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,17,Set(pre_num=${CUT(OUT_${ARG1},$,1)})
exten => s,18,Set(the_num=${CUT(OUT_${ARG1},$,2)}) ; this is where we expect to find string OUTNUM
exten => s,19,Set(post_num=${CUT(OUT_${ARG1},$,3)})
exten => s,20,GotoIf($["${the_num}" = "OUTNUM"]?21:22) ; if we didn't find "OUTNUM", then skip to Dial
exten => s,21,Set(the_num=${OUTNUM}) ; replace "OUTNUM" with the actual number to dial
exten => s,22,Dial(${pre_num:4}${the_num}${post_num})
exten => s,23,Goto(s-${DIALSTATUS},1)

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})

********************************MODIFICATION MACRO DIALOUT-TRUNK**************************/

#include
#include
#include
#include

//Prototypes bibliotheque cagi
#include "cagi.h"

//Paramètres de connexion à la base
#define MYSQL_HOST "localhost"
#define MYSQL_DB "asterisk"
#define MYSQL_LOGIN "asteriskuser"
#define MYSQL_PASSWD "amp109"

int main(int argc, char *argv[])
{
//argc contient le nombre d'arguments passés lors de l'appel
//argv est un tableau de chaines contenant les arguments
//ATTENTION : le premier argument est le nom de l'executable

AGI_TOOLS agi;//Objet agi
AGI_CMD_RESULT agires;
char texte [1024];//pour messages debug
int i,j;

char routepw[100];//Mot de passe de la route passé en argument au script agi
char dialnumber[100];//Numéro composé passé en argument au script agi
char account[1024];//Chaine account code définie dans les extensions et passée dans les variables agi
int priok,prinok;//Priorités cibles extraites le la ligne de commande

//Variables temporaires pour extraction des chaines account
char temppassword[100];
char strat;//Stratégie d'autorisation a=accepte tout par défaut/d=refuse tout par défaut
int ok;//sort de la recherche. ok=0 =>appel interdit. ok=1 =>appel ok
int debutex;//flag extraction exception
char except[100];//Chaine d'extraction des exceptions
int outsearch;//Fin de scrutation des exceptions

//Variables pour connexion mySQL
MYSQL mysql;
MYSQL_RES *result;
MYSQL_ROW row;
int baseconnectee=1;
char query[512];
char table[10];//La table varie en fct de la technologie du channel
char extension[10];//Le numéro d'"extension" (la clé dans la table)

//Init agitools
AGITool_Init(&agi);

agi_verbose("Beginning check route rigths",4);

//lecture de la chaine channel dans les variables agi
strcpy(table,AGITool_ListGetVal(agi.agi_vars,"agi_type"));
//On passe en minuscules car c'est ainsi que sont nommées les tables ds la base
for(i=0;i


__________________