Search:

A POP3 Client

Some weeks ago I decided to re-install a Linux computer for my Internet access as I had it before. The installation includes sendmail as local transfer agent and pine as my mail reader. Everyting ok so far, but where is the fetchmail I used before? And where can I find the latest release on the Internet?

Since I am familiar with POP3 RFC #1939 I decided for some reason to try to do this with awk. The job is not really difficult: connect to the POP3 server, login with username and password, retrieve and delete everything that's on the POP3 server and submit the mails to the local sendmail for the final delivery.

POP3 Protocol Overview

The POP3 protocol is ASCII based, the client sends readable text commands (you could also issue with your telnet program) to the server and the server responds with a status code and perhaps additional information to them. As example I'll show the logon sequence:

client connects to server
S: +OK server ready
C: USER the-username
S: +OK
C: PASS the-password
S: +OK

"C:" means the client sends something to the server and "S:" stands for server sends to client.

The first important thing to notice here is that the server responds to a succesful POP3 command with a +OK response and (not shown above) with -ERR to errors, bad command etc.

Implementation

The client/server communication is not done directly in the source code although it could be done there because the protocol structure is simple. I encapsulate the protocol handling in the functions cfgets(), cfputs() and cfputc(). This makes the source code clean and enables me to modify the client just in case I misinterpreted the RFC (I agree that this is difficult for POP3). Another good reason (shown in the source code) is debugging. If debugging output (the POP3 communication between server and client) is printed on stderr or not is done it single points in this functions.

The details: cfgets() reads a single line from the server. cfgets is an abbreveation for client file get string (the f for file comes from C programming). Similary cfputs() sends a single line to the server.

cfputc() sends a POP3 command to the server, reads the server's response and compares the response's status code against the program's expection. Yes, for POP3 the only status code expection is a +OK but since I used almost the same set of functions also for other Internet protocols I like it that way.

cfputc() is the function that recognizes unexpected server responses (unexpected status code). So it keeps us from doing it everywhere in the source where we communicate with the server. cfputc() solves unexpected responses by simply writing an error message to stderr and terminating the program.

cfputc() is also able to read the initial server greeting because it sends only a command to the server if the parameter is set. If the string is empty cfputc() will read directly a server response.

Having these functions as prerequisites the source code for the POP3 login is done with (see the BEGIN block)

pop3 = "/inet/tcp/0" server "/110";
cfputc(pop3, "", "", "+OK");

cfputc(pop3, "USER", username, "+OK");
cfputc(pop3, "PASS", password, "+OK");

Simple and clean, eh? The next POP3 command shows an additional cfputc feature:

$0 = cfputc(pop3, "STAT", "", "+OK");

After the leading status code the server appends number of messages and total size of mailbox. Since we encapsulate everything in cfputc() this function returns the complete server response (but only if the status response meets out expectation). That way we are able to get the number of emails to read:

$0 = cfputc(pop3, "STAT", "", "+OK");
msgcount = $2+0;
bytes = $3+0;

After we now know how much messages we have to read we enter a loop which runs over all messages in our POP3 mailbox:

for (i=1; i<=msgcount; i++)
	spoolmail(pop3, i, receiver, maxsize, readonly);

spoolmail() is a compound function. It sends several POP3 commands to the server to fetch a message from the server and submits it to the local mail transfer agent (sendmail in this case). spoolmail() itself call another POP3 function: cfgetd().

cfgetd() reads a single data line from the server. The reason why we don't use cfgets() for this is that POP3 uses a common dot'ing scheme for data transmission: A line with only a dot signal the end of data, lines beginning with a dot are prefixed with an additional dot. cfgetd() does the required un-dot'ing for us and returns the `magic' string "\001" if the message's end has been reached.

After we have read and deleted out mail there only one thing to do, disconnecting from the server and terminating:

cfputc(pop3, "QUIT", "", "+OK");
exit (0);

That's basically all.

Further things

The parts of the program which have not been mentioned here deal with options and variations of the basic POP3 scheme: reading a configuration file with configurations for different POP3 server, leaving large messages on the server, a debug/test mode where we retrieve mails without deleting them.

< dag | at | awk-scripting.de >