Complete XDM Working Example

[last changed Oct 4, 2003]

Having spent the better part of an entire day trying to figure out how to configure XDM to accomplish what I wanted, I present a complete, working example of my XDM configuration. From what I found while searching via Google, UseNet news postings and private email, I have combined the information, code, and anything else I felt like using into my configuration. As all of the information was freely offered, I also offer this to anyone for any purpose.

My terminology may not be correct; if anyone is so inclined, feel free to send me any changes you feel would make this a better source of information.

Updated October, 2003

Goal

To be able to use CHOOSER to login to any of the FreeBSD systems on my local LAN from any of the FreeBSD systems.

I will present my changes in order of features. An individual configuration file may be mentioned more than once as each successive feature is added. The complete set of configuration files is also available for download.

Note that your browser may not render certain characters correctly in the following examples. In particular, the grave-quotes (backward-quotes) will most likely be rendered as a normal single-quote.

Solution

XDM manages (via CHOOSER) those systems either in the Xservers file or those that identify themselves over the network as being willing to be managed by XDM. All of XDM's configuration files (XFree86 v4.3.0) are in the directory /usr/X11R6/lib/X11/xdm.

xdm-config

At the bottom of xdm-config, the lines read:
  ! SECURITY: do not listen for XDMCP or Chooser requests
  ! Comment out this line if you want to manage X terminals with xdm
  DisplayManager.requestPort:     0
Therefore, change the last line to:
  !DisplayManager.requestPort:     0

Xaccess

The comments in Xaccess are pretty clear. The two lines shown have been un-commented:
  *                        #any host can get a login window
  *   CHOOSER BROADCAST    #any indirect host can get a chooser

Xservers

XDM, as configured, will provide a nice graphical login screen on the local system. It will not, however, present the CHOOSER menu to allow logging into other systems. This is because the contents of the Xservers file directs XDM to manage the local display. Comment out the line:
  :0 local /usr/X11R6/bin/X
as:
  #:0 local /usr/X11R6/bin/X

Testing

Since XDM will now not start the X server, this must be done manually. To test the configuration up to this point, start xdm:
  # xdm
and then start the X server with the -indirect <hostname> option:
  # X -indirect <use your host name here>
You should see the CHOOSER menu appear with the local system being available for login. Use Ctl-Alt-Backspace to stop the X server.

Further Customization

To wrap things up, I added a clock and some nice buttons to enable shutdown/reboot at the login screen, changed various colors and the screen background, and created startup scripts that run at boot time. If you do not have Tcl/Tk installed, you will not be able to use the buttons.tcl file below.

I've also added support to call sessreg at the beginning/end of the session so as to update /var/log/lastlog, /var/log/wtmp and /var/run/utmp. I found that I could not use sessreg as the man page suggested; using the -l flag with anything other than a valid tty (or an empty string) would cause the w command to issue an error message. In addition, using $DISPLAY as a search string for Xservers will not work when remote systems log in. I had to devise my own method to assign a slot number.

buttons.tcl

A Tcl script to provide the shutdown/reboot buttons.
  #
  # Two buttons, to reboot or halt the system
  #
  if { ! [info exists env(DISPLAY)] } {
    exit
  }
  if { ! [file isdirectory /tmp/.X11-unix] } {
    exit
  }

  set pidFile /var/run/buttons.$env(DISPLAY).pid
  set ipcFile /tmp/.X11-unix/buttons.[pid]

  file delete $ipcFile

  if { [catch {exec mkfifo $ipcFile}] } {
    exit
  }

  # Write our PID
  set fp [open $pidFile w]
  puts -nonewline $fp [pid]
  close $fp

  # Open IPC fifo and set the graceful termination trigger
  proc goaway {} {
    file delete $::pidFile
    file delete $::ipcFile
    exit
  }

  set fp [open $ipcFile w+]
  fileevent $fp readable goaway

  # Establish button event handlers
  proc shutdown {} {
    file delete $::ipcFile
    catch {exec /sbin/shutdown -p now >/dev/null 2>/dev/null}
  }

  proc reboot {} {
    file delete $::ipcFile
    catch {exec /sbin/shutdown -r now >/dev/null 2>/dev/null}
  }

  # Create buttons and wait for either a button press or a termination trigger
  button .t -borderwidth 3 -width 10 -padx 0 -pady 0 -relief raised -text Reboot   -command reboot
  button .b -borderwidth 3 -width 10 -padx 0 -pady 0 -relief raised -text Shutdown -command shutdown

  grid .t -column 0 -row 0
  grid .b -column 0 -row 1

  wm geometry . +10-10

Xsetup_0

Change the background color of the screen and add the shutdown/reboot buttons. Insert the following lines before the final xconsole line:
  /usr/X11R6/bin/xsetroot -solid skyblue4 &

  if [ -r /usr/X11R6/lib/X11/xdm/buttons.tcl ]
  then
    /usr/local/bin/wish /usr/X11R6/lib/X11/xdm/buttons.tcl &
  fi

Setup

