Asterisk Integration von tellows Anruferschutz

Facebooktwitterpinteresttumblrmail

Diese Anleitung wurde uns von Max Grobecker, einem Softwareentwickler für Asterisk-Systeme, zur Verfügung gestellt. An dieser Stelle noch mal vielen Dank an Max für die ausführliche Beschreibung und deine Arbeit, die in dem Projekt steckt. Ein schönes Beispiel, was ein Ziel des Projektes ist, liefert die Demoansage für negativ bewertete Nummern.

Hinweis: Für die Nutzung der tellows-API ist zusätzlich noch ein API-Key (Partnerlogin) nötig, den es hier im tellows-Shop gibt.

Bei Fragen oder Anregungen zu dieser Anleitung schreibt uns bitte eine Mail an [email protected]. Wenn ihr Erweiterungen oder Ideen für andere TK-Anlagen habt, dann würden wir diese natürlich auch gern hier im Blog veröffentlichen.

Präambel:

Ich habe dieses Konstrukt seit Mitte des Jahres so im Einsatz und stetig verbessert.
Inzwischen traut sich tatsächlich kein Callcenter mehr, bei mir anzurufen 😛

Getestet wurde der Dialplan und PHP-Code auf einem
Debian-System mit
Asterisk 1.8.15-certified
PHP 5.3.3

Ob das so noch unter Asterisk 10 oder 11 lauffähig ist, kann ich leider nicht genau sagen,
spätestens ab 11 könnte es jedoch zu kleineren Schwierigkeiten kommen.

Wohin mit den Dateien?

Die PHP-Datei nach /var/lib/asterisk/agi-bin/tellows-api.php

Download PHP-Datei hier

Die Soundfiles entpacken nach /var/lib/asterisk/sounds/de/tellows-grobi/

Download Soundfiles hier

JETZT GEHT ES LOS!

Hinweis vorweg:

  • Mache dir vorher Sicherungen deiner Dateien – ich will keine Klagen hören, wenn jemand dann am Ende mit einer komplett kaputtgefummelten Asterisk da steht und kein Backup hat!
  • Es hat bisher 2-3 False Positives gegeben, bei denen die API den Callcenter-Mitarbeiter meiner Bank verschreckt hat. Wenn du das einbaust, sollte dir klar sein, dass es keine 100%ige Garantie gibt, dass nur Werbung gefiltert wird (und umgekehrt).
  • Ich setze voraus, dass du genug Kentnisse hattest, deine Asterisk dazu zu bringend das zu machen, was sie momentan tut. Bei Lösungen wie Askozia oder AsteriskNow kann es schwierig bis unmöglich werden, die Konfigurationsänderungen durchzuführen! Bitte mache nur weiter, wenn du dir zutraust, diese Änderungen am Dialplan zu machen!
  • Die im Verlauf genannten Extension- oder Kontext-Namen entstammen aus meiner Asterisk-Konfiguration. Wenn sie bei dir anders heißen, heißen sie anders. Bis auf die Macros kannst du eigentlich alles verändern und an deine Installation anpassen resp. musst es auch tun.

Für die extensions.conf

Das ist das Makro, welches 85% der Intelligenz auf Asterisk-Seite erschlägt.
Einfach irgendwo in die extensions.conf werfen oder in eine separate Datei schreiben und diese Inkludieren.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Dieses Makro wird bei eingehenden Anrufen ausgeführt und...
;; ...ruft eine .php-Datei auf, die die anrufende Nummer gegen die API prüft,
;; ...setzt entsprechende Channel-Variablen,
;; ...startet bei zu hohen Scores die Aufnahme,
;; ...spielt bei zu hohen Scores entsprechende Ansagen ein
;;
;; Dieses Macro akzeptiert einen zusätzlichen Parameter:
;; - ARG1: Zweite Rufnummer (Network Provided Number), die ebenfalls
;;         mitgeprüft werden soll. Macht Sinn, da viele Callcenter
;;         eine Screening-Number schicken, deren Voice-Carrier
;;         jedoch die "echte" NPN mitübermittelt.
;;         Das hilft dann bei rotierenden Anruferkennungen
;;         halbwegs zuverlässige Werte zu erhalten.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

