diff --git a/nselib/tftp.lua b/nselib/tftp.lua
new file mode 100644
index 000000000..42a75fecc
--- /dev/null
+++ b/nselib/tftp.lua
@@ -0,0 +1,339 @@
+--- Library implementing a minimal TFTP server
+--
+-- Currently only write-operations are supported so that script can trigger
+-- TFTP transfers and receive the files and return them as result.
+--
+-- The library contains the following classes
+-- * Packet
+-- ** The Packet classes contain one class for each TFTP operation.
+-- * File
+-- ** The File class holds a recieved file including the name and contents
+-- * ConnHandler
+-- ** The ConnHandler class handles and processes incoming connections.
+--
+-- The following code snipplet starts the TFTP server and waits for the file incoming.txt
+-- to be uploaded for 10 seconds:
+--
+-- tftp.start()
+-- local status, f = tftp.waitFile("incoming.txt", 10)
+-- if ( status ) then return f:getContent() end
+--
+--
+-- @author Patrik Karlsson
+-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
+--
+
+-- version 0.2
+--
+-- 2011-01-22 - re-wrote library to use coroutines instead of new_thread code.
+
+module(... or "tftp", package.seeall)
+
+threads, infiles, running = {}, {}, {}
+state = "STOPPED"
+srvthread = {}
+
+-- All opcodes supported by TFTP
+OpCode = {
+ RRQ = 1,
+ WRQ = 2,
+ DATA = 3,
+ ACK = 4,
+ ERROR = 5,
+}
+
+
+--- A minimal packet implementation
+--
+-- The current code only implements the ACK and ERROR packets
+-- As the server is write-only the other packet types are not needed
+Packet = {
+
+ -- Implements the ACK packet
+ ACK = {
+
+ new = function( self, block )
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.block = block
+ return o
+ end,
+
+ __tostring = function( self )
+ return bin.pack(">SS", OpCode.ACK, self.block)
+ end,
+
+ },
+
+ -- Implements the error packet
+ ERROR = {
+
+ new = function( self, code, msg )
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.msg = msg
+ o.code = code
+ return o
+ end,
+
+ __tostring = function( self )
+ return bin.pack(">SSz", OpCode.ERROR, self.code, self.msg)
+ end,
+ }
+
+}
+
+--- The File class holds files received by the TFTP server
+File = {
+
+ --- Creates a new file object
+ --
+ -- @param filename string containing the filename
+ -- @param content string containing the file content
+ -- @return o new class instance
+ new = function(self, filename, content, sender)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.name = filename
+ o.content = content
+ o.sender = sender
+ return o
+ end,
+
+ getContent = function(self) return self.content end,
+ setContent = function(self, content) self.content = content end,
+
+ getName = function(self) return self.name end,
+ setName = function(self, name) self.name = name end,
+
+ setSender = function(self, sender) self.sender = sender end,
+ getSender = function(self) return self.sender end,
+}
+
+
+-- The thread dispatcher is called by the start function once
+local function dispatcher()
+
+ local last = os.time()
+ local f_condvar = nmap.condvar(infiles)
+ local s_condvar = nmap.condvar(state)
+
+ while(true) do
+
+ -- check if other scripts are active
+ local counter = 0
+ for t in pairs(running) do
+ counter = counter + 1
+ end
+ if ( counter == 0 ) then
+ state = "STOPPING"
+ s_condvar "broadcast"
+ end
+
+ local n = table.getn(threads)
+ if ( n == 0 ) then break end
+ for i=1,n do
+ local status, res = coroutine.resume(threads[i])
+ if ( not(res) ) then -- thread finished its task?
+ table.remove(threads, i)
+ break
+ end
+ end
+
+ -- Make sure to process waitFile atleast every 2 seconds
+ -- in case no files have arrived
+ if ( os.time() - last >= 2 ) then
+ last = os.time()
+ f_condvar "broadcast"
+ end
+
+ end
+ state = "STOPPED"
+ s_condvar "broadcast"
+ stdnse.print_debug("Exiting _dispatcher")
+end
+
+-- Processes a new incoming file transfer
+-- Currently only uploads are supported
+--
+-- @param host containing the hostname or ip of the initiating host
+-- @param port containing the port of the initiating host
+-- @param data string containing the initial data passed to the server
+local function processConnection( host, port, data )
+ local pos, op = bin.unpack(">S", data)
+ local socket = nmap.new_socket("udp")
+
+ socket:set_timeout(1000)
+ local status, err = socket:connect(host, port)
+ if ( not(status) ) then return status, err end
+
+ socket:set_timeout(10)
+
+ -- If we get anything else than a write request, abort the connection
+ if ( OpCode.WRQ ~= op ) then
+ stdnse.print_debug("Unsupported opcode")
+ socket:send( tostring(Packet.ERROR:new(0, "TFTP server has write-only support")))
+ end
+
+ local pos, filename, enctype = bin.unpack("zz", data, pos)
+ status, err = socket:send( tostring( Packet.ACK:new(0) ) )
+
+ local blocks = {}
+ local lastread = os.time()
+
+ while( true ) do
+ local status, pdata = socket:receive()
+ if ( not(status) ) then
+ -- if we're here and havent succesfully read a packet for 5 seconds, abort
+ if ( os.time() - lastread > 5 ) then
+ coroutine.yield(false)
+ else
+ coroutine.yield(true)
+ end
+ else
+ -- record last time we had a succesful read
+ lastread = os.time()
+ pos, op = bin.unpack(">S", pdata)
+ if ( OpCode.DATA ~= op ) then
+ stdnse.print_debug("Expected a data packet, terminating TFTP transfer")
+ end
+
+ local block, data
+ pos, block, data = bin.unpack(">SA" .. #pdata - 4, pdata, pos )
+
+ blocks[block] = data
+
+ -- First block was not 1
+ if ( #blocks == 0 ) then
+ socket:send( tostring(Packet.ERROR:new(0, "Did not receive block 1")))
+ break
+ end
+
+ -- for every fith block check that we've received the preceeding four
+ if ( ( #blocks % 5 ) == 0 ) then
+ for b = #blocks - 4, #blocks do
+ if ( not(blocks[b]) ) then
+ socket:send( tostring(Packet.ERROR:new(0, "Did not receive block " .. b)))
+ end
+ end
+ end
+
+ -- Ack the data block
+ status, err = socket:send( tostring(Packet.ACK:new(block)) )
+
+ if ( ( #blocks % 20 ) == 0 ) then
+ -- yield every 5th iteration so other threads may work
+ coroutine.yield(true)
+ end
+
+ -- If the data length was less than 512, this was our last block
+ if ( #data < 512 ) then
+ socket:close()
+ break
+ end
+ end
+ end
+
+ local filecontent = ""
+
+ -- Make sure we received all the blocks needed to proceed
+ for i=1, #blocks do
+ if ( not(blocks[i]) ) then
+ return false, ("Block #%d was missing in transfer")
+ end
+ filecontent = filecontent .. blocks[i]
+ end
+ stdnse.print_debug("Finnished receiving file \"%s\"", filename)
+
+ -- Add anew file to the global infiles table
+ table.insert( infiles, File:new(filename, filecontent, host) )
+
+ local condvar = nmap.condvar(infiles)
+ condvar "broadcast"
+end
+
+-- Waits for a connection from a client
+local function waitForConnection()
+
+ local srvsock = nmap.new_socket("udp")
+ local status = srvsock:bind(nil, 69)
+ assert(status, "Failed to bind to TFTP server port")
+
+ srvsock:set_timeout(0)
+
+ while( state == "RUNNING" ) do
+ local status, data = srvsock:receive()
+ if ( not(status) ) then
+ coroutine.yield(true)
+ else
+ local status, _, _, rhost, rport = srvsock:get_info()
+ local x = coroutine.create( function() processConnection(rhost, rport, data) end )
+ table.insert( threads, x )
+ coroutine.yield(true)
+ end
+ end
+end
+
+
+--- Starts the TFTP server and creates a new thread handing over to the dispatcher
+function start()
+ local disp = nil
+ local mutex = nmap.mutex("srvsocket")
+
+ -- register a running script
+ running[coroutine.running()] = true
+
+ mutex "lock"
+ if ( state == "STOPPED" ) then
+ srvthread = coroutine.running()
+ table.insert( threads, coroutine.create( waitForConnection ) )
+ stdnse.new_thread( dispatcher )
+ state = "RUNNING"
+ end
+ mutex "done"
+
+end
+
+local function waitLast()
+ -- The thread that started the server needs to wait here until the rest
+ -- of the scripts finnish running. We know we are done once the state
+ -- shifts to STOPPED and we get a singla from the condvar in the
+ -- dispatcher
+ local s_condvar = nmap.condvar(state)
+ while( srvthread == coroutine.running() and state ~= "STOPPED" ) do
+ s_condvar "wait"
+ end
+end
+
+--- Waits for a file with a specific filename for at least the number of
+-- seconds specified by the timeout parameter. If this function is called
+-- from the thread that's running the server it will wait until all the
+-- other threads have finnished executing before returning.
+--
+-- @param filename string containing the name of the file to receive
+-- @param timeout number containing the minimum number of seconds to wait
+-- for the file to be received
+-- @return status true on success false on failure
+-- @return File instance on success, nil on failure
+function waitFile( filename, timeout )
+ local condvar = nmap.condvar(infiles)
+ local t = os.time()
+ while(os.time() - t < timeout) do
+ for _, f in ipairs(infiles) do
+ if (f:getName() == filename) then
+ running[coroutine.running()] = nil
+ waitLast()
+ return true, f
+ end
+ end
+ condvar "wait"
+ end
+ -- de-register a running script
+ running[coroutine.running()] = nil
+ waitLast()
+
+ return false
+end
\ No newline at end of file
diff --git a/scripts/script.db b/scripts/script.db
index 340d5c510..dc0220acf 100644
--- a/scripts/script.db
+++ b/scripts/script.db
@@ -126,6 +126,7 @@ Entry { filename = "realvnc-auth-bypass.nse", categories = { "auth", "default",
Entry { filename = "resolveall.nse", categories = { "discovery", "safe", } }
Entry { filename = "rmi-dumpregistry.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "rpcinfo.nse", categories = { "discovery", "safe", } }
+Entry { filename = "servicetags.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "skypev2-version.nse", categories = { "version", } }
Entry { filename = "smb-brute.nse", categories = { "auth", "intrusive", } }
Entry { filename = "smb-check-vulns.nse", categories = { "dos", "exploit", "intrusive", "vuln", } }
@@ -149,6 +150,7 @@ Entry { filename = "smtp-strangeport.nse", categories = { "malware", "safe", } }
Entry { filename = "sniffer-detect.nse", categories = { "discovery", "intrusive", } }
Entry { filename = "snmp-brute.nse", categories = { "auth", "intrusive", } }
Entry { filename = "snmp-interfaces.nse", categories = { "default", "discovery", "safe", } }
+Entry { filename = "snmp-ios-config.nse", categories = { "intrusive", } }
Entry { filename = "snmp-netstat.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "snmp-processes.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "snmp-sysdescr.nse", categories = { "default", "discovery", "safe", } }
diff --git a/scripts/snmp-ios-config.nse b/scripts/snmp-ios-config.nse
new file mode 100644
index 000000000..f8a393722
--- /dev/null
+++ b/scripts/snmp-ios-config.nse
@@ -0,0 +1,204 @@
+description = [[
+Download IOS configuration using SNMP RW (v1) and displays the result or saves it to a file.
+]]
+
+---
+-- @usage
+-- nmap -sU -p 161 --script snmp-ios-config --script-args snmpcommunity=
+--
+-- @output
+-- | snmp-ios-config:
+-- | !
+-- | version 12.3
+-- | service timestamps debug datetime msec
+-- | service timestamps log datetime msec
+-- | no service password-encryption
+-- | !
+-- | hostname Router
+-- | !
+-- | boot-start-marker
+-- | boot-end-marker
+--
+--
+-- @args snmp-ios-config.tftproot If set, specifies to what directory the downloaded config should be saved
+--
+-- Version 0.2
+-- Created 01/03/2011 - v0.1 - created by Vikas Singhal
+-- Revised 02/22/2011 - v0.2 - cleaned up and added support for built-in tftp, Patrik Karlsson
+
+author = "Vikas Singhal, Patrik Karlsson"
+
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
+
+categories = {"intrusive"}
+
+dependencies = {"snmp-brute"}
+
+require "shortport"
+require "snmp"
+require "tftp"
+
+portrule = shortport.portnumber(161, "udp", {"open", "open|filtered"})
+
+local function sendrequest(socket, oid, setparam)
+ local payload
+ local options = {}
+ options.reqId = 28428 -- unnecessary?
+ payload = snmp.encode(snmp.buildPacket(snmp.buildSetRequest(options, oid,setparam)))
+
+ try(socket:send(payload))
+
+ -- read in any response we might get
+ local status, response = socket:receive()
+ if ( not(status) ) then return status, response end
+
+ local result = snmp.fetchFirst(response)
+ return true
+end
+
+---
+-- Sends SNMP packets to host and reads responses
+action = function(host, port)
+
+ local tftproot = stdnse.get_script_args("snmp-ios-config.tftproot")
+
+ if ( tftproot and not( tftproot:match("[\\/]+$") ) ) then
+ return "ERROR: tftproot needs to end with slash"
+ end
+
+ -- create the socket used for our connection
+ local socket = nmap.new_socket()
+
+ -- set a reasonable timeout value
+ socket:set_timeout(5000)
+
+ -- do some exception handling / cleanup
+ catch = function() socket:close() end
+
+ try = nmap.new_try(catch)
+
+ -- connect to the potential SNMP system
+ try(socket:connect(host.ip, port.number, "udp"))
+
+ local status, tftpserver, _, _, _ = socket:get_info()
+ if( not(status) ) then
+ return "ERROR: Failed to determin local ip"
+ end
+
+ -- build a SNMP v1 packet
+ -- set value: .1.3.6.1.4.1.9.9.96.1.1.1.1.2.9999 (ConfigCopyProtocol is set to TFTP [1] )
+
+ request = sendrequest(socket, ".1.3.6.1.4.1.9.9.96.1.1.1.1.2.9999",1)
+
+ -- Fail silently if the first request doesn't get a proper response
+ if ( not(request) ) then return end
+
+ -- since we got something back, the port is definitely open
+ nmap.set_port_state(host, port, "open")
+
+ -------------------------------------------------
+ -- build a SNMP v1 packet
+ -- set value: .1.3.6.1.4.1.9.9.96.1.1.1.1.3 (SourceFileType is set to running-config [4] )
+
+ request = sendrequest(socket, ".1.3.6.1.4.1.9.9.96.1.1.1.1.3.9999",4)
+
+ -------------------------------------------------
+ -- build a SNMP v1 packet
+ -- set value: .1.3.6.1.4.1.9.9.96.1.1.1.1.4 (DestinationFileType is set to networkfile [1] )
+
+ request = sendrequest(socket, ".1.3.6.1.4.1.9.9.96.1.1.1.1.4.9999",1)
+
+ -------------------------------------------------
+ -- build a SNMP v1 packet
+ -- set value: .1.3.6.1.4.1.9.9.96.1.1.1.1.15 (ServerAddress is set to the IP address of the TFTP server )
+
+ local tbl = {}
+ tbl._snmp = '40'
+ for octet in tftpserver:gmatch("%d+") do
+ table.insert(tbl, octet)
+ end
+
+ request = sendrequest(socket, nil, { { snmp.str2oid(".1.3.6.1.4.1.9.9.96.1.1.1.1.5.9999"), tbl } } )
+ -- request = sendrequest(".1.3.6.1.4.1.9.9.96.1.1.1.1.5.9999",tftpserver)
+
+
+ -------------------------------------------------
+ -- build a SNMP v1 packet
+ -- set value: .1.3.6.1.4.1.9.9.96.1.1.1.1.15 (ServerAddressType is set 1 for ipv4 )
+ -- more options - 1:ipv4, 2:ipv6, 3:ipv4z, 4:ipv6z, 16:dns
+
+ request = sendrequest(socket, ".1.3.6.1.4.1.9.9.96.1.1.1.1.15.9999",1)
+
+ -------------------------------------------------
+ -- build a SNMP v1 packet
+ -- set value: .1.3.6.1.4.1.9.9.96.1.1.1.1.16 (ServerAddress is set to the IP address of the TFTP server )
+
+ request = sendrequest(socket, ".1.3.6.1.4.1.9.9.96.1.1.1.1.16.9999",tftpserver)
+
+ -------------------------------------------------
+ -- build a SNMP v1 packet
+ -- set value: .1.3.6.1.4.1.9.9.96.1.1.1.1.6 (CopyFilename is set to IP-config)
+
+ request = sendrequest(socket, ".1.3.6.1.4.1.9.9.96.1.1.1.1.6.9999",host.ip .. "-config")
+
+ -------------------------------------------------
+ -- build a SNMP v1 packet
+ -- set value: .1.3.6.1.4.1.9.9.96.1.1.1.1.14 (Start copying by setting CopyStatus to active [1])
+ -- more options: 1:active, 2:notInService, 3:notReady, 4:createAndGo, 5:createAndWait, 6:destroy
+
+ request = sendrequest(socket, ".1.3.6.1.4.1.9.9.96.1.1.1.1.14.9999",1)
+
+ -- wait for sometime and print the status of filetransfer
+ tftp.start()
+ local status, infile = tftp.waitFile(host.ip .. "-config", 10)
+
+ -- build a SNMP v1 packet
+ -- get value: .1.3.6.1.4.1.9.9.96.1.1.1.1.10 (Check the status of filetransfer) 1:waiting, 2:running, 3:successful, 4:failed
+
+ local options = {}
+ options.reqId = 28428
+ local payload = snmp.encode(snmp.buildPacket(snmp.buildGetRequest(options, ".1.3.6.1.4.1.9.9.96.1.1.1.1.10.9999")))
+
+ try(socket:send(payload))
+
+ local status
+ local response
+ -- read in any response we might get
+ status, response = socket:receive()
+
+ if (not status) or (response == "TIMEOUT") then
+ return "\n ERROR: Failed to receive cisco configuration file"
+ end
+
+ local result
+ result = snmp.fetchFirst(response)
+
+ if result == 3 then
+ result = ( infile and infile:getContent() )
+
+ if ( tftproot ) then
+ local fname = tftproot .. host.ip .. "-config"
+ local file, err = io.open(fname, "w")
+ if ( file ) then
+ file:write(result)
+ file:close()
+ else
+ return "\n ERROR: " .. file
+ end
+ result = ("\n Configuration saved to (%s)"):format(fname)
+ end
+ else
+ result = "Not successful! error code: " .. result .. " (1:waiting, 2:running, 3:successful, 4:failed)"
+ end
+
+ -------------------------------------------------
+ -- build a SNMP v1 packet
+ -- set value: .1.3.6.1.4.1.9.9.96.1.1.1.1.14 (Destroy settings by setting CopyStatus to destroy [6])
+
+ request = sendrequest(socket, ".1.3.6.1.4.1.9.9.96.1.1.1.1.14.9999",6)
+
+ try(socket:close())
+
+ return result
+end
+