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.
 
 
 
 
 

1247 lines
28 KiB

  1. /* SLiM - Simple Login Manager
  2. Copyright (C) 1997, 1998 Per Liden
  3. Copyright (C) 2004-06 Simone Rota <sip@varlock.com>
  4. Copyright (C) 2004-06 Johannes Winkelmann <jw@tks6.net>
  5. This program is free software; you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation; either version 2 of the License, or
  8. (at your option) any later version.
  9. */
  10. #include <sys/types.h>
  11. #include <sys/stat.h>
  12. #include <unistd.h>
  13. #include <fcntl.h>
  14. #include <stdint.h>
  15. #include <cstring>
  16. #include <cstdio>
  17. #include <iostream>
  18. #include <fstream>
  19. #include <sstream>
  20. #include <vector>
  21. #include <algorithm>
  22. #include "app.h"
  23. #include "numlock.h"
  24. #include "util.h"
  25. #ifdef HAVE_SHADOW
  26. #include <shadow.h>
  27. #endif
  28. using namespace std;
  29. #ifdef USE_PAM
  30. #include <string>
  31. int conv(int num_msg, const struct pam_message **msg,
  32. struct pam_response **resp, void *appdata_ptr){
  33. *resp = (struct pam_response *) calloc(num_msg, sizeof(struct pam_response));
  34. Panel* panel = *static_cast<Panel**>(appdata_ptr);
  35. int result = PAM_SUCCESS;
  36. for (int i=0; i<num_msg; i++){
  37. (*resp)[i].resp=0;
  38. (*resp)[i].resp_retcode=0;
  39. switch(msg[i]->msg_style){
  40. case PAM_PROMPT_ECHO_ON:
  41. /* We assume PAM is asking for the username */
  42. panel->EventHandler(Panel::Get_Name);
  43. switch(panel->getAction()){
  44. case Panel::Suspend:
  45. case Panel::Halt:
  46. case Panel::Reboot:
  47. (*resp)[i].resp=strdup("root");
  48. break;
  49. case Panel::Console:
  50. case Panel::Exit:
  51. case Panel::Login:
  52. (*resp)[i].resp=strdup(panel->GetName().c_str());
  53. break;
  54. default:
  55. break;
  56. }
  57. break;
  58. case PAM_PROMPT_ECHO_OFF:
  59. /* We assume PAM is asking for the password */
  60. switch(panel->getAction()){
  61. case Panel::Console:
  62. case Panel::Exit:
  63. /* We should leave now! */
  64. result=PAM_CONV_ERR;
  65. break;
  66. default:
  67. panel->EventHandler(Panel::Get_Passwd);
  68. (*resp)[i].resp=strdup(panel->GetPasswd().c_str());
  69. break;
  70. }
  71. break;
  72. case PAM_ERROR_MSG:
  73. case PAM_TEXT_INFO:
  74. /* We simply write these to the log
  75. TODO: Maybe we should simply ignore them */
  76. logStream << APPNAME << ": " << msg[i]->msg << endl;
  77. break;
  78. }
  79. if (result!=PAM_SUCCESS) break;
  80. }
  81. if (result!=PAM_SUCCESS){
  82. for (int i=0; i<num_msg; i++){
  83. if ((*resp)[i].resp==0) continue;
  84. free((*resp)[i].resp);
  85. (*resp)[i].resp=0;
  86. };
  87. free(*resp);
  88. *resp=0;
  89. };
  90. return result;
  91. }
  92. #endif
  93. extern App* LoginApp;
  94. int xioerror(Display *disp) {
  95. LoginApp->RestartServer();
  96. return 0;
  97. }
  98. void CatchSignal(int sig) {
  99. logStream << APPNAME << ": unexpected signal " << sig << endl;
  100. if (LoginApp->isServerStarted())
  101. LoginApp->StopServer();
  102. LoginApp->RemoveLock();
  103. exit(ERR_EXIT);
  104. }
  105. void User1Signal(int sig) {
  106. signal(sig, User1Signal);
  107. }
  108. #ifdef USE_PAM
  109. App::App(int argc, char** argv)
  110. : pam(conv, static_cast<void*>(&LoginPanel)),
  111. #else
  112. App::App(int argc, char** argv)
  113. :
  114. #endif
  115. mcookiesize(32) /* Must be divisible by 4 */
  116. {
  117. int tmp;
  118. ServerPID = -1;
  119. testing = false;
  120. serverStarted = false;
  121. mcookie = string(App::mcookiesize, 'a');
  122. daemonmode = false;
  123. force_nodaemon = false;
  124. firstlogin = true;
  125. Dpy = NULL;
  126. /* Parse command line
  127. Note: we force a option for nodaemon switch to handle "-nodaemon" */
  128. while((tmp = getopt(argc, argv, "vhp:n:d?")) != EOF) {
  129. switch (tmp) {
  130. case 'p': /* Test theme */
  131. testtheme = optarg;
  132. testing = true;
  133. if (testtheme == NULL) {
  134. logStream << "The -p option requires an argument" << endl;
  135. exit(ERR_EXIT);
  136. }
  137. break;
  138. case 'd': /* Daemon mode */
  139. daemonmode = true;
  140. break;
  141. case 'n': /* Daemon mode */
  142. daemonmode = false;
  143. force_nodaemon = true;
  144. break;
  145. case 'v': /* Version */
  146. std::cout << APPNAME << " version " << VERSION << endl;
  147. exit(OK_EXIT);
  148. break;
  149. case '?': /* Illegal */
  150. logStream << endl;
  151. case 'h': /* Help */
  152. logStream << "usage: " << APPNAME << " [option ...]" << endl
  153. << "options:" << endl
  154. << " -d: daemon mode" << endl
  155. << " -nodaemon: no-daemon mode" << endl
  156. << " -v: show version" << endl
  157. << " -p /path/to/theme/dir: preview theme" << endl;
  158. exit(OK_EXIT);
  159. break;
  160. }
  161. }
  162. #ifndef XNEST_DEBUG
  163. if (getuid() != 0 && !testing) {
  164. logStream << APPNAME << ": only root can run this program" << endl;
  165. exit(ERR_EXIT);
  166. }
  167. #endif /* XNEST_DEBUG */
  168. }
  169. void App::Run() {
  170. DisplayName = DISPLAY;
  171. #ifdef XNEST_DEBUG
  172. char* p = getenv("DISPLAY");
  173. if (p && p[0]) {
  174. DisplayName = p;
  175. cout << "Using display name " << DisplayName << endl;
  176. }
  177. #endif
  178. /* Read configuration and theme */
  179. cfg = new Cfg;
  180. cfg->readConf(CFGFILE);
  181. string themebase = "";
  182. string themefile = "";
  183. string themedir = "";
  184. themeName = "";
  185. if (testing) {
  186. themeName = testtheme;
  187. } else {
  188. themebase = string(THEMESDIR) + "/";
  189. themeName = cfg->getOption("current_theme");
  190. string::size_type pos;
  191. if ((pos = themeName.find(",")) != string::npos) {
  192. /* input is a set */
  193. themeName = findValidRandomTheme(themeName);
  194. if (themeName == "") {
  195. themeName = "default";
  196. }
  197. }
  198. }
  199. #ifdef USE_PAM
  200. try{
  201. pam.start("slim");
  202. pam.set_item(PAM::Authenticator::TTY, DisplayName);
  203. pam.set_item(PAM::Authenticator::Requestor, "root");
  204. }
  205. catch(PAM::Exception& e){
  206. logStream << APPNAME << ": " << e << endl;
  207. exit(ERR_EXIT);
  208. };
  209. #endif
  210. bool loaded = false;
  211. while (!loaded) {
  212. themedir = themebase + themeName;
  213. themefile = themedir + THEMESFILE;
  214. if (!cfg->readConf(themefile)) {
  215. if (themeName == "default") {
  216. logStream << APPNAME << ": Failed to open default theme file "
  217. << themefile << endl;
  218. exit(ERR_EXIT);
  219. } else {
  220. logStream << APPNAME << ": Invalid theme in config: "
  221. << themeName << endl;
  222. themeName = "default";
  223. }
  224. } else {
  225. loaded = true;
  226. }
  227. }
  228. if (!testing) {
  229. /* Create lock file */
  230. LoginApp->GetLock();
  231. /* Start x-server */
  232. setenv("DISPLAY", DisplayName, 1);
  233. signal(SIGQUIT, CatchSignal);
  234. signal(SIGTERM, CatchSignal);
  235. signal(SIGKILL, CatchSignal);
  236. signal(SIGINT, CatchSignal);
  237. signal(SIGHUP, CatchSignal);
  238. signal(SIGPIPE, CatchSignal);
  239. signal(SIGUSR1, User1Signal);
  240. #ifndef XNEST_DEBUG
  241. if (!force_nodaemon && cfg->getOption("daemon") == "yes") {
  242. daemonmode = true;
  243. }
  244. /* Daemonize */
  245. if (daemonmode) {
  246. if (daemon(0, 0) == -1) {
  247. logStream << APPNAME << ": " << strerror(errno) << endl;
  248. exit(ERR_EXIT);
  249. }
  250. }
  251. OpenLog();
  252. if (daemonmode)
  253. UpdatePid();
  254. CreateServerAuth();
  255. StartServer();
  256. #endif
  257. }
  258. /* Open display */
  259. if((Dpy = XOpenDisplay(DisplayName)) == 0) {
  260. logStream << APPNAME << ": could not open display '"
  261. << DisplayName << "'" << endl;
  262. if (!testing) StopServer();
  263. exit(ERR_EXIT);
  264. }
  265. /* Get screen and root window */
  266. Scr = DefaultScreen(Dpy);
  267. Root = RootWindow(Dpy, Scr);
  268. // Intern _XROOTPMAP_ID property
  269. BackgroundPixmapId = XInternAtom(Dpy, "_XROOTPMAP_ID", False);
  270. /* for tests we use a standard window */
  271. if (testing) {
  272. Window RealRoot = RootWindow(Dpy, Scr);
  273. Root = XCreateSimpleWindow(Dpy, RealRoot, 0, 0, 1280, 1024, 0, 0, 0);
  274. XMapWindow(Dpy, Root);
  275. XFlush(Dpy);
  276. } else {
  277. blankScreen();
  278. }
  279. HideCursor();
  280. /* Create panel */
  281. LoginPanel = new Panel(Dpy, Scr, Root, cfg, themedir, Panel::Mode_DM);
  282. bool firstloop = true; /* 1st time panel is shown (for automatic username) */
  283. bool focuspass = cfg->getOption("focus_password")=="yes";
  284. bool autologin = cfg->getOption("auto_login")=="yes";
  285. if (firstlogin && cfg->getOption("default_user") != "") {
  286. LoginPanel->SetName(cfg->getOption("default_user") );
  287. #ifdef USE_PAM
  288. pam.set_item(PAM::Authenticator::User, cfg->getOption("default_user").c_str());
  289. #endif
  290. firstlogin = false;
  291. if (autologin) {
  292. Login();
  293. }
  294. }
  295. /* Set NumLock */
  296. string numlock = cfg->getOption("numlock");
  297. if (numlock == "on") {
  298. NumLock::setOn(Dpy);
  299. } else if (numlock == "off") {
  300. NumLock::setOff(Dpy);
  301. }
  302. /* Start looping */
  303. int panelclosed = 1;
  304. Panel::ActionType Action;
  305. while(1) {
  306. if(panelclosed) {
  307. /* Init root */
  308. setBackground(themedir);
  309. /* Close all clients */
  310. if (!testing) {
  311. KillAllClients(False);
  312. KillAllClients(True);
  313. }
  314. /* Show panel */
  315. LoginPanel->OpenPanel();
  316. }
  317. LoginPanel->Reset();
  318. if (firstloop && cfg->getOption("default_user") != "") {
  319. LoginPanel->SetName(cfg->getOption("default_user") );
  320. }
  321. if (firstloop) {
  322. LoginPanel->SwitchSession();
  323. }
  324. if (!AuthenticateUser(focuspass && firstloop)){
  325. panelclosed = 0;
  326. firstloop = false;
  327. LoginPanel->ClearPanel();
  328. XBell(Dpy, 100);
  329. continue;
  330. }
  331. firstloop = false;
  332. Action = LoginPanel->getAction();
  333. /* for themes test we just quit */
  334. if (testing) {
  335. Action = Panel::Exit;
  336. }
  337. panelclosed = 1;
  338. LoginPanel->ClosePanel();
  339. switch(Action) {
  340. case Panel::Login:
  341. Login();
  342. break;
  343. case Panel::Console:
  344. Console();
  345. break;
  346. case Panel::Reboot:
  347. Reboot();
  348. break;
  349. case Panel::Halt:
  350. Halt();
  351. break;
  352. case Panel::Suspend:
  353. Suspend();
  354. break;
  355. case Panel::Exit:
  356. Exit();
  357. break;
  358. default:
  359. break;
  360. }
  361. }
  362. }
  363. #ifdef USE_PAM
  364. bool App::AuthenticateUser(bool focuspass){
  365. /* Reset the username */
  366. try{
  367. if (!focuspass)
  368. pam.set_item(PAM::Authenticator::User, 0);
  369. pam.authenticate();
  370. }
  371. catch(PAM::Auth_Exception& e){
  372. switch(LoginPanel->getAction()){
  373. case Panel::Exit:
  374. case Panel::Console:
  375. return true; /* <--- This is simply fake! */
  376. default:
  377. break;
  378. };
  379. logStream << APPNAME << ": " << e << endl;
  380. return false;
  381. }
  382. catch(PAM::Exception& e){
  383. logStream << APPNAME << ": " << e << endl;
  384. exit(ERR_EXIT);
  385. };
  386. return true;
  387. }
  388. #else
  389. bool App::AuthenticateUser(bool focuspass){
  390. if (!focuspass){
  391. LoginPanel->EventHandler(Panel::Get_Name);
  392. switch(LoginPanel->getAction()){
  393. case Panel::Exit:
  394. case Panel::Console:
  395. logStream << APPNAME << ": Got a special command (" << LoginPanel->GetName() << ")" << endl;
  396. return true; /* <--- This is simply fake! */
  397. default:
  398. break;
  399. }
  400. }
  401. LoginPanel->EventHandler(Panel::Get_Passwd);
  402. char *encrypted, *correct;
  403. struct passwd *pw;
  404. switch(LoginPanel->getAction()){
  405. case Panel::Suspend:
  406. case Panel::Halt:
  407. case Panel::Reboot:
  408. pw = getpwnam("root");
  409. break;
  410. case Panel::Console:
  411. case Panel::Exit:
  412. case Panel::Login:
  413. pw = getpwnam(LoginPanel->GetName().c_str());
  414. break;
  415. }
  416. endpwent();
  417. if(pw == 0)
  418. return false;
  419. #ifdef HAVE_SHADOW
  420. struct spwd *sp = getspnam(pw->pw_name);
  421. endspent();
  422. if(sp)
  423. correct = sp->sp_pwdp;
  424. else
  425. #endif /* HAVE_SHADOW */
  426. correct = pw->pw_passwd;
  427. if(correct == 0 || correct[0] == '\0')
  428. return true;
  429. encrypted = crypt(LoginPanel->GetPasswd().c_str(), correct);
  430. return ((encrypted && strcmp(encrypted, correct) == 0) ? true : false);
  431. }
  432. #endif
  433. int App::GetServerPID() {
  434. return ServerPID;
  435. }
  436. /* Hide the cursor */
  437. void App::HideCursor() {
  438. if (cfg->getOption("hidecursor") == "true") {
  439. XColor black;
  440. char cursordata[1];
  441. Pixmap cursorpixmap;
  442. Cursor cursor;
  443. cursordata[0]=0;
  444. cursorpixmap=XCreateBitmapFromData(Dpy,Root,cursordata,1,1);
  445. black.red=0;
  446. black.green=0;
  447. black.blue=0;
  448. cursor=XCreatePixmapCursor(Dpy,cursorpixmap,cursorpixmap,&black,&black,0,0);
  449. XDefineCursor(Dpy,Root,cursor);
  450. }
  451. }
  452. void App::Login() {
  453. struct passwd *pw;
  454. pid_t pid;
  455. #ifdef USE_PAM
  456. try{
  457. pam.open_session();
  458. pw = getpwnam(static_cast<const char*>(pam.get_item(PAM::Authenticator::User)));
  459. }
  460. catch(PAM::Cred_Exception& e){
  461. /* Credentials couldn't be established */
  462. logStream << APPNAME << ": " << e << endl;
  463. return;
  464. }
  465. catch(PAM::Exception& e){
  466. logStream << APPNAME << ": " << e << endl;
  467. exit(ERR_EXIT);
  468. };
  469. #else
  470. pw = getpwnam(LoginPanel->GetName().c_str());
  471. #endif
  472. endpwent();
  473. if(pw == 0)
  474. return;
  475. if (pw->pw_shell[0] == '\0') {
  476. setusershell();
  477. strcpy(pw->pw_shell, getusershell());
  478. endusershell();
  479. }
  480. /* Setup the environment */
  481. char* term = getenv("TERM");
  482. string maildir = _PATH_MAILDIR;
  483. maildir.append("/");
  484. maildir.append(pw->pw_name);
  485. string xauthority = pw->pw_dir;
  486. xauthority.append("/.Xauthority");
  487. #ifdef USE_PAM
  488. /* Setup the PAM environment */
  489. try{
  490. if(term) pam.setenv("TERM", term);
  491. pam.setenv("HOME", pw->pw_dir);
  492. pam.setenv("PWD", pw->pw_dir);
  493. pam.setenv("SHELL", pw->pw_shell);
  494. pam.setenv("USER", pw->pw_name);
  495. pam.setenv("LOGNAME", pw->pw_name);
  496. pam.setenv("PATH", cfg->getOption("default_path").c_str());
  497. pam.setenv("DISPLAY", DisplayName);
  498. pam.setenv("MAIL", maildir.c_str());
  499. pam.setenv("XAUTHORITY", xauthority.c_str());
  500. }
  501. catch(PAM::Exception& e){
  502. logStream << APPNAME << ": " << e << endl;
  503. exit(ERR_EXIT);
  504. }
  505. #endif
  506. #ifdef USE_CONSOLEKIT
  507. /* Setup the ConsoleKit session */
  508. try {
  509. ck.open_session(DisplayName, pw->pw_uid);
  510. }
  511. catch(Ck::Exception &e) {
  512. logStream << APPNAME << ": " << e << endl;
  513. exit(ERR_EXIT);
  514. }
  515. #endif
  516. /* Create new process */
  517. pid = fork();
  518. if(pid == 0) {
  519. #ifdef USE_PAM
  520. /* Get a copy of the environment and close the child's copy */
  521. /* of the PAM-handle. */
  522. char** child_env = pam.getenvlist();
  523. # ifdef USE_CONSOLEKIT
  524. char** old_env = child_env;
  525. /* Grow the copy of the environment for the session cookie */
  526. int n;
  527. for(n = 0; child_env[n] != NULL ; n++);
  528. n++;
  529. child_env = static_cast<char**>(malloc(sizeof(char*)*n));
  530. memcpy(child_env, old_env, sizeof(char*)*n+1);
  531. child_env[n - 1] = StrConcat("XDG_SESSION_COOKIE=", ck.get_xdg_session_cookie());
  532. child_env[n] = NULL;
  533. # endif /* USE_CONSOLEKIT */
  534. #else
  535. # ifdef USE_CONSOLEKIT
  536. const int Num_Of_Variables = 12; /* Number of env. variables + 1 */
  537. # else
  538. const int Num_Of_Variables = 11; /* Number of env. variables + 1 */
  539. # endif /* USE_CONSOLEKIT */
  540. char** child_env = static_cast<char**>(malloc(sizeof(char*)*Num_Of_Variables));
  541. int n = 0;
  542. if(term) child_env[n++]=StrConcat("TERM=", term);
  543. child_env[n++]=StrConcat("HOME=", pw->pw_dir);
  544. child_env[n++]=StrConcat("PWD=", pw->pw_dir);
  545. child_env[n++]=StrConcat("SHELL=", pw->pw_shell);
  546. child_env[n++]=StrConcat("USER=", pw->pw_name);
  547. child_env[n++]=StrConcat("LOGNAME=", pw->pw_name);
  548. child_env[n++]=StrConcat("PATH=", cfg->getOption("default_path").c_str());
  549. child_env[n++]=StrConcat("DISPLAY=", DisplayName);
  550. child_env[n++]=StrConcat("MAIL=", maildir.c_str());
  551. child_env[n++]=StrConcat("XAUTHORITY=", xauthority.c_str());
  552. # ifdef USE_CONSOLEKIT
  553. child_env[n++]=StrConcat("XDG_SESSION_COOKIE=", ck.get_xdg_session_cookie());
  554. # endif /* USE_CONSOLEKIT */
  555. child_env[n++]=0;
  556. #endif
  557. /* Login process starts here */
  558. SwitchUser Su(pw, cfg, DisplayName, child_env);
  559. string session = LoginPanel->getSession();
  560. string loginCommand = cfg->getOption("login_cmd");
  561. replaceVariables(loginCommand, SESSION_VAR, session);
  562. replaceVariables(loginCommand, THEME_VAR, themeName);
  563. string sessStart = cfg->getOption("sessionstart_cmd");
  564. if (sessStart != "") {
  565. replaceVariables(sessStart, USER_VAR, pw->pw_name);
  566. system(sessStart.c_str());
  567. }
  568. Su.Login(loginCommand.c_str(), mcookie.c_str());
  569. _exit(OK_EXIT);
  570. }
  571. #ifndef XNEST_DEBUG
  572. CloseLog();
  573. #endif
  574. /* Wait until user is logging out (login process terminates) */
  575. pid_t wpid = -1;
  576. int status;
  577. while (wpid != pid) {
  578. wpid = wait(&status);
  579. if (wpid == ServerPID)
  580. xioerror(Dpy); /* Server died, simulate IO error */
  581. }
  582. if (WIFEXITED(status) && WEXITSTATUS(status)) {
  583. LoginPanel->Message("Failed to execute login command");
  584. sleep(3);
  585. } else {
  586. string sessStop = cfg->getOption("sessionstop_cmd");
  587. if (sessStop != "") {
  588. replaceVariables(sessStop, USER_VAR, pw->pw_name);
  589. system(sessStop.c_str());
  590. }
  591. }
  592. #ifdef USE_CONSOLEKIT
  593. try {
  594. ck.close_session();
  595. }
  596. catch(Ck::Exception &e) {
  597. logStream << APPNAME << ": " << e << endl;
  598. };
  599. #endif
  600. #ifdef USE_PAM
  601. try{
  602. pam.close_session();
  603. }
  604. catch(PAM::Exception& e){
  605. logStream << APPNAME << ": " << e << endl;
  606. };
  607. #endif
  608. /* Close all clients */
  609. KillAllClients(False);
  610. KillAllClients(True);
  611. /* Send HUP signal to clientgroup */
  612. killpg(pid, SIGHUP);
  613. /* Send TERM signal to clientgroup, if error send KILL */
  614. if(killpg(pid, SIGTERM))
  615. killpg(pid, SIGKILL);
  616. HideCursor();
  617. #ifndef XNEST_DEBUG
  618. /* Re-activate log file */
  619. OpenLog();
  620. RestartServer();
  621. #endif
  622. }
  623. void App::Reboot() {
  624. #ifdef USE_PAM
  625. try{
  626. pam.end();
  627. }
  628. catch(PAM::Exception& e){
  629. logStream << APPNAME << ": " << e << endl;
  630. };
  631. #endif
  632. /* Write message */
  633. LoginPanel->Message((char*)cfg->getOption("reboot_msg").c_str());
  634. sleep(3);
  635. /* Stop server and reboot */
  636. StopServer();
  637. RemoveLock();
  638. system(cfg->getOption("reboot_cmd").c_str());
  639. exit(OK_EXIT);
  640. }
  641. void App::Halt() {
  642. #ifdef USE_PAM
  643. try{
  644. pam.end();
  645. }
  646. catch(PAM::Exception& e){
  647. logStream << APPNAME << ": " << e << endl;
  648. };
  649. #endif
  650. /* Write message */
  651. LoginPanel->Message((char*)cfg->getOption("shutdown_msg").c_str());
  652. sleep(3);
  653. /* Stop server and halt */
  654. StopServer();
  655. RemoveLock();
  656. system(cfg->getOption("halt_cmd").c_str());
  657. exit(OK_EXIT);
  658. }
  659. void App::Suspend() {
  660. sleep(1);
  661. system(cfg->getOption("suspend_cmd").c_str());
  662. }
  663. void App::Console() {
  664. int posx = 40;
  665. int posy = 40;
  666. int fontx = 9;
  667. int fonty = 15;
  668. int width = (XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)) - (posx * 2)) / fontx;
  669. int height = (XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)) - (posy * 2)) / fonty;
  670. /* Execute console */
  671. const char* cmd = cfg->getOption("console_cmd").c_str();
  672. char *tmp = new char[strlen(cmd) + 60];
  673. sprintf(tmp, cmd, width, height, posx, posy, fontx, fonty);
  674. system(tmp);
  675. delete [] tmp;
  676. }
  677. void App::Exit() {
  678. #ifdef USE_PAM
  679. try{
  680. pam.end();
  681. }
  682. catch(PAM::Exception& e){
  683. logStream << APPNAME << ": " << e << endl;
  684. };
  685. #endif
  686. if (testing) {
  687. const char* testmsg = "This is a test message :-)";
  688. LoginPanel->Message(testmsg);
  689. sleep(3);
  690. delete LoginPanel;
  691. XCloseDisplay(Dpy);
  692. } else {
  693. delete LoginPanel;
  694. StopServer();
  695. RemoveLock();
  696. }
  697. delete cfg;
  698. exit(OK_EXIT);
  699. }
  700. int CatchErrors(Display *dpy, XErrorEvent *ev) {
  701. return 0;
  702. }
  703. void App::RestartServer() {
  704. #ifdef USE_PAM
  705. try{
  706. pam.end();
  707. }
  708. catch(PAM::Exception& e){
  709. logStream << APPNAME << ": " << e << endl;
  710. };
  711. #endif
  712. StopServer();
  713. RemoveLock();
  714. while (waitpid(-1, NULL, WNOHANG) > 0); /* Collects all dead childrens */
  715. Run();
  716. }
  717. void App::KillAllClients(Bool top) {
  718. Window dummywindow;
  719. Window *children;
  720. unsigned int nchildren;
  721. unsigned int i;
  722. XWindowAttributes attr;
  723. XSync(Dpy, 0);
  724. XSetErrorHandler(CatchErrors);
  725. nchildren = 0;
  726. XQueryTree(Dpy, Root, &dummywindow, &dummywindow, &children, &nchildren);
  727. if(!top) {
  728. for(i=0; i<nchildren; i++) {
  729. if(XGetWindowAttributes(Dpy, children[i], &attr) && (attr.map_state == IsViewable))
  730. children[i] = XmuClientWindow(Dpy, children[i]);
  731. else
  732. children[i] = 0;
  733. }
  734. }
  735. for(i=0; i<nchildren; i++) {
  736. if(children[i])
  737. XKillClient(Dpy, children[i]);
  738. }
  739. XFree((char *)children);
  740. XSync(Dpy, 0);
  741. XSetErrorHandler(NULL);
  742. }
  743. int App::ServerTimeout(int timeout, char* text) {
  744. int i = 0;
  745. int pidfound = -1;
  746. static char *lasttext;
  747. for(;;) {
  748. pidfound = waitpid(ServerPID, NULL, WNOHANG);
  749. if(pidfound == ServerPID)
  750. break;
  751. if(timeout) {
  752. if(i == 0 && text != lasttext)
  753. logStream << endl << APPNAME << ": waiting for " << text;
  754. else
  755. logStream << ".";
  756. }
  757. if(timeout)
  758. sleep(1);
  759. if(++i > timeout)
  760. break;
  761. }
  762. if(i > 0)
  763. logStream << endl;
  764. lasttext = text;
  765. return (ServerPID != pidfound);
  766. }
  767. int App::WaitForServer() {
  768. int ncycles = 120;
  769. int cycles;
  770. for(cycles = 0; cycles < ncycles; cycles++) {
  771. if((Dpy = XOpenDisplay(DisplayName))) {
  772. XSetIOErrorHandler(xioerror);
  773. return 1;
  774. } else {
  775. if(!ServerTimeout(1, (char *) "X server to begin accepting connections"))
  776. break;
  777. }
  778. }
  779. logStream << "Giving up." << endl;
  780. return 0;
  781. }
  782. int App::StartServer() {
  783. ServerPID = fork();
  784. static const int MAX_XSERVER_ARGS = 256;
  785. static char* server[MAX_XSERVER_ARGS+2] = { NULL };
  786. server[0] = (char *)cfg->getOption("default_xserver").c_str();
  787. string argOption = cfg->getOption("xserver_arguments");
  788. /* Add mandatory -xauth option */
  789. argOption = argOption + " -auth " + cfg->getOption("authfile");
  790. char* args = new char[argOption.length()+2]; /* NULL plus vt */
  791. strcpy(args, argOption.c_str());
  792. serverStarted = false;
  793. int argc = 1;
  794. int pos = 0;
  795. bool hasVtSet = false;
  796. while (args[pos] != '\0') {
  797. if (args[pos] == ' ' || args[pos] == '\t') {
  798. *(args+pos) = '\0';
  799. server[argc++] = args+pos+1;
  800. } else if (pos == 0) {
  801. server[argc++] = args+pos;
  802. }
  803. ++pos;
  804. if (argc+1 >= MAX_XSERVER_ARGS) {
  805. /* ignore _all_ arguments to make sure the server starts at */
  806. /* all */
  807. argc = 1;
  808. break;
  809. }
  810. }
  811. for (int i=0; i<argc; i++) {
  812. if (server[i][0] == 'v' && server[i][1] == 't') {
  813. bool ok = false;
  814. Cfg::string2int(server[i]+2, &ok);
  815. if (ok) {
  816. hasVtSet = true;
  817. }
  818. }
  819. }
  820. if (!hasVtSet && daemonmode) {
  821. server[argc++] = (char*)"vt07";
  822. }
  823. server[argc] = NULL;
  824. switch(ServerPID) {
  825. case 0:
  826. signal(SIGTTIN, SIG_IGN);
  827. signal(SIGTTOU, SIG_IGN);
  828. signal(SIGUSR1, SIG_IGN);
  829. setpgid(0,getpid());
  830. execvp(server[0], server);
  831. logStream << APPNAME << ": X server could not be started" << endl;
  832. exit(ERR_EXIT);
  833. break;
  834. case -1:
  835. break;
  836. default:
  837. errno = 0;
  838. if(!ServerTimeout(0, (char *)"")) {
  839. ServerPID = -1;
  840. break;
  841. }
  842. /* Wait for server to start up */
  843. if(WaitForServer() == 0) {
  844. logStream << APPNAME << ": unable to connect to X server" << endl;
  845. StopServer();
  846. ServerPID = -1;
  847. exit(ERR_EXIT);
  848. }
  849. break;
  850. }
  851. delete [] args;
  852. serverStarted = true;
  853. return ServerPID;
  854. }
  855. jmp_buf CloseEnv;
  856. int IgnoreXIO(Display *d) {
  857. logStream << APPNAME << ": connection to X server lost." << endl;
  858. longjmp(CloseEnv, 1);
  859. }
  860. void App::StopServer() {
  861. signal(SIGQUIT, SIG_IGN);
  862. signal(SIGINT, SIG_IGN);
  863. signal(SIGHUP, SIG_IGN);
  864. signal(SIGPIPE, SIG_IGN);
  865. signal(SIGTERM, SIG_DFL);
  866. signal(SIGKILL, SIG_DFL);
  867. /* Catch X error */
  868. XSetIOErrorHandler(IgnoreXIO);
  869. if(!setjmp(CloseEnv) && Dpy)
  870. XCloseDisplay(Dpy);
  871. /* Send HUP to process group */
  872. errno = 0;
  873. if((killpg(getpid(), SIGHUP) != 0) && (errno != ESRCH))
  874. logStream << APPNAME << ": can't send HUP to process group " << getpid() << endl;
  875. /* Send TERM to server */
  876. if(ServerPID < 0)
  877. return;
  878. errno = 0;
  879. if(killpg(ServerPID, SIGTERM) < 0) {
  880. if(errno == EPERM) {
  881. logStream << APPNAME << ": can't kill X server" << endl;
  882. exit(ERR_EXIT);
  883. }
  884. if(errno == ESRCH)
  885. return;
  886. }
  887. /* Wait for server to shut down */
  888. if(!ServerTimeout(10, (char *)"X server to shut down")) {
  889. logStream << endl;
  890. return;
  891. }
  892. logStream << endl << APPNAME << ": X server slow to shut down, sending KILL signal." << endl;
  893. /* Send KILL to server */
  894. errno = 0;
  895. if(killpg(ServerPID, SIGKILL) < 0) {
  896. if(errno == ESRCH)
  897. return;
  898. }
  899. /* Wait for server to die */
  900. if(ServerTimeout(3, (char*)"server to die")) {
  901. logStream << endl << APPNAME << ": can't kill server" << endl;
  902. exit(ERR_EXIT);
  903. }
  904. logStream << endl;
  905. }
  906. void App::blankScreen()
  907. {
  908. GC gc = XCreateGC(Dpy, Root, 0, 0);
  909. XSetForeground(Dpy, gc, BlackPixel(Dpy, Scr));
  910. XFillRectangle(Dpy, Root, gc, 0, 0,
  911. XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)),
  912. XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)));
  913. XFlush(Dpy);
  914. XFreeGC(Dpy, gc);
  915. }
  916. void App::setBackground(const string& themedir) {
  917. string filename;
  918. filename = themedir + "/background.png";
  919. image = new Image;
  920. bool loaded = image->Read(filename.c_str());
  921. if (!loaded){ /* try jpeg if png failed */
  922. filename = themedir + "/background.jpg";
  923. loaded = image->Read(filename.c_str());
  924. }
  925. if (loaded) {
  926. string bgstyle = cfg->getOption("background_style");
  927. if (bgstyle == "stretch") {
  928. image->Resize(XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)));
  929. } else if (bgstyle == "tile") {
  930. image->Tile(XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)));
  931. } else if (bgstyle == "center") {
  932. string hexvalue = cfg->getOption("background_color");
  933. hexvalue = hexvalue.substr(1,6);
  934. image->Center(XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)),
  935. hexvalue.c_str());
  936. } else { /* plain color or error */
  937. string hexvalue = cfg->getOption("background_color");
  938. hexvalue = hexvalue.substr(1,6);
  939. image->Center(XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)),
  940. hexvalue.c_str());
  941. }
  942. Pixmap p = image->createPixmap(Dpy, Scr, Root);
  943. XSetWindowBackgroundPixmap(Dpy, Root, p);
  944. XChangeProperty(Dpy, Root, BackgroundPixmapId, XA_PIXMAP, 32,
  945. PropModeReplace, (unsigned char *)&p, 1);
  946. }
  947. XClearWindow(Dpy, Root);
  948. XFlush(Dpy);
  949. delete image;
  950. }
  951. /* Check if there is a lockfile and a corresponding process */
  952. void App::GetLock() {
  953. std::ifstream lockfile(cfg->getOption("lockfile").c_str());
  954. if (!lockfile) {
  955. /* no lockfile present, create one */
  956. std::ofstream lockfile(cfg->getOption("lockfile").c_str(), ios_base::out);
  957. if (!lockfile) {
  958. logStream << APPNAME << ": Could not create lock file: " << cfg->getOption("lockfile").c_str() << std::endl;
  959. exit(ERR_EXIT);
  960. }
  961. lockfile << getpid() << std::endl;
  962. lockfile.close();
  963. } else {
  964. /* lockfile present, read pid from it */
  965. int pid = 0;
  966. lockfile >> pid;
  967. lockfile.close();
  968. if (pid > 0) {
  969. /* see if process with this pid exists */
  970. int ret = kill(pid, 0);
  971. if (ret == 0 || (ret == -1 && errno == EPERM) ) {
  972. logStream << APPNAME << ": Another instance of the program is already running with PID " << pid << std::endl;
  973. exit(0);
  974. } else {
  975. logStream << APPNAME << ": Stale lockfile found, removing it" << std::endl;
  976. std::ofstream lockfile(cfg->getOption("lockfile").c_str(), ios_base::out);
  977. if (!lockfile) {
  978. logStream << APPNAME << ": Could not create new lock file: " << cfg->getOption("lockfile") << std::endl;
  979. exit(ERR_EXIT);
  980. }
  981. lockfile << getpid() << std::endl;
  982. lockfile.close();
  983. }
  984. }
  985. }
  986. }
  987. /* Remove lockfile and close logs */
  988. void App::RemoveLock() {
  989. remove(cfg->getOption("lockfile").c_str());
  990. }
  991. /* Get server start check flag. */
  992. bool App::isServerStarted() {
  993. return serverStarted;
  994. }
  995. /* Redirect stdout and stderr to log file */
  996. void App::OpenLog() {
  997. if ( !logStream.openLog( cfg->getOption("logfile").c_str() ) ) {
  998. logStream << APPNAME << ": Could not accesss log file: " << cfg->getOption("logfile") << endl;
  999. RemoveLock();
  1000. exit(ERR_EXIT);
  1001. }
  1002. /* I should set the buffers to imediate write, but I just flush on every << operation. */
  1003. }
  1004. /* Relases stdout/err */
  1005. void App::CloseLog(){
  1006. /* Simply closing the log */
  1007. logStream.closeLog();
  1008. }
  1009. string App::findValidRandomTheme(const string& set)
  1010. {
  1011. /* extract random theme from theme set; return empty string on error */
  1012. string name = set;
  1013. struct stat buf;
  1014. if (name[name.length()-1] == ',') {
  1015. name = name.substr(0, name.length() - 1);
  1016. }
  1017. Util::srandom(Util::makeseed());
  1018. vector<string> themes;
  1019. string themefile;
  1020. Cfg::split(themes, name, ',');
  1021. do {
  1022. int sel = Util::random() % themes.size();
  1023. name = Cfg::Trim(themes[sel]);
  1024. themefile = string(THEMESDIR) +"/" + name + THEMESFILE;
  1025. if (stat(themefile.c_str(), &buf) != 0) {
  1026. themes.erase(find(themes.begin(), themes.end(), name));
  1027. logStream << APPNAME << ": Invalid theme in config: "
  1028. << name << endl;
  1029. name = "";
  1030. }
  1031. } while (name == "" && themes.size());
  1032. return name;
  1033. }
  1034. void App::replaceVariables(string& input,
  1035. const string& var,
  1036. const string& value)
  1037. {
  1038. string::size_type pos = 0;
  1039. int len = var.size();
  1040. while ((pos = input.find(var, pos)) != string::npos) {
  1041. input = input.substr(0, pos) + value + input.substr(pos+len);
  1042. }
  1043. }
  1044. /*
  1045. * We rely on the fact that all bits generated by Util::random()
  1046. * are usable, so we are taking full words from its output.
  1047. */
  1048. void App::CreateServerAuth() {
  1049. /* create mit cookie */
  1050. uint16_t word;
  1051. uint8_t hi, lo;
  1052. int i;
  1053. string authfile;
  1054. const char *digits = "0123456789abcdef";
  1055. Util::srandom(Util::makeseed());
  1056. for (i = 0; i < App::mcookiesize; i+=4) {
  1057. word = Util::random() & 0xffff;
  1058. lo = word & 0xff;
  1059. hi = word >> 8;
  1060. mcookie[i] = digits[lo & 0x0f];
  1061. mcookie[i+1] = digits[lo >> 4];
  1062. mcookie[i+2] = digits[hi & 0x0f];
  1063. mcookie[i+3] = digits[hi >> 4];
  1064. }
  1065. /* reinitialize auth file */
  1066. authfile = cfg->getOption("authfile");
  1067. remove(authfile.c_str());
  1068. putenv(StrConcat("XAUTHORITY=", authfile.c_str()));
  1069. Util::add_mcookie(mcookie, ":0", cfg->getOption("xauth_path"),
  1070. authfile);
  1071. }
  1072. char* App::StrConcat(const char* str1, const char* str2) {
  1073. char* tmp = new char[strlen(str1) + strlen(str2) + 1];
  1074. strcpy(tmp, str1);
  1075. strcat(tmp, str2);
  1076. return tmp;
  1077. }
  1078. void App::UpdatePid() {
  1079. std::ofstream lockfile(cfg->getOption("lockfile").c_str(), ios_base::out);
  1080. if (!lockfile) {
  1081. logStream << APPNAME << ": Could not update lock file: " << cfg->getOption("lockfile").c_str() << std::endl;
  1082. exit(ERR_EXIT);
  1083. }
  1084. lockfile << getpid() << std::endl;
  1085. lockfile.close();
  1086. }