[macro-check-caller-reputation]
exten => s,1,NoOp(CALLER-REPUTATION)
same => n,Set(TLW_ANINUM=${ARG1})

same => n,AGI(tellows-api.php,${TLW_ANINUM})  ;; PHP-AGI ausführen und die Nummern prüfen
same => n,NoOp(${TLW_REQUEST})                ;; Debug-Ausgabe, um zu sehen ob der Request erfolgreich war (da sollte "ok" stehen)
same => n,GotoIf($["${TLW_REQUEST}" = "ok"]?reqOK:exit)

same => n(reqOK),NoOp
same => n,GotoIf($[${TLW_SCORE} > 6]?setType:setLoc)   ;; Score höher als 6? SPAM!
same => n(setLoc),Set(CALLERID(name)=${TLW_LOCATION})  ;; Optional (wir haben die Daten jetzt einmal) den Ort des Anrufers als CallerID-Namen setzen
same => n,Goto(exit)


;; Wir finden heraus was man uns andrehen will...

same => n(setType),Set(CALLERID(name)=${TLW_CALLERTYPE})    ;; Anrufertyp als CallerID-Namen setzen. Wenn es das Telefon unterstützt, weiß man direkt woran man ist
same => n,GotoIf($["${TOLOWER(${TLW_CALLERTYPE})}" = "aggr. werbung"]?setAncAggrWerbung)    ;; Einfach so agressive Werbung
same => n,GotoIf($["${TOLOWER(${TLW_CALLERTYPE})}" = "gewinnspiel"]?setAncGewinnspiel)        ;; Gewinnspiele
same => n,GotoIf($["${TOLOWER(${TLW_CALLERTYPE})}" = "meinungsforschung"]?setAncMeinungsforschung)    ;; Meinungsforschung (Auskommentieren, wer gerne daran teilnimmt)
same => n,GotoIf($["${TOLOWER(${TLW_CALLERTYPE})}" = "kostenfalle"]?setAncKostenfalle)        ;; Abofallen
same => n,GotoIf($["${TOLOWER(${TLW_CALLERTYPE})}" = "vermarkter hotline"]?setAncVermarkter)    ;; Schmierige Gebrauchtwagenhändler
same => n,GotoIf($["${TOLOWER(${TLW_CALLERTYPE})}" = "telefonterror"]?setAncAggrWerbung)    ;; Telefonterror wegen dummer Anrufcomputer
same => n,GotoIf($[${TLW_SCORE} > 7]?setAncVermarkter) ;; Kein spezifisches Thema aber Spam? Dann ist es einfach mal Marketing, das ist immer richtig ;-)
same => n,Goto(exit) ;;; Keine Ansage -> Keine Aufzeichnung

;; Hier werden - je nach Anrufertyp - unterschiedliche Ansagen-Files genutzt, der Einfachheit halber schmeißen wir den Namen
;; der abzuspielenden Datei in eine Variable und bedienen uns daraus.
same => n(setAncAggrWerbung),Set(TLW_ANNC_FILE=insc-aggressiver-werbung)
same => n,Goto(playAnc)
same => n(setAncGewinnspiel),Set(TLW_ANNC_FILE=insc-gewinnspielen)
same => n,Goto(playAnc)
same => n(setAncMeinungsforschung),Set(TLW_ANNC_FILE=insc-meinungsforschung)
same => n,Goto(playAnc)
same => n(setAncKostenfalle),Set(TLW_ANNC_FILE=insc-kostenfallen)
same => n,Goto(playAnc)
same => n(setAncVermarkter),Set(TLW_ANNC_FILE=insc-telefon-marketing)
same => n,Goto(playAnc)


;; Es gibt eine Ansage!

