#
#   Copyright 2012 Wade Alcorn wade@bindshell.net
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#
module BeEF
module Extension
module AdminUI
module Controllers

#
# HTTP Controller for the Requester component of BeEF.
#
class Requester < BeEF::Extension::AdminUI::HttpController
  
  # Variable representing the Http DB model.
  H = BeEF::Core::Models::Http
  
  def initialize
    super({
      'paths' => {
        '/send'           => method(:send_request),
        '/history.json'   => method(:get_zombie_history),
        '/response.json'  => method(:get_zombie_response)
      }
    })
  end

  def err_msg(error)
     print_error "[REQUESTER] #{error}"
  end
  
  # Send a new http request to the hooked browser.
  def send_request
    # validate that the hooked browser's session has been sent
    zombie_session = @params['zombie_session'] || nil
    (self.err_msg "Invalid session id";return @body = '{success : false}') if not BeEF::Filters.is_valid_hook_session_id?(zombie_session)
    
    # validate that the hooked browser exists in the db
    zombie = Z.first(:session => zombie_session) || nil
    (self.err_msg "Invalid hooked browser session";return @body = '{success : false}') if zombie.nil?
    
    # validate that the raw request has been sent
    raw_request = @params['raw_request'] || nil
    (self.err_msg "raw_request is nil";return @body = '{success : false}') if raw_request.nil?
    (self.err_msg "raw_request contains non-printable chars";return @body = '{success : false}') if not BeEF::Filters.has_non_printable_char?(raw_request)
    
    # validate nonce
    nonce = @params['nonce'] || nil
    (self.err_msg "nonce is nil";return @body = '{success : false}') if nonce.nil?
    (self.err_msg "nonce incorrect";return @body = '{success : false}') if @session.get_nonce != nonce
    
    # validate that the raw request is correct and can be used
    req_parts = raw_request.split(/ |\n/) # break up the request

    verb = req_parts[0]
    self.err_msg 'Only HEAD, GET, POST, OPTIONS, PUT or DELETE requests are supported' if not BeEF::Filters.is_valid_verb?(verb) #check verb

    uri = req_parts[1]
    (self.err_msg 'Invalid URI';return @body = '{success : false}') if not BeEF::Filters.is_valid_url?(uri) #check uri

    version = req_parts[2]
    (self.err_msg 'Invalid HTTP version';return @body = '{success : false}') if not BeEF::Filters.is_valid_http_version?(version) # check http version - HTTP/1.0 or HTTP/1.1

    host_str = req_parts[3]
    (self.err_msg 'Invalid HTTP Host Header';return @body = '{success : false}') if not BeEF::Filters.is_valid_host_str?(host_str) # check host string - Host:

    host = req_parts[4]
    host_parts = host.split(/:/)
    hostname = host_parts[0]
    (self.err_msg 'Invalid HTTP HostName';return @body = '{success : false}') if not BeEF::Filters.is_valid_hostname?(hostname) #check the target hostname

    hostport = host_parts[1] || nil
    if !hostport.nil?
        (self.err_msg 'Invalid HTTP HostPort';return @body = '{success : false}') if not BeEF::Filters.nums_only?(hostport) #check the target hostport
    end

    # Saves the new HTTP request.
    http = H.new(
      :request => raw_request,
      :method => verb,
      :domain => hostname,
      :port => hostport,
      :path => uri,
      :request_date => Time.now,
      :hooked_browser_id => zombie.id,
      :allow_cross_domain => "true",
    )
    
    if verb.eql? 'POST'
      req_parts.each_with_index do |value, index|
         if value.match(/^Content-Length/)
           http.content_length = req_parts[index+1]
         end
      end
    end
    
    http.save
    
    @body = '{success : true}'
  end
  
  # Returns a JSON object containing the history of requests sent to the zombie.
  def get_zombie_history
    # validate nonce
    nonce = @params['nonce'] || nil
    (self.err_msg "nonce is nil";return @body = '{success : false}') if nonce.nil?
    (self.err_msg "nonce incorrect";return @body = '{success : false}') if @session.get_nonce != nonce
    
    # validate that the hooked browser's session has been sent
    zombie_session = @params['zombie_session'] || nil
    (self.err_msg "Zombie session is nil";return @body = '{success : false}') if zombie_session.nil?
    
    # validate that the hooked browser exists in the db
    zombie = Z.first(:session => zombie_session) || nil
    (self.err_msg "Invalid hooked browser session";return @body = '{success : false}') if zombie.nil?
    
    history = []
    H.all(:hooked_browser_id => zombie.id).each{|http|
      history << {
        'id'      => http.id,
        'domain'  => http.domain,
	      'port'    => http.port,
        'path'    => http.path,
        'has_ran' => http.has_ran,
        'method'  => http.method,
        'request_date'    => http.request_date,
        'response_date'    => http.response_date,
        'response_status_code'    => http.response_status_code,
        'response_status_text'    => http.response_status_text,
	      'response_port_status'    => http.response_port_status
      }
    }
    
    @body = {'success' => 'true', 'history' => history}.to_json
  end
  
  # Returns a JSON objecting containing the response of a request.
  def get_zombie_response
    # validate nonce
    nonce = @params['nonce'] || nil
    (self.err_msg "nonce is nil";return @body = '{success : false}') if nonce.nil?
    (self.err_msg "nonce incorrect";return @body = '{success : false}') if @session.get_nonce != nonce
    
    # validate the http id
    http_id = @params['http_id'] || nil
    (self.err_msg "http_id is nil";return @body = '{success : false}') if http_id.nil?
    
    # validate that the http object exist in the dabatase
    http_db = H.first(:id => http_id) || nil
    (self.err_msg "http object could not be found in the database";return @body = '{success : false}') if http_db.nil?
    
    if http_db.response_data.length > (1024 * 100) #more thank 100K
      response_data = http_db.response_data[0..(1024*100)]
      response_data += "\n<---------- Response Data Truncated---------->"
    else
      response_data = http_db.response_data
    end
    
    res = {
      'id'        => http_db.id,
      'request'   => http_db.request,
      'response'  => response_data,
      'response_headers' => http_db.response_headers,
      'domain'    => http_db.domain,
      'port'      => http_db.port,
      'path'      => http_db.path,
      'date'      => http_db.request_date,
      'has_ran'   => http_db.has_ran
    }
    
    @body = {'success' => 'true', 'result' => res}.to_json
  end
  
end

end
end
end
end
