plugins/mod_console.lua
changeset 3758 41f174b61b6a
parent 3757 e8be634e217a
child 3759 1f7305784e12
equal deleted inserted replaced
3757:e8be634e217a 3758:41f174b61b6a
     1 -- Prosody IM
       
     2 -- Copyright (C) 2008-2010 Matthew Wild
       
     3 -- Copyright (C) 2008-2010 Waqas Hussain
       
     4 -- 
       
     5 -- This project is MIT/X11 licensed. Please see the
       
     6 -- COPYING file in the source package for more information.
       
     7 --
       
     8 
       
     9 module.host = "*";
       
    10 
       
    11 local _G = _G;
       
    12 
       
    13 local prosody = _G.prosody;
       
    14 local hosts = prosody.hosts;
       
    15 local connlisteners_register = require "net.connlisteners".register;
       
    16 
       
    17 local console_listener = { default_port = 5582; default_mode = "*l"; default_interface = "127.0.0.1" };
       
    18 
       
    19 require "util.iterators";
       
    20 local jid_bare = require "util.jid".bare;
       
    21 local set, array = require "util.set", require "util.array";
       
    22 local cert_verify_identity = require "util.x509".verify_identity;
       
    23 
       
    24 local commands = {};
       
    25 local def_env = {};
       
    26 local default_env_mt = { __index = def_env };
       
    27 
       
    28 prosody.console = { commands = commands, env = def_env };
       
    29 
       
    30 local function redirect_output(_G, session)
       
    31 	local env = setmetatable({ print = session.print }, { __index = function (t, k) return rawget(_G, k); end });
       
    32 	env.dofile = function(name)
       
    33 		local f, err = loadfile(name);
       
    34 		if not f then return f, err; end
       
    35 		return setfenv(f, env)();
       
    36 	end;
       
    37 	return env;
       
    38 end
       
    39 
       
    40 console = {};
       
    41 
       
    42 function console:new_session(conn)
       
    43 	local w = function(s) conn:write(s:gsub("\n", "\r\n")); end;
       
    44 	local session = { conn = conn;
       
    45 			send = function (t) w(tostring(t)); end;
       
    46 			print = function (...)
       
    47 				local t = {};
       
    48 				for i=1,select("#", ...) do
       
    49 					t[i] = tostring(select(i, ...));
       
    50 				end
       
    51 				w("| "..table.concat(t, "\t").."\n");
       
    52 			end;
       
    53 			disconnect = function () conn:close(); end;
       
    54 			};
       
    55 	session.env = setmetatable({}, default_env_mt);
       
    56 	
       
    57 	-- Load up environment with helper objects
       
    58 	for name, t in pairs(def_env) do
       
    59 		if type(t) == "table" then
       
    60 			session.env[name] = setmetatable({ session = session }, { __index = t });
       
    61 		end
       
    62 	end
       
    63 	
       
    64 	return session;
       
    65 end
       
    66 
       
    67 local sessions = {};
       
    68 
       
    69 function console_listener.onconnect(conn)
       
    70 	-- Handle new connection
       
    71 	local session = console:new_session(conn);
       
    72 	sessions[conn] = session;
       
    73 	printbanner(session);
       
    74 	session.send(string.char(0));
       
    75 end
       
    76 
       
    77 function console_listener.onincoming(conn, data)
       
    78 	local session = sessions[conn];
       
    79 
       
    80 	-- Handle data
       
    81 	(function(session, data)
       
    82 		local useglobalenv;
       
    83 		
       
    84 		if data:match("^>") then
       
    85 			data = data:gsub("^>", "");
       
    86 			useglobalenv = true;
       
    87 		elseif data == "\004" then
       
    88 			commands["bye"](session, data);
       
    89 			return;
       
    90 		else
       
    91 			local command = data:lower();
       
    92 			command = data:match("^%w+") or data:match("%p");
       
    93 			if commands[command] then
       
    94 				commands[command](session, data);
       
    95 				return;
       
    96 			end
       
    97 		end
       
    98 
       
    99 		session.env._ = data;
       
   100 		
       
   101 		local chunkname = "=console";
       
   102 		local chunk, err = loadstring("return "..data, chunkname);
       
   103 		if not chunk then
       
   104 			chunk, err = loadstring(data, chunkname);
       
   105 			if not chunk then
       
   106 				err = err:gsub("^%[string .-%]:%d+: ", "");
       
   107 				err = err:gsub("^:%d+: ", "");
       
   108 				err = err:gsub("'<eof>'", "the end of the line");
       
   109 				session.print("Sorry, I couldn't understand that... "..err);
       
   110 				return;
       
   111 			end
       
   112 		end
       
   113 		
       
   114 		setfenv(chunk, (useglobalenv and redirect_output(_G, session)) or session.env or nil);
       
   115 		
       
   116 		local ranok, taskok, message = pcall(chunk);
       
   117 		
       
   118 		if not (ranok or message or useglobalenv) and commands[data:lower()] then
       
   119 			commands[data:lower()](session, data);
       
   120 			return;
       
   121 		end
       
   122 		
       
   123 		if not ranok then
       
   124 			session.print("Fatal error while running command, it did not complete");
       
   125 			session.print("Error: "..taskok);
       
   126 			return;
       
   127 		end
       
   128 		
       
   129 		if not message then
       
   130 			session.print("Result: "..tostring(taskok));
       
   131 			return;
       
   132 		elseif (not taskok) and message then
       
   133 			session.print("Command completed with a problem");
       
   134 			session.print("Message: "..tostring(message));
       
   135 			return;
       
   136 		end
       
   137 		
       
   138 		session.print("OK: "..tostring(message));
       
   139 	end)(session, data);
       
   140 	
       
   141 	session.send(string.char(0));
       
   142 end
       
   143 
       
   144 function console_listener.ondisconnect(conn, err)
       
   145 	local session = sessions[conn];
       
   146 	if session then
       
   147 		session.disconnect();
       
   148 		sessions[conn] = nil;
       
   149 	end
       
   150 end
       
   151 
       
   152 connlisteners_register('console', console_listener);
       
   153 
       
   154 -- Console commands --
       
   155 -- These are simple commands, not valid standalone in Lua
       
   156 
       
   157 function commands.bye(session)
       
   158 	session.print("See you! :)");
       
   159 	session.disconnect();
       
   160 end
       
   161 commands.quit, commands.exit = commands.bye, commands.bye;
       
   162 
       
   163 commands["!"] = function (session, data)
       
   164 	if data:match("^!!") and session.env._ then
       
   165 		session.print("!> "..session.env._);
       
   166 		return console_listener.onincoming(session.conn, session.env._);
       
   167 	end
       
   168 	local old, new = data:match("^!(.-[^\\])!(.-)!$");
       
   169 	if old and new then
       
   170 		local ok, res = pcall(string.gsub, session.env._, old, new);
       
   171 		if not ok then
       
   172 			session.print(res)
       
   173 			return;
       
   174 		end
       
   175 		session.print("!> "..res);
       
   176 		return console_listener.onincoming(session.conn, res);
       
   177 	end
       
   178 	session.print("Sorry, not sure what you want");
       
   179 end
       
   180 
       
   181 
       
   182 function commands.help(session, data)
       
   183 	local print = session.print;
       
   184 	local section = data:match("^help (%w+)");
       
   185 	if not section then
       
   186 		print [[Commands are divided into multiple sections. For help on a particular section, ]]
       
   187 		print [[type: help SECTION (for example, 'help c2s'). Sections are: ]]
       
   188 		print [[]]
       
   189 		print [[c2s - Commands to manage local client-to-server sessions]]
       
   190 		print [[s2s - Commands to manage sessions between this server and others]]
       
   191 		print [[module - Commands to load/reload/unload modules/plugins]]
       
   192 		print [[host - Commands to activate, deactivate and list virtual hosts]]
       
   193 		print [[server - Uptime, version, shutting down, etc.]]
       
   194 		print [[config - Reloading the configuration, etc.]]
       
   195 		print [[console - Help regarding the console itself]]
       
   196 	elseif section == "c2s" then
       
   197 		print [[c2s:show(jid) - Show all client sessions with the specified JID (or all if no JID given)]]
       
   198 		print [[c2s:show_insecure() - Show all unencrypted client connections]]
       
   199 		print [[c2s:show_secure() - Show all encrypted client connections]]
       
   200 		print [[c2s:close(jid) - Close all sessions for the specified JID]]
       
   201 	elseif section == "s2s" then
       
   202 		print [[s2s:show(domain) - Show all s2s connections for the given domain (or all if no domain given)]]
       
   203 		print [[s2s:close(from, to) - Close a connection from one domain to another]]
       
   204 	elseif section == "module" then
       
   205 		print [[module:load(module, host) - Load the specified module on the specified host (or all hosts if none given)]]
       
   206 		print [[module:reload(module, host) - The same, but unloads and loads the module (saving state if the module supports it)]]
       
   207 		print [[module:unload(module, host) - The same, but just unloads the module from memory]]
       
   208 		print [[module:list(host) - List the modules loaded on the specified host]]
       
   209 	elseif section == "host" then
       
   210 		print [[host:activate(hostname) - Activates the specified host]]
       
   211 		print [[host:deactivate(hostname) - Disconnects all clients on this host and deactivates]]
       
   212 		print [[host:list() - List the currently-activated hosts]]
       
   213 	elseif section == "server" then
       
   214 		print [[server:version() - Show the server's version number]]
       
   215 		print [[server:uptime() - Show how long the server has been running]]
       
   216 		print [[server:shutdown(reason) - Shut down the server, with an optional reason to be broadcast to all connections]]
       
   217 	elseif section == "config" then
       
   218 		print [[config:reload() - Reload the server configuration. Modules may need to be reloaded for changes to take effect.]]
       
   219 	elseif section == "console" then
       
   220 		print [[Hey! Welcome to Prosody's admin console.]]
       
   221 		print [[First thing, if you're ever wondering how to get out, simply type 'quit'.]]
       
   222 		print [[Secondly, note that we don't support the full telnet protocol yet (it's coming)]]
       
   223 		print [[so you may have trouble using the arrow keys, etc. depending on your system.]]
       
   224 		print [[]]
       
   225 		print [[For now we offer a couple of handy shortcuts:]]
       
   226 		print [[!! - Repeat the last command]]
       
   227 		print [[!old!new! - repeat the last command, but with 'old' replaced by 'new']]
       
   228 		print [[]]
       
   229 		print [[For those well-versed in Prosody's internals, or taking instruction from those who are,]]
       
   230 		print [[you can prefix a command with > to escape the console sandbox, and access everything in]]
       
   231 		print [[the running server. Great fun, but be careful not to break anything :)]]
       
   232 	end
       
   233 	print [[]]
       
   234 end
       
   235 
       
   236 -- Session environment --
       
   237 -- Anything in def_env will be accessible within the session as a global variable
       
   238 
       
   239 def_env.server = {};
       
   240 
       
   241 function def_env.server:insane_reload()
       
   242 	prosody.unlock_globals();
       
   243 	dofile "prosody"
       
   244 	prosody = _G.prosody;
       
   245 	return true, "Server reloaded";
       
   246 end
       
   247 
       
   248 function def_env.server:version()
       
   249 	return true, tostring(prosody.version or "unknown");
       
   250 end
       
   251 
       
   252 function def_env.server:uptime()
       
   253 	local t = os.time()-prosody.start_time;
       
   254 	local seconds = t%60;
       
   255 	t = (t - seconds)/60;
       
   256 	local minutes = t%60;
       
   257 	t = (t - minutes)/60;
       
   258 	local hours = t%24;
       
   259 	t = (t - hours)/24;
       
   260 	local days = t;
       
   261 	return true, string.format("This server has been running for %d day%s, %d hour%s and %d minute%s (since %s)",
       
   262 		days, (days ~= 1 and "s") or "", hours, (hours ~= 1 and "s") or "",
       
   263 		minutes, (minutes ~= 1 and "s") or "", os.date("%c", prosody.start_time));
       
   264 end
       
   265 
       
   266 function def_env.server:shutdown(reason)
       
   267 	prosody.shutdown(reason);
       
   268 	return true, "Shutdown initiated";
       
   269 end
       
   270 
       
   271 def_env.module = {};
       
   272 
       
   273 local function get_hosts_set(hosts, module)
       
   274 	if type(hosts) == "table" then
       
   275 		if hosts[1] then
       
   276 			return set.new(hosts);
       
   277 		elseif hosts._items then
       
   278 			return hosts;
       
   279 		end
       
   280 	elseif type(hosts) == "string" then
       
   281 		return set.new { hosts };
       
   282 	elseif hosts == nil then
       
   283 		local mm = require "modulemanager";
       
   284 		return set.new(array.collect(keys(prosody.hosts)))
       
   285 			/ function (host) return prosody.hosts[host].type == "local" or module and mm.is_loaded(host, module); end;
       
   286 	end
       
   287 end
       
   288 
       
   289 function def_env.module:load(name, hosts, config)
       
   290 	local mm = require "modulemanager";
       
   291 	
       
   292 	hosts = get_hosts_set(hosts);
       
   293 	
       
   294 	-- Load the module for each host
       
   295 	local ok, err, count = true, nil, 0;
       
   296 	for host in hosts do
       
   297 		if (not mm.is_loaded(host, name)) then
       
   298 			ok, err = mm.load(host, name, config);
       
   299 			if not ok then
       
   300 				ok = false;
       
   301 				self.session.print(err or "Unknown error loading module");
       
   302 			else
       
   303 				count = count + 1;
       
   304 				self.session.print("Loaded for "..host);
       
   305 			end
       
   306 		end
       
   307 	end
       
   308 	
       
   309 	return ok, (ok and "Module loaded onto "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err));	
       
   310 end
       
   311 
       
   312 function def_env.module:unload(name, hosts)
       
   313 	local mm = require "modulemanager";
       
   314 
       
   315 	hosts = get_hosts_set(hosts, name);
       
   316 	
       
   317 	-- Unload the module for each host
       
   318 	local ok, err, count = true, nil, 0;
       
   319 	for host in hosts do
       
   320 		if mm.is_loaded(host, name) then
       
   321 			ok, err = mm.unload(host, name);
       
   322 			if not ok then
       
   323 				ok = false;
       
   324 				self.session.print(err or "Unknown error unloading module");
       
   325 			else
       
   326 				count = count + 1;
       
   327 				self.session.print("Unloaded from "..host);
       
   328 			end
       
   329 		end
       
   330 	end
       
   331 	return ok, (ok and "Module unloaded from "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err));
       
   332 end
       
   333 
       
   334 function def_env.module:reload(name, hosts)
       
   335 	local mm = require "modulemanager";
       
   336 
       
   337 	hosts = get_hosts_set(hosts, name);
       
   338 	
       
   339 	-- Reload the module for each host
       
   340 	local ok, err, count = true, nil, 0;
       
   341 	for host in hosts do
       
   342 		if mm.is_loaded(host, name) then
       
   343 			ok, err = mm.reload(host, name);
       
   344 			if not ok then
       
   345 				ok = false;
       
   346 				self.session.print(err or "Unknown error reloading module");
       
   347 			else
       
   348 				count = count + 1;
       
   349 				if ok == nil then
       
   350 					ok = true;
       
   351 				end
       
   352 				self.session.print("Reloaded on "..host);
       
   353 			end
       
   354 		end
       
   355 	end
       
   356 	return ok, (ok and "Module reloaded on "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err));
       
   357 end
       
   358 
       
   359 function def_env.module:list(hosts)
       
   360 	if hosts == nil then
       
   361 		hosts = array.collect(keys(prosody.hosts));
       
   362 	end
       
   363 	if type(hosts) == "string" then
       
   364 		hosts = { hosts };
       
   365 	end
       
   366 	if type(hosts) ~= "table" then
       
   367 		return false, "Please supply a host or a list of hosts you would like to see";
       
   368 	end
       
   369 	
       
   370 	local print = self.session.print;
       
   371 	for _, host in ipairs(hosts) do
       
   372 		print(host..":");
       
   373 		local modules = array.collect(keys(prosody.hosts[host] and prosody.hosts[host].modules or {})):sort();
       
   374 		if #modules == 0 then
       
   375 			if prosody.hosts[host] then
       
   376 				print("    No modules loaded");
       
   377 			else
       
   378 				print("    Host not found");
       
   379 			end
       
   380 		else
       
   381 			for _, name in ipairs(modules) do
       
   382 				print("    "..name);
       
   383 			end
       
   384 		end
       
   385 	end
       
   386 end
       
   387 
       
   388 def_env.config = {};
       
   389 function def_env.config:load(filename, format)
       
   390 	local config_load = require "core.configmanager".load;
       
   391 	local ok, err = config_load(filename, format);
       
   392 	if not ok then
       
   393 		return false, err or "Unknown error loading config";
       
   394 	end
       
   395 	return true, "Config loaded";
       
   396 end
       
   397 
       
   398 function def_env.config:get(host, section, key)
       
   399 	local config_get = require "core.configmanager".get
       
   400 	return true, tostring(config_get(host, section, key));
       
   401 end
       
   402 
       
   403 function def_env.config:reload()
       
   404 	local ok, err = prosody.reload_config();
       
   405 	return ok, (ok and "Config reloaded (you may need to reload modules to take effect)") or tostring(err);
       
   406 end
       
   407 
       
   408 def_env.hosts = {};
       
   409 function def_env.hosts:list()
       
   410 	for host, host_session in pairs(hosts) do
       
   411 		self.session.print(host);
       
   412 	end
       
   413 	return true, "Done";
       
   414 end
       
   415 
       
   416 function def_env.hosts:add(name)
       
   417 end
       
   418 
       
   419 def_env.c2s = {};
       
   420 
       
   421 local function show_c2s(callback)
       
   422 	for hostname, host in pairs(hosts) do
       
   423 		for username, user in pairs(host.sessions or {}) do
       
   424 			for resource, session in pairs(user.sessions or {}) do
       
   425 				local jid = username.."@"..hostname.."/"..resource;
       
   426 				callback(jid, session);
       
   427 			end
       
   428 		end
       
   429 	end
       
   430 end
       
   431 
       
   432 function def_env.c2s:show(match_jid)
       
   433 	local print, count = self.session.print, 0;
       
   434 	local curr_host;
       
   435 	show_c2s(function (jid, session)
       
   436 		if curr_host ~= session.host then
       
   437 			curr_host = session.host;
       
   438 			print(curr_host);
       
   439 		end
       
   440 		if (not match_jid) or jid:match(match_jid) then
       
   441 			count = count + 1;
       
   442 			local status, priority = "unavailable", tostring(session.priority or "-");
       
   443 			if session.presence then
       
   444 				status = session.presence:child_with_name("show");
       
   445 				if status then
       
   446 					status = status:get_text() or "[invalid!]";
       
   447 				else
       
   448 					status = "available";
       
   449 				end
       
   450 			end
       
   451 			print("   "..jid.." - "..status.."("..priority..")");
       
   452 		end		
       
   453 	end);
       
   454 	return true, "Total: "..count.." clients";
       
   455 end
       
   456 
       
   457 function def_env.c2s:show_insecure(match_jid)
       
   458 	local print, count = self.session.print, 0;
       
   459 	show_c2s(function (jid, session)
       
   460 		if ((not match_jid) or jid:match(match_jid)) and not session.secure then
       
   461 			count = count + 1;
       
   462 			print(jid);
       
   463 		end		
       
   464 	end);
       
   465 	return true, "Total: "..count.." insecure client connections";
       
   466 end
       
   467 
       
   468 function def_env.c2s:show_secure(match_jid)
       
   469 	local print, count = self.session.print, 0;
       
   470 	show_c2s(function (jid, session)
       
   471 		if ((not match_jid) or jid:match(match_jid)) and session.secure then
       
   472 			count = count + 1;
       
   473 			print(jid);
       
   474 		end		
       
   475 	end);
       
   476 	return true, "Total: "..count.." secure client connections";
       
   477 end
       
   478 
       
   479 function def_env.c2s:close(match_jid)
       
   480 	local print, count = self.session.print, 0;
       
   481 	show_c2s(function (jid, session)
       
   482 		if jid == match_jid or jid_bare(jid) == match_jid then
       
   483 			count = count + 1;
       
   484 			session:close();
       
   485 		end
       
   486 	end);
       
   487 	return true, "Total: "..count.." sessions closed";
       
   488 end
       
   489 
       
   490 def_env.s2s = {};
       
   491 function def_env.s2s:show(match_jid)
       
   492 	local _print = self.session.print;
       
   493 	local print = self.session.print;
       
   494 	
       
   495 	local count_in, count_out = 0,0;
       
   496 	
       
   497 	for host, host_session in pairs(hosts) do
       
   498 		print = function (...) _print(host); _print(...); print = _print; end
       
   499 		for remotehost, session in pairs(host_session.s2sout) do
       
   500 			if (not match_jid) or remotehost:match(match_jid) or host:match(match_jid) then
       
   501 				count_out = count_out + 1;
       
   502 				print("    "..host.." -> "..remotehost..(session.cert_identity_status == "valid" and " (secure)" or "")..(session.secure and " (encrypted)" or "")..(session.compressed and " (compressed)" or ""));
       
   503 				if session.sendq then
       
   504 					print("        There are "..#session.sendq.." queued outgoing stanzas for this connection");
       
   505 				end
       
   506 				if session.type == "s2sout_unauthed" then
       
   507 					if session.connecting then
       
   508 						print("        Connection not yet established");
       
   509 						if not session.srv_hosts then
       
   510 							if not session.conn then
       
   511 								print("        We do not yet have a DNS answer for this host's SRV records");
       
   512 							else
       
   513 								print("        This host has no SRV records, using A record instead");
       
   514 							end
       
   515 						elseif session.srv_choice then
       
   516 							print("        We are on SRV record "..session.srv_choice.." of "..#session.srv_hosts);
       
   517 							local srv_choice = session.srv_hosts[session.srv_choice];
       
   518 							print("        Using "..(srv_choice.target or ".")..":"..(srv_choice.port or 5269));
       
   519 						end
       
   520 					elseif session.notopen then
       
   521 						print("        The <stream> has not yet been opened");
       
   522 					elseif not session.dialback_key then
       
   523 						print("        Dialback has not been initiated yet");
       
   524 					elseif session.dialback_key then
       
   525 						print("        Dialback has been requested, but no result received");
       
   526 					end
       
   527 				end
       
   528 			end
       
   529 		end	
       
   530 		local subhost_filter = function (h)
       
   531 				return (match_jid and h:match(match_jid));
       
   532 			end
       
   533 		for session in pairs(incoming_s2s) do
       
   534 			if session.to_host == host and ((not match_jid) or host:match(match_jid)
       
   535 				or (session.from_host and session.from_host:match(match_jid))
       
   536 				-- Pft! is what I say to list comprehensions
       
   537 				or (session.hosts and #array.collect(keys(session.hosts)):filter(subhost_filter)>0)) then
       
   538 				count_in = count_in + 1;
       
   539 				print("    "..host.." <- "..(session.from_host or "(unknown)")..(session.cert_identity_status == "valid" and " (secure)" or "")..(session.secure and " (encrypted)" or "")..(session.compressed and " (compressed)" or ""));
       
   540 				if session.type == "s2sin_unauthed" then
       
   541 						print("        Connection not yet authenticated");
       
   542 				end
       
   543 				for name in pairs(session.hosts) do
       
   544 					if name ~= session.from_host then
       
   545 						print("        also hosts "..tostring(name));
       
   546 					end
       
   547 				end
       
   548 			end
       
   549 		end
       
   550 		
       
   551 		print = _print;
       
   552 	end
       
   553 	
       
   554 	for session in pairs(incoming_s2s) do
       
   555 		if not session.to_host and ((not match_jid) or session.from_host and session.from_host:match(match_jid)) then
       
   556 			count_in = count_in + 1;
       
   557 			print("Other incoming s2s connections");
       
   558 			print("    (unknown) <- "..(session.from_host or "(unknown)"));			
       
   559 		end
       
   560 	end
       
   561 	
       
   562 	return true, "Total: "..count_out.." outgoing, "..count_in.." incoming connections";
       
   563 end
       
   564 
       
   565 local function print_subject(print, subject)
       
   566 	for _, entry in ipairs(subject) do
       
   567 		print(
       
   568 			("    %s: %q"):format(
       
   569 				entry.name or entry.oid,
       
   570 				entry.value:gsub("[\r\n%z%c]", " ")
       
   571 			)
       
   572 		);
       
   573 	end
       
   574 end
       
   575 
       
   576 function def_env.s2s:showcert(domain)
       
   577 	local ser = require "util.serialization".serialize;
       
   578 	local print = self.session.print;
       
   579 	local domain_sessions = set.new(array.collect(keys(incoming_s2s)))
       
   580 		/function(session) return session.from_host == domain; end;
       
   581 	for local_host in values(prosody.hosts) do
       
   582 		local s2sout = local_host.s2sout;
       
   583 		if s2sout and s2sout[domain] then
       
   584 			domain_sessions:add(s2sout[domain]);
       
   585 		end
       
   586 	end
       
   587 	local cert_set = {};
       
   588 	for session in domain_sessions do
       
   589 		local conn = session.conn;
       
   590 		conn = conn and conn:socket();
       
   591 		if not conn.getpeercertificate then
       
   592 			if conn.dohandshake then
       
   593 				error("This version of LuaSec does not support certificate viewing");
       
   594 			end
       
   595 		else
       
   596 			local cert = conn:getpeercertificate();
       
   597 			if cert then
       
   598 				local digest = cert:digest("sha1");
       
   599 				if not cert_set[digest] then
       
   600 					local chain_valid, chain_err = conn:getpeerchainvalid();
       
   601 					cert_set[digest] = {
       
   602 						{
       
   603 						  from = session.from_host,
       
   604 						  to = session.to_host,
       
   605 						  direction = session.direction
       
   606 						};
       
   607 						chain_valid = chain_valid;
       
   608 						chain_err = chain_err;
       
   609 						cert = cert;
       
   610 					};
       
   611 				else
       
   612 					table.insert(cert_set[digest], {
       
   613 						from = session.from_host,
       
   614 						to = session.to_host,
       
   615 						direction = session.direction
       
   616 					});
       
   617 				end
       
   618 			end
       
   619 		end
       
   620 	end
       
   621 	local domain_certs = array.collect(values(cert_set));
       
   622 	-- Phew. We now have a array of unique certificates presented by domain.
       
   623 	local print = self.session.print;
       
   624 	local n_certs = #domain_certs;
       
   625 	
       
   626 	if n_certs == 0 then
       
   627 		return "No certificates found for "..domain;
       
   628 	end
       
   629 	
       
   630 	local function _capitalize_and_colon(byte)
       
   631 		return string.upper(byte)..":";
       
   632 	end
       
   633 	local function pretty_fingerprint(hash)
       
   634 		return hash:gsub("..", _capitalize_and_colon):sub(1, -2);
       
   635 	end
       
   636 	
       
   637 	for cert_info in values(domain_certs) do
       
   638 		local cert = cert_info.cert;
       
   639 		print("---")
       
   640 		print("Fingerprint (SHA1): "..pretty_fingerprint(cert:digest("sha1")));
       
   641 		print("");
       
   642 		local n_streams = #cert_info;
       
   643 		print("Currently used on "..n_streams.." stream"..(n_streams==1 and "" or "s")..":");
       
   644 		for _, stream in ipairs(cert_info) do
       
   645 			if stream.direction == "incoming" then
       
   646 				print("    "..stream.to.." <- "..stream.from);
       
   647 			else
       
   648 				print("    "..stream.from.." -> "..stream.to);
       
   649 			end
       
   650 		end
       
   651 		print("");
       
   652 		local chain_valid, err = cert_info.chain_valid, cert_info.chain_err;
       
   653 		local valid_identity = cert_verify_identity(domain, "xmpp-server", cert);
       
   654 		print("Trusted certificate: "..(chain_valid and "Yes" or ("No ("..err..")")));
       
   655 		print("Issuer: ");
       
   656 		print_subject(print, cert:issuer());
       
   657 		print("");
       
   658 		print("Valid for "..domain..": "..(valid_identity and "Yes" or "No"));
       
   659 		print("Subject:");
       
   660 		print_subject(print, cert:subject());
       
   661 	end
       
   662 	print("---");
       
   663 	return ("Showing "..n_certs.." certificate"
       
   664 		..(n_certs==1 and "" or "s")
       
   665 		.." presented by "..domain..".");
       
   666 end
       
   667 
       
   668 function def_env.s2s:close(from, to)
       
   669 	local print, count = self.session.print, 0;
       
   670 	
       
   671 	if not (from and to) then
       
   672 		return false, "Syntax: s2s:close('from', 'to') - Closes all s2s sessions from 'from' to 'to'";
       
   673 	elseif from == to then
       
   674 		return false, "Both from and to are the same... you can't do that :)";
       
   675 	end
       
   676 	
       
   677 	if hosts[from] and not hosts[to] then
       
   678 		-- Is an outgoing connection
       
   679 		local session = hosts[from].s2sout[to];
       
   680 		if not session then
       
   681 			print("No outgoing connection from "..from.." to "..to)
       
   682 		else
       
   683 			(session.close or s2smanager.destroy_session)(session);
       
   684 			count = count + 1;
       
   685 			print("Closed outgoing session from "..from.." to "..to);
       
   686 		end
       
   687 	elseif hosts[to] and not hosts[from] then
       
   688 		-- Is an incoming connection
       
   689 		for session in pairs(incoming_s2s) do
       
   690 			if session.to_host == to and session.from_host == from then
       
   691 				(session.close or s2smanager.destroy_session)(session);
       
   692 				count = count + 1;
       
   693 			end
       
   694 		end
       
   695 		
       
   696 		if count == 0 then
       
   697 			print("No incoming connections from "..from.." to "..to);
       
   698 		else
       
   699 			print("Closed "..count.." incoming session"..((count == 1 and "") or "s").." from "..from.." to "..to);
       
   700 		end
       
   701 	elseif hosts[to] and hosts[from] then
       
   702 		return false, "Both of the hostnames you specified are local, there are no s2s sessions to close";
       
   703 	else
       
   704 		return false, "Neither of the hostnames you specified are being used on this server";
       
   705 	end
       
   706 	
       
   707 	return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s");
       
   708 end
       
   709 
       
   710 def_env.host = {}; def_env.hosts = def_env.host;
       
   711 
       
   712 function def_env.host:activate(hostname, config)
       
   713 	return hostmanager.activate(hostname, config);
       
   714 end
       
   715 function def_env.host:deactivate(hostname, reason)
       
   716 	return hostmanager.deactivate(hostname, reason);
       
   717 end
       
   718 
       
   719 function def_env.host:list()
       
   720 	local print = self.session.print;
       
   721 	local i = 0;
       
   722 	for host in values(array.collect(keys(prosody.hosts)):sort()) do
       
   723 		i = i + 1;
       
   724 		print(host);
       
   725 	end
       
   726 	return true, i.." hosts";
       
   727 end
       
   728 
       
   729 -------------
       
   730 
       
   731 function printbanner(session)
       
   732 	local option = config.get("*", "core", "console_banner");
       
   733 if option == nil or option == "full" or option == "graphic" then
       
   734 session.print [[
       
   735                    ____                \   /     _       
       
   736                     |  _ \ _ __ ___  ___  _-_   __| |_   _ 
       
   737                     | |_) | '__/ _ \/ __|/ _ \ / _` | | | |
       
   738                     |  __/| | | (_) \__ \ |_| | (_| | |_| |
       
   739                     |_|   |_|  \___/|___/\___/ \__,_|\__, |
       
   740                     A study in simplicity            |___/ 
       
   741 
       
   742 ]]
       
   743 end
       
   744 if option == nil or option == "short" or option == "full" then
       
   745 session.print("Welcome to the Prosody administration console. For a list of commands, type: help");
       
   746 session.print("You may find more help on using this console in our online documentation at ");
       
   747 session.print("http://prosody.im/doc/console\n");
       
   748 end
       
   749 if option and option ~= "short" and option ~= "full" and option ~= "graphic" then
       
   750 	if type(option) == "string" then
       
   751 		session.print(option)
       
   752 	elseif type(option) == "function" then
       
   753 		setfenv(option, redirect_output(_G, session));
       
   754 		pcall(option, session);
       
   755 	end
       
   756 end
       
   757 end
       
   758 
       
   759 prosody.net_activate_ports("console", "console", {5582}, "tcp");