1-- Prosody IM
2-- Copyright (C) 2008-2010 Matthew Wild
3-- Copyright (C) 2008-2010 Waqas Hussain
4--
5-- This project is MIT/X11 licensed. Please see the
6-- COPYING file in the source package for more information.
7--
8
9
10local config = require "core.configmanager";
11local encodings = require "util.encodings";
12local stringprep = encodings.stringprep;
13local storagemanager = require "core.storagemanager";
14local usermanager = require "core.usermanager";
15local signal = require "util.signal";
16local set = require "util.set";
17local lfs = require "lfs";
18local pcall = pcall;
19local type = type;
20
21local nodeprep, nameprep = stringprep.nodeprep, stringprep.nameprep;
22
23local io, os = io, os;
24local print = print;
25local tonumber = tonumber;
26
27local _G = _G;
28local prosody = prosody;
29
30-- UI helpers
31local function show_message(msg, ...)
32	print(msg:format(...));
33end
34
35local function show_usage(usage, desc)
36	print("Usage: ".._G.arg[0].." "..usage);
37	if desc then
38		print(" "..desc);
39	end
40end
41
42local function getchar(n)
43	local stty_ret = os.execute("stty raw -echo 2>/dev/null");
44	local ok, char;
45	if stty_ret == true or stty_ret == 0 then
46		ok, char = pcall(io.read, n or 1);
47		os.execute("stty sane");
48	else
49		ok, char = pcall(io.read, "*l");
50		if ok then
51			char = char:sub(1, n or 1);
52		end
53	end
54	if ok then
55		return char;
56	end
57end
58
59local function getline()
60	local ok, line = pcall(io.read, "*l");
61	if ok then
62		return line;
63	end
64end
65
66local function getpass()
67	local stty_ret, _, status_code = os.execute("stty -echo 2>/dev/null");
68	if status_code then -- COMPAT w/ Lua 5.1
69		stty_ret = status_code;
70	end
71	if stty_ret ~= 0 then
72		io.write("\027[08m"); -- ANSI 'hidden' text attribute
73	end
74	local ok, pass = pcall(io.read, "*l");
75	if stty_ret == 0 then
76		os.execute("stty sane");
77	else
78		io.write("\027[00m");
79	end
80	io.write("\n");
81	if ok then
82		return pass;
83	end
84end
85
86local function show_yesno(prompt)
87	io.write(prompt, " ");
88	local choice = getchar():lower();
89	io.write("\n");
90	if not choice:match("%a") then
91		choice = prompt:match("%[.-(%U).-%]$");
92		if not choice then return nil; end
93	end
94	return (choice == "y");
95end
96
97local function read_password()
98	local password;
99	while true do
100		io.write("Enter new password: ");
101		password = getpass();
102		if not password then
103			show_message("No password - cancelled");
104			return;
105		end
106		io.write("Retype new password: ");
107		if getpass() ~= password then
108			if not show_yesno [=[Passwords did not match, try again? [Y/n]]=] then
109				return;
110			end
111		else
112			break;
113		end
114	end
115	return password;
116end
117
118local function show_prompt(prompt)
119	io.write(prompt, " ");
120	local line = getline();
121	line = line and line:gsub("\n$","");
122	return (line and #line > 0) and line or nil;
123end
124
125-- Server control
126local function adduser(params)
127	local user, host, password = nodeprep(params.user), nameprep(params.host), params.password;
128	if not user then
129		return false, "invalid-username";
130	elseif not host then
131		return false, "invalid-hostname";
132	end
133
134	local host_session = prosody.hosts[host];
135	if not host_session then
136		return false, "no-such-host";
137	end
138
139	storagemanager.initialize_host(host);
140	local provider = host_session.users;
141	if not(provider) or provider.name == "null" then
142		usermanager.initialize_host(host);
143	end
144
145	local ok, errmsg = usermanager.create_user(user, password, host);
146	if not ok then
147		return false, errmsg or "creating-user-failed";
148	end
149	return true;
150end
151
152local function user_exists(params)
153	local user, host = nodeprep(params.user), nameprep(params.host);
154
155	storagemanager.initialize_host(host);
156	local provider = prosody.hosts[host].users;
157	if not(provider) or provider.name == "null" then
158		usermanager.initialize_host(host);
159	end
160
161	return usermanager.user_exists(user, host);
162end
163
164local function passwd(params)
165	if not user_exists(params) then
166		return false, "no-such-user";
167	end
168
169	return adduser(params);
170end
171
172local function deluser(params)
173	if not user_exists(params) then
174		return false, "no-such-user";
175	end
176	local user, host = nodeprep(params.user), nameprep(params.host);
177
178	return usermanager.delete_user(user, host);
179end
180
181local function getpid()
182	local pidfile = config.get("*", "pidfile");
183	if not pidfile then
184		return false, "no-pidfile";
185	end
186
187	if type(pidfile) ~= "string" then
188		return false, "invalid-pidfile";
189	end
190
191	pidfile = config.resolve_relative_path(prosody.paths.data, pidfile);
192
193	local modules_disabled = set.new(config.get("*", "modules_disabled"));
194	if prosody.platform ~= "posix" or modules_disabled:contains("posix") then
195		return false, "no-posix";
196	end
197
198	local file, err = io.open(pidfile, "r+");
199	if not file then
200		return false, "pidfile-read-failed", err;
201	end
202
203	local locked, err = lfs.lock(file, "w");
204	if locked then
205		file:close();
206		return false, "pidfile-not-locked";
207	end
208
209	local pid = tonumber(file:read("*a"));
210	file:close();
211
212	if not pid then
213		return false, "invalid-pid";
214	end
215
216	return true, pid;
217end
218
219local function isrunning()
220	local ok, pid, err = getpid();
221	if not ok then
222		if pid == "pidfile-read-failed" or pid == "pidfile-not-locked" then
223			-- Report as not running, since we can't open the pidfile
224			-- (it probably doesn't exist)
225			return true, false;
226		end
227		return ok, pid;
228	end
229	return true, signal.kill(pid, 0) == 0;
230end
231
232local function start(source_dir)
233	local ok, ret = isrunning();
234	if not ok then
235		return ok, ret;
236	end
237	if ret then
238		return false, "already-running";
239	end
240	if not source_dir then
241		os.execute("./prosody -D");
242	else
243		os.execute(source_dir.."/../../bin/prosody -D");
244	end
245	return true;
246end
247
248local function stop()
249	local ok, ret = isrunning();
250	if not ok then
251		return ok, ret;
252	end
253	if not ret then
254		return false, "not-running";
255	end
256
257	local ok, pid = getpid()
258	if not ok then return false, pid; end
259
260	signal.kill(pid, signal.SIGTERM);
261	return true;
262end
263
264local function reload()
265	local ok, ret = isrunning();
266	if not ok then
267		return ok, ret;
268	end
269	if not ret then
270		return false, "not-running";
271	end
272
273	local ok, pid = getpid()
274	if not ok then return false, pid; end
275
276	signal.kill(pid, signal.SIGHUP);
277	return true;
278end
279
280return {
281	show_message = show_message;
282	show_warning = show_message;
283	show_usage = show_usage;
284	getchar = getchar;
285	getline = getline;
286	getpass = getpass;
287	show_yesno = show_yesno;
288	read_password = read_password;
289	show_prompt = show_prompt;
290	adduser = adduser;
291	user_exists = user_exists;
292	passwd = passwd;
293	deluser = deluser;
294	getpid = getpid;
295	isrunning = isrunning;
296	start = start;
297	stop = stop;
298	reload = reload;
299};
300