same => n(playAnc),NoOp()
same => n,Ringing()              ;; Klingeln signalisieren...
same => n,Wait(${RAND(5,11)})    ;; ...und zwar mit variierender Länge (damit die Anrufcomputer nicht sofort eine Verbindung sehen und auflegen)
same => n,Answer()                ;; Wir gehen dran
same => n,Set(VOLUME(rx)=-999)    ;; Entfernten Kanal stummschalten
same => n,Monitor(wav,tellowsinbound_from-${CALLERID(num)}_to-ext${EXTEN}_dnid${CALLERID(dnid)}_at-${STRFTIME(${EPOCH},,%F_%H-%S-%M)}_${UNIQUEID})    ;; Aufnahme beginnen
same => n,Wait(1)                ;; 1s warten
same => n,Playtones(!425/240,!0/240,!425/240,!0/1280)   ;; Das ist der von der DTAG genormte Aufschalteton, der Mithörer signalisiert.
same => n,Wait(1)                
same => n,Playback(de/tellows-grobi/guten-tag)        ;; "Guten Tag"
same => n,Playback(de/tellows-grobi/ansage-schlechte-reputation-part-1)        ;; "Ihre Rufnummer hat eine seeeeehr schlechte Reputation und wurde oft mit...."
same => n,Playback(de/tellows-grobi/${TLW_ANNC_FILE})                        ;; Anrufertyp
same => n,Playback(de/tellows-grobi/ansage-schlechte-reputation-part-2)        ;; "...in Verbindung gebracht. Blabla... Aufnahme.... Blabla"
same => n,Set(VOLUME(rx)=0)        ;; Entfernten Kanal JETZT auf normale Lautstärke ziehen, ab jetzt nehmen wir auf, was der CCA sagt (denn jetzt weiß er es auch)!
same => n,Wait(2)                ;; Kurz Gelegenheit zum Auflegen geben
same => n,Set(MGR_PLAYBACKANNOUNCEMENT=mA(de/tellows-grobi/dial-callee-record-info-beep))    ;; Eine Variable setzen, mit der wir den ANGERUFENEN darüber informieren dass er aufgezeichnet wird

same => n,Goto(exit)        ;; Fertig!

same => n(exit),MacroExit        ;; Macro-Ende!

Jetzt wird es etwas knifflig: Jetzt muss das Macro möglichst früh bei eingehenden Anrufen aufgerufen werden.
Irgendwo zwischen “wir haben eine saubere Caller-ID” und “das Telefon klingelt” sollten wir also jetzt noch etwas Dialplan unterbringen.
Was hier zu sehen ist, ist teilweise Beispielhaft – ich habe die von mir modifizierten Teile mal farblich dargestellt.

[isdn-incoming]
exten => _X.,1,NoOP(Eingehender Anruf per ISDN von ${CALLERID(all)})
...........
;;; Hiermit fragen wir auf einem ISDN-Kanal die sogenannte NPN (Network Provided Number) ab.
;;; Nähere Details dazu findest du weiter unten
;;; Hinweis: Ich selbst benutze den DAHDI-Treiber, ob das mit Zapata oder Bristuff funktioniert...?
same => n,Set(TELLOWS_ANINUM=${CALLERID(ani-num)})
...........
same => n,Macro(check-caller-reputation,${TELLOWS_ANINUM})  ;; Aufruf des Check-Makros, als Parameter die ermittelte NPN
same => n,Goto(${EXTEN},intern-incoming)

Sobald das passiert ist, haben wir mehrere Channelvariablen zur Verfügung:

${TLW_SCORE}    – Da steht der schlechteste nummerische Score für diesen Anrufer drin

${MGR_PLAYBACKANNOUNCEMENT}    – wenn es zu einer Aufzeichnung kommt, steht hier eine Anweisung für die Asterisk drin, die an den Dial()-Command einfach angehangen wird.
Damit bekommt der Angerufene (auf dem internen Telefon) eine Ansage eingespielt, dass der Anruf aufgenommen wird.
Sollte man verwenden, damit evtl. angefallene Aufnahmen vor Gericht verwendbar bleiben (denn dann wussten beide Seiten von der Aufzeichnung!).

Jetzt sollten wir also die MGR_PLAYBACKANNOUNCEMENT-Variable einfriemeln:

