f451b7ee6892863634ed4eb8f618d1b9bb341fbc
[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 -- Switch away from root and into the prosody user --
65 local switched_user, current_uid;
66 local ok, pposix = pcall(require, "util.pposix");
67 if ok and pposix then
68         current_uid = pposix.getuid();
69         if current_uid == 0 then
70                 -- We haz root!
71                 local desired_user = config.get("*", "core", "prosody_user") or "prosody";
72                 local ok, err = pposix.setuid(desired_user);
73                 if ok then
74                         -- Yay!
75                         switched_user = true;
76                 else
77                         -- Boo!
78                         print("Warning: Couldn't switch to Prosody user '"..tostring(desired_user).."': "..tostring(err));
79                 end
80         end
81 else
82         print("Error: Unable to load pposix module. Check that Prosody is installed correctly.")
83         print("For more help send the below error to us through http://prosody.im/discuss");
84         print(tostring(pposix))
85 end
86
87 local error_messages = setmetatable({ 
88                 ["invalid-username"] = "The given username is invalid in a Jabber ID";
89                 ["invalid-hostname"] = "The given hostname is invalid";
90                 ["no-password"] = "No password was supplied";
91                 ["no-such-user"] = "The given user does not exist on the server";
92                 }, { __index = function (t,k) return "Error: "..(tostring(k):gsub("%-", " "):gsub("^.", string.upper)); end });
93
94 hosts = {};
95
96 require "core.hostmanager"
97 require "core.eventmanager".fire_event("server-starting");
98
99 require "util.prosodyctl"
100 -----------------------
101
102 function show_message(msg, ...)
103         print(msg:format(...));
104 end
105
106 function show_warning(msg, ...)
107         print(msg:format(...));
108 end
109
110 function show_usage(usage, desc)
111         print("Usage: "..arg[0].." "..usage);
112         if desc then
113                 print(" "..desc);
114         end
115 end
116
117 local function getchar(n)
118         os.execute("stty raw -echo");
119         local char = io.read(n or 1);
120         os.execute("stty sane");
121         return char;
122 end
123         
124 local function getpass()
125         os.execute("stty -echo");
126         local pass = io.read("*l");
127         os.execute("stty sane");
128         io.write("\n");
129         return pass;
130 end
131
132 function show_yesno(prompt)
133         io.write(prompt, " ");
134         local choice = getchar():lower();
135         io.write("\n");
136         if not choice:match("%a") then
137                 choice = prompt:match("%[.-(%U).-%]$");
138                 if not choice then return nil; end
139         end
140         return (choice == "y");
141 end
142
143 local function read_password()
144         local password;
145         while true do
146                 io.write("Enter new password: ");
147                 password = getpass();
148                 io.write("Retype new password: ");
149                 if getpass() ~= password then
150                         if not show_yesno [=[Passwords did not match, try again? [Y/n]]=] then
151                                 return;
152                         end
153                 else
154                         break;
155                 end
156         end
157         return password;
158 end
159 -----------------------
160 local commands = {};
161 local command = arg[1];
162
163 function commands.adduser(arg)
164         if not arg[1] or arg[1] == "--help" then
165                 show_usage([[adduser JID]], [[Create the specified user account in Prosody]]);
166                 return 1;
167         end
168         local user, host = arg[1]:match("([^@]+)@(.+)");
169         if not user and host then
170                 show_message [[Failed to understand JID, please supply the JID you want to create]]
171                 show_usage [[adduser user@host]]
172                 return 1;
173         end
174         
175         if not host then
176                 show_message [[Please specify a JID, including a host. e.g. alice@example.com]];
177                 return 1;
178         end
179         
180         if prosodyctl.user_exists{ user = user, host = host } then
181                 show_message [[That user already exists]];
182                 return 1;
183         end
184         
185         if not hosts[host] then
186                 show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host)
187                 show_warning("The user will not be able to log in until this is changed.");
188         end
189         
190         local password = read_password();
191         if not password then return 1; end
192         
193         local ok, msg = prosodyctl.adduser { user = user, host = host, password = password };
194         
195         if ok then return 0; end
196         
197         show_message(error_messages[msg])
198         return 1;
199 end
200
201 function commands.passwd(arg)
202         if not arg[1] or arg[1] == "--help" then
203                 show_usage([[passwd JID]], [[Set the password for the specified user account in Prosody]]);
204                 return 1;
205         end
206         local user, host = arg[1]:match("([^@]+)@(.+)");
207         if not user and host then
208                 show_message [[Failed to understand JID, please supply the JID you want to set the password for]]
209                 show_usage [[passwd user@host]]
210                 return 1;
211         end
212         
213         if not host then
214                 show_message [[Please specify a JID, including a host. e.g. alice@example.com]];
215                 return 1;
216         end
217         
218         if not prosodyctl.user_exists { user = user, host = host } then
219                 show_message [[That user does not exist, use prosodyctl adduser to create a new user]]
220                 return 1;
221         end
222         
223         local password = read_password();
224         if not password then return 1; end
225         
226         local ok, msg = prosodyctl.passwd { user = user, host = host, password = password };
227         
228         if ok then return 0; end
229         
230         show_message(error_messages[msg])
231         return 1;
232 end
233
234 function commands.deluser(arg)
235         if not arg[1] or arg[1] == "--help" then
236                 show_usage([[deluser JID]], [[Permanently remove the specified user account from Prosody]]);
237                 return 1;
238         end
239         local user, host = arg[1]:match("([^@]+)@(.+)");
240         if not user and host then
241                 show_message [[Failed to understand JID, please supply the JID you want to set the password for]]
242                 show_usage [[passwd user@host]]
243                 return 1;
244         end
245         
246         if not host then
247                 show_message [[Please specify a JID, including a host. e.g. alice@example.com]];
248                 return 1;
249         end
250         
251         if not prosodyctl.user_exists { user = user, host = host } then
252                 show_message [[That user does not exist on this server]]
253                 return 1;
254         end
255         
256         local ok, msg = prosodyctl.passwd { user = user, host = host };
257         
258         if ok then return 0; end
259         
260         show_message(error_messages[msg])
261         return 1;
262 end
263
264 function commands.start(arg)
265         if arg[1] == "--help" then
266                 show_usage([[start]], [[Start Prosody]]);
267                 return 1;
268         end
269         local ok, ret = prosodyctl.isrunning();
270         if not ok then
271                 show_message(error_messages[ret]);
272                 return 1;
273         end
274         
275         if ret then
276                 local ok, ret = prosodyctl.getpid();
277                 if not ok then
278                         show_message("Couldn't get running Prosody's PID");
279                         show_message(error_messages[ret]);
280                         return 1;
281                 end
282                 show_message("Prosody is already running with PID %s", ret or "(unknown)");
283                 return 1;
284         end
285         
286         local ok, ret = prosodyctl.start();
287         if ok then return 0; end
288
289         show_message("Failed to start Prosody");
290         show_message(error_messages[ret])       
291         return 1;       
292 end
293
294 function commands.status(arg)
295         if arg[1] == "--help" then
296                 show_usage([[status]], [[Reports the running status of Prosody]]);
297                 return 1;
298         end
299
300         local ok, ret = prosodyctl.isrunning();
301         if not ok then
302                 show_message(error_messages[ret]);
303                 return 1;
304         end
305         
306         if ret then
307                 local ok, ret = prosodyctl.getpid();
308                 if not ok then
309                         show_message("Couldn't get running Prosody's PID");
310                         show_message(error_messages[ret]);
311                         return 1;
312                 end
313                 show_message("Prosody is running with PID %s", ret or "(unknown)");
314                 return 0;
315         else
316                 show_message("Prosody is not running");
317                 if not switched_user and current_uid ~= 0 then
318                         print("\nNote: You will also see this if prosodyctl is not running under the same");
319                         print("      user account as Prosody. Try running as root (e.g. with 'sudo' in front) to");
320                         print("      gain access to Prosody's real status.");
321                 end
322                 return 2
323         end
324         return 1;
325 end
326
327 function commands.stop(arg)
328         if arg[1] == "--help" then
329                 show_usage([[stop]], [[Stop a running Prosody server]]);
330                 return 1;
331         end
332
333         if not prosodyctl.isrunning() then
334                 show_message("Prosody is not running");
335                 return 1;
336         end
337         
338         local ok, ret = prosodyctl.stop();
339         if ok then return 0; end
340
341         show_message(error_messages[ret]);
342         return 1;
343 end
344
345 -- ejabberdctl compatibility
346
347 function commands.register(arg)
348         local user, host, password = unpack(arg);
349         if (not (user and host)) or arg[1] == "--help" then
350                 if user ~= "--help" then
351                         if not user then
352                                 show_message [[No username specified]]
353                         elseif not host then
354                                 show_message [[Please specify which host you want to register the user on]];
355                         end
356                 end
357                 show_usage("register USER HOST [PASSWORD]", "Register a user on the server, with the given password");
358                 return 1;
359         end
360         if not password then
361                 password = read_password();
362                 if not password then
363                         show_message [[Unable to register user with no password]];
364                         return 1;
365                 end
366         end
367         
368         local ok, msg = prosodyctl.adduser { user = user, host = host, password = password };
369         
370         if ok then return 0; end
371         
372         show_message(error_messages[msg])
373         return 1;
374 end
375
376 function commands.unregister(arg)
377         local user, host = unpack(arg);
378         if (not (user and host)) or arg[1] == "--help" then
379                 if user ~= "--help" then
380                         if not user then
381                                 show_message [[No username specified]]
382                         elseif not host then
383                                 show_message [[Please specify which host you want to unregister the user from]];
384                         end
385                 end
386                 show_usage("unregister USER HOST [PASSWORD]", "Permanently remove a user account from the server");
387                 return 1;
388         end
389
390         local ok, msg = prosodyctl.deluser { user = user, host = host };
391         
392         if ok then return 0; end
393         
394         show_message(error_messages[msg])
395         return 1;
396 end
397
398
399 ---------------------
400
401 if not commands[command] then -- Show help for all commands
402         function show_usage(usage, desc)
403                 print(" "..usage);
404                 print("    "..desc);
405         end
406
407         print("prosodyctl - Manage a Prosody server");
408         print("");
409         print("Usage: "..arg[0].." COMMAND [OPTIONS]");
410         print("");
411         print("Where COMMAND may be one of:\n");
412
413         local hidden_commands = require "util.set".new{ "register", "unregister" };
414         local commands_order = { "adduser", "passwd", "deluser" };
415
416         local done = {};
417
418         for _, command_name in ipairs(commands_order) do
419                 local command = commands[command_name];
420                 if command then
421                         command{ "--help" };
422                         print""
423                         done[command_name] = true;
424                 end
425         end
426
427         for command_name, command in pairs(commands) do
428                 if not done[command_name] and not hidden_commands:contains(command_name) then
429                         command{ "--help" };
430                         print""
431                         done[command_name] = true;
432                 end
433         end
434         
435         
436         os.exit(0);
437 end
438
439 os.exit(commands[command]({ select(2, unpack(arg)) }));