Mercurial > dovecot > original-hg > dovecot-1.2
changeset 0:3b1985cbc908 HEAD
Initial revision
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(©, &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