2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 Waqas Hussain
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
8 -- luacheck: ignore 212/self
12 local hostmanager
= require
"core.hostmanager";
13 local modulemanager
= require
"core.modulemanager";
14 local s2smanager
= require
"core.s2smanager";
15 local portmanager
= require
"core.portmanager";
16 local helpers
= require
"util.helpers";
17 local server
= require
"net.server";
21 local prosody
= _G
.prosody
;
23 local console_listener
= { default_port
= 5582; default_mode
= "*a"; interface
= "127.0.0.1" };
25 local unpack
= table.unpack
or unpack
; -- luacheck: ignore 113
26 local iterators
= require
"util.iterators";
27 local keys
, values
= iterators
.keys
, iterators
.values
;
28 local jid_bare
, jid_split
, jid_join
= import("util.jid", "bare", "prepped_split", "join");
29 local set
, array
= require
"util.set", require
"util.array";
30 local cert_verify_identity
= require
"util.x509".verify_identity
;
31 local envload
= require
"util.envload".envload
;
32 local envloadfile
= require
"util.envload".envloadfile
;
33 local has_pposix
, pposix
= pcall(require
, "util.pposix");
34 local async
= require
"util.async";
35 local serialize
= require
"util.serialization".new({ fatal
= false, unquoted
= true});
37 local commands
= module
:shared("commands")
38 local def_env
= module
:shared("env");
39 local default_env_mt
= { __index
= def_env
};
41 local function redirect_output(target
, session
)
42 local env
= setmetatable({ print = session
.print }, { __index
= function (_
, k
) return rawget(target
, k
); end });
43 env
.dofile = function(name
)
44 local f
, err
= envloadfile(name
, env
);
45 if not f
then return f
, err
; end
53 local runner_callbacks
= {};
55 function runner_callbacks
:ready()
56 self
.data
.conn
:resume();
59 function runner_callbacks
:waiting()
60 self
.data
.conn
:pause();
63 function runner_callbacks
:error(err
)
64 module
:log("error", "Traceback[telnet]: %s", err
);
66 self
.data
.print("Fatal error while running command, it did not complete");
67 self
.data
.print("Error: "..tostring(err
));
71 function console
:new_session(conn
)
72 local w
= function(s
) conn
:write(s
:gsub("\n", "\r\n")); end;
73 local session
= { conn
= conn
;
74 send
= function (t
) w(tostring(t
)); end;
75 print = function (...)
77 for i
=1,select("#", ...) do
78 t
[i
] = tostring(select(i
, ...));
80 w("| "..table.concat(t
, "\t").."\n");
82 disconnect
= function () conn
:close(); end;
84 session
.env
= setmetatable({}, default_env_mt
);
86 session
.thread
= async
.runner(function (line
)
87 console
:process_line(session
, line
);
88 session
.send(string.char(0));
89 end, runner_callbacks
, session
);
91 -- Load up environment with helper objects
92 for name
, t
in pairs(def_env
) do
93 if type(t
) == "table" then
94 session
.env
[name
] = setmetatable({ session
= session
}, { __index
= t
});
101 function console
:process_line(session
, line
)
104 if line
:match("^>") then
105 line
= line
:gsub("^>", "");
107 elseif line
== "\004" then
108 commands
["bye"](session
, line
);
111 local command
= line
:match("^%w+") or line
:match("%p");
112 if commands
[command
] then
113 commands
[command
](session
, line
);
118 session
.env
._
= line
;
120 if not useglobalenv
and commands
[line
:lower()] then
121 commands
[line
:lower()](session
, line
);
125 local chunkname
= "=console";
126 local env
= (useglobalenv
and redirect_output(_G
, session
)) or session
.env
or nil
127 local chunk
, err
= envload("return "..line
, chunkname
, env
);
129 chunk
, err
= envload(line
, chunkname
, env
);
131 err
= err
:gsub("^%[string .-%]:%d+: ", "");
132 err
= err
:gsub("^:%d+: ", "");
133 err
= err
:gsub("'<eof>'", "the end of the line");
134 session
.print("Sorry, I couldn't understand that... "..err
);
139 local taskok
, message
= chunk();
142 session
.print("Result: "..tostring(taskok
));
144 elseif (not taskok
) and message
then
145 session
.print("Command completed with a problem");
146 session
.print("Message: "..tostring(message
));
150 session
.print("OK: "..tostring(message
));
155 function console_listener
.onconnect(conn
)
156 -- Handle new connection
157 local session
= console
:new_session(conn
);
158 sessions
[conn
] = session
;
159 printbanner(session
);
160 session
.send(string.char(0));
163 function console_listener
.onincoming(conn
, data
)
164 local session
= sessions
[conn
];
166 local partial
= session
.partial_data
;
168 data
= partial
..data
;
171 for line
in data
:gmatch("[^\n]*[\n\004]") do
172 if session
.closed
then return end
173 session
.thread
:run(line
);
175 session
.partial_data
= data
:match("[^\n]+$");
178 function console_listener
.onreadtimeout(conn
)
179 local session
= sessions
[conn
];
186 function console_listener
.ondisconnect(conn
, err
) -- luacheck: ignore 212/err
187 local session
= sessions
[conn
];
189 session
.disconnect();
190 sessions
[conn
] = nil;
194 function console_listener
.ondetach(conn
)
195 sessions
[conn
] = nil;
198 -- Console commands --
199 -- These are simple commands, not valid standalone in Lua
201 function commands
.bye(session
)
202 session
.print("See you! :)");
203 session
.closed
= true;
204 session
.disconnect();
206 commands
.quit
, commands
.exit = commands
.bye
, commands
.bye
;
208 commands
["!"] = function (session
, data
)
209 if data
:match("^!!") and session
.env
._
then
210 session
.print("!> "..session
.env
._
);
211 return console_listener
.onincoming(session
.conn
, session
.env
._
);
213 local old
, new
= data
:match("^!(.-[^\\])!(.-)!$");
215 local ok
, res
= pcall(string.gsub, session
.env
._
, old
, new
);
220 session
.print("!> "..res
);
221 return console_listener
.onincoming(session
.conn
, res
);
223 session
.print("Sorry, not sure what you want");
227 function commands
.help(session
, data
)
228 local print = session
.print;
229 local section
= data
:match("^help (%w+)");
231 print [[Commands are divided into multiple sections. For help on a particular section, ]]
232 print [[type: help SECTION (for example, 'help c2s'). Sections are: ]]
234 print [[c2s - Commands to manage local client-to-server sessions]]
235 print [[s2s - Commands to manage sessions between this server and others]]
236 print [[module - Commands to load/reload/unload modules/plugins]]
237 print [[host - Commands to activate, deactivate and list virtual hosts]]
238 print [[user - Commands to create and delete users, and change their passwords]]
239 print [[server - Uptime, version, shutting down, etc.]]
240 print [[port - Commands to manage ports the server is listening on]]
241 print [[dns - Commands to manage and inspect the internal DNS resolver]]
242 print [[xmpp - Commands for sending XMPP stanzas]]
243 print [[config - Reloading the configuration, etc.]]
244 print [[console - Help regarding the console itself]]
245 elseif section
== "c2s" then
246 print [[c2s:show(jid) - Show all client sessions with the specified JID (or all if no JID given)]]
247 print [[c2s:show_insecure() - Show all unencrypted client connections]]
248 print [[c2s:show_secure() - Show all encrypted client connections]]
249 print [[c2s:show_tls() - Show TLS cipher info for encrypted sessions]]
250 print [[c2s:count() - Count sessions without listing them]]
251 print [[c2s:close(jid) - Close all sessions for the specified JID]]
252 print [[c2s:closeall() - Close all active c2s connections ]]
253 elseif section
== "s2s" then
254 print [[s2s:show(domain) - Show all s2s connections for the given domain (or all if no domain given)]]
255 print [[s2s:show_tls(domain) - Show TLS cipher info for encrypted sessions]]
256 print [[s2s:close(from, to) - Close a connection from one domain to another]]
257 print [[s2s:closeall(host) - Close all the incoming/outgoing s2s sessions to specified host]]
258 elseif section
== "module" then
259 print [[module:load(module, host) - Load the specified module on the specified host (or all hosts if none given)]]
260 print [[module:reload(module, host) - The same, but unloads and loads the module (saving state if the module supports it)]]
261 print [[module:unload(module, host) - The same, but just unloads the module from memory]]
262 print [[module:list(host) - List the modules loaded on the specified host]]
263 elseif section
== "host" then
264 print [[host:activate(hostname) - Activates the specified host]]
265 print [[host:deactivate(hostname) - Disconnects all clients on this host and deactivates]]
266 print [[host:list() - List the currently-activated hosts]]
267 elseif section
== "user" then
268 print [[user:create(jid, password) - Create the specified user account]]
269 print [[user:password(jid, password) - Set the password for the specified user account]]
270 print [[user:delete(jid) - Permanently remove the specified user account]]
271 print [[user:list(hostname, pattern) - List users on the specified host, optionally filtering with a pattern]]
272 elseif section
== "server" then
273 print [[server:version() - Show the server's version number]]
274 print [[server:uptime() - Show how long the server has been running]]
275 print [[server:memory() - Show details about the server's memory usage]]
276 print [[server:shutdown(reason) - Shut down the server, with an optional reason to be broadcast to all connections]]
277 elseif section
== "port" then
278 print [[port:list() - Lists all network ports prosody currently listens on]]
279 print [[port:close(port, interface) - Close a port]]
280 elseif section
== "dns" then
281 print [[dns:lookup(name, type, class) - Do a DNS lookup]]
282 print [[dns:addnameserver(nameserver) - Add a nameserver to the list]]
283 print [[dns:setnameserver(nameserver) - Replace the list of name servers with the supplied one]]
284 print [[dns:purge() - Clear the DNS cache]]
285 print [[dns:cache() - Show cached records]]
286 elseif section
== "xmpp" then
287 print [[xmpp:ping(localhost, remotehost) -- Sends a ping to a remote XMPP server and reports the response]]
288 elseif section
== "config" then
289 print [[config:reload() - Reload the server configuration. Modules may need to be reloaded for changes to take effect.]]
290 elseif section
== "console" then
291 print [[Hey! Welcome to Prosody's admin console.]]
292 print [[First thing, if you're ever wondering how to get out, simply type 'quit'.]]
293 print [[Secondly, note that we don't support the full telnet protocol yet (it's coming)]]
294 print [[so you may have trouble using the arrow keys, etc. depending on your system.]]
296 print [[For now we offer a couple of handy shortcuts:]]
297 print [[!! - Repeat the last command]]
298 print [[!old!new! - repeat the last command, but with 'old' replaced by 'new']]
300 print [[For those well-versed in Prosody's internals, or taking instruction from those who are,]]
301 print [[you can prefix a command with > to escape the console sandbox, and access everything in]]
302 print [[the running server. Great fun, but be careful not to break anything :)]]
307 -- Session environment --
308 -- Anything in def_env will be accessible within the session as a global variable
310 --luacheck: ignore 212/self
314 function def_env
.server
:insane_reload()
315 prosody
.unlock_globals();
317 prosody
= _G
.prosody
;
318 return true, "Server reloaded";
321 function def_env
.server
:version()
322 return true, tostring(prosody
.version
or "unknown");
325 function def_env
.server
:uptime()
326 local t
= os
.time()-prosody
.start_time
;
327 local seconds
= t
%60;
328 t
= (t
- seconds
)/60;
329 local minutes
= t
%60;
330 t
= (t
- minutes
)/60;
334 return true, string.format("This server has been running for %d day%s, %d hour%s and %d minute%s (since %s)",
335 days
, (days
~= 1 and "s") or "", hours
, (hours
~= 1 and "s") or "",
336 minutes
, (minutes
~= 1 and "s") or "", os
.date("%c", prosody
.start_time
));
339 function def_env
.server
:shutdown(reason
)
340 prosody
.shutdown(reason
);
341 return true, "Shutdown initiated";
344 local function human(kb
)
347 kb
, unit
= kb
/1024, "M";
349 return ("%0.2f%sB"):format(kb
, unit
);
352 function def_env
.server
:memory()
353 if not has_pposix
or not pposix
.meminfo
then
354 return true, "Lua is using "..human(collectgarbage("count"));
356 local mem
, lua_mem
= pposix
.meminfo(), collectgarbage("count");
357 local print = self
.session
.print;
358 print("Process: "..human((mem
.allocated
+mem
.allocated_mmap
)/1024));
359 print(" Used: "..human(mem
.used
/1024).." ("..human(lua_mem
).." by Lua)");
360 print(" Free: "..human(mem
.unused
/1024).." ("..human(mem
.returnable
/1024).." returnable)");
366 local function get_hosts_set(hosts
, module
)
367 if type(hosts
) == "table" then
369 return set
.new(hosts
);
370 elseif hosts
._items
then
373 elseif type(hosts
) == "string" then
374 return set
.new
{ hosts
};
375 elseif hosts
== nil then
376 local hosts_set
= set
.new(array
.collect(keys(prosody
.hosts
)))
377 / function (host
) return (prosody
.hosts
[host
].type == "local" or module
and modulemanager
.is_loaded(host
, module
)) and host
or nil; end;
378 if module
and modulemanager
.get_module("*", module
) then
385 function def_env
.module
:load(name
, hosts
, config
)
386 hosts
= get_hosts_set(hosts
);
388 -- Load the module for each host
389 local ok
, err
, count
, mod = true, nil, 0;
391 if (not modulemanager
.is_loaded(host
, name
)) then
392 mod, err
= modulemanager
.load(host
, name
, config
);
395 if err
== "global-module-already-loaded" then
397 ok
, err
, count
= true, nil, 1;
401 self
.session
.print(err
or "Unknown error loading module");
404 self
.session
.print("Loaded for "..mod.module
.host
);
409 return ok
, (ok
and "Module loaded onto "..count
.." host"..(count
~= 1 and "s" or "")) or ("Last error: "..tostring(err
));
412 function def_env
.module
:unload(name
, hosts
)
413 hosts
= get_hosts_set(hosts
, name
);
415 -- Unload the module for each host
416 local ok
, err
, count
= true, nil, 0;
418 if modulemanager
.is_loaded(host
, name
) then
419 ok
, err
= modulemanager
.unload(host
, name
);
422 self
.session
.print(err
or "Unknown error unloading module");
425 self
.session
.print("Unloaded from "..host
);
429 return ok
, (ok
and "Module unloaded from "..count
.." host"..(count
~= 1 and "s" or "")) or ("Last error: "..tostring(err
));
432 local function _sort_hosts(a
, b
)
433 if a
== "*" then return true
434 elseif b
== "*" then return false
435 else return a
< b
; end
438 function def_env
.module
:reload(name
, hosts
)
439 hosts
= array
.collect(get_hosts_set(hosts
, name
)):sort(_sort_hosts
)
441 -- Reload the module for each host
442 local ok
, err
, count
= true, nil, 0;
443 for _
, host
in ipairs(hosts
) do
444 if modulemanager
.is_loaded(host
, name
) then
445 ok
, err
= modulemanager
.reload(host
, name
);
448 self
.session
.print(err
or "Unknown error reloading module");
454 self
.session
.print("Reloaded on "..host
);
458 return ok
, (ok
and "Module reloaded on "..count
.." host"..(count
~= 1 and "s" or "")) or ("Last error: "..tostring(err
));
461 function def_env
.module
:list(hosts
)
463 hosts
= array
.collect(keys(prosody
.hosts
));
464 table.insert(hosts
, 1, "*");
466 if type(hosts
) == "string" then
469 if type(hosts
) ~= "table" then
470 return false, "Please supply a host or a list of hosts you would like to see";
473 local print = self
.session
.print;
474 for _
, host
in ipairs(hosts
) do
475 print((host
== "*" and "Global" or host
)..":");
476 local modules
= array
.collect(keys(modulemanager
.get_modules(host
) or {})):sort();
477 if #modules
== 0 then
478 if prosody
.hosts
[host
] then
479 print(" No modules loaded");
481 print(" Host not found");
484 for _
, name
in ipairs(modules
) do
485 local status
, status_text
= modulemanager
.get_module(host
, name
).module
:get_status();
486 local status_summary
= "";
487 if status
== "warn" or status
== "error" then
488 status_summary
= (" (%s: %s)"):format(status
, status_text
);
490 print((" %s%s"):format(name
, status_summary
));
497 function def_env
.config
:load(filename
, format)
498 local config_load
= require
"core.configmanager".load
;
499 local ok
, err
= config_load(filename
, format);
501 return false, err
or "Unknown error loading config";
503 return true, "Config loaded";
506 function def_env
.config
:get(host
, key
)
508 host
, key
= "*", host
;
510 local config_get
= require
"core.configmanager".get
511 return true, serialize(config_get(host
, key
));
514 function def_env
.config
:reload()
515 local ok
, err
= prosody
.reload_config();
516 return ok
, (ok
and "Config reloaded (you may need to reload modules to take effect)") or tostring(err
);
519 local function common_info(session
, line
)
521 line
[#line
+1] = "["..session
.id
.."]"
523 line
[#line
+1] = "["..session
.type..(tostring(session
):match("%x*$")).."]"
527 local function session_flags(session
, line
)
529 common_info(session
, line
);
530 if session
.type == "c2s" then
531 local status
, priority
= "unavailable", tostring(session
.priority
or "-");
532 if session
.presence
then
533 status
= session
.presence
:get_child_text("show") or "available";
535 line
[#line
+1] = status
.."("..priority
..")";
537 if session
.cert_identity_status
== "valid" then
538 line
[#line
+1] = "(authenticated)";
540 if session
.secure
then
541 line
[#line
+1] = "(encrypted)";
543 if session
.compressed
then
544 line
[#line
+1] = "(compressed)";
546 if session
.smacks
then
547 line
[#line
+1] = "(sm)";
549 if session
.ip
and session
.ip
:match(":") then
550 line
[#line
+1] = "(IPv6)";
552 if session
.remote
then
553 line
[#line
+1] = "(remote)";
555 if session
.incoming
and session
.outgoing
then
556 line
[#line
+1] = "(bidi)";
557 elseif session
.is_bidi
or session
.bidi_session
then
558 line
[#line
+1] = "(bidi)";
560 if session
.bosh_version
then
561 line
[#line
+1] = "(bosh)";
563 if session
.websocket_request
then
564 line
[#line
+1] = "(websocket)";
566 return table.concat(line
, " ");
569 local function tls_info(session
, line
)
571 common_info(session
, line
);
572 if session
.secure
then
573 local sock
= session
.conn
and session
.conn
.socket
and session
.conn
:socket();
574 if sock
and sock
.info
then
575 local info
= sock
:info();
576 line
[#line
+1] = ("(%s with %s)"):format(info
.protocol
, info
.cipher
);
578 line
[#line
+1] = "(cipher info unavailable)";
581 line
[#line
+1] = "(insecure)";
583 return table.concat(line
, " ");
588 local function get_jid(session
)
589 if session
.username
then
590 return session
.full_jid
or jid_join(session
.username
, session
.host
, session
.resource
);
593 local conn
= session
.conn
;
594 local ip
= session
.ip
or "?";
595 local clientport
= conn
and conn
:clientport() or "?";
596 local serverip
= conn
and conn
.server
and conn
:server():ip() or "?";
597 local serverport
= conn
and conn
:serverport() or "?"
598 return jid_join("["..ip
.."]:"..clientport
, session
.host
or "["..serverip
.."]:"..serverport
);
601 local function get_c2s()
602 local c2s
= array
.collect(values(prosody
.full_sessions
));
603 c2s
:append(array
.collect(values(module
:shared
"/*/c2s/sessions")));
604 c2s
:append(array
.collect(values(module
:shared
"/*/bosh/sessions")));
609 local function show_c2s(callback
)
610 get_c2s():sort(function(a
, b
)
611 if a
.host
== b
.host
then
612 if a
.username
== b
.username
then
613 return (a
.resource
or "") > (b
.resource
or "");
615 return (a
.username
or "") > (b
.username
or "");
617 return (a
.host
or "") > (b
.host
or "");
618 end):map(function (session
)
619 callback(get_jid(session
), session
)
623 function def_env
.c2s
:count()
624 local c2s
= get_c2s();
625 return true, "Total: ".. #c2s
.." clients";
628 function def_env
.c2s
:show(match_jid
, annotate
)
629 local print, count
= self
.session
.print, 0;
630 annotate
= annotate
or session_flags
;
631 local curr_host
= false;
632 show_c2s(function (jid
, session
)
633 if curr_host
~= session
.host
then
634 curr_host
= session
.host
;
635 print(curr_host
or "(not connected to any host yet)");
637 if (not match_jid
) or jid
:match(match_jid
) then
639 print(annotate(session
, { " ", jid
}));
642 return true, "Total: "..count
.." clients";
645 function def_env
.c2s
:show_insecure(match_jid
)
646 local print, count
= self
.session
.print, 0;
647 show_c2s(function (jid
, session
)
648 if ((not match_jid
) or jid
:match(match_jid
)) and not session
.secure
then
653 return true, "Total: "..count
.." insecure client connections";
656 function def_env
.c2s
:show_secure(match_jid
)
657 local print, count
= self
.session
.print, 0;
658 show_c2s(function (jid
, session
)
659 if ((not match_jid
) or jid
:match(match_jid
)) and session
.secure
then
664 return true, "Total: "..count
.." secure client connections";
667 function def_env
.c2s
:show_tls(match_jid
)
668 return self
:show(match_jid
, tls_info
);
671 local function build_reason(text
, condition
)
672 if text
or condition
then
675 condition
= condition
or "undefined-condition",
680 function def_env
.c2s
:close(match_jid
, text
, condition
)
682 show_c2s(function (jid
, session
)
683 if jid
== match_jid
or jid_bare(jid
) == match_jid
then
685 session
:close(build_reason(text
, condition
));
688 return true, "Total: "..count
.." sessions closed";
691 function def_env
.c2s
:closeall(text
, condition
)
693 --luacheck: ignore 212/jid
694 show_c2s(function (jid
, session
)
696 session
:close(build_reason(text
, condition
));
698 return true, "Total: "..count
.." sessions closed";
703 function def_env
.s2s
:show(match_jid
, annotate
)
704 local print = self
.session
.print;
705 annotate
= annotate
or session_flags
;
707 local count_in
, count_out
= 0,0;
708 local s2s_list
= { };
710 local s2s_sessions
= module
:shared
"/*/s2s/sessions";
711 for _
, session
in pairs(s2s_sessions
) do
712 local remotehost
, localhost
, direction
;
713 if session
.direction
== "outgoing" then
715 count_out
= count_out
+ 1;
716 remotehost
, localhost
= session
.to_host
or "?", session
.from_host
or "?";
719 count_in
= count_in
+ 1;
720 remotehost
, localhost
= session
.from_host
or "?", session
.to_host
or "?";
722 local sess_lines
= { l
= localhost
, r
= remotehost
,
723 annotate(session
, { "", direction
, remotehost
or "?" })};
725 if (not match_jid
) or remotehost
:match(match_jid
) or localhost
:match(match_jid
) then
726 table.insert(s2s_list
, sess_lines
);
727 -- luacheck: ignore 421/print
728 local print = function (s
) table.insert(sess_lines
, " "..s
); end
729 if session
.sendq
then
730 print("There are "..#session
.sendq
.." queued outgoing stanzas for this connection");
732 if session
.type == "s2sout_unauthed" then
733 if session
.connecting
then
734 print("Connection not yet established");
735 if not session
.srv_hosts
then
736 if not session
.conn
then
737 print("We do not yet have a DNS answer for this host's SRV records");
739 print("This host has no SRV records, using A record instead");
741 elseif session
.srv_choice
then
742 print("We are on SRV record "..session
.srv_choice
.." of "..#session
.srv_hosts
);
743 local srv_choice
= session
.srv_hosts
[session
.srv_choice
];
744 print("Using "..(srv_choice
.target
or ".")..":"..(srv_choice
.port
or 5269));
746 elseif session
.notopen
then
747 print("The <stream> has not yet been opened");
748 elseif not session
.dialback_key
then
749 print("Dialback has not been initiated yet");
750 elseif session
.dialback_key
then
751 print("Dialback has been requested, but no result received");
754 if session
.type == "s2sin_unauthed" then
755 print("Connection not yet authenticated");
756 elseif session
.type == "s2sin" then
757 for name
in pairs(session
.hosts
) do
758 if name
~= session
.from_host
then
759 print("also hosts "..tostring(name
));
766 -- Sort by local host, then remote host
767 table.sort(s2s_list
, function(a
,b
)
768 if a
.l
== b
.l
then return a
.r
< b
.r
; end
772 for _
, sess_lines
in ipairs(s2s_list
) do
773 if sess_lines
.l
~= lasthost
then print(sess_lines
.l
); lasthost
=sess_lines
.l
end
774 for _
, line
in ipairs(sess_lines
) do print(line
); end
776 return true, "Total: "..count_out
.." outgoing, "..count_in
.." incoming connections";
779 function def_env
.s2s
:show_tls(match_jid
)
780 return self
:show(match_jid
, tls_info
);
783 local function print_subject(print, subject
)
784 for _
, entry
in ipairs(subject
) do
787 entry
.name
or entry
.oid
,
788 entry
.value
:gsub("[\r\n%z%c]", " ")
794 -- As much as it pains me to use the 0-based depths that OpenSSL does,
795 -- I think there's going to be more confusion among operators if we
797 local function print_errors(print, errors
)
798 for depth
, t
in pairs(errors
) do
802 table.concat(t
, "\n| ")
808 function def_env
.s2s
:showcert(domain
)
809 local print = self
.session
.print;
810 local s2s_sessions
= module
:shared
"/*/s2s/sessions";
811 local domain_sessions
= set
.new(array
.collect(values(s2s_sessions
)))
812 /function(session
) return (session
.to_host
== domain
or session
.from_host
== domain
) and session
or nil; end;
814 for session
in domain_sessions
do
815 local conn
= session
.conn
;
816 conn
= conn
and conn
:socket();
817 if not conn
.getpeerchain
then
818 if conn
.dohandshake
then
819 error("This version of LuaSec does not support certificate viewing");
822 local cert
= conn
:getpeercertificate();
824 local certs
= conn
:getpeerchain();
825 local digest
= cert
:digest("sha1");
826 if not cert_set
[digest
] then
827 local chain_valid
, chain_errors
= conn
:getpeerverification();
830 from
= session
.from_host
,
831 to
= session
.to_host
,
832 direction
= session
.direction
834 chain_valid
= chain_valid
;
835 chain_errors
= chain_errors
;
839 table.insert(cert_set
[digest
], {
840 from
= session
.from_host
,
841 to
= session
.to_host
,
842 direction
= session
.direction
848 local domain_certs
= array
.collect(values(cert_set
));
849 -- Phew. We now have a array of unique certificates presented by domain.
850 local n_certs
= #domain_certs
;
853 return "No certificates found for "..domain
;
856 local function _capitalize_and_colon(byte
)
857 return string.upper(byte
)..":";
859 local function pretty_fingerprint(hash
)
860 return hash
:gsub("..", _capitalize_and_colon
):sub(1, -2);
863 for cert_info
in values(domain_certs
) do
864 local certs
= cert_info
.certs
;
865 local cert
= certs
[1];
867 print("Fingerprint (SHA1): "..pretty_fingerprint(cert
:digest("sha1")));
869 local n_streams
= #cert_info
;
870 print("Currently used on "..n_streams
.." stream"..(n_streams
==1 and "" or "s")..":");
871 for _
, stream
in ipairs(cert_info
) do
872 if stream
.direction
== "incoming" then
873 print(" "..stream
.to
.." <- "..stream
.from
);
875 print(" "..stream
.from
.." -> "..stream
.to
);
879 local chain_valid
, errors
= cert_info
.chain_valid
, cert_info
.chain_errors
;
880 local valid_identity
= cert_verify_identity(domain
, "xmpp-server", cert
);
882 print("Trusted certificate: Yes");
884 print("Trusted certificate: No");
885 print_errors(print, errors
);
889 print_subject(print, cert
:issuer());
891 print("Valid for "..domain
..": "..(valid_identity
and "Yes" or "No"));
893 print_subject(print, cert
:subject());
896 return ("Showing "..n_certs
.." certificate"
897 ..(n_certs
==1 and "" or "s")
898 .." presented by "..domain
..".");
901 function def_env
.s2s
:close(from
, to
, text
, condition
)
902 local print, count
= self
.session
.print, 0;
903 local s2s_sessions
= module
:shared
"/*/s2s/sessions";
906 if from
and not to
then
907 match_id
, from
= from
, nil;
909 return false, "Syntax: s2s:close('from', 'to') - Closes all s2s sessions from 'from' to 'to'";
910 elseif from
== to
then
911 return false, "Both from and to are the same... you can't do that :)";
914 for _
, session
in pairs(s2s_sessions
) do
915 local id
= session
.id
or (session
.type..tostring(session
):match("[a-f0-9]+$"));
916 if (match_id
and match_id
== id
)
917 or (session
.from_host
== from
and session
.to_host
== to
) then
918 print(("Closing connection from %s to %s [%s]"):format(session
.from_host
, session
.to_host
, id
));
919 (session
.close
or s2smanager
.destroy_session
)(session
, build_reason(text
, condition
));
923 return true, "Closed "..count
.." s2s session"..((count
== 1 and "") or "s");
926 function def_env
.s2s
:closeall(host
, text
, condition
)
928 local s2s_sessions
= module
:shared
"/*/s2s/sessions";
929 for _
,session
in pairs(s2s_sessions
) do
930 if not host
or session
.from_host
== host
or session
.to_host
== host
then
931 session
:close(build_reason(text
, condition
));
935 if count
== 0 then return false, "No sessions to close.";
936 else return true, "Closed "..count
.." s2s session"..((count
== 1 and "") or "s"); end
939 def_env
.host
= {}; def_env
.hosts
= def_env
.host
;
941 function def_env
.host
:activate(hostname
, config
)
942 return hostmanager
.activate(hostname
, config
);
944 function def_env
.host
:deactivate(hostname
, reason
)
945 return hostmanager
.deactivate(hostname
, reason
);
948 function def_env
.host
:list()
949 local print = self
.session
.print;
952 for host
, host_session
in iterators
.sorted_pairs(prosody
.hosts
) do
954 type = host_session
.type;
955 if type == "local" then
958 type = module
:context(host
):get_option_string("component_module", type);
959 if type ~= "component" then
960 type = type .. " component";
962 print(("%s (%s)"):format(host
, type));
965 return true, i
.." hosts";
970 function def_env
.port
:list()
971 local print = self
.session
.print;
972 local services
= portmanager
.get_active_services().data
;
973 local n_services
, n_ports
= 0, 0;
974 for service
, interfaces
in iterators
.sorted_pairs(services
) do
975 n_services
= n_services
+ 1;
976 local ports_list
= {};
977 for interface
, ports
in pairs(interfaces
) do
978 for port
in pairs(ports
) do
979 table.insert(ports_list
, "["..interface
.."]:"..port
);
982 n_ports
= n_ports
+ #ports_list
;
983 print(service
..": "..table.concat(ports_list
, ", "));
985 return true, n_services
.." services listening on "..n_ports
.." ports";
988 function def_env
.port
:close(close_port
, close_interface
)
989 close_port
= assert(tonumber(close_port
), "Invalid port number");
991 local services
= portmanager
.get_active_services().data
;
992 for service
, interfaces
in pairs(services
) do -- luacheck: ignore 213
993 for interface
, ports
in pairs(interfaces
) do
994 if not close_interface
or close_interface
== interface
then
995 if ports
[close_port
] then
996 self
.session
.print("Closing ["..interface
.."]:"..close_port
.."...");
997 local ok
, err
= portmanager
.close(interface
, close_port
)
999 self
.session
.print("Failed to close "..interface
.." "..close_port
..": "..err
);
1001 n_closed
= n_closed
+ 1;
1007 return true, "Closed "..n_closed
.." ports";
1012 local console_room_mt
= {
1013 __index
= function (self
, k
) return self
.room
[k
]; end;
1014 __tostring
= function (self
)
1015 return "MUC room <"..self
.room
.jid
..">";
1019 local function check_muc(jid
)
1020 local room_name
, host
= jid_split(jid
);
1021 if not prosody
.hosts
[host
] then
1022 return nil, "No such host: "..host
;
1023 elseif not prosody
.hosts
[host
].modules
.muc
then
1024 return nil, "Host '"..host
.."' is not a MUC service";
1026 return room_name
, host
;
1029 function def_env
.muc
:create(room_jid
, config
)
1030 local room_name
, host
= check_muc(room_jid
);
1031 if not room_name
then
1032 return room_name
, host
;
1034 if not room_name
then return nil, host
end
1035 if config
~= nil and type(config
) ~= "table" then return nil, "Config must be a table"; end
1036 if prosody
.hosts
[host
].modules
.muc
.get_room_from_jid(room_jid
) then return nil, "Room exists already" end
1037 return prosody
.hosts
[host
].modules
.muc
.create_room(room_jid
, config
);
1040 function def_env
.muc
:room(room_jid
)
1041 local room_name
, host
= check_muc(room_jid
);
1042 if not room_name
then
1043 return room_name
, host
;
1045 local room_obj
= prosody
.hosts
[host
].modules
.muc
.get_room_from_jid(room_jid
);
1046 if not room_obj
then
1047 return nil, "No such room: "..room_jid
;
1049 return setmetatable({ room
= room_obj
}, console_room_mt
);
1052 function def_env
.muc
:list(host
)
1053 local host_session
= prosody
.hosts
[host
];
1054 if not host_session
or not host_session
.modules
.muc
then
1055 return nil, "Please supply the address of a local MUC component";
1057 local print = self
.session
.print;
1059 for room
in host_session
.modules
.muc
.each_room() do
1063 return true, c
.." rooms";
1066 local um
= require
"core.usermanager";
1069 function def_env
.user
:create(jid
, password
)
1070 local username
, host
= jid_split(jid
);
1071 if not prosody
.hosts
[host
] then
1072 return nil, "No such host: "..host
;
1073 elseif um
.user_exists(username
, host
) then
1074 return nil, "User exists";
1076 local ok
, err
= um
.create_user(username
, password
, host
);
1078 return true, "User created";
1080 return nil, "Could not create user: "..err
;
1084 function def_env
.user
:delete(jid
)
1085 local username
, host
= jid_split(jid
);
1086 if not prosody
.hosts
[host
] then
1087 return nil, "No such host: "..host
;
1088 elseif not um
.user_exists(username
, host
) then
1089 return nil, "No such user";
1091 local ok
, err
= um
.delete_user(username
, host
);
1093 return true, "User deleted";
1095 return nil, "Could not delete user: "..err
;
1099 function def_env
.user
:password(jid
, password
)
1100 local username
, host
= jid_split(jid
);
1101 if not prosody
.hosts
[host
] then
1102 return nil, "No such host: "..host
;
1103 elseif not um
.user_exists(username
, host
) then
1104 return nil, "No such user";
1106 local ok
, err
= um
.set_password(username
, password
, host
, nil);
1108 return true, "User password changed";
1110 return nil, "Could not change password for user: "..err
;
1114 function def_env
.user
:list(host
, pat
)
1116 return nil, "No host given";
1117 elseif not prosody
.hosts
[host
] then
1118 return nil, "No such host";
1120 local print = self
.session
.print;
1121 local total
, matches
= 0, 0;
1122 for user
in um
.users(host
) do
1123 if not pat
or user
:match(pat
) then
1124 print(user
.."@"..host
);
1125 matches
= matches
+ 1;
1129 return true, "Showing "..(pat
and (matches
.." of ") or "all " )..total
.." users";
1134 local st
= require
"util.stanza";
1135 local new_id
= require
"util.id".medium
;
1136 function def_env
.xmpp
:ping(localhost
, remotehost
, timeout
)
1137 localhost
= select(2, jid_split(localhost
));
1138 remotehost
= select(2, jid_split(remotehost
));
1139 if not localhost
then
1140 return nil, "Invalid sender hostname";
1141 elseif not prosody
.hosts
[localhost
] then
1142 return nil, "No such local host";
1144 if not remotehost
then
1145 return nil, "Invalid destination hostname";
1146 elseif prosody
.hosts
[remotehost
] then
1147 return nil, "Both hosts are local";
1149 local iq
= st
.iq
{ from
=localhost
, to
=remotehost
, type="get", id
=new_id()}
1150 :tag("ping", {xmlns
="urn:xmpp:ping"});
1152 local wait
, done
= async
.waiter();
1153 module
:context(localhost
):send_iq(iq
, nil, timeout
)
1154 :next(function (ret_
) ret
= ret_
; end,
1155 function (err_
) err
= err_
; end)
1159 return true, "pong from " .. ret
.stanza
.attr
.from
;
1161 return false, tostring(err
);
1166 local adns
= require
"net.adns";
1167 local dns
= require
"net.dns";
1169 function def_env
.dns
:lookup(name
, typ
, class
)
1170 local ret
= "Query sent";
1171 local print = self
.session
.print;
1172 local function handler(...)
1173 ret
= "Got response";
1176 adns
.lookup(handler
, name
, typ
, class
);
1180 function def_env
.dns
:addnameserver(...)
1181 dns
._resolver
:addnameserver(...)
1185 function def_env
.dns
:setnameserver(...)
1186 dns
._resolver
:setnameserver(...)
1190 function def_env
.dns
:purge()
1195 function def_env
.dns
:cache()
1196 return true, "Cache:\n"..tostring(dns
.cache())
1201 function def_env
.http
:list()
1202 local print = self
.session
.print;
1204 for host
in pairs(prosody
.hosts
) do
1205 local http_apps
= modulemanager
.get_items("http-provider", host
);
1206 if #http_apps
> 0 then
1207 local http_host
= module
:context(host
):get_option_string("http_host");
1208 print("HTTP endpoints on "..host
..(http_host
and (" (using "..http_host
.."):") or ":"));
1209 for _
, provider
in ipairs(http_apps
) do
1210 local url
= module
:context(host
):http_url(provider
.name
, provider
.default_path
);
1217 local default_host
= module
:get_option_string("http_default_host");
1218 if not default_host
then
1219 print("HTTP requests to unknown hosts will return 404 Not Found");
1221 print("HTTP requests to unknown hosts will be handled by "..default_host
);
1228 function def_env
.debug
:logevents(host
)
1229 helpers
.log_host_events(host
);
1233 function def_env
.debug
:events(host
, event
)
1235 if host
and host
~= "*" then
1236 if host
== "http" then
1237 events_obj
= require
"net.http.server"._events
;
1238 elseif not prosody
.hosts
[host
] then
1239 return false, "Unknown host: "..host
;
1241 events_obj
= prosody
.hosts
[host
].events
;
1244 events_obj
= prosody
.events
;
1246 return true, helpers
.show_events(events_obj
, event
);
1249 function def_env
.debug
:timers()
1250 local socket
= require
"socket";
1251 local print = self
.session
.print;
1252 local add_task
= require
"util.timer".add_task
;
1253 local h
, params
= add_task
.h
, add_task
.params
;
1255 print("-- util.timer");
1256 for i
, id
in ipairs(h
.ids
) do
1257 if not params
[id
] then
1258 print(os
.date("%F %T", h
.priorities
[i
]), h
.items
[id
]);
1259 elseif not params
[id
].callback
then
1260 print(os
.date("%F %T", h
.priorities
[i
]), h
.items
[id
], unpack(params
[id
]));
1262 print(os
.date("%F %T", h
.priorities
[i
]), params
[id
].callback
, unpack(params
[id
]));
1266 if server
.event_base
then
1268 for _
, v
in pairs(debug
.getregistry()) do
1269 if type(v
) == "function" and v
.callback
and v
.callback
== add_task
._on_timer
then
1273 print(count
.. " libevent callbacks");
1276 local next_time
= h
:peek();
1278 return true, os
.date("Next event at %F %T (in %%.6fs)", next_time
):format(next_time
- socket
.gettime());
1284 -- COMPAT: debug:timers() was timer:info() for some time in trunk
1285 def_env
.timer
= { info
= def_env
.debug
.timers
};
1287 module
:hook("server-stopping", function(event
)
1288 for _
, session
in pairs(sessions
) do
1289 session
.print("Shutting down: "..(event
.reason
or "unknown reason"));
1295 local function format_stat(type, value
, ref_value
)
1296 ref_value
= ref_value
or value
;
1297 --do return tostring(value) end
1298 if type == "duration" then
1299 if ref_value
< 0.001 then
1300 return ("%g µs"):format(value
*1000000);
1301 elseif ref_value
< 0.9 then
1302 return ("%0.2f ms"):format(value
*1000);
1304 return ("%0.2f"):format(value
);
1305 elseif type == "size" then
1306 if ref_value
> 1048576 then
1307 return ("%d MB"):format(value
/1048576);
1308 elseif ref_value
> 1024 then
1309 return ("%d KB"):format(value
/1024);
1311 return ("%d bytes"):format(value
);
1312 elseif type == "rate" then
1313 if ref_value
< 0.9 then
1314 return ("%0.2f/min"):format(value
*60);
1316 return ("%0.2f/sec"):format(value
);
1318 return tostring(value
);
1321 local stats_methods
= {};
1322 function stats_methods
:bounds(_lower
, _upper
)
1323 for _
, stat_info
in ipairs(self
) do
1324 local data
= stat_info
[4];
1326 local lower
= _lower
or data
.min;
1327 local upper
= _upper
or data
.max;
1337 for _
, v
in ipairs(data
.samples
) do
1340 elseif v
>=lower
then
1341 table.insert(new_data
.samples
, v
);
1345 new_data
.sample_count
= #new_data
.samples
;
1346 stat_info
[4] = new_data
;
1347 stat_info
[3] = sum
/new_data
.sample_count
;
1353 function stats_methods
:trim(lower
, upper
)
1354 upper
= upper
or (100-lower
);
1355 local statistics
= require
"util.statistics";
1356 for _
, stat_info
in ipairs(self
) do
1358 local data
= stat_info
[4];
1361 min = statistics
.get_percentile(data
, lower
);
1362 max = statistics
.get_percentile(data
, upper
);
1369 for _
, v
in ipairs(data
.samples
) do
1370 if v
> new_data
.max then
1372 elseif v
>=new_data
.min then
1373 table.insert(new_data
.samples
, v
);
1377 new_data
.sample_count
= #new_data
.samples
;
1378 stat_info
[4] = new_data
;
1379 stat_info
[3] = sum
/new_data
.sample_count
;
1385 function stats_methods
:max(upper
)
1386 return self
:bounds(nil, upper
);
1389 function stats_methods
:min(lower
)
1390 return self
:bounds(lower
, nil);
1393 function stats_methods
:summary()
1394 local statistics
= require
"util.statistics";
1395 for _
, stat_info
in ipairs(self
) do
1396 local type, value
, data
= stat_info
[2], stat_info
[3], stat_info
[4];
1397 if data
and data
.samples
then
1398 table.insert(stat_info
.output
, string.format("Count: %d (%d captured)",
1402 table.insert(stat_info
.output
, string.format("Min: %s Mean: %s Max: %s",
1403 format_stat(type, data
.min),
1404 format_stat(type, value
),
1405 format_stat(type, data
.max)
1407 table.insert(stat_info
.output
, string.format("Q1: %s Median: %s Q3: %s",
1408 format_stat(type, statistics
.get_percentile(data
, 25)),
1409 format_stat(type, statistics
.get_percentile(data
, 50)),
1410 format_stat(type, statistics
.get_percentile(data
, 75))
1417 function stats_methods
:cfgraph()
1418 for _
, stat_info
in ipairs(self
) do
1419 local name
, type, value
, data
= unpack(stat_info
, 1, 4);
1420 local function print(s
)
1421 table.insert(stat_info
.output
, s
);
1424 if data
and data
.sample_count
and data
.sample_count
> 0 then
1425 local raw_histogram
= require
"util.statistics".get_histogram(data
);
1427 local graph_width
, graph_height
= 50, 10;
1428 local eighth_chars
= " ▁▂▃▄▅▆▇█";
1430 local range
= data
.max - data
.min;
1433 local x_scaling
= #raw_histogram
/graph_width
;
1434 local histogram
= {};
1435 for i
= 1, graph_width
do
1436 histogram
[i
] = math
.max(raw_histogram
[i
*x_scaling
-1] or 0, raw_histogram
[i
*x_scaling
] or 0);
1440 print(("_"):rep(52)..format_stat(type, data
.max));
1441 for row
= graph_height
, 1, -1 do
1442 local row_chars
= {};
1443 local min_eighths
, max_eighths
= 8, 0;
1444 for i
= 1, #histogram
do
1445 local char_eighths
= math
.ceil(math
.max(math
.min((graph_height
/(data
.max/histogram
[i
]))-(row
-1), 1), 0)*8);
1446 if char_eighths
< min_eighths
then
1447 min_eighths
= char_eighths
;
1449 if char_eighths
> max_eighths
then
1450 max_eighths
= char_eighths
;
1452 if char_eighths
== 0 then
1455 local char
= eighth_chars
:sub(char_eighths
*3+1, char_eighths
*3+3);
1456 row_chars
[i
] = char
;
1459 print(table.concat(row_chars
).."|-"..format_stat(type, data
.max/(graph_height
/(row
-0.5))));
1461 print(("\\ "):rep(11));
1462 local x_labels
= {};
1464 local s
= ("%-4s"):format((i
-1)*10);
1466 s
= s
:sub(1, 3).."…";
1470 print(" "..table.concat(x_labels
, " "));
1472 local margin
= math
.floor((graph_width
-#units
)/2);
1473 print((" "):rep(margin
)..units
);
1475 print("[range too small to graph]");
1483 function stats_methods
:histogram()
1484 for _
, stat_info
in ipairs(self
) do
1485 local name
, type, value
, data
= unpack(stat_info
, 1, 4);
1486 local function print(s
)
1487 table.insert(stat_info
.output
, s
);
1493 elseif not data
.sample_count
then
1494 print("[not a sampled metric type]");
1498 local graph_width
, graph_height
= 50, 10;
1499 local eighth_chars
= " ▁▂▃▄▅▆▇█";
1501 local range
= data
.max - data
.min;
1504 local n_buckets
= graph_width
;
1506 local histogram
= {};
1507 for i
= 1, n_buckets
do
1510 local max_bin_samples
= 0;
1511 for _
, d
in ipairs(data
.samples
) do
1512 local bucket
= math
.floor(1+(n_buckets
-1)/(range
/(d
-data
.min)));
1513 histogram
[bucket
] = histogram
[bucket
] + 1;
1514 if histogram
[bucket
] > max_bin_samples
then
1515 max_bin_samples
= histogram
[bucket
];
1520 print(("_"):rep(52)..max_bin_samples
);
1521 for row
= graph_height
, 1, -1 do
1522 local row_chars
= {};
1523 local min_eighths
, max_eighths
= 8, 0;
1524 for i
= 1, #histogram
do
1525 local char_eighths
= math
.ceil(math
.max(math
.min((graph_height
/(max_bin_samples
/histogram
[i
]))-(row
-1), 1), 0)*8);
1526 if char_eighths
< min_eighths
then
1527 min_eighths
= char_eighths
;
1529 if char_eighths
> max_eighths
then
1530 max_eighths
= char_eighths
;
1532 if char_eighths
== 0 then
1535 local char
= eighth_chars
:sub(char_eighths
*3+1, char_eighths
*3+3);
1536 row_chars
[i
] = char
;
1539 print(table.concat(row_chars
).."|-"..math
.ceil((max_bin_samples
/graph_height
)*(row
-0.5)));
1541 print(("\\ "):rep(11));
1542 local x_labels
= {};
1544 local s
= ("%-4s"):format(format_stat(type, data
.min+range
*i
/11, data
.min):match("^%S+"));
1546 s
= s
:sub(1, 3).."…";
1550 print(" "..table.concat(x_labels
, " "));
1551 local units
= format_stat(type, data
.min):match("%s+(.+)$") or data
.units
or "";
1552 local margin
= math
.floor((graph_width
-#units
)/2);
1553 print((" "):rep(margin
)..units
);
1555 print("[range too small to graph]");
1562 local function stats_tostring(stats
)
1563 local print = stats
.session
.print;
1564 for _
, stat_info
in ipairs(stats
) do
1565 if #stat_info
.output
> 0 then
1566 print("\n#"..stat_info
[1]);
1568 for _
, v
in ipairs(stat_info
.output
) do
1573 print(("%-50s %s"):format(stat_info
[1], format_stat(stat_info
[2], stat_info
[3])));
1576 return #stats
.." statistics displayed";
1579 local stats_mt
= {__index
= stats_methods
, __tostring
= stats_tostring
}
1580 local function new_stats_context(self
)
1581 return setmetatable({ session
= self
.session
, stats
= true }, stats_mt
);
1584 function def_env
.stats
:show(filter
)
1585 local stats
, changed
, extra
= require
"core.statsmanager".get_stats();
1586 local available
, displayed
= 0, 0;
1587 local displayed_stats
= new_stats_context(self
);
1588 for name
, value
in iterators
.sorted_pairs(stats
) do
1589 available
= available
+ 1;
1590 if not filter
or name
:match(filter
) then
1591 displayed
= displayed
+ 1;
1592 local type = name
:match(":(%a+)$");
1593 table.insert(displayed_stats
, {
1594 name
, type, value
, extra
[name
];
1599 return displayed_stats
;
1606 function printbanner(session
)
1607 local option
= module
:get_option_string("console_banner", "full");
1608 if option
== "full" or option
== "graphic" then
1611 | _ \ _ __ ___ ___ _-_ __| |_ _
1612 | |_) | '__/ _ \/ __|/ _ \ / _` | | | |
1613 | __/| | | (_) \__ \ |_| | (_| | |_| |
1614 |_| |_| \___/|___/\___/ \__,_|\__, |
1615 A study in simplicity |___/
1619 if option
== "short" or option
== "full" then
1620 session
.print("Welcome to the Prosody administration console. For a list of commands, type: help");
1621 session
.print("You may find more help on using this console in our online documentation at ");
1622 session
.print("https://prosody.im/doc/console\n");
1624 if option
~= "short" and option
~= "full" and option
~= "graphic" then
1625 session
.print(option
);
1629 module
:provides("net", {
1631 listener
= console_listener
;
1632 default_port
= 5582;