[intern-incoming]
exten => 123456,1,Dial(SIP/123&SIP/987,60,kwtxr${MGR_PLAYBACKANNOUNCEMENT})
.......

Bitte beachten: Da ist KEIN LEERZEICHEN und auch KEIN KOMMA zwischen den vorhandenen Dial-Parametern und dieser Variable.
In der Variable steht – sollte eine Aufzeichnung stattfinden – z.B. folgendes drin:

mA(de/tellows-grobi/dial-callee-record-info-beep)

Parameter “m” für “Spiele Fahrstuhlmusik während das Telefon klingelt” und der Parameter “A” für “Spiele folgende Datei an den ANGERUFENEN Benutzer, sobald er den Anruf annimmt, dann verbinde ihn”.
Die referenzierten Dateien befinden sich mit im Paket und können gerne so genutzt werden. Alternativ darf natürlich jeder auch seine eigenen Ansagen verwenden 😉

Fehlt jetzt nur noch die PHP-Datei, die den API-Aufruf ausführt – hier ist sie:

#!/usr/bin/php5
<?php


// ### DIESE DATEN MÜSSEN VON DIR NOCH AUSGEFÜLLT WERDEN!
$apiPartnerData = array(
                    'username' => 'DEIN_BENUTZERNAME',
                    'apikey' => 'DEIN_APIKEY'
                    );


// ### AGI-Daten von der Asterisk empfangen
$agiinit = array();
while (!feof(STDIN)) 
{
     $agiintmp = trim(fgets(STDIN));
     if ($agiintmp === '') {
         break;
     }
     $agiintmp = explode(':', $agiintmp);
     $agiinit[$agiintmp[0]] = trim($agiintmp[1]);
}

// ### Whitelist mit Nummern die man nie geprüft haben will und die immer durchkommen sollen:
$whitelist = array();
//$whitelist[] = '0123456789';   // Beispiel für Whitelisting-Eintrag


// ### Liste mit Rufnummern, die geprüft werden sollen
$numbersToCheck = array();
$numbersToCheck[] = $agiinit['agi_callerid'];    // ### Caller-ID (num), von der Asterisk per AGI mitgeteilt


// ### Wurde ein Zusatzparameter (NPN) mitübergeben, diesen ebenfalls der Liste der zu prüfenden Nummern anhängen
if(!empty($argv[1]))
    $numbersToCheck[] = trim($argv[1]);


// ### Abfrage für jede Nummer durchführen...
$tmpArr = array();
foreach($numbersToCheck as $num)
{
    $tmpArr[] = makeRequest($num);

    /**
     * Beginnt die Nummer mit "00"?
     * Da einige Callcenter mit kaputten Caller-IDs ankommen, eine weitere Nummer prüfen:
     * Diese Rufnummer, allerdings mit einer 0 weniger am Anfang!
     */
    if(substr($num, 0, 2) == '00')
        $tmpArr[] = makeRequest( substr($num, 1) );

}


// ### Herausfinden, welche Anfragen erfolgreich waren und den Array-Key ermitteln der den höchsten Score erzielt hat
$highestScoreKey = null;
foreach($tmpArr as $numKey => $requestResult)
{
    if($requestResult['request'] == 'ok')
    {
        if($requestResult['score'] > (int)$highestScoreKey)
        {
            $highestScoreKey = $numKey;
        }
    }
}


// ### Sobald der Gewinner feststeht, alle bei der Prüfung berücksichtigten Nummern zusammentragen
$tmpArr[$highestScoreKey]['numbers_given'] = serialize($argv);

// ### Gewinner steht nicht fest? Dann einfach das Ergebnis der ersten Nummer (sichtbare Caller-ID) zurückliefern
if($highestScoreKey === null)
    printAGI($tmpArr[0]);
else  // ### Sonst natürlich die schlechteste Bewertung
    printAGI($tmpArr[$highestScoreKey]);




/**
  * Anfrage gegen die Tellows-API ausführen.
  */
