You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

371 lines
9.0 KiB

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