Cross-Platform Backups with Bacula
Hack 75 Script User Interaction
Use an expect script to help users generate GPG keys.
There are occasions when you can take advantage of Unix's flexibility to control some other tool or system that is less flexible. I've used Unix scripts to update databases on user-unfriendly mainframe systems when the alternative was an expensive mainframe-programming service contract. You can use the same approach in reverse to let the user interact with a tool, but with a constrained set of choices.
The Expect scripting language is ideal for creating such interactive scripts. It is available from NetBSD pkgsrc as pkgsrc/lang/tcl-expect or pkgsrc/lang/tk-expect, as well as from the FreeBSD ports and OpenBSD packages collections. We'll use the command-line version for this example, but keep in mind that expect-tk allows you to provide a GUI frontend to a command-line process if you're willing to write a more complex script.
In this case, we'll script the generation of a GPG key. Install GPG from either pkgsrc/security/gnupg or the appropriate port or package.
7.8.1 The Key Generation Process
During the process of generating a GPG key, the program asks the user several questions. We may wish to impose constraints so that a set of users ends up with keys with similar parameters. We could train the users, but that would not guarantee correct results. Scripting the generation makes the process easier and eliminates errors.
First, let's look at a typical key generation session:
% gpg --gen-key
gpg (GnuPG) 1.2.4; Copyright (C) 2003 Free Software Foundation, Inc.
This program comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it
under certain conditions. See the file COPYING for details.
Please select what kind of key you want: (1) DSA and ElGamal (default)
(2) DSA (sign only) (4) RSA (sign only) Your selection? 4
What keysize do you want? (1024) 2048 Requested keysize is 2048 bits
Please specify how long the key should be valid. 0 = key does not expire
<n> = key expires in n days <n>w = key expires in n weeks <n>m = key expires in n months <n>y = key expires in n years Key is valid for? (0) 0
Key does not expire at all Is this correct (y/n)? y
You need a User-ID to identify your key; the software constructs the user id
from Real Name, Comment and Email Address in this form:
"Heinrich Heine (Der Dichter) <[email protected]>"
Real name:
Let's pause there to consider the elements we can constrain.
You probably want to specify the cryptographic algorithm and key length for all users consistently, based on your security and
interoperability requirements. I'll choose RSA signing and encryption keys, but GPG doesn't provide a menu option for that. I'll have to create the signing key first and then add the encryption subkey.
7.8.2 A Simple Script
Here's an expect script that would duplicate the session shown so far:
#!/usr/pkg/bin/expect -f
set timeout -1 spawn gpg --gen-key match_max 100000
expect "(4) RSA (sign only)" expect "Your selection? " send "4\r"
expect "What keysize do you want? (1024) " send "2048\r"
expect "Key is valid for? (0) " send -- "0\r"
expect "Key does not expire at all" expect "Is this correct (y/n)? " send -- "y\r"
expect "Real name: "
The script begins by setting timeout to infinite, or -1, so expect will wait forever to match the provided input. Then we spawn the process that we're going to control, gpg --gen-key. match_max sets some buffer size constraints in bytes, and the given value is far more than we will need.
After the initial settings, the script simply consists of strings that we expect from the program and strings that we send in reply. This means that the script will answer all of the questions GPG asks until Real name: , without waiting for the user's input.
Note that in several places we expect things besides the prompt. For example, before responding to the Your selection? prompt, we verify that the version of GPG we have executed still has the same meaning for the fourth option, by expecting that the text of that menu choice is still RSA (sign only). If this were a real, production-ready script, we should print a warning message and terminate the script if the value does not match our expectations, and perhaps include a check of the GPG version number. In this simple example, the script will hang, and you must break out of it with Ctrl-c.
7.8.3 Adding User Interaction
There are several ways of handling the fields we do want the user to provide. For the greatest degree of control over the user experience, we could use individual expect commands, but here we will take a simpler approach. Here's some more of the script:
interact "\r" return send "\r"
expect "Email address: " interact "\r" return send "\r"
expect "Comment: " interact "\r" return send "\r"
expect "Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? "
interact "\r" return send "\r"
expect "Enter passphrase: " interact "\r" return
send "\r"
expect "Repeat passphrase: " interact "\r" return
send "\r"
The interact command allows the user to interact directly with the spawned program. We place a constraint that the user's interaction ends as soon as the user presses the Enter key, which sends the
carriage return character, \r. At that point, the interact command returns and the script resumes. Note that we have to send the \r from the script; expect intercepted the carriage return and GPG did not see it.
7.8.4 Handling Incorrect Input
Again, a correct script would have a more complex flow of execution and allow for cases where the spawned program rejects the user's input with an error message. For example, the Real Name field must be more than five characters long. If a user types less than five characters, GPG will prompt him to retype his username. However, the expect script just shown will not accept the new user input, because it is now waiting for the Email address: prompt.
Alternatively, we could replace these three lines:
interact "\r" return send "\r"
expect "Email address: "
with:
interact -o "Email address: " return send_user "Email address: "
Instead of stopping interaction when the user presses return, we stop interaction when the program outputs the Email address: prompt. That's the difference between interact and interact -o; the former stops
interaction based on input from the user, and the latter on output from the program. This time, we don't need to send the carriage return, because the user's keypress is passed through to GPG. However, we do need to echo the prompt, because expect has consumed it. This method lets GPG handle the error conditions for us:
Real name: abc
Name must be at least 5 characters long Real name: abcde
Email address:
7.8.5 Hacking the Hack
After GPG receives the information it needs to generate the key, it might not be able to find enough high-quality random data from the system. The script ought to handle that by spawning a process to generate more system activity, such as performing a lot of disk activity by running a find across the entire disk.
After generating the signing key, the script could spawn a new instance of GPG with the --edit-key option, to generate the desired RSA encryption key.
Although the final script may end up executing three processes, the whole process is seamless to the user. You can hide even more of the guts by using expect's log_user setting to hide the output of the programs at points where the user does not need to see them.
You can use a script like this in conjunction with any Unix
command-line program. By combining expect with telnet or ssh, you can control non-Unix systems, thereby leveraging the flexibility of Unix into a non-Unix domain. This even works with programs for which you do not have source code, such as control utilities for commercial databases or application software.
In the case of GPG, we do have source code, so we could modify the program, but writing an expect script is easier. A carefully designed expect script may not require changes when a new version of GPG is released. Source code changes to GPG would require integration with any new version of GPG.
7.8.6 See Also
•
• man expect
•
• The expect web site, which includes sample scripts (
http://expect.nist.gov/)
•
• Exploring Expect , by Don Libes, the author of expect (
< Day Day Up >
Hack 76 Create a Trade Show Demo
I frequently represent NetBSD at trade shows. It's challenging to attract attention because there are many booths at a show—people will walk by quickly unless something catches their eye. You also need to balance eye-candy with functionality so that you can attract and keep a visitor's attention. I needed an enticing demo to run on one of the computers in the booth.
I wanted to show off several applications, such as office productivity tools, video, and games, and have music playing, but there's only so much screen real estate. Cramming all of those things on the screen at once would clutter the screen, and the point would be lost.
Most X window managers have some concept of virtual desktops, separate work spaces that you can flip between. For example,
Enlightenment (pkgsrc/wm/enlightenment) not only has the concept of virtual desktops, but as an added bonus for the trade show
environment offers a nice sliding effect as you transition from one desktop to the next.
7.9.1 Introducing eesh
Normally in Enlightenment, to switch from one virtual desktop to the next, you move the mouse pointer to the edge of the screen and then push past it, or you use a key sequence to move to an adjacent desktop. For an unattended demo, we need to automate this process. Enlightenment provides an undocumented utility called eesh that can control most aspects of the Enlightenment window manager. You can write scripts to move windows, resize them, or flip between desktops.
Note that eesh isn't a friendly utility; it doesn't even produce a prompt when you run it. Type help for the menu or exit to quit:
% eesh
help
Enlightenment IPC Commands Help commands currently available:
use "help all" for descriptions of each command use "help <command>" for an individual description
actionclass active_network advanced_focus sfa autosave background border button button_show colormod configpanel copyright current_theme tc cursor default_theme dialog_ok dok dock dump_mem_debug exit q focus_mode sf fx general_info geominfo_mode sgm goto_area sa goto_desktop sd group gc group_info gl group_op gop help ? imageclass internal_list il list_class cl list_remember list_themes tl module move_mode smm nop Unfortunately, the eesh utility seems to be untested. It sometimes behaves inconsistently by not accepting commands until you enter them a second time or by withholding output until you press Enter again. As an example, there are actually more commands than those indicated in the help listing. Look in the Enlightenment source's ipc.c file for a complete list.
7.9.2 Discovering Commands
We'll start our script by making sure that Enlightenment is configured the way we want for our demo. We want six work spaces (3 by 2) to display our programs. Within eesh, try the following commands: num_areas ? Number of Areas: 2 2 help num_areas Enlightenment IPC Commands Help : num_areas (sna) ---Change the size of the virtual desktop
Use "num_areas <width> <height>" to change the size of the virtual desktop.
Example: "num_areas 2 2" makes 2x2 virtual destkops Use "num_areas ?" to retrieve the current setting
num_areas 3 2
Now we have the number of areas we want. areas is the Enlightenment name for virtual desktops, since Enlightenment also supports multiple desktops, but that's different. Now we'd like our screen to display the first area, so that the programs our script runs will open there:
goto_area 0 0
If your terminal wasn't on the first area, it just moved off the screen. Use the mouse to return to that area.
eesh also lets us write commands on the command line with the -e (execute command) flag:
% eesh -e "goto_area 0 0"
7.9.3 Sample Scripts
Now we know enough to write a simple demo script:
#!/bin/sh
eesh -e "num_desks 1" eesh -e "num_areas 3 2" sleep 1
eesh -e "goto_area 0 0"
# Configure the default gqmpeg playlist to play your desired music
gqmpeg
# Show an interesting avi file.
xanim -geometry +50x+10 netbsd3.avi &
# Give the programs time to start, to make sure they # open on the correct area.
# Also, lets people watching see what started up. sleep 3
eesh -e "goto_area 1 0"
# Word Processing
abiword sampledoc.abw & sleep 2
eesh -e "goto_area 2 0"
# Spreadsheet
gnumeric samplesheet.gnumeric & sleep 2 eesh -e "goto_area 0 1" # A lively game battleball & sleep 2 eesh -e "goto_area 1 1"
# Web Browsing (of a local hierarchy, in case you don't have net
# connectivity at a trade show) firebird file://index.html & sleep 3
eesh -e "goto_area 2 1" sleep 1
# Insert your favorite application here # Leave screen back at page 1.
eesh -e "goto_area 0 0"
When you run the script, the screen will slide around to the various areas and pause a few seconds between program launches. We have most of the things we wanted: music, video, and applications. The next step is to keep it moving. Try the following script:
#!/bin/sh while [ 1 ] do eesh -e "goto_area 0 0" sleep 2 eesh -e "goto_area 1 0" sleep 2 eesh -e "goto_area 2 0" sleep 2 eesh -e "goto_area 0 1" sleep 2 eesh -e "goto_area 1 1" sleep 2 eesh -e "goto_area 2 1" sleep 2 done
To stop the moving display, you have to get your keyboard focus into the xterm where the script is running so that you can press Ctrl-c. That can be difficult, but we'll address it shortly.
7.9.4 More Complex Scripts
For a complex demonstration, you can have different sets of these scripts that visit different sets of areas. You can also change the delay so that complex areas display for a longer period. I also made a script that clears all of the viewing areas. That way, when visitors to the booth play around with the machine, I can easily reset to a clean state and then start the demo again.
Since many of the utilities you'll demonstrate don't create .pid files, I find it easiest to use pkill, the "kill process by name" utility. (FreeBSD provides killall.)
I'll also leave you with two example scripts that show how to extract information about Enlightenment's current settings for use in a more complex script.
The first script is retitle:
#!/bin/sh
WIN=`eesh -ewait "set_focus ?" | sed 's/^focused: //' `
xterm -geometry 47x7+227+419 -fn
-*-courier-*-o-*-*-34-*-*-*-*-*-*-* -e \ /home/david/bin/retitle2 $WIN
The second is retitle2:
#!/bin/sh WIN=$1
echo "enter new title:" read TITLE
eesh -e "win_op $WIN title $TITLE"
With these scripts and e16keyedit , you can bind a key combination to change the title of any window. This makes it much easier to keep track of xterms, if you prefer task-oriented titles.
Now back to the control issue. When I first wrote this demo, I used a switch wired to a serial port to start and stop the demo so that
keyboard focus did not matter. However, wiring switches is more work than configuring software, so I found a better way.
The e16keyedit utility, written by Geoff "Mandrake" Harrison and Carsten "Raster" Haitzler (the primary developers of Enlightenment), allows you to bind function keys and Meta keys to run programs or perform the same functions that you can with eesh. Using e16keyedit, you can define function keys to set up the demo, clean up the demo, and start and stop the area rotations. Since the function keys can be bound to work anywhere within Enlightenment, keyboard focus no longer matters. You're ready to give a fantastic demo!
e16keyedit is not part of the main Enlightenment distribution. Download it from SourceForge (
http://sourceforge.net/project/showfiles.php?group_id=2).
7.9.5 See Also
•
< Day Day Up >