jeudi 31 juillet 2008

Un petit script de synchronisation de repertoires

Ma problématique était plus ou moins simple, permettre a deux serveurs de synchroniser certains répertoires entre eux (un serveur de production et un serveur de backup, le but étant de répercuter toutes les créations et modifications de fichiers sur le serveur de backup)
Donc en fait ce petit script quotidien est simple, il scanne les répertoires que l'on souhaite synchroniser, y détecte les modifications/créations ayant eu lieu dans la journée, crée une archive avec et un fichier de script a exécuter sur le serveur de backup ce qui répercute les modifications.Les transferts ont lieu par ftp:
Voici le code source;

modifs_du_jour.sh ; le script a exécuter tous les jours (dans le cron) sur le serveur de production:

#! /bin/sh
# SL 2008-07-21 Script de synchronisation des repertoires entre la prod et le backup
fichiermodifs=`date +%Y%m%d`'_modifs.sh'
fichiermodifsgz='modifsjour'`date +%Y%m%d`'.tar.gz'
#coordonnees ftp du serveur cible
serveurftp='XXXXXXXXXXX'
userftp='XXXX'
mdpftp='XXXX'
#effacement des modifs de la veille
rm -f -R /db/modifsjour/*
#boucle sur chacun des repertoires que l'on veut synchroniser
for chemin in "/usr/local/scripts/" "/usr/local/apache2/htdocs/" "/bin/*.sh" "/db/factures*" "/db/relancesPDF/" "/db/Archivage/" "/db/contentieux/" "/db/contentieux/" "/db/courriersPDF/" "/db/courriersPDF/" "/db/CR_SiegesPDF/" "/db/tickets/"
do
echo "Traitement de : "$chemin
#creation d'une ligne dans le script qui s'executera sur le serveur de backup avec tous les
#fichiers modifies dans la journee
find $chemin -name "*.*" -mtime 0 | awk 'BEGIN{FS="/"}{print "/bin/sh /bin/repcp.sh \"" $NF "\" \""$0"\""}' >> /db/modifsjour/$fichiermodifs
#copie dans un repertoire de tous les fichiers modifies dans la journee
find $chemin -name "*.*" -mtime 0 | awk '{print "cp \"" $0 "\" /db/modifsjour/"}'|sh
done
cd /db/
#creation de l'archive
tar -cvzf $fichiermodifsgz /db/modifsjour/
#transfert ftp vers le serveur de backup
/usr/bin/ftp -nv $serveurftp < <SCRIPT
quote USER $userftp
quote PASS $mdpftp
binary
prompt
cd modifs
mdelete *.gz
put $fichiermodifsgz
prompt
quit
SCRIPT
rm /db/$fichiermodifsgz

Il reste ensuite a exécuter sur le serveur de backup le script `date +%Y%m%d`'_modifs.sh' qui reconstitue les modifications a partir de l'archive 'modifsjour'`date +%Y%m%d`'.tar.gz'.
Il faut également le code source de repcp.sh qui est une copie récursive qui crée les répertoires si ils n'existent pas (très utile dans le cas de répertoires crées sur le serveur de production).

repcp.sh:

#! /bin/sh
cd /home/ilan/modifs/db/modifsjour/
source="$1"
target="$2"

if [ ! -d `dirname "$target"` ]
then
mkdir -m 755 -p $(dirname "$target")
fi
cp -Rfp "$source" "$target"
exit 0

en espérant vous avoir été utile et en implorant votre indulgence sur les scripts, c'est vraiment pas le langage que j'apprécie!

mercredi 23 juillet 2008

Asterisk 1.4 a bannir pour la production

Juste un petit billet pour largement vous encourager a rester sous Asterisk 1.2.x (et les zaptel libpri qui vont avec) et éviter le 1.4.
Ça fait trois ou quatre fois que j'essaie de passer mes serveurs de production sous 1.4.x (au fur et a mesure de la sortie des nouvelles versions stables) et a chaque fois une merde ; coupures,plantages bref la totale , tout disparaît des que je repasse en version 1.2.x.

jeudi 17 juillet 2008

Ma rencontre avec A2billing la plateforme de cartes prepayees et postpayees d'Asterisk

Le Contexte

Heureux administrateur d'une dizaine de serveurs Asterisk, je me suis vu confier un projet de gestion de comptes prépayés couplé avec notre IPBX Open Source favori.
La configuration étant simple
a priori , le client reçoit le numéro de téléphone du serveur, un code d'accès (ou reconnaissance de son numero appelant) et il peut composer et communiquer dans la limite du crédit lui restant.

La perte de temps idiote

Je me dit dans un premier temps que ça doit être faisable "maison" avec un petit script agi qui va taper dans une base de données pour identifier l'appelant (soit par un code tape soit par son CID) et qui lance la commande Dial qui va bien si il est authentifié (avec une limite de temps maximale correspondant a un calcul basé sur le coût de l'appel et son crédit restant).Une fois l'appel terminé une petite décrémentation de son crédit restant epicetou.
En fait j'y a ai pass
é deux jours et c'etait pas si trivial, je me suis meme carrément fait chier avec plein de petits détails galère.

Eureka

Je me dirige alors vers la recherche d'outils Open Source gérant cela (je sais j'ai fait les choses a l'envers faites pas chier, c'est ma fierté mal placée) franchement pas convaincu de trouver un truc pareil en libre.
Je commence donc a arpenter le wiki de voip-info et a essayer divers trucs plus ou moins réussis, plus ou moins payants, plus ou moins ininstallables.
Et la je tombe sur A2biling ,LE TRUC DE LA MORT QUI TUE SA RACE, le produit PHP/MySQL qui gere non seulement tout ce que je cherche mais meme dix fois plus (prépayé,postpayé,identification par CID,recharge de cartes...)!



Apres 3 saltos arriere, une danse du ventre et 1/4 d'heure d'installation sans encombres (franchement génial le wiki http://wiki.asterisk2billing.org/index.php/Installation_guide , je n'ai fait que le suivre a la lettre ) le truc est direct opérationnel et la c'est franchement la folie au niveau de l'ergonomie et de la simplicité de mise en route.

  • Tu crées tes utilisateurs (cartes) qui sont authentifiés soit par leur CID soit par un code a taper.
  • Tu crées tes cartes prepayées, tout est paramétrable.
  • Tu crées tes offres
  • Tu sors des stats, des factures ...
  • Bref une multitude de fonctionnalités (dont je suis loin d'avoir faite le tour tellement c'est vaste)
Vous avez compris je suis fan de a2billing et je vous encourage vivement a aller le tester

http://www.asterisk2billing.org/cgi-bin/trac.cgi

mercredi 2 juillet 2008

Mes sources d'informations Asterisk

A tout seigneur tout honneur , je me dois de remercier les sources qui me permettent d'avancer avec Asterisk depuis mes premiers pas (3 ans déjà!)

L'incontournable voip-info avec son wiki, bible d'Asterisk
Astrecipes , très sympa aussi
Asterisk France
Asterisk guru
Le topic Asterisk d'HFR
Site Officiel

Sans oublier le lien vers LE DOCUMENT DE RÉFÉRENCE sur Asterisk
Asterisk, the future of Telephony@O'Reilly

Petit script en php utilisant le manager Asterisk pour lancer un appel

Script établissant une communication entre deux extensions

if($_POST['lancer'])

{

$appelle=$_POST['extension'];

$appelant=$_POST['sipuser'];

echo "$appelant vous allez être en communication avec le ".$appelle;

// connexion au manager

$socket = fsockopen("xxxxxx","5038", $errno, $errstr);

fputs($socket, "Action: Login\r\n");

fputs($socket, "UserName: superuser\r\n");

fputs($socket, "Secret: xxxxxx\r\n\r\n");

fgets($socket);

// lancement de l'appel

fputs($socket, "Action: Originate\r\n");

fputs($socket, "Exten: $appelle\r\n");

fputs($socket, "Context: yyyyyyy\r\n");

fputs($socket, "CallerID: $appelle\r\n");

fputs($socket, "Priority: 1\r\n");

fputs($socket, "Channel: SIP/$appelant\r\n\r\n");

fputs($socket, "Action: Logoff\r\n");

}

Commander Asterisk avec des fichiers .call

L'utilisation des fichiers .call avec Asterisk


Il existe une autre manière de lancer des applications avec Asterisk, l’utilisation de fichiers .call.

En déposant dans le répertoire /var/spool/asterisk/outgoing des fichiers correctement formatés, il est possible de déclencher des applications d’Asterisk, qui scanne continuellement ce répertoire.

Exemple 1 : Envoi de SMS via un fichier call

Envoie un SMS au yyyyyyyyy en utilisant la passerelle de bezeq (14974800), nécessite un contexte [smsdial] (voir extensions.conf)

Channel: ZAP/g1/14974800

MaxRetries: 1

RetryTime: 60

WaitTime: 30

Context: smsdial

Extension: yyyyyyyyy

Priority: 1

SetVar: MSG=Test SMS coucou

Il est également possible d’utiliser le programme smsq en ligne de commande pour envoyer un SMS ;

smsq --motx-channel= numero_du_centre sur le channel numero_du_destinanataire "test"

Exemple 2 : Envoi de FAX via un fichier call

Envoie le document stocke dans /var/spool/asterisk/fax/1179405466.6.tif en fax au numéro XXXXXXX

Channel:ZAP/g1/XXXXXX

MaxRetries: 1

WaitTime: 20

Application:txfax

Data:/var/spool/asterisk/fax/1179405466.6.tif|caller

Script , capable de générer des fichier call creeficcall :

#!/bin/sh

# SL 20/05/2007 SCRIPT d'envoi de fax/sms

# cree dans le repertoire outgoing un fichier .call executant l'operation

CONCENTRATEUR_SMS=14974800

CHEMIN_FICHIER_CALL="/var/spool/asterisk/outgoing/"

POOL_FAX="/var/spool/asterisk/fax/"

typemessage=$1

destinataire=$2

corps=$3

if [ $1 = 'SMS' ]

then

echo "Channel: ZAP/g1/"$CONCENTRATEUR_SMS"

MaxRetries: 1

RetryTime: 60

WaitTime: 30

Context: smsdial

Extension: "$2"

Priority: 1

SetVar: MSG="$3 > $CHEMIN_FICHIER_CALL$1"_"$2".call"

fi

if [ $1 = 'FAX' ]

then

echo "Channel: ZAP/g1/"$2"

MaxRetries: 1

WaitTime: 20

Application:txfax

Data:"$POOL_FAX$3"|caller" > $CHEMIN_FICHIER_CALL$1"_"$2".call"

fi

if [ $1 = 'VOICEMAIL' ]

then

echo "Channel: SIP/"$2"

MaxRetries: 1

WaitTime: 20

Set:CHANNEL(language)=fr

Application:VoiceMailMain

Data:"$3 > $CHEMIN_FICHIER_CALL$1"_"$2".call"

fi

Script de montee de fiche CRM avec Asterisk

Voici le code en C# d'un petit module que j'ai écrit , qui se connecte au manager d'Asterisk détecte si l'agent présent sur la machine a répondu a un appel et lance un url en conséquence.
Très utile pour faire monter une fiche client lors d'un appel entrant.
Telechargement : http://rapidshare.com/files/126553896/montee_fiche.rar.html

Code Source:

Le fichier config.xml



Mainform.cs

/*
* SAMUEL LEVY
* Date: 21/01/2007
* Time: 11:23
* monte_fiche.cs
* Script de montee de fiche
* Il recupere l'evenement Link du manager d'Asterisk, recupere le numero de
* l'appel entrant puis lans ce browser pour ouvrir la fiche correspondante
*/
using System.Net;
using System.Xml;
using System.Net.Sockets;
using System.Text.RegularExpressions;
using System.Text;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.Win32;

