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