/* slimski - Simple Login Manager
   Copyright (C) 1997, 1998 Per Liden
   Copyright (C) 2004-06 Simone Rota <sip@varlock.com>
   Copyright (C) 2004-06 Johannes Winkelmann <jw@tks6.net>

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.
*/

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
#include <cstring>
#include <cstdio>
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <algorithm>
#include <sys/xattr.h>
#include <errno.h>
#include <string>
#include "app.h"
#include "numlock.h"
#include "capslock.h"
#include "util.h"
#include "pwd.h"      //  howdy bub      recheck this

using namespace std;

int conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr){
    *resp = (struct pam_response *) calloc(num_msg, sizeof(struct pam_response));
    Panel* panel = *static_cast<Panel**>(appdata_ptr);
    int result = PAM_SUCCESS;
    for (int i=0; i<num_msg; i++){
        (*resp)[i].resp=0;
        (*resp)[i].resp_retcode=0;
        switch(msg[i]->msg_style){
            //  Each message can have one of four types, specified by the msg_style member of struct pam_message.
            case PAM_PROMPT_ECHO_ON:
                // We assume PAM is asking for the username
                panel->EventHandler(Panel::Get_Name);
                switch(panel->getAction()){
                    case Panel::Halt:
                    case Panel::Reboot:
                        (*resp)[i].resp=strdup("root");  // systemhalt and reboot BOTH demand root pw
                        break;

                    // for these, fallthrough, and respond based on usernamestring
                    case Panel::Suspend:
                    case Panel::Exit:
                    case Panel::Login:
                        (*resp)[i].resp=strdup(panel->GetName().c_str());    //   howdy    s'okay  reached
// exit command does not demand root pw,
//       so we wrap access to exit in a configurable "exit_enabled" bool check
                        break;
//                  default:    //      \________ howdy      is this truly necessary (or is it perhaps counterproductive)
//                      break;  //      /
                }
                break;

            case PAM_PROMPT_ECHO_OFF:       // We assume PAM is asking for the password
                switch(panel->getAction()){
                    case Panel::Exit:
                        // We should leave now!       // howdy bub          no PAM check at handoff
                        result=PAM_CONV_ERR;
                        break;

                    default:
                        panel->EventHandler(Panel::Get_Passwd);
                        (*resp)[i].resp=strdup(panel->GetPasswd().c_str());
                        break;
                }
                break;

            case PAM_ERROR_MSG:
            case PAM_TEXT_INFO:
                logStream << "slimski: " << msg[i]->msg << endl;
                break;
        }
        if (result!=PAM_SUCCESS) break;
    }
    if (result!=PAM_SUCCESS){
        for (int i=0; i<num_msg; i++){
            if ((*resp)[i].resp==0) continue;
            free((*resp)[i].resp);
            (*resp)[i].resp=0;
        };
        free(*resp);
        *resp=0;
    };
    return result;
}


extern App* LoginApp;

int xioerror(Display *disp) {
    LoginApp->RestartServer();
    return 0;
}

void CatchSignal(int sig) {
    logStream << "slimski: unexpected signal " << sig << endl;
    if (LoginApp->isServerStarted())     LoginApp->StopServer();

    LoginApp->RemovePIDLock();
    exit(ERR_EXIT);
}

void User1Signal(int sig) {
    signal(sig, User1Signal);
}



    bool testing = false;
    bool zeebug = false;
    bool wants_popfirst = false;

App::App(int argc, char** argv)
  : pam(conv, static_cast<void*>(&LoginPanel)), mcookiesize(32)   // Must be divisible by 4
{
    int tmp;
    ServerPID = -1;
    ////testing = false;     //  WAS here until I changed it to extern
    serverStarted = false;
    mcookie = string(App::mcookiesize, 'a');
    daemonmode = false;       // howdy   ref:  https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=114366
    force_nodaemon = false;
    firstlogin = true;
    Dpy = NULL;

    while((tmp = getopt(argc, argv, "vszhp:n:ikd?")) != EOF) {  // Note: we force an option for nodaemon switch to handle "-nodaemon"
        switch (tmp) {
        case 'z':
            zeebug = true;       //   pseudo-debug     cout chatty notices
            //
            break;
        case 's':
            showallvarsvals = true;     // by design, only honored in conjunction with -p (testing) option
            break;
        case 'p':
            testtheme = optarg;
            testing = true;
            if (testtheme == "") {    // howdy     should test for NULL here, but the var was declared as a string
                std::cout << "The -p  _theme_testing_  option requires a THEMENAME argument" << std::endl;
                exit(ERR_EXIT);
                // howdy     this is never reached b/c the p: causes optarg parser to intervene (displays generic help msg)
            }
            break;
        case 'i':
            ignorelang = true;  // force loading of "slimski.theme", ignoring presence of any localized variants
            break;              // (useful when testing//previewing themes)
        case 'k':
            wants_popfirst = true;
            break;
        case 'd':
            daemonmode = true;   //  howdy    documented, but probably STILL begs further clarification
            break;
        case 'n':     // d and n are obviously mutually-exclusive; do not bother checking whether BOTH have been requested
            daemonmode = false;
            force_nodaemon = true;  //  As explained via an inline comment within slimski.conf,
            break;                  //  already set true via the initscript installed by the .deb-packaged slimski
        case 'v':
            //     hardcoded version_number here because reading APPNAME from cmake returns  "..0"
            cout << "slimski version 1.5.0" << endl;
            exit(OK_EXIT);
            break;
        case '?':
            std::cout << "use h for help (a terse list of valid options), or see installed docs: /usr/share/docs/slimski/"<< std::endl;
            exit(OK_EXIT);
        case 'h':
            std::cout << "usage:  " << "slimski [option ...] \n"
            << "options: \n"
            << "    -d            daemon mode \n"
            << "    -nodaemon     force no-daemon mode (override initscript) \n"
            << "    -v     show slimski version \n\n"
            << "    -p <themename> \n          test (preview) a specific theme \n\n"
            << "    -s     (only recognized in tandem with -p)\n"
            << "           showall conf+theme variable::value pairs\n" << std::endl;
            exit(OK_EXIT);
            break;
        }
    }

#ifndef XNEST_DEBUG
    ////    (2019) debian sez: "Use of the Xephyr X server instead of Xnest is recommended"   ////
    ////      BUT
    ////    slimski does not use compositing, does not need Xephyr's extra features, and Xephyr has quite a few languishing bug tickets
    ////      AND
    ////    any "testing" is more meaningful when performed in situ, via qemu or virtualbox
    if (getuid() != 0 && !testing) {
        std::cout << "slimski: only root can run this login manager program" << std::endl;
        exit(ERR_EXIT);
    }
#endif // XNEST_DEBUG

    if (daemonmode || force_nodaemon) {
        zeebug = false;
        testing = false;
        showallvarsvals = false;
    }
}




