local nmap = require "nmap"
local rpc = require "rpc"
local stdnse = require "stdnse"
local table = require "table"

description = [[
Discovers EMC Networker backup software servers on a LAN by sending a network broadcast query.
]]

---
-- @usage nmap --script broadcast-networker-discover
--
-- @output
-- Pre-scan script results:
-- | broadcast-networker-discover:
-- |_  10.20.30.40
--
--

author = "Patrik Karlsson"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"broadcast", "safe"}


prerule = function() return true end

local function Callit( host, port, program, protocol )

  local results = {}
  local portmap, comm = rpc.Portmap:new(), rpc.Comm:new('rpcbind', 2)

  local status, result = comm:Connect(host, port)
  if (not(status)) then
    return false, result
  end

  comm.socket:set_timeout(10000)
  status, result = portmap:Callit(comm, program, protocol, 2 )
  if ( not(status) ) then
    return false, result
  end

  while ( status ) do
    local _, rhost
    status, _, _, rhost, _ = comm:GetSocketInfo()
    if (not(status)) then
      return false, "Failed to get socket information"
    end

    if ( status ) then
      table.insert(results, rhost)
    end

    status, result = comm:ReceivePacket()
  end

  comm:Disconnect()
  return true, results
end

action = function()

  local results = {}
  local ip = ( nmap.address_family() == "inet" ) and "255.255.255.255" or "ff02::202"
  local iface
  local collect_interface = function (if_table)
    if not iface and if_table.up == "up" and if_table.link == "ethernet"
      and if_table.address and (
        (nmap.address_family() == "inet" and if_table.address:match("^%d+%.%d+%.%d+%.%d+$"))
        or (nmap.address_family() == "inet6" and if_table.address:match(":"))
        )
      then
      iface = if_table.device
    end
  end

  stdnse.get_script_interfaces(collect_interface)

  -- handle problematic sends on OS X requiring the interface to be
  -- supplied as part of IPv6
  if ( iface and nmap.address_family() == "inet6" ) then
    ip = ip .. "%" .. iface
  end

  for _, port in ipairs({7938,111}) do
    local host, port = { ip = ip }, { number = port, protocol = "udp" }
    local status
    status, results = Callit( host, port, "nsrstat", "udp" )

    -- warn about problematic sends on OS X requiring the interface to be
    -- supplied as part of IPv6
    if ( not(status) and results == "Portmap.Callit: Failed to send data" ) then
      return stdnse.format_output(false, "Failed sending data, try supplying the correct interface using -e")
    end

    if ( status ) then
      break
    end
  end

  if ( "table" == type(results) and 0 < #results ) then
    return stdnse.format_output(true, results)
  end
end
