A POP3 client to handle Internet protocols
Laura is a senior software engineer at Intercomp, creator of entertainment, hobby, and custom software. She can be contacted at [email protected]
Lately, I've been so upset by all the junk e-mail and messages I've been receiving that I finally decided to do something about it. Sure, some of the systems I use to get e-mail have message filtering, but they weren't customizable enough. Consequently, I decided to write a program to check the e-mail server and delete any messages with unwanted text out on the server before it ever reached my e-mail program.
I started writing the project in Perl because it has a nice, easy-to-use library of routines to handle the POP3 e-mail protocol. Once I began coding, however, I missed using some of the utility routines I'd written in C++ and I wondered why I couldn't do the exact same thing in C/C++. However, getting from the start of a Perl e-mail filtering implementation to something that actually worked in C/C++ was an experience. It's one that may help you, should you need your own POP3 client or a library to handle SMTP, FTP, or some other well-known Internet protocol. You'll see part of the results of my search for a C/C++ POP3 implementation in the code (available electronically; see "Resource Center," page 5). The sample program works as an e-mail notifier to let you know when you receive new e-mail.
Sockets to the Rescue
My first step was to try to locate source code or libraries on the Internet related to POP3. I didn't find much. What I did find was very compiler specific. Since my current compiler, which I've owned and worked with for over 10 years, is in the process of being dropped by the company that sells it, the last thing I wanted was compiler-specific code. Since I have no idea what compiler my new code will eventually run on, why waste time writing something I may not be able to use or even compile in the future?
Realizing that looking for POP3 material was getting me nowhere, I widened my search and began looking for socket-related material. This was a step in the right direction. I found out that POP3 and several other protocols could be implemented fairly portably using Berkeley sockets. I also found a couple of interesting socket libraries available -- one even had a POP3 implementation. I decided not to stop there because the actual libraries weren't necessarily portable and were also quite large. I didn't need the complexity. However, I did realize that between the various FAQs on sockets and the RFC documentation (such as RFC 1725), both available on the Internet, I could write my own implementation.
Using Berkeley sockets as the basis of the implementation sounded like the solution to my problem, until I started digging deeper. UNIX systems such as FreeBSD supply the API. On Windows-based systems you get Winsock. From what I had read, Winsock was supposed to support Berkeley socket-style programming. As soon as I started coding, however, I discovered some interesting differences. I was tripped on those differences all the way through writing my code.
Windows versus UNIX
What's so different about a Windows implementation as compared to a UNIX one? The first thing I found was that most Windows programs used asynchronous sockets, something not supported in the Berkeley socket standard. Winsock provides a group of functions prefixed with "WSA" that are based on the original BSD functions, but adapted to work with message passing in a nonpreemptive multitasking environment. I could write code to take advantage of the Windows message-passing system, but then I'd need two sets of code for everything socket related. I decided to stick with a synchronous implementation.
I still needed to decide whether to go with blocking or nonblocking sockets. There are some tricky issues when dealing with either type of socket in Windows, especially if you're using only the BSD functions. If blocking sockets are used in a Win32 GUI program and not placed in a separate execution thread, they may cause the program to stop responding to user input while the block is being handled. This is dependent on which version of the Winsock library is used. However, nonblocking sockets can be inefficient and eat up CPU time while the program polls to find out when a command is completed. Assuming that the problem of tying up user input can be overcome, the program may crash if users are allowed to accidentally activate a socket function call before a currently executing socket function call is completed.
I decided to stick with blocking sockets, which is the default. It would be simple to write a member function to change the settings to allow nonblocking sockets as well. I also decided to use the BSD select function whenever I was concerned about blocking for long periods of time. This function could let me know if a blocking function would need to wait before it could return when called. It also allowed for setting a time-out, which would let me know if I would otherwise have to wait too long for a blocking function to finish. The particular program I was working on didn't require a GUI. However, if I needed to code a GUI program in the future, I could work with select to do so. Careful use of calling the select function before calling blocking functions would be required. Alternating between calling select with a reasonable time-out and the message system to check for user input should provide a sufficient approach to writing simple socket programs with GUIs.
I decided to limit my implementation to synchronous, blocking sockets, and stay away from the Windows-specific extensions (functions starting with WSA), and then I'd have portable code, right? Wrong. I tried writing a simple socket class that connected to a server, and I absolutely could not get it to connect. Realizing from previous work with MFC that I had to call AfxInitSocket before anything would work, I began to search to see if I needed to do some strange initialization. At this point, I was using Mingw32 as my test compiler (which, by the way, I'm extremely impressed with), which doesn't have an AfxInitSocket function. After a long search, I finally discovered that I had to call some of those WSA functions after all. I needed WSAStartup and WSACleanup. No problem. I just hid those in my socket class and used #ifdef to keep them out of the way when compiling on a UNIX platform.
I also found that the header files were different between UNIX and Windows. More #ifdefs. Also, watch out for the libraries to link in. I could link on a UNIX box without problems (as long as I used the C++ compiler and not the C compiler), but using Mingw32, I needed to add -lwsock32 when linking in order to include the Winsock library.
I added a few more #defines and #ifdefs to cover some of the minor differences between platforms, such as whether or not SOCKET and SOCKET_ERROR were defined. I figured I finally had the platform differences under control, until I added some code to my socket class to deal with server implementation. That's when I found out that functions such as accept used int * with Winsock and on some UNIX platforms and socklen_t * with later versions of UNIX Berkeley sockets. I decided to create a socklen_t definition on Windows and set it to int *. If you're using a UNIX implementation that does not supply socklen_t, you'll need the define as well.
I finally had a working socket class. Implementing the POP3 protocol seemed fairly straightforward following the information in the RFC. I, therefore, won't go into details on the protocol. There is one tricky piece about it that I'd like to mention. You may not want to send an unencrypted password to your mail server out over the Internet. There is the APOP command that lets you send an MD5 encrypted password instead. This only works on servers that support it. Notice the code in the POP3 class that checks if the return string from the server handles MD5 encryption. If it handles MD5, you can take the reply information from the server, which is sent after a connection. The data between angle brackets, plus your password, needs to be encrypted and sent back to the server. I haven't included the actual MD5 encryption routine here. There is a C example included in the RFC 1321 documentation. You may also want to plug in a function from a library that supplies MD5 encryption.
The actual program supplied here is broken up into two parts -- the socket class, which encapsulates a lot of the Berkeley socket functionality, and the POP3 class, which knows about the POP3 protocol. The program here only checks if you have new messages to read on your server. You can use other member functions in the POP3 class to retrieve or delete the messages or do other interesting tasks with them.
There is, of course, a lot of room for expansion in this implementation. My final implementation will split out the socket address-related code into another smaller class. I may decide to encapsulate messages into their own class so I can use it with POP3 or SMTP protocols. This would also be a good place to add routines dealing with MIME encoding. I also intend to add client and server classes between the socket class and the protocol classes to handle functionality that needs to be done repeatedly. I can add classes for SMTP, FTP, and any other protocols I might need in the future. By the time I'm finished, I could end up with a large library of code. If I've chosen my classes carefully, it should be easy to add new code to the library as my needs increase. I should even be able to access APIs other than Berkeley sockets for platforms that don't support the implementation, by finding a working TCP/IP API or library and hiding the connection in my socket class using #ifdefs. This is important if I decide I need to port to a platform such as DOS. However, at the moment all I wanted was a simple program to filter my e-mail, so the classes I have here, combined with some of my parsing classes, are more than sufficient to get the job done.
The code I've included (available electronically) has been compiled successfully under Windows and FreeBSD using Mingw32 and g++ (the GNU C++ compiler implementation). I've only tested it using three different e-mail servers, so results may vary depending on how your particular e-mail server implements the protocol. If you come up with any other changes to make the code work on more platforms or with other servers, or if you add any enhancements for other protocols, I hope you will share them. (It might provide a good opportunity to write your own article.)
The nice part of this exercise is that I no longer feel lost when I think about implementing something like a POP3 client in C/C++. I'm not stuck with having to uses languages such as Perl or Java although the libraries for such functionality are not only available but highly portable. I have the advantage of a simple, fast, compiled executable using C/C++ on whatever platform I need, and I'm not locked in to a particular C/C++ compiler or a particular platform.