summaryrefslogtreecommitdiffstats
path: root/ncat/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'ncat/scripts')
-rw-r--r--ncat/scripts/chargen.lua7
-rw-r--r--ncat/scripts/conditional.lua51
-rw-r--r--ncat/scripts/date.lua3
-rw-r--r--ncat/scripts/discard.lua11
-rw-r--r--ncat/scripts/echo.lua16
-rw-r--r--ncat/scripts/hello-luaexec.lua4
-rw-r--r--ncat/scripts/httpd.lua402
-rw-r--r--ncat/scripts/log_ips.sh10
-rwxr-xr-xncat/scripts/p0fme.py97
-rw-r--r--ncat/scripts/whatismyip.lua4
10 files changed, 605 insertions, 0 deletions
diff --git a/ncat/scripts/chargen.lua b/ncat/scripts/chargen.lua
new file mode 100644
index 0000000..70b937f
--- /dev/null
+++ b/ncat/scripts/chargen.lua
@@ -0,0 +1,7 @@
+--chargen.lua - implements the RFC 864 CHARGEN service which basically spams
+--the remote user until he decides to close the connection.
+
+while true do
+ print "chargen"
+end
+
diff --git a/ncat/scripts/conditional.lua b/ncat/scripts/conditional.lua
new file mode 100644
index 0000000..76817b2
--- /dev/null
+++ b/ncat/scripts/conditional.lua
@@ -0,0 +1,51 @@
+--This is another --lua-exec demo. It displays a menu to a user, waits for her
+--input and makes a decision according to what the user entered. All happens
+--in an infinite loop.
+
+--This function reads a line of at most 8096 bytes (or whatever the first
+--parameter says) from standard input. Returns the string and a boolean value
+--that is true if we hit the newline (defined as "\n") or false if the line had
+--to be truncated. This is here because io.stdin:read("*line") could lead to
+--memory exhaustion if we received gigabytes of characters with no newline.
+function read_line(max_len)
+ local ret = ""
+ for i = 1, (max_len or 8096) do
+ local chr = io.read(1)
+ if chr == "\n" then
+ return ret, true
+ end
+ ret = ret .. chr
+ end
+
+ return ret, false
+end
+
+while true do
+
+ print "Here's a menu for you: "
+ print "1. Repeat the menu."
+ print "0. Exit."
+
+ io.write "Please enter your choice: "
+ io.flush(io.stdout)
+ i = read_line()
+
+ --WARNING! Without this line, the script will go into an infinite loop
+ --that keeps consuming system resources when the connection gets broken.
+ --Ncat's subprocesses are NOT killed in this case!
+ if i == nil then
+ break
+ end
+
+ print("You wrote: ", i, ".")
+
+ if i == "0" then
+ break
+ elseif i == "1" then
+ print "As you wish."
+ else
+ print "No idea what you meant. Please try again."
+ end
+
+ print() --print a newline
+end
diff --git a/ncat/scripts/date.lua b/ncat/scripts/date.lua
new file mode 100644
index 0000000..a7116ef
--- /dev/null
+++ b/ncat/scripts/date.lua
@@ -0,0 +1,3 @@
+--Emulates the daytime service (RFC 867).
+
+print(os.date())
diff --git a/ncat/scripts/discard.lua b/ncat/scripts/discard.lua
new file mode 100644
index 0000000..6b386f6
--- /dev/null
+++ b/ncat/scripts/discard.lua
@@ -0,0 +1,11 @@
+--This script reads data from the standard input and discards them.
+
+while true do
+
+ data = io.stdin:read(512)
+
+ if data == nil then
+ break
+ end
+
+end
diff --git a/ncat/scripts/echo.lua b/ncat/scripts/echo.lua
new file mode 100644
index 0000000..3bfea90
--- /dev/null
+++ b/ncat/scripts/echo.lua
@@ -0,0 +1,16 @@
+--Emulates the RFC 862 echo service, behaving like Unix's "cat" tool.
+
+while true do
+
+ --We're reading in 1-byte chunks because calls like io.stdin:read(512) would
+ --wait for full 512 bytes of data before continuing.
+ data = io.stdin:read(1)
+
+ if data == nil then
+ break
+ end
+
+ io.stdout:write(data)
+ io.stdout:flush()
+
+end
diff --git a/ncat/scripts/hello-luaexec.lua b/ncat/scripts/hello-luaexec.lua
new file mode 100644
index 0000000..31bd498
--- /dev/null
+++ b/ncat/scripts/hello-luaexec.lua
@@ -0,0 +1,4 @@
+--This is a --lua-exec "Hello world" example. In order to send to a client,
+--all you need to do is output it to the standard output.
+
+print "Hello, world!"
diff --git a/ncat/scripts/httpd.lua b/ncat/scripts/httpd.lua
new file mode 100644
index 0000000..3837e32
--- /dev/null
+++ b/ncat/scripts/httpd.lua
@@ -0,0 +1,402 @@
+--httpd.lua - a dead simple HTTP server. Expects GET requests and serves files
+--matching these requests. Can guess mime based on an extension too. Currently
+--disallows any filenames that start or end with "..".
+
+------------------------------------------------------------------------------
+-- Configuration section --
+------------------------------------------------------------------------------
+
+server_headers = {
+ ["Server"] = "Ncat --lua-exec httpd.lua",
+ ["Connection"] = "close",
+}
+
+function guess_mime(resource)
+ if string.sub(resource, -5) == ".html" then return "text/html" end
+ if string.sub(resource, -4) == ".htm" then return "text/html" end
+ return "application/octet-stream"
+end
+
+------------------------------------------------------------------------------
+-- End of configuration section --
+------------------------------------------------------------------------------
+
+function print_rn(str)
+ io.stdout:write(str .. "\r\n")
+ io.stdout:flush()
+end
+
+function debug(str)
+ io.stderr:write("[" .. os.date() .. "] ")
+ io.stderr:write(str .. "\n")
+ io.stderr:flush()
+end
+
+function url_decode(str)
+ --taken from here: http://lua-users.org/wiki/StringRecipes
+ return str:gsub("%%(%x%x)",
+ function(h) return string.char(tonumber(h,16)) end)
+end
+
+--Read a line of at most 8096 bytes (or whatever the first parameter says)
+--from standard input. Returns the string and a boolean value that is true if
+--we hit the newline (defined as "\n") or false if the line had to be
+--truncated. This is here because io.stdin:read("*line") could lead to memory
+--exhaustion if we received gigabytes of characters with no newline.
+function read_line(max_len)
+ local ret = ""
+ for i = 1, (max_len or 8096) do
+ local chr = io.read(1)
+ if chr == "\n" then
+ return ret, true
+ end
+ ret = ret .. chr
+ end
+
+ return ret, false
+end
+
+--The following function and variables was translated from Go to Lua. The
+--original code can be found here:
+--
+--http://golang.org/src/pkg/unicode/utf8/utf8.go#L45
+local surrogate_min = 0xD800
+local surrogate_max = 0xDFFF
+
+local t1 = 0x00 -- 0000 0000
+local tx = 0x80 -- 1000 0000
+local t2 = 0xC0 -- 1100 0000
+local t3 = 0xE0 -- 1110 0000
+local t4 = 0xF0 -- 1111 0000
+local t5 = 0xF8 -- 1111 1000
+
+local maskx = 0x3F -- 0011 1111
+local mask2 = 0x1F -- 0001 1111
+local mask3 = 0x0F -- 0000 1111
+local mask4 = 0x07 -- 0000 0111
+
+local char1_max = 0x7F -- (1<<7) - 1
+local char2_max = 0x07FF -- (1<<11) - 1
+local char3_max = 0xFFFF -- (1<<16) - 1
+
+local max_char = 0x10FFFF -- \U0010FFFF
+
+function get_next_char_len(p)
+ local n = p:len()
+ local c0 = p:byte(1)
+
+ --1-byte, 7-bit sequence?
+ if c0 < tx then
+ return 1
+ end
+
+ --unexpected continuation byte?
+ if c0 < t2 then
+ return nil
+ end
+
+ --need first continuation byte
+ if n < 2 then
+ return nil
+ end
+ local c1 = p:byte(2)
+ if c1 < tx or t2 <= c1 then
+ return nil
+ end
+
+ --2-byte, 11-bit sequence?
+ if c0 < t3 then
+ local l1 = (c0 & mask2) << 6
+ local l2 = c1 & maskx
+ local r = l1 | l2
+ if r <= char1_max then
+ return nil
+ end
+ return 2
+ end
+
+ --need second continuation byte
+ if n < 3 then
+ return nil
+ end
+ local c2 = p:byte(3)
+ if c2 < tx or t2 <= c2 then
+ return nil
+ end
+
+ --3-byte, 16-bit sequence?
+ if c0 < t4 then
+ local l1 = (c0 & mask3) << 12
+ local l2 = (c1 & maskx) << 6
+ local l3 = c2 & maskx
+ local r = l1 | l2 | l3
+ if r <= char2_max then
+ return nil
+ end
+ if surrogate_min <= r and r <= surrogate_max then
+ return nil
+ end
+ return 3
+ end
+
+ --need third continuation byte
+ if n < 4 then
+ return nil
+ end
+ local c3 = p:byte(4)
+ if c3 < tx or t2 <= c3 then
+ return nil
+ end
+
+ --4-byte, 21-bit sequence?
+ if c0 < t5 then
+ local l1 = (c0 & mask4) << 18
+ local l2 = (c1 & maskx) << 12
+ local l3 = (c2 & maskx) << 6
+ local l4 = c3 & maskx
+ local r = l1 | l2 | l3 | l4
+ if r <= char3_max or max_char < r then
+ return nil
+ end
+ return 4
+ end
+
+ --error
+ return nil
+end
+
+function validate_utf8(s)
+ local i = 1
+ local len = s:len()
+ while i <= len do
+ local size = get_next_char_len(s:sub(i))
+ if size == nil then
+ return false
+ end
+ i = i + size
+ end
+ return true
+end
+
+--Returns a table containing the list of directories resulting from splitting
+--the argument by '/'.
+function split_path(path)
+ --[[
+ for _, v in pairs({"/a/b/c", "a/b/c", "//a/b/c", "a/b/c/", "a/b/c//"}) do
+ print(v,table.concat(split_path(v), ','))
+ end
+
+ -- /a/b/c ,a,b,c
+ -- a/b/c a,b,c
+ -- //a/b/c ,,a,b,c
+ -- a/b/c/ a,b,c
+ -- a/b/c// a,b,c,
+ ]]
+ local ret = {}
+ local j = 0
+ for i=1, path:len() do
+ if path:sub(i,i) == '/' then
+ if j == 0 then
+ ret[#ret+1] = path:sub(1, i-1)
+ else
+ ret[#ret+1] = path:sub(j+1, i-1)
+ end
+ j = i
+ end
+ end
+ if j ~= path:len() then
+ ret[#ret+1] = path:sub(j+1, path:len())
+ end
+ return ret
+end
+
+
+function is_path_valid(resource)
+ --remove the beginning slash
+ resource = string.sub(resource, 2, string.len(resource))
+
+ --Windows drive names are not welcome.
+ if resource:match("^([a-zA-Z]):") then
+ return false
+ end
+
+ --if it starts with a dot or a slash or a backslash, forbid any acccess to it.
+ local first_char = resource:sub(1, 1)
+
+ if first_char == "." then
+ return false
+ end
+
+ if first_char == "/" then
+ return false
+ end
+
+ if resource:find("\\") then
+ return false
+ end
+
+ for _, directory in pairs(split_path(resource)) do
+ if directory == '' then
+ return false
+ end
+
+ if directory == '..' then
+ return false
+ end
+ end
+
+ return true
+end
+
+--Make a response, output it and stop execution.
+--
+--It takes an associative array with three optional keys: status (status line)
+--and headers, which lists all additional headers to be sent. You can also
+--specify "data" - either a function that is expected to return nil at some
+--point or a plain string.
+function make_response(params)
+
+ --Print the status line. If we got none, assume it's all okay.
+ if not params["status"] then
+ params["status"] = "HTTP/1.1 200 OK"
+ end
+ print_rn(params["status"])
+
+ --Send the date.
+ print_rn("Date: " .. os.date("!%a, %d %b %Y %H:%M:%S GMT"))
+
+ --Send the server headers as described in the configuration.
+ for key, value in pairs(server_headers) do
+ print_rn(("%s: %s"):format(key, value))
+ end
+
+ --Now send the headers from the parameter, if any.
+ if params["headers"] then
+ for key, value in pairs(params["headers"]) do
+ print_rn(("%s: %s"):format(key, value))
+ end
+ end
+
+ --If there's any data, check if it's a function.
+ if params["data"] then
+
+ if type(params["data"]) == "function" then
+
+ print_rn("")
+ debug("Starting buffered output...")
+
+ --run the function and print its contents, until we hit nil.
+ local f = params["data"]
+ while true do
+ local ret = f()
+ if ret == nil then
+ debug("Buffered output finished.")
+ break
+ end
+ io.stdout:write(ret)
+ io.stdout:flush()
+ end
+
+ else
+
+ --It's a plain string. Send its length and output it.
+ debug("Just printing the data. Status='" .. params["status"] .. "'")
+ print_rn("Content-length: " .. params["data"]:len())
+ print_rn("")
+ io.stdout:write(params["data"])
+ io.stdout:flush()
+
+ end
+ else
+ print_rn("")
+ end
+
+ os.exit(0)
+end
+
+function make_error(error_str)
+ make_response({
+ ["status"] = "HTTP/1.1 "..error_str,
+ ["headers"] = {["Content-type"] = "text/html"},
+ ["data"] = "<h1>"..error_str.."</h1>",
+ })
+end
+
+do_400 = function() make_error("400 Bad Request") end
+do_403 = function() make_error("403 Forbidden") end
+do_404 = function() make_error("404 Not Found") end
+do_405 = function() make_error("405 Method Not Allowed") end
+do_414 = function() make_error("414 Request-URI Too Long") end
+
+------------------------------------------------------------------------------
+-- End of library section --
+------------------------------------------------------------------------------
+
+input, success = read_line()
+
+if not success then
+ do_414()
+end
+
+if input:sub(-1) == "\r" then
+ input = input:sub(1,-2)
+end
+
+--We assume that:
+-- * a method is alphanumeric uppercase,
+-- * resource may contain anything that's not a space,
+-- * protocol version is followed by a single space.
+method, resource, protocol = input:match("([A-Z]+) ([^ ]+) ?(.*)")
+
+if resource:find(string.char(0)) ~= nil then
+ do_400()
+end
+
+if not validate_utf8(resource) then
+ do_400()
+end
+
+if method ~= "GET" then
+ do_405()
+end
+
+while true do
+
+ input = read_line()
+ if input == "" or input == "\r" then
+ break
+ end
+end
+
+debug("Got a request for '" .. resource
+ .. "' (urldecoded: '" .. url_decode(resource) .. "').")
+resource = url_decode(resource)
+
+--make sure that the resource starts with a slash.
+if resource:sub(1, 1) ~= '/' then
+ do_400() --could probably use a fancier error here.
+end
+
+if not is_path_valid(resource) then
+ do_403()
+end
+
+--try to make all file openings from now on relative to the working directory.
+resource = "./" .. resource
+
+--If it's a directory, try to load index.html from it.
+if resource:sub(-1) == "/" then
+ resource = resource .. '/index.html'
+end
+
+--try to open the file...
+f = io.open(resource, "rb")
+if f == nil then
+ do_404() --opening file failed, throw a 404.
+end
+
+--and output it all.
+make_response({
+ ["data"] = function() return f:read(1024) end,
+ ["headers"] = {["Content-type"] = guess_mime(resource)},
+})
diff --git a/ncat/scripts/log_ips.sh b/ncat/scripts/log_ips.sh
new file mode 100644
index 0000000..8b0f0e5
--- /dev/null
+++ b/ncat/scripts/log_ips.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+LOGFILE="log_ips.log"
+
+MSG="[`date`] Incoming connection from $NCAT_REMOTE_ADDR:$NCAT_REMOTE_PORT"
+
+echo $MSG >&2
+echo $MSG >> $LOGFILE
+
+echo "Yeah, hi."
diff --git a/ncat/scripts/p0fme.py b/ncat/scripts/p0fme.py
new file mode 100755
index 0000000..5f4d42e
--- /dev/null
+++ b/ncat/scripts/p0fme.py
@@ -0,0 +1,97 @@
+#!/usr/bin/python
+
+from __future__ import print_function # logging, python2-only.
+
+"""
+A script that reads data generated by p0f -f p0f.log, looking for all entries
+about an IP read from NCAT_REMOTE_ADDR environment variable. Then it prints out
+all the information it has found. To try it out, run "p0f -i any -o p0f.log"
+and ncat -l -k --sh-exec "python p0fme.py".
+
+Script tested under Python versions 2.7 and 3.3.
+"""
+
+P0F_LOG_FILE = "p0f.log"
+
+import datetime # logging
+import sys # logging
+import os # environ
+import time # sleeping to wait for data
+import sys # to flush STDOUT
+
+
+def expand_ipv6(ip):
+ """
+ Expands short IPv6 address like ::1 into an expanded form without trailing
+ zeros. Copied from:
+
+ http://svn.python.org/projects/python/tags/r31b1/Lib/ipaddr.py
+
+ (Py3 standard library; added some modifications to match p0f's output data)
+ """
+
+ new_ip = []
+ hextet = ip.split('::')
+ sep = len(hextet[0].split(':')) + len(hextet[1].split(':'))
+ new_ip = hextet[0].split(':')
+
+ for _ in range(8 - sep):
+ new_ip.append('0')
+ new_ip += hextet[1].split(':')
+
+ # Now need to make sure every hextet is 4 lower case characters.
+ # If a hextet is < 4 characters, we've got missing leading 0's.
+ ret_ip = []
+ for hextet in new_ip:
+ if hextet == '':
+ hextet = '0'
+ ret_ip.append(hextet.lower())
+ return ':'.join(ret_ip)
+
+
+def split_by_equals(str_):
+ ret = str_.split('=')
+ return ret[0], ''.join(ret[1:])
+
+if __name__ == "__main__":
+ try:
+ ip = os.environ['NCAT_REMOTE_ADDR']
+ if os.environ['NCAT_PROTO'] != 'TCP':
+ sys.exit("ERROR: This script works for TCP servers only!")
+ except KeyError:
+ sys.exit("ERROR: This script has to be run from inside of Ncat.")
+ print("[%s] Got a request from %s" % (
+ datetime.datetime.now().isoformat(' '), ip), file=sys.stderr)
+
+ print("Hold on, I'm collecting data on you...")
+ sys.stdout.flush()
+ time.sleep(3.0)
+
+ if ':' in ip: # We need to expand IPv6 addresses in a specific way.
+ ip = expand_ipv6(ip)
+ result = {}
+
+ # Reading the log backward will give us more recent results.
+ for line in reversed(open(P0F_LOG_FILE).readlines()):
+
+ without_date = line.split('] ')
+ if without_date == ['\n']:
+ continue
+ without_date = ''.join(without_date[1:])
+
+ # Create a key-value dictionary out of the '|'-separated substrings.
+ properties = dict(map(split_by_equals, without_date.split('|')))
+
+ if not properties['cli'].startswith(ip):
+ continue # Not the IP we're looking for, check next one.
+
+ for key in properties:
+ if not key in result or result[key] == '???':
+ result[key] = properties[key]
+
+ if not result:
+ print("Got nothing on you. Try again and I will, though.")
+
+ # Now that we've finished, print out the results.
+ for key in sorted(result):
+ print("%s: %s" % (key, result[key].rstrip()))
diff --git a/ncat/scripts/whatismyip.lua b/ncat/scripts/whatismyip.lua
new file mode 100644
index 0000000..13ade22
--- /dev/null
+++ b/ncat/scripts/whatismyip.lua
@@ -0,0 +1,4 @@
+--A "what is my IP" service code. Since most web browsers put up with servers
+--not sending proper HTTP headers, you can simply query the service with it.
+
+print(os.getenv "NCAT_REMOTE_ADDR")