A script run when a remote system is given a login menu. Change the background color of the screen and add the shutdown/reboot buttons. Create a file named Setup with the contents:
  #!/bin/sh
  /usr/X11R6/bin/xsetroot -solid skyblue4 &

  if [ -r /usr/X11R6/lib/X11/xdm/buttons.tcl ]
  then
    /usr/local/bin/wish /usr/X11R6/lib/X11/xdm/buttons.tcl &
  fi
Make the file executable: chmod +x Setup

GiveConsole

Get rid of the shutdown/reboot buttons after a successful login and use sessreg to record the start of a session. Insert the following before the chown $USER /dev/console line:
  # Register login
  getTtySlot() {
    slot=0
    while read line
    do
      case "$line" in
	\#*)
	;;
	ttyv*)
	  slot=`expr $slot + 1`
	  eval set -- $line
	  if [ "$4" = off ]
	  then
	    echo tty=$1 slot=$slot
	    return
	  fi
	;;
	*)
	  slot=`expr $slot + 1`
	;;
      esac
    done </etc/ttys
  }

  eval `getTtySlot`

  if [ -n "$tty" ]
  then
    sessreg -a -s $slot -l $tty $USER
  fi

  # Remove the shutdown/reboot buttons
  if [ -r /var/run/buttons.$DISPLAY.pid ]
  then
    read pid </var/run/buttons.$DISPLAY.pid
    if [ -n "$pid" ]
    then
      if [ -p /tmp/.X11-unix/buttons.$pid ]
      then
	echo >/tmp/.X11-unix/buttons.$pid
      else
	# Unfortunately this leaves buttons.tcl registered as an active
	# Tk interpreter; applications such as tkcon will erroneously think
	# its still active
	kill -9 $pid 2>/dev/null
      fi
    fi
  fi

TakeConsole

Use sessreg to record the end of a session. Insert the following before the chmod 622 /dev/console line:
  getTtySlot() {
    slot=0

    while read line
    do
      case "$line" in
	\#*)
	;;
	ttyv*)
	  slot=`expr $slot + 1`
	  eval set -- $line
	  if [ "$4" = off ]
	  then
	    echo tty=$1 slot=$slot
	    return
	  fi
	;;
	*)
	  slot=`expr $slot + 1`
	;;
      esac
    done </etc/ttys
  }

  eval `getTtySlot`

  if [ -n "$tty" ]
  then
    sessreg -d -s $slot -l $tty $USER
  fi

Startup

Get rid of the shutdown/reboot buttons after a remote system has completed a successful login and use sessreg to record the start of a session. Create a file named Startup with the contents:
  #!/bin/sh

  # Register a remote login
  lock() {
    while true
    do
      if mkdir /tmp/.X11-unix/slotLock
      then
	break
      fi
      sleep 1
    done
  }

  unlock() {
    rmdir /tmp/.X11-unix/slotLock
  }

  slotFile=/tmp/.X11-unix/slots

  lock

  if [ -f $slotFile ]
  then
    if ! fgrep -q -x $DISPLAY $slotFile
    then
      echo $DISPLAY >>$slotFile
    fi
  else
    echo $DISPLAY >$slotFile
  fi

  unlock

  m=`grep -v '^#' /etc/ttys | wc -l`
  n=`fgrep -n -x $DISPLAY $slotFile | cut -d: -f1`
  slot=`expr $m + $n`

  sessreg -a -l "" -s $slot -h $DISPLAY $USER

  # Remove the shutdown/reboot buttons
  if [ -r /var/run/buttons.$DISPLAY.pid ]
  then
    read pid </var/run/buttons.$DISPLAY.pid
    if [ -n "$pid" ]
    then
      if [ -p /tmp/.X11-unix/buttons.$pid ]
      then
	echo >/tmp/.X11-unix/buttons.$pid
      else
	# Unfortunately this leaves buttons.tcl registered as an active
	# Tk interpreter; applications such as tkcon will erroneously think
	# its still active
	kill -9 $pid 2>/dev/null
      fi
    fi
  fi
Make the file executable: chmod +x Startup

Xreset

Run sessreg at the termination of the remote session. Create a file named Xreset with the contents:
  #!/bin/sh
  slotFile=/tmp/.X11-unix/slots

  if [ -f $slotFile ]
  then
    if fgrep -q -x $DISPLAY $slotFile
    then
      m=`grep -v '^#' /etc/ttys | wc -l`
      n=`fgrep -n -x $DISPLAY $slotFile | cut -d: -f1`
      slot=`expr $m + $n`

      sessreg -d -l "" -s $slot -h $DISPLAY $USER
    fi
  fi
Make the file executable: chmod +x Xreset

Chooser

A script to set the background color and show a clock while chooser runs. Xclock could be used if the oclock program is not installed. Create a file named Chooser with the contents:
  #!/bin/sh
  /usr/X11R6/bin/xsetroot -solid skyblue4 &
  /usr/X11R6/bin/oclock -geometry +0-0 &
  /usr/X11R6/lib/X11/xdm/chooser "$@"
  kill -KILL $! 2>/dev/null
Make the file executable: chmod +x Chooser

xdm-config

