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;
+
+	/* write header */
+	memset(&hdr, 0, sizeof(hdr));
+	hdr.indexid = index->indexid;
+
+	if (write(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
+		index_set_error(index, "Error writing to temp index data "
+				"%s: %m", temppath);
+		return NULL;
+	}
+
+	/* move temp file into .data file, deleting old one
+	   if it already exists */
+	realpath = t_strconcat(index->filepath, ".data", NULL);
+	if (rename(temppath, realpath) == -1) {
+		index_set_error(index, "rename(%s, %s) failed: %m",
+				temppath, realpath);
+		(void)unlink(temppath);
+		return NULL;
+	}
+
+	return realpath;
+}
+
+int mail_index_data_create(MailIndex *index)
+{
+	MailIndexData *data;
+	const char *temppath, *realpath;
+	int fd;
+
+	fd = mail_index_create_temp_file(index, &temppath);
+	if (fd == -1)
+		return FALSE;
+
+	realpath = init_data_file(index, fd, temppath);
+	if (realpath == NULL) {
+		(void)close(fd);
+		(void)unlink(temppath);
+		return FALSE;
+	}
+
+	data = i_new(MailIndexData, 1);
+	data->index = index;
+	data->fd = fd;
+	data->filepath = i_strdup(realpath);
+	data->dirty_mmap = TRUE;
+
+	index->data = data;
+	return TRUE;
+}
+
+void mail_index_data_free(MailIndexData *data)
+{
+	data->index->data = NULL;
+
+	if (data->mmap_base != NULL) {
+		munmap(data->mmap_base, data->mmap_length);
+		data->mmap_base = NULL;
+	}
+
+	(void)close(data->fd);
+	i_free(data->filepath);
+	i_free(data);
+}
+
+int mail_index_data_reset(MailIndexData *data)
+{
+	MailIndexDataHeader hdr;
+
+	if (ftruncate(data->fd, sizeof(MailIndexDataHeader)) == -1) {
+		index_set_error(data->index, "ftruncate() failed for data file "
+				"%s: %m", data->filepath);
+		return FALSE;
+	}
+
+	memset(&hdr, 0, sizeof(hdr));
+	hdr.indexid = data->index->indexid;
+	hdr.deleted_space = 0;
+
+	if (lseek(data->fd, 0, SEEK_SET) == (off_t)-1) {
+		index_set_error(data->index, "lseek() failed for data file "
+				"%s: %m", data->filepath);
+		return FALSE;
+	}
+
+	if (write(data->fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
+		index_set_error(data->index, "write() failed for data file "
+				"%s: %m", data->filepath);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+void mail_index_data_new_data_notify(MailIndexData *data)
+{
+	data->dirty_mmap = TRUE;
+}
+
+off_t mail_index_data_append(MailIndexData *data, void *buffer, size_t size)
+{
+	off_t pos;
+
+	i_assert((size & (MEM_ALIGN_SIZE-1)) == 0);
+
+	pos = lseek(data->fd, 0, SEEK_END);
+	if (pos == (off_t)-1) {
+		index_set_error(data->index, "lseek() failed with file %s: %m",
+				data->filepath);
+		return (off_t)-1;
+	}
+
+	if ((size_t) write(data->fd, buffer, size) != size) {
+		index_set_error(data->index, "Error appending to file %s: %m",
+				data->filepath);
+		return (off_t)-1;
+	}
+
+	mail_index_data_new_data_notify(data);
+	return pos;
+}
+
+int mail_index_data_add_deleted_space(MailIndexData *data,
+				      unsigned int data_size)
+{
+	MailIndexDataHeader *hdr;
+	unsigned int max_del_space;
+
+	i_assert(data->index->lock_type == MAIL_LOCK_EXCLUSIVE);
+
+	if (!mmap_update(data, 0, 0))
+		return FALSE;
+
+	hdr = data->mmap_base;
+	hdr->deleted_space += data_size;
+
+	/* see if we've reached the max. deleted space in file */
+	if (data->mmap_length >= COMPRESS_MIN_SIZE) {
+		max_del_space = data->mmap_length / 100 * COMPRESS_PERCENTAGE;
+		if ((size_t) hdr->deleted_space >= max_del_space)
+			data->index->set_flags |= MAIL_INDEX_FLAG_COMPRESS_DATA;
+	}
+	return TRUE;
+}
+
+int mail_index_data_sync_file(MailIndexData *data)
+{
+	if (data->mmap_base != NULL) {
+		if (msync(data->mmap_base, data->mmap_length, MS_SYNC) == -1) {
+			index_set_error(data->index, "msync() failed for "
+					"%s: %m", data->filepath);
+			return FALSE;
+		}
+	}
+
+	if (fsync(data->fd) == -1) {
+		index_set_error(data->index, "fsync() failed for %s: %m",
+				data->filepath);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+MailIndexDataRecord *
+mail_index_data_lookup(MailIndexData *data, MailIndexRecord *index_rec,
+		       MailField field)
+{
+	MailIndexDataRecord *rec;
+	size_t pos, max_pos;
+
+	if (index_rec->data_position == 0) {
+		index_reset_error(data->index);
+		return NULL;
+	}
+
+	if (!mmap_update(data, index_rec->data_position, index_rec->data_size))
+		return NULL;
+
+	max_pos = (size_t) index_rec->data_position + index_rec->data_size;
+	if (max_pos > data->mmap_length) {
+		INDEX_MARK_CORRUPTED(data->index);
+		index_set_error(data->index, "Error in data file %s: "
+				"Given data size larger than file size "
+				"(%lu > %lu)", data->filepath,
+				(unsigned long) max_pos,
+				(unsigned long) data->mmap_length);
+		return NULL;
+	}
+
+	pos = index_rec->data_position;
+	do {
+		rec = (MailIndexDataRecord *) ((char *) data->mmap_base + pos);
+
+		if (pos + rec->full_field_size > max_pos) {
+			INDEX_MARK_CORRUPTED(data->index);
+			index_set_error(data->index, "Error in data file %s: "
+					"Field size points outside file "
+					"(%lu + %u > %lu)", data->filepath,
+					(unsigned long) pos,
+					rec->full_field_size,
+					(unsigned long) data->mmap_length);
+			break;
+		}
+
+		if (rec->field == field) {
+			/* match */
+			return rec;
+		} else if (rec->field < field) {
+			/* jump to next record */
+			pos += DATA_RECORD_SIZE(rec);
+		} else {
+			/* the fields are sorted by field type, so it's not
+			   possible the wanted field could come after this. */
+			break;
+		}
+	} while (pos < max_pos);
+
+	return NULL;
+}
+
+MailIndexDataRecord *
+mail_index_data_next(MailIndexData *data, MailIndexRecord *index_rec,
+		     MailIndexDataRecord *rec)
+{
+	size_t pos, max_pos;
+
+	if (rec == NULL)
+		return NULL;
+
+	/* get position to next record */
+	pos = (size_t) DATA_FILE_POSITION(data, rec) + DATA_RECORD_SIZE(rec);
+	max_pos = index_rec->data_position + index_rec->data_size;
+
+	/* make sure it's within range */
+	if (pos >= max_pos)
+		return NULL;
+
+	rec = (MailIndexDataRecord *) ((char *) data->mmap_base + pos);
+	if (pos + rec->full_field_size > max_pos) {
+		INDEX_MARK_CORRUPTED(data->index);
+		index_set_error(data->index, "Error in data file %s: "
+				"Field size points outside file "
+				"(%lu + %u > %lu)", data->filepath,
+				(unsigned long) pos,
+				rec->full_field_size,
+				(unsigned long) data->mmap_length);
+		return NULL;
+	}
+
+	return rec;
+}
+
+int mail_index_data_record_verify(MailIndexData *data, MailIndexDataRecord *rec)
+{
+	int i;
+
+	/* make sure the data actually contains \0 */
+	for (i = rec->full_field_size-1; i >= 0; i--) {
+		if (rec->data[i] == '\0') {
+			/* yes, everything ok */
+			return TRUE;
+		}
+	}
+
+	INDEX_MARK_CORRUPTED(data->index);
+	index_set_error(data->index, "Error in data file %s: "
+			"Missing \\0 with field %u (%lu)",
+			data->filepath, rec->field,
+			(unsigned long) DATA_FILE_POSITION(data, rec));
+	return FALSE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mail-index-data.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,41 @@
+#ifndef __MAIL_INDEX_DATA_H
+#define __MAIL_INDEX_DATA_H
+
+int mail_index_data_open(MailIndex *index);
+int mail_index_data_create(MailIndex *index);
+void mail_index_data_free(MailIndexData *data);
+
+/* Truncate the data file and update it's indexid */
+int mail_index_data_reset(MailIndexData *data);
+
+/* Needs to be called whenever new messages are added. File must never
+   be shrinked while it's open. */
+void mail_index_data_new_data_notify(MailIndexData *data);
+
+/* Append new data at the end of the file. Returns the position in file
+   where the data begins, or (off_t)-1 if error occured. */
+off_t mail_index_data_append(MailIndexData *data, void *buffer, size_t size);
+
+/* Increase header->deleted_space field */
+int mail_index_data_add_deleted_space(MailIndexData *data,
+				      unsigned int data_size);
+
+/* Synchronize the data into disk */
+int mail_index_data_sync_file(MailIndexData *data);
+
+/* Looks up a field from data file. Returns NULL if not found or
+   if error occured. */
+MailIndexDataRecord *
+mail_index_data_lookup(MailIndexData *data, MailIndexRecord *index_rec,
+		       MailField field);
+
+/* Returns the next record in data file, or NULL if there's no more. */
+MailIndexDataRecord *
+mail_index_data_next(MailIndexData *data, MailIndexRecord *index_rec,
+		     MailIndexDataRecord *rec);
+
+/* Returns TRUE if rec->data is a valid \0-terminated string */
+int mail_index_data_record_verify(MailIndexData *data,
+				  MailIndexDataRecord *rec);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mail-index-fsck.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,81 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "mail-index.h"
+
+int mail_index_fsck(MailIndex *index)
+{
+	/* we verify only the fields in the header. other problems will be
+	   noticed and fixed while reading the messages. */
+	MailIndexHeader *hdr;
+	MailIndexRecord *rec, *end_rec;
+	unsigned int max_uid;
+	off_t pos;
+
+	i_assert(index->lock_type != MAIL_LOCK_SHARED);
+
+	if (!mail_index_set_lock(index, MAIL_LOCK_EXCLUSIVE))
+		return FALSE;
+
+	hdr = index->header;
+
+	hdr->first_hole_position = 0;
+	hdr->first_hole_records = 0;
+
+	hdr->messages_count = 0;
+	hdr->seen_messages_count = 0;
+	hdr->deleted_messages_count = 0;
+
+	hdr->first_unseen_uid_lowwater = 0;
+	hdr->first_deleted_uid_lowwater = 0;
+
+	rec = (MailIndexRecord *) ((char *) index->mmap_base +
+				   sizeof(MailIndexHeader));
+	end_rec = (MailIndexRecord *) ((char *) index->mmap_base +
+				       index->mmap_length);
+
+	max_uid = 0;
+	for (; rec < end_rec; rec++) {
+		if (rec->uid == 0) {
+			/* expunged message */
+			pos = INDEX_FILE_POSITION(index, rec);
+			if (hdr->first_hole_position == 0) {
+				hdr->first_hole_position = pos;
+				hdr->first_hole_records = 1;
+			} else if (hdr->first_hole_position +
+				   (hdr->first_hole_records *
+				    sizeof(MailIndexRecord)) == (size_t) pos) {
+				/* hole continues */
+				hdr->first_hole_records++;
+			}
+			continue;
+		}
+
+		if (rec->uid < max_uid) {
+			i_error("fsck %s: UIDs are not ordered (%u < %u)",
+				index->filepath, rec->uid, max_uid);
+			return mail_index_rebuild_all(index);
+		}
+		max_uid = rec->uid;
+
+		if (rec->msg_flags & MAIL_SEEN)
+			hdr->seen_messages_count++;
+		else if (hdr->first_unseen_uid_lowwater)
+			hdr->first_unseen_uid_lowwater = rec->uid;
+
+		if (rec->msg_flags & MAIL_DELETED) {
+			if (hdr->first_deleted_uid_lowwater == 0)
+                                hdr->first_deleted_uid_lowwater = rec->uid;
+			hdr->deleted_messages_count++;
+		}
+		hdr->messages_count++;
+	}
+
+	if (hdr->next_uid <= max_uid)
+		hdr->next_uid = max_uid+1;
+	if (hdr->last_nonrecent_uid >= hdr->next_uid)
+		hdr->last_nonrecent_uid = hdr->next_uid-1;
+
+	/* FSCK flag is removed automatically by set_lock() */
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mail-index-update.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,410 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "rfc822-date.h"
+#include "rfc822-tokenize.h"
+#include "message-parser.h"
+#include "message-size.h"
+#include "imap-envelope.h"
+#include "imap-bodystructure.h"
+#include "mail-index.h"
+#include "mail-index-data.h"
+
+struct _MailIndexUpdate {
+	Pool pool;
+
+	MailIndex *index;
+	MailIndexRecord *rec;
+
+	unsigned int updated_fields;
+	char *fields[FIELD_TYPE_MAX_BITS];
+	unsigned int field_sizes[FIELD_TYPE_MAX_BITS];
+	unsigned int field_extra_sizes[FIELD_TYPE_MAX_BITS];
+};
+
+MailIndexUpdate *mail_index_update_begin(MailIndex *index, MailIndexRecord *rec)
+{
+	Pool pool;
+	MailIndexUpdate *update;
+
+	i_assert(index->lock_type == MAIL_LOCK_EXCLUSIVE);
+
+	pool = pool_create("MailIndexUpdate", 1024, FALSE);
+
+	update = p_new(pool, MailIndexUpdate, 1);
+	update->pool = pool;
+	update->index = index;
+	update->rec = rec;
+	return update;
+}
+
+static int mail_field_get_index(MailField field)
+{
+	unsigned int i, mask;
+
+	for (i = 0, mask = 1; i < FIELD_TYPE_MAX_BITS; i++, mask <<= 1) {
+		if (field == mask)
+			return i;
+	}
+
+	return -1;
+}
+
+static int have_new_fields(MailIndexUpdate *update)
+{
+	MailField field;
+
+	if (update->rec->cached_fields == 0) {
+		/* new record */
+		return TRUE;
+	}
+
+	for (field = 1; field != FIELD_TYPE_LAST; field <<= 1) {
+		if ((update->updated_fields & field) &&
+		    (update->rec->cached_fields & field) == 0)
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+static int have_too_large_fields(MailIndexUpdate *update)
+{
+	MailIndexDataRecord *rec;
+	int index;
+
+	/* start from the first data field - it's required to exist */
+	rec = mail_index_data_lookup(update->index->data, update->rec, 1);
+	while (rec != NULL) {
+		if (rec->field & update->updated_fields) {
+			/* field was changed */
+			index = mail_field_get_index(rec->field);
+			i_assert(index >= 0);
+
+			if (update->field_sizes[index] >= rec->full_field_size)
+				return TRUE;
+		}
+		rec = mail_index_data_next(update->index->data,
+					   update->rec, rec);
+	}
+
+	return FALSE;
+}
+
+/* Append all the data at the end of the data file and update 
+   the index's data position */
+static int update_by_append(MailIndexUpdate *update)
+{
+        MailIndexDataRecord *rec, *destrec;
+	MailField field;
+	off_t fpos;
+	void *mem;
+	unsigned int max_size, pos;
+	int i;
+
+	/* allocate the old size + also the new size of all changed or added
+	   fields. this is more than required, but it's much easier than
+	   calculating the exact size. */
+	max_size = update->rec->data_size;
+	for (i = 0; i < FIELD_TYPE_MAX_BITS; i++) {
+		max_size += sizeof(MailIndexDataRecord)-sizeof(rec->data) +
+			update->field_sizes[i] +
+			update->field_extra_sizes[i] + MEM_ALIGN_SIZE-1;
+	}
+
+	mem = p_malloc(update->pool, max_size);
+	pos = 0;
+
+	rec = mail_index_data_lookup(update->index->data, update->rec, 1);
+	for (i = 0, field = 1; field != FIELD_TYPE_LAST; i++, field <<= 1) {
+		destrec = (MailIndexDataRecord *) ((char *) mem + pos);
+
+		if (update->fields[i] != NULL) {
+			/* value was modified - use it */
+			destrec->full_field_size = update->field_sizes[i] +
+				update->field_extra_sizes[i];
+			memcpy(destrec->data, update->fields[i],
+			       update->field_sizes[i]);
+		} else if (rec != NULL) {
+			/* use the old value */
+			destrec->full_field_size = rec->full_field_size;
+			memcpy(destrec->data, rec->data, rec->full_field_size);
+		} else {
+			/* the field doesn't exist, jump to next */
+			continue;
+		}
+
+		/* memory alignment fix */
+		destrec->full_field_size = MEM_ALIGN(destrec->full_field_size);
+
+		destrec->field = field;
+		pos += DATA_RECORD_SIZE(destrec);
+
+		if (rec != NULL && rec->field == field) {
+			rec = mail_index_data_next(update->index->data,
+						   update->rec, rec);
+		}
+	}
+
+	i_assert(pos <= max_size);
+
+	/* append the data at the end of the data file */
+	fpos = mail_index_data_append(update->index->data, mem, pos);
+	if (fpos == (off_t)-1)
+		return FALSE;
+
+	/* update index file position - it's mmap()ed so it'll be writte
+	   into disk when index is unlocked. */
+	update->rec->data_position = fpos;
+	update->rec->data_size = pos;
+	return TRUE;
+}
+
+/* Replace the modified fields in the file - assumes there's enough
+   space to do it */
+static void update_by_replace(MailIndexUpdate *update)
+{
+	MailIndexDataRecord *rec;
+	int index;
+
+	/* start from the first data field - it's required to exist */
+	rec = mail_index_data_lookup(update->index->data, update->rec, 1);
+	while (rec != NULL) {
+		if (rec->field & update->updated_fields) {
+			/* field was changed */
+			index = mail_field_get_index(rec->field);
+			i_assert(index >= 0);
+
+			i_assert(update->field_sizes[index] <
+				 rec->full_field_size);
+
+			strcpy(rec->data, update->fields[index]);
+		}
+		rec = mail_index_data_next(update->index->data,
+					   update->rec, rec);
+	}
+}
+
+int mail_index_update_end(MailIndexUpdate *update)
+{
+	int failed;
+
+	i_assert(update->index->lock_type == MAIL_LOCK_EXCLUSIVE);
+
+	/* if any of the fields were newly added, or have grown larger
+	   than their old max. size, we need to move the record to end
+	   of file. */
+	if (have_new_fields(update) || have_too_large_fields(update))
+		failed = !update_by_append(update);
+	else {
+		update_by_replace(update);
+		failed = FALSE;
+	}
+
+	if (!failed) {
+		/* update cached fields mask */
+		update->rec->cached_fields |= update->updated_fields;
+	}
+
+	pool_unref(update->pool);
+	return !failed;
+}
+
+void mail_index_update_field(MailIndexUpdate *update, MailField field,
+			     const char *value, unsigned int extra_space)
+{
+	unsigned int size;
+	int index;
+
+	index = mail_field_get_index(field);
+	i_assert(index >= 0);
+
+	size = strlen(value)+1;
+
+	update->updated_fields |= field;
+	update->field_sizes[index] = size;
+	update->field_extra_sizes[index] = extra_space;
+	update->fields[index] = p_malloc(update->pool, size);
+	memcpy(update->fields[index], value, size);
+}
+
+static MailField mail_header_get_field(const char *str, unsigned int len)
+{
+	if (len == 10 && strncasecmp(str, "Message-ID", 10) == 0)
+		return FIELD_TYPE_MESSAGEID;
+	if (len == 7 && strncasecmp(str, "Subject", 7) == 0)
+		return FIELD_TYPE_SUBJECT;
+	if (len == 4 && strncasecmp(str, "From", 4) == 0)
+		return FIELD_TYPE_FROM;
+	if (len == 2 && strncasecmp(str, "To", 2) == 0)
+		return FIELD_TYPE_TO;
+	if (len == 2 && strncasecmp(str, "Cc", 2) == 0)
+		return FIELD_TYPE_CC;
+	if (len == 3 && strncasecmp(str, "Bcc", 3) == 0)
+		return FIELD_TYPE_BCC;
+
+	return 0;
+}
+
+static const char *field_get_value(const char *value, unsigned int len)
+{
+	char *ret, *p;
+	unsigned int i;
+
+	ret = t_malloc(len+1);
+
+	/* compress the long headers (remove \r?\n before space or tab) */
+	for (i = 0, p = ret; i < len; i++) {
+		if (value[i] == '\r' && i+1 != len && value[i+1] == '\n')
+			i++;
+
+		if (value[i] == '\n') {
+			i_assert(IS_LWSP(value[i+1]));
+		} else {
+			*p++ = value[i];
+		}
+	}
+	*p = '\0';
+
+	return ret;
+}
+
+typedef struct {
+	MailIndexUpdate *update;
+	Pool envelope_pool;
+	MessagePartEnvelopeData *envelope;
+
+	MessageHeaderFunc header_func;
+	void *user_data;
+} HeaderUpdateData;
+
+static void update_header_func(MessagePart *part,
+			       const char *name, unsigned int name_len,
+			       const char *value, unsigned int value_len,
+			       void *user_data)
+{
+	HeaderUpdateData *data = user_data;
+	MailField field;
+	const char *str;
+
+	if (part != NULL && part->parent != NULL)
+		return;
+
+	if (data->header_func != NULL) {
+		data->header_func(part, name, name_len,
+				  value, value_len, data->user_data);
+	}
+
+	if (name_len == 4 && strncasecmp(name, "Date", 4) == 0) {
+		/* date is stored into index record itself */
+		str = field_get_value(value, value_len);
+		if (!rfc822_parse_date(str, &data->update->rec->sent_date))
+			data->update->rec->sent_date = ioloop_time;
+		return;
+	}
+
+	/* see if we can do anything with this field */
+	field = mail_header_get_field(name, name_len);
+	if (field != 0) {
+		/* do we want to store this? */
+		if (data->update->index->header->cache_fields & field) {
+			str = field_get_value(value, value_len);
+			data->update->index->update_field(data->update,
+							  field, str, 0);
+		}
+	}
+
+	if (data->update->index->header->cache_fields & FIELD_TYPE_ENVELOPE) {
+		if (data->envelope_pool == NULL) {
+			data->envelope_pool = pool_create("index envelope",
+							  2048, FALSE);
+		}
+		imap_envelope_parse_header(data->envelope_pool,
+					   &data->envelope,
+					   t_strndup(name, name_len),
+					   value, value_len);
+	}
+}
+
+void mail_index_update_headers(MailIndexUpdate *update,
+			       const char *msg, size_t size,
+			       MessageHeaderFunc header_func, void *user_data)
+{
+	HeaderUpdateData data;
+	MailField cache_fields;
+	MessagePart *part;
+	MessageSize hdr_size, body_size;
+	Pool pool;
+	const char *value;
+
+	data.update = update;
+	data.envelope_pool = NULL;
+	data.envelope = NULL;
+	data.header_func = header_func;
+	data.user_data = user_data;
+
+	cache_fields = update->index->header->cache_fields;
+	if ((cache_fields & (FIELD_TYPE_BODY|FIELD_TYPE_BODYSTRUCTURE)) != 0) {
+		/* for body / bodystructure, we need need to
+		   fully parse the message */
+		pool = pool_create("index message parser", 2048, FALSE);
+		part = message_parse(pool, msg, size,
+				     update_header_func, &data);
+
+		/* update our sizes */
+		update->rec->header_size = part->header_size.physical_size;
+		update->rec->body_size = part->body_size.physical_size;
+		update->rec->full_virtual_size =
+			part->header_size.virtual_size +
+			part->body_size.virtual_size;
+
+		if (cache_fields & FIELD_TYPE_BODY) {
+			t_push();
+			value = imap_part_get_bodystructure(pool, &part,
+							    msg, size, FALSE);
+			update->index->update_field(update, FIELD_TYPE_BODY,
+						    value, 0);
+			t_pop();
+		}
+
+		if (cache_fields & FIELD_TYPE_BODYSTRUCTURE) {
+			t_push();
+			value = imap_part_get_bodystructure(pool, &part,
+							    msg, size, TRUE);
+			update->index->update_field(update,
+						    FIELD_TYPE_BODYSTRUCTURE,
+						    value, 0);
+			t_pop();
+		}
+
+		pool_unref(pool);
+	} else {
+		message_parse_header(NULL, msg, size, &hdr_size,
+				     update_header_func, &data);
+
+		update->rec->header_size = hdr_size.physical_size;
+		update->rec->body_size = size - hdr_size.physical_size;
+
+		if (update->rec->full_virtual_size == 0) {
+			/* we need to calculate virtual size of the
+			   body as well */
+			message_get_body_size(msg + hdr_size.physical_size,
+					      size - hdr_size.physical_size,
+					      &body_size);
+
+			update->rec->full_virtual_size =
+				hdr_size.virtual_size + body_size.virtual_size;
+		}
+	}
+
+	if (data.envelope != NULL) {
+		t_push();
+		value = imap_envelope_get_part_data(data.envelope);
+		update->index->update_field(update, FIELD_TYPE_ENVELOPE,
+					    value, 0);
+		t_pop();
+
+		pool_unref(data.envelope_pool);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mail-index-util.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,59 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "hostpid.h"
+#include "mail-index.h"
+#include "mail-index-util.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+void index_set_error(MailIndex *index, const char *fmt, ...)
+{
+	va_list va;
+
+	i_free(index->error);
+
+	if (fmt == NULL)
+		index->error = NULL;
+	else {
+		va_start(va, fmt);
+		index->error = i_strdup_vprintf(fmt, va);
+		va_end(va);
+
+		i_error("%s", index->error);
+	}
+}
+
+void index_reset_error(MailIndex *index)
+{
+	if (index->error != NULL) {
+		i_free(index->error);
+		index->error = NULL;
+	}
+}
+
+int mail_index_create_temp_file(MailIndex *index, const char **path)
+{
+	int fd;
+
+	hostpid_init();
+
+	/* use ".temp.host.pid" as temporary file name. unlink() it first,
+	   just to be sure it's not symlinked somewhere for some reason.. */
+	*path = t_strconcat(index->dir, "/.temp.",
+			    my_hostname, ".", my_pid, NULL);
+	(void)unlink(*path);
+
+	/* usage of O_EXCL isn't exactly needed since the path should be
+	   trusted, but it shouldn't hurt either - if creating file fails
+	   because of it, it's because something must be wrong (race
+	   condition). also, might not won't work through NFS but that
+	   can't be helped. */
+	fd = open(*path, O_RDWR | O_CREAT | O_EXCL, 0660);
+	if (fd == -1)
+		index_set_error(index, "Can't create temp index %s: %m", *path);
+
+	return fd;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mail-index-util.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,15 @@
+#ifndef __MAIL_INDEX_UTIL_H
+#define __MAIL_INDEX_UTIL_H
+
+/* Set the current error message */
+void index_set_error(MailIndex *index, const char *fmt, ...)
+	__attr_format__(2, 3);
+
+/* Reset the current error */
+void index_reset_error(MailIndex *index);
+
+/* Create temporary file into index's directory. Returns opened file handle
+   and sets *path to the full path of the created file.  */
+int mail_index_create_temp_file(MailIndex *index, const char **path);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mail-index.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,1139 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "hostpid.h"
+#include "mmap-util.h"
+#include "mail-index.h"
+#include "mail-index-data.h"
+#include "mail-index-util.h"
+#include "mail-hash.h"
+#include "mail-lockdir.h"
+#include "mail-modifylog.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <utime.h>
+
+static int mmap_update(MailIndex *index)
+{
+	unsigned int extra;
+
+	if (!index->dirty_mmap) {
+		index->header = (MailIndexHeader *) index->mmap_base;
+		return TRUE;
+	}
+
+	if (index->mmap_base != NULL)
+		(void)munmap(index->mmap_base, index->mmap_length);
+
+	index->mmap_base = mmap_rw_file(index->fd, &index->mmap_length);
+	if (index->mmap_base == MAP_FAILED) {
+		index->mmap_base = NULL;
+		index_set_error(index, "index: mmap() failed with file %s: %m",
+				index->filepath);
+		return FALSE;
+	}
+
+	if (index->mmap_length < sizeof(MailIndexHeader)) {
+                INDEX_MARK_CORRUPTED(index);
+		index_set_error(index, "truncated index file %s",
+				index->filepath);
+		return FALSE;
+	}
+
+	extra = (index->mmap_length - sizeof(MailIndexHeader)) %
+		sizeof(MailIndexRecord);
+
+	if (extra != 0) {
+		/* partial write or corrupted -
+		   truncate the file to valid length */
+		index->mmap_length -= extra;
+		(void)ftruncate(index->fd, (off_t) index->mmap_length);
+	}
+
+	index->header = (MailIndexHeader *) index->mmap_base;
+	index->dirty_mmap = FALSE;
+	return TRUE;
+}
+
+void mail_index_close(MailIndex *index)
+{
+	index->set_flags = 0;
+	index->set_cache_fields = 0;
+
+	index->opened = FALSE;
+	index->updating = FALSE;
+	index->inconsistent = FALSE;
+	index->dirty_mmap = TRUE;
+
+	index->lock_type = MAIL_LOCK_UNLOCK;
+	index->header = NULL;
+
+	if (index->fd != -1) {
+		(void)close(index->fd);
+		index->fd = -1;
+	}
+
+	if (index->filepath != NULL) {
+		i_free(index->filepath);
+		index->filepath = NULL;
+	}
+
+	if (index->mmap_base != NULL) {
+		(void)munmap(index->mmap_base, index->mmap_length);
+		index->mmap_base = NULL;
+	}
+
+	if (index->data != NULL) {
+                mail_index_data_free(index->data);
+		index->data = NULL;
+	}
+
+	if (index->hash != NULL) {
+                mail_hash_free(index->hash);
+		index->hash = NULL;
+	}
+
+	if (index->modifylog != NULL) {
+                mail_modifylog_free(index->modifylog);
+		index->modifylog = NULL;
+	}
+
+	if (index->error != NULL) {
+		i_free(index->error);
+		index->error = NULL;
+	}
+}
+
+int mail_index_sync_file(MailIndex *index)
+{
+	struct utimbuf ut;
+	int failed;
+
+	if (!mail_index_data_sync_file(index->data))
+		return FALSE;
+
+	if (index->mmap_base != NULL) {
+		if (msync(index->mmap_base, index->mmap_length, MS_SYNC) == -1) {
+			index_set_error(index, "msync() failed for %s: %m",
+					index->filepath);
+			return FALSE;
+		}
+	}
+
+	failed = FALSE;
+	if (!mail_hash_sync_file(index->hash))
+		failed = TRUE;
+	if (!mail_modifylog_sync_file(index->modifylog))
+		failed = TRUE;
+
+	/* keep index's modify stamp same as the sync file's stamp */
+	ut.actime = ioloop_time;
+	ut.modtime = index->file_sync_stamp;
+	if (utime(index->filepath, &ut) == -1) {
+		index_set_error(index, "utime() failed for %s: %m",
+				index->filepath);
+		return FALSE;
+	}
+
+	if (fsync(index->fd) == -1) {
+		index_set_error(index, "fsync() failed for %s: %m",
+				index->filepath);
+		return FALSE;
+	}
+
+	return !failed;
+}
+
+int mail_index_rebuild_all(MailIndex *index)
+{
+	if (!index->rebuild(index))
+		return FALSE;
+
+	if (!mail_hash_rebuild(index->hash))
+		return FALSE;
+
+	return TRUE;
+}
+
+static void mail_index_update_header_changes(MailIndex *index)
+{
+	i_assert(index->lock_type == MAIL_LOCK_EXCLUSIVE);
+
+	if (index->set_flags != 0) {
+		index->header->flags |= index->set_flags;
+		index->set_flags = 0;
+	}
+
+	if (index->set_cache_fields != 0) {
+		index->header->cache_fields = index->set_cache_fields;
+		index->set_cache_fields = 0;
+	}
+}
+
+#define MAIL_LOCK_TO_FLOCK(lock_type) \
+        ((lock_type) == MAIL_LOCK_UNLOCK ? F_UNLCK : \
+		(lock_type) == MAIL_LOCK_SHARED ? F_RDLCK : F_WRLCK)
+
+int mail_index_try_lock(MailIndex *index, MailLockType lock_type)
+{
+	struct flock fl;
+
+	if (index->lock_type == lock_type)
+		return TRUE;
+
+	/* lock whole file */
+	fl.l_type = MAIL_LOCK_TO_FLOCK(lock_type);
+	fl.l_whence = SEEK_SET;
+	fl.l_start = 0;
+	fl.l_len = 0;
+
+	if (fcntl(index->fd, F_SETLK, &fl) == -1) {
+		if (errno != EINTR && errno != EACCES) {
+			index_set_error(index, "fcntl(F_SETLKW, %d) "
+					"failed for file %s: %m", fl.l_type,
+					index->filepath);
+		}
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+int mail_index_set_lock(MailIndex *index, MailLockType lock_type)
+{
+	/* yeah, this function is a bit messy. besides locking, it keeps
+	   the index synced and in a good shape. */
+	MailLockType old_lock_type;
+	struct flock fl;
+	int ret;
+
+	if (index->inconsistent) {
+		/* index is in inconsistent state and nothing else than
+		   free() is allowed for it. */
+		return FALSE;
+	}
+
+	if (index->lock_type == lock_type)
+		return TRUE;
+
+	/* shared -> exclusive isn't allowed */
+	i_assert(lock_type != MAIL_LOCK_EXCLUSIVE ||
+		 index->lock_type != MAIL_LOCK_SHARED);
+
+	if (index->lock_type == MAIL_LOCK_EXCLUSIVE) {
+		/* releasing exclusive lock */
+		index->header->flags &= ~MAIL_INDEX_FLAG_FSCK;
+
+		mail_index_update_header_changes(index);
+
+		/* sync mmaped memory */
+		(void)mail_index_sync_file(index);
+	}
+
+	if (lock_type != MAIL_LOCK_UNLOCK &&
+	    index->lock_type == MAIL_LOCK_UNLOCK && !index->updating) {
+		/* unlock -> lock */
+		index->updating = TRUE;
+		(void)index->sync(index);
+
+		ret = mail_index_set_lock(index, lock_type);
+		index->updating = FALSE;
+		return ret;
+	}
+
+	/* lock whole file */
+	fl.l_type = MAIL_LOCK_TO_FLOCK(lock_type);
+	fl.l_whence = SEEK_SET;
+	fl.l_start = 0;
+	fl.l_len = 0;
+
+	while (fcntl(index->fd, F_SETLKW, &fl) == -1) {
+		if (errno != EINTR) {
+			index_set_error(index, "fcntl(F_SETLKW, %d) "
+					"failed for file %s: %m", fl.l_type,
+					index->filepath);
+			return FALSE;
+		}
+	}
+
+	if (lock_type == MAIL_LOCK_UNLOCK) {
+		/* reset last_lookup so rebuilds don't try to use it */
+		index->last_lookup = NULL;
+	}
+
+	old_lock_type = index->lock_type;
+	index->lock_type = lock_type;
+
+	if (lock_type != MAIL_LOCK_UNLOCK) {
+		/* we're always mmap()ed when we're locked */
+		if (!mmap_update(index)) {
+			(void)mail_index_set_lock(index, MAIL_LOCK_UNLOCK);
+			return FALSE;
+		}
+
+		if (index->indexid != index->header->indexid) {
+			/* index was rebuilt, there's no way we can maintain
+			   consistency */
+			index_set_error(index, "Warning: Inconsistency - Index "
+					"%s was rebuilt while we had it open",
+					index->filepath);
+			index->inconsistent = TRUE;
+			return FALSE;
+		}
+	} else if (old_lock_type == MAIL_LOCK_SHARED) {
+		/* releasing shared lock */
+		unsigned int old_flags, old_cache;
+
+		old_flags = index->header->flags;
+		old_cache = index->header->cache_fields;
+
+		if ((old_flags | index->set_flags) != old_flags ||
+		    (old_cache | index->set_cache_fields) != old_cache) {
+			/* need to update the header */
+			index->updating = TRUE;
+			if (mail_index_set_lock(index, MAIL_LOCK_EXCLUSIVE))
+				mail_index_update_header_changes(index);
+			index->updating = FALSE;
+
+			return mail_index_set_lock(index, MAIL_LOCK_UNLOCK);
+		}
+	}
+
+	if (lock_type == MAIL_LOCK_EXCLUSIVE) {
+		/* while holding exclusive lock, keep the FSCK flag on.
+		   when the lock is released, the FSCK flag will also be
+		   removed. */
+		index->header->flags |= MAIL_INDEX_FLAG_FSCK;
+		if (msync(index->mmap_base, sizeof(MailIndexHeader),
+			  MS_SYNC) == -1) {
+			index_set_error(index, "msync() failed for %s: %m",
+					index->filepath);
+			(void)mail_index_set_lock(index, MAIL_LOCK_UNLOCK);
+			return FALSE;
+		}
+		if (fsync(index->fd) == -1) {
+			index_set_error(index, "fsync() failed for %s: %m",
+					index->filepath);
+			(void)mail_index_set_lock(index, MAIL_LOCK_UNLOCK);
+			return FALSE;
+		}
+	}
+
+	if (index->header != NULL && !index->updating &&
+	    (index->header->flags & MAIL_INDEX_FLAG_REBUILD) != 0) {
+		/* index is corrupted, rebuild it */
+		index->updating = TRUE;
+
+		if (lock_type == MAIL_LOCK_SHARED)
+			(void)mail_index_set_lock(index, MAIL_LOCK_UNLOCK);
+
+		if (!mail_index_rebuild_all(index))
+			return FALSE;
+
+		ret = mail_index_set_lock(index, lock_type);
+		index->updating = FALSE;
+		return ret;
+	}
+
+	if (lock_type == MAIL_LOCK_UNLOCK) {
+		/* reset header so it's not used while being unlocked */
+		index->last_lookup = NULL;
+	}
+
+	return TRUE;
+}
+
+static int read_and_verify_header(int fd, MailIndexHeader *hdr)
+{
+	/* read the header */
+	if (lseek(fd, 0, SEEK_SET) != 0)
+		return FALSE;
+
+	if (read(fd, hdr, sizeof(MailIndexHeader)) != sizeof(MailIndexHeader))
+		return FALSE;
+
+	/* check the compatibility */
+	if (hdr->compat_data[0] != MAIL_INDEX_COMPAT_FLAGS ||
+	    hdr->compat_data[1] != sizeof(unsigned int) ||
+	    hdr->compat_data[2] != sizeof(time_t) ||
+	    hdr->compat_data[3] != sizeof(off_t))
+		return FALSE;
+
+	/* check the version */
+	return hdr->version == MAIL_INDEX_VERSION;
+}
+
+/* Returns TRUE if we're compatible with given index file */
+static int mail_is_compatible_index(const char *path)
+{
+        MailIndexHeader hdr;
+	int fd, compatible;
+
+	fd = open(path, O_RDONLY);
+	if (fd == -1)
+		return FALSE;
+
+	compatible = read_and_verify_header(fd, &hdr);
+
+	(void)close(fd);
+	return compatible;
+}
+
+/* Returns a file name of compatible index */
+static const char *mail_find_index(MailIndex *index)
+{
+	DIR *dir;
+	struct dirent *d;
+	const char *name;
+	char path[1024];
+	unsigned int len;
+
+	/* first try the primary name */
+	i_snprintf(path, sizeof(path), "%s/" INDEX_FILE_PREFIX, index->dir);
+	if (mail_is_compatible_index(path))
+		return INDEX_FILE_PREFIX;
+
+	dir = opendir(index->dir);
+	if (dir == NULL) {
+		/* path doesn't exist */
+		index_set_error(index, "Can't open dir %s: %m",
+				index->dir);
+		return NULL;
+	}
+
+	len = strlen(INDEX_FILE_PREFIX);
+	name = NULL;
+	while ((d = readdir(dir)) != NULL) {
+		if (strncmp(d->d_name, INDEX_FILE_PREFIX, len) == 0) {
+			/* index found, check if we're compatible */
+			i_snprintf(path, sizeof(path), "%s/%s",
+				   index->dir, d->d_name);
+			if (mail_is_compatible_index(path)) {
+				name = t_strdup(d->d_name);
+				break;
+			}
+		}
+	}
+
+	(void)closedir(dir);
+	return name;
+}
+
+static int mail_index_open_init(MailIndex *index, int update_recent,
+				MailIndexHeader *hdr)
+{
+	/* update \Recent message counters */
+	if (update_recent && hdr->last_nonrecent_uid != hdr->next_uid-1) {
+		/* keep last_recent_uid to next_uid-1 */
+		if (index->lock_type == MAIL_LOCK_SHARED) {
+			if (!index->set_lock(index, MAIL_LOCK_UNLOCK))
+				return FALSE;
+		}
+
+		if (!index->set_lock(index, MAIL_LOCK_EXCLUSIVE))
+			return FALSE;
+
+		index->first_recent_uid = index->header->last_nonrecent_uid+1;
+		index->header->last_nonrecent_uid = index->header->next_uid-1;
+	} else {
+		index->first_recent_uid = hdr->last_nonrecent_uid+1;
+	}
+
+	if (hdr->next_uid >= INT_MAX-1024) {
+		/* UID values are getting too high, rebuild index */
+		index->set_flags |= MAIL_INDEX_FLAG_REBUILD;
+	}
+
+	return TRUE;
+}
+
+static int mail_index_open_file(MailIndex *index, const char *filename,
+				int update_recent)
+{
+        MailIndexHeader hdr;
+	const char *path;
+	int fd, failed;
+
+	/* the index file should already be checked that it exists and
+	   we're compatible with it. */
+
+	path = t_strconcat(index->dir, "/", filename, NULL);
+	fd = open(path, O_RDWR);
+	if (fd == -1) {
+		index_set_error(index, "Can't open index %s: %m", path);
+		return FALSE;
+	}
+
+	/* check the compatibility anyway just to be sure */
+	if (!read_and_verify_header(fd, &hdr)) {
+		index_set_error(index, "Non-compatible index file %s", path);
+		return FALSE;
+	}
+
+	if (index->fd != -1)
+		mail_index_close(index);
+
+	index->fd = fd;
+	index->filepath = i_strdup(path);
+	index->indexid = hdr.indexid;
+	index->dirty_mmap = TRUE;
+	index->updating = TRUE;
+
+	failed = TRUE;
+	do {
+		/* open/create the index files */
+		if (!mail_index_data_open(index)) {
+			if ((index->set_flags & MAIL_INDEX_FLAG_REBUILD) == 0)
+				break;
+
+			/* data file is corrupted, need to rebuild index */
+			hdr.flags |= MAIL_INDEX_FLAG_REBUILD;
+			index->set_flags = 0;
+
+			if (!mail_index_data_create(index))
+				break;
+		}
+		if (!mail_hash_open_or_create(index))
+			break;
+		if (!mail_modifylog_open_or_create(index))
+			break;
+
+		if (hdr.flags & MAIL_INDEX_FLAG_REBUILD) {
+			/* index is corrupted, rebuild */
+			if (!mail_index_rebuild_all(index))
+				return FALSE;
+		}
+
+		if (hdr.flags & MAIL_INDEX_FLAG_FSCK) {
+			/* index needs fscking */
+			if (!index->fsck(index))
+				break;
+		}
+
+		if (!index->sync(index))
+			break;
+		if (!mail_index_open_init(index, update_recent, &hdr))
+			break;
+
+		failed = FALSE;
+	} while (FALSE);
+
+	index->updating = FALSE;
+
+	/* last_recent_uid update, hash or modifylog creation
+	   may create locks */
+	if (!index->set_lock(index, MAIL_LOCK_UNLOCK))
+		failed = TRUE;
+
+	if (failed)
+		mail_index_close(index);
+
+	return !failed;
+}
+
+void mail_index_init_header(MailIndexHeader *hdr)
+{
+	memset(hdr, 0, sizeof(MailIndexHeader));
+	hdr->compat_data[0] = MAIL_INDEX_COMPAT_FLAGS;
+	hdr->compat_data[1] = sizeof(unsigned int);
+	hdr->compat_data[2] = sizeof(time_t);
+	hdr->compat_data[3] = sizeof(off_t);
+	hdr->version = MAIL_INDEX_VERSION;
+	hdr->indexid = ioloop_time;
+
+	/* mark the index being rebuilt - rebuild() removes this flag
+	   when it succeeds */
+	hdr->flags = MAIL_INDEX_FLAG_REBUILD;
+
+	/* set the fields we always want to cache - currently nothing
+	   except the location. many clients aren't interested about
+	   any of the fields. */
+	hdr->cache_fields = FIELD_TYPE_LOCATION;
+
+	hdr->uid_validity = ioloop_time;
+	hdr->next_uid = 1;
+}
+
+static int mail_index_create(MailIndex *index, int *dir_unlocked,
+			     int update_recent)
+{
+        MailIndexHeader hdr;
+	const char *path;
+	char index_path[1024];
+	int fd, len;
+
+	*dir_unlocked = FALSE;
+
+	/* first create the index into temporary file. */
+	fd = mail_index_create_temp_file(index, &path);
+	if (fd == -1)
+		return FALSE;
+
+	/* fill the header */
+        mail_index_init_header(&hdr);
+
+	/* write header */
+	if (write(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
+		index_set_error(index, "Error writing to temp index %s: %m",
+				path);
+		(void)close(fd);
+		(void)unlink(path);
+		return FALSE;
+	}
+
+	/* move the temp index into the real one. we also need to figure
+	   out what to call ourself on the way. */
+	len = i_snprintf(index_path, sizeof(index_path),
+			 "%s/" INDEX_FILE_PREFIX, index->dir);
+	if (link(path, index_path) == 0)
+		(void)unlink(path);
+	else {
+		if (errno != EEXIST) {
+			/* fatal error */
+			index_set_error(index, "link(%s, %s) failed: %m",
+					path, index_path);
+			(void)close(fd);
+			(void)unlink(path);
+			return FALSE;
+		}
+
+		/* fallback to index.hostname - we require each system to
+		   have a different hostname so it's safe to override
+		   previous index as well */
+		hostpid_init();
+		i_snprintf(index_path + len, sizeof(index_path)-len,
+			   "-%s", my_hostname);
+
+		if (rename(path, index_path) == -1) {
+			index_set_error(index, "rename(%s, %s) failed: %m",
+					path, index_path);
+			(void)close(fd);
+			(void)unlink(path);
+			return FALSE;
+		}
+
+		/* FIXME: race condition here! index may be opened before
+		   it's rebuilt. maybe set it locked here, and make it require
+		   shared lock when finding the indexes.. */
+	}
+
+	if (index->fd != -1)
+		mail_index_close(index);
+
+	index->fd = fd;
+	index->filepath = i_strdup(index_path);
+	index->indexid = hdr.indexid;
+	index->updating = TRUE;
+	index->dirty_mmap = TRUE;
+
+	/* lock the index file and unlock the directory */
+	if (!index->set_lock(index, MAIL_LOCK_EXCLUSIVE)) {
+		index->updating = FALSE;
+		return FALSE;
+	}
+
+	if (mail_index_lock_dir(index, MAIL_LOCK_UNLOCK))
+		*dir_unlocked = TRUE;
+
+	/* create the data file, build the index and hash */
+	if (!mail_index_data_create(index) || !index->rebuild(index) ||
+	    !mail_hash_create(index) || !mail_modifylog_create(index)) {
+		index->updating = FALSE;
+		mail_index_close(index);
+		return FALSE;
+	}
+	index->updating = FALSE;
+
+	if (!mail_index_open_init(index, update_recent, index->header)) {
+		mail_index_close(index);
+		return FALSE;
+	}
+
+	/* unlock finally */
+	if (!index->set_lock(index, MAIL_LOCK_UNLOCK)) {
+		mail_index_close(index);
+		return FALSE;
+	}
+
+        return TRUE;
+}
+
+int mail_index_open(MailIndex *index, int update_recent)
+{
+	const char *name;
+
+	i_assert(!index->opened);
+
+	name = mail_find_index(index);
+	if (name == NULL)
+		return FALSE;
+
+	if (!mail_index_open_file(index, name, update_recent))
+		return FALSE;
+
+	index->opened = TRUE;
+	return TRUE;
+}
+
+int mail_index_open_or_create(MailIndex *index, int update_recent)
+{
+	const char *name;
+	int failed, dir_unlocked;
+
+	i_assert(!index->opened);
+
+	/* first see if it's already there */
+	name = mail_find_index(index);
+	if (name != NULL && mail_index_open_file(index, name, update_recent)) {
+		index->opened = TRUE;
+		return TRUE;
+	}
+
+	/* index wasn't found or it was broken. get exclusive lock and check
+	   again, just to make sure we don't end up having two index files
+	   due to race condition with another process. */
+	if (!mail_index_lock_dir(index, MAIL_LOCK_EXCLUSIVE))
+		return FALSE;
+
+	name = mail_find_index(index);
+	if (name == NULL || !mail_index_open_file(index, name, update_recent)) {
+		/* create/rebuild index */
+		failed = !mail_index_create(index, &dir_unlocked,
+					    update_recent);
+	} else {
+		dir_unlocked = FALSE;
+		failed = FALSE;
+	}
+
+	if (!dir_unlocked && !mail_index_lock_dir(index, MAIL_LOCK_UNLOCK))
+		return FALSE;
+
+	if (failed)
+		return FALSE;
+
+	index->opened = TRUE;
+	return TRUE;
+}
+
+static MailIndexRecord *mail_index_lookup_mapped(MailIndex *index,
+						 unsigned int lookup_seq)
+{
+	MailIndexHeader *hdr;
+	MailIndexRecord *rec, *last_rec;
+	unsigned int seq;
+	off_t seekpos;
+
+	if (lookup_seq == index->last_lookup_seq &&
+	    index->last_lookup != NULL && index->last_lookup->uid != 0) {
+		/* wanted the same record as last time */
+		return index->last_lookup;
+	}
+
+	hdr = index->mmap_base;
+	rec = (MailIndexRecord *) ((char *) index->mmap_base +
+				   sizeof(MailIndexHeader));
+
+	seekpos = sizeof(MailIndexHeader) +
+		(off_t)(lookup_seq-1) * sizeof(MailIndexRecord);
+	if ((size_t) seekpos > index->mmap_length - sizeof(MailIndexRecord)) {
+		/* out of range */
+		return NULL;
+	}
+
+	if (hdr->first_hole_position == 0 ||
+	    hdr->first_hole_position > seekpos) {
+		/* easy, it's just at the expected index */
+		rec += lookup_seq-1;
+		if (rec->uid == 0) {
+			index_set_error(index, "Error in index file %s: "
+					"first_hole_position wasn't updated "
+					"properly", index->filepath);
+			INDEX_MARK_CORRUPTED(index);
+			return NULL;
+		}
+		return rec;
+	}
+
+	/* we need to walk through the index to get to wanted position */
+	last_rec = rec + (index->mmap_length-sizeof(MailIndexHeader)) /
+		sizeof(MailIndexRecord);
+
+	if (lookup_seq > index->last_lookup_seq && index->last_lookup != NULL) {
+		/* we want to lookup data after last lookup -
+		   this helps us some */
+		rec = index->last_lookup;
+		seq = index->last_lookup_seq;
+	} else {
+		/* some mails are deleted, jump after the first known hole
+		   and start counting non-deleted messages.. */
+		i_assert(hdr->first_hole_records > 0);
+
+		seq = INDEX_POSITION_INDEX(hdr->first_hole_position + 1) + 1;
+		rec += seq-1 + hdr->first_hole_records;
+	}
+
+	if (rec == last_rec)
+		return NULL;
+
+	while (seq < lookup_seq || rec->uid == 0) {
+		if (rec->uid != 0)
+			seq++;
+
+		if (rec == last_rec) {
+			/* out of range */
+			return NULL;
+		}
+		rec++;
+	}
+
+	return rec;
+}
+
+MailIndexHeader *mail_index_get_header(MailIndex *index)
+{
+	i_assert(index->lock_type != MAIL_LOCK_UNLOCK);
+
+	return index->header;
+}
+
+MailIndexRecord *mail_index_lookup(MailIndex *index, unsigned int seq)
+{
+	i_assert(seq > 0);
+	i_assert(index->lock_type != MAIL_LOCK_UNLOCK);
+
+	if (!mmap_update(index))
+		return NULL;
+
+	index->last_lookup = mail_index_lookup_mapped(index, seq);
+	index->last_lookup_seq = seq;
+	return index->last_lookup;
+}
+
+MailIndexRecord *mail_index_next(MailIndex *index, MailIndexRecord *rec)
+{
+	MailIndexRecord *end_rec;
+
+	i_assert(!index->dirty_mmap);
+	i_assert(index->lock_type != MAIL_LOCK_UNLOCK);
+
+	if (rec == NULL)
+		return NULL;
+
+	/* go to the next non-deleted record */
+	end_rec = (MailIndexRecord *) ((char *) index->mmap_base +
+				       index->mmap_length);
+	while (++rec < end_rec) {
+		if (rec->uid != 0)
+			return rec;
+	}
+
+	return NULL;
+}
+
+MailIndexRecord *mail_index_lookup_uid_range(MailIndex *index,
+					     unsigned int first_uid,
+					     unsigned int last_uid)
+{
+	MailIndexRecord *rec, *end_rec;
+	unsigned int uid, last_try_uid;
+	off_t pos;
+
+	i_assert(index->lock_type != MAIL_LOCK_UNLOCK);
+	i_assert(first_uid > 0 && last_uid > 0);
+
+	if (first_uid > last_uid)
+		return NULL;
+
+	if (!mmap_update(index))
+		return NULL;
+
+	/* try the few first with hash lookups */
+	last_try_uid = last_uid - first_uid < 10 ? last_uid : first_uid + 4;
+	for (uid = first_uid; uid <= last_try_uid; uid++) {
+		pos = mail_hash_lookup_uid(index->hash, uid);
+		if (pos != 0) {
+			return (MailIndexRecord *)
+				((char *) index->mmap_base + pos);
+		}
+	}
+
+	if (last_try_uid == last_uid)
+		return NULL;
+
+	/* fallback to looking through the whole index - this shouldn't be
+	   needed often, so don't bother trying anything too fancy. */
+	rec = (MailIndexRecord *) ((char *) index->mmap_base +
+				   sizeof(MailIndexHeader));
+	end_rec = (MailIndexRecord *) ((char *) index->mmap_base +
+				       index->mmap_length);
+	while (rec < end_rec) {
+		if (rec->uid != 0) {
+			if (rec->uid > last_uid)
+				return NULL;
+
+			if (rec->uid >= first_uid)
+				return rec;
+		}
+		rec++;
+	}
+
+	return NULL;
+}
+
+const char *mail_index_lookup_field(MailIndex *index, MailIndexRecord *rec,
+				    MailField field)
+{
+	MailIndexDataRecord *datarec;
+
+	i_assert(index->lock_type != MAIL_LOCK_UNLOCK);
+
+	/* first check if the field even could be in the file */
+	if ((rec->cached_fields & field) != field) {
+		/* no, but make sure the future records will have it.
+		   we don't immediately mark the index to cache this field
+		   for old messages as some clients never ask the info again */
+		index->set_cache_fields |= field;
+		return NULL;
+	}
+
+	datarec = mail_index_data_lookup(index->data, rec, field);
+	if (datarec != NULL) {
+		if (mail_index_data_record_verify(index->data, datarec))
+			return datarec->data;
+
+		/* index is corrupted, it will be rebuilt */
+	} else {
+		/* field doesn't exist, make sure it willl be added later
+		   when there's time */
+                index->set_flags |= MAIL_INDEX_FLAG_CACHE_FIELDS;
+	}
+
+	return NULL;
+}
+
+unsigned int mail_index_get_sequence(MailIndex *index, MailIndexRecord *rec)
+{
+	MailIndexRecord *seekrec;
+	unsigned int seq;
+
+	i_assert(index->lock_type != MAIL_LOCK_UNLOCK);
+
+	if (rec == index->last_lookup) {
+		/* same as last lookup sequence - too easy */
+		return index->last_lookup_seq;
+	}
+
+	if (index->header->first_hole_position == 0) {
+		/* easy, it's just at the expected index */
+		return INDEX_POSITION_INDEX(
+			INDEX_FILE_POSITION(index, rec)) + 1;
+	}
+
+	seekrec = (MailIndexRecord *) ((char *) index->mmap_base +
+				       index->header->first_hole_position);
+	if (rec < seekrec) {
+		/* record before first hole */
+		return INDEX_POSITION_INDEX(
+			INDEX_FILE_POSITION(index, rec)) + 1;
+	}
+
+	/* we know the sequence after the first hole - skip to there and
+	   start browsing the records until ours is found */
+	seq = INDEX_POSITION_INDEX(INDEX_FILE_POSITION(index, seekrec))+1;
+	seekrec += index->header->first_hole_records;
+
+	for (; seekrec != rec; seekrec++) {
+		if (seekrec->uid != 0)
+			seq++;
+	}
+
+	return seq;
+}
+
+static void update_first_hole_records(MailIndex *index)
+{
+        MailIndexRecord *rec, *end_rec;
+
+	/* see if first_hole_records can be grown */
+	rec = (MailIndexRecord *) ((char *) index->mmap_base +
+				   index->header->first_hole_position) +
+		index->header->first_hole_records;
+	end_rec = (MailIndexRecord *) ((char *) index->mmap_base +
+				       index->mmap_length);
+	while (rec != end_rec && rec->uid == 0) {
+		index->header->first_hole_records++;
+		rec++;
+	}
+}
+
+static void index_mark_flag_changes(MailIndex *index, MailIndexRecord *rec,
+				    MailFlags old_flags, MailFlags new_flags)
+{
+	if ((old_flags & MAIL_SEEN) == 0 && (new_flags & MAIL_SEEN)) {
+		/* unseen -> seen */
+		index->header->seen_messages_count++;
+	} else if ((old_flags & MAIL_SEEN) && (new_flags & MAIL_SEEN) == 0) {
+		/* seen -> unseen */
+		if (index->header->seen_messages_count ==
+		    index->header->messages_count) {
+			/* this is the first unseen message */
+                        index->header->first_unseen_uid_lowwater = rec->uid;
+		} else if (rec->uid < index->header->first_unseen_uid_lowwater)
+			index->header->first_unseen_uid_lowwater = rec->uid;
+
+		index->header->seen_messages_count--;
+	} else if ((old_flags & MAIL_DELETED) == 0 &&
+		   (new_flags & MAIL_DELETED)) {
+		/* undeleted -> deleted */
+		index->header->deleted_messages_count++;
+
+		if (index->header->deleted_messages_count == 1) {
+			/* this is the first deleted message */
+			index->header->first_deleted_uid_lowwater = rec->uid;
+		} else if (rec->uid < index->header->first_deleted_uid_lowwater)
+			index->header->first_deleted_uid_lowwater = rec->uid;
+	} else if ((old_flags & MAIL_DELETED) &&
+		   (new_flags & MAIL_DELETED) == 0) {
+		/* deleted -> undeleted */
+		index->header->deleted_messages_count--;
+	}
+}
+
+int mail_index_expunge(MailIndex *index, MailIndexRecord *rec,
+		       unsigned int seq, int external_change)
+{
+	MailIndexHeader *hdr;
+	off_t pos;
+
+	i_assert(index->lock_type == MAIL_LOCK_EXCLUSIVE);
+	i_assert(rec->uid != 0);
+
+	if (seq != 0) {
+		if (!mail_modifylog_add_expunge(index->modifylog, seq,
+						rec->uid, external_change))
+			return FALSE;
+	}
+
+	mail_hash_update(index->hash, rec->uid, 0);
+
+	/* setting UID to 0 is enough for deleting the mail from index */
+	rec->uid = 0;
+
+	/* update last_lookup_seq */
+	if (seq != 0) {
+		/* note that last_lookup can be left to point to
+		   invalid record so that next() works properly */
+		if (seq == index->last_lookup_seq)
+			index->last_lookup = NULL;
+		else if (seq < index->last_lookup_seq)
+			index->last_lookup_seq--;
+	}
+
+	hdr = index->header;
+
+	/* update first hole */
+	pos = INDEX_FILE_POSITION(index, rec);
+	if (hdr->first_hole_position == 0) {
+		/* first deleted message in index */
+		hdr->first_hole_position = pos;
+		hdr->first_hole_records = 1;
+	} else if (hdr->first_hole_position -
+		   sizeof(MailIndexRecord) == (size_t) pos) {
+		/* deleted the previous record before hole */
+		hdr->first_hole_position -= sizeof(MailIndexRecord);
+		hdr->first_hole_records++;
+	} else if (hdr->first_hole_position +
+		   (hdr->first_hole_records *
+		    sizeof(MailIndexRecord)) == (size_t) pos) {
+		/* deleted the next record after hole */
+		hdr->first_hole_records++;
+		update_first_hole_records(index);
+	} else {
+		/* second hole coming to index file, the index now needs to
+		   be compressed to keep high performance */
+		index->set_flags |= MAIL_INDEX_FLAG_COMPRESS;
+
+		if (hdr->first_hole_position > pos) {
+			/* new hole before the old hole */
+			hdr->first_hole_position = pos;
+			hdr->first_hole_records = 1;
+		}
+	}
+
+	/* update message counts */
+	hdr->messages_count--;
+	index_mark_flag_changes(index, rec, rec->msg_flags, 0);
+
+	/* update deleted_space in data file */
+	(void)mail_index_data_add_deleted_space(index->data, rec->data_size);
+
+	return TRUE;
+}
+
+int mail_index_update_flags(MailIndex *index, MailIndexRecord *rec,
+			    unsigned int seq, MailFlags flags,
+			    int external_change)
+{
+	i_assert(index->lock_type == MAIL_LOCK_EXCLUSIVE);
+	i_assert(seq != 0);
+
+	if (flags == rec->msg_flags)
+		return TRUE; /* no changes */
+
+        index_mark_flag_changes(index, rec, rec->msg_flags, flags);
+
+	rec->msg_flags = flags;
+	return mail_modifylog_add_flags(index->modifylog, seq,
+					rec->uid, external_change);
+}
+
+int mail_index_append(MailIndex *index, MailIndexRecord **rec)
+{
+	off_t pos;
+
+	i_assert(index->lock_type == MAIL_LOCK_EXCLUSIVE);
+
+	(*rec)->uid = index->header->next_uid++;
+
+	pos = lseek(index->fd, 0, SEEK_END);
+	if (pos == (off_t)-1) {
+		index_set_error(index, "lseek() failed with file %s: %m",
+				index->filepath);
+		return FALSE;
+	}
+
+	if (write(index->fd, *rec, sizeof(MailIndexRecord)) !=
+	    sizeof(MailIndexRecord)) {
+		index_set_error(index, "Error appending to file %s: %m",
+				index->filepath);
+		return FALSE;
+	}
+
+	index->header->messages_count++;
+        index_mark_flag_changes(index, *rec, 0, (*rec)->msg_flags);
+
+	if (index->hash != NULL)
+		mail_hash_update(index->hash, (*rec)->uid, pos);
+
+	index->dirty_mmap = TRUE;
+	if (!mmap_update(index))
+		return FALSE;
+
+	*rec = (MailIndexRecord *) ((char *) index->mmap_base + pos);
+	return TRUE;
+}
+
+const char *mail_index_get_last_error(MailIndex *index)
+{
+	return index->error;
+}
+
+int mail_index_is_inconsistency_error(MailIndex *index)
+{
+	return index->inconsistent;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mail-index.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,356 @@
+#ifndef __MAIL_INDEX_H
+#define __MAIL_INDEX_H
+
+#include "message-parser.h"
+#include "imap-util.h"
+
+#define MAIL_INDEX_VERSION 1
+
+#define INDEX_FILE_PREFIX ".imap.index"
+
+enum {
+	MAIL_INDEX_COMPAT_LITTLE_ENDIAN	= 0x01
+};
+
+enum {
+	/* Rebuild flag is set while index is being rebuilt or when
+	   some error is noticed in the index file. If this flag is set,
+	   the index shouldn't be used before rebuilding it. */
+	MAIL_INDEX_FLAG_REBUILD		= 0x01,
+	MAIL_INDEX_FLAG_FSCK		= 0x02,
+	MAIL_INDEX_FLAG_CACHE_FIELDS	= 0x04,
+	MAIL_INDEX_FLAG_COMPRESS	= 0x08,
+	MAIL_INDEX_FLAG_COMPRESS_DATA	= 0x10,
+	MAIL_INDEX_FLAG_REBUILD_HASH	= 0x20
+};
+
+typedef enum {
+	/* First MUST become a field that ALWAYS exists. This is because some
+	   code which goes through all fields does it by calling
+	   lookup_field(.., .., 1) and next() after that. If the first field
+	   didn't exist, nothing would be found.
+
+	   Location field is a good first field anyway, it's the one most
+	   often needed. With maildir format, it's the file name and with
+	   mbox format it's the file position as a string. */
+	FIELD_TYPE_LOCATION		= 0x0001,
+	FIELD_TYPE_ENVELOPE		= 0x0002,
+	FIELD_TYPE_BODY			= 0x0004,
+	FIELD_TYPE_BODYSTRUCTURE	= 0x0008,
+	FIELD_TYPE_MESSAGEID		= 0x0010,
+	FIELD_TYPE_FROM			= 0x0020,
+	FIELD_TYPE_TO			= 0x0040,
+	FIELD_TYPE_CC			= 0x0080,
+	FIELD_TYPE_BCC			= 0x0100,
+	FIELD_TYPE_SUBJECT		= 0x0200,
+
+	FIELD_TYPE_LAST			= 0x0400,
+	FIELD_TYPE_MAX_BITS		= 10
+} MailField;
+
+#define IS_HEADER_FIELD(field) \
+	(((field) & (FIELD_TYPE_MESSAGEID | FIELD_TYPE_FROM | FIELD_TYPE_TO | \
+		     FIELD_TYPE_CC | FIELD_TYPE_BCC | FIELD_TYPE_SUBJECT)) != 0)
+
+typedef enum {
+	MAIL_LOCK_UNLOCK = 0,
+	MAIL_LOCK_SHARED,
+	MAIL_LOCK_EXCLUSIVE
+} MailLockType;
+
+typedef struct _MailIndex MailIndex;
+typedef struct _MailIndexData MailIndexData;
+typedef struct _MailHash MailHash;
+typedef struct _MailModifyLog MailModifyLog;
+
+typedef struct _MailIndexHeader MailIndexHeader;
+typedef struct _MailIndexDataHeader MailIndexDataHeader;
+
+typedef struct _MailIndexRecord MailIndexRecord;
+typedef struct _MailIndexDataRecord MailIndexDataRecord;
+
+typedef struct _MailIndexUpdate MailIndexUpdate;
+
+struct _MailIndexHeader {
+	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;
+	unsigned int first_hole_records;
+
+	unsigned int uid_validity;
+	unsigned int next_uid;
+
+	unsigned int messages_count;
+	unsigned int seen_messages_count;
+	unsigned int deleted_messages_count;
+	unsigned int last_nonrecent_uid;
+
+	/* these UIDs may not exist and may not even be unseen */
+	unsigned int first_unseen_uid_lowwater;
+	unsigned int first_deleted_uid_lowwater;
+
+	unsigned int reserved_for_future_usage[5];
+};
+
+struct _MailIndexDataHeader {
+	unsigned int indexid;
+	off_t deleted_space;
+};
+
+struct _MailIndexRecord {
+	unsigned int uid;
+	unsigned int msg_flags; /* MailFlags */
+	time_t internal_date;
+	time_t sent_date;
+
+	off_t data_position;
+	unsigned int data_size;
+
+	unsigned int cached_fields;
+	unsigned int header_size;
+	unsigned int body_size;
+	unsigned int full_virtual_size;
+};
+
+#define MSG_HAS_VALID_CRLF_DATA(rec) \
+	((rec)->header_size + (rec)->body_size == (rec)->full_virtual_size)
+
+struct _MailIndexDataRecord {
+	unsigned int field; /* MailField */
+	unsigned int full_field_size;
+	char data[MEM_ALIGN_SIZE]; /* variable size */
+};
+
+#define DATA_RECORD_SIZE(rec) \
+        (sizeof(MailIndexDataRecord) - MEM_ALIGN_SIZE + (rec)->full_field_size)
+
+struct _MailIndex {
+	int (*open)(MailIndex *index, int update_recent);
+	int (*open_or_create)(MailIndex *index, int update_recent);
+
+	/* Free index from memory. */
+	void (*free)(MailIndex *index);
+
+	/* Lock/unlock index. May block. Note that unlocking must not
+	   reset error from get_last_error() as unlocking can be done as
+	   a cleanup after some other function failed. Index is always
+	   mmap()ed after set_lock() succeeds.
+
+	   Trying to change a shared lock into exclusive lock is a fatal
+	   error, since it may create a deadlock. Even though operating
+	   system should detect it and fail, it's not a good idea to even
+	   let it happen. Better ways to do this would be to a) mark the
+	   data to be updated later, b) use try_lock() if the update is
+	   preferred but not required, c) unlock + lock again, but make
+	   sure that won't create race conditions */
+	int (*set_lock)(MailIndex *index, MailLockType lock_type);
+
+	/* Try locking the index. Returns TRUE if the lock was got and
+	   FALSE if lock isn't possible to get currently or some other error
+	   occured. Never blocks. */
+	int (*try_lock)(MailIndex *index, MailLockType lock_type);
+
+	/* Rebuild the whole index. Note that this changes the indexid
+	   so all the other files must also be rebuilt after this call.
+	   Index MUST NOT have shared lock, exclusive lock or no lock at all
+	   is fine. Note that this function may leave the index exclusively
+	   locked. */
+	int (*rebuild)(MailIndex *index);
+
+	/* Verify that the index is valid. If anything invalid is found,
+	   rebuild() is called. Same locking issues as with rebuild(). */
+	int (*fsck)(MailIndex *index);
+
+	/* Synchronize the index with the mailbox. Same locking issues as
+	   with rebuild(). */
+	int (*sync)(MailIndex *index);
+
+	/* Returns the index header (never fails). The index needs to be
+	   locked before calling this function, and must be kept locked as
+	   long as you keep using the returned structure. */
+	MailIndexHeader *(*get_header)(MailIndex *index);
+
+	/* sequence -> data lookup. The index needs to be locked before calling
+	   this function, and must be kept locked as long as you keep using
+	   the returned structure. */
+	MailIndexRecord *(*lookup)(MailIndex *index, unsigned int seq);
+
+	/* Return the next record after specified record, or NULL if it was
+	   last record. The index must be locked all the time between
+	   lookup() and last next() call. */
+	MailIndexRecord *(*next)(MailIndex *index, MailIndexRecord *rec);
+
+	/* First first existing UID in range. */
+	MailIndexRecord *(*lookup_uid_range)(MailIndex *index,
+					     unsigned int first_uid,
+					     unsigned int last_uid);
+
+	/* Find field from specified record, or NULL if it's not in index. */
+	const char *(*lookup_field)(MailIndex *index, MailIndexRecord *rec,
+				    MailField field);
+
+	/* Returns sequence for given message. */
+	unsigned int (*get_sequence)(MailIndex *index, MailIndexRecord *rec);
+
+	/* Open mail file lseek()ed to beginning position and return the
+	   file handle. */
+	int (*open_mail)(MailIndex *index, MailIndexRecord *rec,
+			 off_t *offset, size_t *size);
+
+	/* Expunge a mail from index. Hash and modifylog is also updated. The
+	   index must be exclusively locked before calling this function.
+	   If seq is 0, the modify log isn't updated. This is useful if
+	   after append() something goes wrong and you wish to delete the
+	   mail immediately. If external_change is TRUE, the modify log is
+	   always written.
+
+	   Note that the sequence numbers also update immediately after this
+	   call, so if you want to delete messages 1..4 just call this
+	   function 4 times with seq being 1. */
+	int (*expunge)(MailIndex *index, MailIndexRecord *rec,
+		       unsigned int seq, int external_change);
+
+	/* Update mail flags. The index must be exclusively locked before
+	   calling this function. This shouldn't be called in the middle of
+	   update_begin() as it may modify location field. */
+	int (*update_flags)(MailIndex *index, MailIndexRecord *rec,
+			    unsigned int seq, MailFlags flags,
+			    int external_change);
+
+	/* Append a new record to index. The index must be exclusively
+	   locked before calling this function. The record pointer is
+	   updated to the mmap()ed record. rec->uid field is updated by this
+	   function, nothing else is touched. */
+	int (*append)(MailIndex *index, MailIndexRecord **rec);
+
+	/* Updating fields happens by calling update_begin(), one or more
+	   update_field()s and finally update_end() which does the actual
+	   updating. The index must be exclusively locked all this time.
+	   update_begin() and update_field() functions cannot fail.
+
+	   The extra_space parameter for update_field() specifies the amount
+	   of extra empty space we should leave after the value, so that if
+	   the field grows in future it could be expanded without copying it
+	   to end of file. When the field already exists, the extra_space
+	   is ignored.
+
+	   The files may not actually be updated until after you've unlocked
+	   the file. */
+	MailIndexUpdate *(*update_begin)(MailIndex *index,
+					 MailIndexRecord *rec);
+	int (*update_end)(MailIndexUpdate *update);
+
+	void (*update_field)(MailIndexUpdate *update, MailField field,
+			     const char *value, unsigned int extra_space);
+
+	/* Returns last error message */
+	const char *(*get_last_error)(MailIndex *index);
+
+	/* Returns TRUE if index is now in inconsistent state with the
+	   previous known state, meaning that the message IDs etc. may
+	   have changed - only way to recover this would be to fully close
+	   the mailbox and reopen it. With IMAP connection this would mean
+	   a forced disconnection since we can't do forced CLOSE. */
+	int (*is_inconsistency_error)(MailIndex *index);
+
+/* private: */
+	MailIndexData *data;
+	MailHash *hash;
+	MailModifyLog *modifylog;
+
+	char *dir; /* directory where to place the index files */
+	char *filepath; /* index file path */
+	unsigned int indexid;
+
+	char *mbox_path; /* mbox-specific path to the actual mbox file */
+	off_t mbox_size; /* last synced size of mbox file */
+	int mbox_locks;
+
+	int fd; /* opened index file */
+	char *error; /* last error message */
+
+	void *mmap_base;
+	size_t mmap_length;
+
+        MailLockType lock_type;
+
+	MailIndexHeader *header;
+	MailIndexRecord *last_lookup;
+	unsigned int last_lookup_seq;
+	unsigned int first_recent_uid;
+
+	unsigned int modifylog_id;
+	time_t file_sync_stamp;
+
+	/* these fields are OR'ed to the fields in index header once we
+	   get around grabbing exclusive lock */
+	unsigned int set_flags;
+	unsigned int set_cache_fields;
+
+	unsigned int opened:1;
+	unsigned int updating:1;
+	unsigned int inconsistent:1;
+	unsigned int dirty_mmap:1;
+};
+
+/* defaults - same as above but prefixed with mail_index_. */
+int mail_index_open(MailIndex *index, int update_recent);
+int mail_index_open_or_create(MailIndex *index, int update_recent);
+int mail_index_set_lock(MailIndex *index, MailLockType lock_type);
+int mail_index_try_lock(MailIndex *index, MailLockType lock_type);
+int mail_index_fsck(MailIndex *index);
+MailIndexHeader *mail_index_get_header(MailIndex *index);
+MailIndexRecord *mail_index_lookup(MailIndex *index, unsigned int seq);
+MailIndexRecord *mail_index_next(MailIndex *index, MailIndexRecord *rec);
+MailIndexRecord *mail_index_lookup_uid_range(MailIndex *index,
+					     unsigned int first_uid,
+					     unsigned int last_uid);
+const char *mail_index_lookup_field(MailIndex *index, MailIndexRecord *rec,
+				    MailField field);
+unsigned int mail_index_get_sequence(MailIndex *index, MailIndexRecord *rec);
+int mail_index_expunge(MailIndex *index, MailIndexRecord *rec,
+		       unsigned int seq, int external_change);
+int mail_index_update_flags(MailIndex *index, MailIndexRecord *rec,
+			    unsigned int seq, MailFlags flags,
+			    int external_change);
+int mail_index_append(MailIndex *index, MailIndexRecord **rec);
+MailIndexUpdate *mail_index_update_begin(MailIndex *index,
+					 MailIndexRecord *rec);
+int mail_index_update_end(MailIndexUpdate *update);
+void mail_index_update_field(MailIndexUpdate *update, MailField field,
+			     const char *value, unsigned int extra_space);
+const char *mail_index_get_last_error(MailIndex *index);
+int mail_index_is_inconsistency_error(MailIndex *index);
+
+/* INTERNAL: */
+void mail_index_init_header(MailIndexHeader *hdr);
+void mail_index_close(MailIndex *index);
+int mail_index_rebuild_all(MailIndex *index);
+int mail_index_sync_file(MailIndex *index);
+void mail_index_update_headers(MailIndexUpdate *update,
+			       const char *msg, size_t size,
+			       MessageHeaderFunc header_func, void *user_data);
+
+/* off_t to index file for given record */
+#define INDEX_FILE_POSITION(index, ptr) \
+	((off_t) ((char *) (ptr) - (char *) ((index)->mmap_base)))
+
+/* index number for off_t position */
+#define INDEX_POSITION_INDEX(pos) \
+	(((pos) - sizeof(MailIndexHeader)) / sizeof(MailIndexRecord))
+
+/* mark the index corrupted */
+#define INDEX_MARK_CORRUPTED(index) \
+	STMT_START { (index)->set_flags |= MAIL_INDEX_FLAG_REBUILD; } STMT_END
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mail-lockdir.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,158 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "hostpid.h"
+#include "unlink-lockfiles.h"
+#include "mail-index.h"
+#include "mail-index-util.h"
+#include "mail-lockdir.h"
+
+#include <time.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#define DIRLOCK_FILE_PREFIX ".imap.dirlock"
+
+/* 0.1 .. 0.2msec */
+#define LOCK_RANDOM_USLEEP_TIME (100000 + (unsigned int)rand() % 100000)
+
+/* The dirlock should be used only while creating the index file. After the
+   header is written, the file itself should be locked and dirlock dropped
+   before index is built. So, this value shouldn't be very large, probably
+   even a few seconds would more than enough but we'll use a safe 10 seconds
+   by default. */
+#define MAX_LOCK_WAIT_SECONDS 10
+
+/* Non-local locks have a life time of 30 minutes, just to be sure that
+   small clock differences won't break things. */
+#define NFS_LOCK_TIMEOUT (60*30)
+
+static int mail_index_cleanup_dir_locks(const char *dir)
+{
+	const char *hostprefix, *path;
+	struct stat st;
+
+	hostprefix = t_strconcat(DIRLOCK_FILE_PREFIX ".",
+				 my_hostname, ".", NULL);
+
+	unlink_lockfiles(dir, hostprefix, DIRLOCK_FILE_PREFIX ".",
+			 time(NULL) - NFS_LOCK_TIMEOUT);
+
+	/* if hard link count has dropped to 1, we've unlocked the file */
+	path = t_strconcat(dir, "/" DIRLOCK_FILE_PREFIX, NULL);
+	if (stat(path, &st) == 0 && st.st_nlink == 1) {
+		/* only itself, safe to delete */
+		(void)unlink(path);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static int mail_index_unlock_dir(MailIndex *index, const char *path,
+				 const char *lockpath)
+{
+	struct stat st, lockst;
+
+	if (stat(lockpath, &st) != 0) {
+		index_set_error(index, "stat() failed for lock file %s: %m",
+				lockpath);
+		return FALSE;
+	}
+
+	if (st.st_nlink > 1) {
+		/* make sure we're really the one who's locked it */
+		if (stat(path, &lockst) != 0) {
+			index_set_error(index, "stat() failed for lock file "
+					"%s: %m", path);
+			return FALSE;
+		}
+
+		if (st.st_dev != lockst.st_dev ||
+		    st.st_ino != lockst.st_ino) {
+			index_set_error(index, "Unlocking file %s failed: "
+					"we're not the lock owner "
+					"(%lu,%lu vs %lu,%lu)", lockpath,
+					(unsigned long) st.st_dev,
+					(unsigned long) st.st_ino,
+					(unsigned long) lockst.st_dev,
+					(unsigned long) lockst.st_ino);
+			return FALSE;
+		}
+	}
+
+	/* first unlink the actual lock file */
+	if (unlink(lockpath) == -1) {
+		index_set_error(index, "unlink() failed for lock file %s: %m",
+				lockpath);
+		return FALSE;
+	}
+
+	(void)unlink(path);
+	return TRUE;
+}
+
+int mail_index_lock_dir(MailIndex *index, MailLockType lock_type)
+{
+	struct stat st;
+	const char *path, *lockpath;
+	int fd, orig_errno, first;
+	time_t max_wait_time;
+
+	i_assert(lock_type == MAIL_LOCK_EXCLUSIVE ||
+		 lock_type == MAIL_LOCK_UNLOCK);
+
+	hostpid_init();
+
+	/* use .dirlock.host.pid as our lock indicator file and
+	   .dirlock as the real lock */
+	path = t_strconcat(index->dir, "/" DIRLOCK_FILE_PREFIX ".",
+			   my_hostname, ".", my_pid, NULL);
+	lockpath = t_strconcat(index->dir, "/" DIRLOCK_FILE_PREFIX, NULL);
+
+	if (lock_type == MAIL_LOCK_UNLOCK)
+		return mail_index_unlock_dir(index, path, lockpath);
+
+	(void)unlink(path);
+	fd = open(path, O_RDWR | O_CREAT | O_EXCL, 0660);
+	if (fd == -1) {
+		index_set_error(index, "Can't create lock file %s: %m", path);
+		return FALSE;
+	}
+
+	/* try to link the file into lock file. */
+	first = TRUE; max_wait_time = time(NULL) + MAX_LOCK_WAIT_SECONDS;
+	while (link(path, lockpath) == -1) {
+		if (errno != EEXIST) {
+			orig_errno = errno;
+
+			/* NFS may die and link() fail even if it really
+			   was created */
+			if (stat(path, &st) == 0 && st.st_nlink == 2)
+				break;
+
+			index_set_error(index, "link(%s, %s) lock failed: %m",
+					path, lockpath);
+			return FALSE;
+		}
+
+		if (first) {
+			/* cleanup lock files once */
+			first = FALSE;
+			if (mail_index_cleanup_dir_locks(index->dir))
+				continue; /* lock was deleted, try again */
+		}
+
+		if (time(NULL) > max_wait_time) {
+			index_set_error(index, "Timeout waiting lock in "
+					"directory %s", index->dir);
+			return FALSE;
+		}
+
+		usleep(LOCK_RANDOM_USLEEP_TIME);
+	}
+
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mail-lockdir.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,7 @@
+#ifndef __MAIL_LOCKDIR_H
+#define __MAIL_LOCKDIR_H
+
+/* Exclusively lock whole directory where index is located. */
+int mail_index_lock_dir(MailIndex *index, MailLockType lock_type);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mail-messageset.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,311 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "mail-index.h"
+#include "mail-index-util.h"
+#include "mail-hash.h"
+#include "mail-modifylog.h"
+#include "mail-messageset.h"
+
+static unsigned int get_next_number(const char **str)
+{
+	unsigned int num;
+
+	num = 0;
+	while (**str != '\0') {
+		if (**str < '0' || **str > '9')
+			break;
+
+		num = num*10 + **str - '0';
+		(*str)++;
+	}
+
+	return num;
+}
+
+static int mail_index_foreach(MailIndex *index,
+			      unsigned int seq, unsigned int seq2,
+			      MsgsetForeachFunc func, void *user_data)
+{
+	MailIndexRecord *rec;
+	const unsigned int *expunges;
+	unsigned int expunges_before;
+	int expunges_found;
+
+	if (seq > seq2) {
+		/* Second sequence can't be smaller than first - we could swap
+		   them but I think it's a bug in client if it does this,
+		   and better complain about it immediately than later let
+		   them wonder why it doesn't work with other imapds.. */
+		index_set_error(index, "Invalid messageset range: %u > %u",
+				seq, seq2);
+		return -1;
+	}
+
+	/* get list of expunged messages in our range. the expunges_before
+	   can be used to calculate the current real sequence position */
+	expunges = mail_modifylog_seq_get_expunges(index->modifylog, seq, seq2,
+						   &expunges_before);
+	i_assert(expunges_before < seq);
+	expunges_found = *expunges != '\0';
+
+	/* get the first non-expunged message. note that if all messages
+	   were expunged in the range, this points outside wanted range. */
+	rec = index->lookup(index, seq - expunges_before);
+	for (; rec != NULL; seq++) {
+		/* skip expunged sequences */
+		i_assert(rec->uid != 0);
+
+		while (*expunges != 0 && *expunges < rec->uid) {
+			expunges++;
+			seq++;
+		}
+		i_assert(*expunges != rec->uid);
+
+		if (seq > seq2)
+			break;
+
+		if (!func(index, rec, seq, user_data))
+			return 0;
+
+		rec = index->next(index, rec);
+	}
+
+	if (rec == NULL && index->get_last_error(index) != NULL) {
+		/* error occured */
+		return -1;
+	}
+
+	return !expunges_found && seq > seq2 ? 1 : 2;
+}
+
+int mail_index_messageset_foreach(MailIndex *index, const char *messageset,
+				  unsigned int messages_count,
+				  MsgsetForeachFunc func, void *user_data)
+{
+	const char *input;
+	unsigned int seq, seq2;
+	int ret, all_found;
+
+	i_assert(index->lock_type != MAIL_LOCK_UNLOCK);
+
+	if (messages_count == 0) {
+		/* no messages in mailbox */
+		return 1;
+	}
+
+	all_found = TRUE;
+	input = messageset;
+	while (*input != '\0') {
+		if (*input == '*') {
+			/* last message */
+			seq = messages_count;
+			input++;
+		} else {
+			seq = get_next_number(&input);
+			if (seq == 0) {
+				index_set_error(index, "Invalid messageset: "
+						"%s", messageset);
+				return -1;
+			}
+		}
+
+		if (*input != ':')
+			seq2 = seq;
+		else {
+			/* first:last range */
+			input++;
+
+			if (*input != '*') {
+				seq2 = get_next_number(&input);
+				if (seq2 == 0) {
+					index_set_error(index, "Invalid "
+							"messageset: %s",
+							messageset);
+					return -1;
+				}
+
+				if (seq2 > messages_count) {
+					/* too large .. ignore silently */
+					seq2 = messages_count;
+				}
+			} else {
+				seq2 = messages_count;
+				input++;
+			}
+		}
+
+		if (*input == ',')
+			input++;
+		else if (*input != '\0') {
+			index_set_error(index, "Unexpected char '%c' "
+					"with messageset: %s",
+					*input, messageset);
+			return -1;
+		}
+
+		if (seq > messages_count) {
+			/* too large .. ignore silently */
+		} else {
+			ret = mail_index_foreach(index, seq, seq2,
+						 func, user_data);
+			if (ret <= 0)
+				return ret;
+			if (ret == 2)
+				all_found = FALSE;
+		}
+	}
+
+	return all_found ? 1 : 2;
+}
+
+static int mail_index_uid_foreach(MailIndex *index,
+				  unsigned int uid, unsigned int uid2,
+				  unsigned int max_sequence,
+				  MsgsetForeachFunc func, void *user_data)
+{
+	MailIndexRecord *rec;
+	off_t pos;
+	const unsigned int *expunges;
+	unsigned int seq;
+	int expunges_found;
+
+	if (uid > uid2) {
+		/* not allowed - see mail_index_foreach() */
+		index_set_error(index, "Invalid uidset range: %u > %u",
+				uid, uid2);
+		return -1;
+	}
+
+	/* get list of expunged messages in our range. */
+	expunges = mail_modifylog_uid_get_expunges(index->modifylog, uid, uid2);
+	expunges_found = *expunges != '\0';
+
+	/* skip expunged messages at the beginning */
+	while (*expunges == uid) {
+		expunges++;
+
+		if (++uid == uid2) {
+			/* all were expunged */
+			return 2;
+		}
+	}
+
+	/* since we skipped the known expunged messages at the beginning
+	   and our UIDs are contiguously allocated, the first hash lookup
+	   _should_ work.. */
+	pos = mail_hash_lookup_uid(index->hash, uid);
+	if (pos != 0) {
+		rec = (MailIndexRecord *) ((char *) index->mmap_base + pos);
+	} else {
+		/* ..however if for any reason it doesn't,
+		   still handle it properly */
+		if (uid == uid2)
+			return 2;
+
+		rec = index->lookup_uid_range(index, uid+1, uid2);
+		if (rec == NULL)
+			return 2;
+	}
+
+	seq = index->get_sequence(index, rec);
+	while (rec != NULL && rec->uid <= uid2 && seq <= max_sequence) {
+		uid = rec->uid;
+		while (*expunges != 0 && *expunges < rec->uid) {
+			expunges++;
+			seq++;
+		}
+		i_assert(*expunges != rec->uid);
+
+		if (!func(index, rec, seq, user_data))
+			return 0;
+
+		seq++;
+		rec = index->next(index, rec);
+	}
+
+	if (rec == NULL && index->get_last_error(index) != NULL) {
+		/* error occured */
+		return -1;
+	}
+
+	return !expunges_found && uid == uid2 ? 1 : 2;
+}
+
+int mail_index_uidset_foreach(MailIndex *index, const char *uidset,
+			      unsigned int messages_count,
+			      MsgsetForeachFunc func, void *user_data)
+{
+	MailIndexRecord *rec;
+	const char *input;
+	unsigned int uid, uid2;
+	int ret, all_found;
+
+	i_assert(index->lock_type != MAIL_LOCK_UNLOCK);
+
+	if (messages_count == 0) {
+		/* no messages in mailbox */
+		return 1;
+	}
+
+	all_found = TRUE;
+	input = uidset;
+	while (*input != '\0') {
+		if (*input == '*') {
+			/* last message */
+			rec = index->lookup(index, messages_count);
+			uid = rec == NULL ? 0 : rec->uid;
+			input++;
+		} else {
+			uid = get_next_number(&input);
+			if (uid == 0) {
+				index_set_error(index, "Invalid uidset: %s",
+						uidset);
+				return -1;
+			}
+		}
+
+		if (*input != ':')
+			uid2 = uid;
+		else {
+			/* first:last range */
+			input++;
+
+			if (*input != '*') {
+				uid2 = get_next_number(&input);
+				if (uid2 == 0) {
+					index_set_error(index,
+							"Invalid uidset: %s",
+							uidset);
+					return -1;
+				}
+			} else {
+				uid2 = index->header->next_uid-1;
+				input++;
+			}
+		}
+
+		if (*input == ',')
+			input++;
+		else if (*input != '\0') {
+			index_set_error(index,
+					"Unexpected char '%c' with uidset: %s",
+					*input, uidset);
+			return -1;
+		}
+
+		if (uid >= index->header->next_uid) {
+			/* too large .. ignore silently */
+		} else {
+			ret = mail_index_uid_foreach(index, uid, uid2,
+						     messages_count,
+						     func, user_data);
+			if (ret <= 0)
+				return ret;
+			if (ret == 2)
+				all_found = FALSE;
+		}
+	}
+
+	return all_found ? 1 : 2;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mail-messageset.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,21 @@
+#ifndef __MAIL_MESSAGESET_H
+#define __MAIL_MESSAGESET_H
+
+#include "mail-index.h"
+
+/* If FALSE is returned, the loop is stopped. */
+typedef int (*MsgsetForeachFunc)(MailIndex *index, MailIndexRecord *rec,
+				 unsigned int seq, void *user_data);
+
+/* Returns -1 if error occured, 0 if foreach-func returned FALSE,
+   1 if everything was ok or 2 if some of the given sequences were expunged */
+int mail_index_messageset_foreach(MailIndex *index, const char *messageset,
+				  unsigned int messages_count,
+				  MsgsetForeachFunc func, void *user_data);
+
+/* Like messageset_foreach() but for UIDs. */
+int mail_index_uidset_foreach(MailIndex *index, const char *uidset,
+			      unsigned int messages_count,
+			      MsgsetForeachFunc func, void *user_data);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mail-modifylog.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,674 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "mmap-util.h"
+#include "mail-index.h"
+#include "mail-index-util.h"
+#include "mail-modifylog.h"
+
+#include <stdlib.h>
+#include <fcntl.h>
+
+/* Maximum size for modify log (isn't exact) */
+#define MAX_MODIFYLOG_SIZE 10240
+
+#define MODIFYLOG_FILE_POSITION(log, ptr) \
+	((int) ((char *) (ptr) - (char *) (log)->mmap_base))
+
+struct _MailModifyLog {
+	MailIndex *index;
+
+	int fd;
+	char *filepath;
+
+	void *mmap_base;
+	size_t mmap_length;
+
+	ModifyLogHeader *header;
+	size_t synced_position;
+	unsigned int synced_id, mmaped_id;
+
+	unsigned int modified:1;
+	unsigned int dirty_mmap:1;
+	unsigned int second_log:1;
+};
+
+static int file_lock(int fd, int wait_lock, int lock_type)
+{
+	struct flock fl;
+
+	fl.l_type = lock_type;
+	fl.l_whence = SEEK_SET;
+	fl.l_start = 0;
+	fl.l_len = 0;
+
+	if (fcntl(fd, wait_lock ? F_SETLKW : F_SETLK, &fl) == -1) {
+		if (errno == EACCES)
+			return 0;
+		return -1;
+	}
+
+	return 1;
+}
+
+/* Returns 1 = ok, 0 = failed to get the lock, -1 = error */
+static int mail_modifylog_try_lock(MailModifyLog *log, int lock_type)
+{
+	int ret;
+
+	ret = file_lock(log->fd, FALSE, lock_type);
+	if (ret == -1) {
+		index_set_error(log->index, "fcntl() failed with file %s: %m",
+				log->filepath);
+	}
+
+	return ret;
+}
+
+static int mail_modifylog_wait_lock(MailModifyLog *log)
+{
+	if (file_lock(log->fd, TRUE, F_RDLCK) < 1) {
+		index_set_error(log->index, "fcntl() failed with file %s: %m",
+				log->filepath);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+/* returns 1 = yes, 0 = no, -1 = error */
+static int mail_modifylog_have_other_users(MailModifyLog *log)
+{
+	int ret;
+
+	/* try grabbing exclusive lock */
+	ret = mail_modifylog_try_lock(log, F_WRLCK);
+	if (ret == -1)
+		return -1;
+
+	/* revert back to shared lock */
+	switch (mail_modifylog_try_lock(log, F_WRLCK)) {
+	case 0:
+		/* shouldn't happen */
+		index_set_error(log->index, "fcntl(F_WRLCK -> F_RDLCK) "
+				"failed with file %s", log->filepath);
+		/* fall through */
+	case -1:
+		return -1;
+	}
+
+	return ret == 0 ? 1 : 0;
+}
+
+static int mmap_update(MailModifyLog *log)
+{
+	unsigned int extra;
+
+	if (!log->dirty_mmap && log->mmaped_id == log->header->sync_id)
+		return TRUE;
+
+	if (log->mmap_base != NULL)
+		(void)munmap(log->mmap_base, log->mmap_length);
+
+	log->mmap_base = mmap_rw_file(log->fd, &log->mmap_length);
+	if (log->mmap_base == MAP_FAILED) {
+		log->mmap_base = NULL;
+		log->header = NULL;
+		index_set_error(log->index,
+				"modify log: mmap() failed with file %s: %m",
+				log->filepath);
+		return FALSE;
+	}
+
+	if (log->mmap_length < sizeof(ModifyLogHeader)) {
+		/* FIXME: we could do better.. */
+		(void)unlink(log->filepath);
+		i_assert(0);
+	}
+
+	extra = (log->mmap_length - sizeof(ModifyLogHeader)) %
+		sizeof(ModifyLogRecord);
+
+	if (extra != 0) {
+		/* partial write or corrupted -
+		   truncate the file to valid length */
+		log->mmap_length -= extra;
+		(void)ftruncate(log->fd, (off_t) log->mmap_length);
+	}
+
+	log->dirty_mmap = FALSE;
+	log->header = log->mmap_base;
+	log->mmaped_id = log->header->sync_id;
+	return TRUE;
+}
+
+static MailModifyLog *mail_modifylog_new(MailIndex *index)
+{
+	MailModifyLog *log;
+
+	log = i_new(MailModifyLog, 1);
+	log->fd = -1;
+	log->index = index;
+	log->dirty_mmap = TRUE;
+
+	index->modifylog = log;
+	return log;
+}
+
+static void mail_modifylog_close(MailModifyLog *log)
+{
+	log->dirty_mmap = TRUE;
+
+	if (log->mmap_base != NULL) {
+		munmap(log->mmap_base, log->mmap_length);
+		log->mmap_base = NULL;
+	}
+
+	if (log->fd != -1) {
+		(void)close(log->fd);
+		log->fd = -1;
+	}
+
+	i_free(log->filepath);
+}
+
+static int mail_modifylog_init_fd(MailModifyLog *log, int fd,
+				  const char *path)
+{
+        ModifyLogHeader hdr;
+
+	/* write header */
+	memset(&hdr, 0, sizeof(hdr));
+	hdr.indexid = log->index->indexid;
+
+	if (write(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
+		index_set_error(log->index, "write() failed for modify "
+				"log %s: %m", path);
+		return FALSE;
+	}
+
+	if (ftruncate(fd, sizeof(hdr)) == -1) {
+		index_set_error(log->index, "ftruncate() failed for modify "
+				"log %s: %m", path);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static int mail_modifylog_open_and_init_file(MailModifyLog *log,
+					     const char *path)
+{
+	int fd, ret;
+
+	fd = open(path, O_RDWR | O_CREAT, 0660);
+	if (fd == -1) {
+		index_set_error(log->index, "Error opening modify log "
+				"file %s: %m", path);
+		return FALSE;
+	}
+
+	ret = file_lock(fd, FALSE, F_WRLCK);
+	if (ret == -1) {
+		index_set_error(log->index, "Error locking modify log "
+				"file %s: %m", path);
+	}
+
+	if (ret == 1 && mail_modifylog_init_fd(log, fd, path)) {
+		mail_modifylog_close(log);
+
+		log->fd = fd;
+		log->filepath = i_strdup(path);
+		return TRUE;
+	}
+
+	(void)close(fd);
+	return FALSE;
+}
+
+int mail_modifylog_create(MailIndex *index)
+{
+	MailModifyLog *log;
+	const char *path;
+
+	i_assert(index->lock_type == MAIL_LOCK_EXCLUSIVE);
+
+	log = mail_modifylog_new(index);
+
+	path = t_strconcat(log->index->filepath, ".log", NULL);
+	if (!mail_modifylog_open_and_init_file(log, path) ||
+	    !mail_modifylog_wait_lock(log) ||
+	    !mmap_update(log)) {
+		/* fatal failure */
+		mail_modifylog_free(log);
+		return FALSE;
+	}
+
+	log->synced_id = log->header->sync_id;
+	log->synced_position = log->mmap_length;
+	return TRUE;
+}
+
+/* Returns 1 = ok, 0 = full, -1 = error */
+static int mail_modifylog_open_and_verify(MailModifyLog *log, const char *path)
+{
+	ModifyLogHeader hdr;
+	int fd, ret;
+
+	fd = open(path, O_RDWR);
+	if (fd == -1) {
+		if (errno != ENOENT) {
+			index_set_error(log->index, "Can't open modify log "
+					"file %s: %m", path);
+		}
+		return -1;
+	}
+
+	ret = 1;
+	if (read(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
+		index_set_error(log->index, "read() failed when for modify "
+				"log file %s: %m", path);
+		ret = -1;
+	}
+
+	if (ret != -1 && hdr.indexid != log->index->indexid) {
+		index_set_error(log->index, "IndexID mismatch for modify log "
+				"file %s", path);
+		ret = -1;
+	}
+
+	if (ret != -1 && hdr.sync_id == SYNC_ID_FULL) {
+		/* full */
+		ret = 0;
+	}
+
+	if (ret == 1) {
+		log->fd = fd;
+		log->filepath = i_strdup(path);
+	} else {
+		(void)close(fd);
+	}
+
+	return ret;
+}
+
+static int mail_modifylog_find_or_create(MailModifyLog *log)
+{
+	const char *path1, *path2;
+	int i;
+
+	for (i = 0; i < 2; i++) {
+		/* first try <index>.log */
+		path1 = t_strconcat(log->index->filepath, ".log", NULL);
+		if (mail_modifylog_open_and_verify(log, path1) == 1)
+			return TRUE;
+
+		/* then <index>.log.2 */
+		path2 = t_strconcat(log->index->filepath, ".log.2", NULL);
+		if (mail_modifylog_open_and_verify(log, path2) == 1)
+			return TRUE;
+
+		/* try creating/reusing them */
+		if (mail_modifylog_open_and_init_file(log, path1))
+			return TRUE;
+
+		if (mail_modifylog_open_and_init_file(log, path2))
+			return TRUE;
+
+		/* maybe the file was just switched, check the logs again */
+	}
+
+	index_set_error(log->index, "We could neither use nor create "
+			"the modify log for index %s", log->index->filepath);
+	return FALSE;
+}
+
+int mail_modifylog_open_or_create(MailIndex *index)
+{
+	MailModifyLog *log;
+
+	log = mail_modifylog_new(index);
+
+	if (!mail_modifylog_find_or_create(log) ||
+	    !mail_modifylog_wait_lock(log) ||
+	    !mmap_update(log)) {
+		/* fatal failure */
+		mail_modifylog_free(log);
+		return FALSE;
+	}
+
+	log->synced_id = log->header->sync_id;
+	log->synced_position = log->mmap_length;
+	return TRUE;
+}
+
+void mail_modifylog_free(MailModifyLog *log)
+{
+	log->index->modifylog = NULL;
+
+	mail_modifylog_close(log);
+	i_free(log);
+}
+
+int mail_modifylog_sync_file(MailModifyLog *log)
+{
+	if (!log->modified)
+		return TRUE;
+
+	if (log->mmap_base != NULL) {
+		if (msync(log->mmap_base, log->mmap_length, MS_SYNC) == -1) {
+			index_set_error(log->index, "msync() failed for %s: %m",
+					log->filepath);
+			return FALSE;
+		}
+	}
+
+	if (fsync(log->fd) == -1) {
+		index_set_error(log->index, "fsync() failed for %s: %m",
+				log->filepath);
+		return FALSE;
+	}
+
+	log->modified = FALSE;
+	return TRUE;
+}
+
+static int mail_modifylog_append(MailModifyLog *log, ModifyLogRecord *rec,
+				 int external_change)
+{
+	i_assert(log->index->lock_type == MAIL_LOCK_EXCLUSIVE);
+	i_assert(rec->seq != 0);
+	i_assert(rec->uid != 0);
+
+	if (!external_change) {
+		switch (mail_modifylog_have_other_users(log)) {
+		case 0:
+			/* we're the only one having this log open,
+			   no need for modify log. */
+			return TRUE;
+		case -1:
+			return FALSE;
+		}
+	}
+
+	if (lseek(log->fd, 0, SEEK_END) == (off_t)-1) {
+		index_set_error(log->index, "lseek() failed with file %s: %m",
+				log->filepath);
+		return FALSE;
+	}
+
+	if (write(log->fd, rec, sizeof(ModifyLogRecord)) !=
+	    sizeof(ModifyLogRecord)) {
+		index_set_error(log->index, "Error appending to file %s: %m",
+				log->filepath);
+		return FALSE;
+	}
+
+	log->header->sync_id++;
+	log->modified = TRUE;
+	log->dirty_mmap = TRUE;
+
+	if (!external_change) {
+		log->synced_id = log->header->sync_id;
+		log->synced_position += sizeof(ModifyLogRecord);
+	}
+	return TRUE;
+}
+
+int mail_modifylog_add_expunge(MailModifyLog *log, unsigned int seq,
+			       unsigned int uid, int external_change)
+{
+	ModifyLogRecord rec;
+
+	/* expunges must not be added when log isn't synced */
+	i_assert(external_change || log->synced_id == log->header->sync_id);
+
+	rec.type = RECORD_TYPE_EXPUNGE;
+	rec.seq = seq;
+	rec.uid = uid;
+	return mail_modifylog_append(log, &rec, external_change);
+}
+
+int mail_modifylog_add_flags(MailModifyLog *log, unsigned int seq,
+			     unsigned int uid, int external_change)
+{
+	ModifyLogRecord rec;
+
+	rec.type = RECORD_TYPE_FLAGS_CHANGED;
+	rec.seq = seq;
+	rec.uid = uid;
+	return mail_modifylog_append(log, &rec, external_change);
+}
+
+ModifyLogRecord *mail_modifylog_get_nonsynced(MailModifyLog *log,
+					      unsigned int *count)
+{
+	ModifyLogRecord *rec, *end_rec;
+
+	i_assert(log->index->lock_type != MAIL_LOCK_UNLOCK);
+
+	*count = 0;
+	if (!mmap_update(log))
+		return NULL;
+
+	i_assert(log->synced_position <= log->mmap_length);
+	i_assert(log->synced_position >= sizeof(ModifyLogHeader));
+
+	rec = (ModifyLogRecord *) ((char *) log->mmap_base +
+				   log->synced_position);
+	end_rec = (ModifyLogRecord *) ((char *) log->mmap_base +
+				       log->mmap_length);
+	*count = (unsigned int) (end_rec - rec);
+	return rec;
+}
+
+static int mail_modifylog_switch_file(MailModifyLog *log)
+{
+	MailIndex *index = log->index;
+
+	mail_modifylog_free(log);
+	return mail_modifylog_open_or_create(index);
+}
+
+static void mail_modifylog_try_switch_file(MailModifyLog *log)
+{
+	const char *path;
+
+	path = t_strconcat(log->index->filepath,
+			   log->second_log ? ".log" : ".log.2", NULL);
+
+	if (mail_modifylog_open_and_init_file(log, path))
+		log->header->sync_id = SYNC_ID_FULL;
+}
+
+int mail_modifylog_mark_synced(MailModifyLog *log)
+{
+	i_assert(log->index->lock_type != MAIL_LOCK_UNLOCK);
+
+	if (log->header->sync_id == SYNC_ID_FULL) {
+		/* log file is full, switch to next one */
+		return mail_modifylog_switch_file(log);
+	}
+
+	if (log->synced_id == log->header->sync_id) {
+		/* we are already synced */
+		return TRUE;
+	}
+
+	log->synced_id = log->header->sync_id;
+	log->synced_position = log->mmap_length;
+
+	log->modified = TRUE;
+
+	if (log->mmap_length > MAX_MODIFYLOG_SIZE) {
+		/* if the other file isn't locked, switch to it */
+		mail_modifylog_try_switch_file(log);
+		return TRUE;
+	}
+
+	return TRUE;
+}
+
+static int compare_uint(const void *p1, const void *p2)
+{
+	const unsigned int *u1 = p1;
+	const unsigned int *u2 = p2;
+
+	return *u1 < *u2 ? -1 : *u1 > *u2 ? 1 : 0;
+}
+
+const unsigned int *
+mail_modifylog_seq_get_expunges(MailModifyLog *log,
+				unsigned int first_seq,
+				unsigned int last_seq,
+				unsigned int *expunges_before)
+{
+	ModifyLogRecord *rec, *end_rec;
+	unsigned int last_pos_seq, before, max_records, *arr, *expunges;
+
+	i_assert(log->index->lock_type != MAIL_LOCK_UNLOCK);
+
+	*expunges_before = 0;
+
+	if (!mmap_update(log))
+		return NULL;
+
+	/* find the first expunged message that affects our range */
+	rec = (ModifyLogRecord *) ((char *) log->mmap_base +
+				   log->synced_position);
+	end_rec = (ModifyLogRecord *) ((char *) log->mmap_base +
+				       log->mmap_length);
+
+	while (rec < end_rec) {
+		if (rec->type == RECORD_TYPE_EXPUNGE && rec->seq <= last_seq)
+			break;
+		rec++;
+	}
+
+	if (rec >= end_rec) {
+		/* none found */
+		expunges = t_malloc(sizeof(unsigned int));
+		*expunges = 0;
+		return expunges;
+	}
+
+	/* allocate memory for the returned array. the file size - synced
+	   position should be quite near the amount of memory we need, unless
+	   there's lots of FLAGS_CHANGED records which is why there's the
+	   second check to make sure it's not unneededly large. */
+	max_records = (log->mmap_length - MODIFYLOG_FILE_POSITION(log, rec)) /
+		sizeof(ModifyLogRecord);
+	if (max_records > last_seq - first_seq + 1)
+		max_records = last_seq - first_seq + 1;
+
+	expunges = arr = t_malloc((max_records+1) * sizeof(unsigned int));
+
+	/* last_pos_seq is updated all the time to contain the last_seq
+	   comparable to current record's seq. number */
+	last_pos_seq = last_seq;
+
+	before = 0;
+	for (; rec < end_rec; rec++) {
+		if (rec->type != RECORD_TYPE_EXPUNGE)
+			continue;
+
+		if (rec->seq + before < first_seq) {
+			/* before our range */
+			before++;
+			last_pos_seq--;
+		} else if (rec->seq <= last_pos_seq) {
+			/* within our range */
+			last_pos_seq--;
+
+			if (max_records-- == 0) {
+				/* log contains more data than it should
+				   have - must be corrupted. */
+				index_set_error(log->index,
+						"Modify log %s is corrupted",
+						log->filepath);
+				return NULL;
+			}
+
+			*arr++ = rec->uid;
+		}
+	}
+	*arr = 0;
+
+	/* sort the UID array, not including the terminating 0 */
+	qsort(expunges, (unsigned int) (arr - expunges), sizeof(unsigned int),
+	      compare_uint);
+
+	*expunges_before = before;
+	return expunges;
+}
+
+const unsigned int *
+mail_modifylog_uid_get_expunges(MailModifyLog *log,
+				unsigned int first_uid,
+				unsigned int last_uid)
+{
+	/* pretty much copy&pasted from sequence code above ..
+	   kind of annoying */
+	ModifyLogRecord *rec, *end_rec;
+	unsigned int before, max_records, *arr, *expunges;
+
+	i_assert(log->index->lock_type != MAIL_LOCK_UNLOCK);
+
+	if (!mmap_update(log))
+		return NULL;
+
+	/* find the first expunged message that affects our range */
+	rec = (ModifyLogRecord *) ((char *) log->mmap_base +
+				   log->synced_position);
+	end_rec = (ModifyLogRecord *) ((char *) log->mmap_base +
+				       log->mmap_length);
+
+	while (rec < end_rec) {
+		if (rec->type == RECORD_TYPE_EXPUNGE && rec->uid <= last_uid)
+			break;
+		rec++;
+	}
+
+	if (rec >= end_rec) {
+		/* none found */
+		expunges = t_malloc(sizeof(unsigned int));
+		*expunges = 0;
+		return expunges;
+	}
+
+	/* allocate memory for the returned array. the file size - synced
+	   position should be quite near the amount of memory we need, unless
+	   there's lots of FLAGS_CHANGED records which is why there's the
+	   second check to make sure it's not unneededly large. */
+	max_records = (log->mmap_length - MODIFYLOG_FILE_POSITION(log, rec)) /
+		sizeof(ModifyLogRecord);
+	if (max_records > last_uid - first_uid + 1)
+		max_records = last_uid - first_uid + 1;
+
+	expunges = arr = t_malloc((max_records+1) * sizeof(unsigned int));
+
+	before = 0;
+	while (rec < end_rec) {
+		if (rec->type == RECORD_TYPE_EXPUNGE &&
+		    rec->uid >= first_uid && rec->uid <= last_uid) {
+			/* within our range */
+			if (max_records-- == 0) {
+				/* log contains more data than it should
+				   have - must be corrupted. */
+				index_set_error(log->index,
+						"Modify log %s is corrupted",
+						log->filepath);
+				return NULL;
+			}
+			*arr++ = rec->uid;
+		}
+		rec++;
+	}
+	*arr = 0;
+
+	/* sort the UID array, not including the terminating 0 */
+	qsort(expunges, (unsigned int) (arr - expunges), sizeof(unsigned int),
+	      compare_uint);
+
+	return expunges;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mail-modifylog.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,67 @@
+#ifndef __MAIL_MODIFYLOG_H
+#define __MAIL_MODIFYLOG_H
+
+typedef enum {
+	RECORD_TYPE_EXPUNGE,
+	RECORD_TYPE_FLAGS_CHANGED
+} ModifyLogRecordType;
+
+typedef struct _ModifyLogHeader ModifyLogHeader;
+typedef struct _ModifyLogRecord ModifyLogRecord;
+
+/* if sync_id has this value, the log file is full and should be
+   deleted or reused. */
+#define SYNC_ID_FULL ((unsigned int)-1)
+
+struct _ModifyLogHeader {
+	unsigned int indexid;
+	unsigned int sync_id;
+};
+
+struct _ModifyLogRecord {
+	unsigned int type;
+	unsigned int seq;
+	unsigned int uid;
+};
+
+/* NOTE: All these functions require the index file to be locked. */
+
+int mail_modifylog_create(MailIndex *index);
+int mail_modifylog_open_or_create(MailIndex *index);
+void mail_modifylog_free(MailModifyLog *log);
+
+/* Append EXPUGE or FLAGS entry to modify log. Index must be exclusively
+   locked before calling these functions, and modifylog must have been
+   marked synced within the same lock. */
+int mail_modifylog_add_expunge(MailModifyLog *log, unsigned int seq,
+			       unsigned int uid, int external_change);
+int mail_modifylog_add_flags(MailModifyLog *log, unsigned int seq,
+			     unsigned int uid, int external_change);
+
+/* Synchronize the data into disk */
+int mail_modifylog_sync_file(MailModifyLog *log);
+
+/* Returns the nonsynced log entries. count is set to number of log records. */
+ModifyLogRecord *mail_modifylog_get_nonsynced(MailModifyLog *log,
+					      unsigned int *count);
+
+/* Marks the modify log as being synced with in-memory state. */
+int mail_modifylog_mark_synced(MailModifyLog *log);
+
+/* Finds expunged messages for the given sequence range, and number of
+   expunged messages before the range. Returns sorted 0-terminated list of
+   expunged UIDs, or NULL if error occured. */
+const unsigned int *
+mail_modifylog_seq_get_expunges(MailModifyLog *log,
+				unsigned int first_seq,
+				unsigned int last_seq,
+				unsigned int *expunges_before);
+
+/* Returns sorted 0-terminated list of expunged UIDs in given range,
+   or NULL if error occured. */
+const unsigned int *
+mail_modifylog_uid_get_expunges(MailModifyLog *log,
+				unsigned int first_uid,
+				unsigned int last_uid);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/maildir/.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/maildir/Makefile.am	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,18 @@
+noinst_LIBRARIES = libstorage_index_maildir.a
+
+INCLUDES = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-mail \
+	-I$(top_srcdir)/src/lib-imap \
+	-I$(top_srcdir)/src/lib-index
+
+libstorage_index_maildir_a_SOURCES = \
+	maildir-index.c \
+	maildir-build.c \
+	maildir-open.c \
+	maildir-rebuild.c \
+	maildir-sync.c \
+	maildir-update.c
+
+noinst_HEADERS = \
+	maildir-index.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/maildir/maildir-build.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,173 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "maildir-index.h"
+#include "mail-index-data.h"
+#include "mail-index-util.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+static MailIndexRecord *
+mail_index_record_append(MailIndex *index, time_t internal_date)
+{
+	MailIndexRecord trec, *rec;
+
+	memset(&trec, 0, sizeof(MailIndexRecord));
+	trec.internal_date = internal_date;
+
+	rec = &trec;
+	if (!index->append(index, &rec))
+		return NULL;
+
+	return rec;
+}
+
+static int maildir_index_append_fd(MailIndex *index, int fd, const char *path,
+				   const char *fname)
+{
+	MailIndexRecord *rec;
+	MailIndexUpdate *update;
+	struct stat st;
+	int failed;
+
+	i_assert(path != NULL);
+	i_assert(fname != NULL);
+
+	/* check that file size is somewhat reasonable */
+	if (fstat(fd, &st) == -1) {
+		index_set_error(index, "fstat() failed with file %s: %m", path);
+		return FALSE;
+	}
+
+	if (st.st_size < 10) {
+		/* This cannot be a mail file - delete it */
+		index_set_error(index,
+				"Invalid size %lu with mail in %s - deleted",
+				(unsigned long) st.st_size, path);
+		(void)unlink(path);
+		return TRUE;
+	}
+
+	if (!index->set_lock(index, MAIL_LOCK_EXCLUSIVE))
+		return FALSE;
+
+	/* append the file into index */
+	rec = mail_index_record_append(index, st.st_mtime);
+	if (rec == NULL)
+		return FALSE;
+
+	update = index->update_begin(index, rec);
+
+	/* set the location */
+	index->update_field(update, FIELD_TYPE_LOCATION, fname,
+			    MAILDIR_LOCATION_EXTRA_SPACE);
+
+	/* parse the header and update record's fields */
+	failed = !maildir_record_update(index, update, fd, path);
+
+	if (!index->update_end(update) || failed) {
+		/* failed - delete the record */
+		(void)index->expunge(index, rec, 0, FALSE);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+int maildir_index_append_file(MailIndex *index, const char *dir,
+			      const char *fname)
+{
+	const char *path;
+	int fd, ret;
+
+	i_assert(index->lock_type != MAIL_LOCK_SHARED);
+
+	i_assert(dir != NULL);
+	i_assert(fname != NULL);
+
+	path = t_strconcat(dir, "/", fname, NULL);
+	fd = open(path, O_RDONLY);
+	if (fd == -1) {
+		/* open() failed - treat it as error unless the error was
+		   "file doesn't exist" in which case someone just managed
+		   to delete it before we saw it */
+		if (errno == EEXIST)
+			return TRUE;
+
+		index_set_error(index, "Error opening mail file %s: %m", path);
+		return FALSE;
+	}
+
+	ret = maildir_index_append_fd(index, fd, path, fname);
+	(void)close(fd);
+	return ret;
+}
+
+int maildir_index_build_dir(MailIndex *index, const char *source_dir,
+			    const char *dest_dir)
+{
+	DIR *dirp;
+	const char *final_dir;
+	struct dirent *d;
+	struct stat st;
+	char sourcepath[1024], destpath[1024];
+	int failed;
+
+	i_assert(index->lock_type != MAIL_LOCK_SHARED);
+
+	i_assert(source_dir != NULL);
+
+	dirp = opendir(source_dir);
+	if (dirp == NULL) {
+		index_set_error(index, "Couldn't build index from %s: %m",
+				source_dir);
+		return FALSE;
+	}
+
+	final_dir = dest_dir != NULL ? dest_dir : source_dir;
+
+	failed = FALSE;
+	while (!failed && (d = readdir(dirp)) != NULL) {
+		if (d->d_name[0] == '.')
+			continue;
+
+		if (dest_dir != NULL) {
+			/* move the file into dest_dir - abort everything if it
+			   already exists, as that should never happen */
+			i_snprintf(sourcepath, sizeof(sourcepath), "%s/%s",
+				   source_dir, d->d_name);
+			i_snprintf(destpath, sizeof(destpath), "%s/%s",
+				   dest_dir, d->d_name);
+			if (stat(destpath, &st) == 0) {
+				index_set_error(index, "Can't move mail %s to "
+						"%s: file already exists",
+						sourcepath, destpath);
+				failed = TRUE;
+				break;
+			}
+
+			/* race condition here - ignore it as the chance of it
+			   happening is pretty much zero */
+
+			if (rename(sourcepath, destpath) == -1) {
+				index_set_error(index, "maildir build: "
+						"rename(%s, %s) failed: %m",
+						sourcepath, destpath);
+				failed = TRUE;
+				break;
+			}
+		}
+
+		t_push();
+		failed = !maildir_index_append_file(index, final_dir,
+						    d->d_name);
+		t_pop();
+	}
+
+	(void)closedir(dirp);
+	return !failed;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/maildir/maildir-index.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,182 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "maildir-index.h"
+#include "mail-index-util.h"
+
+#include <stdio.h>
+
+static MailIndex maildir_index;
+
+const char *maildir_filename_set_flags(const char *fname, MailFlags flags)
+{
+	const char *info, *oldflags;
+	char *flags_buf, *p;
+	int i, nextflag;
+
+	/* remove the old :info from file name, and get the old flags */
+	info = strrchr(fname, ':');
+	if (info != NULL && strrchr(fname, '/') > info)
+		info = NULL;
+
+	oldflags = "";
+	if (info != NULL) {
+		fname = t_strndup(fname, (unsigned int) (info-fname));
+		if (info[1] == '2' && info[2] == ',')
+			oldflags = info+3;
+	}
+
+	/* insert the new flags between old flags. flags must be sorted by
+	   their ASCII code. unknown flags are kept. */
+	flags_buf = t_malloc(MAIL_FLAGS_COUNT+strlen(oldflags)+1);
+	p = flags_buf;
+
+	for (;;) {
+		/* skip all known flags */
+		while (*oldflags == 'D' || *oldflags == 'F' ||
+		       *oldflags == 'R' || *oldflags == 'S' ||
+		       *oldflags == 'T' ||
+		       (*oldflags >= 'a' && *oldflags <= 'z'))
+			oldflags++;
+
+		nextflag = *oldflags == '\0' ? 256 :
+			(unsigned char) *oldflags;
+
+		if ((flags & MAIL_DRAFT) && nextflag > 'D') {
+			*p++ = 'D';
+			flags &= ~MAIL_DRAFT;
+		}
+		if ((flags & MAIL_FLAGGED) && nextflag > 'F') {
+			*p++ = 'F';
+			flags &= ~MAIL_FLAGGED;
+		}
+		if ((flags & MAIL_ANSWERED) && nextflag > 'R') {
+			*p++ = 'R';
+			flags &= ~MAIL_ANSWERED;
+		}
+		if ((flags & MAIL_SEEN) && nextflag > 'S') {
+			*p++ = 'S';
+			flags &= ~MAIL_SEEN;
+		}
+		if ((flags & MAIL_DELETED) && nextflag > 'T') {
+			*p++ = 'T';
+			flags &= ~MAIL_DELETED;
+		}
+
+		if ((flags & MAIL_CUSTOM_FLAGS_MASK) && nextflag > 'a') {
+			for (i = 0; i < MAIL_CUSTOM_FLAGS_COUNT; i++) {
+				if (flags & (1 << (i + MAIL_CUSTOM_FLAG_1_BIT)))
+					*p++ = 'a' + i;
+			}
+			flags &= ~MAIL_CUSTOM_FLAGS_MASK;
+		}
+
+		if (*oldflags == '\0')
+			break;
+
+		*p++ = *oldflags++;
+	}
+
+	*p = '\0';
+
+	return t_strconcat(fname, ":2,", flags_buf, NULL);
+}
+
+MailIndex *maildir_index_alloc(const char *dir)
+{
+	MailIndex *index;
+	int len;
+
+	i_assert(dir != NULL);
+
+	index = i_new(MailIndex, 1);
+	memcpy(index, &maildir_index, sizeof(MailIndex));
+
+	index->fd = -1;
+	index->dir = i_strdup(dir);
+
+	len = strlen(index->dir);
+	if (index->dir[len-1] == '/')
+		index->dir[len-1] = '\0';
+
+	return (MailIndex *) index;
+}
+
+static void maildir_index_free(MailIndex *index)
+{
+	mail_index_close(index);
+	i_free(index->dir);
+	i_free(index);
+}
+
+static int maildir_index_update_flags(MailIndex *index, MailIndexRecord *rec,
+				      unsigned int seq, MailFlags flags,
+				      int external_change)
+{
+	MailIndexUpdate *update;
+	const char *old_fname, *new_fname;
+	const char *old_path, *new_path;
+
+	if (!mail_index_update_flags(index, rec, seq, flags, external_change))
+		return FALSE;
+
+	/* we need to update the flags in the file name */
+	old_fname = index->lookup_field(index, rec, FIELD_TYPE_LOCATION);
+	if (old_fname == NULL) {
+                INDEX_MARK_CORRUPTED(index);
+		index_set_error(index, "Corrupted index file %s: "
+				"Missing location field for record %u",
+				index->filepath, rec->uid);
+		return FALSE;
+	}
+
+	new_fname = maildir_filename_set_flags(old_fname, flags);
+
+	if (strcmp(old_fname, new_fname) != 0) {
+		old_path = t_strconcat(index->dir, "/cur/", old_fname, NULL);
+		new_path = t_strconcat(index->dir, "/cur/", new_fname, NULL);
+
+		/* minor problem: new_path is overwritten if it exists.. */
+		if (rename(old_path, new_path) == -1) {
+			index_set_error(index, "maildir flags update: "
+					"rename(%s, %s) failed: %m",
+					old_path, new_path);
+			return FALSE;
+		}
+
+		/* update the filename in index */
+		update = index->update_begin(index, rec);
+		index->update_field(update, FIELD_TYPE_LOCATION, new_fname, 0);
+
+		if (!index->update_end(update))
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+static MailIndex maildir_index = {
+	mail_index_open,
+	mail_index_open_or_create,
+	maildir_index_free,
+	mail_index_set_lock,
+	mail_index_try_lock,
+	maildir_index_rebuild,
+	mail_index_fsck,
+	maildir_index_sync,
+	mail_index_get_header,
+	mail_index_lookup,
+	mail_index_next,
+        mail_index_lookup_uid_range,
+	mail_index_lookup_field,
+	mail_index_get_sequence,
+	maildir_open_mail,
+	mail_index_expunge,
+	maildir_index_update_flags,
+	mail_index_append,
+	mail_index_update_begin,
+	mail_index_update_end,
+	mail_index_update_field,
+	mail_index_get_last_error,
+	mail_index_is_inconsistency_error
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/maildir/maildir-index.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,27 @@
+#ifndef __MAILDIR_INDEX_H
+#define __MAILDIR_INDEX_H
+
+#include "mail-index.h"
+
+/* ":2,DFRST" - leave the 2 extra for other clients' additions */
+#define MAILDIR_LOCATION_EXTRA_SPACE 10
+
+MailIndex *maildir_index_alloc(const char *dir);
+
+const char *maildir_filename_set_flags(const char *fname, MailFlags flags);
+
+int maildir_index_rebuild(MailIndex *index);
+int maildir_index_sync(MailIndex *index);
+
+int maildir_index_append_file(MailIndex *index, const char *dir,
+			      const char *fname);
+int maildir_index_build_dir(MailIndex *index, const char *source_dir,
+			    const char *dest_dir);
+
+int maildir_open_mail(MailIndex *index, MailIndexRecord *rec,
+		      off_t *offset, size_t *size);
+
+int maildir_record_update(MailIndex *index, MailIndexUpdate *update,
+			  int fd, const char *path);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/maildir/maildir-open.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,45 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "maildir-index.h"
+#include "mail-index-util.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+int maildir_open_mail(MailIndex *index, MailIndexRecord *rec,
+		      off_t *offset, size_t *size)
+{
+	off_t pos;
+	const char *fname, *path;
+	int fd;
+
+	fname = index->lookup_field(index, rec, FIELD_TYPE_LOCATION);
+	if (fname == NULL) {
+                INDEX_MARK_CORRUPTED(index);
+		index_set_error(index, "Corrupted index file %s: "
+				"Missing location field for record %u",
+				index->filepath, rec->uid);
+		return -1;
+	}
+
+	path = t_strconcat(index->dir, "/cur/", fname, NULL);
+
+	fd = open(path, O_RDONLY);
+	if (fd == -1) {
+		index_set_error(index, "Error opening mail file %s: %m", path);
+		return -1;
+	}
+
+	pos = lseek(fd, 0, SEEK_END);
+	if (pos == (off_t)-1 || lseek(fd, 0, SEEK_SET) == (off_t)-1) {
+		index_set_error(index, "lseek() failed with mail file %s: %m",
+				path);
+		(void)close(fd);
+		return -1;
+	}
+
+	*offset = 0;
+	*size = (size_t) pos;
+	return fd;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/maildir/maildir-rebuild.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,66 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "maildir-index.h"
+#include "mail-index-data.h"
+#include "mail-index-util.h"
+#include "mail-hash.h"
+
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+int maildir_index_rebuild(MailIndex *index)
+{
+	struct stat st;
+	const char *cur_dir, *new_dir;
+
+	i_assert(index->lock_type != MAIL_LOCK_SHARED);
+
+	if (!mail_index_set_lock(index, MAIL_LOCK_EXCLUSIVE))
+		return FALSE;
+
+	/* reset the header */
+	mail_index_init_header(index->header);
+
+	/* update indexid */
+	index->indexid = index->header->indexid;
+
+	if (msync(index->mmap_base, sizeof(MailIndexHeader), MS_SYNC) == -1)
+		return FALSE;
+
+	/* truncate the file first, so it won't contain
+	   any invalid data even if we crash */
+	if (ftruncate(index->fd, sizeof(MailIndexHeader)) == -1) {
+		index_set_error(index, "Can't truncate index file %s: %m",
+				index->filepath);
+		return FALSE;
+	}
+
+	/* reset data file */
+	if (!mail_index_data_reset(index->data))
+		return FALSE;
+
+	/* rebuild cur/ directory */
+	cur_dir = t_strconcat(index->dir, "/cur", NULL);
+	if (!maildir_index_build_dir(index, cur_dir, NULL))
+		return FALSE;
+
+	/* also see if there's new mail */
+	new_dir = t_strconcat(index->dir, "/new", NULL);
+	if (!maildir_index_build_dir(index, new_dir, cur_dir))
+		return FALSE;
+
+	/* update sync stamp */
+	if (stat(cur_dir, &st) == -1) {
+		index_set_error(index, "fstat() failed for maildir %s: %m",
+				cur_dir);
+		return FALSE;
+	}
+
+	index->file_sync_stamp = st.st_mtime;
+
+	/* rebuild is complete - remove the flag */
+	index->header->flags &= ~(MAIL_INDEX_FLAG_REBUILD|MAIL_INDEX_FLAG_FSCK);
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/maildir/maildir-sync.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,366 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "hash.h"
+#include "maildir-index.h"
+#include "mail-index-util.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <utime.h>
+#include <sys/stat.h>
+
+static int maildir_index_sync_file(MailIndex *index,
+				   MailIndexRecord *rec, unsigned int seq,
+				   const char *fname, const char *path,
+				   int fname_changed, int file_changed)
+{
+	MailIndexUpdate *update;
+	MailFlags flags;
+	const char *info;
+	int fd, failed;
+
+	i_assert(fname != NULL);
+	i_assert(path != NULL);
+
+	flags = rec->msg_flags;
+
+	info = strchr(fname, ':');
+	if (info != NULL && info[1] == '2' && info[2] == ',') {
+		/* update flags */
+		flags = 0;
+		for (info += 3; *info != '\0'; info++) {
+			switch (*info) {
+			case 'R': /* replied */
+				flags |= MAIL_ANSWERED;
+				break;
+			case 'S': /* seen */
+				flags |= MAIL_SEEN;
+				break;
+			case 'T': /* trashed */
+				flags |= MAIL_DELETED;
+				break;
+			case 'D': /* draft */
+				flags |= MAIL_DRAFT;
+				break;
+			case 'F': /* flagged */
+				flags |= MAIL_FLAGGED;
+				break;
+			default:
+				if (*info >= 'a' && *info <= 'z') {
+					/* custom flag */
+					flags |= 1 << (MAIL_CUSTOM_FLAG_1_BIT +
+						       *info-'a');
+					break;
+				}
+
+				/* unknown flag - ignore */
+				break;
+			}
+		}
+	}
+
+	if (!index->set_lock(index, MAIL_LOCK_EXCLUSIVE))
+		return FALSE;
+
+	failed = FALSE;
+	update = index->update_begin(index, rec);
+
+	if (fname_changed)
+		index->update_field(update, FIELD_TYPE_LOCATION, fname, 0);
+	if (file_changed) {
+		/* file itself changed - reload the header */
+		fd = open(path, O_RDONLY);
+		if (fd == -1) {
+			index_set_error(index, "Error opening file %s: %m",
+					path);
+			failed = TRUE;
+		} else {
+			if (!maildir_record_update(index, update, fd, path))
+				failed = TRUE;
+			(void)close(fd);
+		}
+	}
+
+	if (!index->update_end(update))
+		failed = TRUE;
+
+	/* update flags after filename has been updated, so it can be
+	   compared correctly */
+	if (!failed && flags != rec->msg_flags) {
+		if (!index->update_flags(index, rec, seq, flags, TRUE))
+			failed = TRUE;
+	}
+
+	return !failed;
+}
+
+static int maildir_index_sync_files(MailIndex *index, const char *dir,
+				    HashTable *files, int check_content_changes)
+{
+	MailIndexRecord *rec;
+	struct stat st;
+	const char *fname, *value;
+	char str[1024], *p;
+	unsigned int seq;
+	int fname_changed, file_changed;
+
+	i_assert(dir != NULL);
+
+	rec = index->lookup(index, 1);
+	for (seq = 1; rec != NULL; rec = index->next(index, rec), seq++) {
+		fname = index->lookup_field(index, rec, FIELD_TYPE_LOCATION);
+		if (fname == NULL) {
+			INDEX_MARK_CORRUPTED(index);
+			index_set_error(index, "Corrupted index file %s: "
+					"Missing location field for record %u",
+					index->filepath, rec->uid);
+			return FALSE;
+		}
+
+		/* get the filename without the ":flags" part */
+		strncpy(str, fname, sizeof(str)-1); str[sizeof(str)-1] = '\0';
+		p = strchr(str, ':');
+		if (p != NULL) *p = '\0';
+
+		value = hash_lookup(files, str);
+		hash_remove(files, str);
+
+		if (value == NULL) {
+			/* mail is expunged */
+			if (!index->set_lock(index, MAIL_LOCK_EXCLUSIVE))
+				return FALSE;
+
+			if (!index->expunge(index, rec, seq, TRUE))
+				return FALSE;
+			continue;
+		}
+
+		/* file still exists */
+		i_snprintf(str, sizeof(str), "%s/%s", dir, value);
+
+		if (!check_content_changes)
+			file_changed = FALSE;
+		else {
+			if (stat(str, &st) == -1) {
+				index_set_error(index, "stat() failed with "
+						"file %s: %m", str);
+				return FALSE;
+			}
+
+			file_changed = rec->body_size + rec->header_size !=
+				(size_t) st.st_size;
+		}
+
+		/* changed - update */
+		fname_changed = strcmp(value, fname) != 0;
+		if (fname_changed || file_changed) {
+			if (!maildir_index_sync_file(index, rec, seq, value,
+						     str, fname_changed,
+						     file_changed))
+				return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+typedef struct {
+	MailIndex *index;
+	const char *dir;
+	int failed;
+} HashAppendData;
+
+static void maildir_index_hash_append_file(void *key __attr_unused__,
+					   void *value, void *user_data)
+{
+	HashAppendData *data = user_data;
+
+	if (!maildir_index_append_file(data->index, data->dir, value)) {
+		data->failed = TRUE;
+                hash_foreach_stop();
+	}
+}
+
+static int maildir_index_append_files(MailIndex *index, const char *dir,
+				      HashTable *files)
+{
+	HashAppendData data;
+
+	data.failed = FALSE;
+	data.index = index;
+	data.dir = dir;
+	hash_foreach(files, maildir_index_hash_append_file, &data);
+
+	return !data.failed;
+}
+
+static int maildir_index_sync_dir(MailIndex *index, const char *dir)
+{
+	Pool pool;
+	HashTable *files;
+	DIR *dirp;
+	struct dirent *d;
+	const char *key, *value, *p;
+	unsigned int count;
+	int failed, check_content_changes;
+
+	i_assert(dir != NULL);
+
+	/* get exclusive lock always, this way the index file's timestamp
+	   is updated even if there's no changes, which is useful to make
+	   sure the cur/ directory isn't scanned all the time when it's
+	   timestamp has changed but hasn't had any other changes. */
+	if (!index->set_lock(index, MAIL_LOCK_EXCLUSIVE))
+		return FALSE;
+
+	if (index->header->messages_count >= INT_MAX/32) {
+		INDEX_MARK_CORRUPTED(index);
+		index_set_error(index, "Corrupted index file %s: Header "
+				"says %u messages", index->filepath,
+				index->header->messages_count);
+		return FALSE;
+	}
+
+	/* we need to find out the new and the deleted files. do this by
+	   first building a hash of what files really exist, then go through
+	   the index and after updated/removed the index, remove the file
+	   from hash, so finally the hash should contain only the new
+	   files which will be added then. */
+	dirp = opendir(dir);
+	if (dirp == NULL) {
+		index_set_error(index, "Couldn't sync index in dir %s: %m",
+				dir);
+		return FALSE;
+	}
+
+	count = index->header->messages_count + 16;
+	pool = pool_create("Maildir sync", nearest_power(count*30), FALSE);
+	files = hash_create(pool, index->header->messages_count*2, str_hash,
+			    (HashCompareFunc) strcmp);
+
+	while ((d = readdir(dirp)) != NULL) {
+		if (d->d_name[0] == '.')
+			continue;
+
+		/* hash key is the file name without the ":flags" part */
+		p = strrchr(d->d_name, ':');
+		if (p == d->d_name)
+			continue;
+
+		value = p_strdup(pool, d->d_name);
+		key = p == NULL ? value :
+			p_strndup(pool, d->d_name,
+				  (unsigned int) (p - d->d_name));
+		hash_insert(files, key, value);
+	}
+	(void)closedir(dirp);
+
+	/* Do we want to check changes in file contents? This slows down
+	   things as we need to do extra stat() for all files. */
+	check_content_changes = getenv("CHECK_CONTENT_CHANGES") != NULL;
+
+	/* now walk through the index syncing and expunging existing mails */
+	failed = !maildir_index_sync_files(index, dir, files,
+					   check_content_changes);
+
+	if (!failed) {
+		/* then add the new mails */
+		failed = !maildir_index_append_files(index, dir, files);
+	}
+
+	hash_destroy(files);
+	pool_unref(pool);
+	return !failed;
+}
+
+int maildir_index_sync(MailIndex *index)
+{
+	struct stat sti, std;
+	struct utimbuf ut;
+	const char *cur_dir, *new_dir;
+
+	i_assert(index->lock_type != MAIL_LOCK_SHARED);
+
+	if (fstat(index->fd, &sti) == -1) {
+		index_set_error(index, "fstat() failed with index file %s: %m",
+				index->filepath);
+		return FALSE;
+	}
+
+	/* cur/ and new/ directories can have new mail - sync the cur/ first
+	   so it'll be a bit bit faster since we haven't yet added the new
+	   mail. */
+        cur_dir = t_strconcat(index->dir, "/cur", NULL);
+	if (stat(cur_dir, &std) == -1) {
+		index_set_error(index, "fstat() failed for maildir %s: %m",
+				cur_dir);
+		return FALSE;
+	}
+
+	if (std.st_mtime != sti.st_mtime) {
+		if (!maildir_index_sync_dir(index, cur_dir))
+			return FALSE;
+	}
+
+	/* move mail from new/ to cur/ */
+	new_dir = t_strconcat(index->dir, "/new", NULL);
+	if (stat(new_dir, &std) == -1) {
+		index_set_error(index, "fstat() failed for maildir "
+				"%s: %m", new_dir);
+		return FALSE;
+	}
+
+	if (std.st_mtime != sti.st_mtime) {
+		if (!maildir_index_build_dir(index, new_dir, cur_dir))
+			return FALSE;
+
+		/* set cur/ and new/ directory's timestamp into past to
+		   make sure if someone adds new mail it the new/ dir's
+		   timestamp isn't set to same as cur/ directory's. */
+		ut.actime = ut.modtime = ioloop_time-60;
+		if (utime(cur_dir, &ut) == -1) {
+			index_set_error(index, "utime() failed for %s: %m",
+					cur_dir);
+			return FALSE;
+		}
+		if (utime(new_dir, &ut) == -1) {
+			index_set_error(index, "utime() failed for %s: %m",
+					new_dir);
+			return FALSE;
+		}
+
+		/* it's possible that new mail came in just after we
+		   scanned the directory. scan the directory again, this will
+		   update the directory's timestamps so at next sync we'll
+		   always check the new/ dir once more, but at least we can be
+		   sure that no mail got lost. */
+		if (!maildir_index_build_dir(index, new_dir, cur_dir))
+			return FALSE;
+	}
+
+	/* update sync stamp */
+	if (stat(cur_dir, &std) == -1) {
+		index_set_error(index, "fstat() failed for maildir %s: %m",
+				cur_dir);
+		return FALSE;
+	}
+	index->file_sync_stamp = std.st_mtime;
+
+	if (index->lock_type == MAIL_LOCK_UNLOCK) {
+		/* no changes, we need to update index's timestamp
+		   ourself to get it changed */
+		ut.actime = ioloop_time;
+		ut.modtime = index->file_sync_stamp;
+		if (utime(index->filepath, &ut) == -1) {
+			index_set_error(index, "utime() failed for %s: %m",
+					index->filepath);
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/maildir/maildir-update.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,34 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "mmap-util.h"
+#include "maildir-index.h"
+#include "mail-index-util.h"
+
+int maildir_record_update(MailIndex *index, MailIndexUpdate *update,
+			  int fd, const char *path)
+{
+	void *mmap_base;
+	size_t mmap_length;
+
+	i_assert(path != NULL);
+
+	/* we need only the header which probably fits into one page,
+	   so don't use MADV_SEQUENTIAL which would just read more than
+	   is needed. */
+	mmap_base = mmap_ro_file(fd, &mmap_length);
+	if (mmap_base == MAP_FAILED) {
+		index_set_error(index, "update: mmap() failed with file %s: %m",
+				path);
+		return FALSE;
+	}
+
+	if (mmap_base == NULL) {
+		/* empty file */
+		return TRUE;
+	}
+
+	mail_index_update_headers(update, mmap_base, mmap_length, NULL, NULL);
+	(void)munmap(mmap_base, mmap_length);
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mbox/.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/mbox/Makefile.am	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,20 @@
+noinst_LIBRARIES = libstorage_index_mbox.a
+
+INCLUDES = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-mail \
+	-I$(top_srcdir)/src/lib-imap \
+	-I$(top_srcdir)/src/lib-index
+
+libstorage_index_mbox_a_SOURCES = \
+	mbox-append.c \
+	mbox-fsck.c \
+	mbox-index.c \
+	mbox-lock.c \
+	mbox-open.c \
+	mbox-rebuild.c \
+	mbox-sync.c
+
+noinst_HEADERS = \
+	mbox-index.h \
+	mbox-lock.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mbox/mbox-append.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,269 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "mmap-util.h"
+#include "ioloop.h"
+#include "mbox-index.h"
+#include "mail-index-util.h"
+
+#include <time.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <sys/mman.h>
+
+static const char *months[] = {
+	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+static MailIndexRecord *
+mail_index_record_append(MailIndex *index, time_t internal_date,
+			 size_t full_virtual_size)
+{
+	MailIndexRecord trec, *rec;
+
+	memset(&trec, 0, sizeof(MailIndexRecord));
+	trec.internal_date = internal_date;
+	trec.full_virtual_size = full_virtual_size;
+
+	rec = &trec;
+	if (!index->append(index, &rec))
+		return NULL;
+
+	return rec;
+}
+
+static time_t from_line_parse_date(const char *msg, size_t size)
+{
+	const char *msg_end;
+	struct tm tm;
+	int i;
+
+	/* From <sender> <date> <moreinfo> */
+	if (strncmp(msg, "From ", 5) != 0)
+		return 0;
+
+	msg_end = msg + size;
+
+	/* skip sender */
+	msg += 5;
+	while (*msg != ' ' && msg < msg_end) msg++;
+	while (*msg == ' ' && msg < msg_end) msg++;
+
+	/* next 24 chars are the date in asctime() format,
+	   eg. "Thu Nov 29 22:33:52 2001" */
+	if (msg+24 > msg_end)
+		return 0;
+
+	memset(&tm, 0, sizeof(tm));
+
+	/* skip weekday */
+	msg += 4;
+
+	/* month */
+	for (i = 0; i < 12; i++) {
+		if (strncasecmp(months[i], msg, 3) == 0) {
+			tm.tm_mon = i;
+			break;
+		}
+	}
+
+	if (i == 12 || msg[3] != ' ')
+		return 0;
+	msg += 4;
+
+	/* day */
+	if (!i_isdigit(msg[0]) || !i_isdigit(msg[1]) || msg[2] != ' ')
+		return 0;
+	tm.tm_mday = (msg[0]-'0') * 10 + (msg[1]-'0');
+	msg += 3;
+
+	/* hour */
+	if (!i_isdigit(msg[0]) || !i_isdigit(msg[1]) || msg[2] != ':')
+		return 0;
+	tm.tm_hour = (msg[0]-'0') * 10 + (msg[1]-'0');
+	msg += 3;
+
+	/* minute */
+	if (!i_isdigit(msg[0]) || !i_isdigit(msg[1]) || msg[2] != ':')
+		return 0;
+	tm.tm_min = (msg[0]-'0') * 10 + (msg[1]-'0');
+	msg += 3;
+
+	/* second */
+	if (!i_isdigit(msg[0]) || !i_isdigit(msg[1]) || msg[2] != ' ')
+		return 0;
+	tm.tm_sec = (msg[0]-'0') * 10 + (msg[1]-'0');
+	msg += 3;
+
+	/* year */
+	if (!i_isdigit(msg[0]) || !i_isdigit(msg[1]) ||
+	    !i_isdigit(msg[2]) || !i_isdigit(msg[3]))
+		return 0;
+	tm.tm_year = (msg[0]-'0') * 1000 + (msg[1]-'0') * 100 +
+		(msg[2]-'0') * 10 + (msg[3]-'0') - 1900;
+
+	tm.tm_isdst = -1;
+	return mktime(&tm);
+}
+
+static void header_func(MessagePart *part __attr_unused__,
+			const char *name, unsigned int name_len,
+			const char *value, unsigned int value_len,
+			void *user_data)
+{
+	MailIndexRecord *rec = user_data;
+
+	rec->msg_flags |= mbox_header_get_flags(name, name_len,
+						value, value_len);
+}
+
+static int mbox_index_append_data(MailIndex *index, const char *msg,
+				  off_t offset, size_t physical_size,
+				  size_t virtual_size)
+{
+	MailIndexRecord *rec;
+	MailIndexUpdate *update;
+	time_t internal_date;
+	char location[MAX_INT_STRLEN];
+	unsigned int i;
+
+	internal_date = from_line_parse_date(msg, physical_size);
+	if (internal_date <= 0)
+		internal_date = ioloop_time;
+
+	/* skip the From-line */
+	for (i = 0; i < physical_size; i++) {
+		if (msg[i] == '\n') {
+			i++;
+			break;
+		}
+	}
+
+	if (i == physical_size)
+		return FALSE;
+
+	msg += i;
+	offset += i;
+	physical_size -= i;
+	virtual_size -= i;
+	if (i > 0 && msg[i-1] != '\r')
+		virtual_size--;
+
+	rec = mail_index_record_append(index, internal_date, virtual_size);
+	if (rec == NULL)
+		return FALSE;
+
+	update = index->update_begin(index, rec);
+
+	/* location = offset to beginning of message */
+	i_snprintf(location, sizeof(location), "%lu", (unsigned long) offset);
+	index->update_field(update, FIELD_TYPE_LOCATION, location, 0);
+
+	/* parse the header and add cache wanted fields */
+	mail_index_update_headers(update, msg, physical_size, header_func, rec);
+
+	if (!index->update_end(update)) {
+		/* failed - delete the record */
+		(void)index->expunge(index, rec, 0, FALSE);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+int mbox_index_append_mmaped(MailIndex *index, const char *data,
+			     size_t data_size, off_t start_offset)
+{
+	const char *data_start, *data_end, *start, *cr;
+	size_t size, vsize;
+	off_t pos;
+	int missing_cr_count;
+
+	/* we should start with "From ". if we don't, something's messed up
+	   and we should check the whole file instead. */
+	if (strncmp(data, "From ", 5) != 0) {
+		index->set_flags |= MAIL_INDEX_FLAG_FSCK;
+		return FALSE;
+	}
+
+	/* each message ends at "\nFrom ". first get the size of the message,
+	   then parse it. calculate the missing CR count as well. */
+	start = data; cr = NULL; missing_cr_count = 0;
+
+	data_start = data;
+	data_end = data + data_size;
+	for (; data != data_end; data++) {
+		if (*data == '\r')
+			cr = data;
+		else if (*data == '\n') {
+			if (cr != data-1)
+				missing_cr_count++;
+
+			if (data+6 < data_end && data[1] == 'F' &&
+			    data[2] == 'r' && data[3] == 'o' &&
+			    data[4] == 'm' && data[5] == ' ') {
+				/* end of message */
+				pos = (off_t) (start - data_start) +
+					start_offset;
+				size = (size_t) (data - start) + 1;
+				vsize = size + missing_cr_count;
+				if (!mbox_index_append_data(index, start, pos,
+							    size, vsize))
+					return FALSE;
+
+				missing_cr_count = 0;
+				start = data+1;
+			}
+		}
+	}
+
+	/* last message */
+	pos = (off_t) (start - data_start);
+	size = (size_t) (data - start);
+	vsize = size + missing_cr_count;
+	return mbox_index_append_data(index, start, pos, size, vsize);
+}
+
+int mbox_index_append(MailIndex *index, int fd, const char *path)
+{
+	void *mmap_base;
+	size_t mmap_length;
+	off_t pos, end_pos;
+	int ret;
+
+	/* get our current position */
+	pos = lseek(fd, 0, SEEK_CUR);
+
+	/* get the size of the file */
+	end_pos = lseek(fd, 0, SEEK_END);
+
+	if (pos == (off_t)-1 || end_pos == (off_t)-1) {
+		index_set_error(index, "lseek() failed with mbox file %s: %m",
+				path);
+		return FALSE;
+	}
+
+	if (pos == end_pos) {
+		/* no new data */
+		return TRUE;
+	}
+
+	if (!index->set_lock(index, MAIL_LOCK_EXCLUSIVE))
+		return FALSE;
+
+	/* mmap() the file */
+	mmap_length = end_pos-pos;
+	mmap_base = mmap(NULL, mmap_length, PROT_READ, MAP_SHARED, fd, pos);
+	if (mmap_base == MAP_FAILED) {
+		index_set_error(index, "mmap() failed with mbox file %s: %m",
+				path);
+		return FALSE;
+	}
+
+	(void)madvise(mmap_base, mmap_length, MADV_SEQUENTIAL);
+
+	ret = mbox_index_append_mmaped(index, mmap_base, mmap_length, pos);
+	(void)munmap(mmap_base, mmap_length);
+	return ret;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mbox/mbox-fsck.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,184 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "mmap-util.h"
+#include "message-parser.h"
+#include "mbox-index.h"
+#include "mbox-lock.h"
+#include "mail-index-util.h"
+
+#include <fcntl.h>
+
+typedef struct {
+	const char *msgid;
+	MailFlags flags;
+} HeaderData;
+
+static void header_func(MessagePart *part __attr_unused__,
+			const char *name, unsigned int name_len,
+			const char *value, unsigned int value_len,
+			void *user_data)
+{
+	HeaderData *data = user_data;
+
+	if (name_len != 10 || strncasecmp(name, "Message-ID", 10) != 0)
+		return;
+
+	data->msgid = t_strndup(value, value_len);
+	data->flags |= mbox_header_get_flags(name, name_len, value, value_len);
+}
+
+static MailIndexRecord *
+match_next_record(MailIndex *index, MailIndexRecord *rec, unsigned int *seq,
+		  const char **data, const char *data_end)
+{
+	MessageSize hdr_size;
+        HeaderData hdr_data;
+	const char *rec_msgid, *data_next;
+
+	/* skip the From-line */
+	while (*data != data_end && **data != '\n')
+		(*data)++;
+	(*data)++;
+
+	if (*data >= data_end) {
+		/* end of data */
+		(void)index->expunge(index, rec, *seq, TRUE);
+		return rec;
+	}
+
+	/* find the Message-ID from the header */
+	memset(&hdr_data, 0, sizeof(hdr_data));
+	message_parse_header(NULL, *data, (size_t) (data_end-*data), &hdr_size,
+			     header_func, &hdr_data);
+
+	do {
+		do {
+			/* message-id must match (or be non-existant) */
+			rec_msgid = index->lookup_field(index, rec,
+							FIELD_TYPE_MESSAGEID);
+			if (hdr_data.msgid == NULL && rec_msgid != NULL)
+				break;
+			if (hdr_data.msgid != NULL &&
+			    (rec_msgid == NULL ||
+			     strcmp(hdr_data.msgid, rec_msgid) != 0))
+				break;
+
+			/* don't bother parsing the whole body, just make
+			   sure it ends properly */
+			data_next = *data + rec->header_size + rec->body_size;
+			if (data_next == data_end) {
+				/* last message */
+			} else if (data_next+5 >= data_end ||
+				   strncmp(data_next-1, "\nFrom ", 6) != 0)
+				break;
+
+			/* valid message, update flags */
+			if ((rec->msg_flags & hdr_data.flags) != hdr_data.flags)
+				rec->msg_flags |= hdr_data.flags;
+
+			*data = data_next;
+			return rec;
+		} while (0);
+
+		/* try next message */
+		(*seq)++;
+		(void)index->expunge(index, rec, *seq, TRUE);
+		rec = index->next(index, rec);
+	} while (rec != NULL);
+
+	return NULL;
+}
+
+static int mbox_index_fsck_mmap(MailIndex *index, const char *data, size_t size)
+{
+	MailIndexRecord *rec;
+	const char *data_end;
+	unsigned int seq;
+
+	if (!index->set_lock(index, MAIL_LOCK_EXCLUSIVE))
+		return FALSE;
+
+	/* first make sure we start with a "From " line. */
+	if (size <= 5 || strncmp(data, "From ", 5) != 0) {
+		index_set_error(index, "File isn't in mbox format: %s",
+				index->mbox_path);
+		return FALSE;
+	}
+
+	/* we'll go through the mailbox and index in order matching the
+	   messages by their size and Message-ID. old mails aren't remembered,
+	   so we handle well only the cases when mail has been deleted. if
+	   mails have been reordered (eg. sorted by someone) most of the mails
+	   will show up as being new. if we really wanted to support that well,
+	   we could save the message-ids into hash but I don't know if it's
+	   worth the trouble. */
+
+	seq = 1;
+	rec = index->lookup(index, 1);
+
+	data_end = data + size;
+	while (rec != NULL) {
+		rec = match_next_record(index, rec, &seq, &data, data_end);
+		if (rec == NULL)
+			break;
+
+		seq++;
+		rec = index->next(index, rec);
+	}
+
+	if (data == data_end)
+		return TRUE;
+	else {
+		return mbox_index_append_mmaped(index, data,
+						(size_t) (data_end-data), 0);
+	}
+}
+
+int mbox_index_fsck(MailIndex *index)
+{
+	void *mmap_base;
+	size_t mmap_length;
+	int fd, failed;
+
+	/* open the mbox file. we don't really need to open it read-write,
+	   but fcntl() locking requires it. */
+	fd = open(index->mbox_path, O_RDWR);
+	if (fd == -1) {
+		index_set_error(index, "Can't open mbox file %s: %m",
+				index->mbox_path);
+		return FALSE;
+	}
+
+	mmap_base = mmap_ro_file(fd, &mmap_length);
+	if (mmap_base == MAP_FAILED) {
+		index_set_error(index, "mmap() failed with mbox file %s: %m",
+				index->mbox_path);
+		return FALSE;
+	}
+	(void)madvise(mmap_base, mmap_length, MADV_SEQUENTIAL);
+
+	if (mmap_base == NULL) {
+		/* file is empty */
+		(void)close(fd);
+		return TRUE;
+	}
+
+	/* lock the mailbox so we can be sure no-one interrupts us.
+	   we are trying to repair our index after all. */
+	if (!mbox_lock(index, index->mbox_path, fd))
+		failed = TRUE;
+	else {
+		failed = !mbox_index_fsck_mmap(index, mmap_base, mmap_length);
+		(void)mbox_unlock(index, index->mbox_path, fd);
+	}
+
+	(void)munmap(mmap_base, mmap_length);
+	(void)close(fd);
+
+	if (failed)
+		return FALSE;
+
+	/* check the header */
+	return mail_index_fsck(index);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mbox/mbox-index.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,91 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "mbox-index.h"
+#include "mail-index-util.h"
+
+static MailIndex mbox_index;
+
+MailFlags mbox_header_get_flags(const char *name, unsigned int name_len,
+				const char *value, unsigned int value_len)
+{
+	MailFlags flags;
+	unsigned int i;
+
+	flags = 0;
+	if ((name_len == 6 && strncasecmp(name, "Status", 6) == 0) ||
+	    (name_len == 8 && strncasecmp(name, "X-Status", 8) == 0)) {
+		    for (i = 0; i < value_len; i++) {
+			    switch (value[i]) {
+			    case 'A':
+				    flags |= MAIL_ANSWERED;
+				    break;
+			    case 'F':
+				    flags |= MAIL_FLAGGED;
+				    break;
+			    case 'R':
+				    flags |= MAIL_SEEN;
+				    break;
+			    case 'D':
+				    flags |= MAIL_DELETED;
+				    break;
+			    }
+		    }
+	}
+
+	return flags;
+}
+
+MailIndex *mbox_index_alloc(const char *dir, const char *mbox_path)
+{
+	MailIndex *index;
+	int len;
+
+	i_assert(dir != NULL);
+
+	index = i_new(MailIndex, 1);
+	memcpy(index, &mbox_index, sizeof(MailIndex));
+
+	index->fd = -1;
+	index->dir = i_strdup(dir);
+
+	len = strlen(index->dir);
+	if (index->dir[len-1] == '/')
+		index->dir[len-1] = '\0';
+
+	index->mbox_path = i_strdup(mbox_path);
+	return (MailIndex *) index;
+}
+
+static void mbox_index_free(MailIndex *index)
+{
+	mail_index_close(index);
+	i_free(index->dir);
+	i_free(index);
+}
+
+static MailIndex mbox_index = {
+	mail_index_open,
+	mail_index_open_or_create,
+	mbox_index_free,
+	mail_index_set_lock,
+	mail_index_try_lock,
+	mbox_index_rebuild,
+	mbox_index_fsck,
+	mbox_index_sync,
+	mail_index_get_header,
+	mail_index_lookup,
+	mail_index_next,
+        mail_index_lookup_uid_range,
+	mail_index_lookup_field,
+	mail_index_get_sequence,
+	mbox_open_mail,
+	mail_index_expunge,
+	mail_index_update_flags,
+	mail_index_append,
+	mail_index_update_begin,
+	mail_index_update_end,
+	mail_index_update_field,
+	mail_index_get_last_error,
+	mail_index_is_inconsistency_error
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mbox/mbox-index.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,21 @@
+#ifndef __MBOX_INDEX_H
+#define __MBOX_INDEX_H
+
+#include "mail-index.h"
+
+MailIndex *mbox_index_alloc(const char *dir, const char *mbox_path);
+
+MailFlags mbox_header_get_flags(const char *name, unsigned int name_len,
+				const char *value, unsigned int value_len);
+
+int mbox_index_rebuild(MailIndex *index);
+int mbox_index_sync(MailIndex *index);
+int mbox_index_fsck(MailIndex *index);
+int mbox_open_mail(MailIndex *index, MailIndexRecord *rec,
+		   off_t *offset, size_t *size);
+
+int mbox_index_append(MailIndex *index, int fd, const char *path);
+int mbox_index_append_mmaped(MailIndex *index, const char *data,
+			     size_t data_size, off_t start_offset);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mbox/mbox-lock.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,160 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "mbox-index.h"
+#include "mbox-lock.h"
+#include "mail-index-util.h"
+
+#include <time.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_FLOCK
+#  include <sys/file.h>
+#endif
+
+#ifdef HAVE_FLOCK
+#  define USE_FLOCK
+#endif
+
+/* 0.1 .. 0.2msec */
+#define LOCK_RANDOM_USLEEP_TIME (100000 + (unsigned int)rand() % 100000)
+
+/* abort trying to get lock after 30 seconds */
+#define MAX_LOCK_WAIT_SECONDS 30
+
+/* remove lock after 10 mins */
+#define STALE_LOCK_TIMEOUT (60*10)
+
+#ifdef USE_FLOCK
+
+static int mbox_lock_flock(MailIndex *index, const char *path, int fd, int set)
+{
+	if (flock(fd, set ? LOCK_EX : LOCK_UN) == -1) {
+		index_set_error(index, "flock() mbox lock failed for file "
+				"%s: %m", path);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+#else
+
+static int mbox_lock_fcntl(MailIndex *index, const char *path, int fd, int set)
+{
+	struct flock fl;
+
+	fl.l_type = set ? F_WRLCK : F_UNLCK;
+	fl.l_whence = SEEK_SET;
+	fl.l_start = 0;
+	fl.l_len = 0;
+
+	while (fcntl(fd, F_SETLKW, &fl) == -1) {
+		if (errno != EINTR) {
+			index_set_error(index, "fcntl() mbox lock "
+					"failed for file %s: %m", path);
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+#endif
+
+static int mbox_lock_dotlock(MailIndex *index, const char *path, int set)
+{
+	struct stat st;
+	time_t now, max_wait_time;
+	int fd;
+
+	path = t_strconcat(path, ".lock", NULL);
+	if (!set) {
+		if (unlink(path) == 0 || errno == ENOENT)
+			return TRUE;
+
+		index_set_error(index, "unlink() failed for dotlock file "
+				"%s: %m", path);
+		return FALSE;
+	}
+
+	/* don't bother with the temp files as we'd just leave them lying
+	   around. besides, postfix also relies on O_EXCL working so we
+	   might as well. */
+	max_wait_time = time(NULL) + MAX_LOCK_WAIT_SECONDS;
+	do {
+		now = time(NULL);
+
+		if (stat(path, &st) == 0) {
+			/* lock exists, see if it's too old */
+			if (now > st.st_ctime + STALE_LOCK_TIMEOUT) {
+				if (unlink(path) == -1 && errno != ENOENT) {
+					index_set_error(index, "unlink() failed"
+							" for dotlock file "
+							"%s: %m", path);
+					break;
+				}
+			}
+
+			usleep(LOCK_RANDOM_USLEEP_TIME);
+			continue;
+		}
+
+		fd = open(path, O_WRONLY | O_EXCL | O_CREAT, 0);
+		if (fd >= 0) {
+			/* got it */
+			(void)close(fd);
+			return TRUE;
+		}
+
+		if (errno != EEXIST) {
+			index_set_error(index, "Can't create dotlock file "
+					"%s: %m", path);
+			break;
+		}
+	} while (now < max_wait_time);
+
+	return FALSE;
+}
+
+int mbox_lock(MailIndex *index, const char *path, int fd)
+{
+	i_assert(fd >= 0);
+
+	if (++index->mbox_locks > 1)
+		return TRUE;
+
+#ifdef USE_FLOCK
+	if (!mbox_lock_flock(index, path, fd, TRUE))
+		return FALSE;
+#else
+	if (!mbox_lock_fcntl(index, path, fd, TRUE))
+		return FALSE;
+#endif
+	if (!mbox_lock_dotlock(index, path, TRUE))
+		return FALSE;
+
+	return TRUE;
+}
+
+int mbox_unlock(MailIndex *index, const char *path, int fd)
+{
+	i_assert(fd >= 0);
+
+	if (--index->mbox_locks > 0)
+		return TRUE;
+
+#ifdef USE_FLOCK
+	if (!mbox_lock_flock(index, path, fd, FALSE))
+		return FALSE;
+#else
+	if (!mbox_lock_fcntl(index, path, fd, FALSE))
+		return FALSE;
+#endif
+	if (!mbox_lock_dotlock(index, path, FALSE))
+		return FALSE;
+
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mbox/mbox-lock.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,7 @@
+#ifndef __MBOX_LOCK_H
+#define __MBOX_LOCK_H
+
+int mbox_lock(MailIndex *index, const char *path, int fd);
+int mbox_unlock(MailIndex *index, const char *path, int fd);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mbox/mbox-open.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,77 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "mbox-index.h"
+#include "mail-index-util.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+int mbox_open_mail(MailIndex *index, MailIndexRecord *rec,
+		   off_t *offset, size_t *size)
+{
+	const char *location;
+	off_t pos;
+	char buf[5];
+	int fd, ret, ok;
+
+	i_assert(index->lock_type != MAIL_LOCK_UNLOCK);
+
+	location = index->lookup_field(index, rec, FIELD_TYPE_LOCATION);
+	if (location == NULL) {
+                INDEX_MARK_CORRUPTED(index);
+		index_set_error(index, "Corrupted index file %s: "
+				"Missing location field for record %u",
+				index->filepath, rec->uid);
+		return -1;
+	}
+
+	/* location = offset */
+	*offset = (off_t)strtoul(location, NULL, 10);
+	*size = rec->header_size + rec->body_size;
+
+	fd = open(index->mbox_path, O_RDONLY);
+	if (fd == -1) {
+		index_set_error(index, "Can't open mbox file %s: %m",
+				index->mbox_path);
+		return -1;
+	}
+
+	pos = lseek(fd, *offset, SEEK_SET);
+	if (pos == (off_t)-1) {
+		index_set_error(index, "lseek() failed with mbox file %s: %m",
+				index->mbox_path);
+		(void)close(fd);
+		return -1;
+	}
+
+	ok = FALSE;
+	if (pos == *offset) {
+		/* make sure message size is valid */
+		pos = *offset + *size;
+		if (lseek(fd, pos, SEEK_SET) == pos) {
+			/* and check that we end with either EOF or to
+			   beginning of next message */
+			ret = read(fd, buf, 5);
+			if (ret == 0)
+				ok = TRUE; /* end of file */
+			else if (ret == 5 && strncmp(buf, "From ", 5) == 0)
+				ok = TRUE;
+		}
+	}
+
+	if (ok) {
+		if (lseek(fd, *offset, SEEK_SET) == *offset)
+			return fd;
+
+		index_set_error(index, "lseek() failed with mbox file %s: %m",
+				index->mbox_path);
+	} else {
+		/* file has been updated, rescan it */
+		index->set_flags |= MAIL_INDEX_FLAG_FSCK;
+	}
+
+	(void)close(fd);
+	return -1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mbox/mbox-rebuild.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,85 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "mbox-index.h"
+#include "mbox-lock.h"
+#include "mail-index-data.h"
+#include "mail-index-util.h"
+#include "mail-hash.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+int mbox_index_rebuild(MailIndex *index)
+{
+	struct stat st;
+	int fd;
+
+	i_assert(index->lock_type != MAIL_LOCK_SHARED);
+
+	if (!mail_index_set_lock(index, MAIL_LOCK_EXCLUSIVE))
+		return FALSE;
+
+	/* reset the header */
+	mail_index_init_header(index->header);
+
+	/* we require Message-ID to be cached */
+	index->header->cache_fields |= FIELD_TYPE_MESSAGEID;
+
+	/* update indexid */
+	index->indexid = index->header->indexid;
+
+	if (msync(index->mmap_base, sizeof(MailIndexHeader), MS_SYNC) == -1)
+		return FALSE;
+
+	/* truncate the file first, so it won't contain
+	   any invalid data even if we crash */
+	if (ftruncate(index->fd, sizeof(MailIndexHeader)) == -1) {
+		index_set_error(index, "Can't truncate index file %s: %m",
+				index->filepath);
+		return FALSE;
+	}
+
+	/* reset data file */
+	if (!mail_index_data_reset(index->data))
+		return FALSE;
+
+	/* open the mbox file. we don't really need to open it read-write,
+	   but fcntl() locking requires it. */
+	fd = open(index->mbox_path, O_RDWR);
+	if (fd == -1) {
+		index_set_error(index, "Error opening mbox file %s: %m",
+				index->mbox_path);
+		return FALSE;
+	}
+
+	/* lock the mailbox so we can be sure no-one interrupts us. */
+	if (!mbox_lock(index, index->mbox_path, fd)) {
+		(void)close(fd);
+		return FALSE;
+	}
+
+	if (!mbox_index_append(index, fd, index->mbox_path)) {
+		(void)mbox_unlock(index, index->mbox_path, fd);
+		(void)close(fd);
+		return FALSE;
+	}
+
+	(void)mbox_unlock(index, index->mbox_path, fd);
+	(void)close(fd);
+
+	/* update sync stamp */
+	if (stat(index->mbox_path, &st) == -1) {
+		index_set_error(index, "fstat() failed for mbox file %s: %m",
+				index->mbox_path);
+		return FALSE;
+	}
+
+	index->file_sync_stamp = st.st_mtime;
+
+	/* rebuild is complete - remove the flag */
+	index->header->flags &= ~(MAIL_INDEX_FLAG_REBUILD|MAIL_INDEX_FLAG_FSCK);
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-index/mbox/mbox-sync.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,121 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "mbox-index.h"
+#include "mail-index-util.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+static size_t get_indexed_mbox_size(MailIndex *index)
+{
+	MailIndexRecord *rec, *prev;
+	const char *location;
+	size_t size;
+
+	if (index->lock_type == MAIL_LOCK_UNLOCK) {
+		if (!mail_index_set_lock(index, MAIL_LOCK_SHARED))
+			return 0;
+	}
+
+	rec = index->header->messages_count == 0 ? NULL :
+		index->lookup(index, index->header->messages_count);
+	if (rec == NULL) {
+		rec = prev = index->lookup(index, 1);
+		while (rec != NULL) {
+			prev = rec;
+			rec = index->next(index, rec);
+		}
+
+		rec = prev;
+	}
+
+	size = 0;
+	if (rec != NULL) {
+		location = index->lookup_field(index, rec, FIELD_TYPE_LOCATION);
+		if (location == NULL) {
+			INDEX_MARK_CORRUPTED(index);
+			index_set_error(index, "Corrupted index file %s: "
+					"Missing location field for record %u",
+					index->filepath, rec->uid);
+		} else {
+			size = strtoul(location, NULL, 10) +
+				rec->header_size + rec->body_size;
+		}
+	}
+
+	if (index->lock_type == MAIL_LOCK_SHARED)
+		(void)mail_index_set_lock(index, MAIL_LOCK_UNLOCK);
+	return size;
+}
+
+static int mbox_check_new_mail(MailIndex *index)
+{
+	off_t pos;
+	int fd, ret;
+
+	fd = open(index->mbox_path, O_RDONLY);
+	if (fd == -1) {
+		index_set_error(index, "Can't open mbox file %s: %m",
+				index->mbox_path);
+		return FALSE;
+	}
+
+	pos = lseek(fd, index->mbox_size, SEEK_SET);
+	if (pos == (off_t)-1) {
+		index_set_error(index, "lseek() failed with mbox file %s: %m",
+				index->mbox_path);
+		(void)close(fd);
+		return FALSE;
+	}
+
+	if (pos != index->mbox_size) {
+		/* someone just shrinked the file? */
+		(void)close(fd);
+		return mbox_index_fsck(index);
+	}
+
+	/* add the new data */
+	ret = mbox_index_append(index, fd, index->mbox_path);
+	(void)close(fd);
+
+	if (index->set_flags & MAIL_INDEX_FLAG_FSCK) {
+		/* it wasn't just new mail, reread the mbox */
+		index->set_flags &= ~MAIL_INDEX_FLAG_FSCK;
+		return mbox_index_fsck(index);
+	}
+
+	return ret;
+}
+
+int mbox_index_sync(MailIndex *index)
+{
+	struct stat st;
+
+	i_assert(index->lock_type != MAIL_LOCK_SHARED);
+
+	if (stat(index->mbox_path, &st) == -1) {
+		index_set_error(index, "stat() failed with mbox file %s: %m",
+				index->mbox_path);
+		return FALSE;
+	}
+
+	if (index->file_sync_stamp == st.st_mtime)
+		return TRUE;
+
+	index->file_sync_stamp = st.st_mtime;
+
+	if (index->mbox_size == 0 && st.st_size != 0)
+		index->mbox_size = get_indexed_mbox_size(index);
+
+	/* file has been modified. */
+	if (index->mbox_size > st.st_size) {
+		/* file was grown, hopefully just new mail */
+		return mbox_check_new_mail(index);
+	} else {
+		/* something changed, scan through the whole mbox */
+		return mbox_index_fsck(index);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-mail/.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-mail/Makefile.am	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,20 @@
+noinst_LIBRARIES = libmail.a
+
+INCLUDES = \
+	-I$(top_srcdir)/src/lib
+
+libmail_a_SOURCES = \
+	message-parser.c \
+	message-content-parser.c \
+	message-size.c \
+	rfc822-address.c \
+	rfc822-date.c \
+	rfc822-tokenize.c
+
+noinst_HEADERS = \
+	message-parser.h \
+	message-content-parser.h \
+	message-size.h \
+	rfc822-address.h \
+	rfc822-date.h \
+	rfc822-tokenize.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-mail/message-content-parser.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,50 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "rfc822-tokenize.h"
+#include "message-content-parser.h"
+
+int message_content_parse_header(const char *value,
+				 ParseContentFunc func,
+				 ParseContentParamFunc param_func,
+				 void *user_data)
+{
+	const Rfc822Token *tokens;
+	int i, next, ntokens;
+
+	tokens = rfc822_tokenize(value, &ntokens, NULL, NULL);
+	if (tokens == NULL) {
+		/* error */
+		return FALSE;
+	}
+
+	/* first ';' separates the parameters */
+	for (i = 0; i < ntokens; i++) {
+		if (tokens[i].token == ';')
+			break;
+	}
+
+	if (func != NULL)
+		func(tokens, i, user_data);
+
+	if (param_func != NULL) {
+		/* parse the parameters */
+		for (i++; i < ntokens; i = next) {
+			/* find the next ';' */
+			for (next = i+1; next < ntokens; next++) {
+				if (tokens[next].token == ';')
+					break;
+			}
+
+			if (i+2 < next &&
+			    tokens[i].token == 'A' &&
+			    tokens[i+1].token == '=') {
+				/* <atom> = <value> */
+				param_func(tokens + i, tokens + i + 2,
+					   next - (i+2), user_data);
+			}
+		}
+	}
+
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-mail/message-content-parser.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,21 @@
+#ifndef __MESSAGE_CONTENT_PARSER_H
+#define __MESSAGE_CONTENT_PARSER_H
+
+/* functions can safely store data into temporary memory pool,
+   ie. message_content_parse_header() is guaranteed not to call
+   t_push()/t_pop() */
+
+/* Note that count can be 0 */
+typedef void (*ParseContentFunc)(const Rfc822Token *tokens, int count,
+				 void *user_data);
+/* name is always atom, value_count is always > 0 */
+typedef void (*ParseContentParamFunc)(const Rfc822Token *name,
+				      const Rfc822Token *value,
+				      int value_count, void *user_data);
+
+int message_content_parse_header(const char *value,
+				 ParseContentFunc func,
+				 ParseContentParamFunc param_func,
+				 void *user_data);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-mail/message-parser.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,501 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "rfc822-tokenize.h"
+#include "message-content-parser.h"
+#include "message-parser.h"
+
+typedef struct _MessageBoundary {
+	struct _MessageBoundary *next;
+
+	MessagePart *part;
+	const char *boundary;
+	unsigned int len;
+} MessageBoundary;
+
+typedef struct {
+	Pool pool;
+	MessagePart *part;
+
+	char *last_boundary;
+	char *last_content_type;
+	MessageBoundary *boundaries;
+
+	MessageHeaderFunc func;
+	void *user_data;
+} MessageParseData;
+
+static MessagePart *message_parse_part(const char *msg, size_t size,
+				       MessageParseData *parse_data);
+static MessagePart *message_parse_body(const char *msg, size_t size,
+				       MessageBoundary *boundaries,
+				       MessageSize *body_size);
+static MessagePart *message_skip_boundary(const char *msg, size_t size,
+					  MessageBoundary *boundaries,
+					  MessageSize *boundary_size);
+
+static void message_size_add_part(MessageSize *dest, MessagePart *part)
+{
+	dest->physical_size +=
+		part->header_size.physical_size +
+		part->body_size.physical_size;
+	dest->virtual_size +=
+		part->header_size.virtual_size +
+		part->body_size.virtual_size;
+	dest->lines += part->header_size.lines + part->body_size.lines;
+}
+
+static MessagePart *message_part_append(Pool pool, MessagePart *parent)
+{
+	MessagePart *part, **list;
+
+	part = p_new(pool, MessagePart, 1);
+	part->parent = parent;
+
+	list = &part->parent->children;
+	while (*list != NULL)
+		list = &(*list)->next;
+
+	*list = part;
+	return part;
+}
+
+static void parse_content_type(const Rfc822Token *tokens, int count,
+			       void *user_data)
+{
+	MessageParseData *parse_data = user_data;
+	const char *str;
+
+	if (tokens[0].token != 'A')
+		return;
+
+	if (parse_data->last_content_type != NULL)
+		return;
+
+	str = rfc822_tokens_get_value(tokens, count, FALSE);
+	parse_data->last_content_type = p_strdup(parse_data->pool, str);
+
+	if (strcasecmp(str, "message/rfc822") == 0)
+		parse_data->part->message_rfc822 = TRUE;
+	else if (strncasecmp(str, "text/", 5) == 0)
+		parse_data->part->text = TRUE;
+	else if (strncasecmp(str, "multipart/", 10) == 0) {
+		parse_data->part->multipart = TRUE;
+
+		if (strcasecmp(str+10, "digest") == 0)
+			parse_data->part->multipart_digest = TRUE;
+	}
+}
+
+static void parse_content_type_param(const Rfc822Token *name,
+				     const Rfc822Token *value,
+				     int value_count, void *user_data)
+{
+	MessageParseData *parse_data = user_data;
+	const char *str;
+
+	if (!parse_data->part->multipart || name->len != 8 ||
+	    strncasecmp(name->ptr, "boundary", 8) != 0)
+		return;
+
+	if (parse_data->last_boundary == NULL) {
+		str = rfc822_tokens_get_value(value, value_count, FALSE);
+		parse_data->last_boundary = p_strdup(parse_data->pool, str);
+	}
+}
+
+static void parse_header_field(MessagePart *part,
+			       const char *name, unsigned int name_len,
+			       const char *value, unsigned int value_len,
+			       void *user_data)
+{
+	MessageParseData *parse_data = user_data;
+
+	/* call the user-defined header parser */
+	if (parse_data->func != NULL) {
+		parse_data->func(part, name, name_len, value, value_len,
+				 parse_data->user_data);
+	}
+
+	if (name_len == 12 && strncasecmp(name, "Content-Type", 12) == 0) {
+		/* we need to know the boundary */
+		(void)message_content_parse_header(t_strndup(value, value_len),
+						   parse_content_type,
+						   parse_content_type_param,
+						   parse_data);
+	}
+}
+
+static MessagePart *message_parse_multipart(const char *msg, size_t size,
+					    MessageParseData *parse_data)
+{
+	MessagePart *parent_part, *next_part, *part;
+	MessageBoundary *b;
+	off_t offset;
+
+	/* multipart message. add new boundary */
+	b = t_new(MessageBoundary, 1);
+	b->part = parse_data->part;
+	b->boundary = t_strdup(parse_data->last_boundary);
+	b->len = strlen(b->boundary);
+
+	b->next = parse_data->boundaries;
+	parse_data->boundaries = b;
+
+	/* reset fields */
+	p_free_and_null(parse_data->pool, parse_data->last_boundary);
+	p_free_and_null(parse_data->pool, parse_data->last_content_type);
+
+	/* skip the data before the first boundary */
+	parent_part = parse_data->part;
+	next_part = message_skip_boundary(msg, size, parse_data->boundaries,
+					  &parent_part->body_size);
+
+	/* now, parse the parts */
+	while (next_part == parent_part) {
+		/* new child */
+		part = message_part_append(parse_data->pool, parent_part);
+
+		/* set child position */
+		memcpy(&part->pos, &parent_part->pos, sizeof(MessagePosition));
+		part->pos.physical_pos += parent_part->body_size.physical_size +
+			parent_part->header_size.physical_size;
+		part->pos.virtual_pos += parent_part->body_size.virtual_size +
+			parent_part->header_size.virtual_size;
+
+		offset = parent_part->body_size.physical_size;
+                parse_data->part = part;
+		next_part = message_parse_part(msg + offset, size - offset,
+					       parse_data);
+
+		/* update our size */
+		message_size_add_part(&parent_part->body_size, part);
+
+		if (next_part != parent_part)
+			break;
+
+		/* skip the boundary */
+		offset = parent_part->body_size.physical_size;
+		next_part = message_skip_boundary(msg + offset, size - offset,
+						  parse_data->boundaries,
+						  &parent_part->body_size);
+	}
+
+	/* remove boundary */
+	i_assert(parse_data->boundaries == b);
+	parse_data->boundaries = b->next;
+	return next_part;
+}
+
+static MessagePart *message_parse_part(const char *msg, size_t size,
+				       MessageParseData *parse_data)
+{
+	MessagePart *next_part, *part;
+	size_t hdr_size;
+
+	message_parse_header(parse_data->part, msg, size,
+			     &parse_data->part->header_size,
+			     parse_header_field, parse_data);
+
+	/* update message position/size */
+	hdr_size = parse_data->part->header_size.physical_size;
+	msg += hdr_size; size -= hdr_size;
+
+	if (parse_data->last_boundary != NULL)
+		return message_parse_multipart(msg, size, parse_data);
+
+	if (parse_data->last_content_type == NULL) {
+		if (parse_data->part->parent != NULL &&
+		    parse_data->part->parent->multipart_digest) {
+			/* when there's no content-type specified and we're
+			   below multipart/digest, the assume message/rfc822
+			   content-type */
+			parse_data->part->message_rfc822 = TRUE;
+		} else {
+			/* otherwise we default to text/plain */
+			parse_data->part->text = TRUE;
+		}
+	}
+
+	p_free_and_null(parse_data->pool, parse_data->last_boundary);
+	p_free_and_null(parse_data->pool, parse_data->last_content_type);
+
+	if (parse_data->part->message_rfc822) {
+		/* message/rfc822 part - the message body begins with
+		   headers again, this works pretty much the same as
+		   a single multipart/mixed item */
+		part = message_part_append(parse_data->pool, parse_data->part);
+
+		parse_data->part = part;
+		next_part = message_parse_part(msg, size, parse_data);
+		parse_data->part = part->parent;
+
+		/* our body size is the size of header+body in message/rfc822 */
+		message_size_add_part(&part->parent->body_size, part);
+	} else {
+		/* normal message, read until the next boundary */
+		part = parse_data->part;
+		next_part = message_parse_body(msg, size,
+					       parse_data->boundaries,
+					       &part->body_size);
+	}
+
+	return next_part;
+}
+
+MessagePart *message_parse(Pool pool, const char *msg, size_t size,
+			   MessageHeaderFunc func, void *user_data)
+{
+	MessagePart *part;
+	MessageParseData parse_data;
+
+	memset(&parse_data, 0, sizeof(parse_data));
+	parse_data.pool = pool;
+	parse_data.func = func;
+	parse_data.user_data = user_data;
+	parse_data.part = part = p_new(pool, MessagePart, 1);
+
+	t_push();
+	message_parse_part(msg, size, &parse_data);
+	t_pop();
+	return part;
+}
+
+void message_parse_header(MessagePart *part, const char *msg, size_t size,
+			  MessageSize *hdr_size,
+			  MessageHeaderFunc func, void *user_data)
+{
+	const char *msg_start, *msg_end, *cr, *last_lf;
+	const char *name, *value, *name_end, *value_end;
+	int missing_cr_count, stop;
+
+	msg_start = msg;
+	msg_end = msg + size;
+
+	missing_cr_count = 0; cr = NULL;
+	name = msg; name_end = value = last_lf = NULL;
+
+	if (hdr_size != NULL)
+		hdr_size->lines = 0;
+
+	stop = FALSE;
+	while (msg != msg_end && !stop) {
+		switch (*msg) {
+		case '\n':
+			if (hdr_size != NULL)
+				hdr_size->lines++;
+
+			if (msg == msg_start ||
+			    (cr == msg_start && cr == msg-1)) {
+				/* no headers at all */
+				if (cr != msg-1)
+					missing_cr_count++;
+				stop = TRUE;
+				break;
+			} else if (cr == msg-1) {
+				/* CR+LF */
+				value_end = cr;
+
+				if (last_lf == cr-1) {
+					/* LF+CR+LF -> end of headers */
+					stop = TRUE;
+					break;
+				}
+			} else {
+				/* missing CR */
+				missing_cr_count++;
+				value_end = msg;
+
+				if (last_lf == msg-1) {
+					/* LF+LF -> end of headers */
+					stop = TRUE;
+					break;
+				}
+			}
+			last_lf = msg;
+
+			if (msg+1 != msg_end && IS_LWSP(msg[1])) {
+				/* long header continuing in next line */
+				break;
+			}
+
+			/* Ignore header lines missing ':' (value == NULL) */
+			if (func != NULL && value != NULL) {
+				func(part, name, (unsigned int) (name_end-name),
+				     value, (unsigned int) (value_end-value),
+				     user_data);
+			}
+
+			/* reset the data */
+			name = msg+1;
+			name_end = NULL;
+			value = NULL;
+			break;
+		case '\r':
+			cr = msg;
+			break;
+		case ':':
+			if (value != NULL)
+				break;
+			name_end = msg;
+
+			/* skip the ending whitespace for field */
+			while (name_end != name && IS_LWSP(name_end[-1]))
+				name_end--;
+
+			/* get beginning of field value */
+			value = msg+1;
+			if (msg+1 != msg_end && IS_LWSP(msg[1]))
+				value++;
+			break;
+		}
+
+		msg++;
+	}
+
+	if (hdr_size != NULL) {
+		hdr_size->physical_size = (int) (msg - msg_start);
+		hdr_size->virtual_size =
+			hdr_size->physical_size + missing_cr_count;
+	}
+}
+
+static MessageBoundary *boundary_find(MessageBoundary *boundaries,
+				      const char *msg, unsigned int len)
+{
+	while (boundaries != NULL) {
+		if (boundaries->len <= len &&
+		    strncmp(boundaries->boundary, msg, boundaries->len) == 0)
+			return boundaries;
+
+		boundaries = boundaries->next;
+	}
+
+	return NULL;
+}
+
+static MessagePart *message_parse_body(const char *msg, size_t size,
+				       MessageBoundary *boundaries,
+				       MessageSize *body_size)
+{
+	MessageBoundary *boundary;
+	const char *msg_start, *msg_end, *cr;
+	unsigned int missing_cr_count, len;
+
+	msg_start = msg;
+	msg_end = msg + size;
+
+	missing_cr_count = 0; cr = NULL;
+
+	boundary = NULL;
+	while (msg != msg_end) {
+		if (*msg == '\r')
+			cr = msg;
+		else if (*msg == '\n') {
+			if (cr != msg-1)
+				missing_cr_count++;
+			body_size->lines++;
+		} else if (*msg == '-' && msg+2 < msg_end && msg[1] == '-' &&
+			   (msg == msg_start || msg[-1] == '\n')) {
+			/* "\n--", could be boundary */
+			len = (unsigned int) (msg_end - (msg+2));
+
+			boundary = boundary_find(boundaries, msg+2, len);
+			if (boundary != NULL) {
+				/* boundary found, move the pointer
+				   before the [CR]LF */
+				if (msg != msg_start) {
+					msg--;
+					if (cr == msg-1)
+						msg--;
+					else
+						missing_cr_count--;
+					body_size->lines--;
+				}
+				break;
+			}
+		}
+
+		msg++;
+	}
+
+	len = (unsigned int) (msg - msg_start);
+	body_size->physical_size += len;
+	body_size->virtual_size += len + missing_cr_count;
+
+	return boundary == NULL ? NULL : boundary->part;
+}
+
+static MessagePart *message_skip_boundary(const char *msg, size_t size,
+					  MessageBoundary *boundaries,
+					  MessageSize *boundary_size)
+{
+	MessageBoundary *boundary;
+	const char *msg_start, *msg_end, *cr;
+	unsigned int len, missing_cr_count;
+	int end_boundary;
+
+	/* first find and skip the boundary */
+	msg_start = msg;
+	msg_end = msg + size;
+
+	cr = NULL; missing_cr_count = 0;
+
+	boundary = NULL;
+	while (msg != msg_end) {
+		if (*msg == '-' && msg+2 < msg_end && msg[1] == '-' &&
+		    (msg == msg_start || msg[-1] == '\n')) {
+			/* possible boundary */
+			len = (unsigned int) (msg_end - (msg+2));
+			boundary = boundary_find(boundaries, msg+2, len);
+			if (boundary != NULL) {
+				/* skip the boundary */
+				msg += 2 + boundary->len;
+				break;
+			}
+		} else if (*msg == '\r')
+			cr = msg;
+		else if (*msg == '\n') {
+			if (cr != msg-1)
+				missing_cr_count++;
+			boundary_size->lines++;
+		}
+		msg++;
+	}
+
+	len = (unsigned int) (msg - msg_start);
+	boundary_size->physical_size += len;
+	boundary_size->virtual_size += len + missing_cr_count;
+
+	if (boundary == NULL)
+		return NULL;
+
+	/* now read the boundary until we reach the end of line */
+	msg_start = msg;
+	end_boundary = msg+2 <= msg_end && msg[0] == '-' && msg[1] == '-';
+	while (msg != msg_end) {
+		if (*msg == '\r')
+			cr = msg;
+		else if (*msg == '\n') {
+			if (cr != msg-1)
+				boundary_size->virtual_size++;
+			boundary_size->lines++;
+			msg++;
+			break;
+		}
+
+		msg++;
+	}
+
+	len = (unsigned int) (msg - msg_start);
+	boundary_size->physical_size += len;
+	boundary_size->virtual_size += len;
+
+	if (end_boundary) {
+		/* skip the footer */
+		return message_parse_body(msg, (unsigned int) (msg_end-msg),
+					  boundaries, boundary_size);
+	}
+
+	return boundary->part;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-mail/message-parser.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,55 @@
+#ifndef __MESSAGE_PARSER_H
+#define __MESSAGE_PARSER_H
+
+typedef struct _MessagePart MessagePart;
+typedef struct _MessagePosition MessagePosition;
+typedef struct _MessageSize MessageSize;
+
+struct _MessagePosition {
+	off_t physical_pos;
+	off_t virtual_pos;
+};
+
+struct _MessageSize {
+	size_t physical_size;
+	size_t virtual_size;
+	unsigned int lines;
+};
+
+struct _MessagePart {
+	MessagePart *parent;
+	MessagePart *next;
+	MessagePart *children;
+
+        MessagePosition pos;
+	MessageSize header_size;
+	MessageSize body_size;
+
+	unsigned int multipart:1;
+	unsigned int multipart_digest:1;
+	unsigned int message_rfc822:1;
+	unsigned int text:1; /* content-type: text/.. */
+	unsigned int binary:1; /* content-transfer-encoding: binary */
+
+	void *user_data;
+};
+
+/* NOTE: name and value aren't \0-terminated */
+typedef void (*MessageHeaderFunc)(MessagePart *part,
+				  const char *name, unsigned int name_len,
+				  const char *value, unsigned int value_len,
+				  void *user_data);
+
+/* func is called for each field in message header. */
+MessagePart *message_parse(Pool pool, const char *msg, size_t size,
+			   MessageHeaderFunc func, void *user_data);
+
+/* Call func for each field in message header. Fills the hdr_size.
+   part can be NULL, just make sure your header function works with it.
+   This function doesn't use temp. mempool so your header function may save
+   return values to it. */
+void message_parse_header(MessagePart *part, const char *msg, size_t size,
+			  MessageSize *hdr_size,
+			  MessageHeaderFunc func, void *user_data);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-mail/message-size.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,95 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "message-parser.h"
+#include "message-size.h"
+
+void message_get_header_size(const char *msg, size_t size, MessageSize *hdr)
+{
+	const char *msg_start, *msg_end, *cr, *last_lf;
+	int missing_cr_count;
+
+	hdr->lines = 0;
+
+	msg_start = msg;
+	msg_end = msg + size;
+
+	/* get header size */
+
+	cr = last_lf = NULL; missing_cr_count = 0;
+	while (msg != msg_end) {
+		if (*msg == '\r')
+			cr = msg;
+		else if (*msg == '\n') {
+			hdr->lines++;
+
+			if (msg == msg_start ||
+			    (cr == msg_start && cr == msg-1)) {
+				/* no headers at all */
+				if (cr != msg-1)
+					missing_cr_count++;
+				msg++;
+				break;
+			}
+
+			if (cr == msg-1) {
+				/* CR+LF */
+				if (last_lf == cr-1) {
+					/* LF+CR+LF -> end of headers */
+					msg++;
+					break;
+				}
+			} else {
+				/* missing CR */
+				missing_cr_count++;
+
+				if (last_lf == msg-1) {
+					/* LF+LF -> end of headers */
+					msg++;
+					break;
+				}
+			}
+			last_lf = msg;
+		}
+
+		msg++;
+	}
+
+	hdr->physical_size = (int) (msg-msg_start);
+	hdr->virtual_size = hdr->physical_size + missing_cr_count;
+}
+
+void message_get_body_size(const char *msg, size_t size, MessageSize *body)
+{
+	const char *msg_start, *msg_end, *cr;
+	int missing_cr_count;
+
+	msg_start = msg;
+	msg_end = msg + size;
+
+	body->lines = 0;
+
+	cr = NULL; missing_cr_count = 0;
+	while (msg != msg_end) {
+		if (*msg == '\r')
+			cr = msg;
+		else if (*msg == '\n') {
+			body->lines++;
+
+			if (cr != msg-1)
+				missing_cr_count++;
+		}
+
+		msg++;
+	}
+
+	body->physical_size = (int) (msg-msg_start);
+	body->virtual_size = (int) (msg-msg_start) + missing_cr_count;
+}
+
+void message_size_add(MessageSize *dest, MessageSize *src)
+{
+	dest->virtual_size += src->virtual_size;
+	dest->physical_size += src->physical_size;
+	dest->lines += src->lines;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-mail/message-size.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,10 @@
+#ifndef __MESSAGE_SIZE_H
+#define __MESSAGE_SIZE_H
+
+#include "message-parser.h"
+
+void message_get_header_size(const char *msg, size_t size, MessageSize *hdr);
+void message_get_body_size(const char *msg, size_t size, MessageSize *body);
+void message_size_add(MessageSize *dest, MessageSize *src);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-mail/rfc822-address.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,215 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "temp-string.h"
+#include "rfc822-tokenize.h"
+#include "rfc822-address.h"
+
+static Rfc822Address *new_address(Pool pool, Rfc822Address ***next_addr)
+{
+	Rfc822Address *addr;
+
+	addr = p_new(pool, Rfc822Address, 1);
+
+	**next_addr = addr;
+	*next_addr = &addr->next;
+
+	return addr;
+}
+
+static int read_until(const Rfc822Token *tokens, const char *stop_tokens,
+		      TempString *comment)
+{
+	int i, pos;
+
+	/* find the stop token */
+	for (i = 0; tokens[i].token != 0; i++) {
+		if (strchr(stop_tokens, tokens[i].token) != NULL)
+			break;
+
+		if (tokens[i].token == '(') {
+			/* save comment */
+			if (comment->len > 0)
+				t_string_append_c(comment, ' ');
+			pos = comment->len;
+
+			t_string_append_n(comment, tokens[i].ptr,
+					  tokens[i].len);
+
+			string_remove_escapes(comment->str + pos);
+			comment->len = strlen(comment->str);
+		}
+	}
+
+	return i;
+}
+
+static void read_until_get(const Rfc822Token **tokens, const char *stop_tokens,
+			   TempString *phrase, TempString *comment)
+{
+	const char *value;
+	int count;
+
+	count = read_until(*tokens, stop_tokens, comment);
+	if (count > 0) {
+		value = rfc822_tokens_get_value(*tokens, count, FALSE);
+		t_string_append(phrase, value);
+
+		*tokens += count;
+	}
+}
+
+Rfc822Address *rfc822_address_parse(Pool pool, const char *str)
+{
+	Rfc822Address *first_addr, **next_addr, *addr;
+	TempString *mailbox, *domain, *route, *name, *comment, *next_phrase;
+	const Rfc822Token *tokens;
+	const char *list, *value;
+	int ingroup, stop, count, spaces;
+
+	if (str == NULL || *str == '\0')
+		return NULL;
+
+	first_addr = NULL;
+	next_addr = &first_addr;
+
+	/* 1) name <@route:mailbox@domain>, ...
+	   2) mailbox@domain (name), ...
+	   3) group: name <box@domain>, box2@domain2 (name2), ... ;, ...
+
+	   ENVELOPE wants groups to be stored like (NIL, NIL, group, NIL),
+	   ..., (NIL, NIL, NIL, NIL)
+	*/
+	tokens = rfc822_tokenize(str, NULL, NULL, NULL);
+
+	t_push();
+	mailbox = t_string_new(128);
+	domain = t_string_new(128);
+	route = t_string_new(128);
+	name = t_string_new(128);
+	comment = t_string_new(128);
+
+	ingroup = FALSE;
+	list = ",@<:";
+
+	next_phrase = mailbox; stop = FALSE;
+	while (!stop) {
+		count = read_until(tokens, list, comment);
+		if (count > 0) {
+			/* put spaces around tokens if we're parsing name */
+			spaces = tokens[count].token == '<' ||
+				next_phrase == name;
+			if (spaces && next_phrase->len > 0)
+				t_string_append_c(next_phrase, ' ');
+
+			value = rfc822_tokens_get_value(tokens, count, spaces);
+			t_string_append(next_phrase, value);
+			tokens += count;
+		}
+
+		switch (tokens->token) {
+		case 0:
+		case ',':
+		case ';':
+			/* end of address */
+			if (mailbox->len > 0 || domain->len > 0 ||
+			    route->len > 0 || name->len > 0) {
+				addr = new_address(pool, &next_addr);
+				addr->mailbox = p_strdup(pool, mailbox->str);
+				addr->domain = domain->len == 0 ? NULL :
+					p_strdup(pool, domain->str);
+				addr->route = route->len == 0 ? NULL :
+					p_strdup(pool, route->str);
+				addr->name = next_phrase == name ?
+					p_strdup(pool, name->str) :
+					p_strdup(pool, comment->str);
+			}
+
+			if (ingroup && tokens->token == ';') {
+				/* end of group - add end of group marker */
+				ingroup = FALSE;
+				(void)new_address(pool, &next_addr);
+			}
+
+			if (tokens->token == 0) {
+				stop = TRUE;
+				break;
+			}
+
+			list = ingroup ? ",@<;" :  ",@<:";
+
+			t_string_truncate(mailbox, 0);
+			t_string_truncate(domain, 0);
+			t_string_truncate(route, 0);
+			t_string_truncate(name, 0);
+			t_string_truncate(comment, 0);
+
+			tokens++;
+			next_phrase = mailbox;
+			break;
+		case '@':
+			/* domain part comes next */
+			tokens++;
+			next_phrase = domain;
+			list = ingroup ? ",<;" : ",<";
+			break;
+		case '<':
+			/* route-addr */
+			tokens++;
+
+			/* mailbox/domain name so far has actually
+			   been the real name */
+			t_string_append(name, mailbox->str);
+			if (domain->len > 0) {
+                                t_string_append_c(name, '@');
+				t_string_append(name, domain->str);
+			}
+
+			t_string_truncate(mailbox, 0);
+			t_string_truncate(domain, 0);
+
+			read_until_get(&tokens, "@>", mailbox, NULL);
+			if (tokens->token == '@' && mailbox->len == 0) {
+				/* route is given */
+				tokens++;
+				read_until_get(&tokens, ":>", route, NULL);
+				if (tokens->token == ':') {
+					/* mailbox comes next */
+					tokens++;
+					read_until_get(&tokens, "@>",
+						       mailbox, NULL);
+				}
+			}
+
+			if (tokens->token == '@') {
+				tokens++;
+				read_until_get(&tokens, ">", domain, NULL);
+			}
+
+			if (tokens->token == '>')
+				tokens++;
+
+			next_phrase = name;
+			list = ingroup ? ",;" : ",";
+			break;
+		case ':':
+			/* beginning of group */
+			addr = new_address(pool, &next_addr);
+			addr->name = p_strdup(pool, mailbox->str);
+
+			t_string_truncate(mailbox, 0);
+			tokens++;
+
+			ingroup = TRUE;
+			list = ",@<;";
+			break;
+		}
+	}
+
+	if (ingroup)
+		(void)new_address(pool, &next_addr);
+
+	t_pop();
+	return first_addr;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-mail/rfc822-address.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,14 @@
+#ifndef __RFC822_ADDRLIST_H
+#define __RFC822_ADDRLIST_H
+
+typedef struct _Rfc822Address Rfc822Address;
+
+struct _Rfc822Address {
+	Rfc822Address *next;
+
+	char *name, *route, *mailbox, *domain;
+};
+
+Rfc822Address *rfc822_address_parse(Pool pool, const char *str);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-mail/rfc822-date.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,226 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "gmtoff.h"
+#include "rfc822-date.h"
+#include "rfc822-tokenize.h"
+
+#include <ctype.h>
+
+static const char *month_names[] = {
+	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+static const char *weekday_names[] = {
+	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+static int parse_timezone(const char *str, unsigned int len)
+{
+	int offset;
+	char chr;
+
+	if (len == 5 && (*str == '+' || *str == '-')) {
+		/* numeric offset */
+		offset = (str[1]-'0') * 1000 + (str[2]-'0') * 100 +
+			(str[3]-'0') * 10 + (str[4]-'0');
+		return *str == '+' ? offset : -offset;
+	}
+
+	if (len == 1) {
+		/* military zone - handle them the correct way, not as
+		   RFC822 says. RFC2822 though suggests that they'd be
+		   considered as unspecified.. */
+		chr = i_toupper(*str);
+		if (chr < 'J')
+			return (*str-'A'+1) * 60;
+		if (chr == 'J')
+			return 0;
+		if (chr <= 'M')
+			return (*str-'A') * 60;
+		if (chr < 'Z')
+			return ('M'-*str) * 60;
+		return 0;
+	}
+
+	if (len == 2 && i_toupper(str[0]) == 'U' && i_toupper(str[1]) == 'T') {
+		/* UT - Universal Time */
+		return 0;
+	}
+
+	if (len == 3) {
+		/* GMT | [ECMP][DS]T */
+		if (str[2] != 'T')
+			return 0;
+
+		switch (i_toupper(*str)) {
+		case 'E':
+			offset = -5 * 60;
+			break;
+		case 'C':
+			offset = -6 * 60;
+			break;
+		case 'M':
+			offset = -7 * 60;
+			break;
+		case 'P':
+			offset = -8 * 60;
+			break;
+		default:
+			/* GMT and others */
+			return 0;
+		}
+
+		if (i_toupper(str[1]) == 'D')
+			return offset + 60;
+		if (i_toupper(str[1] == 'S'))
+			return offset;
+	}
+
+	return 0;
+}
+
+static const Rfc822Token *next_token(const Rfc822Token **tokens)
+{
+	const Rfc822Token *ret;
+
+	if ((*tokens)->token == 0)
+		return NULL;
+
+	ret = *tokens;
+	(*tokens)++;
+	return ret;
+}
+
+int rfc822_parse_date(const char *str, time_t *time)
+{
+	struct tm tm;
+	const Rfc822Token *tokens, *tok;
+	unsigned int i;
+	int zone_offset;
+
+	if (str == NULL || *str == '\0')
+		return FALSE;
+
+	/* [weekday_name "," ] dd month_name [yy]yy hh:mi[:ss] timezone
+
+	   don't waste time checking it too properly, if there's errors we
+	   either pick them up at mktime() or have an invalid timestamp,
+	   which would happen anyway.
+
+	   we support comments here even while no-one ever uses them */
+
+	tokens = rfc822_tokenize(str, NULL, NULL, NULL);
+
+	memset(&tm, 0, sizeof(tm));
+
+	/* skip the optional weekday */
+	tok = next_token(&tokens);
+	if (tok != NULL && tok->token == 'A' && tok->len == 3) {
+		tok = next_token(&tokens);
+		if (tok == NULL || tok->token != ',')
+			return FALSE;
+
+		tok = next_token(&tokens);
+	}
+
+	/* dd */
+	if (tok == NULL || tok->token != 'A' || tok->len != 2)
+		return FALSE;
+	tm.tm_mday = (tok->ptr[0]-'0') * 10 + tok->ptr[1]-'0';
+
+	/* month name */
+	tok = next_token(&tokens);
+	if (tok == NULL || tok->token != 'A' || tok->len != 3)
+		return FALSE;
+
+	for (i = 0; i < 12; i++) {
+		if (strncasecmp(month_names[i], tok->ptr, 3) == 0) {
+			tm.tm_mon = i;
+			break;
+		}
+	}
+	if (i == 12)
+		return FALSE;
+
+	/* [yy]yy */
+	tok = next_token(&tokens);
+	if (tok == NULL || tok->token != 'A')
+		return FALSE;
+
+	for (i = 0; i < tok->len; i++)
+		tm.tm_year = tm.tm_year * 10 + tok->ptr[i]-'0';
+
+	if (tok->len == 2) {
+		/* two digit year, assume 1970+ */
+		if (tm.tm_year < 70)
+			tm.tm_year += 100;
+	} else if (tok->len != 4) {
+		/* y10k bug here */
+		tm.tm_year -= 1900;
+		return FALSE;
+	}
+
+	/* hh */
+	tok = next_token(&tokens);
+	if (tok == NULL || tok->token != 'A' || tok->len != 2)
+		return FALSE;
+	tm.tm_hour = (tok->ptr[0]-'0') * 10 + tok->ptr[1]-'0';
+
+	/* :mm */
+	tok = next_token(&tokens);
+	if (tok == NULL || tok->token != ':')
+		return FALSE;
+	tok = next_token(&tokens);
+	if (tok == NULL || (tok->token != 'A' && tok->len != 2))
+		return FALSE;
+	tm.tm_min = (tok->ptr[0]-'0') * 10 + tok->ptr[1]-'0';
+
+	/* [:ss] */
+	tok = next_token(&tokens);
+	if (tok != NULL && tok->token == ':') {
+		tok = next_token(&tokens);
+		if (tok == NULL || (tok->token != 'A' && tok->len != 2))
+			return FALSE;
+		tm.tm_sec = (tok->ptr[0]-'0') * 10 + tok->ptr[1]-'0';
+	}
+
+	/* timezone */
+	if (tok == NULL || tok->token != 'A')
+		return FALSE;
+
+	zone_offset = parse_timezone(tok->ptr, tok->len);
+
+	tm.tm_isdst = -1;
+	*time = mktime(&tm);
+	if (*time < 0)
+		return FALSE;
+
+	*time -= zone_offset * 60;
+	return TRUE;
+}
+
+const char *rfc822_to_date(time_t time)
+{
+	struct tm *tm;
+	int offset, negative;
+
+	tm = localtime(&time);
+	offset = gmtoff(tm, time);
+	if (offset >= 0)
+		negative = 0;
+	else {
+		negative = 1;
+		offset = -offset;
+	}
+	offset /= 60;
+
+	return t_strdup_printf("%s, %02d %s %04d %02d:%02d:%02d %c%02d%02d",
+			       weekday_names[tm->tm_wday],
+			       tm->tm_mday,
+			       month_names[tm->tm_mon],
+			       tm->tm_year+1900,
+			       tm->tm_hour, tm->tm_min, tm->tm_sec,
+			       negative ? '-' : '+', offset / 60, offset % 60);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-mail/rfc822-date.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,7 @@
+#ifndef __RFC822_DATE
+#define __RFC822_DATE
+
+int rfc822_parse_date(const char *str, time_t *time);
+const char *rfc822_to_date(time_t time);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-mail/rfc822-tokenize.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,300 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "rfc822-tokenize.h"
+
+#define INITIAL_COUNT 4
+
+#define PARSE_ERROR() \
+	STMT_START { \
+	if (error_func != NULL && \
+	    !error_func(str, (int) (p-str), '\0', user_data)) \
+		return NULL; \
+	} STMT_END
+
+#define PARSE_ERROR_MISSING(c) \
+	STMT_START { \
+	if (error_func != NULL && \
+	    !error_func(str, (int) (p-str), c, user_data)) \
+		return NULL; \
+	} STMT_END
+
+static Rfc822Token *alloc_token(Rfc822Token **tokens, int *pos, int type)
+{
+	Rfc822Token *token;
+
+	if (*pos+1 >= INITIAL_COUNT)
+		*tokens = t_buffer_reget_type(*tokens, Rfc822Token, *pos + 2);
+
+	token = (*tokens) + *pos;
+	(*pos)++;
+
+	token->token = type;
+	token->ptr = NULL;
+	token->len = 0;
+	return token;
+}
+
+const Rfc822Token *rfc822_tokenize(const char *str, int *tokens_count,
+				   Rfc822TokenizeErrorFunc error_func,
+				   void *user_data)
+{
+	Rfc822Token *first_token, *token;
+	const char *p, *last_atom;
+	int level, in_bracket, pos;
+
+	first_token = t_buffer_get_type(Rfc822Token, INITIAL_COUNT);
+	pos = 0;
+
+	token = NULL;
+	last_atom = NULL;
+
+	in_bracket = FALSE;
+	for (p = str; *p != '\0'; p++) {
+		switch (*p) {
+		case ' ':
+		case '\t':
+		case '\r':
+		case '\n':
+			/* skip whitespace */
+			break;
+
+		/* RFC822 specials: */
+		case '@':
+		case ',':
+		case ';':
+		case ':':
+		case '.':
+		/* RFC 2045 specials: */
+		case '/':
+		case '?':
+		case '=':
+			token = alloc_token(&first_token, &pos, *p);
+			break;
+
+		case '(':
+			/* (comment) - nesting is allowed */
+			token = alloc_token(&first_token, &pos, '(');
+			token->ptr = ++p;
+
+			level = 1;
+			for (; *p != '\0'; p++) {
+				if (*p == '\\' && p[1] != '\0')
+					p++;
+				else if (*p == '(')
+					level++;
+				else if (*p == ')') {
+					if (--level == 0)
+						break;
+				}
+			}
+
+			if (level > 0)
+				PARSE_ERROR_MISSING(')');
+
+			token->len = (int) (p - token->ptr);
+			break;
+
+		case '[':
+			/* domain literal - nesting isn't allowed */
+			token = alloc_token(&first_token, &pos, '[');
+			token->ptr = ++p;
+
+			for (; *p != '\0' && *p != ']'; p++) {
+				if (*p == '\\' && p[1] != '\0')
+					p++;
+				else if (*p == '[') {
+					/* nesting not allowed, but
+					   continue anyway */
+					PARSE_ERROR();
+				}
+			}
+			token->len = (int) (p - token->ptr);
+
+			if (*p == '\0')
+				PARSE_ERROR_MISSING(']');
+			break;
+
+		case '"':
+			/* quoted string */
+			token = alloc_token(&first_token, &pos, '"');
+			token->ptr = ++p;
+
+			for (; *p != '\0' && *p != '"'; p++) {
+				if (*p == '\\' && p[1] != '\0')
+					p++;
+			}
+			token->len = (int) (p - token->ptr);
+
+			if (*p == '\0')
+				PARSE_ERROR_MISSING('"');
+			break;
+
+		case '<':
+			if (in_bracket) {
+				/* '<' cannot be nested */
+				PARSE_ERROR();
+				break;
+			}
+
+			token = alloc_token(&first_token, &pos, '<');
+			in_bracket = TRUE;
+			break;
+		case '>':
+			if (!in_bracket) {
+				/* missing '<' */
+                                PARSE_ERROR();
+				break;
+			}
+
+			token = alloc_token(&first_token, &pos, '>');
+			in_bracket = FALSE;
+			break;
+
+		case ')':
+		case ']':
+		case '\\':
+                        PARSE_ERROR();
+			break;
+		default:
+			/* atom */
+			if (last_atom != p-1) {
+				token = alloc_token(&first_token, &pos, 'A');
+				token->ptr = p;
+			}
+
+			token->len++;
+			last_atom = p;
+			break;
+		}
+
+		if (*p == '\0')
+			break;
+	}
+
+	if (in_bracket && error_func != NULL) {
+		if (!error_func(str, (int) (p-str), '>', user_data))
+			return NULL;
+	}
+
+	if (tokens_count != NULL)
+		*tokens_count = pos;
+
+	first_token[pos++].token = 0;
+	t_buffer_alloc(sizeof(Rfc822Token) * pos);
+	return first_token;
+}
+
+const char *rfc822_tokens_get_value(const Rfc822Token *tokens, int count,
+				    int space_separators)
+{
+	char *buf;
+	unsigned int i, len, buf_size;
+
+	if (count <= 0)
+		return "";
+
+	buf_size = 256;
+	buf = t_buffer_get(buf_size);
+
+	len = 0;
+	for (; count > 0; count--, tokens++) {
+		if (tokens->token == '(')
+			continue; /* skip comments */
+
+		/* +4 == ' ' '[' ']' '\0' */
+		if (len + tokens->len+4 >= buf_size) {
+			buf_size = nearest_power(buf_size + tokens->len + 3);
+			buf = t_buffer_reget(buf, buf_size);
+		}
+
+		if (space_separators && len > 0)
+			buf[len++] = ' ';
+
+		switch (tokens->token) {
+		case '"':
+		case '[':
+			if (tokens->token == '[')
+				buf[len++] = '[';
+
+			/* copy the string removing '\' chars */
+			for (i = 0; i < tokens->len; i++) {
+				if (tokens->ptr[i] == '\\' && i+1 < tokens->len)
+					i++;
+
+				buf[len++] = tokens->ptr[i];
+			}
+
+			if (tokens->token == '[')
+				buf[len++] = ']';
+			break;
+		case 'A':
+			memcpy(buf+len, tokens->ptr, tokens->len);
+			len += tokens->len;
+			break;
+		default:
+			i_assert(tokens->token != 0);
+			buf[len++] = (char) tokens->token;
+			break;
+		}
+	}
+
+	buf[len++] = '\0';
+        t_buffer_alloc(len);
+	return buf;
+}
+
+const char *rfc822_tokens_get_value_quoted(const Rfc822Token *tokens,
+					   int count, int space_separators)
+{
+	char *buf;
+	unsigned int len, buf_size;
+
+	if (count <= 0)
+		return "\"\"";
+
+	buf_size = 256;
+	buf = t_buffer_get(buf_size);
+	buf[0] = '"'; len = 1;
+
+	for (; count > 0; count--, tokens++) {
+		if (tokens->token == '(')
+			continue; /* skip comments */
+
+		/* +5 == ' ' '[' ']' '"' '\0' */
+		if (len + tokens->len+5 >= buf_size) {
+			buf_size = nearest_power(buf_size + tokens->len + 3);
+			buf = t_buffer_reget(buf, buf_size);
+		}
+
+		if (space_separators && len > 0)
+			buf[len++] = ' ';
+
+		switch (tokens->token) {
+		case '"':
+		case '[':
+			if (tokens->token == '[')
+				buf[len++] = '[';
+
+			memcpy(buf+len, tokens->ptr, tokens->len);
+			len += tokens->len;
+
+			if (tokens->token == '[')
+				buf[len++] = ']';
+			break;
+		case 'A':
+			memcpy(buf+len, tokens->ptr, tokens->len);
+			len += tokens->len;
+			break;
+		default:
+			i_assert(tokens->token != 0);
+			buf[len++] = (char) tokens->token;
+			break;
+		}
+	}
+
+	buf[len++] = '"';
+	buf[len++] = '\0';
+        t_buffer_alloc(len);
+	return buf;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-mail/rfc822-tokenize.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,58 @@
+#ifndef __RFC822_TOKENIZE_H
+#define __RFC822_TOKENIZE_H
+
+typedef struct _Rfc822Token Rfc822Token;
+
+#define IS_TOKEN_STRING(token) \
+	((token) == 'A' || (token) == '"' || (token) == '(' || (token) == '['))
+
+#define IS_LWSP(c) \
+	((c) == ' ' || (c) == '\t')
+
+struct _Rfc822Token {
+	/*
+	   0   = last token
+	   'A' = atom
+	   '"' = quoted string
+	   '(' = comment
+	   '[' = domain literal
+
+	   RFC822 specials:
+
+	   '<', '>', '@', ',', ';', ':', '\', '.'
+
+	   RFC2045 tspecials:
+
+	   '/', '?', '='
+	*/
+	int token;
+
+        /* - not including enclosing "", () or []
+	   - '\' isn't expanded
+	   - [CR+]LF+LWSP (continued header) isn't removed */
+	const char *ptr;
+	unsigned int len;
+};
+
+/* Parsing is aborted if returns FALSE. There's two kinds of errors:
+
+   missing_char == '\0': unexpected character at str[pos]
+   missing_char != '\0': missing character */
+typedef int (*Rfc822TokenizeErrorFunc)(const char *str, int pos,
+				       char missing_char, void *user_data);
+
+/* Tokenize the string. Returns NULL if string is empty. Memory for
+   returned array is allocated from temporary pool. You don't have to use
+   the tokens_count, since last token is always 0. */
+const Rfc822Token *rfc822_tokenize(const char *str, int *tokens_count,
+				   Rfc822TokenizeErrorFunc error_func,
+				   void *user_data);
+
+/* Returns the tokens as a string. */
+const char *rfc822_tokens_get_value(const Rfc822Token *tokens, int count,
+				    int space_separators);
+/* Returns the tokens as a "string". */
+const char *rfc822_tokens_get_value_quoted(const Rfc822Token *tokens,
+					   int count, int space_separators);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/.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-storage/Makefile.am	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,24 @@
+SUBDIRS = index subscription-file flags-file
+
+noinst_LIBRARIES = libstorage.a
+
+INCLUDES = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-imap
+
+real_sources = \
+	mail-search.c \
+	mail-storage.c
+
+libstorage_a_SOURCES = \
+	$(real_sources) \
+	mail-storage-register.c
+
+noinst_HEADERS = \
+	mail-search.h \
+	mail-storage.h
+
+DISTFILES = $(DIST_COMMON) $(real_sources) $(HEADERS) $(TEXINFOS) $(EXTRA_DIST)
+
+distclean-generic:
+	rm -f mail-storage-register.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/flags-file/.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-storage/flags-file/Makefile.am	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,13 @@
+noinst_LIBRARIES = libstorage_flags_file.a
+
+INCLUDES = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-storage \
+	-I$(top_srcdir)/src/lib-imap
+
+libstorage_flags_file_a_SOURCES = \
+	flags-file.c
+
+noinst_HEADERS = \
+	flags-file.h
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/flags-file/flags-file.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,507 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "mmap-util.h"
+#include "imap-util.h"
+#include "flags-file.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#define COUNTER_SIZE 4
+#define HEADER_SIZE (COUNTER_SIZE + 1) /* 0000\n */
+
+struct _FlagsFile {
+	MailStorage *storage;
+	char *path;
+	int fd;
+	int lock_type;
+
+	char sync_counter[COUNTER_SIZE];
+	char *custom_flags[MAIL_CUSTOM_FLAGS_COUNT];
+	int custom_flags_refcount;
+
+	void *mmap_base;
+	size_t mmap_length;
+
+	unsigned int syncing:1;
+};
+
+static int lock_file(FlagsFile *ff, int type);
+
+static int update_mmap(FlagsFile *ff)
+{
+	ff->mmap_base = mmap_rw_file(ff->fd, &ff->mmap_length);
+	if (ff->mmap_base == MAP_FAILED) {
+		ff->mmap_base = NULL;
+		mail_storage_set_critical(ff->storage, "mmap() failed for "
+					  "flags file %s: %m", ff->path);
+		return FALSE;
+	}
+
+	(void)madvise(ff->mmap_base, ff->mmap_length, MADV_SEQUENTIAL);
+	return TRUE;
+}
+
+static int flags_file_init(FlagsFile *ff)
+{
+	static char buf[HEADER_SIZE] = "0000\n";
+	off_t pos;
+
+	if (!lock_file(ff, F_WRLCK))
+		return FALSE;
+
+	/* make sure it's still empty after locking */
+	pos = lseek(ff->fd, 0, SEEK_END);
+	if (pos != (off_t)-1 && pos < HEADER_SIZE)
+		pos = lseek(ff->fd, 0, SEEK_SET);
+
+	if (pos == (off_t)-1) {
+		mail_storage_set_critical(ff->storage, "lseek() failed for "
+					  "flags file %s: %m", ff->path);
+		return FALSE;
+	}
+
+	/* write the header - it's a 4 byte counter as hex */
+	if (write(ff->fd, buf, HEADER_SIZE) != HEADER_SIZE) {
+		mail_storage_set_critical(ff->storage, "write() failed for "
+					  "flags file %s: %m", ff->path);
+		return FALSE;
+	}
+
+	if (!lock_file(ff, F_UNLCK))
+		return FALSE;
+
+	return TRUE;
+}
+
+static void flags_file_sync(FlagsFile *ff)
+{
+	char *data, *data_end, *line;
+	int i, num;
+
+	memcpy(ff->sync_counter, ff->mmap_base, COUNTER_SIZE);
+
+	for (i = 0; i < MAIL_CUSTOM_FLAGS_COUNT; i++) {
+		if (ff->custom_flags[i] != NULL) {
+			i_free(ff->custom_flags[i]);
+                        ff->custom_flags[i] = NULL;
+		}
+	}
+
+	data = ff->mmap_base;
+	data_end = data + ff->mmap_length;
+
+	/* this loop skips the first line, which is the header */
+	while (data != data_end) {
+		if (*data != '\n') {
+			data++;
+			continue;
+		}
+
+		/* beginning of line, get the index */
+		if (data+1 == data_end)
+			break;
+		data++;
+
+		if (!i_isdigit(*data))
+			continue;
+
+		num = 0;
+		while (data != data_end && i_isdigit(*data)) {
+			num = num*10 + *data-'0';
+			data++;
+		}
+
+		if (num >= 0 && num < MAIL_CUSTOM_FLAGS_COUNT) {
+			/* get the name */
+			if (data == data_end || *data != ' ')
+				continue;
+
+			line = ++data;
+			while (data != data_end && *data != '\n')
+				data++;
+
+			ff->custom_flags[num] =
+				i_strndup(line, (unsigned int) (data - line));
+		}
+	}
+}
+
+static int flags_file_check_sync(FlagsFile *ff)
+{
+	if (ff->custom_flags_refcount > 0) {
+		/* we've been locked from updates for now.. */
+		return TRUE;
+	}
+
+	if (ff->mmap_length != 0 &&
+	    memcmp(ff->sync_counter, ff->mmap_base, COUNTER_SIZE) == 0)
+		return TRUE;
+
+	/* file modified, resync */
+	if (!update_mmap(ff))
+		return FALSE;
+
+	if (ff->mmap_length < HEADER_SIZE) {
+		/* it's broken, rewrite header */
+		if (ff->lock_type == F_RDLCK)
+			(void)lock_file(ff, F_UNLCK);
+
+		if (!flags_file_init(ff))
+			return FALSE;
+
+		if (!update_mmap(ff))
+			return FALSE;
+	}
+
+	flags_file_sync(ff);
+	return TRUE;
+}
+
+static int lock_file(FlagsFile *ff, int type)
+{
+	struct flock fl;
+
+	if (ff->lock_type == type)
+		return TRUE;
+
+	/* lock whole file */
+	fl.l_type = type;
+	fl.l_whence = SEEK_SET;
+	fl.l_start = 0;
+	fl.l_len = 0;
+
+	while (fcntl(ff->fd, F_SETLKW, &fl) == -1) {
+		if (errno != EINTR) {
+			mail_storage_set_critical(ff->storage, "fcntl() failed "
+						  "for flags file %s: %m",
+						  ff->path);
+			return FALSE;
+		}
+	}
+
+	ff->lock_type = type;
+
+	if (type != F_UNLCK && !ff->syncing) {
+		ff->syncing = TRUE;
+		if (!flags_file_check_sync(ff)) {
+			ff->syncing = FALSE;
+			return FALSE;
+		}
+
+		/* syncing may have changed locking, do it again */
+		if (!lock_file(ff, type)) {
+			ff->syncing = FALSE;
+			return FALSE;
+		}
+
+		ff->syncing = FALSE;
+	}
+	return TRUE;
+}
+
+FlagsFile *flags_file_open_or_create(MailStorage *storage, const char *path)
+{
+	FlagsFile *ff;
+	int fd;
+
+	fd = open(path, O_RDWR | O_CREAT, 0660);
+	if (fd == -1) {
+		mail_storage_set_critical(storage, "Can't open flags file "
+					  "%s: %m", path);
+		return NULL;
+	}
+
+	ff = i_new(FlagsFile, 1);
+	ff->storage = storage;
+	ff->path = i_strdup(path);
+	ff->fd = fd;
+
+	if (!update_mmap(ff)) {
+		flags_file_destroy(ff);
+		return NULL;
+	}
+
+	if (ff->mmap_length < HEADER_SIZE) {
+		/* we just created it, write the header */
+		ff->syncing = TRUE;
+		if (!flags_file_init(ff) || !update_mmap(ff)) {
+			flags_file_destroy(ff);
+			return NULL;
+		}
+		ff->syncing = FALSE;
+	}
+
+        flags_file_sync(ff);
+	return ff;
+}
+
+void flags_file_destroy(FlagsFile *ff)
+{
+	int i;
+
+	for (i = 0; i < MAIL_CUSTOM_FLAGS_COUNT; i++)
+		i_free(ff->custom_flags[i]);
+
+	(void)munmap(ff->mmap_base, ff->mmap_length);
+	(void)close(ff->fd);
+
+	i_free(ff->path);
+	i_free(ff);
+}
+
+static int flags_file_update_counter(FlagsFile *ff)
+{
+	int i;
+
+	if (lseek(ff->fd, 0, SEEK_SET) == (off_t)-1) {
+		mail_storage_set_critical(ff->storage, "lseek() failed for "
+					  "flags file %s: %m", ff->path);
+		return FALSE;
+	}
+
+	for (i = COUNTER_SIZE-1; i >= 0; i--) {
+		if (ff->sync_counter[i] == '9') {
+			ff->sync_counter[i] = 'A';
+			break;
+		}
+
+		if (ff->sync_counter[i] == 'F') {
+			/* digit wrapped, update next one */
+			ff->sync_counter[i] = '0';
+		} else {
+			ff->sync_counter[i]++;
+			break;
+		}
+	}
+
+	if (write(ff->fd, ff->sync_counter, COUNTER_SIZE) != COUNTER_SIZE) {
+		mail_storage_set_critical(ff->storage, "write() failed for "
+					  "flags file %s: %m", ff->path);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static int flags_file_add(FlagsFile *ff, int idx, const char *name)
+{
+	const char *buf;
+	unsigned int len;
+	off_t pos;
+
+	i_assert(idx < MAIL_CUSTOM_FLAGS_COUNT);
+
+	/* first update the sync counter */
+	if (!flags_file_update_counter(ff))
+		return FALSE;
+
+	/* add the flag */
+	pos = lseek(ff->fd, 0, SEEK_END);
+	if (pos == (off_t)-1) {
+		mail_storage_set_critical(ff->storage, "lseek() failed for "
+					  "flags file %s: %m", ff->path);
+		return FALSE;
+	}
+
+	if ((size_t) pos != ff->mmap_length) {
+		mail_storage_set_critical(ff->storage, "flags file %s was "
+					  "changed by someone while we were"
+					  "trying to modify it", ff->path);
+		return FALSE;
+	}
+
+	buf = t_strdup_printf("\n%d %s\n", idx, name);
+	len = strlen(buf);
+
+	if (((char *) ff->mmap_base)[ff->mmap_length-1] == '\n') {
+		/* don't add the \n prefix */
+		buf++;
+		len--;
+	}
+
+	if ((size_t) write(ff->fd, buf, len) != len) {
+		mail_storage_set_critical(ff->storage, "write() failed for "
+					  "flags file %s: %m", ff->path);
+		return FALSE;
+	}
+
+	if (!update_mmap(ff))
+		return FALSE;
+
+	return TRUE;
+}
+
+static int flags_file_remove(FlagsFile *ff, int idx)
+{
+	char *data, *data_end, *line;
+	int num, pos, linelen;
+
+	data = ff->mmap_base;
+	data_end = data + ff->mmap_length;
+
+	while (data != data_end) {
+		if (*data != '\n') {
+			data++;
+			continue;
+		}
+
+		/* beginning of line, get the index */
+		if (data+1 == data_end)
+			break;
+		line = ++data;
+
+		num = 0;
+		while (data != data_end && i_isdigit(*data)) {
+			num = num*10 + *data-'0';
+			data++;
+		}
+
+		if (num == idx) {
+			/* remove this line */
+			while (data != data_end && data[-1] != '\n')
+				data++;
+
+			linelen = (int) (data - line);
+			pos = (int) (data - (char *) ff->mmap_base);
+			memmove(line, data, ff->mmap_length - pos);
+
+			ff->mmap_length -= linelen;
+			if (ftruncate(ff->fd, (off_t) ff->mmap_length) == -1) {
+				mail_storage_set_critical(ff->storage,
+					"ftruncate() failed for flags file "
+					"%s: %m", ff->path);
+				return FALSE;
+			}
+
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+static int find_first_unused_flag(FlagsFile *ff)
+{
+	int i;
+
+	for (i = 0; i < MAIL_CUSTOM_FLAGS_COUNT; i++) {
+		if (ff->custom_flags[i] == NULL)
+			return i;
+	}
+
+	return -1;
+}
+
+static void remove_unused_custom_flags(FlagsFile *ff, MailFlags used_flags)
+{
+	int i;
+
+	for (i = 0; i < MAIL_CUSTOM_FLAGS_COUNT; i++) {
+		if ((used_flags & (1 << (i + MAIL_CUSTOM_FLAG_1_BIT))) == 0) {
+			i_free(ff->custom_flags[i]);
+			ff->custom_flags[i] = NULL;
+
+			flags_file_remove(ff, i);
+		}
+	}
+}
+
+static int get_flag_index(FlagsFile *ff, const char *flag,
+			  MailFlags (*get_used_flags)(void *user_data),
+			  void *user_data)
+{
+	int i, first_empty;
+
+	/* check existing flags */
+	for (i = 0; i < MAIL_CUSTOM_FLAGS_COUNT; i++) {
+		if (ff->custom_flags[i] == NULL)
+			continue;
+
+		i_assert(ff->custom_flags[i] != '\0');
+		if (strcasecmp(ff->custom_flags[i], flag) == 0)
+			return i;
+	}
+
+	/* unlock + write lock, don't directly change from read -> write lock
+	   to prevent deadlocking */
+	if (!lock_file(ff, F_UNLCK) || !lock_file(ff, F_WRLCK))
+		return -1;
+
+	/* new flag, add it. first find the first free flag, note that
+	   unlock+lock might have just changed it. */
+	first_empty = find_first_unused_flag(ff);
+	if (first_empty == -1) {
+		/* all custom flags are used, see if some of them are unused */
+		remove_unused_custom_flags(ff, get_used_flags(user_data));
+
+		first_empty = find_first_unused_flag(ff);
+		if (first_empty == -1) {
+			/* everything is in use */
+			return -1;
+		}
+	}
+
+	if (!flags_file_add(ff, first_empty, flag))
+		return -1;
+
+	ff->custom_flags[first_empty] = i_strdup(flag);
+	return first_empty;
+}
+
+int flags_file_fix_custom_flags(FlagsFile *ff, MailFlags *flags,
+				const char *custom_flags[],
+				MailFlags (*get_used_flags)(void *user_data),
+				void *user_data)
+{
+	MailFlags oldflags, flag;
+	int i, idx;
+
+	if ((*flags & MAIL_CUSTOM_FLAGS_MASK) == 0)
+		return TRUE;
+
+	if (!lock_file(ff, F_RDLCK))
+		return FALSE;
+
+	oldflags = *flags;
+	*flags &= MAIL_SYSTEM_FLAGS_MASK;
+
+	flag = MAIL_CUSTOM_FLAG_1;
+	for (i = 0; i < MAIL_CUSTOM_FLAGS_COUNT; i++, flag <<= 1) {
+		if (oldflags & flag) {
+			i_assert(custom_flags[i] != NULL &&
+				 *custom_flags[i] != '\0');
+
+			idx = get_flag_index(ff, custom_flags[i],
+					     get_used_flags, user_data);
+			if (idx == -1) {
+				mail_storage_set_error(ff->storage,
+					"Maximum number of different custom "
+					"flags exceeded");
+				(void)lock_file(ff, F_UNLCK);
+				return FALSE;
+			}
+			*flags |= 1 << (idx + MAIL_CUSTOM_FLAG_1_BIT);
+		}
+	}
+
+	if (!lock_file(ff, F_UNLCK))
+		return FALSE;
+
+	return TRUE;
+}
+
+const char **flags_file_list_get(FlagsFile *ff)
+{
+	ff->custom_flags_refcount++;
+	return (const char **) ff->custom_flags;
+}
+
+void flags_file_list_unref(FlagsFile *ff)
+{
+	i_assert(ff->custom_flags_refcount > 0);
+
+	ff->custom_flags_refcount--;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/flags-file/flags-file.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,27 @@
+#ifndef __FLAGS_FILE_H
+#define __FLAGS_FILE_H
+
+#include "mail-storage.h"
+
+#define FLAGS_FILE_NAME ".customflags"
+
+typedef struct _FlagsFile FlagsFile;
+
+FlagsFile *flags_file_open_or_create(MailStorage *storage, const char *path);
+void flags_file_destroy(FlagsFile *ff);
+
+/* Change custom flags so that they reflect the real flag numbers in
+   the file. get_used_flags is called when all flags are in use to figure
+   out which of them could be removed. */
+int flags_file_fix_custom_flags(FlagsFile *ff, MailFlags *flags,
+				const char *custom_flags[],
+				MailFlags (*get_used_flags)(void *user_data),
+				void *user_data);
+
+/* Returns a pointer to list of flags. */
+const char **flags_file_list_get(FlagsFile *ff);
+
+/* Call this after you've done with the flags list above */
+void flags_file_list_unref(FlagsFile *ff);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/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-storage/index/Makefile.am	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,26 @@
+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 \
+	-I$(top_srcdir)/src/lib-index \
+	-I$(top_srcdir)/src/lib-storage
+
+libstorage_index_a_SOURCES = \
+	index-copy.c \
+	index-expunge.c \
+	index-fetch.c \
+	index-fetch-section.c \
+	index-save.c \
+	index-search.c \
+	index-status.c \
+	index-storage.c \
+	index-sync.c \
+	index-update-flags.c
+
+noinst_HEADERS = \
+	index-fetch.h \
+	index-storage.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/index-copy.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,73 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "iobuffer.h"
+#include "index-storage.h"
+#include "mail-messageset.h"
+
+#include <unistd.h>
+
+typedef struct {
+	Mailbox *dest;
+	const char **custom_flags;
+} CopyData;
+
+static int copy_func(MailIndex *index, MailIndexRecord *rec,
+		     unsigned int seq __attr_unused__, void *user_data)
+{
+	CopyData *cd = user_data;
+	IOBuffer *buf;
+	off_t offset;
+	size_t size;
+	int fd, failed;
+
+	fd = index->open_mail(index, rec, &offset, &size);
+	if (fd == -1)
+		return FALSE;
+
+	/* save it in destination mailbox */
+	buf = io_buffer_create_file(fd, default_pool, 4096);
+	failed = !cd->dest->save(cd->dest, rec->msg_flags,
+				 cd->custom_flags, rec->internal_date,
+				 buf, size);
+
+	(void)close(fd);
+	return !failed;
+}
+
+int index_storage_copy(Mailbox *box, Mailbox *destbox,
+		       const char *messageset, int uidset)
+{
+	IndexMailbox *ibox = (IndexMailbox *) box;
+        CopyData cd;
+	int failed;
+
+	if (destbox->readonly) {
+		mail_storage_set_error(box->storage,
+				       "Destination mailbox is read-only");
+		return FALSE;
+	}
+
+	if (!ibox->index->set_lock(ibox->index, MAIL_LOCK_SHARED))
+		return mail_storage_set_index_error(ibox);
+
+	cd.custom_flags = flags_file_list_get(ibox->flagsfile);
+	cd.dest = destbox;
+
+	if (uidset) {
+		failed = mail_index_uidset_foreach(ibox->index, messageset,
+						   ibox->synced_messages_count,
+						   copy_func, destbox) <= 0;
+	} else {
+		failed = mail_index_messageset_foreach(ibox->index, messageset,
+			ibox->synced_messages_count, copy_func, destbox) <= 0;
+	}
+
+	flags_file_list_unref(ibox->flagsfile);
+
+	if (!ibox->index->set_lock(ibox->index, MAIL_LOCK_UNLOCK) || failed)
+		return mail_storage_set_index_error(ibox);
+
+	return TRUE;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/index-expunge.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,54 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "index-storage.h"
+
+MailIndexRecord *index_expunge_seek_first(IndexMailbox *ibox,
+					  unsigned int *seq)
+{
+	MailIndexHeader *hdr;
+	MailIndexRecord *rec;
+
+	hdr = ibox->index->get_header(ibox->index);
+	if (hdr->deleted_messages_count == 0)
+		return NULL;
+
+	/* find mails with DELETED flag and expunge them */
+	if (hdr->first_deleted_uid_lowwater > 1) {
+		rec = ibox->index->lookup_uid_range(ibox->index,
+			hdr->first_deleted_uid_lowwater, hdr->next_uid-1);
+		if (rec == NULL) {
+			i_warning("index header's deleted_messages_count or "
+				  "first_deleted_uid_lowwater is invalid.");
+                        INDEX_MARK_CORRUPTED(ibox->index);
+			return NULL;
+		} else {
+			*seq = ibox->index->get_sequence(ibox->index, rec);
+		}
+	} else {
+		rec = ibox->index->lookup(ibox->index, 1);
+		*seq = 1;
+	}
+
+	return rec;
+}
+
+int index_storage_expunge(Mailbox *box)
+{
+	IndexMailbox *ibox = (IndexMailbox *) box;
+	int failed;
+
+	if (box->readonly) {
+		mail_storage_set_error(box->storage, "Mailbox is read-only");
+		return FALSE;
+	}
+
+	if (!ibox->index->set_lock(ibox->index, MAIL_LOCK_EXCLUSIVE))
+		return mail_storage_set_index_error(ibox);
+
+	failed = !ibox->expunge_locked(ibox, NULL, NULL);
+
+	if (!ibox->index->set_lock(ibox->index, MAIL_LOCK_UNLOCK) || failed)
+		return mail_storage_set_index_error(ibox);
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/index-fetch-section.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,401 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "temp-string.h"
+#include "iobuffer.h"
+#include "rfc822-tokenize.h"
+#include "imap-message-send.h"
+#include "index-storage.h"
+#include "index-fetch.h"
+
+#include <ctype.h>
+#include <unistd.h>
+
+ImapCacheField index_fetch_body_get_cache(const char *section)
+{
+	if (*section >= '0' && *section <= '9')
+		return IMAP_CACHE_MESSAGE_PART | IMAP_CACHE_MESSAGE_OPEN;
+
+	if (*section == '\0' || strcasecmp(section, "TEXT") == 0) {
+		/* no IMAP_CACHE_MESSAGE_BODY_SIZE, so that we don't
+		   uselessly check it when we want to read partial data */
+		return IMAP_CACHE_MESSAGE_OPEN;
+	}
+
+	if (strncasecmp(section, "HEADER", 6) == 0 ||
+	    strcasecmp(section, "MIME") == 0)
+		return IMAP_CACHE_MESSAGE_HDR_SIZE | IMAP_CACHE_MESSAGE_OPEN;
+
+	/* error */
+	return 0;
+}
+
+/* fetch BODY[] or BODY[TEXT] */
+static int fetch_body(MailIndexRecord *rec, MailFetchBodyData *sect,
+		      FetchData *data, int fetch_header)
+{
+	MessageSize size;
+	const char *msg, *str;
+	int fd;
+
+	if (!imap_msgcache_get_rfc822_partial(data->cache, rec->uid,
+					      sect->skip, sect->max_size,
+					      fetch_header, &size, &msg, &fd)) {
+		i_error("Couldn't get BODY[] for UID %u (index %s)",
+			rec->uid, data->index->filepath);
+		return FALSE;
+	}
+
+	str = t_strdup_printf("{%lu}\r\n", (unsigned long) size.virtual_size);
+	(void)io_buffer_send(data->outbuf, str, strlen(str));
+
+	(void)imap_message_send(data->outbuf, msg, fd, &size,
+				0, sect->max_size);
+	return TRUE;
+}
+
+static char *const *get_fields_array(const char *fields)
+{
+	char **field_list, **field;
+
+	while (*fields == ' ')
+		fields++;
+	if (*fields == '(')
+		fields++;
+
+	field_list = (char **) t_strsplit(fields, " )");
+
+	/* array ends at ")" element */
+	for (field = field_list; *field != NULL; field++) {
+		if (strcasecmp(*field, ")") == 0)
+			*field = NULL;
+	}
+
+	return field_list;
+}
+
+static int header_match(char *const *fields, const char *data, size_t size)
+{
+	const char *field, *data_start, *data_end;
+
+	i_assert(size > 0);
+
+	data_start = data;
+	data_end = data + size;
+
+	for (; *fields != NULL; fields++) {
+		field = *fields;
+		if (*field == '\0')
+			continue;
+
+		for (data = data_start; data != data_end; data++) {
+			/* field has been uppercased long time ago while
+			   parsing FETCH command */
+			if (i_toupper(*data) != *field)
+				break;
+
+			field++;
+			if (*field == '\0') {
+				/* "field : value" is valid */
+				while (data+1 != data_end && IS_LWSP(data[1]))
+					data++;
+
+				if (data+1 != data_end && data[1] == ':')
+					return TRUE;
+				break;
+			}
+		}
+	}
+
+	return FALSE;
+}
+
+static int header_match_not(char *const *fields, const char *data, size_t size)
+{
+	return !header_match(fields, data, size);
+}
+
+static int header_match_mime(char *const *fields __attr_unused__,
+			     const char *data, size_t size)
+{
+	if (size > 8 && strncasecmp(data, "Content-", 8) == 0)
+		return TRUE;
+
+	if (size >= 13 && strncasecmp(data, "Mime-Version:", 13) == 0)
+		return TRUE;
+
+	return FALSE;
+}
+
+/* Store headers into dest, returns number of bytes written. */
+static unsigned int
+fetch_header_fields(const char *msg, size_t size,
+		    char *dest, char *const *fields,
+		    int (*match_func) (char *const *, const char *, size_t))
+{
+	const char *msg_start, *msg_end, *cr;
+	char *dest_start;
+	unsigned int i;
+	int matched;
+
+	dest_start = dest;
+
+	/* parse fields uppercased into array - no error checking */
+	msg_start = msg;
+	msg_end = msg + size;
+
+	cr = NULL; matched = FALSE;
+	for (; msg != msg_end; msg++) {
+		if (*msg == '\r')
+			cr = msg;
+		else if (*msg == '\n') {
+			if (!matched && msg != msg_start &&
+			    !IS_LWSP(*msg_start)) {
+				matched = match_func(fields, msg_start,
+						     (size_t) (msg-msg_start));
+			}
+
+			if (matched) {
+				if (cr == msg-1) {
+					/* contains CR+LF, copy them */
+					i = (unsigned int) (msg-msg_start)+1;
+					memcpy(dest, msg_start, i);
+					dest += i;
+				} else {
+					/* copy line without LF, appending
+					   CR+LF afterwards */
+					i = (unsigned int) (msg-msg_start);
+					memcpy(dest, msg_start, i);
+					dest += i;
+
+					*dest++ = '\r';
+					*dest++ = '\n';
+				}
+
+				/* see if it continues in next line */
+				matched = msg+1 != msg_end && IS_LWSP(msg[1]);
+			}
+
+			msg_start = msg+1;
+		}
+	}
+
+	/* headers should always end with \n\n, so we don't need to
+	   check the last line here */
+
+	return (unsigned int) (dest - dest_start);
+}
+
+/* fetch wanted headers from given data */
+static void fetch_header_from(const char *msg, int fd, MessageSize *size,
+			      const char *section, MailFetchBodyData *sect,
+			      FetchData *data)
+{
+	const char *str;
+	char *dest;
+	unsigned int len;
+
+	/* HEADER, MIME, HEADER.FIELDS (list), HEADER.FIELDS.NOT (list) */
+
+	if (strcasecmp(section, "HEADER") == 0) {
+		/* all headers */
+		str = t_strdup_printf("{%lu}\r\n",
+				      (unsigned long) size->virtual_size);
+		(void)io_buffer_send(data->outbuf, str, strlen(str));
+		(void)imap_message_send(data->outbuf, msg, fd,
+					size, sect->skip, sect->max_size);
+		return;
+	}
+
+	/* partial headers - copy the wanted fields into temporary memory.
+	   Insert missing CRs on the way. */
+	t_push();
+	dest = t_malloc(size->virtual_size);
+
+	if (strncasecmp(section, "HEADER.FIELDS ", 14) == 0) {
+		len = fetch_header_fields(msg, size->physical_size, dest,
+					  get_fields_array(section + 14),
+					  header_match);
+	} else if (strncasecmp(section, "HEADER.FIELDS.NOT ", 18) == 0) {
+		len = fetch_header_fields(msg, size->physical_size, dest,
+					  get_fields_array(section + 18),
+					  header_match_not);
+	} else if (strcasecmp(section, "MIME") == 0) {
+		/* Mime-Version + Content-* fields */
+		len = fetch_header_fields(msg, size->physical_size, dest,
+					  NULL, header_match_mime);
+	} else {
+		/* error */
+		len = 0;
+	}
+
+	i_assert(len <= size->virtual_size);
+
+	if ((off_t) len <= sect->skip)
+		len = 0;
+	else {
+		dest += sect->skip;
+		len -= sect->skip;
+
+		if (sect->max_size > 0 && len > sect->max_size)
+			len = sect->max_size;
+	}
+
+	str = t_strdup_printf("{%u}\r\n", len);
+	io_buffer_send(data->outbuf, str, strlen(str));
+	if (len > 0) io_buffer_send(data->outbuf, dest, len);
+
+	t_pop();
+}
+
+/* fetch BODY[HEADER...] */
+static int fetch_header(MailIndexRecord *rec, MailFetchBodyData *sect,
+			FetchData *data)
+{
+	MessageSize hdr_size;
+	const char *msg;
+	int fd;
+
+	if (!imap_msgcache_get_rfc822(data->cache, rec->uid,
+				      &hdr_size, NULL, &msg, &fd))
+		return FALSE;
+
+	fetch_header_from(msg, fd, &hdr_size, sect->section, sect, data);
+	return TRUE;
+}
+
+/* Find MessagePart for section (eg. 1.3.4) */
+static MessagePart *part_find(MailIndexRecord *rec, MailFetchBodyData *sect,
+			      FetchData *data, const char **section)
+{
+	MessagePart *part;
+	const char *path;
+	int num;
+
+	part = imap_msgcache_get_parts(data->cache, rec->uid);
+
+	path = sect->section;
+	while (*path >= '0' && *path <= '9' && part != NULL) {
+		/* get part number */
+		num = 0;
+		while (*path != '\0' && *path != '.') {
+			if (*path < '0' || *path > '9')
+				return NULL;
+			num = num*10 + *path - '0';
+			path++;
+		}
+
+		if (*path == '.')
+			path++;
+
+		if (part->multipart) {
+			/* find the part */
+			part = part->children;
+			for (; num > 1 && part != NULL; num--)
+				part = part->next;
+		} else {
+			/* only 1 allowed with non-multipart messages */
+			if (num != 1)
+				return NULL;
+		}
+	}
+
+	*section = path;
+	return part;
+}
+
+/* fetch BODY[1.2] or BODY[1.2.TEXT] */
+static int fetch_part_body(MessagePart *part, unsigned int uid,
+			   MailFetchBodyData *sect, FetchData *data)
+{
+	const char *msg, *str;
+	off_t skip_pos;
+	int fd;
+
+	if (!imap_msgcache_get_data(data->cache, uid, &msg, &fd, NULL))
+		return FALSE;
+
+	/* jump to beginning of wanted data */
+	skip_pos = (off_t) (part->pos.physical_pos +
+			    part->header_size.physical_size);
+	msg += skip_pos;
+	if (fd != -1 && lseek(fd, skip_pos, SEEK_CUR) == (off_t)-1)
+		fd = -1;
+
+	str = t_strdup_printf("{%lu}\r\n",
+			      (unsigned long) part->body_size.virtual_size);
+	(void)io_buffer_send(data->outbuf, str, strlen(str));
+
+	/* FIXME: potential performance problem with big messages:
+	   FETCH BODY[1]<100000..1024>, hopefully no clients do this */
+	(void)imap_message_send(data->outbuf, msg, -1, &part->body_size,
+				sect->skip, sect->max_size);
+	return TRUE;
+}
+
+/* fetch BODY[1.2.MIME|HEADER...] */
+static int fetch_part_header(MessagePart *part, unsigned int uid,
+			     const char *section, MailFetchBodyData *sect,
+			     FetchData *data)
+{
+	const char *msg;
+
+	if (!imap_msgcache_get_data(data->cache, uid, &msg, NULL, NULL))
+		return FALSE;
+
+	fetch_header_from(msg + part->pos.physical_pos, -1,
+			  &part->header_size, section, sect, data);
+	return TRUE;
+}
+
+static int fetch_part(MailIndexRecord *rec, MailFetchBodyData *sect,
+		      FetchData *data)
+{
+	MessagePart *part;
+	const char *section;
+
+	part = part_find(rec, sect, data, &section);
+	if (part == NULL)
+		return FALSE;
+
+	if (*section == '\0' || strcasecmp(section, "TEXT") == 0)
+		return fetch_part_body(part, rec->uid, sect, data);
+
+	if (strncasecmp(section, "HEADER", 6) == 0)
+		return fetch_part_header(part, rec->uid, section, sect, data);
+	if (strcasecmp(section, "MIME") == 0)
+		return fetch_part_header(part, rec->uid, section, sect, data);
+
+	return FALSE;
+}
+
+void index_fetch_body_section(MailIndexRecord *rec,
+			      unsigned int seq __attr_unused__,
+			      MailFetchBodyData *sect, FetchData *data)
+{
+	const char *str;
+	int fetch_ok;
+
+	str = !sect->skip_set ?
+		t_strdup_printf(" BODY[%s] ", sect->section) :
+		t_strdup_printf(" BODY[%s]<%lu> ", sect->section,
+				(unsigned long) sect->skip);
+	(void)io_buffer_send(data->outbuf, str, strlen(str));
+
+	if (*sect->section == '\0') {
+		fetch_ok = fetch_body(rec, sect, data, TRUE);
+	} else if (strcasecmp(sect->section, "TEXT") == 0) {
+		fetch_ok = fetch_body(rec, sect, data, FALSE);
+	} else if (strncasecmp(sect->section, "HEADER", 6) == 0) {
+		fetch_ok = fetch_header(rec, sect, data);
+	} else if (*sect->section >= '0' && *sect->section <= '9') {
+		fetch_ok = fetch_part(rec, sect, data);
+	} else {
+		fetch_ok = FALSE;
+	}
+
+	if (!fetch_ok) {
+		/* error */
+		(void)io_buffer_send(data->outbuf, "{0}\r\n", 5);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/index-fetch.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,365 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "iobuffer.h"
+#include "temp-string.h"
+#include "rfc822-date.h"
+#include "index-storage.h"
+#include "index-fetch.h"
+#include "mail-messageset.h"
+#include "imap-util.h"
+#include "imap-message-cache.h"
+#include "imap-message-send.h"
+
+#include <unistd.h>
+
+static void index_fetch_body(MailIndexRecord *rec, FetchData *data)
+{
+	const char *body;
+
+	body = imap_msgcache_get(data->cache, rec->uid, IMAP_CACHE_BODY);
+	if (body != NULL)
+		t_string_printfa(data->str, " BODY %s", body);
+	else {
+		i_error("Couldn't generate BODY for UID %u (index %s)",
+			rec->uid, data->index->filepath);
+	}
+}
+
+static void index_fetch_bodystructure(MailIndexRecord *rec, FetchData *data)
+{
+	const char *bodystructure;
+
+	bodystructure = imap_msgcache_get(data->cache, rec->uid,
+					  IMAP_CACHE_BODYSTRUCTURE);
+	if (bodystructure != NULL) {
+		t_string_printfa(data->str, " BODYSTRUCTURE %s",
+				 bodystructure);
+	} else {
+		i_error("Couldn't generate BODYSTRUCTURE for UID %u (index %s)",
+			rec->uid, data->index->filepath);
+	}
+}
+
+static void index_fetch_envelope(MailIndexRecord *rec, FetchData *data)
+{
+	const char *envelope;
+
+	envelope = imap_msgcache_get(data->cache, rec->uid,
+				     IMAP_CACHE_ENVELOPE);
+	if (envelope != NULL)
+		t_string_printfa(data->str, " ENVELOPE (%s)", envelope);
+	else {
+		i_error("Couldn't generate ENVELOPE for UID %u (index %s)",
+			rec->uid, data->index->filepath);
+	}
+}
+
+static void index_fetch_rfc822_size(MailIndexRecord *rec, FetchData *data)
+{
+	t_string_printfa(data->str, " RFC822.SIZE %lu",
+			 (unsigned long) rec->full_virtual_size);
+}
+
+static void index_fetch_flags(MailIndexRecord *rec, FetchData *data)
+{
+	MailFlags flags;
+
+	flags = rec->msg_flags;
+	if (rec->uid >= data->index->first_recent_uid)
+		flags |= MAIL_RECENT;
+	if (data->update_seen)
+		flags |= MAIL_SEEN;
+
+	t_string_printfa(data->str, " FLAGS (%s)",
+			 imap_write_flags(flags, data->custom_flags));
+}
+
+static void index_fetch_internaldate(MailIndexRecord *rec, FetchData *data)
+{
+	t_string_printfa(data->str, " INTERNALDATE \"%s\"",
+                         rfc822_to_date(rec->internal_date));
+}
+
+static void index_fetch_uid(MailIndexRecord *rec, FetchData *data)
+{
+	t_string_printfa(data->str, " UID %u", rec->uid);
+}
+
+static void index_fetch_rfc822(MailIndexRecord *rec, FetchData *data)
+{
+	MessageSize hdr_size, body_size;
+	const char *msg, *str;
+	int fd;
+
+	if (!imap_msgcache_get_rfc822(data->cache, rec->uid,
+				      &hdr_size, &body_size, &msg, &fd)) {
+		i_error("Couldn't get RFC822 for UID %u (index %s)",
+			rec->uid, data->index->filepath);
+		return;
+	}
+
+	str = t_strdup_printf(" RFC822 {%lu}\r\n",
+			      (unsigned long) (hdr_size.virtual_size +
+					       body_size.virtual_size));
+	(void)io_buffer_send(data->outbuf, str, strlen(str));
+
+	body_size.physical_size += hdr_size.physical_size;
+	body_size.virtual_size += hdr_size.virtual_size;
+	(void)imap_message_send(data->outbuf, msg, fd, &body_size, 0, 0);
+}
+
+static void index_fetch_rfc822_header(MailIndexRecord *rec, FetchData *data)
+{
+	MessageSize hdr_size;
+	const char *msg, *str;
+	int fd;
+
+	if (!imap_msgcache_get_rfc822(data->cache, rec->uid,
+				      &hdr_size, NULL, &msg, &fd)) {
+		i_error("Couldn't get RFC822.HEADER for UID %u (index %s)",
+			rec->uid, data->index->filepath);
+		return;
+	}
+
+	str = t_strdup_printf(" RFC822.HEADER {%lu}\r\n",
+			      (unsigned long) hdr_size.virtual_size);
+	(void)io_buffer_send(data->outbuf, str, strlen(str));
+	(void)imap_message_send(data->outbuf, msg, fd, &hdr_size, 0, 0);
+}
+
+static void index_fetch_rfc822_text(MailIndexRecord *rec, FetchData *data)
+{
+	MessageSize body_size;
+	const char *msg, *str;
+	int fd;
+
+	if (!imap_msgcache_get_rfc822(data->cache, rec->uid,
+				      NULL, &body_size, &msg, &fd)) {
+		i_error("Couldn't get RFC822.TEXT for UID %u (index %s)",
+			rec->uid, data->index->filepath);
+		return;
+	}
+
+	str = t_strdup_printf(" RFC822.TEXT {%lu}\r\n",
+			      (unsigned long) body_size.virtual_size);
+	(void)io_buffer_send(data->outbuf, str, strlen(str));
+	(void)imap_message_send(data->outbuf, msg, fd, &body_size, 0, 0);
+}
+
+static ImapCacheField index_get_cache(MailFetchData *fetch_data)
+{
+	MailFetchBodyData *sect;
+	ImapCacheField field;
+
+	field = 0;
+	if (fetch_data->body)
+		field |= IMAP_CACHE_BODY;
+	if (fetch_data->bodystructure)
+		field |= IMAP_CACHE_BODYSTRUCTURE;
+	if (fetch_data->envelope)
+		field |= IMAP_CACHE_ENVELOPE;
+
+	if (fetch_data->rfc822_size) {
+		field |= IMAP_CACHE_MESSAGE_HDR_SIZE |
+			IMAP_CACHE_MESSAGE_BODY_SIZE;
+	}
+	if (fetch_data->rfc822) {
+		field |= IMAP_CACHE_MESSAGE_OPEN | IMAP_CACHE_MESSAGE_HDR_SIZE |
+			IMAP_CACHE_MESSAGE_BODY_SIZE;
+	}
+	if (fetch_data->rfc822_header)
+		field |= IMAP_CACHE_MESSAGE_OPEN | IMAP_CACHE_MESSAGE_HDR_SIZE;
+	if (fetch_data->rfc822_text)
+		field |= IMAP_CACHE_MESSAGE_OPEN | IMAP_CACHE_MESSAGE_BODY_SIZE;
+
+	/* check what body[] sections want */
+	sect = fetch_data->body_sections;
+	for (; sect != NULL; sect = sect->next)
+		field |= index_fetch_body_get_cache(sect->section);
+	return field;
+}
+
+static int index_cache_message(MailIndexRecord *rec, FetchData *data,
+			       ImapCacheField field)
+{
+	off_t offset;
+	size_t size;
+	int fd;
+
+	fd = data->index->open_mail(data->index, rec, &offset, &size);
+	if (fd == -1) {
+		i_error("Couldn't open message UID %u (index %s)",
+			rec->uid, data->index->filepath);
+		return FALSE;
+	}
+
+	if (MSG_HAS_VALID_CRLF_DATA(rec)) {
+		imap_msgcache_message(data->cache, rec->uid, fd, offset, size,
+				      rec->full_virtual_size,
+				      rec->header_size, rec->body_size, field);
+	} else {
+		imap_msgcache_message(data->cache, rec->uid, fd, offset, size,
+				      rec->full_virtual_size, 0, 0, field);
+	}
+	return TRUE;
+}
+
+static void index_cache_mail(FetchData *data, MailIndexRecord *rec)
+{
+	ImapCacheField fields;
+	const char *value;
+
+	fields = index_get_cache(data->fetch_data);
+	if (imap_msgcache_is_cached(data->cache, rec->uid, fields))
+		return;
+
+	/* see if we can get some of the values from our index */
+	if (fields & IMAP_CACHE_BODY) {
+		value = data->index->lookup_field(data->index, rec,
+						  FIELD_TYPE_BODY);
+		imap_msgcache_set(data->cache, rec->uid,
+				  IMAP_CACHE_BODY, value);
+	}
+
+	if (fields & IMAP_CACHE_BODYSTRUCTURE) {
+		value = data->index->lookup_field(data->index, rec,
+						  FIELD_TYPE_BODYSTRUCTURE);
+		imap_msgcache_set(data->cache, rec->uid,
+				  IMAP_CACHE_BODYSTRUCTURE, value);
+	}
+
+	if (fields & IMAP_CACHE_ENVELOPE) {
+		value = data->index->lookup_field(data->index, rec,
+						  FIELD_TYPE_ENVELOPE);
+		imap_msgcache_set(data->cache, rec->uid,
+				  IMAP_CACHE_ENVELOPE, value);
+	}
+
+	/* if we still don't have everything, open the message and
+	   cache the needed fields */
+	if (fields != 0 &&
+	    !imap_msgcache_is_cached(data->cache, rec->uid, fields))
+		index_cache_message(rec, data, fields);
+}
+
+static int index_fetch_mail(MailIndex *index __attr_unused__,
+			    MailIndexRecord *rec, unsigned int seq,
+			    void *user_data)
+{
+	FetchData *data = user_data;
+	MailFetchBodyData *sect;
+
+	data->str = t_string_new(2048);
+
+	t_string_printfa(data->str, "* %u FETCH (", seq);
+	(void)io_buffer_send(data->outbuf, data->str->str, data->str->len);
+	t_string_truncate(data->str, 0);
+
+	/* first see what we need to do. this way we don't first do some
+	   light parsing and later notice that we need to do heavier parsing
+	   anyway */
+	index_cache_mail(data, rec);
+
+	if (data->fetch_data->uid)
+		index_fetch_uid(rec, data);
+	if (data->fetch_data->flags)
+		index_fetch_flags(rec, data);
+	if (data->fetch_data->internaldate)
+		index_fetch_internaldate(rec, data);
+
+	if (data->fetch_data->body)
+		index_fetch_body(rec, data);
+	if (data->fetch_data->bodystructure)
+		index_fetch_bodystructure(rec, data);
+	if (data->fetch_data->envelope)
+		index_fetch_envelope(rec, data);
+	if (data->fetch_data->rfc822_size)
+		index_fetch_rfc822_size(rec, data);
+
+	/* send the data written into temp string, skipping the initial space */
+	if (data->str->len > 0) {
+		(void)io_buffer_send(data->outbuf, data->str->str+1,
+				     data->str->len-1);
+	}
+
+	/* large data */
+	if (data->fetch_data->rfc822)
+		index_fetch_rfc822(rec, data);
+	if (data->fetch_data->rfc822_text)
+		index_fetch_rfc822_text(rec, data);
+	if (data->fetch_data->rfc822_header)
+		index_fetch_rfc822_header(rec, data);
+
+	sect = data->fetch_data->body_sections;
+	for (; sect != NULL; sect = sect->next)
+		index_fetch_body_section(rec, seq, sect, data);
+
+	(void)io_buffer_send(data->outbuf, ")\r\n", 3);
+
+	return TRUE;
+}
+
+int index_storage_fetch(Mailbox *box, MailFetchData *fetch_data,
+			IOBuffer *outbuf, int *all_found)
+{
+	IndexMailbox *ibox = (IndexMailbox *) box;
+	FetchData data;
+	MailFetchBodyData *sect;
+	int ret;
+
+	if (!ibox->index->set_lock(ibox->index, MAIL_LOCK_SHARED))
+		return mail_storage_set_index_error(ibox);
+
+	memset(&data, 0, sizeof(data));
+	data.box = box;
+	data.cache = ibox->cache;
+	data.index = ibox->index;
+	data.custom_flags = flags_file_list_get(ibox->flagsfile);
+
+	data.fetch_data = fetch_data;
+	data.outbuf = outbuf;
+
+	/* If we have any BODY[..] sections, \Seen flag is added for
+	   all messages */
+	sect = data.fetch_data->body_sections;
+	for (; sect != NULL; sect = sect->next) {
+		if (!sect->peek) {
+			data.update_seen = TRUE;
+			break;
+		}
+	}
+
+	if (fetch_data->uidset) {
+		ret = mail_index_uidset_foreach(ibox->index,
+						fetch_data->messageset,
+						ibox->synced_messages_count,
+						index_fetch_mail, &data);
+	} else {
+		ret = mail_index_messageset_foreach(ibox->index,
+						    fetch_data->messageset,
+						    ibox->synced_messages_count,
+						    index_fetch_mail, &data);
+	}
+
+        flags_file_list_unref(ibox->flagsfile);
+
+	if (!ibox->index->set_lock(ibox->index, MAIL_LOCK_UNLOCK) || ret == -1)
+		return mail_storage_set_index_error(ibox);
+
+	if (all_found != NULL)
+		*all_found = ret == 1;
+
+	if (ret >= 1 && data.update_seen && !box->readonly) {
+		/* BODY[..] was fetched, set \Seen flag for all messages.
+		   This needs to be done separately because we need exclusive
+		   lock for it */
+		if (!index_storage_update_flags(box, fetch_data->messageset,
+						fetch_data->uidset,
+						MAIL_SEEN, NULL, MODIFY_ADD,
+						NULL, NULL, NULL))
+			return FALSE;
+	}
+
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/index-fetch.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,20 @@
+#ifndef __INDEX_FETCH_H
+#define __INDEX_FETCH_H
+
+typedef struct {
+	Mailbox *box;
+	ImapMessageCache *cache;
+	MailIndex *index;
+	const char **custom_flags;
+
+	MailFetchData *fetch_data;
+	IOBuffer *outbuf;
+	TempString *str;
+	int update_seen;
+} FetchData;
+
+ImapCacheField index_fetch_body_get_cache(const char *section);
+void index_fetch_body_section(MailIndexRecord *rec, unsigned int seq,
+			      MailFetchBodyData *sect, FetchData *data);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/index-save.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,74 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "iobuffer.h"
+#include "index-storage.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+
+static int write_with_crlf(int fd, const unsigned char *data,
+			   unsigned int size, unsigned int *last_cr)
+{
+	int i, cr;
+
+	i_assert(size < INT_MAX);
+
+	cr = *last_cr ? -1 : -2;
+	for (i = 0; i < (int)size; i++) {
+		if (data[i] == '\r')
+			cr = i;
+		else if (data[i] == '\n' && cr != i-1) {
+			/* missing CR */
+			if (write(fd, data, (size_t) i) != i)
+				return FALSE;
+			if (write(fd, "\r", 1) != 1)
+				return FALSE;
+
+			/* skip the data so far. \n is left into buffer and
+			   we'll continue from the next character. */
+			data += i;
+			size -= i;
+			i = 0; cr = -2;
+		}
+	}
+
+	return write(fd, data, size) == i;
+}
+
+int index_storage_save_into_fd(MailStorage *storage, int fd, const char *path,
+			       IOBuffer *buf, size_t data_size)
+{
+	unsigned char *data;
+	unsigned int size, last_cr;
+	int ret;
+
+	last_cr = FALSE;
+
+	while ((ret = io_buffer_read(buf)) != 0) {
+		if (ret == -1) {
+			mail_storage_set_critical(storage,
+						  "Error reading mail: %m");
+			return FALSE;
+		}
+
+		/* -2 = buffer full, ignore it since we're just emptying it.. */
+
+		data = io_buffer_get_data(buf, &size);
+		if (size == 0)
+			continue;
+
+		if (size > data_size)
+			size = data_size;
+		buf->pos += size;
+		data_size -= size;
+
+		if (write_with_crlf(fd, data, size, &last_cr)) {
+			mail_storage_set_critical(storage, "write() failed "
+						  "for file %s: %m", path);
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/index-search.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,635 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "iobuffer.h"
+#include "mmap-util.h"
+#include "rfc822-date.h"
+#include "rfc822-tokenize.h"
+#include "index-storage.h"
+#include "mail-search.h"
+
+#include <stdlib.h>
+#include <ctype.h>
+
+#define ARG_SET_RESULT(arg, res) \
+	STMT_START { \
+		(arg)->result = !(arg)->not ? (res) : -(res); \
+	} STMT_END
+
+typedef struct {
+	IndexMailbox *ibox;
+	MailIndexRecord *rec;
+	unsigned int seq;
+} SearchIndexData;
+
+typedef struct {
+	MailSearchArg *args;
+	int custom_header;
+
+	const char *name, *value;
+	unsigned int name_len, value_len;
+} SearchHeaderData;
+
+typedef struct {
+	MailSearchArg *args;
+	const char *msg;
+	size_t size;
+	int last_block;
+} SearchTextData;
+
+/* truncate timestamp to day */
+static time_t timestamp_trunc(time_t t)
+{
+	struct tm *tm;
+
+	tm = localtime(&t);
+	tm->tm_hour = tm->tm_min = tm->tm_sec = 0;
+
+	return mktime(tm);
+}
+
+static int msgset_contains(const char *set, unsigned int match_num,
+			   unsigned int max_num)
+{
+	unsigned int num, num2;
+
+	while (*set != '\0') {
+		if (*set == '*') {
+			set++;
+			num = max_num;
+		} else {
+			num = 0;
+			while (*set >= '0' && *set <= '9') {
+				num = num*10 + (*set-'0');
+				set++;
+			}
+		}
+
+		if (*set == ',' || *set == '\0') {
+			if (num == match_num)
+				return TRUE;
+			if (*set == '\0')
+				return FALSE;
+		} else if (*set == ':') {
+			if (*set == '*') {
+				set++;
+
+				if (match_num >= num && num <= max_num)
+					return TRUE;
+			} else {
+				num2 = 0;
+				while (*set >= '0' && *set <= '9') {
+					num2 = num2*10 + (*set-'0');
+					set++;
+				}
+
+				if (match_num >= num && match_num <= num2)
+					return TRUE;
+			}
+
+			if (*set != ',')
+				return FALSE;
+		}
+
+		set++;
+	}
+
+	return FALSE;
+}
+
+/* Returns >0 = matched, 0 = not matched, -1 = unknown */
+static int search_arg_match_index(IndexMailbox *ibox, MailIndexRecord *rec,
+				  unsigned int seq, MailSearchArgType type,
+				  const char *value)
+{
+	time_t t;
+
+	switch (type) {
+	case SEARCH_ALL:
+		return TRUE;
+	case SEARCH_SET:
+		return msgset_contains(value, seq, ibox->synced_messages_count);
+	case SEARCH_UID:
+		return msgset_contains(value, rec->uid,
+				       ibox->synced_messages_count);
+
+	/* flags */
+	case SEARCH_ANSWERED:
+		return rec->msg_flags & MAIL_ANSWERED;
+	case SEARCH_DELETED:
+		return rec->msg_flags & MAIL_DELETED;
+	case SEARCH_DRAFT:
+		return rec->msg_flags & MAIL_DRAFT;
+	case SEARCH_FLAGGED:
+		return rec->msg_flags & MAIL_FLAGGED;
+	case SEARCH_SEEN:
+		return rec->msg_flags & MAIL_SEEN;
+	case SEARCH_RECENT:
+		return rec->uid >= ibox->index->first_recent_uid;
+	case SEARCH_KEYWORD:
+		return FALSE;
+
+	/* dates */
+	case SEARCH_BEFORE:
+		if (!rfc822_parse_date(value, &t))
+			return FALSE;
+		return rec->internal_date < timestamp_trunc(t);
+	case SEARCH_ON:
+		if (!rfc822_parse_date(value, &t))
+			return FALSE;
+		t = timestamp_trunc(t);
+		return rec->internal_date >= t &&
+			rec->internal_date < t + 3600*24;
+	case SEARCH_SINCE:
+		if (!rfc822_parse_date(value, &t))
+			return FALSE;
+		return rec->internal_date >= timestamp_trunc(t);
+
+	case SEARCH_SENTBEFORE:
+		if (!rfc822_parse_date(value, &t))
+			return FALSE;
+		return rec->sent_date < timestamp_trunc(t);
+	case SEARCH_SENTON:
+		if (!rfc822_parse_date(value, &t))
+			return FALSE;
+		t = timestamp_trunc(t);
+		return rec->sent_date >= t &&
+			rec->sent_date < t + 3600*24;
+	case SEARCH_SENTSINCE:
+		if (!rfc822_parse_date(value, &t))
+			return FALSE;
+		return rec->sent_date >= timestamp_trunc(t);
+
+	/* sizes */
+	case SEARCH_SMALLER:
+		return rec->full_virtual_size < strtoul(value, NULL, 10);
+	case SEARCH_LARGER:
+		return rec->full_virtual_size > strtoul(value, NULL, 10);
+
+	default:
+		return -1;
+	}
+}
+
+static void search_index_arg(MailSearchArg *arg, void *user_data)
+{
+	SearchIndexData *data = user_data;
+
+	switch (search_arg_match_index(data->ibox, data->rec, data->seq,
+				       arg->type, arg->value.str)) {
+	case -1:
+		/* unknown */
+		break;
+	case 0:
+		ARG_SET_RESULT(arg, -1);
+		break;
+	default:
+		ARG_SET_RESULT(arg, 1);
+		break;
+	}
+}
+
+static int match_field(MailIndex *index, MailIndexRecord *rec,
+		       MailField field, const char *value)
+{
+	const char *field_value;
+	unsigned int i, value_len;
+
+	field_value = index->lookup_field(index, rec, field);
+	if (field_value == NULL)
+		return -1;
+
+	/* note: value is already uppercased */
+	value_len = strlen(value);
+	for (i = 0; field_value[i] != '\0'; i++) {
+		if (value[0] == i_toupper(field_value[i]) &&
+		    strncasecmp(value, field_value+i, value_len) == 0)
+			return 1;
+	}
+
+	return 0;
+}
+
+/* Returns >0 = matched, 0 = not matched, -1 = unknown */
+static int search_arg_match_cached(MailIndex *index, MailIndexRecord *rec,
+				   MailSearchArgType type, const char *value)
+{
+	switch (type) {
+	case SEARCH_FROM:
+		return match_field(index, rec, FIELD_TYPE_FROM, value);
+	case SEARCH_TO:
+		return match_field(index, rec, FIELD_TYPE_TO, value);
+	case SEARCH_CC:
+		return match_field(index, rec, FIELD_TYPE_CC, value);
+	case SEARCH_BCC:
+		return match_field(index, rec, FIELD_TYPE_BCC, value);
+	case SEARCH_SUBJECT:
+		return match_field(index, rec, FIELD_TYPE_SUBJECT, value);
+	default:
+		return -1;
+	}
+}
+
+static void search_cached_arg(MailSearchArg *arg, void *user_data)
+{
+	SearchIndexData *data = user_data;
+
+	switch (search_arg_match_cached(data->ibox->index, data->rec,
+					arg->type, arg->value.str)) {
+	case -1:
+		/* unknown */
+		break;
+	case 0:
+		ARG_SET_RESULT(arg, -1);
+		break;
+	default:
+		ARG_SET_RESULT(arg, 1);
+		break;
+	}
+}
+
+/* needle must be uppercased */
+static int header_value_match(const char *haystack, unsigned int haystack_len,
+			      const char *needle)
+{
+	const char *n;
+	unsigned int i, j, needle_len, max;
+
+	if (*needle == '\0')
+		return TRUE;
+
+	needle_len = strlen(needle);
+	if (haystack_len < needle_len)
+		return FALSE;
+
+	max = haystack_len - needle_len;
+	for (i = 0; i <= max; i++) {
+		if (needle[0] != i_toupper(haystack[i]))
+			continue;
+
+		for (j = i, n = needle; j < haystack_len; j++) {
+			if (haystack[j] == '\r') {
+				if (j+1 != haystack_len)
+					j++;
+			}
+
+			if (haystack[j] == '\n' && j+1 < haystack_len &&
+			    IS_LWSP(haystack[j+1])) {
+				/* long header continuation */
+				j++;
+			}
+
+			if (*n++ != i_toupper(haystack[j]))
+				break;
+
+			if (*n == '\0')
+				return 1;
+		}
+	}
+
+	return -1;
+}
+
+static void search_header_arg(MailSearchArg *arg, void *user_data)
+{
+	SearchHeaderData *data = user_data;
+	const char *value;
+	unsigned int len;
+	int ret;
+
+	/* first check that the field name matches to argument. */
+	switch (arg->type) {
+	case SEARCH_FROM:
+		if (data->name_len != 4 ||
+		    strncasecmp(data->name, "From", 4) != 0)
+			return;
+		value = arg->value.str;
+		break;
+	case SEARCH_TO:
+		if (data->name_len != 2 ||
+		    strncasecmp(data->name, "To", 2) != 0)
+			return;
+		value = arg->value.str;
+		break;
+	case SEARCH_CC:
+		if (data->name_len != 2 ||
+		    strncasecmp(data->name, "Cc", 2) != 0)
+			return;
+		value = arg->value.str;
+		break;
+	case SEARCH_BCC:
+		if (data->name_len != 3 ||
+		    strncasecmp(data->name, "Bcc", 3) != 0)
+			return;
+		value = arg->value.str;
+		break;
+	case SEARCH_SUBJECT:
+		if (data->name_len != 7 ||
+		    strncasecmp(data->name, "Subject", 7) != 0)
+			return;
+		value = arg->value.str;
+		break;
+	case SEARCH_HEADER:
+		data->custom_header = TRUE;
+
+		len = strlen(arg->value.str);
+		if (data->name_len != len ||
+		    strncasecmp(data->name, arg->value.str, len) != 0)
+			return;
+
+		value = arg->hdr_value;
+	default:
+		return;
+	}
+
+	/* then check if the value matches */
+	ret = header_value_match(data->value, data->value_len, value);
+        ARG_SET_RESULT(arg, ret);
+}
+
+static void search_header(MessagePart *part __attr_unused__,
+			  const char *name, unsigned int name_len,
+			  const char *value, unsigned int value_len,
+			  void *user_data)
+{
+	SearchHeaderData *data = user_data;
+
+	if (data->custom_header ||
+	    (name_len == 4 && strncasecmp(name, "From", 4) == 0) ||
+	    (name_len == 2 && strncasecmp(name, "To", 2) == 0) ||
+	    (name_len == 2 && strncasecmp(name, "Cc", 2) == 0) ||
+	    (name_len == 3 && strncasecmp(name, "Bcc", 3) == 0) ||
+	    (name_len == 7 && strncasecmp(name, "Subject", 7) == 0)) {
+		data->name = name;
+		data->value = value;
+		data->name_len = name_len;
+		data->value_len = value_len;
+
+		data->custom_header = FALSE;
+		mail_search_args_foreach(data->args, search_header_arg, data);
+	}
+}
+
+static void search_text(MailSearchArg *arg, SearchTextData *data)
+{
+	const char *p;
+	unsigned int i, len, max;
+
+	if (arg->result != 0)
+		return;
+
+	len = strlen(arg->value.str);
+	max = data->size-len;
+	for (i = 0, p = data->msg; i <= max; i++, p++) {
+		if (i_toupper(*p) == arg->value.str[0] &&
+		    strncasecmp(p, arg->value.str, len) == 0) {
+			/* match */
+			ARG_SET_RESULT(arg, 1);
+			return;
+		}
+	}
+
+	if (data->last_block)
+		ARG_SET_RESULT(arg, -1);
+}
+
+static void search_text_header(MailSearchArg *arg, void *user_data)
+{
+	SearchTextData *data = user_data;
+
+	if (arg->type == SEARCH_TEXT)
+		search_text(arg, data);
+}
+
+static void search_text_body(MailSearchArg *arg, void *user_data)
+{
+	SearchTextData *data = user_data;
+
+	if (arg->type == SEARCH_TEXT || arg->type == SEARCH_BODY)
+		search_text(arg, data);
+}
+
+static int search_arg_match_text(IndexMailbox *ibox, MailIndexRecord *rec,
+				 MailSearchArg *args)
+{
+	const char *msg;
+	void *mmap_base;
+	off_t offset;
+	size_t size, mmap_length;
+	int fd, failed;
+	int have_headers, have_body, have_text;
+
+	/* first check what we need to use */
+	mail_search_args_analyze(args, &have_headers, &have_body, &have_text);
+	if (!have_headers && !have_body && !have_text)
+		return TRUE;
+
+	fd = ibox->index->open_mail(ibox->index, rec, &offset, &size);
+	if (fd == -1)
+		return FALSE;
+
+	mmap_base = mmap_aligned(fd, PROT_READ, offset, size,
+				 (void **) &msg, &mmap_length);
+	if (mmap_base == MAP_FAILED) {
+		failed = TRUE;
+		mail_storage_set_critical(ibox->box.storage, "mmap() failed "
+					  "for msg %u: %m", rec->uid);
+	} else {
+		failed = FALSE;
+		(void)madvise(mmap_base, mmap_length, MADV_SEQUENTIAL);
+
+		if (have_headers) {
+			SearchHeaderData data;
+
+			memset(&data, 0, sizeof(data));
+
+			/* header checks */
+			data.custom_header = TRUE;
+			data.args = args;
+			message_parse_header(NULL, msg, size, NULL,
+					     search_header, &data);
+		}
+
+		if (have_text) {
+			/* first search text from header*/
+			SearchTextData data;
+
+			data.args = args;
+			data.msg = msg;
+			data.size = rec->header_size;
+			data.last_block = FALSE;
+
+			mail_search_args_foreach(args, search_text_header,
+						 &data);
+		}
+
+		if (have_text || have_body) {
+			/* search text from body */
+			SearchTextData data;
+
+			/* FIXME: we should check this in blocks, so the whole
+			   message doesn't need to be in memory */
+			data.args = args;
+			data.msg = msg + rec->header_size;
+			data.size = size - rec->header_size;
+			data.last_block = TRUE;
+
+			mail_search_args_foreach(args, search_text_body, &data);
+		}
+	}
+
+	(void)munmap(mmap_base, mmap_length);
+	(void)close(fd);
+	return !failed;
+}
+
+static void seq_update(const char *set, unsigned int *first_seq,
+		       unsigned int *last_seq, unsigned int max_value)
+{
+	unsigned int seq;
+
+	while (*set != '\0') {
+		if (*set == '*') {
+			seq = max_value;
+			set++;
+		} else {
+			seq = 0;
+			while (*set >= '0' && *set <= '9') {
+				seq = seq*10 + (*set-'0');
+				set++;
+			}
+		}
+
+		if (seq != 0) {
+			if (*first_seq == 0 || seq < *first_seq)
+				*first_seq = seq;
+			if (*last_seq == 0 || seq > *last_seq)
+				*last_seq = seq;
+		}
+
+		seq++;
+	}
+}
+
+static void search_get_sequid(IndexMailbox *ibox, MailSearchArg *args,
+			      unsigned int *first_seq, unsigned int *last_seq,
+			      unsigned int *first_uid, unsigned int *last_uid)
+{
+	for (; args != NULL; args = args->next) {
+		if (args->type == SEARCH_OR || args->type == SEARCH_SUB) {
+			search_get_sequid(ibox, args->value.subargs,
+					  first_seq, last_seq,
+					  first_uid, last_uid);
+		} if (args->type == SEARCH_SET) {
+			seq_update(args->value.str, first_seq, last_seq,
+				   ibox->synced_messages_count);
+		} else if (args->type == SEARCH_UID) {
+			seq_update(args->value.str, first_uid, last_uid,
+				   ibox->index->header->next_uid-1);
+		} else if (args->type == SEARCH_ALL) {
+			/* go through everything */
+			*first_seq = 1;
+			*last_seq = ibox->synced_messages_count;
+			return;
+		}
+	}
+}
+
+static void search_get_sequences(IndexMailbox *ibox, MailSearchArg *args,
+				 unsigned int *first_seq,
+				 unsigned int *last_seq)
+{
+	MailIndexRecord *rec;
+	unsigned int seq, first_uid, last_uid;
+
+	*first_seq = *last_seq = 0;
+	first_uid = last_uid = 0;
+
+	search_get_sequid(ibox, args, first_seq, last_seq,
+			  &first_uid, &last_uid);
+
+	if (first_uid != 0 && (*first_seq != 1 ||
+			       *last_seq != ibox->synced_messages_count)) {
+		/* UIDs were used - see if they affect the sequences */
+		rec = ibox->index->lookup_uid_range(ibox->index,
+						    first_uid, last_uid);
+		if (rec != NULL) {
+			/* update lower UID */
+			seq = ibox->index->get_sequence(ibox->index, rec);
+			if (seq < *first_seq)
+				*first_seq = seq;
+
+			/* update higher UID .. except we don't really
+			   know it and it'd be uselessly slow to find it.
+			   use a kludgy method which might limit the
+			   sequences. */
+			seq += last_uid-first_uid;
+			if (seq >= ibox->synced_messages_count)
+				seq = ibox->synced_messages_count;
+
+			if (seq > *last_seq)
+				*last_seq = seq;
+		}
+	}
+
+	if (*first_seq == 0)
+		*first_seq = 1;
+	if (*last_seq == 0)
+		*last_seq = ibox->synced_messages_count;
+}
+
+static void search_messages(IndexMailbox *ibox, MailSearchArg *args,
+			    IOBuffer *outbuf, int uid_result)
+{
+	SearchIndexData data;
+	MailIndexRecord *rec;
+	unsigned int first_seq, last_seq, seq;
+	char num[MAX_INT_STRLEN+10];
+
+	/* see if we can limit the records we look at */
+	search_get_sequences(ibox, args, &first_seq, &last_seq);
+
+	data.ibox = ibox;
+
+	rec = ibox->index->lookup(ibox->index, first_seq);
+	for (seq = first_seq; rec != NULL && seq <= last_seq; seq++) {
+		data.rec = rec;
+		data.seq = seq;
+
+		mail_search_args_reset(args);
+
+		mail_search_args_foreach(args, search_index_arg, &data);
+		mail_search_args_foreach(args, search_cached_arg, &data);
+
+		if (search_arg_match_text(ibox, rec, args) &&
+		    args->result == 1) {
+			i_snprintf(num, sizeof(num), " %u",
+				   uid_result ? rec->uid : seq);
+			io_buffer_send(outbuf, num, strlen(num));
+		}
+		rec = ibox->index->next(ibox->index, rec);
+	}
+}
+
+int index_storage_search(Mailbox *box, MailSearchArg *args,
+			 IOBuffer *outbuf, int uid_result)
+{
+	IndexMailbox *ibox = (IndexMailbox *) box;
+	int failed;
+
+	if (!ibox->index->set_lock(ibox->index, MAIL_LOCK_SHARED))
+		failed = TRUE;
+	else {
+		io_buffer_send(outbuf, "* SEARCH", 8);
+
+		search_messages(ibox, args, outbuf, uid_result);
+		failed = !ibox->index->set_lock(ibox->index,
+						MAIL_LOCK_UNLOCK);
+		io_buffer_send(outbuf, "\r\n", 2);
+	}
+
+	if (failed)
+		(void)mail_storage_set_index_error(ibox);
+
+	return !failed;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/index-status.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,109 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "index-storage.h"
+
+static unsigned int get_recent_count(MailIndex *index)
+{
+	MailIndexHeader *hdr;
+	MailIndexRecord *rec;
+
+	hdr = mail_index_get_header(index);
+	if (index->first_recent_uid <= 1) {
+		/* all are recent */
+		return hdr->messages_count;
+	}
+
+	/* get the first recent message */
+	rec = index->lookup_uid_range(index, index->first_recent_uid,
+				      hdr->next_uid - 1);
+	if (rec == NULL)
+		return 0;
+
+	/* now we know the record, but we'd still need to know how many
+	   messages there's after this. there's two way to do this -
+	   get the sequence number thus far (fast, unless there's deleted
+	   messages) or just start reading messages forward until we're at
+	   the end (fast assuming there's only a few recent messages).
+	   it's a bit easier to use the first method and often it should be
+	   faster too.. */
+	return hdr->messages_count - index->get_sequence(index, rec) + 1;
+}
+
+static unsigned int get_first_unseen_seq(MailIndex *index)
+{
+	MailIndexHeader *hdr;
+	MailIndexRecord *rec;
+	unsigned int seq, lowwater_uid;
+
+	hdr = mail_index_get_header(index);
+	if (hdr->seen_messages_count == hdr->messages_count) {
+		/* no unseen messages */
+		return 0;
+	}
+
+	lowwater_uid = hdr->first_unseen_uid_lowwater;
+	if (lowwater_uid != 0) {
+		/* begin scanning from the low water mark */
+		rec = index->lookup_uid_range(index, lowwater_uid,
+					      hdr->next_uid - 1);
+		if (rec == NULL) {
+			i_warning("index header's seen_messages_count or "
+				  "first_unseen_uid_lowwater is invalid.");
+                        INDEX_MARK_CORRUPTED(index);
+			return 0;
+		} else {
+			seq = index->get_sequence(index, rec);
+		}
+	} else {
+		/* begin scanning from the beginning */
+		rec = index->lookup(index, 1);
+		seq = 1;
+	}
+
+	while (rec != NULL && (rec->msg_flags & MAIL_SEEN)) {
+		rec = index->next(index, rec);
+		seq++;
+	}
+
+	if (rec != NULL && rec->uid != lowwater_uid) {
+		/* update the low water mark if we can get exclusive
+		   lock immediately. */
+		if (index->try_lock(index, MAIL_LOCK_EXCLUSIVE))
+			hdr->first_unseen_uid_lowwater = rec->uid;
+	}
+
+	return rec == NULL ? 0 : seq;
+}
+
+int index_storage_get_status(Mailbox *box, MailboxStatusItems items,
+			     MailboxStatus *status)
+{
+	IndexMailbox *ibox = (IndexMailbox *) box;
+	MailIndexHeader *hdr;
+
+	if (!ibox->index->set_lock(ibox->index, MAIL_LOCK_SHARED))
+		return mail_storage_set_index_error(ibox);
+
+	/* we can get most of the status items without any trouble */
+	hdr = mail_index_get_header(ibox->index);
+	status->messages = hdr->messages_count;
+	status->unseen = hdr->messages_count - hdr->seen_messages_count;
+	status->uidvalidity = hdr->uid_validity;
+	status->uidnext = hdr->next_uid;
+
+	if (items & STATUS_FIRST_UNSEEN_SEQ) {
+		status->first_unseen_seq =
+			get_first_unseen_seq(ibox->index);
+	}
+
+	if (items & STATUS_RECENT)
+		status->recent = get_recent_count(ibox->index);
+
+	/* STATUS sends EXISTS, so we've synced it */
+	ibox->synced_messages_count = hdr->messages_count;
+
+	if (!ibox->index->set_lock(ibox->index, MAIL_LOCK_UNLOCK))
+		return mail_storage_set_index_error(ibox);
+	return hdr != NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/index-storage.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,97 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "mail-index.h"
+#include "index-storage.h"
+
+IndexMailbox *index_storage_init(MailStorage *storage, Mailbox *box,
+				 MailIndex *index, const char *name,
+				 int readonly)
+{
+	IndexMailbox *ibox;
+	FlagsFile *flagsfile;
+	const char *path, *error;
+
+	i_assert(name != NULL);
+
+	/* open the index first */
+	if (!index->open_or_create(index, !readonly)) {
+		error = index->get_last_error(index);
+		if (error == NULL)
+			error = "(maildir_open)";
+		mail_storage_set_error(storage, "%s", error);
+
+		index->free(index);
+		return NULL;
+	}
+
+	/* then flags file */
+	path = t_strconcat(index->dir, "/", FLAGS_FILE_NAME, NULL);
+	flagsfile = flags_file_open_or_create(storage, path);
+	if (flagsfile == NULL) {
+		index->free(index);
+		return NULL;
+	}
+
+	ibox = i_new(IndexMailbox, 1);
+	ibox->box = *box;
+
+	ibox->box.storage = storage;
+	ibox->box.name = i_strdup(name);
+	ibox->box.readonly = readonly;
+
+	ibox->index = index;
+	ibox->flagsfile = flagsfile;
+	ibox->cache = imap_msgcache_alloc();
+
+	return ibox;
+}
+
+void index_storage_close(Mailbox *box)
+{
+	IndexMailbox *ibox = (IndexMailbox *) box;
+
+	flags_file_destroy(ibox->flagsfile);
+	imap_msgcache_free(ibox->cache);
+	ibox->index->free(ibox->index);
+	i_free(box->name);
+	i_free(box);
+}
+
+int mail_storage_set_index_error(IndexMailbox *ibox)
+{
+	const char *error;
+
+	error = ibox->index->get_last_error(ibox->index);
+	if (error == NULL)
+		error = "(no error message)";
+
+	ibox->box.inconsistent =
+		ibox->index->is_inconsistency_error(ibox->index);
+	mail_storage_set_error(ibox->box.storage, "%s", error);
+	return FALSE;
+}
+
+static MailFlags get_used_flags(void *user_data)
+{
+        IndexMailbox *ibox = user_data;
+	MailIndexRecord *rec;
+	MailFlags used_flags;
+
+	used_flags = 0;
+
+	rec = ibox->index->lookup(ibox->index, 1);
+	while (rec != NULL) {
+		used_flags |= rec->msg_flags;
+		rec = ibox->index->next(ibox->index, rec);
+	}
+
+	return used_flags;
+}
+
+int index_mailbox_fix_custom_flags(IndexMailbox *ibox, MailFlags *flags,
+				   const char *custom_flags[])
+{
+	return flags_file_fix_custom_flags(ibox->flagsfile, flags,
+					   custom_flags, get_used_flags, ibox);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/index-storage.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,61 @@
+#ifndef __INDEX_STORAGE_H
+#define __INDEX_STORAGE_H
+
+#include "mail-storage.h"
+#include "mail-index.h"
+#include "imap-message-cache.h"
+#include "flags-file/flags-file.h"
+
+typedef struct _IndexMailbox IndexMailbox;
+
+struct _IndexMailbox {
+	Mailbox box;
+
+	/* expunge messages marked as deleted, requires index to be
+	   exclusively locked */
+	int (*expunge_locked)(IndexMailbox *ibox,
+			      MailExpungeFunc expunge_func, void *user_data);
+
+	MailIndex *index;
+	FlagsFile *flagsfile;
+	ImapMessageCache *cache;
+	unsigned int synced_messages_count;
+};
+
+IndexMailbox *index_storage_init(MailStorage *storage, Mailbox *box,
+				 MailIndex *index, const char *name,
+				 int readonly);
+void index_storage_close(Mailbox *box);
+
+int mail_storage_set_index_error(IndexMailbox *ibox);
+
+int index_mailbox_fix_custom_flags(IndexMailbox *ibox, MailFlags *flags,
+				   const char *custom_flags[]);
+
+MailIndexRecord *index_expunge_seek_first(IndexMailbox *ibox,
+					  unsigned int *seq);
+
+int index_storage_save_into_fd(MailStorage *storage, int fd, const char *path,
+			       IOBuffer *buf, size_t data_size);
+
+/* Mailbox methods: */
+int index_storage_copy(Mailbox *box, Mailbox *destbox,
+		       const char *messageset, int uidset);
+int index_storage_expunge(Mailbox *box);
+int index_storage_get_status(Mailbox *box, MailboxStatusItems items,
+			     MailboxStatus *status);
+int index_storage_sync(Mailbox *box, unsigned int *messages, int expunge,
+		       MailExpungeFunc expunge_func,
+		       MailFlagUpdateFunc flag_func,
+		       void *user_data);
+int index_storage_update_flags(Mailbox *box, const char *messageset, int uidset,
+			       MailFlags flags, const char *custom_flags[],
+			       ModifyType modify_type,
+			       MailFlagUpdateFunc func, void *user_data,
+			       int *all_found);
+int index_storage_fetch(Mailbox *box, MailFetchData *fetch_data,
+			IOBuffer *outbuf, int *all_found);
+int index_storage_search(Mailbox *box, MailSearchArg *args,
+			 IOBuffer *outbuf, int uid_result);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/index-sync.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,87 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "index-storage.h"
+#include "mail-modifylog.h"
+
+int index_storage_sync(Mailbox *box, unsigned int *messages, int expunge,
+		       MailExpungeFunc expunge_func,
+		       MailFlagUpdateFunc flag_func,
+		       void *user_data)
+{
+	IndexMailbox *ibox = (IndexMailbox *) box;
+	ModifyLogRecord *log;
+	MailIndexRecord *rec;
+	MailFlags flags;
+	const char **custom_flags;
+	unsigned int count;
+	int failed;
+
+	if (expunge && box->readonly) {
+		mail_storage_set_error(box->storage, "Mailbox is read-only");
+		return FALSE;
+	}
+
+	*messages = 0;
+
+	if (!ibox->index->set_lock(ibox->index, expunge ?
+				   MAIL_LOCK_EXCLUSIVE : MAIL_LOCK_SHARED))
+		return mail_storage_set_index_error(ibox);
+
+	/* show the log */
+	log = mail_modifylog_get_nonsynced(ibox->index->modifylog, &count);
+	if (log == NULL)
+		failed = TRUE;
+
+	custom_flags = flags_file_list_get(ibox->flagsfile);
+	for (; count > 0; count--, log++) {
+		switch (log->type) {
+		case RECORD_TYPE_EXPUNGE:
+			if (expunge_func != NULL) {
+				expunge_func(box, log->seq,
+					     log->uid, user_data);
+			}
+			break;
+		case RECORD_TYPE_FLAGS_CHANGED:
+			if (flag_func == NULL)
+				break;
+
+			rec = ibox->index->lookup_uid_range(ibox->index,
+							    log->uid,
+							    log->uid);
+			if (rec != NULL) {
+				flags = rec->msg_flags;
+				if (rec->uid >= ibox->index->first_recent_uid)
+					flags |= MAIL_RECENT;
+
+				flag_func(box, log->seq, log->uid, flags,
+					  custom_flags, user_data);
+			}
+			break;
+		}
+	}
+	flags_file_list_unref(ibox->flagsfile);
+
+	/* mark synced */
+	failed = !mail_modifylog_mark_synced(ibox->index->modifylog);
+
+	if (!failed && expunge) {
+		/* expunge messages */
+		failed = !ibox->expunge_locked(ibox, expunge_func, user_data);
+	}
+
+	/* get the messages count even if there was some failures.
+	   also it must be done after expunging messages */
+	count = ibox->index->get_header(ibox->index)->messages_count;
+	if (count != ibox->synced_messages_count) {
+		if (count > ibox->synced_messages_count) {
+			/* new messages in mailbox */
+			*messages = count;
+		}
+		ibox->synced_messages_count = count;
+	}
+
+	if (!ibox->index->set_lock(ibox->index, MAIL_LOCK_UNLOCK) || failed)
+		return mail_storage_set_index_error(ibox);
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/index-update-flags.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,97 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "index-storage.h"
+#include "mail-messageset.h"
+
+typedef struct {
+	Mailbox *box;
+	MailFlags flags;
+	FlagsFile *flagsfile;
+	ModifyType modify_type;
+	MailFlagUpdateFunc func;
+	void *user_data;
+} UpdateData;
+
+static int update_func(MailIndex *index, MailIndexRecord *rec,
+		       unsigned int seq, void *user_data)
+{
+	UpdateData *data = user_data;
+	MailFlags flags;
+
+	switch (data->modify_type) {
+	case MODIFY_ADD:
+		flags = rec->msg_flags | data->flags;
+		break;
+	case MODIFY_REMOVE:
+		flags = rec->msg_flags & ~data->flags;
+		break;
+	case MODIFY_REPLACE:
+		flags = data->flags;
+		break;
+	default:
+		flags = 0;
+		i_assert(0);
+	}
+
+	if (rec->uid >= index->first_recent_uid)
+		flags |= MAIL_RECENT;
+
+	if (!index->update_flags(index, rec, seq, flags, FALSE))
+		return FALSE;
+
+	if (data->func != NULL) {
+		data->func(data->box, seq, rec->uid, flags,
+			   flags_file_list_get(data->flagsfile),
+			   data->user_data);
+		flags_file_list_unref(data->flagsfile);
+	}
+	return TRUE;
+}
+
+int index_storage_update_flags(Mailbox *box, const char *messageset, int uidset,
+			       MailFlags flags, const char *custom_flags[],
+			       ModifyType modify_type,
+			       MailFlagUpdateFunc func, void *user_data,
+			       int *all_found)
+{
+	IndexMailbox *ibox = (IndexMailbox *) box;
+        UpdateData data;
+	int ret;
+
+	if (box->readonly) {
+		mail_storage_set_error(box->storage, "Mailbox is read-only");
+		return FALSE;
+	}
+
+	if (!index_mailbox_fix_custom_flags(ibox, &flags, custom_flags))
+		return mail_storage_set_index_error((IndexMailbox *) box);
+
+	if (!ibox->index->set_lock(ibox->index, MAIL_LOCK_EXCLUSIVE))
+		return mail_storage_set_index_error(ibox);
+
+	data.box = box;
+	data.flags = flags & ~MAIL_RECENT; /* \Recent can't be changed */
+	data.flagsfile = ibox->flagsfile;
+	data.modify_type = modify_type;
+	data.func = func;
+	data.user_data = user_data;
+
+	if (uidset) {
+		ret = mail_index_uidset_foreach(ibox->index, messageset,
+						ibox->synced_messages_count,
+						update_func, &data);
+	} else {
+		ret = mail_index_messageset_foreach(ibox->index,
+						    messageset,
+						    ibox->synced_messages_count,
+						    update_func, &data);
+	}
+
+	if (!ibox->index->set_lock(ibox->index, MAIL_LOCK_UNLOCK) || ret == -1)
+		return mail_storage_set_index_error(ibox);
+
+	if (all_found != NULL)
+		*all_found = ret == 1;
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/maildir/.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-storage/index/maildir/Makefile.am	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,20 @@
+noinst_LIBRARIES = libstorage_maildir.a
+
+INCLUDES = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-mail \
+	-I$(top_srcdir)/src/lib-imap \
+	-I$(top_srcdir)/src/lib-index \
+	-I$(top_srcdir)/src/lib-index/maildir \
+	-I$(top_srcdir)/src/lib-storage \
+	-I$(top_srcdir)/src/lib-storage/index
+
+libstorage_maildir_a_SOURCES = \
+	maildir-copy.c \
+	maildir-expunge.c \
+	maildir-list.c \
+	maildir-save.c \
+	maildir-storage.c
+
+noinst_HEADERS = \
+	maildir-storage.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/maildir/maildir-copy.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,111 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "mail-messageset.h"
+#include "maildir-storage.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+
+typedef struct {
+	MailStorage *storage;
+	const char *dest_maildir;
+	int error;
+} CopyHardData;
+
+static int copy_hard_func(MailIndex *index, MailIndexRecord *rec,
+			  unsigned int seq __attr_unused__, void *user_data)
+{
+	CopyHardData *data = user_data;
+	const char *fname;
+	char src[1024], dest[1024];
+
+	/* link the file */
+	fname = index->lookup_field(index, rec, FIELD_TYPE_LOCATION);
+	i_snprintf(src, sizeof(src), "%s/cur/%s", index->dir, fname);
+	i_snprintf(dest, sizeof(dest), "%s/new/%s", data->dest_maildir, fname);
+
+	if (link(src, dest) == 0)
+		return TRUE;
+	else {
+		if (errno != EXDEV) {
+			mail_storage_set_critical(data->storage, "link(%s, %s) "
+						  "failed: %m", src, dest);
+			data->error = TRUE;
+		}
+		return FALSE;
+	}
+}
+
+static int maildir_copy_with_hardlinks(IndexMailbox *src,
+				       IndexMailbox *dest,
+				       const char *messageset, int uidset)
+{
+        CopyHardData data;
+	int ret;
+
+	if (!src->index->set_lock(src->index, MAIL_LOCK_SHARED))
+		return mail_storage_set_index_error(src);
+	if (!dest->index->set_lock(dest->index, MAIL_LOCK_EXCLUSIVE)) {
+		(void)src->index->set_lock(src->index, MAIL_LOCK_UNLOCK);
+		return mail_storage_set_index_error(dest);
+	}
+
+	data.storage = src->box.storage;
+	data.dest_maildir = dest->index->dir;
+	data.error = FALSE;
+
+	if (uidset) {
+		ret = mail_index_uidset_foreach(src->index, messageset,
+						src->synced_messages_count,
+						copy_hard_func, &data);
+	} else {
+		ret = mail_index_messageset_foreach(src->index, messageset,
+						    src->synced_messages_count,
+						    copy_hard_func, &data);
+	}
+
+	if (ret == -1)
+		mail_storage_set_index_error(src);
+
+	if (!dest->index->set_lock(dest->index, MAIL_LOCK_UNLOCK)) {
+		mail_storage_set_index_error(dest);
+		ret = -1;
+	}
+
+	if (!src->index->set_lock(src->index, MAIL_LOCK_SHARED)) {
+		mail_storage_set_index_error(src);
+		ret = -1;
+	}
+
+	return data.error ? -1 : ret;
+}
+
+int maildir_storage_copy(Mailbox *box, Mailbox *destbox,
+			 const char *messageset, int uidset)
+{
+	IndexMailbox *ibox = (IndexMailbox *) box;
+
+	if (destbox->readonly) {
+		mail_storage_set_error(box->storage,
+				       "Destination mailbox is read-only");
+		return FALSE;
+	}
+
+	if (getenv("COPY_WITH_HARDLINKS") != NULL &&
+	    destbox->storage == box->storage) {
+		/* both source and destination mailbox are in maildirs and
+		   copy_with_hardlinks option is on, do it */
+		switch (maildir_copy_with_hardlinks(ibox,
+			(IndexMailbox *) destbox, messageset, uidset)) {
+		case -1:
+			return FALSE;
+		case 1:
+			return TRUE;
+		}
+
+		/* non-fatal hardlinking failure, try the slow way */
+	}
+
+	return index_storage_copy(box, destbox, messageset, uidset);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/maildir/maildir-expunge.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,57 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "maildir-storage.h"
+
+#include <unistd.h>
+
+static int expunge_msg(IndexMailbox *ibox, MailIndexRecord *rec,
+		       unsigned int seq)
+{
+	const char *fname;
+	char path[1024];
+
+	/* get our file name - ignore if it's missing,
+	   we're deleting it after all.. */
+	fname = ibox->index->lookup_field(ibox->index, rec,
+					  FIELD_TYPE_LOCATION);
+	if (fname != NULL) {
+		i_snprintf(path, sizeof(path), "%s/cur/%s",
+			   ibox->index->dir, fname);
+		if (unlink(path) == -1 && errno != ENOENT) {
+			mail_storage_set_error(ibox->box.storage,
+					       "unlink() failed for "
+					       "message file %s: %m", path);
+			/* continue anyway */
+		}
+	}
+
+	return ibox->index->expunge(ibox->index, rec, seq, FALSE);
+
+}
+
+int maildir_expunge_locked(IndexMailbox *ibox,
+			   MailExpungeFunc expunge_func, void *user_data)
+{
+	MailIndexRecord *rec;
+	unsigned int seq, uid;
+
+	rec = index_expunge_seek_first(ibox, &seq);
+	while (rec != NULL) {
+		if (rec->msg_flags & MAIL_DELETED) {
+			/* save UID before deletion */
+			uid = rec->uid;
+
+			if (!expunge_msg(ibox, rec, seq))
+				return FALSE;
+
+			if (expunge_func != NULL)
+				expunge_func(&ibox->box, seq, uid, user_data);
+			seq--;
+		}
+		rec = ibox->index->next(ibox->index, rec);
+		seq++;
+	}
+
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/maildir/maildir-list.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,163 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "unlink-directory.h"
+#include "imap-match.h"
+#include "subscription-file/subscription-file.h"
+#include "maildir-index.h"
+#include "maildir-storage.h"
+
+#include <dirent.h>
+#include <sys/stat.h>
+
+typedef struct {
+	MailboxFunc func;
+	void *user_data;
+} FindSubscribedData;
+
+static MailboxFlags maildir_get_marked_flags(const char *dir)
+{
+	struct stat st;
+	char path[1024];
+	time_t index_stamp, cur_stamp;
+
+	i_snprintf(path, sizeof(path), "%s/" INDEX_FILE_PREFIX, dir);
+	if (stat(path, &st) == -1) {
+		/* index file wasn't found. it might be with another name,
+		   but finding it would be too slow. */
+		return 0;
+	}
+
+	index_stamp = st.st_mtime;
+
+	i_snprintf(path, sizeof(path), "%s/cur", dir);
+	if (stat(path, &st) == -1) {
+		/* no cur/ directory - broken */
+		return 0;
+	}
+
+	cur_stamp = st.st_mtime;
+	if (cur_stamp != index_stamp) {
+		/* changes in cur directory */
+		return MAILBOX_MARKED;
+	}
+
+	i_snprintf(path, sizeof(path), "%s/new", dir);
+	if (stat(path, &st) == -1) {
+		/* no new/ directory - broken */
+		return 0;
+	}
+
+	return st.st_mtime <= cur_stamp ? MAILBOX_UNMARKED : MAILBOX_MARKED;
+}
+
+int maildir_find_mailboxes(MailStorage *storage, const char *mask,
+			   MailboxFunc func, void *user_data)
+{
+        const ImapMatchGlob *glob;
+	DIR *dirp;
+	struct dirent *d;
+	struct stat st;
+        MailboxFlags flags;
+	char path[1024];
+	int failed, found_inbox;
+
+	mail_storage_clear_error(storage);
+
+	dirp = opendir(storage->dir);
+	if (dirp == NULL) {
+		mail_storage_set_critical(storage, "opendir(%s) failed: %m",
+					  storage->dir);
+		return FALSE;
+	}
+
+	glob = imap_match_init(mask, TRUE, '.');
+
+	failed = found_inbox = FALSE;
+	while ((d = readdir(dirp)) != NULL) {
+		const char *fname = d->d_name;
+
+		if (fname[0] != '.')
+			continue;
+
+		/* skip . and .. */
+		if (fname[1] == '\0' || (fname[1] == '.' && fname[2] == '\0'))
+			continue;
+
+		/* make sure the mask matches - dirs beginning with ".."
+		   should be deleted and we always want to check those. */
+		if (fname[1] == '.' || imap_match(glob, fname+1, 0, NULL) < 0)
+			continue;
+
+		/* make sure it's a directory */
+		i_snprintf(path, sizeof(path), "%s/%s", storage->dir, fname);
+		if (stat(path, &st) != 0) {
+			if (errno == ENOENT)
+				continue; /* just deleted, ignore */
+
+			mail_storage_set_critical(storage,
+						  "stat(%s) failed: %m", path);
+			failed = TRUE;
+			break;
+		}
+
+		if (!S_ISDIR(st.st_mode))
+			continue;
+
+		if (fname[1] == '.') {
+			/* this mailbox is in the middle of being deleted,
+			   or the process trying to delete it had died.
+
+			   delete it ourself if it's been there longer than
+			   one hour */
+			if (st.st_mtime < 3600)
+				(void)unlink_directory(path);
+			continue;
+		}
+
+                flags = maildir_get_marked_flags(path);
+		func(storage, fname+1, flags, user_data);
+	}
+
+	if (!failed && !found_inbox &&
+	    imap_match(glob, "INBOX", 0, NULL) >= 0) {
+		/* .INBOX directory doesn't exist yet, but INBOX still exists */
+		func(storage, "INBOX", 0, user_data);
+	}
+
+	(void)closedir(dirp);
+	return !failed;
+}
+
+static int maildir_subs_func(MailStorage *storage, const char *name,
+			     void *user_data)
+{
+	FindSubscribedData *data = user_data;
+	MailboxFlags flags;
+	struct stat st;
+	char path[1024];
+
+	i_snprintf(path, sizeof(path), "%s/.%s", storage->dir, name);
+
+	if (stat(path, &st) == 0 && S_ISDIR(st.st_mode))
+		flags = maildir_get_marked_flags(path);
+	else
+		flags = MAILBOX_NOSELECT;
+
+	data->func(storage, name, flags, data->user_data);
+	return TRUE;
+}
+
+int maildir_find_subscribed(MailStorage *storage, const char *mask,
+			    MailboxFunc func, void *user_data)
+{
+	FindSubscribedData data;
+
+	data.func = func;
+	data.user_data = user_data;
+
+	if (subsfile_foreach(storage, mask, maildir_subs_func, &data) <= 0)
+		return FALSE;
+
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/maildir/maildir-save.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,108 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "hostpid.h"
+#include "iobuffer.h"
+#include "maildir-index.h"
+#include "maildir-storage.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <utime.h>
+
+static int maildir_create_tmp(MailStorage *storage, const char *dir,
+			      const char **fname)
+{
+	static unsigned int create_count = 0;
+	const char *path;
+	int fd;
+
+	hostpid_init();
+
+	*fname = t_strdup_printf("%lu.%s_%u.%s", (unsigned long) ioloop_time,
+				 my_pid, create_count++, my_hostname);
+
+	path = t_strconcat(dir, "/", *fname, NULL);
+	fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0660);
+	if (fd == -1) {
+		/* don't bother checking if it was because file existed -
+		   if that happens it's itself an error. */
+		mail_storage_set_critical(storage,
+					  "Can't create file %s: %m", path);
+	}
+
+	return fd;
+}
+
+static const char *maildir_read_into_tmp(MailStorage *storage, const char *dir,
+					 IOBuffer *buf, size_t data_size)
+{
+	const char *fname, *path;
+	int fd;
+
+	fd = maildir_create_tmp(storage, dir, &fname);
+	if (fd == -1)
+		return NULL;
+
+	path = t_strconcat(dir, "/", fname, NULL);
+	if (!index_storage_save_into_fd(storage, fd, path, buf, data_size))
+		fname = NULL;
+
+	(void)close(fd);
+
+	if (fname == NULL)
+		(void)unlink(path);
+	return fname;
+}
+
+int maildir_storage_save(Mailbox *box, MailFlags flags,
+			 const char *custom_flags[], time_t internal_date,
+			 IOBuffer *data, size_t data_size)
+{
+        IndexMailbox *ibox = (IndexMailbox *) box;
+        struct utimbuf buf;
+	const char *tmpdir, *fname, *tmp_path, *new_path;
+	int failed;
+
+	if (box->readonly) {
+		mail_storage_set_error(box->storage, "Mailbox is read-only");
+		return FALSE;
+	}
+
+	if (!index_mailbox_fix_custom_flags(ibox, &flags, custom_flags))
+		return mail_storage_set_index_error(ibox);
+
+	t_push();
+
+	/* create the file into tmp/ directory */
+	tmpdir = t_strconcat(box->storage->dir, "/tmp", NULL);
+	fname = maildir_read_into_tmp(box->storage, tmpdir, data, data_size);
+	if (fname == NULL) {
+		t_pop();
+		return FALSE;
+	}
+	tmp_path = t_strconcat(tmpdir, "/", fname, NULL);
+
+	fname = maildir_filename_set_flags(fname, flags);
+	new_path = t_strconcat(box->storage->dir, "/new/", fname, NULL);
+
+	/* set the internal_date by modifying mtime */
+	buf.actime = ioloop_time;
+	buf.modtime = internal_date;
+	(void)utime(tmp_path, &buf);
+
+	/* move the file into new/ directory - syncing will pick it
+	   up from there */
+	if (rename(tmp_path, new_path) == 0)
+		failed = FALSE;
+	else {
+		mail_storage_set_critical(box->storage, "rename(%s, %s) "
+					  "failed: %m", tmp_path, new_path);
+		(void)unlink(tmp_path);
+		failed = TRUE;
+	}
+
+	t_pop();
+	return !failed;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/maildir/maildir-storage.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,373 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "unlink-directory.h"
+#include "subscription-file/subscription-file.h"
+#include "maildir-index.h"
+#include "maildir-storage.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#define CREATE_MODE 0770 /* umask() should limit it more */
+
+extern MailStorage maildir_storage;
+static Mailbox maildir_mailbox;
+
+static const char *maildirs[] = { "cur", "new", "tmp", NULL  };
+
+static MailStorage *maildir_create(const char *data)
+{
+	MailStorage *storage;
+	const char *home, *path;
+
+	if (data == NULL || *data == '\0') {
+		/* we'll need to figure out the maildir location ourself.
+		   it's either root dir if we've already chroot()ed, or
+		   $HOME/Maildir otherwise */
+		if (access("/cur", R_OK|W_OK|X_OK) == 0)
+			data = "/";
+		else {
+			home = getenv("HOME");
+			if (home != NULL) {
+				path = t_strconcat(home, "/Maildir", NULL);
+				if (access(path, R_OK|W_OK|X_OK) == 0)
+					data = path;
+			}
+		}
+	}
+
+	if (data == NULL)
+		return NULL;
+
+	storage = i_new(MailStorage, 1);
+	memcpy(storage, &maildir_storage, sizeof(MailStorage));
+
+	storage->dir = i_strdup(data);
+	return storage;
+}
+
+static void maildir_free(MailStorage *storage)
+{
+	i_free(storage->dir);
+	i_free(storage);
+}
+
+static int maildir_autodetect(const char *data)
+{
+	struct stat st;
+
+	return stat(t_strconcat(data, "/cur", NULL), &st) == 0 &&
+		S_ISDIR(st.st_mode);
+}
+
+static int maildir_is_valid_name(MailStorage *storage, const char *name)
+{
+	return name[0] != '\0' && name[0] != storage->hierarchy_sep &&
+		strchr(name, '/') == NULL;
+}
+
+/* create or fix maildir, ignore if it already exists */
+static int create_maildir(const char *dir, int verify)
+{
+	const char **tmp;
+	char path[1024];
+
+	if (mkdir(dir, CREATE_MODE) == -1 && (errno != EEXIST || !verify))
+		return FALSE;
+
+	for (tmp = maildirs; *tmp != NULL; tmp++) {
+		i_snprintf(path, sizeof(path), "%s/%s", dir, *tmp);
+
+		if (mkdir(path, CREATE_MODE) == -1 &&
+		    (errno != EEXIST || !verify))
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+static int verify_inbox(MailStorage *storage, const char *dir)
+{
+	const char **tmp;
+	char src[1024], dest[1024];
+
+	/* first make sure the cur/ new/ and tmp/ dirs exist in root dir */
+	(void)create_maildir(dir, TRUE);
+
+	/* create the .INBOX directory */
+	i_snprintf(dest, sizeof(dest), "%s/.INBOX", dir);
+	if (mkdir(dest, CREATE_MODE) == -1 && errno != EEXIST) {
+		mail_storage_set_critical(storage, "Can't create directory "
+					  "%s: %m", dest);
+		return FALSE;
+	}
+
+	/* then symlink the cur/ new/ and tmp/ into the .INBOX/ directory */
+	for (tmp = maildirs; *tmp != NULL; tmp++) {
+		i_snprintf(src, sizeof(src), "../%s", *tmp);
+		i_snprintf(dest, sizeof(dest), "%s/.INBOX/%s", dir, *tmp);
+
+		if (symlink(src, dest) == -1 && errno != EEXIST) {
+			mail_storage_set_critical(storage, "symlink(%s, %s) "
+						  "failed: %m", src, dest);
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+static Mailbox *maildir_open(MailStorage *storage, const char *name,
+			     int readonly)
+{
+	IndexMailbox *ibox;
+	const char *path;
+
+	path = t_strconcat(storage->dir, "/.", name, NULL);
+
+	ibox = index_storage_init(storage, &maildir_mailbox,
+				  maildir_index_alloc(path), name, readonly);
+	if (ibox != NULL)
+		ibox->expunge_locked = maildir_expunge_locked;
+	return (Mailbox *) ibox;
+}
+
+static Mailbox *maildir_open_mailbox(MailStorage *storage, const char *name,
+				     int readonly)
+{
+	struct stat st;
+	char path[1024];
+
+	mail_storage_clear_error(storage);
+
+	/* INBOX is always case-insensitive */
+	if (strcasecmp(name, "INBOX") == 0) {
+		if (!verify_inbox(storage, storage->dir))
+			return NULL;
+		return maildir_open(storage, "INBOX", readonly);
+	}
+
+	i_snprintf(path, sizeof(path), "%s/.%s", storage->dir, name);
+	if (stat(path, &st) == 0) {
+		/* exists - make sure the required directories are also there */
+		(void)create_maildir(path, TRUE);
+
+		return maildir_open(storage, name, readonly);
+	} else if (errno == ENOENT) {
+		mail_storage_set_error(storage, "Mailbox doesn't exist");
+		return NULL;
+	} else {
+		mail_storage_set_critical(storage, "Can't open mailbox %s: %m",
+					  name);
+		return NULL;
+	}
+}
+
+static int maildir_create_mailbox(MailStorage *storage, const char *name)
+{
+	char path[1024];
+
+	mail_storage_clear_error(storage);
+
+	if (strcasecmp(name, "INBOX") == 0)
+		name = "INBOX";
+
+	if (!maildir_is_valid_name(storage, name)) {
+		mail_storage_set_error(storage, "Invalid mailbox name");
+		return FALSE;
+	}
+
+	i_snprintf(path, sizeof(path), "%s/.%s", storage->dir, name);
+	if (create_maildir(path, FALSE))
+		return TRUE;
+	else if (errno == EEXIST) {
+		mail_storage_set_error(storage, "Mailbox already exists");
+		return FALSE;
+	} else {
+		mail_storage_set_critical(storage, "Can't create mailbox "
+					  "%s: %m", name);
+		return FALSE;
+	}
+}
+
+static int maildir_delete_mailbox(MailStorage *storage, const char *name)
+{
+	struct stat st;
+	char src[1024], dest[1024];
+	int count;
+
+	mail_storage_clear_error(storage);
+
+	if (strcasecmp(name, "INBOX") == 0) {
+		mail_storage_set_error(storage, "INBOX can't be deleted.");
+		return FALSE;
+	}
+
+	/* rename the .maildir into ..maildir which marks it as being
+	   deleted. this way we never see partially deleted maildirs. */
+	i_snprintf(src, sizeof(src), "%s/.%s", storage->dir, name);
+	i_snprintf(dest, sizeof(dest), "%s/..%s", storage->dir, name);
+
+	if (stat(src, &st) != 0 && errno == ENOENT) {
+		mail_storage_set_error(storage, "Mailbox doesn't exist.");
+		return FALSE;
+	}
+
+	count = 0;
+	while (rename(src, dest) == -1 && count < 2) {
+		if (errno != EEXIST) {
+			mail_storage_set_critical(storage,
+						  "rename(%s, %s) failed: %m",
+						  src, dest);
+			return FALSE;
+		}
+
+		/* ..dir already existed? delete it and try again */
+		if (!unlink_directory(dest)) {
+			mail_storage_set_critical(storage,
+						  "unlink_directory(%s) "
+						  "failed: %m", dest);
+			return FALSE;
+		}
+		count++;
+	}
+
+	if (!unlink_directory(dest)) {
+		mail_storage_set_critical(storage, "unlink_directory(%s) "
+					  "failed: %m", dest);
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static int move_inbox_data(MailStorage *storage, const char *newdir)
+{
+	const char **tmp;
+	char oldpath[1024], newpath[1024];
+
+	/* newpath points to the destination folder directory, which contains
+	   symlinks to real INBOX directories. unlink() the symlinks and
+	   move the real cur/ directory here. */
+	for (tmp = maildirs; *tmp != NULL; tmp++) {
+		i_snprintf(newpath, sizeof(newpath), "%s/%s", newdir, *tmp);
+
+		if (unlink(newpath) == -1 && errno != EEXIST) {
+			mail_storage_set_error(storage, "unlink(%s) failed: "
+					       "%m", newpath);
+			return FALSE;
+		}
+	}
+
+	i_snprintf(oldpath, sizeof(oldpath), "%s/cur", storage->dir);
+	i_snprintf(newpath, sizeof(newpath), "%s/cur", newdir);
+	if (rename(oldpath, newpath) != 0) {
+		mail_storage_set_critical(storage, "rename(%s, %s) failed: %m",
+					  oldpath, newpath);
+		return FALSE;
+	}
+
+	/* create back the cur/ directory for INBOX */
+	(void)mkdir(oldpath, CREATE_MODE);
+	return TRUE;
+}
+
+static int maildir_rename_mailbox(MailStorage *storage, const char *oldname,
+				  const char *newname)
+{
+	char oldpath[1024], newpath[1024];
+
+	mail_storage_clear_error(storage);
+
+	if (strcasecmp(oldname, "INBOX") == 0)
+		oldname = "INBOX";
+
+	/* NOTE: renaming INBOX works just fine with us, it's simply created
+	   the next time it's needed. Only problem with it is that it's not
+	   atomic operation but that can't be really helped. */
+	i_snprintf(oldpath, sizeof(oldpath), "%s/.%s", storage->dir, oldname);
+	i_snprintf(newpath, sizeof(newpath), "%s/.%s", storage->dir, newname);
+	if (rename(oldpath, newpath) == 0) {
+		if (strcmp(oldname, "INBOX") == 0)
+			return move_inbox_data(storage, newpath);
+		return TRUE;
+	}
+
+	if (errno == EEXIST) {
+		mail_storage_set_error(storage,
+				       "Target mailbox already exists");
+		return FALSE;
+	} else {
+		mail_storage_set_critical(storage, "rename(%s, %s) failed: %m",
+					  oldpath, newpath);
+		return FALSE;
+	}
+}
+
+static int maildir_get_mailbox_name_status(MailStorage *storage,
+					   const char *name,
+					   MailboxNameStatus *status)
+{
+	struct stat st;
+	char path[1024];
+
+	mail_storage_clear_error(storage);
+
+	if (strcasecmp(name, "INBOX") == 0)
+		name = "INBOX";
+
+	if (!maildir_is_valid_name(storage, name)) {
+		*status = MAILBOX_NAME_INVALID;
+		return TRUE;
+	}
+
+	i_snprintf(path, sizeof(path), "%s/.%s", storage->dir, name);
+	if (stat(path, &st) == 0) {
+		*status = MAILBOX_NAME_EXISTS;
+		return TRUE;
+	} else if (errno == ENOENT) {
+		*status = MAILBOX_NAME_VALID;
+		return TRUE;
+	} else {
+		mail_storage_set_critical(storage, "mailbox name status: "
+					  "stat(%s) failed: %m", path);
+		return FALSE;
+	}
+}
+
+MailStorage maildir_storage = {
+	"maildir", /* name */
+
+	'.', /* hierarchy_sep - can't be changed */
+
+	maildir_create,
+	maildir_free,
+	maildir_autodetect,
+	maildir_open_mailbox,
+	maildir_create_mailbox,
+	maildir_delete_mailbox,
+	maildir_rename_mailbox,
+	maildir_find_mailboxes,
+	subsfile_set_subscribed,
+	maildir_find_subscribed,
+	maildir_get_mailbox_name_status,
+	mail_storage_get_last_error
+};
+
+static Mailbox maildir_mailbox = {
+	NULL, /* name */
+	NULL, /* storage */
+
+	index_storage_close,
+	index_storage_get_status,
+	index_storage_sync,
+	index_storage_expunge,
+	index_storage_update_flags,
+	maildir_storage_copy,
+	index_storage_fetch,
+	index_storage_search,
+	maildir_storage_save,
+	mail_storage_is_inconsistency_error
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/maildir/maildir-storage.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,20 @@
+#ifndef __MAILDIR_STORAGE_H
+#define __MAILDIR_STORAGE_H
+
+#include "index-storage.h"
+
+int maildir_storage_copy(Mailbox *box, Mailbox *destbox,
+			 const char *messageset, int uidset);
+int maildir_storage_save(Mailbox *box, MailFlags flags,
+			 const char *custom_flags[], time_t internal_date,
+			 IOBuffer *data, size_t data_size);
+
+int maildir_find_mailboxes(MailStorage *storage, const char *mask,
+			   MailboxFunc func, void *user_data);
+int maildir_find_subscribed(MailStorage *storage, const char *mask,
+			    MailboxFunc func, void *user_data);
+
+int maildir_expunge_locked(IndexMailbox *ibox,
+			   MailExpungeFunc expunge_func, void *user_data);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/mbox/.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-storage/index/mbox/Makefile.am	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,19 @@
+noinst_LIBRARIES = libstorage_mbox.a
+
+INCLUDES = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-mail \
+	-I$(top_srcdir)/src/lib-imap \
+	-I$(top_srcdir)/src/lib-index \
+	-I$(top_srcdir)/src/lib-index/mbox \
+	-I$(top_srcdir)/src/lib-storage \
+	-I$(top_srcdir)/src/lib-storage/index
+
+libstorage_mbox_a_SOURCES = \
+	mbox-expunge.c \
+	mbox-list.c \
+	mbox-save.c \
+	mbox-storage.c
+
+noinst_HEADERS = \
+	mbox-storage.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/mbox/mbox-expunge.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,35 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "mbox-storage.h"
+
+int mbox_expunge_locked(IndexMailbox *ibox,
+			MailExpungeFunc expunge_func, void *user_data)
+{
+	MailIndexRecord *rec;
+	unsigned int seq, uid;
+
+	/* FIXME: open the mbox file, lock it, and remove the deleted
+	   blocks. probably better to do it in small blocks than to
+	   memmove() megabytes of data.. */
+
+	rec = index_expunge_seek_first(ibox, &seq);
+	while (rec != NULL) {
+		if (rec->msg_flags & MAIL_DELETED) {
+			/* save UID before deletion */
+			uid = rec->uid;
+
+			if (!ibox->index->expunge(ibox->index, rec,
+						  seq, FALSE))
+				return FALSE;
+
+			if (expunge_func != NULL)
+				expunge_func(&ibox->box, seq, uid, user_data);
+			seq--;
+		}
+		rec = ibox->index->next(ibox->index, rec);
+		seq++;
+	}
+
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/mbox/mbox-list.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,166 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "unlink-directory.h"
+#include "imap-match.h"
+#include "subscription-file/subscription-file.h"
+#include "mbox-index.h"
+#include "mbox-storage.h"
+
+#include <dirent.h>
+#include <sys/stat.h>
+
+typedef struct {
+	MailboxFunc func;
+	void *user_data;
+} FindSubscribedData;
+
+static int mbox_find_path(MailStorage *storage, const ImapMatchGlob *glob,
+			  MailboxFunc func, void *user_data,
+			  const char *relative_dir, int *found_inbox)
+{
+	DIR *dirp;
+	struct dirent *d;
+	struct stat st;
+	const char *dir;
+	char fulldir[1024], path[1024];
+	int failed, len;
+
+	if (relative_dir == NULL)
+		dir = storage->dir;
+	else {
+		i_snprintf(fulldir, sizeof(fulldir), "%s/%s",
+			   storage->dir, relative_dir);
+		dir = fulldir;
+	}
+
+	dirp = opendir(dir);
+	if (dirp == NULL) {
+		mail_storage_set_critical(storage, "opendir(%s) failed: %m",
+					  dir);
+		return FALSE;
+	}
+
+	failed = FALSE;
+	while ((d = readdir(dirp)) != NULL) {
+		const char *fname = d->d_name;
+
+		/* skip all hidden files */
+		if (fname[0] == '.')
+			continue;
+
+		/* skip all .lock files */
+		len = strlen(fname);
+		if (len > 5 && strcmp(fname+len-5, ".lock") == 0)
+			continue;
+
+		/* make sure the mask matches */
+		if (relative_dir == NULL) {
+			if (imap_match(glob, fname, 0, NULL) < 0)
+				continue;
+		} else {
+			i_snprintf(path, sizeof(path),
+				   "%s/%s", relative_dir, fname);
+			if (imap_match(glob, path, 0, NULL) < 0)
+				continue;
+		}
+
+		/* see if it's a directory */
+		i_snprintf(path, sizeof(path), "%s/%s", dir, fname);
+		if (stat(path, &st) != 0) {
+			if (errno == ENOENT)
+				continue; /* just deleted, ignore */
+
+			mail_storage_set_critical(storage, "stat(%s) failed: "
+						  "%m", path);
+			failed = TRUE;
+			break;
+		}
+
+		if (relative_dir == NULL) {
+			strncpy(path, fname, sizeof(path)-1);
+			path[sizeof(path)-1] = '\0';
+		} else {
+			i_snprintf(path, sizeof(path), "%s/%s",
+				   relative_dir, fname);
+		}
+
+		if (S_ISDIR(st.st_mode)) {
+			/* subdirectory, scan it too */
+			if (!mbox_find_path(storage, glob, func,
+					    user_data, path, NULL)) {
+				failed = TRUE;
+				break;
+			}
+		} else {
+			if (found_inbox != NULL &&
+			    strcasecmp(path, "inbox") == 0)
+				*found_inbox = TRUE;
+
+			func(storage, path, MAILBOX_NOINFERIORS, user_data);
+		}
+	}
+
+	(void)closedir(dirp);
+	return !failed;
+}
+
+int mbox_find_mailboxes(MailStorage *storage, const char *mask,
+			MailboxFunc func, void *user_data)
+{
+        const ImapMatchGlob *glob;
+	int found_inbox;
+
+	mail_storage_clear_error(storage);
+
+	glob = imap_match_init(mask, TRUE, '/');
+
+	found_inbox = FALSE;
+	if (!mbox_find_path(storage, glob, func, user_data,
+			    NULL, &found_inbox))
+		return FALSE;
+
+	if (!found_inbox && imap_match(glob, "INBOX", 0, NULL) < 0) {
+		/* INBOX always exists */
+		func(storage, "INBOX", MAILBOX_UNMARKED | MAILBOX_NOINFERIORS,
+		     user_data);
+	}
+
+	return TRUE;
+}
+
+static int mbox_subs_func(MailStorage *storage, const char *name,
+			  void *user_data)
+{
+	FindSubscribedData *data = user_data;
+	MailboxFlags flags;
+	struct stat st;
+	char path[1024];
+
+	/* see if the mailbox exists, don't bother with the marked flags */
+	if (strcasecmp(name, "INBOX") == 0) {
+		/* inbox always exists */
+		flags = 0;
+	} else {
+		i_snprintf(path, sizeof(path), "%s/%s", storage->dir, name);
+		flags = stat(path, &st) == 0 && !S_ISDIR(st.st_mode) ?
+			0 : MAILBOX_NOSELECT;
+	}
+
+	data->func(storage, name, flags, data->user_data);
+	return TRUE;
+}
+
+int mbox_find_subscribed(MailStorage *storage, const char *mask,
+			 MailboxFunc func, void *user_data)
+{
+	FindSubscribedData data;
+
+	data.func = func;
+	data.user_data = user_data;
+
+	if (subsfile_foreach(storage, mask, mbox_subs_func, &data) <= 0)
+		return FALSE;
+
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/mbox/mbox-save.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,62 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "hostpid.h"
+#include "iobuffer.h"
+#include "mbox-index.h"
+#include "mbox-lock.h"
+#include "mbox-storage.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+int mbox_storage_save(Mailbox *box, MailFlags flags, const char *custom_flags[],
+		      time_t internal_date, IOBuffer *data, size_t data_size)
+{
+	IndexMailbox *ibox = (IndexMailbox *) box;
+	off_t pos;
+	int fd, failed;
+
+	if (box->readonly) {
+		mail_storage_set_error(box->storage, "Mailbox is read-only");
+		return FALSE;
+	}
+
+	if (!index_mailbox_fix_custom_flags(ibox, &flags, custom_flags))
+		return mail_storage_set_index_error(ibox);
+
+	/* append the data into mbox file */
+	fd = open(ibox->index->mbox_path, O_RDWR | O_CREAT);
+	if (fd == -1) {
+		mail_storage_set_error(box->storage, "Can't open mbox file "
+				       "%s: %m", ibox->index->mbox_path);
+		return FALSE;
+	}
+
+	if (!mbox_lock(ibox->index, ibox->index->mbox_path, fd)) {
+		(void)close(fd);
+		return mail_storage_set_index_error(ibox);
+	}
+
+	failed = FALSE;
+
+	pos = lseek(fd, 0, SEEK_END);
+	if (pos == (off_t)-1) {
+		mail_storage_set_error(box->storage, "lseek() failed for mbox "
+				       "file %s: %m", ibox->index->mbox_path);
+		failed = TRUE;
+	}
+
+	if (!failed && !index_storage_save_into_fd(box->storage, fd,
+						   ibox->index->mbox_path,
+						   data, data_size)) {
+		/* failed, truncate file back to original size */
+		(void)ftruncate(fd, pos);
+		failed = TRUE;
+	}
+
+	(void)mbox_unlock(ibox->index, ibox->index->mbox_path, fd);
+	(void)close(fd);
+	return !failed;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/mbox/mbox-storage.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,365 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "unlink-directory.h"
+#include "subscription-file/subscription-file.h"
+#include "mbox-index.h"
+#include "mbox-storage.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#define CREATE_MODE 0770 /* umask() should limit it more */
+
+extern MailStorage mbox_storage;
+static Mailbox mbox_mailbox;
+
+static int mbox_autodetect(const char *data)
+{
+	const char *path;
+	struct stat st;
+
+        path = t_strconcat(data, "/.imap", NULL);
+	if (stat(path, &st) == 0 && S_ISDIR(st.st_mode) &&
+	    access(path, R_OK|W_OK|X_OK) == 0)
+		return TRUE;
+
+	path = t_strconcat(data, "/inbox", NULL);
+	if (stat(path, &st) == 0 && !S_ISDIR(st.st_mode) &&
+	    access(path, R_OK|W_OK) == 0)
+		return TRUE;
+
+	path = t_strconcat(data, "/mbox", NULL);
+	if (stat(path, &st) == 0 && !S_ISDIR(st.st_mode) &&
+	    access(path, R_OK|W_OK) == 0)
+		return TRUE;
+
+	return FALSE;
+}
+
+static MailStorage *mbox_create(const char *data)
+{
+	MailStorage *storage;
+	const char *home, *path;
+
+	if (data == NULL || *data == '\0') {
+		/* we'll need to figure out the mail location ourself.
+		   it's root dir if we've already chroot()ed, otherwise
+		   either $HOME/mail or $HOME/Mail */
+		if (mbox_autodetect(""))
+			data = "/";
+		else {
+			home = getenv("HOME");
+			if (home != NULL) {
+				path = t_strconcat(home, "/mail", NULL);
+				if (access(path, R_OK|W_OK|X_OK) == 0)
+					data = path;
+				else {
+					path = t_strconcat(home, "/Mail", NULL);
+					if (access(path, R_OK|W_OK|X_OK) == 0)
+						data = path;
+				}
+			}
+		}
+	}
+
+	if (data == NULL)
+		return NULL;
+
+	storage = i_new(MailStorage, 1);
+	memcpy(storage, &mbox_storage, sizeof(MailStorage));
+
+	storage->dir = i_strdup(data);
+	return storage;
+}
+
+static void mbox_free(MailStorage *storage)
+{
+	i_free(storage->dir);
+	i_free(storage);
+}
+
+static int mbox_is_valid_name(MailStorage *storage, const char *name)
+{
+	return name[0] != '\0' && name[0] != storage->hierarchy_sep;
+}
+
+static const char *mbox_get_index_dir(const char *mbox_path)
+{
+	const char *p, *rootpath;
+
+	p = strrchr(mbox_path, '/');
+	if (p == NULL)
+		return t_strconcat(".imap/", mbox_path);
+	else {
+		rootpath = t_strndup(mbox_path, (unsigned int) (p-mbox_path));
+		return t_strconcat(rootpath, "/.imap/", p+1, NULL);
+	}
+}
+
+static int create_mbox_index_dirs(const char *mbox_path, int verify)
+{
+	const char *index_dir, *imap_dir;
+
+	index_dir = mbox_get_index_dir(mbox_path);
+	imap_dir = strstr(index_dir, ".imap/");
+	imap_dir = t_strndup(index_dir,
+			     (unsigned int) (imap_dir - index_dir) + 5);
+
+	if (mkdir(imap_dir, CREATE_MODE) == -1 && errno != EEXIST)
+		return FALSE;
+	if (mkdir(index_dir, CREATE_MODE) == -1 && (errno != EEXIST || !verify))
+		return FALSE;
+
+	return TRUE;
+}
+
+static void verify_inbox(MailStorage *storage)
+{
+	char path[1024];
+	int fd;
+
+	i_snprintf(path, sizeof(path), "%s/inbox", storage->dir);
+
+	/* make sure inbox file itself exists */
+	fd = open(path, O_RDWR | O_CREAT | O_EXCL);
+	if (fd != -1)
+		(void)close(fd);
+
+	/* make sure the index directories exist */
+	(void)create_mbox_index_dirs(path, TRUE);
+}
+
+static Mailbox *mbox_open(MailStorage *storage, const char *name, int readonly)
+{
+	IndexMailbox *ibox;
+	const char *path, *index_dir;
+
+	/* name = "foo/bar"
+	   mbox_path = "/mail/foo/bar"
+	   index_dir = "/mail/foo/.imap/bar" */
+	path = t_strconcat(storage->dir, "/", name, NULL);
+	index_dir = mbox_get_index_dir(path);
+
+	ibox = index_storage_init(storage, &mbox_mailbox,
+				  mbox_index_alloc(index_dir, path),
+				  name, readonly);
+	if (ibox != NULL)
+		ibox->expunge_locked = mbox_expunge_locked;
+	return (Mailbox *) ibox;
+}
+
+static Mailbox *mbox_open_mailbox(MailStorage *storage, const char *name,
+				  int readonly)
+{
+	struct stat st;
+	char path[1024];
+
+	mail_storage_clear_error(storage);
+
+	/* INBOX is always case-insensitive */
+	if (strcasecmp(name, "INBOX") == 0) {
+		/* make sure inbox exists */
+		verify_inbox(storage);
+		return mbox_open(storage, "inbox", readonly);
+	}
+
+	i_snprintf(path, sizeof(path), "%s/%s", storage->dir, name);
+	if (stat(path, &st) == 0) {
+		/* exists - make sure the required directories are also there */
+		(void)create_mbox_index_dirs(path, TRUE);
+
+		return mbox_open(storage, name, readonly);
+	} else if (errno == ENOENT) {
+		mail_storage_set_error(storage, "Mailbox doesn't exist");
+		return NULL;
+	} else {
+		mail_storage_set_critical(storage, "Can't open mailbox %s: %m",
+					  name);
+		return NULL;
+	}
+}
+
+static int mbox_create_mailbox(MailStorage *storage, const char *name)
+{
+	struct stat st;
+	char path[1024];
+	int fd;
+
+	mail_storage_clear_error(storage);
+
+	if (strcasecmp(name, "INBOX") == 0)
+		name = "inbox";
+
+	if (!mbox_is_valid_name(storage, name)) {
+		mail_storage_set_error(storage, "Invalid mailbox name");
+		return FALSE;
+	}
+
+	/* make sure it doesn't exist already */
+	i_snprintf(path, sizeof(path), "%s/%s", storage->dir, name);
+	if (stat(path, &st) == 0) {
+		mail_storage_set_error(storage, "Mailbox already exists");
+		return FALSE;
+	}
+
+	if (errno != EEXIST) {
+		mail_storage_set_critical(storage, "stat() failed for mbox "
+					  "file %s: %m", path);
+		return FALSE;
+	}
+
+	/* create the mailbox file */
+	fd = open(path, O_RDWR | O_CREAT | O_EXCL);
+	if (fd != -1) {
+		(void)close(fd);
+		return TRUE;
+	} else if (errno == EEXIST) {
+		/* mailbox was just created between stat() and open() call.. */
+		mail_storage_set_error(storage, "Mailbox already exists");
+		return FALSE;
+	} else {
+		mail_storage_set_critical(storage, "Can't create mailbox "
+					  "%s: %m", name);
+		return FALSE;
+	}
+}
+
+static int mbox_delete_mailbox(MailStorage *storage, const char *name)
+{
+	const char *index_dir;
+	char path[1024];
+
+	mail_storage_clear_error(storage);
+
+	if (strcasecmp(name, "INBOX") == 0) {
+		mail_storage_set_error(storage, "INBOX can't be deleted.");
+		return FALSE;
+	}
+
+	/* first unlink the mbox file */
+	i_snprintf(path, sizeof(path), "%s/%s", storage->dir, name);
+	if (unlink(path) == -1) {
+		if (errno == ENOENT) {
+			mail_storage_set_error(storage,
+					       "Mailbox doesn't exist.");
+		} else {
+			mail_storage_set_error(storage, "Can't delete mbox "
+					       "file %s: %m", path);
+		}
+		return FALSE;
+	}
+
+	/* next delete the index directory */
+	index_dir = mbox_get_index_dir(path);
+	if (!unlink_directory(index_dir)) {
+		mail_storage_set_critical(storage, "unlink_directory(%s) "
+					  "failed: %m", index_dir);
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static int mbox_rename_mailbox(MailStorage *storage, const char *oldname,
+			       const char *newname)
+{
+	const char *old_indexdir, *new_indexdir;
+	char oldpath[1024], newpath[1024];
+
+	mail_storage_clear_error(storage);
+
+	if (strcasecmp(oldname, "INBOX") == 0)
+		oldname = "inbox";
+
+	/* NOTE: renaming INBOX works just fine with us, it's simply created
+	   the next time it's needed. */
+	i_snprintf(oldpath, sizeof(oldpath), "%s/%s", storage->dir, oldname);
+	i_snprintf(newpath, sizeof(newpath), "%s/%s", storage->dir, newname);
+	if (link(oldpath, newpath) == 0) {
+		(void)unlink(oldpath);
+		/* ... */
+	} else if (errno == EEXIST) {
+		mail_storage_set_error(storage,
+				       "Target mailbox already exists");
+		return FALSE;
+	} else {
+		mail_storage_set_critical(storage, "link(%s, %s) failed: %m",
+					  oldpath, newpath);
+		return FALSE;
+	}
+
+	/* we need to rename the index directory as well */
+	old_indexdir = mbox_get_index_dir(oldpath);
+	new_indexdir = mbox_get_index_dir(newpath);
+	(void)rename(old_indexdir, new_indexdir);
+
+	return TRUE;
+}
+
+static int mbox_get_mailbox_name_status(MailStorage *storage, const char *name,
+					MailboxNameStatus *status)
+{
+	struct stat st;
+	char path[1024];
+
+	mail_storage_clear_error(storage);
+
+	if (strcasecmp(name, "INBOX") == 0)
+		name = "inbox";
+
+	if (!mbox_is_valid_name(storage, name)) {
+		*status = MAILBOX_NAME_INVALID;
+		return TRUE;
+	}
+
+	i_snprintf(path, sizeof(path), "%s/%s", storage->dir, name);
+	if (stat(path, &st) == 0) {
+		*status = MAILBOX_NAME_EXISTS;
+		return TRUE;
+	} else if (errno == ENOENT) {
+		*status = MAILBOX_NAME_VALID;
+		return TRUE;
+	} else {
+		mail_storage_set_critical(storage, "mailbox name status: "
+					  "stat(%s) failed: %m", path);
+		return FALSE;
+	}
+}
+
+MailStorage mbox_storage = {
+	"mbox", /* name */
+
+	'/', /* hierarchy_sep - can't be changed */
+
+	mbox_create,
+	mbox_free,
+	mbox_autodetect,
+	mbox_open_mailbox,
+	mbox_create_mailbox,
+	mbox_delete_mailbox,
+	mbox_rename_mailbox,
+	mbox_find_mailboxes,
+	subsfile_set_subscribed,
+	mbox_find_subscribed,
+	mbox_get_mailbox_name_status,
+	mail_storage_get_last_error
+};
+
+static Mailbox mbox_mailbox = {
+	NULL, /* name */
+	NULL, /* storage */
+
+	index_storage_close,
+	index_storage_get_status,
+	index_storage_sync,
+	index_storage_expunge,
+	index_storage_update_flags,
+	index_storage_copy,
+	index_storage_fetch,
+	index_storage_search,
+	mbox_storage_save,
+	mail_storage_is_inconsistency_error
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/mbox/mbox-storage.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,19 @@
+#ifndef __MBOX_STORAGE_H
+#define __MBOX_STORAGE_H
+
+#include "index-storage.h"
+
+int mbox_storage_copy(Mailbox *box, Mailbox *destbox,
+		      const char *messageset, int uidset);
+int mbox_storage_save(Mailbox *box, MailFlags flags, const char *custom_flags[],
+		      time_t internal_date, IOBuffer *data, size_t data_size);
+
+int mbox_find_mailboxes(MailStorage *storage, const char *mask,
+			MailboxFunc func, void *user_data);
+int mbox_find_subscribed(MailStorage *storage, const char *mask,
+			 MailboxFunc func, void *user_data);
+
+int mbox_expunge_locked(IndexMailbox *ibox,
+			MailExpungeFunc expunge_func, void *user_data);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/mail-search.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,496 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "mail-search.h"
+
+typedef struct {
+	Pool pool;
+	const char *error;
+} SearchBuildData;
+
+static MailSearchArg *search_arg_new(Pool pool, MailSearchArgType type)
+{
+	MailSearchArg *arg;
+
+	arg = p_new(pool, MailSearchArg, 1);
+	arg->type = type;
+
+	return arg;
+}
+
+#define ARG_NEW(type, value) \
+	arg_new(data, args, next_sarg, type, value)
+
+static int arg_new(SearchBuildData *data, ImapArgList **args,
+		   MailSearchArg **next_sarg, MailSearchArgType type, int value)
+{
+	MailSearchArg *sarg;
+
+	*next_sarg = sarg = search_arg_new(data->pool, type);
+	if (value == 0)
+		return TRUE;
+
+	/* first arg */
+	if (*args == NULL) {
+		data->error = "Missing parameter for argument";
+		return FALSE;
+	}
+
+	sarg->value.str = str_ucase((*args)->arg.data.str);
+	*args = (*args)->next;
+
+	/* second arg */
+	if (value == 2) {
+		if (*args == NULL) {
+			data->error = "Missing parameter for argument";
+			return FALSE;
+		}
+
+		sarg->hdr_value = str_ucase((*args)->arg.data.str);
+		*args = (*args)->next;
+	}
+
+	return TRUE;
+}
+
+static int search_arg_build(SearchBuildData *data, ImapArgList **args,
+			    MailSearchArg **next_sarg)
+{
+	MailSearchArg **subargs;
+	ImapArg *arg;
+	char *str;
+
+	if (*args == NULL) {
+		data->error = "Missing argument";
+		return FALSE;
+	}
+
+	arg = &(*args)->arg;
+
+	if (arg->type == IMAP_ARG_NIL) {
+		/* NIL not allowed */
+		data->error = "NIL not allowed";
+		return FALSE;
+	}
+
+	if (arg->type == IMAP_ARG_LIST) {
+		ImapArgList *list = arg->data.list;
+
+		*next_sarg = search_arg_new(data->pool, SEARCH_SUB);
+		subargs = &(*next_sarg)->value.subargs;
+		while (list != NULL) {
+			if (!search_arg_build(data, &list, subargs))
+				return FALSE;
+			subargs = &(*subargs)->next;
+		}
+
+		*args = (*args)->next;
+		return TRUE;
+	}
+
+	i_assert(arg->type == IMAP_ARG_ATOM ||
+		 arg->type == IMAP_ARG_STRING);
+
+	/* string argument - get the name and jump to next */
+	str = arg->data.str;
+	*args = (*args)->next;
+	str_ucase(str);
+
+	switch (*str) {
+	case 'A':
+		if (strcmp(str, "ANSWERED") == 0)
+			return ARG_NEW(SEARCH_ANSWERED, 0);
+		else if (strcmp(str, "ALL") == 0)
+			return ARG_NEW(SEARCH_ALL, 0);
+		break;
+	case 'B':
+		if (strcmp(str, "BODY") == 0) {
+			/* <string> */
+			return ARG_NEW(SEARCH_BODY, 1);
+		} else if (strcmp(str, "BEFORE") == 0) {
+			/* <date> */
+			return ARG_NEW(SEARCH_BEFORE, 1);
+		} else if (strcmp(str, "BCC") == 0) {
+			/* <string> */
+			return ARG_NEW(SEARCH_BCC, 1);
+		}
+		break;
+	case 'C':
+		if (strcmp(str, "CC") == 0) {
+			/* <string> */
+			return ARG_NEW(SEARCH_CC, 1);
+		}
+		break;
+	case 'D':
+		if (strcmp(str, "DELETED") == 0)
+			return ARG_NEW(SEARCH_DELETED, 0);
+		else if (strcmp(str, "DRAFT") == 0)
+			return ARG_NEW(SEARCH_DRAFT, 0);
+		break;
+	case 'F':
+		if (strcmp(str, "FLAGGED") == 0)
+			return ARG_NEW(SEARCH_FLAGGED, 0);
+		else if (strcmp(str, "FROM") == 0) {
+			/* <string> */
+			return ARG_NEW(SEARCH_FROM, 1);
+		}
+		break;
+	case 'H':
+		if (strcmp(str, "HEADER") == 0) {
+			/* <field-name> <string> */
+			const char *key;
+
+			if (*args == NULL) {
+				data->error = "Missing parameter for HEADER";
+				return FALSE;
+			}
+			key = str_ucase((*args)->arg.data.str);
+
+			if (strcmp(key, "FROM") == 0) {
+				*args = (*args)->next;
+				return ARG_NEW(SEARCH_FROM, 1);
+			} else if (strcmp(key, "TO") == 0) {
+				*args = (*args)->next;
+				return ARG_NEW(SEARCH_TO, 1);
+			} else if (strcmp(key, "CC") == 0) {
+				*args = (*args)->next;
+				return ARG_NEW(SEARCH_CC, 1);
+			} else if (strcmp(key, "BCC") == 0) {
+				*args = (*args)->next;
+				return ARG_NEW(SEARCH_BCC, 1);
+			} else if (strcmp(key, "SUBJECT") == 0) {
+				*args = (*args)->next;
+				return ARG_NEW(SEARCH_SUBJECT, 1);
+			} else {
+				return ARG_NEW(SEARCH_HEADER, 2);
+			}
+		}
+		break;
+	case 'K':
+		if (strcmp(str, "KEYWORD") == 0) {
+			/* <flag> */
+			return ARG_NEW(SEARCH_KEYWORD, 1);
+		}
+		break;
+	case 'L':
+		if (strcmp(str, "LARGER") == 0) {
+			/* <n> */
+			return ARG_NEW(SEARCH_LARGER, 1);
+		}
+		break;
+	case 'N':
+		if (strcmp(str, "NOT") == 0) {
+			if (!search_arg_build(data, args, next_sarg))
+				return FALSE;
+			(*next_sarg)->not = !(*next_sarg)->not;
+			return TRUE;
+		} else if (strcmp(str, "NEW") == 0) {
+			/* NEW == (RECENT UNSEEN) */
+			*next_sarg = search_arg_new(data->pool, SEARCH_SUB);
+
+			subargs = &(*next_sarg)->value.subargs;
+			*subargs = search_arg_new(data->pool, SEARCH_RECENT);
+			(*subargs)->next = search_arg_new(data->pool,
+							  SEARCH_SEEN);
+			(*subargs)->next->not = TRUE;
+			return TRUE;
+		}
+		break;
+	case 'O':
+		if (strcmp(str, "OR") == 0) {
+			/* <search-key1> <search-key2> */
+			*next_sarg = search_arg_new(data->pool, SEARCH_OR);
+
+			subargs = &(*next_sarg)->value.subargs;
+			for (;;) {
+				if (!search_arg_build(data, args, subargs))
+					return FALSE;
+
+				subargs = &(*subargs)->next;
+
+				/* <key> OR <key> OR ... <key> - put them all
+				   under one SEARCH_OR list. */
+				if (*args == NULL)
+					break;
+
+				arg = &(*args)->arg;
+				if (arg->type != IMAP_ARG_ATOM ||
+				    strcasecmp(arg->data.str, "OR") != 0)
+					break;
+
+				*args = (*args)->next;
+			}
+
+			if (!search_arg_build(data, args, subargs))
+				return FALSE;
+			return TRUE;
+		} if (strcmp(str, "ON") == 0) {
+			/* <date> */
+			return ARG_NEW(SEARCH_ON, 1);
+		} if (strcmp(str, "OLD") == 0) {
+			/* OLD == NOT RECENT */
+			if (!ARG_NEW(SEARCH_RECENT, 0))
+				return FALSE;
+
+			(*next_sarg)->not = TRUE;
+			return TRUE;
+		}
+		break;
+	case 'R':
+		if (strcmp(str, "RECENT") == 0)
+			return ARG_NEW(SEARCH_RECENT, 0);
+		break;
+	case 'S':
+		if (strcmp(str, "SEEN") == 0)
+			return ARG_NEW(SEARCH_SEEN, 0);
+		else if (strcmp(str, "SUBJECT") == 0) {
+			/* <string> */
+			return ARG_NEW(SEARCH_SUBJECT, 1);
+		} else if (strcmp(str, "SENTBEFORE") == 0) {
+			/* <date> */
+			return ARG_NEW(SEARCH_SENTBEFORE, 1);
+		} else if (strcmp(str, "SENTON") == 0) {
+			/* <date> */
+			return ARG_NEW(SEARCH_SENTON, 1);
+		} else if (strcmp(str, "SENTSINCE") == 0) {
+			/* <date> */
+			return ARG_NEW(SEARCH_SENTSINCE, 1);
+		} else if (strcmp(str, "SINCE") == 0) {
+			/* <date> */
+			return ARG_NEW(SEARCH_SINCE, 1);
+		} else if (strcmp(str, "SMALLER") == 0) {
+			/* <n> */
+			return ARG_NEW(SEARCH_SMALLER, 1);
+		}
+		break;
+	case 'T':
+		if (strcmp(str, "TEXT") == 0) {
+			/* <string> */
+			return ARG_NEW(SEARCH_TEXT, 1);
+		} else if (strcmp(str, "TO") == 0) {
+			/* <string> */
+			return ARG_NEW(SEARCH_TO, 1);
+		}
+		break;
+	case 'U':
+		if (strcmp(str, "UID") == 0) {
+			/* <message set> */
+			return ARG_NEW(SEARCH_UID, 1);
+		} else if (strcmp(str, "UNANSWERED") == 0) {
+			if (!ARG_NEW(SEARCH_ANSWERED, 0))
+				return FALSE;
+			(*next_sarg)->not = TRUE;
+			return TRUE;
+		} else if (strcmp(str, "UNDELETED") == 0) {
+			if (!ARG_NEW(SEARCH_DELETED, 0))
+				return FALSE;
+			(*next_sarg)->not = TRUE;
+			return TRUE;
+		} else if (strcmp(str, "UNDRAFT") == 0) {
+			if (!ARG_NEW(SEARCH_DRAFT, 0))
+				return FALSE;
+			(*next_sarg)->not = TRUE;
+			return TRUE;
+		} else if (strcmp(str, "UNFLAGGED") == 0) {
+			if (!ARG_NEW(SEARCH_FLAGGED, 0))
+				return FALSE;
+			(*next_sarg)->not = TRUE;
+			return TRUE;
+		} else if (strcmp(str, "UNKEYWORD") == 0) {
+			if (!ARG_NEW(SEARCH_KEYWORD, 0))
+				return FALSE;
+			(*next_sarg)->not = TRUE;
+			return TRUE;
+		} else if (strcmp(str, "UNSEEN") == 0) {
+			if (!ARG_NEW(SEARCH_SEEN, 0))
+				return FALSE;
+			(*next_sarg)->not = TRUE;
+			return TRUE;
+		}
+		break;
+	default:
+		if (*str == '*' || (*str >= '0' && *str <= '9')) {
+			/* <message-set> */
+			if (!ARG_NEW(SEARCH_SET, 0))
+				return FALSE;
+
+			(*next_sarg)->value.str = str;
+			return TRUE;
+		}
+		break;
+	}
+
+	data->error = t_strconcat("Unknown argument ", str, NULL);
+	return FALSE;
+}
+
+MailSearchArg *mail_search_args_build(Pool pool, ImapArg *args, int args_count,
+				      const char **error)
+{
+        SearchBuildData data;
+	MailSearchArg *first_sarg, **sargs;
+	ImapArgList *list, **listp;
+	int i;
+
+	/* first we need to conver the imap arguments into ImapArgList */
+	list = NULL; listp = &list;
+	for (i = 0; i < args_count; i++) {
+		*listp = t_new(ImapArgList, 1);
+		memcpy(&(*listp)->arg, &args[i], sizeof(ImapArg));
+		listp = &(*listp)->next;
+	}
+
+	data.pool = pool;
+	data.error = NULL;
+
+	/* get the first arg */
+	first_sarg = NULL; sargs = &first_sarg;
+	while (list != NULL) {
+		if (!search_arg_build(&data, &list, sargs)) {
+			*error = data.error;
+			return NULL;
+		}
+		sargs = &(*sargs)->next;
+	}
+
+	*error = NULL;
+	return first_sarg;
+}
+
+void mail_search_args_reset(MailSearchArg *args)
+{
+	while (args != NULL) {
+		if (args->type == SEARCH_OR || args->type == SEARCH_SUB)
+			mail_search_args_reset(args->value.subargs);
+		args->result = 0;
+
+		args = args->next;
+	}
+}
+
+static void search_arg_foreach(MailSearchArg *arg, MailSearchForeachFunc func,
+			       void *user_data)
+{
+	MailSearchArg *subarg;
+
+	if (arg->result != 0)
+		return;
+
+	if (arg->type == SEARCH_SUB) {
+		/* sublist of conditions */
+		i_assert(arg->value.subargs != NULL);
+
+		arg->result = 1;
+		subarg = arg->value.subargs;
+		while (subarg != NULL) {
+			if (subarg->result == 0)
+				search_arg_foreach(subarg, func, user_data);
+
+			if (subarg->result == -1) {
+				/* failed */
+				arg->result = -1;
+				break;
+			}
+
+			if (subarg->result == 0)
+				arg->result = 0;
+
+			subarg = subarg->next;
+		}
+	} else if (arg->type == SEARCH_OR) {
+		/* OR-list of conditions */
+		i_assert(arg->value.subargs != NULL);
+
+		subarg = arg->value.subargs;
+		arg->result = -1;
+		while (subarg != NULL) {
+			if (subarg->result == 0)
+				search_arg_foreach(subarg, func, user_data);
+
+			if (subarg->result == 1) {
+				/* matched */
+				arg->result = 1;
+				break;
+			}
+
+			if (subarg->result == 0)
+				arg->result = 0;
+
+			subarg = subarg->next;
+		}
+	} else {
+		/* just a single condition */
+		func(arg, user_data);
+	}
+}
+
+int mail_search_args_foreach(MailSearchArg *args, MailSearchForeachFunc func,
+			     void *user_data)
+{
+	int result;
+
+	result = 1;
+	for (; args != NULL; args = args->next) {
+		search_arg_foreach(args, func, user_data);
+
+		if (args->result == -1) {
+			/* failed, abort */
+			return -1;
+		}
+
+		if (args->result == 0)
+			result = 0;
+	}
+
+	return result;
+}
+
+static void search_arg_analyze(MailSearchArg *arg, int *have_headers,
+			       int *have_body, int *have_text)
+{
+	MailSearchArg *subarg;
+
+	if (arg->result != 0)
+		return;
+
+	switch (arg->type) {
+	case SEARCH_OR:
+	case SEARCH_SUB:
+		subarg = arg->value.subargs;
+		while (subarg != NULL) {
+			if (subarg->result == 0) {
+				search_arg_analyze(subarg, have_headers,
+						   have_body, have_text);
+			}
+
+			subarg = subarg->next;
+		}
+		break;
+	case SEARCH_FROM:
+	case SEARCH_TO:
+	case SEARCH_CC:
+	case SEARCH_BCC:
+	case SEARCH_SUBJECT:
+	case SEARCH_HEADER:
+		*have_headers = TRUE;
+		break;
+	case SEARCH_BODY:
+		*have_body = TRUE;
+		break;
+	case SEARCH_TEXT:
+		*have_text = TRUE;
+		break;
+	default:
+		break;
+	}
+}
+
+void mail_search_args_analyze(MailSearchArg *args, int *have_headers,
+			      int *have_body, int *have_text)
+{
+	*have_headers = *have_body = *have_text = FALSE;
+
+	for (; args != NULL; args = args->next)
+		search_arg_analyze(args, have_headers, have_body, have_text);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/mail-search.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,84 @@
+#ifndef __MAIL_SEARCH_H
+#define __MAIL_SEARCH_H
+
+#include "imap-parser.h"
+#include "mail-storage.h"
+
+typedef enum {
+	SEARCH_OR,
+	SEARCH_SUB,
+
+	/* message sets */
+	SEARCH_ALL,
+	SEARCH_SET,
+	SEARCH_UID,
+
+	/* flags */
+	SEARCH_ANSWERED,
+	SEARCH_DELETED,
+	SEARCH_DRAFT,
+	SEARCH_FLAGGED,
+	SEARCH_SEEN,
+	SEARCH_RECENT,
+	SEARCH_KEYWORD,
+
+	/* dates */
+	SEARCH_BEFORE,
+	SEARCH_ON,
+	SEARCH_SINCE,
+	SEARCH_SENTBEFORE,
+	SEARCH_SENTON,
+	SEARCH_SENTSINCE,
+
+	/* sizes */
+	SEARCH_SMALLER,
+	SEARCH_LARGER,
+
+	/* headers */
+	SEARCH_FROM,
+	SEARCH_TO,
+	SEARCH_CC,
+	SEARCH_BCC,
+	SEARCH_SUBJECT,
+	SEARCH_HEADER,
+
+	/* body */
+	SEARCH_BODY,
+	SEARCH_TEXT
+} MailSearchArgType;
+
+struct _MailSearchArg {
+	MailSearchArg *next;
+
+	MailSearchArgType type;
+	union {
+		MailSearchArg *subargs;
+		const char *str;
+	} value;
+
+	const char *hdr_value; /* for SEARCH_HEADER */
+	unsigned int not:1;
+
+	int result;
+};
+
+typedef void (*MailSearchForeachFunc)(MailSearchArg *arg, void *user_data);
+
+/* Builds search arguments based on IMAP arguments. */
+MailSearchArg *mail_search_args_build(Pool pool, ImapArg *args, int args_count,
+				      const char **error);
+
+/* Reset the results in search arguments */
+void mail_search_args_reset(MailSearchArg *args);
+
+/* goes through arguments in list that don't have a result yet.
+   Returns 1 = search matched, -1 = search unmatched, 0 = don't know yet */
+int mail_search_args_foreach(MailSearchArg *args, MailSearchForeachFunc func,
+			     void *user_data);
+
+/* Fills have_headers, have_body and have_text based on if such search
+   argument exists that needs to be checked. */
+void mail_search_args_analyze(MailSearchArg *args, int *have_headers,
+			      int *have_body, int *have_text);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/mail-storage.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,166 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "mail-storage.h"
+
+#include <ctype.h>
+
+typedef struct _MailStorageList MailStorageList;
+
+struct _MailStorageList {
+	MailStorageList *next;
+	MailStorage *storage;
+};
+
+static MailStorageList *storages = NULL;
+
+void mail_storage_class_register(MailStorage *storage_class)
+{
+	MailStorageList *list, **pos;
+
+	list = i_new(MailStorageList, 1);
+	list->storage = storage_class;
+
+	/* append it after the list, so the autodetection order is correct */
+	pos = &storages;
+	while (*pos != NULL)
+		pos = &(*pos)->next;
+	*pos = list;
+}
+
+void mail_storage_class_unregister(MailStorage *storage_class)
+{
+	MailStorageList **list, *next;
+
+	for (list = &storages; *list != NULL; list = &(*list)->next) {
+		if ((*list)->storage == storage_class) {
+			next = (*list)->next;
+
+			(*list)->storage->free((*list)->storage);
+			i_free(*list);
+
+			*list = next;
+		}
+	}
+}
+
+MailStorage *mail_storage_create(const char *name, const char *data)
+{
+	MailStorageList *list;
+
+	i_assert(name != NULL);
+
+	for (list = storages; list != NULL; list = list->next) {
+		if (strcasecmp(list->storage->name, name) == 0)
+			return list->storage->create(data);
+	}
+
+	return NULL;
+}
+
+MailStorage *mail_storage_create_default(void)
+{
+	MailStorageList *list;
+	MailStorage *storage;
+
+	for (list = storages; list != NULL; list = list->next) {
+		storage = list->storage->create(NULL);
+		if (storage != NULL)
+			return storage;
+	}
+
+	return NULL;
+}
+
+static MailStorage *mail_storage_autodetect(const char *data)
+{
+	MailStorageList *list;
+
+	for (list = storages; list != NULL; list = list->next) {
+		if (list->storage->autodetect(data))
+			return list->storage;
+	}
+
+	return NULL;
+}
+
+MailStorage *mail_storage_create_with_data(const char *data)
+{
+	MailStorage *storage;
+	const char *p, *name;
+
+	if (data == NULL || *data == '\0')
+		return mail_storage_create_default();
+
+	/* check if we're in the form of mailformat:data
+	   (eg. maildir:Maildir) */
+	p = data;
+	while (i_isalnum(*p)) p++;
+
+	if (*p == ':') {
+		name = t_strndup(data, (unsigned int) (p-data));
+		storage = mail_storage_create(name, p+1);
+	} else {
+		storage = mail_storage_autodetect(data);
+		if (storage != NULL)
+			storage = storage->create(data);
+	}
+
+	return storage;
+}
+
+void mail_storage_destroy(MailStorage *storage)
+{
+	i_assert(storage != NULL);
+
+	i_free(storage->dir);
+	i_free(storage);
+}
+
+void mail_storage_clear_error(MailStorage *storage)
+{
+	i_free(storage->error);
+	storage->error = NULL;
+}
+
+void mail_storage_set_error(MailStorage *storage, const char *fmt, ...)
+{
+	va_list va;
+
+	i_free(storage->error);
+
+	if (fmt == NULL)
+		storage->error = NULL;
+	else {
+		va_start(va, fmt);
+		storage->error = i_strdup_vprintf(fmt, va);
+		va_end(va);
+	}
+}
+
+void mail_storage_set_critical(MailStorage *storage, const char *fmt, ...)
+{
+	va_list va;
+
+	i_free(storage->error);
+
+	if (fmt == NULL)
+		storage->error = NULL;
+	else {
+		va_start(va, fmt);
+		storage->error = i_strdup_vprintf(fmt, va);
+		va_end(va);
+
+		i_error("%s", storage->error);
+	}
+}
+
+const char *mail_storage_get_last_error(MailStorage *storage)
+{
+	return storage->error;
+}
+
+int mail_storage_is_inconsistency_error(Mailbox *box)
+{
+	return box->inconsistent;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/mail-storage.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,243 @@
+#ifndef __MAIL_STORAGE_H
+#define __MAIL_STORAGE_H
+
+#include "imap-util.h"
+#include "imap-parser.h"
+
+typedef enum {
+	MAILBOX_NOSELECT	= 0x01,
+	MAILBOX_CHILDREN	= 0x02,
+	MAILBOX_NOCHILDREN	= 0x04,
+	MAILBOX_NOINFERIORS	= 0x08,
+	MAILBOX_MARKED		= 0x10,
+	MAILBOX_UNMARKED	= 0x20,
+
+	MAILBOX_READONLY	= 0x40
+} MailboxFlags;
+
+typedef enum {
+	STATUS_MESSAGES		= 0x01,
+	STATUS_RECENT		= 0x02,
+	STATUS_UIDNEXT		= 0x04,
+	STATUS_UIDVALIDITY	= 0x08,
+	STATUS_UNSEEN		= 0x10,
+	STATUS_FIRST_UNSEEN_SEQ	= 0x20
+} MailboxStatusItems;
+
+typedef enum {
+	MAILBOX_NAME_EXISTS,
+	MAILBOX_NAME_VALID,
+	MAILBOX_NAME_INVALID
+} MailboxNameStatus;
+
+typedef enum {
+	MODIFY_ADD,
+	MODIFY_REMOVE,
+	MODIFY_REPLACE
+} ModifyType;
+
+typedef struct _MailStorage MailStorage;
+typedef struct _Mailbox Mailbox;
+typedef struct _MailboxStatus MailboxStatus;
+typedef struct _MailFetchData MailFetchData;
+typedef struct _MailFetchBodyData MailFetchBodyData;
+typedef struct _MailSearchArg MailSearchArg;
+
+typedef void (*MailboxFunc)(MailStorage *storage, const char *name,
+			    MailboxFlags flags, void *user_data);
+
+typedef void (*MailExpungeFunc)(Mailbox *mailbox, unsigned int seq,
+				unsigned int uid, void *user_data);
+typedef void (*MailFlagUpdateFunc)(Mailbox *mailbox, unsigned int seq,
+				   unsigned int uid, MailFlags flags,
+				   const char *custom_flags[],
+				   void *user_data);
+
+/* All methods returning int return either TRUE or FALSE. */
+struct _MailStorage {
+	char *name;
+
+	char hierarchy_sep;
+
+	/* Create new instance */
+	MailStorage *(*create)(const char *data);
+
+	/* Free this instance */
+	void (*free)(MailStorage *storage);
+
+	/* Returns TRUE if this storage would accept the given data
+	   as a valid parameter to create(). */
+	int (*autodetect)(const char *data);
+
+	/* Open a mailbox. If readonly is TRUE, mailbox must not be
+	   modified in any way even when it's asked. */
+	Mailbox *(*open_mailbox)(MailStorage *storage, const char *name,
+				 int readonly);
+
+	/* name is allowed to contain multiple new hierarchy levels */
+	int (*create_mailbox)(MailStorage *storage, const char *name);
+	int (*delete_mailbox)(MailStorage *storage, const char *name);
+	/* If the name has inferior hierarchical names, then the inferior
+	   hierarchical names MUST also be renamed (ie. foo -> bar renames
+	   also foo/bar -> bar/bar).
+
+	   If oldname is case-insensitively "INBOX", the mails are moved
+	   into new folder but the INBOX folder must not be deleted. */
+	int (*rename_mailbox)(MailStorage *storage, const char *oldname,
+			      const char *newname);
+
+	/* Execute specified function for all mailboxes matching given
+	   mask. The mask is in RFC2060 LIST format. */
+	int (*find_mailboxes)(MailStorage *storage, const char *mask,
+			      MailboxFunc func, void *user_data);
+
+	/* Subscribe/unsubscribe mailbox. There should be no error when
+	   subscribing to already subscribed mailbox. Subscribing to
+	   unexisting mailboxes is optional. */
+	int (*set_subscribed)(MailStorage *storage, const char *name, int set);
+
+	/* Exactly like find_mailboxes(), but list only subscribed mailboxes. */
+	int (*find_subscribed)(MailStorage *storage, const char *mask,
+			       MailboxFunc func, void *user_data);
+
+	/* Returns mailbox name status */
+	int (*get_mailbox_name_status)(MailStorage *storage, const char *name,
+				       MailboxNameStatus *status);
+
+	/* Returns the error message of last occured error. */
+	const char *(*get_last_error)(MailStorage *storage);
+
+/* private: */
+	char *dir; /* root directory */
+	char *error;
+};
+
+struct _Mailbox {
+	char *name;
+
+	MailStorage *storage;
+
+	/* Close the box */
+	void (*close)(Mailbox *box);
+
+	/* Gets the mailbox status information. */
+	int (*get_status)(Mailbox *box, MailboxStatusItems items,
+			  MailboxStatus *status);
+
+	/* Synchronize the mailbox by reading all expunges and flag changes.
+	   If new mail has been added to mailbox, messages contains the total
+	   number of messages in mailbox, otherwise 0. functions may be NULL.
+
+	   If expunge is TRUE, deleted mails are expunged as well. Difference
+	   to expunge() function is that expunge_func is also called. */
+	int (*sync)(Mailbox *box, unsigned int *messages, int expunge,
+		    MailExpungeFunc expunge_func, MailFlagUpdateFunc flag_func,
+		    void *user_data);
+
+	/* Expunge all mails with \Deleted flag. */
+	int (*expunge)(Mailbox *box);
+
+	/* Update mail flags. func may be NULL. */
+	int (*update_flags)(Mailbox *box, const char *messageset, int uidset,
+			    MailFlags flags, const char *custom_flags[],
+			    ModifyType modify_type,
+			    MailFlagUpdateFunc func, void *user_data,
+			    int *all_found);
+
+	/* Copy mails to another mailbox */
+	int (*copy)(Mailbox *box, Mailbox *destbox,
+		    const char *messageset, int uidset);
+
+	/* Fetch wanted mail data. The results are written into outbuf
+	   in RFC2060 FETCH format. */
+	int (*fetch)(Mailbox *box, MailFetchData *fetch_data,
+		     IOBuffer *outbuf, int *all_found);
+
+	/* Search wanted mail data. args contains the search criteria.
+	   results are written into outbuf in RFC2060 SEARCH format. */
+	int (*search)(Mailbox *box, MailSearchArg *args,
+		      IOBuffer *outbuf, int uid_result);
+
+	/* Save a new mail into mailbox. */
+	int (*save)(Mailbox *box, MailFlags flags, const char *custom_flags[],
+		    time_t internal_date, IOBuffer *data, size_t data_size);
+
+	/* Returns TRUE if mailbox is now in inconsistent state, meaning that
+	   the message IDs etc. may have changed - only way to recover this
+	   would be to fully close the mailbox and reopen it. With IMAP
+	   connection this would mean a forced disconnection since we can't
+	   do forced CLOSE. */
+	int (*is_inconsistency_error)(Mailbox *box);
+
+/* private: */
+	unsigned int readonly:1;
+	unsigned int inconsistent:1;
+};
+
+struct _MailboxStatus {
+	unsigned int messages;
+	unsigned int recent;
+	unsigned int unseen;
+
+	unsigned int uidvalidity;
+	unsigned int uidnext;
+
+	unsigned int first_unseen_seq;
+};
+
+struct _MailFetchData {
+	const char *messageset;
+	unsigned int uidset:1;
+
+	unsigned int body:1;
+	unsigned int bodystructure:1;
+	unsigned int envelope:1;
+	unsigned int flags:1;
+	unsigned int internaldate:1;
+	unsigned int rfc822:1;
+	unsigned int rfc822_header:1;
+	unsigned int rfc822_size:1;
+	unsigned int rfc822_text:1;
+	unsigned int uid:1;
+
+	MailFetchBodyData *body_sections;
+};
+
+struct _MailFetchBodyData {
+	MailFetchBodyData *next;
+
+	const char *section; /* NOTE: always uppercased */
+	off_t skip;
+	size_t max_size;
+	unsigned int skip_set:1;
+	unsigned int peek:1;
+};
+
+/* register all mail storages */
+void mail_storage_register_all(void);
+
+/* Register mail storage class with given name - all methods that are NULL
+   are set to default methods */
+void mail_storage_class_register(MailStorage *storage_class);
+void mail_storage_class_unregister(MailStorage *storage_class);
+
+/* Create a new instance of registered mail storage class with given
+   storage-specific data. If data is NULL, it tries to use defaults.
+   May return NULL if anything fails. */
+MailStorage *mail_storage_create(const char *name, const char *data);
+void mail_storage_destroy(MailStorage *storage);
+
+MailStorage *mail_storage_create_default(void);
+MailStorage *mail_storage_create_with_data(const char *data);
+
+/* Set error message in storage. Critical errors are logged with syslog() */
+void mail_storage_clear_error(MailStorage *storage);
+void mail_storage_set_error(MailStorage *storage, const char *fmt, ...)
+	__attr_format__(2, 3);
+void mail_storage_set_critical(MailStorage *storage, const char *fmt, ...)
+	__attr_format__(2, 3);
+
+const char *mail_storage_get_last_error(MailStorage *storage);
+int mail_storage_is_inconsistency_error(Mailbox *box);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/subscription-file/.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-storage/subscription-file/Makefile.am	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,13 @@
+noinst_LIBRARIES = libstorage_subscription_file.a
+
+INCLUDES = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-storage \
+	-I$(top_srcdir)/src/lib-imap
+
+libstorage_subscription_file_a_SOURCES = \
+	subscription-file.c
+
+noinst_HEADERS = \
+	subscription-file.h
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/subscription-file/subscription-file.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,219 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+/* ugly code here - text files are annoying to manage */
+
+#include "lib.h"
+#include "mmap-util.h"
+#include "imap-match.h"
+#include "mail-storage.h"
+#include "subscription-file.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#define SUBSCRIPTION_FILE_NAME ".subscriptions"
+
+static int lock_file(int fd, int type)
+{
+	struct flock fl;
+
+	/* lock whole file */
+	fl.l_type = type;
+	fl.l_whence = SEEK_SET;
+	fl.l_start = 0;
+	fl.l_len = 0;
+
+	while (fcntl(fd, F_SETLKW, &fl) == -1) {
+		if (errno != EINTR)
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+static int subscription_open(MailStorage *storage, int update,
+			     const char **path, void **mmap_base,
+			     size_t *mmap_length)
+{
+	int fd;
+
+	*path = t_strconcat(storage->dir, "/" SUBSCRIPTION_FILE_NAME, NULL);
+
+	fd = update ? open(*path, O_RDWR | O_CREAT, 0660) :
+		open(*path, O_RDONLY);
+	if (fd == -1) {
+		if (update || errno != ENOENT) {
+			mail_storage_set_critical(storage, "Can't open "
+						  "subscription file %s: %m",
+						  *path);
+		}
+		return -1;
+	}
+
+	if (!lock_file(fd, update ? F_WRLCK : F_RDLCK)) {
+		mail_storage_set_critical(storage, "fcntl() failed for "
+					  "subscription file %s: %m", *path);
+		(void)close(fd);
+		return -1;
+	}
+
+	*mmap_base = update ? mmap_rw_file(fd, mmap_length) :
+		mmap_ro_file(fd, mmap_length);
+	if (*mmap_base == MAP_FAILED) {
+		*mmap_base = NULL;
+		mail_storage_set_critical(storage, "mmap() failed for "
+					  "subscription file %s: %m", *path);
+		(void)close(fd);
+		return -1;
+	}
+
+	(void)madvise(*mmap_base, *mmap_length, MADV_SEQUENTIAL);
+	return fd;
+}
+
+static int subscription_append(MailStorage *storage, int fd, const char *name,
+			       unsigned int len, int prefix_lf,
+			       const char *path)
+{
+	char *buf;
+
+	if (lseek(fd, 0, SEEK_END) == (off_t)-1) {
+		mail_storage_set_critical(storage, "lseek() failed for "
+					  "subscription file %s: %m", path);
+		return FALSE;
+	}
+
+	buf = t_buffer_get(len+2);
+	buf[0] = '\n';
+	memcpy(buf+1, name, len);
+	buf[len+1] = '\n';
+
+	if (prefix_lf)
+		len += 2;
+	else {
+		buf++;
+		len++;
+	}
+
+	if ((size_t) write(fd, buf, len) != len) {
+		mail_storage_set_critical(storage, "write() failed for "
+					  "subscription file %s: %m", path);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+int subsfile_set_subscribed(MailStorage *storage, const char *name, int set)
+{
+	void *mmap_base;
+	size_t mmap_length;
+	const char *path;
+	char *subscriptions, *end, *p;
+	unsigned int namelen, afterlen, removelen;
+	int fd,  failed, prefix_lf;
+
+	if (strcasecmp(name, "INBOX") == 0)
+		name = "INBOX";
+
+	fd = subscription_open(storage, TRUE, &path, &mmap_base, &mmap_length);
+	if (fd == -1)
+		return FALSE;
+
+	namelen = strlen(name);
+
+	subscriptions = mmap_base;
+	if (subscriptions == NULL)
+		p = NULL;
+	else {
+		end = subscriptions + mmap_length;
+		for (p = subscriptions; p != end; p++) {
+			if (*p == *name && p+namelen <= end &&
+			    strncmp(p, name, namelen) == 0) {
+				/* make sure beginning and end matches too */
+				if ((p == subscriptions || p[-1] == '\n') &&
+				    (p+namelen == end || p[namelen] == '\n'))
+					break;
+			}
+		}
+
+		if (p == end)
+			p = NULL;
+	}
+
+	failed = FALSE;
+	if (p != NULL && !set) {
+		/* remove it */
+		afterlen = mmap_length - (unsigned int) (p - subscriptions);
+		removelen = namelen < afterlen ? namelen+1 : namelen;
+
+		if (removelen < afterlen)
+			memmove(p, p+removelen, afterlen-removelen);
+
+		if (ftruncate(fd, (off_t) (mmap_length - removelen)) == -1) {
+			mail_storage_set_critical(storage, "ftruncate() "
+						  "failed for subscription "
+						  "file %s: %m", path);
+			failed = TRUE;
+		}
+	} else if (p == NULL && set) {
+		/* append it */
+		prefix_lf = mmap_length > 0 &&
+			subscriptions[mmap_length-1] != '\n';
+		if (!subscription_append(storage, fd, name, namelen,
+					 prefix_lf, path))
+			failed = TRUE;
+	}
+
+	if (mmap_base != NULL && munmap(mmap_base, mmap_length) == -1) {
+		mail_storage_set_critical(storage, "munmap() failed for "
+					  "subscription file %s: %m", path);
+		failed = TRUE;
+	}
+
+	if (close(fd) == -1) {
+		mail_storage_set_critical(storage, "close() failed for "
+					  "subscription file %s: %m", path);
+		failed = TRUE;
+	}
+	return !failed;
+}
+
+int subsfile_foreach(MailStorage *storage, const char *mask,
+		     SubsFileForeachFunc func, void *user_data)
+{
+        const ImapMatchGlob *glob;
+	const char *path, *start, *end, *p, *line;
+	void *mmap_base;
+	size_t mmap_length;
+	int fd, ret;
+
+	fd = subscription_open(storage, FALSE, &path, &mmap_base, &mmap_length);
+	if (fd == -1)
+		return -1;
+
+	glob = imap_match_init(mask, TRUE, storage->hierarchy_sep);
+
+	start = mmap_base; end = start + mmap_length; ret = 1;
+	while (ret) {
+		t_push();
+
+		for (p = start; p != end; p++) {
+			if (*p == '\n')
+				break;
+		}
+
+		line = t_strndup(start, (unsigned int) (p-start));
+		if (line != NULL && *line != '\0' &&
+		    imap_match(glob, line, 0, NULL) >= 0)
+			ret = func(storage, line, user_data);
+		t_pop();
+
+		if (p == end)
+			break;
+		start = p+1;
+	}
+
+	(void)close(fd);
+	return ret;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/subscription-file/subscription-file.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,16 @@
+#ifndef __SUBSCRIPTION_FILE_H
+#define __SUBSCRIPTION_FILE_H
+
+#include "mail-storage.h"
+
+/* Returns FALSE if foreach should be aborted */
+typedef int (*SubsFileForeachFunc)(MailStorage *storage, const char *name,
+				   void *user_data);
+
+int subsfile_set_subscribed(MailStorage *storage, const char *name, int set);
+
+/* Returns -1 if error, 0 if foreach function returned FALSE or 1 if all ok */
+int subsfile_foreach(MailStorage *storage, const char *mask,
+		     SubsFileForeachFunc func, void *user_data);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/.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/Makefile.am	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,73 @@
+noinst_LIBRARIES = liblib.a
+
+if IOLOOP_POLL
+ioloop_source = ioloop-poll.c
+else
+ioloop_source = ioloop-select.c
+endif
+
+ioloop_sources = \
+	ioloop-poll.c \
+	ioloop-select.c
+
+liblib_a_SOURCES = \
+	base64.c \
+	compat.c \
+	failures.c \
+	fdpass.c \
+	gmtoff.c \
+	hash.c \
+	hex-binary.c \
+	hostpid.c \
+	imem.c \
+	iobuffer.c \
+	ioloop.c \
+	$(ioloop_source) \
+	lib.c \
+	lib-signals.c \
+	md5.c \
+	mempool.c \
+	mempool-alloconly.c \
+	mempool-system.c \
+	mmap-util.c \
+	network.c \
+	primes.c \
+	randgen.c \
+	restrict-access.c \
+	strfuncs.c \
+	temp-mempool.c \
+	temp-string.c \
+	unlink-directory.c \
+	unlink-lockfiles.c
+
+noinst_HEADERS = \
+	base64.h \
+	compat.h \
+	failures.h \
+	fdpass.h \
+	gmtoff.h \
+	hash.h \
+	hex-binary.h \
+	hostpid.h \
+	imem.h \
+	iobuffer.h \
+	ioloop.h \
+	ioloop-internal.h \
+	lib.h \
+	lib-signals.h \
+	macros.h \
+	md5.h \
+	mempool.h \
+	mmap-util.h \
+	network.h \
+	primes.h \
+	randgen.h \
+	restrict-access.h \
+	strfuncs.h \
+	temp-mempool.h \
+	temp-string.h \
+	unlink-directory.h \
+	unlink-lockfiles.h
+
+EXTRA_DIST = \
+	$(ioloop_sources)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/base64.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,145 @@
+/* Based on the sources of Cyrus IMAP:
+ *
+ * Copyright (c) 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.
+ */
+
+#include "lib.h"
+#include "base64.h"
+
+static const char basis_64[] =
+   "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+const char *base64_encode(const unsigned char *data, unsigned int size)
+{
+	char *buffer, *p;
+	int c1, c2, c3;
+
+	buffer = p = t_malloc(size*2 + 5);
+	while (size > 0) {
+		c1 = *data++; size--;
+		*p++ = basis_64[c1 >> 2];
+
+		c2 = size == 0 ? 0 : *data++;
+		*p++ = basis_64[((c1 & 0x03) << 4) | ((c2 & 0xf0) >> 4)];
+		if (size-- == 0) {
+			*p++ = '=';
+			*p++ = '=';
+			break;
+		}
+
+		c3 = size == 0 ? 0 : *data++;
+		*p++ = basis_64[((c2 & 0x0f) << 2) | ((c3 & 0xc0) >> 6)];
+		if (size-- == 0) {
+			*p++ = '=';
+			break;
+		}
+
+		*p++ = basis_64[c3 & 0x3f];
+	}
+
+	*p = '\0';
+	return buffer;
+}
+
+#define XX 127
+
+/* Table for decoding base64 */
+static const char index_64[256] = {
+    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
+    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
+    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,62, XX,XX,XX,63,
+    52,53,54,55, 56,57,58,59, 60,61,XX,XX, XX,XX,XX,XX,
+    XX, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
+    15,16,17,18, 19,20,21,22, 23,24,25,XX, XX,XX,XX,XX,
+    XX,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
+    41,42,43,44, 45,46,47,48, 49,50,51,XX, XX,XX,XX,XX,
+    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
+    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
+    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
+    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
+    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
+    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
+    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
+    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
+};
+#define CHAR64(c)  (index_64[(int)(unsigned char)(c)])
+
+int base64_decode(char *data)
+{
+	char *p, *start;
+	int c1, c2, c3, c4;
+
+	p = start = data;
+	while (*data != '\0') {
+		c1 = *data++;
+
+		if (CHAR64(c1) == XX)
+			return -1;
+
+		c2 = *data++;
+		if (CHAR64(c2) == XX)
+			return -1;
+
+		c3 = *data++;
+		if (c3 != '=' && CHAR64(c3) == XX)
+			return -1;
+
+		c4 = *data++;
+		if (c4 != '=' && CHAR64(c4) == XX)
+			return -1;
+
+		*p++ = ((CHAR64(c1) << 2) | ((CHAR64(c2) & 0x30) >> 4));
+
+		if (c3 == '=') {
+			if (*data != '\0' || c4 != '=')
+				return -1;
+			break;
+		}
+
+		*p++ = (((CHAR64(c2) & 0xf) << 4) | ((CHAR64(c3) & 0x3c) >> 2));
+		if (c4 == '=') {
+			if (*data != '\0')
+				return -1;
+			break;
+		}
+		*p++ = (((CHAR64(c3) & 0x3) << 6) | CHAR64(c4));
+	}
+
+	return (int) (p-start);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/base64.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,11 @@
+#ifndef __BASE64_H
+#define __BASE64_H
+
+/* Translates binary data into base64. Allocates memory from temporary pool. */
+const char *base64_encode(const unsigned char *data, unsigned int size);
+
+/* Translates base64 data into binary modifying the data itself.
+   Returns size of the binary data, or -1 if error occured. */
+int base64_decode(char *data);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/compat.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,122 @@
+/*
+ compat.c : Compatibility functions for OSes not having them
+
+    Copyright (c) 2002 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <ctype.h>
+#include <syslog.h>
+
+#include "lib.h"
+
+#ifndef INADDR_NONE
+#  define INADDR_NONE INADDR_BROADCAST
+#endif
+
+#ifndef HAVE_MEMMOVE
+void *my_memmove(void *dest, const void *src, size_t size)
+{
+	char *destp = dest;
+        const char *srcp = src;
+
+	if (destp < srcp) {
+		/* dest = 1234, src=234 */
+		destp = dest;
+                srcp = src;
+		while (size > 0) {
+			*destp++ = *srcp++;
+                        size--;
+		}
+	} else if (destp > srcp) {
+		/* dest = 234, src=123 */
+		destp += size-1;
+                srcp += size-1;
+		while (size > 0) {
+			*destp-- = *srcp--;
+                        size--;
+		}
+	}
+
+        return dest;
+}
+#endif
+
+#if !defined (HAVE_STRCASECMP) && !defined (HAVE_STRICMP)
+int my_strcasecmp(const char *s1, const char *s2)
+{
+	while (*s1 != '\0' && i_toupper(*s1) == i_toupper(*s2)) {
+		s1++; s2++;
+	}
+
+        return i_toupper(*s1) - i_toupper(*s2);
+}
+
+int my_strncasecmp(const char *s1, const char *s2, size_t max_chars)
+{
+	while (max_chars > 0 && *s1 != '\0' &&
+	       i_toupper(*s1) == i_toupper(*s2)) {
+		s1++; s2++;
+	}
+
+        return i_toupper(*s1) - i_toupper(*s2);
+}
+#endif
+
+#ifndef HAVE_INET_ATON
+int my_inet_aton(const char *cp, struct in_addr *inp)
+{
+	in_addr_t addr;
+
+	addr = inet_addr(cp);
+	if (addr == INADDR_NONE)
+		return 0;
+
+	inp->s_addr = addr;
+        return 1;
+}
+#endif
+
+#ifndef HAVE_VSYSLOG
+void my_vsyslog(int priority, const char *format, va_list args)
+{
+	const char *str;
+	char buf[1024];
+
+#ifdef HAVE_VSNPRINTF
+	vsnprintf(buf, sizeof(buf), format, args);
+	str = buf;
+#else
+        va_list args2;
+
+	VA_COPY(args2, args);
+
+	if (printf_string_upper_bound(format, args) < sizeof(buf)) {
+		vsprintf(buf, format, args);
+		str = buf;
+	} else {
+		/* this may not be safe but not choice really.. */
+		str = t_strdup_vprintf(format, args2);
+	}
+#endif
+	syslog(priority, "%s", str);
+}
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/compat.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,54 @@
+#ifndef __COMPAT_H
+#define __COMPAT_H
+
+/* memmove() */
+#ifndef HAVE_MEMMOVE
+#  define memmove my_memmove
+void *my_memmove(void *dest, const void *src, size_t n);
+#endif
+
+/* strcasecmp(), strncasecmp() */
+#ifndef HAVE_STRCASECMP
+#  ifdef HAVE_STRICMP
+#    define strcasecmp stricmp
+#    define strncasecmp strnicmp
+#  else
+#    define strcasecmp my_strcasecmp
+#    define strncasecmp my_strncasecmp
+int my_strcasecmp(const char *s1, const char *s2);
+int my_strncasecmp(const char *s1, const char *s2, size_t max_chars);
+#  endif
+#endif
+
+#ifndef HAVE_INET_ATON
+#  include <sys/socket.h>
+#  include <netinet/in.h>
+#  include <arpa/inet.h>
+#  define inet_aton my_inet_aton
+int my_inet_aton(const char *cp, struct in_addr *inp);
+#endif
+
+#ifndef HAVE_VSYSLOG
+#  define vsyslog my_vsyslog
+void my_vsyslog(int priority, const char *format, va_list args);
+#endif
+
+/* ctype.h isn't safe with signed chars,
+   use our own instead if really needed */
+#define i_toupper(x) toupper((int) (unsigned char) (x))
+#define i_tolower(x) tolower((int) (unsigned char) (x))
+#define i_isalnum(x) isalnum((int) (unsigned char) (x))
+#define i_isalpha(x) isalpha((int) (unsigned char) (x))
+#define i_isascii(x) isascii((int) (unsigned char) (x))
+#define i_isblank(x) isblank((int) (unsigned char) (x))
+#define i_iscntrl(x) iscntrl((int) (unsigned char) (x))
+#define i_isdigit(x) isdigit((int) (unsigned char) (x))
+#define i_isgraph(x) isgraph((int) (unsigned char) (x))
+#define i_islower(x) islower((int) (unsigned char) (x))
+#define i_isprint(x) isprint((int) (unsigned char) (x))
+#define i_ispunct(x) ispunct((int) (unsigned char) (x))
+#define i_isspace(x) isspace((int) (unsigned char) (x))
+#define i_isupper(x) isupper((int) (unsigned char) (x))
+#define i_isxdigit(x) isxdigit((int) (unsigned char) (x))
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/failures.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,228 @@
+/*
+ failures.c : Failure manager
+
+    Copyright (c) 2001-2002 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+
+#include "lib.h"
+#include "ioloop.h"
+
+#include <stdio.h>
+#include <syslog.h>
+#include <time.h>
+
+static FailureFunc panic_handler __attr_noreturn__, fatal_handler __attr_noreturn__;
+static FailureFunc error_handler, warning_handler;
+
+static FILE *log_fd;
+static char *log_prefix, *log_stamp_format;
+
+static void default_panic_handler(const char *format, va_list args)
+	__attr_noreturn__;
+static void default_fatal_handler(const char *format, va_list args)
+	__attr_noreturn__;
+
+static void write_prefix(void)
+{
+	struct tm *tm;
+	char str[256];
+
+	if (log_prefix != NULL)
+		fputs(log_prefix, log_fd);
+
+	if (log_stamp_format != NULL) {
+		tm = localtime(&ioloop_time);
+
+		if (strftime(str, sizeof(str), log_stamp_format, tm) > 0)
+			fputs(str, log_fd);
+	}
+}
+
+static void default_panic_handler(const char *format, va_list args)
+{
+	write_prefix();
+
+	fputs("Panic: ", log_fd);
+	vfprintf(log_fd, format, args);
+	fputc('\n', log_fd);
+
+	abort();
+}
+
+static void default_fatal_handler(const char *format, va_list args)
+{
+	write_prefix();
+
+	fputs("Fatal: ", log_fd);
+	vfprintf(log_fd, format, args);
+	fputc('\n', log_fd);
+
+	exit(98);
+}
+
+static void default_error_handler(const char *format, va_list args)
+{
+	write_prefix();
+
+	fputs("Error: ", log_fd);
+	vfprintf(log_fd, format, args);
+        fputc('\n', log_fd);
+
+	fflush(log_fd);
+}
+
+static void default_warning_handler(const char *format, va_list args)
+{
+	write_prefix();
+
+	fputs("Warning: ", log_fd);
+	vfprintf(log_fd, format, args);
+        fputc('\n', log_fd);
+
+	fflush(log_fd);
+}
+
+void i_panic(const char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	panic_handler(format, args);
+	va_end(args);
+}
+
+void i_fatal(const char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	fatal_handler(format, args);
+	va_end(args);
+}
+
+void i_error(const char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	error_handler(format, args);
+	va_end(args);
+}
+
+void i_warning(const char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	warning_handler(format, args);
+	va_end(args);
+}
+
+void i_set_panic_handler(FailureFunc func __attr_noreturn__)
+{
+	if (func == NULL)
+		func = default_panic_handler;
+        panic_handler = func;
+}
+
+void i_set_fatal_handler(FailureFunc func __attr_noreturn__)
+{
+	if (func == NULL)
+		func = default_fatal_handler;
+        fatal_handler = func;
+}
+
+void i_set_error_handler(FailureFunc func)
+{
+	if (func == NULL)
+		func = default_error_handler;
+        error_handler = func;
+}
+
+void i_set_warning_handler(FailureFunc func)
+{
+	if (func == NULL)
+		func = default_warning_handler;
+        warning_handler = func;
+}
+
+void i_syslog_panic_handler(const char *fmt, va_list args)
+{
+	vsyslog(LOG_CRIT, fmt, args);
+        abort();
+}
+
+void i_syslog_fatal_handler(const char *fmt, va_list args)
+{
+	vsyslog(LOG_CRIT, fmt, args);
+	exit(98);
+}
+
+void i_syslog_error_handler(const char *fmt, va_list args)
+{
+	vsyslog(LOG_ERR, fmt, args);
+}
+
+void i_syslog_warning_handler(const char *fmt, va_list args)
+{
+	vsyslog(LOG_WARNING, fmt, args);
+}
+
+void i_set_failure_file(const char *path, const char *prefix)
+{
+	if (log_fd != stderr)
+		(void)fclose(log_fd);
+
+	log_fd = fopen(path, "a");
+	if (log_fd == NULL)
+		i_fatal("Can't open log file %s: %m", path);
+
+	i_free(log_prefix);
+	log_prefix = i_strconcat(prefix, ": ", NULL);
+}
+
+void i_set_failure_timestamp_format(const char *fmt)
+{
+	i_free(log_stamp_format);
+        log_stamp_format = i_strdup(fmt);
+}
+
+void failures_init(void)
+{
+	log_fd = stderr;
+	log_prefix = NULL;
+        log_stamp_format = NULL;
+
+        i_set_panic_handler(NULL);
+        i_set_fatal_handler(NULL);
+        i_set_error_handler(NULL);
+        i_set_warning_handler(NULL);
+}
+
+void failures_deinit(void)
+{
+	if (log_fd != stderr) {
+		(void)fclose(log_fd);
+		log_fd = stderr;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/failures.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,33 @@
+#ifndef __FAILURES_H
+#define __FAILURES_H
+
+#define DEFAULT_FAILURE_STAMP_FORMAT "%b %d %H:%M:%S "
+
+typedef void (*FailureFunc) (const char *, va_list);
+
+void i_panic(const char *format, ...) __attr_format__(1, 2) __attr_noreturn__;
+void i_fatal(const char *format, ...) __attr_format__(1, 2) __attr_noreturn__;
+void i_error(const char *format, ...) __attr_format__(1, 2);
+void i_warning(const char *format, ...) __attr_format__(1, 2);
+
+void i_set_panic_handler(FailureFunc func __attr_noreturn__);
+void i_set_fatal_handler(FailureFunc func __attr_noreturn__);
+void i_set_error_handler(FailureFunc func);
+void i_set_warning_handler(FailureFunc func);
+
+/* send failures to syslog() */
+void i_syslog_panic_handler(const char *fmt, va_list args) __attr_noreturn__;
+void i_syslog_fatal_handler(const char *fmt, va_list args) __attr_noreturn__;
+void i_syslog_error_handler(const char *fmt, va_list args);
+void i_syslog_warning_handler(const char *fmt, va_list args);
+
+/* send failures to specified log file instead of stderr. */
+void i_set_failure_file(const char *path, const char *prefix);
+
+/* prefix failures with a timestamp. fmt is in strftime() format. */
+void i_set_failure_timestamp_format(const char *fmt);
+
+void failures_init(void);
+void failures_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/fdpass.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,95 @@
+/*
+ fdpass.c - FD passing, based on an example by
+
+    Copyright (c) 2002 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifdef __sun__
+#  define _XPG4_2
+#endif
+
+#include "lib.h"
+#include "network.h"
+#include "fdpass.h"
+
+#include <sys/un.h>
+#include <sys/uio.h>
+
+#if defined (__sun__) && !defined(CMSG_SPACE)
+#  define CMSG_ALIGN(len) \
+	(((len) + sizeof(size_t) - 1) & ~(sizeof(size_t) - 1))
+#  define CMSG_SPACE(len) \
+	(CMSG_ALIGN(len) + CMSG_ALIGN(sizeof(struct cmsghdr)))
+#  define CMSG_LEN(len) \
+	(CMSG_ALIGN(sizeof(struct cmsghdr)) + (len))
+#endif
+
+int fd_send(int handle, int send_fd, const void *data, int size)
+{
+        struct msghdr msg;
+        struct iovec iov;
+        struct cmsghdr *cmsg;
+	int *fdptr;
+        char buf[CMSG_SPACE(sizeof(int))];
+
+	memset(&msg, 0, sizeof (struct msghdr));
+
+        iov.iov_base = (void *) data;
+        iov.iov_len = size;
+        msg.msg_control = buf;
+        msg.msg_controllen = sizeof(buf);
+        msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+
+        cmsg = CMSG_FIRSTHDR(&msg);
+        cmsg->cmsg_level = SOL_SOCKET;
+        cmsg->cmsg_type = SCM_RIGHTS;
+	cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+	fdptr = (int *) CMSG_DATA(cmsg);
+	*fdptr = send_fd;
+	return sendmsg(handle, &msg, 0);
+}
+
+int fd_read(int handle, void *data, int size, int *fd)
+{
+	struct msghdr msg;
+	struct iovec iov;
+	struct cmsghdr *cmsg;
+	int ret;
+	char buf[CMSG_SPACE(sizeof(int))];
+
+	memset(&msg, 0, sizeof (struct msghdr));
+
+	msg.msg_control = buf;
+	msg.msg_controllen = sizeof(buf);
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+	cmsg = CMSG_FIRSTHDR(&msg);
+	cmsg->cmsg_level = SOL_SOCKET;
+	cmsg->cmsg_type = SCM_RIGHTS;
+	iov.iov_base = (void *) data;
+	iov.iov_len = size;
+
+	ret = recvmsg(handle, &msg, 0);
+	*fd = *(int *) CMSG_DATA(cmsg);
+	return ret;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/fdpass.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,11 @@
+#ifndef __FDPASS_H
+#define __FDPASS_H
+
+/* Returns number of bytes sent, -1 if error. */
+int fd_send(int handle, int send_fd, const void *data, int size);
+
+/* Returns number of bytes read, or -1 if error. fd is set only
+   if return value is larger than 0. */
+int fd_read(int handle, void *data, int size, int *fd);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/gmtoff.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,61 @@
+/*
+ compat.c : Compatibility functions for OSes not having them
+
+    Copyright (c) 2002 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include "lib.h"
+#include "gmtoff.h"
+
+#include <time.h>
+
+#undef HAVE_TM_GMTOFF
+
+int gmtoff(struct tm *tm, time_t t __attr_unused__)
+{
+#ifdef HAVE_TM_GMTOFF
+	return tm->tm_gmtoff;
+#else
+	struct tm ltm, gtm;
+	int offset;
+
+	/* gmtime() overwrites tm, so we need to copy it elsewhere */
+	ltm = *tm;
+	tm = gmtime(&t);
+	gtm = *tm;
+
+	/* max offset of 24 hours */
+	if (ltm.tm_yday < gtm.tm_yday)
+		offset = -24 * 3600;
+	else if (ltm.tm_yday > gtm.tm_yday)
+		offset = 24 * 3600;
+	else
+		offset = 0;
+
+	offset += (ltm.tm_hour - gtm.tm_hour) * 3600;
+	offset += (ltm.tm_min - gtm.tm_min) * 60;
+
+	/* restore overwritten tm */
+	*tm = ltm;
+	return offset;
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/gmtoff.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,9 @@
+#ifndef __GMTOFF_H
+#define __GMTOFF_H
+
+#include <time.h>
+
+/* Returns GMT offset in seconds. */
+int gmtoff(struct tm *tm, time_t t);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/hash.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,412 @@
+/* GLIB - Library of useful routines for C programming
+ * Copyright (C) 1995-1997  Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library 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.
+ */
+
+/*
+ * Modified by the GLib Team and others 1997-1999.  See the AUTHORS
+ * file for a list of people on the GLib Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GLib at ftp://ftp.gtk.org/pub/gtk/. 
+ */
+
+/* several modifications Copyright (C) 2002 by Timo Sirainen */
+
+#include <ctype.h>
+
+#include "lib.h"
+#include "hash.h"
+#include "primes.h"
+
+#define HASH_TABLE_MIN_SIZE 11
+#define HASH_TABLE_MAX_SIZE 13845163
+
+typedef struct _HashNode {
+	void *key;
+	void *value;
+
+	int destroyed;
+	struct _HashNode *next;
+} HashNode;
+
+struct _HashTable {
+	Pool pool;
+
+	unsigned int size;
+	unsigned int nodes_count, nodes_destroyed;
+	int frozen;
+	HashNode **nodes;
+
+	HashFunc hash_func;
+	HashCompareFunc key_compare_func;
+};
+
+static void hash_cleanup(HashTable *table);
+static int hash_resize(HashTable *table);
+
+static int foreach_stop;
+
+static unsigned int direct_hash(const void *p)
+{
+	/* NOTE: may truncate the value, but that doesn't matter. */
+	return POINTER_TO_UINT(p);
+}
+
+static HashNode *hash_node_create(Pool pool, const void *key,
+				  const void *value)
+{
+	HashNode *node;
+
+        node = p_new(pool, HashNode, 1);
+	node->key = (void *) key;
+	node->value = (void *) value;
+
+	return node;
+}
+
+static void hash_nodes_destroy(HashTable *table, HashNode *node)
+{
+	HashNode *next;
+
+	while (node != NULL) {
+		next = node->next;
+                p_free(table->pool, node);
+                node = next;
+	}
+}
+
+HashTable *hash_create(Pool pool, unsigned int initial_size,
+		       HashFunc hash_func, HashCompareFunc key_compare_func)
+{
+	HashTable *table;
+
+        i_assert(pool != NULL);
+
+	table = p_new(pool, HashTable, 1);
+        table->pool = pool;
+	table->size = CLAMP(primes_closest(initial_size),
+			    HASH_TABLE_MIN_SIZE,
+			    HASH_TABLE_MAX_SIZE);
+
+	table->hash_func = hash_func != NULL ? hash_func : direct_hash;
+	table->key_compare_func = key_compare_func;
+	table->nodes = p_new(pool, HashNode *, table->size);
+
+	return table;
+}
+
+void hash_destroy(HashTable *table)
+{
+	unsigned int i;
+
+	if (table == NULL)
+                return;
+
+	for (i = 0; i < table->size; i++)
+		hash_nodes_destroy(table, table->nodes[i]);
+
+	p_free(table->pool, table->nodes);
+	p_free(table->pool, table);
+}
+
+void hash_clear(HashTable *table)
+{
+	unsigned int i;
+
+	i_assert(table != NULL);
+
+	for (i = 0; i < table->size; i++) {
+		hash_nodes_destroy(table, table->nodes[i]);
+		table->nodes[i] = NULL;
+	}
+}
+
+static inline HashNode **
+hash_lookup_node(HashTable *table, const void *key)
+{
+	HashNode **node;
+
+	node = &table->nodes[table->hash_func(key) % table->size];
+
+	/* Hash table lookup needs to be fast.
+	   We therefore remove the extra conditional of testing
+	   whether to call the key_compare_func or not from
+	   the inner loop. */
+	if (table->key_compare_func) {
+		while (*node != NULL) {
+                        if (!(*node)->destroyed &&
+			    table->key_compare_func((*node)->key, key) == 0)
+                                break;
+			node = &(*node)->next;
+		}
+	} else {
+		while (*node != NULL && (*node)->key != key)
+			node = &(*node)->next;
+	}
+
+	return node;
+}
+
+void *hash_lookup(HashTable *table, const void *key)
+{
+	HashNode *node;
+
+	i_assert(table != NULL);
+
+	node = *hash_lookup_node(table, key);
+	return node != NULL && !node->destroyed ? node->value : NULL;
+}
+
+int hash_lookup_full(HashTable *table, const void *lookup_key,
+		     void **orig_key, void **value)
+{
+	HashNode *node;
+
+	i_assert(table != NULL);
+
+	node = *hash_lookup_node(table, lookup_key);
+	if (node == NULL || node->destroyed)
+		return FALSE;
+
+	if (orig_key != NULL)
+		*orig_key = node->key;
+	if (value != NULL)
+		*value = node->value;
+	return TRUE;
+}
+
+static void hash_insert_full(HashTable *table, const void *key,
+			     const void *value, int replace_key)
+{
+	HashNode **node;
+
+	i_assert(table != NULL);
+
+	node = hash_lookup_node(table, key);
+	if (*node == NULL) {
+		*node = hash_node_create(table->pool, key, value);
+
+		table->nodes_count++;
+		if (!table->frozen)
+			hash_resize(table);
+	} else {
+		if (replace_key || (*node)->destroyed) {
+			(*node)->key = (void *) key;
+			(*node)->destroyed = FALSE;
+		}
+
+		(*node)->value = (void *) value;
+	}
+}
+
+void hash_insert(HashTable *table, const void *key, const void *value)
+{
+	hash_insert_full(table, key, value, TRUE);
+}
+
+void hash_update(HashTable *table, const void *key, const void *value)
+{
+	hash_insert_full(table, key, value, FALSE);
+}
+
+void hash_remove(HashTable *table, const void *key)
+{
+	HashNode **node, *old_node;
+
+	i_assert(table != NULL);
+
+	node = hash_lookup_node(table, key);
+	if (*node != NULL && !(*node)->destroyed) {
+		table->nodes_count--;
+
+		if (table->frozen) {
+			(*node)->destroyed = TRUE;
+                        table->nodes_destroyed++;
+		} else {
+			old_node = *node;
+			*node = old_node->next;
+			p_free(table->pool, old_node);
+
+			hash_resize(table);
+		}
+	}
+}
+
+void hash_freeze(HashTable *table)
+{
+	i_assert(table != NULL);
+
+	table->frozen++;
+}
+
+void hash_thaw(HashTable *table)
+{
+	i_assert(table != NULL);
+	i_assert(table->frozen > 0);
+
+	if (--table->frozen == 0)
+                hash_cleanup(table);
+}
+
+void hash_foreach(HashTable *table, HashForeachFunc func, void *user_data)
+{
+	HashNode *node;
+	unsigned int i;
+
+	i_assert(table != NULL);
+	i_assert(func != NULL);
+
+	hash_freeze(table);
+
+        foreach_stop = FALSE;
+	for (i = 0; i < table->size; i++) {
+		for (node = table->nodes[i]; node; node = node->next) {
+			if (!node->destroyed) {
+				func(node->key, node->value, user_data);
+
+				if (foreach_stop) {
+					foreach_stop = FALSE;
+					hash_thaw(table);
+                                        return;
+				}
+			}
+		}
+	}
+        hash_thaw(table);
+}
+
+void hash_foreach_stop(void)
+{
+        foreach_stop = TRUE;
+}
+
+/* Returns the number of elements contained in the hash table. */
+unsigned int hash_size(HashTable *table)
+{
+	i_assert(table != NULL);
+
+	return table->nodes_count;
+}
+
+static int hash_resize(HashTable *table)
+{
+        HashFunc hash_func;
+	HashNode *node, *next, **new_nodes;
+	float nodes_per_list;
+	unsigned int hash_val, new_size, i;
+
+	nodes_per_list = (float) table->nodes_count / (float) table->size;
+	if ((nodes_per_list > 0.3 || table->size <= HASH_TABLE_MIN_SIZE) &&
+	    (nodes_per_list < 3.0 || table->size >= HASH_TABLE_MAX_SIZE))
+		return FALSE;
+
+	new_size = CLAMP(primes_closest(table->nodes_count),
+			 HASH_TABLE_MIN_SIZE,
+			 HASH_TABLE_MAX_SIZE);
+
+	new_nodes = p_new(table->pool, HashNode *, new_size);
+
+        hash_func = table->hash_func;
+	for (i = 0; i < table->size; i++) {
+		for (node = table->nodes[i]; node != NULL; node = next) {
+			next = node->next;
+
+			if (node->destroyed) {
+                                p_free(table->pool, node);
+			} else {
+				hash_val = hash_func(node->key) % new_size;
+
+				node->next = new_nodes[hash_val];
+				new_nodes[hash_val] = node;
+			}
+		}
+	}
+
+	p_free(table->pool, table->nodes);
+	table->nodes = new_nodes;
+	table->size = new_size;
+	table->nodes_destroyed = 0;
+        return TRUE;
+}
+
+static void hash_cleanup(HashTable *table)
+{
+	HashNode **node, **next, *old_node;
+        unsigned int i;
+
+	if (hash_resize(table))
+		return;
+
+	if (table->nodes_destroyed == 0)
+                return;
+
+        /* find the destroyed nodes from hash table and remove them */
+	for (i = 0; i < table->size; i++) {
+		for (node = &table->nodes[i]; *node != NULL; node = next) {
+			next = &(*node)->next;
+
+			if ((*node)->destroyed) {
+                                old_node = *node;
+				*node = *next;
+				p_free(table->pool, old_node);
+
+				/* next points to free'd memory area now,
+				   fix it */
+				next = node;
+
+				if (--table->nodes_destroyed == 0)
+                                        return;
+			}
+		}
+	}
+}
+
+/* a char* hash function from ASU -- from glib */
+unsigned int str_hash(const void *p)
+{
+        const unsigned char *s = p;
+	unsigned int g, h = 0;
+
+	while (*s != '\0') {
+		h = (h << 4) + *s;
+		if ((g = h & 0xf0000000UL)) {
+			h = h ^ (g >> 24);
+			h = h ^ g;
+		}
+		s++;
+	}
+
+	return h;
+}
+
+/* a char* hash function from ASU -- from glib */
+unsigned int strcase_hash(const void *p)
+{
+        const unsigned char *s = p;
+	unsigned int g, h = 0;
+
+	while (*s != '\0') {
+		h = (h << 4) + i_toupper(*s);
+		if ((g = h & 0xf0000000UL)) {
+			h = h ^ (g >> 24);
+			h = h ^ g;
+		}
+		s++;
+	}
+
+	return h;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/hash.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,55 @@
+#ifndef __HASH_H
+#define __HASH_H
+
+/* Returns hash code. */
+typedef unsigned int (*HashFunc) (const void *p);
+/* Returns 0 if the pointers are equal. */
+typedef int (*HashCompareFunc) (const void *p1, const void *p2);
+typedef void (*HashForeachFunc) (void *key, void *value, void *user_data);
+
+typedef struct _HashTable HashTable;
+
+/* Create a new hash table. If initial_size is 0, the default value is used.
+   If hash_func or key_compare_func is NULL, direct hashing/comparing
+   is used. */
+HashTable *hash_create(Pool pool, unsigned int initial_size,
+		       HashFunc hash_func, HashCompareFunc key_compare_func);
+void hash_destroy(HashTable *table);
+
+#ifdef POOL_CHECK_LEAKS
+#  define hash_destroy_clean(table) hash_destroy(table)
+#else
+#  define hash_destroy_clean(table)
+#endif
+
+void hash_clear(HashTable *table);
+
+void *hash_lookup(HashTable *table, const void *key);
+int hash_lookup_full(HashTable *table, const void *lookup_key,
+		     void **orig_key, void **value);
+
+/* Insert/update node in hash table. The difference is that hash_insert()
+   replaces the key in table to given one, while hash_update() doesnt. */
+void hash_insert(HashTable *table, const void *key, const void *value);
+void hash_update(HashTable *table, const void *key, const void *value);
+
+void hash_remove(HashTable *table, const void *key);
+unsigned int hash_size(HashTable *table);
+
+/* Calls the given function for each node in hash table. You may safely
+   call hash_*() functions inside your function, but if you add any
+   new nodes, they may or may not be called for in this foreach loop. */
+void hash_foreach(HashTable *table, HashForeachFunc func, void *user_data);
+/* Stop the active hash_foreach() loop */
+void hash_foreach_stop(void);
+
+/* Hash table isn't resized, and removed nodes aren't removed from
+   the list while hash table is freezed. Supports nesting. */
+void hash_freeze(HashTable *table);
+void hash_thaw(HashTable *table);
+
+/* hash function for strings */
+unsigned int str_hash(const void *p);
+unsigned int strcase_hash(const void *p);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/hex-binary.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,77 @@
+/*
+ hex-binary.c : hex translations
+
+    Copyright (c) 2002 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include "lib.h"
+#include "hex-binary.h"
+
+const char *binary_to_hex(const unsigned char *data, unsigned int size)
+{
+	unsigned int i, value;
+	char *buf, *p;
+
+	buf = p = t_malloc(size * 2 + 1);
+	for (i = 0; i < size; i++) {
+		value = data[i] >> 4;
+		*p++ = value < 10 ? value + '0' : value - 10 + 'a';
+
+		value = data[i] & 0x0f;
+		*p++ = value < 10 ? value + '0' : value - 10 + 'a';
+	}
+
+	*p = '\0';
+	return buf;
+}
+
+int hex_to_binary(const char *data, unsigned char *dest)
+{
+	int size, value;
+
+	size = 0;
+	while (*data != '\0') {
+		if (*data >= '0' && *data <= '9')
+			value = (*data - '0') << 4;
+		else if (*data >= 'a' && *data <= 'f')
+			value = (*data - 'a' + 10) << 4;
+		else if (*data >= 'A' && *data <= 'F')
+			value = (*data - 'A' + 10) << 4;
+		else
+			return -1;
+
+		data++;
+		if (*data >= '0' && *data <= '9')
+			value |= *data - '0';
+		else if (*data >= 'a' && *data <= 'f')
+			value |= *data - 'a' + 10;
+		else if (*data >= 'A' && *data <= 'F')
+			value |= *data - 'A' + 10;
+		else
+			return -1;
+
+		dest[size++] = value;
+		data++;
+	}
+
+	return size;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/hex-binary.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,13 @@
+#ifndef __HEX_BINARY_H
+#define __HEX_BINARY_H
+
+/* Convert binary to lowercased hex digits allocating return value from
+   temporary memory pool */
+const char *binary_to_hex(const unsigned char *data, unsigned int size);
+
+/* Convert hex to binary. data and dest may point to same value.
+   Returns TRUE if successful. Returns number of bytes writte to dest,
+   or -1 if error occured. */
+int hex_to_binary(const char *data, unsigned char *dest);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/hostpid.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,50 @@
+/*
+ hostpid.c : Easy way to get our own hostname and pid
+
+    Copyright (c) 2002 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include "lib.h"
+#include "hostpid.h"
+
+#include <unistd.h>
+
+const char *my_hostname = NULL;
+const char *my_pid = NULL;
+
+void hostpid_init(void)
+{
+	static char hostname[256], pid[100];
+
+	if (my_hostname == NULL) {
+		hostname[sizeof(hostname)-1] = '\0';
+		if (gethostname(hostname, sizeof(hostname)-1) == -1)
+			strcpy(hostname, "unknown");
+
+		my_hostname = hostname;
+	}
+
+	if (my_pid == NULL) {
+		i_snprintf(pid, sizeof(pid), "%lu", (unsigned long) getpid());
+		my_pid = pid;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/hostpid.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,12 @@
+#ifndef __HOSTPID_H
+#define __HOSTPID_H
+
+extern const char *my_hostname;
+extern const char *my_pid;
+
+/* Initializes my_hostname and my_pid. Done only once, so it's safe and
+   fast to call this function multiple times. */
+void hostpid_init(void);
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/imem.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,107 @@
+/*
+ imem.c : Wrappers for allocating memory from default memory pool
+
+    Copyright (c) 2002 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include "lib.h"
+
+Pool default_pool;
+
+void *i_malloc(unsigned int size)
+{
+        return p_malloc(default_pool, size);
+}
+
+void i_free(void *mem)
+{
+        p_free(default_pool, mem);
+}
+
+void *i_realloc(void *mem, unsigned int size)
+{
+        return p_realloc(default_pool, mem, size);
+}
+
+char *i_strdup(const char *str)
+{
+        return p_strdup(default_pool, str);
+}
+
+char *i_strdup_empty(const char *str)
+{
+        return p_strdup_empty(default_pool, str);
+}
+
+char *i_strndup(const char *str, unsigned int max_chars)
+{
+        return p_strndup(default_pool, str, max_chars);
+}
+
+char *i_strdup_printf(const char *format, ...)
+{
+	va_list args;
+        char *ret;
+
+        va_start(args, format);
+	ret = p_strdup_vprintf(default_pool, format, args);
+	va_end(args);
+        return ret;
+}
+
+char *i_strdup_vprintf(const char *format, va_list args)
+{
+        return p_strdup_vprintf(default_pool, format, args);
+}
+
+void i_strdup_replace(char **dest, const char *str)
+{
+	p_free(default_pool, *dest);
+        *dest = p_strdup(default_pool, str);
+}
+
+char *i_strconcat(const char *str1, ...)
+{
+	va_list args;
+        const char *temp;
+	char *ret;
+        unsigned int len;
+
+	va_start(args, str1);
+
+	temp = temp_strconcat(str1, args, &len);
+        ret = p_malloc(default_pool, len);
+        memcpy(ret, temp, len);
+
+	va_end(args);
+        return ret;
+}
+
+void imem_init(void)
+{
+	default_pool = pool_create("Default pool", 4096, TRUE);
+}
+
+void imem_deinit(void)
+{
+        pool_unref(default_pool);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/imem.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,26 @@
+#ifndef __IMEM_H
+#define __IMEM_H
+
+extern Pool default_pool;
+
+/* For easy allocation of memory from default memory pool. */
+#define i_new(type, count) \
+        ((type *) i_malloc((unsigned) sizeof(type) * (count)))
+void *i_malloc(unsigned int size);
+void i_free(void *mem);
+void *i_realloc(void *mem, unsigned int size);
+
+/* string functions */
+char *i_strdup(const char *str);
+char *i_strdup_empty(const char *str); /* like i_strdup(), but if str == "", return NULL */
+char *i_strndup(const char *str, unsigned int max_chars);
+char *i_strdup_printf(const char *format, ...) __attr_format__(1, 2);
+char *i_strdup_vprintf(const char *format, va_list args);
+void i_strdup_replace(char **dest, const char *str);
+
+char *i_strconcat(const char *str1, ...); /* NULL terminated */
+
+void imem_init(void);
+void imem_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/iobuffer.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,659 @@
+/*
+   iobuffer.c : Input/output transmit buffer handling
+
+    Copyright (c) 2002 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include "lib.h"
+#include "ioloop.h"
+#include "iobuffer.h"
+#include "network.h"
+
+#include <unistd.h>
+
+#ifdef HAVE_SYS_SENDFILE_H
+#  include <sys/sendfile.h>
+#endif
+
+IOBuffer *io_buffer_create(int fd, Pool pool, int priority,
+			   unsigned int max_size)
+{
+	IOBuffer *buf;
+
+        i_assert(fd >= 0);
+        i_assert(pool != NULL);
+
+	buf = p_new(pool, IOBuffer, 1);
+	buf->fd = fd;
+	buf->pool = pool;
+	buf->priority = priority;
+	buf->max_size = max_size;
+	return buf;
+}
+
+IOBuffer *io_buffer_create_file(int fd, Pool pool, unsigned int max_size)
+{
+	IOBuffer *buf;
+
+	buf = io_buffer_create(fd, pool, IO_PRIORITY_DEFAULT, max_size);
+	buf->file = TRUE;
+        return buf;
+}
+
+void io_buffer_destroy(IOBuffer *buf)
+{
+	if (buf == NULL)
+		return;
+
+        if (buf->io != NULL)
+		io_remove(buf->io);
+        p_free(buf->pool, buf->buffer);
+        p_free(buf->pool, buf);
+}
+
+void io_buffer_close(IOBuffer *buf)
+{
+	if (buf == NULL)
+		return;
+
+        buf->closed = TRUE;
+}
+
+void io_buffer_reset(IOBuffer *buf)
+{
+	buf->pos = buf->skip = buf->cr_lookup_pos = 0;
+        buf->last_cr = FALSE;
+}
+
+IOBuffer *io_buffer_set_pool(IOBuffer *buf, Pool pool)
+{
+	IOBuffer *newbuf;
+
+        i_assert(buf != NULL);
+        i_assert(pool != NULL);
+
+	newbuf = p_new(pool, IOBuffer, 1);
+	memcpy(newbuf, buf, sizeof(IOBuffer));
+
+	newbuf->pool = pool;
+	newbuf->buffer = p_malloc(pool, buf->size);
+	memcpy(newbuf->buffer, buf->buffer + buf->skip,
+	       buf->size - buf->skip);
+
+	newbuf->cr_lookup_pos -= newbuf->skip;
+        newbuf->pos -= newbuf->skip;
+	newbuf->skip = 0;
+
+        p_free(buf->pool, buf->buffer);
+        p_free(buf->pool, buf);
+        return newbuf;
+}
+
+void io_buffer_set_max_size(IOBuffer *buf, unsigned int max_size)
+{
+        buf->max_size = max_size;
+}
+
+void io_buffer_set_send_blocking(IOBuffer *buf, unsigned int max_size,
+				 int timeout_msecs, TimeoutFunc timeout_func,
+				 void *user_data)
+{
+	i_assert(!buf->receive);
+
+	buf->transmit = TRUE;
+	buf->timeout_msecs = timeout_msecs;
+	buf->timeout_func = timeout_func;
+	buf->timeout_user_data = user_data;
+	buf->blocking = max_size > 0;
+	buf->max_size = max_size;
+}
+
+static int my_write(int fd, const void *buf, unsigned int size)
+{
+	int ret;
+
+	i_assert(size <= INT_MAX);
+
+	if (size == 0)
+		return 1;
+
+	ret = write(fd, buf, size);
+	if (ret < 0 && (errno == EINTR || errno == EAGAIN))
+		ret = 0;
+
+	return ret;
+}
+
+static void buf_send_real(IOBuffer *buf)
+{
+	int ret;
+
+	if (!buf->file) {
+		ret = net_transmit(buf->fd, buf->buffer + buf->skip,
+				   buf->pos - buf->skip);
+	} else {
+		ret = my_write(buf->fd, buf->buffer + buf->skip,
+			       buf->pos - buf->skip);
+	}
+
+	if (ret < 0) {
+		buf->closed = TRUE;
+	} else {
+		buf->transfd += ret;
+		buf->skip += ret;
+		if (buf->skip == buf->pos) {
+			/* everything sent */
+			buf->skip = buf->pos = 0;
+
+			/* call flush function */
+			if (buf->flush_func != NULL) {
+				buf->flush_func(buf->flush_user_data, buf);
+				buf->flush_func = NULL;
+
+				if (buf->corked) {
+					/* remove cork */
+					net_set_cork(buf->fd, FALSE);
+					buf->corked = FALSE;
+				}
+			}
+		}
+	}
+}
+
+static int buf_send(IOBuffer *buf)
+{
+	buf_send_real(buf);
+
+	if (buf->closed || buf->pos == 0) {
+		io_remove(buf->io);
+                buf->io = NULL;
+		return FALSE;
+	}
+
+        return TRUE;
+}
+
+typedef struct {
+	IOLoop ioloop;
+	IOBuffer *buf;
+
+	const char *data;
+	unsigned int size;
+
+	int in_fd;
+	off_t offset;
+
+	int timeout;
+} IOBufferBlockData;
+
+static void block_loop_send(IOBufferBlockData *bd)
+{
+	int ret;
+
+	if (bd->buf->skip != bd->buf->pos) {
+		buf_send_real(bd->buf);
+	} else {
+		/* send the data */
+		ret = !bd->buf->file ?
+			net_transmit(bd->buf->fd, bd->data, bd->size) :
+			my_write(bd->buf->fd, bd->data, bd->size);
+
+		if (ret < 0) {
+			bd->buf->closed = TRUE;
+		} else {
+			bd->data += ret;
+			bd->size -= ret;
+		}
+	}
+
+	if (bd->buf->closed || bd->size == 0)
+		io_loop_stop(bd->ioloop);
+}
+
+static void block_loop_timeout(void *user_data, Timeout timeout __attr_unused__)
+{
+	IOBufferBlockData *data = user_data;
+
+	data->timeout = TRUE;
+	io_loop_stop(data->ioloop);
+}
+
+static int io_buffer_ioloop(IOBuffer *buf, IOBufferBlockData *bd,
+			    void (*send_func)(IOBufferBlockData *bd))
+{
+	Timeout to;
+
+	/* close old IO */
+	if (buf->io != NULL)
+		io_remove(buf->io);
+
+	/* create a new I/O loop */
+	bd->ioloop = io_loop_create();
+	bd->buf = buf;
+
+	buf->io = io_add(buf->fd, IO_WRITE, (IOFunc) send_func, bd);
+	to = buf->timeout_msecs <= 0 ? NULL :
+		timeout_add(buf->timeout_msecs, block_loop_timeout, bd);
+
+	io_loop_run(bd->ioloop);
+
+	if (buf->corked) {
+		/* remove cork */
+		net_set_cork(buf->fd, FALSE);
+		buf->corked = FALSE;
+	}
+
+	if (buf->io != NULL) {
+		io_remove(buf->io);
+		buf->io = NULL;
+	}
+
+	if (to != NULL) {
+		if (bd->timeout && buf->timeout_func != NULL) {
+			/* call user-given timeout function */
+			buf->timeout_func(buf->timeout_user_data, to);
+		}
+		timeout_remove(to);
+	}
+
+	io_loop_destroy(bd->ioloop);
+	return bd->size > 0 ? -1 : 1;
+}
+
+static int io_buffer_send_blocking(IOBuffer *buf, const void *data,
+				   unsigned int size)
+{
+        IOBufferBlockData bd;
+
+	memset(&bd, 0, sizeof(IOBufferBlockData));
+
+	bd.data = data;
+	bd.size = size;
+
+        return io_buffer_ioloop(buf, &bd, block_loop_send);
+}
+
+void io_buffer_cork(IOBuffer *buf)
+{
+	i_assert(!buf->receive);
+
+	if (!buf->file && !buf->corked) {
+		net_set_cork(buf->fd, TRUE);
+		buf->corked = TRUE;
+	}
+}
+
+static void buffer_alloc_more(IOBuffer *buf, unsigned int size)
+{
+	buf->size = buf->pos+size;
+	buf->size = buf->size <= IO_BUFFER_MIN_SIZE ? IO_BUFFER_MIN_SIZE :
+		nearest_power(buf->size);
+
+	if (buf->max_size > 0 && buf->size > buf->max_size)
+		buf->size = buf->max_size;
+
+	buf->buffer = p_realloc(buf->pool, buf->buffer, buf->size);
+	if (buf->buffer == NULL) {
+		/* pool limit exceeded */
+		buf->pos = buf->size = 0;
+	}
+}
+
+static inline void io_buffer_compress(IOBuffer *buf)
+{
+	memmove(buf->buffer, buf->buffer + buf->skip,
+		buf->pos - buf->skip);
+	buf->pos -= buf->skip;
+
+	if (buf->skip > buf->cr_lookup_pos)
+		buf->cr_lookup_pos = 0;
+	else
+		buf->cr_lookup_pos -= buf->skip;
+
+	buf->skip = 0;
+}
+
+int io_buffer_send(IOBuffer *buf, const void *data, unsigned int size)
+{
+	int ret;
+
+	i_assert(!buf->receive);
+        i_assert(data != NULL);
+	i_assert(size < INT_MAX);
+	buf->transmit = TRUE;
+
+	if (buf->closed)
+                return -1;
+
+	if (buf->pos == 0) {
+		/* buffer is empty, try to send the data immediately */
+		ret = buf->file ? my_write(buf->fd, data, size) :
+			net_transmit(buf->fd, data, size);
+		if (ret < 0) {
+			/* disconnected */
+			buf->closed = TRUE;
+			return -1;
+		}
+
+		buf->transfd += ret;
+		data = (const char *) data + ret;
+                size -= ret;
+	}
+
+	if (size == 0)
+		return 1;
+
+	if (io_buffer_get_space(buf, size) == NULL) {
+		if (buf->blocking) {
+			/* if we don't have space, we block */
+			return io_buffer_send_blocking(buf, data, size);
+		}
+		return -2;
+	}
+
+	/* add to buffer */
+	memcpy(buf->buffer + buf->pos, data, size);
+	buf->pos += size;
+
+	if (buf->io == NULL) {
+		buf->io = io_add_priority(buf->fd, buf->priority, IO_WRITE,
+					  (IOFunc) buf_send, buf);
+	}
+        return 1;
+}
+
+#ifdef HAVE_SYS_SENDFILE_H
+static void block_loop_sendfile(IOBufferBlockData *bd)
+{
+	int ret;
+
+	ret = sendfile(bd->buf->fd, bd->in_fd, &bd->offset, bd->size);
+	if (ret < 0) {
+		if (errno != EINTR && errno != EAGAIN)
+			bd->buf->closed = TRUE;
+		ret = 0;
+	}
+
+	bd->size -= ret;
+	if (bd->buf->closed || bd->size == 0)
+		io_loop_stop(bd->ioloop);
+}
+#endif
+
+int io_buffer_send_file(IOBuffer *buf, int fd, off_t offset,
+			const void *data, unsigned int size)
+{
+#ifdef HAVE_SYS_SENDFILE_H
+        IOBufferBlockData bd;
+	int ret;
+#endif
+
+	i_assert(fd >= 0);
+	i_assert(data != NULL);
+	i_assert(size < INT_MAX);
+
+#ifdef HAVE_SYS_SENDFILE_H
+	io_buffer_send_flush(buf);
+
+	/* first try if we can do it with a single sendfile() call */
+	ret = sendfile(buf->fd, fd, &offset, size);
+	if (ret < 0) {
+		if (errno != EINTR && errno != EAGAIN)
+			return -1;
+		ret = 0;
+	}
+
+	if ((unsigned int) ret == size)
+		return 1;
+
+	if (buf->blocking) {
+		memset(&bd, 0, sizeof(IOBufferBlockData));
+
+		bd.in_fd = fd;
+		bd.offset = offset + ret;
+		bd.size = size - ret;
+
+		return io_buffer_ioloop(buf, &bd, block_loop_sendfile);
+	} else {
+		data = (char *) data + ret;
+		size -= ret;
+	}
+#endif
+	return io_buffer_send(buf, data, size);
+}
+
+void io_buffer_send_flush(IOBuffer *buf)
+{
+	i_assert(!buf->receive);
+
+	if (buf->closed || buf->io == NULL)
+                return;
+
+	if (buf->skip != buf->pos)
+		io_buffer_send_blocking(buf, NULL, 0);
+}
+
+void io_buffer_send_flush_callback(IOBuffer *buf, IOBufferFlushFunc func,
+				   void *user_data)
+{
+	i_assert(!buf->receive);
+
+	if (buf->skip == buf->pos) {
+		func(user_data, buf);
+		return;
+	}
+
+	buf->flush_func = func;
+	buf->flush_user_data = user_data;
+}
+
+int io_buffer_read_max(IOBuffer *buf, unsigned int size)
+{
+	int ret;
+
+	i_assert(size <= INT_MAX || size == UINT_MAX);
+	i_assert(!buf->transmit);
+	buf->receive = TRUE;
+
+	if (buf->closed)
+                return -1;
+
+	if (buf->pos == buf->size) {
+		if (buf->skip > 0) {
+			/* remove the unused bytes from beginning of buffer */
+                        io_buffer_compress(buf);
+		} else if (buf->max_size == 0 || buf->size < buf->max_size) {
+			/* buffer is full - grow it */
+			buffer_alloc_more(buf, IO_BUFFER_MIN_SIZE);
+		}
+
+		if (buf->pos == buf->size)
+                        return -2; /* buffer full */
+	}
+
+        /* fill the buffer */
+	if (size == UINT_MAX || buf->size-buf->pos < size)
+		size = buf->size - buf->pos;
+
+	if (!buf->file) {
+		ret = net_receive(buf->fd, buf->buffer + buf->pos,
+				  buf->size - buf->pos);
+	} else {
+                ret = read(buf->fd, buf->buffer + buf->pos,
+			   buf->size - buf->pos);
+		if (ret == 0)
+			ret = -1; /* EOF */
+		else if (ret < 0 && (errno == EINTR || errno == EAGAIN))
+                        ret = 0;
+	}
+
+	if (ret < 0) {
+		/* disconnected */
+                return -1;
+	}
+
+        buf->transfd += ret;
+	buf->pos += ret;
+        return ret;
+}
+
+int io_buffer_read(IOBuffer *buf)
+{
+        return io_buffer_read_max(buf, UINT_MAX);
+}
+
+/* skip the first LF, if it exists */
+static inline void io_buffer_skip_lf(IOBuffer *buf)
+{
+	if (!buf->last_cr || buf->skip >= buf->pos)
+		return;
+
+	if (buf->buffer[buf->skip] == 10) {
+		if (buf->skip == buf->cr_lookup_pos)
+			buf->cr_lookup_pos++;
+		buf->skip++;
+	}
+	buf->last_cr = FALSE;
+}
+
+char *io_buffer_next_line(IOBuffer *buf)
+{
+	unsigned char *ret_buf;
+        unsigned int i;
+
+        i_assert(buf != NULL);
+
+	io_buffer_skip_lf(buf);
+	if (buf->skip >= buf->pos)
+		return NULL;
+
+	ret_buf = NULL;
+	for (i = buf->cr_lookup_pos; i < buf->pos; i++) {
+		if (buf->buffer[i] == 13 || buf->buffer[i] == 10) {
+			/* got it */
+                        buf->last_cr = buf->buffer[i] == 13;
+			buf->buffer[i] = '\0';
+			ret_buf = buf->buffer + buf->skip;
+
+			i++;
+			buf->skip = i;
+                        break;
+		}
+	}
+
+	buf->cr_lookup_pos = i;
+        return (char *) ret_buf;
+}
+
+unsigned char *io_buffer_get_data(IOBuffer *buf, unsigned int *size)
+{
+	io_buffer_skip_lf(buf);
+
+	if (buf->skip >= buf->pos) {
+		*size = 0;
+		return NULL;
+	}
+
+        *size = buf->pos - buf->skip;
+        return buf->buffer + buf->skip;
+}
+
+unsigned char *io_buffer_get_space(IOBuffer *buf, unsigned int size)
+{
+	i_assert(size <= INT_MAX);
+	i_assert(!buf->receive);
+	buf->transmit = TRUE;
+
+	/* make sure we have enough space in buffer */
+	if (buf->size - buf->pos < size && buf->skip > 0) {
+		/* remove the unused bytes from beginning of buffer */
+		io_buffer_compress(buf);
+	}
+
+	if (buf->size - buf->pos < size &&
+	    (buf->max_size == 0 || size <= buf->max_size - buf->pos)) {
+		/* allocate more space */
+                buffer_alloc_more(buf, size);
+	}
+
+	if (buf->size - buf->pos < size)
+                return NULL;
+
+        return buf->buffer + buf->pos;
+}
+
+int io_buffer_send_buffer(IOBuffer *buf, unsigned int size)
+{
+	int ret;
+
+	i_assert(size <= INT_MAX);
+	i_assert(!buf->receive);
+
+	if (buf->pos == 0) {
+		/* buffer is empty, try to send the data immediately */
+		ret = buf->file ? my_write(buf->fd, buf->buffer, size) :
+			net_transmit(buf->fd, buf->buffer, size);
+		if (ret < 0) {
+			/* disconnected */
+                        buf->closed = TRUE;
+			return -1;
+		}
+
+		buf->transfd += ret;
+		if ((unsigned int) ret == size) {
+                        /* all sent */
+			return 1;
+		}
+
+		buf->skip += ret;
+	}
+
+	buf->pos += size;
+
+	if (buf->io == NULL) {
+		buf->io = io_add_priority(buf->fd, buf->priority, IO_WRITE,
+					  (IOFunc) buf_send, buf);
+	}
+
+        return 1;
+}
+
+int io_buffer_set_data(IOBuffer *buf, const void *data, unsigned int size)
+{
+	io_buffer_reset(buf);
+
+	if (buf->size < size) {
+		buffer_alloc_more(buf, size);
+		if (buf->size < size)
+                        return -2;
+	}
+
+	memcpy(buf->buffer, data, size);
+	buf->pos = size;
+        buf->transfd += size;
+        return 1;
+}
+
+int io_buffer_is_empty(IOBuffer *buf)
+{
+        return buf->skip >= buf->pos;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/iobuffer.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,119 @@
+#ifndef __IOBUFFER_H
+#define __IOBUFFER_H
+
+#include "ioloop.h"
+
+#define IO_BUFFER_MIN_SIZE		512
+
+typedef void (*IOBufferFlushFunc) (void *user_data, IOBuffer *buf);
+
+struct _IOBuffer {
+	int fd;
+        IO io;
+
+	Pool pool;
+	int priority;
+
+	int timeout_msecs;
+	TimeoutFunc timeout_func;
+	void *timeout_user_data;
+
+	IOBufferFlushFunc flush_func;
+	void *flush_user_data;
+
+	unsigned char *buffer;
+
+        unsigned int cr_lookup_pos; /* used only when reading a line */
+	unsigned int pos, skip;
+	unsigned int size, max_size;
+        unsigned int transfd;
+
+	unsigned int file:1; /* reading/writing a file */
+	unsigned int closed:1; /* all further read/writes will return 0 */
+	unsigned int transmit:1; /* this is a transmit buffer */
+	unsigned int receive:1; /* this is a receive buffer */
+	unsigned int last_cr:1; /* we're expecting a LF to be skipped in
+		next call to io_buffer_next_line() */
+	unsigned int blocking:1; /* writes block if buffer is full */
+	unsigned int corked:1; /* TCP_CORK set */
+};
+
+/* Create an I/O buffer. It can be used for either sending or receiving data,
+   NEVER BOTH AT SAME TIME. If max_size is 0, there's no limit. */
+IOBuffer *io_buffer_create(int fd, Pool pool, int priority,
+			   unsigned int max_size);
+/* Same as io_buffer_create(), but specify that we're reading/writing file. */
+IOBuffer *io_buffer_create_file(int fd, Pool pool, unsigned int max_size);
+/* Read the file by mmap()ing it in blocks. max_size specifies the maximum
+   amount of data to read from the file. */
+IOBuffer *io_buffer_create_mmap(int fd, Pool pool, unsigned int block_size,
+				unsigned int max_size);
+/* Destroy a buffer. */
+void io_buffer_destroy(IOBuffer *buf);
+/* Mark the buffer closed. Any sends/reads after this will return -1.
+   The data already in buffer can be used, and the remaining output buffer
+   will be sent. */
+void io_buffer_close(IOBuffer *buf);
+/* Reset all pointers so that the buffer looks empty, the actual data is
+   not touched and can be used. */
+void io_buffer_reset(IOBuffer *buf);
+
+/* Change the memory pool used by the buffer. Data already in
+   buffer will be transferred to new buffer. */
+IOBuffer *io_buffer_set_pool(IOBuffer *buf, Pool pool);
+/* Change the maximum size for buffer to grow. */
+void io_buffer_set_max_size(IOBuffer *buf, unsigned int max_size);
+/* Change output buffer's blocking state. When buffer reaches max_size,
+   it will block until all the data has been sent or timeout has been
+   reached. Setting max_size to 0 disables this (default). Setting
+   timeout_msecs to 0 may block infinitely. */
+void io_buffer_set_send_blocking(IOBuffer *buf, unsigned int max_size,
+				 int timeout_msecs, TimeoutFunc timeout_func,
+				 void *user_data);
+
+/* Set TCP_CORK on if supported, ie. don't send out partial frames.
+   io_buffer_send_flush() removes the cork. */
+void io_buffer_cork(IOBuffer *buf);
+
+/* Returns 1 if all was ok, -1 if disconnected, -2 if buffer is full */
+int io_buffer_send(IOBuffer *buf, const void *data, unsigned int size);
+/* Send data using sendfile(), of if it's not available fallback to regular
+   sending from data-pointer. Returns 1 if all was ok, -1 if disconnected. */
+int io_buffer_send_file(IOBuffer *buf, int fd, off_t offset,
+			const void *data, unsigned int size);
+/* Flush the output buffer, blocks until all is sent. If
+   io_buffer_set_send_blocking() is called, it's timeout settings are used. */
+void io_buffer_send_flush(IOBuffer *buf);
+/* Call specified function when the whole transmit buffer has been sent.
+   If the buffer is empty already, the function will be called immediately.
+   The function will be called only once. */
+void io_buffer_send_flush_callback(IOBuffer *buf, IOBufferFlushFunc func,
+				   void *user_data);
+
+/* Returns number of bytes read if read was ok,
+   -1 if disconnected, -2 if the buffer is full */
+int io_buffer_read(IOBuffer *buf);
+/* Like io_buffer_read(), but don't read more than specified size. */
+int io_buffer_read_max(IOBuffer *buf, unsigned int size);
+/* Returns the next line from input buffer, or NULL if more data is needed
+   to make a full line. NOTE: call to io_buffer_read() invalidates the
+   returned data. */
+char *io_buffer_next_line(IOBuffer *buf);
+/* Returns pointer to beginning of data in buffer,
+   or NULL if there's no data. */
+unsigned char *io_buffer_get_data(IOBuffer *buf, unsigned int *size);
+
+/* Returns a pointer to buffer wanted amount of space,
+   or NULL if size is too big. */
+unsigned char *io_buffer_get_space(IOBuffer *buf, unsigned int size);
+/* Send data saved to buffer from io_buffer_get_space().
+   Returns -1 if disconnected. */
+int io_buffer_send_buffer(IOBuffer *buf, unsigned int size);
+
+/* Put data to buffer as if it was received.
+   Returns 1 if successful, -2 if buffer isn't big enough. */
+int io_buffer_set_data(IOBuffer *buf, const void *data, unsigned int size);
+/* Returns TRUE if there's nothing in buffer. */
+int io_buffer_is_empty(IOBuffer *buf);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/ioloop-internal.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,66 @@
+#ifndef __IOLOOP_INTERNAL_H
+#define __IOLOOP_INTERNAL_H
+
+#include "ioloop.h"
+
+#include <sys/time.h>
+
+typedef struct _IOLoopHandlerData IOLoopHandlerData;
+
+struct _IOLoop {
+        struct _IOLoop *prev;
+
+	Pool pool;
+	int highest_fd;
+
+	IO ios; /* sorted by priority */
+	Timeout timeouts; /* sorted by next_run */
+
+        IOLoopHandlerData *handler_data;
+
+	unsigned int running:1;
+};
+
+struct _IO {
+	IO prev, next;
+
+	int fd;
+        int priority;
+	int condition;
+
+	unsigned int destroyed:1;
+	unsigned int invalid:1;
+
+	IOFunc func;
+        void *user_data;
+};
+
+struct _Timeout {
+	Timeout next;
+
+	struct timeval next_run;
+        int msecs;
+	int run_now;
+        int destroyed;
+
+	TimeoutFunc func;
+        void *user_data;
+};
+
+int io_loop_get_wait_time(Timeout timeout, struct timeval *tv,
+			  struct timeval *tv_now);
+void io_loop_handle_timeouts(IOLoop ioloop);
+
+/* call only when io->destroyed is TRUE */
+void io_destroy(IOLoop ioloop, IO io);
+/* call only when timeout->destroyed is TRUE */
+void timeout_destroy(IOLoop ioloop, Timeout timeout);
+
+/* I/O handler calls */
+void io_loop_handle_add(IOLoop ioloop, int fd, int condition);
+void io_loop_handle_remove(IOLoop ioloop, int fd, int condition);
+
+void io_loop_handler_init(IOLoop ioloop);
+void io_loop_handler_deinit(IOLoop ioloop);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/ioloop-poll.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,213 @@
+/*
+ ioloop-poll.c : I/O loop handler using poll()
+
+    Copyright (c) 2002 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include "lib.h"
+#include "ioloop-internal.h"
+
+#include <sys/poll.h>
+
+#ifndef INITIAL_POLL_FDS
+#  define INITIAL_POLL_FDS 128
+#endif
+
+struct _IOLoopHandlerData {
+	unsigned int fds_size, fds_pos;
+	struct pollfd *fds;
+
+	unsigned int idx_size;
+	int *fd_index;
+};
+
+void io_loop_handler_init(IOLoop ioloop)
+{
+	IOLoopHandlerData *data;
+
+	ioloop->handler_data = data =
+		p_new(ioloop->pool, IOLoopHandlerData, 1);
+	data->fds_size = INITIAL_POLL_FDS;
+	data->fds = p_new(ioloop->pool, struct pollfd, data->fds_size);
+
+	data->idx_size = INITIAL_POLL_FDS;
+	data->fd_index = p_new(ioloop->pool, int, data->idx_size);
+        memset(data->fd_index, 0xff, sizeof(int) * data->idx_size);
+}
+
+void io_loop_handler_deinit(IOLoop ioloop)
+{
+        p_free(ioloop->pool, ioloop->handler_data->fds);
+        p_free(ioloop->pool, ioloop->handler_data->fd_index);
+        p_free(ioloop->pool, ioloop->handler_data);
+}
+
+#define IO_POLL_INPUT (POLLIN|POLLPRI|POLLERR|POLLHUP|POLLNVAL)
+#define IO_POLL_OUTPUT (POLLOUT|POLLERR|POLLHUP|POLLNVAL)
+
+void io_loop_handle_add(IOLoop ioloop, int fd, int condition)
+{
+	IOLoopHandlerData *data;
+	int index, old_size;
+
+        data = ioloop->handler_data;
+	if ((unsigned int) fd >= data->idx_size) {
+                /* grow the fd -> index array */
+		old_size = data->idx_size;
+
+		data->idx_size = nearest_power((unsigned int) fd+1);
+		data->fd_index = p_realloc(ioloop->pool, data->fd_index,
+					   sizeof(int) * data->idx_size);
+		memset(data->fd_index + old_size, 0xff,
+		       sizeof(int) * (data->idx_size-old_size));
+	}
+
+	if (data->fds_pos >= data->fds_size) {
+		/* grow the fd array */
+		data->fds_size = nearest_power(data->fds_size+1);
+		data->fds = p_realloc(ioloop->pool, data->fds,
+				      sizeof(struct pollfd) * data->fds_size);
+	}
+
+	if (data->fd_index[fd] != -1) {
+		/* update existing pollfd */
+                index = data->fd_index[fd];
+	} else {
+                /* add new pollfd */
+                index = data->fds_pos++;
+
+		data->fd_index[fd] = index;
+		data->fds[index].fd = fd;
+		data->fds[index].events = 0;
+		data->fds[index].revents = 0;
+	}
+
+        if (condition & IO_READ)
+		data->fds[index].events |= IO_POLL_INPUT;
+        if (condition & IO_WRITE)
+		data->fds[index].events |= IO_POLL_OUTPUT;
+}
+
+void io_loop_handle_remove(IOLoop ioloop, int fd, int condition)
+{
+	IOLoopHandlerData *data;
+	int index;
+
+        data = ioloop->handler_data;
+	index = data->fd_index[fd];
+	i_assert(index >= 0 && (unsigned int) index < data->fds_size);
+
+	if (condition & IO_READ)
+		data->fds[index].events &= ~(POLLIN|POLLPRI);
+        if (condition & IO_WRITE)
+		data->fds[index].events &= ~POLLOUT;
+
+	if ((data->fds[index].events & (POLLIN|POLLOUT)) == 0) {
+		/* remove the whole pollfd */
+		data->fd_index[data->fds[index].fd] = -1;
+		if (--data->fds_pos == (unsigned int) index)
+                        return; /* removing last one */
+
+                /* move the last pollfd over the removed one */
+		data->fds[index] = data->fds[data->fds_pos];
+		data->fd_index[data->fds[index].fd] = index;
+	}
+}
+
+void io_loop_handler_run(IOLoop ioloop)
+{
+	IOLoopHandlerData *data;
+        struct pollfd *pollfd;
+        struct timeval tv;
+	IO io, next;
+	int msecs, ret, t_id;
+
+        data = ioloop->handler_data;
+
+        /* get the time left for next timeout task */
+	msecs = io_loop_get_wait_time(ioloop->timeouts, &tv, NULL);
+
+	ret = poll(data->fds, data->fds_pos, msecs);
+	if (ret < 0 && errno != EINTR)
+		i_warning("poll() : %m");
+
+	/* execute timeout handlers */
+        io_loop_handle_timeouts(ioloop);
+
+	if (ret <= 0 || !ioloop->running) {
+                /* no I/O events */
+		return;
+	}
+
+	/* execute the I/O handlers in prioritized order */
+	for (io = ioloop->ios; io != NULL && ret > 0; io = next) {
+		next = io->next;
+
+		if (io->destroyed) {
+			/* we were destroyed, and io->fd points to
+			   -1 now, so we can't know if there was any
+			   revents left. */
+			io_destroy(ioloop, io);
+			continue;
+		}
+
+		i_assert(io->fd >= 0);
+
+		pollfd = &data->fds[data->fd_index[io->fd]];
+		if (pollfd->revents != 0)
+			ret--;
+
+		if (pollfd->revents == 0)
+			continue;
+
+		if (pollfd->revents & POLLNVAL) {
+			if (!io->invalid) {
+				io->invalid = TRUE;
+				i_warning("invalid I/O fd %d, func %p",
+					  io->fd, io->func);
+			}
+
+                        continue;
+		}
+
+		if ((io->condition &
+		     (IO_READ|IO_WRITE)) == (IO_READ|IO_WRITE)) {
+			pollfd->revents = 0;
+		} else if (io->condition & IO_READ) {
+			if ((pollfd->revents & IO_POLL_INPUT) == 0)
+				continue;
+                        pollfd->revents &= ~IO_POLL_INPUT;
+		} else if (io->condition & IO_WRITE) {
+			if ((pollfd->revents & IO_POLL_OUTPUT) == 0)
+				continue;
+                        pollfd->revents &= ~IO_POLL_OUTPUT;
+		}
+
+		t_id = t_push();
+		io->func(io->user_data, io->fd, io);
+		if (t_pop() != t_id)
+			i_panic("Leaked a t_pop() call!");
+
+		if (io->destroyed)
+			io_destroy(ioloop, io);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/ioloop-select.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,122 @@
+/*
+ ioloop-select.c : I/O loop handler using select()
+
+    Copyright (c) 2002 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include "lib.h"
+#include "ioloop-internal.h"
+
+#include <sys/types.h>
+#include <unistd.h>
+
+struct _IOLoopHandlerData {
+	fd_set read_fds, write_fds;
+};
+
+static fd_set tmp_read_fds, tmp_write_fds;
+
+void io_loop_handler_init(IOLoop ioloop)
+{
+	ioloop->handler_data = p_new(ioloop->pool, IOLoopHandlerData, 1);
+        FD_ZERO(&ioloop->handler_data->read_fds);
+	FD_ZERO(&ioloop->handler_data->write_fds);
+}
+
+void io_loop_handler_deinit(IOLoop ioloop)
+{
+        p_free(ioloop->pool, ioloop->handler_data);
+}
+
+void io_loop_handle_add(IOLoop ioloop, int fd, int condition)
+{
+        if (condition & IO_READ)
+		FD_SET(fd, &ioloop->handler_data->read_fds);
+        if (condition & IO_WRITE)
+		FD_SET(fd, &ioloop->handler_data->write_fds);
+}
+
+void io_loop_handle_remove(IOLoop ioloop, int fd, int condition)
+{
+        if (condition & IO_READ)
+		FD_CLR(fd, &ioloop->handler_data->read_fds);
+        if (condition & IO_WRITE)
+		FD_CLR(fd, &ioloop->handler_data->write_fds);
+}
+
+#define io_check_condition(fd, condition) \
+	((((condition) & IO_READ) && \
+	  FD_ISSET((fd), &tmp_read_fds)) || \
+	 (((condition) & IO_WRITE) && \
+	  FD_ISSET((fd), &tmp_write_fds)))
+
+void io_loop_handler_run(IOLoop ioloop)
+{
+	struct timeval tv;
+	IO io, next;
+	int ret, fd, condition, destroyed, t_id;
+
+	/* get the time left for next timeout task */
+	io_loop_get_wait_time(ioloop->timeouts, &tv, NULL);
+
+        memcpy(&tmp_read_fds, &ioloop->handler_data->read_fds, sizeof(fd_set));
+	memcpy(&tmp_write_fds, &ioloop->handler_data->write_fds,
+	       sizeof(fd_set));
+
+	ret = select(ioloop->highest_fd + 1, &tmp_read_fds, &tmp_write_fds,
+		     NULL, &tv);
+	if (ret < 0 && errno != EINTR)
+		i_warning("select() : %m");
+
+	/* execute timeout handlers */
+        io_loop_handle_timeouts(ioloop);
+
+	if (ret <= 0 || !ioloop->running) {
+                /* no I/O events */
+		return;
+	}
+
+	/* execute the I/O handlers in prioritized order */
+	for (io = ioloop->ios; io != NULL; io = next) {
+		next = io->next;
+
+		fd = io->fd;
+		condition = io->condition;
+
+		destroyed = io->destroyed;
+		if (destroyed)
+			io_destroy(ioloop, io);
+
+		if (!io_check_condition(fd, condition))
+                        continue;
+
+		if (!destroyed) {
+			t_id = t_push();
+			io->func(io->user_data, io->fd, io);
+			if (t_pop() != t_id)
+				i_panic("Leaked a t_pop() call!");
+		}
+
+		if (--ret == 0)
+                        break;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/ioloop.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,351 @@
+/*
+ ioloop.c : I/O loop
+
+    Copyright (c) 2002 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+/* FIXME: inserting IO is slow if there's lots of them. I should add a linked
+   list of priorities pointing to first item in the list with the priority. */
+
+#include "lib.h"
+#include "ioloop-internal.h"
+
+#undef timercmp
+#define timercmp(tvp, uvp) \
+	((tvp)->tv_sec > (uvp)->tv_sec || \
+	 ((tvp)->tv_sec == (uvp)->tv_sec && \
+	  (tvp)->tv_usec > (uvp)->tv_usec))
+
+time_t ioloop_time;
+struct timeval ioloop_timeval;
+
+static IOLoop current_ioloop = NULL;
+
+static void update_highest_fd(IOLoop ioloop)
+{
+        IO io;
+	int max_highest_fd;
+
+        max_highest_fd = ioloop->highest_fd-1;
+	ioloop->highest_fd = -1;
+
+	for (io = ioloop->ios; io != NULL; io = io->next) {
+		if (!io->destroyed && io->fd > ioloop->highest_fd) {
+			ioloop->highest_fd = io->fd;
+
+			if (ioloop->highest_fd == max_highest_fd)
+                                break;
+		}
+	}
+}
+
+static void io_list_insert(IOLoop ioloop, IO io)
+{
+	IO prev, next;
+
+        prev = NULL;
+	for (next = ioloop->ios; next != NULL; next = next->next) {
+		if (next->priority >= io->priority)
+                        break;
+                prev = next;
+	}
+
+	if (prev == NULL)
+                ioloop->ios = io;
+	else {
+		io->prev = prev;
+                prev->next = io;
+	}
+
+	if (next != NULL) {
+		io->next = next;
+		next->prev = io;
+	}
+}
+
+IO io_add(int fd, int condition, IOFunc func, void *data)
+{
+	return io_add_priority(fd, IO_PRIORITY_DEFAULT,
+			       condition, func, data);
+}
+
+IO io_add_priority(int fd, int priority, int condition,
+		   IOFunc func, void *user_data)
+{
+	IO io;
+
+	i_assert(fd >= 0);
+	i_assert(func != NULL);
+
+	io = p_new(current_ioloop->pool, struct _IO, 1);
+	io->fd = fd;
+	io->priority = priority;
+        io->condition = condition;
+
+	io->func = func;
+        io->user_data = user_data;
+
+	if (io->fd > current_ioloop->highest_fd)
+                current_ioloop->highest_fd = io->fd;
+
+        io_loop_handle_add(current_ioloop, io->fd, io->condition);
+	io_list_insert(current_ioloop, io);
+
+	return io;
+}
+
+void io_remove(IO io)
+{
+	i_assert(io != NULL);
+
+        /* notify the real I/O handler */
+	io_loop_handle_remove(current_ioloop, io->fd, io->condition);
+
+        /* check if we removed the highest fd */
+	if (io->fd == current_ioloop->highest_fd)
+                update_highest_fd(current_ioloop);
+
+	io->destroyed = TRUE;
+	io->fd = -1;
+}
+
+void io_destroy(IOLoop ioloop, IO io)
+{
+        /* remove from list */
+	if (io->prev == NULL)
+                ioloop->ios = io->next;
+	else
+		io->prev->next = io->next;
+
+	if (io->next != NULL)
+		io->next->prev = io->prev;
+
+	p_free(ioloop->pool, io);
+}
+
+static void timeout_list_insert(IOLoop ioloop, Timeout timeout)
+{
+	Timeout *t;
+        struct timeval *next_run;
+
+        next_run = &timeout->next_run;
+	for (t = &ioloop->timeouts; *t != NULL; t = &(*t)->next) {
+		if (timercmp(&(*t)->next_run, next_run))
+                        break;
+	}
+
+        timeout->next = *t;
+        *t = timeout;
+}
+
+inline static void timeout_update_next(Timeout timeout, struct timeval *tv_now)
+{
+        if (tv_now == NULL)
+		gettimeofday(&timeout->next_run, NULL);
+	else {
+                timeout->next_run.tv_sec = tv_now->tv_sec;
+                timeout->next_run.tv_usec = tv_now->tv_usec;
+	}
+
+	/* we don't want microsecond accuracy or this function will be
+	   called all the time - millisecond is more than enough */
+	timeout->next_run.tv_usec /= 1000;
+	timeout->next_run.tv_usec *= 1000;
+
+	timeout->next_run.tv_sec += timeout->msecs/1000;
+	timeout->next_run.tv_usec += (timeout->msecs%1000)*1000;
+
+	if (timeout->next_run.tv_usec > 1000000) {
+                timeout->next_run.tv_sec++;
+                timeout->next_run.tv_usec -= 1000000;
+	}
+}
+
+Timeout timeout_add(int msecs, TimeoutFunc func, void *user_data)
+{
+	Timeout timeout;
+
+	timeout = p_new(current_ioloop->pool, struct _Timeout, 1);
+        timeout->msecs = msecs;
+
+	timeout->func = func;
+	timeout->user_data = user_data;
+
+        timeout_update_next(timeout, NULL);
+        timeout_list_insert(current_ioloop, timeout);
+	return timeout;
+}
+
+void timeout_remove(Timeout timeout)
+{
+	i_assert(timeout != NULL);
+
+	timeout->destroyed = TRUE;
+}
+
+void timeout_destroy(IOLoop ioloop, Timeout timeout)
+{
+	Timeout *t;
+
+	for (t = &ioloop->timeouts; *t != NULL; t = &(*t)->next) {
+		if (*t == timeout)
+			break;
+	}
+	*t = timeout->next;
+
+        p_free(ioloop->pool, timeout);
+}
+
+int io_loop_get_wait_time(Timeout timeout, struct timeval *tv,
+			  struct timeval *tv_now)
+{
+	if (timeout == NULL)
+		return INT_MAX;
+
+	if (tv_now == NULL)
+		gettimeofday(tv, NULL);
+	else {
+		tv->tv_sec = tv_now->tv_sec;
+		tv->tv_usec = tv_now->tv_usec;
+	}
+
+	tv->tv_sec = timeout->next_run.tv_sec - tv->tv_sec;
+	tv->tv_usec = timeout->next_run.tv_usec - tv->tv_usec;
+	if (tv->tv_usec < 0) {
+		tv->tv_sec--;
+		tv->tv_usec += 1000000;
+	}
+
+	if (tv->tv_sec > 0 || (tv->tv_sec == 0 && tv->tv_usec > 0))
+		return tv->tv_sec*1000 + tv->tv_usec/1000;
+
+	/* no need to calculate the times again with this timeout */
+        tv->tv_sec = tv->tv_usec = 0;
+	timeout->run_now = TRUE;
+        return 0;
+}
+
+void io_loop_handle_timeouts(IOLoop ioloop)
+{
+	Timeout t, next;
+	struct timeval tv;
+        int t_id;
+
+	gettimeofday(&ioloop_timeval, NULL);
+	ioloop_time = ioloop_timeval.tv_sec;
+
+	if (ioloop->timeouts == NULL || !ioloop->timeouts->run_now)
+		return;
+
+	for (t = ioloop->timeouts; t != NULL; t = next) {
+		next = t->next;
+
+		if (t->destroyed) {
+                        timeout_destroy(ioloop, t);
+			continue;
+		}
+
+		if (!t->run_now) {
+			io_loop_get_wait_time(t, &tv, &ioloop_timeval);
+
+			if (!t->run_now)
+				break;
+		}
+
+                t->run_now = FALSE;
+                timeout_update_next(t, &ioloop_timeval);
+
+                t_id = t_push();
+		t->func(t->user_data, t);
+		if (t_pop() != t_id)
+                        i_panic("Leaked a t_pop() call!");
+	}
+}
+
+void io_loop_run(IOLoop ioloop)
+{
+        ioloop->running = TRUE;
+	while (ioloop->running)
+		io_loop_handler_run(ioloop);
+}
+
+void io_loop_stop(IOLoop ioloop)
+{
+        ioloop->running = FALSE;
+}
+
+void io_loop_set_running(IOLoop ioloop)
+{
+        ioloop->running = TRUE;
+}
+
+IOLoop io_loop_create(void)
+{
+	IOLoop ioloop;
+
+	/* initialize time */
+	gettimeofday(&ioloop_timeval, NULL);
+	ioloop_time = ioloop_timeval.tv_sec;
+
+        ioloop = i_new(struct _IOLoop, 1);
+	ioloop->pool = pool_create("I/O loop", 10240, TRUE);
+	ioloop->highest_fd = -1;
+
+	io_loop_handler_init(ioloop);
+
+	ioloop->prev = current_ioloop;
+        current_ioloop = ioloop;
+
+        return ioloop;
+}
+
+void io_loop_destroy(IOLoop ioloop)
+{
+	while (ioloop->ios != NULL) {
+		IO io = ioloop->ios;
+
+		if (!io->destroyed) {
+			i_warning("I/O leak: %p (%d)", io->func, io->fd);
+			io_remove(io);
+		}
+		io_destroy(ioloop, io);
+	}
+
+	while (ioloop->timeouts != NULL) {
+		Timeout to = ioloop->timeouts;
+
+		if (!to->destroyed) {
+			i_warning("Timeout leak: %p", to->func);
+			timeout_remove(to);
+		}
+                timeout_destroy(ioloop, to);
+	}
+
+        io_loop_handler_deinit(ioloop);
+
+        /* ->prev won't work unless loops are destroyed in create order */
+        i_assert(ioloop == current_ioloop);
+	current_ioloop = current_ioloop->prev;
+
+	pool_unref(ioloop->pool);
+        i_free(ioloop);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/ioloop.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,43 @@
+#ifndef __IOLOOP_H
+#define __IOLOOP_H
+
+#include <time.h>
+
+#define IO_READ			(1 << 0)
+#define IO_WRITE		(1 << 1)
+
+#define IO_PRIORITY_LOW		100
+#define IO_PRIORITY_DEFAULT	0
+#define IO_PRIORITY_HIGH	-100
+
+typedef void (*IOFunc) (void *user_data, int fd, IO io);
+typedef void (*TimeoutFunc) (void *user_data, Timeout timeout);
+
+/* Time when the I/O loop started calling handlers.
+   Can be used instead of time(NULL). */
+extern time_t ioloop_time;
+extern struct timeval ioloop_timeval;
+
+/* I/O listeners - you can create different handlers for IO_READ and IO_WRITE,
+   but make sure you don't create multiple handlers of same type, it's not
+   checked and removing one will stop the other from working as well. */
+IO io_add(int fd, int condition, IOFunc func, void *user_data);
+IO io_add_priority(int fd, int priority, int condition,
+		   IOFunc func, void *user_data);
+void io_remove(IO io);
+
+/* Timeout handlers */
+Timeout timeout_add(int msecs, TimeoutFunc func, void *user_data);
+void timeout_remove(Timeout timeout);
+
+void io_loop_run(IOLoop ioloop);
+void io_loop_stop(IOLoop ioloop); /* safe to run in signal handler */
+
+/* call these if you wish to run the iteration only once */
+void io_loop_set_running(IOLoop ioloop);
+void io_loop_handler_run(IOLoop ioloop);
+
+IOLoop io_loop_create(void);
+void io_loop_destroy(IOLoop ioloop);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/lib-signals.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,84 @@
+/*
+ lib-signals.c : Handling of commonly used signals
+
+    Copyright (c) 2001-2002 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include "lib.h"
+#include "lib-signals.h"
+
+#include <stdio.h>
+#include <signal.h>
+
+int lib_signal_hup, lib_signal_kill;
+static void (*quit_handler) (int);
+
+static void sig_hup(int signo)
+{
+#ifndef HAVE_SIGACTION
+	/* some systems may have changed the signal handler to default one */
+        signal(SIGHUP, sig_hup);
+#endif
+
+	lib_signal_hup = signo;
+}
+
+static void sig_quit(int signo)
+{
+	/* if we get killed after this, just die instead of coming back here. */
+	signal(SIGINT, SIG_DFL);
+	signal(SIGTERM, SIG_DFL);
+
+	/* quit the I/O loop, deinitialization is be done properly */
+	lib_signal_kill = signo;
+	quit_handler(signo);
+}
+
+void lib_init_signals(void (*sig_quit_handler) (int))
+{
+#ifdef HAVE_SIGACTION
+	struct sigaction act;
+#endif
+
+        lib_signal_kill = 0;
+	lib_signal_hup = 0;
+	quit_handler = sig_quit_handler;
+
+	/* signal() behaviour is a bit inconsistent between systems
+	   after the signal handler has been called. If the signal
+	   isn't ignored, or your handler doesn't kill the program,
+	   sigaction() should be used. */
+#ifdef HAVE_SIGACTION
+	sigemptyset(&act.sa_mask);
+	act.sa_flags = 0;
+	act.sa_handler = sig_hup;
+	sigaction(SIGHUP, &act, NULL);
+#else
+        signal(SIGHUP, sig_hup);
+#endif
+
+	/* these signals should be called only once, so it's safe to use
+	   signal() */
+	signal(SIGINT, sig_quit);
+        signal(SIGTERM, sig_quit);
+        signal(SIGPIPE, SIG_IGN);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/lib-signals.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,8 @@
+#ifndef __LIB_SIGNALS_H
+#define __LIB_SIGNALS_H
+
+extern int lib_signal_hup, lib_signal_kill;
+
+void lib_init_signals(void (*sig_quit_handler) (int));
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/lib.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,56 @@
+/*
+ lib.c : Initialize the library functions
+
+    Copyright (c) 2001-2002 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include "lib.h"
+
+#include <stdlib.h>
+#include <time.h>
+
+unsigned int nearest_power(unsigned int num)
+{
+	unsigned int n = 1;
+
+	i_assert(num <= (unsigned int) (1 << (BITS_IN_UINT-1)));
+
+	while (n < num) n <<= 1;
+	return n;
+}
+
+void lib_init(void)
+{
+	/* standard way to get rand() return different values. */
+	srand((unsigned int) time(NULL));
+
+	failures_init();
+	temp_mempool_init();
+	imem_init();
+}
+
+void lib_deinit(void)
+{
+        imem_deinit();
+	temp_mempool_deinit();
+        failures_deinit();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/lib.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,37 @@
+#ifndef __LIB_H
+#define __LIB_H
+
+/* default system includes - keep these at minimum.. */
+#include <string.h> /* strcmp() etc. */
+#include <stdarg.h> /* va_list is used everywhere */
+#include <errno.h> /* error checking is good */
+#include <sys/types.h> /* many other includes want this */
+
+typedef struct _IOLoop *IOLoop;
+typedef struct _IO *IO;
+typedef struct _Timeout *Timeout;
+
+typedef struct _IPADDR IPADDR;
+typedef struct _IOBuffer IOBuffer;
+typedef struct _TempString TempString;
+
+/* default lib includes */
+#ifdef HAVE_CONFIG_H
+#  include "../../config.h"
+#endif
+#include "compat.h"
+#include "macros.h"
+#include "failures.h"
+
+#include "mempool.h"
+#include "temp-mempool.h"
+#include "imem.h"
+
+#include "strfuncs.h"
+
+unsigned int nearest_power(unsigned int num);
+
+void lib_init(void);
+void lib_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/macros.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,180 @@
+#ifndef __MACROS_H
+#define __MACROS_H
+
+/* several useful macros, mostly from glib.h */
+
+#ifndef NULL
+#  define NULL ((void *)0)
+#endif
+
+#ifndef FALSE
+#  define FALSE (0)
+#endif
+
+#ifndef TRUE
+#  define TRUE (!FALSE)
+#endif
+
+#define BITS_IN_UINT (CHAR_BIT * sizeof(unsigned int))
+
+#define MEM_ALIGN(size) \
+	(((size) + MEM_ALIGN_SIZE-1) & ~((unsigned int) MEM_ALIGN_SIZE-1))
+
+/* Don't use simply MIN/MAX, as they're often defined elsewhere in include
+   files that are included after this file generating tons of warnings. */
+#define I_MIN(a, b)  (((a) < (b)) ? (a) : (b))
+#define I_MAX(a, b)  (((a) > (b)) ? (a) : (b))
+
+#undef CLAMP
+#define CLAMP(x, low, high)  (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
+
+#undef NVL
+#define NVL(str, nullstr) ((str) != NULL ? (str) : (nullstr))
+
+#define POINTER_TO_INT(p)	((int) (p))
+#define POINTER_TO_UINT(p)	((unsigned int) (p))
+
+#define INT_TO_POINTER(i)	((void *) (i))
+#define UINT_TO_POINTER(u)	((void *) (u))
+
+/* Define VA_COPY() to do the right thing for copying va_list variables. */
+#ifndef VA_COPY
+#  if defined (__GNUC__) && defined (__PPC__) && (defined (_CALL_SYSV) || defined (_WIN32))
+#    define VA_COPY(ap1, ap2) (*(ap1) = *(ap2))
+#  elif defined (VA_COPY_AS_ARRAY)
+#    define VA_COPY(ap1, ap2) i_memmove ((ap1), (ap2), sizeof (va_list))
+#  else /* va_list is a pointer */
+#    define VA_COPY(ap1, ap2) ((ap1) = (ap2))
+#  endif /* va_list is a pointer */
+#endif
+
+/* Provide convenience macros for handling structure
+ * fields through their offsets.
+ */
+#define STRUCT_OFFSET(struct_p, member) \
+    ((long) ((char *) &((struct_p)->member) - (char *) (struct_p)))
+#define STRUCT_MEMBER_P(struct_p, struct_offset) \
+    ((void *) ((char *) (struct_p) + (long) (struct_offset)))
+#define STRUCT_MEMBER(member_type, struct_p, struct_offset) \
+    (*(member_type *) G_STRUCT_MEMBER_P((struct_p), (struct_offset)))
+
+/* Provide simple macro statement wrappers (adapted from Perl):
+   STMT_START { statements; } STMT_END;
+   can be used as a single statement, as in
+   if (x) STMT_START { ... } STMT_END; else ...
+
+   For gcc we will wrap the statements within `({' and `})' braces.
+   For SunOS they will be wrapped within `if (1)' and `else (void) 0',
+   and otherwise within `do' and `while (0)'. */
+#if !(defined (STMT_START) && defined (STMT_END))
+#  if defined (__GNUC__) && !defined (__STRICT_ANSI__) && !defined (__cplusplus)
+#    define STMT_START (void)(
+#    define STMT_END   )
+#  else
+#    if (defined (sun) || defined (__sun__))
+#      define STMT_START if (1)
+#      define STMT_END   else (void)0
+#    else
+#      define STMT_START do
+#      define STMT_END   while (0)
+#    endif
+#  endif
+#endif
+
+/* Provide macros to feature the GCC function attribute. */
+#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)
+#  define __attr_format__(format_idx, arg_idx) \
+	__attribute__((format (printf, format_idx, arg_idx)))
+#  define __attr_format_arg__(arg_idx) \
+	__attribute__((format_arg (arg_idx)))
+#  define __attr_unused__ __attribute__((unused))
+#  define __attr_noreturn__ __attribute__((noreturn))
+#  define __attr_const__ __attribute__((const))
+#else
+#  define __attr_format__(format_idx, arg_idx)
+#  define __attr_format_arg__(arg_idx)
+#  define __attr_unused__
+#  define __attr_noreturn__
+#  define __attr_const__
+#  define __attr_unused__
+#endif
+
+/* Wrap the gcc __PRETTY_FUNCTION__ and __FUNCTION__ variables with
+   macros, so we can refer to them as strings unconditionally. */
+#ifdef __GNUC__
+#  define GNUC_FUNCTION __FUNCTION__
+#  define GNUC_PRETTY_FUNCTION __PRETTY_FUNCTION__
+#else
+#  define GNUC_FUNCTION ""
+#  define GNUC_PRETTY_FUNCTION ""
+#endif
+
+/* Provide macros for error handling. */
+#ifdef DISABLE_CHECKS
+#  define i_assert(expr)
+#  define return_if_fail(expr)
+#  define return_val_if_fail(expr,val)
+#elif defined (__GNUC__) && !defined (__STRICT_ANSI__)
+
+#define i_assert(expr)			STMT_START{			\
+     if (!(expr))							\
+       i_panic("file %s: line %d (%s): assertion failed: (%s)",		\
+		__FILE__,							\
+		__LINE__,							\
+		__PRETTY_FUNCTION__,					\
+		#expr);			}STMT_END
+
+#define return_if_fail(expr)		STMT_START{			\
+     if (!(expr))							\
+       {								\
+	 i_warning("file %s: line %d (%s): assertion `%s' failed.",	\
+		__FILE__,						\
+		__LINE__,						\
+		__PRETTY_FUNCTION__,					\
+		#expr);							\
+	 return;							\
+       };				}STMT_END
+
+#define return_val_if_fail(expr,val)	STMT_START{			\
+     if (!(expr))							\
+       {								\
+	 i_warning("file %s: line %d (%s): assertion `%s' failed.",	\
+		__FILE__,						\
+		__LINE__,						\
+		__PRETTY_FUNCTION__,					\
+		#expr);							\
+	 return val;							\
+       };				}STMT_END
+
+#else /* !__GNUC__ */
+
+#define i_assert(expr)			STMT_START{		\
+     if (!(expr))						\
+       i_panic("file %s: line %d: assertion failed: (%s)",	\
+	      __FILE__,						\
+	      __LINE__,						\
+	      #expr);			}STMT_END
+
+#define return_if_fail(expr)		STMT_START{		\
+     if (!(expr))						\
+       {							\
+	 i_warning("file %s: line %d: assertion `%s' failed.",	\
+		__FILE__,					\
+		__LINE__,					\
+		#expr);						\
+	 return;						\
+       };				}STMT_END
+
+#define return_val_if_fail(expr, val)	STMT_START{		\
+     if (!(expr))						\
+       {							\
+	 i_warning("file %s: line %d: assertion `%s' failed.",	\
+		__FILE__,					\
+		__LINE__,					\
+		#expr);						\
+	 return val;						\
+       };				}STMT_END
+
+#endif
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/md5.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,314 @@
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest.  This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to md5_init, call md5_update as
+ * needed on buffers full of bytes, and then call md5_Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+
+/* parts of this file are :
+ * Written March 1993 by Branko Lankester
+ * Modified June 1993 by Colin Plumb for altered md5.c.
+ * Modified October 1995 by Erik Troan for RPM
+ */
+
+
+#include <stdio.h>
+#include <string.h>
+#include "md5.h"
+
+static void md5_transform (unsigned int buf[4], const unsigned int in[16]);
+
+static int _ie = 0x44332211;
+static union _endian { int i; char b[4]; } *_endian = (union _endian *)&_ie;
+#define	IS_BIG_ENDIAN()		(_endian->b[0] == '\x44')
+#define	IS_LITTLE_ENDIAN()	(_endian->b[0] == '\x11')
+
+
+/*
+ * Note: this code is harmless on little-endian machines.
+ */
+static void 
+_byte_reverse (unsigned char *buf, unsigned int longs)
+{
+	unsigned int t;
+	do {
+		t = (unsigned int) ((unsigned int) buf[3] << 8 | buf[2]) << 16 |
+			((unsigned int) buf[1] << 8 | buf[0]);
+		*(unsigned int *) buf = t;
+		buf += 4;
+	} while (--longs);
+}
+
+/**
+ * md5_init: Initialise an md5 context object
+ * @ctx: md5 context 
+ * 
+ * Initialise an md5 buffer. 
+ *
+ **/
+void 
+md5_init (MD5Context *ctx)
+{
+	ctx->buf[0] = 0x67452301;
+	ctx->buf[1] = 0xefcdab89;
+	ctx->buf[2] = 0x98badcfe;
+	ctx->buf[3] = 0x10325476;
+	
+	ctx->bits[0] = 0;
+	ctx->bits[1] = 0;
+	
+	if (IS_BIG_ENDIAN())	
+		ctx->doByteReverse = 1;		
+	else 
+		ctx->doByteReverse = 0;	
+}
+
+
+
+/**
+ * md5_update: add a buffer to md5 hash computation
+ * @ctx: conetxt object used for md5 computaion
+ * @buf: buffer to add
+ * @len: buffer length
+ * 
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes. Use this to progressively construct an md5 hash.
+ **/
+void 
+md5_update (MD5Context *ctx, const void *buf, unsigned int len)
+{
+	unsigned int t;
+	
+	/* Update bitcount */
+	
+	t = ctx->bits[0];
+	if ((ctx->bits[0] = t + ((unsigned int) len << 3)) < t)
+		ctx->bits[1]++;		/* Carry from low to high */
+	ctx->bits[1] += len >> 29;
+	
+	t = (t >> 3) & 0x3f;	/* Bytes already in shsInfo->data */
+	
+	/* Handle any leading odd-sized chunks */
+	
+	if (t) {
+		unsigned char *p = (unsigned char *) ctx->in + t;
+		
+		t = 64 - t;
+		if (len < t) {
+			memcpy (p, buf, len);
+			return;
+		}
+		memcpy (p, buf, t);
+		if (ctx->doByteReverse)
+			_byte_reverse (ctx->in, 16);
+		md5_transform (ctx->buf, (unsigned int *) ctx->in);
+		buf = (char *) buf + t;
+		len -= t;
+	}
+	/* Process data in 64-byte chunks */
+	
+	while (len >= 64) {
+		memcpy (ctx->in, buf, 64);
+		if (ctx->doByteReverse)
+			_byte_reverse (ctx->in, 16);
+		md5_transform (ctx->buf, (unsigned int *) ctx->in);
+		buf = (char *) buf + 64;
+		len -= 64;
+	}
+	
+	/* Handle any remaining bytes of data. */
+	
+	memcpy (ctx->in, buf, len);
+}
+
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern 
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+/**
+ * md5_final: copy the final md5 hash to a bufer
+ * @digest: 16 bytes buffer
+ * @ctx: context containing the calculated md5
+ * 
+ * copy the final md5 hash to a bufer
+ **/
+void 
+md5_final (MD5Context *ctx, unsigned char digest[16])
+{
+	unsigned int count;
+	unsigned char *p;
+	
+	/* Compute number of bytes mod 64 */
+	count = (ctx->bits[0] >> 3) & 0x3F;
+	
+	/* Set the first char of padding to 0x80.  This is safe since there is
+	   always at least one byte free */
+	p = ctx->in + count;
+	*p++ = 0x80;
+	
+	/* Bytes of padding needed to make 64 bytes */
+	count = 64 - 1 - count;
+	
+	/* Pad out to 56 mod 64 */
+	if (count < 8) {
+		/* Two lots of padding:  Pad the first block to 64 bytes */
+		memset (p, 0, count);
+		if (ctx->doByteReverse)
+			_byte_reverse (ctx->in, 16);
+		md5_transform (ctx->buf, (unsigned int *) ctx->in);
+		
+		/* Now fill the next block with 56 bytes */
+		memset (ctx->in, 0, 56);
+	} else {
+		/* Pad block to 56 bytes */
+		memset (p, 0, count - 8);
+	}
+	if (ctx->doByteReverse)
+		_byte_reverse (ctx->in, 14);
+	
+	/* Append length in bits and transform */
+	((unsigned int *) ctx->in)[14] = ctx->bits[0];
+	((unsigned int *) ctx->in)[15] = ctx->bits[1];
+	
+	md5_transform (ctx->buf, (unsigned int *) ctx->in);
+	if (ctx->doByteReverse)
+		_byte_reverse ((unsigned char *) ctx->buf, 4);
+	memcpy (digest, ctx->buf, 16);
+}
+
+
+
+
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f, w, x, y, z, data, s) \
+	( w += f(x, y, z) + data,  w = w<<s | w>>(32-s),  w += x )
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data.  md5_Update blocks
+ * the data and converts bytes into longwords for this routine.
+ */
+static void 
+md5_transform (unsigned int buf[4], const unsigned int in[16])
+{
+	register unsigned int a, b, c, d;
+	
+	a = buf[0];
+	b = buf[1];
+	c = buf[2];
+	d = buf[3];
+	
+	MD5STEP (F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+	MD5STEP (F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+	MD5STEP (F1, c, d, a, b, in[2] + 0x242070db, 17);
+	MD5STEP (F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+	MD5STEP (F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+	MD5STEP (F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+	MD5STEP (F1, c, d, a, b, in[6] + 0xa8304613, 17);
+	MD5STEP (F1, b, c, d, a, in[7] + 0xfd469501, 22);
+	MD5STEP (F1, a, b, c, d, in[8] + 0x698098d8, 7);
+	MD5STEP (F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+	MD5STEP (F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+	MD5STEP (F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+	MD5STEP (F1, a, b, c, d, in[12] + 0x6b901122, 7);
+	MD5STEP (F1, d, a, b, c, in[13] + 0xfd987193, 12);
+	MD5STEP (F1, c, d, a, b, in[14] + 0xa679438e, 17);
+	MD5STEP (F1, b, c, d, a, in[15] + 0x49b40821, 22);
+	
+	MD5STEP (F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+	MD5STEP (F2, d, a, b, c, in[6] + 0xc040b340, 9);
+	MD5STEP (F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+	MD5STEP (F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+	MD5STEP (F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+	MD5STEP (F2, d, a, b, c, in[10] + 0x02441453, 9);
+	MD5STEP (F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+	MD5STEP (F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+	MD5STEP (F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+	MD5STEP (F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+	MD5STEP (F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+	MD5STEP (F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+	MD5STEP (F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+	MD5STEP (F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+	MD5STEP (F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+	MD5STEP (F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+	
+	MD5STEP (F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+	MD5STEP (F3, d, a, b, c, in[8] + 0x8771f681, 11);
+	MD5STEP (F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+	MD5STEP (F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+	MD5STEP (F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+	MD5STEP (F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+	MD5STEP (F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+	MD5STEP (F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+	MD5STEP (F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+	MD5STEP (F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+	MD5STEP (F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+	MD5STEP (F3, b, c, d, a, in[6] + 0x04881d05, 23);
+	MD5STEP (F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+	MD5STEP (F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+	MD5STEP (F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+	MD5STEP (F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+	
+	MD5STEP (F4, a, b, c, d, in[0] + 0xf4292244, 6);
+	MD5STEP (F4, d, a, b, c, in[7] + 0x432aff97, 10);
+	MD5STEP (F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+	MD5STEP (F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+	MD5STEP (F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+	MD5STEP (F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+	MD5STEP (F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+	MD5STEP (F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+	MD5STEP (F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+	MD5STEP (F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+	MD5STEP (F4, c, d, a, b, in[6] + 0xa3014314, 15);
+	MD5STEP (F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+	MD5STEP (F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+	MD5STEP (F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+	MD5STEP (F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+	MD5STEP (F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+	
+	buf[0] += a;
+	buf[1] += b;
+	buf[2] += c;
+	buf[3] += d;
+}
+
+
+/**
+ * md5_get_digest: get the md5 hash of a buffer
+ * @buffer: byte buffer
+ * @buffer_size: buffer size (in bytes)
+ * @digest: 16 bytes buffer receiving the hash code.
+ * 
+ * Get the md5 hash of a buffer. The result is put in 
+ * the 16 bytes buffer @digest .
+ **/
+void
+md5_get_digest (const char *buffer, unsigned int buffer_size,
+		unsigned char digest[16])
+{	
+	MD5Context ctx;
+
+	md5_init (&ctx);
+	md5_update (&ctx, buffer, buffer_size);
+	md5_final (&ctx, digest);
+	
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/md5.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,45 @@
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest.  This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to rpmMD5Init, call rpmMD5Update as
+ * needed on buffers full of bytes, and then call rpmMD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+
+/* parts of this file are :
+ * Written March 1993 by Branko Lankester
+ * Modified June 1993 by Colin Plumb for altered md5.c.
+ * Modified October 1995 by Erik Troan for RPM
+ */
+
+
+#ifndef MD5_H
+#define MD5_H
+
+typedef struct {
+	unsigned int buf[4];
+	unsigned int bits[2];
+	unsigned char in[64];
+	int doByteReverse;
+} MD5Context;
+
+
+void md5_get_digest (const char *buffer, unsigned int buffer_size,
+		     unsigned char digest[16]);
+
+/* raw routines */
+void md5_init (MD5Context *ctx);
+void md5_update (MD5Context *ctx, const void *buf, unsigned int len);
+void md5_final (MD5Context *ctx, unsigned char digest[16]);
+
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/mempool-allocfree.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,843 @@
+/*
+ mempool-allocfree.c : Memory pool manager for custom alloc+free
+
+    Copyright (c) 2001-2002 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+
+/* TODO:
+
+    - p_free() could check better that it's freeing a properly allocated
+      memory from pool it was told to.
+
+    - try to keep optimal pool sizes ie. keep them in the size they're
+      using 90% of the time
+    - add statistics functions that print/return the memory usage
+    - don't free() anything, save them to unused-list and reuse later.
+*/
+
+/* extensive debugging */
+/* #define POOL_DEBUG */
+
+/* always clear the memory area that has been free'd. for debugging mostly */
+/* #define POOL_FREE_CLEAR 0xd0 */
+
+/* Save the used pool block for each memory allocation, so you can be sure
+   that the memory is being free'd from correct pool. Also, with this option
+   we don't need to compare pointers which makes this code fully ANSI-C
+   compatible :) */
+#define POOL_SAVE_BLOCK
+
+#include "lib.h"
+#include "mempool.h"
+
+#include <stdlib.h>
+
+#define is_pool(pool) ((pool) != NULL && (pool)->magic == 0xbeef)
+
+#define check_pool(pool) \
+	if (!is_pool(pool)) \
+		i_panic("Trying to use invalid memory pool, aborting")
+
+#define MEM_FREE_BIT 0x80000000
+
+#define is_mem_free(size) (((size) & MEM_FREE_BIT) != 0)
+#define mem_size(size) ((size) & ~MEM_FREE_BIT)
+
+#define mem2int(_mem, _int) \
+	memcpy(&(_int), ((unsigned char *) (_mem)), sizeof(int));
+
+#define int2mem(_mem, _int) \
+	memcpy(((unsigned char *) (_mem)), &(_int), sizeof(int));
+
+#ifdef POOL_SAVE_BLOCK
+#  define ALLOC_EXTRA_SIZE sizeof(PoolBlock *)
+#else
+#  define ALLOC_EXTRA_SIZE 0
+#endif
+
+/* max. number of bytes to even try to allocate. This is done just to avoid
+   allocating less memory than was actually requested because of integer
+   overflows. -128 is much more than is actually needed. */
+#define MAX_ALLOC_SIZE (UINT_MAX - 128)
+
+typedef struct {
+        /* total number of bytes in data[] */
+	unsigned int size;
+        /* free data position at data+free_index, none if >= size */
+	unsigned int free_index;
+        /* largest data[] size (not including int size) */
+        unsigned int largest_free_space;
+
+	/* unsigned int size - if the last bit is set the data block is used
+                               if 0, the rest of the block is free
+	   unsigned char data[]
+	   unsigned int size
+           ... */
+        unsigned char data[1];
+} PoolBlock;
+
+#define BLOCK_INCREMENT_COUNT 5
+typedef struct {
+	struct Pool pool;
+
+	unsigned short magic; /* 0xbeef */
+	int refcount;
+
+	int num_blocks, used_blocks;
+        int first_block_with_space; /* -1 = all used */
+	PoolBlock **blocks; /* num_blocks size */
+
+	char name[1]; /* human readable name for blocks - used in statistics.
+	                 variable size */
+} AllocfreePool;
+
+static struct Pool static_allocfree_pool;
+
+static void pool_allocfree_free(Pool pool, void *mem);
+
+static void pool_block_create(AllocfreePool *apool, unsigned int size)
+{
+	PoolBlock *block;
+        int alloc_size;
+
+	i_assert(size > sizeof(int));
+
+	if (apool->used_blocks >= apool->num_blocks) {
+                apool->num_blocks += BLOCK_INCREMENT_COUNT;
+
+		alloc_size = sizeof(PoolBlock *) * apool->num_blocks;
+		apool->blocks = apool->blocks == NULL ? malloc(alloc_size) :
+			realloc(apool->blocks, alloc_size);
+		if (apool->blocks == NULL) {
+			i_panic("pool_block_create(): "
+				"Out of memory when reallocating %d bytes",
+				alloc_size);
+		}
+	}
+
+        alloc_size = sizeof(PoolBlock)-1 + size;
+	block = malloc(alloc_size);
+	if (block == NULL) {
+		i_panic("pool_block_create(): "
+			"Out of memory when allocating %d bytes", alloc_size);
+	}
+
+	block->size = size;
+	block->free_index = 0;
+	block->largest_free_space = size-sizeof(int)-ALLOC_EXTRA_SIZE;
+        memset(block->data, 0, sizeof(int));
+
+	if (apool->first_block_with_space == -1)
+                apool->first_block_with_space = apool->used_blocks;
+	apool->blocks[apool->used_blocks] = block;
+	apool->used_blocks++;
+}
+
+static int pool_block_get_pos(AllocfreePool *apool, PoolBlock *block)
+{
+	int pos;
+
+	for (pos = 0; pos < apool->used_blocks; pos++) {
+		if (apool->blocks[pos] == block)
+			return pos;
+	}
+
+        return -1;
+}
+
+static void pool_reset_first_free_block(AllocfreePool *apool)
+{
+	int pos;
+
+	pos = apool->first_block_with_space;
+	i_assert(pos >= 0);
+
+	apool->first_block_with_space = -1;
+	for (; pos < apool->used_blocks; pos++) {
+		if (apool->blocks[pos]->largest_free_space > 0) {
+			apool->first_block_with_space = pos;
+                        break;
+		}
+	}
+}
+
+static void pool_block_destroy(AllocfreePool *apool, PoolBlock *block)
+{
+	int pos;
+
+        pos = pool_block_get_pos(apool, block);
+	if (pos == -1)
+		return;
+
+	memmove(apool->blocks+pos, apool->blocks+pos+1,
+		sizeof(PoolBlock *) * (apool->num_blocks-pos-1));
+        apool->used_blocks--;
+
+	if (apool->first_block_with_space > pos)
+		apool->first_block_with_space--;
+	else if (apool->first_block_with_space == pos)
+		pool_reset_first_free_block(apool);
+        free(block);
+}
+
+static PoolBlock *pool_block_find(AllocfreePool *apool, void *mem)
+{
+        PoolBlock *block;
+	int pos;
+
+#ifdef POOL_SAVE_BLOCK
+	memcpy(&block, (unsigned char *) mem - sizeof(PoolBlock *),
+	       sizeof(PoolBlock *));
+
+	for (pos = 0; pos < apool->used_blocks; pos++) {
+		if (apool->blocks[pos] == block)
+                        return block;
+	}
+#else
+
+	/* NOTE: this may be a portability problem, since it compares
+	   unrelated pointers. */
+	for (pos = 0; pos < apool->used_blocks; pos++) {
+                block = apool->blocks[pos];
+
+		if ((unsigned char *) mem >= block->data &&
+		    (unsigned char *) mem < block->data + block->size)
+                        return block;
+	}
+#endif
+
+        return NULL;
+}
+
+#ifdef POOL_DEBUG
+static void pool_block_dump(PoolBlock *block)
+{
+	unsigned char *mem;
+	unsigned int holesize, holesize_real;
+
+	printf("Block: size %d\n", block->size);
+
+	mem = block->data;
+	mem2int(mem, holesize);
+	while (holesize > 0) {
+		holesize_real = mem_size(holesize);
+		printf(" - %u %s\n", holesize_real,
+		       is_mem_free(holesize) ? " (free)" : "");
+
+		mem += holesize_real + sizeof(int);
+		if (mem-block->data == block->size)
+			break;
+
+		if (mem-block->data > block->size)
+			i_panic("pool_block_alloc() : corrupted pool");
+
+		mem2int(mem, holesize);
+	}
+}
+
+static void pool_dump(AllocfreePool *apool)
+{
+	int i;
+
+	printf("Dumping pool: %s\n", apool->name);
+
+	for (i = 0; i < apool->used_blocks; i++)
+                pool_block_dump(apool->blocks[i]);
+}
+#endif
+
+static void *pool_block_alloc(AllocfreePool *apool, PoolBlock *block,
+			      unsigned int size)
+{
+	unsigned char *mem, *alloc;
+	unsigned int largest, holesize, holesize_real, avail_size;
+        unsigned int alloc_index, next_free_index;
+
+#ifdef POOL_DEBUG
+        printf("pool_block_alloc(%s, %d)\n", apool->name, size);
+#endif
+
+        size += ALLOC_EXTRA_SIZE;
+
+	/* search for first large enough free space in the block.
+	   remember the largest free block so far so if we use the
+	   current largest, we don't have to scan the largest one
+	   from the beginning. */
+        largest = 0;
+	mem = block->data + block->free_index;
+	mem2int(mem, holesize);
+	while (holesize > 0) {
+		holesize_real = mem_size(holesize);
+		if (holesize_real + sizeof(int)*2 > block->size)
+			i_panic("pool_block_alloc() : corrupted pool");
+
+		if (is_mem_free(holesize)) {
+			if (size <= holesize_real)
+				break;
+
+			if (holesize_real-ALLOC_EXTRA_SIZE > largest)
+				largest = holesize_real-ALLOC_EXTRA_SIZE;
+		}
+
+		mem += holesize_real + sizeof(int);
+		mem2int(mem, holesize);
+	}
+
+	avail_size = mem_size(holesize);
+	if (avail_size == 0) {
+                /* rest of the block is free */
+		if ((int) (mem-block->data)+size >= block->size) {
+			i_panic("pool_block_alloc(): not enough space "
+				"in block (should have been)");
+		}
+
+		avail_size = block->size - (int) (mem-block->data) -
+			sizeof(int);
+	} else if (avail_size-ALLOC_EXTRA_SIZE != block->largest_free_space) {
+		/* we didn't use the largest free space in block,
+		   so we don't need to scan the rest of the data to
+		   search it */
+                largest = block->largest_free_space;
+	}
+
+	if (avail_size < size+sizeof(int)*3) {
+                /* don't bother leaving small holes in the pool */
+		size = avail_size;
+	}
+
+#ifdef POOL_DEBUG
+        if (size == avail_size)
+                printf(" - pool is now full\n");
+#endif
+
+        /* [i.....] -> [iXXi..] (i = space size, X = data, . = empty space) */
+	int2mem(mem, size);
+	alloc = mem + sizeof(int);
+	alloc_index = (int) (mem-block->data);
+
+#ifdef POOL_SAVE_BLOCK
+        memcpy(alloc, &block, sizeof(PoolBlock *));
+        alloc += sizeof(PoolBlock *);
+#endif
+
+        /* set mem point to beginning of next hole */
+	mem += size + sizeof(int);
+
+	if (size < avail_size) {
+                /* we didn't use the whole space, mark it unused */
+		avail_size = holesize == 0 ? 0 : MEM_FREE_BIT |
+			(avail_size - size - sizeof(int));
+		int2mem(mem, avail_size);
+		next_free_index = (int) (mem-block->data);
+	} else if (block->free_index == alloc_index) {
+		/* we used the first free hole, get the next one */
+		next_free_index = block->size;
+		while (mem < block->data+block->size) {
+			mem2int(mem, holesize);
+			if (holesize == 0) {
+				next_free_index = (int) (mem-block->data);
+				break;
+			}
+
+			holesize_real = mem_size(holesize);
+			if (is_mem_free(holesize)) {
+				next_free_index =
+					(int) (mem-block->data);
+				break;
+			}
+
+			mem += holesize_real + sizeof(int);
+		}
+	} else {
+		/* just to suppress compiler warnings */
+		next_free_index = 0;
+	}
+
+         /* update the first free space index */
+	if (block->free_index == alloc_index)
+		block->free_index = next_free_index;
+
+	if (holesize > 0 && largest < block->largest_free_space) {
+		/* we just used the largest space in the block,
+		   we'll need to find the next largest one and it
+		   could be after the space we just used */
+		while (mem < block->data+block->size) {
+			mem2int(mem, holesize);
+			if (holesize == 0)
+                                break;
+
+			holesize_real = mem_size(holesize);
+			if (holesize_real +
+			    sizeof(int)*2 > block->size)
+				i_panic("pool_block_alloc() : corrupted pool");
+
+			if (is_mem_free(holesize) &&
+			    holesize_real-ALLOC_EXTRA_SIZE > largest)
+				largest = holesize_real-ALLOC_EXTRA_SIZE;
+
+			mem += holesize_real + sizeof(int);
+		}
+	}
+
+	if (holesize == 0 && mem < block->data+block->size) {
+                /* rest of the block is free */
+		holesize = block->size - (int) (mem-block->data + sizeof(int));
+		if (holesize-ALLOC_EXTRA_SIZE > largest)
+			largest = holesize-ALLOC_EXTRA_SIZE;
+	}
+
+	block->largest_free_space = largest;
+	if (largest == 0)
+		pool_reset_first_free_block(apool);
+
+	return alloc;
+}
+
+/* Returns the previous block for "mem", or NULL if either there's no
+   previous block or if previous block is before any of the free blocks. */
+static unsigned char *
+pool_block_prev(PoolBlock *block, unsigned char *find_mem)
+{
+	unsigned char *mem, *next_mem;
+        unsigned int holesize;
+
+	if (block->free_index >= (unsigned int) (find_mem-block->data))
+		return NULL;
+
+	mem = block->data + block->free_index;
+	mem2int(mem, holesize);
+	while (holesize > 0) {
+		next_mem = mem + mem_size(holesize) + sizeof(int);
+		if (next_mem == find_mem)
+			return mem;
+
+                mem = next_mem;
+		mem2int(mem, holesize);
+	}
+
+        return NULL;
+}
+
+static void pool_block_free(AllocfreePool *apool, PoolBlock *block,
+			    unsigned char *mem)
+{
+	unsigned char *next_mem, *prev_mem;
+	unsigned int holesize, next_holesize, avail_space, prevsize;
+	unsigned int next_free_index;
+        int pos;
+
+#ifdef POOL_SAVE_BLOCK
+        mem -= sizeof(PoolBlock *);
+#endif
+        mem -= sizeof(int);
+	mem2int(mem, holesize);
+
+#ifdef POOL_DEBUG
+	printf("pool_block_free(%s, %u)\n", apool->name, mem_size(holesize));
+#endif
+
+	if ((holesize & MEM_FREE_BIT) != 0 ||
+	    mem_size(holesize) + sizeof(int) > block->size)
+		i_panic("pool_block_free() : corrupted pool");
+
+	if ((int) (mem-block->data) + holesize + sizeof(int) >= block->size) {
+                /* this is the last space in block */
+                holesize = 0;
+	} else {
+		next_mem = ((unsigned char *) mem) + holesize + sizeof(int);
+		mem2int(next_mem, next_holesize);
+
+		if (next_holesize == 0) {
+			/* last used space - combine to free space at end */
+                        holesize = 0;
+		} else if (!is_mem_free(next_holesize)) {
+			/* mark the space free */
+			holesize |= MEM_FREE_BIT;
+		} else {
+			/* combine the two free spaces */
+			holesize = (mem_size(holesize) + sizeof(int) +
+				    mem_size(next_holesize)) | MEM_FREE_BIT;
+		}
+	}
+	int2mem(mem, holesize);
+
+        /* if previous block is free, we can combine the free space */
+	prev_mem = pool_block_prev(block, mem);
+	if (prev_mem != NULL) {
+		mem2int(prev_mem, prevsize);
+		if (is_mem_free(prevsize)) {
+			if (holesize != 0) {
+				holesize = (mem_size(holesize) + sizeof(int) +
+					    mem_size(prevsize)) | MEM_FREE_BIT;
+			}
+			int2mem(prev_mem, holesize);
+                        mem = prev_mem;
+		}
+	}
+
+        /* update largest free space size */
+	avail_space = holesize != 0 ? mem_size(holesize) :
+		block->size - (int) (mem-block->data + sizeof(int));
+
+#ifdef POOL_FREE_CLEAR
+	memset(mem + sizeof(int), POOL_FREE_CLEAR, avail_space);
+#elif defined (POOL_SAVE_BLOCK)
+	memset(mem + sizeof(int), 0, sizeof(PoolBlock *));
+#endif
+
+	if (block->largest_free_space < avail_space-ALLOC_EXTRA_SIZE)
+		block->largest_free_space = avail_space-ALLOC_EXTRA_SIZE;
+
+        /* update the first free space index */
+        next_free_index = (int) (mem-block->data);
+	if (block->free_index > next_free_index)
+		block->free_index = next_free_index;
+
+        /* update pool's first block with free space index */
+	pos = pool_block_get_pos(apool, block);
+	if (apool->first_block_with_space < 0 ||
+	    pos < apool->first_block_with_space)
+		apool->first_block_with_space = pos;
+
+	if (holesize == 0 && mem == block->data) {
+		/* FIXME: the block is completely unused, if there's more
+		   than two empty blocks, free them */
+	}
+}
+
+Pool pool_allocfree_create(const char *name, unsigned int size)
+{
+        AllocfreePool *apool;
+
+	i_assert(size > sizeof(int));
+
+	apool = calloc(sizeof(AllocfreePool) + strlen(name), 1);
+	if (apool == NULL)
+		i_panic("pool_create(): Out of memory");
+
+	apool->pool = static_allocfree_pool;
+	apool->magic = 0xbeef;
+        apool->refcount = 1;
+
+        apool->first_block_with_space = -1;
+	pool_block_create(apool, nearest_power(size));
+
+	strcpy(apool->name, name);
+	return &apool->pool;
+}
+
+#ifdef POOL_CHECK_LEAKS
+static const char *get_leak_string(const unsigned char *mem, int size)
+{
+	int i;
+
+	mem += sizeof(int);
+
+#ifdef POOL_SAVE_BLOCK
+        mem += sizeof(PoolBlock *);
+        size -= sizeof(PoolBlock *);
+#endif
+
+	for (i = 0; i < size; i++) {
+		if (mem[i] == '\0')
+			return (const char *) mem;
+
+		if ((mem[i] & 0x7f) < 32)
+                        break;
+	}
+
+        return NULL;
+}
+
+static const char *pool_block_count_leaks(PoolBlock *block, int *leak_count,
+					  int *leak_size)
+{
+        const char *leak_string;
+	unsigned char *mem;
+	unsigned int holesize, holesize_real;
+
+        leak_string = NULL;
+
+	mem = block->data;
+	mem2int(mem, holesize);
+	while (holesize > 0) {
+		holesize_real = mem_size(holesize);
+		if (!is_mem_free(holesize)) {
+			(*leak_count)++;
+			*leak_size += holesize_real;
+
+			if (leak_string == NULL) {
+				leak_string = get_leak_string(mem,
+							      holesize_real);
+			}
+		}
+
+		if (holesize_real + sizeof(int)*2 > block->size)
+			i_panic("pool_block_count_leaks() : corrupted pool");
+
+		mem += holesize_real + sizeof(int);
+		mem2int(mem, holesize);
+	}
+
+        return leak_string;
+}
+
+static void pool_check_leaks(AllocfreePool *apool)
+{
+        const char *leak_string;
+	int i, leak_count, leak_size;
+
+        leak_string = NULL;
+        leak_count = leak_size = 0;
+	for (i = 0; i < apool->used_blocks; i++) {
+                PoolBlock *block = apool->blocks[i];
+
+		if (block->free_index < block->size) {
+			leak_string = pool_block_count_leaks(block, &leak_count,
+							     &leak_size);
+		}
+	}
+
+	if (leak_count > 0) {
+		i_warning("Pool '%s' leaked %d allocs with "
+			  "total size of %d (%s)",
+			  apool->name, leak_count, leak_size,
+			  leak_string == NULL ? "" : leak_string);
+	}
+}
+#endif
+
+static void pool_destroy(AllocfreePool *apool)
+{
+        check_pool(apool);
+
+#ifdef POOL_CHECK_LEAKS
+        pool_check_leaks(apool);
+#endif
+
+	while (apool->used_blocks > 0)
+		pool_block_destroy(apool, apool->blocks[0]);
+	free(apool->blocks);
+        free(apool->name);
+	free(apool);
+}
+
+static void pool_allocfree_ref(Pool pool)
+{
+        AllocfreePool *apool = (AllocfreePool *) pool;
+
+	apool->refcount++;
+}
+
+static void pool_allocfree_unref(Pool pool)
+{
+        AllocfreePool *apool = (AllocfreePool *) pool;
+
+	if (--apool->refcount == 0)
+                pool_destroy(apool);
+}
+
+static void *pool_allocfree_malloc(Pool pool, unsigned int size)
+{
+        AllocfreePool *apool = (AllocfreePool *) pool;
+        PoolBlock *block;
+	void *mem;
+        int i, allocsize;
+
+	if (size == 0)
+		return NULL;
+
+	if (size > MAX_ALLOC_SIZE)
+		i_panic("Trying to allocate too much memory");
+
+	/* allocate only aligned amount of memory so alignment comes
+	   always properly */
+	size = (size + MEM_ALIGN-1) & ~(MEM_ALIGN-1);
+
+        check_pool(apool);
+
+	/* check if there's enough space in one of the existing blocks */
+        block = NULL;
+	for (i = 0; i < apool->used_blocks; i++) {
+		if (apool->blocks[i]->largest_free_space >= size) {
+                        block = apool->blocks[i];
+                        break;
+		}
+	}
+
+	if (block == NULL) {
+		/* create new block to pool */
+		allocsize = 2*apool->blocks[apool->used_blocks-1]->size;
+		if (allocsize-sizeof(int)-ALLOC_EXTRA_SIZE < size)
+			allocsize = size*2 + sizeof(int) + ALLOC_EXTRA_SIZE;
+
+		pool_block_create(apool, nearest_power(allocsize));
+                block = apool->blocks[apool->used_blocks-1];
+	}
+
+	mem = pool_block_alloc(apool, block, size);
+	memset(mem, 0, size);
+
+#ifdef POOL_DEBUG
+	pool_dump(apool);
+#endif
+        return mem;
+}
+
+static void *pool_allocfree_realloc(Pool pool, void *mem, unsigned int size)
+{
+        AllocfreePool *apool = (AllocfreePool *) pool;
+        unsigned char *mem_size_pos, *oldmem;
+	unsigned int memsize;
+
+	if (size == 0) {
+		pool_allocfree_free(pool, mem);
+		return NULL;
+	}
+
+	if (mem == NULL)
+                return pool_allocfree_malloc(pool, size);
+
+        check_pool(apool);
+
+        mem_size_pos = (unsigned char *) mem - sizeof(int);
+#ifdef POOL_SAVE_BLOCK
+	mem_size_pos -= sizeof(PoolBlock *);
+#endif
+
+        mem2int(mem_size_pos, memsize);
+	if (memsize == size)
+                return mem;
+
+	/* FIXME: shrinking could be done more efficiently, also growing
+	   might be able to check if it can extend it's current allocation */
+        oldmem = mem;
+	mem = pool_allocfree_malloc(pool, size);
+	memcpy(mem, oldmem, memsize < size ? memsize : size);
+        pool_allocfree_free(pool, oldmem);
+
+#ifdef POOL_DEBUG
+	pool_dump(apool);
+#endif
+
+	if (size > memsize)
+		memset((char *) mem + memsize, 0, size-memsize);
+        return mem;
+}
+
+static void *pool_allocfree_realloc_min(Pool pool, void *mem,
+					unsigned int size)
+{
+        unsigned char *mem_size_pos;
+	unsigned int memsize;
+
+	if (mem == NULL)
+                return pool_allocfree_malloc(pool, size);
+
+        mem_size_pos = (unsigned char *) mem - sizeof(int);
+#ifdef POOL_SAVE_BLOCK
+	mem_size_pos -= sizeof(PoolBlock *);
+#endif
+
+	mem2int(mem_size_pos, memsize);
+	if (size <= memsize)
+                return mem;
+
+	return pool_allocfree_realloc(pool, mem, size);
+}
+
+static void pool_allocfree_free(Pool pool, void *mem)
+{
+        AllocfreePool *apool = (AllocfreePool *) pool;
+	PoolBlock *block;
+
+        if (mem == NULL)
+                return;
+
+        check_pool(apool);
+
+	block = pool_block_find(apool, mem);
+	if (block == NULL)
+		i_panic("pool_allocfree_free(): invalid memory address");
+
+        pool_block_free(apool, block, mem);
+
+#ifdef POOL_DEBUG
+	pool_dump(apool);
+#endif
+}
+
+static void pool_allocfree_clear(Pool pool)
+{
+        AllocfreePool *apool = (AllocfreePool *) pool;
+	int i;
+
+	apool->first_block_with_space = 0;
+
+	for (i = 0; i < apool->used_blocks; i++) {
+		PoolBlock *block = apool->blocks[i];
+
+		block->free_index = 0;
+		block->largest_free_space = block->size -
+			sizeof(int) - ALLOC_EXTRA_SIZE;
+		memset(block->data, 0, sizeof(int));
+	}
+}
+
+static struct Pool static_allocfree_pool = {
+	pool_allocfree_ref,
+	pool_allocfree_unref,
+
+	pool_allocfree_malloc,
+	pool_allocfree_free,
+
+	pool_allocfree_realloc,
+	pool_allocfree_realloc_min,
+
+	pool_allocfree_clear
+};
+
+#ifdef POOL_DEBUG
+#include <stdlib.h>
+
+void mempool_test(void)
+{
+	Pool p;
+        void *arr[100];
+	int i, j;
+
+	memset(arr, 0, sizeof(arr));
+
+	p = pool_create("temp", 32);
+	for (i = 0; i < 10000; i++) {
+		arr[rand()%100] = p_malloc(p, 4*(rand()%10+1));
+
+		if (rand()%3 == 1) {
+			for (j = 0; j < 100; j++) {
+				if (arr[j] != NULL)
+					p_free_and_null(p, arr[j]);
+			}
+		}
+	}
+}
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/mempool-allocfree.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,47 @@
+#ifndef __MEMPOOL_H
+#define __MEMPOOL_H
+
+#include "macros.h"
+
+/* #define POOL_CHECK_LEAKS */
+
+/* Memory allocated and reallocated (the new data in it) in pools is always
+   zeroed, it will cost only a few CPU cycles and may well save some debug
+   time. */
+
+typedef struct Pool *Pool;
+
+Pool pool_create(const char *name, unsigned int size);
+
+void pool_ref(Pool pool);
+void pool_unref(Pool pool);
+
+#define p_new(pool, type, count) \
+	((type *) p_malloc(pool, (unsigned) sizeof(type) * (count)))
+void *p_malloc(Pool pool, unsigned int size);
+
+void p_free(Pool pool, void *mem);
+#define p_free_and_null(pool, rec) \
+	STMT_START { \
+          p_free(pool, rec); \
+          (rec) = NULL; \
+	} STMT_END
+
+/* p_free_clean() should be used when pool is being destroyed, so freeing
+   memory isn't needed for anything else than detecting memory leaks. */
+#ifdef POOL_CHECK_LEAKS
+#  define p_free_clean(pool, mem) p_free(pool, mem)
+#else
+#  define p_free_clean(pool, mem)
+#endif
+
+/* reallocate the `mem' to be exactly `size' */
+void *p_realloc(Pool pool, void *mem, unsigned int size);
+/* reallocate the `mem' to be at least `size' if it wasn't previously */
+void *p_realloc_min(Pool pool, void *mem, unsigned int size);
+
+/* Clear the pool. Memory allocated from pool before this call must not be
+   used after. */
+void p_clear(Pool pool);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/mempool-alloconly.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,250 @@
+/*
+ mempool-alloconly.c : Memory pool for fast allocation of memory without
+                       need to free it in small blocks
+
+    Copyright (c) 2002 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include "lib.h"
+#include "mempool.h"
+
+#include <stdlib.h>
+
+#define MAX_ALLOC_SIZE (UINT_MAX - sizeof(unsigned int))
+
+typedef struct _PoolBlock PoolBlock;
+
+typedef struct {
+	struct Pool pool;
+	int refcount;
+
+	PoolBlock *block;
+	unsigned int last_alloc_size;
+
+	char name[MEM_ALIGN_SIZE]; /* variable size */
+} AlloconlyPool;
+#define SIZEOF_ALLOCONLYPOOL (sizeof(AlloconlyPool)-MEM_ALIGN_SIZE)
+
+struct _PoolBlock {
+	PoolBlock *prev;
+
+	unsigned int size;
+	unsigned int left;
+
+	unsigned char data[MEM_ALIGN_SIZE]; /* variable size */
+};
+#define SIZEOF_POOLBLOCK (sizeof(PoolBlock)-MEM_ALIGN_SIZE)
+
+typedef struct {
+	unsigned int size;
+	unsigned char data[MEM_ALIGN_SIZE]; /* variable size */
+} PoolAlloc;
+#define SIZEOF_POOLALLOC (sizeof(PoolAlloc)-MEM_ALIGN_SIZE)
+
+static struct Pool static_alloconly_pool;
+static void pool_alloconly_clear(Pool pool);
+
+static void block_alloc(AlloconlyPool *pool, unsigned int size);
+static void *pool_alloconly_realloc_min(Pool pool, void *mem,
+					unsigned int size);
+
+Pool pool_alloconly_create(const char *name, unsigned int size)
+{
+	AlloconlyPool *apool;
+	int len;
+
+	len = strlen(name);
+
+	apool = calloc(SIZEOF_ALLOCONLYPOOL + len+1, 1);
+	apool->pool = static_alloconly_pool;
+	apool->refcount = 1;
+
+	block_alloc(apool, size);
+
+	strcpy(apool->name, name);
+	return (Pool) apool;
+}
+
+static void pool_alloconly_destroy(AlloconlyPool *apool)
+{
+	/* destroy all but the last block */
+	pool_alloconly_clear(&apool->pool);
+
+	/* destroy the last block */
+	free(apool->block);
+	free(apool);
+}
+
+static void pool_alloconly_ref(Pool pool)
+{
+	AlloconlyPool *apool = (AlloconlyPool *) pool;
+
+	apool->refcount++;
+}
+
+static void pool_alloconly_unref(Pool pool)
+{
+	AlloconlyPool *apool = (AlloconlyPool *) pool;
+
+	if (--apool->refcount == 0)
+		pool_alloconly_destroy(apool);
+}
+
+static void block_alloc(AlloconlyPool *apool, unsigned int size)
+{
+	PoolBlock *block;
+
+	/* each block is at least twice the size of the previous one */
+	if (apool->block != NULL)
+		size += apool->block->size;
+
+	if (size <= sizeof(PoolBlock))
+		size += sizeof(PoolBlock);
+	size = nearest_power(size);
+
+	block = calloc(size, 1);
+	block->prev = apool->block;
+	apool->block = block;
+
+	block->size = size - SIZEOF_POOLBLOCK;
+	block->left = block->size;
+}
+
+static void *pool_alloconly_malloc(Pool pool, unsigned int size)
+{
+	AlloconlyPool *apool = (AlloconlyPool *) pool;
+	PoolAlloc *alloc;
+
+	size = MEM_ALIGN(size);
+
+	if (apool->block->left < size + SIZEOF_POOLALLOC) {
+		/* we need a new block */
+		block_alloc(apool, size);
+	}
+
+	alloc = (PoolAlloc *) (apool->block->data +
+			       apool->block->size - apool->block->left);
+	alloc->size = size;
+
+	apool->block->left -= size + SIZEOF_POOLALLOC;
+	apool->last_alloc_size = size;
+	return alloc->data;
+}
+
+static void pool_alloconly_free(Pool pool __attr_unused__,
+				void *mem __attr_unused__)
+{
+	/* ignore */
+}
+
+static void *pool_alloconly_realloc(Pool pool, void *mem, unsigned int size)
+{
+	/* there's no point in shrinking the memory usage,
+	   so just do the same as realloc_min() */
+	return pool_alloconly_realloc_min(pool, mem, size);
+}
+
+static int pool_try_grow(AlloconlyPool *apool, void *mem, unsigned int size)
+{
+	/* see if we want to grow the memory we allocated last */
+	if (apool->block->data + (apool->block->size -
+				  apool->block->left -
+				  apool->last_alloc_size) == mem) {
+		/* yeah, see if we can grow */
+		if (apool->block->left >= size-apool->last_alloc_size) {
+			/* just shrink the available size */
+			apool->block->left -= size - apool->last_alloc_size;
+			apool->last_alloc_size = size;
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+static void *pool_alloconly_realloc_min(Pool pool, void *mem, unsigned int size)
+{
+	AlloconlyPool *apool = (AlloconlyPool *) pool;
+	PoolAlloc *alloc;
+	unsigned char *new_mem;
+	unsigned int old_size;
+
+	if (mem == NULL) {
+		alloc = NULL;
+		old_size = 0;
+	} else {
+		/* get old size */
+                alloc = (PoolAlloc *) ((char *) mem - SIZEOF_POOLALLOC);
+		old_size = alloc->size;
+	}
+
+	if (old_size >= size)
+		return mem;
+
+	size = MEM_ALIGN(size);
+
+	/* see if we can directly grow it */
+	if (pool_try_grow(apool, mem, size))
+		return mem;
+
+	/* slow way - allocate + copy */
+        new_mem = pool_alloconly_malloc(pool, size);
+	if (size > old_size) {
+                /* clear new data */
+		memset(new_mem + old_size, 0, size - old_size);
+	}
+
+        return new_mem;
+}
+
+static void pool_alloconly_clear(Pool pool)
+{
+	AlloconlyPool *apool = (AlloconlyPool *) pool;
+	PoolBlock *block;
+
+	/* destroy all blocks but the last, which is the largest */
+	while (apool->block->prev != NULL) {
+		block = apool->block;
+		apool->block = block->prev;
+
+		free(block);
+	}
+
+	/* clear the last block */
+	memset(apool->block->data, 0, apool->block->size - apool->block->left);
+	apool->block->left = apool->block->size;
+
+	apool->last_alloc_size = 0;
+}
+
+static struct Pool static_alloconly_pool = {
+	pool_alloconly_ref,
+	pool_alloconly_unref,
+
+	pool_alloconly_malloc,
+	pool_alloconly_free,
+
+	pool_alloconly_realloc,
+	pool_alloconly_realloc_min,
+
+	pool_alloconly_clear
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/mempool-system.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,137 @@
+/*
+ mempool-system.c : Memory pool wrapper for malloc() + realloc() + free()
+
+    Copyright (c) 2002 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include "lib.h"
+#include "mempool.h"
+
+#include <stdlib.h>
+
+#define MAX_ALLOC_SIZE (UINT_MAX - sizeof(unsigned int))
+
+typedef struct {
+	unsigned int size;
+	/* void data[]; */
+} PoolAlloc;
+
+static struct Pool static_system_pool;
+
+Pool system_pool = &static_system_pool;
+
+static void pool_system_ref(Pool pool __attr_unused__)
+{
+}
+
+static void pool_system_unref(Pool pool __attr_unused__)
+{
+}
+
+static void *pool_system_malloc(Pool pool __attr_unused__, unsigned int size)
+{
+	PoolAlloc *alloc;
+
+	if (size > MAX_ALLOC_SIZE)
+		i_panic("Trying to allocate too much memory");
+
+	alloc = calloc(sizeof(PoolAlloc) + size, 1);
+	if (alloc == NULL)
+		i_panic("pool_system_malloc(): Out of memory");
+	alloc->size = size;
+
+	return (char *) alloc + sizeof(PoolAlloc);
+}
+
+static void pool_system_free(Pool pool __attr_unused__, void *mem)
+{
+	if (mem != NULL)
+		free((char *) mem - sizeof(PoolAlloc));
+}
+
+static void *pool_system_realloc(Pool pool __attr_unused__, void *mem,
+				 unsigned int size)
+{
+	PoolAlloc *alloc;
+	unsigned int old_size;
+	char *rmem;
+
+	if (mem == NULL) {
+		alloc = NULL;
+		old_size = 0;
+	} else {
+		/* get old size */
+		alloc = (PoolAlloc *) ((char *) mem - sizeof(PoolAlloc));
+		old_size = alloc->size;
+	}
+
+        /* alloc & set new size */
+	alloc = realloc(alloc, sizeof(PoolAlloc) + size);
+	if (alloc == NULL)
+		i_panic("pool_system_realloc(): Out of memory");
+	alloc->size = size;
+
+        rmem = (char *) alloc + sizeof(PoolAlloc);
+	if (size > old_size) {
+                /* clear new data */
+		memset(rmem + old_size, 0, size-old_size);
+	}
+
+        return rmem;
+}
+
+static void *pool_system_realloc_min(Pool pool, void *mem, unsigned int size)
+{
+	PoolAlloc *alloc;
+        unsigned int old_size;
+
+	if (mem == NULL)
+		old_size = 0;
+	else {
+		/* get old size */
+                alloc = (PoolAlloc *) ((char *) mem - sizeof(PoolAlloc));
+		old_size = alloc->size;
+	}
+
+	if (old_size >= size)
+		return mem;
+	else
+                return pool_system_realloc(pool, mem, size);
+}
+
+static void pool_system_clear(Pool pool __attr_unused__)
+{
+	i_panic("pool_system_clear() must not be called");
+}
+
+static struct Pool static_system_pool = {
+	pool_system_ref,
+	pool_system_unref,
+
+	pool_system_malloc,
+	pool_system_free,
+
+	pool_system_realloc,
+	pool_system_realloc_min,
+
+	pool_system_clear
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/mempool.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,37 @@
+/*
+ mempool.c : Memory pool initialization
+
+    Copyright (c) 2001-2002 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include "lib.h"
+#include "mempool.h"
+
+Pool pool_alloconly_create(const char *name, unsigned int size);
+
+Pool pool_create(const char *name, unsigned int size, int allocfree)
+{
+	if (allocfree)
+		return system_pool;
+
+	return pool_alloconly_create(name, size);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/mempool.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,66 @@
+#ifndef __MEMPOOL_H
+#define __MEMPOOL_H
+
+#include "macros.h"
+
+/* #define POOL_CHECK_LEAKS */
+
+/* Memory allocated and reallocated (the new data in it) in pools is always
+   zeroed, it will cost only a few CPU cycles and may well save some debug
+   time. */
+
+typedef struct Pool *Pool;
+
+struct Pool {
+	void (*ref)(Pool pool);
+	void (*unref)(Pool pool);
+
+	void *(*malloc)(Pool pool, unsigned int size);
+	void (*free)(Pool pool, void *mem);
+
+	/* reallocate the `mem' to be exactly `size' */
+	void *(*realloc)(Pool pool, void *mem, unsigned int size);
+	/* reallocate the `mem' to be at least `size' if it wasn't previously */
+	void *(*realloc_min)(Pool pool, void *mem, unsigned int size);
+
+	/* Frees all the memory in pool. NOTE: system_pool doesn't support
+	   this and crashes if it's used */
+	void (*clear)(Pool pool);
+};
+
+/* system_pool uses calloc() + realloc() + free() */
+extern Pool system_pool;
+
+/* If allocfree is FALSE, p_free() has no effect. Note that `size' specifies
+   the initial malloc()ed block size, part of it is used internally. */
+Pool pool_create(const char *name, unsigned int size, int allocfree);
+
+/* Pools should be used through these macros: */
+#define pool_ref(pool) (pool)->ref(pool)
+#define pool_unref(pool) (pool)->unref(pool)
+
+#define p_malloc(pool, size) (pool)->malloc(pool, size)
+#define p_realloc(pool, mem, size) (pool)->realloc(pool, mem, size)
+#define p_realloc_min(pool, mem, size) (pool)->realloc_min(pool, mem, size)
+#define p_free(pool, mem) (pool)->free(pool, mem)
+
+#define p_clear(pool) (pool)->clear(pool)
+
+/* Extra macros to make life easier: */
+#define p_new(pool, type, count) \
+	((type *) p_malloc(pool, (unsigned) sizeof(type) * (count)))
+#define p_free_and_null(pool, rec) \
+	STMT_START { \
+          p_free(pool, rec); \
+          (rec) = NULL; \
+	} STMT_END
+
+/* p_free_clean() should be used when pool is being destroyed, so freeing
+   memory isn't needed for anything else than detecting memory leaks. */
+#ifdef POOL_CHECK_LEAKS
+#  define p_free_clean(pool, mem) p_free(pool, mem)
+#else
+#  define p_free_clean(pool, mem)
+#endif
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/mmap-util.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,88 @@
+/*
+ mmap-util.c - Memory mapping utilities
+
+    Copyright (c) 2002 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include "lib.h"
+#include "mmap-util.h"
+
+static void *mmap_file(int fd, size_t *length, int access)
+{
+	*length = lseek(fd, 0, SEEK_END);
+	if ((off_t)*length == (off_t)-1)
+		return MAP_FAILED;
+
+	if (*length == 0)
+		return NULL;
+
+	i_assert(*length > 0 && *length < INT_MAX);
+
+	return mmap(NULL, *length, access, MAP_SHARED, fd, 0);
+}
+
+void *mmap_ro_file(int fd, size_t *length)
+{
+	return mmap_file(fd, length, PROT_READ);
+}
+
+void *mmap_rw_file(int fd, size_t *length)
+{
+	return mmap_file(fd, length, PROT_READ | PROT_WRITE);
+}
+
+void *mmap_aligned(int fd, int access, off_t offset, size_t length,
+		   void **data_start, size_t *mmap_length)
+{
+	void *mmap_base;
+
+#ifdef HAVE_GETPAGESIZE
+	static int pagemask = 0;
+
+	if (pagemask == 0) {
+		pagemask = getpagesize();
+		i_assert(pagemask > 0);
+		pagemask--;
+	}
+
+	*mmap_length = length + (offset & pagemask);
+
+	mmap_base = mmap(NULL, *mmap_length, access, MAP_SHARED,
+			 fd, offset & ~pagemask);
+	*data_start = mmap_base == MAP_FAILED || mmap_base == NULL ? NULL :
+		(char *) mmap_base + (offset & pagemask);
+#else
+	*mmap_length = length + offset;
+
+	mmap_base = mmap(NULL, *mmap_length, access, MAP_SHARED, fd, 0);
+	*data_start = mmap_base == MAP_FAILED || mmap_base == NULL ? NULL :
+		(char *) mmap_base + offset;
+#endif
+
+	return mmap_base;
+}
+
+#ifndef HAVE_MADVISE
+int madvise(void *start, size_t length, int advice)
+{
+}
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/mmap-util.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,24 @@
+#ifndef __MMAP_UTIL_H
+#define __MMAP_UTIL_H
+
+#include <unistd.h>
+#include <sys/mman.h>
+
+#ifndef HAVE_MADVISE
+int madvise(void *start, size_t length, int advice);
+#  ifndef MADV_NORMAL
+#    define MADV_NORMAL 0
+#    define MADV_RANDOM 0
+#    define MADV_SEQUENTIAL 0
+#    define MADV_WILLNEED 0
+#    define MADV_DONTNEED 0
+#  endif
+#endif
+
+void *mmap_ro_file(int fd, size_t *length);
+void *mmap_rw_file(int fd, size_t *length);
+
+void *mmap_aligned(int fd, int access, off_t offset, size_t length,
+		   void **data_start, size_t *mmap_length);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/network.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,596 @@
+/*
+   network.c : Network stuff with IPv6 support
+
+    Copyright (c) 1999-2002 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include "lib.h"
+#include "network.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/un.h>
+#include <netinet/tcp.h>
+
+#define LISTEN_BACKLOG 8
+
+union sockaddr_union {
+	struct sockaddr sa;
+	struct sockaddr_in sin;
+#ifdef HAVE_IPV6
+	struct sockaddr_in6 sin6;
+#endif
+};
+
+#ifdef HAVE_IPV6
+#  define SIZEOF_SOCKADDR(so) ((so).sa.sa_family == AF_INET6 ? \
+	sizeof(so.sin6) : sizeof(so.sin))
+#else
+#  define SIZEOF_SOCKADDR(so) (sizeof(so.sin))
+#endif
+
+int net_ip_compare(IPADDR *ip1, IPADDR *ip2)
+{
+	if (ip1->family != ip2->family)
+		return 0;
+
+#ifdef HAVE_IPV6
+	if (ip1->family == AF_INET6)
+		return memcmp(&ip1->ip, &ip2->ip, sizeof(ip1->ip)) == 0;
+#endif
+
+	return memcmp(&ip1->ip, &ip2->ip, 4) == 0;
+}
+
+
+/* copy IP to sockaddr */
+static inline void sin_set_ip(union sockaddr_union *so, const IPADDR *ip)
+{
+	if (ip == NULL) {
+#ifdef HAVE_IPV6
+		so->sin6.sin6_family = AF_INET6;
+		so->sin6.sin6_addr = in6addr_any;
+#else
+		so->sin.sin_family = AF_INET;
+		so->sin.sin_addr.s_addr = INADDR_ANY;
+#endif
+		return;
+	}
+
+	so->sin.sin_family = ip->family;
+#ifdef HAVE_IPV6
+	if (ip->family == AF_INET6)
+		memcpy(&so->sin6.sin6_addr, &ip->ip, sizeof(ip->ip));
+	else
+#endif
+		memcpy(&so->sin.sin_addr, &ip->ip, 4);
+}
+
+static inline void sin_get_ip(const union sockaddr_union *so, IPADDR *ip)
+{
+	ip->family = so->sin.sin_family;
+
+#ifdef HAVE_IPV6
+	if (ip->family == AF_INET6)
+		memcpy(&ip->ip, &so->sin6.sin6_addr, sizeof(ip->ip));
+	else
+#endif
+		memcpy(&ip->ip, &so->sin.sin_addr, 4);
+}
+
+static inline void sin_set_port(union sockaddr_union *so, int port)
+{
+#ifdef HAVE_IPV6
+	if (so->sin.sin_family == AF_INET6)
+                so->sin6.sin6_port = htons((unsigned short) port);
+	else
+#endif
+		so->sin.sin_port = htons((unsigned short) port);
+}
+
+static inline int sin_get_port(union sockaddr_union *so)
+{
+#ifdef HAVE_IPV6
+	if (so->sin.sin_family == AF_INET6)
+		return ntohs(so->sin6.sin6_port);
+#endif
+	return ntohs(so->sin.sin_port);
+}
+
+static inline void close_save_errno(int fd)
+{
+	int old_errno = errno;
+	close(fd);
+	errno = old_errno;
+}
+
+/* Connect to socket with ip address */
+int net_connect_ip(IPADDR *ip, int port, IPADDR *my_ip)
+{
+	union sockaddr_union so;
+	int fd, ret, opt = 1;
+
+	if (my_ip != NULL && ip->family != my_ip->family) {
+		i_warning("net_connect_ip(): ip->family != my_ip->family");
+                my_ip = NULL;
+	}
+
+	/* create the socket */
+	memset(&so, 0, sizeof(so));
+        so.sin.sin_family = ip->family;
+	fd = socket(ip->family, SOCK_STREAM, 0);
+
+	if (fd == -1)
+		return -1;
+
+	/* set socket options */
+        net_set_nonblock(fd, TRUE);
+	setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+	setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt));
+
+	/* set our own address */
+	if (my_ip != NULL) {
+		sin_set_ip(&so, my_ip);
+		if (bind(fd, &so.sa, SIZEOF_SOCKADDR(so)) == -1) {
+			/* failed, set it back to INADDR_ANY */
+			sin_set_ip(&so, NULL);
+			bind(fd, &so.sa, SIZEOF_SOCKADDR(so));
+		}
+	}
+
+	/* connect */
+	sin_set_ip(&so, ip);
+	sin_set_port(&so, port);
+	ret = connect(fd, &so.sa, SIZEOF_SOCKADDR(so));
+
+#ifndef WIN32
+	if (ret < 0 && errno != EINPROGRESS)
+#else
+	if (ret < 0 && WSAGetLastError() != WSAEWOULDBLOCK)
+#endif
+	{
+                close_save_errno(fd);
+		return -1;
+	}
+
+	return fd;
+}
+
+int net_connect_unix(const char *path)
+{
+	struct sockaddr_un sa;
+	int fd, ret;
+
+	if (strlen(path) > sizeof(sa.sun_path)-1) {
+		/* too long path */
+		errno = EINVAL;
+		return -1;
+	}
+
+	/* create the socket */
+	fd = socket(PF_UNIX, SOCK_STREAM, 0);
+	if (fd == -1)
+		return -1;
+
+	/* set socket options */
+        net_set_nonblock(fd, TRUE);
+
+	/* connect */
+	memset(&sa, 0, sizeof(sa));
+	sa.sun_family = AF_UNIX;
+	strcpy(sa.sun_path, path);
+
+	ret = connect(fd, (struct sockaddr *) &sa, sizeof(sa));
+	if (ret < 0 && errno != EINPROGRESS) {
+                close_save_errno(fd);
+		return -1;
+	}
+
+	return fd;
+}
+
+/* Disconnect socket */
+void net_disconnect(int fd)
+{
+        close(fd);
+}
+
+/* Set socket blocking/nonblocking */
+void net_set_nonblock(int fd, int nonblock)
+{
+#ifdef HAVE_FCNTL
+	if (fcntl(fd, F_SETFL, nonblock ? O_NONBLOCK : 0) < 0)
+		i_fatal("net_send_nonblock() failed: %m");
+#endif
+}
+
+void net_set_cork(int fd, int cork)
+{
+#ifdef TCP_CORK
+	setsockopt(fd, SOL_TCP, TCP_CORK, &cork, sizeof(cork));
+#endif
+}
+
+/* Listen for connections on a socket. if `my_ip' is NULL, listen in any
+   address. */
+int net_listen(IPADDR *my_ip, int *port)
+{
+	union sockaddr_union so;
+	int ret, fd, opt = 1;
+	socklen_t len;
+
+	i_assert(port != NULL);
+
+	memset(&so, 0, sizeof(so));
+	sin_set_port(&so, *port);
+	sin_set_ip(&so, my_ip);
+
+	/* create the socket */
+	fd = socket(so.sin.sin_family, SOCK_STREAM, 0);
+#ifdef HAVE_IPV6
+	if (fd == -1 && (errno == EINVAL || errno == EAFNOSUPPORT)) {
+		/* IPv6 is not supported by OS */
+		so.sin.sin_family = AF_INET;
+		so.sin.sin_addr.s_addr = INADDR_ANY;
+
+		fd = socket(AF_INET, SOCK_STREAM, 0);
+	}
+#endif
+	if (fd == -1)
+		return -1;
+
+	/* set socket options */
+        net_set_nonblock(fd, TRUE);
+	setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+	setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt));
+
+	/* specify the address/port we want to listen in */
+	ret = bind(fd, &so.sa, SIZEOF_SOCKADDR(so));
+	if (ret >= 0) {
+		/* get the actual port we started listen */
+		len = SIZEOF_SOCKADDR(so);
+		ret = getsockname(fd, &so.sa, &len);
+		if (ret >= 0) {
+			*port = sin_get_port(&so);
+
+			/* start listening */
+			if (listen(fd, LISTEN_BACKLOG) >= 0)
+                                return fd;
+		}
+
+	}
+
+        /* error */
+	close_save_errno(fd);
+	return -1;
+}
+
+int net_listen_unix(const char *path)
+{
+	struct sockaddr_un sa;
+	int fd;
+
+	if (strlen(path) > sizeof(sa.sun_path)-1) {
+		/* too long path */
+		errno = EINVAL;
+		return -1;
+	}
+
+	/* create the socket */
+	fd = socket(PF_UNIX, SOCK_STREAM, 0);
+	if (fd == -1)
+		return -1;
+
+	/* set socket options */
+        net_set_nonblock(fd, TRUE);
+
+	/* bind */
+	memset(&sa, 0, sizeof(sa));
+	sa.sun_family = AF_UNIX;
+	strcpy(sa.sun_path, path);
+
+	if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) == 0) {
+		/* start listening */
+		if (listen(fd, LISTEN_BACKLOG) == 0)
+			return fd;
+	}
+
+	close_save_errno(fd);
+	return -1;
+}
+
+/* Accept a connection on a socket */
+int net_accept(int fd, IPADDR *addr, int *port)
+{
+	union sockaddr_union so;
+	int ret;
+	socklen_t addrlen;
+
+	i_assert(fd >= 0);
+
+	addrlen = sizeof(so);
+	ret = accept(fd, &so.sa, &addrlen);
+
+	if (ret < 0)
+		return -1;
+
+	if (addr != NULL) sin_get_ip(&so, addr);
+	if (port != NULL) *port = sin_get_port(&so);
+
+        net_set_nonblock(fd, TRUE);
+	return ret;
+}
+
+/* Read data from socket, return number of bytes read, -1 = error */
+int net_receive(int fd, void *buf, unsigned int len)
+{
+	int ret;
+
+	i_assert(fd >= 0);
+	i_assert(buf != NULL);
+	i_assert(len <= INT_MAX);
+
+	ret = recv(fd, buf, len, 0);
+	if (ret == 0)
+		return -1; /* disconnected */
+
+	if (ret < 0 && (errno == EINTR || errno == EAGAIN))
+                return 0;
+
+	return ret;
+}
+
+/* Transmit data, return number of bytes sent, -1 = error */
+int net_transmit(int fd, const void *data, unsigned int len)
+{
+        int ret;
+
+	i_assert(fd >= 0);
+	i_assert(data != NULL);
+	i_assert(len <= INT_MAX);
+
+	ret = send(fd, (void *) data, len, 0);
+	if (ret == -1 && (errno == EINTR || errno == EPIPE || errno == EAGAIN))
+                return 0;
+
+        return ret;
+}
+
+/* Get IP addresses for host. ips contains ips_count of IPs, they don't need
+   to be free'd. Returns 0 = ok, others = error code for net_gethosterror() */
+int net_gethostbyname(const char *addr, IPADDR **ips, int *ips_count)
+{
+#ifdef HAVE_IPV6
+	union sockaddr_union *so;
+	struct addrinfo hints, *ai, *origai;
+	char hbuf[NI_MAXHOST];
+	int host_error;
+#else
+	struct hostent *hp;
+#endif
+        int count;
+
+	i_assert(addr != NULL);
+	i_assert(ips != NULL);
+	i_assert(ips_count != NULL);
+
+	*ips = NULL;
+        *ips_count = 0;
+
+#ifdef HAVE_IPV6
+	memset(&hints, 0, sizeof(struct addrinfo));
+	hints.ai_socktype = SOCK_STREAM;
+
+	/* save error to host_error for later use */
+	host_error = getaddrinfo(addr, NULL, &hints, &ai);
+	if (host_error != 0)
+		return host_error;
+
+	if (getnameinfo(ai->ai_addr, ai->ai_addrlen, hbuf,
+			sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0)
+		return 1;
+
+
+        /* get number of IPs */
+        origai = ai;
+	for (count = 0; ai != NULL; ai = ai->ai_next)
+                count++;
+
+        *ips_count = count;
+        *ips = t_malloc(sizeof(IPADDR) * count);
+
+        count = 0;
+	for (ai = origai; ai != NULL; ai = ai->ai_next, count++) {
+		so = (union sockaddr_union *) ai->ai_addr;
+
+		sin_get_ip(so, ips[count]);
+	}
+	freeaddrinfo(origai);
+#else
+	hp = gethostbyname(addr);
+	if (hp == NULL)
+		return h_errno;
+
+        /* get number of IPs */
+	count = 0;
+	while (hp->h_addr_list[count] != NULL)
+		count++;
+
+        *ips_count = count;
+        *ips = t_malloc(sizeof(IPADDR) * count);
+
+	while (count > 0) {
+		count--;
+
+		(*ips)[count].family = AF_INET;
+                memcpy(&(*ips)[count].ip, hp->h_addr_list[count], 4);
+	}
+#endif
+
+	return 0;
+}
+
+/* Get socket address/port */
+int net_getsockname(int fd, IPADDR *addr, int *port)
+{
+	union sockaddr_union so;
+	socklen_t addrlen;
+
+	i_assert(fd >= 0);
+
+	addrlen = sizeof(so);
+	if (getsockname(fd, (struct sockaddr *) &so, &addrlen) == -1)
+		return -1;
+
+        if (addr != NULL) sin_get_ip(&so, addr);
+	if (port != NULL) *port = sin_get_port(&so);
+
+	return 0;
+}
+
+int net_ip2host(IPADDR *ip, char *host)
+{
+#ifdef HAVE_IPV6
+	if (!inet_ntop(ip->family, &ip->ip, host, MAX_IP_LEN))
+		return -1;
+#else
+	unsigned long ip4;
+
+	if (ip->family != AF_INET) {
+		strcpy(host, "0.0.0.0");
+		return -1;
+	}
+
+	ip4 = ntohl(ip->ip.s_addr);
+	i_snprintf(host, MAX_IP_LEN, "%lu.%lu.%lu.%lu",
+		   (ip4 & 0xff000000UL) >> 24,
+		   (ip4 & 0x00ff0000) >> 16,
+		   (ip4 & 0x0000ff00) >> 8,
+		   (ip4 & 0x000000ff));
+#endif
+	return 0;
+}
+
+int net_host2ip(const char *host, IPADDR *ip)
+{
+	if (strchr(host, ':') != NULL) {
+		/* IPv6 */
+		ip->family = AF_INET6;
+#ifdef HAVE_IPV6
+		if (inet_pton(AF_INET6, host, &ip->ip) == 0)
+			return -1;
+#else
+		ip->ip.s_addr = 0;
+#endif
+ 	} else {
+		/* IPv4 */
+		ip->family = AF_INET;
+		if (inet_aton(host, (struct in_addr *) &ip->ip) == 0)
+			return -1;
+	}
+
+	return 0;
+}
+
+/* Get socket error */
+int net_geterror(int fd)
+{
+	int data;
+	socklen_t len = sizeof(data);
+
+	if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &data, &len) == -1)
+		return -1;
+
+	return data;
+}
+
+/* get error of net_gethostname() */
+const char *net_gethosterror(int error)
+{
+#ifdef HAVE_IPV6
+	i_assert(error != 0);
+
+	if (error == 1) {
+		/* getnameinfo() failed */
+		return strerror(errno);
+	}
+
+	return gai_strerror(error);
+#else
+	switch (error) {
+	case HOST_NOT_FOUND:
+		return "Host not found";
+	case NO_ADDRESS:
+		return "No IP address found for name";
+	case NO_RECOVERY:
+		return "A non-recovable name server error occurred";
+	case TRY_AGAIN:
+		return "A temporary error on an authoritative name server";
+	}
+
+	/* unknown error */
+	return NULL;
+#endif
+}
+
+/* return TRUE if host lookup failed because it didn't exist (ie. not
+   some error with name server) */
+int net_hosterror_notfound(int error)
+{
+#ifdef HAVE_IPV6
+	return error != 1 && (error == EAI_NONAME || error == EAI_NODATA);
+#else
+	return error == HOST_NOT_FOUND || error == NO_ADDRESS;
+#endif
+}
+
+/* Get name of TCP service */
+char *net_getservbyport(int port)
+{
+	struct servent *entry;
+
+	entry = getservbyport(htons((unsigned short) port), "tcp");
+	return entry == NULL ? NULL : entry->s_name;
+}
+
+int is_ipv4_address(const char *host)
+{
+	while (*host != '\0') {
+		if (*host != '.' && !i_isdigit(*host))
+			return 0;
+                host++;
+	}
+
+	return 1;
+}
+
+int is_ipv6_address(const char *host)
+{
+	while (*host != '\0') {
+		if (*host != ':' && !i_isxdigit(*host))
+			return 0;
+                host++;
+	}
+
+	return 1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/network.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,98 @@
+#ifndef __NETWORK_H
+#define __NETWORK_H
+
+#ifndef WIN32
+#  include <sys/socket.h>
+#  include <netinet/in.h>
+#  include <netdb.h>
+#  include <arpa/inet.h>
+#endif
+
+#ifdef HAVE_SOCKS_H
+#include <socks.h>
+#endif
+
+#ifndef AF_INET6
+#  ifdef PF_INET6
+#    define AF_INET6 PF_INET6
+#  else
+#    define AF_INET6 10
+#  endif
+#endif
+
+struct _IPADDR {
+	unsigned short family;
+#ifdef HAVE_IPV6
+	struct in6_addr ip;
+#else
+	struct in_addr ip;
+#endif
+};
+
+/* maxmimum string length of IP address */
+#ifdef HAVE_IPV6
+#  define MAX_IP_LEN INET6_ADDRSTRLEN
+#else
+#  define MAX_IP_LEN 20
+#endif
+
+#define IPADDR_IS_V4(ip) ((ip)->family == AF_INET)
+#define IPADDR_IS_V6(ip) ((ip)->family == AF_INET6)
+
+/* returns 1 if IPADDRs are the same */
+int net_ip_compare(IPADDR *ip1, IPADDR *ip2);
+
+/* Connect to socket with ip address */
+int net_connect_ip(IPADDR *ip, int port, IPADDR *my_ip);
+/* Connect to named UNIX socket */
+int net_connect_unix(const char *path);
+/* Disconnect socket */
+void net_disconnect(int fd);
+/* Try to let the other side close the connection, if it still isn't
+   disconnected after certain amount of time, close it ourself */
+void net_disconnect_later(int fd);
+
+/* Set socket blocking/nonblocking */
+void net_set_nonblock(int fd, int nonblock);
+/* Set TCP_CORK if supported, ie. don't send out partial frames. */
+void net_set_cork(int fd, int cork);
+
+/* Listen for connections on a socket */
+int net_listen(IPADDR *my_ip, int *port);
+/* Listen for connections on an UNIX socket */
+int net_listen_unix(const char *path);
+/* Accept a connection on a socket */
+int net_accept(int fd, IPADDR *addr, int *port);
+
+/* Read data from socket, return number of bytes read, -1 = error */
+int net_receive(int fd, void *buf, unsigned int len);
+/* Transmit data, return number of bytes sent, -1 = error */
+int net_transmit(int fd, const void *data, unsigned int len);
+
+/* Get IP addresses for host. ips contains ips_count of IPs, they don't need
+   to be free'd. Returns 0 = ok, others = error code for net_gethosterror() */
+int net_gethostbyname(const char *addr, IPADDR **ips, int *ips_count);
+/* get error of net_gethostname() */
+const char *net_gethosterror(int error);
+/* return TRUE if host lookup failed because it didn't exist (ie. not
+   some error with name server) */
+int net_hosterror_notfound(int error);
+
+/* Get socket address/port */
+int net_getsockname(int fd, IPADDR *addr, int *port);
+
+/* IPADDR -> char* translation. `host' must be at least MAX_IP_LEN bytes */
+int net_ip2host(IPADDR *ip, char *host);
+/* char* -> IPADDR translation. */
+int net_host2ip(const char *host, IPADDR *ip);
+
+/* Get socket error */
+int net_geterror(int fd);
+
+/* Get name of TCP service */
+char *net_getservbyport(int port);
+
+int is_ipv4_address(const char *host);
+int is_ipv6_address(const char *host);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/primes.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,79 @@
+/* GLIB - Library of useful routines for C programming
+ * Copyright (C) 1995-1997  Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library 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.
+ */
+
+/*
+ * Modified by the GLib Team and others 1997-1999.  See the AUTHORS
+ * file for a list of people on the GLib Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GLib at ftp://ftp.gtk.org/pub/gtk/. 
+ */
+
+#include "lib.h"
+#include "primes.h"
+
+static const unsigned int primes[] =
+{
+  11,
+  19,
+  37,
+  73,
+  109,
+  163,
+  251,
+  367,
+  557,
+  823,
+  1237,
+  1861,
+  2777,
+  4177,
+  6247,
+  9371,
+  14057,
+  21089,
+  31627,
+  47431,
+  71143,
+  106721,
+  160073,
+  240101,
+  360163,
+  540217,
+  810343,
+  1215497,
+  1823231,
+  2734867,
+  4102283,
+  6153409,
+  9230113,
+  13845163
+};
+
+static const unsigned int primes_count = sizeof(primes) / sizeof(primes[0]);
+
+unsigned int primes_closest(unsigned int num)
+{
+	unsigned int i;
+
+	for (i = 0; i < primes_count; i++)
+		if (primes[i] > num)
+			return primes[i];
+
+	return primes[primes_count - 1];
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/primes.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,6 @@
+#ifndef __PRIMES_H
+#define __PRIMES_H
+
+unsigned int primes_closest(unsigned int num);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/randgen.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,76 @@
+/*
+ randgen.c : Random generator
+
+    Copyright (c) 2002 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include "lib.h"
+#include "randgen.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+static int init_refcount = 0;
+static int urandom_fd;
+
+void random_fill(const void *buf, unsigned int size)
+{
+	unsigned int pos;
+	int ret;
+
+	i_assert(init_refcount > 0);
+	i_assert(size < INT_MAX);
+
+	for (pos = 0; pos < size; pos += ret) {
+		ret = read(urandom_fd, (char *) buf + pos, size - pos);
+		if (ret < 0)
+			i_fatal("Error reading from /dev/urandom: %m");
+	}
+}
+
+void random_init(void)
+{
+	if (init_refcount++ > 0)
+		return;
+
+	urandom_fd = open("/dev/urandom", O_RDONLY);
+	if (urandom_fd == -1) {
+		if (errno == ENOENT) {
+			i_fatal("/dev/urandom doesn't exist, currently we "
+				"require it");
+		} else {
+			i_fatal("Can't open /dev/urandom: %m");
+		}
+	}
+
+	if (fcntl(urandom_fd, FD_CLOEXEC, 1L) < 0)
+		i_fatal("Error setting close-on-exec flag to /dev/urandom: %m");
+}
+
+void random_deinit(void)
+{
+	if (--init_refcount > 0)
+		return;
+
+	(void)close(urandom_fd);
+	urandom_fd = -1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/randgen.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,10 @@
+#ifndef __RANDGEN_H
+#define __RANDGEN_H
+
+void random_fill(const void *buf, unsigned int size);
+
+/* may be called multiple times */
+void random_init(void);
+void random_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/restrict-access.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,94 @@
+/*
+    Copyright (c) 2002 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include "lib.h"
+#include "restrict-access.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <grp.h>
+
+void restrict_access_set_env(const char *user, uid_t uid, gid_t gid,
+			     const char *chroot_dir)
+{
+	if (user != NULL && *user != '\0')
+		putenv((char *) t_strconcat("USER=", user, NULL));
+	if (chroot_dir != NULL && *chroot_dir != '\0')
+		putenv((char *) t_strconcat("CHROOT=", chroot_dir, NULL));
+
+	putenv((char *) t_strdup_printf("SETUID=%ld", (long) uid));
+	putenv((char *) t_strdup_printf("SETGID=%ld", (long) gid));
+}
+
+void restrict_access_by_env(void)
+{
+	const char *env;
+	gid_t gid;
+	uid_t uid;
+
+	/* chrooting */
+	env = getenv("CHROOT");
+	if (env != NULL) {
+		if (chroot(env) != 0)
+			i_fatal("chroot(%s) failed: %m", env);
+
+		if (chdir("/") != 0)
+			i_fatal("chdir(/) failed: %m");
+	}
+
+	/* groups - the getgid() checks are just so we don't fail if we're
+	   not running as root and try to just use our own GID. */
+	env = getenv("SETGID");
+	gid = env == NULL ? 0 : (gid_t) atol(env);
+	if (gid != 0 && (gid != getgid() || gid != getegid())) {
+		if (setgid(gid) != 0)
+			i_fatal("setgid(%ld) failed: %m", (long) gid);
+
+		env = getenv("USER");
+		if (env == NULL) {
+			/* user not known, use only this one group */
+			(void)setgroups(1, &gid);
+		} else {
+			if (initgroups(env, gid) != 0) {
+				i_fatal("initgroups(%s, %ld) failed: %m",
+					env, (long) gid);
+			}
+		}
+	}
+
+	/* uid last */
+	env = getenv("SETUID");
+	uid = env == NULL ? 0 : (uid_t) atol(env);
+	if (uid != 0) {
+		if (setuid(uid) != 0)
+			i_fatal("setuid(%ld) failed: %m", (long) uid);
+
+		/* just extra verification */
+#ifdef HAVE_SETREUID
+		if (setreuid((uid_t)-1, 0) == 0)
+#else
+		if (setuid(0) == 0)
+#endif
+			i_fatal("We couldn't drop root privileges");
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/restrict-access.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,12 @@
+#ifndef __RESTRICT_ACCESS_H
+#define __RESTRICT_ACCESS_H
+
+/* set environment variables so they can be read with
+   restrict_access_by_env() */
+void restrict_access_set_env(const char *user, uid_t uid, gid_t gid,
+			     const char *chroot_dir);
+
+/* chroot, setuid() and setgid() based on environment variables */
+void restrict_access_by_env(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/strfuncs.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,884 @@
+/*
+ strfuncs.c : String manipulation functions (note: LGPL, because the )
+
+    Copyright (C) 2001-2002 Timo Sirainen
+
+    printf_string_upper_bound() code is taken from GLIB:
+    Copyright (C) 1995-1997  Peter Mattis, Spencer Kimball and Josh MacDonald
+    Modified by the GLib Team and others 1997-1999.
+
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Library General Public
+    License as published by the Free Software Foundation; either
+    version 2 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
+    Library General Public License for more details.
+  
+    You should have received a copy of the GNU Library 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.
+*/
+
+#include "lib.h"
+#include "strfuncs.h"
+
+#include <stdio.h>
+#include <limits.h>
+#include <ctype.h>
+
+#define STRCONCAT_BUFSIZE 512
+
+typedef void *(*ALLOC_FUNC)(Pool, unsigned int);
+
+static void *tp_malloc(Pool pool __attr_unused__, unsigned int size)
+{
+        return t_malloc(size);
+}
+
+typedef union  _GDoubleIEEE754  GDoubleIEEE754;
+#define G_IEEE754_DOUBLE_BIAS   (1023)
+/* multiply with base2 exponent to get base10 exponent (nomal numbers) */
+#define G_LOG_2_BASE_10         (0.30102999566398119521)
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+union _GDoubleIEEE754
+{
+  double v_double;
+  struct {
+    unsigned int mantissa_low : 32;
+    unsigned int mantissa_high : 20;
+    unsigned int biased_exponent : 11;
+    unsigned int sign : 1;
+  } mpn;
+};
+#elif G_BYTE_ORDER == G_BIG_ENDIAN
+union _GDoubleIEEE754
+{
+  double v_double;
+  struct {
+    unsigned int sign : 1;
+    unsigned int biased_exponent : 11;
+    unsigned int mantissa_high : 20;
+    unsigned int mantissa_low : 32;
+  } mpn;
+};
+#else /* !G_LITTLE_ENDIAN && !G_BIG_ENDIAN */
+#error unknown ENDIAN type
+#endif /* !G_LITTLE_ENDIAN && !G_BIG_ENDIAN */
+
+typedef struct
+{
+  unsigned int min_width;
+  unsigned int precision;
+  int alternate_format, zero_padding, adjust_left, locale_grouping;
+  int add_space, add_sign, possible_sign, seen_precision;
+  int mod_half, mod_long, mod_extra_long;
+} PrintfArgSpec;
+
+#if (SIZEOF_LONG > 4) || (SIZEOF_VOID_P > 4)
+#  define HONOUR_LONGS 1
+#else
+#  define HONOUR_LONGS 0
+#endif
+
+unsigned int printf_string_upper_bound(const char *format, va_list args)
+{
+  int len = 1;
+
+  if (!format)
+    return len;
+
+  while (*format)
+    {
+      register char c = *format++;
+
+      if (c != '%')
+        len += 1;
+      else /* (c == '%') */
+        {
+          PrintfArgSpec spec;
+          int seen_l = FALSE, conv_done = FALSE;
+          unsigned int conv_len = 0;
+          const char *spec_start = format;
+
+          memset(&spec, 0, sizeof(spec));
+          do
+            {
+              c = *format++;
+              switch (c)
+                {
+                  GDoubleIEEE754 u_double;
+                  unsigned int v_uint;
+                  int v_int;
+                  const char *v_string;
+
+                  /* beware of positional parameters
+                   */
+                case '$':
+                  i_warning (GNUC_PRETTY_FUNCTION
+                             "(): unable to handle positional parameters (%%n$)");
+                  len += 1024; /* try adding some safety padding */
+                  break;
+
+                  /* parse flags
+                   */
+                case '#':
+                  spec.alternate_format = TRUE;
+                  break;
+                case '0':
+                  spec.zero_padding = TRUE;
+                  break;
+                case '-':
+                  spec.adjust_left = TRUE;
+                  break;
+                case ' ':
+                  spec.add_space = TRUE;
+                  break;
+                case '+':
+                  spec.add_sign = TRUE;
+                  break;
+                case '\'':
+                  spec.locale_grouping = TRUE;
+                  break;
+
+                  /* parse output size specifications
+                   */
+                case '.':
+                  spec.seen_precision = TRUE;
+                  break;
+                case '1':
+                case '2':
+                case '3':
+                case '4':
+                case '5':
+                case '6':
+                case '7':
+                case '8':
+                case '9':
+                  v_uint = c - '0';
+                  c = *format;
+                  while (c >= '0' && c <= '9')
+                    {
+                      format++;
+                      v_uint = v_uint * 10 + c - '0';
+                      c = *format;
+                    }
+                  if (spec.seen_precision)
+                    spec.precision = I_MAX (spec.precision, v_uint);
+                  else
+                    spec.min_width = I_MAX (spec.min_width, v_uint);
+                  break;
+                case '*':
+                  v_int = va_arg (args, int);
+                  if (spec.seen_precision)
+                    {
+                      /* forget about negative precision */
+                      if (v_int >= 0)
+                        spec.precision = I_MAX ((int)spec.precision, v_int);
+                    }
+                  else
+                    {
+                      if (v_int < 0)
+                        {
+                          v_int = - v_int;
+                          spec.adjust_left = TRUE;
+                        }
+                      spec.min_width = I_MAX ((int)spec.min_width, v_int);
+                    }
+                  break;
+
+                  /* parse type modifiers
+                   */
+                case 'h':
+                  spec.mod_half = TRUE;
+                  break;
+                case 'l':
+                  if (!seen_l)
+                    {
+                      spec.mod_long = TRUE;
+                      seen_l = TRUE;
+                      break;
+                    }
+                  /* else, fall through */
+                case 'L':
+                case 'q':
+                  spec.mod_long = TRUE;
+                  spec.mod_extra_long = TRUE;
+                  break;
+                case 'z':
+                case 'Z':
+#if GLIB_SIZEOF_SIZE_T > 4
+                  spec.mod_long = TRUE;
+                  spec.mod_extra_long = TRUE;
+#endif /* GLIB_SIZEOF_SIZE_T > 4 */
+                  break;
+                case 't':
+#if GLIB_SIZEOF_PTRDIFF_T > 4
+                  spec.mod_long = TRUE;
+                  spec.mod_extra_long = TRUE;
+#endif /* GLIB_SIZEOF_PTRDIFF_T > 4 */
+                  break;
+                case 'j':
+#if GLIB_SIZEOF_INTMAX_T > 4
+                  spec.mod_long = TRUE;
+                  spec.mod_extra_long = TRUE;
+#endif /* GLIB_SIZEOF_INTMAX_T > 4 */
+                  break;
+
+                  /* parse output conversions
+                   */
+                case '%':
+                  conv_len += 1;
+                  break;
+                case 'O':
+                case 'D':
+                case 'I':
+                case 'U':
+                  /* some C libraries feature long variants for these as well? */
+                  spec.mod_long = TRUE;
+                  /* fall through */
+                case 'o':
+                  conv_len += 2;
+                  /* fall through */
+                case 'd':
+                case 'i':
+                  conv_len += 1; /* sign */
+                  /* fall through */
+                case 'u':
+                  conv_len += 4;
+                  /* fall through */
+                case 'x':
+                case 'X':
+                  spec.possible_sign = TRUE;
+                  conv_len += 10;
+                  if (spec.mod_long && HONOUR_LONGS)
+                    conv_len *= 2;
+                  if (spec.mod_extra_long)
+                    conv_len *= 2;
+                  if (spec.mod_extra_long)
+                    {
+#ifdef G_HAVE_GINT64
+                      (void) va_arg (args, gint64);
+#else
+                      (void) va_arg (args, long);
+#endif
+                    }
+                  else if (spec.mod_long)
+                    (void) va_arg (args, long);
+                  else
+                    (void) va_arg (args, int);
+                  break;
+                case 'A':
+                case 'a':
+                  /*          0x */
+                  conv_len += 2;
+                  /* fall through */
+                case 'g':
+                case 'G':
+                case 'e':
+                case 'E':
+                case 'f':
+                  spec.possible_sign = TRUE;
+                  /*          n   .   dddddddddddddddddddddddd   E   +-  eeee */
+                  conv_len += 1 + 1 + I_MAX (24, spec.precision) + 1 + 1 + 4;
+                  if (spec.mod_extra_long)
+                    i_warning (GNUC_PRETTY_FUNCTION
+                               "(): unable to handle long double, collecting double only");
+#ifdef HAVE_LONG_DOUBLE
+#error need to implement special handling for long double
+#endif
+                  u_double.v_double = va_arg (args, double);
+                  /* %f can expand up to all significant digits before '.' (308) */
+                  if (c == 'f' &&
+                      u_double.mpn.biased_exponent > 0 && u_double.mpn.biased_exponent < 2047)
+                    {
+                      int exp = u_double.mpn.biased_exponent;
+
+                      exp -= G_IEEE754_DOUBLE_BIAS;
+                      exp = exp * G_LOG_2_BASE_10 + 1;
+                      conv_len += exp;
+                    }
+                  /* some printf() implementations require extra padding for rounding */
+                  conv_len += 2;
+                  /* we can't really handle locale specific grouping here */
+                  if (spec.locale_grouping)
+                    conv_len *= 2;
+                  break;
+                case 'C':
+                  spec.mod_long = TRUE;
+                  /* fall through */
+                case 'c':
+                  conv_len += spec.mod_long ? MB_LEN_MAX : 1;
+                  (void) va_arg (args, int);
+                  break;
+                case 'S':
+                  spec.mod_long = TRUE;
+                  /* fall through */
+                case 's':
+                  v_string = va_arg (args, char*);
+                  if (!v_string)
+                    conv_len += 8; /* hold "(null)" */
+                  else if (spec.seen_precision)
+                    conv_len += spec.precision;
+                  else
+                    conv_len += strlen (v_string);
+                  conv_done = TRUE;
+                  if (spec.mod_long)
+                    {
+                      i_warning (GNUC_PRETTY_FUNCTION
+                                 "(): unable to handle wide char strings");
+                      len += 1024; /* try adding some safety padding */
+                    }
+                  break;
+                case 'P': /* do we actually need this? */
+                  /* fall through */
+                case 'p':
+                  spec.alternate_format = TRUE;
+                  conv_len += 10;
+                  if (HONOUR_LONGS)
+                    conv_len *= 2;
+                  /* fall through */
+                case 'n':
+                  conv_done = TRUE;
+                  (void) va_arg (args, void*);
+                  break;
+                case 'm':
+                  /* there's not much we can do to be clever */
+                  v_string = strerror (errno);
+                  v_uint = v_string ? strlen (v_string) : 0;
+                  conv_len += I_MAX (256, v_uint);
+                  break;
+
+                  /* handle invalid cases
+                   */
+                case '\000':
+                  /* no conversion specification, bad bad */
+                  conv_len += format - spec_start;
+                  break;
+                default:
+                  i_warning (GNUC_PRETTY_FUNCTION
+                             "(): unable to handle `%c' while parsing format",
+                             c);
+                  break;
+                }
+              conv_done |= conv_len > 0;
+            }
+          while (!conv_done);
+          /* handle width specifications */
+          conv_len = I_MAX (conv_len, I_MAX (spec.precision, spec.min_width));
+          /* handle flags */
+          conv_len += spec.alternate_format ? 2 : 0;
+          conv_len += (spec.add_space || spec.add_sign || spec.possible_sign);
+          /* finally done */
+          len += conv_len;
+        } /* else (c == '%') */
+    } /* while (*format) */
+
+  return len;
+}
+
+static const char *fix_format_real(const char *fmt, const char *p)
+{
+	const char *errstr;
+	char *buf;
+	unsigned int pos, alloc, errlen;
+
+	errstr = strerror(errno);
+	errlen = strlen(errstr);
+
+	pos = (unsigned int) (p-fmt);
+	i_assert(pos < INT_MAX);
+
+	alloc = pos + errlen + 128;
+	buf = t_buffer_get(alloc);
+
+	memcpy(buf, fmt, pos);
+
+	while (*p != '\0') {
+		if (*p == '%' && p[1] == 'm') {
+			if (pos+errlen+1 > alloc) {
+				alloc += errlen+1 + 128;
+				buf = t_buffer_get(alloc);
+			}
+
+			memcpy(buf+pos, errstr, errlen);
+			pos += errlen;
+			p += 2;
+		} else {
+			/* p + \0 */
+			if (pos+2 > alloc) {
+				alloc += 128;
+				buf = t_buffer_get(alloc);
+			}
+
+			buf[pos++] = *p;
+			p++;
+		}
+	}
+
+	buf[pos++] = '\0';
+	t_buffer_alloc(pos);
+	return buf;
+}
+
+/* replace %m with strerror() */
+static const char *fix_format(const char *fmt)
+{
+	const char *p;
+
+	for (p = fmt; *p != '\0'; p++) {
+		if (*p == '%' && p[1] == 'm')
+			return fix_format_real(fmt, p);
+	}
+
+	return fmt;
+}
+
+int i_snprintf(char *str, unsigned int max_chars, const char *format, ...)
+{
+#ifdef HAVE_VSNPRINTF
+	va_list args;
+	int ret;
+
+	i_assert(str != NULL);
+	i_assert(max_chars < INT_MAX);
+	i_assert(format != NULL);
+
+	va_start(args, format);
+	ret = vsnprintf(str, max_chars, fix_format(format), args);
+	va_end(args);
+
+	if (ret < 0) {
+		str[max_chars-1] = '\0';
+		ret = strlen(str);
+	}
+
+	return ret;
+#else
+	char *buf;
+	va_list args;
+        int len;
+
+	i_assert(str != NULL);
+	i_assert(max_chars < INT_MAX);
+	i_assert(format != NULL);
+
+	va_start(args, format);
+	format = fix_format(format);
+	buf = t_buffer_get(printf_string_upper_bound(format, args));
+	va_end(args);
+
+	len = vsprintf(buf, format, args);
+	if (len >= (int)max_chars)
+		len = max_chars-1;
+
+        memcpy(str, buf, len);
+	str[len] = '\0';
+	return len;
+#endif
+}
+
+#define STRDUP_CORE(alloc_func, str) STMT_START { \
+	void *mem;				\
+	unsigned int len;			\
+						\
+	for (len = 0; (str)[len] != '\0'; )	\
+		len++;				\
+	len++;					\
+	mem = alloc_func;			\
+	memcpy(mem, str, sizeof(str[0])*len);	\
+	return mem;				\
+	} STMT_END
+
+char *p_strdup(Pool pool, const char *str)
+{
+	if (str == NULL)
+                return NULL;
+
+        STRDUP_CORE(p_malloc(pool, len), str);
+}
+
+const char *t_strdup(const char *str)
+{
+	if (str == NULL)
+                return NULL;
+
+        STRDUP_CORE(t_malloc(len), str);
+}
+
+int *p_intarrdup(Pool pool, const int *arr)
+{
+	if (arr == NULL)
+                return NULL;
+
+        STRDUP_CORE(p_malloc(pool, sizeof(int) * len), arr);
+}
+
+const int *t_intarrdup(const int *arr)
+{
+	if (arr == NULL)
+                return NULL;
+
+        STRDUP_CORE(t_malloc(sizeof(int) * len), arr);
+}
+
+#define STRDUP_EMPTY_CORE(alloc_func, str) STMT_START { \
+	if ((str) == NULL || (str)[0] == '\0')	\
+                return NULL;			\
+						\
+	STRDUP_CORE(alloc_func, str);		\
+	} STMT_END
+
+
+char *p_strdup_empty(Pool pool, const char *str)
+{
+        STRDUP_EMPTY_CORE(p_malloc(pool, len), str);
+}
+
+const char *t_strdup_empty(const char *str)
+{
+        STRDUP_EMPTY_CORE(t_malloc(len), str);
+}
+
+static inline char *
+strndup_core(const char *str, unsigned int max_chars,
+	     ALLOC_FUNC alloc, Pool pool)
+{
+	char *mem;
+	unsigned int len;
+
+	i_assert(max_chars < INT_MAX);
+
+	if (str == NULL)
+		return NULL;
+
+	len = 0;
+	while (str[len] != '\0' && len < max_chars)
+		len++;
+
+	mem = alloc(pool, len+1);
+	memcpy(mem, str, len);
+	mem[len] = '\0';
+	return mem;
+}
+
+char *p_strndup(Pool pool, const char *str, unsigned int max_chars)
+{
+        return strndup_core(str, max_chars, pool->malloc, pool);
+}
+
+const char *t_strndup(const char *str, unsigned int max_chars)
+{
+        return strndup_core(str, max_chars, tp_malloc, NULL);
+}
+
+char *p_strdup_printf(Pool pool, const char *format, ...)
+{
+	va_list args;
+        char *ret;
+
+	va_start(args, format);
+        ret = p_strdup_vprintf(pool, format, args);
+	va_end(args);
+
+	return ret;
+}
+
+const char *t_strdup_printf(const char *format, ...)
+{
+	va_list args;
+        const char *ret;
+
+	va_start(args, format);
+        ret = t_strdup_vprintf(format, args);
+	va_end(args);
+
+	return ret;
+}
+
+static inline char *
+strdup_vprintf_core(const char *format, va_list args,
+		    ALLOC_FUNC alloc_func, Pool pool)
+{
+        va_list temp_args;
+        char *ret;
+
+	if (format == NULL)
+		return NULL;
+	format = fix_format(format);
+
+	VA_COPY(temp_args, args);
+
+        ret = alloc_func(pool, printf_string_upper_bound(format, args));
+	vsprintf(ret, format, args);
+
+	va_end(temp_args);
+
+        return ret;
+}
+
+char *p_strdup_vprintf(Pool pool, const char *format, va_list args)
+{
+        return strdup_vprintf_core(format, args, pool->malloc, pool);
+}
+
+const char *t_strdup_vprintf(const char *format, va_list args)
+{
+        return strdup_vprintf_core(format, args, tp_malloc, NULL);
+}
+
+void p_strdup_replace(Pool pool, char **dest, const char *str)
+{
+	p_free(pool, *dest);
+        *dest = p_strdup(pool, str);
+}
+
+const char *temp_strconcat(const char *str1, va_list args,
+			   unsigned int *ret_len)
+{
+	const char *str;
+        char *temp;
+	unsigned int full_len, len, bufsize;
+
+	if (str1 == NULL)
+		return NULL;
+
+        /* put str1 to buffer */
+        len = strlen(str1);
+	bufsize = len <= STRCONCAT_BUFSIZE ? STRCONCAT_BUFSIZE :
+		nearest_power(len+1);
+        temp = t_buffer_get(bufsize);
+
+	memcpy(temp, str1, len);
+	full_len = len;
+
+        /* put rest of the strings to buffer */
+	while ((str = va_arg(args, char *)) != NULL) {
+		len = strlen(str);
+		if (len == 0)
+			continue;
+
+		if (bufsize < full_len+len+1) {
+			bufsize = nearest_power(bufsize+len+1);
+			temp = t_buffer_reget(temp, bufsize);
+		}
+
+                memcpy(temp+full_len, str, len);
+		full_len += len;
+	}
+
+	temp[full_len] = '\0';
+        *ret_len = full_len+1;
+        return temp;
+}
+
+char *p_strconcat(Pool pool, const char *str1, ...)
+{
+	va_list args;
+        const char *temp;
+	char *ret;
+        unsigned int len;
+
+	va_start(args, str1);
+
+	temp = temp_strconcat(str1, args, &len);
+	if (temp == NULL)
+		ret = NULL;
+	else {
+		ret = p_malloc(pool, len);
+		memcpy(ret, temp, len);
+	}
+
+	va_end(args);
+        return ret;
+}
+
+const char *t_strconcat(const char *str1, ...)
+{
+	va_list args;
+	const char *ret;
+        unsigned int len;
+
+	va_start(args, str1);
+
+	ret = temp_strconcat(str1, args, &len);
+	if (ret != NULL)
+		t_buffer_alloc(len);
+
+	va_end(args);
+        return ret;
+}
+
+const char *t_strcut(const char *str, char cutchar)
+{
+	const char *p;
+
+	for (p = str; *p != '\0'; p++) {
+		if (*p == cutchar)
+                        return t_strndup(str, (unsigned int) (p-str));
+	}
+
+        return str;
+}
+
+int is_numeric(const char *str, char end_char)
+{
+	if (*str == '\0' || *str == end_char)
+		return FALSE;
+
+	while (*str != '\0' && *str != end_char) {
+		if (!i_isdigit(*str))
+			return FALSE;
+		str++;
+	}
+
+	return TRUE;
+}
+
+char *str_ucase(char *str)
+{
+	char *p;
+
+	for (p = str; *p != '\0'; p++)
+		*p = i_toupper(*p);
+        return str;
+}
+
+char *str_lcase(char *str)
+{
+	char *p;
+
+	for (p = str; *p != '\0'; p++)
+		*p = i_tolower(*p);
+        return str;
+}
+
+char *i_strtoken(char **str, char delim)
+{
+	char *ret;
+
+	if (*str == NULL || **str == '\0')
+                return NULL;
+
+	ret = *str;
+	while (**str != '\0') {
+		if (**str == delim) {
+			**str = '\0';
+                        (*str)++;
+                        break;
+		}
+                (*str)++;
+	}
+        return ret;
+}
+
+void string_remove_escapes(char *str)
+{
+	char *dest;
+
+	for (dest = str; *str != '\0'; str++) {
+		if (*str != '\\' || str[1] == '\0')
+			*dest++ = *str;
+	}
+
+	*dest = '\0';
+}
+
+int strarray_length(char *const array[])
+{
+	int len;
+
+	len = 0;
+	while (*array) {
+		len++;
+                array++;
+	}
+        return len;
+}
+
+int strarray_find(char *const array[], const char *item)
+{
+	int index;
+
+	i_assert(item != NULL);
+
+	for (index = 0; *array != NULL; index++, array++) {
+		if (strcasecmp(*array, item) == 0)
+			return index;
+	}
+
+	return -1;
+}
+
+char *const *t_strsplit(const char *data, const char *separators)
+{
+        const char **array;
+	char *str;
+        int alloc_len, len;
+
+        i_assert(*separators != '\0');
+
+	str = (char *) t_strdup(data);
+
+        alloc_len = 20;
+        array = t_buffer_get(sizeof(const char *) * alloc_len);
+
+	array[0] = str; len = 1;
+	while (*str != '\0') {
+		if (strchr(separators, *str) != NULL) {
+			/* separator found */
+			if (len+1 >= alloc_len) {
+                                alloc_len *= 2;
+				array = t_buffer_reget(array,
+						       sizeof(const char *) *
+						       alloc_len);
+			}
+
+                        *str = '\0';
+			array[len++] = str+1;
+		}
+
+                str++;
+	}
+        array[len] = NULL;
+
+	t_buffer_alloc(sizeof(const char *) * (len+1));
+        return (char *const *) array;
+}
+
+const char *t_strjoin_replace(char *const args[], char separator,
+			      int replacearg, const char *replacedata)
+{
+        const char *arg;
+        char *data;
+	unsigned int alloc_len, arg_len, full_len;
+	int i;
+
+	if (args[0] == NULL)
+                return NULL;
+
+        alloc_len = 512; full_len = 0;
+	data = t_buffer_get(alloc_len);
+	for (i = 0; args[i] != NULL; i++) {
+		arg = i == replacearg ? replacedata : args[i];
+		arg_len = strlen(arg);
+
+		if (full_len + arg_len+1 >= alloc_len) {
+			alloc_len = nearest_power(full_len + arg_len+1);
+                        data = t_buffer_reget(data, alloc_len);
+		}
+
+		memcpy(data+full_len, arg, arg_len);
+                full_len += arg_len;
+
+                data[full_len++] = separator;
+	}
+        data[full_len-1] = '\0';
+
+        t_buffer_alloc(full_len);
+        return data;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/strfuncs.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,66 @@
+#ifndef __STRFUNC_H
+#define __STRFUNC_H
+
+#include <limits.h>
+
+/* max. size for %d */
+#define MAX_INT_STRLEN ((sizeof(int) * CHAR_BIT + 2) / 3 + 1)
+/* `str' should be type char[MAX_INT_STRLEN] */
+#define itoa(str, num) \
+	i_snprintf(str, sizeof(str), "%d", num)
+
+#define is_empty_str(str) \
+        ((str) == NULL || (str)[0] == '\0')
+
+unsigned int printf_string_upper_bound(const char *format, va_list args);
+int i_snprintf(char *str, unsigned int max_chars, const char *format, ...)
+	__attr_format__(3, 4);
+
+char *p_strdup(Pool pool, const char *str);
+char *p_strdup_empty(Pool pool, const char *str); /* return NULL if str = "" */
+char *p_strndup(Pool pool, const char *str, unsigned int max_chars);
+char *p_strdup_printf(Pool pool, const char *format, ...) __attr_format__(2, 3);
+char *p_strdup_vprintf(Pool pool, const char *format, va_list args);
+void p_strdup_replace(Pool pool, char **dest, const char *str);
+int *p_intarrdup(Pool pool, const int *arr);
+
+char *p_strconcat(Pool pool, const char *str1, ...); /* NULL terminated */
+
+/* same with temporary memory allocations: */
+const char *t_strdup(const char *str);
+const char *t_strdup_empty(const char *str); /* return NULL if str = "" */
+const char *t_strndup(const char *str, unsigned int max_chars);
+const char *t_strdup_printf(const char *format, ...) __attr_format__(1, 2);
+const char *t_strdup_vprintf(const char *format, va_list args);
+const int *t_intarrdup(const int *arr);
+
+const char *t_strconcat(const char *str1, ...); /* NULL terminated */
+const char *t_strcut(const char *str, char cutchar);
+
+/* Return TRUE if all characters in string are numbers.
+   Stop when `end_char' is found from string. */
+int is_numeric(const char *str, char end_char);
+
+char *str_ucase(char *str);
+char *str_lcase(char *str);
+char *i_strtoken(char **str, char delim);
+void string_remove_escapes(char *str);
+
+/* returns number of items in array */
+int strarray_length(char *const array[]);
+/* return index of item in array, or -1 if not found */
+int strarray_find(char *const array[], const char *item);
+
+/* seprators is an array of separator characters, not a separator string. */
+char * const *t_strsplit(const char *data, const char *separators);
+
+#define t_strjoin(args, separator) \
+	t_strjoin_replace(args, separator, -1, NULL)
+const char *t_strjoin_replace(char *const args[], char separator,
+			      int replacearg, const char *replacedata);
+
+/* INTERNAL */
+const char *temp_strconcat(const char *str1, va_list args,
+			   unsigned int *ret_len);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/temp-mempool.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,470 @@
+/*
+ temp-mempool.c : Memory pool for temporary memory allocations
+
+    Copyright (c) 2001-2002 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <stdlib.h>
+
+#include "lib.h"
+#include "temp-mempool.h"
+
+/* #define TEMP_POOL_DISABLE */
+
+#ifndef TEMP_POOL_DISABLE
+
+/* max. number of bytes to even try to allocate. This is done just to avoid
+   allocating less memory than was actually requested because of integer
+   overflows. */
+#define MAX_ALLOC_SIZE (UINT_MAX - (MEM_ALIGN_SIZE-1))
+
+/* Initial pool size - this should be kept in a size that doesn't exceed
+   in a normal use to keep it fast. */
+#define INITIAL_POOL_SIZE (1024*32)
+
+typedef struct _MemBlock MemBlock;
+typedef struct _MemBlockStack MemBlockStack;
+
+struct _MemBlock {
+	MemBlock *next;
+
+	unsigned int size, left;
+	unsigned char data[1];
+};
+
+/* current_stack contains last t_push()ed blocks. After that new
+   MemBlockStack is created and it's ->prev is set to current_stack. */
+#define MEM_LIST_BLOCK_COUNT 16
+
+struct _MemBlockStack {
+	MemBlockStack *prev;
+
+	MemBlock *block[MEM_LIST_BLOCK_COUNT];
+        int block_space_used[MEM_LIST_BLOCK_COUNT];
+};
+
+static int stack_pos; /* next free position in current_stack->block[] */
+static MemBlockStack *current_stack; /* current stack position */
+static MemBlockStack *unused_stack_list; /* unused stack blocks */
+
+static MemBlock *current_block; /* block currently used for allocation */
+static MemBlock *unused_block; /* largest unused block is kept here */
+
+static int last_alloc_size;
+
+static MemBlock *last_buffer_block;
+static unsigned int last_buffer_size;
+
+int t_push(void)
+{
+        MemBlockStack *stack;
+
+	if (stack_pos == MEM_LIST_BLOCK_COUNT) {
+		/* stack list full */
+		stack_pos = 0;
+		if (unused_stack_list == NULL) {
+			/* allocate new stack */
+			stack = calloc(sizeof(MemBlockStack), 1);
+			if (stack == NULL)
+				i_panic("t_push(): Out of memory");
+		} else {
+			/* use existing unused stack */
+			stack = unused_stack_list;
+			unused_stack_list = unused_stack_list->prev;
+		}
+
+		stack->prev = current_stack;
+		current_stack = stack;
+	}
+
+	/* mark our current position */
+	current_stack->block[stack_pos] = current_block;
+	current_stack->block_space_used[stack_pos] = current_block->left;
+
+        return stack_pos++;
+}
+
+static void free_blocks(MemBlock *block)
+{
+	/* free all the blocks, except if any of them is bigger than
+	   unused_block, replace it */
+	while (block != NULL) {
+		if (unused_block == NULL || block->size > unused_block->size) {
+			free(unused_block);
+			unused_block = block;
+		} else {
+			free(block);
+		}
+
+		block = block->next;
+	}
+}
+
+int t_pop(void)
+{
+	MemBlockStack *stack;
+
+	if (stack_pos == 0)
+		i_panic("t_pop() called with empty stack");
+	stack_pos--;
+
+	/* update the current block */
+	current_block = current_stack->block[stack_pos];
+	current_block->left = current_stack->block_space_used[stack_pos];
+
+	if (current_block->next != NULL) {
+		/* free unused blocks */
+		free_blocks(current_block->next);
+		current_block->next = NULL;
+	}
+
+	if (stack_pos == 0) {
+		/* stack block is now unused, add it to unused list */
+		stack_pos = MEM_LIST_BLOCK_COUNT;
+
+		stack = current_stack;
+		current_stack = stack->prev;
+
+		stack->prev = unused_stack_list;
+		unused_stack_list = stack;
+	}
+
+        return stack_pos;
+}
+
+static MemBlock *mem_block_alloc(unsigned int min_size)
+{
+	MemBlock *block;
+	unsigned int prev_size, alloc_size;
+
+	prev_size = current_block == NULL ? 0 : current_block->size;
+	alloc_size = nearest_power(prev_size + min_size);
+
+	block = malloc(sizeof(MemBlock)-1 + alloc_size);
+	if (block == NULL) {
+		i_panic("mem_block_alloc(): "
+			"Out of memory when allocating %u bytes",
+			sizeof(MemBlock)-1 + alloc_size);
+	}
+	block->size = alloc_size;
+	block->next = NULL;
+
+	return block;
+}
+
+static void *t_malloc_real(unsigned int size, int permanent)
+{
+	MemBlock *block;
+        void *ret;
+
+	if (size == 0)
+		return NULL;
+
+	if (size > MAX_ALLOC_SIZE)
+		i_panic("Trying to allocate too much memory");
+
+	/* reset t_buffer_get() mark - not really needed but makes it easier
+	   to notice if t_malloc() is called between t_buffer_get() and
+	   t_buffer_alloc() */
+        last_buffer_block = NULL;
+
+	/* allocate only aligned amount of memory so alignment comes
+	   always properly */
+	size = MEM_ALIGN(size);
+
+	/* used for t_try_grow() */
+	last_alloc_size = size;
+
+	if (current_block->left >= size) {
+		/* enough space in current block, use it */
+		ret = current_block->data +
+			(current_block->size - current_block->left);
+                if (permanent)
+			current_block->left -= size;
+		return ret;
+	}
+
+	/* current block is full, see if we can use the unused_block */
+	if (unused_block != NULL && unused_block->size >= size) {
+		block = unused_block;
+		unused_block = NULL;
+	} else {
+		block = mem_block_alloc(size);
+	}
+
+	block->left = block->size;
+	if (permanent)
+		block->left -= size;
+	block->next = NULL;
+
+	current_block->next = block;
+	current_block = block;
+
+        return current_block->data;
+}
+
+void *t_malloc(unsigned int size)
+{
+        return t_malloc_real(size, TRUE);
+}
+
+void *t_malloc0(unsigned int size)
+{
+	void *mem;
+
+	mem = t_malloc_real(size, TRUE);
+	memset(mem, 0, size);
+        return mem;
+}
+
+int t_try_grow(void *mem, unsigned int size)
+{
+	/* see if we want to grow the memory we allocated last */
+	if (current_block->data + (current_block->size -
+				   current_block->left -
+				   last_alloc_size) == mem) {
+		/* yeah, see if we can grow */
+		size = MEM_ALIGN(size);
+		if (current_block->left >= size-last_alloc_size) {
+			/* just shrink the available size */
+			current_block->left -= size - last_alloc_size;
+			last_alloc_size = size;
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+void *t_buffer_get(unsigned int size)
+{
+	void *ret;
+
+	ret = t_malloc_real(size, FALSE);
+
+	last_buffer_size = size;
+	last_buffer_block = current_block;
+	return ret;
+}
+
+void *t_buffer_reget(void *buffer, unsigned int size)
+{
+	unsigned int old_size;
+        void *new_buffer;
+
+	old_size = last_buffer_size;
+	if (size <= old_size)
+                return buffer;
+
+	new_buffer = t_buffer_get(size);
+	if (new_buffer != buffer)
+                memcpy(new_buffer, buffer, old_size);
+
+        return new_buffer;
+}
+
+void t_buffer_alloc(unsigned int size)
+{
+	i_assert(last_buffer_block != NULL);
+	i_assert(last_buffer_size >= size);
+	i_assert(current_block->left >= size);
+
+	/* we've already reserved the space, now we just mark it used */
+	t_malloc_real(size, TRUE);
+}
+
+void temp_mempool_init(void)
+{
+	current_block = mem_block_alloc(INITIAL_POOL_SIZE);
+	current_block->left = current_block->size;
+	current_block->next = NULL;
+
+	current_stack = NULL;
+	unused_stack_list = NULL;
+	stack_pos = MEM_LIST_BLOCK_COUNT;
+
+	t_push();
+
+        last_alloc_size = 0;
+
+        last_buffer_block = NULL;
+	last_buffer_size = 0;
+}
+
+void temp_mempool_deinit(void)
+{
+	t_pop();
+
+	if (stack_pos != MEM_LIST_BLOCK_COUNT)
+		i_panic("Missing t_pop() call");
+
+	while (unused_stack_list != NULL) {
+                MemBlockStack *stack = unused_stack_list;
+		unused_stack_list = unused_stack_list->prev;
+
+                free(stack);
+	}
+
+	free(current_block);
+	free(unused_block);
+}
+
+#else
+
+typedef struct _Stack Stack;
+typedef struct _Alloc Alloc;
+
+struct _Stack {
+	Stack *next;
+	Alloc *allocs;
+};
+
+struct _Alloc {
+	Alloc *next;
+	void *mem;
+};
+
+static int stack_counter;
+static Stack *current_stack;
+static void *buffer_mem;
+
+int t_push(void)
+{
+	Stack *stack;
+
+	stack = malloc(sizeof(Stack));
+	stack->allocs = NULL;
+
+	stack->next = current_stack;
+	current_stack = stack;
+	return stack_counter++;
+}
+
+int t_pop(void)
+{
+	Stack *stack;
+	Alloc *alloc;
+
+	stack = current_stack;
+	current_stack = stack->next;
+
+	while (stack->allocs != NULL) {
+		alloc = stack->allocs;
+		stack->allocs = alloc->next;
+
+		free(alloc->mem);
+		free(alloc);
+	}
+
+	free(stack);
+	return --stack_counter;
+}
+
+static void add_alloc(void *mem)
+{
+	Alloc *alloc;
+
+	alloc = malloc(sizeof(Alloc));
+	alloc->mem = mem;
+	alloc->next = current_stack->allocs;
+	current_stack->allocs = alloc;
+
+	if (buffer_mem != NULL) {
+		free(buffer_mem);
+		buffer_mem = NULL;
+	}
+}
+
+void *t_malloc(unsigned int size)
+{
+	void *mem;
+
+	mem = malloc(size);
+	add_alloc(mem);
+	return mem;
+}
+
+void *t_malloc0(unsigned int size)
+{
+	void *mem;
+
+	mem = calloc(size, 1);
+	add_alloc(mem);
+	return mem;
+}
+
+int t_try_grow(void *mem, unsigned int size)
+{
+	void *new_mem;
+
+	new_mem = realloc(mem, size);
+	if (new_mem == mem)
+		return TRUE;
+
+	free(new_mem);
+	return FALSE;
+}
+
+void *t_buffer_get(unsigned int size)
+{
+	buffer_mem = realloc(buffer_mem, size);
+	return buffer_mem;
+}
+
+void *t_buffer_reget(void *buffer, unsigned int size)
+{
+	i_assert(buffer == buffer_mem);
+
+	buffer_mem = realloc(buffer_mem, size);
+	return buffer_mem;
+}
+
+void t_buffer_alloc(unsigned int size)
+{
+	void *mem;
+
+	i_assert(buffer_mem != NULL);
+
+	mem = buffer_mem;
+	buffer_mem = NULL;
+
+	add_alloc(mem);
+}
+
+void temp_mempool_init(void)
+{
+        stack_counter = 0;
+	current_stack = NULL;
+	buffer_mem = NULL;
+
+	t_push();
+}
+
+void temp_mempool_deinit(void)
+{
+	t_pop();
+
+	if (stack_counter != 0)
+		i_panic("Missing t_pop() call");
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/temp-mempool.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,51 @@
+#ifndef __TEMP_MEMPOOL_H
+#define __TEMP_MEMPOOL_H
+
+/* temporary memory allocations. All t_..() allocations between
+   t_push() and t_pop() are free'd after t_pop() is called. */
+int t_push(void);
+int t_pop(void);
+
+/* WARNING: Be careful when using this functions, it's too easy to
+   accidentally save the returned value somewhere permanently.
+
+   You probably should never use this function directly, rather
+   create functions that return 'const xxx*' types and use t_malloc()
+   internally in them. This is a lot safer, since usually compiler
+   warns if you try to place them in xxx*. See strfuncs.c for examples. */
+void *t_malloc(unsigned int size);
+void *t_malloc0(unsigned int size);
+
+/* Try growing allocated memory. Returns TRUE if successful. */
+int t_try_grow(void *mem, unsigned int size);
+
+#define t_new(type, count) \
+	((type *) t_malloc0((unsigned) sizeof(type) * (count)))
+
+/* Returns pointer to temporary buffer you can use. The buffer will be
+   invalid as soon as t_malloc() or t_pop() is called!
+
+   If you wish to grow the buffer, you must give the full wanted size
+   in the size parameter. If return value doesn't point to the same value
+   as last time, you need to memcpy() the data from old buffer the this
+   new one (or do some other trickery). See t_buffer_reget(). */
+#define t_buffer_get_type(type, size) \
+	t_buffer_get(sizeof(type) * (size))
+void *t_buffer_get(unsigned int size);
+
+/* Grow the buffer, memcpy()ing the memory to new location if needed. */
+#define t_buffer_reget_type(buffer, type, size) \
+	t_buffer_reget(buffer, sizeof(type) * (size))
+void *t_buffer_reget(void *buffer, unsigned int size);
+
+/* Make given t_buffer_get()ed buffer permanent. Note that size MUST be
+   less or equal than the size you gave with last t_buffer_get() or the
+   result will be undefined. */
+#define t_buffer_alloc_type(type, size) \
+        t_buffer_alloc(sizeof(type) * (size))
+void t_buffer_alloc(unsigned int size);
+
+void temp_mempool_init(void);
+void temp_mempool_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/temp-string.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,119 @@
+/*
+ temp-string.c : Temporary string
+
+    Copyright (c) 2002 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include "lib.h"
+#include "temp-string.h"
+
+#include <stdio.h>
+
+typedef struct {
+	char *str;
+	unsigned int len;
+
+	unsigned int alloc_size;
+} RealTempString;
+
+TempString *t_string_new(unsigned int initial_size)
+{
+	RealTempString *rstr;
+
+	if (initial_size <= 0)
+		initial_size = 64;
+
+	rstr = t_new(RealTempString, 1);
+	rstr->alloc_size = initial_size;
+	rstr->str = t_malloc(rstr->alloc_size);
+	rstr->str[0] = '\0';
+	return (TempString *) rstr;
+}
+
+static void t_string_inc(TempString *tstr, unsigned int size)
+{
+	RealTempString *rstr = (RealTempString *) tstr;
+	char *str;
+
+	if (rstr->len + size + 1 > rstr->alloc_size) {
+                rstr->alloc_size = nearest_power(rstr->len + size + 1);
+
+		if (!t_try_grow(rstr->str, rstr->alloc_size)) {
+			str = t_malloc(rstr->alloc_size);
+			memcpy(str, rstr->str, rstr->len+1);
+			rstr->str = str;
+		}
+	}
+}
+
+/* Append string/character */
+void t_string_append(TempString *tstr, const char *str)
+{
+	t_string_append_n(tstr, str, strlen(str));
+}
+
+void t_string_append_n(TempString *tstr, const char *str, unsigned int size)
+{
+	i_assert(size < INT_MAX);
+
+	t_string_inc(tstr, size);
+	memcpy(tstr->str + tstr->len, str, size);
+
+	tstr->len += size;
+	tstr->str[tstr->len] = '\0';
+}
+
+void t_string_append_c(TempString *tstr, char chr)
+{
+	t_string_inc(tstr, 1);
+	tstr->str[tstr->len++] = chr;
+	tstr->str[tstr->len] = '\0';
+}
+
+void t_string_printfa(TempString *tstr, const char *fmt, ...)
+{
+	va_list args, args2;
+
+	va_start(args, fmt);
+	VA_COPY(args2, args);
+
+	t_string_inc(tstr, printf_string_upper_bound(fmt, args));
+	tstr->len += vsprintf(tstr->str + tstr->len, fmt, args2);
+
+	va_end(args);
+}
+
+void t_string_erase(TempString *tstr, unsigned int pos, unsigned int len)
+{
+	i_assert(pos < tstr->len && tstr->len - pos >= len);
+
+	memmove(tstr->str + pos + len, tstr->str + pos,
+		tstr->len - pos - len + 1);
+}
+
+void t_string_truncate(TempString *tstr, unsigned int len)
+{
+	i_assert(len <= tstr->len);
+
+	tstr->len = len;
+	tstr->str[tstr->len] = '\0';
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/temp-string.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,32 @@
+#ifndef __TEMP_STRING_H
+#define __TEMP_STRING_H
+
+/* All memory in TempString is allocated from temporary memory pool,
+   so it can't be stored permanently. */
+
+struct _TempString {
+	char *str;
+	unsigned int len;
+};
+
+TempString *t_string_new(unsigned int initial_size);
+
+/* Append string/character */
+void t_string_append(TempString *tstr, const char *str);
+void t_string_append_n(TempString *tstr, const char *str, unsigned int size);
+void t_string_append_c(TempString *tstr, char chr);
+
+/* Insert string/character (FIXME: not implemented) */
+/*void t_string_insert(TempString *tstr, int pos, const char *str);
+void t_string_insert_n(TempString *tstr, int pos, const char *str, int size);
+void t_string_insert_c(TempString *tstr, int pos, char chr);*/
+
+/* Append printf()-like data */
+void t_string_printfa(TempString *tstr, const char *fmt, ...)
+	__attr_format__(2, 3);
+
+/* Erase/truncate */
+void t_string_erase(TempString *tstr, unsigned int pos, unsigned int len);
+void t_string_truncate(TempString *tstr, unsigned int len);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/unlink-directory.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,72 @@
+/*
+ unlink-directory.c : Unlink directory with everything under it.
+
+    Copyright (c) 2002 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include "lib.h"
+#include "unlink-directory.h"
+
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+int unlink_directory(const char *dir)
+{
+	DIR *dirp;
+	struct dirent *d;
+	struct stat st;
+	char path[1024];
+
+	dirp = opendir(dir);
+	if (dirp == NULL)
+		return FALSE;
+
+	while ((d = readdir(dirp)) != NULL) {
+		if (d->d_name[0] == '.' &&
+		    (d->d_name[1] == '\0' ||
+		     (d->d_name[1] == '.' && d->d_name[2] == '\0'))) {
+			/* skip . and .. */
+			continue;
+		}
+
+		i_snprintf(path, sizeof(path), "%s/%s", dir, d->d_name);
+
+		if (unlink(path) == -1) {
+			if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
+				if (!unlink_directory(path))
+					return FALSE;
+			} else {
+				/* so it wasn't a directory, unlink() again
+				   to get correct errno */
+				if (unlink(path) == -1)
+					return FALSE;
+			}
+		}
+	}
+
+	(void)closedir(dirp);
+
+	if (rmdir(dir) == -1)
+		return FALSE;
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/unlink-directory.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,7 @@
+#ifndef __UNLINK_DIRECTORY_H
+#define __UNLINK_DIRECTORY_H
+
+/* Unlink directory with everything under it. Returns TRUE if successful. */
+int unlink_directory(const char *dir);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/unlink-lockfiles.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,77 @@
+/*
+ unlink-lockfiles.c : Utility function for easier deletion of lock files.
+
+    Copyright (c) 2002 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include "lib.h"
+#include "unlink-lockfiles.h"
+
+#include <stdlib.h>
+#include <signal.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+void unlink_lockfiles(const char *dir, const char *pidprefix,
+		      const char *otherprefix, time_t other_min_time)
+{
+	DIR *dirp;
+	struct dirent *d;
+	struct stat st;
+	char path[1024];
+	unsigned int pidlen, otherlen;
+
+	/* check for any invalid access files */
+	dirp = opendir(dir);
+	if (dirp == NULL)
+		return;
+
+	pidlen = pidprefix == NULL ? 0 : strlen(pidprefix);
+	otherlen = otherprefix == NULL ? 0 : strlen(otherprefix);
+
+	while ((d = readdir(dirp)) != NULL) {
+		const char *fname = d->d_name;
+
+		if (pidprefix != NULL &&
+		    strncmp(fname, pidprefix, pidlen) == 0 &&
+		    is_numeric(fname+pidlen, '\0')) {
+			/* found a lock file from our host - see if the PID
+			   is valid (meaning it exists, and the it's with
+			   the same UID as us) */
+			if (kill(atoi(fname+pidlen), 0) == 0)
+				continue; /* valid */
+
+			i_snprintf(path, sizeof(path), "%s/%s", dir, fname);
+			(void)unlink(path);
+		} else if (otherprefix != 0 &&
+			   strncmp(fname, otherprefix, otherlen) == 0) {
+			i_snprintf(path, sizeof(path), "%s/%s", dir, fname);
+			if (stat(path, &st) == 0 &&
+			    st.st_mtime < other_min_time) {
+				(void)unlink(path);
+			}
+		}
+	}
+
+	(void)closedir(dirp);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/unlink-lockfiles.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,7 @@
+#ifndef __UNLINK_LOCKFILES_H
+#define __UNLINK_LOCKFILES_H
+
+void unlink_lockfiles(const char *dir, const char *pidprefix,
+		      const char *otherprefix, time_t other_min_time);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/login/.cvsignore	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,9 @@
+*.la
+*.lo
+*.o
+.deps
+.libs
+Makefile
+Makefile.in
+so_locations
+imap-login
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/login/Makefile.am	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,24 @@
+pkglib_PROGRAMS = imap-login
+
+INCLUDES = \
+	-I$(top_srcdir)/src/lib
+
+imap_login_LDADD = \
+	../lib/liblib.a \
+	$(SSL_LIBS)
+
+imap_login_SOURCES = \
+	auth-connection.c \
+	client.c \
+	client-authenticate.c \
+	main.c \
+	master.c \
+	ssl-proxy.c
+
+noinst_HEADERS = \
+	auth-connection.h \
+	common.h \
+	client.h \
+	client-authenticate.h \
+	master.h \
+	ssl-proxy.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/login/auth-connection.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,352 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "hash.h"
+#include "network.h"
+#include "iobuffer.h"
+#include "auth-connection.h"
+
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+#define MAX_INBUF_SIZE (AUTH_MAX_REQUEST_DATA_SIZE)
+#define MAX_OUTBUF_SIZE \
+	(sizeof(AuthContinuedRequestData) + AUTH_MAX_REQUEST_DATA_SIZE)
+
+struct _AuthConnection {
+	AuthConnection *next;
+
+	char *path;
+	int fd;
+	IO io;
+	IOBuffer *inbuf, *outbuf;
+
+	int auth_process;
+	AuthMethod available_auth_methods;
+        AuthReplyData in_reply;
+
+        HashTable *requests;
+
+	unsigned int init_received:1;
+	unsigned int in_reply_received:1;
+};
+
+AuthMethod available_auth_methods;
+
+static int auth_connects_failed;
+static int request_id_counter;
+static AuthConnection *auth_connections;
+
+static void auth_input(void *user_data, int fd, IO io);
+static void auth_connect_missing(void);
+
+static AuthConnection *auth_connection_new(const char *path)
+{
+        AuthConnection *conn;
+	int fd;
+
+	fd = net_connect_unix(path);
+	if (fd == -1) {
+		i_error("Can't connect to imap-auth at %s: %m", path);
+                auth_connects_failed = TRUE;
+		return NULL;
+	}
+
+	conn = i_new(AuthConnection, 1);
+	conn->path = i_strdup(path);
+	conn->fd = fd;
+	conn->io = io_add(fd, IO_READ, auth_input, conn);
+	conn->inbuf = io_buffer_create(fd, default_pool, IO_PRIORITY_HIGH,
+				       MAX_INBUF_SIZE);
+	conn->outbuf = io_buffer_create(fd, default_pool, IO_PRIORITY_DEFAULT,
+					MAX_OUTBUF_SIZE);
+	conn->requests = hash_create(default_pool, 100, NULL, NULL);
+
+	conn->next = auth_connections;
+	auth_connections = conn;
+	return conn;
+}
+
+static void request_destroy(AuthRequest *request)
+{
+	hash_remove(request->conn->requests, INT_TO_POINTER(request->id));
+	i_free(request);
+}
+
+static void request_abort(AuthRequest *request)
+{
+	request->callback(request, request->conn->auth_process,
+			  AUTH_RESULT_INTERNAL_FAILURE,
+			  "Authentication process died", 0, request->user_data);
+	request_destroy(request);
+}
+
+static void request_hash_destroy(void *key __attr_unused__, void *value,
+				 void *user_data __attr_unused__)
+{
+	request_abort(value);
+}
+
+static void auth_connection_destroy(AuthConnection *conn, int reconnect)
+{
+	AuthConnection **pos;
+	char *path;
+
+	for (pos = &auth_connections; *pos != NULL; pos = &(*pos)->next) {
+		if (*pos == conn) {
+			*pos = conn->next;
+			break;
+		}
+	}
+
+	path = conn->path;
+
+	hash_foreach(conn->requests, request_hash_destroy, NULL);
+	hash_destroy(conn->requests);
+
+	(void)close(conn->fd);
+	io_remove(conn->io);
+	io_buffer_destroy(conn->inbuf);
+	io_buffer_destroy(conn->outbuf);
+	i_free(conn);
+
+	if (reconnect) {
+		auth_connection_new(path);
+		i_free(path);
+	}
+}
+
+static AuthConnection *auth_connection_get(AuthMethod method, unsigned int size,
+					   const char **error)
+{
+	AuthConnection *conn;
+	int found;
+
+	found = FALSE;
+	for (conn = auth_connections; conn != NULL; conn = conn->next) {
+		if ((conn->available_auth_methods & method)) {
+			if (io_buffer_get_space(conn->outbuf, size) != NULL)
+				return conn;
+
+			found = TRUE;
+		}
+	}
+
+	if (!found)
+		*error = "Unsupported authentication method";
+	else {
+		*error = "Authentication servers are busy, wait..";
+		i_warning("Authentication servers are busy");
+	}
+
+	return NULL;
+}
+
+static void update_available_auth_methods(void)
+{
+	AuthConnection *conn;
+
+        available_auth_methods = 0;
+	for (conn = auth_connections; conn != NULL; conn = conn->next)
+                available_auth_methods |= conn->available_auth_methods;
+}
+
+static void auth_handle_init(AuthConnection *conn, AuthInitData *init_data)
+{
+	conn->auth_process = init_data->auth_process;
+	conn->available_auth_methods = init_data->auth_methods;
+	conn->init_received = TRUE;
+
+	update_available_auth_methods();
+}
+
+static void auth_handle_reply(AuthConnection *conn, AuthReplyData *reply_data,
+			      unsigned char *data)
+{
+	AuthRequest *request;
+
+	request = hash_lookup(conn->requests, INT_TO_POINTER(reply_data->id));
+	if (request == NULL) {
+		i_error("BUG: imap-auth sent us reply with unknown ID %u",
+			reply_data->id);
+		return;
+	}
+
+	/* save the returned cookie */
+	memcpy(request->cookie, reply_data->cookie, AUTH_COOKIE_SIZE);
+
+	t_push();
+	request->callback(request, request->conn->auth_process,
+			  reply_data->result, data, reply_data->data_size,
+			  request->user_data);
+	t_pop();
+
+	if (reply_data->result != AUTH_RESULT_CONTINUE)
+		request_destroy(request);
+}
+
+static void auth_input(void *user_data, int fd __attr_unused__,
+		       IO io __attr_unused__)
+{
+	AuthConnection *conn = user_data;
+        AuthInitData init_data;
+	unsigned char *data;
+	unsigned int size;
+
+	switch (io_buffer_read(conn->inbuf)) {
+	case 0:
+		return;
+	case -1:
+		/* disconnected */
+		auth_connection_destroy(conn, TRUE);
+		return;
+	case -2:
+		/* buffer full - can't happen unless imap-auth is buggy */
+		i_error("BUG: imap-auth sent us more than %d bytes of data",
+			MAX_INBUF_SIZE);
+		auth_connection_destroy(conn, TRUE);
+		return;
+	}
+
+	data = io_buffer_get_data(conn->inbuf, &size);
+
+	if (!conn->init_received) {
+		if (size == sizeof(AuthInitData)) {
+			memcpy(&init_data, data, sizeof(AuthInitData));
+			conn->inbuf->skip += sizeof(AuthInitData);
+
+			auth_handle_init(conn, &init_data);
+		} else if (size > sizeof(AuthInitData)) {
+			i_error("BUG: imap-auth sent us too much "
+				"initialization data (%u vs %u)",
+				size, sizeof(AuthInitData));
+			auth_connection_destroy(conn, TRUE);
+		}
+
+		return;
+	}
+
+	if (!conn->in_reply_received) {
+		data = io_buffer_get_data(conn->inbuf, &size);
+		if (size < sizeof(AuthReplyData))
+			return;
+
+		memcpy(&conn->in_reply, data, sizeof(AuthReplyData));
+		data += sizeof(AuthReplyData);
+		size -= sizeof(AuthReplyData);
+		conn->inbuf->skip += sizeof(AuthReplyData);
+		conn->in_reply_received = TRUE;
+	}
+
+	if (size < conn->in_reply.data_size)
+		return;
+
+	/* we've got a full reply */
+	size = conn->in_reply.data_size;
+	conn->inbuf->skip += size;
+	conn->in_reply_received = FALSE;
+
+	auth_handle_reply(conn, &conn->in_reply, data);
+}
+
+int auth_init_request(AuthMethod method, AuthCallback callback,
+		      void *user_data, const char **error)
+{
+	AuthConnection *conn;
+	AuthRequest *request;
+	AuthInitRequestData request_data;
+
+	if (auth_connects_failed)
+		auth_connect_missing();
+
+	conn = auth_connection_get(method, sizeof(AuthInitRequestData), error);
+	if (conn == NULL)
+		return FALSE;
+
+	/* create internal request structure */
+	request = i_new(AuthRequest, 1);
+	request->method = method;
+	request->conn = conn;
+	request->id = ++request_id_counter;
+	request->callback = callback;
+	request->user_data = user_data;
+
+	hash_insert(conn->requests, INT_TO_POINTER(request->id), request);
+
+	/* send request to auth */
+	request_data.type = AUTH_REQUEST_INIT;
+	request_data.method = request->method;
+	request_data.id = request->id;
+	if (io_buffer_send(request->conn->outbuf, &request_data,
+			   sizeof(request_data)) < 0)
+		auth_connection_destroy(request->conn, TRUE);
+	return TRUE;
+}
+
+void auth_continue_request(AuthRequest *request, const unsigned char *data,
+			   unsigned int data_size)
+{
+	AuthContinuedRequestData request_data;
+
+	/* send continued request to auth */
+	memcpy(request_data.cookie, request->cookie, AUTH_COOKIE_SIZE);
+	request_data.type = AUTH_REQUEST_CONTINUE;
+	request_data.id = request->id;
+	request_data.data_size = data_size;
+
+	if (io_buffer_send(request->conn->outbuf, &request_data,
+			   sizeof(request_data)) < 0)
+		auth_connection_destroy(request->conn, TRUE);
+	else if (io_buffer_send(request->conn->outbuf, data, data_size) < 0)
+		auth_connection_destroy(request->conn, TRUE);
+}
+
+static void auth_connect_missing(void)
+{
+	DIR *dirp;
+	struct dirent *dp;
+	struct stat st;
+
+	auth_connects_failed = TRUE;
+
+	/* we're chrooted into */
+	dirp = opendir(".");
+	if (dirp == NULL) {
+		i_error("opendir(\".\") failed when trying to get list of "
+			"authentication servers: %m");
+		return;
+	}
+
+	while ((dp = readdir(dirp)) != NULL) {
+		if (dp->d_name[0] == '.')
+			continue;
+
+		if (stat(dp->d_name, &st) == 0 && S_ISSOCK(st.st_mode)) {
+			if (auth_connection_new(dp->d_name) != NULL)
+				auth_connects_failed = FALSE;
+		}
+	}
+
+	(void)closedir(dirp);
+}
+
+void auth_connection_init(void)
+{
+	auth_connections = NULL;
+	request_id_counter = 0;
+        auth_connects_failed = FALSE;
+
+	auth_connect_missing();
+}
+
+void auth_connection_deinit(void)
+{
+	AuthConnection *next;
+
+	while (auth_connections != NULL) {
+		next = auth_connections->next;
+		auth_connection_destroy(auth_connections, FALSE);
+		auth_connections = next;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/login/auth-connection.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,36 @@
+#ifndef __AUTH_CONNECTION_H
+#define __AUTH_CONNECTION_H
+
+typedef struct _AuthConnection AuthConnection;
+
+/* If result == AUTH_RESULT_INTERNAL_FAILURE, request may be NULL and
+   reply_data_size contains the error message. */
+typedef void (*AuthCallback)(AuthRequest *request, int auth_process,
+			     AuthResult result, const unsigned char *reply_data,
+			     unsigned int reply_data_size, void *user_data);
+
+struct _AuthRequest {
+        AuthMethod method;
+        AuthConnection *conn;
+
+	int id;
+	unsigned char cookie[AUTH_COOKIE_SIZE];
+
+	AuthCallback callback;
+	void *user_data;
+
+	unsigned int init_sent:1;
+};
+
+extern AuthMethod available_auth_methods;
+
+int auth_init_request(AuthMethod method, AuthCallback callback,
+		      void *user_data, const char **error);
+
+void auth_continue_request(AuthRequest *request, const unsigned char *data,
+			   unsigned int data_size);
+
+void auth_connection_init(void);
+void auth_connection_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/login/client-authenticate.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,299 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "base64.h"
+#include "iobuffer.h"
+#include "temp-string.h"
+#include "auth-connection.h"
+#include "client.h"
+#include "client-authenticate.h"
+#include "master.h"
+
+typedef struct {
+	int method;
+	const char *name;
+	int plaintext;
+} AuthMethodDesc;
+
+static AuthMethod auth_methods = 0;
+static char *auth_methods_capability = NULL;
+
+static AuthMethodDesc auth_method_desc[AUTH_METHODS_COUNT] = {
+	{ AUTH_METHOD_PLAIN,		NULL,		TRUE },
+	{ AUTH_METHOD_DIGEST_MD5,	"DIGEST-MD5",	FALSE }
+};
+
+const char *client_authenticate_get_capabilities(void)
+{
+	TempString *str;
+	int i;
+
+	if (auth_methods == available_auth_methods)
+		return auth_methods_capability;
+
+	auth_methods = available_auth_methods;
+	i_free(auth_methods_capability);
+
+	str = t_string_new(128);
+	t_string_append_c(str, ' ');
+
+	for (i = 0; i < AUTH_METHODS_COUNT; i++) {
+		if ((auth_methods & auth_method_desc[i].method) &&
+		    auth_method_desc[i].name != NULL) {
+			if (str->len > 0)
+				t_string_append_c(str, ' ');
+			t_string_append(str, "AUTH=");
+			t_string_append(str, auth_method_desc[i].name);
+		}
+	}
+
+	auth_methods_capability = str->len == 1 ? NULL :
+		i_strdup_empty(str->str);
+	return auth_methods_capability;
+}
+
+static AuthMethodDesc *auth_method_find(const char *name)
+{
+	int i;
+
+	for (i = 0; i < AUTH_METHODS_COUNT; i++) {
+		if (auth_method_desc[i].name != NULL &&
+		    strcasecmp(auth_method_desc[i].name, name) == 0)
+			return &auth_method_desc[i];
+	}
+
+	return NULL;
+}
+
+static void client_auth_abort(Client *client, const char *msg)
+{
+	client->auth_request = NULL;
+
+	client_send_tagline(client, msg != NULL ? msg :
+			    "NO Authentication failed.");
+
+	/* get back to normal client input */
+	if (client->io != NULL)
+		io_remove(client->io);
+	client->io = io_add(client->fd, IO_READ, client_input, client);
+
+	client_unref(client);
+}
+
+static void master_callback(MasterReplyResult result, void *user_data)
+{
+	Client *client = user_data;
+
+	switch (result) {
+	case MASTER_RESULT_SUCCESS:
+		client_destroy(client, "Logged in.");
+		break;
+	case MASTER_RESULT_INTERNAL_FAILURE:
+		client_auth_abort(client, "Internal failure");
+		break;
+	default:
+		client_auth_abort(client, NULL);
+		break;
+	}
+
+	client_unref(client);
+}
+
+static void client_send_auth_data(Client *client, const unsigned char *data,
+				  unsigned int size)
+{
+	const char *base64_data;
+
+	t_push();
+
+	base64_data = base64_encode(data, size);
+	io_buffer_send(client->outbuf, "+ ", 2);
+	io_buffer_send(client->outbuf, base64_data, strlen(base64_data));
+	io_buffer_send(client->outbuf, "\r\n", 2);
+
+	t_pop();
+}
+
+static int auth_callback(AuthRequest *request, int auth_process,
+			 AuthResult result, const unsigned char *reply_data,
+			 unsigned int reply_data_size, void *user_data)
+{
+	Client *client = user_data;
+
+	switch (result) {
+	case AUTH_RESULT_CONTINUE:
+		client->auth_request = request;
+		return TRUE;
+
+	case AUTH_RESULT_SUCCESS:
+		client->auth_request = NULL;
+
+		master_request_imap(client->fd, auth_process, client->tag,
+				    request->cookie, master_callback, client);
+
+		/* disable IO until we're back from master */
+		if (client->io != NULL) {
+			io_remove(client->io);
+			client->io = NULL;
+		}
+		return FALSE;
+
+	case AUTH_RESULT_FAILURE:
+		/* see if we have error message */
+		if (reply_data_size > 0 &&
+		    reply_data[reply_data_size-1] == '\0') {
+			client_auth_abort(client, t_strconcat(
+				"NO Authentication failed: ",
+				(char *) reply_data, NULL));
+		} else {
+			/* default error message */
+			client_auth_abort(client, NULL);
+		}
+		return FALSE;
+	default:
+		client_auth_abort(client, t_strconcat(
+			"NO Authentication failed: ", reply_data, NULL));
+		return FALSE;
+	}
+}
+
+static void login_callback(AuthRequest *request, int auth_process,
+			   AuthResult result, const unsigned char *reply_data,
+			   unsigned int reply_data_size, void *user_data)
+{
+	Client *client = user_data;
+
+	if (auth_callback(request, auth_process, result,
+			  reply_data, reply_data_size, user_data)) {
+		auth_continue_request(request, client->plain_login,
+				      client->plain_login_len);
+
+		i_free(client->plain_login);
+                client->plain_login = NULL;
+	}
+}
+
+int cmd_login(Client *client, const char *user, const char *pass)
+{
+	const char *error;
+	char *p;
+	unsigned int len, user_len, pass_len;
+
+	if (disable_plaintext_auth) {
+		client_send_tagline(client,
+				    "NO Plaintext authentication disabled.");
+		return TRUE;
+	}
+
+	/* code it into user\0user\0password */
+	user_len = strlen(user);
+	pass_len = strlen(pass);
+	len = user_len + 1 + user_len + 1 + pass_len;
+
+	i_free(client->plain_login);
+	client->plain_login = p = i_malloc(len);
+	client->plain_login_len = len;
+
+	memcpy(p, user, user_len); p += user_len; *p++ = '\0';
+	memcpy(p, user, user_len); p += user_len; *p++ = '\0';
+	memcpy(p, pass, pass_len);
+
+	client_ref(client);
+	if (auth_init_request(AUTH_METHOD_PLAIN,
+			      login_callback, client, &error)) {
+		/* don't read any input from client until login is finished */
+		io_remove(client->io);
+		client->io = NULL;
+		return TRUE;
+	} else {
+		client_send_tagline(client, t_strconcat(
+			"NO Login failed: ", error, NULL));
+		client_unref(client);
+		return TRUE;
+	}
+}
+
+static void authenticate_callback(AuthRequest *request, int auth_process,
+				  AuthResult result,
+				  const unsigned char *reply_data,
+				  unsigned int reply_data_size, void *user_data)
+{
+	Client *client = user_data;
+
+	if (auth_callback(request, auth_process, result,
+			  reply_data, reply_data_size, user_data))
+		client_send_auth_data(client, reply_data, reply_data_size);
+}
+
+static void client_auth_input(void *user_data, int fd __attr_unused__,
+			      IO io __attr_unused__)
+{
+	Client *client = user_data;
+	char *line;
+	int size;
+
+	if (!client_read(client))
+		return;
+
+	line = io_buffer_next_line(client->inbuf);
+	if (line == NULL)
+		return;
+
+	if (strcmp(line, "*") == 0) {
+		client_auth_abort(client, "NO Authentication aborted");
+		return;
+	}
+
+	size = base64_decode(line);
+	if (size < 0) {
+		/* failed */
+		client_auth_abort(client, "NO Invalid base64 data");
+		return;
+	}
+
+	if (client->auth_request == NULL) {
+		client_auth_abort(client, "NO Don't send unrequested data");
+		return;
+	}
+
+	auth_continue_request(client->auth_request, (unsigned char *) line,
+			      (unsigned int) size);
+}
+
+int cmd_authenticate(Client *client, const char *method_name)
+{
+	AuthMethodDesc *method;
+	const char *error;
+
+	if (*method_name == '\0')
+		return FALSE;
+
+	method = auth_method_find(method_name);
+	if (method == NULL) {
+		client_send_tagline(client,
+				    "NO Unsupported authentication method.");
+		return TRUE;
+	}
+
+	if (method->plaintext && disable_plaintext_auth) {
+		client_send_tagline(client,
+				    "NO Plaintext authentication disabled.");
+		return TRUE;
+	}
+
+	client_ref(client);
+	if (auth_init_request(method->method, authenticate_callback,
+			      client, &error)) {
+		/* following input data will go to authentication */
+		io_remove(client->io);
+		client->io = io_add(client->fd, IO_READ,
+				    client_auth_input, client);
+	} else {
+		client_send_tagline(client, t_strconcat(
+			"NO Authentication failed: ", error, NULL));
+		client_unref(client);
+	}
+
+	return TRUE;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/login/client-authenticate.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,9 @@
+#ifndef __CLIENT_AUTHENTICATE_H
+#define __CLIENT_AUTHENTICATE_H
+
+const char *client_authenticate_get_capabilities(void);
+
+int cmd_login(Client *client, const char *user, const char *pass);
+int cmd_authenticate(Client *client, const char *method_name);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/login/client.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,352 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "hash.h"
+#include "iobuffer.h"
+#include "client.h"
+#include "client-authenticate.h"
+#include "ssl-proxy.h"
+
+#include <syslog.h>
+
+/* Disconnect client after idling this many seconds */
+#define CLIENT_LOGIN_IDLE_TIMEOUT 60
+
+/* When max. number of simultaneous connections is reached, few of the
+   oldest connections are disconnected. Since we have to go through the whole
+   client hash, it's faster if we disconnect multiple clients. */
+#define CLIENT_DESTROY_OLDEST_COUNT 16
+
+static HashTable *clients;
+static Timeout to_idle;
+
+static int cmd_capability(Client *client)
+{
+	const char *capability;
+
+	capability = t_strconcat("* CAPABILITY " CAPABILITY_STRING,
+				 disable_plaintext_auth && !client->tls ?
+				 " LOGINDISABLED" : "",
+				 client_authenticate_get_capabilities(),
+				 NULL);
+	client_send_line(client, capability);
+	client_send_tagline(client, "OK Capability completed.");
+	return TRUE;
+}
+
+static int cmd_starttls(Client *client)
+{
+#ifdef HAVE_SSL
+	int fd_ssl;
+
+	if (client->tls) {
+		client_send_tagline(client, "BAD TLS is already active.");
+		return TRUE;
+	}
+
+	client_send_tagline(client, "OK Begin TLS negotiation now.");
+	io_buffer_send_flush(client->outbuf);
+
+	fd_ssl = ssl_proxy_new(client->fd);
+	if (fd_ssl != -1) {
+		client->tls = TRUE;
+		client->fd = fd_ssl;
+		client->inbuf->fd = fd_ssl;
+		client->outbuf->fd = fd_ssl;
+	} else {
+		client_send_line(client, " * BYE TLS handehake failed.");
+		client_destroy(client, "TLS handshake failed");
+	}
+#else
+	client_send_tagline(client, "BAD TLS support isn't enabled.");
+#endif
+	return TRUE;
+}
+
+static int cmd_noop(Client *client)
+{
+	client_send_tagline(client, "OK NOOP completed.");
+	return TRUE;
+}
+
+static int cmd_logout(Client *client)
+{
+	client_send_line(client, "* BYE Logging out");
+	client_send_tagline(client, "OK Logout completed.");
+	client_destroy(client, "Logged out");
+	return TRUE;
+}
+
+int client_read(Client *client)
+{
+	switch (io_buffer_read(client->inbuf)) {
+	case -2:
+		/* buffer full */
+		client_send_line(client, "* BYE Input buffer full, aborting");
+		client_destroy(client, "Disconnected: Input buffer full");
+		return FALSE;
+	case -1:
+		/* disconnected */
+		client_destroy(client, "Disconnected");
+		return FALSE;
+	default:
+		/* something was read */
+		return TRUE;
+	}
+}
+
+static char *get_next_arg(char **line)
+{
+	char *start;
+	int quoted;
+
+	while (**line == ' ') (*line)++;
+
+	if (**line == '"') {
+		quoted = TRUE;
+		(*line)++;
+
+		start = *line;
+		while (**line != '\0' && **line != '"') {
+			if (**line == '\\' && (*line)[1] != '\0')
+				(*line)++;
+			(*line)++;
+		}
+
+		if (**line == '"')
+			*(*line)++ = '\0';
+		string_remove_escapes(start);
+	} else {
+		start = *line;
+		while (**line != '\0' && **line != ' ')
+			(*line)++;
+
+		if (**line == ' ')
+			*(*line)++ = '\0';
+	}
+
+	return start;
+}
+
+static int client_command_execute(Client *client, char *line)
+{
+	char *cmd;
+
+	cmd = get_next_arg(&line);
+	str_ucase(cmd);
+
+	if (strcmp(cmd, "LOGIN") == 0) {
+		char *user, *pass;
+
+		user = get_next_arg(&line);
+		pass = get_next_arg(&line);
+		return cmd_login(client, user, pass);
+	}
+	if (strcmp(cmd, "AUTHENTICATE") == 0)
+		return cmd_authenticate(client, get_next_arg(&line));
+	if (strcmp(cmd, "CAPABILITY") == 0)
+		return cmd_capability(client);
+	if (strcmp(cmd, "STARTTLS") == 0)
+		return cmd_starttls(client);
+	if (strcmp(cmd, "NOOP") == 0)
+		return cmd_noop(client);
+	if (strcmp(cmd, "LOGOUT") == 0)
+		return cmd_logout(client);
+
+	return FALSE;
+}
+
+void client_input(void *user_data, int fd __attr_unused__,
+		  IO io __attr_unused__)
+{
+	Client *client = user_data;
+	char *line;
+
+	client->last_input = ioloop_time;
+
+	i_free(client->tag);
+	client->tag = i_strdup("*");
+
+	if (!client_read(client))
+		return;
+
+	client_ref(client);
+	io_buffer_cork(client->outbuf);
+
+	while ((line = io_buffer_next_line(client->inbuf)) != NULL) {
+		/* split the arguments, make sure we have at
+		   least tag + command */
+		i_free(client->tag);
+		client->tag = i_strdup(get_next_arg(&line));
+
+		if (*client->tag == '\0' ||
+		    !client_command_execute(client, line)) {
+			/* error */
+			client_send_tagline(client, "BAD Error in IMAP command "
+					    "received by server.");
+		}
+	}
+
+	if (client_unref(client))
+		io_buffer_send_flush(client->outbuf);
+}
+
+static void client_hash_destroy_oldest(void *key, void *value __attr_unused__,
+				       void *user_data)
+{
+	Client *client = key;
+	Client **destroy_clients = user_data;
+	int i;
+
+	for (i = 0; i < CLIENT_DESTROY_OLDEST_COUNT; i++) {
+		if (destroy_clients[i] == NULL ||
+		    destroy_clients[i]->created > client->created) {
+			memmove(destroy_clients+i+1, destroy_clients+i,
+				sizeof(Client *) *
+				(CLIENT_DESTROY_OLDEST_COUNT - i-1));
+			destroy_clients[i] = client;
+			break;
+		}
+	}
+}
+
+static void client_destroy_oldest(void)
+{
+	Client *destroy_clients[CLIENT_DESTROY_OLDEST_COUNT];
+	int i;
+
+	memset(destroy_clients, 0, sizeof(destroy_clients));
+	hash_foreach(clients, client_hash_destroy_oldest, destroy_clients);
+
+	for (i = 0; i < CLIENT_DESTROY_OLDEST_COUNT; i++) {
+		client_destroy(destroy_clients[i],
+			       "Disconnected: Connection queue full");
+	}
+}
+
+Client *client_create(int fd, IPADDR *ip)
+{
+	Client *client;
+
+	if (max_logging_users > CLIENT_DESTROY_OLDEST_COUNT &&
+	    hash_size(clients) >= max_logging_users) {
+		/* reached max. users count, kill few of the
+		   oldest connections */
+		client_destroy_oldest();
+	}
+
+	client = i_new(Client, 1);
+	client->created = ioloop_time;
+	client->refcount = 1;
+
+	memcpy(&client->ip, ip, sizeof(IPADDR));
+	client->fd = fd;
+	client->io = io_add(fd, IO_READ, client_input, client);
+	client->inbuf = io_buffer_create(fd, default_pool,
+					 IO_PRIORITY_DEFAULT, 8192);
+	client->outbuf = io_buffer_create(fd, default_pool,
+					  IO_PRIORITY_DEFAULT, 1024);
+        client->last_input = ioloop_time;
+	hash_insert(clients, client, client);
+
+	client_send_line(client, "* OK " PACKAGE " ready.");
+	return client;
+}
+
+void client_destroy(Client *client, const char *reason)
+{
+	if (reason != NULL)
+		client_syslog(client, reason);
+
+	hash_remove(clients, client);
+
+	io_buffer_close(client->inbuf);
+	io_buffer_close(client->outbuf);
+
+	if (client->io != NULL) {
+		io_remove(client->io);
+		client->io = NULL;
+	}
+
+	net_disconnect(client->fd);
+	client->fd = -1;
+
+	client_unref(client);
+}
+
+void client_ref(Client *client)
+{
+	client->refcount++;
+}
+
+int client_unref(Client *client)
+{
+	if (--client->refcount > 0)
+		return TRUE;
+
+	io_buffer_destroy(client->inbuf);
+	io_buffer_destroy(client->outbuf);
+
+	i_free(client->tag);
+	i_free(client->plain_login);
+	i_free(client);
+	return FALSE;
+}
+
+void client_send_line(Client *client, const char *line)
+{
+	io_buffer_send(client->outbuf, line, strlen(line));
+	io_buffer_send(client->outbuf, "\r\n", 2);
+}
+
+void client_send_tagline(Client *client, const char *line)
+{
+	client_send_line(client, t_strconcat(client->tag, " ", line, NULL));
+}
+
+void client_syslog(Client *client, const char *text)
+{
+	char host[MAX_IP_LEN];
+
+	if (net_ip2host(&client->ip, host) == -1)
+		host[0] = '\0';
+
+	syslog(LOG_INFO, "%s [%s]", text, host);
+}
+
+static void client_hash_check_idle(void *key, void *value __attr_unused__,
+				   void *user_data __attr_unused__)
+{
+	Client *client = key;
+
+	if (ioloop_time - client->last_input >= CLIENT_LOGIN_IDLE_TIMEOUT) {
+		client_send_line(client, "* BYE Disconnected for inactivity.");
+		client_destroy(client, "Disconnected: Inactivity");
+	}
+}
+
+static void idle_timeout(void *user_data __attr_unused__,
+			 Timeout timeout __attr_unused__)
+{
+	hash_foreach(clients, client_hash_check_idle, NULL);
+}
+
+void clients_init(void)
+{
+	clients = hash_create(default_pool, 128, NULL, NULL);
+	to_idle = timeout_add(1000, idle_timeout, NULL);
+}
+
+static void client_hash_destroy(void *key, void *value __attr_unused__,
+				void *user_data __attr_unused__)
+{
+	client_destroy(key, NULL);
+}
+
+void clients_deinit(void)
+{
+	hash_foreach(clients, client_hash_destroy, NULL);
+	hash_destroy(clients);
+
+	timeout_remove(to_idle);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/login/client.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,42 @@
+#ifndef __CLIENT_H
+#define __CLIENT_H
+
+#include "network.h"
+
+struct _Client {
+	time_t created;
+	int refcount;
+	IPADDR ip;
+
+	int fd;
+	IO io;
+	IOBuffer *inbuf, *outbuf;
+
+	time_t last_input;
+	char *tag;
+
+	char *plain_login;
+	unsigned int plain_login_len;
+
+	AuthRequest *auth_request;
+
+	unsigned int tls:1;
+};
+
+Client *client_create(int fd, IPADDR *ip);
+void client_destroy(Client *client, const char *reason);
+
+void client_ref(Client *client);
+int client_unref(Client *client);
+
+void client_send_line(Client *client, const char *line);
+void client_send_tagline(Client *client, const char *line);
+void client_syslog(Client *client, const char *text);
+
+int client_read(Client *client);
+void client_input(void *user_data, int fd, IO io);
+
+void clients_init(void);
+void clients_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/login/common.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,14 @@
+#ifndef __COMMON_H
+#define __COMMON_H
+
+#include "lib.h"
+#include "../auth/auth-interface.h"
+
+typedef struct _Client Client;
+typedef struct _AuthRequest AuthRequest;
+
+extern IOLoop ioloop;
+extern int disable_plaintext_auth;
+extern unsigned int max_logging_users;
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/login/main.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,143 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "ioloop.h"
+#include "lib-signals.h"
+#include "restrict-access.h"
+#include "auth-connection.h"
+#include "master.h"
+#include "client.h"
+#include "ssl-proxy.h"
+
+#include <stdlib.h>
+#include <syslog.h>
+
+IOLoop ioloop;
+int disable_plaintext_auth;
+unsigned int max_logging_users;
+
+static IO io_imap, io_imaps;
+
+static void sig_quit(int signo __attr_unused__)
+{
+	io_loop_stop(ioloop);
+}
+
+static void login_accept(void *user_data __attr_unused__, int listen_fd,
+			 IO io __attr_unused__)
+{
+	IPADDR addr;
+	int fd;
+
+	fd = net_accept(listen_fd, &addr, NULL);
+	if (fd == -1)
+		return;
+
+	(void)client_create(fd, &addr);
+}
+
+static void login_accept_ssl(void *user_data __attr_unused__, int listen_fd,
+			     IO io __attr_unused__)
+{
+	Client *client;
+	IPADDR addr;
+	int fd, fd_ssl;
+
+	fd = net_accept(listen_fd, &addr, NULL);
+	if (fd == -1)
+		return;
+
+	fd_ssl = ssl_proxy_new(fd);
+	if (fd_ssl == -1)
+		net_disconnect(fd);
+	else {
+		client = client_create(fd_ssl, &addr);
+		client->tls = TRUE;
+	}
+}
+
+static void main_init(void)
+{
+	const char *logfile, *value;
+
+	lib_init_signals(sig_quit);
+
+	logfile = getenv("IMAP_LOGFILE");
+	if (logfile == NULL) {
+		/* open the syslog immediately so chroot() won't
+		   break logging */
+		openlog("imap-login", 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-login");
+		i_set_failure_timestamp_format(DEFAULT_FAILURE_STAMP_FORMAT);
+	}
+
+	disable_plaintext_auth = getenv("DISABLE_PLAINTEXT_AUTH") != NULL;
+
+	value = getenv("MAX_LOGGING_USERS");
+	max_logging_users = value == NULL ? 0 : strtoul(value, NULL, 10);
+
+	/* Initialize SSL proxy before dropping privileges so it can read
+	   the certificate and private key file. */
+	ssl_proxy_init();
+
+	restrict_access_by_env();
+
+	auth_connection_init();
+	master_init();
+	clients_init();
+
+	io_imap = io_imaps = NULL;
+
+	if (net_getsockname(LOGIN_IMAP_LISTEN_FD, NULL, NULL) == 0) {
+		/* we're listening for imap */
+		io_imap = io_add(LOGIN_IMAP_LISTEN_FD, IO_READ,
+				 login_accept, NULL);
+	}
+
+	if (net_getsockname(LOGIN_IMAPS_LISTEN_FD, NULL, NULL) == 0) {
+		/* we're listening for imaps */
+		io_imaps = io_add(LOGIN_IMAPS_LISTEN_FD, IO_READ,
+				  login_accept_ssl, NULL);
+	}
+}
+
+static void main_deinit(void)
+{
+        if (lib_signal_kill != 0)
+		i_warning("Killed with signal %d", lib_signal_kill);
+
+	if (io_imap != NULL) io_remove(io_imap);
+	if (io_imaps != NULL) io_remove(io_imaps);
+
+	clients_deinit();
+	master_deinit();
+	auth_connection_deinit();
+
+	ssl_proxy_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/login/master.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,129 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "ioloop.h"
+#include "network.h"
+#include "fdpass.h"
+#include "master.h"
+
+typedef struct _WaitingRequest WaitingRequest;
+
+struct _WaitingRequest {
+	WaitingRequest *next;
+
+	int id;
+	MasterCallback callback;
+	void *user_data;
+};
+
+static IO io_master;
+static WaitingRequest *requests, **next_request;
+
+static unsigned int master_pos;
+static char master_buf[sizeof(MasterReply)];
+
+static void push_request(int id, MasterCallback callback, void *user_data)
+{
+	WaitingRequest *req;
+
+	req = i_new(WaitingRequest, 1);
+	req->id = id;
+	req->callback = callback;
+	req->user_data = user_data;
+
+	*next_request = req;
+	next_request = &req->next;
+}
+
+static void pop_request(MasterReply *reply)
+{
+	WaitingRequest *req;
+
+	req = requests;
+	if (req == NULL) {
+		i_error("Master sent us unrequested reply for id %d",
+			reply->id);
+		return;
+	}
+
+	if (reply->id != req->id) {
+		i_fatal("Master sent invalid id for reply "
+			"(got %d, expecting %d)", reply->id, req->id);
+	}
+
+	req->callback(reply->result, req->user_data);
+
+	requests = req->next;
+	if (requests == NULL)
+		next_request = &requests;
+
+	i_free(req);
+}
+
+void master_request_imap(int fd, int auth_process, const char *login_tag,
+			 unsigned char cookie[AUTH_COOKIE_SIZE],
+			 MasterCallback callback, void *user_data)
+{
+	MasterRequest req;
+
+	i_assert(fd > 1);
+
+	memset(&req, 0, sizeof(req));
+	req.id = fd;
+	req.auth_process = auth_process;
+	memcpy(req.cookie, cookie, AUTH_COOKIE_SIZE);
+
+	if (strlen(login_tag) >= sizeof(req.login_tag))
+		strcpy(req.login_tag, "*");
+	else
+		strcpy(req.login_tag, login_tag);
+
+	if (fd_send(LOGIN_MASTER_SOCKET_FD,
+		    fd, &req, sizeof(req)) != sizeof(req))
+		i_fatal("fd_send() failed: %m");
+
+	push_request(req.id, callback, user_data);
+}
+
+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 */
+	pop_request((MasterReply *) master_buf);
+	master_pos = 0;
+}
+
+void master_init(void)
+{
+	requests = NULL;
+	next_request = &requests;
+
+        master_pos = 0;
+	io_master = io_add(LOGIN_MASTER_SOCKET_FD, IO_READ, master_input, NULL);
+}
+
+void master_deinit(void)
+{
+	WaitingRequest *next;
+
+	while (requests != NULL) {
+		next = requests->next;
+		i_free(requests);
+		requests = next;
+	}
+	io_remove(io_master);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/login/master.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,16 @@
+#ifndef __MASTER_H
+#define __MASTER_H
+
+#include "../master/master-interface.h"
+
+typedef void (*MasterCallback)(MasterReplyResult result, void *user_data);
+
+/* Request IMAP process for given cookie. */
+void master_request_imap(int fd, int auth_process, const char *login_tag,
+			 unsigned char cookie[AUTH_COOKIE_SIZE],
+			 MasterCallback callback, void *user_data);
+
+void master_init(void);
+void master_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/login/ssl-proxy.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,334 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "ioloop.h"
+#include "network.h"
+#include "ssl-proxy.h"
+
+#ifdef HAVE_SSL
+
+#include <stdlib.h>
+#include <gnutls/gnutls.h>
+
+typedef struct {
+	GNUTLS_STATE state;
+	int fd_ssl, fd_plain;
+	IO io_ssl, io_plain;
+
+	unsigned char outbuf_plain[1024];
+	unsigned int outbuf_pos_plain;
+
+	unsigned int send_left_ssl, send_left_plain;
+} SSLProxy;
+
+#define DH_BITS 1024
+
+const int protocol_priority[] =
+	{ GNUTLS_TLS1, GNUTLS_SSL3, 0 };
+const int kx_priority[] =
+	{ GNUTLS_KX_RSA, GNUTLS_KX_DHE_RSA, 0 };
+const int cipher_priority[] =
+	{ GNUTLS_CIPHER_RIJNDAEL_CBC, GNUTLS_CIPHER_3DES_CBC, 0 };
+const int comp_priority[] =
+	{ GNUTLS_COMP_ZLIB, GNUTLS_COMP_NULL, 0 };
+const int mac_priority[] =
+	{ GNUTLS_MAC_SHA, GNUTLS_MAC_MD5, 0 };
+
+static GNUTLS_CERTIFICATE_SERVER_CREDENTIALS x509_cred;
+static GNUTLS_DH_PARAMS dh_params;
+
+static void ssl_input(void *user_data, int handle, IO io);
+static void plain_input(void *user_data, int handle, IO io);
+static void ssl_proxy_destroy(SSLProxy *proxy);
+
+static int proxy_recv_ssl(SSLProxy *proxy, void *data, unsigned int size)
+{
+	int rcvd;
+
+	rcvd = gnutls_record_recv(proxy->state, data, size);
+	if (rcvd > 0)
+		return rcvd;
+
+	if (rcvd == 0) {
+		/* disconnected */
+		ssl_proxy_destroy(proxy);
+		return -1;
+	}
+
+	if (!gnutls_error_is_fatal(rcvd))
+		return 0;
+
+	/* fatal error occured */
+	i_warning("Error reading from SSL client: %s", gnutls_strerror(rcvd));
+	ssl_proxy_destroy(proxy);
+	return -1;
+}
+
+static int proxy_send_ssl(SSLProxy *proxy, const void *data, unsigned int size)
+{
+	int sent;
+
+	sent = gnutls_record_send(proxy->state, data, size);
+	if (sent >= 0)
+		return sent;
+
+	if (!gnutls_error_is_fatal(sent))
+		return 0;
+
+	/* error occured */
+	i_warning("Error sending to SSL client: %s", gnutls_strerror(sent));
+	ssl_proxy_destroy(proxy);
+	return -1;
+}
+
+static void ssl_proxy_destroy(SSLProxy *proxy)
+{
+	gnutls_deinit(proxy->state);
+
+	(void)net_disconnect(proxy->fd_ssl);
+	(void)net_disconnect(proxy->fd_plain);
+
+	io_remove(proxy->io_ssl);
+	io_remove(proxy->io_plain);
+
+	i_free(proxy);
+}
+
+static void ssl_output(void *user_data, int fd __attr_unused__,
+		       IO io __attr_unused__)
+{
+        SSLProxy *proxy = user_data;
+	int sent;
+
+	sent = net_transmit(proxy->fd_plain,
+			    proxy->outbuf_plain + proxy->outbuf_pos_plain,
+			    proxy->send_left_plain);
+	if (sent < 0) {
+		/* disconnected */
+		ssl_proxy_destroy(proxy);
+		return;
+	}
+
+	proxy->send_left_plain -= sent;
+	proxy->outbuf_pos_plain += sent;
+
+	if (proxy->send_left_plain > 0)
+		return;
+
+	/* everything is sent, start reading again */
+	io_remove(proxy->io_ssl);
+	proxy->io_ssl = io_add(proxy->fd_ssl, IO_READ, ssl_input, proxy);
+}
+
+static void ssl_input(void *user_data, int fd __attr_unused__,
+		      IO io __attr_unused__)
+{
+        SSLProxy *proxy = user_data;
+	int rcvd, sent;
+
+	rcvd = proxy_recv_ssl(proxy, proxy->outbuf_plain,
+			      sizeof(proxy->outbuf_plain));
+	if (rcvd <= 0)
+		return;
+
+	sent = net_transmit(proxy->fd_plain, proxy->outbuf_plain,
+			    (unsigned int) rcvd);
+	if (sent == rcvd)
+		return;
+
+	if (sent < 0) {
+		/* disconnected */
+		ssl_proxy_destroy(proxy);
+		return;
+	}
+
+	/* everything wasn't sent - don't read anything until we've
+	   sent it all */
+        proxy->outbuf_pos_plain = 0;
+	proxy->send_left_plain = rcvd - sent;
+
+	io_remove(proxy->io_ssl);
+	proxy->io_ssl = io_add(proxy->fd_ssl, IO_WRITE, ssl_output, proxy);
+}
+
+static void plain_output(void *user_data, int fd __attr_unused__,
+			 IO io __attr_unused__)
+{
+	SSLProxy *proxy = user_data;
+	int sent;
+
+	/* FIXME: (void*) 1 is horrible kludge, but there's no need for us
+	   to store the data as gnutls does it already, maybe it needes an
+	   api change or some clarification how to do it better.. */
+	sent = proxy_send_ssl(proxy, (void *) 1, proxy->send_left_ssl);
+	if (sent <= 0)
+		return;
+
+	proxy->send_left_ssl -= sent;
+	if (proxy->send_left_ssl > 0)
+		return;
+
+	/* everything is sent, start reading again */
+	io_remove(proxy->io_plain);
+	proxy->io_plain = io_add(proxy->fd_plain, IO_READ, plain_input, proxy);
+}
+
+static void plain_input(void *user_data, int fd __attr_unused__,
+			IO io __attr_unused__)
+{
+	SSLProxy *proxy = user_data;
+	char buf[1024];
+	int rcvd, sent;
+
+	rcvd = net_receive(proxy->fd_plain, buf, sizeof(buf));
+	if (rcvd < 0) {
+		/* disconnected */
+		gnutls_bye(proxy->state, 1);
+		ssl_proxy_destroy(proxy);
+		return;
+	}
+
+	sent = proxy_send_ssl(proxy, buf, (unsigned int) rcvd);
+	if (sent < 0 || sent == rcvd)
+		return;
+
+	/* everything wasn't sent - don't read anything until we've
+	   sent it all */
+	proxy->send_left_ssl = rcvd - sent;
+
+	io_remove(proxy->io_plain);
+	proxy->io_plain = io_add(proxy->fd_ssl, IO_WRITE, plain_output, proxy);
+}
+
+static GNUTLS_STATE initialize_state(void)
+{
+	GNUTLS_STATE state;
+
+	gnutls_init(&state, GNUTLS_SERVER);
+
+	gnutls_protocol_set_priority(state, protocol_priority);
+	gnutls_cipher_set_priority(state, cipher_priority);
+	gnutls_compression_set_priority(state, comp_priority);
+	gnutls_kx_set_priority(state, kx_priority);
+	gnutls_mac_set_priority(state, mac_priority);
+
+	gnutls_cred_set(state, GNUTLS_CRD_CERTIFICATE, x509_cred);
+
+	/*gnutls_certificate_server_set_request(state, GNUTLS_CERT_REQUEST);*/
+
+	gnutls_dh_set_prime_bits(state, DH_BITS);
+	return state;
+}
+
+int ssl_proxy_new(int fd)
+{
+        SSLProxy *proxy;
+	GNUTLS_STATE state;
+	int ret, sfd[2];
+
+	state = initialize_state();
+	gnutls_transport_set_ptr(state, fd);
+
+	net_set_nonblock(fd, FALSE); /* FIXME: blocks! */
+	if ((ret = gnutls_handshake(state)) < 0) {
+		gnutls_deinit(state);
+		return -1;
+	}
+	net_set_nonblock(fd, TRUE);
+
+	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sfd) == -1) {
+		i_error("socketpair() failed: %m");
+		gnutls_deinit(state);
+		return -1;
+	}
+
+	proxy = i_new(SSLProxy, 1);
+	proxy->state = state;
+	proxy->fd_ssl = fd;
+	proxy->fd_plain = sfd[0];
+
+	proxy->io_ssl = io_add(proxy->fd_ssl, IO_READ, ssl_input, proxy);
+	proxy->io_plain = io_add(proxy->fd_plain, IO_READ, plain_input, proxy);
+
+	return sfd[1];
+}
+
+static void generate_dh_primes(void)
+{
+	gnutls_datum prime, generator;
+	int ret;
+
+	/* Generate Diffie Hellman parameters - for use with DHE
+	   kx algorithms. These should be discarded and regenerated
+	   once a day, once a week or once a month. Depends on the
+	   security requirements. */
+	if ((ret = gnutls_dh_params_init(&dh_params)) < 0) {
+		i_fatal("gnutls_dh_params_init() failed: %s",
+			gnutls_strerror(ret));
+	}
+
+	ret = gnutls_dh_params_generate(&prime, &generator, DH_BITS);
+	if (ret < 0) {
+		i_fatal("gnutls_dh_params_generate() failed: %s",
+			gnutls_strerror(ret));
+	}
+
+	ret = gnutls_dh_params_set(dh_params, prime, generator, DH_BITS);
+	if (ret < 0) {
+		i_fatal("gnutls_dh_params_set() failed: %s",
+			gnutls_strerror(ret));
+	}
+
+	free(prime.data);
+	free(generator.data);
+}
+
+void ssl_proxy_init(void)
+{
+	const char *certfile, *keyfile;
+	int ret;
+
+	certfile = getenv("SSL_CERT_FILE");
+	keyfile = getenv("SSL_KEY_FILE");
+
+	if (certfile == NULL)
+		i_fatal("SSL_CERT_FILE environment not set");
+	if (keyfile == NULL)
+		i_fatal("SSL_KEY_FILE environment not set");
+
+	if ((ret = gnutls_global_init() < 0)) {
+		i_fatal("gnu_tls_global_init() failed: %s",
+			gnutls_strerror(ret));
+	}
+
+	if ((ret = gnutls_certificate_allocate_cred(&x509_cred)) < 0) {
+		i_fatal("gnutls_certificate_allocate_cred() failed: %s",
+			gnutls_strerror(ret));
+	}
+
+	ret = gnutls_certificate_set_x509_key_file(x509_cred, certfile, keyfile,
+						   GNUTLS_X509_FMT_PEM);
+	if (ret < 0) {
+		i_fatal("Can't load certificate files %s and %s: %s",
+			certfile, keyfile, gnutls_strerror(ret));
+	}
+
+	generate_dh_primes();
+	gnutls_certificate_set_dh_params(x509_cred, dh_params);
+}
+
+void ssl_proxy_deinit(void)
+{
+	gnutls_certificate_free_cred(x509_cred);
+	gnutls_global_deinit();
+}
+
+#else
+
+/* no SSL support */
+
+int ssl_proxy_new(int fd) { return -1; }
+void ssl_proxy_init(void) {}
+void ssl_proxy_deinit(void) {}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/login/ssl-proxy.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,12 @@
+#ifndef __SSL_PROXY_H
+#define __SSL_PROXY_H
+
+/* establish SSL connection with the given fd, returns a new fd which you
+   must use from now on, or -1 if error occured. Unless -1 is returned,
+   the given fd must be simply forgotten. */
+int ssl_proxy_new(int fd);
+
+void ssl_proxy_init(void);
+void ssl_proxy_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/master/.cvsignore	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,9 @@
+*.la
+*.lo
+*.o
+.deps
+.libs
+Makefile
+Makefile.in
+so_locations
+imap-master
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/master/Makefile.am	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,24 @@
+sbin_PROGRAMS = imap-master
+
+INCLUDES = \
+	-I$(top_srcdir)/src/lib \
+	-DSYSCONFDIR=\""$(sysconfdir)"\" \
+	-DPKG_RUNDIR=\""$(localstatedir)/run/$(PACKAGE)"\" \
+	-DPKG_LIBDIR=\""$(pkglibdir)"\"
+
+imap_master_LDADD = \
+	../lib/liblib.a
+
+imap_master_SOURCES = \
+	auth-process.c \
+	imap-process.c \
+	login-process.c \
+	main.c \
+	settings.c
+
+noinst_HEADERS = \
+	auth-process.h \
+	common.h \
+	login-process.h \
+	master-interface.h \
+	settings.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/master/auth-process.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,329 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "network.h"
+#include "iobuffer.h"
+#include "restrict-access.h"
+#include "auth-process.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <sys/stat.h>
+
+typedef struct _WaitingRequest WaitingRequest;
+
+struct _AuthProcess {
+	AuthProcess *next;
+
+	char *name;
+	pid_t pid;
+	int fd;
+	IO io;
+	IOBuffer *outbuf;
+
+	unsigned int reply_pos;
+	char reply_buf[sizeof(AuthCookieReplyData)];
+
+	WaitingRequest *requests, **next_request;
+};
+
+struct _WaitingRequest {
+        WaitingRequest *next;
+	int id;
+
+	AuthCallback callback;
+	void *user_data;
+};
+
+static Timeout to;
+static AuthProcess *processes;
+
+static void auth_process_destroy(AuthProcess *p);
+
+static void push_request(AuthProcess *process, int id,
+			 AuthCallback callback, void *user_data)
+{
+	WaitingRequest *req;
+
+	req = i_new(WaitingRequest, 1);
+	req->id = id;
+	req->callback = callback;
+	req->user_data = user_data;
+
+	*process->next_request = req;
+	process->next_request = &req->next;
+}
+
+static void pop_request(AuthProcess *process, AuthCookieReplyData *reply)
+{
+	WaitingRequest *req;
+
+	req = process->requests;
+	if (req == NULL) {
+		i_warning("imap-auth %ld sent us unrequested reply for id %d",
+			  (long)process->pid, reply->id);
+		return;
+	}
+
+	if (reply->id != req->id) {
+		i_fatal("imap-auth %ld sent invalid id for reply "
+			"(got %d, expecting %d)",
+			(long)process->pid, reply->id, req->id);
+	}
+
+	/* auth process isn't trusted, validate all data to make sure
+	   it's not trying to exploit us */
+	if (!VALIDATE_STR(reply->user) || !VALIDATE_STR(reply->mail) ||
+	    !VALIDATE_STR(reply->home)) {
+		i_error("auth: Received corrupted data");
+		auth_process_destroy(process);
+		return;
+	}
+
+	process->requests = req->next;
+	if (process->requests == NULL)
+		process->next_request = &process->requests;
+
+	req->callback(reply, req->user_data);
+
+	i_free(req);
+}
+
+static void auth_process_input(void *user_data, int fd, IO io __attr_unused__)
+{
+	AuthProcess *p = user_data;
+	int ret;
+
+	ret = net_receive(fd, p->reply_buf + p->reply_pos,
+			  sizeof(p->reply_buf) - p->reply_pos);
+	if (ret < 0) {
+		/* disconnected */
+		auth_process_destroy(p);
+		return;
+	}
+
+	p->reply_pos += ret;
+	if (p->reply_pos < sizeof(p->reply_buf))
+		return;
+
+	/* reply is now read */
+	pop_request(p, (AuthCookieReplyData *) p->reply_buf);
+	p->reply_pos = 0;
+}
+
+static AuthProcess *auth_process_new(pid_t pid, int fd, const char *name)
+{
+	AuthProcess *p;
+
+	PID_ADD_PROCESS_TYPE(pid, PROCESS_TYPE_AUTH);
+
+	p = i_new(AuthProcess, 1);
+	p->name = i_strdup(name);
+	p->pid = pid;
+	p->fd = fd;
+	p->io = io_add(fd, IO_READ, auth_process_input, p);
+	p->outbuf = io_buffer_create(fd, default_pool, IO_PRIORITY_DEFAULT,
+				     sizeof(AuthCookieRequestData)*100);
+
+	p->next_request = &p->requests;
+
+	p->next = processes;
+	processes = p;
+	return p;
+}
+
+static void auth_process_destroy(AuthProcess *p)
+{
+	AuthProcess **pos;
+	WaitingRequest *next;
+
+	for (pos = &processes; *pos != NULL; pos = &(*pos)->next) {
+		if (*pos == p) {
+			*pos = p->next;
+			break;
+		}
+	}
+
+	for (; p->requests != NULL; p->requests = next) {
+		next = p->requests->next;
+
+		p->requests->callback(NULL, p->requests->user_data);
+		i_free(p->requests);
+	}
+
+	(void)unlink(t_strconcat(set_login_dir, "/", p->name, NULL));
+
+	io_buffer_destroy(p->outbuf);
+	io_remove(p->io);
+	(void)close(p->fd);
+	i_free(p->name);
+	i_free(p);
+}
+
+static pid_t create_auth_process(AuthConfig *config)
+{
+	static const char *argv[] = { NULL, NULL };
+	const char *path;
+	struct passwd *pwd;
+	pid_t pid;
+	int fd[2], listen_fd;
+
+	if ((pwd = getpwnam(config->user)) == NULL)
+		i_fatal("Auth user doesn't exist: %s", config->user);
+
+	/* create communication to process with a socket pair
+	   FIXME: pipe() would work as well, would it be better? */
+	if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1) {
+		i_error("socketpair() failed: %m");
+		return -1;
+	}
+
+	pid = fork();
+	if (pid < 0) {
+		(void)close(fd[0]);
+		(void)close(fd[1]);
+		i_error("fork() failed: %m");
+		return -1;
+	}
+
+	if (pid != 0) {
+		/* master */
+		auth_process_new(pid, fd[0], config->name);
+		(void)close(fd[1]);
+		return pid;
+	}
+
+	/* create socket for listening auth requests from imap-login */
+	path = t_strconcat(set_login_dir, "/", config->name, NULL);
+	(void)unlink(path);
+        (void)umask(0177); /* we want 0600 mode for the socket */
+	listen_fd = net_listen_unix(path);
+	if (listen_fd == -1)
+		i_fatal("Can't listen in UNIX socket %s: %m", path);
+
+	/* set correct permissions */
+	(void)chown(path, set_login_uid, set_login_gid);
+
+	/* move master communication handle to 0 */
+	if (dup2(fd[1], 0) < 0)
+		i_fatal("login: dup2() failed: %m");
+
+	/* move login communication handle to 1 */
+	if (dup2(listen_fd, 1) < 0)
+		i_fatal("login: dup2() failed: %m");
+
+	/* set /dev/null handle into 2, so if something is printed into
+	   stderr it can't go anywhere where it could cause harm */
+	if (dup2(null_fd, 2) < 0)
+		i_fatal("login: dup2() failed: %m");
+
+	(void)close(listen_fd);
+
+	(void)close(fd[0]);
+	(void)close(fd[1]);
+
+	clean_child_process();
+
+	/* setup access environment - needs to be done after
+	   clean_child_process() since it clears environment */
+	restrict_access_set_env(config->user, pwd->pw_uid, pwd->pw_gid,
+				config->chroot);
+
+	/* set other environment */
+	putenv((char *) t_strdup_printf("AUTH_PROCESS=%d", (int) getpid()));
+	putenv((char *) t_strconcat("METHODS=", config->methods, NULL));
+	putenv((char *) t_strconcat("REALMS=", config->realms, NULL));
+	putenv((char *) t_strconcat("USERINFO=", config->userinfo, NULL));
+	putenv((char *) t_strconcat("USERINFO_ARGS=", config->userinfo_args,
+				    NULL));
+	/* hide the path, it's ugly */
+	argv[0] = strrchr(config->executable, '/');
+	if (argv[0] == NULL) argv[0] = config->executable; else argv[0]++;
+
+	execv(config->executable, (char **) argv);
+
+	i_fatal("execv(%s) failed: %m", argv[0]);
+	return -1;
+}
+
+AuthProcess *auth_process_find(int id)
+{
+	AuthProcess *p;
+
+	for (p = processes; p != NULL; p = p->next) {
+		if (p->pid == id)
+			return p;
+	}
+
+	return NULL;
+}
+
+void auth_process_request(AuthProcess *process, int id,
+			  unsigned char cookie[AUTH_COOKIE_SIZE],
+			  AuthCallback callback, void *user_data)
+{
+	AuthCookieRequestData req;
+
+	req.id = id;
+	memcpy(req.cookie, cookie, AUTH_COOKIE_SIZE);
+
+	if (io_buffer_send(process->outbuf, &req, sizeof(req)) < 0)
+		auth_process_destroy(process);
+
+	push_request(process, id, callback, user_data);
+}
+
+static int auth_process_get_count(const char *name)
+{
+	AuthProcess *p;
+	int count = 0;
+
+	for (p = processes; p != NULL; p = p->next) {
+		if (strcmp(p->name, name) == 0)
+			count++;
+	}
+
+	return count;
+}
+
+void auth_processes_cleanup(void)
+{
+	AuthProcess *p;
+
+	for (p = processes; p != NULL; p = p->next)
+		(void)close(p->fd);
+}
+
+static void auth_processes_start_missing(void *user_data __attr_unused__,
+					 Timeout timeout __attr_unused__)
+{
+	AuthConfig *config;
+	int count;
+
+        config = auth_processes_config;
+	for (; config != NULL; config = config->next) {
+		count = auth_process_get_count(config->name);
+		for (; count < config->count; count++)
+			(void)create_auth_process(config);
+	}
+}
+
+void auth_processes_init(void)
+{
+	processes = NULL;
+	to = timeout_add(1000, auth_processes_start_missing, NULL);
+}
+
+void auth_processes_deinit(void)
+{
+	AuthProcess *next;
+
+	timeout_remove(to);
+
+	while (processes != NULL) {
+		next = processes->next;
+		auth_process_destroy(processes);
+                processes = next;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/master/auth-process.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,24 @@
+#ifndef __AUTH_PROCESS_H
+#define __AUTH_PROCESS_H
+
+/* cookie_reply is NULL if some error occured */
+typedef void (*AuthCallback)(AuthCookieReplyData *cookie_reply,
+			     void *user_data);
+
+typedef struct _AuthProcess AuthProcess;
+
+/* Find process for given id */
+AuthProcess *auth_process_find(int id);
+
+/* Request information about given cookie */
+void auth_process_request(AuthProcess *process, int id,
+			  unsigned char cookie[AUTH_COOKIE_SIZE],
+			  AuthCallback callback, void *user_data);
+
+/* Close any fds used by auth processes */
+void auth_processes_cleanup(void);
+
+void auth_processes_init(void);
+void auth_processes_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/master/common.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,47 @@
+#ifndef __COMMON_H
+#define __COMMON_H
+
+#include "lib.h"
+#include "hash.h"
+#include "settings.h"
+
+#include "../auth/auth-interface.h"
+#include "master-interface.h"
+
+enum {
+	PROCESS_TYPE_UNKNOWN,
+	PROCESS_TYPE_AUTH,
+	PROCESS_TYPE_LOGIN,
+	PROCESS_TYPE_IMAP,
+
+	PROCESS_TYPE_MAX
+};
+
+const char *process_type_names[PROCESS_TYPE_MAX];
+
+extern HashTable *pids;
+extern int null_fd, imap_fd, imaps_fd;
+
+/* processes */
+#define PID_GET_PROCESS_TYPE(pid) \
+	POINTER_TO_INT(hash_lookup(pids, INT_TO_POINTER(pid)))
+
+#define PID_ADD_PROCESS_TYPE(pid, type) \
+	hash_insert(pids, INT_TO_POINTER(pid), INT_TO_POINTER(type))
+
+#define PID_REMOVE_PROCESS_TYPE(pid) \
+	hash_remove(pids, INT_TO_POINTER(pid))
+
+void clean_child_process(void);
+
+MasterReplyResult
+create_imap_process(int socket, const char *user, uid_t uid, gid_t gid,
+		    const char *home, int chroot, const char *env[]);
+void imap_process_destroyed(pid_t pid);
+
+/* misc */
+#define VALIDATE_STR(str) \
+	validate_str(str, sizeof(str))
+int validate_str(const char *str, int max_len);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/master/imap-process.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,148 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "restrict-access.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <syslog.h>
+#include <grp.h>
+
+static unsigned int imap_process_count = 0;
+
+static int validate_uid_gid(uid_t uid, gid_t gid)
+{
+	if (uid == 0) {
+		i_error("imap process isn't allowed for root");
+		return FALSE;
+	}
+
+	if (uid != 0 && gid == 0) {
+		i_error("imap process isn't allowed to be in group 0");
+		return FALSE;
+	}
+
+	if (uid < set_first_valid_uid || (set_last_valid_uid != 0 &&
+					  uid > set_last_valid_uid)) {
+		i_error("imap process isn't allowed to use UID %ld",
+			(long) uid);
+		return FALSE;
+	}
+
+	if (gid < set_first_valid_gid || (set_last_valid_gid != 0 &&
+					  gid > set_last_valid_gid)) {
+		i_error("imap process isn't allowed to use "
+			"GID %ld (UID is %ld)", (long) gid, (long) uid);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static int validate_chroot(const char *dir)
+{
+	char *const *chroot_dirs;
+
+	if (*dir == '\0')
+		return TRUE;
+
+	if (set_valid_chroot_dirs == '\0')
+		return FALSE;
+
+	chroot_dirs = t_strsplit(set_valid_chroot_dirs, ":");
+	while (*chroot_dirs != NULL) {
+		if (strncmp(dir, *chroot_dirs, strlen(*chroot_dirs)) == 0)
+			return TRUE;
+		chroot_dirs++;
+	}
+
+	return FALSE;
+}
+
+MasterReplyResult
+create_imap_process(int socket, const char *user, uid_t uid, gid_t gid,
+		    const char *home, int chroot, const char *env[])
+{
+	static char *argv[] = { NULL, "-s", NULL };
+	pid_t pid;
+	int i, j, err;
+
+	if (imap_process_count == set_max_imap_processes) {
+		i_error("Maximum number of imap processes exceeded");
+		return MASTER_RESULT_INTERNAL_FAILURE;
+	}
+
+	if (!validate_uid_gid(uid, gid))
+		return MASTER_RESULT_FAILURE;
+
+	if (chroot && !validate_chroot(home))
+		return MASTER_RESULT_FAILURE;
+
+	pid = fork();
+	if (pid < 0) {
+		i_error("fork() failed: %m");
+		return MASTER_RESULT_INTERNAL_FAILURE;
+	}
+
+	if (pid != 0) {
+		/* master */
+		imap_process_count++;
+		PID_ADD_PROCESS_TYPE(pid, PROCESS_TYPE_IMAP);
+		(void)close(socket);
+		return MASTER_RESULT_SUCCESS;
+	}
+
+	clean_child_process();
+
+	/* move the imap socket into stdin, stdout and stderr fds */
+	for (i = 0; i < 3; i++) {
+		if (dup2(socket, i) < 0) {
+			err = errno;
+			for (j = 0; j < i; j++)
+				(void)close(j);
+			(void)close(socket);
+			i_fatal("imap: dup2() failed: %m");
+		}
+	}
+	(void)close(socket);
+
+	/* setup environment */
+	while (env[0] != NULL && env[1] != NULL) {
+		putenv((char *) t_strconcat(env[0], "=", env[1], NULL));
+		env += 2;
+	}
+
+	putenv((char *) t_strconcat("HOME=", home, NULL));
+
+	if (set_maildir_copy_with_hardlinks)
+		putenv("COPY_WITH_HARDLINKS=1");
+	if (set_maildir_check_content_changes)
+		putenv("CHECK_CONTENT_CHANGES=1");
+	if (umask(set_umask) != set_umask)
+		i_fatal("Invalid umask: %o", set_umask);
+
+	/* setup access environment - needs to be done after
+	   clean_child_process() since it clears environment */
+	restrict_access_set_env(user, uid, gid, chroot ? home : NULL);
+
+	/* hide the path, it's ugly */
+	argv[0] = strrchr(set_imap_executable, '/');
+	if (argv[0] == NULL) argv[0] = set_imap_executable; else argv[0]++;
+
+	execv(set_imap_executable, argv);
+	err = errno;
+
+	for (i = 0; i < 3; i++)
+		(void)close(i);
+
+	i_fatal("execv(%s) failed: %m", set_imap_executable);
+
+	/* not reached */
+	return 0;
+}
+
+void imap_process_destroyed(pid_t pid __attr_unused__)
+{
+	imap_process_count--;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/master/login-process.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,301 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "network.h"
+#include "iobuffer.h"
+#include "fdpass.h"
+#include "restrict-access.h"
+#include "login-process.h"
+#include "auth-process.h"
+#include "master-interface.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+
+typedef struct {
+	int refcount;
+
+	pid_t pid;
+	int fd;
+	IO io;
+	IOBuffer *outbuf;
+	unsigned int destroyed:1;
+} LoginProcess;
+
+typedef struct {
+	LoginProcess *process;
+	int login_id;
+	int auth_id;
+	int fd;
+
+	char login_tag[LOGIN_TAG_SIZE];
+} LoginAuthRequest;
+
+static int auth_id_counter;
+static Timeout to;
+static HashTable *processes = NULL;
+
+static void login_process_destroy(LoginProcess *p);
+static void login_process_unref(LoginProcess *p);
+
+static void auth_callback(AuthCookieReplyData *cookie_reply, void *user_data)
+{
+	const char *env[] = {
+		"MAIL", NULL,
+		"LOGIN_TAG", NULL,
+		NULL
+	};
+	LoginAuthRequest *request = user_data;
+        LoginProcess *process;
+	MasterReply reply;
+
+	env[1] = cookie_reply->mail;
+	env[3] = request->login_tag;
+
+	if (cookie_reply == NULL || !cookie_reply->success)
+		reply.result = MASTER_RESULT_FAILURE;
+	else {
+		reply.result = create_imap_process(request->fd,
+						   cookie_reply->user,
+						   cookie_reply->uid,
+						   cookie_reply->gid,
+						   cookie_reply->home,
+						   cookie_reply->chroot, env);
+	}
+
+	/* reply to login */
+	reply.id = request->login_id;
+
+	process = request->process;
+	if (io_buffer_send(process->outbuf, &reply, sizeof(reply)) < 0)
+		login_process_destroy(process);
+
+	(void)close(request->fd);
+	login_process_unref(process);
+	i_free(request);
+}
+
+static void login_process_input(void *user_data, int fd __attr_unused__,
+				IO io __attr_unused__)
+{
+	LoginProcess *p = user_data;
+	AuthProcess *auth_process;
+	LoginAuthRequest *authreq;
+	MasterRequest req;
+	int client_fd, ret;
+
+	ret = fd_read(p->fd, &req, sizeof(req), &client_fd);
+	if (ret != sizeof(req)) {
+		if (ret == 0) {
+			/* disconnected, ie. the login process died */
+		} else if (ret > 0) {
+			/* req wasn't fully read */
+			i_error("login: fd_read() couldn't read all req");
+		} else {
+			i_error("login: fd_read() failed: %m");
+		}
+
+		login_process_destroy(p);
+		return;
+	}
+
+	/* login process isn't trusted, validate all data to make sure
+	   it's not trying to exploit us */
+	if (!VALIDATE_STR(req.login_tag)) {
+		i_error("login: Received corrupted data");
+		login_process_destroy(p);
+		return;
+	}
+
+	/* ask the cookie from the auth process */
+	authreq = i_new(LoginAuthRequest, 1);
+	p->refcount++;
+	authreq->process = p;
+	authreq->login_id = req.id;
+	authreq->auth_id = ++auth_id_counter;
+	authreq->fd = client_fd;
+	strcpy(authreq->login_tag, req.login_tag);
+
+	auth_process = auth_process_find(req.auth_process);
+	if (auth_process == NULL) {
+		i_error("login: Authentication process %u doesn't exist",
+			req.auth_process);
+		auth_callback(NULL, &authreq);
+	} else {
+		auth_process_request(auth_process, authreq->auth_id, req.cookie,
+				     auth_callback, authreq);
+	}
+}
+
+static LoginProcess *login_process_new(pid_t pid, int fd)
+{
+	LoginProcess *p;
+
+	PID_ADD_PROCESS_TYPE(pid, PROCESS_TYPE_LOGIN);
+
+	p = i_new(LoginProcess, 1);
+	p->refcount = 1;
+	p->pid = pid;
+	p->fd = fd;
+	p->io = io_add(fd, IO_READ, login_process_input, p);
+	p->outbuf = io_buffer_create(fd, default_pool, IO_PRIORITY_DEFAULT,
+				     sizeof(MasterReply)*10);
+
+	hash_insert(processes, INT_TO_POINTER(pid), p);
+	return p;
+}
+
+static void login_process_destroy(LoginProcess *p)
+{
+	if (p->destroyed)
+		return;
+	p->destroyed = TRUE;
+
+	io_buffer_close(p->outbuf);
+	io_remove(p->io);
+	(void)close(p->fd);
+
+	hash_remove(processes, INT_TO_POINTER(p->pid));
+	login_process_unref(p);
+}
+
+static void login_process_unref(LoginProcess *p)
+{
+	if (--p->refcount > 0)
+		return;
+
+	io_buffer_destroy(p->outbuf);
+	i_free(p);
+}
+
+static pid_t create_login_process(void)
+{
+	static const char *argv[] = { NULL, NULL };
+	pid_t pid;
+	int fd[2];
+
+	if (set_login_uid == 0)
+		i_fatal("Login process must not run as root");
+
+	/* create communication to process with a socket pair */
+	if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1) {
+		i_error("socketpair() failed: %m");
+		return -1;
+	}
+
+	pid = fork();
+	if (pid < 0) {
+		(void)close(fd[0]);
+		(void)close(fd[1]);
+		i_error("fork() failed: %m");
+		return -1;
+	}
+
+	if (pid != 0) {
+		/* master */
+		login_process_new(pid, fd[0]);
+		(void)close(fd[1]);
+		return pid;
+	}
+
+	/* move communication handle */
+	if (dup2(fd[1], LOGIN_MASTER_SOCKET_FD) < 0)
+		i_fatal("login: dup2() failed: %m");
+
+	/* move the listen handle */
+	if (dup2(imap_fd, LOGIN_IMAP_LISTEN_FD) < 0)
+		i_fatal("login: dup2() failed: %m");
+
+	/* move the SSL listen handle */
+	if (dup2(imaps_fd, LOGIN_IMAPS_LISTEN_FD) < 0)
+		i_fatal("login: dup2() failed: %m");
+
+	/* imap_fd and imaps_fd are closed by clean_child_process() */
+
+	(void)close(fd[0]);
+	(void)close(fd[1]);
+
+	clean_child_process();
+
+	/* setup access environment - needs to be done after
+	   clean_child_process() since it clears environment */
+	restrict_access_set_env(set_login_user, set_login_uid, set_login_gid,
+				set_login_chroot ? set_login_dir : NULL);
+
+	if (!set_login_chroot) {
+		/* no chrooting, but still change to the directory */
+		if (chdir(set_login_dir) < 0) {
+			i_fatal("chdir(%s) failed: %m",
+				set_login_dir);
+		}
+	}
+
+	if (set_ssl_cert_file != NULL) {
+		putenv((char *) t_strconcat("SSL_CERT_FILE=",
+					    set_ssl_cert_file, NULL));
+	}
+
+	if (set_ssl_key_file != NULL) {
+		putenv((char *) t_strconcat("SSL_KEY_FILE=",
+					    set_ssl_key_file, NULL));
+	}
+
+	if (set_disable_plaintext_auth)
+		putenv("DISABLE_PLAINTEXT_AUTH=1");
+
+	putenv((char *) t_strdup_printf("MAX_LOGGING_USERS=%d",
+					set_max_logging_users));
+
+	/* hide the path, it's ugly */
+	argv[0] = strrchr(set_login_executable, '/');
+	if (argv[0] == NULL) argv[0] = set_login_executable; else argv[0]++;
+
+	execv(set_login_executable, (char **) argv);
+
+	i_fatal("execv(%s) failed: %m", argv[0]);
+	return -1;
+}
+
+static void login_hash_cleanup(void *key __attr_unused__, void *value,
+			       void *user_data __attr_unused__)
+{
+	LoginProcess *p = value;
+
+	(void)close(p->fd);
+}
+
+void login_processes_cleanup(void)
+{
+	hash_foreach(processes, login_hash_cleanup, NULL);
+}
+
+static void login_processes_start_missing(void *user_data __attr_unused__,
+					  Timeout timeout __attr_unused__)
+{
+	/* create max. one process every second, that way if it keeps
+	   dying all the time we don't eat all cpu with fork()ing. */
+	if (hash_size(processes) < set_login_processes_count)
+                (void)create_login_process();
+}
+
+void login_processes_init(void)
+{
+        auth_id_counter = 0;
+        processes = hash_create(default_pool, 128, NULL, NULL);
+	to = timeout_add(1000, login_processes_start_missing, NULL);
+}
+
+static void login_hash_destroy(void *key __attr_unused__, void *value,
+			       void *user_data __attr_unused__)
+{
+	login_process_destroy(value);
+}
+
+void login_processes_deinit(void)
+{
+	timeout_remove(to);
+
+	hash_foreach(processes, login_hash_destroy, NULL);
+	hash_destroy(processes);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/master/login-process.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,9 @@
+#ifndef __CHILD_LOGIN_H
+#define __CHILD_LOGIN_H
+
+void login_processes_cleanup(void);
+
+void login_processes_init(void);
+void login_processes_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/master/main.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,258 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "common.h"
+#include "ioloop.h"
+#include "lib-signals.h"
+#include "network.h"
+
+#include "auth-process.h"
+#include "login-process.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+const char *process_names[PROCESS_TYPE_MAX] = {
+	"unknown",
+	"auth",
+	"login",
+	"imap"
+};
+
+static const char *logfile = NULL;
+static IOLoop ioloop;
+static Timeout to_children;
+
+HashTable *pids;
+int null_fd, imap_fd, imaps_fd;
+
+int validate_str(const char *str, int max_len)
+{
+	int i;
+
+	for (i = 0; i < max_len; i++) {
+		if (str[i] == '\0')
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+void clean_child_process(void)
+{
+	extern char **environ;
+
+	/* remove all environment, we don't need them */
+	if (environ != NULL)
+		*environ = NULL;
+
+	/* set the failure log */
+	if (logfile != NULL)
+		putenv((char *) t_strconcat("IMAP_LOGFILE=", logfile, NULL));
+
+	(void)close(null_fd);
+	(void)close(imap_fd);
+	(void)close(imaps_fd);
+
+	/* close fds for auth/login processes */
+	login_processes_cleanup();
+        auth_processes_cleanup();
+
+	closelog();
+}
+
+static void sig_quit(int signo __attr_unused__)
+{
+	io_loop_stop(ioloop);
+}
+
+static void children_check_timeout(void *user_data __attr_unused__,
+				   Timeout timeout __attr_unused__)
+{
+	const char *process_type_name;
+	pid_t pid;
+	int status, process_type;
+
+	while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
+		/* get the type and remove from hash */
+		process_type = PID_GET_PROCESS_TYPE(pid);
+		PID_REMOVE_PROCESS_TYPE(pid);
+
+		if (process_type == PROCESS_TYPE_IMAP)
+			imap_process_destroyed(pid);
+
+		/* write errors to syslog */
+		process_type_name = process_names[process_type];
+		if (WIFEXITED(status)) {
+			status = WEXITSTATUS(status);
+			if (status != 0) {
+				i_error("child %d (%s) returned error %d",
+					(int)pid, process_type_name, status);
+			}
+		} else if (WIFSIGNALED(status)) {
+			i_error("child %d (%s) killed with signal %d",
+				(int)pid, process_type_name, WTERMSIG(status));
+		}
+	}
+
+	if (pid == -1 && errno != EINTR && errno != ECHILD)
+		i_warning("waitpid() failed: %m");
+}
+
+static IPADDR *resolve_ip(const char *name)
+{
+	IPADDR *ip;
+	int ret, ips_count;
+
+	if (set_imap_listen == NULL || *set_imap_listen == '\0')
+		return NULL;
+
+	ret = net_gethostbyname(name, &ip, &ips_count);
+	if (ret != 0)
+		i_fatal("Can't resolve address: %s", set_imap_listen);
+
+	if (ips_count < 1)
+		i_fatal("No IPs for address: %s", set_imap_listen);
+
+	return ip;
+}
+
+static void open_fds(void)
+{
+	IPADDR *imap_ip, *imaps_ip;
+
+	imap_ip = resolve_ip(set_imap_listen);
+	imaps_ip = resolve_ip(set_imaps_listen);
+
+	if (imaps_ip == NULL && set_imaps_listen == NULL)
+		imaps_ip = imap_ip;
+
+	null_fd = open("/dev/null", O_RDONLY);
+	if (null_fd == -1)
+		i_fatal("Can't open /dev/null: %m");
+
+	imap_fd = set_imap_port == 0 ? dup(null_fd) :
+		net_listen(imap_ip, &set_imap_port);
+	if (imap_fd == -1) {
+		i_fatal("listen(%d) failed: %ms", set_imap_port);
+	}
+
+	imaps_fd = set_ssl_cert_file == NULL || set_ssl_key_file == NULL ||
+		set_imaps_port == 0 ? dup(null_fd) :
+		net_listen(imaps_ip, &set_imaps_port);
+	if (imaps_fd == -1) {
+		i_fatal("listen(%d) failed: %m", set_imaps_port);
+	}
+}
+
+static void main_init(const char *logfile)
+{
+	lib_init_signals(sig_quit);
+
+	/* deny file access from everyone else except owner */
+        (void)umask(0077);
+
+	if (logfile == NULL)
+		logfile = getenv("IMAP_LOGFILE");
+
+	if (logfile == NULL) {
+		/* open the syslog immediately so chroot() won't
+		   break logging */
+		openlog("imap-master", 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-master");
+		i_set_failure_timestamp_format(DEFAULT_FAILURE_STAMP_FORMAT);
+	}
+
+	pids = hash_create(default_pool, 128, NULL, NULL);
+	to_children = timeout_add(100, children_check_timeout, NULL);
+
+	auth_processes_init();
+	login_processes_init();
+}
+
+static void main_deinit(void)
+{
+        if (lib_signal_kill != 0)
+		i_warning("Killed with signal %d", lib_signal_kill);
+
+	login_processes_deinit();
+	auth_processes_deinit();
+
+	timeout_remove(to_children);
+
+	(void)close(null_fd);
+	(void)close(imap_fd);
+	(void)close(imaps_fd);
+
+	hash_destroy(pids);
+	closelog();
+}
+
+static void daemonize(void)
+{
+	pid_t pid;
+
+	pid = fork();
+	if (pid < 0)
+		i_fatal("fork() failed: %m");
+
+	if (pid != 0)
+		_exit(0);
+}
+
+int main(int argc, char *argv[])
+{
+	/* parse arguments */
+	const char *configfile = SYSCONFDIR "/" PACKAGE ".conf";
+	int foreground = FALSE;
+	int i;
+
+	lib_init();
+
+	for (i = 1; i < argc; i++) {
+		if (strcmp(argv[i], "-F") == 0) {
+			/* foreground */
+			foreground = TRUE;
+		} else if (strcmp(argv[i], "-c") == 0) {
+			/* config file */
+			i++;
+			if (i == argc) i_fatal("Missing config file argument");
+			configfile = argv[i];
+		} else if (strcmp(argv[i], "-l") == 0) {
+			/* log file */
+			i++;
+			if (i == argc) i_fatal("Missing log file argument");
+			logfile = argv[i];
+		} else {
+			i_fatal("Unknown argument: %s", argv[1]);
+		}
+	}
+
+	/* read and verify settings before forking */
+	settings_read(configfile);
+	open_fds();
+
+	if (!foreground)
+		daemonize();
+
+	ioloop = io_loop_create();
+
+	main_init(logfile);
+        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/master/master-interface.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,32 @@
+#ifndef __MASTER_INTERFACE_H
+#define __MASTER_INTERFACE_H
+
+#include "../auth/auth-interface.h"
+
+#define LOGIN_TAG_SIZE 32
+
+#define LOGIN_MASTER_SOCKET_FD 0
+#define LOGIN_IMAP_LISTEN_FD 1
+#define LOGIN_IMAPS_LISTEN_FD 2
+
+typedef enum {
+	MASTER_RESULT_INTERNAL_FAILURE,
+	MASTER_RESULT_SUCCESS,
+	MASTER_RESULT_FAILURE
+} MasterReplyResult;
+
+typedef struct {
+	int id;
+
+	int auth_process;
+	unsigned char cookie[AUTH_COOKIE_SIZE];
+
+	char login_tag[LOGIN_TAG_SIZE];
+} MasterRequest;
+
+typedef struct {
+	int id;
+        MasterReplyResult result;
+} MasterReply;
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/master/settings.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,335 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "iobuffer.h"
+#include "settings.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <sys/stat.h>
+
+typedef enum {
+	SET_STR,
+	SET_INT,
+	SET_BOOL
+} SettingType;
+
+typedef struct {
+	const char *name;
+	SettingType type;
+	void *ptr;
+} Setting;
+
+static Setting settings[] = {
+	{ "login_executable",	SET_STR, &set_login_executable },
+	{ "login_user",		SET_STR, &set_login_user },
+	{ "login_dir",		SET_STR, &set_login_dir },
+	{ "login_chroot",	SET_BOOL,&set_login_chroot },
+	{ "login_processes_count", SET_INT, &set_login_processes_count },
+
+	{ "max_logging_users",	SET_INT, &set_max_logging_users },
+	{ "imap_executable",	SET_STR, &set_imap_executable },
+	{ "valid_chroot_dirs",	SET_STR, &set_valid_chroot_dirs },
+	{ "max_imap_processes",	SET_INT, &set_max_imap_processes },
+	{ "imap_listen",	SET_STR, &set_imap_listen },
+	{ "imaps_listen",	SET_STR, &set_imaps_listen },
+	{ "imap_port",		SET_INT, &set_imap_port },
+	{ "imaps_port",		SET_INT, &set_imaps_port },
+	{ "ssl_cert_file",	SET_STR, &set_ssl_cert_file },
+	{ "ssl_key_file",	SET_STR, &set_ssl_key_file },
+	{ "disable_plaintext_auth",
+				SET_BOOL,&set_disable_plaintext_auth },
+	{ "first_valid_uid",	SET_INT, &set_first_valid_uid },
+	{ "last_valid_uid",	SET_INT, &set_last_valid_uid },
+	{ "first_valid_gid",	SET_INT, &set_first_valid_gid },
+	{ "last_valid_gid",	SET_INT, &set_last_valid_gid },
+	{ "maildir_copy_with_hardlinks",
+				SET_BOOL,&set_maildir_copy_with_hardlinks },
+	{ "maildir_check_content_changes",
+				SET_BOOL,&set_maildir_check_content_changes },
+	{ "umask",		SET_INT, &set_umask },
+
+	{ NULL, 0, NULL }
+};
+
+/* login */
+char *set_login_executable = PKG_LIBDIR "/imap-login";
+char *set_login_user = "imapd";
+char *set_login_dir = PKG_RUNDIR;
+
+int set_login_chroot = TRUE;
+unsigned int set_login_processes_count = 1;
+unsigned int set_max_logging_users = 256;
+
+uid_t set_login_uid; /* generated from set_login_user */
+gid_t set_login_gid; /* generated from set_login_user */
+
+/* imap */
+char *set_imap_executable = PKG_LIBDIR "/imap";
+char *set_valid_chroot_dirs = NULL;
+unsigned int set_max_imap_processes = 1024;
+
+char *set_imap_listen = NULL;
+char *set_imaps_listen = NULL;
+unsigned int set_imap_port = 143;
+unsigned int set_imaps_port = 993;
+
+char *set_ssl_cert_file = NULL;
+char *set_ssl_key_file = NULL;
+int set_disable_plaintext_auth = FALSE;
+
+unsigned int set_first_valid_uid = 500, set_last_valid_uid = 0;
+unsigned int set_first_valid_gid = 1, set_last_valid_gid = 0;
+
+int set_maildir_copy_with_hardlinks = FALSE;
+int set_maildir_check_content_changes = FALSE;
+unsigned int set_umask = 0077;
+
+/* auth */
+AuthConfig *auth_processes_config = NULL;
+
+static void get_login_uid(void)
+{
+	struct passwd *pw;
+
+	if ((pw = getpwnam(set_login_user)) == NULL)
+		i_fatal("Login user doesn't exist: %s", set_login_user);
+
+	set_login_uid = pw->pw_uid;
+	set_login_gid = pw->pw_gid;
+}
+
+static void settings_initialize(void)
+{
+	Setting *set;
+
+	/* strdup() all default settings */
+	for (set = settings; set->name != NULL; set++) {
+		if (set->type == SET_STR) {
+			char **str = set->ptr;
+			*str = i_strdup(*str);
+		}
+	}
+}
+
+static void settings_verify(void)
+{
+	get_login_uid();
+
+	if (access(set_login_executable, X_OK) == -1) {
+		i_fatal("Can't use login executable %s: %m",
+			set_login_executable);
+	}
+
+	if (access(set_imap_executable, X_OK) == -1) {
+		i_fatal("Can't use imap executable %s: %m",
+			set_imap_executable);
+	}
+
+	/* since it's under /var/run by default, it may have been deleted */
+	if (mkdir(set_login_dir, 0700) == 0)
+		(void)chown(set_login_dir, set_login_uid, set_login_gid);
+	if (access(set_login_dir, X_OK) == -1)
+		i_fatal("Can't access login directory %s: %m", set_login_dir);
+
+	if (set_login_processes_count < 1)
+		i_fatal("login_processes_count must be at least 1");
+	if (set_first_valid_uid < set_last_valid_uid)
+		i_fatal("first_valid_uid can't be larger than last_valid_uid");
+	if (set_first_valid_gid < set_last_valid_gid)
+		i_fatal("first_valid_gid can't be larger than last_valid_gid");
+}
+
+static AuthConfig *auth_config_new(const char *name)
+{
+	AuthConfig *auth;
+
+	auth = i_new(AuthConfig, 1);
+	auth->name = i_strdup(name);
+	auth->executable = i_strdup(PKG_LIBDIR "/imap-auth");
+	auth->count = 1;
+
+	auth->next = auth_processes_config;
+        auth_processes_config = auth;
+	return auth;
+}
+
+static const char *parse_new_auth(const char *name)
+{
+	AuthConfig *auth;
+
+	for (auth = auth_processes_config; auth != NULL; auth = auth->next) {
+		if (strcmp(auth->name, name) == 0) {
+			return "Authentication process already exists "
+				"with the same name";
+		}
+	}
+
+	(void)auth_config_new(name);
+	return NULL;
+}
+
+static const char *parse_auth(const char *key, const char *value)
+{
+	AuthConfig *auth = auth_processes_config;
+	const char *p;
+	char **ptr;
+
+	if (auth == NULL)
+		return "Authentication process name not defined yet";
+
+	/* check the easy string values first */
+	if (strcmp(key, "auth_methods") == 0)
+		ptr = &auth->methods;
+	else if (strcmp(key, "auth_realms") == 0)
+		ptr = &auth->realms;
+	else if (strcmp(key, "auth_executable") == 0)
+		ptr = &auth->executable;
+	else if (strcmp(key, "auth_user") == 0)
+		ptr = &auth->user;
+	else if (strcmp(key, "auth_chroot") == 0)
+		ptr = &auth->chroot;
+	else
+		ptr = NULL;
+
+	if (ptr != NULL) {
+		i_strdup_replace(ptr, value);
+		return NULL;
+	}
+
+	if (strcmp(key, "auth_userinfo") == 0) {
+		/* split it into userinfo + userinfo_args */
+		for (p = value; *p != ' ' && *p != '\0'; )
+			p++;
+
+		i_free(auth->userinfo);
+		auth->userinfo = i_strndup(value, (unsigned int) (p-value));
+
+		while (*p == ' ') p++;
+
+		i_free(auth->userinfo_args);
+		auth->userinfo_args = i_strdup(p);
+		return NULL;
+	}
+
+	if (strcmp(key, "auth_count") == 0) {
+		if (!sscanf(value, "%i", &auth->count))
+			return t_strconcat("Invalid number: ", value, NULL);
+		return NULL;
+	}
+
+
+	return t_strconcat("Unknown setting: ", key, NULL);
+}
+
+static const char *parse_setting(const char *key, const char *value)
+{
+	Setting *set;
+
+	if (strcmp(key, "auth") == 0)
+		return parse_new_auth(value);
+	if (strncmp(key, "auth_", 5) == 0)
+		return parse_auth(key, value);
+
+	for (set = settings; set->name != NULL; set++) {
+		if (strcmp(set->name, key) == 0) {
+			switch (set->type) {
+			case SET_STR:
+				i_strdup_replace((char **) set->ptr, value);
+				break;
+			case SET_INT:
+				/* use %i so we can handle eg. 0600
+				   as octal value with umasks */
+				if (!sscanf(value, "%i", (int *) set->ptr))
+					return t_strconcat("Invalid number: ",
+							   value, NULL);
+				break;
+			case SET_BOOL:
+				if (strcasecmp(value, "yes") == 0)
+					*((int *) set->ptr) = TRUE;
+				else if (strcasecmp(value, "no") == 0)
+					*((int *) set->ptr) = FALSE;
+				else
+					return t_strconcat("Invalid boolean: ",
+							   value, NULL);
+				break;
+			}
+			return NULL;
+		}
+	}
+
+	return t_strconcat("Unknown setting: ", key, NULL);
+}
+
+#define IS_WHITE(c) ((c) == ' ' || (c) == '\t')
+
+void settings_read(const char *path)
+{
+	IOBuffer *inbuf;
+	const char *errormsg;
+	char *line, *key, *p;
+	int fd, linenum;
+
+        settings_initialize();
+
+	fd = open(path, O_RDONLY);
+	if (fd == -1)
+		i_fatal("Can't open configuration file %s: %m", path);
+
+	linenum = 0;
+	inbuf = io_buffer_create_file(fd, default_pool, 2048);
+	for (;;) {
+		line = io_buffer_next_line(inbuf);
+		if (line == NULL) {
+			if (io_buffer_read(inbuf) <= 0)
+				break;
+                        continue;
+		}
+		linenum++;
+
+		/* skip whitespace */
+		while (IS_WHITE(*line))
+			line++;
+
+		/* ignore comments or empty lines */
+		if (*line == '#' || *line == '\0')
+			continue;
+
+		/* all lines must be in format "key = value" */
+		key = line;
+		while (!IS_WHITE(*line) && *line != '\0')
+			line++;
+		if (IS_WHITE(*line)) {
+			*line++ = '\0';
+			while (IS_WHITE(*line)) line++;
+		}
+
+		if (*line != '=') {
+			errormsg = "Missing value";
+		} else {
+			/* skip whitespace after '=' */
+			*line++ = '\0';
+			while (IS_WHITE(*line)) line++;
+
+			/* skip trailing whitespace */
+			p = line + strlen(line);
+			while (p > line && IS_WHITE(p[-1]))
+				p--;
+			*p = '\0';
+
+			errormsg = parse_setting(key, line);
+		}
+
+		if (errormsg != NULL) {
+			i_fatal("Error in configuration file %s line %d: %s",
+				path, linenum, errormsg);
+		}
+	};
+
+	io_buffer_destroy(inbuf);
+	(void)close(fd);
+
+        settings_verify();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/master/settings.h	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,57 @@
+#ifndef __SETTINGS_H
+#define __SETTINGS_H
+
+/* login */
+extern char *set_login_executable;
+extern char *set_login_user;
+extern char *set_login_dir;
+extern int set_login_chroot;
+extern unsigned int set_login_processes_count;
+extern unsigned int set_max_logging_users;
+
+extern uid_t set_login_uid;
+extern gid_t set_login_gid;
+
+/* imap */
+extern char *set_imap_executable;
+extern char *set_valid_chroot_dirs;
+extern unsigned int set_max_imap_processes;
+
+extern char *set_imap_listen;
+extern char *set_imaps_listen;
+extern unsigned int set_imap_port;
+extern unsigned int set_imaps_port;
+
+extern char *set_ssl_cert_file;
+extern char *set_ssl_key_file;
+extern int set_disable_plaintext_auth;
+
+extern unsigned int set_first_valid_uid, set_last_valid_uid;
+extern unsigned int set_first_valid_gid, set_last_valid_gid;
+
+extern int set_maildir_copy_with_hardlinks;
+extern int set_maildir_check_content_changes;
+extern unsigned int set_umask;
+
+/* auth */
+typedef struct _AuthConfig AuthConfig;
+
+struct _AuthConfig {
+	AuthConfig *next;
+
+	char *name;
+	char *methods;
+	char *realms;
+	char *userinfo, *userinfo_args;
+	char *executable;
+	char *user;
+	char *chroot;
+
+	int count;
+};
+
+extern AuthConfig *auth_processes_config;
+
+void settings_read(const char *path);
+
+#endif