Valid HTML 4.01 Transitional

Healing X Windows Login

James F. Carter <jimc@math.ucla.edu>, 2009-07-27

I have a unique set of requirements for my laptop which have turned the login process into a really unsatisfactory mess. But I have reconfigured a lot of programs and gotten it into reasonably good shape.

Contents

Login Requirements

Network Control

The requirement to start the Kerberos KDC with no network has been met by creating a loopback net by cowboy programming. The Perl package Linux::TunTap.pm was used; my script reads anything sent to it and retransmits it on the same interface. The KDC accepts this as a real network, and ticket requests can be made to its address and they will be seen and honored.

Here are copies of my network scripts. The network startup script is specific to SuSE Linux (OpenSuSE v11.1, presently) and contains bash-isms, earning hisses and boos from the Debian police: Debian policy is that all admin scripts that they distribute should work with any POSIX-compliant shell.

In former incarnations the network was configured statically and the home WEP key was present in the configuration file. Thus the network could come up early, before it was needed by programs like Kerberos. But it was always traumatic to use the laptop on outside nets, e.g. at work, or with no net. Also, having a WPA key in an unencrypted file violates the rule that secrets must not be made available to thieves.

I have changed over to NetworkManager. This program is controlled by nm-applet in the user's session, which talks to gnome-keyring-manager which stores the WEP or WPA key(s) in its encrypted file. nm-applet is nice enough to start gnome-keyring-manager if not running, which then asks for a password to decrypt the keyring. But I really want to have gnome-keyring-manager start and accept the password during PAM authentication. pam_gnome_keyring.so is available for this purpose.

As an additional feature, gnome-keyring-manager can act as a SSH key agent (but not as a GPG agent) and can load keys using the login password.

However, my login experience is unsatisfactory because the keyring manager started by PAM cannot be found and used in the session. This essay describes my efforts to heal the login experience.

PAM and the Display Manager

PAM, Pluggable Authentication Modules, is a framework whereby a broad range of programs that initiate sessions can use common infrastructure and policies to handle authentication, authorization, session setup and teardown, and password changing. Among the programs that use PAM are:

PAM's documentation is not wonderful, and many calling programs abuse the API. In particular, display managers are erratic in which entry points they call, what user they execute as, and what they do with environment variables set by the PAM modules. (However, in the past few years apparently a lot of work has been done to clean up PAM bugs that were present formerly.) PAM bugs have been the main motivation in the past and present for me to switch window managers.

For reasons discussed in the next section I am using wdm (WINGs Display Manger), but it has PAM bugs which are preventing a clean login, so once again I am searching for a replacement.

Choosing a Display Manager

The display manager's job, on a laptop or desktop machine, is to start the X-Server (or whatever alternate graphics server, e.g. on Microsoft Windows) and then to run a program called the greeter which obtains the user's identity and credential (normally, loginID and password) and then starts a session for the user. When the user logs out this cycle repeats. Modern display managers may have several useful additional features:

On a shared execution server typically the X-Server will be on a remote machine, but the server's display manager can put the greeter's window there and will then start a session that executes on the server but displays on the remote host. This host could be a dedicated X-terminal or could have a general-purpose operating system (Windows or even Linux) on which an X-Server functions as a client application for use with remote sessions. The protocol for getting the greeter onto a remote server is called XDMCP.

In the antediluvian world of teletypes and serial lines the display manager's function is closely analogous to getty, for which login is the greeter.

Wikipedia has this article about X display managers. It lists these display managers (sorted into categories and annotated by jimc):

Mainstream Display Managers
Unfamiliar but Credible Display Managers
Non-Finished, Obsolete and Impaired Windows Managers

A major activity for this document will be testing display managers. I will confine my attention to the first two groups.

Item XDM GDM KDM WDM SLiM Qingy
Pick session NO Yes Yes Yes Yes Yes
Shut down NO Yes Yes Yes ? NO
Custom look Yes Broken Yes Images Yes Yes
Bugs Good PAM probs Good PAM probs Key focus Seg fault
Environment Yes Can't tell Yes NO Yes Yes
Overall rating Useable Forget this Useable
oinkware
PAM bugs Touchy Touchy

Customizing XDM

Finished Screenshot
Screenshot of finished XDM
(Click for full size)

Login Box Geometry

