Merged with trunk
[prosody.git] / prosodyctl
1 #!/usr/bin/env lua
2 -- Prosody IM v0.4
3 -- Copyright (C) 2008-2009 Matthew Wild
4 -- Copyright (C) 2008-2009 Waqas Hussain
5 -- 
6 -- This project is MIT/X11 licensed. Please see the
7 -- COPYING file in the source package for more information.
8 --
9
10 -- prosodyctl - command-line controller for Prosody XMPP server
11
12 -- Will be modified by configure script if run --
13
14 CFG_SOURCEDIR=nil;
15 CFG_CONFIGDIR=os.getenv("PROSODY_CFGDIR");
16 CFG_PLUGINDIR=nil;
17 CFG_DATADIR=os.getenv("PROSODY_DATADIR");
18
19 -- -- -- -- -- -- -- ---- -- -- -- -- -- -- -- --
20
21 if CFG_SOURCEDIR then
22         package.path = CFG_SOURCEDIR.."/?.lua;"..package.path
23         package.cpath = CFG_SOURCEDIR.."/?.so;"..package.cpath
24 end
25
26 if CFG_DATADIR then
27         if os.getenv("HOME") then
28                 CFG_DATADIR = CFG_DATADIR:gsub("^~", os.getenv("HOME"));
29         end
30 end
31
32 -- Required to be able to find packages installed with luarocks
33 pcall(require, "luarocks.require")
34
35
36 config = require "core.configmanager"
37
38 do
39         -- TODO: Check for other formats when we add support for them
40         -- Use lfs? Make a new conf/ dir?
41         local ok, level, err = config.load((CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
42         if not ok then
43                 print("\n");
44                 print("**************************");
45                 if level == "parser" then
46                         print("A problem occured while reading the config file "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
47                         local err_line, err_message = tostring(err):match("%[string .-%]:(%d*): (.*)");
48                         print("Error"..(err_line and (" on line "..err_line) or "")..": "..(err_message or tostring(err)));
49                         print("");
50                 elseif level == "file" then
51                         print("Prosody was unable to find the configuration file.");
52                         print("We looked for: "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
53                         print("A sample config file is included in the Prosody download called prosody.cfg.lua.dist");
54                         print("Copy or rename it to prosody.cfg.lua and edit as necessary.");
55                 end
56                 print("More help on configuring Prosody can be found at http://prosody.im/doc/configure");
57                 print("Good luck!");
58                 print("**************************");
59                 print("");
60                 os.exit(1);
61         end
62 end
63
64 local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
65 require "util.datamanager".set_data_path(data_path);
66
67 -- Switch away from root and into the prosody user --
68 local switched_user, current_uid;
69 local ok, pposix = pcall(require, "util.pposix");
70 if ok and pposix then
71         current_uid = pposix.getuid();
72         if current_uid == 0 then
73                 -- We haz root!
74                 local desired_user = config.get("*", "core", "prosody_user") or "prosody";
75                 local ok, err = pposix.setuid(desired_user);
76                 if ok then
77                         -- Yay!
78                         switched_user = true;
79                 else
80                         -- Boo!
81                         print("Warning: Couldn't switch to Prosody user '"..tostring(desired_user).."': "..tostring(err));
82                 end
83         end
84 else
85         print("Error: Unable to load pposix module. Check that Prosody is installed correctly.")
86         print("For more help send the below error to us through http://prosody.im/discuss");
87         print(tostring(pposix))
88 end
89
90 local error_messages = setmetatable({ 
91                 ["invalid-username"] = "The given username is invalid in a Jabber ID";
92                 ["invalid-hostname"] = "The given hostname is invalid";
93                 ["no-password"] = "No password was supplied";
94                 ["no-such-user"] = "The given user does not exist on the server";
95                 ["unable-to-save-data"] = "Unable to store, perhaps you don't have permission?";
96                 ["no-pidfile"] = "There is no pidfile option in the configuration file, see http://prosody.im/doc/prosodyctl#pidfile for help";
97                 ["no-such-method"] = "This module has no commands";
98                 }, { __index = function (t,k) return "Error: "..(tostring(k):gsub("%-", " "):gsub("^.", string.upper)); end });
99
100 hosts = {};
101
102 require "core.hostmanager"
103 require "core.eventmanager".fire_event("server-starting");
104 require "core.modulemanager"
105
106 require "util.prosodyctl"
107 -----------------------
108
109 function show_message(msg, ...)
110         print(msg:format(...));
111 end
112
113 function show_warning(msg, ...)
114         print(msg:format(...));
115 end
116
117 function show_usage(usage, desc)
118         print("Usage: "..arg[0].." "..usage);
119         if desc then
120                 print(" "..desc);
121         end
122 end
123
124 local function getchar(n)
125         os.execute("stty raw -echo");
126         local char = io.read(n or 1);
127         os.execute("stty sane");
128         return char;
129 end
130         
131 local function getpass()
132         os.execute("stty -echo");
133         local pass = io.read("*l");
134         os.execute("stty sane");
135         io.write("\n");
136         return pass;
137 end
138
139 function show_yesno(prompt)
140         io.write(prompt, " ");
141         local choice = getchar():lower();
142         io.write("\n");
143         if not choice:match("%a") then
144                 choice = prompt:match("%[.-(%U).-%]$");
145                 if not choice then return nil; end
146         end
147         return (choice == "y");
148 end
149
150 local function read_password()
151         local password;
152         while true do
153                 io.write("Enter new password: ");
154                 password = getpass();
155                 io.write("Retype new password: ");
156                 if getpass() ~= password then
157                         if not show_yesno [=[Passwords did not match, try again? [Y/n]]=] then
158                                 return;
159                         end
160                 else
161                         break;
162                 end
163         end
164         return password;
165 end
166 -----------------------
167 local commands = {};
168 local command = arg[1];
169
170 function commands.adduser(arg)
171         if not arg[1] or arg[1] == "--help" then
172                 show_usage([[adduser JID]], [[Create the specified user account in Prosody]]);
173                 return 1;
174         end
175         local user, host = arg[1]:match("([^@]+)@(.+)");
176         if not user and host then
177                 show_message [[Failed to understand JID, please supply the JID you want to create]]
178                 show_usage [[adduser user@host]]
179                 return 1;
180         end
181         
182         if not host then
183                 show_message [[Please specify a JID, including a host. e.g. alice@example.com]];
184                 return 1;
185         end
186         
187         if prosodyctl.user_exists{ user = user, host = host } then
188                 show_message [[That user already exists]];
189                 return 1;
190         end
191         
192         if not hosts[host] then
193                 show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host)
194                 show_warning("The user will not be able to log in until this is changed.");
195         end
196         
197         local password = read_password();
198         if not password then return 1; end
199         
200         local ok, msg = prosodyctl.adduser { user = user, host = host, password = password };
201         
202         if ok then return 0; end
203         
204         show_message(error_messages[msg])
205         return 1;
206 end
207
208 function commands.passwd(arg)
209         if not arg[1] or arg[1] == "--help" then
210                 show_usage([[passwd JID]], [[Set the password for the specified user account in Prosody]]);
211                 return 1;
212         end
213         local user, host = arg[1]:match("([^@]+)@(.+)");
214         if not user and host then
215                 show_message [[Failed to understand JID, please supply the JID you want to set the password for]]
216                 show_usage [[passwd user@host]]
217                 return 1;
218         end
219         
220         if not host then
221                 show_message [[Please specify a JID, including a host. e.g. alice@example.com]];
222                 return 1;
223         end
224         
225         if not prosodyctl.user_exists { user = user, host = host } then
226                 show_message [[That user does not exist, use prosodyctl adduser to create a new user]]
227                 return 1;
228         end
229         
230         local password = read_password();
231         if not password then return 1; end
232         
233         local ok, msg = prosodyctl.passwd { user = user, host = host, password = password };
234         
235         if ok then return 0; end
236         
237         show_message(error_messages[msg])
238         return 1;
239 end
240
241 function commands.deluser(arg)
242         if not arg[1] or arg[1] == "--help" then
243                 show_usage([[deluser JID]], [[Permanently remove the specified user account from Prosody]]);
244                 return 1;
245         end
246         local user, host = arg[1]:match("([^@]+)@(.+)");
247         if not user and host then
248                 show_message [[Failed to understand JID, please supply the JID you want to set the password for]]
249                 show_usage [[passwd user@host]]
250                 return 1;
251         end
252         
253         if not host then
254                 show_message [[Please specify a JID, including a host. e.g. alice@example.com]];
255                 return 1;
256         end
257         
258         if not prosodyctl.user_exists { user = user, host = host } then
259                 show_message [[That user does not exist on this server]]
260                 return 1;
261         end
262         
263         local ok, msg = prosodyctl.passwd { user = user, host = host };
264         
265         if ok then return 0; end
266         
267         show_message(error_messages[msg])
268         return 1;
269 end
270
271 function commands.start(arg)
272         if arg[1] == "--help" then
273                 show_usage([[start]], [[Start Prosody]]);
274                 return 1;
275         end
276         local ok, ret = prosodyctl.isrunning();
277         if not ok then
278                 show_message(error_messages[ret]);
279                 return 1;
280         end
281         
282         if ret then
283                 local ok, ret = prosodyctl.getpid();
284                 if not ok then
285                         show_message("Couldn't get running Prosody's PID");
286                         show_message(error_messages[ret]);
287                         return 1;
288                 end
289                 show_message("Prosody is already running with PID %s", ret or "(unknown)");
290                 return 1;
291         end
292         
293         local ok, ret = prosodyctl.start();
294         if ok then return 0; end
295
296         show_message("Failed to start Prosody");
297         show_message(error_messages[ret])       
298         return 1;       
299 end
300
301 function commands.status(arg)
302         if arg[1] == "--help" then
303                 show_usage([[status]], [[Reports the running status of Prosody]]);
304                 return 1;
305         end
306
307         local ok, ret = prosodyctl.isrunning();
308         if not ok then
309                 show_message(error_messages[ret]);
310                 return 1;
311         end
312         
313         if ret then
314                 local ok, ret = prosodyctl.getpid();
315                 if not ok then
316                         show_message("Couldn't get running Prosody's PID");
317                         show_message(error_messages[ret]);
318                         return 1;
319                 end
320                 show_message("Prosody is running with PID %s", ret or "(unknown)");
321                 return 0;
322         else
323                 show_message("Prosody is not running");
324                 if not switched_user and current_uid ~= 0 then
325                         print("\nNote:")
326                         print(" You will also see this if prosodyctl is not running under");
327                         print(" the same user account as Prosody. Try running as root (e.g. ");
328                         print(" with 'sudo' in front) to gain access to Prosody's real status.");
329                 end
330                 return 2
331         end
332         return 1;
333 end
334
335 function commands.stop(arg)
336         if arg[1] == "--help" then
337                 show_usage([[stop]], [[Stop a running Prosody server]]);
338                 return 1;
339         end
340
341         if not prosodyctl.isrunning() then
342                 show_message("Prosody is not running");
343                 return 1;
344         end
345         
346         local ok, ret = prosodyctl.stop();
347         if ok then return 0; end
348
349         show_message(error_messages[ret]);
350         return 1;
351 end
352
353 -- ejabberdctl compatibility
354
355 function commands.register(arg)
356         local user, host, password = unpack(arg);
357         if (not (user and host)) or arg[1] == "--help" then
358                 if user ~= "--help" then
359                         if not user then
360                                 show_message [[No username specified]]
361                         elseif not host then
362                                 show_message [[Please specify which host you want to register the user on]];
363                         end
364                 end
365                 show_usage("register USER HOST [PASSWORD]", "Register a user on the server, with the given password");
366                 return 1;
367         end
368         if not password then
369                 password = read_password();
370                 if not password then
371                         show_message [[Unable to register user with no password]];
372                         return 1;
373                 end
374         end
375         
376         local ok, msg = prosodyctl.adduser { user = user, host = host, password = password };
377         
378         if ok then return 0; end
379         
380         show_message(error_messages[msg])
381         return 1;
382 end
383
384 function commands.unregister(arg)
385         local user, host = unpack(arg);
386         if (not (user and host)) or arg[1] == "--help" then
387                 if user ~= "--help" then
388                         if not user then
389                                 show_message [[No username specified]]
390                         elseif not host then
391                                 show_message [[Please specify which host you want to unregister the user from]];
392                         end
393                 end
394                 show_usage("unregister USER HOST [PASSWORD]", "Permanently remove a user account from the server");
395                 return 1;
396         end
397
398         local ok, msg = prosodyctl.deluser { user = user, host = host };
399         
400         if ok then return 0; end
401         
402         show_message(error_messages[msg])
403         return 1;
404 end
405
406
407 ---------------------
408
409 if command:match("^mod_") then -- Is a command in a module
410         local module_name = command:match("^mod_(.+)");
411         local ret, err = modulemanager.load("*", module_name);
412         if not ret then
413                 show_message("Failed to load module '"..module_name.."': "..err);
414                 os.exit(1);
415         end
416         
417         table.remove(arg, 1);
418         
419         local module = modulemanager.get_module("*", module_name);
420         if not module then
421                 show_message("Failed to load module '"..module_name.."': Unknown error");
422                 os.exit(1);
423         end
424         
425         if not modulemanager.module_has_method(module, "command") then
426                 show_message("Fail: mod_"..module_name.." does not support any commands");
427                 os.exit(1);
428         end
429         
430         local ok, ret = modulemanager.call_module_method(module, "command", arg);
431         if ok then
432                 if type(ret) == "number" then
433                         os.exit(ret);
434                 elseif type(ret) == "string" then
435                         show_message(ret);
436                 end
437                 os.exit(0); -- :)
438         else
439                 show_message("Failed to execute command: "..error_messages[ret]);
440                 os.exit(1); -- :(
441         end
442 end
443
444 if not commands[command] then -- Show help for all commands
445         function show_usage(usage, desc)
446                 print(" "..usage);
447                 print("    "..desc);
448         end
449
450         print("prosodyctl - Manage a Prosody server");
451         print("");
452         print("Usage: "..arg[0].." COMMAND [OPTIONS]");
453         print("");
454         print("Where COMMAND may be one of:\n");
455
456         local hidden_commands = require "util.set".new{ "register", "unregister" };
457         local commands_order = { "adduser", "passwd", "deluser" };
458
459         local done = {};
460
461         for _, command_name in ipairs(commands_order) do
462                 local command = commands[command_name];
463                 if command then
464                         command{ "--help" };
465                         print""
466                         done[command_name] = true;
467                 end
468         end
469
470         for command_name, command in pairs(commands) do
471                 if not done[command_name] and not hidden_commands:contains(command_name) then
472                         command{ "--help" };
473                         print""
474                         done[command_name] = true;
475                 end
476         end
477         
478         
479         os.exit(0);
480 end
481
482 os.exit(commands[command]({ select(2, unpack(arg)) }));