| 1 | #!/usr/bin/env lua |
| 2 | |
| 3 | function softreq(...) local ok, lib = pcall(require, ...); if ok then return lib; else |
| 4 | return nil, lib; end end |
| 5 | |
| 6 | -- Required to be able to find packages installed with luarocks |
| 7 | if not softreq "luarocks.loader" then -- LuaRocks 2.x |
| 8 | softreq "luarocks.require"; -- LuaRocks <1.x |
| 9 | end |
| 10 | |
| 11 | local socket = softreq "socket"; |
| 12 | |
| 13 | -- Defaults -- |
| 14 | default_port = 5222; |
| 15 | default_namespace = "jabber:client"; |
| 16 | default_timeout = 1.5; |
| 17 | |
| 18 | -- Print debug output -- |
| 19 | function debug() end -- Keep debug output quiet by default |
| 20 | |
| 21 | -- Command-line flag handlers -- |
| 22 | local flags = {}; |
| 23 | function flags.d() -- Show debug |
| 24 | debug = print; |
| 25 | end |
| 26 | |
| 27 | function flags.s() -- Use s2s |
| 28 | -- Use s2s |
| 29 | default_port = 5269; |
| 30 | default_namespace = "jabber:server"; |
| 31 | end |
| 32 | |
| 33 | function flags.t(arg) |
| 34 | default_timeout = tonumber(arg[1] or 1); |
| 35 | return 1; |
| 36 | end |
| 37 | |
| 38 | function flags.help() |
| 39 | print("XMPP Server Fingerprinter"); |
| 40 | print("(C) 2010 Matthew Wild. Licensed under the MIT/X11 License"); |
| 41 | print(""); |
| 42 | print("Syntax: "..arg[0].." [OPTIONS] ADDRESS [PORT]"); |
| 43 | print(""); |
| 44 | print("ADDRESS must be the network address or IP to connect to, SRV lookup"); |
| 45 | print("is not supported."); |
| 46 | print(""); |
| 47 | print("PORT is the port number to connect to. Defaults to 5222, or 5269 for"); |
| 48 | print("server-to-server testing."); |
| 49 | print(""); |
| 50 | print("Options:"); |
| 51 | print(" -s:", "Server-to-server port testing (less reliable)"); |
| 52 | print("-t NUM:", "Set timeout to NUM seconds"); |
| 53 | print(" -d:", "Enable debug output (useful for adding new fingerprints)"); |
| 54 | print(""); |
| 55 | end |
| 56 | |
| 57 | -- Process flags -- |
| 58 | local function process_args(arg) |
| 59 | local f = arg[1] and arg[1]:match("^%-%-?(%S+)"); |
| 60 | if f then |
| 61 | table.remove(arg, 1); |
| 62 | if flags[f] then |
| 63 | local used = flags[f](arg); |
| 64 | if used then |
| 65 | for i=1,used do |
| 66 | table.remove(arg, 1); |
| 67 | end |
| 68 | end |
| 69 | end |
| 70 | return process_args(arg); |
| 71 | end |
| 72 | end |
| 73 | if #arg > 0 then |
| 74 | process_args(arg); |
| 75 | else |
| 76 | flags.help(); |
| 77 | os.exit(1); |
| 78 | end |
| 79 | |
| 80 | if #arg < 1 then |
| 81 | io.stderr:write("ERROR: No server address specified.\n"); |
| 82 | os.exit(1); |
| 83 | end |
| 84 | |
| 85 | local received_cache = {}; |
| 86 | local function _test(server, port, question, answer) |
| 87 | local received = received_cache[question]; |
| 88 | if not received then |
| 89 | debug("||", "Connecting to "..server..":"..port.."..."); |
| 90 | local conn = assert(socket.connect(server, port), "Failed to connect to "..arg[1]); |
| 91 | conn:send(question); |
| 92 | debug(">>", tostring(question)); |
| 93 | conn:settimeout(default_timeout); |
| 94 | local data, err, partial = conn:receive("*a"); |
| 95 | received = data or partial or ""; |
| 96 | conn:close(); |
| 97 | received_cache[question] = received; |
| 98 | if received ~= "" then |
| 99 | debug("<<", received); |
| 100 | else |
| 101 | debug("!!", tostring(err)); |
| 102 | end |
| 103 | end |
| 104 | if received == "" then |
| 105 | return answer == false |
| 106 | elseif answer then |
| 107 | return received:match(answer); |
| 108 | end |
| 109 | end |
| 110 | |
| 111 | function test(question, answer) |
| 112 | return _test(arg[1], tonumber(arg[2]) or default_port, question, answer); |
| 113 | end |
| 114 | |
| 115 | function server_comment(comment) |
| 116 | table.insert(getfenv(2).server_comments, comment); |
| 117 | end |
| 118 | |
| 119 | function literal(s) |
| 120 | return s:gsub("%p", "%%%1"); |
| 121 | end |
| 122 | |
| 123 | ----- |
| 124 | q_invalid_xml = "abc\n"; |
| 125 | q_empty_message_tag = "<message/>"; |
| 126 | q_invalid_host = "<stream:stream xmlns='"..default_namespace.."' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' id='abc' to='0.0.0.0'>"; |
| 127 | ----- |
| 128 | |
| 129 | testers = {}; |
| 130 | |
| 131 | dofile "servers/ejabberd.lua" |
| 132 | dofile "servers/google_talk.lua" |
| 133 | dofile "servers/isode_mlink.lua" |
| 134 | dofile "servers/jabberd14.lua" |
| 135 | dofile "servers/jabberd2.lua" |
| 136 | dofile "servers/prosody.lua" |
| 137 | dofile "servers/tigase.lua" |
| 138 | dofile "servers/djabberd.lua" |
| 139 | dofile "servers/openfire.lua" |
| 140 | dofile "servers/jabber_xcp.lua" |
| 141 | dofile "servers/soapbox.lua" |
| 142 | dofile "servers/facebook.lua" |
| 143 | |
| 144 | function test_fingerprint(server) |
| 145 | local tester = testers[server]; |
| 146 | local env = setmetatable({server_comments = {}}, { __index = _G }); |
| 147 | setfenv(tester, env); |
| 148 | local ok, err = pcall(tester); |
| 149 | if not ok then |
| 150 | print("Error while testing for "..server..": "..err); |
| 151 | end |
| 152 | return env; |
| 153 | end |
| 154 | |
| 155 | ------------------------------------------------------------------ |
| 156 | |
| 157 | local results = {}; |
| 158 | for server in pairs(testers) do |
| 159 | local res = test_fingerprint(server); |
| 160 | if res.server_name then |
| 161 | table.insert(results, res); |
| 162 | end |
| 163 | end |
| 164 | |
| 165 | if #results > 1 then |
| 166 | print("Note... multiple matches, could be any:"); |
| 167 | end |
| 168 | |
| 169 | for _, result in ipairs(results) do |
| 170 | io.write(result.server_name); |
| 171 | if result.server_min_version then |
| 172 | io.write(", version "..result.server_min_version.." or later"); |
| 173 | elseif result.server_version then |
| 174 | io.write(", version "..result.server_version); |
| 175 | end |
| 176 | io.write("\n"); |
| 177 | if #result.server_comments > 0 then |
| 178 | print(" Notes:\n", table.concat(result.server_comments, "\n ")); |
| 179 | end |
| 180 | end |