My main issue with XDM, actually with all the display managers, is that with the default configuration the login box goes right in the center of the screen. This is logical: the main purpose of the greeter display is to get your loginID and credential. But the greeter background has to be a solid color, a content-free abstract design, or a tiling of small images: a single image generally also has the main interest in the center, and the login box spoils it. The solution is to move the login box to an edge or corner. The XDM defaults also make it huge, and I needed to shrink it. Finally, the color had to be coordinated with the background photo.

These items were configured in /etc/X11/xdm/Xresources (download the complete file here) which is loaded on the X-Server before Xsetup_0 and the greeter are executed. Here are the key points:

xlogin*greeting: CLIENTHOST on CouchNet

You will certainly want to customize the greeting (title) text. Other texts can be customized as well.

xlogin*geometry: +0-0

The default geometry is to center the greeter, but this can be overridden. I put mine in the lower left corner. A negative location means to put the positive edge (here, the bottom) of the box that many pixels from the positive edge of the screen.

xlogin*Face: Serif-11

The size of the login box is mainly controlled by the font size, which is huge in the default configuration. This spec cuts it almost in half. See the man page for fonts.conf (section 5) for the font name syntax that goes with libXft -- much nicer than the original X-Windows font specs. Serif is the CSS generic font name and should be mapped to some reasonable actually installed font. If you have a decorative favorite, specify it here.

xlogin*greetFace: Serif-14

This font is specially for the title line.

*Foreground: white
*Background: black

These specifications are phrased generically and hence apply to the xmessage and xconsole windows as well.

XConsole.geometry: -0-0

The xconsole is pushed to the lower right corner.

XConsole.text.geometry: 480x130

This is how you set the size of the xconsole; setting it through Console.geometry was ineffective.

Chooser*geometry: 700x500+300+200

I don't use the chooser. If you do, this section could use some love.

GUI Control Elements

The second issue is that I would like to be able to switch session types and to shut down the machine from the greeter screen, like you can in all the other modern display managers. To make this happen I wrote a Perl script called Bootbox (download here) that uses one xmessage that tells the script to reboot or shut down the machine, and another to let the user choose a session type. The latter deposits the result in /var/lib/xdm/dmrc (not $HOME/.xdmrc or some such) since when this script starts up we don't know who the next user is going to be.

The Login Process

All the display managers go through their assigned PAM script and then run a system-provided script which execs the user's session. Here are key details needed to turn the login process from a mess to a joy.

Daemon Startup Strategy

In the login process a recurring theme is seen. You need a particular daemon to run for the life of the session. It can be started various ways:

Since the design of the login process is subject to change without notice, at each stage in starting daemons the designer should check if a previous step has already started it. One of my major problems has been that earlier and better daemon instances (e.g. started by PAM knowing the login password) are superceded by two or three more instances that are useless.

Similarly, be careful that the daemons get killed. The shared execution servers at work seem to accumulate stray dbus, gpg-agent and ssh-agent daemons.

List of Daemons to Start

Session Registration

In one of the PAM phases (typically authentication though authorization or session setup seems more useful) the module pam_ck_connector.so has to be executed to register the session that will start up. This operation must be done as root. The identity of the session is returned and is passed onward as the environment variable XDG_SESSION_COOKIE. Then the session leader, or one of the setup scripts, running as the user, must do the equivalent of /usr/bin/ck-launch-session, giving the session leader process on the command line. This simply confirms that the session is starting, executes the leader, and when it finishes, de-registers the session. To check what sessions have been registered you can use ck-list-sessions.

PolicyKit (as set up by the distro) has policies that restrict access to physical hardware, including the network interface, to the active user(s), that is, those who are logged in to the physical console, as opposed to users logged in remotely. There is also a policy restricting shutdown and reboot to the active user. HAL receives requests such as rebooting, and uses PolicyKit to interpret the applicable policy. ConsoleKit is involved in identifying which user is active and in generating requests to HAL to grant access to devices through POSIX ACLs or actual ownership changes. Use getfacl /dev/whatever to see the ACL, if there is one. Thus successful session registration is important for my login requirements.

Look for a complaint like this in your ~/.xsession-errors file:


(nm-applet:3787): WARNING **: <WARN> applet_dbus_manager_start_service(): Could not acquire the NetworkManagerUserSettings service.
Message: 'Connection ":1.14" is not allowed to own the service "org.freedesktop.NetworkManagerUserSettings" due to security policies in the configuration file

