1 /* slimlock
2  * Copyright (c) 2010-2012 Joel Burget <joelburget@gmail.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  */
9 
10 #include <cstdio>
11 #include <cstring>
12 #include <algorithm>
13 #include <sys/types.h>
14 #include <sys/ioctl.h>
15 #include <linux/vt.h>
16 #include <X11/keysym.h>
17 #include <X11/Xlib.h>
18 #include <X11/Xutil.h>
19 #include <X11/extensions/dpms.h>
20 #include <security/pam_appl.h>
21 #include <pthread.h>
22 #include <err.h>
23 #include <signal.h>
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <sys/file.h>
27 #include <errno.h>
28 #include <sys/file.h>
29 #include <fcntl.h>
30 
31 #include "cfg.h"
32 #include "util.h"
33 #include "panel.h"
34 
35 #undef APPNAME
36 #define APPNAME "slimlock"
37 #define SLIMLOCKCFG SYSCONFDIR"/slimlock.conf"
38 
39 using namespace std;
40 
41 void setBackground(const string& themedir);
42 void HideCursor();
43 bool AuthenticateUser();
44 static int ConvCallback(int num_msgs, const struct pam_message **msg,
45 						struct pam_response **resp, void *appdata_ptr);
46 string findValidRandomTheme(const string& set);
47 void HandleSignal(int sig);
48 void *RaiseWindow(void *data);
49 
50 // I really didn't wanna put these globals here, but it's the only way...
51 static Display* dpy;
52 static int scr;
53 static Window win;
54 static Cfg* cfg;
55 static Panel* loginPanel;
56 static string themeName = "";
57 
58 static pam_handle_t *pam_handle;
59 static struct pam_conv conv = {ConvCallback, NULL};
60 
61 static CARD16 dpms_standby, dpms_suspend, dpms_off, dpms_level;
62 static BOOL dpms_state, using_dpms;
63 static int term;
64 
65 static void
die(const char * errstr,...)66 die(const char *errstr, ...) {
67 	va_list ap;
68 
69 	va_start(ap, errstr);
70 	vfprintf(stderr, errstr, ap);
71 	va_end(ap);
72 	exit(EXIT_FAILURE);
73 }
74 
main(int argc,char ** argv)75 int main(int argc, char **argv) {
76 
77 	// We need to set the locale to get the input encoded in UTF-8
78 	setlocale (LC_ALL, "");
79 
80 	if((argc == 2) && !strcmp("-v", argv[1]))
81 		die(APPNAME"-"VERSION", © 2010-2012 Joel Burget\n");
82 	else if(argc != 1)
83 		die("usage: "APPNAME" [-v]\n");
84 
85 	void (*prev_fn)(int);
86 
87 	// restore DPMS settings should slimlock be killed in the line of duty
88 	prev_fn = signal(SIGTERM, HandleSignal);
89 	if (prev_fn == SIG_IGN) signal(SIGTERM, SIG_IGN);
90 
91 	// create a lock file to solve mutliple instances problem
92 	// /var/lock used to be the place to put this, now it's /run/lock
93 	// ...i think
94 	struct stat statbuf;
95 	int lock_file;
96 
97 	// try /run/lock first, since i believe it's preferred
98 	if (!stat("/run/lock", &statbuf))
99 		lock_file = open("/run/lock/"APPNAME".lock", O_CREAT | O_RDWR, 0666);
100 	else
101 		lock_file = open("/var/lock/"APPNAME".lock", O_CREAT | O_RDWR, 0666);
102 
103 	int rc = flock(lock_file, LOCK_EX | LOCK_NB);
104 
105 	if(rc) {
106 		if(EWOULDBLOCK == errno)
107 			die(APPNAME" already running\n");
108 	}
109 
110 	unsigned int cfg_passwd_timeout;
111 	// Read user's current theme
112 	cfg = new Cfg;
113 	cfg->readConf(CFGFILE);
114 	cfg->readConf(SLIMLOCKCFG);
115 	string themebase = "";
116 	string themefile = "";
117 	string themedir = "";
118 	themeName = "";
119 	themebase = string(THEMESDIR) + "/";
120 	themeName = cfg->getOption("current_theme");
121 	string::size_type pos;
122 	if ((pos = themeName.find(",")) != string::npos) {
123 		themeName = findValidRandomTheme(themeName);
124 	}
125 
126 	bool loaded = false;
127 	while (!loaded) {
128 		themedir =  themebase + themeName;
129 		themefile = themedir + THEMESFILE;
130 		if (!cfg->readConf(themefile)) {
131 			if (themeName == "default") {
132 				cerr << APPNAME << ": Failed to open default theme file "
133 					 << themefile << endl;
134 				exit(ERR_EXIT);
135 			} else {
136 				cerr << APPNAME << ": Invalid theme in config: "
137 					 << themeName << endl;
138 				themeName = "default";
139 			}
140 		} else {
141 			loaded = true;
142 		}
143 	}
144 
145 	const char *display = getenv("DISPLAY");
146 	if (!display)
147 		display = DISPLAY;
148 
149 	if(!(dpy = XOpenDisplay(display)))
150 		die(APPNAME": cannot open display\n");
151 	scr = DefaultScreen(dpy);
152 
153 	XSetWindowAttributes wa;
154 	wa.override_redirect = 1;
155 	wa.background_pixel = BlackPixel(dpy, scr);
156 
157 	// Create a full screen window
158 	Window root = RootWindow(dpy, scr);
159 	win = XCreateWindow(dpy,
160 	  root,
161 	  0,
162 	  0,
163 	  DisplayWidth(dpy, scr),
164 	  DisplayHeight(dpy, scr),
165 	  0,
166 	  DefaultDepth(dpy, scr),
167 	  CopyFromParent,
168 	  DefaultVisual(dpy, scr),
169 	  CWOverrideRedirect | CWBackPixel,
170 	  &wa);
171 	XMapWindow(dpy, win);
172 
173 	XFlush(dpy);
174 	for (int len = 1000; len; len--) {
175 		if(XGrabKeyboard(dpy, root, True, GrabModeAsync, GrabModeAsync, CurrentTime)
176 			== GrabSuccess)
177 			break;
178 		usleep(1000);
179 	}
180 	XSelectInput(dpy, win, ExposureMask | KeyPressMask);
181 
182 	// This hides the cursor if the user has that option enabled in their
183 	// configuration
184 	HideCursor();
185 
186 	loginPanel = new Panel(dpy, scr, win, cfg, themedir, Panel::Mode_Lock);
187 
188 	int ret = pam_start(APPNAME, loginPanel->GetName().c_str(), &conv, &pam_handle);
189 	// If we can't start PAM, just exit because slimlock won't work right
190 	if (ret != PAM_SUCCESS)
191 		die("PAM: %s\n", pam_strerror(pam_handle, ret));
192 
193 	// disable tty switching
194 	if(cfg->getOption("tty_lock") == "1") {
195 		if ((term = open("/dev/console", O_RDWR)) == -1)
196 			perror("error opening console");
197 
198 		if ((ioctl(term, VT_LOCKSWITCH)) == -1)
199 			perror("error locking console");
200 	}
201 
202 	// Set up DPMS
203 	unsigned int cfg_dpms_standby, cfg_dpms_off;
204 	cfg_dpms_standby = Cfg::string2int(cfg->getOption("dpms_standby_timeout").c_str());
205 	cfg_dpms_off = Cfg::string2int(cfg->getOption("dpms_off_timeout").c_str());
206 	using_dpms = DPMSCapable(dpy) && (cfg_dpms_standby > 0);
207 	if (using_dpms) {
208 		DPMSGetTimeouts(dpy, &dpms_standby, &dpms_suspend, &dpms_off);
209 
210 		DPMSSetTimeouts(dpy, cfg_dpms_standby,
211 						cfg_dpms_standby, cfg_dpms_off);
212 
213 		DPMSInfo(dpy, &dpms_level, &dpms_state);
214 		if (!dpms_state)
215 			DPMSEnable(dpy);
216 	}
217 
218 	// Get password timeout
219 	cfg_passwd_timeout = Cfg::string2int(cfg->getOption("wrong_passwd_timeout").c_str());
220 	// Let's just make sure it has a sane value
221 	cfg_passwd_timeout = cfg_passwd_timeout > 60 ? 60 : cfg_passwd_timeout;
222 
223 	pthread_t raise_thread;
224 	pthread_create(&raise_thread, NULL, RaiseWindow, NULL);
225 
226 	// Main loop
227 	while (true)
228 	{
229 		loginPanel->ResetPasswd();
230 
231 		// AuthenticateUser returns true if authenticated
232 		if (AuthenticateUser())
233 			break;
234 
235 		loginPanel->WrongPassword(cfg_passwd_timeout);
236 	}
237 
238 	// kill thread before destroying the window that it's supposed to be raising
239 	pthread_cancel(raise_thread);
240 
241 	loginPanel->ClosePanel();
242 	delete loginPanel;
243 
244 	// Get DPMS stuff back to normal
245 	if (using_dpms) {
246 		DPMSSetTimeouts(dpy, dpms_standby, dpms_suspend, dpms_off);
247 		// turn off DPMS if it was off when we entered
248 		if (!dpms_state)
249 			DPMSDisable(dpy);
250 	}
251 
252 	XCloseDisplay(dpy);
253 
254 	close(lock_file);
255 
256 	if(cfg->getOption("tty_lock") == "1") {
257 		if ((ioctl(term, VT_UNLOCKSWITCH)) == -1) {
258 			perror("error unlocking console");
259 		}
260 	}
261 	close(term);
262 
263 	return 0;
264 }
265 
HideCursor()266 void HideCursor()
267 {
268 	if (cfg->getOption("hidecursor") == "true") {
269 		XColor black;
270 		char cursordata[1];
271 		Pixmap cursorpixmap;
272 		Cursor cursor;
273 		cursordata[0] = 0;
274 		cursorpixmap = XCreateBitmapFromData(dpy, win, cursordata, 1, 1);
275 		black.red = 0;
276 		black.green = 0;
277 		black.blue = 0;
278 		cursor = XCreatePixmapCursor(dpy, cursorpixmap, cursorpixmap,
279 									 &black, &black, 0, 0);
280 		XFreePixmap(dpy, cursorpixmap);
281 		XDefineCursor(dpy, win, cursor);
282 	}
283 }
284 
ConvCallback(int num_msgs,const struct pam_message ** msg,struct pam_response ** resp,void * appdata_ptr)285 static int ConvCallback(int num_msgs, const struct pam_message **msg,
286 						struct pam_response **resp, void *appdata_ptr)
287 {
288 	loginPanel->EventHandler(Panel::Get_Passwd);
289 
290 	// PAM expects an array of responses, one for each message
291 	if (num_msgs == 0 ||
292 		(*resp = (pam_response*) calloc(num_msgs, sizeof(struct pam_message))) == NULL)
293 		return PAM_BUF_ERR;
294 
295 	for (int i = 0; i < num_msgs; i++) {
296 		if (msg[i]->msg_style != PAM_PROMPT_ECHO_OFF &&
297 			msg[i]->msg_style != PAM_PROMPT_ECHO_ON)
298 			continue;
299 
300 		// return code is currently not used but should be set to zero
301 		resp[i]->resp_retcode = 0;
302 		if ((resp[i]->resp = strdup(loginPanel->GetPasswd().c_str())) == NULL) {
303 			free(*resp);
304 			return PAM_BUF_ERR;
305 		}
306 	}
307 
308 	return PAM_SUCCESS;
309 }
310 
AuthenticateUser()311 bool AuthenticateUser()
312 {
313 	return(pam_authenticate(pam_handle, 0) == PAM_SUCCESS);
314 }
315 
findValidRandomTheme(const string & set)316 string findValidRandomTheme(const string& set)
317 {
318 	// extract random theme from theme set; return empty string on error
319 	string name = set;
320 	struct stat buf;
321 
322 	if (name[name.length() - 1] == ',') {
323 		name.erase(name.length() - 1);
324 	}
325 
326 	Util::srandom(Util::makeseed());
327 
328 	vector<string> themes;
329 	string themefile;
330 	Cfg::split(themes, name, ',');
331 	do {
332 		int sel = Util::random() % themes.size();
333 
334 		name = Cfg::Trim(themes[sel]);
335 		themefile = string(THEMESDIR) +"/" + name + THEMESFILE;
336 		if (stat(themefile.c_str(), &buf) != 0) {
337 			themes.erase(find(themes.begin(), themes.end(), name));
338 			cerr << APPNAME << ": Invalid theme in config: "
339 				 << name << endl;
340 			name = "";
341 		}
342 	} while (name == "" && themes.size());
343 	return name;
344 }
345 
HandleSignal(int sig)346 void HandleSignal(int sig)
347 {
348 	// Get DPMS stuff back to normal
349 	if (using_dpms) {
350 		DPMSSetTimeouts(dpy, dpms_standby, dpms_suspend, dpms_off);
351 		// turn off DPMS if it was off when we entered
352 		if (!dpms_state)
353 			DPMSDisable(dpy);
354 	}
355 
356 	if ((ioctl(term, VT_UNLOCKSWITCH)) == -1) {
357 		perror("error unlocking console");
358 	}
359 	close(term);
360 
361 	loginPanel->ClosePanel();
362 	delete loginPanel;
363 
364 	die(APPNAME": Caught signal; dying\n");
365 }
366 
RaiseWindow(void * data)367 void* RaiseWindow(void *data) {
368 	while(1) {
369 		XRaiseWindow(dpy, win);
370 		sleep(1);
371 	}
372 
373 	return (void *)0;
374 }
375