class Rex::Proto::IAX2::Call

Attributes

audio_buff[RW]
audio_hook[RW]
busy[RW]
caller_name[RW]
caller_number[RW]
client[RW]
codec[RW]
dcall[RW]
dtmf[RW]
iseq[RW]
itime[RW]
oseq[RW]
queue[RW]
ring_finish[RW]
ring_start[RW]
scall[RW]
state[RW]
time_limit[RW]

Public Class Methods

new(client, src_id) click to toggle source
# File lib/rex/proto/iax2/call.rb, line 23
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

Public Instance Methods

audio_packet_data(pkt) click to toggle source
# File lib/rex/proto/iax2/call.rb, line 318
def audio_packet_data(pkt)
  (pkt[0,1].unpack("C")[0] & 0x80 == 0) ? pkt[4,pkt.length-4] : pkt[12,pkt.length-12]
end
decode_audio_frame(buff) click to toggle source
# File lib/rex/proto/iax2/call.rb, line 296
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
dial(number) click to toggle source
# File lib/rex/proto/iax2/call.rb, line 108
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
dprint(msg) click to toggle source
# File lib/rex/proto/iax2/call.rb, line 41
def dprint(msg)
  self.client.dprint(msg)
end
each_audio_frame(&block) click to toggle source
# File lib/rex/proto/iax2/call.rb, line 290
def each_audio_frame(&block)
  self.audio_buff.each do |frame|
    block.call(frame)
  end
end
handle_audio(pkt) click to toggle source

Encoded audio from the client

# File lib/rex/proto/iax2/call.rb, line 271
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
handle_control(pkt) click to toggle source

Handling incoming control packets TODO: Enforce sequence order to prevent duplicates from breaking our state

# File lib/rex/proto/iax2/call.rb, line 160
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
hangup() click to toggle source
# File lib/rex/proto/iax2/call.rb, line 133
def hangup
  self.client.send_hangup(self)
  self.state = :hangup
  true
end
process_elements(data,off=0) click to toggle source
# File lib/rex/proto/iax2/call.rb, line 147
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
register() click to toggle source

Register with the IAX endpoint

# File lib/rex/proto/iax2/call.rb, line 60
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
ring_time() click to toggle source
# File lib/rex/proto/iax2/call.rb, line 139
def ring_time
  (self.ring_finish || Time.now).to_i - self.ring_start.to_i
end
timestamp() click to toggle source
# File lib/rex/proto/iax2/call.rb, line 143
def timestamp
  (( ::Time.now - self.itime) * 1000.0 ).to_i & 0xffffffff
end
wait_for(*stypes) click to toggle source
# File lib/rex/proto/iax2/call.rb, line 45
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