Hallo zusammen,
als Referenz poste ich hier mal die relevanten Teile meines momentanen Asterisk Setups.
Insbesondere habe ich kaum relevante Informationen zur Verwendung von Lua als Extension-Sprache gefunden,
daher hoffe ich, dass dies jemand weiterhelfen kann.
Es ist nicht ganz fertig, da ich auf Hardware-technische Schwierigkeiten (CLIP Bug des Telefons) und
Einschränkungen von Asterisk (remote bridging von ge-NAT-teten Telefonen bei dynamischer IP) gestoßen bin.
Der Aufbau momentan:
Asterisk überprüft die anrufende Nummer:
Bei ungewünschten Anrufern in der Datenbank klingelt somit in der Regel nichts, muss erst im Internet nachgeschlagen werden,
so kann es sein, dass die analogen Telefone 1-2x klingeln.
Der Code:
MySQL-Tabellen
sip.conf
rtp.conf
extensions.lua
voicemail.conf
musiconhold.conf
lookup-number Perl-Skript
als Referenz poste ich hier mal die relevanten Teile meines momentanen Asterisk Setups.
Insbesondere habe ich kaum relevante Informationen zur Verwendung von Lua als Extension-Sprache gefunden,
daher hoffe ich, dass dies jemand weiterhelfen kann.
Es ist nicht ganz fertig, da ich auf Hardware-technische Schwierigkeiten (CLIP Bug des Telefons) und
Einschränkungen von Asterisk (remote bridging von ge-NAT-teten Telefonen bei dynamischer IP) gestoßen bin.
Der Aufbau momentan:
- Vodafone DSL
- Easybox 602
- analoge Telefone angeschlossen, funktionieren unabhängig von Asterisk
- zusätzlich meldet sich Asterisk ebenfalls am Vodafone VOIP an, es klingelt bei Asterisk und analogen Telefonen gleichzeitig
- 2 SIP Telefone (fest und WLAN) am Asterisk
- SIP/RTP UDP-Ports fest auf interne Adressen gemappt
- MySQL Datenbank zum Speichern von Anrufen und den Zuordnungen von Nummern
Asterisk überprüft die anrufende Nummer:
- Lookup in der Datenbank
- Lookup im Internet: momentan dasortliche.de und "whocallsme.com", ab 4 Reports
- Reaktion je nach detektiertem Anrufer:
- "ads": Werbung, Asterisk hebt ab, sagt ein wenig was, spielt eine Weile (lizenzfreie) Musik, und geht dann auf Anrufbeantworter-Modus (für die ganz hartnäckigen)
- "dontanswer": Asterisk geht ran und simuliert einen Klingelton
- "hidden", "unknown": klingeln lassen, detektierten Anrufer anzeigen, beim Abheben wird aufgezeichnet
- "family", "job", etc.: klingeln lassen, detektierten Anrufer anzeigen, später evtl. besonderer Klingelton
- später evtl. Klingeln nur bei gefragter Person
Bei ungewünschten Anrufern in der Datenbank klingelt somit in der Regel nichts, muss erst im Internet nachgeschlagen werden,
so kann es sein, dass die analogen Telefone 1-2x klingeln.
Der Code:
MySQL-Tabellen
Code:
CREATE TABLE `numbers` (
`number` varchar(100) NOT NULL,
`type` varchar(50) NOT NULL default 'ads',
`name` varchar(100) NOT NULL default 'Unbekannt',
`for` varchar(100) default 'me',
PRIMARY KEY (`number`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
CREATE TABLE `calls` (
`number` varchar(100) default NULL,
`direction` varchar(10) default 'in',
`date` datetime NOT NULL default '0000-00-00 00:00:00',
`name` varchar(100) NOT NULL,
`type` varchar(100) NOT NULL,
PRIMARY KEY (`date`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
sip.conf
Code:
[general]
context=default ; Default context for incoming calls
realm=192.168.0.3
udpbindaddr=0.0.0.0:5060 ; IP address to bind UDP listen socket to (0.0.0.0 binds to all)
tcpenable=no ; Enable server for incoming TCP connections (default is no)
tcpbindaddr=0.0.0.0 ; IP address for TCP server to bind to (0.0.0.0 binds to all interfaces)
srvlookup=yes ; Enable DNS SRV lookups on outbound calls
disallow=all
useragent=DSL-EasyBox/DSL-EasyBox-20.02.022
rtptimeout=120
rtpholdtimeout=160
language=de
dtmfmode = rfc2833
relaxdtmf=yes
rfc2833compensate=yes
localnet=192.168.0.0/255.255.255.0
externhost=meine.dyndns.org:5069
externrefresh=180
nat=yes
directrtpsetup=no
directmedia=yes
prematuremedia=no
progressinband=no
register => [email protected]:[email protected]/0765432101234~300
[vodafone]
type=peer
host=07654.sip.arcor.de
outboundproxy=07654.sip.arcor.de
fromuser=0765432101234
username=0765432101234
authuser=0765432101234
fromdomain=arcor.de
secret=passwort
remotesecret=passwort
canreinvite=no
insecure=very,port,invite
disallow=all
allow=alaw
allow=g729
allow=g726
qualify=1800
nat=yes
context=fromvodafone
call-limit=1
dtmfmode=rfc2833
mailbox=1
[intern0]
type=friend
username=intern0
secret=123123
host=dynamic
defaultip=192.168.0.1
qualify=yes
canreinvite=no
call-limit=1
dtmfmode=rfc2833
disallow=all
allow=alaw
context=internal
nat=no
mailbox=1
[intern1]
type=friend
username=intern1
secret=123123
host=dynamic
defaultip=192.168.0.70
qualify=yes
canreinvite=yes
call-limit=1
nat=no
dtmfmode=rfc2833
context=internal
disallow=all
allow=alaw
mailbox=1
[intern2]
type=friend
username=intern2
secret=225533
host=dynamic
defaultip=192.168.0.73
qualify=yes
canreinvite=yes
call-limit=1
nat=no
dtmfmode=rfc2833
disallow=all
allow=alaw
context=internal
mailbox=1
rtp.conf
Code:
[general]
rtpstart=20000
rtpend=20049
rtpchecksums=no
extensions.lua
Code:
CONSOLE = "Console/dsp" -- Console interface for demo
IAXINFO = "guest" -- IAXtel username/password
TRUNK = "DAHDI/G2"
TRUNKMSD = 1
require "string"
require "io"
function os.capture(cmd, raw)
local f = assert(io.popen(cmd, 'r'))
local s = assert(f:read('*a'))
f:close()
if raw then return s end
s = string.gsub(s, '^%s+', '')
s = string.gsub(s, '%s+$', '')
s = string.gsub(s, '[\n\r]+', ' ')
return s
end
-- status: book, ads
function lookupNumberInWeb(nr)
local res = os.capture("/opt/asterisk/misc/lookup-number "..nr)
app.noop("lookup result: "..res)
if res == "unknown" then return nil,nil end
local typ = string.sub(res, 1, 4)
local name = string.sub(res, 5)
typ = string.gsub(typ, '^%s+', '')
typ = string.gsub(typ, '%s+$', '')
return typ, name, nil
end
function lookupNumberInDB(nr)
local typ,name,forPerson = querysingle([[SELECT type, name, `for`
FROM numbers
WHERE ']]..nr..[[' LIKE CONCAT(number, '%')
ORDER BY LENGTH(number) DESC]])
if typ == nil then return nil,nil,nil end
return typ,name,forPerson
end
function lookupNumber(nr)
local typ,name,forPerson
if nr == nil then return nil,nil,nil end
app.noop("looking up number "..nr)
typ,name,forPerson = lookupNumberInDB(nr)
if typ ~= nil then return typ,name,forPerson end
typ,name,forPerson = lookupNumberInWeb(nr)
if typ ~= nil then return typ,name,forPerson end
return nil,nil,nil
end
function identifyCaller(name, nr)
app.noop(channel.CALLERID("name"):get())
app.noop(channel.CALLERID("num"):get())
channel.CallerType = "hidden"
channel.CallerName = "unknown"
channel.CallerNr = "unknown"
channel.CallerFor = ""
if nr == nil then return end
if nr == "anonymous" then return end
channel.CallerType = "nr"
channel.CallerNr = nr
local typ,name,forPerson = lookupNumber(nr)
if typ ~= nil then
channel.CallerType = typ
if name ~= nil then channel.CallerName = name end
if forPerson ~= nil then channel.CallerFor = forPerson end
end
end
function connect_database()
if channel.connid.value ~= nil then return end
app.noop("connecting to database")
app.mysql("CONNECT connid localhost asterisk asteriskDBPW asterisk")
end
function query(resname, querystring)
connect_database()
app.mysql("QUERY "..resname.." "..channel.connid.value.." "..querystring)
end
function execute(querystring)
resultName = "executequery"
query(resultName, querystring)
end
function querysingle(querystring)
resultName = "singlequery"
query(resultName, querystring)
v1,v2,v3,v4,v5,v6,v7,v8,v9,v10 = fetch(resultName)
if v1 == nil then v1str = "nil" else v1str = v1 end
app.noop("Result Single Query: "..v1str)
endquery(resultName)
return v1,v2,v3,v4,v5,v6,v7,v8,v9,v10
end
function fetch(resname)
app.mysql("FETCH fetchid "..channel[resname].value.." var1 var2 var3 var4 var5 var6 var7 var8 var9 var10")
vars = {}
for i=1, 10 do
if channel["var"..i] ~= nil then vars[i] = channel["var"..i].value end
end
return unpack(vars)
end
function endquery(resname)
app.mysql("CLEAR "..channel[resname].value)
end
function close_database()
if channel.connid.value == nil then return end
app.noop("closing database connection")
app.mysql("DISCONNECT "..channel.connid.value.."")
end
function say(text)
app.festival(text)
end
function handleinvalid()
nr = channel.INVALID_EXTEN.value;
app.answer()
app.playtones("busy")
app.wait(10)
app.hangup()
end;
function handletimeout()
app.answer()
app.playtones("busy")
app.wait(10)
app.hangup()
end;
function initialize()
end;
function cleanup()
close_database()
app.hangup()
end;
function ringThePhones(timeout, monitor)
if timeout == nil then timeout = 20 end
if monitor == nil then monitor = false end
if monitor then
app.mixmonitor("/data/calls/current.wav", "ab", "mv /data/calls/current.wav \"/data/calls/`date +%Y-%m-%d-%H:%M:%S`.wav\"")
end
-- set the identified caller name
if channel.CallerName ~= nil then
channel.CALLERID("name"):set(channel.CallerName.value)
end
app.dial("SIP/intern0&SIP/intern1&SIP/intern2", timeout, "g")
if channel.DIALSTATUS:get() ~= "ANSWER" then
app.answer()
app.wait(1)
app.VoiceMail(1)
end
end
function recordCall(nr, typ, name, direction)
if nr == nil then nr = "unknown" end
if typ == nil then typ = "unknown" end
if name == nil then name = "unknown" end
execute("INSERT INTO calls (number,direction,date,name,type) VALUES"..
"('"..nr.."', '"..direction.."', NOW(), '"..name.."', '"..typ.."')")
end
function handleExternalCall(name, num)
identifyCaller(name, num)
recordCall(channel.CallerNr.value, channel.CallerType.value, channel.CallerName.value, "in")
local action = {
["hidden"] = function(number, name, forPerson)
ringThePhones(40, true)
end,
["unknown"] = function(number, name, forPerson)
ringThePhones(40, true)
end,
["nr"] = function(number, name, forPerson)
ringThePhones(40, true)
end,
["known"] = function(number, name, forPerson)
ringThePhones(40)
end,
["friend"] = function(number, name, forPerson)
ringThePhones(40)
end,
["family"] = function(number, name, forPerson)
ringThePhones(60)
end,
["job"] = function(number, name, forPerson)
ringThePhones(40)
end,
["ads"] = function(number, name, forPerson)
app.answer()
app.wait(1)
app.background("willkommen")
app.wait(1)
app.background("wenn-sie-warten-werden-sie-automatisch-mit-der-telefonzentrale-verbunden")
app.wait(1)
app.background("vielen-dank")
app.MusicOnHold("default", 20)
app.VoiceMail(1)
app.hangup()
end,
["dontanswer"] = function(number, name, forPerson)
app.answer()
app.playtones("ring")
app.wait(60)
app.playtones("busy")
app.wait(50)
app.hangup()
end,
}
app.noop(channel.CallerType.value)
action[channel.CallerType.value](channel.CallerNr.value, channel.CallerName.value, channel.CallerFor.value)
app.hangup()
end;
function handleAfterDial()
app.noop(channel.DIALSTATUS:get())
if channel.DIALSTATUS:get() == "BUSY" then
app.playtones("busy")
app.wait(10)
end
end;
extensions = {
fromvodafone = {
s = function()
initialize()
handleExternalCall(channel.CALLERID("name"):get(), channel.CALLERID("num"):get())
end;
i = handleinvalid;
t = handletimeout;
h = cleanup;
["<vorwahl><nummer>"] = function()
app.goto("default", "s", 1)
end;
};
internal = {
h = cleanup;
["_X."] = function(c,e)
initialize()
app.answer()
channel.CALLERID("name"):set("<vorwahl><nummer>")
app.dial("SIP/"..e.."@vodafone", 180, "gm")
recordCall(e, "-", "-", "out")
handleAfterDial()
app.hangup()
end;
["*1"] = function ()
initialize()
app.answer()
app.dial("SIP/intern0", 60, "g")
handleAfterDial()
app.hangup()
end;
["*2"] = function ()
initialize()
app.answer()
app.dial("SIP/intern1", 150, "gm")
handleAfterDial()
app.hangup()
end;
["*3"] = function ()
initialize()
app.answer()
app.dial("SIP/intern2", 150, "gm")
handleAfterDial()
app.hangup()
end;
["#"] = function ()
app.VoiceMailMain(1, "s")
end;
["_*9."] = function (context, exten)
local num = string.sub(exten, 3)
handleExternalCall(num, num)
end;
};
default = {
include = {"fromvodafone"};
};
}
voicemail.conf
Code:
[general]
format=wav49|gsm|wav
serveremail=asterisk
attach=yes
maxmsg=999
maxsecs=300
minsecs=3
skipms=3000
maxsilence=10
silencethreshold=128
maxlogins=3
moveheard=yes
emaildateformat=%A, %B %d, %Y at %r
pagerdateformat=%A, %B %d, %Y at %r
sendvoicemail=yes
[zonemessages]
eastern=America/New_York|'vm-received' Q 'digits/at' IMp
central=America/Chicago|'vm-received' Q 'digits/at' IMp
central24=America/Chicago|'vm-received' q 'digits/at' H N 'hours'
military=Zulu|'vm-received' q 'digits/at' H N 'hours' 'phonetic/z_p'
european=Europe/Copenhagen|'vm-received' a d b 'digits/at' HM
[default]
1 => 0,Name,root@localhost
musiconhold.conf
Code:
[general]
[default]
mode=quietmp3
directory=/opt/asterisk/var/lib/asterisk/mohmp3free
digit=#
sort=random
lookup-number Perl-Skript
Code:
#!/usr/bin/perl
use warnings;
no warnings qw(redefine);
use strict;
use POSIX;
use URI;
use List::Util qw[min max];
use LWP 5.64;
use Switch;
use Time::Local;
use Module::Load;
sub getBrowser
{
my $browser;
$browser = LWP::UserAgent->new;
$browser->agent('Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)');
return $browser;
}
sub lookupTelephonebook
{
my $nr = shift;
my $browser = getBrowser();
my $url = "http://dasoertliche.de/Controller?form_name=search_inv&ph=".$nr;
my $resp = $browser->get($url);
return undef unless $resp->is_success;
# get the result count
########################################
my $fullhtml = $resp->content;
print LOGFILE $fullhtml;
if(not ($fullhtml =~ m/vorname=(.*?)&nachname=(.*?)&strasse/))
{
return undef;
}
my $name = $1.$2;
$name =~ s/\+/ /g;
return $name;
}
sub lookupWhoCallsMe
{
my $nr = shift;
my $browser = getBrowser();
my $url = "http://whocallsme.com/Phone-Number.aspx/".$nr;
my $resp = $browser->get($url);
return undef unless $resp->is_success;
# get the result count
########################################
my $fullhtml = $resp->content;
my $cnt = () = ($fullhtml =~ m/class="oos_p2/g);
if($cnt > 3)
{
return 1;
}
return 0;
}
open(LOGFILE, '>>/var/log/ncid.log');
# READ INPUT
my $CIDNMBR = $ARGV[0];
print LOGFILE "Working on $CIDNMBR\n";
my $announcedName = "unknown";
my $name = lookupTelephonebook($CIDNMBR);
if($name)
{
print LOGFILE "Found name $name in telephonebook.\n";
$announcedName = "book ".$name;
}
my $idiot = lookupWhoCallsMe($CIDNMBR);
if($idiot)
{
print LOGFILE "Found in whocallsme.\n";
$announcedName = "ads whocallsme";
}
print LOGFILE "Resolved $CIDNMBR to $announcedName\n";
print $announcedName;
close(LOGFILE);
exit 0