function makeRequest($numberrToBeChecked)
{
    global $whitelist;
    global $apiPartnerData;

    // ### Nummer auf der Whitelist?
    if( in_array($numberrToBeChecked, $whitelist) )
    {
        $agiVars['score'] = 1;
        $agiVars['callertype'] = 'Whitelist';
        $agiVars['location'] = 'Whitelist';
        $agiVars['country'] = 'DE';
        $agiVars['request'] = 'ok';
        $agiVars['num_checked'] = $numberrToBeChecked;

        return $agiVars;
    }


    $baseURL = 'http://www.tellows.de/basic/num/';
    $params = 'xml=1&partner='.$apiPartnerData['username'].'&apikey='.$apiPartnerData['apikey'];
    $finalRequest = $baseURL.$numberrToBeChecked.'?'.$params;


    $agiVars = array();
    $agiVars['request'] = 'failed';


    $ch = curl_init($finalRequest);

    // ### Es wird maximal 1,5s gewartet, bis eine Verbindung zur API besteht und nur weitere 3,2s bis eine Antwort da ist.
    // ### Ist die Internetverbindung gestört oder der API-Server nicht erreichbar, wird ein Anruf nur maximal 4,7s verzögert
    curl_setopt_array($ch, array(
        CURLOPT_DNS_USE_GLOBAL_CACHE => true,
        CURLOPT_FAILONERROR => true,
        CURLOPT_HTTPGET => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT_MS => (1000 * 1.5),
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
        CURLOPT_TIMEOUT_MS => (1000 * 3.2),
        CURLOPT_USERAGENT => 'Asterisk API-Request',
        )
    );

    // ### API-Request ausführen
    $apiValue = curl_exec($ch);
    $curlHTTPC = curl_getinfo($ch, CURLINFO_HTTP_CODE);


    // ### Request erfolgreich?
    if($apiValue !== false && ($curlHTTPC >= 200 && $curlHTTPC < 300) )
    {
        try
        {
            $sxml = new SimpleXMLElement($apiValue);
            $tmpBestCType = array('name' => '', 'count' => 0);

            if(is_object($sxml->callerTypes->caller))
            {
                foreach($sxml->callerTypes->caller as $callertype)
                {
                    if((string) strtolower($callertype->name) == 'unbekannt')
                        continue;
    
                    if((int) $callertype->count > $tmpBestCType['count'])
                        $tmpBestCType = array('name' => (string) $callertype->name, 'count' => (int) $callertype->count);
                }
            }

            // ### Die interessanten Daten aus der XML zusammensuchen, das wird später per AGI an die Asterisk zurückgegeben
            $agiVars['score'] = (int) $sxml->score[0];
            $agiVars['callertype'] = $tmpBestCType['name'];
            $agiVars['location'] = (string) $sxml->location;
            $agiVars['country'] = (string) $sxml->country;
            $agiVars['request'] = 'ok';
            $agiVars['num_checked'] = $numberrToBeChecked;

            // ### "Aggressive Werbung" etwas einkürzen, damit es aufs Telefon-Display passt
            $agiVars['callertype'] = str_replace('Aggressive Werbung', 'Aggr. Werbung', $agiVars['callertype']);
    }
    catch (Exception $e)
    {
        // ### XML konnte nicht geparsed werden!
        $agiVars['request'] = 'failed_unparseable_xml';
    }
}



return $agiVars;
} // function



/**
  * Hilfsfunktion für AGI-Rückgabe an die Asterisk
  */
function printAGI($agiVars)
{
    global $numbersToCheck, $agiinit;

    // ### Jeden einzelnen Schlüssel aus dem übergebenen Array mit dem Präfix "TLW_" versehen und an die Asterisk zurückgeben
    foreach($agiVars as $agiKey => $agiVal)
    {
        printf('SET VARIABLE TLW_%s "%s"', strtoupper($agiKey), $agiVal);
        echo "\n";
        fread(STDIN, 10000);
    }
}

?>

Einfach irgendwo hin legen, wo die Asterisk sie ausführen kann – z.B. nach /var/lib/agi-bin/

Testen