void App::Run() {
    DisplayName = DISPLAY;
    char* p = getenv("DISPLAY");

#ifdef XNEST_DEBUG
    if (p && p[0]) {
        DisplayName = p;
        //cout << "Using the display named " << DisplayName << std::endl;    // useless, eh
        //  howdy   consider removing this
    }
#endif

    if ( testing  &&  p == NULL) {   //   SERVER_NOT_RUNNING
        cout << "slimski 'theme preview mode' is available only during an already running Xsession"  << endl;
        exit(ERR_EXIT);
    }

#ifndef XNEST_DEBUG
    OpenLog();     //   HOWDY BUB     TODO    timestamp the startup logfile entry
#endif

    if (!testing)
        LoginApp->GetPIDLock();  // Create lockfile   GAH! cfgfile not yet loaded, do not yet know path to the configurable lockfile (pathstring now hardcoded)



    try{
        pam.start("slimski");
        pam.set_item(PAM::Authenticator::TTY, DisplayName);
        pam.set_item(PAM::Authenticator::Requestor, "root");

        pam.setenv("XDG_SESSION_CLASS", "greeter");
        //   ^--- howdy bub   late addition
        // (reputedly, per devuan, this enables logind to recognize that we do not fork a separate login session
        //  thereby ensuring logind sessions will be closed immediately, so that we always succeed in getting
        //  a session in case of rapid logout/login cycles)
    }
    catch(PAM::Exception& e){
        logStream << "slimski: " << e << endl;
        exit(ERR_EXIT);
    }


    string themefile = "";   //    themefile has opportunity to override any value specified in conf
    string themedir = "";
    themeName = "";

    cfg = new Cfg;
    string nowconf;
    nowconf = "/etc/slimski.local.conf";
    if (cfg->readConf(nowconf)) {
        if (testing)      cout << "found /etc/slimski.local.conf" << endl;
    } else {
        nowconf = "/etc/slimski.conf";
////    if (cfg->readConf("/etc/slimski.conf") != true) {   /////   DAY-UM!  INEFFECTIVE
        if ( !cfg->readConf(nowconf) ) {
            cout << "cannot load /etc/slimski.conf" << endl;
            //logStream << "slimski: cannot load /etc/slimski.conf" << endl;
            //  howdy    readConf() currently emits a redundant msg in this case      are we performing exit without proper cleanup?
            exit(ERR_EXIT);
        } else {
            if (testing)      cout << "/etc/slimski.local.conf not found.  Loading /etc/slimski.conf instead\n" << endl;
        }
    }


    if (testing) {
        //   howdy bub    themeName (tainted string, received via optarg) begs validation because it can be passed along
        //                (via atlogin_cmd, et al) as %theme variable
        string valid_themename_chars = "0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
        bool notvalid = false;
        int len2 = testtheme.length();
        for (int izzy = 0; izzy < len2; ++izzy){
            notvalid = valid_themename_chars.find(testtheme[izzy]) == string::npos;     //  minus char shown as  0x2d
            if(notvalid) {
                cout << "THEMENAME contains one or more illegal characters" << endl;
                cout << "...or parser found another option, instead of themename, immediately following -p " << endl;
                cout << "list of valid characters:\n     0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" << endl;
                cout << "\nslimski will now exit." << endl;
                exit(ERR_EXIT);
            }
        }
        themeName = testtheme;
    } else {                       //     howdy  could merge the following into the above but, eh
        themeName = cfg->getOption("current_theme");
        string::size_type pos;
        if ( (pos = themeName.find(",") != string::npos)  ||  (pos = themeName.find(" ") != string::npos) ) {
            if (zeebug) {
                cout << "invalid current_theme name: " << themeName << endl;
                cout << "Expected: one WORD (no internal spaces nor punctuation characters)" << endl;
            }
            exit(ERR_EXIT);
        }
    }

    themedir =  "/usr/share/slimski/themes/" + themeName;         // themedir, without trailing slash,  is put to use elsewhere
    themefile = themedir + "/slimski.theme";
    if (!cfg->readConf(themefile)) {
        if ( testing) {
            cout << themefile <<" not found.\nslimski will now exit." << endl;
            exit(ERR_EXIT);
        } else {
            logStream << "slimski: Invalid theme specified in config: " << themeName << endl;
            exit(ERR_EXIT);
        }
    }
    // below, variant (if present, and ignorelang=false) will have opportunity to supply overriding values



    if (ignorelang)
    {
        if ( testing)
            cout << "commandline -i option was specified.  Skipping localized theme variant check." << endl;
    } else {

        ////     howdy   TODO   UTF-8 OUTLIERS (non-dotted and 3char prefix) EXIST  e.g.    gez_ER@abegede  and   nan_TW@latin
        string envlang;
        if (daemonmode) {
            envlang = getenv("BLANG");  //  xref   debian/slimski.init (aka /etc/init.d/slimski)
            if (envlang[0] == '\0') {
                envlang = getenv("LANG");
                if (envlang[0] == '\0') {
                    envlang = "en_US.UTF-8";         //    v---- how might this possibly happen?  idunno
                    if(zeebug)      cout << "LANG env var not set , so falling back to en_US.UTF-8" << endl;
                }
            }
        } else {
            envlang = getenv("LANG");
            if (envlang[0] == '\0') {
                envlang = "en_US.UTF-8";         //    v---- how might this possibly happen?  idunno
                if(zeebug)      cout << "LANG env var not set , so falling back to en_US.UTF-8" << endl;
            }
        }
        cout << "envlang received as: " << envlang << endl;
        logStream << "envlang received as: " << envlang << endl;

        string wantedlang; // -----  do not declare as initially empty (because its type would become stdstring, vs c11string)
        string::size_type pos; //   MUST class
        int len = envlang.length();        //   en_US.UTF    correctly reported 11chars      //   multibyte     strlen("ø") = 2

        if ( len > 1  &&  ( (pos = envlang.find(".")) != string::npos)  &&  len < 14 ) {
            wantedlang = envlang.substr(0, pos);
        } else if ( len > 1  &&  len < 14 ) {             // could be "C"
            wantedlang = envlang.substr(0, len);   // recognized a non-dotted langstring
        }

        wantedlang = wantedlang + "\0";
        int len2 = wantedlang.length();
        string valid_chars = "_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
        bool badstring = false;
        for (int it = 0; it < len2; ++it) {
            badstring = valid_chars.find(wantedlang[it]) == string::npos;     //  minus char shown as  0x2d
            if(badstring) {
                wantedlang = "";
                if (zeebug)
                    cout << "wantedlang contains illegal char " << wantedlang[it] << " so rejecting" << endl;
            }
        }

        string locthemefile;
        int lenn = wantedlang.length();
        if ( !badstring  && lenn > 1  && lenn < 6 ) {
            locthemefile = "/usr/share/slimski/themes/" + themeName + "/" + wantedlang;
            //~ if (testing)
            //~ {
                if ( cfg->readConf(locthemefile) ) {
                    cout << "found and loaded a localized themefile: " << locthemefile << endl;
                } else {
                    cout << "localized themefile would be: " << locthemefile << endl;
                    cout << "...but no matching locale-specific theme file is present\n" << locthemefile << endl;
                }
            //~ }
        }
    } // END if ignorelang








    if (!testing) {     //   howdy      ultimately, "preview" is probably a more suitable varname
        // start the X server

        setenv("DISPLAY", DisplayName, 1);
        signal(SIGQUIT, CatchSignal);
        signal(SIGTERM, CatchSignal);
        signal(SIGKILL, CatchSignal);
        signal(SIGINT, CatchSignal);
        signal(SIGHUP, CatchSignal);
        signal(SIGPIPE, CatchSignal);
        signal(SIGUSR1, User1Signal);

        //////////////  hmmmmmmm   "testing" and "XNEST_DEBUG" are mutually-exclusive  (and testing and daemonmode are, already, mutually exclusive)
#ifndef XNEST_DEBUG
        if (!force_nodaemon && cfg->getOption("daemonmode_enabled") == "true")
            daemonmode = true;

        if (daemonmode) {
            //   syscall daemon()  forks, reparents to PID1, closes fds, changes pwd to /, calls setsid(), redirects std{in,out,err} to /dev/null
            if (daemon(0, 0) == -1) {             //             https://man7.org/linux/man-pages/man3/daemon.3.html
                logStream << "slimski: " << strerror(errno) << endl;
                exit(ERR_EXIT);
            }
        }

        if (daemonmode)
            UpdatePid();   // howdy   initscript specifies use daemonmode. Do we ever observe the pid changing?

        CreateServerAuth();
        StartServer();
        //  howdy      at this codepoint (or after the endif?), some slim forks would exec a configurable "xsetup_cmd"
        //             toward support Optimus-based nvidia drivers which must execute two xrandr commands in order for
        //             Xorg to render on screen correctly
#endif
    }




    // Open display
    if((Dpy = XOpenDisplay(DisplayName)) == 0) {
        logStream << "slimski: could not open display '" << DisplayName << "'" << endl;
        if (!testing)
            StopServer();      // (inherited)   howdy bub     why should this hinge on testing?

        exit(ERR_EXIT);
    }

    Scr = DefaultScreen(Dpy);
    Rootwin = RootWindow(Dpy, Scr);

    // Internal _XROOTPMAP_ID property
    BackgroundPixmapId = XInternAtom(Dpy, "_XROOTPMAP_ID", False);



    Visual* visual = DefaultVisual(Dpy, Scr);
    Colormap colormap = DefaultColormap(Dpy, Scr);
    //XftColorAllocName(Dpy, visual, colormap, cfg->getOption("background_color").c_str(), &smackgroundcolor); // xref backgroundcolor

    if (testing) {    // for tests we paint to a standard window instead of actual rootwin
        Window RealWoot = RootWindow(Dpy, Scr);
      //Rootwin = XCreateSimpleWindow(Dpy, RealWoot, 0, 0, 1280, 1024, 0, 0, 0);   // howdy bub   screen height x width (can go fullscreen, vs fixed size)
        Rootwin = XCreateSimpleWindow( Dpy, RealWoot, 0, 0,
                    XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)),
                    XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)),
                    0, GetColor(cfg->getOption("background_color").c_str()), GetColor(cfg->getOption("background_color").c_str()) );
                  //0,  GetColor("smackgroundcolor"),  GetColor("smackgroundcolor") );
        XMapWindow(Dpy, Rootwin);
        XFlush(Dpy);
    } else {
        blankScreen();
    }




    HideMouseCursor();
    if (showallvarsvals)    cfg->printAllOptvals();

    bool autologin = cfg->getOption("autologin_enabled")=="true";  // howdy   miserable syntax
    if (!autologin  &&  wants_popfirst) {
            if (cfg->getOption("popfirst_cmd") != "") {
                //Util::runCommand( cfg->getOption("popfirst_cmd"), "");  // blank string arg here is intentional
                system( cfg->getOption("popfirst_cmd").c_str() );
                string bogus = cfg->getOption("popfirst_cmd");
                if (testing)
                    cout << "---- executing popfirst_cmd : " << bogus.c_str() << endl;
            } else {
                if (testing)
                    cout << "----  wants_popfirst=true... BUT popfirst_cmd is BLANK" << endl;
            }
    }

    if (cfg->getOption("numlock_enabled") == "true")    NumLock::setOn(Dpy);
    else if (cfg->getOption("numlock_enabled") == "false")   NumLock::setOff(Dpy);

     // Create panel
    LoginPanel = new Panel(Dpy, Scr, Rootwin, cfg, themedir);
    bool firstloop = true; // 1st time panel is shown (for automatic username)
    bool focuspass = cfg->getOption("passwdfocus_enabled")=="true";
    if ( cfg->getOption("default_user") == "root"  ||  cfg->getOption("default_user") == "" ) {  // this codepoint not reached when autologin_enabled=true
        focuspass =  false;      // without this line... grief ensues
    }

    if (firstlogin  &&  cfg->getOption("default_user") != "root"  &&  cfg->getOption("default_user") != "" ) {
        LoginPanel->SetName(cfg->getOption("default_user") );
        pam.set_item(PAM::Authenticator::User, cfg->getOption("default_user").c_str());
        firstlogin = false;
        if ( autologin ){
            Login();   //  WAS   DoLogin()   reverted
/**   //       v---^
            try {   //   howdy bub     this try/catch is a late edit ( along with adding PAM::check_acct() )
                pam.check_acct();    //     added n removed     howdy bub     retest or nixme
                Login();  //  WAS   DoLogin()   reverted
            }
            catch(PAM::Auth_Exception& e){ }                  // The specified default user is invalid
*/
        }
    }

    // Start looping
    int panelclosed = 1;
    Panel::ActionType Action;

    while(1) {
        if(panelclosed) {
            setBackground(themedir);  // Initialize roooooot window

            if (!testing) {
                KillAllClients(False);  // Close all clients
                KillAllClients(True);
            }
            LoginPanel->OpenPanel(themedir);  // Show panel
        }

        LoginPanel->ResetName();
        LoginPanel->ResetPasswd();

        if (firstloop &&  cfg->getOption("default_user") != "root"  &&  cfg->getOption("default_user") != "") {
            LoginPanel->SetName( cfg->getOption("default_user") );
        }

        if (!AuthenticateUser(focuspass && firstloop)){
            panelclosed = 0;
            firstloop = false;
            if (CapsLock::checkCapslock(Dpy)) {
                LoginPanel->showMuMsg(cfg->getOption("password_feedback_capslock_msg"), 2);
            } else {
                LoginPanel->showMuMsg(cfg->getOption("password_feedback_msg"), 2);
            }

            LoginPanel->ClearPanel();   //   howdy        still necessary   (   it calls ResetPasswd()    )

            //sleep(1); // howdy     may be advisable here, to preclude infinite loops without pauses
            continue;
        }
/**
////////////////////// works but... placed here, does not transpire until user has successfully authenticated
        int ok = system("bash -c 'which yad'");       // surprising that this "works"
        if (ok == 0) {
            ////system("touch /tmp/bleep");    // would be unsafe to passthru user-supplied commandstring
//          ////  system("bash -c 'yad --maximized --color&'");   // -------   NO, AMPERSAND TO SEND TO BG IS PROBABLY INADVISABLE
            //     (probably, if used elsewhere for helpviewer) beneficial to FULLSCREEN maximize the helpviewer app window
            //     b/c   slimski conf may have specified "swallow mouse cursor"
            //     and   a/the still-open helper app window can become "lost" behind the fullscreen slimski window
            //     (user would discover helper app window still in place after successful login?)
        } else {
            if (testing)
                cout << "the system reported NotFound for the specified external command" << endl;
        }

        ///////   ^---- the above devnote is moot. We do NOT invoke system() and we do not attempt to assess "command not found"
        //Util::runCommand("yad --maximized --color", "");
        system( "yad --maximized --color" );

*/
        firstloop = false;   // this makes the conditional assignment (above) superfluous

        Action = LoginPanel->getAction();
        // for themes test we just quit
//      if ( testing) {
//          Action = Panel::Exit;       ////////////////////   howdy bub    revisit this (we would lose ability to test exit_enabled?)
//      }                                                     //       mmmm  could insert a conditional to test right here
        panelclosed = 1;
        LoginPanel->ClosePanel();

        switch(Action) {
            case Panel::Login:
                Login();  //  WAS   DoLogin()   reverted
                break;
            case Panel::Reboot:
                Reboot();      // yes pam check
                break;
            case Panel::Halt:
                Halt();        // yes pam check
                break;
            case Panel::Suspend:
                Suspend();     // NO pam check
                break;
            case Panel::Exit:
                Exit();        // no pam check ~~ availability is governed via "exit_enabled" conf option
                break;
//          default:
//              break;
        }
    } //    end WHILE
}


