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

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

Just for completeness, make sure that the /etc/passwd file gets closed after reading system users.

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