(And nm-applet dies.) This is telling you that your process is not allowed to own the service "org.freedesktop.NetworkManagerUserSettings", i.e. to send user settings to NetworkManager. The reason is that you are not an active user, i.e. logged in at the console, and the reason for that is that your login scripts failed to successfully launch your session.

Dbus for the Session

Every session needs a dbus so all the components can chatter to each other. In a scripted session initiator the right way to start it is to run /usr/bin/dbus-launch --exit-with-session with the session leader process on the command line. It starts the dbus daemon, executes the session leader, and when it finishes, kills the daemon. The contact socket for the daemon is passed onward in the environment variable DBUS_SESSION_BUS_ADDRESS.

Kerberos

Kerberos transitive authentication, one of my login requirements, is not handled by a session daemon -- the Ticket-Granting Ticket is written in a file protected by UNIX file permissions -- but Kerberos authentication is one of the steps in setting up the session.

GPG Key Agent

If you are going to be using PGP encryption you need gpg-agent running. It also can act as a SSH key agent if you tell it --enable-ssh-support. Put the session leader on the command line and the daemon will exit when the session finishes. The agent's contact socket is passed onward in the environment variable GPG_AGENT_INFO (which includes the agent's PID); the SSH agent socket is in SSH_AUTH_SOCK.

Getting gpg-agent into the session is one of my login requirements.

SSH Key Agent

If you are going to do transitive authentication by SSH and if you use the authentic SSH key agent and not a Gnu substitute, you need to run ssh-agent. Put the session leader on the command line, and the agent will exit when the session finishes. The agent's contact socket is passed onward in the environment variable SSH_AUTH_SOCK. For killing the daemon its PID is also available as SSH_AGENT_PID.

SSH transitive authentication is one of my login requirements. But in practice I use the SSH agent facility of the Gnome Keyring Daemon.

Gnome Keyring Daemon