bool App::AuthenticateUser(bool focuspass){
    // Reset the username
    try{
        if (!focuspass)    //  howdy bub      conditionally forced to false, above
            pam.set_item(PAM::Authenticator::User, 0);
        pam.authenticate();
    }
    catch(PAM::Auth_Exception& e){
        switch(LoginPanel->getAction()){  //     howdy    PAM rules govern whether or not user|r00t can perform the action
            case Panel::Exit:
                return true; // <--- we emulate successful auth
            default:
                break;
        }
        logStream << "slimski: " << e << endl;
        return false;
    }
    catch(PAM::Exception& e){
        logStream << "slimski: " << e << endl;
        exit(ERR_EXIT);
    };
    return true;
}


void App::HideMouseCursor() {      //    not truly hidden, just 1x1px  tiny
    if(cfg->getOption("mouse_enabled") != "true") {
        XColor            black;
        char              cursordata[1];
        Pixmap            cursorpixmap;
        Cursor            cursor;
        cursordata[0]=0;
        cursorpixmap=XCreateBitmapFromData(Dpy,Rootwin,cursordata,1,1);
        black.red=0;
        black.green=0;
        black.blue=0;
        cursor=XCreatePixmapCursor(Dpy,cursorpixmap,cursorpixmap,&black,&black,0,0);
        XDefineCursor(Dpy,Rootwin,cursor);
    }
}


