prosodyctl, prosody: Pass the selected config file from prosodyctl to prosody
[prosody.git] / prosodyctl
1 #!/usr/bin/env lua
2 -- Prosody IM
3 -- Copyright (C) 2008-2010 Matthew Wild
4 -- Copyright (C) 2008-2010 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=os.getenv("PROSODY_SRCDIR");
15 CFG_CONFIGDIR=os.getenv("PROSODY_CFGDIR");
16 CFG_PLUGINDIR=os.getenv("PROSODY_PLUGINDIR");
17 CFG_DATADIR=os.getenv("PROSODY_DATADIR");
18
19 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20
21 local function is_relative(path)
22         local path_sep = package.config:sub(1,1);
23         return ((path_sep == "/" and path:sub(1,1) ~= "/")
24         or (path_sep == "\\" and (path:sub(1,1) ~= "/" and path:sub(2,3) ~= ":\\")))
25 end
26
27 -- Tell Lua where to find our libraries
28 if CFG_SOURCEDIR then
29         local function filter_relative_paths(path)
30                 if is_relative(path) then return ""; end
31         end
32         local function sanitise_paths(paths)
33                 return (paths:gsub("[^;]+;?", filter_relative_paths):gsub(";;+", ";"));
34         end
35         package.path = sanitise_paths(CFG_SOURCEDIR.."/?.lua;"..package.path);
36         package.cpath = sanitise_paths(CFG_SOURCEDIR.."/?.so;"..package.cpath);
37 end
38
39 -- Substitute ~ with path to home directory in data path
40 if CFG_DATADIR then
41         if os.getenv("HOME") then
42                 CFG_DATADIR = CFG_DATADIR:gsub("^~", os.getenv("HOME"));
43         end
44 end
45
46 -- Global 'prosody' object
47 local prosody = {
48         hosts = {};
49         events = require "util.events".new();
50         platform = "posix";
51         lock_globals = function () end;
52         unlock_globals = function () end;
53         installed = CFG_SOURCEDIR ~= nil;
54 };
55 _G.prosody = prosody;
56
57 local dependencies = require "util.dependencies";
58 if not dependencies.check_dependencies() then
59         os.exit(1);
60 end
61
62 config = require "core.configmanager"
63
64 local ENV_CONFIG;
65 do
66         local filenames = {};
67         
68         local filename;
69         if arg[1] == "--config" and arg[2] then
70                 table.insert(filenames, arg[2]);
71                 if CFG_CONFIGDIR then
72                         table.insert(filenames, CFG_CONFIGDIR.."/"..arg[2]);
73                 end
74                 table.remove(arg, 1); table.remove(arg, 1);
75         else
76                 for _, format in ipairs(config.parsers()) do
77                         table.insert(filenames, (CFG_CONFIGDIR or ".").."/prosody.cfg."..format);
78                 end
79         end
80         for _,_filename in ipairs(filenames) do
81                 filename = _filename;
82                 local file = io.open(filename);
83                 if file then
84                         file:close();
85                         ENV_CONFIG = filename;
86                         CFG_CONFIGDIR = filename:match("^(.*)[\\/][^\\/]*$");
87                         break;
88                 end
89         end
90         local ok, level, err = config.load(filename);
91         if not ok then
92                 print("\n");
93                 print("**************************");
94                 if level == "parser" then
95                         print("A problem occured while reading the config file "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
96                         local err_line, err_message = tostring(err):match("%[string .-%]:(%d*): (.*)");
97                         print("Error"..(err_line and (" on line "..err_line) or "")..": "..(err_message or tostring(err)));
98                         print("");
99                 elseif level == "file" then
100                         print("Prosody was unable to find the configuration file.");
101                         print("We looked for: "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
102                         print("A sample config file is included in the Prosody download called prosody.cfg.lua.dist");
103                         print("Copy or rename it to prosody.cfg.lua and edit as necessary.");
104                 end
105                 print("More help on configuring Prosody can be found at http://prosody.im/doc/configure");
106                 print("Good luck!");
107                 print("**************************");
108                 print("");
109                 os.exit(1);
110         end
111 end
112 local original_logging_config = config.get("*", "core", "log");
113 config.set("*", "core", "log", { { levels = { min="info" }, to = "console" } });
114
115 local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
116 local custom_plugin_paths = config.get("*", "core", "plugin_paths");
117 if custom_plugin_paths then
118         local path_sep = package.config:sub(3,3);
119         -- path1;path2;path3;defaultpath...
120         CFG_PLUGINDIR = table.concat(custom_plugin_paths, path_sep)..path_sep..(CFG_PLUGINDIR or "plugins");
121 end
122 prosody.paths = { source = CFG_SOURCEDIR, config = CFG_CONFIGDIR, 
123                   plugins = CFG_PLUGINDIR or "plugins", data = data_path };
124
125 if prosody.installed then
126         -- Change working directory to data path.
127         require "lfs".chdir(data_path);
128 end
129
130 require "core.loggingmanager"
131
132 dependencies.log_warnings();
133
134 -- Switch away from root and into the prosody user --
135 local switched_user, current_uid;
136
137 local want_pposix_version = "0.3.5";
138 local ok, pposix = pcall(require, "util.pposix");
139
140 if ok and pposix then
141         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
142         current_uid = pposix.getuid();
143         if current_uid == 0 then
144                 -- We haz root!
145                 local desired_user = config.get("*", "core", "prosody_user") or "prosody";
146                 local desired_group = config.get("*", "core", "prosody_group") or desired_user;
147                 local ok, err = pposix.setgid(desired_group);
148                 if ok then
149                         ok, err = pposix.initgroups(desired_user);
150                 end
151                 if ok then
152                         ok, err = pposix.setuid(desired_user);
153                         if ok then
154                                 -- Yay!
155                                 switched_user = true;
156                         end
157                 end
158                 if not switched_user then
159                         -- Boo!
160                         print("Warning: Couldn't switch to Prosody user/group '"..tostring(desired_user).."'/'"..tostring(desired_group).."': "..tostring(err));
161                 end
162         end
163         
164         -- Set our umask to protect data files
165         pposix.umask(config.get("*", "core", "umask") or "027");
166         pposix.setenv("HOME", data_path);
167         pposix.setenv("PROSODY_CONFIG", ENV_CONFIG);
168 else
169         print("Error: Unable to load pposix module. Check that Prosody is installed correctly.")
170         print("For more help send the below error to us through http://prosody.im/discuss");
171         print(tostring(pposix))
172         os.exit(1);
173 end
174
175 local function test_writeable(filename)
176         local f, err = io.open(filename, "a");
177         if not f then
178                 return false, err;
179         end
180         f:close();
181         return true;
182 end
183
184 local unwriteable_files = {};
185 if type(original_logging_config) == "string" and original_logging_config:sub(1,1) ~= "*" then
186         local ok, err = test_writeable(original_logging_config);
187         if not ok then
188                 table.insert(unwriteable_files, err);
189         end
190 elseif type(original_logging_config) == "table" then
191         for _, rule in ipairs(original_logging_config) do
192                 if rule.filename then
193                         local ok, err = test_writeable(rule.filename);
194                         if not ok then
195                                 table.insert(unwriteable_files, err);
196                         end
197                 end
198         end
199 end
200
201 if #unwriteable_files > 0 then
202         print("One of more of the Prosody log files are not");
203         print("writeable, please correct the errors and try");
204         print("starting prosodyctl again.");
205         print("");
206         for _, err in ipairs(unwriteable_files) do
207                 print(err);
208         end
209         print("");
210         os.exit(1);
211 end
212
213
214 local error_messages = setmetatable({ 
215                 ["invalid-username"] = "The given username is invalid in a Jabber ID";
216                 ["invalid-hostname"] = "The given hostname is invalid";
217                 ["no-password"] = "No password was supplied";
218                 ["no-such-user"] = "The given user does not exist on the server";
219                 ["no-such-host"] = "The given hostname does not exist in the config";
220                 ["unable-to-save-data"] = "Unable to store, perhaps you don't have permission?";
221                 ["no-pidfile"] = "There is no 'pidfile' option in the configuration file, see http://prosody.im/doc/prosodyctl#pidfile for help";
222                 ["no-posix"] = "The mod_posix module is not enabled in the Prosody config file, see http://prosody.im/doc/prosodyctl for more info";
223                 ["no-such-method"] = "This module has no commands";
224                 ["not-running"] = "Prosody is not running";
225                 }, { __index = function (t,k) return "Error: "..(tostring(k):gsub("%-", " "):gsub("^.", string.upper)); end });
226
227 hosts = prosody.hosts;
228
229 local function make_host(hostname)
230         return {
231                 type = "local",
232                 events = prosody.events,
233                 modules = {},
234                 users = require "core.usermanager".new_null_provider(hostname)
235         };
236 end
237
238 for hostname, config in pairs(config.getconfig()) do
239         hosts[hostname] = make_host(hostname);
240 end
241         
242 local modulemanager = require "core.modulemanager"
243
244 local prosodyctl = require "util.prosodyctl"
245 require "socket"
246 -----------------------
247
248  -- FIXME: Duplicate code waiting for util.startup
249 function read_version()
250         -- Try to determine version
251         local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version");
252         if version_file then
253                 prosody.version = version_file:read("*a"):gsub("%s*$", "");
254                 version_file:close();
255                 if #prosody.version == 12 and prosody.version:match("^[a-f0-9]+$") then
256                         prosody.version = "hg:"..prosody.version;
257                 end
258         else
259                 prosody.version = "unknown";
260         end
261 end
262
263 local show_message, show_warning = prosodyctl.show_message, prosodyctl.show_warning;
264 local show_usage = prosodyctl.show_usage;
265 local getchar, getpass = prosodyctl.getchar, prosodyctl.getpass;
266 local show_yesno = prosodyctl.show_yesno;
267 local show_prompt = prosodyctl.show_prompt;
268 local read_password = prosodyctl.read_password;
269
270 local prosodyctl_timeout = (config.get("*", "core", "prosodyctl_timeout") or 5) * 2;
271 -----------------------
272 local commands = {};
273 local command = arg[1];
274
275 function commands.adduser(arg)
276         if not arg[1] or arg[1] == "--help" then
277                 show_usage([[adduser JID]], [[Create the specified user account in Prosody]]);
278                 return 1;
279         end
280         local user, host = arg[1]:match("([^@]+)@(.+)");
281         if not user and host then
282                 show_message [[Failed to understand JID, please supply the JID you want to create]]
283                 show_usage [[adduser user@host]]
284                 return 1;
285         end
286         
287         if not host then
288                 show_message [[Please specify a JID, including a host. e.g. alice@example.com]];
289                 return 1;
290         end
291         
292         if not hosts[host] then
293                 show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host)
294                 show_warning("The user will not be able to log in until this is changed.");
295                 hosts[host] = make_host(host);
296         end
297         
298         if prosodyctl.user_exists{ user = user, host = host } then
299                 show_message [[That user already exists]];
300                 return 1;
301         end
302         
303         local password = read_password();
304         if not password then return 1; end
305         
306         local ok, msg = prosodyctl.adduser { user = user, host = host, password = password };
307         
308         if ok then return 0; end
309         
310         show_message(msg)
311         return 1;
312 end
313
314 function commands.passwd(arg)
315         if not arg[1] or arg[1] == "--help" then
316                 show_usage([[passwd JID]], [[Set the password for the specified user account in Prosody]]);
317                 return 1;
318         end
319         local user, host = arg[1]:match("([^@]+)@(.+)");
320         if not user and host then
321                 show_message [[Failed to understand JID, please supply the JID you want to set the password for]]
322                 show_usage [[passwd user@host]]
323                 return 1;
324         end
325         
326         if not host then
327                 show_message [[Please specify a JID, including a host. e.g. alice@example.com]];
328                 return 1;
329         end
330         
331         if not hosts[host] then
332                 show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host)
333                 show_warning("The user will not be able to log in until this is changed.");
334                 hosts[host] = make_host(host);
335         end
336         
337         if not prosodyctl.user_exists { user = user, host = host } then
338                 show_message [[That user does not exist, use prosodyctl adduser to create a new user]]
339                 return 1;
340         end
341         
342         local password = read_password();
343         if not password then return 1; end
344         
345         local ok, msg = prosodyctl.passwd { user = user, host = host, password = password };
346         
347         if ok then return 0; end
348         
349         show_message(error_messages[msg])
350         return 1;
351 end
352
353 function commands.deluser(arg)
354         if not arg[1] or arg[1] == "--help" then
355                 show_usage([[deluser JID]], [[Permanently remove the specified user account from Prosody]]);
356                 return 1;
357         end
358         local user, host = arg[1]:match("([^@]+)@(.+)");
359         if not user and host then
360                 show_message [[Failed to understand JID, please supply the JID you want to set the password for]]
361                 show_usage [[passwd user@host]]
362                 return 1;
363         end
364         
365         if not host then
366                 show_message [[Please specify a JID, including a host. e.g. alice@example.com]];
367                 return 1;
368         end
369         
370         if not hosts[host] then
371                 show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host)
372                 show_warning("The user will not be able to log in until this is changed.");
373                 hosts[host] = make_host(host);
374         end
375
376         if not prosodyctl.user_exists { user = user, host = host } then
377                 show_message [[That user does not exist on this server]]
378                 return 1;
379         end
380         
381         local ok, msg = prosodyctl.deluser { user = user, host = host };
382         
383         if ok then return 0; end
384         
385         show_message(error_messages[msg])
386         return 1;
387 end
388
389 function commands.start(arg)
390         if arg[1] == "--help" then
391                 show_usage([[start]], [[Start Prosody]]);
392                 return 1;
393         end
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 already running with PID %s", ret or "(unknown)");
408                 return 1;
409         end
410         
411         local ok, ret = prosodyctl.start();
412         if ok then
413                 if config.get("*", "core", "daemonize") ~= false then
414                         local i=1;
415                         while true do
416                                 local ok, running = prosodyctl.isrunning();
417                                 if ok and running then
418                                         break;
419                                 elseif i == 5 then
420                                         show_message("Still waiting...");
421                                 elseif i >= prosodyctl_timeout then
422                                         show_message("Prosody is still not running. Please give it some time or check your log files for errors.");
423                                         return 2;
424                                 end
425                                 socket.sleep(0.5);
426                                 i = i + 1;
427                         end
428                         show_message("Started");
429                 end
430                 return 0;
431         end
432
433         show_message("Failed to start Prosody");
434         show_message(error_messages[ret])       
435         return 1;       
436 end
437
438 function commands.status(arg)
439         if arg[1] == "--help" then
440                 show_usage([[status]], [[Reports the running status of Prosody]]);
441                 return 1;
442         end
443
444         local ok, ret = prosodyctl.isrunning();
445         if not ok then
446                 show_message(error_messages[ret]);
447                 return 1;
448         end
449         
450         if ret then
451                 local ok, ret = prosodyctl.getpid();
452                 if not ok then
453                         show_message("Couldn't get running Prosody's PID");
454                         show_message(error_messages[ret]);
455                         return 1;
456                 end
457                 show_message("Prosody is running with PID %s", ret or "(unknown)");
458                 return 0;
459         else
460                 show_message("Prosody is not running");
461                 if not switched_user and current_uid ~= 0 then
462                         print("\nNote:")
463                         print(" You will also see this if prosodyctl is not running under");
464                         print(" the same user account as Prosody. Try running as root (e.g. ");
465                         print(" with 'sudo' in front) to gain access to Prosody's real status.");
466                 end
467                 return 2
468         end
469         return 1;
470 end
471
472 function commands.stop(arg)
473         if arg[1] == "--help" then
474                 show_usage([[stop]], [[Stop a running Prosody server]]);
475                 return 1;
476         end
477
478         if not prosodyctl.isrunning() then
479                 show_message("Prosody is not running");
480                 return 1;
481         end
482         
483         local ok, ret = prosodyctl.stop();
484         if ok then
485                 local i=1;
486                 while true do
487                         local ok, running = prosodyctl.isrunning();
488                         if ok and not running then
489                                 break;
490                         elseif i == 5 then
491                                 show_message("Still waiting...");
492                         elseif i >= prosodyctl_timeout then
493                                 show_message("Prosody is still running. Please give it some time or check your log files for errors.");
494                                 return 2;
495                         end
496                         socket.sleep(0.5);
497                         i = i + 1;
498                 end
499                 show_message("Stopped");
500                 return 0;
501         end
502
503         show_message(error_messages[ret]);
504         return 1;
505 end
506
507 function commands.restart(arg)
508         if arg[1] == "--help" then
509                 show_usage([[restart]], [[Restart a running Prosody server]]);
510                 return 1;
511         end
512         
513         commands.stop(arg);
514         return commands.start(arg);
515 end
516
517 function commands.about(arg)
518         read_version();
519         if arg[1] == "--help" then
520                 show_usage([[about]], [[Show information about this Prosody installation]]);
521                 return 1;
522         end
523         
524         local array = require "util.array";
525         local keys = require "util.iterators".keys;
526         
527         print("Prosody "..(prosody.version or "(unknown version)"));
528         print("");
529         print("# Prosody directories");
530         print("Data directory:  ", CFG_DATADIR or "./");
531         print("Plugin directory:", CFG_PLUGINDIR or "./");
532         print("Config directory:", CFG_CONFIGDIR or "./");
533         print("Source directory:", CFG_SOURCEDIR or "./");
534         print("");
535         print("# Lua environment");
536         print("Lua version:             ", _G._VERSION);
537         print("");
538         print("Lua module search paths:");
539         for path in package.path:gmatch("[^;]+") do
540                 print("  "..path);
541         end
542         print("");
543         print("Lua C module search paths:");
544         for path in package.cpath:gmatch("[^;]+") do
545                 print("  "..path);
546         end
547         print("");
548         local luarocks_status = (pcall(require, "luarocks.loader") and "Installed ("..(luarocks.cfg.program_version or "2.x+")..")")
549                 or (pcall(require, "luarocks.require") and "Installed (1.x)")
550                 or "Not installed";
551         print("LuaRocks:        ", luarocks_status);
552         print("");
553         print("# Lua module versions");
554         local module_versions, longest_name = {}, 8;
555         for name, module in pairs(package.loaded) do
556                 if type(module) == "table" and rawget(module, "_VERSION")
557                 and name ~= "_G" and not name:match("%.") then
558                         if #name > longest_name then
559                                 longest_name = #name;
560                         end
561                         module_versions[name] = module._VERSION;
562                 end
563         end
564         local sorted_keys = array.collect(keys(module_versions)):sort();
565         for _, name in ipairs(array.collect(keys(module_versions)):sort()) do
566                 print(name..":"..string.rep(" ", longest_name-#name), module_versions[name]);
567         end
568         print("");
569 end
570
571 function commands.reload(arg)
572         if arg[1] == "--help" then
573                 show_usage([[reload]], [[Reload Prosody's configuration and re-open log files]]);
574                 return 1;
575         end
576
577         if not prosodyctl.isrunning() then
578                 show_message("Prosody is not running");
579                 return 1;
580         end
581         
582         local ok, ret = prosodyctl.reload();
583         if ok then
584                 
585                 show_message("Prosody log files re-opened and config file reloaded. You may need to reload modules for some changes to take effect.");
586                 return 0;
587         end
588
589         show_message(error_messages[ret]);
590         return 1;
591 end
592 -- ejabberdctl compatibility
593
594 function commands.register(arg)
595         local user, host, password = unpack(arg);
596         if (not (user and host)) or arg[1] == "--help" then
597                 if user ~= "--help" then
598                         if not user then
599                                 show_message [[No username specified]]
600                         elseif not host then
601                                 show_message [[Please specify which host you want to register the user on]];
602                         end
603                 end
604                 show_usage("register USER HOST [PASSWORD]", "Register a user on the server, with the given password");
605                 return 1;
606         end
607         if not password then
608                 password = read_password();
609                 if not password then
610                         show_message [[Unable to register user with no password]];
611                         return 1;
612                 end
613         end
614         
615         local ok, msg = prosodyctl.adduser { user = user, host = host, password = password };
616         
617         if ok then return 0; end
618         
619         show_message(error_messages[msg])
620         return 1;
621 end
622
623 function commands.unregister(arg)
624         local user, host = unpack(arg);
625         if (not (user and host)) or arg[1] == "--help" then
626                 if user ~= "--help" then
627                         if not user then
628                                 show_message [[No username specified]]
629                         elseif not host then
630                                 show_message [[Please specify which host you want to unregister the user from]];
631                         end
632                 end
633                 show_usage("unregister USER HOST [PASSWORD]", "Permanently remove a user account from the server");
634                 return 1;
635         end
636
637         local ok, msg = prosodyctl.deluser { user = user, host = host };
638         
639         if ok then return 0; end
640         
641         show_message(error_messages[msg])
642         return 1;
643 end
644
645 local openssl;
646 local lfs;
647
648 local cert_commands = {};
649
650 local function ask_overwrite(filename)
651         return lfs.attributes(filename) and not show_yesno("Overwrite "..filename .. "?");
652 end
653
654 function cert_commands.config(arg)
655         if #arg >= 1 and arg[1] ~= "--help" then
656                 local conf_filename = (CFG_DATADIR or ".") .. "/" .. arg[1] .. ".cnf";
657                 if ask_overwrite(conf_filename) then
658                         return nil, conf_filename;
659                 end
660                 local conf = openssl.config.new();
661                 conf:from_prosody(hosts, config, arg);
662                 for k, v in pairs(conf.distinguished_name) do
663                         local nv;
664                         if k == "commonName" then 
665                                 v = arg[1]
666                         elseif k == "emailAddress" then
667                                 v = "xmpp@" .. arg[1];
668                         end
669                         nv = show_prompt(("%s (%s):"):format(k, nv or v));
670                         nv = (not nv or nv == "") and v or nv;
671                         if nv:find"[\192-\252][\128-\191]+" then
672                                 conf.req.string_mask = "utf8only"
673                         end
674                         conf.distinguished_name[k] = nv ~= "." and nv or nil;
675                 end
676                 local conf_file = io.open(conf_filename, "w");
677                 conf_file:write(conf:serialize());
678                 conf_file:close();
679                 print("");
680                 show_message("Config written to " .. conf_filename);
681                 return nil, conf_filename;
682         else
683                 show_usage("cert config HOSTNAME [HOSTNAME+]", "Builds a certificate config file covering the supplied hostname(s)")
684         end
685 end
686
687 function cert_commands.key(arg)
688         if #arg >= 1 and arg[1] ~= "--help" then
689                 local key_filename = (CFG_DATADIR or ".") .. "/" .. arg[1] .. ".key";
690                 if ask_overwrite(key_filename) then
691                         return nil, key_filename;
692                 end
693                 os.remove(key_filename); -- This file, if it exists is unlikely to have write permissions
694                 local key_size = tonumber(arg[2] or show_prompt("Choose key size (2048):") or 2048);
695                 local old_umask = pposix.umask("0377");
696                 if openssl.genrsa{out=key_filename, key_size} then
697                         os.execute(("chmod 400 '%s'"):format(key_filename));
698                         show_message("Key written to ".. key_filename);
699                         pposix.umask(old_umask);
700                         return nil, key_filename;
701                 end
702                 show_message("There was a problem, see OpenSSL output");
703         else
704                 show_usage("cert key HOSTNAME <bits>", "Generates a RSA key named HOSTNAME.key\n "
705                 .."Prompts for a key size if none given")
706         end
707 end
708
709 function cert_commands.request(arg)
710         if #arg >= 1 and arg[1] ~= "--help" then
711                 local req_filename = (CFG_DATADIR or ".") .. "/" .. arg[1] .. ".req";
712                 if ask_overwrite(req_filename) then
713                         return nil, req_filename;
714                 end
715                 local _, key_filename = cert_commands.key({arg[1]});
716                 local _, conf_filename = cert_commands.config(arg);
717                 if openssl.req{new=true, key=key_filename, utf8=true, config=conf_filename, out=req_filename} then
718                         show_message("Certificate request written to ".. req_filename);
719                 else
720                         show_message("There was a problem, see OpenSSL output");
721                 end
722         else
723                 show_usage("cert request HOSTNAME [HOSTNAME+]", "Generates a certificate request for the supplied hostname(s)")
724         end
725 end
726
727 function cert_commands.generate(arg)
728         if #arg >= 1 and arg[1] ~= "--help" then
729                 local cert_filename = (CFG_DATADIR or ".") .. "/" .. arg[1] .. ".crt";
730                 if ask_overwrite(cert_filename) then
731                         return nil, cert_filename;
732                 end
733                 local _, key_filename = cert_commands.key({arg[1]});
734                 local _, conf_filename = cert_commands.config(arg);
735                 local ret;
736                 if key_filename and conf_filename and cert_filename
737                         and openssl.req{new=true, x509=true, nodes=true, key=key_filename,
738                                 days=365, sha1=true, utf8=true, config=conf_filename, out=cert_filename} then
739                         show_message("Certificate written to ".. cert_filename);
740                 else
741                         show_message("There was a problem, see OpenSSL output");
742                 end
743         else
744                 show_usage("cert generate HOSTNAME [HOSTNAME+]", "Generates a self-signed certificate for the current hostname(s)")
745         end
746 end
747
748 function commands.cert(arg)
749         if #arg >= 1 and arg[1] ~= "--help" then
750                 openssl = require "util.openssl";
751                 lfs = require "lfs";
752                 local subcmd = table.remove(arg, 1);
753                 if type(cert_commands[subcmd]) == "function" then
754                         if not arg[1] then
755                                 show_message"You need to supply at least one hostname"
756                                 arg = { "--help" };
757                         end
758                         if arg[1] ~= "--help" and not hosts[arg[1]] then
759                                 show_message(error_messages["no-such-host"]);
760                                 return
761                         end
762                         return cert_commands[subcmd](arg);
763                 end
764         end
765         show_usage("cert config|request|generate|key", "Helpers for generating X.509 certificates and keys.")
766 end
767
768 ---------------------
769
770 if command and command:match("^mod_") then -- Is a command in a module
771         local module_name = command:match("^mod_(.+)");
772         local ret, err = modulemanager.load("*", module_name);
773         if not ret then
774                 show_message("Failed to load module '"..module_name.."': "..err);
775                 os.exit(1);
776         end
777         
778         table.remove(arg, 1);
779         
780         local module = modulemanager.get_module("*", module_name);
781         if not module then
782                 show_message("Failed to load module '"..module_name.."': Unknown error");
783                 os.exit(1);
784         end
785         
786         if not modulemanager.module_has_method(module, "command") then
787                 show_message("Fail: mod_"..module_name.." does not support any commands");
788                 os.exit(1);
789         end
790         
791         local ok, ret = modulemanager.call_module_method(module, "command", arg);
792         if ok then
793                 if type(ret) == "number" then
794                         os.exit(ret);
795                 elseif type(ret) == "string" then
796                         show_message(ret);
797                 end
798                 os.exit(0); -- :)
799         else
800                 show_message("Failed to execute command: "..error_messages[ret]);
801                 os.exit(1); -- :(
802         end
803 end
804
805 if not commands[command] then -- Show help for all commands
806         function show_usage(usage, desc)
807                 print(" "..usage);
808                 print("    "..desc);
809         end
810
811         print("prosodyctl - Manage a Prosody server");
812         print("");
813         print("Usage: "..arg[0].." COMMAND [OPTIONS]");
814         print("");
815         print("Where COMMAND may be one of:\n");
816
817         local hidden_commands = require "util.set".new{ "register", "unregister", "addplugin" };
818         local commands_order = { "adduser", "passwd", "deluser", "start", "stop", "restart", "reload", "about" };
819
820         local done = {};
821
822         for _, command_name in ipairs(commands_order) do
823                 local command = commands[command_name];
824                 if command then
825                         command{ "--help" };
826                         print""
827                         done[command_name] = true;
828                 end
829         end
830
831         for command_name, command in pairs(commands) do
832                 if not done[command_name] and not hidden_commands:contains(command_name) then
833                         command{ "--help" };
834                         print""
835                         done[command_name] = true;
836                 end
837         end
838         
839         
840         os.exit(0);
841 end
842
843 os.exit(commands[command]({ select(2, unpack(arg)) }));