# -*- coding: binary -*- module Rex module Proto module IAX2 class Call attr_accessor :client attr_accessor :oseq, :iseq attr_accessor :scall, :dcall attr_accessor :codec, :state attr_accessor :ring_start, :ring_finish attr_accessor :itime attr_accessor :queue attr_accessor :audio_hook attr_accessor :audio_buff attr_accessor :time_limit attr_accessor :busy attr_accessor :caller_name attr_accessor :caller_number attr_accessor :dtmf def initialize(client, src_id) self.client = client self.scall = src_id self.dcall = 0 self.iseq = 0 self.oseq = 0 self.state = nil self.itime = ::Time.now self.queue = ::Queue.new self.audio_buff = [] self.busy = false self.dtmf = '' end def dprint(msg) self.client.dprint(msg) end def wait_for(*stypes) begin ::Timeout.timeout( IAX_DEFAULT_TIMEOUT ) do while (res = self.queue.pop ) if stypes.include?(res[1]) return res end end end rescue ::Timeout::Error return nil end end # Register with the IAX endpoint def register self.client.send_regreq(self) res = wait_for( IAX_SUBTYPE_REGAUTH, IAX_SUBTYPE_REGREJ ) return if not res if res[1] == IAX_SUBTYPE_REGREJ reason = res[2][IAX_IE_REGREJ_CAUSE] || "Unknown Reason" dprint("REGREJ: #{reason}") # Acknowledge the REGREJ self.client.send_ack(self) return end chall = nil if res[2][14] == "\x00\x03" and res[2][IAX_IE_CHALLENGE_DATA] self.dcall = res[0][0] chall = res[2][IAX_IE_CHALLENGE_DATA] end if chall.nil? dprint("REGAUTH: No challenge data received") return end self.client.send_regreq_chall_response(self, chall) res = wait_for( IAX_SUBTYPE_REGACK, IAX_SUBTYPE_REGREJ ) return if not res if res[1] == IAX_SUBTYPE_REGREJ reason = res[2][IAX_IE_REGREJ_CAUSE] || "Unknown Reason" dprint("REGREJ: #{reason}") return end if res[2][IAX_IE_APPARENT_ADDR] r_fam, r_port, r_addr = res[2][IAX_IE_APPARENT_ADDR].unpack('nnA4') r_addr = r_addr.unpack("C*").map{|x| x.to_s }.join(".") dprint("REGACK: Registered from address #{r_addr}:#{r_port}") end # Acknowledge the REGACK self.client.send_ack(self) self.state = :registered true end def dial(number) self.client.send_new(self, number) res = wait_for(IAX_SUBTYPE_AUTHREQ, IAX_SUBTYPE_ACCEPT) return if not res # Handle authentication if its requested if res[1] == IAX_SUBTYPE_AUTHREQ chall = nil if res[2][14] == "\x00\x03" and res[1][15] self.dcall = res[0][0] chall = res[2][15] end self.client.send_authrep_chall_response(self, chall) res = wait_for( IAX_SUBTYPE_ACCEPT) return if not res end self.codec = res[2][IAX_IE_DESIRED_CODEC].unpack("N")[0] self.state = :ringing self.ring_start = ::Time.now.to_i self.client.send_ack(self) true end def hangup self.client.send_hangup(self) self.state = :hangup true end def ring_time (self.ring_finish || Time.now).to_i - self.ring_start.to_i end def timestamp (( ::Time.now - self.itime) * 1000.0 ).to_i & 0xffffffff end def process_elements(data,off=0) res = {} while( off < data.length ) ie_type = data[off ,1].unpack("C")[0] ie_len = data[off + 1,2].unpack("C")[0] res[ie_type] = data[off + 2, ie_len] off += ie_len + 2 end res end # Handling incoming control packets # TODO: Enforce sequence order to prevent duplicates from breaking our state def handle_control(pkt) src_call, dst_call, tstamp, out_seq, inp_seq, itype = pkt.unpack('nnNCCC') # Scrub the high bits out of the call IDs src_call ^= 0x8000 if (src_call & 0x8000 != 0) dst_call ^= 0x8000 if (dst_call & 0x8000 != 0) phdr = [ src_call, dst_call, tstamp, out_seq, inp_seq, itype ] info = nil stype = pkt[11,1].unpack("C")[0] info = process_elements(pkt, 12) if [IAX_TYPE_IAX, IAX_TYPE_CONTROL].include?(itype) if dst_call != self.scall dprint("Incoming packet to inactive call: #{dst_call} vs #{self.scall}: #{phdr.inspect} #{stype.inspect} #{info.inspect}") return end # Increment the received sequence number self.iseq = (self.iseq + 1) & 0xff if self.state == :hangup dprint("Packet received after hangup, replying with invalid") self.client.send_invalid(self) return end # Technically these all require an ACK reply # NEW, HANGUP, REJECT, ACCEPT, PONG, AUTHREP, REGREL, REGACK, REGREJ, TXREL case itype when IAX_TYPE_DTMF_BEGIN self.dprint("DTMF BEG: #{pkt[11,1]}") self.dtmf << pkt[11,1] when IAX_TYPE_DTMF_END self.dprint("DTMF END: #{pkt[11,1]}") when IAX_TYPE_CONTROL case stype when IAX_CTRL_HANGUP dprint("HANGUP") self.client.send_ack(self) self.state = :hangup when IAX_CTRL_RINGING dprint("RINGING") self.client.send_ack(self) when IAX_CTRL_BUSY dprint("BUSY") self.busy = true self.state = :hangup self.client.send_ack(self) when IAX_CTRL_ANSWER dprint("ANSWER") if self.state == :ringing self.state = :answered self.ring_finish = ::Time.now.to_i end self.client.send_ack(self) when IAX_CTRL_PROGRESS dprint("PROGRESS") when IAX_CTRL_PROCEED dprint("PROCEED") when 255 dprint("STOP SOUNDS") end # Acknowledge all control packets # self.client.send_ack(self) when IAX_TYPE_IAX dprint( ["RECV", phdr, stype, info].inspect ) case stype when IAX_SUBTYPE_HANGUP self.state = :hangup self.client.send_ack(self) when IAX_SUBTYPE_LAGRQ # Lagrps echo the timestamp self.client.send_lagrp(self, tstamp) when IAX_SUBTYPE_ACK # Nothing to do here when IAX_SUBTYPE_PING # Pongs echo the timestamp self.client.send_pong(self, tstamp) when IAX_SUBTYPE_PONG self.client.send_ack(self) else dprint( ["RECV-QUEUE", phdr, stype, info].inspect ) self.queue.push( [phdr, stype, info ] ) end when IAX_TYPE_VOICE v_codec = stype if self.state == :answered handle_audio(pkt) end self.client.send_ack(self) when nil dprint("Invalid control packet: #{pkt.unpack("H*")[0]}") end end # Encoded audio from the client def handle_audio(pkt) # Ignore audio received before the call is answered (ring ring) return if self.state != :answered # Extract the data from the packet (full or mini) data = audio_packet_data(pkt) # Decode the data into linear PCM frames buff = decode_audio_frame(data) # Call the caller-provided hook if its exists if self.audio_hook self.audio_buff(buff) # Otherwise append the frame to the buffer else self.audio_buff << buff end end def each_audio_frame(&block) self.audio_buff.each do |frame| block.call(frame) end end def decode_audio_frame(buff) case self.codec # Convert u-law into signed PCM when IAX_CODEC_G711_MULAW Rex::Proto::IAX2::Codecs::MuLaw.decode(buff) # Convert a-law into signed PCM when IAX_CODEC_G711_ALAW Rex::Proto::IAX2::Codecs::ALaw.decode(buff) # Linear little-endian signed PCM is our native format when IAX_CODEC_LINEAR_PCM buff # Unsupported codec, return empty else dprint("UNKNOWN CODEC: #{self.codec.inspect}") '' end end def audio_packet_data(pkt) (pkt[0,1].unpack("C")[0] & 0x80 == 0) ? pkt[4,pkt.length-4] : pkt[12,pkt.length-12] end end end end end