void App::Login() {    //  WAS   DoLogin()   reverted
    struct passwd *pw;
    pid_t pid;   // signed 32bit int

    try{
        pam.open_session();
        pw = getpwnam(static_cast<const char*>(pam.get_item(PAM::Authenticator::User)));
    }       //     ^--- returns: home directory, preferred shell, etc
    catch(PAM::Cred_Exception& e){
        logStream << "slimski: " << e << endl;  // Credentials could not be established
        return;
    }
    catch(PAM::Exception& e){
        logStream << "slimski: " << e << endl;
        exit(ERR_EXIT);
    };

    endpwent();
    if(pw == 0)
        return;
    if (pw->pw_shell[0] == '\0') {
        setusershell();     // rewind the line pointer from /etc/shells
        strcpy(pw->pw_shell, getusershell());   // the .profile shell of the user (not the id of the shell slimski was launched from)
        endusershell();                         //
    }

    // Setup the environment
    char* term = getenv("TERM");
    string maildir = _PATH_MAILDIR;   //  howdy   a login manager just passes this onward...
    maildir.append("/");
    maildir.append(pw->pw_name);
    string xauthority = pw->pw_dir;
    xauthority.append("/.Xauthority");

    // Setup the PAM environment
    //      (noted on antiX17+19) all lines within this file are outcommented:  /etc/security/pam_env.conf
    //                     including lines which would export values for PAGER,MANPAGER,LESS,NNTPSERVER
    try{
        if(term) pam.setenv("TERM", term);
        pam.setenv("HOME", pw->pw_dir);
        pam.setenv("PWD", pw->pw_dir);
        pam.setenv("SHELL", pw->pw_shell);
        pam.setenv("USER", pw->pw_name);
        pam.setenv("LOGNAME", pw->pw_name);
        pam.setenv("PATH", cfg->getOption("default_path").c_str());
        pam.setenv("DISPLAY", DisplayName);
        pam.setenv("MAIL", maildir.c_str());
        pam.setenv("XAUTHORITY", xauthority.c_str());
    }
    catch(PAM::Exception& e){
        logStream << "slimski: " << e << endl;
        exit(ERR_EXIT);
    }
/**        ////////        withheld for smoketesting      (may need to  pam.getenv()  to access the content)
    if (zeebug) {
        cout << "_____ setting env variables _____" << endl;
        cout << "          USER : "  << pw->pw_dir    << endl;
        cout << "       LOGNAME : "  << pw->pw_dir    << endl;
        cout << "          HOME : "  << pw->pw_dir    << endl;
        cout << "           PWD : "  << pw->pw_dir    << endl;
        cout << "         SHELL : "  << pw->pw_shell  << endl;
        cout << "          PATH : "  << cfg->getOption("default_path").c_str() << endl;
        cout << "       DISPLAY : "  << DisplayName   << endl;
        cout << "          MAIL : "  << maildir.c_str()         << endl;
        cout << "    XAUTHORITY : "  << xauthority.c_str()      << endl;
        if(term)    cout << "          TERM : " << term << endl;
    }
*/
    if (!testing) {
        pid = fork();          // Create new process
    }

    if(pid == 0 ||  testing) {
        string sessiontype = LoginPanel->getSessiontype();
        string atloginCommand = cfg->getOption("atlogin_cmd");

        string dest = pw->pw_dir;
        dest = dest + "/.config/slimski.lastused";
        if ( zeebug ) {
            cout << "on-the-way-out sessiontype (actual) is: " << sessiontype.c_str() << endl;
            cout << "       ^---> writing: " << dest << endl;
        }
        ofstream luwfile(dest);
        luwfile << sessiontype;       // update the content of the lastused flagfile
        luwfile.close();              //     howdy    consider suppressing this during testing

// ----             not necessary during testing, but no harm (and need to define Swu at this scope)
        // Get a copy of the environment and close the child's copy of the PAM handle
        char** child_env = pam.getenvlist();

        // login process starts here
        SwitchUser Swu(pw, cfg, DisplayName, child_env);
             //  ^---- howdy bub      we have ability to drop elevated permissions if necessary     ref  SwitchUser::Execute()
             //
             //  Currently, we do not utilize any of the switchuser methods (fns), but...
             //      could create a limited group//user account post-install during package installation
             //      and ExitAs that limited-priv user, eh
// ----


        if (!testing) {
            //       During testing, atloginCommand is not used.
            replaceVariables(atloginCommand, SESSIONTYPE_VAR, sessiontype);   //   %sessiontype
            replaceVariables(atloginCommand, THEME_VAR, themeName);
        }

        string sessStart = cfg->getOption("sessionstart_cmd");
        if (sessStart != "") {
            replaceVariables(sessStart, USER_VAR, pw->pw_name);
            if ( testing) {
                cout << "sessStart commandstring passed to system would be:\n    " << sessStart.c_str() << endl;
            } else {
                system(sessStart.c_str());     //  e.g.    /usr/bin/sessreg -a -l $DISPLAY %username
            }
        } else {
            //    howdy bub      should we check blank, and fail, at an earlier codepoint?
            if (testing)
                cout << "sessionstart_cmd has not been configured, is blank"  << endl;
        }

#ifndef XNEST_DEBUG
        CloseLog();
#endif
        if (!testing) {
            // howdy   previously we did not reach this point during testing anyhow ...except in the case of autologin=true (whoopsie)
            Swu.Login(atloginCommand.c_str(), mcookie.c_str());
        } else {
            cout << "slimski preview mode: successful 'login' action performed" << endl;
            try                     {     pam.end();          }     //          howdy bub         to exit
            catch(PAM::Exception& e){     logStream << "slimski: " << e << endl;           }

            // ------------ during preview mode, this is displayed upon successful login
            //~ const char* teststring= "test: UTF8 chars ¥·£·€·$·¢·₡·₢·₣·₤·₥·₦·₧·₨·₩·₪·₫·₭·₮·₯·₹";
            const char* teststring= "Test: Starting session...";
            LoginPanel->Message(teststring);
            sleep(2);
            delete LoginPanel;
            delete cfg;
            XCloseDisplay(Dpy);  //  howdy bub      check whether PIDlock was created during testing
            cout << "finished testing... slimski will now exit" << endl;
        }
        _exit(OK_EXIT);
    }

#ifndef XNEST_DEBUG
    CloseLog();
#endif

    if ( testing) {
        cout << "SHOULD NOT REACH THIS POINT DURING TESTING" << endl;
        exit(OK_EXIT);    ////////////////////////////////////////
    }

    pid_t wpid = -1;
    int status;
    while (wpid != pid) {  // Wait until user is logging out (login process terminates)
        wpid = wait(&status);
        if (wpid == ServerPID)      xioerror(Dpy); // Server died, simulate IO error
    }
    if (WIFEXITED(status) && WEXITSTATUS(status)) {
        LoginPanel->showMuMsg(cfg->getOption("login_cmd_failed_msg"), 2);
    } else {
         string sessStop = cfg->getOption("sessionstop_cmd");
         if (sessStop != "") {
            replaceVariables(sessStop, USER_VAR, pw->pw_name);
            system(sessStop.c_str());     // e.g.      /usr/bin/sessreg -d -l $DISPLAY %username
        }
    }

    try                     {  pam.close_session(); }
//  catch(PAM::Exception& e){  logStream << "slimski: " << e << endl; }
//    LOG HAS BEEN CLOSED AT THIS POINT
    catch(PAM::Exception& e){ }


    // Close all clients
    KillAllClients(False);
    KillAllClients(True);
    killpg(pid, SIGHUP);  // Send HUP signal to clientgroup
    if(killpg(pid, SIGTERM))     killpg(pid, SIGKILL);  // Send TERM signal to clientgroup, if error send KILL

    HideMouseCursor();

#ifndef XNEST_DEBUG
    OpenLog();       // reactivate log file
    RestartServer();
#endif
}


