I promised to explain our difficulties with Qingy and so I am. First of all, I guess it should be time to introduce Qingy, since I’m pretty sure that the average user is unlikely to ever have heard of it. Qingy is a login manager that is designed to be a replacement for both the various desktop managers (GDM, XDM, Slim, …) and the usual agetty+login combination. What Qingy uses is mostly DirectFB, or X; it has some pretty interesting features, such as console sessions support, or saving the last logged in user. It obviously users PAM to do the job.
The agetty+login combination is what you use for classic login on serial ports and tty; agetty is the one who opens and sets up the tty, but it’s login (part of the shadow package) that takes care of logging users in. In the case of Qingy, there are still two components, but they are all part of the same package.
There are a few shortcomings with their current approach to PAM to be honest, such as the fact that they still take care of looking at users’ mail and lastlog without relying on the designed PAM modules (which is some of the reason why PAM make sense: you don’t have to reimplement the same code to do the same task on a number of packages; you just have to call into the modules that are implemented already. But alas, that’s not too common a design. But one of these misdesigns in particular causes ConsoleKit not to be able to provide the user logging in with Qingy to get a proper ConsoleKit session, and not just not getting a local one, but none at all.
At any rate, let me recap how the PAM ConsoleKit Connector (pam_ck_connector
module) works. The design in by itself is actually quite simple: within a session chain, during the opening sequence, the connector opens a connection (sorry) to the ConsoleKit daemon, via D-Bus. As long as this connection is open, then there is an active session. Further work is done to register the session as well as setting up the environment variables to allow the session to be recognized, but that’s about it. The connection is obviously closed when the session is closed, but the session gets de-registered as soon as the connection is closed.
Now it’s time to look at how shadow’s login works.
I’m simplifying a lot here since there are a number of preparative tests that need to be taken before going on with this part; but on the other hand we’re now only focusing on the sequence, rather than the actual code.
First of all as you can see there is a fork in there, which means that the login
process spawns a child; this is needed because both opening and closing the PAM session is a task that should be done with super-user privileges, for creating and removing directories for instance, or updating the lastlogs. Since the user shell has to be executed with reduced privileges instead, you obviously need two processes: one to keep super-user privileges and one to drop them to the user.
In the case of login
as yo can see, the session is opened before the call to fork()
, so the ConsoleKit connection is duplicated in the child; this doesn’t leak further because the descriptor is marked with O_CLOEXEC
; a flag that states that the descriptor is not to be passed over when calling functions like execve()
(which is exactly what the child process does, after dropping privileges to the needed user). The original ConsoleKit connection is kept open on the parent process, and is closed only when the process is killed, or properly completes its life of waiting for the user shell to terminate.
The activity diagram for Qingy is quite similar, actually; the only difference is that the opening and closing of the session is split between the two processes (parent and child); unfortunately this has one serious implication: since the session is opened in the child process, once execve()
is called, the connection is closed, and there is not a copy of it that is kept open. This means that a session is opened, but it’s immediately dropped by executing the user shell itself. The only solution to this is to implement the same sequence that login uses, and execute both the opening and closing of the session in the parent process.
Interestingly, I’m seriously wondering if it is allowed at all by PAM to execute the open and close actions on two different processes, even though the handle structure is copied over. It gets more complicated by the fact that any change added to the handle on session opening is not copied over to the parent’s process handle that is used to close the session. And I’m ready to bet ConsoleKit is far from the only module that has trouble with the way Qingy currently works.
And maybe you can see why we’re taking this long with Samuli to tackle all the issues. We’ve been trying to push as fast as possible those changes that can easily be changed, but now we’re in a situation where we need a much deeper understanding of the software we’re dealing with, and it’s a definite huge amount of software involved: just in the case of Qingy, to actually gather a clue of what’s going on, I had to look at Qingy, ConsoleKit, shadow, Linux-PAM and D-Bus, and this is with me knowing at least how fork()
works. Can you really blame us for taking this much time to get it right, now that we know that what was done two years ago either wasn’t enough or it obsoleted over time?
For those who wonders, the two above UML diagrams have been drawn with Visual Paradigm for UML software — it’s not Free, but since I needed something quite complete for some work projects last year, I had to get something working, Free or not. On the other hand I think it can still be of help to me and to the Free Software movement as it allows me to clarify points, like in this post here.