class Inspec::Resources::LsofPorts

extracts udp and tcp ports from the lsof command

Attributes

lsof[R]

Public Class Methods

new(inspec, lsofpath = nil) click to toggle source
Calls superclass method Inspec::Resources::PortsInfo::new
# File lib/inspec/resources/port.rb, line 167
def initialize(inspec, lsofpath = nil)
  @lsof = lsofpath || "lsof"
  super(inspec)
end

Public Instance Methods

info() click to toggle source
# File lib/inspec/resources/port.rb, line 172
def info
  ports = []

  # check that lsof is available, otherwise fail
  raise "Please ensure `lsof` is available on the machine." unless inspec.command(@lsof.to_s).exist?

  # -F p=pid, c=command, P=protocol name, t=type, n=internet addresses
  # see 'OUTPUT FOR OTHER PROGRAMS' in LSOF(8)
  lsof_cmd = inspec.command("#{@lsof} -nP -i -FpctPn")
  return nil if lsof_cmd.exit_status.to_i != 0

  # map to desired return struct
  lsof_parser(lsof_cmd).each do |process, port_ids|
    pid, cmd = process.split(":")
    port_ids.each do |port_str|
      # should not break on ipv6 addresses
      ipv, proto, port, host = port_str.split(":", 4)
      ports.push({ "port" => port.to_i,
                   "address" => host,
                   "protocol" => ipv == "ipv6" ? proto + "6" : proto,
                   "process" => cmd,
                   "pid" => pid.to_i })
    end
  end

  ports
end
lsof_parser(lsof_cmd) click to toggle source

rubocop:disable Metrics/CyclomaticComplexity rubocop:disable Metrics/AbcSize

# File lib/inspec/resources/port.rb, line 202
def lsof_parser(lsof_cmd)
  procs = {}
  # build this with formatted output (-F) from lsof
  # procs = {
  #   '123:sshd' => [
  #      'ipv4:tcp:22:127.0.0.1',
  #      'ipv6:tcp:22:::1',
  #      'ipv4:tcp:*',
  #      'ipv6:tcp:*',
  #   ],
  #   '456:ntpd' => [
  #      'ipv4:udp:123:*',
  #      'ipv6:udp:123:*',
  #   ]
  # }
  proc_id = port_id = nil
  lsof_cmd.stdout.each_line do |line|
    line.chomp!
    key = line.slice!(0)
    case key
    when "p"
      proc_id = line
      port_id = nil
    when "c"
      proc_id += ":" + line
    when "t"
      port_id = line.downcase
    when "P"
      port_id += ":" + line.downcase
    when "n"
      src, dst = line.split("->")

      # skip active comm streams
      next if dst

      host, port = /^(\S+):(\d+|\*)$/.match(src)[1, 2]

      # skip channels from port 0 - what does this mean?
      next if port == "*"

      # create new array stub if !exist?
      procs[proc_id] = [] unless procs.key?(proc_id)

      # change address '*' to zero
      host = port_id =~ /^ipv6:/ ? "[::]" : "0.0.0.0" if host == "*"
      # entrust URI to scrub the host and port
      begin
        uri = URI("addr://#{host}:#{port}")
        uri.host && uri.port
      rescue => e
        warn "could not parse URI 'addr://#{host}:#{port}' - #{e}"
        next
      end

      # e.g. 'ipv4:tcp:22:127.0.0.1'
      #                             strip ipv6 squares for inspec
      port_id += ":" + port + ":" + host.gsub(/^\[|\]$/, "")

      # lsof will give us another port unless it's done
      procs[proc_id] << port_id
    end
  end

  procs
end