void App::Reboot() {
/**       //    v------- moved, to Panel::OnKeyPress() to intervene before an auth attempt occurs
    if (cfg->getOption("reboot_enabled") != "true") {     // howdy    redundant
        LoginPanel->showMuMsg(cfg->getOption("disabledviaconf_msg"), 2);
        return;
    }
    if ( testing) {
        string boba ="reboot_enabled=true  BUT THIS ACTION IS UNAVAILABLE DURING PREVIEW";
        LoginPanel->showMuMsg(boba, 3);
        return;
    }
*/
    try{      pam.end();   }
    catch(PAM::Exception& e){   logStream << "slimski: " << e << endl;    }

    LoginPanel->Message((char*)cfg->getOption("reboot_msg").c_str());
    sleep(2);
    StopServer();
    RemovePIDLock();
    system(cfg->getOption("reboot_cmd").c_str());
    exit(OK_EXIT);
}


void App::Halt() {
/**       //    v------- moved, to Panel::OnKeyPress() to intervene before an auth attempt occurs
    if (cfg->getOption("systemhalt_enabled") != "true") {
        LoginPanel->showMuMsg(cfg->getOption("disabledviaconf_msg"), 2);
        return;
    }
    if ( testing) {
        string boba = "systemhalt_enabled=true  BUT THIS ACTION IS UNAVAILABLE DURING PREVIEW";
        LoginPanel->showMuMsg(boba, 3);
        return;   // --------------- added for safety, during betatesting.   followup: nixme
    }
*/
    try{      pam.end();   }
    catch(PAM::Exception& e){   logStream << "slimski: " << e << endl;    }

    LoginPanel->Message((char*)cfg->getOption("systemhalt_msg").c_str(),1);
    sleep(2);
    StopServer();
    RemovePIDLock();
    system(cfg->getOption("systemhalt_cmd").c_str());
    exit(OK_EXIT);
}


