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