Validating Email Forwarding
Many people in the computer community have multiple accounts. They may have accounts at different computing sites that they support, or accounts for different development projects they are involved with, or private and professional accounts. Also, a person may use one Internet provider one month, and then get a better offer with another the next month. Thus, checking multiple computer accounts for email can be an annoying task. Users could check their email by logging onto each different computer, but this can be time consuming and not very rewarding if no new mail has arrived. Users could setup a popmail configuration for each account, but that isn't very graceful either. Enter the .forward file.
The .forward file is a mechanism by which a person can have his or her email sent from one account to another. When an email message is received by the mail services of a computer, it checks for the presence of a .forward file in the user's directory. The assumption here is that the host is configured to handle this. If a .forward file is present, it is parsed and a copy of the message is sent to each destination listed. This destination can be on the same computer or across the world. With email forwarding, email will be delivered to the user, as mail should.
Listing 1 shows the contents of a sample .forward file. The first line instructs the host to forward the email to myaccnt at a remote computer system. The second line forces a local delivery in addition to the remote forwarding. This is accomplished by the leading "\" in front of the username. Mail processing commands could also be implemented within this file. The .forward file must be owned by the user, and it must be readable by root. If it fails to meet these criteria, it will be ignored.
With email forwarding, you can direct where email is going, but what happens when an account disappears? Suppose a person has a computer account at work, but works from home and uses a personal account. The work account is configured to forward email to the personal account. What happens if this person relocates, discontinues the personal account, and forgets to redirect the other account? The machine at work will still try to send email to the account named in the .forward file. The message will never get delivered, obviously, and will probably wind up in the admin's mailbox marked as undeliverable. Or, it may just dissappear.
For another scenario, consider a developer, who is involved in several projects each with an account on a different host. This person will want to receive email on the account associated with the current active project. So, they'll setup a .forward file to forward from machine A to machine B. As the project enters a test phase, they move to active development on machine C. Now, machine A forwards email to machine B, and machine B forwards to machine C. If the developer returns to machine A, they may inadvertantly set machine C to forward email back to machine A. Now the email is being sent from A to B to C and back to A again, and so on. The mail systems will be more than happy to do this. This is a correctable situation, but it has negative effects on network and computer load.
I wanted a utility to check the email forwarding of users on a given host. So I set about writing checkfor. This is a simple utility that takes a username@hostname pair as its input. It recursively traces the email forwarding tree from that host, down to its terminations. The program then generates a report of all username@hostname pairs that it finds along the way. The next few paragraphs describe how checkfor accomplishes this task. If you're the go-ahead-and-try-it type, then skip ahead to the "How is it used" section.
Checkfor uses TCP sockets to connect to and talk with the hosts that it encounters. Given a username@hostname pair, checkfor first opens a socket to the remote machine on port 25. This is accomplished in the sock_open() subroutine. This routine first creates the local socket with the socket() function call. The parameters to this call define the socket as using the ARPA Internet protocol and as a two-way connection-oriented data stream.
Next comes the hostname lookup. Checkfor uses the gethostbyname() system call. This returns the numeric IP address of the host in question. The remote machine may actually be the local machine name. One feature of socket programming is that local and remote connections appear the same, and don't require different code. Once the socket has been created, and the IP address determined, the connect() function call is used to form the data link between the two machines, using port 25. Now the remote machine can be interrogated for information about the user. Data is sent and received through the socket to the remote machine using the write() and read() system calls.
Port 25 is used because it is the port on which the Unix mail programs listen for incoming mail. This is one of Unix's well-known ports, and thus should only be used for this purpose. If you were to telnet to a Unix machine on port 25, you would be greeted by the local SMTP utility. (SMTP stands for Simple Mail Transfer Protocol and was defined in RFC-0821 by Jonathan B. Postel.) SMTP provides textual responses as well as three-digit numeric responses. Each line begins with a three-digit code. If a response spans multiple lines, the three-digit code is followed by a hyphen, otherwise it is followed by a space. Checkfor uses the numeric responses. Refer to Listing 2 for a sample SMTP query session.
Checkfor connects to the remote machine's SMTP service and waits for a response. The SMTP service will open up discussion with a 220 message identifying itself and the host. Checkfor uses the responses() subroutine to wait for the three-digit code. Upon receipt of a 220, checkfor then identifies itself to the SMTP service. It sends the HELO command through the socket to the remote machine. Again, checkfor waits for a response from the remote SMTP service, this time the code is a 250. If a timeout occurs, checkfor issues a QUIT and disconnects the socket.
If the HELO command was successful, then checkfor starts to ask about the user in question. It first tries to use the EXPN command. This command has the username as the parameter. If the username is found on the machine, it should return a result code of 250. The text following the result code indicates where the email will be sent.
The program now has the information it came for and can parse out any username@hostname pairs. However, I found an Ultrix machine that will return a result code of 252, without the information I wanted. Instead of informing me about email forwarding, it just tells me whether the username is valid on that machine. If a 252 is returned, then checkfor issues the VRFY command with the username as the parameter. Again, if the username is found, a result code of 250 should be returned, followed by text containing the forwarding information. In both cases, if the username is not found, a 550 result code is returned. Checkfor prints a user not found message on the standard output and stops following this lead.
I mentioned that SMTP returns a numeric result code and textual information. If the username was found, it will return a 250 result code, followed by the user's email address. Checkfor's responses() subroutine spools this information into a buffer. The parse_names() subroutine breaks the buffer up into username@hostname pairs. It then checks to see whether the username@hostname pair is the same as the pair it just checked. If so, it stops pursuing this branch, and backs up the tree one level. If this is not the same as the immediate username, checkfor compares the history buffer to see if this pair has previously been found. If it has, checkfor displays a message stating that this is a duplicate username@hostname pair. If this is a completely new pair, then a recursive call is made to the check() subroutine, thus starting the whole thing over again.
How is it used?
Checkfor was written on a 486 platform running Linux. It should work on most Unix machines. The only requirement is BSD Socket compatibility. The source code is available via anonymous ftp from: ftp://ftp.mfi.com/pub/sysadmin.
Place the source in any working directory and unpack it with tar -xvf checkfor.tar. You will need to edit checkfor.c and enter your machine name in between the quotes beside OURHOSTNAME. Then compile with cc -o checkfor checkfor.c.
There are two ways to use checkfor. The first is to enter the username@hostname pair on the command line (e.g., checkfor snowman waterw.com). This would check the mail forwarding of just that particular user. The invocation and results are in Listing 3. The second form of usage is to pipe the data through the standard input. This is accomplished using the -s option to checkfor (e.g., cat userdata | checkfor -s > results.txt). The format of userdata is each username hostname pair on a line by itself, separated by a space, see Listing 3.
So, now you're ready to check the email forwarding of the users at your site. What do you do if you find an email routing problem? The first thing you should do is contact the user in question. Depending on the problem, such as forwarding to a nonexistent address, it may be impossible to contact the user electronically, and you might have to resort to more primitive means of communication. If this doesn't work, ask the remote sys admin, perhaps they know where the user can be contacted. If this turns up no leads, consult your local policies on account modifications and fix the problem.
In retrospect, I would like to redo the hostname lookup. The use of gethostbyname() does not work for all mail exchangers. DNS can have MX entries for a host, but no A record of it. This will cause checkfor to fail. To correct this problem, the hostname lookup should be performed using the res_* revolver library functions. Aside from that shortcoming, the code works well and has saved me from various email headaches. I hope you find it to be a useful tool.
About the Author
William Schaffer recently started as a Software Engineer at Securicor Telesciences, Inc.,where he develops software for embedded Lynx OS platforms. He also spends time working with Linux and FreeBSD.