void App::Suspend() {
    sleep(1);
    if (cfg->getOption("suspend_cmd") != "") {
        system(cfg->getOption("suspend_cmd").c_str());
        // no feedback provided in case where conf file contains an invalid cmdstring
        // and no enable|disable toggle provided (conf can leave blank if unwanted)
    }
}



void App::Exit() {    //  v--- currently the caller, Panel::OnKeyPress(), (PERHAPS INEFFECTIVELY) prevents exit during testing
    if (cfg->getOption("exit_enabled") != "true") {        // howdy     we could/might also accept "yes" here
        LoginPanel->showMuMsg(cfg->getOption("disabledviaconf_msg"), 2);
        return;
    }

    try{      pam.end();   }
    catch(PAM::Exception& e){   logStream << "slimski: " << e << endl;    }

    delete LoginPanel;      //   howdy    not showing teststring ?
    StopServer();
    RemovePIDLock();
    delete cfg;
    exit(OK_EXIT);
}



////////////////////   uninteresting differences below this point




int CatchErrors(Display *dpy, XErrorEvent *ev) {
    //   howdy     todo     why no loggie?    just stands as "assigned XSetErrorHandler"
    return 0;
}


void App::RestartServer() {
    try{      pam.end();   }
    catch(PAM::Exception& e){   logStream << "slimski: " << e << endl;    }

    StopServer();
    RemovePIDLock();
    while (waitpid(-1, NULL, WNOHANG) > 0); // Collects all dead child processes
    Run();
}


void App::KillAllClients(Bool top) {
    Window dummywindow;
    Window *children;
    unsigned int nchildren;
    unsigned int i;
    XWindowAttributes attr;

    XSync(Dpy, 0);
    XSetErrorHandler(CatchErrors);

    nchildren = 0;
    XQueryTree(Dpy, Rootwin, &dummywindow, &dummywindow, &children, &nchildren);
    if(!top) {
        for(i=0; i<nchildren; i++) {
            if(XGetWindowAttributes(Dpy, children[i], &attr) && (attr.map_state == IsViewable))
                children[i] = XmuClientWindow(Dpy, children[i]);
            else
                children[i] = 0;
        }
    }

    for(i=0; i<nchildren; i++) {
        if(children[i])
            XKillClient(Dpy, children[i]);
    }
    XFree((char *)children);

    XSync(Dpy, 0);  // zero instructs XSync to NOT discard all events in the event queue
    XSetErrorHandler(NULL);
}


int App::ServerTimeout(int timeout, char* text) {
    int    i = 0;
    int pidfound = -1;
    static char    *lasttext;

    for(;;) {
        pidfound = waitpid(ServerPID, NULL, WNOHANG);
        if(pidfound == ServerPID)
            break;
        if(timeout) {
            if(i == 0 && text != lasttext)
                logStream << "slimski: waiting for " << text << endl;
            else
                logStream << "." << endl;
        }
        if(timeout)
            sleep(1);
        if(++i > timeout)
            break;
    }

    if(i > 0)
        logStream << endl;

    lasttext = text;
    return (ServerPID != pidfound);
}


int App::WaitForServer() {
    int    ncycles     = 120;
    int    cycles;

    for(cycles = 0; cycles < ncycles; cycles++) {
        if((Dpy = XOpenDisplay(DisplayName))) {
            XSetIOErrorHandler(xioerror);
            return 1;
        } else {
            if(!ServerTimeout(1, (char *) "X server to begin accepting connections"))
                break;
        }
    }

    logStream << "Giving up." << endl;
    return 0;
}


int App::StartServer() {
    ServerPID = fork();

    static const int MAX_XSERVER_ARGS = 256;
    static char* server[MAX_XSERVER_ARGS+2] = { NULL };
    server[0] = (char *)cfg->getOption("default_xserver").c_str();
    string argOption = cfg->getOption("xserver_arguments");
    //  Add mandatory -xauth option
    argOption = argOption + " -auth " + cfg->getOption("authfile");
    char* args = new char[argOption.length()+2]; // NULL plus vt
    strcpy(args, argOption.c_str());

    serverStarted = false;

    int argc = 1;
    int pos = 0;
    bool hasVtSet = false;
    while (args[pos] != '\0') {
        if (args[pos] == ' ' || args[pos] == '\t') {
            *(args+pos) = '\0';
            server[argc++] = args+pos+1;
        } else if (pos == 0) {
            server[argc++] = args+pos;
        }
        ++pos;

        if (argc+1 >= MAX_XSERVER_ARGS) {
            // ignore _all_ arguments to make sure the server starts at all
            argc = 1;
            break;
        }
    }

    for (int i=0; i<argc; i++) {
        if (server[i][0] == 'v' && server[i][1] == 't') {
            bool ok = false;
            Cfg::string2int(server[i]+2, &ok);
            if (ok) {
                hasVtSet = true;
            }
        }
    }
                   //  check xnest//zephyr
    if (!hasVtSet && daemonmode) {
        server[argc++] = (char*)"vt07";  //  howdy bub
    }
    server[argc] = NULL;

    switch(ServerPID) {
    case 0:
        signal(SIGTTIN, SIG_IGN);
        signal(SIGTTOU, SIG_IGN);
        signal(SIGUSR1, SIG_IGN);
        setpgid(0,getpid());

        ////CloseLog();     // late change, toward avoid leaking fd      howdy   reverted (does not seem sensible prior to stream call)
        execvp(server[0], server);
        logStream << "slimski: X server could not be started" << endl;
        exit(ERR_EXIT);
        break;

    case -1:
        break;

    default:
        errno = 0;
        if(!ServerTimeout(0, (char *)"")) {
            ServerPID = -1;
            break;
        }

        // Wait for server to start up
        if(WaitForServer() == 0) {
            logStream << "slimski: unable to connect to X server" << endl;
            StopServer();
            ServerPID = -1;
            exit(ERR_EXIT);
        }
        break;
    }

    delete [] args;
    serverStarted = true;
    return ServerPID;
}


