slimlock.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  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. cerr << APPNAME << ": HOGE14b: " << endl;
  195. loginPanel->WrongPassword(cfg_passwd_timeout);
  196. cerr << APPNAME << ": HOGE14c: " << endl;
  197. }
  198. // kill thread before destroying the window that it's supposed to be raising
  199. pthread_cancel(raise_thread);
  200. loginPanel->ClosePanel();
  201. delete loginPanel;
  202. // Get DPMS stuff back to normal
  203. if (using_dpms) {
  204. DPMSSetTimeouts(dpy, dpms_standby, dpms_suspend, dpms_off);
  205. // turn off DPMS if it was off when we entered
  206. if (!dpms_state)
  207. DPMSDisable(dpy);
  208. }
  209. XCloseDisplay(dpy);
  210. close(lock_file);
  211. if(cfg->getOption("tty_lock") == "1") {
  212. if ((ioctl(term, VT_UNLOCKSWITCH)) == -1) {
  213. perror("error unlocking console");
  214. }
  215. }
  216. close(term);
  217. return 0;
  218. }
  219. void HideCursor()
  220. {
  221. if (cfg->getOption("hidecursor") == "true") {
  222. XColor black;
  223. char cursordata[1];
  224. Pixmap cursorpixmap;
  225. Cursor cursor;
  226. cursordata[0] = 0;
  227. cursorpixmap = XCreateBitmapFromData(dpy, win, cursordata, 1, 1);
  228. black.red = 0;
  229. black.green = 0;
  230. black.blue = 0;
  231. cursor = XCreatePixmapCursor(dpy, cursorpixmap, cursorpixmap,
  232. &black, &black, 0, 0);
  233. XFreePixmap(dpy, cursorpixmap);
  234. XDefineCursor(dpy, win, cursor);
  235. }
  236. }
  237. static int ConvCallback(int num_msgs, const struct pam_message **msg,
  238. struct pam_response **resp, void *appdata_ptr)
  239. {
  240. loginPanel->EventHandler(Panel::Get_Passwd);
  241. // PAM expects an array of responses, one for each message
  242. if (num_msgs == 0 ||
  243. (*resp = (pam_response*) calloc(num_msgs, sizeof(struct pam_message))) == NULL)
  244. return PAM_BUF_ERR;
  245. for (int i = 0; i < num_msgs; i++) {
  246. if (msg[i]->msg_style != PAM_PROMPT_ECHO_OFF &&
  247. msg[i]->msg_style != PAM_PROMPT_ECHO_ON)
  248. continue;
  249. // return code is currently not used but should be set to zero
  250. resp[i]->resp_retcode = 0;
  251. if ((resp[i]->resp = strdup(loginPanel->GetPasswd().c_str())) == NULL) {
  252. free(*resp);
  253. return PAM_BUF_ERR;
  254. }
  255. }
  256. return PAM_SUCCESS;
  257. }
  258. bool AuthenticateUser()
  259. {
  260. return(pam_authenticate(pam_handle, 0) == PAM_SUCCESS);
  261. }
  262. string findValidRandomTheme(const string& set)
  263. {
  264. // extract random theme from theme set; return empty string on error
  265. string name = set;
  266. struct stat buf;
  267. if (name[name.length() - 1] == ',') {
  268. name.erase(name.length() - 1);
  269. }
  270. Util::srandom(Util::makeseed());
  271. vector<string> themes;
  272. string themefile;
  273. Cfg::split(themes, name, ',');
  274. do {
  275. int sel = Util::random() % themes.size();
  276. name = Cfg::Trim(themes[sel]);
  277. themefile = string(THEMESDIR) +"/" + name + THEMESFILE;
  278. if (stat(themefile.c_str(), &buf) != 0) {
  279. themes.erase(find(themes.begin(), themes.end(), name));
  280. cerr << APPNAME << ": Invalid theme in config: "
  281. << name << endl;
  282. name = "";
  283. }
  284. } while (name == "" && themes.size());
  285. return name;
  286. }
  287. void HandleSignal(int sig)
  288. {
  289. // Get DPMS stuff back to normal
  290. if (using_dpms) {
  291. DPMSSetTimeouts(dpy, dpms_standby, dpms_suspend, dpms_off);
  292. // turn off DPMS if it was off when we entered
  293. if (!dpms_state)
  294. DPMSDisable(dpy);
  295. }
  296. if ((ioctl(term, VT_UNLOCKSWITCH)) == -1) {
  297. perror("error unlocking console");
  298. }
  299. close(term);
  300. loginPanel->ClosePanel();
  301. delete loginPanel;
  302. die(APPNAME": Caught signal; dying\n");
  303. }
  304. void* RaiseWindow(void *data) {
  305. while(1) {
  306. XRaiseWindow(dpy, win);
  307. sleep(1);
  308. }
  309. return (void *)0;
  310. }