This is an old revision of the document!
Sip registrace je podobná 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.
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 délka registrace se nastavuje na začátku skriptu.
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: <sip:#{$cislo_linky}@sip.odorik.cz>;tag=3778c6ce2e0777ceo2 To: <sip:#{$cislo_linky}@sip.odorik.cz> Call-ID: #{$last_reg_callid=(('a'..'z').to_a.shuffle[0,15].join).to_s} CSeq: 1 REGISTER Max-Forwards: 69 Contact: <sip:<#{$cislo_linky}@#{$lokalni_ip_a_port};transport=tcp>;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: <sip:#{$cislo_linky}@sip.odorik.cz>;tag=3778c6ce2e0777ceo2 To: <sip:#{$cislo_linky}@sip.odorik.cz> 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: <sip:#{$cislo_linky}@#{$lokalni_ip_a_port};transport=tcp>;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
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