//   Ugly hack, but...
//   XCloseDisplay (in StopServer) might call the IOErrorHandler (IgnoreXIO)
//   When that handler returns the program aborts, though
//   so we save the state before calling XCloseDisplay using setjmp and
//   restore it in the handler using longjmp
static jmp_buf CloseEnv;
static int IgnoreXIO(Display *d) {
    logStream << "slimski: connection to X server lost." << endl;
    longjmp(CloseEnv, 1);
}


void App::StopServer() {
    signal(SIGQUIT, SIG_IGN);
    signal(SIGINT, SIG_IGN);
    signal(SIGHUP, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
    signal(SIGTERM, SIG_DFL);
    signal(SIGKILL, SIG_DFL);

    // Catch X error
    XSetIOErrorHandler(IgnoreXIO);
    if(setjmp(CloseEnv) == 0 && Dpy != nullptr)
        XCloseDisplay(Dpy);

    // Send HUP to process group
    errno = 0;
    if((killpg(getpid(), SIGHUP) != 0) && (errno != ESRCH))
        logStream << "slimski: cannot send HUP to the process group " << getpid() << endl;

    // Send TERM to server
    if(ServerPID < 0)
        return;
    errno = 0;
    if(killpg(ServerPID, SIGTERM) < 0) {
        if(errno == EPERM) {
            logStream << "slimski: cannot kill X server" << endl;
            exit(ERR_EXIT);
        }
        if(errno == ESRCH)
            return;
    }

    // Wait for server to shut down           howdy bub      Do we require an EXACT matcht here?
    if(!ServerTimeout(10, (char *)"X server to shut down")) {
//      ^---v   changed, then reverted
//  if(!ServerTimeout(10, (char *)"waiting for X server to shut down")) {
        logStream << endl;
        return;
    }

    logStream << "slimski:  X server slow to shut down, sending KILL signal." << endl;

    // Send KILL to server
    errno = 0;
    if(killpg(ServerPID, SIGKILL) < 0) {
        if(errno == ESRCH)
            return;
    }

    // Wait for server to die         howdy bub      Do we require an EXACT match here?
//  if(ServerTimeout(3, (char*)"waiting for server process to die")) {
//      ^---v   changed, then reverted
    if(ServerTimeout(3, (char*)"server to die")) {
        logStream << "slimski: cannot kill server" << endl;
        exit(ERR_EXIT);
    }
    logStream << endl;
}


//   currently unused
void App::blankScreen() {
    GC gc = XCreateGC(Dpy, Rootwin, 0, 0);
    XSetForeground(Dpy, gc, BlackPixel(Dpy, Scr));
    XFillRectangle(Dpy, Rootwin, gc, 0, 0, XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)));
    XFlush(Dpy);
    XFreeGC(Dpy, gc);
}


void App::setBackground(const string& themedir) {
    string filename;
    filename = themedir + "/background.png";
    bimage = new Image;
    bool bimgloaded = bimage->Read(filename.c_str());
    if (!bimgloaded){
        filename = themedir + "/background.jpg";
        bimgloaded = bimage->Read(filename.c_str());
    }

    string bgstyle = cfg->getOption("background_style");
    string hexivalue = cfg->getOption("background_color");
    hexivalue = hexivalue.substr(1,6);

    Visual* visual = DefaultVisual(Dpy, Scr);
    Colormap colormap = DefaultColormap(Dpy, Scr);
    XftColorAllocName(Dpy, visual, colormap, cfg->getOption("background_color").c_str(), &backgroundcolor);

    if (!bimgloaded  ||  bgstyle == "solidcolor") {
        Window RealWoot = RootWindow(Dpy, Scr);
        Rootwin = XCreateSimpleWindow( Dpy, RealWoot, 0, 0,
                    XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)),
                    XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)),
                    0,  GetColor("backgroundcolor"),  GetColor("backgroundcolor") );

    } else {
        if (bgstyle == "stretch" || bgstyle == "stretched") {   // skidoo   late change   (undocumented)
            bimage->Resize(XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)));
        } else if (bgstyle == "tile" || bgstyle == "tiled") {
            bimage->Tile(XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)));
        } else if (bgstyle == "center" || bgstyle == "centered") {
            bimage->Center(XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)), hexivalue.c_str());
        } else {  //  solidcolor or badvalue
            bimage->Center(XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)), hexivalue.c_str());
        }
        Pixmap p = bimage->createPixmap(Dpy, Scr, Rootwin);
        XSetWindowBackgroundPixmap(Dpy, Rootwin, p);
        XChangeProperty(Dpy, Rootwin, BackgroundPixmapId, XA_PIXMAP, 32, PropModeReplace, (unsigned char *)&p, 1);
    }


