source: src-qt4/PCDM/src/pcdm-xprocess.cpp @ fbbd5c0

9.2-releasereleng/10.0releng/10.0.1
Last change on this file since fbbd5c0 was fbbd5c0, checked in by Ken Moore <ken@…>, 5 months ago

Make sure that a LANG of C is not set in the user session, use en_US instead (PCDM default locale)

  • Property mode set to 100644
File size: 12.9 KB
Line 
1/* PCDM Login Manager:
2*  Written by Ken Moore (ken@pcbsd.org) 2012/2013
3*  Modified by Kris Moore (kris@pcbsd.org) 2013
4*  Copyright(c) 2013 by the PC-BSD Project
5*  Available under the 3-clause BSD license
6*/
7
8#include <sys/types.h>
9#include <sys/wait.h>
10#include <unistd.h>
11#include <pwd.h>
12#include <login_cap.h>
13#include <QMessageBox>
14
15/*
16Sub-classed QProcess for starting an XSession Process
17*/
18
19#include "pcdm-xprocess.h"
20
21XProcess::XProcess() : QProcess(0) {
22  //initialize the variables
23  xuser.clear();
24  xcmd.clear();
25  xhome.clear();
26  xpwd.clear();
27  xshell.clear();
28  pam_started = FALSE;
29  pam_session_open = FALSE;
30  //Setup the finished signal/slot
31  connect( this, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotCleanup(int, QProcess::ExitStatus)) );
32}
33
34XProcess::~XProcess(){
35  if( isRunning() ){
36    this->terminate();
37  }
38  this->close();
39}
40
41void XProcess::loginToXSession(QString username, QString password, QString desktop){
42  //Setup the variables
43  xuser = username;
44  xpwd = password;
45  xhome = Backend::getUserHomeDir(xuser);
46  xcmd = Backend::getDesktopBinary(desktop);
47  xshell = Backend::getUserShell(xuser);
48  xde = desktop;
49  //Now start the login process
50  startXSession();
51}
52
53bool XProcess::isRunning(){
54  if(this->state() != QProcess::NotRunning){ return TRUE; }
55  else{ return FALSE; }
56}
57
58void XProcess::waitForSessionClosed(){
59  // CAUTION!!
60  // This function will pause the calling program to wait for the session to end!
61  if( isRunning() ){ this->waitForFinished(-1); }
62
63}
64
65/*
66 ========== SESSION STARTUP ==========
67*/
68
69bool XProcess::startXSession(){
70  //Check that the necessary info to start the session is available
71  if( xuser.isEmpty() || xcmd.isEmpty() || xhome.isEmpty() || xde.isEmpty() ){
72    emit InvalidLogin();  //Make sure the GUI knows that it was a failure
73    return FALSE;
74  }
75  //Backend::log("Starting up Desktop environment ("+xcmd+") as user ("+xuser+")");
76 
77  //Check for PAM username/password validity
78  if( !pam_checkPW() ){ emit InvalidLogin(); pam_shutdown(); return FALSE; }
79
80
81  //Save the current user/desktop as the last login
82  Backend::saveLoginInfo(Backend::getDisplayNameFromUsername(xuser),xde);
83
84  // Get the users uid/gid information
85  struct passwd *pw;
86  int uid;
87  char *ok;
88
89  if (!(pw = getpwnam(xuser.toLatin1()))) {
90      uid = strtol(xuser.toLatin1(), &ok, 10);
91      if (!(pw = getpwuid(uid))) {
92          emit InvalidLogin();  //Make sure the GUI knows that it was a failure
93          return FALSE;
94      }
95  }
96
97  // Get the environment before we drop priv
98  this->setProcessEnvironment( QProcessEnvironment::systemEnvironment() ); //current environment
99  //Now allow this user access to the Xserver
100  QString xhostcmd = "xhost si:localuser:"+xuser;
101  system(xhostcmd.toUtf8());
102 
103  //QWidget *wid = new QWidget();
104  if (setgid(pw->pw_gid) < 0) {
105      qDebug() << "setgid() failed!";
106      emit InvalidLogin();  //Make sure the GUI knows that it was a failure
107      return FALSE;
108  }
109
110  // Setup our other groups
111  if (initgroups(xuser.toLatin1(), pw->pw_gid) < 0) {
112      qDebug() << "initgroups() failed!";
113      emit InvalidLogin();  //Make sure the GUI knows that it was a failure
114      setgid(0);
115      return FALSE;
116  }
117
118  // Lets drop to user privs
119  if (setuid(pw->pw_uid) < 0) {
120      qDebug() << "setuid() failed!";
121      emit InvalidLogin();  //Make sure the GUI knows that it was a failure
122      return FALSE;
123  }
124
125  //Startup the PAM session
126  if( !pam_startSession() ){ pam_shutdown(); return FALSE; }
127  pam_session_open = TRUE; //flag that pam has an open session
128 
129  QString cmd;
130  // Configure the DE startup command
131
132  //  - Add the DE startup command to the end
133  cmd.append("dbus-launch --exit-with-session "+xcmd);
134  //cmd.append(xcmd);
135  //cmd.append("; kill -l KILL"); //to clean up the session afterwards
136 
137  //Backend::log("Startup command: "+cmd);
138  // Setup the process environment
139  setupSessionEnvironment();
140 
141  //Log the DE startup outputs as well
142  this->setStandardOutputFile(xhome+"/.pcdm-startup.log",QIODevice::Truncate);
143  this->setStandardErrorFile(xhome+"/.pcdm-startup.err",QIODevice::Truncate);
144  // Startup the process(s)
145   //  - Setup to run the user's <home-dir>/.xprofile startup script
146  if(QFile::exists(xhome+"/.xprofile")){
147    disconnect(SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotCleanup(int, QProcess::ExitStatus)) );
148    this->start("sh "+xhome+"/.xprofile &");//make sure to start it in parallel
149    if(!this->waitForFinished(30000) ){
150      //If it still has not finished this after 30 seconds, kill it
151      this->terminate();
152    }
153    connect( this, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotCleanup(int, QProcess::ExitStatus)) );
154  }
155  this->start(cmd);
156  return TRUE;
157}
158
159void XProcess::slotCleanup(int exitCode, QProcess::ExitStatus status){
160  pam_shutdown(); //make sure that PAM shuts down properly
161  //Now remove this user's access to the Xserver
162  QString xhostcmd = "xhost -si:localuser:"+xuser;
163  system(xhostcmd.toUtf8());
164}
165
166void XProcess::setupSessionEnvironment(){
167  // Setup any specialized environment variables
168  QProcessEnvironment environ = this->processEnvironment();
169  // Get the current locale code
170  QLocale mylocale;
171  QString langCode = mylocale.name();
172  if( langCode.toLower() == "c" ){ langCode = "en_US"; } // default to the US english (PCDM code default), LANG=C causes problems
173  if(!environ.value("MM_CHARSET").isEmpty() ){ langCode.append( "."+environ.value("MM_CHARSET") ); }
174  else{ langCode.append(".UTF-8"); }
175  // USER, HOME, and SHELL are set by the "su" login
176  environ.insert("LOGNAME",xuser); //Login name
177  environ.insert("USERNAME",xuser); // Username
178  environ.insert("USER",xuser); // Username
179  environ.insert("PATH",environ.value("PATH")+":"+xhome+"/bin"); // Append the user's home dir to the path
180  environ.insert("LANG",langCode); //Set the proper localized language
181  environ.insert("MAIL","/var/mail/"+xuser); //Set the mail variable
182  environ.insert("GROUP",xuser); //Set the proper group id
183  environ.insert("SHLVL","0"); //Set the proper shell level
184  environ.insert("HOME",xhome); //Set the users home directory
185  environ.insert("SHELL",xshell); //Set the user's default shell
186  this->setProcessEnvironment(environ);
187  this->setWorkingDirectory(xhome); //set the current directory to the user's home directory
188}
189/*
190//Start the desktop in the current process with C functions
191void XProcess::startDesktop(){
192  //Check for PAM username/password validity
193  if( !pam_checkPW() ){ qDebug() << "Invalid username/password"; pam_shutdown(); return; }
194  //Startup the PAM session
195  if( !pam_startSession() ){ qDebug() << "Unable to open PAM session"; pam_shutdown(); return; }
196  pam_session_open = TRUE; //flag that pam has an open session
197 
198  //Save the current user/desktop as the last login
199  Backend::saveLoginInfo(Backend::getDisplayNameFromUsername(xuser),xde);
200
201  // We will now fork off, so the child can drop root perms and do its thing
202  int pid = fork();
203  if(pid < 0){
204    Backend::log("Error: Could not fork for user permissions");
205    return;
206  }else if( pid !=0 ){
207    //Parent (calling) process
208    int status;
209    sleep(2);
210    waitpid(pid,&status,0); //wait for the child (session) to finish
211
212    // Child is all done, lets close down the pam session and cleanup
213    pam_shutdown();
214    exit(0);
215  }
216 
217  // Get the users uid/gid information
218  struct passwd *pw;
219  int uid;
220  char *ok;
221
222  if (!(pw = getpwnam(xuser.toLatin1()))) {
223      uid = strtol(xuser.toLatin1(), &ok, 10);
224      if (!(pw = getpwuid(uid))) {
225          emit InvalidLogin();  //Make sure the GUI knows that it was a failure
226          return;
227      }
228  }
229
230  if (setgid(pw->pw_gid) < 0) {
231      qDebug() << "setgid() failed!";
232      emit InvalidLogin();  //Make sure the GUI knows that it was a failure
233      return;
234  }
235
236  // Setup our other groups
237  if (initgroups(xuser.toLatin1(), pw->pw_gid) < 0) {
238      qDebug() << "initgroups() failed!";
239      emit InvalidLogin();  //Make sure the GUI knows that it was a failure
240      setgid(0);
241      return;
242  }
243
244  // Lets drop to user privs
245  if (setuid(pw->pw_uid) < 0) {
246      qDebug() << "setuid() failed!";
247      emit InvalidLogin();  //Make sure the GUI knows that it was a failure
248      return;
249  }
250
251  QString cmd;
252  // Configure the DE startup command
253  //  - Setup to run the user's <home-dir>/.xprofile startup script
254  if(QFile::exists(xhome+"/.xprofile")){
255    cmd.append(". "+xhome+"/.xprofile; ");  //make sure to start it in parallel
256  }
257  //  - Add the DE startup command to the end
258  cmd.append("dbus-launch --exit-with-session "+xcmd);
259
260  // Get the current locale code
261  QLocale mylocale;
262  QString langCode = mylocale.name();
263 
264  //Alternate way of starting a process using c library functions
265 
266  //setup the environment variables
267  setenv("LOGNAME",xuser.toUtf8(),1);
268  setenv("USERNAME",xuser.toUtf8(),1);
269  QString pth = QString(getenv("PATH"))+":"+xhome+"/bin";
270  setenv("PATH",pth.toUtf8(),1);
271  if(langCode.toLower()=="c"){}
272  else if(QString(getenv("MM_CHARSET")).isEmpty() ){ langCode.append("."+QString(getenv("MM_CHARSET"))); }
273  else{ langCode.append(".UTF-8"); }
274  setenv("LANG",langCode.toUtf8(),1);
275  setenv("MAIL",QString("/var/mail/"+xuser).toUtf8(),1);
276  setenv("GROUP",xuser.toUtf8(),1);
277  setenv("HOME",xhome.toUtf8(),1);
278  setenv("SHELL",pw->pw_shell,1);
279  setenv("SHLVL","0",1);
280  chdir(xhome.toUtf8()); //move to home dir
281   
282  //Now start the process
283  system(cmd.toLatin1());
284}
285*/
286 
287/*void XProcess::setupDesktop(QString user, QString pwd, QString desktop){
288  //Setup internal variables
289  xuser = Backend::getUsernameFromDisplayname(user);
290  xpwd = pwd;
291  xhome = Backend::getUserHomeDir(xuser);
292  xcmd = Backend::getDesktopBinary(desktop);
293  xde = desktop;
294  //Also check password  validity
295  bool ok = pam_checkPW();
296  pam_shutdown();
297  if(ok){ emit ValidLogin(); }
298  else{ emit InvalidLogin(); }
299}
300  */
301 
302
303//Stand-alone function to check a username/password combination for validity
304void XProcess::checkPW(QString user, QString pwd){
305  xuser = Backend::getUsernameFromDisplayname(user); 
306  xpwd = pwd;
307  bool ok = pam_checkPW();
308  pam_shutdown();
309  xuser.clear();
310  xpwd.clear();
311  if(ok){ emit ValidLogin(); }
312  else{ emit InvalidLogin(); }
313}
314
315/*
316 ========== PAM FUNCTIONS ==========
317*/
318static struct pam_conv pamc = { openpam_nullconv, NULL };
319
320bool XProcess::pam_checkPW(){
321 //Requires internal "xuser" and "xpwd" variables to be set
322       
323//Convert the inputs to C character arrays for use in PAM
324  QByteArray tmp = xuser.toUtf8();
325  char* cUser = tmp.data();
326  QByteArray tmp2 = xpwd.toUtf8();
327  char* cPassword = tmp2.data();
328  //initialize variables
329  bool result = FALSE;
330  int ret;
331  //Initialize PAM
332  ret = pam_start("login", cUser, &pamc, &pamh);
333  if( ret == PAM_SUCCESS ){
334    pam_started = TRUE; //flag that pam is started
335    //Place the user-supplied password into the structure
336    ret = pam_set_item(pamh, PAM_AUTHTOK, cPassword);
337    //Set the TTY
338    //ret = pam_set_item(pamh, PAM_TTY, "pcdm-terminal");
339    //Authenticate with PAM
340    ret = pam_authenticate(pamh,0);
341    if( ret == PAM_SUCCESS ){
342      //Check for valid, unexpired account and verify access restrictions
343      ret = pam_acct_mgmt(pamh,0);
344      if( ret == PAM_SUCCESS ){ result = TRUE; }
345   
346    }else{
347      pam_logFailure(ret);
348    }
349  }
350  //return verification result
351  return result;       
352}
353
354bool XProcess::pam_startSession(){
355  //This should only be run if pam_checkPW was successful
356  int ret = pam_open_session(pamh,0);
357  bool ok = FALSE;
358  if(ret == PAM_SUCCESS){ ok = TRUE; }
359  else{ pam_logFailure(ret); }
360 
361  return ok;
362}
363
364bool XProcess::pam_stopSession(){
365  //This should only be run if pam_startSession was successful
366  int ret = pam_close_session(pamh,0);
367  bool ok = FALSE;
368  if(ret == PAM_SUCCESS){ ok = TRUE; }
369  else{ pam_logFailure(ret); }
370 
371  return ok;
372}
373
374void XProcess::pam_logFailure(int ret){
375  //Interpret a PAM error message and log it
376  Backend::log("PAM Error: " + QString::number(ret));
377  switch( ret ){
378  case PAM_ABORT:
379    Backend::log(" - PAM abort error");
380    break;
381  case PAM_AUTHINFO_UNAVAIL:
382    Backend::log(" - Authentication info unavailable");
383    break;
384  case PAM_AUTH_ERR:
385    Backend::log(" - Authentication error");
386    break;
387  case PAM_BUF_ERR:
388    Backend::log(" - Buffer error");
389    break;
390  case PAM_CONV_ERR:
391    Backend::log(" - Conversion error");
392    break;
393  case PAM_CRED_INSUFFICIENT:
394    Backend::log(" - Credentials insufficient");
395    break;
396  case PAM_MAXTRIES:
397    Backend::log(" - Maximum number of tries exceeded");
398    break;
399  case PAM_PERM_DENIED:
400    Backend::log(" - Permission denied");
401    break;
402  case PAM_SERVICE_ERR:
403    Backend::log(" - Service error");
404    break;
405  case PAM_SYMBOL_ERR:
406    Backend::log(" - Symbol error");
407    break;
408  case PAM_SYSTEM_ERR:
409    Backend::log(" - System error");
410    break;
411  case PAM_USER_UNKNOWN:
412    Backend::log(" - Unknown user");
413    break;
414  default:
415    Backend::log(" - Unrecognized authentication error");
416  }
417       
418}
419
420void XProcess::pam_shutdown(){
421  if(pam_session_open){
422    pam_stopSession();
423    pam_session_open = FALSE;
424  }
425  if(pam_started){
426    pam_end(pamh,0);
427    pam_started = FALSE;
428  }
429}
Note: See TracBrowser for help on using the repository browser.