Search the Catalog
Linux Network Administrator's Guide, 2nd Edition

Linux Network Administrator's Guide, 2nd Edition

By Olaf Kirch & Terry Dawson
2nd Edition June 2000
1-56592-400-2, Order Number: 4002
506 pages, $34.95

Chapter 19
Getting Exim Up and Running

Contents:
Running Exim
If Your Mail Doesn't Get Through
Compiling Exim
Mail Delivery Modes
Miscellaneous config Options
Message Routing and Delivery
Protecting Against Mail Spam
UUCP Setup

This chapter gives you a quick introduction to setting up Exim and an overview of its functionality. Although Exim is largely compatible with sendmail in its behavior, its configuration files are completely different.

The main configuration file is usually called /etc/exim.conf or /etc/exim/config in most Linux distributions, or /usr/lib/exim/config in older configurations. You can find out where the configuration file is by running the command:

$ exim -bP configure_file

You may have to edit the configuration file to reflect values specific to your site. In most common configurations there isn't a great deal to change, and a working configuration should rarely have to be modified.

By default, Exim processes and delivers all incoming mail immediately. If you have relatively high traffic, you may instead have Exim collect all messages in the so-called queue, and process them at regular intervals only.

When handling mail within a TCP/IP network, Exim is frequently run in daemon mode: at system boot time, it is invoked from /etc/init.d/exim[1] and puts itself in the background, where it waits for incoming TCP connections on the SMTP port (usually port 25). This is beneficial whenever you expect to have a significant amount of traffic because Exim doesn't have to start up for every incoming connection. Alternatively, inetd could manage the SMTP port and have it spawn Exim whenever there is a connection on this port. This configuration might be useful when you have limited memory and low mail traffic volumes.

[1] Other possible locations are /etc/rc.d/init.d and rc.inet2. The latter is common on systems using a BSD-style structure for system administration files in the /etc directory.

