What is nupop?

nupop is a GPL'd POP3 server designed for high-performance (read: large user) enviroments. In particular, it optimizes for the best case POP session, where a user has no new mail and is simply receiving the same UIDL/STAT information as before. It is RFC 1939 compliant and supports the optional UIDL and TOP commands.

Why write yet another POP3 server?

Because there aren't really a lot of pop servers out there that are purely pop servers. Most are built on IMAP, and the IMAP back-ends have to do a lot more fooling around with message contents than a POP server does.

In addition, we can save a lot of resources by caching the information the server sends to the client, and only regenerating that information when needed. Say someone POPs every minute-- that's 1,440 POPs per day per user. Most POP servers will go through the mailbox and inspect every mail to create a UIDL list every time the user POPs. nupop, however, only rebuilds its list when the user has new mail, and the rest of the time uses a cache file. This makes the average pop session very effecient.



Since it's GPL'd, please feel free to add whatever features you want. Patches would be appreciated. ;)

Obtaining the software

Download the latest tarball.

Building the executable

You'll need Berkeley db installed to compile nupop. You can obtain a copy from SleepyCat Softwate, or if you're running RedHat 7.2, just install the "db" rpm.

After initializing the build environment, you'll probably want to make some changes in the .h files. Sorry, but since nupop runs out of inetd, having it read and parse a configuration file seemed pretty wasteful. Here are the directives and files you may want to change:

PAM_SERVICE_NAMEsrc/misc.hnupopThe PAM service name
SYSLOGNAMEsrc/misc.hnupopsyslog will prepend each line in the logfile with this
SYSLOGFACILITYsrc/misc.hLOG_LOCAL7The logging facility to which the messages will be sent
FAKE_UID_MAXsrc/fake_uid.h60000Emails with mtimes below this number go through the rewrite engine.
FAKE_UID_FILEsrc/fake_uid.h/Maildir/.fake_uidsThe default filename where the fake uids are stored
MAILDIR_DIRECTORYsrc/maildir.h/MaildirUse this if your maildir directory is called something else
POPUSERTABLEprivate/virtservers.h/etc/mail/popusertable.dbThe location of the database used for user rewrites
virtservers[]private/virtservers.h..undefined..A null terminated array of mappings of IP address to default domain
localdomains[]private/virtservers.h..undefined..A list of domains whose usernames correspond exactly to real unix usernames
UID_MINsrc/main.c99Won't authenticate a user whose uid is lower than this
GID_MINsrc/main.c99Won't authenticate a user whose gid is lower than this
MAXHUNKSsrc/maildir.h50Maximuim number of hunks (of emails) that will be malloc'd
HUNKSIZEsrc/maildir.h500Number of emails in each hunk
AUTHORIZATION_TIMEOUTsrc/main.c15 secMax time that connection can sit idle in AUTH state
TRANSACTION_TIMEOUTsrc/main.c30 secMax time that connection can sit idle in TRANSACTION

Set what you want, then make and make install.

The mailbox format

nupop expects there to be MAILDIR_DIRECTORY/new and MAILDIR_DIRECTORY/cur directories within the user's home directory. So if the user is "john" with a home directory of "/home/john" then there should be (using the default MAILDIR_DIRECTORY) a "/home/john/Maildir/new" and a "/home/john/Maildir/cur". As of 1.0, nupop will create these directories if they do not exist.

How it works (Q&D)

Command Handling: main.c takes lines of input and splits them into argc, argv. argc[0] (i.e. the command issued) corresponds to an entry in a jumptable. The command handlers then do all the work. The states (i.e. AUTHORIZATION, TRANSACTION, UPDATE) are numbered so that a simple and'ing can tell you what commands are allowed in any particular state.

Authentication: There is a single function called to authenticate the user. (This facilitates making a drop in replacement if your environment dictates.) After a user is authenticated, nupop does a setuid, setgid, and a chroot. This makes the string manipulation a lot easier in the actual function handlers.

Loading the emails: The MAILDIR_DIRECTORY/.statcache file is read if possible. If not, calc_mail_stats() stats all of the emails in new and cur, building the list of emails. The message list is then sorted, and mails with duplicate uids have their timestamps adjusted.

Retrieving an email: The file is mmapped, crlf's are added as needed (RFC822), and a uw-style "Status:" header is added. The RFC822 size of the output is printed to the client before the mail is sent. (see __send_file().)

Data structures: Each email is in a struct email; a hunk is an array of struct emails of size HUNKSIZE; which are indexed in array of size MAXHUNKS of pointers to hunks. (Basically it's one big array of emails split up into segments.) The cusor_reset, cursor_next, and cursor_seek functions set a global cursor pointer that lets you access any email you want by its message number.

After the data is calculated from the mailbox, it is sorted. This is done by creating an array of size nemails of pointers to struct emails. The list of pointers is sorted. If a sorted list exists, the cursor_next, cursor_reset, and cursor_seek functions will act on the sorted list transparently.

Saving the cache: The cache contains a version number, the timestamps on the new and cur directories, and all of the struct emails. If the list is sorted, then the cache will be output sorted and will not need to be resorted on a cache load. When the user gets new email the new and cur timestamps won't agree with the mailbox, and the cache will be discarded. Also note that the cache is _not_ written unless something has changed during the session, and the cache is deleted if there is an error during the session.


Please address problems to kstone_at_nuvox_dot_net. I try to help you as best I can. Patches, suggestion, questions are all welcome.