Activate the additional scripts. Add the following lines somewhere after the DisplayManager._0.: lines:
  DisplayManager*setup:       /usr/X11R6/lib/X11/xdm/Setup
  DisplayManager*startup:     /usr/X11R6/lib/X11/xdm/Startup
  DisplayManager*reset:       /usr/X11R6/lib/X11/xdm/Xreset
  DisplayManager*chooser:     /usr/X11R6/lib/X11/xdm/Chooser

pixmaps/bsd.xpm

In order to replace the XFree86 logo on the login screen with a picture of the BSD daemon, we need a pixmap of the daemon. One can be made from /usr/share/examples/BSD_daemon/beastie.fig. Install the xfig port/package and then run:

  # cd /usr/share/examples/BSD_daemon
  # fig2dev -L xpm -m 0.5 beastie.fig > /usr/X11R6/lib/X11/xdm/pixmaps/bsd.xpm
To make the background transparent, patch bsd.xpm with:
  --- bsd.xpm.orig        Sun Nov 17 19:20:44 2002
  +++ bsd.xpm     Sun Nov 17 19:25:49 2002
  @@ -1,13 +1,12 @@
   /* XPM */
   static char *noname[] = {
   /* width height ncolors chars_per_pixel */
  -"212 231 7 1",
  +"212 231 6 1",
   /* colors */
   "  c #000000",
   ". c #00FFFF",
   "X c #D10000",
  -"o c #FFFFFF",
  +"o c none",
   "O c #00B000",
   "+ c #FFD700",
  -"@ c None",
   /* pixels */

Xresources

Change some colors, geometry and text, and change the login screen logo.

For placement of the CHOOSER window:

  Chooser*geometry:             500x200+150+200
To change the title on the CHOOSER menu:
  Chooser*label.label:          Dave's Home Computing Empire
Do not provide a font for the menu list:
  !Chooser*list.font:           -*-*-medium-r-normal-*-*-230-*-*-c-*-iso8859-1
Not sure what this does...
  Chooser*ShapeStyle:           Oval
Use a different font:
  Chooser*Command.font:         -adobe-helvetica-bold-r-*-*-12-*
Add some pleasing colors:
  #ifdef COLOR
  Chooser*label.foreground:     white
  Chooser*label.background:     midnightblue
  Chooser*Command.background:   gray80
  Chooser*internalBorderColor:  black
  Chooser*viewport.useRight:    true
  #endif
Replace the XFree86 logo with our beloved BSD daemon:
  xlogin*logoFileName:          /usr/X11R6/lib/X11/xdm/pixmaps/bsd.xpm

Starting XDM and X

Boot-time startup files are added to the /usr/X11R6/etc/rc.d directory. Create this directory if it does not exist. If /etc/defaults/rc.conf does not contain a line looking like:

  local_startup="/usr/local/etc/rc.d /usr/X11R6/etc/rc.d"
then add the above line to the /etc/rc.conf file. Finally create two files in /usr/X11R6/etc/rc.d:

00xdm.sh

  #!/bin/sh
  XDM=/usr/X11R6/bin/xdm
  case "$1" in
    start)
      [ -x $XDM ] && ( $XDM ) && echo -n ' xdm'
      ;;
    stop)
      if [ -f /var/run/xdm.pid ]
      then
	kill -KILL `cat /var/run/xdm.pid` 2>/dev/null && echo -n ' xdm'
      fi
      ;;
    *)
      echo "Usage: `basename $0` {start|stop}" >&2
      ;;
  esac
  exit 0

90X.sh

  #!/bin/sh
  X=/usr/X11R6/bin/X
  case "$1" in
    start)
      [ -x $X ] && ( sleep 5 && $X -wm -indirect `hostname` & ) && echo -n ' X'
      ;;
    stop)
      killall XFree86 && echo -n ' X'
      ;;
    *)
      echo "Usage: `basename $0` {start|stop}" >&2
      ;;
  esac
  exit 0
Make them both executable: chmod +x 00xdm.sh 90X.sh

The "sleep 5" above allows you to see the final messages on your console when you boot before the X server starts. (It might also be required so that all the getty's start properly... I'm not sure about this.) You can always use Ctl-Alt-F1 to go back, but I like to see the root login prompt after I boot.

Make the same changes to each system on your network. When completed, you will be given a CHOOSER menu on each system allowing you to login to any system you choose.

Bugs

The /var/run/buttons.pid file is a poor choice -- if multiple users login at once, the file will be overwritten by the last user. A better choice would be to name the file based on the host:screen. As I cannot login simultaneously, this does not bother me. [Fixed October 2003]

When wish is killed, it leaves itself registerd as an active Tk interpreter. Later Tk applications (such as tkcon) will erroneously think that it is still active. [Fixed October 2003]

The user's connection does not show up with the w or who commands. [Fixed October 2003]

The shutdown/reboot buttons appear on the login screen regardless of which system is given the login screen. Therefore, system A can reboot system B. This is by design. I am the only user on my network, so this does not bother me.

If I use ^R to exit the login screen, I get another login screen. The second time I use ^R, I finally go back to the CHOOSER screen. I don't know why this is happening. It's a minor inconvenience, so I am not going to pursue it further.


Hit counter: counter