====== Introduction ====== * J'ai de plus en plus d'appels de type "Spam" * Soit des appels en Anonyme * Soit des appels en pas Anonyme mais dont quelque fois, on peut retrouver des traces sur Internet * chez [[http://fr.spampages.info]] * chez [[http://fr.phonespamfilter.com/]] y a meme carrement une doc pour brancher son Asterisk dessus :) * Je suis en France donc j'ai deja de la chance que le CallerId fonctionne, mais faudrait pas qu'on m'envoie le callerName. * Pour cela j'ai les carnets d'adresse de mon GSM et de celui de ma femme stockes sur mon Owncloud * Pour cela j'ai trouvé un site www fort sympatique [[http://french-phone.com/]] qui fait un annuaire inverse ====== Plan d'action ====== - Je ne veux pas dependre d'Internet ie si les sites ne repondent plus => l'appel doit pouvoir etre traité - Je ne desire ne pas solliciter en permance l'Internet => Generation de caches ===== Appels masqués ===== * Je veux que les appels masqués atterissent sur un repondeur special pour si jamais c'etait un etre humain * Si l'appelant appuis sur une touche de son clavier (DTMF) alors, ca passe la barriere ===== Appels Spam / blacklist ===== * Je veux que les appels commerciaux atterissent sur un repondeur special, pour si jamais il y a une erreure ds les blacklistes ===== Carnets d'addresse ===== * Je considere nos carnets d'addresses comme des white listes ===== Pendant un appel accepté ===== * Je desire que pendant l'appel (non reconnu comme un spammeur) on puisse qualifier l'appel: * appuyer sur le 0, et ca deroule toute la procedure d'inscription a la blackliste, envoyer sur un repondeur... * appuyer sur le 1, que ca ne fasse qu'envoyer la vcard par mail de l'appelant (pour proposer l'enregistrement dans l'annuaire) ===== Repondeurs ===== * Peut importe l'appelant, le repondeur doit envoyer les messages ainsi - Le sujet du mail doit contenir la cause du rejet d'appel (SPAM/BlackList/Anonyme/Absence) - Si le numero n'est pas dans les annuaiers interne cela doit renvoyer une VCard dans le mail pour stocker ce numero (Attention cela le blanchit...) - Si le numero n'est pas trouvé sur l'Internet, cela renvoie vers un formulaire web pour generer la vcard - Le mail doit contenir un liens vers une procedure de blacklistage Interne et Internet - Le sujet du mail doit contenir le nom de l'appelant, si connu * Le repondeur lorsqu'il s'enclanche, en fonction des tranches horaires doit se mettre a parler, comme les vieux repondeurs - Mettre les telephones listes pour cela en mode intercom - Pendant que le message d'accueil est lue cote appelant, cote intercom, si le nom est dans l'annuaire, tentative de lecture de celui ci - L'appelant est ensuite mis sur l'intercom, mais ne peut entendre ce qui est dit - Si quelqu'un appuis sur des touches il y a les reaction qui suit : - 1 Decrochage et mise en relation directe - 3 Mise en silence - 0 Mise en silence et envoyé sur le repondeur SPAM - Rien pendant 5 secondes passage en siliencieux - Peut importe le moment si l'on fait un *8 ou autre... on doit pouvoir recuperer l'appel en cours d'enregistrement sur le repondeur - En cas de prise d'appel est envoyé un message avec la partie d'enregistrement faire et la cause de l'enregistrement : prise d'appel en cours ====== Techniquement... ====== FIXME Absolument rien de la partie Repondeur n'est traité FIXME Lorsqu'un numero est dans l'annuaire interne, on lance quand meme les procedure AntiSpam, et c'est mal (mais bien pour mes tests) FIXME Actuellement rien n'est fait sur la partie reverse annuaire sur l'internet FIXME Actuellement rien n'est fait sur la qualification de l'appel en cours ===== Dialplan ===== ==== appel entrant ==== exten => _X.,1,NoOp(Entrant ${EXTEN} CallerId ${CALLERID(all)}) exten => _X.,n,AGI(jabber_notif,"Debut ${STRFTIME(${EPOCH},,%Y%m%d-%H%M%S)}" "${EXTEN}" "${CALLERID(all)}") exten => _X.,n,macro(loadvcard,${CALLERID(num)}) exten => _X.,n,macro(spamcheck, ${CALLERID(num)}, 1) ...Traitement de l'appel Normalement (repondeur, ou pas...) FIXME documenter l'AGI jabber_notif, et meme la faire evoluer pour son parametrage ==== Macro: loadvcard ==== Il y-aura ici autant d'appel a des chargement d'annuaire que necessaire, c'est pourquoi il n'y a qu'une ligne, aujourd'hui [macro-loadvcard] exten => s,1,Noop(Debut de la macro-loadvcard, tel a identifier ${ARG1}) exten => s,n,AGI(VCardReader4Asterisk,"CALLERID(name)",${ARG1},"VCRRET","/usr/local/etc/VCardReader4Asterisk.conf") exten =>s,n,MacroExit ==== Macro: spamcheck ==== [macro-spamcheck] exten => s,1,Noop(Determinig to be a spam or not) exten => s,n,GotoIf($["${CALLERID(num)}" = ""]?NOCID,1:) exten => s,n,AGI(localbwlist,${CALLERID(num)},"LOCALWHITELISTRC","/usr/local/etc/asterisk_whitelist.txt") exten => s,n,GotoIf($["${LOCALWHITELISTRC}" = "0"]?NOSPAM,1:) exten => s,n,AGI(localbwlist,${CALLERID(num)},"LOCALBLACKLISTRC","/usr/local/etc/asterisk_blacklist.txt","LOCALBLACKLISTEX") exten => s,n,Set(SPAMDISCOVER="localblacklist") exten => s,n,Set(SPAMSCODE=${LOCALBLACKLISTEX}) exten => s,n,GotoIf($["${LOCALBLACKLISTRC}" = "0"]?SPAM,1:) exten => s,n,AGI(phonespamfilter.php) exten => s,n,Set(SPAMDISCOVER="phonespamfilter.php") exten => s,n,Set(SPAMSCODE=${PHONESPAMFILTER}) exten => s,n,GotoIf($["${PHONESPAMFILTER}" = "2"]?SPAM,1:) exten => s,n,AGI(fr.spampages.info,${CALLERID(num)},"SPAMPAGEINFORC","SPALPAGEINFOEX") exten => s,n,Set(SPAMDISCOVER="fr.spampages.info") exten => s,n,Set(SPAMSCODE=${SPAMPAGEINFORC}) exten => s,n,GotoIf($["${SPAMPAGEINFORC}" = "0"]?SPAM,1:) exten => s,n,MacroExit exten => NOSPAM,1,MacroExit ; For SPAM calls exten => SPAM,1,Noop(You are a spammer) exten => SPAM,n,AGI(add2localbwlist,${CALLERID(num)},${SPAMDISCOVER},${SPAMSCODE},"localblacklist","/usr/local/etc/asterisk_blacklist.txt") exten => SPAM,n,AGI(jabber_notif,"${CALLERID(num)} is a spammer because : ${SPAMDISCOVER} (${SPAMSCODE})") exten => SPAM,n,Answer() exten => SPAM,n,Wait(1) exten => SPAM,n,Hangup() exten => SPAM,n,MacroExit ; For NOCID calls exten => NOCID,1,Noop(No callerid) exten => NOCID,n,Answer(1) exten => NOCID,n,Wait(1) exten => NOCID,n,Hangup() exten => NOCID,n,MacroExit ===== AGI ===== ==== VCardReader4Asterisk ==== exten => s,n,AGI(VCardReader4Asterisk,"CALLERID(name)",${ARG1},"VCRRET","/usr/local/etc/VCardReader4Asterisk.conf") Information complementaire: * Ce script execute un programme mis dans le fichier de configuration qui doit lui renvoyer un fichier vcard * Dans l'exemple ci-dessous, il genere un export d'un Owncloud, mais cela reste un exemple ! Parametres: - Variable ou sera stoke le nom s'il est trouve * Defaut : CALLERID(name) - Le numero de l'appelant - Variable ou est stocke le code retour pour post traitement * Depend du fichier de configuration - Fichier de configuration * Defaut : /usr/local/etc/VCardReader4Asterisk.conf Fichier de configuration: [returns] code_anonymous=0 code_unknown=1 code_wellknown=2 str_anonymous="Appel Masque" #str_unknown="Inconnu" #str_wellknown="Connu" [vcardaccess] cachettl=0.5 #0.028 == 10min cachefile=/var/lib/asterisk/VCardReader4Asterisk.cache vcardreq="/usr/bin/wget --quiet --timeout=2 --auth-no-challenge --no-clobber --http-user=LOGIN --http-password=PASSWORD --no-check-certificate -O - https://example.lan/owncloud/index.php/apps/contacts/addressbook/local/4/export" #!/usr/bin/perl # License # GPLv2 (http://www.gnu.org/licenses/gpl-2.0.txt) # Authors # DELAPORTE Antoine use strict; use warnings; use Asterisk::AGI; use Text::vCard::Addressbook; use Text::vCard::Node; use Text::vCard; use Storable; use Data::Dumper; use Config::Simple; my $AGI = new Asterisk::AGI; my %input = $AGI->ReadParse(); my $wh2answh=$ARGV[0] || "CALLERID(name)"; my $callerid=$ARGV[1] || ""; my $returnco=$ARGV[2]; my $conffile=$ARGV[3] || "/usr/local/etc/VCardReader4Asterisk.conf"; my %cfg; my $config = new Config::Simple(); if(-s $conffile) { $config->read($conffile); %cfg = $config->vars(); } my $callername; my $returncode; if(defined($cfg{'vcardaccess.vcardreq'}) or defined($cfg{'vcardaccess.cachefile'})) { #Ya un peu de conf, on accepte d'aller plus loin :) if(!defined($cfg{'vcardaccess.cachettl'})) { $cfg{'vcardaccess.cachettl'}=0.028; } if(!defined($cfg{'returns.code_anonymous'})) { $cfg{'returns.code_anonymous'}=10; } if(!defined($cfg{'returns.code_unknown'})) { $cfg{'returns.code_unknown'}=20; } if(!defined($cfg{'returns.code_wellknown'})) { $cfg{'returns.code_wellknown'}=30; } if(!defined($cfg{'returns.str_anonymous'})) { $cfg{'returns.str_anonymous'}="Anonymous"; } if ($callerid eq "") { $callername=$cfg{'returns.str_anonymous'}; $returncode=$cfg{'returns.code_anonymous'}; } elsif(!defined($callername=FromVcard($callerid))) { if(defined($cfg{'returns.str_unknown'})) { $callername=$cfg{'returns.str_unknown'}; } $returncode=$cfg{'returns.code_unknown'}; } else { $returncode=$cfg{'returns.code_wellknown'}; } } else { $returncode=10; } $AGI->set_variable("$wh2answh", "$callername"); if(defined($returnco)) { $AGI->set_variable("$returnco", $returncode); } exit($returncode); sub FromVcard { my ($callerid)=@_; if(CacheIsOld($cfg{'vcardaccess.cachefile'},$cfg{'vcardaccess.cachettl'})) { LoadNewCache($cfg{'vcardaccess.cachefile'},$cfg{'vcardaccess.vcardreq'}) } $callername=FindNumInCache($cfg{'vcardaccess.cachefile'},$callerid); return($callername); } sub FindNumInCache { my ($CacheFile,$callerid)=@_; my %CacheData = %{retrieve($cfg{'vcardaccess.cachefile'})}; return($CacheData{$callerid}); } sub CacheIsOld { my ($CacheFile,$CacheTTL)=@_; my $ret=0; if((! -s $CacheFile) or (-M $CacheFile > $CacheTTL )) { $ret=1; } return($ret); } sub LoadNewCache { my ($CacheFile,$VCardReq)=@_; my $card_text=""; my %CacheData; open(WWW,"$VCardReq |"); while() { $card_text.=$_; } close(WWW); # Create Addressbook my $address_book = Text::vCard::Addressbook->new({ 'source_text' => $card_text, }); my $count=0; #Loop through Addressbook foreach my $vcard ($address_book->vcards()) { my $tels = $vcard->get({ 'node_type' => 'tel' }); if(defined($tels)) { my %tabgr; my $xabs = $vcard->get({ 'node_type' => 'X-ABLABEL' }); if(defined($xabs)) { # Load all the X-ABLABEL in a table to get them faster my $xab; foreach $xab (@{$xabs}) { $tabgr{$xab->group}=$xab->value; } } my $tel; foreach $tel (@{$tels}) { # Boucle sur chaques numeros my $com; my $name; my @nm=$tel->types; if (defined($nm[0])) { $com=$nm[0]; } elsif (defined($tel->group)) { $com=$tabgr{$tel->group}; } else { $com="Other"; } my $telnumber=$tel->value(); $telnumber=~s/\+33/0/g; $telnumber=~s/#31#//g; $telnumber=~s/[^0-9\+]//g; if(!defined($vcard->fullname())) { $name="<$telnumber>"; } else { $name=$vcard->fullname();} $CacheData{$telnumber}= $name."($com)"; $count++; } } } if($count) { store(\%CacheData,$CacheFile); } #On ne stocj pas si on a rien lu } ==== localbwlist ==== exten => s,n,AGI(localbwlist,${CALLERID(num)},"LOCALBLACKLISTRC","/usr/local/etc/asterisk_blacklist.txt","LOCALBLACKLISTEX") #!/usr/bin/perl # License # GPLv2 (http://www.gnu.org/licenses/gpl-2.0.txt) # Authors # DELAPORTE Antoine use Asterisk::AGI; use strict; use warnings; my $AGI = new Asterisk::AGI; my %input = $AGI->ReadParse(); my $returncode=10; my $returnstr; if(!defined($ARGV[0])) { $returncode=2; $returnstr="No number given"; } elsif(!defined($ARGV[1])) { $returncode=3; $returnstr="No comment"; } elsif(!defined($ARGV[2]) or (!-e $ARGV[2])) { $returncode=4; $returnstr="No X-list File"; } if($returncode == 10) { open(BWLF,"< $ARGV[2]") or $returncode=5; if($returncode != 5) { $returnstr="Not in the file"; $returncode=6; while() { if(/^$ARGV[0];/ or /^$ARGV[0]$/) { chomp(); $returncode=0; $returnstr=$_; ### Ici il faudrait trouver l'instruction pour sortir de la boucle en express } } close(BWLF); } else { $returnstr="Pb access X-list File"; } } if(defined($ARGV[1])) { $AGI->set_variable("$ARGV[1]", $returncode); } if(defined($ARGV[3])) { $AGI->set_variable("$ARGV[3]", $returnstr); } exit($returncode); ==== add2localbwlist ==== exten => SPAM,n,AGI(add2localbwlist,${CALLERID(num)},${SPAMDISCOVER},${SPAMSCODE},"localblacklist","/usr/local/etc/asterisk_blacklist.txt") #!/usr/bin/perl # License # GPLv2 (http://www.gnu.org/licenses/gpl-2.0.txt) # Authors # DELAPORTE Antoine use Asterisk::AGI; use POSIX; use strict; use warnings; my $AGI = new Asterisk::AGI; my %input = $AGI->ReadParse(); # 0 Call number # 1 Who want to add # 2 With spam score # 3 in which list (to avoid doublon...) but a fake... # 4 in which file if(defined($ARGV[1]) and defined($ARGV[2])) { if($ARGV[1] ne $ARGV[3]) { if(defined($ARGV[0]) and defined($ARGV[3]) and defined($ARGV[4])) { open(BWF,">>$ARGV[4]") or die; my $nwl=$ARGV[0].";".$ARGV[1]."(".$ARGV[2].");".strftime("%F %T", localtime $^T); print BWF $nwl."\n"; close(BWF); } } } ==== phonespamfilter.php ==== Je vous invite aller voire l'AGI proposé sur le site : FIXME Je la reecrirais un jour, de tel facon a ce qu'elle accpete plus de parametre, dont le callerid ==== fr.spampages.info ==== Je trouve ma facon de travailler pas tres correcte, desole pour eux... :/ Il faudra tout de meme qu'un jour je prenne la peine de les contacter... exten => s,n,AGI(fr.spampages.info,${CALLERID(num)},"SPAMPAGEINFORC","SPALPAGEINFOEX") #!/usr/bin/perl # License # GPLv2 (http://www.gnu.org/licenses/gpl-2.0.txt) # Authors # DELAPORTE Antoine use Asterisk::AGI; use strict; use warnings; use LWP; my $AGI = new Asterisk::AGI; my %input = $AGI->ReadParse(); my $browser = LWP::UserAgent->new; $browser->timeout(2); my $response = $browser->get("http://fr.spampages.info/".$ARGV[0]); my $returncode; my $retstr; $_=$response->status_line; if((!defined($ARGV[0])) or ($ARGV[0] eq "")) { $returncode=4; $retstr="Bad argument"; } elsif(/^500 /) { $returncode=2; $retstr="Service Unavailable"; } elsif(/^200 /) { $returncode=0; $retstr="Number declared"; } elsif(/^404 /) { $returncode=1; $retstr="Number not declared"; } else { $returncode=3; $retstr="Script Lost"; } if(defined($ARGV[1])) { $AGI->set_variable("$ARGV[1]", $returncode); } if(defined($ARGV[2])) { $AGI->set_variable("$ARGV[2]", $retstr); } exit($returncode); ====== Remerciments ====== * Daniel de chez Tootai.net * fr.spampages.info.pl chez qui je vais lachement recuperer des infos * [[https://stebalien.com/posts/2008/06/vcard-viewer-perl-script/|Stebalien]] qui m'a appris a jouer avec les vcard * [[http://tanghus.net/2012/04/backup-owncloud-calendar-and-contacts/|Tanghus]] qui m'a montre comment backup les vcard d'un Owncloud :)