source: src-qt4/PCDM/src/pcdm-backend.cpp @ fdf420c

releng/10.0.2releng/10.0.3
Last change on this file since fdf420c was fdf420c, checked in by Ken Moore <ken@…>, 6 months ago

Small update to PCDM:
1) Clean up session management/restarts (in case login fails during all the PAM or user/group changes)
2) Update the location of the lastlogin file (/var/db/pcdm/lastlogin), and make sure it is getting saved/loaded properly.

  • Property mode set to 100644
File size: 19.6 KB
Line 
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
10#include "pcdm-backend.h"
11#include "pcdm-config.h"
12#include "pcbsd-utils.h"
13
14QStringList displaynameList,usernameList,homedirList,usershellList,instXNameList,instXBinList,instXCommentList,instXIconList,instXDEList;
15QString logFile;
16QString saveX,saveUsername, lastUser, lastDE;
17
18QStringList Backend::getAvailableDesktops(){ 
19  if(instXNameList.isEmpty()){ loadXSessionsData(); }
20  QStringList out = instXNameList;
21  return out;
22}
23
24QString Backend::getDesktopComment(QString xName){
25  if(instXNameList.isEmpty()){ loadXSessionsData(); }
26  int index = instXNameList.indexOf(xName);
27  if(index == -1){ Backend::log("PCDM: Invalid Desktop Name: " + xName); return ""; }
28  return instXCommentList[index];
29}
30
31QString Backend::getNLDesktopName(QString xName){
32  //Get the non-localized desktop name from the localized version
33  if(instXNameList.isEmpty()){ loadXSessionsData(); }
34  int index = instXNameList.indexOf(xName);
35  if(index == -1){ Backend::log("PCDM: Invalid Desktop Name: " +xName); return ""; }
36  return instXDEList[index];   
37}
38
39QString Backend::getDesktopIcon(QString xName){
40  if(instXNameList.isEmpty()){ loadXSessionsData(); }
41  int index = instXNameList.indexOf(xName);
42  if(index == -1){ Backend::log("PCDM: Invalid Desktop Name: " +xName); return ""; }
43  return instXIconList[index];
44}
45
46QString Backend::getDesktopBinary(QString xName){
47  if(instXNameList.isEmpty()){ loadXSessionsData(); }
48  int index = instXNameList.indexOf(xName);
49  if(index == -1){ Backend::log("PCDM: Invalid Desktop Name: " + xName); return ""; }
50  return instXBinList[index];
51}
52
53QStringList Backend::getSystemUsers(){
54  if(usernameList.isEmpty()){
55    readSystemUsers();
56  }
57  return displaynameList;
58}
59
60QString Backend::getALUsername(){
61  //Make sure the requested user is valid
62  readSystemUsers(); //first read the available users on this system
63  QString ruser = Config::autoLoginUsername();
64  int index = usernameList.indexOf(ruser);
65  if(index == -1){ //invalid username
66    //Check if a display name was given instead
67    index = displaynameList.indexOf(ruser);
68    if(index == -1){ //invalid display name
69      log("Invalid Auto-Login user requested - skipping....");
70      ruser.clear();
71    }else{
72      //use the valid username for the given display name
73      ruser = usernameList[index]; 
74    }
75  }
76  return ruser;
77}
78
79QString Backend::getALPassword(){
80  QString rpassword = Config::autoLoginPassword();
81  return rpassword;
82}
83
84QString Backend::getUsernameFromDisplayname(QString dspname){
85  int i = displaynameList.indexOf(dspname);
86  if(i == -1){ i = usernameList.indexOf(dspname); }
87 
88  if(i == -1){ return ""; }
89  else{ return usernameList[i]; }
90}
91
92QString Backend::getDisplayNameFromUsername(QString username){
93  int i = usernameList.indexOf(username);
94  if(i==-1){ i = displaynameList.indexOf(username); } //make sure it was not a display name passed in
95  if(i==-1){ return ""; }
96  else{
97    return displaynameList[i]; 
98  }
99}
100
101QString Backend::getUserHomeDir(QString username){
102  int i = usernameList.indexOf(username);
103  if( i == -1 ){ i = displaynameList.indexOf(username); }
104  return homedirList[i];
105}
106
107QString Backend::getUserShell(QString username){
108  int i = usernameList.indexOf(username);
109  if( i == -1 ){ i = displaynameList.indexOf(username); }
110  return usershellList[i];     
111}
112
113QStringList Backend::keyModels()
114{
115    QStringList _models;
116    QString code, desc, line;
117
118    Process p(QStringList() << "xkeyboard-models");
119
120    if (p.waitForFinished()) {
121        while (p.canReadLine()) {
122            line = p.readLine();
123            code = line;
124            code.truncate(line.indexOf(" "));
125            desc = line.remove(0, line.indexOf(" "));
126            _models.append(desc.simplified() + " - (" + code.simplified() + ")");
127        }
128    }
129    return _models;
130}
131
132QStringList Backend::keyLayouts()
133{
134    QStringList _layouts;
135    QString code, desc, line;
136
137    Process p(QStringList() << "xkeyboard-layouts");
138
139    if (p.waitForFinished()) {
140        while (p.canReadLine()) {
141            line = p.readLine();
142            code = line;
143            code.truncate(line.indexOf(" "));
144            desc = line.remove(0, line.indexOf(" "));
145            _layouts.append(desc.simplified() + " - (" + code.simplified() + ")");
146        }
147    }
148    return _layouts;
149}
150
151// Function which gets the key Variants for the target layout
152QStringList Backend::keyVariants(const QString &layout, QStringList &savedKeyVariants)
153{
154    QStringList _variants;
155    QString code, desc, line;
156
157    if ( savedKeyVariants.empty() )
158    {
159      Process p(QStringList() << "xkeyboard-variants");
160      if (p.waitForFinished()) {
161        while (p.canReadLine()) {
162            line = p.readLine();
163            savedKeyVariants << line;
164        }
165      }
166    }
167
168    for (int i = 0; i < savedKeyVariants.size(); ++i) {
169       // Look for variants for this particular layout
170       line = savedKeyVariants.at(i);
171       if ( line.indexOf(" " + layout + ":") != -1 )
172       {
173         code = line.simplified();
174         code.truncate(code.indexOf(" "));
175         desc = line.remove(0, line.indexOf(": ") + 1);
176         _variants.append(desc.simplified() + " - (" + code.simplified() + ")");
177       }
178    }
179
180    return _variants;
181}
182
183// Function which lets us run setxkbmap
184bool Backend::changeKbMap(QString model, QString layout, QString variant)
185{
186   QProcess kbp;
187        kbp.setProcessChannelMode(QProcess::MergedChannels);
188   QStringList args;
189   QString prog;
190   prog = "setxkbmap"; 
191   if(!model.isEmpty()){ args << "-model" << model; }
192   if(!layout.isEmpty()){ args << "-layout" << layout; }
193   if(!variant.isEmpty()){ args << "-variant" << variant; }
194   Backend::log("setxkbmap: " + args.join(" "));
195   kbp.start(prog, args);
196   kbp.waitForFinished();
197   bool ok = (kbp.exitCode() == 0);
198   if(!ok){
199     Backend::log("setxkbmap Failed: "+QString(kbp.readAllStandardOutput()) );
200   }
201   return ok;
202}
203
204QStringList Backend::languages()
205{
206    QStringList _languages;
207    _languages.append("Default - (en_US)"); //make sure this is always at the top of the list
208    QString code, desc, line;
209
210    QFile mFile;
211    mFile.setFileName("/usr/share/pc-sysinstall/conf/avail-langs");
212    if ( ! mFile.open(QIODevice::ReadOnly | QIODevice::Text))
213       return QStringList();
214
215    // Read in the meta-file for categories
216    QTextStream in(&mFile);
217    in.setCodec("UTF-8");
218    while ( !in.atEnd() ) {
219       line = in.readLine();
220       code = line;
221       code.truncate(line.indexOf(" "));
222       desc = line.remove(0, line.indexOf(" "));
223        _languages.append(desc.simplified() + " - (" + code.simplified() + ")");
224    }
225    mFile.close();
226    return _languages;
227}
228
229void Backend::openLogFile(QString logFilePath){
230  //If a log file exists, move it to *.old
231  if(QFile::exists(logFilePath)){ 
232    if(QFile::exists(logFilePath+".old")){ QFile::remove(logFilePath+".old"); }
233    QFile::rename(logFilePath, logFilePath+".old"); 
234  }
235  //save the path to the logfile
236  logFile = logFilePath;
237}
238
239void Backend::log(QString line){
240  QFile lFile(logFile);
241  lFile.open(QIODevice::Append);
242  QTextStream out(&lFile);
243  out << line << "\n";
244  lFile.close();
245}
246
247void Backend::checkLocalDirs(){
248  //Check for directories first
249  QString base = "/usr/local/share/PCDM";
250  QDir mainDir(base);
251  if(!mainDir.exists()){ mainDir.mkdir(base); }
252  if(!mainDir.exists("themes")){ mainDir.mkdir("themes"); }
253  //Check for sample files
254  if(!mainDir.exists("pcdm.conf.sample")){ QFile::copy(":samples/pcdm.conf",base+"/pcdm.conf.sample"); } 
255  //Check for the PCDM runtime directory
256  mainDir.cd(DBDIR);
257  if(!mainDir.exists()){ mainDir.mkpath(DBDIR); }
258}
259
260QString Backend::getLastUser(){
261  //Load the file if necessary
262  if(lastUser.isEmpty()){
263    readSystemLastLogin(); 
264  }
265  //return the value
266  QString user = getDisplayNameFromUsername(lastUser);
267  return user;
268}
269
270QString Backend::getLastDE(QString user){
271  if(lastDE.isEmpty()){
272    readSystemLastLogin();
273  }
274  QString de = readUserLastDesktop(user);
275  if(de.isEmpty()){ return lastDE; }
276  else{ return de; }
277 
278}
279
280void Backend::saveLoginInfo(QString user, QString desktop){
281  writeSystemLastLogin(user,desktop); //save the system file (DBDIR/lastlogin)
282  writeUserLastDesktop(user,desktop); //save the user file (~/.lastlogin)
283}
284
285void Backend::readDefaultSysEnvironment(QString &lang, QString &keymodel, QString &keylayout, QString &keyvariant){
286  //Set the default values
287    lang = "en_US";
288    keymodel = "pc104";
289    keylayout = "us";
290    keyvariant = "";
291  //Read the current inputs file and overwrite default values
292  QFile file(DBDIR+"defaultInputs");
293  bool goodFile=false;
294  if(file.exists()){
295    if(file.open(QIODevice::ReadOnly | QIODevice::Text) ){
296      QTextStream in(&file);
297      while(!in.atEnd()){
298        QString line = in.readLine();
299        QString var = line.section("=",0,0).simplified();
300        QString val = line.section("=",1,1).simplified();
301        if(var=="Lang"){ lang = val;}
302        else if(var=="KeyModel"){ keymodel = val; }
303        else if(var=="KeyLayout"){ keylayout = val; }
304        else if(var=="KeyVariant"){ keyvariant = val; }
305      }
306      file.close();
307    }
308  }
309  if(!goodFile){
310    //Save our own defaults
311    saveDefaultSysEnvironment(lang, keymodel, keylayout, keyvariant);
312  }     
313}
314
315void Backend::saveDefaultSysEnvironment(QString lang, QString keymodel, QString keylayout, QString keyvariant){
316  QFile file(DBDIR+"defaultInputs");
317  //Make sure the containing directory exists
318  if(!QFile::exists(DBDIR)){
319    QDir dir;
320    dir.mkpath(DBDIR);
321  }
322  //Now save the file
323    if(file.open(QIODevice::WriteOnly | QIODevice::Text) ){
324      QTextStream out(&file);
325      out << "Lang=" + lang + "\n";
326      out << "KeyModel=" + keymodel + " \n";
327      out << "KeyLayout=" + keylayout + " \n";
328      out << "KeyVariant=" + keyvariant + " \n";
329      file.close();
330    }
331}
332
333bool Backend::writeFile(QString fileName, QStringList contents){
334  //Open the file with .tmp extension
335  QFile file(fileName+".tmp");
336  if( !file.open(QIODevice::WriteOnly | QIODevice::Text) ){
337    qDebug() << fileName+".tmp: Failure -- Could not open file";
338    return false;
339  }
340  //Write the file
341  QTextStream ofile(&file); //start the output stream
342  for(int i=0; i<contents.length(); i++){
343    ofile << contents[i];
344    ofile << "\n";
345  }
346  //Close the File
347  file.close();
348  //Remove any existing file with the final name/location
349  if( QFile::exists(fileName) ){
350    if( !QFile::remove(fileName) ){
351      qDebug() << fileName+": Error -- Could not overwrite existing file";
352      QFile::remove(fileName+".tmp");
353      return false;
354    }
355  }
356  //Move the temporary file into its final location
357  if( !file.rename(fileName) ){
358    qDebug() << fileName+": Error: Could not rename "+fileName+".tmp as "+fileName;
359    return false;
360  }
361  //Return success
362  QString extra = QDir::homePath(); //remove this from the filename display
363  qDebug() << "Saved:" << fileName.replace(extra,"~");
364  return true;; 
365}
366
367
368//****** PRIVATE FUNCTIONS ******
369
370void Backend::loadXSessionsData(){
371  //Clear the current variables
372  instXNameList.clear(); instXBinList.clear(); 
373  instXCommentList.clear(); instXIconList.clear();
374  instXDEList.clear();
375  //Load the default paths/locale
376  QString xDir = Config::xSessionsDir();
377  QStringList paths = QString(getenv("PATH")).split(":");
378  if(paths.isEmpty()){ paths <<"/usr/local/bin" << "/usr/local/sbin" << "/usr/bin" << "/usr/sbin" << "/bin" << "/sbin"; }
379  if(!xDir.endsWith("/")){ xDir.append("/"); }
380  QString xIconDir = Config::xSessionsImageDir();
381  if(!xIconDir.endsWith("/")){ xIconDir.append("/"); }
382  QString localeCode = QLocale().name(); //gets the current locale code
383  //Find all *.desktop files
384  QDir dir(xDir);
385  QStringList deFiles = dir.entryList(QDir::Files);
386  deFiles = deFiles.filter(".desktop"); //only get *.desktop files
387  //Read each file to see if that desktop is installed
388  for(int i=0; i<deFiles.length(); i++){
389    QStringList tmp = readXSessionsFile(xDir+deFiles[i],localeCode);
390    //tmp[exec, name, comment, icon, tryexec]
391    if(!tmp.isEmpty()){
392      //Complete file paths if necessary
393      //if(!tmp[0].startsWith("/")){ tmp[0] = "/usr/local/bin/"+tmp[0]; }
394      if(!tmp[3].startsWith("/")&&!tmp[3].startsWith(":")&&!tmp[3].isEmpty()){ tmp[3] = xIconDir+tmp[3]; }
395      if(!tmp[4].startsWith("/") && !QFile::exists(tmp[4])){ 
396        for(int p=0; p<paths.length(); p++){
397          if(QFile::exists(paths[p]+"/"+tmp[4])){
398            tmp[4] = paths[p]+"/"+tmp[4];
399          }
400        }
401      }
402      //Check for valid DE using the "tryexec" line
403        //this allows for special startup commands on the "exec" line
404      if(QFile::exists(tmp[4])){
405        //Add the DE to list of installed xsessions
406        instXBinList << tmp[0];
407        instXNameList << tmp[1];
408        instXCommentList << tmp[2];
409        instXDEList << tmp[5]; //Non-localized name of the DE
410        //Check to make sure we have a valid icon
411        if(!tmp[3].isEmpty() && !QFile::exists(tmp[3]) ){ tmp[3] = ""; }
412        instXIconList << tmp[3];
413        Backend::log( "PCDM: Found xsession: " + tmp.join(" ") );
414      }
415    }
416  }
417  if(instXNameList.isEmpty()){
418    //Create an entry so that we know this function has been run already
419    instXNameList << "None";
420    instXBinList << "none";
421    instXCommentList << "No xSession Environments available in" << xDir;
422    instXIconList << ":images/nodesktop.png";
423  }
424}
425
426QStringList Backend::readXSessionsFile(QString filePath, QString locale){
427//output: [Exec, Localized Name, Localized Comment, Icon, TryExec]
428  QString name, lname, comm, lcomm, icon, exec, tryexec;
429  QStringList output;
430  QString lna = "Name["+locale+"]"; //variable to look at for localized name
431  QString lco = "Comment["+locale+"]"; //variable to look at for localized comment
432  QFile file(filePath);
433  if(file.open(QIODevice::ReadOnly | QIODevice::Text)){
434    QTextStream in(&file);
435    while (!in.atEnd()){
436      QString line = in.readLine().simplified();
437      QString var = line.section("=",0,0,QString::SectionSkipEmpty).simplified();
438      QString val = line.section("=",1,50,QString::SectionSkipEmpty).simplified();
439      if(var.toLower()=="exec"){ exec = val; }
440      else if(var.toLower()=="tryexec"){ tryexec = val; }
441      else if(var.toLower()=="icon"){ icon = val; }
442      else if(var.toLower()=="name"){ name = val; }
443      else if(var.toLower()=="comment"){ comm = val; }
444      else if(var==lna){ lname = val; }
445      else if(var==lco){ lcomm = val; }
446      else{} //do nothing with other lines
447
448    }
449  }
450  //Use the unlocalized name/comment if localized values not detected
451  if(lname.isEmpty()){ lname = name; }
452  if(lcomm.isEmpty()){ lcomm = comm; }
453  //Make sure that we have a name/exec for the session, otherwise invalid file
454  if(lname.isEmpty() || exec.isEmpty() || tryexec.isEmpty()){ return output; }
455  //Check that there is an icon given
456  if(icon.isEmpty()){
457    //Try to use a built in icon if a known DE
458    if(name.toLower().contains("gnome")){icon = ":images/gnome.png"; }
459    if(name.toLower().contains("kde")){icon = ":images/kde.png"; }
460    if(name.toLower().contains("xfce")){icon = ":images/xfce.png"; }
461    if(name.toLower().contains("lxde")){icon = ":images/lxde.png"; }
462    if(name.toLower().contains("fluxbox")){icon = ":images/fluxbox.png"; }
463    if(name.toLower().contains("openbox")){icon = ":images/openbox.png"; }
464  }
465  //Format the results into the output list
466  output.clear();
467  output << exec << lname << lcomm << icon << tryexec << name;
468  return output;
469
470}
471
472void Backend::readSystemUsers(){
473  //make sure the lists are empty
474  usernameList.clear(); displaynameList.clear(); homedirList.clear();
475  QStringList uList;   
476  bool usepw = true; //for testing purposes
477  if(usepw){
478    //Use "getent" to get all possible users
479    QProcess p;
480    p.setProcessChannelMode(QProcess::MergedChannels);
481      QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
482      env.insert("MM_CHARSET","UTF-8");
483    p.setProcessEnvironment(env);
484    p.start("getent passwd");
485    while(p.state()==QProcess::Starting || p.state() == QProcess::Running){
486      p.waitForFinished(200);
487      QCoreApplication::processEvents();
488    }
489    uList = QString( p.readAllStandardOutput() ).split("\n");
490   
491    //Remove all users that have:
492   for(int i=0; i<uList.length(); i++){
493    bool bad = FALSE;
494    // "nologin" as their shell
495    if(uList[i].section(":",6,6).contains("nologin")){bad=TRUE;}
496    // "nonexistent" as their user directory
497    else if(uList[i].section(":",5,5).contains("nonexistent")){bad=TRUE;}
498    // uid > 1000
499    else if(uList[i].section(":",2,2).toInt() < 1000){bad=TRUE;}
500
501    //See if it failed any checks
502    if(bad){ uList.removeAt(i); i--; }
503    else{
504      //Add this user to the lists if it is good
505      usernameList << uList[i].section(":",0,0).simplified();
506      displaynameList << uList[i].section(":",4,4).simplified();
507      homedirList << uList[i].section(":",5,5).simplified();
508      usershellList << uList[i].section(":",6,6).simplified();
509    }
510   }
511  }else{ 
512    //Get all the users from the file "/etc/passwd"
513    QFile PWF("/etc/passwd");
514    if( PWF.open(QIODevice::ReadOnly | QIODevice::Text) ){
515      QTextStream in(&PWF);
516        in.setCodec( "UTF-8" );
517      while( !in.atEnd() ){
518        uList << QString( in.readLine() );
519      }
520      PWF.close();   
521    }
522  //Remove all users that have:
523  for(int i=0; i<uList.length(); i++){
524    bool bad = FALSE;
525    // "nologin" as their shell
526    if(uList[i].section(":",6,6).contains("nologin")){bad=TRUE;}
527    // "nonexistent" as their user directory
528    else if(uList[i].section(":",5,5).contains("nonexistent")){bad=TRUE;}
529    // uid > 1000
530    else if(uList[i].section(":",2,2).toInt() < 1000){bad=TRUE;}
531
532    //See if it failed any checks
533    if(bad){ uList.removeAt(i); i--; }
534    else{
535      //Add this user to the lists if it is good
536      usernameList << uList[i].section(":",0,0).simplified();
537      displaynameList << uList[i].section(":",4,4).simplified();
538      homedirList << uList[i].section(":",5,5).simplified();
539      usershellList << uList[i].section(":",6,6).simplified();
540    }
541  }
542 
543  }
544 
545}
546
547void Backend::readSystemLastLogin(){
548    if(!QFile::exists(DBDIR+"lastlogin")){
549      lastUser.clear();
550      Backend::log("PCDM: No previous login data found");
551    }else{
552      //Load the previous login data
553      QFile file(DBDIR+"lastlogin");
554      if(!file.open(QIODevice::ReadOnly | QIODevice::Text)){
555        Backend::log("PCDM: Unable to open previous login data file");   
556      }else{
557        QTextStream in(&file);
558        lastUser= in.readLine();
559        lastDE= in.readLine();
560        file.close();
561      }
562    } 
563}
564
565void Backend::writeSystemLastLogin(QString user, QString desktop){
566  QFile file1(DBDIR+"lastlogin");
567  if(!file1.open(QIODevice::Truncate | QIODevice::WriteOnly | QIODevice::Text)){
568    Backend::log("PCDM: Unable to save last login data to system directory");     
569  }else{
570    QTextStream out(&file1);
571    out << user << "\n" << desktop;
572    file1.close();
573  }
574
575}
576
577QString Backend::readUserLastDesktop(QString user){
578  QString desktop;
579  QString LLpath = Backend::getUserHomeDir(user) + "/.lastlogin";
580  if(!QFile::exists(LLpath)){
581    Backend::log("PCDM: No previous user login data found for user: "+user);
582  }else{
583    //Load the previous login data
584    QFile file(LLpath);
585    if(!file.open(QIODevice::ReadOnly | QIODevice::Text)){
586      Backend::log("PCDM: Unable to open previous user login file: "+user);   
587    }else{
588      QTextStream in(&file);
589      desktop = in.readLine();
590      file.close();
591    }
592  }
593  return desktop;
594}
595
596void Backend::writeUserLastDesktop(QString user, QString desktop){
597  QFile file2( Backend::getUserHomeDir(user) + "/.lastlogin" );
598  if(!file2.open(QIODevice::Truncate | QIODevice::WriteOnly | QIODevice::Text)){
599    Backend::log("PCDM: Unable to save last login data for user:"+user);         
600  }else{
601    QTextStream out(&file2);
602    out << desktop;
603    file2.close();
604  }
605}
606
607
Note: See TracBrowser for help on using the repository browser.