changeset 0:3b1985cbc908 HEAD

Initial revision
author Timo Sirainen <tss@iki.fi>
date Fri, 09 Aug 2002 12:15:38 +0300
parents
children 6d0a30d1197c
files .cvsignore .inslog2 AUTHORS COPYING.LGPL Makefile.am NEWS README TODO acconfig.h autogen.sh configure.in doc/.cvsignore doc/Makefile.am doc/auth.txt doc/design.txt doc/index.txt doc/multiaccess.txt doc/nfs.txt dovecot-example.conf src/.cvsignore src/Makefile.am src/auth/.cvsignore src/auth/Makefile.am src/auth/auth-digest-md5.c src/auth/auth-interface.h src/auth/auth-plain.c src/auth/auth.c src/auth/auth.h src/auth/common.h src/auth/cookie.c src/auth/cookie.h src/auth/login-connection.c src/auth/login-connection.h src/auth/main.c src/auth/master.c src/auth/userinfo-pam.c src/auth/userinfo-passwd-file.c src/auth/userinfo-passwd.c src/auth/userinfo-passwd.h src/auth/userinfo-shadow.c src/auth/userinfo.c src/auth/userinfo.h src/imap/.cvsignore src/imap/.psrc src/imap/Makefile.am src/imap/client.c src/imap/client.h src/imap/cmd-append.c src/imap/cmd-authenticate.c src/imap/cmd-capability.c src/imap/cmd-check.c src/imap/cmd-close.c src/imap/cmd-copy.c src/imap/cmd-create.c src/imap/cmd-delete.c src/imap/cmd-examine.c src/imap/cmd-expunge.c src/imap/cmd-fetch.c src/imap/cmd-list.c src/imap/cmd-login.c src/imap/cmd-logout.c src/imap/cmd-lsub.c src/imap/cmd-noop.c src/imap/cmd-rename.c src/imap/cmd-search.c src/imap/cmd-select.c src/imap/cmd-status.c src/imap/cmd-store.c src/imap/cmd-subscribe.c src/imap/cmd-uid.c src/imap/cmd-unsubscribe.c src/imap/commands-util.c src/imap/commands-util.h src/imap/commands.c src/imap/commands.h src/imap/common.h src/imap/main.c src/lib-imap/.cvsignore src/lib-imap/Makefile.am src/lib-imap/imap-bodystructure.c src/lib-imap/imap-bodystructure.h src/lib-imap/imap-envelope.c src/lib-imap/imap-envelope.h src/lib-imap/imap-match.c src/lib-imap/imap-match.h src/lib-imap/imap-message-cache.c src/lib-imap/imap-message-cache.h src/lib-imap/imap-message-send.c src/lib-imap/imap-message-send.h src/lib-imap/imap-parser.c src/lib-imap/imap-parser.h src/lib-imap/imap-util.c src/lib-imap/imap-util.h src/lib-index/.cvsignore src/lib-index/Makefile.am src/lib-index/mail-hash.c src/lib-index/mail-hash.h src/lib-index/mail-index-data.c src/lib-index/mail-index-data.h src/lib-index/mail-index-fsck.c src/lib-index/mail-index-update.c src/lib-index/mail-index-util.c src/lib-index/mail-index-util.h src/lib-index/mail-index.c src/lib-index/mail-index.h src/lib-index/mail-lockdir.c src/lib-index/mail-lockdir.h src/lib-index/mail-messageset.c src/lib-index/mail-messageset.h src/lib-index/mail-modifylog.c src/lib-index/mail-modifylog.h src/lib-index/maildir/.cvsignore src/lib-index/maildir/Makefile.am src/lib-index/maildir/maildir-build.c src/lib-index/maildir/maildir-index.c src/lib-index/maildir/maildir-index.h src/lib-index/maildir/maildir-open.c src/lib-index/maildir/maildir-rebuild.c src/lib-index/maildir/maildir-sync.c src/lib-index/maildir/maildir-update.c src/lib-index/mbox/.cvsignore src/lib-index/mbox/Makefile.am src/lib-index/mbox/mbox-append.c src/lib-index/mbox/mbox-fsck.c src/lib-index/mbox/mbox-index.c src/lib-index/mbox/mbox-index.h src/lib-index/mbox/mbox-lock.c src/lib-index/mbox/mbox-lock.h src/lib-index/mbox/mbox-open.c src/lib-index/mbox/mbox-rebuild.c src/lib-index/mbox/mbox-sync.c src/lib-mail/.cvsignore src/lib-mail/Makefile.am src/lib-mail/message-content-parser.c src/lib-mail/message-content-parser.h src/lib-mail/message-parser.c src/lib-mail/message-parser.h src/lib-mail/message-size.c src/lib-mail/message-size.h src/lib-mail/rfc822-address.c src/lib-mail/rfc822-address.h src/lib-mail/rfc822-date.c src/lib-mail/rfc822-date.h src/lib-mail/rfc822-tokenize.c src/lib-mail/rfc822-tokenize.h src/lib-storage/.cvsignore src/lib-storage/Makefile.am src/lib-storage/flags-file/.cvsignore src/lib-storage/flags-file/Makefile.am src/lib-storage/flags-file/flags-file.c src/lib-storage/flags-file/flags-file.h src/lib-storage/index/.cvsignore src/lib-storage/index/Makefile.am src/lib-storage/index/index-copy.c src/lib-storage/index/index-expunge.c src/lib-storage/index/index-fetch-section.c src/lib-storage/index/index-fetch.c src/lib-storage/index/index-fetch.h src/lib-storage/index/index-save.c src/lib-storage/index/index-search.c src/lib-storage/index/index-status.c src/lib-storage/index/index-storage.c src/lib-storage/index/index-storage.h src/lib-storage/index/index-sync.c src/lib-storage/index/index-update-flags.c src/lib-storage/index/maildir/.cvsignore src/lib-storage/index/maildir/Makefile.am src/lib-storage/index/maildir/maildir-copy.c src/lib-storage/index/maildir/maildir-expunge.c src/lib-storage/index/maildir/maildir-list.c src/lib-storage/index/maildir/maildir-save.c src/lib-storage/index/maildir/maildir-storage.c src/lib-storage/index/maildir/maildir-storage.h src/lib-storage/index/mbox/.cvsignore src/lib-storage/index/mbox/Makefile.am src/lib-storage/index/mbox/mbox-expunge.c src/lib-storage/index/mbox/mbox-list.c src/lib-storage/index/mbox/mbox-save.c src/lib-storage/index/mbox/mbox-storage.c src/lib-storage/index/mbox/mbox-storage.h src/lib-storage/mail-search.c src/lib-storage/mail-search.h src/lib-storage/mail-storage.c src/lib-storage/mail-storage.h src/lib-storage/subscription-file/.cvsignore src/lib-storage/subscription-file/Makefile.am src/lib-storage/subscription-file/subscription-file.c src/lib-storage/subscription-file/subscription-file.h src/lib/.cvsignore src/lib/Makefile.am src/lib/base64.c src/lib/base64.h src/lib/compat.c src/lib/compat.h src/lib/failures.c src/lib/failures.h src/lib/fdpass.c src/lib/fdpass.h src/lib/gmtoff.c src/lib/gmtoff.h src/lib/hash.c src/lib/hash.h src/lib/hex-binary.c src/lib/hex-binary.h src/lib/hostpid.c src/lib/hostpid.h src/lib/imem.c src/lib/imem.h src/lib/iobuffer.c src/lib/iobuffer.h src/lib/ioloop-internal.h src/lib/ioloop-poll.c src/lib/ioloop-select.c src/lib/ioloop.c src/lib/ioloop.h src/lib/lib-signals.c src/lib/lib-signals.h src/lib/lib.c src/lib/lib.h src/lib/macros.h src/lib/md5.c src/lib/md5.h src/lib/mempool-allocfree.c src/lib/mempool-allocfree.h src/lib/mempool-alloconly.c src/lib/mempool-system.c src/lib/mempool.c src/lib/mempool.h src/lib/mmap-util.c src/lib/mmap-util.h src/lib/network.c src/lib/network.h src/lib/primes.c src/lib/primes.h src/lib/randgen.c src/lib/randgen.h src/lib/restrict-access.c src/lib/restrict-access.h src/lib/strfuncs.c src/lib/strfuncs.h src/lib/temp-mempool.c src/lib/temp-mempool.h src/lib/temp-string.c src/lib/temp-string.h src/lib/unlink-directory.c src/lib/unlink-directory.h src/lib/unlink-lockfiles.c src/lib/unlink-lockfiles.h src/login/.cvsignore src/login/.psrc src/login/Makefile.am src/login/auth-connection.c src/login/auth-connection.h src/login/client-authenticate.c src/login/client-authenticate.h src/login/client.c src/login/client.h src/login/common.h src/login/main.c src/login/master.c src/login/master.h src/login/ssl-proxy.c src/login/ssl-proxy.h src/master/.cvsignore src/master/.psrc src/master/Makefile.am src/master/auth-process.c src/master/auth-process.h src/master/common.h src/master/imap-process.c src/master/login-process.c src/master/login-process.h src/master/main.c src/master/master-interface.h src/master/settings.c src/master/settings.h stamp.h.in
diffstat 272 files changed, 33537 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.cvsignore	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,30 @@
+Makefile
+Makefile.in
+aclocal.m4
+config.cache
+config.guess
+config.h
+config.log
+config.status
+config.sub
+configure
+configure.scan
+libtool
+libtool-shared
+ltconfig
+ltmain.sh
+stamp-h
+stamp-h.in
+stamp.h
+version.h
+config.h.in
+.exrc
+install-sh
+missing
+mkinstalldirs
+INSTALL
+intl
+ABOUT-NLS
+COPYING
+build-stamp
+configure-stamp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.inslog2	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,4 @@
+92e1e6668c743330e53b24b2c424b27b47ee62a6e49d1c2b3d9321ec8ae8e79a628ccb
+74a9b29fe624b1e15990fb1c56598fa94c2b93b455e388c9b4a2855f296d0503072fb2
+71b63590cf183ec61f83cc8431c3dd18b93ac37465c33da8abbb40e40695858c46dac8
+3016ee7c4b6ec0ed7f2b9c4ae30b9147cc070188818915e6125a64ae85eeb4
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/AUTHORS	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,21 @@
+Timo Sirainen <tss@iki.fi>
+
+Solar Designer <solar@openwall.com> (src/auth/userinfo-passwd|shadow|pam.c)
+
+This product includes software developed by Computing Services
+at Carnegie Mellon University (http://www.cmu.edu/computing/).
+(src/lib/base64.c, src/lib-imap/imap-match.c)
+
+GLib Team (src/lib/hash.c, primes.c, strfuncs.c)
+---------
+Shawn T. Amundson  <amundson@gimp.org>
+Jeff Garzik        <jgarzik@pobox.com>
+Raja R Harinath    <harinath@cs.umn.edu>
+Tim Janik          <timj@gtk.org>
+Elliot Lee         <sopwith@redhat.com>
+Tor Lillqvist      <tml@iki.fi>
+Paolo Molaro       <lupus@debian.org>
+Havoc Pennington   <hp@pobox.com>
+Manish Singh       <yosh@gimp.org>
+Owen Taylor        <otaylor@gtk.org>
+Sebastian Wilhelmi <wilhelmi@ira.uka.de>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/COPYING.LGPL	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,510 @@
+
+                  GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations
+below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+^L
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it
+becomes a de-facto standard.  To achieve this, non-free programs must
+be allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+^L
+                  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control
+compilation and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+^L
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+^L
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at least
+    three years, to give the same user the materials specified in
+    Subsection 6a, above, for a charge no more than the cost of
+    performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+^L
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+^L
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply, and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License
+may add an explicit geographical distribution limitation excluding those
+countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+^L
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                            NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+^L
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms
+of the ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.
+It is safest to attach them to the start of each source file to most
+effectively convey the exclusion of warranty; and each file should
+have at least the "copyright" line and a pointer to where the full
+notice is found.
+
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or
+your school, if any, to sign a "copyright disclaimer" for the library,
+if necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James
+  Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Makefile.am	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,8 @@
+SUBDIRS = src doc
+
+confdir = $(sysconfdir)
+conf_DATA = dovecot-example.conf
+
+EXTRA_DIST = \
+	COPYING.LGPL \
+	$(conf_DATA)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,86 @@
+
+ * Alpha release
+
+This release has not been extensively tested, I don't recommend putting it
+into production usage yet. All the IMAP4rev1 features are fully supported,
+except custom message flags still need to be implemented. There's several
+minor problems left, see TODO file.
+
+ * Mail storages
+
+Maildir is the preferred storage with most robust implementation. mbox
+support is available but currently it relies a little bit on good luck, I
+made it mostly for personal use and I strongly recommend using maildir if
+at all possible.
+
+Maildir folders are done like with courier. INBOX is the ~/Maildir
+directory, all the other folders named as ".<folder>.<subfolder>.<etc>"
+under it, any directories not starting with a dot are simply ignored.
+Deleting folders is made atomic by renaming them as "..<folder name>" and
+deleting the directory after that. So, all directories beginning with ".."
+are deleted whenever they're noticed. Indexes are stored into each folder's
+root directory.
+
+mbox storage currently requires that all mail is in user-writable
+directory, eg. ~/mail. Especially using /var/mail/user is NOT supported.
+You may however create a ~/mail/inbox hardlink (or symlink if not chrooted)
+to pointing to /var/mail/user. All files in the mail directory are
+considered as mailboxes. Directories specify subfolders in IMAP. "inbox"
+file specifies the INBOX folder and can not be named otherwise. Indexes are
+stored into ".imap/<mailbox name>/" directories.
+
+imap process detects the storage from MAIL-environment which is preferred
+to be in format "<storage>:<data>", for example "maildir:~/Maildir". It's
+anyway allowed to be in pretty much any format as long as some of the
+storages recognizes it as a valid data, so for example "MAIL=~/mail" is
+first checked by maildir storage to see if it's valid maildir and
+mbox storage after that.
+
+If the MAIL environment isn't given at all, all the storages are gone
+through which try to find a valid directory for themselves to use. Also as
+a special case, if MAILDIR environment exists, maildir storage is used with
+the directory specified in it.
+
+Maildir storage is autodetected by checking if <directory>/cur/ exists and
+we have rwx access to it. If directory isn't known, / and ~/Maildir are
+checked. / is checked because we could be chrooted.
+
+mbox storage is autodetected by checking if .imap/ (+rwx), inbox (+rw) or
+mbox (+rw) exists in directory. If directory isn't known, / is tried first.
+After that, ~/mail and ~/Mail directories are used if they're found without
+checking if they even contain any files.
+
+ * Code
+
+The code is split into a several libraries and binaries:
+
+ src/master      - imap-master binary (see docs/design.txt)
+ src/login       - imap-login binary
+ src/auth        - imap-auth binary
+ src/imap        - imap binary
+
+ src/lib         - Generic library functions
+ src/lib-mail    - RFC-822 and MIME parsering code
+ src/lib-imap    - IMAP-specific functions for parsing, sending, etc.
+ src/lib-index   - Mailbox indexing library, slightly IMAP-specific
+ src/lib-storage - Mail storage separated into interface and implementation.
+                   imap binary uses only the interface so it's possible to
+		   add support for any kind of mail storage (eg. SQL).
+
+ * RFCs conformed
+
+822  - Standard for ARPA Internet Text Messages
+2822 - Internet Message Format (updated rfc822)
+2045..2049 - Multipurpose Internet Mail Extensions (MIME)
+
+2060 - IMAP4rev1
+2180 - IMAP4 Multi-Accessed Mailbox Practice
+
+2595 - Using TLS with IMAP, POP3 and ACAP
+2831 - Using Digest Authentication as a SASL Mechanism (DIGEST-MD5)
+
+rfc2831
+
+ * Contact info
+
+Timo Sirainen <tss@iki.fi>, http://dovecot.procontrol.fi/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TODO	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,142 @@
+test:
+ - make sure mmap()s work properly with NFS
+ - make sure first_hole_records is updated properly
+ - make sure locking is done properly when opening/switching modifylog 
+ - make sure index->header->flags are updated at correct times
+ - make sure SELECT rebuilds index properly when next_uid is near 32bit value
+ - make sure io_buffer_send() handles huge inputs properly
+ - make sure rfc822_parse_date() works properly
+ - make sure imap_match functions work properly
+ - try imap_message_send() / io_buffer_send_file()
+ - make sure connection limits work
+
+index:
+ - optimization:
+     - optimize so that when all mail is deleted, the indexes will be
+       truncated
+     - could hash function be better..? like uid*uid? what about changing
+       probe strategy from linear to something else?
+     - support shrinking hash file when it becomes 99% empty or so
+     - save part sizes + positions so MessagePart could be regenerated from
+       index..? probably only needed if BODY/BODYSTRUCTURE is saved, as it's
+       only useful with FETCH BODY[mime.sections] and they wouldn't be known
+       without bodystructure..
+     - index->lookup_uid_range(): first_uid could quite often be either the
+       first UID or some UIDs below the first. optimize these by remembering
+       the first UID in index.
+ - mbox:
+     - BUG: adding new mail after indexes are created doesn't work
+     - save MD5 sums for messages?
+     - update Status and X-Status headers when flags are changed
+     - last \n shouldn't be sent for messages. also remember to fix
+       the From-checks to check for [\r]\nFrom instead then..
+     - EXPUNGE doesn't delete the mail from the mbox file
+     - fsck should probably (or optionally?) really scan the message body
+       for "\nFrom " text instead of just jumping over the message body.
+       Quite useless actually, but this would make it fully reliable with md5
+       anyway..
+ - there's some race condition issues when opening mailboxes..
+ - when opening index files, check the flags and do what's needed. fsck and
+   rebuild is supported currently. compression and hash rebuilding is still
+   needed. and the cache_fields .. not sure when that'd be done, preferably
+   in the separate compress-process..
+ - set_lock() is ugly and horrible and should really be done something.
+   does the syncing really need to be there? maybe put it into separate
+   function which can be called after set_lock() by functions which actually
+   care about the sync state (fetch, search, store, etc).
+ - read-only support so we could use an index where we don't have
+   write-access? we should use MAP_PRIVATE everywhere with these boxes to
+   make sure we don't get exploited .. and anyway recheck everything to make
+   sure there wouldn't be a way to exploit them.
+ - if index was just rebuilt, modify log complains about indexid mismatch
+   at first open
+ - does append work?
+
+lib-storage:
+ - support multiple mailbox formats and locations for one user. that would
+   require support for multiple MailStorages, and since we're chroot()ed,
+   usually the only way to communicate with others would be to create
+   RemoteMailStorage which would use TCP/UNIX sockets to connect to another 
+   imap session.
+ - DELETE/RENAME: when someone else had the mailbox open, we should
+   disconnect it (when stat() fails with ENOENT while syncing)
+ - optimize SEARCH [UN]SEEN, [UN]DELETED and [UN]RECENT. They're able to
+   skip lots of messages based on the index header data.
+ - use a trie index for fast text searching, like cyrus squat?
+ - hardlink-COPY doesn't copy flags
+ - maildir: atomic COPY could be done by setting a "temporary" flag into the
+   file's name. once copying is done, set an ignore-temporary field into
+   index's header. at next sync the temporary flag will be removed.
+ - mbox: internal_date isn't saved 
+ - select "" shouldn't work.
+
+general:
+ - capabilities:
+     - acl (rfc2086)
+     - quota (rfc2087)
+     - namespace (rfc2342), id (rfc2971), mailbox-referrals (rfc2193),
+       literal+ (rfc2088), idle (rfc2177), uidplus (rfc2359)
+     - drafts: listext, children, unselect, multiappend, annotatemore
+         - sort, thread: are these really useful for clients? do any actually
+	   use them? i'd think most clients want to know all the messages
+	   anyway and can do the sorting/threading themselves.
+         - http://www.imc.org/ids.html
+ - check if t_push()/t_pop() should be added somewhere
+ - rfc-2231 continuation support
+ - "UID FETCH|SEARCH|STORE *" doesn't work if latest message was deleted.
+   should we bother to fix this? I doubt there's a client that would use this.
+ - RENAME INBOX isn't atomic with Maildir. And in general, RENAME can't
+   be moved to another storage. Maybe support doing also using COPY + delete
+   once COPY is atomic?
+
+ - go through .temp files and delete them
+ - grep for FIXME
+ - cache keeps the last message mmap()ed .. is there some case when it's not
+   a good idea? like the file changes in the background? cache should be
+   updated then. yes, especially with mbox support. the mmap should be
+   removed after unlocking. also, it shouldn't depend on mmap() anyway as
+   it's not possible to use it with eg. SQL storage.. except if we make
+   mmap()ing it optional, just give it some function which in some way
+   generates const char *msg + size_t.
+ - if auth process died and login couldn't immediately reconnect to it, it's
+   left until next user connects. however the connection needs to read the
+   init data before it can be used, so the user gets "NO Unknown
+   authentication method" error the first time
+ - ulimit / setrlimit() should be set somewhere
+ - create indexer binary
+ - SEARCH CHARSET support, iconv()?
+ - Fix the blocking SSL handshake
+ - SRP authentication support?
+ - Digest-MD5: support integrity protection, and maybe crypting. Do it
+   through imap-login like SSL is done?
+ - imap-auth should limit how fast authentication requests are allowed from
+   login processes. especially if there's one login/connection the speed
+   should be something like once/sec.
+ - support executing each login in it's own process, so if an exploit is ever
+   found from it, the attacker can't see other users' passwords
+ - the error messages given in command replies can sometimes be quite
+   specific, eg. rename(/full/path, /full/new/path) failed: xxx. These
+   probably shouldn't be shown to user, instead just print some "internal
+   error" with a timestamp and the real error would be written into syslog.
+   all errors from lib-index should be done this, and maybe some/all
+   lib-storage errors as well (there's separate error vs. critical)
+ - mmap()ing large messages isn't very good idea. we need to support doing
+   everything in pieces .. add mmap() support for iobuffers, and use them
+   everywhere?
+ - Make sure messages of size INT_MAX..UINT_MAX work correctly
+ - allocating readwrite pools now just uses system_pool .. so pool_unref()
+   can't free memory used by it .. what to do about it? at least count the
+   malloc/free calls and make complain if at the exit they don't match
+ - put IMAP_LOGFILE into config file. and the timestamp format.
+ - SIGHUPing master should reload the configuration
+ - Something's wrong with expunging mails from maildir ..
+
+optional optimizations:
+ - provide some helper binary to save new mail into mailboxes with CR+LF
+   line breaks?
+ - disk I/O is the biggest problem, so split the mail into multiple computers
+   based on user and have a proxy in the front redirecting the connection.
+   cyrus had something like this except a lot more complicated - it tried
+   to fix the problem of having shared mailboxes. we have the same problem
+   with local shared mailboxes as we chroot(), so locally we could communicate
+   with UNIX sockets, remotely that could be done with TCP sockets.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/acconfig.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,23 @@
+/* Build with SSL/TLS support */
+#undef HAVE_SSL
+
+/* build with IPv6 support */
+#undef HAVE_IPV6
+
+/* Define if you have struct tm->tm_gmtoff */
+#undef HAVE_TM_GMTOFF
+
+#undef USERINFO_PASSWD
+#undef USERINFO_PASSWD_FILE
+#undef USERINFO_SHADOW
+#undef USERINFO_PAM
+#undef AUTH_PAM_USERPASS
+
+/* IMAP capabilities */
+#undef CAPABILITY_STRING
+
+/* Index file compatibility flags */
+#undef MAIL_INDEX_COMPAT_FLAGS
+
+/* Required memory alignment */
+#undef MEM_ALIGN_SIZE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/autogen.sh	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,4 @@
+aclocal
+automake --add-missing
+autoheader
+autoconf
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/configure.in	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,302 @@
+AC_INIT(src)
+
+AM_CONFIG_HEADER(config.h)
+AM_INIT_AUTOMAKE(dovecot, 0.96)
+
+AM_MAINTAINER_MODE
+
+AC_ISC_POSIX
+AC_PROG_CC
+AC_PROG_CPP
+AC_STDC_HEADERS
+AC_C_INLINE
+AC_ARG_PROGRAM
+AM_PROG_LIBTOOL
+
+AC_CHECK_HEADERS(string.h stdlib.h unistd.h dirent.h sys/sendfile.h)
+
+# check posix headers
+AC_CHECK_HEADERS(sys/time.h)
+
+AC_ARG_ENABLE(ipv6,
+[  --enable-ipv6           Enable IPv6 support],
+	if test x$enableval = xno; then
+		want_ipv6=no
+	else
+		want_ipv6=yes
+	fi,
+	want_ipv6=no)
+
+AC_ARG_ENABLE(passwd,
+[  --disable-passwd        Disable /etc/passwd support],
+	if test x$enableval = xno; then
+		want_passwd=no
+	else
+		want_passwd=yes
+	fi,
+	want_passwd=yes)
+
+AC_ARG_ENABLE(passwd-file,
+[  --disable-passwd-file   Disable passwd-like file support],
+	if test x$enableval = xno; then
+		want_passwd_file=no
+	else
+		want_passwd_file=yes
+	fi,
+	want_passwd_file=yes)
+
+AC_ARG_ENABLE(shadow,
+[  --disable-shadow        Disable shadow password support],
+	if test x$enableval = xno; then
+		want_shadow=no
+	else
+		want_shadow=yes
+	fi,
+	want_shadow=yes)
+
+AC_ARG_ENABLE(pam,
+[  --disable-pam           Disable PAM support],
+	if test x$enableval = xno; then
+		want_pam=no
+	else
+		want_pam=yes
+	fi,
+	want_pam=yes)
+
+dnl **
+dnl ** just some generic stuff...
+dnl **
+
+AC_CHECK_FUNC(socket, [], [
+	AC_CHECK_LIB(socket, socket, [
+		LIBS="$LIBS -lsocket"
+	])
+])
+
+AC_CHECK_FUNC(inet_addr, [], [
+	AC_CHECK_LIB(nsl, inet_addr, [
+		LIBS="$LIBS -lnsl"
+	])
+])
+
+dnl * after -lsocket and -lnsl tests, inet_aton() may be in them
+AC_CHECK_FUNCS(fcntl flock inet_aton sigaction getpagesize madvise setreuid)
+AC_CHECK_FUNCS(strcasecmp stricmp vsnprintf memmove vsyslog)
+
+dnl * poll/select?
+
+AC_CHECK_FUNC(poll, [
+	have_poll=yes
+], [
+	have_poll=no
+])
+AM_CONDITIONAL(IOLOOP_POLL, test "$have_poll" = "yes")
+
+dnl * gcc specific options
+if test "x$ac_cv_prog_gcc" = "xyes"; then
+	# -W -Wchar-subscripts -Wpointer-arith -Wcast-align -Wconversion -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations
+	CFLAGS="$CFLAGS -Wall"
+fi
+
+dnl * OS specific options
+case "$host_os" in
+	hpux*)
+		CFLAGS="$CFLAGS -D_XOPEN_SOURCE_EXTENDED"
+		;;
+	*)
+		;;
+esac
+
+dnl * memory alignment, could be 1 for x86 systems but 4 should be
+dnl * compatible with everyone. note that only 1, 2 and 4 work corrently.
+dnl * is 8 needed anywhere?
+AC_DEFINE(MEM_ALIGN_SIZE, 4)
+
+dnl * socklen_t - AC_CHECK_TYPE() would be _really_ useful if it only would
+dnl * accept header files where to find the typedef..
+AC_MSG_CHECKING([for socklen_t])
+AC_CACHE_VAL(i_cv_type_socklen_t,
+[AC_TRY_COMPILE([
+#include <sys/types.h>
+#include <sys/socket.h>],
+[socklen_t t;],
+i_cv_type_socklen_t=yes,
+i_cv_type_socklen_t=no,
+)])
+if test $i_cv_type_socklen_t = no; then
+	AC_DEFINE(socklen_t, int, Define to 'int' if <sys/socket.h> doesn't define.)
+fi
+AC_MSG_RESULT($i_cv_type_socklen_t)
+
+dnl * do we have tm_gmtoff
+AC_MSG_CHECKING([for tm_gmtoff])
+AC_CACHE_VAL(i_cv_field_tm_gmtoff,
+[AC_TRY_COMPILE([
+#include <time.h>],
+[struct tm *tm; return tm->tm_gmtoff;],
+i_cv_field_tm_gmtoff=yes,
+i_cv_field_tm_gmtoff=no,
+)])
+if test $i_cv_field_tm_gmtoff = yes; then
+	AC_DEFINE(HAVE_TM_GMTOFF)
+fi
+AC_MSG_RESULT($i_cv_field_tm_gmtoff)
+
+dnl **
+dnl ** SSL (gnutls)
+dnl **
+
+AC_CHECK_LIB(gnutls, gnutls_global_init, [
+	AC_DEFINE(HAVE_SSL)
+	SSL_LIBS="-lgnutls -lgcrypt"
+	AC_SUBST(SSL_LIBS)
+	have_ssl=yes
+], [
+	have_ssl=no
+], -lgcrypt)
+
+dnl **
+dnl ** shadow/pam support
+dnl **
+
+need_crypt=no
+auths=""
+
+if test $want_passwd = yes; then
+	need_crypt=yes
+        AC_DEFINE(USERINFO_PASSWD)
+	auths="$auths passwd"
+fi
+
+if test $want_passwd_file = yes; then
+	need_crypt=yes
+        AC_DEFINE(USERINFO_PASSWD_FILE)
+	auths="$auths passwd-file"
+fi
+
+if test $want_shadow = yes; then
+	AC_CHECK_FUNC(getspnam, [
+		need_crypt=yes
+		AC_DEFINE(USERINFO_SHADOW)
+		auths="$auths shadow"
+	])
+fi
+
+if test $want_pam = yes; then
+	AC_CHECK_LIB(pam, pam_start, [
+		AC_CHECK_HEADER(security/pam_appl.h, [
+			USERINFO_LIBS="$USERINFO_LIBS -lpam"
+			AC_DEFINE(USERINFO_PAM)
+			auths="$auths pam"
+		])
+	])
+fi
+
+if test $need_crypt = yes; then
+	AC_CHECK_LIB(crypt, crypt, [
+		USERINFO_LIBS="$USERINFO_LIBS -lcrypt"
+	], [
+		AC_CHECK_FUNC(crypt,, [
+			AC_ERROR([crypt() wasn't found])
+		])
+	])
+fi
+
+AC_SUBST(USERINFO_LIBS)
+
+dnl **
+dnl ** Index file compatibility flags
+dnl **
+
+dnl * currently just checking for endianess
+
+AC_C_BIGENDIAN
+
+if test $ac_cv_c_bigendian = yes; then
+	flags=0
+
+else
+	flags=1
+fi
+
+AC_DEFINE_UNQUOTED(MAIL_INDEX_COMPAT_FLAGS, $flags)
+
+dnl **
+dnl ** IPv6 support
+dnl **
+
+if test "x$want_ipv6" = "xyes"; then
+	AC_MSG_CHECKING([for IPv6])
+	AC_CACHE_VAL(i_cv_type_in6_addr,
+	[AC_TRY_COMPILE([
+	#include <sys/types.h>
+	#include <sys/socket.h>
+	#include <netinet/in.h>
+	#include <netdb.h>
+	#include <arpa/inet.h>],
+	[struct in6_addr i;],
+	i_cv_type_in6_addr=yes,
+	i_cv_type_in6_addr=no,
+	)])
+	if test $i_cv_type_in6_addr = yes; then
+		AC_DEFINE(HAVE_IPV6)
+	fi
+	AC_MSG_RESULT($i_cv_type_in6_addr)
+fi
+
+dnl **
+dnl ** capabilities
+dnl **
+
+capability="IMAP4rev1"
+if test "$have_ssl" = "yes"; then
+	capability="$capability STARTTLS"
+fi
+AC_DEFINE_UNQUOTED(CAPABILITY_STRING, "$capability")
+
+dnl **
+dnl ** register the storage classes
+dnl **
+
+STORAGE="maildir mbox"
+file="src/lib-storage/mail-storage-register.c"
+
+echo "/* this file is generated by configure */" > $file
+echo '#include "lib.h"' >> $file
+echo '#include "mail-storage.h"' >> $file
+for storage in $STORAGE; do
+	echo "extern MailStorage ${storage}_storage;" >> $file
+done
+echo "void mail_storage_register_all(void) {" >> $file
+for storage in $STORAGE; do
+	echo "mail_storage_class_register(&${storage}_storage);" >> $file
+done
+echo "}" >> $file
+
+AC_OUTPUT(
+Makefile
+doc/Makefile
+src/Makefile
+src/lib/Makefile
+src/lib-imap/Makefile
+src/lib-index/Makefile
+src/lib-index/maildir/Makefile
+src/lib-index/mbox/Makefile
+src/lib-mail/Makefile
+src/lib-storage/Makefile
+src/lib-storage/index/Makefile
+src/lib-storage/index/maildir/Makefile
+src/lib-storage/index/mbox/Makefile
+src/lib-storage/subscription-file/Makefile
+src/lib-storage/flags-file/Makefile
+src/auth/Makefile
+src/imap/Makefile
+src/login/Makefile
+src/master/Makefile
+stamp.h)
+
+echo
+echo "Install prefix ............. : $prefix"
+echo "Building with auth modules . :$auths"
+echo "Building with SSL support .. : $have_ssl"
+echo "Building with IPv6 support . : $want_ipv6"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/.cvsignore	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/Makefile.am	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,6 @@
+EXTRA_DIST = \
+	auth.txt \
+	design.txt \
+	index.txt \
+	multiaccess.txt \
+	nfs.txt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/auth.txt	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,68 @@
+Authentication is split into two separate parts: the authentication method,
+and the password/user information backend (FIXME: is there a good name for
+that? I can't think of any).
+
+Currently supported methods:
+
+ - plaintext: By itself it's very insecure, but through secured SSL/TLS
+   connection it should be fine.
+ - Digest-MD5: Should be quite secure by itself, and it also supports
+   integrity protection and crypting the rest of the communication, but
+   we don't support those yet.
+
+Currently supported backends:
+
+ - passwd: /etc/passwd or similiar, using getpwnam()
+ - shadow: /etc/shadow or similiar, using getspnam()
+ - pam: PAM authentication
+ - passwd-file: /etc/passwd-like file
+
+passwd, shadow and pam backends work only with plaintext authentication.
+passwd-file can be used with both plaintext and Digest-MD5. More backends
+can be easily added later.
+
+
+passwd-file
+-----------
+
+This is compatible with regular /etc/passwd, and a password file used by
+libpam-pwdfile. It's in the following format:
+
+user:password:uid:gid:(ignored):home:(ignored):realm:mail:flags
+
+Only user and password fields are required. If uid, gid or home fields
+aren't set, they're read from system's passwd file. If the user doesn't
+exist in system, a warning is printed to syslog and the user entry is
+ignored.
+
+Either home or mail is required to exist. Home specifies the home directory
+of user under which mail is located. The actual mail format and location is
+automatically detected, just as if you run the imap-binary directly. Other
+way is to specify the mail storage parameters directly using the
+mail-field, see README file for more information about it (the MAIL
+environment).
+
+Flags is a comma-separated list of flags, currently only recognized value
+is "chroot", which makes the imap process chroot into home directory, if
+it's allowed by master process.
+
+Realm is useful only with Digest-MD5 authentication. It's possible to have
+multiple users with same name but in different realms. If plaintext is used
+to log in, the user is searched from all the realms, and first found is
+used.
+
+The password field is in format: <data> "[" <type> "]", like "foo[13]".
+Type can be one of the following:
+
+ 13: DES password
+ 34: MD5 password
+ 56: Digest-MD5 password - Hexadecimal MD5 sum of "user:realm:password"
+
+If [type] isn't specified at all or it's unknown, DES is used. The 13 and
+34 methods are compatible with PAM module pwdfile. Only the 56 method works
+with Digest-MD5 authentication.
+
+Easiest way to generate Digest-MD5 passwords is to use perl:
+
+perl -MDigest::MD5 -e 'print Digest::MD5::md5_hex("user:realm:pass")."\n"'
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/design.txt	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,121 @@
+
+Design
+------
+
+Security is the major goal of this project, with reliability coming second.
+I also try to keep things fast, extensible and portable.
+
+Things are broken into multiple processes running with minimal required
+privileges. Communication between processes is trusted as little as
+possible. Processes running as root are kept as simple as possible even if
+it means minor performance hits.
+
+imap-master
+-----------
+
+Runs as root. Executes new processes, some by itself and some by request of
+another process. The requested processes can never be started as root, and
+the allowed UID range can also be limited.
+
+It's also possible to configure everything to be run under a single UID.
+This is useful only if you wish to use imap somewhere you don't have root
+access.
+
+imap-login
+----------
+
+Runs as non-privileged user (imapd). Handles accepting new client
+connections and parsing commands until the client is authenticated. There
+can be either a few of them which handle multiple connections, or one
+process per connection. One per connection is much more secure in case it's
+ever found to be exploitable, but it's also less efficient.
+
+SSL and TLS connections are also fully handled by the login process.
+Instead of passing the connection's fd to imap process, login creates a new
+anonymous UNIX socket and uses it to translate communication between imap
+process and the client. If you're using one login process per connection,
+this also means that you have actually two processes all the time for an
+active SSL IMAP connection.
+
+Since SSL protocol is quite complex and I'm using gnutls which is still in
+beta, it shouldn't be trusted to be fully secure. Using one login process 
+per connection should however make it quite safe to use, as the login is
+running in a chrooted environment without any privileges. However, the
+attacker could get your private SSL key..
+
+Note that if you let a single login process handle multiple connections, a
+security flaw would allow the attacker to see all the other user
+connections connecting to it, possibly hijacking them or stealing their
+passwords if plaintext authentication was used.
+
+imap-auth
+---------
+
+Runs under minimal required privileges to be able to authenticate users.
+In case of shadow passwords or PAM, that's root. Communicates with
+imap-login and imap-master to authenticate users.
+
+ * imap-login
+    - Receives LOGIN or AUTHENTICATE command
+    - Begins authentication with imap-auth process, with AUTHENTICATE
+      continuing passing data between client and imap-auth until done
+       - If successful, we've received a cookie which we send to imap-master
+          * imap-master
+	     - Requests data from imap-auth for the cookie. Data includes
+	       UID, GID and mail format and mail format specific data (eg.
+	       mail directory). Optionally also receives chroot directory.
+	     - Checks that the UID is in valid range, and that it's allowed
+	       to be chrooted under given directory
+		- If everything is valid, pass the connection to imap process
+	     - Replies to imap-login whether everything was valid
+	  - If successful, stop processing the connection, imap process takes
+	    care of the rest
+
+ * imap-auth
+    a) Receives authentication request with given protocol
+        - Initialize the request in protocol-specific manner
+	- If failed, send a failure-reply to client
+	- Otherwise send a secret cookie (randomized data) to client
+    b) Receives continued authentication request for given cookie
+        - Verifies that the cookie is valid, replying if not
+        - Handle the data in protocol-specific manner
+	- Reply to client with information whether the authentication
+	  is finished
+	- Reset cookie expiration time
+    c) Receives a request to receive data associated to cookie
+        - Verifies that the cookie is valid, replying if not
+	- Reply with the data
+
+imap
+----
+
+Runs non-privileged and optionally chrooted (when it's safe). Since this is
+the largest part of the imapd, this is where most of the potential security
+flaws are.
+
+Maildir and mbox formats use a few index files to look up data fast, the
+actual mail files aren't opened unless they're really needed. The index
+files are accessed using shared memory maps and locked using fcntl().
+
+Using shared memory maps creates a security problem if the file isn't
+trusted. It might well be possible to create a buffer overflow in some
+function by modifying the file as while it's being used. Currently this
+should not be a problem as we require write access to the files ourself, so
+attacker shouldn't be able to get any extra privileges by exploiting the
+imap process. However, once support for shared readonly mailboxes are
+implemented, we need to switch into using private memory maps for them and
+to make sure that truncating files abruptly won't create any problems.
+
+Other than the shared memory mapping problem, the index files are not
+trusted to contain valid data. Everything in them validated before being
+used.
+
+
+indexer
+-------
+
+Indexer may be started by master process when it thinks there's some extra
+time to be used. It goes through users' mailboxes and compresses or
+rebuilds indexes when it sees a need for it. The actual indexing is done by
+dropping root privileges just as with imap process.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/index.txt	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,180 @@
+imap index files
+----------------
+
+Designed to be NFS-safe and accessible from multiple computers, even with
+different architecture. Should support pretty much any mail format, at
+least maildir and mbox can be implemented with it.
+
+
+Index file
+----------
+
+.imap.index: ID => data lookups
+
+header:
+	unsigned char compat_data[4];
+	/* 0 = flags,
+	   1 = sizeof(unsigned int),
+	   2 = sizeof(time_t),
+	   3 = sizeof(off_t) */
+
+	unsigned int version;
+	unsigned int indexid;
+
+	unsigned int flags;
+	unsigned int cache_fields;
+
+	off_t first_hole_position;
+	off_t first_hole_records;
+
+	unsigned int uid_validity;
+	unsigned int first_uid;
+	unsigned int next_uid;
+
+	unsigned int messages_count;
+	unsigned int last_nonrecent_uid;
+	unsigned int first_unseen_seq;
+
+	unsigned int reserved_for_future_usage[5];
+
+Version is always currently always 1, anything else will be considered
+invalid. The compat_data[] is just for making sure the index isn't tried to
+be accessed by incompatible computers. If they don't match, either another
+index is created or everything is aborted.
+
+All index files must begin with name ".imap.index", which is also the first
+file name tried. If it can't be used, all the files beginning with 
+".imap.index" are checked. If compatible index isn't found,
+".imap.index-<hostname>" is created as the index.
+
+File name of all the other files related to the index (data, hash, modify
+log) begin with the index's name and have ".data", ".hash" etc. appended to
+it. Also, all the files must have the same value in indexid field as the
+index or they'll be treated as being corrupted.
+
+first_hole_position and first_hole_size specify the first deleted block in
+this index file. This allows us to quickly do sequence => UID lookup even
+if some messages are already deleted. The deleted blocks should be
+compressed whenever there's time, to keep index lookups fast. 
+
+cache_fields contains the bitmask of fields that should be indexed, it can
+be updated at any time, so some earlier messages may not have indexed
+everything that newer messages have. This field can be used to quickly
+check if it's even possible to find some field from index.
+
+data:
+	unsigned int uid;
+	unsigned int msg_flags; /* MailFlags | IndexMailFlags */
+	time_t internal_date;
+	time_t sent_date;
+
+	off_t data_position;
+	unsigned int data_size;
+
+	unsigned int cached_fields;
+	unsigned int headers_size;
+
+cached_fields is a bitmask of indexed fields in data file.
+
+
+Index data file
+---------------
+
+.imap.index.data: variable length sized data
+
+header:
+	unsigned int indexid
+
+data:
+	unsigned int field; /* MailField */
+	unsigned int full_field_size;
+	char data[]; /* variable size */
+	...
+
+Fields are ordered by the field type, beginning with lowest. The fields are
+always \0 terminated which determines their real length, the
+full_field_size only marks how much space is entirely allocated for the
+field. This may differ from the real size if we've allocated some extra
+space for a field which may grow (eg. flags in maildir filename).
+
+
+Hash File
+---------
+
+.imap.index.hash: UID => index lookups
+
+header:
+	unsigned int indexid;
+	unsigned int flags;
+	unsigned int used_records;
+
+data:
+	unsigned int uid;
+	off_t position;
+
+File is treated as a hash map. The hash function is UID*2 % size, where
+size is (filesize - sizeof(header)) / sizeof(data). If the position is
+already taken, the value is placed into next available position. When
+looking up the hash, lookup can't be aborted until first free slot is
+found. Free slots are identified by having UID 0.
+
+
+Locking
+-------
+
+File locking is done using fcntl(), so currently there's no support for NFS
+servers that don't support it. File based locking would be possible, but I
+haven't bothered to do it at least yet.
+
+There's also directory lock which is done by creating a
+.dirlock.<hostname>.<pid> file and once linking it to .dirlock succeeds,
+the process owns the lock.
+
+There's of course the problem that some process may die and leave the file
+locked. So, if the first lock try fails, the invalid locks are looked up
+and deleted. For locks in the same host, the pids are checked to be valid.
+For other hosts there's a timeout of 30 minutes. The locks shouldn't be
+hold more than a few seconds at maximum, so the 30 mins is probably a bit
+too much, but it's there only to be sure that small clock differences
+between hosts don't break things.
+
+
+Modify log file
+---------------
+
+.imap.index.access: mailbox access counter
+
+Everyone who are accessing the mailbox must mark themselves known, so when
+someone is updating the mailbox, it should append to log file the expunges
+and mail flag updates it did. Using the log file other imap processes can
+quickly notify clients about the changes.
+
+Besides using it to notify clients, it's also used to map client given
+message sequence numbers to real sequence numbers. They're different when
+client hasn't yet been notified of the latest expunges in the mailbox.
+
+The access counter is implemented using hard links - there's the one base
+.imap.index.access file that all processes link to
+.imap.index.access.<hostname>.<pid> files. Invalid links are checked and
+deleted the same way as directory locks above.
+
+
+External changes
+----------------
+
+(Maildir-specific)
+
+External changes are noticed when index file's timestamp is different than
+the maildir's timestamp. When modifying the index file, it's timestamp
+should be set to maildir directory's timestamp when it was last in a known
+synced state.
+
+There's still the possibility that new mail comes just after we've synced
+the dir (or in the middle of it), but that's a bit difficult to fix without
+scanning the directory through all the time which gets slow.
+
+Luckily however this shouldn't be much of a problem, as new mail comes to
+new/ directory where it's always noticed. It's only the cur/ directory that
+may not always be exactly synced if someone else has been messing up with
+it. And if someone else has done that, she most likely has also seen the
+mail using that other mail client.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/multiaccess.txt	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,16 @@
+RFC2180 describes several ways to handle multiple client access in same
+mailbox. Here's how we've implemented them.
+
+DELETE and RENAME commands work always, other clients that had the mailbox
+selected are disconnected next time they try to use a selected-state
+command.
+
+FETCH command sends replies to all non-expunged messages and if any
+expunged messages were references, replies with a tagged NO.
+
+STORE with .SILENT silently ignores expunged messages. Without .SILENT it's
+handled the same way as FETCH.
+
+SEARCH ignores expunged messages.
+
+COPY fails if any of the given messages were expunged.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/nfs.txt	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,18 @@
+ - In general, we should be NFS-safe.
+
+ - NFS server must support fcntl() locking currently. It's possible to
+   add file-based locking code, but I haven't bothered.
+
+ - Modifylog uses fcntl() for figuring out when to delete the log file, and
+   assumes that changing file locking between F_RDLCK / F_WRLCK is atomic
+   (not sure if this is the case with all operating systems, I hope so).
+   This anyway could be more difficult to change not to use fcntl().
+
+ - gethostname() must return different name for each IMAP server accessing
+   a user's mailboxes
+
+ - Clocks should be somewhat synchronized:
+    - maildir: One minute difference is enough to have a small chance of
+      new mail to get temporarily lost until more mail arrives.
+    - One hour is enough to create a problem when two imap servers try to
+      open the same mailbox at the same time.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dovecot-example.conf	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,161 @@
+## Dovecot 1.0 configuration file
+
+# Default values are shown after each value, it's not required to uncomment
+# any of the lines.
+
+# Port to listen in for IMAP connections. This port is used for TLS
+# connections as well. Setting it to 0 disables it.
+#imap_port = 143
+
+# Port to listen in for SSL IMAP connections. Setting it to 0 disables it.
+#imaps_port = 993
+
+# IP or host address where to listen in for IMAP connections. Empty means to
+# listen in all interfaces. It's not possible to specify multiple.
+#imap_listen = 
+
+# IP or host address where to listen in for SSL IMAP connections. Defaults
+# to imap_listen if not specified.
+#imaps_listen = 
+
+# SSL certificate/key, they're opened as root
+#ssl_cert_file = /etc/ssl/certs/imapd.pem
+#ssl_key_file = /etc/ssl/private/imapd.pem
+
+# Disable LOGIN command and all other plaintext authentications unless
+# SSL/TLS is used (LOGINDISABLED capability)
+#disable_plaintext_auth = no
+
+##
+## Login process
+##
+
+# Executable location
+#login_executable = /usr/lib/dovecot/imap-login
+
+# User to use for imap-login process
+#login_user = imapd
+
+# Directory where imap-auth places authentication UNIX sockets which login
+# needs to be able to connect to. The sockets are created when running as
+# root, so you don't need to give imap-auth any access for it.
+#login_dir = /var/run/dovecot/login
+
+# chroot() imap-login process to the login_dir. Only reason not to do this
+# is if you wish to run the whole imapd without roots.
+#login_chroot = yes
+
+# Number of imap-login processes to use, one or two is enough
+#login_processes_count = 1
+
+# Maximum number of connections allowed in login state. When this limit is
+# reached, the oldest connections are dropped.
+#max_logging_users = 256
+
+##
+## IMAP process
+##
+
+# Executable location
+#imap_executable = /usr/lib/dovecot/imap
+
+# Maximum number of running imap processes. When this limit is reached,
+# new users aren't allowed to log in.
+#max_imap_processes = 1024
+
+# Valid UID/GID ranges for imap users, defaults to 500 and above.
+# Note that denying root logins is hardcoded to imap-master binary and
+# can't be done even if first_valid_uid is set to 0.
+#first_valid_uid = 500
+#last_valid_uid = 0
+
+#first_valid_gid = 1
+#last_valid_gid = 0
+
+# ':' separated list of directories under which chrooting is allowed for imap
+# processes (ie. /var/mail will allow chrooting to /var/mail/foo/bar too).
+# WARNING: Never add directories here which local users can modify, that
+# may lead to root exploit. Usually this should be done only if you don't
+# allow shell access for users.
+#valid_chroot_dirs = 
+
+# Copy mail to another folders using hard links. This is much faster than
+# actually copying the file. Only problem with it is that if either of the
+# mails are modified directly both will change. This isn't a problem with
+# IMAP however since it offers no way to modify the existing mails. Also
+# at least mutt modifies mails by deleting the old one and inserting a new
+# modified mail. So if performance matters at all you should turn this on.
+#maildir_copy_with_hardlinks = no
+
+# Check if mails' content has been changed by external programs. This slows
+# down things as extra stat() needs to be called for each file.
+#maildir_check_content_changes = no
+
+# umask to use for mail files and directories
+#umask = 0077
+
+##
+## Authentication processes
+##
+
+# You can have multiple processes; each time "auth = xx" is seen, a new
+# process definition is started. The point of multiple processes is to be
+# able to set stricter permissions to others. For example, plain/PAM
+# authentication requires roots, but if you also use digest-md5 authentication
+# for some users, you can authenticate them without any privileges in a
+# separate auth process. Just remember that only one auth process is asked
+# for the password, so you can't have different passwords with different
+# processes (unless they have different auth methods, and you're ok with
+# having different password for each method).
+
+# Authentication process name.
+auth = default
+
+# Authentication methods this process allows separated with a space
+auth_methods = plain
+
+# Space separated list of realms with authentication methods that need them.
+# This is usually empty or the host name of the server (eg.
+# mail.mycompany.com).
+#  - plain auth checks the password from all realms specified in here
+#  - digest-md5 must have the password added for each realm separately, and
+#    many clients simply use the first realm listed here. so if you really
+#    need to add more realms, add them to end of the list.
+#auth_realms =
+
+# Where the user information and passwords are stored into:
+#   passwd: /etc/passwd or similiar, using getpwnam()
+#   shadow: /etc/shadow or similiar, using getspnam()
+#   pam: PAM authentication
+#   passwd-file /etc/passwd.imap: /etc/passwd-like file. Supports digest-md5
+#                                 style passwords
+auth_userinfo = shadow
+
+# Executable location
+#auth_executable = /var/lib/dovecot/imap-auth
+
+# User to use for the process. Only shadow and pam authentication requires
+# roots, so use something else if possible.
+auth_user = root
+
+# Directory where to chroot the process
+#auth_chroot = 
+
+# Number of authentication processes to create
+#auth_count = 1
+
+
+# digest-md5 authentication process. It requires special MD5 passwords which
+# /etc/shadow and PAM doesn't support, so we never need roots to handle it.
+# Note that the passwd-file is opened before chrooting and dropping root
+# privileges, so it may be 0600-root owned file.
+
+#auth = digest_md5
+#auth_methods = digest-md5
+#auth_realms = 
+#auth_userinfo = passwd-file /etc/passwd.imap
+#auth_user = imapauth
+#auth_chroot = /var/run/dovecot/auth
+
+# if you plan to use only passwd-file, you don't need the two auth processes,
+# simply set "auth_methods = plain digest-md5"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/.cvsignore	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Makefile.am	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,1 @@
+SUBDIRS = lib lib-mail lib-imap lib-index lib-storage auth master login imap
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/.cvsignore	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,9 @@
+*.la
+*.lo
+*.o
+.deps
+.libs
+Makefile
+Makefile.in
+so_locations
+imap-auth
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/Makefile.am	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,31 @@
+pkglib_PROGRAMS = imap-auth
+
+INCLUDES = \
+	-I$(top_srcdir)/src/lib
+
+imap_auth_LDADD = \
+	../lib/liblib.a \
+	$(USERINFO_LIBS)
+
+imap_auth_SOURCES = \
+	auth.c \
+	auth-plain.c \
+	auth-digest-md5.c \
+	cookie.c \
+	login-connection.c \
+	main.c \
+	master.c \
+	userinfo.c \
+	userinfo-passwd.c \
+	userinfo-shadow.c \
+	userinfo-pam.c \
+	userinfo-passwd-file.c
+
+noinst_HEADERS = \
+	auth.h \
+	auth-interface.h \
+	common.h \
+	cookie.h \
+	login-connection.h \
+	userinfo.h \
+	userinfo-passwd.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/auth-digest-md5.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,612 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+/* Digest-MD5 SASL authentication, see rfc-2831 */
+
+#include "common.h"
+#include "base64.h"
+#include "hex-binary.h"
+#include "md5.h"
+#include "randgen.h"
+#include "temp-string.h"
+
+#include "auth.h"
+#include "cookie.h"
+#include "userinfo.h"
+
+#include <stdlib.h>
+
+#define SERVICE_TYPE "imap"
+
+/* Linear whitespace */
+#define IS_LWS(c) ((c) == ' ' || (c) == '\t')
+
+typedef enum {
+	QOP_AUTH	= 0x01,	/* authenticate */
+	QOP_AUTH_INT	= 0x02, /* + integrity protection, not supported yet */
+	QOP_AUTH_CONF	= 0x04, /* + encryption, not supported yet */
+
+	QOP_COUNT	= 3
+} QopOption;
+
+static char *qop_names[] = { "auth", "auth-int", "auth-conf" };
+
+typedef struct {
+	Pool pool;
+	unsigned int authenticated:1;
+
+	/* requested: */
+	char *nonce;
+	QopOption qop;
+
+	/* received: */
+	char *realm; /* may be NULL */
+	char *username;
+	char *cnonce;
+	char *nonce_count;
+	char *qop_value;
+	char *digest_uri; /* may be NULL */
+	unsigned char response[32];
+	unsigned long maxbuf;
+	unsigned int nonce_found:1;
+	unsigned int utf8:1;
+
+	/* final reply: */
+	char *rspauth;
+        AuthCookieReplyData cookie_reply;
+} AuthData;
+
+static const char *get_digest_challenge(AuthData *auth)
+{
+	TempString *qoplist, *realms;
+	char *const *tmp;
+	unsigned char nonce[16];
+	int i;
+
+	/*
+	   realm="hostname" (multiple allowed)
+	   nonce="randomized data, at least 64bit"
+	   qop-options="auth,auth-int,auth-conf"
+	   maxbuf=number (with auth-int, auth-conf, defaults to 64k)
+	   charset="utf-8" (iso-8859-1 if it doesn't exist)
+	   algorithm="md5-sess"
+	   cipher="3des,des,rc4-40,rc4,rc4-56" (with auth-conf)
+	*/
+
+	/* get 128bit of random data as nonce */
+	random_fill(nonce, sizeof(nonce));
+	auth->nonce = p_strdup(auth->pool,
+			       base64_encode(nonce, sizeof(nonce)));
+
+	/* get list of allowed QoPs */
+	qoplist = t_string_new(32);
+	for (i = 0; i < QOP_COUNT; i++) {
+		if (auth->qop & (1 << i)) {
+			if (qoplist->len > 0)
+				t_string_append_c(qoplist, ',');
+			t_string_append(qoplist, qop_names[i]);
+		}
+	}
+
+	realms = t_string_new(128);
+	for (tmp = auth_realms; *tmp != NULL; tmp++) {
+		if (realms->len > 0)
+			t_string_append_c(realms, ',');
+		t_string_printfa(realms, "realm=\"%s\"", *tmp);
+	}
+
+	return t_strconcat(realms->str,
+			   "nonce=\"", auth->nonce, "\",",
+			   "qop-options=\"", qoplist->str, "\",",
+			   "charset=\"utf-8\",",
+			   "algorithm=\"md5-sess\"",
+			   NULL);
+}
+
+static int verify_auth(AuthData *auth)
+{
+	MD5Context ctx;
+	unsigned char digest[16];
+	const char *a1_hex, *a2_hex, *response_hex;
+	int i;
+
+	/* we should have taken care of this at startup */
+	i_assert(userinfo->lookup_digest_md5 != NULL);
+
+	/* get the MD5 password */
+	if (!userinfo->lookup_digest_md5(auth->username, auth->realm != NULL ?
+					 auth->realm : "", auth->utf8, digest,
+					 &auth->cookie_reply))
+		return FALSE;
+
+	/*
+	   response =
+	     HEX( KD ( HEX(H(A1)),
+		     { nonce-value, ":" nc-value, ":",
+		       cnonce-value, ":", qop-value, ":", HEX(H(A2)) }))
+
+	   and since we don't support authzid yet:
+
+	   A1 = { H( { username-value, ":", realm-value, ":", passwd } ),
+		":", nonce-value, ":", cnonce-value }
+
+	   If the "qop" directive's value is "auth", then A2 is:
+	
+	      A2       = { "AUTHENTICATE:", digest-uri-value }
+	
+	   If the "qop" value is "auth-int" or "auth-conf" then A2 is:
+	
+	      A2       = { "AUTHENTICATE:", digest-uri-value,
+		       ":00000000000000000000000000000000" }
+	*/
+
+	/* A1 */
+	md5_init(&ctx);
+	md5_update(&ctx, digest, 16);
+	md5_update(&ctx, ":", 1);
+	md5_update(&ctx, auth->nonce, strlen(auth->nonce));
+	md5_update(&ctx, ":", 1);
+	md5_update(&ctx, auth->cnonce, strlen(auth->cnonce));
+	md5_final(&ctx, digest);
+	a1_hex = binary_to_hex(digest, 16);
+
+	/* do it twice, first verify the user's response, the second is
+	   sent for client as a reply */
+	for (i = 0; i < 2; i++) {
+		/* A2 */
+		md5_init(&ctx);
+		if (i == 0)
+			md5_update(&ctx, "AUTHENTICATE:", 13);
+		else
+			md5_update(&ctx, ":", 1);
+
+		if (auth->digest_uri != NULL) {
+			md5_update(&ctx, auth->digest_uri,
+				   strlen(auth->digest_uri));
+		}
+		if (auth->qop == QOP_AUTH_INT || auth->qop == QOP_AUTH_CONF)
+			md5_update(&ctx, ":00000000000000000000000000000000", 33);
+		md5_final(&ctx, digest);
+		a2_hex = binary_to_hex(digest, 16);
+
+		/* response */
+		md5_init(&ctx);
+		md5_update(&ctx, a1_hex, 32);
+		md5_update(&ctx, ":", 1);
+		md5_update(&ctx, auth->nonce, strlen(auth->nonce));
+		md5_update(&ctx, ":", 1);
+		md5_update(&ctx, auth->nonce_count, strlen(auth->nonce_count));
+		md5_update(&ctx, ":", 1);
+		md5_update(&ctx, auth->cnonce, strlen(auth->cnonce));
+		md5_update(&ctx, ":", 1);
+		md5_update(&ctx, auth->qop_value, strlen(auth->qop_value));
+		md5_update(&ctx, ":", 1);
+		md5_update(&ctx, a2_hex, 32);
+		md5_final(&ctx, digest);
+		response_hex = binary_to_hex(digest, 16);
+
+		if (i == 0) {
+			/* verify response */
+			if (memcmp(response_hex, auth->response, 32) != 0)
+				return FALSE;
+		} else {
+			auth->rspauth = p_strconcat(auth->pool, "rspauth=",
+						    response_hex, NULL);
+		}
+	}
+
+	return TRUE;
+}
+
+static int verify_realm(const char *realm)
+{
+	char *const *tmp;
+
+	for (tmp = auth_realms; *tmp != NULL; tmp++) {
+		if (strcasecmp(realm, *tmp) == 0)
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+static int parse_next(char **data, char **key, char **value)
+{
+	char *p, *dest;
+
+	p = *data;
+	while (IS_LWS(*p)) p++;
+
+	/* get key */
+	*key = p;
+	while (*p != '\0' && *p != '=' && *p != ',')
+		p++;
+
+	if (*p != '=') {
+		*data = p;
+		return FALSE;
+	}
+
+	*value = p+1;
+
+	/* skip trailing whitespace in key */
+	while (IS_LWS(p[-1]))
+		p--;
+	*p = '\0';
+
+	/* get value */
+	p = *value;
+	while (IS_LWS(*p)) p++;
+
+	if (*p != '"') {
+		while (*p != '\0' && *p != ',')
+			p++;
+
+		*data = p+1;
+		while (IS_LWS(p[-1]))
+			p--;
+		*p = '\0';
+	} else {
+		/* quoted string */
+		*value = dest = ++p;
+		while (*p != '\0' && *p != '"') {
+			if (*p == '\\' && p[1] != '\0')
+				p++;
+			*dest++ = *p++;
+		}
+
+		*data = *p == '"' ? p+1 : p;
+		*dest = '\0';
+	}
+
+	return TRUE;
+}
+
+/* remove leading and trailing whitespace */
+static char *trim(char *str)
+{
+	char *ret;
+
+	while (IS_LWS(*str)) str++;
+	ret = str;
+
+	while (*str != '\0') str++;
+	if (str > ret) {
+		while (IS_LWS(str[-1])) str--;
+		*str = '\0';
+	}
+
+	return ret;
+}
+
+static int auth_handle_response(AuthData *auth, char *key, char *value,
+				const char **error)
+{
+	int i;
+
+	str_lcase(key);
+
+	if (strcmp(key, "realm") == 0) {
+		if (!verify_realm(value)) {
+			*error = "Invalid realm";
+			return FALSE;
+		}
+		if (auth->realm == NULL)
+			auth->realm = p_strdup(auth->pool, value);
+		return TRUE;
+	}
+
+	if (strcmp(key, "username") == 0) {
+		if (auth->username != NULL) {
+			*error = "username must not exist more than once";
+			return FALSE;
+		}
+
+		if (*value == '\0') {
+			*error = "empty username";
+			return FALSE;
+		}
+
+		auth->username = p_strdup(auth->pool, value);
+		return TRUE;
+	}
+
+	if (strcmp(key, "nonce") == 0) {
+		/* nonce must be same */
+		if (strcmp(value, auth->nonce) != 0) {
+			*error = "Invalid nonce";
+			return FALSE;
+		}
+
+		auth->nonce_found = TRUE;
+		return TRUE;
+	}
+
+	if (strcmp(key, "cnonce") == 0) {
+		if (auth->cnonce != NULL) {
+			*error = "cnonce must not exist more than once";
+			return FALSE;
+		}
+
+		if (*value == '\0') {
+			*error = "cnonce can't contain empty value";
+			return FALSE;
+		}
+
+		auth->cnonce = p_strdup(auth->pool, value);
+		return TRUE;
+	}
+
+	if (strcmp(key, "nonce-count") == 0) {
+		if (auth->nonce_count != NULL) {
+			*error = "nonce-count must not exist more than once";
+			return FALSE;
+		}
+
+		if (atoi(value) != 1) {
+			*error = "re-auth not supported currently";
+			return FALSE;
+		}
+
+		auth->nonce_count = p_strdup(auth->pool, value);
+		return TRUE;
+	}
+
+	if (strcmp(key, "qop") == 0) {
+		for (i = 0; i < QOP_COUNT; i++) {
+			if (strcasecmp(qop_names[i], value) == 0)
+				break;
+		}
+
+		if (i == QOP_COUNT) {
+			*error = "Unknown QoP value";
+			return FALSE;
+		}
+
+		auth->qop &= (1 << i);
+		if (auth->qop == 0) {
+			*error = "Nonallowed QoP requested";
+			return FALSE;
+		} 
+
+		auth->qop_value = p_strdup(auth->pool, value);
+		return TRUE;
+	}
+
+	if (strcmp(key, "digest-uri") == 0) {
+		/* type / host / serv-name */
+		char *const *uri = t_strsplit(value, "/");
+
+		if (uri[0] == NULL || uri[1] == NULL) {
+			*error = "Invalid digest-uri";
+			return FALSE;
+		}
+
+		if (strcasecmp(trim(uri[0]), SERVICE_TYPE) != 0) {
+			*error = "Unexpected service type in digest-uri";
+			return FALSE;
+		}
+
+		/* FIXME: RFC recommends that we verify the host/serv-type.
+		   But isn't the realm enough already? That'd be just extra
+		   configuration.. Maybe optionally list valid hosts in
+		   config file? */
+		auth->digest_uri = p_strdup(auth->pool, value);
+		return TRUE;
+	}
+
+	if (strcmp(key, "maxbuf") == 0) {
+		if (auth->maxbuf != 0) {
+			*error = "maxbuf must not exist more than once";
+			return FALSE;
+		}
+
+		auth->maxbuf = strtoul(value, NULL, 10);
+		if (auth->maxbuf == 0) {
+			*error = "Invalid maxbuf value";
+			return FALSE;
+		}
+		return TRUE;
+	}
+
+	if (strcmp(key, "charset") == 0) {
+		if (strcasecmp(value, "utf-8") != 0) {
+			*error = "Only utf-8 charset is allowed";
+			return FALSE;
+		}
+
+		auth->utf8 = TRUE;
+		return TRUE;
+	}
+
+	if (strcmp(key, "response") == 0) {
+		if (strlen(value) != 32) {
+			*error = "Invalid response value";
+			return FALSE;
+		}
+
+		memcpy(auth->response, value, 32);
+		return TRUE;
+	}
+
+	if (strcmp(key, "cipher") == 0) {
+		/* not supported, ignore */
+		return TRUE;
+	}
+
+	if (strcmp(key, "authzid") == 0) {
+		/* not supported, abort */
+		return FALSE;
+	}
+
+	/* unknown key, ignore */
+	return TRUE;
+}
+
+static int parse_digest_response(AuthData *auth, const char *data,
+				 unsigned int size, const char **error)
+{
+	char *copy, *key, *value;
+	int failed;
+
+	/*
+	   realm="realm"
+	   username="username"
+	   nonce="randomized data"
+	   cnonce="??"
+	   nc=00000001
+	   qop="auth|auth-int|auth-conf"
+	   digest-uri="serv-type/host[/serv-name]"
+	   response=32 HEX digits
+	   maxbuf=number (with auth-int, auth-conf, defaults to 64k)
+	   charset="utf-8" (iso-8859-1 if it doesn't exist)
+	   cipher="cipher-value"
+	   authzid="authzid-value"
+	*/
+
+	t_push();
+
+	failed = FALSE;
+
+	copy = (char *) t_strndup(data, size);
+	while (*copy != '\0') {
+		if (parse_next(&copy, &key, &value)) {
+			if (!auth_handle_response(auth, key, value, error)) {
+				failed = TRUE;
+				break;
+			}
+		}
+
+		if (*copy == ',')
+			copy++;
+	}
+
+	if (!auth->nonce_found) {
+		*error = "Missing nonce parameter";
+		failed = TRUE;
+	} else if (auth->cnonce == NULL) {
+		*error = "Missing cnonce parameter";
+		failed = TRUE;
+	} else if (auth->username == NULL) {
+		*error = "Missing username parameter";
+		failed = TRUE;
+	}
+
+	if (auth->nonce_count == NULL)
+		auth->nonce_count = p_strdup(auth->pool, "00000001");
+	if (auth->qop_value == NULL)
+		auth->qop_value = p_strdup(auth->pool, "auth");
+
+	if (!failed && !verify_auth(auth)) {
+		*error = "Authentication failed";
+		failed = TRUE;
+	}
+
+	t_pop();
+
+	/* error message is actually ignored here, we could send it to
+	   syslog or maybe to client, but it's not specified if that's
+	   allowed and how. */
+	return !failed;
+}
+
+static void auth_digest_md5_continue(CookieData *cookie,
+				     AuthContinuedRequestData *request,
+				     const unsigned char *data,
+				     AuthCallback callback, void *user_data)
+{
+	AuthData *auth = cookie->user_data;
+	AuthReplyData reply;
+	const char *error;
+
+	/* initialize reply */
+	memset(&reply, 0, sizeof(reply));
+	reply.id = request->id;
+	memcpy(reply.cookie, cookie->cookie, AUTH_COOKIE_SIZE);
+
+	if (auth->authenticated) {
+		/* authentication is done, we were just waiting the last
+		   word from client */
+		auth->cookie_reply.success = TRUE;
+		reply.result = AUTH_RESULT_SUCCESS;
+		callback(&reply, NULL, user_data);
+		return;
+	}
+
+	if (parse_digest_response(auth, (const char *) data,
+					 request->data_size, &error)) {
+		/* authentication ok */
+		reply.result = AUTH_RESULT_CONTINUE;
+
+		reply.data_size = strlen(auth->rspauth);
+		callback(&reply, (const unsigned char *) auth->rspauth,
+			 user_data);
+		auth->authenticated = TRUE;
+		return;
+	}
+
+	/* failed */
+	reply.result = AUTH_RESULT_FAILURE;
+	callback(&reply, error, user_data);
+	cookie_remove(cookie->cookie);
+}
+
+static int auth_digest_md5_fill_reply(CookieData *cookie,
+				      AuthCookieReplyData *reply)
+{
+	AuthData *auth = cookie->user_data;
+
+	if (!auth->authenticated)
+		return FALSE;
+
+	memcpy(reply, &auth->cookie_reply, sizeof(AuthCookieReplyData));
+	return TRUE;
+}
+
+static void auth_digest_md5_free(CookieData *cookie)
+{
+	pool_unref(((AuthData *) cookie->user_data)->pool);
+}
+
+static void auth_digest_md5_init(AuthInitRequestData *request,
+				 AuthCallback callback, void *user_data)
+{
+	CookieData *cookie;
+	AuthReplyData reply;
+	AuthData *auth;
+	Pool pool;
+	const char *challenge;
+
+	pool = pool_create("Digest-MD5", 256, FALSE);
+	auth = p_new(pool, AuthData, 1);
+	auth->pool = pool;
+
+	auth->qop = QOP_AUTH;
+
+	cookie = p_new(pool, CookieData, 1);
+	cookie->auth_fill_reply = auth_digest_md5_fill_reply;
+	cookie->auth_continue = auth_digest_md5_continue;
+	cookie->free = auth_digest_md5_free;
+	cookie->user_data = auth;
+
+	cookie_add(cookie);
+
+	/* initialize reply */
+	memset(&reply, 0, sizeof(reply));
+	reply.id = request->id;
+	reply.result = AUTH_RESULT_CONTINUE;
+	memcpy(reply.cookie, cookie->cookie, AUTH_COOKIE_SIZE);
+
+	/* send the initial challenge */
+	t_push();
+
+	challenge = get_digest_challenge(auth);
+	reply.data_size = strlen(challenge);
+	callback(&reply, (const unsigned char *) challenge, user_data);
+
+	t_pop();
+}
+
+AuthModule auth_digest_md5 = {
+	AUTH_METHOD_DIGEST_MD5,
+	auth_digest_md5_init
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/auth-interface.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,90 @@
+#ifndef __AUTH_INTERFACE_H
+#define __AUTH_INTERFACE_H
+
+#define AUTH_COOKIE_SIZE		16
+
+#define AUTH_MAX_REQUEST_DATA_SIZE	4096
+#define AUTH_MAX_REPLY_DATA_SIZE	4096
+
+#define AUTH_MAX_USER_LEN		64
+#define AUTH_MAX_HOME_LEN		256
+#define AUTH_MAX_MAIL_LEN		256
+
+typedef enum {
+	AUTH_REQUEST_NONE, /* must not be requested */
+	AUTH_REQUEST_INIT,
+        AUTH_REQUEST_CONTINUE
+} AuthRequestType;
+
+typedef enum {
+	AUTH_RESULT_INTERNAL_FAILURE, /* never sent by imap-auth */
+
+	AUTH_RESULT_CONTINUE,
+	AUTH_RESULT_SUCCESS,
+	AUTH_RESULT_FAILURE
+} AuthResult;
+
+typedef enum {
+	AUTH_METHOD_PLAIN	= 0x01,
+	AUTH_METHOD_DIGEST_MD5	= 0x02,
+
+	AUTH_METHODS_COUNT	= 2
+} AuthMethod;
+
+/* Initialization reply, sent after client is connected */
+typedef struct {
+	int auth_process; /* unique auth process identifier */
+	AuthMethod auth_methods; /* valid authentication methods */
+} AuthInitData;
+
+/* New authentication request */
+typedef struct {
+	AuthRequestType type; /* AUTH_REQUEST_INIT */
+
+	AuthMethod method;
+	int id; /* AuthReplyData.id will contain this value */
+} AuthInitRequestData;
+
+/* Continued authentication request */
+typedef struct {
+	AuthRequestType type; /* AUTH_REQUEST_CONTINUE */
+
+	unsigned char cookie[AUTH_COOKIE_SIZE];
+	int id; /* AuthReplyData.id will contain this value */
+
+	unsigned int data_size;
+	/* unsigned char data[]; */
+} AuthContinuedRequestData;
+
+/* Reply to authentication */
+typedef struct {
+	int id;
+	unsigned char cookie[AUTH_COOKIE_SIZE];
+	AuthResult result;
+
+	unsigned int data_size;
+	/* unsigned char data[]; */
+} AuthReplyData;
+
+/* Request data associated to cookie */
+typedef struct {
+	int id;
+	unsigned char cookie[AUTH_COOKIE_SIZE];
+} AuthCookieRequestData;
+
+/* Reply to cookie request */
+typedef struct {
+	int id;
+	int success; /* FALSE if cookie wasn't found */
+
+	char user[AUTH_MAX_USER_LEN]; /* system user, if available */
+	uid_t uid;
+	gid_t gid;
+
+	char home[AUTH_MAX_HOME_LEN];
+	char mail[AUTH_MAX_MAIL_LEN];
+
+	int chroot; /* chroot to home directory */
+} AuthCookieReplyData;
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/auth-plain.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,98 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "auth.h"
+#include "cookie.h"
+#include "userinfo.h"
+
+static void auth_plain_continue(CookieData *cookie,
+				AuthContinuedRequestData *request,
+				const unsigned char *data,
+				AuthCallback callback, void *user_data)
+{
+	AuthCookieReplyData *cookie_reply = cookie->user_data;
+	AuthReplyData reply;
+	const char *user, *pass;
+	unsigned int i, count;
+
+	/* initialize reply */
+	memset(&reply, 0, sizeof(reply));
+	reply.id = request->id;
+	reply.result = AUTH_RESULT_FAILURE;
+	memcpy(reply.cookie, cookie->cookie, AUTH_COOKIE_SIZE);
+
+	/* data should contain user\0...\0pass */
+	user = (const char *) data;
+	pass = NULL;
+	count = 0;
+	for (i = 0; i < request->data_size; i++) {
+		if (data[i] == '\0') {
+			if (++count == 2) {
+				pass = i+1 == request->data_size ? "" :
+					t_strndup((const char *) data + i + 1,
+						  request->data_size - i - 1);
+				break;
+			}
+		}
+	}
+
+	if (pass != NULL) {
+		if (userinfo->verify_plain(user, pass, cookie_reply)) {
+			cookie_reply->success = TRUE;
+			reply.result = AUTH_RESULT_SUCCESS;
+		}
+	}
+
+        callback(&reply, NULL, user_data);
+
+	if (!cookie_reply->success) {
+		/* failed, we don't need the cookie anymore */
+		cookie_remove(cookie->cookie);
+	}
+}
+
+static int auth_plain_fill_reply(CookieData *cookie, AuthCookieReplyData *reply)
+{
+	AuthCookieReplyData *cookie_reply;
+
+	cookie_reply = cookie->user_data;
+	if (!cookie_reply->success)
+		return FALSE;
+
+	memcpy(reply, cookie_reply, sizeof(AuthCookieReplyData));
+	return TRUE;
+}
+
+static void auth_plain_free(CookieData *cookie)
+{
+	i_free(cookie->user_data);
+	i_free(cookie);
+}
+
+static void auth_plain_init(AuthInitRequestData *request,
+			    AuthCallback callback, void *user_data)
+{
+	CookieData *cookie;
+	AuthReplyData reply;
+
+	cookie = i_new(CookieData, 1);
+	cookie->auth_fill_reply = auth_plain_fill_reply;
+	cookie->auth_continue = auth_plain_continue;
+	cookie->free = auth_plain_free;
+	cookie->user_data = i_new(AuthCookieReplyData, 1);
+
+	cookie_add(cookie);
+
+	/* initialize reply */
+	memset(&reply, 0, sizeof(reply));
+	reply.id = request->id;
+	reply.result = AUTH_RESULT_CONTINUE;
+	memcpy(reply.cookie, cookie->cookie, AUTH_COOKIE_SIZE);
+
+	callback(&reply, NULL, user_data);
+}
+
+AuthModule auth_plain = {
+	AUTH_METHOD_PLAIN,
+	auth_plain_init
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/auth.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,143 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "auth.h"
+#include "cookie.h"
+
+#include <stdlib.h>
+
+typedef struct _AuthModuleList AuthModuleList;
+
+struct _AuthModuleList {
+	AuthModuleList *next;
+
+	AuthModule module;
+};
+
+AuthMethod auth_methods;
+char *const *auth_realms;
+
+static AuthModuleList *auth_modules;
+static AuthReplyData failure_reply;
+
+void auth_register_module(AuthModule *module)
+{
+	AuthModuleList *list;
+
+	i_assert((auth_methods & module->method) == 0);
+
+	auth_methods |= module->method;
+
+	list = i_new(AuthModuleList, 1);
+	memcpy(&list->module, module, sizeof(AuthModule));
+
+	list->next = auth_modules;
+	auth_modules = list;
+}
+
+void auth_unregister_module(AuthModule *module)
+{
+	AuthModuleList **pos, *list;
+
+	if ((auth_methods & module->method) == 0)
+		return; /* not registered */
+
+        auth_methods &= ~module->method;
+
+	for (pos = &auth_modules; *pos != NULL; pos = &(*pos)->next) {
+		if ((*pos)->module.method == module->method) {
+			list = *pos;
+			*pos = (*pos)->next;
+			i_free(list);
+			break;
+		}
+	}
+}
+
+void auth_init_request(AuthInitRequestData *request,
+		       AuthCallback callback, void *user_data)
+{
+	AuthModuleList *list;
+
+	if ((auth_methods & request->method) == 0) {
+		/* unsupported method */
+		i_error("BUG: imap-login requested unsupported "
+			"auth method %d", request->method);
+		failure_reply.id = request->id;
+		callback(&failure_reply, NULL, user_data);
+		return;
+	}
+
+	for (list = auth_modules; list != NULL; list = list->next) {
+		if (list->module.method == request->method) {
+			list->module.init(request, callback, user_data);
+			return;
+		}
+	}
+
+	i_assert(0);
+}
+
+void auth_continue_request(AuthContinuedRequestData *request,
+			   const unsigned char *data,
+			   AuthCallback callback, void *user_data)
+{
+	CookieData *cookie_data;
+
+	cookie_data = cookie_lookup(request->cookie);
+	if (cookie_data == NULL) {
+		/* timeouted cookie */
+		failure_reply.id = request->id;
+		callback(&failure_reply, NULL, user_data);
+	} else {
+		cookie_data->auth_continue(cookie_data, request, data,
+					   callback, user_data);
+	}
+}
+
+extern AuthModule auth_plain;
+extern AuthModule auth_digest_md5;
+
+void auth_init(void)
+{
+	char *const *methods;
+	const char *env;
+
+        auth_modules = NULL;
+	auth_methods = 0;
+
+	memset(&failure_reply, 0, sizeof(failure_reply));
+	failure_reply.result = AUTH_RESULT_FAILURE;
+
+	/* register wanted methods */
+	env = getenv("METHODS");
+	if (env == NULL || *env == '\0')
+		i_fatal("METHODS environment is unset");
+
+	methods = t_strsplit(env, " ");
+	while (*methods != NULL) {
+		if (strcasecmp(*methods, "plain") == 0)
+			auth_register_module(&auth_plain);
+		else if (strcasecmp(*methods, "digest-md5") == 0)
+			auth_register_module(&auth_digest_md5);
+		else {
+			i_fatal("Unknown authentication method '%s'",
+				*methods);
+		}
+		methods++;
+	}
+
+	/* get our realm - note that we allocate from temp. memory pool so
+	   this function should never be called inside I/O loop or anywhere
+	   else where t_pop() is called */
+	env = getenv("REALMS");
+	if (env == NULL)
+		env = "";
+	auth_realms = t_strsplit(env, " ");
+}
+
+void auth_deinit(void)
+{
+	auth_unregister_module(&auth_plain);
+	auth_unregister_module(&auth_digest_md5);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/auth.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,31 @@
+#ifndef __AUTH_H
+#define __AUTH_H
+
+#include "auth-interface.h"
+
+typedef void (*AuthCallback) (AuthReplyData *reply, const unsigned char *data,
+			      void *user_data);
+
+typedef struct {
+	AuthMethod method;
+
+	void (*init)(AuthInitRequestData *request,
+		     AuthCallback callback, void *user_data);
+} AuthModule;
+
+extern AuthMethod auth_methods;
+extern char *const *auth_realms;
+
+void auth_register_module(AuthModule *module);
+void auth_unregister_module(AuthModule *module);
+
+void auth_init_request(AuthInitRequestData *request,
+		       AuthCallback callback, void *user_data);
+void auth_continue_request(AuthContinuedRequestData *request,
+			   const unsigned char *data,
+			   AuthCallback callback, void *user_data);
+
+void auth_init(void);
+void auth_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/common.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,12 @@
+#ifndef __COMMON_H
+#define __COMMON_H
+
+#include "lib.h"
+#include "auth.h"
+
+#define MASTER_SOCKET_FD 0
+#define LOGIN_LISTEN_FD 1
+
+extern IOLoop ioloop;
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/cookie.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,153 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "ioloop.h"
+#include "hash.h"
+#include "cookie.h"
+#include "randgen.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+/* 30 seconds should be more than enough */
+#define COOKIE_TIMEOUT 30
+
+typedef struct _CookieList CookieList;
+
+struct _CookieList {
+	CookieList *next;
+	time_t created;
+
+	CookieData *data;
+};
+
+static HashTable *cookies;
+static CookieList *oldest_cookie, **next_cookie;
+
+static Timeout to;
+
+/* a char* hash function from ASU -- from glib */
+static unsigned int cookie_hash(const void *p)
+{
+        const unsigned char *s = p;
+	unsigned int i, g, h = 0;
+
+	for (i = 0; i < AUTH_COOKIE_SIZE; i++) {
+		h = (h << 4) + s[i];
+		if ((g = h & 0xf0000000UL)) {
+			h = h ^ (g >> 24);
+			h = h ^ g;
+		}
+	}
+
+	return h;
+}
+
+static int cookie_cmp(const void *p1, const void *p2)
+{
+	int i, ret;
+
+	for (i = 0; i < AUTH_COOKIE_SIZE; i++) {
+		ret = ((unsigned char *) p1)[i] - ((unsigned char *) p2)[i];
+		if (ret != 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+void cookie_add(CookieData *data)
+{
+	CookieList *list;
+
+	do {
+		random_fill(data->cookie, AUTH_COOKIE_SIZE);
+	} while (hash_lookup(cookies, data->cookie));
+
+	/* add to linked list */
+	list = i_new(CookieList, 1);
+	list->created = ioloop_time;
+	list->data = data;
+
+	*next_cookie = list;
+	next_cookie = &list->next;
+
+	/* add to hash */
+	hash_insert(cookies, data->cookie, data);
+}
+
+static void cookie_destroy(unsigned char cookie[AUTH_COOKIE_SIZE],
+			   int free_data)
+{
+	CookieList **pos, *list;
+
+	hash_remove(cookies, cookie);
+
+	list = NULL;
+	for (pos = &oldest_cookie; *pos != NULL; pos = &(*pos)->next) {
+		if (cookie_cmp((*pos)->data->cookie, cookie) == 0) {
+			list = *pos;
+			*pos = list->next;
+			break;
+		}
+	}
+	i_assert(list != NULL);
+
+	if (list->next == NULL)
+		next_cookie = pos;
+
+	if (free_data)
+		list->data->free(list->data);
+	i_free(list);
+}
+
+CookieData *cookie_lookup(unsigned char cookie[AUTH_COOKIE_SIZE])
+{
+	return hash_lookup(cookies, cookie);
+}
+
+void cookie_remove(unsigned char cookie[AUTH_COOKIE_SIZE])
+{
+	cookie_destroy(cookie, TRUE);
+}
+
+CookieData *cookie_lookup_and_remove(unsigned char cookie[AUTH_COOKIE_SIZE])
+{
+	CookieData *data;
+
+	data = hash_lookup(cookies, cookie);
+	if (data != NULL)
+		cookie_destroy(cookie, FALSE);
+	return data;
+}
+
+static void cookie_timeout(void *user_data __attr_unused__,
+			   Timeout timeout __attr_unused__)
+{
+	time_t remove_time;
+
+        remove_time = ioloop_time - COOKIE_TIMEOUT;
+	while (oldest_cookie != NULL && oldest_cookie->created < remove_time)
+		cookie_destroy(oldest_cookie->data->cookie, TRUE);
+}
+
+void cookies_init(void)
+{
+	random_init();
+
+	oldest_cookie = NULL;
+	next_cookie = &oldest_cookie;
+
+	cookies = hash_create(default_pool, 100, cookie_hash, cookie_cmp);
+	to = timeout_add(10000, cookie_timeout, NULL);
+}
+
+void cookies_deinit(void)
+{
+	while (oldest_cookie != NULL)
+		cookie_destroy(oldest_cookie->data->cookie, TRUE);
+	hash_destroy(cookies);
+
+	timeout_remove(to);
+	random_deinit();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/cookie.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,40 @@
+#ifndef __COOKIE_H
+#define __COOKIE_H
+
+#include "auth-interface.h"
+
+typedef struct _CookieData CookieData;
+
+struct _CookieData {
+	unsigned char cookie[AUTH_COOKIE_SIZE];
+
+	/* continue authentication */
+	void (*auth_continue)(CookieData *cookie,
+			      AuthContinuedRequestData *request,
+			      const unsigned char *data,
+			      AuthCallback callback, void *user_data);
+
+	/* fills reply from cookie, returns TRUE if successful */
+	int (*auth_fill_reply)(CookieData *cookie, AuthCookieReplyData *reply);
+
+	/* Free all data related to cookie */
+	void (*free)(CookieData *cookie);
+
+	void *user_data;
+};
+
+typedef void (*CookieFreeFunc)(void *data);
+
+/* data->cookie is filled */
+void cookie_add(CookieData *data);
+/* Looks up the cookie */
+CookieData *cookie_lookup(unsigned char cookie[AUTH_COOKIE_SIZE]);
+/* Removes and frees the cookie */
+void cookie_remove(unsigned char cookie[AUTH_COOKIE_SIZE]);
+/* Looks up the cookie and removes it, you have to free the returned data. */
+CookieData *cookie_lookup_and_remove(unsigned char cookie[AUTH_COOKIE_SIZE]);
+
+void cookies_init(void);
+void cookies_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/login-connection.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,181 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "network.h"
+#include "iobuffer.h"
+#include "login-connection.h"
+
+#include <stdlib.h>
+#include <syslog.h>
+
+#define MAX_INBUF_SIZE \
+	(sizeof(AuthContinuedRequestData) + AUTH_MAX_REQUEST_DATA_SIZE)
+#define MAX_OUTBUF_SIZE \
+	(10 * (sizeof(AuthReplyData) + AUTH_MAX_REPLY_DATA_SIZE))
+
+struct _LoginConnection {
+	LoginConnection *next;
+
+	int fd;
+	IO io;
+	IOBuffer *inbuf, *outbuf;
+        AuthRequestType type;
+};
+
+static AuthInitData auth_init_data;
+static LoginConnection *connections;
+
+static void request_callback(AuthReplyData *reply, const unsigned char *data,
+			     void *user_data)
+{
+	LoginConnection *conn = user_data;
+
+	i_assert(reply->data_size <= AUTH_MAX_REPLY_DATA_SIZE);
+
+	if (io_buffer_send(conn->outbuf, reply, sizeof(AuthReplyData)) < 0)
+		login_connection_destroy(conn);
+	else if (reply->data_size > 0) {
+		if (io_buffer_send(conn->outbuf, data, reply->data_size) < 0)
+			login_connection_destroy(conn);
+	}
+}
+
+static void login_input(void *user_data, int fd __attr_unused__,
+			IO io __attr_unused__)
+{
+	LoginConnection *conn  = user_data;
+        unsigned char *data;
+	unsigned int size;
+
+	switch (io_buffer_read(conn->inbuf)) {
+	case 0:
+		return;
+	case -1:
+		/* disconnected */
+		login_connection_destroy(conn);
+		return;
+	case -2:
+		/* buffer full */
+		i_error("BUG: imap-login sent us more than %d bytes of data",
+			(int)MAX_INBUF_SIZE);
+		login_connection_destroy(conn);
+		return;
+	}
+
+	data = io_buffer_get_data(conn->inbuf, &size);
+	if (size < sizeof(AuthRequestType))
+		return;
+
+	/* note that we can't directly cast the received data pointer into
+	   structures, as it may not be aligned properly. */
+	if (conn->type == AUTH_REQUEST_NONE) {
+		/* get the request type */
+		memcpy(&conn->type, data, sizeof(AuthRequestType));
+	}
+
+	if (conn->type == AUTH_REQUEST_INIT) {
+		AuthInitRequestData request;
+
+		if (size < sizeof(request))
+			return;
+
+		memcpy(&request, data, sizeof(request));
+		conn->inbuf->skip += sizeof(request);
+
+		/* we have a full init request */
+		auth_init_request(&request, request_callback, conn);
+		conn->type = AUTH_REQUEST_NONE;
+	} else if (conn->type == AUTH_REQUEST_CONTINUE) {
+                AuthContinuedRequestData request;
+
+		if (size < sizeof(request))
+			return;
+
+		memcpy(&request, data, sizeof(request));
+		if (size < sizeof(request) + request.data_size)
+			return;
+
+		conn->inbuf->skip += sizeof(request) + request.data_size;
+
+		/* we have a full continued request */
+		auth_continue_request(&request, data + sizeof(request),
+				      request_callback, conn);
+		conn->type = AUTH_REQUEST_NONE;
+	} else {
+		/* unknown request */
+		i_error("BUG: imap-login sent us unknown request %u",
+			conn->type);
+		login_connection_destroy(conn);
+	}
+}
+
+LoginConnection *login_connection_create(int fd)
+{
+	LoginConnection *conn;
+
+	conn = i_new(LoginConnection, 1);
+
+	conn->fd = fd;
+	conn->inbuf = io_buffer_create(fd, default_pool,
+				       IO_PRIORITY_DEFAULT, MAX_INBUF_SIZE);
+	conn->outbuf = io_buffer_create(fd, default_pool,
+					IO_PRIORITY_DEFAULT, MAX_OUTBUF_SIZE);
+	conn->io = io_add(fd, IO_READ, login_input, conn);
+	conn->type = AUTH_REQUEST_NONE;
+
+	conn->next = connections;
+	connections = conn;
+
+	if (io_buffer_send(conn->outbuf, &auth_init_data,
+			   sizeof(auth_init_data)) < 0) {
+		login_connection_destroy(conn);
+		conn = NULL;
+	}
+
+	return conn;
+}
+
+void login_connection_destroy(LoginConnection *conn)
+{
+	LoginConnection **pos;
+
+	for (pos = &connections; *pos != NULL; pos = &(*pos)->next) {
+		if (*pos == conn) {
+			*pos = conn->next;
+			break;
+		}
+	}
+
+	io_buffer_close(conn->inbuf);
+	io_buffer_close(conn->outbuf);
+
+	io_remove(conn->io);
+	net_disconnect(conn->fd);
+	i_free(conn);
+}
+
+void login_connections_init(void)
+{
+	const char *env;
+
+	env = getenv("AUTH_PROCESS");
+	if (env == NULL)
+		i_fatal("AUTH_PROCESS environment is unset");
+
+	memset(&auth_init_data, 0, sizeof(auth_init_data));
+	auth_init_data.auth_process = atoi(env);
+	auth_init_data.auth_methods = auth_methods;
+
+	connections = NULL;
+}
+
+void login_connections_deinit(void)
+{
+	LoginConnection *next;
+
+	while (connections != NULL) {
+		next = connections->next;
+		login_connection_destroy(connections);
+		connections = next;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/login-connection.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,12 @@
+#ifndef __LOGIN_CONNECTION_H
+#define __LOGIN_CONNECTION_H
+
+typedef struct _LoginConnection LoginConnection;
+
+LoginConnection *login_connection_create(int fd);
+void login_connection_destroy(LoginConnection *conn);
+
+void login_connections_init(void);
+void login_connections_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/main.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,101 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "ioloop.h"
+#include "network.h"
+#include "lib-signals.h"
+#include "restrict-access.h"
+#include "auth.h"
+#include "cookie.h"
+#include "login-connection.h"
+#include "userinfo.h"
+
+#include <stdlib.h>
+#include <syslog.h>
+
+void master_init(void);
+void master_deinit(void);
+
+IOLoop ioloop;
+static IO io_listen;
+
+static void sig_quit(int signo __attr_unused__)
+{
+	io_loop_stop(ioloop);
+}
+
+static void auth_accept(void *user_data __attr_unused__, int listen_fd,
+			IO io __attr_unused__)
+{
+	int fd;
+
+	fd = net_accept(listen_fd, NULL, NULL);
+	if (fd != -1)
+		(void)login_connection_create(fd);
+}
+
+static void main_init(void)
+{
+	const char *logfile;
+
+	lib_init_signals(sig_quit);
+
+	logfile = getenv("IMAP_LOGFILE");
+	if (logfile == NULL) {
+		/* open the syslog immediately so chroot() won't
+		   break logging */
+		openlog("imap-auth", LOG_NDELAY, LOG_MAIL);
+
+		i_set_panic_handler(i_syslog_panic_handler);
+		i_set_fatal_handler(i_syslog_fatal_handler);
+		i_set_error_handler(i_syslog_error_handler);
+		i_set_warning_handler(i_syslog_warning_handler);
+	} else {
+		/* log failures into specified log file */
+		i_set_failure_file(logfile, "imap-auth");
+		i_set_failure_timestamp_format(DEFAULT_FAILURE_STAMP_FORMAT);
+	}
+
+	restrict_access_by_env();
+
+	auth_init();
+	cookies_init();
+	login_connections_init();
+	master_init();
+	userinfo_init();
+
+	io_listen = io_add(LOGIN_LISTEN_FD, IO_READ, auth_accept, NULL);
+}
+
+static void main_deinit(void)
+{
+        if (lib_signal_kill != 0)
+		i_warning("Killed with signal %d", lib_signal_kill);
+
+	io_remove(io_listen);
+
+	userinfo_deinit();
+	master_deinit();
+	login_connections_deinit();
+	cookies_deinit();
+	auth_deinit();
+
+	closelog();
+}
+
+int main(int argc __attr_unused__, char *argv[] __attr_unused__)
+{
+	/* NOTE: we start rooted, so keep the code minimal until
+	   restrict_access_by_env() is called */
+	lib_init();
+	ioloop = io_loop_create();
+
+	main_init();
+        io_loop_run(ioloop);
+	main_deinit();
+
+	io_loop_destroy(ioloop);
+	lib_deinit();
+
+        return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/master.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,82 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "iobuffer.h"
+#include "network.h"
+#include "cookie.h"
+
+#define MAX_OUTBUF_SIZE (10 * sizeof(AuthCookieReplyData))
+
+static AuthCookieReplyData failure_reply;
+
+static IOBuffer *outbuf;
+static IO io_master;
+
+static unsigned int master_pos;
+static char master_buf[sizeof(AuthCookieRequestData)];
+
+static void master_handle_request(AuthCookieRequestData *request,
+				  int fd __attr_unused__)
+{
+	CookieData *cookie;
+        AuthCookieReplyData *reply, temp_reply;
+
+	cookie = cookie_lookup_and_remove(request->cookie);
+	if (cookie == NULL)
+		reply = &failure_reply;
+	else {
+		if (cookie->auth_fill_reply(cookie, &temp_reply))
+			reply = &temp_reply;
+		else
+			reply = &failure_reply;
+		cookie->free(cookie);
+	}
+
+	reply->id = request->id;
+	switch (io_buffer_send(outbuf, reply, sizeof(AuthCookieReplyData))) {
+	case -2:
+		i_fatal("Master transmit buffer full, aborting");
+	case -1:
+		/* master died, kill ourself too */
+		io_loop_stop(ioloop);
+		break;
+	}
+}
+
+static void master_input(void *user_data __attr_unused__, int fd,
+			 IO io __attr_unused__)
+{
+	int ret;
+
+	ret = net_receive(fd, master_buf + master_pos,
+			  sizeof(master_buf) - master_pos);
+	if (ret < 0) {
+		/* master died, kill ourself too */
+		io_loop_stop(ioloop);
+		return;
+	}
+
+	master_pos += ret;
+	if (master_pos < sizeof(master_buf))
+		return;
+
+	/* reply is now read */
+	master_handle_request((AuthCookieRequestData *) master_buf, fd);
+	master_pos = 0;
+}
+
+void master_init(void)
+{
+	memset(&failure_reply, 0, sizeof(failure_reply));
+
+	master_pos = 0;
+	outbuf = io_buffer_create(MASTER_SOCKET_FD, default_pool,
+				  IO_PRIORITY_DEFAULT, MAX_OUTBUF_SIZE);
+	io_master = io_add(MASTER_SOCKET_FD, IO_READ, master_input, NULL);
+}
+
+void master_deinit(void)
+{
+	io_buffer_destroy(outbuf);
+	io_remove(io_master);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/userinfo-pam.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,217 @@
+/*
+   Based on auth_pam.c from popa3d by Solar Designer <solar@openwall.com>.
+
+   You're allowed to do whatever you like with this software (including
+   re-distribution in source and/or binary form, with or without
+   modification), provided that credit is given where it is due and any
+   modified versions are marked as such.  There's absolutely no warranty.
+*/
+
+#define _XOPEN_SOURCE 4
+#define _XOPEN_SOURCE_EXTENDED
+#define _XOPEN_VERSION 4
+#define _XPG4_2
+
+#include "common.h"
+
+#ifdef USERINFO_PAM
+
+#include "userinfo.h"
+#include "userinfo-passwd.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <shadow.h>
+
+#include <security/pam_appl.h>
+
+#if defined(__sun__) && !defined(LINUX_PAM)
+#  define linux_const			/* Sun's PAM doesn't use const here */
+#else
+#  define linux_const			const
+#endif
+typedef linux_const void *pam_item_t;
+
+#ifdef AUTH_PAM_USERPASS
+#  include <security/pam_client.h>
+
+#  ifndef PAM_BP_RCONTROL
+/* Linux-PAM prior to 0.74 */
+#    define PAM_BP_RCONTROL	PAM_BP_CONTROL
+#    define PAM_BP_WDATA	PAM_BP_DATA
+#    define PAM_BP_RDATA	PAM_BP_DATA
+#  endif
+
+#  define USERPASS_AGENT_ID		"userpass"
+#  define USERPASS_AGENT_ID_LENGTH	8
+
+#  define USERPASS_USER_MASK		0x03
+#  define USERPASS_USER_REQUIRED	1
+#  define USERPASS_USER_KNOWN		2
+#  define USERPASS_USER_FIXED		3
+#endif
+
+typedef struct {
+	const char *user;
+	const char *pass;
+} pam_userpass_t;
+
+static pam_handle_t *pamh;
+static pam_userpass_t userpass;
+
+static int pam_userpass_conv(int num_msg, linux_const struct pam_message **msg,
+	struct pam_response **resp, void *appdata_ptr)
+{
+	pam_userpass_t *userpass = (pam_userpass_t *)appdata_ptr;
+#ifdef AUTH_PAM_USERPASS
+	pamc_bp_t prompt;
+	const char *input;
+	char *output;
+	char flags;
+
+	if (num_msg != 1 || msg[0]->msg_style != PAM_BINARY_PROMPT)
+		return PAM_CONV_ERR;
+
+	prompt = (pamc_bp_t)msg[0]->msg;
+	input = PAM_BP_RDATA(prompt);
+
+	if (PAM_BP_RCONTROL(prompt) != PAM_BPC_SELECT ||
+	    strncmp(input, USERPASS_AGENT_ID "/", USERPASS_AGENT_ID_LENGTH + 1))
+		return PAM_CONV_ERR;
+
+	flags = input[USERPASS_AGENT_ID_LENGTH + 1];
+	input += USERPASS_AGENT_ID_LENGTH + 1 + 1;
+
+	if ((flags & USERPASS_USER_MASK) == USERPASS_USER_FIXED &&
+	    strcmp(input, userpass->user))
+		return PAM_CONV_AGAIN;
+
+	if (!(*resp = malloc(sizeof(struct pam_response))))
+		return PAM_CONV_ERR;
+
+	prompt = NULL;
+	PAM_BP_RENEW(&prompt, PAM_BPC_DONE,
+		strlen(userpass->user) + 1 + strlen(userpass->pass));
+	output = PAM_BP_WDATA(prompt);
+
+	strcpy(output, userpass->user);
+	output += strlen(output) + 1;
+	memcpy(output, userpass->pass, strlen(userpass->pass));
+
+	(*resp)[0].resp_retcode = 0;
+	(*resp)[0].resp = (char *)prompt;
+#else
+	char *string;
+	int i;
+
+	if (!(*resp = malloc(num_msg * sizeof(struct pam_response))))
+		return PAM_CONV_ERR;
+
+	for (i = 0; i < num_msg; i++) {
+		switch (msg[i]->msg_style) {
+		case PAM_PROMPT_ECHO_ON:
+			string = strdup(userpass->user);
+			if (string == NULL)
+				i_fatal("Out of memory");
+			break;
+		case PAM_PROMPT_ECHO_OFF:
+			string = strdup(userpass->pass);
+			if (string == NULL)
+				i_fatal("Out of memory");
+			break;
+		case PAM_ERROR_MSG:
+		case PAM_TEXT_INFO:
+			string = NULL;
+			break;
+		default:
+			while (--i >= 0) {
+				if ((*resp)[i].resp == NULL)
+					continue;
+				memset((*resp)[i].resp, 0,
+				       strlen((*resp)[i].resp));
+				free((*resp)[i].resp);
+				(*resp)[i].resp = NULL;
+			}
+
+			free(*resp);
+			*resp = NULL;
+
+			return PAM_CONV_ERR;
+		}
+
+		(*resp)[i].resp_retcode = PAM_SUCCESS;
+		(*resp)[i].resp = string;
+	}
+#endif
+
+	return PAM_SUCCESS;
+}
+
+static int pam_verify_plain(const char *user, const char *password,
+			    AuthCookieReplyData *reply)
+{
+	struct passwd *pw;
+	char *item;
+	int status;
+
+	userpass.user = user;
+	userpass.pass = password;
+
+	if ((status = pam_authenticate(pamh, 0)) != PAM_SUCCESS) {
+		if (status == PAM_ABORT)
+			i_fatal("pam_authenticate() requested abort");
+		return FALSE;
+	}
+
+	if ((status = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS) {
+		if (status == PAM_ABORT)
+			i_fatal("pam_acct_mgmt() requested abort");
+		return FALSE;
+	}
+
+	status = pam_get_item(pamh, PAM_USER, (pam_item_t *)&item);
+	if (status != PAM_SUCCESS) {
+		if (status == PAM_ABORT)
+			i_fatal("pam_get_item() requested abort");
+		return FALSE;
+	}
+
+	/* password ok, save the user info */
+	pw = getpwnam(user);
+	if (pw == NULL)
+		return FALSE;
+
+	memset(pw->pw_passwd, 0, strlen(pw->pw_passwd));
+	passwd_fill_cookie_reply(pw, reply);
+	return TRUE;
+}
+
+static void pam_init(const char *args)
+{
+	static struct pam_conv conv = {
+		pam_userpass_conv,
+		&userpass
+	};
+	const char *service_name;
+	int status;
+
+	service_name = *args != '\0' ? args : "imap";
+	status = pam_start(service_name, NULL, &conv, &pamh);
+	if (status != PAM_SUCCESS)
+		i_fatal("pam_start() failed: %s", pam_strerror(pamh, status));
+}
+
+static void pam_deinit(void)
+{
+	(void)pam_end(pamh, PAM_SUCCESS);
+}
+
+UserInfoModule userinfo_pam = {
+	pam_init,
+	pam_deinit,
+
+	pam_verify_plain,
+	NULL
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/userinfo-passwd-file.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,374 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#define _XOPEN_SOURCE 4
+#define _XOPEN_SOURCE_EXTENDED
+#define _XOPEN_VERSION 4
+#define _XPG4_2
+
+#include "common.h"
+
+#ifdef USERINFO_PASSWD_FILE
+
+#include "iobuffer.h"
+#include "hash.h"
+#include "hex-binary.h"
+#include "md5.h"
+#include "userinfo.h"
+#include "userinfo-passwd.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+typedef struct {
+	Pool pool;
+
+	char *path;
+	time_t stamp;
+	int fd;
+
+	HashTable *users;
+} PasswdFile;
+
+typedef enum {
+	PASSWORD_DES,
+	PASSWORD_MD5,
+	PASSWORD_DIGEST_MD5
+} PasswordType;
+
+typedef struct {
+	char *user_realm; /* user:realm */
+	const char *realm; /* NULL or points to user_realm */
+	char *password;
+	char *home;
+	char *mail;
+
+	uid_t uid;
+	gid_t gid;
+
+	PasswordType password_type;
+	unsigned int chroot:1;
+} PasswdUser;
+
+static PasswdFile *passwd_file;
+
+static int get_reply_data(PasswdUser *pu, AuthCookieReplyData *reply)
+{
+	const char *user;
+	struct passwd *pw;
+
+	if (pu->uid == 0 || pu->gid == 0 ||
+	    (pu->home == NULL && pu->mail == NULL)) {
+		/* all required information was not set in passwd file,
+		   check from system's passwd */
+		user = pu->realm == NULL ? pu->user_realm :
+			t_strndup(pu->user_realm, strlen(pu->user_realm) -
+				  strlen(pu->realm) - 1);
+
+		pw = getpwnam(user);
+		if (pw == NULL)
+			return FALSE;
+
+		passwd_fill_cookie_reply(pw, reply);
+	}
+
+	if (pu->uid != 0)
+		reply->uid = pu->uid;
+	if (pu->gid != 0)
+		reply->gid = pu->gid;
+
+	if (pu->home != NULL) {
+		i_assert(sizeof(reply->home) > strlen(pu->home));
+		strcpy(reply->home, pu->home);
+	}
+
+	if (pu->mail != NULL) {
+		i_assert(sizeof(reply->mail) > strlen(pu->mail));
+		strcpy(reply->mail, pu->mail);
+	}
+
+	reply->chroot = pu->chroot;
+	return TRUE;
+}
+
+static int passwd_file_verify_plain(const char *user, const char *password,
+				    AuthCookieReplyData *reply)
+{
+	PasswdUser *pu;
+	char *const *tmp;
+	unsigned char digest[16];
+	const char *str;
+
+	/* find it from all realms */
+	pu = hash_lookup(passwd_file->users, user);
+	if (pu == NULL) {
+		t_push();
+		for (tmp = auth_realms; *tmp != NULL; tmp++) {
+                        str = t_strconcat(user, ":", *tmp, NULL);
+			pu = hash_lookup(passwd_file->users, str);
+		}
+		t_pop();
+	}
+
+	if (pu == NULL)
+		return FALSE;
+
+	/* verify that password matches */
+	switch (pu->password_type) {
+	case PASSWORD_DES:
+		if (strcmp(crypt(password, pu->password), pu->password) != 0)
+			return FALSE;
+		break;
+	case PASSWORD_MD5:
+		md5_get_digest(password, strlen(password), digest);
+		str = binary_to_hex(digest, sizeof(digest));
+
+		if (strcmp(str, pu->password) != 0)
+			return FALSE;
+		break;
+	case PASSWORD_DIGEST_MD5:
+		/* user:realm:passwd */
+		str = t_strconcat(pu->user_realm,
+				  pu->realm == NULL ? ":" : "",  ":",
+				  password, NULL);
+
+		md5_get_digest(str, strlen(str), digest);
+		str = binary_to_hex(digest, sizeof(digest));
+
+		if (strcmp(str, pu->password) != 0)
+			return FALSE;
+		break;
+	default:
+		i_assert(0);
+	}
+
+	/* found */
+	return get_reply_data(pu, reply);
+}
+
+static int passwd_file_lookup_digest_md5(const char *user, const char *realm,
+					 int utf8, unsigned char digest[16],
+					 AuthCookieReplyData *reply)
+{
+	const char *id;
+	PasswdUser *pu;
+
+	/* FIXME: we simply ignore UTF8 setting.. */
+
+	id = realm == NULL || *realm == '\0' ? user :
+		t_strconcat(user, ":", realm, NULL);
+
+	pu = hash_lookup(passwd_file->users, id);
+	if (pu == NULL)
+		return FALSE;
+
+	/* found */
+	i_assert(strlen(pu->password) == 32);
+	if (!hex_to_binary(pu->password, digest))
+		return FALSE;
+	
+	return get_reply_data(pu, reply);
+}
+
+static void passwd_file_add(PasswdFile *pw, const char *username,
+			    const char *pass, char *const *args)
+{
+	/* args = uid, gid, user info, home dir, shell, realm, mail, chroot */
+	PasswdUser *pu;
+	const char *p;
+
+	if (strlen(username) >= AUTH_MAX_USER_LEN) {
+		i_error("Username %s is too long (max. %d chars) in password "
+			"file %s", username, AUTH_MAX_USER_LEN, pw->path);
+		return;
+	}
+
+	pu = p_new(pw->pool, PasswdUser, 1);
+
+	p = strchr(pass, '[');
+	if (p == NULL) {
+		pu->password = p_strdup(pw->pool, pass);
+		pu->password_type = PASSWORD_DES;
+	} else {
+		/* password[type] - we're being libpam-pwdfile compatible
+		   here. it uses 13 = DES and 34 = MD5. We add
+		   56 = Digest-MD5. */
+		pu->password = p_strndup(pw->pool, pass,
+					 (unsigned int) (p-pass));
+		if (p[1] == '3' && p[2] == '4') {
+			pu->password_type = PASSWORD_MD5;
+			str_lcase(pu->password);
+		} else if (p[1] == '5' && p[2] == '6') {
+			pu->password_type = PASSWORD_DIGEST_MD5;
+			if (strlen(pu->password) != 32) {
+				i_error("User %s has invalid password in "
+					"file %s", username, pw->path);
+				return;
+			}
+			str_lcase(pu->password);
+		} else {
+			pu->password_type = PASSWORD_DES;
+		}
+	}
+
+	if (args[0] != NULL) {
+		pu->uid = atoi(args[0]);
+		if (pu->uid == 0) {
+			i_error("User %s has UID 0 in password file %s",
+				username, pw->path);
+			return;
+		}
+		args++;
+	}
+
+	if (args[0] != NULL) {
+		pu->gid = atoi(args[0]);
+		if (pu->gid == 0) {
+			i_error("User %s has GID 0 in password file %s",
+				username, pw->path);
+			return;
+		}
+		args++;
+	}
+
+	/* user info */
+	if (args[0] != NULL)
+		args++;
+
+	/* home */
+	if (args[0] != NULL) {
+		if (strlen(args[0]) >= AUTH_MAX_HOME_LEN) {
+			i_error("User %s has too long home directory in "
+				"password file %s", username, pw->path);
+			return;
+		}
+
+		pu->home = p_strdup(pw->pool, args[0]);
+		args++;
+	}
+
+	/* shell */
+	if (args[0] != NULL)
+		args++;
+
+	/* realm */
+	if (args[0] == NULL || *args[0] == '\0') {
+		pu->user_realm = p_strdup(pw->pool, username);
+		if (hash_lookup(pw->users, username) != NULL) {
+			i_error("User %s already exists in password file %s",
+				username, pw->path);
+			return;
+		}
+	} else {
+		pu->user_realm = p_strconcat(pw->pool, username, ":",
+					     args[0], NULL);
+		pu->realm = pu->user_realm + strlen(username)+1;
+
+		if (hash_lookup(pw->users, pu->user_realm) != NULL) {
+			i_error("User %s already exists in realm %s in "
+				"password file %s", username, args[0],
+				pw->path);
+			return;
+		}
+	}
+
+	/* mail storage */
+	if (args[0] != NULL) {
+		if (strlen(args[0]) >= AUTH_MAX_MAIL_LEN) {
+			i_error("User %s has too long mail storage in "
+				"password file %s", username, pw->path);
+			return;
+		}
+
+		pu->mail = p_strdup(pw->pool, args[0]);
+		args++;
+	}
+
+	/* chroot */
+	if (args[0] != NULL && strstr(args[0], "chroot") != NULL)
+		pu->chroot = TRUE;
+
+	hash_insert(pw->users, pu->user_realm, pu);
+}
+
+static void passwd_file_parse_file(PasswdFile *pw)
+{
+	IOBuffer *inbuf;
+	char *const *args;
+	char *line;
+
+	inbuf = io_buffer_create_file(pw->fd, default_pool, 2048);
+	for (;;) {
+		line = io_buffer_next_line(inbuf);
+		if (line == NULL) {
+			if (io_buffer_read(inbuf) <= 0)
+				break;
+                        continue;
+		}
+
+		if (*line == '\0' || *line == ':')
+			continue; /* no username */
+
+		t_push();
+		args = t_strsplit(line, ":");
+		if (args[1] != NULL && IS_VALID_PASSWD(args[1])) {
+			/* valid user/pass */
+			passwd_file_add(pw, args[0], args[1], args+2);
+		}
+		t_pop();
+	}
+	io_buffer_destroy(inbuf);
+}
+
+static PasswdFile *passwd_file_parse(const char *path)
+{
+	PasswdFile *pw;
+	Pool pool;
+	struct stat st;
+	int fd;
+
+	fd = open(path, O_RDONLY);
+	if (fd == -1) {
+		i_fatal("Can't open passwd-file %s: %m", path);
+	}
+
+	if (fstat(fd, &st) != 0)
+		i_fatal("fstat() failed for passwd-file %s: %m", path);
+
+	pool = pool_create("PasswdFile", 10240, FALSE);
+	pw = p_new(pool, PasswdFile, 1);
+	pw->pool = pool;
+	pw->path = p_strdup(pool, path);
+	pw->stamp = st.st_mtime;
+	pw->fd = fd;
+	pw->users = hash_create(pool, 100, str_hash, (HashCompareFunc) strcmp);
+
+	passwd_file_parse_file(pw);
+	return pw;
+}
+
+static void passwd_file_free(PasswdFile *pw)
+{
+	pool_unref(pw->pool);
+}
+
+static void passwd_file_init(const char *args)
+{
+	passwd_file = passwd_file_parse(args);
+}
+
+static void passwd_file_deinit(void)
+{
+	passwd_file_free(passwd_file);
+}
+
+UserInfoModule userinfo_passwd_file = {
+	passwd_file_init,
+	passwd_file_deinit,
+
+	passwd_file_verify_plain,
+        passwd_file_lookup_digest_md5
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/userinfo-passwd.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,74 @@
+/*
+   Loosely based on auth_passwd.c from popa3d by
+   Solar Designer <solar@openwall.com>
+
+   Copyright (C) 2002 Timo Sirainen
+*/
+
+#define _XOPEN_SOURCE 4
+#define _XOPEN_SOURCE_EXTENDED
+#define _XOPEN_VERSION 4 /* FIXME: needed? solaris throws warnings with it */
+#define _XPG4_2
+
+#include "common.h"
+
+#ifdef USERINFO_PASSWD
+
+#include "userinfo.h"
+#include "userinfo-passwd.h"
+
+#include <unistd.h>
+
+void passwd_fill_cookie_reply(struct passwd *pw, AuthCookieReplyData *reply)
+{
+	i_assert(sizeof(reply->user) > strlen(pw->pw_name));
+	i_assert(sizeof(reply->home) > strlen(pw->pw_dir));
+
+	reply->uid = pw->pw_uid;
+	reply->gid = pw->pw_gid;
+
+	strcpy(reply->user, pw->pw_name);
+	strcpy(reply->home, pw->pw_dir);
+}
+
+static int passwd_verify_plain(const char *user, const char *password,
+			       AuthCookieReplyData *reply)
+{
+	struct passwd *pw;
+	char *passdup;
+	int result;
+
+	pw = getpwnam(user);
+	if (pw == NULL || !IS_VALID_PASSWD(pw->pw_passwd))
+		return FALSE;
+
+	/* check if the password is valid */
+        passdup = (char *) t_strdup(password);
+	result = strcmp(crypt(passdup, pw->pw_passwd), pw->pw_passwd) == 0;
+
+	/* clear the passwords from memory */
+	memset(passdup, 0, strlen(passdup));
+	memset(pw->pw_passwd, 0, strlen(pw->pw_passwd));
+
+	if (!result)
+		return FALSE;
+
+	/* password ok, save the user info */
+        passwd_fill_cookie_reply(pw, reply);
+	return TRUE;
+}
+
+static void passwd_deinit(void)
+{
+	endpwent();
+}
+
+UserInfoModule userinfo_passwd = {
+	NULL,
+	passwd_deinit,
+
+	passwd_verify_plain,
+	NULL
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/userinfo-passwd.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,11 @@
+#ifndef __USERINFO_PASSWD_H
+#define __USERINFO_PASSWD_H
+
+#include <pwd.h>
+
+#define IS_VALID_PASSWD(pass) \
+	((pass)[0] != '\0' && (pass)[0] != '*' && (pass)[0] != '!')
+
+void passwd_fill_cookie_reply(struct passwd *pw, AuthCookieReplyData *reply);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/userinfo-shadow.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,69 @@
+/*
+   Loosely based on auth_shadow.c from popa3d by
+   Solar Designer <solar@openwall.com>
+
+   Copyright (C) 2002 Timo Sirainen
+*/
+
+#define _XOPEN_SOURCE 4
+#define _XOPEN_SOURCE_EXTENDED
+#define _XOPEN_VERSION 4
+#define _XPG4_2
+
+#include "common.h"
+
+#ifdef USERINFO_SHADOW
+
+#include "userinfo.h"
+#include "userinfo-passwd.h"
+
+#include <unistd.h>
+#include <shadow.h>
+
+static int shadow_verify_plain(const char *user, const char *password,
+			       AuthCookieReplyData *reply)
+{
+	struct passwd *pw;
+	struct spwd *spw;
+	char *passdup;
+	int result;
+
+	spw = getspnam(user);
+	if (spw == NULL || !IS_VALID_PASSWD(spw->sp_pwdp))
+		return FALSE;
+
+	/* check if the password is valid */
+        passdup = (char *) t_strdup(password);
+	result = strcmp(crypt(passdup, spw->sp_pwdp), spw->sp_pwdp) == 0;
+
+	/* clear the passwords from memory */
+	memset(passdup, 0, strlen(passdup));
+	memset(spw->sp_pwdp, 0, strlen(spw->sp_pwdp));
+
+	if (!result)
+		return FALSE;
+
+	/* password ok, save the user info */
+	pw = getpwnam(user);
+	if (pw == NULL)
+		return FALSE;
+
+        passwd_fill_cookie_reply(pw, reply);
+	return TRUE;
+}
+
+static void shadow_deinit(void)
+{
+	endpwent();
+        endspent();
+}
+
+UserInfoModule userinfo_shadow = {
+	NULL,
+	shadow_deinit,
+
+	shadow_verify_plain,
+	NULL
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/userinfo.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,60 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "userinfo.h"
+
+#include <stdlib.h>
+
+UserInfoModule *userinfo;
+
+void userinfo_init(void)
+{
+	const char *name, *args;
+
+	userinfo = NULL;
+
+	name = getenv("USERINFO");
+	if (name == NULL)
+		i_fatal("USERINFO environment is unset");
+
+#ifdef USERINFO_PASSWD
+	if (strcasecmp(name, "passwd") == 0)
+		userinfo = &userinfo_passwd;
+#endif
+#ifdef USERINFO_SHADOW
+	if (strcasecmp(name, "shadow") == 0)
+		userinfo = &userinfo_shadow;
+#endif
+#ifdef USERINFO_PAM
+	if (strcasecmp(name, "pam") == 0)
+		userinfo = &userinfo_pam;
+#endif
+#ifdef USERINFO_PASSWD_FILE
+	if (strcasecmp(name, "passwd-file") == 0)
+		userinfo = &userinfo_passwd_file;
+#endif
+
+	if (userinfo == NULL)
+		i_fatal("Unknown userinfo type '%s'", name);
+
+	/* initialize */
+	if (userinfo->init != NULL) {
+		args = getenv("USERINFO_ARGS");
+		if (args == NULL) args = "";
+
+		userinfo->init(args);
+	}
+
+	if ((auth_methods & AUTH_METHOD_PLAIN) &&
+	    userinfo->verify_plain == NULL)
+		i_fatal("Userinfo %s doesn't support PLAIN method", name);
+	if ((auth_methods & AUTH_METHOD_DIGEST_MD5) &&
+	    userinfo->lookup_digest_md5 == NULL)
+		i_fatal("Userinfo %s doesn't support DIGEST-MD5 method", name);
+}
+
+void userinfo_deinit(void)
+{
+	if (userinfo != NULL && userinfo->deinit != NULL)
+		userinfo->deinit();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/userinfo.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,35 @@
+#ifndef __USERINFO_H
+#define __USERINFO_H
+
+#include "auth-interface.h"
+
+typedef struct {
+	void (*init)(const char *args);
+	void (*deinit)(void);
+
+	/* Returns TRUE if user/pass matches, and fills reply with user
+	   information. reply should have been initialized (zeroed) before
+	   calling this function. */
+	int (*verify_plain)(const char *user, const char *password,
+			   AuthCookieReplyData *reply);
+
+	/* Digest-MD5 specific password lookup. The digest is filled with
+	   the MD5 password which consists of a MD5 sum of
+	   "user:realm:password". If utf8 is TRUE, the user and realm are
+	   in UTF-8, otherwise ISO-8859-1. Returns TRUE if user was found. */
+	int (*lookup_digest_md5)(const char *user, const char *realm,
+				 int utf8, unsigned char digest[16],
+				 AuthCookieReplyData *reply);
+} UserInfoModule;
+
+extern UserInfoModule *userinfo;
+
+extern UserInfoModule userinfo_passwd;
+extern UserInfoModule userinfo_shadow;
+extern UserInfoModule userinfo_pam;
+extern UserInfoModule userinfo_passwd_file;
+
+void userinfo_init(void);
+void userinfo_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/.cvsignore	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,9 @@
+*.la
+*.lo
+*.o
+.deps
+.libs
+Makefile
+Makefile.in
+so_locations
+imap
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/Makefile.am	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,60 @@
+pkglib_PROGRAMS = imap
+
+INCLUDES = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-mail \
+	-I$(top_srcdir)/src/lib-imap \
+	-I$(top_srcdir)/src/lib-storage
+
+imap_LDADD = \
+	../lib-storage/libstorage.a \
+	../lib-storage/index/maildir/libstorage_maildir.a \
+	../lib-storage/index/mbox/libstorage_mbox.a \
+	../lib-storage/index/libstorage_index.a \
+	../lib-index/maildir/libstorage_index_maildir.a \
+	../lib-index/mbox/libstorage_index_mbox.a \
+	../lib-index/libstorage_index.a \
+	../lib-storage/subscription-file/libstorage_subscription_file.a \
+	../lib-storage/flags-file/libstorage_flags_file.a \
+	../lib-imap/libimap.a \
+	../lib-mail/libmail.a \
+	../lib/liblib.a
+
+cmds = \
+	cmd-append.c \
+	cmd-authenticate.c \
+	cmd-capability.c \
+	cmd-check.c \
+	cmd-close.c \
+	cmd-copy.c \
+	cmd-create.c \
+	cmd-delete.c \
+	cmd-examine.c \
+	cmd-expunge.c \
+	cmd-fetch.c \
+	cmd-list.c \
+	cmd-login.c \
+	cmd-logout.c \
+	cmd-lsub.c \
+	cmd-noop.c \
+	cmd-rename.c \
+	cmd-search.c \
+	cmd-select.c \
+	cmd-status.c \
+	cmd-store.c \
+	cmd-subscribe.c \
+	cmd-uid.c \
+	cmd-unsubscribe.c
+
+imap_SOURCES = \
+	$(cmds) \
+	client.c \
+	commands.c \
+	commands-util.c \
+	main.c
+
+noinst_HEADERS = \
+	client.h \
+	commands.h \
+	commands-util.h \
+	common.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/client.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,360 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "iobuffer.h"
+#include "commands.h"
+
+#include <stdlib.h>
+
+/* max. size of one parameter in line */
+#define MAX_INBUF_SIZE 8192
+
+/* If we can't send a buffer in a minute, disconnect the client */
+#define CLIENT_OUTPUT_TIMEOUT (60*1000)
+
+/* Disconnect client when it sends too many bad commands */
+#define CLIENT_MAX_BAD_COMMANDS 20
+
+/* Disconnect client after idling this many seconds */
+#define CLIENT_IDLE_TIMEOUT (60*30)
+
+static Client *my_client; /* we don't need more than one currently */
+static Timeout to_idle;
+
+static void client_input(Client *client);
+
+static void client_send_timeout(Client *client)
+{
+	io_buffer_close(client->inbuf);
+	io_buffer_close(client->outbuf);
+}
+
+Client *client_create(int hin, int hout, int socket, MailStorage *storage)
+{
+	Client *client;
+
+	client = i_new(Client, 1);
+	client->socket = socket;
+	client->inbuf = io_buffer_create(hin, default_pool, 0,
+					 MAX_INBUF_SIZE);
+	client->outbuf = io_buffer_create(hout, default_pool, 0, 0);
+
+	io_buffer_set_send_blocking(client->outbuf, 4096,
+				    CLIENT_OUTPUT_TIMEOUT,
+				    (TimeoutFunc) client_send_timeout, client);
+
+	client->inbuf->file = !socket;
+	client->outbuf->file = !socket;
+
+	client->io = io_add(socket, IO_READ, (IOFunc) client_input, client);
+	client->parser = imap_parser_create(client->inbuf, client->outbuf);
+        client->last_input = ioloop_time;
+
+	client->storage = storage;
+
+	i_assert(my_client == NULL);
+	my_client = client;
+	return client;
+}
+
+void client_destroy(Client *client)
+{
+	if (client->mailbox != NULL)
+		client->mailbox->close(client->mailbox);
+	mail_storage_destroy(client->storage);
+
+	imap_parser_destroy(client->parser);
+	io_remove(client->io);
+
+	io_buffer_destroy(client->inbuf);
+	io_buffer_destroy(client->outbuf);
+
+	i_free(client);
+
+	/* quit the program */
+	my_client = NULL;
+	io_loop_stop(ioloop);
+}
+
+void client_disconnect(Client *client)
+{
+	io_buffer_close(client->inbuf);
+	io_buffer_close(client->outbuf);
+}
+
+void client_send_line(Client *client, const char *data)
+{
+	unsigned char *buf;
+	unsigned int len;
+
+	if (client->outbuf->closed)
+		return;
+
+	len = strlen(data);
+
+	buf = io_buffer_get_space(client->outbuf, len+2);
+	if (buf != NULL) {
+		memcpy(buf, data, len);
+		buf[len++] = '\r'; buf[len++] = '\n';
+
+		/* Returns error only if we disconnected -
+		   we don't need to do anything about it. */
+		(void)io_buffer_send_buffer(client->outbuf, len);
+	} else {
+		/* not enough space in output buffer, send this directly.
+		   will block. */
+		io_buffer_send(client->outbuf, data, len);
+		io_buffer_send(client->outbuf, "\r\n", 2);
+	}
+}
+
+void client_send_tagline(Client *client, const char *data)
+{
+	const char *tag = client->cmd_tag;
+	unsigned char *buf;
+	unsigned int taglen, len;
+
+	if (client->outbuf->closed)
+		return;
+
+	if (tag == NULL || *tag == '\0')
+		tag = "*";
+
+	taglen = strlen(tag);
+	len = strlen(data);
+
+	buf = io_buffer_get_space(client->outbuf, taglen+1+len+2);
+	if (buf != NULL) {
+		memcpy(buf, tag, taglen); buf[taglen] = ' ';
+		buf += taglen+1;
+
+		memcpy(buf, data, len); buf += len;
+		buf[0] = '\r'; buf[1] = '\n';
+
+		(void)io_buffer_send_buffer(client->outbuf, taglen+1+len+2);
+	} else {
+		const char *str;
+
+		str = t_strconcat(tag, " ", data, "\r\n", NULL);
+		(void)io_buffer_send(client->outbuf, str, strlen(str));
+	}
+}
+
+void client_send_command_error(Client *client, const char *msg)
+{
+	const char *error;
+
+	if (msg == NULL)
+		error = "BAD Error in IMAP command.";
+	else
+		error = t_strconcat("BAD Error in IMAP command: ", msg, NULL);
+
+	client->cmd_error = TRUE;
+	client_send_tagline(client, error);
+
+	if (++client->bad_counter >= CLIENT_MAX_BAD_COMMANDS) {
+		client_send_line(client,
+				 "* BYE Too many invalid IMAP commands.");
+		client_disconnect(client);
+	}
+}
+
+int client_read_args(Client *client, unsigned int count, unsigned int flags,
+		     ImapArg **args)
+{
+	int ret;
+
+	ret = imap_parser_read_args(client->parser, count, flags, args);
+	if ((unsigned int) ret == count) {
+		/* all parameters read successfully */
+		return TRUE;
+	} else if (ret == -2) {
+		/* need more data */
+		return FALSE;
+	} else {
+		/* error, or missing arguments */
+		client_send_command_error(client,
+					  "Missing or invalid arguments.");
+		return FALSE;
+	}
+}
+
+int client_read_string_args(Client *client, unsigned int count, ...)
+{
+	ImapArg *imap_args;
+	va_list va;
+	const char *str;
+	unsigned int i;
+
+	if (!client_read_args(client, count, 0, &imap_args))
+		return FALSE;
+
+	va_start(va, count);
+	for (i = 0; i < count; i++) {
+		const char **ret = va_arg(va, const char **);
+
+		str = imap_arg_string(&imap_args[i]);
+		if (str == NULL) {
+			client_send_command_error(client, "Missing arguments.");
+			va_end(va);
+			return FALSE;
+		}
+
+		if (ret != NULL)
+			*ret = str;
+	}
+	va_end(va);
+
+	return TRUE;
+}
+
+static void client_reset_command(Client *client)
+{
+	client->cmd_tag = NULL;
+	client->cmd_name = NULL;
+	client->cmd_func = NULL;
+	client->cmd_error = FALSE;
+	client->cmd_uid = FALSE;
+
+        imap_parser_reset(client->parser);
+}
+
+static void client_command_finished(Client *client)
+{
+	client->inbuf_skip_line = TRUE;
+        client_reset_command(client);
+}
+
+/* Skip incoming data until newline is found,
+   returns TRUE if newline was found. */
+static int client_skip_line(Client *client)
+{
+	unsigned char *data;
+	unsigned int i, data_size;
+
+	/* get the beginning of data in input buffer */
+	data = io_buffer_get_data(client->inbuf, &data_size);
+
+	for (i = 0; i < data_size; i++) {
+		if (data[i] == '\n') {
+			client->inbuf_skip_line = FALSE;
+			client->inbuf->skip += i+1;
+			break;
+		}
+	}
+
+	return !client->inbuf_skip_line;
+}
+
+static int client_handle_input(Client *client)
+{
+        if (client->cmd_func != NULL) {
+		/* command is being executed - continue it */
+		if (client->cmd_func(client)) {
+			/* command is finished */
+			client_command_finished(client);
+			return TRUE;
+		}
+		return FALSE;
+	}
+
+	if (client->inbuf_skip_line) {
+		/* we're just waiting for new line.. */
+		if (!client_skip_line(client))
+			return FALSE;
+
+		/* got the newline */
+		client_reset_command(client);
+
+		/* pass through to parse next command */
+	}
+
+	if (client->cmd_tag == NULL) {
+                client->cmd_tag = imap_parser_read_word(client->parser);
+		if (client->cmd_tag == NULL)
+			return FALSE; /* need more data */
+	}
+
+	if (client->cmd_name == NULL) {
+                client->cmd_name = imap_parser_read_word(client->parser);
+		if (client->cmd_name == NULL)
+			return FALSE; /* need more data */
+	}
+
+	if (client->cmd_name == '\0') {
+		/* command not given - cmd_func is already NULL. */
+	} else {
+		/* find the command function */
+		client->cmd_func = client_command_find(client->cmd_name);
+	}
+
+	if (client->cmd_func == NULL) {
+		/* unknown command */
+		client_send_command_error(client, t_strconcat(
+			"Unknown command '", client->cmd_name, "'", NULL));
+		client_command_finished(client);
+	} else {
+		if (client->cmd_func(client) || client->cmd_error) {
+			/* command execution was finished */
+			client_command_finished(client);
+		}
+	}
+
+	return TRUE;
+}
+
+static void client_input(Client *client)
+{
+	client->last_input = ioloop_time;
+
+	switch (io_buffer_read(client->inbuf)) {
+	case -1:
+		/* disconnected */
+		client_destroy(client);
+		return;
+	case -2:
+		/* parameter word is longer than max. input buffer size.
+		   this is most likely an error, so skip the new data
+		   until newline is found. */
+		client->inbuf_skip_line = TRUE;
+
+		client_send_command_error(client, "Too long argument.");
+		client_command_finished(client);
+		break;
+	}
+
+	io_buffer_cork(client->outbuf);
+	while (client_handle_input(client))
+		;
+	io_buffer_send_flush(client->outbuf);
+
+	if (client->outbuf->closed)
+		client_destroy(client);
+}
+
+static void idle_timeout(void *user_data __attr_unused__,
+			 Timeout timeout __attr_unused__)
+{
+	if (my_client == NULL)
+		return;
+
+	if (ioloop_time - my_client->last_input >= CLIENT_IDLE_TIMEOUT) {
+		client_send_line(my_client,
+				 "* BYE Disconnected for inactivity.");
+		client_destroy(my_client);
+	}
+}
+
+void clients_init(void)
+{
+	my_client = NULL;
+	to_idle = timeout_add(10000, idle_timeout, NULL);
+}
+
+void clients_deinit(void)
+{
+	if (my_client != NULL)
+		client_destroy(my_client);
+
+	timeout_remove(to_idle);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/client.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,60 @@
+#ifndef __CLIENT_H
+#define __CLIENT_H
+
+#include "imap-parser.h"
+#include "mail-storage.h"
+
+typedef struct _Client Client;
+
+typedef int (*ClientCommandFunc) (Client *client);
+
+struct _Client {
+	int socket;
+	IO io;
+	IOBuffer *inbuf, *outbuf;
+
+	MailStorage *storage;
+	Mailbox *mailbox;
+
+	time_t last_input;
+	unsigned int bad_counter;
+
+	ImapParser *parser;
+	const char *cmd_tag; /* tag of command (allocated from parser pool), */
+	const char *cmd_name; /* command name (allocated from parser pool) */
+	ClientCommandFunc cmd_func;
+
+	unsigned int cmd_error:1;
+	unsigned int cmd_uid:1; /* used UID command */
+	unsigned int inbuf_skip_line:1; /* skip all the data until we've
+					   found a new line */
+};
+
+/* Create new client with specified input/output handles. socket specifies
+   if the handle is a socket. */
+Client *client_create(int hin, int hout, int socket, MailStorage *storage);
+void client_destroy(Client *client);
+
+/* Disconnect client connection */
+void client_disconnect(Client *client);
+
+/* Send a line of data to client */
+void client_send_line(Client *client, const char *data);
+/* Send line of data to client, prefixed with client->tag */
+void client_send_tagline(Client *client, const char *data);
+
+/* Send BAD command error to client. msg can be NULL. */
+void client_send_command_error(Client *client, const char *msg);
+
+/* Read a number of arguments. Returns TRUE if everything was read or
+   FALSE if either needs more data or error occured. */
+int client_read_args(Client *client, unsigned int count, unsigned int flags,
+		     ImapArg **args);
+/* Reads a number of string arguments. ... is a list of pointers where to
+   store the arguments. */
+int client_read_string_args(Client *client, unsigned int count, ...);
+
+void clients_init(void);
+void clients_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-append.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,132 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "commands.h"
+#include "imap-parser.h"
+#include "rfc822-date.h"
+
+/* Returns -1 = error, 0 = need more data, 1 = successful. flags and
+   internal_date may be NULL as a result, but mailbox and msg_size are always
+   set when successful. */
+static int validate_args(Client *client, const char **mailbox,
+			 ImapArgList **flags, const char **internal_date,
+			 unsigned int *msg_size, unsigned int count)
+{
+	ImapArg *args;
+	int ret;
+
+	i_assert(count >= 2 && count <= 4);
+
+	*flags = NULL;
+	*internal_date = NULL;
+
+	ret = client_read_args(client, count, IMAP_PARSE_FLAG_LITERAL_SIZE,
+			       &args);
+	i_assert((unsigned int) ret == count);
+
+	switch (count) {
+	case 2:
+		/* do we have flags or internal date parameter? */
+		if (args[1].type == IMAP_ARG_LIST ||
+		    args[1].type == IMAP_ARG_STRING)
+			return validate_args(client, mailbox, flags,
+					     internal_date, msg_size, 3);
+
+		break;
+	case 3:
+		/* do we have both flags and internal date? */
+		if (args[1].type == IMAP_ARG_LIST &&
+		    args[2].type == IMAP_ARG_STRING)
+			return validate_args(client, mailbox, flags,
+					     internal_date, msg_size, 4);
+
+		if (args[1].type == IMAP_ARG_LIST)
+			*flags = args[1].data.list;
+		else if (args[1].type == IMAP_ARG_STRING)
+			*internal_date = args[1].data.str;
+		else
+			return -1;
+		break;
+	case 4:
+		/* we have all parameters */
+		*flags = args[1].data.list;
+		*internal_date = args[2].data.str;
+		break;
+	default:
+		i_assert(0);
+	}
+
+	/* check that mailbox and message arguments are ok */
+	*mailbox = imap_arg_string(&args[0]);
+	if (*mailbox == NULL)
+		return -1;
+
+	if (args[ret-1].type != IMAP_ARG_LITERAL_SIZE)
+		return -1;
+
+	*msg_size = args[ret-1].data.literal_size;
+	return 1;
+}
+
+int cmd_append(Client *client)
+{
+	ImapArgList *flags_list;
+	Mailbox *box;
+	MailFlags flags;
+	time_t internal_date;
+	const char *custom_flags[MAIL_CUSTOM_FLAGS_COUNT];
+	const char *mailbox, *internal_date_str;
+	unsigned int msg_size;
+	int failed;
+
+	/* <mailbox> [<flags>] [<internal date>] <message literal> */
+	switch (validate_args(client, &mailbox, &flags_list,
+			      &internal_date_str, &msg_size, 2)) {
+	case -1:
+		/* error */
+		client_send_command_error(client, "Invalid APPEND arguments.");
+		return TRUE;
+	case 0:
+		/* need more data */
+		return FALSE;
+	default:
+	}
+
+	if (!client_parse_mail_flags(client, flags_list, &flags, custom_flags))
+		return TRUE;
+
+	if (!rfc822_parse_date(internal_date_str, &internal_date)) {
+		client_send_tagline(client, "BAD Invalid internal date.");
+		return TRUE;
+	}
+
+	if (client->mailbox != NULL &&
+	    strcmp(client->mailbox->name, mailbox) == 0) {
+		/* this mailbox is selected */
+		box = client->mailbox;
+	} else {
+		/* open the mailbox */
+		if (!client_verify_mailbox_name(client, mailbox, TRUE))
+			return TRUE;
+
+		box = client->storage->open_mailbox(client->storage,
+						    mailbox, FALSE);
+		if (box == NULL) {
+			client_send_storage_error(client);
+			return TRUE;
+		}
+	}
+
+	/* save the mail */
+	failed = !box->save(box, flags, custom_flags, internal_date,
+			    client->inbuf, msg_size);
+	if (box != client->mailbox)
+		box->close(box);
+
+	if (failed)
+		return FALSE;
+
+	client_sync_mailbox(client);
+	client_send_tagline(client, "OK Append completed.");
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-authenticate.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,10 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "commands.h"
+
+int cmd_authenticate(Client *client)
+{
+	client_send_tagline(client, "OK Already authenticated.");
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-capability.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,13 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "commands.h"
+
+int cmd_capability(Client *client)
+{
+	client_send_line(client, "* CAPABILITY " CAPABILITY_STRING);
+
+	client_sync_mailbox(client);
+	client_send_tagline(client, "OK Capability completed.");
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-check.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,15 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "commands.h"
+
+int cmd_check(Client *client)
+{
+	if (!client_verify_open_mailbox(client))
+		return TRUE;
+
+	/* we don't need this command, but sync the mailbox anyway. */
+	client_sync_mailbox(client);
+	client_send_tagline(client, "OK Check completed.");
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-close.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,19 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "commands.h"
+
+int cmd_close(Client *client)
+{
+	if (!client_verify_open_mailbox(client))
+		return TRUE;
+
+	/* Ignore expunge errors - we can't really do anything about it */
+	(void)client->mailbox->expunge(client->mailbox);
+
+	client->mailbox->close(client->mailbox);
+	client->mailbox = NULL;
+
+	client_send_tagline(client, "OK Close completed.");
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-copy.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,39 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "commands.h"
+
+int cmd_copy(Client *client)
+{
+	Mailbox *destbox;
+	const char *messageset, *mailbox;
+
+	/* <message set> <mailbox> */
+	if (!client_read_string_args(client, 2, &messageset, &mailbox))
+		return FALSE;
+
+	if (!client_verify_mailbox_name(client, mailbox, TRUE))
+		return TRUE;
+
+	/* open the destination mailbox */
+	if (!client_verify_mailbox_name(client, mailbox, TRUE))
+		return TRUE;
+
+	destbox = client->storage->open_mailbox(client->storage,
+						mailbox, FALSE);
+	if (destbox == NULL) {
+		client_send_storage_error(client);
+		return TRUE;
+	}
+
+	/* copy the mail */
+	if (client->mailbox->copy(client->mailbox, destbox,
+				  messageset, client->cmd_uid)) {
+                client_sync_mailbox(client);
+		client_send_tagline(client, "OK Copy completed.");
+	} else
+		client_send_storage_error(client);
+
+	destbox->close(destbox);
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-create.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,28 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "commands.h"
+
+int cmd_create(Client *client)
+{
+	const char *mailbox;
+
+	/* <mailbox> */
+	if (!client_read_string_args(client, 1, &mailbox))
+		return FALSE;
+
+	if (!client_verify_mailbox_name(client, mailbox, FALSE))
+		return TRUE;
+
+	if (mailbox[strlen(mailbox)-1] == client->storage->hierarchy_sep) {
+		/* name ends with hierarchy separator - client is just
+		   informing us that it wants to create a mailbox under
+		   this name. we don't need that information. */
+	} else if (!client->storage->create_mailbox(client->storage, mailbox)) {
+		client_send_storage_error(client);
+		return TRUE;
+	}
+
+	client_send_tagline(client, "OK Create completed.");
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-delete.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,25 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "commands.h"
+
+int cmd_delete(Client *client)
+{
+	const char *mailbox;
+
+	/* <mailbox> */
+	if (!client_read_string_args(client, 1, &mailbox))
+		return FALSE;
+
+	if (strcasecmp(mailbox, "INBOX") == 0) {
+		/* INBOX can't be deleted */
+		client_send_tagline(client, "NO INBOX can't be deleted.");
+		return TRUE;
+	}
+
+	if (client->storage->delete_mailbox(client->storage, mailbox))
+		client_send_tagline(client, "OK Delete completed.");
+	else
+		client_send_storage_error(client);
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-examine.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,11 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "commands.h"
+
+int cmd_select_full(Client *client, int readonly);
+
+int cmd_examine(Client *client)
+{
+	return cmd_select_full(client, TRUE);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-expunge.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,17 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "commands.h"
+
+int cmd_expunge(Client *client)
+{
+	if (!client_verify_open_mailbox(client))
+		return TRUE;
+
+	if (client_sync_and_expunge_mailbox(client))
+		client_send_tagline(client, "OK Expunge completed.");
+	else
+		client_send_storage_error(client);
+
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-fetch.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,278 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "commands.h"
+
+/* Parse next digits in string into integer. Returns FALSE if the integer
+   becomes too big and wraps. */
+static int read_int(char **p, unsigned int *value)
+{
+	unsigned int prev;
+	*value = 0;
+
+	while (**p >= '0' && **p <= '9') {
+		prev = *value;
+		*value = *value * 10 + **p - '0';
+
+		if (*value < prev)
+			return FALSE;
+
+		(*p)++;
+	}
+
+	return TRUE;
+}
+
+/* BODY[] and BODY.PEEK[] items. item points to next character after '[' */
+static int parse_body_section(Client *client, const char *item,
+			      MailFetchData *data, int peek)
+{
+	MailFetchBodyData *body;
+	unsigned int num;
+	const char *section;
+	char *p;
+
+	body = t_new(MailFetchBodyData, 1);
+	body->peek = peek;
+
+	p = (char *) t_strdup(item);
+
+	/* read section */
+	body->section = p;
+	for (section = p; *p != ']'; p++) {
+		if (*p == '\0') {
+			client_send_tagline(client, t_strconcat(
+				"BAD Missing ']' with ", item, NULL));
+			return FALSE;
+		}
+	}
+	*p++ = '\0';
+
+	/* <start.end> */
+	body->skip = body->max_size = 0;
+	if (*p != '<' && *p != '\0') {
+		client_send_tagline(client, t_strconcat(
+			"BAD Unexpected character after ']' with ",
+			item, NULL));
+	} else if (*p == '<') {
+		/* read start */
+		p++;
+
+		body->skip_set = TRUE;
+		if (!read_int(&p, &num) || num > INT_MAX) {
+			/* wrapped */
+			client_send_tagline(client, t_strconcat(
+				"BAD Too big partial start with ", item, NULL));
+			return FALSE;
+		}
+		body->skip = num;
+
+		if (*p == '.') {
+			/* read end */
+			p++;
+			if (!read_int(&p, &num) || num > INT_MAX) {
+				/* wrapped */
+				client_send_tagline(client, t_strconcat(
+					"BAD Too big partial end with ",
+					item, NULL));
+				return FALSE;
+			}
+
+                        body->max_size = num;
+		}
+
+		if (*p != '>') {
+			client_send_tagline(client, t_strconcat(
+				"BAD Invalid partial ", item, NULL));
+			return FALSE;
+		}
+	}
+
+	body->next = data->body_sections;
+	data->body_sections = body;
+
+	return TRUE;
+}
+
+static int parse_arg(Client *client, ImapArg *arg, MailFetchData *data)
+{
+	char *item;
+
+	if (arg->type != IMAP_ARG_ATOM) {
+		client_send_command_error(client,
+					  "FETCH list contains non-atoms.");
+		return FALSE;
+	}
+
+	item = arg->data.str;
+	str_ucase(item);
+
+	switch (*item) {
+	case 'A':
+		if (strcmp(item, "ALL") == 0) {
+			data->flags = TRUE;
+			data->internaldate = TRUE;
+			data->rfc822_size = TRUE;
+			data->envelope = TRUE;
+		} else
+			item = NULL;
+		break;
+	case 'B':
+		/* all start with BODY so skip it */
+		if (strncmp(item, "BODY", 4) != 0) {
+			item = NULL;
+			break;
+		}
+		item += 4;
+
+		if (*item == '\0') {
+			/* BODY */
+			data->body = TRUE;
+		} else if (*item == '[') {
+			/* BODY[...] */
+			if (!parse_body_section(client, item+1, data, FALSE))
+				return FALSE;
+		} else if (strncmp(item, ".PEEK[", 6) == 0) {
+			/* BODY.PEEK[..] */
+			if (!parse_body_section(client, item+6, data, TRUE))
+				return FALSE;
+		} else if (strcmp(item, "STRUCTURE") == 0) {
+			/* BODYSTRUCTURE */
+			data->bodystructure = TRUE;
+		} else
+			item = NULL;
+		break;
+	case 'E':
+		if (strcmp(item, "ENVELOPE") == 0)
+			data->envelope = TRUE;
+		else
+			item = NULL;
+		break;
+	case 'F':
+		if (strcmp(item, "FLAGS") == 0)
+			data->flags = TRUE;
+		else if (strcmp(item, "FAST") == 0) {
+			data->flags = TRUE;
+			data->internaldate = TRUE;
+			data->rfc822_size = TRUE;
+		} else if (strcmp(item, "FULL") == 0) {
+			data->flags = TRUE;
+			data->internaldate = TRUE;
+			data->rfc822_size = TRUE;
+			data->envelope = TRUE;
+			data->body = TRUE;
+		} else
+			item = NULL;
+		break;
+	case 'I':
+		if (strcmp(item, "INTERNALDATE") == 0)
+			data->internaldate = TRUE;
+		else
+			item = NULL;
+		break;
+	case 'R':
+		/* all start with RFC822 so skip it */
+		if (strncmp(item, "RFC822", 6) != 0) {
+			item = NULL;
+			break;
+		}
+		item += 6;
+
+		if (*item == '\0') {
+			/* RFC822 */
+			data->rfc822 = TRUE;
+			break;
+		}
+
+		/* only items beginning with "RFC822." left */
+		if (*item != '.') {
+			item = NULL;
+			break;
+		}
+		item++;
+
+		if (strcmp(item, "HEADER") == 0)
+			data->rfc822_header = TRUE;
+		else if (strcmp(item, "SIZE") == 0)
+			data->rfc822_size = TRUE;
+		else if (strcmp(item, "TEXT") == 0)
+			data->rfc822_text = TRUE;
+		else
+			item = NULL;
+		break;
+	case 'U':
+		if (strcmp(item, "UID") == 0)
+			data->uid = TRUE;
+		else
+			item = NULL;
+		break;
+	default:
+		item = NULL;
+		break;
+	}
+
+	if (item == NULL) {
+		/* unknown item */
+		client_send_tagline(client, t_strconcat(
+			"BAD Invalid item ", arg->data.str, NULL));
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+int cmd_fetch(Client *client)
+{
+	ImapArg *args;
+	ImapArgList *list;
+	MailFetchData data;
+	const char *messageset;
+	int all_found;
+
+	if (!client_read_args(client, 2, 0, &args))
+		return FALSE;
+
+	if (!client_verify_open_mailbox(client))
+		return TRUE;
+
+	messageset = imap_arg_string(&args[0]);
+	if (messageset == NULL ||
+	    (args[1].type != IMAP_ARG_LIST && args[1].type != IMAP_ARG_ATOM)) {
+		client_send_command_error(client, "Invalid FETCH arguments.");
+		return TRUE;
+	}
+
+	/* parse items argument */
+	memset(&data, 0, sizeof(MailFetchData));
+	if (args[1].type == IMAP_ARG_ATOM) {
+		if (!parse_arg(client, &args[1], &data))
+			return TRUE;
+	} else {
+		list = args[1].data.list;
+		while (list != NULL) {
+			ImapArg *arg = &list->arg;
+
+			if (!parse_arg(client, arg, &data))
+				return TRUE;
+
+			list = list->next;
+		}
+	}
+
+	data.messageset = messageset;
+	data.uidset = client->cmd_uid;
+	if (data.uidset)
+                data.uid = TRUE;
+
+	/* fetch it */
+	if (client->mailbox->fetch(client->mailbox, &data,
+				   client->outbuf, &all_found)) {
+		/* NOTE: syncing isn't allowed here */
+		client_send_tagline(client, all_found ? "OK Fetch completed." :
+				    "NO Some messages were not found.");
+	} else {
+		client_send_storage_error(client);
+	}
+
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-list.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,193 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "commands.h"
+#include "imap-match.h"
+
+typedef struct _ListNode ListNode;
+
+struct _ListNode {
+	ListNode *next;
+	ListNode *children;
+
+	char *name; /* escaped */
+	MailboxFlags flags;
+};
+
+typedef struct {
+	Pool pool;
+	ListNode *nodes;
+	MailStorage *storage;
+} ListData;
+
+static const char *mailbox_flags2str(MailboxFlags flags)
+{
+	const char *str;
+
+	str = t_strconcat((flags & MAILBOX_NOSELECT) ? " \\Noselect" : "",
+			  (flags & MAILBOX_CHILDREN) ? " \\HasChildren" : "",
+			  (flags & MAILBOX_NOCHILDREN) ? " \\HasNoChildren" :"",
+			  (flags & MAILBOX_NOINFERIORS) ? " \\NoInferiors" : "",
+			  (flags & MAILBOX_MARKED) ? " \\Marked" : "",
+			  (flags & MAILBOX_UNMARKED) ? " \\UnMarked" : "",
+			  NULL);
+
+	return *str == '\0' ? "" : str+1;
+}
+
+static ListNode *list_node_get(Pool pool, ListNode **node,
+			       const char *path, char separator)
+{
+	const char *name, *parent;
+
+	parent = NULL;
+
+	t_push();
+	for (name = path;; path++) {
+		if (*path != separator && *path != '\0')
+			continue;
+
+		/* escaping is done here to make sure we don't try to escape
+		   the separator char */
+		name = imap_escape(t_strndup(name, (unsigned int) (path-name)));
+
+		/* find the node */
+		while (*node != NULL) {
+			if (strcmp((*node)->name, name) == 0)
+				break;
+
+			node = &(*node)->next;
+		}
+
+		if (*node == NULL) {
+			/* not found, create it */
+			*node = p_new(pool, ListNode, 1);
+			(*node)->name = p_strdup(pool, name);
+			(*node)->flags = MAILBOX_NOSELECT;
+		}
+
+		if (*path == '\0')
+			break;
+
+		name = path+1;
+		parent = (*node)->name;
+		node = &(*node)->children;
+	}
+	t_pop();
+
+	return *node;
+}
+
+static void list_func(MailStorage *storage __attr_unused__, const char *name,
+		     MailboxFlags flags, void *user_data)
+{
+	ListData *data = user_data;
+	ListNode *node;
+
+	node = list_node_get(data->pool, &data->nodes, name,
+			     data->storage->hierarchy_sep);
+
+	/* set the flags, this also nicely overrides the NOSELECT flag
+	   set by list_node_get() */
+	node->flags = flags;
+}
+
+static void list_send(Client *client, ListNode *node, const char *cmd,
+		      const char *path, const char *sep,
+		      const ImapMatchGlob *glob)
+{
+	const char *name;
+
+	for (; node != NULL; node = node->next) {
+		t_push();
+
+		name = path == NULL ? node->name :
+			t_strconcat(path, sep, node->name, NULL);
+
+		if (node->children == NULL)
+			node->flags |= MAILBOX_NOCHILDREN;
+		else {
+			node->flags |= MAILBOX_CHILDREN;
+			list_send(client, node->children, cmd, name, sep, glob);
+		}
+
+		if ((node->flags & MAILBOX_NOSELECT) &&
+		    imap_match(glob, name, 0, NULL) < 0) {
+			/* doesn't match the mask */
+			t_pop();
+			continue;
+		}
+
+		/* node->name should already be escaped */
+		client_send_line(client,
+				 t_strdup_printf("* %s (%s) \"%s\" \"%s\"", cmd,
+						 mailbox_flags2str(node->flags),
+						 sep, name));
+		t_pop();
+	}
+}
+
+int cmd_list_full(Client *client, int subscribed)
+{
+	ListData data;
+	const char *ref, *pattern;
+	char sep_chr, sep[3];
+
+	sep_chr = client->storage->hierarchy_sep;
+	if (IS_ESCAPED_CHAR(sep_chr)) {
+		sep[0] = '\\';
+		sep[1] = sep_chr;
+		sep[2] = '\0';
+	} else {
+		sep[0] = sep_chr;
+		sep[1] = '\0';
+	}
+
+	/* <reference> <mailbox wildcards> */
+	if (!client_read_string_args(client, 2, &ref, &pattern))
+		return FALSE;
+
+	if (*pattern == '\0' && !subscribed) {
+		/* special request to return the hierarchy delimiter */
+		client_send_line(client, t_strconcat(
+			"* LIST (\\Noselect) \"", sep, "\" \"\"", NULL));
+	} else {
+		if (*ref != '\0') {
+			/* join reference + pattern */
+			if (*pattern == sep_chr &&
+			    ref[strlen(ref)-1] == sep_chr) {
+				/* LIST A. .B -> A.B */
+				pattern++;
+			}
+			pattern = t_strconcat(ref, pattern, NULL);
+		}
+
+		data.pool = pool_create("ListData", 10240, FALSE);
+		data.nodes = NULL;
+		data.storage = client->storage;
+
+		if (!subscribed) {
+			client->storage->find_mailboxes(client->storage,
+							pattern,
+							list_func, &data);
+		} else {
+			client->storage->find_subscribed(client->storage,
+							 pattern,
+							 list_func, &data);
+		}
+
+		list_send(client, data.nodes, subscribed ? "LSUB" : "LIST",
+			  NULL, sep, imap_match_init(pattern, TRUE, sep_chr));
+		pool_unref(data.pool);
+	}
+
+	client_send_tagline(client, subscribed ?
+			    "OK Lsub completed." :
+			    "OK List completed.");
+	return TRUE;
+}
+
+int cmd_list(Client *client)
+{
+	return cmd_list_full(client, FALSE);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-login.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,10 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "commands.h"
+
+int cmd_login(Client *client)
+{
+	client_send_tagline(client, "OK Already logged in.");
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-logout.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,12 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "commands.h"
+
+int cmd_logout(Client *client)
+{
+	client_send_line(client, "* BYE Logging out");
+	client_send_tagline(client, "OK Logout completed.");
+	client_disconnect(client);
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-lsub.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,11 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "commands.h"
+
+int cmd_list_full(Client *client, int subscribed);
+
+int cmd_lsub(Client *client)
+{
+	return cmd_list_full(client, TRUE);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-noop.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,11 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "commands.h"
+
+int cmd_noop(Client *client)
+{
+	client_sync_mailbox(client);
+	client_send_tagline(client, "OK NOOP completed.");
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-rename.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,24 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "commands.h"
+
+int cmd_rename(Client *client)
+{
+	const char *oldname, *newname;
+
+	/* <old name> <new name> */
+	if (!client_read_string_args(client, 2, &oldname, &newname))
+		return FALSE;
+
+	if (!client_verify_mailbox_name(client, newname, FALSE))
+		return TRUE;
+
+	if (client->storage->rename_mailbox(client->storage,
+					    oldname, newname))
+		client_send_tagline(client, "OK Rename completed.");
+	else
+		client_send_storage_error(client);
+
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-search.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,46 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "commands.h"
+#include "mail-search.h"
+
+int cmd_search(Client *client)
+{
+	MailSearchArg *sargs;
+	ImapArg *args;
+	int args_count;
+	Pool pool;
+	const char *error;
+
+	args_count = imap_parser_read_args(client->parser, 0, 0, &args);
+	if (args_count == -2)
+		return FALSE;
+
+	if (args_count < 1) {
+		client_send_command_error(client,
+					  "Missing or invalid arguments.");
+		return TRUE;
+	}
+
+	if (!client_verify_open_mailbox(client))
+		return TRUE;
+
+	pool = pool_create("MailSearchArgs", 2048, FALSE);
+
+	sargs = mail_search_args_build(pool, args, args_count, &error);
+	if (sargs == NULL) {
+		/* error in search arguments */
+		client_send_tagline(client, t_strconcat("NO ", error, NULL));
+	} else {
+		if (client->mailbox->search(client->mailbox, sargs,
+					    client->outbuf, client->cmd_uid)) {
+			/* NOTE: syncing isn't allowed here */
+			client_send_tagline(client, "OK Search completed.");
+		} else {
+			client_send_storage_error(client);
+		}
+	}
+
+	pool_unref(pool);
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-select.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,69 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "commands.h"
+
+int cmd_select_full(Client *client, int readonly)
+{
+	Mailbox *box;
+	MailboxStatus status;
+	const char *mailbox;
+
+	/* <mailbox> */
+	if (!client_read_string_args(client, 1, &mailbox))
+		return FALSE;
+
+	if (client->mailbox != NULL)
+		client->mailbox->close(client->mailbox);
+
+	client->mailbox = client->storage->open_mailbox(client->storage,
+							mailbox, readonly);
+	if (client->mailbox == NULL) {
+		client_send_storage_error(client);
+		return TRUE;
+	}
+
+	box = client->mailbox;
+	if (!box->get_status(box, STATUS_MESSAGES | STATUS_RECENT |
+			     STATUS_FIRST_UNSEEN_SEQ | STATUS_UIDVALIDITY,
+			     &status)) {
+		client_send_storage_error(client);
+		return TRUE;
+	}
+
+	client_send_line(client, "* FLAGS (\\Answered \\Flagged "
+			 "\\Deleted \\Seen \\Draft \\Recent)");
+	if (box->readonly) {
+		client_send_line(client, "* OK [PERMANENTFLAGS ()] "
+				 "Read-only mailbox.");
+	} else {
+		client_send_line(client, "* OK [PERMANENTFLAGS (\\Answered "
+				 "\\Flagged \\Deleted \\Seen \\Draft)] "
+				 "Flags permitted.");
+	}
+
+	client_send_line(client,
+		t_strdup_printf("* %u EXISTS", status.messages));
+	client_send_line(client,
+		t_strdup_printf("* %u RECENT", status.recent));
+
+	if (status.first_unseen_seq != 0) {
+		client_send_line(client,
+			t_strdup_printf("* OK [UNSEEN %u] First unseen.",
+					status.first_unseen_seq));
+	}
+
+	client_send_line(client,
+		t_strdup_printf("* OK [UIDVALIDITY %u] UIDs valid",
+				status.uidvalidity));
+
+	client_send_tagline(client, box->readonly ?
+			    "OK [READ-ONLY] Select completed." :
+			    "OK [READ-WRITE] Select completed.");
+	return TRUE;
+}
+
+int cmd_select(Client *client)
+{
+	return cmd_select_full(client, FALSE);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-status.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,126 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "temp-string.h"
+#include "commands.h"
+
+/* Returns status items, or -1 if error */
+static MailboxStatusItems get_status_items(Client *client, ImapArgList *list)
+{
+	const char *item;
+	MailboxStatusItems items;
+
+	items = 0;
+	for (; list != NULL; list = list->next) {
+		if (list->arg.type != IMAP_ARG_ATOM) {
+			/* list may contain only atoms */
+			client_send_command_error(client, "Status list "
+						  "contains non-atoms.");
+			return -1;
+		}
+
+		str_ucase(list->arg.data.str);
+		item = list->arg.data.str;
+
+		if (strcmp(item, "MESSAGES") == 0)
+			items |= STATUS_MESSAGES;
+		else if (strcmp(item, "RECENT") == 0)
+			items |= STATUS_RECENT;
+		else if (strcmp(item, "UIDNEXT") == 0)
+			items |= STATUS_UIDNEXT;
+		else if (strcmp(item, "UIDVALIDITY") == 0)
+			items |= STATUS_UIDVALIDITY;
+		else if (strcmp(item, "UNSEEN") == 0)
+			items |= STATUS_UNSEEN;
+		else {
+			client_send_tagline(client, t_strconcat(
+				"BAD Invalid status item ", item, NULL));
+			return -1;
+		}
+	}
+
+	return items;
+}
+
+static int get_mailbox_status(Client *client, const char *mailbox,
+			      MailboxStatusItems items, MailboxStatus *status)
+{
+	Mailbox *box;
+
+	if (strcasecmp(mailbox, "inbox") == 0)
+		mailbox = "INBOX";
+
+	if (client->mailbox != NULL &&
+	    strcmp(client->mailbox->name, mailbox) == 0) {
+		/* this mailbox is selected */
+		box = client->mailbox;
+	} else {
+		/* open the mailbox */
+		box = client->storage->open_mailbox(client->storage,
+						    mailbox, FALSE);
+		if (box == NULL)
+			return FALSE;
+	}
+
+	if (!box->get_status(box, items, status))
+		return FALSE;
+
+	if (box != client->mailbox)
+		box->close(box);
+
+	return TRUE;
+}
+
+int cmd_status(Client *client)
+{
+	ImapArg *args;
+	MailboxStatus status;
+	MailboxStatusItems items;
+	const char *mailbox;
+	TempString *str;
+
+	/* <mailbox> <status items> */
+	if (!client_read_args(client, 2, 0, &args))
+		return FALSE;
+
+	mailbox = imap_arg_string(&args[0]);
+	if (mailbox == NULL || args[1].type != IMAP_ARG_LIST) {
+		client_send_command_error(client, "Status items must be list.");
+		return TRUE;
+	}
+
+	/* get the items client wants */
+	items = get_status_items(client, args[1].data.list);
+	if (items == (MailboxStatusItems)-1) {
+		/* error */
+		return TRUE;
+	}
+
+	/* get status */
+	if (!get_mailbox_status(client, mailbox, items, &status)) {
+		client_send_storage_error(client);
+		return TRUE;
+	}
+
+	str = t_string_new(128);
+	t_string_printfa(str, "* STATUS %s (", mailbox);
+	if (items & STATUS_MESSAGES)
+		t_string_printfa(str, "MESSAGES %u ", status.messages);
+	if (items & STATUS_RECENT)
+		t_string_printfa(str, "RECENT %u ", status.recent);
+	if (items & STATUS_UIDNEXT)
+		t_string_printfa(str, "UIDNEXT %u ", status.uidnext);
+	if (items & STATUS_UIDVALIDITY)
+		t_string_printfa(str, "UIDVALIDITY %u ", status.uidvalidity);
+	if (items & STATUS_UNSEEN)
+		t_string_printfa(str, "UNSEEN %u ", status.unseen);
+
+	if (str->str[str->len-1] == ' ')
+		t_string_truncate(str, str->len-1);
+	t_string_append_c(str, ')');
+
+	client_send_line(client, str->str);
+	client_send_tagline(client, "OK Status completed.");
+
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-store.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,118 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "commands.h"
+#include "imap-util.h"
+
+static void update_func(Mailbox *mailbox __attr_unused__, unsigned int seq,
+			unsigned int uid __attr_unused__, MailFlags flags,
+			const char *custom_flags[], void *user_data)
+{
+	Client *client = user_data;
+	const char *str;
+
+	t_push();
+	str = imap_write_flags(flags, custom_flags);
+	client_send_line(client,
+			 t_strdup_printf("* %u FETCH (FLAGS (%s))", seq, str));
+	t_pop();
+}
+
+static void update_func_uid(Mailbox *mailbox __attr_unused__, unsigned int seq,
+			    unsigned int uid, MailFlags flags,
+			    const char *custom_flags[], void *user_data)
+{
+	Client *client = user_data;
+	const char *str;
+
+	t_push();
+	str = imap_write_flags(flags, custom_flags);
+	client_send_line(client, t_strdup_printf(
+		"* %u FETCH (FLAGS (%s) UID %u)", seq, str, uid));
+	t_pop();
+}
+
+static int get_modify_type(Client *client, const char *item,
+			   ModifyType *modify_type, int *silent)
+{
+	if (*item == '+') {
+		*modify_type = MODIFY_ADD;
+		item++;
+	} else if (*item == '-') {
+		*modify_type = MODIFY_REMOVE;
+		item++;
+	} else {
+		*modify_type = MODIFY_REPLACE;
+	}
+
+	if (strncasecmp(item, "FLAGS", 5) != 0) {
+		client_send_tagline(client, t_strconcat(
+			"NO Invalid item ", item, NULL));
+		return FALSE;
+	}
+
+	*silent = strcasecmp(item+5, ".SILENT") == 0;
+	if (!*silent && item[5] != '\0') {
+		client_send_tagline(client, t_strconcat(
+			"NO Invalid item ", item, NULL));
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+int cmd_store(Client *client)
+{
+	ImapArg *args;
+	ImapArgList *list, tmplist;
+	MailFlags flags;
+	ModifyType modify_type;
+	MailFlagUpdateFunc func;
+	const char *custflags[MAIL_CUSTOM_FLAGS_COUNT];
+	const char *messageset, *item;
+	int silent, all_found;
+
+	if (!client_read_args(client, 3, 0, &args))
+		return FALSE;
+
+	if (!client_verify_open_mailbox(client))
+		return TRUE;
+
+	/* validate arguments */
+	messageset = imap_arg_string(&args[0]);
+	item = imap_arg_string(&args[1]);
+
+	if (messageset == NULL || item == NULL) {
+		client_send_command_error(client, "Invalid STORE arguments.");
+		return TRUE;
+	}
+
+	if (!get_modify_type(client, item, &modify_type, &silent))
+		return TRUE;
+
+	if (args[2].type == IMAP_ARG_LIST)
+		list = args[2].data.list;
+	else {
+		tmplist.next = NULL;
+		tmplist.arg = args[2];
+		list = &tmplist;
+	}
+
+	if (!client_parse_mail_flags(client, list, &flags, custflags))
+		return TRUE;
+
+	/* and update the flags */
+	func = silent ? NULL :
+		client->cmd_uid ? update_func_uid : update_func;
+	if (client->mailbox->update_flags(client->mailbox, messageset,
+					  client->cmd_uid, flags, custflags,
+					  modify_type, func, client,
+					  &all_found)) {
+		/* NOTE: syncing isn't allowed here */
+		client_send_tagline(client, all_found ? "OK Store completed." :
+				    "NO Some messages were not found.");
+	} else
+		client_send_storage_error(client);
+
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-subscribe.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,32 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "commands.h"
+
+int cmd_subscribe_full(Client *client, int subscribe)
+{
+	const char *mailbox;
+
+	/* <mailbox> */
+	if (!client_read_string_args(client, 1, &mailbox))
+		return FALSE;
+
+	if (!client_verify_mailbox_name(client, mailbox, TRUE))
+		return TRUE;
+
+	if (client->storage->set_subscribed(client->storage,
+					    mailbox, subscribe)) {
+		client_send_tagline(client, subscribe ?
+				    "OK Subscribe completed." :
+				    "OK Unsubscribe completed.");
+	} else {
+		client_send_storage_error(client);
+	}
+
+	return TRUE;
+}
+
+int cmd_subscribe(Client *client)
+{
+	return cmd_subscribe_full(client, TRUE);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-uid.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,44 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "commands.h"
+
+int cmd_uid(Client *client)
+{
+	const char *cmd;
+
+	/* UID <command> <args> */
+	cmd = imap_parser_read_word(client->parser);
+	if (cmd == NULL)
+		return FALSE;
+
+	client->cmd_func = NULL;
+	switch (*cmd) {
+	case 'c':
+	case 'C':
+		if (strcasecmp(cmd, "COPY") == 0)
+			client->cmd_func = cmd_copy;
+		break;
+	case 'f':
+	case 'F':
+		if (strcasecmp(cmd, "FETCH") == 0)
+			client->cmd_func = cmd_fetch;
+		break;
+	case 's':
+	case 'S':
+		if (strcasecmp(cmd, "STORE") == 0)
+			client->cmd_func = cmd_store;
+		else if (strcasecmp(cmd, "SEARCH") == 0)
+			client->cmd_func = cmd_search;
+		break;
+	}
+
+	if (client->cmd_func != NULL) {
+		client->cmd_uid = TRUE;
+		return client->cmd_func(client);
+	} else {
+		client_send_tagline(client, t_strconcat(
+			"BAD Unknown UID command ", cmd, NULL));
+		return TRUE;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-unsubscribe.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,11 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "commands.h"
+
+int cmd_subscribe_full(Client *client, int subscribe);
+
+int cmd_unsubscribe(Client *client)
+{
+	return cmd_subscribe_full(client, FALSE);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/commands-util.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,202 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "commands-util.h"
+#include "imap-util.h"
+
+int client_verify_mailbox_name(Client *client, const char *mailbox,
+			       int should_exist)
+{
+	MailboxNameStatus mailbox_status;
+	const char *p;
+	char sep;
+
+	/* make sure it even looks valid */
+	sep = client->storage->hierarchy_sep;
+	if (*mailbox == '\0' || *mailbox == sep ||
+	    strspn(mailbox, "\r\n*%?") != 0) {
+		client_send_tagline(client, "NO Invalid mailbox name.");
+		return FALSE;
+	}
+
+	/* make sure two hierarchy separators aren't next to each others */
+	for (p = mailbox+1; *p != '\0'; p++) {
+		if (p[0] == sep && p[1] == sep) {
+			client_send_tagline(client, "NO Invalid mailbox name.");
+			return FALSE;
+		}
+	}
+
+	/* check what our storage thinks of it */
+	if (!client->storage->get_mailbox_name_status(client->storage, mailbox,
+						      &mailbox_status)) {
+		client_send_storage_error(client);
+		return FALSE;
+	}
+
+	switch (mailbox_status) {
+	case MAILBOX_NAME_VALID:
+		if (!should_exist)
+			return TRUE;
+
+		client_send_tagline(client, "NO [TRYCREATE] "
+				    "Mailbox doesn't exist.");
+		break;
+
+	case MAILBOX_NAME_INVALID:
+		client_send_tagline(client, "NO Invalid mailbox name.");
+		break;
+
+	case MAILBOX_NAME_EXISTS:
+		if (should_exist)
+			return TRUE;
+
+		client_send_tagline(client, "NO Mailbox exists.");
+		break;
+	default:
+		i_assert(0);
+	}
+
+	return FALSE;
+}
+
+int client_verify_open_mailbox(Client *client)
+{
+	if (client->mailbox != NULL)
+		return TRUE;
+	else {
+		client_send_tagline(client, "NO No mailbox selected.");
+		return FALSE;
+	}
+}
+
+static void sync_expunge_func(Mailbox *mailbox __attr_unused__,
+			      unsigned int seq,
+			      unsigned int uid __attr_unused__, void *user_data)
+{
+	Client *client = user_data;
+	char str[MAX_INT_STRLEN+20];
+
+	i_snprintf(str, sizeof(str), "* %u EXPUNGE", seq);
+	client_send_line(client, str);
+}
+
+static void sync_flags_func(Mailbox *mailbox __attr_unused__, unsigned int seq,
+			    unsigned int uid __attr_unused__, MailFlags flags,
+			    const char *custom_flags[], void *user_data)
+{
+	Client *client = user_data;
+	const char *str;
+
+	t_push();
+	str = imap_write_flags(flags, custom_flags);
+	client_send_line(client,
+			 t_strdup_printf("* %u FETCH (FLAGS (%s))", seq, str));
+	t_pop();
+}
+
+static int client_sync_full(Client *client, int expunge)
+{
+	unsigned int messages;
+	char str[MAX_INT_STRLEN+20];
+
+	if (client->mailbox == NULL)
+		return TRUE;
+
+	if (!client->mailbox->sync(client->mailbox, &messages, expunge,
+				   sync_expunge_func, sync_flags_func, client))
+		return FALSE;
+
+	if (messages != 0) {
+		i_snprintf(str, sizeof(str), "* %u EXISTS", messages);
+		client_send_line(client, str);
+	}
+
+	return TRUE;
+}
+
+void client_sync_mailbox(Client *client)
+{
+	(void)client_sync_full(client, FALSE);
+}
+
+int client_sync_and_expunge_mailbox(Client *client)
+{
+	return client_sync_full(client, TRUE);
+}
+
+void client_send_storage_error(Client *client)
+{
+	if (client->mailbox != NULL &&
+	    client->mailbox->is_inconsistency_error(client->mailbox)) {
+		/* we can't do forced CLOSE, so have to disconnect */
+		client_send_line(client, "* BYE Mailbox is in inconsistent "
+				 "state, please relogin.");
+		client_disconnect(client);
+		return;
+	}
+
+	client_send_tagline(client, t_strconcat("NO ",
+		client->storage->get_last_error(client->storage), NULL));
+}
+
+int client_parse_mail_flags(Client *client, ImapArgList *list, MailFlags *flags,
+			    const char *custflags[MAIL_CUSTOM_FLAGS_COUNT])
+{
+	char *atom;
+	int i, custpos;
+
+	memset(custflags, 0, sizeof(const char *) * MAIL_CUSTOM_FLAGS_COUNT);
+
+	*flags = 0; custpos = 0;
+	while (list != NULL) {
+		if (list->arg.type != IMAP_ARG_ATOM) {
+			client_send_command_error(client, "Flags list "
+						  "contains non-atoms.");
+			return FALSE;
+		}
+
+		atom = list->arg.data.str;
+		if (*atom == '\\') {
+			/* system flag */
+			str_ucase(atom);
+			if (strcmp(atom, "\\ANSWERED") == 0)
+				*flags |= MAIL_ANSWERED;
+			else if (strcmp(atom, "\\FLAGGED") == 0)
+				*flags |= MAIL_FLAGGED;
+			else if (strcmp(atom, "\\DELETED") == 0)
+				*flags |= MAIL_DELETED;
+			else if (strcmp(atom, "\\SEEN") == 0)
+				*flags |= MAIL_SEEN;
+			else if (strcmp(atom, "\\DRAFT") == 0)
+				*flags |= MAIL_DRAFT;
+			else {
+				client_send_tagline(client, t_strconcat(
+					"BAD Invalid system flag ", atom, NULL));
+				return FALSE;
+			}
+		} else {
+			/* custom flag - first make sure it's not a duplicate */
+			for (i = 0; i < custpos; i++) {
+				if (strcasecmp(custflags[i], atom) == 0)
+					break;
+			}
+
+			if (i == MAIL_CUSTOM_FLAGS_COUNT) {
+				client_send_tagline(client,
+					"Maximum number of different custom "
+					"flags exceeded");
+				return FALSE;
+			}
+
+			if (i == custpos) {
+				*flags |= 1 << (custpos +
+						MAIL_CUSTOM_FLAG_1_BIT);
+				custflags[custpos++] = atom;
+			}
+		}
+
+		list = list->next;
+	}
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/commands-util.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,33 @@
+#ifndef __COMMANDS_UTIL_H
+#define __COMMANDS_UTIL_H
+
+/* If should_exist is TRUE, this function returns TRUE if the mailbox
+   exists. If it doesn't exist but would be a valid mailbox name, the
+   error message is prefixed with [TRYCREATE].
+
+   If should_exist is FALSE, this function returns TRUE if the mailbox
+   name is valid and doesn't exist. */
+int client_verify_mailbox_name(Client *client, const char *mailbox,
+			       int should_exist);
+
+/* Returns TRUE if mailbox is selected. If not, sends "No mailbox selected"
+   error message to client. */
+int client_verify_open_mailbox(Client *client);
+
+/* Synchronize selected mailbox with client by sending EXPUNGE and
+   FETCH FLAGS responses. */
+void client_sync_mailbox(Client *client);
+
+/* Synchronize selected mailbox and expunge messages with \Deleted flag. */
+int client_sync_and_expunge_mailbox(Client *client);
+
+/* Send last mail storage error message to client. */
+void client_send_storage_error(Client *client);
+
+/* Parse flags, stores custom flag names into custflags[]. The names point to
+   strings in ImapArgList. Returns TRUE if successful, if not sends an error
+   message to client. */
+int client_parse_mail_flags(Client *client, ImapArgList *list, MailFlags *flags,
+			    const char *custflags[MAIL_CUSTOM_FLAGS_COUNT]);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/commands.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,83 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "commands.h"
+
+ClientCommandFunc client_command_find(const char *name)
+{
+	/* keep the command uppercased */
+	name = str_ucase((char *) t_strdup(name));
+
+	switch (*name) {
+	case 'A':
+		if (strcmp(name, "APPEND") == 0)
+			return cmd_append;
+		if (strcmp(name, "AUTHENTICATE") == 0)
+			return cmd_authenticate;
+		break;
+	case 'C':
+		if (strcmp(name, "CREATE") == 0)
+			return cmd_create;
+		if (strcmp(name, "COPY") == 0)
+			return cmd_copy;
+		if (strcmp(name, "CLOSE") == 0)
+			return cmd_close;
+		if (strcmp(name, "CHECK") == 0)
+			return cmd_copy;
+		if (strcmp(name, "CAPABILITY") == 0)
+			return cmd_capability;
+		break;
+	case 'D':
+		if (strcmp(name, "DELETE") == 0)
+			return cmd_delete;
+		break;
+	case 'E':
+		if (strcmp(name, "EXPUNGE") == 0)
+			return cmd_expunge;
+		if (strcmp(name, "EXAMINE") == 0)
+			return cmd_examine;
+		break;
+	case 'F':
+		if (strcmp(name, "FETCH") == 0)
+			return cmd_fetch;
+		break;
+	case 'L':
+		if (strcmp(name, "LIST") == 0)
+			return cmd_list;
+		if (strcmp(name, "LSUB") == 0)
+			return cmd_lsub;
+		if (strcmp(name, "LOGOUT") == 0)
+			return cmd_logout;
+		if (strcmp(name, "LOGIN") == 0)
+			return cmd_login;
+		break;
+	case 'N':
+		if (strcmp(name, "NOOP") == 0)
+			return cmd_noop;
+		break;
+	case 'R':
+		if (strcmp(name, "RENAME") == 0)
+			return cmd_rename;
+		break;
+	case 'S':
+		if (strcmp(name, "STORE") == 0)
+			return cmd_store;
+		if (strcmp(name, "SEARCH") == 0)
+			return cmd_search;
+		if (strcmp(name, "SELECT") == 0)
+			return cmd_select;
+		if (strcmp(name, "STATUS") == 0)
+			return cmd_status;
+		if (strcmp(name, "SUBSCRIBE") == 0)
+			return cmd_subscribe;
+		break;
+	case 'U':
+		if (strcmp(name, "UID") == 0)
+			return cmd_uid;
+		if (strcmp(name, "UNSUBSCRIBE") == 0)
+			return cmd_unsubscribe;
+		break;
+	}
+
+	return NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/commands.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,44 @@
+#ifndef __COMMANDS_H
+#define __COMMANDS_H
+
+#include "commands-util.h"
+#include "imap-parser.h"
+
+ClientCommandFunc client_command_find(const char *name);
+
+/* Non-Authenticated State */
+int cmd_authenticate(Client *client);
+int cmd_login(Client *client);
+int cmd_logout(Client *client);
+
+int cmd_capability(Client *client);
+int cmd_noop(Client *client);
+
+/* Authenticated State */
+int cmd_select(Client *client);
+int cmd_examine(Client *client);
+
+int cmd_create(Client *client);
+int cmd_delete(Client *client);
+int cmd_rename(Client *client);
+
+int cmd_subscribe(Client *client);
+int cmd_unsubscribe(Client *client);
+
+int cmd_list(Client *client);
+int cmd_lsub(Client *client);
+
+int cmd_status(Client *client);
+int cmd_append(Client *client);
+
+/* Selected state */
+int cmd_check(Client *client);
+int cmd_close(Client *client);
+int cmd_expunge(Client *client);
+int cmd_search(Client *client);
+int cmd_fetch(Client *client);
+int cmd_store(Client *client);
+int cmd_copy(Client *client);
+int cmd_uid(Client *client);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/common.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,9 @@
+#ifndef __COMMON_H
+#define __COMMON_H
+
+#include "lib.h"
+#include "client.h"
+
+extern IOLoop ioloop;
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/main.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,111 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "ioloop.h"
+#include "lib-signals.h"
+#include "restrict-access.h"
+
+#include <stdlib.h>
+#include <syslog.h>
+
+IOLoop ioloop;
+static char log_prefix[64];
+
+static void sig_quit(int signo __attr_unused__)
+{
+	io_loop_stop(ioloop);
+}
+
+static void main_init(int use_syslog)
+{
+	Client *client;
+	MailStorage *storage;
+	const char *logfile, *mail, *tag;
+
+	lib_init_signals(sig_quit);
+
+	i_snprintf(log_prefix, sizeof(log_prefix), "imap(%s)", getenv("USER"));
+
+	logfile = getenv("IMAP_LOGFILE");
+	if (logfile != NULL) {
+		/* log failures into specified log file */
+		i_set_failure_file(logfile, log_prefix);
+		i_set_failure_timestamp_format(DEFAULT_FAILURE_STAMP_FORMAT);
+	} else if (use_syslog) {
+		/* prefix with imapd(user) */
+		openlog(log_prefix, 0, LOG_MAIL);
+
+		i_set_panic_handler(i_syslog_panic_handler);
+		i_set_fatal_handler(i_syslog_fatal_handler);
+		i_set_error_handler(i_syslog_error_handler);
+		i_set_warning_handler(i_syslog_warning_handler);
+	}
+
+	/* do the chrooting etc. */
+	restrict_access_by_env();
+
+	mail_storage_register_all();
+	clients_init();
+
+	mail = getenv("MAIL");
+	if (mail == NULL) {
+		/* support also maildir-specific environment */
+		mail = getenv("MAILDIR");
+		if (mail != NULL)
+			mail = t_strconcat("maildir:", mail, NULL);
+	}
+
+	storage = mail_storage_create_with_data(mail);
+	if (storage == NULL) {
+		/* failed */
+		if (mail != NULL && *mail != '\0')
+			i_fatal("Failed to create storage with data: %s", mail);
+		else {
+			const char *home;
+
+			home = getenv("HOME");
+			if (home == NULL) home = "not set";
+
+			i_fatal("MAIL environment missing and "
+				"autodetection failed (home %s)", home);
+		}
+	} else {
+		client = client_create(0, 1, FALSE, storage);
+
+		tag = getenv("LOGIN_TAG");
+		if (tag == NULL || *tag == '\0')
+			tag = "*";
+
+		client_send_line(client, t_strconcat(tag, " OK Logged in.",
+						     NULL));
+	}
+}
+
+static void main_deinit(void)
+{
+	/* warn about being killed because of some signal, except SIGINT (^C)
+	   which is too common at least while testing :) */
+	if (lib_signal_kill != 0 && lib_signal_kill != 2)
+		i_warning("Killed with signal %d", lib_signal_kill);
+
+	clients_deinit();
+
+	closelog();
+}
+
+int main(int argc, char *argv[])
+{
+	/* NOTE: we start rooted, so keep the code minimal until
+	   restrict_access_by_env() is called */
+	lib_init();
+	ioloop = io_loop_create();
+
+	main_init(argc == 2 && strcmp(argv[1], "-s") == 0);
+        io_loop_run(ioloop);
+	main_deinit();
+
+	io_loop_destroy(ioloop);
+	lib_deinit();
+
+	return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap/.cvsignore	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,8 @@
+*.la
+*.lo
+*.o
+.deps
+.libs
+Makefile
+Makefile.in
+so_locations
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap/Makefile.am	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,23 @@
+noinst_LIBRARIES = libimap.a
+
+INCLUDES = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-mail
+
+libimap_a_SOURCES = \
+	imap-bodystructure.c \
+	imap-envelope.c \
+	imap-match.c \
+	imap-message-cache.c \
+	imap-message-send.c \
+	imap-parser.c \
+	imap-util.c
+
+noinst_HEADERS = \
+	imap-bodystructure.h \
+	imap-envelope.h \
+	imap-match.h \
+	imap-message-cache.h \
+	imap-message-send.h \
+	imap-parser.h \
+	imap-util.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap/imap-bodystructure.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,367 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "temp-string.h"
+#include "rfc822-tokenize.h"
+#include "message-parser.h"
+#include "message-content-parser.h"
+#include "imap-envelope.h"
+#include "imap-bodystructure.h"
+
+#define EMPTY_BODYSTRUCTURE \
+        "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0)"
+
+typedef struct {
+	Pool pool;
+	TempString *str;
+	char *content_type, *content_subtype;
+	char *content_type_params;
+	char *content_transfer_encoding;
+	char *content_id;
+	char *content_description;
+	char *content_disposition;
+	char *content_disposition_params;
+	char *content_md5;
+	char *content_language;
+
+	MessagePartEnvelopeData *envelope;
+} MessagePartBodyData;
+
+static void part_write_bodystructure(MessagePart *part, TempString *str,
+				     int extended);
+
+static void parse_content_type(const Rfc822Token *tokens,
+			       int count, void *user_data)
+{
+        MessagePartBodyData *data = user_data;
+	const char *value;
+	int i;
+
+	/* find the content type separator */
+	for (i = 0; i < count; i++) {
+		if (tokens[i].token == '/')
+			break;
+	}
+
+	value = rfc822_tokens_get_value_quoted(tokens, i, FALSE);
+	data->content_type = p_strdup(data->pool, value);
+
+	value = rfc822_tokens_get_value_quoted(tokens+i+1, count-i-1, FALSE);
+	data->content_subtype = p_strdup(data->pool, value);
+}
+
+static void parse_save_params_list(const Rfc822Token *name,
+				   const Rfc822Token *value, int value_count,
+				   void *user_data)
+{
+        MessagePartBodyData *data = user_data;
+	const char *str;
+
+	if (data->str->len != 0)
+		t_string_append_c(data->str, ' ');
+
+	t_string_append_c(data->str, '"');
+	t_string_append_n(data->str, name->ptr, name->len);
+	t_string_append(data->str, "\" ");
+
+        str = rfc822_tokens_get_value_quoted(value, value_count, FALSE);
+	t_string_append(data->str, str);
+}
+
+static void parse_content_transfer_encoding(const Rfc822Token *tokens,
+					    int count, void *user_data)
+{
+        MessagePartBodyData *data = user_data;
+	const char *value;
+
+	value = rfc822_tokens_get_value_quoted(tokens, count, FALSE);
+	data->content_transfer_encoding = p_strdup(data->pool, value);
+}
+
+static void parse_content_disposition(const Rfc822Token *tokens,
+				      int count, void *user_data)
+{
+        MessagePartBodyData *data = user_data;
+	const char *value;
+
+	value = rfc822_tokens_get_value_quoted(tokens, count, FALSE);
+	data->content_disposition = p_strdup(data->pool, value);
+}
+
+static void parse_content_language(const Rfc822Token *tokens,
+				   int count, void *user_data)
+{
+        MessagePartBodyData *data = user_data;
+	const char *value;
+
+	if (count <= 0)
+		return;
+
+	value = rfc822_tokens_get_value_quoted(tokens, count, FALSE);
+	data->content_language = p_strdup(data->pool, value);
+
+	/* FIXME: a,b,c -> "a" "b" "c" */
+}
+
+static void parse_header(MessagePart *part,
+			 const char *name, unsigned int name_len,
+			 const char *value,
+			 unsigned int value_len, void *user_data)
+{
+	Pool pool = user_data;
+	MessagePartBodyData *part_data;
+	int parent_rfc822;
+
+        parent_rfc822 = part->parent != NULL && part->parent->message_rfc822;
+	if (!parent_rfc822 && (name_len <= 8 ||
+			       strncasecmp(name, "Content-", 8) != 0))
+		return;
+
+	if (part->user_data == NULL) {
+		/* initialize message part data */
+		part->user_data = part_data =
+			p_new(pool, MessagePartBodyData, 1);
+		part_data->pool = pool;
+	}
+	part_data = part->user_data;
+
+	t_push();
+
+	/* fix the name to be \0-terminated */
+	name = t_strndup(name, name_len);
+
+	if (strcasecmp(name, "Content-Type") == 0) {
+		part_data->str = t_string_new(256);
+		(void)message_content_parse_header(t_strndup(value, value_len),
+						   parse_content_type,
+						   parse_save_params_list,
+						   part_data);
+		part_data->content_type_params =
+			p_strdup(pool, part_data->str->str);
+	} else if (strcasecmp(name, "Content-Transfer-Encoding") == 0) {
+		(void)message_content_parse_header(t_strndup(value, value_len),
+						parse_content_transfer_encoding,
+						NULL, part_data);
+	} else if (strcasecmp(name, "Content-ID") == 0) {
+		part_data->content_id = p_strndup(pool, value, value_len);
+	} else if (strcasecmp(name, "Content-Description") == 0) {
+		part_data->content_description =
+			p_strndup(pool, value, value_len);
+	} else if (strcasecmp(name, "Content-Disposition") == 0) {
+		part_data->str = t_string_new(256);
+		(void)message_content_parse_header(t_strndup(value, value_len),
+						   parse_content_disposition,
+						   parse_save_params_list,
+						   part_data);
+		part_data->content_disposition_params =
+			p_strdup(pool, part_data->str->str);
+	} else if (strcasecmp(name, "Content-Language") == 0) {
+		(void)message_content_parse_header(t_strndup(value, value_len),
+						   parse_content_language, NULL,
+						   part_data);
+	} else if (strcasecmp(name, "Content-MD5") == 0) {
+		part_data->content_md5 = p_strndup(pool, value, value_len);
+	} else if (parent_rfc822) {
+		/* message/rfc822, we need the envelope */
+		imap_envelope_parse_header(pool, &part_data->envelope,
+					   name, value, value_len);
+	}
+	t_pop();
+}
+
+static void part_parse_headers(MessagePart *part, const char *msg,
+			       size_t size, Pool pool)
+{
+	while (part != NULL) {
+		/* note that we want to parse the header of all
+		   the message parts, multiparts too. */
+		message_parse_header(part, msg + part->pos.physical_pos,
+				     part->header_size.physical_size,
+				     NULL, parse_header, pool);
+
+		if (part->children != NULL)
+			part_parse_headers(part->children, msg, size, pool);
+
+		part = part->next;
+	}
+}
+
+static void part_write_body_multipart(MessagePart *part, TempString *str,
+				      int extended)
+{
+	MessagePartBodyData *data = part->user_data;
+
+	if (part->children != NULL)
+		part_write_bodystructure(part->children, str, extended);
+	else {
+		/* no parts in multipart message,
+		   that's not allowed. write a single
+		   0-length text/plain structure */
+		t_string_append(str, EMPTY_BODYSTRUCTURE);
+	}
+
+	t_string_append_c(str, ' ');
+	t_string_append(str, data->content_subtype);
+
+	if (!extended)
+		return;
+
+	/* BODYSTRUCTURE data */
+	t_string_append_c(str, ' ');
+	if (data->content_type_params == NULL)
+		t_string_append(str, "NIL");
+	else {
+		t_string_append_c(str, '(');
+		t_string_append(str, data->content_type_params);
+		t_string_append_c(str, ')');
+	}
+
+	t_string_append_c(str, ' ');
+	if (data->content_disposition == NULL)
+		t_string_append(str, "NIL");
+	else {
+		t_string_append_c(str, '(');
+		t_string_append(str, data->content_disposition);
+		if (data->content_disposition_params != NULL) {
+			t_string_append(str, " (");
+			t_string_append(str, data->content_disposition_params);
+			t_string_append_c(str, ')');
+		}
+		t_string_append_c(str, ')');
+	}
+
+	t_string_append_c(str, ' ');
+	if (data->content_language == NULL)
+		t_string_append(str, "NIL");
+	else {
+		t_string_append_c(str, '(');
+		t_string_append(str, data->content_language);
+		t_string_append_c(str, ')');
+	}
+}
+
+static void part_write_body(MessagePart *part, TempString *str, int extended)
+{
+	MessagePartBodyData *data = part->user_data;
+
+	if (data == NULL) {
+		/* there was no content headers, use an empty structure */
+		data = t_new(MessagePartBodyData, 1);
+	}
+
+	/* "content type" "subtype" */
+	t_string_append(str, NVL(data->content_type, "\"text\""));
+	t_string_append_c(str, ' ');
+	t_string_append(str, NVL(data->content_subtype, "\"plain\""));
+
+	/* ("content type param key" "value" ...) */
+	t_string_append_c(str, ' ');
+	if (data->content_type_params == NULL)
+		t_string_append(str, "NIL");
+	else {
+		t_string_append_c(str, '(');
+		t_string_append(str, data->content_type_params);
+		t_string_append_c(str, ')');
+	}
+
+	t_string_printfa(str, " %s %s %s %lu",
+			 NVL(data->content_id, "NIL"),
+			 NVL(data->content_description, "NIL"),
+			 NVL(data->content_transfer_encoding, "\"8bit\""),
+			 (unsigned long) part->body_size.virtual_size);
+
+	if (part->text) {
+		/* text/.. contains line count */
+		t_string_printfa(str, " %u", part->body_size.lines);
+	} else if (part->message_rfc822) {
+		/* message/rfc822 contains envelope + body + line count */
+		MessagePartBodyData *child_data;
+
+		i_assert(part->children != NULL);
+		i_assert(part->children->next == NULL);
+
+                child_data = part->children->user_data;
+
+		t_string_append_c(str, ' ');
+		if (child_data != NULL && child_data->envelope != NULL) {
+			imap_envelope_write_part_data(child_data->envelope,
+						      str);
+		} else {
+			/* buggy message */
+			t_string_append(str, "NIL");
+		}
+		t_string_append_c(str, ' ');
+		part_write_bodystructure(part->children, str, extended);
+		t_string_printfa(str, " %u", part->body_size.lines);
+	}
+
+	if (!extended)
+		return;
+
+	/* BODYSTRUCTURE data */
+
+	/* "md5" ("content disposition" ("disposition" "params"))
+	   ("body" "language" "params") */
+	t_string_append_c(str, ' ');
+	t_string_append(str, NVL(data->content_md5, "NIL"));
+
+	t_string_append_c(str, ' ');
+	if (data->content_disposition == NULL)
+		t_string_append(str, "NIL");
+	else {
+		t_string_append_c(str, '(');
+		t_string_append(str, data->content_disposition);
+		t_string_append_c(str, ')');
+
+		if (data->content_disposition_params != NULL) {
+			t_string_append(str, " (");
+			t_string_append(str, data->content_disposition_params);
+			t_string_append_c(str, ')');
+		}
+	}
+
+	t_string_append_c(str, ' ');
+	if (data->content_language == NULL)
+		t_string_append(str, "NIL");
+	else {
+		t_string_append_c(str, '(');
+		t_string_append(str, data->content_language);
+		t_string_append_c(str, ')');
+	}
+}
+
+static void part_write_bodystructure(MessagePart *part, TempString *str,
+				     int extended)
+{
+	while (part != NULL) {
+		t_string_append_c(str, '(');
+		if (part->multipart)
+			part_write_body_multipart(part, str, extended);
+		else
+			part_write_body(part, str, extended);
+
+		part = part->next;
+		t_string_append_c(str, ')');
+	}
+}
+
+static const char *part_get_bodystructure(MessagePart *part, int extended)
+{
+	TempString *str;
+
+	str = t_string_new(2048);
+	part_write_bodystructure(part, str, extended);
+	return str->str;
+}
+
+const char *imap_part_get_bodystructure(Pool pool, MessagePart **part,
+					const char *msg, size_t size,
+					int extended)
+{
+	if (*part == NULL)
+		*part = message_parse(pool, msg, size, parse_header, pool);
+	else
+		part_parse_headers(*part, msg, size, pool);
+
+	return part_get_bodystructure(*part, extended);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap/imap-bodystructure.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,10 @@
+#ifndef __IMAP_BODYSTRUCTURE_H
+#define __IMAP_BODYSTRUCTURE_H
+
+/* If *part is non-NULL, it's used as base for building the body structure.
+   Otherwise it's set to the root MessagePart and parsed. */
+const char *imap_part_get_bodystructure(Pool pool, MessagePart **part,
+					const char *msg, size_t size,
+					int extended);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap/imap-envelope.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,160 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "temp-string.h"
+#include "rfc822-address.h"
+#include "imap-envelope.h"
+
+struct _MessagePartEnvelopeData {
+	Pool pool;
+
+	char *date, *subject;
+	Rfc822Address *from, *sender, *reply_to;
+	Rfc822Address *to, *cc, *bcc;
+
+	char *in_reply_to, *message_id;
+};
+
+static const char *t_buffer_get_quote(const char *value,
+				      unsigned int *value_len)
+{
+	char *buf, *p;
+	unsigned int i, len;
+
+	len = *value_len;
+	p = buf = t_buffer_get(len * 2 + 3);
+	*p++ = '"';
+	for (i = 0; i < len; i++) {
+		if (value[i] == '\\' || value[i] == '"')
+			*p++ = '\\';
+		*p++ = value[i];
+	}
+	*p++ = '"';
+	*p++ = '\0';
+
+	*value_len = (unsigned int) (p-buf);
+	return buf;
+}
+
+static const char *quote_str_nil(const char *value)
+{
+	const char *buf;
+	unsigned int value_len;
+
+	if (value == NULL)
+		return "NIL";
+
+	value_len = strlen(value);
+	buf = t_buffer_get_quote(value, &value_len);
+	t_buffer_alloc((unsigned int) value_len);
+	return buf;
+}
+
+static char *quote_value(Pool pool, const char *value, unsigned int value_len)
+{
+	const char *buf;
+
+	buf = t_buffer_get_quote(value, &value_len);
+	return p_strndup(pool, buf, value_len);
+}
+
+static Rfc822Address *parse_address(Pool pool, const char *value,
+				    unsigned int value_len)
+{
+	Rfc822Address *ret;
+
+	t_push();
+	ret = rfc822_address_parse(pool, t_strndup(value, value_len));
+	t_pop();
+	return ret;
+}
+
+void imap_envelope_parse_header(Pool pool, MessagePartEnvelopeData **data,
+				const char *name,
+				const char *value, unsigned int value_len)
+{
+	if (*data == NULL) {
+		*data = p_new(pool, MessagePartEnvelopeData, 1);
+		(*data)->pool = pool;
+	}
+
+	if (strcasecmp(name, "Date") == 0)
+		(*data)->date = quote_value(pool, value, value_len);
+	else if (strcasecmp(name, "Subject") == 0)
+		(*data)->subject = quote_value(pool, value, value_len);
+	else if (strcasecmp(name, "From") == 0)
+		(*data)->from = parse_address(pool, value, value_len);
+	else if (strcasecmp(name, "Sender") == 0)
+		(*data)->sender = parse_address(pool, value, value_len);
+	else if (strcasecmp(name, "Reply-To") == 0)
+		(*data)->reply_to = parse_address(pool, value, value_len);
+	else if (strcasecmp(name, "To") == 0)
+		(*data)->to = parse_address(pool, value, value_len);
+	else if (strcasecmp(name, "Cc") == 0)
+		(*data)->cc = parse_address(pool, value, value_len);
+	else if (strcasecmp(name, "Bcc") == 0)
+		(*data)->bcc = parse_address(pool, value, value_len);
+	else if (strcasecmp(name, "In-Reply-To") == 0)
+		(*data)->in_reply_to = quote_value(pool, value, value_len);
+	else if (strcasecmp(name, "Message-Id") == 0)
+		(*data)->message_id = quote_value(pool, value, value_len);
+}
+
+static void imap_write_address(TempString *str, Rfc822Address *addr)
+{
+	if (addr == NULL) {
+		t_string_append(str, "NIL");
+		return;
+	}
+
+	t_string_append_c(str, '(');
+	while (addr != NULL) {
+		t_string_append_c(str, '(');
+		t_string_append(str, quote_str_nil(addr->name));
+		t_string_append_c(str, ' ');
+		t_string_append(str, quote_str_nil(addr->route));
+		t_string_append_c(str, ' ');
+		t_string_append(str, quote_str_nil(addr->mailbox));
+		t_string_append_c(str, ' ');
+		t_string_append(str, quote_str_nil(addr->domain));
+		t_string_append_c(str, ')');
+
+		addr = addr->next;
+	}
+	t_string_append_c(str, ')');
+}
+
+void imap_envelope_write_part_data(MessagePartEnvelopeData *data,
+				   TempString *str)
+{
+	t_string_append(str, NVL(data->date, "NIL"));
+	t_string_append_c(str, ' ');
+	t_string_append(str, NVL(data->subject, "NIL"));
+
+	t_string_append_c(str, ' ');
+	imap_write_address(str, data->from);
+	t_string_append_c(str, ' ');
+	imap_write_address(str, NVL(data->sender, data->from));
+	t_string_append_c(str, ' ');
+	imap_write_address(str, NVL(data->reply_to, data->from));
+	t_string_append_c(str, ' ');
+	imap_write_address(str, data->to);
+	t_string_append_c(str, ' ');
+	imap_write_address(str, data->cc);
+	t_string_append_c(str, ' ');
+	imap_write_address(str, data->bcc);
+
+	t_string_append_c(str, ' ');
+	t_string_append(str, NVL(data->in_reply_to, "NIL"));
+	t_string_append_c(str, ' ');
+	t_string_append(str, NVL(data->message_id, "NIL"));
+}
+
+const char *imap_envelope_get_part_data(MessagePartEnvelopeData *data)
+{
+	TempString *str;
+
+	str = t_string_new(2048);
+        imap_envelope_write_part_data(data, str);
+	return str->str;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap/imap-envelope.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,14 @@
+#ifndef __IMAP_ENVELOPE_H
+#define __IMAP_ENVELOPE_H
+
+typedef struct _MessagePartEnvelopeData MessagePartEnvelopeData;
+
+void imap_envelope_parse_header(Pool pool, MessagePartEnvelopeData **data,
+				const char *name,
+				const char *value, unsigned int value_len);
+
+void imap_envelope_write_part_data(MessagePartEnvelopeData *data,
+				   TempString *str);
+const char *imap_envelope_get_part_data(MessagePartEnvelopeData *data);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap/imap-match.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,240 @@
+/* Stripped down version of Cyrus imapd's glob.c
+ *
+ * Copyright (c) 1998-2000 Carnegie Mellon University.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer. 
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The name "Carnegie Mellon University" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For permission or any other legal
+ *    details, please contact  
+ *      Office of Technology Transfer
+ *      Carnegie Mellon University
+ *      5000 Forbes Avenue
+ *      Pittsburgh, PA  15213-3890
+ *      (412) 268-4387, fax: (412) 268-7395
+ *      tech-transfer@andrew.cmu.edu
+ *
+ * 4. Redistributions of any form whatsoever must retain the following
+ *    acknowledgment:
+ *    "This product includes software developed by Computing Services
+ *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
+ *
+ * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
+ * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
+ * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Author: Chris Newman
+ * Start Date: 4/5/93
+ */
+/*
+ * $Id$
+ */
+
+#include "lib.h"
+#include "imap-match.h"
+
+#include <ctype.h>
+
+struct _ImapMatchGlob {
+    int inboxcase;
+    const char *gstar, *ghier, *gptr;	/* INBOX prefix comparison state */
+    char sep_char;		/* separator character */
+    char inbox[6];		/* INBOX in the correct case */
+    char str[1];		/* glob string */
+};
+
+/* name of "INBOX" -- must have no repeated substrings */
+static char inbox[] = "INBOX";
+#define INBOXLEN (sizeof (inbox) - 1)
+
+/* initialize globbing structure
+ *  This makes the following changes to the input string:
+ *   1) '*' eats all '*'s and '%'s connected by any wildcard
+ *   2) '%' eats all adjacent '%'s
+ */
+const ImapMatchGlob *imap_match_init(const char *str, int inboxcase,
+				     char separator)
+{
+    ImapMatchGlob *g;
+    char *dst;
+
+    g = t_malloc(sizeof(ImapMatchGlob) + strlen(str) + 1);
+
+    strcpy(g->inbox, inbox);
+    g->sep_char = separator;
+    dst = g->str;
+    while (*str) {
+	if (*str == '*' || *str == '%') {
+	    /* remove duplicate hierarchy match (5) */
+	    while (*str == '%') ++str;
+	    /* If we found a '*', treat '%' as '*' (4) */
+	    if (*str == '*') {
+		/* remove duplicate wildcards (4) */
+		while (*str == '*' || (*str == '%' && str[1])) ++str;
+		*dst++ = '*';
+	    } else {
+		*dst++ = '%';
+	    }
+	} else {
+	    *dst++ = *str++;
+	}
+    }
+    *dst++ = '\0';
+
+    /* pre-match "INBOX" to the pattern case insensitively and save state
+     * also keep track of the matching case for "INBOX"
+     * NOTE: this only works because "INBOX" has no repeated substrings
+     */
+    if (inboxcase) {
+	g->inboxcase = TRUE,
+	str = g->str;
+	dst = g->inbox;
+	g->gstar = g->ghier = NULL;
+	do {
+	    while (*dst && i_toupper(*str) == i_toupper(*dst)) {
+		*dst++ = *str++;
+	    }
+	    if (*str == '*') g->gstar = ++str, g->ghier = 0;
+	    else if (*str == '%') g->ghier = ++str;
+	    else break;
+	    if (*str != '%') {
+		while (*dst && i_toupper(*str) != i_toupper(*dst)) ++dst;
+	    }
+	} while (*str && *dst);
+	g->gptr = str;
+	if (*dst) g->inboxcase = FALSE;
+    }
+
+    return (g);
+}
+
+/* returns -1 if no match, otherwise length of match or partial-match
+ *  g         pre-processed glob string
+ *  ptr       string to perform glob on
+ *  len       length of ptr string
+ *  min       pointer to minimum length of a valid partial-match
+ *            set to return value + 1 on partial match, otherwise -1
+ *            if NULL, partial matches not allowed
+ */
+int imap_match(const ImapMatchGlob *glob, const char *ptr,
+	       int len, int *min)
+{
+    const char *gptr, *pend;	/* glob pointer, end of ptr string */
+    const char *gstar, *pstar;	/* pointers for '*' patterns */
+    const char *ghier, *phier;	/* pointers for '%' patterns */
+    const char *start;		/* start of input string */
+
+    /* check for remaining partial matches */
+    if (min && *min < 0) return (-1);
+
+    /* get length */
+    if (!len) len = strlen(ptr);
+
+    /* initialize globbing */
+    gptr = glob->str;
+    start = ptr;
+    pend = ptr + len;
+    gstar = ghier = NULL;
+    phier = pstar = NULL;	/* initialize to eliminate warnings */
+
+    /* check for INBOX prefix */
+    if (glob->inboxcase && strncmp(ptr, inbox, INBOXLEN) == 0) {
+	pstar = phier = ptr += INBOXLEN;
+	gstar = glob->gstar;
+	ghier = glob->ghier;
+	gptr = glob->gptr;
+    }
+
+    /* main globbing loops */
+    /* case sensitive version */
+
+    /* loop to manage wildcards */
+    do {
+	/* see if we match to the next '%' or '*' wildcard */
+	while (*gptr != '*' && *gptr != '%' && ptr != pend && *gptr == *ptr) {
+	    ++ptr, ++gptr;
+	}
+	if (*gptr == '\0' && ptr == pend) break;
+	if (*gptr == '*') {
+	    ghier = NULL;
+	    gstar = ++gptr;
+	    pstar = ptr;
+	}
+	if (*gptr == '%') {
+	    ghier = ++gptr;
+	    phier = ptr;
+	}
+	if (ghier) {
+	    /* look for a match with first char following '%',
+	     * stop at a sep_char unless we're doing "*%"
+	     */
+	    ptr = phier;
+	    while (ptr != pend && *ghier != *ptr
+		   && (*ptr != glob->sep_char ||
+		       (!*ghier && gstar && *gstar == '%' && min
+			&& ptr - start < *min))) {
+		++ptr;
+	    }
+	    if (ptr == pend) {
+		gptr = ghier;
+		break;
+	    }
+	    if (*ptr == glob->sep_char && *ptr != *ghier) {
+		if (!*ghier && min
+		    && *min < ptr - start && ptr != pend
+		    && *ptr == glob->sep_char
+		    ) {
+		    *min = gstar ? ptr - start + 1 : -1;
+		    return (ptr - start);
+		}
+		gptr = ghier;
+		ghier = NULL;
+	    } else {
+		phier = ++ptr;
+		gptr = ghier + 1;
+	    }
+	}
+	if (gstar && !ghier) {
+	    if (!*gstar) {
+		ptr = pend;
+		break;
+	    }
+	    /* look for a match with first char following '*' */
+	    while (pstar != pend && *gstar != *pstar) ++pstar;
+	    if (pstar == pend) {
+		gptr = gstar;
+		break;
+	    }
+	    ptr = ++pstar;
+	    gptr = gstar + 1;
+	}
+	if (*gptr == '\0' && min && *min < ptr - start && ptr != pend &&
+	    *ptr == glob->sep_char) {
+	    /* The pattern ended on a hierarchy separator
+	     * return a partial match */
+	    *min = ptr - start + 1;
+	    return ptr - start;
+	}
+
+	/* continue if at wildcard or we passed an asterisk */
+    } while (*gptr == '*' || *gptr == '%' ||
+	     ((gstar || ghier) && (*gptr || ptr != pend)));
+
+    if (min) *min = -1;
+    return (*gptr == '\0' && ptr == pend ? ptr - start : -1);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap/imap-match.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,22 @@
+#ifndef __IMAP_MATCH_H
+#define __IMAP_MATCH_H
+
+typedef struct _ImapMatchGlob ImapMatchGlob;
+
+/* If inboxcase is TRUE, the "INBOX" string at the beginning of line is
+   compared case-insensitively */
+const ImapMatchGlob *imap_match_init(const char *str, int inboxcase,
+				     char separator);
+
+/* returns -1 if no match, otherwise length of match or partial-match
+ *  glob      pre-processed glob string
+ *  ptr       string to perform glob on
+ *  len       length of ptr string (if 0, strlen() is used)
+ *  min       pointer to minimum length of a valid partial-match.
+ *            Set to -1 if no more matches.  Set to return value + 1
+ *     	      if another match is possible.  If NULL, no partial-matches
+ *            are returned.
+ */
+int imap_match(const ImapMatchGlob *glob, const char *ptr, int len, int *min);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap/imap-message-cache.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,656 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "temp-string.h"
+#include "mmap-util.h"
+#include "message-parser.h"
+#include "message-size.h"
+#include "imap-bodystructure.h"
+#include "imap-envelope.h"
+#include "imap-message-cache.h"
+
+#include <unistd.h>
+
+/* It's not very useful to cache lots of messages, as they're mostly wanted
+   just once. The biggest reason for this cache to exist is to get just the
+   latest message. */
+#define MAX_CACHED_MESSAGES 16
+
+#define DEFAULT_MESSAGE_POOL_SIZE 4096
+
+typedef struct _CachedMessage CachedMessage;
+
+struct _CachedMessage {
+	CachedMessage *next;
+
+	Pool pool;
+	unsigned int uid;
+
+	MessagePart *part;
+	MessageSize *hdr_size;
+	MessageSize *body_size;
+	MessageSize *partial_size;
+
+	char *cached_body;
+	char *cached_bodystructure;
+	char *cached_envelope;
+
+	MessagePartEnvelopeData *envelope;
+};
+
+struct _ImapMessageCache {
+	CachedMessage *messages;
+	int messages_count;
+
+	CachedMessage *open_msg;
+	int open_fd;
+	void *open_mmap_base;
+	const char *open_msg_data;
+	off_t open_start_pos;
+	size_t open_mmap_size, open_size, open_virtual_size;
+};
+
+ImapMessageCache *imap_msgcache_alloc(void)
+{
+	ImapMessageCache *cache;
+
+	cache = i_new(ImapMessageCache, 1);
+	cache->open_fd = -1;
+	return cache;
+}
+
+static void cached_message_free(CachedMessage *msg)
+{
+	pool_unref(msg->pool);
+}
+
+static void cache_close_msg(ImapMessageCache *cache)
+{
+	if (cache->open_mmap_base != NULL) {
+		(void)munmap(cache->open_mmap_base, cache->open_mmap_size);
+		cache->open_mmap_base = NULL;
+		cache->open_msg_data = NULL;
+	}
+
+	if (cache->open_fd != -1) {
+		(void)close(cache->open_fd);
+		cache->open_fd = -1;
+	}
+
+	cache->open_msg = NULL;
+	cache->open_size = 0;
+	cache->open_start_pos = 0;
+}
+
+void imap_msgcache_clear(ImapMessageCache *cache)
+{
+	CachedMessage *next;
+
+	cache_close_msg(cache);
+
+	while (cache->messages != NULL) {
+		next = cache->messages->next;
+		cached_message_free(cache->messages);
+		cache->messages = next;
+	}
+}
+
+void imap_msgcache_free(ImapMessageCache *cache)
+{
+	imap_msgcache_clear(cache);
+	i_free(cache);
+}
+
+static CachedMessage *cache_new(ImapMessageCache *cache, unsigned int uid)
+{
+	CachedMessage *msg, **msgp;
+	Pool pool;
+
+	if (cache->messages_count < MAX_CACHED_MESSAGES)
+		cache->messages_count++;
+	else {
+		/* remove the last message from cache */
+                msgp = &cache->messages;
+		while ((*msgp)->next != NULL)
+			msgp = &(*msgp)->next;
+
+		cached_message_free(*msgp);
+		*msgp = NULL;
+	}
+
+	pool = pool_create("CachedMessage", DEFAULT_MESSAGE_POOL_SIZE, FALSE);
+
+	msg = p_new(pool, CachedMessage, 1);
+	msg->pool = pool;
+	msg->uid = uid;
+
+	msg->next = cache->messages;
+	cache->messages = msg;
+	return msg;
+}
+
+static void parse_envelope_header(MessagePart *part,
+				  const char *name, unsigned int name_len,
+				  const char *value, unsigned int value_len,
+				  void *user_data)
+{
+	CachedMessage *msg = user_data;
+
+	if (part == NULL || part->parent == NULL) {
+		/* parse envelope headers if we're at the root message part */
+		imap_envelope_parse_header(msg->pool, &msg->envelope,
+					   t_strndup(name, name_len),
+					   value, value_len);
+	}
+}
+
+static CachedMessage *cache_find(ImapMessageCache *cache, unsigned int uid)
+{
+	CachedMessage *msg;
+
+	for (msg = cache->messages; msg != NULL; msg = msg->next) {
+		if (msg->uid == uid)
+			return msg;
+	}
+
+	return NULL;
+}
+
+int imap_msgcache_is_cached(ImapMessageCache *cache, unsigned int uid,
+			    ImapCacheField fields)
+{
+	CachedMessage *msg;
+
+	if (cache->open_msg != NULL && cache->open_msg->uid == uid)
+		return TRUE;
+
+	/* not open, see if the wanted fields are cached */
+	msg = cache_find(cache, uid);
+	if (msg == NULL)
+		return FALSE;
+
+	if ((fields & IMAP_CACHE_BODY) && msg->cached_body == NULL)
+		return FALSE;
+	if ((fields & IMAP_CACHE_BODYSTRUCTURE) &&
+	    msg->cached_bodystructure == NULL)
+		return FALSE;
+	if ((fields & IMAP_CACHE_ENVELOPE) && msg->cached_envelope == NULL)
+		return FALSE;
+
+	if ((fields & IMAP_CACHE_MESSAGE_OPEN) && msg != cache->open_msg)
+		return FALSE;
+	if ((fields & IMAP_CACHE_MESSAGE_PART) && msg->part == NULL)
+		return FALSE;
+	if ((fields & IMAP_CACHE_MESSAGE_HDR_SIZE) && msg->hdr_size == NULL)
+		return FALSE;
+	if ((fields & IMAP_CACHE_MESSAGE_BODY_SIZE) && msg->body_size == NULL)
+		return FALSE;
+
+	return TRUE;
+}
+
+/* Caches the fields for given message if possible */
+static void cache_fields(ImapMessageCache *cache, CachedMessage *msg,
+			 ImapCacheField fields)
+{
+	const char *value;
+
+	t_push();
+	if ((fields & IMAP_CACHE_BODY) && msg->cached_body == NULL &&
+	    msg == cache->open_msg) {
+		value = imap_part_get_bodystructure(msg->pool, &msg->part,
+						    cache->open_msg_data,
+						    cache->open_size, FALSE);
+		msg->cached_body = p_strdup(msg->pool, value);
+	}
+
+	if ((fields & IMAP_CACHE_BODYSTRUCTURE) &&
+	    msg->cached_bodystructure == NULL && msg == cache->open_msg) {
+		value = imap_part_get_bodystructure(msg->pool, &msg->part,
+						    cache->open_msg_data,
+						    cache->open_size, TRUE);
+		msg->cached_bodystructure = p_strdup(msg->pool, value);
+	}
+
+	if ((fields & IMAP_CACHE_MESSAGE_PART) && msg->part == NULL &&
+	    msg == cache->open_msg) {
+		/* we need to parse the message */
+		MessageHeaderFunc func;
+
+		if ((fields & IMAP_CACHE_ENVELOPE) &&
+		    msg->cached_envelope == NULL) {
+			/* we need envelope too, fill the info
+			   while parsing headers */
+			func = parse_envelope_header;
+		} else {
+			func = NULL;
+		}
+
+		msg->part = message_parse(msg->pool, cache->open_msg_data,
+					  cache->open_size, func, msg);
+	}
+
+	if ((fields & IMAP_CACHE_ENVELOPE) && msg->cached_envelope == NULL) {
+		if (msg->envelope == NULL && msg == cache->open_msg) {
+			/* envelope isn't parsed yet, do it. header size
+			   is calculated anyway so save it */
+			if (msg->hdr_size == NULL) {
+				msg->hdr_size = p_new(msg->pool,
+						      MessageSize, 1);
+			}
+			message_parse_header(NULL, cache->open_msg_data,
+					     cache->open_size, msg->hdr_size,
+					     parse_envelope_header, msg);
+		}
+
+		if (msg->envelope != NULL) {
+			value = imap_envelope_get_part_data(msg->envelope);
+			msg->cached_envelope = p_strdup(msg->pool, value);
+		}
+	}
+
+	if ((fields & IMAP_CACHE_MESSAGE_BODY_SIZE) &&
+	    msg->body_size == NULL &&
+	    (msg == cache->open_msg || msg->part != NULL)) {
+		/* fill the body size, and while at it fill the header size
+		   as well */
+		if (msg->hdr_size == NULL)
+			msg->hdr_size = p_new(msg->pool, MessageSize, 1);
+		msg->body_size = p_new(msg->pool, MessageSize, 1);
+
+		if (msg->part != NULL) {
+			/* easy, get it from root part */
+			*msg->hdr_size = msg->part->header_size;
+			*msg->body_size = msg->part->body_size;
+		} else {
+			/* first get the header's size, then calculate the
+			   body size from it and the total virtual size */
+			message_get_header_size(cache->open_msg_data,
+						cache->open_size,
+						msg->hdr_size);
+
+			msg->body_size->lines = 0;
+			msg->body_size->physical_size = cache->open_size -
+				msg->hdr_size->physical_size;
+			msg->body_size->virtual_size =
+				cache->open_virtual_size -
+				msg->hdr_size->virtual_size;
+		}
+	}
+
+	if ((fields & IMAP_CACHE_MESSAGE_HDR_SIZE) && msg->hdr_size == NULL &&
+	    (msg == cache->open_msg || msg->part != NULL)) {
+		msg->hdr_size = p_new(msg->pool, MessageSize, 1);
+
+		if (msg->part != NULL) {
+			/* easy, get it from root part */
+			*msg->hdr_size = msg->part->header_size;
+		} else {
+			/* need to do some light parsing */
+			message_get_header_size(cache->open_msg_data,
+						cache->open_size,
+						msg->hdr_size);
+		}
+	}
+
+	t_pop();
+}
+
+static int cache_mmap(ImapMessageCache *cache)
+{
+	if (cache->open_mmap_base == NULL) {
+		cache->open_mmap_base =
+			mmap_aligned(cache->open_fd, PROT_READ,
+				     cache->open_start_pos, cache->open_size,
+				     (void **) &cache->open_msg_data,
+				     &cache->open_mmap_size);
+		if (cache->open_mmap_base == MAP_FAILED) {
+			i_error("mmap() failed for msg %u: %m",
+				cache->open_msg->uid);
+			return FALSE;
+		}
+	}
+	return TRUE;
+}
+
+void imap_msgcache_message(ImapMessageCache *cache, unsigned int uid,
+			   int fd, off_t offset, size_t size,
+			   size_t virtual_size, size_t pv_headers_size,
+			   size_t pv_body_size, ImapCacheField fields)
+{
+	CachedMessage **pos, *msg;
+
+	i_assert(fd != -1);
+
+	if (cache->open_msg == NULL || cache->open_msg->uid != uid) {
+		cache_close_msg(cache);
+
+                pos = &cache->messages;
+		for (; *pos != NULL; pos = &(*pos)->next) {
+			if ((*pos)->uid == uid)
+				break;
+		}
+
+		if (*pos == NULL) {
+			/* not found, add it */
+			msg = cache_new(cache, uid);
+		} else if (*pos != cache->messages) {
+			/* move it to first in list */
+			msg = *pos;
+			*pos = msg->next;
+
+			msg->next = cache->messages;
+			cache->messages = msg;
+		} else {
+			msg = *pos;
+		}
+
+		cache->open_msg = msg;
+		cache->open_fd = fd;
+		cache->open_size = size;
+		cache->open_virtual_size = virtual_size;
+		cache->open_start_pos = offset;
+
+		if (!cache_mmap(cache)) {
+			cache_close_msg(cache);
+			return;
+		}
+	}
+
+	msg = cache->open_msg;
+
+	if (pv_headers_size != 0 && msg->hdr_size == NULL) {
+		/* physical size == virtual size */
+		msg->hdr_size = p_new(msg->pool, MessageSize, 1);
+		msg->hdr_size->physical_size = msg->hdr_size->virtual_size =
+			pv_headers_size;
+	}
+
+	if (pv_body_size != 0 && msg->body_size == NULL) {
+		/* physical size == virtual size */
+		msg->body_size = p_new(msg->pool, MessageSize, 1);
+		msg->body_size->physical_size = msg->body_size->virtual_size =
+			pv_body_size;
+	}
+
+	cache_fields(cache, msg, fields);
+}
+
+void imap_msgcache_set(ImapMessageCache *cache, unsigned int uid,
+		       ImapCacheField field, const char *value)
+{
+	CachedMessage *msg;
+
+	msg = cache_find(cache, uid);
+	if (msg == NULL)
+		msg = cache_new(cache, uid);
+
+	switch (field) {
+	case IMAP_CACHE_BODY:
+		msg->cached_body = p_strdup(msg->pool, value);
+		break;
+	case IMAP_CACHE_BODYSTRUCTURE:
+		msg->cached_bodystructure = p_strdup(msg->pool, value);
+		break;
+	case IMAP_CACHE_ENVELOPE:
+		msg->cached_envelope = p_strdup(msg->pool, value);
+		break;
+	default:
+		i_assert(0);
+	}
+}
+
+const char *imap_msgcache_get(ImapMessageCache *cache, unsigned int uid,
+			      ImapCacheField field)
+{
+	CachedMessage *msg;
+
+	msg = cache_find(cache, uid);
+	if (msg == NULL)
+		return NULL;
+
+	switch (field) {
+	case IMAP_CACHE_BODY:
+		if (msg->cached_body == NULL)
+			cache_fields(cache, msg, field);
+		return msg->cached_body;
+	case IMAP_CACHE_BODYSTRUCTURE:
+		if (msg->cached_bodystructure == NULL)
+			cache_fields(cache, msg, field);
+		return msg->cached_bodystructure;
+	case IMAP_CACHE_ENVELOPE:
+		if (msg->cached_envelope == NULL)
+			cache_fields(cache, msg, field);
+		return msg->cached_envelope;
+	default:
+		i_assert(0);
+	}
+
+	return NULL;
+}
+
+MessagePart *imap_msgcache_get_parts(ImapMessageCache *cache, unsigned int uid)
+{
+	CachedMessage *msg;
+
+	msg = cache_find(cache, uid);
+	if (msg == NULL)
+		return NULL;
+
+	if (msg->part == NULL)
+		cache_fields(cache, msg, IMAP_CACHE_MESSAGE_PART);
+	return msg->part;
+}
+
+int imap_msgcache_get_rfc822(ImapMessageCache *cache, unsigned int uid,
+			     MessageSize *hdr_size, MessageSize *body_size,
+			     const char **data, int *fd)
+{
+	CachedMessage *msg;
+
+	if (data != NULL || fd != NULL) {
+		if (cache->open_msg == NULL || cache->open_msg->uid != uid)
+			return FALSE;
+
+		msg = cache->open_msg;
+		if (data != NULL)
+			*data = cache->open_msg_data;
+		if (fd != NULL) {
+			*fd = cache->open_fd;
+			if (*fd != -1 && lseek(*fd, cache->open_start_pos,
+					       SEEK_SET) == (off_t)-1)
+				*fd = -1;
+		}
+	} else {
+		msg = cache_find(cache, uid);
+		if (msg == NULL)
+			return FALSE;
+	}
+
+	if (body_size != NULL) {
+		if (msg->body_size == NULL)
+			cache_fields(cache, msg, IMAP_CACHE_MESSAGE_BODY_SIZE);
+		if (msg->body_size == NULL)
+			return FALSE;
+		*body_size = *msg->body_size;
+	}
+
+	if (hdr_size != NULL) {
+		if (msg->hdr_size == NULL)
+			cache_fields(cache, msg, IMAP_CACHE_MESSAGE_HDR_SIZE);
+		if (msg->hdr_size == NULL)
+			return FALSE;
+		*hdr_size = *msg->hdr_size;
+	} else {
+		/* header isn't wanted, skip it */
+		if (data != NULL)
+			*data += msg->hdr_size->physical_size;
+		if (fd != NULL) {
+			if (lseek(*fd, (off_t) msg->hdr_size->physical_size,
+				  SEEK_CUR) == (off_t)-1)
+				return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+static void get_partial_size(const char *msg, size_t max_physical_size,
+			     off_t virtual_skip, size_t max_virtual_size,
+			     MessageSize *partial, MessageSize *dest)
+{
+	const char *msg_start, *msg_end, *cr;
+
+	msg_end = msg + max_physical_size;
+
+	/* see if we can use the existing partial */
+	if (partial->virtual_size > (size_t) virtual_skip)
+		memset(partial, 0, sizeof(MessageSize));
+
+	/* first do the virtual skip - FIXME: <..\r><\n..> skipping! */
+	if (virtual_skip > 0) {
+		msg = msg_start = msg + partial->physical_size;
+
+		cr = NULL;
+		while (msg != msg_end &&
+		       partial->virtual_size < (size_t) virtual_skip) {
+			if (*msg == '\r')
+				cr = msg;
+			else if (*msg == '\n') {
+				partial->lines++;
+
+				if (cr != msg-1) {
+					if (++partial->virtual_size ==
+					    (size_t) virtual_skip) {
+						/* FIXME: CR thingy */
+					}
+				}
+			}
+
+			msg++;
+			partial->virtual_size++;
+		}
+
+		partial->physical_size += (int) (msg-msg_start);
+                max_physical_size -= partial->physical_size;
+	}
+
+	if (max_virtual_size == 0) {
+		/* read the rest of the message */
+		message_get_body_size(msg, max_physical_size, dest);
+		return;
+	}
+
+	/* now read until the message is either finished or we've read
+	   max_virtual_size */
+	msg_start = msg;
+	memset(dest, 0, sizeof(MessageSize));
+
+	cr = NULL;
+	while (msg != msg_end && dest->virtual_size < (size_t) virtual_skip) {
+		if (*msg == '\r')
+			cr = msg;
+		else if (*msg == '\n') {
+			dest->lines++;
+
+			if (cr != msg-1)
+				dest->virtual_size++;
+		}
+
+		msg++;
+		dest->virtual_size++;
+	}
+
+	dest->physical_size = (int) (msg-msg_start);
+}
+
+int imap_msgcache_get_rfc822_partial(ImapMessageCache *cache, unsigned int uid,
+				     off_t virtual_skip,
+				     size_t max_virtual_size,
+				     int get_header, MessageSize *size,
+				     const char **data, int *fd)
+{
+	CachedMessage *msg;
+	const char *body;
+	size_t body_size;
+	off_t physical_skip;
+	int size_got;
+
+	msg = cache->open_msg;
+	if (msg == NULL || msg->uid != uid)
+		return FALSE;
+
+	if (msg->hdr_size == NULL) {
+		msg->hdr_size = p_new(msg->pool, MessageSize, 1);
+		message_get_header_size(cache->open_msg_data,
+					cache->open_size,
+					msg->hdr_size);
+	}
+
+	body = cache->open_msg_data + msg->hdr_size->physical_size;
+	body_size = cache->open_size - msg->hdr_size->physical_size;
+
+	if (fd != NULL) *fd = cache->open_fd;
+	physical_skip = get_header ? 0 : msg->hdr_size->physical_size;
+
+	/* see if we can do this easily */
+	size_got = FALSE;
+	if (virtual_skip == 0) {
+		if (max_virtual_size == 0 && msg->body_size == NULL) {
+			msg->body_size = p_new(msg->pool, MessageSize, 1);
+			msg->body_size->physical_size = cache->open_size -
+				msg->hdr_size->physical_size;
+			msg->body_size->virtual_size =
+				cache->open_virtual_size -
+				msg->hdr_size->virtual_size;
+		}
+
+		if (msg->body_size != NULL &&
+		    (max_virtual_size == 0 ||
+		     max_virtual_size >= msg->body_size->virtual_size)) {
+			*size = *msg->body_size;
+			size_got = TRUE;
+		}
+	}
+
+	if (!size_got) {
+		if (msg->partial_size == NULL)
+			msg->partial_size = p_new(msg->pool, MessageSize, 1);
+
+		get_partial_size(body, body_size, virtual_skip,
+				 max_virtual_size, msg->partial_size, size);
+
+		physical_skip += msg->partial_size->physical_size;
+	}
+
+	if (get_header)
+		message_size_add(size, msg->hdr_size);
+
+	/* seek to wanted position */
+	*data = cache->open_msg_data + physical_skip;
+	if (fd != NULL && *fd != -1) {
+		if (lseek(*fd, cache->open_start_pos + physical_skip,
+			  SEEK_SET) == (off_t)-1)
+			*fd = -1;
+	}
+	return TRUE;
+}
+
+int imap_msgcache_get_data(ImapMessageCache *cache, unsigned int uid,
+			   const char **data, int *fd, size_t *size)
+{
+	if (cache->open_msg == NULL || cache->open_msg->uid != uid)
+		return FALSE;
+
+	*data = cache->open_msg_data;
+	if (fd != NULL) {
+		*fd = cache->open_fd;
+		if (lseek(*fd, cache->open_start_pos, SEEK_SET) == (off_t)-1)
+			return FALSE;
+	}
+
+	if (size != NULL)
+		*size = cache->open_size;
+
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap/imap-message-cache.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,76 @@
+#ifndef __IMAP_MESSAGE_CACHE_H
+#define __IMAP_MESSAGE_CACHE_H
+
+/* IMAP message cache. Caches are mailbox-specific and must be cleared
+   if UID validity changes.
+
+   Caching is mostly done to avoid parsing the same message multiple times
+   when client fetches the message in parts.
+*/
+
+#include "message-parser.h"
+
+typedef enum {
+	IMAP_CACHE_BODY			= 0x01,
+	IMAP_CACHE_BODYSTRUCTURE	= 0x02,
+	IMAP_CACHE_ENVELOPE		= 0x04,
+
+	IMAP_CACHE_MESSAGE_OPEN		= 0x08,
+	IMAP_CACHE_MESSAGE_PART		= 0x10,
+	IMAP_CACHE_MESSAGE_HDR_SIZE	= 0x20,
+	IMAP_CACHE_MESSAGE_BODY_SIZE	= 0x40
+} ImapCacheField;
+
+typedef struct _ImapMessageCache ImapMessageCache;
+
+ImapMessageCache *imap_msgcache_alloc(void);
+void imap_msgcache_clear(ImapMessageCache *cache);
+void imap_msgcache_free(ImapMessageCache *cache);
+
+/* Returns TRUE if all given fields are fully cached, or at least the
+   message is open (ie. you don't need imap_msgcache_message()). */
+int imap_msgcache_is_cached(ImapMessageCache *cache, unsigned int uid,
+			    ImapCacheField fields);
+
+/* Parse and cache the message. The given file handle is automatically
+   closed by cache and must not be closed elsewhere. If pv_headers_size
+   and pv_body_size is non-zero, they're set to saved to message's both
+   physical and virtual sizes (ie. doesn't need to be calculated). */
+void imap_msgcache_message(ImapMessageCache *cache, unsigned int uid,
+			   int fd, off_t offset, size_t size,
+			   size_t virtual_size, size_t pv_headers_size,
+			   size_t pv_body_size, ImapCacheField fields);
+
+/* Store a value for field in cache */
+void imap_msgcache_set(ImapMessageCache *cache, unsigned int uid,
+		       ImapCacheField field, const char *value);
+
+/* Returns the field from cache, or NULL if it's not cached. */
+const char *imap_msgcache_get(ImapMessageCache *cache, unsigned int uid,
+			      ImapCacheField field);
+
+/* Returns the root MessagePart for message, or NULL if it's not cached. */
+MessagePart *imap_msgcache_get_parts(ImapMessageCache *cache, unsigned int uid);
+
+/* Returns FALSE if message isn't in cache. If data is not NULL, it's set to
+   point to beginning of message data. If fd is not NULL, it's set to contain
+   the message file descriptor seeked to same position as *data. If hdr_size
+   is NULL, *data contains only the message body. */
+int imap_msgcache_get_rfc822(ImapMessageCache *cache, unsigned int uid,
+			     MessageSize *hdr_size, MessageSize *body_size,
+			     const char **data, int *fd);
+
+/* Returns FALSE if message isn't in cache. *data is set to point to the first
+   non-skipped character. size is set to specify the real size for message. */
+int imap_msgcache_get_rfc822_partial(ImapMessageCache *cache, unsigned int uid,
+				     off_t virtual_skip,
+				     size_t max_virtual_size,
+				     int get_header, MessageSize *size,
+				     const char **data, int *fd);
+
+/* Like imap_msgcache_get_rfc822() without calculating virtual sizes.
+   size and fd may be NULL. */
+int imap_msgcache_get_data(ImapMessageCache *cache, unsigned int uid,
+			   const char **data, int *fd, size_t *size);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap/imap-message-send.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,90 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "iobuffer.h"
+#include "imap-message-send.h"
+
+int imap_message_send(IOBuffer *outbuf, const char *msg, int msg_fd,
+		      MessageSize *size, off_t virtual_skip,
+		      size_t max_virtual_size)
+{
+	const char *msg_start, *msg_end, *cr;
+	unsigned int len;
+
+	if (size->physical_size == 0)
+		return TRUE;
+
+	if (size->physical_size == size->virtual_size) {
+		/* no need to kludge with CRs, we can use sendfile() */
+		size_t send_size;
+
+		send_size = size->physical_size - virtual_skip;
+		if (msg_fd == -1) {
+			return io_buffer_send(outbuf, msg + virtual_skip,
+					      send_size) > 0;
+		} else {
+			return io_buffer_send_file(outbuf, msg_fd, virtual_skip,
+						   msg + virtual_skip,
+						   send_size) > 0;
+		}
+	}
+
+	msg_start = msg;
+	msg_end = msg + size->physical_size;
+
+	/* first do the virtual skip - FIXME: <..\r><\n..> skipping! */
+	if (virtual_skip > 0) {
+		cr = NULL;
+		while (msg != msg_end && virtual_skip > 0) {
+			if (*msg == '\r')
+				cr = msg;
+			else if (*msg == '\n') {
+				if (cr != msg-1) {
+					if (--virtual_skip == 0) {
+						/* FIXME: cr thingy */
+					}
+				}
+			}
+
+			msg++;
+			virtual_skip--;
+		}
+
+		msg_start = msg;
+	}
+
+	/* go through the message data and insert CRs where needed.  */
+	cr = NULL;
+	while (msg != msg_end) {
+		if (*msg == '\r')
+			cr = msg;
+		else if (*msg == '\n' && cr != msg-1) {
+			len = (unsigned int) (msg - msg_start);
+			if (max_virtual_size != 0 && max_virtual_size <= len) {
+				/* reached max. size limit */
+				return io_buffer_send(outbuf, msg_start,
+						      max_virtual_size) > 0;
+			}
+
+			if (io_buffer_send(outbuf, msg_start, len) <= 0)
+				return FALSE;
+
+			if (io_buffer_send(outbuf, "\r", 1) <= 0)
+				return FALSE;
+
+			/* update max. size */
+			if (max_virtual_size == len+1)
+				return TRUE;
+			max_virtual_size -= len+1;
+
+			msg_start = msg;
+		}
+		msg++;
+	}
+
+	/* send the rest */
+	len = (unsigned int) (msg - msg_start);
+	if (max_virtual_size != 0 && max_virtual_size < len)
+		len = max_virtual_size;
+	return io_buffer_send(outbuf, msg_start, len) > 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap/imap-message-send.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,13 @@
+#ifndef __IMAP_MESSAGE_SEND_H
+#define __IMAP_MESSAGE_SEND_H
+
+#include "message-parser.h"
+
+/* Send message to client inserting CRs if needed. If max_virtual_size is
+   non-zero, only that much of the message is sent. If msg_fd is -1, only
+   msg is used. Returns TRUE if successful. */
+int imap_message_send(IOBuffer *outbuf, const char *msg, int msg_fd,
+		      MessageSize *size, off_t virtual_skip,
+		      size_t max_virtual_size);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap/imap-parser.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,541 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "iobuffer.h"
+#include "imap-parser.h"
+
+#define is_linebreak(c) \
+	((c) == '\r' || (c) == '\n')
+
+typedef enum {
+	ARG_PARSE_NONE = 0,
+	ARG_PARSE_ATOM,
+	ARG_PARSE_STRING,
+	ARG_PARSE_LITERAL,
+	ARG_PARSE_LITERAL_DATA
+} ArgParseType;
+
+struct _ImapParser {
+	Pool pool;
+	IOBuffer *inbuf, *outbuf;
+
+	unsigned int pos;
+	ImapArg *args;
+
+	ArgParseType cur_type;
+	unsigned int cur_pos;
+	ImapArg *cur_arg;
+
+	ImapArg *cur_list_arg; /* argument which contains current list */
+	ImapArgList **cur_list; /* pointer where to append next list item */
+
+        ImapParserFlags flags;
+	int str_first_escape; /* ARG_PARSE_STRING: index to first '\' */
+	unsigned int literal_size; /* ARG_PARSE_LITERAL: string size */
+	unsigned int literal_skip_crlf:1;
+
+	unsigned int inside_bracket:1;
+	unsigned int eol:1;
+	unsigned int error:1;
+};
+
+ImapParser *imap_parser_create(IOBuffer *inbuf, IOBuffer *outbuf)
+{
+	ImapParser *parser;
+
+	parser = i_new(ImapParser, 1);
+	parser->pool = pool_create("IMAP parser", 8192, FALSE);
+	parser->inbuf = inbuf;
+	parser->outbuf = outbuf;
+	return parser;
+}
+
+void imap_parser_destroy(ImapParser *parser)
+{
+	pool_unref(parser->pool);
+	i_free(parser);
+}
+
+void imap_parser_reset(ImapParser *parser)
+{
+	p_clear(parser->pool);
+
+	parser->pos = 0;
+	parser->args = NULL;
+
+	parser->cur_type = ARG_PARSE_NONE;
+	parser->cur_pos = 0;
+	parser->cur_arg = NULL;
+
+	parser->cur_list_arg = NULL;
+	parser->cur_list = NULL;
+
+	parser->eol = FALSE;
+	parser->error = FALSE;
+}
+
+static int imap_parser_skip_whitespace(ImapParser *parser, char **data,
+				       unsigned int *data_size)
+{
+	unsigned int i;
+
+	for (i = parser->cur_pos; i < *data_size; i++) {
+		if ((*data)[i] != ' ')
+			break;
+	}
+
+	parser->inbuf->skip += i;
+	parser->cur_pos = 0;
+
+	*data += i;
+	*data_size -= i;
+	return *data_size > 0;
+}
+
+static ImapArg *imap_arg_create(ImapParser *parser)
+{
+	ImapArgList *list;
+
+	/* create new argument into list */
+	i_assert(parser->cur_list != NULL);
+
+	list = p_new(parser->pool, ImapArgList, 1);
+	*parser->cur_list = list;
+	parser->cur_list = &list->next;
+
+	return &list->arg;
+}
+
+static void imap_parser_open_list(ImapParser *parser)
+{
+	if (parser->cur_arg == NULL)
+		parser->cur_arg = imap_arg_create(parser);
+
+	parser->cur_arg->type = IMAP_ARG_LIST;
+	parser->cur_list = &parser->cur_arg->data.list;
+	parser->cur_list_arg = parser->cur_arg;
+
+	parser->cur_type = ARG_PARSE_NONE;
+	parser->cur_arg = NULL;
+}
+
+static int imap_parser_close_list(ImapParser *parser)
+{
+	ImapArgList **list;
+
+	if (parser->cur_list_arg == NULL) {
+		/* we're not inside list */
+		parser->error = TRUE;
+		return FALSE;
+	}
+
+	parser->cur_list_arg = parser->cur_list_arg->parent;
+	if (parser->cur_list_arg == NULL) {
+		/* end of argument */
+		parser->cur_list = NULL;
+		return TRUE;
+	}
+
+	/* skip to end of the upper list */
+        list = &parser->cur_list_arg->data.list;
+	while (*list != NULL)
+		list = &(*list)->next;
+	parser->cur_list = list;
+
+	parser->cur_type = ARG_PARSE_NONE;
+	parser->cur_arg = NULL;
+
+	return TRUE;
+}
+
+static void imap_parser_save_arg(ImapParser *parser, char *data,
+				 unsigned int lastpos)
+{
+	ImapArg *arg;
+
+	arg = parser->cur_arg;
+	if (arg == NULL)
+		arg = imap_arg_create(parser);
+
+	switch (parser->cur_type) {
+	case ARG_PARSE_ATOM:
+		if (lastpos == 3 && strncmp(data, "NIL", 3) == 0) {
+			/* NIL argument */
+			arg->type = IMAP_ARG_NIL;
+		} else {
+			/* simply save the string */
+			arg->type = IMAP_ARG_ATOM;
+			arg->data.str = p_strndup(parser->pool, data, lastpos);
+		}
+		break;
+	case ARG_PARSE_STRING:
+		/* data is quoted and may contain escapes. */
+		arg->type = IMAP_ARG_STRING;
+		arg->data.str = p_strndup(parser->pool, data+1, lastpos-1);
+
+		/* remove the escapes */
+		if (parser->str_first_escape >= 0) {
+			/* -1 because we skipped the '"' prefix */
+			string_remove_escapes(arg->data.str +
+					      parser->str_first_escape-1);
+		}
+		break;
+	case ARG_PARSE_LITERAL_DATA:
+		if ((parser->flags & IMAP_PARSE_FLAG_LITERAL_SIZE) == 0) {
+			/* simply save the string */
+			arg->type = IMAP_ARG_STRING;
+			arg->data.str = p_strndup(parser->pool, data, lastpos);
+		} else {
+			/* save literal size */
+			arg->type = IMAP_ARG_LITERAL_SIZE;
+			arg->data.literal_size = parser->literal_size;
+		}
+		break;
+	default:
+		i_assert(0);
+	}
+
+	parser->cur_arg = NULL;
+        parser->cur_type = ARG_PARSE_NONE;
+}
+
+static int imap_parser_read_atom(ImapParser *parser, char *data,
+				 unsigned int data_size)
+{
+	unsigned int i;
+
+	/* read until we've found space, CR or LF. Data inside '[' and ']'
+	   characters are an exception though, allow spaces inside them. */
+	for (i = parser->cur_pos; i < data_size; i++) {
+		if (parser->inside_bracket) {
+			if (data[i] == '[' || is_linebreak(data[i])) {
+				/* a) nested '[' characters not allowed
+				      (too much trouble and imap doesn't need)
+				   b) missing ']' character */
+				parser->error = TRUE;
+				return FALSE;
+			}
+
+			if (data[i] == ']') {
+				parser->inside_bracket = FALSE;
+			}
+		} else {
+			if (data[i] == '[')
+				parser->inside_bracket = TRUE;
+			else if (data[i] == ' ' || data[i] == ')' ||
+				 is_linebreak(data[i])) {
+				imap_parser_save_arg(parser, data, i);
+				break;
+			}
+		}
+	}
+
+	parser->cur_pos = i;
+	return TRUE;
+}
+
+static int imap_parser_read_string(ImapParser *parser, char *data,
+				   unsigned int data_size)
+{
+	unsigned int i;
+
+	/* read until we've found non-escaped ", CR or LF */
+	for (i = parser->cur_pos; i < data_size; i++) {
+		if (data[i] == '"') {
+			imap_parser_save_arg(parser, data, i);
+
+			i++; /* skip the trailing '"' too */
+			break;
+		}
+
+		if (data[i] == '\\') {
+			if (i+1 == data_size) {
+				/* known data ends with '\' - leave it to
+				   next time as well if it happens to be \" */
+				break;
+			}
+
+			/* save the first escaped char */
+			if (parser->str_first_escape < 0)
+				parser->str_first_escape = i;
+
+			/* skip the escaped char */
+			i++;
+		}
+
+		/* check linebreaks here, so escaping CR/LF isn't possible.
+		   string always ends with '"', so it's an error if we found
+		   a linebreak.. */
+		if (is_linebreak(data[i])) {
+			parser->error = TRUE;
+			return FALSE;
+		}
+	}
+
+	parser->cur_pos = i;
+	return TRUE;
+}
+
+static int imap_parser_read_literal(ImapParser *parser, char *data,
+				    unsigned int data_size)
+{
+	unsigned int i, prev_size;
+
+	/* expecting digits + "}" */
+	for (i = parser->cur_pos; i < data_size; i++) {
+		if (data[i] == '}') {
+			if (parser->literal_size > parser->inbuf->max_size) {
+				/* too long string, abort. */
+				parser->error = TRUE;
+				return FALSE;
+			}
+
+                        io_buffer_send(parser->outbuf, "+ OK\r\n", 6);
+			parser->cur_type = ARG_PARSE_LITERAL_DATA;
+			parser->literal_skip_crlf = TRUE;
+
+			parser->inbuf->skip += i+1;
+			parser->cur_pos = 0;
+			break;
+		}
+
+		if (data[i] < '0' || data[i] > '9')
+			return FALSE;
+
+		prev_size = parser->literal_size;
+		parser->literal_size = parser->literal_size*10 + data[i]-'0';
+
+		if (parser->literal_size < prev_size) {
+			/* wrapped around, abort. */
+			parser->error = TRUE;
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+static int imap_parser_read_literal_data(ImapParser *parser, char *data,
+					 unsigned int data_size)
+{
+	if (parser->literal_skip_crlf) {
+		/* skip \r\n or \n, anything else gives an error */
+		if (*data == '\r') {
+			if (data_size == 1)
+				return TRUE;
+
+			data++; data_size--;
+			parser->inbuf->skip++;
+		}
+
+		if (*data != '\n')
+			return FALSE;
+
+		data++; data_size--;
+		parser->inbuf->skip++;
+		parser->literal_skip_crlf = FALSE;
+
+		i_assert(parser->cur_pos == 0);
+	}
+
+	if ((parser->flags & IMAP_PARSE_FLAG_LITERAL_SIZE) == 0) {
+		/* now we just wait until we've read enough data */
+		if (data_size >= parser->literal_size) {
+			imap_parser_save_arg(parser, data,
+					     parser->literal_size);
+			parser->cur_pos = parser->literal_size;
+		}
+	} else {
+		/* we want to save only literal size, not the literal itself. */
+		imap_parser_save_arg(parser, NULL, 0);
+	}
+
+	return TRUE;
+}
+
+/* Returns TRUE if argument was fully processed. Also returns TRUE if
+   an argument inside a list was processed. */
+static int imap_parser_read_arg(ImapParser *parser, ImapArg *root_arg)
+{
+	char *data;
+	unsigned int data_size;
+
+	data = (char *) io_buffer_get_data(parser->inbuf, &data_size);
+	if (data_size == 0)
+		return FALSE;
+
+	if (parser->cur_arg == NULL && parser->cur_list == NULL) {
+		/* beginning to parse a new argument */
+		parser->cur_arg = root_arg;
+	}
+
+	while (parser->cur_type == ARG_PARSE_NONE) {
+		/* we haven't started parsing yet */
+		if (!imap_parser_skip_whitespace(parser, &data, &data_size))
+			return FALSE;
+		i_assert(parser->cur_pos == 0);
+
+		switch (data[0]) {
+		case '\r':
+		case '\n':
+			/* unexpected end of line */
+			parser->eol = TRUE;
+			return FALSE;
+		case '"':
+			parser->cur_type = ARG_PARSE_STRING;
+			parser->str_first_escape = -1;
+			break;
+		case '{':
+			parser->cur_type = ARG_PARSE_LITERAL;
+			parser->literal_size = 0;
+			break;
+		case '(':
+			imap_parser_open_list(parser);
+			break;
+		case ')':
+			if (!imap_parser_close_list(parser))
+				return FALSE;
+
+			if (parser->cur_list_arg == NULL) {
+				/* end of argument */
+				parser->cur_pos++;
+				return TRUE;
+			}
+			break;
+		default:
+			parser->cur_type = ARG_PARSE_ATOM;
+                        parser->inside_bracket = FALSE;
+			break;
+		}
+
+		parser->cur_pos++;
+	}
+
+	i_assert(data_size > 0);
+
+	switch (parser->cur_type) {
+	case ARG_PARSE_ATOM:
+		if (!imap_parser_read_atom(parser, data, data_size))
+			return FALSE;
+		break;
+	case ARG_PARSE_STRING:
+		if (!imap_parser_read_string(parser, data, data_size))
+			return FALSE;
+		break;
+	case ARG_PARSE_LITERAL:
+		if (!imap_parser_read_literal(parser, data, data_size))
+			return FALSE;
+
+		/* pass through to parsing data. since inbuf->skip was
+		   modified, we need to get the data start position again. */
+		data = io_buffer_get_data(parser->inbuf, &data_size);
+	case ARG_PARSE_LITERAL_DATA:
+		imap_parser_read_literal_data(parser, data, data_size);
+		break;
+	default:
+		i_assert(0);
+	}
+
+	/* NOTE: data and data_size are invalid here, the functions above
+	   may have changed them. */
+
+	return parser->cur_type == ARG_PARSE_NONE;
+}
+
+int imap_parser_read_args(ImapParser *parser, unsigned int count,
+			  ImapParserFlags flags, ImapArg **args)
+{
+	unsigned int args_size;
+
+	parser->flags = flags;
+
+	args_size = 0;
+	while (count == 0 || parser->pos < count) {
+		if (parser->pos >= args_size) {
+			args_size = nearest_power(parser->pos);
+			if (args_size < 8) args_size = 8;
+
+			parser->args =
+				p_realloc_min(parser->pool, parser->args,
+					      args_size * sizeof(ImapArg));
+		}
+
+		if (!imap_parser_read_arg(parser, &parser->args[parser->pos]))
+			break;
+
+		/* jump to next argument, unless we're processing a list */
+		if (parser->cur_list == NULL)
+			parser->pos++;
+	}
+
+	if (parser->pos >= count || parser->eol) {
+		/* all arguments read / end of line */
+		*args = parser->args;
+		return count == 0 || parser->pos < count ? parser->pos : count;
+	} else if (parser->error) {
+		/* error, abort */
+		*args = NULL;
+		return -1;
+	} else {
+		/* need more data */
+		*args = NULL;
+		return -2;
+	}
+}
+
+const char *imap_parser_read_word(ImapParser *parser)
+{
+	unsigned char *data;
+	unsigned int i, data_size;
+
+	/* get the beginning of data in input buffer */
+	data = io_buffer_get_data(parser->inbuf, &data_size);
+
+	for (i = 0; i < data_size; i++) {
+		if (data[i] == ' ' || data[i] == '\r' || data[i] == '\n')
+			break;
+	}
+
+	if (i < data_size) {
+		parser->inbuf->skip += i + (data[i] == ' ' ? 1 : 0);
+		return p_strndup(parser->pool, data, i);
+	} else {
+		return NULL;
+	}
+}
+
+const char *imap_parser_read_line(ImapParser *parser)
+{
+	unsigned char *data;
+	unsigned int i, data_size;
+
+	/* get the beginning of data in input buffer */
+	data = io_buffer_get_data(parser->inbuf, &data_size);
+
+	for (i = 0; i < data_size; i++) {
+		if (data[i] == '\r' || data[i] == '\n')
+			break;
+	}
+
+	if (i < data_size) {
+		parser->inbuf->skip += i;
+		return p_strndup(parser->pool, data, i);
+	} else {
+		return NULL;
+	}
+}
+
+const char *imap_arg_string(ImapArg *arg)
+{
+	switch (arg->type) {
+	case IMAP_ARG_NIL:
+		return "";
+
+	case IMAP_ARG_ATOM:
+	case IMAP_ARG_STRING:
+		return arg->data.str;
+
+	default:
+		return NULL;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap/imap-parser.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,69 @@
+#ifndef __IMAP_PARSER_H
+#define __IMAP_PARSER_H
+
+typedef enum {
+	/* Set this flag if you wish to read only size of literal argument
+	   and not convert literal into string. Useful when you need to deal
+	   with large literal sizes. The literal must be the last read
+	   parameter. */
+	IMAP_PARSE_FLAG_LITERAL_SIZE	= 0x01
+} ImapParserFlags;
+
+typedef enum {
+	IMAP_ARG_NIL = 0,
+	IMAP_ARG_ATOM,
+	IMAP_ARG_STRING,
+	IMAP_ARG_LITERAL_SIZE,
+	IMAP_ARG_LIST
+} ImapArgType;
+
+typedef struct _ImapParser ImapParser;
+typedef struct _ImapArg ImapArg;
+typedef struct _ImapArgList ImapArgList;
+
+struct _ImapArg {
+	ImapArgType type;
+        ImapArg *parent; /* always of type IMAP_ARG_LIST */
+
+	union {
+		char *str;
+		unsigned int literal_size;
+		ImapArgList *list;
+	} data;
+};
+
+struct _ImapArgList {
+	ImapArgList *next;
+	ImapArg arg;
+};
+
+/* Create new IMAP argument parser. The max. size of inbuf limits the
+   maximum size of each argument. outbuf is used for sending command
+   continuation requests for string literals. */
+ImapParser *imap_parser_create(IOBuffer *inbuf, IOBuffer *outbuf);
+void imap_parser_destroy(ImapParser *parser);
+
+/* Reset the parser to initial state. */
+void imap_parser_reset(ImapParser *parser);
+
+/* Read a number of arguments. This function doesn't call tbuffer_read(), you
+   need to do that. Returns number of arguments read (may be less than count
+   in case of EOL), -2 if more data is needed or -1 if error occured.
+
+   count-sized array of arguments are stored into args when return value is
+   0 or larger. If all arguments weren't read, they're set to NIL. count
+   can be set to 0 to read all arguments in the line. */
+int imap_parser_read_args(ImapParser *parser, unsigned int count,
+			  ImapParserFlags flags, ImapArg **args);
+
+/* Read one word - used for reading tag and command name.
+   Returns NULL if more data is needed. */
+const char *imap_parser_read_word(ImapParser *parser);
+
+/* Read the rest of the line. Returns NULL if more data is needed. */
+const char *imap_parser_read_line(ImapParser *parser);
+
+/* Returns the imap argument as string. NIL returns "" and list returns NULL. */
+const char *imap_arg_string(ImapArg *arg);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap/imap-util.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,72 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "temp-string.h"
+#include "imap-util.h"
+
+const char *imap_write_flags(MailFlags flags, const char *custom_flags[])
+{
+	TempString *str;
+	const char *sysflags, *name;
+	int i;
+
+	if (flags == 0)
+		return "";
+
+	sysflags = t_strconcat((flags & MAIL_ANSWERED) ? " \\Answered" : "",
+			       (flags & MAIL_FLAGGED) ? " \\Flagged" : "",
+			       (flags & MAIL_DELETED) ? " \\Deleted" : "",
+			       (flags & MAIL_SEEN) ? " \\Seen" : "",
+			       (flags & MAIL_DRAFT) ? " \\Draft" : "",
+			       (flags & MAIL_RECENT)  ? " \\Recent" : "",
+			       NULL);
+
+	if (*sysflags != '\0')
+		sysflags++;
+
+	if ((flags & MAIL_CUSTOM_FLAGS_MASK) == 0)
+		return sysflags;
+
+	/* we have custom flags too */
+	str = t_string_new(256);
+	t_string_append(str, sysflags);
+
+	for (i = 0; i < MAIL_CUSTOM_FLAGS_COUNT; i++) {
+		if (flags & (1 << (i + MAIL_CUSTOM_FLAG_1_BIT))) {
+			name = custom_flags[i];
+			if (name != NULL && *name != '\0') {
+				if (str->len > 0)
+					t_string_append_c(str, ' ');
+				t_string_append(str, name);
+			}
+		}
+	}
+
+	return str->str;
+}
+
+const char *imap_escape(const char *str)
+{
+	char *ret, *p;
+	unsigned int i, esc;
+
+	/* get length of string and number of chars to escape */
+	esc = 0;
+	for (i = 0; str[i] != '\0'; i++) {
+		if (IS_ESCAPED_CHAR(str[i]))
+			esc++;
+	}
+
+	if (esc == 0)
+		return str;
+
+	/* escape them */
+	p = ret = t_malloc(i + esc + 1);
+	for (; *str != '\0'; str++) {
+		if (IS_ESCAPED_CHAR(str[i]))
+			*p++ = '\\';
+		*p++ = *str;
+	}
+	*p = '\0';
+	return ret;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap/imap-util.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,40 @@
+#ifndef __IMAP_UTIL_H
+#define __IMAP_UTIL_H
+
+typedef enum {
+	MAIL_ANSWERED		= 0x0000001,
+	MAIL_FLAGGED		= 0x0000002,
+	MAIL_DELETED		= 0x0000004,
+	MAIL_SEEN		= 0x0000008,
+	MAIL_DRAFT		= 0x0000010,
+	MAIL_RECENT		= 0x0000020,
+
+	/* rest of the bits are custom flags */
+	MAIL_CUSTOM_FLAG_1      = 0x0000040
+} MailFlags;
+
+/* growing number of flags isn't very easy. biggest problem is that they're
+   stored into unsigned int, which is 32bit almost everywhere. another thing
+   to remember is that with maildir format, the custom flags are stored into
+   file name using 'a'..'z' letters which gets us exactly the needed 26
+   flags. if more is added, the current code breaks. */
+enum {
+	MAIL_SYSTEM_FLAGS_MASK	= 0x000003f,
+	MAIL_CUSTOM_FLAGS_MASK	= 0xfffffc0,
+
+	MAIL_CUSTOM_FLAG_1_BIT	= 6,
+	MAIL_CUSTOM_FLAGS_COUNT	= 26,
+
+	MAIL_FLAGS_COUNT	= 32
+};
+
+#define IS_ESCAPED_CHAR(c) ((c) == '"' || (c) == '\\')
+
+/* Return flags as a space separated string. custom_flags[] is a list of
+   names for custom flags, flags having NULL or "" entry are ignored. */
+const char *imap_write_flags(MailFlags flags, const char *custom_flags[]);
+
+/* Escape the string */
+const char *imap_escape(const char *str);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/.cvsignore	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,8 @@
+*.la
+*.lo
+*.o
+.deps
+.libs
+Makefile
+Makefile.in
+so_locations
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/Makefile.am	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,28 @@
+SUBDIRS = maildir mbox
+
+noinst_LIBRARIES = libstorage_index.a
+
+INCLUDES = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-mail \
+	-I$(top_srcdir)/src/lib-imap
+
+libstorage_index_a_SOURCES = \
+	mail-hash.c \
+        mail-index.c \
+        mail-index-fsck.c \
+        mail-index-data.c \
+        mail-index-update.c \
+        mail-index-util.c \
+        mail-lockdir.c \
+        mail-messageset.c \
+	mail-modifylog.c
+
+noinst_HEADERS = \
+	mail-hash.h \
+	mail-index.h \
+        mail-index-data.h \
+        mail-index-util.h \
+        mail-lockdir.h \
+        mail-messageset.h \
+	mail-modifylog.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mail-hash.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,434 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "primes.h"
+#include "mmap-util.h"
+#include "mail-index.h"
+#include "mail-index-util.h"
+#include "mail-hash.h"
+
+#include <stdio.h>
+#include <fcntl.h>
+
+/* Minimum record count for a hash file. By default, the hash file size is
+   the number of messages * 3, and it's rebuilt after the file is 3/4 full.
+   Use only primes as hash file sizes. */
+#define MIN_HASH_SIZE 109
+
+/* When rebuilding hash, make it 30% full */
+#define MIN_PERCENTAGE 30
+
+/* Try rebuilding hash sometimes soon after it's 60% full */
+#define REBUILD_PERCENTAGE 60
+
+/* Force a rebuild when hash is 80% full */
+#define FORCED_REBUILD_PERCENTAGE 80
+
+#define HASH_FUNC(uid) (uid * 2)
+
+struct _MailHash {
+	MailIndex *index;
+
+	unsigned int size;
+
+	int fd;
+	char *filepath;
+
+	void *mmap_base;
+	size_t mmap_length;
+
+	MailHashHeader *header;
+	unsigned int dirty_mmap:1;
+	unsigned int modified:1;
+};
+
+static int mmap_update(MailHash *hash)
+{
+	if (!hash->dirty_mmap)
+		return TRUE;
+
+	if (hash->mmap_base != NULL)
+		(void)munmap(hash->mmap_base, hash->mmap_length);
+
+	hash->mmap_base = mmap_rw_file(hash->fd, &hash->mmap_length);
+	if (hash->mmap_base == MAP_FAILED) {
+		hash->mmap_base = NULL;
+		hash->header = NULL;
+		index_set_error(hash->index,
+				"hash: mmap() failed with file %s: %m",
+				hash->filepath);
+		return FALSE;
+	}
+
+	if (hash->mmap_length <= sizeof(MailHashHeader) ||
+	    (hash->mmap_length - sizeof(MailHashHeader)) %
+	    sizeof(MailHashRecord) != 0) {
+		/* hash must be corrupted, rebuilding should have noticed
+		   if it was only partially written. */
+		hash->header = NULL;
+		index_set_error(hash->index, "Corrupted hash file %s: %m",
+				hash->filepath);
+		return FALSE;
+	}
+
+	hash->dirty_mmap = FALSE;
+	hash->header = hash->mmap_base;
+	return TRUE;
+}
+
+static MailHash *mail_hash_new(MailIndex *index)
+{
+	MailHash *hash;
+
+	hash = i_new(MailHash, 1);
+	hash->fd = -1;
+	hash->index = index;
+	hash->filepath = i_strconcat(index->filepath, ".hash", NULL);
+	hash->dirty_mmap = TRUE;
+
+	index->hash = hash;
+	return hash;
+}
+
+int mail_hash_create(MailIndex *index)
+{
+	i_assert(index->lock_type == MAIL_LOCK_EXCLUSIVE);
+
+	return mail_hash_rebuild(mail_hash_new(index));
+}
+
+static int mail_hash_lock_and_rebuild(MailHash *hash)
+{
+	if (!hash->index->set_lock(hash->index, MAIL_LOCK_EXCLUSIVE))
+		return FALSE;
+	return mail_hash_rebuild(hash);
+}
+
+int mail_hash_open_or_create(MailIndex *index)
+{
+	MailHash *hash;
+
+	hash = mail_hash_new(index);
+
+	hash->fd = open(hash->filepath, O_RDWR);
+	if (hash->fd == -1)
+		return mail_hash_lock_and_rebuild(hash);
+
+	if (!mmap_update(hash)) {
+		/* mmap() failure is fatal */
+		mail_hash_free(hash);
+		return FALSE;
+	}
+
+	/* verify that this really is the hash file for wanted index */
+	if (hash->header->indexid != index->indexid) {
+		/* mismatch - just recreate it */
+		(void)munmap(hash->mmap_base, hash->mmap_length);
+		hash->mmap_base = NULL;
+		hash->dirty_mmap = TRUE;
+
+		return mail_hash_lock_and_rebuild(hash);
+	}
+
+	hash->size = (hash->mmap_length - sizeof(MailHashHeader)) /
+		sizeof(MailHashRecord);
+	return TRUE;
+}
+
+void mail_hash_free(MailHash *hash)
+{
+	hash->index->hash = NULL;
+
+	if (hash->mmap_base != NULL) {
+		(void)munmap(hash->mmap_base, hash->mmap_length);
+		hash->mmap_base = NULL;
+	}
+
+	(void)close(hash->fd);
+	i_free(hash->filepath);
+	i_free(hash);
+}
+
+static int file_set_size(int fd, off_t size)
+{
+	char block[1024];
+	unsigned int i, full_blocks;
+	off_t pos;
+
+	/* try truncating it to the size we want. if this succeeds, the written
+	   area is full of zeros - exactly what we want. however, this may not
+	   work at all, in which case we fallback to write()ing the zeros. */
+	if (ftruncate(fd, size) != -1 && lseek(fd, 0, SEEK_END) == size)
+		return lseek(fd, 0, SEEK_SET) == 0;
+
+	/* skip the existing data in file */
+	pos = lseek(fd, 0, SEEK_END);
+	if (pos == (off_t)-1)
+		return FALSE;
+	size -= pos;
+
+	memset(block, 0, sizeof(block));
+
+	/* write in 1kb blocks */
+	full_blocks = size / sizeof(block);
+	for (i = 0; i < full_blocks; i++) {
+		if (write(fd, block, sizeof(block)) != sizeof(block))
+			return FALSE;
+	}
+
+	/* write the remainder */
+	i = size % sizeof(block);
+	return i == 0 ? TRUE : (size_t) write(fd, block, i) == i;
+}
+
+static int hash_rebuild_to_file(MailIndex *index, int fd,
+				unsigned int hash_size,
+				unsigned int messages_count)
+{
+	MailHashHeader *hdr;
+        MailHashRecord *rec;
+	MailIndexRecord *idx_rec;
+	void *mmap_base;
+	unsigned int i, count;
+	size_t mmap_length;
+	size_t new_size;
+
+	/* fill the file with zeros */
+	new_size = sizeof(MailHashHeader) + hash_size * sizeof(MailHashRecord);
+	if (!file_set_size(fd, (off_t) new_size)) {
+		index_set_error(index,
+				"Failed to fill temp hash to size %lu: %m",
+				(unsigned long) new_size);
+		return FALSE;
+	}
+
+	/* now, mmap() it */
+	mmap_base = mmap_rw_file(fd, &mmap_length);
+	if (mmap_base == MAP_FAILED) {
+		index_set_error(index, "mmap()ing temp hash failed: %m");
+		return FALSE;
+	}
+
+	i_assert(mmap_length == new_size);
+
+	/* we have empty hash file mmap()ed now. fill it by reading the
+	   messages from index. */
+	rec = (MailHashRecord *) ((char *) mmap_base + sizeof(MailHashHeader));
+        idx_rec = index->lookup(index, 1);
+	for (count = 0; idx_rec != NULL; count++) {
+		i = HASH_FUNC(idx_rec->uid) % hash_size;
+		rec[i].uid = idx_rec->uid;
+		rec[i].position = INDEX_FILE_POSITION(index, idx_rec);
+		idx_rec = index->next(index, idx_rec);
+	}
+
+	if (count != messages_count) {
+		/* mark this as an error but don't fail because of it. */
+                INDEX_MARK_CORRUPTED(index);
+		index_set_error(index, "Missing messages while rebuilding "
+				"hash file %s - %u found, header says %u",
+				index->filepath, count, messages_count);
+	}
+
+	/* setup header */
+	hdr = mmap_base;
+	hdr->indexid = index->indexid;
+	hdr->used_records = count;
+
+	return munmap(mmap_base, mmap_length) == 0;
+}
+
+int mail_hash_sync_file(MailHash *hash)
+{
+	if (!hash->modified)
+		return TRUE;
+	hash->modified = FALSE;
+
+	if (msync(hash->mmap_base, hash->mmap_length, MS_SYNC) == 0)
+		return TRUE;
+	else {
+		index_set_error(hash->index, "msync() failed for %s: %m",
+				hash->filepath);
+		return FALSE;
+	}
+}
+
+int mail_hash_rebuild(MailHash *hash)
+{
+	MailIndexHeader *index_header;
+	const char *path;
+	unsigned int hash_size;
+	int fd;
+
+	i_assert(hash->index->lock_type == MAIL_LOCK_EXCLUSIVE);
+
+	/* first get the number of messages in index */
+	index_header = hash->index->get_header(hash->index);
+
+	/* then figure out size for our hash */
+	hash_size = primes_closest(index_header->messages_count * 100 / MIN_PERCENTAGE);
+	if (hash_size < MIN_HASH_SIZE)
+		hash_size = MIN_HASH_SIZE;
+
+	/* create the hash in a new temp file */
+	fd = mail_index_create_temp_file(hash->index, &path);
+	if (fd == -1)
+		return FALSE;
+
+	if (!hash_rebuild_to_file(hash->index, fd, hash_size,
+				  index_header->messages_count)) {
+		(void)close(fd);
+		(void)unlink(path);
+		return FALSE;
+	}
+
+	if (fsync(fd) == -1) {
+		index_set_error(hash->index,
+				"fsync() failed with temp hash %s: %m", path);
+		(void)close(fd);
+		(void)unlink(path);
+		return FALSE;
+	}
+
+	/* replace old hash file with this new one */
+	if (rename(path, hash->filepath) == -1) {
+		index_set_error(hash->index, "rename(%s, %s) failed: %m",
+				path, hash->filepath);
+
+		(void)close(fd);
+		(void)unlink(path);
+		return FALSE;
+	}
+
+	/* switch fds */
+	if (hash->fd != -1)
+		(void)close(hash->fd);
+	hash->size = hash_size;
+	hash->fd = fd;
+	hash->dirty_mmap = TRUE;
+	return TRUE;
+}
+
+off_t mail_hash_lookup_uid(MailHash *hash, unsigned int uid)
+{
+        MailHashRecord *rec;
+	unsigned int hashidx, idx;
+
+	i_assert(uid > 0);
+	i_assert(hash->index->lock_type != MAIL_LOCK_UNLOCK);
+
+	if (hash->fd == -1 || !mmap_update(hash))
+		return 0;
+
+	/* our hashing function is simple - UID*2. The *2 is there because
+	   UIDs are normally contiguous, so once the UIDs wrap around, we
+	   don't want to go through lots of records just to find an empty
+	   spot */
+	hashidx = HASH_FUNC(uid) % hash->size;
+	rec = (MailHashRecord *) ((char *) hash->mmap_base +
+				  sizeof(MailHashHeader));
+
+	/* check from hash index to end of file */
+	for (idx = hashidx; idx < hash->size; idx++) {
+		if (rec[idx].uid == uid)
+			return rec[idx].position;
+
+		if (rec[idx].uid == 0) {
+			/* empty hash record - not found. */
+			return 0;
+		}
+	}
+
+	/* check from beginning of file to hash index */
+	for (idx = 0; idx < hashidx; idx++) {
+		if (rec[idx].uid == uid)
+			return rec[idx].position;
+
+		if (rec[idx].uid == 0) {
+			/* empty hash record - not found. */
+			return 0;
+		}
+	}
+
+	/* checked through the whole hash file. this really shouldn't happen,
+	   we should have rebuilt it long time ago.. */
+	return 0;
+}
+
+static MailHashRecord *hash_find_uid_or_free(MailHash *hash, unsigned int uid)
+{
+        MailHashRecord *rec;
+	unsigned int hashidx, idx;
+
+	hashidx = HASH_FUNC(uid) % hash->size;
+	rec = (MailHashRecord *) ((char *) hash->mmap_base +
+				  sizeof(MailHashHeader));
+
+	/* check from hash index to end of file */
+	for (idx = hashidx; idx < hash->size; idx++) {
+		if (rec[idx].uid == 0 || rec[idx].uid == uid)
+			return rec+idx;
+	}
+
+	/* check from beginning of file to hash index */
+	for (idx = 0; idx < hashidx; idx++) {
+		if (rec[idx].uid == 0 || rec[idx].uid == uid)
+			return rec+idx;
+	}
+
+	/* hash file is full */
+	return NULL;
+}
+
+void mail_hash_update(MailHash *hash, unsigned int uid, off_t pos)
+{
+	MailHashRecord *rec;
+	unsigned int max_used;
+
+	i_assert(uid > 0);
+	i_assert(hash->index->lock_type == MAIL_LOCK_EXCLUSIVE);
+
+	if (hash->fd == -1 || !mmap_update(hash))
+		return;
+
+	if (hash->header->used_records >
+	    hash->size * FORCED_REBUILD_PERCENTAGE / 100) {
+		/* we really need a rebuild. */
+		mail_hash_rebuild(hash);
+	}
+
+	/* place the hash into first free record after wanted position */
+	rec = hash_find_uid_or_free(hash, uid);
+
+	if (rec == NULL) {
+		/* this should never happen, we had already checked that
+		   at least 1/5 of hash was empty. except, if the header
+		   contained invalid record count for some reason. rebuild.. */
+		mail_hash_rebuild(hash);
+
+		rec = hash_find_uid_or_free(hash, uid);
+		i_assert(rec != NULL);
+	}
+
+	if (pos != 0) {
+		/* insert/update record */
+		if (rec->uid == 0) {
+			/* update records count, and see if hash is
+			   getting full */
+			max_used = hash->size / 100 * REBUILD_PERCENTAGE;
+			if (++hash->header->used_records > max_used) {
+				hash->index->set_flags |=
+					MAIL_INDEX_FLAG_REBUILD_HASH;
+			}
+		}
+		rec->uid = uid;
+		rec->position = pos;
+	} else {
+		/* delete record */
+		rec->uid = 0;
+		rec->position = 0;
+                hash->header->used_records--;
+	}
+
+	hash->modified = FALSE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mail-hash.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,38 @@
+#ifndef __MAIL_HASH_H
+#define __MAIL_HASH_H
+
+typedef struct _MailHashHeader MailHashHeader;
+typedef struct _MailHashRecord MailHashRecord;
+
+struct _MailHashHeader {
+	unsigned int indexid;
+	unsigned int used_records;
+};
+
+struct _MailHashRecord {
+	unsigned int uid;
+	off_t position;
+};
+
+/* Open or create a hash file for index. If the hash needs to be created,
+   it's also immediately built from the given index. */
+int mail_hash_create(MailIndex *index);
+int mail_hash_open_or_create(MailIndex *index);
+
+void mail_hash_free(MailHash *hash);
+
+/* Synchronize the hash file with memory map */
+int mail_hash_sync_file(MailHash *hash);
+
+/* Rebuild hash from index and reset the FLAG_REBUILD in header.
+   The index must have an exclusive lock before this function is called. */
+int mail_hash_rebuild(MailHash *hash);
+
+/* Returns position in index file to given UID, or 0 if not found. */
+off_t mail_hash_lookup_uid(MailHash *hash, unsigned int uid);
+
+/* Update hash file. If pos is 0, the record is deleted. This call may
+   rebuild the hash if it's too full. */
+void mail_hash_update(MailHash *hash, unsigned int uid, off_t pos);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mail-index-data.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,378 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "mmap-util.h"
+#include "mail-index.h"
+#include "mail-index-data.h"
+#include "mail-index-util.h"
+
+#include <stdio.h>
+#include <fcntl.h>
+
+#define DATA_FILE_POSITION(data, rec) \
+	((off_t) ((char *) (rec) - (char *) ((data)->mmap_base)))
+
+/* Never compress the file if it's smaller than this (50kB) */
+#define COMPRESS_MIN_SIZE (1024*50)
+
+/* Compress the file when deleted space reaches 20% of total size */
+#define COMPRESS_PERCENTAGE 20
+
+struct _MailIndexData {
+	MailIndex *index;
+
+	int fd;
+	char *filepath;
+
+	void *mmap_base;
+	size_t mmap_length;
+
+	unsigned int dirty_mmap:1;
+};
+
+static int mmap_update(MailIndexData *data, off_t pos, unsigned int size)
+{
+	if (!data->dirty_mmap || (size != 0 && pos+size <= data->mmap_length))
+		return TRUE;
+
+	if (data->mmap_base != NULL)
+		(void)munmap(data->mmap_base, data->mmap_length);
+
+	data->mmap_base = mmap_rw_file(data->fd, &data->mmap_length);
+	if (data->mmap_base == MAP_FAILED) {
+		data->mmap_base = NULL;
+		index_set_error(data->index, "index data: mmap() failed with "
+				"file %s: %m", data->filepath);
+		return FALSE;
+	} else if (data->mmap_length < sizeof(MailIndexDataHeader)) {
+                INDEX_MARK_CORRUPTED(data->index);
+		index_set_error(data->index, "index data: truncated data "
+				"file %s", data->filepath);
+		return FALSE;
+	} else {
+		data->dirty_mmap = FALSE;
+		return TRUE;
+	}
+}
+
+int mail_index_data_open(MailIndex *index)
+{
+	MailIndexData *data;
+        MailIndexDataHeader *hdr;
+	const char *path;
+	int fd;
+
+	path = t_strconcat(index->filepath, ".data", NULL);
+	fd = open(path, O_RDWR);
+	if (fd == -1) {
+		if (errno == ENOENT) {
+			/* doesn't exist, rebuild the index */
+			INDEX_MARK_CORRUPTED(index);
+		}
+		index_set_error(index, "Can't open index data %s: %m",
+				path);
+		return FALSE;
+	}
+
+	data = i_new(MailIndexData, 1);
+	data->index = index;
+	data->fd = fd;
+	data->filepath = i_strdup(path);
+	data->dirty_mmap = TRUE;
+
+	index->data = data;
+
+	if (!mmap_update(data, 0, sizeof(MailIndexDataHeader))) {
+		mail_index_data_free(data);
+		return FALSE;
+	}
+
+	/* verify that this really is the data file for wanted index */
+	hdr = data->mmap_base;
+	if (hdr->indexid != index->indexid) {
+		INDEX_MARK_CORRUPTED(index);
+		index_set_error(index, "IndexID mismatch with file %s", path);
+		mail_index_data_free(data);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static const char *init_data_file(MailIndex *index, int fd,
+				  const char *temppath)
+{
+        MailIndexDataHeader hdr;
+	const char *realpath;
+
+	/* w