]> rtime.felk.cvut.cz Git - sojka/lightdm.git/blob - liblightdm-qt/greeter.cpp
Renamed to lose LDM prefix on classes in Qt lib.
[sojka/lightdm.git] / liblightdm-qt / greeter.cpp
1 #include "greeter.h"
2
3 #include "user.h"
4 #include "sessionsmodel.h"
5
6 #include <security/pam_appl.h>
7 #include <pwd.h>
8 #include <errno.h>
9
10 #include <QtNetwork/QHostInfo> //needed for localHostName
11 #include <QtCore/QDebug>
12 #include <QtCore/QDir>
13 #include <QtCore/QVariant>
14 #include <QtCore/QFile>
15 #include <QtDBus/QDBusPendingReply>
16
17
18 typedef enum
19 {
20     /* Messages from the greeter to the server */
21     GREETER_MESSAGE_CONNECT                 = 1,
22     GREETER_MESSAGE_START_AUTHENTICATION    = 2,
23     GREETER_MESSAGE_CONTINUE_AUTHENTICATION = 3,
24     GREETER_MESSAGE_LOGIN                   = 4,
25     GREETER_MESSAGE_CANCEL_AUTHENTICATION   = 5,
26
27     /* Messages from the server to the greeter */
28     GREETER_MESSAGE_CONNECTED               = 101,
29     GREETER_MESSAGE_QUIT                    = 102,
30     GREETER_MESSAGE_PROMPT_AUTHENTICATION   = 103,
31     GREETER_MESSAGE_END_AUTHENTICATION      = 104
32 } GreeterMessage;
33
34 #define HEADER_SIZE 8
35
36 using namespace QLightDM;
37
38 class GreeterPrivate
39 {
40 public:
41     QString theme;
42     QString defaultLayout;
43     QString defaultSession;
44     QString timedUser;
45     int loginDelay;
46     
47     SessionsModel *sessionsModel;
48
49     QSettings *config;
50     bool haveConfig;
51
52     QList<User*> users;
53     bool haveUsers;
54
55     QDBusInterface* lightdmInterface;
56     QDBusInterface* powerManagementInterface;
57     QDBusInterface* consoleKitInterface;
58   
59     int toServerFd;
60     int fromServerFd;
61     QSocketNotifier *n;
62     char *readBuffer;
63     int nRead;
64     bool inAuthentication;
65     bool isAuthenticated;
66     QString authenticationUser;
67 };
68
69
70 Greeter::Greeter(QObject *parent) :
71     QObject(parent),
72     d(new GreeterPrivate)
73 {
74     d->readBuffer = (char *)malloc (HEADER_SIZE);
75     d->nRead = 0;
76     d->haveConfig = false;
77     d->haveUsers = false;
78     
79     d->sessionsModel = new SessionsModel(this);
80 }
81
82 Greeter::~Greeter()
83 {
84     delete d->readBuffer;
85     delete d;
86 }
87
88 static int intLength()
89 {
90     return 4;
91 }
92
93 static int stringLength(QString value)
94 {
95     QByteArray a = value.toUtf8();  
96     return intLength() + a.size();
97 }
98
99 void Greeter::writeInt(int value)
100 {
101     char buffer[4];
102     buffer[0] = value >> 24;
103     buffer[1] = (value >> 16) & 0xFF;
104     buffer[2] = (value >> 8) & 0xFF;
105     buffer[3] = value & 0xFF;   
106     if (write(d->toServerFd, buffer, intLength()) != intLength()) {
107         qDebug() << "Error writing to server";
108     }
109 }
110
111 void Greeter::writeString(QString value)
112 {
113     QByteArray a = value.toUtf8();
114     writeInt(a.size());
115     if (write(d->toServerFd, a.data(), a.size()) != a.size()) {
116         qDebug() << "Error writing to server";
117     }
118 }
119
120 void Greeter::writeHeader(int id, int length)
121 {
122     writeInt(id);
123     writeInt(length);
124 }
125
126 void Greeter::flush()
127 {
128     fsync(d->toServerFd);
129 }
130
131 int Greeter::getPacketLength()
132 {
133     int offset = intLength();
134     return readInt(&offset);
135 }
136
137 int Greeter::readInt(int *offset)
138 {
139     if(d->nRead - *offset < intLength()) {
140         qDebug() << "Not enough space for int, need " << intLength() << ", got " << (d->nRead - *offset);
141         return 0;
142     }
143
144     char *buffer = d->readBuffer + *offset;
145     int value = buffer[0] << 24 | buffer[1] << 16 | buffer[2] << 8 | buffer[3];
146     *offset += intLength();
147     return value;
148 }
149
150 QString Greeter::readString(int *offset)
151 {
152     int length = readInt(offset);
153     if(d->nRead - *offset < length) {
154         qDebug() << "Not enough space for string, need " << length << ", got " << (d->nRead - *offset);
155         return "";
156     }
157     char *start = d->readBuffer + *offset;
158     *offset += length;
159     return QString::fromUtf8(start, length);
160 }
161
162 void Greeter::connectToServer()
163 {
164     QDBusConnection busType = QDBusConnection::systemBus();
165     QString ldmBus(qgetenv("LDM_BUS"));
166     if(ldmBus == QLatin1String("SESSION")) {
167         busType = QDBusConnection::sessionBus();
168     }
169
170
171     d->lightdmInterface = new QDBusInterface("org.lightdm.LightDisplayManager", "/org/lightdm/LightDisplayManager", "org.lightdm.LightDisplayManager", busType);
172     d->powerManagementInterface = new QDBusInterface("org.freedesktop.PowerManagement","/org/freedesktop/PowerManagement", "org.freedesktop.PowerManagement");
173     d->consoleKitInterface = new QDBusInterface("org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit");
174
175     char* fd = getenv("LDM_TO_SERVER_FD");
176     if(!fd) {
177        qDebug() << "No LDM_TO_SERVER_FD environment variable";
178        return;
179     }
180     d->toServerFd = atoi(fd);
181
182
183     qDebug() << "***connecting to server";
184     QFile toServer;
185     qDebug() << toServer.open(d->toServerFd, QIODevice::WriteOnly);
186
187     fd = getenv("LDM_FROM_SERVER_FD");
188     if(!fd) {
189        qDebug() << "No LDM_FROM_SERVER_FD environment variable";
190        return;
191     }
192     d->fromServerFd = atoi(fd);
193
194     d->n = new QSocketNotifier(d->fromServerFd, QSocketNotifier::Read);
195     connect(d->n, SIGNAL(activated(int)), this, SLOT(onRead(int)));
196
197     qDebug() << "Connecting to display manager...";
198     writeHeader(GREETER_MESSAGE_CONNECT, 0);
199     flush();
200 }
201
202 void Greeter::startAuthentication(const QString &username)
203 {
204     d->inAuthentication = true;
205     d->isAuthenticated = false;
206     d->authenticationUser = username;
207     qDebug() << "Starting authentication for user " << username << "...";
208     writeHeader(GREETER_MESSAGE_START_AUTHENTICATION, stringLength(username));
209     writeString(username);
210     flush();     
211 }
212
213 void Greeter::provideSecret(const QString &secret)
214 {
215     qDebug() << "Providing secret to display manager";
216     writeHeader(GREETER_MESSAGE_CONTINUE_AUTHENTICATION, intLength() + stringLength(secret));
217     // FIXME: Could be multiple secrets required
218     writeInt(1);
219     writeString(secret);
220     flush();
221 }
222
223 void Greeter::cancelAuthentication()
224 {
225     qDebug() << "Cancelling authentication";
226     writeHeader(GREETER_MESSAGE_CANCEL_AUTHENTICATION, 0);
227     flush();  
228 }
229
230 bool Greeter::inAuthentication() const
231 {
232     return d->inAuthentication;
233 }
234
235 bool Greeter::isAuthenticated() const
236 {
237     return d->isAuthenticated;
238 }
239
240 QString Greeter::authenticationUser() const
241 {
242     return d->authenticationUser;
243 }
244
245 void Greeter::login(const QString &username, const QString &session, const QString &language)
246 {
247     qDebug() << "Logging in as " << username << " for session " << session << " with language " << language;
248     writeHeader(GREETER_MESSAGE_LOGIN, stringLength(username) + stringLength(session) + stringLength(language));
249     writeString(username);
250     writeString(session);
251     writeString(language);
252     flush();
253 }
254
255 void Greeter::loginWithDefaults(const QString &username)
256 {
257     login(username, NULL, NULL);
258 }
259
260 void Greeter::onRead(int fd)
261 {
262     //qDebug() << "Reading from server";
263
264     int nToRead = HEADER_SIZE;
265     if(d->nRead >= HEADER_SIZE)
266         nToRead += getPacketLength();
267   
268     ssize_t nRead;
269     nRead = read(fd, d->readBuffer + d->nRead, nToRead - d->nRead);
270     if(nRead < 0)
271     {
272         qDebug() << "Error reading from server";
273         return;
274     }
275     if (nRead == 0)
276     {
277         qDebug() << "EOF reading from server";
278         return;
279     }  
280
281     //qDebug() << "Read " << nRead << "octets";
282     d->nRead += nRead;
283     if(d->nRead != nToRead)
284         return;
285   
286     /* If have header, rerun for content */
287     if(d->nRead == HEADER_SIZE)
288     {
289         nToRead = getPacketLength();
290         if(nToRead > 0)
291         {
292             d->readBuffer = (char *)realloc(d->readBuffer, HEADER_SIZE + nToRead);
293             onRead(fd);
294             return;
295         }
296     }
297
298     int offset = 0;
299     int id = readInt(&offset);
300     readInt(&offset);
301     int nMessages, returnCode;
302     switch(id)
303     {
304     case GREETER_MESSAGE_CONNECTED:
305         d->theme = readString(&offset);
306         d->defaultLayout = readString(&offset);
307         d->defaultSession = readString(&offset);
308         d->timedUser = readString(&offset);
309         d->loginDelay = readInt(&offset);
310         qDebug() << "Connected theme=" << d->theme << " default-layout=" << d->defaultLayout << " default-session=" << d->defaultSession << " timed-user=" << d->timedUser << " login-delay" << d->loginDelay;
311
312         /* Set timeout for default login */
313         if(d->timedUser != "" && d->loginDelay > 0)
314         {
315             qDebug() << "Logging in as " << d->timedUser << " in " << d->loginDelay << " seconds";
316             //FIXME: d->login_timeout = g_timeout_add (d->login_delay * 1000, timed_login_cb, greeter);
317         }
318         emit connected();
319         break;
320     case GREETER_MESSAGE_QUIT:
321         qDebug() << "Got quit request from server";
322         emit quit();
323         break;
324     case GREETER_MESSAGE_PROMPT_AUTHENTICATION:
325         nMessages = readInt(&offset);
326         qDebug() << "Prompt user with " << nMessages << " message(s)";
327         for(int i = 0; i < nMessages; i++)
328         {
329             int msg_style = readInt (&offset);
330             QString msg = readString (&offset);
331
332             // FIXME: Should stop on prompts?
333             switch (msg_style)
334             {
335             case PAM_PROMPT_ECHO_OFF:
336             case PAM_PROMPT_ECHO_ON:
337                 emit showPrompt(msg);
338                 break;
339             case PAM_ERROR_MSG:
340                 emit showError(msg);
341                 break;
342             case PAM_TEXT_INFO:
343                 emit showMessage(msg);
344                 break;
345             }
346         }
347         break;
348     case GREETER_MESSAGE_END_AUTHENTICATION:
349         returnCode = readInt(&offset);
350         qDebug() << "Authentication complete with return code " << returnCode;
351         d->isAuthenticated = (returnCode == 0);
352         if(!d->isAuthenticated) {
353             d->authenticationUser = "";
354         }
355         emit authenticationComplete(d->isAuthenticated);
356         d->inAuthentication = false;
357         break;
358     default:
359         qDebug() << "Unknown message from server: " << id;
360     }
361
362     d->nRead = 0;
363 }
364
365 QString Greeter::hostname() const
366 {
367     return QHostInfo::localHostName();
368 }
369
370 QString Greeter::theme() const
371 {
372     return d->theme;
373 }
374
375 QVariant Greeter::getProperty(const QString &name) const
376 {
377     return QVariant(); //FIXME TODO
378 }
379
380 QString Greeter::defaultLanguage() const
381 {
382     return getenv("LANG");
383 }
384
385 QString Greeter::defaultLayout() const
386 {
387     return d->defaultLayout;
388 }
389
390 QString Greeter::defaultSession() const
391 {
392     return d->defaultSession;
393 }
394
395 QString Greeter::timedLoginUser() const
396 {
397     return d->timedUser;
398 }
399
400 int Greeter::timedLoginDelay() const
401 {
402     return d->loginDelay;
403 }
404
405 void Greeter::loadConfig()
406 {
407     if(d->haveConfig)
408         return;
409   
410      QString file;
411      if(false)
412        file = d->lightdmInterface->property("ConfigFile").toString();
413      qDebug() << "Loading configuration from " << file;
414      d->config = new QSettings(file, QSettings::IniFormat);
415
416     d->haveConfig = true;
417 }
418
419 void Greeter::loadUsers()
420 {
421     QStringList hiddenUsers, hiddenShells;
422     int minimumUid;
423     QList<User*> users, oldUsers, newUsers, changedUsers;
424
425     loadConfig();
426
427     if(d->config->contains("UserManager/minimum-uid"))
428         minimumUid = d->config->value("UserManager/minimum-uid").toInt();
429     else
430         minimumUid = 500;
431
432     if (d->config->contains("UserManager/hidden-shells"))
433         hiddenUsers = d->config->value("UserManager/hidden-shells").toString().split(" ");
434     else
435         hiddenUsers << "nobody" << "nobody4" << "noaccess";
436
437     if (d->config->contains("UserManager/hidden-shells"))
438         hiddenShells = d->config->value("UserManager/hidden-shells").toString().split(" ");
439     else
440         hiddenShells << "/bin/false" << "/usr/sbin/nologin";
441
442     setpwent();
443
444     while(TRUE)
445     {
446         struct passwd *entry;
447         User *user;
448         QStringList tokens;
449         QString realName, image;
450         QFile *imageFile;
451         int i;
452
453         errno = 0;
454         entry = getpwent();
455         if(!entry)
456             break;
457
458         /* Ignore system users */
459         if(entry->pw_uid < minimumUid)
460             continue;
461
462         /* Ignore users disabled by shell */
463         if(entry->pw_shell)
464         {
465             for(i = 0; i < hiddenShells.size(); i++)
466                 if(entry->pw_shell == hiddenShells.at(i))
467                     break;
468             if(i < hiddenShells.size())
469                 continue;
470         }
471
472         /* Ignore certain users */
473         for(i = 0; i < hiddenUsers.size(); i++)
474             if(entry->pw_name == hiddenUsers.at(i))
475                 break;
476         if(i < hiddenUsers.size())
477             continue;
478  
479         tokens = QString(entry->pw_gecos).split(",");
480         if(tokens.size() > 0 && tokens.at(i) != "")
481             realName = tokens.at(i);
482       
483         QDir homeDir(entry->pw_dir);
484         imageFile = new QFile(homeDir.filePath(".face"));
485         if(!imageFile->exists())
486         {
487             delete imageFile;
488             imageFile = new QFile(homeDir.filePath(".face.icon"));
489         }
490         if(imageFile->exists())
491             image = "file://" + imageFile->fileName();
492         delete imageFile;
493
494         user = new User(entry->pw_name, realName, entry->pw_dir, image, FALSE);
495
496         /* Update existing users if have them */
497         bool matchedUser = false;
498         foreach(User *info, d->users)
499         {
500             if(info->name() == user->name())
501             {
502                 matchedUser = true;
503                 if(info->update(user->realName(), user->homeDirectory(), user->image(), user->isLoggedIn()))
504                     changedUsers.append(user);
505                 delete user;
506                 user = info;
507                 break;
508             }
509         }
510         if(!matchedUser)
511         {
512             /* Only notify once we have loaded the user list */
513             if(d->haveUsers)
514                 newUsers.append(user);
515         }
516         users.append(user);
517     }
518
519     if(errno != 0)
520         qDebug() << "Failed to read password database: " << strerror(errno);
521
522     endpwent();
523
524     /* Use new user list */
525     oldUsers = d->users;
526     d->users = users;
527
528     /* Notify of changes */
529     foreach(User *user, newUsers)
530     {
531         qDebug() << "User " << user->name() << " added";
532         emit userAdded(user);
533     }
534
535     foreach(User *user, changedUsers)
536     {
537         qDebug() << "User " << user->name() << " changed";
538         emit userChanged(user);
539     }
540
541     foreach(User *user, oldUsers)
542     {
543         /* See if this user is in the current list */
544         bool existing = false;
545         foreach(User *new_user, d->users)
546         {
547             if (new_user == user)
548             {
549                 existing = true;
550                 break;
551             }
552         }
553
554         if(!existing)
555         {
556             qDebug() << "User " << user->name() << " removed";
557             emit userRemoved(user);
558             delete user;
559         }
560     }
561 }
562
563 void Greeter::updateUsers()
564 {
565     if (d->haveUsers) {
566         return;
567     }
568   
569     loadConfig();
570
571     /* User listing is disabled */
572     if (d->config->contains("UserManager/load-users") &&
573         !d->config->value("UserManager/load-users").toBool())
574     {
575         d->haveUsers = true;
576         return;
577     }
578
579     loadUsers();
580
581     d->haveUsers = true;
582 }
583
584 QList<User*> Greeter::users()
585 {
586     updateUsers();
587     return d->users;
588 }
589
590 SessionsModel* Greeter::sessionsModel() const
591 {
592     return d->sessionsModel; 
593 }
594
595
596 bool Greeter::canSuspend() const
597 {
598     QDBusReply<bool> reply = d->powerManagementInterface->call("CanSuspend");
599     if (reply.isValid())
600         return reply.value();
601     else
602         return false;
603 }
604
605 void Greeter::suspend()
606 {
607     d->powerManagementInterface->call("Suspend");
608 }
609
610 bool Greeter::canHibernate() const
611 {
612     QDBusReply<bool> reply = d->powerManagementInterface->call("CanHibernate");
613     if (reply.isValid())
614         return reply.value();
615     else
616         return false;
617 }
618
619 void Greeter::hibernate()
620 {
621     d->powerManagementInterface->call("Hibernate");
622 }
623
624 bool Greeter::canShutdown() const
625 {
626     QDBusReply<bool> reply = d->consoleKitInterface->call("CanStop");
627     if (reply.isValid())
628         return reply.value();
629     else
630         return false;
631 }
632
633 void Greeter::shutdown()
634 {
635     d->consoleKitInterface->call("stop");
636 }
637
638 bool Greeter::canRestart() const
639 {
640     QDBusReply<bool> reply = d->consoleKitInterface->call("CanRestart");
641     if (reply.isValid())
642         return reply.value();
643     else
644         return false;
645 }
646
647 void Greeter::restart()
648 {
649     d->consoleKitInterface->call("Restart");
650 }