Binc IMAP uses either Maildir++
or a structure called IMAPdir to
store its set of mailboxes. IMAPdir is more or less similar to Maildir++,
but it provides more flexibility with regards to mailbox names and
hierarchy structure.
In a sense, IMAPdir takes all
the goods from Maildir and adds root
level mailboxes, submailboxes both of regular root level mailboxes and
of the special mailbox INBOX, mail in mailboxes of any level, and with
no restrictions.
In the root of the IMAPdir
structure, Binc IMAP stores the list of a user's subscribed folders in
a file called
bincimap-subscribed. This file should only be edited manually
if you are confident with Binc::Storage. Normally the
administrator and the IMAP user will leave this to Binc IMAP.
Binc IMAP's Maildir backend
(default) will temporarily create a lock file called
bincimap-scan-lock inside a Maildir when it is
scanning for mailbox changes and delegating unique message
identifiers. This is to ensure that UIDs are delegated exactly once to
every message that has been detected by any one Binc IMAP server
instance. Binc IMAP does not require a common locking locking
mechanism to function well with other concurrent Maildir accessors, so
you can run it next to any other IMAP or POP server.
Inside each Maildir, Binc IMAP
stores two files that allow multiple instances of the server to
communicate the state and changes of the mailbox:
bincimap-uidvalidity and
bincimap-cache. These files are safe to delete, although that
will trigger UIDVALIDITY to bounce and clients may have to
resynchronize their local state.
|
Binc IMAP's design is simple and modular, yet
modern and advanced. This makes it easy to maintain and
extend.
Although the IMAP protocol is relatively complex, you will
find that Binc IMAP's solution is surprisingly easy to
grasp.
At the heart of Binc IMAP's implementation lies the basic
functionality for Object Oriented Design provided by the ISO C++
standard and general knowledge in the area of standard Design
Patterns.
The main components are:
Note: This overview corresponds to version 1.1.9.
|
|
Summary
The flow of operations is as follows:
- We find the Broker for the current state.
- The Broker parses the stub (first two or three tokens)
and generates a Request
- The Broker finds an Operator that can handle the Request.
- The Operator continues parsing and decorates the Request.
- The Operator processes the Request with the Depot.
- The Operator can list mailboxes by using the Depot Iterator.
- The Operator can work on Mailboxes and Messages.
- Messages are looked up with the Mailbox Iterator.
- The Session state data is updated using the Session singleton.
- The Operator writes output data using the IO unit.
- When the Operator is done, we update the current state in the
Session singleton, and start over.
Request
A Request object holds all information
that is necessary for an Operator to serve a
specific IMAP command.
Request objects have names such as
"CHECK", "COPY" and "LOGOUT", that reflect what operation they mean
to perform.
For the name "FETCH", the Request object
is decorated with a
sequence set, optionally a section and so on. The
parse() method in each Operator is
responsible for decorating the Request
object.
The Request object is
short-lived. It is created, decorated, passed on to the Operator, then discarded.
Broker
One Broker holds a set of Operators. For each
state Binc IMAP is in, the BrokerFactory delegates exactly one Broker to hold the relevant Operator objects.
Typically, an Operator can be assigned to
more than one Broker. For example, the Operator that serves the IMAP command "NOOP" (a
command that is available in all three IMAP states),
NoopOperator, is available in all Broker objects.
In bincimap-up, the unprivileged stub, there is only ony
Broker. In bincimapd, the authenticated
main server, there are three Brokers.
The Broker is responsible for finding the
appropriate Operator, when given a Request object.
// Get broker for current state
Broker *broker = BrokerFactory.getBroker(STATE_SELECTED);
// Generate request and have broker parse the stub
Request request;
broker->parseStub(request);
// Find an operator for the request
Operator *o = broker->get(command.getName());
// Have the operator finish the parsing
o->parse(request);
// Process the request.
o->process(depot, request);
|
BrokerFactory
The BrokerFactory manages the Broker objects.
Given a state, the BrokerFactory provides a Broker that holds all the Operator objects available to the client.
This provides a modular and safe separation of the priviledges
available at the different states in the IMAP session.
The preauthenticate stub has a BrokerFactory that can only generate Broker objects for the non-authenticated
state.
Depot
A Depot is responsible for handling the
different Mailbox objects, and it is the
mailbox structure authority.
Given an IMAP mailbox path as input, a Depot
can give the caller a corresponding Mailbox
object if it finds one that successfully identifies the type of Mailbox.
The Depot is also aware of what the
default Mailbox type object is. This
Mailbox object is used when creating new IMAP
mailboxes.
Finally, the Depot is used to translate
mailbox names to a representation on the file system and back. There
are currently two specializations of the Depot
object available: one for Maildir++
and one for IMAPdir. Each has
its own characteristics in how do translate the mailbox hierarchy to
the file system.
Mailbox *mailbox = depot.getDefaultMailbox();
mailbox->createMailbox("work/2003/07/todo");
|
DepotFactory
The DepotFactory manages the Depot objects.
New Depot objects are assigned to the DepotFactory in runtime. This makes it easy
to add new Depot objects using loadable
modules. The Depot objects are registered and
accessed via their names, such as "Maildir++"
or "IMAPdir".
The DepotFactory gives individual
users of Binc IMAP the option to choose the Depot object that suits their needs the best.
IO
The IO is a global. It consists of two instances -
com and logger.
com reads and writes characters to and from the client,
and hides the optional SSL encryption transparently.
logger writes characters to Binc IMAP's log files. It
hides the method used to log data. Currently it supports logging to
stderr and syslog.
Mailbox
The Mailbox is an abstract for Binc IMAP's different
backends. Bundled with Binc is a backend for Maildir. The class
Maildir inherits Mailbox.
In short, a Mailbox contains all methods
needed for Binc IMAP to serve a specific backend. It also holds a
method to identify a Mailbox of its own
kind.
An Operator uses the Mailbox abstract when working on
mailboxes. It can perform all operations, such as fetching, deleting
and copying with no knowledge of the type of mailbox it is working
with. Through polymorphism, details about the specific mailbox
types' internal methods for performing different tasks are hidden
transparently.
The Mailbox also has a generic iterator. By using the Mailbox
iterator, the Operator can access all messages in the Mailbox
through the Message abstract. Again, the Operator has no knowledge
of the format of each message nor the way in which they are
stored. The Mailbox iterator is a Proxy Iterator.
All registered Mailbox objects are
controlled by the Depot, and they are accessed
through Depot methods such as getSelected() and getDefault().
Mailbox *mailbox = depot.getSelected();
if (!mailbox)
return NO;
Mailbox::iterator i = mailbox->begin();
while (i != mailbox->end()) {
Message &message = *i;
message.setStdFlag(F_DELETED);
++i;
}
mailbox->expungeMailbox();
mailbox->close();
|
|
This example shows how the Mailbox and Mailbox::iterator
provide a natural interface for working with mailboxes and
messages.
|
Message
The Message is an abstract used by the Operator when working with
messages of any type. Through the interface provided by this class,
Operators can perform operations on messages with no knowledge of
the type of message or how it is stored.
In Maildir, messages are stored as files. Each file is a MIME
document stored in Unix-style with only LF. The MaildirMessage is an
implementation of Message used in Maildir. When using a Maildir
mailbox, Mailbox::iterator will return a reference to a
MaildirMessage. MaildirMessage also uses a MaildirMessageCache
singleton to handle cacheing of messages.
Although the inside of MaildirMessage both deals with files,
cacheing and MIME, the Operator needs not think about this.
Operator
An Operator is associated with an IMAP command such as
"SEARCH" or "AUTHENTICATE". In short, the Operator is used to
perform an arbitrary operation on a Mailbox.
Typically, an Operator can be assigned to one or more
Broker objects.
Operators contain, among others, the two public methods:
parse() and process().
The parse() method decorates a Request object. This object can then be fed to
process() together with a Depot.
When processing its request, an Operator
is allowed to generate untagged responses and it can also
update the state of a Mailbox, the Depot or the Session
singleton.
Operator objects are assigned
dynamically to each Broker, making it
very easy to write extensions that add or replace existing Operator objects using Binc IMAP's loadable
module support.
Session
The Session is a singleton object that holds
information that is relevant to the current IMAP session.
Currently, the Session contains information about:
- Global configuration (administrator settings)
- Local configuration (user settings)
- Command line arguments
- Folder subscription list
Last updated on 2003-07-31.
Please direct comments on this document to the Binc IMAP mailing list. Remember to search
the archives first.