/**    xref     panel.cpp  Line 178
    // ----------------------  INEFFECTIVE    B/C   PANEL RE-LOADS BGIMAGE AND REDRAWS ROOT WINDOW.
    //                         Also, if injected here, "LOGO"  WOULD BE DISPLAYED FOR THE 3 SECONDS DURING PREVIEW, FOLLOWING SUCCESSFUL PSEUDOLOGIN
    // ----------------------               OR
    //                         COULD BE REPURPOSED TO DISPLAY A GRAPHIC FOLLOWING ACTUAL SUCCESSFUL LOGIN
    //                         BY INSERTING A   sleep()  DELAY
    showlogo = cfg->getOption("showlogo_enabled")=="true";
    string logofile = themedir + "/logo.png";
    logoimage = new Image;
    bool limloaded = logoimage->Read( logofile.c_str() );
    if (showlogo) {
        if (!limloaded) {
            logofile = themedir + "/logo.jpg";
            limloaded = logoimage->Read(logofile.c_str());
            if (!limloaded  &&  testing ) {
                cout << "slimski: could not load a logo image for the specified theme '"
                     << basename((char*)themedir.c_str()) << "'" << std::endl;
            }
        } else {
            string cfgX = cfg->getOption("logo_x");
            string cfgY = cfg->getOption("logo_y");
            Xog = Cfg::absolutepos(cfgX, XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), logoimage->Width());
            Yog = Cfg::absolutepos(cfgY, XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)), logoimage->Height());
          //logoimage->Merge(bimage, Xog, Yog);
            logoimage->Merge(bimage, 100, 100);    //  grabbing at straws here
            //LogoPixmap = logoimage->createPixmap(Dpy, Scr, Rootwin);   // no effect
        }
    }
    //------------------
*/
    XClearWindow(Dpy, Rootwin);
    XFlush(Dpy);
    //if (bimgloaded) {
    delete bimage;
    //}
    ////delete logoimage;
}


unsigned long App::GetColor(const char* colorname) {   // redundant. Should be public and called by Panel fns
    XColor color;
    XWindowAttributes attributes;

    XGetWindowAttributes(Dpy, Rootwin, &attributes);
    color.pixel = 0;

    if(!XParseColor(Dpy, attributes.colormap, colorname, &color))
        logStream << "slimski: App::GetColor cannot parse color " << colorname << endl;
    else if(!XAllocColor(Dpy, attributes.colormap, &color))
        logStream << "slimski: App::GetColor cannot allocate color " << colorname << endl;

    return color.pixel;
}


// Check if there is a lockfile and a corresponding process
void App::GetPIDLock() {
    std::ifstream lockfile("/var/run/slimski.pidlock");      // howdy    shadowed declaration
    if (!lockfile) {
        std::ofstream lockfile("/var/run/slimski.pidlock", ios_base::out);
        if (!lockfile) {
            logStream << "slimski: Could not create lockfile:  /var/run/slimski.pidlock" << endl;
            exit(ERR_EXIT);
        }
        lockfile << getpid() << std::endl;
        lockfile.close();
    } else {
        // lockfile present, read pid from it
        int pid = 0;
        lockfile >> pid;
        lockfile.close();
        if (pid > 0) {
            // see if process with this pid exists
            int ret = kill(pid, 0);
            if (ret == 0 || (ret == -1 && errno == EPERM) ) {
                logStream << "slimski: Another instance of the program is already running ~~ PID " << pid << endl;
                exit(0);
            } else {
                logStream << "slimski: Stale lockfile found, removing it" << endl;
                std::ofstream lockfile("/var/run/slimski.pidlock", ios_base::out);
                if (!lockfile) {
                    logStream << "slimski could not create new lockfile:  /var/run/slimski.pidlock" << endl;
                    exit(ERR_EXIT);
                }
                lockfile << getpid() << std::endl;
                lockfile.close();
            }
        }
    }
}


void App::RemovePIDLock() {
    remove("/var/run/slimski.pidlock");  // Remove lockfile   (howdy bub, recheck to ensure that we are, elsewhere, also closing the logfile)
}


void App::UpdatePid() {
    std::ofstream lockfile("/var/run/slimski.pidlock", ios_base::out);
    if (!lockfile) {
        logStream << "slimski: Could not update lockfile:  /var/run/slimski.pidlock" << std::endl;
        exit(ERR_EXIT);
    }
    lockfile << getpid() << std::endl;
    lockfile.close();
}


bool App::isServerStarted() {
    return serverStarted;   // Get server start check flag
}


// Redirect stdout and stderr to logfile
void App::OpenLog() {
    if (testing) {
        string estlo = "/tmp/slimski_"+Util::getCurrentDateTime("now")+".log";
        char testlog[estlo.size() + 1];
        strcpy(testlog, estlo.c_str());
        if (!logStream.openLog(testlog)) {
            cout << "slimski: Could not create tmp logfile" << endl;
            exit(ERR_EXIT);
        }
    } else {   // could set the buffers to immediate write, instead of just flushing on every << operation.
        if ( !logStream.openLog("/var/log/slimski.log") ) {
            cout << "slimski: Could not access log file:  /var/log/slimski.log" << endl;
            RemovePIDLock();
            exit(ERR_EXIT);
        }
    }
}


void App::CloseLog(){
    logStream.closeLog();  // Releases stdout/err
}


void App::replaceVariables(string& input, const string& var, const string& value) {
    string::size_type pos = 0;
    int len = var.size();
    while ((pos = input.find(var, pos)) != string::npos) {
        input = input.substr(0, pos) + value + input.substr(pos+len);
    }
}



//  We rely on the fact that all bits generated by Util::random()
//  are usable, so we are taking full words from its output.
void App::CreateServerAuth() {
    //   create mit cookie
    uint16_t word;
    uint8_t hi, lo;
    int i;
    string authfile;
    const char *digits = "0123456789abcdef";
    Util::srandom(Util::makeseed());
    for (i = 0; i < App::mcookiesize; i+=4) {
        word = Util::random() & 0xffff;
        lo = word & 0xff;
        hi = word >> 8;
        mcookie[i] = digits[lo & 0x0f];
        mcookie[i+1] = digits[lo >> 4];
        mcookie[i+2] = digits[hi & 0x0f];
        mcookie[i+3] = digits[hi >> 4];
    }
    // reinitialize auth file
    authfile = cfg->getOption("authfile");
    remove(authfile.c_str());
    putenv(StrConcat("XAUTHORITY=", authfile.c_str()));
    Util::add_mcookie(mcookie, ":0", cfg->getOption("xauth_path"), authfile);
}


//    currently, the sole caller is  CreateServerAuth(), above
char* App::StrConcat(const char* str1, const char* str2) {
/**
    size_t str1len = 0;
    size_t str2len = 0;
    size_t tmplen;
    if (str1 != NULL)   str1len = strlen(str1);

    if (str2 != NULL)   str2len = strlen(str2);

    tmplen = str1len + str2len + 1;
    char* tmp = new char[tmplen];
    strlcpy(tmp, str1, tmplen);
    if (str2 != NULL)   strcat(tmp, str2);
*/
    char* tmp = new char[strlen(str1) + strlen(str2) + 1];
    strcpy(tmp, str1);
    strcat(tmp, str2);
    return tmp;
}