Um das Konstrukt zu testen, könnt ihr einfach in der PHP-Datei ab Zeile 155 hardcoded irgendwelche Werte hinterlegen,
die an die Asterisk weitergegeben werden sollen – z.B.:

// ### Die interessanten Daten aus der XML zusammensuchen, das wird später per AGI an die Asterisk zurückgegeben
$agiVars['score'] = 8
$agiVars['callertype'] = 'gewinnspiel';
$agiVars['location'] = 'Testhausen';
$agiVars['country'] = 'Testland';
$agiVars['request'] = 'ok';
$agiVars['num_checked'] = $numberrToBeChecked;

Damit werden dann aber auf der Stelle ALLE Anrufe als Spam eingestuft, man sollte also tunlichst nicht vergessen, den originalen Zustand nach dem Testen wiederherzustellen!

Etwas zur NPN

In digitalen Telefonnetzen können zwei Rufnummern als Caller-ID übertragen werden:
Einmal die Nummer die der Angerufene angezeigt bekommen soll (User Provided Number) und eine Rufnummer die normalerweise
durch die Vermittlungsstelle gesetzt wird und wirklich mit dem Anschluss verknüpft ist (Network Provided Number).
Ruft nun jemand an und es steht eine 0800-Nummer im Display, findet sich in der Regel über die Network Provided Number leicht heraus,
dass der Anruf z.B. von einer Münchner oder Frankfurter Nummer kommt.

Wie man an diese Daten herankommt hängt schwer von der Anschlussart, vom Anbieter und ggf. auch von verwendeter Hardware ab.

Bei ISDN-Zugängen der meisten Anbieter lässt sich zusammen mit dem DAHDI-Treiber diese Nummer aus der Variable
${CALLERID(ani-num)} auslesen.

Wird VoIP genutzt befindet sich diese Information üblicherweise im “P-Asserted-Identity”-Header und kann mit SIP_HEADER(P-Asserted-Identity) im Dialplan
abgefragt werden.

Ob und wie es bei eurem Anschluss funktioniert müsst ihr am Besten selbst ein wenig heraustüfteln, Fakt ist aber dass damit einige
Callcenter als Spam-Anrufer erkannt wurden, die sonst nicht genug Bewertungen in der Tellows-Datenbank mit der angezeigten Nummer hatten.
Ich habe bei mir das CDR-Logging ebenfalls etwas ausgebaut und lasse nun grundsätzlich diese beiden Daten mitprotokollieren.

Etwas zur Anrufaufzeichnung

Ich bin kein Anwalt, bilde mir aber ein richtig zu handeln.
Der Anrufer wird – wenn es denn Spam ist – automatisiert zu Beginn des Gespräches darauf hingewiesen dass sein Anruf aufgezeichnet wird, der Angerufene ebenfalls.
Während der Ansage ist der Anrufer stummgeschaltet, so dass streng genommen keine Aufnahme erfolgt, bis er mit Ende der Ansage weiß was los ist.
Falls jemand ein juristisches Studium genossen hat und dazu etwas sagen kann, wäre ich für eine Meinung dazu sehr dankbar 🙂

Fußnoten

* “funktionierend” im Sinne von “Nur wenige Anrufer hatten genug Eier in der Hose um dranzubleiben”. Freundlich, aber bestimmt.

Facebooktwitteryoutubeinstagram

5 thoughts on “Asterisk Integration von tellows Anruferschutz

  1. Pingback: Asterisk und FritzBox …

  2. Pingback: Tellows-Screening via Asterisk | blogdoch reloaded

  3. Max Grobecker

    Ich habe das momentan unter 11.6-Cert laufen.
    Da mit Minor-Releases normalerweise keine Änderungen am Verhalten der Software kommen sondern nur Bugfixes sollte das problemlos so laufen.

  4. Dietmar Schultz

    Besten Dank! Unter debian Jessie braucht es noch folgendes Paket: “apt-get install php5-curl”
    Und beim Codeschnipsel zum Testen in Zeile 2 fehlt noch ein Semikolon am Ende. Aber jetzt schaut’s gut aus. Test läuft.

Leave a Reply

Your email address will not be published.