====== SIP registrace ======
Sip registrace je podobná [[https://en.wikipedia.org/wiki/Digest_access_authentication#Overview|http digest autentizaci]]. Aby se nepřenášelo nechráněné heslo, provádí se ve dvou krocích.
Na první REGISTER odpoví SIP proxy server //Not authorized//. Z této odpovědi je použito políčko //nonce// - náhodný řetězec generovaný SIP proxy, se kterým se zašifruje heslo hashovací funkcí MD5 a provede se druhý REGISTER. V druhém REGISTER zašifrované heslo je obsaženo v políčku //response//, což vede k úspěšné registraci.
{{:sip_register_flow.png?nolink|}}
====== Hlavní program ======
Program má demonstrovat co nejednodušším způsobem registraci a reakci na příchozí hovory (INVITE). Program neošetřuje možné problémy s internetem a navazováním/vypadávání TCP spojení. Číslo linky, heslo a doba platnosti registrace v sekundách se nastavuje na začátku skriptu. Po vypršení registrace se automaticky provede přeregistrace.
{{:registrace_ruby_sip_clienta.png?nolink|}}
require 'socket'
require "digest/md5"
$cislo_linky="300100"
$sip_heslo="222eg4bf"
$expires=120
$s = TCPSocket.open("sip.odorik.cz", 5060)
$lokalni_ip_a_port="#{$s.addr[2]}:#{$s.addr[1]}"
def nacti_sip_hlavicky
radek_cislo=0
while (line = $s.gets)!="\r\n" # čteme jednotlivé řádky hlavičky, prázdná řádka znamená konec hlavičky
radek_cislo=radek_cislo+1
if radek_cislo==1 # Podle prvního řádku se pozná, jaký tip SIP zprávy to je - jmenuje se sip request line
puts $sip_request_line=line.dup
if line.start_with?("SIP/2.0 ") # je to response
$sip_request_line="response"
$response_code=/SIP\/2.0 ([0-9]*) /.match(line)[1]
next
end
if line.start_with?("INVITE ") # Volají nám. Na jaké číslo?
$called_phone_number=/([0-9]*)@/.match(line)[1]
next
end
else
if line.start_with?("WWW-Authenticate:")
$nonce=/nonce="([^"]*)"/.match(line)[1]
$digest_realm=/Digest realm="([^"]*)"/.match(line)[1]
next
end
if line.start_with?("Call-ID: ")
$callid=/Call-ID: ([^\s]*)/.match(line)[1]
next
end
if line.start_with?("CSeq: ")
$cseq=/CSeq: ([0-9]*)/.match(line)[1].to_i
next
end
if line.start_with?("Content-Length: ")
$content_length=line.dup.gsub("Content-Length: ","").to_i
next
end
if line.start_with?("From:")
$from_header=/sip:([^@]*)@/.match(line)[1]
next
end
end
end
if $content_length>0
$header_body=$s.read($content_length)
end
end
def registrace_faze1(expires=$expires)
sip= <<-END_HEADERS
REGISTER sip:sip.odorik.cz SIP/2.0
Via: SIP/2.0/TCP #{$lokalni_ip_a_port};branch=z9hG4bK-#{('a'..'z').to_a.shuffle[0,8].join}
From: ;tag=3778c6ce2e0777ceo2
To:
Call-ID: #{$last_reg_callid=(('a'..'z').to_a.shuffle[0,15].join).to_s}
CSeq: 1 REGISTER
Max-Forwards: 69
Contact: ;expires=#{expires}
User-Agent: Ruby script
Content-Length: 0
Allow: ACK, BYE, CANCEL, INFO, INVITE, NOTIFY, OPTIONS, PRACK, REFER, UPDATE, MESSAGE
Supported: 100rel, replaces
END_HEADERS
sip.gsub!(/^[\s\t]*/,"") # odstraním mezery a tabulátory zleva, které zde jsou kvůli přehlednosti
sip.gsub!(/\n/,"\r\n") # HTTP i SIP vymýšleli lidé od Windows, tedy namysleli si dva znaky pro nový řádek místo jednoho
sip<< "\r\n" # konec hlavičky je zakončen prázdným řádkem
# https://www.sitepoint.com/understanding-scope-in-ruby/
$s.write(sip)
$second_reg_stage_expires=expires # druhá fáze registrace bude mít expiraci vždy stejnou jako ta první
end
def registrace_faze2
# viz https://en.wikipedia.org/wiki/Digest_access_authentication#Overview
ha1=Digest::MD5.hexdigest("#{$cislo_linky}:#{$digest_realm}:#{$sip_heslo}")
ha2=Digest::MD5.hexdigest("REGISTER:sip:sip.odorik.cz")
response=Digest::MD5.hexdigest("#{ha1}:#{$nonce}:#{ha2}")
sip= <<-END_HEADERS
REGISTER sip:sip.odorik.cz SIP/2.0
Via: SIP/2.0/TCP #{$lokalni_ip_a_port};branch=z9hG4bK-#{('a'..'z').to_a.shuffle[0,8].join}
From: ;tag=3778c6ce2e0777ceo2
To:
Call-ID: #{$callid}
CSeq: #{$cseq+1} REGISTER
Max-Forwards: 69
Authorization: Digest username="#{$cislo_linky}",realm="sip.odorik.cz",nonce="#{$nonce}",uri="sip:sip.odorik.cz",algorithm=MD5,response="#{response}"
Contact: ;expires=#{$second_reg_stage_expires}
User-Agent: Ruby script
Content-Length: 0
Allow: ACK, BYE, CANCEL, INFO, INVITE, NOTIFY, OPTIONS, PRACK, REFER, UPDATE, MESSAGE
Supported: 100rel, replaces
END_HEADERS
sip.gsub!(/^[\s\t]*/,"")
sip.gsub!(/\n/,"\r\n")
sip<< "\r\n" # konec hlaviček je zakončen prázdným řádkem
$s.write(sip)
end
puts " Pošleme registraci fáze 1"
registrace_faze1
Thread.new { # tohle vlákno čeká na a zpracovává příchozí SIP pakety
while true
nacti_sip_hlavicky
if $sip_request_line=="response" and $response_code=="401" and $last_reg_callid == $callid
puts " Dostali jsme odpověď na fázi registrace 1 - pošleme registrace fáze 2 "
registrace_faze2
end
if $sip_request_line=="response" and $response_code=="200" and $last_reg_callid == $callid
puts " Jsme úspěšně (od)registrováni !!!!"
end
if $sip_request_line.start_with?("INVITE")
puts "volá nám číslo #{$from_header} na číslo #{$called_phone_number} "
end
end
}
Thread.new { # tohle vlákno zajistí novou registraci po expiraci
while true
sleep($expires)
registrace_faze1
end
}
# hlavní vlákno bude obsluhovat menu
def precti_moznosti_menu
sleep 1 # počkáme, až se zpracují předchozí požadavky v jiných vláknech
puts ""
puts "Zadej příkaz a zmáčkněte Enter:"
puts "ukončit program - k"
puts "registrovat znovu - r"
puts "odregistrace - o"
end
precti_moznosti_menu
while (prikaz=gets.chomp)!="k"
if prikaz=="r"
puts "znovu zaregistrujeme"
registrace_faze1
end
if prikaz=="o"
puts "odregistrujeme = zaregistrujeme s nulovou expirací"
registrace_faze1(0)
end
precti_moznosti_menu
end
puts "odregistrujeme = zaregistrujeme s nulovou expirací"
registrace_faze1(0)
sleep 1 # čekáme než proběhne 2. fáze odregistrace v jiných vláknech
$s.close
====== Řešení potíží ======
Sledujte, co se opravdu posílá a co opravdu odpovídá server:
ngrep -W byline host 81.31.45.51 and port 5060
Podrobnosti: http://forum.odorik.cz/viewtopic.php?f=29&t=3513
====== Inspirace ======
https://github.com/ibc/nagios-sip-plugin/blob/master/nagios-sip-plugin.rb