1 /* PCDM Login Manager:
2 *  Written by Ken Moore (ken@pcbsd.org) 2012/2013
3 *  Copyright(c) 2013 by the PC-BSD Project
4 *  Available under the 3-clause BSD license
5 */
6 
7 #include <QProcess>
8 #include <QProcessEnvironment>
9 #include <QTemporaryFile>
10 #include <QTextStream>
11 #include <QDebug>
12 #include <QStringList>
13 #include <QCoreApplication>
14 #include <QDateTime>
15 
16 #include "pcdm-backend.h"
17 #include "pcdm-config.h"
18 //#include "pcbsd-utils.h"
19 
20 //QStringList displaynameList,usernameList,homedirList,usershellList,
21 QStringList instXNameList,instXBinList,instXCommentList,instXIconList,instXDEList;//,excludedUsers;
22 QString logFile;
23 QString saveX,saveUsername, lastUser, lastDE;
24 //bool Over1K = true;
25 
26 //========================
27 //    USERLIST CLASS
28 //========================
UserList(QObject * parent)29 UserList::UserList(QObject *parent) : QObject(parent){
30   //Setup processes
31   syncProc = 0; userProc = 0; //not created yet
32   //Setup timers
33   syncTimer = new QTimer(this);
34     syncTimer->setInterval(15000); //15 seconds between status updates minimum
35     connect(syncTimer, SIGNAL(timeout()), this, SLOT(startSyncProc()) );
36   userTimer = new QTimer(this);
37     userTimer->setInterval(10000); //10 seconds between personacrypt user scans
38     connect(userTimer, SIGNAL(timeout()), this, SLOT(startUserProc()) );
39   //Setup other default values
40   allowunder1kUID = false;
41   allowroot = false;
42 }
43 
~UserList()44 UserList::~UserList(){
45 
46 }
47 
allowUnder1K(bool allow)48 void UserList::allowUnder1K(bool allow){
49   allowunder1kUID = allow;
50 }
51 
allowRootUser(bool allow)52 void UserList::allowRootUser(bool allow){
53   allowroot = allow;
54 }
55 
excludeUsers(QStringList exclude)56 void UserList::excludeUsers(QStringList exclude){
57   excludedUsers = exclude;
58 }
59 
updateList()60 void UserList::updateList(){
61   //results will be ready when signals are send out
62   QTimer::singleShot(0, this, SLOT(startUserProc()) );
63 }
64 
stopUpdates()65 void UserList::stopUpdates(){
66   if(userTimer->isActive()){ userTimer->stop(); }
67   if(syncTimer->isActive()){ syncTimer->stop(); }
68 }
69 
70 //Get usernames
users()71 QStringList UserList::users(){
72   QStringList keys = HASH.keys().filter("/name");
73   for(int i=0; i<keys.length(); i++){ keys[i] = keys[i].section("/",0,0); }
74   keys.sort();
75   return keys;
76 }
77 
findUser(QString displayname)78 QString UserList::findUser(QString displayname){
79   QStringList keys = HASH.keys().filter("/name");
80   for(int i=0; i<keys.length(); i++){
81     if(HASH.value(keys[i])==displayname){ return keys[i].section("/",0,0); }
82     else if(keys[i].section("/",0,0)==displayname){ return displayname; }   //Just in case the username was passed through instead of the displayname
83   }
84   return ""; //no match found
85 }
86 
87 //Return info for a username
homedir(QString user)88 QString UserList::homedir(QString user){
89   if(!HASH.contains(user+"/home")){ return ""; }
90   return HASH.value(user+"/home");
91 }
92 
displayname(QString user)93 QString UserList::displayname(QString user){
94   if(!HASH.contains(user+"/name")){ return ""; }
95   return HASH.value(user+"/name");
96 }
97 
shell(QString user)98 QString UserList::shell(QString user){
99   if(!HASH.contains(user+"/shell")){ return ""; }
100   return HASH.value(user+"/shell");
101 }
102 
status(QString user)103 QString UserList::status(QString user){
104   if(!HASH.contains(user+"/status")){
105     if(HASH.contains(user+"/pcstat")){
106       QString stat= HASH.value(user+"/pcstat");
107       if(stat!="ready"){ return stat; }
108       else{ return tr("Ready"); } //don't show the internal flag for a device which is ready
109     }else{ return ""; }
110   }
111   return HASH.value(user+"/status");
112 }
113 
isReady(QString user)114 bool UserList::isReady(QString user){
115   if(!HASH.contains(user+"/name")){ return false; }
116   if(HASH.contains(user+"/pcstat")){  return (HASH.value(user+"/pcstat")=="ready"); }
117   return true; //non-PC user - always ready if found
118 }
119 
120 //Private
parseUserLine(QString line,QStringList * oldusers,const QStringList * allPC,const QStringList * activePC)121 bool UserList::parseUserLine(QString line, QStringList *oldusers, const QStringList *allPC, const QStringList *activePC){
122   //returns true if data changed, and removes itself from oldusers as needed
123   //qDebug() << "Parse Line:" << line;
124   //if(line.endsWith("\n")){ line.chop(1); }
125   //Remove all users that have:
126   static QStringList filter;
127    if(filter.isEmpty()){ filter << "server" << "daemon" << "database" << "system"<< "account"<<"pseudo"; }
128   //List any shells which are still valid - if not installed fall back on csh
129   static QStringList validShells;
130   if(validShells.isEmpty()){ validShells << "/usr/local/bin/zsh" << "/usr/local/bin/fish" << "/usr/local/bin/bash"; }
131   //Now load the information
132   QStringList info = line.section("\n",0,0).split(":"); //FIELDS: <username, *, uid, gid, comment, home, shell>
133   //qDebug() << "User Info:" << info << line;
134   if(info.length() < 7){ return false; } //invalid user line - missing fields
135     bool bad = false;
136     bool fixshell = false;
137     QString dispcheck = info[4].toLower();
138     QString shell = info[6];
139     //First see if the listed shell is broken, but valid
140     if(!QFile::exists(shell) && validShells.contains(shell)){ fixshell = true; }
141     // Shell Checks
142     if(shell.contains("nologin") || shell.isEmpty() ){bad=true;}
143     else if( !QFile::exists(shell) && !fixshell ){ bad = true; }
144     // User Home Dir
145     else if(info[5].contains("nonexistent") || info[5].contains("/empty") || info[5].isEmpty() ){bad=true;}
146     // uid > 0
147     else if(info[2].toInt() < 1){bad=!allowroot;} //don't show the root user
148     else if(info[0].indexOf("picoauth") != -1){bad=true;} //don't show pico connect users
149     //Check that the name/description does not contain "server"
150     else if(info[2].toInt() < 1000){
151 	if(!allowunder1kUID){ bad = true;} //ignore anything under UID 1000
152 	else{
153 	  //Apply the special <1000 filters
154 	  if(excludedUsers.contains(info[0])){ bad = true; }
155 	  for(int f=0;f<filter.length() && !bad; f++){
156 	    if(dispcheck.contains(filter[f])){ bad = true; }
157           }
158         }
159     }
160     bool change = false;
161     //See if it failed any checks
162     if(!bad){
163       //qDebug() << "Good User:" << info << allPC->contains(info[0]) << activePC->contains(info[0]);
164       if(fixshell){ shell = "/bin/csh"; }
165       //Add this user to the lists if it is good
166       change = !oldusers->contains(info[0]);
167       if(!change){ //go one level deeper to see if anything is different
168         //qDebug() << " - Check existing user info:" << info;
169         oldusers->removeAll(info[0]);
170         if(HASH.value(info[0]+"/name")!=info[4].simplified()){ change = true; }
171        else if(HASH.value(info[0]+"/home")!=info[5].simplified()){ change = true; }
172        else if(HASH.value(info[0]+"/shell")!=shell){ change = true; }
173         if(allPC->contains(info[0])){
174           change = HASH.value(info[0]+"/pcstat") != (activePC->contains(info[0]) ? "ready" : "disconnected");
175           //HASH.insert(info[0]+"/pcstat", activePC->contains(info[0]) ? "ready" : "disconnected");
176         }else if(HASH.contains(info[0]+"/pcstat")){ change = true; }
177       }
178       if(change){ //need to update the hash
179         //qDebug() << " - Change info in HASH:" << info;
180         HASH.insert(info[0]+"/name", info[4].simplified());
181         HASH.insert(info[0]+"/home", info[5].simplified());
182         HASH.insert(info[0]+"/shell",shell);
183 	if(allPC->contains(info[0])){
184           HASH.insert(info[0]+"/pcstat", activePC->contains(info[0]) ? "ready" : "disconnected");
185         }else if(HASH.contains(info[0]+"/pcstat")){ HASH.remove(info[0]+"/pcstat"); }
186       }
187       //qDebug() << " - Done with user info";
188     }
189   return change;
190 }
191 
192 //Private slots
userProcFinished()193 void UserList::userProcFinished(){
194   bool changed = false;
195   //Get all the user lists
196   QStringList cusers = this->users(); //read all the current users
197   QStringList oldpcusers = HASH.keys().filter("/pcstat");
198     for(int i=0; i<oldpcusers.length(); i++){ oldpcusers[i] = oldpcusers[i].section("/",0,0); }
199   QStringList allpcusers = Backend::getRegisteredPersonaCryptUsers();
200   QStringList activepcusers;
201   if(!allpcusers.isEmpty()){ activepcusers = Backend::getAvailablePersonaCryptUsers(); }
202   //qDebug() << "User Probe Finished:" << cusers << oldpcusers << allpcusers << activepcusers;
203   //Parse the user data
204   QStringList data = QString::fromUtf8(userProc->readAllStandardOutput()).split("\n");
205   //qDebug() << " - Data lines:" << data.length();
206   for(int i=0; i<data.length(); i++){
207     bool gotchange = parseUserLine( data[i], &cusers, &allpcusers, &activepcusers);
208     //if(gotchange){ qDebug() << "Got Change:" << i << data[i]; }
209     changed = changed || gotchange;
210   }
211   //qDebug() << " - done parsing process data" << userProc->canReadLine() << users();
212 
213   //Now clean up the process
214   userProc->deleteLater();
215   userProc = 0;
216   //Clean up any old user data
217   for(int i=0; i<cusers.length(); i++){
218     //qDebug() << "Remove User Data from HASH:" << cusers[i];
219     QStringList keys = HASH.keys().filter(cusers[i]+"/");
220     for(int j=0; j<keys.length(); j++){
221       if(keys[j].startsWith(cusers[i]+"/")){ HASH.remove(keys[j]); }
222     }
223     changed = true;
224   }
225   for(int i=0; i<oldpcusers.length(); i++){
226     if(!allpcusers.contains(oldpcusers[i]) && HASH.contains(oldpcusers[i]+"/pcstat") ){
227       //qDebug() << "PC User Disconnected" << oldpcusers[i];
228       HASH.insert(oldpcusers[i]+"/pcstat", "disconnected");
229       if(HASH.contains(oldpcusers[i]+"/status")){ HASH.remove(oldpcusers[i]+"/status"); }
230       changed = true;
231     }
232   }
233   if(userTimer->isActive()){ userTimer->stop(); }
234   if(HASH.keys().isEmpty() && !allowunder1kUID){
235     allowunder1kUID = true;
236     startUserProc(); //run this process again with the larger "pool" of possible users
237     return;
238   }else if(HASH.keys().isEmpty() && !allowroot){
239     //Still no available users - permit the root user to login as a last-ditch effort
240     allowroot = true;
241     startUserProc();
242     return;
243   }
244   if(!allpcusers.isEmpty()){
245     startSyncProc(); //need to probe PC users now
246   }
247   //qDebug() << " - End Of Probe: " << users();
248   userTimer->start();
249   if(changed){
250     emit UsersChanged();
251   }
252 }
253 
syncProcFinished()254 void UserList::syncProcFinished(){
255   //qDebug() << "Sync Proc Finished:" << QDateTime::currentDateTime().toString();
256   QStringList data = QString::fromUtf8(syncProc->readAllStandardOutput() ).split("\n");
257   //qDebug() << "Sync Proc Data:" << data;
258   //QStringList usersChanged;
259   for(int i=0; i<data.length(); i++){
260     QString user = data[i].section(" on ",0,0);
261     QString stat = data[i].section("(",1,1).section(")",0,0);
262     //usersChanged << user; //flag this as an updated user status
263     if(HASH.contains(user+"/name")){
264       if(HASH.value(user+"/pcstat")!=stat){
265         HASH.insert(user+"/pcstat", stat);
266         qDebug() << " - PC User Status Changed:" << user;
267         emit UserStatusChanged(user, stat);
268       }
269     }
270   }
271   //Now go through and update all the PC user status's that were *not* returned by PC
272   /*QStringList uList = users();
273   for(int i=0; i<uList.length(); i++){
274     if(usersChanged.contains(uList[i])){ continue; } //already changed
275     else if(HASH.value(uList[i]+"/status") == ""){ continue; } //not a personacrypt user
276     else{ HASH.insert(uList[i]+"/status","Disconnected"); }
277   }*/
278 
279   //Now clean up the process
280   //syncProc->deleteLater();
281   //syncProc = 0;
282 }
283 
startSyncProc()284 void UserList::startSyncProc(){
285  if(syncProc==0){
286     syncProc = new QProcess(this);
287     syncProc->setProgram("personacrypt");
288     syncProc->setArguments(QStringList() << "list" << "-s");
289     connect(syncProc, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(syncProcFinished()) );
290   }
291   if(syncProc->state()==QProcess::Running){ return; } //already running
292   if(syncTimer->isActive()){ syncTimer->stop(); }
293   //qDebug() << "Starting Sync Proc";
294   syncProc->start();
295 }
296 
startUserProc()297 void UserList::startUserProc(){
298   if(userProc==0){
299     userProc = new QProcess(this);
300       //userProc->setProcessChannelMode(QProcess::MergedChannels);
301         QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
302         //Make sure to set all the possible UTF-8 flags before reading users
303         env.insert("LANG", "en_US.UTF-8");
304         env.insert("LC_ALL", "en_US.UTF-8");
305         env.insert("MM_CHARSET","UTF-8");
306     userProc->setProcessEnvironment(env);
307     userProc->setProgram("getent");
308     userProc->setArguments(QStringList() << "passwd");
309     connect(userProc, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(userProcFinished()) );
310   }
311   if(userProc->state()==QProcess::Running){ return; } //already running
312   if(userTimer->isActive()){ userTimer->stop(); }
313   userProc->start();
314 }
315 
316 
317 //========================
318 //      STATIC FUNCTIONS
319 //========================
getAvailableDesktops()320 QStringList Backend::getAvailableDesktops(){
321   if(instXNameList.isEmpty()){ loadXSessionsData(); }
322   QStringList out = instXNameList;
323   return out;
324 }
325 
getDesktopComment(QString xName)326 QString Backend::getDesktopComment(QString xName){
327   if(instXNameList.isEmpty()){ loadXSessionsData(); }
328   int index = instXNameList.indexOf(xName);
329   if(index == -1){ Backend::log("PCDM: Invalid Desktop Name: " + xName); return ""; }
330   return instXCommentList[index];
331 }
332 
getNLDesktopName(QString xName)333 QString Backend::getNLDesktopName(QString xName){
334   //Get the non-localized desktop name from the localized version
335   if(instXNameList.isEmpty()){ loadXSessionsData(); }
336   int index = instXNameList.indexOf(xName);
337   if(index == -1){ Backend::log("PCDM: Invalid Desktop Name: " +xName); return ""; }
338   return instXDEList[index];
339 }
340 
getDesktopIcon(QString xName)341 QString Backend::getDesktopIcon(QString xName){
342   if(instXNameList.isEmpty()){ loadXSessionsData(); }
343   int index = instXNameList.indexOf(xName);
344   if(index == -1){ Backend::log("PCDM: Invalid Desktop Name: " +xName); return ""; }
345   return instXIconList[index];
346 }
347 
getDesktopBinary(QString xName)348 QString Backend::getDesktopBinary(QString xName){
349   if(instXNameList.isEmpty()){ loadXSessionsData(); }
350   int index = instXNameList.indexOf(xName);
351   if(index == -1){ Backend::log("PCDM: Invalid Desktop Name: " + xName); return ""; }
352   return instXBinList[index];
353 }
354 
355 
getALUsername()356 QString Backend::getALUsername(){
357   //Make sure the requested user is valid
358   QString ruser = Config::autoLoginUsername();
359   return ruser;
360 }
361 
362 //Run a shell command (return a list of lines and an optional success flag)
runShellCommand(QString command)363 QStringList Backend::runShellCommand(QString command){
364  //split the command string with individual commands seperated by a ";" (if any)
365  QStringList cmdl = command.split(";");
366  QString outstr;
367  //run each individual command
368  bool ok = true;
369  for(int i=0;i<cmdl.length() && ok;i++){
370    QProcess p;
371    //Make sure we use the system environment to properly read system variables, etc.
372    p.setProcessEnvironment(QProcessEnvironment::systemEnvironment());
373    //Merge the output channels to retrieve all output possible
374    p.setProcessChannelMode(QProcess::MergedChannels);
375    p.start(cmdl[i]);
376    while(p.state()==QProcess::Starting || p.state() == QProcess::Running){
377      p.waitForFinished(200);
378      QCoreApplication::processEvents();
379    }
380    QString tmp = p.readAllStandardOutput();
381    outstr.append(tmp);
382    ok = (p.exitCode()==0);
383  }
384  if(outstr.endsWith("\n")){outstr.chop(1);} //remove the newline at the end
385  return outstr.split("\n");
386 }
387 
getALPassword()388 QString Backend::getALPassword(){
389   QString rpassword = Config::autoLoginPassword();
390   return rpassword;
391 }
392 
keyModels()393 QStringList Backend::keyModels()
394 {
395     QStringList _models;
396     QString code, desc, line;
397 
398     Process p(QStringList() << "xkeyboard-models");
399 
400     if (p.waitForFinished()) {
401         while (p.canReadLine()) {
402             line = p.readLine();
403             code = line;
404             code.truncate(line.indexOf(" "));
405             desc = line.remove(0, line.indexOf(" "));
406             _models.append(desc.simplified() + " - (" + code.simplified() + ")");
407         }
408     }
409     return _models;
410 }
411 
keyLayouts()412 QStringList Backend::keyLayouts()
413 {
414     QStringList _layouts;
415     QString code, desc, line;
416 
417     Process p(QStringList() << "xkeyboard-layouts");
418 
419     if (p.waitForFinished()) {
420         while (p.canReadLine()) {
421             line = p.readLine();
422             code = line;
423             code.truncate(line.indexOf(" "));
424             desc = line.remove(0, line.indexOf(" "));
425             _layouts.append(desc.simplified() + " - (" + code.simplified() + ")");
426         }
427     }
428     return _layouts;
429 }
430 
431 // Function which gets the key Variants for the target layout
keyVariants(const QString & layout,QStringList & savedKeyVariants)432 QStringList Backend::keyVariants(const QString &layout, QStringList &savedKeyVariants)
433 {
434     QStringList _variants;
435     QString code, desc, line;
436 
437     if ( savedKeyVariants.empty() )
438     {
439       Process p(QStringList() << "xkeyboard-variants");
440       if (p.waitForFinished()) {
441         while (p.canReadLine()) {
442             line = p.readLine();
443             savedKeyVariants << line;
444         }
445       }
446     }
447 
448     for (int i = 0; i < savedKeyVariants.size(); ++i) {
449        // Look for variants for this particular layout
450        line = savedKeyVariants.at(i);
451        if ( line.indexOf(" " + layout + ":") != -1 )
452        {
453          code = line.simplified();
454          code.truncate(code.indexOf(" "));
455          desc = line.remove(0, line.indexOf(": ") + 1);
456          _variants.append(desc.simplified() + " - (" + code.simplified() + ")");
457        }
458     }
459 
460     return _variants;
461 }
462 
463 // Function which lets us run setxkbmap
changeKbMap(QString model,QString layout,QString variant)464 bool Backend::changeKbMap(QString model, QString layout, QString variant)
465 {
466    QProcess kbp;
467 	kbp.setProcessChannelMode(QProcess::MergedChannels);
468    QStringList args;
469    QString prog;
470    prog = "setxkbmap";
471    if(!model.isEmpty()){ args << "-model" << model; }
472    if(!layout.isEmpty()){ args << "-layout" << layout; }
473    if(!variant.isEmpty()){ args << "-variant" << variant; }
474    Backend::log("setxkbmap: " + args.join(" "));
475    kbp.start(prog, args);
476    kbp.waitForFinished();
477    bool ok = (kbp.exitCode() == 0);
478    if(!ok){
479      Backend::log("setxkbmap Failed: "+QString(kbp.readAllStandardOutput()) );
480    }
481    return ok;
482 }
483 
resetKbdCmd()484 QString Backend::resetKbdCmd(){
485   //This will return the command to re-set the current keyboard settings
486   QString lang, keymodel, keylayout, keyvariant;
487   Backend::readDefaultSysEnvironment(lang, keymodel, keylayout, keyvariant);
488   //Generate the command
489   QStringList args;
490   if(!keymodel.isEmpty()){  args << "-model" << keymodel; }
491   if(!keylayout.isEmpty()){ args << "-layout" << keylayout; }
492   if(!keyvariant.isEmpty()){ args << "-variant" << keyvariant; }
493   if(args.isEmpty()){ return ""; } //nothing to reset
494   QString cmd = "setxkbmap "+args.join(" ");
495   return cmd;
496 }
497 
languages()498 QStringList Backend::languages()
499 {
500     QStringList _languages;
501     _languages.append("Default - (en_US)"); //make sure this is always at the top of the list
502     QString code, desc, line;
503 
504     QFile mFile;
505     mFile.setFileName("/usr/share/pc-sysinstall/conf/avail-langs");
506     if ( ! mFile.open(QIODevice::ReadOnly | QIODevice::Text))
507        return QStringList();
508 
509     // Read in the meta-file for categories
510     QTextStream in(&mFile);
511     in.setCodec("UTF-8");
512     while ( !in.atEnd() ) {
513        line = in.readLine();
514        code = line;
515        code.truncate(line.indexOf(" "));
516        desc = line.remove(0, line.indexOf(" "));
517         _languages.append(desc.simplified() + " - (" + code.simplified() + ")");
518     }
519     mFile.close();
520     return _languages;
521 }
522 
openLogFile(QString logFilePath)523 void Backend::openLogFile(QString logFilePath){
524   //If a log file exists, move it to *.old
525   if(QFile::exists(logFilePath)){
526     if(QFile::exists(logFilePath+".old")){ QFile::remove(logFilePath+".old"); }
527     QFile::rename(logFilePath, logFilePath+".old");
528   }
529   //save the path to the logfile
530   logFile = logFilePath;
531 }
532 
log(QString line)533 void Backend::log(QString line){
534   qDebug() << line; //quick replacement to verify the new logging system works
535   /*QFile lFile(logFile);
536   lFile.open(QIODevice::Append);
537   QTextStream out(&lFile);
538   out << line << "\n";
539   lFile.close();*/
540 }
541 
checkLocalDirs()542 void Backend::checkLocalDirs(){
543   //Check for directories first
544   QString base = "/usr/local/share/PCDM";
545   QDir mainDir(base);
546   if(!mainDir.exists()){ mainDir.mkdir(base); }
547   if(!mainDir.exists("themes")){ mainDir.mkdir("themes"); }
548   //Check for sample files
549   if(!mainDir.exists("pcdm.conf.sample")){ QFile::copy(":samples/pcdm.conf",base+"/pcdm.conf.sample"); }
550   //Check for the PCDM runtime directory
551   mainDir.cd(DBDIR);
552   if(!mainDir.exists()){ mainDir.mkpath(DBDIR); }
553 }
554 
getLastUser()555 QString Backend::getLastUser(){
556   //Load the file if necessary
557   if(lastUser.isEmpty()){
558     readSystemLastLogin();
559   }
560   //return the value
561   //QString user = getDisplayNameFromUsername(lastUser);
562   return lastUser;
563 }
564 
getLastDE(QString user,QString home)565 QString Backend::getLastDE(QString user, QString home){
566   if(lastDE.isEmpty()){
567     readSystemLastLogin();
568   }
569   QString de = readUserLastDesktop(home);
570   if(de.isEmpty() || user.isEmpty() || home.isEmpty() ){ return lastDE; }
571   else{ return de; }
572 
573 }
574 
saveLoginInfo(QString user,QString home,QString desktop)575 void Backend::saveLoginInfo(QString user, QString home, QString desktop){
576   writeSystemLastLogin(user,desktop); //save the system file (DBDIR/lastlogin)
577   writeUserLastDesktop(home,desktop); //save the user file (~/.lastlogin)
578 }
579 
loadDPIpreference()580 void Backend::loadDPIpreference(){
581   if( !QFile::exists(DBDIR+"last-dpi") ){ return; }
582   QFile file(DBDIR+"last-dpi");
583   QString dpi = "96";
584   if(file.open(QIODevice::ReadOnly) ){
585     QTextStream in(&file);
586     dpi = in.readLine().simplified();
587     file.close();
588   }else{
589     return;
590   }
591   if(dpi.toInt()>47){
592     qDebug() << "Setting DPI:" << dpi;
593     QProcess::execute("xrandr --dpi "+dpi);
594   }
595 }
596 
setDPIpreference(QString dpi)597 void Backend::setDPIpreference(QString dpi){
598   if(dpi.toInt()<48){ return; } //not an integer value or invalid number
599   QFile file(DBDIR+"last-dpi");
600   if(file.open(QIODevice::WriteOnly | QIODevice::Truncate)){
601     QTextStream out(&file);
602     out << dpi;
603     file.close();
604   }
605 }
606 
readDefaultSysEnvironment(QString & lang,QString & keymodel,QString & keylayout,QString & keyvariant)607 void Backend::readDefaultSysEnvironment(QString &lang, QString &keymodel, QString &keylayout, QString &keyvariant){
608   //Set the default values
609     lang = "en_US";
610     keymodel = "pc104";
611     keylayout = "us";
612     keyvariant = "";
613   //Read the current inputs file and overwrite default values
614   QFile file(DBDIR+"defaultInputs");
615   bool goodFile=false;
616   if(file.exists()){
617     if(file.open(QIODevice::ReadOnly | QIODevice::Text) ){
618       QTextStream in(&file);
619       while(!in.atEnd()){
620         QString line = in.readLine();
621 	QString var = line.section("=",0,0).simplified();
622 	QString val = line.section("=",1,1).simplified();
623 	if(var=="Lang"){ lang = val;}
624 	else if(var=="KeyModel"){ keymodel = val; }
625 	else if(var=="KeyLayout"){ keylayout = val; }
626 	else if(var=="KeyVariant"){ keyvariant = val; }
627       }
628       file.close();
629     }
630   }
631   if(!goodFile){
632     //Save our own defaults
633     saveDefaultSysEnvironment(lang, keymodel, keylayout, keyvariant);
634   }
635 }
636 
saveDefaultSysEnvironment(QString lang,QString keymodel,QString keylayout,QString keyvariant)637 void Backend::saveDefaultSysEnvironment(QString lang, QString keymodel, QString keylayout, QString keyvariant){
638   QFile file(DBDIR+"defaultInputs");
639   //Make sure the containing directory exists
640   if(!QFile::exists(DBDIR)){
641     QDir dir;
642     dir.mkpath(DBDIR);
643   }
644   //Now save the file
645     if(file.open(QIODevice::WriteOnly | QIODevice::Text) ){
646       QTextStream out(&file);
647       out << "Lang=" + lang + "\n";
648       out << "KeyModel=" + keymodel + " \n";
649       out << "KeyLayout=" + keylayout + " \n";
650       out << "KeyVariant=" + keyvariant + " \n";
651       file.close();
652     }
653 }
654 
getRegisteredPersonaCryptUsers()655 QStringList Backend::getRegisteredPersonaCryptUsers(){
656   //This is just a quick check to see what users are personacrypt-enabled on this system
657   //  needs to do any of the personacrypt stuff on this system
658   if( !QFile::exists("/usr/local/bin/personacrypt") ){ return QStringList(); } //not installed
659   //Make sure there is at least one profile available
660   QDir dir("/var/db/personacrypt");
661   QStringList users = dir.entryList(QStringList()<<"*.key", QDir::Files | QDir::NoDotAndDotDot, QDir::Name);
662   for(int i=0; i<users.length(); i++){
663     users[i].chop(4); //chop the ".key" off the end to get the username
664   }
665   return users;
666 }
667 
getAvailablePersonaCryptUsers()668 QStringList Backend::getAvailablePersonaCryptUsers(){
669   QStringList info = runShellCommand("personacrypt list");
670   QStringList users;
671   for(int i=0; i<info.length(); i++){
672     if(info[i].contains(" on ")){
673       users << info[i].section(" on ",0,0);
674     }
675   }
676   return users;
677 }
678 
MountPersonaCryptUser(QString user,QString pass)679 bool Backend::MountPersonaCryptUser(QString user, QString pass){
680   //First, the password needs to be saved to a temporary file for input
681   QTemporaryFile tmpfile("/var/tmp/.XXXXXXXXXXXXXXXXXXXX"); //just a long/hidden randomized name
682     tmpfile.setAutoRemove(true);
683   if( !tmpfile.open() ){ return false; } //could not open the temporary file
684   QTextStream out(&tmpfile);
685   out << pass;
686   tmpfile.close();
687 
688   //Second, the mount command needs to be run (be careful about spaces in the names)
689   return (0 == QProcess::execute("personacrypt mount \""+user+"\" \""+tmpfile.fileName()+"\"") );
690   //Finally, delete the temporary input file (if personacrypt did not already)
691     //QTemporaryFile automatically tries to delete the file when it goes out of scope
692     // no need to manually remove it
693 }
694 
UnmountPersonaCryptUser(QString user)695 bool Backend::UnmountPersonaCryptUser(QString user){
696   return 0==QProcess::execute("personacrypt umount \""+user+"\"");
697 }
698 
699 
writeFile(QString fileName,QStringList contents)700 bool Backend::writeFile(QString fileName, QStringList contents){
701   //Open the file with .tmp extension
702   QFile file(fileName+".tmp");
703   if( !file.open(QIODevice::WriteOnly | QIODevice::Text) ){
704     qDebug() << fileName+".tmp: Failure -- Could not open file";
705     return false;
706   }
707   //Write the file
708   QTextStream ofile(&file); //start the output stream
709   for(int i=0; i<contents.length(); i++){
710     ofile << contents[i];
711     ofile << "\n";
712   }
713   //Close the File
714   file.close();
715   //Remove any existing file with the final name/location
716   if( QFile::exists(fileName) ){
717     if( !QFile::remove(fileName) ){
718       qDebug() << fileName+": Error -- Could not overwrite existing file";
719       QFile::remove(fileName+".tmp");
720       return false;
721     }
722   }
723   //Move the temporary file into its final location
724   if( !file.rename(fileName) ){
725     qDebug() << fileName+": Error: Could not rename "+fileName+".tmp as "+fileName;
726     return false;
727   }
728   //Return success
729   QString extra = QDir::homePath(); //remove this from the filename display
730   qDebug() << "Saved:" << fileName.replace(extra,"~");
731   return true;;
732 }
733 
734 
735 // ****** PRIVATE FUNCTIONS ******
736 
loadXSessionsData()737 void Backend::loadXSessionsData(){
738   //Clear the current variables
739   instXNameList.clear(); instXBinList.clear();
740   instXCommentList.clear(); instXIconList.clear();
741   instXDEList.clear();
742   //Load the default paths/locale
743   QString xDir = Config::xSessionsDir();
744   QStringList paths = QString(getenv("PATH")).split(":");
745   if(paths.isEmpty()){ paths <<"/usr/local/bin" << "/usr/local/sbin" << "/usr/bin" << "/usr/sbin" << "/bin" << "/sbin"; }
746   if(!xDir.endsWith("/")){ xDir.append("/"); }
747   QString xIconDir = Config::xSessionsImageDir();
748   if(!xIconDir.endsWith("/")){ xIconDir.append("/"); }
749   QString localeCode = QLocale().name(); //gets the current locale code
750   //Find all *.desktop files
751   QDir dir(xDir);
752   QStringList deFiles = dir.entryList(QStringList() << "*.desktop", QDir::Files, QDir::Name);
753   //deFiles = deFiles.filter(".desktop"); //only get *.desktop files
754   //Read each file to see if that desktop is installed
755   for(int i=0; i<deFiles.length(); i++){
756     QStringList tmp = readXSessionsFile(xDir+deFiles[i],localeCode);
757     //tmp[exec, name, comment, icon, tryexec]
758     if(!tmp.isEmpty()){
759       //Complete file paths if necessary
760       //if(!tmp[0].startsWith("/")){ tmp[0] = "/usr/local/bin/"+tmp[0]; }
761       if(!tmp[4].startsWith("/") && !QFile::exists(tmp[4])){
762 	for(int p=0; p<paths.length(); p++){
763 	  if(QFile::exists(paths[p]+"/"+tmp[4])){
764 	    tmp[4] = paths[p]+"/"+tmp[4];
765 	  }
766 	}
767       }
768       //Check for valid DE using the "tryexec" line
769         //this allows for special startup commands on the "exec" line
770       if(QFile::exists(tmp[4])){
771         //Add the DE to list of installed xsessions
772 	instXBinList << tmp[0];
773 	instXNameList << tmp[1];
774 	instXCommentList << tmp[2];
775 	instXDEList << tmp[5]; //Non-localized name of the DE
776 	//Check to make sure we have a valid icon
777 	//if(!tmp[3].isEmpty() && !QFile::exists(tmp[3]) ){
778         if( QFile::exists(xIconDir+tmp[3]) ){ tmp[3] = xIconDir+tmp[3]; }
779         else if( QFile::exists(xIconDir+tmp[3]+".png") ){ tmp[3] = xIconDir+tmp[3]+".png"; }
780         else{
781           //Try to turn this into a valid icon path
782          QDir dir("/usr/local/share/pixmaps");
783          QStringList found = dir.entryList(QStringList() << tmp[3]+".*", QDir::Files, QDir::NoSort);
784          if(found.isEmpty()){ found = dir.entryList(QStringList() << tmp[3]+"*", QDir::Files, QDir::NoSort); }
785          if(found.isEmpty()){ found = dir.entryList(QStringList() << "*"+tmp[3]+"*", QDir::Files, QDir::NoSort); }
786          if(!found.isEmpty()){ tmp[3] = dir.filePath(found.first()); }
787 	   else{  tmp[3] = ""; }
788        }
789       //}
790 	instXIconList << tmp[3];
791 	Backend::log( "PCDM: Found xsession: " + deFiles[i].section("/",-1)+": "+tmp.join(" - ") );
792       }
793     }
794   }
795   if(instXNameList.isEmpty()){
796     //Create an entry so that we know this function has been run already
797     instXNameList << "None";
798     instXBinList << "none";
799     instXCommentList << "No xSession Environments available in" << xDir;
800     instXIconList << ":images/nodesktop.png";
801   }
802 }
803 
readXSessionsFile(QString filePath,QString locale)804 QStringList Backend::readXSessionsFile(QString filePath, QString locale){
805 //output: [Exec, Localized Name, Localized Comment, Icon, TryExec]
806   QString name, lname, comm, lcomm, icon, exec, tryexec;
807   QStringList output;
808   QString lna = "Name["+locale+"]"; //variable to look at for localized name
809   QString lco = "Comment["+locale+"]"; //variable to look at for localized comment
810   QFile file(filePath);
811   if(file.open(QIODevice::ReadOnly | QIODevice::Text)){
812     QTextStream in(&file);
813     while (!in.atEnd()){
814       QString line = in.readLine().simplified();
815       QString var = line.section("=",0,0,QString::SectionSkipEmpty).simplified();
816       QString val = line.section("=",1,50,QString::SectionSkipEmpty).simplified();
817       if(var.toLower()=="exec"){ exec = val; }
818       else if(var.toLower()=="tryexec"){ tryexec = val; }
819       else if(var.toLower()=="icon"){ icon = val; }
820       else if(var.toLower()=="name"){ name = val; }
821       else if(var.toLower()=="comment"){ comm = val; }
822       else if(var==lna){ lname = val; }
823       else if(var==lco){ lcomm = val; }
824       else{} //do nothing with other lines
825 
826     }
827   }
828   //Use the unlocalized name/comment if localized values not detected
829   if(lname.isEmpty()){ lname = name; }
830   if(lcomm.isEmpty()){ lcomm = comm; }
831   //Make sure that we have a name/exec for the session, otherwise invalid file
832   if(lname.isEmpty() || exec.isEmpty() ){ return output; }
833   //If no tryexec given, check for the first binary given on the Exec line
834   if(tryexec.isEmpty()){ tryexec = exec.section(" ",0,0,QString::SectionSkipEmpty).simplified(); }
835   //Check that there is an icon given
836   if(icon.isEmpty()){
837     //Try to use a built in icon if a known DE
838     if(name.toLower().contains("gnome")){icon = ":images/gnome.png"; }
839     if(name.toLower().contains("kde")){icon = ":images/kde.png"; }
840     if(name.toLower().contains("xfce")){icon = ":images/xfce.png"; }
841     if(name.toLower().contains("lxde")){icon = ":images/lxde.png"; }
842     if(name.toLower().contains("fluxbox")){icon = ":images/fluxbox.png"; }
843     if(name.toLower().contains("openbox")){icon = ":images/openbox.png"; }
844   }
845   //Format the results into the output list
846   output.clear();
847   output << exec << lname << lcomm << icon << tryexec << name;
848   return output;
849 
850 }
851 
852 /*void Backend::readSystemUsers(bool directfile){
853   //make sure the lists are empty
854   usernameList.clear(); displaynameList.clear(); homedirList.clear();
855   QStringList uList;
856     if(directfile){
857       //This is a general fallback for reading the file directly in case something goes wrong with the getent case below
858       QFile file("/etc/passwd");
859       if(file.open(QIODevice::ReadOnly | QIODevice::Text)){
860         QTextStream in(&file);
861         while (!in.atEnd()){ uList << in.readLine().simplified(); }
862         file.close();
863       }
864     }else{
865       //Use "getent" to get all possible users (detects LDAP/AD users - preferred)
866       QProcess p;
867       p.setProcessChannelMode(QProcess::MergedChannels);
868         QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
869         //Make sure to set all the possible UTF-8 flags before reading users
870         env.insert("LANG", "en_US.UTF-8");
871         env.insert("LC_ALL", "en_US.UTF-8");
872         env.insert("MM_CHARSET","UTF-8");
873       p.setProcessEnvironment(env);
874       p.start("getent passwd");
875       while(p.state()==QProcess::Starting || p.state() == QProcess::Running){
876         p.waitForFinished(200);
877         QCoreApplication::processEvents();
878       }
879       uList = QString::fromUtf8( p.readAllStandardOutput() ).split("\n");
880     }
881 
882     //Remove all users that have:
883    QStringList filter; filter << "server" << "daemon" << "database" << "system"<< "account"<<"pseudo";
884    //List any shells which are still valid - if not installed fall back on csh
885    QStringList validShells; validShells << "/usr/local/bin/zsh" << "/usr/local/bin/fish" << "/usr/local/bin/bash";
886    for(int i=0; i<uList.length(); i++){
887     bool bad = false;
888     bool fixshell = false;
889     QString dispcheck = uList[i].section(":",4,4).toLower();
890     QString shell = uList[i].section(":",6,6);
891     //First see if the listed shell is broken, but valid
892     if(!QFile::exists(shell) && validShells.contains(shell)){ fixshell = true; }
893     // Shell Checks
894     if(shell.contains("nologin") || shell.isEmpty() ){bad=true;}
895     else if( !QFile::exists(shell) && !fixshell ){ bad = true; }
896     // User Home Dir
897     else if(uList[i].section(":",5,5).contains("nonexistent") || uList[i].section(":",5,5).contains("/empty") || uList[i].section(":",5,5).isEmpty() ){bad=true;}
898     // uid > 0
899     else if(uList[i].section(":",2,2).toInt() < 1){bad=true;} //don't show the root user
900     //Check that the name/description does not contain "server"
901     else if(uList[i].section(":",2,2).toInt() < 1000){
902 	if(Over1K){ bad = true;} //ignore anything under UID 1000
903 	else{
904 	  //Apply the special <1000 filters
905 	  if(excludedUsers.contains(uList[i].section(":",0,0))){ bad = true; }
906 	  for(int f=0;f<filter.length() && !bad; f++){
907 	    if(dispcheck.contains(filter[f])){ bad = true; }
908           }
909         }
910     }
911 
912     //See if it failed any checks
913     if(bad){ uList.removeAt(i); i--; }
914     else{
915       //Add this user to the lists if it is good
916       usernameList << uList[i].section(":",0,0).simplified();
917       displaynameList << uList[i].section(":",4,4).simplified();
918       homedirList << uList[i].section(":",5,5).simplified();
919       if(fixshell){ usershellList << "/bin/csh"; }
920       else{ usershellList << uList[i].section(":",6,6).simplified(); }
921     }
922    } //end loop over uList
923   if(usernameList.isEmpty() && !directfile){
924     //We need to find a valid user somewhere - try to directly read the file instead
925     qWarning() << "No users found with \"getent passwd\", reading the database directly...";
926     readSystemUsers(true);
927   }
928 }*/
929 
readSystemLastLogin()930 void Backend::readSystemLastLogin(){
931     lastDE="Lumina"; //PC-BSD default desktop (use if nothing else set)
932     if(!QFile::exists(DBDIR+"lastlogin")){
933       lastUser.clear();
934       Backend::log("PCDM: No previous login data found");
935     }else{
936       //Load the previous login data
937       QFile file(DBDIR+"lastlogin");
938       if(!file.open(QIODevice::ReadOnly | QIODevice::Text)){
939         Backend::log("PCDM: Unable to open previous login data file");
940       }else{
941         QTextStream in(&file);
942         lastUser= in.readLine();
943 	  lastDE= in.readLine();
944         file.close();
945       }
946     }
947 }
948 
writeSystemLastLogin(QString user,QString desktop)949 void Backend::writeSystemLastLogin(QString user, QString desktop){
950   QFile file1(DBDIR+"lastlogin");
951   if(!file1.open(QIODevice::Truncate | QIODevice::WriteOnly | QIODevice::Text)){
952     Backend::log("PCDM: Unable to save last login data to system directory");
953   }else{
954     QTextStream out(&file1);
955     out << user << "\n" << desktop;
956     file1.close();
957   }
958 
959 }
960 
readUserLastDesktop(QString userhome)961 QString Backend::readUserLastDesktop(QString userhome){
962   QString desktop;
963   QString LLpath = userhome + "/.lastlogin";
964   if(!QFile::exists(LLpath)){
965     Backend::log("PCDM: No previous user login data found for user: "+userhome);
966   }else{
967     //Load the previous login data
968     QFile file(LLpath);
969     if(!file.open(QIODevice::ReadOnly | QIODevice::Text)){
970       Backend::log("PCDM: Unable to open previous user login file: "+userhome);
971     }else{
972       QTextStream in(&file);
973       desktop = in.readLine();
974       file.close();
975     }
976   }
977   return desktop;
978 }
979 
writeUserLastDesktop(QString userhome,QString desktop)980 void Backend::writeUserLastDesktop(QString userhome, QString desktop){
981   QFile file2( userhome + "/.lastlogin" );
982   if(!file2.open(QIODevice::Truncate | QIODevice::WriteOnly | QIODevice::Text)){
983     Backend::log("PCDM: Unable to save last login data for user:"+userhome);
984   }else{
985     QTextStream out(&file2);
986     out << desktop;
987     file2.close();
988   }
989 }
990