This daemon does crypto operations and manages an encrypted file containing the relevant secret and/or public keys. It can serve as a SSH agent, can hold WEP/WPA keys for NetworkManager to use, and can handle X.509 (RSA) certificates and secret keys (but doesn't do PGP/GPG keys).

The Gnome Keyring Daemon serves two of my login requirements: SSH transitive authentication, and protecting the WEP/WPA keys of the wireless networks I use.

Here are links to the Gnome documentation about Gnome Keyring Daemon:

According to the documentation, there is a plugin for Firefox enabling the browser to use X.509 certificates in the keyring. This capability is important to me and I should get it working.

Seahorse Agent

Here is the Gnome documentation about Seahorse-Agent. It appears to be a successor to the Gnome Keyring Daemon. But it has two problems for my use-case: it isn't in my distro (I have seahorse (the GUI) and seahorse-daemon, but no agent), and the docs do not describe any way to start it in PAM so as to use the login password to decrypt the secrets.

Additional Login-Related Issues

Shutdown and Reboot

/usr/lib/hal/scripts/linux/hal-system-power-shutdown-linux or /usr/lib/hal/scripts/linux/hal-system-power-reboot-linux are execed by HAL when the user sends an appropriate dbus signal and if policy allows the user to do these actions. I have had my X-Server freeze up if killed in the middle of reinitializing the graphics chip (ATI Radeon Mobility X1400, radeon driver). I have hacked the scripts to wait for the user's session to shut down and for the greeter to reappear, before starting the shutdown. If the machine shuts down, the effects of these scripts are seen to happen; in other words, these scripts are the ones actually used.

ConsoleKit also has /usr/lib/ConsoleKit/scripts/ck-system-stop and /usr/lib/ConsoleKit/scripts/ck-system-restart which exec shutdown -h (or -r). These must be a fallback if HAL is not available.

ConsoleKit has a policy in /usr/share/PolicyKit/policy/ConsoleKit.policy which forbids shutdown to inactive users (those logged in at other than the console or another defined seat), and allows shutdown to active users if auth_admin.

One prerequisite is that the session from which the shutdown request is issued must be an active session when HAL makes the decision whether to allow and execute the shutdown script. I suspect, but cannot prove, that xfce (at least) sends the dbus request to shut down, and then quickly exits. That invalidates the session, and the in resulting race condition the shutdown request sometimes loses.

See my script ck-launch-wrapper for a solution.

Device Access: Sound Card

Formerly the sound devices, /dev/snd/*, on my laptop were not being made accessible to the console user, whereas the CDROM gets a POSIX ACL giving the console user permission for it. But on two desktop systems the sound devices also get the ACLs.

/usr/share/hal/fdi/policy/10osvendor/20-acl-management.fdi declares that the ALSA sound devices can be managed by HAL and are in the sound category. (Uniform on working vs non-working hosts.)

/usr/share/PolicyKit/policy/org.freedesktop.hal.device-access.policy declares that active users may access sound category devices (the sound card) and inactive users (logged in to other than the console) may not.

With the new login scripts the sound devices are getting their proper POSIX ACLs. My guess is that once the various tangles in session registration were cleared up, the sound devices were properly made available.

Device Access: Graphics

As with the sound devices, the device files for direct rendering (/dev/nvidia* or /dev/dri/*) were not being made available to the console user. The reason is different, though: the proprietary fglrx (ATI/AMD) and nVidia graphics drivers are allowed to be used with the Linux kernel but are not allowed to create sysfs data about their graphics devices, because of peculiar legal issues with the Gnu Public License. Thus udev, HAL and ConsoleKit have no idea that they exist. So I added a script /etc/ConsoleKit/run-session.d/nvidia-ati.ck which sets the owner of /dev/nvidia* or /dev/dri/* to the console user by cowboy programming.

When an xorg driver such as radeonhd is used, this script is not necessary because ConsoleKit knows about the device and provides an ACL, but it doesn't break things if used.

Annotated PAM Script for X-Windows Login

This thoroughly hacked version of /etc/pam.d/xdm (or gdm) (download here), in conjunction with some Xsession hacks and additional nonstandard kludges, accomplishes all the login requirements. The account and password phases are written out below but the configuration file actually includes the standard common code scripts.

Authentication Phase
auth requisite pam_nologin.so
Kicks off the user before authentication if logins are not allowed. Technically this is an authorization issue, but if /etc/nologin is present the reason probably interferes with looking up authentication information, so it's best to not even try to authenticate the user. (Standard)
auth required pam_env.so
Sets the environment variables configured in /etc/security/pam_env.conf if any. On my system this file contains only comments. (Standard)
auth optional pam_permit.so
Prime the stack with a real success, so Kerberos success will count.
auth [success=ok default=ignore] pam_runscript.so debug try_first_pass passcopies=1 /usr/diklo/bin/vaultlogin
Mounts the Cryptographic Vault, if not yet mounted and if the user has one. It has, among other things, the secret key needed for the Kerberos KDC to start up. The start script for Kerberos delays until the key appears, before the KDC is started. (Requirement)
auth [success=1 default=ignore] pam_krb5.so forwardable renewable try_first_pass
If Kerberos works the next step, pam_unix2, will be omitted; if it's broken we rely on pam_unix2. (Requirement)
auth requisite pam_unix2.so
Fall back to passwd/shadow files if Kerberos failed (or the user gave the wrong password). Omit further steps if this fails. The try_first_pass argument is not necessary; this is the default behavior. (Standard)
#auth requisite pam_homecheck.so
Many sysops want to deny logins to a user whose home directory is unavailable (temporarily or permanently), but I allow the login with the homedir set to /tmp. This is properly an authorization issue, but you want to avoid substantive activities if you're just going to kick off the user.
auth optional pam_ck_connector.so
Notifies ConsoleKit that a session is beginning. This is a prerequisite for ck-launch-session to be functional, which is a prerequisite for the console user to get access to the hardware and to shut down the machine. (Requirement)
auth optional pam_gnome_keyring.so
Doesn't start the daemon; it just saves the login password. (Requirement)
Authorization (Account) Phase

This is mostly standard stuff.

account required pam_nologin.so
Kick off the authenticated user if logins are not allowed. Technically this is an authorization issue so the call belongs here, but see the comments in the auth section. (Standard)
account [success=ok default=ignore] pam_krb5.so
Kerberos does not do authorization, but the idea here is to bypass pam_unix2 if there is a Kerberos credential.
account required pam_unix2.so
Traditionally in UNIX, every user who is authenticated is ipso facto authorized to be on the machine. The man page does not indicate any more complex authorization issues in pam_unix2; in other words this call probably does nothing. (Standard)
Session Phase

When the session is started and when it is closed, the listed modules are called at separate entry points.

session [success=1 default=ignore] pam_krb5.so
The Kerberos credential (Ticket-Granting Ticket) is written or shredded. (Requirement)
session required pam_unix2.so
Only if Kerberos is inoperative, traditional session setup activities are done. (Standard)
session required pam_loginuid.so
Sets the login UID attribute, for process auditing. (Semi-standard)
session required pam_limits.so
Sets system resource limits per /etc/security/limits.conf if any. (Standard)
session optional pam_ck_connector.so
Obtains and sets into the PAM environment the XDG_SESSION_COOKIE. (Requirement)
session optional pam_gnome_keyring.so auto_start
Starts (or kills) the Gnome Keyring Daemon and places its contact socket(s) into the PAM environment. The login password is piped in so it can decrypt the master keyring, which may include links and passwords for additional keyrings. (Requirement)
#session optional pam_mail.so standard
Shows the you have [new] mail message. On a X-Windows login this generally just annoys the user, so I comment it out.
#session required pam_lastlog.so nowtmp
Shows the last login time. If anyone has stolen your passowrd you might notice a discrepancy. However, the message box is very annoying to the user, so I comment it out. Credit on this to a blog post by Rogalikus about lastlog dated 2008-04-15.
session optional pam_dumpenv.so /dev/shm/laptop_setup.jimc
This is the key item to make this all work if you're using wdm. This module writes the entire PAM environment to a file, where Xsession can reload it later.
Password Phase

Most of this is standard stuff, but note these points:

password requisite pam_pwcheck.so nullok
Password strength checking. Very important since most clueless lusers produce passwords that are very easy for the hackers to crack. Alternate strength checking should be configured in /etc/security/pam_pwcheck.conf. (Standard)
password required pam_unix2.so nullok use_authtok
Changes the UNIX password in /etc/shadow. (Standard)
password required pam_krb5.so use_authtok
Changes the Kerberos password. Do not specify use_first_pass as the module would refuse to ask for the old password and would fail.
password optional pam_gnome_keyring.so use_authtok
Changes the encryption on the master keyring. According to the documentation the SSH secret keys in this keyring are not separately encrypted and will be available after the password change.
#password optional pam_ssh.so use_authtok
We all wish that pam_ssh would re-encrypt the secret keys, but it doesn't.
#password required pam_make.so /var/yp
If you use NIS or a similarly engineered substitute such as LDAP, and if everyone changes the password on the master site, pam_make can be useful, but in practice yppasswdd is much more satisfactory.

Xsession Hacks

The various PAM modules which start daemons set environment variables with the daemons' contact sockets etc. Some display managers, specifically wdm, fail to pass this environment to the user's session. If pam_dumpenv has been used to dump the environment to a file, the following addition to /etc/X11/xdm/Xsession can be used to reacquire it. It reports on stderr the disposition of each variable, and this report should end up in ~/.xsession-errors.

lsfile=/dev/shm/laptop_setup.$USER
if [ -r $lsfile ] ; then
    echo "Setting environment from $lsfile" 1>&2
    sed -e 's/=/ /' $lsfile | while read key val junk ; do
        cmd="echo \$$key"
        oval="`eval $cmd`"
        if [ -z "$oval" ] ; then
            echo "export ${key}='$val'"
            echo "    ${key}='$val' (was unset)" 1>&2
        elif [ "$oval" = "$val" ] ; then
            echo "    ${key}='$oval' (unchanged)" 1>&2
        else
            echo "    ${key}='$oval' (file wants '$val')" 1>&2
        fi
    done > $lsfile.xdm
    . $lsfile.xdm
    rm $lsfile.xdm
fi

Here are copies of all my hacked X session startup scripts, in order of being executed.

Window Managers (Session Types)

A Desktop Environment is a coordinated suite of programs that support use of X-Windows programs. The environment can vary in complexity from a simple window manager with useful accompanying configuration files, up to XFCE, Gnome or KDE which fill up 0.5Gb of your RAM.

/usr/share/xsessions contains desktop files for the installed desktop environments: KDE, XFCE, Gnome, etc. The display manager generally reads these files to discover their names and session leader programs. For completeness I have listed and briefly evaluated the desktop environments installed on my machine. However, my conclusion is foregone: I currently use XFCE and I'm not going to change. But it's useful to be familiar with the possibilities.

Except as noted, all the desktop environments ended up with one each of all the required service daemons, with the appropriate environment variables so applications can find them. If the network was brought up, both SSH and Kerberos could be used for transitive authentication on other machines. In the cases where the network was not brought up, SSH and Kerberos were tested successfully connecting to localhost. (Credentials were available.) Permission was checked for /dev/sr0, /dev/dri/card0, /dev/snd/controlC0 and the user got it (except as noted).

Xfce

Nm-applet is autostarted and brings up the network. There is 1 session, is local, is not active. (ck-launch-session is not used.) Lacks permission for /dev/snd/controlC0.

FVWM

Nm-applet was not autostarted but it brings up the net if started by hand. There are 2 sessions, both local, 1st is active, 2nd isn't. (ck-launch-session is used.) The fvwm2 configuration has been locally hacked but SuSE provides a flashy default. At startup the local hack gives you a taskbar with four workspace groups and two menus for programs. Left-middle-right click in the background for window manager action menus. FVWM is configured with smart placement, i.e. the windows appear without overlapping existing windows (if possible). It is also configured for focus-follows-mouse.

Gnome

Nm-applet was autostarted and brought up the net. Could not test kerberos and ssh due to daemon problems. There is 1 session, local but not active. (ck-launch-session is not used.) Lacks permission for /dev/snd/controlC0. At startup you're presented with a taskbar with four workspace groups. The problem here is how to get the main menu, and from there, how to start an xterm. I think I have failed to install some required packages. Limited testing was done by flipping to a text console and starting an xterm from there.

KDM

Not installed on the test machine.

IceWM

Nm-applet was not autostarted but it brings up the net if started by hand. There are 2 sessions, both local, 1st is not active, 2nd is. (ck-launch-session is used.) At startup you're presented with a taskbar with four workspace groups; the main menu is available from the logo there, or right click in the background. IceWM has smart placement, i.e. the windows appear without overlapping existing windows (if possible). The default is click-to-focus (annoying), but this can be configured for focus-follows-mouse.

MWM

Network is not up: nm-applet was not autostarted and I didn'to test starting it by hand. There are 2 sessions, both local, 1st is not active, 2nd is. (ck-launch-session is used.) At startup you're presented with a gray screen. Right click in the background for a menu of window manager actions -- the key one is New Window, which means to launch xterm. MWM has cascade placement, i.e. the windows appear in a nonadaptively computed location on the diagonal of the screen. You always have to move the window. This is annoying. MWM also has click-to-focus, also annoying.

TWM

Network is not up: nm-applet was not autostarted and didn't test starting it by hand. There are 2 sessions, both local, 1st is not active, 2nd is. (ck-launch-session is used.) At startup you're presented with a gray screen. Left click in the background for a menu of window manager actions -- the key one is xterm. TWM does not have smart placement, i.e. for each window you need to individually pick its location. This is annoying.

WindowMaker

Network is not up: nm-applet was not autostarted and didn't test starting it by hand. There are 2 sessions, both local, 1st is active, 2nd isn't. (ck-launch-session is used.) When it starts up you get a taskbar at the bottom. It would help to find/read the documentation, but it looks like this WM may be functional.

Failsafe

Network is not up: nm-applet was not autostarted and didn't test starting it by hand. Session is registered and active but is not local. All you get is one xterm and no window manager. The purpose of the failsafe session is to straighten out messes in your normal desktop environment or customized startup script.

Custom

This is a special case among session types: it does not start any service daemons, but rather runs the user's .xsession or .xinitrc file (or /etc/X11/xdm/sys.xsession if neither exists). Then the user can start whatever service daemons or session leader he pleases, without having to fight with the system's idea of what a desktop environment should be doing to the user.

I believe I may not have session registration set up perfectly for the unfamiliar session managers. Similarly, I have a feeling that the lacking permission for /dev/snd/controlC0 may not be the responsibility of the session manager but rather of session registration.

Conclusion

There are an awful lot of interacting items needed to get a user logged on. After a fair amount of research I have my login procedure pretty much working the way I want.