prosodyctl: Remove warnings when using a non-standard auth provider, prosodyctl now...
[prosody.git] / prosodyctl
1 #!/usr/bin/env lua
2 -- Prosody IM
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 -- Global 'prosody' object
33 prosody = {
34         hosts = {},
35         events = require "util.events".new(),
36         platform = "posix"
37 };
38 local prosody = prosody;
39
40 config = require "core.configmanager"
41
42 do
43         -- TODO: Check for other formats when we add support for them
44         -- Use lfs? Make a new conf/ dir?
45         local ok, level, err = config.load((CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
46         if not ok then
47                 print("\n");
48                 print("**************************");
49                 if level == "parser" then
50                         print("A problem occured while reading the config file "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
51                         local err_line, err_message = tostring(err):match("%[string .-%]:(%d*): (.*)");
52                         print("Error"..(err_line and (" on line "..err_line) or "")..": "..(err_message or tostring(err)));
53                         print("");
54                 elseif level == "file" then
55                         print("Prosody was unable to find the configuration file.");
56                         print("We looked for: "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
57                         print("A sample config file is included in the Prosody download called prosody.cfg.lua.dist");
58                         print("Copy or rename it to prosody.cfg.lua and edit as necessary.");
59                 end
60                 print("More help on configuring Prosody can be found at http://prosody.im/doc/configure");
61                 print("Good luck!");
62                 print("**************************");
63                 print("");
64                 os.exit(1);
65         end
66 end
67
68 require "core.loggingmanager"
69
70 if not require "util.dependencies".check_dependencies() then
71         os.exit(1);
72 end
73
74 local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
75 require "util.datamanager".set_data_path(data_path);
76
77 -- Switch away from root and into the prosody user --
78 local switched_user, current_uid;
79
80 local want_pposix_version = "0.3.3";
81 local ok, pposix = pcall(require, "util.pposix");
82
83 if ok and pposix then
84         if pposix._VERSION ~= want_pposix_version then print(string.format("Unknown version (%s) of binary pposix module, expected %s", tostring(pposix._VERSION), want_pposix_version)); return; end
85         current_uid = pposix.getuid();
86         if current_uid == 0 then
87                 -- We haz root!
88                 local desired_user = config.get("*", "core", "prosody_user") or "prosody";
89                 local desired_group = config.get("*", "core", "prosody_group") or desired_user;
90                 local ok, err = pposix.setgid(desired_group);
91                 if ok then
92                         ok, err = pposix.setuid(desired_user);
93                         if ok then
94                                 -- Yay!
95                                 switched_user = true;
96                         end
97                 end
98                 if not switched_user then
99                         -- Boo!
100                         print("Warning: Couldn't switch to Prosody user/group '"..tostring(desired_user).."'/'"..tostring(desired_group).."': "..tostring(err));
101                 end
102         end
103         
104         -- Set our umask to protect data files
105         pposix.umask(config.get("*", "core", "umask") or "027");
106 else
107         print("Error: Unable to load pposix module. Check that Prosody is installed correctly.")
108         print("For more help send the below error to us through http://prosody.im/discuss");
109         print(tostring(pposix))
110 end
111
112 local error_messages = setmetatable({ 
113                 ["invalid-username"] = "The given username is invalid in a Jabber ID";
114                 ["invalid-hostname"] = "The given hostname is invalid";
115                 ["no-password"] = "No password was supplied";
116                 ["no-such-user"] = "The given user does not exist on the server";
117                 ["unable-to-save-data"] = "Unable to store, perhaps you don't have permission?";
118                 ["no-pidfile"] = "There is no 'pidfile' option in the configuration file, see http://prosody.im/doc/prosodyctl#pidfile for help";
119                 ["no-such-method"] = "This module has no commands";
120                 ["not-running"] = "Prosody is not running";
121                 }, { __index = function (t,k) return "Error: "..(tostring(k):gsub("%-", " "):gsub("^.", string.upper)); end });
122
123 hosts = prosody.hosts;
124
125 local function make_host(hostname)
126         return { events = prosody.events, users = require "core.usermanager".new_null_provider(hostname) };
127 end
128
129 for hostname, config in pairs(config.getconfig()) do
130         hosts[hostname] = make_host(hostname);
131 end
132         
133 require "core.modulemanager"
134
135 require "util.prosodyctl"
136 require "socket"
137 -----------------------
138
139 function show_message(msg, ...)
140         print(msg:format(...));
141 end
142
143 function show_warning(msg, ...)
144         print(msg:format(...));
145 end
146
147 function show_usage(usage, desc)
148         print("Usage: "..arg[0].." "..usage);
149         if desc then
150                 print(" "..desc);
151         end
152 end
153
154 local function getchar(n)
155         local stty_ret = os.execute("stty raw -echo 2>/dev/null");
156         local ok, char;
157         if stty_ret == 0 then
158                 ok, char = pcall(io.read, n or 1);
159                 os.execute("stty sane");
160         else
161                 ok, char = pcall(io.read, "*l");
162                 if ok then
163                         char = char:sub(1, n or 1);
164                 end
165         end
166         if ok then
167                 return char;
168         end
169 end
170         
171 local function getpass()
172         local stty_ret = os.execute("stty -echo 2>/dev/null");
173         if stty_ret ~= 0 then
174                 io.write("\027[08m"); -- ANSI 'hidden' text attribute
175         end
176         local ok, pass = pcall(io.read, "*l");
177         if stty_ret == 0 then
178                 os.execute("stty sane");
179         else
180                 io.write("\027[00m");
181         end
182         io.write("\n");
183         if ok then
184                 return pass;
185         end
186 end
187
188 function show_yesno(prompt)
189         io.write(prompt, " ");
190         local choice = getchar():lower();
191         io.write("\n");
192         if not choice:match("%a") then
193                 choice = prompt:match("%[.-(%U).-%]$");
194                 if not choice then return nil; end
195         end
196         return (choice == "y");
197 end
198
199 local function read_password()
200         local password;
201         while true do
202                 io.write("Enter new password: ");
203                 password = getpass();
204                 if not password then
205                         show_message("No password - cancelled");
206                         return;
207                 end
208                 io.write("Retype new password: ");
209                 if getpass() ~= password then
210                         if not show_yesno [=[Passwords did not match, try again? [Y/n]]=] then
211                                 return;
212                         end
213                 else
214                         break;
215                 end
216         end
217         return password;
218 end
219
220 local prosodyctl_timeout = (config.get("*", "core", "prosodyctl_timeout") or 5) * 2;
221 -----------------------
222 local commands = {};
223 local command = arg[1];
224
225 function commands.adduser(arg)
226         if not arg[1] or arg[1] == "--help" then
227                 show_usage([[adduser JID]], [[Create the specified user account in Prosody]]);
228                 return 1;
229         end
230         local user, host = arg[1]:match("([^@]+)@(.+)");
231         if not user and host then
232                 show_message [[Failed to understand JID, please supply the JID you want to create]]
233                 show_usage [[adduser user@host]]
234                 return 1;
235         end
236         
237         if not host then
238                 show_message [[Please specify a JID, including a host. e.g. alice@example.com]];
239                 return 1;
240         end
241         
242         if not hosts[host] then
243                 show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host)
244                 show_warning("The user will not be able to log in until this is changed.");
245                 hosts[host] = make_host(host);
246         end
247         
248         if prosodyctl.user_exists{ user = user, host = host } then
249                 show_message [[That user already exists]];
250                 return 1;
251         end
252         
253         local password = read_password();
254         if not password then return 1; end
255         
256         local ok, msg = prosodyctl.adduser { user = user, host = host, password = password };
257         
258         if ok then return 0; end
259         
260         show_message(error_messages[msg])
261         return 1;
262 end
263
264 function commands.passwd(arg)
265         if not arg[1] or arg[1] == "--help" then
266                 show_usage([[passwd JID]], [[Set the password for the specified user account in Prosody]]);
267                 return 1;
268         end
269         local user, host = arg[1]:match("([^@]+)@(.+)");
270         if not user and host then
271                 show_message [[Failed to understand JID, please supply the JID you want to set the password for]]
272                 show_usage [[passwd user@host]]
273                 return 1;
274         end
275         
276         if not host then
277                 show_message [[Please specify a JID, including a host. e.g. alice@example.com]];
278                 return 1;
279         end
280         
281         if not hosts[host] then
282                 show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host)
283                 show_warning("The user will not be able to log in until this is changed.");
284                 hosts[host] = make_host(host);
285         end
286         
287         if not prosodyctl.user_exists { user = user, host = host } then
288                 show_message [[That user does not exist, use prosodyctl adduser to create a new user]]
289                 return 1;
290         end
291         
292         local password = read_password();
293         if not password then return 1; end
294         
295         local ok, msg = prosodyctl.passwd { user = user, host = host, password = password };
296         
297         if ok then return 0; end
298         
299         show_message(error_messages[msg])
300         return 1;
301 end
302
303 function commands.deluser(arg)
304         if not arg[1] or arg[1] == "--help" then
305                 show_usage([[deluser JID]], [[Permanently remove the specified user account from Prosody]]);
306                 return 1;
307         end
308         local user, host = arg[1]:match("([^@]+)@(.+)");
309         if not user and host then
310                 show_message [[Failed to understand JID, please supply the JID you want to set the password for]]
311                 show_usage [[passwd user@host]]
312                 return 1;
313         end
314         
315         if not host then
316                 show_message [[Please specify a JID, including a host. e.g. alice@example.com]];
317                 return 1;
318         end
319         
320         if not hosts[host] then
321                 show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host)
322                 show_warning("The user will not be able to log in until this is changed.");
323                 hosts[host] = make_host(host);
324         end
325
326         if not prosodyctl.user_exists { user = user, host = host } then
327                 show_message [[That user does not exist on this server]]
328                 return 1;
329         end
330         
331         local ok, msg = prosodyctl.passwd { user = user, host = host };
332         
333         if ok then return 0; end
334         
335         show_message(error_messages[msg])
336         return 1;
337 end
338
339 function commands.start(arg)
340         if arg[1] == "--help" then
341                 show_usage([[start]], [[Start Prosody]]);
342                 return 1;
343         end
344         local ok, ret = prosodyctl.isrunning();
345         if not ok then
346                 show_message(error_messages[ret]);
347                 return 1;
348         end
349         
350         if ret then
351                 local ok, ret = prosodyctl.getpid();
352                 if not ok then
353                         show_message("Couldn't get running Prosody's PID");
354                         show_message(error_messages[ret]);
355                         return 1;
356                 end
357                 show_message("Prosody is already running with PID %s", ret or "(unknown)");
358                 return 1;
359         end
360         
361         local ok, ret = prosodyctl.start();
362         if ok then
363                 if config.get("*", "core", "daemonize") ~= false then
364                         local i=1;
365                         while true do
366                                 local ok, running = prosodyctl.isrunning();
367                                 if ok and running then
368                                         break;
369                                 elseif i == 5 then
370                                         show_message("Still waiting...");
371                                 elseif i >= prosodyctl_timeout then
372                                         show_message("Prosody is still not running. Please give it some time or check your log files for errors.");
373                                         return 2;
374                                 end
375                                 socket.sleep(0.5);
376                                 i = i + 1;
377                         end
378                         show_message("Started");
379                 end
380                 return 0;
381         end
382
383         show_message("Failed to start Prosody");
384         show_message(error_messages[ret])       
385         return 1;       
386 end
387
388 function commands.status(arg)
389         if arg[1] == "--help" then
390                 show_usage([[status]], [[Reports the running status of Prosody]]);
391                 return 1;
392         end
393
394         local ok, ret = prosodyctl.isrunning();
395         if not ok then
396                 show_message(error_messages[ret]);
397                 return 1;
398         end
399         
400         if ret then
401                 local ok, ret = prosodyctl.getpid();
402                 if not ok then
403                         show_message("Couldn't get running Prosody's PID");
404                         show_message(error_messages[ret]);
405                         return 1;
406                 end
407                 show_message("Prosody is running with PID %s", ret or "(unknown)");
408                 return 0;
409         else
410                 show_message("Prosody is not running");
411                 if not switched_user and current_uid ~= 0 then
412                         print("\nNote:")
413                         print(" You will also see this if prosodyctl is not running under");
414                         print(" the same user account as Prosody. Try running as root (e.g. ");
415                         print(" with 'sudo' in front) to gain access to Prosody's real status.");
416                 end
417                 return 2
418         end
419         return 1;
420 end
421
422 function commands.stop(arg)
423         if arg[1] == "--help" then
424                 show_usage([[stop]], [[Stop a running Prosody server]]);
425                 return 1;
426         end
427
428         if not prosodyctl.isrunning() then
429                 show_message("Prosody is not running");
430                 return 1;
431         end
432         
433         local ok, ret = prosodyctl.stop();
434         if ok then
435                 local i=1;
436                 while true do
437                         local ok, running = prosodyctl.isrunning();
438                         if ok and not running then
439                                 break;
440                         elseif i == 5 then
441                                 show_message("Still waiting...");
442                         elseif i >= prosodyctl_timeout then
443                                 show_message("Prosody is still running. Please give it some time or check your log files for errors.");
444                                 return 2;
445                         end
446                         socket.sleep(0.5);
447                         i = i + 1;
448                 end
449                 show_message("Stopped");
450                 return 0;
451         end
452
453         show_message(error_messages[ret]);
454         return 1;
455 end
456
457 function commands.restart(arg)
458         if arg[1] == "--help" then
459                 show_usage([[restart]], [[Restart a running Prosody server]]);
460                 return 1;
461         end
462         
463         local ret = commands.stop(arg);
464         if ret == 0 then
465                 ret = commands.start(arg);
466         end
467         return ret;
468 end
469
470 -- ejabberdctl compatibility
471
472 function commands.register(arg)
473         local user, host, password = unpack(arg);
474         if (not (user and host)) or arg[1] == "--help" then
475                 if user ~= "--help" then
476                         if not user then
477                                 show_message [[No username specified]]
478                         elseif not host then
479                                 show_message [[Please specify which host you want to register the user on]];
480                         end
481                 end
482                 show_usage("register USER HOST [PASSWORD]", "Register a user on the server, with the given password");
483                 return 1;
484         end
485         if not password then
486                 password = read_password();
487                 if not password then
488                         show_message [[Unable to register user with no password]];
489                         return 1;
490                 end
491         end
492         
493         local ok, msg = prosodyctl.adduser { user = user, host = host, password = password };
494         
495         if ok then return 0; end
496         
497         show_message(error_messages[msg])
498         return 1;
499 end
500
501 function commands.unregister(arg)
502         local user, host = unpack(arg);
503         if (not (user and host)) or arg[1] == "--help" then
504                 if user ~= "--help" then
505                         if not user then
506                                 show_message [[No username specified]]
507                         elseif not host then
508                                 show_message [[Please specify which host you want to unregister the user from]];
509                         end
510                 end
511                 show_usage("unregister USER HOST [PASSWORD]", "Permanently remove a user account from the server");
512                 return 1;
513         end
514
515         local ok, msg = prosodyctl.deluser { user = user, host = host };
516         
517         if ok then return 0; end
518         
519         show_message(error_messages[msg])
520         return 1;
521 end
522
523 ---------------------
524
525 if command and command:match("^mod_") then -- Is a command in a module
526         local module_name = command:match("^mod_(.+)");
527         local ret, err = modulemanager.load("*", module_name);
528         if not ret then
529                 show_message("Failed to load module '"..module_name.."': "..err);
530                 os.exit(1);
531         end
532         
533         table.remove(arg, 1);
534         
535         local module = modulemanager.get_module("*", module_name);
536         if not module then
537                 show_message("Failed to load module '"..module_name.."': Unknown error");
538                 os.exit(1);
539         end
540         
541         if not modulemanager.module_has_method(module, "command") then
542                 show_message("Fail: mod_"..module_name.." does not support any commands");
543                 os.exit(1);
544         end
545         
546         local ok, ret = modulemanager.call_module_method(module, "command", arg);
547         if ok then
548                 if type(ret) == "number" then
549                         os.exit(ret);
550                 elseif type(ret) == "string" then
551                         show_message(ret);
552                 end
553                 os.exit(0); -- :)
554         else
555                 show_message("Failed to execute command: "..error_messages[ret]);
556                 os.exit(1); -- :(
557         end
558 end
559
560 if not commands[command] then -- Show help for all commands
561         function show_usage(usage, desc)
562                 print(" "..usage);
563                 print("    "..desc);
564         end
565
566         print("prosodyctl - Manage a Prosody server");
567         print("");
568         print("Usage: "..arg[0].." COMMAND [OPTIONS]");
569         print("");
570         print("Where COMMAND may be one of:\n");
571
572         local hidden_commands = require "util.set".new{ "register", "unregister", "addplugin" };
573         local commands_order = { "adduser", "passwd", "deluser", "start", "stop", "restart" };
574
575         local done = {};
576
577         for _, command_name in ipairs(commands_order) do
578                 local command = commands[command_name];
579                 if command then
580                         command{ "--help" };
581                         print""
582                         done[command_name] = true;
583                 end
584         end
585
586         for command_name, command in pairs(commands) do
587                 if not done[command_name] and not hidden_commands:contains(command_name) then
588                         command{ "--help" };
589                         print""
590                         done[command_name] = true;
591                 end
592         end
593         
594         
595         os.exit(0);
596 end
597
598 os.exit(commands[command]({ select(2, unpack(arg)) }));