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

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

Get the PCDM dbus-launch and ~/.xprofile integration working again.

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