namespace monte_fiche
{

// SL 21/01/2007
// Classe de connexion au manager d'asterisk
// Gere la reception d'evenements tels les appels entrants
// Permet l'ouverture de la fiche associee au numero entrant
class manager_access
{
private string IPadress;
private int port;
private string utilisateur;
private string password;
private string sipuser;
private string scriptmontee;
private Socket MyAstSocket;
private IPEndPoint MyAstServerEndPoint;

//constructeur par defaut
public manager_access()
{
this.IPadress="";
this.port=0;
this.utilisateur="";
this.password="";
this.sipuser="";
this.scriptmontee="";
}

//constructeur
public manager_access(string t_server,int t_port,string t_utilisateur,
string t_password,string t_sipuser,string t_scriptmontee)
{
this.IPadress=t_server;
this.port=t_port;
this.utilisateur=t_utilisateur;
this.password=t_password;
this.sipuser=t_sipuser;
this.scriptmontee=t_scriptmontee;
}

//constructeur a partir du fichier XML
public manager_access(XmlTextReader FileConf)
{
FileConf.WhitespaceHandling=WhitespaceHandling.None;

//parsing du fichier de conf et ecriture dans les variables de la classe
while(FileConf.Read())
{

if(FileConf.LocalName=="serveur")
{
this.IPadress = FileConf.ReadString();
FileConf.Read();
this.port = Convert.ToInt32(FileConf.ReadString());
FileConf.Read();
this.utilisateur = FileConf.ReadString();
FileConf.Read();
this.password = FileConf.ReadString();
FileConf.Read();
this.scriptmontee = FileConf.ReadString();
FileConf.Read();
this.sipuser = FileConf.ReadString();
}
}
FileConf.Close();

//recuperation de l'user xlite de la machine
//this.sipuser = Recupere_User_Xlite();
}

~manager_access()
{
MyAstSocket.Close();
}

private string[] SplitByString(string testString, string split) {
int offset = 0;
int index = 0;
int[] offsets = new int[testString.Length + 1];

while(index < testString.Length) {
int indexOf = testString.IndexOf(split, index);
if ( indexOf != -1 ) {
offsets[offset++] = indexOf;
index = (indexOf + split.Length);
} else {
index = testString.Length;
}
}

string[] final = new string[offset+1];
if (offset == 0 ) {
final[0] = testString;
} else {
offset--;
final[0] = testString.Substring(0, offsets[0]);
for(int i = 0; i < offset; i++) {
final[i + 1] = testString.Substring(offsets[i] + split.Length, offsets[i+1] - offsets[i] - split.Length);
}
final[offset + 1] = testString.Substring(offsets[offset] + split.Length);
}
return final;
}

private string Recupere_User_Xlite()
{
//recupere dans la base de registre le nom du user X-Lite en cours
RegistryKey rk = Registry.CurrentUser;
RegistryKey rk2 = rk.OpenSubKey("Software\\CounterPathSolutionsInc\\X-Lite");
return rk2.GetValue("general:Username").ToString();
}
//Fonction de connexion au socket pour dialoguer avec le manager Asterisk
public void Ast_Connecte()
{
this.MyAstSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
this.MyAstServerEndPoint = new IPEndPoint(IPAddress.Parse(this.IPadress), this.port);
MyAstSocket.Connect(this.MyAstServerEndPoint);

}

//Cette fonction se logue au manager Asterisk et attend un evenement Link
// Si link cela veut dire qu'une communication est etablie pour le user
// on recupere alors le numero et l'on ouvre le browser sur la fiche correspondante
public void Ast_Logue_Et_Ecoute()
{
// Se logue
this.MyAstSocket.Send(Encoding.ASCII.GetBytes("Action: Login\r\nUsername: "+this.utilisateur+"\r\nSecret: "+this.password+"\r\nActionID: 1\r\n\r\n"));
int bytesRead = 0;
char[] delimiterChars = { ' ', '\n' };
string delimiterChars2 = "\r\n\r\n";
do
{
int affiche = 0;
byte[] buffer = new byte[1024];
bytesRead = this.MyAstSocket.Receive(buffer);
string response = Encoding.ASCII.GetString(buffer, 0, bytesRead);

string[] blocs = SplitByString(response,delimiterChars2);
//string[] blocs = response.Split(delimiterChars2);
foreach(string bloc in blocs)
{
string[] reponseclatees = bloc.Split(delimiterChars);

if(Regex.Match(bloc, sipuser, RegexOptions.IgnoreCase).Success
&& Regex.Match(bloc, "Link", RegexOptions.IgnoreCase).Success
&& !Regex.Match(bloc, "UnLink", RegexOptions.IgnoreCase).Success
&& !(Regex.Match(bloc, "Channel2: SIP", RegexOptions.IgnoreCase).Success
&& Regex.Match(bloc, "Channel1: SIP", RegexOptions.IgnoreCase).Success
)
)
{
foreach(string s in reponseclatees)
{
if(affiche == 1)
{
//Si appel entrants lance le browser
if(!Regex.Match(s, sipuser, RegexOptions.IgnoreCase).Success)
{
affiche = 0;
//Lancement du browser et de la fiche
System.Diagnostics.Process.Start(scriptmontee+s);
affiche = 0;
// reponseclatees = null;
// break;
}
//appel sortant ne lance pas le browser
else
{
affiche = 0;
}
}
if(Regex.Match(s, "CallerID1:", RegexOptions.IgnoreCase).Success)
//if(Regex.Match(s, "CallerID2:", RegexOptions.IgnoreCase).Success)
//if(Regex.Match(s, "Channel2:", RegexOptions.IgnoreCase).Success)
{
affiche = 1;
}
else
{
affiche = 0;
}
}
}

if(Regex.Match(bloc, "Message: Authentication accepted", RegexOptions.IgnoreCase).Success)
{
// Send a ping request the asterisk server will send back a pong response.
this.MyAstSocket.Send(Encoding.ASCII.GetBytes("Action: Events\r\nEventMask: ON\r\n\r\n"));
}
}
}while(bytesRead != 0);
}

//accesseurs
public string a_serveur
{
get
{
return this.IPadress;
}
set
{
this.IPadress = value;
}
}
public int a_port
{
get
{
return this.port;
}
set
{
this.port = value;
}
}

public string a_utilisateur
{
get
{
return this.utilisateur;
}
set
{
this.utilisateur = value;
}
}

public string a_password
{
get
{
return this.password;
}
set
{
this.password = value;
}
}

public string a_sipuser
{
get
{
return this.sipuser;
}
set
{
this.sipuser = value;
}
}

public string a_scriptmontee
{
get
{
return this.scriptmontee;
}
set
{
this.scriptmontee = value;
}
}
//Fin accesseurs

}//Fin Classe manager_access



public partial class MainForm
{
[STAThread]
public static void Main(string[] args)
{
Application.Run(new MainForm());
}

public MainForm()
{
//this.Show();
//this.Hide();
this.Activate();

string configuration_file = "C:\\Program Files\\montee_fiche\\config.xml";
//InitializeComponent();

//lecture de la configuration
XmlTextReader filexml=new XmlTextReader(configuration_file);

//cree une instance de connexion au Manager Asterisk grace au fichier de conf
manager_access MyManager = new manager_access(filexml);

// Se connecte au manager.
MyManager.Ast_Connecte();

// Se met en position d'attente d'evenements du manager
MyManager.Ast_Logue_Et_Ecoute();


}
}
}



Pour fonctionner , ce script doit être compile et installe avec le fichier de config dans C:\Program Files\montee_fiche\.
Il necessite .NET.