Linux Programming Hints

In this month's column, I said that I would give a simple screen-locking example that uses the VT, or Virtual Terminal, ioctl()'s that I documented in that column.

In case you can't remember or didn't read
last month's column, the VT ioctl()'s allow you to specify from a
user program what the kernel should do about the virtual terminals,
or virtual consoles. (These are essentially the same. For the rest
of the column, I will refer to them as virtual consoles, not
virtual terminals, for no particular reason).

A program can request that the kernel give it raw scan codes
instead of full keystrokes, can tell the kernel that you are going
into graphics mode on that terminal, and do many other low-level
things. XFree86 uses these ioctl()'s heavily, as does svgalib. The
Linux DOS emulator (which is really a BIOS emulator) uses them, and
the loadable keymaps program (kbd) uses them.

If you didn't read last month's column, the main content of
the column will be included in future man pages to be released by
the Linux man page project.

I have written a program called vlock, which is a screen
locker which can lock virtual consoles. I don't have space here to
reproduce the entire source code, but I will give enough details
for you to easily construct your own similar program. Instructions
for obtaining a copy via anonymous ftp on the Internet follow the
code in this column.

Why?

My original purpose in writing vlock was to demonstrate a use
for the VT ioctl()'s that they had really not been designed for, to
show their flexibility.

If you are like many Linux users, you may have one or two
sessions of X running, and a few console logins active at the save
time, and be switching back and forth between them. Perhaps you
have been editing a program you have been working on, and don't
want your roommates or children to start playing with your files
while you go away from your computer for one reason or another, but
you really don't feel like logging out and restarting all your
sessions.

xlock could solve your problem if you only have an X session
that you want to lock, but anyone can still switch to the console
even when xlock is running. You need a program that can lock all
the sessions at once. Well, maybe you need a program that can lock
all the sessions at once...

How?

My first idea for locking the console was to read raw
scancodes from the keyboard instead of reading normal characters,
and to ignore anything but the scancodes for alphanumeric keys, the
shift key, the caps lock key, and the control key, and write a
state machine to get keys from that. This would automatically
ignore the ALT-Fn keys that are normally used to switch from
virtual console to virtual console, so those keypresses would not
make the VC switch. Of course, it would be possible that there
would be some problems with some national keyboards, but it would
mostly work for mostly anyone.

However, that would involve a lot of work, and a lot of
testing, and I'm too lazy to do that much work if there is an easy
way to do it. (I later realized that there was a serious security
problem with this approach as well. I'll let you try to figure out
what the flaw is, and I'll explain at the end of this
column.)

I then noticed that there are ioctl()'s specifically for
telling the kernel to ask first before switching virtual consoles.
It is possible for a program to explicitly refuse to let the kernel
switch virtual consoles. These ioctl()'s only work on virtual
consoles, so first we need to open one of the virtual consoles to
perform the ioctl()'s on. The easiest thing to do is this:

/dev/console stands for the current screen. The assumption is
that when vlock is run, it will be run on the current virtual
console. (It turns out that this assumption does not create a
security hole, although it might look to you like it ought
to.)

It would also be possible to switch to an unallocated virtual
terminal, like X does, which might be preferable in some
circumstances. To do this, we could have used the ioctl VT_OPENQRY
to find the number of the first available virtual console, opened
the appropriate device (/dev/ttynn, where nn is the number returned
by VT_OPENQRY), and used VT_ACTIVATE to switch to that virtual
console.

We will treat the VT_GETMODE and VT_SETMODE ioctl()'s like
the termios interface: first we get the current settings, then we
change the local copy, then we set the kernel's copy to look like
the changed local copy.

VT_GETMODE fills a vt_mode structure with the current VT
settings. If it returns an error, the program must not be running
on a virtual console. vlock does not exit on this error, but it
does set the is_vt variable to 0, and it does not try to use any
more VT ioctl()'s if the is_vt variable is set to 0.

We will arrange in a moment for SIGUSR1 to be sent to the
process whenever the kernel is requested to change away from the
virtual console the program is running on, and for SIGUSR2 to be
sent to the process whenever the kernel is requested to change to
the virtual console the program is running on. These requests can
be caused by the user pressing ALT-Fn keys or by other programs
issuing a VT_ACTIVATE ioctl.

The variable o_lock_all is set if the user wants to lock all
virtual consoles at once. It is not set if the user only wants to
lock the current virtual console. VT_RELDISP is used to tell the
kernel that the program acknowledges that it has received the
signal asking it to relinquish the virtual console, and tells the
kernel whether or not it agrees to do so. The third argument is set
to 1 to allow the kernel to switch to another virtual console, or
set to 0 to prevent the kernel from switching to another virtual
console.

When SIGUSR2 is received, acquire_vt() is called:

void acquire_vt(int signo) {
/* This call is not currently required under Linux,
but it won't hurt, either... */
ioctl(vfd, VT_RELDISP, VT_ACKACQ);
}

Linux does not actually require that this be done; it is
included for compatibility with SYSV, which does require that it is
called. I included it in vlock mainly so that if someone wanted to
port vlock to some version of SYSV, there would be one less
stumbling block for him or her.

Now that we have set up these signal handlers, we will tell
the virtual console manager about them.

We did not want to tell the virtual console manager to route
requests to change virtual consoles through these signals until the
signals' handlers had been installed, because to do otherwise could
cause a small possibility of a bug on very slow machines which are
running too many processes at once.

ovtm is another vt_mode structure, like vtm. Setting vtm.mode
to VT_PROCESS causes the kernel to ask permission to change virtual
consoles. Setting vtm.relsig to
SIGUSR1 and vtm.acqsig to
SIGUSR2 tells the kernel how to ask
permission.

At this point, all that needs to be done is to handle all
reasonable signals, so that people can't break in by typing
control-c or control-\ or
control-break, and to then ask for the user to type in a password
and check it against the real password. There is a library
function, getpass(), which gets a password from
the user without echoing it to the screen.

Unfortunately, this function is broken under at least one
shadow password implementation, because signal handlers are not
installed correctly, so to make a screen locking program that works
with shadow passwords, you either have to fix the shadow password
library or write your own version of getpass().
With vlock, I chose to tell people that vlock doesn't work right
with shadow passwords without fixing their shadow password library,
rather than writing my own version of the function.

Once a correct password has been entered, the program can
just exit. This is acceptable under Linux, at least. However, in
case this doesn't work with some other SYSV implementations of the
VT ioctl()'s, I have included code in vlock to
restore everything, including the VT state, to the original
settings. That's why I made the copy of vtm called ovtm a few code
fragments ago.