Automated merge with http://waqas.ath.cx:8000/
[prosody.git] / plugins / mod_console.lua
1 -- Prosody IM v0.4
2 -- Copyright (C) 2008-2009 Matthew Wild
3 -- Copyright (C) 2008-2009 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 prosody = _G.prosody;
12 local hosts = prosody.hosts;
13 local connlisteners_register = require "net.connlisteners".register;
14
15 local console_listener = { default_port = 5582; default_mode = "*l"; };
16
17 require "util.iterators";
18 local set, array = require "util.set", require "util.array";
19
20 local commands = {};
21 local def_env = {};
22 local default_env_mt = { __index = def_env };
23
24 console = {};
25
26 function console:new_session(conn)
27         local w = function(s) conn.write(s:gsub("\n", "\r\n")); end;
28         local session = { conn = conn;
29                         send = function (t) w(tostring(t)); end;
30                         print = function (t) w("| "..tostring(t).."\n"); end;
31                         disconnect = function () conn.close(); end;
32                         };
33         session.env = setmetatable({}, default_env_mt);
34         
35         -- Load up environment with helper objects
36         for name, t in pairs(def_env) do
37                 if type(t) == "table" then
38                         session.env[name] = setmetatable({ session = session }, { __index = t });
39                 end
40         end
41         
42         return session;
43 end
44
45 local sessions = {};
46
47 function console_listener.listener(conn, data)
48         local session = sessions[conn];
49         
50         if not session then
51                 -- Handle new connection
52                 session = console:new_session(conn);
53                 sessions[conn] = session;
54                 printbanner(session);
55         end
56         if data then
57                 -- Handle data
58                 (function(session, data)
59                         local useglobalenv;
60                         
61                         if data:match("[!.]$") then
62                                 local command = data:lower();
63                                 command = data:match("^%w+") or data:match("%p");
64                                 if commands[command] then
65                                         commands[command](session, data);
66                                         return;
67                                 end
68                         end
69
70                         if data:match("^>") then
71                                 data = data:gsub("^>", "");
72                                 useglobalenv = true;
73                         end
74                         
75                         session.env._ = data;
76                         
77                         local chunk, err = loadstring("return "..data);
78                         if not chunk then
79                                 chunk, err = loadstring(data);
80                                 if not chunk then
81                                         err = err:gsub("^%[string .-%]:%d+: ", "");
82                                         err = err:gsub("^:%d+: ", "");
83                                         err = err:gsub("'<eof>'", "the end of the line");
84                                         session.print("Sorry, I couldn't understand that... "..err);
85                                         return;
86                                 end
87                         end
88                         
89                         setfenv(chunk, (useglobalenv and _G) or session.env or nil);
90                         
91                         local ranok, taskok, message = pcall(chunk);
92                         
93                         if not ranok then
94                                 session.print("Fatal error while running command, it did not complete");
95                                 session.print("Error: "..taskok);
96                                 return;
97                         end
98                         
99                         if not message then
100                                 session.print("Result: "..tostring(taskok));
101                                 return;
102                         elseif (not taskok) and message then
103                                 session.print("Command completed with a problem");
104                                 session.print("Message: "..tostring(message));
105                                 return;
106                         end
107                         
108                         session.print("OK: "..tostring(message));
109                 end)(session, data);
110         end
111         session.send(string.char(0));
112 end
113
114 function console_listener.disconnect(conn, err)
115         
116 end
117
118 connlisteners_register('console', console_listener);
119
120 -- Console commands --
121 -- These are simple commands, not valid standalone in Lua
122
123 function commands.bye(session)
124         session.print("See you! :)");
125         session.disconnect();
126 end
127
128 commands["!"] = function (session, data)
129         if data:match("^!!") then
130                 session.print("!> "..session.env._);
131                 return console_listener.listener(session.conn, session.env._);
132         end
133         local old, new = data:match("^!(.-[^\\])!(.-)!$");
134         if old and new then
135                 local ok, res = pcall(string.gsub, session.env._, old, new);
136                 if not ok then
137                         session.print(res)
138                         return;
139                 end
140                 session.print("!> "..res);
141                 return console_listener.listener(session.conn, res);
142         end
143         session.print("Sorry, not sure what you want");
144 end
145
146 -- Session environment --
147 -- Anything in def_env will be accessible within the session as a global variable
148
149 def_env.server = {};
150 function def_env.server:reload()
151         prosody.unlock_globals();
152         dofile "prosody"
153         prosody = _G.prosody;
154         return true, "Server reloaded";
155 end
156
157 def_env.module = {};
158
159 local function get_hosts_set(hosts)
160         if type(hosts) == "table" then
161                 if hosts[1] then
162                         return set.new(hosts);
163                 elseif hosts._items then
164                         return hosts;
165                 end
166         elseif type(hosts) == "string" then
167                 return set.new { hosts };
168         elseif hosts == nil then
169                 return set.new(array.collect(keys(prosody.hosts)))
170                         / function (host) return prosody.hosts[host].type == "local"; end;
171         end
172 end
173
174 function def_env.module:load(name, hosts, config)
175         local mm = require "modulemanager";
176         
177         hosts = get_hosts_set(hosts);
178         
179         -- Load the module for each host
180         local ok, err, count = true, nil, 0;
181         for host in hosts do
182                 if (not mm.is_loaded(host, name)) then
183                         ok, err = mm.load(host, name, config);
184                         if not ok then
185                                 ok = false;
186                                 self.session.print(err or "Unknown error loading module");
187                         else
188                                 count = count + 1;
189                                 self.session.print("Loaded for "..host);
190                         end
191                 end
192         end
193         
194         return ok, (ok and "Module loaded onto "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err));       
195 end
196
197 function def_env.module:unload(name, hosts)
198         local mm = require "modulemanager";
199
200         hosts = get_hosts_set(hosts);
201         
202         -- Unload the module for each host
203         local ok, err, count = true, nil, 0;
204         for host in hosts do
205                 if mm.is_loaded(host, name) then
206                         ok, err = mm.unload(host, name);
207                         if not ok then
208                                 ok = false;
209                                 self.session.print(err or "Unknown error unloading module");
210                         else
211                                 count = count + 1;
212                                 self.session.print("Unloaded from "..host);
213                         end
214                 end
215         end
216         return ok, (ok and "Module unloaded from "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err));
217 end
218
219 function def_env.module:reload(name, hosts)
220         local mm = require "modulemanager";
221
222         hosts = get_hosts_set(hosts);
223         
224         -- Reload the module for each host
225         local ok, err, count = true, nil, 0;
226         for host in hosts do
227                 if mm.is_loaded(host, name) then
228                         ok, err = mm.reload(host, name);
229                         if not ok then
230                                 ok = false;
231                                 self.session.print(err or "Unknown error reloading module");
232                         else
233                                 count = count + 1;
234                                 if ok == nil then
235                                         ok = true;
236                                 end
237                                 self.session.print("Reloaded on "..host);
238                         end
239                 end
240         end
241         return ok, (ok and "Module reloaded on "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err));
242 end
243
244 def_env.config = {};
245 function def_env.config:load(filename, format)
246         local config_load = require "core.configmanager".load;
247         local ok, err = config_load(filename, format);
248         if not ok then
249                 return false, err or "Unknown error loading config";
250         end
251         return true, "Config loaded";
252 end
253
254 function def_env.config:get(host, section, key)
255         local config_get = require "core.configmanager".get
256         return true, tostring(config_get(host, section, key));
257 end
258
259 def_env.hosts = {};
260 function def_env.hosts:list()
261         for host, host_session in pairs(hosts) do
262                 self.session.print(host);
263         end
264         return true, "Done";
265 end
266
267 function def_env.hosts:add(name)
268 end
269
270 def_env.c2s = {};
271
272 local function show_c2s(callback)
273         for hostname, host in pairs(hosts) do
274                 for username, user in pairs(host.sessions or {}) do
275                         for resource, session in pairs(user.sessions or {}) do
276                                 local jid = username.."@"..hostname.."/"..resource;
277                                 callback(jid, session);
278                         end
279                 end
280         end
281 end
282
283 function def_env.c2s:show(match_jid)
284         local print, count = self.session.print, 0;
285         show_c2s(function (jid)
286                 if (not match_jid) or jid:match(match_jid) then
287                         count = count + 1;
288                         print(jid);
289                 end             
290         end);
291         return true, "Total: "..count.." clients";
292 end
293
294 function def_env.c2s:show_insecure(match_jid)
295         local print, count = self.session.print, 0;
296         show_c2s(function (jid, session)
297                 if ((not match_jid) or jid:match(match_jid)) and not session.secure then
298                         count = count + 1;
299                         print(jid);
300                 end             
301         end);
302         return true, "Total: "..count.." insecure client connections";
303 end
304
305 function def_env.c2s:show_secure(match_jid)
306         local print, count = self.session.print, 0;
307         show_c2s(function (jid, session)
308                 if ((not match_jid) or jid:match(match_jid)) and session.secure then
309                         count = count + 1;
310                         print(jid);
311                 end             
312         end);
313         return true, "Total: "..count.." secure client connections";
314 end
315
316
317 def_env.s2s = {};
318 function def_env.s2s:show(match_jid)
319         local _print = self.session.print;
320         local print = self.session.print;
321         
322         local count_in, count_out = 0,0;
323         
324         for host, host_session in pairs(hosts) do
325                 print = function (...) _print(host); _print(...); print = _print; end
326                 for remotehost, session in pairs(host_session.s2sout) do
327                         if (not match_jid) or remotehost:match(match_jid) or host:match(match_jid) then
328                                 count_out = count_out + 1;
329                                 print("    "..host.." -> "..remotehost);
330                                 if session.sendq then
331                                         print("        There are "..#session.sendq.." queued outgoing stanzas for this connection");
332                                 end
333                                 if session.type == "s2sout_unauthed" then
334                                         if session.connecting then
335                                                 print("        Connection not yet established");
336                                                 if not session.srv_hosts then
337                                                         if not session.conn then
338                                                                 print("        We do not yet have a DNS answer for this host's SRV records");
339                                                         else
340                                                                 print("        This host has no SRV records, using A record instead");
341                                                         end
342                                                 elseif session.srv_choice then
343                                                         print("        We are on SRV record "..session.srv_choice.." of "..#session.srv_hosts);
344                                                         local srv_choice = session.srv_hosts[session.srv_choice];
345                                                         print("        Using "..(srv_choice.target or ".")..":"..(srv_choice.port or 5269));
346                                                 end
347                                         elseif session.notopen then
348                                                 print("        The <stream> has not yet been opened");
349                                         elseif not session.dialback_key then
350                                                 print("        Dialback has not been initiated yet");
351                                         elseif session.dialback_key then
352                                                 print("        Dialback has been requested, but no result received");
353                                         end
354                                 end
355                         end
356                 end     
357                 
358                 for session in pairs(incoming_s2s) do
359                         if session.to_host == host and ((not match_jid) or host:match(match_jid) 
360                                 or (session.from_host and session.from_host:match(match_jid))) then
361                                 count_in = count_in + 1;
362                                 print("    "..host.." <- "..(session.from_host or "(unknown)"));
363                                 if session.type == "s2sin_unauthed" then
364                                                 print("        Connection not yet authenticated");
365                                 end
366                                 for name in pairs(session.hosts) do
367                                         if name ~= session.from_host then
368                                                 print("        also hosts "..tostring(name));
369                                         end
370                                 end
371                         end
372                 end
373                 
374                 print = _print;
375         end
376         
377         for session in pairs(incoming_s2s) do
378                 if not session.to_host and ((not match_jid) or session.from_host and session.from_host:match(match_jid)) then
379                         count_in = count_in + 1;
380                         print("Other incoming s2s connections");
381                         print("    (unknown) <- "..(session.from_host or "(unknown)"));                 
382                 end
383         end
384         
385         return true, "Total: "..count_out.." outgoing, "..count_in.." incoming connections";
386 end
387
388 -------------
389
390 function printbanner(session)
391 session.print [[
392                    ____                \   /     _       
393                     |  _ \ _ __ ___  ___  _-_   __| |_   _ 
394                     | |_) | '__/ _ \/ __|/ _ \ / _` | | | |
395                     |  __/| | | (_) \__ \ |_| | (_| | |_| |
396                     |_|   |_|  \___/|___/\___/ \__,_|\__, |
397                     A study in simplicity            |___/ 
398
399 ]]
400 session.print("Welcome to the Prosody administration console. For a list of commands, type: help");
401 session.print("You may find more help on using this console in our online documentation at ");
402 session.print("http://prosody.im/doc/console\n");
403 end