Exim has a complicated set of command-line options, including many that match those of sendmail. Instead of trying to put together exactly the right options for your needs, you can implement the most common types of operation by invoking traditional commands like rmail or rsmtp. These are symbolic links to Exim (or if they're not, you can easily link them to it). When you run one of the commands, Exim checks the name you used to invoke it and sets the proper options itself.

There are two links to Exim that you should have under all circumstances: /usr/bin/rmail and /usr/sbin/sendmail.[2] When you compose and send a mail message with a user agent like elm, the message is piped to sendmail or rmail for delivery, which is why both /usr/sbin/sendmail and /usr/bin/rmail should point to Exim. The list of recipients for the message is passed to Exim on the command line.[3] The same happens with mail coming in via UUCP. You can set up the required pathnames to point to Exim by typing the following at a shell prompt:

$ ln -s /usr/sbin/exim /usr/bin/rmail
$ ln -s /usr/sbin/exim /usr/sbin/sendmail

[2] This is the new standard location of sendmail according to the Linux File System Standard. Another common location is /usr/lib/sendmail, which is likely to be used by mail programs that are not specially configured for Linux. You can define both filenames as symbolic links to Exim so that programs and scripts invoking sendmail will instead invoke Exim to do the same things.

[3] Some user agents, however, use the SMTP protocol to pass messages to the transport agent, calling it with the -bs option.

If you want to dig further into the details of configuring Exim, you should consult the full Exim specification. If this isn't included in your favorite Linux distribution, you can get it from the source to Exim, or read it online from Exim's web site at http://www.exim.org.

Running Exim

To run Exim, you must first decide whether you want it to handle incoming SMTP messages by running as a separate daemon, or whether to have inetd manage the SMTP port and invoke Exim only whenever an SMTP connection is requested from a client. Usually, you will prefer daemon operation on the mail server because it loads the machine far less than spawning Exim over and over again for each connection. As the mail server also delivers most incoming mail directly to the users, you should choose inetd operation on most other hosts.

Whatever mode of operation you choose for each individual host, you have to make sure you have the following entry in your /etc/services file:

smtp            25/tcp          # Simple Mail Transfer Protocol

This defines the TCP port number that is used for SMTP conversations. Port number 25 is the standard defined by the "Assigned Numbers" RFC (RFC-1700).

When run in daemon mode, Exim puts itself in the background and waits for connections on the SMTP port. When a connection occurs, it forks, and the child process conducts an SMTP conversation with the peer process on the calling host. The Exim daemon is usually started by invoking it from the rc script at boot time using the following command:

/usr/sbin/exim -bd -q15m

The -bd flag turns on daemon mode, and -q15m makes it process whatever messages have accumulated in the message queue every 15 minutes.

If you want to use inetd instead, your /etc/inetd.conf file should contain a line like this:

smtp    stream  tcp nowait  root  /usr/sbin/exim  in.exim -bs

Remember you have to make inetd re-read inetd.conf by sending it an HUP signal after making any changes.[4]

[4] Use kill HUP pid, for which pid is the process ID of the inetd process retrieved from a ps listing.

Daemon and inetd modes are mutually exclusive. If you run Exim in daemon mode, you should make sure to comment out any line in inetd.conf for the smtp service. Equivalently, when having inetd manage Exim, make sure that no rc script starts the Exim daemon.

You can check that Exim is correctly set up for receiving incoming SMTP messages by telnetting to the SMTP port on your machine. This is what a successful connect to the SMTP server looks like:

$ telnet localhost smtp
Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. 220 richard.vbrew.com ESMTP Exim 3.13 #1 Sun, 30 Jan 2000 16:23:55 +0600 quit
221 richard.brew.com closing connection Connection closed by foreign host.

If this test doesn't produce the SMTP banner (the line starting with the 220 code), check that you are either running an Exim daemon process or have inetd correctly configured. If that doesn't reveal the problem, look in the Exim log files (described next) in case there is an error in Exim's configuration file.

If Your Mail Doesn't Get Through

A number of features are available for troubleshooting installation problems. The first place to check is Exim's log files. On Linux systems they are normally kept in /var/log/exim/log and are named exim_mainlog, exim_rejectlog, and exim_paniclog. On other operating systems, they are often kept in /var/spool/exim/log. You can find out where the log files are by running the command:

exim -bP log_file_path

The main log lists all transactions, the reject log contains details of messages that were rejected for policy reasons, and the panic log is for messages related to configuration errors and the like.

Typical entries in the main log are shown below. Each entry in the log itself is a single line of text, starting with a date and time. They have been split into several lines here in order to fit them on the page:

2000-01-30 15:46:37 12EwYe-0004WO-00 <= jack@vstout.vbrew.com 
  H=vstout.vbrew.com [192.168.131.111] U=exim P=esmtp S=32100 
  id=38690D72.286F@vstout.vbrew.com
2000-01-30 15:46:37 12EwYe-0004WO-00 => jill <jill@vbrew.com> 
  D=localuser T=local_delivery
2000-01-30 15:46:37 12EwYe-0004WO-00 Completed

These entries show that a message from jack@vstout.vbrew.com to jill@vbrew.com was successfully delivered to a mailbox on the local host. Message arrivals are flagged with <=, and deliveries with =>.

There are two kinds of delivery errors: permanent and temporary. A permanent delivery error is recorded in a log entry like this, flagged with "**":

2000-01-30 14:48:28 12EvcH-0003rC-00 ** bill@lager.vbrew.com 
  R=lookuphost T=smtp: SMTP error from remote mailer after RCPT TO:
  <bill@lager.vbrew.com>: host lager.vbrew.com [192.168.157.2]: 
  550 <bill@lager.vbrew.com>... User unknown

After a failure like this, Exim sends a delivery failure report, often called a bounce message back to the sender.

Temporary errors are flagged with "==":

2000-01-30 12:50:50 12E9Un-0004Wq-00 == jim@bitter.vbrew.com 
  T=smtp defer (145): Connection timed out

This error is typical for a situation in which Exim properly recognizes that the message should be delivered to a remote host, but is not able to connect to the SMTP service on that host. The host may be down or there could be a network problem. Whenever a message is deferred like this, it remains on Exim's queue and is retried at intervals. However, if it fails to be delivered for a sufficiently long time (usually several days), a permanent error occurs and the message is bounced.

If you are unable to locate your problem from the error message Exim generates, you may want to turn on debugging messages. You can do this using the -d flag, optionally followed by a number specifying the level of verbosity (a value of 9 gives maximum information). Exim then displays a report of its operation on the screen, which may give you more hints about what is going wrong.

Compiling Exim

Exim is still under active development; the version of Exim included in Linux distributions is probably not the latest release. If you need a feature or a bugfix found in a later release, you have to obtain a copy of the source code and compile it yourself. The latest release can be found via Exim's web page at http://www.exim.org.

Linux is one of the many operating systems supported by the Exim source. To compile Exim for Linux, you should edit the src/EDITME file and put the result in a file called Local/Makefile. There are comments in src/EDITME that tell you what the various settings are used for. Then run make. See the Exim manual for detailed information on building Exim from source.

Mail Delivery Modes

As noted previously, Exim is able to deliver messages immediately or queue them for later processing. All incoming mail is stored in the input directory below /var/spool/exim. When queueing is not in operation, a delivery process is started for each message as soon as it arrives. Otherwise, it is left on the queue until a queue-runner process picks it up. Queueing can be made unconditional by setting queue_only in the configuration file, or it can be conditional on the 1-minute system load by a setting such as:

queue_only_load = 4
which causes messages to be queued if the system load exceeds 4.[5]

[5] The system load is a standard Unix measure of the average number of processes that are queued up, waiting to run. The uptime shows load averages taken over the previous 1, 5, and 15 minutes.

If your host is not permanently connected to the Internet, you may want to turn on queueing for remote addresses, while allowing Exim to perform local deliveries immediately. You can do this by setting:

queue_remote_domains = *
in the configuration file.

If you turn on any form of queuing, you have to make sure the queues are checked regularly, probably every 10 or 15 minutes. Even without any explicit queueing options, the queues need to be checked for messages that have been deferred because of temporary delivery failures. If you run Exim in daemon mode, you must add the -q15m option on the command line to process the queue every 15 minutes. You can also invoke exim -q from cron at these intervals.

You can display the current mail queue by invoking Exim with the -bp option. Equivalently, you can make mailq a link to Exim, and invoke mailq:

$ mailq
2h 52K 12EwGE-0005jD-00 <sam@vbrew.com> D bob@vbrew.com harry@example.net

This shows a single message from sam@vbrew.com to two recipients sitting in the message queue. It has been successfully delivered to bob@vbrew.com, but has not yet been delivered to harry@example.net, though it has been on the queue for two hours. The size of the message is 52K, and the ID by which Exim identifies this message is 12EwGE-0005jD-00. You can find out why the delivery is not yet complete by looking at the message's individual log file, which is kept in the msglog directory in Exim's spool directory. The -Mvl option is an easy way of doing this:

$ exim -Mvl 12EwGE-0005jD-00
2000-01-30 17:28:13 example.net [192.168.8.2]: Connection timed out 2000-01-30 17:28:13 harry@example.net: remote_smtp transport deferred: Connection timed out
Individual log files keep a copy of log entries for each message so you can easily inspect them. The same information could have been extracted from the main log file using the exigrep utility:
$ exigrep 12EwGE-0005jD-00 /var/log/exim/exim_mainlog
That would take longer, especially on a busy system where the log files can get quite big. The exigrep utility comes into its own when looking for information about more than one message. Its first argument is a regular expression, and it picks out all the log lines concerned with any messages that have at least one log line that matches the expression. Thus it can be used to pick out all messages for one specific address, or all those to or from a specific host.

You can keep a general watch on what a running Exim is doing by running tail on its main log file. Another way of doing this is to run the eximon utility that comes with Exim. This is an X11 application that puts up a scrolling display of the main log, and also shows a list of messages that are awaiting delivery, as well as some stripcharts about delivery activity.

Miscellaneous config Options

Here are a few of the more useful options you can set in the configuration file:

message_size_limit

Setting this option limits the size of message that Exim will accept.

return_size_limit

Setting this option limits the amount of an incoming message that Exim will return as part of a bounce message.

deliver_load_max

If the system load exceeds the value given for this option, all mail delivery is suspended, though messages are still accepted.

smtp_accept_max

This is the maximum number of simultaneous incoming SMTP calls Exim is prepared to accept.

log_level

This option controls the amount of material that is written to the log. There are also some options with names beginning with log_ that control the logging of specific information.

Message Routing and Delivery

Exim splits up mail delivery into three different tasks: routing, directing, and transporting. There are a number of code modules of each type, and each is separately configurable. Usually a number of different routers, directors, and transports are set up in the configuration file.

Routers resolve remote addresses, determining which host the message should be sent to and which transport should be used. In Internet-connected hosts there is often just one router, which does the resolution by looking up the domain in the DNS. Alternatively, there may be one router that handles addresses destined for hosts on a local LAN, and a second to send any other addresses to a single smart host; for example, an ISP's mail server.

Local addresses are given to the directors, of which there are normally several, to handle aliasing and forwarding as well as identifying local mailboxes. Mailing lists can be handled by aliasing or forwarding directors. If an address gets aliased or forwarded, any generated addresses are handled independently by the routers or directors, as necessary. By far the most common case will be delivery to a mailbox, but messages may also be piped into a command or appended to a file other than the default mailbox.

A transport is responsible for implementing a method of delivery; for example, sending the message over an SMTP connection or adding it to a specific mailbox. Routers and directors select which transport to use for each recipient address. If a transport fails, Exim either generates a bounce message or defers the address for a later retry.

With Exim, you have a lot of freedom in configuring these tasks. For each of them, a number of drivers are available, from which you can choose those you need. You describe them to Exim in different sections of its configuration file. The transports are defined first, followed by the directors, and then the routers. There are no built-in defaults, though Exim is distributed with a default configuration file that covers simple cases. If you want to change Exim's routing policy or modify a transport, it is easiest to start from the default configuration and make changes rather than attempt to set up a complete configuration from scratch.

Routing Messages

When given an address to deliver, Exim first checks whether the domain is one that is handled on the local host by matching it against a list in the local_domains configuration variable. If this option is not set, the local host name is used as the only local domain. If the domain is local, the address is handed to the directors. Otherwise, it is handed to the routers to find out which host to forward a message to.[6]

[6] This is a simplification. It is possible for directors to pass addresses to transports that deliver to remote hosts, and similarly, it is possible for routers to pass addresses to local transports that write the messsage to a file or a pipe. It is also possible for routers to pass addresses to the directors in some circumstances.

Delivering Messages to Local Addresses

Most commonly, a local address is just a user's login name, in which case the message is delivered to the user's mailbox, /var/spool/mail/user-name. Other cases include aliases, mailing list names, and mail forwarding by the user. In these cases, the local address expands to a new list of addresses, which may be either local or remote.

Apart from these "normal" addresses, Exim can handle other types of local message destinations, like filenames and pipe commands. When delivering to a file, Exim appends the message, creating the file if necessary. File and pipe destinations are not addresses in their own right, so you can't send mail to, say, /etc/passwd@vbrew.com and expect to overwrite the password file; deliveries to a specific file are valid only if they come from forwarding or alias files. Note, however, that /etc/passwd@vbrew.com is a syntactically valid email address, but if Exim received it, it would (typically) search for a user whose login name was /etc/passwd, fail to find one, and bounce the message.

In an alias list or forwarding file, a filename is anything that begins with a slash (/) that does not parse as a fully qualified email address. For example, /tmp/junk in a forwarding or alias file is interpreted as a file name, but /tmp/junk@vbrew.com is an email address, though it is not likely to be a very useful one. However, valid addresses of this type are seen when sending mail through X.400 gateways, because X.400 addresses start with a slash.

Similarly, a pipe command may be any Unix command preceded by the pipe symbol (|), unless the string parses as a valid email address complete with domain. Unless you have changed the configuration, Exim does not use a shell to run the command; instead, it splits it up into a command name, arguments itself, and runs it directly. The message is fed to the command on its standard input.

For example, to gate a mailing list into a local newsgroup, you might use a shell script named gateit, and set up a local alias that delivers all messages from this mailing list to the script using |gateit. If the command line contains a comma, it and the preceding pipe symbol must be enclosed in double quotes.

Local users

A local address most commonly denotes a user's mailbox. This is normally located in /var/spool/mail and has the name of the user, who also owns the file. If it does not exist, it is created by Exim.

In some configurations, the group is set to the user's group and the mode is 0600. In these cases, delivery processes are run as the user, and the user may delete the mailbox entirely. In other configurations, the mailbox's group is mail, and it has mode 660; delivery processes are run under a system uid and group mail, and users cannot delete their mailbox files, though they can empty them.

Note that although /var/spool/mail is currently the standard place to put the mailbox files, some mail software may be compiled to use different paths, for example, /usr/spool/mail. If delivery to users on your machine fails consistently, you should see if it helps to make this a symbolic link to /var/spool/mail.

The addresses MAILER-DAEMON and postmaster should normally appear in your alias file, expanding into the email address of the system administrator. MAILER-DAEMON is used by Exim as the sender address in bounce messages. It is also recommended that root be set up as an alias for an administrator, especially when deliveries are being run under the permissions of the recipient users, in order to avoid running any delivery as root.

Forwarding

Users can redirect their mail to alternative addresses by creating a .forward file in their home directories. This contains a list of recipients separated by commas and/or newlines. All lines of the file are read and interpreted. Any type of address may be used. A practical example of a .forward file for vacations might be:

janet, "|vacation"
In other descriptions of .forward files, you might see the username at the start preceded by a backslash. This was necessary in some older MTAs to stop a search for a .forward for the new name, which could lead to looping. The backslash is not necessary in Exim, which automatically avoids loops of this kind.[7] However, a backslash is permitted, and in fact it does make a difference in configurations where several domains are being handled at once. Without a backslash, an unqualified username is qualified with a default domain; with a backslash the incoming domain is preserved.

[7] A director is skipped if the address it is about to process is one that it has previously processed in the course of generating the present address.

The first address in the forward file delivers the incoming message to janet's mailbox, while the vacation command returns a short notification to the sender.[8]

[8] Please, if you choose to use a vacation program, make sure it will not reply to messages sent from mailing lists! It is very annoying to discover that someone has gone on vacation and find a vacation message for every message they've received. Mailing list administrators: this is a good example of why it is bad practice to force the Reply-To: field of mailing list messages to that of the list submission address.

In addition to supporting "traditional" forwarding files, Exim can be configured to allow more complex files called filters. Instead of being just a list of forwarding addresses, a filter file can contain tests on the contents of the incoming message so that, for example, messages could be forwarded only if the subject contained the message "urgent." The system administrator must decide whether to allow users this flexibility.

Alias Files

Exim is able to handle alias files compatible with Berkeley's sendmail alias files. Entries in the alias file can have the following form:

alias: recipients

recipients is a comma-separated list of addresses that will be substituted for the alias. The recipient list may be continued across newlines if the next line begins with whitespace.

A special feature allows Exim to handle mailing lists that are held separately from the alias file: if you specify :include:filename as a recipient, Exim reads the specified file and substitutes its contents as a list of recipients. An alternative to handling mailing lists is shown later in this chapter in "Mailing Lists".

The main aliases file is /etc/aliases. If you make this file world-writable or group-writeable, Exim will refuse to use it and will defer local deliveries. You can control the test it applies to the file's permissions by setting modemask in the system_aliases director.

This is a sample aliases file:

# vbrew.com /etc/aliases file
hostmaster: janet
postmaster: janet
usenet: phil
# The development mailing list.
development: joe, sue, mark, biff,
        /var/mail/log/development
owner-development: joe
# Announcements of general interest are mailed to all
# of the staff
announce: :include: /etc/Exim/staff,
        /var/mail/log/announce
owner-announce: root
# gate the ppp mailing list to a local newsgroup
ppp-list: "|/usr/local/bin/gateit local.lists.ppp"

When there are file names and pipe commands in an alias file, as here, Exim needs to be told which userid to run the deliveries under. The user option (and possibly group, too) must be set in Exim's configuration file, either on the director that is handling the aliases, or on the transports to which it directs these items.

If an error occurs while delivering to an address generated from the aliases file, Exim will send a bounce message to the sender of the message, as usual, but this might not be appropriate. The errors_to option can be used to specify that bounce messages are to be sent elsewhere; for example, to the postmaster.

Mailing Lists

Instead of the aliases file, mailing lists may also be managed by means a forwardfile director. The lists are all kept in a single directory such as /etc/exim/lists/, and a mailing list named nag-bugs is described by the file lists/nag-bugs. This should contain the members' addresses separated by commas or newlines. Lines beginning with a hash sign (#) are treated as comments. A simple director to use such data is as follows:

lists:
  driver = forwardfile
  file = /etc/exim/lists/${local_part}
  no_check_local_user
  errors_to = ${local_part}-request
When this director runs, the values of the file and errors_to options are expanded. Expansion causes certain portions of the strings beginning with dollar characters to be replaced every time the string is used. The simplest kind of expansion is the insertion of the value of one of Exim's variables, and this is what is happening here. The substring ${local_part} substitutes the value of the $local_part, which is the local part of the address that is being processed.

For each mailing list, a user (or alias or mailing list) named listname-request should exist; any errors occurring when resolving an address or delivering to a list member are reported to this address.

Protecting Against Mail Spam

Mail spam, or unsolicited email advertising, is an annoying problem for many users. A project has been formed to address this problem called the Mail Abuse Protection System (MAPS), and a mechanism has been built that reduces the problem, called the Real Time Blackhole List (RBL). Information on how the MAPS RBL works can be obtained from its online documentation at http://maps.vix.com/rbl/. The idea is simple. Sites that are caught generating mail spam are added into the database and mail transfer agents like Exim are able to query the database to confirm that a source is not a spammer before accepting mail from it.

Since the advent of the RBL, several other similar lists have been created. One of the most useful is the Dial-Up List (DUL), which lists the IP addresses of dial-up hosts. These should normally send outgoing mail only to their ISP's mail servers. Many sites block mail from external dial-ups because when such a host avoids its own ISP's server, it is usually up to no good.

Exim provides support for the real-time and other blacklists. It is very easily configured. To enable it, add the following lines to your /etc/exim.conf file:

# Vixie / MAPS RBL (http://maps.vix.com/rbl)
rbl_domains = rbl.maps.vix.com : dul.maps.vix.com

This example checks both the RBL and the DUL, rejecting any messages from hosts that are on either list. The rbl_hosts option allows you to specify groups of hosts to which RBL checking does (or does not) apply. The default setting is:

rbl_hosts = *
which means that all hosts are subject to RBL checking. If you wanted to override blacklisting and accept mail from a specific host without performing the RBL checking you could, for example, use:
rbl_hosts = ! nocheck.example.com : *
The exclamation mark before the first item in this list indicates a negated item: if the calling host is nocheck.example.com, it will match this item. But because of the negation, RBL checking is not performed. Any other host matches the second item in the list.

UUCP Setup

Exim does not have any specific code for transporting mail via UUCP, nor does it support UUCP bang path addresses. However, if domain addressing is being used, Exim can be interfaced to UUCP fairly simply. Here is a configuration fragment for sending certain domains to UUCP, taken from a real installation:

# Transport
uucp:
  driver = pipe
  user = nobody
  command = "/usr/local/bin/uux -r - \
    ${substr_-5:$host}!rmail ${local_part}"
  return_fail_output = true

# Router
uucphost:
  transport = uucp
  driver = domainlist
  route_file = /usr/exim/uucphosts
  search_type = lsearch

In a complete configuration file, the transport would be inserted among the other transports, and the router probably defined as the first router. The file /usr/exim/uucphosts contains entries like this:

darksite.example.com:           darksite.UUCP
which is interpreted to mean, "Send mail addressed to the domain darksite.example.com to the UUCP host darksite." This configuration could be set up more simply without the router adding the suffix .UUCP to darksite only to have the transport take it off again, but this way is useful because it makes clear the distinction between the domain name darksite.example.com and the UUCP host name darksite.

Whenever the router comes across a domain that is in the route file, it will send the address to the UUCP transport, which subsequently pipes it to the uux command (described in Chapter 16, Managing Taylor UUCP). If there is a problem, uux will generate some output and terminate with a non-zero error code. The setting of return_fail_output makes sure that the output is returned to the sender.

If incoming UUCP messages are grouped into files in batched SMTP format, they can be passed directly to Exim using a command like this:

exim -bS </var/uucp/incoming/001

However, there is one catch. When Exim receives a message locally, it insists that the sender is the logged-in user that calls it, but for a UUCP batch we want the senders to be taken from the incoming messages. Exim will do this if the process that calls it is running as a trusted user. If you arrange for incoming UUCP to be handled by a user called uucp, for example, you need to specify:

trusted_users = uucp
in the Exim configuration file to ensure that sender addresses are correctly handled.

Back to: Sample Chapter Index

Back to: Linux Network Administrator's Guide, 2nd Edition


O'Reilly Home | O'Reilly Bookstores | How to Order | O'Reilly Contacts
International | About O'Reilly | Affiliated Companies

© 2001, O'Reilly & Associates, Inc.
webmaster@oreilly.com