Table of Contents

SIP registrace

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.

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.

sipclient.rb
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

Ř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