diff --git a/CHANGELOG b/CHANGELOG index dcaa08b70..1b56eff6e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,11 @@ [NOT YET RELEASED] +o [NSE] Added the scripts couchdb-databases and couchdb-stats by + Martin Holst Swende, which list CouchDB databases and show access + statistics. These scripts use the new json.lua library, also by + Martin. + o Fixed the parsing of libdnet DLPI interface names that contain more than one string of digits. Joe Dietz reported that an interface with the name e1000g0 was causing the error message diff --git a/nselib/json.lua b/nselib/json.lua new file mode 100644 index 000000000..32eb184d7 --- /dev/null +++ b/nselib/json.lua @@ -0,0 +1,513 @@ +--- Library methods for handling Json data. It handles +-- json encoding and decoding +-- +-- There is a test-section at the bottom which shows some example +-- parsing. If you want to parse json, you can test it by pasting sample json +-- into the TESTS table and run the test() method + +-- More info about Json at http://www.ietf.org/rfc/rfc4627.txt + +-- !NOTE! Due to some differences between javascript and lua, there are some +-- conversion problems for null-values. Null-values in javascript are not equal to +-- nil values in lua. Nil is more corresponding to javascript 'undefined'. + +-- As an example : +-- Executing the following javascript : var a= {b:null}; alert(a.b + " != " + a.c); +-- yields the string "null != undefined". Assigning a table a nil value in lua basically +-- removes it from the table, without leaving the key in place. I.e, +-- >a ={b=nil} +-- >print(a.b, a.c) +-- nil nil + +-- !!!Therefore, javascript null values are represented by json.NULL.!!! +-- +-- @author Martin Holst Swende +-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html +-- TODO: Unescape/escape unicode +-- Version 0.4 + +-- Created 01/25/2010 - v0.1 - created by Martin Holst Swende +-- Heavily modified 02/22/2010 - v0.3. Rewrote the parser into an OO-form, to not have to handle +-- all kinds of state with parameters and return values. +-- Modified 02/27/2010 - v0.4 Added unicode handling (written by David Fifield). Renamed toJson +-- and fromJson intogenerate() and parse(), implemented more proper numeric parsing and added some more error checking. + +module("json", package.seeall) +require("bit") +require("nsedebug") + +--Some local shortcuts +local function dbg(str,...) + stdnse.print_debug("Json:"..str, unpack(arg)) +end +local function d4(str,...) + if nmap.debugging() > 3 then dbg(str,unpack(arg)) end +end +local function d3(str,...) + if nmap.debugging() > 2 then dbg(str,unpack(arg)) end +end + +--local dbg =stdnse.print_debug +local function dbg_err(str,...) + stdnse.print_debug("json-ERR:"..str, unpack(arg)) +end + + -- Javascript null representation, see explanation above +NULL = {} + +local ESCAPE_TABLE={ + ['\\"']='"', + ['\\/']= '/', + ['\\b']=string.char(0x08), + ['\\f']=string.char(0x0C), + ['\\n']=string.char(0x0A), + ['\\r']=string.char(0x0D), + ['\\t']=string.char(0x09), +} +-- Escapes a string +--@param str the string +--@return a string where the special chars have been escaped +local function escape(str) + + --This should be done first, so not in table + str = string.gsub(str, '\\\\', '\\')-- Escape \ + + for esc,char in pairs(ESCAPE_TABLE) do + str =str:gsub(char,esc) + end + --!TODO : Unicode escape + return ('"%s"'):format(str) +end + +-- Creates json data from an object +--@param object a table containing data +--@return a string containing valid json +function generate(obj) + + -- NULL-check must be performed before + -- checking type == table, since the NULL-object + -- is a table + if obj == NULL then + return "null" + elseif obj == false then + return "false" + elseif obj == true then + return "true" + elseif type(obj) == "number" then + return string.format("%g", obj) + elseif type(obj) == "string" then + return escape(obj) + elseif type(obj) == "table" then + local k, v, elems + elems = {} + if #obj > 0 then + -- Array + for _, v in ipairs(obj) do + elems[#elems + 1] = generate(v) + end + return "[" .. table.concat(elems, ", ") .. "]" + else + -- Object + for k, v in pairs(obj) do + elems[#elems + 1] = escape(k) .. ": " .. generate(v) + end + return "{" .. table.concat(elems, ", ") .. "}" + end + else + error("Unknown data type in generate") + end +end + +local esc_chars = +{ + ['\\'] = '\\\\', + n =string.char(0x0A), + f=string.char(0x0C), + r=string.char(0x0D), + t=string.char(0x09), + b=string.char(0x08), + ['"'] = '\"', +} +-- This is the parser, implemented in OO-form to deal with state better +Json = {} +-- Constructor +function Json:new(input) + local o = {} + setmetatable(o, self) + self.__index = self + o.input = input + o.pos = 1 -- Pos is where the NEXT letter will be read + return o +end + +-- Gets next character and ups the position +--@return next character +function Json:next() + self.pos = self.pos+1 + return self.input:sub(self.pos-1, self.pos-1) +end +-- Updates the position to next non whitespace position +function Json:eatWhiteSpace() + --Find next non-white char + local a,b = self.input:find("%S",self.pos) + if not a then + self:syntaxerror("Empty data") + return + end + self.pos = a +end + +-- Jumps to a specified position +--@param position where to go +function Json:jumpTo(position) + self.pos = position +end + +-- Returns next character, but without upping position +--@return next character +function Json:peek() + return self.input:sub(self.pos, self.pos) +end + +--@return true if more input is in store +function Json:hasMore() + return self.input:len() >= self.pos +end + +-- Checks that the following input is equal to a string +-- and updates position so next char will be after that string +-- If false, triggers a syntax error +--@param str the string to test +function Json:assertStr(str) + local content = self.input:sub(self.pos,self.pos+str:len()-1) + if(content == str) then-- All ok + -- Jump forward + self:jumpTo(self.pos+str:len()) + return + end + self:syntaxerror(("Expected '%s' but got '%s'"):format( str, content)) +end + +-- Trigger a syntax error +function Json:syntaxerror(reason) + self.error = ("Syntax error near pos %d: %s input: %s"):format( self.pos, reason, self.input) + dbg(self.error) +end +-- Check if any errors has occurred +function Json:errors() + return self.error ~= nil +end +-- This is where the parsing begins. +--@return the parsed object or puts error messages in self.error +function Json:parseStart() + return self:parseValue(true) +end + +-- Parses a value +--@param first if this is the first time the input is read, +-- the value must be array or object, otherwise a syntax error will +-- be triggered +--@return the parsed value +function Json:parseValue(first) + + -- If first is set to true, this is the first + -- object received. Therefore, this must be + -- either an Object or Array ( first chars { or [ ) + + self:eatWhiteSpace() + local c = self:peek() + + if(first and c ~= '{' and c ~= '[') then + self:syntaxerror(("Json must start with object or array (started with %s)"):format(c)) + return + end + local value + if c == '{' then + value = self:parseObject() + elseif c == '[' then + value = self:parseArray() + elseif c == '"' then + value = self:parseString() + elseif c == 'n' then + self:assertStr("null") + value = NULL + elseif c == 't' then + self:assertStr("true") + value = true + elseif c == 'f' then + self:assertStr("false") + value = false + else -- numeric + -- number = [ minus ] int [ frac ] [ exp ] + local a,b =self.input:find("-?%d+%.?%d*[eE]?[+-]?%d*", self.pos) + if not a or not b then + self:syntaxerror("Error 1 parsing numeric value") + return + end + value = tonumber(self.input:sub(a,b)) + if(value == nil) then + self:syntaxerror("Error 2 parsing numeric value") + return + end + self:jumpTo(b+1) + end + return value +end +-- Parses a json object {} +--@return the object (or triggers a syntax error) +function Json:parseObject() + local object = {} + local _= self:next() -- Eat { + + while(self:hasMore() and not self:errors()) do + self:eatWhiteSpace() + local c = self:peek() + if(c == '}') then -- Empty object, probably + self:next() -- Eat it + return object + end + + if(c ~= '"') then + self:syntaxerror(("Expected '\"', got '%s'"):format(c)) + return + end + + local key = self:parseString() + if self:errors() then + return + end + self:eatWhiteSpace() + c = self:next() + if(c ~= ':') then + self:syntaxerror("Expected ':' got "..c) + return + end + local value = self:parseValue() + + if self:errors() then + return + end + + object[key] = value + + self:eatWhiteSpace() + c = self:next() + -- Valid now is , or } + if(c == '}') then + return object + end + if(c ~= ',') then + self:syntaxerror("Expected ',' or '}', got "..c) + return + end + end +end +-- Parses a json array [] or triggers a syntax error +--@return the array object +function Json:parseArray() + local array = {} + self:next() + while(self:hasMore() and not self:errors()) do + self:eatWhiteSpace() + if(self:peek() == ']') then -- Empty array, probably + self:next() + break + end + local value = self:parseValue() + if self:errors() then + return + end + table.insert(array, value) + self:eatWhiteSpace() + local c = self:next() + -- Valid now is , or ] + if(c == ']') then return array end + if(c ~= ',') then + self:syntaxerror(("Expected ',' but got '%s'"):format(c)) + return + end + end + return array +end + +-- Decode a Unicode escape, assuming that self.pos starts just after the +-- initial \u. May consume an additional escape in the case of a UTF-16 +-- surrogate pair. See RFC 2781 for UTF-16. +function Json:parseUnicodeEscape() + local n, cp + local hex, lowhex + local s, e + + s, e, hex = self.input:find("^(....)", self.pos) + if not hex then + self:syntaxerror(("EOF in Unicode escape \\u%s"):format(self.input:sub(self.pos))) + return + end + n = tonumber(hex, 16) + if not n then + self:syntaxerror(("Bad unicode escape \\u%s"):format(hex)) + return + end + cp = n + self.pos = e + 1 + if n < 0xD800 or n > 0xDFFF then + return cp + end + if n >= 0xDC00 and n <= 0xDFFF then + self:syntaxerror(("Not a Unicode character: U+%04X"):format(cp)) + return + end + + -- Beginning of a UTF-16 surrogate. + s, e, lowhex = self.input:find("^\\u(....)", self.pos) + if not lowhex then + self:syntaxerror(("Bad unicode escape \\u%s (missing low surrogate)"):format(hex)) + return + end + n = tonumber(lowhex, 16) + if not n or not (n >= 0xDC00 and n <= 0xDFFF) then + self:syntaxerror(("Bad unicode escape \\u%s\\u%s (bad low surrogate)"):format(hex, lowhex)) + return + end + self.pos = e + 1 + cp = 0x10000 + bit.band(cp, 0x3FF) * 0x400 + bit.band(n, 0x3FF) + -- also remove last " + return cp +end + +-- Encode a Unicode code point to UTF-8. See RFC 3629. +-- Does not check that cp is a real charaacter; that is, doesn't exclude the +-- surrogate range U+D800 - U+DFFF and a handful of others. +local function utf8_enc(cp) + local bytes = {} + local n, mask + + if cp % 1.0 ~= 0.0 or cp < 0 then + -- Only defined for nonnegative integers. + return nil + elseif cp <= 0x7F then + -- Special case of one-byte encoding. + return string.char(cp) + elseif cp <= 0x7FF then + n = 2 + mask = 0xC0 + elseif cp <= 0xFFFF then + n = 3 + mask = 0xE0 + elseif cp <= 0x10FFFF then + n = 4 + mask = 0xF0 + else + return nil + end + + while n > 1 do + bytes[n] = string.char(0x80 + bit.band(cp, 0x3F)) + cp = bit.rshift(cp, 6) + n = n - 1 + end + bytes[1] = string.char(mask + cp) + + return table.concat(bytes) +end + +-- Parses a json string +-- @return the string or triggers syntax error +function Json:parseString() + + local val = '' + local c = self:next() + assert( c == '"') + while(self:hasMore()) do + local c = self:next() + + if(c == '"') then -- end of string + break + elseif(c == '\\') then-- Escaped char + local d = self:next() + if esc_chars[d] ~= nil then + val = val .. esc_chars[d] + elseif d == 'u' then -- Unicode chars + local codepoint = self:parseUnicodeEscape() + if not codepoint then + return + end + val = val .. utf8_enc(codepoint) + else + self:syntaxerror(("Undefined escape character '%s'"):format(d)) + return false + end + else -- Char + val = val .. c + end + end + return val +end +--- Parses json data into an object form +-- This is the method you probably want to use if you +-- use this library from a script. +--@param input : a json string +--@return status : true if ok, false if bad +--@return an object representing the json, or error message +function parse(data) + local parser = Json:new(data) + local result = parser:parseStart() + if(parser.error) then + return false, parser.error + end + return true, result +end + +---------------------------------------------------------------------------------- +-- Test-code for debugging purposes below +---------------------------------------------------------------------------------- + +local TESTS = { + '{"a":1}', + '{"a":true}', + '{"a": false}', + '{"a": null \r\n, \t "b" \f:"ehlo"}', + '{"a\\"a":"a\\"b\\"c\\"d"}', + '{"foo":"gaz\\"onk", "pi":3.14159,"hello":{ "wo":"rld"}}', + '{"a":1, "b":2}', + '{"foo":"gazonk", "pi":3.14159,"hello":{ "wo":"rl\\td"}}', + '[1,2,3,4,5,null,false,true,"\195\164\195\165\195\182\195\177","bar"]', + '[]',-- This will yield {} in toJson, since in lua there is only one basic datatype - and no difference when empty + '{}', + + '', -- error + 'null', -- error + '"abc"', -- error + '{a":1}', -- error + '{"a" bad :1}', -- error + '["a\\\\t"]', -- Should become Lua {"a\t"} + '[0.0.0]', -- error + '[-1]', + '[-1.123e-2]', + '[5e3]', + '[5e+3]', + '[5E-3]', + '[5.5e3]', + '["a\\\\"]', -- Should become Lua {"a\"} + '{"a}": 1}', -- Should become Lua {"a}" = 1} + '["key": "value"]', -- error + '["\\u0041"]', -- Should become Lua {"A"} + '["\\uD800"]', -- error + '["\\uD834\\uDD1EX"]', -- Should become Lua {"\240\157\132\158X"} +} +function test() + print("Tests running") + local i,v,res,status + for i,v in pairs(TESTS) do + print("----------------------------") + print(v) + status,res = parse(v) + if not status then print( res) end + if(status) then + print(generate(res)) + else + print("Error:".. res) + end + end +end +test() \ No newline at end of file diff --git a/scripts/couchdb-databases.nse b/scripts/couchdb-databases.nse new file mode 100644 index 000000000..2ed191541 --- /dev/null +++ b/scripts/couchdb-databases.nse @@ -0,0 +1,93 @@ +description = [[ +Gets database tables from a CouchDB database +For more info about the CouchDB HTTP Api, see +http://wiki.apache.org/couchdb/HTTP_database_API + +]] +--- +-- @usage +-- nmap -p 5984 --script "couchdb-databases.nse" +-- @output +-- PORT STATE SERVICE REASON +-- 5984/tcp open unknown syn-ack +-- | couchdb-databases: +-- | 1 = test_suite_db +-- | 2 = test_suite_db_a +-- | 3 = test_suite_db/with_slashes +-- | 4 = moneyz +-- | 5 = creditcards +-- | 6 = test_suite_users +-- |_ 7 = test_suite_db_b +--@version 0.2 +-- Created 01/12/2010 - v0.1 - created by Martin Holst Swende + +-- TODO : Authentication not implemented + +author = "Martin Holst Swende" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"discovery", "safe"} +require "shortport" +require "http" +require "json" + +portrule = shortport.port_or_service({5984}) +-- Some lazy shortcuts +local function dbg(str,...) + stdnse.print_debug("couchdb-get-tables:"..str, unpack(arg)) +end + +local DISCARD = {} +--- Removes uninteresting data from the table +-- uses the DISCARD table above to see what +-- keys should be omitted from the results +-- @param data a table containg data +--@return another table containing data, with some keys removed +local function queryResultToTable(data) + local result = {} + for k,v in pairs(data) do + dbg("(%s,%s)",k,tostring(v)) + if DISCARD[k] ~= 1 then + if type(v) == 'table' then + table.insert(result,k) + table.insert(result,queryResultToTable(v)) + else + table.insert(result,(("%s = %s"):format(tostring(k), tostring(v)))) + end + end + end + return result +end + +action = function(host, port) + local data, result, err + dbg("Requesting all databases") + data = http.get( host, port, '/_all_dbs' ) + + -- check that body was received + if not data.body or data.body == "" then + local msg = ("%s did not respond with any data."):format(host.targetname or host.ip ) + dbg( msg ) + return msg + end + + -- The html body should look like this : + -- ["somedatabase", "anotherdatabase"] + + local status, result = json.parse(data.body) + if not status then + dbg(result) + return result + end + + -- Here we know it is a couchdb + port.version.name ='httpd' + port.version.product='Apache CouchDB' + nmap.set_port_version(host,port,'hardmatched') + + -- We have a valid table in result containing the parsed json + -- now, get all the interesting bits + + result = queryResultToTable(result) + + return stdnse.format_output(true, result ) +end diff --git a/scripts/couchdb-stats.nse b/scripts/couchdb-stats.nse new file mode 100644 index 000000000..16d92f4df --- /dev/null +++ b/scripts/couchdb-stats.nse @@ -0,0 +1,166 @@ +description = [[ +Gets database statistics from a CouchDB database +For more info about the CouchDB HTTP Api, see +http://wiki.apache.org/couchdb/Runtime_Statistics +and +http://wiki.apache.org/couchdb/HTTP_database_API + +]] +--- +-- @usage +-- nmap -p 5984 --script "couchdb-stats.nse" +-- @output +-- PORT STATE SERVICE REASON +-- 5984/tcp open httpd syn-ack +-- | couchdb-stats: +-- | httpd_request_methods +-- | GET (number of HTTP GET requests) +-- | current = 5 +-- | count = 1617 +-- | couchdb +-- | request_time (length of a request inside CouchDB without MochiWeb) +-- | current = 1 +-- | count = 5 +-- | httpd_status_codes +-- | 200 (number of HTTP 200 OK responses) +-- | current = 5 +-- | count = 1617 +-- | httpd +-- | requests (number of HTTP requests) +-- | current = 5 +-- | count = 1617 +-- |_ Authentication : NOT enabled ('admin party') +--@version 0.3 +-- +-- Created 01/20/2010 - v0.1 - created by Martin Holst Swende +-- Modified 07/02/2010 - v0.2 - added test if auth is enabled, compacted output a bit (mhs) + +author = "Martin Holst Swende" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"discovery", "safe"} +require "shortport" +require "http" +require "json" +portrule = shortport.port_or_service({5984}) +-- Some lazy shortcuts +local function dbg(str,...) + stdnse.print_debug("couchdb-stats:"..str, unpack(arg)) +end + + +local DISCARD = {stddev=1,min=1,max=1, mean=1} +--- Removes uninteresting data from the table +-- uses the DISCARD table above to see what +-- keys should be omitted from the results +-- @param data a table containg data +--@return another table containing data, with some keys removed +local function queryResultToTable(data) + local result = {} + for k,v in pairs(data) do + dbg("(%s,%s)",k,tostring(v)) + if DISCARD[k] ~= 1 then + if type(v) == 'table' then + if v["description"] ~= nil then + k = string.format("%s (%s)",tostring(k), tostring(v["description"])) + v["description"] = nil + end + table.insert(result,k) + table.insert(result,queryResultToTable(v)) + else + table.insert(result,(("%s = %s"):format(tostring(k), tostring(v)))) + end + end + end + return result +end + + +action = function(host, port) + local data, result, err + + data = http.get( host, port, '/_stats' ) + + -- check that body was received + if not data.body or data.body == "" then + local msg = ("%s did not respond with any data."):format(hostrgetname or host.ip ) + dbg( msg ) + return msg + end + + -- The html body should look like this : + -- + --{"httpd_status_codes":{"200":{"current":10,"count":29894,"mean":0.0003345152873486337,"min":0,"max":1,"stddev":0.01828669972606202,"description":"number of HTTP 200 OK responses"},"500":{"current":1,"count":28429,"mean":0.00003517534911534013,"min":0,"max":1,"stddev":0.005930776661631644,"description":"number of HTTP 500 Internal Server Error responses"}},"httpd_request_methods":{"GET":{"current":12,"count":29894,"mean":0.00040141834481835866,"min":0,"max":2,"stddev":0.02163701147572207,"description":"number of HTTP GET requests"}},"httpd":{"requests":{"current":12,"count":29894,"mean":0.00040141834481835866,"min":0,"max":2,"stddev":0.02163701147572207,"description":"number of HTTP requests"}},"couchdb":{"request_time":{"current":23,"count":12,"mean":32.58333333333333,"min":1,"max":287,"stddev":77.76723638882608,"description":"length of a request inside CouchDB without MochiWeb"}}} + + local status, result = json.parse(data.body) + if not status then + dbg(result) + return result + end + + -- Here we know it is a couchdb + port.version.name ='httpd' + port.version.product='Apache CouchDB' + nmap.set_port_version(host,port,'hardmatched') + + -- We have a valid table in result containing the parsed json + -- now, get all the interesting bits + + result = queryResultToTable(result) + + -- Additionally, we can check if authentication is used : + -- The following actions are restricted if auth is used +-- create db (PUT /database) +-- delete db (DELETE /database) +-- Creating a design document (PUT /database/_design/app) +-- Updating a design document (PUT /database/_design/app?rev=1-4E2) +-- Deleting a design document (DELETE /database/_design/app?rev=1-6A7) +-- Triggering compaction (POST /_compact) +-- Reading the task status list (GET /_active_tasks) +-- Restart the server (POST /_restart) +-- Read the active configuration (GET /_config) +-- Update the active configuration (PUT /_config) + + data = http.get( host, port, '/_config' ) + local status, authresult = json.parse(data.body) + + -- If authorization is used, we should get back something like + -- {"error":"unauthorized","reason":"You are not a server admin."} + -- Otherwise, a *lot* of data, : +-- {"httpd_design_handlers":{"_info":"{couch_httpd_db, handle_design_info_req}", +-- "_list":"{couch_httpd_show, handle_view_list_req}","_show":"{couch_httpd_show, handle_doc_show_req}", +-- "_update":"{couch_httpd_show, handle_doc_update_req}","_view":"{couch_httpd_view, handle_view_req}"}, +-- "httpd_global_handlers":{"/":"{couch_httpd_misc_handlers, handle_welcome_req, <<\"Welcome\">>}", +-- "_active_tasks":"{couch_httpd_misc_handlers, handle_task_status_req}", +-- "_all_dbs":"{couch_httpd_misc_handlers, handle_all_dbs_req}", +-- "_config":"{couch_httpd_misc_handlers, handle_config_req}", +-- "_log":"{couch_httpd_misc_handlers, handle_log_req}","_oauth":"{couch_httpd_oauth, handle_oauth_req}", +-- "_replicate":"{couch_httpd_misc_handlers, handle_replicate_req}","_restart":"{couch_httpd_misc_handlers, handle_restart_req}", +-- "_session":"{couch_httpd_auth, handle_session_req}","_sleep":"{couch_httpd_misc_handlers, handle_sleep_req}", +-- "_stats":"{couch_httpd_stats_handlers, handle_stats_req}","_user":"{couch_httpd_auth, handle_user_req}", +-- "_utils":"{couch_httpd_misc_handlers, handle_utils_dir_req, \"/usr/share/couchdb/www\"}", +-- "_uuids":"{couch_httpd_misc_handlers, handle_uuids_req}","favicon.ico":"{couch_httpd_misc_handlers, handle_favicon_req, \"/usr/share/couchdb/www\"}"}, +-- "query_server_config":{"reduce_limit":"true"},"log":{"file":"/var/log/couchdb/0.10.0/couch.log","level":"info"}, +-- "query_servers":{"javascript":"/usr/bin/couchjs /usr/share/couchdb/server/main.js"}, +-- "daemons":{"batch_save":"{couch_batch_save_sup, start_link, []}","db_update_notifier":"{couch_db_update_notifier_sup, start_link, []}", +-- "external_manager":"{couch_external_manager, start_link, []}","httpd":"{couch_httpd, start_link, []}", +-- "query_servers":"{couch_query_servers, start_link, []}","stats_aggregator":"{couch_stats_aggregator, start, []}", +-- "stats_collector":"{couch_stats_collector, start, []}","view_manager":"{couch_view, start_link, []}"}, +-- "httpd":{"WWW-Authenticate":"Basic realm=\"administrator\"","authentication_handlers":"{couch_httpd_oauth, oauth_authentication_handler}, {couch_httpd_auth, default_authentication_handler}", +-- "bind_address":"127.0.0.1","default_handler":"{couch_httpd_db, handle_request}","port":"5984"},"httpd_db_handlers":{"_changes":"{couch_httpd_db, handle_changes_req}", +-- "_compact":"{couch_httpd_db, handle_compact_req}","_design":"{couch_httpd_db, handle_design_req}","_temp_view":"{couch_httpd_view, handle_temp_view_req}", +-- "_view":"{couch_httpd_view, handle_db_view_req}","_view_cleanup":"{couch_httpd_db, handle_view_cleanup_req}"}, +-- "couch_httpd_auth":{"authentication_db":"users","require_valid_user":"false","secret":"replace this with a real secret in your local.ini file"}, +-- "couchdb":{"batch_save_interval":"1000","batch_save_size":"1000","database_dir":"/var/lib/couchdb/0.10.0","delayed_commits":"true", +-- "max_attachment_chunk_size":"4294967296","max_dbs_open":"100","max_document_size":"4294967296", +-- "os_process_timeout":"5000","util_driver_dir":"/usr/lib/couchdb/erlang/lib/couch-0.10.0/priv/lib","view_index_dir":"/var/lib/couchdb/0.10.0"}} + local auth = "Authentication : %s" + local authEnabled = "unknown" + + if(status) then + if(authresult["error"] == "unauthorized") then authEnabled = "enabled" + elseif (authresult["httpd_design_handlers"] ~= nil) then authEnabled = "NOT enabled ('admin party')" + end + end + table.insert(result, auth:format(authEnabled)) + return stdnse.format_output(true, result ) +end